diff --git a/.gemini/commands/cr/ios/extend-histogram.toml b/.gemini/commands/cr/ios/extend-histogram.toml
new file mode 100644
index 0000000..b220508f
--- /dev/null
+++ b/.gemini/commands/cr/ios/extend-histogram.toml
@@ -0,0 +1,66 @@
+description = "Extends the expiration date of a Bling UMA histogram for an additional year."
+prompt = """
+ # Task: Extend a Bling UMA histgram's expiration date by one year.
+
+You are an expert iOS software engineer for the Chromium project. Your
+task is to extend a UMA histogram's expiration date by one year and
+optionally upload a changelist (CL) with the fix.
+
+**The user's command is appended below your instructions.** It will
+contain the histogram's name and the CrBug number that will be attached
+ to the optional CL's description.
+
+## Workflow:
+
+1.  **Parse Arguments:**
+
+    *   Identify the required `<histogram_name>`, required `<bug_number>`,
+        and optional `<conditions>` from the user's input.
+    *   Example input: `/cr:ios:extend-histogram testMyFeature 12345678`
+    *   **If you can't parse all the required arguments:** Do not proceed with
+         this task and print out the missing arguments to the user.
+
+2.  **Locate and read the histogram file:**
+
+    *   Use codesearch to find the file containing the `<histogram_name>`. Do not use
+        any language filter which are too strict.
+
+3.  **Determine and Histogram Extension Strategy:**
+
+    *   The histogram file follows XML syntax rules.
+    *   Extract the date from the `expires_after` XML attribute. The date
+        value will be a string following the format 'YYYY-DD-MM', where
+        the year is the first value followed by a dash. The second value
+        is the month followed by a dash. The final value is the day.
+    *   Extract the year from the date and then increase the year by one.
+
+4.  **Make the code changes:**
+
+    *   Switch to a new branch: `git new-branch
+        extend-histogram-<histogram_name>` Do not use a branch name
+        that exceeds 20 characters so feel free to shorten the `<histogram_name>` if
+        needed.
+    *   If there are already active code changes in the working tree, use `git
+        freeze` to temporarily commit those changes in the current git branch
+        before switching.
+    *   Apply the histogram extension change to the histogram file.
+    *   Format the changes with `git cl format`.
+    *   Add the file to git and commit the change. The commit message should be:
+        ``` [iOS] Extend UMA Histogram <histogram_name>
+
+        Bug: <bug_number> ```
+
+        It is **REALLY IMPORTANT** to include the `Bug: <bug_number>` line in the
+        commit message.
+
+5.  **(Optional) Create and Upload a CL:**
+
+    *   First, ask the user if they want to upload the changelist for review. Do
+        not upload a cl if the answer is no.
+    *   If yes, upload for review: `git cl upload -f -d -m "..."`
+
+Execute these steps methodically. Ask for clarification if any of the user's
+inputs are ambiguous.
+
+"""
+
diff --git a/AUTHORS b/AUTHORS
index 041e1ef..2384448 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -1426,6 +1426,7 @@
 Siddharth Bagai <b.siddharth@samsung.com>
 Siddharth Shankar <funkysidd@gmail.com>
 Siddhartha Barman Joy <siddhartha.j@samsung.com>
+Sidhin S Thomas <sidhin.thomas@gmail.com>
 Simeon Kuran <simeon.kuran@gmail.com>
 Simon Arlott <simon.arlott@gmail.com>
 Simon Cadman <simon@cadman.uk>
diff --git a/DEPS b/DEPS
index 0b9eb60e..8e1321db 100644
--- a/DEPS
+++ b/DEPS
@@ -308,11 +308,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'src_internal_revision': '7dfea0b6b4f4cdf12c2f7c5df4790b03d0be142c',
+  'src_internal_revision': '20987331c080610f354e4a0a7fbd4a5b71fd6624',
   # 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': 'a0de596ec9d554be6b39847634a2287dde4d7fc0',
+  'skia_revision': '4371ed0ce49e08ac80bca3f997b72a11f4557a21',
   # 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,11 +320,11 @@
   # 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': 'a6c4431f99175f7dcc7effc7dcad3801aae2585c',
+  'angle_revision': 'c4fe7abe14469bafce44bc4e9502e84c458d4801',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
-  'swiftshader_revision': '498a6f760dea6cdda0302406c49b8fbb09b9af92',
+  'swiftshader_revision': '04fbb7daf5a53689e067190e7ef1047c5d49e292',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # 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': 'b94d71f87ff943a617d77f3ff029f9a01a1ec6bc',
+  'boringssl_revision': '4faf6b1e463c1f9e8cb596b1bfcd8b27454bea9a',
   # 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.
@@ -376,11 +376,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling catapult
   # and whatever else without interference from each other.
-  'catapult_revision': '4646f4c0fa1f6daa506d06153beae34eb7d87265',
+  'catapult_revision': 'a202c86635d505fa893d73bad1e220a66bb644e6',
   # 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': '77240be1ccd1dc99b73be332223a8856641a2073',
+  'crossbench_revision': '11e5da1e630acf5dc2c8185eca8345645da4d2c8',
   # 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': 'd962481a57f0c1a6788204524f8ddc6729e5b6be',
+  'devtools_frontend_revision': '7d26bddc0d06cd6a294663fd23d0918e6ad33475',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libprotobuf-mutator
   # and whatever else without interference from each other.
@@ -424,7 +424,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'dawn_revision': '0223916e3a572e7c264d0b8da5f077556e660871',
+  'dawn_revision': '518caee86914cb4346f7f82a90c3ab99b7f84532',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -508,7 +508,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'libunwind_revision':    '7ff36f1e358ffb63e25f27a1b5e8e0c52e15c49c',
+  'libunwind_revision':    'c65639bf792928e0d38aed822dc34d3e72066a6c',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -528,7 +528,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.
-  'llvm_libc_revision':    'f950018bf8a84246c899b9ac360a271064577d42',
+  'llvm_libc_revision':    'fe97633934c21742a74962d52c17b3bcba7ad824',
   # 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.
@@ -1195,7 +1195,7 @@
       'packages': [
           {
               'package': 'chromium/chrome/android/orderfiles/arm',
-              'version': 'Cd6R3y_NZDV18Dfx1IMgNoA-vGiMjsRvJ49DedDa3fQC',
+              'version': 'AkxesWEFFxC2Z1mEb93py9fkzE2Cy9mDW47vk7226nQC',
           },
       ],
       'condition': 'checkout_android and non_git_source',
@@ -1206,7 +1206,7 @@
       'packages': [
           {
               'package': 'chromium/chrome/android/orderfiles/arm64',
-              'version': 'Uk7g2ZVG-QONBfs-dto4DKCvg3LtADkcU3ynJX4aEUsC',
+              'version': '2it76cjLdPaCbfU_n1nHToxGA9Yqb6oDjoMjvjkBkeUC',
           },
       ],
       'condition': 'checkout_android and non_git_source',
@@ -1217,7 +1217,7 @@
       'packages': [
           {
               'package': 'chromium/android_webview/tools/orderfiles/arm',
-              'version': 'YlmM_2rHDlVRSm6s4EAXiWBnXdJ_mH8BqqGvcIyj9kQC',
+              'version': '_4uvw-BnNaJItX2a3LgLJqofccyB-mMNAYHSLC8MXL8C',
           },
       ],
       'condition': 'checkout_android and non_git_source',
@@ -1613,7 +1613,7 @@
     'packages': [
       {
         'package': 'chromium/chrome/test/data/variations/cipd',
-        'version': '4EF7YmLI1HGTj3vn3OxThNvNHDyCj8A7eqJFWJUcivcC',
+        'version': 'Su70hwhseutRCC8sRZccSvzz097YZLRXoPGGsEao4iIC',
       },
     ],
     'dep_type': 'cipd',
@@ -1624,7 +1624,7 @@
 
   'src/clank': {
     'url': Var('chrome_git') + '/clank/internal/apps.git' + '@' +
-    '0072ba49c188b839f19271436781d4d0ceeb77cb',
+    '9c5c23a8cfe0538d87debe05f72e848c017b3121',
     'condition': 'checkout_android and checkout_src_internal',
   },
 
@@ -2060,7 +2060,7 @@
     Var('chromium_git') + '/chromium/web-tests.git' + '@' + Var('crossbench_web_tests_revision'),
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '2e88a3f08bd8c4a0014eae82729beca935f7f188',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + 'db99cc40f562179c59f12b10e8af2d8fe2770ad2',
 
   'src/third_party/devtools-frontend/src':
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
@@ -2072,7 +2072,7 @@
     Var('chromium_git') + '/external/github.com/jk-jeon/dragonbox.git' + '@' + '6c7c925b571d54486b9ffae8d9d18a822801cbda',
 
   'src/third_party/eigen3/src':
-    Var('chromium_git') + '/external/gitlab.com/libeigen/eigen.git' + '@' + 'dcbaf2d608f306450f1e74949eb87e9a22a7ef4b',
+    Var('chromium_git') + '/external/gitlab.com/libeigen/eigen.git' + '@' + '49623d0c4e1af3c680845191948d10f6d3e92f8a',
 
   'src/third_party/emoji-metadata/src': {
     'url': Var('chromium_git') + '/external/github.com/googlefonts/emoji-metadata' + '@' + '045f146fca682a836e01cd265171312bfb300e06',
@@ -2584,7 +2584,7 @@
     Var('chromium_git') + '/external/github.com/cisco/openh264' + '@' + '652bdb7719f30b52b08e506645a7322ff1b2cc6f',
 
   'src/third_party/openscreen/src':
-    Var('chromium_git') + '/openscreen' + '@' + '734877394201dcfcc786b3c8ea057b7607a56993',
+    Var('chromium_git') + '/openscreen' + '@' + '2f501fd6f616e647b8f3f4af2e8decfc91151a49',
 
   'src/third_party/openxr/src': {
     'url': Var('chromium_git') + '/external/github.com/KhronosGroup/OpenXR-SDK' + '@' + '75c53b6e853dc12c7b3c771edc9c9c841b15faaa',
@@ -2610,7 +2610,7 @@
     Var('pdfium_git') + '/pdfium.git' + '@' +  Var('pdfium_revision'),
 
   'src/third_party/perfetto':
-    Var('chromium_git') + '/external/github.com/google/perfetto.git' + '@' + 'a55ca8aac81bd5b9b52ed7e66584949b4a4a3d8d',
+    Var('chromium_git') + '/external/github.com/google/perfetto.git' + '@' + 'f67fc0ed0a168a0d80b35d07215c8fed2735d7d3',
 
   'src/base/tracing/test/data': {
     'bucket': 'perfetto',
@@ -2793,7 +2793,7 @@
       'packages': [
           {
               'package': 'chromium/third_party/r8',
-              'version': 'zz88LYVGjeSDKwtTv2TbS4Cwz_pg7XAQeR-E1RqKEN8C',
+              'version': 'sjuUu6_BDMGSci_jpKyt68J7gQ3sA59eJI7biFlTGXoC',
           },
       ],
       'condition': 'checkout_android and non_git_source',
@@ -2831,13 +2831,13 @@
   },
 
   'src/third_party/ruy/src':
-    Var('chromium_git') + '/external/github.com/google/ruy.git' + '@' + '9940fbf1e0c0863907e77e0600b99bb3e2bc2b9f',
+    Var('chromium_git') + '/external/github.com/google/ruy.git' + '@' + '1e6e50872655a73b5250f954d7b9da9a87292fd3',
 
   'src/third_party/search_engines_data/resources':
     Var('chromium_git') + '/external/search_engines_data.git' + '@' + '97355b69daee73d74ac38f2227788458bd95d372',
 
   'src/third_party/search_engines_data/resources_internal': {
-    'url': Var('chrome_git') + '/external/search_engines_data_internal.git' + '@' + 'd796f6348f95a4ff08a8efe02f11e55c2dd257ec',
+    'url': Var('chrome_git') + '/external/search_engines_data_internal.git' + '@' + '238cd4ef4ac82a9f819fa12da03ce22745eee391',
     'condition': 'checkout_src_internal',
   },
 
@@ -2922,7 +2922,7 @@
     Var('chromium_git') + '/external/github.com/GoogleChromeLabs/text-fragments-polyfill.git' + '@' + 'c036420683f672d685e27415de0a5f5e85bdc23f',
 
   'src/third_party/tflite/src':
-    Var('chromium_git') + '/external/github.com/tensorflow/tensorflow.git' + '@' + '581d1a6689062926c6459ad3c7807278220e24c9',
+    Var('chromium_git') + '/external/github.com/tensorflow/tensorflow.git' + '@' + '247b0cf254fbbf3d326feed3820ec24503a353a5',
 
   'src/third_party/turbine/cipd': {
       'packages': [
@@ -2935,16 +2935,16 @@
       'dep_type': 'cipd',
   },
 
-  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@bb0f420085e991834149a0d40e93136118d68dde',
-  'src/third_party/glslang/src': '{chromium_git}/external/github.com/KhronosGroup/glslang@7a47e2531cb334982b2a2dd8513dca0a3de4373d',
+  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@d96e0682f2c04a2e4b1d847c125d561a1a74e763',
+  'src/third_party/glslang/src': '{chromium_git}/external/github.com/KhronosGroup/glslang@b5782e52ee2f7b3e40bb9c80d15b47016e008bc9',
   '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@f410b3c178740f9f5bd28d5b22a71d4bc10acd49',
   'src/third_party/vulkan-headers/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-Headers@2fa203425eb4af9dfc6b03f97ef72b0b5bcb8350',
-  'src/third_party/vulkan-loader/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-Loader@052ac24611eced7b0ca62cc5cca2eeeb2051fa28',
-  'src/third_party/vulkan-tools/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-Tools@2a3347d5e74d359e3ecb8e229917f3335bfa2dfa',
+  'src/third_party/vulkan-loader/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-Loader@e042a3a16bdf37e8c9d61b95b7a5933bccef0f45',
+  'src/third_party/vulkan-tools/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-Tools@48b5d246b2d0b1a41ee7ea1b69525ae7bb38a2ae',
   'src/third_party/vulkan-utility-libraries/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-Utility-Libraries@c010c19e796035e92fb3b0462cb887518a41a7c1',
-  'src/third_party/vulkan-validation-layers/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-ValidationLayers@bf463deb91577c8ccf2031407a94cf36e66d6096',
+  'src/third_party/vulkan-validation-layers/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-ValidationLayers@e1c92ec6cd2be77ef1fc2a0a0dabd432a1bccca7',
 
   'src/third_party/vulkan_memory_allocator':
     Var('chromium_git') + '/external/github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git' + '@' + 'cb0597213b0fcb999caa9ed08c2f88dc45eb7d50',
@@ -2981,7 +2981,7 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + 'c01b768bce4a143e152c1870b6ba99ea6267d2b0',
 
   'src/third_party/webgpu-cts/src':
-    Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + 'ef27c0b88b802ca64677e2c0ed60378951ac6c42',
+    Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + 'e7cad0143f136c69b345024d0a60e0d859dd7503',
 
   'src/third_party/webpagereplay':
     Var('chromium_git') + '/webpagereplay.git' + '@' + Var('webpagereplay_revision'),
@@ -3011,7 +3011,7 @@
   },
 
   'src/third_party/xnnpack/src':
-    Var('chromium_git') + '/external/github.com/google/XNNPACK.git' + '@' + 'b26a32f4afcef0a41c8b5f9981864ef7c838067d',
+    Var('chromium_git') + '/external/github.com/google/XNNPACK.git' + '@' + 'd3efd0a2fcd944931416811da6d24222c91ddd9d',
 
   'src/third_party/libei/cipd': {
 
@@ -3120,7 +3120,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/boca_app/app',
-        'version': 'HY2KIDBBaHRkLqNmgH6sGNp7lPkGlnlVDu5ntsklqhYC',
+        'version': 'pTKBtqveNA8zKL-NbM-IASyWGyYGSBNZ66UJBbAlGKgC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
@@ -3512,7 +3512,7 @@
 
   'src/chrome/browser/glic/e2e_test/internal': {
       'url': Var('chrome_git') + '/chrome/browser/glic/test/internal.git' + '@' +
-        '663cabcaa66039de359b513c6e562c04f3e09fbc',
+        '5dbeda3a1dd50c77b82185b21cfb47fef05c0686',
       'condition': 'checkout_glic_e2e_tests',
   },
 
@@ -3656,7 +3656,7 @@
 
   'src/chrome/updater/internal': {
     'url': Var('chrome_git') + '/chrome/updater/internal.git' + '@' +
-        'db5d0ba0230a45fd9b60e140b553aebb121ee31b',
+        'f036eed064b40f7cc3651cb666b2551d7e4f73ae',
     'condition': 'checkout_src_internal',
   },
 
@@ -3697,7 +3697,7 @@
 
   'src/components/optimization_guide/internal': {
       'url': Var('chrome_git') + '/chrome/components/optimization_guide.git' + '@' +
-        'fb463d9b64048748ee57dafb48dd3f9450d6ebee',
+        'ad72f8dcbf4b3f29798846d1746b02c3e3a7b3ff',
       'condition': 'checkout_src_internal',
   },
 
@@ -3769,7 +3769,7 @@
 
   'src/ios_internal':  {
       'url': Var('chrome_git') + '/chrome/ios_internal.git' + '@' +
-        '697336ed8df2b6bb7d4858c2ce9b16d62e4e2bcf',
+        '7beeb0ac6c8ff93ab6be32ab8d6523c9196db833',
       'condition': 'checkout_ios and checkout_src_internal',
   },
 
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index 4284ca2..92efc55 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -947,7 +947,6 @@
 
             # Needed to use QUICHE API.
             r'android_webview/browser/ip_protection/.*',
-            r'chrome/browser/ip_protection/.*',
             r'components/ip_protection/.*',
             r'net/third_party/quiche/overrides/quiche_platform_impl/quiche_stack_trace_impl\.*',
             r'services/network/web_transport\.cc',
@@ -969,7 +968,6 @@
 
             # Needed to use QUICHE API.
             r'android_webview/browser/ip_protection/.*',
-            r'chrome/browser/ip_protection/.*',
             r'components/ip_protection/.*',
             r'net/quic/dedicated_web_transport_http3_client\.cc',
 
@@ -1001,7 +999,6 @@
         [
             # Needed to use QUICHE API.
             r'android_webview/browser/ip_protection/.*',
-            r'chrome/browser/ip_protection/.*',
             r'components/ip_protection/.*',
 
             # Needed to integrate with //third_party/nearby
diff --git a/android_webview/browser/BUILD.gn b/android_webview/browser/BUILD.gn
index 6970244..cf280c4 100644
--- a/android_webview/browser/BUILD.gn
+++ b/android_webview/browser/BUILD.gn
@@ -79,8 +79,6 @@
     "aw_feature_list_creator.h",
     "aw_field_trials.cc",
     "aw_field_trials.h",
-    "aw_form_database_service.cc",
-    "aw_form_database_service.h",
     "aw_http_auth_handler.cc",
     "aw_http_auth_handler.h",
     "aw_javascript_dialog_manager.cc",
diff --git a/android_webview/browser/aw_browser_context.cc b/android_webview/browser/aw_browser_context.cc
index b5c1a55..cdeb0e8e 100644
--- a/android_webview/browser/aw_browser_context.cc
+++ b/android_webview/browser/aw_browser_context.cc
@@ -21,7 +21,6 @@
 #include "android_webview/browser/aw_content_browser_client.h"
 #include "android_webview/browser/aw_contents_origin_matcher.h"
 #include "android_webview/browser/aw_download_manager_delegate.h"
-#include "android_webview/browser/aw_form_database_service.h"
 #include "android_webview/browser/aw_origin_matched_header.h"
 #include "android_webview/browser/aw_permission_manager.h"
 #include "android_webview/browser/aw_quota_manager_bridge.h"
@@ -224,9 +223,6 @@
       std::make_unique<visitedlink::VisitedLinkWriter>(this, this, false);
   visitedlink_writer_->Init();
 
-  form_database_service_ =
-      std::make_unique<AwFormDatabaseService>(context_storage_path_);
-
   EnsureResourceContextInitialized();
   prefetch_manager_ = std::make_unique<AwPrefetchManager>(this);
   preconnector_ = std::make_unique<AwPreconnector>(this);
diff --git a/android_webview/browser/aw_browser_context.h b/android_webview/browser/aw_browser_context.h
index 95352d70..befd038 100644
--- a/android_webview/browser/aw_browser_context.h
+++ b/android_webview/browser/aw_browser_context.h
@@ -58,7 +58,6 @@
 namespace android_webview {
 
 class AwBrowserContextIoThreadHandle;
-class AwFormDatabaseService;
 class AwQuotaManagerBridge;
 class CookieManager;
 
@@ -107,7 +106,6 @@
   AwQuotaManagerBridge* GetQuotaManagerBridge();
   jlong GetQuotaManagerBridge(JNIEnv* env);
 
-  AwFormDatabaseService* GetFormDatabaseService();
   CookieManager* GetCookieManager();
 
   bool IsDefaultBrowserContext() const;
@@ -269,11 +267,6 @@
 
   scoped_refptr<AwQuotaManagerBridge> quota_manager_bridge_;
 
-  // Cleans up the database tables created by the AwFormDatabaseService
-  // until M132.
-  // TODO(crbug.com/390473673): Remove after M143.
-  std::unique_ptr<AwFormDatabaseService> form_database_service_;
-
   std::unique_ptr<visitedlink::VisitedLinkWriter> visitedlink_writer_;
 
   std::unique_ptr<PrefService> user_pref_service_;
diff --git a/android_webview/browser/aw_form_database_service.cc b/android_webview/browser/aw_form_database_service.cc
deleted file mode 100644
index 72d045e..0000000
--- a/android_webview/browser/aw_form_database_service.cc
+++ /dev/null
@@ -1,44 +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 "android_webview/browser/aw_form_database_service.h"
-
-#include "android_webview/browser/aw_browser_process.h"
-#include "base/functional/bind.h"
-#include "base/task/sequenced_task_runner.h"
-#include "base/task/thread_pool.h"
-#include "base/threading/thread_restrictions.h"
-#include "components/autofill/core/browser/webdata/addresses/address_autofill_table.h"
-#include "components/autofill/core/browser/webdata/autocomplete/autocomplete_table.h"
-#include "components/autofill/core/browser/webdata/payments/payments_autofill_table.h"
-#include "components/webdata/common/webdata_constants.h"
-#include "content/public/browser/browser_thread.h"
-
-namespace android_webview {
-
-AwFormDatabaseService::AwFormDatabaseService(const base::FilePath path) {
-  web_database_ = new WebDatabaseService(
-      path.Append(kWebDataFilename), content::GetUIThreadTaskRunner({}),
-      base::ThreadPool::CreateSequencedTaskRunner(
-          {base::MayBlock(), base::TaskPriority::USER_VISIBLE,
-           base::TaskShutdownBehavior::BLOCK_SHUTDOWN}));
-  // The old AwFormDatabaseService used to create:
-  // - autofill::AutocompleteTable
-  // - autofill::AddressAutofillTable
-  // - autofill::PaymentsAutofillTable
-  web_database_->AddTable(
-      std::make_unique<autofill::AutocompleteTable::Dropper>());
-  web_database_->AddTable(
-      std::make_unique<autofill::AddressAutofillTable::Dropper>());
-  web_database_->AddTable(
-      std::make_unique<autofill::PaymentsAutofillTable::Dropper>());
-  web_database_->LoadDatabase(
-      AwBrowserProcess::GetInstance()->GetOSCryptAsync());
-}
-
-AwFormDatabaseService::~AwFormDatabaseService() {
-  web_database_->ShutdownDatabase();
-}
-
-}  // namespace android_webview
diff --git a/android_webview/browser/aw_form_database_service.h b/android_webview/browser/aw_form_database_service.h
deleted file mode 100644
index 47e4ef5..0000000
--- a/android_webview/browser/aw_form_database_service.h
+++ /dev/null
@@ -1,30 +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 ANDROID_WEBVIEW_BROWSER_AW_FORM_DATABASE_SERVICE_H_
-#define ANDROID_WEBVIEW_BROWSER_AW_FORM_DATABASE_SERVICE_H_
-
-#include "base/files/file_path.h"
-#include "components/webdata/common/web_database_service.h"
-
-namespace android_webview {
-
-// Drops the tables created by the old AwFormDatabaseService that was used until
-// M132.
-class AwFormDatabaseService {
- public:
-  explicit AwFormDatabaseService(const base::FilePath path);
-
-  AwFormDatabaseService(const AwFormDatabaseService&) = delete;
-  AwFormDatabaseService& operator=(const AwFormDatabaseService&) = delete;
-
-  ~AwFormDatabaseService();
-
- private:
-  scoped_refptr<WebDatabaseService> web_database_;
-};
-
-}  // namespace android_webview
-
-#endif  // ANDROID_WEBVIEW_BROWSER_AW_FORM_DATABASE_SERVICE_H_
diff --git a/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumAwInit.java b/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumAwInit.java
index 94942a28..1dfbbca9 100644
--- a/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumAwInit.java
+++ b/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumAwInit.java
@@ -713,6 +713,15 @@
                         AwDarkMode.enableSimplifiedDarkMode();
                     }
 
+                    if (WebViewCachedFlags.get()
+                            .isCachedFeatureEnabled(
+                                    AwFeatures.WEBVIEW_OPT_IN_TO_GMS_BIND_SERVICE_OPTIMIZATION)) {
+                        AwBrowserProcess.maybeEnableSafeBrowsingFromGms();
+                        AwBrowserProcess.setupSupervisedUser();
+                        AwBrowserProcess.handleMinidumpsAndSetMetricsConsent(
+                                /* updateMetricsConsent= */ true);
+                    }
+
                     AwBrowserProcess.postBackgroundTasks(
                             mFactory.isSafeModeEnabled(), mFactory.getWebViewPrefs());
 
@@ -796,8 +805,11 @@
     private void runImmediateTaskAfterBrowserProcessInit() {
         // TODO(crbug.com/332706093): See if this can be moved before loading native.
         AwClassPreloader.preloadClasses();
-
-        AwBrowserProcess.handleMinidumpsAndSetMetricsConsent(/* updateMetricsConsent= */ true);
+        if (!WebViewCachedFlags.get()
+                .isCachedFeatureEnabled(
+                        AwFeatures.WEBVIEW_OPT_IN_TO_GMS_BIND_SERVICE_OPTIMIZATION)) {
+            AwBrowserProcess.handleMinidumpsAndSetMetricsConsent(/* updateMetricsConsent= */ true);
+        }
         AwBrowserProcess.doNetworkInitializations(ContextUtils.getApplicationContext());
     }
 
diff --git a/android_webview/java/src/org/chromium/android_webview/AwBrowserProcess.java b/android_webview/java/src/org/chromium/android_webview/AwBrowserProcess.java
index 76dadff..3da9c045 100644
--- a/android_webview/java/src/org/chromium/android_webview/AwBrowserProcess.java
+++ b/android_webview/java/src/org/chromium/android_webview/AwBrowserProcess.java
@@ -266,15 +266,10 @@
                 AwContentsLifecycleNotifier.initialize();
             }
 
-            try (DualTraceEvent ignored =
-                    DualTraceEvent.scoped(
-                            "AwBrowserProcess.finishBrowserProcessStart"
-                                    + ".setupSupervisedUrlClassifier")) {
-                AwSupervisedUserUrlClassifier classifier =
-                        AwSupervisedUserUrlClassifier.getInstance();
-                if (classifier != null && AwSupervisedUserSafeModeAction.isSupervisionEnabled()) {
-                    classifier.checkIfNeedRestrictedContentBlocking();
-                }
+            if (!WebViewCachedFlags.get()
+                    .isCachedFeatureEnabled(
+                            AwFeatures.WEBVIEW_OPT_IN_TO_GMS_BIND_SERVICE_OPTIMIZATION)) {
+                setupSupervisedUser();
             }
 
             if (AwFeatureMap.isEnabled(AwFeatures.WEBVIEW_CACHE_BOUNDARY_INTERFACE_METHODS)) {
@@ -347,6 +342,28 @@
                     DualTraceEvent.scoped("AwBrowserProcess.maybeEnableSafeBrowsingFromManifest")) {
                 AwSafeBrowsingConfigHelper.maybeEnableSafeBrowsingFromManifest();
             }
+            if (!WebViewCachedFlags.get()
+                    .isCachedFeatureEnabled(
+                            AwFeatures.WEBVIEW_OPT_IN_TO_GMS_BIND_SERVICE_OPTIMIZATION)) {
+                maybeEnableSafeBrowsingFromGms();
+            }
+        }
+    }
+
+    public static void setupSupervisedUser() {
+        try (DualTraceEvent ignored =
+                DualTraceEvent.scoped("AwBrowserProcess.setupSupervisedUser")) {
+            AwSupervisedUserUrlClassifier classifier = AwSupervisedUserUrlClassifier.getInstance();
+            if (classifier != null && AwSupervisedUserSafeModeAction.isSupervisionEnabled()) {
+                classifier.checkIfNeedRestrictedContentBlocking();
+            }
+        }
+    }
+
+    public static void maybeEnableSafeBrowsingFromGms() {
+        try (DualTraceEvent e2 =
+                DualTraceEvent.scoped("AwBrowserProcess.maybeEnableSafeBrowsingFromGms")) {
+            AwSafeBrowsingConfigHelper.maybeEnableSafeBrowsingFromGms();
         }
     }
 
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 dc6e3b0d..73d392a 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
@@ -423,9 +423,6 @@
         Flag.baseFeature(
                 BlinkFeatures.THREADED_PRELOAD_SCANNER,
                 "If enabled, the HTMLPreloadScanner will run on a worker thread."),
-        Flag.baseFeature(
-                BlinkFeatures.CHECK_HTML_PARSER_BUDGET_LESS_OFTEN,
-                "If enabled, avoids calling the clock for every token in the HTML parser."),
         Flag.baseFeature(BaseFeatures.ALIGN_WAKE_UPS, "Align delayed wake ups at 125 Hz"),
         Flag.baseFeature(
                 GpuFeatures.INCREASED_CMD_BUFFER_PARSE_SLICE,
@@ -1124,6 +1121,9 @@
                 ContentFeatures.ACCESSIBILITY_SEQUENTIAL_FOCUS,
                 "When enabled, keyboard focus starting point will be synchronized with"
                         + " accessibility focus."),
+        Flag.baseFeature(
+                "HttpCacheInitializeDiskCacheBackendEarly",
+                "If true, Initialize disk cache backend early for HTTP cache."),
         // Add new commandline switches and features above. The final entry should have a
         // trailing comma for cleaner diffs.
 
diff --git a/android_webview/java/src/org/chromium/android_webview/safe_browsing/AwSafeBrowsingConfigHelper.java b/android_webview/java/src/org/chromium/android_webview/safe_browsing/AwSafeBrowsingConfigHelper.java
index 56e4c68e..031876c 100644
--- a/android_webview/java/src/org/chromium/android_webview/safe_browsing/AwSafeBrowsingConfigHelper.java
+++ b/android_webview/java/src/org/chromium/android_webview/safe_browsing/AwSafeBrowsingConfigHelper.java
@@ -79,14 +79,15 @@
             // the existence of the CLI switch.
             setSafeBrowsingEnabledByManifest(
                     appOptIn == null ? !isDisabledByCommandLine() : appOptIn);
-
-            Callback<Boolean> cb =
-                    verifyAppsValue ->
-                            setSafeBrowsingUserOptIn(Boolean.TRUE.equals(verifyAppsValue));
-            PlatformServiceBridge.getInstance().querySafeBrowsingUserConsent(cb);
         }
     }
 
+    public static void maybeEnableSafeBrowsingFromGms() {
+        Callback<Boolean> cb =
+                verifyAppsValue -> setSafeBrowsingUserOptIn(Boolean.TRUE.equals(verifyAppsValue));
+        PlatformServiceBridge.getInstance().querySafeBrowsingUserConsent(cb);
+    }
+
     @Nullable
     private static Boolean getOptInPreferenceTraced() {
         try (DualTraceEvent e =
diff --git a/android_webview/test/data/web_tests/webexposed/global-interface-listing-expected.txt b/android_webview/test/data/web_tests/webexposed/global-interface-listing-expected.txt
index cb6d553..849ac9ce 100644
--- a/android_webview/test/data/web_tests/webexposed/global-interface-listing-expected.txt
+++ b/android_webview/test/data/web_tests/webexposed/global-interface-listing-expected.txt
@@ -743,6 +743,9 @@
     getter namespaceURI
     getter prefix
     method constructor
+interface CSSNavigationRule : CSSConditionRule
+    attribute @@toStringTag
+    method constructor
 interface CSSNestedDeclarations : CSSRule
     attribute @@toStringTag
     getter style
@@ -974,9 +977,6 @@
     setter x
     setter y
     setter z
-interface CSSRouteRule : CSSConditionRule
-    attribute @@toStringTag
-    method constructor
 interface CSSRule
     attribute @@toStringTag
     attribute CHARSET_RULE
@@ -3358,6 +3358,14 @@
     method constructor
     method playEffect
     method reset
+interface GamepadRawInputChangeEvent : GamepadEvent
+    attribute @@toStringTag
+    getter axesChanged
+    getter buttonsPressed
+    getter buttonsReleased
+    getter buttonsValueChanged
+    getter touchesChanged
+    method constructor
 interface GamepadTouch
     attribute @@toStringTag
     getter position
@@ -3547,6 +3555,7 @@
     getter onfocus
     getter ongamepadconnected
     getter ongamepaddisconnected
+    getter ongamepadrawinputchanged
     getter onhashchange
     getter onlanguagechange
     getter onload
@@ -3581,6 +3590,7 @@
     setter onfocus
     setter ongamepadconnected
     setter ongamepaddisconnected
+    setter ongamepadrawinputchanged
     setter onhashchange
     setter onlanguagechange
     setter onload
@@ -4116,6 +4126,7 @@
     getter onfocus
     getter ongamepadconnected
     getter ongamepaddisconnected
+    getter ongamepadrawinputchanged
     getter onhashchange
     getter onlanguagechange
     getter onload
@@ -4146,6 +4157,7 @@
     setter onfocus
     setter ongamepadconnected
     setter ongamepaddisconnected
+    setter ongamepadrawinputchanged
     setter onhashchange
     setter onlanguagechange
     setter onload
@@ -12244,6 +12256,7 @@
     getter onformdata
     getter ongamepadconnected
     getter ongamepaddisconnected
+    getter ongamepadrawinputchanged
     getter ongotpointercapture
     getter onhashchange
     getter oninput
@@ -12468,6 +12481,7 @@
     setter onformdata
     setter ongamepadconnected
     setter ongamepaddisconnected
+    setter ongamepadrawinputchanged
     setter ongotpointercapture
     setter onhashchange
     setter oninput
diff --git a/ash/display/display_prefs.cc b/ash/display/display_prefs.cc
index e0b9113..d09f072 100644
--- a/ash/display/display_prefs.cc
+++ b/ash/display/display_prefs.cc
@@ -265,13 +265,10 @@
     // display info.
     double refresh_rate = 60.0;
     bool is_interlaced = false;
-    if (display::features::IsListAllDisplayModesEnabled()) {
-      refresh_rate =
-          dict_value->FindDouble("refresh-rate").value_or(refresh_rate);
-      std::optional<bool> is_interlaced_opt =
-          dict_value->FindBool("interlaced");
-      is_interlaced = is_interlaced_opt.value_or(false);
-    }
+    refresh_rate =
+        dict_value->FindDouble("refresh-rate").value_or(refresh_rate);
+    std::optional<bool> is_interlaced_opt = dict_value->FindBool("interlaced");
+    is_interlaced = is_interlaced_opt.value_or(false);
 
     gfx::Insets insets;
     if (base::CommandLine::ForCurrentProcess()->HasSwitch(
@@ -614,10 +611,8 @@
       property_value.Set("device-scale-factor",
                          static_cast<int>(mode.device_scale_factor() * 1000));
 
-      if (display::features::IsListAllDisplayModesEnabled()) {
-        property_value.Set("interlaced", mode.is_interlaced());
-        property_value.Set("refresh-rate", mode.refresh_rate());
-      }
+      property_value.Set("interlaced", mode.is_interlaced());
+      property_value.Set("refresh-rate", mode.refresh_rate());
     }
     if (!info.overscan_insets_in_dip().IsEmpty()) {
       InsetsToValue(info.overscan_insets_in_dip(), property_value);
diff --git a/ash/display/resolution_notification_controller.cc b/ash/display/resolution_notification_controller.cc
index 378e1cdf..9a2eaf0 100644
--- a/ash/display/resolution_notification_controller.cc
+++ b/ash/display/resolution_notification_controller.cc
@@ -151,45 +151,32 @@
   constexpr char16_t kTimeoutPlaceHolder[] = u"$1";
 
   std::u16string timeout_message_with_placeholder;
-  if (display::features::IsListAllDisplayModesEnabled()) {
-    const std::u16string actual_refresh_rate = ConvertRefreshRateToString16(
-        change_info_->current_resolution.refresh_rate());
-    const std::u16string requested_refresh_rate = ConvertRefreshRateToString16(
-        change_info_->new_resolution.refresh_rate());
+  const std::u16string actual_refresh_rate = ConvertRefreshRateToString16(
+      change_info_->current_resolution.refresh_rate());
+  const std::u16string requested_refresh_rate =
+      ConvertRefreshRateToString16(change_info_->new_resolution.refresh_rate());
 
-    const bool no_fallback = actual_display_size == requested_display_size &&
-                             actual_refresh_rate == requested_refresh_rate;
+  const bool no_fallback = actual_display_size == requested_display_size &&
+                           actual_refresh_rate == requested_refresh_rate;
 
-    dialog_title =
-        no_fallback
-            ? l10n_util::GetStringUTF16(
-                  IDS_ASH_RESOLUTION_REFRESH_CHANGE_DIALOG_TITLE_SUCCESS)
-            : l10n_util::GetStringUTF16(
-                  IDS_ASH_RESOLUTION_REFRESH_CHANGE_DIALOG_TITLE_FALLBACK);
+  dialog_title =
+      no_fallback
+          ? l10n_util::GetStringUTF16(
+                IDS_ASH_RESOLUTION_REFRESH_CHANGE_DIALOG_TITLE_SUCCESS)
+          : l10n_util::GetStringUTF16(
+                IDS_ASH_RESOLUTION_REFRESH_CHANGE_DIALOG_TITLE_FALLBACK);
 
-    timeout_message_with_placeholder =
-        no_fallback ? l10n_util::GetStringFUTF16(
-                          IDS_ASH_RESOLUTION_REFRESH_CHANGE_DIALOG_CHANGED_NEW,
-                          display_name, actual_display_size,
-                          actual_refresh_rate, kTimeoutPlaceHolder)
-                    : l10n_util::GetStringFUTF16(
-                          IDS_ASH_RESOLUTION_REFRESH_CHANGE_DIALOG_FALLBACK_NEW,
-                          {display_name, actual_display_size,
-                           actual_refresh_rate, requested_display_size,
-                           requested_refresh_rate, kTimeoutPlaceHolder},
-                          /*offsets=*/nullptr);
-
-  } else {
-    timeout_message_with_placeholder =
-        actual_display_size == requested_display_size
-            ? l10n_util::GetStringFUTF16(
-                  IDS_ASH_RESOLUTION_CHANGE_DIALOG_CHANGED, display_name,
-                  actual_display_size, kTimeoutPlaceHolder)
-            : l10n_util::GetStringFUTF16(
-                  IDS_ASH_RESOLUTION_CHANGE_DIALOG_FALLBACK, display_name,
-                  requested_display_size, actual_display_size,
-                  kTimeoutPlaceHolder);
-  }
+  timeout_message_with_placeholder =
+      no_fallback ? l10n_util::GetStringFUTF16(
+                        IDS_ASH_RESOLUTION_REFRESH_CHANGE_DIALOG_CHANGED_NEW,
+                        display_name, actual_display_size, actual_refresh_rate,
+                        kTimeoutPlaceHolder)
+                  : l10n_util::GetStringFUTF16(
+                        IDS_ASH_RESOLUTION_REFRESH_CHANGE_DIALOG_FALLBACK_NEW,
+                        {display_name, actual_display_size, actual_refresh_rate,
+                         requested_display_size, requested_refresh_rate,
+                         kTimeoutPlaceHolder},
+                        /*offsets=*/nullptr);
 
   DisplayChangeDialog* dialog = new DisplayChangeDialog(
       std::move(dialog_title), std::move(timeout_message_with_placeholder),
diff --git a/ash/display/resolution_notification_controller_unittest.cc b/ash/display/resolution_notification_controller_unittest.cc
index c449bd5..9b7e67b2 100644
--- a/ash/display/resolution_notification_controller_unittest.cc
+++ b/ash/display/resolution_notification_controller_unittest.cc
@@ -28,9 +28,7 @@
 
 namespace ash {
 
-class ResolutionNotificationControllerTest
-    : public AshTestBase,
-      public ::testing::WithParamInterface<bool> {
+class ResolutionNotificationControllerTest : public AshTestBase {
  public:
   ResolutionNotificationControllerTest() : accept_count_(0) {}
 
@@ -50,15 +48,10 @@
     const std::u16string countdown = ui::TimeFormat::Simple(
         ui::TimeFormat::FORMAT_DURATION, ui::TimeFormat::LENGTH_LONG,
         base::Seconds(timeout_count));
-    if (::display::features::IsListAllDisplayModesEnabled()) {
-      return l10n_util::GetStringFUTF16(
-          IDS_ASH_RESOLUTION_REFRESH_CHANGE_DIALOG_CHANGED_NEW, display_name,
-          base::UTF8ToUTF16(new_resolution.ToString()),
-          ConvertRefreshRateToString16(new_refresh_rate), countdown);
-    }
     return l10n_util::GetStringFUTF16(
-        IDS_ASH_RESOLUTION_CHANGE_DIALOG_CHANGED, display_name,
-        base::UTF8ToUTF16(new_resolution.ToString()), countdown);
+        IDS_ASH_RESOLUTION_REFRESH_CHANGE_DIALOG_CHANGED_NEW, display_name,
+        base::UTF8ToUTF16(new_resolution.ToString()),
+        ConvertRefreshRateToString16(new_refresh_rate), countdown);
   }
 
   std::u16string ExpectedFallbackNotificationMessage(
@@ -73,33 +66,16 @@
     const std::u16string countdown = ui::TimeFormat::Simple(
         ui::TimeFormat::FORMAT_DURATION, ui::TimeFormat::LENGTH_LONG,
         base::Seconds(timeout_count));
-    if (::display::features::IsListAllDisplayModesEnabled()) {
-      return l10n_util::GetStringFUTF16(
-          IDS_ASH_RESOLUTION_REFRESH_CHANGE_DIALOG_FALLBACK_NEW,
-          {display_name, base::UTF8ToUTF16(fallback_resolution.ToString()),
-           ConvertRefreshRateToString16(fallback_refresh_rate),
-           base::UTF8ToUTF16(specified_resolution.ToString()),
-           ConvertRefreshRateToString16(specified_refresh_rate), countdown},
-          /*offsets=*/nullptr);
-    }
     return l10n_util::GetStringFUTF16(
-        IDS_ASH_RESOLUTION_CHANGE_DIALOG_FALLBACK, display_name,
-        base::UTF8ToUTF16(specified_resolution.ToString()),
-        base::UTF8ToUTF16(fallback_resolution.ToString()), countdown);
+        IDS_ASH_RESOLUTION_REFRESH_CHANGE_DIALOG_FALLBACK_NEW,
+        {display_name, base::UTF8ToUTF16(fallback_resolution.ToString()),
+         ConvertRefreshRateToString16(fallback_refresh_rate),
+         base::UTF8ToUTF16(specified_resolution.ToString()),
+         ConvertRefreshRateToString16(specified_refresh_rate), countdown},
+        /*offsets=*/nullptr);
   }
 
  protected:
-  void SetUp() override {
-    if (GetParam()) {
-      scoped_feature_list_.InitAndEnableFeature(
-          display::features::kListAllDisplayModes);
-    } else {
-      scoped_feature_list_.InitAndDisableFeature(
-          display::features::kListAllDisplayModes);
-    }
-    AshTestBase::SetUp();
-  }
-
   void SetDisplayResolutionAndNotifyWithResolution(
       const display::Display& display,
       const gfx::Size& new_resolution,
@@ -201,7 +177,7 @@
 };
 
 // Basic behaviors and verifies it doesn't cause crashes.
-TEST_P(ResolutionNotificationControllerTest, Basic) {
+TEST_F(ResolutionNotificationControllerTest, Basic) {
   UpdateDisplay("400x300#400x300%57|300x200%58,300x250#300x250%60|300x200%59");
   display::test::DisplayManagerTestApi display_manager_test(display_manager());
   int64_t id2 = display_manager_test.GetSecondaryDisplay().id();
@@ -233,7 +209,7 @@
 }
 
 // Check that notification is not shown when changes are forced by policy.
-TEST_P(ResolutionNotificationControllerTest, ForcedByPolicy) {
+TEST_F(ResolutionNotificationControllerTest, ForcedByPolicy) {
   UpdateDisplay("400x300#400x300%57|300x200%58,300x250#300x250%59|300x200%60");
   display::test::DisplayManagerTestApi display_manager_test(display_manager());
   int64_t id2 = display_manager_test.GetSecondaryDisplay().id();
@@ -252,7 +228,7 @@
   EXPECT_EQ(60.0, mode.refresh_rate());
 }
 
-TEST_P(ResolutionNotificationControllerTest, ClickMeansAccept) {
+TEST_F(ResolutionNotificationControllerTest, ClickMeansAccept) {
   UpdateDisplay("400x300#400x300%57|300x200%58,300x250#300x250%59|300x200%60");
   display::test::DisplayManagerTestApi display_manager_test(display_manager());
   int64_t id2 = display_manager_test.GetSecondaryDisplay().id();
@@ -278,7 +254,7 @@
   EXPECT_EQ(60.0, mode.refresh_rate());
 }
 
-TEST_P(ResolutionNotificationControllerTest, AcceptButton) {
+TEST_F(ResolutionNotificationControllerTest, AcceptButton) {
   UpdateDisplay("400x300#400x300%59|300x200%60");
   const display::Display& display = display::Screen::Get()->GetPrimaryDisplay();
   SetDisplayResolutionAndNotify(display, gfx::Size(300, 200), 60,
@@ -318,7 +294,7 @@
   EXPECT_EQ(60.0f, mode.refresh_rate());
 }
 
-TEST_P(ResolutionNotificationControllerTest, Close) {
+TEST_F(ResolutionNotificationControllerTest, Close) {
   UpdateDisplay("200x100,250x150#250x150%59|300x200%60");
   display::test::DisplayManagerTestApi display_manager_test(display_manager());
   int64_t id2 = display_manager_test.GetSecondaryDisplay().id();
@@ -343,7 +319,7 @@
   EXPECT_EQ(1, accept_count());
 }
 
-TEST_P(ResolutionNotificationControllerTest, Timeout) {
+TEST_F(ResolutionNotificationControllerTest, Timeout) {
   UpdateDisplay("400x300#400x300%60|300x200%60");
   const display::Display& display = display::Screen::Get()->GetPrimaryDisplay();
   SetDisplayResolutionAndNotify(display, gfx::Size(300, 200), 60,
@@ -365,7 +341,7 @@
   EXPECT_EQ(60.0f, mode.refresh_rate());
 }
 
-TEST_P(ResolutionNotificationControllerTest, DisplayDisconnected) {
+TEST_F(ResolutionNotificationControllerTest, DisplayDisconnected) {
   UpdateDisplay(
       "400x300#400x300%56|300x200%57,"
       "300x200#300x250%58|300x200%60|200x100%60");
@@ -388,7 +364,7 @@
 }
 
 // See http://crbug.com/869401 for details.
-TEST_P(ResolutionNotificationControllerTest, MultipleResolutionChange) {
+TEST_F(ResolutionNotificationControllerTest, MultipleResolutionChange) {
   UpdateDisplay(
       "400x300#400x300%56|300x200%57,"
       "350x250#350x250%58|300x200%59");
@@ -425,7 +401,7 @@
   EXPECT_EQ(58.0f, mode.refresh_rate());
 }
 
-TEST_P(ResolutionNotificationControllerTest, Fallback) {
+TEST_F(ResolutionNotificationControllerTest, Fallback) {
   UpdateDisplay(
       "400x300#400x300%56|300x200%57,"
       "350x250#350x250%60|220x210%60|300x200%60");
@@ -473,7 +449,7 @@
 
 }  // namespace
 
-TEST_P(NoSessionResolutionNotificationControllerTest, NoTimeoutInKioskMode) {
+TEST_F(NoSessionResolutionNotificationControllerTest, NoTimeoutInKioskMode) {
   // Login in as kiosk app.
   SimulateKioskMode(user_manager::UserType::kKioskChromeApp);
 
@@ -487,7 +463,7 @@
                                 /*new_is_native=*/false);
 }
 
-TEST_P(NoSessionResolutionNotificationControllerTest, NoDialogInKioskMode) {
+TEST_F(NoSessionResolutionNotificationControllerTest, NoDialogInKioskMode) {
   // Login in as kiosk app.
   SimulateKioskMode(user_manager::UserType::kKioskChromeApp);
 
@@ -511,13 +487,4 @@
   EXPECT_EQ(60.0f, mode.refresh_rate());
 }
 
-// Parametrizes all tests to run with display::features::kListAllDisplayModes
-// enabled and disabled.
-INSTANTIATE_TEST_SUITE_P(All,
-                         ResolutionNotificationControllerTest,
-                         ::testing::Bool());
-INSTANTIATE_TEST_SUITE_P(All,
-                         NoSessionResolutionNotificationControllerTest,
-                         ::testing::Bool());
-
 }  // namespace ash
diff --git a/base/BUILD.gn b/base/BUILD.gn
index 213e3b0c..4617ca8 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -596,7 +596,6 @@
     "run_loop.h",
     "sampling_heap_profiler/lock_free_address_hash_set.cc",
     "sampling_heap_profiler/lock_free_address_hash_set.h",
-    "sampling_heap_profiler/lock_free_bloom_filter.cc",
     "sampling_heap_profiler/lock_free_bloom_filter.h",
     "sampling_heap_profiler/poisson_allocation_sampler.cc",
     "sampling_heap_profiler/poisson_allocation_sampler.h",
diff --git a/base/allocator/partition_allocator/src/partition_alloc/pointers/raw_ptr.h b/base/allocator/partition_allocator/src/partition_alloc/pointers/raw_ptr.h
index 00e8b56..7d6e9a0 100644
--- a/base/allocator/partition_allocator/src/partition_alloc/pointers/raw_ptr.h
+++ b/base/allocator/partition_allocator/src/partition_alloc/pointers/raw_ptr.h
@@ -127,11 +127,6 @@
   // Don't use directly, use AllowPtrArithmetic instead.
   kAllowPtrArithmetic = (1 << 3),
 
-  // This pointer has BRP disabled for experimental rewrites of containers.
-  //
-  // Don't use directly.
-  kDisableBRP = (1 << 4),
-
   // Uninitialized pointers are discouraged and disabled by default.
   //
   // Don't use directly, use AllowUninitialized instead.
@@ -152,7 +147,7 @@
   // Test only.
   kDummyForTest = (1 << 11),
 
-  kAllMask = kMayDangle | kDisableHooks | kAllowPtrArithmetic | kDisableBRP |
+  kAllMask = kMayDangle | kDisableHooks | kAllowPtrArithmetic |
              kAllowUninitialized | kUseCountingImplForTest | kDummyForTest,
 };
 // Template specialization to use |PA_DEFINE_OPERATORS_FOR_FLAGS| without
@@ -245,10 +240,7 @@
 using UnderlyingImplForTraits = internal::RawPtrBackupRefImpl<
     /*AllowDangling=*/partition_alloc::internal::ContainsFlags(
         Traits,
-        RawPtrTraits::kMayDangle),
-    /*DisableBRP=*/partition_alloc::internal::ContainsFlags(
-        Traits,
-        RawPtrTraits::kDisableBRP)>;
+        RawPtrTraits::kMayDangle)>;
 
 #elif PA_BUILDFLAG(USE_RAW_PTR_ASAN_UNOWNED_IMPL)
 template <RawPtrTraits Traits>
diff --git a/base/allocator/partition_allocator/src/partition_alloc/pointers/raw_ptr_backup_ref_impl.cc b/base/allocator/partition_allocator/src/partition_alloc/pointers/raw_ptr_backup_ref_impl.cc
index cc5de1d..ed5c608 100644
--- a/base/allocator/partition_allocator/src/partition_alloc/pointers/raw_ptr_backup_ref_impl.cc
+++ b/base/allocator/partition_allocator/src/partition_alloc/pointers/raw_ptr_backup_ref_impl.cc
@@ -22,9 +22,8 @@
 
 namespace base::internal {
 
-template <bool AllowDangling, bool DisableBRP>
-void RawPtrBackupRefImpl<AllowDangling, DisableBRP>::AcquireInternal(
-    uintptr_t address) {
+template <bool AllowDangling>
+void RawPtrBackupRefImpl<AllowDangling>::AcquireInternal(uintptr_t address) {
 #if PA_BUILDFLAG(DCHECKS_ARE_ON) || \
     PA_BUILDFLAG(ENABLE_BACKUP_REF_PTR_SLOW_CHECKS)
   PA_BASE_CHECK(UseBrp(address));
@@ -42,9 +41,8 @@
   }
 }
 
-template <bool AllowDangling, bool DisableBRP>
-void RawPtrBackupRefImpl<AllowDangling, DisableBRP>::ReleaseInternal(
-    uintptr_t address) {
+template <bool AllowDangling>
+void RawPtrBackupRefImpl<AllowDangling>::ReleaseInternal(uintptr_t address) {
 #if PA_BUILDFLAG(DCHECKS_ARE_ON) || \
     PA_BUILDFLAG(ENABLE_BACKUP_REF_PTR_SLOW_CHECKS)
   PA_BASE_CHECK(UseBrp(address));
@@ -72,8 +70,8 @@
   }
 }
 
-template <bool AllowDangling, bool DisableBRP>
-void RawPtrBackupRefImpl<AllowDangling, DisableBRP>::ReportIfDanglingInternal(
+template <bool AllowDangling>
+void RawPtrBackupRefImpl<AllowDangling>::ReportIfDanglingInternal(
     uintptr_t address) {
   if (partition_alloc::internal::IsUnretainedDanglingRawPtrCheckEnabled()) {
     if (IsSupportedAndNotNull(address)) {
@@ -87,11 +85,11 @@
 }
 
 // static
-template <bool AllowDangling, bool DisableBRP>
-bool RawPtrBackupRefImpl<AllowDangling, DisableBRP>::
-    CheckPointerWithinSameAlloc(uintptr_t before_addr,
-                                uintptr_t after_addr,
-                                size_t type_size) {
+template <bool AllowDangling>
+bool RawPtrBackupRefImpl<AllowDangling>::CheckPointerWithinSameAlloc(
+    uintptr_t before_addr,
+    uintptr_t after_addr,
+    size_t type_size) {
   partition_alloc::internal::PtrPosWithinAlloc ptr_pos_within_alloc =
       partition_alloc::internal::IsPtrWithinSameAlloc(before_addr, after_addr,
                                                       type_size);
@@ -109,9 +107,8 @@
 #endif
 }
 
-template <bool AllowDangling, bool DisableBRP>
-bool RawPtrBackupRefImpl<AllowDangling, DisableBRP>::IsPointeeAlive(
-    uintptr_t address) {
+template <bool AllowDangling>
+bool RawPtrBackupRefImpl<AllowDangling>::IsPointeeAlive(uintptr_t address) {
 #if PA_BUILDFLAG(DCHECKS_ARE_ON) || \
     PA_BUILDFLAG(ENABLE_BACKUP_REF_PTR_SLOW_CHECKS)
   PA_BASE_CHECK(UseBrp(address));
@@ -127,14 +124,8 @@
 
 // Explicitly instantiates the two BackupRefPtr variants in the .cc. This
 // ensures the definitions not visible from the .h are available in the binary.
-template struct RawPtrBackupRefImpl</*AllowDangling=*/false,
-                                    /*DisableBRP=*/false>;
-template struct RawPtrBackupRefImpl</*AllowDangling=*/false,
-                                    /*DisableBRP=*/true>;
-template struct RawPtrBackupRefImpl</*AllowDangling=*/true,
-                                    /*DisableBRP=*/false>;
-template struct RawPtrBackupRefImpl</*AllowDangling=*/true,
-                                    /*DisableBRP=*/true>;
+template struct RawPtrBackupRefImpl</*AllowDangling=*/false>;
+template struct RawPtrBackupRefImpl</*AllowDangling=*/true>;
 
 #if PA_BUILDFLAG(DCHECKS_ARE_ON) || \
     PA_BUILDFLAG(ENABLE_BACKUP_REF_PTR_SLOW_CHECKS)
diff --git a/base/allocator/partition_allocator/src/partition_alloc/pointers/raw_ptr_backup_ref_impl.h b/base/allocator/partition_allocator/src/partition_alloc/pointers/raw_ptr_backup_ref_impl.h
index 4adcc17..6b3b931 100644
--- a/base/allocator/partition_allocator/src/partition_alloc/pointers/raw_ptr_backup_ref_impl.h
+++ b/base/allocator/partition_allocator/src/partition_alloc/pointers/raw_ptr_backup_ref_impl.h
@@ -38,7 +38,7 @@
 // Note that `RawPtrBackupRefImpl` itself is not thread-safe. If multiple
 // threads modify the same raw_ptr object without synchronization, a data race
 // will occur.
-template <bool AllowDangling = false, bool DisableBRP = false>
+template <bool AllowDangling = false>
 struct RawPtrBackupRefImpl {
   // These are needed for correctness, or else we may end up manipulating
   // ref-count where we shouldn't, thus affecting the BRP's integrity. Unlike
@@ -51,11 +51,6 @@
 
  private:
   PA_ALWAYS_INLINE static bool UseBrp(uintptr_t address) {
-    // BRP is temporarily disabled for Pointers annotated with
-    // DisableBRP.
-    if constexpr (DisableBRP) {
-      return false;
-    }
     return partition_alloc::IsManagedByPartitionAllocBRPPool(address);
   }
 
diff --git a/base/allocator/partition_allocator/src/partition_alloc/pointers/raw_ptr_unittest.cc b/base/allocator/partition_allocator/src/partition_alloc/pointers/raw_ptr_unittest.cc
index 7f70da8..7fec9ba5 100644
--- a/base/allocator/partition_allocator/src/partition_alloc/pointers/raw_ptr_unittest.cc
+++ b/base/allocator/partition_allocator/src/partition_alloc/pointers/raw_ptr_unittest.cc
@@ -2444,54 +2444,6 @@
   partition_alloc::PartitionAllocHooks::SetQuarantineOverrideHook(nullptr);
 }
 
-TEST_F(BackupRefPtrTest, RawPtrTraits_DisableBRP) {
-  // Allocate a slot so that a slot span doesn't get decommitted from memory,
-  // while we allocate/deallocate/access the tested slot below.
-  void* sentinel = allocator_.root()->Alloc(sizeof(unsigned int), "");
-  constexpr uint32_t kQuarantined2Bytes =
-      partition_alloc::internal::kQuarantinedByte |
-      (partition_alloc::internal::kQuarantinedByte << 8);
-  constexpr uint32_t kQuarantined4Bytes =
-      kQuarantined2Bytes | (kQuarantined2Bytes << 16);
-
-  {
-    raw_ptr<unsigned int, DanglingUntriaged> ptr = static_cast<unsigned int*>(
-        allocator_.root()->Alloc(sizeof(unsigned int), ""));
-    *ptr = 0;
-    // Freeing would  update the MTE tag so use |TagPtr()| to dereference it
-    // below.
-    allocator_.root()->Free(ptr);
-#if PA_BUILDFLAG(DCHECKS_ARE_ON) || \
-    PA_BUILDFLAG(ENABLE_BACKUP_REF_PTR_SLOW_CHECKS)
-    // Recreate the raw_ptr so we can use a pointer with the updated MTE tag.
-    // Reassigning to |ptr| would hit the PartitionRefCount cookie check rather
-    // than the |IsPointeeAlive()| check.
-    raw_ptr<unsigned int, DanglingUntriaged> dangling_ptr =
-        partition_alloc::internal::TagPtr(ptr.get());
-    EXPECT_DEATH_IF_SUPPORTED(*dangling_ptr = 0, "");
-#else
-    EXPECT_EQ(kQuarantined4Bytes,
-              *partition_alloc::internal::TagPtr(ptr.get()));
-#endif
-  }
-  // raw_ptr with DisableBRP, BRP is expected to be off.
-  {
-    raw_ptr<unsigned int, DanglingUntriaged | RawPtrTraits::kDisableBRP> ptr =
-        static_cast<unsigned int*>(
-            allocator_.root()->Alloc(sizeof(unsigned int), ""));
-    *ptr = 0;
-    allocator_.root()->Free(ptr);
-    // A tad fragile as a new allocation or free-list pointer may be there, but
-    // highly unlikely it'll match 4 quarantine bytes in a row.
-    // Use |TagPtr()| for this dereference because freeing would have updated
-    // the MTE tag.
-    EXPECT_NE(kQuarantined4Bytes,
-              *partition_alloc::internal::TagPtr(ptr.get()));
-  }
-
-  allocator_.root()->Free(sentinel);
-}
-
 #endif  // PA_BUILDFLAG(USE_RAW_PTR_BACKUP_REF_IMPL) &&
         // !defined(MEMORY_TOOL_REPLACES_ALLOCATOR)
 
diff --git a/base/cpu.cc b/base/cpu.cc
index 7a053915..01107b90 100644
--- a/base/cpu.cc
+++ b/base/cpu.cc
@@ -43,6 +43,10 @@
 #endif
 #endif
 
+#if BUILDFLAG(IS_MAC)
+#include "base/mac/mac_util.h"
+#endif
+
 namespace base {
 
 #if defined(ARCH_CPU_X86_FAMILY)
@@ -151,6 +155,10 @@
 }  // namespace
 
 void CPU::Initialize() {
+#if BUILDFLAG(IS_MAC)
+  is_running_in_vm_ = mac::IsVirtualMachine();
+#endif
+
 #if defined(ARCH_CPU_X86_FAMILY)
   int cpu_info[4] = {-1, 0, 0, 0};
 
@@ -204,7 +212,9 @@
     // This is checking for any hypervisor. Hypervisors may choose not to
     // announce themselves. Hypervisors trap CPUID and sometimes return
     // different results to underlying hardware.
+#if !BUILDFLAG(IS_MAC)
     is_running_in_vm_ = (static_cast<uint32_t>(cpu_info[2]) & 0x80000000) != 0;
+#endif
 
     // AVX instructions will generate an illegal instruction exception unless
     //   a) they are supported by the CPU,
diff --git a/base/sampling_heap_profiler/lock_free_address_hash_set.cc b/base/sampling_heap_profiler/lock_free_address_hash_set.cc
index e48f597..fa47194 100644
--- a/base/sampling_heap_profiler/lock_free_address_hash_set.cc
+++ b/base/sampling_heap_profiler/lock_free_address_hash_set.cc
@@ -4,6 +4,7 @@
 
 #include "base/sampling_heap_profiler/lock_free_address_hash_set.h"
 
+#include <algorithm>
 #include <atomic>
 #include <bit>
 #include <limits>
@@ -24,9 +25,37 @@
 
 namespace {
 
-// See the probability table in lock_free_bloom_filter.h to estimate the
-// optimal bits per key. It's a tradeoff between better performance for the most
-// common table sizes and better performance at outliers.
+// Choosing the parameters of the Bloom filter:
+//
+// The estimated false positive rate is approximately (1-e^(-kn/m))^k, where
+// k is the number of hash functions (bits per key), m is the number of bits
+// in the filter, and n is the number of keys. (See
+// https://en.wikipedia.org/wiki/Bloom_filter#Probability_of_false_positives.)
+//
+// Since the implementation uses a fixed size, m is hardcoded to 64. This gives
+// estimated false positives (in percent) of:
+//
+//           k=2     k=3     k=4     k=5     k=6     k=7     k=8     k=9    k=10
+// n=1:     0.09    0.01    0.00    0.00    0.00    0.00    0.00    0.00    0.00
+// n=2:     0.37    0.07    0.02    0.01    0.00    0.00    0.00    0.00    0.00
+// n=3:     0.80    0.23    0.09    0.04    0.02    0.01    0.01    0.01    0.01
+// n=4:     1.38    0.50    0.24    0.14    0.09    0.07    0.06    0.05    0.05
+// n=5:     2.09    0.91    0.52    0.35    0.27    0.24    0.22    0.21    0.22
+// n=6:     2.92    1.47    0.96    0.73    0.63    0.60    0.60    0.63    0.69
+// n=7:     3.86    2.19    1.58    1.33    1.24    1.25    1.34    1.48    1.69
+// n=8:     4.89    3.06    2.40    2.17    2.16    2.29    2.55    2.92    3.42
+// n=10:    7.20    5.24    4.66    4.68    5.07    5.75    6.72    7.97    9.51
+// n=20:   21.60   22.52   25.92   30.85   36.83   43.47   50.40   57.27   63.81
+// n=40:   50.91   60.69   70.99   79.88   86.69   91.51   94.74   96.81   98.09
+// n=80:   84.26   93.11   97.33   99.04   99.67   99.89   99.96   99.99  100.00
+// n=100:  91.41   97.26   99.23   99.80   99.95   99.99  100.00  100.00  100.00
+//
+// Please update this table if kMaxLockFreeBloomFilterBits changes.
+//
+// This can be used to estimate the optimal number of hash functions (k) for the
+// expected number of keys that will be added. It's a tradeoff between better
+// performance for the most common table sizes and better performance at
+// outliers.
 //
 // Field data shows that on most platforms the hash table has about 5-10 entries
 // at the 50th percentile, 10-20 entries at the 75th percentile, and 40-100
@@ -43,8 +72,32 @@
 // 4 bits per key:  0.5% to  4.7% at the 50th
 //                  4.7% to 25.0% at the 75th
 //                 71.0% to 99.2% at the 99th
-constexpr base::FeatureParam<size_t> kBitsPerKey{&kUseLockFreeBloomFilter,
-                                                 "bits_per_key", 3};
+//
+// 3 bits per key seems optimal, but even that isn't very good.
+//
+// Individual buckets hold about 1-2 entries at the 50th percentile, 2-4 at the
+// 75th, 4-6 at the 99th, and 5-7 at the 99.9th, depending on process and
+// platform. With a filter for each bucket, that gives expected false positive
+// rates of:
+//
+// 6 bits per key: 0.0%  to 0.09% at the 75th
+//                 0.09% to 0.63% at the 99th
+//                 0.27% to 1.24% at the 99.9th
+//
+// 7 bits per key: 0.0%  to 0.07% at the 75th
+//                 0.07% to 0.60% at the 99th
+//                 0.24% to 1.25% at the 99.9th
+//
+// 8 bits per key: 0.0%  to 0.06% at the 75th
+//                 0.06% to 0.60% at the 99th
+//                 0.22% to 1.34% at the 99.9th
+//
+// 9 bits per key: 0.0%  to 0.05% at the 75th
+//                 0.05% to 0.63% at the 99th
+//                 0.21% to 1.48% at the 99.9th
+//
+// 7 or 8 bits per key seems optimal: after that point the false positive rate
+// starts to rise at the 99th, which could lead to spikes of poor performance.
 
 // Returns the result of a chi-squared test showing how evenly keys are
 // distributed. `bucket_key_counts` is the count of keys stored in each bucket.
@@ -70,19 +123,18 @@
 }  // namespace
 
 LockFreeAddressHashSet::LockFreeAddressHashSet(size_t buckets_count, Lock& lock)
-    : lock_(lock), buckets_(buckets_count), bucket_mask_(buckets_count - 1) {
-  DCHECK(std::has_single_bit(buckets_count));
-  DCHECK_LE(bucket_mask_, std::numeric_limits<uint32_t>::max());
-  if (base::FeatureList::IsEnabled(kUseLockFreeBloomFilter)) {
-    const size_t bits_per_key = kBitsPerKey.Get();
-    CHECK_GT(bits_per_key, 0u);
-    filter_.emplace(bits_per_key);
-  }
+    : lock_(lock),
+      buckets_(buckets_count),
+      bucket_mask_(buckets_count - 1),
+      bloom_filters_enabled_(
+          base::FeatureList::IsEnabled(kUseLockFreeBloomFilter)) {
+  CHECK(std::has_single_bit(buckets_count));
+  CHECK_LE(bucket_mask_, std::numeric_limits<uint32_t>::max());
 }
 
 LockFreeAddressHashSet::~LockFreeAddressHashSet() {
-  for (std::atomic<Node*>& bucket : buckets_) {
-    Node* node = bucket.load(std::memory_order_relaxed);
+  for (const Bucket& bucket : buckets_) {
+    Node* node = bucket.head.load(std::memory_order_relaxed);
     while (node) {
       Node* next = node->next;
       delete node;
@@ -93,7 +145,7 @@
 
 void LockFreeAddressHashSet::Insert(void* key) {
   lock_->AssertAcquired();
-  DCHECK_NE(key, nullptr);
+  CHECK_NE(key, nullptr);
   CHECK_NE(Contains(key), ContainsResult::kFound);
 
   // Also store the key in the bloom filter.
@@ -175,15 +227,15 @@
   // Each of the 3 states that could be seen by T1 due to unsynchronized updates
   // of `filter_` and `buckets_` could already be seen by T1 if the updates were
   // sequentially consistent.
-  if (filter_) {
-    filter_->Add(key);
+  Bucket& bucket = buckets_[Hash(key) & bucket_mask_];
+  if (bloom_filters_enabled_) {
+    bucket.filter.Add(key);
   }
 
   ++size_;
   // Note: There's no need to use std::atomic_compare_exchange here,
   // as we do not support concurrent inserts, so values cannot change midair.
-  std::atomic<Node*>& bucket = buckets_[Hash(key) & bucket_mask_];
-  Node* node = bucket.load(std::memory_order_relaxed);
+  Node* node = bucket.head.load(std::memory_order_relaxed);
   // First iterate over the bucket nodes and try to reuse an empty one if found.
   for (; node != nullptr; node = node->next) {
     if (node->key.load(std::memory_order_relaxed) == nullptr) {
@@ -193,16 +245,62 @@
   }
   // There are no empty nodes to reuse left in the bucket.
   // Create a new node first...
-  Node* new_node = new Node(key, bucket.load(std::memory_order_relaxed));
+  Node* new_node = new Node(key, bucket.head.load(std::memory_order_relaxed));
   // ... and then publish the new chain.
-  bucket.store(new_node, std::memory_order_release);
+  bucket.head.store(new_node, std::memory_order_release);
+}
+
+void LockFreeAddressHashSet::Remove(void* key) {
+  lock_->AssertAcquired();
+  Bucket& bucket = buckets_[Hash(key) & bucket_mask_];
+
+  // Keys can't be removed from the bloom filter, so rebuild it from scratch to
+  // keep the false positive rate as low as possible. The overhead of rebuilding
+  // the filter on every delete is acceptable because adding and removing keys
+  // is extremely rare compared to looking them up. (See the comment in Insert()
+  // explaining why this is thread-safe.)
+  LockFreeBloomFilterBits bits = 0;
+
+  // memory_order_relaxed is enough here because the lock prevents the head
+  // pointer from being modified during Remove().
+  Node* node = bucket.head.load(std::memory_order_relaxed);
+  for (; node != nullptr; node = node->next) {
+    void* node_key = node->key.load(std::memory_order_relaxed);
+    if (node_key == key) {
+      break;
+    }
+    // Rebuild the bloom filter as we go, without the removed key.
+    if (bloom_filters_enabled_ && node_key != nullptr) {
+      bits |= bucket.filter.GetBitsForKey(node_key);
+    }
+  }
+  DCHECK_NE(node, nullptr);
+
+  // We can never delete the node, nor detach it from the current bucket as
+  // there may always be another thread currently iterating over it. Instead we
+  // just mark it as empty, so |Insert| can reuse it later.
+  node->key.store(nullptr, std::memory_order_relaxed);
+  --size_;
+
+  // Finish rebuilding the bloom filter if needed.
+  if (bloom_filters_enabled_) {
+    for (node = node->next; node != nullptr; node = node->next) {
+      void* node_key = node->key.load(std::memory_order_relaxed);
+      CHECK_NE(node_key, key);
+      if (node_key != nullptr) {
+        bits |= bucket.filter.GetBitsForKey(node_key);
+      }
+    }
+
+    bucket.filter.AtomicSetBits(bits);
+  }
 }
 
 void LockFreeAddressHashSet::Copy(const LockFreeAddressHashSet& other) {
   lock_->AssertAcquired();
-  DCHECK_EQ(0u, size());
-  for (const std::atomic<Node*>& bucket : other.buckets_) {
-    for (const Node* node = bucket.load(std::memory_order_relaxed); node;
+  CHECK_EQ(0u, size());
+  for (const Bucket& bucket : other.buckets_) {
+    for (const Node* node = bucket.head.load(std::memory_order_relaxed); node;
          node = node->next) {
       void* key = node->key.load(std::memory_order_relaxed);
       if (key) {
@@ -219,13 +317,13 @@
   lengths.reserve(buckets_.size());
   std::vector<size_t> key_counts;
   key_counts.reserve(buckets_.size());
-  for (const std::atomic<Node*>& bucket : buckets_) {
+  for (const Bucket& bucket : buckets_) {
     // Bucket length includes all nodes, including ones with null keys, since
     // they will need to be searched when iterating. Key count only includes
     // real keys.
     size_t length = 0;
     size_t key_count = 0;
-    for (const Node* node = bucket.load(std::memory_order_relaxed);
+    for (const Node* node = bucket.head.load(std::memory_order_relaxed);
          node != nullptr; node = node->next) {
       ++length;
       if (node->key.load(std::memory_order_relaxed)) {
@@ -238,19 +336,14 @@
   return BucketStats(std::move(lengths), ChiSquared(key_counts));
 }
 
-void LockFreeAddressHashSet::RebuildFilter() {
-  DCHECK(filter_);
+size_t LockFreeAddressHashSet::MaxBloomFilterSaturation() const {
+  CHECK(bloom_filters_enabled_);
   lock_->AssertAcquired();
-  LockFreeBloomFilter::BitStorage bits = 0;
-  for (const std::atomic<Node*>& bucket : buckets_) {
-    for (const Node* node = bucket.load(std::memory_order_relaxed); node;
-         node = node->next) {
-      if (void* key = node->key.load(std::memory_order_relaxed)) {
-        bits |= filter_->GetBitsForKey(key);
-      }
-    }
+  size_t max_bits = 0;
+  for (const Bucket& bucket : buckets_) {
+    max_bits = std::max(max_bits, bucket.filter.CountBits());
   }
-  filter_->AtomicSetBits(bits);
+  return max_bits;
 }
 
 LockFreeAddressHashSet::BucketStats::BucketStats(std::vector<size_t> lengths,
diff --git a/base/sampling_heap_profiler/lock_free_address_hash_set.h b/base/sampling_heap_profiler/lock_free_address_hash_set.h
index 549ee81..fe4f758 100644
--- a/base/sampling_heap_profiler/lock_free_address_hash_set.h
+++ b/base/sampling_heap_profiler/lock_free_address_hash_set.h
@@ -7,7 +7,6 @@
 
 #include <atomic>
 #include <cstdint>
-#include <optional>
 #include <vector>
 
 #include "base/base_export.h"
@@ -19,7 +18,6 @@
 #include "base/sampling_heap_profiler/lock_free_bloom_filter.h"
 #include "base/synchronization/lock.h"
 #include "base/thread_annotations.h"
-#include "base/types/optional_util.h"
 
 namespace base {
 
@@ -35,8 +33,15 @@
 // However, please note the result of concurrent execution of |Contains|
 // with |Insert| or |Remove| over the same key is racy.
 //
+// The destructor must only be called from single-threaded context because it is
+// unsafe to call while any thread is calling |Contains|. In practice
+// LockFreeAddressHashSet objects are only destroyed in tests. In production
+// PoissonAllocationSampler uses a global LockFreeAddressHashSet that is leaked.
+//
 // The hash set never rehashes, so the number of buckets stays the same
-// for the lifetime of the set.
+// for the lifetime of the set. PoissonAllocationSampler handles rebalancing by
+// creating a larger LockFreeAddressHashSet to take over as the global set and
+// leaking the original.
 //
 // Internally the hashset is implemented as a vector of N buckets
 // (N has to be a power of 2). Each bucket holds a single-linked list of
@@ -69,9 +74,10 @@
     double chi_squared = 0.0;
   };
 
-  // Creates a hash set with `buckets_count` buckets. `lock` is a lock that
-  // must be held by callers of |Insert|, |Remove| and |Copy|. |Contains| is
-  // lock-free.
+  // Creates a hash set with `buckets_count` buckets (which must be a power of
+  // 2). `lock` is a reference to a global lock (shared by all
+  // LockFreeAddressHashSet instances) that must be held by callers of |Insert|,
+  // |Remove| and |Copy|. |Contains| is lock-free.
   LockFreeAddressHashSet(size_t buckets_count, Lock& lock);
 
   ~LockFreeAddressHashSet();
@@ -98,7 +104,7 @@
   // Removes the |key|, which must not be nullptr, from the set. The key must be
   // present in the set before the invocation. Concurrent execution of |Insert|,
   // |Remove|, or |Copy| is not supported.
-  ALWAYS_INLINE void Remove(void* key);
+  void Remove(void* key);
 
   // Inserts the |key|, which must not be nullptr, into the set. The key must
   // not be present in the set before the invocation. Also adds the key to the
@@ -132,10 +138,11 @@
   // |Insert|, |Remove| or |Copy|.
   BucketStats GetBucketStats() const;
 
-  LockFreeBloomFilter* bloom_filter() { return base::OptionalToPtr(filter_); }
-  const LockFreeBloomFilter* bloom_filter() const {
-    return base::OptionalToPtr(filter_);
-  }
+  // Returns true if bloom filters are enabled.
+  bool HasBloomFilter() const { return bloom_filters_enabled_; }
+
+  // Returns the highest number of bits set in any bloom filter, for metrics.
+  size_t MaxBloomFilterSaturation() const;
 
  private:
   friend class LockFreeAddressHashSetTest;
@@ -151,64 +158,58 @@
     RAW_PTR_EXCLUSION Node* next;
   };
 
-  // Returns the node containing `key` (which must not be null), or nullptr if
-  // it's not in the hash set.
-  ALWAYS_INLINE Node* FindNode(void* key);
-  ALWAYS_INLINE const Node* FindNode(void* key) const;
+  struct Bucket {
+    // The current head Node for this bucket, potentially updated under `lock_`
+    // with memory_order_release semantics during Insert(). Readers under
+    // `lock_` can read it with memory_order_relaxed, but concurrent readers
+    // (Contains) must use memory_order_acquire to ensure they view a fully
+    // initialized Node.
+    std::atomic<Node*> head;
+
+    // A bloom filter to speed up lookups of addresses not in the hash set.
+    // If a key hashes to this bucket, it can be checked against this filter
+    // (which is a simple bit compare) before spending the overhead of
+    // traversing the bucket nodes.
+    // The BitsPerKey param is set to 8 based on the table in
+    // lock_free_address_hash_set.cc. However 32-bit platforms only have room
+    // for 5 keys.
+    LockFreeBloomFilter<sizeof(size_t) < 8 ? 5 : 8> filter;
+  };
+
+  // Returns the node in `bucket` containing `key` (which must not be null), or
+  // nullptr if it's not in the hash set.
+  ALWAYS_INLINE const Node* FindNode(const Bucket& bucket, void* key) const;
 
   // Returns the hash of `key`.
   ALWAYS_INLINE static uint32_t Hash(void* key);
 
-  // Resets the bloom filter to exactly match the contents of the set.
-  void RebuildFilter();
-
   raw_ref<Lock> lock_;
 
-  std::vector<std::atomic<Node*>> buckets_;
+  std::vector<Bucket> buckets_;
   size_t size_ GUARDED_BY(lock_) = 0;
   const size_t bucket_mask_;
-
-  // A bloom filter to speed up lookups of addresses not in the hash set.
-  std::optional<LockFreeBloomFilter> filter_;
+  const bool bloom_filters_enabled_;
 };
 
 ALWAYS_INLINE LockFreeAddressHashSet::ContainsResult
 LockFreeAddressHashSet::Contains(void* key) const {
-  if (!filter_) {
-    return FindNode(key) ? ContainsResult::kFound : ContainsResult::kNotFound;
+  const Bucket& bucket = buckets_[Hash(key) & bucket_mask_];
+  if (!bloom_filters_enabled_) {
+    return FindNode(bucket, key) ? ContainsResult::kFound
+                                 : ContainsResult::kNotFound;
   }
-  if (filter_->MaybeContains(key)) {
+  if (bucket.filter.MaybeContains(key)) {
     // The filter may have false positives, so need to check the hash set.
-    return FindNode(key) ? ContainsResult::kFound
-                         : ContainsResult::kNotFoundButMatchedInBloomFilter;
+    return FindNode(bucket, key)
+               ? ContainsResult::kFound
+               : ContainsResult::kNotFoundButMatchedInBloomFilter;
   }
   return ContainsResult::kNotFound;
 }
 
-ALWAYS_INLINE void LockFreeAddressHashSet::Remove(void* key) {
-  lock_->AssertAcquired();
-  Node* node = FindNode(key);
-  DCHECK_NE(node, nullptr);
-  // We can never delete the node, nor detach it from the current bucket
-  // as there may always be another thread currently iterating over it.
-  // Instead we just mark it as empty, so |Insert| can reuse it later.
-  node->key.store(nullptr, std::memory_order_relaxed);
-  --size_;
-
-  // Keys can't be removed from the bloom filter, so rebuild it from scratch to
-  // keep the false positive rate as low as possible. The overhead of rebuilding
-  // the filter on every delete is acceptable because adding and removing keys
-  // is extremely rare compared to looking them up. (See the comment in Insert()
-  // explaining why this is thread-safe.)
-  if (filter_) {
-    RebuildFilter();
-  }
-}
-
-ALWAYS_INLINE LockFreeAddressHashSet::Node* LockFreeAddressHashSet::FindNode(
-    void* key) {
+ALWAYS_INLINE const LockFreeAddressHashSet::Node*
+LockFreeAddressHashSet::FindNode(const Bucket& bucket, void* key) const {
   DCHECK_NE(key, nullptr);
-  const std::atomic<Node*>& bucket = buckets_[Hash(key) & bucket_mask_];
   // It's enough to use std::memory_order_consume ordering here, as the
   // node->next->...->next loads form a dependency chain.
   // However std::memory_order_consume is temporarily deprecated in C++17.
@@ -222,8 +223,8 @@
   // happens-before" - but "Release-Consume ordering" still carries the note
   // that it's "temporarily discouraged" so it's unclear if it's now safe to use
   // here.
-  for (Node* node = bucket.load(std::memory_order_acquire); node != nullptr;
-       node = node->next) {
+  for (Node* node = bucket.head.load(std::memory_order_acquire);
+       node != nullptr; node = node->next) {
     if (node->key.load(std::memory_order_relaxed) == key) {
       return node;
     }
@@ -231,11 +232,6 @@
   return nullptr;
 }
 
-ALWAYS_INLINE const LockFreeAddressHashSet::Node*
-LockFreeAddressHashSet::FindNode(void* key) const {
-  return const_cast<LockFreeAddressHashSet*>(this)->FindNode(key);
-}
-
 // static
 ALWAYS_INLINE uint32_t LockFreeAddressHashSet::Hash(void* key) {
   // A simple fast hash function for addresses.
diff --git a/base/sampling_heap_profiler/lock_free_address_hash_set_unittest.cc b/base/sampling_heap_profiler/lock_free_address_hash_set_unittest.cc
index 72c31cc..7cf0821 100644
--- a/base/sampling_heap_profiler/lock_free_address_hash_set_unittest.cc
+++ b/base/sampling_heap_profiler/lock_free_address_hash_set_unittest.cc
@@ -14,6 +14,7 @@
 #include "base/memory/raw_ref.h"
 #include "base/synchronization/lock.h"
 #include "base/test/gtest_util.h"
+#include "base/test/scoped_feature_list.h"
 #include "base/threading/simple_thread.h"
 #include "partition_alloc/shim/allocator_shim.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -22,14 +23,18 @@
 
 using ContainsResult = LockFreeAddressHashSet::ContainsResult;
 
-class LockFreeAddressHashSetTest : public ::testing::Test {
+class LockFreeAddressHashSetTest : public ::testing::TestWithParam<bool> {
  public:
+  LockFreeAddressHashSetTest() {
+    scoped_feature_list_.InitWithFeatureState(kUseLockFreeBloomFilter,
+                                              GetParam());
+  }
+
   static bool IsSubset(const LockFreeAddressHashSet& superset,
                        const LockFreeAddressHashSet& subset) {
-    for (const std::atomic<LockFreeAddressHashSet::Node*>& bucket :
-         subset.buckets_) {
+    for (const LockFreeAddressHashSet::Bucket& bucket : subset.buckets_) {
       for (const LockFreeAddressHashSet::Node* node =
-               bucket.load(std::memory_order_acquire);
+               bucket.head.load(std::memory_order_acquire);
            node; node = node->next) {
         void* key = node->key.load(std::memory_order_relaxed);
         if (key && superset.Contains(key) != ContainsResult::kFound) {
@@ -49,7 +54,7 @@
   static size_t BucketSize(const LockFreeAddressHashSet& set, size_t bucket) {
     size_t count = 0;
     for (const LockFreeAddressHashSet::Node* node =
-             set.buckets_[bucket].load(std::memory_order_acquire);
+             set.buckets_[bucket].head.load(std::memory_order_acquire);
          node; node = node->next) {
       if (node->key.load(std::memory_order_relaxed) != nullptr) {
         ++count;
@@ -58,22 +63,20 @@
     return count;
   }
 
-  // Returns the number of nodes in `bucket`, whether or not they contain keys.
-  static size_t BucketCapacity(const LockFreeAddressHashSet& set,
-                               size_t bucket) {
-    size_t capacity = 0;
-    for (const LockFreeAddressHashSet::Node* node =
-             set.buckets_[bucket].load(std::memory_order_acquire);
-         node; node = node->next) {
-      ++capacity;
-    }
-    return capacity;
-  }
+ protected:
+  base::test::ScopedFeatureList scoped_feature_list_;
 };
 
 using LockFreeAddressHashSetDeathTest = LockFreeAddressHashSetTest;
 
-TEST_F(LockFreeAddressHashSetTest, EmptySet) {
+INSTANTIATE_TEST_SUITE_P(EnableBloomFilter,
+                         LockFreeAddressHashSetTest,
+                         ::testing::Bool());
+INSTANTIATE_TEST_SUITE_P(EnableBloomFilter,
+                         LockFreeAddressHashSetDeathTest,
+                         ::testing::Bool());
+
+TEST_P(LockFreeAddressHashSetTest, EmptySet) {
   Lock lock;
   LockFreeAddressHashSet set(8, lock);
 
@@ -84,7 +87,7 @@
   EXPECT_NE(set.Contains(&set), ContainsResult::kFound);
 }
 
-TEST_F(LockFreeAddressHashSetTest, BasicOperations) {
+TEST_P(LockFreeAddressHashSetTest, BasicOperations) {
   Lock lock;
   LockFreeAddressHashSet set(8, lock);
 
@@ -116,7 +119,7 @@
   }
 }
 
-TEST_F(LockFreeAddressHashSetTest, Copy) {
+TEST_P(LockFreeAddressHashSetTest, Copy) {
   Lock lock;
   LockFreeAddressHashSet set(16, lock);
 
@@ -178,7 +181,7 @@
   raw_ref<std::atomic_bool> cancel_;
 };
 
-TEST_F(LockFreeAddressHashSetTest, ConcurrentAccess) {
+TEST_P(LockFreeAddressHashSetTest, ConcurrentAccess) {
   // The purpose of this test is to make sure adding/removing keys concurrently
   // does not disrupt the state of other keys.
   Lock lock;
@@ -214,7 +217,7 @@
             ContainsResult::kFound);
 }
 
-TEST_F(LockFreeAddressHashSetTest, BucketsUsage) {
+TEST_P(LockFreeAddressHashSetTest, BucketsUsage) {
   // Test the uniformity of buckets usage.
   size_t count = 10000;
   Lock lock;
@@ -237,7 +240,7 @@
   EXPECT_LE(set.GetBucketStats().chi_squared, 1.05);
 }
 
-TEST_F(LockFreeAddressHashSetDeathTest, LockAsserts) {
+TEST_P(LockFreeAddressHashSetDeathTest, LockAsserts) {
   Lock lock;
   LockFreeAddressHashSet set(8, lock);
   LockFreeAddressHashSet set2(8, lock);
diff --git a/base/sampling_heap_profiler/lock_free_bloom_filter.cc b/base/sampling_heap_profiler/lock_free_bloom_filter.cc
deleted file mode 100644
index 7c1e8f41..0000000
--- a/base/sampling_heap_profiler/lock_free_bloom_filter.cc
+++ /dev/null
@@ -1,82 +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 "base/sampling_heap_profiler/lock_free_bloom_filter.h"
-
-#include <stddef.h>
-#include <stdint.h>
-
-#include <array>
-#include <atomic>
-#include <bitset>
-
-#include "base/compiler_specific.h"
-#include "base/containers/span.h"
-#include "base/hash/hash.h"
-
-namespace base {
-
-namespace {
-
-ALWAYS_INLINE LockFreeBloomFilter::BitStorage CreateBitmask(
-    void* ptr,
-    size_t num_hash_functions,
-    bool use_fake_hash_functions) {
-  LockFreeBloomFilter::BitStorage bitmask = 0;
-  const uintptr_t int_ptr = reinterpret_cast<uintptr_t>(ptr);
-  for (size_t i = 0; i < num_hash_functions; ++i) {
-    std::array<uintptr_t, 2> hash_input{int_ptr, i};
-    const size_t hash = use_fake_hash_functions
-                            ? (int_ptr >> i)
-                            : base::FastHash(base::as_byte_span(hash_input));
-    // Make sure the argument of << is the same type as `bitmask` to avoid
-    // undefined behaviour on overflow if size_t is narrower.
-    static constexpr LockFreeBloomFilter::BitStorage kOneBit = 1;
-    bitmask |= kOneBit << (hash % LockFreeBloomFilter::kMaxBits);
-  }
-  return bitmask;
-}
-
-}  // namespace
-
-LockFreeBloomFilter::LockFreeBloomFilter(size_t num_hash_functions)
-    : num_hash_functions_(num_hash_functions) {}
-
-LockFreeBloomFilter::~LockFreeBloomFilter() = default;
-
-bool LockFreeBloomFilter::MaybeContains(void* ptr) const {
-  // `ptr` is potentially in the filter iff ALL bits in the bitmask are set.
-  const BitStorage bitmask =
-      CreateBitmask(ptr, num_hash_functions_, use_fake_hash_functions_);
-  return (bits_.load(std::memory_order_relaxed) & bitmask) == bitmask;
-}
-
-void LockFreeBloomFilter::Add(void* ptr) {
-  const BitStorage bitmask =
-      CreateBitmask(ptr, num_hash_functions_, use_fake_hash_functions_);
-  bits_.fetch_or(bitmask, std::memory_order_relaxed);
-}
-
-void LockFreeBloomFilter::AtomicSetBits(BitStorage bits) {
-  // memory_order_relaxed is sufficient because this function only guarantees
-  // that `bits_` is updated atomically. If the caller has other data depending
-  // on `bits_`, it's up to them to enforce ordering.
-  bits_.store(bits, std::memory_order_relaxed);
-}
-
-LockFreeBloomFilter::BitStorage LockFreeBloomFilter::GetBitsForKey(
-    void* ptr) const {
-  return CreateBitmask(ptr, num_hash_functions_, use_fake_hash_functions_);
-}
-
-LockFreeBloomFilter::BitStorage LockFreeBloomFilter::GetBitsForTesting() const {
-  return bits_.load(std::memory_order_relaxed);
-}
-
-size_t LockFreeBloomFilter::CountBits() const {
-  // Relaxed ordering is enough since this is only for statistics.
-  return std::bitset<kMaxBits>(bits_.load(std::memory_order_relaxed)).count();
-}
-
-}  // namespace base
diff --git a/base/sampling_heap_profiler/lock_free_bloom_filter.h b/base/sampling_heap_profiler/lock_free_bloom_filter.h
index 399fa56..3f98807 100644
--- a/base/sampling_heap_profiler/lock_free_bloom_filter.h
+++ b/base/sampling_heap_profiler/lock_free_bloom_filter.h
@@ -9,8 +9,11 @@
 #include <stdint.h>
 
 #include <atomic>
+#include <bit>
+#include <bitset>
 
-#include "base/base_export.h"
+#include "base/containers/span.h"
+#include "base/hash/hash.h"
 
 namespace base {
 
@@ -47,103 +50,115 @@
 // This class only guarantees that accessing the Bloom filter is thread-safe.
 // The caller is responsible for ensuring that accessing both the filter and
 // LockFreeAddressHashSet from multiple threads gives consistent results.
-class BASE_EXPORT LockFreeBloomFilter {
+//
+// See lock_free_address_hash_set.cc for a table of estimated false positive
+// rates when the filter is used with LockFreeAddressHashSet.
+
+// An integer to hold the bits that are set in the filter.
+using LockFreeBloomFilterBits = uint64_t;
+
+// Maximum number of bits in the filter.
+static constexpr size_t kMaxLockFreeBloomFilterBits =
+    8 * sizeof(LockFreeBloomFilterBits);
+
+// A bloom filter of `kMaxLockFreeBloomFilterBits` size that sets up to
+// `BitsPerKey` bits per entry. Each key added to the filter is hashed with
+// `BitsPerKey` separate hash functions, each of which returns the index of a
+// bit to set. (Fewer bits can be set for a given entry if multiple hash
+// functions return the same index.)
+//
+// If UseFakeHashFunctionsForTesting is `true`, hashing a key with hash function
+// N will shift the key N bits to the right, allowing the test to precisely
+// control how keys are hashed.
+template <size_t BitsPerKey, bool UseFakeHashFunctionsForTesting = false>
+class LockFreeBloomFilter {
  public:
-  // An integer to hold the bits that are set in the filter.
-  //
-  // The estimated false positive rate is approximately (1-e^(-kn/m))^k, where
-  // k is the number of hash functions (bits per key), m is the number of bits
-  // in the filter, and n is the number of keys. (See
-  // https://en.wikipedia.org/wiki/Bloom_filter#Probability_of_false_positives.)
-  //
-  // Since this implementation uses a fixed size, m is hardcoded to 64. This
-  // gives estimated false positives of:
-  //
-  // k=2, n=5: 2.1%
-  // k=2, n=10: 7.2%
-  // k=2, n=20: 21.6%
-  // k=2, n=40: 50.9%
-  // k=2, n=80: 84.3%
-  // k=2, n=100: 91.4%
-  //
-  // k=3, n=5: 0.9%
-  // k=3, n=10: 5.2%
-  // k=3, n=20: 22.5%
-  // k=3, n=40: 60.7%
-  // k=3, n=80: 93.1%
-  // k=3, n=100: 97.3%
-  //
-  // k=4, n=5: 0.5%
-  // k=4, n=10: 4.7%
-  // k=4, n=20: 25.9%
-  // k=4, n=40: 71.0%
-  // k=4, n=80: 97.3%
-  // k=4, n=100: 99.2%
-  //
-  // Please update this table if the size of BitStorage changes. This can be
-  // used to estimate the optimal number of hash functions (k) for the expected
-  // number of keys that will be added.
-  using BitStorage = uint64_t;
-
-  // Atomic wrapper for the bits.
-  using AtomicBitStorage = std::atomic<BitStorage>;
-  static_assert(AtomicBitStorage::is_always_lock_free);
-
-  // Maximum number of bits in the filter.
-  static constexpr size_t kMaxBits = 8 * sizeof(BitStorage);
-
-  // Constructs a Bloom filter of `kMaxBits` size with zero-ed data and using
-  // `num_hash_functions` per entry.
-  explicit LockFreeBloomFilter(size_t num_hash_functions);
+  LockFreeBloomFilter() = default;
+  ~LockFreeBloomFilter() = default;
 
   LockFreeBloomFilter(const LockFreeBloomFilter&) = delete;
   LockFreeBloomFilter& operator=(const LockFreeBloomFilter&) = delete;
 
-  ~LockFreeBloomFilter();
+  // Returns the bits corresponding to `ptr` in this Bloom filter.
+  LockFreeBloomFilterBits GetBitsForKey(void* ptr) const;
 
   // Returns whether `ptr` may have been added as a key in this Bloom filter. If
   // this returns false, `ptr` is definitely not in the filter. Otherwise `ptr`
   // may or may not be in the filter, since Bloom filters inherently have false
   // positives. (See the table of estimated false positive rates above.)
-  bool MaybeContains(void* ptr) const;
+  bool MaybeContains(void* ptr) const {
+    // `ptr` is potentially in the filter iff ALL bits in the bitmask are set.
+    const LockFreeBloomFilterBits bitmask = GetBitsForKey(ptr);
+    return (bits_.load(std::memory_order_relaxed) & bitmask) == bitmask;
+  }
 
   // Adds `ptr` as a key in this Bloom filter. After this call
   // MaybeContains(ptr) will always return true.
-  void Add(void* ptr);
+  void Add(void* ptr) {
+    bits_.fetch_or(GetBitsForKey(ptr), std::memory_order_relaxed);
+  }
 
   // Atomically overwrites the bloom filter with `bits`.
-  void AtomicSetBits(BitStorage bits);
-
-  // Returns the bits corresponding to `ptr` in this Bloom filter.
-  BitStorage GetBitsForKey(void* ptr) const;
+  void AtomicSetBits(LockFreeBloomFilterBits bits) {
+    bits_.store(bits, std::memory_order_relaxed);
+  }
 
   // Returns the bit array data of this Bloom filter as an integer.
-  BitStorage GetBitsForTesting() const;
-
-  // If called with `true`, hashing a key with hash function N will shift the
-  // key N bits to the right, allowing the test to precisely control how keys
-  // are hashed. Calling this with `false` will restore the default hash
-  // functions.
-  void SetFakeHashFunctionsForTesting(bool use_fake_hash_functions) {
-    use_fake_hash_functions_ = use_fake_hash_functions;
+  LockFreeBloomFilterBits GetBitsForTesting() const {
+    return bits_.load(std::memory_order_relaxed);
   }
 
   // Returns the number of bits that are set, for stats.
-  size_t CountBits() const;
+  size_t CountBits() const {
+    return std::bitset<kMaxLockFreeBloomFilterBits>(
+               bits_.load(std::memory_order_relaxed))
+        .count();
+  }
 
  private:
-  // Number of bits to set for each added key.
-  const size_t num_hash_functions_;
-
   // Bit data for the filter.
   // Accessed with std::memory_order_relaxed since the class doesn't synchronize
   // access to pointed-to memory. Instead pointers passed to Add() are treated
   // as opaque keys.
-  AtomicBitStorage bits_ = 0;
-
-  bool use_fake_hash_functions_ = false;
+  static_assert(std::atomic<LockFreeBloomFilterBits>::is_always_lock_free);
+  std::atomic<LockFreeBloomFilterBits> bits_ = 0;
 };
 
+template <size_t BitsPerKey, bool UseFakeHashFunctionsForTesting>
+LockFreeBloomFilterBits
+LockFreeBloomFilter<BitsPerKey, UseFakeHashFunctionsForTesting>::GetBitsForKey(
+    void* ptr) const {
+  // If LockFreeBloomFilterBits is N bits wide, then an evenly-distributed hash
+  // function can be used as an index into it by masking off the last N-1 bits.
+  // (Equivalent to mod N when N is divisible by 2.)
+  static_assert(std::has_single_bit(kMaxLockFreeBloomFilterBits),
+                "kMaxLockFreeBloomFilterBits must be divisible by 2");
+
+  constexpr size_t kIndexMask = kMaxLockFreeBloomFilterBits - 1;
+  constexpr size_t kShiftWidth = std::bit_width(kIndexMask);
+  static_assert(BitsPerKey * kShiftWidth <= 8 * sizeof(size_t),
+                "Must be able to mask off `BitsPerKey` subsets of a size_t");
+
+  LockFreeBloomFilterBits bitmask = 0;
+  const uintptr_t int_ptr = reinterpret_cast<uintptr_t>(ptr);
+  size_t hash = base::FastHash(base::as_byte_span({int_ptr}));
+  for (size_t i = 0; i < BitsPerKey; ++i) {
+    if constexpr (UseFakeHashFunctionsForTesting) {
+      // Overwrite the previous hash with a fake.
+      hash = int_ptr >> i;
+    }
+    // Use the last bits of the hash as an index, and set the bit at that index.
+    // Make sure the argument of << is the same type as `bitmask` to avoid
+    // undefined behaviour on overflow if size_t is narrower.
+    constexpr LockFreeBloomFilterBits kOneBit = 1;
+    bitmask |= kOneBit << (hash & kIndexMask);
+
+    // Shift the used bits out of the hash.
+    hash >>= kShiftWidth;
+  }
+  return bitmask;
+}
+
 }  // namespace base
 
 #endif  // BASE_SAMPLING_HEAP_PROFILER_LOCK_FREE_BLOOM_FILTER_H_
diff --git a/base/sampling_heap_profiler/lock_free_bloom_filter_unittest.cc b/base/sampling_heap_profiler/lock_free_bloom_filter_unittest.cc
index d5fc2f2..84a08db 100644
--- a/base/sampling_heap_profiler/lock_free_bloom_filter_unittest.cc
+++ b/base/sampling_heap_profiler/lock_free_bloom_filter_unittest.cc
@@ -50,9 +50,11 @@
 // the same set of filters.
 class WriterThread : public SimpleThread {
  public:
+  using LockFreeBloomFilterType = LockFreeBloomFilter<2>;
+
   WriterThread(uintptr_t start_value,
                uintptr_t max_value,
-               LockFreeBloomFilter& filter,
+               LockFreeBloomFilterType& filter,
                base::TestWaitableEvent& all_started_event,
                base::AtomicFlag& cancel_flag,
                base::RepeatingClosure on_started_closure)
@@ -96,7 +98,7 @@
  private:
   uintptr_t start_value_;
   uintptr_t max_value_;
-  raw_ref<LockFreeBloomFilter> filter_;
+  raw_ref<LockFreeBloomFilterType> filter_;
   raw_ref<base::TestWaitableEvent> all_started_event_;
   raw_ref<base::AtomicFlag> cancel_flag_;
   base::RepeatingClosure on_started_closure_;
@@ -105,8 +107,8 @@
 }  // namespace
 
 TEST(LockFreeBloomFilterTest, SingleHash) {
-  LockFreeBloomFilter filter(/*num_hash_functions=*/1);
-  filter.SetFakeHashFunctionsForTesting(true);
+  LockFreeBloomFilter</*BitsPerKey=*/1, /*UseFakeHashFunctionsForTesting=*/true>
+      filter;
   EXPECT_EQ(0, filter.CountBits());
 
   // See the chart above the kAlpha definition for expected hash results.
@@ -152,8 +154,8 @@
 }
 
 TEST(LockFreeBloomFilterTest, MultiHash) {
-  LockFreeBloomFilter filter(/*num_hash_functions=*/3);
-  filter.SetFakeHashFunctionsForTesting(true);
+  LockFreeBloomFilter</*BitsPerKey=*/3, /*UseFakeHashFunctionsForTesting=*/true>
+      filter;
   EXPECT_EQ(filter.CountBits(), 0);
 
   // See the chart above the kAlpha definition for expected hash results.
@@ -206,11 +208,11 @@
 }
 
 TEST(LockFreeBloomFilterTest, FalsePositivesWithSingleBitFilterCollisions) {
-  LockFreeBloomFilter filter(/*num_hash_functions=*/1);
+  LockFreeBloomFilter<1> filter;
 
   // Loop until a hash collision occurs. This is guaranteed to happen by the
-  // time kMaxBits keys are added.
-  for (size_t i = 0; i <= LockFreeBloomFilter::kMaxBits; ++i) {
+  // time kMaxLockFreeBloomFilterBits keys are added.
+  for (size_t i = 0; i <= kMaxLockFreeBloomFilterBits; ++i) {
     void* ptr = reinterpret_cast<void*>(i);
     if (filter.MaybeContains(ptr)) {
       // Hash collision occurred. Adding the new key should appear to succeed,
@@ -228,14 +230,14 @@
     EXPECT_TRUE(filter.MaybeContains(ptr));
   }
 
-  FAIL() << "Added " << LockFreeBloomFilter::kMaxBits
+  FAIL() << "Added " << kMaxLockFreeBloomFilterBits
          << " keys without a false positive";
 }
 
 TEST(LockFreeBloomFilterTest, EverythingMatches) {
   // Provide filter data with all bits set ON.
-  LockFreeBloomFilter filter(/*num_hash_functions=*/7);
-  filter.AtomicSetBits(static_cast<LockFreeBloomFilter::BitStorage>(-1));
+  LockFreeBloomFilter<5> filter;
+  filter.AtomicSetBits(static_cast<LockFreeBloomFilterBits>(-1));
 
   EXPECT_TRUE(filter.MaybeContains(reinterpret_cast<void*>(kAlfa)));
   EXPECT_TRUE(filter.MaybeContains(reinterpret_cast<void*>(kBravo)));
@@ -249,7 +251,7 @@
   // does not disrupt the state of other keys. Each writer races to set the bits
   // for a single key. To get a high amount of parallelism they set the bits in
   // many filters.
-  LockFreeBloomFilter expected_filter(/*num_hash_functions=*/2);
+  WriterThread::LockFreeBloomFilterType expected_filter;
 
   // Add two dozen elements to `expected_filter`, in serial. Make sure this
   // doesn't saturate all the bits in the filter because that wouldn't be an
@@ -260,9 +262,9 @@
     void* ptr = reinterpret_cast<void*>(value);
     expected_filter.Add(ptr);
   }
-  ASSERT_LT(expected_filter.CountBits(), LockFreeBloomFilter::kMaxBits);
+  ASSERT_LT(expected_filter.CountBits(), kMaxLockFreeBloomFilterBits);
 
-  LockFreeBloomFilter filter(/*num_hash_functions=*/2);
+  WriterThread::LockFreeBloomFilterType filter;
 
   // Add the same elements to `filter`, in parallel, and expect the outcome
   // to be identical.
@@ -311,7 +313,9 @@
 }
 
 TEST(LockFreeBloomFilterTest, IndependentHashes) {
-  LockFreeBloomFilter filter(3);
+  LockFreeBloomFilter<3> filter;
+  // 32-bit platforms only have room for 5 bits per key.
+  LockFreeBloomFilter<sizeof(size_t) < 8 ? 5 : 8> filter2;
 
   std::vector<void*> ptrs;
   absl::Cleanup free_on_exit = [&ptrs] {
@@ -320,12 +324,16 @@
     }
   };
 
-  absl::flat_hash_set<LockFreeBloomFilter::BitStorage> bit_patterns;
+  absl::flat_hash_set<LockFreeBloomFilterBits> bit_patterns;
+  absl::flat_hash_set<LockFreeBloomFilterBits> bit_patterns2;
   for (int i = 1; i <= 1000; ++i) {
     ptrs.push_back(malloc(64));
     bit_patterns.insert(filter.GetBitsForKey(ptrs.back()));
+    bit_patterns2.insert(filter2.GetBitsForKey(ptrs.back()));
     ASSERT_GE(bit_patterns.size(), floor(i * 0.8))
         << i << " keys, " << bit_patterns.size() << " distinct bit patterns";
+    ASSERT_GE(bit_patterns2.size(), floor(i * 0.8))
+        << i << " keys, " << bit_patterns2.size() << " distinct bit patterns";
   }
 }
 
diff --git a/base/sampling_heap_profiler/poisson_allocation_sampler.cc b/base/sampling_heap_profiler/poisson_allocation_sampler.cc
index 7beeac0b..5fd022c 100644
--- a/base/sampling_heap_profiler/poisson_allocation_sampler.cc
+++ b/base/sampling_heap_profiler/poisson_allocation_sampler.cc
@@ -376,10 +376,10 @@
         std::max(address_cache_max_size_, balanced_address_cache.size());
     address_cache_max_load_factor_ = std::max(
         address_cache_max_load_factor_, balanced_address_cache.load_factor());
-    if (balanced_address_cache.bloom_filter()) {
+    if (balanced_address_cache.HasBloomFilter()) {
       bloom_filter_max_saturation_ =
           std::max(bloom_filter_max_saturation_,
-                   balanced_address_cache.bloom_filter()->CountBits());
+                   balanced_address_cache.MaxBloomFilterSaturation());
     }
     observers_copy = observers_;
   }
diff --git a/base/sampling_heap_profiler/poisson_allocation_sampler.h b/base/sampling_heap_profiler/poisson_allocation_sampler.h
index c25ba58..3a59906 100644
--- a/base/sampling_heap_profiler/poisson_allocation_sampler.h
+++ b/base/sampling_heap_profiler/poisson_allocation_sampler.h
@@ -390,7 +390,7 @@
   const LockFreeAddressHashSet& address_cache = sampled_addresses_set();
   switch (address_cache.Contains(address)) {
     [[likely]] case LockFreeAddressHashSet::ContainsResult::kNotFound:
-      if (address_cache.bloom_filter()) {
+      if (address_cache.HasBloomFilter()) {
         bloom_filter_misses_.fetch_add(1, std::memory_order_relaxed);
       } else {
         address_cache_misses_.fetch_add(1, std::memory_order_relaxed);
@@ -402,7 +402,7 @@
       address_cache_misses_.fetch_add(1, std::memory_order_relaxed);
       return;
     [[unlikely]] case LockFreeAddressHashSet::ContainsResult::kFound:
-      if (address_cache.bloom_filter()) {
+      if (address_cache.HasBloomFilter()) {
         bloom_filter_hits_.fetch_add(1, std::memory_order_relaxed);
       }
       address_cache_hits_.fetch_add(1, std::memory_order_relaxed);
diff --git a/base/test/android/javatests/src/org/chromium/base/test/transit/ActivityElement.java b/base/test/android/javatests/src/org/chromium/base/test/transit/ActivityElement.java
index d4a54465..91f5df8 100644
--- a/base/test/android/javatests/src/org/chromium/base/test/transit/ActivityElement.java
+++ b/base/test/android/javatests/src/org/chromium/base/test/transit/ActivityElement.java
@@ -5,6 +5,8 @@
 package org.chromium.base.test.transit;
 
 import android.app.Activity;
+import android.app.ActivityManager;
+import android.content.Context;
 
 import org.chromium.base.ActivityState;
 import org.chromium.base.ApplicationStatus;
@@ -55,6 +57,17 @@
         replaceEnterCondition(new ActivityExistsInAnyTaskCondition());
     }
 
+    TripBuilder bringWindowToFrontTo() {
+        return Triggers.runOnUiThreadTo(
+                () -> {
+                    var activity = get();
+                    assert activity != null;
+                    ActivityManager activityManager =
+                            (ActivityManager) activity.getSystemService(Context.ACTIVITY_SERVICE);
+                    activityManager.moveTaskToFront(activity.getTaskId(), 0);
+                });
+    }
+
     /**
      * Expect the Activity to be destroyed unless transitioning to a ConditionalState which also has
      * this Activity.
diff --git a/base/test/android/javatests/src/org/chromium/base/test/transit/Station.java b/base/test/android/javatests/src/org/chromium/base/test/transit/Station.java
index f372193a..1b9f260 100644
--- a/base/test/android/javatests/src/org/chromium/base/test/transit/Station.java
+++ b/base/test/android/javatests/src/org/chromium/base/test/transit/Station.java
@@ -4,6 +4,8 @@
 
 package org.chromium.base.test.transit;
 
+import static org.chromium.base.test.transit.Condition.whether;
+
 import android.app.Activity;
 
 import org.chromium.build.annotations.NullMarked;
@@ -144,4 +146,20 @@
         mActivityElement.expectActivityDestroyed();
         runTo(() -> getActivity().finish()).reachLastStop();
     }
+
+    /** Brings the task of the associated Activity to front and waits until it has focus. */
+    public void bringWindowToFront() {
+        assert mActivityElement != null;
+        mActivityElement
+                .bringWindowToFrontTo()
+                .waitFor(
+                        SimpleConditions.instrumentationThreadCondition(
+                                String.format("%s has window focus", getName()),
+                                mActivityElement,
+                                activity ->
+                                        whether(
+                                                activity.getWindow()
+                                                        .getDecorView()
+                                                        .hasWindowFocus())));
+    }
 }
diff --git a/base/threading/thread_restrictions.h b/base/threading/thread_restrictions.h
index 32b7e2e..089384b9 100644
--- a/base/threading/thread_restrictions.h
+++ b/base/threading/thread_restrictions.h
@@ -165,7 +165,6 @@
 namespace android_webview {
 class AwBrowserContext;
 class AwBrowserContextStore;
-class AwFormDatabaseService;
 class AwMetricsServiceClient;
 class CookieManager;
 class JsSandboxIsolate;
@@ -833,8 +832,6 @@
   friend class ::KeyStorageLinux;
   friend class ::NativeDesktopMediaList;
   friend class android::JavaHandlerThread;
-  friend class android_webview::
-      AwFormDatabaseService;  // http://crbug.com/904431
   friend class android_webview::CookieManager;
   friend class android_webview::VizCompositorThreadRunnerWebView;
   friend class audio::OutputDevice;
diff --git a/build/android/test_runner.py b/build/android/test_runner.py
index b7d0b79..ac1b649 100755
--- a/build/android/test_runner.py
+++ b/build/android/test_runner.py
@@ -64,7 +64,8 @@
     host_paths.DIR_SOURCE_ROOT, 'build', 'android', 'devil_config.json'))
 
 _RERUN_FAILED_TESTS_FILE = 'rerun_failed_tests.filter'
-
+# A dictionary of a test suite and a flat test name to overwrite.
+_REWRITE_SCHEME = {'components_perftests': 'components_perftests'}
 
 def _RealPath(arg):
   if arg.startswith('//'):
@@ -1128,6 +1129,14 @@
       instantiation = ""
       case_id = ""
 
+    # Some android gtests are incompatible with the upload scheme on other
+    # test runners.
+    if test_instance.suite in _REWRITE_SCHEME:
+      struct_test_dict['caseNameComponents'] = [
+          _REWRITE_SCHEME.get(test_instance.suite)
+      ]
+      return struct_test_dict
+
     struct_test_dict['coarseName'] = None  # Not used.
     struct_test_dict['fineName'] = suite
     if not case_id:
diff --git a/build/config/compiler/compiler.gni b/build/config/compiler/compiler.gni
index b70dadec..ae3b36f0 100644
--- a/build/config/compiler/compiler.gni
+++ b/build/config/compiler/compiler.gni
@@ -168,7 +168,9 @@
 
 # The Abseil .def lists for Windows component builds are created expecting sized
 # deallocation. Disabling it leads to symbol mismatches and build failures.
-assert(!is_win || !is_component_build || use_sized_deallocation,
+# This assertion is restricted to Clang builds. ANGLE MSVC builds, where
+# is_clang is false, do not use Abseil that requires this assertion.
+assert(!is_clang || !is_win || !is_component_build || use_sized_deallocation,
        "Sized deallocation must be enabled in component builds on Windows.")
 assert(is_clang || !use_sized_deallocation,
        "Sized deallocation is supported on clang.")
diff --git a/build/config/freetype/freetype.gni b/build/config/freetype/freetype.gni
index f5177e9..e868316 100644
--- a/build/config/freetype/freetype.gni
+++ b/build/config/freetype/freetype.gni
@@ -21,10 +21,10 @@
   # Ideally, this should be set with the same value as `enable_pdf` in
   # //pdf/features.gni, but:
   #
-  # - Fuchsia still needs FreeType: https://crbug.com/457696386
   # - Cast on Linux depends on Fontconfig, and Fontconfig depends on FreeType:
   #   https://crbug.com/370816215
   #
   # Note: //build should not import directly from //pdf for layering reasons.
-  enable_freetype = !is_android && !is_ios && (is_linux || !is_castos)
+  enable_freetype =
+      !is_android && !is_ios && (is_linux || !is_castos) && !is_fuchsia
 }
diff --git a/build/config/sanitizers/BUILD.gn b/build/config/sanitizers/BUILD.gn
index 52ffb8d..c748ab4 100644
--- a/build/config/sanitizers/BUILD.gn
+++ b/build/config/sanitizers/BUILD.gn
@@ -57,6 +57,11 @@
     if (is_asan || is_lsan || is_msan || is_tsan || is_ubsan_any) {
       public_configs += [ ":sanitizer_options_link_helper" ]
       deps += [ ":options_sources" ]
+
+      # Sanitizer malloc/free hooks depends on weak symbols.
+      if (is_asan && is_linux) {
+        deps += [ ":hooks_sources" ]
+      }
     }
     if (use_prebuilt_instrumented_libraries ||
         use_locally_built_instrumented_libraries) {
@@ -127,6 +132,61 @@
   }
 }
 
+config("sanitizer_impl") {
+  defines = [ "SANITIZERS_IMPLEMENTATION" ]
+}
+
+# Provide exported functions invoked from sanitizer_hooks.cc.
+# asan ---> options_sources.a --> sanitizer_shared_hooks.so
+#   override weak           strong
+# Each binary will be linked with options_sources.a and its
+# sanitizer hooks will be replaced with our own hooks via
+# sanitizer_shared_hooks.so.
+if (is_asan && is_linux) {
+  shared_library("sanitizer_shared_hooks") {
+    sources = [
+      "//build/sanitizers/sanitizer_shared_hooks.cc",
+      "//build/sanitizers/sanitizer_shared_hooks.h",
+    ]
+
+    # To avoid dependency cycle:
+    # ERROR Dependency cycle:
+    #  //build/config:common_deps ->
+    #  //build/config:common_deps_without_libcxx ->
+    #  //build/config/sanitizers:deps ->
+    #  //build/config/sanitizers:hooks_sources ->
+    #  //build/config/sanitizers:sanitizer_shared_hooks ->
+    #  //build/config:shared_library_deps ->
+    #  //build/config:common_deps
+    # `no_default_deps = true` is required here.
+    no_default_deps = true
+
+    # Don't compile this target with any sanitizer code. It can be called from
+    # the sanitizer runtimes, so instrumenting these functions could cause
+    # recursive calls into the runtime if there is an error.
+    configs -= [ "//build/config/sanitizers:default_sanitizer_flags" ]
+    configs += [ ":sanitizer_impl" ]
+
+    # To avoid `libsanitizer_shared_hooks.so is not found`, need to specify
+    # rpath for the shared_library. If is_component_build=true, the config
+    # has been already added by //build/config/*.
+    if (!is_component_build) {
+      all_dependent_configs =
+          [ "//build/config/gcc:rpath_for_built_shared_libraries" ]
+    }
+  }
+}
+
+# TODO(crbug.com/447520906): Because of `build/android/android_only_explicit_jni_exports.lst`,
+# `sanitizer_shared_hooks` shared library doesn't export any
+# functions. It will cause undefined symbols when linking binaries.
+if (is_asan && is_linux) {
+  source_set("hooks_sources") {
+    sources = [ "//build/sanitizers/sanitizer_hooks.cc" ]
+    deps = [ ":sanitizer_shared_hooks" ]
+  }
+}
+
 static_library("options_sources") {
   # This is a static_library instead of a source_set, as it shouldn't be
   # unconditionally linked into targets.
@@ -317,6 +377,7 @@
   rustflags = []
   if (is_asan) {
     cflags += [ "-fsanitize=address" ]
+
     rustflags += [ "-Zsanitizer=address" ]
     if (!is_win && !is_apple && !is_fuchsia) {
       # TODO(crbug.com/1459233, crbug.com/1462248): This causes asan
diff --git a/build/sanitizers/sanitizer_hooks.cc b/build/sanitizers/sanitizer_hooks.cc
new file mode 100644
index 0000000..dd02bb17
--- /dev/null
+++ b/build/sanitizers/sanitizer_hooks.cc
@@ -0,0 +1,34 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "build/build_config.h"
+
+#if defined(ADDRESS_SANITIZER)
+
+#include <cstddef>
+
+#include "build/sanitizers/sanitizer_shared_hooks.h"
+
+#define SANITIZER_HOOK_ATTRIBUTE                                               \
+  extern "C"                                                                   \
+      __attribute__((no_sanitize("address", "memory", "thread", "undefined"))) \
+      __attribute__((visibility("default"))) __attribute__((used))             \
+      __attribute__((noinline))
+
+SANITIZER_HOOK_ATTRIBUTE
+void __sanitizer_malloc_hook(const volatile void* ptr, size_t size) {
+  build_sanitizers::RunSanitizerMallocHook(ptr, size);
+}
+
+SANITIZER_HOOK_ATTRIBUTE
+void __sanitizer_free_hook(const volatile void* ptr) {
+  build_sanitizers::RunSanitizerFreeHook(ptr);
+}
+
+SANITIZER_HOOK_ATTRIBUTE int __sanitizer_ignore_free_hook(
+    const volatile void* ptr) {
+  return build_sanitizers::RunSanitizerIgnoreFreeHook(ptr);
+}
+
+#endif  // defined(ADDRESS_SANITIZER)
diff --git a/build/sanitizers/sanitizer_shared_hooks.cc b/build/sanitizers/sanitizer_shared_hooks.cc
new file mode 100644
index 0000000..f5077bb2
--- /dev/null
+++ b/build/sanitizers/sanitizer_shared_hooks.cc
@@ -0,0 +1,65 @@
+// 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 "build/sanitizers/sanitizer_shared_hooks.h"
+
+#include <atomic>
+
+#include "build/build_config.h"
+
+namespace build_sanitizers {
+
+namespace {
+
+std::atomic<SanitizerMallocHook> g_malloc_hook = nullptr;
+std::atomic<SanitizerFreeHook> g_free_hook = nullptr;
+std::atomic<SanitizerIgnoreFreeHook> g_ignore_free_hook = nullptr;
+
+}  // namespace
+
+#define SANITIZER_HOOK_ATTRIBUTE                                           \
+  __attribute__((no_sanitize("address", "memory", "thread", "undefined"))) \
+  __attribute__((used)) __attribute__((noinline))
+
+SANITIZER_HOOK_ATTRIBUTE SANITIZERS_EXPORT void InstallSanitizerHooks(
+    SanitizerMallocHook malloc_hook,
+    SanitizerFreeHook free_hook,
+    SanitizerIgnoreFreeHook ignore_free_hook) {
+  g_free_hook = free_hook;
+  g_ignore_free_hook = ignore_free_hook;
+  g_malloc_hook = malloc_hook;
+}
+
+SANITIZER_HOOK_ATTRIBUTE SANITIZERS_EXPORT void UninstallSanitizerHooks() {
+  g_malloc_hook = nullptr;
+  g_free_hook = nullptr;
+  g_ignore_free_hook = nullptr;
+}
+
+SANITIZER_HOOK_ATTRIBUTE SANITIZERS_EXPORT void RunSanitizerMallocHook(
+    const volatile void* ptr,
+    size_t size) {
+  if (SanitizerMallocHook hook =
+          g_malloc_hook.load(std::memory_order_relaxed)) {
+    hook(ptr, size);
+  }
+}
+
+SANITIZER_HOOK_ATTRIBUTE SANITIZERS_EXPORT void RunSanitizerFreeHook(
+    const volatile void* ptr) {
+  if (SanitizerFreeHook hook = g_free_hook.load(std::memory_order_relaxed)) {
+    hook(ptr);
+  }
+}
+
+SANITIZER_HOOK_ATTRIBUTE SANITIZERS_EXPORT int RunSanitizerIgnoreFreeHook(
+    const volatile void* ptr) {
+  if (SanitizerIgnoreFreeHook hook =
+          g_ignore_free_hook.load(std::memory_order_relaxed)) {
+    return hook(ptr);
+  }
+  return 0;
+}
+
+}  // namespace build_sanitizers
diff --git a/build/sanitizers/sanitizer_shared_hooks.h b/build/sanitizers/sanitizer_shared_hooks.h
new file mode 100644
index 0000000..a3b868b
--- /dev/null
+++ b/build/sanitizers/sanitizer_shared_hooks.h
@@ -0,0 +1,47 @@
+// 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 BUILD_SANITIZERS_SANITIZER_SHARED_HOOKS_H_
+#define BUILD_SANITIZERS_SANITIZER_SHARED_HOOKS_H_
+
+#include "build/build_config.h"
+
+#if defined(ADDRESS_SANITIZER)
+
+#include <cstddef>
+
+#if defined(WIN32)
+#if defined(SANITIZERS_IMPLEMENTATION)
+#define SANITIZERS_EXPORT __declspec(dllexport)
+#else
+#define SANITIZERS_EXPORT __declspec(dllimport)
+#endif  // defined(SANITIZERS_IMPLEMENTATION)
+
+#else  // defined(WIN32)
+#define SANITIZERS_EXPORT __attribute__((visibility("default"), noinline))
+#endif
+
+typedef void (*SanitizerMallocHook)(const volatile void*, size_t);
+typedef void (*SanitizerFreeHook)(const volatile void*);
+typedef int (*SanitizerIgnoreFreeHook)(const volatile void*);
+
+namespace build_sanitizers {
+
+// Install malloc(), free() and ignore_free() hooks.
+SANITIZERS_EXPORT void InstallSanitizerHooks(SanitizerMallocHook,
+                                             SanitizerFreeHook,
+                                             SanitizerIgnoreFreeHook);
+
+// Uninstall the hooks
+SANITIZERS_EXPORT void UninstallSanitizerHooks();
+
+SANITIZERS_EXPORT void RunSanitizerMallocHook(const volatile void*, size_t);
+SANITIZERS_EXPORT void RunSanitizerFreeHook(const volatile void*);
+SANITIZERS_EXPORT int RunSanitizerIgnoreFreeHook(const volatile void*);
+
+}  // namespace build_sanitizers
+
+#endif  // ADDRESS_SANITIZER
+
+#endif  // BUILD_SANITIZERS_SANITIZER_SHARED_HOOKS_H_
diff --git a/cc/BUILD.gn b/cc/BUILD.gn
index 8fadc02..1f4d5e2 100644
--- a/cc/BUILD.gn
+++ b/cc/BUILD.gn
@@ -239,6 +239,8 @@
     "metrics/scroll_jank_ukm_reporter.h",
     "metrics/scroll_jank_v4_decider.cc",
     "metrics/scroll_jank_v4_decider.h",
+    "metrics/scroll_jank_v4_decision_queue.cc",
+    "metrics/scroll_jank_v4_decision_queue.h",
     "metrics/scroll_jank_v4_frame.cc",
     "metrics/scroll_jank_v4_frame.h",
     "metrics/scroll_jank_v4_frame_stage.cc",
@@ -845,6 +847,7 @@
     "metrics/scroll_jank_dropped_frame_tracker_unittest.cc",
     "metrics/scroll_jank_ukm_reporter_unittest.cc",
     "metrics/scroll_jank_v4_decider_unittest.cc",
+    "metrics/scroll_jank_v4_decision_queue_unittest.cc",
     "metrics/scroll_jank_v4_frame_stage_unittest.cc",
     "metrics/scroll_jank_v4_frame_unittest.cc",
     "metrics/scroll_jank_v4_histogram_emitter_unittest.cc",
diff --git a/cc/layers/layer.cc b/cc/layers/layer.cc
index c00467b..80c4f2f 100644
--- a/cc/layers/layer.cc
+++ b/cc/layers/layer.cc
@@ -41,10 +41,6 @@
 #include "ui/gfx/geometry/rect_conversions.h"
 #include "ui/gfx/geometry/vector2d_conversions.h"
 
-#if BUILDFLAG(IS_CHROMEOS)
-#include "base/debug/dump_without_crashing.h"
-#endif
-
 namespace cc {
 
 struct SameSizeAsLayer : public base::RefCounted<SameSizeAsLayer>,
@@ -74,9 +70,6 @@
   bool allow_remove_for_readd;
 #endif
   uint8_t bit_fields[2];
-#if BUILDFLAG(IS_CHROMEOS)
-  bool is_valid_to_destroy_;
-#endif
 };
 
 static_assert(sizeof(Layer) == sizeof(SameSizeAsLayer),
@@ -134,15 +127,6 @@
 
   // Remove the parent reference from all children and dependents.
   RemoveAllChildren();
-#if BUILDFLAG(IS_CHROMEOS)
-  // `is_valid_to_destroy_` should never be false at this point.
-  // DCHECK to catch this issue in bots and reports the stack if this ever
-  // happened in production.
-  DCHECK(is_valid_to_destroy_);
-  if (!is_valid_to_destroy_) {
-    base::debug::DumpWithoutCrashing();
-  }
-#endif
 }
 
 Layer::LayerTreeInputs& Layer::EnsureLayerTreeInputs() {
diff --git a/cc/layers/layer.h b/cc/layers/layer.h
index 7343157..7b9210f6 100644
--- a/cc/layers/layer.h
+++ b/cc/layers/layer.h
@@ -874,12 +874,6 @@
   // surface, returns the ID of that resource.
   virtual viz::ViewTransitionElementResourceId ViewTransitionResourceId() const;
 
-#if BUILDFLAG(IS_CHROMEOS)
-  bool is_valid_to_destroy() const { return is_valid_to_destroy_; }
-
-  void set_is_valid_to_destroy(bool enable) { is_valid_to_destroy_ = enable; }
-#endif
-
  protected:
   friend class LayerImpl;
   friend class TreeSynchronizer;
@@ -1202,10 +1196,6 @@
   };
   ProtectedSequenceReadable<uint8_t> bitflags_;
   ProtectedSequenceWritable<uint8_t> changed_properties_;
-#if BUILDFLAG(IS_CHROMEOS)
-  // TODO(crbug.com/443811562): Remove this once the crash is fixed.
-  bool is_valid_to_destroy_ = true;
-#endif
 
 #if DCHECK_IS_ON()
   class AllowRemoveForReadd {
diff --git a/cc/layers/picture_layer_impl.cc b/cc/layers/picture_layer_impl.cc
index 6c279fd..bb85fa4 100644
--- a/cc/layers/picture_layer_impl.cc
+++ b/cc/layers/picture_layer_impl.cc
@@ -281,9 +281,8 @@
     AppendQuadsData* append_quads_data,
     viz::SharedQuadState* shared_quad_state,
     const Occlusion& scaled_occlusion,
-    const gfx::Vector2d& quad_offset) {
-  float max_contents_scale = GetMaximumContentsScaleForUseInAppendQuads();
-
+    const gfx::Vector2d& quad_offset,
+    float max_contents_scale) {
   // Keep track of the tilings that were used so that tilings that are
   // unused can be considered for removal.
   last_append_quads_tilings_.clear();
diff --git a/cc/layers/picture_layer_impl.h b/cc/layers/picture_layer_impl.h
index a7e52b4..f1fd5e2 100644
--- a/cc/layers/picture_layer_impl.h
+++ b/cc/layers/picture_layer_impl.h
@@ -407,7 +407,8 @@
                                  AppendQuadsData* append_quads_data,
                                  viz::SharedQuadState* shared_quad_state,
                                  const Occlusion& scaled_occlusion,
-                                 const gfx::Vector2d& quad_offset) override;
+                                 const gfx::Vector2d& quad_offset,
+                                 float max_contents_scale) override;
   float GetMaximumContentsScaleForUseInAppendQuads() override;
   void AppendQuadsForResourcelessSoftwareDraw(
       const AppendQuadsContext& context,
diff --git a/cc/layers/tile_based_layer_impl.h b/cc/layers/tile_based_layer_impl.h
index ea81355..61f4b63 100644
--- a/cc/layers/tile_based_layer_impl.h
+++ b/cc/layers/tile_based_layer_impl.h
@@ -81,7 +81,8 @@
       AppendQuadsData* append_quads_data,
       viz::SharedQuadState* shared_quad_state,
       const Occlusion& scaled_occlusion,
-      const gfx::Vector2d& quad_offset) = 0;
+      const gfx::Vector2d& quad_offset,
+      float max_contents_scale) = 0;
 
   virtual float GetMaximumContentsScaleForUseInAppendQuads() = 0;
 
@@ -240,7 +241,8 @@
   }
 
   AppendQuadsSpecialization(context, render_pass, append_quads_data,
-                            shared_quad_state, scaled_occlusion, quad_offset);
+                            shared_quad_state, scaled_occlusion, quad_offset,
+                            max_contents_scale);
 
   // Adjust shared_quad_state with the quad_offset, since by contract
   // AppendQuadsSpecialization() has adjusted each quad appended by that offset.
diff --git a/cc/layers/tile_based_layer_impl_unittest.cc b/cc/layers/tile_based_layer_impl_unittest.cc
index 88fcb8f..c19f7fe3 100644
--- a/cc/layers/tile_based_layer_impl_unittest.cc
+++ b/cc/layers/tile_based_layer_impl_unittest.cc
@@ -76,7 +76,8 @@
                                  AppendQuadsData* append_quads_data,
                                  viz::SharedQuadState* shared_quad_state,
                                  const Occlusion& scaled_occlusion,
-                                 const gfx::Vector2d& quad_offset) override {}
+                                 const gfx::Vector2d& quad_offset,
+                                 float max_contents_scale) override {}
   float GetMaximumContentsScaleForUseInAppendQuads() override { return 1.f; }
   bool IsDirectlyCompositedImage() const override { return false; }
   TilingResolution GetTilingResolutionForDebugBorders(
@@ -399,7 +400,8 @@
                                  AppendQuadsData* append_quads_data,
                                  viz::SharedQuadState* shared_quad_state,
                                  const Occlusion& scaled_occlusion,
-                                 const gfx::Vector2d& quad_offset) override {
+                                 const gfx::Vector2d& quad_offset,
+                                 float max_contents_scale) override {
     scaled_occlusion_ = scaled_occlusion;
     // Create a dummy quad to avoid tripping debug checks.
     auto* quad =
@@ -551,7 +553,8 @@
                                  AppendQuadsData* append_quads_data,
                                  viz::SharedQuadState* shared_quad_state,
                                  const Occlusion& scaled_occlusion,
-                                 const gfx::Vector2d& quad_offset) override {
+                                 const gfx::Vector2d& quad_offset,
+                                 float max_contents_scale) override {
     quad_offset_ = quad_offset;
     // Create a dummy quad to avoid tripping debug checks.
     auto* quad =
@@ -627,7 +630,8 @@
                                  AppendQuadsData* append_quads_data,
                                  viz::SharedQuadState* shared_quad_state,
                                  const Occlusion& scaled_occlusion,
-                                 const gfx::Vector2d& quad_offset) override {
+                                 const gfx::Vector2d& quad_offset,
+                                 float max_contents_scale) override {
     shared_quad_state_at_specialization_ =
         std::make_unique<viz::SharedQuadState>(*shared_quad_state);
     // Create a dummy quad to avoid tripping debug checks.
diff --git a/cc/layers/tile_display_layer_impl.cc b/cc/layers/tile_display_layer_impl.cc
index c100058..e6e5055 100644
--- a/cc/layers/tile_display_layer_impl.cc
+++ b/cc/layers/tile_display_layer_impl.cc
@@ -184,9 +184,8 @@
     AppendQuadsData* append_quads_data,
     viz::SharedQuadState* shared_quad_state,
     const Occlusion& scaled_occlusion,
-    const gfx::Vector2d& quad_offset) {
-  const float max_contents_scale = GetMaximumContentsScaleForUseInAppendQuads();
-
+    const gfx::Vector2d& quad_offset,
+    float max_contents_scale) {
   // Keep track of the tilings that were used so that tilings that are
   // unused can be considered for removal.
   last_append_quads_scales_.clear();
@@ -200,8 +199,10 @@
     if (transform_tree_index() == scroll_node->transform_id) {
       if (const gfx::Rect* cull_rect =
               scroll_tree.ScrollingContentsCullRect(scroll_node->element_id)) {
-        scaled_cull_rect =
-            gfx::ScaleToEnclosingRect(*cull_rect, max_contents_scale);
+        scaled_cull_rect = gfx::ToEnclosingRect(gfx::ScaleRect(
+            // Convert into layer space.
+            gfx::RectF(*cull_rect) - offset_to_transform_parent(),
+            max_contents_scale));
       }
     }
   }
diff --git a/cc/layers/tile_display_layer_impl.h b/cc/layers/tile_display_layer_impl.h
index 7b8af42..d9a29d0 100644
--- a/cc/layers/tile_display_layer_impl.h
+++ b/cc/layers/tile_display_layer_impl.h
@@ -218,7 +218,8 @@
                                  AppendQuadsData* append_quads_data,
                                  viz::SharedQuadState* shared_quad_state,
                                  const Occlusion& scaled_occlusion,
-                                 const gfx::Vector2d& quad_offset) override;
+                                 const gfx::Vector2d& quad_offset,
+                                 float max_contents_scale) override;
   float GetMaximumContentsScaleForUseInAppendQuads() override;
   float GetIdealContentsScaleKey() const override;
   void AppendQuadsForResourcelessSoftwareDraw(
diff --git a/cc/metrics/compositor_frame_reporting_controller_unittest.cc b/cc/metrics/compositor_frame_reporting_controller_unittest.cc
index bc00ac9..be9fcfe 100644
--- a/cc/metrics/compositor_frame_reporting_controller_unittest.cc
+++ b/cc/metrics/compositor_frame_reporting_controller_unittest.cc
@@ -144,6 +144,7 @@
   // occur for each phase of the reporting controller.
   void SimulateBeginImplFrame() {
     IncrementCurrentId();
+    args_.frame_time = test_tick_clock_.NowTicks();
     begin_impl_time_ = AdvanceNowByMs(10);
     reporting_controller_.WillBeginImplFrame(args_,
                                              /*will_throttle_main=*/false);
@@ -2837,14 +2838,13 @@
   std::unique_ptr<EventMetrics> metrics_3 = CreateScrollUpdateEventMetrics(
       ui::ScrollInputType::kWheel, /*is_inertial=*/true,
       ScrollUpdateEventMetrics::ScrollUpdateType::kContinued, std::nullopt);
-  base::TimeTicks event3_generation_ts = metrics_3->GetDispatchStageTimestamp(
-      EventMetrics::DispatchStage::kGenerated);
 
   std::unique_ptr<EventMetrics> non_scroll_event =
       CreateEventMetrics(ui::EventType::kTouchPressed, std::nullopt);
 
   base::TimeDelta vsync_interval = event2_generation_ts - event1_generation_ts;
   args_.interval = vsync_interval;
+  base::TimeTicks first_begin_frame_ts = test_tick_clock_.NowTicks();
 
   SimulateBeginImplFrame();  // BF1
   reporting_controller_.OnFinishImplFrame(current_id_,
@@ -2854,7 +2854,8 @@
   SimulateSubmitCompositorFrame({{}, std::move(metrics_list_1), {}});
 
   viz::FrameTimingDetails details_1 = {};
-  details_1.presentation_feedback.timestamp = event3_generation_ts;
+  details_1.presentation_feedback.timestamp =
+      first_begin_frame_ts + 2 * vsync_interval;
   reporting_controller_.DidPresentCompositorFrame(*current_token_,
                                                   details_1);  // PF1
 
@@ -2867,7 +2868,7 @@
 
   viz::FrameTimingDetails details_2 = {};
   details_2.presentation_feedback.timestamp =
-      details_1.presentation_feedback.timestamp + 2 * vsync_interval;
+      first_begin_frame_ts + 4 * vsync_interval;
   reporting_controller_.DidPresentCompositorFrame(*current_token_,
                                                   details_2);  // PF2
 
@@ -2880,7 +2881,7 @@
 
   viz::FrameTimingDetails details_3 = {};
   details_3.presentation_feedback.timestamp =
-      details_2.presentation_feedback.timestamp + vsync_interval;
+      first_begin_frame_ts + 5 * vsync_interval;
   reporting_controller_.DidPresentCompositorFrame(*current_token_,
                                                   details_3);  // PF3
 
@@ -2893,7 +2894,7 @@
 
   viz::FrameTimingDetails details_4 = {};
   details_4.presentation_feedback.timestamp =
-      details_3.presentation_feedback.timestamp + vsync_interval;
+      first_begin_frame_ts + 6 * vsync_interval;
   reporting_controller_.DidPresentCompositorFrame(*current_token_,
                                                   details_4);  // PF4
 
@@ -2912,14 +2913,14 @@
               "adjusted_delivery_cutoff_us", "current_delivery_cutoff_us",
               "is_damaging_frame"},
           std::vector<std::string>{"0", "0", "[NULL]", "10", "0", "[NULL]",
-                                   "[NULL]", "[NULL]", "86000", "1"},
+                                   "[NULL]", "[NULL]", "248000", "1"},
           std::vector<std::string>{
               "1", "1",
               "MISSED_VSYNC_DUE_TO_DECELERATING_INPUT_FRAME_DELIVERY(1),MISSED_"
               "VSYNC_DURING_FAST_SCROLL(1)",
-              "10", "0", "2", "86000", "84000", "129000", "1"},
+              "10", "0", "2", "248000", "246000", "291000", "1"},
           std::vector<std::string>{"0", "0", "[NULL]", "10", "10", "1",
-                                   "129000", "[NULL]", "129000", "1"},
+                                   "291000", "[NULL]", "291000", "1"},
           std::vector<std::string>{"[NULL]", "[NULL]", "[NULL]", "[NULL]",
                                    "[NULL]", "[NULL]", "[NULL]", "[NULL]",
                                    "[NULL]", "[NULL]"}));
@@ -2956,11 +2957,10 @@
   std::unique_ptr<EventMetrics> metrics_3 = CreateScrollUpdateEventMetrics(
       ui::ScrollInputType::kWheel, /*is_inertial=*/false,
       ScrollUpdateEventMetrics::ScrollUpdateType::kContinued, std::nullopt);
-  base::TimeTicks event3_generation_ts = metrics_3->GetDispatchStageTimestamp(
-      EventMetrics::DispatchStage::kGenerated);
 
   base::TimeDelta vsync_interval = event2_generation_ts - event1_generation_ts;
   args_.interval = vsync_interval;
+  base::TimeTicks first_begin_frame_ts = test_tick_clock_.NowTicks();
 
   SimulateBeginImplFrame();  // BF1
   reporting_controller_.OnFinishImplFrame(current_id_,
@@ -2970,7 +2970,8 @@
   SimulateSubmitCompositorFrame({{}, std::move(metrics_list_1), {}});
 
   viz::FrameTimingDetails details_1 = {};
-  details_1.presentation_feedback.timestamp = event3_generation_ts;
+  details_1.presentation_feedback.timestamp =
+      first_begin_frame_ts + 2 * vsync_interval;
   reporting_controller_.DidPresentCompositorFrame(*current_token_,
                                                   details_1);  // PF1
 
@@ -2991,7 +2992,7 @@
 
   viz::FrameTimingDetails details_3 = {};
   details_3.presentation_feedback.timestamp =
-      details_1.presentation_feedback.timestamp + 2 * vsync_interval;
+      first_begin_frame_ts + 4 * vsync_interval;
   reporting_controller_.DidPresentCompositorFrame(*current_token_,
                                                   details_3);  // PF3
 
@@ -3009,12 +3010,12 @@
               "adjusted_delivery_cutoff_us", "current_delivery_cutoff_us",
               "is_damaging_frame"},
           std::vector<std::string>{"0", "0", "[NULL]", "10", "0", "[NULL]",
-                                   "[NULL]", "[NULL]", "86000", "1"},
+                                   "[NULL]", "[NULL]", "205000", "1"},
           std::vector<std::string>{
               "[NULL]", "1",
               "MISSED_VSYNC_DUE_TO_DECELERATING_INPUT_FRAME_DELIVERY(1),MISSED_"
               "VSYNC_DURING_FAST_SCROLL(1)",
-              "20", "0", "2", "86000", "84000", "86000", "1"},
+              "20", "0", "2", "205000", "203000", "205000", "1"},
           std::vector<std::string>{"0", "[NULL]", "[NULL]", "[NULL]", "[NULL]",
                                    "[NULL]", "[NULL]", "[NULL]", "[NULL]",
                                    "[NULL]"}));
@@ -3058,21 +3059,16 @@
   std::unique_ptr<EventMetrics> scroll_end_metrics =
       CreateScrollEndEventMetrics(ui::ScrollInputType::kWheel,
                                   /*is_inertial=*/false);
-  base::TimeTicks scroll_end_generation_ts =
-      scroll_end_metrics->GetDispatchStageTimestamp(
-          EventMetrics::DispatchStage::kGenerated);
 
   std::unique_ptr<EventMetrics> scroll_update3_metrics =
       CreateScrollUpdateEventMetrics(
           ui::ScrollInputType::kWheel, /*is_inertial=*/false,
           ScrollUpdateEventMetrics::ScrollUpdateType::kStarted, std::nullopt);
-  base::TimeTicks scroll_update3_generation_ts =
-      scroll_update3_metrics->GetDispatchStageTimestamp(
-          EventMetrics::DispatchStage::kGenerated);
 
   base::TimeDelta vsync_interval =
       scroll_update2_generation_ts - scroll_update1_generation_ts;
   args_.interval = vsync_interval;
+  base::TimeTicks first_begin_frame_ts = test_tick_clock_.NowTicks();
 
   {
     base::HistogramTester histogram_tester;
@@ -3086,7 +3082,7 @@
 
     viz::FrameTimingDetails details_1 = {};
     details_1.presentation_feedback.timestamp =
-        scroll_update1_generation_ts + 2 * vsync_interval;
+        first_begin_frame_ts + 2 * vsync_interval;
     reporting_controller_.DidPresentCompositorFrame(*current_token_,
                                                     details_1);  // PF1
 
@@ -3099,7 +3095,7 @@
 
     viz::FrameTimingDetails details_2 = {};
     details_2.presentation_feedback.timestamp =
-        scroll_update2_generation_ts + 2 * vsync_interval;
+        first_begin_frame_ts + 3 * vsync_interval;
     reporting_controller_.DidPresentCompositorFrame(*current_token_,
                                                     details_2);  // PF2
 
@@ -3119,7 +3115,7 @@
 
     viz::FrameTimingDetails details_4 = {};
     details_4.presentation_feedback.timestamp =
-        scroll_end_generation_ts + 3 * vsync_interval;
+        first_begin_frame_ts + 5 * vsync_interval;
     reporting_controller_.DidPresentCompositorFrame(*current_token_,
                                                     details_4);  // PF4
 
@@ -3143,7 +3139,7 @@
 
     viz::FrameTimingDetails details_5 = {};
     details_5.presentation_feedback.timestamp =
-        scroll_update3_generation_ts + 3 * vsync_interval;
+        first_begin_frame_ts + 6 * vsync_interval;
     reporting_controller_.DidPresentCompositorFrame(*current_token_,
                                                     details_5);  // PF5
 
diff --git a/cc/metrics/scroll_jank_v4_decider.cc b/cc/metrics/scroll_jank_v4_decider.cc
index 0bb7f354..b023b8d 100644
--- a/cc/metrics/scroll_jank_v4_decider.cc
+++ b/cc/metrics/scroll_jank_v4_decider.cc
@@ -6,7 +6,6 @@
 
 #include <algorithm>
 #include <optional>
-#include <utility>
 #include <variant>
 
 #include "base/check.h"
@@ -15,6 +14,7 @@
 #include "cc/base/features.h"
 #include "cc/metrics/event_metrics.h"
 #include "cc/metrics/scroll_jank_v4_frame.h"
+#include "cc/metrics/scroll_jank_v4_frame_stage.h"
 #include "third_party/abseil-cpp/absl/functional/overload.h"
 
 namespace cc {
@@ -23,43 +23,62 @@
 
 using ScrollDamage = ScrollJankV4Frame::ScrollDamage;
 using DamagingFrame = ScrollJankV4Frame::DamagingFrame;
+using ScrollUpdates = ScrollJankV4FrameStage::ScrollUpdates;
 
 }  // namespace
 
-std::optional<ScrollUpdateEventMetrics::ScrollJankV4Result>
+ScrollUpdateEventMetrics::ScrollJankV4Result
+ScrollJankV4Decider::DecideJankForFrameWithRealScrollUpdates(
+    const ScrollJankV4FrameStage::ScrollUpdates& updates,
+    const ScrollJankV4Frame::ScrollDamage& damage,
+    const ScrollJankV4Frame::BeginFrameArgsForScrollJank& args) {
+  CHECK(updates.real().has_value());
+  return DecideJankForFrameWithScrollUpdates(updates, damage, args,
+                                             IsFastScroll(*updates.real()));
+}
+
+ScrollUpdateEventMetrics::ScrollJankV4Result
+ScrollJankV4Decider::DecideJankForFrameWithSyntheticScrollUpdatesOnly(
+    const ScrollJankV4FrameStage::ScrollUpdates& updates,
+    const ScrollJankV4Frame::ScrollDamage& damage,
+    const ScrollJankV4Frame::BeginFrameArgsForScrollJank& args,
+    bool future_real_frame_is_fast_scroll_or_sufficiently_fast_fling) {
+  CHECK(!updates.real().has_value());
+  return DecideJankForFrameWithScrollUpdates(
+      updates, damage, args,
+      future_real_frame_is_fast_scroll_or_sufficiently_fast_fling);
+}
+
+ScrollUpdateEventMetrics::ScrollJankV4Result
 ScrollJankV4Decider::DecideJankForFrameWithScrollUpdates(
-    base::TimeTicks first_input_generation_ts,
-    base::TimeTicks last_input_generation_ts,
+    const ScrollUpdates& updates,
     const ScrollDamage& damage,
     const ScrollJankV4Frame::BeginFrameArgsForScrollJank& args,
-    bool has_inertial_input,
-    float abs_total_raw_delta_pixels,
-    float max_abs_inertial_raw_delta_pixels) {
-  CHECK(has_inertial_input || max_abs_inertial_raw_delta_pixels == 0);
-
-  if (!IsValidFrame(first_input_generation_ts, last_input_generation_ts, damage,
-                    args)) {
-    return std::nullopt;
-  }
+    bool treat_as_fast_scroll) {
+  DCHECK(IsValidFrame(updates, damage, args));
+  DCHECK(!prev_frame_data_.has_value() ||
+         args.frame_time > prev_frame_data_->begin_frame_ts);
 
   base::TimeDelta vsync_interval = args.interval;
   const DamagingFrame* damaging_frame = std::get_if<DamagingFrame>(&damage);
 
   ScrollUpdateEventMetrics::ScrollJankV4Result result = {
       .is_damaging_frame = !!damaging_frame,
-      .abs_total_raw_delta_pixels = abs_total_raw_delta_pixels,
-      .max_abs_inertial_raw_delta_pixels = max_abs_inertial_raw_delta_pixels,
   };
+  if (updates.real().has_value()) {
+    result.abs_total_raw_delta_pixels =
+        updates.real()->abs_total_raw_delta_pixels;
+    result.max_abs_inertial_raw_delta_pixels =
+        updates.real()->max_abs_inertial_raw_delta_pixels;
+  }
+
+  std::optional<base::TimeTicks> earliest_input_generation_ts =
+      GetEarliestScrollUpdateGenerationTs(updates);
 
   bool is_janky = false;
   int vsyncs_since_previous_frame = 0;
   if (prev_frame_data_.has_value()) {
-    const std::optional<PreviousFrameData::PresentationData>&
-        prev_presentation_data = prev_frame_data_->presentation_data;
-    if (prev_presentation_data.has_value()) {
-      result.running_delivery_cutoff =
-          prev_presentation_data->running_delivery_cutoff;
-    }
+    result.running_delivery_cutoff = prev_frame_data_->running_delivery_cutoff;
 
     // Determine how many VSyncs there have been between the previous and
     // current frame. By default, compare the presentation times. If the current
@@ -68,9 +87,9 @@
     // of `vsync_interval`. We add `(vsync_interval / 2)` to round the result
     // properly to the nearest integer.
     base::TimeDelta delta =
-        damaging_frame && prev_presentation_data.has_value()
+        damaging_frame && prev_frame_data_->presentation_ts.has_value()
             ? damaging_frame->presentation_ts -
-                  prev_presentation_data->presentation_ts
+                  *prev_frame_data_->presentation_ts
             : args.frame_time - prev_frame_data_->begin_frame_ts;
     vsyncs_since_previous_frame =
         std::max<int>((delta + (vsync_interval / 2)) / vsync_interval, 1);
@@ -79,14 +98,13 @@
     if (vsyncs_since_previous_frame > 1) {
       // If there was at least one VSync between the previous and current frame,
       // determine whether the current frame should be marked as janky because
-      // Chrome should have presented its first input (`earliest_event`) in an
+      // Chrome should have presented its first inputs (`earliest_event`) in an
       // earlier VSync based on the rules described in
       // https://docs.google.com/document/d/1AaBvTIf8i-c-WTKkjaL4vyhQMkSdynxo3XEiwpofdeA.
       JankReasonArray<int> missed_vsyncs_per_reason =
           CalculateMissedVsyncsPerReason(
-              vsyncs_since_previous_frame, first_input_generation_ts, damage,
-              args, abs_total_raw_delta_pixels,
-              max_abs_inertial_raw_delta_pixels, result);
+              vsyncs_since_previous_frame, earliest_input_generation_ts,
+              updates, damage, args, treat_as_fast_scroll, result);
 
       // A frame is janky if ANY of the rules decided that Chrome missed one or
       // more VSyncs.
@@ -98,22 +116,99 @@
     }
   }
 
-  // How quickly Chrome was able to deliver input in the current frame?
-  std::optional<PreviousFrameData::PresentationData> presentation_data =
-      CalculatePresentationData(vsyncs_since_previous_frame, is_janky,
-                                last_input_generation_ts, damage, args, result);
-
   // Finally, update internal state for the next iteration.
   prev_frame_data_ = {
-      .has_inertial_input = has_inertial_input,
-      .abs_total_raw_delta_pixels = abs_total_raw_delta_pixels,
+      .has_inertial_input =
+          updates.real().has_value() && updates.real()->has_inertial_input,
+      .is_most_recent_real_frame_fast_scroll =
+          [&]() {
+            if (updates.real().has_value()) {
+              return treat_as_fast_scroll;
+            }
+            return prev_frame_data_.has_value() &&
+                   prev_frame_data_->is_most_recent_real_frame_fast_scroll;
+          }(),
+      .last_input_generation_ts = [&]() -> std::optional<base::TimeTicks> {
+        if (updates.real().has_value()) {
+          return updates.real()->last_input_generation_ts;
+        }
+        if (!prev_frame_data_.has_value() ||
+            !prev_frame_data_->last_input_generation_ts.has_value() ||
+            is_janky) {
+          return std::nullopt;
+        }
+        // If this is a synthetic frame, we assume that the synthetic input had
+        // the same duration between its generation and original begin frame
+        // timestamp as the most recent real frame.
+        return *prev_frame_data_->last_input_generation_ts +
+               (updates.synthetic()->first_input_begin_frame_ts -
+                prev_frame_data_->begin_frame_ts);
+      }(),
       .begin_frame_ts = args.frame_time,
-      .presentation_data = presentation_data,
+      .presentation_ts = [&]() -> std::optional<base::TimeTicks> {
+        if (damaging_frame) {
+          return damaging_frame->presentation_ts;
+        }
+        if (!prev_frame_data_.has_value() ||
+            !prev_frame_data_->presentation_ts.has_value() || is_janky) {
+          return std::nullopt;
+        }
+        // If this is a non-damaging frame, we assume that it had the same
+        // duration between its begin frame and presentation timestamps as the
+        // most recent damaging frame.
+        return *prev_frame_data_->presentation_ts +
+               (args.frame_time - prev_frame_data_->begin_frame_ts);
+      }(),
+      .running_delivery_cutoff = CalculateRunningDeliveryCutoff(
+          vsyncs_since_previous_frame, is_janky, updates, damage, args, result),
   };
 
   return result;
 }
 
+// static
+bool ScrollJankV4Decider::IsValidFrame(
+    const ScrollJankV4FrameStage::ScrollUpdates& updates,
+    const ScrollJankV4Frame::ScrollDamage& damage,
+    const ScrollJankV4Frame::BeginFrameArgsForScrollJank& args) {
+  if (const DamagingFrame* damaging_frame =
+          std::get_if<DamagingFrame>(&damage)) {
+    base::TimeTicks presentation_ts = damaging_frame->presentation_ts;
+    if (args.frame_time >= presentation_ts) {
+      return false;
+    }
+
+    if (updates.real().has_value() &&
+        updates.real()->last_input_generation_ts >= presentation_ts) {
+      // TODO(crbug.com/40913586): Investigate when these edge cases can be
+      // triggered in field and web tests. We have already seen this triggered
+      // in field, and some web tests where an event with null(0) timestamp gets
+      // coalesced with a "normal" inputs.
+      return false;
+    }
+
+    if (updates.synthetic().has_value() &&
+        updates.synthetic()->first_input_begin_frame_ts >= presentation_ts) {
+      return false;
+    }
+  }
+
+  if (updates.real().has_value() &&
+      updates.real()->first_input_generation_ts >
+          updates.real()->last_input_generation_ts) {
+    return false;
+  }
+
+  // Note: We don't use `else if` because `updates` might contain both real and
+  // synthetic scroll updates.
+  if (updates.synthetic().has_value() &&
+      updates.synthetic()->first_input_begin_frame_ts > args.frame_time) {
+    return false;
+  }
+
+  return true;
+}
+
 void ScrollJankV4Decider::OnScrollStarted() {
   Reset();
 }
@@ -122,50 +217,31 @@
   Reset();
 }
 
-bool ScrollJankV4Decider::IsValidFrame(
-    base::TimeTicks first_input_generation_ts,
-    base::TimeTicks last_input_generation_ts,
-    const ScrollDamage& damage,
-    const ScrollJankV4Frame::BeginFrameArgsForScrollJank& args) const {
-  if (last_input_generation_ts < first_input_generation_ts) {
-    return false;
-  }
+// static
+bool ScrollJankV4Decider::IsFastScroll(
+    const ScrollUpdates::Real& real_updates) {
+  static const double kFastScrollContinuityThreshold =
+      features::kScrollJankV4MetricFastScrollContinuityThreshold.Get();
+  return real_updates.abs_total_raw_delta_pixels >=
+         kFastScrollContinuityThreshold;
+}
 
-  const DamagingFrame* damaging_frame = std::get_if<DamagingFrame>(&damage);
-  if (damaging_frame &&
-      damaging_frame->presentation_ts <= last_input_generation_ts) {
-    // TODO(crbug.com/40913586): Investigate when these edge cases can be
-    // triggered in field and web tests. We have already seen this triggered in
-    // field, and some web tests where an event with null(0) timestamp gets
-    // coalesced with a "normal" input.
-    return false;
-  }
-
-  if (!prev_frame_data_.has_value()) {
-    // If this is the first frame, then there's nothing left to check.
-    return true;
-  }
-
-  // TODO(crbug.com/276722271) : Analyze and reduce these cases of out of order
-  // frame termination.
-  if (damaging_frame && prev_frame_data_->presentation_data.has_value()) {
-    // If we have presentation timestamps for both the previous and current
-    // frame, compare them.
-    return damaging_frame->presentation_ts >
-           prev_frame_data_->presentation_data->presentation_ts;
-  } else {
-    // If not, compare their begin frame timestamps.
-    return args.frame_time > prev_frame_data_->begin_frame_ts;
-  }
+// static
+bool ScrollJankV4Decider::IsSufficientlyFastFling(
+    const ScrollUpdates::Real& real_updates) {
+  static const double kFlingContinuityThreshold =
+      features::kScrollJankV4MetricFlingContinuityThreshold.Get();
+  return real_updates.max_abs_inertial_raw_delta_pixels >=
+         kFlingContinuityThreshold;
 }
 
 JankReasonArray<int> ScrollJankV4Decider::CalculateMissedVsyncsPerReason(
     int vsyncs_since_previous_frame,
-    base::TimeTicks first_input_generation_ts,
-    const ScrollDamage& damage,
+    std::optional<base::TimeTicks> first_input_generation_ts,
+    const ScrollUpdates& updates,
+    const ScrollJankV4Frame::ScrollDamage& damage,
     const ScrollJankV4Frame::BeginFrameArgsForScrollJank& args,
-    float abs_total_raw_delta_pixels,
-    float max_abs_inertial_raw_delta_pixels,
+    bool treat_as_fast_scroll,
     ScrollUpdateEventMetrics::ScrollJankV4Result& result) const {
   DCHECK_GT(vsyncs_since_previous_frame, 1);
 
@@ -173,10 +249,6 @@
       features::kScrollJankV4MetricStabilityCorrection.Get();
   static const double kDiscountFactor =
       features::kScrollJankV4MetricDiscountFactor.Get();
-  static const double kFastScrollContinuityThreshold =
-      features::kScrollJankV4MetricFastScrollContinuityThreshold.Get();
-  static const double kFlingContinuityThreshold =
-      features::kScrollJankV4MetricFlingContinuityThreshold.Get();
 
   JankReasonArray<int> missed_vsyncs_per_reason = {};
 
@@ -190,7 +262,9 @@
   // more lenient) and subtract stability correction (to be a bit more strict).
   // This is what the current VSync would hypothetically have been judged
   // against if it didn't contain any inputs.
-  if (prev_frame_data.presentation_data.has_value()) {
+  if (prev_frame_data.running_delivery_cutoff.has_value() &&
+      first_input_generation_ts.has_value()) {
+    DCHECK(prev_frame_data.presentation_ts.has_value());
     base::TimeTicks presentation_ts = [&]() {
       if (const DamagingFrame* damaging_frame =
               std::get_if<DamagingFrame>(&damage)) {
@@ -203,16 +277,16 @@
       // this method will evaluate the running consistency rule against this
       // frame's and the previous frame's begin frame timestamps (rather than
       // presentation timestamps).
-      return prev_frame_data_->presentation_data->presentation_ts +
+      return *prev_frame_data_->presentation_ts +
              (args.frame_time - prev_frame_data_->begin_frame_ts);
     }();
     base::TimeDelta adjusted_delivery_cutoff =
-        prev_frame_data.presentation_data->running_delivery_cutoff +
+        *prev_frame_data.running_delivery_cutoff +
         (vsyncs_since_previous_frame - 1) * kDiscountFactor * vsync_interval -
         kStabilityCorrection * vsync_interval;
     result.adjusted_delivery_cutoff = adjusted_delivery_cutoff;
     base::TimeDelta first_input_to_presentation =
-        presentation_ts - first_input_generation_ts;
+        presentation_ts - *first_input_generation_ts;
     // Based on Chrome's past performance (`adjusted_delivery_cutoff`), how many
     // VSyncs ago could Chrome have presented the current frame's first input?
     // Note that we divide by `(1 - kDiscountFactor)` because we need to reverse
@@ -229,25 +303,22 @@
 
   // Rules 2 & 3: Fast scroll and fling continuity.
   bool cur_is_sufficiently_fast_fling =
-      max_abs_inertial_raw_delta_pixels >= kFlingContinuityThreshold;
-  bool cur_is_fast_scroll =
-      abs_total_raw_delta_pixels >= kFastScrollContinuityThreshold;
-  bool prev_is_fast_scroll = prev_frame_data.abs_total_raw_delta_pixels >=
-                             kFastScrollContinuityThreshold;
+      updates.real().has_value() && IsSufficientlyFastFling(*updates.real());
   if (cur_is_sufficiently_fast_fling) {
     if (prev_frame_data.has_inertial_input) {
       // Chrome missed one or more VSyncs in the middle of a fling.
       missed_vsyncs_per_reason[static_cast<int>(
           JankReason::kMissedVsyncDuringFling)] =
           vsyncs_since_previous_frame - 1;
-    } else if (prev_is_fast_scroll) {
+    } else if (prev_frame_data.is_most_recent_real_frame_fast_scroll) {
       // Chrome missed one or more VSyncs during the transition from a fast
       // regular scroll to a fling.
       missed_vsyncs_per_reason[static_cast<int>(
           JankReason::kMissedVsyncAtStartOfFling)] =
           vsyncs_since_previous_frame - 1;
     }
-  } else if (prev_is_fast_scroll && cur_is_fast_scroll) {
+  } else if (prev_frame_data.is_most_recent_real_frame_fast_scroll &&
+             treat_as_fast_scroll) {
     // Chrome missed one or more VSyncs in the middle of a fast regular scroll.
     missed_vsyncs_per_reason[static_cast<int>(
         JankReason::kMissedVsyncDuringFastScroll)] =
@@ -257,76 +328,79 @@
   return missed_vsyncs_per_reason;
 }
 
-std::optional<ScrollJankV4Decider::PreviousFrameData::PresentationData>
-ScrollJankV4Decider::CalculatePresentationData(
+std::optional<base::TimeDelta>
+ScrollJankV4Decider::CalculateRunningDeliveryCutoff(
     int vsyncs_since_previous_frame,
     bool is_janky,
-    base::TimeTicks last_input_generation_ts,
-    const ScrollDamage& damage,
+    const ScrollUpdates& updates,
+    const ScrollJankV4Frame::ScrollDamage& damage,
     const ScrollJankV4Frame::BeginFrameArgsForScrollJank& args,
     ScrollUpdateEventMetrics::ScrollJankV4Result& result) const {
   // We should consider Chrome's past performance
-  // (`prev_frame_data_->presentation_data->running_delivery_cutoff`) to update
+  // (`*prev_frame_data_->running_delivery_cutoff`) to update
   // the running delivery cut-off as long as there's data available for the
   // previous frame and the current frame is not janky. If there's no data
   // available for the previous frame (because the current frame is the first
-  // damaging frame in the scroll or the first damaging frame since the decider
-  // marked a non-damaging frame as jank), we start from scratch. Alternatively,
-  // if we've just marked the current frame as janky, forget Chrome's past
-  // performance and start from scratch.
+  // real damaging frame in the scroll or the first real damaging frame since
+  // the decider marked a synthetic and/or non-damaging frame as jank), we start
+  // from scratch. Alternatively, if we've just marked the current frame as
+  // janky, forget Chrome's past performance and start from scratch.
   std::optional<base::TimeDelta> discounted_prev_delivery_cutoff =
       [&]() -> std::optional<base::TimeDelta> {
     bool should_consider_prev_frame_cutoff =
         prev_frame_data_.has_value() &&
-        prev_frame_data_->presentation_data.has_value() && !is_janky;
+        prev_frame_data_->running_delivery_cutoff.has_value() && !is_janky;
     if (!should_consider_prev_frame_cutoff) {
       return std::nullopt;
     }
     static const double kDiscountFactor =
         features::kScrollJankV4MetricDiscountFactor.Get();
-    return prev_frame_data_->presentation_data->running_delivery_cutoff +
+    return *prev_frame_data_->running_delivery_cutoff +
            vsyncs_since_previous_frame * kDiscountFactor * args.interval;
   }();
 
-  if (const DamagingFrame* damaging_frame =
-          std::get_if<DamagingFrame>(&damage)) {
+  const DamagingFrame* damaging_frame = std::get_if<DamagingFrame>(&damage);
+
+  if (damaging_frame && updates.real().has_value()) {
     base::TimeDelta cur_delivery_cutoff =
-        damaging_frame->presentation_ts - last_input_generation_ts;
+        damaging_frame->presentation_ts -
+        updates.real()->last_input_generation_ts;
     result.current_delivery_cutoff = cur_delivery_cutoff;
-    base::TimeDelta new_running_delivery_cutoff =
-        discounted_prev_delivery_cutoff.has_value()
-            ? std::min(*discounted_prev_delivery_cutoff, cur_delivery_cutoff)
-            : cur_delivery_cutoff;
-    return PreviousFrameData::PresentationData{
-        .presentation_ts = damaging_frame->presentation_ts,
-        .running_delivery_cutoff = new_running_delivery_cutoff,
-    };
+    return discounted_prev_delivery_cutoff.has_value()
+               ? std::min(*discounted_prev_delivery_cutoff, cur_delivery_cutoff)
+               : cur_delivery_cutoff;
   }
 
-  if (discounted_prev_delivery_cutoff.has_value()) {
-    // If this is a non-damaging frame that's not janky, we pretend as if it
-    // was presented consistently, i.e. we assume that it has the same
-    // duration between its begin frame and presentation timestamps as the
-    // most recent damaging frame.
-    base::TimeTicks extrapolated_presentation_ts =
-        prev_frame_data_->presentation_data->presentation_ts +
-        (args.frame_time - prev_frame_data_->begin_frame_ts);
-    // We don't know whether Chrome would have been actually able to deliver
-    // the non-damaging inputs at `extrapolated_presentation_ts`, so we don't
-    // calculate the current frame's delivery cut-off. Instead, we keep
-    // discounting the previous frame's delivery cut-off.
-    return PreviousFrameData::PresentationData{
-        .presentation_ts = extrapolated_presentation_ts,
-        .running_delivery_cutoff = *discounted_prev_delivery_cutoff,
-    };
-  }
+  // If the frame is non-damaging, we don't know when Chrome would actually have
+  // presented it. Similarly, if the frame is synthetic, we don't know what's
+  // the latest input generation timestamp that Chrome would have included in
+  // the frame. Either way, we don't calculate the current frame's delivery
+  // cut-off. Instead, we keep discounting the previous frame's delivery
+  // cut-off.
+  return discounted_prev_delivery_cutoff;
+}
 
-  // If the decider hasn't received any damaging frames since the beginning
-  // of the scroll or since the most recent non-damaging frame that the
-  // decider marked as janky, then we cannot extrapolate Chrome's past
-  // performance to the current non-damaging frame. The same argument applies if
-  // the current non-damaging frame is janky.
-  return std::nullopt;
+std::optional<base::TimeTicks>
+ScrollJankV4Decider::GetEarliestScrollUpdateGenerationTs(
+    const ScrollUpdates& updates) const {
+  std::optional<base::TimeTicks>
+      extrapolated_first_synthetic_input_generation_ts =
+          [&]() -> std::optional<base::TimeTicks> {
+    if (!updates.synthetic().has_value() || !prev_frame_data_.has_value() ||
+        !prev_frame_data_->last_input_generation_ts.has_value()) {
+      return std::nullopt;
+    }
+    return *prev_frame_data_->last_input_generation_ts +
+           (updates.synthetic()->first_input_begin_frame_ts -
+            prev_frame_data_->begin_frame_ts);
+  }();
+  if (updates.real().has_value() &&
+      (!extrapolated_first_synthetic_input_generation_ts.has_value() ||
+       updates.real()->first_input_generation_ts <=
+           *extrapolated_first_synthetic_input_generation_ts)) {
+    return updates.real()->first_input_generation_ts;
+  }
+  return extrapolated_first_synthetic_input_generation_ts;
 }
 
 void ScrollJankV4Decider::Reset() {
diff --git a/cc/metrics/scroll_jank_v4_decider.h b/cc/metrics/scroll_jank_v4_decider.h
index 852a0fc..c57475f 100644
--- a/cc/metrics/scroll_jank_v4_decider.h
+++ b/cc/metrics/scroll_jank_v4_decider.h
@@ -6,12 +6,12 @@
 #define CC_METRICS_SCROLL_JANK_V4_DECIDER_H_
 
 #include <optional>
-#include <variant>
+#include <utility>
 
 #include "base/time/time.h"
 #include "cc/cc_export.h"
-#include "cc/metrics/event_metrics.h"
 #include "cc/metrics/scroll_jank_v4_frame.h"
+#include "cc/metrics/scroll_jank_v4_frame_stage.h"
 
 namespace cc {
 
@@ -20,35 +20,35 @@
 // work correctly, it must be informed about each frame that contained one or
 // more scroll updates in chronological order.
 //
-// To avoid false positives, the decider must even be informed about
-// non-damaging scroll updates and frames. See
-// `ScrollUpdateEventMetrics::ScrollJankV4Result::is_damaging_frame` for the
-// definition of non-damaging scroll updates and frames.
+// Scroll updates (and subsequently frames) can be categorized according to the
+// following criteria:
+//
+//   1. Whether the scroll update cause a frame update and changed the scroll
+//      offset: damaging vs. non-damaging scroll updates. See
+//      `ScrollJankV4Result::is_damaging_frame` for
+//      the definition of non-damaging scroll updates and frames.
+//   2. Whether the scroll update originated from hardware/OS: real vs.
+//      synthetic scroll updates. See `ScrollJankV4FrameStage::ScrollUpdates`
+//      for the definition of synthetic scroll updates and frames.
+//
+// To avoid false positives, the decider must be informed about all four types
+// of scroll updates and frames that occur within a scroll (damaging real,
+// non-damaging real, damaging synthetic, non-damaging synthetic).
 //
 // See
 // https://docs.google.com/document/d/1AaBvTIf8i-c-WTKkjaL4vyhQMkSdynxo3XEiwpofdeA
 // for more details about the scroll jank v4 metric.
 class CC_EXPORT ScrollJankV4Decider {
  public:
-  // Decides whether a frame which contains scroll updates was janky based on
-  // the following information:
+  // Decides whether a real frame, which contains at least one real scroll
+  // update, was janky based on the following information:
   //
-  //   * `first_input_generation_ts` and `last_input_generation_ts`: The
-  //     generation timestamp of the first and last scroll update included
-  //     (coalesced) in the frame.
-  //      updates included (coalesced) in the frame.
+  //   * `updates` the real and/or synthetic scroll updates included (coalesced)
+  //     in the frame.
   //   * `damage`: Information about a frame's scroll damage. For damaging
   //     frames, `DamagingFrame::presentation_ts` specifies when the frame was
   //     presented to the user.
   //   * `args`: The presented frame's arguments (especially `args.interval`).
-  //   * `has_inertial_input`: Whether at least one of the scroll updates in the
-  //     frame was inertial.
-  //   * `abs_total_raw_delta_pixels`: The absolute value of the total raw delta
-  //     (`ScrollUpdateEventMetrics::delta()`) of all scroll updates included in
-  //     the frame.
-  //   * `max_abs_inertial_raw_delta_pixels`: The maximum absolute value of raw
-  //     delta (`ScrollUpdateEventMetrics::delta()`) over all inertial scroll
-  //     updates included in the frame.
   //
   // This method treats non-damaging frames as if Chrome successfully presented
   // them on time, even if Chrome ended up not presenting the frames or they
@@ -56,181 +56,247 @@
   // user can't tell whether Chrome presented the frame on time (or even whether
   // Chrome presented the frame at all).
   //
-  // Returns an empty optional if the frame is malformed in some way (e.g. it
-  // has an earlier presentation time than the previous frame provided to the
-  // decider).
-  std::optional<ScrollUpdateEventMetrics::ScrollJankV4Result>
-  DecideJankForFrameWithScrollUpdates(
-      base::TimeTicks first_input_generation_ts,
-      base::TimeTicks last_input_generation_ts,
+  // This method requires that the frame is valid (see `IsValidFrame()`).
+  // Furthermore, calls to this method must be ordered chronologically with
+  // respect to the begin frame timestamp, i.e. given the following calls:
+  //
+  // ```
+  // decider.DecideJankForFrameWithRealScrollUpdates(updates1, damage1, args1,
+  //                                                 treat_as_fast_scroll1);
+  // decider.DecideJankForFrameWithRealScrollUpdates(updates2, damage2, args2,
+  //                                                 treat_as_fast_scroll2);
+  // ```
+  //
+  // `args2.frame_time` must be strictly greater than `args1.frame_time`.
+  ScrollUpdateEventMetrics::ScrollJankV4Result
+  DecideJankForFrameWithRealScrollUpdates(
+      const ScrollJankV4FrameStage::ScrollUpdates& updates,
+      const ScrollJankV4Frame::ScrollDamage& damage,
+      const ScrollJankV4Frame::BeginFrameArgsForScrollJank& args);
+
+  // Decides whether a synthetic frame, which contains only synthetic scroll
+  // updates, was janky based on the following information:
+  //
+  // This method behaves similarly to
+  // `DecideJankForFrameWithRealScrollUpdates()` with one additional argument:
+  //
+  //   * `future_real_frame_is_fast_scroll_or_sufficiently_fast_fling`: Whether
+  //     the earliest future real frame is a fast scroll or a sufficiently fast
+  //     scroll (i.e.
+  //     `future_real_frame_is_fast_scroll_or_sufficiently_fast_fling ==
+  //     (IsFastScroll(future_real) || IsSufficientlyFastFling(future_real))`).
+  //     False if there are no real frames between this synthetic frame and the
+  //     end of the current scroll.
+  //
+  // Note: The method signature (specifically the
+  // `future_real_frame_is_fast_scroll_or_sufficiently_fast_fling` argument)
+  // might seem awkward because, in order to decide whether a synthetic frame is
+  // janky, the decider needs information about the speed of a future real
+  // frame. `ScrollJankV4DecisionQueue` takes care of this "look-ahead"
+  // dependency and presents a simpler callback-based API.
+  ScrollUpdateEventMetrics::ScrollJankV4Result
+  DecideJankForFrameWithSyntheticScrollUpdatesOnly(
+      const ScrollJankV4FrameStage::ScrollUpdates& updates,
       const ScrollJankV4Frame::ScrollDamage& damage,
       const ScrollJankV4Frame::BeginFrameArgsForScrollJank& args,
-      bool has_inertial_input,
-      float abs_total_raw_delta_pixels,
-      float max_abs_inertial_raw_delta_pixels);
+      bool future_real_frame_is_fast_scroll_or_sufficiently_fast_fling);
 
   void OnScrollStarted();
   void OnScrollEnded();
 
+  static bool IsValidFrame(
+      const ScrollJankV4FrameStage::ScrollUpdates& updates,
+      const ScrollJankV4Frame::ScrollDamage& damage,
+      const ScrollJankV4Frame::BeginFrameArgsForScrollJank& args);
+  static bool IsFastScroll(
+      const ScrollJankV4FrameStage::ScrollUpdates::Real& real_updates);
+  static bool IsSufficientlyFastFling(
+      const ScrollJankV4FrameStage::ScrollUpdates::Real& real_updates);
+
  private:
-  // Information about the previous frame relevant for the scroll jank v4
-  // metric.
+  // Information about the previous frame, for which the decider has most
+  // recently decided whether it's janky or not.
   struct PreviousFrameData {
     // Whether the previous frame contained an inertial input (i.e. was it a
     // fling).
     bool has_inertial_input;
 
-    // The absolute total raw (unpredicted) delta of all inputs included in the
-    // previous frame (in pixels).
-    float abs_total_raw_delta_pixels;
+    // Whether the most recent real frame (possibly the previous frame) was a
+    // fast scroll.
+    //
+    // True if the absolute total raw (unpredicted) delta of all
+    // real inputs in the most recent real frame was at least
+    // `features::kScrollJankV4MetricFastScrollContinuityThreshold`. See
+    // `IsFastScroll()`.
+    //
+    // False if there have been no real frames since the start of the scroll
+    // (which is very unlikely).
+    bool is_most_recent_real_frame_fast_scroll;
+
+    // When the last real input included (coalesced) in the previous frame was
+    // generated by the hardware.
+    //
+    // If the frame was synthetic (i.e. contained only synthetic inputs):
+    //
+    //   1. If there have been no real frames since the beginning of the
+    //      scroll, this value is empty.
+    //   2. If the frame that the decider most recently marked as janky was
+    //      synthetic and there have been no real frames since then, this
+    //      value is empty.
+    //   3. Otherwise, this value is extrapolated from the most recently
+    //      presented real frame (i.e. we assume a constant duration between
+    //      `last_input_generation_ts` and `begin_frame_ts`):
+    //
+    //        ```
+    //        synthetic_frame.last_input_generation_ts
+    //          = real_frame.last_input_generation_ts
+    //          + (synthetic_input.begin_frame_ts - real_frame.begin_frame_ts)
+    //        ```
+    //
+    //      where `synthetic_input.begin_frame_ts` refers to the frame for
+    //      which the synthetic input was originally predicted and
+    //      `synthetic_frame` refers to the frame where it was actually
+    //      presented (the same frame if it was presented on time or if it was
+    //      a non-damaging frame).
+    std::optional<base::TimeTicks> last_input_generation_ts;
 
     // The time at which the frame started. See
     // `viz::BeginFrameArgs::frame_time`.
+    //
+    // The following inequality should hold:
+    //
+    //   ```
+    //   *last_input_generation_ts <= begin_frame_ts <= *presentation_ts
+    //   ```
     base::TimeTicks begin_frame_ts;
 
-    struct PresentationData {
-      // When the previous frame was presented to the user.
-      //
-      // If the previous frame was non-damaging, this value is instead
-      // extrapolated from the most recently presented damaging frame (i.e. we
-      // assume a constant duration between `begin_frame_ts` and
-      // `presentation_ts`):
-      //
-      // ```
-      // non_damaging_frame.presentation_data.presentation_ts
-      //   = non_damaging_frame.begin_frame_ts
-      //   + (presented_damaging_frame.presentation_data.presentation_ts
-      //        - presented_damaging_frame.begin_frame_ts)
-      // ```
-      base::TimeTicks presentation_ts;
+    // When the frame was presented to the user.
+    //
+    // If the frame was non-damaging:
+    //
+    //   1. If there have been no damaging frames since the beginning of the
+    //      scroll, this value is empty.
+    //   2. If the frame that the decider most recently marked as janky was
+    //      non-damaging and there have been no damaging frames since then,
+    //      this value is empty.
+    //   3. Otherwise, this value is extrapolated from the most recently
+    //      presented damaging frame (i.e. we assume a constant duration
+    //      between `begin_frame_ts` and `presentation_ts`):
+    //
+    //        ```
+    //        non_damaging_frame.presentation_ts
+    //          = presented_damaging_frame.presentation_ts
+    //          + (non_damaging_frame.begin_frame_ts
+    //              - presented_damaging_frame.begin_frame_ts)
+    //        ```
+    std::optional<base::TimeTicks> presentation_ts;
 
-      // The running delivery cut-off. At a high-level, this value represents
-      // how quickly Chrome was previously able to present inputs (weighted
-      // towards recent frames). If Chrome misses a VSync, the decider will
-      // judge the subsequent frame (i.e. determine whether the frame should
-      // be marked as janky) against this value. This value equals:
-      //
-      // ```
-      // min_{i from 1 to N} (
-      //   presentation_ts[i]
-      //     - last_input_generation_ts[i]
-      //     + (
-      //         VsyncsBetween(i, N)
-      //           * features::kScrollJankV4MetricDiscountFactor.Get()
-      //           * vsync_interval
-      //       )
-      // )
-      // ```
-      //
-      // where:
-      //
-      //   * `i = 1` corresponds a presented damaging frame as follows:
-      //       * If the frame that the decider most recently marked as janky was
-      //         damaging, `i = 1` corresponds to that janky frame.
-      //       * If the frame that the decider most recently marked as janky was
-      //         non-damaging, `i = 1` corresponds to the first damaging frame
-      //         that the decider processed after that janky frame.
-      //       * If the decider hasn't marked any frame in this scroll as janky,
-      //         `i = 1` corresponds to the first damaging frame within the
-      //         current scroll.
-      //   * `i = N` corresponds to the frame (damaging or non-damaging) that
-      //     the decider has most recently processed.
-      //   * `presentation_ts[i]` and `last_input_generation_ts[i]` refer to:
-      //       * If the i-th frame was a damaging frame, they refer to the
-      //         values supplied to the i-th
-      //         `DecideJankForPresentedDamagingFrame()` call.
-      //       * If the i-th frame was a non-damaging frame, they refer to the
-      //         values supplied to the j-th
-      //         `DecideJankForPresentedDamagingFrame()` call where j was the
-      //         most recent damaging frame before i (we assume a constant
-      //         duration between `last_input_generation_ts` and
-      //         `presentation_ts`).
-      //   * `VsyncsBetween(i, N)` is approximately:
-      //
-      //     ```
-      //     (presentation_ts[N] - presentation_ts[i] + (vsync_interval / 2))
-      //       / vsync_interval
-      //     ```
-      //
-      //     Approximation for non-damaging frames:
-      //
-      //     ```
-      //     (begin_frame_ts[N] - begin_frame_ts[i] + (vsync_interval / 2))
-      //       / vsync_interval
-      //     ```
-      base::TimeDelta running_delivery_cutoff;
-    };
-
-    // The documentation of `prev_frame_data_` below explains when this field is
-    // non-empty.
-    std::optional<PresentationData> presentation_data;
+    // The running delivery cut-off. At a high-level, this value represents
+    // how quickly Chrome was previously able to present inputs (weighted
+    // towards recent frames). If Chrome misses a VSync, the decider will
+    // judge the subsequent frame (i.e. determine whether the frame should
+    // be marked as janky) against this value.
+    //
+    // If there have been no real damaging frames since the beginning scroll
+    // or the frame that the decider most recently marked as janky was
+    // synthetic or non-damaging and there have been no real damaging frames
+    // since then, this value is empty. Otherwise, this value equals:
+    //
+    // ```
+    // min_{i from 1 to N} (
+    //   presentation_ts[i]
+    //     - last_input_generation_ts[i]
+    //     + (
+    //         VsyncsBetween(i, N)
+    //           * features::kScrollJankV4MetricDiscountFactor.Get()
+    //           * vsync_interval
+    //       )
+    // )
+    // ```
+    //
+    // where:
+    //
+    //   * `i = 1` corresponds a presented real damaging frame as follows:
+    //       * If the frame that the decider most recently marked as janky was
+    //         real and damaging, `i = 1` corresponds to that janky frame.
+    //       * If the frame that the decider most recently marked as janky was
+    //         synthetic or non-damaging, `i = 1` corresponds to the first
+    //         real damaging frame that the decider processed after that janky
+    //         frame.
+    //       * If the decider hasn't marked any frame in this scroll as janky,
+    //         `i = 1` corresponds to the first real damaging frame within the
+    //         current scroll.
+    //   * `i = N` corresponds to the frame (damaging or non-damaging) that
+    //     the decider has most recently processed.
+    //   * `presentation_ts[i]` and `last_input_generation_ts[i]` refer to:
+    //       * If the i-th frame was a real damaging frame, they refer to the
+    //         values supplied to the i-th
+    //         `DecideJankForPresentedDamagingFrame()` call.
+    //       * Otherwise, assume `presentation_ts[i] ==
+    //       base::TimeTicks::Max()` and `last_input_generation_ts[i] ==
+    //       base::TimeTicks::Min()` (i.e. ignore the i-th frame if it was
+    //       synthetic or non-damaging).
+    //   * `VsyncsBetween(i, N)` is approximately:
+    //
+    //     ```
+    //     (presentation_ts[N] - presentation_ts[i] + (vsync_interval / 2))
+    //       / vsync_interval
+    //     ```
+    //
+    //     Approximation for non-damaging frames:
+    //
+    //     ```
+    //     (begin_frame_ts[N] - begin_frame_ts[i] + (vsync_interval / 2))
+    //       / vsync_interval
+    //     ```
+    std::optional<base::TimeDelta> running_delivery_cutoff;
   };
 
-  void Reset();
-
-  bool IsValidFrame(
-      base::TimeTicks first_input_generation_ts,
-      base::TimeTicks last_input_generation_ts,
+  ScrollUpdateEventMetrics::ScrollJankV4Result
+  DecideJankForFrameWithScrollUpdates(
+      const ScrollJankV4FrameStage::ScrollUpdates& updates,
       const ScrollJankV4Frame::ScrollDamage& damage,
-      const ScrollJankV4Frame::BeginFrameArgsForScrollJank& args) const;
+      const ScrollJankV4Frame::BeginFrameArgsForScrollJank& args,
+      bool treat_as_fast_scroll);
 
   JankReasonArray<int> CalculateMissedVsyncsPerReason(
       int vsyncs_since_previous_frame,
-      base::TimeTicks first_input_generation_ts,
+      std::optional<base::TimeTicks> earliest_input_generation_ts,
+      const ScrollJankV4FrameStage::ScrollUpdates& updates,
       const ScrollJankV4Frame::ScrollDamage& damage,
       const ScrollJankV4Frame::BeginFrameArgsForScrollJank& args,
-      float abs_total_raw_delta_pixels,
-      float max_abs_inertial_raw_delta_pixels,
+      bool treat_as_fast_scroll,
       ScrollUpdateEventMetrics::ScrollJankV4Result& result) const;
 
-  std::optional<PreviousFrameData::PresentationData> CalculatePresentationData(
+  std::optional<base::TimeDelta> CalculateRunningDeliveryCutoff(
       int vsyncs_since_previous_frame,
       bool is_janky,
-      base::TimeTicks last_input_generation_ts,
+      const ScrollJankV4FrameStage::ScrollUpdates& updates,
       const ScrollJankV4Frame::ScrollDamage& damage,
       const ScrollJankV4Frame::BeginFrameArgsForScrollJank& args,
       ScrollUpdateEventMetrics::ScrollJankV4Result& result) const;
 
-  // Information about the previous frame, which can be in three states (2A and
-  // 2B are different conditions for the same state):
+  void Reset();
+
+  // Returns the earliest input generation timestamp of scroll updates in
+  // `updates`.
   //
-  //   1.  If the decider hasn't been informed about any frames (damaging or
-  //       non-damaging) since the beginning of the current scroll (i.e. neither
-  //       `DecideJankForPresentedDamagingFrame()` nor
-  //       `DecideJankForNonDamagingFrame()` has been called since the last call
-  //       to either `OnScrollStarted()` or `OnScrollEnded()`), then
-  //       `prev_frame_data_` is empty.
-  //   2A. If the decider has only been informed about non-damaging frames since
-  //       the beginning of the current scroll (i.e. only
-  //       `DecideJankForNonDamagingFrame()` has been called since the last call
-  //       to either `OnScrollStarted()` or `OnScrollEnded()`), then
-  //       `prev_frame_data_` has a value but
-  //       `prev_frame_data_.presentation_data` is empty.
-  //   2B. If the decider marked a non-damaging frame as janky and it has only
-  //       been informed about non-damaging frames since then (i.e. only
-  //       `DecideJankForNonDamagingFrame()` has been called since
-  //       `DecideJankForNonDamagingFrame()` returned a janky result), then
-  //       `prev_frame_data_` has a value but
-  //       `prev_frame_data_.presentation_data` is empty.
-  //   3.  Otherwise, both `prev_frame_data` and
-  //       `prev_frame_data_.presentation_data` have values.
+  // For real scroll updates, this method uses their actual input generation
+  // timestamp.
   //
-  // The state has the following practical implications for the decider's
-  // behavior on the next frame:
+  // For synthetic scroll updates, this method extrapolates their input
+  // generation timestamp from their begin frame timestamp and the most recently
+  // presented real frame (similarly to
+  // `PreviousFrameData::last_input_generation_ts`).
+  std::optional<base::TimeTicks> GetEarliestScrollUpdateGenerationTs(
+      const ScrollJankV4FrameStage::ScrollUpdates& updates) const;
+
+  // Information about the previous frame, for which the decider has most
+  // recently decided whether it's janky or not.
   //
-  //   * If `prev_frame_data_` is empty, then there's no information about the
-  //     previous frame, so the decider will definitely NOT mark the next frame
-  //     as janky.
-  //   * If `prev_frame_data_` has a value but
-  //     `prev_frame_data_.presentation_data`
-  //     is empty, then the decider cannot evaluate Chrome's input→frame
-  //     delivery, so it will definitely NOT mark the next frame as janky due to
-  //     `JankReason::kMissedVsyncDueToDeceleratingInputFrameDelivery`. However,
-  //     the decider MIGHT still mark the next frame as janky for any other
-  //     `JankReason`.
-  //   * If both `prev_frame_data` and `prev_frame_data_.presentation_data` have
-  //     values, the decider MIGHT mark the next frame as janky for any
-  //     `JankReason`.
+  // Empty if the decider hasn't processed any frames since the beginning of
+  // the current scroll, in which case the decider will definitely NOT mark
+  // the next frame as janky.
   std::optional<PreviousFrameData> prev_frame_data_ = std::nullopt;
 };
 
diff --git a/cc/metrics/scroll_jank_v4_decider_unittest.cc b/cc/metrics/scroll_jank_v4_decider_unittest.cc
index d9405f31..fe91b1b 100644
--- a/cc/metrics/scroll_jank_v4_decider_unittest.cc
+++ b/cc/metrics/scroll_jank_v4_decider_unittest.cc
@@ -6,7 +6,10 @@
 
 #include <optional>
 #include <string>
+#include <tuple>
+#include <vector>
 
+#include "base/strings/strcat.h"
 #include "base/time/time.h"
 #include "cc/metrics/event_metrics.h"
 #include "cc/metrics/scroll_jank_v4_frame.h"
@@ -14,7 +17,6 @@
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace cc {
-
 namespace {
 
 constexpr base::TimeDelta kVsyncInterval = base::Milliseconds(16);
@@ -30,21 +32,22 @@
 using ScrollDamage = ScrollJankV4Frame::ScrollDamage;
 using DamagingFrame = ScrollJankV4Frame::DamagingFrame;
 using NonDamagingFrame = ScrollJankV4Frame::NonDamagingFrame;
+using ScrollUpdates = ScrollJankV4FrameStage::ScrollUpdates;
+using Real = ScrollUpdates::Real;
+using Synthetic = ScrollUpdates::Synthetic;
 using ScrollJankV4Result = ScrollUpdateEventMetrics::ScrollJankV4Result;
 
 /* Matches a result iff `matcher` matches `result->missed_vsyncs_per_reason`. */
-::testing::Matcher<const std::optional<ScrollJankV4Result>&>
-HasMissedVsyncsPerReasonMatching(
+::testing::Matcher<const ScrollJankV4Result&> HasMissedVsyncsPerReasonMatching(
     ::testing::Matcher<const JankReasonArray<int>&> matcher) {
-  return ::testing::Optional(
-      ::testing::Field(&ScrollJankV4Result::missed_vsyncs_per_reason, matcher));
+  return ::testing::Field(&ScrollJankV4Result::missed_vsyncs_per_reason,
+                          matcher);
 }
 
-const ::testing::Matcher<const std::optional<ScrollJankV4Result>&>
-    kHasNoMissedVsyncs =
-        HasMissedVsyncsPerReasonMatching(::testing::Each(::testing::Eq(0)));
+const ::testing::Matcher<const ScrollJankV4Result&> kHasNoMissedVsyncs =
+    HasMissedVsyncsPerReasonMatching(::testing::Each(::testing::Eq(0)));
 
-::testing::Matcher<const std::optional<ScrollJankV4Result>&> HasMissedVsyncs(
+::testing::Matcher<const ScrollJankV4Result&> HasMissedVsyncs(
     JankReason reason,
     int missed_vsyncs) {
   JankReasonArray<int> expected_missed_vsyncs = {};
@@ -53,285 +56,640 @@
       ::testing::ElementsAreArray(expected_missed_vsyncs));
 }
 
-}  // namespace
-
 class ScrollJankV4DeciderTest : public testing::Test {
  protected:
-  ScrollJankV4Decider decider_;
-
   static ScrollJankV4Frame::BeginFrameArgsForScrollJank CreateBeginFrameArgs(
       base::TimeTicks frame_time) {
     return {.frame_time = frame_time, .interval = kVsyncInterval};
   }
+
+  ScrollJankV4Decider decider_;
 };
 
+// Type of a frame provided to `ScrollJankV4Decider` along two axes:
+//
+//   * whether it's damaging or non-damaging,
+//   * whether it contains only real scroll updates, only synthetic scroll
+//     updates, or both.
+struct TestFrameType {
+  std::string frame_type_name;
+  bool is_damaging;
+  bool has_real_inputs;
+  bool has_synthetic_inputs;
+};
+
+static const std::vector<TestFrameType> kAllFrameTypes = []() {
+  std::vector<TestFrameType> frame_types = {};
+  for (const auto is_damaging : {false, true}) {
+    const char* damage_name = is_damaging ? "Damaging" : "NonDamaging";
+    frame_types.push_back({
+        .frame_type_name = base::StrCat({damage_name, "RealOnly"}),
+        .is_damaging = is_damaging,
+        .has_real_inputs = true,
+        .has_synthetic_inputs = false,
+    });
+    frame_types.push_back({
+        .frame_type_name = base::StrCat({damage_name, "SyntheticOnly"}),
+        .is_damaging = is_damaging,
+        .has_real_inputs = false,
+        .has_synthetic_inputs = true,
+    });
+    frame_types.push_back({
+        .frame_type_name = base::StrCat({damage_name, "BothRealAndSynthetic"}),
+        .is_damaging = is_damaging,
+        .has_real_inputs = true,
+        .has_synthetic_inputs = true,
+    });
+  }
+  return frame_types;
+}();
+
+static const std::vector<TestFrameType> kRealOnlyFrameTypes = []() {
+  std::vector<TestFrameType> frame_types = {};
+  for (const auto is_damaging : {false, true}) {
+    const char* damage_name = is_damaging ? "Damaging" : "NonDamaging";
+    frame_types.push_back({
+        .frame_type_name = base::StrCat({damage_name, "RealOnly"}),
+        .is_damaging = is_damaging,
+        .has_real_inputs = true,
+        .has_synthetic_inputs = false,
+    });
+  }
+  return frame_types;
+}();
+
+// Fixture for tests which are parameterized with one or two `TestFrameType`s.
+class ParameterizedScrollJankV4DeciderTest : public ScrollJankV4DeciderTest {
+ protected:
+  // Recipe which specifies how to construct a frame (call to
+  // `ScrollJankV4Decider::DecideJankForFrameWith*()`) for a `TestFrameType`.
+  //
+  // For example, given the following recipe:
+  //
+  // ```
+  // TestFrameRecipe{
+  //   .if_real = Real{R},
+  //   .if_synthetic = Synthetic{S},
+  //   .if_synthetic_only = {
+  //     .future_real_frame_is_fast_scroll_or_sufficiently_fast_fling = F,
+  //   },
+  //   .if_damaging = DamagingFrame{D},
+  //   .args = BeginFrameArgsForScrollJank{A},
+  // }
+  // ```
+  //
+  // 1. If combined with the following frame type:
+  //
+  //    ```
+  //    TestFrameType{
+  //      .is_damaging = true,
+  //      .has_real_inputs = true,
+  //      .has_synthetic_inputs = false,
+  //    }
+  //    ```
+  //
+  //    the recipe corresponds to the following frame:
+  //
+  //    ```
+  //    decider.DecideJankForFrameWithRealScrollUpdates(
+  //      ScrollUpdates(/* earliest_event= */ nullptr, Real{R}, /* synthetic= */
+  //      std::nullopt), DamagingFrame{D}, BeginFrameArgsForScrollJank{A});
+  //    ```
+  //
+  // 2. If combined with the following frame type:
+  //
+  //    ```
+  //    TestFrameType{
+  //      .is_damaging = false,
+  //      .has_real_inputs = false,
+  //      .has_synthetic_inputs = true,
+  //    }
+  //    ```
+  //
+  //    the recipe corresponds to the following frame:
+  //
+  //    ```
+  //    decider.DecideJankForFrameWithSyntheticScrollUpdatesOnly(
+  //      ScrollUpdates(/* earliest_event= */ nullptr, /* real= */ std::nullopt,
+  //      Synthetic{S}), NonDamagingFrame{}, BeginFrameArgsForScrollJank{A},
+  //      /* future_real_frame_is_fast_scroll_or_sufficiently_fast_fling= */ F);
+  //    ```
+  //
+  // Note: All fields are declared as `std::optional` so that callers wouldn't
+  // have to provide parameters which aren't relevant for their test case (e.g.
+  // if they exclude frames containing synthetic scroll updates). At the same
+  // time, `std::optional` allows `DecideJankForParameterizedFrame()` below to
+  // enforce that callers provided all required parameters (instead of silently
+  // using their default values).
+  struct TestFrameRecipe {
+    std::optional<Real> if_real = std::nullopt;
+    std::optional<Synthetic> if_synthetic = std::nullopt;
+    struct {
+      std::optional<bool>
+          future_real_frame_is_fast_scroll_or_sufficiently_fast_fling =
+              std::nullopt;
+    } if_synthetic_only;
+    std::optional<DamagingFrame> if_damaging = std::nullopt;
+    std::optional<ScrollJankV4Frame::BeginFrameArgsForScrollJank> args =
+        std::nullopt;
+  };
+
+  ScrollJankV4Result DecideJankForParameterizedFrame(
+      const TestFrameType& frame_type,
+      const TestFrameRecipe& frame_recipe) {
+#define GET_FRAME_RECIPE_PARAM_OR_FAIL(param)                           \
+  [&]() {                                                               \
+    if (!frame_recipe.param.has_value()) {                              \
+      ADD_FAILURE() << "Missing ." #param " parameter in frame_recipe"; \
+    }                                                                   \
+    return *frame_recipe.param;                                         \
+  }()
+    const ScrollDamage damage =
+        frame_type.is_damaging
+            ? ScrollDamage{GET_FRAME_RECIPE_PARAM_OR_FAIL(if_damaging)}
+            : ScrollDamage{NonDamagingFrame{}};
+    const ScrollJankV4Frame::BeginFrameArgsForScrollJank& args =
+        GET_FRAME_RECIPE_PARAM_OR_FAIL(args);
+
+    if (frame_type.has_real_inputs) {
+      return decider_.DecideJankForFrameWithRealScrollUpdates(
+          ScrollUpdates(
+              /* earliest_event= */ nullptr,
+              GET_FRAME_RECIPE_PARAM_OR_FAIL(if_real),
+              frame_type.has_synthetic_inputs
+                  ? std::make_optional(
+                        GET_FRAME_RECIPE_PARAM_OR_FAIL(if_synthetic))
+                  : std::nullopt),
+          damage, args);
+    }
+    return decider_.DecideJankForFrameWithSyntheticScrollUpdatesOnly(
+        ScrollUpdates(/* earliest_event= */ nullptr, /* real= */ std::nullopt,
+                      GET_FRAME_RECIPE_PARAM_OR_FAIL(if_synthetic)),
+        damage, args,
+        GET_FRAME_RECIPE_PARAM_OR_FAIL(
+            if_synthetic_only
+                .future_real_frame_is_fast_scroll_or_sufficiently_fast_fling));
+#undef GET_FRAME_RECIPE_PARAM_OR_FAIL
+  }
+};
+
+// Fixture for tests parameterized with one `TestFrameType`.
+//
+// Legend for diagrams:
+//
+//   (p)  = frame with the parameterized type (`GetParam()`)
+//   (RD) = real damaging frame
+class SinglyParameterizedScrollJankV4DeciderTest
+    : public ParameterizedScrollJankV4DeciderTest,
+      public testing::WithParamInterface<TestFrameType> {};
+
+INSTANTIATE_TEST_SUITE_P(
+    SinglyParameterizedScrollJankV4DeciderTest,
+    SinglyParameterizedScrollJankV4DeciderTest,
+    testing::ValuesIn(kAllFrameTypes),
+    [](const testing::TestParamInfo<
+        SinglyParameterizedScrollJankV4DeciderTest::ParamType>& info) {
+      return info.param.frame_type_name;
+    });
+
+// Fixture for tests parameterized with two different `TestFrameType`s. This
+// allows tests to check that certain behaviors are preserved even when the
+// frame type changes mid-scroll.
+//
+// Legend for diagrams:
+//
+//   (a)  = frame with the first parameterized type (`frame_type_a_`)
+//   (b)  = frame with the second parameterized type (`frame_type_b_`)
+//   (RD) = real damaging frame
+class DoublyParameterizedScrollJankV4DeciderTest
+    : public ParameterizedScrollJankV4DeciderTest,
+      public testing::WithParamInterface<
+          std::tuple<TestFrameType, TestFrameType>> {
+ public:
+  DoublyParameterizedScrollJankV4DeciderTest()
+      : frame_type_a_(std::get<0>(GetParam())),
+        frame_type_b_(std::get<1>(GetParam())) {}
+
+ protected:
+  const TestFrameType frame_type_a_;
+  const TestFrameType frame_type_b_;
+};
+INSTANTIATE_TEST_SUITE_P(
+    DoublyParameterizedScrollJankV4DeciderTest,
+    DoublyParameterizedScrollJankV4DeciderTest,
+    testing::Combine(testing::ValuesIn(kAllFrameTypes),
+                     testing::ValuesIn(kAllFrameTypes)),
+    [](const testing::TestParamInfo<
+        DoublyParameterizedScrollJankV4DeciderTest::ParamType>& info) {
+      return base::StrCat({std::get<0>(info.param).frame_type_name, "To",
+                           std::get<1>(info.param).frame_type_name});
+    });
+
+// Fixture for tests parameterized with two different `TestFrameType`s where the
+// SECOND type only contains real scroll updates.
+//
+// This fixture is used for tests that focus on the transition from a regular
+// scroll to an inertial scroll (because inertial scroll updates cannot be
+// synthetic).
+class FlingTransitionDoublyParameterizedScrollJankV4DeciderTest
+    : public DoublyParameterizedScrollJankV4DeciderTest {};
+INSTANTIATE_TEST_SUITE_P(
+    FlingTransitionDoublyParameterizedScrollJankV4DeciderTest,
+    FlingTransitionDoublyParameterizedScrollJankV4DeciderTest,
+    testing::Combine(testing::ValuesIn(kAllFrameTypes),
+                     testing::ValuesIn(kRealOnlyFrameTypes)),
+    [](const testing::TestParamInfo<
+        FlingTransitionDoublyParameterizedScrollJankV4DeciderTest::ParamType>&
+           info) {
+      return base::StrCat({std::get<0>(info.param).frame_type_name, "To",
+                           std::get<1>(info.param).frame_type_name});
+    });
+
+// Fixture for tests parameterized with two different `TestFrameType`s where
+// BOTH types only contain real scroll updates.
+//
+// This fixture is used for tests that focus on an ongoing inertial scroll
+// (because inertial scroll updates cannot be synthetic).
+class MidFlingDoublyParameterizedScrollJankV4DeciderTest
+    : public DoublyParameterizedScrollJankV4DeciderTest {};
+INSTANTIATE_TEST_SUITE_P(
+    MidFlingDoublyParameterizedScrollJankV4DeciderTest,
+    MidFlingDoublyParameterizedScrollJankV4DeciderTest,
+    testing::Combine(testing::ValuesIn(kRealOnlyFrameTypes),
+                     testing::ValuesIn(kRealOnlyFrameTypes)),
+    [](const testing::TestParamInfo<
+        MidFlingDoublyParameterizedScrollJankV4DeciderTest::ParamType>& info) {
+      return base::StrCat({std::get<0>(info.param).frame_type_name, "To",
+                           std::get<1>(info.param).frame_type_name});
+    });
+
 /*
-Test that regular frame production doesn't cause missed frames.
+Tests that the decider doesn't mark regular frame production in a fast scroll
+with one frame produced every VSync as janky.
 
-vsync                         v0      v1      v2
-                              |       |       |
-input   I0  I1  I2  I3  I4  I5
-        |   |   |   |   |   |
-F1:     |---------------------| {I0, I1}
-F2:             |---------------------| {I2, I3}
-F3:                     |---------------------| {I4, I5}
+VSync    V     V     V     V     V     V     V     V     V
+Input     I0 I1 I2 I3:I4 I5:I6 I7:I8 I9:I10  :     :     :
+           | |   | | : | | : | | : | | : |I11:     :     :
+F1(a):     |---------BF----|     :     : | | :     :     :
+F2(a):           |---------BF----|     :     :     :     :
+F3(b):                 |---------BF----|     :     :     :
+F4(b):                       |---------BF----|     :     :
+F5(a):                             |---------BF----|     :
+F6(a):                                   |---------BF----|
  */
-TEST_F(ScrollJankV4DeciderTest, FrameProducedEveryVsync) {
-  std::optional<ScrollJankV4Result> result1 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(103),
-          /* last_input_generation_ts= */ MillisSinceEpoch(111),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(148)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(132)),
-          /* has_inertial_input= */ true,
-          /* abs_total_raw_delta_pixels= */ 10.0f,
-          /* max_abs_inertial_raw_delta_pixels= */ 10.0f);
+TEST_P(DoublyParameterizedScrollJankV4DeciderTest,
+       FastScrollWithFramesProducedEveryVsync) {
+  // 2 frames with `frame_type_a_`.
+  ScrollJankV4Result result1 = DecideJankForParameterizedFrame(
+      frame_type_a_,
+      {
+          .if_real = Real{.first_input_generation_ts = MillisSinceEpoch(103),
+                          .last_input_generation_ts = MillisSinceEpoch(111),
+                          .has_inertial_input = false,
+                          .abs_total_raw_delta_pixels = 5.0f,
+                          .max_abs_inertial_raw_delta_pixels = 0.0f},
+          .if_synthetic =
+              Synthetic{.first_input_begin_frame_ts = MillisSinceEpoch(116)},
+          .if_synthetic_only =
+              {.future_real_frame_is_fast_scroll_or_sufficiently_fast_fling =
+                   true},
+          .if_damaging =
+              DamagingFrame{.presentation_ts = MillisSinceEpoch(132)},
+          .args = CreateBeginFrameArgs(MillisSinceEpoch(116)),
+      });
   EXPECT_THAT(result1, kHasNoMissedVsyncs);
-
-  std::optional<ScrollJankV4Result> result2 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(119),
-          /* last_input_generation_ts= */ MillisSinceEpoch(127),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(164)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(148)),
-          /* has_inertial_input= */ true,
-          /* abs_total_raw_delta_pixels= */ 10.0f,
-          /* max_abs_inertial_raw_delta_pixels= */ 10.0f);
+  ScrollJankV4Result result2 = DecideJankForParameterizedFrame(
+      frame_type_a_,
+      {
+          .if_real = Real{.first_input_generation_ts = MillisSinceEpoch(119),
+                          .last_input_generation_ts = MillisSinceEpoch(127),
+                          .has_inertial_input = false,
+                          .abs_total_raw_delta_pixels = 5.0f,
+                          .max_abs_inertial_raw_delta_pixels = 0.0f},
+          .if_synthetic =
+              Synthetic{.first_input_begin_frame_ts = MillisSinceEpoch(132)},
+          .if_synthetic_only =
+              {.future_real_frame_is_fast_scroll_or_sufficiently_fast_fling =
+                   true},
+          .if_damaging =
+              DamagingFrame{.presentation_ts = MillisSinceEpoch(148)},
+          .args = CreateBeginFrameArgs(MillisSinceEpoch(132)),
+      });
   EXPECT_THAT(result2, kHasNoMissedVsyncs);
 
-  std::optional<ScrollJankV4Result> result3 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(135),
-          /* last_input_generation_ts= */ MillisSinceEpoch(143),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(180)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(164)),
-          /* has_inertial_input= */ true,
-          /* abs_total_raw_delta_pixels= */ 10.0f,
-          /* max_abs_inertial_raw_delta_pixels= */ 10.0f);
+  // 2 frames with `frame_type_b_`.
+  ScrollJankV4Result result3 = DecideJankForParameterizedFrame(
+      frame_type_b_,
+      {
+          .if_real = Real{.first_input_generation_ts = MillisSinceEpoch(135),
+                          .last_input_generation_ts = MillisSinceEpoch(143),
+                          .has_inertial_input = false,
+                          .abs_total_raw_delta_pixels = 5.0f,
+                          .max_abs_inertial_raw_delta_pixels = 0.0f},
+          .if_synthetic =
+              Synthetic{.first_input_begin_frame_ts = MillisSinceEpoch(148)},
+          .if_synthetic_only =
+              {.future_real_frame_is_fast_scroll_or_sufficiently_fast_fling =
+                   true},
+          .if_damaging =
+              DamagingFrame{.presentation_ts = MillisSinceEpoch(164)},
+          .args = CreateBeginFrameArgs(MillisSinceEpoch(148)),
+      });
   EXPECT_THAT(result3, kHasNoMissedVsyncs);
+  ScrollJankV4Result result4 = DecideJankForParameterizedFrame(
+      frame_type_b_,
+      {
+          .if_real = Real{.first_input_generation_ts = MillisSinceEpoch(151),
+                          .last_input_generation_ts = MillisSinceEpoch(159),
+                          .has_inertial_input = false,
+                          .abs_total_raw_delta_pixels = 5.0f,
+                          .max_abs_inertial_raw_delta_pixels = 0.0f},
+          .if_synthetic =
+              Synthetic{.first_input_begin_frame_ts = MillisSinceEpoch(164)},
+          .if_synthetic_only =
+              {.future_real_frame_is_fast_scroll_or_sufficiently_fast_fling =
+                   true},
+          .if_damaging =
+              DamagingFrame{.presentation_ts = MillisSinceEpoch(180)},
+          .args = CreateBeginFrameArgs(MillisSinceEpoch(164)),
+      });
+  EXPECT_THAT(result4, kHasNoMissedVsyncs);
+
+  // 2 frames with `frame_type_a_` again.
+  ScrollJankV4Result result5 = DecideJankForParameterizedFrame(
+      frame_type_a_,
+      {
+          .if_real = Real{.first_input_generation_ts = MillisSinceEpoch(167),
+                          .last_input_generation_ts = MillisSinceEpoch(175),
+                          .has_inertial_input = false,
+                          .abs_total_raw_delta_pixels = 5.0f,
+                          .max_abs_inertial_raw_delta_pixels = 0.0f},
+          .if_synthetic =
+              Synthetic{.first_input_begin_frame_ts = MillisSinceEpoch(180)},
+          .if_synthetic_only =
+              {.future_real_frame_is_fast_scroll_or_sufficiently_fast_fling =
+                   true},
+          .if_damaging =
+              DamagingFrame{.presentation_ts = MillisSinceEpoch(196)},
+          .args = CreateBeginFrameArgs(MillisSinceEpoch(180)),
+      });
+  EXPECT_THAT(result5, kHasNoMissedVsyncs);
+  ScrollJankV4Result result6 = DecideJankForParameterizedFrame(
+      frame_type_a_,
+      {
+          .if_real = Real{.first_input_generation_ts = MillisSinceEpoch(183),
+                          .last_input_generation_ts = MillisSinceEpoch(191),
+                          .has_inertial_input = false,
+                          .abs_total_raw_delta_pixels = 5.0f,
+                          .max_abs_inertial_raw_delta_pixels = 0.0f},
+          .if_synthetic =
+              Synthetic{.first_input_begin_frame_ts = MillisSinceEpoch(196)},
+          .if_synthetic_only =
+              {.future_real_frame_is_fast_scroll_or_sufficiently_fast_fling =
+                   true},
+          .if_damaging =
+              DamagingFrame{.presentation_ts = MillisSinceEpoch(212)},
+          .args = CreateBeginFrameArgs(MillisSinceEpoch(196)),
+      });
+  EXPECT_THAT(result6, kHasNoMissedVsyncs);
 }
 
 /*
-Test that sporadic input timing doesn't cause missed frames when no
-frame is expected.
-vsync                       v0              v1
-                    |       |       |       |
-input   I0  I1        I2  I3
-        |   |         |   |
-F1:     |-------------------| {I0, I1}
-F2:                   |---------------------| {I2, I3}
- */
-TEST_F(ScrollJankV4DeciderTest, NoFrameProducedForMissingInput) {
-  std::optional<ScrollJankV4Result> result1 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(103),
-          /* last_input_generation_ts= */ MillisSinceEpoch(111),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(148)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(132)),
-          /* has_inertial_input= */ false,
-          /* abs_total_raw_delta_pixels= */ 2.0f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.0f);
-  EXPECT_THAT(result1, kHasNoMissedVsyncs);
+Tests that the decider doesn't mark regular frame production in a slow scroll
+with one frame produced every VSync as janky.
 
-  std::optional<ScrollJankV4Result> result2 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(135),
-          /* last_input_generation_ts= */ MillisSinceEpoch(143),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(180)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(164)),
-          /* has_inertial_input= */ false,
-          /* abs_total_raw_delta_pixels= */ 2.0f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.0f);
+VSync    V   V   V   V   V   V   V   V   V   V   V   V   V   V   V   V   V
+Input     I0      I2     :I4     :I6     :I8     :I10    :       :       :
+           |I1     |I3   : |I5   : |I7   : |I9   : |I11  :       :       :
+           ||      ||    : ||    : ||    : ||    : ||    :       :       :
+F1(a):     |-------------BF------|       :       : ||    :       :       :
+F2(a):             |-------------BF------|       :       :       :       :
+F3(b):                     |-------------BF------|       :       :       :
+F4(b):                             |-------------BF------|       :       :
+F5(a):                                     |-------------BF------|       :
+F6(a):                                             |-------------BF------|
+ */
+TEST_P(DoublyParameterizedScrollJankV4DeciderTest,
+       SlowScrollWithFramesProducedEveryOtherVsync) {
+  // 2 frames with `frame_type_a_`.
+  ScrollJankV4Result result1 = DecideJankForParameterizedFrame(
+      frame_type_a_,
+      {
+          .if_real = Real{.first_input_generation_ts = MillisSinceEpoch(103),
+                          .last_input_generation_ts = MillisSinceEpoch(111),
+                          .has_inertial_input = false,
+                          .abs_total_raw_delta_pixels = 1.0f,
+                          .max_abs_inertial_raw_delta_pixels = 0.0f},
+          .if_synthetic =
+              Synthetic{.first_input_begin_frame_ts = MillisSinceEpoch(116)},
+          .if_synthetic_only =
+              {.future_real_frame_is_fast_scroll_or_sufficiently_fast_fling =
+                   false},
+          .if_damaging =
+              DamagingFrame{.presentation_ts = MillisSinceEpoch(132)},
+          .args = CreateBeginFrameArgs(MillisSinceEpoch(116)),
+      });
+  EXPECT_THAT(result1, kHasNoMissedVsyncs);
+  ScrollJankV4Result result2 = DecideJankForParameterizedFrame(
+      frame_type_a_,
+      {
+          .if_real = Real{.first_input_generation_ts = MillisSinceEpoch(135),
+                          .last_input_generation_ts = MillisSinceEpoch(143),
+                          .has_inertial_input = false,
+                          .abs_total_raw_delta_pixels = 1.0f,
+                          .max_abs_inertial_raw_delta_pixels = 0.0f},
+          .if_synthetic =
+              Synthetic{.first_input_begin_frame_ts = MillisSinceEpoch(148)},
+          .if_synthetic_only =
+              {.future_real_frame_is_fast_scroll_or_sufficiently_fast_fling =
+                   false},
+          .if_damaging =
+              DamagingFrame{.presentation_ts = MillisSinceEpoch(164)},
+          .args = CreateBeginFrameArgs(MillisSinceEpoch(148)),
+      });
   EXPECT_THAT(result2, kHasNoMissedVsyncs);
+
+  // 2 frames with `frame_type_b_`.
+  ScrollJankV4Result result3 = DecideJankForParameterizedFrame(
+      frame_type_b_,
+      {
+          .if_real = Real{.first_input_generation_ts = MillisSinceEpoch(167),
+                          .last_input_generation_ts = MillisSinceEpoch(175),
+                          .has_inertial_input = false,
+                          .abs_total_raw_delta_pixels = 1.0f,
+                          .max_abs_inertial_raw_delta_pixels = 0.0f},
+          .if_synthetic =
+              Synthetic{.first_input_begin_frame_ts = MillisSinceEpoch(180)},
+          .if_synthetic_only =
+              {.future_real_frame_is_fast_scroll_or_sufficiently_fast_fling =
+                   false},
+          .if_damaging =
+              DamagingFrame{.presentation_ts = MillisSinceEpoch(196)},
+          .args = CreateBeginFrameArgs(MillisSinceEpoch(180)),
+      });
+  EXPECT_THAT(result3, kHasNoMissedVsyncs);
+  ScrollJankV4Result result4 = DecideJankForParameterizedFrame(
+      frame_type_b_,
+      {
+          .if_real = Real{.first_input_generation_ts = MillisSinceEpoch(199),
+                          .last_input_generation_ts = MillisSinceEpoch(207),
+                          .has_inertial_input = false,
+                          .abs_total_raw_delta_pixels = 1.0f,
+                          .max_abs_inertial_raw_delta_pixels = 0.0f},
+          .if_synthetic =
+              Synthetic{.first_input_begin_frame_ts = MillisSinceEpoch(212)},
+          .if_synthetic_only =
+              {.future_real_frame_is_fast_scroll_or_sufficiently_fast_fling =
+                   false},
+          .if_damaging =
+              DamagingFrame{.presentation_ts = MillisSinceEpoch(228)},
+          .args = CreateBeginFrameArgs(MillisSinceEpoch(212)),
+      });
+  EXPECT_THAT(result4, kHasNoMissedVsyncs);
+
+  // 2 frames with `frame_type_a_` again.
+  ScrollJankV4Result result5 = DecideJankForParameterizedFrame(
+      frame_type_a_,
+      {
+          .if_real = Real{.first_input_generation_ts = MillisSinceEpoch(231),
+                          .last_input_generation_ts = MillisSinceEpoch(239),
+                          .has_inertial_input = false,
+                          .abs_total_raw_delta_pixels = 1.0f,
+                          .max_abs_inertial_raw_delta_pixels = 0.0f},
+          .if_synthetic =
+              Synthetic{.first_input_begin_frame_ts = MillisSinceEpoch(244)},
+          .if_synthetic_only =
+              {.future_real_frame_is_fast_scroll_or_sufficiently_fast_fling =
+                   false},
+          .if_damaging =
+              DamagingFrame{.presentation_ts = MillisSinceEpoch(260)},
+          .args = CreateBeginFrameArgs(MillisSinceEpoch(244)),
+      });
+  EXPECT_THAT(result5, kHasNoMissedVsyncs);
+  ScrollJankV4Result result6 = DecideJankForParameterizedFrame(
+      frame_type_a_,
+      {
+          .if_real = Real{.first_input_generation_ts = MillisSinceEpoch(263),
+                          .last_input_generation_ts = MillisSinceEpoch(271),
+                          .has_inertial_input = false,
+                          .abs_total_raw_delta_pixels = 1.0f,
+                          .max_abs_inertial_raw_delta_pixels = 0.0f},
+          .if_synthetic =
+              Synthetic{.first_input_begin_frame_ts = MillisSinceEpoch(276)},
+          .if_synthetic_only =
+              {.future_real_frame_is_fast_scroll_or_sufficiently_fast_fling =
+                   false},
+          .if_damaging =
+              DamagingFrame{.presentation_ts = MillisSinceEpoch(292)},
+          .args = CreateBeginFrameArgs(MillisSinceEpoch(276)),
+      });
+  EXPECT_THAT(result6, kHasNoMissedVsyncs);
 }
 
 /*
 Test that when a frame took too long to be produced shows up in the metric.
-vsync                   v0              v1        v2
-                        |    |    |     |    |    |
-input   I0  I1  I2  I3  I4  I5
-        |   |   |   |   |   |
-F1:     |---------------| {I0, I1}
-F2:             |-----------------------| {I2, I3}
-F3:                     |-------------------------| {I4, I5}
+
+time    100     116     132     148     164     180     196     212     228
+vsync    |       |       |       |       |       |       |       |       |
+input     I0  I1  I2  I3  I4  I5
+          |   |   |   |   |   |
+F1(RD):   |--------------BF------|
+F2(a):            |------------------------------BF------|
+F3(b):                    |--------------------------------------BF------|
  */
-TEST_F(ScrollJankV4DeciderTest, MissedVsyncWhenInputWasPresent) {
-  std::optional<ScrollJankV4Result> result1 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(103),
-          /* last_input_generation_ts= */ MillisSinceEpoch(111),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(148)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(132)),
-          /* has_inertial_input= */ false,
-          /* abs_total_raw_delta_pixels= */ 2.0f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.0f);
+TEST_P(DoublyParameterizedScrollJankV4DeciderTest,
+       MissedVsyncWhenInputWasPresent) {
+  ScrollJankV4Result result1 = decider_.DecideJankForFrameWithRealScrollUpdates(
+      ScrollUpdates(/* earliest_event */ nullptr,
+                    Real{.first_input_generation_ts = MillisSinceEpoch(103),
+                         .last_input_generation_ts = MillisSinceEpoch(111),
+                         .has_inertial_input = false,
+                         .abs_total_raw_delta_pixels = 2.0f,
+                         .max_abs_inertial_raw_delta_pixels = 0.0f},
+                    /* synthetic= */ std::nullopt),
+      DamagingFrame{.presentation_ts = MillisSinceEpoch(148)},
+      CreateBeginFrameArgs(MillisSinceEpoch(132)));
   EXPECT_THAT(result1, kHasNoMissedVsyncs);
 
-  std::optional<ScrollJankV4Result> result2 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(119),
-          /* last_input_generation_ts= */ MillisSinceEpoch(127),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(196)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(180)),
-          /* has_inertial_input= */ false,
-          /* abs_total_raw_delta_pixels= */ 2.0f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.0f);
+  ScrollJankV4Result result2 = DecideJankForParameterizedFrame(
+      frame_type_a_,
+      {
+          .if_real = Real{.first_input_generation_ts = MillisSinceEpoch(119),
+                          .last_input_generation_ts = MillisSinceEpoch(127),
+                          .has_inertial_input = false,
+                          .abs_total_raw_delta_pixels = 2.0f,
+                          .max_abs_inertial_raw_delta_pixels = 0.0f},
+          .if_synthetic =
+              Synthetic{.first_input_begin_frame_ts = MillisSinceEpoch(148)},
+          .if_synthetic_only =
+              {.future_real_frame_is_fast_scroll_or_sufficiently_fast_fling =
+                   false},
+          .if_damaging =
+              DamagingFrame{.presentation_ts = MillisSinceEpoch(196)},
+          .args = CreateBeginFrameArgs(MillisSinceEpoch(180)),
+      });
   EXPECT_THAT(
       result2,
       HasMissedVsyncs(
           JankReason::kMissedVsyncDueToDeceleratingInputFrameDelivery, 2));
 
-  std::optional<ScrollJankV4Result> result3 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(135),
-          /* last_input_generation_ts= */ MillisSinceEpoch(143),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(228)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(212)),
-          /* has_inertial_input= */ false,
-          /* abs_total_raw_delta_pixels= */ 2.0f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.0f);
-  EXPECT_THAT(
-      result3,
-      HasMissedVsyncs(
-          JankReason::kMissedVsyncDueToDeceleratingInputFrameDelivery, 1));
+  ScrollJankV4Result result3 = DecideJankForParameterizedFrame(
+      frame_type_b_,
+      {
+          .if_real = Real{.first_input_generation_ts = MillisSinceEpoch(135),
+                          .last_input_generation_ts = MillisSinceEpoch(143),
+                          .has_inertial_input = false,
+                          .abs_total_raw_delta_pixels = 2.0f,
+                          .max_abs_inertial_raw_delta_pixels = 0.0f},
+          .if_synthetic =
+              Synthetic{.first_input_begin_frame_ts = MillisSinceEpoch(196)},
+          .if_synthetic_only =
+              {.future_real_frame_is_fast_scroll_or_sufficiently_fast_fling =
+                   false},
+          .if_damaging =
+              DamagingFrame{.presentation_ts = MillisSinceEpoch(228)},
+          .args = CreateBeginFrameArgs(MillisSinceEpoch(212)),
+      });
+  if (frame_type_a_.has_real_inputs && frame_type_a_.is_damaging) {
+    EXPECT_THAT(
+        result3,
+        HasMissedVsyncs(
+            JankReason::kMissedVsyncDueToDeceleratingInputFrameDelivery, 1));
+  } else {
+    EXPECT_THAT(result3, kHasNoMissedVsyncs);
+  }
 }
 
 // Regression test for https://crbug.com/404637348.
 TEST_F(ScrollJankV4DeciderTest, ScrollWithZeroVsyncs) {
-  std::optional<ScrollJankV4Result> result1 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(103),
-          /* last_input_generation_ts= */ MillisSinceEpoch(111),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(148)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(132)),
-          /* has_inertial_input= */ false,
-          /* abs_total_raw_delta_pixels= */ 2.0f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.0f);
+  ScrollJankV4Result result1 = decider_.DecideJankForFrameWithRealScrollUpdates(
+      ScrollUpdates(/* earliest_event= */ nullptr,
+                    Real{.first_input_generation_ts = MillisSinceEpoch(103),
+                         .last_input_generation_ts = MillisSinceEpoch(111),
+                         .has_inertial_input = false,
+                         .abs_total_raw_delta_pixels = 2.0f,
+                         .max_abs_inertial_raw_delta_pixels = 0.0f},
+                    /* synthetic= */ std::nullopt),
+      ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(148)}},
+      CreateBeginFrameArgs(MillisSinceEpoch(132)));
   EXPECT_THAT(result1, kHasNoMissedVsyncs);
 
   // A malformed frame whose presentation timestamp is less than half a vsync
   // greater than than the previous frame's presentation timestamp.
-  std::optional<ScrollJankV4Result> result2 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(119),
-          /* last_input_generation_ts= */ MillisSinceEpoch(127),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(149)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(133)),
-          /* has_inertial_input= */ false,
-          /* abs_total_raw_delta_pixels= */ 2.0f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.0f);
+  ScrollJankV4Result result2 = decider_.DecideJankForFrameWithRealScrollUpdates(
+      ScrollUpdates(/* earliest_event= */ nullptr,
+                    Real{.first_input_generation_ts = MillisSinceEpoch(119),
+                         .last_input_generation_ts = MillisSinceEpoch(127),
+                         .has_inertial_input = false,
+                         .abs_total_raw_delta_pixels = 2.0f,
+                         .max_abs_inertial_raw_delta_pixels = 0.0f},
+                    /* synthetic= */ std::nullopt),
+      ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(149)}},
+      CreateBeginFrameArgs(MillisSinceEpoch(133)));
   EXPECT_THAT(result2, kHasNoMissedVsyncs);
 }
 
 /*
-Tests that the decider ignores frames which contain inputs that were generated
-after the frame was presented.
-
-VSync V  V  V  V  V  V  V  V  V  V
-      :  :  :  :  :  :  :  :  :  :
-Input I1    :  I2 I3 :           :
-      :     :  :  :  :           :
-F1:   |-----:--:--:--|           :
-F2:         |<!|  :              :
-F3:               |--------------|
-
-F2 was presented before I2 was generated, which is unexpected, so the decider
-should completely ignore it. It should then evaluate F3 against F1 only.
-*/
-TEST_F(ScrollJankV4DeciderTest, InputGeneratedAfterItWasPresented) {
-  std::optional<ScrollJankV4Result> result1 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(100),
-          /* last_input_generation_ts= */ MillisSinceEpoch(100),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(180)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(164)),
-          /* has_inertial_input= */ false,
-          /* abs_total_raw_delta_pixels= */ 2.0f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.0f);
-  EXPECT_THAT(result1, kHasNoMissedVsyncs);
-
-  // A malformed frame which contains an input that was generated after the
-  // frame was presented. The decider should completely ignore the frame and not
-  // return any result.
-  std::optional<ScrollJankV4Result> result2 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(148),
-          /* last_input_generation_ts= */ MillisSinceEpoch(148),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(132)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(116)),
-          /* has_inertial_input= */ false,
-          /* abs_total_raw_delta_pixels= */ 2.0f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.0f);
-  EXPECT_EQ(result2, std::nullopt);
-
-  // The decider should ignore the malformed frame when assessing subsequent
-  // frames.
-  std::optional<ScrollJankV4Result> result3 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(164),
-          /* last_input_generation_ts= */ MillisSinceEpoch(164),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(244)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(228)),
-          /* has_inertial_input= */ false,
-          /* abs_total_raw_delta_pixels= */ 2.0f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.0f);
-  EXPECT_THAT(result3, kHasNoMissedVsyncs);
-}
-
-/*
-Tests that the decider ignores frames which arrive out of order.
-
-VSync V  V  V  V  V  V  V  V
-      :  :  :  :  :  :  :  :
-Input I1 I2 I3 :     :     :
-      :  :  :  :     :     :
-F1:   |--:--:--:-----|     :
-F2:      |--:--|           :
-F3:         |--------------|
-
-F2 was presented before F1, which is unexpected, so the decider should
-completely ignore it. It should then evaluate F3 against F1 only.
-*/
-TEST_F(ScrollJankV4DeciderTest, OutOfOrderFrameTermination) {
-  std::optional<ScrollJankV4Result> result1 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(100),
-          /* last_input_generation_ts= */ MillisSinceEpoch(100),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(180)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(164)),
-          /* has_inertial_input= */ false,
-          /* abs_total_raw_delta_pixels= */ 2.0f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.0f);
-  EXPECT_THAT(result1, kHasNoMissedVsyncs);
-
-  // A malformed frame whose presentation timestamp before the previous frame.
-  // The decider should completely ignore it and not return any result.
-  std::optional<ScrollJankV4Result> result2 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(116),
-          /* last_input_generation_ts= */ MillisSinceEpoch(116),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(148)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(132)),
-          /* has_inertial_input= */ false,
-          /* abs_total_raw_delta_pixels= */ 2.0f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.0f);
-  EXPECT_EQ(result2, std::nullopt);
-
-  // The decider should ignore the malformed frame when assessing subsequent
-  // frames.
-  std::optional<ScrollJankV4Result> result3 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(132),
-          /* last_input_generation_ts= */ MillisSinceEpoch(132),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(212)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(196)),
-          /* has_inertial_input= */ false,
-          /* abs_total_raw_delta_pixels= */ 2.0f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.0f);
-  EXPECT_THAT(result3, kHasNoMissedVsyncs);
-}
-
-/*
 Tests that the decider evaluates each scroll separately (i.e. doesn't evaluate a
 scroll against a previous scroll).
 
@@ -355,40 +713,43 @@
 */
 TEST_F(ScrollJankV4DeciderTest, EvaluatesEachScrollSeparately) {
   // Scroll 1: First input took only 8 ms (half a VSync) to deliver.
-  std::optional<ScrollJankV4Result> result1 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(108),
-          /* last_input_generation_ts= */ MillisSinceEpoch(108),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(116)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(100)),
-          /* has_inertial_input= */ false,
-          /* abs_total_raw_delta_pixels= */ 4.0f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.0f);
+  ScrollJankV4Result result1 = decider_.DecideJankForFrameWithRealScrollUpdates(
+      ScrollUpdates(/* earliest_event= */ nullptr,
+                    Real{.first_input_generation_ts = MillisSinceEpoch(108),
+                         .last_input_generation_ts = MillisSinceEpoch(108),
+                         .has_inertial_input = false,
+                         .abs_total_raw_delta_pixels = 4.0f,
+                         .max_abs_inertial_raw_delta_pixels = 0.0f},
+                    /* synthetic= */ std::nullopt),
+      ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(116)}},
+      CreateBeginFrameArgs(MillisSinceEpoch(100)));
   EXPECT_THAT(result1, kHasNoMissedVsyncs);
 
   decider_.OnScrollEnded();
   decider_.OnScrollStarted();
 
   // Scroll 2: Inputs 2 and 3 took 40 ms (2.5 VSyncs) to deliver.
-  std::optional<ScrollJankV4Result> result2 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(124),
-          /* last_input_generation_ts= */ MillisSinceEpoch(124),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(164)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(148)),
-          /* has_inertial_input= */ false,
-          /* abs_total_raw_delta_pixels= */ 4.0f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.0f);
+  ScrollJankV4Result result2 = decider_.DecideJankForFrameWithRealScrollUpdates(
+      ScrollUpdates(/* earliest_event= */ nullptr,
+                    Real{.first_input_generation_ts = MillisSinceEpoch(124),
+                         .last_input_generation_ts = MillisSinceEpoch(124),
+                         .has_inertial_input = false,
+                         .abs_total_raw_delta_pixels = 4.0f,
+                         .max_abs_inertial_raw_delta_pixels = 0.0f},
+                    /* synthetic= */ std::nullopt),
+      ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(164)}},
+      CreateBeginFrameArgs(MillisSinceEpoch(148)));
   EXPECT_THAT(result2, kHasNoMissedVsyncs);
-  std::optional<ScrollJankV4Result> result3 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(140),
-          /* last_input_generation_ts= */ MillisSinceEpoch(140),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(180)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(164)),
-          /* has_inertial_input= */ false,
-          /* abs_total_raw_delta_pixels= */ 4.0f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.0f);
+  ScrollJankV4Result result3 = decider_.DecideJankForFrameWithRealScrollUpdates(
+      ScrollUpdates(/* earliest_event= */ nullptr,
+                    Real{.first_input_generation_ts = MillisSinceEpoch(140),
+                         .last_input_generation_ts = MillisSinceEpoch(140),
+                         .has_inertial_input = false,
+                         .abs_total_raw_delta_pixels = 4.0f,
+                         .max_abs_inertial_raw_delta_pixels = 0.0f},
+                    /* synthetic= */ std::nullopt),
+      ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(180)}},
+      CreateBeginFrameArgs(MillisSinceEpoch(164)));
   EXPECT_THAT(result3, kHasNoMissedVsyncs);
 }
 
@@ -398,39 +759,42 @@
 */
 TEST_F(ScrollJankV4DeciderTest, EvaluatesEachScrollSeparatelyScrollStartOnly) {
   // Scroll 1: First input took only 8 ms (half a VSync) to deliver.
-  std::optional<ScrollJankV4Result> result1 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(108),
-          /* last_input_generation_ts= */ MillisSinceEpoch(108),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(116)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(100)),
-          /* has_inertial_input= */ false,
-          /* abs_total_raw_delta_pixels= */ 4.0f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.0f);
+  ScrollJankV4Result result1 = decider_.DecideJankForFrameWithRealScrollUpdates(
+      ScrollUpdates(/* earliest_event= */ nullptr,
+                    Real{.first_input_generation_ts = MillisSinceEpoch(108),
+                         .last_input_generation_ts = MillisSinceEpoch(108),
+                         .has_inertial_input = false,
+                         .abs_total_raw_delta_pixels = 4.0f,
+                         .max_abs_inertial_raw_delta_pixels = 0.0f},
+                    /* synthetic= */ std::nullopt),
+      ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(116)}},
+      CreateBeginFrameArgs(MillisSinceEpoch(100)));
   EXPECT_THAT(result1, kHasNoMissedVsyncs);
 
   decider_.OnScrollStarted();
 
   // Scroll 2: Inputs 2 and 3 took 40 ms (2.5 VSyncs) to deliver.
-  std::optional<ScrollJankV4Result> result2 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(124),
-          /* last_input_generation_ts= */ MillisSinceEpoch(124),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(164)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(148)),
-          /* has_inertial_input= */ false,
-          /* abs_total_raw_delta_pixels= */ 4.0f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.0f);
+  ScrollJankV4Result result2 = decider_.DecideJankForFrameWithRealScrollUpdates(
+      ScrollUpdates(/* earliest_event= */ nullptr,
+                    Real{.first_input_generation_ts = MillisSinceEpoch(124),
+                         .last_input_generation_ts = MillisSinceEpoch(124),
+                         .has_inertial_input = false,
+                         .abs_total_raw_delta_pixels = 4.0f,
+                         .max_abs_inertial_raw_delta_pixels = 0.0f},
+                    /* synthetic= */ std::nullopt),
+      ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(164)}},
+      CreateBeginFrameArgs(MillisSinceEpoch(148)));
   EXPECT_THAT(result2, kHasNoMissedVsyncs);
-  std::optional<ScrollJankV4Result> result3 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(140),
-          /* last_input_generation_ts= */ MillisSinceEpoch(140),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(180)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(164)),
-          /* has_inertial_input= */ false,
-          /* abs_total_raw_delta_pixels= */ 4.0f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.0f);
+  ScrollJankV4Result result3 = decider_.DecideJankForFrameWithRealScrollUpdates(
+      ScrollUpdates(/* earliest_event= */ nullptr,
+                    Real{.first_input_generation_ts = MillisSinceEpoch(140),
+                         .last_input_generation_ts = MillisSinceEpoch(140),
+                         .has_inertial_input = false,
+                         .abs_total_raw_delta_pixels = 4.0f,
+                         .max_abs_inertial_raw_delta_pixels = 0.0f},
+                    /* synthetic= */ std::nullopt),
+      ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(180)}},
+      CreateBeginFrameArgs(MillisSinceEpoch(164)));
   EXPECT_THAT(result3, kHasNoMissedVsyncs);
 }
 
@@ -440,39 +804,42 @@
 */
 TEST_F(ScrollJankV4DeciderTest, EvaluatesEachScrollSeparatelyScrollEndOnly) {
   // Scroll 1: First input took only 8 ms (half a VSync) to deliver.
-  std::optional<ScrollJankV4Result> result1 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(108),
-          /* last_input_generation_ts= */ MillisSinceEpoch(108),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(116)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(100)),
-          /* has_inertial_input= */ false,
-          /* abs_total_raw_delta_pixels= */ 4.0f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.0f);
+  ScrollJankV4Result result1 = decider_.DecideJankForFrameWithRealScrollUpdates(
+      ScrollUpdates(/* earliest_event= */ nullptr,
+                    Real{.first_input_generation_ts = MillisSinceEpoch(108),
+                         .last_input_generation_ts = MillisSinceEpoch(108),
+                         .has_inertial_input = false,
+                         .abs_total_raw_delta_pixels = 4.0f,
+                         .max_abs_inertial_raw_delta_pixels = 0.0f},
+                    /* synthetic= */ std::nullopt),
+      ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(116)}},
+      CreateBeginFrameArgs(MillisSinceEpoch(100)));
   EXPECT_THAT(result1, kHasNoMissedVsyncs);
 
   decider_.OnScrollEnded();
 
   // Scroll 2: Inputs 2 and 3 took 40 ms (2.5 VSyncs) to deliver.
-  std::optional<ScrollJankV4Result> result2 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(124),
-          /* last_input_generation_ts= */ MillisSinceEpoch(124),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(164)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(148)),
-          /* has_inertial_input= */ false,
-          /* abs_total_raw_delta_pixels= */ 4.0f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.0f);
+  ScrollJankV4Result result2 = decider_.DecideJankForFrameWithRealScrollUpdates(
+      ScrollUpdates(/* earliest_event= */ nullptr,
+                    Real{.first_input_generation_ts = MillisSinceEpoch(124),
+                         .last_input_generation_ts = MillisSinceEpoch(124),
+                         .has_inertial_input = false,
+                         .abs_total_raw_delta_pixels = 4.0f,
+                         .max_abs_inertial_raw_delta_pixels = 0.0f},
+                    /* synthetic= */ std::nullopt),
+      ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(164)}},
+      CreateBeginFrameArgs(MillisSinceEpoch(148)));
   EXPECT_THAT(result2, kHasNoMissedVsyncs);
-  std::optional<ScrollJankV4Result> result3 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(140),
-          /* last_input_generation_ts= */ MillisSinceEpoch(140),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(180)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(164)),
-          /* has_inertial_input= */ false,
-          /* abs_total_raw_delta_pixels= */ 4.0f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.0f);
+  ScrollJankV4Result result3 = decider_.DecideJankForFrameWithRealScrollUpdates(
+      ScrollUpdates(/* earliest_event= */ nullptr,
+                    Real{.first_input_generation_ts = MillisSinceEpoch(140),
+                         .last_input_generation_ts = MillisSinceEpoch(140),
+                         .has_inertial_input = false,
+                         .abs_total_raw_delta_pixels = 4.0f,
+                         .max_abs_inertial_raw_delta_pixels = 0.0f},
+                    /* synthetic= */ std::nullopt),
+      ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(180)}},
+      CreateBeginFrameArgs(MillisSinceEpoch(164)));
   EXPECT_THAT(result3, kHasNoMissedVsyncs);
 }
 
@@ -480,49 +847,60 @@
 Tests that the decider doesn't unfairly mark a frame as janky just because
 Chrome "got lucky" (quickly presented an input in a frame) once many frames ago.
 
-VSync V0  :   V1      V2      V3 ... V62     V63     V64  :  V65     V66
-      :   :   :       :       :  ...  :       :       :   :   :       :
-Input :   I1  I2      I3      I4 ... I63     I64      :  I65  :       :
-          :   :       :       :  ...  :       :       :   :           :
-F1:       |8ms|       :       :       :       :       :   :           :
-F2:           |-16ms--|       :       :       :       :   :           :
-F3:                   |-16ms--|       :       :       :   :           :
-F4:                           |--...  :       :       :   :           :
-...                                   :       :       :   :           :
-F62:                             ...--|       :       :   :           :
-F63:                             ...  |-16ms--|       :   :           :
-F64:                             ...          |-16ms--|   :           :
-F65:                                                      |----24ms---|
+VSync     V0  :   V1      V2      V3 ... V62     V63     V64  :  V65     V66
+          :   :   :       :       :  ...  :       :       :   :   :       :
+Input     :   I1  I2      I3      I4 ... I63     I64      :  I65  :       :
+              :   :       :       :  ...  :       :       :   :           :
+F1(RD):       |8ms|       :       :       :       :       :   :           :
+F2(p):            |-16ms--|       :       :       :       :   :           :
+F3(p):                    |-16ms--|       :       :       :   :           :
+F4(p):                            |--...  :       :       :   :           :
+...                                       :       :       :   :           :
+F62(p):                              ...--|       :       :   :           :
+F63(p):                              ...  |-16ms--|       :   :           :
+F64(p):                              ...          |-16ms--|   :           :
+F65(RD):                                                      |----24ms---|
 
 The decider should NOT evaluate I65/F65 against I1/F1 (because it happened a
 long time ago), so the decider should NOT mark F65 as janky.
 */
-TEST_F(ScrollJankV4DeciderTest, MissedVsyncLongAfterQuickInputFrameDelivery) {
+TEST_P(SinglyParameterizedScrollJankV4DeciderTest,
+       MissedVsyncLongAfterQuickInputFrameDelivery) {
   // First input took only 8 ms (half a VSync) to deliver.
-  std::optional<ScrollJankV4Result> result1 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(108),
-          /* last_input_generation_ts= */ MillisSinceEpoch(108),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(116)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(100)),
-          /* has_inertial_input= */ false,
-          /* abs_total_raw_delta_pixels= */ 2.0f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.0f);
+  ScrollJankV4Result result1 = decider_.DecideJankForFrameWithRealScrollUpdates(
+      ScrollUpdates(/* earliest_event= */ nullptr,
+                    Real{.first_input_generation_ts = MillisSinceEpoch(108),
+                         .last_input_generation_ts = MillisSinceEpoch(108),
+                         .has_inertial_input = false,
+                         .abs_total_raw_delta_pixels = 2.0f,
+                         .max_abs_inertial_raw_delta_pixels = 0.0f},
+                    /* synthetic= */ std::nullopt),
+      ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(116)}},
+      CreateBeginFrameArgs(MillisSinceEpoch(100)));
   EXPECT_THAT(result1, kHasNoMissedVsyncs);
 
   // Inputs 2-64 took 16 ms (one VSync) to deliver.
   for (int i = 2; i <= 64; i++) {
     base::TimeDelta offset = (i - 2) * kVsyncInterval;
-    std::optional<ScrollJankV4Result> result =
-        decider_.DecideJankForFrameWithScrollUpdates(
-            /* first_input_generation_ts= */ MillisSinceEpoch(116) + offset,
-            /* last_input_generation_ts= */ MillisSinceEpoch(116) + offset,
-            ScrollDamage{DamagingFrame{.presentation_ts =
-                                           MillisSinceEpoch(132) + offset}},
-            CreateBeginFrameArgs(MillisSinceEpoch(116) + offset),
-            /* has_inertial_input= */ false,
-            /* abs_total_raw_delta_pixels= */ 2.0f,
-            /* max_abs_inertial_raw_delta_pixels= */ 0.0f);
+    ScrollJankV4Result result = DecideJankForParameterizedFrame(
+        GetParam(),
+        {
+            .if_real =
+                Real{
+                    .first_input_generation_ts = MillisSinceEpoch(116) + offset,
+                    .last_input_generation_ts = MillisSinceEpoch(116) + offset,
+                    .has_inertial_input = false,
+                    .abs_total_raw_delta_pixels = 2.0f,
+                    .max_abs_inertial_raw_delta_pixels = 0.0f},
+            .if_synthetic = Synthetic{.first_input_begin_frame_ts =
+                                          MillisSinceEpoch(116) + offset},
+            .if_synthetic_only =
+                {.future_real_frame_is_fast_scroll_or_sufficiently_fast_fling =
+                     false},
+            .if_damaging = DamagingFrame{.presentation_ts =
+                                             MillisSinceEpoch(132) + offset},
+            .args = CreateBeginFrameArgs(MillisSinceEpoch(116) + offset),
+        });
     EXPECT_THAT(result, kHasNoMissedVsyncs);
   }
 
@@ -531,16 +909,19 @@
   // first frame (8 ms). Therefore, it's not reasonable to assume that F65's
   // first input (generated at 1132 ms) could have been included in the missed
   // VSync (presented at 1140 ms), so F65 should NOT be marked as janky.
-  std::optional<ScrollJankV4Result> result65 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(1132),
-          /* last_input_generation_ts= */ MillisSinceEpoch(1132),
+  ScrollJankV4Result result65 =
+      decider_.DecideJankForFrameWithRealScrollUpdates(
+          ScrollUpdates(
+              /* earliest_event= */ nullptr,
+              Real{.first_input_generation_ts = MillisSinceEpoch(1132),
+                   .last_input_generation_ts = MillisSinceEpoch(1132),
+                   .has_inertial_input = false,
+                   .abs_total_raw_delta_pixels = 2.0f,
+                   .max_abs_inertial_raw_delta_pixels = 0.0f},
+              /* synthetic= */ std::nullopt),
           ScrollDamage{
               DamagingFrame{.presentation_ts = MillisSinceEpoch(1156)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(1140)),
-          /* has_inertial_input= */ false,
-          /* abs_total_raw_delta_pixels= */ 2.0f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.0f);
+          CreateBeginFrameArgs(MillisSinceEpoch(1140)));
   EXPECT_THAT(result65, kHasNoMissedVsyncs);
 }
 
@@ -549,52 +930,64 @@
 immediately preceding frame (in which Chrome quickly presented an input in a
 frame).
 
-VSync V0      V1      V2      V3 ... V62     V63  :  V64  :  V65     V66
-      :       :       :       :  ...  :       :   :   :   :   :       :
-Input I1      I2      I3      I4 ... I63      :  I64  :  I65  :       :
-      :       :       :       :  ...  :       :   :   :   :           :
-F1:   |-16ms--|       :       :       :       :   :   :   :           :
-F2:           |-16ms--|       :       :       :   :   :   :           :
-F3:                   |-16ms--|       :       :   :   :   :           :
-F4:                           |--...  :       :   :   :   :           :
-...                                   :       :   :   :   :           :
-F62:                             ...--|       :   :   :   :           :
-F63:                             ...  |-16ms--|   :   :   :           :
-F64:                             ...              |8ms|   :           :
-F65:                                                      |----24ms---|
+VSync    V0      V1      V2      V3 ... V62     V63  :  V64  :  V65     V66
+         :       :       :       :  ...  :       :   :   :   :   :       :
+Input    I1      I2      I3      I4 ... I63      :  I64  :  I65  :       :
+         :       :       :       :  ...  :       :   :   :   :           :
+F1(p):   |-16ms--|       :       :       :       :   :   :   :           :
+F2(p):           |-16ms--|       :       :       :   :   :   :           :
+F3(p):                   |-16ms--|       :       :   :   :   :           :
+F4(p):                           |--...  :       :   :   :   :           :
+...                                      :       :   :   :   :           :
+F62(p):                             ...--|       :   :   :   :           :
+F63(p):                             ...  |-16ms--|   :   :   :           :
+F64(RD):                            ...              |8ms|   :           :
+F65(RD):                                                     |----24ms---|
 
 The decider SHOULD evaluate I65/F65 against I64/F64 (because it just happened),
 so the decider SHOULD mark F65 as janky.
 */
-TEST_F(ScrollJankV4DeciderTest,
+TEST_P(SinglyParameterizedScrollJankV4DeciderTest,
        MissedVsyncImmediatelyAfterQuickInputFrameDelivery) {
   // Inputs 1-63 took 16 ms (one VSync) to deliver.
   for (int i = 1; i <= 63; i++) {
     base::TimeDelta offset = (i - 1) * kVsyncInterval;
-    std::optional<ScrollJankV4Result> result =
-        decider_.DecideJankForFrameWithScrollUpdates(
-            /* first_input_generation_ts= */ MillisSinceEpoch(100) + offset,
-            /* last_input_generation_ts= */ MillisSinceEpoch(100) + offset,
-            ScrollDamage{DamagingFrame{.presentation_ts =
-                                           MillisSinceEpoch(116) + offset}},
-            CreateBeginFrameArgs(MillisSinceEpoch(100) + offset),
-            /* has_inertial_input= */ false,
-            /* abs_total_raw_delta_pixels= */ 2.0f,
-            /* max_abs_inertial_raw_delta_pixels= */ 0.0f);
+    ScrollJankV4Result result = DecideJankForParameterizedFrame(
+        GetParam(),
+        {
+            .if_real =
+                Real{
+                    .first_input_generation_ts = MillisSinceEpoch(100) + offset,
+                    .last_input_generation_ts = MillisSinceEpoch(100) + offset,
+                    .has_inertial_input = false,
+                    .abs_total_raw_delta_pixels = 2.0f,
+                    .max_abs_inertial_raw_delta_pixels = 0.0f},
+            .if_synthetic = Synthetic{.first_input_begin_frame_ts =
+                                          MillisSinceEpoch(100) + offset},
+            .if_synthetic_only =
+                {.future_real_frame_is_fast_scroll_or_sufficiently_fast_fling =
+                     false},
+            .if_damaging = DamagingFrame{.presentation_ts =
+                                             MillisSinceEpoch(116) + offset},
+            .args = CreateBeginFrameArgs(MillisSinceEpoch(100) + offset),
+        });
     EXPECT_THAT(result, kHasNoMissedVsyncs);
   }
 
   // Inputs 64 took only 8 ms (half a VSync) to deliver.
-  std::optional<ScrollJankV4Result> result64 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(1116),
-          /* last_input_generation_ts= */ MillisSinceEpoch(1116),
+  ScrollJankV4Result result64 =
+      decider_.DecideJankForFrameWithRealScrollUpdates(
+          ScrollUpdates(
+              /* earliest_event= */ nullptr,
+              Real{.first_input_generation_ts = MillisSinceEpoch(1116),
+                   .last_input_generation_ts = MillisSinceEpoch(1116),
+                   .has_inertial_input = false,
+                   .abs_total_raw_delta_pixels = 2.0f,
+                   .max_abs_inertial_raw_delta_pixels = 0.0f},
+              /* synthetic= */ std::nullopt),
           ScrollDamage{
               DamagingFrame{.presentation_ts = MillisSinceEpoch(1124)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(1108)),
-          /* has_inertial_input= */ false,
-          /* abs_total_raw_delta_pixels= */ 2.0f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.0f);
+          CreateBeginFrameArgs(MillisSinceEpoch(1108)));
   EXPECT_THAT(result64, kHasNoMissedVsyncs);
 
   // There's one VSync missed between F64 and F65. F65 should be evaluated
@@ -602,16 +995,19 @@
   // the earlier frames (16 ms). Therefore, it's reasonable to assume that F65's
   // first input (generated at 1132 ms) could have been included in the missed
   // VSync (presented at 1140 ms), so F65 SHOULD be marked as janky.
-  std::optional<ScrollJankV4Result> result65 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(1132),
-          /* last_input_generation_ts= */ MillisSinceEpoch(1132),
+  ScrollJankV4Result result65 =
+      decider_.DecideJankForFrameWithRealScrollUpdates(
+          ScrollUpdates(
+              /* earliest_event= */ nullptr,
+              Real{.first_input_generation_ts = MillisSinceEpoch(1132),
+                   .last_input_generation_ts = MillisSinceEpoch(1132),
+                   .has_inertial_input = false,
+                   .abs_total_raw_delta_pixels = 2.0f,
+                   .max_abs_inertial_raw_delta_pixels = 0.0f},
+              /* synthetic= */ std::nullopt),
           ScrollDamage{
               DamagingFrame{.presentation_ts = MillisSinceEpoch(1156)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(1140)),
-          /* has_inertial_input= */ false,
-          /* abs_total_raw_delta_pixels= */ 2.0f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.0f);
+          CreateBeginFrameArgs(MillisSinceEpoch(1140)));
   EXPECT_THAT(
       result65,
       HasMissedVsyncs(
@@ -622,79 +1018,134 @@
 Tests that the decider marks frames which missed one or more VSyncs in the
 middle of a fast scroll as janky (even with sparse inputs).
 
-VSync V V V V V V V V V V V V V V V V V V V V V V V V V V
-      : : : : : : : : : : : : : : : : :   : :           :
-Input I1I2  I3I4          I5        : :   : :           :
-      : :   : :           :         : :   : :           :
-F1:   |-----:-:-----------:---------| :   : :           :
-F2:     |---:-:-----------:-----------|(A): :           :
-F3:         |-:-----------:---------------| :           :
-F4:           |-----------:-----------------|    (B)    :
-F5:                       |-----------------------------|
+VSync    V V V V V V V V V V V V V V V V V V V V V V V V V V
+         : : : : : : : : : : : : : : : : :   : :           :
+Input    I1I2  I3I4          I5        : :   : :           :
+         : :   : :           :         : :   : :           :
+F1(a):   |-----:-:-----------:---------| :   : :           :
+F2(a):     |---:-:-----------:-----------|(A): :           :
+F3(b):         |-:-----------:---------------| :           :
+F4(b):           |-----------:-----------------|    (B)    :
+F5(a):                       |-----------------------------|
 
-Assuming I1-I5 are all above the fast scroll threshold (each have at least
+Assuming I2-I5 are all above the fast scroll threshold (each have at least
 3px absolute scroll delta), the decider should mark F3 and F5 janky with 1 (A)
 and 5 (B) missed VSyncs respectively.
 */
-TEST_F(ScrollJankV4DeciderTest, MissedVsyncDuringFastScroll) {
-  std::optional<ScrollJankV4Result> result1 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(100),
-          /* last_input_generation_ts= */ MillisSinceEpoch(100),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(340)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(324)),
-          /* has_inertial_input= */ false,
-          /* abs_total_raw_delta_pixels= */ 4.0f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.0f);
+TEST_P(DoublyParameterizedScrollJankV4DeciderTest,
+       MissedVsyncDuringFastScroll) {
+  ScrollJankV4Result result1 = DecideJankForParameterizedFrame(
+      frame_type_a_,
+      {
+          .if_real = Real{.first_input_generation_ts = MillisSinceEpoch(100),
+                          .last_input_generation_ts = MillisSinceEpoch(100),
+                          .has_inertial_input = false,
+                          .abs_total_raw_delta_pixels = 2.0f,
+                          .max_abs_inertial_raw_delta_pixels = 0.0f},
+          .if_synthetic =
+              Synthetic{.first_input_begin_frame_ts = MillisSinceEpoch(324)},
+          .if_synthetic_only =
+              {.future_real_frame_is_fast_scroll_or_sufficiently_fast_fling =
+                   true},
+          .if_damaging =
+              DamagingFrame{.presentation_ts = MillisSinceEpoch(340)},
+          .args = CreateBeginFrameArgs(MillisSinceEpoch(324)),
+      });
   EXPECT_THAT(result1, kHasNoMissedVsyncs);
 
-  std::optional<ScrollJankV4Result> result2 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(116),
-          /* last_input_generation_ts= */ MillisSinceEpoch(116),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(356)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(340)),
-          /* has_inertial_input= */ false,
-          /* abs_total_raw_delta_pixels= */ 4.0f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.0f);
+  ScrollJankV4Result result2 = DecideJankForParameterizedFrame(
+      frame_type_a_,
+      {
+          .if_real = Real{.first_input_generation_ts = MillisSinceEpoch(116),
+                          .last_input_generation_ts = MillisSinceEpoch(116),
+                          .has_inertial_input = false,
+                          .abs_total_raw_delta_pixels = 4.0f,
+                          .max_abs_inertial_raw_delta_pixels = 0.0f},
+          .if_synthetic =
+              Synthetic{.first_input_begin_frame_ts = MillisSinceEpoch(340)},
+          .if_synthetic_only =
+              {.future_real_frame_is_fast_scroll_or_sufficiently_fast_fling =
+                   true},
+          .if_damaging =
+              DamagingFrame{.presentation_ts = MillisSinceEpoch(356)},
+          .args = CreateBeginFrameArgs(MillisSinceEpoch(340)),
+      });
   EXPECT_THAT(result2, kHasNoMissedVsyncs);
 
-  // 1 VSync missed between F2 and F3, so F3 should be marked as JANKY.
-  std::optional<ScrollJankV4Result> result3 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(148),
-          /* last_input_generation_ts= */ MillisSinceEpoch(148),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(388)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(372)),
-          /* has_inertial_input= */ false,
-          /* abs_total_raw_delta_pixels= */ 4.0f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.0f);
-  EXPECT_THAT(result3,
-              HasMissedVsyncs(JankReason::kMissedVsyncDuringFastScroll, 1));
+  // 1 VSync missed between F2 and F3, so F3 should be marked as JANKY UNLESS F1
+  // and F2 were both synthetic.
+  ScrollJankV4Result result3 = DecideJankForParameterizedFrame(
+      frame_type_b_,
+      {
+          .if_real = Real{.first_input_generation_ts = MillisSinceEpoch(148),
+                          .last_input_generation_ts = MillisSinceEpoch(148),
+                          .has_inertial_input = false,
+                          .abs_total_raw_delta_pixels = 4.0f,
+                          .max_abs_inertial_raw_delta_pixels = 0.0f},
+          .if_synthetic =
+              Synthetic{.first_input_begin_frame_ts = MillisSinceEpoch(372)},
+          .if_synthetic_only =
+              {.future_real_frame_is_fast_scroll_or_sufficiently_fast_fling =
+                   true},
+          .if_damaging =
+              DamagingFrame{.presentation_ts = MillisSinceEpoch(388)},
+          .args = CreateBeginFrameArgs(MillisSinceEpoch(372)),
+      });
+  if (frame_type_a_.has_real_inputs) {
+    EXPECT_THAT(result3,
+                HasMissedVsyncs(JankReason::kMissedVsyncDuringFastScroll, 1));
+  } else {
+    // If there were no real inputs before F3, then the metric won't consider
+    // the scroll to be fast.
+    EXPECT_THAT(result3, kHasNoMissedVsyncs);
+  }
 
-  std::optional<ScrollJankV4Result> result4 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(164),
-          /* last_input_generation_ts= */ MillisSinceEpoch(164),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(404)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(388)),
-          /* has_inertial_input= */ false,
-          /* abs_total_raw_delta_pixels= */ 4.0f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.0f);
+  ScrollJankV4Result result4 = DecideJankForParameterizedFrame(
+      frame_type_b_,
+      {
+          .if_real = Real{.first_input_generation_ts = MillisSinceEpoch(164),
+                          .last_input_generation_ts = MillisSinceEpoch(164),
+                          .has_inertial_input = false,
+                          .abs_total_raw_delta_pixels = 4.0f,
+                          .max_abs_inertial_raw_delta_pixels = 0.0f},
+          .if_synthetic =
+              Synthetic{.first_input_begin_frame_ts = MillisSinceEpoch(388)},
+          .if_synthetic_only =
+              {.future_real_frame_is_fast_scroll_or_sufficiently_fast_fling =
+                   true},
+          .if_damaging =
+              DamagingFrame{.presentation_ts = MillisSinceEpoch(404)},
+          .args = CreateBeginFrameArgs(MillisSinceEpoch(388)),
+      });
   EXPECT_THAT(result4, kHasNoMissedVsyncs);
 
-  // 5 VSyncs missed between F4 and F5, so F5 should be marked as JANKY.
-  std::optional<ScrollJankV4Result> result5 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(260),
-          /* last_input_generation_ts= */ MillisSinceEpoch(260),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(500)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(484)),
-          /* has_inertial_input= */ false,
-          /* abs_total_raw_delta_pixels= */ 4.0f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.0f);
-  EXPECT_THAT(result5,
-              HasMissedVsyncs(JankReason::kMissedVsyncDuringFastScroll, 5));
+  // 5 VSyncs missed between F4 and F5, so F5 should be marked as JANKY UNLESS
+  // F1-F4 were all synthetic.
+  ScrollJankV4Result result5 = DecideJankForParameterizedFrame(
+      frame_type_a_,
+      {
+          .if_real = Real{.first_input_generation_ts = MillisSinceEpoch(260),
+                          .last_input_generation_ts = MillisSinceEpoch(260),
+                          .has_inertial_input = false,
+                          .abs_total_raw_delta_pixels = 4.0f,
+                          .max_abs_inertial_raw_delta_pixels = 0.0f},
+          .if_synthetic =
+              Synthetic{.first_input_begin_frame_ts = MillisSinceEpoch(484)},
+          .if_synthetic_only =
+              {.future_real_frame_is_fast_scroll_or_sufficiently_fast_fling =
+                   true},
+          .if_damaging =
+              DamagingFrame{.presentation_ts = MillisSinceEpoch(500)},
+          .args = CreateBeginFrameArgs(MillisSinceEpoch(484)),
+      });
+  if (frame_type_a_.has_real_inputs || frame_type_b_.has_real_inputs) {
+    EXPECT_THAT(result5,
+                HasMissedVsyncs(JankReason::kMissedVsyncDuringFastScroll, 5));
+  } else {
+    // If there were no real inputs before F5, then the metric won't consider
+    // the scroll to be fast.
+    EXPECT_THAT(result5, kHasNoMissedVsyncs);
+  }
 }
 
 /*
@@ -702,15 +1153,15 @@
 janky if inputs were sparse and the frames weren't in the middle of a fast
 scroll.
 
-VSync V V V V V V V V V V V V V V V V V V V V V V V V V V
-      : : : : : : : : : : : : : : : : :   : :           :
-Input I1I2  I3I4          I5        : :   : :           :
-      : :   : :           :         : :   : :           :
-F1:   |-----:-:-----------:---------| :   : :           :
-F2:     |---:-:-----------:-----------|(A): :           :
-F3:         |-:-----------:---------------| :           :
-F4:           |-----------:-----------------|    (B)    :
-F5:                       |-----------------------------|
+VSync    V V V V V V V V V V V V V V V V V V V V V V V V V V
+         : : : : : : : : : : : : : : : : :   : :           :
+Input    I1I2  I3I4          I5        : :   : :           :
+         : :   : :           :         : :   : :           :
+F1(a):   |-----:-:-----------:---------| :   : :           :
+F2(a):     |---:-:-----------:-----------|(A): :           :
+F3(b):         |-:-----------:---------------| :           :
+F4(b):           |-----------:-----------------|    (B)    :
+F5(a):                       |-----------------------------|
 
 If I2 or I3 is below the fast scroll threshold (has less than 3px absolute
 scroll delta), the decider should NOT mark F3 as janky even though it missed 1
@@ -718,64 +1169,105 @@
 than 3px absolute scroll delta), the decider should NOT mark F5 as janky even
 though it missed 5 VSyncs (B).
 */
-TEST_F(ScrollJankV4DeciderTest, MissedVsyncOutsideFastScroll) {
-  std::optional<ScrollJankV4Result> result1 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(100),
-          /* last_input_generation_ts= */ MillisSinceEpoch(100),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(340)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(324)),
-          /* has_inertial_input= */ false,
-          /* abs_total_raw_delta_pixels= */ 4.0f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.0f);
+TEST_P(DoublyParameterizedScrollJankV4DeciderTest,
+       MissedVsyncOutsideFastScroll) {
+  ScrollJankV4Result result1 = DecideJankForParameterizedFrame(
+      frame_type_a_,
+      {
+          .if_real = Real{.first_input_generation_ts = MillisSinceEpoch(100),
+                          .last_input_generation_ts = MillisSinceEpoch(100),
+                          .has_inertial_input = false,
+                          .abs_total_raw_delta_pixels = 4.0f,
+                          .max_abs_inertial_raw_delta_pixels = 0.0f},
+          .if_synthetic =
+              Synthetic{.first_input_begin_frame_ts = MillisSinceEpoch(324)},
+          .if_synthetic_only =
+              {.future_real_frame_is_fast_scroll_or_sufficiently_fast_fling =
+                   false},
+          .if_damaging =
+              DamagingFrame{.presentation_ts = MillisSinceEpoch(340)},
+          .args = CreateBeginFrameArgs(MillisSinceEpoch(324)),
+      });
   EXPECT_THAT(result1, kHasNoMissedVsyncs);
 
-  std::optional<ScrollJankV4Result> result2 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(116),
-          /* last_input_generation_ts= */ MillisSinceEpoch(116),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(356)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(340)),
-          /* has_inertial_input= */ false,
-          /* abs_total_raw_delta_pixels= */ 4.0f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.0f);
+  ScrollJankV4Result result2 = DecideJankForParameterizedFrame(
+      frame_type_a_,
+      {
+          .if_real = Real{.first_input_generation_ts = MillisSinceEpoch(116),
+                          .last_input_generation_ts = MillisSinceEpoch(116),
+                          .has_inertial_input = false,
+                          .abs_total_raw_delta_pixels = 2.0f,
+                          .max_abs_inertial_raw_delta_pixels = 0.0f},
+          .if_synthetic =
+              Synthetic{.first_input_begin_frame_ts = MillisSinceEpoch(340)},
+          .if_synthetic_only =
+              {.future_real_frame_is_fast_scroll_or_sufficiently_fast_fling =
+                   false},
+          .if_damaging =
+              DamagingFrame{.presentation_ts = MillisSinceEpoch(356)},
+          .args = CreateBeginFrameArgs(MillisSinceEpoch(340)),
+      });
   EXPECT_THAT(result2, kHasNoMissedVsyncs);
 
-  // 1 VSync missed between F2 and F3, BUT F3 has scroll delta below the fast
+  // 1 VSync missed between F2 and F3, BUT F2 has scroll delta below the fast
   // scroll threshold, so F3 should NOT be marked as janky.
-  std::optional<ScrollJankV4Result> result3 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(148),
-          /* last_input_generation_ts= */ MillisSinceEpoch(148),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(388)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(372)),
-          /* has_inertial_input= */ false,
-          /* abs_total_raw_delta_pixels= */ 2.0f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.0f);
+  ScrollJankV4Result result3 = DecideJankForParameterizedFrame(
+      frame_type_b_,
+      {
+          .if_real = Real{.first_input_generation_ts = MillisSinceEpoch(148),
+                          .last_input_generation_ts = MillisSinceEpoch(148),
+                          .has_inertial_input = false,
+                          .abs_total_raw_delta_pixels = 4.0f,
+                          .max_abs_inertial_raw_delta_pixels = 0.0f},
+          .if_synthetic =
+              Synthetic{.first_input_begin_frame_ts = MillisSinceEpoch(372)},
+          .if_synthetic_only =
+              {.future_real_frame_is_fast_scroll_or_sufficiently_fast_fling =
+                   false},
+          .if_damaging =
+              DamagingFrame{.presentation_ts = MillisSinceEpoch(388)},
+          .args = CreateBeginFrameArgs(MillisSinceEpoch(372)),
+      });
   EXPECT_THAT(result3, kHasNoMissedVsyncs);
 
-  std::optional<ScrollJankV4Result> result4 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(164),
-          /* last_input_generation_ts= */ MillisSinceEpoch(164),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(404)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(388)),
-          /* has_inertial_input= */ false,
-          /* abs_total_raw_delta_pixels= */ 2.0f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.0f);
+  ScrollJankV4Result result4 = DecideJankForParameterizedFrame(
+      frame_type_b_,
+      {
+          .if_real = Real{.first_input_generation_ts = MillisSinceEpoch(164),
+                          .last_input_generation_ts = MillisSinceEpoch(164),
+                          .has_inertial_input = false,
+                          .abs_total_raw_delta_pixels = 4.0f,
+                          .max_abs_inertial_raw_delta_pixels = 0.0f},
+          .if_synthetic =
+              Synthetic{.first_input_begin_frame_ts = MillisSinceEpoch(388)},
+          .if_synthetic_only =
+              {.future_real_frame_is_fast_scroll_or_sufficiently_fast_fling =
+                   false},
+          .if_damaging =
+              DamagingFrame{.presentation_ts = MillisSinceEpoch(404)},
+          .args = CreateBeginFrameArgs(MillisSinceEpoch(388)),
+      });
   EXPECT_THAT(result4, kHasNoMissedVsyncs);
 
-  // 5 VSyncs missed between F4 and F5, BUT F4 has scroll delta below the fast
+  // 5 VSyncs missed between F4 and F5, BUT F5 has scroll delta below the fast
   // scroll threshold, so F5 should NOT be marked as janky.
-  std::optional<ScrollJankV4Result> result5 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(260),
-          /* last_input_generation_ts= */ MillisSinceEpoch(260),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(500)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(484)),
-          /* has_inertial_input= */ false,
-          /* abs_total_raw_delta_pixels= */ 4.0f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.0f);
+  ScrollJankV4Result result5 = DecideJankForParameterizedFrame(
+      frame_type_a_,
+      {
+          .if_real = Real{.first_input_generation_ts = MillisSinceEpoch(260),
+                          .last_input_generation_ts = MillisSinceEpoch(260),
+                          .has_inertial_input = false,
+                          .abs_total_raw_delta_pixels = 2.0f,
+                          .max_abs_inertial_raw_delta_pixels = 0.0f},
+          .if_synthetic =
+              Synthetic{.first_input_begin_frame_ts = MillisSinceEpoch(484)},
+          .if_synthetic_only =
+              {.future_real_frame_is_fast_scroll_or_sufficiently_fast_fling =
+                   false},
+          .if_damaging =
+              DamagingFrame{.presentation_ts = MillisSinceEpoch(500)},
+          .args = CreateBeginFrameArgs(MillisSinceEpoch(484)),
+      });
   EXPECT_THAT(result5, kHasNoMissedVsyncs);
 }
 
@@ -783,126 +1275,171 @@
 Tests that the decider marks frames which missed one or more VSyncs at the
 transition from a fast regular scroll to a fast fling as janky.
 
-VSync V  V  V  V  V  V  V  V  V  V
-      :  :  :  :  :  :  :  :  :  :
-Input I1          I2 :           :
-      :           :  :           :
-F1:   |-----------:--|    (A)    :
-F2:               |--------------|
+VSync    V  V  V  V  V  V  V  V  V  V
+         :  :  :  :  :  :  :  :  :  :
+Input    I1          I2 :           :
+         :           :  :           :
+F1(a):   |-----------:--|    (A)    :
+F2(b):               |--------------|
 
 I1 and I2 are regular and inertial scroll updates respectively. Assuming I1 is
 above the fast scroll threshold (has at least 3 px absolute scroll delta) and I2
 is above the fling threshold (has at least 0.2 px absolute scroll delta), the
 decider should mark F2 as janky with 3 missed VSyncs (A).
 */
-TEST_F(ScrollJankV4DeciderTest,
+TEST_P(FlingTransitionDoublyParameterizedScrollJankV4DeciderTest,
        MissedVsyncAtTransitionFromFastRegularScrollToFastFling) {
-  std::optional<ScrollJankV4Result> result1 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(100),
-          /* last_input_generation_ts= */ MillisSinceEpoch(100),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(180)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(164)),
-          /* has_inertial_input= */ false,
-          /* abs_total_raw_delta_pixels= */ 4.0f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.0f);
+  ScrollJankV4Result result1 = DecideJankForParameterizedFrame(
+      frame_type_a_,
+      {
+          .if_real = Real{.first_input_generation_ts = MillisSinceEpoch(100),
+                          .last_input_generation_ts = MillisSinceEpoch(100),
+                          .has_inertial_input = false,
+                          .abs_total_raw_delta_pixels = 4.0f,
+                          .max_abs_inertial_raw_delta_pixels = 0.0f},
+          .if_synthetic =
+              Synthetic{.first_input_begin_frame_ts = MillisSinceEpoch(164)},
+          .if_synthetic_only =
+              {.future_real_frame_is_fast_scroll_or_sufficiently_fast_fling =
+                   true},
+          .if_damaging =
+              DamagingFrame{.presentation_ts = MillisSinceEpoch(180)},
+          .args = CreateBeginFrameArgs(MillisSinceEpoch(164)),
+      });
   EXPECT_THAT(result1, kHasNoMissedVsyncs);
 
-  // 3 VSync missed between F1 and F2, so F2 should be marked as JANKY.
-  std::optional<ScrollJankV4Result> result2 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(164),
-          /* last_input_generation_ts= */ MillisSinceEpoch(164),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(244)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(228)),
-          /* has_inertial_input= */ true,
-          /* abs_total_raw_delta_pixels= */ 0.5f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.5f);
-  EXPECT_THAT(result2,
-              HasMissedVsyncs(JankReason::kMissedVsyncAtStartOfFling, 3));
+  // 3 VSync missed between F1 and F2, so F2 should be marked as JANKY UNLESS F1
+  // was synthetic.
+  ScrollJankV4Result result2 = DecideJankForParameterizedFrame(
+      frame_type_b_,
+      {
+          .if_real = Real{.first_input_generation_ts = MillisSinceEpoch(164),
+                          .last_input_generation_ts = MillisSinceEpoch(164),
+                          .has_inertial_input = true,
+                          .abs_total_raw_delta_pixels = 0.5f,
+                          .max_abs_inertial_raw_delta_pixels = 0.5f},
+          .if_synthetic =
+              Synthetic{.first_input_begin_frame_ts = MillisSinceEpoch(228)},
+          .if_synthetic_only =
+              {.future_real_frame_is_fast_scroll_or_sufficiently_fast_fling =
+                   true},
+          .if_damaging =
+              DamagingFrame{.presentation_ts = MillisSinceEpoch(244)},
+          .args = CreateBeginFrameArgs(MillisSinceEpoch(228)),
+      });
+  if (frame_type_a_.has_real_inputs) {
+    EXPECT_THAT(result2,
+                HasMissedVsyncs(JankReason::kMissedVsyncAtStartOfFling, 3));
+  } else {
+    // If there were no real inputs before F2, then the metric won't consider
+    // the scroll to be fast.
+    EXPECT_THAT(result2, kHasNoMissedVsyncs);
+  }
 }
 
 /*
 Tests that the decider does NOT mark frames which missed one or more VSyncs at
 the transition from a slow regular scroll to a fling as janky.
 
-VSync V  V  V  V  V  V  V  V  V  V
-      :  :  :  :  :  :  :  :  :  :
-Input I1          I2 :           :
-      :           :  :           :
-F1:   |-----------:--|    (A)    :
-F2:               |--------------|
+VSync    V  V  V  V  V  V  V  V  V  V
+         :  :  :  :  :  :  :  :  :  :
+Input    I1          I2 :           :
+         :           :  :           :
+F1(a):   |-----------:--|    (A)    :
+F2(b):               |--------------|
 
 I1 and I2 are regular and inertial scroll updates respectively. Assuming I1 is
 below the fast scroll threshold (has less than 3 px absolute scroll delta), the
 decider should NOT mark F2 as janky even though it missed 3 VSyncs (A).
 */
-TEST_F(ScrollJankV4DeciderTest,
+TEST_P(FlingTransitionDoublyParameterizedScrollJankV4DeciderTest,
        MissedVsyncAtTransitionFromSlowRegularScrollToFling) {
-  std::optional<ScrollJankV4Result> result1 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(100),
-          /* last_input_generation_ts= */ MillisSinceEpoch(100),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(180)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(164)),
-          /* has_inertial_input= */ false,
-          /* abs_total_raw_delta_pixels= */ 2.0f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.0f);
+  ScrollJankV4Result result1 = DecideJankForParameterizedFrame(
+      frame_type_a_,
+      {
+          .if_real = Real{.first_input_generation_ts = MillisSinceEpoch(100),
+                          .last_input_generation_ts = MillisSinceEpoch(100),
+                          .has_inertial_input = false,
+                          .abs_total_raw_delta_pixels = 2.0f,
+                          .max_abs_inertial_raw_delta_pixels = 0.0f},
+          .if_synthetic =
+              Synthetic{.first_input_begin_frame_ts = MicrosSinceEpoch(164)},
+          .if_synthetic_only =
+              {.future_real_frame_is_fast_scroll_or_sufficiently_fast_fling =
+                   true},
+          .if_damaging =
+              DamagingFrame{.presentation_ts = MillisSinceEpoch(180)},
+          .args = CreateBeginFrameArgs(MillisSinceEpoch(164)),
+      });
   EXPECT_THAT(result1, kHasNoMissedVsyncs);
 
   // 3 VSync missed between F1 and F2, BUT F1 has scroll delta below the fast
   // scroll threshold, so F2 should NOT be marked as janky.
-  std::optional<ScrollJankV4Result> result2 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(164),
-          /* last_input_generation_ts= */ MillisSinceEpoch(164),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(244)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(228)),
-          /* has_inertial_input= */ true,
-          /* abs_total_raw_delta_pixels= */ 0.5f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.5f);
+  ScrollJankV4Result result2 = DecideJankForParameterizedFrame(
+      frame_type_b_,
+      {
+          .if_real = Real{.first_input_generation_ts = MillisSinceEpoch(164),
+                          .last_input_generation_ts = MillisSinceEpoch(164),
+                          .has_inertial_input = true,
+                          .abs_total_raw_delta_pixels = 0.5f,
+                          .max_abs_inertial_raw_delta_pixels = 0.5f},
+          .if_damaging =
+              DamagingFrame{.presentation_ts = MillisSinceEpoch(244)},
+          .args = CreateBeginFrameArgs(MillisSinceEpoch(228)),
+      });
   EXPECT_THAT(result2, kHasNoMissedVsyncs);
 }
 
 /*
-Tests that the decider marks frames which missed one or more VSyncs at the
-transition from a regular scroll to a slow fling as janky.
+Tests that the decider does NOT mark frames which missed one or more VSyncs at
+the transition from a regular scroll to a slow fling as janky.
 
-VSync V  V  V  V  V  V  V  V  V  V
-      :  :  :  :  :  :  :  :  :  :
-Input I1          I2 :           :
-      :           :  :           :
-F1:   |-----------:--|    (A)    :
-F2:               |--------------|
+VSync    V  V  V  V  V  V  V  V  V  V
+         :  :  :  :  :  :  :  :  :  :
+Input    I1          I2 :           :
+         :           :  :           :
+F1(a):   |-----------:--|    (A)    :
+F2(b):               |--------------|
 
 I1 and I2 are regular and inertial scroll updates respectively. Assuming I2 is
 below the fling threshold (has less than 0.2 px absolute scroll delta), the
 decuder should NOT mark F2 as janky even though it missed 3 VSyncs (A).
 */
-TEST_F(ScrollJankV4DeciderTest,
+TEST_P(FlingTransitionDoublyParameterizedScrollJankV4DeciderTest,
        MissedVsyncAtTransitionFromRegularScrollToSlowFling) {
-  std::optional<ScrollJankV4Result> result1 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(100),
-          /* last_input_generation_ts= */ MillisSinceEpoch(100),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(180)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(164)),
-          /* has_inertial_input= */ false,
-          /* abs_total_raw_delta_pixels= */ 4.0f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.0f);
+  ScrollJankV4Result result1 = DecideJankForParameterizedFrame(
+      frame_type_a_,
+      {
+          .if_real = Real{.first_input_generation_ts = MillisSinceEpoch(100),
+                          .last_input_generation_ts = MillisSinceEpoch(100),
+                          .has_inertial_input = false,
+                          .abs_total_raw_delta_pixels = 4.0f,
+                          .max_abs_inertial_raw_delta_pixels = 0.0f},
+          .if_synthetic =
+              Synthetic{.first_input_begin_frame_ts = MillisSinceEpoch(164)},
+          .if_synthetic_only =
+              {.future_real_frame_is_fast_scroll_or_sufficiently_fast_fling =
+                   false},
+          .if_damaging =
+              DamagingFrame{.presentation_ts = MillisSinceEpoch(180)},
+          .args = CreateBeginFrameArgs(MillisSinceEpoch(164)),
+      });
   EXPECT_THAT(result1, kHasNoMissedVsyncs);
 
   // 3 VSync missed between F1 and F2, BUT F2 has scroll delta below the fling
   // threshold, so F2 should NOT be marked as janky.
-  std::optional<ScrollJankV4Result> result2 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(164),
-          /* last_input_generation_ts= */ MillisSinceEpoch(164),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(244)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(228)),
-          /* has_inertial_input= */ true,
-          /* abs_total_raw_delta_pixels= */ 0.1f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.1f);
+  ScrollJankV4Result result2 = DecideJankForParameterizedFrame(
+      frame_type_b_,
+      {
+          .if_real = Real{.first_input_generation_ts = MillisSinceEpoch(164),
+                          .last_input_generation_ts = MillisSinceEpoch(164),
+                          .has_inertial_input = true,
+                          .abs_total_raw_delta_pixels = 0.1f,
+                          .max_abs_inertial_raw_delta_pixels = 0.1f},
+          .if_damaging =
+              DamagingFrame{.presentation_ts = MillisSinceEpoch(244)},
+          .args = CreateBeginFrameArgs(MillisSinceEpoch(228)),
+      });
   EXPECT_THAT(result2, kHasNoMissedVsyncs);
 }
 
@@ -920,29 +1457,40 @@
 I1 and I2 are regular and inertial scroll updates respectively. The decider
 should NOT mark F2 as janky because it didn't miss any VSyncs.
 */
-TEST_F(ScrollJankV4DeciderTest,
+TEST_P(FlingTransitionDoublyParameterizedScrollJankV4DeciderTest,
        NoMissedVsyncAtTransitionFromRegularScrollToFling) {
-  std::optional<ScrollJankV4Result> result1 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(100),
-          /* last_input_generation_ts= */ MillisSinceEpoch(100),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(180)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(164)),
-          /* has_inertial_input= */ false,
-          /* abs_total_raw_delta_pixels= */ 4.0f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.0f);
+  ScrollJankV4Result result1 = DecideJankForParameterizedFrame(
+      frame_type_a_,
+      {
+          .if_real = Real{.first_input_generation_ts = MillisSinceEpoch(100),
+                          .last_input_generation_ts = MillisSinceEpoch(100),
+                          .has_inertial_input = false,
+                          .abs_total_raw_delta_pixels = 4.0f,
+                          .max_abs_inertial_raw_delta_pixels = 0.0f},
+          .if_synthetic =
+              Synthetic{.first_input_begin_frame_ts = MillisSinceEpoch(164)},
+          .if_synthetic_only =
+              {.future_real_frame_is_fast_scroll_or_sufficiently_fast_fling =
+                   true},
+          .if_damaging =
+              DamagingFrame{.presentation_ts = MillisSinceEpoch(180)},
+          .args = CreateBeginFrameArgs(MillisSinceEpoch(164)),
+      });
   EXPECT_THAT(result1, kHasNoMissedVsyncs);
 
-  // 3 VSync missed between F1 and F2, so F2 should be marked as JANKY.
-  std::optional<ScrollJankV4Result> result2 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(116),
-          /* last_input_generation_ts= */ MillisSinceEpoch(116),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(196)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(180)),
-          /* has_inertial_input= */ true,
-          /* abs_total_raw_delta_pixels= */ 0.5f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.5f);
+  // No VSyncs missed between F1 and F2, so F2 should NOT be marked as janky.
+  ScrollJankV4Result result2 = DecideJankForParameterizedFrame(
+      frame_type_b_,
+      {
+          .if_real = Real{.first_input_generation_ts = MillisSinceEpoch(116),
+                          .last_input_generation_ts = MillisSinceEpoch(116),
+                          .has_inertial_input = true,
+                          .abs_total_raw_delta_pixels = 0.5f,
+                          .max_abs_inertial_raw_delta_pixels = 0.5f},
+          .if_damaging =
+              DamagingFrame{.presentation_ts = MillisSinceEpoch(196)},
+          .args = CreateBeginFrameArgs(MillisSinceEpoch(180)),
+      });
   EXPECT_THAT(result2, kHasNoMissedVsyncs);
 }
 
@@ -950,77 +1498,93 @@
 Tests that the decider marks frames which missed one or more VSyncs in the
 middle of a fast fling as janky.
 
-VSync V V V V V V V V V V V V V V V V V V V V V V V V V V
-      : : : : : : : : : : : : : : : : :   : :           :
-Input I1I2  I3I4          I5        : :   : :           :
-      : :   : :           :         : :   : :           :
-F1:   |-----:-:-----------:---------| :   : :           :
-F2:     |---:-:-----------:-----------|(A): :           :
-F3:         |-:-----------:---------------| :           :
-F4:           |-----------:-----------------|    (B)    :
-F5:                       |-----------------------------|
+VSync    V V V V V V V V V V V V V V V V V V V V V V V V V V
+         : : : : : : : : : : : : : : : : :   : :           :
+Input    I1I2  I3I4          I5        : :   : :           :
+         : :   : :           :         : :   : :           :
+F1(a):   |-----:-:-----------:---------| :   : :           :
+F2(a):     |---:-:-----------:-----------|(A): :           :
+F3(b):         |-:-----------:---------------| :           :
+F4(b):           |-----------:-----------------|    (B)    :
+F5(a):                       |-----------------------------|
 
 I1-I5 are all inertial scroll updates. If I3 and I5 are above the fling
 threshold (both have at least 0.2px absolute scroll delta), the decider should
 mark F3 and F5 janky with 1 (A) and 5 (B) missed VSyncs respectively.
 */
-TEST_F(ScrollJankV4DeciderTest, MissedVsyncDuringFastFling) {
-  std::optional<ScrollJankV4Result> result1 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(100),
-          /* last_input_generation_ts= */ MillisSinceEpoch(100),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(340)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(324)),
-          /* has_inertial_input= */ true,
-          /* abs_total_raw_delta_pixels= */ 0.5f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.5f);
+TEST_P(MidFlingDoublyParameterizedScrollJankV4DeciderTest,
+       MissedVsyncDuringFastFling) {
+  ScrollJankV4Result result1 = DecideJankForParameterizedFrame(
+      frame_type_a_,
+      {
+          .if_real = Real{.first_input_generation_ts = MillisSinceEpoch(100),
+                          .last_input_generation_ts = MillisSinceEpoch(100),
+                          .has_inertial_input = true,
+                          .abs_total_raw_delta_pixels = 0.5f,
+                          .max_abs_inertial_raw_delta_pixels = 0.5f},
+          .if_damaging =
+              DamagingFrame{.presentation_ts = MillisSinceEpoch(340)},
+          .args = CreateBeginFrameArgs(MillisSinceEpoch(324)),
+      });
   EXPECT_THAT(result1, kHasNoMissedVsyncs);
 
-  std::optional<ScrollJankV4Result> result2 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(116),
-          /* last_input_generation_ts= */ MillisSinceEpoch(116),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(356)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(340)),
-          /* has_inertial_input= */ true,
-          /* abs_total_raw_delta_pixels= */ 0.5f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.5f);
+  ScrollJankV4Result result2 = DecideJankForParameterizedFrame(
+      frame_type_a_,
+      {
+          .if_real = Real{.first_input_generation_ts = MillisSinceEpoch(116),
+                          .last_input_generation_ts = MillisSinceEpoch(116),
+                          .has_inertial_input = true,
+                          .abs_total_raw_delta_pixels = 0.5f,
+                          .max_abs_inertial_raw_delta_pixels = 0.5f},
+          .if_damaging =
+              DamagingFrame{.presentation_ts = MillisSinceEpoch(356)},
+          .args = CreateBeginFrameArgs(MillisSinceEpoch(340)),
+      });
   EXPECT_THAT(result2, kHasNoMissedVsyncs);
 
   // 1 VSync missed between F2 and F3, so F3 should be marked as JANKY.
-  std::optional<ScrollJankV4Result> result3 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(148),
-          /* last_input_generation_ts= */ MillisSinceEpoch(148),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(388)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(372)),
-          /* has_inertial_input= */ true,
-          /* abs_total_raw_delta_pixels= */ 0.5f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.5f);
+  ScrollJankV4Result result3 = DecideJankForParameterizedFrame(
+      frame_type_b_,
+      {
+          .if_real = Real{.first_input_generation_ts = MillisSinceEpoch(148),
+                          .last_input_generation_ts = MillisSinceEpoch(148),
+                          .has_inertial_input = true,
+                          .abs_total_raw_delta_pixels = 0.5f,
+                          .max_abs_inertial_raw_delta_pixels = 0.5f},
+          .if_damaging =
+              DamagingFrame{.presentation_ts = MillisSinceEpoch(388)},
+          .args = CreateBeginFrameArgs(MillisSinceEpoch(372)),
+      });
   EXPECT_THAT(result3, HasMissedVsyncs(JankReason::kMissedVsyncDuringFling, 1));
 
-  std::optional<ScrollJankV4Result> result4 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(164),
-          /* last_input_generation_ts= */ MillisSinceEpoch(164),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(404)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(388)),
-          /* has_inertial_input= */ true,
-          /* abs_total_raw_delta_pixels= */ 0.1f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.1f);
+  ScrollJankV4Result result4 = DecideJankForParameterizedFrame(
+      frame_type_b_,
+      {
+          .if_real = Real{.first_input_generation_ts = MillisSinceEpoch(164),
+                          .last_input_generation_ts = MillisSinceEpoch(164),
+                          .has_inertial_input = true,
+                          .abs_total_raw_delta_pixels = 0.1f,
+                          .max_abs_inertial_raw_delta_pixels = 0.1f},
+          .if_damaging =
+              DamagingFrame{.presentation_ts = MillisSinceEpoch(404)},
+          .args = CreateBeginFrameArgs(MillisSinceEpoch(388)),
+      });
   EXPECT_THAT(result4, kHasNoMissedVsyncs);
 
   // 5 VSyncs missed between F4 and F5 (EVEN THOUGH F4 has scroll delta below
   // the fling threshold), so F5 should be marked as JANKY.
-  std::optional<ScrollJankV4Result> result5 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(260),
-          /* last_input_generation_ts= */ MillisSinceEpoch(260),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(500)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(484)),
-          /* has_inertial_input= */ true,
-          /* abs_total_raw_delta_pixels= */ 0.5f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.5f);
+  ScrollJankV4Result result5 = DecideJankForParameterizedFrame(
+      frame_type_a_,
+      {
+          .if_real = Real{.first_input_generation_ts = MillisSinceEpoch(260),
+                          .last_input_generation_ts = MillisSinceEpoch(260),
+                          .has_inertial_input = true,
+                          .abs_total_raw_delta_pixels = 0.5f,
+                          .max_abs_inertial_raw_delta_pixels = 0.5f},
+          .if_damaging =
+              DamagingFrame{.presentation_ts = MillisSinceEpoch(500)},
+          .args = CreateBeginFrameArgs(MillisSinceEpoch(484)),
+      });
   EXPECT_THAT(result5, HasMissedVsyncs(JankReason::kMissedVsyncDuringFling, 5));
 }
 
@@ -1028,15 +1592,15 @@
 Tests that the decider does NOT mark frames which missed one or more VSyncs in
 the middle of a slow fling (typically towards the end of a fling) as janky.
 
-VSync V V V V V V V V V V V V V V V V V V V V V V V V V V
-      : : : : : : : : : : : : : : : : :   : :           :
-Input I1I2  I3I4          I5        : :   : :           :
-      : :   : :           :         : :   : :           :
-F1:   |-----:-:-----------:---------| :   : :           :
-F2:     |---:-:-----------:-----------|(A): :           :
-F3:         |-:-----------:---------------| :           :
-F4:           |-----------:-----------------|    (B)    :
-F5:                       |-----------------------------|
+VSync    V V V V V V V V V V V V V V V V V V V V V V V V V V
+         : : : : : : : : : : : : : : : : :   : :           :
+Input    I1I2  I3I4          I5        : :   : :           :
+         : :   : :           :         : :   : :           :
+F1(a):   |-----:-:-----------:---------| :   : :           :
+F2(a):     |---:-:-----------:-----------|(A): :           :
+F3(b):         |-:-----------:---------------| :           :
+F4(b):           |-----------:-----------------|    (B)    :
+F5(a):                       |-----------------------------|
 
 I1-I5 are all inertial scroll updates. If I3 is below the fling threshold (has
 less than 0.2px absolute scroll delta), the decider should NOT mark F3 as janky
@@ -1044,96 +1608,83 @@
 threshold (has less than 0.2px absolute scroll delta), the decider should NOT
 mark F5 as janky even though it missed 5 VSyncs (B).
 */
-TEST_F(ScrollJankV4DeciderTest, MissedVsyncDuringSlowFling) {
-  std::optional<ScrollJankV4Result> result1 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(100),
-          /* last_input_generation_ts= */ MillisSinceEpoch(100),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(300)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(284)),
-          /* has_inertial_input= */ true,
-          /* abs_total_raw_delta_pixels= */ 0.5f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.5f);
+TEST_P(MidFlingDoublyParameterizedScrollJankV4DeciderTest,
+       MissedVsyncDuringSlowFling) {
+  ScrollJankV4Result result1 = DecideJankForParameterizedFrame(
+      frame_type_a_,
+      {
+          .if_real = Real{.first_input_generation_ts = MillisSinceEpoch(100),
+                          .last_input_generation_ts = MillisSinceEpoch(100),
+                          .has_inertial_input = true,
+                          .abs_total_raw_delta_pixels = 0.5f,
+                          .max_abs_inertial_raw_delta_pixels = 0.5f},
+          .if_damaging =
+              DamagingFrame{.presentation_ts = MillisSinceEpoch(300)},
+          .args = CreateBeginFrameArgs(MillisSinceEpoch(284)),
+      });
   EXPECT_THAT(result1, kHasNoMissedVsyncs);
 
-  std::optional<ScrollJankV4Result> result2 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(116),
-          /* last_input_generation_ts= */ MillisSinceEpoch(116),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(316)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(300)),
-          /* has_inertial_input= */ true,
-          /* abs_total_raw_delta_pixels= */ 0.5f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.5f);
+  ScrollJankV4Result result2 = DecideJankForParameterizedFrame(
+      frame_type_a_,
+      {
+          .if_real = Real{.first_input_generation_ts = MillisSinceEpoch(116),
+                          .last_input_generation_ts = MillisSinceEpoch(116),
+                          .has_inertial_input = true,
+                          .abs_total_raw_delta_pixels = 0.5f,
+                          .max_abs_inertial_raw_delta_pixels = 0.5f},
+          .if_damaging =
+              DamagingFrame{.presentation_ts = MillisSinceEpoch(316)},
+          .args = CreateBeginFrameArgs(MillisSinceEpoch(300)),
+      });
   EXPECT_THAT(result2, kHasNoMissedVsyncs);
 
   // 1 VSync missed between F2 and F3, BUT F3 has scroll delta below the fling
   // threshold, so F3 should NOT be marked as janky.
-  std::optional<ScrollJankV4Result> result3 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(148),
-          /* last_input_generation_ts= */ MillisSinceEpoch(148),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(348)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(332)),
-          /* has_inertial_input= */ true,
-          /* abs_total_raw_delta_pixels= */ 0.1f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.1f);
+  ScrollJankV4Result result3 = DecideJankForParameterizedFrame(
+      frame_type_b_,
+      {
+          .if_real = Real{.first_input_generation_ts = MillisSinceEpoch(148),
+                          .last_input_generation_ts = MillisSinceEpoch(148),
+                          .has_inertial_input = true,
+                          .abs_total_raw_delta_pixels = 0.1f,
+                          .max_abs_inertial_raw_delta_pixels = 0.1f},
+          .if_damaging =
+              DamagingFrame{.presentation_ts = MillisSinceEpoch(348)},
+          .args = CreateBeginFrameArgs(MillisSinceEpoch(332)),
+      });
   EXPECT_THAT(result3, kHasNoMissedVsyncs);
 
-  std::optional<ScrollJankV4Result> result4 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(164),
-          /* last_input_generation_ts= */ MillisSinceEpoch(164),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(364)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(348)),
-          /* has_inertial_input= */ true,
-          /* abs_total_raw_delta_pixels= */ 0.1f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.1f);
+  ScrollJankV4Result result4 = DecideJankForParameterizedFrame(
+      frame_type_b_,
+      {
+          .if_real = Real{.first_input_generation_ts = MillisSinceEpoch(164),
+                          .last_input_generation_ts = MillisSinceEpoch(164),
+                          .has_inertial_input = true,
+                          .abs_total_raw_delta_pixels = 0.1f,
+                          .max_abs_inertial_raw_delta_pixels = 0.1f},
+          .if_damaging =
+              DamagingFrame{.presentation_ts = MillisSinceEpoch(364)},
+          .args = CreateBeginFrameArgs(MillisSinceEpoch(348)),
+      });
   EXPECT_THAT(result4, kHasNoMissedVsyncs);
 
   // 5 VSyncs missed between F4 and F5, BUT F5 has scroll delta below the fling
   // threshold, so F5 should NOT be marked as janky.
-  std::optional<ScrollJankV4Result> result5 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(260),
-          /* last_input_generation_ts= */ MillisSinceEpoch(260),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(460)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(444)),
-          /* has_inertial_input= */ true,
-          /* abs_total_raw_delta_pixels= */ 0.1f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.1f);
+  ScrollJankV4Result result5 = DecideJankForParameterizedFrame(
+      frame_type_a_,
+      {
+          .if_real = Real{.first_input_generation_ts = MillisSinceEpoch(260),
+                          .last_input_generation_ts = MillisSinceEpoch(260),
+                          .has_inertial_input = true,
+                          .abs_total_raw_delta_pixels = 0.1f,
+                          .max_abs_inertial_raw_delta_pixels = 0.1f},
+          .if_damaging =
+              DamagingFrame{.presentation_ts = MillisSinceEpoch(460)},
+          .args = CreateBeginFrameArgs(MillisSinceEpoch(444)),
+      });
   EXPECT_THAT(result5, kHasNoMissedVsyncs);
 }
 
-/*
-Tests that the decider doesn't crash when `last_input_generation_ts` <
-`first_input_generation_ts`. Regression test for https://crbug.com/454900155.
-*/
-TEST_F(ScrollJankV4DeciderTest,
-       HandlesIncorrectInputGenerationTimestampOrderingGracefully) {
-  std::optional<ScrollJankV4Result> damaging_result =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(200),
-          /* last_input_generation_ts= */ MillisSinceEpoch(100),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(400)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(300)),
-          /* has_inertial_input= */ false,
-          /* abs_total_raw_delta_pixels= */ 5.0f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.0f);
-  EXPECT_EQ(damaging_result, std::nullopt);
-
-  std::optional<ScrollJankV4Result> non_damaging_result =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(200),
-          /* last_input_generation_ts= */ MillisSinceEpoch(100),
-          ScrollDamage{NonDamagingFrame{}},
-          CreateBeginFrameArgs(MillisSinceEpoch(300)),
-          /* has_inertial_input= */ true,
-          /* abs_total_raw_delta_pixels= */ 0.5f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.5f);
-  EXPECT_EQ(non_damaging_result, std::nullopt);
-}
-
 struct ScrollJankV4DeciderRunningConsistencyTestCase {
   std::string test_name;
   base::TimeTicks input_ts;
@@ -1189,39 +1740,44 @@
   const ScrollJankV4DeciderRunningConsistencyTestCase& params = GetParam();
 
   // F1: 164 - 108.1 = 55.9 ms delivery cutoff.
-  std::optional<ScrollJankV4Result> result1 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(100),
-          /* last_input_generation_ts= */ MicrosSinceEpoch(108100),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(164)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(148)),
-          /* has_inertial_input= */ false,
-          /* abs_total_raw_delta_pixels= */ 0.0f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.0f);
+  ScrollJankV4Result result1 = decider_.DecideJankForFrameWithRealScrollUpdates(
+      ScrollUpdates(
+          /* earliest_event= */ nullptr,
+          Real{.first_input_generation_ts = MillisSinceEpoch(100),
+               .last_input_generation_ts = MicrosSinceEpoch(108100),
+               .has_inertial_input = false,
+               .abs_total_raw_delta_pixels = 0.0f,
+               .max_abs_inertial_raw_delta_pixels = 0.0f},
+          /* synthetic= */ std::nullopt),
+      ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(164)}},
+      CreateBeginFrameArgs(MillisSinceEpoch(148)));
   EXPECT_THAT(result1, kHasNoMissedVsyncs);
 
   // F2: 180 - 124 = 56 ms delivery cutoff.
-  std::optional<ScrollJankV4Result> result2 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(116),
-          /* last_input_generation_ts= */ MillisSinceEpoch(124),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(180)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(164)),
-          /* has_inertial_input= */ false,
-          /* abs_total_raw_delta_pixels= */ 0.0f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.0f);
+  ScrollJankV4Result result2 = decider_.DecideJankForFrameWithRealScrollUpdates(
+      ScrollUpdates(/* earliest_event= */ nullptr,
+                    Real{.first_input_generation_ts = MillisSinceEpoch(116),
+                         .last_input_generation_ts = MillisSinceEpoch(124),
+                         .has_inertial_input = false,
+                         .abs_total_raw_delta_pixels = 0.0f,
+                         .max_abs_inertial_raw_delta_pixels = 0.0f},
+                    /* synthetic= */ std::nullopt),
+      ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(180)}},
+      CreateBeginFrameArgs(MillisSinceEpoch(164)));
   EXPECT_THAT(result2, kHasNoMissedVsyncs);
 
   // F3: 196 - 139.8 = 56.2 ms delivery cutoff
-  std::optional<ScrollJankV4Result> result3 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(132),
-          /* last_input_generation_ts= */ MicrosSinceEpoch(139800),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(196)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(180)),
-          /* has_inertial_input= */ false,
-          /* abs_total_raw_delta_pixels= */ 0.0f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.0f);
+  ScrollJankV4Result result3 = decider_.DecideJankForFrameWithRealScrollUpdates(
+      ScrollUpdates(
+          /* earliest_event= */ nullptr,
+          Real{.first_input_generation_ts = MillisSinceEpoch(132),
+               .last_input_generation_ts = MicrosSinceEpoch(139800),
+               .has_inertial_input = false,
+               .abs_total_raw_delta_pixels = 0.0f,
+               .max_abs_inertial_raw_delta_pixels = 0.0f},
+          /* synthetic= */ std::nullopt),
+      ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(196)}},
+      CreateBeginFrameArgs(MillisSinceEpoch(180)));
   EXPECT_THAT(result3, kHasNoMissedVsyncs);
 
   // 3 VSyncs missed between F3 and F4. Whether the first input in F4 could have
@@ -1254,15 +1810,16 @@
   // For example, if `params.input_ts` (I7's generation timestamp) is 157 ms,
   // then the formula above resolves to floor(2.98) = 2, which means that F4
   // should be marked as JANKY with 2 missed VSyncs.
-  std::optional<ScrollJankV4Result> result4 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ params.input_ts,
-          /* last_input_generation_ts= */ params.input_ts,
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(260)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(244)),
-          /* has_inertial_input= */ false,
-          /* abs_total_raw_delta_pixels= */ 0.0f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.0f);
+  ScrollJankV4Result result4 = decider_.DecideJankForFrameWithRealScrollUpdates(
+      ScrollUpdates(/* earliest_event= */ nullptr,
+                    Real{.first_input_generation_ts = params.input_ts,
+                         .last_input_generation_ts = params.input_ts,
+                         .has_inertial_input = false,
+                         .abs_total_raw_delta_pixels = 0.0f,
+                         .max_abs_inertial_raw_delta_pixels = 0.0f},
+                    /* synthetic= */ std::nullopt),
+      ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(260)}},
+      CreateBeginFrameArgs(MillisSinceEpoch(244)));
   EXPECT_THAT(result4,
               HasMissedVsyncs(
                   JankReason::kMissedVsyncDueToDeceleratingInputFrameDelivery,
@@ -1322,156 +1879,6 @@
     });
 
 /*
-Tests that the decider doesn't mark regular frame production where damaging and
-non-damaging frames are interleaved as janky.
-
-VSync V     V     V     V     V     V     V     V     V
-Input  I0 I1 I2 I3:I4 I5:I6 I7:I8 I9:I10  :     :     :
-        | |   | | : | | : | | : | | : |I11:     :     :
-F1:     |---------BF----|     :     : | | :     :     :
-F2:           |---------BF----|     :     :     :     :
-F3:                 |---------BF-xxx:     :     :     :
-F4:                       |---------BF-xxx:     :     :
-F5:                             |---------BF----|     :
-F6:                                   |---------BF----|
- */
-TEST_F(ScrollJankV4DeciderTest,
-       ConsistentInterleavedDamagingAndNonDamagingFrames) {
-  // 2 damaging frames.
-  std::optional<ScrollJankV4Result> result1 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(103),
-          /* last_input_generation_ts= */ MillisSinceEpoch(111),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(148)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(132)),
-          /* has_inertial_input= */ true,
-          /* abs_total_raw_delta_pixels= */ 10.0f,
-          /* max_abs_inertial_raw_delta_pixels= */ 10.0f);
-  EXPECT_THAT(result1, kHasNoMissedVsyncs);
-  std::optional<ScrollJankV4Result> result2 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(119),
-          /* last_input_generation_ts= */ MillisSinceEpoch(127),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(164)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(148)),
-          /* has_inertial_input= */ true,
-          /* abs_total_raw_delta_pixels= */ 10.0f,
-          /* max_abs_inertial_raw_delta_pixels= */ 10.0f);
-  EXPECT_THAT(result2, kHasNoMissedVsyncs);
-
-  // 2 non-damaging frames.
-  std::optional<ScrollJankV4Result> result3 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(135),
-          /* last_input_generation_ts= */ MillisSinceEpoch(143),
-          ScrollDamage{NonDamagingFrame{}},
-          CreateBeginFrameArgs(MillisSinceEpoch(164)),
-          /* has_inertial_input= */ true,
-          /* abs_total_raw_delta_pixels= */ 10.0f,
-          /* max_abs_inertial_raw_delta_pixels= */ 10.0f);
-  EXPECT_THAT(result3, kHasNoMissedVsyncs);
-  std::optional<ScrollJankV4Result> result4 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(151),
-          /* last_input_generation_ts= */ MillisSinceEpoch(159),
-          ScrollDamage{NonDamagingFrame{}},
-          CreateBeginFrameArgs(MillisSinceEpoch(180)),
-          /* has_inertial_input= */ true,
-          /* abs_total_raw_delta_pixels= */ 10.0f,
-          /* max_abs_inertial_raw_delta_pixels= */ 10.0f);
-  EXPECT_THAT(result4, kHasNoMissedVsyncs);
-
-  // 2 damaging frames.
-  std::optional<ScrollJankV4Result> result5 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(167),
-          /* last_input_generation_ts= */ MillisSinceEpoch(175),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(212)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(196)),
-          /* has_inertial_input= */ true,
-          /* abs_total_raw_delta_pixels= */ 10.0f,
-          /* max_abs_inertial_raw_delta_pixels= */ 10.0f);
-  EXPECT_THAT(result5, kHasNoMissedVsyncs);
-  std::optional<ScrollJankV4Result> result6 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(183),
-          /* last_input_generation_ts= */ MillisSinceEpoch(191),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(228)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(212)),
-          /* has_inertial_input= */ true,
-          /* abs_total_raw_delta_pixels= */ 10.0f,
-          /* max_abs_inertial_raw_delta_pixels= */ 10.0f);
-  EXPECT_THAT(result6, kHasNoMissedVsyncs);
-}
-
-/*
-Tests that the decider can handle a scenario where the scroll starts with
-non-damaging frames.
-
-VSync V     V     V     V     V     V     V     V     V
-Input  I0 I1 I2 I3:I4 I5:I6 I7:     :     :     :     :
-        | |   | | : | | : | | :     :     :     :     :
-F1:     |---------BF-xxx:     :     :     :     :     :
-F2:           |---------BF-xxx:     :     :     :     :
-F3:                 |---------BF----|     :     :     :
-F4:                       |---------BF----------------|
-
-The decider should mark F4 as janky because Chrome should have presented I6 two
-VSyncs earlier.
- */
-TEST_F(ScrollJankV4DeciderTest, ScrollStartsWithNonDamagingFrames) {
-  // 2 non-damaging frames.
-  std::optional<ScrollJankV4Result> result1 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(103),
-          /* last_input_generation_ts= */ MillisSinceEpoch(111),
-          ScrollDamage{NonDamagingFrame{}},
-          CreateBeginFrameArgs(MillisSinceEpoch(132)),
-          /* has_inertial_input= */ false,
-          /* abs_total_raw_delta_pixels= */ 2.0f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.0f);
-  EXPECT_THAT(result1, kHasNoMissedVsyncs);
-  std::optional<ScrollJankV4Result> result2 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(119),
-          /* last_input_generation_ts= */ MillisSinceEpoch(127),
-          ScrollDamage{NonDamagingFrame{}},
-          CreateBeginFrameArgs(MillisSinceEpoch(148)),
-          /* has_inertial_input= */ false,
-          /* abs_total_raw_delta_pixels= */ 2.0f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.0f);
-  EXPECT_THAT(result2, kHasNoMissedVsyncs);
-
-  // Non-janky damaging frame.
-  std::optional<ScrollJankV4Result> result3 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(135),
-          /* last_input_generation_ts= */ MillisSinceEpoch(143),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(180)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(164)),
-          /* has_inertial_input= */ false,
-          /* abs_total_raw_delta_pixels= */ 2.0f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.0f);
-  EXPECT_THAT(result3, kHasNoMissedVsyncs);
-
-  // Janky damaging frame (we would have expected it to be presented two VSyncs
-  // earlier at 196 ms rather than 228 ms).
-  std::optional<ScrollJankV4Result> result4 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(151),
-          /* last_input_generation_ts= */ MillisSinceEpoch(159),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(228)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(180)),
-          /* has_inertial_input= */ false,
-          /* abs_total_raw_delta_pixels= */ 2.0f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.0f);
-  EXPECT_THAT(
-      result4,
-      HasMissedVsyncs(
-          JankReason::kMissedVsyncDueToDeceleratingInputFrameDelivery, 2));
-}
-
-/*
 Tests that the decider can handle a scenario where the scroll starts with
 non-damaging frames.
 
@@ -1494,71 +1901,77 @@
 2 missed VSyncs.
  */
 TEST_F(ScrollJankV4DeciderTest, JankyNonDamagingFrames) {
-  std::optional<ScrollJankV4Result> result1 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(103),
-          /* last_input_generation_ts= */ MillisSinceEpoch(111),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(148)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(132)),
-          /* has_inertial_input= */ false,
-          /* abs_total_raw_delta_pixels= */ 5.0f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.0f);
+  ScrollJankV4Result result1 = decider_.DecideJankForFrameWithRealScrollUpdates(
+      ScrollUpdates(/* earliest_event= */ nullptr,
+                    Real{.first_input_generation_ts = MillisSinceEpoch(103),
+                         .last_input_generation_ts = MillisSinceEpoch(111),
+                         .has_inertial_input = false,
+                         .abs_total_raw_delta_pixels = 5.0f,
+                         .max_abs_inertial_raw_delta_pixels = 0.0f},
+                    /* synthetic= */ std::nullopt),
+      ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(148)}},
+      CreateBeginFrameArgs(MillisSinceEpoch(132)));
   EXPECT_THAT(result1, kHasNoMissedVsyncs);
 
-  std::optional<ScrollJankV4Result> result2 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(119),
-          /* last_input_generation_ts= */ MillisSinceEpoch(127),
-          ScrollDamage{NonDamagingFrame{}},
-          CreateBeginFrameArgs(MillisSinceEpoch(148)),
-          /* has_inertial_input= */ false,
-          /* abs_total_raw_delta_pixels= */ 5.0f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.0f);
+  ScrollJankV4Result result2 = decider_.DecideJankForFrameWithRealScrollUpdates(
+      ScrollUpdates(/* earliest_event= */ nullptr,
+                    Real{.first_input_generation_ts = MillisSinceEpoch(119),
+                         .last_input_generation_ts = MillisSinceEpoch(127),
+                         .has_inertial_input = false,
+                         .abs_total_raw_delta_pixels = 5.0f,
+                         .max_abs_inertial_raw_delta_pixels = 0.0f},
+                    /* synthetic= */ std::nullopt),
+      ScrollDamage{NonDamagingFrame{}},
+      CreateBeginFrameArgs(MillisSinceEpoch(148)));
   EXPECT_THAT(result2, kHasNoMissedVsyncs);
 
-  std::optional<ScrollJankV4Result> result3 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(151),
-          /* last_input_generation_ts= */ MillisSinceEpoch(159),
-          ScrollDamage{NonDamagingFrame{}},
-          CreateBeginFrameArgs(MillisSinceEpoch(180)),
-          /* has_inertial_input= */ false,
-          /* abs_total_raw_delta_pixels= */ 5.0f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.0f);
+  ScrollJankV4Result result3 = decider_.DecideJankForFrameWithRealScrollUpdates(
+      ScrollUpdates(/* earliest_event= */ nullptr,
+                    Real{.first_input_generation_ts = MillisSinceEpoch(151),
+                         .last_input_generation_ts = MillisSinceEpoch(159),
+                         .has_inertial_input = false,
+                         .abs_total_raw_delta_pixels = 5.0f,
+                         .max_abs_inertial_raw_delta_pixels = 0.0f},
+                    /* synthetic= */ std::nullopt),
+      ScrollDamage{NonDamagingFrame{}},
+      CreateBeginFrameArgs(MillisSinceEpoch(180)));
   EXPECT_THAT(result3,
               HasMissedVsyncs(JankReason::kMissedVsyncDuringFastScroll, 1));
 
-  std::optional<ScrollJankV4Result> result4 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(196),
-          /* last_input_generation_ts= */ MillisSinceEpoch(196),
-          ScrollDamage{NonDamagingFrame{}},
-          CreateBeginFrameArgs(MillisSinceEpoch(196)),
-          /* has_inertial_input= */ true,
-          /* abs_total_raw_delta_pixels= */ 2.0f,
-          /* max_abs_inertial_raw_delta_pixels= */ 2.0f);
+  ScrollJankV4Result result4 = decider_.DecideJankForFrameWithRealScrollUpdates(
+      ScrollUpdates(/* earliest_event= */ nullptr,
+                    Real{.first_input_generation_ts = MillisSinceEpoch(196),
+                         .last_input_generation_ts = MillisSinceEpoch(196),
+                         .has_inertial_input = true,
+                         .abs_total_raw_delta_pixels = 2.0f,
+                         .max_abs_inertial_raw_delta_pixels = 2.0f},
+                    /* synthetic= */ std::nullopt),
+      ScrollDamage{NonDamagingFrame{}},
+      CreateBeginFrameArgs(MillisSinceEpoch(196)));
   EXPECT_THAT(result4, kHasNoMissedVsyncs);
 
-  std::optional<ScrollJankV4Result> result5 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(244),
-          /* last_input_generation_ts= */ MillisSinceEpoch(244),
-          ScrollDamage{NonDamagingFrame{}},
-          CreateBeginFrameArgs(MillisSinceEpoch(244)),
-          /* has_inertial_input= */ true,
-          /* abs_total_raw_delta_pixels= */ 2.0f,
-          /* max_abs_inertial_raw_delta_pixels= */ 2.0f);
+  ScrollJankV4Result result5 = decider_.DecideJankForFrameWithRealScrollUpdates(
+      ScrollUpdates(/* earliest_event= */ nullptr,
+                    Real{.first_input_generation_ts = MillisSinceEpoch(244),
+                         .last_input_generation_ts = MillisSinceEpoch(244),
+                         .has_inertial_input = true,
+                         .abs_total_raw_delta_pixels = 2.0f,
+                         .max_abs_inertial_raw_delta_pixels = 2.0f},
+                    /* synthetic= */ std::nullopt),
+      ScrollDamage{NonDamagingFrame{}},
+      CreateBeginFrameArgs(MillisSinceEpoch(244)));
   EXPECT_THAT(result5, HasMissedVsyncs(JankReason::kMissedVsyncDuringFling, 2));
 
-  std::optional<ScrollJankV4Result> result6 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(260),
-          /* last_input_generation_ts= */ MillisSinceEpoch(260),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(292)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(260)),
-          /* has_inertial_input= */ true,
-          /* abs_total_raw_delta_pixels= */ 2.0f,
-          /* max_abs_inertial_raw_delta_pixels= */ 2.0f);
+  ScrollJankV4Result result6 = decider_.DecideJankForFrameWithRealScrollUpdates(
+      ScrollUpdates(/* earliest_event= */ nullptr,
+                    Real{.first_input_generation_ts = MillisSinceEpoch(260),
+                         .last_input_generation_ts = MillisSinceEpoch(260),
+                         .has_inertial_input = true,
+                         .abs_total_raw_delta_pixels = 2.0f,
+                         .max_abs_inertial_raw_delta_pixels = 2.0f},
+                    /* synthetic= */ std::nullopt),
+      ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(292)}},
+      CreateBeginFrameArgs(MillisSinceEpoch(260)));
   EXPECT_THAT(result6, kHasNoMissedVsyncs);
 }
 
@@ -1582,66 +1995,284 @@
  */
 TEST_F(ScrollJankV4DeciderTest,
        JankyNonDamagingFramesViolatingRunningConsistency) {
-  std::optional<ScrollJankV4Result> result1 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(103),
-          /* last_input_generation_ts= */ MillisSinceEpoch(111),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(148)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(132)),
-          /* has_inertial_input= */ false,
-          /* abs_total_raw_delta_pixels= */ 0.1f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.0f);
+  ScrollJankV4Result result1 = decider_.DecideJankForFrameWithRealScrollUpdates(
+      ScrollUpdates(/* earliest_event= */ nullptr,
+                    Real{.first_input_generation_ts = MillisSinceEpoch(103),
+                         .last_input_generation_ts = MillisSinceEpoch(111),
+                         .has_inertial_input = false,
+                         .abs_total_raw_delta_pixels = 0.1f,
+                         .max_abs_inertial_raw_delta_pixels = 0.0f},
+                    /* synthetic= */ std::nullopt),
+      ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(148)}},
+      CreateBeginFrameArgs(MillisSinceEpoch(132)));
   EXPECT_THAT(result1, kHasNoMissedVsyncs);
 
-  std::optional<ScrollJankV4Result> result2 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(119),
-          /* last_input_generation_ts= */ MillisSinceEpoch(127),
-          ScrollDamage{NonDamagingFrame{}},
-          CreateBeginFrameArgs(MillisSinceEpoch(164)),
-          /* has_inertial_input= */ false,
-          /* abs_total_raw_delta_pixels= */ 0.1f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.0f);
+  ScrollJankV4Result result2 = decider_.DecideJankForFrameWithRealScrollUpdates(
+      ScrollUpdates(/* earliest_event= */ nullptr,
+                    Real{.first_input_generation_ts = MillisSinceEpoch(119),
+                         .last_input_generation_ts = MillisSinceEpoch(127),
+                         .has_inertial_input = false,
+                         .abs_total_raw_delta_pixels = 0.1f,
+                         .max_abs_inertial_raw_delta_pixels = 0.0f},
+                    /* synthetic= */ std::nullopt),
+      ScrollDamage{NonDamagingFrame{}},
+      CreateBeginFrameArgs(MillisSinceEpoch(164)));
   EXPECT_THAT(
       result2,
       HasMissedVsyncs(
           JankReason::kMissedVsyncDueToDeceleratingInputFrameDelivery, 1));
 
-  std::optional<ScrollJankV4Result> result3 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(151),
-          /* last_input_generation_ts= */ MillisSinceEpoch(159),
-          ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(196)}},
-          CreateBeginFrameArgs(MillisSinceEpoch(180)),
-          /* has_inertial_input= */ true,
-          /* abs_total_raw_delta_pixels= */ 0.1f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.0f);
+  ScrollJankV4Result result3 = decider_.DecideJankForFrameWithRealScrollUpdates(
+      ScrollUpdates(/* earliest_event= */ nullptr,
+                    Real{.first_input_generation_ts = MillisSinceEpoch(151),
+                         .last_input_generation_ts = MillisSinceEpoch(159),
+                         .has_inertial_input = true,
+                         .abs_total_raw_delta_pixels = 0.1f,
+                         .max_abs_inertial_raw_delta_pixels = 0.0f},
+                    /* synthetic= */ std::nullopt),
+      ScrollDamage{DamagingFrame{.presentation_ts = MillisSinceEpoch(196)}},
+      CreateBeginFrameArgs(MillisSinceEpoch(180)));
   EXPECT_THAT(result3, kHasNoMissedVsyncs);
 
-  std::optional<ScrollJankV4Result> result4 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(167),
-          /* last_input_generation_ts= */ MillisSinceEpoch(175),
-          ScrollDamage{NonDamagingFrame{}},
-          CreateBeginFrameArgs(MillisSinceEpoch(196)),
-          /* has_inertial_input= */ false,
-          /* abs_total_raw_delta_pixels= */ 0.1f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.0f);
+  ScrollJankV4Result result4 = decider_.DecideJankForFrameWithRealScrollUpdates(
+      ScrollUpdates(/* earliest_event= */ nullptr,
+                    Real{.first_input_generation_ts = MillisSinceEpoch(167),
+                         .last_input_generation_ts = MillisSinceEpoch(175),
+                         .has_inertial_input = false,
+                         .abs_total_raw_delta_pixels = 0.1f,
+                         .max_abs_inertial_raw_delta_pixels = 0.0f},
+                    /* synthetic= */ std::nullopt),
+      ScrollDamage{NonDamagingFrame{}},
+      CreateBeginFrameArgs(MillisSinceEpoch(196)));
   EXPECT_THAT(result4, kHasNoMissedVsyncs);
 
-  std::optional<ScrollJankV4Result> result5 =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          /* first_input_generation_ts= */ MillisSinceEpoch(183),
-          /* last_input_generation_ts= */ MillisSinceEpoch(191),
-          ScrollDamage{NonDamagingFrame{}},
-          CreateBeginFrameArgs(MillisSinceEpoch(244)),
-          /* has_inertial_input= */ false,
-          /* abs_total_raw_delta_pixels= */ 0.1f,
-          /* max_abs_inertial_raw_delta_pixels= */ 0.0f);
+  ScrollJankV4Result result5 = decider_.DecideJankForFrameWithRealScrollUpdates(
+      ScrollUpdates(/* earliest_event= */ nullptr,
+                    Real{.first_input_generation_ts = MillisSinceEpoch(183),
+                         .last_input_generation_ts = MillisSinceEpoch(191),
+                         .has_inertial_input = false,
+                         .abs_total_raw_delta_pixels = 0.1f,
+                         .max_abs_inertial_raw_delta_pixels = 0.0f},
+                    /* synthetic= */ std::nullopt),
+      ScrollDamage{NonDamagingFrame{}},
+      CreateBeginFrameArgs(MillisSinceEpoch(244)));
   EXPECT_THAT(
       result5,
       HasMissedVsyncs(
           JankReason::kMissedVsyncDueToDeceleratingInputFrameDelivery, 2));
 }
 
+/*
+Tests a scenario where a frame that contains both real and synthetic scroll
+updates is marked as janky because the real scroll updates violate the running
+consistency rule.
+
+time  100     116     132     148     164
+vsync  |       |       |       |       |
+input   I0  I1  I2  I3         S1
+        |   |   |   |          :
+F1:     |------BF------|       :
+F2:             |--------------BF------|
+                               <-jank-->
+
+F2 contains real inputs I2 and I3 generated at 119 ms and 127 ms. It also
+contains a synthetic input S1 that was predicted at 148 ms. The decider should
+mark F2 as janky with 1 missed VSync because Chrome should have presented the
+real inputs 1 VSync earlier.
+ */
+TEST_F(ScrollJankV4DeciderTest,
+       BothRealAndSyntheticFrameJankyDueToRealScrollUpdates) {
+  ScrollJankV4Result result1 = decider_.DecideJankForFrameWithRealScrollUpdates(
+      ScrollUpdates(/* earliest_event= */ nullptr,
+                    Real{.first_input_generation_ts = MillisSinceEpoch(103),
+                         .last_input_generation_ts = MillisSinceEpoch(111),
+                         .has_inertial_input = false,
+                         .abs_total_raw_delta_pixels = 2.0f,
+                         .max_abs_inertial_raw_delta_pixels = 0.0f},
+                    /* synthetic= */ std::nullopt),
+      DamagingFrame{.presentation_ts = MillisSinceEpoch(132)},
+      CreateBeginFrameArgs(MillisSinceEpoch(116)));
+  EXPECT_THAT(result1, kHasNoMissedVsyncs);
+
+  ScrollJankV4Result result2 = decider_.DecideJankForFrameWithRealScrollUpdates(
+      ScrollUpdates(
+          /* earliest_event= */ nullptr,
+          Real{.first_input_generation_ts = MillisSinceEpoch(119),
+               .last_input_generation_ts = MillisSinceEpoch(127),
+               .has_inertial_input = false,
+               .abs_total_raw_delta_pixels = 2.0f,
+               .max_abs_inertial_raw_delta_pixels = 0.0f},
+          Synthetic{.first_input_begin_frame_ts = MillisSinceEpoch(148)}),
+      DamagingFrame{.presentation_ts = MillisSinceEpoch(164)},
+      CreateBeginFrameArgs(MillisSinceEpoch(148)));
+  EXPECT_THAT(
+      result2,
+      HasMissedVsyncs(
+          JankReason::kMissedVsyncDueToDeceleratingInputFrameDelivery, 1));
+}
+
+/*
+Tests a scenario where a frame that contains both real and synthetic scroll
+updates is marked as janky because the real scroll updates violate the running
+consistency rule.
+
+time  100     116     132     148     164
+vsync  |       |       |       |       |
+input   I0  I1         S1   I3
+        |   |          |I2  |
+        |   |          ||   |
+F1:     |------BF-------|   |
+F2:                    |-------BF------|
+                               <-jank-->
+
+F2 contains real inputs I2 and I3 generated at 135 ms and 143 ms. It also
+contains a synthetic input that was predicted at 132 ms. The metric extrapolates
+the synthetic input's generation timestamp to 127 ms (assuming the same duration
+between input generation and begin frame timestamps as the previous real input
+I1). Based on past performance, Chrome should have been able to present the
+synthetic input at 148 ms. The decider should therefore mark F2 as janky with 1
+missed VSync.
+ */
+TEST_F(ScrollJankV4DeciderTest,
+       BothRealAndSyntheticFrameJankyDueToRealSyntheticUpdates) {
+  ScrollJankV4Result result1 = decider_.DecideJankForFrameWithRealScrollUpdates(
+      ScrollUpdates(/* earliest_event= */ nullptr,
+                    Real{.first_input_generation_ts = MillisSinceEpoch(103),
+                         .last_input_generation_ts = MillisSinceEpoch(111),
+                         .has_inertial_input = false,
+                         .abs_total_raw_delta_pixels = 2.0f,
+                         .max_abs_inertial_raw_delta_pixels = 0.0f},
+                    /* synthetic= */ std::nullopt),
+      DamagingFrame{.presentation_ts = MillisSinceEpoch(132)},
+      CreateBeginFrameArgs(MillisSinceEpoch(116)));
+  EXPECT_THAT(result1, kHasNoMissedVsyncs);
+
+  ScrollJankV4Result result2 = decider_.DecideJankForFrameWithRealScrollUpdates(
+      ScrollUpdates(
+          /* earliest_event= */ nullptr,
+          Real{.first_input_generation_ts = MillisSinceEpoch(135),
+               .last_input_generation_ts = MillisSinceEpoch(143),
+               .has_inertial_input = false,
+               .abs_total_raw_delta_pixels = 2.0f,
+               .max_abs_inertial_raw_delta_pixels = 0.0f},
+          Synthetic{.first_input_begin_frame_ts = MillisSinceEpoch(132)}),
+      DamagingFrame{.presentation_ts = MillisSinceEpoch(164)},
+      CreateBeginFrameArgs(MillisSinceEpoch(148)));
+  EXPECT_THAT(
+      result2,
+      HasMissedVsyncs(
+          JankReason::kMissedVsyncDueToDeceleratingInputFrameDelivery, 1));
+}
+
+TEST_F(ScrollJankV4DeciderTest, IsValidFrame) {
+  EXPECT_TRUE(ScrollJankV4Decider::IsValidFrame(
+      ScrollUpdates(
+          /* earliest_event= */ nullptr,
+          Real{
+              .first_input_generation_ts = MillisSinceEpoch(80),
+              .last_input_generation_ts = MillisSinceEpoch(80),
+          },
+          Synthetic{.first_input_begin_frame_ts = MillisSinceEpoch(80)}),
+      DamagingFrame{.presentation_ts = MillisSinceEpoch(100)},
+      CreateBeginFrameArgs(MillisSinceEpoch(90))));
+}
+
+TEST_F(ScrollJankV4DeciderTest,
+       IsNotValidFrameWithArgsFrameTimeAfterPresentation) {
+  // Violates `args.frame_time < presentation_ts`.
+  EXPECT_FALSE(ScrollJankV4Decider::IsValidFrame(
+      ScrollUpdates(
+          /* earliest_event= */ nullptr,
+          Real{
+              .first_input_generation_ts = MillisSinceEpoch(80),
+              .last_input_generation_ts = MillisSinceEpoch(80),
+          },
+          Synthetic{.first_input_begin_frame_ts = MillisSinceEpoch(80)}),
+      DamagingFrame{.presentation_ts = MillisSinceEpoch(100)},
+      CreateBeginFrameArgs(MillisSinceEpoch(101))));
+}
+
+TEST_F(ScrollJankV4DeciderTest,
+       IsNotValidFrameWithInputGenerationAfterPresentation) {
+  // Real update violates `last_input_generation_ts < presentation_ts`.
+  EXPECT_FALSE(ScrollJankV4Decider::IsValidFrame(
+      ScrollUpdates(
+          /* earliest_event= */ nullptr,
+          Real{
+              .first_input_generation_ts = MillisSinceEpoch(80),
+              .last_input_generation_ts = MillisSinceEpoch(101),
+          },
+          Synthetic{.first_input_begin_frame_ts = MillisSinceEpoch(80)}),
+      DamagingFrame{.presentation_ts = MillisSinceEpoch(100)},
+      CreateBeginFrameArgs(MillisSinceEpoch(90))));
+}
+
+TEST_F(ScrollJankV4DeciderTest,
+       IsNotValidFrameWithInputBeginFrameAfterPresentation) {
+  // Synthetic update violates `first_input_begin_frame_ts < presentation_ts`.
+  EXPECT_FALSE(ScrollJankV4Decider::IsValidFrame(
+      ScrollUpdates(/* earliest_event= */ nullptr,
+                    Real{
+                        .first_input_generation_ts = MillisSinceEpoch(80),
+                        .last_input_generation_ts = MillisSinceEpoch(80),
+                    },
+                    Synthetic{
+                        .first_input_begin_frame_ts = MillisSinceEpoch(101),
+                    }),
+      DamagingFrame{.presentation_ts = MillisSinceEpoch(100)},
+      CreateBeginFrameArgs(MillisSinceEpoch(90))));
+}
+
+TEST_F(ScrollJankV4DeciderTest, IsNotValidFrameWithFirstInputAfterLastInput) {
+  // Real update violates
+  // `first_input_generation_ts <= last_input_generation_ts`.
+  EXPECT_FALSE(ScrollJankV4Decider::IsValidFrame(
+      ScrollUpdates(
+          /* earliest_event= */ nullptr,
+          Real{
+              .first_input_generation_ts = MillisSinceEpoch(81),
+              .last_input_generation_ts = MillisSinceEpoch(80),
+          },
+          Synthetic{.first_input_begin_frame_ts = MillisSinceEpoch(80)}),
+      DamagingFrame{.presentation_ts = MillisSinceEpoch(100)},
+      CreateBeginFrameArgs(MillisSinceEpoch(90))));
+}
+
+TEST_F(ScrollJankV4DeciderTest,
+       IsNotValidFrameWithFirstInputBeginFrameAfterArgsFrameTime) {
+  // Synthetic update violates `first_input_begin_frame_ts <= args.frame_time`.
+  EXPECT_FALSE(ScrollJankV4Decider::IsValidFrame(
+      ScrollUpdates(/* earliest_event= */ nullptr,
+                    Real{
+                        .first_input_generation_ts = MillisSinceEpoch(80),
+                        .last_input_generation_ts = MillisSinceEpoch(80),
+                    },
+                    Synthetic{
+                        .first_input_begin_frame_ts = MillisSinceEpoch(91),
+                    }),
+      DamagingFrame{.presentation_ts = MillisSinceEpoch(100)},
+      CreateBeginFrameArgs(MillisSinceEpoch(90))));
+}
+
+TEST_F(ScrollJankV4DeciderTest, IsFastScroll) {
+  EXPECT_TRUE(ScrollJankV4Decider::IsFastScroll(
+      Real{.abs_total_raw_delta_pixels = 4.0}));
+}
+
+TEST_F(ScrollJankV4DeciderTest, IsNotFastScroll) {
+  EXPECT_FALSE(ScrollJankV4Decider::IsFastScroll(
+      Real{.abs_total_raw_delta_pixels = 2.0}));
+}
+
+TEST_F(ScrollJankV4DeciderTest, IsSufficientlyFastFling) {
+  EXPECT_TRUE(ScrollJankV4Decider::IsSufficientlyFastFling(Real{
+      .has_inertial_input = true, .max_abs_inertial_raw_delta_pixels = 0.3}));
+}
+
+TEST_F(ScrollJankV4DeciderTest, IsNotSufficientlyFastFling) {
+  EXPECT_FALSE(ScrollJankV4Decider::IsSufficientlyFastFling(Real{
+      .has_inertial_input = true, .max_abs_inertial_raw_delta_pixels = 0.1}));
+}
+
+}  // namespace
 }  // namespace cc
diff --git a/cc/metrics/scroll_jank_v4_decision_queue.cc b/cc/metrics/scroll_jank_v4_decision_queue.cc
new file mode 100644
index 0000000..0c47efc9
--- /dev/null
+++ b/cc/metrics/scroll_jank_v4_decision_queue.cc
@@ -0,0 +1,133 @@
+// 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 "cc/metrics/scroll_jank_v4_decision_queue.h"
+
+#include <utility>
+#include <variant>
+
+#include "base/check.h"
+#include "base/check_op.h"
+#include "cc/metrics/scroll_jank_v4_decider.h"
+#include "cc/metrics/scroll_jank_v4_frame.h"
+#include "cc/metrics/scroll_jank_v4_frame_stage.h"
+#include "third_party/abseil-cpp/absl/functional/overload.h"
+
+namespace cc {
+
+namespace {
+
+using ScrollDamage = ScrollJankV4Frame::ScrollDamage;
+using DamagingFrame = ScrollJankV4Frame::DamagingFrame;
+using ScrollUpdates = ScrollJankV4FrameStage::ScrollUpdates;
+
+}  // namespace
+
+ScrollJankV4DecisionQueue::ResultConsumer::~ResultConsumer() = default;
+
+ScrollJankV4DecisionQueue::ScrollJankV4DecisionQueue(
+    std::unique_ptr<ResultConsumer> result_consumer)
+    : result_consumer_(std::move(result_consumer)) {}
+
+ScrollJankV4DecisionQueue::~ScrollJankV4DecisionQueue() {
+  FlushDeferredSyntheticFrames(
+      /* future_real_frame_is_fast_scroll_or_sufficiently_fast_fling= */ false);
+}
+
+bool ScrollJankV4DecisionQueue::ProcessFrameWithScrollUpdates(
+    ScrollUpdates& updates,
+    const ScrollDamage& damage,
+    const ScrollJankV4Frame::BeginFrameArgsForScrollJank& args) {
+  // TODO(crbug.com/464210135): Enforce the below invariant about
+  // `has_inertial_input` and `max_abs_inertial_raw_delta_pixels` in
+  // `ScrollUpdates::Real`.
+  CHECK(!updates.real().has_value() || updates.real()->has_inertial_input ||
+        updates.real()->max_abs_inertial_raw_delta_pixels == 0);
+
+  if (!AcceptFrameIfValidAndChronological(updates, damage, args)) {
+    return false;
+  }
+
+  // If the frame contains only synthetic inputs, defer the decision until we
+  // receive a frame with at least one real input.
+  if (!updates.real().has_value()) {
+    // Set `earliest_event` to null to avoid a dangling pointer.
+    ScrollUpdates updates_without_earliest_event = ScrollUpdates(
+        /* earliest_event= */ nullptr, updates.real(), updates.synthetic());
+    deferred_synthetic_frames_.emplace_back(updates_without_earliest_event,
+                                            damage, args);
+    return true;
+  }
+
+  // If the new frame contains at least one real input, we have enough
+  // information to decide if any preceding synthetic frames and the new frame
+  // itself are janky (in chronological order).
+  const ScrollUpdates::Real& real_updates = *updates.real();
+  bool future_real_frame_is_fast_scroll_or_sufficiently_fast_fling =
+      ScrollJankV4Decider::IsFastScroll(real_updates) ||
+      ScrollJankV4Decider::IsSufficientlyFastFling(real_updates);
+  FlushDeferredSyntheticFrames(
+      future_real_frame_is_fast_scroll_or_sufficiently_fast_fling);
+  auto result =
+      decider_.DecideJankForFrameWithRealScrollUpdates(updates, damage, args);
+  result_consumer_->OnFrameResult(result, updates.earliest_event());
+  return true;
+}
+
+void ScrollJankV4DecisionQueue::OnScrollStarted() {
+  // There should be no deferred synthetic frames to flush UNLESS we didn't
+  // receive the scroll end event for some reason.
+  FlushDeferredSyntheticFrames(
+      /* future_real_frame_is_fast_scroll_or_sufficiently_fast_fling= */ false);
+  decider_.OnScrollStarted();
+  result_consumer_->OnScrollStarted();
+}
+
+void ScrollJankV4DecisionQueue::OnScrollEnded() {
+  FlushDeferredSyntheticFrames(
+      /* future_real_frame_is_fast_scroll_or_sufficiently_fast_fling= */ false);
+  decider_.OnScrollEnded();
+  result_consumer_->OnScrollEnded();
+}
+
+bool ScrollJankV4DecisionQueue::AcceptFrameIfValidAndChronological(
+    const ScrollJankV4FrameStage::ScrollUpdates& updates,
+    const ScrollJankV4Frame::ScrollDamage& damage,
+    const ScrollJankV4Frame::BeginFrameArgsForScrollJank& args) {
+  // Check that the frame is valid and that it came after the most recently
+  // provided frame.
+  if (!ScrollJankV4Decider::IsValidFrame(updates, damage, args)) {
+    return false;
+  }
+  if (args.frame_time <= last_provided_valid_begin_frame_ts_) {
+    return false;
+  }
+  const DamagingFrame* damaging_frame = std::get_if<DamagingFrame>(&damage);
+  if (damaging_frame &&
+      damaging_frame->presentation_ts <= last_provided_valid_presentation_ts_) {
+    return false;
+  }
+
+  // Accept the new frame.
+  last_provided_valid_begin_frame_ts_ = args.frame_time;
+  if (damaging_frame) {
+    last_provided_valid_presentation_ts_ = damaging_frame->presentation_ts;
+  }
+  return true;
+}
+
+void ScrollJankV4DecisionQueue::FlushDeferredSyntheticFrames(
+    bool future_real_frame_is_fast_scroll_or_sufficiently_fast_fling) {
+  for (const auto& [updates, damage, args] : deferred_synthetic_frames_) {
+    DCHECK(!updates.real().has_value());
+    DCHECK_EQ(updates.earliest_event(), nullptr);
+    auto result = decider_.DecideJankForFrameWithSyntheticScrollUpdatesOnly(
+        updates, damage, args,
+        future_real_frame_is_fast_scroll_or_sufficiently_fast_fling);
+    result_consumer_->OnFrameResult(result, /* earliest_event= */ nullptr);
+  }
+  deferred_synthetic_frames_.clear();
+}
+
+}  // namespace cc
diff --git a/cc/metrics/scroll_jank_v4_decision_queue.h b/cc/metrics/scroll_jank_v4_decision_queue.h
new file mode 100644
index 0000000..708672d
--- /dev/null
+++ b/cc/metrics/scroll_jank_v4_decision_queue.h
@@ -0,0 +1,147 @@
+// 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 CC_METRICS_SCROLL_JANK_V4_DECISION_QUEUE_H_
+#define CC_METRICS_SCROLL_JANK_V4_DECISION_QUEUE_H_
+
+#include <memory>
+#include <optional>
+#include <vector>
+
+#include "base/time/time.h"
+#include "cc/cc_export.h"
+#include "cc/metrics/event_metrics.h"
+#include "cc/metrics/scroll_jank_v4_decider.h"
+#include "cc/metrics/scroll_jank_v4_frame.h"
+#include "cc/metrics/scroll_jank_v4_frame_stage.h"
+
+namespace cc {
+
+// Class responsible for deciding whether a frame containing one or more scroll
+// updates was janky or not according to the scroll jank v4 metric. In order to
+// work correctly, it must be informed about each frame that contained one or
+// more scroll updates in chronological order.
+//
+// Scroll updates (and subsequently frames) can be categorized according to the
+// following criteria:
+//
+//   1. Whether the scroll update cause a frame update and changed the scroll
+//      offset: damaging vs. non-damaging scroll updates. See
+//      `ScrollJankV4Result::is_damaging_frame` for
+//      the definition of non-damaging scroll updates and frames.
+//   2. Whether the scroll update originated from hardware/OS: real vs.
+//      synthetic scroll updates. See `ScrollJankV4FrameStage::ScrollUpdates`
+//      for the definition of synthetic scroll updates and frames.
+//
+// To avoid false positives, the decider must be informed about all four types
+// of scroll updates and frames that occur within a scroll (damaging real,
+// non-damaging real, damaging synthetic, non-damaging synthetic).
+//
+// See
+// https://docs.google.com/document/d/1AaBvTIf8i-c-WTKkjaL4vyhQMkSdynxo3XEiwpofdeA
+// for more details about the scroll jank v4 metric.
+class CC_EXPORT ScrollJankV4DecisionQueue {
+ public:
+  class CC_EXPORT ResultConsumer {
+   public:
+    virtual ~ResultConsumer();
+    virtual void OnFrameResult(
+        ScrollUpdateEventMetrics::ScrollJankV4Result result,
+        // TODO(crbug.com/456180776): Remove this argument once scroll jank v4
+        // metrics are recorded in dedicated trace events.
+        ScrollUpdateEventMetrics* earliest_event) = 0;
+    virtual void OnScrollStarted() = 0;
+    virtual void OnScrollEnded() = 0;
+  };
+
+  explicit ScrollJankV4DecisionQueue(
+      std::unique_ptr<ResultConsumer> result_consumer);
+  ~ScrollJankV4DecisionQueue();
+  ScrollJankV4DecisionQueue(const ScrollJankV4DecisionQueue&) = delete;
+  ScrollJankV4DecisionQueue(ScrollJankV4DecisionQueue&&) = delete;
+
+  // Processes a frame which contains scroll updates to decide whether it was
+  // janky.
+  //
+  // If the frame is malformed in any way (e.g. it has an earlier presentation
+  // time than the previous frame provided to the decider), this method false.
+  // returns without invoking the queue's `ResultConsumer`. Otherwise, this
+  // method returns true and provides the jank result to the queue's
+  // `ResultConsumer`. Depending on whether there is enough information to make
+  // a decision, the method will provide the jank result to the
+  // `ResultConsumer`:
+  //
+  //   * either immediately before returning,
+  //   * or at a later point when it receives the necessary information (e.g.
+  //     when this method is called later with information about a subsequent
+  //     frame or when the scroll ends).
+  //
+  // Either way, this method guarantees that the queue's `ResultConsumer` will
+  // be invoked with jank results in the same order as the frames were provided
+  // to this method. For example, if the caller does the following (with valid
+  // arguments):
+  //
+  // ```
+  // queue.ProcessFrameWithScrollUpdates(updates1, damage1, args1);
+  // queue.ProcessFrameWithScrollUpdates(updates2, damage2, args2);
+  // ```
+  //
+  // this method guarantees to invoke the `ResultConsumer` with the jank results
+  // for (`updates1`, `damage1`, `args1`) before invoking it with the jank
+  // results for (`updates2`, `damage2`, `args2`).
+  bool ProcessFrameWithScrollUpdates(
+      ScrollJankV4FrameStage::ScrollUpdates& updates,
+      const ScrollJankV4Frame::ScrollDamage& damage,
+      const ScrollJankV4Frame::BeginFrameArgsForScrollJank& args);
+
+  void OnScrollStarted();
+  void OnScrollEnded();
+
+ private:
+  bool AcceptFrameIfValidAndChronological(
+      const ScrollJankV4FrameStage::ScrollUpdates& updates,
+      const ScrollJankV4Frame::ScrollDamage& damage,
+      const ScrollJankV4Frame::BeginFrameArgsForScrollJank& args);
+
+  void FlushDeferredSyntheticFrames(
+      bool future_real_frame_is_fast_scroll_or_sufficiently_fast_fling);
+
+  ScrollJankV4Decider decider_;
+
+  std::unique_ptr<ResultConsumer> result_consumer_;
+
+  // Begin frame and presentation timestamps of the most recent valid frame
+  // provided to `ProcessFrameWithScrollUpdates()`. The timestamps increase
+  // monotonically with each new valid frame.
+  base::TimeTicks last_provided_valid_begin_frame_ts_ = base::TimeTicks::Min();
+  base::TimeTicks last_provided_valid_presentation_ts_ = base::TimeTicks::Min();
+
+  // Synthetic frames provided to `ProcessFrameWithScrollUpdates()` which the
+  // decider hasn't yet decided whether they're janky or not. They're waiting to
+  // hear if they're in the middle of a fast scroll.
+  //
+  // The vector is sorted in chronological order (i.e. new synthetic frames are
+  // appended to the end of the vector). If this vector is non-empty, then
+  // `last_provided_valid_begin_frame_ts_` is equal to the begin frame timestamp
+  // of the last frame in this vector
+  // (`deferred_synthetic_frames_.rbegin()->args.frame_time`). Furthermore, if
+  // this vector contains any damaging frames,
+  // `last_provided_valid_presentation_ts_` is equal to the presentation time of
+  // the last damaging frame in this vector.
+  //
+  // None of the frames should contain real updates, i.e.
+  // `frame.updates.real().has_value()` is false for each `frame` in this
+  // vector. None of the frames should contain a pointer to event metrics, i.e.
+  // `frame.updates.earliest_event` is null for each `frame` in this vector.
+  struct DeferredSyntheticFrame {
+    ScrollJankV4FrameStage::ScrollUpdates updates;
+    ScrollJankV4Frame::ScrollDamage damage;
+    ScrollJankV4Frame::BeginFrameArgsForScrollJank args;
+  };
+  std::vector<DeferredSyntheticFrame> deferred_synthetic_frames_;
+};
+
+}  // namespace cc
+
+#endif  // CC_METRICS_SCROLL_JANK_V4_DECISION_QUEUE_H_
diff --git a/cc/metrics/scroll_jank_v4_decision_queue_unittest.cc b/cc/metrics/scroll_jank_v4_decision_queue_unittest.cc
new file mode 100644
index 0000000..c10cdb6
--- /dev/null
+++ b/cc/metrics/scroll_jank_v4_decision_queue_unittest.cc
@@ -0,0 +1,583 @@
+// 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 "cc/metrics/scroll_jank_v4_decision_queue.h"
+
+#include <concepts>
+#include <memory>
+#include <optional>
+
+#include "base/memory/raw_ptr.h"
+#include "base/time/time.h"
+#include "cc/metrics/event_metrics.h"
+#include "cc/metrics/scroll_jank_v4_frame.h"
+#include "cc/test/event_metrics_test_creator.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cc {
+namespace {
+
+constexpr base::TimeDelta kVsyncInterval = base::Milliseconds(16);
+
+constexpr base::TimeTicks MillisSinceEpoch(int64_t millis) {
+  return base::TimeTicks() + base::Milliseconds(millis);
+}
+
+using DamagingFrame = ScrollJankV4Frame::DamagingFrame;
+using ScrollUpdates = ScrollJankV4FrameStage::ScrollUpdates;
+using Real = ScrollUpdates::Real;
+using Synthetic = ScrollUpdates::Synthetic;
+using ScrollJankV4Result = ScrollUpdateEventMetrics::ScrollJankV4Result;
+
+using ::testing::_;
+using ::testing::InSequence;
+using ::testing::StrictMock;
+
+/* Matches a result iff `matcher` matches `result->missed_vsyncs_per_reason`. */
+::testing::Matcher<ScrollJankV4Result> HasMissedVsyncsPerReasonMatching(
+    ::testing::Matcher<const JankReasonArray<int>&> matcher) {
+  return ::testing::Field(&ScrollJankV4Result::missed_vsyncs_per_reason,
+                          matcher);
+}
+
+const ::testing::Matcher<ScrollJankV4Result> kHasNoMissedVsyncs =
+    HasMissedVsyncsPerReasonMatching(::testing::Each(::testing::Eq(0)));
+
+::testing::Matcher<ScrollJankV4Result> HasMissedVsyncs(JankReason reason,
+                                                       int missed_vsyncs) {
+  JankReasonArray<int> expected_missed_vsyncs = {};
+  expected_missed_vsyncs[static_cast<int>(reason)] = missed_vsyncs;
+  return HasMissedVsyncsPerReasonMatching(
+      ::testing::ElementsAreArray(expected_missed_vsyncs));
+}
+
+class MockResultConsumer : public ScrollJankV4DecisionQueue::ResultConsumer {
+ public:
+  MOCK_METHOD(void,
+              OnFrameResult,
+              (ScrollUpdateEventMetrics::ScrollJankV4Result result,
+               ScrollUpdateEventMetrics* earliest_event),
+              (override));
+  MOCK_METHOD(void, OnScrollStarted, (), (override));
+  MOCK_METHOD(void, OnScrollEnded, (), (override));
+};
+
+// A result consumer which forwards all method calls to another result consumer
+// (of type `R`). This consumer doesn't own the other consumer.
+template <typename R>
+  requires std::derived_from<R, ScrollJankV4DecisionQueue::ResultConsumer>
+class ForwardingResultConsumer
+    : public ScrollJankV4DecisionQueue::ResultConsumer {
+ public:
+  explicit ForwardingResultConsumer(R* forwardee) : forwardee_(forwardee) {}
+
+  void OnFrameResult(ScrollUpdateEventMetrics::ScrollJankV4Result result,
+                     ScrollUpdateEventMetrics* earliest_event) override {
+    forwardee_->OnFrameResult(result, earliest_event);
+  }
+  void OnScrollStarted() override { forwardee_->OnScrollStarted(); }
+  void OnScrollEnded() override { forwardee_->OnScrollEnded(); }
+
+ private:
+  raw_ptr<R> forwardee_;
+};
+
+class ScrollJankV4DecisionQueueTest : public testing::Test {
+ protected:
+  ScrollJankV4DecisionQueueTest()
+      : result_consumer_(std::make_unique<StrictMock<MockResultConsumer>>()),
+        decision_queue_(std::make_unique<ScrollJankV4DecisionQueue>(
+            std::make_unique<
+                ForwardingResultConsumer<StrictMock<MockResultConsumer>>>(
+                result_consumer_.get()))) {}
+
+  static ScrollJankV4Frame::BeginFrameArgsForScrollJank CreateBeginFrameArgs(
+      base::TimeTicks frame_time) {
+    return {.frame_time = frame_time, .interval = kVsyncInterval};
+  }
+
+  // `result_consumer_` must be declared above `decision_queue_` because
+  // `decision_queue_` owns a `ForwardingResultConsumer` which has a raw pointer
+  // to `result_consumer_` (so `decision_queue_` must be destroyed before
+  // `result_consumer_`).
+  std::unique_ptr<StrictMock<MockResultConsumer>> result_consumer_;
+  std::unique_ptr<ScrollJankV4DecisionQueue> decision_queue_;
+};
+
+TEST_F(ScrollJankV4DecisionQueueTest, ImmediatelyReportsResultsForRealFrames) {
+  {
+    EXPECT_CALL(*result_consumer_, OnScrollStarted());
+    decision_queue_->OnScrollStarted();
+  }
+
+  // F1: Real frame.
+  {
+    std::unique_ptr<ScrollUpdateEventMetrics> earliest_event1 =
+        EventMetricsTestCreator().CreateGestureScrollUpdate({});
+    EXPECT_CALL(*result_consumer_,
+                OnFrameResult(kHasNoMissedVsyncs, earliest_event1.get()));
+    ScrollUpdates updates1 =
+        ScrollUpdates(earliest_event1.get(),
+                      Real{.first_input_generation_ts = MillisSinceEpoch(103),
+                           .last_input_generation_ts = MillisSinceEpoch(111),
+                           .has_inertial_input = false,
+                           .abs_total_raw_delta_pixels = 5.0f,
+                           .max_abs_inertial_raw_delta_pixels = 0.0f},
+                      /* synthetic= */ std::nullopt);
+    bool success1 = decision_queue_->ProcessFrameWithScrollUpdates(
+        updates1, DamagingFrame{.presentation_ts = MillisSinceEpoch(132)},
+        CreateBeginFrameArgs(MillisSinceEpoch(116)));
+    EXPECT_TRUE(success1);
+  }
+
+  // F2: Real frame with no missed VSyncs.
+  {
+    std::unique_ptr<ScrollUpdateEventMetrics> earliest_event2 =
+        EventMetricsTestCreator().CreateGestureScrollUpdate({});
+    EXPECT_CALL(*result_consumer_,
+                OnFrameResult(kHasNoMissedVsyncs, earliest_event2.get()));
+    ScrollUpdates updates2 =
+        ScrollUpdates(earliest_event2.get(),
+                      Real{.first_input_generation_ts = MillisSinceEpoch(119),
+                           .last_input_generation_ts = MillisSinceEpoch(127),
+                           .has_inertial_input = false,
+                           .abs_total_raw_delta_pixels = 5.0f,
+                           .max_abs_inertial_raw_delta_pixels = 0.0f},
+                      /* synthetic= */ std::nullopt);
+    bool success2 = decision_queue_->ProcessFrameWithScrollUpdates(
+        updates2, DamagingFrame{.presentation_ts = MillisSinceEpoch(148)},
+        CreateBeginFrameArgs(MillisSinceEpoch(132)));
+    EXPECT_TRUE(success2);
+  }
+
+  // F3: Real frame with 1 VSync missed due to fast scroll continuity rule.
+  {
+    std::unique_ptr<ScrollUpdateEventMetrics> earliest_event3 =
+        EventMetricsTestCreator().CreateGestureScrollUpdate({});
+    EXPECT_CALL(*result_consumer_,
+                OnFrameResult(HasMissedVsyncs(
+                                  JankReason::kMissedVsyncDuringFastScroll, 1),
+                              earliest_event3.get()));
+    ScrollUpdates updates3 =
+        ScrollUpdates(earliest_event3.get(),
+                      Real{.first_input_generation_ts = MillisSinceEpoch(151),
+                           .last_input_generation_ts = MillisSinceEpoch(159),
+                           .has_inertial_input = false,
+                           .abs_total_raw_delta_pixels = 5.0f,
+                           .max_abs_inertial_raw_delta_pixels = 0.0f},
+                      /* synthetic= */ std::nullopt);
+    bool success3 = decision_queue_->ProcessFrameWithScrollUpdates(
+        updates3, DamagingFrame{.presentation_ts = MillisSinceEpoch(180)},
+        CreateBeginFrameArgs(MillisSinceEpoch(164)));
+    EXPECT_TRUE(success3);
+  }
+
+  {
+    EXPECT_CALL(*result_consumer_, OnScrollEnded());
+    decision_queue_->OnScrollEnded();
+  }
+}
+
+TEST_F(ScrollJankV4DecisionQueueTest,
+       DefersReportingResultsForSyntheticFramesUntilNextRealFrame) {
+  {
+    EXPECT_CALL(*result_consumer_, OnScrollStarted());
+    decision_queue_->OnScrollStarted();
+  }
+
+  // F1: Synthetic frame.
+  {
+    std::unique_ptr<ScrollUpdateEventMetrics> earliest_event1 =
+        EventMetricsTestCreator().CreateGestureScrollUpdate({});
+    // The queue should defer the decision (i.e. no result should be reported
+    // yet for F1).
+    EXPECT_CALL(*result_consumer_, OnFrameResult(_, _)).Times(0);
+    ScrollUpdates updates1 = ScrollUpdates(
+        earliest_event1.get(),
+        /* real= */ std::nullopt,
+        Synthetic{.first_input_begin_frame_ts = MillisSinceEpoch(116)});
+    bool success1 = decision_queue_->ProcessFrameWithScrollUpdates(
+        updates1, DamagingFrame{.presentation_ts = MillisSinceEpoch(132)},
+        CreateBeginFrameArgs(MillisSinceEpoch(116)));
+    EXPECT_TRUE(success1);
+  }
+
+  // F2: Real frame with no missed VSyncs.
+  {
+    std::unique_ptr<ScrollUpdateEventMetrics> earliest_event2 =
+        EventMetricsTestCreator().CreateGestureScrollUpdate({});
+    // The deferred decision for the synthetic frame F1 should be flushed
+    // before F2's result.
+    InSequence in_sequence;
+    EXPECT_CALL(*result_consumer_,
+                OnFrameResult(kHasNoMissedVsyncs, /* F1 */ nullptr));
+    EXPECT_CALL(*result_consumer_,
+                OnFrameResult(kHasNoMissedVsyncs, earliest_event2.get()));
+    ScrollUpdates updates2 =
+        ScrollUpdates(earliest_event2.get(),
+                      Real{.first_input_generation_ts = MillisSinceEpoch(135),
+                           .last_input_generation_ts = MillisSinceEpoch(143),
+                           .has_inertial_input = false,
+                           .abs_total_raw_delta_pixels = 5.0f,
+                           .max_abs_inertial_raw_delta_pixels = 0.0f},
+                      /* synthetic= */ std::nullopt);
+    bool success2 = decision_queue_->ProcessFrameWithScrollUpdates(
+        updates2, DamagingFrame{.presentation_ts = MillisSinceEpoch(164)},
+        CreateBeginFrameArgs(MillisSinceEpoch(148)));
+    EXPECT_TRUE(success2);
+  }
+
+  // F3: Synthetic frame with no missed VSyncs.
+  {
+    std::unique_ptr<ScrollUpdateEventMetrics> earliest_event3 =
+        EventMetricsTestCreator().CreateGestureScrollUpdate({});
+    // The queue should defer the decision (i.e. no result should be reported
+    // yet for F3).
+    EXPECT_CALL(*result_consumer_, OnFrameResult(_, _)).Times(0);
+    ScrollUpdates updates3 = ScrollUpdates(
+        earliest_event3.get(),
+        /* real= */ std::nullopt,
+        Synthetic{.first_input_begin_frame_ts = MillisSinceEpoch(164)});
+    bool success3 = decision_queue_->ProcessFrameWithScrollUpdates(
+        updates3, DamagingFrame{.presentation_ts = MillisSinceEpoch(180)},
+        CreateBeginFrameArgs(MillisSinceEpoch(164)));
+    EXPECT_TRUE(success3);
+  }
+
+  // F4: Synthetic frame with 1 VSync missed due to fast scroll continuity rule.
+  {
+    std::unique_ptr<ScrollUpdateEventMetrics> earliest_event4 =
+        EventMetricsTestCreator().CreateGestureScrollUpdate({});
+    // The queue should defer the decision (i.e. no result should be reported
+    // yet for F4).
+    EXPECT_CALL(*result_consumer_, OnFrameResult(_, _)).Times(0);
+    ScrollUpdates updates3 = ScrollUpdates(
+        earliest_event4.get(),
+        /* real= */ std::nullopt,
+        Synthetic{.first_input_begin_frame_ts = MillisSinceEpoch(196)});
+    bool success4 = decision_queue_->ProcessFrameWithScrollUpdates(
+        updates3, DamagingFrame{.presentation_ts = MillisSinceEpoch(212)},
+        CreateBeginFrameArgs(MillisSinceEpoch(196)));
+    EXPECT_TRUE(success4);
+  }
+
+  // F5: Real frame with 2 VSyncs missed due to the fling continuity rule.
+  {
+    std::unique_ptr<ScrollUpdateEventMetrics> earliest_event5 =
+        EventMetricsTestCreator().CreateGestureScrollUpdate({});
+    // The deferred decisions for the synthetic frames F3 and F4 should be
+    // flushed (in chronological order) before F5's result.
+    InSequence in_sequence;
+    EXPECT_CALL(*result_consumer_,
+                OnFrameResult(kHasNoMissedVsyncs, /* F3 */ nullptr));
+    EXPECT_CALL(*result_consumer_,
+                OnFrameResult(HasMissedVsyncs(
+                                  JankReason::kMissedVsyncDuringFastScroll, 1),
+                              /* F4 */ nullptr));
+    EXPECT_CALL(*result_consumer_,
+                OnFrameResult(
+                    HasMissedVsyncs(JankReason::kMissedVsyncAtStartOfFling, 2),
+                    earliest_event5.get()));
+    ScrollUpdates updates5 =
+        ScrollUpdates(earliest_event5.get(),
+                      Real{.first_input_generation_ts = MillisSinceEpoch(236),
+                           .last_input_generation_ts = MillisSinceEpoch(236),
+                           .has_inertial_input = true,
+                           .abs_total_raw_delta_pixels = 1.0f,
+                           .max_abs_inertial_raw_delta_pixels = 1.0f},
+                      /* synthetic= */ std::nullopt);
+    bool success5 = decision_queue_->ProcessFrameWithScrollUpdates(
+        updates5, DamagingFrame{.presentation_ts = MillisSinceEpoch(260)},
+        CreateBeginFrameArgs(MillisSinceEpoch(244)));
+    EXPECT_TRUE(success5);
+  }
+
+  {
+    EXPECT_CALL(*result_consumer_, OnScrollEnded());
+    decision_queue_->OnScrollEnded();
+  }
+}
+
+TEST_F(ScrollJankV4DecisionQueueTest,
+       DefersReportingResultsForSyntheticFramesUntilEndOfScroll) {
+  {
+    EXPECT_CALL(*result_consumer_, OnScrollStarted());
+    decision_queue_->OnScrollStarted();
+  }
+
+  // F1: Real frame.
+  {
+    std::unique_ptr<ScrollUpdateEventMetrics> earliest_event1 =
+        EventMetricsTestCreator().CreateGestureScrollUpdate({});
+    EXPECT_CALL(*result_consumer_,
+                OnFrameResult(kHasNoMissedVsyncs, earliest_event1.get()));
+    ScrollUpdates updates1 =
+        ScrollUpdates(earliest_event1.get(),
+                      Real{.first_input_generation_ts = MillisSinceEpoch(135),
+                           .last_input_generation_ts = MillisSinceEpoch(143),
+                           .has_inertial_input = false,
+                           .abs_total_raw_delta_pixels = 5.0f,
+                           .max_abs_inertial_raw_delta_pixels = 0.0f},
+                      /* synthetic= */ std::nullopt);
+    bool success1 = decision_queue_->ProcessFrameWithScrollUpdates(
+        updates1, DamagingFrame{.presentation_ts = MillisSinceEpoch(164)},
+        CreateBeginFrameArgs(MillisSinceEpoch(148)));
+    EXPECT_TRUE(success1);
+  }
+
+  // F2: Synthetic frame with no missed VSyncs (because we're not in a fast
+  // scroll).
+  {
+    std::unique_ptr<ScrollUpdateEventMetrics> earliest_event2 =
+        EventMetricsTestCreator().CreateGestureScrollUpdate({});
+    // The queue should defer the decision (i.e. no result should be reported
+    // yet for F2).
+    EXPECT_CALL(*result_consumer_, OnFrameResult(_, _)).Times(0);
+    ScrollUpdates updates2 = ScrollUpdates(
+        earliest_event2.get(),
+        /* real= */ std::nullopt,
+        Synthetic{.first_input_begin_frame_ts = MillisSinceEpoch(196)});
+    bool success2 = decision_queue_->ProcessFrameWithScrollUpdates(
+        updates2, DamagingFrame{.presentation_ts = MillisSinceEpoch(212)},
+        CreateBeginFrameArgs(MillisSinceEpoch(196)));
+    EXPECT_TRUE(success2);
+  }
+
+  {
+    // The deferred decision for the synthetic frame F2 should be flushed before
+    // the scroll end.
+    InSequence in_sequence;
+    EXPECT_CALL(*result_consumer_,
+                OnFrameResult(kHasNoMissedVsyncs, /* F2 */ nullptr));
+    EXPECT_CALL(*result_consumer_, OnScrollEnded());
+    decision_queue_->OnScrollEnded();
+  }
+}
+
+TEST_F(ScrollJankV4DecisionQueueTest,
+       DefersReportingResultsForSyntheticFramesUntilStartOfNextScroll) {
+  {
+    EXPECT_CALL(*result_consumer_, OnScrollStarted());
+    decision_queue_->OnScrollStarted();
+  }
+
+  // F1: Real frame.
+  {
+    std::unique_ptr<ScrollUpdateEventMetrics> earliest_event1 =
+        EventMetricsTestCreator().CreateGestureScrollUpdate({});
+    EXPECT_CALL(*result_consumer_,
+                OnFrameResult(kHasNoMissedVsyncs, earliest_event1.get()));
+    ScrollUpdates updates1 =
+        ScrollUpdates(earliest_event1.get(),
+                      Real{.first_input_generation_ts = MillisSinceEpoch(135),
+                           .last_input_generation_ts = MillisSinceEpoch(143),
+                           .has_inertial_input = false,
+                           .abs_total_raw_delta_pixels = 5.0f,
+                           .max_abs_inertial_raw_delta_pixels = 0.0f},
+                      /* synthetic= */ std::nullopt);
+    bool success1 = decision_queue_->ProcessFrameWithScrollUpdates(
+        updates1, DamagingFrame{.presentation_ts = MillisSinceEpoch(164)},
+        CreateBeginFrameArgs(MillisSinceEpoch(148)));
+    EXPECT_TRUE(success1);
+  }
+
+  // F2: Synthetic frame with no missed VSyncs (because we're not in a fast
+  // scroll).
+  {
+    std::unique_ptr<ScrollUpdateEventMetrics> earliest_event2 =
+        EventMetricsTestCreator().CreateGestureScrollUpdate({});
+    // The queue should defer the decision (i.e. no result should be reported
+    // yet for F2).
+    EXPECT_CALL(*result_consumer_, OnFrameResult(_, _)).Times(0);
+    ScrollUpdates updates2 = ScrollUpdates(
+        earliest_event2.get(),
+        /* real= */ std::nullopt,
+        Synthetic{.first_input_begin_frame_ts = MillisSinceEpoch(196)});
+    bool success2 = decision_queue_->ProcessFrameWithScrollUpdates(
+        updates2, DamagingFrame{.presentation_ts = MillisSinceEpoch(212)},
+        CreateBeginFrameArgs(MillisSinceEpoch(196)));
+    EXPECT_TRUE(success2);
+  }
+
+  {
+    // The deferred decision for the synthetic frame F2 should be flushed before
+    // the next scroll start.
+    InSequence in_sequence;
+    EXPECT_CALL(*result_consumer_,
+                OnFrameResult(kHasNoMissedVsyncs, /* F2 */ nullptr));
+    EXPECT_CALL(*result_consumer_, OnScrollStarted());
+    decision_queue_->OnScrollStarted();
+  }
+}
+
+TEST_F(ScrollJankV4DecisionQueueTest,
+       DefersReportingResultsForSyntheticFramesUntilDestruction) {
+  {
+    EXPECT_CALL(*result_consumer_, OnScrollStarted());
+    decision_queue_->OnScrollStarted();
+  }
+
+  // F1: Real frame.
+  {
+    std::unique_ptr<ScrollUpdateEventMetrics> earliest_event1 =
+        EventMetricsTestCreator().CreateGestureScrollUpdate({});
+    EXPECT_CALL(*result_consumer_,
+                OnFrameResult(kHasNoMissedVsyncs, earliest_event1.get()));
+    ScrollUpdates updates1 =
+        ScrollUpdates(earliest_event1.get(),
+                      Real{.first_input_generation_ts = MillisSinceEpoch(135),
+                           .last_input_generation_ts = MillisSinceEpoch(143),
+                           .has_inertial_input = false,
+                           .abs_total_raw_delta_pixels = 5.0f,
+                           .max_abs_inertial_raw_delta_pixels = 0.0f},
+                      /* synthetic= */ std::nullopt);
+    bool success1 = decision_queue_->ProcessFrameWithScrollUpdates(
+        updates1, DamagingFrame{.presentation_ts = MillisSinceEpoch(164)},
+        CreateBeginFrameArgs(MillisSinceEpoch(148)));
+    EXPECT_TRUE(success1);
+  }
+
+  // F2: Synthetic frame with no missed VSyncs (because we're not in a fast
+  // scroll).
+  {
+    std::unique_ptr<ScrollUpdateEventMetrics> earliest_event2 =
+        EventMetricsTestCreator().CreateGestureScrollUpdate({});
+    // The queue should defer the decision (i.e. no result should be reported
+    // yet for F2).
+    EXPECT_CALL(*result_consumer_, OnFrameResult(_, _)).Times(0);
+    ScrollUpdates updates2 = ScrollUpdates(
+        earliest_event2.get(),
+        /* real= */ std::nullopt,
+        Synthetic{.first_input_begin_frame_ts = MillisSinceEpoch(196)});
+    bool success2 = decision_queue_->ProcessFrameWithScrollUpdates(
+        updates2, DamagingFrame{.presentation_ts = MillisSinceEpoch(212)},
+        CreateBeginFrameArgs(MillisSinceEpoch(196)));
+    EXPECT_TRUE(success2);
+  }
+
+  {
+    // The deferred decision for the synthetic frame F2 should be flushed before
+    // the queue is destroyed.
+    EXPECT_CALL(*result_consumer_,
+                OnFrameResult(kHasNoMissedVsyncs, /* F2 */ nullptr));
+    delete decision_queue_.release();
+  }
+}
+
+TEST_F(ScrollJankV4DecisionQueueTest, HandlesInvalidFramesGracefully) {
+  {
+    EXPECT_CALL(*result_consumer_, OnScrollStarted());
+    decision_queue_->OnScrollStarted();
+  }
+
+  // F1: Valid real frame.
+  {
+    std::unique_ptr<ScrollUpdateEventMetrics> earliest_event1 =
+        EventMetricsTestCreator().CreateGestureScrollUpdate({});
+    EXPECT_CALL(*result_consumer_,
+                OnFrameResult(kHasNoMissedVsyncs, earliest_event1.get()));
+    ScrollUpdates updates1 =
+        ScrollUpdates(earliest_event1.get(),
+                      Real{.first_input_generation_ts = MillisSinceEpoch(103),
+                           .last_input_generation_ts = MillisSinceEpoch(111),
+                           .has_inertial_input = false,
+                           .abs_total_raw_delta_pixels = 5.0f,
+                           .max_abs_inertial_raw_delta_pixels = 0.0f},
+                      /* synthetic= */ std::nullopt);
+    bool success1 = decision_queue_->ProcessFrameWithScrollUpdates(
+        updates1, DamagingFrame{.presentation_ts = MillisSinceEpoch(148)},
+        CreateBeginFrameArgs(MillisSinceEpoch(116)));
+    EXPECT_TRUE(success1);
+  }
+
+  // F2: Invalid real frame (because last_input_generation_ts is after
+  // presentation_ts).
+  {
+    std::unique_ptr<ScrollUpdateEventMetrics> earliest_event2 =
+        EventMetricsTestCreator().CreateGestureScrollUpdate({});
+    EXPECT_CALL(*result_consumer_, OnFrameResult(_, _)).Times(0);
+    ScrollUpdates updates2 = ScrollUpdates(
+        earliest_event2.get(),
+        Real{.first_input_generation_ts = MillisSinceEpoch(119),
+             .last_input_generation_ts = MillisSinceEpoch(165) /* wrong */,
+             .has_inertial_input = false,
+             .abs_total_raw_delta_pixels = 5.0f,
+             .max_abs_inertial_raw_delta_pixels = 0.0f},
+        /* synthetic= */ std::nullopt);
+    bool success2 = decision_queue_->ProcessFrameWithScrollUpdates(
+        updates2, DamagingFrame{.presentation_ts = MillisSinceEpoch(164)},
+        CreateBeginFrameArgs(MillisSinceEpoch(132)));
+    EXPECT_FALSE(success2);
+  }
+
+  // F3: Invalid real frame (because its args.frame_time is before F1's, i.e.
+  // it's not chronologically ordered).
+  {
+    std::unique_ptr<ScrollUpdateEventMetrics> earliest_event3 =
+        EventMetricsTestCreator().CreateGestureScrollUpdate({});
+    EXPECT_CALL(*result_consumer_, OnFrameResult(_, _)).Times(0);
+    ScrollUpdates updates4 =
+        ScrollUpdates(earliest_event3.get(),
+                      Real{.first_input_generation_ts = MillisSinceEpoch(119),
+                           .last_input_generation_ts = MillisSinceEpoch(127),
+                           .has_inertial_input = false,
+                           .abs_total_raw_delta_pixels = 5.0f,
+                           .max_abs_inertial_raw_delta_pixels = 0.0f},
+                      /* synthetic= */ std::nullopt);
+    bool success3 = decision_queue_->ProcessFrameWithScrollUpdates(
+        updates4, DamagingFrame{.presentation_ts = MillisSinceEpoch(164)},
+        CreateBeginFrameArgs(MillisSinceEpoch(115) /* wrong */));
+    EXPECT_FALSE(success3);
+  }
+
+  // F4: Invalid real frame (because its presentation_ts is before F1's, i.e.
+  // it's not chronologically ordered).
+  {
+    std::unique_ptr<ScrollUpdateEventMetrics> earliest_event4 =
+        EventMetricsTestCreator().CreateGestureScrollUpdate({});
+    EXPECT_CALL(*result_consumer_, OnFrameResult(_, _)).Times(0);
+    ScrollUpdates updates4 =
+        ScrollUpdates(earliest_event4.get(),
+                      Real{.first_input_generation_ts = MillisSinceEpoch(119),
+                           .last_input_generation_ts = MillisSinceEpoch(127),
+                           .has_inertial_input = false,
+                           .abs_total_raw_delta_pixels = 5.0f,
+                           .max_abs_inertial_raw_delta_pixels = 0.0f},
+                      /* synthetic= */ std::nullopt);
+    bool success4 = decision_queue_->ProcessFrameWithScrollUpdates(
+        updates4,
+        DamagingFrame{.presentation_ts = MillisSinceEpoch(147) /* wrong */},
+        CreateBeginFrameArgs(MillisSinceEpoch(132)));
+    EXPECT_FALSE(success4);
+  }
+
+  // F5: Valid real frame.
+  {
+    std::unique_ptr<ScrollUpdateEventMetrics> earliest_event5 =
+        EventMetricsTestCreator().CreateGestureScrollUpdate({});
+    EXPECT_CALL(*result_consumer_,
+                OnFrameResult(kHasNoMissedVsyncs, earliest_event5.get()));
+    ScrollUpdates updates5 =
+        ScrollUpdates(earliest_event5.get(),
+                      Real{.first_input_generation_ts = MillisSinceEpoch(119),
+                           .last_input_generation_ts = MillisSinceEpoch(127),
+                           .has_inertial_input = false,
+                           .abs_total_raw_delta_pixels = 5.0f,
+                           .max_abs_inertial_raw_delta_pixels = 0.0f},
+                      /* synthetic= */ std::nullopt);
+    bool success5 = decision_queue_->ProcessFrameWithScrollUpdates(
+        updates5, DamagingFrame{.presentation_ts = MillisSinceEpoch(164)},
+        CreateBeginFrameArgs(MillisSinceEpoch(132)));
+    EXPECT_TRUE(success5);
+  }
+
+  {
+    EXPECT_CALL(*result_consumer_, OnScrollEnded());
+    decision_queue_->OnScrollEnded();
+  }
+}
+
+}  // namespace
+}  // namespace cc
diff --git a/cc/metrics/scroll_jank_v4_processor.cc b/cc/metrics/scroll_jank_v4_processor.cc
index 049d720..bf1f3a0 100644
--- a/cc/metrics/scroll_jank_v4_processor.cc
+++ b/cc/metrics/scroll_jank_v4_processor.cc
@@ -4,16 +4,16 @@
 
 #include "cc/metrics/scroll_jank_v4_processor.h"
 
-#include <optional>
-#include <utility>
+#include <memory>
 #include <variant>
 
-#include "base/check.h"
-#include "base/time/time.h"
+#include "base/trace_event/trace_event.h"
 #include "cc/base/features.h"
 #include "cc/metrics/event_metrics.h"
+#include "cc/metrics/scroll_jank_v4_decision_queue.h"
 #include "cc/metrics/scroll_jank_v4_frame.h"
 #include "cc/metrics/scroll_jank_v4_frame_stage.h"
+#include "cc/metrics/scroll_jank_v4_histogram_emitter.h"
 #include "components/viz/common/frame_sinks/begin_frame_args.h"
 #include "third_party/abseil-cpp/absl/functional/overload.h"
 
@@ -21,11 +21,35 @@
 
 namespace {
 
-using ScrollDamage = ScrollJankV4Frame::ScrollDamage;
-using DamagingFrame = ScrollJankV4Frame::DamagingFrame;
+class ProcessorResultConsumer
+    : public ScrollJankV4DecisionQueue::ResultConsumer {
+ public:
+  void OnFrameResult(ScrollUpdateEventMetrics::ScrollJankV4Result result,
+                     ScrollUpdateEventMetrics* earliest_event) override {
+    bool counts_towards_histogram_frame_count =
+        result.is_damaging_frame ||
+        features::kCountNonDamagingFramesTowardsHistogramFrameCount.Get();
+    histogram_emitter_.OnFrameWithScrollUpdates(
+        result.missed_vsyncs_per_reason, counts_towards_histogram_frame_count);
+    if (earliest_event) {
+      CHECK(!earliest_event->scroll_jank_v4().has_value());
+      earliest_event->set_scroll_jank_v4(result);
+    }
+  }
+
+  void OnScrollStarted() override { histogram_emitter_.OnScrollStarted(); }
+
+  void OnScrollEnded() override { histogram_emitter_.OnScrollEnded(); }
+
+ private:
+  ScrollJankV4HistogramEmitter histogram_emitter_;
+};
 
 }  // namespace
 
+ScrollJankV4Processor::ScrollJankV4Processor()
+    : decision_queue_(std::make_unique<ProcessorResultConsumer>()) {}
+
 void ScrollJankV4Processor::ProcessEventsMetricsForPresentedFrame(
     EventMetrics::List& events_metrics,
     base::TimeTicks presentation_ts,
@@ -42,86 +66,41 @@
     ScrollJankV4FrameStage::List stages =
         ScrollJankV4FrameStage::CalculateStages(
             events_metrics, /* skip_non_damaging_events= */ true);
-    HandleFrame(stages, DamagingFrame(presentation_ts),
-                ScrollJankV4Frame::BeginFrameArgsForScrollJank::From(args),
-                /* counts_towards_histogram_frame_count= */ true);
+    HandleFrame(stages, ScrollJankV4Frame::DamagingFrame(presentation_ts),
+                ScrollJankV4Frame::BeginFrameArgsForScrollJank::From(args));
     return;
   }
 
   ScrollJankV4Frame::Timeline timeline = ScrollJankV4Frame::CalculateTimeline(
       events_metrics, args, presentation_ts);
-  bool count_non_damaging_frames_towards_histogram_frame_count =
-      features::kCountNonDamagingFramesTowardsHistogramFrameCount.Get();
   for (auto& frame : timeline) {
-    bool counts_towards_histogram_frame_count =
-        count_non_damaging_frames_towards_histogram_frame_count ||
-        std::holds_alternative<DamagingFrame>(frame.damage);
-    HandleFrame(frame.stages, frame.damage, frame.args,
-                counts_towards_histogram_frame_count);
+    HandleFrame(frame.stages, frame.damage, frame.args);
   }
 }
 
 void ScrollJankV4Processor::HandleFrame(
     ScrollJankV4FrameStage::List& stages,
-    const ScrollDamage& damage,
-    const ScrollJankV4Frame::BeginFrameArgsForScrollJank& args,
-    bool counts_towards_histogram_frame_count) {
+    const ScrollJankV4Frame::ScrollDamage& damage,
+    const ScrollJankV4Frame::BeginFrameArgsForScrollJank& args) {
   for (ScrollJankV4FrameStage& stage : stages) {
     std::visit(absl::Overload{
                    [&](ScrollJankV4FrameStage::ScrollStart& end) {
-                     HandleScrollStarted();
+                     decision_queue_.OnScrollStarted();
                    },
                    [&](ScrollJankV4FrameStage::ScrollUpdates& updates) {
-                     HandleFrameWithScrollUpdates(
-                         updates, damage, args,
-                         counts_towards_histogram_frame_count);
+                     if (!decision_queue_.ProcessFrameWithScrollUpdates(
+                             updates, damage, args)) {
+                       TRACE_EVENT(
+                           "input.scrolling",
+                           "ScrollJankV4Processor::HandleFrame: Invalid frame");
+                     }
                    },
                    [&](ScrollJankV4FrameStage::ScrollEnd& end) {
-                     HandleScrollEnded();
+                     decision_queue_.OnScrollEnded();
                    },
                },
                stage.stage);
   }
 }
 
-void ScrollJankV4Processor::HandleFrameWithScrollUpdates(
-    ScrollJankV4FrameStage::ScrollUpdates& updates,
-    const ScrollDamage& damage,
-    const ScrollJankV4Frame::BeginFrameArgsForScrollJank& args,
-    bool counts_towards_histogram_frame_count) {
-  // TODO(crbug.com/456180776): Handle synthetic inputs.
-  CHECK(updates.real().has_value());
-  CHECK(!updates.synthetic().has_value());
-  const ScrollJankV4FrameStage::ScrollUpdates::Real& real_updates =
-      *updates.real();
-  std::optional<ScrollUpdateEventMetrics::ScrollJankV4Result> result =
-      decider_.DecideJankForFrameWithScrollUpdates(
-          real_updates.first_input_generation_ts,
-          real_updates.last_input_generation_ts, damage, args,
-          real_updates.has_inertial_input,
-          real_updates.abs_total_raw_delta_pixels,
-          real_updates.max_abs_inertial_raw_delta_pixels);
-  if (!result.has_value()) {
-    return;
-  }
-
-  histogram_emitter_.OnFrameWithScrollUpdates(
-      result->missed_vsyncs_per_reason, counts_towards_histogram_frame_count);
-
-  if (ScrollUpdateEventMetrics* earliest_event = updates.earliest_event()) {
-    CHECK(!earliest_event->scroll_jank_v4().has_value());
-    earliest_event->set_scroll_jank_v4(std::move(result));
-  }
-}
-
-void ScrollJankV4Processor::HandleScrollStarted() {
-  decider_.OnScrollStarted();
-  histogram_emitter_.OnScrollStarted();
-}
-
-void ScrollJankV4Processor::HandleScrollEnded() {
-  decider_.OnScrollEnded();
-  histogram_emitter_.OnScrollEnded();
-}
-
 }  // namespace cc
diff --git a/cc/metrics/scroll_jank_v4_processor.h b/cc/metrics/scroll_jank_v4_processor.h
index d01c894..8df281f 100644
--- a/cc/metrics/scroll_jank_v4_processor.h
+++ b/cc/metrics/scroll_jank_v4_processor.h
@@ -5,13 +5,11 @@
 #ifndef CC_METRICS_SCROLL_JANK_V4_PROCESSOR_H_
 #define CC_METRICS_SCROLL_JANK_V4_PROCESSOR_H_
 
-#include "base/time/time.h"
 #include "cc/cc_export.h"
 #include "cc/metrics/event_metrics.h"
-#include "cc/metrics/scroll_jank_v4_decider.h"
+#include "cc/metrics/scroll_jank_v4_decision_queue.h"
 #include "cc/metrics/scroll_jank_v4_frame.h"
 #include "cc/metrics/scroll_jank_v4_frame_stage.h"
-#include "cc/metrics/scroll_jank_v4_histogram_emitter.h"
 
 namespace cc {
 
@@ -25,6 +23,8 @@
 // for more details about the scroll jank v4 metric.
 class CC_EXPORT ScrollJankV4Processor {
  public:
+  ScrollJankV4Processor();
+
   void ProcessEventsMetricsForPresentedFrame(EventMetrics::List& events_metrics,
                                              base::TimeTicks presentation_ts,
                                              const viz::BeginFrameArgs& args);
@@ -32,18 +32,9 @@
  private:
   void HandleFrame(ScrollJankV4FrameStage::List& stages,
                    const ScrollJankV4Frame::ScrollDamage& damage,
-                   const ScrollJankV4Frame::BeginFrameArgsForScrollJank& args,
-                   bool counts_towards_histogram_frame_count);
-  void HandleFrameWithScrollUpdates(
-      ScrollJankV4FrameStage::ScrollUpdates& updates,
-      const ScrollJankV4Frame::ScrollDamage& damage,
-      const ScrollJankV4Frame::BeginFrameArgsForScrollJank& args,
-      bool counts_towards_histograms);
-  void HandleScrollStarted();
-  void HandleScrollEnded();
+                   const ScrollJankV4Frame::BeginFrameArgsForScrollJank& args);
 
-  ScrollJankV4Decider decider_;
-  ScrollJankV4HistogramEmitter histogram_emitter_;
+  ScrollJankV4DecisionQueue decision_queue_;
 };
 
 }  // namespace cc
diff --git a/cc/paint/transfer_cache_unittest.cc b/cc/paint/transfer_cache_unittest.cc
index 26c4d101..ef78615c4 100644
--- a/cc/paint/transfer_cache_unittest.cc
+++ b/cc/paint/transfer_cache_unittest.cc
@@ -39,7 +39,6 @@
         /*enable_gpu_rasterization=*/true, nullptr, nullptr);
 
     ASSERT_EQ(result, gpu::ContextResult::kSuccess);
-    ASSERT_TRUE(context_->GetCapabilities().gpu_rasterization);
   }
 
   void TearDown() override { context_.reset(); }
diff --git a/chrome/MAJOR_BRANCH_DATE b/chrome/MAJOR_BRANCH_DATE
index ba88faa..8b60ad6 100644
--- a/chrome/MAJOR_BRANCH_DATE
+++ b/chrome/MAJOR_BRANCH_DATE
@@ -1 +1 @@
-MAJOR_BRANCH_DATE=2025-10-28
+MAJOR_BRANCH_DATE=2025-12-02
diff --git a/chrome/VERSION b/chrome/VERSION
index cf79d4a..45d2e152 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
-MAJOR=144
+MAJOR=145
 MINOR=0
-BUILD=7559
+BUILD=7560
 PATCH=0
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index 372b06a..a98156b 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -60,16 +60,9 @@
 }
 
 if (android_64bit_target_cpu) {
-  _main_monochrome_public_bundle_target = "monochrome_32_64_public_bundle"
   _main_trichrome_chrome_bundle_target = "trichrome_chrome_32_64_bundle"
   _main_trichrome_library_apk_target = "trichrome_library_32_64_apk"
-
-  # monochrome_apk is only 64-bit primary, we have no 32-bit primary version,
-  # as we don't ship it to users.
-  _main_monochrome_public_apk_target = "monochrome_64_32_public_apk"
 } else {
-  _main_monochrome_public_bundle_target = "monochrome_public_bundle"
-  _main_monochrome_public_apk_target = "monochrome_public_apk"
   _main_trichrome_chrome_bundle_target = "trichrome_chrome_bundle"
   _main_trichrome_library_apk_target = "trichrome_library_apk"
 }
@@ -808,6 +801,7 @@
       ":resource_id_javagen",
       "//chrome:offline_pages_enum_javagen",
       "//chrome/browser:screenshot_mode_enum",
+      "//chrome/browser/media/webrtc:java_enums",
       "//chrome/browser/notifications/scheduler/public:jni_enums",
       "//components/dom_distiller/core:distiller_type_java",
       "//components/ntp_tiles:ntp_tiles_enums_java",
@@ -2213,26 +2207,11 @@
     group("trichrome_library_apk") {
       deps = [ ":trichrome_library_64_apk" ]
     }
-    group("monochrome_public_apk") {
-      deps = [ ":monochrome_64_public_apk" ]
-    }
   } else {
     if (android_64bit_target_cpu) {
       alias_with_wrapper_script("trichrome_library_apk") {
         alias_target = ":trichrome_library_64_32_apk"
       }
-      alias_with_wrapper_script("monochrome_public_apk") {
-        alias_target = ":monochrome_64_32_public_apk"
-      }
-    }
-    chrome_public_apk_or_module_tmpl(_main_monochrome_public_apk_target) {
-      is_monochrome = true
-      apk_name = "MonochromePublic"
-      target_type = "android_apk"
-      if (android_64bit_target_cpu) {
-        is_64_bit_browser = true
-        include_32_bit_webview = true
-      }
     }
 
     trichrome_library_apk_tmpl(_main_trichrome_library_apk_target) {
@@ -2541,28 +2520,6 @@
     extra_args = _common_smoke_test_args
   }
 
-  # Public webview targets don't work with non-public sdks.
-  # https://crbug.com/1000763
-  instrumentation_test_runner("monochrome_public_smoke_test") {
-    if (android_64bit_target_cpu && !defined(android_app_secondary_abi)) {
-      apk_under_test = ":monochrome_64_public_apk"
-      if (!is_java_debug) {
-        proguard_mapping_path =
-            "$root_build_dir/apks/MonochromePublic64.apk.mapping"
-      }
-    } else {
-      apk_under_test = ":$_main_monochrome_public_apk_target"
-      if (!is_java_debug) {
-        proguard_mapping_path =
-            "$root_build_dir/apks/MonochromePublic.apk.mapping"
-      }
-    }
-    android_test_apk = ":chrome_smoke_test_apk"
-    additional_apks = [ "//net/android:net_test_support_apk" ]
-    never_incremental = true
-    extra_args = _common_smoke_test_args
-  }
-
   android_test_apk("chrome_bundle_smoke_test_apk") {
     apk_name = "ChromeBundleSmokeTest"
     android_manifest = "javatests/src/org/chromium/chrome/test/smoke/AndroidManifest_bundle.xml"
@@ -2604,34 +2561,6 @@
     "--disable-fre",
   ]
 
-  instrumentation_test_runner("monochrome_public_bundle_smoke_test") {
-    if (android_64bit_target_cpu) {
-      if (!defined(android_app_secondary_abi)) {
-        apk_under_test = "//chrome/android:monochrome_64_public_bundle_apks"
-        if (!is_java_debug) {
-          proguard_mapping_path =
-              "$root_build_dir/apks/MonochromePublic64.aab.mapping"
-        }
-      } else {
-        apk_under_test = "//chrome/android:monochrome_64_32_public_bundle_apks"
-        if (!is_java_debug) {
-          proguard_mapping_path =
-              "$root_build_dir/apks/MonochromePublic6432.aab.mapping"
-        }
-      }
-    } else {
-      apk_under_test = "//chrome/android:monochrome_public_bundle_apks"
-      if (!is_java_debug) {
-        proguard_mapping_path =
-            "$root_build_dir/apks/MonochromePublic.aab.mapping"
-      }
-    }
-    android_test_apk = ":chrome_bundle_smoke_test_apk"
-    never_incremental = true
-    modules = [ "test_dummy" ]
-    extra_args = _bundle_smoke_test_extra_args
-  }
-
   _verify_32bit_static_initializers =
       defined(expected_static_initializer_32bit_count) &&
       defined(android_app_secondary_abi)
@@ -2781,34 +2710,16 @@
   }
 
   if (android_64bit_target_cpu && !defined(android_app_secondary_abi)) {
-    alias_with_wrapper_script("monochrome_public_bundle") {
-      alias_target = ":monochrome_64_public_bundle"
-    }
     alias_with_wrapper_script("trichrome_chrome_bundle") {
       alias_target = ":trichrome_chrome_64_bundle"
     }
   } else {
     if (android_64bit_target_cpu) {
-      alias_with_wrapper_script("monochrome_public_bundle") {
-        alias_target = ":monochrome_64_32_public_bundle"
-      }
       alias_with_wrapper_script("trichrome_chrome_bundle") {
         alias_target = ":trichrome_chrome_64_32_bundle"
       }
     }
 
-    # Public webview targets don't work with non-public sdks.
-    # https://crbug.com/1000763
-    chrome_public_bundle(_main_monochrome_public_bundle_target) {
-      is_monochrome = true
-      bundle_name = "MonochromePublic"
-
-      if (android_64bit_target_cpu) {
-        is_64_bit_browser = false
-        include_64_bit_webview = true
-      }
-    }
-
     chrome_public_bundle(_main_trichrome_chrome_bundle_target) {
       is_trichrome = true
       bundle_name = "TrichromeChrome"
@@ -3010,13 +2921,6 @@
   }
 
   if (android_64bit_target_cpu) {
-    chrome_public_bundle("monochrome_64_public_bundle") {
-      is_monochrome = true
-      bundle_name = "MonochromePublic64"
-      is_64_bit_browser = true
-      include_32_bit_webview = false
-    }
-
     chrome_public_bundle("trichrome_chrome_64_bundle") {
       is_trichrome = true
       bundle_name = "TrichromeChrome64"
@@ -3034,20 +2938,6 @@
     }
 
     if (defined(android_app_secondary_abi)) {
-      chrome_public_bundle("monochrome_32_public_bundle") {
-        is_monochrome = true
-        bundle_name = "MonochromePublic32"
-        is_64_bit_browser = false
-        include_64_bit_webview = false
-      }
-
-      chrome_public_bundle("monochrome_64_32_public_bundle") {
-        is_monochrome = true
-        bundle_name = "MonochromePublic6432"
-        is_64_bit_browser = true
-        include_32_bit_webview = true
-      }
-
       chrome_public_bundle("trichrome_chrome_64_32_bundle") {
         is_trichrome = true
         bundle_name = "TrichromeChrome6432"
@@ -3489,14 +3379,14 @@
     }
 
     defines = []
+    java_targets = [
+      "//chrome/android:chrome_public_bundle",
+      "//android_webview:system_webview_bundle",  # Needed to support trichrome.
+    ]
     if (android_64bit_target_cpu) {
-      java_targets = [ "//chrome/android:monochrome_64_public_bundle" ]
-
       # To be compatible with secondary ABI library.
       priority_java_targets = [ "//android_webview:system_webview_64_bundle" ]
       never_omit_switch_num = true
-    } else {
-      java_targets = [ "//chrome/android:monochrome_public_bundle" ]
     }
 
     is_monochrome = true
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 6c5478c..349ece3b 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
@@ -300,7 +300,7 @@
         @StringRes
         int titleId =
                 (mCreationMode == CreationMode.ITEM_PICKER)
-                        ? R.string.tab_selection_editor_toolbar_add_tabs
+                        ? R.string.tab_selection_editor_toolbar_add_recent_tabs
                         : R.string.tab_selection_editor_toolbar_select_items;
         mModel.set(TabListEditorProperties.TOOLBAR_TITLE, mContext.getString(titleId));
 
diff --git a/chrome/android/features/tab_ui/java/strings/android_chrome_tab_ui_strings.grd b/chrome/android/features/tab_ui/java/strings/android_chrome_tab_ui_strings.grd
index c9923f1d..d28a4e0 100644
--- a/chrome/android/features/tab_ui/java/strings/android_chrome_tab_ui_strings.grd
+++ b/chrome/android/features/tab_ui/java/strings/android_chrome_tab_ui_strings.grd
@@ -241,8 +241,8 @@
       <message name="IDS_TAB_SELECTION_EDITOR_TOOLBAR_SELECT_ITEMS" desc="Label shown on Tab Selection Editor Toolbar asking user to select tabs and tab groups denoted as items from the list.">
         Select items
       </message>
-      <message name="IDS_TAB_SELECTION_EDITOR_TOOLBAR_ADD_TABS" desc="Label shown on Tab Selection Editor Toolbar asking user to add tabs as context for the ChromeItemPickerActivity.">
-        Add tabs
+      <message name="IDS_TAB_SELECTION_EDITOR_TOOLBAR_ADD_RECENT_TABS" desc="Label shown on Tab Selection Editor Toolbar asking user to add tabs as context for the ChromeItemPickerActivity.">
+        Add recent tabs
       </message>
       <message name="IDS_ACCESSIBILITY_TAB_SELECTION_EDITOR_BACK_BUTTON" desc="Accessibility string for the back button in the Tab Selection Editor Toolbar. Tap this button to exit out of the multi-select mode">
         Hide multi-select mode
diff --git a/chrome/android/features/tab_ui/java/strings/android_chrome_tab_ui_strings_grd/IDS_TAB_SELECTION_EDITOR_TOOLBAR_ADD_RECENT_TABS.png.sha1 b/chrome/android/features/tab_ui/java/strings/android_chrome_tab_ui_strings_grd/IDS_TAB_SELECTION_EDITOR_TOOLBAR_ADD_RECENT_TABS.png.sha1
new file mode 100644
index 0000000..73732912
--- /dev/null
+++ b/chrome/android/features/tab_ui/java/strings/android_chrome_tab_ui_strings_grd/IDS_TAB_SELECTION_EDITOR_TOOLBAR_ADD_RECENT_TABS.png.sha1
@@ -0,0 +1 @@
+0ac1e77ea58d242706a0fe2d6eccedae8eda76d5
\ No newline at end of file
diff --git a/chrome/android/features/tab_ui/java/strings/android_chrome_tab_ui_strings_grd/IDS_TAB_SELECTION_EDITOR_TOOLBAR_ADD_TABS.png.sha1 b/chrome/android/features/tab_ui/java/strings/android_chrome_tab_ui_strings_grd/IDS_TAB_SELECTION_EDITOR_TOOLBAR_ADD_TABS.png.sha1
deleted file mode 100644
index 156f614..0000000
--- a/chrome/android/features/tab_ui/java/strings/android_chrome_tab_ui_strings_grd/IDS_TAB_SELECTION_EDITOR_TOOLBAR_ADD_TABS.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-1ac9250078c76d9ef165cbf1e73d2b20a980320e
\ No newline at end of file
diff --git a/chrome/android/java/res/layout/tips_opt_in_bottom_sheet.xml b/chrome/android/java/res/layout/tips_opt_in_bottom_sheet.xml
index 4cf361ac..b3a756a 100644
--- a/chrome/android/java/res/layout/tips_opt_in_bottom_sheet.xml
+++ b/chrome/android/java/res/layout/tips_opt_in_bottom_sheet.xml
@@ -30,6 +30,7 @@
             android:focusable="true">
 
             <ImageView
+                android:id="@+id/opt_in_logo"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:layout_gravity="center_horizontal"
diff --git a/chrome/android/java/res/values/dimens.xml b/chrome/android/java/res/values/dimens.xml
index 82cfd15b..46e3266 100644
--- a/chrome/android/java/res/values/dimens.xml
+++ b/chrome/android/java/res/values/dimens.xml
@@ -359,6 +359,7 @@
     <dimen name="settings_narrow_header_width">360dp</dimen>
     <dimen name="settings_wide_header_width">412dp</dimen>
     <dimen name="settings_search_ui_height">48dp</dimen>
+    <dimen name="settings_search_ui_bottom_margin">4dp</dimen>
     <dimen name="settings_search_icon_padding">16dp</dimen>
     <dimen name="settings_detailed_title_padding">10dp</dimen>
     <dimen name="settings_detailed_title_height">48dp</dimen>
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 cac3118c..d1ca02a 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
@@ -180,27 +180,16 @@
         mTabModelSelector = tabModelSelector;
         mWindowTag = windowTag;
         mTabCreatorManager = tabCreatorManager;
+
+        tabModelSelector.getModel(false).addObserver(mTabModelObserver);
+        tabModelSelector.getModel(true).addObserver(mTabModelObserver);
     }
 
     @Override
     public void onNativeLibraryReady() {
-        mTabModelSelector.getModel(false).addObserver(mTabModelObserver);
-        mTabModelSelector.getModel(true).addObserver(mTabModelObserver);
-        if (ChromeFeatureList.sTabStorageSqlitePrototypeAuthoritativeReadSource.getValue()) {
-            catchUpAndBeginTracking();
-        }
-    }
-
-    private void catchUpAndBeginTracking() {
-        assert mTabRegistrationObserver == null;
-        mTabRegistrationObserver = new TabModelSelectorTabRegistrationObserver(mTabModelSelector);
-        mTabRegistrationObserver.addObserverAndNotifyExistingTabRegistration(
-                new InnerRegistrationObserver());
-
-        // TODO(https://crbug.com/451614469): Watch for incognito as well eventually. But before
-        // things are fully functional, do not write any incognito data to avoid regressing on
-        // privacy.
-        initVisualDataTracking(false);
+        // Native is already initialized in the constructor as the TabStateStorageService requires
+        // native. This method is never called.
+        assert false;
     }
 
     @Override
@@ -402,6 +391,7 @@
         if (ChromeFeatureList.sTabStorageSqlitePrototypeAuthoritativeReadSource.getValue()) {
             TabGroupVisualDataStore.cacheGroups(data.getGroupsData());
             initRestoreOrchestrator(data);
+            beginTracking();
         }
 
         // TODO(ckitagawa): Change back to assert if the `mIsDestroyed` check is sufficient.
@@ -410,6 +400,18 @@
         }
     }
 
+    private void beginTracking() {
+        assert mTabRegistrationObserver == null;
+        mTabRegistrationObserver = new TabModelSelectorTabRegistrationObserver(mTabModelSelector);
+        mTabRegistrationObserver.addObserverAndNotifyExistingTabRegistration(
+                new InnerRegistrationObserver());
+
+        // TODO(https://crbug.com/451614469): Watch for incognito as well eventually. But before
+        // things are fully functional, do not write any incognito data to avoid regressing on
+        // privacy.
+        initVisualDataTracking(false);
+    }
+
     private void onFinishedCreatingAllTabs() {
         deleteDbIfNonAuthoritative();
 
@@ -424,7 +426,7 @@
         }
 
         if (!ChromeFeatureList.sTabStorageSqlitePrototypeAuthoritativeReadSource.getValue()) {
-            catchUpAndBeginTracking();
+            beginTracking();
         }
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/chrome_item_picker/TabItemPickerCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/chrome_item_picker/TabItemPickerCoordinator.java
index fb407b6..8425ea7 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/chrome_item_picker/TabItemPickerCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/chrome_item_picker/TabItemPickerCoordinator.java
@@ -14,6 +14,7 @@
 import org.chromium.base.Callback;
 import org.chromium.base.CallbackController;
 import org.chromium.base.CallbackUtils;
+import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.supplier.ObservableSupplier;
 import org.chromium.base.supplier.ObservableSupplierImpl;
 import org.chromium.base.supplier.OneshotSupplier;
@@ -195,7 +196,7 @@
         for (Tab tab : allTabs) {
             // TODO(crbug.com/458152854): Allow reloading of tabs.
             boolean isActiveOrCachedTab =
-                    tab.getWebContents() != null || cachedTabIdsSet.contains(tab.getId());
+                    FuseboxTabUtils.isTabActive(tab) || cachedTabIdsSet.contains(tab.getId());
             if (FuseboxTabUtils.isTabEligibleForAttachment(tab) && isActiveOrCachedTab) {
                 tabsToShow.add(tab);
             }
@@ -208,6 +209,9 @@
 
         TabListEditorController controller = mTabListEditorCoordinator.getController();
 
+        RecordHistogram.recordCount100Histogram(
+                "Android.TabItemPicker.SelectableTabs.Count", tabs.size());
+
         if (mActivity instanceof ComponentActivity componentActivity) {
             // Add the callback to the Dispatcher
             componentActivity
@@ -217,7 +221,11 @@
 
         controller.getHandleBackPressChangedSupplier().addObserver(mBackPressEnabledObserver);
 
-        int currentTabIndex = mTabModelSelector.getModel(/* incognito= */ false).index();
+        Profile profile = mProfileSupplier.get();
+        int currentTabIndex =
+                mTabModelSelector
+                        .getModel(profile == null ? false : profile.isIncognitoBranded())
+                        .index();
         RecyclerViewPosition position = new RecyclerViewPosition(currentTabIndex, 0);
 
         controller.show(
@@ -274,6 +282,8 @@
 
         @Override
         public void finishSelection(List<TabListEditorItemSelectionId> selectedItems) {
+            RecordHistogram.recordCount100Histogram(
+                    "Android.TabItemPicker.SelectedTabs.Count", selectedItems.size());
             mController.hideByAction();
 
             // Route the result to the Activity's success handler.
@@ -288,10 +298,12 @@
     /** Creates a TabGroupModelFilter instance required by the TabListEditorCoordinator. */
     private ObservableSupplier<@Nullable TabGroupModelFilter> createTabGroupModelFilterSupplier(
             TabModelSelector tabModelSelector) {
+        Profile profile = mProfileSupplier.get();
         return new ObservableSupplierImpl<@Nullable TabGroupModelFilter>(
                 tabModelSelector
                         .getTabGroupModelFilterProvider()
-                        .getTabGroupModelFilter(/* isIncognito= */ false));
+                        .getTabGroupModelFilter(
+                                profile == null ? false : profile.isIncognitoBranded()));
     }
 
     /** Creates a TabContentManager instance required by the TabListEditorCoordinator. */
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/TabStripContextMenuCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/TabStripContextMenuCoordinator.java
index 13b7a3cb..3f5816cb 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/TabStripContextMenuCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/TabStripContextMenuCoordinator.java
@@ -113,7 +113,7 @@
     private void configureMenuItems(ModelList itemList, boolean isIncognito) {
         // Add "Name window" option.
         if (MultiWindowUtils.isMultiInstanceApi31Enabled()
-                && ChromeFeatureList.isEnabled(ChromeFeatureList.ROBUST_WINDOW_MANAGEMENT)) {
+                && ChromeFeatureList.sRobustWindowManagement.isEnabled()) {
             itemList.add(
                     new ListItemBuilder()
                             .withTitleRes(R.string.menu_name_window)
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/hub/HubLayoutPublicTransitTest.java b/chrome/android/java/src/org/chromium/chrome/browser/hub/HubLayoutPublicTransitTest.java
index 046718a..147efb6 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/hub/HubLayoutPublicTransitTest.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/hub/HubLayoutPublicTransitTest.java
@@ -110,6 +110,8 @@
     @Test
     @LargeTest
     @EnableFeatures(ChromeFeatureList.TAB_GROUP_ENTRY_POINTS_ANDROID)
+    // TODO(crbug.com/461916575): Test disabled for Incognito windowing, delete once fixed
+    @DisableFeatures(ChromeFeatureList.ANDROID_OPEN_INCOGNITO_AS_WINDOW)
     public void testTabGroupPane_newTabGroup() {
         WebPageStation firstPage = mCtaTestRule.startOnBlankPage();
         int firstTabId = firstPage.loadedTabElement.value().getId();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/MediaCapturePickerDialog.java b/chrome/android/java/src/org/chromium/chrome/browser/media/MediaCapturePickerDialog.java
index cf3931e2..c57a5da 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/media/MediaCapturePickerDialog.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/media/MediaCapturePickerDialog.java
@@ -134,7 +134,8 @@
         mPositiveButton = mButtonsView.findViewById(R.id.positive_button);
         mScreenButton = mButtonsView.findViewById(R.id.screen_button);
 
-        if (params.captureThisTab) {
+        if (params.captureThisTab
+                || params.allowedCaptureLevel < AllowedScreenCaptureLevel.WINDOW) {
             mScreenButton.setVisibility(View.GONE);
         }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/MediaCapturePickerManager.java b/chrome/android/java/src/org/chromium/chrome/browser/media/MediaCapturePickerManager.java
index baff791b..0de849b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/media/MediaCapturePickerManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/media/MediaCapturePickerManager.java
@@ -76,6 +76,9 @@
         /** True if screen sharing should be excluded. From MediaStreamRequest. */
         public final boolean excludeMonitorTypeSurfaces;
 
+        /** The allowed capture level. From DesktopMediaPicker::Params. */
+        public final @AllowedScreenCaptureLevel int allowedCaptureLevel;
+
         /**
          * @param webContents The {@link WebContents} to show the dialog on behalf of.
          * @param appName Name of the app that wants to share content.
@@ -87,6 +90,7 @@
          * @param captureThisTab True if we are just capturing this tab.
          * @param excludeSelfBrowserSurface True if the current tab should be excluded.
          * @param excludeMonitorTypeSurfaces True if screen sharing should be excluded.
+         * @param allowedCaptureLevel The allowed capture level.
          */
         public Params(
                 WebContents webContents,
@@ -98,7 +102,8 @@
                 @PreferredDisplaySurface.EnumType int preferredDisplaySurface,
                 boolean captureThisTab,
                 boolean excludeSelfBrowserSurface,
-                boolean excludeMonitorTypeSurfaces) {
+                boolean excludeMonitorTypeSurfaces,
+                @AllowedScreenCaptureLevel int allowedCaptureLevel) {
             this.webContents = webContents;
             this.appName = appName;
             this.targetName = targetName;
@@ -109,6 +114,7 @@
             this.captureThisTab = captureThisTab;
             this.excludeSelfBrowserSurface = excludeSelfBrowserSurface;
             this.excludeMonitorTypeSurfaces = excludeMonitorTypeSurfaces;
+            this.allowedCaptureLevel = allowedCaptureLevel;
         }
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/MediaCapturePickerManagerBridge.java b/chrome/android/java/src/org/chromium/chrome/browser/media/MediaCapturePickerManagerBridge.java
index 4ccef948..274b2a98 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/media/MediaCapturePickerManagerBridge.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/media/MediaCapturePickerManagerBridge.java
@@ -56,7 +56,8 @@
             @PreferredDisplaySurface.EnumType int preferredDisplaySurface,
             boolean captureThisTab,
             boolean excludeSelfBrowserSurface,
-            boolean excludeMonitorTypeSurfaces) {
+            boolean excludeMonitorTypeSurfaces,
+            @AllowedScreenCaptureLevel int allowedCaptureLevel) {
         MediaCapturePickerManager.Params params =
                 new MediaCapturePickerManager.Params(
                         webContents,
@@ -68,7 +69,8 @@
                         preferredDisplaySurface,
                         captureThisTab,
                         excludeSelfBrowserSurface,
-                        excludeMonitorTypeSurfaces);
+                        excludeMonitorTypeSurfaces,
+                        allowedCaptureLevel);
         MediaCapturePickerManager.showDialog(params, /* delegate= */ this);
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/multiwindow/MultiInstanceManagerApi31.java b/chrome/android/java/src/org/chromium/chrome/browser/multiwindow/MultiInstanceManagerApi31.java
index b53c1a9d..a006ef90 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/multiwindow/MultiInstanceManagerApi31.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/multiwindow/MultiInstanceManagerApi31.java
@@ -370,7 +370,24 @@
     @Override
     public void moveTabGroupToOtherWindow(
             TabGroupMetadata tabGroupMetadata, @NewWindowAppSource int source) {
-        if (MultiWindowUtils.getInstanceCountWithFallback(PersistedInstanceType.ACTIVE) == 1) {
+        // TODO(crbug.com/465141949): Add unit tests.
+        // Check the number of instances that the tab group is able to move into.
+        int instanceCount =
+                MultiWindowUtils.getInstanceCountWithFallback(PersistedInstanceType.ACTIVE);
+        @PersistedInstanceType int instanceType = PersistedInstanceType.ANY;
+        if (IncognitoUtils.shouldOpenIncognitoAsWindow()) {
+            if (tabGroupMetadata.isIncognito) {
+                instanceCount = MultiWindowUtils.getIncognitoInstanceCount(/* activeOnly= */ true);
+                instanceType = PersistedInstanceType.ACTIVE | PersistedInstanceType.OFF_THE_RECORD;
+            } else {
+                instanceCount =
+                        MultiWindowUtils.getInstanceCountWithFallback(
+                                PersistedInstanceType.ACTIVE | PersistedInstanceType.REGULAR);
+                instanceType = PersistedInstanceType.ACTIVE | PersistedInstanceType.REGULAR;
+            }
+        }
+
+        if (instanceCount <= 1) {
             moveTabGroupToNewWindow(tabGroupMetadata, source);
             return;
         }
@@ -386,7 +403,7 @@
                     // Close the source instance window, if needed.
                     closeChromeWindowIfEmpty(mInstanceId);
                 },
-                getInstanceInfo(),
+                getInstanceInfo(instanceType),
                 UiUtils.isInstanceSwitcherV2Enabled()
                         ? R.string.menu_move_group_to_other_window
                         : R.string.menu_move_to_other_window);
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 524cf5d..553120fd 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
@@ -4,7 +4,9 @@
 
 package org.chromium.chrome.browser.notifications.tips;
 
+import android.content.ComponentCallbacks;
 import android.content.Context;
+import android.content.res.Configuration;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.widget.ScrollView;
@@ -53,8 +55,22 @@
 
     // LINT.ThenChange(//tools/metrics/histograms/metadata/notifications/enums.xml:TipsNotificationsOptInPromoEventType)
 
+    private final ComponentCallbacks mComponentCallbacks =
+            new ComponentCallbacks() {
+                @Override
+                public void onConfigurationChanged(Configuration configuration) {
+                    TipsUtils.scaleBottomSheetImageLogoByWidth(
+                            mContext, configuration, mContentView, R.id.opt_in_logo);
+                }
+
+                @Override
+                public void onLowMemory() {}
+            };
+
+    private final Context mContext;
     private final BottomSheetController mBottomSheetController;
     private final TipsOptInSheetContent mSheetContent;
+    private final View mContentView;
 
     /**
      * Constructor.
@@ -63,31 +79,38 @@
      * @param bottomSheetController The system {@link BottomSheetController}.
      */
     public TipsOptInCoordinator(Context context, BottomSheetController bottomSheetController) {
+        mContext = context;
         mBottomSheetController = bottomSheetController;
 
-        View contentView =
-                LayoutInflater.from(context)
+        mContentView =
+                LayoutInflater.from(mContext)
                         .inflate(R.layout.tips_opt_in_bottom_sheet, /* root= */ null);
-        mSheetContent = new TipsOptInSheetContent(contentView, bottomSheetController);
+        mSheetContent = new TipsOptInSheetContent(mContentView, bottomSheetController);
 
-        ButtonCompat positiveButtonView = contentView.findViewById(R.id.opt_in_positive_button);
+        ButtonCompat positiveButtonView = mContentView.findViewById(R.id.opt_in_positive_button);
         positiveButtonView.setOnClickListener(
                 (view) -> {
-                    TipsUtils.launchTipsNotificationsSettings(context);
+                    TipsUtils.launchTipsNotificationsSettings(mContext);
                     mBottomSheetController.hideContent(mSheetContent, /* animate= */ true);
                     recordOptInPromoEventType(OptInPromoEventType.ACCEPTED);
                 });
 
-        ButtonCompat negativeButtonView = contentView.findViewById(R.id.opt_in_negative_button);
+        ButtonCompat negativeButtonView = mContentView.findViewById(R.id.opt_in_negative_button);
         negativeButtonView.setOnClickListener(
                 (view) -> {
                     mBottomSheetController.hideContent(mSheetContent, /* animate= */ true);
                     recordOptInPromoEventType(OptInPromoEventType.IGNORED);
                 });
+
+        // Fire an event for the original setup.
+        mComponentCallbacks.onConfigurationChanged(mContext.getResources().getConfiguration());
+        mContext.registerComponentCallbacks(mComponentCallbacks);
     }
 
     /** Cleans up resources. */
-    public void destroy() {}
+    public void destroy() {
+        mContext.unregisterComponentCallbacks(mComponentCallbacks);
+    }
 
     /** Shows the promo. The caller is responsible for all eligibility checks. */
     public void showBottomSheet() {
@@ -230,4 +253,12 @@
     TipsOptInSheetContent getBottomSheetContentForTesting() {
         return mSheetContent;
     }
+
+    View getViewForTesting() {
+        return mContentView;
+    }
+
+    void triggerConfigurationChangeForTesting(Configuration configuration) {
+        mComponentCallbacks.onConfigurationChanged(configuration);
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/notifications/tips/TipsPromoCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/notifications/tips/TipsPromoCoordinator.java
index bbdd8e1..49ef36a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/notifications/tips/TipsPromoCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/notifications/tips/TipsPromoCoordinator.java
@@ -4,8 +4,10 @@
 
 package org.chromium.chrome.browser.notifications.tips;
 
+import android.content.ComponentCallbacks;
 import android.content.Context;
 import android.content.Intent;
+import android.content.res.Configuration;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.widget.ImageView;
@@ -77,6 +79,18 @@
 
     // LINT.ThenChange(//tools/metrics/histograms/metadata/notifications/enums.xml:TipsNotificationsFeatureTipPromoEventType)
 
+    private final ComponentCallbacks mComponentCallbacks =
+            new ComponentCallbacks() {
+                @Override
+                public void onConfigurationChanged(Configuration configuration) {
+                    TipsUtils.scaleBottomSheetImageLogoByWidth(
+                            mContext, configuration, mContentView, R.id.main_page_logo);
+                }
+
+                @Override
+                public void onLowMemory() {}
+            };
+
     public static final int INVALID_TIPS_NOTIFICATION_FEATURE_TYPE = -1;
 
     private final Context mContext;
@@ -138,11 +152,16 @@
                                 mPropertyModel.get(TipsPromoProperties.CURRENT_SCREEN));
                     }
                 });
+
+        // Fire an event for the original setup.
+        mComponentCallbacks.onConfigurationChanged(mContext.getResources().getConfiguration());
+        mContext.registerComponentCallbacks(mComponentCallbacks);
     }
 
     /** Cleans up resources. */
     public void destroy() {
         mChangeProcessor.destroy();
+        mContext.unregisterComponentCallbacks(mComponentCallbacks);
     }
 
     /** Shows the promo. The caller is responsible for all eligibility checks. */
@@ -462,4 +481,8 @@
     void setLensControllerForTesting(LensController lensController) {
         mLensController = lensController;
     }
+
+    void triggerConfigurationChangeForTesting(Configuration configuration) {
+        mComponentCallbacks.onConfigurationChanged(configuration);
+    }
 }
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 7e49ab86..22f6968 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
@@ -7,11 +7,17 @@
 import android.app.NotificationManager;
 import android.content.Context;
 import android.content.Intent;
+import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.provider.Settings;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
 
 import androidx.annotation.DrawableRes;
+import androidx.annotation.IdRes;
 import androidx.annotation.StringRes;
+import androidx.annotation.VisibleForTesting;
 
 import org.chromium.base.Callback;
 import org.chromium.base.supplier.OneshotSupplier;
@@ -36,6 +42,7 @@
 import org.chromium.components.browser_ui.notifications.NotificationProxyUtils;
 import org.chromium.components.browser_ui.notifications.channels.ChannelsInitializer;
 import org.chromium.components.user_prefs.UserPrefs;
+import org.chromium.ui.base.ViewUtils;
 import org.chromium.ui.base.WindowAndroid;
 
 import java.util.ArrayList;
@@ -45,6 +52,8 @@
 /** Static utilities for Tips Notifications. */
 @NullMarked
 public class TipsUtils {
+    @VisibleForTesting public static final float LOGO_IMAGE_MAX_WIDTH_RATIO = 0.45f;
+
     // LINT.IfChange(TipsShownPrefs)
     public static final String ENHANCED_SAFE_BROWSING_SHOWN =
             "android.tips.notifications.esb_shown";
@@ -90,7 +99,7 @@
                 mainPageTitleRes = R.string.tips_promo_bottom_sheet_title_quick_delete;
                 mainPageDescriptionRes = R.string.tips_promo_bottom_sheet_description_quick_delete;
                 mainPageLogoViewRes = R.drawable.tips_promo_quick_delete_logo;
-                detailPageTitleRes = R.string.tips_promo_bottom_sheet_title_quick_delete;
+                detailPageTitleRes = R.string.tips_promo_bottom_sheet_title_quick_delete_short;
                 detailPageSteps.add(
                         context.getString(
                                 R.string.tips_promo_bottom_sheet_first_step_quick_delete));
@@ -307,4 +316,32 @@
         UserPrefs.get(profile).setBoolean(GOOGLE_LENS_SHOWN, false);
         UserPrefs.get(profile).setBoolean(BOTTOM_OMNIBOX_SHOWN, false);
     }
+
+    /**
+     * Scale the image logo for the bottom sheet by width based on orientation. The bottom sheet
+     * logo images are designed for portrait mode to match parent width and when scaled for
+     * landscape mode/split screen are too large. A ratio is applied based on the screen width to
+     * scale it down to a reasonable size.
+     *
+     * @param context The current context.
+     * @param configuration The current configuration of the device.
+     * @param contentView The content view of the bottom sheet that holds the image.
+     * @param logoRes The resource id of the image logo to be scaled.
+     */
+    public static void scaleBottomSheetImageLogoByWidth(
+            Context context, Configuration configuration, View contentView, @IdRes int logoRes) {
+        // While the logic does not need to be applied to LFFs, detection for tablets is
+        // dependent on minimum width which is the same as the detection for landscape
+        // mode and exclusion would nullify the effects, so this applies to all devices.
+        int screenWidthDp = configuration.screenWidthDp;
+        int screenWidthPixels = ViewUtils.dpToPx(context, screenWidthDp);
+        ImageView logoView = contentView.findViewById(logoRes);
+        ViewGroup.LayoutParams layoutParams = logoView.getLayoutParams();
+
+        layoutParams.width =
+                configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
+                        ? Math.round(screenWidthPixels * LOGO_IMAGE_MAX_WIDTH_RATIO)
+                        : ViewGroup.LayoutParams.MATCH_PARENT;
+        logoView.setLayoutParams(layoutParams);
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/settings/search/SettingsSearchCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/settings/search/SettingsSearchCoordinator.java
index d2402227..ddc5153e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/settings/search/SettingsSearchCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/settings/search/SettingsSearchCoordinator.java
@@ -172,6 +172,7 @@
         LayoutInflater.from(mActivity).inflate(R.layout.settings_search_box, searchBoxParent, true);
         LayoutInflater.from(mActivity).inflate(R.layout.settings_search_query, actionBar, true);
         View searchBox = mActivity.findViewById(R.id.search_box);
+        setSearchBoxBottomMargin(searchBox, mUseMultiColumn);
         searchBox.setOnClickListener(v -> enterSearchState());
 
         if (mMultiColumnSettings != null) {
@@ -515,6 +516,7 @@
         View query = mActivity.findViewById(R.id.search_query_container);
         if (mUseMultiColumn) {
             ViewGroup actionBar = mActivity.findViewById(R.id.action_bar);
+            setSearchBoxBottomMargin(searchBox, true);
             assumeNonNull(actionBar).addView(searchBox);
             if (mFragmentState == FS_RESULTS) {
                 // Make the query edit UI visible which was hidden in single-column mode.
@@ -523,6 +525,7 @@
         } else {
             // Search bar goes beneath the toolbar (app_bar_layout) in single-column layout.
             ViewGroup appBarLayout = mActivity.findViewById(R.id.app_bar_layout);
+            setSearchBoxBottomMargin(searchBox, false);
             appBarLayout.addView(searchBox);
 
             // Query edit UI should be hidden while we're browsing results.
@@ -539,6 +542,12 @@
         }
     }
 
+    private void setSearchBoxBottomMargin(View searchBox, boolean multiColumn) {
+        var lp = (ViewGroup.MarginLayoutParams) searchBox.getLayoutParams();
+        lp.bottomMargin = multiColumn ? 0 : getPixelSize(R.dimen.settings_search_ui_bottom_margin);
+        searchBox.setLayoutParams(lp);
+    }
+
     private void setUpQueryEdit(EditText queryEdit) {
         queryEdit.addTextChangedListener(
                 new TextWatcher() {
diff --git a/chrome/android/javatests/BUILD.gn b/chrome/android/javatests/BUILD.gn
index 6482c91..9d24740d 100644
--- a/chrome/android/javatests/BUILD.gn
+++ b/chrome/android/javatests/BUILD.gn
@@ -1293,6 +1293,7 @@
 
   deps = [
     ":chrome_test_java_helper",
+    "//base:base_java_test_support",
     "//chrome/browser/ui/android/extensions:java",
     "//chrome/browser/user_education:java",
     "//components/sync/android:sync_java",
@@ -1598,6 +1599,7 @@
     "src/org/chromium/chrome/browser/webauth/AuthenticatorImplTest.java",
     "src/org/chromium/chrome/browser/webauth/AuthenticatorTest.java",
     "src/org/chromium/chrome/browser/webauth/Fido2CredentialRequestTest.java",
+    "src/org/chromium/chrome/browser/webauth/GetMatchingCredentialsDelegateTest.java",
     "src/org/chromium/chrome/browser/webauth/WebauthnTestUtils.java",
     "src/org/chromium/chrome/browser/webid/DigitalCredentialProviderTest.java",
     "src/org/chromium/chrome/browser/webshare/WebShareTest.java",
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/app/appmenu/TabbedAppMenuTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/app/appmenu/TabbedAppMenuTest.java
index 1bae7c8..1d4769d3 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/app/appmenu/TabbedAppMenuTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/app/appmenu/TabbedAppMenuTest.java
@@ -6,7 +6,6 @@
 
 import static org.junit.Assert.assertEquals;
 
-import static org.chromium.base.test.util.Batch.PER_CLASS;
 import static org.chromium.chrome.browser.ui.appmenu.AppMenuHandler.AppMenuItemType.DIVIDER;
 
 import android.view.KeyEvent;
@@ -30,11 +29,11 @@
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.task.PostTask;
 import org.chromium.base.task.TaskTraits;
-import org.chromium.base.test.util.Batch;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Criteria;
 import org.chromium.base.test.util.CriteriaHelper;
 import org.chromium.base.test.util.DisabledTest;
+import org.chromium.base.test.util.DoNotBatch;
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.Features.DisableFeatures;
 import org.chromium.base.test.util.Restriction;
@@ -69,7 +68,7 @@
 import java.util.concurrent.Callable;
 
 /** Tests tabbed mode app menu popup. */
-@Batch(PER_CLASS)
+@DoNotBatch(reason = "Affects sign-in state, which is global.")
 @RunWith(ChromeJUnit4ClassRunner.class)
 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
 // Prevents the sync UI from exposing an error due to outdated GmsCore
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/media/ui/MediaCapturePickerTabObserverTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/media/ui/MediaCapturePickerTabObserverTest.java
index 0b104539..7ac778ac 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/media/ui/MediaCapturePickerTabObserverTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/media/ui/MediaCapturePickerTabObserverTest.java
@@ -23,6 +23,7 @@
 import org.chromium.base.test.util.Batch;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
+import org.chromium.chrome.browser.media.AllowedScreenCaptureLevel;
 import org.chromium.chrome.browser.media.MediaCapturePickerManager;
 import org.chromium.chrome.browser.media.MediaCapturePickerTabObserver;
 import org.chromium.chrome.browser.tab.Tab;
@@ -68,7 +69,8 @@
                     /* preferredDisplaySurface= */ 0,
                     mCaptureThisTab,
                     /* excludeSelfBrowserSurface= */ false,
-                    /* excludeMonitorTypeSurfaces= */ false);
+                    /* excludeMonitorTypeSurfaces= */ false,
+                    AllowedScreenCaptureLevel.DESKTOP);
         }
     }
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/multiwindow/MultiInstanceManagerApi31Test.java b/chrome/android/javatests/src/org/chromium/chrome/browser/multiwindow/MultiInstanceManagerApi31Test.java
index e84087d2..94148f9 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/multiwindow/MultiInstanceManagerApi31Test.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/multiwindow/MultiInstanceManagerApi31Test.java
@@ -410,6 +410,65 @@
 
     @Test
     @SmallTest
+    @Features.EnableFeatures({
+        ChromeFeatureList.ANDROID_OPEN_INCOGNITO_AS_WINDOW,
+        ChromeFeatureList.ROBUST_WINDOW_MANAGEMENT
+    })
+    public void moveTabGroupToOtherWindow_multipleWindowsOpen_incognitoWindow_hideTargetSelector() {
+        createNewWindows(
+                mActivityTestRule.getActivity(),
+                /* numWindows= */ 2,
+                /* addIncognitoExtras= */ true);
+        TabGroupMetadata tabGroupMetadata = getTabGroupMetaData();
+
+        ChromeTabbedActivity newWindow =
+                ApplicationTestUtils.waitForActivityWithClass(
+                        ChromeTabbedActivity.class,
+                        Stage.RESUMED,
+                        () ->
+                                mMultiInstanceManager.moveTabGroupToOtherWindow(
+                                        tabGroupMetadata,
+                                        MultiInstanceManager.NewWindowAppSource.WINDOW_MANAGER));
+        assertFalse(
+                "Target selector dialog should not be visible", mModalDialogManager.isShowing());
+        assertFalse("New window should not be incognito", newWindow.isIncognitoWindow());
+    }
+
+    @Test
+    @SmallTest
+    @Features.EnableFeatures({
+        ChromeFeatureList.ANDROID_OPEN_INCOGNITO_AS_WINDOW,
+        ChromeFeatureList.ROBUST_WINDOW_MANAGEMENT
+    })
+    public void moveTabGroupToOtherWindow_multipleWindowsOpen_incognitoWindow_showTargetSelector() {
+        createNewWindows(
+                mActivityTestRule.getActivity(),
+                /* numWindows= */ 3,
+                /* addIncognitoExtras= */ true);
+        TabGroupMetadata incognitoTabGroupMetadata =
+                new TabGroupMetadata(
+                        /* selectedTabId= */ TAB1_ID,
+                        /* sourceWindowId= */ 1,
+                        TAB_GROUP_ID1,
+                        TAB_IDS_TO_URLS,
+                        /* tabGroupColor= */ 0,
+                        TAB_GROUP_TITLE,
+                        /* mhtmlTabTitle= */ null,
+                        /* tabGroupCollapsed= */ true,
+                        /* isGroupShared= */ false,
+                        /* isIncognito= */ true);
+
+        ThreadUtils.runOnUiThreadBlocking(
+                () -> {
+                    mMultiInstanceManager.moveTabGroupToOtherWindow(
+                            incognitoTabGroupMetadata,
+                            MultiInstanceManager.NewWindowAppSource.WINDOW_MANAGER);
+                });
+        assertTrue("Target selector dialog should be visible", mModalDialogManager.isShowing());
+    }
+
+    @Test
+    @SmallTest
     public void openUrlInOtherWindow_multipleWindowsOpen() {
         createNewWindow(
                 mActivityTestRule.getActivity(),
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/partnercustomizations/PartnerDisableIncognitoModeIntegrationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/partnercustomizations/PartnerDisableIncognitoModeIntegrationTest.java
index 650f1a4..4a864f8e 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/partnercustomizations/PartnerDisableIncognitoModeIntegrationTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/partnercustomizations/PartnerDisableIncognitoModeIntegrationTest.java
@@ -36,6 +36,7 @@
 import org.chromium.chrome.test.partnercustomizations.TestPartnerBrowserCustomizationsProvider;
 import org.chromium.chrome.test.transit.ChromeTransitTestRules;
 import org.chromium.chrome.test.transit.FreshCtaTransitTestRule;
+import org.chromium.chrome.test.transit.page.WebPageStation;
 import org.chromium.net.test.EmbeddedTestServer;
 import org.chromium.ui.base.UiAndroidFeatures;
 import org.chromium.ui.modelutil.MVCListAdapter;
@@ -162,13 +163,27 @@
         setParentalControlsEnabled(false);
         var page = mActivityTestRule.startOnBlankPage();
         waitForParentalControlsEnabledState(false);
-        page.openNewIncognitoTabOrWindowFast()
-                .loadWebPageProgrammatically(testUrls[0])
-                .openNewIncognitoTabOrWindowFast()
-                .loadWebPageProgrammatically(testUrls[1])
-                .openNewIncognitoTabOrWindowFast()
-                .loadWebPageProgrammatically(testUrls[2]);
-        mActivityTestRule.loadUrlInNewTab(testUrls[0], false);
+        var incognitoPage =
+                page.openNewIncognitoTabOrWindowFast()
+                        .loadWebPageProgrammatically(testUrls[0])
+                        .openNewIncognitoTabOrWindowFast()
+                        .loadWebPageProgrammatically(testUrls[1])
+                        .openNewIncognitoTabOrWindowFast()
+                        .loadWebPageProgrammatically(testUrls[2]);
+
+        if (IncognitoUtils.shouldOpenIncognitoAsWindow()) {
+            // Incognito tabs were opened in a new window, so just focus the regular window.
+            page.bringWindowToFront();
+            page.loadWebPageProgrammatically(testUrls[0]);
+        } else {
+            // Otherwise, switch to the regular tabs pane then to a regular tab.
+            incognitoPage
+                    .openIncognitoTabSwitcher()
+                    .selectRegularTabsPane()
+                    .selectTabAtIndex(0, WebPageStation.newBuilder().initSelectingExistingTab())
+                    .loadWebPageProgrammatically(testUrls[0]);
+        }
+
         setParentalControlsEnabled(true);
         toggleActivityForegroundState();
         waitForParentalControlsEnabledState(true);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/sync/settings/PersonalizeGoogleServicesSettingsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/sync/settings/PersonalizeGoogleServicesSettingsTest.java
index 59d9def..3cf6796 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/sync/settings/PersonalizeGoogleServicesSettingsTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/sync/settings/PersonalizeGoogleServicesSettingsTest.java
@@ -25,9 +25,9 @@
 
 import org.chromium.base.ServiceLoaderUtil;
 import org.chromium.base.test.util.ApplicationTestUtils;
-import org.chromium.base.test.util.Batch;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.DisabledTest;
+import org.chromium.base.test.util.DoNotBatch;
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.Features.DisableFeatures;
 import org.chromium.chrome.R;
@@ -42,7 +42,7 @@
 
 /** Tests for {@link PersonalizeGoogleServicesSettings}. */
 @RunWith(ChromeJUnit4ClassRunner.class)
-@Batch(Batch.PER_CLASS)
+@DoNotBatch(reason = "Affects sign-in state, which is global.")
 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
 public class PersonalizeGoogleServicesSettingsTest {
     private static final int RENDER_TEST_REVISION = 1;
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebappDisplayModeTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebappDisplayModeTest.java
index 7b5f539..1ffcb34 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebappDisplayModeTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebappDisplayModeTest.java
@@ -5,8 +5,6 @@
 package org.chromium.chrome.browser.webapps;
 
 import android.graphics.Color;
-import android.os.Build.VERSION_CODES;
-import android.view.View;
 
 import androidx.test.filters.MediumTest;
 import androidx.test.filters.SmallTest;
@@ -19,7 +17,6 @@
 import org.chromium.base.test.util.Batch;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.CriteriaHelper;
-import org.chromium.base.test.util.DisableIf;
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.Restriction;
 import org.chromium.blink.mojom.DisplayMode;
@@ -47,30 +44,34 @@
     public void testStandalone() {
         WebappActivity activity = startActivity(DisplayMode.STANDALONE, "");
 
+        Assert.assertEquals(
+                DisplayMode.STANDALONE,
+                activity.getIntentDataProvider().getWebappExtras().displayMode);
         Assert.assertFalse(activity.getToolbarManager().getToolbarLayoutForTesting().isShown());
-        Assert.assertFalse(isFullscreen(activity));
     }
 
     @Test
     @SmallTest
-    @DisableIf.Build(sdk_is_greater_than = VERSION_CODES.Q) // https://crbug.com/1231227
     @Feature({"Webapps"})
     public void testFullScreen() {
         WebappActivity activity = startActivity(DisplayMode.FULLSCREEN, "");
 
+        Assert.assertEquals(
+                DisplayMode.FULLSCREEN,
+                activity.getIntentDataProvider().getWebappExtras().displayMode);
         Assert.assertFalse(activity.getToolbarManager().getToolbarLayoutForTesting().isShown());
-        Assert.assertTrue(isFullscreen(activity));
     }
 
     @Test
     @MediumTest
-    @DisableIf.Build(sdk_is_greater_than = VERSION_CODES.Q) // https://crbug.com/1231227
     @Feature({"Webapps"})
     public void testFullScreenInFullscreen() {
         WebappActivity activity = startActivity(DisplayMode.FULLSCREEN, "fullscreen_on_click");
 
+        Assert.assertEquals(
+                DisplayMode.FULLSCREEN,
+                activity.getIntentDataProvider().getWebappExtras().displayMode);
         Assert.assertFalse(activity.getToolbarManager().getToolbarLayoutForTesting().isShown());
-        Assert.assertTrue(isFullscreen(activity));
 
         WebContents contents = activity.getActivityTab().getWebContents();
 
@@ -78,12 +79,10 @@
         // Poll because clicking races with evaluating js evaluation.
         CriteriaHelper.pollInstrumentationThread(
                 () -> getJavascriptResult(contents, "isBodyFullscreen()").equals("true"));
-        Assert.assertTrue(isFullscreen(activity));
 
         TouchCommon.singleClickView(activity.getActivityTab().getContentView());
         CriteriaHelper.pollInstrumentationThread(
                 () -> getJavascriptResult(contents, "isBodyFullscreen()").equals("false"));
-        Assert.assertTrue(isFullscreen(activity));
     }
 
     @Test
@@ -93,7 +92,9 @@
     public void testMinimalUi() {
         WebappActivity activity = startActivity(DisplayMode.MINIMAL_UI, "");
 
-        Assert.assertFalse(isFullscreen(activity));
+        Assert.assertEquals(
+                DisplayMode.MINIMAL_UI,
+                activity.getIntentDataProvider().getWebappExtras().displayMode);
         Assert.assertFalse(activity.getToolbarManager().getToolbarLayoutForTesting().isShown());
 
         Assert.assertEquals(Color.CYAN, activity.getToolbarManager().getPrimaryColor());
@@ -123,9 +124,4 @@
 
         return mActivityTestRule.getActivity();
     }
-
-    private static boolean isFullscreen(WebappActivity activity) {
-        int systemUiVisibility = activity.getWindow().getDecorView().getSystemUiVisibility();
-        return (systemUiVisibility & View.SYSTEM_UI_FLAG_IMMERSIVE) != 0;
-    }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/webauth/AuthenticatorTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/webauth/AuthenticatorTest.java
index ff0b6a0f..dad246c 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/webauth/AuthenticatorTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/webauth/AuthenticatorTest.java
@@ -34,12 +34,14 @@
 import org.chromium.chrome.test.transit.ChromeTransitTestRules;
 import org.chromium.chrome.test.transit.FreshCtaTransitTestRule;
 import org.chromium.chrome.test.transit.page.WebPageStation;
-import org.chromium.components.webauthn.AuthenticatorErrorResponseCallback;
+import org.chromium.components.webauthn.AuthenticationContextProvider;
 import org.chromium.components.webauthn.AuthenticatorImpl;
 import org.chromium.components.webauthn.Fido2CredentialRequest;
 import org.chromium.components.webauthn.IsUvpaaResponseCallback;
 import org.chromium.components.webauthn.WebauthnMode;
 import org.chromium.components.webauthn.WebauthnModeProvider;
+import org.chromium.components.webauthn.WebauthnRequestCallback;
+import org.chromium.components.webauthn.WebauthnRequestResponse;
 import org.chromium.content_public.common.ContentSwitches;
 import org.chromium.net.test.EmbeddedTestServer;
 import org.chromium.net.test.ServerCertificate;
@@ -70,6 +72,7 @@
     private Tab mTab;
     private AuthenticatorUpdateWaiter mUpdateWaiter;
     @Mock private Fido2CredentialRequest mMockCredentialRequest;
+    private AuthenticationContextProvider mCapturedProvider;
 
     /** Waits until the JavaScript code supplies a result. */
     private class AuthenticatorUpdateWaiter extends EmptyTabObserver {
@@ -110,23 +113,41 @@
         WebauthnModeProvider.getInstance().setGlobalWebauthnMode(WebauthnMode.CHROME);
         doAnswer(
                         invocation -> {
-                            AuthenticatorErrorResponseCallback errorCallback =
-                                    invocation.getArgument(6);
-                            errorCallback.onError(AuthenticatorStatus.NOT_IMPLEMENTED);
+                            mCapturedProvider = invocation.getArgument(0);
                             return null;
                         })
                 .when(mMockCredentialRequest)
-                .handleMakeCredentialRequest(
-                        any(), any(), any(), any(), any(), any(), any(), any());
+                .setAuthenticationContextProvider(any());
         doAnswer(
                         invocation -> {
-                            AuthenticatorErrorResponseCallback errorCallback =
-                                    invocation.getArgument(5);
-                            errorCallback.onError(AuthenticatorStatus.NOT_IMPLEMENTED);
+                            if (mCapturedProvider != null) {
+                                WebauthnRequestCallback callback =
+                                        mCapturedProvider.getRequestCallback();
+                                if (callback != null) {
+                                    callback.onComplete(
+                                            WebauthnRequestResponse.forFailedMakeCredential(
+                                                    AuthenticatorStatus.NOT_IMPLEMENTED, null));
+                                }
+                            }
                             return null;
                         })
                 .when(mMockCredentialRequest)
-                .handleGetCredentialRequest(any(), any(), any(), any(), any(), any(), any());
+                .handleMakeCredentialRequest(any(), any(), any(), any(), any());
+        doAnswer(
+                        invocation -> {
+                            if (mCapturedProvider != null) {
+                                WebauthnRequestCallback callback =
+                                        mCapturedProvider.getRequestCallback();
+                                if (callback != null) {
+                                    callback.onComplete(
+                                            WebauthnRequestResponse.forFailedGetCredential(
+                                                    AuthenticatorStatus.NOT_IMPLEMENTED, null));
+                                }
+                            }
+                            return null;
+                        })
+                .when(mMockCredentialRequest)
+                .handleGetCredentialRequest(any(), any(), any(), any());
         doAnswer(
                         invocation -> {
                             IsUvpaaResponseCallback callback = invocation.getArgument(0);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/webauth/Fido2CredentialRequestTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/webauth/Fido2CredentialRequestTest.java
index 6ad48d4..fda73d7 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/webauth/Fido2CredentialRequestTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/webauth/Fido2CredentialRequestTest.java
@@ -55,7 +55,9 @@
 import org.chromium.blink.mojom.AuthenticatorStatus;
 import org.chromium.blink.mojom.AuthenticatorTransport;
 import org.chromium.blink.mojom.GetAssertionAuthenticatorResponse;
+import org.chromium.blink.mojom.GetAssertionResponse;
 import org.chromium.blink.mojom.GetCredentialOptions;
+import org.chromium.blink.mojom.GetCredentialResponse;
 import org.chromium.blink.mojom.MakeCredentialAuthenticatorResponse;
 import org.chromium.blink.mojom.Mediation;
 import org.chromium.blink.mojom.PaymentOptions;
@@ -67,6 +69,7 @@
 import org.chromium.blink.mojom.ResidentKeyRequirement;
 import org.chromium.blink_public.common.BlinkFeatures;
 import org.chromium.build.BuildConfig;
+import org.chromium.build.annotations.Nullable;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.test.ChromeJUnit4RunnerDelegate;
 import org.chromium.chrome.test.transit.AutoResetCtaTransitTestRule;
@@ -90,6 +93,7 @@
 import org.chromium.components.webauthn.WebauthnCredentialDetails;
 import org.chromium.components.webauthn.WebauthnMode;
 import org.chromium.components.webauthn.WebauthnModeProvider;
+import org.chromium.components.webauthn.WebauthnRequestCallback;
 import org.chromium.components.webauthn.cred_man.CredManSupportProvider;
 import org.chromium.content.browser.ClientDataJsonImpl;
 import org.chromium.content.browser.ClientDataJsonImplJni;
@@ -155,6 +159,8 @@
     private static final String PASSWORD_CRED_PASSWORD = "DLuffy2";
     private static String16 sPasswordCredPassword16;
 
+    private WebauthnRequestCallback mRequestCallback;
+
     /**
      * This class constructs the parameters array that is used for testMakeCredential_with_param and
      * testGetAssertion_with_param as input parameters.
@@ -252,6 +258,13 @@
         mOrigin = Origin.create(gurl);
         mBrowserOptions = GpmBrowserOptionsHelper.createDefaultBrowserOptions();
         GpmBrowserOptionsHelper.setIsIncognitoExtraUntilTearDown(false);
+
+        // Initialize with a temporary callback to handle any requests during page load.
+        Fido2ApiTestHelper.AuthenticatorCallback tempCallback =
+                Fido2ApiTestHelper.getAuthenticatorCallback();
+        mTestAuthenticatorImplJni = new WebauthnTestUtils.TestAuthenticatorImplJni(tempCallback);
+        InternalAuthenticatorJni.setInstanceForTesting(mTestAuthenticatorImplJni);
+
         mActivityTestRule.loadUrl(url);
         mFrameHost = new WebauthnTestUtils.MockAuthenticatorRenderFrameHost();
         mFrameHost.setLastCommittedUrl(gurl);
@@ -287,6 +300,11 @@
                     public WebContents getWebContents() {
                         return mWebContents;
                     }
+
+                    @Override
+                    public WebauthnRequestCallback getRequestCallback() {
+                        return mRequestCallback;
+                    }
                 };
         mRequest = new Fido2CredentialRequest(mAuthenticationContextProvider);
         AuthenticatorImpl.overrideFido2CredentialRequestForTesting(mRequest);
@@ -313,6 +331,55 @@
                 /* overrideForcesGpm= */ false);
     }
 
+    private void handleMakeCredentialRequestTestHelper(
+            PublicKeyCredentialCreationOptions options,
+            @Nullable Bundle browserOptions,
+            Origin origin,
+            @Nullable Origin topOrigin,
+            @Nullable PaymentOptions paymentOptions) {
+        mRequestCallback =
+                WebauthnRequestCallback.forMakeCredential(
+                        (status, response, domException) -> {
+                            if (status == AuthenticatorStatus.SUCCESS) {
+                                mCallback.onRegisterResponse(status, response);
+                            } else {
+                                mCallback.onError(status);
+                            }
+                        },
+                        mCallback::onRequestOutcome);
+        mRequest.handleMakeCredentialRequest(
+                options, browserOptions, origin, topOrigin, paymentOptions);
+    }
+
+    private void handleGetCredentialRequestTestHelper(
+            GetCredentialOptions options,
+            Origin origin,
+            @Nullable Origin topOrigin,
+            PaymentOptions payment) {
+        mRequestCallback =
+                WebauthnRequestCallback.forGetCredential(
+                        (response) -> {
+                            if (response.which()
+                                    == GetCredentialResponse.Tag.GetAssertionResponse) {
+                                GetAssertionResponse assertionResponse =
+                                        response.getGetAssertionResponse();
+                                if (assertionResponse.status == AuthenticatorStatus.SUCCESS) {
+                                    mCallback.onSignResponse(
+                                            assertionResponse.credential,
+                                            /* passwordCredential= */ null);
+                                } else {
+                                    mCallback.onError(assertionResponse.status);
+                                }
+                            } else if (response.which()
+                                    == GetCredentialResponse.Tag.PasswordResponse) {
+                                mCallback.onSignResponse(
+                                        /* response= */ null, response.getPasswordResponse());
+                            }
+                        },
+                        mCallback::onRequestOutcome);
+        mRequest.handleGetCredentialRequest(options, origin, topOrigin, payment);
+    }
+
     @Test
     @SmallTest
     public void testConvertOriginToString_defaultPortRemoved() {
@@ -328,21 +395,13 @@
                 Fido2ApiTestHelper.createSuccessfulMakeCredentialIntent());
         Fido2ApiTestHelper.mockClientDataJson();
 
-        mRequest.handleMakeCredentialRequest(
-                mCreationOptions,
-                mBrowserOptions,
-                mOrigin,
-                mOrigin,
-                /* paymentOptions= */ null,
-                mCallback::onRegisterResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleMakeCredentialRequestTestHelper(
+                mCreationOptions, mBrowserOptions, mOrigin, mOrigin, /* paymentOptions= */ null);
         mCallback.blockUntilCalled();
         Assert.assertEquals(mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.SUCCESS));
         Fido2ApiTestHelper.validateMakeCredentialResponse(mCallback.getMakeCredentialResponse());
         Fido2ApiTestHelper.verifyRespondedBeforeTimeout(mStartTimeMs);
-        // Only error outcomes are reported through the outcome callback.
-        Assert.assertEquals(mCallback.getOutcome(), null);
+        Assert.assertEquals(mCallback.getOutcome(), Integer.valueOf(MakeCredentialOutcome.SUCCESS));
     }
 
     @Test
@@ -351,15 +410,8 @@
         mIntentSender.setNextResultIntent(
                 Fido2ApiTestHelper.createSuccessfulPasskeyMakeCredentialIntent());
 
-        mRequest.handleMakeCredentialRequest(
-                mCreationOptions,
-                mBrowserOptions,
-                mOrigin,
-                mOrigin,
-                /* paymentOptions= */ null,
-                mCallback::onRegisterResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleMakeCredentialRequestTestHelper(
+                mCreationOptions, mBrowserOptions, mOrigin, mOrigin, /* paymentOptions= */ null);
         mCallback.blockUntilCalled();
         Assert.assertEquals(mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.SUCCESS));
 
@@ -379,15 +431,8 @@
     @Test
     @SmallTest
     public void testMakeCredential_unsuccessfulAttemptToShowCancelableIntent() {
-        mRequest.handleMakeCredentialRequest(
-                mCreationOptions,
-                mBrowserOptions,
-                mOrigin,
-                mOrigin,
-                /* paymentOptions= */ null,
-                mCallback::onRegisterResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleMakeCredentialRequestTestHelper(
+                mCreationOptions, mBrowserOptions, mOrigin, mOrigin, /* paymentOptions= */ null);
         mCallback.blockUntilCalled();
         Assert.assertEquals(
                 mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.UNKNOWN_ERROR));
@@ -400,15 +445,8 @@
         // An intent missing FIDO2_KEY_RESPONSE_EXTRA.
         mIntentSender.setNextResultIntent(new Intent());
 
-        mRequest.handleMakeCredentialRequest(
-                mCreationOptions,
-                mBrowserOptions,
-                mOrigin,
-                mOrigin,
-                /* paymentOptions= */ null,
-                mCallback::onRegisterResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleMakeCredentialRequestTestHelper(
+                mCreationOptions, mBrowserOptions, mOrigin, mOrigin, /* paymentOptions= */ null);
         mCallback.blockUntilCalled();
         Assert.assertEquals(
                 mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.UNKNOWN_ERROR));
@@ -421,15 +459,8 @@
         // Don't set an intent to be returned at all.
         mIntentSender.setNextResultIntent(null);
 
-        mRequest.handleMakeCredentialRequest(
-                mCreationOptions,
-                mBrowserOptions,
-                mOrigin,
-                mOrigin,
-                /* paymentOptions= */ null,
-                mCallback::onRegisterResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleMakeCredentialRequestTestHelper(
+                mCreationOptions, mBrowserOptions, mOrigin, mOrigin, /* paymentOptions= */ null);
         mCallback.blockUntilCalled();
         Assert.assertEquals(
                 mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.NOT_ALLOWED_ERROR));
@@ -441,15 +472,8 @@
     public void testMakeCredential_resultCanceled() {
         mIntentSender.setNextResult(Activity.RESULT_CANCELED, null);
 
-        mRequest.handleMakeCredentialRequest(
-                mCreationOptions,
-                mBrowserOptions,
-                mOrigin,
-                mOrigin,
-                /* paymentOptions= */ null,
-                mCallback::onRegisterResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleMakeCredentialRequestTestHelper(
+                mCreationOptions, mBrowserOptions, mOrigin, mOrigin, /* paymentOptions= */ null);
         mCallback.blockUntilCalled();
         Assert.assertEquals(
                 mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.NOT_ALLOWED_ERROR));
@@ -465,15 +489,8 @@
                 Activity.RESULT_FIRST_USER,
                 Fido2ApiTestHelper.createSuccessfulMakeCredentialIntent());
 
-        mRequest.handleMakeCredentialRequest(
-                mCreationOptions,
-                mBrowserOptions,
-                mOrigin,
-                mOrigin,
-                /* paymentOptions= */ null,
-                mCallback::onRegisterResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleMakeCredentialRequestTestHelper(
+                mCreationOptions, mBrowserOptions, mOrigin, mOrigin, /* paymentOptions= */ null);
         mCallback.blockUntilCalled();
         Assert.assertEquals(
                 mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.UNKNOWN_ERROR));
@@ -489,15 +506,8 @@
         parameters.type = PublicKeyCredentialType.PUBLIC_KEY;
         customOptions.publicKeyParameters = new PublicKeyCredentialParameters[] {parameters};
 
-        mRequest.handleMakeCredentialRequest(
-                customOptions,
-                mBrowserOptions,
-                mOrigin,
-                mOrigin,
-                /* paymentOptions= */ null,
-                mCallback::onRegisterResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleMakeCredentialRequestTestHelper(
+                customOptions, mBrowserOptions, mOrigin, mOrigin, /* paymentOptions= */ null);
         mCallback.blockUntilCalled();
         Assert.assertEquals(
                 mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.ALGORITHM_UNSUPPORTED));
@@ -523,15 +533,8 @@
                 Fido2ApiTestHelper.createSuccessfulMakeCredentialIntent());
         Fido2ApiTestHelper.mockClientDataJson();
 
-        mRequest.handleMakeCredentialRequest(
-                customOptions,
-                mBrowserOptions,
-                mOrigin,
-                mOrigin,
-                /* paymentOptions= */ null,
-                mCallback::onRegisterResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleMakeCredentialRequestTestHelper(
+                customOptions, mBrowserOptions, mOrigin, mOrigin, /* paymentOptions= */ null);
         mCallback.blockUntilCalled();
         Assert.assertEquals(mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.SUCCESS));
         Fido2ApiTestHelper.validateMakeCredentialResponse(mCallback.getMakeCredentialResponse());
@@ -547,15 +550,8 @@
 
         PublicKeyCredentialCreationOptions customOptions = mCreationOptions;
         customOptions.excludeCredentials = new PublicKeyCredentialDescriptor[0];
-        mRequest.handleMakeCredentialRequest(
-                customOptions,
-                mBrowserOptions,
-                mOrigin,
-                mOrigin,
-                /* paymentOptions= */ null,
-                mCallback::onRegisterResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleMakeCredentialRequestTestHelper(
+                customOptions, mBrowserOptions, mOrigin, mOrigin, /* paymentOptions= */ null);
         mCallback.blockUntilCalled();
         Assert.assertEquals(mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.SUCCESS));
         Fido2ApiTestHelper.validateMakeCredentialResponse(mCallback.getMakeCredentialResponse());
@@ -568,15 +564,8 @@
         mIntentSender.setNextResultIntent(
                 Fido2ApiTestHelper.createSuccessfulMakeCredentialIntentWithAttestation());
 
-        mRequest.handleMakeCredentialRequest(
-                mCreationOptions,
-                mBrowserOptions,
-                mOrigin,
-                mOrigin,
-                /* paymentOptions= */ null,
-                mCallback::onRegisterResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleMakeCredentialRequestTestHelper(
+                mCreationOptions, mBrowserOptions, mOrigin, mOrigin, /* paymentOptions= */ null);
         mCallback.blockUntilCalled();
         Assert.assertEquals(mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.SUCCESS));
         MakeCredentialAuthenticatorResponse response = mCallback.getMakeCredentialResponse();
@@ -595,15 +584,8 @@
                 Fido2ApiTestHelper.createDefaultMakeCredentialOptions();
         creationOptions.authenticatorSelection.residentKey = ResidentKeyRequirement.REQUIRED;
 
-        mRequest.handleMakeCredentialRequest(
-                creationOptions,
-                mBrowserOptions,
-                mOrigin,
-                mOrigin,
-                /* paymentOptions= */ null,
-                mCallback::onRegisterResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleMakeCredentialRequestTestHelper(
+                creationOptions, mBrowserOptions, mOrigin, mOrigin, /* paymentOptions= */ null);
         mCallback.blockUntilCalled();
         Assert.assertEquals(mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.SUCCESS));
         MakeCredentialAuthenticatorResponse response = mCallback.getMakeCredentialResponse();
@@ -622,15 +604,8 @@
                 Fido2ApiTestHelper.createDefaultMakeCredentialOptions();
         creationOptions.authenticatorSelection.residentKey = ResidentKeyRequirement.PREFERRED;
 
-        mRequest.handleMakeCredentialRequest(
-                creationOptions,
-                mBrowserOptions,
-                mOrigin,
-                mOrigin,
-                /* paymentOptions= */ null,
-                mCallback::onRegisterResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleMakeCredentialRequestTestHelper(
+                creationOptions, mBrowserOptions, mOrigin, mOrigin, /* paymentOptions= */ null);
         mCallback.blockUntilCalled();
         Assert.assertEquals(mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.SUCCESS));
         MakeCredentialAuthenticatorResponse response = mCallback.getMakeCredentialResponse();
@@ -647,15 +622,8 @@
                 Fido2ApiTestHelper.createDefaultMakeCredentialOptions();
         creationOptions.credProps = true;
 
-        mRequest.handleMakeCredentialRequest(
-                creationOptions,
-                mBrowserOptions,
-                mOrigin,
-                mOrigin,
-                /* paymentOptions= */ null,
-                mCallback::onRegisterResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleMakeCredentialRequestTestHelper(
+                creationOptions, mBrowserOptions, mOrigin, mOrigin, /* paymentOptions= */ null);
         mCallback.blockUntilCalled();
         Assert.assertEquals(mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.SUCCESS));
         MakeCredentialAuthenticatorResponse response = mCallback.getMakeCredentialResponse();
@@ -670,14 +638,8 @@
         mIntentSender.setNextResultIntent(Fido2ApiTestHelper.createSuccessfulGetAssertionIntent());
         Fido2ApiTestHelper.mockClientDataJson();
 
-        mRequest.handleGetCredentialRequest(
-                mRequestOptions,
-                mOrigin,
-                mOrigin,
-                /* payment= */ null,
-                mCallback::onSignResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleGetCredentialRequestTestHelper(
+                mRequestOptions, mOrigin, mOrigin, /* payment= */ null);
         mCallback.blockUntilCalled();
         Assert.assertEquals(mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.SUCCESS));
         Fido2ApiTestHelper.validateGetAssertionResponse(mCallback.getGetAssertionResponse());
@@ -691,14 +653,8 @@
         mIntentSender.setNextResultIntent(Fido2ApiTestHelper.createSuccessfulGetAssertionIntent());
 
         mRequestOptions.publicKey.extensions.userVerificationMethods = true;
-        mRequest.handleGetCredentialRequest(
-                mRequestOptions,
-                mOrigin,
-                mOrigin,
-                /* payment= */ null,
-                mCallback::onSignResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleGetCredentialRequestTestHelper(
+                mRequestOptions, mOrigin, mOrigin, /* payment= */ null);
         mCallback.blockUntilCalled();
         Assert.assertEquals(mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.SUCCESS));
         Fido2ApiTestHelper.validateGetAssertionResponse(mCallback.getGetAssertionResponse());
@@ -713,14 +669,8 @@
                 Fido2ApiTestHelper.createSuccessfulGetAssertionIntentWithUvm());
 
         mRequestOptions.publicKey.extensions.userVerificationMethods = true;
-        mRequest.handleGetCredentialRequest(
-                mRequestOptions,
-                mOrigin,
-                mOrigin,
-                /* payment= */ null,
-                mCallback::onSignResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleGetCredentialRequestTestHelper(
+                mRequestOptions, mOrigin, mOrigin, /* payment= */ null);
         mCallback.blockUntilCalled();
         Assert.assertEquals(mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.SUCCESS));
         GetAssertionAuthenticatorResponse response = mCallback.getGetAssertionResponse();
@@ -735,14 +685,8 @@
         mIntentSender.setNextResultIntent(
                 Fido2ApiTestHelper.createSuccessfulGetAssertionIntentWithUserId());
 
-        mRequest.handleGetCredentialRequest(
-                mRequestOptions,
-                mOrigin,
-                mOrigin,
-                /* payment= */ null,
-                mCallback::onSignResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleGetCredentialRequestTestHelper(
+                mRequestOptions, mOrigin, mOrigin, /* payment= */ null);
         mCallback.blockUntilCalled();
         Assert.assertEquals(mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.SUCCESS));
         GetAssertionAuthenticatorResponse response = mCallback.getGetAssertionResponse();
@@ -752,14 +696,8 @@
     @Test
     @SmallTest
     public void testGetCredential_unsuccessfulAttemptToShowCancelableIntent() {
-        mRequest.handleGetCredentialRequest(
-                mRequestOptions,
-                mOrigin,
-                mOrigin,
-                /* payment= */ null,
-                mCallback::onSignResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleGetCredentialRequestTestHelper(
+                mRequestOptions, mOrigin, mOrigin, /* payment= */ null);
         mCallback.blockUntilCalled();
         Assert.assertEquals(
                 mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.UNKNOWN_ERROR));
@@ -772,14 +710,8 @@
         // An intent missing FIDO2_KEY_RESPONSE_EXTRA.
         mIntentSender.setNextResultIntent(new Intent());
 
-        mRequest.handleGetCredentialRequest(
-                mRequestOptions,
-                mOrigin,
-                mOrigin,
-                /* payment= */ null,
-                mCallback::onSignResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleGetCredentialRequestTestHelper(
+                mRequestOptions, mOrigin, mOrigin, /* payment= */ null);
         mCallback.blockUntilCalled();
         Assert.assertEquals(
                 mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.UNKNOWN_ERROR));
@@ -792,14 +724,8 @@
         // Don't set an intent to be returned at all.
         mIntentSender.setNextResultIntent(null);
 
-        mRequest.handleGetCredentialRequest(
-                mRequestOptions,
-                mOrigin,
-                mOrigin,
-                /* payment= */ null,
-                mCallback::onSignResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleGetCredentialRequestTestHelper(
+                mRequestOptions, mOrigin, mOrigin, /* payment= */ null);
         mCallback.blockUntilCalled();
         Assert.assertEquals(
                 mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.NOT_ALLOWED_ERROR));
@@ -811,14 +737,8 @@
     public void testGetCredential_resultCanceled() {
         mIntentSender.setNextResult(Activity.RESULT_CANCELED, null);
 
-        mRequest.handleGetCredentialRequest(
-                mRequestOptions,
-                mOrigin,
-                mOrigin,
-                /* payment= */ null,
-                mCallback::onSignResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleGetCredentialRequestTestHelper(
+                mRequestOptions, mOrigin, mOrigin, /* payment= */ null);
         mCallback.blockUntilCalled();
         Assert.assertEquals(
                 mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.NOT_ALLOWED_ERROR));
@@ -834,14 +754,8 @@
                 Activity.RESULT_FIRST_USER,
                 Fido2ApiTestHelper.createSuccessfulGetAssertionIntent());
 
-        mRequest.handleGetCredentialRequest(
-                mRequestOptions,
-                mOrigin,
-                mOrigin,
-                /* payment= */ null,
-                mCallback::onSignResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleGetCredentialRequestTestHelper(
+                mRequestOptions, mOrigin, mOrigin, /* payment= */ null);
         mCallback.blockUntilCalled();
         Assert.assertEquals(
                 mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.UNKNOWN_ERROR));
@@ -855,14 +769,8 @@
                 Fido2ApiTestHelper.createErrorIntent(
                         Fido2Api.UNKNOWN_ERR, "Low level error 0x6a80"));
 
-        mRequest.handleGetCredentialRequest(
-                mRequestOptions,
-                mOrigin,
-                mOrigin,
-                /* payment= */ null,
-                mCallback::onSignResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleGetCredentialRequestTestHelper(
+                mRequestOptions, mOrigin, mOrigin, /* payment= */ null);
         mCallback.blockUntilCalled();
         Assert.assertEquals(
                 mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.NOT_ALLOWED_ERROR));
@@ -880,14 +788,8 @@
         customOptions.publicKey.extensions.appid = "www.example.com";
         mIntentSender.setNextResultIntent(Fido2ApiTestHelper.createSuccessfulGetAssertionIntent());
 
-        mRequest.handleGetCredentialRequest(
-                mRequestOptions,
-                mOrigin,
-                mOrigin,
-                /* payment= */ null,
-                mCallback::onSignResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleGetCredentialRequestTestHelper(
+                mRequestOptions, mOrigin, mOrigin, /* payment= */ null);
         mCallback.blockUntilCalled();
         Assert.assertEquals(mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.SUCCESS));
         GetAssertionAuthenticatorResponse response = mCallback.getGetAssertionResponse();
@@ -905,15 +807,8 @@
 
         PublicKeyCredentialCreationOptions customOptions = mCreationOptions;
         customOptions.attestation = org.chromium.blink.mojom.AttestationConveyancePreference.NONE;
-        mRequest.handleMakeCredentialRequest(
-                customOptions,
-                mBrowserOptions,
-                mOrigin,
-                mOrigin,
-                /* paymentOptions= */ null,
-                mCallback::onRegisterResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleMakeCredentialRequestTestHelper(
+                customOptions, mBrowserOptions, mOrigin, mOrigin, /* paymentOptions= */ null);
         mCallback.blockUntilCalled();
         Assert.assertEquals(mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.SUCCESS));
         Fido2ApiTestHelper.validateMakeCredentialResponse(mCallback.getMakeCredentialResponse());
@@ -930,15 +825,8 @@
         PublicKeyCredentialCreationOptions customOptions = mCreationOptions;
         customOptions.attestation =
                 org.chromium.blink.mojom.AttestationConveyancePreference.INDIRECT;
-        mRequest.handleMakeCredentialRequest(
-                customOptions,
-                mBrowserOptions,
-                mOrigin,
-                mOrigin,
-                /* paymentOptions= */ null,
-                mCallback::onRegisterResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleMakeCredentialRequestTestHelper(
+                customOptions, mBrowserOptions, mOrigin, mOrigin, /* paymentOptions= */ null);
         mCallback.blockUntilCalled();
         Assert.assertEquals(mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.SUCCESS));
         Fido2ApiTestHelper.validateMakeCredentialResponse(mCallback.getMakeCredentialResponse());
@@ -954,15 +842,8 @@
 
         PublicKeyCredentialCreationOptions customOptions = mCreationOptions;
         customOptions.attestation = org.chromium.blink.mojom.AttestationConveyancePreference.DIRECT;
-        mRequest.handleMakeCredentialRequest(
-                customOptions,
-                mBrowserOptions,
-                mOrigin,
-                mOrigin,
-                /* paymentOptions= */ null,
-                mCallback::onRegisterResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleMakeCredentialRequestTestHelper(
+                customOptions, mBrowserOptions, mOrigin, mOrigin, /* paymentOptions= */ null);
         mCallback.blockUntilCalled();
         Assert.assertEquals(mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.SUCCESS));
         Fido2ApiTestHelper.validateMakeCredentialResponse(mCallback.getMakeCredentialResponse());
@@ -979,15 +860,8 @@
         PublicKeyCredentialCreationOptions customOptions = mCreationOptions;
         customOptions.attestation =
                 org.chromium.blink.mojom.AttestationConveyancePreference.ENTERPRISE;
-        mRequest.handleMakeCredentialRequest(
-                customOptions,
-                mBrowserOptions,
-                mOrigin,
-                mOrigin,
-                /* paymentOptions= */ null,
-                mCallback::onRegisterResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleMakeCredentialRequestTestHelper(
+                customOptions, mBrowserOptions, mOrigin, mOrigin, /* paymentOptions= */ null);
         mCallback.blockUntilCalled();
         Assert.assertEquals(mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.SUCCESS));
         Fido2ApiTestHelper.validateMakeCredentialResponse(mCallback.getMakeCredentialResponse());
@@ -1002,15 +876,8 @@
                         Fido2Api.INVALID_STATE_ERR,
                         "One of the excluded credentials exists on the local device"));
 
-        mRequest.handleMakeCredentialRequest(
-                mCreationOptions,
-                mBrowserOptions,
-                mOrigin,
-                mOrigin,
-                /* paymentOptions= */ null,
-                mCallback::onRegisterResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleMakeCredentialRequestTestHelper(
+                mCreationOptions, mBrowserOptions, mOrigin, mOrigin, /* paymentOptions= */ null);
         mCallback.blockUntilCalled();
         Assert.assertEquals(
                 mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.CREDENTIAL_EXCLUDED));
@@ -1030,15 +897,8 @@
 
         mCreationOptions.isPaymentCredentialCreation = true;
         Assert.assertFalse(mFrameHost.isPaymentCredentialCreation());
-        mRequest.handleMakeCredentialRequest(
-                mCreationOptions,
-                mBrowserOptions,
-                mOrigin,
-                mOrigin,
-                /* paymentOptions= */ null,
-                mCallback::onRegisterResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleMakeCredentialRequestTestHelper(
+                mCreationOptions, mBrowserOptions, mOrigin, mOrigin, /* paymentOptions= */ null);
         Assert.assertTrue(mFrameHost.isPaymentCredentialCreation());
     }
 
@@ -1054,15 +914,12 @@
 
         mCreationOptions.isPaymentCredentialCreation = true;
         Assert.assertFalse(mFrameHost.isPaymentCredentialCreation());
-        mRequest.handleMakeCredentialRequest(
+        handleMakeCredentialRequestTestHelper(
                 mCreationOptions,
                 mBrowserOptions,
                 mOrigin,
                 mOrigin,
-                Fido2ApiTestHelper.createPaymentOptions(),
-                mCallback::onRegisterResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+                Fido2ApiTestHelper.createPaymentOptions());
         Assert.assertTrue(mFrameHost.isPaymentCredentialCreation());
     }
 
@@ -1081,14 +938,8 @@
         // local passkeys available.
         mFido2ApiCallHelper.setReturnedCredentialDetails(new ArrayList<>());
 
-        mRequest.handleGetCredentialRequest(
-                mRequestOptions,
-                mOrigin,
-                mOrigin,
-                /* payment= */ null,
-                mCallback::onSignResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleGetCredentialRequestTestHelper(
+                mRequestOptions, mOrigin, mOrigin, /* payment= */ null);
         mCallback.blockUntilCalled();
         Assert.assertEquals(
                 mCallback.getStatus(),
@@ -1114,14 +965,7 @@
         // local passkeys available.
         mFido2ApiCallHelper.setReturnedCredentialDetails(new ArrayList<>());
 
-        mRequest.handleGetCredentialRequest(
-                customOptions,
-                mOrigin,
-                mOrigin,
-                /* payment= */ null,
-                mCallback::onSignResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleGetCredentialRequestTestHelper(customOptions, mOrigin, mOrigin, /* payment= */ null);
         mCallback.blockUntilCalled();
         Assert.assertEquals(
                 mCallback.getStatus(),
@@ -1137,15 +981,8 @@
                 Fido2ApiTestHelper.createErrorIntent(
                         Fido2Api.CONSTRAINT_ERR, "The device is not secured with any screen lock"));
 
-        mRequest.handleMakeCredentialRequest(
-                mCreationOptions,
-                mBrowserOptions,
-                mOrigin,
-                mOrigin,
-                /* paymentOptions= */ null,
-                mCallback::onRegisterResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleMakeCredentialRequestTestHelper(
+                mCreationOptions, mBrowserOptions, mOrigin, mOrigin, /* paymentOptions= */ null);
         mCallback.blockUntilCalled();
         Assert.assertEquals(
                 mCallback.getStatus(),
@@ -1162,14 +999,8 @@
                 Fido2ApiTestHelper.createErrorIntent(
                         Fido2Api.CONSTRAINT_ERR, "The device is not secured with any screen lock"));
 
-        mRequest.handleGetCredentialRequest(
-                mRequestOptions,
-                mOrigin,
-                mOrigin,
-                /* payment= */ null,
-                mCallback::onSignResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleGetCredentialRequestTestHelper(
+                mRequestOptions, mOrigin, mOrigin, /* payment= */ null);
         mCallback.blockUntilCalled();
         Assert.assertEquals(
                 mCallback.getStatus(),
@@ -1186,15 +1017,8 @@
         mIntentSender.setNextResultIntent(
                 Fido2ApiTestHelper.createErrorIntent(errorCode, errorMsg));
 
-        mRequest.handleMakeCredentialRequest(
-                mCreationOptions,
-                mBrowserOptions,
-                mOrigin,
-                mOrigin,
-                /* paymentOptions= */ null,
-                mCallback::onRegisterResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleMakeCredentialRequestTestHelper(
+                mCreationOptions, mBrowserOptions, mOrigin, mOrigin, /* paymentOptions= */ null);
         mCallback.blockUntilCalled();
         Assert.assertEquals(mCallback.getStatus(), status);
         Fido2ApiTestHelper.verifyRespondedBeforeTimeout(mStartTimeMs);
@@ -1207,14 +1031,8 @@
         mIntentSender.setNextResultIntent(
                 Fido2ApiTestHelper.createErrorIntent(errorCode, errorMsg));
 
-        mRequest.handleGetCredentialRequest(
-                mRequestOptions,
-                mOrigin,
-                mOrigin,
-                /* payment= */ null,
-                mCallback::onSignResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleGetCredentialRequestTestHelper(
+                mRequestOptions, mOrigin, mOrigin, /* payment= */ null);
         mCallback.blockUntilCalled();
         Assert.assertEquals(mCallback.getStatus(), status);
         Fido2ApiTestHelper.verifyRespondedBeforeTimeout(mStartTimeMs);
@@ -1227,15 +1045,8 @@
             Integer errorCode, String errorMsg, Integer status) {
         mIntentSender.setNextResultIntent(Fido2ApiTestHelper.createErrorIntent(errorCode, null));
 
-        mRequest.handleMakeCredentialRequest(
-                mCreationOptions,
-                mBrowserOptions,
-                mOrigin,
-                mOrigin,
-                /* paymentOptions= */ null,
-                mCallback::onRegisterResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleMakeCredentialRequestTestHelper(
+                mCreationOptions, mBrowserOptions, mOrigin, mOrigin, /* paymentOptions= */ null);
         mCallback.blockUntilCalled();
         Assert.assertEquals(mCallback.getStatus(), status);
         Fido2ApiTestHelper.verifyRespondedBeforeTimeout(mStartTimeMs);
@@ -1248,14 +1059,8 @@
             Integer errorCode, String errorMsg, Integer status) {
         mIntentSender.setNextResultIntent(Fido2ApiTestHelper.createErrorIntent(errorCode, null));
 
-        mRequest.handleGetCredentialRequest(
-                mRequestOptions,
-                mOrigin,
-                mOrigin,
-                /* payment= */ null,
-                mCallback::onSignResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleGetCredentialRequestTestHelper(
+                mRequestOptions, mOrigin, mOrigin, /* payment= */ null);
         mCallback.blockUntilCalled();
         Assert.assertEquals(mCallback.getStatus(), status);
         Fido2ApiTestHelper.verifyRespondedBeforeTimeout(mStartTimeMs);
@@ -1274,14 +1079,7 @@
 
         PaymentOptions payment = Fido2ApiTestHelper.createPaymentOptions();
         mRequestOptions.publicKey.challenge = new byte[3];
-        mRequest.handleGetCredentialRequest(
-                mRequestOptions,
-                mOrigin,
-                mOrigin,
-                payment,
-                mCallback::onSignResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleGetCredentialRequestTestHelper(mRequestOptions, mOrigin, mOrigin, payment);
         mCallback.blockUntilCalled();
         Assert.assertEquals(mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.SUCCESS));
         Assert.assertEquals(
@@ -1304,14 +1102,7 @@
 
         PaymentOptions payment = Fido2ApiTestHelper.createPaymentOptions();
         mRequestOptions.publicKey.challenge = new byte[3];
-        mRequest.handleGetCredentialRequest(
-                mRequestOptions,
-                mOrigin,
-                mOrigin,
-                payment,
-                mCallback::onSignResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleGetCredentialRequestTestHelper(mRequestOptions, mOrigin, mOrigin, payment);
         mCallback.blockUntilCalled();
         Assert.assertEquals(
                 Integer.valueOf(AuthenticatorStatus.NOT_ALLOWED_ERROR), mCallback.getStatus());
@@ -1334,14 +1125,7 @@
 
         PaymentOptions payment = Fido2ApiTestHelper.createPaymentOptions();
         mRequestOptions.publicKey.challenge = new byte[3];
-        mRequest.handleGetCredentialRequest(
-                mRequestOptions,
-                mOrigin,
-                topOrigin,
-                payment,
-                mCallback::onSignResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleGetCredentialRequestTestHelper(mRequestOptions, mOrigin, topOrigin, payment);
         mCallback.blockUntilCalled();
         Fido2ApiTestHelper.verifyRespondedBeforeTimeout(mStartTimeMs);
 
@@ -1375,14 +1159,8 @@
 
         mRequestOptions.publicKey.allowCredentials = new PublicKeyCredentialDescriptor[0];
 
-        mRequest.handleGetCredentialRequest(
-                mRequestOptions,
-                mOrigin,
-                mOrigin,
-                /* payment= */ null,
-                mCallback::onSignResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleGetCredentialRequestTestHelper(
+                mRequestOptions, mOrigin, mOrigin, /* payment= */ null);
         mCallback.blockUntilCalled();
         Assert.assertEquals(Integer.valueOf(AuthenticatorStatus.SUCCESS), mCallback.getStatus());
         Fido2ApiTestHelper.validateGetAssertionResponse(mCallback.getGetAssertionResponse());
@@ -1402,14 +1180,8 @@
 
         mRequestOptions.publicKey.allowCredentials = new PublicKeyCredentialDescriptor[0];
 
-        mRequest.handleGetCredentialRequest(
-                mRequestOptions,
-                mOrigin,
-                mOrigin,
-                /* payment= */ null,
-                mCallback::onSignResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleGetCredentialRequestTestHelper(
+                mRequestOptions, mOrigin, mOrigin, /* payment= */ null);
         mCallback.blockUntilCalled();
         Assert.assertEquals(
                 Integer.valueOf(AuthenticatorStatus.NOT_ALLOWED_ERROR), mCallback.getStatus());
@@ -1431,20 +1203,14 @@
         mRequestOptions.publicKey.allowCredentials = new PublicKeyCredentialDescriptor[0];
         mRequestOptions.mediation = Mediation.CONDITIONAL;
 
-        mRequest.handleGetCredentialRequest(
-                mRequestOptions,
-                mOrigin,
-                mOrigin,
-                /* payment= */ null,
-                mCallback::onSignResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleGetCredentialRequestTestHelper(
+                mRequestOptions, mOrigin, mOrigin, /* payment= */ null);
         mCallback.blockUntilCalled();
         Assert.assertEquals(Integer.valueOf(AuthenticatorStatus.SUCCESS), mCallback.getStatus());
         Fido2ApiTestHelper.validateGetAssertionResponse(mCallback.getGetAssertionResponse());
         Fido2ApiTestHelper.verifyRespondedBeforeTimeout(mStartTimeMs);
         Assert.assertEquals(1, mMockBrowserBridge.getCleanupCalledCount());
-        Assert.assertEquals(mCallback.getOutcome(), null);
+        Assert.assertEquals(mCallback.getOutcome(), Integer.valueOf(GetAssertionOutcome.SUCCESS));
     }
 
     @Test
@@ -1455,14 +1221,8 @@
         mRequestOptions.publicKey.allowCredentials = new PublicKeyCredentialDescriptor[0];
         mRequestOptions.mediation = Mediation.CONDITIONAL;
 
-        mRequest.handleGetCredentialRequest(
-                mRequestOptions,
-                mOrigin,
-                mOrigin,
-                /* payment= */ null,
-                mCallback::onSignResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleGetCredentialRequestTestHelper(
+                mRequestOptions, mOrigin, mOrigin, /* payment= */ null);
         Assert.assertEquals(
                 Integer.valueOf(AuthenticatorStatus.UNKNOWN_ERROR), mCallback.getStatus());
         Assert.assertNull(mCallback.getGetAssertionResponse());
@@ -1483,14 +1243,8 @@
         mRequestOptions.publicKey.allowCredentials = new PublicKeyCredentialDescriptor[0];
         mRequestOptions.mediation = Mediation.CONDITIONAL;
 
-        mRequest.handleGetCredentialRequest(
-                mRequestOptions,
-                mOrigin,
-                mOrigin,
-                /* payment= */ null,
-                mCallback::onSignResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleGetCredentialRequestTestHelper(
+                mRequestOptions, mOrigin, mOrigin, /* payment= */ null);
         mCallback.blockUntilCalled();
         Assert.assertEquals(
                 Integer.valueOf(AuthenticatorStatus.UNKNOWN_ERROR), mCallback.getStatus());
@@ -1506,14 +1260,8 @@
 
         mFido2ApiCallHelper.setInvokeCallbackImmediately(false);
 
-        mRequest.handleGetCredentialRequest(
-                mRequestOptions,
-                mOrigin,
-                mOrigin,
-                /* payment= */ null,
-                mCallback::onSignResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleGetCredentialRequestTestHelper(
+                mRequestOptions, mOrigin, mOrigin, /* payment= */ null);
         mRequest.cancelGetAssertion();
         Assert.assertEquals(
                 Integer.valueOf(AuthenticatorStatus.ABORT_ERROR), mCallback.getStatus());
@@ -1538,14 +1286,8 @@
         mRequestOptions.publicKey.allowCredentials = new PublicKeyCredentialDescriptor[0];
         mRequestOptions.mediation = Mediation.CONDITIONAL;
 
-        mRequest.handleGetCredentialRequest(
-                mRequestOptions,
-                mOrigin,
-                mOrigin,
-                /* payment= */ null,
-                mCallback::onSignResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleGetCredentialRequestTestHelper(
+                mRequestOptions, mOrigin, mOrigin, /* payment= */ null);
         mRequest.cancelGetAssertion();
 
         Assert.assertEquals(
@@ -1569,14 +1311,8 @@
 
         mIntentSender.setInvokeCallbackImmediately(false);
 
-        mRequest.handleGetCredentialRequest(
-                mRequestOptions,
-                mOrigin,
-                mOrigin,
-                /* payment= */ null,
-                mCallback::onSignResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleGetCredentialRequestTestHelper(
+                mRequestOptions, mOrigin, mOrigin, /* payment= */ null);
         mIntentSender.blockUntilShowIntentCalled();
         mRequest.cancelGetAssertion();
         mIntentSender.invokeCallback();
@@ -1601,14 +1337,8 @@
 
         mIntentSender.setInvokeCallbackImmediately(false);
 
-        mRequest.handleGetCredentialRequest(
-                mRequestOptions,
-                mOrigin,
-                mOrigin,
-                /* payment= */ null,
-                mCallback::onSignResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleGetCredentialRequestTestHelper(
+                mRequestOptions, mOrigin, mOrigin, /* payment= */ null);
         mIntentSender.blockUntilShowIntentCalled();
         mRequest.cancelGetAssertion();
         mIntentSender.invokeCallback();
@@ -1631,14 +1361,8 @@
         mRequestOptions.publicKey.allowCredentials = new PublicKeyCredentialDescriptor[0];
         mRequestOptions.mediation = Mediation.CONDITIONAL;
 
-        mRequest.handleGetCredentialRequest(
-                mRequestOptions,
-                mOrigin,
-                mOrigin,
-                /* payment= */ null,
-                mCallback::onSignResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleGetCredentialRequestTestHelper(
+                mRequestOptions, mOrigin, mOrigin, /* payment= */ null);
         mIntentSender.blockUntilShowIntentCalled();
         // Null status indicates the callback was not invoked.
         Assert.assertNull(mCallback.getStatus());
@@ -1660,14 +1384,8 @@
         mRequestOptions.publicKey.allowCredentials = new PublicKeyCredentialDescriptor[0];
         mRequestOptions.mediation = Mediation.CONDITIONAL;
 
-        mRequest.handleGetCredentialRequest(
-                mRequestOptions,
-                mOrigin,
-                mOrigin,
-                /* payment= */ null,
-                mCallback::onSignResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleGetCredentialRequestTestHelper(
+                mRequestOptions, mOrigin, mOrigin, /* payment= */ null);
         mIntentSender.blockUntilShowIntentCalled();
         // Null status indicates the callback was not invoked.
         Assert.assertNull(mCallback.getStatus());
@@ -1695,14 +1413,8 @@
 
         mRequestOptions.mediation = Mediation.CONDITIONAL;
 
-        mRequest.handleGetCredentialRequest(
-                mRequestOptions,
-                mOrigin,
-                mOrigin,
-                /* payment= */ null,
-                mCallback::onSignResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleGetCredentialRequestTestHelper(
+                mRequestOptions, mOrigin, mOrigin, /* payment= */ null);
         mCallback.blockUntilCalled();
         Assert.assertEquals(Integer.valueOf(AuthenticatorStatus.SUCCESS), mCallback.getStatus());
         Fido2ApiTestHelper.validateGetAssertionResponse(mCallback.getGetAssertionResponse());
@@ -1724,14 +1436,8 @@
                 new PublicKeyCredentialDescriptor[] {descriptor};
         mRequestOptions.mediation = Mediation.CONDITIONAL;
 
-        mRequest.handleGetCredentialRequest(
-                mRequestOptions,
-                mOrigin,
-                mOrigin,
-                /* payment= */ null,
-                mCallback::onSignResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleGetCredentialRequestTestHelper(
+                mRequestOptions, mOrigin, mOrigin, /* payment= */ null);
         mCallback.blockUntilCalled();
         Assert.assertEquals(
                 Integer.valueOf(AuthenticatorStatus.UNKNOWN_ERROR), mCallback.getStatus());
@@ -1754,14 +1460,8 @@
         mRequestOptions.mediation = Mediation.IMMEDIATE;
         mRequestOptions.password = true;
 
-        mRequest.handleGetCredentialRequest(
-                mRequestOptions,
-                mOrigin,
-                mOrigin,
-                /* payment= */ null,
-                mCallback::onSignResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleGetCredentialRequestTestHelper(
+                mRequestOptions, mOrigin, mOrigin, /* payment= */ null);
         mCallback.blockUntilCalled();
         Assert.assertEquals(Integer.valueOf(AuthenticatorStatus.SUCCESS), mCallback.getStatus());
         Fido2ApiTestHelper.validateGetAssertionResponse(mCallback.getGetAssertionResponse());
@@ -1776,14 +1476,8 @@
         mRequestOptions.mediation = Mediation.IMMEDIATE;
         mRequestOptions.password = true;
 
-        mRequest.handleGetCredentialRequest(
-                mRequestOptions,
-                mOrigin,
-                mOrigin,
-                /* payment= */ null,
-                mCallback::onSignResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleGetCredentialRequestTestHelper(
+                mRequestOptions, mOrigin, mOrigin, /* payment= */ null);
         mCallback.blockUntilCalled();
         Assert.assertEquals(
                 Integer.valueOf(AuthenticatorStatus.NOT_ALLOWED_ERROR), mCallback.getStatus());
@@ -1803,14 +1497,8 @@
         mRequestOptions.publicKey.allowCredentials = new PublicKeyCredentialDescriptor[0];
         mRequestOptions.mediation = Mediation.IMMEDIATE;
 
-        mRequest.handleGetCredentialRequest(
-                mRequestOptions,
-                mOrigin,
-                mOrigin,
-                /* payment= */ null,
-                mCallback::onSignResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleGetCredentialRequestTestHelper(
+                mRequestOptions, mOrigin, mOrigin, /* payment= */ null);
         mCallback.blockUntilCalled();
         Assert.assertEquals(
                 Integer.valueOf(AuthenticatorStatus.NOT_ALLOWED_ERROR), mCallback.getStatus());
@@ -1832,14 +1520,8 @@
         mRequestOptions.mediation = Mediation.IMMEDIATE;
         mRequestOptions.password = true;
 
-        mRequest.handleGetCredentialRequest(
-                mRequestOptions,
-                mOrigin,
-                mOrigin,
-                /* payment= */ null,
-                mCallback::onSignResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleGetCredentialRequestTestHelper(
+                mRequestOptions, mOrigin, mOrigin, /* payment= */ null);
         mCallback.blockUntilCalled();
         Assert.assertEquals(Integer.valueOf(AuthenticatorStatus.SUCCESS), mCallback.getStatus());
         Assert.assertEquals(
@@ -1860,14 +1542,8 @@
         mRequestOptions.password = true;
         mRequestOptions.publicKey.allowCredentials = new PublicKeyCredentialDescriptor[0];
 
-        mRequest.handleGetCredentialRequest(
-                mRequestOptions,
-                mOrigin,
-                mOrigin,
-                /* payment= */ null,
-                mCallback::onSignResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleGetCredentialRequestTestHelper(
+                mRequestOptions, mOrigin, mOrigin, /* payment= */ null);
         mCallback.blockUntilCalled();
         Assert.assertEquals(
                 Integer.valueOf(AuthenticatorStatus.NOT_ALLOWED_ERROR), mCallback.getStatus());
@@ -1898,14 +1574,8 @@
         mRequestOptions.mediation = Mediation.IMMEDIATE;
         mRequestOptions.password = true;
 
-        mRequest.handleGetCredentialRequest(
-                mRequestOptions,
-                mOrigin,
-                mOrigin,
-                /* payment= */ null,
-                mCallback::onSignResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleGetCredentialRequestTestHelper(
+                mRequestOptions, mOrigin, mOrigin, /* payment= */ null);
         mCallback.blockUntilCalled();
         Assert.assertEquals(
                 Integer.valueOf(AuthenticatorStatus.NOT_ALLOWED_ERROR), mCallback.getStatus());
@@ -1913,92 +1583,6 @@
 
     @Test
     @SmallTest
-    public void testGetMatchingCredentialIds_success() {
-        String relyingPartyId = "subdomain.example.test";
-        byte[][] allowCredentialIds =
-                new byte[][] {
-                    {1, 2, 3},
-                    {10, 11, 12},
-                    {13, 14, 15},
-                };
-        boolean requireThirdPartyPayment = false;
-
-        WebauthnCredentialDetails credential1 = Fido2ApiTestHelper.getCredentialDetails();
-        credential1.mCredentialId = new byte[] {1, 2, 3};
-        credential1.mIsPayment = true;
-
-        WebauthnCredentialDetails credential2 = Fido2ApiTestHelper.getCredentialDetails();
-        credential2.mCredentialId = new byte[] {4, 5, 6};
-        credential2.mIsPayment = false;
-
-        WebauthnCredentialDetails credential3 = Fido2ApiTestHelper.getCredentialDetails();
-        credential3.mCredentialId = new byte[] {7, 8, 9};
-        credential3.mIsPayment = true;
-
-        WebauthnCredentialDetails credential4 = Fido2ApiTestHelper.getCredentialDetails();
-        credential4.mCredentialId = new byte[] {10, 11, 12};
-        credential4.mIsPayment = false;
-
-        mFido2ApiCallHelper.setReturnedCredentialDetails(
-                Arrays.asList(credential1, credential2, credential3, credential4));
-
-        mRequest.handleGetMatchingCredentialIdsRequest(
-                relyingPartyId,
-                allowCredentialIds,
-                requireThirdPartyPayment,
-                mCallback::onGetMatchingCredentialIds,
-                mCallback::onError);
-        mCallback.blockUntilCalled();
-        Assert.assertArrayEquals(
-                mCallback.getGetMatchingCredentialIdsResponse().toArray(),
-                new byte[][] {{1, 2, 3}, {10, 11, 12}});
-    }
-
-    @Test
-    @SmallTest
-    public void testGetMatchingCredentialIds_requireThirdPartyBit() {
-        String relyingPartyId = "subdomain.example.test";
-        byte[][] allowCredentialIds =
-                new byte[][] {
-                    {1, 2, 3},
-                    {10, 11, 12},
-                    {13, 14, 15},
-                };
-        boolean requireThirdPartyPayment = true;
-
-        WebauthnCredentialDetails credential1 = Fido2ApiTestHelper.getCredentialDetails();
-        credential1.mCredentialId = new byte[] {1, 2, 3};
-        credential1.mIsPayment = true;
-
-        WebauthnCredentialDetails credential2 = Fido2ApiTestHelper.getCredentialDetails();
-        credential2.mCredentialId = new byte[] {4, 5, 6};
-        credential2.mIsPayment = false;
-
-        WebauthnCredentialDetails credential3 = Fido2ApiTestHelper.getCredentialDetails();
-        credential3.mCredentialId = new byte[] {7, 8, 9};
-        credential3.mIsPayment = true;
-
-        WebauthnCredentialDetails credential4 = Fido2ApiTestHelper.getCredentialDetails();
-        credential4.mCredentialId = new byte[] {10, 11, 12};
-        credential4.mIsPayment = false;
-
-        mFido2ApiCallHelper.setReturnedCredentialDetails(
-                Arrays.asList(credential1, credential2, credential3, credential4));
-
-        mRequest.handleGetMatchingCredentialIdsRequest(
-                relyingPartyId,
-                allowCredentialIds,
-                requireThirdPartyPayment,
-                mCallback::onGetMatchingCredentialIds,
-                mCallback::onError);
-        mCallback.blockUntilCalled();
-        Assert.assertArrayEquals(
-                mCallback.getGetMatchingCredentialIdsResponse().toArray(),
-                new byte[][] {{1, 2, 3}});
-    }
-
-    @Test
-    @SmallTest
     public void testGetAssertion_conditionalUiHybrid_success() {
         if (!GmsCoreUtils.isHybridClientApiSupported()) {
             return;
@@ -2014,14 +1598,8 @@
         mRequestOptions.publicKey.allowCredentials = new PublicKeyCredentialDescriptor[0];
         mRequestOptions.mediation = Mediation.CONDITIONAL;
 
-        mRequest.handleGetCredentialRequest(
-                mRequestOptions,
-                mOrigin,
-                mOrigin,
-                /* payment= */ null,
-                mCallback::onSignResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleGetCredentialRequestTestHelper(
+                mRequestOptions, mOrigin, mOrigin, /* payment= */ null);
         mCallback.blockUntilCalled();
         Assert.assertEquals(Integer.valueOf(AuthenticatorStatus.SUCCESS), mCallback.getStatus());
         Fido2ApiTestHelper.validateGetAssertionResponse(mCallback.getGetAssertionResponse());
@@ -2120,14 +1698,8 @@
         mFrameHost.setLastCommittedUrl(new GURL("https://www.example.com"));
 
         mRequestOptions.publicKey.challenge = new byte[3];
-        mRequest.handleGetCredentialRequest(
-                mRequestOptions,
-                mOrigin,
-                mOrigin,
-                /* payment= */ null,
-                mCallback::onSignResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleGetCredentialRequestTestHelper(
+                mRequestOptions, mOrigin, mOrigin, /* payment= */ null);
         mCallback.blockUntilCalled();
         Assert.assertEquals(mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.SUCCESS));
         Assert.assertEquals(
@@ -2149,15 +1721,8 @@
 
         mFrameHost.setLastCommittedUrl(new GURL("https://www.example.com"));
 
-        mRequest.handleMakeCredentialRequest(
-                mCreationOptions,
-                mBrowserOptions,
-                mOrigin,
-                mOrigin,
-                /* paymentOptions= */ null,
-                mCallback::onRegisterResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleMakeCredentialRequestTestHelper(
+                mCreationOptions, mBrowserOptions, mOrigin, mOrigin, /* paymentOptions= */ null);
         mCallback.blockUntilCalled();
         Assert.assertEquals(mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.SUCCESS));
         Assert.assertEquals(
@@ -2184,15 +1749,8 @@
         PaymentOptions payment = Fido2ApiTestHelper.createPaymentOptions();
         mCreationOptions.isPaymentCredentialCreation = true;
 
-        mRequest.handleMakeCredentialRequest(
-                mCreationOptions,
-                mBrowserOptions,
-                mOrigin,
-                mOrigin,
-                payment,
-                mCallback::onRegisterResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleMakeCredentialRequestTestHelper(
+                mCreationOptions, mBrowserOptions, mOrigin, mOrigin, payment);
         mCallback.blockUntilCalled();
 
         Mockito.verify(mClientDataJsonImplMock, Mockito.times(1))
@@ -2232,15 +1790,8 @@
         // called instead of CredManHelper.
         mCreationOptions.authenticatorSelection.residentKey = ResidentKeyRequirement.DISCOURAGED;
 
-        mRequest.handleMakeCredentialRequest(
-                mCreationOptions,
-                mBrowserOptions,
-                mOrigin,
-                mOrigin,
-                payment,
-                mCallback::onRegisterResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleMakeCredentialRequestTestHelper(
+                mCreationOptions, mBrowserOptions, mOrigin, mOrigin, payment);
         mCallback.blockUntilCalled();
 
         Mockito.verify(mClientDataJsonImplMock, Mockito.times(1))
@@ -2290,15 +1841,8 @@
                 sameOriginWithAncestors;
 
         mFrameHost.setLastCommittedUrl(new GURL("https://www.example.com"));
-        mRequest.handleMakeCredentialRequest(
-                mCreationOptions,
-                mBrowserOptions,
-                mOrigin,
-                mOrigin,
-                /* paymentOptions= */ null,
-                mCallback::onRegisterResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleMakeCredentialRequestTestHelper(
+                mCreationOptions, mBrowserOptions, mOrigin, mOrigin, /* paymentOptions= */ null);
         mCallback.blockUntilCalled();
 
         Mockito.verify(mClientDataJsonImplMock, Mockito.times(1))
@@ -2353,14 +1897,8 @@
                 sameOriginWithAncestors;
 
         mFrameHost.setLastCommittedUrl(new GURL("https://www.example.com"));
-        mRequest.handleGetCredentialRequest(
-                mRequestOptions,
-                mOrigin,
-                mOrigin,
-                /* payment= */ null,
-                mCallback::onSignResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        handleGetCredentialRequestTestHelper(
+                mRequestOptions, mOrigin, mOrigin, /* payment= */ null);
         mCallback.blockUntilCalled();
 
         Mockito.verify(mClientDataJsonImplMock, Mockito.times(1))
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/webauth/GetMatchingCredentialsDelegateTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/webauth/GetMatchingCredentialsDelegateTest.java
new file mode 100644
index 0000000..3277132
--- /dev/null
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/webauth/GetMatchingCredentialsDelegateTest.java
@@ -0,0 +1,203 @@
+// 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.webauth;
+
+import android.os.ConditionVariable;
+
+import androidx.test.filters.SmallTest;
+
+import com.google.android.gms.tasks.OnFailureListener;
+import com.google.android.gms.tasks.OnSuccessListener;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import org.chromium.base.ContextUtils;
+import org.chromium.base.test.util.Batch;
+import org.chromium.base.test.util.CommandLineFlags;
+import org.chromium.base.test.util.Restriction;
+import org.chromium.chrome.browser.flags.ChromeSwitches;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
+import org.chromium.components.webauthn.AuthenticationContextProvider;
+import org.chromium.components.webauthn.Fido2ApiCallHelper;
+import org.chromium.components.webauthn.Fido2ApiTestHelper;
+import org.chromium.components.webauthn.FidoIntentSender;
+import org.chromium.components.webauthn.GetMatchingCredentialIdsDelegate;
+import org.chromium.components.webauthn.GetMatchingCredentialIdsDelegate.ResponseCallback;
+import org.chromium.components.webauthn.WebauthnCredentialDetails;
+import org.chromium.components.webauthn.WebauthnRequestCallback;
+import org.chromium.content_public.browser.RenderFrameHost;
+import org.chromium.content_public.browser.WebContents;
+import org.chromium.content_public.common.ContentSwitches;
+import org.chromium.ui.test.util.GmsCoreVersionRestriction;
+
+import java.util.Arrays;
+import java.util.List;
+
+/** Unit tests for {@link GetMatchingCredentialIdsDelegate}. */
+@RunWith(ChromeJUnit4ClassRunner.class)
+@CommandLineFlags.Add({
+    ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
+    ContentSwitches.HOST_RESOLVER_RULES + "=MAP * 127.0.0.1",
+    "ignore-certificate-errors",
+})
+@Batch(Batch.PER_CLASS)
+@Restriction(GmsCoreVersionRestriction.RESTRICTION_TYPE_VERSION_GE_19W13)
+public class GetMatchingCredentialsDelegateTest {
+    @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+    private AuthenticationContextProvider mAuthenticationContextProvider;
+    private GetMatchingCredentialIdsCallback mCallback;
+    private MockFido2ApiCallHelper mFido2ApiCallHelper;
+
+    private static final byte[] CREDENTIAL_ID_1 = new byte[] {1, 11, 101};
+    private static final byte[] CREDENTIAL_ID_2 = new byte[] {2, 22, 102};
+    private static final byte[] CREDENTIAL_ID_3 = new byte[] {3, 33, 103};
+    private static final byte[] CREDENTIAL_ID_4 = new byte[] {4, 44, 104};
+    private static final byte[] CREDENTIAL_ID_5 = new byte[] {5, 55, 105};
+
+    private static List<WebauthnCredentialDetails> getAllCredentials() {
+        WebauthnCredentialDetails credential1 = Fido2ApiTestHelper.getCredentialDetails();
+        credential1.mCredentialId = CREDENTIAL_ID_1;
+        credential1.mIsPayment = true;
+
+        WebauthnCredentialDetails credential2 = Fido2ApiTestHelper.getCredentialDetails();
+        credential2.mCredentialId = CREDENTIAL_ID_2;
+        credential2.mIsPayment = false;
+
+        WebauthnCredentialDetails credential3 = Fido2ApiTestHelper.getCredentialDetails();
+        credential3.mCredentialId = CREDENTIAL_ID_3;
+        credential3.mIsPayment = true;
+
+        WebauthnCredentialDetails credential4 = Fido2ApiTestHelper.getCredentialDetails();
+        credential4.mCredentialId = CREDENTIAL_ID_4;
+        credential4.mIsPayment = false;
+        return Arrays.asList(credential1, credential2, credential3, credential4);
+    }
+
+    private static class MockFido2ApiCallHelper extends Fido2ApiCallHelper {
+        private List<WebauthnCredentialDetails> mReturnedCredentialDetails;
+
+        @Override
+        public void invokeFido2GetCredentials(
+                AuthenticationContextProvider authenticationContextProvider,
+                String relyingPartyId,
+                OnSuccessListener<List<WebauthnCredentialDetails>> successCallback,
+                OnFailureListener failureCallback) {
+            successCallback.onSuccess(mReturnedCredentialDetails);
+        }
+
+        @Override
+        public boolean arePlayServicesAvailable() {
+            return true;
+        }
+
+        public void setReturnedCredentialDetails(List<WebauthnCredentialDetails> details) {
+            mReturnedCredentialDetails = details;
+        }
+    }
+
+    private static class GetMatchingCredentialIdsCallback implements ResponseCallback {
+        private final ConditionVariable mCv = new ConditionVariable();
+        private List<byte[]> mResponse;
+
+        @Override
+        public void onResponse(List<byte[]> matchingCredentialIds) {
+            mResponse = matchingCredentialIds;
+            mCv.open();
+        }
+
+        public void blockUntilCalled() {
+            mCv.block();
+        }
+
+        public List<byte[]> getResponse() {
+            return mResponse;
+        }
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mAuthenticationContextProvider =
+                new AuthenticationContextProvider() {
+                    @Override
+                    public android.content.Context getContext() {
+                        return ContextUtils.getApplicationContext();
+                    }
+
+                    @Override
+                    public RenderFrameHost getRenderFrameHost() {
+                        return null;
+                    }
+
+                    @Override
+                    public FidoIntentSender getIntentSender() {
+                        return null;
+                    }
+
+                    @Override
+                    public WebContents getWebContents() {
+                        return null;
+                    }
+
+                    @Override
+                    public WebauthnRequestCallback getRequestCallback() {
+                        return null;
+                    }
+                };
+        mCallback = new GetMatchingCredentialIdsCallback();
+        mFido2ApiCallHelper = new MockFido2ApiCallHelper();
+        Fido2ApiCallHelper.overrideInstanceForTesting(mFido2ApiCallHelper);
+    }
+
+    @Test
+    @SmallTest
+    public void testGetMatchingCredentialIds_success() {
+        String relyingPartyId = "subdomain.example.test";
+        byte[][] allowCredentialIds =
+                new byte[][] {
+                    CREDENTIAL_ID_1, CREDENTIAL_ID_4, CREDENTIAL_ID_5,
+                };
+
+        mFido2ApiCallHelper.setReturnedCredentialDetails(getAllCredentials());
+
+        GetMatchingCredentialIdsDelegate.getInstance()
+                .getMatchingCredentialIds(
+                        mAuthenticationContextProvider,
+                        relyingPartyId,
+                        allowCredentialIds,
+                        /* requireThirdPartyPayment= */ false,
+                        mCallback);
+        mCallback.blockUntilCalled();
+        Assert.assertArrayEquals(
+                mCallback.getResponse().toArray(), new byte[][] {CREDENTIAL_ID_1, CREDENTIAL_ID_4});
+    }
+
+    @Test
+    @SmallTest
+    public void testGetMatchingCredentialIds_requireThirdPartyBit() {
+        String relyingPartyId = "subdomain.example.test";
+        byte[][] allowCredentialIds =
+                new byte[][] {CREDENTIAL_ID_1, CREDENTIAL_ID_4, CREDENTIAL_ID_5};
+        boolean requireThirdPartyPayment = true;
+
+        mFido2ApiCallHelper.setReturnedCredentialDetails(getAllCredentials());
+
+        GetMatchingCredentialIdsDelegate.getInstance()
+                .getMatchingCredentialIds(
+                        mAuthenticationContextProvider,
+                        relyingPartyId,
+                        allowCredentialIds,
+                        /* requireThirdPartyPayment= */ true,
+                        mCallback);
+        mCallback.blockUntilCalled();
+        Assert.assertArrayEquals(mCallback.getResponse().toArray(), new byte[][] {CREDENTIAL_ID_1});
+    }
+}
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheetControllerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheetControllerTest.java
index b83cf72..88f229c 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheetControllerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheetControllerTest.java
@@ -705,33 +705,6 @@
 
     @Test
     @MediumTest
-    public void testHeightUpdatedWithHalfRatioChanged() {
-        float customHalfHeight = 0.3f;
-        // Disable peek state, so that the opening state will be half state.
-        mLowPriorityContent.setPeekHeight(BottomSheetContent.HeightMode.DISABLED);
-        mLowPriorityContent.setHalfHeightRatio(customHalfHeight);
-        requestContentInSheet(mLowPriorityContent, true);
-
-        expandSheet();
-
-        customHalfHeight = 0.6f;
-        mLowPriorityContent.setHalfHeightRatio(customHalfHeight);
-
-        ThreadUtils.runOnUiThreadBlocking(
-                () -> {
-                    mSheetController.updateSheetHeight();
-                    mTestSupport.endAllAnimations();
-                });
-
-        int computedOffset = (int) (customHalfHeight * mSheetController.getContainerHeight());
-        assertEquals(
-                "Half height is incorrect after half ratio changed.",
-                computedOffset,
-                mSheetController.getCurrentOffset());
-    }
-
-    @Test
-    @MediumTest
     public void testCustomFullRatio() {
         final float customFullHeight = 0.5f;
         mLowPriorityContent.setFullHeightRatio(customFullHeight);
@@ -748,31 +721,6 @@
 
     @Test
     @MediumTest
-    public void testHeightUpdatedWithFullRatioChanged() {
-        float customFullHeight = 0.5f;
-        mLowPriorityContent.setFullHeightRatio(customFullHeight);
-        requestContentInSheet(mLowPriorityContent, true);
-
-        maximizeSheet();
-
-        customFullHeight = 0.8f;
-        mLowPriorityContent.setFullHeightRatio(customFullHeight);
-
-        ThreadUtils.runOnUiThreadBlocking(
-                () -> {
-                    mSheetController.updateSheetHeight(SheetState.FULL);
-                    mTestSupport.endAllAnimations();
-                });
-
-        int computedOffset = (int) (customFullHeight * mSheetController.getContainerHeight());
-        assertEquals(
-                "Full height is incorrect for custom ratio.",
-                computedOffset,
-                mSheetController.getCurrentOffset());
-    }
-
-    @Test
-    @MediumTest
     public void testExpandWithDisabledHalfState() {
         mLowPriorityContent.setHalfHeightRatio(BottomSheetContent.HeightMode.DISABLED);
         requestContentInSheet(mLowPriorityContent, true);
@@ -787,44 +735,6 @@
 
     @Test
     @MediumTest
-    public void testUpdateSheetHeightWithDisabledHalfState() {
-        mLowPriorityContent.setHalfHeightRatio(BottomSheetContent.HeightMode.DISABLED);
-        requestContentInSheet(mLowPriorityContent, true);
-
-        ThreadUtils.runOnUiThreadBlocking(
-                () -> {
-                    mSheetController.updateSheetHeight(SheetState.HALF);
-                    mTestSupport.endAllAnimations();
-                });
-
-        assertEquals(
-                "The bottom sheet should be at the full state when half is disabled.",
-                SheetState.FULL,
-                mSheetController.getSheetState());
-    }
-
-    @Test
-    @MediumTest
-    public void testUpdateSheetHeightUpdatesSheetState() {
-        requestContentInSheet(mLowPriorityContent, true);
-
-        expandSheet();
-
-        ThreadUtils.runOnUiThreadBlocking(
-                () -> {
-                    mSheetController.updateSheetHeight(SheetState.FULL);
-                    mTestSupport.endAllAnimations();
-                });
-
-        assertEquals(
-                "The bottom sheet should be at the full state when height is updated to full"
-                        + " state.",
-                SheetState.FULL,
-                mSheetController.getSheetState());
-    }
-
-    @Test
-    @MediumTest
     public void testCollapseSheet() {
         requestContentInSheet(mLowPriorityContent, true);
 
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/chrome_item_picker/TabItemPickerCoordinatorUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/chrome_item_picker/TabItemPickerCoordinatorUnitTest.java
index 0438706..f6fa47e 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/chrome_item_picker/TabItemPickerCoordinatorUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/chrome_item_picker/TabItemPickerCoordinatorUnitTest.java
@@ -54,6 +54,7 @@
 import org.chromium.chrome.browser.tasks.tab_management.TabListEditorItemSelectionId;
 import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
 import org.chromium.components.browser_ui.widget.selectable_list.SelectionDelegate;
+import org.chromium.content_public.browser.RenderWidgetHostView;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.url.GURL;
 import org.chromium.url.JUnitTestGURLs;
@@ -83,6 +84,7 @@
     @Mock private WindowManager mWindowManager;
     @Mock private TabModel mRegularTabModel;
     @Mock private PageContentExtractionService mPageContentExtractionService;
+    @Mock private WebContents mWebContents;
 
     @Mock private SelectionDelegate<TabListEditorItemSelectionId> mSelectionDelegateMock;
     @Mock private ObservableSupplier<Boolean> mBackPressChangedSupplierMock;
@@ -211,7 +213,10 @@
         GURL testUrl = JUnitTestGURLs.URL_1;
         Tab tab1WithWebContents = Mockito.mock(Tab.class);
         when(tab1WithWebContents.getId()).thenReturn(101);
-        when(tab1WithWebContents.getWebContents()).thenReturn(Mockito.mock(WebContents.class));
+        when(tab1WithWebContents.getWebContents()).thenReturn(mWebContents);
+        when(mWebContents.isLoading()).thenReturn(false);
+        when(mWebContents.getRenderWidgetHostView())
+                .thenReturn(Mockito.mock(RenderWidgetHostView.class));
         when(tab1WithWebContents.isInitialized()).thenReturn(true);
         when(tab1WithWebContents.isFrozen()).thenReturn(false);
         when(tab1WithWebContents.getUrl()).thenReturn(testUrl);
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/notifications/tips/TipsOptInCoordinatorUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/notifications/tips/TipsOptInCoordinatorUnitTest.java
index 61a2f75..ba55fae8 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/notifications/tips/TipsOptInCoordinatorUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/notifications/tips/TipsOptInCoordinatorUnitTest.java
@@ -4,6 +4,7 @@
 
 package org.chromium.chrome.browser.notifications.tips;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.any;
@@ -11,9 +12,13 @@
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.verify;
 
+import static org.chromium.chrome.browser.notifications.tips.TipsUtils.LOGO_IMAGE_MAX_WIDTH_RATIO;
 import static org.chromium.chrome.browser.preferences.ChromePreferenceKeys.TIPS_NOTIFICATIONS_OPT_IN_PROMO_SHOWN;
 
 import android.app.Activity;
+import android.content.res.Configuration;
+import android.view.View;
+import android.view.ViewGroup;
 
 import androidx.test.filters.SmallTest;
 
@@ -29,6 +34,7 @@
 import org.robolectric.Robolectric;
 import org.robolectric.annotation.Config;
 
+import org.chromium.base.MathUtils;
 import org.chromium.base.shared_preferences.SharedPreferencesManager;
 import org.chromium.base.test.BaseRobolectricTestRunner;
 import org.chromium.base.test.util.HistogramWatcher;
@@ -38,11 +44,16 @@
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController.StateChangeReason;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetObserver;
+import org.chromium.ui.base.DeviceFormFactor;
+import org.chromium.ui.base.ViewUtils;
 
 /** Unit tests for {@link TipsOptInCoordinator}. */
 @RunWith(BaseRobolectricTestRunner.class)
 @Config(manifest = Config.NONE)
 public class TipsOptInCoordinatorUnitTest {
+    private static final int NARROW_SCREEN_WIDTH_DP = 300;
+    private static final int WIDE_SCREEN_WIDTH_DP = DeviceFormFactor.MINIMUM_TABLET_WIDTH_DP;
+
     @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
     @Mock private BottomSheetController mBottomSheetController;
 
@@ -52,12 +63,14 @@
     private TipsOptInCoordinator mTipsOptInCoordinator;
     private TipsOptInSheetContent mBottomSheetContent;
     private SharedPreferencesManager mSharedPreferenceManager;
+    private View mView;
 
     @Before
     public void setUp() {
         mActivity = Robolectric.buildActivity(Activity.class).create().get();
         mActivity.setTheme(R.style.Theme_BrowserUI_DayNight);
         mTipsOptInCoordinator = new TipsOptInCoordinator(mActivity, mBottomSheetController);
+        mView = mTipsOptInCoordinator.getViewForTesting();
         verify(mBottomSheetController).addObserver(mBottomSheetObserverCaptor.capture());
         mBottomSheetContent = mTipsOptInCoordinator.getBottomSheetContentForTesting();
         mSharedPreferenceManager = ChromeSharedPreferences.getInstance();
@@ -123,4 +136,46 @@
 
         histogramWatcher.assertExpected();
     }
+
+    @Test
+    public void testConfigurationChangeScalesImageLogo() {
+        // Set up a scenario where context configuration and parameter configuration differ
+        // to verify that the parameter configuration is used.
+
+        // Default context configuration should be narrow screen (phone).
+        // Assert that the width is match parent for portrait phones.
+        assertEquals(
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                mView.findViewById(R.id.opt_in_logo).getLayoutParams().width);
+
+        // Create a configuration parameter with wide screen (tablet)
+        Configuration tabletConfig = new Configuration();
+        tabletConfig.screenWidthDp = WIDE_SCREEN_WIDTH_DP;
+        tabletConfig.orientation = Configuration.ORIENTATION_LANDSCAPE;
+
+        // Simulate configuration change with tablet config parameter
+        // while context still has phone config.
+        mTipsOptInCoordinator.triggerConfigurationChangeForTesting(tabletConfig);
+
+        // Should now scale (tablet behavior) - proving it uses parameter config.
+        assertEquals(
+                Math.round(
+                        ViewUtils.dpToPx(mActivity, WIDE_SCREEN_WIDTH_DP)
+                                * LOGO_IMAGE_MAX_WIDTH_RATIO),
+                mView.findViewById(R.id.opt_in_logo).getLayoutParams().width,
+                MathUtils.EPSILON);
+
+        // Create another configuration parameter with narrow screen (phone).
+        Configuration phoneConfig = new Configuration();
+        phoneConfig.screenWidthDp = NARROW_SCREEN_WIDTH_DP;
+        phoneConfig.orientation = Configuration.ORIENTATION_PORTRAIT;
+
+        // Simulate configuration change back to phone config.
+        mTipsOptInCoordinator.triggerConfigurationChangeForTesting(phoneConfig);
+
+        // Should now be match parent (phone behavior).
+        assertEquals(
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                mView.findViewById(R.id.opt_in_logo).getLayoutParams().width);
+    }
 }
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/notifications/tips/TipsPromoCoordinatorUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/notifications/tips/TipsPromoCoordinatorUnitTest.java
index 56093fd..1cabf2bb 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/notifications/tips/TipsPromoCoordinatorUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/notifications/tips/TipsPromoCoordinatorUnitTest.java
@@ -11,9 +11,13 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import static org.chromium.chrome.browser.notifications.tips.TipsUtils.LOGO_IMAGE_MAX_WIDTH_RATIO;
+
 import android.app.Activity;
 import android.content.Intent;
+import android.content.res.Configuration;
 import android.view.View;
+import android.view.ViewGroup;
 
 import androidx.test.filters.SmallTest;
 
@@ -29,6 +33,7 @@
 import org.robolectric.Robolectric;
 import org.robolectric.annotation.Config;
 
+import org.chromium.base.MathUtils;
 import org.chromium.base.test.BaseRobolectricTestRunner;
 import org.chromium.base.test.util.HistogramWatcher;
 import org.chromium.base.test.util.UserActionTester;
@@ -43,6 +48,8 @@
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController.StateChangeReason;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetObserver;
 import org.chromium.components.browser_ui.settings.SettingsNavigation;
+import org.chromium.ui.base.DeviceFormFactor;
+import org.chromium.ui.base.ViewUtils;
 import org.chromium.ui.base.WindowAndroid;
 import org.chromium.ui.modelutil.PropertyModel;
 
@@ -50,6 +57,9 @@
 @RunWith(BaseRobolectricTestRunner.class)
 @Config(manifest = Config.NONE)
 public class TipsPromoCoordinatorUnitTest {
+    private static final int NARROW_SCREEN_WIDTH_DP = 300;
+    private static final int WIDE_SCREEN_WIDTH_DP = DeviceFormFactor.MINIMUM_TABLET_WIDTH_DP;
+
     @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
     @Mock private BottomSheetController mBottomSheetController;
     @Mock private QuickDeleteController mQuickDeleteController;
@@ -340,6 +350,48 @@
         histogramWatcher.assertExpected();
     }
 
+    @Test
+    public void testConfigurationChangeScalesImageLogo() {
+        // Set up a scenario where context configuration and parameter configuration differ
+        // to verify that the parameter configuration is used.
+
+        // Default context configuration should be narrow screen (phone).
+        // Assert that the width is match parent for portrait phones.
+        assertEquals(
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                mView.findViewById(R.id.main_page_logo).getLayoutParams().width);
+
+        // Create a configuration parameter with wide screen (tablet)
+        Configuration tabletConfig = new Configuration();
+        tabletConfig.screenWidthDp = WIDE_SCREEN_WIDTH_DP;
+        tabletConfig.orientation = Configuration.ORIENTATION_LANDSCAPE;
+
+        // Simulate configuration change with tablet config parameter
+        // while context still has phone config.
+        mTipsPromoCoordinator.triggerConfigurationChangeForTesting(tabletConfig);
+
+        // Should now scale (tablet behavior) - proving it uses parameter config.
+        assertEquals(
+                Math.round(
+                        ViewUtils.dpToPx(mActivity, WIDE_SCREEN_WIDTH_DP)
+                                * LOGO_IMAGE_MAX_WIDTH_RATIO),
+                mView.findViewById(R.id.main_page_logo).getLayoutParams().width,
+                MathUtils.EPSILON);
+
+        // Create another configuration parameter with narrow screen (phone).
+        Configuration phoneConfig = new Configuration();
+        phoneConfig.screenWidthDp = NARROW_SCREEN_WIDTH_DP;
+        phoneConfig.orientation = Configuration.ORIENTATION_PORTRAIT;
+
+        // Simulate configuration change back to phone config.
+        mTipsPromoCoordinator.triggerConfigurationChangeForTesting(phoneConfig);
+
+        // Should now be match parent (phone behavior).
+        assertEquals(
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                mView.findViewById(R.id.main_page_logo).getLayoutParams().width);
+    }
+
     private void setUpTipsPromoCoordinator(@TipsNotificationsFeatureType int featureType) {
         mTipsPromoCoordinator =
                 new TipsPromoCoordinator(
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/notifications/tips/TipsUtilsUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/notifications/tips/TipsUtilsUnitTest.java
index 3db15b6b..9982a25 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/notifications/tips/TipsUtilsUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/notifications/tips/TipsUtilsUnitTest.java
@@ -86,7 +86,7 @@
                 mActivity.getString(R.string.tips_promo_bottom_sheet_third_step_quick_delete),
                 promoData.detailPageSteps.get(2));
         assertEquals(
-                mActivity.getString(R.string.tips_promo_bottom_sheet_title_quick_delete),
+                mActivity.getString(R.string.tips_promo_bottom_sheet_title_quick_delete_short),
                 promoData.detailPageTitle);
     }
 
diff --git a/chrome/android/profiles/arm.newest.txt b/chrome/android/profiles/arm.newest.txt
index 7e9ff25d..1aa50ab3a 100644
--- a/chrome/android/profiles/arm.newest.txt
+++ b/chrome/android/profiles/arm.newest.txt
@@ -1 +1 @@
-chromeos-chrome-arm-144.0.7552.0_pre1551373_rc-r1-merged.afdo.bz2
+chromeos-chrome-arm-144.0.7558.0_pre1552116_rc-r1-merged.afdo.bz2
diff --git a/chrome/android/profiles/newest.txt b/chrome/android/profiles/newest.txt
index 0cda043..cf9af6d 100644
--- a/chrome/android/profiles/newest.txt
+++ b/chrome/android/profiles/newest.txt
@@ -1 +1 @@
-chromeos-chrome-amd64-144.0.7552.0_pre1551373_rc-r1-merged.afdo.bz2
+chromeos-chrome-amd64-144.0.7558.0_pre1552116_rc-r1-merged.afdo.bz2
diff --git a/chrome/app/vector_icons/BUILD.gn b/chrome/app/vector_icons/BUILD.gn
index c30fef5..855d764 100644
--- a/chrome/app/vector_icons/BUILD.gn
+++ b/chrome/app/vector_icons/BUILD.gn
@@ -68,6 +68,7 @@
     "developer_tools.icon",
     "devices.icon",
     "devices_chrome_refresh.icon",
+    "dock_to_right_spark.icon",
     "download_in_progress_chrome_refresh.icon",
     "download_in_progress_touch.icon",
     "download_menu.icon",
diff --git a/chrome/app/vector_icons/dock_to_right_spark.icon b/chrome/app/vector_icons/dock_to_right_spark.icon
new file mode 100644
index 0000000..56090d97
--- /dev/null
+++ b/chrome/app/vector_icons/dock_to_right_spark.icon
@@ -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.
+
+CANVAS_DIMENSIONS, 20,
+FILL_RULE_NONZERO,
+MOVE_TO, 10.42f, 2.5f,
+CUBIC_TO, 10.31f, 2.76f, 10.22f, 3.03f, 10.15f, 3.31f,
+CUBIC_TO, 10.09f, 3.59f, 10.05f, 3.88f, 10.02f, 4.17f,
+H_LINE_TO, 8.92f,
+V_LINE_TO, 15.83f,
+H_LINE_TO, 15.83f,
+V_LINE_TO, 9.98f,
+CUBIC_TO, 16.12f, 9.95f, 16.41f, 9.91f, 16.69f, 9.85f,
+CUBIC_TO, 16.97f, 9.79f, 17.24f, 9.69f, 17.5f, 9.58f,
+V_LINE_TO, 15.83f,
+CUBIC_TO, 17.5f, 16.29f, 17.33f, 16.69f, 17, 17.02f,
+CUBIC_TO, 16.68f, 17.34f, 16.29f, 17.5f, 15.83f, 17.5f,
+H_LINE_TO, 4.17f,
+CUBIC_TO, 3.71f, 17.5f, 3.31f, 17.34f, 2.98f, 17.02f,
+CUBIC_TO, 2.66f, 16.69f, 2.5f, 16.29f, 2.5f, 15.83f,
+V_LINE_TO, 4.17f,
+CUBIC_TO, 2.5f, 3.71f, 2.66f, 3.32f, 2.98f, 3,
+CUBIC_TO, 3.31f, 2.67f, 3.71f, 2.5f, 4.17f, 2.5f,
+H_LINE_TO, 10.42f,
+CLOSE,
+MOVE_TO, 4.17f, 4,
+V_LINE_TO, 16,
+H_LINE_TO, 7.17f,
+V_LINE_TO, 4,
+H_LINE_TO, 4.17f,
+CLOSE,
+MOVE_TO, 15.42f, 0.83f,
+CUBIC_TO, 15.44f, 0.83f, 15.5f, 0.88f, 15.58f, 0.96f,
+CUBIC_TO, 15.82f, 1.81f, 16.24f, 2.54f, 16.85f, 3.17f,
+CUBIC_TO, 17.47f, 3.78f, 18.19f, 4.19f, 19.04f, 4.42f,
+CUBIC_TO, 19.07f, 4.42f, 19.11f, 4.47f, 19.17f, 4.58f,
+CUBIC_TO, 19.17f, 4.6f, 19.13f, 4.65f, 19.04f, 4.75f,
+CUBIC_TO, 18.19f, 4.97f, 17.46f, 5.4f, 16.83f, 6.02f,
+CUBIC_TO, 16.22f, 6.63f, 15.81f, 7.36f, 15.58f, 8.21f,
+CUBIC_TO, 15.58f, 8.24f, 15.53f, 8.28f, 15.42f, 8.33f,
+CUBIC_TO, 15.4f, 8.33f, 15.35f, 8.29f, 15.25f, 8.21f,
+CUBIC_TO, 15.03f, 7.36f, 14.6f, 6.63f, 13.98f, 6.02f,
+CUBIC_TO, 13.37f, 5.4f, 12.64f, 4.97f, 11.79f, 4.75f,
+CUBIC_TO, 11.76f, 4.74f, 11.72f, 4.68f, 11.67f, 4.58f,
+CUBIC_TO, 11.67f, 4.56f, 11.71f, 4.5f, 11.79f, 4.42f,
+CUBIC_TO, 12.64f, 4.19f, 13.37f, 3.78f, 13.98f, 3.17f,
+CUBIC_TO, 14.6f, 2.54f, 15.03f, 1.81f, 15.25f, 0.96f,
+CUBIC_TO, 15.26f, 0.93f, 15.32f, 0.89f, 15.42f, 0.83f,
+CLOSE
\ No newline at end of file
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 51505c2c..4f2d6e9 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -1616,7 +1616,6 @@
     "//chrome/browser/feedback:feedback_impl",
     "//chrome/browser/file_system_access:impl",
     "//chrome/browser/google",
-    "//chrome/browser/ip_protection",
     "//chrome/browser/media/webrtc",
     "//chrome/browser/navigation_predictor:impl",
 
@@ -1901,7 +1900,6 @@
     "//chrome/browser/history",
     "//chrome/browser/image_decoder",
     "//chrome/browser/image_fetcher",
-    "//chrome/browser/ip_protection",
     "//chrome/browser/legion",
     "//chrome/browser/lifetime:termination_notification",
     "//chrome/browser/media:access_handler",
@@ -2226,8 +2224,6 @@
     "//components/infobars/core",
     "//components/invalidation",
     "//components/invalidation:legacy_topics_cleanup",
-    "//components/ip_protection/common:ip_protection_telemetry",
-    "//components/ip_protection/common:masked_domain_list",
     "//components/javascript_dialogs",
     "//components/keyed_service/content",
     "//components/language/content/browser",
diff --git a/chrome/browser/DEPS b/chrome/browser/DEPS
index 48685b93..84b898c 100644
--- a/chrome/browser/DEPS
+++ b/chrome/browser/DEPS
@@ -177,7 +177,6 @@
   "+components/input",
   "+components/installedapp",
   "+components/invalidation",
-  "+components/ip_protection",
   "+components/javascript_dialogs",
   "+components/keep_alive_registry",
   "+components/keyed_service/content",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 76810cfa..3db506e 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -42,7 +42,6 @@
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/devtools/features.h"
 #include "chrome/browser/flag_descriptions.h"
-#include "chrome/browser/ip_protection/ip_protection_switches.h"
 #include "chrome/browser/login_detection/login_detection_util.h"
 #include "chrome/browser/media/router/discovery/access_code/access_code_cast_constants.h"
 #include "chrome/browser/media/router/discovery/access_code/access_code_cast_feature.h"
@@ -1020,12 +1019,6 @@
      switches::kForceDirectionRTL},
 };
 
-const FeatureEntry::Choice kIpProtectionProxyOptOutChoices[] = {
-    {flag_descriptions::kIpProtectionProxyOptOutChoiceDefault, "", ""},
-    {flag_descriptions::kIpProtectionProxyOptOutChoiceOptOut,
-     switches::kDisableIpProtectionProxy, ""},
-};
-
 #if BUILDFLAG(IS_CHROMEOS)
 const FeatureEntry::Choice kSchedulerConfigurationChoices[] = {
     {flags_ui::kGenericExperimentChoiceDefault, "", ""},
@@ -4918,6 +4911,12 @@
      std::size(kAndroidTipsNotificationsResetFeatureTipShown), nullptr},
 };
 
+const FeatureEntry::FeatureParam kRobustWindowManagementBulkCloseEnabled[] = {
+    {"bulk_close", "false"}};
+const FeatureEntry::FeatureVariation kRobustWindowManagementVariations[] = {
+    {"Bulk Close", kRobustWindowManagementBulkCloseEnabled,
+     std::size(kRobustWindowManagementBulkCloseEnabled), nullptr}};
+
 #endif  // BUILDFLAG(IS_ANDROID)
 
 #if !BUILDFLAG(IS_ANDROID)
@@ -5552,9 +5551,6 @@
         kOsCrOS,
         FEATURE_VALUE_TYPE(ash::features::kEnableBrightnessControlInSettings),
     },
-    {"list-all-display-modes", flag_descriptions::kListAllDisplayModesName,
-     flag_descriptions::kListAllDisplayModesDescription, kOsCrOS,
-     FEATURE_VALUE_TYPE(display::features::kListAllDisplayModes)},
     {"enable-edid-based-display-ids",
      flag_descriptions::kEnableEdidBasedDisplayIdsName,
      flag_descriptions::kEnableEdidBasedDisplayIdsDescription, kOsCrOS,
@@ -6773,10 +6769,6 @@
      flag_descriptions::kArcUnthrottleOnActiveAudioV2Name,
      flag_descriptions::kArcUnthrottleOnActiveAudioV2Description, kOsCrOS,
      FEATURE_VALUE_TYPE(arc::kUnthrottleOnActiveAudioV2)},
-    {"arc-video-encode-use-mappable-si",
-     flag_descriptions::kArcVideoEncodeUseMappableSIName,
-     flag_descriptions::kArcVideoEncodeUseMappableSIDescription, kOsCrOS,
-     FEATURE_VALUE_TYPE(arc::kVideoEncodeUseMappableSI)},
     {"arc-vmm-swap-keyboard-shortcut",
      flag_descriptions::kArcVmmSwapKBShortcutName,
      flag_descriptions::kArcVmmSwapKBShortcutDesc, kOsCrOS,
@@ -10628,11 +10620,6 @@
 #endif  // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN) ||
         // BUILDFLAG(IS_CHROMEOS)
 
-    {"ip-protection-proxy-opt-out",
-     flag_descriptions::kIpProtectionProxyOptOutName,
-     flag_descriptions::kIpProtectionProxyOptOutDescription, kOsAll,
-     MULTI_VALUE_TYPE(kIpProtectionProxyOptOutChoices)},
-
     {"protected-audience-debug-token",
      flag_descriptions::kProtectedAudiencesConsentedDebugTokenName,
      flag_descriptions::kProtectedAudiencesConsentedDebugTokenDescription,
@@ -12392,7 +12379,9 @@
 
     {"robust-window-management", flag_descriptions::kRobustWindowManagementName,
      flag_descriptions::kRobustWindowManagementDescription, kOsAndroid,
-     FEATURE_VALUE_TYPE(chrome::android::kRobustWindowManagement)},
+     FEATURE_WITH_PARAMS_VALUE_TYPE(chrome::android::kRobustWindowManagement,
+                                    kRobustWindowManagementVariations,
+                                    "RobustWindowManagement")},
 
     {"robust-window-management-experimental",
      flag_descriptions::kRobustWindowManagementExperimentalName,
diff --git a/chrome/browser/actor/BUILD.gn b/chrome/browser/actor/BUILD.gn
index 684288e..682948d252 100644
--- a/chrome/browser/actor/BUILD.gn
+++ b/chrome/browser/actor/BUILD.gn
@@ -22,6 +22,7 @@
     "actor_tab_data.h",
     "actor_task.h",
     "actor_task_metadata.h",
+    "actor_util.h",
     "aggregated_journal.h",
     "aggregated_journal_file_serializer.h",
     "aggregated_journal_in_memory_serializer.h",
@@ -90,6 +91,7 @@
     "actor_tab_data.cc",
     "actor_task.cc",
     "actor_task_metadata.cc",
+    "actor_util.cc",
     "aggregated_journal.cc",
     "aggregated_journal_file_serializer.cc",
     "aggregated_journal_in_memory_serializer.cc",
diff --git a/chrome/browser/actor/actor_features.cc b/chrome/browser/actor/actor_features.cc
index 2e45d15..d1851660 100644
--- a/chrome/browser/actor/actor_features.cc
+++ b/chrome/browser/actor/actor_features.cc
@@ -93,6 +93,11 @@
 // but with different risk profile.  No-op if above flag is on.
 BASE_FEATURE(kGlicNavigateWithoutUserGesture, base::FEATURE_ENABLED_BY_DEFAULT);
 
+// Killswitch for updating the Glic Actor API to ensure that calls to
+// performAction return first when a task is stopped or paused.
+BASE_FEATURE(kGlicPerformActionsReturnsBeforeStateChange,
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 BASE_FEATURE(kGlicSkipBeforeUnloadDialogAndNavigate,
              base::FEATURE_DISABLED_BY_DEFAULT);
 
diff --git a/chrome/browser/actor/actor_features.h b/chrome/browser/actor/actor_features.h
index 3ed4233c..a06de3d8 100644
--- a/chrome/browser/actor/actor_features.h
+++ b/chrome/browser/actor/actor_features.h
@@ -72,6 +72,8 @@
 
 BASE_DECLARE_FEATURE(kGlicNavigateWithoutUserGesture);
 
+BASE_DECLARE_FEATURE(kGlicPerformActionsReturnsBeforeStateChange);
+
 // Enables a full page screenshot to be taken rather than only the viewport.
 extern const base::FeatureParam<bool> kFullPageScreenshot;
 
diff --git a/chrome/browser/actor/actor_functional_browsertest.cc b/chrome/browser/actor/actor_functional_browsertest.cc
index 6afcdeb..b5d68f68 100644
--- a/chrome/browser/actor/actor_functional_browsertest.cc
+++ b/chrome/browser/actor/actor_functional_browsertest.cc
@@ -61,12 +61,13 @@
   // Helper that sets a future if an ActorTask with `task_id` enters a completed
   // state.
   base::CallbackListSubscription CreateTaskCompletetionSubscription(
-      TaskId task_id,
+      TaskId for_task_id,
       TestFuture<ActorTask::State>& future) {
     return actor_keyed_service()->AddTaskStateChangedCallback(
-        base::BindLambdaForTesting([&future, task_id](const ActorTask& task) {
-          if (task.id() == task_id && task.IsCompleted()) {
-            future.SetValue(task.GetState());
+        base::BindLambdaForTesting([&future, for_task_id](
+                                       TaskId task_id, ActorTask::State state) {
+          if (task_id == for_task_id && ActorTask::IsCompletedState(state)) {
+            future.SetValue(state);
           }
         }));
   }
diff --git a/chrome/browser/actor/actor_keyed_service.cc b/chrome/browser/actor/actor_keyed_service.cc
index 1a08414..84c4a8f 100644
--- a/chrome/browser/actor/actor_keyed_service.cc
+++ b/chrome/browser/actor/actor_keyed_service.cc
@@ -245,7 +245,7 @@
   task->SetId(base::PassKey<ActorKeyedService>(), task_id);
   task->GetExecutionEngine()->SetOwner(task.get());
   // Notify of task creation now that the task id is set.
-  NotifyTaskStateChanged(*task);
+  NotifyTaskStateChanged(task->id(), task->GetState());
   active_tasks_[task_id] = std::move(task);
   return task_id;
 }
@@ -297,8 +297,9 @@
   return tab_state_change_callback_list_.Add(std::move(callback));
 }
 
-void ActorKeyedService::NotifyTaskStateChanged(const ActorTask& task) {
-  tab_state_change_callback_list_.Notify(task);
+void ActorKeyedService::NotifyTaskStateChanged(TaskId task_id,
+                                               ActorTask::State state) {
+  tab_state_change_callback_list_.Notify(task_id, state);
 }
 
 void ActorKeyedService::OnActOnWebCapabilityChanged(bool can_act_on_web) {
diff --git a/chrome/browser/actor/actor_keyed_service.h b/chrome/browser/actor/actor_keyed_service.h
index 3ececcde..19a10fc5 100644
--- a/chrome/browser/actor/actor_keyed_service.h
+++ b/chrome/browser/actor/actor_keyed_service.h
@@ -133,11 +133,11 @@
       base::OnceCallback<void(TabObservationResult)> callback);
 
   using TaskStateChangedCallback =
-      base::RepeatingCallback<void(const ActorTask&)>;
+      base::RepeatingCallback<void(TaskId, ActorTask::State)>;
   base::CallbackListSubscription AddTaskStateChangedCallback(
       TaskStateChangedCallback callback);
 
-  void NotifyTaskStateChanged(const ActorTask& task);
+  void NotifyTaskStateChanged(TaskId task_id, ActorTask::State state);
   void OnActOnWebCapabilityChanged(bool can_act_on_web);
 
   using ActOnWebCapabilityChangedCallback = base::RepeatingCallback<void(bool)>;
@@ -196,7 +196,7 @@
 
   std::unique_ptr<ActorPolicyChecker> policy_checker_;
 
-  base::RepeatingCallbackList<void(const ActorTask&)>
+  base::RepeatingCallbackList<void(TaskId, ActorTask::State)>
       tab_state_change_callback_list_;
 
   base::RepeatingCallbackList<ActOnWebCapabilityChangedCallback::RunType>
diff --git a/chrome/browser/actor/actor_policy_checker_browsertest.cc b/chrome/browser/actor/actor_policy_checker_browsertest.cc
index ddf5702..17074bb 100644
--- a/chrome/browser/actor/actor_policy_checker_browsertest.cc
+++ b/chrome/browser/actor/actor_policy_checker_browsertest.cc
@@ -306,10 +306,7 @@
                    ->GetPolicyChecker()
                    .can_act_on_web());
 
-  // Note: because we explicitly paused the task, the result will be
-  // `ActionResultCode::kError` instead of `ActionResultCode::kTaskWentAway`.
-  // See `ActorTask::OnFinishedAct` for more details.
-  ExpectErrorResult(result, mojom::ActionResultCode::kError);
+  ExpectErrorResult(result, mojom::ActionResultCode::kTaskPaused);
 }
 
 IN_PROC_BROWSER_TEST_F(ActorPolicyCheckerBrowserTestManagedBrowser,
diff --git a/chrome/browser/actor/actor_task.cc b/chrome/browser/actor/actor_task.cc
index f9dbc24..020e91d 100644
--- a/chrome/browser/actor/actor_task.cc
+++ b/chrome/browser/actor/actor_task.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/actor/actor_task.h"
 
 #include <memory>
+#include <optional>
 #include <ostream>
 
 #include "base/feature_list.h"
@@ -236,7 +237,19 @@
                                              .new_state = new_state,
                                              .title = title_});
 
-  actor::ActorKeyedService::Get(profile_)->NotifyTaskStateChanged(*this);
+  if (base::FeatureList::IsEnabled(
+          actor::kGlicPerformActionsReturnsBeforeStateChange)) {
+    // The callback_for_act_ is posted before calling SetState. We want that to
+    // invoke before the client sees the state change so post that as well.
+    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
+        FROM_HERE,
+        base::BindOnce(&ActorKeyedService::NotifyTaskStateChanged,
+                       actor::ActorKeyedService::Get(profile_)->GetWeakPtr(),
+                       id_, state_));
+  } else {
+    actor::ActorKeyedService::Get(profile_)->NotifyTaskStateChanged(id_,
+                                                                    state_);
+  }
 
   // If the state is to be finished/cancelled record a histogram.
   if (state_ == kFinished || state_ == kCancelled || state_ == kFailed) {
@@ -275,53 +288,58 @@
   total_number_of_actions_ += actions.size();
 
   action_tracker_for_metrics_->WillAct(actions);
+  callback_for_act_ = std::move(callback);
 
-  execution_engine_->Act(
-      std::move(actions),
-      base::BindOnce(&ActorTask::OnFinishedAct, weak_ptr_factory_.GetWeakPtr(),
-                     std::move(callback)));
+  execution_engine_->Act(std::move(actions),
+                         base::BindOnce(&ActorTask::OnFinishedAct,
+                                        weak_ptr_factory_.GetWeakPtr()));
 }
 
 void ActorTask::OnFinishedAct(
-    base::WeakPtr<ActorTask> actor_task,
-    ActCallback callback,
-    mojom::ActionResultPtr result,
-    std::optional<size_t> index_of_failed_action,
-    std::vector<ActionResultWithLatencyInfo> action_results) {
-  // Actor task disappeared.
-  if (!actor_task) {
-    std::move(callback).Run(MakeResult(mojom::ActionResultCode::kTaskWentAway),
-                            std::nullopt, {});
-    return;
-  }
-  actor_task->OnFinishedActImpl(std::move(callback), std::move(result),
-                                index_of_failed_action,
-                                std::move(action_results));
-}
-
-void ActorTask::OnFinishedActImpl(
-    ActCallback callback,
     mojom::ActionResultPtr result,
     std::optional<size_t> index_of_failed_action,
     std::vector<ActionResultWithLatencyInfo> action_results) {
   if (state_ != State::kActing) {
+    // Note: this likely isn't a problem when it happens - e.g. the task was
+    // paused while the act was in progress but we note it here for debugging
+    // purposes.
     journal_->Log(GURL(), id(), "ActorTask::OnFinishedAct",
                   JournalDetailsBuilder()
                       .Add("result", ToDebugString(*result))
-                      .AddError("Not in kActing state")
+                      .Add("Not in kActing state", base::ToString(state_))
                       .Build());
-    mojom::ActionResultPtr error_result = MakeErrorResult();
-    action_tracker_for_metrics_->OnFinishedAct(*error_result);
-    std::move(callback).Run(std::move(error_result), std::nullopt, {});
-    return;
   }
-  action_tracker_for_metrics_->OnFinishedAct(*result);
-  SetState(State::kReflecting);
-  std::move(callback).Run(std::move(result), index_of_failed_action,
-                          std::move(action_results));
+
+  // The callback may already have been called, if the task was stopped or
+  // paused.
+  if (callback_for_act_) {
+    // Interruption (WaitingOnUser) can happen while acting, but in that case
+    // the tool is the source and must not finish before uninterrupting.
+    DCHECK_EQ(state_, State::kActing);
+    action_tracker_for_metrics_->OnFinishedAct(*result);
+    std::move(callback_for_act_)
+        .Run(std::move(result), index_of_failed_action,
+             std::move(action_results));
+  }
+
+  if (state_ == State::kActing) {
+    SetState(State::kReflecting);
+  }
 }
 
 void ActorTask::Stop(StoppedReason stop_reason) {
+  // Invoke the callback before changing states so that the client sees the Act
+  // result before seeing the state transition.
+  if (callback_for_act_) {
+    DCHECK(state_ == State::kActing || state_ == State::kWaitingOnUser);
+    mojom::ActionResultPtr result =
+        MakeResult(mojom::ActionResultCode::kTaskWentAway);
+    action_tracker_for_metrics_->OnFinishedAct(*result);
+    std::move(callback_for_act_)
+        .Run(std::move(result), /*index_of_failed_action=*/std::nullopt,
+             /*action_results=*/{});
+  }
+
   if (execution_engine_) {
     execution_engine_->CancelOngoingActions(
         mojom::ActionResultCode::kTaskWentAway);
@@ -357,6 +375,19 @@
   if (IsCompleted()) {
     return;
   }
+
+  // Invoke the callback before changing states so that the client sees the Act
+  // result before seeing the state transition.
+  if (callback_for_act_) {
+    DCHECK(state_ == State::kActing || state_ == State::kWaitingOnUser);
+    mojom::ActionResultPtr result =
+        MakeResult(mojom::ActionResultCode::kTaskPaused);
+    action_tracker_for_metrics_->OnFinishedAct(*result);
+    std::move(callback_for_act_)
+        .Run(MakeResult(mojom::ActionResultCode::kTaskPaused),
+             /*index_of_failed_action=*/std::nullopt, /*action_results=*/{});
+  }
+
   if (execution_engine_) {
     execution_engine_->CancelOngoingActions(
         mojom::ActionResultCode::kTaskPaused);
@@ -402,8 +433,13 @@
 }
 
 bool ActorTask::IsCompleted() const {
-  return (GetState() == State::kFinished) ||
-         (GetState() == State::kCancelled) || (GetState() == State::kFailed);
+  return IsCompletedState(GetState());
+}
+
+// static
+bool ActorTask::IsCompletedState(State state) {
+  return (state == State::kFinished) || (state == State::kCancelled) ||
+         (state == State::kFailed);
 }
 
 base::Time ActorTask::GetEndTime() const {
diff --git a/chrome/browser/actor/actor_task.h b/chrome/browser/actor/actor_task.h
index aa8a9fc..b885564 100644
--- a/chrome/browser/actor/actor_task.h
+++ b/chrome/browser/actor/actor_task.h
@@ -119,6 +119,9 @@
   // //tools/metrics/histograms/metadata/actor/enums.xml:StoppedReasonEnum)
 
   State GetState() const;
+  // TODO(bokan): This should be private (this class must be in control of its
+  // state) but is used by tests. Make the tests friends (or update the tests)
+  // and remove it from the public interface.
   void SetState(State new_state);
 
   base::Time GetEndTime() const;
@@ -127,6 +130,8 @@
            ActCallback callback);
 
   // Sets State to `stop_reason` and cancels any pending actions.
+  // TODO(bokan): It's important that Stop only be called from ActorKeyedService
+  // since that has to clean up actor tasks. Add a PassKey.
   void Stop(StoppedReason stop_reason);
 
   // Pause() is called to indicate that either the actor or user is pausing
@@ -156,6 +161,7 @@
 
   // Returns true if the task has completed, either successfully or cancelled.
   bool IsCompleted() const;
+  static bool IsCompletedState(State state);
 
   ExecutionEngine* GetExecutionEngine() const;
 
@@ -235,17 +241,9 @@
                              content::WebContents* old_contents,
                              content::WebContents* new_contents);
 
-  static void OnFinishedAct(
-      base::WeakPtr<ActorTask> actor_task,
-      ActCallback callback,
-      mojom::ActionResultPtr result,
-      std::optional<size_t> index_of_failed_action,
-      std::vector<ActionResultWithLatencyInfo> action_results);
-  void OnFinishedActImpl(
-      ActCallback callback,
-      mojom::ActionResultPtr result,
-      std::optional<size_t> index_of_failed_action,
-      std::vector<ActionResultWithLatencyInfo> action_results);
+  void OnFinishedAct(mojom::ActionResultPtr result,
+                     std::optional<size_t> index_of_failed_action,
+                     std::vector<ActionResultWithLatencyInfo> action_results);
 
   void OnTabWillDetach(tabs::TabInterface* tab,
                        tabs::TabInterface::DetachReason reason);
@@ -282,6 +280,9 @@
   // The title does not change for the duration of a task.
   const std::string title_;
 
+  // The callback to notify the client of the result of calling Act().
+  ActCallback callback_for_act_;
+
   // A timer for the current state.
   base::ElapsedTimer current_state_timer_;
   // An accumulation of elapsed times for previous "active" states. i.e. the
diff --git a/chrome/browser/actor/actor_util.cc b/chrome/browser/actor/actor_util.cc
new file mode 100644
index 0000000..426b5bd
--- /dev/null
+++ b/chrome/browser/actor/actor_util.cc
@@ -0,0 +1,24 @@
+// 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/actor_util.h"
+
+#include "base/command_line.h"
+#include "base/feature_list.h"
+#include "chrome/browser/actor/actor_features.h"
+#include "chrome/browser/actor/actor_switches.h"
+
+namespace actor {
+
+bool IsActorSafetyCheckDisabled() {
+  return base::CommandLine::ForCurrentProcess()->HasSwitch(
+      switches::kDisableActorSafetyChecks);
+}
+
+bool IsNavigationGatingEnabled() {
+  return !IsActorSafetyCheckDisabled() &&
+         base::FeatureList::IsEnabled(kGlicCrossOriginNavigationGating);
+}
+
+}  // namespace actor
diff --git a/chrome/browser/actor/actor_util.h b/chrome/browser/actor/actor_util.h
new file mode 100644
index 0000000..cab1a573a
--- /dev/null
+++ b/chrome/browser/actor/actor_util.h
@@ -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.
+
+#ifndef CHROME_BROWSER_ACTOR_ACTOR_UTIL_H_
+#define CHROME_BROWSER_ACTOR_ACTOR_UTIL_H_
+
+namespace actor {
+
+// Whether actor safety checks are disabled.
+bool IsActorSafetyCheckDisabled();
+
+// Whether actor navigation gating is enabled.
+bool IsNavigationGatingEnabled();
+
+}  // namespace actor
+
+#endif  // CHROME_BROWSER_ACTOR_ACTOR_UTIL_H_
diff --git a/chrome/browser/actor/execution_engine.cc b/chrome/browser/actor/execution_engine.cc
index d5be4c9..d7c74fa 100644
--- a/chrome/browser/actor/execution_engine.cc
+++ b/chrome/browser/actor/execution_engine.cc
@@ -29,6 +29,7 @@
 #include "chrome/browser/actor/actor_metrics.h"
 #include "chrome/browser/actor/actor_policy_checker.h"
 #include "chrome/browser/actor/actor_task.h"
+#include "chrome/browser/actor/actor_util.h"
 #include "chrome/browser/actor/browser_action_util.h"
 #include "chrome/browser/actor/safety_list_manager.h"
 #include "chrome/browser/actor/site_policy.h"
@@ -100,7 +101,7 @@
 // to a new origin. See note on `kGlicNavigationGatingUseSiteNotOrigin`.
 bool IsSameForNewOriginNavigationGating(const url::Origin& reference_origin,
                                         const GURL& navigation_url) {
-  CHECK(base::FeatureList::IsEnabled(kGlicCrossOriginNavigationGating));
+  CHECK(IsNavigationGatingEnabled());
 
   if (kGlicNavigationGatingUseSiteNotOrigin.Get()) {
     return net::SchemefulSite::IsSameSite(reference_origin.GetURL(),
@@ -223,7 +224,7 @@
 bool ExecutionEngine::ShouldGateNavigation(
     content::NavigationHandle& navigation_handle,
     ExecutionEngine::NavigationDecisionCallback callback) {
-  if (!base::FeatureList::IsEnabled(kGlicCrossOriginNavigationGating)) {
+  if (!IsNavigationGatingEnabled()) {
     return false;
   }
 
@@ -601,14 +602,12 @@
   absl::flat_hash_set<int32_t> acting_tab_handles;
 
   action_sequence_ = std::move(actions);
-  bool origin_gating_enabled =
-      base::FeatureList::IsEnabled(kGlicCrossOriginNavigationGating);
   for (const std::unique_ptr<ToolRequest>& action : action_sequence_) {
     CHECK(action);
     if (action->GetTabHandle() != tabs::TabHandle::Null()) {
       acting_tab_handles.insert(action->GetTabHandle().raw_value());
     }
-    if (origin_gating_enabled) {
+    if (IsNavigationGatingEnabled()) {
       if (std::optional<url::Origin> maybe_origin =
               action->AssociatedOriginGrant();
           maybe_origin) {
@@ -669,7 +668,7 @@
       DidFinishAsyncSafetyChecks(evaluated_origin, /*may_act=*/true);
       return;
     case MayActOnUrlBlockReason::kOptimizationGuideBlock:
-      if (base::FeatureList::IsEnabled(kGlicCrossOriginNavigationGating) &&
+      if (IsNavigationGatingEnabled() &&
           kGlicPromptUserForSensitiveNavigations.Get()) {
         SendUserConfirmationDialogRequest(
             evaluated_origin,
@@ -966,7 +965,7 @@
 
 void ExecutionEngine::AddWritableMainframeOrigins(
     const ExecutionEngine::AllowedOriginSet& added_writable_mainframe_origins) {
-  if (!base::FeatureList::IsEnabled(kGlicCrossOriginNavigationGating)) {
+  if (!IsNavigationGatingEnabled()) {
     return;
   }
   for (const auto& origin : added_writable_mainframe_origins) {
diff --git a/chrome/browser/actor/execution_engine_origin_gating_browsertest.cc b/chrome/browser/actor/execution_engine_origin_gating_browsertest.cc
index 3874123c..b4bac9bb 100644
--- a/chrome/browser/actor/execution_engine_origin_gating_browsertest.cc
+++ b/chrome/browser/actor/execution_engine_origin_gating_browsertest.cc
@@ -7,6 +7,7 @@
 #include "base/test/test_future.h"
 #include "chrome/browser/actor/actor_features.h"
 #include "chrome/browser/actor/actor_policy_checker.h"
+#include "chrome/browser/actor/actor_switches.h"
 #include "chrome/browser/actor/actor_task_metadata.h"
 #include "chrome/browser/actor/actor_test_util.h"
 #include "chrome/browser/actor/execution_engine.h"
@@ -1131,6 +1132,40 @@
                            NOTREACHED();
                          });
 
+class ExecutionEngineOriginGatingSafetyDisabledBrowserTest
+    : public ExecutionEngineOriginGatingBrowserTestBase {
+ public:
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    ExecutionEngineOriginGatingBrowserTestBase::SetUpCommandLine(command_line);
+    command_line->AppendSwitch(actor::switches::kDisableActorSafetyChecks);
+  }
+};
+
+IN_PROC_BROWSER_TEST_F(ExecutionEngineOriginGatingSafetyDisabledBrowserTest,
+                       IgnoreBlocklist) {
+  const GURL start_url =
+      embedded_https_test_server().GetURL("example.com", "/actor/link.html");
+  const GURL blocked_url = embedded_https_test_server().GetURL(
+      "blocked.example.com", "/actor/blank.html");
+
+  ASSERT_TRUE(content::NavigateToURL(web_contents(), start_url));
+  OpenGlicAndCreateTask();
+
+  // Create a navigation request to the blocked URL.
+  std::unique_ptr<ToolRequest> navigate_to_blocked =
+      MakeNavigateRequest(*active_tab(), blocked_url.spec());
+
+  // Execute the navigation action.
+  ActResultFuture result;
+  actor_task().Act(ToRequestList(navigate_to_blocked), result.GetCallback());
+
+  // The navigation should succeed because the safety checks are disabled.
+  ExpectOkResult(result);
+
+  // Verify that the browser navigated to the blocked URL.
+  EXPECT_EQ(web_contents()->GetLastCommittedURL(), blocked_url);
+}
+
 class ExecutionEngineSiteGatingBrowserTest
     : public ExecutionEngineOriginGatingBrowserTestBase,
       public testing::WithParamInterface<bool> {
diff --git a/chrome/browser/actor/safety_list_manager.cc b/chrome/browser/actor/safety_list_manager.cc
index 47030a3..a564250 100644
--- a/chrome/browser/actor/safety_list_manager.cc
+++ b/chrome/browser/actor/safety_list_manager.cc
@@ -12,6 +12,7 @@
 #include "base/no_destructor.h"
 #include "base/values.h"
 #include "chrome/browser/actor/actor_features.h"
+#include "chrome/browser/actor/actor_util.h"
 #include "chrome/browser/actor/safety_list.h"
 #include "components/content_settings/core/common/content_settings_pattern.h"
 
@@ -23,7 +24,7 @@
 constexpr std::string_view kBlockedFieldName = "navigation_blocked";
 
 void MaybeAppendHardcodedPatterns(SafetyList::Patterns& patterns) {
-  if (base::FeatureList::IsEnabled(kGlicCrossOriginNavigationGating) &&
+  if (IsNavigationGatingEnabled() &&
       kGlicIncludeHardcodedBlockListEntries.Get()) {
     patterns.emplace_back(
         ContentSettingsPattern::FromString("*"),
diff --git a/chrome/browser/actor/site_policy.cc b/chrome/browser/actor/site_policy.cc
index a9eb471..5b72f5df3 100644
--- a/chrome/browser/actor/site_policy.cc
+++ b/chrome/browser/actor/site_policy.cc
@@ -8,7 +8,6 @@
 #include <string_view>
 #include <vector>
 
-#include "base/command_line.h"
 #include "base/containers/contains.h"
 #include "base/feature_list.h"
 #include "base/functional/callback.h"
@@ -16,7 +15,7 @@
 #include "base/strings/string_split.h"
 #include "base/task/sequenced_task_runner.h"
 #include "chrome/browser/actor/actor_features.h"
-#include "chrome/browser/actor/actor_switches.h"
+#include "chrome/browser/actor/actor_util.h"
 #include "chrome/browser/actor/aggregated_journal.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/lookalikes/lookalike_url_service.h"
@@ -48,11 +47,6 @@
 
 namespace {
 
-bool DisableSafetyChecks() {
-  return base::CommandLine::ForCurrentProcess()->HasSwitch(
-      switches::kDisableActorSafetyChecks);
-}
-
 class DecisionWrapper {
  public:
   DecisionWrapper(AggregatedJournal& journal,
@@ -170,7 +164,7 @@
     return;
   }
 
-  if (DisableSafetyChecks()) {
+  if (IsActorSafetyCheckDisabled()) {
     decision_wrapper->Accept();
     return;
   }
@@ -255,7 +249,7 @@
   // feature is enabled, and origins the user allowed the actor to interact with
   // will be included in the `allowed_origins` set. If `url` has an origin not
   // in the set, we apply the optimization guide check.
-  if (base::FeatureList::IsEnabled(kGlicCrossOriginNavigationGating) &&
+  if (IsNavigationGatingEnabled() &&
       (!allowed_origins ||
        base::Contains(*allowed_origins, url::Origin::Create(url)))) {
     decision_wrapper->Accept();
@@ -325,7 +319,7 @@
   // Do not act on such a page.
   if (safe_browsing::SafeBrowsingUserInteractionObserver::FromWebContents(
           &web_contents) &&
-      !DisableSafetyChecks()) {
+      !IsActorSafetyCheckDisabled()) {
     decision_wrapper->Reject("Blocked by safebrowsing",
                              MayActOnUrlBlockReason::kSafeBrowsing);
     return;
diff --git a/chrome/browser/actor/tools/drag_and_release_tool_browsertest.cc b/chrome/browser/actor/tools/drag_and_release_tool_browsertest.cc
index e2814a9..a1e7a52 100644
--- a/chrome/browser/actor/tools/drag_and_release_tool_browsertest.cc
+++ b/chrome/browser/actor/tools/drag_and_release_tool_browsertest.cc
@@ -16,6 +16,11 @@
 #include "ui/gfx/geometry/rect_f.h"
 #include "ui/gfx/geometry/vector2d.h"
 
+#if BUILDFLAG(IS_CHROMEOS)
+#include "base/test/scoped_feature_list.h"
+#include "chromeos/constants/chromeos_features.h"
+#endif  // BUILDFLAG(IS_CHROMEOS)
+
 using base::test::TestFuture;
 using content::EvalJs;
 using content::ExecJs;
@@ -33,7 +38,14 @@
 
 class ActorDragAndReleaseToolBrowserTest : public ActorToolsTest {
  public:
-  ActorDragAndReleaseToolBrowserTest() = default;
+  ActorDragAndReleaseToolBrowserTest() {
+#if BUILDFLAG(IS_CHROMEOS)
+    // TODO(crbug.com/465305046): Investigate how the rounded windows feature
+    // affects the hit test.
+    scoped_feature_list_.InitAndDisableFeature(
+        chromeos::features::kFeatureManagementRoundedWindows);
+#endif  // BUILDFLAG(IS_CHROMEOS)
+  }
   ~ActorDragAndReleaseToolBrowserTest() override = default;
 
   void SetUpOnMainThread() override {
@@ -41,6 +53,11 @@
     ASSERT_TRUE(embedded_test_server()->Start());
     ASSERT_TRUE(embedded_https_test_server().Start());
   }
+
+ private:
+#if BUILDFLAG(IS_CHROMEOS)
+  base::test::ScopedFeatureList scoped_feature_list_;
+#endif  // BUILDFLAG(IS_CHROMEOS)
 };
 
 // Test the drag and release tool by moving the thumb on a range slider control.
@@ -205,16 +222,8 @@
   EXPECT_EQ(50, GetRangeValue(*main_frame(), "#offscreenRange"));
 }
 
-// TODO(crbug.com/460801630): Enable on ChromeOS.
-#if BUILDFLAG(IS_CHROMEOS)
-#define MAYBE_DragAndReleaseTool_CrossOriginSubframe \
-  DISABLED_DragAndReleaseTool_CrossOriginSubframe
-#else
-#define MAYBE_DragAndReleaseTool_CrossOriginSubframe \
-  DragAndReleaseTool_CrossOriginSubframe
-#endif
 IN_PROC_BROWSER_TEST_F(ActorDragAndReleaseToolBrowserTest,
-                       MAYBE_DragAndReleaseTool_CrossOriginSubframe) {
+                       DragAndReleaseTool_CrossOriginSubframe) {
   const GURL url = embedded_https_test_server().GetURL(
       "/actor/positioned_iframe_no_scroll.html");
   ASSERT_TRUE(content::NavigateToURL(web_contents(), url));
diff --git a/chrome/browser/actor/tools/page_stability_metrics_browsertest.cc b/chrome/browser/actor/tools/page_stability_metrics_browsertest.cc
index 85d039c..023fc71 100644
--- a/chrome/browser/actor/tools/page_stability_metrics_browsertest.cc
+++ b/chrome/browser/actor/tools/page_stability_metrics_browsertest.cc
@@ -92,7 +92,29 @@
 
 }  // namespace
 
-class PageStabilityMetricsTest : public PageStabilityTest {
+class PageStabilityMetricsTestBase : public PageStabilityTest {
+ public:
+  PageStabilityMetricsTestBase() = default;
+
+  PageStabilityMetricsTestBase(const PageStabilityMetricsTestBase&) = delete;
+  PageStabilityMetricsTestBase& operator=(const PageStabilityMetricsTestBase&) =
+      delete;
+
+  ~PageStabilityMetricsTestBase() override = default;
+
+  void WaitForFrameReady() {
+    content::MainThreadFrameObserver frame_observer(
+        web_contents()->GetPrimaryMainFrame()->GetRenderWidgetHost());
+    frame_observer.Wait();
+  }
+
+  void ClickAndWaitForFrameReady(std::string_view element_id) {
+    content::SimulateMouseClickOrTapElementWithId(web_contents(), element_id);
+    WaitForFrameReady();
+  }
+};
+
+class PageStabilityMetricsTest : public PageStabilityMetricsTestBase {
  public:
   PageStabilityMetricsTest() {
     scoped_feature_list_.InitAndEnableFeatureWithParameters(
@@ -161,7 +183,8 @@
       kActorRendererPageStabilityTimeFromMonitoringToPaintStabilityMetricName,
       0);
 
-  content::SimulateMouseClickOrTapElementWithId(web_contents(), "btnPaint");
+  ClickAndWaitForFrameReady("btnPaint");
+
   ASSERT_TRUE(EnsureHistogramsRecorded(
       histogram_tester,
       {kActorRendererPageStabilityTimeFromMonitoringToPaintStabilityMetricName,
@@ -196,10 +219,12 @@
   ASSERT_EQ(GetOutputText(), "INITIAL");
   EXPECT_FALSE(result.IsReady());
 
-  content::SimulateMouseClickOrTapElementWithId(web_contents(), "btnPaint");
+  WaitForFrameReady();
+
+  ClickAndWaitForFrameReady("btnPaint");
 
   ASSERT_TRUE(result.Wait());
-  ASSERT_EQ(GetOutputText(), "PAINT");
+  ASSERT_EQ(GetOutputText(), "PAINT 1");
 
   metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
 
@@ -240,11 +265,13 @@
 
   // Verify that the metrics for subsequent interaction contentful paints were
   // still recorded after paint stability.
-  content::SimulateMouseClickOrTapElementWithId(web_contents(), "btnPaint");
-  content::SimulateMouseClickOrTapElementWithId(web_contents(), "btnPaint");
+  ClickAndWaitForFrameReady("btnPaint");
+  ASSERT_EQ(GetOutputText(), "PAINT 2");
 
-  // Wait until timeout to flush the metrics.
-  Sleep(features::kGlicActorPageStabilityTimeout.Get());
+  ClickAndWaitForFrameReady("btnPaint");
+  ASSERT_EQ(GetOutputText(), "PAINT 3");
+
+  // The metrics will be flushed on timeout.
 
   ASSERT_TRUE(EnsureHistogramsRecorded(
       histogram_tester,
@@ -300,7 +327,7 @@
   // Verify that paint stability and network/main thread metrics were not
   // recorded when the stabilicy check completed after callback invocation due
   // to timeout.
-  content::SimulateMouseClickOrTapElementWithId(web_contents(), "btnPaint");
+  ClickAndWaitForFrameReady("btnPaint");
   Respond("NETWORK DONE");
 
   ASSERT_TRUE(EnsureHistogramsNotRecorded(
@@ -416,7 +443,7 @@
 
   // Verify that paint stability and network/main thread metrics were still
   // recorded when the stabilicy check completed after mojo disconnection.
-  content::SimulateMouseClickOrTapElementWithId(web_contents(), "btnPaint");
+  ClickAndWaitForFrameReady("btnPaint");
   Respond("NETWORK DONE");
 
   ASSERT_TRUE(EnsureHistogramsRecorded(
@@ -478,7 +505,7 @@
 
   // Verify that paint stability and network/main thread metrics were not
   // recorded after timeout.
-  content::SimulateMouseClickOrTapElementWithId(web_contents(), "btnPaint");
+  ClickAndWaitForFrameReady("btnPaint");
   Respond("NETWORK DONE");
 
   ASSERT_TRUE(EnsureHistogramsNotRecorded(
@@ -497,7 +524,7 @@
       0);
 }
 
-class PageStabilityMetricsMinWaitTest : public PageStabilityTest {
+class PageStabilityMetricsMinWaitTest : public PageStabilityMetricsTestBase {
  public:
   PageStabilityMetricsMinWaitTest() {
     scoped_feature_list_.InitAndEnableFeatureWithParameters(
@@ -549,7 +576,7 @@
 
   // Verify that paint stability metric was still recorded when paint stability
   // was reached while waiting for minimum wait.
-  content::SimulateMouseClickOrTapElementWithId(web_contents(), "btnPaint");
+  ClickAndWaitForFrameReady("btnPaint");
 
   ASSERT_TRUE(result.Wait());
 
@@ -592,7 +619,9 @@
   ASSERT_EQ(GetOutputText(), "INITIAL");
   EXPECT_FALSE(result.IsReady());
 
-  content::SimulateMouseClickOrTapElementWithId(web_contents(), "btnPaint");
+  WaitForFrameReady();
+
+  ClickAndWaitForFrameReady("btnPaint");
 
   ASSERT_TRUE(EnsureHistogramsRecorded(
       histogram_tester,
@@ -602,7 +631,7 @@
       1);
   ASSERT_FALSE(result.IsReady());
 
-  // Verify tht the network/main thread metric was still recorded when the
+  // Verify that the network/main thread metric was still recorded when the
   // network/main thread became idle while waiting for minimum wait.
   Respond("NETWORK DONE");
 
diff --git a/chrome/browser/actor/tools/tool_agnostic_browsertest.cc b/chrome/browser/actor/tools/tool_agnostic_browsertest.cc
index 1aab3b1..574ba26 100644
--- a/chrome/browser/actor/tools/tool_agnostic_browsertest.cc
+++ b/chrome/browser/actor/tools/tool_agnostic_browsertest.cc
@@ -27,6 +27,10 @@
 #include "ui/base/page_transition_types.h"
 #include "ui/gfx/geometry/point_conversions.h"
 
+#if BUILDFLAG(IS_CHROMEOS)
+#include "chromeos/constants/chromeos_features.h"
+#endif  // BUILDFLAG(IS_CHROMEOS)
+
 using base::test::TestFuture;
 using content::ChildFrameAt;
 using content::EvalJs;
@@ -44,7 +48,13 @@
   ActorToolAgnosticBrowserTest() {
     scoped_feature_list_.InitWithFeatures(
         /*enabled_features=*/{},
-        /*disabled_features=*/{kGlicCrossOriginNavigationGating});
+        /*disabled_features=*/{
+#if BUILDFLAG(IS_CHROMEOS)
+            // TODO(crbug.com/465305046): Investigate how the rounded windows
+            // feature affects the hit test.
+            chromeos::features::kFeatureManagementRoundedWindows,
+#endif  // BUILDFLAG(IS_CHROMEOS)
+            kGlicCrossOriginNavigationGating});
   }
   ~ActorToolAgnosticBrowserTest() override = default;
 
@@ -210,16 +220,8 @@
 
 // Basic test to ensure sending a click to a coordinate in cross origin subframe
 // works.
-// TODO(crbug.com/460824293): Reenable on ChromeOS.
-#if BUILDFLAG(IS_CHROMEOS)
-#define MAYBE_InvokeToolCrossSiteSubframeWithCoordinateTarget \
-  DISABLED_InvokeToolCrossSiteSubframeWithCoordinateTarget
-#else
-#define MAYBE_InvokeToolCrossSiteSubframeWithCoordinateTarget \
-  InvokeToolCrossSiteSubframeWithCoordinateTarget
-#endif
 IN_PROC_BROWSER_TEST_F(ActorToolAgnosticBrowserTest,
-                       MAYBE_InvokeToolCrossSiteSubframeWithCoordinateTarget) {
+                       InvokeToolCrossSiteSubframeWithCoordinateTarget) {
   const GURL url = embedded_https_test_server().GetURL(
       "/actor/positioned_iframe_no_scroll.html");
   ASSERT_TRUE(content::NavigateToURL(web_contents(), url));
diff --git a/chrome/browser/actor/ui/actor_ui_metrics.cc b/chrome/browser/actor/ui/actor_ui_metrics.cc
index 4e944e75..ef9061b 100644
--- a/chrome/browser/actor/ui/actor_ui_metrics.cc
+++ b/chrome/browser/actor/ui/actor_ui_metrics.cc
@@ -48,6 +48,11 @@
       GetActorUiMetricName("TaskNudge.", ToString(nudge_state), ".Click"));
 }
 
+void RecordTaskNudgeShown(ActorTaskNudgeState nudge_state) {
+  base::UmaHistogramEnumeration(GetActorUiMetricName("TaskNudge.Shown"),
+                                nudge_state.text);
+}
+
 void RecordActuatingTabWebContentsAttached() {
   base::RecordAction(base::UserMetricsAction(
       GetActorUiMetricName("ActuatingTabWebContentsAttached").c_str()));
diff --git a/chrome/browser/actor/ui/actor_ui_metrics.h b/chrome/browser/actor/ui/actor_ui_metrics.h
index 952ad38..4e493fe 100644
--- a/chrome/browser/actor/ui/actor_ui_metrics.h
+++ b/chrome/browser/actor/ui/actor_ui_metrics.h
@@ -24,6 +24,9 @@
 // This fails if the nudge is in the default state.
 void LogTaskNudgeClick(ActorTaskNudgeState nudge_state);
 
+// Recorded when the task nudge is shown.
+void RecordTaskNudgeShown(ActorTaskNudgeState nudge_state);
+
 // Records web content attachment for the actuating tab.
 void RecordActuatingTabWebContentsAttached();
 
diff --git a/chrome/browser/actor/ui/states/actor_task_nudge_state.h b/chrome/browser/actor/ui/states/actor_task_nudge_state.h
index 15a65b5..0c17437 100644
--- a/chrome/browser/actor/ui/states/actor_task_nudge_state.h
+++ b/chrome/browser/actor/ui/states/actor_task_nudge_state.h
@@ -8,7 +8,7 @@
 #include <string_view>
 
 namespace actor::ui {
-
+// LINT.IfChange(ActorTaskNudgeState)
 struct ActorTaskNudgeState {
   enum class Text {
     // Default/no text.
@@ -27,6 +27,7 @@
     return text == other.text;
   }
 };
+// LINT.ThenChange(//chrome/tools/metrics/histograms/metadata/actor/enums.xml:TaskNudgeState)
 
 inline std::string_view ToString(const ActorTaskNudgeState& state) {
   switch (state.text) {
diff --git a/chrome/browser/actor/ui/task_list_bubble/actor_task_list_bubble_controller.cc b/chrome/browser/actor/ui/task_list_bubble/actor_task_list_bubble_controller.cc
index c099cb5..3f0ecdc 100644
--- a/chrome/browser/actor/ui/task_list_bubble/actor_task_list_bubble_controller.cc
+++ b/chrome/browser/actor/ui/task_list_bubble/actor_task_list_bubble_controller.cc
@@ -124,7 +124,9 @@
   // Regardless of tab navigation, remove the row and close the bubble when
   // done.
   icon_manager->RemoveRowFromTaskListBubble(task_id);
-  bubble_widget_->Close();
+  if (bubble_widget_) {
+    bubble_widget_->Close();
+  }
 #endif
 }
 
diff --git a/chrome/browser/ash/app_mode/kiosk_chrome_app_manager.cc b/chrome/browser/ash/app_mode/kiosk_chrome_app_manager.cc
index ac49ca3..7583879 100644
--- a/chrome/browser/ash/app_mode/kiosk_chrome_app_manager.cc
+++ b/chrome/browser/ash/app_mode/kiosk_chrome_app_manager.cc
@@ -164,12 +164,8 @@
 // static
 const char KioskChromeAppManager::kKioskDictionaryName[] = "kiosk";
 
-const char kKioskPrimaryAppInstallErrorHistogram[] =
-    "Kiosk.ChromeApp.PrimaryAppInstallError";
 const char kKioskPrimaryAppUpdateResultHistogram[] =
     "Kiosk.ChromeApp.PrimaryAppUpdateResult";
-const char kKioskExternalUpdateSuccessHistogram[] =
-    "Kiosk.ChromeApp.ExternalUpdateSuccess";
 
 namespace {
 // This class is owned by `ChromeBrowserMainPartsAsh`.
@@ -439,7 +435,6 @@
 }
 
 void KioskChromeAppManager::OnKioskAppExternalUpdateComplete(bool success) {
-  base::UmaHistogramBoolean(kKioskExternalUpdateSuccessHistogram, success);
   for (auto& observer : observers_) {
     observer.OnKioskAppExternalUpdateComplete(success);
   }
@@ -663,8 +658,6 @@
 
   if (!external_cache_->GetExtension(id, nullptr, nullptr)) {
     // Initial install fail.
-    base::UmaHistogramEnumeration(kKioskPrimaryAppInstallErrorHistogram,
-                                  PrimaryAppDownloadResultFromError(error));
     return;
   }
   base::UmaHistogramEnumeration(kKioskPrimaryAppUpdateResultHistogram,
diff --git a/chrome/browser/ash/app_mode/kiosk_chrome_app_manager.h b/chrome/browser/ash/app_mode/kiosk_chrome_app_manager.h
index 417496b7..46a7c00f 100644
--- a/chrome/browser/ash/app_mode/kiosk_chrome_app_manager.h
+++ b/chrome/browser/ash/app_mode/kiosk_chrome_app_manager.h
@@ -49,9 +49,7 @@
 class KioskExternalUpdater;
 class KioskCryptohomeRemover;
 
-extern const char kKioskPrimaryAppInstallErrorHistogram[];
 extern const char kKioskPrimaryAppUpdateResultHistogram[];
-extern const char kKioskExternalUpdateSuccessHistogram[];
 
 // KioskChromeAppManager manages cached app data.
 class KioskChromeAppManager : public KioskAppManagerBase,
diff --git a/chrome/browser/ash/app_mode/startup_app_launcher_unittest.cc b/chrome/browser/ash/app_mode/startup_app_launcher_unittest.cc
index ad37f47..49d0b27 100644
--- a/chrome/browser/ash/app_mode/startup_app_launcher_unittest.cc
+++ b/chrome/browser/ash/app_mode/startup_app_launcher_unittest.cc
@@ -883,11 +883,6 @@
 
   EXPECT_EQ(KioskAppLaunchError::Error::kUnableToDownload,
             startup_launch_delegate_.launch_error());
-
-  histogram.ExpectUniqueSample(
-      kKioskPrimaryAppInstallErrorHistogram,
-      KioskChromeAppManager::PrimaryAppDownloadResult::kCrxFetchFailed,
-      /*expected_bucket_count=*/1);
 }
 
 TEST_F(StartupAppLauncherTest, PrimaryAppCrxInstallFailure) {
diff --git a/chrome/browser/ash/arc/fileapi/arc_documents_provider_file_stream_reader.h b/chrome/browser/ash/arc/fileapi/arc_documents_provider_file_stream_reader.h
index 8cefa2d..c122bfc 100644
--- a/chrome/browser/ash/arc/fileapi/arc_documents_provider_file_stream_reader.h
+++ b/chrome/browser/ash/arc/fileapi/arc_documents_provider_file_stream_reader.h
@@ -26,7 +26,6 @@
 
 // FileStreamReader implementation for ARC documents provider file system.
 // It actually delegates operations to ArcContentFileSystemFileStreamReader.
-// TODO(crbug.com/678886): Write unit tests.
 class ArcDocumentsProviderFileStreamReader : public storage::FileStreamReader {
  public:
   ArcDocumentsProviderFileStreamReader(const storage::FileSystemURL& url,
diff --git a/chrome/browser/ash/arc/fileapi/arc_documents_provider_file_stream_writer.h b/chrome/browser/ash/arc/fileapi/arc_documents_provider_file_stream_writer.h
index ec150ed..45a1556e 100644
--- a/chrome/browser/ash/arc/fileapi/arc_documents_provider_file_stream_writer.h
+++ b/chrome/browser/ash/arc/fileapi/arc_documents_provider_file_stream_writer.h
@@ -23,7 +23,6 @@
 
 // FileStreamWriter implementation for ARC documents provider file system.
 // It actually delegates operations to ArcContentFileSystemFileStreamWriter.
-// TODO(crbug.com/678886): Write unit tests.
 class ArcDocumentsProviderFileStreamWriter : public storage::FileStreamWriter {
  public:
   ArcDocumentsProviderFileStreamWriter(const storage::FileSystemURL& url,
diff --git a/chrome/browser/ash/arc/input_overlay/ui/editing_list.cc b/chrome/browser/ash/arc/input_overlay/ui/editing_list.cc
index 44cda20b..8b11bf8c 100644
--- a/chrome/browser/ash/arc/input_overlay/ui/editing_list.cc
+++ b/chrome/browser/ash/arc/input_overlay/ui/editing_list.cc
@@ -364,7 +364,6 @@
   // | ---------------------- |
   // | ......                 |
   // --------------------------
-  // TODO(b/270969479): Wrap `scroll_content` in a scroll view.
   DCHECK(controller_);
   DCHECK(scroll_content_);
   for (const auto& action : controller_->touch_injector()->actions()) {
diff --git a/chrome/browser/ash/input_method/input_method_engine_browsertests.cc b/chrome/browser/ash/input_method/input_method_engine_browsertests.cc
index 31cf2bc..dbe190f 100644
--- a/chrome/browser/ash/input_method/input_method_engine_browsertests.cc
+++ b/chrome/browser/ash/input_method/input_method_engine_browsertests.cc
@@ -287,10 +287,9 @@
 
 // Test is flaky. https://crbug.com/1462135.
 IN_PROC_BROWSER_TEST_P(InputMethodEngineBrowserTest, DISABLED_APIArgumentTest) {
-  // TODO(crbug.com/41455212): Makes real end to end test without mocking the
-  // input context handler. The test should mock the TextInputClient instance
-  // hooked up with `InputMethodAsh`, or even using the real `TextInputClient`
-  // if possible.
+  // Makes real end to end test without mocking the input context handler.
+  // Ideally the test should mock the TextInputClient instance hooked up with
+  // `InputMethodAsh`, or even using the real `TextInputClient` if possible.
   LoadTestInputMethod();
 
   InputMethodManager::Get()->GetActiveIMEState()->ChangeInputMethod(
diff --git a/chrome/browser/ash/login/app_mode/test/kiosk_remote_command_browsertest.cc b/chrome/browser/ash/login/app_mode/test/kiosk_remote_command_browsertest.cc
index a83115f..c922607 100644
--- a/chrome/browser/ash/login/app_mode/test/kiosk_remote_command_browsertest.cc
+++ b/chrome/browser/ash/login/app_mode/test/kiosk_remote_command_browsertest.cc
@@ -211,7 +211,6 @@
   ASSERT_TRUE(WaitKioskLaunched());
 
   // Skips real image upload.
-  // TODO(crbug.com/269432279): Try real upload with embedded test server.
   policy::DeviceCommandsFactoryAsh::set_commands_for_testing(true);
 
   auto response = IssueCommandAndGetResponse(
diff --git a/chrome/browser/ash/wallpaper/wallpaper_enumerator.h b/chrome/browser/ash/wallpaper/wallpaper_enumerator.h
index c195806..f3216ebd 100644
--- a/chrome/browser/ash/wallpaper/wallpaper_enumerator.h
+++ b/chrome/browser/ash/wallpaper/wallpaper_enumerator.h
@@ -16,9 +16,6 @@
 
 // Searches the user's files for jpg and png images. This is used for
 // displaying images that the user could select as a custom wallpaper.
-// TODO(crbug.com/40562168): Add metrics on the number of files retrieved, and
-// support getting paths incrementally in case the user has a large number of
-// local images.
 void EnumerateLocalWallpaperFiles(
     Profile* profile,
     base::OnceCallback<void(const std::vector<base::FilePath>&)> callback);
diff --git a/chrome/browser/back_press/android/java/src/org/chromium/chrome/browser/back_press/BackPressHelper.java b/chrome/browser/back_press/android/java/src/org/chromium/chrome/browser/back_press/BackPressHelper.java
index 6e0424a..12235d6 100644
--- a/chrome/browser/back_press/android/java/src/org/chromium/chrome/browser/back_press/BackPressHelper.java
+++ b/chrome/browser/back_press/android/java/src/org/chromium/chrome/browser/back_press/BackPressHelper.java
@@ -142,10 +142,7 @@
 
     /**
      * Register a list of {@link BackPressHandler} on a given {@link OnBackPressedDispatcher}. The
-     * first handler has the top priority and the last one has the least. TODO(crbug.com/40252517):
-     * consider introducing a lightweight {@link
-     * org.chromium.chrome.browser.back_press.BackPressManager} if too many handlers should be
-     * registered.
+     * first handler has the top priority and the last one has the least.
      *
      * @param lifecycleOwner {@link LifecycleOwner} managing the back press logic's lifecycle.
      * @param dispatcher {@link OnBackPressedDispatcher} that holds other callbacks.
diff --git a/chrome/browser/back_press/android/java/src/org/chromium/chrome/browser/back_press/BackPressManager.java b/chrome/browser/back_press/android/java/src/org/chromium/chrome/browser/back_press/BackPressManager.java
index e86073b..7a21f1c4 100644
--- a/chrome/browser/back_press/android/java/src/org/chromium/chrome/browser/back_press/BackPressManager.java
+++ b/chrome/browser/back_press/android/java/src/org/chromium/chrome/browser/back_press/BackPressManager.java
@@ -346,7 +346,6 @@
     /**
      * Set a callback fired when a back press is triggered. This is introduced to investigate data
      * inconsistency between experimental groups. and is not intended to be re-used.
-     * TODO(crbug.com/40944523): remove after sufficient data is collected.
      */
     public void setOnBackPressedListener(Runnable callback) {
         mOnBackPressed = callback;
diff --git a/chrome/browser/banners/android/chrome_app_banner_manager_android.cc b/chrome/browser/banners/android/chrome_app_banner_manager_android.cc
index 69e2c1b..871b2d43 100644
--- a/chrome/browser/banners/android/chrome_app_banner_manager_android.cc
+++ b/chrome/browser/banners/android/chrome_app_banner_manager_android.cc
@@ -60,10 +60,10 @@
 }
 
 void ChromeAppBannerManagerAndroid::RecordExtraMetricsForInstallEvent(
-    AddToHomescreenInstaller::Event event,
+    AddToHomescreenEvent event,
     const AddToHomescreenParams& a2hs_params) {
   if (a2hs_params.app_type == AddToHomescreenParams::AppType::WEBAPK &&
-      event == AddToHomescreenInstaller::Event::UI_CANCELLED) {
+      event == AddToHomescreenEvent::UI_CANCELLED) {
     // TODO(b/320681613): Maybe move this to components.
     webapk::TrackInstallEvent(
         webapk::ADD_TO_HOMESCREEN_DIALOG_DISMISSED_BEFORE_INSTALLATION);
diff --git a/chrome/browser/banners/android/chrome_app_banner_manager_android.h b/chrome/browser/banners/android/chrome_app_banner_manager_android.h
index 6c4280c..4b4a884eb 100644
--- a/chrome/browser/banners/android/chrome_app_banner_manager_android.h
+++ b/chrome/browser/banners/android/chrome_app_banner_manager_android.h
@@ -37,7 +37,7 @@
   GetSegmentationPlatformService() override;
   PrefService* GetPrefService() override;
   void RecordExtraMetricsForInstallEvent(
-      AddToHomescreenInstaller::Event event,
+      AddToHomescreenEvent event,
       const AddToHomescreenParams& a2hs_params) override;
 
  private:
diff --git a/chrome/browser/browsing_data/navigation_entry_remover_browsertest.cc b/chrome/browser/browsing_data/navigation_entry_remover_browsertest.cc
index d9ff202..270ae03 100644
--- a/chrome/browser/browsing_data/navigation_entry_remover_browsertest.cc
+++ b/chrome/browser/browsing_data/navigation_entry_remover_browsertest.cc
@@ -11,6 +11,7 @@
 #include "chrome/browser/ui/browser_commands.h"
 #include "chrome/browser/ui/browser_finder.h"
 #include "chrome/browser/ui/browser_list.h"
+#include "chrome/browser/ui/browser_window/public/browser_window_interface_iterator.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/common/buildflags.h"
 #include "chrome/test/base/chrome_test_utils.h"
@@ -87,14 +88,18 @@
 
   std::vector<GURL> GetEntries() {
     std::vector<GURL> urls;
-    for (Browser* browser : *BrowserList::GetInstance()) {
-      for (int j = 0; j < browser->tab_strip_model()->count(); j++) {
-        content::NavigationController* controller =
-            &browser->tab_strip_model()->GetWebContentsAt(j)->GetController();
-        for (int i = 0; i < controller->GetEntryCount(); i++)
-          urls.push_back(controller->GetEntryAtIndex(i)->GetURL());
-      }
-    }
+    ForEachCurrentBrowserWindowInterfaceOrderedByActivation(
+        [&urls](BrowserWindowInterface* browser) {
+          TabStripModel* const tab_strip_model = browser->GetTabStripModel();
+          for (int j = 0; j < tab_strip_model->count(); j++) {
+            content::NavigationController* const controller =
+                &tab_strip_model->GetWebContentsAt(j)->GetController();
+            for (int i = 0; i < controller->GetEntryCount(); i++) {
+              urls.push_back(controller->GetEntryAtIndex(i)->GetURL());
+            }
+          }
+          return true;
+        });
     return urls;
   }
 
@@ -156,11 +161,11 @@
 
   AddBrowser(browser(), {url_a_, url_b_});
   EXPECT_EQ(2U, chrome::GetTotalBrowserCount());
-  ExpectEntries({about_blank_, url_a_, url_b_}, GetEntries());
+  ExpectEntries({url_a_, url_b_, about_blank_}, GetEntries());
 
   AddBrowser(browser(), {url_c_, url_d_});
   EXPECT_EQ(3U, chrome::GetTotalBrowserCount());
-  ExpectEntries({about_blank_, url_a_, url_b_, url_c_, url_d_}, GetEntries());
+  ExpectEntries({url_c_, url_d_, url_a_, url_b_, about_blank_}, GetEntries());
 }
 
 IN_PROC_BROWSER_TEST_F(NavigationEntryRemoverTest, GoBack) {
@@ -283,7 +288,7 @@
   browsing_data::RemoveNavigationEntries(profile(),
                                          DeletionInfo::ForAllHistory());
 
-  ExpectEntries({url_b_, url_d_}, GetEntries());
+  ExpectEntries({url_d_, url_b_}, GetEntries());
 }
 
 IN_PROC_BROWSER_TEST_F(NavigationEntryRemoverTest, GoBackAndDelete) {
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index ba26356..6788652 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -8527,12 +8527,6 @@
   media_prefs::PreferenceRankVideoDeviceInfos(*prefs, infos);
 }
 
-network::mojom::IpProtectionProxyBypassPolicy
-ChromeContentBrowserClient::GetIpProtectionProxyBypassPolicy() {
-  return network::mojom::IpProtectionProxyBypassPolicy::
-      kFirstPartyToTopLevelFrame;
-}
-
 void ChromeContentBrowserClient::MaybePrewarmHttpDiskCache(
     content::BrowserContext& browser_context,
     const std::optional<url::Origin>& initiator_origin,
diff --git a/chrome/browser/chrome_content_browser_client.h b/chrome/browser/chrome_content_browser_client.h
index b128160..7580b55 100644
--- a/chrome/browser/chrome_content_browser_client.h
+++ b/chrome/browser/chrome_content_browser_client.h
@@ -1108,8 +1108,6 @@
   void PreferenceRankVideoDeviceInfos(
       content::BrowserContext* browser_context,
       blink::WebMediaDeviceInfoArray& infos) override;
-  network::mojom::IpProtectionProxyBypassPolicy
-  GetIpProtectionProxyBypassPolicy() override;
   void MaybePrewarmHttpDiskCache(
       content::BrowserContext& browser_context,
       const std::optional<url::Origin>& initiator_origin,
diff --git a/chrome/browser/chromeos/app_mode/chrome_kiosk_app_installer.cc b/chrome/browser/chromeos/app_mode/chrome_kiosk_app_installer.cc
index 09084e6..c128399 100644
--- a/chrome/browser/chromeos/app_mode/chrome_kiosk_app_installer.cc
+++ b/chrome/browser/chromeos/app_mode/chrome_kiosk_app_installer.cc
@@ -17,7 +17,6 @@
 #include "base/check_op.h"
 #include "base/containers/flat_set.h"
 #include "base/functional/bind.h"
-#include "base/metrics/histogram_functions.h"
 #include "base/strings/string_util.h"
 #include "base/syslog_logging.h"
 #include "chrome/browser/chromeos/app_mode/chrome_kiosk_external_loader_broker.h"
@@ -39,15 +38,6 @@
 
 namespace {
 
-const std::string_view kChromeKioskExtensionUpdateErrorHistogram =
-    "Kiosk.ChromeApp.ExtensionUpdateError";
-
-const std::string_view kChromeKioskExtensionHasUpdateDurationHistogram =
-    "Kiosk.ChromeApp.ExtensionUpdateDuration.HasUpdate";
-
-const std::string_view kChromeKioskExtensionNoUpdateDurationHistogram =
-    "Kiosk.ChromeApp.ExtensionUpdateDuration.NoUpdate";
-
 // Returns true if the app with `id` is pending an install or update.
 bool IsExtensionInstallPending(content::BrowserContext& browser_context,
                                const std::string& id) {
@@ -260,11 +250,6 @@
     SYSLOG(INFO) << "Reloaded extension with id " << primary_app_id();
   }
 
-  base::UmaHistogramMediumTimes(
-      update_found ? kChromeKioskExtensionHasUpdateDurationHistogram
-                   : kChromeKioskExtensionNoUpdateDurationHistogram,
-      base::Time::Now() - extension_update_start_time_);
-
   FinalizeAppInstall();
 }
 
@@ -360,13 +345,6 @@
   }
 }
 
-void ChromeKioskAppInstaller::OnExtensionInstallationFailed(
-    const extensions::ExtensionId& id,
-    extensions::InstallStageTracker::FailureReason reason) {
-  base::UmaHistogramEnumeration(kChromeKioskExtensionUpdateErrorHistogram,
-                                reason);
-}
-
 void ChromeKioskAppInstaller::ReportInstallSuccess() {
   DCHECK(install_complete_);
   SYSLOG(INFO) << "Kiosk app install succeeded";
diff --git a/chrome/browser/chromeos/app_mode/chrome_kiosk_app_installer.h b/chrome/browser/chromeos/app_mode/chrome_kiosk_app_installer.h
index e4e6cc48..11da7d5 100644
--- a/chrome/browser/chromeos/app_mode/chrome_kiosk_app_installer.h
+++ b/chrome/browser/chromeos/app_mode/chrome_kiosk_app_installer.h
@@ -55,11 +55,6 @@
                           const extensions::Extension* extension,
                           bool success) override;
 
-  // extensions::InstallStageTracker::Observer overrides.
-  void OnExtensionInstallationFailed(
-      const extensions::ExtensionId& id,
-      extensions::InstallStageTracker::FailureReason reason) override;
-
   void ReportInstallSuccess();
   void ReportInstallFailure(InstallResult result);
 
diff --git a/chrome/browser/collaboration/android/java/src/org/chromium/chrome/browser/collaboration/DataSharingServiceStartupTest.java b/chrome/browser/collaboration/android/java/src/org/chromium/chrome/browser/collaboration/DataSharingServiceStartupTest.java
index bbabfcd5..9a765659 100644
--- a/chrome/browser/collaboration/android/java/src/org/chromium/chrome/browser/collaboration/DataSharingServiceStartupTest.java
+++ b/chrome/browser/collaboration/android/java/src/org/chromium/chrome/browser/collaboration/DataSharingServiceStartupTest.java
@@ -14,9 +14,9 @@
 import org.junit.runner.RunWith;
 
 import org.chromium.base.ThreadUtils;
-import org.chromium.base.test.util.Batch;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
+import org.chromium.base.test.util.DoNotBatch;
 import org.chromium.base.test.util.Features;
 import org.chromium.base.test.util.Features.EnableFeatures;
 import org.chromium.base.test.util.Restriction;
@@ -49,7 +49,7 @@
 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
 @EnableFeatures({ChromeFeatureList.DATA_SHARING})
 @Restriction(GmsCoreVersionRestriction.RESTRICTION_TYPE_VERSION_GE_20W02)
-@Batch(Batch.PER_CLASS)
+@DoNotBatch(reason = "Affects sign-in state, which is global.")
 // TODO(crbug.com/419289558): Re-enable color surface feature flags.
 @Features.DisableFeatures({
     ChromeFeatureList.ANDROID_SURFACE_COLOR_UPDATE,
diff --git a/chrome/browser/contextual_tasks/BUILD.gn b/chrome/browser/contextual_tasks/BUILD.gn
index 8b74cd0..595c4d6 100644
--- a/chrome/browser/contextual_tasks/BUILD.gn
+++ b/chrome/browser/contextual_tasks/BUILD.gn
@@ -97,6 +97,8 @@
       "//chrome/browser/ui/browser_window",
       "//chrome/browser/ui/tabs:tab_strip",
       "//components/contextual_search:public",
+      "//components/optimization_guide/core:model_execution",
+      "//components/optimization_guide/proto:optimization_guide_proto",
       "//components/tabs:public",
       "//components/url_deduplication",
       "//components/visited_url_ranking/public",
diff --git a/chrome/browser/contextual_tasks/contextual_tasks_context_service.cc b/chrome/browser/contextual_tasks/contextual_tasks_context_service.cc
index 06087580..ef78479 100644
--- a/chrome/browser/contextual_tasks/contextual_tasks_context_service.cc
+++ b/chrome/browser/contextual_tasks/contextual_tasks_context_service.cc
@@ -4,6 +4,9 @@
 
 #include "chrome/browser/contextual_tasks/contextual_tasks_context_service.h"
 
+#include <memory>
+
+#include "base/containers/contains.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/stl_util.h"
 #include "base/strings/stringprintf.h"
@@ -20,6 +23,8 @@
 #include "chrome/browser/ui/browser_window/public/browser_window_interface_iterator.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "components/contextual_tasks/public/features.h"
+#include "components/optimization_guide/core/model_quality/model_quality_log_entry.h"
+#include "components/optimization_guide/proto/features/contextual_tasks_context.pb.h"
 #include "components/passage_embeddings/passage_embeddings_types.h"
 #include "content/public/browser/web_contents.h"
 #include "url/gurl.h"
@@ -92,27 +97,6 @@
   return score;
 }
 
-std::vector<content::WebContents*> GetAllTabsForProfile(Profile* profile) {
-  std::vector<content::WebContents*> all_tabs;
-  ForEachCurrentBrowserWindowInterfaceOrderedByActivation(
-      [profile, &all_tabs](BrowserWindowInterface* browser) {
-        if (browser->GetProfile() != profile) {
-          return true;
-        }
-        TabStripModel* const tab_strip_model = browser->GetTabStripModel();
-        for (int i = 0; i < tab_strip_model->count(); i++) {
-          content::WebContents* web_contents =
-              tab_strip_model->GetWebContentsAt(i);
-          if (web_contents &&
-              web_contents->GetLastCommittedURL().SchemeIsHTTPOrHTTPS()) {
-            all_tabs.push_back(web_contents);
-          }
-        }
-        return true;
-      });
-  return all_tabs;
-}
-
 void RecordContextDeterminationStatus(ContextDeterminationStatus status) {
   base::UmaHistogramEnumeration(
       "ContextualTasks.Context.ContextDeterminationStatus", status);
@@ -188,7 +172,7 @@
   AUTO_CONTEXT_LOG(base::StringPrintf("Processing query %s in mode %d", query,
                                       options.tab_selection_mode));
 
-  if (!is_embedder_available_) {
+  if (!embedder_model_version_) {
     AUTO_CONTEXT_LOG("Embedder not available");
     RecordContextDeterminationStatus(
         ContextDeterminationStatus::kEmbedderNotAvailable);
@@ -213,7 +197,9 @@
 
 void ContextualTasksContextService::EmbedderMetadataUpdated(
     passage_embeddings::EmbedderMetadata metadata) {
-  is_embedder_available_ = metadata.IsValid();
+  embedder_model_version_ = metadata.IsValid()
+                                ? std::make_optional(metadata.model_version)
+                                : std::nullopt;
 }
 
 passage_embeddings::PageEmbeddingsService::Priority
@@ -250,37 +236,35 @@
     return;
   }
 
-  RecordContextDeterminationStatus(ContextDeterminationStatus::kSuccess);
-
   AUTO_CONTEXT_LOG(
       base::StringPrintf("Processing query embedding for %s", query));
 
-  passage_embeddings::Embedding query_embedding = embeddings[0];
-  std::vector<content::WebContents*> all_tabs = GetAllTabsForProfile(profile_);
-  std::vector<content::WebContents*> relevant_tabs =
-      SelectRelevantTabs(query, options, query_embedding, all_tabs);
-  size_t initial_relevant_count = relevant_tabs.size();
-
-  // Remove tabs that are not eligible for server upload.
-  std::erase_if(relevant_tabs, [this](content::WebContents* web_contents) {
-    bool removed = !ShouldAddTabToSelection(web_contents);
-    if (removed) {
-      AUTO_CONTEXT_LOG(
-          base::StringPrintf("Removing %s from relevant set as it is not "
-                             "eligible for server upload",
-                             web_contents->GetLastCommittedURL().spec()));
-    }
-    return removed;
-  });
-
-  if (relevant_tabs.size() != initial_relevant_count) {
-    base::UmaHistogramCounts100(
-        "ContextualTasks.Context.RelevantButInvalidTabsCount",
-        initial_relevant_count - relevant_tabs.size());
+  std::vector<content::WebContents*> all_tabs = GetAllEligibleTabs();
+  if (all_tabs.empty()) {
+    AUTO_CONTEXT_LOG("No eligible tabs");
+    RecordContextDeterminationStatus(
+        ContextDeterminationStatus::kNoEligibleTabs);
+    std::move(callback).Run({});
+    return;
   }
 
-  AUTO_CONTEXT_LOG(base::StringPrintf("Number of open tabs for query %s: %d",
-                                      query, all_tabs.size()));
+  RecordContextDeterminationStatus(ContextDeterminationStatus::kSuccess);
+
+  auto log_entry = std::make_unique<optimization_guide::ModelQualityLogEntry>(
+      optimization_guide_keyed_service_->GetModelQualityLogsUploaderService()
+          ->GetWeakPtr());
+
+  passage_embeddings::Embedding query_embedding = embeddings[0];
+  auto* quality_log = log_entry->log_ai_data_request()
+                          ->mutable_contextual_tasks_context()
+                          ->mutable_quality();
+  quality_log->set_embedding_model_version(
+      embedder_model_version_.value_or(-1));
+  std::vector<content::WebContents*> relevant_tabs = SelectRelevantTabs(
+      query, options, query_embedding, all_tabs, explicit_urls, quality_log);
+
+  AUTO_CONTEXT_LOG(base::StringPrintf(
+      "Number of eligible open tabs for query %s: %d", query, all_tabs.size()));
   AUTO_CONTEXT_LOG(base::StringPrintf(
       "Number of relevant tabs for query %s: %d", query, relevant_tabs.size()));
 
@@ -299,19 +283,60 @@
         std::set<GURL>(explicit_urls.begin(), explicit_urls.end()));
   }
 
+  if (!ShouldLogContextualTasksContextQuality() ||
+      quality_log->eligible_tabs().size() == 0) {
+    // Explicitly drop when we don't want to log. Otherwise, the destructor of
+    // the log entry will trigger an upload.
+    optimization_guide::ModelQualityLogEntry::Drop(std::move(log_entry));
+  }
+
   std::move(callback).Run(std::move(relevant_tabs));
 }
 
 std::vector<content::WebContents*>
+ContextualTasksContextService::GetAllEligibleTabs() {
+  std::vector<content::WebContents*> all_tabs;
+  ForEachCurrentBrowserWindowInterfaceOrderedByActivation(
+      [this, &all_tabs](BrowserWindowInterface* browser) {
+        if (browser->GetProfile() != profile_) {
+          return true;
+        }
+        TabStripModel* const tab_strip_model = browser->GetTabStripModel();
+        for (int i = 0; i < tab_strip_model->count(); i++) {
+          content::WebContents* web_contents =
+              tab_strip_model->GetWebContentsAt(i);
+          if (!web_contents) {
+            continue;
+          }
+          if (!web_contents->GetLastCommittedURL().SchemeIsHTTPOrHTTPS()) {
+            continue;
+          }
+          if (!ShouldAddTabToSelection(web_contents)) {
+            AUTO_CONTEXT_LOG(
+                base::StringPrintf("Removing %s from relevant set as it is not "
+                                   "eligible for server upload",
+                                   web_contents->GetLastCommittedURL().spec()));
+            continue;
+          }
+          all_tabs.push_back(web_contents);
+        }
+        return true;
+      });
+  return all_tabs;
+}
+
+std::vector<content::WebContents*>
 ContextualTasksContextService::SelectRelevantTabs(
     const std::string& query,
     const TabSelectionOptions& options,
     const passage_embeddings::Embedding& query_embedding,
-    const std::vector<content::WebContents*>& all_tabs) {
+    const std::vector<content::WebContents*>& all_tabs,
+    const std::vector<GURL>& explicit_urls,
+    optimization_guide::proto::ContextualTasksContextQuality* quality_log) {
   switch (options.tab_selection_mode) {
     case mojom::TabSelectionMode::kMultiSignalScoring:
       return SelectTabsByMultiSignalScore(query, options, query_embedding,
-                                          all_tabs);
+                                          all_tabs, explicit_urls, quality_log);
     case mojom::TabSelectionMode::kEmbeddingsMatch:
       return SelectTabsByEmbeddingsMatch(query, options, query_embedding,
                                          all_tabs);
@@ -323,9 +348,14 @@
     const std::string& query,
     const TabSelectionOptions& options,
     const passage_embeddings::Embedding& query_embedding,
-    const std::vector<content::WebContents*>& all_tabs) {
+    const std::vector<content::WebContents*>& all_tabs,
+    const std::vector<GURL>& explicit_urls,
+    optimization_guide::proto::ContextualTasksContextQuality* quality_log) {
   std::vector<content::WebContents*> relevant_tabs;
   for (auto* web_contents : all_tabs) {
+    optimization_guide::proto::ContextualTasksTabContext* tab_context =
+        quality_log->add_eligible_tabs();
+
     // Collect tab signals.
     TabSignals tab_signals;
     tab_signals.web_contents = web_contents;
@@ -343,23 +373,32 @@
           "ContextualTasks.Context.EmbeddingSimilarityScore",
           static_cast<int>(
               std::min(100 * *(tab_signals.embedding_score), 100.0f)));
+      tab_context->set_best_embedding_score(*tab_signals.embedding_score);
     }
     if (tab_signals.duration_since_last_active.has_value()) {
       base::UmaHistogramTimes("ContextualTasks.Context.DurationSinceLastActive",
                               *(tab_signals.duration_since_last_active));
+      tab_context->set_seconds_since_last_active(
+          tab_signals.duration_since_last_active->InSeconds());
     }
     if (tab_signals.num_query_title_matching_words.has_value()) {
       base::UmaHistogramCounts100(
           "ContextualTasks.Context.MatchingWordsCount",
           std::min(*(tab_signals.num_query_title_matching_words), 100));
+      tab_context->set_number_of_common_words(
+          *tab_signals.num_query_title_matching_words);
     }
 
     // Score and select qualifying tabs.
     double score = GetTabScore(tab_signals);
+    tab_context->set_aggregate_tab_score(score);
     if (score >= options.min_model_score.value_or(kMinMultiSignalScore.Get())) {
       relevant_tabs.push_back(tab_signals.web_contents);
     }
 
+    tab_context->set_was_explicitly_chosen(
+        base::Contains(explicit_urls, web_contents->GetLastCommittedURL()));
+
     base::UmaHistogramSparse("ContextualTasks.Context.TabScore",
                              static_cast<int>(std::min(100 * score, 100.0)));
 
diff --git a/chrome/browser/contextual_tasks/contextual_tasks_context_service.h b/chrome/browser/contextual_tasks/contextual_tasks_context_service.h
index 88b134b..f4cb205 100644
--- a/chrome/browser/contextual_tasks/contextual_tasks_context_service.h
+++ b/chrome/browser/contextual_tasks/contextual_tasks_context_service.h
@@ -27,6 +27,10 @@
 class WebContents;
 }  // namespace content
 
+namespace optimization_guide::proto {
+class ContextualTasksContextQuality;
+}  // namespace optimization_guide::proto
+
 namespace page_content_annotations {
 class PageContentExtractionService;
 }  // namespace page_content_annotations
@@ -44,10 +48,11 @@
   kEmbedderNotAvailable = 1,
   kQueryEmbeddingFailed = 2,
   kQueryEmbeddingOutputMalformed = 3,
+  kNoEligibleTabs = 4,
 
   // Keep in sync with ContextualTasksContextDeterminationStatus in
   // contextual_tasks/enums.xml.
-  kMaxValue = kQueryEmbeddingOutputMalformed,
+  kMaxValue = kNoEligibleTabs,
 };
 
 // Options to regulate tab selection behavior.
@@ -109,12 +114,17 @@
       passage_embeddings::Embedder::TaskId task_id,
       passage_embeddings::ComputeEmbeddingsStatus status);
 
+  // Returns all tabs for the profile that are eligible for selection.
+  std::vector<content::WebContents*> GetAllEligibleTabs();
+
   // Returns the relevant tabs for `query` based on given `tab_selection_mode`.
   std::vector<content::WebContents*> SelectRelevantTabs(
       const std::string& query,
       const TabSelectionOptions& options,
       const passage_embeddings::Embedding& query_embedding,
-      const std::vector<content::WebContents*>& all_tabs);
+      const std::vector<content::WebContents*>& all_tabs,
+      const std::vector<GURL>& explicit_urls,
+      optimization_guide::proto::ContextualTasksContextQuality* quality_log);
 
   // Selects tabs based on embeddings match.
   std::vector<content::WebContents*> SelectTabsByEmbeddingsMatch(
@@ -129,7 +139,9 @@
       const std::string& query,
       const TabSelectionOptions& options,
       const passage_embeddings::Embedding& query_embedding,
-      const std::vector<content::WebContents*>& all_tabs);
+      const std::vector<content::WebContents*>& all_tabs,
+      const std::vector<GURL>& explicit_urls,
+      optimization_guide::proto::ContextualTasksContextQuality* quality_log);
 
   // Returns the duration since the tab was last active.
   std::optional<base::TimeDelta> GetDurationSinceLastActive(
@@ -138,8 +150,8 @@
   // Returns whether the tab should be added to the selection.
   bool ShouldAddTabToSelection(content::WebContents* web_contents);
 
-  // Whether the embedder is available.
-  bool is_embedder_available_ = false;
+  // The version of the embedder model.
+  std::optional<int64_t> embedder_model_version_;
 
   // Not owned. Guaranteed to outlive `this`.
   raw_ptr<Profile> profile_;
diff --git a/chrome/browser/contextual_tasks/contextual_tasks_context_service_browsertest.cc b/chrome/browser/contextual_tasks/contextual_tasks_context_service_browsertest.cc
index 6151731..1f84246 100644
--- a/chrome/browser/contextual_tasks/contextual_tasks_context_service_browsertest.cc
+++ b/chrome/browser/contextual_tasks/contextual_tasks_context_service_browsertest.cc
@@ -8,7 +8,9 @@
 #include "base/test/scoped_feature_list.h"
 #include "base/test/simple_test_tick_clock.h"
 #include "base/test/test_future.h"
+#include "chrome/browser/browser_process.h"
 #include "chrome/browser/contextual_tasks/contextual_tasks_context_service_factory.h"
+#include "chrome/browser/optimization_guide/optimization_guide_keyed_service.h"
 #include "chrome/browser/optimization_guide/optimization_guide_keyed_service_factory.h"
 #include "chrome/browser/page_content_annotations/page_content_extraction_service.h"
 #include "chrome/browser/page_content_annotations/page_content_extraction_service_factory.h"
@@ -20,7 +22,7 @@
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/ui_test_utils.h"
 #include "components/contextual_tasks/public/features.h"
-#include "components/optimization_guide/proto/features/common_quality_data.pb.h"
+#include "components/optimization_guide/core/model_quality/test_model_quality_logs_uploader_service.h"
 #include "components/passage_embeddings/passage_embeddings_features.h"
 #include "components/passage_embeddings/passage_embeddings_test_util.h"
 #include "content/public/test/browser_test.h"
@@ -120,18 +122,25 @@
 
 class ContextualTasksContextServiceTest : public InProcessBrowserTest {
  public:
-  ContextualTasksContextServiceTest() { InitializeFeatureList(); }
+  void SetUp() override {
+    InitializeFeatureList();
+    InProcessBrowserTest::SetUp();
+  }
 
-  ~ContextualTasksContextServiceTest() override {
+  void TearDown() override {
     scoped_feature_list_.Reset();
+    InProcessBrowserTest::TearDown();
   }
 
   virtual void InitializeFeatureList() {
     scoped_feature_list_.InitWithFeaturesAndParameters(
-        {{kContextualTasksContext,
-          {{{"ContextualTasksContextOnlyUseTitles", "false"},
-            {"ContextualTasksContextEmbeddingSimilarityScore", "0.8"},
-            {"ContextualTasksContextMinMultiSignalScore", "0.8"}}}}},
+        {
+            {kContextualTasksContext,
+             {{{"ContextualTasksContextOnlyUseTitles", "false"},
+               {"ContextualTasksContextEmbeddingSimilarityScore", "0.8"},
+               {"ContextualTasksContextMinMultiSignalScore", "0.8"}}}},
+            {kContextualTasksContextLogging, {}},
+        },
         /*disabled_features=*/{});
   }
 
@@ -142,10 +151,12 @@
     embedded_test_server()->ServeFilesFromSourceDirectory(
         "chrome/test/data/optimization_guide");
     ASSERT_TRUE(embedded_test_server()->Start());
-  }
 
-  void TearDown() override {
-    InProcessBrowserTest::TearDown();
+    OptimizationGuideKeyedServiceFactory::GetForProfile(browser()->profile())
+        ->SetModelQualityLogsUploaderServiceForTesting(
+            std::make_unique<
+                optimization_guide::TestModelQualityLogsUploaderService>(
+                g_browser_process->local_state()));
   }
 
   void SetUpBrowserContextKeyedServices(
@@ -214,6 +225,14 @@
             GetForProfile(browser()->profile()));
   }
 
+  optimization_guide::TestModelQualityLogsUploaderService* logs_uploader() {
+    return static_cast<
+        optimization_guide::TestModelQualityLogsUploaderService*>(
+        OptimizationGuideKeyedServiceFactory::GetForProfile(
+            browser()->profile())
+            ->GetModelQualityLogsUploaderService());
+  }
+
   void NotifyEmbedderMetadata() {
     embedder_metadata_provider_.NotifyObservers();
   }
@@ -343,7 +362,8 @@
 
   base::test::TestFuture<std::vector<content::WebContents*>> future;
   service()->GetRelevantTabsForQuery(
-      /*options=*/{}, "some text",
+      {.tab_selection_mode = mojom::TabSelectionMode::kEmbeddingsMatch},
+      "some text",
       /*explicit_urls=*/{valid_url()}, future.GetCallback());
   EXPECT_EQ(1u, future.Get().size());
 
@@ -362,31 +382,19 @@
       "ContextualTasks.Context.TabOverlapPercentage", 100, 1);
   histogram_tester.ExpectUniqueSample("ContextualTasks.Context.TabExcessCount",
                                       0, 1);
+
+  EXPECT_TRUE(logs_uploader()->uploaded_logs().empty());
 }
 
 IN_PROC_BROWSER_TEST_F(ContextualTasksContextServiceTest,
-                       SuccessButNotValidForServerUpload) {
+                       NotValidForServerUpload) {
   base::HistogramTester histogram_tester;
 
   NavigateToValidURL();
 
   NotifyEmbedderMetadata();
 
-  std::vector<passage_embeddings::PassageEmbedding> fake_page_embeddings = {
-      // Not match.
-      {std::make_pair("passage 1",
-                      passage_embeddings::PassageType::kPageContent),
-       CreateFakeEmbedding(0.1f)},
-      // Match - active tab is added.
-      {std::make_pair("passage 2",
-                      passage_embeddings::PassageType::kPageContent),
-       CreateFakeEmbedding(1.0f)},
-      // Match - should be skipped.
-      {std::make_pair("passage 3",
-                      passage_embeddings::PassageType::kPageContent),
-       CreateFakeEmbedding(1.0f)}};
-  EXPECT_CALL(*page_embeddings_service(), GetEmbeddings(_))
-      .WillOnce(Return(fake_page_embeddings));
+  EXPECT_CALL(*page_embeddings_service(), GetEmbeddings(_)).Times(0);
   page_content_annotations::ExtractedPageContentResult result;
   result.is_eligible_for_server_upload = false;
   EXPECT_CALL(*page_content_extraction_service(),
@@ -395,23 +403,22 @@
 
   base::test::TestFuture<std::vector<content::WebContents*>> future;
   service()->GetRelevantTabsForQuery(
-      /*options=*/{}, "some text",
+      {.tab_selection_mode = mojom::TabSelectionMode::kEmbeddingsMatch},
+      "some text",
       /*explicit_urls=*/{}, future.GetCallback());
   EXPECT_TRUE(future.Get().empty());
 
-  histogram_tester.ExpectUniqueSample(
-      "ContextualTasks.Context.RelevantTabsCount", 0, 1);
-  histogram_tester.ExpectUniqueSample(
-      "ContextualTasks.Context.RelevantButInvalidTabsCount", 1, 1);
+  histogram_tester.ExpectTotalCount("ContextualTasks.Context.RelevantTabsCount",
+                                    0);
   histogram_tester.ExpectTotalCount(
-      "ContextualTasks.Context.ContextCalculationLatency", 1);
+      "ContextualTasks.Context.ContextCalculationLatency", 0);
   histogram_tester.ExpectUniqueSample(
       "ContextualTasks.Context.ContextDeterminationStatus",
-      ContextDeterminationStatus::kSuccess, 1);
+      ContextDeterminationStatus::kNoEligibleTabs, 1);
 }
 
 IN_PROC_BROWSER_TEST_F(ContextualTasksContextServiceTest,
-                       MultiSignalScoringHistograms) {
+                       MultiSignalScoringMetrics) {
   base::HistogramTester histogram_tester;
 
   test_clock_.SetNowTicks(base::TimeTicks::Now());
@@ -431,6 +438,9 @@
 
   test_clock_.Advance(base::Seconds(10));
 
+  base::test::TestFuture<void> logging_future;
+  logs_uploader()->WaitForLogUpload(logging_future.GetCallback());
+
   base::test::TestFuture<std::vector<content::WebContents*>> future;
   service()->GetRelevantTabsForQuery(
       {.tab_selection_mode = mojom::TabSelectionMode::kMultiSignalScoring},
@@ -460,6 +470,25 @@
       "ContextualTasks.Context.TabOverlapPercentage", 0, 1);
   histogram_tester.ExpectUniqueSample("ContextualTasks.Context.TabExcessCount",
                                       1, 1);
+
+  ASSERT_TRUE(logging_future.Wait());
+  EXPECT_EQ(logs_uploader()->uploaded_logs().size(), 1u);
+  optimization_guide::proto::ContextualTasksContextQuality
+      uploaded_quality_log = logs_uploader()
+                                 ->uploaded_logs()[0]
+                                 ->contextual_tasks_context()
+                                 .quality();
+  EXPECT_EQ(uploaded_quality_log.eligible_tabs().size(), 1);
+  EXPECT_GT(uploaded_quality_log.eligible_tabs()[0].best_embedding_score(),
+            0.99f);
+  EXPECT_GE(uploaded_quality_log.eligible_tabs()[0].seconds_since_last_active(),
+            10);
+  EXPECT_EQ(uploaded_quality_log.eligible_tabs()[0].number_of_common_words(),
+            0);
+  EXPECT_FLOAT_EQ(uploaded_quality_log.eligible_tabs()[0].aggregate_tab_score(),
+                  1.0f);
+  EXPECT_EQ(uploaded_quality_log.eligible_tabs()[0].was_explicitly_chosen(),
+            false);
 }
 
 IN_PROC_BROWSER_TEST_F(ContextualTasksContextServiceTest,
@@ -532,7 +561,7 @@
   base::test::TestFuture<std::vector<content::WebContents*>> future;
   service()->GetRelevantTabsForQuery(
       {.tab_selection_mode = mojom::TabSelectionMode::kMultiSignalScoring},
-      "some text", /*explicit_urls*/ {}, future.GetCallback());
+      "some text", /*explicit_urls=*/{}, future.GetCallback());
   EXPECT_EQ(1u, future.Get().size());
 
   histogram_tester.ExpectUniqueSample(
@@ -587,7 +616,7 @@
   base::test::TestFuture<std::vector<content::WebContents*>> future;
   service()->GetRelevantTabsForQuery(
       {.tab_selection_mode = mojom::TabSelectionMode::kMultiSignalScoring},
-      "some text", /*explicit_urls*/ {}, future.GetCallback());
+      "some text", /*explicit_urls=*/{}, future.GetCallback());
   EXPECT_EQ(0u, future.Get().size());
 }
 
@@ -604,19 +633,23 @@
   EXPECT_TRUE(future.Get().empty());
 
   histogram_tester.ExpectUniqueSample(
-      "ContextualTasks.Context.RelevantTabsCount", 0, 1);
+      "ContextualTasks.Context.ContextDeterminationStatus",
+      ContextDeterminationStatus::kNoEligibleTabs, 1);
+  histogram_tester.ExpectTotalCount("ContextualTasks.Context.RelevantTabsCount",
+                                    0);
   histogram_tester.ExpectTotalCount(
-      "ContextualTasks.Context.ContextCalculationLatency", 1);
+      "ContextualTasks.Context.ContextCalculationLatency", 0);
 }
 
 class ContextualTasksContextServiceTitlesOnlyTest
     : public ContextualTasksContextServiceTest {
  public:
+  // ContextualTasksContextServiceTest:
   void InitializeFeatureList() override {
     scoped_feature_list_.InitWithFeaturesAndParameters(
         {{kContextualTasksContext,
           {{{"ContextualTasksContextOnlyUseTitles", "true"}}}}},
-        /*disabled_features=*/{});
+        {kContextualTasksContextLogging});
   }
 };
 
@@ -644,6 +677,7 @@
   service()->GetRelevantTabsForQuery(
       /*options=*/{}, "some text", /*explicit_urls=*/{}, future.GetCallback());
   EXPECT_EQ(1u, future.Get().size());
+  EXPECT_TRUE(logs_uploader()->uploaded_logs().empty());
 }
 
 }  // namespace contextual_tasks
diff --git a/chrome/browser/contextual_tasks/contextual_tasks_side_panel_coordinator.cc b/chrome/browser/contextual_tasks/contextual_tasks_side_panel_coordinator.cc
index 83f205d..716558f 100644
--- a/chrome/browser/contextual_tasks/contextual_tasks_side_panel_coordinator.cc
+++ b/chrome/browser/contextual_tasks/contextual_tasks_side_panel_coordinator.cc
@@ -11,9 +11,13 @@
 #include "chrome/browser/contextual_tasks/contextual_tasks_ui.h"
 #include "chrome/browser/contextual_tasks/contextual_tasks_ui_service.h"
 #include "chrome/browser/contextual_tasks/contextual_tasks_ui_service_factory.h"
+#include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser_element_identifiers.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/hats/hats_service.h"
+#include "chrome/browser/ui/hats/hats_service_factory.h"
+#include "chrome/browser/ui/hats/survey_config.h"
 #include "chrome/browser/ui/views/interaction/browser_elements_views.h"
 #include "chrome/browser/ui/views/side_panel/side_panel_coordinator.h"
 #include "chrome/browser/ui/views/side_panel/side_panel_entry.h"
@@ -22,9 +26,11 @@
 #include "chrome/browser/ui/views/side_panel/side_panel_registry.h"
 #include "chrome/browser/ui/views/side_panel/side_panel_web_ui_view.h"
 #include "chrome/browser/ui/webui/webui_embedding_context.h"
+#include "chrome/common/pref_names.h"
 #include "chrome/common/webui_url_constants.h"
 #include "chrome/grit/generated_resources.h"
 #include "components/contextual_tasks/public/features.h"
+#include "components/prefs/pref_service.h"
 #include "components/sessions/content/session_tab_helper.h"
 #include "components/strings/grit/components_strings.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
@@ -111,6 +117,7 @@
               browser_window->GetProfile())),
       ui_service_(ContextualTasksUiServiceFactory::GetForBrowserContext(
           browser_window->GetProfile())),
+      pref_service_(browser_window->GetProfile()->GetPrefs()),
       scoped_unowned_user_data_(browser_window->GetUnownedUserDataHost(),
                                 *this) {
   CreateAndRegisterEntry(SidePanelRegistry::From(browser_window_));
@@ -154,6 +161,24 @@
 }
 
 void ContextualTasksSidePanelCoordinator::Show(bool transition_from_tab) {
+  // Increment the impression count and attempt to show the HaTS survey.
+  int impression_count =
+      pref_service_->GetInteger(prefs::kContextualTasksNextPanelOpenCount);
+  if (base::FeatureList::IsEnabled(
+          features::kHappinessTrackingSurveysForDesktopNextPanel) &&
+      impression_count >= 1) {
+    HatsService* hats_service =
+        HatsServiceFactory::GetForProfile(browser_window_->GetProfile(),
+                                          /* create_if_necessary = */ true);
+    if (hats_service) {
+      hats_service->LaunchDelayedSurvey(
+          kHatsSurveyTriggerNextPanel, 90000, {},
+          {{"Experiment ID", "4R1Q1L4GennVNwyF88Ccc6"}});
+    }
+  }
+  pref_service_->SetInteger(prefs::kContextualTasksNextPanelOpenCount,
+                            impression_count + 1);
+
   if (!GetCurrentTask()) {
     // If no task is found, create a new task and associate it with the active
     // tab.
diff --git a/chrome/browser/contextual_tasks/contextual_tasks_side_panel_coordinator.h b/chrome/browser/contextual_tasks/contextual_tasks_side_panel_coordinator.h
index 5b681bc..f5dd83d7 100644
--- a/chrome/browser/contextual_tasks/contextual_tasks_side_panel_coordinator.h
+++ b/chrome/browser/contextual_tasks/contextual_tasks_side_panel_coordinator.h
@@ -16,6 +16,7 @@
 class BrowserWindowInterface;
 class SidePanelEntryScope;
 class SidePanelRegistry;
+class PrefService;
 
 namespace base {
 class Uuid;
@@ -170,6 +171,9 @@
 
   const raw_ptr<ContextualTasksUiService> ui_service_;
 
+  // Pref service for the current profile.
+  const raw_ptr<PrefService> pref_service_;
+
   // WebView of the current side panel. It's owned by side panel framework so
   // weak pointer is needed in case it's destroyed. The WebContents in the
   // WebView is owned by the cache and can change based on active task change.
diff --git a/chrome/browser/devtools/devtools_ui_bindings.cc b/chrome/browser/devtools/devtools_ui_bindings.cc
index abb0ef8..ac082a4 100644
--- a/chrome/browser/devtools/devtools_ui_bindings.cc
+++ b/chrome/browser/devtools/devtools_ui_bindings.cc
@@ -1947,16 +1947,6 @@
                       std::move(devtools_animation_styles_in_styles_tab_dict));
   }
 
-  if (net::features::kIpPrivacyEnableIppInDevTools.Get()) {
-    response_dict.Set("devToolsIpProtectionInDevTools",
-                      base::Value::Dict().Set("enabled", true));
-  }
-
-  if (net::features::kIpPrivacyEnableIppPanelInDevTools.Get()) {
-    response_dict.Set("devToolsIpProtectionPanelInDevTools",
-                      base::Value::Dict().Set("enabled", true));
-  }
-
   base::Value::Dict deep_links_via_extensibility_api_dict;
   deep_links_via_extensibility_api_dict.Set(
       "enabled",
diff --git a/chrome/browser/enterprise/reporting/policy_info_unittest.cc b/chrome/browser/enterprise/reporting/policy_info_unittest.cc
index 9c1b22a..f567e0e 100644
--- a/chrome/browser/enterprise/reporting/policy_info_unittest.cc
+++ b/chrome/browser/enterprise/reporting/policy_info_unittest.cc
@@ -43,8 +43,6 @@
 using ::testing::_;
 using ::testing::Eq;
 
-// TODO(crbug.com/40700771): Get rid of chrome/browser dependencies and then
-// move this file to components/enterprise/browser.
 class PolicyInfoTest : public ::testing::Test {
  public:
   void SetUp() override {
diff --git a/chrome/browser/enterprise/reporting/report_request_queue_generator_unittest.cc b/chrome/browser/enterprise/reporting/report_request_queue_generator_unittest.cc
index f6741958..3f54d5a 100644
--- a/chrome/browser/enterprise/reporting/report_request_queue_generator_unittest.cc
+++ b/chrome/browser/enterprise/reporting/report_request_queue_generator_unittest.cc
@@ -55,8 +55,6 @@
 
 }  // namespace
 
-// TODO(crbug.com/40704763): Get rid of chrome/browser dependencies and then
-// move this file to components/enterprise/browser.
 class ReportRequestQueueGeneratorTest : public ::testing::Test {
  public:
   ReportRequestQueueGeneratorTest()
diff --git a/chrome/browser/extensions/api/autofill_private/autofill_private_api.cc b/chrome/browser/extensions/api/autofill_private/autofill_private_api.cc
index 0495f5d..6b39bc75 100644
--- a/chrome/browser/extensions/api/autofill_private/autofill_private_api.cc
+++ b/chrome/browser/extensions/api/autofill_private/autofill_private_api.cc
@@ -1189,10 +1189,13 @@
       params = autofill_private::SetWalletablePassDetectionOptInStatus::Params::
           Create(args());
   EXTENSION_FUNCTION_VALIDATE(params);
-  wallet::SetWalletablePassDetectionOptInStatus(
+
+  const bool success = wallet::SetWalletablePassDetectionOptInStatus(
       autofill_client()->GetPrefs(), autofill_client()->GetIdentityManager(),
+      wallet::GeoIpCountryCode(
+          autofill_client()->GetVariationConfigCountryCode().value()),
       params->opted_in);
-  return RespondNow(NoArguments());
+  return RespondNow(WithArguments(success));
 }
 
 }  // namespace extensions
diff --git a/chrome/browser/extensions/api/autofill_private/autofill_private_api_unittest.cc b/chrome/browser/extensions/api/autofill_private/autofill_private_api_unittest.cc
index 9a47e1e..5d0536e 100644
--- a/chrome/browser/extensions/api/autofill_private/autofill_private_api_unittest.cc
+++ b/chrome/browser/extensions/api/autofill_private/autofill_private_api_unittest.cc
@@ -37,6 +37,7 @@
 #include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/pref_service.h"
 #include "components/sync/test/test_sync_service.h"
+#include "components/wallet/core/browser/walletable_permission_utils.h"
 #include "components/wallet/core/common/wallet_features.h"
 #include "components/wallet/core/common/wallet_prefs.h"
 #include "content/public/test/browser_test.h"
@@ -188,13 +189,14 @@
 class AutofillPrivateApiUnitTest : public extensions::ExtensionApiTest {
  public:
   AutofillPrivateApiUnitTest() {
-    feature_list_.InitWithFeatures(
+    feature_list_.InitWithFeaturesAndParameters(
         /*enabled_features=*/
         {
-            autofill::features::kAutofillAiWithDataSchema,
-            autofill::features::kAutofillAiWalletFlightReservation,
-            autofill::features::kAutofillAiWalletVehicleRegistration,
-            wallet::kWalletablePassDetection,
+            {autofill::features::kAutofillAiWithDataSchema, {}},
+            {autofill::features::kAutofillAiWalletFlightReservation, {}},
+            {autofill::features::kAutofillAiWalletVehicleRegistration, {}},
+            {wallet::kWalletablePassDetection,
+             {{wallet::kWalletablePassDetectionCountryAllowlist.name, "US"}}},
         },
         /*disabled_features=*/
         {autofill::features::kAutofillAiIgnoreLocale,
@@ -496,4 +498,41 @@
       RunAutofillSubtest("verifyUserOptedOutOfWalletablePassDetection"));
 }
 
+IN_PROC_BROWSER_TEST_F(
+    AutofillPrivateApiUnitTest,
+    SetWalletablePassDetectionOptInStatus_SwitchEligibility) {
+  autofill_client()->GetPrefs()->registry()->RegisterDictionaryPref(
+      wallet::prefs::kWalletablePassDetectionOptInStatus);
+  autofill_client()->SetUpPrefsAndIdentityForAutofillAi();
+
+  // Ensure we are eligible initially (US is usually supported).
+  autofill_client()->SetVariationConfigCountryCode(
+      autofill::GeoIpCountryCode("US"));
+  ASSERT_TRUE(wallet::IsEligibleForWalletablePassDetection(
+      autofill_client()->GetIdentityManager(),
+      wallet::GeoIpCountryCode(
+          autofill_client()->GetVariationConfigCountryCode().value())));
+
+  EXPECT_TRUE(RunAutofillSubtest("optIntoWalletablePassDetection"));
+  EXPECT_TRUE(RunAutofillSubtest("verifyUserOptedIntoWalletablePassDetection"));
+
+  EXPECT_TRUE(RunAutofillSubtest("optOutOfWalletablePassDetection"));
+  EXPECT_TRUE(
+      RunAutofillSubtest("verifyUserOptedOutOfWalletablePassDetection"));
+
+  // Become ineligible.
+  autofill_client()->SetVariationConfigCountryCode(
+      autofill::GeoIpCountryCode("XX"));
+  ASSERT_FALSE(wallet::IsEligibleForWalletablePassDetection(
+      autofill_client()->GetIdentityManager(),
+      wallet::GeoIpCountryCode(
+          autofill_client()->GetVariationConfigCountryCode().value())));
+
+  // Verify that we cannot opt into Walletable Pass Detection anymore.
+  EXPECT_TRUE(
+      RunAutofillSubtest("optIntoWalletablePassDetectionExpectingFailure"));
+  EXPECT_TRUE(
+      RunAutofillSubtest("verifyUserOptedOutOfWalletablePassDetection"));
+}
+
 }  // namespace
diff --git a/chrome/browser/extensions/mv2_experiment_stage.h b/chrome/browser/extensions/mv2_experiment_stage.h
index af0ebf6..41fd39225 100644
--- a/chrome/browser/extensions/mv2_experiment_stage.h
+++ b/chrome/browser/extensions/mv2_experiment_stage.h
@@ -25,9 +25,8 @@
   // re-enable them.
   kUnsupported,
 
-  // TODO(https://crbug.com/337191307): Continue adding more experiment stages
-  // here. For each new entry, update Mv2ExperimentStage in
-  // chrome/browser/resources/extensions/mv2_deprecation_util.ts.
+  // Continue adding more experiment stages here. For each new entry, update
+  // Mv2ExperimentStage in chrome/browser/resources/extensions/mv2_deprecation_util.ts.
 };
 
 }  // namespace extensions
diff --git a/chrome/browser/extensions/web_accessible_resources_browsertest.cc b/chrome/browser/extensions/web_accessible_resources_browsertest.cc
index a60a64b5..f4383f6 100644
--- a/chrome/browser/extensions/web_accessible_resources_browsertest.cc
+++ b/chrome/browser/extensions/web_accessible_resources_browsertest.cc
@@ -34,11 +34,6 @@
 #include "net/dns/mock_host_resolver.h"
 #include "testing/gmock/include/gmock/gmock.h"
 
-#if BUILDFLAG(ENABLE_EXTENSIONS)
-#include "chrome/browser/ui/browser.h"
-#include "chrome/test/base/ui_test_utils.h"
-#endif
-
 static_assert(BUILDFLAG(ENABLE_EXTENSIONS_CORE));
 
 namespace extensions {
@@ -290,10 +285,7 @@
   }
 }
 
-#if BUILDFLAG(ENABLE_EXTENSIONS)
 // Navigate to a web page and then try to load an extension subresource.
-// TODO(crbug.com/390687767): Port to desktop Android. Currently the redirect
-// doesn't happen.
 IN_PROC_BROWSER_TEST_F(WebAccessibleResourcesBrowserTest,
                        SubresourceReachabilityAfterServerRedirect) {
   // Load extension.
@@ -327,7 +319,9 @@
 
   for (const auto& test_case : test_cases) {
     SCOPED_TRACE(test_case.title);
-    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), gurl));
+    // Some test cases have net errors, so NavigateToURL() may return true or
+    // false.
+    (void)NavigateToURL(GetActiveWebContents(), gurl);
 
     // Navigate to a web page and then fetch the supplied subresource.
     static constexpr char kScriptTemplate[] = R"(
@@ -372,8 +366,6 @@
 }
 
 // Server redirect to a web accessible resource whereby `matches` doesn't match.
-// TODO(crbug.com/390687767): Port to desktop Android. Currently the redirect
-// doesn't happen.
 IN_PROC_BROWSER_TEST_F(WebAccessibleResourcesBrowserTest,
                        ServerRedirectSubresource) {
   // Load extension.
@@ -397,7 +389,7 @@
   const char* filename = "accessible.html";
   net::Error error = net::ERR_BLOCKED_BY_CLIENT;
 
-  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), gurl));
+  ASSERT_TRUE(NavigateToURL(GetActiveWebContents(), gurl));
 
   // Navigate to a web page and then fetch the supplied subresource.
   static constexpr char kScriptTemplate[] = R"(
@@ -441,8 +433,6 @@
 }
 
 // Server redirect to a web accessible resource whereby `matches` doesn't match.
-// TODO(crbug.com/390687767): Port to desktop Android. Currently the redirect
-// doesn't happen.
 IN_PROC_BROWSER_TEST_F(WebAccessibleResourcesBrowserTest,
                        ServerRedirectMainframe) {
   // Load extension.
@@ -469,12 +459,12 @@
 
   auto* web_contents = GetActiveWebContents();
   content::TestNavigationObserver observer(web_contents);
-  EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), gurl));
+  // Navigation should be blocked, so NavigateToURL() returns false.
+  EXPECT_FALSE(NavigateToURL(web_contents, gurl));
   observer.WaitForNavigationFinished();
   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
@@ -592,9 +582,6 @@
   EXPECT_EQ("dnr redirect success", result.ExtractString());
 }
 
-#if !BUILDFLAG(IS_ANDROID)
-
-// TODO(crbug.com/425708956): Enable this test for desktop Android.
 class WebAccessibleResourcesServiceWorkerBrowserTest
     : public WebAccessibleResourcesBrowserTest {
  public:
@@ -661,9 +648,6 @@
       << expected_content << " not found in " << result.ExtractString();
 }
 
-// TODO(crbug.com/390687767): Port to desktop Android. Currently the redirect
-// doesn't happen.
-
 // Test server redirect to a web accessible or extension resource.
 IN_PROC_BROWSER_TEST_F(WebAccessibleResourcesBrowserTest,
                        BrowserProcessRedirect) {
@@ -689,7 +673,9 @@
               extension->GetResourceURL(resource).spec().c_str()));
       auto* web_contents = GetActiveWebContents();
       content::TestNavigationObserver observer(web_contents);
-      EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), gurl));
+      // Some test cases force a net error, so NavigateToURL() may return
+      // either true or false. Discard its return value.
+      (void)NavigateToURL(web_contents, gurl);
       observer.WaitForNavigationFinished();
       EXPECT_EQ(extension->GetResourceURL(resource),
                 observer.last_navigation_url());
@@ -704,6 +690,8 @@
     server_redirect(net::ERR_BLOCKED_BY_CLIENT, "resource.html", false);
   };
 
+  // Android only supports manifest V3.
+#if !BUILDFLAG(IS_ANDROID)
   auto TestBrowserRedirectMV2 = [&]() {
     TestBrowserRedirect(
         R"({
@@ -714,6 +702,8 @@
         })",
         "Extensions.WAR.XOriginWebAccessible.MV2");
   };
+  TestBrowserRedirectMV2();
+#endif  // BUILDFLAG(IS_ANDROID)
 
   auto TestBrowserRedirectMV3 = [&]() {
     TestBrowserRedirect(
@@ -730,8 +720,6 @@
         })",
         "Extensions.WAR.XOriginWebAccessible.MV3");
   };
-
-  TestBrowserRedirectMV2();
   TestBrowserRedirectMV3();
 }
 
@@ -827,7 +815,9 @@
     GURL gurl = embedded_test_server()->GetURL("a.example.com", "/empty.html");
     auto* web_contents = GetActiveWebContents();
     content::TestNavigationObserver observer(web_contents);
-    EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), gurl));
+    // A network error is expected and this version of NavigateToURL() returns
+    // false on navigation errors, similar to content::NavigateToURL().
+    EXPECT_FALSE(NavigateToURL(web_contents, gurl));
     observer.WaitForNavigationFinished();
     EXPECT_EQ(expect_net_error == net::OK,
               observer.last_navigation_succeeded());
@@ -880,7 +870,10 @@
   };
 
   TestBrowserRedirect(MV3);
+  // Android only supports manifest V3.
+#if !BUILDFLAG(IS_ANDROID)
   TestBrowserRedirect(MV2);
+#endif
 }
 
 // Test dynamic origins in web accessible resources.
@@ -930,8 +923,8 @@
 
   // Resource and extension origin should match.
   {
-    ASSERT_TRUE(ui_test_utils::NavigateToURL(
-        browser(), extension->GetResourceURL("ok.html")));
+    ASSERT_TRUE(NavigateToURL(GetActiveWebContents(),
+                              extension->GetResourceURL("ok.html")));
     ASSERT_EQ(extension->origin(),
               GetPrimaryMainFrame()->GetLastCommittedOrigin());
   }
@@ -941,7 +934,7 @@
     GURL static_url = extension->url().Resolve("ok.html");
     GURL dynamic_url = extension->dynamic_url().Resolve("ok.html");
     ASSERT_NE(static_url, dynamic_url);
-    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), dynamic_url));
+    ASSERT_TRUE(NavigateToURL(GetActiveWebContents(), dynamic_url));
     EXPECT_EQ(static_url, GetPrimaryMainFrame()->GetLastCommittedURL());
     EXPECT_EQ(extension->origin(),
               GetPrimaryMainFrame()->GetLastCommittedOrigin());
@@ -985,8 +978,8 @@
                                     << ": frame_url = " << frame_url
                                     << "; fetch_url = " << fetch_url);
     // Fetch and test resource.
-    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), frame_url));
     content::WebContents* web_contents = GetActiveWebContents();
+    ASSERT_TRUE(NavigateToURL(web_contents, frame_url));
     EXPECT_EQ(expected_frame_url,
               web_contents->GetPrimaryMainFrame()->GetLastCommittedURL());
 
@@ -1034,8 +1027,6 @@
   }
 }
 
-#endif  // !BUILDFLAG(IS_ANDROID)
-
 // Fetch web accessible resources directly from a file:// page.
 IN_PROC_BROWSER_TEST_F(WebAccessibleResourcesBrowserTest,
                        FileSchemeInitiators_MainWorld) {
@@ -1115,11 +1106,8 @@
   ASSERT_TRUE(content::EvalJs(web_contents, script).ExtractBool());
 }
 
-#if !BUILDFLAG(IS_ANDROID)
 // Test loading of subresources using an initiator coming from a file:// scheme,
 // and, notably, from within a content script context.
-// TODO(crbug.com/391921606): Port to desktop Android when the chrome.scripting
-// API is ported.
 IN_PROC_BROWSER_TEST_F(WebAccessibleResourcesBrowserTest,
                        FileSchemeInitiators_ContentScript) {
   // Load extension.
@@ -1206,7 +1194,6 @@
       profile(), extension->id(), base::StringPrintf(kScript, tab_id));
   ASSERT_TRUE(catcher.GetNextResult()) << catcher.message();
 }
-#endif  // !BUILDFLAG(IS_ANDROID)
 
 // Useful for testing web accessible resources loaded from a content script.
 class WebAccessibleResourcesDynamicUrlScriptingBrowserTest
@@ -1328,10 +1315,7 @@
   ASSERT_TRUE(catcher.GetNextResult()) << catcher.message();
 }
 
-#if !BUILDFLAG(IS_ANDROID)
 // Load dynamic web accessible resources via chrome.scripting.executeScript().
-// TODO(crbug.com/391921606): Port to desktop Android when the chrome.scripting
-// API is ported.
 IN_PROC_BROWSER_TEST_F(WebAccessibleResourcesDynamicUrlScriptingBrowserTest,
                        ExecuteScript) {
   // Load extension.
@@ -1358,7 +1342,6 @@
       profile(), extension->id(), base::StringPrintf(kScript, tab_id));
   ASSERT_TRUE(catcher.GetNextResult()) << catcher.message();
 }
-#endif  // !BUILDFLAG(IS_ANDROID)
 
 }  // namespace
 }  // namespace extensions
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index f47e994..aec6f44e 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -2356,6 +2356,11 @@
     "expiry_milestone": 145
   },
   {
+    "name": "default-browser-promo-refresh",
+    "owners": [ "cheickcisse@google.com", "bling-mony-pod@google.com" ],
+    "expiry_milestone": 150
+  },
+  {
     "name": "default-browser-promo-trigger-criteria-experiment",
     "owners": [ "gayane@google.com", "bling-flags@google.com" ],
     "expiry_milestone": 140
@@ -5508,6 +5513,16 @@
     "expiry_milestone": 135
   },
   {
+    "name": "gemini-copresence",
+    "owners": [
+      "adamta@chromium.org",
+      "joemerramos@google.com",
+      "tarekmakkouk@google.com",
+      "bling-alchemy-eng@google.com"
+    ],
+    "expiry_milestone": 160
+  },
+  {
     "name": "gemini-cross-tab",
     "owners": [ "adamta@google.com", "bling-alchemy-eng@google.com" ],
     "expiry_milestone": 150
@@ -6273,11 +6288,6 @@
     "expiry_milestone": 145
   },
   {
-    "name": "ios-new-share-extension",
-    "owners": [ "erahmaoui@google.com", "olivierrobin@google.com", "bling-flags@google.com" ],
-    "expiry_milestone": 140
-  },
-  {
     "name": "ios-one-tap-mini-map-remove-section-breaks",
     "owners": [ "olivierrobin@google.com", "bling-flags@google.com" ],
     "expiry_milestone": 145
@@ -6420,11 +6430,6 @@
     "expiry_milestone": 145
   },
   {
-    "name": "ip-protection-proxy-opt-out",
-    "owners": [ "awillia@chromium.org", "//chrome/browser/ip_protection/OWNERS" ],
-    "expiry_milestone": 148
-  },
-  {
     "name": "iph-autofill-credit-card-benefit-feature",
     "owners": [ "viplavkadam@google.com", "payments-autofill-team@google.com"],
     "expiry_milestone": 145
@@ -6722,6 +6727,11 @@
     "expiry_milestone": 145
   },
   {
+    "name": "lens-stream-service-web-channel-transport-enabled",
+    "owners": ["cmyang@google.com", "brandonli@google.com"],
+    "expiry_milestone": 152
+  },
+  {
     "name": "lens-strokes-api-enabled",
     "owners": ["aboodmufti@google.com", "cmyang@google.com"],
     "expiry_milestone": 145
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 954a3afb..40ae5ee 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -2554,16 +2554,6 @@
     "Switches Instant Hotspot to use Nearby Presence for device discovery, as "
     "well as Nearby Connections for device communication.";
 
-inline constexpr char kIpProtectionProxyOptOutName[] =
-    "Disable IP Protection Proxy";
-inline constexpr char kIpProtectionProxyOptOutDescription[] =
-    "When disabled, prevents use of the IP Protection proxy. This is intended "
-    "to help with diagnosing any issues that could be caused by the feature "
-    "being enabled. For the current status of this feature, see: "
-    "https://chromestatus.com/feature/5111460239245312";
-inline constexpr char kIpProtectionProxyOptOutChoiceDefault[] = "Default";
-inline constexpr char kIpProtectionProxyOptOutChoiceOptOut[] = "Disabled";
-
 inline constexpr char
     kInvalidateSearchEngineChoiceOnDeviceRestoreDetectionName[] =
         "Invalidate search engine choice after the install detects it has been "
@@ -6753,11 +6743,6 @@
 inline constexpr char kArcUnthrottleOnActiveAudioV2Description[] =
     "Do not throttle ARC when there is an active audio stream running.";
 
-inline constexpr char kArcVideoEncodeUseMappableSIName[] =
-    "ARC video encode use mappable SharedImage";
-inline constexpr char kArcVideoEncodeUseMappableSIDescription[] =
-    "Controls whether ARC video encoding uses mappable SharedImage.";
-
 inline constexpr char kAshEnableUnifiedDesktopName[] = "Unified desktop mode";
 inline constexpr char kAshEnableUnifiedDesktopDescription[] =
     "Enable unified desktop mode which allows a window to span multiple "
@@ -7619,10 +7604,6 @@
     "Limits items on the shelf to the ones associated with windows on the "
     "active desk";
 
-inline constexpr char kListAllDisplayModesName[] = "List all display modes";
-inline constexpr char kListAllDisplayModesDescription[] =
-    "Enables listing all external displays' modes in the display settings.";
-
 inline constexpr char kHindiInscriptLayoutName[] =
     "Hindi Inscript Layout on CrOS";
 inline constexpr char kHindiInscriptLayoutDescription[] =
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 a35bd13c..324e15c 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
@@ -1837,4 +1837,7 @@
             sAndroidTabHighlighting.newBooleanParam("force_ctrl_click", false);
     public static final MutableBooleanParamWithSafeDefault sAndroidTabHighlightingForceShiftClick =
             sAndroidTabHighlighting.newBooleanParam("force_shift_click", false);
+
+    public static final MutableBooleanParamWithSafeDefault sRobustWindowManagementBulkClose =
+            sRobustWindowManagement.newBooleanParam("bulk_close", false);
 }
diff --git a/chrome/browser/glic/actor/glic_actor_task_manager.cc b/chrome/browser/glic/actor/glic_actor_task_manager.cc
index 8fc515e..e43da7d 100644
--- a/chrome/browser/glic/actor/glic_actor_task_manager.cc
+++ b/chrome/browser/glic/actor/glic_actor_task_manager.cc
@@ -18,6 +18,7 @@
 #include "chrome/browser/actor/tools/tool_request.h"
 #include "chrome/browser/glic/host/context/glic_tab_data.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/common/actor.mojom-data-view.h"
 #include "chrome/common/actor/action_result.h"
 #include "chrome/common/actor/journal_details_builder.h"
 #include "chrome/common/chrome_features.h"
@@ -81,6 +82,17 @@
     return;
   }
 
+  // If the task went away it must have been handled in the !task branch above.
+  DCHECK_NE(result_code, actor::mojom::ActionResultCode::kTaskWentAway);
+
+  if (result_code == actor::mojom::ActionResultCode::kTaskPaused) {
+    optimization_guide::proto::ActionsResult response =
+        actor::BuildErrorActionsResult(
+            actor::mojom::ActionResultCode::kTaskPaused, std::nullopt);
+    std::move(callback).Run(mojo_base::ProtoWrapper(response));
+    return;
+  }
+
   // The callback doesn't need any weak semantics since all it does is wrap the
   // result and pass it to the mojo callback. If `this` is destroyed the mojo
   // connection is closed so this will be a no-op but the callback doesn't touch
@@ -380,8 +392,7 @@
     return;
   }
   actor::ActorTask::State next_state = actor::ActorTask::State::kReflecting;
-  if (base::FeatureList::IsEnabled(features::kGlicActorUninterruptDuringAct) &&
-      task->GetExecutionEngine() &&
+  if (task->GetExecutionEngine() &&
       task->GetExecutionEngine()->HasActionSequence()) {
     // TODO(mcnee): Explicitly stash the old state instead of inferring it.
     next_state = actor::ActorTask::State::kActing;
diff --git a/chrome/browser/glic/browser_ui/glic_button_controller.h b/chrome/browser/glic/browser_ui/glic_button_controller.h
index ba2bed5b..39d23a9 100644
--- a/chrome/browser/glic/browser_ui/glic_button_controller.h
+++ b/chrome/browser/glic/browser_ui/glic_button_controller.h
@@ -9,7 +9,6 @@
 
 #include "base/callback_list.h"
 #include "base/memory/raw_ptr.h"
-#include "chrome/browser/glic/host/glic.mojom.h"
 #include "chrome/browser/glic/widget/glic_window_controller.h"
 #include "components/prefs/pref_change_registrar.h"
 
diff --git a/chrome/browser/glic/e2e_test/internal b/chrome/browser/glic/e2e_test/internal
index 663cabc..5dbeda3 160000
--- a/chrome/browser/glic/e2e_test/internal
+++ b/chrome/browser/glic/e2e_test/internal
@@ -1 +1 @@
-Subproject commit 663cabcaa66039de359b513c6e562c04f3e09fbc
+Subproject commit 5dbeda3a1dd50c77b82185b21cfb47fef05c0686
diff --git a/chrome/browser/glic/glic_user_status_interactive_uitest.cc b/chrome/browser/glic/glic_user_status_interactive_uitest.cc
index 8ac06163..6223685 100644
--- a/chrome/browser/glic/glic_user_status_interactive_uitest.cc
+++ b/chrome/browser/glic/glic_user_status_interactive_uitest.cc
@@ -138,7 +138,10 @@
       identity_manager->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin);
   AccountInfo account_info =
       identity_manager->FindExtendedAccountInfo(core_account_info);
-  account_info.hosted_domain = gaia::ExtractDomainName(account_info.email);
+  account_info =
+      AccountInfo::Builder(account_info)
+          .SetHostedDomain(gaia::ExtractDomainName(account_info.email))
+          .Build();
   signin::UpdateAccountInfoForAccount(identity_manager, account_info);
 }
 
diff --git a/chrome/browser/glic/host/context/glic_page_context_fetcher.cc b/chrome/browser/glic/host/context/glic_page_context_fetcher.cc
index b89f0e0..116a781 100644
--- a/chrome/browser/glic/host/context/glic_page_context_fetcher.cc
+++ b/chrome/browser/glic/host/context/glic_page_context_fetcher.cc
@@ -77,8 +77,8 @@
             page_context.inner_text_result->truncated));
   }
 
-  // TODO(crbug.com/446700005): This path is used for both actor and non-actor
-  // context but includes bits specific to actor.
+  // TODO(b/446700005): This path is used for both actor and non-actor context
+  // fetching, but includes bits specific to actor.
   actor::AggregatedJournal* journal = nullptr;
   actor::TaskId task_id;
   if (journal_entry) {
@@ -97,8 +97,7 @@
         page_context.screenshot_result->dimensions.height(),
         std::move(page_context.screenshot_result->screenshot_data),
         page_context.screenshot_result->mime_type,
-        // TODO(b/380495633): Finalize and implement image
-        // annotations.
+        // Implement image annotations (see b/380495633).
         glic::mojom::ImageOriginAnnotations::New());
   }
 
diff --git a/chrome/browser/glic/host/glic_actor_general_interactive_uitest.cc b/chrome/browser/glic/host/glic_actor_general_interactive_uitest.cc
index f9e4a49..db6c2659 100644
--- a/chrome/browser/glic/host/glic_actor_general_interactive_uitest.cc
+++ b/chrome/browser/glic/host/glic_actor_general_interactive_uitest.cc
@@ -6,7 +6,9 @@
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/protobuf_matchers.h"
 #include "base/time/time.h"
+#include "base/types/cxx23_to_underlying.h"
 #include "build/build_config.h"
+#include "chrome/browser/actor/actor_features.h"
 #include "chrome/browser/actor/actor_tab_data.h"
 #include "chrome/browser/actor/actor_test_util.h"
 #include "chrome/browser/actor/browser_action_util.h"
@@ -607,6 +609,222 @@
   // clang-format on
 }
 
+// This test suite sets a 60s click delay so that the click tool waits 60
+// seconds between mouse down and mouse up. This is used to ensure that once the
+// tool is invoked it doesn't return unless canceled.
+class GlicActorCallbackOrderGeneralUiTest : public GlicActorGeneralUiTest {
+ public:
+  GlicActorCallbackOrderGeneralUiTest() {
+    feature_list_.InitWithFeaturesAndParameters(
+        {
+            {features::kGlicActor,
+             {{features::kGlicActorClickDelay.name, "60000ms"}}},
+            {actor::kGlicPerformActionsReturnsBeforeStateChange, {}},
+        },
+        /*disabled_features=*/{});
+  }
+
+  ~GlicActorCallbackOrderGeneralUiTest() override = default;
+
+  MultiStep RecordActorTaskStateChanges() {
+    return Steps(ExecuteInGlic(base::BindLambdaForTesting(
+        [&task_id = task_id_](content::WebContents* glic_contents) {
+          std::string script = content::JsReplace(R"JS(
+              window.event_log= [];
+              window.taskStateObs = client.browser.getActorTaskState($1);
+              window.taskStateObs.subscribe((new_state) => {
+                const state_name = (() => {
+                  switch(new_state) {
+                    case ActorTaskState.UNKNOWN: return 'UNKNOWN';
+                    case ActorTaskState.IDLE: return 'IDLE';
+                    case ActorTaskState.ACTING: return 'ACTING';
+                    case ActorTaskState.PAUSED: return 'PAUSED';
+                    case ActorTaskState.STOPPED: return 'STOPPED';
+                    default: return 'UNEXPECTED';
+                  }
+                })();
+                window.event_log.push(state_name);
+              });
+            )JS",
+                                                  task_id.value());
+          ASSERT_TRUE(content::ExecJs(glic_contents, script));
+        })));
+  }
+
+  MultiStep WaitForActorTaskState(mojom::ActorTaskState state) {
+    return Steps(ExecuteInGlic(base::BindLambdaForTesting(
+        [state](content::WebContents* glic_contents) {
+          std::string script = content::JsReplace(
+              R"JS(
+            window.taskStateObs.waitUntil((state) => {
+              return state == $1;
+            });
+          )JS",
+              base::to_underlying(state));
+          ASSERT_TRUE(content::ExecJs(glic_contents, script));
+        })));
+  }
+
+  MultiStep InvokeToolThatNeverFinishes(ui::ElementIdentifier tab_id) {
+    return Steps(
+        InAnyContext(WithElement(
+            tab_id,
+            [this](ui::TrackedElement* el) {
+              content::WebContents* contents =
+                  AsInstrumentedWebContents(el)->web_contents();
+              acting_tab_ =
+                  tabs::TabInterface::GetFromContents(contents)->GetHandle();
+            })),
+        ExecuteInGlic(base::BindLambdaForTesting(
+            [&](content::WebContents* glic_contents) {
+              apc::Actions action =
+                  actor::MakeClick(acting_tab_, gfx::Point(15, 15),
+                                   ClickAction::LEFT, ClickAction::SINGLE);
+              action.set_task_id(task_id_.value());
+              std::string encoded_action = EncodeActionProto(action);
+              std::string script = content::JsReplace(
+                  R"JS(
+                  window.performActionsPromise =
+                      client.browser.performActions(
+                          Uint8Array.fromBase64($1).buffer);
+                  window.performActionsPromise.then(() => {
+                        window.event_log.push('PERFORM_ACTIONS_RETURNED');
+                    });
+                )JS",
+                  encoded_action);
+              ASSERT_TRUE(
+                  content::ExecJs(glic_contents, std::move(script),
+                                  content::EXECUTE_SCRIPT_NO_RESOLVE_PROMISES));
+            })));
+  }
+
+  std::string GetEventLog() {
+    content::WebContents* glic_contents = GetGlicContents();
+    return content::EvalJs(glic_contents, "window.event_log.join(',')")
+        .ExtractString();
+  }
+
+ private:
+  tabs::TabHandle acting_tab_;
+  base::test::ScopedFeatureList feature_list_;
+};
+
+// Ensure that an in-progress call to PerformActions returns before the task
+// state change callback after a task is canceled.
+IN_PROC_BROWSER_TEST_F(GlicActorCallbackOrderGeneralUiTest,
+                       PerformActionsReturnsBeforeStateChangeOnStop) {
+  DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kNewActorTabId);
+
+  RunTestSequence(
+      // clang-format off
+      InitializeWithOpenGlicWindow(),
+      StartActorTaskInNewTab(GURL(url::kAboutBlankURL), kNewActorTabId),
+      SetOnIncompatibleAction(OnIncompatibleAction::kSkipTest,
+                              kActivateSurfaceIncompatibilityNotice),
+
+      RecordActorTaskStateChanges(),
+      InvokeToolThatNeverFinishes(kNewActorTabId),
+      StopActorTask(),
+      WaitForActorTaskState(mojom::ActorTaskState::kStopped),
+
+      CheckResult([this]() { return GetEventLog(); },
+          "IDLE,"
+          "ACTING,"
+          "PERFORM_ACTIONS_RETURNED,"
+          "STOPPED")
+    );
+  // clang-format on
+}
+
+// Ensure that an in-progress call to PerformActions returns before the task
+// state change callback after a task is paused.
+IN_PROC_BROWSER_TEST_F(GlicActorCallbackOrderGeneralUiTest,
+                       PerformActionsReturnsBeforeStateChangeOnPause) {
+  DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kNewActorTabId);
+
+  RunTestSequence(
+      // clang-format off
+      InitializeWithOpenGlicWindow(),
+      StartActorTaskInNewTab(GURL(url::kAboutBlankURL), kNewActorTabId),
+      SetOnIncompatibleAction(OnIncompatibleAction::kSkipTest,
+                              kActivateSurfaceIncompatibilityNotice),
+
+      RecordActorTaskStateChanges(),
+      InvokeToolThatNeverFinishes(kNewActorTabId),
+      PauseActorTask(),
+      WaitForActorTaskState(mojom::ActorTaskState::kPaused),
+
+      CheckResult([this]() { return GetEventLog(); },
+          "IDLE,"
+          "ACTING,"
+          "PERFORM_ACTIONS_RETURNED,"
+          "PAUSED")
+    );
+  // clang-format on
+}
+
+// Ensure that an in-progress call to PerformActions returns before the task
+// state change callback after a task is stopped while interrupted.
+IN_PROC_BROWSER_TEST_F(
+    GlicActorCallbackOrderGeneralUiTest,
+    PerformActionsReturnsBeforeStateChangeFromInterruptAndStop) {
+  DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kNewActorTabId);
+
+  RunTestSequence(
+      // clang-format off
+      InitializeWithOpenGlicWindow(),
+      StartActorTaskInNewTab(GURL(url::kAboutBlankURL), kNewActorTabId),
+      SetOnIncompatibleAction(OnIncompatibleAction::kSkipTest,
+                              kActivateSurfaceIncompatibilityNotice),
+
+      RecordActorTaskStateChanges(),
+      InvokeToolThatNeverFinishes(kNewActorTabId),
+      WaitForActorTaskState(mojom::ActorTaskState::kActing),
+      InterruptActorTask(),
+      StopActorTask(),
+      WaitForActorTaskState(mojom::ActorTaskState::kStopped),
+
+      CheckResult([this]() { return GetEventLog(); },
+          "IDLE,"
+          "ACTING,"
+          "IDLE,"
+          "PERFORM_ACTIONS_RETURNED,"
+          "STOPPED")
+    );
+  // clang-format on
+}
+
+// Ensure that an in-progress call to PerformActions returns before the task
+// state change callback after a task is paused while interrupted.
+IN_PROC_BROWSER_TEST_F(
+    GlicActorCallbackOrderGeneralUiTest,
+    PerformActionsReturnsBeforeStateChangeFromInterruptAndPause) {
+  DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kNewActorTabId);
+
+  RunTestSequence(
+      // clang-format off
+      InitializeWithOpenGlicWindow(),
+      StartActorTaskInNewTab(GURL(url::kAboutBlankURL), kNewActorTabId),
+      SetOnIncompatibleAction(OnIncompatibleAction::kSkipTest,
+                              kActivateSurfaceIncompatibilityNotice),
+
+      RecordActorTaskStateChanges(),
+      InvokeToolThatNeverFinishes(kNewActorTabId),
+      WaitForActorTaskState(mojom::ActorTaskState::kActing),
+      InterruptActorTask(),
+      PauseActorTask(),
+      WaitForActorTaskState(mojom::ActorTaskState::kPaused),
+
+      CheckResult([this]() { return GetEventLog(); },
+          "IDLE,"
+          "ACTING,"
+          "IDLE,"
+          "PERFORM_ACTIONS_RETURNED,"
+          "PAUSED")
+    );
+  // clang-format on
+}
+
 class GlicActorGeneralUiTestHighDPI : public GlicActorGeneralUiTest {
  public:
   static constexpr double kDeviceScaleFactor = 2.0;
@@ -655,6 +873,47 @@
   // clang-format on
 }
 
+// Test for the above behavior when the killswitch is turned off. i.e. that the
+// state change callback invokes before performActions resolves.
+class GlicActorCallbackOrderKillSwitchGeneralUiTest
+    : public GlicActorCallbackOrderGeneralUiTest {
+ public:
+  GlicActorCallbackOrderKillSwitchGeneralUiTest() {
+    feature_list_.InitAndDisableFeature(
+        actor::kGlicPerformActionsReturnsBeforeStateChange);
+  }
+
+  ~GlicActorCallbackOrderKillSwitchGeneralUiTest() override = default;
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+IN_PROC_BROWSER_TEST_F(GlicActorCallbackOrderKillSwitchGeneralUiTest,
+                       StateChangeBeforePerformActionsResolves) {
+  DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kNewActorTabId);
+
+  RunTestSequence(
+      // clang-format off
+      InitializeWithOpenGlicWindow(),
+      StartActorTaskInNewTab(GURL(url::kAboutBlankURL), kNewActorTabId),
+      SetOnIncompatibleAction(OnIncompatibleAction::kSkipTest,
+                              kActivateSurfaceIncompatibilityNotice),
+
+      RecordActorTaskStateChanges(),
+      InvokeToolThatNeverFinishes(kNewActorTabId),
+      StopActorTask(),
+      WaitForActorTaskState(mojom::ActorTaskState::kStopped),
+
+      CheckResult([this]() { return GetEventLog(); },
+          "IDLE,"
+          "ACTING,"
+          "STOPPED,"
+          "PERFORM_ACTIONS_RETURNED")
+    );
+  // clang-format on
+}
+
 }  // namespace
 
 }  // namespace glic::test
diff --git a/chrome/browser/glic/host/glic_actor_interactive_uitest_common.cc b/chrome/browser/glic/host/glic_actor_interactive_uitest_common.cc
index d20325d..5a19bd8 100644
--- a/chrome/browser/glic/host/glic_actor_interactive_uitest_common.cc
+++ b/chrome/browser/glic/host/glic_actor_interactive_uitest_common.cc
@@ -449,6 +449,16 @@
   }));
 }
 
+MultiStep GlicActorUiTest::InterruptActorTask() {
+  return Steps(Do([this, &task_id = task_id_]() mutable {
+                 content::WebContents* glic_contents = GetGlicContents();
+                 std::string script = content::JsReplace(
+                     "client.browser.interruptActorTask($1);", task_id.value());
+                 ASSERT_TRUE(content::ExecJs(glic_contents, script));
+               }),
+               RoundTrip(task_id_));
+}
+
 MultiStep GlicActorUiTest::WaitForActorTaskState(
     mojom::ActorTaskState expected_state) {
   // WaitForActorTaskState doesn't reliably check the stopped state, since the
diff --git a/chrome/browser/glic/host/glic_actor_interactive_uitest_common.h b/chrome/browser/glic/host/glic_actor_interactive_uitest_common.h
index a5687245..bcd7ae3 100644
--- a/chrome/browser/glic/host/glic_actor_interactive_uitest_common.h
+++ b/chrome/browser/glic/host/glic_actor_interactive_uitest_common.h
@@ -162,6 +162,9 @@
   MultiStep ResumeActorTask(base::Value::Dict context_options,
                             ExpectedResumeResult expected_result);
 
+  // Interrupts a task by calling the glic InterruptActorTask API.
+  MultiStep InterruptActorTask();
+
   MultiStep WaitForActorTaskState(mojom::ActorTaskState expected_state);
 
   // Gets a reference to a state observable for use in
diff --git a/chrome/browser/glic/host/glic_api_browsertest.cc b/chrome/browser/glic/host/glic_api_browsertest.cc
index cb07757..99ba1da 100644
--- a/chrome/browser/glic/host/glic_api_browsertest.cc
+++ b/chrome/browser/glic/host/glic_api_browsertest.cc
@@ -145,6 +145,7 @@
 
 DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kFirstTab);
 DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kSecondTab);
+DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kThirdTab);
 DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kSettingsTab);
 std::vector<std::string> GetTestSuiteNames() {
   return {
@@ -1165,7 +1166,8 @@
             tab0_instance->GetPanelState().kind);
 }
 
-IN_PROC_BROWSER_TEST_P(GlicApiTest, testSwitchConversationToSpecific) {
+IN_PROC_BROWSER_TEST_P(GlicApiTest,
+                       testSwitchConversationToOldConversationNewInstance) {
   if (!GetParam().multi_instance) {
     GTEST_SKIP() << "Multi-instance only";
   }
@@ -1177,7 +1179,8 @@
       GlicSwitchConversationTarget::kSwitchedToNewInstance, 1);
 }
 
-IN_PROC_BROWSER_TEST_P(GlicApiTest, testSwitchConversationToNew) {
+IN_PROC_BROWSER_TEST_P(GlicApiTest,
+                       testSwitchConversationToNewConversationNewInstance) {
   if (!GetParam().multi_instance) {
     GTEST_SKIP() << "Multi-instance only";
   }
@@ -1189,6 +1192,71 @@
       GlicSwitchConversationTarget::kStartNewConversation, 1);
 }
 
+IN_PROC_BROWSER_TEST_P(GlicApiTest,
+                       testSwitchConversationToLastActiveConversation) {
+  if (!GetParam().multi_instance) {
+    GTEST_SKIP() << "Multi-instance only";
+  }
+  RunTestSequence(OpenGlicWindow(GlicWindowMode::kDetached,
+                                 GlicInstrumentMode::kHostAndContents));
+
+  ExecuteJsTest({.params = base::Value("step1")});
+
+  ASSERT_TRUE(AddTabAtIndex(1, page_url(), ui::PAGE_TRANSITION_TYPED));
+  browser()->tab_strip_model()->ActivateTabAt(1);
+  TrackGlicInstanceWithTabIndex(1);
+  RunTestSequence(InstrumentTab(kSecondTab),
+                  OpenGlicWindow(GlicWindowMode::kDetached,
+                                 GlicInstrumentMode::kHostAndContents));
+
+  ExecuteJsTest({.params = base::Value("step2")});
+  ASSERT_TRUE(base::test::RunUntil([&]() {
+    return histogram_tester->GetBucketCount(
+               "Glic.Interaction.SwitchConversationTarget",
+               GlicSwitchConversationTarget::kSwitchedToLastActive) == 1;
+  }));
+  ContinueJsTest();
+}
+
+IN_PROC_BROWSER_TEST_P(GlicApiTest,
+                       testSwitchConversationToOldConversationInOldInstance) {
+  if (!GetParam().multi_instance) {
+    GTEST_SKIP() << "Multi-instance only";
+  }
+  RunTestSequence(OpenGlicWindow(GlicWindowMode::kDetached,
+                                 GlicInstrumentMode::kHostAndContents));
+
+  ExecuteJsTest({.params = base::Value("step1")});
+
+  ASSERT_TRUE(AddTabAtIndex(1, page_url(), ui::PAGE_TRANSITION_TYPED));
+  browser()->tab_strip_model()->ActivateTabAt(1);
+  TrackGlicInstanceWithTabIndex(1);
+  RunTestSequence(InstrumentTab(kSecondTab),
+                  OpenGlicWindow(GlicWindowMode::kDetached,
+                                 GlicInstrumentMode::kHostAndContents));
+
+  ExecuteJsTest({.params = base::Value("step2")});
+  ASSERT_TRUE(base::test::RunUntil([&]() {
+    return histogram_tester->GetBucketCount(
+               "Glic.Interaction.SwitchConversationTarget",
+               GlicSwitchConversationTarget::kSwitchedToNewInstance) == 1;
+  }));
+  ASSERT_TRUE(AddTabAtIndex(1, page_url(), ui::PAGE_TRANSITION_TYPED));
+  browser()->tab_strip_model()->ActivateTabAt(1);
+  RunTestSequence(InstrumentTab(kThirdTab),
+                  OpenGlicWindow(GlicWindowMode::kDetached,
+                                 GlicInstrumentMode::kHostAndContents));
+
+  ExecuteJsTest({.params = base::Value("step3")});
+
+  ASSERT_TRUE(base::test::RunUntil([&]() {
+    return histogram_tester->GetBucketCount(
+               "Glic.Interaction.SwitchConversationTarget",
+               GlicSwitchConversationTarget::kSwitchedToExistingInstance) == 1;
+  }));
+  ContinueJsTest();
+}
+
 IN_PROC_BROWSER_TEST_P(GlicApiTest, testTabSwitchDoesNotLogActivationMetric) {
   if (!GetParam().multi_instance) {
     GTEST_SKIP() << "This test requires multi-instance mode.";
@@ -1512,9 +1580,7 @@
 }
 
 // TODO(crbug.com/457010934): Flaky on Linux.
-// TODO(crbug.com/460828109): Enable on ChromeOS.
-// Also flaky on linux-win-cross-rel.
-#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_CHROMEOS)
+#if BUILDFLAG(IS_LINUX)
 #define MAYBE_testNoExtractionWhileHidden DISABLED_testNoExtractionWhileHidden
 #else
 #define MAYBE_testNoExtractionWhileHidden testNoExtractionWhileHidden
@@ -1524,28 +1590,28 @@
   // Attempt to extract focused tab context with the preloaded client.
   ExecuteJsTest();
 
-  // TODO(b/450923405): Metrics checks fail on win-rel.
-#if !BUILDFLAG(IS_WIN) && !defined(DEBUG)
+  // TODO(b/450923405): Hidden metrics off by one on win and chromeos.
+  int expected_count_hidden = 1;
+#if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_WIN)
+  expected_count_hidden = 0;
+#endif
   histogram_tester->ExpectBucketCount(
       "Glic.Api.RequestCounts.GetContextFromFocusedTab",
-      GlicRequestEvent::kRequestReceivedWhileHidden, 1);
+      GlicRequestEvent::kRequestReceivedWhileHidden, expected_count_hidden);
   histogram_tester->ExpectBucketCount(
       "Glic.Api.RequestCounts.GetContextFromFocusedTab",
       GlicRequestEvent::kRequestHandlerException, 1);
   histogram_tester->ExpectTotalCount("Glic.Api.RequestCounts.GetContextFromTab",
                                      0);
-#endif
 
   // Open the glic panel and attempt to extract focused and arbitrary tab
   // context.
   RunTestSequence(OpenGlicWindow(GlicWindowMode::kDetached,
                                  GlicInstrumentMode::kHostAndContents));
   ContinueJsTest();
-  // TODO(b/450923405): Metrics checks fail on win-rel.
-#if !BUILDFLAG(IS_WIN) && !defined(DEBUG)
   histogram_tester->ExpectBucketCount(
       "Glic.Api.RequestCounts.GetContextFromFocusedTab",
-      GlicRequestEvent::kRequestReceivedWhileHidden, 1);
+      GlicRequestEvent::kRequestReceivedWhileHidden, expected_count_hidden);
   histogram_tester->ExpectBucketCount(
       "Glic.Api.RequestCounts.GetContextFromFocusedTab",
       GlicRequestEvent::kRequestHandlerException, 1);
@@ -1555,17 +1621,14 @@
   histogram_tester->ExpectBucketCount(
       "Glic.Api.RequestCounts.GetContextFromTab",
       GlicRequestEvent::kRequestHandlerException, 0);
-#endif
 
   // Hide the glic panel again and attempt to extract focused and arbitrary tab
   // context.
   RunTestSequence(CloseGlic());
   ContinueJsTest();
-  // TODO(b/450923405): Metrics checks fail on win-rel.
-#if !BUILDFLAG(IS_WIN) && !defined(DEBUG)
   histogram_tester->ExpectBucketCount(
       "Glic.Api.RequestCounts.GetContextFromFocusedTab",
-      GlicRequestEvent::kRequestReceivedWhileHidden, 2);
+      GlicRequestEvent::kRequestReceivedWhileHidden, ++expected_count_hidden);
   histogram_tester->ExpectBucketCount(
       "Glic.Api.RequestCounts.GetContextFromFocusedTab",
       GlicRequestEvent::kRequestHandlerException, 2);
@@ -1575,7 +1638,6 @@
   histogram_tester->ExpectBucketCount(
       "Glic.Api.RequestCounts.GetContextFromTab",
       GlicRequestEvent::kRequestHandlerException, 1);
-#endif
 }
 
 IN_PROC_BROWSER_TEST_P(GlicApiTestWithOneTab, testGetFocusedTabStateV2) {
@@ -2599,7 +2661,10 @@
       identity_manager->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin);
   AccountInfo account_info =
       identity_manager->FindExtendedAccountInfo(core_account_info);
-  account_info.hosted_domain = gaia::ExtractDomainName(account_info.email);
+  account_info =
+      AccountInfo::Builder(account_info)
+          .SetHostedDomain(gaia::ExtractDomainName(account_info.email))
+          .Build();
   signin::UpdateAccountInfoForAccount(identity_manager, account_info);
 }
 
diff --git a/chrome/browser/glic/host/glic_features.mojom b/chrome/browser/glic/host/glic_features.mojom
index dd8e1bb1f..91f70fe 100644
--- a/chrome/browser/glic/host/glic_features.mojom
+++ b/chrome/browser/glic/host/glic_features.mojom
@@ -23,7 +23,7 @@
 
 feature kGlicAppendModelQualityClientId {
   const string name = "GlicAppendModelQualityClientId";
-  const bool default_state = false;
+  const bool default_state = true;
 };
 
 // Allows actor code to activate a tab.
diff --git a/chrome/browser/glic/host/glic_page_handler.cc b/chrome/browser/glic/host/glic_page_handler.cc
index 5bf90f2..ebf0137 100644
--- a/chrome/browser/glic/host/glic_page_handler.cc
+++ b/chrome/browser/glic/host/glic_page_handler.cc
@@ -1785,9 +1785,10 @@
     web_client_->NotifyFocusedTabChanged(std::move(data));
   }
 
-  void NotifyActorTaskStateChanged(const actor::ActorTask& task) {
+  void NotifyActorTaskStateChanged(actor::TaskId task_id,
+                                   actor::ActorTask::State task_state) {
     const mojom::ActorTaskState state = [&]() {
-      switch (task.GetState()) {
+      switch (task_state) {
         case actor::ActorTask::State::kCreated:
         case actor::ActorTask::State::kReflecting:
         case actor::ActorTask::State::kWaitingOnUser:
@@ -1803,7 +1804,7 @@
           return mojom::ActorTaskState::kStopped;
       }
     }();
-    web_client_->NotifyActorTaskStateChanged(task.id().value(), state);
+    web_client_->NotifyActorTaskStateChanged(task_id.value(), state);
   }
 
   void RequestToShowCredentialSelectionDialog(
diff --git a/chrome/browser/glic/host/glic_ui.cc b/chrome/browser/glic/host/glic_ui.cc
index 5cd12ac..8803e8ec 100644
--- a/chrome/browser/glic/host/glic_ui.cc
+++ b/chrome/browser/glic/host/glic_ui.cc
@@ -136,10 +136,6 @@
   if (allowed_origins.empty()) {
     allowed_origins = features::kGlicAllowedOriginsOverride.Get();
   }
-  if (allowed_origins.empty()) {
-    // TODO(crbug.com/396147389): Replace with the correct default.
-    allowed_origins = "https://*.google.com/";
-  }
 
   // Allow corp origins for @google accounts.
   signin::IdentityManager* identity_manager =
diff --git a/chrome/browser/infobars/infobars_browsertest.cc b/chrome/browser/infobars/infobars_browsertest.cc
index 9ad6c4a4..0a69dc7 100644
--- a/chrome/browser/infobars/infobars_browsertest.cc
+++ b/chrome/browser/infobars/infobars_browsertest.cc
@@ -456,6 +456,12 @@
   ShowAndVerifyUi();
 }
 
-IN_PROC_BROWSER_TEST_F(InfoBarUiTest, InvokeUi_multiple_infobars) {
+// Consistently failing on Windows https://crbug.com/1462107.
+#if BUILDFLAG(IS_WIN)
+#define MAYBE_InvokeUi_multiple_infobars DISABLED_InvokeUi_multiple_infobars
+#else
+#define MAYBE_InvokeUi_multiple_infobars InvokeUi_multiple_infobars
+#endif
+IN_PROC_BROWSER_TEST_F(InfoBarUiTest, MAYBE_InvokeUi_multiple_infobars) {
   ShowAndVerifyUi();
 }
diff --git a/chrome/browser/ip_protection/BUILD.gn b/chrome/browser/ip_protection/BUILD.gn
deleted file mode 100644
index 2125a368..0000000
--- a/chrome/browser/ip_protection/BUILD.gn
+++ /dev/null
@@ -1,119 +0,0 @@
-# Copyright 2024 The Chromium Authors
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import("//third_party/protobuf/proto_library.gni")
-
-static_library("ip_protection") {
-  visibility = [
-    ":*",
-    "//chrome/browser:*",
-    "//chrome/browser/profiles:profiles_extra_parts_impl",
-  ]
-
-  sources = [
-    "ip_protection_core_host.cc",
-    "ip_protection_core_host.h",
-    "ip_protection_core_host_factory.cc",
-    "ip_protection_core_host_factory.h",
-    "ip_protection_switches.cc",
-    "ip_protection_switches.h",
-  ]
-
-  public_deps = [
-    "//chrome/browser:browser_public_dependencies",
-    "//components/ip_protection/common:ip_protection_token_direct_fetcher",
-  ]
-
-  deps = [
-    "//base",
-    "//build:branding_buildflags",
-    "//chrome/browser:browser_process",
-    "//chrome/browser/profiles:profile",
-    "//chrome/browser/signin",
-    "//chrome/common:channel_info",
-    "//components/ip_protection:get_proxy_config_proto",
-    "//components/ip_protection/common:ip_protection_data_types",
-    "//components/ip_protection/common:ip_protection_proxy_config_direct_fetcher",
-    "//components/ip_protection/common:ip_protection_telemetry",
-    "//components/ip_protection/common:ip_protection_telemetry_uma",
-    "//components/prefs",
-    "//components/privacy_sandbox:features",
-    "//components/privacy_sandbox:tracking_protection_prefs",
-    "//components/privacy_sandbox:tracking_protection_settings",
-    "//components/signin/public/identity_manager",
-    "//components/variations/service",
-    "//content/public/browser",
-    "//google_apis",
-    "//google_apis/common:request_util",
-    "//mojo/public/cpp/bindings",
-    "//net",
-    "//net/third_party/quiche:blind_sign_auth",
-    "//services/network/public/cpp",
-    "//services/network/public/mojom",
-    "//services/network/public/mojom:url_loader_base",
-    "//third_party/anonymous_tokens:anonymous_tokens_cc_proto",
-  ]
-}
-
-source_set("unit_tests") {
-  testonly = true
-
-  sources = [
-    "ip_protection_core_host_factory_unittest.cc",
-    "ip_protection_core_host_unittest.cc",
-  ]
-
-  deps = [
-    ":ip_protection",
-    "//base/test:test_support",
-    "//chrome/test:test_support",
-    "//components/ip_protection/common:ip_protection_config_http",
-    "//components/ip_protection/common:ip_protection_data_types",
-    "//components/ip_protection/common:ip_protection_proxy_config_direct_fetcher",
-    "//components/ip_protection/common:mock_blind_sign_auth",
-    "//components/prefs:test_support",
-    "//components/privacy_sandbox:features",
-    "//components/privacy_sandbox:privacy_sandbox_prefs",
-    "//components/privacy_sandbox:tracking_protection_settings",
-    "//components/signin/public/identity_manager",
-    "//components/signin/public/identity_manager:test_support",
-    "//components/variations/service",
-    "//content/test:test_support",
-    "//net/third_party/quiche:blind_sign_auth",
-    "//net/traffic_annotation:test_support",
-    "//services/network:test_support",
-    "//testing/gtest",
-    "//third_party/anonymous_tokens:anonymous_tokens_cc_proto",
-  ]
-}
-
-source_set("browser_tests") {
-  testonly = true
-
-  sources = [ "ip_protection_core_host_browsertest.cc" ]
-
-  defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ]
-
-  deps = [
-    ":ip_protection",
-    "//base/test:test_support",
-    "//chrome/browser:browser_process",
-    "//chrome/browser/content_settings:content_settings_factory",
-    "//chrome/browser/policy:test_support",
-    "//chrome/browser/signin",
-    "//chrome/test:test_support",
-    "//components/ip_protection/common:ip_protection_data_types",
-    "//components/privacy_sandbox:features",
-    "//components/signin/public/identity_manager",
-    "//components/signin/public/identity_manager:test_support",
-    "//components/variations/service",
-    "//third_party/anonymous_tokens:anonymous_tokens_cc_proto",
-  ]
-
-  if (is_android) {
-    deps += [ "//chrome/test:test_support_ui_android" ]
-  } else {
-    deps += [ "//chrome/test:test_support_ui" ]
-  }
-}
diff --git a/chrome/browser/ip_protection/DIR_METADATA b/chrome/browser/ip_protection/DIR_METADATA
deleted file mode 100644
index 0dd79617..0000000
--- a/chrome/browser/ip_protection/DIR_METADATA
+++ /dev/null
@@ -1,7 +0,0 @@
-monorail: {
-  component: "Privacy>Fingerprinting>IPProtection"
-}
-team_email: "ip-protection-trio@google.com"
-buganizer_public: {
-  component_id: 1456782
-}
diff --git a/chrome/browser/ip_protection/OWNERS b/chrome/browser/ip_protection/OWNERS
deleted file mode 100644
index 6c85d8a5..0000000
--- a/chrome/browser/ip_protection/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-file://components/ip_protection/OWNERS
-
diff --git a/chrome/browser/ip_protection/ip_protection_core_host.cc b/chrome/browser/ip_protection/ip_protection_core_host.cc
deleted file mode 100644
index 8aa06e9..0000000
--- a/chrome/browser/ip_protection/ip_protection_core_host.cc
+++ /dev/null
@@ -1,470 +0,0 @@
-// Copyright 2024 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/ip_protection/ip_protection_core_host.h"
-
-#include <algorithm>
-#include <memory>
-#include <optional>
-
-#include "base/command_line.h"
-#include "base/feature_list.h"
-#include "base/functional/bind.h"
-#include "base/hash/hash.h"
-#include "base/metrics/histogram_functions.h"
-#include "base/sequence_checker.h"
-#include "base/strings/strcat.h"
-#include "base/task/bind_post_task.h"
-#include "base/task/task_traits.h"
-#include "base/task/thread_pool.h"
-#include "base/threading/sequence_bound.h"
-#include "base/time/time.h"
-#include "build/branding_buildflags.h"
-#include "chrome/browser/browser_process.h"
-#include "chrome/browser/ip_protection/ip_protection_switches.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/common/channel_info.h"
-#include "components/ip_protection/common/ip_protection_data_types.h"
-#include "components/ip_protection/common/ip_protection_proxy_config_direct_fetcher.h"
-#include "components/ip_protection/common/ip_protection_telemetry.h"
-#include "components/ip_protection/common/ip_protection_token_direct_fetcher.h"
-#include "components/prefs/pref_service.h"
-#include "components/privacy_sandbox/privacy_sandbox_features.h"
-#include "components/privacy_sandbox/tracking_protection_prefs.h"
-#include "components/privacy_sandbox/tracking_protection_settings.h"
-#include "components/variations/service/variations_service.h"
-#include "components/network_session_configurator/common/network_switches.h"
-#include "content/public/browser/browser_thread.h"
-#include "content/public/browser/storage_partition.h"
-#include "google_apis/common/api_key_request_util.h"
-#include "google_apis/gaia/gaia_constants.h"
-#include "google_apis/google_api_keys.h"
-#include "mojo/public/cpp/bindings/message.h"
-#include "mojo/public/cpp/bindings/pending_receiver.h"
-#include "mojo/public/cpp/bindings/pending_remote.h"
-#include "net/base/features.h"
-#include "net/base/proxy_chain.h"
-#include "net/base/proxy_server.h"
-#include "net/base/proxy_string_util.h"
-#include "net/third_party/quiche/src/quiche/blind_sign_auth/blind_sign_auth.h"
-#include "net/third_party/quiche/src/quiche/blind_sign_auth/proto/blind_sign_auth_options.pb.h"
-#include "net/third_party/quiche/src/quiche/blind_sign_auth/proto/spend_token_data.pb.h"
-#include "third_party/abseil-cpp/absl/status/status.h"
-
-using ::ip_protection::TryGetAuthTokensResult;
-
-namespace {
-
-bool IsLikelyDogfoodClient() {
-  variations::VariationsService* variations_service =
-      g_browser_process->variations_service();
-  return variations_service && variations_service->IsLikelyDogfoodClient();
-}
-
-}  // namespace
-IpProtectionCoreHost::IpProtectionCoreHost(
-    signin::IdentityManager* identity_manager,
-    privacy_sandbox::TrackingProtectionSettings* tracking_protection_settings,
-    PrefService* pref_service,
-    Profile* profile)
-    : identity_manager_(identity_manager),
-      tracking_protection_settings_(tracking_protection_settings),
-      pref_service_(pref_service),
-      profile_(profile) {
-  CHECK(identity_manager);
-  identity_manager_->AddObserver(this);
-  CHECK(tracking_protection_settings);
-  CHECK(pref_service_);
-  tracking_protection_settings_->AddObserver(this);
-}
-
-void IpProtectionCoreHost::SetUp() {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-
-  scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory;
-  if (!ip_protection_token_fetcher_ || !ip_protection_proxy_config_fetcher_) {
-    CHECK(profile_);
-    url_loader_factory = profile_->GetDefaultStoragePartition()
-                             ->GetURLLoaderFactoryForBrowserProcess();
-  }
-  if (!ip_protection_token_fetcher_) {
-    ip_protection_token_fetcher_ =
-        std::make_unique<ip_protection::IpProtectionTokenDirectFetcher>(
-            this, url_loader_factory->Clone());
-  }
-  if (!ip_protection_proxy_config_fetcher_) {
-    ip_protection_proxy_config_fetcher_ =
-        std::make_unique<ip_protection::IpProtectionProxyConfigDirectFetcher>(
-            std::move(url_loader_factory),
-            ip_protection::IpProtectionTokenFetcherHelper::kChromeIpBlinding,
-            this);
-  }
-}
-
-void IpProtectionCoreHost::SetUpForTesting(
-    scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
-    std::unique_ptr<quiche::BlindSignAuthInterface> bsa) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  for_testing_ = true;
-
-  // Carefully destroy any existing values in the correct order.
-  ip_protection_proxy_config_fetcher_.reset();
-  ip_protection_token_fetcher_.reset();
-
-  ip_protection_token_fetcher_ =
-      std::make_unique<ip_protection::IpProtectionTokenDirectFetcher>(
-          this, url_loader_factory->Clone(), std::move(bsa));
-  ip_protection_proxy_config_fetcher_ =
-      std::make_unique<ip_protection::IpProtectionProxyConfigDirectFetcher>(
-          std::move(url_loader_factory),
-          ip_protection::IpProtectionTokenFetcherHelper::kChromeIpBlinding,
-          this);
-}
-
-IpProtectionCoreHost::~IpProtectionCoreHost() = default;
-
-void IpProtectionCoreHost::TryGetAuthTokens(
-    uint32_t batch_size,
-    ip_protection::ProxyLayer proxy_layer,
-    TryGetAuthTokensCallback callback) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  CHECK(!is_shutting_down_);
-  SetUp();
-
-  // The `batch_size` is cast to an `int` for use by BlindSignAuth, so check
-  // for overflow here.
-  if (batch_size == 0 || batch_size > INT_MAX) {
-    mojo::ReportBadMessage("Invalid batch_size");
-    return;
-  }
-
-  ip_protection_token_fetcher_->TryGetAuthTokens(batch_size, proxy_layer,
-                                                 std::move(callback));
-}
-
-void IpProtectionCoreHost::GetProxyConfig(GetProxyConfigCallback callback) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  CHECK(!is_shutting_down_);
-  SetUp();
-
-  // Neither the API key nor the OAuth token will be available to
-  // non-Chrome-branded builds, so unless we are testing, bail out.
-#if !BUILDFLAG(GOOGLE_CHROME_BRANDING)
-  if (!for_testing_) {
-    std::move(callback).Run(std::nullopt, std::nullopt);
-    return;
-  }
-#endif  // !BUILDFLAG(GOOGLE_CHROME_BRANDING)
-
-  ip_protection_proxy_config_fetcher_->GetProxyConfig(base::BindOnce(
-      // Convert the mojo style callback, which takes `const
-      // std::optional<..>&`, to the preferred style, passing `std::optional` by
-      // value.
-      [](GetProxyConfigCallback callback,
-         std::optional<std::vector<::net::ProxyChain>> proxy_chain,
-         std::optional<ip_protection::GeoHint> geo_hint) {
-        std::move(callback).Run(proxy_chain, geo_hint);
-      },
-      std::move(callback)));
-}
-
-void IpProtectionCoreHost::RecycleTokens(
-    ip_protection::ProxyLayer proxy_layer,
-    std::vector<ip_protection::BlindSignedAuthToken> tokens) {
-  recycled_tokens_[proxy_layer] = std::move(tokens);
-}
-
-IpProtectionCoreHost::IpProtectionTokenCache
-IpProtectionCoreHost::TakeRecycledTokens() {
-  return std::exchange(recycled_tokens_, {});
-}
-
-void IpProtectionCoreHost::AuthenticateRequest(
-    std::unique_ptr<network::ResourceRequest> resource_request,
-    ip_protection::IpProtectionProxyConfigDirectFetcher::Delegate::
-        AuthenticateRequestCallback callback) {
-  // Apply either an OAuth token (which must be fetched) or an API key to the
-  // request.
-  if (net::features::kIpPrivacyIncludeOAuthTokenInGetProxyConfig.Get()) {
-    if (!CanRequestOAuthToken()) {
-      std::move(callback).Run(false, std::move(resource_request));
-      return;
-    }
-    RequestOAuthTokenInternal(base::BindOnce(
-        [](std::unique_ptr<network::ResourceRequest> resource_request,
-           ip_protection::IpProtectionProxyConfigDirectFetcher::Delegate::
-               AuthenticateRequestCallback callback,
-           GoogleServiceAuthError error,
-           signin::AccessTokenInfo access_token_info) {
-          if (error.state() != GoogleServiceAuthError::NONE) {
-            std::move(callback).Run(false, std::move(resource_request));
-            return;
-          }
-          resource_request->headers.SetHeader(
-              net::HttpRequestHeaders::kAuthorization,
-              base::StrCat({"Bearer ", access_token_info.token}));
-          std::move(callback).Run(true, std::move(resource_request));
-        },
-        std::move(resource_request), std::move(callback)));
-  } else {
-    google_apis::AddAPIKeyToRequest(
-        *resource_request, google_apis::GetAPIKey(chrome::GetChannel()));
-    std::move(callback).Run(true, std::move(resource_request));
-  }
-}
-
-void IpProtectionCoreHost::RequestOAuthToken(
-    ip_protection::IpProtectionTokenDirectFetcher::Delegate::
-        RequestOAuthTokenCallback callback) {
-  if (!CanRequestOAuthToken()) {
-    std::move(callback).Run(TryGetAuthTokensResult::kFailedNoAccount,
-                            std::nullopt);
-    return;
-  }
-
-  // If the user is not eligible and not a dogfooder, do not even try to fetch
-  // tokens. If unknown, fall back to trying anyway.
-  const CoreAccountId account_id =
-      identity_manager_->GetPrimaryAccountId(signin::ConsentLevel::kSignin);
-  CHECK(!account_id.empty());
-  const AccountInfo account_info =
-      identity_manager_->FindExtendedAccountInfoByAccountId(account_id);
-  bool cannot_use_chrome_ip_protection =
-      !account_info.IsEmpty() &&
-      account_info.capabilities.can_use_chrome_ip_protection() ==
-          signin::Tribool::kFalse;
-  if (cannot_use_chrome_ip_protection && !IsLikelyDogfoodClient()) {
-    VLOG(2) << "RequestOAuthToken failed: not eligible";
-    std::move(callback).Run(TryGetAuthTokensResult::kFailedNotEligible,
-                            std::nullopt);
-    return;
-  }
-
-  RequestOAuthTokenInternal(base::BindOnce(
-      [](ip_protection::IpProtectionTokenDirectFetcher::Delegate::
-             RequestOAuthTokenCallback callback,
-         GoogleServiceAuthError error,
-         signin::AccessTokenInfo access_token_info) {
-        if (error.state() != GoogleServiceAuthError::NONE) {
-          VLOG(2) << "RequestOAuthToken got an error: "
-                  << static_cast<int>(error.state());
-          std::move(callback).Run(
-              error.IsTransientError()
-                  ? TryGetAuthTokensResult::kFailedOAuthTokenTransient
-                  : TryGetAuthTokensResult::kFailedOAuthTokenPersistent,
-              std::nullopt);
-          return;
-        }
-        std::optional<std::string> access_token = access_token_info.token;
-        std::move(callback).Run(TryGetAuthTokensResult::kSuccess, access_token);
-      },
-      std::move(callback)));
-}
-
-void IpProtectionCoreHost::RequestOAuthTokenInternal(
-    RequestOAuthTokenInternalCallback callback) {
-  // Waits for the account to have a refresh token before making the request.
-  auto mode =
-      signin::PrimaryAccountAccessTokenFetcher::Mode::kWaitUntilAvailable;
-
-  // Create the OAuth token fetcher and call `OnRequestOAuthTokenCompleted()`
-  // when complete. The callback will own the
-  // `signin::PrimaryAccountAccessTokenFetcher()` object to ensure it stays
-  // alive long enough for the callback to occur, and we will pass a weak
-  // pointer to ensure that the callback won't be called if this object gets
-  // destroyed.
-  auto oauth_token_fetcher =
-      std::make_unique<signin::PrimaryAccountAccessTokenFetcher>(
-          signin::OAuthConsumerId::kIpProtectionService, identity_manager_,
-          mode, signin::ConsentLevel::kSignin);
-  auto* oauth_token_fetcher_ptr = oauth_token_fetcher.get();
-  oauth_token_fetcher_ptr->Start(base::BindOnce(
-      [](std::unique_ptr<signin::PrimaryAccountAccessTokenFetcher>
-             oauth_token_fetcher,
-         RequestOAuthTokenInternalCallback callback,
-         GoogleServiceAuthError error,
-         signin::AccessTokenInfo access_token_info) {
-        DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-        std::move(callback).Run(error, access_token_info);
-      },
-
-      std::move(oauth_token_fetcher), std::move(callback)));
-}
-
-void IpProtectionCoreHost::InvalidateNetworkContextTryAgainAfterTime() {
-  if (!profile_) {
-    // `profile_` will be nullptr if `Shutdown()` was called or if this is
-    // called in unit tests.
-    return;
-  }
-
-  for (auto& ipp_control : remotes_) {
-    ipp_control->AuthTokensMayBeAvailable();
-  }
-}
-
-void IpProtectionCoreHost::Shutdown() {
-  if (is_shutting_down_) {
-    return;
-  }
-  is_shutting_down_ = true;
-  CHECK(identity_manager_);
-  identity_manager_->RemoveObserver(this);
-  identity_manager_ = nullptr;
-  CHECK(tracking_protection_settings_);
-  tracking_protection_settings_->RemoveObserver(this);
-  tracking_protection_settings_ = nullptr;
-  pref_service_ = nullptr;
-  profile_ = nullptr;
-  ip_protection_token_fetcher_.reset();
-  // If we are shutting down, we can't process messages anymore because we
-  // rely on having `identity_manager_` to get the OAuth token. Thus, just
-  // reset the receiver set.
-  receivers_.Clear();
-}
-
-void IpProtectionCoreHost::AddNetworkService(
-    mojo::PendingReceiver<ip_protection::mojom::CoreHost> pending_receiver,
-    mojo::PendingRemote<ip_protection::mojom::CoreControl> pending_remote) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  if (is_shutting_down_) {
-    return;
-  }
-  receiver_id_for_testing_ = receivers_.Add(this, std::move(pending_receiver));
-  remote_id_for_testing_ = remotes_.Add(std::move(pending_remote));
-
-  // We only expect two concurrent receivers, one corresponding to the main
-  // profile network context and one for an associated incognito mode profile
-  // (if an incognito window is open). However, if the network service crashes
-  // and is restarted, there might be lingering receivers that are bound until
-  // they are eventually cleaned up.
-}
-
-void IpProtectionCoreHost::AccountStatusChanged(bool account_available) {
-  if (ip_protection_proxy_config_fetcher_) {
-    ip_protection_proxy_config_fetcher_->AccountStatusChanged(
-        account_available);
-  }
-
-  if (ip_protection_token_fetcher_) {
-    ip_protection_token_fetcher_->AccountStatusChanged(account_available);
-  }
-
-  // Tell the `IpProtectionConfigCache()` in the Network Service so that it
-  // will begin making token requests.
-  if (account_available) {
-    InvalidateNetworkContextTryAgainAfterTime();
-  }
-}
-
-void IpProtectionCoreHost::OnPrimaryAccountChanged(
-    const signin::PrimaryAccountChangeEvent& event) {
-  auto signin_event_type = event.GetEventTypeFor(signin::ConsentLevel::kSignin);
-  VLOG(2) << "IPATP::OnPrimaryAccountChanged kSignin event type: "
-          << static_cast<int>(signin_event_type);
-  switch (signin_event_type) {
-    case signin::PrimaryAccountChangeEvent::Type::kSet: {
-      // Account information is now available, so resume making requests for
-      // the OAuth token.
-      AccountStatusChanged(true);
-      break;
-    }
-    case signin::PrimaryAccountChangeEvent::Type::kCleared:
-      // No need to tell the Network Service - it will find out the next time
-      // it calls `TryGetAuthTokens()`.
-      AccountStatusChanged(false);
-      break;
-    case signin::PrimaryAccountChangeEvent::Type::kNone:
-      break;
-  }
-}
-
-void IpProtectionCoreHost::OnErrorStateOfRefreshTokenUpdatedForAccount(
-    const CoreAccountInfo& account_info,
-    const GoogleServiceAuthError& error,
-    signin_metrics::SourceForRefreshTokenOperation token_operation_source) {
-  VLOG(2) << "IPATP::OnErrorStateOfRefreshTokenUpdatedForAccount: "
-          << error.ToString();
-  // Workspace user accounts can have account credential expirations that
-  // cause persistent OAuth token errors until the user logs in to Chrome
-  // again. To handle this, watch for these error events and treat them the
-  // same way we do login/logout events.
-  if (error.state() == GoogleServiceAuthError::State::NONE) {
-    AccountStatusChanged(true);
-    return;
-  }
-  if (error.IsPersistentError()) {
-    AccountStatusChanged(false);
-    return;
-  }
-}
-
-bool IpProtectionCoreHost::CanRequestOAuthToken() {
-  if (is_shutting_down_) {
-    return false;
-  }
-
-  return identity_manager_->HasPrimaryAccount(signin::ConsentLevel::kSignin);
-}
-
-// static
-bool IpProtectionCoreHost::CanIpProtectionBeEnabled() {
-  const base::CommandLine& command_line =
-      *base::CommandLine::ForCurrentProcess();
-  return base::FeatureList::IsEnabled(
-             net::features::kEnableIpProtectionProxy) &&
-         !command_line.HasSwitch(switches::kDisableIpProtectionProxy) &&
-         !command_line.HasSwitch(switches::kDisableHttp2);
-}
-
-bool IpProtectionCoreHost::ShouldDisableIpProtectionForEnterpriseForTesting() {
-  return ShouldDisableIpProtectionForEnterprise();
-}
-
-bool IpProtectionCoreHost::ShouldDisableIpProtectionForEnterprise() {
-  return tracking_protection_settings_->IsIpProtectionDisabledForEnterprise();
-}
-
-bool IpProtectionCoreHost::IsIpProtectionEnabled() {
-  if (is_shutting_down_) {
-    return false;
-  }
-
-  if (ShouldDisableIpProtectionForEnterprise()) {
-    return false;
-  }
-
-  // TODO(crbug.com/41494110): We should ultimately use
-  // `tracking_protection_settings_->IsIpProtectionEnabled()` but we can't yet
-  // because it would prevent us from being able to do experiments via Finch
-  // without showing the user setting.
-  if (!base::FeatureList::IsEnabled(privacy_sandbox::kIpProtectionUx)) {
-    // If the preference isn't visible to users then IP Protection is enabled
-    // via other means like via Finch experiment.
-    return true;
-  }
-  return tracking_protection_settings_->IsIpProtectionEnabled();
-}
-
-bool IpProtectionCoreHost::IsProxyConfigFetchEnabled() {
-  return IsIpProtectionEnabled();
-}
-
-bool IpProtectionCoreHost::IsTokenFetchEnabled() {
-  return IsIpProtectionEnabled();
-}
-
-void IpProtectionCoreHost::OnIpProtectionEnabledChanged() {
-  if (is_shutting_down_) {
-    return;
-  }
-
-  if (IsIpProtectionEnabled()) {
-    AccountStatusChanged(true);
-  }
-
-  for (auto& ipp_control : remotes_) {
-    ipp_control->SetIpProtectionEnabled(IsIpProtectionEnabled());
-  }
-}
diff --git a/chrome/browser/ip_protection/ip_protection_core_host.h b/chrome/browser/ip_protection/ip_protection_core_host.h
deleted file mode 100644
index 2efd061..0000000
--- a/chrome/browser/ip_protection/ip_protection_core_host.h
+++ /dev/null
@@ -1,253 +0,0 @@
-// Copyright 2024 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_IP_PROTECTION_IP_PROTECTION_CORE_HOST_H_
-#define CHROME_BROWSER_IP_PROTECTION_IP_PROTECTION_CORE_HOST_H_
-
-#include <memory>
-#include <optional>
-#include <string>
-
-#include "base/containers/flat_map.h"
-#include "base/memory/raw_ptr.h"
-#include "base/memory/scoped_refptr.h"
-#include "base/threading/sequence_bound.h"
-#include "base/time/time.h"
-#include "chrome/browser/ip_protection/ip_protection_core_host_factory.h"
-#include "components/ip_protection/common/ip_protection_data_types.h"
-#include "components/ip_protection/common/ip_protection_proxy_config_direct_fetcher.h"
-#include "components/ip_protection/common/ip_protection_telemetry.h"
-#include "components/ip_protection/common/ip_protection_token_direct_fetcher.h"
-#include "components/ip_protection/mojom/core.mojom.h"
-#include "components/privacy_sandbox/tracking_protection_settings.h"
-#include "components/privacy_sandbox/tracking_protection_settings_observer.h"
-#include "components/signin/public/identity_manager/access_token_info.h"
-#include "components/signin/public/identity_manager/account_info.h"
-#include "components/signin/public/identity_manager/primary_account_access_token_fetcher.h"
-#include "google_apis/gaia/google_service_auth_error.h"
-#include "mojo/public/cpp/bindings/pending_receiver.h"
-#include "mojo/public/cpp/bindings/pending_remote.h"
-#include "mojo/public/cpp/bindings/receiver_set.h"
-#include "mojo/public/cpp/bindings/remote_set.h"
-#include "services/network/public/cpp/resource_request.h"
-#include "services/network/public/cpp/shared_url_loader_factory.h"
-#include "third_party/abseil-cpp/absl/status/status.h"
-
-class Profile;
-
-namespace quiche {
-class BlindSignAuthInterface;
-enum class ProxyLayer;
-}  // namespace quiche
-
-// Fetches IP protection tokens on demand for the network service.
-//
-// This class handles both requesting OAuth2 tokens for the signed-in user, and
-// fetching blind-signed auth tokens for that user. It may only be used on the
-// UI thread.
-class IpProtectionCoreHost
-    : public KeyedService,
-      public ip_protection::mojom::CoreHost,
-      public signin::IdentityManager::Observer,
-      public privacy_sandbox::TrackingProtectionSettingsObserver,
-      public ip_protection::IpProtectionProxyConfigDirectFetcher::Delegate,
-      public ip_protection::IpProtectionTokenDirectFetcher::Delegate {
- public:
-  // A map from proxy layer to a list of tokens.
-  using IpProtectionTokenCache =
-      base::flat_map<ip_protection::ProxyLayer,
-                     std::vector<ip_protection::BlindSignedAuthToken>>;
-
-  IpProtectionCoreHost(
-      signin::IdentityManager* identity_manager,
-      privacy_sandbox::TrackingProtectionSettings* tracking_protection_settings,
-      PrefService* pref_service,
-      Profile* profile);
-
-  ~IpProtectionCoreHost() override;
-
-  // CoreHost implementation:
-
-  // Get a batch of blind-signed auth tokens. It is forbidden for two calls to
-  // this method for the same proxy layer to be outstanding at the same time.
-  void TryGetAuthTokens(uint32_t batch_size,
-                        ip_protection::ProxyLayer proxy_layer,
-                        TryGetAuthTokensCallback callback) override;
-  void GetProxyConfig(GetProxyConfigCallback callback) override;
-  // Receives recycled tokens from the network service and caches them.
-  void RecycleTokens(
-      ip_protection::ProxyLayer proxy_layer,
-      std::vector<ip_protection::BlindSignedAuthToken> tokens) override;
-
-  // Called by `ProfileNetworkContextService` to get the tokens recycled from a
-  // previous Incognito session. This method moves the tokens out of the cache,
-  // clearing it.
-  IpProtectionTokenCache TakeRecycledTokens();
-
-  static bool CanIpProtectionBeEnabled();
-  bool IsIpProtectionEnabled();
-  bool CanRequestOAuthToken();
-
-  // IpProtectionTokenDirectFetcher::Delegate implementation.
-  bool IsTokenFetchEnabled() override;
-  void RequestOAuthToken(
-      ip_protection::IpProtectionTokenDirectFetcher::Delegate::
-          RequestOAuthTokenCallback callback) override;
-
-  // IpProtectionProxyConfigDirectFetcher::Delegate implementation.
-  bool IsProxyConfigFetchEnabled() override;
-  void AuthenticateRequest(std::unique_ptr<network::ResourceRequest>,
-                           ip_protection::IpProtectionProxyConfigDirectFetcher::
-                               Delegate::AuthenticateRequestCallback) override;
-  // Add bidirectional pipes to a new network service.
-  void AddNetworkService(
-      mojo::PendingReceiver<ip_protection::mojom::CoreHost> pending_receiver,
-      mojo::PendingRemote<ip_protection::mojom::CoreControl> pending_remote);
-
-  // KeyedService:
-  void Shutdown() override;
-
-  mojo::ReceiverSet<ip_protection::mojom::CoreHost>& receivers_for_testing() {
-    return receivers_;
-  }
-  mojo::ReceiverId receiver_id_for_testing() {
-    return receiver_id_for_testing_;
-  }
-  ip_protection::mojom::CoreControl* last_remote_for_testing() {
-    return remotes_.Get(remote_id_for_testing_);
-  }
-
-  // Like `SetUp()`, but providing values for each of the member variables. Note
-  // `bsa` is moved onto a separate sequence when initializing
-  // `ip_protection_token_fetcher_`.
-  void SetUpForTesting(
-      scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
-      std::unique_ptr<quiche::BlindSignAuthInterface> bsa);
-
-  // Returns whether IP Protection should be disabled for managed users and/or
-  // devices, for testing.
-  bool ShouldDisableIpProtectionForEnterpriseForTesting();
-
- private:
-  friend class IpProtectionCoreHostTest;
-  FRIEND_TEST_ALL_PREFIXES(IpProtectionCoreHostIdentityBrowserTest,
-                           BackoffTimeResetAfterProfileAvailabilityChange);
-  FRIEND_TEST_ALL_PREFIXES(IpProtectionCoreHostUserSettingBrowserTest,
-                           OnIpProtectionEnabledChanged);
-
-  // Creating a generic callback in order for `RequestOAuthToken()` to work for
-  // `TryGetAuthTokens()` and `GetProxyConfig()`.
-  using RequestOAuthTokenInternalCallback =
-      base::OnceCallback<void(GoogleServiceAuthError error,
-                              signin::AccessTokenInfo access_token_info)>;
-
-  // Set up `ip_protection_proxy_config_fetcher_`,
-  // `ip_protection_token_fetcher_` if not already initialized. This
-  // accomplishes lazy loading of these components to break dependency loops in
-  // browser startup.
-  void SetUp();
-
-  // `FetchBlindSignedToken()` uses the
-  // `ip_protection_token_fetcher_` to make an async call on the
-  // bound sequence into the `quiche::BlindSignAuth` library to request a
-  // blind-signed auth token for use at the IP Protection proxies.
-  void FetchBlindSignedToken(
-      std::optional<signin::AccessTokenInfo> access_token_info,
-      uint32_t batch_size,
-      quiche::ProxyLayer quiche_proxy_layer,
-      TryGetAuthTokensCallback callback);
-
-  void OnFetchBlindSignedTokenCompleted(
-      base::TimeTicks bsa_get_tokens_start_time,
-      TryGetAuthTokensCallback callback,
-      absl::StatusOr<std::vector<quiche::BlindSignToken>> tokens);
-
-  // Calls the IdentityManager asynchronously to request the OAuth token for the
-  // logged in user. This method must only be called when
-  // `CanRequestOAuthToken()` returns true.
-  void RequestOAuthTokenInternal(RequestOAuthTokenInternalCallback callback);
-
-  // The status of the account has changed, either becoming available or
-  // becoming unavailable. This is a signal to reset various timeouts (if
-  // available) or extend them (if not).
-  void AccountStatusChanged(bool account_available);
-
-  // Returns whether IP Protection should be disabled for managed users and/or
-  // devices.
-  bool ShouldDisableIpProtectionForEnterprise();
-
-  // Instruct the `IpProtectionConfigCache()`(s) in the Network Service to
-  // ignore any previously sent `try_again_after` times.
-  void InvalidateNetworkContextTryAgainAfterTime();
-
-  // IdentityManager::Observer:
-  void OnPrimaryAccountChanged(
-      const signin::PrimaryAccountChangeEvent& event) override;
-  void OnErrorStateOfRefreshTokenUpdatedForAccount(
-      const CoreAccountInfo& account_info,
-      const GoogleServiceAuthError& error,
-      signin_metrics::SourceForRefreshTokenOperation token_operation_source)
-      override;
-
-  // TrackingProtectionSettingsObserver:
-  void OnIpProtectionEnabledChanged() override;
-
-  // The object used to get an OAuth token. `identity_manager_` will be set to
-  // nullptr after `Shutdown()` is called, but will otherwise be non-null.
-  raw_ptr<signin::IdentityManager> identity_manager_;
-  // Used to retrieve whether the user has enabled IP protection via settings.
-  // `tracking_protection_settings_` will be set to nullptr after `Shutdown()`
-  // is called, but will otherwise be non-null.
-  raw_ptr<privacy_sandbox::TrackingProtectionSettings>
-      tracking_protection_settings_;
-  // Used to request the state of the IP Protection user setting. Will be set to
-  // nullptr after `Shutdown()` is called.
-  raw_ptr<PrefService> pref_service_;
-  // The `Profile` object associated with this `IpProtectionCoreHost()`. Will be
-  // reset to nullptr after `Shutdown()` is called.
-  // NOTE: If this is used in any `GetForProfile()` call, ensure that there is a
-  // corresponding dependency (if needed) registered in the factory class.
-  raw_ptr<Profile> profile_;
-
-  std::unique_ptr<ip_protection::IpProtectionProxyConfigDirectFetcher>
-      ip_protection_proxy_config_fetcher_;
-  std::unique_ptr<ip_protection::IpProtectionTokenDirectFetcher>
-      ip_protection_token_fetcher_;
-
-  // Whether `Shutdown()` has been called.
-  bool is_shutting_down_ = false;
-
-  // The `mojo::Receiver` objects allowing the network service to call methods
-  // on `this`.
-  //
-  // At any given time there should only be two receivers, one for the main
-  // profile and another one if an associated incognito window is opened.
-  // If one of the corresponding Network Contexts restarts, the
-  // corresponding receiver will automatically be removed and a new one
-  // bound as part of the Network Context initialization flow.
-  mojo::ReceiverSet<ip_protection::mojom::CoreHost> receivers_;
-
-  // Similar to `receivers_`, but containing remotes for all existing
-  // IpProtectionProxyDelegates.
-  mojo::RemoteSet<ip_protection::mojom::CoreControl> remotes_;
-
-  // The `mojo::ReceiverId` of the most recently added `mojo::Receiver`, for
-  // testing.
-  mojo::ReceiverId receiver_id_for_testing_;
-
-  // The `mojo::RemoteSetElementId` of the most recently added `mojo::Remote`,
-  // for testing.
-  mojo::RemoteSetElementId remote_id_for_testing_;
-
-  // True if this class is being tested.
-  bool for_testing_ = false;
-
-  // Unspent tokens from a previous session, for pre-warming the token cache.
-  IpProtectionTokenCache recycled_tokens_;
-
-  // This must be the last member in this class.
-  base::WeakPtrFactory<IpProtectionCoreHost> weak_ptr_factory_{this};
-};
-
-#endif  // CHROME_BROWSER_IP_PROTECTION_IP_PROTECTION_CORE_HOST_H_
diff --git a/chrome/browser/ip_protection/ip_protection_core_host_browsertest.cc b/chrome/browser/ip_protection/ip_protection_core_host_browsertest.cc
deleted file mode 100644
index 9805a49..0000000
--- a/chrome/browser/ip_protection/ip_protection_core_host_browsertest.cc
+++ /dev/null
@@ -1,1090 +0,0 @@
-// Copyright 2024 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/ip_protection/ip_protection_core_host.h"
-
-#include <string>
-#include <vector>
-
-#include "base/strings/to_string.h"
-#include "base/test/bind.h"
-#include "base/test/scoped_feature_list.h"
-#include "base/test/test_future.h"
-#include "base/time/time.h"
-#include "base/values.h"
-#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
-#include "chrome/browser/enterprise/browser_management/management_service_factory.h"
-#include "chrome/browser/ip_protection/ip_protection_core_host_factory.h"
-#include "chrome/browser/net/profile_network_context_service.h"
-#include "chrome/browser/policy/policy_test_utils.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/profiles/profile_test_util.h"
-#include "chrome/browser/signin/identity_manager_factory.h"
-#include "chrome/browser/signin/identity_test_environment_profile_adaptor.h"
-#include "chrome/common/buildflags.h"
-#include "chrome/test/base/chrome_test_utils.h"
-#include "chrome/test/base/platform_browser_test.h"
-#include "components/ip_protection/common/ip_protection_data_types.h"
-#include "components/ip_protection/mojom/core.mojom-test-utils.h"
-#include "components/ip_protection/mojom/core.mojom.h"
-#include "components/ip_protection/mojom/core_test.mojom.h"
-#include "components/ip_protection/mojom/data_types.mojom-test-utils.h"
-#include "components/ip_protection/mojom/data_types.mojom.h"
-#include "components/policy/core/common/management/scoped_management_service_override_for_testing.h"
-#include "components/policy/core/common/policy_map.h"
-#include "components/policy/core/common/policy_types.h"
-#include "components/policy/policy_constants.h"
-#include "components/privacy_sandbox/privacy_sandbox_features.h"
-#include "components/signin/public/base/consent_level.h"
-#include "components/signin/public/identity_manager/identity_test_utils.h"
-#include "components/variations/service/variations_service.h"
-#include "content/public/browser/network_service_util.h"
-#include "content/public/browser/storage_partition.h"
-#include "content/public/test/browser_test.h"
-#include "mojo/public/cpp/bindings/receiver_set.h"
-#include "mojo/public/cpp/bindings/remote.h"
-#include "net/base/features.h"
-#include "services/network/public/cpp/features.h"
-#include "services/network/public/mojom/network_context.mojom-test-utils.h"
-#include "services/network/public/mojom/network_context.mojom.h"
-
-#if BUILDFLAG(IS_CHROMEOS)
-#include "components/signin/public/identity_manager/primary_account_change_event.h"
-#endif
-
-#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) || \
-    BUILDFLAG(IS_CHROMEOS)
-#include "chrome/common/chrome_features.h"
-#endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) ||
-        // BUILDFLAG(IS_CHROMEOS)
-
-using ::ip_protection::BlindSignedAuthToken;
-using ::ip_protection::GeoHint;
-
-namespace {
-class ScopedIpProtectionFeatureList {
- public:
-  explicit ScopedIpProtectionFeatureList(bool incognito_mode,
-                                         bool enterprise_killswitch_enabled) {
-    std::vector<base::test::FeatureRefAndParams> features_and_params;
-    features_and_params.push_back(
-        {net::features::kEnableIpProtectionProxy,
-         {{net::features::kIpPrivacyOnlyInIncognito.name,
-           base::ToString(incognito_mode)},
-          {net::features::kIpPrivacyDisableForEnterpriseByDefault.name,
-           base::ToString(enterprise_killswitch_enabled)}}});
-    features_and_params.push_back({network::features::kMaskedDomainList, {}});
-    feature_list_.InitWithFeaturesAndParameters(features_and_params, {});
-  }
-
- private:
-  base::test::ScopedFeatureList feature_list_;
-};
-
-// Helper class to intercept `IpProtectionCoreHost::TryGetAuthTokens()`
-// requests and return a fake token value and a fake expiration value for
-// testing.
-class IpProtectionCoreHostInterceptor
-    : public ip_protection::mojom::CoreHostInterceptorForTesting {
- public:
-  IpProtectionCoreHostInterceptor(IpProtectionCoreHost* getter,
-                                  std::string token,
-                                  base::Time expiration,
-                                  GeoHint geo_hint,
-                                  bool should_intercept = true)
-      : getter_(getter),
-        receiver_id_(getter_->receiver_id_for_testing()),
-        token_(std::move(token)),
-        expiration_(expiration),
-        geo_hint_(geo_hint),
-        should_intercept_(should_intercept) {
-    auto* old_impl =
-        getter_->receivers_for_testing().SwapImplForTesting(receiver_id_, this);
-    // We should only ever be replacing `getter` as the impl.
-    CHECK_EQ(getter, old_impl);
-  }
-
-  ~IpProtectionCoreHostInterceptor() override {
-    std::ignore = getter_->receivers_for_testing().SwapImplForTesting(
-        receiver_id_, getter_);
-  }
-
-  ip_protection::mojom::CoreHost* GetForwardingInterface() override {
-    return getter_;
-  }
-
-  void TryGetAuthTokens(uint32_t batch_size,
-                        ip_protection::ProxyLayer proxy_layer,
-                        TryGetAuthTokensCallback callback) override {
-    if (should_intercept_) {
-      std::vector<BlindSignedAuthToken> tokens;
-      for (uint32_t i = 0; i < batch_size; i++) {
-        BlindSignedAuthToken token = {
-            .token = token_, .expiration = expiration_, .geo_hint = geo_hint_};
-        tokens.push_back(std::move(token));
-      }
-      std::move(callback).Run(std::move(tokens), base::Time());
-      return;
-    }
-    GetForwardingInterface()->TryGetAuthTokens(batch_size, proxy_layer,
-                                               std::move(callback));
-  }
-
-  std::string token() const { return token_; }
-
-  base::Time expiration() const { return expiration_; }
-
-  GeoHint geo_hint() const { return geo_hint_; }
-
-  void EnableInterception() { should_intercept_ = true; }
-  void DisableInterception() { should_intercept_ = false; }
-
- private:
-  raw_ptr<IpProtectionCoreHost> getter_;
-  // `getter_->receiver_id_for_testing()` will return the `mojo::ReceiverId` of
-  // the most recently added receiver, so save this value off to ensure that on
-  // destruction we restore the implementation for the correct receiver (for
-  // cases where we have multiple receivers and use more than one interceptor).
-  mojo::ReceiverId receiver_id_;
-  std::string token_;
-  base::Time expiration_;
-  GeoHint geo_hint_;
-  bool should_intercept_;
-};
-
-constexpr base::Time kDontRetry = base::Time::Max();
-}  // namespace
-
-class IpProtectionCoreHostBrowserTest : public PlatformBrowserTest {
- public:
-  explicit IpProtectionCoreHostBrowserTest(
-      bool incognito_mode = false,
-      bool enterprise_killswitch_enabled = false)
-      : scoped_ip_protection_feature_list_(incognito_mode,
-                                           enterprise_killswitch_enabled),
-        profile_selections_(
-            IpProtectionCoreHostFactory::GetInstance(),
-            IpProtectionCoreHostFactory::CreateProfileSelectionsForTesting()) {}
-
-  void TearDownOnMainThread() override {
-    PlatformBrowserTest::TearDownOnMainThread();
-
-    IpProtectionCoreHostFactory::GetForProfile(GetProfile())->Shutdown();
-  }
-
-  void CreateIncognitoNetworkContextAndInterceptors() {
-    IpProtectionCoreHost* provider =
-        IpProtectionCoreHostFactory::GetForProfile(GetProfile());
-    ASSERT_TRUE(provider);
-    ASSERT_EQ(provider->receivers_for_testing().size(), 1U);
-
-    std::string token = "best_token_ever";
-    base::Time expiration = base::Time::Now() + base::Seconds(12345);
-    GeoHint geo_hint = {
-        .country_code = "US", .iso_region = "US-AL", .city_name = "ALABASTER"};
-    main_profile_auth_token_getter_interceptor_ =
-        std::make_unique<IpProtectionCoreHostInterceptor>(
-            provider, token, expiration, geo_hint, /*should_intercept=*/false);
-
-    network::mojom::NetworkContext* main_profile_network_context =
-        GetProfile()->GetDefaultStoragePartition()->GetNetworkContext();
-    main_profile_ipp_control_ = provider->last_remote_for_testing();
-    main_profile_ipp_control_->BindTestInterfaceForTesting(
-        main_profile_ipp_control_test_.BindNewPipeAndPassReceiver());
-
-    incognito_profile_ =
-        GetProfile()->GetPrimaryOTRProfile(/*create_if_needed=*/true);
-    ASSERT_EQ(provider->receivers_for_testing().size(), 2U);
-    network::mojom::NetworkContext* incognito_profile_network_context =
-        incognito_profile_->GetDefaultStoragePartition()->GetNetworkContext();
-    incognito_profile_ipp_control_ = provider->last_remote_for_testing();
-    incognito_profile_ipp_control_->BindTestInterfaceForTesting(
-        incognito_profile_ipp_control_test_.BindNewPipeAndPassReceiver());
-    ASSERT_NE(main_profile_network_context, incognito_profile_network_context);
-    ASSERT_NE(main_profile_ipp_control_, incognito_profile_ipp_control_);
-
-    incognito_profile_auth_token_getter_interceptor_ =
-        std::make_unique<IpProtectionCoreHostInterceptor>(
-            provider, token, expiration, geo_hint, /*should_intercept=*/false);
-  }
-
-  void EnableInterception() {
-    main_profile_auth_token_getter_interceptor_->EnableInterception();
-    incognito_profile_auth_token_getter_interceptor_->EnableInterception();
-  }
-
-  void DestroyIncognitoNetworkContextAndInterceptors() {
-    ASSERT_TRUE(incognito_profile_)
-        << "CreateNetworkContextsAndInterceptors() must have been called first";
-
-    incognito_profile_auth_token_getter_interceptor_ = nullptr;
-    main_profile_auth_token_getter_interceptor_ = nullptr;
-
-    main_profile_ipp_control_ = nullptr;
-    main_profile_ipp_control_test_.reset();
-    incognito_profile_ipp_control_ = nullptr;
-    incognito_profile_ipp_control_test_.reset();
-
-    Profile* incognito_profile = incognito_profile_;
-    incognito_profile_ = nullptr;
-    GetProfile()->DestroyOffTheRecordProfile(incognito_profile);
-  }
-
-  Profile* GetProfile() { return chrome_test_utils::GetProfile(this); }
-
- protected:
-  raw_ptr<ip_protection::mojom::CoreControl> main_profile_ipp_control_ =
-      nullptr;
-  mojo::Remote<ip_protection::mojom::CoreControlTest>
-      main_profile_ipp_control_test_;
-  std::unique_ptr<IpProtectionCoreHostInterceptor>
-      main_profile_auth_token_getter_interceptor_;
-
-  raw_ptr<Profile> incognito_profile_ = nullptr;
-  raw_ptr<ip_protection::mojom::CoreControl> incognito_profile_ipp_control_ =
-      nullptr;
-  mojo::Remote<ip_protection::mojom::CoreControlTest>
-      incognito_profile_ipp_control_test_;
-  std::unique_ptr<IpProtectionCoreHostInterceptor>
-      incognito_profile_auth_token_getter_interceptor_;
-
- private:
-  // Note that the order of initialization is important here - we want to set
-  // the value of the features before anything else since it's used by the
-  // `IpProtectionCoreHostFactory` logic.
-  ScopedIpProtectionFeatureList scoped_ip_protection_feature_list_;
-  profiles::testing::ScopedProfileSelectionsForFactoryTesting
-      profile_selections_;
-};
-
-IN_PROC_BROWSER_TEST_F(IpProtectionCoreHostBrowserTest,
-                       NetworkServiceCanRequestTokens) {
-  IpProtectionCoreHost* getter =
-      IpProtectionCoreHostFactory::GetForProfile(GetProfile());
-  ASSERT_TRUE(getter);
-
-  std::string token = "best_token_ever";
-  base::Time expiration = base::Time::Now() + base::Seconds(12345);
-  GeoHint geo_hint = {
-      .country_code = "US", .iso_region = "US-AL", .city_name = "ALABASTER"};
-  ASSERT_EQ(getter->receivers_for_testing().size(), 1U);
-  auto auth_token_getter_interceptor_ =
-      std::make_unique<IpProtectionCoreHostInterceptor>(getter, token,
-                                                        expiration, geo_hint);
-
-  // To test that the Network Service can successfully request tokens, use the
-  // test method on NetworkContext that will have it request tokens and then
-  // send back the first token that it receives.
-  base::test::TestFuture<std::optional<BlindSignedAuthToken>,
-                         std::optional<base::Time>>
-      future;
-  auto* ipp_control = getter->last_remote_for_testing();
-  mojo::Remote<ip_protection::mojom::CoreControlTest> ipp_control_test;
-  ipp_control->BindTestInterfaceForTesting(
-      ipp_control_test.BindNewPipeAndPassReceiver());
-  ipp_control_test->VerifyIpProtectionCoreHostForTesting(future.GetCallback());
-  const std::optional<BlindSignedAuthToken>& result =
-      future.Get<std::optional<BlindSignedAuthToken>>();
-  ASSERT_TRUE(result);
-  EXPECT_EQ(result->token, token);
-  // Expiration is "fuzzed" backward in time, so expect less-than.
-  EXPECT_LT(result->expiration, expiration);
-
-  // Now create a new incognito mode profile (with a different associated
-  // network context) and see whether we can request tokens from that.
-  GetProfile()->GetPrimaryOTRProfile(/*create_if_needed=*/true);
-
-  // We need a new interceptor that will intercept messages corresponding to the
-  // incognito mode network context's `mojo::Receiver()`.
-  ASSERT_EQ(getter->receivers_for_testing().size(), 2U);
-  auto incognito_auth_token_getter_interceptor_ =
-      std::make_unique<IpProtectionCoreHostInterceptor>(getter, token,
-                                                        expiration, geo_hint);
-
-  // Verify that we can get tokens from the incognito mode profile.
-  future.Clear();
-  auto* incognito_ipp_control = getter->last_remote_for_testing();
-  mojo::Remote<ip_protection::mojom::CoreControlTest>
-      incognito_ipp_control_test;
-  incognito_ipp_control->BindTestInterfaceForTesting(
-      incognito_ipp_control_test.BindNewPipeAndPassReceiver());
-  ASSERT_NE(incognito_ipp_control, ipp_control);
-  incognito_ipp_control_test->VerifyIpProtectionCoreHostForTesting(
-      future.GetCallback());
-  const std::optional<BlindSignedAuthToken>& incognito_result =
-      future.Get<std::optional<BlindSignedAuthToken>>();
-  ASSERT_TRUE(incognito_result);
-  EXPECT_EQ(incognito_result->token, token);
-  EXPECT_LT(incognito_result->expiration, expiration);
-
-  // Ensure that we can still get tokens from the main profile.
-  future.Clear();
-  ipp_control_test->VerifyIpProtectionCoreHostForTesting(future.GetCallback());
-  const std::optional<BlindSignedAuthToken>& second_attempt_result =
-      future.Get<std::optional<BlindSignedAuthToken>>();
-  ASSERT_TRUE(second_attempt_result);
-  EXPECT_EQ(second_attempt_result->token, token);
-  EXPECT_LT(second_attempt_result->expiration, expiration);
-}
-
-IN_PROC_BROWSER_TEST_F(IpProtectionCoreHostBrowserTest,
-                       ReturnAndRecycleTokens) {
-  IpProtectionCoreHost* host =
-      IpProtectionCoreHostFactory::GetForProfile(GetProfile());
-  auto* prev_control = host->last_remote_for_testing();
-  ASSERT_TRUE(host);
-  const GeoHint geo_hint = {.country_code = "US",
-                            .iso_region = "US-CA",
-                            .city_name = "MOUNTAIN VIEW"};
-  const std::string token = "orphan-token";
-
-  // Start a first Incognito session, populate the token cache, and then
-  // destroy the session. The interceptor is scoped to this session and will be
-  // destroyed with it, ensuring that the second session uses the recycled
-  // token.
-  {
-    Profile* incognito_profile =
-        GetProfile()->GetPrimaryOTRProfile(/*create_if_needed=*/true);
-    auto* incognito_ipp_control = host->last_remote_for_testing();
-    mojo::Remote<ip_protection::mojom::CoreControlTest>
-        incognito_ipp_control_test;
-    incognito_ipp_control->BindTestInterfaceForTesting(
-        incognito_ipp_control_test.BindNewPipeAndPassReceiver());
-    EXPECT_NE(incognito_ipp_control, prev_control);
-
-    base::Time expiration = base::Time::Now() + base::Hours(1);
-    auto interceptor = std::make_unique<IpProtectionCoreHostInterceptor>(
-        host, token, expiration, geo_hint);
-
-    base::test::TestFuture<std::optional<BlindSignedAuthToken>,
-                           std::optional<base::Time>>
-        future;
-    incognito_ipp_control_test->VerifyIpProtectionCoreHostForTesting(
-        future.GetCallback());
-    ASSERT_TRUE(future.Get<0>().has_value());
-
-    // Finish the first Incognito session, and flush the pending RecycleTokens
-    // call from the network service.
-    GetProfile()->DestroyOffTheRecordProfile(incognito_profile);
-    host->receivers_for_testing().FlushForTesting();
-  }
-
-  // Start a second Incognito session.
-  Profile* incognito_profile =
-      GetProfile()->GetPrimaryOTRProfile(/*create_if_needed=*/true);
-  auto* incognito_ipp_control = host->last_remote_for_testing();
-  mojo::Remote<ip_protection::mojom::CoreControlTest>
-      incognito_ipp_control_test;
-  incognito_ipp_control->BindTestInterfaceForTesting(
-      incognito_ipp_control_test.BindNewPipeAndPassReceiver());
-
-  // Verify that the orphaned token from the first session is now in the second
-  // session's token cache.
-  base::test::TestFuture<std::optional<BlindSignedAuthToken>> get_token_future;
-  incognito_ipp_control_test->GetAuthTokenForTesting(
-      ip_protection::ProxyLayer::kProxyA,
-      ip_protection::GetGeoIdFromGeoHint(geo_hint),
-      get_token_future.GetCallback());
-  auto cached_token = get_token_future.Get();
-  ASSERT_TRUE(cached_token.has_value());
-  EXPECT_EQ(cached_token->token, token);
-
-  // Clean up the second incognito profile.
-  GetProfile()->DestroyOffTheRecordProfile(incognito_profile);
-}
-
-IN_PROC_BROWSER_TEST_F(IpProtectionCoreHostBrowserTest,
-                       ExpectedReceiverSetStateAfterNetworkServiceCrash) {
-  IpProtectionCoreHost* getter =
-      IpProtectionCoreHostFactory::GetForProfile(GetProfile());
-  ASSERT_TRUE(getter);
-  ASSERT_EQ(getter->receivers_for_testing().size(), 1U);
-
-  Profile* incognito_profile =
-      GetProfile()->GetPrimaryOTRProfile(/*create_if_needed=*/true);
-  ASSERT_EQ(getter->receivers_for_testing().size(), 2U);
-
-  // If the network service isn't out-of-process then we can't crash it.
-  if (!content::IsOutOfProcessNetworkService()) {
-    return;
-  }
-
-  // Crash the Network Service process and ensure that the error notifications
-  // get propagated. When the network contexts are recreated, they will both
-  // attempt to get tokens and we should ensure that nothing crashes as a
-  // result.
-  SimulateNetworkServiceCrash();
-  GetProfile()->GetDefaultStoragePartition()->FlushNetworkInterfaceForTesting();
-  incognito_profile->GetDefaultStoragePartition()
-      ->FlushNetworkInterfaceForTesting();
-  // Ensure that any lingering receivers have had time to be removed.
-  getter->receivers_for_testing().FlushForTesting();
-
-  ASSERT_EQ(getter->receivers_for_testing().size(), 2U);
-}
-
-IN_PROC_BROWSER_TEST_F(IpProtectionCoreHostBrowserTest,
-                       NotDisabledForManagedDevice) {
-  IpProtectionCoreHost* getter =
-      IpProtectionCoreHostFactory::GetForProfile(GetProfile());
-  ASSERT_TRUE(getter);
-  ASSERT_TRUE(getter->IsIpProtectionEnabled());
-
-  {
-    policy::ScopedManagementServiceOverrideForTesting browser_management{
-        policy::ManagementServiceFactory::GetForPlatform(),
-        policy::EnterpriseManagementAuthority::COMPUTER_LOCAL};
-
-    EXPECT_TRUE(getter->IsIpProtectionEnabled());
-  }
-}
-
-class IpProtectionBrowserTestEnterpriseKillSwitchEnabled
-    : public IpProtectionCoreHostBrowserTest {
- public:
-  IpProtectionBrowserTestEnterpriseKillSwitchEnabled()
-      : IpProtectionCoreHostBrowserTest(
-            /*incognito_mode=*/false,
-            /*enterprise_killswitch_enabled=*/true) {}
-};
-
-IN_PROC_BROWSER_TEST_F(IpProtectionBrowserTestEnterpriseKillSwitchEnabled,
-                       DisabledForManagedDevice) {
-  IpProtectionCoreHost* getter =
-      IpProtectionCoreHostFactory::GetForProfile(GetProfile());
-  ASSERT_TRUE(getter);
-
-  // If this test is running on a device that is already managed, we expect IP
-  // Protection to be disabled. Verify this and then skip the rest of the test.
-  if (getter->ShouldDisableIpProtectionForEnterpriseForTesting()) {
-    EXPECT_FALSE(getter->IsIpProtectionEnabled());
-    return;
-  }
-  ASSERT_TRUE(getter->IsIpProtectionEnabled());
-
-  {
-    policy::ScopedManagementServiceOverrideForTesting browser_management{
-        policy::ManagementServiceFactory::GetForPlatform(),
-        policy::EnterpriseManagementAuthority::COMPUTER_LOCAL};
-
-    EXPECT_FALSE(getter->IsIpProtectionEnabled());
-  }
-}
-
-class IpProtectionBrowserTestIncognitoOnlyModeDisabled
-    : public IpProtectionCoreHostBrowserTest {
- public:
-  IpProtectionBrowserTestIncognitoOnlyModeDisabled()
-      : IpProtectionCoreHostBrowserTest(/*incognito_mode=*/false) {}
-};
-
-IN_PROC_BROWSER_TEST_F(
-    IpProtectionBrowserTestIncognitoOnlyModeDisabled,
-    IpProtectionReceiversPresentForRegularAndIncognitoProfiles) {
-  IpProtectionCoreHost* getter =
-      IpProtectionCoreHostFactory::GetForProfile(GetProfile());
-  ASSERT_TRUE(getter);
-
-  // If IPP is active/allowed, the `IpProtectionCoreHost` will have receivers
-  // for a regular profile.
-  ASSERT_EQ(getter->receivers_for_testing().size(), 1U);
-
-  // Now create a new incognito mode profile (with a different associated
-  // network context).
-  GetProfile()->GetPrimaryOTRProfile(/*create_if_needed=*/true);
-
-  // A new network context for the incognito profile implies we should have
-  // additional receivers for the new network context.
-  ASSERT_EQ(getter->receivers_for_testing().size(), 2U);
-}
-
-class IpProtectionBrowserTestIncognitoOnlyModeEnabled
-    : public IpProtectionCoreHostBrowserTest {
- public:
-  IpProtectionBrowserTestIncognitoOnlyModeEnabled()
-      : IpProtectionCoreHostBrowserTest(/*incognito_mode=*/true) {}
-};
-
-IN_PROC_BROWSER_TEST_F(IpProtectionBrowserTestIncognitoOnlyModeEnabled,
-                       IpProtectionReceiversAbsentForRegularProfiles) {
-  IpProtectionCoreHost* getter =
-      IpProtectionCoreHostFactory::GetForProfile(GetProfile());
-  ASSERT_TRUE(getter);
-
-  // The `IpProtectionCoreHost` will not have receivers for a regular profile
-  // when the incognito mode feature param is enabled.
-  ASSERT_EQ(getter->receivers_for_testing().size(), 0U);
-}
-
-IN_PROC_BROWSER_TEST_F(IpProtectionBrowserTestIncognitoOnlyModeEnabled,
-                       IpProtectionReceiversPresentForIncognitoProfiles) {
-  IpProtectionCoreHost* getter = IpProtectionCoreHostFactory::GetForProfile(
-      GetProfile()->GetPrimaryOTRProfile(/*create_if_needed=*/true));
-  ASSERT_TRUE(getter);
-
-  // The `IpProtectionCoreHost` will have a receiver for an incognito profile
-  // when the incognito mode feature param is enabled.
-  ASSERT_EQ(getter->receivers_for_testing().size(), 1U);
-}
-
-#if !BUILDFLAG(IS_ANDROID)
-class IpProtectionCoreHostIdentityBrowserTest
-    : public IpProtectionCoreHostBrowserTest {
- public:
-  IpProtectionCoreHostIdentityBrowserTest() = default;
-
-  void SetUpBrowserContextKeyedServices(
-      content::BrowserContext* context) override {
-    IpProtectionCoreHostBrowserTest::SetUpBrowserContextKeyedServices(context);
-    IdentityTestEnvironmentProfileAdaptor::
-        SetIdentityTestEnvironmentFactoriesOnBrowserContext(context);
-  }
-
-  void SetUpOnMainThread() override {
-    identity_test_environment_adaptor_ =
-        std::make_unique<IdentityTestEnvironmentProfileAdaptor>(GetProfile());
-
-    MakePrimaryAccountAvailable();
-
-    identity_test_environment_adaptor_->identity_test_env()
-        ->SetAutomaticIssueOfAccessTokens(true);
-  }
-
-  void TearDownOnMainThread() override {
-    IpProtectionCoreHostBrowserTest::TearDownOnMainThread();
-
-    identity_test_environment_adaptor_.reset();
-  }
-
-  signin::IdentityManager* IdentityManager() {
-    return identity_test_environment_adaptor_->identity_test_env()
-        ->identity_manager();
-  }
-
-  void MakePrimaryAccountAvailable() {
-    identity_test_environment_adaptor_->identity_test_env()
-        ->MakePrimaryAccountAvailable("test@example.com",
-                                      signin::ConsentLevel::kSignin);
-  }
-
-  void ClearPrimaryAccount() {
-    identity_test_environment_adaptor_->identity_test_env()
-        ->ClearPrimaryAccount();
-  }
-
- private:
-  std::unique_ptr<IdentityTestEnvironmentProfileAdaptor>
-      identity_test_environment_adaptor_;
-};
-
-IN_PROC_BROWSER_TEST_F(IpProtectionCoreHostIdentityBrowserTest,
-                       BackoffTimeResetAfterProfileAvailabilityChange) {
-  CreateIncognitoNetworkContextAndInterceptors();
-  // Simulate logging the user out, which should make the provider indicate that
-  // `TryGetAuthTokens()` calls should not be retried on the next request.
-#if !BUILDFLAG(IS_CHROMEOS)
-  ClearPrimaryAccount();
-#else
-  IpProtectionCoreHost* provider =
-      IpProtectionCoreHostFactory::GetForProfile(GetProfile());
-  ASSERT_TRUE(provider);
-
-  // On ChromeOS, `ClearPrimaryAccount()` either isn't supported (Ash) or
-  // doesn't seem to work well (Lacros), but we can still test that our
-  // implementation works by mocking up the account status change event.
-  provider->OnPrimaryAccountChanged(signin::PrimaryAccountChangeEvent(
-      signin::PrimaryAccountChangeEvent::State(
-          IdentityManager()->GetPrimaryAccountInfo(
-              signin::ConsentLevel::kSignin),
-          signin::ConsentLevel::kSignin),
-      signin::PrimaryAccountChangeEvent::State(),
-      signin_metrics::ProfileSignout::kTest));
-#endif
-
-  // Request tokens from both contexts and ensure that the "don't retry"
-  // cooldown time is returned. The provider should do this itself, so the
-  // interceptors won't be used for this part.
-  base::test::TestFuture<std::optional<BlindSignedAuthToken>,
-                         std::optional<base::Time>>
-      future;
-  main_profile_ipp_control_test_->VerifyIpProtectionCoreHostForTesting(
-      future.GetCallback());
-  const std::optional<base::Time>& main_profile_first_attempt_result =
-      future.Get<std::optional<base::Time>>();
-  EXPECT_EQ(main_profile_first_attempt_result.value(), kDontRetry);
-
-  future.Clear();
-  incognito_profile_ipp_control_test_->VerifyIpProtectionCoreHostForTesting(
-      future.GetCallback());
-  const std::optional<base::Time>& incognito_profile_first_attempt_result =
-      future.Get<std::optional<base::Time>>();
-  EXPECT_EQ(incognito_profile_first_attempt_result.value(), kDontRetry);
-
-  // Make the interceptors return tokens now so that if the network service
-  // isn't respecting the cooldown, tokens will be returned and the test will
-  // fail below.
-  EnableInterception();
-
-  // Run the test again and check that the network service is still in a
-  // cooldown phase.
-  future.Clear();
-  main_profile_ipp_control_test_->VerifyIpProtectionCoreHostForTesting(
-      future.GetCallback());
-  const std::optional<base::Time>& main_profile_second_attempt_result =
-      future.Get<std::optional<base::Time>>();
-  EXPECT_EQ(main_profile_second_attempt_result.value(), kDontRetry);
-
-  future.Clear();
-  incognito_profile_ipp_control_test_->VerifyIpProtectionCoreHostForTesting(
-      future.GetCallback());
-  const std::optional<base::Time>& incognito_profile_second_attempt_result =
-      future.Get<std::optional<base::Time>>();
-  EXPECT_EQ(incognito_profile_second_attempt_result.value(), kDontRetry);
-
-  // Simulate the account becoming active again, which should cause `provider`
-  // to notify the network contexts. No need to flush the remotes after, since
-  // the messages generated will be in order with the
-  // `VerifyIpProtectionCoreHostForTesting()` messages used below.
-#if !BUILDFLAG(IS_CHROMEOS)
-  MakePrimaryAccountAvailable();
-#else
-  provider->OnPrimaryAccountChanged(signin::PrimaryAccountChangeEvent(
-      signin::PrimaryAccountChangeEvent::State(),
-      signin::PrimaryAccountChangeEvent::State(
-          IdentityManager()->GetPrimaryAccountInfo(
-              signin::ConsentLevel::kSignin),
-          signin::ConsentLevel::kSignin),
-      signin_metrics::AccessPoint::kUnknown));
-#endif
-
-  // Verify that cooldown timers in the network context have been reset and
-  // that we can now request tokens successfully.
-  future.Clear();
-  main_profile_ipp_control_test_->VerifyIpProtectionCoreHostForTesting(
-      future.GetCallback());
-  const std::optional<BlindSignedAuthToken>& main_profile_third_attempt_result =
-      future.Get<std::optional<BlindSignedAuthToken>>();
-  ASSERT_TRUE(main_profile_third_attempt_result);
-  EXPECT_EQ(main_profile_third_attempt_result->token,
-            main_profile_auth_token_getter_interceptor_->token());
-  EXPECT_LT(main_profile_third_attempt_result->expiration,
-            main_profile_auth_token_getter_interceptor_->expiration());
-
-  future.Clear();
-  incognito_profile_ipp_control_test_->VerifyIpProtectionCoreHostForTesting(
-      future.GetCallback());
-  const std::optional<BlindSignedAuthToken>&
-      incognito_profile_third_attempt_result =
-          future.Get<std::optional<BlindSignedAuthToken>>();
-  ASSERT_TRUE(incognito_profile_third_attempt_result);
-  EXPECT_EQ(incognito_profile_third_attempt_result->token,
-            incognito_profile_auth_token_getter_interceptor_->token());
-  EXPECT_LT(incognito_profile_third_attempt_result->expiration,
-            incognito_profile_auth_token_getter_interceptor_->expiration());
-
-  DestroyIncognitoNetworkContextAndInterceptors();
-}
-#endif
-
-class IpProtectionCoreHostUserSettingBrowserTest
-    : public IpProtectionCoreHostBrowserTest {
- public:
-  IpProtectionCoreHostUserSettingBrowserTest() {
-    scoped_feature_list_.InitAndEnableFeature(privacy_sandbox::kIpProtectionUx);
-  }
-
- protected:
-  const GURL kTestUrl = GURL("https://a.test");
-
- private:
-  base::test::ScopedFeatureList scoped_feature_list_;
-};
-
-IN_PROC_BROWSER_TEST_F(IpProtectionCoreHostUserSettingBrowserTest,
-                       OnIpProtectionEnabledChanged) {
-  IpProtectionCoreHost* provider =
-      IpProtectionCoreHostFactory::GetForProfile(GetProfile());
-  ASSERT_TRUE(provider);
-
-  CreateIncognitoNetworkContextAndInterceptors();
-
-  // Simulate the user disabling the IP Protection setting.
-  GetProfile()->GetPrefs()->SetBoolean(prefs::kIpProtectionEnabled, false);
-  provider->OnIpProtectionEnabledChanged();
-
-  provider->remotes_.FlushForTesting();
-
-  // Check that network contexts got notified that IP Protection should be
-  // disabled.
-  base::test::TestFuture<bool> main_profile_is_enabled_future;
-  base::test::TestFuture<bool> incognito_profile_is_enabled_future;
-
-  main_profile_ipp_control_test_->IsIpProtectionEnabledForTesting(
-      main_profile_is_enabled_future.GetCallback());
-  incognito_profile_ipp_control_test_->IsIpProtectionEnabledForTesting(
-      incognito_profile_is_enabled_future.GetCallback());
-
-  EXPECT_FALSE(main_profile_is_enabled_future.Get());
-  EXPECT_FALSE(incognito_profile_is_enabled_future.Get());
-
-  // Request tokens from both contexts and ensure that the "don't retry"
-  // cooldown time is returned.
-  base::test::TestFuture<std::optional<BlindSignedAuthToken>,
-                         std::optional<base::Time>>
-      main_profile_verification_future;
-  base::test::TestFuture<std::optional<BlindSignedAuthToken>,
-                         std::optional<base::Time>>
-      incognito_profile_verification_future;
-
-  main_profile_ipp_control_test_->VerifyIpProtectionCoreHostForTesting(
-      main_profile_verification_future.GetCallback());
-  incognito_profile_ipp_control_test_->VerifyIpProtectionCoreHostForTesting(
-      incognito_profile_verification_future.GetCallback());
-
-  const std::optional<base::Time>& main_profile_first_attempt_result =
-      main_profile_verification_future.Get<std::optional<base::Time>>();
-  const std::optional<base::Time>& incognito_profile_first_attempt_result =
-      incognito_profile_verification_future.Get<std::optional<base::Time>>();
-
-  EXPECT_EQ(main_profile_first_attempt_result.value(), kDontRetry);
-  EXPECT_EQ(incognito_profile_first_attempt_result.value(), kDontRetry);
-
-  // Re-enable the setting and ensure that the network contexts got notified
-  // accordingly.
-  GetProfile()->GetPrefs()->SetBoolean(prefs::kIpProtectionEnabled, true);
-  provider->OnIpProtectionEnabledChanged();
-
-  provider->remotes_.FlushForTesting();
-
-  main_profile_is_enabled_future.Clear();
-  incognito_profile_is_enabled_future.Clear();
-
-  main_profile_ipp_control_test_->IsIpProtectionEnabledForTesting(
-      main_profile_is_enabled_future.GetCallback());
-  incognito_profile_ipp_control_test_->IsIpProtectionEnabledForTesting(
-      incognito_profile_is_enabled_future.GetCallback());
-
-  EXPECT_TRUE(main_profile_is_enabled_future.Get());
-  EXPECT_TRUE(incognito_profile_is_enabled_future.Get());
-
-  // Return tokens for testing so that the calls below will complete
-  // successfully if the backoff time was successfully reset.
-  EnableInterception();
-
-  main_profile_verification_future.Clear();
-  incognito_profile_verification_future.Clear();
-
-  // Verify that cooldown timers in the network context have been reset and
-  // that we can now request tokens successfully.
-  main_profile_ipp_control_test_->VerifyIpProtectionCoreHostForTesting(
-      main_profile_verification_future.GetCallback());
-  incognito_profile_ipp_control_test_->VerifyIpProtectionCoreHostForTesting(
-      incognito_profile_verification_future.GetCallback());
-
-  const std::optional<BlindSignedAuthToken>&
-      main_profile_second_attempt_result =
-          main_profile_verification_future
-              .Get<std::optional<BlindSignedAuthToken>>();
-  const std::optional<BlindSignedAuthToken>&
-      incognito_profile_second_attempt_result =
-          incognito_profile_verification_future
-              .Get<std::optional<BlindSignedAuthToken>>();
-
-  ASSERT_TRUE(main_profile_second_attempt_result);
-  ASSERT_TRUE(incognito_profile_second_attempt_result);
-
-  EXPECT_EQ(main_profile_second_attempt_result->token,
-            main_profile_auth_token_getter_interceptor_->token());
-  EXPECT_LT(main_profile_second_attempt_result->expiration,
-            main_profile_auth_token_getter_interceptor_->expiration());
-
-  EXPECT_EQ(incognito_profile_second_attempt_result->token,
-            incognito_profile_auth_token_getter_interceptor_->token());
-  EXPECT_LT(incognito_profile_second_attempt_result->expiration,
-            incognito_profile_auth_token_getter_interceptor_->expiration());
-
-  DestroyIncognitoNetworkContextAndInterceptors();
-}
-
-// Verify that Tracking Protection exceptions persist across network service
-// restarts and crashes.
-IN_PROC_BROWSER_TEST_F(IpProtectionCoreHostUserSettingBrowserTest,
-                       PRE_TrackingProtectionExceptionRemainsAfterRestart) {
-  // Ensure that the tracking protection exception is not set yet.
-  base::test::TestFuture<bool> has_exception_future;
-
-  ip_protection::mojom::CoreControl* ipp_control =
-      IpProtectionCoreHostFactory::GetForProfile(GetProfile())
-          ->last_remote_for_testing();
-
-  mojo::Remote<ip_protection::mojom::CoreControlTest> ipp_control_test;
-  ipp_control->BindTestInterfaceForTesting(
-      ipp_control_test.BindNewPipeAndPassReceiver());
-
-  ipp_control_test->HasTrackingProtectionExceptionForTesting(
-      kTestUrl, has_exception_future.GetCallback());
-
-  ASSERT_FALSE(has_exception_future.Get());
-
-  // Simulate setting User Bypass settings.
-  auto* host_content_settings_map =
-      HostContentSettingsMapFactory::GetForProfile(GetProfile());
-
-  host_content_settings_map->SetContentSettingCustomScope(
-      ContentSettingsPattern::Wildcard(),
-      ContentSettingsPattern::FromURLToSchemefulSitePattern(kTestUrl),
-      ContentSettingsType::TRACKING_PROTECTION, CONTENT_SETTING_ALLOW);
-
-  GetProfile()->GetDefaultStoragePartition()->FlushNetworkInterfaceForTesting();
-
-  // Verify that the settings are propagated to IP Protection Core.
-  has_exception_future.Clear();
-
-  ipp_control_test->HasTrackingProtectionExceptionForTesting(
-      kTestUrl, has_exception_future.GetCallback());
-  // Ensure that the exception is present before the restart.
-  EXPECT_TRUE(has_exception_future.Get());
-}
-
-IN_PROC_BROWSER_TEST_F(IpProtectionCoreHostUserSettingBrowserTest,
-                       TrackingProtectionExceptionRemainsAfterRestart) {
-  // Check that the settings are still propagated to IP Protection Core after
-  // restart.
-  ip_protection::mojom::CoreControl* ipp_control =
-      IpProtectionCoreHostFactory::GetForProfile(GetProfile())
-          ->last_remote_for_testing();
-  mojo::Remote<ip_protection::mojom::CoreControlTest> ipp_control_test;
-  ipp_control->BindTestInterfaceForTesting(
-      ipp_control_test.BindNewPipeAndPassReceiver());
-
-  base::test::TestFuture<bool> has_exception_future;
-
-  ipp_control_test->HasTrackingProtectionExceptionForTesting(
-      kTestUrl, has_exception_future.GetCallback());
-
-  EXPECT_TRUE(has_exception_future.Get());
-}
-
-IN_PROC_BROWSER_TEST_F(
-    IpProtectionCoreHostUserSettingBrowserTest,
-    TrackingProtectionExceptionRemainsAfterNetworkServiceCrash) {
-  // If the network service isn't out-of-process then we can't crash it.
-  if (!content::IsOutOfProcessNetworkService()) {
-    GTEST_SKIP() << "Unable to crash: Network service is not out-of-process. "
-                    "Skipping test.";
-  }
-
-  // Ensure that the tracking protection exception is not set yet.
-  ip_protection::mojom::CoreControl* ipp_control_pre_crash =
-      IpProtectionCoreHostFactory::GetForProfile(GetProfile())
-          ->last_remote_for_testing();
-
-  mojo::Remote<ip_protection::mojom::CoreControlTest>
-      ipp_control_test_pre_crash;
-  ipp_control_pre_crash->BindTestInterfaceForTesting(
-      ipp_control_test_pre_crash.BindNewPipeAndPassReceiver());
-
-  base::test::TestFuture<bool> has_exception_future;
-
-  ipp_control_test_pre_crash->HasTrackingProtectionExceptionForTesting(
-      kTestUrl, has_exception_future.GetCallback());
-
-  ASSERT_FALSE(has_exception_future.Get());
-
-  // Simulate setting User Bypass settings.
-  auto* host_content_settings_map =
-      HostContentSettingsMapFactory::GetForProfile(GetProfile());
-
-  host_content_settings_map->SetContentSettingCustomScope(
-      ContentSettingsPattern::Wildcard(),
-      ContentSettingsPattern::FromURLToSchemefulSitePattern(kTestUrl),
-      ContentSettingsType::TRACKING_PROTECTION, CONTENT_SETTING_ALLOW);
-
-  GetProfile()->GetDefaultStoragePartition()->FlushNetworkInterfaceForTesting();
-
-  // Check that the settings are propagated to IP Protection Core before the
-  // crash.
-  has_exception_future.Clear();
-
-  ipp_control_test_pre_crash->HasTrackingProtectionExceptionForTesting(
-      kTestUrl, has_exception_future.GetCallback());
-
-  ASSERT_TRUE(has_exception_future.Get());
-
-  // Crash the Network Service, and then restart it to ensure that the settings
-  // are still propagated after the crash.
-  SimulateNetworkServiceCrash();
-
-  // Even though the network service has crashed and restarted by this point,
-  // not all of its related interfaces have been notified of the crash yet.
-  // Flush them so they get the error message and restart.
-  GetProfile()->GetDefaultStoragePartition()->FlushNetworkInterfaceForTesting();
-
-  // Check that the settings are still propagated to IP Protection Core after
-  // the crash.
-  ip_protection::mojom::CoreControl* ipp_control_post_crash =
-      IpProtectionCoreHostFactory::GetForProfile(GetProfile())
-          ->last_remote_for_testing();
-
-  ASSERT_NE(ipp_control_post_crash, ipp_control_pre_crash);
-
-  mojo::Remote<ip_protection::mojom::CoreControlTest>
-      ipp_control_test_post_crash;
-  ipp_control_post_crash->BindTestInterfaceForTesting(
-      ipp_control_test_post_crash.BindNewPipeAndPassReceiver());
-
-  has_exception_future.Clear();
-  ipp_control_test_post_crash->HasTrackingProtectionExceptionForTesting(
-      kTestUrl, has_exception_future.GetCallback());
-
-  EXPECT_TRUE(has_exception_future.Get());
-}
-
-class IpProtectionCoreHostPolicyBrowserTest : public policy::PolicyTest {
- public:
-  explicit IpProtectionCoreHostPolicyBrowserTest(
-      bool incognito_mode = false,
-      bool enterprise_killswitch_enabled = false)
-      : scoped_ip_protection_feature_list_(incognito_mode,
-                                           enterprise_killswitch_enabled),
-        profile_selections_(
-            IpProtectionCoreHostFactory::GetInstance(),
-            IpProtectionCoreHostFactory::CreateProfileSelectionsForTesting()) {}
-
-  void UpdateIpProtectionEnterpisePolicyValue(bool enabled) {
-    policy::PolicyMap policies;
-    policies.Set(policy::key::kPrivacySandboxIpProtectionEnabled,
-                 policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_USER,
-                 policy::POLICY_SOURCE_CLOUD, base::Value(enabled), nullptr);
-    provider_.UpdateChromePolicy(policies);
-  }
-
-  // Enable an unrelated policy (currently BuiltInDnsClientEnabled, chosen
-  // arbitrarily) that will cause the browser to be considered managed. Note
-  // that this method will unset any previously set IP Protection enterprise
-  // policy values when called.
-  void EnableUnrelatedPolicy() {
-    policy::PolicyMap policies;
-    policies.Set(policy::key::kBuiltInDnsClientEnabled,
-                 policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_USER,
-                 policy::POLICY_SOURCE_CLOUD, base::Value(true), nullptr);
-    provider_.UpdateChromePolicy(policies);
-  }
-
-  void UnsetPolicyValues() {
-    policy::PolicyMap policies;
-    provider_.UpdateChromePolicy(policies);
-  }
-
-  Profile* GetProfile() { return chrome_test_utils::GetProfile(this); }
-
-  // Note that the order of initialization is important here.
-  ScopedIpProtectionFeatureList scoped_ip_protection_feature_list_;
-  profiles::testing::ScopedProfileSelectionsForFactoryTesting
-      profile_selections_;
-};
-
-IN_PROC_BROWSER_TEST_F(IpProtectionCoreHostPolicyBrowserTest,
-                       IpProtectionEnterprisePolicyDisableAndEnable) {
-  IpProtectionCoreHost* provider =
-      IpProtectionCoreHostFactory::GetForProfile(GetProfile());
-  ASSERT_TRUE(provider);
-  ASSERT_TRUE(provider->IsIpProtectionEnabled());
-
-  // Setting the enterprise policy value to "Disabled" should change the default
-  // IP Protection feature state.
-  UpdateIpProtectionEnterpisePolicyValue(/*enabled=*/false);
-  EXPECT_FALSE(provider->IsIpProtectionEnabled());
-
-  // Setting the enterprise policy value to "Enabled" should re-enable IP
-  // Protection.
-  UpdateIpProtectionEnterpisePolicyValue(/*enabled=*/true);
-  EXPECT_TRUE(provider->IsIpProtectionEnabled());
-}
-
-// Test transitioning from the policy being set to the policy being unset - the
-// pref value should no longer be considered managed and should effectively be
-// reset to its initial state.
-IN_PROC_BROWSER_TEST_F(IpProtectionCoreHostPolicyBrowserTest,
-                       IpProtectionEnterprisePolicyUnsetAfterSet) {
-  IpProtectionCoreHost* provider =
-      IpProtectionCoreHostFactory::GetForProfile(GetProfile());
-  ASSERT_TRUE(provider);
-
-  // The initial state likely indicates that IP protection is enabled, unless
-  // the machine this test is running on is managed.
-  bool initial_state = provider->IsIpProtectionEnabled();
-
-  UpdateIpProtectionEnterpisePolicyValue(/*enabled=*/false);
-  EXPECT_FALSE(provider->IsIpProtectionEnabled());
-
-  UnsetPolicyValues();
-  EXPECT_EQ(provider->IsIpProtectionEnabled(), initial_state);
-}
-
-IN_PROC_BROWSER_TEST_F(IpProtectionCoreHostPolicyBrowserTest,
-                       NotDisabledForManagedBrowser) {
-#if BUILDFLAG(IS_CHROMEOS)
-  // On ChromeOS this is required for the unrelated policy enabled below to
-  // cause the profile `policy::ManagementService` to reflect that the profile
-  // is managed.
-  policy::ScopedManagementServiceOverrideForTesting browser_management{
-      policy::ManagementServiceFactory::GetForProfile(GetProfile()),
-      policy::EnterpriseManagementAuthority::COMPUTER_LOCAL};
-#endif
-
-  IpProtectionCoreHost* provider =
-      IpProtectionCoreHostFactory::GetForProfile(GetProfile());
-  ASSERT_TRUE(provider);
-  ASSERT_TRUE(provider->IsIpProtectionEnabled());
-
-  EnableUnrelatedPolicy();
-
-  EXPECT_TRUE(provider->IsIpProtectionEnabled());
-
-  UnsetPolicyValues();
-  EXPECT_TRUE(provider->IsIpProtectionEnabled());
-}
-
-class IpProtectionPolicyBrowserTestEnterpriseKillSwitchEnabled
-    : public IpProtectionCoreHostPolicyBrowserTest {
- public:
-  IpProtectionPolicyBrowserTestEnterpriseKillSwitchEnabled()
-      : IpProtectionCoreHostPolicyBrowserTest(
-            /*incognito_mode=*/false,
-            /*enterprise_killswitch_enabled=*/true) {}
-};
-
-// With the killswitch, if the browser is considered managed and IP Protection
-// isn't enabled via enterprise policy, IP Protection should be disabled.
-IN_PROC_BROWSER_TEST_F(IpProtectionPolicyBrowserTestEnterpriseKillSwitchEnabled,
-                       DisabledForManagedBrowser) {
-#if BUILDFLAG(IS_CHROMEOS)
-  // On ChromeOS this is required for the unrelated policy enabled below to
-  // cause the profile `policy::ManagementService` to reflect that the profile
-  // is managed.
-  policy::ScopedManagementServiceOverrideForTesting browser_management{
-      policy::ManagementServiceFactory::GetForProfile(GetProfile()),
-      policy::EnterpriseManagementAuthority::COMPUTER_LOCAL};
-#endif
-
-  IpProtectionCoreHost* provider =
-      IpProtectionCoreHostFactory::GetForProfile(GetProfile());
-  ASSERT_TRUE(provider);
-
-  // If this test is running on a device that is already managed, we expect IP
-  // Protection to be disabled. Verify this and then skip the rest of the test.
-  if (provider->ShouldDisableIpProtectionForEnterpriseForTesting()) {
-    EXPECT_FALSE(provider->IsIpProtectionEnabled());
-    return;
-  }
-  ASSERT_TRUE(provider->IsIpProtectionEnabled());
-
-  EnableUnrelatedPolicy();
-
-  EXPECT_FALSE(provider->IsIpProtectionEnabled());
-
-  UnsetPolicyValues();
-  EXPECT_TRUE(provider->IsIpProtectionEnabled());
-}
diff --git a/chrome/browser/ip_protection/ip_protection_core_host_factory.cc b/chrome/browser/ip_protection/ip_protection_core_host_factory.cc
deleted file mode 100644
index 5c8410fe..0000000
--- a/chrome/browser/ip_protection/ip_protection_core_host_factory.cc
+++ /dev/null
@@ -1,77 +0,0 @@
-// Copyright 2024 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/ip_protection/ip_protection_core_host_factory.h"
-
-#include "chrome/browser/ip_protection/ip_protection_core_host.h"
-#include "chrome/browser/privacy_sandbox/tracking_protection_settings_factory.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/profiles/profile_selections.h"
-#include "chrome/browser/signin/identity_manager_factory.h"
-#include "content/public/browser/browser_context.h"
-#include "content/public/browser/storage_partition.h"
-
-// static
-IpProtectionCoreHost* IpProtectionCoreHostFactory::GetForProfile(
-    Profile* profile) {
-  return static_cast<IpProtectionCoreHost*>(
-      GetInstance()->GetServiceForBrowserContext(profile, /*create=*/true));
-}
-
-// static
-IpProtectionCoreHostFactory* IpProtectionCoreHostFactory::GetInstance() {
-  static base::NoDestructor<IpProtectionCoreHostFactory> instance;
-  return instance.get();
-}
-
-// static
-ProfileSelections IpProtectionCoreHostFactory::CreateProfileSelections() {
-  if (!IpProtectionCoreHost::CanIpProtectionBeEnabled()) {
-    return ProfileSelections::BuildNoProfilesSelected();
-  }
-  // IP Protection usage requires that a Gaia account is available when
-  // authenticating to the proxy (to prevent it from being abused). For
-  // incognito mode where there's not an account available by default, use the
-  // profile associated with the logged in user if there is one. There's a small
-  // privacy trade-off with this, the downside being that the incognito mode
-  // profile will send an OAuth token associated with the user to the proxy
-  // token provider server periodically as new blinded proxy tokens are needed,
-  // and users might not expect this behavior. The privacy benefits of being
-  // able to use IP Protection in incognito mode should far outweigh this,
-  // though. Skip other profile types like Guest and System where no Gaia is
-  // available.
-  return ProfileSelections::Builder()
-      .WithRegular(ProfileSelection::kRedirectedToOriginal)
-      .WithGuest(ProfileSelection::kNone)
-      .WithSystem(ProfileSelection::kNone)
-      .WithAshInternals(ProfileSelection::kNone)
-      .Build();
-}
-
-IpProtectionCoreHostFactory::IpProtectionCoreHostFactory()
-    : ProfileKeyedServiceFactory("IpProtectionCoreHostFactory",
-                                 CreateProfileSelections()) {
-  DependsOn(IdentityManagerFactory::GetInstance());
-  DependsOn(TrackingProtectionSettingsFactory::GetInstance());
-}
-
-IpProtectionCoreHostFactory::~IpProtectionCoreHostFactory() = default;
-
-std::unique_ptr<KeyedService>
-IpProtectionCoreHostFactory::BuildServiceInstanceForBrowserContext(
-    content::BrowserContext* context) const {
-  Profile* profile = Profile::FromBrowserContext(context);
-  return std::make_unique<IpProtectionCoreHost>(
-      IdentityManagerFactory::GetForProfile(profile),
-      TrackingProtectionSettingsFactory::GetForProfile(profile),
-      profile->GetPrefs(), profile);
-}
-
-bool IpProtectionCoreHostFactory::ServiceIsCreatedWithBrowserContext() const {
-  // Auth tokens will be requested soon after `Profile()` creation (after the
-  // per-profile `NetworkContext()` gets created) so instantiate the
-  // `IpProtectionCoreHost()` so that it already exists by the time
-  // that request is made.
-  return true;
-}
diff --git a/chrome/browser/ip_protection/ip_protection_core_host_factory.h b/chrome/browser/ip_protection/ip_protection_core_host_factory.h
deleted file mode 100644
index 54cb6a5..0000000
--- a/chrome/browser/ip_protection/ip_protection_core_host_factory.h
+++ /dev/null
@@ -1,43 +0,0 @@
-// Copyright 2024 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_IP_PROTECTION_IP_PROTECTION_CORE_HOST_FACTORY_H_
-#define CHROME_BROWSER_IP_PROTECTION_IP_PROTECTION_CORE_HOST_FACTORY_H_
-
-#include "base/no_destructor.h"
-#include "chrome/browser/profiles/profile_keyed_service_factory.h"
-
-class Profile;
-class IpProtectionCoreHost;
-
-// Responsible for managing IP Protection auth token fetching.
-class IpProtectionCoreHostFactory : public ProfileKeyedServiceFactory {
- public:
-  static IpProtectionCoreHost* GetForProfile(Profile* profile);
-
-  static IpProtectionCoreHostFactory* GetInstance();
-
-  static ProfileSelections CreateProfileSelectionsForTesting() {
-    return CreateProfileSelections();
-  }
-
-  IpProtectionCoreHostFactory(const IpProtectionCoreHostFactory&) = delete;
-  IpProtectionCoreHostFactory& operator=(const IpProtectionCoreHostFactory&) =
-      delete;
-
- private:
-  friend base::NoDestructor<IpProtectionCoreHostFactory>;
-
-  static ProfileSelections CreateProfileSelections();
-
-  IpProtectionCoreHostFactory();
-  ~IpProtectionCoreHostFactory() override;
-
-  // BrowserContextKeyedServiceFactory:
-  std::unique_ptr<KeyedService> BuildServiceInstanceForBrowserContext(
-      content::BrowserContext* context) const override;
-  bool ServiceIsCreatedWithBrowserContext() const override;
-};
-
-#endif  // CHROME_BROWSER_IP_PROTECTION_IP_PROTECTION_CORE_HOST_FACTORY_H_
diff --git a/chrome/browser/ip_protection/ip_protection_core_host_factory_unittest.cc b/chrome/browser/ip_protection/ip_protection_core_host_factory_unittest.cc
deleted file mode 100644
index 5d59f16b4..0000000
--- a/chrome/browser/ip_protection/ip_protection_core_host_factory_unittest.cc
+++ /dev/null
@@ -1,147 +0,0 @@
-// Copyright 2024 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/ip_protection/ip_protection_core_host_factory.h"
-
-#include <string_view>
-
-#include "base/strings/string_util.h"
-#include "base/test/scoped_command_line.h"
-#include "base/test/scoped_feature_list.h"
-#include "chrome/browser/ip_protection/ip_protection_core_host.h"
-#include "chrome/browser/ip_protection/ip_protection_switches.h"
-#include "chrome/browser/profiles/profile_test_util.h"
-#include "chrome/browser/signin/identity_test_environment_profile_adaptor.h"
-#include "chrome/test/base/testing_profile.h"
-#include "content/public/test/browser_task_environment.h"
-#include "net/base/features.h"
-#include "components/network_session_configurator/common/network_switches.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace {
-class ScopedInitFeature {
- public:
-  ScopedInitFeature(const base::Feature& feature, bool enable) {
-    feature_list_.InitWithFeatureState(feature, enable);
-  }
-
- private:
-  base::test::ScopedFeatureList feature_list_;
-};
-
-class ScopedInitCommandLine {
- public:
-  explicit ScopedInitCommandLine(std::string_view command_line_switch) {
-    if (!command_line_switch.empty()) {
-      command_line_.GetProcessCommandLine()->AppendSwitch(command_line_switch);
-    }
-  }
-
- private:
-  base::test::ScopedCommandLine command_line_;
-};
-
-}  // namespace
-
-class IpProtectionCoreHostFactoryTest : public testing::Test {
- protected:
-  explicit IpProtectionCoreHostFactoryTest(bool feature_enabled = true,
-                                           const char* command_line_switch = "")
-      // Note that the order of initialization is important here - we want to
-      // set the value of the feature before anything else since it's used by
-      // the `IpProtectionCoreHostFactory` logic. Same for the command
-      // line switch, if specified.
-      : scoped_feature_(net::features::kEnableIpProtectionProxy,
-                        feature_enabled),
-        scoped_command_line_(command_line_switch),
-        profile_selections_(
-            IpProtectionCoreHostFactory::GetInstance(),
-            IpProtectionCoreHostFactory::CreateProfileSelectionsForTesting()) {}
-
-  void SetUp() override {
-    TestingProfile::Builder builder;
-    profile_ = builder.Build();
-  }
-
-  void TearDown() override { profile_.reset(); }
-
-  TestingProfile* profile() { return profile_.get(); }
-
-  ScopedInitFeature scoped_feature_;
-  ScopedInitCommandLine scoped_command_line_;
-  profiles::testing::ScopedProfileSelectionsForFactoryTesting
-      profile_selections_;
-  content::BrowserTaskEnvironment task_environment_;
-
- private:
-  std::unique_ptr<TestingProfile> profile_;
-};
-
-TEST_F(IpProtectionCoreHostFactoryTest,
-       ServiceCreationSucceedsWhenFlagEnabled) {
-  IpProtectionCoreHost* service =
-      IpProtectionCoreHostFactory::GetForProfile(profile());
-  ASSERT_TRUE(service);
-  service->Shutdown();
-}
-
-TEST_F(IpProtectionCoreHostFactoryTest, OtrProfileUsesPrimaryProfile) {
-  Profile* otr_profile =
-      profile()->GetPrimaryOTRProfile(/*create_if_needed=*/true);
-
-  // The regular profile and the off-the-record profile must be different.
-  ASSERT_NE(profile(), otr_profile);
-
-  // The same `IpProtectionCoreHost` should be used for both the main
-  // profile and the corresponding OTR profile.
-  EXPECT_EQ(IpProtectionCoreHostFactory::GetForProfile(profile()),
-            IpProtectionCoreHostFactory::GetForProfile(otr_profile));
-}
-
-class IpProtectionCoreHostFactoryFeatureDisabledTest
-    : public IpProtectionCoreHostFactoryTest {
- public:
-  IpProtectionCoreHostFactoryFeatureDisabledTest()
-      : IpProtectionCoreHostFactoryTest(/*feature_enabled=*/false) {}
-};
-
-TEST_F(IpProtectionCoreHostFactoryFeatureDisabledTest,
-       ServiceCreationFailsWhenFlagDisabled) {
-  IpProtectionCoreHost* service =
-      IpProtectionCoreHostFactory::GetForProfile(profile());
-  ASSERT_FALSE(service);
-}
-
-class IpProtectionCoreHostFactoryOptOutEnabled
-    : public IpProtectionCoreHostFactoryTest {
- public:
-  IpProtectionCoreHostFactoryOptOutEnabled()
-      : IpProtectionCoreHostFactoryTest(
-            /*feature_enabled=*/true,
-            /*command_line_switch=*/switches::kDisableIpProtectionProxy) {}
-};
-
-TEST_F(IpProtectionCoreHostFactoryOptOutEnabled,
-       ServiceCreationFailsWhenUserOptedOut) {
-  IpProtectionCoreHost* service =
-      IpProtectionCoreHostFactory::GetForProfile(profile());
-  ASSERT_FALSE(service);
-}
-
-class IpProtectionCoreHostFactoryHttp2DisabledTest
-    : public IpProtectionCoreHostFactoryTest {
- public:
-  IpProtectionCoreHostFactoryHttp2DisabledTest()
-      : IpProtectionCoreHostFactoryTest(
-            /*feature_enabled=*/true,
-            /*command_line_switch=*/switches::kDisableHttp2) {}
-};
-
-TEST_F(IpProtectionCoreHostFactoryHttp2DisabledTest,
-       ServiceCreationFailsWhenHttp2Disabled) {
-  // IP Protection requires HTTP/2.
-  IpProtectionCoreHost* service =
-      IpProtectionCoreHostFactory::GetForProfile(profile());
-  ASSERT_FALSE(service);
-}
diff --git a/chrome/browser/ip_protection/ip_protection_core_host_unittest.cc b/chrome/browser/ip_protection/ip_protection_core_host_unittest.cc
deleted file mode 100644
index f503a8bf..0000000
--- a/chrome/browser/ip_protection/ip_protection_core_host_unittest.cc
+++ /dev/null
@@ -1,1125 +0,0 @@
-// Copyright 2024 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/ip_protection/ip_protection_core_host.h"
-
-#include <memory>
-#include <optional>
-#include <vector>
-
-#include "base/base64.h"
-#include "base/command_line.h"
-#include "base/memory/scoped_refptr.h"
-#include "base/notreached.h"
-#include "base/strings/strcat.h"
-#include "base/task/single_thread_task_runner.h"
-#include "base/test/bind.h"
-#include "base/test/metrics/histogram_tester.h"
-#include "base/test/scoped_command_line.h"
-#include "base/test/scoped_feature_list.h"
-#include "base/test/test_future.h"
-#include "base/time/time.h"
-#include "base/types/expected.h"
-#include "chrome/browser/browser_process.h"
-#include "chrome/browser/ip_protection/ip_protection_switches.h"
-#include "chrome/test/base/testing_browser_process.h"
-#include "components/content_settings/core/browser/host_content_settings_map.h"
-#include "components/ip_protection/common/ip_protection_config_http.h"
-#include "components/ip_protection/common/ip_protection_data_types.h"
-#include "components/ip_protection/common/ip_protection_proxy_config_direct_fetcher.h"
-#include "components/ip_protection/common/mock_blind_sign_auth.h"
-#include "components/metrics/enabled_state_provider.h"
-#include "components/metrics/metrics_state_manager.h"
-#include "components/metrics/test/test_enabled_state_provider.h"
-#include "components/network_session_configurator/common/network_switches.h"
-#include "components/policy/core/common/management/management_service.h"
-#include "components/prefs/testing_pref_service.h"
-#include "components/privacy_sandbox/privacy_sandbox_features.h"
-#include "components/privacy_sandbox/privacy_sandbox_prefs.h"
-#include "components/privacy_sandbox/tracking_protection_settings.h"
-#include "components/signin/public/identity_manager/account_capabilities_test_mutator.h"
-#include "components/signin/public/identity_manager/identity_test_environment.h"
-#include "components/signin/public/identity_manager/primary_account_change_event.h"
-#include "components/sync_preferences/testing_pref_service_syncable.h"
-#include "components/variations/service/test_variations_service.h"
-#include "components/variations/service/variations_service.h"
-#include "content/public/browser/browser_thread.h"
-#include "content/public/test/browser_task_environment.h"
-#include "net/base/features.h"
-#include "net/third_party/quiche/src/quiche/blind_sign_auth/blind_sign_auth_interface.h"
-#include "net/third_party/quiche/src/quiche/blind_sign_auth/proto/spend_token_data.pb.h"
-#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
-#include "services/network/test/test_url_loader_factory.h"
-#include "testing/gmock/include/gmock/gmock.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "third_party/abseil-cpp/absl/status/status.h"
-
-#if BUILDFLAG(IS_CHROMEOS)
-#include "chromeos/ash/components/install_attributes/install_attributes.h"
-#include "chromeos/ash/components/install_attributes/stub_install_attributes.h"
-#endif
-
-namespace {
-
-using ::ip_protection::BlindSignedAuthToken;
-using ::ip_protection::GeoHint;
-using ::testing::Pair;
-using ::testing::UnorderedElementsAre;
-
-// A Gmock matcher for a `base::TimeDelta` within the jitter range defined by
-// `net::features::kIpPrivacyBackoffJitter`.
-MATCHER_P(IsNearWithJitter, expected, "") {
-  if (arg == base::TimeDelta::Max() && (expected) == base::TimeDelta::Max()) {
-    return true;
-  }
-
-  const auto jitter = net::features::kIpPrivacyBackoffJitter.Get();
-  const auto lower_bound = (expected) * (1.0 - jitter);
-  const auto upper_bound = (expected) * (1.0 + jitter);
-  if (arg >= lower_bound && arg <= upper_bound) {
-    return true;
-  }
-
-  *result_listener << "which is outside the expected range [" << lower_bound
-                   << ", " << upper_bound << "]";
-  return false;
-}
-
-constexpr char kTryGetAuthTokensResultHistogram[] =
-    "NetworkService.IpProtection.TryGetAuthTokensResult2";
-constexpr char kOAuthTokenFetchHistogram[] =
-    "NetworkService.IpProtection.OAuthTokenFetchTime";
-constexpr char kTryGetAuthTokensErrorHistogram[] =
-    "NetworkService.IpProtection.TryGetAuthTokensErrors";
-constexpr char kTokenBatchHistogram[] =
-    "NetworkService.IpProtection.TokenBatchRequestTime";
-
-constexpr char kTestEmail[] = "test@example.com";
-
-enum class PrimaryAccountBehavior {
-  // Primary account not set.
-  kNone,
-
-  // Primary account exists but returns a transient error fetching access token.
-  kTokenFetchTransientError,
-
-  // Primary account exists but returns a persistent error fetching access
-  // token.
-  kTokenFetchPersistentError,
-
-  // Primary account exists but is not eligible for IP protection.
-  kIneligible,
-
-  // Like kIneligible, but also a dogfooder.
-  kIneligibleDogfooder,
-
-  // Primary account exists but eligibility is kUnknown.
-  kUnknownEligibility,
-
-  // Primary account exists, is eligible, and returns OAuth token
-  // "access_token".
-  kReturnsToken,
-};
-
-}  // namespace
-
-class IpProtectionCoreHostTest : public testing::Test {
- protected:
-  IpProtectionCoreHostTest()
-      : expiration_time_(base::Time::Now() + base::Hours(1)),
-        geo_hint_({.country_code = "US",
-                   .iso_region = "US-AL",
-                   .city_name = "ALABASTER"}),
-        enabled_state_provider_(/*consent=*/false, /*enabled=*/false) {
-    privacy_sandbox::RegisterProfilePrefs(prefs()->registry());
-    HostContentSettingsMap::RegisterProfilePrefs(prefs()->registry());
-    variations::TestVariationsService::RegisterPrefs(prefs()->registry());
-    metrics_state_manager_ = metrics::MetricsStateManager::Create(
-        prefs(), &enabled_state_provider_,
-        /*backup_registry_key=*/std::wstring(),
-        /*user_data_dir=*/base::FilePath(),
-        metrics::StartupVisibility::kUnknown);
-    variations_service_ = std::make_unique<variations::TestVariationsService>(
-        prefs(), metrics_state_manager_.get());
-  }
-
-  void SetUp() override {
-    host_content_settings_map_ = base::MakeRefCounted<HostContentSettingsMap>(
-        prefs(), /*is_off_the_record=*/false, /*store_last_modified=*/false,
-        /*restore_session=*/false, /*should_record_metrics=*/false);
-    auto bsa = std::make_unique<ip_protection::MockBlindSignAuth>();
-    bsa_ = bsa.get();
-#if BUILDFLAG(IS_CHROMEOS)
-    install_attributes_ = std::make_unique<ash::StubInstallAttributes>();
-    ash::InstallAttributes::SetForTesting(install_attributes_.get());
-#endif
-    management_service_ = std::make_unique<policy::ManagementService>(
-        std::vector<std::unique_ptr<policy::ManagementStatusProvider>>());
-    tracking_protection_settings_ =
-        std::make_unique<privacy_sandbox::TrackingProtectionSettings>(
-            prefs(),
-            /*host_content_settings_map=*/host_content_settings_map_.get(),
-            /*management_service=*/management_service_.get(),
-            /*is_incognito=*/false);
-    core_host_ = std::make_unique<IpProtectionCoreHost>(
-        IdentityManager(), tracking_protection_settings_.get(), prefs(),
-        /*profile=*/nullptr);
-    core_host_->SetUpForTesting(test_url_loader_factory_.GetSafeWeakWrapper(),
-                                std::move(bsa));
-
-    token_server_get_proxy_config_url_ = GURL(base::StrCat(
-        {net::features::kIpPrivacyTokenServer.Get(),
-         net::features::kIpPrivacyTokenServerGetProxyConfigPath.Get()}));
-    ASSERT_TRUE(token_server_get_proxy_config_url_.is_valid());
-
-    default_not_eligible_backoff_ =
-        net::features::kIpPrivacyTryGetAuthTokensNotEligibleBackoff.Get();
-    default_transient_backoff_ =
-        net::features::kIpPrivacyTryGetAuthTokensTransientBackoff.Get();
-    default_bug_backoff_ =
-        net::features::kIpPrivacyTryGetAuthTokensBugBackoff.Get();
-
-    TestingBrowserProcess::GetGlobal()->SetVariationsService(
-        variations_service_.get());
-  }
-
-  void TearDown() override {
-    TestingBrowserProcess::GetGlobal()->SetVariationsService(nullptr);
-
-    // Remove the raw_ptr to the Mock BSA before `core_host_` frees it.
-    bsa_ = nullptr;
-    host_content_settings_map_->ShutdownOnUIThread();
-    tracking_protection_settings_->Shutdown();
-    core_host_->Shutdown();
-#if BUILDFLAG(IS_CHROMEOS)
-    ash::InstallAttributes::ShutdownForTesting();
-#endif
-  }
-
-  // Get the IdentityManager for this test.
-  signin::IdentityManager* IdentityManager() {
-    return identity_test_env_.identity_manager();
-  }
-
-  void SetupAccount() {
-    if (primary_account_behavior_ == PrimaryAccountBehavior::kNone) {
-#if !BUILDFLAG(IS_CHROMEOS)
-      // Simulate a log out event on all platforms except ChromeOS Ash where
-      // this is not supported.
-      identity_test_env_.ClearPrimaryAccount();
-#endif
-    } else {
-      identity_test_env_.MakePrimaryAccountAvailable(
-          kTestEmail, signin::ConsentLevel::kSignin);
-
-      if (primary_account_behavior_ == PrimaryAccountBehavior::kIneligible) {
-        SetCanUseChromeIpProtectionCapability(false);
-      } else {
-        SetCanUseChromeIpProtectionCapability(true);
-      }
-
-      variations_service_->SetIsLikelyDogfoodClientForTesting(
-          primary_account_behavior_ ==
-          PrimaryAccountBehavior::kIneligibleDogfooder);
-    }
-  }
-
-  void RespondToAccessTokenRequest() {
-    switch (primary_account_behavior_) {
-      case PrimaryAccountBehavior::kNone:
-      case PrimaryAccountBehavior::kIneligible:
-        break;
-      case PrimaryAccountBehavior::kTokenFetchTransientError:
-        identity_test_env_
-            .WaitForAccessTokenRequestIfNecessaryAndRespondWithError(
-                GoogleServiceAuthError(
-                    GoogleServiceAuthError::CONNECTION_FAILED));
-        break;
-      case PrimaryAccountBehavior::kTokenFetchPersistentError:
-        identity_test_env_
-            .WaitForAccessTokenRequestIfNecessaryAndRespondWithError(
-                GoogleServiceAuthError(
-                    GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS));
-        break;
-      case PrimaryAccountBehavior::kUnknownEligibility:
-      case PrimaryAccountBehavior::kReturnsToken:
-      case PrimaryAccountBehavior::kIneligibleDogfooder:
-        identity_test_env_
-            .WaitForAccessTokenRequestIfNecessaryAndRespondWithToken(
-                "access_token", base::Time::Now());
-        break;
-    }
-  }
-
-  // Call `TryGetAuthTokens()` and run until it completes.
-  void TryGetAuthTokens(int num_tokens, ip_protection::ProxyLayer proxy_layer) {
-    SetupAccount();
-
-    core_host_->TryGetAuthTokens(num_tokens, proxy_layer,
-                                 tokens_future_.GetCallback());
-
-    RespondToAccessTokenRequest();
-    ASSERT_TRUE(tokens_future_.Wait()) << "TryGetAuthTokens did not call back";
-  }
-
-  // Set the CanUseChromeIpProtection account capability. The capability tribool
-  // defaults to `kUnknown`.
-  void SetCanUseChromeIpProtectionCapability(bool enabled) {
-    auto account_info = identity_test_env_.identity_manager()
-                            ->FindExtendedAccountInfoByEmailAddress(kTestEmail);
-    AccountCapabilitiesTestMutator mutator(&account_info.capabilities);
-    mutator.set_can_use_chrome_ip_protection(enabled);
-    signin::UpdateAccountInfoForAccount(identity_test_env_.identity_manager(),
-                                        account_info);
-  }
-
-  // Expect that the TryGetAuthTokens call returned the given tokens.
-  void ExpectTryGetAuthTokensResult(
-      std::vector<BlindSignedAuthToken> bsa_tokens) {
-    EXPECT_EQ(std::get<0>(tokens_future_.Get()), bsa_tokens);
-  }
-
-  // Expect that the TryGetAuthTokens call returned nullopt, with
-  // `try_again_after` at the given delta from the current time.
-  void ExpectTryGetAuthTokensResultFailed(base::TimeDelta try_again_delta) {
-    auto& [bsa_tokens, try_again_after] = tokens_future_.Get();
-    EXPECT_EQ(bsa_tokens, std::nullopt);
-    if (!bsa_tokens) {
-      EXPECT_THAT(*try_again_after - base::Time::Now(),
-                  IsNearWithJitter(try_again_delta));
-    }
-    // Clear future so it can be reused and accept new tokens.
-    tokens_future_.Clear();
-  }
-
-  sync_preferences::TestingPrefServiceSyncable* prefs() { return &prefs_; }
-
-  // The behavior of the identity manager.
-  PrimaryAccountBehavior primary_account_behavior_ =
-      PrimaryAccountBehavior::kReturnsToken;
-
-  // Run on the UI thread.
-  content::BrowserTaskEnvironment task_environment_{
-      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
-  base::test::TestFuture<std::optional<std::vector<BlindSignedAuthToken>>,
-                         std::optional<base::Time>>
-      tokens_future_;
-
-  base::test::TestFuture<const std::optional<std::vector<net::ProxyChain>>&,
-                         const std::optional<GeoHint>&>
-      proxy_list_future_;
-
-  // URL loader factory used for all fetchers.
-  network::TestURLLoaderFactory test_url_loader_factory_;
-
-  // URL at which getProxyConfig is invoked.
-  GURL token_server_get_proxy_config_url_;
-
-  // Test environment for IdentityManager. This must come after the
-  // TaskEnvironment.
-  signin::IdentityTestEnvironment identity_test_env_;
-
-  // A convenient expiration time for fake tokens, in the future.
-  base::Time expiration_time_;
-
-  // A convenient geo hint for fake tokens.
-  GeoHint geo_hint_;
-
-  base::HistogramTester histogram_tester_;
-
-  sync_preferences::TestingPrefServiceSyncable prefs_;
-  scoped_refptr<HostContentSettingsMap> host_content_settings_map_;
-  std::unique_ptr<privacy_sandbox::TrackingProtectionSettings>
-      tracking_protection_settings_;
-#if BUILDFLAG(IS_CHROMEOS)
-  std::unique_ptr<ash::StubInstallAttributes> install_attributes_;
-#endif
-  std::unique_ptr<policy::ManagementService> management_service_;
-
-  std::unique_ptr<IpProtectionCoreHost> core_host_;
-  // quiche::BlindSignAuthInterface owned and used by the sequence bound
-  // ip_protection_token_fetcher_ in core_host_.
-  raw_ptr<ip_protection::MockBlindSignAuth> bsa_;
-
-  // Variations service and associated values, used to indicate
-  // whether the client is dogfooding.
-  metrics::TestEnabledStateProvider enabled_state_provider_;
-  std::unique_ptr<metrics::MetricsStateManager> metrics_state_manager_;
-  std::unique_ptr<variations::TestVariationsService> variations_service_;
-
-  // Default backoff times applied for calculating `try_again_after`.
-  base::TimeDelta default_not_eligible_backoff_;
-  base::TimeDelta default_transient_backoff_;
-  base::TimeDelta default_bug_backoff_;
-};
-
-// NOTE: Many of these tests are similar those for
-// IpProtectionTokenDirectFetcher, but both make sense. In the fetcher, they
-// serve as unit tests with a fake delegate. Here, they incorporate
-// IpProtectionCoreHost as a delegate.
-
-TEST_F(IpProtectionCoreHostTest, CanIpProtectionBeEnabled) {
-  // Ensure feature is enabled so we only test the switches.
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndEnableFeature(
-      net::features::kEnableIpProtectionProxy);
-
-  // Base case: Feature enabled, no switches.
-  EXPECT_TRUE(IpProtectionCoreHost::CanIpProtectionBeEnabled());
-
-  {
-    // Case: Disable IP Protection switch.
-    base::test::ScopedCommandLine scoped_command_line;
-    scoped_command_line.GetProcessCommandLine()->AppendSwitch(
-        switches::kDisableIpProtectionProxy);
-    EXPECT_FALSE(IpProtectionCoreHost::CanIpProtectionBeEnabled());
-  }
-
-  {
-    // Case: Disable HTTP/2 switch.
-    base::test::ScopedCommandLine scoped_command_line;
-    scoped_command_line.GetProcessCommandLine()->AppendSwitch(
-        switches::kDisableHttp2);
-    EXPECT_FALSE(IpProtectionCoreHost::CanIpProtectionBeEnabled());
-  }
-
-  {
-    // Case: Both switches.
-    base::test::ScopedCommandLine scoped_command_line;
-    scoped_command_line.GetProcessCommandLine()->AppendSwitch(
-        switches::kDisableIpProtectionProxy);
-    scoped_command_line.GetProcessCommandLine()->AppendSwitch(
-        switches::kDisableHttp2);
-    EXPECT_FALSE(IpProtectionCoreHost::CanIpProtectionBeEnabled());
-  }
-}
-
-// The success case: a primary account is available, and BSA gets a token for
-// it.
-TEST_F(IpProtectionCoreHostTest, Success) {
-  primary_account_behavior_ = PrimaryAccountBehavior::kReturnsToken;
-  bsa_->set_tokens({ip_protection::IpProtectionTokenFetcherHelper::
-                        CreateBlindSignTokenForTesting(
-                            "single-use-1", expiration_time_, geo_hint_),
-                    ip_protection::IpProtectionTokenFetcherHelper::
-                        CreateBlindSignTokenForTesting(
-                            "single-use-2", expiration_time_, geo_hint_)});
-
-  TryGetAuthTokens(2, ip_protection::ProxyLayer::kProxyB);
-
-  EXPECT_TRUE(bsa_->get_tokens_called());
-  EXPECT_EQ(bsa_->oauth_token(), "access_token");
-  EXPECT_EQ(bsa_->num_tokens(), 2);
-  EXPECT_EQ(bsa_->proxy_layer(), quiche::ProxyLayer::kProxyB);
-  std::vector<BlindSignedAuthToken> expected;
-  expected.push_back(ip_protection::IpProtectionTokenFetcherHelper::
-                         CreateMockBlindSignedAuthTokenForTesting(
-                             "single-use-1", expiration_time_, geo_hint_)
-                             .value());
-  expected.push_back(ip_protection::IpProtectionTokenFetcherHelper::
-                         CreateMockBlindSignedAuthTokenForTesting(
-                             "single-use-2", expiration_time_, geo_hint_)
-                             .value());
-  ExpectTryGetAuthTokensResult(std::move(expected));
-  histogram_tester_.ExpectUniqueSample(
-      kTryGetAuthTokensResultHistogram,
-      ip_protection::TryGetAuthTokensResult::kSuccess, 1);
-  histogram_tester_.ExpectTotalCount(kOAuthTokenFetchHistogram, 1);
-  histogram_tester_.ExpectTotalCount(kTokenBatchHistogram, 1);
-}
-
-// BSA returns no tokens.
-TEST_F(IpProtectionCoreHostTest, NoTokens) {
-  primary_account_behavior_ = PrimaryAccountBehavior::kReturnsToken;
-
-  TryGetAuthTokens(1, ip_protection::ProxyLayer::kProxyA);
-
-  EXPECT_TRUE(bsa_->get_tokens_called());
-  EXPECT_EQ(bsa_->num_tokens(), 1);
-  EXPECT_EQ(bsa_->proxy_layer(), quiche::ProxyLayer::kProxyA);
-  EXPECT_EQ(bsa_->oauth_token(), "access_token");
-  ExpectTryGetAuthTokensResultFailed(default_transient_backoff_);
-  histogram_tester_.ExpectUniqueSample(
-      kTryGetAuthTokensResultHistogram,
-      ip_protection::TryGetAuthTokensResult::kFailedBSAOther, 1);
-  histogram_tester_.ExpectTotalCount(kOAuthTokenFetchHistogram, 1);
-  histogram_tester_.ExpectTotalCount(kTokenBatchHistogram, 0);
-}
-
-// BSA returns malformed tokens.
-TEST_F(IpProtectionCoreHostTest, MalformedTokens) {
-  primary_account_behavior_ = PrimaryAccountBehavior::kReturnsToken;
-  auto geo_hint = anonymous_tokens::GeoHint{
-      .geo_hint = "US,US-CA,MOUNTAIN VIEW",
-      .country_code = "US",
-      .region = "US-CA",
-      .city = "MOUNTAIN VIEW",
-  };
-  bsa_->set_tokens(
-      {{"invalid-token-proto-data", absl::Now() + absl::Hours(1), geo_hint}});
-
-  TryGetAuthTokens(1, ip_protection::ProxyLayer::kProxyB);
-
-  EXPECT_TRUE(bsa_->get_tokens_called());
-  EXPECT_EQ(bsa_->num_tokens(), 1);
-  EXPECT_EQ(bsa_->proxy_layer(), quiche::ProxyLayer::kProxyB);
-  EXPECT_EQ(bsa_->oauth_token(), "access_token");
-  ExpectTryGetAuthTokensResultFailed(default_transient_backoff_);
-  histogram_tester_.ExpectUniqueSample(
-      kTryGetAuthTokensResultHistogram,
-      ip_protection::TryGetAuthTokensResult::kFailedBSAOther, 1);
-  histogram_tester_.ExpectTotalCount(kOAuthTokenFetchHistogram, 1);
-  histogram_tester_.ExpectTotalCount(kTokenBatchHistogram, 0);
-}
-
-TEST_F(IpProtectionCoreHostTest, TokenGeoHintContainsOnlyCountry) {
-  primary_account_behavior_ = PrimaryAccountBehavior::kReturnsToken;
-  GeoHint geo_hint_country;
-  geo_hint_country.country_code = "US";
-  bsa_->set_tokens(
-      {ip_protection::IpProtectionTokenFetcherHelper::
-           CreateBlindSignTokenForTesting("single-use-1", expiration_time_,
-                                          geo_hint_country),
-       ip_protection::IpProtectionTokenFetcherHelper::
-           CreateBlindSignTokenForTesting("single-use-2", expiration_time_,
-                                          geo_hint_country)});
-
-  TryGetAuthTokens(2, ip_protection::ProxyLayer::kProxyB);
-
-  EXPECT_TRUE(bsa_->get_tokens_called());
-  EXPECT_EQ(bsa_->oauth_token(), "access_token");
-  EXPECT_EQ(bsa_->num_tokens(), 2);
-  EXPECT_EQ(bsa_->proxy_layer(), quiche::ProxyLayer::kProxyB);
-  std::vector<BlindSignedAuthToken> expected;
-  expected.push_back(ip_protection::IpProtectionTokenFetcherHelper::
-                         CreateMockBlindSignedAuthTokenForTesting(
-                             "single-use-1", expiration_time_, geo_hint_country)
-                             .value());
-  expected.push_back(ip_protection::IpProtectionTokenFetcherHelper::
-                         CreateMockBlindSignedAuthTokenForTesting(
-                             "single-use-2", expiration_time_, geo_hint_country)
-                             .value());
-  ExpectTryGetAuthTokensResult(std::move(expected));
-  histogram_tester_.ExpectUniqueSample(
-      kTryGetAuthTokensResultHistogram,
-      ip_protection::TryGetAuthTokensResult::kSuccess, 1);
-  histogram_tester_.ExpectTotalCount(kOAuthTokenFetchHistogram, 1);
-  histogram_tester_.ExpectTotalCount(kTokenBatchHistogram, 1);
-}
-
-TEST_F(IpProtectionCoreHostTest, TokenHasMissingGeoHint) {
-  primary_account_behavior_ = PrimaryAccountBehavior::kReturnsToken;
-  GeoHint geo_hint;
-  bsa_->set_tokens({ip_protection::IpProtectionTokenFetcherHelper::
-                        CreateBlindSignTokenForTesting(
-                            "single-use-1", expiration_time_, geo_hint)});
-
-  TryGetAuthTokens(1, ip_protection::ProxyLayer::kProxyA);
-
-  EXPECT_TRUE(bsa_->get_tokens_called());
-  EXPECT_EQ(bsa_->num_tokens(), 1);
-  EXPECT_EQ(bsa_->proxy_layer(), quiche::ProxyLayer::kProxyA);
-  EXPECT_EQ(bsa_->oauth_token(), "access_token");
-  ExpectTryGetAuthTokensResultFailed(default_transient_backoff_);
-  histogram_tester_.ExpectUniqueSample(
-      kTryGetAuthTokensResultHistogram,
-      ip_protection::TryGetAuthTokensResult::kFailedBSAOther, 1);
-  histogram_tester_.ExpectTotalCount(kOAuthTokenFetchHistogram, 1);
-  histogram_tester_.ExpectTotalCount(kTokenBatchHistogram, 0);
-}
-
-// BSA returns a 400 error.
-TEST_F(IpProtectionCoreHostTest, BlindSignedTokenError400) {
-  primary_account_behavior_ = PrimaryAccountBehavior::kReturnsToken;
-  bsa_->set_status(absl::InvalidArgumentError("uhoh"));
-
-  TryGetAuthTokens(1, ip_protection::ProxyLayer::kProxyA);
-
-  EXPECT_TRUE(bsa_->get_tokens_called());
-  EXPECT_EQ(bsa_->num_tokens(), 1);
-  EXPECT_EQ(bsa_->proxy_layer(), quiche::ProxyLayer::kProxyA);
-  EXPECT_EQ(bsa_->oauth_token(), "access_token");
-  ExpectTryGetAuthTokensResultFailed(default_bug_backoff_);
-  histogram_tester_.ExpectUniqueSample(
-      kTryGetAuthTokensResultHistogram,
-      ip_protection::TryGetAuthTokensResult::kFailedBSA400, 1);
-  histogram_tester_.ExpectUniqueSample(
-      kTryGetAuthTokensErrorHistogram,
-      4043967578,  // base::PersistentHash("INVALID_ARGUMENT: uhoh")
-      1);
-  histogram_tester_.ExpectTotalCount(kOAuthTokenFetchHistogram, 1);
-  histogram_tester_.ExpectTotalCount(kTokenBatchHistogram, 0);
-}
-
-// BSA returns a 401 error.
-TEST_F(IpProtectionCoreHostTest, BlindSignedTokenError401) {
-  primary_account_behavior_ = PrimaryAccountBehavior::kReturnsToken;
-  bsa_->set_status(absl::UnauthenticatedError("uhoh"));
-
-  TryGetAuthTokens(1, ip_protection::ProxyLayer::kProxyB);
-
-  EXPECT_TRUE(bsa_->get_tokens_called());
-  EXPECT_EQ(bsa_->num_tokens(), 1);
-  EXPECT_EQ(bsa_->proxy_layer(), quiche::ProxyLayer::kProxyB);
-  EXPECT_EQ(bsa_->oauth_token(), "access_token");
-  ExpectTryGetAuthTokensResultFailed(default_bug_backoff_);
-  histogram_tester_.ExpectUniqueSample(
-      kTryGetAuthTokensResultHistogram,
-      ip_protection::TryGetAuthTokensResult::kFailedBSA401, 1);
-  histogram_tester_.ExpectUniqueSample(
-      kTryGetAuthTokensErrorHistogram,
-      4264091263,  // base::PersistentHash("UNAUTHENTICATED: uhoh")
-      1);
-  histogram_tester_.ExpectTotalCount(kOAuthTokenFetchHistogram, 1);
-  histogram_tester_.ExpectTotalCount(kTokenBatchHistogram, 0);
-}
-
-// BSA returns a 403 error.
-TEST_F(IpProtectionCoreHostTest, BlindSignedTokenError403) {
-  primary_account_behavior_ = PrimaryAccountBehavior::kReturnsToken;
-  bsa_->set_status(absl::PermissionDeniedError("uhoh"));
-
-  TryGetAuthTokens(1, ip_protection::ProxyLayer::kProxyA);
-
-  EXPECT_TRUE(bsa_->get_tokens_called());
-  EXPECT_EQ(bsa_->num_tokens(), 1);
-  EXPECT_EQ(bsa_->proxy_layer(), quiche::ProxyLayer::kProxyA);
-  EXPECT_EQ(bsa_->oauth_token(), "access_token");
-  ExpectTryGetAuthTokensResultFailed(default_not_eligible_backoff_);
-  histogram_tester_.ExpectUniqueSample(
-      kTryGetAuthTokensResultHistogram,
-      ip_protection::TryGetAuthTokensResult::kFailedBSA403, 1);
-  histogram_tester_.ExpectUniqueSample(
-      kTryGetAuthTokensErrorHistogram,
-      4104528123,  // base::PersistentHash("PERMISSION_DENIED: uhoh")
-      1);
-  // Failed to parse GetInitialDataResponse
-  histogram_tester_.ExpectTotalCount(kOAuthTokenFetchHistogram, 1);
-  histogram_tester_.ExpectTotalCount(kTokenBatchHistogram, 0);
-}
-
-// BSA returns some other error.
-TEST_F(IpProtectionCoreHostTest, BlindSignedTokenErrorOther) {
-  primary_account_behavior_ = PrimaryAccountBehavior::kReturnsToken;
-  bsa_->set_status(absl::UnknownError("uhoh"));
-
-  TryGetAuthTokens(1, ip_protection::ProxyLayer::kProxyB);
-
-  EXPECT_TRUE(bsa_->get_tokens_called());
-  EXPECT_EQ(bsa_->num_tokens(), 1);
-  EXPECT_EQ(bsa_->proxy_layer(), quiche::ProxyLayer::kProxyB);
-  EXPECT_EQ(bsa_->oauth_token(), "access_token");
-  ExpectTryGetAuthTokensResultFailed(default_transient_backoff_);
-  histogram_tester_.ExpectUniqueSample(
-      kTryGetAuthTokensResultHistogram,
-      ip_protection::TryGetAuthTokensResult::kFailedBSAOther, 1);
-  histogram_tester_.ExpectUniqueSample(
-      kTryGetAuthTokensErrorHistogram,
-      2844845398,  // base::PersistentHash("UNKNOWN: uhoh")
-      1);
-  histogram_tester_.ExpectTotalCount(kOAuthTokenFetchHistogram, 1);
-  histogram_tester_.ExpectTotalCount(kTokenBatchHistogram, 0);
-}
-
-// The CanUseChromeIpProtection capability is not present (`kUnknown`).
-TEST_F(IpProtectionCoreHostTest, AccountCapabilityUnknown) {
-  primary_account_behavior_ = PrimaryAccountBehavior::kUnknownEligibility;
-  bsa_->set_tokens({ip_protection::IpProtectionTokenFetcherHelper::
-                        CreateBlindSignTokenForTesting(
-                            "single-use-1", expiration_time_, geo_hint_),
-                    ip_protection::IpProtectionTokenFetcherHelper::
-                        CreateBlindSignTokenForTesting(
-                            "single-use-2", expiration_time_, geo_hint_)});
-
-  TryGetAuthTokens(2, ip_protection::ProxyLayer::kProxyA);
-
-  EXPECT_TRUE(bsa_->get_tokens_called());
-  EXPECT_EQ(bsa_->oauth_token(), "access_token");
-  EXPECT_EQ(bsa_->num_tokens(), 2);
-  EXPECT_EQ(bsa_->proxy_layer(), quiche::ProxyLayer::kProxyA);
-  std::vector<BlindSignedAuthToken> expected;
-  expected.push_back(ip_protection::IpProtectionTokenFetcherHelper::
-                         CreateMockBlindSignedAuthTokenForTesting(
-                             "single-use-1", expiration_time_, geo_hint_)
-                             .value());
-  expected.push_back(ip_protection::IpProtectionTokenFetcherHelper::
-                         CreateMockBlindSignedAuthTokenForTesting(
-                             "single-use-2", expiration_time_, geo_hint_)
-                             .value());
-  ExpectTryGetAuthTokensResult(std::move(expected));
-  histogram_tester_.ExpectUniqueSample(
-      kTryGetAuthTokensResultHistogram,
-      ip_protection::TryGetAuthTokensResult::kSuccess, 1);
-  histogram_tester_.ExpectTotalCount(kOAuthTokenFetchHistogram, 1);
-  histogram_tester_.ExpectTotalCount(kTokenBatchHistogram, 1);
-}
-
-// Fetching OAuth token returns a transient error.
-TEST_F(IpProtectionCoreHostTest, AuthTokenTransientError) {
-  primary_account_behavior_ = PrimaryAccountBehavior::kTokenFetchTransientError;
-
-  TryGetAuthTokens(1, ip_protection::ProxyLayer::kProxyB);
-
-  EXPECT_FALSE(bsa_->get_tokens_called());
-  ExpectTryGetAuthTokensResultFailed(default_transient_backoff_);
-  histogram_tester_.ExpectUniqueSample(
-      kTryGetAuthTokensResultHistogram,
-      ip_protection::TryGetAuthTokensResult::kFailedOAuthTokenTransient, 1);
-}
-
-// Fetching OAuth token returns a persistent error.
-TEST_F(IpProtectionCoreHostTest, AuthTokenPersistentError) {
-  primary_account_behavior_ =
-      PrimaryAccountBehavior::kTokenFetchPersistentError;
-
-  TryGetAuthTokens(1, ip_protection::ProxyLayer::kProxyA);
-
-  EXPECT_FALSE(bsa_->get_tokens_called());
-  ExpectTryGetAuthTokensResultFailed(base::TimeDelta::Max());
-  histogram_tester_.ExpectUniqueSample(
-      kTryGetAuthTokensResultHistogram,
-      ip_protection::TryGetAuthTokensResult::kFailedOAuthTokenPersistent, 1);
-}
-
-// No primary account.
-TEST_F(IpProtectionCoreHostTest, NoPrimary) {
-  primary_account_behavior_ = PrimaryAccountBehavior::kNone;
-
-  TryGetAuthTokens(1, ip_protection::ProxyLayer::kProxyB);
-
-  EXPECT_FALSE(bsa_->get_tokens_called());
-  ExpectTryGetAuthTokensResultFailed(base::TimeDelta::Max());
-  histogram_tester_.ExpectUniqueSample(
-      kTryGetAuthTokensResultHistogram,
-      ip_protection::TryGetAuthTokensResult::kFailedNoAccount, 1);
-  histogram_tester_.ExpectTotalCount(kOAuthTokenFetchHistogram, 0);
-  histogram_tester_.ExpectTotalCount(kTokenBatchHistogram, 0);
-}
-
-// Primary account exists but does not have CanUseChromeIpProtection.
-TEST_F(IpProtectionCoreHostTest, NoCapability) {
-  primary_account_behavior_ = PrimaryAccountBehavior::kIneligible;
-
-  TryGetAuthTokens(1, ip_protection::ProxyLayer::kProxyB);
-
-  EXPECT_FALSE(bsa_->get_tokens_called());
-  ExpectTryGetAuthTokensResultFailed(default_not_eligible_backoff_);
-  histogram_tester_.ExpectUniqueSample(
-      kTryGetAuthTokensResultHistogram,
-      ip_protection::TryGetAuthTokensResult::kFailedNotEligible, 1);
-  histogram_tester_.ExpectTotalCount(kOAuthTokenFetchHistogram, 0);
-  histogram_tester_.ExpectTotalCount(kTokenBatchHistogram, 0);
-}
-
-// Primary account exists, does not have CanUseChromeIpProtection, but is a
-// dogfooder.
-TEST_F(IpProtectionCoreHostTest, NoCapabilityButDogfooder) {
-  primary_account_behavior_ = PrimaryAccountBehavior::kIneligibleDogfooder;
-  bsa_->set_tokens({ip_protection::IpProtectionTokenFetcherHelper::
-                        CreateBlindSignTokenForTesting(
-                            "single-use-1", expiration_time_, geo_hint_)});
-
-  TryGetAuthTokens(1, ip_protection::ProxyLayer::kProxyB);
-
-  EXPECT_TRUE(bsa_->get_tokens_called());
-  EXPECT_EQ(bsa_->proxy_layer(), quiche::ProxyLayer::kProxyB);
-  std::vector<BlindSignedAuthToken> expected;
-  expected.push_back(ip_protection::IpProtectionTokenFetcherHelper::
-                         CreateMockBlindSignedAuthTokenForTesting(
-                             "single-use-1", expiration_time_, geo_hint_)
-                             .value());
-  ExpectTryGetAuthTokensResult(std::move(expected));
-  histogram_tester_.ExpectUniqueSample(
-      kTryGetAuthTokensResultHistogram,
-      ip_protection::TryGetAuthTokensResult::kSuccess, 1);
-  histogram_tester_.ExpectTotalCount(kOAuthTokenFetchHistogram, 1);
-  histogram_tester_.ExpectTotalCount(kTokenBatchHistogram, 1);
-}
-
-// TryGetAuthTokens() fails because IP Protection is disabled by user settings.
-TEST_F(IpProtectionCoreHostTest, TryGetAuthTokens_IpProtectionDisabled) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndEnableFeature(privacy_sandbox::kIpProtectionUx);
-
-  primary_account_behavior_ = PrimaryAccountBehavior::kNone;
-
-  prefs()->SetBoolean(prefs::kIpProtectionEnabled, false);
-
-  TryGetAuthTokens(1, ip_protection::ProxyLayer::kProxyA);
-
-  EXPECT_FALSE(bsa_->get_tokens_called());
-  ExpectTryGetAuthTokensResultFailed(base::TimeDelta::Max());
-  histogram_tester_.ExpectUniqueSample(
-      kTryGetAuthTokensResultHistogram,
-      ip_protection::TryGetAuthTokensResult::kFailedDisabledByUser, 1);
-  histogram_tester_.ExpectTotalCount(kOAuthTokenFetchHistogram, 0);
-  histogram_tester_.ExpectTotalCount(kTokenBatchHistogram, 0);
-}
-
-// No primary account initially but this changes when the account status
-// changes.
-#if !BUILDFLAG(IS_CHROMEOS)
-TEST_F(IpProtectionCoreHostTest, AccountLoginTriggersBackoffReset) {
-  primary_account_behavior_ = PrimaryAccountBehavior::kNone;
-
-  TryGetAuthTokens(1, ip_protection::ProxyLayer::kProxyA);
-
-  EXPECT_FALSE(bsa_->get_tokens_called());
-  ExpectTryGetAuthTokensResultFailed(base::TimeDelta::Max());
-
-  primary_account_behavior_ = PrimaryAccountBehavior::kReturnsToken;
-  bsa_->set_tokens({ip_protection::IpProtectionTokenFetcherHelper::
-                        CreateBlindSignTokenForTesting(
-                            "single-use-1", expiration_time_, geo_hint_)});
-
-  TryGetAuthTokens(1, ip_protection::ProxyLayer::kProxyA);
-
-  EXPECT_TRUE(bsa_->get_tokens_called());
-  EXPECT_EQ(bsa_->oauth_token(), "access_token");
-  EXPECT_EQ(bsa_->num_tokens(), 1);
-  EXPECT_EQ(bsa_->proxy_layer(), quiche::ProxyLayer::kProxyA);
-}
-#endif
-
-// If the account session token expires and is renewed, the persistent backoff
-// should be cleared.
-TEST_F(IpProtectionCoreHostTest, SessionRefreshTriggersBackoffReset) {
-  AccountInfo account_info = identity_test_env_.MakePrimaryAccountAvailable(
-      kTestEmail, signin::ConsentLevel::kSignin);
-  SetCanUseChromeIpProtectionCapability(true);
-
-  identity_test_env_.UpdatePersistentErrorOfRefreshTokenForAccount(
-      account_info.account_id,
-      GoogleServiceAuthError(
-          GoogleServiceAuthError::State::INVALID_GAIA_CREDENTIALS));
-
-  base::test::TestFuture<std::optional<std::vector<BlindSignedAuthToken>>,
-                         std::optional<base::Time>>
-      tokens_future;
-  core_host_->TryGetAuthTokens(1, ip_protection::ProxyLayer::kProxyB,
-                               tokens_future.GetCallback());
-  const std::optional<base::Time>& try_again_after =
-      tokens_future.Get<std::optional<base::Time>>();
-  ASSERT_TRUE(try_again_after);
-  EXPECT_EQ(*try_again_after, base::Time::Max());
-
-  identity_test_env_.UpdatePersistentErrorOfRefreshTokenForAccount(
-      account_info.account_id,
-      GoogleServiceAuthError(GoogleServiceAuthError::State::NONE));
-
-  bsa_->set_tokens({ip_protection::IpProtectionTokenFetcherHelper::
-                        CreateBlindSignTokenForTesting(
-                            "single-use-1", expiration_time_, geo_hint_)});
-  tokens_future.Clear();
-  core_host_->TryGetAuthTokens(1, ip_protection::ProxyLayer::kProxyB,
-                               tokens_future.GetCallback());
-  identity_test_env_.WaitForAccessTokenRequestIfNecessaryAndRespondWithToken(
-      "access_token", base::Time::Now());
-  const std::optional<std::vector<BlindSignedAuthToken>>& tokens =
-      tokens_future.Get<std::optional<std::vector<BlindSignedAuthToken>>>();
-  ASSERT_TRUE(tokens);
-}
-
-TEST_F(IpProtectionCoreHostTest, GetProxyConfigWithApiKey) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndEnableFeatureWithParameters(
-      net::features::kEnableIpProtectionProxy,
-      {
-          {net::features::kIpPrivacyIncludeOAuthTokenInGetProxyConfig.name,
-           "false"},
-      });
-  std::vector<net::ProxyChain> response_proxy_list = {
-      ip_protection::IpProtectionProxyConfigDirectFetcher::MakeChainForTesting(
-          {"proxyA", "proxyB"}),
-  };
-
-  ip_protection::GetProxyConfigResponse response;
-  auto* chain = response.add_proxy_chain();
-  chain->set_proxy_a("proxyA");
-  chain->set_proxy_b("proxyB");
-  response.mutable_geo_hint()->set_country_code(geo_hint_.country_code);
-  response.mutable_geo_hint()->set_iso_region(geo_hint_.iso_region);
-  response.mutable_geo_hint()->set_city_name(geo_hint_.city_name);
-  std::string response_str = response.SerializeAsString();
-
-  test_url_loader_factory_.SetInterceptor(
-      base::BindLambdaForTesting([&](const network::ResourceRequest& request) {
-        ASSERT_TRUE(request.url.is_valid());
-        ASSERT_EQ(request.url, token_server_get_proxy_config_url_);
-        EXPECT_TRUE(request.headers.HasHeader("X-Goog-Api-Key"));
-        EXPECT_FALSE(request.headers.HasHeader("Authentication"));
-        auto head = network::mojom::URLResponseHead::New();
-        test_url_loader_factory_.AddResponse(
-            token_server_get_proxy_config_url_, std::move(head), response_str,
-            network::URLLoaderCompletionStatus(net::OK));
-      }));
-
-  core_host_->GetProxyConfig(proxy_list_future_.GetCallback());
-  ASSERT_TRUE(proxy_list_future_.Wait()) << "GetProxyConfig did not call back";
-
-  // Extract tuple elements for individual comparison.
-  const auto& [proxy_list, geo_hint] = proxy_list_future_.Get();
-
-  ASSERT_TRUE(proxy_list.has_value());  // Check if optional has value.
-  EXPECT_THAT(proxy_list.value(),
-              testing::ElementsAreArray(response_proxy_list));
-
-  ASSERT_TRUE(geo_hint.has_value());  // Check that GeoHint is not null.
-  EXPECT_TRUE(geo_hint == geo_hint_);
-}
-
-TEST_F(IpProtectionCoreHostTest, GetProxyConfigWithOAuthToken) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndEnableFeatureWithParameters(
-      net::features::kEnableIpProtectionProxy,
-      {
-          {net::features::kIpPrivacyIncludeOAuthTokenInGetProxyConfig.name,
-           "true"},
-      });
-  std::vector<net::ProxyChain> response_proxy_list = {
-      ip_protection::IpProtectionProxyConfigDirectFetcher::MakeChainForTesting(
-          {"proxyA", "proxyB"}),
-  };
-
-  ip_protection::GetProxyConfigResponse response;
-  auto* chain = response.add_proxy_chain();
-  chain->set_proxy_a("proxyA");
-  chain->set_proxy_b("proxyB");
-  response.mutable_geo_hint()->set_country_code(geo_hint_.country_code);
-  response.mutable_geo_hint()->set_iso_region(geo_hint_.iso_region);
-  response.mutable_geo_hint()->set_city_name(geo_hint_.city_name);
-  std::string response_str = response.SerializeAsString();
-
-  test_url_loader_factory_.SetInterceptor(
-      base::BindLambdaForTesting([&](const network::ResourceRequest& request) {
-        ASSERT_TRUE(request.url.is_valid());
-        ASSERT_EQ(request.url, token_server_get_proxy_config_url_);
-        EXPECT_FALSE(request.headers.HasHeader("X-Goog-Api-Key"));
-        EXPECT_TRUE(request.headers.HasHeader("Authorization"));
-        auto head = network::mojom::URLResponseHead::New();
-        test_url_loader_factory_.AddResponse(
-            token_server_get_proxy_config_url_, std::move(head), response_str,
-            network::URLLoaderCompletionStatus(net::OK));
-      }));
-
-  SetupAccount();
-  core_host_->GetProxyConfig(proxy_list_future_.GetCallback());
-  RespondToAccessTokenRequest();
-  ASSERT_TRUE(proxy_list_future_.Wait()) << "GetProxyConfig did not call back";
-
-  // Extract tuple elements for individual comparison.
-  const auto& [proxy_list, geo_hint] = proxy_list_future_.Get();
-
-  ASSERT_TRUE(proxy_list.has_value());  // Check if optional has value.
-  EXPECT_THAT(proxy_list.value(),
-              testing::ElementsAreArray(response_proxy_list));
-
-  ASSERT_TRUE(geo_hint.has_value());  // Check that GeoHint is not null.
-  EXPECT_TRUE(geo_hint == geo_hint_);
-}
-
-TEST_F(IpProtectionCoreHostTest, ProxyOverrideFlagsAll) {
-  std::vector<net::ProxyChain> proxy_override_list = {
-      ip_protection::IpProtectionProxyConfigDirectFetcher::MakeChainForTesting(
-          {"proxyAOverride", "proxyBOverride"}),
-      ip_protection::IpProtectionProxyConfigDirectFetcher::MakeChainForTesting(
-          {"proxyAOverride", "proxyBOverride"}),
-  };
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndEnableFeatureWithParameters(
-      net::features::kEnableIpProtectionProxy,
-      {
-          {net::features::kIpPrivacyProxyAHostnameOverride.name,
-           "proxyAOverride"},
-          {net::features::kIpPrivacyProxyBHostnameOverride.name,
-           "proxyBOverride"},
-      });
-  std::vector<std::vector<std::string>> response_proxy_list = {
-      {"proxyA1", "proxyB1"}, {"proxyA2", "proxyB2"}};
-  ip_protection::GetProxyConfigResponse response;
-  auto* chain = response.add_proxy_chain();
-  chain->set_proxy_a("proxyA1");
-  chain->set_proxy_b("proxyB1");
-  chain = response.add_proxy_chain();
-  chain->set_proxy_a("proxyA2");
-  chain->set_proxy_b("proxyB2");
-
-  response.mutable_geo_hint()->set_country_code(geo_hint_.country_code);
-  response.mutable_geo_hint()->set_iso_region(geo_hint_.iso_region);
-  response.mutable_geo_hint()->set_city_name(geo_hint_.city_name);
-  std::string response_str = response.SerializeAsString();
-
-  test_url_loader_factory_.AddResponse(
-      token_server_get_proxy_config_url_.spec(), response_str);
-
-  core_host_->GetProxyConfig(proxy_list_future_.GetCallback());
-  ASSERT_TRUE(proxy_list_future_.Wait()) << "GetProxyConfig did not call back";
-
-  // Extract tuple elements for individual comparison.
-  const auto& [proxy_list, geo_hint] = proxy_list_future_.Get();
-
-  ASSERT_TRUE(proxy_list.has_value());  // Check if optional has value.
-  EXPECT_THAT(proxy_list.value(),
-              testing::ElementsAreArray(proxy_override_list));
-
-  ASSERT_TRUE(geo_hint.has_value());  // Check that GeoHint is not null.
-  EXPECT_TRUE(geo_hint == geo_hint_);
-}
-
-TEST_F(IpProtectionCoreHostTest, GetProxyConfigFailure) {
-  // Count each call to the retriever's GetProxyConfig and return an error.
-  int get_proxy_config_calls = 0;
-  bool get_proxy_config_fails = true;
-  test_url_loader_factory_.SetInterceptor(
-      base::BindLambdaForTesting([&](const network::ResourceRequest& request) {
-        ASSERT_TRUE(request.url.is_valid());
-        ASSERT_EQ(request.url, token_server_get_proxy_config_url_);
-        get_proxy_config_calls++;
-        if (get_proxy_config_fails) {
-          test_url_loader_factory_.AddResponse(
-              token_server_get_proxy_config_url_.spec(), "",
-              net::HTTP_INTERNAL_SERVER_ERROR);
-        } else {
-          ip_protection::GetProxyConfigResponse response;
-          test_url_loader_factory_.AddResponse(
-              token_server_get_proxy_config_url_.spec(),
-              response.SerializeAsString());
-        }
-      }));
-
-  auto call_get_proxy_list = [this](bool expect_success) {
-    base::test::TestFuture<const std::optional<std::vector<net::ProxyChain>>&,
-                           const std::optional<GeoHint>&>
-        future;
-    this->core_host_->GetProxyConfig(future.GetCallback());
-    ASSERT_TRUE(future.Wait());
-
-    // Extract tuple elements for individual comparison.
-    const auto& [proxy_list, geo_hint] = future.Get();
-    if (expect_success) {
-      ASSERT_TRUE(
-          proxy_list.has_value());  // Check if optional vector has value.
-      EXPECT_EQ(proxy_list,
-                std::make_optional<std::vector<net::ProxyChain>>({}));
-
-      // `GetProxyConfigResponse` used is default instance which means GeoHint
-      // will not be populated and should be a nullptr.
-      EXPECT_FALSE(geo_hint.has_value());
-    } else {
-      EXPECT_EQ(proxy_list, std::nullopt);
-      EXPECT_FALSE(geo_hint.has_value());
-    }
-  };
-
-  call_get_proxy_list(/*expect_success=*/false);
-  EXPECT_EQ(get_proxy_config_calls, 1);
-
-  // An immediate second call to GetProxyConfig should not call the retriever
-  // again.
-  call_get_proxy_list(/*expect_success=*/false);
-  EXPECT_EQ(get_proxy_config_calls, 1);
-
-  const base::TimeDelta timeout = ip_protection::
-      IpProtectionProxyConfigDirectFetcher::kGetProxyConfigFailureTimeout;
-
-  // A call after the timeout is allowed to proceed, but fails so the new
-  // backoff is 2*timeout.
-  task_environment_.FastForwardBy(timeout);
-  call_get_proxy_list(/*expect_success=*/false);
-  EXPECT_EQ(get_proxy_config_calls, 2);
-
-  // A call another timeout later does nothing because the backoff is 2*timeout
-  // now.
-  task_environment_.FastForwardBy(timeout);
-  call_get_proxy_list(/*expect_success=*/false);
-  EXPECT_EQ(get_proxy_config_calls, 2);
-
-  // A call another timeout later is allowed to proceed, and this time succeeds.
-  get_proxy_config_fails = false;
-  task_environment_.FastForwardBy(timeout);
-  call_get_proxy_list(/*expect_success=*/true);
-  EXPECT_EQ(get_proxy_config_calls, 3);
-
-  // An immediate second call to GetProxyConfig is also allowed to proceed.
-  // Note that the network service also applies a minimum time between calls,
-  // so this would not happen in production.
-  get_proxy_config_fails = true;
-  call_get_proxy_list(/*expect_success=*/false);
-  EXPECT_EQ(get_proxy_config_calls, 4);
-
-  // The backoff timeout starts over.
-  call_get_proxy_list(/*expect_success=*/false);
-  EXPECT_EQ(get_proxy_config_calls, 4);
-  task_environment_.FastForwardBy(timeout);
-  call_get_proxy_list(/*expect_success=*/false);
-  EXPECT_EQ(get_proxy_config_calls, 5);
-
-  get_proxy_config_fails = false;
-  // SetupAccount should trigger ClearOAuthTokenProblemBackoff() and reset
-  // fetcher's backoff.
-  SetupAccount();
-  call_get_proxy_list(/*expect_success=*/true);
-  EXPECT_EQ(get_proxy_config_calls, 6);
-}
-
-TEST_F(IpProtectionCoreHostTest, GetProxyConfig_IpProtectionDisabled) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndEnableFeature(privacy_sandbox::kIpProtectionUx);
-
-  prefs()->SetBoolean(prefs::kIpProtectionEnabled, false);
-
-  core_host_->GetProxyConfig(proxy_list_future_.GetCallback());
-
-  ASSERT_TRUE(proxy_list_future_.Wait()) << "GetProxyConfig did not call back";
-
-  // Extract tuple elements for individual comparison.
-  const auto& [proxy_list, geo_hint] = proxy_list_future_.Get();
-
-  EXPECT_EQ(proxy_list, std::nullopt);
-  EXPECT_FALSE(geo_hint.has_value());
-}
-
-// Do a basic check of the token formats.
-TEST_F(IpProtectionCoreHostTest, TokenFormat) {
-  BlindSignedAuthToken result =
-      ip_protection::IpProtectionTokenFetcherHelper::
-          CreateMockBlindSignedAuthTokenForTesting("single-use-1",
-                                                   expiration_time_, geo_hint_)
-              .value();
-  std::string& token = result.token;
-  size_t token_position = token.find("PrivateToken token=");
-  size_t extensions_position = token.find("extensions=");
-
-  EXPECT_EQ(token_position, 0u);
-  EXPECT_NE(extensions_position, std::string::npos);
-
-  // Check if the comma is between "PrivateToken token=" and "extensions=".
-  size_t comma_position = token.find(",", token_position);
-  EXPECT_NE(comma_position, std::string::npos);
-  EXPECT_LT(comma_position, extensions_position);
-}
-
-TEST_F(IpProtectionCoreHostTest, RecycleAndTakeRecycledTokens) {
-  std::vector<BlindSignedAuthToken> tokens_a;
-  tokens_a.push_back(ip_protection::IpProtectionTokenFetcherHelper::
-                         CreateMockBlindSignedAuthTokenForTesting(
-                             "single-use-1", expiration_time_, geo_hint_)
-                             .value());
-  std::vector<BlindSignedAuthToken> tokens_b;
-  tokens_b.push_back(ip_protection::IpProtectionTokenFetcherHelper::
-                         CreateMockBlindSignedAuthTokenForTesting(
-                             "single-use-2", expiration_time_, geo_hint_)
-                             .value());
-
-  core_host_->RecycleTokens(ip_protection::ProxyLayer::kProxyA, tokens_a);
-  core_host_->RecycleTokens(ip_protection::ProxyLayer::kProxyB, tokens_b);
-
-  EXPECT_THAT(
-      core_host_->TakeRecycledTokens(),
-      UnorderedElementsAre(Pair(ip_protection::ProxyLayer::kProxyA, tokens_a),
-                           Pair(ip_protection::ProxyLayer::kProxyB, tokens_b)));
-
-  // The previous operation should empty the cache.
-  EXPECT_THAT(core_host_->TakeRecycledTokens(), testing::IsEmpty());
-}
diff --git a/chrome/browser/ip_protection/ip_protection_switches.cc b/chrome/browser/ip_protection/ip_protection_switches.cc
deleted file mode 100644
index b33754e..0000000
--- a/chrome/browser/ip_protection/ip_protection_switches.cc
+++ /dev/null
@@ -1,11 +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/ip_protection/ip_protection_switches.h"
-
-namespace switches {
-
-const char kDisableIpProtectionProxy[] = "disable-ip-privacy-proxy";
-
-}  // namespace switches
diff --git a/chrome/browser/ip_protection/ip_protection_switches.h b/chrome/browser/ip_protection/ip_protection_switches.h
deleted file mode 100644
index 33b7301..0000000
--- a/chrome/browser/ip_protection/ip_protection_switches.h
+++ /dev/null
@@ -1,16 +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_IP_PROTECTION_IP_PROTECTION_SWITCHES_H_
-#define CHROME_BROWSER_IP_PROTECTION_IP_PROTECTION_SWITCHES_H_
-
-namespace switches {
-
-// Disable IP Protection proxy, for use with a corresponding chrome://flags
-// entry in case of breakage or when debugging.
-extern const char kDisableIpProtectionProxy[];
-
-}  // namespace switches
-
-#endif  // CHROME_BROWSER_IP_PROTECTION_IP_PROTECTION_SWITCHES_H_
diff --git a/chrome/browser/lifetime/browser_close_manager.cc b/chrome/browser/lifetime/browser_close_manager.cc
index 12d2725..1639e1a 100644
--- a/chrome/browser/lifetime/browser_close_manager.cc
+++ b/chrome/browser/lifetime/browser_close_manager.cc
@@ -80,9 +80,11 @@
   }
 
   browser_shutdown::SetTryingToQuit(false);
-  for (Browser* browser : *BrowserList::GetInstance()) {
-    browser->ResetTryToCloseWindow();
-  }
+  ForEachCurrentBrowserWindowInterfaceOrderedByActivation(
+      [](BrowserWindowInterface* browser) {
+        browser->GetBrowserForMigrationOnly()->ResetTryToCloseWindow();
+        return true;
+      });
 }
 
 void BrowserCloseManager::TryToCloseBrowsers() {
@@ -91,13 +93,20 @@
   // stop closing. CallBeforeUnloadHandlers prompts the user and calls
   // OnBrowserReportCloseable with the result. If the user confirms the close,
   // this will trigger TryToCloseBrowsers to try again.
-  for (Browser* browser : *BrowserList::GetInstance()) {
-    if (browser->TryToCloseWindow(
-            false, base::BindRepeating(
-                       &BrowserCloseManager::OnBrowserReportCloseable, this))) {
-      current_browser_ = browser;
-      return;
-    }
+  bool should_stop = false;
+  ForEachCurrentBrowserWindowInterfaceOrderedByActivation(
+      [this, &should_stop](BrowserWindowInterface* browser) {
+        if (browser->GetBrowserForMigrationOnly()->TryToCloseWindow(
+                false,
+                base::BindRepeating(
+                    &BrowserCloseManager::OnBrowserReportCloseable, this))) {
+          current_browser_ = browser;
+          should_stop = true;
+        }
+        return !should_stop;
+      });
+  if (should_stop) {
+    return;
   }
 
   // This is the success endpoint. If we get here, all beforeunload handlers
diff --git a/chrome/browser/lifetime/browser_close_manager.h b/chrome/browser/lifetime/browser_close_manager.h
index 90cf25a..e5ec35a 100644
--- a/chrome/browser/lifetime/browser_close_manager.h
+++ b/chrome/browser/lifetime/browser_close_manager.h
@@ -12,7 +12,7 @@
 #include "base/memory/ref_counted.h"
 #include "base/timer/elapsed_timer.h"
 
-class Browser;
+class BrowserWindowInterface;
 
 // Manages confirming that browser windows are closeable and closing them at
 // shutdown.
@@ -68,7 +68,7 @@
 
   // The browser for which we are waiting for a callback to
   // OnBrowserReportCloseable.
-  raw_ptr<Browser> current_browser_;
+  raw_ptr<BrowserWindowInterface> current_browser_;
 };
 
 #endif  // CHROME_BROWSER_LIFETIME_BROWSER_CLOSE_MANAGER_H_
diff --git a/chrome/browser/lifetime/browser_close_manager_browsertest.cc b/chrome/browser/lifetime/browser_close_manager_browsertest.cc
index 0139cae..045bfc3 100644
--- a/chrome/browser/lifetime/browser_close_manager_browsertest.cc
+++ b/chrome/browser/lifetime/browser_close_manager_browsertest.cc
@@ -17,6 +17,7 @@
 #include "base/memory/raw_ptr.h"
 #include "base/run_loop.h"
 #include "base/test/mock_callback.h"
+#include "base/test/run_until.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/threading/thread_restrictions.h"
 #include "build/build_config.h"
@@ -708,9 +709,8 @@
   PrepareForDialog(browsers_[0]);
   PrepareForDialog(browsers_[1]);
 
-  AllBrowsersClosingCancelledObserver cancel_observer(2);
+  AllBrowsersClosingCancelledObserver cancel_observer(1);
   chrome::CloseAllBrowsersAndQuit();
-  ASSERT_NO_FATAL_FAILURE(AcceptClose());
   AddBlankTabAndShow(browsers_[0]);
   ASSERT_NO_FATAL_FAILURE(ASSERT_TRUE(ui_test_utils::NavigateToURL(
       browsers_[0], embedded_test_server()->GetURL("/beforeunload.html"))));
@@ -719,7 +719,7 @@
       browsers_[1], embedded_test_server()->GetURL("/beforeunload.html"))));
   PrepareForDialog(browsers_[0]);
   PrepareForDialog(browsers_[1]);
-  ASSERT_NO_FATAL_FAILURE(AcceptClose());
+
   ASSERT_NO_FATAL_FAILURE(CancelClose());
   cancel_observer.Wait();
   EXPECT_FALSE(browser_shutdown::IsTryingToQuit());
@@ -843,21 +843,33 @@
 
   AllBrowsersClosingCancelledObserver cancel_observer(1);
   chrome::CloseAllBrowsersAndQuit();
+  EXPECT_TRUE(browsers_[0]->IsAttemptingToCloseBrowser());
 
   browsers_.push_back(CreateBrowser(browser()->profile()));
   ASSERT_NO_FATAL_FAILURE(ASSERT_TRUE(ui_test_utils::NavigateToURL(
       browsers_[1], embedded_test_server()->GetURL("/beforeunload.html"))));
   PrepareForDialog(browsers_[1]);
+
+  // Attempt to close all tabs on the newly created browser. It should be
+  // blocked by the before-unload handler.
+  EXPECT_TRUE(browsers_[0]->IsAttemptingToCloseBrowser());
+  EXPECT_FALSE(browsers_[1]->IsAttemptingToCloseBrowser());
   browsers_[1]->tab_strip_model()->CloseAllTabs();
-  ASSERT_NO_FATAL_FAILURE(CancelClose());
+  EXPECT_EQ(1, browsers_[1]->tab_strip_model()->count());
+  EXPECT_TRUE(browsers_[0]->IsAttemptingToCloseBrowser());
+  EXPECT_FALSE(browsers_[1]->IsAttemptingToCloseBrowser());
+
+  // Dismiss the first Browser's before unload dialog.
   ASSERT_NO_FATAL_FAILURE(CancelClose());
   cancel_observer.Wait();
+
   EXPECT_FALSE(browser_shutdown::IsTryingToQuit());
+  EXPECT_FALSE(browsers_[0]->IsAttemptingToCloseBrowser());
+  EXPECT_FALSE(browsers_[1]->IsAttemptingToCloseBrowser());
   EXPECT_EQ(1, browsers_[0]->tab_strip_model()->count());
   EXPECT_EQ(1, browsers_[1]->tab_strip_model()->count());
 
   chrome::CloseAllBrowsersAndQuit();
-  browsers_[1]->tab_strip_model()->CloseAllTabs();
   ASSERT_NO_FATAL_FAILURE(AcceptClose());
   ASSERT_NO_FATAL_FAILURE(AcceptClose());
 
@@ -912,8 +924,8 @@
   AllBrowsersClosingCancelledObserver cancel_observer(1);
   chrome::CloseAllBrowsersAndQuit();
 
-  ASSERT_FALSE(browsers_.empty());
-  EXPECT_FALSE(browsers_[0]->HandleBeforeClose());
+  ASSERT_EQ(browsers_.size(), 2u);
+  EXPECT_FALSE(browsers_[1]->HandleBeforeClose());
   ASSERT_NO_FATAL_FAILURE(CancelClose());
   cancel_observer.Wait();
   EXPECT_FALSE(browser_shutdown::IsTryingToQuit());
@@ -921,8 +933,8 @@
   EXPECT_EQ(1, browsers_[1]->tab_strip_model()->count());
 
   chrome::CloseAllBrowsersAndQuit();
-  ASSERT_FALSE(browsers_.empty());
-  EXPECT_FALSE(browsers_[0]->HandleBeforeClose());
+  ASSERT_EQ(browsers_.size(), 2u);
+  EXPECT_FALSE(browsers_[1]->HandleBeforeClose());
   ASSERT_NO_FATAL_FAILURE(AcceptClose());
   ASSERT_NO_FATAL_FAILURE(AcceptClose());
 
diff --git a/chrome/browser/local_network_access/local_network_access_browsertest.cc b/chrome/browser/local_network_access/local_network_access_browsertest.cc
index 53bd1ce4..0a985e28 100644
--- a/chrome/browser/local_network_access/local_network_access_browsertest.cc
+++ b/chrome/browser/local_network_access/local_network_access_browsertest.cc
@@ -2,16 +2,26 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "base/files/file_util.h"
 #include "base/strings/stringprintf.h"
 #include "base/test/bind.h"
+#include "base/test/values_test_util.h"
+#include "chrome/browser/extensions/extension_service.h"
+#include "chrome/browser/extensions/test_extension_system.h"
 #include "chrome/browser/local_network_access/local_network_access_browsertest_base.h"
+#include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
+#include "chrome/common/url_constants.h"
 #include "chrome/test/base/ui_test_utils.h"
 #include "components/permissions/permission_request_manager.h"
 #include "content/public/browser/web_contents.h"
+#include "content/public/common/url_constants.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/browser_test_utils.h"
 #include "content/public/test/content_browser_test_utils.h"
+#include "extensions/browser/install_verifier.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/extension_builder.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
 #include "third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom.h"
 
@@ -19,20 +29,37 @@
 
 namespace local_network_access {
 
+namespace {
+// We use a custom page that explicitly disables its own favicon (by providing
+// an invalid data: URL for it) so as to prevent the browser from making an
+// automatic request to /favicon.ico.
+//
+// It also carries a header that makes the browser consider it came from the
+// `public` address space, irrespective of the fact that we loaded the web page
+// from localhost.
+constexpr char kTreatAsPublicAddressPath[] =
+    "/local_network_access/no-favicon-treat-as-public-address.html";
+
 // Path to a response that passes Local Network Access checks.
 constexpr char kLnaPath[] =
     "/set-header"
     "?Access-Control-Allow-Origin: *";
 
+// The returned script evaluates to a boolean indicating whether the fetch
+// succeeded or not.
+std::string FetchScript(const GURL& url) {
+  return content::JsReplace(
+      "fetch($1).then(response => true).catch(error => false)", url);
+}
+}  // namespace
+
 class LocalNetworkAccessBrowserTest : public LocalNetworkAccessBrowserTestBase {
 };
 
 IN_PROC_BROWSER_TEST_F(LocalNetworkAccessBrowserTest, FetchDenyPermission) {
   ASSERT_TRUE(content::NavigateToURL(
       web_contents(),
-      https_server().GetURL(
-          "a.com",
-          "/local_network_access/no-favicon-treat-as-public-address.html")));
+      https_server().GetURL("a.com", kTreatAsPublicAddressPath)));
 
   // Enable auto-denial of LNA permission request.
   bubble_factory()->set_response_type(
@@ -49,9 +76,7 @@
 IN_PROC_BROWSER_TEST_F(LocalNetworkAccessBrowserTest, FetchAcceptPermission) {
   ASSERT_TRUE(content::NavigateToURL(
       web_contents(),
-      https_server().GetURL(
-          "a.com",
-          "/local_network_access/no-favicon-treat-as-public-address.html")));
+      https_server().GetURL("a.com", kTreatAsPublicAddressPath)));
 
   // Enable auto-accept of LNA permission request.
   bubble_factory()->set_response_type(
@@ -125,9 +150,7 @@
                        CheckPrivateAliasFeatureCounter) {
   ASSERT_TRUE(content::NavigateToURL(
       web_contents(),
-      https_server().GetURL(
-          "a.com",
-          "/local_network_access/no-favicon-treat-as-public-address.html")));
+      https_server().GetURL("a.com", kTreatAsPublicAddressPath)));
 
   // LNA fetch fails due to mismatched targetAddressSpace. Result doesn't matter
   // here though, as we're just checking a use counter that doesn't depend on
@@ -146,9 +169,7 @@
                        CheckPrivateAliasFeatureCounterLocalNotCounted) {
   ASSERT_TRUE(content::NavigateToURL(
       web_contents(),
-      https_server().GetURL(
-          "a.com",
-          "/local_network_access/no-favicon-treat-as-public-address.html")));
+      https_server().GetURL("a.com", kTreatAsPublicAddressPath)));
 
   // LNA fetch fails due to mismatched targetAddressSpace. Result doesn't matter
   // here though, as we're just checking a use counter that doesn't depend on
@@ -163,4 +184,119 @@
   CheckCounter(WebFeature::kLocalNetworkAccessPrivateAliasUse, 0);
 }
 
+// ================
+// 0.0.0.0 TESTS
+// ================
+
+// This test verifies that a 0.0.0.0 subresource is blocked on a nonsecure
+// public URL.
+IN_PROC_BROWSER_TEST_F(LocalNetworkAccessBrowserTest,
+                       NullIPBlockedOnNonsecure) {
+  if constexpr (BUILDFLAG(IS_WIN)) {
+    GTEST_SKIP() << "0.0.0.0 behavior varies across platforms and is "
+                    "unreachable on Windows.";
+  }
+
+  ASSERT_TRUE(content::NavigateToURL(
+      web_contents(),
+      embedded_test_server()->GetURL("a.com", kTreatAsPublicAddressPath)));
+  GURL subresource_url =
+      embedded_test_server()->GetURL("0.0.0.0", "/cors-ok.txt");
+  EXPECT_EQ(false,
+            content::EvalJs(web_contents(), FetchScript(subresource_url)));
+}
+
+// ====================
+// SPECIAL SCHEME TESTS
+// ====================
+//
+// These tests verify the IP address space assigned to documents loaded from a
+// variety of special URL schemes. Since these are not loaded over the network,
+// an IP address space must be made up for them.
+
+// This test verifies that the devtools:// scheme is considered loopback for the
+// purpose of Local Network Access.
+IN_PROC_BROWSER_TEST_F(LocalNetworkAccessBrowserTest, SpecialSchemeDevtools) {
+  EXPECT_TRUE(content::NavigateToURL(
+      web_contents(), GURL("devtools://devtools/bundled/devtools_app.html")));
+  EXPECT_TRUE(
+      web_contents()->GetPrimaryMainFrame()->GetLastCommittedURL().SchemeIs(
+          content::kChromeDevToolsScheme));
+
+  GURL fetch_url = https_server().GetURL("/cors-ok.txt");
+
+  EXPECT_EQ(true, content::EvalJs(web_contents(), FetchScript(fetch_url)));
+}
+
+// This test verifies that the chrome-search:// scheme is considered loopback
+// for the purpose of Local Network Access.
+IN_PROC_BROWSER_TEST_F(LocalNetworkAccessBrowserTest,
+                       SpecialSchemeChromeSearch) {
+  EXPECT_TRUE(content::NavigateToURL(
+      web_contents(), GURL("chrome-search://most-visited/title.html")));
+  ASSERT_TRUE(
+      web_contents()->GetPrimaryMainFrame()->GetLastCommittedURL().SchemeIs(
+          chrome::kChromeSearchScheme));
+
+  GURL fetch_url = https_server().GetURL("/cors-ok.txt");
+
+  EXPECT_EQ(true, content::EvalJs(web_contents(), FetchScript(fetch_url),
+                                  content::EXECUTE_SCRIPT_DEFAULT_OPTIONS,
+                                  content::ISOLATED_WORLD_ID_CONTENT_END));
+}
+
+// This test verifies that the chrome-extension:// scheme is considered local
+// for the purpose of Local Network Access.
+IN_PROC_BROWSER_TEST_F(LocalNetworkAccessBrowserTest,
+                       SpecialSchemeChromeExtension) {
+  base::ScopedAllowBlockingForTesting allow_blocking;
+  extensions::ScopedInstallVerifierBypassForTest install_verifier_bypass;
+
+  base::ScopedTempDir temp_dir;
+  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+  static constexpr char kPageFile[] = "page.html";
+  constexpr char kContents[] = R"(
+  <html>
+    <head>
+      <title>IPAddressSpace of chrome-extension:// schemes.</title>
+    </head>
+    <body>
+    </body>
+  </html>
+  )";
+  base::WriteFile(temp_dir.GetPath().AppendASCII(kPageFile), kContents);
+  static constexpr char kWebAccessibleResources[] =
+      R"([{
+            "resources": ["page.html"],
+            "matches": ["*://*/*"]
+         }])";
+
+  extensions::ExtensionBuilder builder("test");
+  builder.SetPath(temp_dir.GetPath())
+      .SetVersion("1.0")
+      .SetLocation(extensions::mojom::ManifestLocation::kExternalPolicyDownload)
+      .SetManifestKey("web_accessible_resources",
+                      base::test::ParseJson(kWebAccessibleResources));
+
+  scoped_refptr<const extensions::Extension> extension = builder.Build();
+  extensions::ExtensionRegistrar::Get(browser()->profile())
+      ->OnExtensionInstalled(extension.get(), syncer::StringOrdinal(), 0);
+
+  const GURL url = extension->GetResourceURL(kPageFile);
+
+  EXPECT_TRUE(content::NavigateToURL(web_contents(), url));
+  ASSERT_TRUE(
+      web_contents()->GetPrimaryMainFrame()->GetLastCommittedURL().SchemeIs(
+          extensions::kExtensionScheme));
+
+  GURL fetch_url = https_server().GetURL("/cors-ok.txt");
+
+  // Note: CSP is blocking javascript eval, unless we run it in an isolated
+  // world.
+  EXPECT_EQ(true, content::EvalJs(web_contents(), FetchScript(fetch_url),
+                                  content::EXECUTE_SCRIPT_DEFAULT_OPTIONS,
+                                  content::ISOLATED_WORLD_ID_CONTENT_END));
+}
+
 }  // namespace local_network_access
diff --git a/chrome/browser/local_network_access/local_network_access_counters_browsertest.cc b/chrome/browser/local_network_access/local_network_access_counters_browsertest.cc
new file mode 100644
index 0000000..8777f365
--- /dev/null
+++ b/chrome/browser/local_network_access/local_network_access_counters_browsertest.cc
@@ -0,0 +1,640 @@
+// 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 <map>
+#include <string>
+#include <string_view>
+
+#include "base/files/file_util.h"
+#include "base/functional/bind.h"
+#include "base/test/metrics/histogram_tester.h"
+#include "base/test/scoped_feature_list.h"
+#include "base/test/values_test_util.h"
+#include "chrome/browser/local_network_access/local_network_access_browsertest_base.h"
+#include "chrome/browser/net/profile_network_context_service.h"
+#include "chrome/browser/net/profile_network_context_service_factory.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/common/url_constants.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/web_feature_histogram_tester.h"
+#include "components/error_page/content/browser/net_error_auto_reloader.h"
+#include "components/metrics/content/subprocess_metrics_provider.h"
+#include "components/permissions/permission_request_manager.h"
+#include "components/prefs/pref_service.h"
+#include "components/proxy_config/proxy_config_dictionary.h"
+#include "components/proxy_config/proxy_config_pref_names.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/common/content_features.h"
+#include "content/public/common/url_constants.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_browser_test_utils.h"
+#include "content/public/test/test_navigation_observer.h"
+#include "content/public/test/url_loader_interceptor.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "services/network/public/cpp/features.h"
+#include "services/network/public/cpp/private_network_access_check_result.h"
+#include "services/network/public/cpp/url_loader_completion_status.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom.h"
+
+// Local Network Access browser tests testing UseCounters
+
+namespace local_network_access {
+
+using blink::mojom::WebFeature;
+using testing::IsEmpty;
+
+// We use a custom page that explicitly disables its own favicon (by providing
+// an invalid data: URL for it) so as to prevent the browser from making an
+// automatic request to /favicon.ico. This is because the automatic request
+// messes with our tests, in which we want to trigger a single request from the
+// web page to a resource of our choice and observe the side-effect in metrics.
+constexpr char kNoFaviconPath[] = "/local_network_access/no-favicon.html";
+
+// Same as kNoFaviconPath, except it carries a header that makes the browser
+// consider it came from the `public` address space, irrespective of the fact
+// that we loaded the web page from localhost.
+constexpr char kTreatAsPublicAddressPath[] =
+    "/local_network_access/no-favicon-treat-as-public-address.html";
+
+GURL SecureURL(const net::EmbeddedTestServer& server, const std::string& path) {
+  return server.GetURL(path);
+}
+
+GURL SecureURLWithHostName(const net::EmbeddedTestServer& server,
+                           const std::string& path,
+                           const std::string& hostname) {
+  return server.GetURL(hostname, path);
+}
+
+GURL LocalSecureURL(const net::EmbeddedTestServer& server) {
+  return SecureURL(server, kNoFaviconPath);
+}
+
+GURL LocalSecureURLWithHost(const net::EmbeddedTestServer& server,
+                            const std::string& hostname) {
+  return SecureURLWithHostName(server, kNoFaviconPath, hostname);
+}
+
+GURL PublicSecureURL(const net::EmbeddedTestServer& server) {
+  return SecureURL(server, kTreatAsPublicAddressPath);
+}
+
+// Path to a worker script that posts a message to its creator once loaded.
+constexpr char kWorkerScriptPath[] = "/workers/post_ready.js";
+
+std::string FetchWorkerScript(std::string_view relative_url) {
+  constexpr char kTemplate[] = R"(
+    new Promise((resolve) => {
+      const worker = new Worker($1);
+      worker.addEventListener("message", () => { resolve(true); });
+      worker.addEventListener("error", () => { resolve(false); });
+    });
+  )";
+  return content::JsReplace(kTemplate, relative_url);
+}
+
+// Path to a worker script that posts a message to each client that connects.
+constexpr char kSharedWorkerScriptPath[] = "/workers/shared_post_ready.js";
+
+// Instantiates a shared worker script from `path`.
+// If it loads successfully, the worker should post a message to each client
+// that connects to it to signal success.
+std::string FetchSharedWorkerScript(std::string_view path) {
+  constexpr char kTemplate[] = R"(
+    new Promise((resolve) => {
+      const worker = new SharedWorker($1);
+      worker.port.addEventListener("message", () => resolve(true));
+      worker.addEventListener("error", () => resolve(false));
+      worker.port.start();
+    })
+  )";
+
+  return content::JsReplace(kTemplate, path);
+}
+
+std::vector<WebFeature> AllAddressSpaceFeatures() {
+  return {
+      WebFeature::kAddressSpaceLocalSecureContextEmbeddedLoopbackV2,
+      WebFeature::kAddressSpaceLocalNonSecureContextEmbeddedLoopbackV2,
+      WebFeature::kAddressSpacePublicSecureContextEmbeddedLoopbackV2,
+      WebFeature::kAddressSpacePublicNonSecureContextEmbeddedLoopbackV2,
+      WebFeature::kAddressSpaceUnknownSecureContextEmbeddedLoopbackV2,
+      WebFeature::kAddressSpaceUnknownNonSecureContextEmbeddedLoopbackV2,
+      WebFeature::kAddressSpacePublicSecureContextEmbeddedLocalV2,
+      WebFeature::kAddressSpacePublicNonSecureContextEmbeddedLocalV2,
+      WebFeature::kAddressSpaceUnknownSecureContextEmbeddedLocalV2,
+      WebFeature::kAddressSpaceUnknownNonSecureContextEmbeddedLocalV2,
+      WebFeature::kAddressSpaceLocalSecureContextNavigatedToLoopbackV2,
+      WebFeature::kAddressSpaceLocalNonSecureContextNavigatedToLoopbackV2,
+      WebFeature::kAddressSpacePublicSecureContextNavigatedToLoopbackV2,
+      WebFeature::kAddressSpacePublicNonSecureContextNavigatedToLoopbackV2,
+      WebFeature::kAddressSpaceUnknownSecureContextNavigatedToLoopbackV2,
+      WebFeature::kAddressSpaceUnknownNonSecureContextNavigatedToLoopbackV2,
+      WebFeature::kAddressSpacePublicSecureContextNavigatedToLocalV2,
+      WebFeature::kAddressSpacePublicNonSecureContextNavigatedToLocalV2,
+      WebFeature::kAddressSpaceUnknownSecureContextNavigatedToLocalV2,
+      WebFeature::kAddressSpaceUnknownNonSecureContextNavigatedToLocalV2,
+      WebFeature::kPrivateNetworkAccessFetchedWorkerScript,
+      WebFeature::kPrivateNetworkAccessFetchedSubFrame,
+      WebFeature::kPrivateNetworkAccessFetchedTopFrame,
+      WebFeature::kPrivateNetworkAccessWithinWorker,
+  };
+}
+
+class LocalNetworkAccessCountersBrowserTest
+    : public LocalNetworkAccessBrowserTestBase {};
+
+// ================
+// USECOUNTER TESTS
+// ================
+//
+// UseCounters are translated into UMA histograms at the chrome/ layer, by the
+// page_load_metrics component. These tests verify that UseCounters are recorded
+// correctly by Local Network Access code in the right circumstances.
+
+// This test verifies that no feature is counted for the initial navigation from
+// a new tab to a page served by localhost.
+//
+// Regression test for https://crbug.com/1134601.
+IN_PROC_BROWSER_TEST_F(LocalNetworkAccessCountersBrowserTest,
+                       DoesNotRecordAddressSpaceFeatureForInitialNavigation) {
+  WebFeatureHistogramTester feature_histogram_tester;
+
+  EXPECT_TRUE(
+      content::NavigateToURL(web_contents(), PublicSecureURL(https_server())));
+
+  EXPECT_THAT(
+      feature_histogram_tester.GetNonZeroCounts(AllAddressSpaceFeatures()),
+      IsEmpty());
+}
+
+// This test verifies that no feature is counted for top-level navigations from
+// a public page to a local page.
+IN_PROC_BROWSER_TEST_F(LocalNetworkAccessCountersBrowserTest,
+                       DoesNotRecordAddressSpaceFeatureForRegularNavigation) {
+  WebFeatureHistogramTester feature_histogram_tester;
+
+  EXPECT_TRUE(
+      content::NavigateToURL(web_contents(), PublicSecureURL(https_server())));
+  EXPECT_TRUE(
+      content::NavigateToURL(web_contents(), LocalSecureURL(https_server())));
+
+  EXPECT_THAT(
+      feature_histogram_tester.GetNonZeroCounts(AllAddressSpaceFeatures()),
+      IsEmpty());
+}
+
+// This test verifies that when a `public` document navigates itself to a
+// document served by a non-public IP, the correct address space feature is
+// recorded.
+IN_PROC_BROWSER_TEST_F(LocalNetworkAccessCountersBrowserTest,
+                       RecordsAddressSpaceFeatureForNavigation) {
+  WebFeatureHistogramTester feature_histogram_tester;
+
+  EXPECT_TRUE(
+      content::NavigateToURL(web_contents(), PublicSecureURL(https_server())));
+  EXPECT_THAT(
+      feature_histogram_tester.GetNonZeroCounts(AllAddressSpaceFeatures()),
+      IsEmpty());
+
+  EXPECT_TRUE(content::NavigateToURLFromRenderer(
+      web_contents(), LocalSecureURL(https_server())));
+
+  feature_histogram_tester.ExpectCounts(AddFeatureCounts(
+      AllZeroFeatureCounts(AllAddressSpaceFeatures()),
+      {
+          {WebFeature::kAddressSpacePublicNonSecureContextNavigatedToLocalV2,
+           1},
+          {WebFeature::kPrivateNetworkAccessFetchedTopFrame, 1},
+      }));
+}
+
+// This test verifies that when a page embeds an empty iframe pointing to
+// about:blank, no address space feature is recorded. It serves as a basis for
+// comparison with the following tests, which test behavior with iframes.
+IN_PROC_BROWSER_TEST_F(
+    LocalNetworkAccessCountersBrowserTest,
+    DoesNotRecordAddressSpaceFeatureForChildAboutBlankNavigation) {
+  WebFeatureHistogramTester feature_histogram_tester;
+
+  EXPECT_TRUE(
+      content::NavigateToURL(web_contents(), PublicSecureURL(https_server())));
+  EXPECT_EQ(true, content::EvalJs(web_contents(), R"(
+    new Promise(resolve => {
+      const child = document.createElement("iframe");
+      child.src = "about:blank";
+      child.onload = () => { resolve(true); };
+      document.body.appendChild(child);
+    })
+  )"));
+
+  EXPECT_THAT(
+      feature_histogram_tester.GetNonZeroCounts(AllAddressSpaceFeatures()),
+      IsEmpty());
+}
+
+// This test verifies that when a secure context served from the public
+// address space loads a child frame from the local network, the correct
+// WebFeature is use-counted.
+IN_PROC_BROWSER_TEST_F(LocalNetworkAccessCountersBrowserTest,
+                       RecordsAddressSpaceFeatureForChildNavigation) {
+  WebFeatureHistogramTester feature_histogram_tester;
+
+  EXPECT_TRUE(
+      content::NavigateToURL(web_contents(), PublicSecureURL(https_server())));
+  EXPECT_THAT(
+      feature_histogram_tester.GetNonZeroCounts(AllAddressSpaceFeatures()),
+      IsEmpty());
+
+  std::string_view script_template = R"(
+    new Promise(resolve => {
+      const child = document.createElement("iframe");
+      child.src = $1;
+      child.onload = () => { resolve(true); };
+      document.body.appendChild(child);
+    })
+  )";
+  EXPECT_EQ(true, content::EvalJs(
+                      web_contents(),
+                      content::JsReplace(script_template,
+                                         LocalSecureURL(https_server()))));
+
+  feature_histogram_tester.ExpectCounts(AddFeatureCounts(
+      AllZeroFeatureCounts(AllAddressSpaceFeatures()),
+      {
+          {WebFeature::kAddressSpacePublicNonSecureContextNavigatedToLocalV2,
+           1},
+          {WebFeature::kPrivateNetworkAccessFetchedSubFrame, 1},
+      }));
+}
+
+// This test verifies that when a secure context served from the public
+// address space loads a grand-child frame from the local network, the correct
+// WebFeature is use-counted. If inheritance did not work correctly, the
+// intermediate about:blank frame might confuse the address space logic.
+IN_PROC_BROWSER_TEST_F(LocalNetworkAccessCountersBrowserTest,
+                       RecordsAddressSpaceFeatureForGrandchildNavigation) {
+  WebFeatureHistogramTester feature_histogram_tester;
+
+  EXPECT_TRUE(
+      content::NavigateToURL(web_contents(), PublicSecureURL(https_server())));
+  EXPECT_THAT(
+      feature_histogram_tester.GetNonZeroCounts(AllAddressSpaceFeatures()),
+      IsEmpty());
+
+  std::string_view script_template = R"(
+    function addChildFrame(doc, src) {
+      return new Promise(resolve => {
+        const child = doc.createElement("iframe");
+        child.src = src;
+        child.onload = () => { resolve(child); };
+        doc.body.appendChild(child);
+      });
+    }
+
+    addChildFrame(document, "about:blank")
+      .then(child => addChildFrame(child.contentDocument, $1))
+      .then(grandchild =>  true);
+  )";
+  EXPECT_EQ(true, content::EvalJs(
+                      web_contents(),
+                      content::JsReplace(script_template,
+                                         LocalSecureURL(https_server()))));
+
+  feature_histogram_tester.ExpectCounts(AddFeatureCounts(
+      AllZeroFeatureCounts(AllAddressSpaceFeatures()),
+      {
+          {WebFeature::kAddressSpacePublicNonSecureContextNavigatedToLocalV2,
+           1},
+          {WebFeature::kPrivateNetworkAccessFetchedSubFrame, 1},
+      }));
+}
+
+// This test verifies that the right address space feature is recorded when a
+// navigation results in a local network request. Specifically, in this test
+// the document being navigated is not the one initiating the navigation (the
+// latter being the "remote initiator" referenced by the test name).
+IN_PROC_BROWSER_TEST_F(LocalNetworkAccessCountersBrowserTest,
+                       RecordsAddressSpaceFeatureForRemoteInitiatorNavigation) {
+  WebFeatureHistogramTester feature_histogram_tester;
+
+  EXPECT_TRUE(content::NavigateToURL(
+      web_contents(),
+      SecureURL(https_server(),
+                "/local_network_access/remote-initiator-navigation.html")));
+  EXPECT_THAT(
+      feature_histogram_tester.GetNonZeroCounts(AllAddressSpaceFeatures()),
+      IsEmpty());
+
+  EXPECT_EQ(true, content::EvalJs(web_contents(), content::JsReplace(R"(
+    runTest({
+      url: "/defaultresponse",
+    });
+  )")));
+
+  feature_histogram_tester.ExpectCounts(AddFeatureCounts(
+      AllZeroFeatureCounts(AllAddressSpaceFeatures()),
+      {
+          {WebFeature::kAddressSpacePublicNonSecureContextNavigatedToLocalV2,
+           1},
+          {WebFeature::kPrivateNetworkAccessFetchedSubFrame, 1},
+      }));
+}
+
+// This test verifies that when the initiator of a navigation is no longer
+// around by the time the navigation finishes, then no address space feature is
+// recorded, and importantly: the browser does not crash.
+IN_PROC_BROWSER_TEST_F(
+    LocalNetworkAccessCountersBrowserTest,
+    DoesNotRecordAddressSpaceFeatureForClosedInitiatorNavigation) {
+  WebFeatureHistogramTester feature_histogram_tester;
+
+  EXPECT_TRUE(content::NavigateToURL(
+      web_contents(),
+      SecureURL(https_server(),
+                "/local_network_access/remote-initiator-navigation.html")));
+
+  EXPECT_EQ(true, content::EvalJs(web_contents(), R"(
+    runTest({
+      url: new URL("/slow?3", window.location).href,
+      initiatorBehavior: "close",
+    });
+  )"));
+
+  EXPECT_THAT(
+      feature_histogram_tester.GetNonZeroCounts(AllAddressSpaceFeatures()),
+      IsEmpty());
+}
+
+// This test verifies that when the initiator of a navigation has already
+// navigated itself by the time the navigation finishes, then no address space
+// feature is recorded.
+IN_PROC_BROWSER_TEST_F(
+    LocalNetworkAccessCountersBrowserTest,
+    DoesNotRecordAddressSpaceFeatureForMissingInitiatorNavigation) {
+  WebFeatureHistogramTester feature_histogram_tester;
+
+  EXPECT_TRUE(content::NavigateToURL(
+      web_contents(),
+      SecureURL(https_server(),
+                "/local_network_access/remote-initiator-navigation.html")));
+
+  EXPECT_EQ(true, content::EvalJs(web_contents(), R"(
+    runTest({
+      url: new URL("/slow?3", window.location).href,
+      initiatorBehavior: "navigate",
+    });
+  )"));
+
+  EXPECT_THAT(
+      feature_histogram_tester.GetNonZeroCounts(AllAddressSpaceFeatures()),
+      IsEmpty());
+}
+
+// This test verifies that local network requests that are blocked are not
+// use-counted.
+IN_PROC_BROWSER_TEST_F(LocalNetworkAccessCountersBrowserTest,
+                       DoesNotRecordAddressSpaceFeatureForBlockedRequests) {
+  WebFeatureHistogramTester feature_histogram_tester;
+
+  EXPECT_TRUE(
+      content::NavigateToURL(web_contents(), PublicSecureURL(https_server())));
+  EXPECT_THAT(
+      feature_histogram_tester.GetNonZeroCounts(AllAddressSpaceFeatures()),
+      IsEmpty());
+
+  base::HistogramTester base_histogram_tester;
+
+  bubble_factory()->set_response_type(
+      permissions::PermissionRequestManager::AutoResponseType::DENY_ALL);
+
+  EXPECT_EQ(true,
+            content::EvalJs(web_contents(),
+                            content::JsReplace("fetch($1).catch(() => true)",
+                                               LocalSecureURLWithHost(
+                                                   https_server(), "a.test"))));
+
+  metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
+  base_histogram_tester.ExpectBucketCount(
+      "Security.PrivateNetworkAccess.CheckResult",
+      network::PrivateNetworkAccessCheckResult::kLNAPermissionRequired, 1);
+
+  EXPECT_THAT(
+      feature_histogram_tester.GetNonZeroCounts(AllAddressSpaceFeatures()),
+      IsEmpty());
+}
+
+// This test verifies that resources proxied through a proxy on localhost can
+// be fetched from documents in the public IP address space.
+// Regression test for https://crbug.com/1253239.
+IN_PROC_BROWSER_TEST_F(LocalNetworkAccessCountersBrowserTest,
+                       ProxiedResourcesAllowed) {
+  EXPECT_TRUE(
+      content::NavigateToURL(web_contents(), PublicSecureURL(https_server())));
+
+  browser()->profile()->GetPrefs()->SetDict(
+      proxy_config::prefs::kProxy,
+      ProxyConfigDictionary::CreateFixedServers(
+          https_server().host_port_pair().ToString(), ""));
+  ProfileNetworkContextServiceFactory::GetForContext(browser()->profile())
+      ->FlushProxyConfigMonitorForTesting();
+
+  WebFeatureHistogramTester feature_histogram_tester;
+
+  EXPECT_EQ(true, content::EvalJs(web_contents(), R"(
+    fetch("/defaultresponse").then(response => response.ok)
+  )"));
+
+  EXPECT_THAT(
+      feature_histogram_tester.GetNonZeroCounts(AllAddressSpaceFeatures()),
+      IsEmpty());
+}
+
+// This test verifies that a UseCounter is recorded when a document makes a
+// local network request to load a worker script from a secure context, does
+// not trigger LNA because the request is same-origin and the origin is
+// potentially trustworthy, and loads the script anyway.
+IN_PROC_BROWSER_TEST_F(LocalNetworkAccessCountersBrowserTest,
+                       RecordsFeatureForWorkerScriptFetchFromSecure) {
+  EXPECT_TRUE(
+      content::NavigateToURL(web_contents(), PublicSecureURL(https_server())));
+
+  WebFeatureHistogramTester feature_histogram_tester;
+
+  EXPECT_EQ(true, content::EvalJs(web_contents(),
+                                  FetchWorkerScript(kWorkerScriptPath)));
+
+  feature_histogram_tester.ExpectCounts(AddFeatureCounts(
+      AllZeroFeatureCounts(AllAddressSpaceFeatures()),
+      {
+          {WebFeature::kPrivateNetworkAccessFetchedWorkerScript, 1},
+      }));
+}
+
+// This test verifies that a UseCounter is recorded when a document makes a
+// local network request to load a shared worker script from a secure context,
+// does not trigger LNA because the request is same-origin and the origin is
+// potentially trustworthy, and loads the script anyway.
+IN_PROC_BROWSER_TEST_F(LocalNetworkAccessCountersBrowserTest,
+                       RecordsFeatureForSharedWorkerScriptFetchFromSecure) {
+  EXPECT_TRUE(
+      content::NavigateToURL(web_contents(), PublicSecureURL(https_server())));
+
+  WebFeatureHistogramTester feature_histogram_tester;
+
+  EXPECT_EQ(true,
+            content::EvalJs(web_contents(),
+                            FetchSharedWorkerScript(kSharedWorkerScriptPath)));
+
+  feature_histogram_tester.ExpectCounts(AddFeatureCounts(
+      AllZeroFeatureCounts(AllAddressSpaceFeatures()),
+      {
+          {WebFeature::kPrivateNetworkAccessFetchedWorkerScript, 1},
+      }));
+}
+
+// This test verifies that a UseCounter is recorded when a document makes a
+// local network request to load a service worker script from treat-as-public
+// to local.
+IN_PROC_BROWSER_TEST_F(
+    LocalNetworkAccessCountersBrowserTest,
+    RecordsFeatureForServiceWorkerScriptFetchFromTreatAsPublicToLocal) {
+  EXPECT_TRUE(
+      content::NavigateToURL(web_contents(), PublicSecureURL(https_server())));
+
+  WebFeatureHistogramTester feature_histogram_tester;
+  bubble_factory()->set_response_type(
+      permissions::PermissionRequestManager::AutoResponseType::ACCEPT_ALL);
+
+  EXPECT_EQ(true, content::EvalJs(web_contents(), R"(
+    navigator.serviceWorker.register('/service_worker/empty.js')
+      .then(navigator.serviceWorker.ready)
+      .then(() => true);
+  )"));
+
+  feature_histogram_tester.ExpectCounts(AddFeatureCounts(
+      AllZeroFeatureCounts(AllAddressSpaceFeatures()),
+      {
+          {WebFeature::kPrivateNetworkAccessFetchedWorkerScript, 1},
+      }));
+}
+
+// This test verifies that a UseCounter is not recorded when a document makes a
+// local network request to load a service worker script from local to local.
+IN_PROC_BROWSER_TEST_F(
+    LocalNetworkAccessCountersBrowserTest,
+    ShouldNotRecordFeatureForServiceWorkerScriptFetchFromLocalToLocal) {
+  EXPECT_TRUE(
+      content::NavigateToURL(web_contents(), LocalSecureURL(https_server())));
+
+  WebFeatureHistogramTester feature_histogram_tester;
+
+  EXPECT_EQ(true, content::EvalJs(web_contents(), R"(
+    navigator.serviceWorker.register('/service_worker/empty.js')
+      .then(navigator.serviceWorker.ready)
+      .then(() => true);
+  )"));
+
+  feature_histogram_tester.ExpectCounts(
+      AllZeroFeatureCounts(AllAddressSpaceFeatures()));
+}
+
+// Test the experimental use counter for accesses to the 0.0.0.0 IP address
+// (and the corresponding `[::]` IPv6 address).
+//
+// In the Internet Protocol Version 4, the address 0.0.0.0 is a non-routable
+// meta-address used to designate an invalid, unknown or non-applicable target.
+// The real life behavior for 0.0.0.0 is different between operating systems.
+// On Windows, it is unreachable, while on MacOS and Linux, 0.0.0.0 means
+// all IP addresses on the local machine.
+//
+// In this case, 0.0.0.0 can be used to access localhost on MacOS and Linux
+// and bypass Local Network Access checks, so that we would like to forbid
+// fetches to 0.0.0.0. See more: https://crbug.com/1300021
+#if BUILDFLAG(IS_WIN)
+#define MAYBE_FetchNullIpAddressForNavigation \
+  DISABLED_FetchNullIpAddressForNavigation
+#else
+#define MAYBE_FetchNullIpAddressForNavigation FetchNullIpAddressForNavigation
+#endif
+IN_PROC_BROWSER_TEST_F(LocalNetworkAccessCountersBrowserTest,
+                       MAYBE_FetchNullIpAddressForNavigation) {
+  WebFeatureHistogramTester feature_histogram_tester;
+
+  EXPECT_TRUE(content::NavigateToURL(
+      web_contents(), https_server().GetURL("0.0.0.0", kNoFaviconPath)));
+
+  feature_histogram_tester.ExpectCounts(
+      AddFeatureCounts(AllZeroFeatureCounts(AllAddressSpaceFeatures()),
+                       {
+                           {WebFeature::kPrivateNetworkAccessNullIpAddress, 1},
+                       }));
+}
+
+#if BUILDFLAG(IS_WIN)
+#define MAYBE_FetchNullIpAddressFromDocument \
+  DISABLED_FetchNullIpAddressFromDocument
+#else
+#define MAYBE_FetchNullIpAddressFromDocument FetchNullIpAddressFromDocument
+#endif
+IN_PROC_BROWSER_TEST_F(LocalNetworkAccessCountersBrowserTest,
+                       MAYBE_FetchNullIpAddressFromDocument) {
+  WebFeatureHistogramTester feature_histogram_tester;
+
+  EXPECT_TRUE(content::NavigateToURL(web_contents(),
+                                     https_server().GetURL(kNoFaviconPath)));
+
+  auto subresource_url = https_server().GetURL(
+      "0.0.0.0", "/set-header?Access-Control-Allow-Origin: *");
+  constexpr char kSubresourceScript[] = R"(
+    new Promise(resolve => {
+      fetch($1).then(e => resolve(true));
+    }))";
+  EXPECT_EQ(true, content::EvalJs(
+                      web_contents(),
+                      content::JsReplace(kSubresourceScript, subresource_url)));
+
+  feature_histogram_tester.ExpectCounts(
+      AddFeatureCounts(AllZeroFeatureCounts(AllAddressSpaceFeatures()),
+                       {
+                           {WebFeature::kPrivateNetworkAccessNullIpAddress, 1},
+                       }));
+}
+
+#if BUILDFLAG(IS_WIN)
+#define MAYBE_FetchNullIpAddressFromWorker DISABLED_FetchNullIpAddressFromWorker
+#else
+#define MAYBE_FetchNullIpAddressFromWorker FetchNullIpAddressFromWorker
+#endif
+IN_PROC_BROWSER_TEST_F(LocalNetworkAccessCountersBrowserTest,
+                       MAYBE_FetchNullIpAddressFromWorker) {
+  WebFeatureHistogramTester feature_histogram_tester;
+
+  EXPECT_TRUE(content::NavigateToURL(
+      web_contents(),
+      https_server().GetURL("/workers/fetch_from_worker.html")));
+
+  constexpr char kWorkerScript[] = R"(
+    new Promise(resolve => {
+      fetch_from_worker($1);
+      resolve(true);
+    }))";
+  auto worker_url = https_server().GetURL(
+      "0.0.0.0", "/set-header?Access-Control-Allow-Origin: *");
+  EXPECT_EQ(true,
+            content::EvalJs(web_contents(),
+                            content::JsReplace(kWorkerScript, worker_url)));
+
+  feature_histogram_tester.ExpectCounts(
+      AddFeatureCounts(AllZeroFeatureCounts(AllAddressSpaceFeatures()),
+                       {
+                           {WebFeature::kPrivateNetworkAccessNullIpAddress, 1},
+                       }));
+}
+
+}  // namespace local_network_access
diff --git a/chrome/browser/media/android/media_capture_picker_manager_bridge.cc b/chrome/browser/media/android/media_capture_picker_manager_bridge.cc
index 0e918f29..7f3c944 100644
--- a/chrome/browser/media/android/media_capture_picker_manager_bridge.cc
+++ b/chrome/browser/media/android/media_capture_picker_manager_bridge.cc
@@ -50,7 +50,8 @@
       static_cast<int>(params.window_audio_preference),
       static_cast<int>(params.preferred_display_surface),
       params.capture_this_tab, params.exclude_self_browser_surface,
-      params.exclude_monitor_type_surfaces);
+      params.exclude_monitor_type_surfaces,
+      static_cast<int>(params.allowed_capture_level));
 }
 
 void MediaCapturePickerManagerBridge::OnPickTab(
diff --git a/chrome/browser/media/router/discovery/mdns/cast_media_sink_service_impl.cc b/chrome/browser/media/router/discovery/mdns/cast_media_sink_service_impl.cc
index 7500188..5d20109f 100644
--- a/chrome/browser/media/router/discovery/mdns/cast_media_sink_service_impl.cc
+++ b/chrome/browser/media/router/discovery/mdns/cast_media_sink_service_impl.cc
@@ -462,8 +462,6 @@
                  kMaxLivenessTimeoutInSeconds);
   }
 
-  // TODO(crbug.com/41371793): Switching cast socket implementation to use
-  // network service will allow us to get back NetLog.
   return cast_channel::CastSocketOpenParams(
       sink.cast_data().ip_endpoint, base::Seconds(connect_timeout_in_seconds),
       base::Seconds(liveness_timeout_in_seconds),
diff --git a/chrome/browser/media/router/providers/cast/cast_internal_message_util.h b/chrome/browser/media/router/providers/cast/cast_internal_message_util.h
index beeb27a..5a95693 100644
--- a/chrome/browser/media/router/providers/cast/cast_internal_message_util.h
+++ b/chrome/browser/media/router/providers/cast/cast_internal_message_util.h
@@ -44,7 +44,6 @@
 // PresentationConnection.
 class CastInternalMessage {
  public:
-  // TODO(crbug.com/40561499): Add other types of messages.
   enum class Type {
     kClientConnect,   // Initial message sent by SDK client to connect to MRP.
     kAppMessage,      // App messages to pass through between SDK client and the
diff --git a/chrome/browser/media/router/providers/cast/cast_media_route_provider.cc b/chrome/browser/media/router/providers/cast/cast_media_route_provider.cc
index 6e2bf03..cd009eb 100644
--- a/chrome/browser/media/router/providers/cast/cast_media_route_provider.cc
+++ b/chrome/browser/media/router/providers/cast/cast_media_route_provider.cc
@@ -191,8 +191,6 @@
                                          CreateRouteCallback callback) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
-  // TODO(crbug.com/40561499): Handle mirroring routes, including
-  // mirror-to-Cast transitions.
   const MediaSinkInternal* sink = media_sink_service_->GetSinkById(sink_id);
   if (!sink) {
     logger_->LogError(mojom::LogCategory::kRoute, kLoggerComponent,
diff --git a/chrome/browser/media/webrtc/BUILD.gn b/chrome/browser/media/webrtc/BUILD.gn
index 5352bed..06b7f16a 100644
--- a/chrome/browser/media/webrtc/BUILD.gn
+++ b/chrome/browser/media/webrtc/BUILD.gn
@@ -323,3 +323,11 @@
     "//testing/gmock",
   ]
 }
+
+if (is_android) {
+  import("//build/config/android/rules.gni")
+
+  java_cpp_enum("java_enums") {
+    sources = [ "capture_policy_utils.h" ]
+  }
+}
diff --git a/chrome/browser/media/webrtc/capture_policy_utils.h b/chrome/browser/media/webrtc/capture_policy_utils.h
index e115997..0d2c583c 100644
--- a/chrome/browser/media/webrtc/capture_policy_utils.h
+++ b/chrome/browser/media/webrtc/capture_policy_utils.h
@@ -22,13 +22,17 @@
 // restrictive to least restrictive, to which capture may be restricted by
 // enterprise policy. It should not be used in Logs, so that it's order may be
 // changed as needed.
+// GENERATED_JAVA_ENUM_PACKAGE: org.chromium.chrome.browser.media
+// GENERATED_JAVA_PREFIX_TO_STRIP: k
 enum class AllowedScreenCaptureLevel {
   kDisallowed = 0,
   kSameOrigin = 1,
   kTab = 2,
   kWindow = 3,
   kDesktop = 4,
-  kUnrestricted = kDesktop,
+  // kUnrestricted should be set to the value of the most permissive element
+  // (kDesktop), but using the actual symbol breaks Java enum generation.
+  kUnrestricted = 4,
 };
 
 namespace capture_policy {
diff --git a/chrome/browser/media/webrtc/desktop_capture_access_handler.cc b/chrome/browser/media/webrtc/desktop_capture_access_handler.cc
index 7fc6e86..d17483d 100644
--- a/chrome/browser/media/webrtc/desktop_capture_access_handler.cc
+++ b/chrome/browser/media/webrtc/desktop_capture_access_handler.cc
@@ -412,23 +412,29 @@
   content::RenderFrameHost* const main_frame =
       web_contents_for_stream ? web_contents_for_stream->GetPrimaryMainFrame()
                               : nullptr;
-  if (main_frame) {
-    // This function would have already returned if this vector was empty.
-    CHECK(!request.requested_video_device_ids.empty());
-    media_id =
-        content::DesktopStreamsRegistry::GetInstance()->RequestMediaForStreamId(
-            request.requested_video_device_ids.front(),
-            main_frame->GetProcess()->GetDeprecatedID(),
-            main_frame->GetRoutingID(),
-            url::Origin::Create(request.security_origin),
-            content::kRegistryStreamTypeDesktop);
+  if (!main_frame) {
+    std::move(pending_request->callback)
+        .Run(blink::mojom::StreamDevicesSet(),
+             MediaStreamRequestResult::FAILED_DUE_TO_SHUTDOWN,
+             /*ui=*/nullptr);
+    return;
   }
 
+  // This function would have already returned if this vector was empty.
+  CHECK(!request.requested_video_device_ids.empty());
+  media_id =
+      content::DesktopStreamsRegistry::GetInstance()->RequestMediaForStreamId(
+          request.requested_video_device_ids.front(),
+          main_frame->GetProcess()->GetDeprecatedID(),
+          main_frame->GetRoutingID(),
+          url::Origin::Create(request.security_origin),
+          content::kRegistryStreamTypeDesktop);
+
   // Received invalid device id.
   if (media_id.type == content::DesktopMediaID::TYPE_NONE) {
     std::move(pending_request->callback)
         .Run(blink::mojom::StreamDevicesSet(),
-             MediaStreamRequestResult::INVALID_STATE,
+             MediaStreamRequestResult::STREAM_NOT_FOUND_IN_REGISTRY,
              /*ui=*/nullptr);
     return;
   }
@@ -603,6 +609,7 @@
       pending_request.request.exclude_self_browser_surface;
   picker_params.exclude_monitor_type_surfaces =
       pending_request.request.exclude_monitor_type_surfaces;
+  picker_params.allowed_capture_level = capture_level;
   picker_params.includable_web_contents_filter = includable_web_contents_filter;
 #endif
 
diff --git a/chrome/browser/media/webrtc/desktop_media_picker.h b/chrome/browser/media/webrtc/desktop_media_picker.h
index 9482b45..ae140c37 100644
--- a/chrome/browser/media/webrtc/desktop_media_picker.h
+++ b/chrome/browser/media/webrtc/desktop_media_picker.h
@@ -125,6 +125,9 @@
     bool exclude_self_browser_surface = false;
     // On Android, this indicates that screen sharing should be excluded.
     bool exclude_monitor_type_surfaces = false;
+    // On Android, this indicates the allowed capture level for this request.
+    AllowedScreenCaptureLevel allowed_capture_level =
+        AllowedScreenCaptureLevel::kUnrestricted;
     // On Android, this filter is used to filter the tabs that can be captured.
     DesktopMediaList::WebContentsFilter includable_web_contents_filter;
 #endif
diff --git a/chrome/browser/media/webrtc/display_media_access_handler.cc b/chrome/browser/media/webrtc/display_media_access_handler.cc
index 777d0fe..16e728c1 100644
--- a/chrome/browser/media/webrtc/display_media_access_handler.cc
+++ b/chrome/browser/media/webrtc/display_media_access_handler.cc
@@ -597,6 +597,7 @@
       pending_request.request.exclude_self_browser_surface;
   picker_params.exclude_monitor_type_surfaces =
       pending_request.request.exclude_monitor_type_surfaces;
+  picker_params.allowed_capture_level = capture_level;
   picker_params.includable_web_contents_filter = includable_web_contents_filter;
 #endif
 
diff --git a/chrome/browser/media/webrtc/webrtc_event_log_history.cc b/chrome/browser/media/webrtc/webrtc_event_log_history.cc
index 1a72a60..cba4d25 100644
--- a/chrome/browser/media/webrtc/webrtc_event_log_history.cc
+++ b/chrome/browser/media/webrtc/webrtc_event_log_history.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/media/webrtc/webrtc_event_log_history.h"
 
 #include <limits>
+#include <optional>
 #include <utility>
 #include <vector>
 
@@ -310,14 +311,13 @@
 
   std::string file_contents;
   file_contents.resize(kMaxHistoryFileSizeBytes);
-  const int read_bytes =
-      UNSAFE_TODO(file.Read(0, &file_contents[0], file_contents.size()));
-  if (read_bytes < 0) {
+  const std::optional<size_t> read_bytes =
+      file.Read(0, base::as_writable_byte_span(file_contents));
+  if (!read_bytes) {
     LOG(WARNING) << "Couldn't read contents of history file.";
     return false;
   }
-  DCHECK_LE(static_cast<size_t>(read_bytes), file_contents.size());
-  file_contents.resize(static_cast<size_t>(read_bytes));
+  file_contents.resize(*read_bytes);
   // Note: In excessively long files, the rest of the file will be ignored; the
   // beginning of the file will encounter a parse error.
 
diff --git a/chrome/browser/media/webrtc/webrtc_event_log_manager_unittest.cc b/chrome/browser/media/webrtc/webrtc_event_log_manager_unittest.cc
index 1de810e8..2c6f5290 100644
--- a/chrome/browser/media/webrtc/webrtc_event_log_manager_unittest.cc
+++ b/chrome/browser/media/webrtc/webrtc_event_log_manager_unittest.cc
@@ -4347,9 +4347,6 @@
 // ProfileDefaultsToLoggingEnabledTestCase in
 // webrtc_event_log_manager_common_unittest because the test setup in this
 // class currently does not seem to allow for an easy setup of some user types.
-// TODO(crbug.com/1035829): Figure out whether this can be resolved by tweaking
-// the test setup or whether the Active Directory services need to be adapted
-// for easy testing.
 #if BUILDFLAG(IS_CHROMEOS)
 TEST_F(WebRtcEventLogManagerTestPolicy,
        ManagedProfileDoesNotAllowRemoteLoggingForSupervisedProfiles) {
diff --git a/chrome/browser/net/private_network_access_browsertest.cc b/chrome/browser/net/private_network_access_browsertest.cc
deleted file mode 100644
index 074dfed..0000000
--- a/chrome/browser/net/private_network_access_browsertest.cc
+++ /dev/null
@@ -1,1298 +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 <map>
-#include <string>
-#include <string_view>
-
-#include "base/files/file_util.h"
-#include "base/functional/bind.h"
-#include "base/test/metrics/histogram_tester.h"
-#include "base/test/scoped_feature_list.h"
-#include "base/test/values_test_util.h"
-#include "chrome/browser/extensions/extension_service.h"
-#include "chrome/browser/extensions/test_extension_system.h"
-#include "chrome/browser/net/profile_network_context_service.h"
-#include "chrome/browser/net/profile_network_context_service_factory.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/ui/browser.h"
-#include "chrome/common/url_constants.h"
-#include "chrome/test/base/in_process_browser_test.h"
-#include "chrome/test/base/web_feature_histogram_tester.h"
-#include "components/embedder_support/switches.h"
-#include "components/error_page/content/browser/net_error_auto_reloader.h"
-#include "components/metrics/content/subprocess_metrics_provider.h"
-#include "components/proxy_config/proxy_config_dictionary.h"
-#include "components/proxy_config/proxy_config_pref_names.h"
-#include "content/public/browser/web_contents.h"
-#include "content/public/common/content_features.h"
-#include "content/public/common/url_constants.h"
-#include "content/public/test/browser_test.h"
-#include "content/public/test/browser_test_utils.h"
-#include "content/public/test/content_browser_test_utils.h"
-#include "content/public/test/test_navigation_observer.h"
-#include "content/public/test/url_loader_interceptor.h"
-#include "extensions/browser/install_verifier.h"
-#include "extensions/common/constants.h"
-#include "extensions/common/extension_builder.h"
-#include "net/dns/mock_host_resolver.h"
-#include "net/test/embedded_test_server/embedded_test_server.h"
-#include "services/network/public/cpp/features.h"
-#include "services/network/public/cpp/network_switches.h"
-#include "services/network/public/cpp/private_network_access_check_result.h"
-#include "services/network/public/cpp/url_loader_completion_status.h"
-#include "testing/gmock/include/gmock/gmock.h"
-#include "third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom.h"
-
-namespace {
-
-using blink::mojom::WebFeature;
-using testing::IsEmpty;
-
-// We use a custom page that explicitly disables its own favicon (by providing
-// an invalid data: URL for it) so as to prevent the browser from making an
-// automatic request to /favicon.ico. This is because the automatic request
-// messes with our tests, in which we want to trigger a single request from the
-// web page to a resource of our choice and observe the side-effect in metrics.
-constexpr char kNoFaviconPath[] = "/local_network_access/no-favicon.html";
-
-// Same as kNoFaviconPath, except it carries a header that makes the browser
-// consider it came from the `public` address space, irrespective of the fact
-// that we loaded the web page from localhost.
-constexpr char kTreatAsPublicAddressPath[] =
-    "/local_network_access/no-favicon-treat-as-public-address.html";
-
-GURL SecureURL(const net::EmbeddedTestServer& server, const std::string& path) {
-  // Test HTTPS servers cannot lie about their hostname, so they yield URLs
-  // starting with https://localhost. http://localhost is already a secure
-  // context, so we do not bother instantiating an HTTPS server.
-  return server.GetURL(path);
-}
-
-GURL NonSecureURL(const net::EmbeddedTestServer& server,
-                  const std::string& path) {
-  return server.GetURL("foo.test", path);
-}
-
-GURL LocalSecureURL(const net::EmbeddedTestServer& server) {
-  return SecureURL(server, kNoFaviconPath);
-}
-
-GURL LocalNonSecureURL(const net::EmbeddedTestServer& server) {
-  return NonSecureURL(server, kNoFaviconPath);
-}
-
-GURL PublicSecureURL(const net::EmbeddedTestServer& server) {
-  return SecureURL(server, kTreatAsPublicAddressPath);
-}
-
-GURL PublicNonSecureURL(const net::EmbeddedTestServer& server) {
-  return NonSecureURL(server, kTreatAsPublicAddressPath);
-}
-
-// Similar to LocalNonSecure() but can be fetched by any origin.
-GURL LocalNonSecureWithCrossOriginCors(const net::EmbeddedTestServer& server) {
-  return SecureURL(server, "/cors-ok.txt");
-}
-
-// Path to a worker script that posts a message to its creator once loaded.
-constexpr char kWorkerScriptPath[] = "/workers/post_ready.js";
-
-// The returned script evaluates to a boolean indicating whether the fetch
-// succeeded or not.
-std::string FetchScript(const GURL& url) {
-  return content::JsReplace(
-      "fetch($1).then(response => true).catch(error => false)", url);
-}
-
-std::string FetchWorkerScript(std::string_view relative_url) {
-  constexpr char kTemplate[] = R"(
-    new Promise((resolve) => {
-      const worker = new Worker($1);
-      worker.addEventListener("message", () => { resolve(true); });
-      worker.addEventListener("error", () => { resolve(false); });
-    });
-  )";
-  return content::JsReplace(kTemplate, relative_url);
-}
-
-// Path to a worker script that posts a message to each client that connects.
-constexpr char kSharedWorkerScriptPath[] = "/workers/shared_post_ready.js";
-
-// Instantiates a shared worker script from `path`.
-// If it loads successfully, the worker should post a message to each client
-// that connects to it to signal success.
-std::string FetchSharedWorkerScript(std::string_view path) {
-  constexpr char kTemplate[] = R"(
-    new Promise((resolve) => {
-      const worker = new SharedWorker($1);
-      worker.port.addEventListener("message", () => resolve(true));
-      worker.addEventListener("error", () => resolve(false));
-      worker.port.start();
-    })
-  )";
-
-  return content::JsReplace(kTemplate, path);
-}
-
-std::vector<WebFeature> AllAddressSpaceFeatures() {
-  return {
-      WebFeature::kAddressSpaceLocalSecureContextEmbeddedLoopbackV2,
-      WebFeature::kAddressSpaceLocalNonSecureContextEmbeddedLoopbackV2,
-      WebFeature::kAddressSpacePublicSecureContextEmbeddedLoopbackV2,
-      WebFeature::kAddressSpacePublicNonSecureContextEmbeddedLoopbackV2,
-      WebFeature::kAddressSpaceUnknownSecureContextEmbeddedLoopbackV2,
-      WebFeature::kAddressSpaceUnknownNonSecureContextEmbeddedLoopbackV2,
-      WebFeature::kAddressSpacePublicSecureContextEmbeddedLocalV2,
-      WebFeature::kAddressSpacePublicNonSecureContextEmbeddedLocalV2,
-      WebFeature::kAddressSpaceUnknownSecureContextEmbeddedLocalV2,
-      WebFeature::kAddressSpaceUnknownNonSecureContextEmbeddedLocalV2,
-      WebFeature::kAddressSpaceLocalSecureContextNavigatedToLoopbackV2,
-      WebFeature::kAddressSpaceLocalNonSecureContextNavigatedToLoopbackV2,
-      WebFeature::kAddressSpacePublicSecureContextNavigatedToLoopbackV2,
-      WebFeature::kAddressSpacePublicNonSecureContextNavigatedToLoopbackV2,
-      WebFeature::kAddressSpaceUnknownSecureContextNavigatedToLoopbackV2,
-      WebFeature::kAddressSpaceUnknownNonSecureContextNavigatedToLoopbackV2,
-      WebFeature::kAddressSpacePublicSecureContextNavigatedToLocalV2,
-      WebFeature::kAddressSpacePublicNonSecureContextNavigatedToLocalV2,
-      WebFeature::kAddressSpaceUnknownSecureContextNavigatedToLocalV2,
-      WebFeature::kAddressSpaceUnknownNonSecureContextNavigatedToLocalV2,
-      WebFeature::kPrivateNetworkAccessFetchedWorkerScript,
-      WebFeature::kPrivateNetworkAccessFetchedSubFrame,
-      WebFeature::kPrivateNetworkAccessFetchedTopFrame,
-      WebFeature::kPrivateNetworkAccessWithinWorker,
-  };
-}
-
-// Private Network Access is a web platform specification aimed at securing
-// requests made from public websites to the private network and localhost.
-//
-// It is mostly implemented in content/, but some of its integrations (
-// (with Blink UseCounters, with chrome/-specific special schemes) cannot be
-// tested in content/, however, thus we define this standalone test here.
-//
-// See also:
-//
-//  - specification: https://wicg.github.io/private-network-access.
-//  - feature browsertests:
-//    //content/browser/renderer_host/private_network_access_browsertest.cc
-//
-class PrivateNetworkAccessBrowserTestBase : public InProcessBrowserTest {
- public:
-  PrivateNetworkAccessBrowserTestBase(
-      std::vector<base::test::FeatureRef> enabled_features,
-      std::vector<base::test::FeatureRef> disabled_features) {
-    features_.InitWithFeatures(enabled_features, disabled_features);
-  }
-
-  content::WebContents* web_contents() {
-    return browser()->tab_strip_model()->GetActiveWebContents();
-  }
-
-  // Never returns nullptr. The returned server is already Start()ed.
-  //
-  // NOTE: This is defined as a method on the test fixture instead of a free
-  // function because GetChromeTestDataDir() is a test fixture method itself.
-  // We return a unique_ptr because EmbeddedTestServer is not movable and C++17
-  // support is not available at time of writing.
-  std::unique_ptr<net::EmbeddedTestServer> NewServer(
-      net::EmbeddedTestServer::Type server_type =
-          net::EmbeddedTestServer::TYPE_HTTP) {
-    std::unique_ptr<net::EmbeddedTestServer> server =
-        std::make_unique<net::EmbeddedTestServer>(server_type);
-    server->AddDefaultHandlers(GetChromeTestDataDir());
-    EXPECT_TRUE(server->Start());
-    return server;
-  }
-
- protected:
-  void SetUpOnMainThread() override {
-    InProcessBrowserTest::SetUpOnMainThread();
-    host_resolver()->AddRule("*", "127.0.0.1");
-  }
-
-  void SetUpCommandLine(base::CommandLine* command_line) override {
-    // The public key used to verify test trial tokens.
-    // See: //docs/origin_trial_integration.md
-    constexpr char kOriginTrialTestPublicKey[] =
-        "dRCs+TocuKkocNKa0AtZ4awrt9XKH2SQCI6o4FY6BNA=";
-    command_line->AppendSwitchASCII(embedder_support::kOriginTrialPublicKey,
-                                    kOriginTrialTestPublicKey);
-    // Clear default from InProcessBrowserTest as test doesn't want 127.0.0.1 in
-    // the public address space
-    command_line->AppendSwitchASCII(network::switches::kIpAddressSpaceOverrides,
-                                    "");
-  }
-
- private:
-  base::test::ScopedFeatureList features_;
-};
-
-class PrivateNetworkAccessWithFeatureDisabledBrowserTest
-    : public PrivateNetworkAccessBrowserTestBase {
- public:
-  PrivateNetworkAccessWithFeatureDisabledBrowserTest()
-      : PrivateNetworkAccessBrowserTestBase(
-            {},
-            {
-                features::kBlockInsecurePrivateNetworkRequests,
-                features::kBlockInsecurePrivateNetworkRequestsFromPrivate,
-                network::features::kLocalNetworkAccessChecks,
-            }) {}
-};
-
-struct IsWarningOnlyTestData {
-  bool is_warning_only;
-};
-
-class PrivateNetworkAccessWithFeatureEnabledBrowserTest
-    : public PrivateNetworkAccessBrowserTestBase {
- public:
-  explicit PrivateNetworkAccessWithFeatureEnabledBrowserTest(
-      bool is_warning_only = false)
-      : PrivateNetworkAccessBrowserTestBase(
-            {
-                features::kBlockInsecurePrivateNetworkRequests,
-                features::kBlockInsecurePrivateNetworkRequestsFromPrivate,
-            },
-            {
-                network::features::kLocalNetworkAccessChecks,
-            }) {}
-};
-
-// ================
-// USECOUNTER TESTS
-// ================
-//
-// UseCounters are translated into UMA histograms at the chrome/ layer, by the
-// page_load_metrics component. These tests verify that UseCounters are recorded
-// correctly by Private Network Access code in the right circumstances.
-
-// This test verifies that no feature is counted for the initial navigation from
-// a new tab to a page served by localhost.
-//
-// Regression test for https://crbug.com/1134601.
-IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureDisabledBrowserTest,
-                       DoesNotRecordAddressSpaceFeatureForInitialNavigation) {
-  WebFeatureHistogramTester feature_histogram_tester;
-  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();
-
-  EXPECT_TRUE(content::NavigateToURL(web_contents(), PublicSecureURL(*server)));
-
-  EXPECT_THAT(
-      feature_histogram_tester.GetNonZeroCounts(AllAddressSpaceFeatures()),
-      IsEmpty());
-}
-
-// This test verifies that no feature is counted for top-level navigations from
-// a public page to a local page.
-//
-// TODO(crbug.com/40149351): Revisit this once the story around top-level
-// navigations is closer to being resolved. Counting these events will help
-// decide what to do.
-IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureDisabledBrowserTest,
-                       DoesNotRecordAddressSpaceFeatureForRegularNavigation) {
-  WebFeatureHistogramTester feature_histogram_tester;
-  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();
-
-  EXPECT_TRUE(content::NavigateToURL(web_contents(), PublicSecureURL(*server)));
-  EXPECT_TRUE(content::NavigateToURL(web_contents(), LocalSecureURL(*server)));
-
-  EXPECT_THAT(
-      feature_histogram_tester.GetNonZeroCounts(AllAddressSpaceFeatures()),
-      IsEmpty());
-}
-
-// This test verifies that when a non-secure context served from the public
-// address space loads a resource from the private network, the correct
-// WebFeature
-// is use-counted.
-IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureDisabledBrowserTest,
-                       RecordsAddressSpaceFeatureForFetchInNonSecureContext) {
-  WebFeatureHistogramTester feature_histogram_tester;
-  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();
-
-  EXPECT_TRUE(
-      content::NavigateToURL(web_contents(), PublicNonSecureURL(*server)));
-  EXPECT_THAT(
-      feature_histogram_tester.GetNonZeroCounts(AllAddressSpaceFeatures()),
-      IsEmpty());
-
-  EXPECT_EQ(true, content::EvalJs(web_contents(), R"(
-    fetch("/defaultresponse").then(response => response.ok)
-  )"));
-
-  feature_histogram_tester.ExpectCounts(AddFeatureCounts(
-      AllZeroFeatureCounts(AllAddressSpaceFeatures()),
-      {
-          {WebFeature::kAddressSpacePublicNonSecureContextEmbeddedLocalV2, 1},
-      }));
-}
-
-// This test verifies that when the user navigates a `public` document to a
-// document served by a non-public IP, no address space feature is recorded.
-IN_PROC_BROWSER_TEST_F(
-    PrivateNetworkAccessWithFeatureEnabledBrowserTest,
-    DoesNotRecordAddressSpaceFeatureForBrowserInitiatedNavigation) {
-  WebFeatureHistogramTester feature_histogram_tester;
-  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();
-
-  EXPECT_TRUE(
-      content::NavigateToURL(web_contents(), PublicNonSecureURL(*server)));
-
-  EXPECT_TRUE(
-      content::NavigateToURL(web_contents(), LocalNonSecureURL(*server)));
-
-  EXPECT_THAT(
-      feature_histogram_tester.GetNonZeroCounts(AllAddressSpaceFeatures()),
-      IsEmpty());
-}
-
-// This test verifies that when a `public` document navigates itself to a
-// document served by a non-public IP, the correct address space feature is
-// recorded.
-IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureDisabledBrowserTest,
-                       RecordsAddressSpaceFeatureForNavigation) {
-  WebFeatureHistogramTester feature_histogram_tester;
-  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();
-
-  EXPECT_TRUE(
-      content::NavigateToURL(web_contents(), PublicNonSecureURL(*server)));
-  EXPECT_THAT(
-      feature_histogram_tester.GetNonZeroCounts(AllAddressSpaceFeatures()),
-      IsEmpty());
-
-  EXPECT_TRUE(content::NavigateToURLFromRenderer(web_contents(),
-                                                 LocalNonSecureURL(*server)));
-
-  feature_histogram_tester.ExpectCounts(AddFeatureCounts(
-      AllZeroFeatureCounts(AllAddressSpaceFeatures()),
-      {
-          {WebFeature::kAddressSpacePublicNonSecureContextNavigatedToLocalV2,
-           1},
-          {WebFeature::kPrivateNetworkAccessFetchedTopFrame, 1},
-      }));
-}
-
-// This test verifies that when a `public` document navigates itself to a
-// document served by a non-public IP, the correct address space feature is
-// recorded, even if the target document carries a CSP `treat-as-public-address`
-// directive.
-IN_PROC_BROWSER_TEST_F(
-    PrivateNetworkAccessWithFeatureDisabledBrowserTest,
-    RecordsAddressSpaceFeatureForNavigationToTreatAsPublicAddress) {
-  WebFeatureHistogramTester feature_histogram_tester;
-  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();
-
-  EXPECT_TRUE(
-      content::NavigateToURL(web_contents(), PublicNonSecureURL(*server)));
-  EXPECT_THAT(
-      feature_histogram_tester.GetNonZeroCounts(AllAddressSpaceFeatures()),
-      IsEmpty());
-
-  // Navigate to a different URL with the same CSP directive. If we just tried
-  // to navigate to `PublicNonSecureURL(*server)`, nothing would happen.
-  EXPECT_TRUE(content::NavigateToURLFromRenderer(
-      web_contents(),
-      NonSecureURL(
-          *server,
-          "/set-header?Content-Security-Policy: treat-as-public-address")));
-
-  feature_histogram_tester.ExpectCounts(AddFeatureCounts(
-      AllZeroFeatureCounts(AllAddressSpaceFeatures()),
-      {
-          {WebFeature::kAddressSpacePublicNonSecureContextNavigatedToLocalV2,
-           1},
-          {WebFeature::kPrivateNetworkAccessFetchedTopFrame, 1},
-      }));
-}
-
-// This test verifies that when a page embeds an empty iframe pointing to
-// about:blank, no address space feature is recorded. It serves as a basis for
-// comparison with the following tests, which test behavior with iframes.
-IN_PROC_BROWSER_TEST_F(
-    PrivateNetworkAccessWithFeatureDisabledBrowserTest,
-    DoesNotRecordAddressSpaceFeatureForChildAboutBlankNavigation) {
-  WebFeatureHistogramTester feature_histogram_tester;
-  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();
-
-  EXPECT_TRUE(
-      content::NavigateToURL(web_contents(), PublicNonSecureURL(*server)));
-  EXPECT_EQ(true, content::EvalJs(web_contents(), R"(
-    new Promise(resolve => {
-      const child = document.createElement("iframe");
-      child.src = "about:blank";
-      child.onload = () => { resolve(true); };
-      document.body.appendChild(child);
-    })
-  )"));
-
-  EXPECT_THAT(
-      feature_histogram_tester.GetNonZeroCounts(AllAddressSpaceFeatures()),
-      IsEmpty());
-}
-
-// This test verifies that when a non-secure context served from the public
-// address space loads a child frame from the private network, the correct
-// WebFeature is use-counted.
-IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureDisabledBrowserTest,
-                       RecordsAddressSpaceFeatureForChildNavigation) {
-  WebFeatureHistogramTester feature_histogram_tester;
-  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();
-
-  EXPECT_TRUE(
-      content::NavigateToURL(web_contents(), PublicNonSecureURL(*server)));
-  EXPECT_THAT(
-      feature_histogram_tester.GetNonZeroCounts(AllAddressSpaceFeatures()),
-      IsEmpty());
-
-  std::string_view script_template = R"(
-    new Promise(resolve => {
-      const child = document.createElement("iframe");
-      child.src = $1;
-      child.onload = () => { resolve(true); };
-      document.body.appendChild(child);
-    })
-  )";
-  EXPECT_EQ(true,
-            content::EvalJs(web_contents(),
-                            content::JsReplace(script_template,
-                                               LocalNonSecureURL(*server))));
-
-  feature_histogram_tester.ExpectCounts(AddFeatureCounts(
-      AllZeroFeatureCounts(AllAddressSpaceFeatures()),
-      {
-          {WebFeature::kAddressSpacePublicNonSecureContextNavigatedToLocalV2,
-           1},
-          {WebFeature::kPrivateNetworkAccessFetchedSubFrame, 1},
-      }));
-}
-
-// This test verifies that when a non-secure context served from the public
-// address space loads a grand-child frame from the private network, the correct
-// WebFeature is use-counted. If inheritance did not work correctly, the
-// intermediate about:blank frame might confuse the address space logic.
-IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureDisabledBrowserTest,
-                       RecordsAddressSpaceFeatureForGrandchildNavigation) {
-  WebFeatureHistogramTester feature_histogram_tester;
-  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();
-
-  EXPECT_TRUE(
-      content::NavigateToURL(web_contents(), PublicNonSecureURL(*server)));
-  EXPECT_THAT(
-      feature_histogram_tester.GetNonZeroCounts(AllAddressSpaceFeatures()),
-      IsEmpty());
-
-  std::string_view script_template = R"(
-    function addChildFrame(doc, src) {
-      return new Promise(resolve => {
-        const child = doc.createElement("iframe");
-        child.src = src;
-        child.onload = () => { resolve(child); };
-        doc.body.appendChild(child);
-      });
-    }
-
-    addChildFrame(document, "about:blank")
-      .then(child => addChildFrame(child.contentDocument, $1))
-      .then(grandchild =>  true);
-  )";
-  EXPECT_EQ(true,
-            content::EvalJs(web_contents(),
-                            content::JsReplace(script_template,
-                                               LocalNonSecureURL(*server))));
-
-  feature_histogram_tester.ExpectCounts(AddFeatureCounts(
-      AllZeroFeatureCounts(AllAddressSpaceFeatures()),
-      {
-          {WebFeature::kAddressSpacePublicNonSecureContextNavigatedToLocalV2,
-           1},
-          {WebFeature::kPrivateNetworkAccessFetchedSubFrame, 1},
-      }));
-}
-
-// This test verifies that the right address space feature is recorded when a
-// navigation results in a private network request. Specifically, in this test
-// the document being navigated is not the one initiating the navigation (the
-// latter being the "remote initiator" referenced by the test name).
-IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureDisabledBrowserTest,
-                       RecordsAddressSpaceFeatureForRemoteInitiatorNavigation) {
-  WebFeatureHistogramTester feature_histogram_tester;
-  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();
-
-  EXPECT_TRUE(content::NavigateToURL(
-      web_contents(),
-      NonSecureURL(*server,
-                   "/local_network_access/remote-initiator-navigation.html")));
-  EXPECT_THAT(
-      feature_histogram_tester.GetNonZeroCounts(AllAddressSpaceFeatures()),
-      IsEmpty());
-
-  EXPECT_EQ(true, content::EvalJs(web_contents(), content::JsReplace(R"(
-    runTest({
-      url: "/defaultresponse",
-    });
-  )")));
-
-  feature_histogram_tester.ExpectCounts(AddFeatureCounts(
-      AllZeroFeatureCounts(AllAddressSpaceFeatures()),
-      {
-          {WebFeature::kAddressSpacePublicNonSecureContextNavigatedToLocalV2,
-           1},
-          {WebFeature::kPrivateNetworkAccessFetchedSubFrame, 1},
-      }));
-}
-
-// This test verifies that when the initiator of a navigation is no longer
-// around by the time the navigation finishes, then no address space feature is
-// recorded, and importantly: the browser does not crash.
-IN_PROC_BROWSER_TEST_F(
-    PrivateNetworkAccessWithFeatureDisabledBrowserTest,
-    DoesNotRecordAddressSpaceFeatureForClosedInitiatorNavigation) {
-  WebFeatureHistogramTester feature_histogram_tester;
-  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();
-
-  EXPECT_TRUE(content::NavigateToURL(
-      web_contents(),
-      NonSecureURL(*server,
-                   "/local_network_access/remote-initiator-navigation.html")));
-
-  EXPECT_EQ(true, content::EvalJs(web_contents(), R"(
-    runTest({
-      url: new URL("/slow?3", window.location).href,
-      initiatorBehavior: "close",
-    });
-  )"));
-
-  EXPECT_THAT(
-      feature_histogram_tester.GetNonZeroCounts(AllAddressSpaceFeatures()),
-      IsEmpty());
-}
-
-// This test verifies that when the initiator of a navigation has already
-// navigated itself by the time the navigation finishes, then no address space
-// feature is recorded.
-IN_PROC_BROWSER_TEST_F(
-    PrivateNetworkAccessWithFeatureDisabledBrowserTest,
-    DoesNotRecordAddressSpaceFeatureForMissingInitiatorNavigation) {
-  WebFeatureHistogramTester feature_histogram_tester;
-  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();
-
-  EXPECT_TRUE(content::NavigateToURL(
-      web_contents(),
-      NonSecureURL(*server,
-                   "/local_network_access/remote-initiator-navigation.html")));
-
-  EXPECT_EQ(true, content::EvalJs(web_contents(), R"(
-    runTest({
-      url: new URL("/slow?3", window.location).href,
-      initiatorBehavior: "navigate",
-    });
-  )"));
-
-  EXPECT_THAT(
-      feature_histogram_tester.GetNonZeroCounts(AllAddressSpaceFeatures()),
-      IsEmpty());
-}
-
-// This test verifies that private network requests that are blocked are not
-// use-counted.
-IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureEnabledBrowserTest,
-                       DoesNotRecordAddressSpaceFeatureForBlockedRequests) {
-  WebFeatureHistogramTester feature_histogram_tester;
-  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();
-
-  EXPECT_TRUE(
-      content::NavigateToURL(web_contents(), PublicNonSecureURL(*server)));
-  EXPECT_THAT(
-      feature_histogram_tester.GetNonZeroCounts(AllAddressSpaceFeatures()),
-      IsEmpty());
-
-  base::HistogramTester base_histogram_tester;
-
-  EXPECT_EQ(true, content::EvalJs(web_contents(), R"(
-    fetch("/defaultresponse").catch(() => true)
-  )"));
-
-  metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
-  base_histogram_tester.ExpectBucketCount(
-      "Security.PrivateNetworkAccess.CheckResult",
-      network::PrivateNetworkAccessCheckResult::kBlockedByPolicyBlock, 1);
-
-  EXPECT_THAT(
-      feature_histogram_tester.GetNonZeroCounts(AllAddressSpaceFeatures()),
-      IsEmpty());
-}
-
-// This test verifies that resources proxied through a proxy on localhost can
-// be fetched from documents in the public IP address space.
-// Regression test for https://crbug.com/1253239.
-IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureEnabledBrowserTest,
-                       ProxiedResourcesAllowed) {
-  auto server = NewServer();
-
-  EXPECT_TRUE(
-      content::NavigateToURL(web_contents(), PublicNonSecureURL(*server)));
-
-  browser()->profile()->GetPrefs()->SetDict(
-      proxy_config::prefs::kProxy,
-      ProxyConfigDictionary::CreateFixedServers(
-          server->host_port_pair().ToString(), ""));
-  ProfileNetworkContextServiceFactory::GetForContext(browser()->profile())
-      ->FlushProxyConfigMonitorForTesting();
-
-  WebFeatureHistogramTester feature_histogram_tester;
-
-  EXPECT_EQ(true, content::EvalJs(web_contents(), R"(
-    fetch("/defaultresponse").then(response => response.ok)
-  )"));
-
-  EXPECT_THAT(
-      feature_histogram_tester.GetNonZeroCounts(AllAddressSpaceFeatures()),
-      IsEmpty());
-}
-
-// This test verifies that resources fetched from cache are subject to Private
-// Network Access checks. When the fetch is blocked, it is not use-counted.
-IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureEnabledBrowserTest,
-                       DoesNotRecordAddressSpaceFeatureForCachedBlocked) {
-  auto server = NewServer();
-
-  EXPECT_TRUE(
-      content::NavigateToURL(web_contents(), LocalNonSecureURL(*server)));
-
-  // Load the resource a first time, to prime the HTTP cache.
-  //
-  // This caching hinges on the fact that `PublicNonSecureURL(*server)` is
-  // same-origin with `LocalNonSecureURL(*server)` (the public one just uses
-  // the `Content-Security-Policy: treat-as-public-address` header). Therefore
-  // both documents share the same cache key.
-  EXPECT_EQ(true, content::EvalJs(web_contents(), R"(
-    fetch("/cachetime").then(response => response.ok)
-  )"));
-
-  EXPECT_TRUE(
-      content::NavigateToURL(web_contents(), PublicNonSecureURL(*server)));
-
-  WebFeatureHistogramTester feature_histogram_tester;
-
-  EXPECT_EQ(false, content::EvalJs(web_contents(), R"(
-    fetch("/cachetime").then(response => true).catch(error => false)
-  )"));
-
-  EXPECT_THAT(
-      feature_histogram_tester.GetNonZeroCounts(AllAddressSpaceFeatures()),
-      IsEmpty());
-}
-
-// This test verifies that resources fetched from cache are subject to Private
-// Network Access checks. When the fetch is allowed, it is use-counted.
-IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureEnabledBrowserTest,
-                       RecordsAddressSpaceFeatureForCachedResource) {
-  auto server = NewServer();
-
-  EXPECT_TRUE(content::NavigateToURL(web_contents(), LocalSecureURL(*server)));
-
-  // Load the resource a first time, to prime the HTTP cache.
-  //
-  // This caching hinges on the fact that `PublicNonSecureURL(*server)` is
-  // same-origin with `LocalNonSecureURL(*server)` (the public one just uses
-  // the `Content-Security-Policy: treat-as-public-address` header). Therefore
-  // both documents share the same cache key.
-  EXPECT_EQ(true, content::EvalJs(web_contents(), R"(
-    fetch("/cachetime").then(response => response.ok)
-  )"));
-
-  EXPECT_TRUE(content::NavigateToURL(web_contents(), PublicSecureURL(*server)));
-
-  WebFeatureHistogramTester feature_histogram_tester;
-
-  EXPECT_EQ(true, content::EvalJs(web_contents(), R"(
-    fetch("/cachetime").then(response => response.ok)
-  )"));
-
-  feature_histogram_tester.ExpectCounts(AddFeatureCounts(
-      AllZeroFeatureCounts(AllAddressSpaceFeatures()),
-      {
-          {WebFeature::kAddressSpacePublicSecureContextEmbeddedLocalV2, 1},
-      }));
-}
-
-// This test verifies that a UseCounter is recorded when a document makes a
-// private network request to load a worker script from a non-secure context,
-// even when the PNA for workers feature is disabled.
-IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureDisabledBrowserTest,
-                       RecordsFeatureForWorkerScriptFetchFromNonSecure) {
-  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();
-
-  EXPECT_TRUE(
-      content::NavigateToURL(web_contents(), PublicNonSecureURL(*server)));
-
-  WebFeatureHistogramTester feature_histogram_tester;
-
-  EXPECT_EQ(true, content::EvalJs(web_contents(),
-                                  FetchWorkerScript(kWorkerScriptPath)));
-
-  feature_histogram_tester.ExpectCounts(AddFeatureCounts(
-      AllZeroFeatureCounts(AllAddressSpaceFeatures()),
-      {
-          {WebFeature::kPrivateNetworkAccessFetchedWorkerScript, 1},
-      }));
-}
-
-// This test verifies that a UseCounter is recorded when a document makes a
-// private network request to load a worker script from a non-secure context,
-// and the request fails due to LNA.
-IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureEnabledBrowserTest,
-                       RecordsFeatureForWorkerScriptFetchFromNonSecure) {
-  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();
-
-  EXPECT_TRUE(
-      content::NavigateToURL(web_contents(), PublicNonSecureURL(*server)));
-
-  WebFeatureHistogramTester feature_histogram_tester;
-
-  EXPECT_EQ(false, content::EvalJs(web_contents(),
-                                   FetchWorkerScript(kWorkerScriptPath)));
-
-  feature_histogram_tester.ExpectCounts(AddFeatureCounts(
-      AllZeroFeatureCounts(AllAddressSpaceFeatures()),
-      {
-          {WebFeature::kPrivateNetworkAccessFetchedWorkerScript, 1},
-      }));
-}
-
-// This test verifies that a UseCounter is recorded when a document makes a
-// private network request to load a worker script from a secure context, even
-// when the PNA for workers feature is disabled.
-IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureDisabledBrowserTest,
-                       RecordsFeatureForWorkerScriptFetchFromSecure) {
-  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();
-
-  EXPECT_TRUE(content::NavigateToURL(web_contents(), PublicSecureURL(*server)));
-
-  WebFeatureHistogramTester feature_histogram_tester;
-
-  EXPECT_EQ(true, content::EvalJs(web_contents(),
-                                  FetchWorkerScript(kWorkerScriptPath)));
-
-  feature_histogram_tester.ExpectCounts(AddFeatureCounts(
-      AllZeroFeatureCounts(AllAddressSpaceFeatures()),
-      {
-          {WebFeature::kPrivateNetworkAccessFetchedWorkerScript, 1},
-      }));
-}
-
-// This test verifies that a UseCounter is recorded when a document makes a
-// private network request to load a worker script from a secure context, does
-// not trigger LNA because the request is same-origin and the origin is
-// potentially trustworthy, and loads the script anyway.
-IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureEnabledBrowserTest,
-                       RecordsFeatureForWorkerScriptFetchFromSecure) {
-  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();
-
-  EXPECT_TRUE(content::NavigateToURL(web_contents(), PublicSecureURL(*server)));
-
-  WebFeatureHistogramTester feature_histogram_tester;
-
-  EXPECT_EQ(true, content::EvalJs(web_contents(),
-                                  FetchWorkerScript(kWorkerScriptPath)));
-
-  feature_histogram_tester.ExpectCounts(AddFeatureCounts(
-      AllZeroFeatureCounts(AllAddressSpaceFeatures()),
-      {
-          {WebFeature::kPrivateNetworkAccessFetchedWorkerScript, 1},
-      }));
-}
-
-// This test verifies that a UseCounter is recorded when a document makes a
-// private network request to load a shared worker script from a non-secure
-// context, even when the PNA for workers feature is disabled.
-IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureDisabledBrowserTest,
-                       RecordsFeatureForSharedWorkerScriptFetchFromNonSecure) {
-  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();
-
-  EXPECT_TRUE(
-      content::NavigateToURL(web_contents(), PublicNonSecureURL(*server)));
-
-  WebFeatureHistogramTester feature_histogram_tester;
-
-  EXPECT_EQ(true,
-            content::EvalJs(web_contents(),
-                            FetchSharedWorkerScript(kSharedWorkerScriptPath)));
-
-  feature_histogram_tester.ExpectCounts(AddFeatureCounts(
-      AllZeroFeatureCounts(AllAddressSpaceFeatures()),
-      {
-          {WebFeature::kPrivateNetworkAccessFetchedWorkerScript, 1},
-      }));
-}
-
-// This test verifies that a UseCounter is recorded when a document makes a
-// private network request to load a shared worker script from a non-secure
-// context, and the request fails due to LNA.
-IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureEnabledBrowserTest,
-                       RecordsFeatureForSharedWorkerScriptFetchFromNonSecure) {
-  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();
-
-  EXPECT_TRUE(
-      content::NavigateToURL(web_contents(), PublicNonSecureURL(*server)));
-
-  WebFeatureHistogramTester feature_histogram_tester;
-
-  EXPECT_EQ(false,
-            content::EvalJs(web_contents(),
-                            FetchSharedWorkerScript(kSharedWorkerScriptPath)));
-
-  feature_histogram_tester.ExpectCounts(AddFeatureCounts(
-      AllZeroFeatureCounts(AllAddressSpaceFeatures()),
-      {
-          {WebFeature::kPrivateNetworkAccessFetchedWorkerScript, 1},
-      }));
-}
-
-// This test verifies that a UseCounter is recorded when a document makes a
-// private network request to load a shared worker script from a secure context,
-// even when the PNA for workers feature is disabled.
-IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureDisabledBrowserTest,
-                       RecordsFeatureForSharedWorkerScriptFetchFromSecure) {
-  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();
-
-  EXPECT_TRUE(content::NavigateToURL(web_contents(), PublicSecureURL(*server)));
-
-  WebFeatureHistogramTester feature_histogram_tester;
-
-  EXPECT_EQ(true,
-            content::EvalJs(web_contents(),
-                            FetchSharedWorkerScript(kSharedWorkerScriptPath)));
-
-  feature_histogram_tester.ExpectCounts(AddFeatureCounts(
-      AllZeroFeatureCounts(AllAddressSpaceFeatures()),
-      {
-          {WebFeature::kPrivateNetworkAccessFetchedWorkerScript, 1},
-      }));
-}
-
-// This test verifies that a UseCounter is recorded when a document makes a
-// private network request to load a shared worker script from a secure context,
-// does not trigger LNA because the request is same-origin and the origin is
-// potentially trustworthy, and loads the script anyway.
-IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureEnabledBrowserTest,
-                       RecordsFeatureForSharedWorkerScriptFetchFromSecure) {
-  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();
-
-  EXPECT_TRUE(content::NavigateToURL(web_contents(), PublicSecureURL(*server)));
-
-  WebFeatureHistogramTester feature_histogram_tester;
-
-  EXPECT_EQ(true,
-            content::EvalJs(web_contents(),
-                            FetchSharedWorkerScript(kSharedWorkerScriptPath)));
-
-  feature_histogram_tester.ExpectCounts(AddFeatureCounts(
-      AllZeroFeatureCounts(AllAddressSpaceFeatures()),
-      {
-          {WebFeature::kPrivateNetworkAccessFetchedWorkerScript, 1},
-      }));
-}
-
-// This test verifies that a UseCounter is recorded when a document makes a
-// private network request to load a service worker script from treat-as-public
-// to local.
-IN_PROC_BROWSER_TEST_F(
-    PrivateNetworkAccessWithFeatureEnabledBrowserTest,
-    RecordsFeatureForServiceWorkerScriptFetchFromTreatAsPublicToLocal) {
-  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();
-
-  EXPECT_TRUE(content::NavigateToURL(web_contents(), PublicSecureURL(*server)));
-
-  WebFeatureHistogramTester feature_histogram_tester;
-
-  EXPECT_EQ(true, content::EvalJs(web_contents(), R"(
-    navigator.serviceWorker.register('/service_worker/empty.js')
-      .then(navigator.serviceWorker.ready)
-      .then(() => true);
-  )"));
-
-  feature_histogram_tester.ExpectCounts(AddFeatureCounts(
-      AllZeroFeatureCounts(AllAddressSpaceFeatures()),
-      {
-          {WebFeature::kPrivateNetworkAccessFetchedWorkerScript, 1},
-      }));
-}
-
-// This test verifies that a UseCounter is not recorded when a document makes a
-// private network request to load a service worker script from local to local.
-IN_PROC_BROWSER_TEST_F(
-    PrivateNetworkAccessWithFeatureEnabledBrowserTest,
-    ShouldNotRecordFeatureForServiceWorkerScriptFetchFromLocalToLocal) {
-  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();
-
-  EXPECT_TRUE(content::NavigateToURL(web_contents(), LocalSecureURL(*server)));
-
-  WebFeatureHistogramTester feature_histogram_tester;
-
-  EXPECT_EQ(true, content::EvalJs(web_contents(), R"(
-    navigator.serviceWorker.register('/service_worker/empty.js')
-      .then(navigator.serviceWorker.ready)
-      .then(() => true);
-  )"));
-
-  feature_histogram_tester.ExpectCounts(
-      AllZeroFeatureCounts(AllAddressSpaceFeatures()));
-}
-
-// Test the experimental use counter for accesses to the 0.0.0.0 IP address
-// (and the corresponding `[::]` IPv6 address).
-//
-// In the Internet Protocol Version 4, the address 0.0.0.0 is a non-routable
-// meta-address used to designate an invalid, unknown or non-applicable target.
-// The real life behavior for 0.0.0.0 is different between operating systems.
-// On Windows, it is unreachable, while on MacOS and Linux, 0.0.0.0 means
-// all IP addresses on the local machine.
-//
-// In this case, 0.0.0.0 can be used to access localhost on MacOS and Linux
-// and bypass Private Network Access checks, so that we would like to forbid
-// fetches to 0.0.0.0. See more: https://crbug.com/1300021
-#if BUILDFLAG(IS_WIN)
-#define MAYBE_FetchNullIpAddressForNavigation \
-  DISABLED_FetchNullIpAddressForNavigation
-#else
-#define MAYBE_FetchNullIpAddressForNavigation FetchNullIpAddressForNavigation
-#endif
-IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureEnabledBrowserTest,
-                       MAYBE_FetchNullIpAddressForNavigation) {
-  WebFeatureHistogramTester feature_histogram_tester;
-  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();
-
-  EXPECT_TRUE(content::NavigateToURL(
-      web_contents(), server->GetURL("0.0.0.0", kNoFaviconPath)));
-
-  feature_histogram_tester.ExpectCounts(
-      AddFeatureCounts(AllZeroFeatureCounts(AllAddressSpaceFeatures()),
-                       {
-                           {WebFeature::kPrivateNetworkAccessNullIpAddress, 1},
-                       }));
-}
-
-#if BUILDFLAG(IS_WIN)
-#define MAYBE_FetchNullIpAddressFromDocument \
-  DISABLED_FetchNullIpAddressFromDocument
-#else
-#define MAYBE_FetchNullIpAddressFromDocument FetchNullIpAddressFromDocument
-#endif
-IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureEnabledBrowserTest,
-                       MAYBE_FetchNullIpAddressFromDocument) {
-  WebFeatureHistogramTester feature_histogram_tester;
-  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();
-
-  EXPECT_TRUE(
-      content::NavigateToURL(web_contents(), server->GetURL(kNoFaviconPath)));
-
-  auto subresource_url =
-      server->GetURL("0.0.0.0", "/set-header?Access-Control-Allow-Origin: *");
-  constexpr char kSubresourceScript[] = R"(
-    new Promise(resolve => {
-      fetch($1).then(e => resolve(true));
-    }))";
-  EXPECT_EQ(true, content::EvalJs(
-                      web_contents(),
-                      content::JsReplace(kSubresourceScript, subresource_url)));
-
-  feature_histogram_tester.ExpectCounts(
-      AddFeatureCounts(AllZeroFeatureCounts(AllAddressSpaceFeatures()),
-                       {
-                           {WebFeature::kPrivateNetworkAccessNullIpAddress, 1},
-                       }));
-}
-
-#if BUILDFLAG(IS_WIN)
-#define MAYBE_FetchNullIpAddressFromWorker DISABLED_FetchNullIpAddressFromWorker
-#else
-#define MAYBE_FetchNullIpAddressFromWorker FetchNullIpAddressFromWorker
-#endif
-IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureEnabledBrowserTest,
-                       MAYBE_FetchNullIpAddressFromWorker) {
-  WebFeatureHistogramTester feature_histogram_tester;
-  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();
-
-  EXPECT_TRUE(content::NavigateToURL(
-      web_contents(), server->GetURL("/workers/fetch_from_worker.html")));
-
-  constexpr char kWorkerScript[] = R"(
-    new Promise(resolve => {
-      fetch_from_worker($1);
-      resolve(true);
-    }))";
-  auto worker_url =
-      server->GetURL("0.0.0.0", "/set-header?Access-Control-Allow-Origin: *");
-  EXPECT_EQ(true,
-            content::EvalJs(web_contents(),
-                            content::JsReplace(kWorkerScript, worker_url)));
-
-  feature_histogram_tester.ExpectCounts(
-      AddFeatureCounts(AllZeroFeatureCounts(AllAddressSpaceFeatures()),
-                       {
-                           {WebFeature::kPrivateNetworkAccessNullIpAddress, 1},
-                       }));
-}
-
-// ====================
-// SPECIAL SCHEME TESTS
-// ====================
-//
-// These tests verify the IP address space assigned to documents loaded from a
-// variety of special URL schemes. Since these are not loaded over the network,
-// an IP address space must be made up for them.
-
-// This test verifies that the chrome-untrusted:// scheme is considered local
-// for the purpose of Private Network Access computations.
-// TODO(crbug.com/40195864): The NTP no longer loads a chrome-untrusted://
-// iframe in all cases. Find another way to test the chrome-untrusted:// scheme.
-IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureEnabledBrowserTest,
-                       DISABLED_SpecialSchemeChromeUntrusted) {
-  // The only way to have a page with a loaded chrome-untrusted:// url without
-  // relying on platform specific or components features, is to use the
-  // new-tab-page host. chrome-untrusted://new-tab-page is restricted to iframes
-  // however so we load chrome://new-tab-page that embeds chrome-untrusted://
-  // frame(s) by default.
-  EXPECT_TRUE(
-      content::NavigateToURL(web_contents(), GURL("chrome://new-tab-page")));
-  content::RenderFrameHost* iframe = ChildFrameAt(web_contents(), 0);
-  ASSERT_TRUE(iframe);
-  EXPECT_TRUE(iframe->GetLastCommittedURL().SchemeIs(
-      content::kChromeUIUntrustedScheme));
-
-  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();
-  GURL fetch_url = LocalNonSecureWithCrossOriginCors(*server);
-
-  // TODO(crbug.com/40459152): The chrome-untrusted:// page should be kLocal,
-  // and not require a Private Network Access CORS preflight. However we have
-  // not yet implemented the CORS preflight mechanism, and fixing the underlying
-  // issue will not change the test result. Once CORS preflight is implemented,
-  // review this test and delete this comment.
-  // Note: CSP is blocking javascript eval, unless we run it in an isolated
-  // world.
-  EXPECT_EQ(true, content::EvalJs(iframe, FetchScript(fetch_url),
-                                  content::EXECUTE_SCRIPT_DEFAULT_OPTIONS,
-                                  content::ISOLATED_WORLD_ID_CONTENT_END));
-}
-
-// This test verifies that the devtools:// scheme is considered local for the
-// purpose of Private Network Access.
-IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureEnabledBrowserTest,
-                       SpecialSchemeDevtools) {
-  EXPECT_TRUE(content::NavigateToURL(
-      web_contents(), GURL("devtools://devtools/bundled/devtools_app.html")));
-  EXPECT_TRUE(
-      web_contents()->GetPrimaryMainFrame()->GetLastCommittedURL().SchemeIs(
-          content::kChromeDevToolsScheme));
-
-  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();
-  GURL fetch_url = LocalNonSecureWithCrossOriginCors(*server);
-
-  // TODO(crbug.com/40459152): The devtools:// page should be kLocal, and not
-  // require a Private Network Access CORS preflight. However we have not yet
-  // implemented the CORS preflight mechanism, and fixing the underlying issue
-  // will not change the test result. Once CORS preflight is implemented, review
-  // this test and delete this comment.
-  EXPECT_EQ(true, content::EvalJs(web_contents(), FetchScript(fetch_url)));
-}
-
-// This test verifies that the chrome-search:// scheme is considered local for
-// the purpose of Private Network Access.
-IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureEnabledBrowserTest,
-                       SpecialSchemeChromeSearch) {
-  EXPECT_TRUE(content::NavigateToURL(
-      web_contents(), GURL("chrome-search://most-visited/title.html")));
-  ASSERT_TRUE(
-      web_contents()->GetPrimaryMainFrame()->GetLastCommittedURL().SchemeIs(
-          chrome::kChromeSearchScheme));
-
-  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();
-  GURL fetch_url = LocalNonSecureWithCrossOriginCors(*server);
-
-  // TODO(crbug.com/40459152): The chrome-search:// page should be kLocal, and
-  // not require a Private Network Access CORS preflight. However we have not
-  // yet implemented the CORS preflight mechanism, and fixing the underlying
-  // issue will not change the test result. Once CORS preflight is implemented,
-  // review this test and delete this comment. Note: CSP is blocking javascript
-  // eval, unless we run it in an isolated world.
-  EXPECT_EQ(true, content::EvalJs(web_contents(), FetchScript(fetch_url),
-                                  content::EXECUTE_SCRIPT_DEFAULT_OPTIONS,
-                                  content::ISOLATED_WORLD_ID_CONTENT_END));
-}
-
-// This test verifies that the chrome-extension:// scheme is considered local
-// for the purpose of Private Network Access.
-IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureEnabledBrowserTest,
-                       SpecialSchemeChromeExtension) {
-  base::ScopedAllowBlockingForTesting allow_blocking;
-  extensions::ScopedInstallVerifierBypassForTest install_verifier_bypass;
-
-  base::ScopedTempDir temp_dir;
-  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
-
-  static constexpr char kPageFile[] = "page.html";
-  constexpr char kContents[] = R"(
-  <html>
-    <head>
-      <title>IPAddressSpace of chrome-extension:// schemes.</title>
-    </head>
-    <body>
-    </body>
-  </html>
-  )";
-  base::WriteFile(temp_dir.GetPath().AppendASCII(kPageFile), kContents);
-  static constexpr char kWebAccessibleResources[] =
-      R"([{
-            "resources": ["page.html"],
-            "matches": ["*://*/*"]
-         }])";
-
-  extensions::ExtensionBuilder builder("test");
-  builder.SetPath(temp_dir.GetPath())
-      .SetVersion("1.0")
-      .SetLocation(extensions::mojom::ManifestLocation::kExternalPolicyDownload)
-      .SetManifestKey("web_accessible_resources",
-                      base::test::ParseJson(kWebAccessibleResources));
-
-  scoped_refptr<const extensions::Extension> extension = builder.Build();
-  extensions::ExtensionRegistrar::Get(browser()->profile())
-      ->OnExtensionInstalled(extension.get(), syncer::StringOrdinal(), 0);
-
-  const GURL url = extension->GetResourceURL(kPageFile);
-
-  EXPECT_TRUE(content::NavigateToURL(web_contents(), url));
-  ASSERT_TRUE(
-      web_contents()->GetPrimaryMainFrame()->GetLastCommittedURL().SchemeIs(
-          extensions::kExtensionScheme));
-
-  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();
-  GURL fetch_url = LocalNonSecureWithCrossOriginCors(*server);
-
-  // TODO(crbug.com/40459152): The chrome-extension:// page should be kLocal,
-  // and not require a Private Network Access CORS preflight. However we have
-  // not yet implemented the CORS preflight mechanism, and fixing the underlying
-  // issue will not change the test result. Once CORS preflight is implemented,
-  // review this test and delete this comment.
-  // Note: CSP is blocking javascript eval, unless we run it in an isolated
-  // world.
-  EXPECT_EQ(true, content::EvalJs(web_contents(), FetchScript(fetch_url),
-                                  content::EXECUTE_SCRIPT_DEFAULT_OPTIONS,
-                                  content::ISOLATED_WORLD_ID_CONTENT_END));
-}
-
-// =================
-// AUTO-RELOAD TESTS
-// =================
-
-// Intercepts the first load to a URL and fails the request with an error.
-class NetErrorInterceptor final {
- public:
-  NetErrorInterceptor(GURL url, net::Error error)
-      : url_(std::move(url)),
-        error_(error),
-        interceptor_(base::BindRepeating(&NetErrorInterceptor::Intercept,
-                                         base::Unretained(this))) {}
-
-  ~NetErrorInterceptor() = default;
-
-  // Instances of this type are neither copyable nor movable.
-  NetErrorInterceptor(const NetErrorInterceptor&) = delete;
-  NetErrorInterceptor& operator=(const NetErrorInterceptor&) = delete;
-
- private:
-  bool Intercept(content::URLLoaderInterceptor::RequestParams* params) {
-    const GURL& request_url = params->url_request.url;
-    if (request_url != url_ || did_intercept_) {
-      return false;
-    }
-
-    did_intercept_ = true;
-
-    network::URLLoaderCompletionStatus status;
-    status.error_code = error_;
-    params->client->OnComplete(status);
-    return true;
-  }
-
-  const GURL url_;
-  const net::Error error_;
-
-  // Whether this instance already intercepted and failed a request.
-  bool did_intercept_ = false;
-
-  // Interceptor must be declared after all state used in `Intercept()`, to
-  // avoid use-after-free at destruction time.
-  const content::URLLoaderInterceptor interceptor_;
-};
-
-class PrivateNetworkAccessAutoReloadBrowserTest
-    : public PrivateNetworkAccessBrowserTestBase {
- public:
-  PrivateNetworkAccessAutoReloadBrowserTest()
-      : PrivateNetworkAccessBrowserTestBase(
-            {
-                features::kBlockInsecurePrivateNetworkRequests,
-            },
-            {
-                network::features::kLocalNetworkAccessChecks,
-            }) {}
-
-  void SetUpOnMainThread() override {
-    PrivateNetworkAccessBrowserTestBase::SetUpOnMainThread();
-
-    error_page::NetErrorAutoReloader::CreateForWebContents(web_contents());
-  }
-};
-
-// This test verifies that when a document in the `local` address space fails to
-// load due to a transient network error, it is auto-reloaded a short while
-// later and that fetch is not blocked as a private network request.
-//
-// TODO(crbug.com/40225769): Test is flaky.
-IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessAutoReloadBrowserTest,
-                       DISABLED_AutoReloadWorks) {
-  ASSERT_TRUE(embedded_test_server()->Start());
-  const GURL url = embedded_test_server()->GetURL("/defaultresponse");
-
-  // There should be two navigations in total: one failed, one successful.
-  content::TestNavigationObserver observer(web_contents(), 2);
-
-  // This interceptor will only fail the first request to `url`.
-  NetErrorInterceptor interceptor(url, net::ERR_UNEXPECTED);
-
-  EXPECT_FALSE(content::NavigateToURL(web_contents(), url));
-
-  // Observe second navigation, which succeeds.
-  observer.Wait();
-  EXPECT_TRUE(observer.last_navigation_succeeded());
-}
-
-// ================
-// 0.0.0.0 TESTS
-// ================
-
-// This test verifies that a 0.0.0.0 subresource is blocked on a nonsecure
-// public URL.
-IN_PROC_BROWSER_TEST_F(PrivateNetworkAccessWithFeatureEnabledBrowserTest,
-                       NullIPBlockedOnNonsecure) {
-  if constexpr (BUILDFLAG(IS_WIN)) {
-    GTEST_SKIP() << "0.0.0.0 behavior varies across platforms and is "
-                    "unreachable on Windows.";
-  }
-
-  std::unique_ptr<net::EmbeddedTestServer> server = NewServer();
-  GURL url = PublicNonSecureURL(*server);
-  EXPECT_TRUE(content::NavigateToURL(web_contents(), url));
-  GURL subresource_url = server->GetURL("0.0.0.0", "/cors-ok.txt");
-  EXPECT_EQ(false, content::EvalJs(web_contents(),
-                                   content::JsReplace(R"(
-    fetch($1).then(response => true).catch(error => false)
-  )",
-                                                      subresource_url)));
-}
-
-}  // namespace
diff --git a/chrome/browser/net/profile_network_context_service.cc b/chrome/browser/net/profile_network_context_service.cc
index b2e37ea..9427aec 100644
--- a/chrome/browser/net/profile_network_context_service.cc
+++ b/chrome/browser/net/profile_network_context_service.cc
@@ -40,8 +40,6 @@
 #include "chrome/browser/domain_reliability/service_factory.h"
 #include "chrome/browser/first_party_sets/first_party_sets_policy_service.h"
 #include "chrome/browser/first_party_sets/first_party_sets_policy_service_factory.h"
-#include "chrome/browser/ip_protection/ip_protection_core_host.h"
-#include "chrome/browser/ip_protection/ip_protection_core_host_factory.h"
 #include "chrome/browser/net/system_network_context_manager.h"
 #include "chrome/browser/privacy_sandbox/privacy_sandbox_settings_factory.h"
 #include "chrome/browser/privacy_sandbox/tracking_protection_settings_factory.h"
@@ -256,17 +254,6 @@
   }
 }
 
-void UpdateTrackingProtectionSettings(Profile* profile) {
-  auto settings =
-      HostContentSettingsMapFactory::GetForProfile(profile)
-          ->GetSettingsForOneType(ContentSettingsType::TRACKING_PROTECTION);
-  profile->ForEachLoadedStoragePartition(
-      [&](content::StoragePartition* storage_partition) {
-        storage_partition->GetNetworkContext()
-            ->SetTrackingProtectionContentSetting(settings);
-      });
-}
-
 void UpdateCookieSettings(Profile* profile, ContentSettingsType type) {
   if (!IsContentSettingsTypeEnabled(type)) {
     return;
@@ -419,17 +406,6 @@
 }
 #endif
 
-// Returns true if IP Protection is needed.
-// Returns false if any of the following:
-//   1. ipp_core_host == nullptr. A nullptr implies the profile does not
-//      participate in IPP.
-//   2. kIpPrivacyIncognitoMode is enabled and the profile in not incognito.
-bool NeedsIpProtection(const IpProtectionCoreHost* ipp_core_host,
-                       const Profile& profile) {
-  return ipp_core_host && (profile.IsIncognitoProfile() ||
-                           !net::features::kIpPrivacyOnlyInIncognito.Get());
-}
-
 constexpr std::string_view kDiskCacheExperimentNameSeparator = " ";
 constexpr std::string_view kDiskCacheExperimentNameNone = "None";
 
@@ -1601,31 +1577,6 @@
       profile_->GetPrefs()->GetBoolean(
           prefs::kAccessControlAllowMethodsInCORSPreflightSpecConformant);
 
-  IpProtectionCoreHost* ipp_core_host =
-      IpProtectionCoreHostFactory::GetForProfile(profile_);
-  if (NeedsIpProtection(ipp_core_host, *profile_)) {
-    ipp_core_host->AddNetworkService(
-        network_context_params->ip_protection_core_host
-            .InitWithNewPipeAndPassReceiver(),
-        network_context_params->ip_protection_control
-            .InitWithNewPipeAndPassRemote());
-    network_context_params->enable_ip_protection =
-        ipp_core_host->IsIpProtectionEnabled();
-    network_context_params->ip_protection_incognito =
-        profile_->IsIncognitoProfile();
-    if (profile_->IsIncognitoProfile()) {
-      network_context_params->initial_ip_protection_tokens =
-          ipp_core_host->TakeRecycledTokens();
-    }
-
-    ContentSettingsForOneType tracking_protection_content_settings =
-        HostContentSettingsMapFactory::GetForProfile(profile_)
-            ->GetSettingsForOneType(ContentSettingsType::TRACKING_PROTECTION);
-
-    network_context_params->tracking_protection_content_settings =
-        std::move(tracking_protection_content_settings);
-  }
-
   network_context_params->device_bound_sessions_enabled =
       base::FeatureList::IsEnabled(net::features::kDeviceBoundSessions);
 
@@ -1682,9 +1633,6 @@
     case ContentSettingsType::ANTI_ABUSE:
       UpdateAntiAbuseSettings(profile_);
       break;
-    case ContentSettingsType::TRACKING_PROTECTION:
-      UpdateTrackingProtectionSettings(profile_);
-      break;
     case ContentSettingsType::DEFAULT:
       UpdateAntiAbuseSettings(profile_);
       for (auto type :
diff --git a/chrome/browser/net/profile_network_context_service_factory.cc b/chrome/browser/net/profile_network_context_service_factory.cc
index f719937f..c61bea6e 100644
--- a/chrome/browser/net/profile_network_context_service_factory.cc
+++ b/chrome/browser/net/profile_network_context_service_factory.cc
@@ -9,7 +9,6 @@
 #include "chrome/browser/content_settings/cookie_settings_factory.h"
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
 #include "chrome/browser/first_party_sets/first_party_sets_policy_service_factory.h"
-#include "chrome/browser/ip_protection/ip_protection_core_host_factory.h"
 #include "chrome/browser/net/profile_network_context_service.h"
 #include "chrome/browser/privacy_sandbox/privacy_sandbox_settings_factory.h"
 #include "chrome/browser/privacy_sandbox/tracking_protection_settings_factory.h"
@@ -88,7 +87,6 @@
   DependsOn(
       first_party_sets::FirstPartySetsPolicyServiceFactory::GetInstance());
   DependsOn(SCTReportingServiceFactory::GetInstance());
-  DependsOn(IpProtectionCoreHostFactory::GetInstance());
 }
 
 ProfileNetworkContextServiceFactory::~ProfileNetworkContextServiceFactory() =
diff --git a/chrome/browser/notifications/scheduler/internal/tips_client.cc b/chrome/browser/notifications/scheduler/internal/tips_client.cc
index bed5314..836cb55 100644
--- a/chrome/browser/notifications/scheduler/internal/tips_client.cc
+++ b/chrome/browser/notifications/scheduler/internal/tips_client.cc
@@ -6,7 +6,6 @@
 
 #include <utility>
 
-#include "base/notimplemented.h"
 #include "base/strings/string_number_conversions.h"
 #include "chrome/browser/notifications/scheduler/internal/stats.h"
 #include "chrome/browser/notifications/scheduler/public/notification_scheduler_constant.h"
@@ -45,11 +44,8 @@
   std::move(callback).Run(std::move(notification_data));
 }
 
-void TipsClient::OnSchedulerInitialized(
-    bool success,
-    std::set<std::string> guids) {
-  NOTIMPLEMENTED();
-}
+void TipsClient::OnSchedulerInitialized(bool success,
+                                        std::set<std::string> guids) {}
 
 void TipsClient::OnUserAction(const UserActionData& action_data) {
 #if BUILDFLAG(IS_ANDROID)
diff --git a/chrome/browser/notifications/scheduler/public/tips_agent.cc b/chrome/browser/notifications/scheduler/public/tips_agent.cc
index 8a1f5945..1bad9aa5 100644
--- a/chrome/browser/notifications/scheduler/public/tips_agent.cc
+++ b/chrome/browser/notifications/scheduler/public/tips_agent.cc
@@ -4,8 +4,6 @@
 
 #include "chrome/browser/notifications/scheduler/public/tips_agent.h"
 
-#include "base/notimplemented.h"
-
 namespace notifications {
 
 // Default implementation of TipsAgent.
@@ -17,9 +15,7 @@
   ~TipsAgentDefault() override = default;
 
  private:
-  void ShowTipsPromo(TipsNotificationsFeatureType feature_type) override {
-    NOTIMPLEMENTED();
-  }
+  void ShowTipsPromo(TipsNotificationsFeatureType feature_type) override {}
 };
 
 // static
diff --git a/chrome/browser/page_content_annotations/page_content_annotations_web_contents_observer.cc b/chrome/browser/page_content_annotations/page_content_annotations_web_contents_observer.cc
index e6aad0c..d0d65ed 100644
--- a/chrome/browser/page_content_annotations/page_content_annotations_web_contents_observer.cc
+++ b/chrome/browser/page_content_annotations/page_content_annotations_web_contents_observer.cc
@@ -85,7 +85,10 @@
 }
 
 PageContentAnnotationsWebContentsObserver::
-    ~PageContentAnnotationsWebContentsObserver() = default;
+    ~PageContentAnnotationsWebContentsObserver() {
+  page_content_annotations_service_->RemoveObserver(
+      AnnotationType::kContentVisibility, this);
+}
 
 void PageContentAnnotationsWebContentsObserver::
     DocumentOnLoadCompletedInPrimaryMainFrame() {
diff --git a/chrome/browser/payments/BUILD.gn b/chrome/browser/payments/BUILD.gn
index c4edb69..ac889869 100644
--- a/chrome/browser/payments/BUILD.gn
+++ b/chrome/browser/payments/BUILD.gn
@@ -54,6 +54,7 @@
     "//components/payments/core:test_support",
     "//components/ukm:test_support",
     "//components/webdata_services",
+    "//crypto:test_support",
     "//device/fido:test_support",
   ]
 
diff --git a/chrome/browser/payments/secure_payment_confirmation_authenticator_browsertest.cc b/chrome/browser/payments/secure_payment_confirmation_authenticator_browsertest.cc
index aae7fcb..e42f8f2 100644
--- a/chrome/browser/payments/secure_payment_confirmation_authenticator_browsertest.cc
+++ b/chrome/browser/payments/secure_payment_confirmation_authenticator_browsertest.cc
@@ -15,6 +15,7 @@
 #include "content/public/browser/scoped_authenticator_environment_for_testing.h"
 #include "content/public/common/content_features.h"
 #include "content/public/test/browser_test.h"
+#include "crypto/scoped_fake_unexportable_key_provider.h"
 #include "device/fido/virtual_fido_device_factory.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -133,6 +134,9 @@
   }
 
   std::unique_ptr<autofill::EventWaiter<Event>> event_waiter_;
+
+ private:
+  crypto::ScopedFakeUnexportableKeyProvider scoped_key_provider_;
 };
 
 using SecurePaymentConfirmationAuthenticatorCreateTest =
diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc
index 99f76bc..2c7878ae 100644
--- a/chrome/browser/prefs/browser_prefs.cc
+++ b/chrome/browser/prefs/browser_prefs.cc
@@ -1613,6 +1613,7 @@
   collaboration::prefs::RegisterProfilePrefs(registry);
   commerce::RegisterProfilePrefs(registry);
   contextual_search::ContextualSearchService::RegisterProfilePrefs(registry);
+  registry->RegisterIntegerPref(prefs::kContextualTasksNextPanelOpenCount, 0);
   cross_device::RegisterProfilePrefs(registry);
   enterprise::RegisterIdentifiersProfilePrefs(registry);
   enterprise_connectors::RegisterProfilePrefs(registry);
diff --git a/chrome/browser/privacy_sandbox/BUILD.gn b/chrome/browser/privacy_sandbox/BUILD.gn
index e072e6fa..68e8a33 100644
--- a/chrome/browser/privacy_sandbox/BUILD.gn
+++ b/chrome/browser/privacy_sandbox/BUILD.gn
@@ -399,9 +399,6 @@
   }
 }
 
-# TODO(crbug.com/408801109) move other privacy_sandbox sources
-# from chrome/test/BUILD.gn here.
-
 source_set("test_support") {
   testonly = true
 
diff --git a/chrome/browser/privacy_sandbox/notice/notice_service.h b/chrome/browser/privacy_sandbox/notice/notice_service.h
index 744e7b4..1dad70e2 100644
--- a/chrome/browser/privacy_sandbox/notice/notice_service.h
+++ b/chrome/browser/privacy_sandbox/notice/notice_service.h
@@ -55,7 +55,6 @@
 
   NoticeStorage* notice_storage() { return notice_storage_.get(); }
 
-  // TODO(crbug.com/392612108): Create eligibility and notice result callbacks.
   raw_ptr<Profile> profile_;
   std::unique_ptr<NoticeCatalog> catalog_;
   std::unique_ptr<NoticeStorage> notice_storage_;
diff --git a/chrome/browser/profiles/BUILD.gn b/chrome/browser/profiles/BUILD.gn
index f8f5100e4..63dd060 100644
--- a/chrome/browser/profiles/BUILD.gn
+++ b/chrome/browser/profiles/BUILD.gn
@@ -287,7 +287,6 @@
     "//chrome/browser/feature_engagement",
     "//chrome/browser/file_system_access",
     "//chrome/browser/history",
-    "//chrome/browser/ip_protection",
     "//chrome/browser/media/router",
     "//chrome/browser/media/webrtc",
     "//chrome/browser/notifications",
diff --git a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc b/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
index a6d35c8f98..2d4042e 100644
--- a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
+++ b/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
@@ -96,7 +96,6 @@
 #include "chrome/browser/history/top_sites_factory.h"
 #include "chrome/browser/history_clusters/history_clusters_service_factory.h"
 #include "chrome/browser/history_embeddings/history_embeddings_service_factory.h"
-#include "chrome/browser/ip_protection/ip_protection_core_host_factory.h"
 #include "chrome/browser/k_anonymity_service/k_anonymity_service_factory.h"
 #include "chrome/browser/language/accept_languages_service_factory.h"
 #include "chrome/browser/language/language_model_manager_factory.h"
@@ -1019,7 +1018,6 @@
 #if !BUILDFLAG(IS_ANDROID)
   IOSPromoTriggerServiceFactory::GetInstance();
 #endif
-  IpProtectionCoreHostFactory::GetInstance();
 #if BUILDFLAG(IS_WIN)
   JumpListFactory::GetInstance();
 #endif
diff --git a/chrome/browser/profiles/profile_window_browsertest.cc b/chrome/browser/profiles/profile_window_browsertest.cc
index de25994..5469057 100644
--- a/chrome/browser/profiles/profile_window_browsertest.cc
+++ b/chrome/browser/profiles/profile_window_browsertest.cc
@@ -118,7 +118,7 @@
 
 IN_PROC_BROWSER_TEST_F(ProfileWindowBrowserTest, CountForNullBrowser) {
   EXPECT_EQ(size_t{0}, chrome::GetBrowserCount(nullptr));
-  EXPECT_EQ(0, BrowserList::GetOffTheRecordBrowsersActiveForProfile(nullptr));
+  EXPECT_EQ(0, chrome::GetOffTheRecordBrowsersActiveForProfile(nullptr));
 }
 
 class ProfileWindowCountBrowserTest : public ProfileWindowBrowserTest,
@@ -129,10 +129,10 @@
   bool is_incognito() { return GetParam(); }
 
   int GetWindowCount() {
-    return is_incognito()
-               ? BrowserList::GetOffTheRecordBrowsersActiveForProfile(
-                     browser()->profile())
-               : BrowserList::GetGuestBrowserCount();
+    return is_incognito() ? static_cast<int>(
+                                chrome::GetOffTheRecordBrowsersActiveForProfile(
+                                    browser()->profile()))
+                          : BrowserList::GetGuestBrowserCount();
   }
 
   Browser* CreateGuestOrIncognitoBrowser() {
diff --git a/chrome/browser/renderer_preferences_util.cc b/chrome/browser/renderer_preferences_util.cc
index 6c664c1..0768b06 100644
--- a/chrome/browser/renderer_preferences_util.cc
+++ b/chrome/browser/renderer_preferences_util.cc
@@ -26,6 +26,7 @@
 #include "components/language/core/browser/pref_names.h"
 #include "components/prefs/pref_service.h"
 #include "content/public/browser/renderer_preferences_util.h"
+#include "content/public/common/content_features.h"
 #include "media/media_buildflags.h"
 #include "third_party/blink/public/common/peerconnection/webrtc_ip_handling_policy.h"
 #include "third_party/blink/public/common/renderer_preferences/renderer_preferences.h"
@@ -202,6 +203,12 @@
 #endif
   prefs->caret_browsing_enabled =
       pref_service->GetBoolean(prefs::kCaretBrowsingEnabled);
+#if BUILDFLAG(IS_ANDROID)
+  if (!base::FeatureList::IsEnabled(features::kAndroidCaretBrowsing)) {
+    // ensures caret browsing is disabled on Clank if the feature flag is off
+    prefs->caret_browsing_enabled = false;
+  }
+#endif
   ui::AXPlatform::GetInstance().SetCaretBrowsingState(
       prefs->caret_browsing_enabled);
   if (PrefService* const local_state = g_browser_process->local_state()) {
diff --git a/chrome/browser/renderer_preferences_util_unittest.cc b/chrome/browser/renderer_preferences_util_unittest.cc
index 28b60a0..4ce58bb 100644
--- a/chrome/browser/renderer_preferences_util_unittest.cc
+++ b/chrome/browser/renderer_preferences_util_unittest.cc
@@ -7,11 +7,13 @@
 #include <array>
 
 #include "base/memory/raw_ptr.h"
+#include "base/test/scoped_feature_list.h"
 #include "chrome/common/pref_names.h"
 #include "chrome/test/base/testing_profile.h"
 #include "components/content_settings/core/common/content_settings_pattern.h"
 #include "components/prefs/pref_service.h"
 #include "components/sync_preferences/testing_pref_service_syncable.h"
+#include "content/public/common/content_features.h"
 #include "content/public/test/browser_task_environment.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/common/peerconnection/webrtc_ip_handling_policy.h"
@@ -132,3 +134,31 @@
   EXPECT_EQ(renderer_preferences.webrtc_ip_handling_urls[1].handling,
             blink::mojom::WebRtcIpHandlingPolicy::kDefault);
 }
+
+#if BUILDFLAG(IS_ANDROID)
+TEST_F(RendererPreferencesUtilTest, CaretBrowsingAndroidKillSwitch) {
+  // Case 1: Feature Enabled, Pref Enabled -> Result: Enabled
+  {
+    base::test::ScopedFeatureList feature_list;
+    feature_list.InitAndEnableFeature(features::kAndroidCaretBrowsing);
+    pref_service_->SetBoolean(prefs::kCaretBrowsingEnabled, true);
+
+    blink::RendererPreferences renderer_preferences;
+    renderer_preferences_util::UpdateFromSystemSettings(&renderer_preferences,
+                                                        &profile_);
+    EXPECT_TRUE(renderer_preferences.caret_browsing_enabled);
+  }
+
+  // Case 2: Feature Disabled, Pref Enabled -> Result: Disabled
+  {
+    base::test::ScopedFeatureList feature_list;
+    feature_list.InitAndDisableFeature(features::kAndroidCaretBrowsing);
+    pref_service_->SetBoolean(prefs::kCaretBrowsingEnabled, true);
+
+    blink::RendererPreferences renderer_preferences;
+    renderer_preferences_util::UpdateFromSystemSettings(&renderer_preferences,
+                                                        &profile_);
+    EXPECT_FALSE(renderer_preferences.caret_browsing_enabled);
+  }
+}
+#endif
diff --git a/chrome/browser/resources/app_settings/app.ts b/chrome/browser/resources/app_settings/app.ts
index 883b332..d660e94 100644
--- a/chrome/browser/resources/app_settings/app.ts
+++ b/chrome/browser/resources/app_settings/app.ts
@@ -34,7 +34,6 @@
 
 const AppElementBase = I18nMixinLit(CrLitElement);
 
-// TODO(crbug.com/40213759): Investigate end-to-end WebAppSettings tests
 export class AppElement extends AppElementBase {
   static get is() {
     return 'web-app-settings-app';
diff --git a/chrome/browser/resources/ash/settings/device_page/display.ts b/chrome/browser/resources/ash/settings/device_page/display.ts
index 154b8d71..ff661dc 100644
--- a/chrome/browser/resources/ash/settings/device_page/display.ts
+++ b/chrome/browser/resources/ash/settings/device_page/display.ts
@@ -200,13 +200,6 @@
         },
       },
 
-      listAllDisplayModes_: {
-        type: Boolean,
-        value() {
-          return loadTimeData.getBoolean('listAllDisplayModes');
-        },
-      },
-
       excludeDisplayInMirrorModeEnabled_: {
         type: Boolean,
         value() {
@@ -338,7 +331,6 @@
   private isDisplayPerformanceEnabled_: boolean;
   private readonly isDisplayPerformanceSupported_: boolean;
   private isTabletMode_: boolean;
-  private listAllDisplayModes_: boolean;
   private logicalResolutionText_: string;
   private mirroringExcludedId_: string;
   private modeToParentModeMap_: Map<number, number>;
@@ -654,33 +646,6 @@
         !resolutionPref.value!.recommended;
   }
 
-
-  /**
-   * Parses the display modes for |selectedDisplay|. |displayModeList_| will
-   * contain entries representing a combined resolution + refresh rate.
-   * Only one parse*DisplayModes_ method must be called, depending on the
-   * state of |listAllDisplayModes_|.
-   */
-  private parseCompoundDisplayModes_(selectedDisplay: DisplayUnitInfo): void {
-    assert(!this.listAllDisplayModes_);
-    const optionList: DropdownMenuOptionList = [];
-    for (let i = 0; i < selectedDisplay.modes.length; ++i) {
-      const mode = selectedDisplay.modes[i];
-
-      const id = 'displayResolutionMenuItem';
-      const refreshRate = Math.round(mode.refreshRate * 100) / 100;
-      const resolution = this.i18n(
-          id, mode.widthInNativePixels.toString(),
-          mode.heightInNativePixels.toString(), refreshRate.toString());
-
-      optionList.push({
-        name: resolution,
-        value: i,
-      });
-    }
-    this.displayModeList_ = optionList;
-  }
-
   /**
    * Uses the modes of |selectedDisplay| to build a nested map of width =>
    * height => refreshRate => modeIndex. modeIndex is the index of the
@@ -727,11 +692,9 @@
    * method goes through the mode list for a given display creating data
    * structures so that given a resolution, the default refresh rate is
    * selected, and other possible refresh rates at that resolution are shown
-   * in a dropdown. Only one parse*DisplayModes_ method must be called,
-   * depending on the state of |listAllDisplayModes_|.
+   * in a dropdown.
    */
-  private parseSplitDisplayModes_(selectedDisplay: DisplayUnitInfo): void {
-    assert(this.listAllDisplayModes_);
+  private parseDisplayModes_(selectedDisplay: DisplayUnitInfo): void {
     // Clear the mappings before recalculating.
     this.modeToParentModeMap_ = new Map();
     this.parentModeToRefreshRateMap_ = new Map();
@@ -801,8 +764,6 @@
    */
   private addResolution_(
       parentModeIndex: number, width: number, height: number): void {
-    assert(this.listAllDisplayModes_);
-
     // Add an entry in the outer map for |parentModeIndex|. The inner
     // array (the value at |parentModeIndex|) will be populated with all
     // possible refresh rates for the given resolution.
@@ -826,8 +787,6 @@
   private addRefreshRate_(
       parentModeIndex: number, modeIndex: number, rate: number,
       isInterlaced?: boolean): void {
-    assert(this.listAllDisplayModes_);
-
     // Truncate at two decimal places for display. If the refresh rate
     // is a whole number, remove the mantissa.
     let refreshRate = Number(rate).toFixed(2);
@@ -866,16 +825,12 @@
 
   /**
    * Parses display modes for |selectedDisplay|. A 'mode' is a resolution +
-   * refresh rate combo. If |listAllDisplayModes_| is on, resolution and
-   * refresh rate are parsed into separate dropdowns and
-   * |parentModeToRefreshRateMap_| + |modeToParentModeMap_| are populated.
+   * refresh rate combo. Resolution and refresh rate are parsed into separate
+   * dropdowns and |parentModeToRefreshRateMap_| + |modeToParentModeMap_| are
+   * populated.
    */
   private updateDisplayModeStructures_(selectedDisplay: DisplayUnitInfo): void {
-    if (this.listAllDisplayModes_) {
-      this.parseSplitDisplayModes_(selectedDisplay);
-    } else {
-      this.parseCompoundDisplayModes_(selectedDisplay);
-    }
+    this.parseDisplayModes_(selectedDisplay);
   }
 
   /**
@@ -950,16 +905,12 @@
     // This will also cause the parent mode to be updated.
     this.set('selectedModePref_.value', this.currentSelectedModeIndex_);
 
-    if (this.listAllDisplayModes_) {
-      // Now that everything is in sync, set the selected mode to its correct
-      // value right before updating the pref.
-      this.currentSelectedParentModeIndex_ =
-          this.modeToParentModeMap_.get(currentModeIndex)!;
-      this.refreshRateList_ = this.parentModeToRefreshRateMap_.get(
-          this.currentSelectedParentModeIndex_)!;
-    } else {
-      this.currentSelectedParentModeIndex_ = currentModeIndex;
-    }
+    // Now that everything is in sync, set the selected mode to its correct
+    // value right before updating the pref.
+    this.currentSelectedParentModeIndex_ =
+        this.modeToParentModeMap_.get(currentModeIndex)!;
+    this.refreshRateList_ = this.parentModeToRefreshRateMap_.get(
+        this.currentSelectedParentModeIndex_)!;
 
     this.set(
         'selectedParentModePref_.value', this.currentSelectedParentModeIndex_);
@@ -978,8 +929,7 @@
    * Returns true if the refresh rate setting needs to be displayed.
    */
   private showRefreshRateSetting_(display: DisplayUnitInfo): boolean {
-    return this.listAllDisplayModes_ &&
-        this.showDropDownResolutionSetting_(display);
+    return this.showDropDownResolutionSetting_(display);
   }
 
   /**
diff --git a/chrome/browser/resources/ash/settings/os_bluetooth_page/os_bluetooth_page.html b/chrome/browser/resources/ash/settings/os_bluetooth_page/os_bluetooth_page.html
index 3e0850a..97fb6b4 100644
--- a/chrome/browser/resources/ash/settings/os_bluetooth_page/os_bluetooth_page.html
+++ b/chrome/browser/resources/ash/settings/os_bluetooth_page/os_bluetooth_page.html
@@ -12,7 +12,6 @@
   </div>
 
   <template is="dom-if" route-path="/bluetoothDevices">
-    <!-- TODO(b/332926512): remove hide-back-button once Bluetooth L1 page is reactivated -->
     <os-settings-subpage page-title="$i18n{bluetoothPageTitle}"
         hide-back-button>
       <div slot="subpage-title-extra">
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/mv2/background/input/command_handler.ts b/chrome/browser/resources/chromeos/accessibility/chromevox/mv2/background/input/command_handler.ts
index 2f9220f4..472207b 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/mv2/background/input/command_handler.ts
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/mv2/background/input/command_handler.ts
@@ -1519,7 +1519,6 @@
 
   /**
    * Launch ChromeVox options page in settings app.
-   * TODO(b/268196299): Add test for showing options page.
    */
   private showOptionsPage_(): void {
     // Launch ChromeVox settings (inside ChromeOS Settings App).
diff --git a/chrome/browser/resources/chromeos/accessibility/enhanced_network_tts/mv3/enhanced_network_tts_unittest.js b/chrome/browser/resources/chromeos/accessibility/enhanced_network_tts/mv3/enhanced_network_tts_unittest.js
index d414b891..7bb0fbf 100644
--- a/chrome/browser/resources/chromeos/accessibility/enhanced_network_tts/mv3/enhanced_network_tts_unittest.js
+++ b/chrome/browser/resources/chromeos/accessibility/enhanced_network_tts/mv3/enhanced_network_tts_unittest.js
@@ -22,9 +22,8 @@
       // Prepare the mockTtsApi to respond with audio data that corresponds to a
       // 0.2135s playback containing "Hello world".
       const mockTtsApi = MockTtsApi;
-      mockTtsApi.enqueueAudioData(
-          generateTestBufferData(), generateTestTimeInfoData(),
-          true /* lastData */);
+      const testAudio = generateTestAudioBuffer();
+      mockTtsApi.enqueueAudioData(testAudio, true /* lastData */);
       chrome.mojoPrivate.registerMockedModuleForTesting(
           'ash.enhanced_network_tts', mockTtsApi);
 
@@ -35,7 +34,7 @@
       const audioStreamOptions = {bufferSize, sampleRate};
       const decodedAudioData =
           await enhancedNetworkTts.decodeAudioDataAtSampleRateForTesting(
-              generateTestBufferData(), sampleRate);
+              testAudio, sampleRate);
       // Each buffer corresponds to 0.04s.
       const expectedBuffers = [
         // The first 0.04s is empty.
@@ -133,28 +132,39 @@
 SYNC_TEST_F(
     'Mv3EnhancedNetworkTtsUnitTest', 'DecodeAudioDataAtSampleRate',
     async function() {
-      const testAudioLength = 0.2135;
-      let sampleRate = 4000;
+      const testAudioLengthInSeconds = 0.2135;
+      const testAudio = generateTestAudioBuffer();
 
+      // Helper method for determining that the buffer is a correct length.
+      const assertBufferIsCorrectLength = (audioBuffer, sampleRate) => {
+        // The legacy AudioFileReader incorrectly includes the preskip samples.
+        // TODO(crbug.com/440616500): remove legacy path when experiment ready.
+        let lengthWithPreskip =
+            Math.floor(testAudioLengthInSeconds * sampleRate);
+        let lengthWithoutPreskip =
+            lengthWithPreskip - getPreskipSampleCount(testAudio, sampleRate);
+        assertTrue(
+            audioBuffer.length == lengthWithPreskip ||
+            audioBuffer.length == lengthWithoutPreskip);
+      };
+
+      let sampleRate = 4000;
       let audioBuffer =
           await enhancedNetworkTts.decodeAudioDataAtSampleRateForTesting(
-              generateTestBufferData(), sampleRate);
-      assertEquals(
-          audioBuffer.length, Math.floor(testAudioLength * sampleRate));
+              testAudio, sampleRate);
+      assertBufferIsCorrectLength(audioBuffer, sampleRate);
 
       sampleRate = 6000;
       audioBuffer =
           await enhancedNetworkTts.decodeAudioDataAtSampleRateForTesting(
-              generateTestBufferData(), sampleRate);
-      assertEquals(
-          audioBuffer.length, Math.floor(testAudioLength * sampleRate));
+              testAudio, sampleRate);
+      assertBufferIsCorrectLength(audioBuffer, sampleRate);
 
       sampleRate = 10000;
       audioBuffer =
           await enhancedNetworkTts.decodeAudioDataAtSampleRateForTesting(
-              generateTestBufferData(), sampleRate);
-      assertEquals(
-          audioBuffer.length, Math.floor(testAudioLength * sampleRate));
+              testAudio, sampleRate);
+      assertBufferIsCorrectLength(audioBuffer, sampleRate);
     });
 
 SYNC_TEST_F('Mv3EnhancedNetworkTtsUnitTest', 'SubarrayFrom', async function() {
@@ -183,13 +193,21 @@
     async function() {
       // Prepare the decodedAudioData for testing. The data corresponds to a
       // 0.2135s playback.
-      const testAudioLength = 0.2135;
+      const testAudioLengthInSeconds = 0.2135;
       const sampleRate = 10000;
-      const decodedAudioDataLength = testAudioLength * sampleRate;  // 2135
+      const testAudio = generateTestAudioBuffer();
+
+      // The legacy AudioFileReader incorrectly includes the preskip samples.
+      // TODO(crbug.com/440616500): remove legacy path when experiment ready.
+      const lengthWithPreskip = testAudioLengthInSeconds * sampleRate;
+      const lengthWithoutPreskip =
+          lengthWithPreskip - getPreskipSampleCount(testAudio, sampleRate);
       const decodedAudioData =
           await enhancedNetworkTts.decodeAudioDataAtSampleRateForTesting(
-              generateTestBufferData(), sampleRate);
-      assertEquals(decodedAudioData.length, decodedAudioDataLength);
+              testAudio, sampleRate);
+      assertTrue(
+          decodedAudioData.length == lengthWithPreskip ||
+          decodedAudioData.length == lengthWithoutPreskip);
 
       const timeInfo = generateTestTimeInfoData();
       // The |decodedAudioData| will be sent through 6 buffers. The first five
@@ -267,11 +285,13 @@
 
 /**
  * Generates audio data for testing. The audio data contains a clip of silent
- * audio originally in ogg format. The audio lasts 0.2135 seconds.
- * @return {!Array<number>}
+ * audio encoded as opus audio in an ogg container. The audio lasts 0.2135
+ * seconds, and has an opus pre-skip value of 312 at the Opus native sample rate
+ * of 48 kHz.
+ * @returns {!Array<number>} audio data encoded as a numerical array.
  */
-function generateTestBufferData() {
-  const testData = [
+function generateTestAudioBuffer() {
+  const testAudio = [
     79,  103, 103, 83,  0,   2,   0,   0,   0,   0,   0,   0,   0,   0,   138,
     1,   148, 99,  0,   0,   0,   0,   19,  72,  225, 168, 1,   19,  79,  112,
     117, 115, 72,  101, 97,  100, 1,   1,   56,  1,   192, 93,  0,   0,   0,
@@ -296,7 +316,33 @@
     74,  40,  33,  234, 158, 16,  44,  104, 9,   73,  154, 65,  180, 184, 46,
     212, 58,  33,  41,  158, 252, 16,  100, 140, 106, 65,  21,  168, 221,
   ];
-  return testData;
+  return testAudio;
+}
+
+/**
+ * Returns the number of preskip samples in the given `testAudio`, scaled to
+ * match the given `sampleRate`.
+ * @returns {number} the number of skip samples at the stream beginning.
+ */
+function getPreskipSampleCount(testAudio, sampleRate) {
+  // Opus uses a default sample rate of 48kHz. If a different sample rate is
+  // requested, the preskip count must be scaled by the ratio between the two
+  // sample rates.
+  const opusSampleRate = 48000;
+
+  // The preskip value is located 10 bytes into the Opus header. The `testAudio`
+  // file starts with a 28-byte Ogg page header. Combined, we get a total offset
+  // of 10 + 28 = 38 bytes.
+  const preskipOffset = 38;
+
+  // The testAudio preskip count is stored as a 16-bit little endian integer
+  // in the testAudio itself.
+  const preskipCount =
+      testAudio[preskipOffset] + 256 * testAudio[preskipOffset + 1];
+
+  // Return the raw count scaled to the ratio between the given `sampleRate` and
+  // the standard opus sample rate.
+  return preskipCount * sampleRate / opusSampleRate;
 }
 
 /**
@@ -304,7 +350,7 @@
  * playback is empty. "Hello" is spoken during 0.05 - 0.15s, "world" is spoken
  * during 0.15 - 0.21s. The last 0.0035s is empty, assuming the audio lasts
  * 0.2135 seconds.
- * @return {!Array<!ash.enhancedNetworkTts.mojom.TimingInfo>}
+ * @returns {!Array<!ash.enhancedNetworkTts.mojom.TimingInfo>} timestamp data.
  */
 function generateTestTimeInfoData() {
   return [
diff --git a/chrome/browser/resources/chromeos/password_change/confirm_password_change.js b/chrome/browser/resources/chromeos/password_change/confirm_password_change.js
index 0737e7d..99fd1e6b 100644
--- a/chrome/browser/resources/chromeos/password_change/confirm_password_change.js
+++ b/chrome/browser/resources/chromeos/password_change/confirm_password_change.js
@@ -12,9 +12,6 @@
  * the main confirm dialog is shown.
  */
 
-// TODO(crbug.com/40613129): Add logic to show only some of the passwords
-// fields if some of the passwords were successfully scraped.
-
 import 'chrome://confirm-password-change/strings.m.js';
 import 'chrome://resources/ash/common/cr_elements/cr_button/cr_button.js';
 import 'chrome://resources/ash/common/cr_elements/cr_input/cr_input.js';
diff --git a/chrome/browser/resources/data_sharing/browser_proxy.ts b/chrome/browser/resources/data_sharing/browser_proxy.ts
index 18e7ab7..e29d9c3 100644
--- a/chrome/browser/resources/data_sharing/browser_proxy.ts
+++ b/chrome/browser/resources/data_sharing/browser_proxy.ts
@@ -72,8 +72,6 @@
       const previews: DataSharingSdkSitePreview[] = [];
       this.handler.getTabGroupPreview(groupId, tokenSecret).then((res) => {
         if (res.groupPreview.statusCode !== AbslStatusCode.kOk) {
-          // TODO(crbug.com/368634445): Ask Chrome to handle different
-          // errors in addition to closing the WebUI.
           this.handler.closeUI(Code.UNKNOWN);
         }
 
diff --git a/chrome/browser/resources/lens/overlay/text_highlights.ts b/chrome/browser/resources/lens/overlay/text_highlights.ts
index d305436f..c283164f 100644
--- a/chrome/browser/resources/lens/overlay/text_highlights.ts
+++ b/chrome/browser/resources/lens/overlay/text_highlights.ts
@@ -45,7 +45,6 @@
   assert(endIndex < words.length);
   const highlightedLines: HighlightedLine[] = [];
 
-  // TODO(crbug.com/397669819): Handle cases for curved text.
   for (let i = startIndex; i <= endIndex; i++) {
     const firstWord = words[i];
     assert(firstWord);
diff --git a/chrome/browser/resources/new_tab_page/app.css b/chrome/browser/resources/new_tab_page/app.css
index e478fea5..317c5c0 100644
--- a/chrome/browser/resources/new_tab_page/app.css
+++ b/chrome/browser/resources/new_tab_page/app.css
@@ -11,6 +11,8 @@
  * #css_wrapper_metadata_end */
 
 :host {
+  --cr-composebox-input-placeholder-color:
+      var(--color-new-tab-page-common-input-placeholder);
   --cr-focus-outline-color: var(--color-new-tab-page-focus-ring);
   --cr-searchbox-height: 48px;
   --cr-searchbox-shadow: 0 1px 6px 0 var(--color-searchbox-shadow);
diff --git a/chrome/browser/resources/omnibox_popup/aim_app.css b/chrome/browser/resources/omnibox_popup/aim_app.css
index 94befd7e..38208d77 100644
--- a/chrome/browser/resources/omnibox_popup/aim_app.css
+++ b/chrome/browser/resources/omnibox_popup/aim_app.css
@@ -28,7 +28,6 @@
 }
 
 cr-composebox {
-  --cr-composebox-background-color: var(--color-omnibox-results-background);
   --color-composebox-background: var(--color-omnibox-results-background);
   --color-composebox-cancel-button: unset; /* Lens only */
   --color-composebox-cancel-button-light: var(--color-omnibox-text-dimmed);
@@ -51,6 +50,8 @@
   --color-composebox-type-ahead-chip: var(--color-omnibox-composebox-foreground-disabled);
   --color-composebox-upload-button: var(--omnibox-results-chip-background);
   --color-composebox-upload-button-disabled: var(--color-omnibox-composebox-foreground-disabled);
+  --cr-composebox-background-color: var(--color-omnibox-results-background);
+  --cr-composebox-input-placeholder-color: var(--color-omnibox-text-dimmed);
   --cr-composebox-outline-hcm: none;
   --cr-composebox-submit-button-margin-inline-start: auto;
   --cr-composebox-suggestion-activity-bottom: 0;
diff --git a/chrome/browser/resources/tab_search/tab_search_page.ts b/chrome/browser/resources/tab_search/tab_search_page.ts
index c45a68c..5d1ca92 100644
--- a/chrome/browser/resources/tab_search/tab_search_page.ts
+++ b/chrome/browser/resources/tab_search/tab_search_page.ts
@@ -312,8 +312,6 @@
     }
 
     this.apiProxy_.getProfileData().then(({profileData}) => {
-      // TODO(crbug.com/40205026): this is a side-by-side comparison of metrics
-      // reporter histogram vs. old histogram. Cleanup when the experiment ends.
       this.metricsReporter.measure('TabListDataReceived')
           .then(
               e => this.metricsReporter.umaReportTime(
diff --git a/chrome/browser/safe_browsing/BUILD.gn b/chrome/browser/safe_browsing/BUILD.gn
index c10d66e..999ff6b 100644
--- a/chrome/browser/safe_browsing/BUILD.gn
+++ b/chrome/browser/safe_browsing/BUILD.gn
@@ -313,6 +313,8 @@
         "chrome_safe_browsing_hats_delegate.h",
         "cloud_content_scanning/binary_upload_service.cc",
         "cloud_content_scanning/binary_upload_service.h",
+        "cloud_content_scanning/browser_thread_guard_impl.cc",
+        "cloud_content_scanning/browser_thread_guard_impl.h",
         "cloud_content_scanning/cloud_binary_upload_service.cc",
         "cloud_content_scanning/cloud_binary_upload_service.h",
         "cloud_content_scanning/cloud_binary_upload_service_factory.cc",
diff --git a/chrome/browser/safe_browsing/cloud_content_scanning/browser_thread_guard_impl.cc b/chrome/browser/safe_browsing/cloud_content_scanning/browser_thread_guard_impl.cc
new file mode 100644
index 0000000..9ce6dc5
--- /dev/null
+++ b/chrome/browser/safe_browsing/cloud_content_scanning/browser_thread_guard_impl.cc
@@ -0,0 +1,15 @@
+// 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/safe_browsing/cloud_content_scanning/browser_thread_guard_impl.h"
+
+#include "content/public/browser/browser_thread.h"
+
+namespace safe_browsing {
+
+void BrowserThreadGuardImpl::AssertCalledOnUIThread() {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+}
+
+}  // namespace safe_browsing
diff --git a/chrome/browser/safe_browsing/cloud_content_scanning/browser_thread_guard_impl.h b/chrome/browser/safe_browsing/cloud_content_scanning/browser_thread_guard_impl.h
new file mode 100644
index 0000000..920c3e26
--- /dev/null
+++ b/chrome/browser/safe_browsing/cloud_content_scanning/browser_thread_guard_impl.h
@@ -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.
+
+#ifndef CHROME_BROWSER_SAFE_BROWSING_CLOUD_CONTENT_SCANNING_BROWSER_THREAD_GUARD_IMPL_H_
+#define CHROME_BROWSER_SAFE_BROWSING_CLOUD_CONTENT_SCANNING_BROWSER_THREAD_GUARD_IMPL_H_
+
+#include "components/enterprise/connectors/core/cloud_content_scanning/browser_thread_guard.h"
+
+namespace safe_browsing {
+
+class BrowserThreadGuardImpl
+    : public enterprise_connectors::BrowserThreadGuard {
+ public:
+  void AssertCalledOnUIThread() override;
+  ~BrowserThreadGuardImpl() override = default;
+};
+
+}  // namespace safe_browsing
+
+#endif  // CHROME_BROWSER_SAFE_BROWSING_CLOUD_CONTENT_SCANNING_BROWSER_THREAD_GUARD_IMPL_H_
diff --git a/chrome/browser/safe_browsing/cloud_content_scanning/multipart_uploader.cc b/chrome/browser/safe_browsing/cloud_content_scanning/multipart_uploader.cc
index dbbbc61e..3406b19 100644
--- a/chrome/browser/safe_browsing/cloud_content_scanning/multipart_uploader.cc
+++ b/chrome/browser/safe_browsing/cloud_content_scanning/multipart_uploader.cc
@@ -7,6 +7,7 @@
 #include <memory>
 
 #include "base/memory/scoped_refptr.h"
+#include "chrome/browser/safe_browsing/cloud_content_scanning/browser_thread_guard_impl.h"
 #include "components/enterprise/connectors/core/cloud_content_scanning/multipart_uploader_base.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
@@ -15,16 +16,8 @@
 
 namespace {
 
-using ::enterprise_connectors::BrowserThreadGuard;
 using ::enterprise_connectors::ConnectorUploadRequest;
 
-class BrowserThreadGuardImpl : public BrowserThreadGuard {
- public:
-  void AssertCalledOnUIThread() override {
-    DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  }
-};
-
 }  // namespace
 
 MultipartUploadRequest::MultipartUploadRequest(
diff --git a/chrome/browser/safe_browsing/cloud_content_scanning/multipart_uploader.h b/chrome/browser/safe_browsing/cloud_content_scanning/multipart_uploader.h
index 06d67d1..5865257d 100644
--- a/chrome/browser/safe_browsing/cloud_content_scanning/multipart_uploader.h
+++ b/chrome/browser/safe_browsing/cloud_content_scanning/multipart_uploader.h
@@ -103,19 +103,6 @@
       MultipartUploadRequest::Callback callback);
 
  private:
-  FRIEND_TEST_ALL_PREFIXES(MultipartUploadRequestTest, GeneratesCorrectBody);
-  FRIEND_TEST_ALL_PREFIXES(MultipartUploadRequestTest, RetriesCorrectly);
-  FRIEND_TEST_ALL_PREFIXES(MultipartUploadRequestTest,
-                           EmitsNetworkRequestResponseCodeOrErrorHistogram);
-  FRIEND_TEST_ALL_PREFIXES(MultipartUploadRequestTest,
-                           EmitsUploadSuccessHistogram);
-  FRIEND_TEST_ALL_PREFIXES(MultipartUploadRequestTest,
-                           EmitsRetriesNeededHistogram);
-  FRIEND_TEST_ALL_PREFIXES(MultipartUploadDataPipeRequestTest, Retries);
-  FRIEND_TEST_ALL_PREFIXES(MultipartUploadDataPipeRequestTest, DataControls);
-  FRIEND_TEST_ALL_PREFIXES(MultipartUploadDataPipeRequestTest,
-                           EquivalentToStringRequest);
-
   scoped_refptr<base::TaskRunner> GetTaskRunner() override;
 };
 
diff --git a/chrome/browser/safe_browsing/cloud_content_scanning/multipart_uploader_unittest.cc b/chrome/browser/safe_browsing/cloud_content_scanning/multipart_uploader_unittest.cc
index 31d9f0b..7584056 100644
--- a/chrome/browser/safe_browsing/cloud_content_scanning/multipart_uploader_unittest.cc
+++ b/chrome/browser/safe_browsing/cloud_content_scanning/multipart_uploader_unittest.cc
@@ -6,24 +6,15 @@
 
 #include <memory>
 
-#include "base/compiler_specific.h"
-#include "base/containers/span.h"
-#include "base/files/file_path.h"
-#include "base/files/scoped_temp_dir.h"
 #include "base/functional/callback_helpers.h"
-#include "base/memory/read_only_shared_memory_region.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/run_loop.h"
 #include "base/test/bind.h"
-#include "base/test/gmock_callback_support.h"
 #include "base/test/metrics/histogram_tester.h"
-#include "components/enterprise/connectors/core/uploader_test_utils.h"
-#include "components/file_access/test/mock_scoped_file_access_delegate.h"
 #include "content/public/test/browser_task_environment.h"
 #include "net/base/net_errors.h"
 #include "net/http/http_status_code.h"
 #include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
-#include "services/network/public/cpp/resource_request.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"
@@ -33,12 +24,9 @@
 
 namespace safe_browsing {
 
-using ::testing::_;
-
 class MultipartUploadRequestTest : public testing::Test {
  public:
-  MultipartUploadRequestTest()
-      : task_environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
+  MultipartUploadRequestTest() = default;
 
   void SetUp() override {
     testing::Test::SetUp();
@@ -48,31 +36,11 @@
             &test_url_loader_factory_);
   }
 
-  base::FilePath CreateFile(const std::string& file_name,
-                            const std::string& content) {
-    if (!temp_dir_.IsValid())
-      EXPECT_TRUE(temp_dir_.CreateUniqueTempDir());
-
-    base::FilePath path = temp_dir_.GetPath().AppendASCII(file_name);
-    base::File file(path, base::File::FLAG_CREATE_ALWAYS |
-                              base::File::FLAG_READ | base::File::FLAG_WRITE);
-    file.WriteAtCurrentPos(base::as_byte_span(content));
-    return path;
-  }
-
-  base::ReadOnlySharedMemoryRegion CreatePage(const std::string& content) {
-    base::MappedReadOnlyRegion region =
-        base::ReadOnlySharedMemoryRegion::Create(content.size());
-    EXPECT_TRUE(region.IsValid());
-    region.mapping.GetMemoryAsSpan<char>().copy_from(content);
-    return std::move(region.region);
-  }
-
  protected:
-  content::BrowserTaskEnvironment task_environment_;
+  content::BrowserTaskEnvironment task_environment_{
+      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
   network::TestURLLoaderFactory test_url_loader_factory_;
   scoped_refptr<network::SharedURLLoaderFactory> test_shared_loader_factory_;
-  base::ScopedTempDir temp_dir_;
 };
 
 class MockMultipartUploadRequest : public MultipartUploadRequest {
@@ -89,341 +57,6 @@
   MOCK_METHOD0(SendRequest, void());
 };
 
-class MockMultipartUploadDataPipeRequest : public MultipartUploadRequest {
- public:
-  MockMultipartUploadDataPipeRequest(const base::FilePath& path,
-                                     MultipartUploadRequest::Callback callback)
-      : MultipartUploadRequest(nullptr,
-                               GURL(),
-                               "metadata",
-                               path,
-                               123,
-                               false,
-                               "histogram_suffix",
-                               TRAFFIC_ANNOTATION_FOR_TESTS,
-                               std::move(callback)) {}
-
-  MockMultipartUploadDataPipeRequest(
-      base::ReadOnlySharedMemoryRegion page_region,
-      MultipartUploadRequest::Callback callback)
-      : MultipartUploadRequest(nullptr,
-                               GURL(),
-                               "metadata",
-                               std::move(page_region),
-                               "histogram_suffix",
-                               TRAFFIC_ANNOTATION_FOR_TESTS,
-                               std::move(callback)) {}
-
-  MOCK_METHOD1(CompleteSendRequest,
-               void(std::unique_ptr<network::ResourceRequest> request));
-};
-
-TEST_F(MultipartUploadRequestTest, GeneratesCorrectBody) {
-  auto connector_request = MultipartUploadRequest::CreateStringRequest(
-      nullptr, GURL(), "metadata", "data", "histogram_suffix",
-      TRAFFIC_ANNOTATION_FOR_TESTS, base::DoNothing());
-  auto* request = static_cast<MultipartUploadRequest*>(connector_request.get());
-
-  std::string expected_body =
-      "--boundary\r\n"
-      "Content-Type: application/octet-stream\r\n"
-      "\r\n"
-      "metadata\r\n"
-      "--boundary\r\n"
-      "Content-Type: application/octet-stream\r\n"
-      "\r\n"
-      "file data\r\n"
-      "--boundary--\r\n";
-
-  request->set_boundary("boundary");
-  EXPECT_EQ(request->GenerateRequestBody("metadata", "file data"),
-            expected_body);
-}
-
-TEST_F(MultipartUploadRequestTest, RetriesCorrectly) {
-  {
-    MockMultipartUploadRequest mock_request;
-
-    EXPECT_CALL(mock_request, SendRequest())
-        .Times(1)
-        .WillRepeatedly([&mock_request]() {
-          mock_request.RetryOrFinish(net::OK, net::HTTP_BAD_REQUEST,
-                                     "response");
-        });
-    mock_request.Start();
-    task_environment_.FastForwardUntilNoTasksRemain();
-  }
-  {
-    MockMultipartUploadRequest mock_request;
-
-    EXPECT_CALL(mock_request, SendRequest())
-        .Times(3)
-        .WillRepeatedly([&mock_request]() {
-          mock_request.RetryOrFinish(net::OK, net::HTTP_SERVICE_UNAVAILABLE,
-                                     "response");
-        });
-    mock_request.Start();
-    task_environment_.FastForwardUntilNoTasksRemain();
-  }
-}
-
-class MultipartUploadDataPipeRequestTest
-    : public MultipartUploadRequestTest,
-      public testing::WithParamInterface<bool> {
- public:
-  bool is_file_request() { return GetParam(); }
-
-  std::unique_ptr<MockMultipartUploadDataPipeRequest> CreateRequest(
-      const std::string& content,
-      base::OnceCallback<void(bool success,
-                              int http_status,
-                              const std::string& response_data)> callback) {
-    if (is_file_request()) {
-      return std::make_unique<MockMultipartUploadDataPipeRequest>(
-          CreateFile("text.txt", content), std::move(callback));
-    } else {
-      return std::make_unique<MockMultipartUploadDataPipeRequest>(
-          CreatePage(content), std::move(callback));
-    }
-  }
-};
-
-INSTANTIATE_TEST_SUITE_P(, MultipartUploadDataPipeRequestTest, testing::Bool());
-
-// Disabled due to flakiness on Windows https://crbug.com/1286638
-#if BUILDFLAG(IS_WIN)
-#define MAYBE_Retries DISABLED_Retries
-#else
-#define MAYBE_Retries Retries
-#endif
-TEST_P(MultipartUploadDataPipeRequestTest, MAYBE_Retries) {
-  std::string expected_body =
-      "--boundary\r\n"
-      "Content-Type: application/octet-stream\r\n"
-      "\r\n"
-      "metadata\r\n"
-      "--boundary\r\n"
-      "Content-Type: application/octet-stream\r\n"
-      "\r\n"
-      "file content\r\n"
-      "--boundary--\r\n";
-  {
-    base::RunLoop run_loop;
-    std::unique_ptr<MockMultipartUploadDataPipeRequest> mock_request =
-        CreateRequest("file content",
-                      base::BindLambdaForTesting(
-                          [&run_loop](bool success, int http_status,
-                                      const std::string& response_data) {
-                            EXPECT_TRUE(success);
-                            EXPECT_EQ(net::HTTP_OK, http_status);
-                            EXPECT_EQ("response", response_data);
-                            run_loop.Quit();
-                          }));
-    mock_request->set_boundary("boundary");
-
-    EXPECT_CALL(*mock_request, CompleteSendRequest(_))
-        .WillOnce([&mock_request, &expected_body](
-                      std::unique_ptr<network::ResourceRequest> request) {
-          EXPECT_EQ(expected_body,
-                    enterprise_connectors::GetBodyFromFileOrPageRequest(
-                        mock_request->data_pipe_getter_for_testing()));
-          mock_request->RetryOrFinish(net::OK, net::HTTP_OK, "response");
-          mock_request->MarkScanAsCompleteForTesting();
-        });
-    mock_request->Start();
-    task_environment_.FastForwardUntilNoTasksRemain();
-    run_loop.Run();
-    EXPECT_EQ(mock_request->GetUploadInfo(), "Multipart - Complete");
-  }
-  {
-    int retry_count = 0;
-    base::RunLoop run_loop;
-    std::unique_ptr<MockMultipartUploadDataPipeRequest> mock_request =
-        CreateRequest(
-            "file content",
-            base::BindLambdaForTesting(
-                [&run_loop, &retry_count](bool success, int http_status,
-                                          const std::string& response_data) {
-                  EXPECT_TRUE(success);
-                  EXPECT_EQ(net::HTTP_OK, http_status);
-                  EXPECT_EQ("response", response_data);
-                  EXPECT_EQ(3, retry_count);
-                  run_loop.Quit();
-                }));
-    mock_request->set_boundary("boundary");
-
-    EXPECT_CALL(*mock_request, CompleteSendRequest(_))
-        .Times(3)
-        .WillRepeatedly([&mock_request, &expected_body, &retry_count](
-                            std::unique_ptr<network::ResourceRequest> request) {
-          // Every call to CompleteSendRequest should be able to get the
-          // same body from the request's data pipe getter.
-          EXPECT_EQ(expected_body,
-                    enterprise_connectors::GetBodyFromFileOrPageRequest(
-                        mock_request->data_pipe_getter_for_testing()));
-
-          ++retry_count;
-          mock_request->RetryOrFinish(
-              net::OK,
-              retry_count < 3 ? net::HTTP_SERVICE_UNAVAILABLE : net::HTTP_OK,
-              "response");
-          if (retry_count == 3) {
-            mock_request->MarkScanAsCompleteForTesting();
-          }
-        });
-    mock_request->Start();
-    task_environment_.FastForwardUntilNoTasksRemain();
-    run_loop.Run();
-    EXPECT_EQ(mock_request->GetUploadInfo(), "Multipart - Complete");
-  }
-}
-
-TEST_P(MultipartUploadDataPipeRequestTest, DataControls) {
-  std::string expected_body =
-      "--boundary\r\n"
-      "Content-Type: application/octet-stream\r\n"
-      "\r\n"
-      "metadata\r\n"
-      "--boundary\r\n"
-      "Content-Type: application/octet-stream\r\n"
-      "\r\n"
-      "file content\r\n"
-      "--boundary--\r\n";
-  file_access::MockScopedFileAccessDelegate scoped_files_access_delegate;
-
-  if (is_file_request()) {
-    EXPECT_CALL(scoped_files_access_delegate, RequestFilesAccessForSystem)
-        .WillOnce(base::test::RunOnceCallback<1>(
-            file_access::ScopedFileAccess::Allowed()));
-  } else {
-    EXPECT_CALL(scoped_files_access_delegate, RequestFilesAccessForSystem)
-        .Times(0);
-  }
-
-  base::RunLoop run_loop;
-  std::unique_ptr<MockMultipartUploadDataPipeRequest> mock_request =
-      CreateRequest("file content",
-                    base::BindLambdaForTesting(
-                        [&run_loop](bool success, int http_status,
-                                    const std::string& response_data) {
-                          EXPECT_TRUE(success);
-                          EXPECT_EQ(net::HTTP_OK, http_status);
-                          EXPECT_EQ("response", response_data);
-                          run_loop.Quit();
-                        }));
-  mock_request->set_boundary("boundary");
-
-  EXPECT_CALL(*mock_request, CompleteSendRequest(_))
-      .WillOnce([&mock_request, &expected_body](
-                    std::unique_ptr<network::ResourceRequest> request) {
-        EXPECT_EQ(expected_body,
-                  enterprise_connectors::GetBodyFromFileOrPageRequest(
-                      mock_request->data_pipe_getter_for_testing()));
-        mock_request->RetryOrFinish(net::OK, net::HTTP_OK, "response");
-        mock_request->MarkScanAsCompleteForTesting();
-      });
-  mock_request->Start();
-  task_environment_.FastForwardUntilNoTasksRemain();
-  run_loop.Run();
-  EXPECT_EQ(mock_request->GetUploadInfo(), "Multipart - Complete");
-}
-
-TEST_P(MultipartUploadDataPipeRequestTest, EquivalentToStringRequest) {
-  // The request body should be identical when obtained through a string request
-  // and a data pipe request with equivalent content.
-  std::unique_ptr<MockMultipartUploadDataPipeRequest> data_pipe_request =
-      CreateRequest("data", base::DoNothing());
-  data_pipe_request->set_boundary("boundary");
-
-  // Start the data pipe request to initialize the internal data pipe getter.
-  ASSERT_FALSE(data_pipe_request->data_pipe_getter_for_testing());
-  EXPECT_CALL(*data_pipe_request, CompleteSendRequest(_)).Times(1);
-  data_pipe_request->Start();
-  task_environment_.FastForwardUntilNoTasksRemain();
-  ASSERT_TRUE(data_pipe_request->data_pipe_getter_for_testing());
-
-  MockMultipartUploadRequest string_request;
-  string_request.set_boundary("boundary");
-
-  std::string expected_body =
-      "--boundary\r\n"
-      "Content-Type: application/octet-stream\r\n"
-      "\r\n"
-      "metadata\r\n"
-      "--boundary\r\n"
-      "Content-Type: application/octet-stream\r\n"
-      "\r\n"
-      "data\r\n"
-      "--boundary--\r\n";
-
-  EXPECT_EQ(expected_body,
-            string_request.GenerateRequestBody("metadata", "data"));
-  EXPECT_EQ(expected_body,
-            enterprise_connectors::GetBodyFromFileOrPageRequest(
-                data_pipe_request->data_pipe_getter_for_testing()));
-}
-
-TEST_F(MultipartUploadRequestTest, GeneratesCorrectHeaders_StringRequest) {
-  network::ResourceRequest resource_request;
-
-  auto connector_request = MultipartUploadRequest::CreateStringRequest(
-      nullptr, GURL(), "metadata", "data", "histogram_suffix",
-      TRAFFIC_ANNOTATION_FOR_TESTS, base::DoNothing());
-  auto* request = static_cast<MultipartUploadRequest*>(connector_request.get());
-
-  request->SetRequestHeaders(&resource_request);
-  ASSERT_TRUE(resource_request.headers.HasHeader("X-Goog-Upload-Protocol"));
-  ASSERT_THAT(resource_request.headers.GetHeader("X-Goog-Upload-Protocol"),
-              testing::Optional(std::string("multipart")));
-  ASSERT_TRUE(resource_request.headers.HasHeader(
-      "X-Goog-Upload-Header-Content-Length"));
-  ASSERT_THAT(
-      resource_request.headers.GetHeader("X-Goog-Upload-Header-Content-Length"),
-      testing::Optional(std::string("4")));
-  EXPECT_EQ(request->GetUploadInfo(), "Multipart - Pending");
-}
-
-TEST_F(MultipartUploadRequestTest, GeneratesCorrectHeaders_FileRequest) {
-  network::ResourceRequest resource_request;
-
-  auto connector_request = MultipartUploadRequest::CreateFileRequest(
-      nullptr, GURL(), "metadata", CreateFile("my_file_name.foo", "file_data"),
-      9, false, "histogram_suffix", TRAFFIC_ANNOTATION_FOR_TESTS,
-      base::DoNothing());
-  auto* request = static_cast<MultipartUploadRequest*>(connector_request.get());
-
-  request->SetRequestHeaders(&resource_request);
-  ASSERT_TRUE(resource_request.headers.HasHeader("X-Goog-Upload-Protocol"));
-  ASSERT_THAT(resource_request.headers.GetHeader("X-Goog-Upload-Protocol"),
-              testing::Optional(std::string("multipart")));
-  ASSERT_TRUE(resource_request.headers.HasHeader(
-      "X-Goog-Upload-Header-Content-Length"));
-  ASSERT_THAT(
-      resource_request.headers.GetHeader("X-Goog-Upload-Header-Content-Length"),
-      testing::Optional(std::string("9")));
-  EXPECT_EQ(request->GetUploadInfo(), "Multipart - Pending");
-}
-
-TEST_F(MultipartUploadRequestTest, GeneratesCorrectHeaders_PageRequest) {
-  network::ResourceRequest resource_request;
-
-  auto connector_request = MultipartUploadRequest::CreatePageRequest(
-      nullptr, GURL(), "metadata", CreatePage("print_data"), "histogram_suffix",
-      TRAFFIC_ANNOTATION_FOR_TESTS, base::DoNothing());
-  auto* request = static_cast<MultipartUploadRequest*>(connector_request.get());
-
-  request->SetRequestHeaders(&resource_request);
-  ASSERT_TRUE(resource_request.headers.HasHeader("X-Goog-Upload-Protocol"));
-  ASSERT_THAT(resource_request.headers.GetHeader("X-Goog-Upload-Protocol"),
-              testing::Optional(std::string("multipart")));
-  ASSERT_TRUE(resource_request.headers.HasHeader(
-      "X-Goog-Upload-Header-Content-Length"));
-  ASSERT_THAT(
-      resource_request.headers.GetHeader("X-Goog-Upload-Header-Content-Length"),
-      testing::Optional(std::string("10")));
-  EXPECT_EQ(request->GetUploadInfo(), "Multipart - Pending");
-}
-
 TEST_F(MultipartUploadRequestTest, StringRequest_Failure) {
   base::HistogramTester histogram_tester;
   auto dummy_upload_url = GURL("https://google.com");
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 026c354..0793bf6 100644
--- a/chrome/browser/safe_browsing/cloud_content_scanning/resumable_uploader.cc
+++ b/chrome/browser/safe_browsing/cloud_content_scanning/resumable_uploader.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/safe_browsing/cloud_content_scanning/resumable_uploader.h"
 
+// TODO(crbug.com/456489971): Remove unused header files
 #include <memory>
 
 #include "base/base64.h"
@@ -14,17 +15,14 @@
 #include "base/memory/scoped_refptr.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/strings/strcat.h"
-#include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
 #include "base/task/thread_pool.h"
 #include "base/time/time.h"
 #include "components/enterprise/connectors/core/features.h"
 #include "components/file_access/scoped_file_access_delegate.h"
-#include "components/safe_browsing/core/common/utils.h"
 #include "content/public/browser/browser_thread.h"
 #include "net/http/http_status_code.h"
 #include "net/traffic_annotation/network_traffic_annotation.h"
-#include "services/network/public/cpp/resource_request.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 #include "services/network/public/cpp/simple_url_loader.h"
 #include "services/network/public/mojom/url_response_head.mojom.h"
@@ -36,36 +34,11 @@
 using ::enterprise_connectors::ConnectorDataPipeGetter;
 using ::enterprise_connectors::ConnectorUploadRequest;
 
-// HTTP headers for resumable upload requests
-constexpr char kUploadProtocolHeader[] = "X-Goog-Upload-Protocol";
-constexpr char kUploadCommandHeader[] = "X-Goog-Upload-Command";
-constexpr char kUploadHeaderContentLengthHeader[] =
-    "X-Goog-Upload-Header-Content-Length";
-constexpr char kUploadHeaderContentTypeHeader[] =
-    "X-Goog-Upload-Header-Content-Type";
-constexpr char kUploadStatusHeader[] = "X-Goog-Upload-Status";
+// TODO(crbug.com/456489971): Remove copies between ResumableUploadRequest and
+// ResumableUploadRequestBase HTTP headers for resumable upload requests
 constexpr char kUploadUrlHeader[] = "X-Goog-Upload-Url";
-constexpr char kUploadOffsetHeader[] = "X-Goog-Upload-Offset";
 constexpr char kUploadIntermediateHeader[] =
     "X-Goog-Upload-Header-Cep-Response";
-// Content type of the upload contents.
-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,
-    bool is_obfuscated) {
-  // FLAG_WIN_SHARE_DELETE is necessary to allow the file to be renamed by the
-  // user clicking "Open Now" without causing download errors.
-  base::File file(path, base::File::FLAG_OPEN | base::File::FLAG_READ |
-                            base::File::FLAG_WIN_SHARE_DELETE);
-
-  return ConnectorDataPipeGetter::CreateResumablePipeGetter(std::move(file),
-                                                            is_obfuscated);
-}
 
 bool IsSuccess(int net_error, int response_code) {
   return net_error == net::OK && response_code == net::HTTP_OK;
@@ -86,20 +59,18 @@
     VerdictReceivedCallback verdict_received_callback,
     ContentUploadedCallback content_uploaded_callback,
     bool force_sync_upload)
-    : ConnectorUploadRequest(std::move(url_loader_factory),
-                             base_url,
-                             metadata,
-                             path,
-                             file_size,
-                             is_obfuscated,
-                             histogram_suffix,
-                             traffic_annotation,
-                             base::DoNothing()),
-      verdict_received_callback_(std::move(verdict_received_callback)),
-      get_data_result_(get_data_result),
-      is_obfuscated_(is_obfuscated),
-      content_uploaded_callback_(std::move(content_uploaded_callback)),
-      force_sync_upload_(force_sync_upload) {
+    : ResumableUploadRequestBase(std::move(url_loader_factory),
+                                 base_url,
+                                 metadata,
+                                 get_data_result,
+                                 path,
+                                 file_size,
+                                 is_obfuscated,
+                                 histogram_suffix,
+                                 traffic_annotation,
+                                 std::move(verdict_received_callback),
+                                 std::move(content_uploaded_callback),
+                                 force_sync_upload) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 }
 
@@ -114,17 +85,16 @@
     VerdictReceivedCallback verdict_received_callback,
     ContentUploadedCallback content_uploaded_callback,
     bool force_sync_upload)
-    : ConnectorUploadRequest(std::move(url_loader_factory),
-                             base_url,
-                             metadata,
-                             std::move(page_region),
-                             histogram_suffix,
-                             traffic_annotation,
-                             base::DoNothing()),
-      verdict_received_callback_(std::move(verdict_received_callback)),
-      get_data_result_(get_data_result),
-      content_uploaded_callback_(std::move(content_uploaded_callback)),
-      force_sync_upload_(force_sync_upload) {
+    : ResumableUploadRequestBase(std::move(url_loader_factory),
+                                 base_url,
+                                 metadata,
+                                 get_data_result,
+                                 std::move(page_region),
+                                 histogram_suffix,
+                                 traffic_annotation,
+                                 std::move(verdict_received_callback),
+                                 std::move(content_uploaded_callback),
+                                 force_sync_upload) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 }
 
@@ -138,70 +108,25 @@
     VerdictReceivedCallback verdict_received_callback,
     ContentUploadedCallback content_uploaded_callback,
     bool force_sync_upload)
-    : ConnectorUploadRequest(std::move(url_loader_factory),
-                             base_url,
-                             metadata,
-                             data,
-                             histogram_suffix,
-                             traffic_annotation,
-                             base::DoNothing()),
-      verdict_received_callback_(std::move(verdict_received_callback)),
-      get_data_result_(enterprise_connectors::ScanRequestUploadResult::SUCCESS),
-      content_uploaded_callback_(std::move(content_uploaded_callback)),
-      force_sync_upload_(force_sync_upload) {
+    : ResumableUploadRequestBase(std::move(url_loader_factory),
+                                 base_url,
+                                 metadata,
+                                 data,
+                                 histogram_suffix,
+                                 traffic_annotation,
+                                 std::move(verdict_received_callback),
+                                 std::move(content_uploaded_callback),
+                                 force_sync_upload) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 }
 
 ResumableUploadRequest::~ResumableUploadRequest() = default;
 
-void ResumableUploadRequest::SetMetadataRequestHeaders(
-    network::ResourceRequest* request) {
-  CHECK(request);
-
-  // Page, string and file requests should have non-zero `data_size_`.
-  DCHECK_GT(data_size_, (uint64_t)0);
-
-  request->headers.SetHeader(kUploadProtocolHeader, "resumable");
-  request->headers.SetHeader(kUploadCommandHeader, "start");
-  request->headers.SetHeader(kUploadHeaderContentLengthHeader,
-                             base::NumberToString(data_size_));
-  // `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);
-    SetAccessToken(request, access_token_);
-  }
-  request->credentials_mode = network::mojom::CredentialsMode::kOmit;
-}
-
 void ResumableUploadRequest::Start() {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   SendMetadataRequest();
 }
 
-std::string ResumableUploadRequest::GetUploadInfo() {
-  std::string scan_info;
-  switch (scan_type_) {
-    case PENDING:
-      scan_info = "Pending";
-      break;
-    case FULL_CONTENT:
-      scan_info = "Full content scan";
-      break;
-    case METADATA_ONLY:
-      scan_info = "Metadata only scan";
-      break;
-    case ASYNC:
-      scan_info = "Async content upload";
-      break;
-  }
-
-  return base::StrCat({"Resumable - ", scan_info});
-}
-
 // static
 std::unique_ptr<ConnectorUploadRequest>
 ResumableUploadRequest::CreateStringRequest(
@@ -282,23 +207,6 @@
       std::move(content_uploaded_callback), force_sync_upload);
 }
 
-void ResumableUploadRequest::SendMetadataRequest() {
-  auto resource_request = std::make_unique<network::ResourceRequest>();
-  resource_request->url = base_url_;
-  resource_request->method = "POST";
-  SetMetadataRequestHeaders(resource_request.get());
-
-  url_loader_ = network::SimpleURLLoader::Create(std::move(resource_request),
-                                                 traffic_annotation_);
-  url_loader_->SetAllowHttpErrorResults(true);
-  url_loader_->AttachStringForUpload(base::StrCat({metadata_, "\r\n"}),
-                                     kMetadataContentType);
-  url_loader_->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
-      url_loader_factory_.get(),
-      base::BindOnce(&ResumableUploadRequest::OnMetadataUploadCompleted,
-                     weak_factory_.GetWeakPtr(), base::TimeTicks::Now()));
-}
-
 void ResumableUploadRequest::OnMetadataUploadCompleted(
     base::TimeTicks start_time,
     std::optional<std::string> response_body) {
@@ -359,99 +267,6 @@
   SendContentSoon(headers->GetNormalizedHeader(kUploadUrlHeader).value());
 }
 
-bool ResumableUploadRequest::ShouldUploadEncryptedFile() {
-  return base::FeatureList::IsEnabled(
-             enterprise_connectors::kEnableEncryptedFileUpload) &&
-         scan_type_ == ASYNC;
-}
-void ResumableUploadRequest::SendContentSoon(const std::string& upload_url) {
-  auto request = std::make_unique<network::ResourceRequest>();
-  request->method = "POST";
-  request->url = GURL(upload_url);
-  // Only sends content smaller than 50MB, in a single request.
-  request->headers.SetHeader(kUploadCommandHeader, "upload, finalize");
-  request->headers.SetHeader(kUploadOffsetHeader, "0");
-
-  // TODO(b/322005992): Add retry logics.
-  switch (data_source_) {
-    case FILE:
-      file_access::RequestFilesAccessForSystem(
-          {path_},
-          base::BindOnce(&ResumableUploadRequest::CreateDatapipe,
-                         weak_factory_.GetWeakPtr(), std::move(request)));
-      break;
-    case PAGE:
-      OnDataPipeCreated(std::move(request),
-                        ConnectorDataPipeGetter::CreateResumablePipeGetter(
-                            std::move(page_region_)));
-      break;
-    // Resumable uploads are used for pasted images, which are handled as string
-    // data. Using resumable uploads for pasted images is enabled by the
-    // `enterprise_connectors::kDlpScanPastedImages` feature flag. Text pastes
-    // use multipart uploads.
-    case STRING:
-      SendContentNow(std::move(request));
-      break;
-    default:
-      NOTREACHED();
-  }
-}
-
-// TODO(b/328415950): Move the data pipe creation logics to
-// connector_upload_request.
-void ResumableUploadRequest::CreateDatapipe(
-    std::unique_ptr<network::ResourceRequest> request,
-    file_access::ScopedFileAccess file_access) {
-  scoped_file_access_ =
-      std::make_unique<file_access::ScopedFileAccess>(std::move(file_access));
-  base::ThreadPool::PostTaskAndReplyWithResult(
-      FROM_HERE, {base::TaskPriority::USER_VISIBLE, base::MayBlock()},
-      base::BindOnce(&CreateFileDataPipeGetterBlocking, path_, is_obfuscated_),
-      base::BindOnce(&ResumableUploadRequest::OnDataPipeCreated,
-                     weak_factory_.GetWeakPtr(), std::move(request)));
-}
-
-void ResumableUploadRequest::OnDataPipeCreated(
-    std::unique_ptr<network::ResourceRequest> request,
-    std::unique_ptr<ConnectorDataPipeGetter> data_pipe_getter) {
-  scoped_file_access_.reset();
-  if (!data_pipe_getter) {
-    // TODO(329293309): Replace with meaningful net_error value since 0 does not
-    // indicate an error.
-    Finish(0, 0, std::nullopt);
-    return;
-  }
-
-  data_pipe_getter_ = std::move(data_pipe_getter);
-  SendContentNow(std::move(request));
-}
-
-void ResumableUploadRequest::SendContentNow(
-    std::unique_ptr<network::ResourceRequest> request) {
-  // `data_pipe_getter_` is null for STRING requests, which are handled by
-  // attaching the string data directly to the URL loader. For FILE and PAGE
-  // requests, `data_pipe_getter_` will be non-null.
-  if (data_pipe_getter_) {
-    mojo::PendingRemote<network::mojom::DataPipeGetter> data_pipe_getter;
-    data_pipe_getter_->Clone(data_pipe_getter.InitWithNewPipeAndPassReceiver());
-    request->request_body = new network::ResourceRequestBody();
-    request->request_body->AppendDataPipe(std::move(data_pipe_getter));
-  }
-
-  url_loader_ =
-      network::SimpleURLLoader::Create(std::move(request), traffic_annotation_);
-  url_loader_->SetAllowHttpErrorResults(true);
-
-  if (!data_pipe_getter_) {
-    url_loader_->AttachStringForUpload(data_, kImageContentType);
-  }
-
-  url_loader_->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
-      url_loader_factory_.get(),
-      base::BindOnce(&ResumableUploadRequest::OnSendContentCompleted,
-                     weak_factory_.GetWeakPtr(), base::TimeTicks::Now()));
-}
-
 void ResumableUploadRequest::OnSendContentCompleted(
     base::TimeTicks start_time,
     std::optional<std::string> response_body) {
@@ -476,48 +291,4 @@
   Finish(url_loader_->NetError(), response_code, std::move(response_body));
 }
 
-bool ResumableUploadRequest::CanUploadContent(
-    const scoped_refptr<net::HttpResponseHeaders>& headers) {
-  if (headers->response_code() != net::HTTP_OK) {
-    return false;
-  }
-  std::optional<std::string> upload_status =
-      headers->GetNormalizedHeader(kUploadStatusHeader);
-  if (!upload_status || !headers->HasHeader(kUploadUrlHeader)) {
-    return false;
-  }
-  return base::EqualsCaseInsensitiveASCII(upload_status.value_or(std::string()),
-                                          "active");
-}
-
-void ResumableUploadRequest::Finish(int net_error,
-                                    int response_code,
-                                    std::optional<std::string> response_body) {
-  if (!histogram_suffix_.empty()) {
-    std::string histogram = base::StrCat(
-        {"SafeBrowsing.ResumableUploader.NetworkResult.", histogram_suffix_});
-    RecordHttpResponseOrErrorCode(histogram.c_str(), net_error, response_code);
-  }
-
-  // The callback may have been invoked when the metadata verdict was received
-  // with the CEP header, to unblock the user initiate an async upload.
-  if (!verdict_received_callback_.is_null()) {
-    std::move(verdict_received_callback_)
-        .Run(/*success=*/IsSuccess(net_error, response_code), response_code,
-             response_body.value_or(""));
-  }
-  std::move(content_uploaded_callback_).Run();
-}
-
-std::string ResumableUploadRequest::GetRequestType() {
-  switch (data_source_) {
-    case FILE:
-      return "File";
-    case STRING:
-      return "Text";
-    case PAGE:
-      return "Print";
-  }
-}
-
 }  // namespace safe_browsing
diff --git a/chrome/browser/safe_browsing/cloud_content_scanning/resumable_uploader.h b/chrome/browser/safe_browsing/cloud_content_scanning/resumable_uploader.h
index 2e14c7a..451a88f0 100644
--- a/chrome/browser/safe_browsing/cloud_content_scanning/resumable_uploader.h
+++ b/chrome/browser/safe_browsing/cloud_content_scanning/resumable_uploader.h
@@ -18,6 +18,7 @@
 #include "components/enterprise/connectors/core/cloud_content_scanning/common.h"
 #include "components/enterprise/connectors/core/cloud_content_scanning/connector_data_pipe_getter.h"
 #include "components/enterprise/connectors/core/cloud_content_scanning/connector_upload_request.h"
+#include "components/enterprise/connectors/core/cloud_content_scanning/resumable_uploader_base.h"
 #include "net/traffic_annotation/network_traffic_annotation.h"
 #include "services/network/public/cpp/resource_request.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
@@ -32,11 +33,12 @@
 // This class encapsulates the upload of a file with metadata using the
 // resumable protocol. This class is neither movable nor copyable.
 class ResumableUploadRequest
-    : public enterprise_connectors::ConnectorUploadRequest {
+    : public enterprise_connectors::ResumableUploadRequestBase {
  public:
-  using ContentUploadedCallback = base::OnceClosure;
-  using VerdictReceivedCallback =
-      enterprise_connectors::ConnectorUploadRequest::Callback;
+  using enterprise_connectors::ResumableUploadRequestBase::
+      ContentUploadedCallback;
+  using enterprise_connectors::ResumableUploadRequestBase::
+      VerdictReceivedCallback;
 
   // Creates a ResumableUploadRequest, which will upload the `metadata` of the
   // file corresponding to the provided `path` to the given `base_url`, and then
@@ -134,87 +136,20 @@
       ContentUploadedCallback content_uploaded_callback,
       bool force_sync_upload);
 
-  // Set the headers for the given metadata `request`.
-  void SetMetadataRequestHeaders(network::ResourceRequest* request);
-
   // Start the upload. This must be called on the UI thread. When complete, this
   // will call `callback_` on the UI thread.
   void Start() override;
 
-  std::string GetUploadInfo() override;
-
- protected:
-  // Called after a metadata request finishes successfully. Virtual for testing.
-  virtual void SendContentSoon(const std::string& upload_url);
-
-  // Called whenever a net request finishes (on success or failure). Protected
-  // for testing
-  void Finish(int net_error,
-              int response_code,
-              std::optional<std::string> response_body);
-
-  bool force_sync_upload() const { return force_sync_upload_; }
-
-  VerdictReceivedCallback verdict_received_callback_;
-
  private:
-  // Send the metadata information about the file/page to the server.
-  void SendMetadataRequest();
-
   // Called whenever a metadata request finishes (on success or failure).
-  void OnMetadataUploadCompleted(base::TimeTicks start_time,
-                                 std::optional<std::string> response_body);
-
-  // Initialize `data_pipe_getter_`
-  void CreateDatapipe(std::unique_ptr<network::ResourceRequest> request,
-                      file_access::ScopedFileAccess file_access);
-
-  // Called after `data_pipe_getter_` has be
-  void OnDataPipeCreated(
-      std::unique_ptr<network::ResourceRequest> request,
-      std::unique_ptr<enterprise_connectors::ConnectorDataPipeGetter>
-          data_pipe_getter);
-
-  // Called after `data_pipe_getter_` is known to be initialized to a correct
-  // state.
-  void SendContentNow(std::unique_ptr<network::ResourceRequest> request);
+  void OnMetadataUploadCompleted(
+      base::TimeTicks start_time,
+      std::optional<std::string> response_body) override;
 
   // Called whenever a content request finishes (on success or failure).
-  void OnSendContentCompleted(base::TimeTicks start_time,
-                              std::optional<std::string> response_body);
-
-  // Returns true if all of the following conditions are met:
-  //    1. The HTTP status is OK.
-  //    2. The `headers` have `upload_status` and `upload_url`.
-  //    3. The `upload_status` is "active".
-  // This method also has the side effect of setting upload_url_.
-  bool CanUploadContent(const scoped_refptr<net::HttpResponseHeaders>& headers);
-
-  // Returns true if `kEnableEncryptedFileUpload`
-  // feature is enabled and the `scan_type_` is ASYNC.
-  bool ShouldUploadEncryptedFile();
-
-  // Helper used by metrics logging code.
-  std::string GetRequestType();
-
-  // The result returned by BinaryUploadService::Request::GetRequestData() when
-  // retrieving the data.
-  enterprise_connectors::ScanRequestUploadResult get_data_result_;
-
-  bool is_obfuscated_ = false;
-
-  enum {
-    PENDING = 0,
-    METADATA_ONLY = 1,
-    FULL_CONTENT = 2,
-    ASYNC = 3
-  } scan_type_ = PENDING;
-
-  ContentUploadedCallback content_uploaded_callback_;
-
-  bool force_sync_upload_ = false;
-
-  base::WeakPtrFactory<ResumableUploadRequest> weak_factory_{this};
+  void OnSendContentCompleted(
+      base::TimeTicks start_time,
+      std::optional<std::string> response_body) override;
 };
 }  // namespace safe_browsing
 
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 d8d7fc0a..2562491 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
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 #include "chrome/browser/safe_browsing/cloud_content_scanning/resumable_uploader.h"
 
+// TODO(crbug.com/456489971): Move unit tests
 #include <memory>
 
 #include "base/base64.h"
diff --git a/chrome/browser/sessions/tab_restore_browsertest.cc b/chrome/browser/sessions/tab_restore_browsertest.cc
index 204366c..87e1ca81 100644
--- a/chrome/browser/sessions/tab_restore_browsertest.cc
+++ b/chrome/browser/sessions/tab_restore_browsertest.cc
@@ -895,7 +895,7 @@
 // Simulates rejecting the unload handle on a single grouped tab when:
 // - The group is closing - in this case we should ungroup the tabs
 // - The group is not closing - in this case we should do nothinig
-// TODO(crbug.com/370559961): This is a regression test. See bug for more info.
+// This is a regression test. See crbug.com/370559961 for more info.
 IN_PROC_BROWSER_TEST_F(TabRestoreTest, KeepTabWhenUnloadHandlerRejected) {
   ASSERT_TRUE(browser()->tab_strip_model()->SupportsTabGroups());
 
diff --git a/chrome/browser/signin/chrome_signin_client.cc b/chrome/browser/signin/chrome_signin_client.cc
index 01228a47..0d221dad 100644
--- a/chrome/browser/signin/chrome_signin_client.cc
+++ b/chrome/browser/signin/chrome_signin_client.cc
@@ -357,8 +357,6 @@
   // `signin_metrics::ProfileSignout::kRevokeSyncFromSettings` when the user
   // turns off sync from the settings, we should also keep the window open at
   // this point.
-  // TODO(crbug.com/40280466): Check for managed accounts to be modified
-  // when aligning Managed vs Consumer accounts.
   bool user_declines_sync_after_consenting_to_management =
       (signout_source_metric == signin_metrics::ProfileSignout::kAbortSignin ||
        signout_source_metric ==
diff --git a/chrome/browser/signin/dice_browsertest.cc b/chrome/browser/signin/dice_browsertest.cc
index 5e143a7c..b1ef9e0 100644
--- a/chrome/browser/signin/dice_browsertest.cc
+++ b/chrome/browser/signin/dice_browsertest.cc
@@ -1651,8 +1651,13 @@
       builder.AsPrimary(signin::ConsentLevel::kSync)
           .WithAccessPoint(signin_metrics::AccessPoint::kWebSignin)
           .Build(kMainGmailEmail));
-  ASSERT_EQ(signin::GetPrimaryAccountConsentLevel(GetIdentityManager()),
-            signin::ConsentLevel::kSync);
+  syncer::SyncService* sync_service =
+      SyncServiceFactory::GetForProfile(profile);
+  // TODO(crbug.com/464457988): Mark sync setup as complete by default in the
+  // sign-in helper method.
+  sync_service->GetUserSettings()->SetInitialSyncFeatureSetupComplete(
+      syncer::SyncFirstSetupCompleteSource::BASIC_FLOW);
+  ASSERT_TRUE(sync_service->IsSyncFeatureEnabled());
 
   ASSERT_FALSE(profile->GetPrefs()->GetBoolean(prefs::kExplicitBrowserSignin));
 
@@ -2203,7 +2208,15 @@
   ASSERT_TRUE(deleted_profiles.empty());
 
   // Sign the profile in.
+  // Using `kSignin` leads to test failure for some reason but should be
+  // addressed under crbug.com/417950948.
   SetupSignedInAccounts(signin::ConsentLevel::kSync);
+  syncer::SyncService* sync_service =
+      SyncServiceFactory::GetForProfile(browser()->profile());
+  // TODO(crbug.com/464457988): Mark sync setup as complete by default in the
+  // sign-in helper method.
+  sync_service->GetUserSettings()->SetInitialSyncFeatureSetupComplete(
+      syncer::SyncFirstSetupCompleteSource::BASIC_FLOW);
   enterprise_util::SetUserAcceptedAccountManagement(browser()->profile(), true);
 
   // Prohibit sign-in on next start-up.
diff --git a/chrome/browser/signin/signin_util.cc b/chrome/browser/signin/signin_util.cc
index d721bba3..6f35261c 100644
--- a/chrome/browser/signin/signin_util.cc
+++ b/chrome/browser/signin/signin_util.cc
@@ -567,6 +567,7 @@
         kEnterpriseManagementDisclaimerAfterSignin:
     case signin_metrics::AccessPoint::kNtpFeaturePromo:
     case signin_metrics::AccessPoint::kEnterpriseDialogAfterSigninInterception:
+    case signin_metrics::AccessPoint::kCredentialExchangeImport:
       return true;
   }
 }
diff --git a/chrome/browser/site_protection/site_familiarity_utils.cc b/chrome/browser/site_protection/site_familiarity_utils.cc
index 1f84125..633cbb7 100644
--- a/chrome/browser/site_protection/site_familiarity_utils.cc
+++ b/chrome/browser/site_protection/site_familiarity_utils.cc
@@ -40,6 +40,12 @@
 ComputeDefaultJavascriptOptimizerSetting(Profile* profile) {
   HostContentSettingsMap* host_content_settings_map =
       HostContentSettingsMapFactory::GetForProfile(profile);
+  if (!host_content_settings_map) {
+    // If there is no HostContentSettingsMap, assume that this is being called
+    // during the startup sequence for the profile picker. Return that
+    // JavaScript optimizers are allowed.
+    return content_settings::JavascriptOptimizerSetting::kAllowed;
+  }
   content_settings::ProviderType content_setting_provider;
   const auto default_content_setting =
       host_content_settings_map->GetDefaultContentSetting(
diff --git a/chrome/browser/sync/test/integration/single_client_polling_sync_test.cc b/chrome/browser/sync/test/integration/single_client_polling_sync_test.cc
index 00ba66f5..e17bb14 100644
--- a/chrome/browser/sync/test/integration/single_client_polling_sync_test.cc
+++ b/chrome/browser/sync/test/integration/single_client_polling_sync_test.cc
@@ -62,24 +62,6 @@
                          GetSyncTestModes(),
                          testing::PrintToStringParamName());
 
-// Some tests are flaky on Chromeos when run with IP Protection enabled.
-// TODO(crbug.com/40935754): Fix flakes.
-class SingleClientPollingSyncTestNoIpProt : public SingleClientPollingSyncTest {
- public:
-  SingleClientPollingSyncTestNoIpProt() {
-    feature_list_.InitAndDisableFeature(
-        net::features::kEnableIpProtectionProxy);
-  }
-
- private:
-  base::test::ScopedFeatureList feature_list_;
-};
-
-INSTANTIATE_TEST_SUITE_P(,
-                         SingleClientPollingSyncTestNoIpProt,
-                         GetSyncTestModes(),
-                         testing::PrintToStringParamName());
-
 // This test verifies that the poll interval in prefs gets initialized if no
 // data is available yet.
 IN_PROC_BROWSER_TEST_P(SingleClientPollingSyncTest, ShouldInitializePollPrefs) {
@@ -98,7 +80,7 @@
 // This test verifies that updates of the poll interval get persisted
 // That's important make sure clients with short live times will eventually poll
 // (e.g. Android).
-IN_PROC_BROWSER_TEST_P(SingleClientPollingSyncTestNoIpProt,
+IN_PROC_BROWSER_TEST_P(SingleClientPollingSyncTest,
                        PRE_ShouldUsePollIntervalFromPrefs) {
   ASSERT_TRUE(SetupSync());
 
@@ -121,7 +103,7 @@
   EXPECT_THAT(transport_data_prefs.GetPollInterval().InSeconds(), Eq(67));
 }
 
-IN_PROC_BROWSER_TEST_P(SingleClientPollingSyncTestNoIpProt,
+IN_PROC_BROWSER_TEST_P(SingleClientPollingSyncTest,
                        ShouldUsePollIntervalFromPrefs) {
   // Execute a sync cycle and verify this cycle used that interval.
   // This test assumes the SyncScheduler reads the actual interval from the
diff --git a/chrome/browser/sync/test/integration/single_client_preferences_sync_test.cc b/chrome/browser/sync/test/integration/single_client_preferences_sync_test.cc
index 15b58c9..becf1f5 100644
--- a/chrome/browser/sync/test/integration/single_client_preferences_sync_test.cc
+++ b/chrome/browser/sync/test/integration/single_client_preferences_sync_test.cc
@@ -119,9 +119,21 @@
   return result;
 }
 
-class SingleClientPreferencesSyncTest : public SyncTest {
+class SingleClientPreferencesSyncTest
+    : public SyncTest,
+      public testing::WithParamInterface<SyncTest::SetupSyncMode> {
  public:
-  SingleClientPreferencesSyncTest() : SyncTest(SINGLE_CLIENT) {}
+  SingleClientPreferencesSyncTest() : SyncTest(SINGLE_CLIENT) {
+    if (GetSetupSyncMode() == SyncTest::SetupSyncMode::kSyncTransportOnly) {
+      feature_list_.InitWithFeatures(
+          /*enabled_features=*/{syncer::kReplaceSyncPromosWithSignInPromos,
+                                switches::kEnablePreferencesAccountStorage,
+                                // This is needed to enable prefs in transport
+                                // mode.
+                                syncer::kSeparateLocalAndAccountSearchEngines},
+          /*disabled_features=*/{});
+    }
+  }
 
   SingleClientPreferencesSyncTest(const SingleClientPreferencesSyncTest&) =
       delete;
@@ -130,6 +142,10 @@
 
   ~SingleClientPreferencesSyncTest() override = default;
 
+  SyncTest::SetupSyncMode GetSetupSyncMode() const override {
+    return GetParam();
+  }
+
  protected:
   void InjectPreferenceToFakeServer(syncer::DataType data_type,
                                     const char* name,
@@ -146,9 +162,16 @@
             /*client_tag=*/name, specifics,
             /*creation_time=*/0, /*last_modified_time=*/0));
   }
+
+  base::test::ScopedFeatureList feature_list_;
 };
 
-IN_PROC_BROWSER_TEST_F(SingleClientPreferencesSyncTest, Sanity) {
+INSTANTIATE_TEST_SUITE_P(,
+                         SingleClientPreferencesSyncTest,
+                         GetSyncTestModes(),
+                         testing::PrintToStringParamName());
+
+IN_PROC_BROWSER_TEST_P(SingleClientPreferencesSyncTest, Sanity) {
   ASSERT_TRUE(SetupSync());
 
   const bool kDefaultValue =
@@ -161,7 +184,7 @@
 
 // Regression test to verify that pagination during GetUpdates() contributes
 // properly to UMA histograms.
-IN_PROC_BROWSER_TEST_F(SingleClientPreferencesSyncTest,
+IN_PROC_BROWSER_TEST_P(SingleClientPreferencesSyncTest,
                        EmitDataTypeEntityChangeToUma) {
   const int kNumEntities = 17;
 
@@ -187,7 +210,7 @@
 
 // TODO(crbug.com/40200835): PRE_ tests are not supported on Android.
 #if !BUILDFLAG(IS_ANDROID)
-IN_PROC_BROWSER_TEST_F(SingleClientPreferencesSyncTest,
+IN_PROC_BROWSER_TEST_P(SingleClientPreferencesSyncTest,
                        PRE_PersistProgressMarkerOnRestart) {
   sync_pb::EntitySpecifics specifics;
   specifics.mutable_preference()->set_name("testing.my-test-preference");
@@ -205,7 +228,7 @@
                    syncer::DataTypeEntityChange::kRemoteInitialUpdate));
 }
 
-IN_PROC_BROWSER_TEST_F(SingleClientPreferencesSyncTest,
+IN_PROC_BROWSER_TEST_P(SingleClientPreferencesSyncTest,
                        PersistProgressMarkerOnRestart) {
   sync_pb::EntitySpecifics specifics;
   specifics.mutable_preference()->set_name("testing.my-test-preference");
@@ -237,7 +260,7 @@
 // Verifies that priority synced preferences and regular synced preferences are
 // kept separate. Tests that incoming priority preference change does not have
 // any effect if the corresponding pref is registered as a regular preference.
-IN_PROC_BROWSER_TEST_F(SingleClientPreferencesSyncTest,
+IN_PROC_BROWSER_TEST_P(SingleClientPreferencesSyncTest,
                        ShouldIsolatePreferencesOfDifferentTypes) {
   // Register a pref as regular synced with client.
   ASSERT_TRUE(SetupClients());
@@ -281,7 +304,12 @@
   base::test::ScopedFeatureList feature_list_;
 };
 
-IN_PROC_BROWSER_TEST_F(SingleClientPreferencesWithAccountStorageSyncTest,
+INSTANTIATE_TEST_SUITE_P(,
+                         SingleClientPreferencesWithAccountStorageSyncTest,
+                         GetSyncTestModes(),
+                         testing::PrintToStringParamName());
+
+IN_PROC_BROWSER_TEST_P(SingleClientPreferencesWithAccountStorageSyncTest,
                        ShouldPreserveLocalPrefsAndNotUploadToAccountOnSignin) {
   ASSERT_TRUE(SetupClients());
   // Register `sync_preferences::kSyncablePrefForTesting`.
@@ -311,7 +339,7 @@
 // ChromeOS does not support signing out of a primary account.
 #if !BUILDFLAG(IS_CHROMEOS)
 
-IN_PROC_BROWSER_TEST_F(SingleClientPreferencesWithAccountStorageSyncTest,
+IN_PROC_BROWSER_TEST_P(SingleClientPreferencesWithAccountStorageSyncTest,
                        ShouldCleanupAccountStoreOnSignout) {
   ASSERT_TRUE(SetupClients());
   // Register `sync_preferences::kSyncablePrefForTesting`.
@@ -348,7 +376,7 @@
 
 #endif  // !BUILDFLAG(IS_CHROMEOS)
 
-IN_PROC_BROWSER_TEST_F(SingleClientPreferencesWithAccountStorageSyncTest,
+IN_PROC_BROWSER_TEST_P(SingleClientPreferencesWithAccountStorageSyncTest,
                        ShouldCleanupAccountStoreOnDisable) {
   ASSERT_TRUE(SetupClients());
   // Register `sync_preferences::kSyncablePrefForTesting`.
@@ -384,7 +412,7 @@
             "local value");
 }
 
-IN_PROC_BROWSER_TEST_F(SingleClientPreferencesWithAccountStorageSyncTest,
+IN_PROC_BROWSER_TEST_P(SingleClientPreferencesWithAccountStorageSyncTest,
                        ShouldChangeSyncablePrefLocallyAndOnAccount) {
   ASSERT_TRUE(SetupClients());
   // Register `sync_preferences::kSyncablePrefForTesting`.
@@ -430,7 +458,7 @@
             "new value");
 }
 
-IN_PROC_BROWSER_TEST_F(SingleClientPreferencesWithAccountStorageSyncTest,
+IN_PROC_BROWSER_TEST_P(SingleClientPreferencesWithAccountStorageSyncTest,
                        ShouldNotSyncNonSyncablePrefToAccount) {
   constexpr char kNonSyncablePref[] = "non-syncable pref";
 
@@ -471,7 +499,7 @@
           .Wait());
 }
 
-IN_PROC_BROWSER_TEST_F(SingleClientPreferencesWithAccountStorageSyncTest,
+IN_PROC_BROWSER_TEST_P(SingleClientPreferencesWithAccountStorageSyncTest,
                        ShouldNotSyncSensitivePrefsIfHistorySyncOff) {
   ASSERT_TRUE(SetupClients());
   // Register `sync_preferences::kSyncableHistorySensitiveListPrefForTesting`.
@@ -537,7 +565,7 @@
             ConvertPrefValueToValueInSpecifics(base::Value(new_value.Clone())));
 }
 
-IN_PROC_BROWSER_TEST_F(SingleClientPreferencesWithAccountStorageSyncTest,
+IN_PROC_BROWSER_TEST_P(SingleClientPreferencesWithAccountStorageSyncTest,
                        ShouldSyncSensitivePrefsIfHistorySyncOn) {
   ASSERT_TRUE(SetupClients());
   // Register `sync_preferences::kSyncableHistorySensitiveListPrefForTesting`.
@@ -588,7 +616,7 @@
           .Wait());
 }
 
-IN_PROC_BROWSER_TEST_F(SingleClientPreferencesWithAccountStorageSyncTest,
+IN_PROC_BROWSER_TEST_P(SingleClientPreferencesWithAccountStorageSyncTest,
                        ShouldListenToHistorySyncOptInChanges) {
   ASSERT_TRUE(SetupClients());
   // Register `sync_preferences::kSyncableHistorySensitiveListPrefForTesting`.
@@ -656,7 +684,7 @@
 // Regression test for crbug.com/1456872.
 // ChromeOS does not support signing out of a primary account.
 #if !BUILDFLAG(IS_CHROMEOS)
-IN_PROC_BROWSER_TEST_F(SingleClientPreferencesWithAccountStorageSyncTest,
+IN_PROC_BROWSER_TEST_P(SingleClientPreferencesWithAccountStorageSyncTest,
                        ShouldHandleWalletSideEffectsWhenSyncDisabled) {
   ASSERT_TRUE(SetupClients());
   ASSERT_FALSE(
@@ -691,7 +719,7 @@
 
 #if !BUILDFLAG(IS_ANDROID)
 
-IN_PROC_BROWSER_TEST_F(SingleClientPreferencesWithAccountStorageSyncTest,
+IN_PROC_BROWSER_TEST_P(SingleClientPreferencesWithAccountStorageSyncTest,
                        ShouldCleanupAccountPreferencesFileOnDisable) {
   ASSERT_TRUE(SetupClients());
   // Register `sync_preferences::kSyncablePrefForTesting`.
@@ -751,7 +779,7 @@
 
 #if !BUILDFLAG(IS_CHROMEOS)
 
-IN_PROC_BROWSER_TEST_F(SingleClientPreferencesWithAccountStorageSyncTest,
+IN_PROC_BROWSER_TEST_P(SingleClientPreferencesWithAccountStorageSyncTest,
                        ShouldCleanupAccountPreferencesFileOnSignout) {
   ASSERT_TRUE(SetupClients());
   // Register `sync_preferences::kSyncablePrefForTesting`.
@@ -811,7 +839,7 @@
 // TODO(crbug.com/40200835): PRE_ tests are not supported on Android.
 #if !BUILDFLAG(IS_ANDROID)
 // Adds pref values to persistent storage.
-IN_PROC_BROWSER_TEST_F(
+IN_PROC_BROWSER_TEST_P(
     SingleClientPreferencesWithAccountStorageSyncTest,
     PRE_ShouldReadAccountPreferencesFromFileBeforeSyncStart) {
   ASSERT_TRUE(SetupClients());
@@ -837,7 +865,7 @@
             "account value");
 }
 
-IN_PROC_BROWSER_TEST_F(SingleClientPreferencesWithAccountStorageSyncTest,
+IN_PROC_BROWSER_TEST_P(SingleClientPreferencesWithAccountStorageSyncTest,
                        ShouldReadAccountPreferencesFromFileBeforeSyncStart) {
   ASSERT_TRUE(SetupClients());
   // Register `sync_preferences::kSyncablePrefForTesting`.
@@ -854,7 +882,7 @@
 }
 
 // Adds pref values to persistent storage.
-IN_PROC_BROWSER_TEST_F(SingleClientPreferencesWithAccountStorageSyncTest,
+IN_PROC_BROWSER_TEST_P(SingleClientPreferencesWithAccountStorageSyncTest,
                        PRE_ShouldNotNotifyUponSyncStart) {
   ASSERT_TRUE(SetupClients());
   // Register `sync_preferences::kSyncablePrefForTesting`.
@@ -875,7 +903,7 @@
 }
 
 // Regression test for crbug.com/1470161.
-IN_PROC_BROWSER_TEST_F(SingleClientPreferencesWithAccountStorageSyncTest,
+IN_PROC_BROWSER_TEST_P(SingleClientPreferencesWithAccountStorageSyncTest,
                        ShouldNotNotifyUponSyncStart) {
   ASSERT_TRUE(SetupClients());
   // Register `sync_preferences::kSyncablePrefForTesting`.
@@ -912,12 +940,141 @@
       GetPrefs(0)->GetBoolean(sync_preferences::kSyncablePrefForTesting));
 }
 
+// ChromeOS does not support sign-in allowed flag.
+#if !BUILDFLAG(IS_CHROMEOS)
+IN_PROC_BROWSER_TEST_P(
+    SingleClientPreferencesWithAccountStorageSyncTest,
+    PRE_ShouldClearAccountDataOnStartupIfSignInAllowedBitChanged) {
+  ASSERT_TRUE(SetupClients());
+  GetRegistry(GetProfile(0))
+      ->RegisterStringPref(sync_preferences::kSyncablePrefForTesting, "",
+                           user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
+
+  // Set the sign-in allowed bit to true initially.
+  preferences_helper::GetPrefs(/*index=*/0)
+      ->SetBoolean(prefs::kSigninAllowedOnNextStartup, true);
+
+  // Set local value of the test pref.
+  preferences_helper::ChangeStringPref(
+      0, sync_preferences::kSyncablePrefForTesting, "local value");
+
+  InjectPreferenceToFakeServer(syncer::PREFERENCES,
+                               sync_preferences::kSyncablePrefForTesting,
+                               base::Value("account value"));
+
+  ASSERT_TRUE(GetClient(0)->SignInPrimaryAccount());
+  ASSERT_TRUE(GetClient(0)->AwaitSyncTransportActive());
+
+  // Account value is effective.
+  ASSERT_EQ(GetPrefs(0)->GetString(sync_preferences::kSyncablePrefForTesting),
+            "account value");
+
+  // Simulate turning off the sign-in allowed bit on the settings page.
+  preferences_helper::GetPrefs(/*index=*/0)
+      ->SetBoolean(prefs::kSigninAllowedOnNextStartup, false);
+}
+
+IN_PROC_BROWSER_TEST_P(
+    SingleClientPreferencesWithAccountStorageSyncTest,
+    ShouldClearAccountDataOnStartupIfSignInAllowedBitChanged) {
+  base::HistogramTester histogram_tester;
+  ASSERT_TRUE(SetupClients());
+  GetRegistry(GetProfile(0))
+      ->RegisterStringPref(sync_preferences::kSyncablePrefForTesting, "",
+                           user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
+
+  // Original local value should get re-applied.
+  // Note: The account value is not cleared instantaneously upon startup, but
+  // upon preferences sync initialization.
+  EXPECT_TRUE(PrefValueChecker(GetPrefs(0),
+                               sync_preferences::kSyncablePrefForTesting,
+                               base::Value("local value"))
+                  .Wait());
+  // Clearing happened with StayStoppedAndMaybeClearData() called upon startup.
+  histogram_tester.ExpectUniqueSample(
+      "Sync.SyncableService.MaybeClearDataIfMetadataEmptyOrInvalid.PREFERENCE",
+      true,
+      /*expected_bucket_count=*/1);
+}
+#endif  // !BUILDFLAG(IS_CHROMEOS)
+
+IN_PROC_BROWSER_TEST_P(
+    SingleClientPreferencesWithAccountStorageSyncTest,
+    PRE_ShouldClearAccountDataOnStartupIfAccountStateChanged) {
+  ASSERT_TRUE(SetupClients());
+  GetRegistry(GetProfile(0))
+      ->RegisterStringPref(sync_preferences::kSyncablePrefForTesting, "",
+                           user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
+
+  // Set local value of the test pref.
+  preferences_helper::ChangeStringPref(
+      0, sync_preferences::kSyncablePrefForTesting, "local value");
+
+  InjectPreferenceToFakeServer(syncer::PREFERENCES,
+                               sync_preferences::kSyncablePrefForTesting,
+                               base::Value("account value"));
+
+#if BUILDFLAG(IS_CHROMEOS)
+  ASSERT_TRUE(SetupSync());
+#else
+  ASSERT_TRUE(GetClient(0)->SignInPrimaryAccount());
+  ASSERT_TRUE(GetClient(0)->AwaitSyncTransportActive());
+#endif  // BUILDFLAG(IS_CHROMEOS)
+
+  // Account value is effective.
+  ASSERT_EQ(GetPrefs(0)->GetString(sync_preferences::kSyncablePrefForTesting),
+            "account value");
+
+  // Simulate a data type error to prevent clearing of account data.
+  GetSyncService(0)->ReportDataTypeErrorForTest(syncer::PREFERENCES);
+#if BUILDFLAG(IS_CHROMEOS)
+  // Disable sync.
+  ASSERT_TRUE(GetClient(0)->DisableSelectableType(
+      syncer::UserSelectableType::kPreferences));
+#else
+  // Sign out. Account value should stay because of the data type error.
+  GetClient(0)->SignOutPrimaryAccount();
+#endif  // BUILDFLAG(IS_CHROMEOS)
+  // Local value is not restored because stop sync is not called when there is a
+  // data type error.
+  ASSERT_EQ(GetPrefs(0)->GetString(sync_preferences::kSyncablePrefForTesting),
+            "account value");
+
+  ExcludeDataTypesFromCheckForDataTypeFailures({syncer::PREFERENCES});
+}
+
+IN_PROC_BROWSER_TEST_P(SingleClientPreferencesWithAccountStorageSyncTest,
+                       ShouldClearAccountDataOnStartupIfAccountStateChanged) {
+  base::HistogramTester histogram_tester;
+  ASSERT_TRUE(SetupClients());
+  GetRegistry(GetProfile(0))
+      ->RegisterStringPref(sync_preferences::kSyncablePrefForTesting, "",
+                           user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
+
+  // Original local value should get re-applied.
+  // Note: The account value is not cleared instantaneously upon startup, but
+  // upon preferences sync initialization.
+  EXPECT_TRUE(PrefValueChecker(GetPrefs(0),
+                               sync_preferences::kSyncablePrefForTesting,
+                               base::Value("local value"))
+                  .Wait());
+  // Clearing happened with StayStoppedAndMaybeClearData() called upon startup.
+  histogram_tester.ExpectUniqueSample(
+      "Sync.SyncableService.MaybeClearDataIfMetadataEmptyOrInvalid.PREFERENCE",
+      true,
+      /*expected_bucket_count=*/1);
+}
 #endif  // !BUILDFLAG(IS_ANDROID)
 
 using SingleClientPreferencesWithAccountStorageMergeSyncTest =
     SingleClientPreferencesWithAccountStorageSyncTest;
 
-IN_PROC_BROWSER_TEST_F(SingleClientPreferencesWithAccountStorageMergeSyncTest,
+INSTANTIATE_TEST_SUITE_P(,
+                         SingleClientPreferencesWithAccountStorageMergeSyncTest,
+                         GetSyncTestModes(),
+                         testing::PrintToStringParamName());
+
+IN_PROC_BROWSER_TEST_P(SingleClientPreferencesWithAccountStorageMergeSyncTest,
                        ShouldMergeLocalAndAccountMergeableDictPref) {
   ASSERT_TRUE(SetupClients());
   ASSERT_FALSE(
@@ -970,7 +1127,7 @@
             local_value);
 }
 
-IN_PROC_BROWSER_TEST_F(SingleClientPreferencesWithAccountStorageMergeSyncTest,
+IN_PROC_BROWSER_TEST_P(SingleClientPreferencesWithAccountStorageMergeSyncTest,
                        ShouldUnmergeMergeableDictPrefUponUpdate) {
   ASSERT_TRUE(SetupClients());
   ASSERT_FALSE(
@@ -1055,7 +1212,7 @@
             updated_local_value);
 }
 
-IN_PROC_BROWSER_TEST_F(SingleClientPreferencesWithAccountStorageMergeSyncTest,
+IN_PROC_BROWSER_TEST_P(SingleClientPreferencesWithAccountStorageMergeSyncTest,
                        ShouldMergeLocalAndAccountMergeableListPref) {
   ASSERT_TRUE(SetupClients());
   // Register `kSyncableMergeableListPrefForTesting`.
@@ -1103,7 +1260,7 @@
             local_value);
 }
 
-IN_PROC_BROWSER_TEST_F(SingleClientPreferencesWithAccountStorageMergeSyncTest,
+IN_PROC_BROWSER_TEST_P(SingleClientPreferencesWithAccountStorageMergeSyncTest,
                        ShouldUnmergeMergeableListPrefUponUpdate) {
   ASSERT_TRUE(SetupClients());
   // Register `kSyncableMergeableListPrefForTesting`.
@@ -1186,7 +1343,14 @@
   base::test::ScopedFeatureList feature_list_;
 };
 
-IN_PROC_BROWSER_TEST_F(
+INSTANTIATE_TEST_SUITE_P(
+    ,
+    SingleClientPreferencesWithMigrateAccountPrefsDisabledSyncTest,
+    // Only transport mode is supported on Android.
+    testing::Values(SyncTest::SetupSyncMode::kSyncTransportOnly),
+    testing::PrintToStringParamName());
+
+IN_PROC_BROWSER_TEST_P(
     SingleClientPreferencesWithMigrateAccountPrefsDisabledSyncTest,
     ShouldCreateAccountPrefsFile) {
   ASSERT_TRUE(SetupClients());
@@ -1213,7 +1377,7 @@
   EXPECT_TRUE(base::PathExists(AccountPreferencesFilePath()));
 }
 
-IN_PROC_BROWSER_TEST_F(
+IN_PROC_BROWSER_TEST_P(
     SingleClientPreferencesWithMigrateAccountPrefsDisabledSyncTest,
     ShouldCleanupAccountPreferencesFileOnDisable) {
   ASSERT_TRUE(SetupClients());
@@ -1270,7 +1434,7 @@
       file_content->FindString(sync_preferences::kSyncablePrefForTesting));
 }
 
-IN_PROC_BROWSER_TEST_F(
+IN_PROC_BROWSER_TEST_P(
     SingleClientPreferencesWithMigrateAccountPrefsDisabledSyncTest,
     ShouldCleanupAccountPreferencesFileOnSignout) {
   ASSERT_TRUE(SetupClients());
@@ -1337,7 +1501,14 @@
   base::test::ScopedFeatureList feature_list_;
 };
 
-IN_PROC_BROWSER_TEST_F(
+INSTANTIATE_TEST_SUITE_P(
+    ,
+    SingleClientPreferencesWithMigrateAccountPrefsEnabledSyncTest,
+    // Only transport mode is supported on Android.
+    testing::Values(SyncTest::SetupSyncMode::kSyncTransportOnly),
+    testing::PrintToStringParamName());
+
+IN_PROC_BROWSER_TEST_P(
     SingleClientPreferencesWithMigrateAccountPrefsEnabledSyncTest,
     ShouldNotCreateAccountPrefsFile) {
   ASSERT_TRUE(SetupClients());
@@ -1364,7 +1535,7 @@
   EXPECT_FALSE(base::PathExists(AccountPreferencesFilePath()));
 }
 
-IN_PROC_BROWSER_TEST_F(
+IN_PROC_BROWSER_TEST_P(
     SingleClientPreferencesWithMigrateAccountPrefsEnabledSyncTest,
     ShouldDownloadAccountPrefsWithInitialSync) {
   ASSERT_TRUE(SetupClients());
@@ -1411,7 +1582,7 @@
   EXPECT_TRUE(account_pref_values_on_disk->empty());
 }
 
-IN_PROC_BROWSER_TEST_F(
+IN_PROC_BROWSER_TEST_P(
     SingleClientPreferencesWithMigrateAccountPrefsEnabledSyncTest,
     ShouldCleanupAccountPreferencesFileOnDisable) {
   ASSERT_TRUE(SetupClients());
@@ -1470,7 +1641,7 @@
       file_content->FindString(sync_preferences::kSyncablePrefForTesting));
 }
 
-IN_PROC_BROWSER_TEST_F(
+IN_PROC_BROWSER_TEST_P(
     SingleClientPreferencesWithMigrateAccountPrefsEnabledSyncTest,
     ShouldCleanupAccountPreferencesFileOnSignout) {
   ASSERT_TRUE(SetupClients());
@@ -1560,7 +1731,12 @@
   }
 };
 
-IN_PROC_BROWSER_TEST_F(SingleClientTrackedPreferencesSyncTest,
+INSTANTIATE_TEST_SUITE_P(,
+                         SingleClientTrackedPreferencesSyncTest,
+                         GetSyncTestModes(),
+                         testing::PrintToStringParamName());
+
+IN_PROC_BROWSER_TEST_P(SingleClientTrackedPreferencesSyncTest,
                        ShouldStoreUnprotectedPrefsInPreferencesFile) {
   ASSERT_TRUE(SetupClients());
   ASSERT_FALSE(
@@ -1603,7 +1779,7 @@
       << "Incorrect key " << account_pref_name << " in " << *secured_prefs;
 }
 
-IN_PROC_BROWSER_TEST_F(SingleClientTrackedPreferencesSyncTest,
+IN_PROC_BROWSER_TEST_P(SingleClientTrackedPreferencesSyncTest,
                        ShouldStoreProtectedPrefsInCorrectPreferencesFile) {
   ASSERT_TRUE(SetupClients());
   ASSERT_FALSE(
@@ -1658,7 +1834,7 @@
       << "Missing key " << account_pref_name << " in " << *secured_prefs;
 }
 
-IN_PROC_BROWSER_TEST_F(SingleClientTrackedPreferencesSyncTest,
+IN_PROC_BROWSER_TEST_P(SingleClientTrackedPreferencesSyncTest,
                        ShouldHashTrackedSyncablePrefs) {
   const char kHashesPrefName[] = "protection.macs";
 
@@ -1702,7 +1878,7 @@
       << protection_values.value();
 }
 
-IN_PROC_BROWSER_TEST_F(SingleClientTrackedPreferencesSyncTest,
+IN_PROC_BROWSER_TEST_P(SingleClientTrackedPreferencesSyncTest,
                        PRE_ShouldLoadTrackedSyncablePrefsBeforeSyncStart) {
   ASSERT_TRUE(SetupClients());
   ASSERT_FALSE(
@@ -1722,7 +1898,7 @@
   ASSERT_TRUE(GetPrefs(0)->GetBoolean(kProtectedPrefName));
 }
 
-IN_PROC_BROWSER_TEST_F(SingleClientTrackedPreferencesSyncTest,
+IN_PROC_BROWSER_TEST_P(SingleClientTrackedPreferencesSyncTest,
                        ShouldLoadTrackedSyncablePrefsBeforeSyncStart) {
   ASSERT_TRUE(SetupClients());
   ASSERT_FALSE(
@@ -1783,7 +1959,12 @@
   base::HistogramTester histogram_tester_;
 };
 
-IN_PROC_BROWSER_TEST_F(SingleClientTrackedPreferencesSyncTestWithAttack,
+INSTANTIATE_TEST_SUITE_P(,
+                         SingleClientTrackedPreferencesSyncTestWithAttack,
+                         GetSyncTestModes(),
+                         testing::PrintToStringParamName());
+
+IN_PROC_BROWSER_TEST_P(SingleClientTrackedPreferencesSyncTestWithAttack,
                        PRE_ShouldProtectTrackedSyncablePrefs) {
   ASSERT_TRUE(SetupClients());
   ASSERT_FALSE(
@@ -1804,7 +1985,7 @@
   ASSERT_FALSE(GetPrefs(0)->GetBoolean(kProtectedPrefName));
 }
 
-IN_PROC_BROWSER_TEST_F(SingleClientTrackedPreferencesSyncTestWithAttack,
+IN_PROC_BROWSER_TEST_P(SingleClientTrackedPreferencesSyncTestWithAttack,
                        ShouldProtectTrackedSyncablePrefs) {
   ASSERT_TRUE(SetupClients());
   ASSERT_FALSE(
@@ -1862,7 +2043,12 @@
   base::test::ScopedFeatureList feature_list_;
 };
 
-IN_PROC_BROWSER_TEST_F(SingleClientPreferencesGlicTieredRolloutTest, E2E) {
+INSTANTIATE_TEST_SUITE_P(,
+                         SingleClientPreferencesGlicTieredRolloutTest,
+                         GetSyncTestModes(),
+                         testing::PrintToStringParamName());
+
+IN_PROC_BROWSER_TEST_P(SingleClientPreferencesGlicTieredRolloutTest, E2E) {
   ASSERT_TRUE(SetupClients());
 
   // Have user be eligible for Glic from an account perspective.
@@ -1902,7 +2088,12 @@
   }
 };
 
-IN_PROC_BROWSER_TEST_F(SingleClientPreferencesSubscriptionEligibilityTest,
+INSTANTIATE_TEST_SUITE_P(,
+                         SingleClientPreferencesSubscriptionEligibilityTest,
+                         GetSyncTestModes(),
+                         testing::PrintToStringParamName());
+
+IN_PROC_BROWSER_TEST_P(SingleClientPreferencesSubscriptionEligibilityTest,
                        E2E) {
   ASSERT_TRUE(SetupClients());
 
@@ -1934,7 +2125,13 @@
   base::test::ScopedFeatureList feature_list_;
 };
 
-IN_PROC_BROWSER_TEST_F(
+INSTANTIATE_TEST_SUITE_P(
+    ,
+    SingleClientDecouplePriorityPreferencesSyncTestWithFlagDisabled,
+    GetSyncTestModes(),
+    testing::PrintToStringParamName());
+
+IN_PROC_BROWSER_TEST_P(
     SingleClientDecouplePriorityPreferencesSyncTestWithFlagDisabled,
     InactiveWhenUserToggleIsOff) {
   ASSERT_TRUE(SetupClients());
@@ -1970,7 +2167,12 @@
       syncer::kSyncSupportAlwaysSyncingPriorityPreferences};
 };
 
-IN_PROC_BROWSER_TEST_F(SingleClientDecouplePriorityPreferencesSyncTest,
+INSTANTIATE_TEST_SUITE_P(,
+                         SingleClientDecouplePriorityPreferencesSyncTest,
+                         GetSyncTestModes(),
+                         testing::PrintToStringParamName());
+
+IN_PROC_BROWSER_TEST_P(SingleClientDecouplePriorityPreferencesSyncTest,
                        ActiveWhenUserToggleIsOff) {
   ASSERT_TRUE(SetupClients());
   ASSERT_TRUE(GetSyncService(0)->GetActiveDataTypes().empty());
@@ -1999,7 +2201,7 @@
       syncer::PRIORITY_PREFERENCES));
 }
 
-IN_PROC_BROWSER_TEST_F(SingleClientDecouplePriorityPreferencesSyncTest,
+IN_PROC_BROWSER_TEST_P(SingleClientDecouplePriorityPreferencesSyncTest,
                        ShouldSyncAllowlistedPriorityPrefWithoutOptIn) {
   ASSERT_TRUE(SetupClients());
   // Regular priority pref.
@@ -2067,7 +2269,7 @@
       ConvertPrefValueToValueInSpecifics(base::Value("value1")));
 }
 
-IN_PROC_BROWSER_TEST_F(SingleClientDecouplePriorityPreferencesSyncTest,
+IN_PROC_BROWSER_TEST_P(SingleClientDecouplePriorityPreferencesSyncTest,
                        ShouldSyncRegularPriorityPrefWithOptIn) {
   ASSERT_TRUE(SetupClients());
   // Regular syncable pref.
@@ -2139,138 +2341,15 @@
   testing::NiceMock<policy::MockConfigurationPolicyProvider> policy_provider_;
 };
 
-IN_PROC_BROWSER_TEST_F(SingleClientFeatureListEarlyAccessTest,
+INSTANTIATE_TEST_SUITE_P(,
+                         SingleClientFeatureListEarlyAccessTest,
+                         GetSyncTestModes(),
+                         testing::PrintToStringParamName());
+
+IN_PROC_BROWSER_TEST_P(SingleClientFeatureListEarlyAccessTest,
                        ShouldNotCrashUponEarlyFeatureAccess) {
   ASSERT_TRUE(SetupClients());
 }
 #endif  // !BUILDFLAG(IS_ANDROID)
 
-// PRE_ tests are not supported on Android.
-#if !BUILDFLAG(IS_ANDROID)
-// ChromeOS does not support sign-in allowed flag.
-#if !BUILDFLAG(IS_CHROMEOS)
-IN_PROC_BROWSER_TEST_F(
-    SingleClientPreferencesWithAccountStorageSyncTest,
-    PRE_ShouldClearAccountDataOnStartupIfSignInAllowedBitChanged) {
-  ASSERT_TRUE(SetupClients());
-  GetRegistry(GetProfile(0))
-      ->RegisterStringPref(sync_preferences::kSyncablePrefForTesting, "",
-                           user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
-
-  // Set the sign-in allowed bit to true initially.
-  preferences_helper::GetPrefs(/*index=*/0)
-      ->SetBoolean(prefs::kSigninAllowedOnNextStartup, true);
-
-  // Set local value of the test pref.
-  preferences_helper::ChangeStringPref(
-      0, sync_preferences::kSyncablePrefForTesting, "local value");
-
-  InjectPreferenceToFakeServer(syncer::PREFERENCES,
-                               sync_preferences::kSyncablePrefForTesting,
-                               base::Value("account value"));
-
-  ASSERT_TRUE(GetClient(0)->SignInPrimaryAccount());
-  ASSERT_TRUE(GetClient(0)->AwaitSyncTransportActive());
-
-  // Account value is effective.
-  ASSERT_EQ(GetPrefs(0)->GetString(sync_preferences::kSyncablePrefForTesting),
-            "account value");
-
-  // Simulate turning off the sign-in allowed bit on the settings page.
-  preferences_helper::GetPrefs(/*index=*/0)
-      ->SetBoolean(prefs::kSigninAllowedOnNextStartup, false);
-}
-
-IN_PROC_BROWSER_TEST_F(
-    SingleClientPreferencesWithAccountStorageSyncTest,
-    ShouldClearAccountDataOnStartupIfSignInAllowedBitChanged) {
-  base::HistogramTester histogram_tester;
-  ASSERT_TRUE(SetupClients());
-  GetRegistry(GetProfile(0))
-      ->RegisterStringPref(sync_preferences::kSyncablePrefForTesting, "",
-                           user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
-
-  // Original local value should get re-applied.
-  // Note: The account value is not cleared instantaneously upon startup, but
-  // upon preferences sync initialization.
-  EXPECT_TRUE(PrefValueChecker(GetPrefs(0),
-                               sync_preferences::kSyncablePrefForTesting,
-                               base::Value("local value"))
-                  .Wait());
-  // Clearing happened with StayStoppedAndMaybeClearData() called upon startup.
-  histogram_tester.ExpectUniqueSample(
-      "Sync.SyncableService.MaybeClearDataIfMetadataEmptyOrInvalid.PREFERENCE",
-      true,
-      /*expected_bucket_count=*/1);
-}
-#endif  // !BUILDFLAG(IS_CHROMEOS)
-
-IN_PROC_BROWSER_TEST_F(
-    SingleClientPreferencesWithAccountStorageSyncTest,
-    PRE_ShouldClearAccountDataOnStartupIfAccountStateChanged) {
-  ASSERT_TRUE(SetupClients());
-  GetRegistry(GetProfile(0))
-      ->RegisterStringPref(sync_preferences::kSyncablePrefForTesting, "",
-                           user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
-
-  // Set local value of the test pref.
-  preferences_helper::ChangeStringPref(
-      0, sync_preferences::kSyncablePrefForTesting, "local value");
-
-  InjectPreferenceToFakeServer(syncer::PREFERENCES,
-                               sync_preferences::kSyncablePrefForTesting,
-                               base::Value("account value"));
-
-#if BUILDFLAG(IS_CHROMEOS)
-  ASSERT_TRUE(SetupSync());
-#else
-  ASSERT_TRUE(GetClient(0)->SignInPrimaryAccount());
-  ASSERT_TRUE(GetClient(0)->AwaitSyncTransportActive());
-#endif  // BUILDFLAG(IS_CHROMEOS)
-
-  // Account value is effective.
-  ASSERT_EQ(GetPrefs(0)->GetString(sync_preferences::kSyncablePrefForTesting),
-            "account value");
-
-  // Simulate a data type error to prevent clearing of account data.
-  GetSyncService(0)->ReportDataTypeErrorForTest(syncer::PREFERENCES);
-#if BUILDFLAG(IS_CHROMEOS)
-  // Disable sync.
-  ASSERT_TRUE(GetClient(0)->DisableSelectableType(
-      syncer::UserSelectableType::kPreferences));
-#else
-  // Sign out. Account value should stay because of the data type error.
-  GetClient(0)->SignOutPrimaryAccount();
-#endif  // BUILDFLAG(IS_CHROMEOS)
-  // Local value is not restored because stop sync is not called when there is a
-  // data type error.
-  ASSERT_EQ(GetPrefs(0)->GetString(sync_preferences::kSyncablePrefForTesting),
-            "account value");
-
-  ExcludeDataTypesFromCheckForDataTypeFailures({syncer::PREFERENCES});
-}
-
-IN_PROC_BROWSER_TEST_F(SingleClientPreferencesWithAccountStorageSyncTest,
-                       ShouldClearAccountDataOnStartupIfAccountStateChanged) {
-  base::HistogramTester histogram_tester;
-  ASSERT_TRUE(SetupClients());
-  GetRegistry(GetProfile(0))
-      ->RegisterStringPref(sync_preferences::kSyncablePrefForTesting, "",
-                           user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
-
-  // Original local value should get re-applied.
-  // Note: The account value is not cleared instantaneously upon startup, but
-  // upon preferences sync initialization.
-  EXPECT_TRUE(PrefValueChecker(GetPrefs(0),
-                               sync_preferences::kSyncablePrefForTesting,
-                               base::Value("local value"))
-                  .Wait());
-  // Clearing happened with StayStoppedAndMaybeClearData() called upon startup.
-  histogram_tester.ExpectUniqueSample(
-      "Sync.SyncableService.MaybeClearDataIfMetadataEmptyOrInvalid.PREFERENCE",
-      true,
-      /*expected_bucket_count=*/1);
-}
-#endif  // !BUILDFLAG(IS_ANDROID)
-
 }  // namespace
diff --git a/chrome/browser/sync/test/integration/single_client_standalone_transport_sync_test.cc b/chrome/browser/sync/test/integration/single_client_standalone_transport_sync_test.cc
index 962f47c..0958d37 100644
--- a/chrome/browser/sync/test/integration/single_client_standalone_transport_sync_test.cc
+++ b/chrome/browser/sync/test/integration/single_client_standalone_transport_sync_test.cc
@@ -16,6 +16,7 @@
 #include "chrome/browser/sync/test/integration/sync_service_impl_harness.h"
 #include "chrome/browser/sync/test/integration/sync_test.h"
 #include "chrome/common/chrome_paths.h"
+#include "components/browser_sync/browser_sync_switches.h"
 #include "components/commerce/core/commerce_feature_list.h"
 #include "components/data_sharing/public/features.h"
 #include "components/password_manager/core/browser/features/password_features.h"
@@ -648,5 +649,83 @@
 }
 #endif  // !BUILDFLAG(IS_CHROMEOS)
 
+// On ChromeOS, there exists no sync setup incomplete state.
+#if !BUILDFLAG(IS_CHROMEOS)
+class SyncSetupIncompleteMigrationSyncTest : public SyncTest {
+ public:
+  SyncSetupIncompleteMigrationSyncTest() : SyncTest(SINGLE_CLIENT) {
+    std::vector<base::test::FeatureRef> features = {
+        switches::kMigrateOutOfSyncSetupIncompleteState,
+        syncer::kUnoPhase2FollowUp};
+    std::vector<base::test::FeatureRef> enabled_features;
+    std::vector<base::test::FeatureRef> disabled_features = {
+        // This is disabled because if enabled, the sync to sign-in migration is
+        // triggered which also migrates the sync setup incomplete users.
+        switches::kMigrateSyncingUserToSignedIn};
+    if (content::IsPreTest()) {
+      disabled_features.insert(disabled_features.end(), features.begin(),
+                               features.end());
+    } else {
+      enabled_features.insert(enabled_features.end(), features.begin(),
+                              features.end());
+    }
+    feature_list_.InitWithFeatures(enabled_features, disabled_features);
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+IN_PROC_BROWSER_TEST_F(SyncSetupIncompleteMigrationSyncTest,
+                       PRE_MigratesSyncSetupIncompleteUser) {
+  ASSERT_TRUE(SetupClients());
+
+  // Using `SetupSyncWithCustomSettings` with `base::DoNothing()` doesn't set
+  // the sync-setup-complete bit.
+  ASSERT_TRUE(GetClient(0)->SetupSyncWithCustomSettings(base::DoNothing()));
+  ASSERT_TRUE(GetSyncService(0)->HasSyncConsent());
+  ASSERT_FALSE(GetSyncService(0)
+                   ->GetUserSettings()
+                   ->IsInitialSyncFeatureSetupComplete());
+}
+
+IN_PROC_BROWSER_TEST_F(SyncSetupIncompleteMigrationSyncTest,
+                       MigratesSyncSetupIncompleteUser) {
+  ASSERT_TRUE(SetupClients());
+  ASSERT_TRUE(GetClient(0)->AwaitSyncTransportActive());
+  // The user is not syncing anymore.
+  EXPECT_FALSE(GetSyncService(0)->HasSyncConsent());
+  // History is toggled off.
+  EXPECT_FALSE(GetSyncService(0)->GetUserSettings()->GetSelectedTypes().Has(
+      syncer::UserSelectableType::kHistory));
+}
+
+IN_PROC_BROWSER_TEST_F(SyncSetupIncompleteMigrationSyncTest,
+                       PRE_DoesNotMigrateSyncSetupCompleteUser) {
+  ASSERT_TRUE(SetupClients());
+
+  ASSERT_TRUE(GetClient(0)->SetupSyncWithCustomSettings(base::DoNothing()));
+  ASSERT_TRUE(GetSyncService(0)->HasSyncConsent());
+
+  GetSyncService(0)->GetUserSettings()->SetInitialSyncFeatureSetupComplete(
+      syncer::SyncFirstSetupCompleteSource::BASIC_FLOW);
+  ASSERT_TRUE(GetSyncService(0)
+                  ->GetUserSettings()
+                  ->IsInitialSyncFeatureSetupComplete());
+}
+
+IN_PROC_BROWSER_TEST_F(SyncSetupIncompleteMigrationSyncTest,
+                       DoesNotMigrateSyncSetupCompleteUser) {
+  ASSERT_TRUE(SetupClients());
+  ASSERT_TRUE(GetClient(0)->AwaitSyncTransportActive());
+  EXPECT_TRUE(GetSyncService(0)->HasSyncConsent());
+  EXPECT_TRUE(GetSyncService(0)
+                  ->GetUserSettings()
+                  ->IsInitialSyncFeatureSetupComplete());
+  EXPECT_TRUE(GetSyncService(0)->GetUserSettings()->GetSelectedTypes().Has(
+      syncer::UserSelectableType::kHistory));
+}
+
+#endif  // !BUILDFLAG(IS_CHROMEOS)
 
 }  // namespace
diff --git a/chrome/browser/sync/test/integration/sync_auth_test.cc b/chrome/browser/sync/test/integration/sync_auth_test.cc
index 50dd7d5..a575399 100644
--- a/chrome/browser/sync/test/integration/sync_auth_test.cc
+++ b/chrome/browser/sync/test/integration/sync_auth_test.cc
@@ -30,7 +30,6 @@
 #include "google_apis/gaia/fake_oauth2_token_response.h"
 #include "google_apis/gaia/google_service_auth_error.h"
 #include "google_apis/gaia/oauth2_response.h"
-#include "net/base/features.h"
 #include "net/base/net_errors.h"
 #include "net/http/http_status_code.h"
 
@@ -138,26 +137,6 @@
                          GetSyncTestModes(),
                          testing::PrintToStringParamName());
 
-class SyncAuthTestOAuthTokens : public SyncAuthTest {
- public:
-  SyncAuthTestOAuthTokens() {
-    // This test suite intercepts OAuth token requests, and IP Protection also
-    // requests OAuth tokens. Those requests race with the requests from the
-    // sync service, resulting in intermittent failures. Disabling IP Protection
-    // prevents these races.
-    feature_list_.InitAndDisableFeature(
-        net::features::kEnableIpProtectionProxy);
-  }
-
- private:
-  base::test::ScopedFeatureList feature_list_;
-};
-
-INSTANTIATE_TEST_SUITE_P(,
-                         SyncAuthTestOAuthTokens,
-                         GetSyncTestModes(),
-                         testing::PrintToStringParamName());
-
 // Verify that sync works with a valid OAuth2 token.
 IN_PROC_BROWSER_TEST_P(SyncAuthTest, Sanity) {
   ASSERT_TRUE(SetupSync());
@@ -390,7 +369,7 @@
 }
 
 // Verify that SyncServiceImpl fetches a new token when an old token expires.
-IN_PROC_BROWSER_TEST_P(SyncAuthTestOAuthTokens, TokenExpiry) {
+IN_PROC_BROWSER_TEST_P(SyncAuthTest, TokenExpiry) {
   // Initial sync succeeds with a short lived OAuth2 Token.
   ASSERT_TRUE(SetupClients());
   GetFakeServer()->ClearHttpError();
diff --git a/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodRenderTest.java b/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodRenderTest.java
index 71cec69d..b11c5b3 100644
--- a/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodRenderTest.java
+++ b/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodRenderTest.java
@@ -23,7 +23,6 @@
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -464,7 +463,6 @@
                             new LegalMessageLine("Affirm legal message line")));
 
     private BottomSheetController mBottomSheetController;
-    private BottomSheetTestSupport mSheetTestSupport;
     private TouchToFillPaymentMethodCoordinator mCoordinator;
     private WebPageStation mPage;
 
@@ -484,7 +482,6 @@
                         .getActivity()
                         .getRootUiCoordinatorForTesting()
                         .getBottomSheetController();
-        mSheetTestSupport = new BottomSheetTestSupport(mBottomSheetController);
         runOnUiThreadBlocking(
                 () -> {
                     mCoordinator = new TouchToFillPaymentMethodCoordinator();
@@ -823,7 +820,6 @@
     @Test
     @MediumTest
     @Feature({"RenderTest"})
-    @Ignore("TODO(crbug.com/430575808): Fix screen state update with animation in test")
     public void testShowsBnplIssuerTosScreen() throws IOException {
         runOnUiThreadBlocking(
                 () -> {
diff --git a/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodView.java b/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodView.java
index cc6d2956..aa75a95 100644
--- a/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodView.java
+++ b/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodView.java
@@ -29,7 +29,6 @@
 import org.chromium.chrome.browser.touch_to_fill.common.TouchToFillViewBase;
 import org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.ItemType;
 import org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.ScreenId;
-import org.chromium.components.browser_ui.bottomsheet.BottomSheetContent;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
 
 import java.util.Set;
@@ -46,7 +45,6 @@
     private @StringRes int mSheetFullHeightDescriptionId;
     private @StringRes int mSheetHalfHeightDescriptionId;
     private @StringRes int mSheetClosedDescriptionId;
-    private @ScreenId int mCurrentScreenId;
 
     private static class HorizontalDividerItemDecoration extends ItemDividerBase {
         HorizontalDividerItemDecoration(Context context) {
@@ -98,7 +96,6 @@
     }
 
     void setCurrentScreen(@ScreenId int screenId) {
-        mCurrentScreenId = screenId;
         ViewFlipper viewFlipper =
                 getContentView().findViewById(R.id.touch_to_fill_payment_method_view_flipper);
         viewFlipper.setDisplayedChild(getDisplayedChildForScreenId(screenId));
@@ -246,27 +243,4 @@
         assert false : "Undefined ScreenId: " + screenId;
         return 0;
     }
-
-    private boolean shouldAlwaysShowFullSheetForScreenId(@ScreenId int screenId) {
-        switch (screenId) {
-            case PROGRESS_SCREEN:
-            case BNPL_ISSUER_TOS_SCREEN:
-            case ERROR_SCREEN:
-                return true;
-            case HOME_SCREEN:
-            case ALL_LOYALTY_CARDS_SCREEN:
-            case BNPL_ISSUER_SELECTION_SCREEN:
-                return false;
-        }
-        assert false : "Undefined ScreenId: " + screenId;
-        return false;
-    }
-
-    @Override
-    public float getHalfHeightRatio() {
-        if (shouldAlwaysShowFullSheetForScreenId(mCurrentScreenId)) {
-            return BottomSheetContent.HeightMode.DISABLED;
-        }
-        return super.getHalfHeightRatio();
-    }
 }
diff --git a/chrome/browser/touch_to_fill/common/android/java/src/org/chromium/chrome/browser/touch_to_fill/common/TouchToFillViewBase.java b/chrome/browser/touch_to_fill/common/android/java/src/org/chromium/chrome/browser/touch_to_fill/common/TouchToFillViewBase.java
index 2fc70ba7..7c36e797 100644
--- a/chrome/browser/touch_to_fill/common/android/java/src/org/chromium/chrome/browser/touch_to_fill/common/TouchToFillViewBase.java
+++ b/chrome/browser/touch_to_fill/common/android/java/src/org/chromium/chrome/browser/touch_to_fill/common/TouchToFillViewBase.java
@@ -76,14 +76,6 @@
                     if (newState == BottomSheetController.SheetState.FULL) {
                         // The list of items should be scrollable in full state.
                         assumeNonNull(mSheetItemListView).suppressLayout(false);
-                        // The child view heights may change while an animation is running.
-                        // The target screen height will not be updated based on the new
-                        // child view heights. Request expansion again to fix this.
-                        if (mBottomSheetController.getCurrentOffset()
-                                != getMaximumSheetHeightPx()) {
-                            mBottomSheetController.updateSheetHeight(
-                                    BottomSheetController.SheetState.FULL);
-                        }
                     } else if (newState == BottomSheetController.SheetState.HALF
                             && mScrollListener.isScrolledToTop()) {
                         // The list of items should not be scrollable when the sheet transitions
@@ -404,7 +396,7 @@
 
     public void updateScreenHeight() {
         remeasure();
-        mBottomSheetController.updateSheetHeight();
+        mBottomSheetController.expandSheet();
     }
 
     @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
diff --git a/chrome/browser/touch_to_fill/password_manager/android/javatests/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillViewTest.java b/chrome/browser/touch_to_fill/password_manager/android/javatests/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillViewTest.java
index 158beef..aac639c 100644
--- a/chrome/browser/touch_to_fill/password_manager/android/javatests/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillViewTest.java
+++ b/chrome/browser/touch_to_fill/password_manager/android/javatests/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillViewTest.java
@@ -49,7 +49,6 @@
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -973,7 +972,6 @@
 
     @Test
     @MediumTest
-    @Ignore("TODO(crbug.com/430575808): Fix screen state update with animation in test")
     public void testMorePasskeysButtonIsClickable() {
         ThreadUtils.runOnUiThreadBlocking(
                 () -> {
@@ -984,9 +982,6 @@
                                             buildFooterItem(/* showHybrid= */ false)));
                     mModel.set(VISIBLE, true);
                 });
-        BottomSheetTestSupport.waitForState(mBottomSheetController, SheetState.SCROLLING);
-
-        ThreadUtils.runOnUiThreadBlocking(() -> mSheetTestSupport.endAllAnimations());
         BottomSheetTestSupport.waitForOpen(mBottomSheetController);
 
         ThreadUtils.runOnUiThreadBlocking(
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index ced743e..678822f6 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -479,7 +479,6 @@
     "//components/infobars/content",
     "//components/infobars/core",
     "//components/input",
-    "//components/ip_protection/common:ip_protection_status",
     "//components/javascript_dialogs",
     "//components/keyed_service/content",
     "//components/keyed_service/core",
diff --git a/chrome/browser/ui/DEPS b/chrome/browser/ui/DEPS
index a4715ac..da2bde98 100644
--- a/chrome/browser/ui/DEPS
+++ b/chrome/browser/ui/DEPS
@@ -8,7 +8,6 @@
   "+components/data_sharing/public",
   "+components/endpoint_fetcher",
   "+components/enterprise",
-  "+components/ip_protection/common",
   "+components/infobars/android",
   "+components/language_detection/core",
   "+components/live_caption",
diff --git a/chrome/browser/ui/android/extensions/extension_install_dialog_view_android.cc b/chrome/browser/ui/android/extensions/extension_install_dialog_view_android.cc
index c514520..1187facf 100644
--- a/chrome/browser/ui/android/extensions/extension_install_dialog_view_android.cc
+++ b/chrome/browser/ui/android/extensions/extension_install_dialog_view_android.cc
@@ -110,59 +110,53 @@
 void ExtensionInstallDialogViewAndroid::BuildPropertyModel() {
   JNIEnv* env = base::android::AttachCurrentThread();
 
-  Java_ExtensionInstallDialogBridge_withTitleAndButtons(
-      env, java_object_,
-      ConvertUTF16ToJavaString(env, prompt_->GetDialogTitle()),
-      gfx::ConvertToJavaBitmap(prompt_->icon().AsBitmap()),
-      ConvertUTF16ToJavaString(env, prompt_->GetAcceptButtonLabel()),
-      ConvertUTF16ToJavaString(env, prompt_->GetAbortButtonLabel()));
-
   bool has_permissions = prompt_->GetPermissionCount() > 0;
-  bool requires_justification =
-      prompt_->type() ==
-      ExtensionInstallPrompt::PromptType::EXTENSION_REQUEST_PROMPT;
-
-  // Dialog doesn't have a custom view if there are no permissions or doesn't
-  // require justification.
-  if (!has_permissions && !requires_justification) {
-    return;
-  }
-
-  std::u16string permissions_heading = std::u16string();
-  std::vector<std::u16string> permissions_text;
-  std::vector<std::u16string> permissions_details;
   if (has_permissions) {
-    permissions_heading = prompt_->GetPermissionsHeading();
+    std::u16string permissions_heading = prompt_->GetPermissionsHeading();
+    std::vector<std::u16string> permissions_text;
+    std::vector<std::u16string> permissions_details;
+
     auto permissions = prompt_->GetPermissions();
     for (size_t i = 0; i < permissions.permissions.size(); ++i) {
       permissions_text.push_back(permissions.permissions[i]);
       permissions_details.push_back(permissions.details[i]);
     }
+
+    ScopedJavaLocalRef<jstring> java_permissions_heading =
+        ConvertUTF16ToJavaString(env, permissions_heading);
+    ScopedJavaLocalRef<jobjectArray> java_permissions_text_array =
+        base::android::ToJavaArrayOfStrings(env, permissions_text);
+    ScopedJavaLocalRef<jobjectArray> java_permissions_details_array =
+        base::android::ToJavaArrayOfStrings(env, permissions_details);
+    Java_ExtensionInstallDialogBridge_withPermissions(
+        env, java_object_, java_permissions_heading,
+        java_permissions_text_array, java_permissions_details_array);
   }
 
-  std::u16string justification_heading = std::u16string();
-  std::u16string justification_placeholder = std::u16string();
+  bool requires_justification =
+      prompt_->type() ==
+      ExtensionInstallPrompt::PromptType::EXTENSION_REQUEST_PROMPT;
   if (requires_justification) {
-    justification_heading = l10n_util::GetStringUTF16(
+    std::u16string justification_heading = l10n_util::GetStringUTF16(
         IDS_ENTERPRISE_EXTENSION_REQUEST_JUSTIFICATION);
-    justification_placeholder = l10n_util::GetStringUTF16(
+    std::u16string justification_placeholder = l10n_util::GetStringUTF16(
         IDS_ENTERPRISE_EXTENSION_REQUEST_JUSTIFICATION_PLACEHOLDER);
+
+    ScopedJavaLocalRef<jstring> java_justification_heading =
+        ConvertUTF16ToJavaString(env, justification_heading);
+    ScopedJavaLocalRef<jstring> java_justification_placeholder =
+        ConvertUTF16ToJavaString(env, justification_placeholder);
+    Java_ExtensionInstallDialogBridge_withJustification(
+        env, java_object_, java_justification_heading,
+        java_justification_placeholder);
   }
 
-  ScopedJavaLocalRef<jstring> java_permissions_heading =
-      ConvertUTF16ToJavaString(env, permissions_heading);
-  ScopedJavaLocalRef<jobjectArray> java_permissions_text_array =
-      base::android::ToJavaArrayOfStrings(env, permissions_text);
-  ScopedJavaLocalRef<jobjectArray> java_permissions_details_array =
-      base::android::ToJavaArrayOfStrings(env, permissions_details);
-  ScopedJavaLocalRef<jstring> java_justification_heading =
-      ConvertUTF16ToJavaString(env, justification_heading);
-  ScopedJavaLocalRef<jstring> java_justification_placeholder =
-      ConvertUTF16ToJavaString(env, justification_placeholder);
-  Java_ExtensionInstallDialogBridge_withCustomView(
-      env, java_object_, java_permissions_heading, java_permissions_text_array,
-      java_permissions_details_array, java_justification_heading,
-      java_justification_placeholder);
+  Java_ExtensionInstallDialogBridge_buildDialog(
+      env, java_object_,
+      ConvertUTF16ToJavaString(env, prompt_->GetDialogTitle()),
+      gfx::ConvertToJavaBitmap(prompt_->icon().AsBitmap()),
+      ConvertUTF16ToJavaString(env, prompt_->GetAcceptButtonLabel()),
+      ConvertUTF16ToJavaString(env, prompt_->GetAbortButtonLabel()));
 }
 
 }  // namespace extensions
diff --git a/chrome/browser/ui/android/extensions/java/src/org/chromium/chrome/browser/ui/extensions/ExtensionInstallDialogBridge.java b/chrome/browser/ui/android/extensions/java/src/org/chromium/chrome/browser/ui/extensions/ExtensionInstallDialogBridge.java
index 8780355b..120e220f 100644
--- a/chrome/browser/ui/android/extensions/java/src/org/chromium/chrome/browser/ui/extensions/ExtensionInstallDialogBridge.java
+++ b/chrome/browser/ui/android/extensions/java/src/org/chromium/chrome/browser/ui/extensions/ExtensionInstallDialogBridge.java
@@ -44,6 +44,7 @@
     private final Context mContext;
     private final PropertyModel.Builder mPropertyModelBuilder;
     private @Nullable PropertyModel mDialogModel;
+    private @Nullable View mContentView;
     private @Nullable TextInputEditText mJustificationInputText;
 
     @VisibleForTesting
@@ -78,7 +79,7 @@
     }
 
     /**
-     * Adds title and buttons to the dialog's property model.
+     * Finalizes the dialog construction.
      *
      * @param title Text for the title.
      * @param iconBitmap Icon for the title.
@@ -86,7 +87,7 @@
      * @param cancelButtonLabel Label for the negative button which acts as the cancel button.
      */
     @CalledByNative
-    public void withTitleAndButtons(
+    public void buildDialog(
             String title, Bitmap iconBitmap, String acceptButtonLabel, String cancelButtonLabel) {
         Drawable iconDrawable = new BitmapDrawable(mContext.getResources(), iconBitmap);
         mPropertyModelBuilder
@@ -94,86 +95,89 @@
                 .with(ModalDialogProperties.TITLE_ICON, iconDrawable)
                 .with(ModalDialogProperties.POSITIVE_BUTTON_TEXT, acceptButtonLabel)
                 .with(ModalDialogProperties.NEGATIVE_BUTTON_TEXT, cancelButtonLabel);
+
+        if (mContentView != null) {
+            mPropertyModelBuilder
+                    .with(ModalDialogProperties.CUSTOM_VIEW, mContentView)
+                    .with(ModalDialogProperties.WRAP_CUSTOM_VIEW_IN_SCROLLABLE, true);
+        }
     }
 
     /**
-     * Adds the custom view container with permissions and/or justifications to the dialog's
-     * property model.
+     * Populates the permissions section of the dialog.
      *
-     * @param heading Optional text for the permissions heading
-     * @param permissionsText List of permissions information.
-     * @param permissionsDetails List of permissions details, which may be empty.
-     * @param heading Optional text for the justifications heading.
-     * @param placeholderText Optional text for the justifications text area placeholder.
+     * @param permissionsHeading The heading text for the permissions section.
+     * @param permissionsText An array of strings describing the specific permissions requested.
+     * @param permissionsDetails An array of strings providing optional details for each permission.
      */
     @CalledByNative
-    public void withCustomView(
-            String permissionsHeading,
-            String[] permissionsText,
-            String[] permissionsDetails,
-            String justificationHeading,
-            String justificationPlaceholderText) {
-        View contentView =
-                LayoutInflater.from(mContext).inflate(R.layout.extension_install_dialog, null);
+    public void withPermissions(
+            String permissionsHeading, String[] permissionsText, String[] permissionsDetails) {
+        View contentView = getContentView();
         LinearLayout scrollViewContainer = contentView.findViewById(R.id.scroll_view_container);
 
-        if (permissionsHeading.length() > 0) {
-            LinearLayout permissionsContainer =
-                    scrollViewContainer.findViewById(R.id.permissions_container);
-            TextView permissionsHeadingView =
-                    permissionsContainer.findViewById(R.id.permissions_heading);
-            permissionsHeadingView.setText(permissionsHeading);
+        LinearLayout permissionsContainer =
+                scrollViewContainer.findViewById(R.id.permissions_container);
+        permissionsContainer.setVisibility(View.VISIBLE);
 
-            LayoutInflater inflater = LayoutInflater.from(mContext);
-            for (String permissionText : permissionsText) {
-                TextViewWithLeading permissionTextView =
-                        (TextViewWithLeading)
-                                inflater.inflate(
-                                        R.layout.modal_dialog_paragraph_view,
-                                        permissionsContainer,
-                                        false);
-                permissionTextView.setText(permissionText);
-                permissionsContainer.addView(permissionTextView);
-                // TODO(crbug.com/424010795): Add permissionsDetails as a collapsible view, if
-                // existent.
-            }
+        TextView permissionsHeadingView =
+                permissionsContainer.findViewById(R.id.permissions_heading);
+        permissionsHeadingView.setText(permissionsHeading);
 
-            permissionsContainer.setVisibility(View.VISIBLE);
+        LayoutInflater inflater = LayoutInflater.from(mContext);
+        for (String permissionText : permissionsText) {
+            TextViewWithLeading permissionTextView =
+                    (TextViewWithLeading)
+                            inflater.inflate(
+                                    R.layout.modal_dialog_paragraph_view,
+                                    permissionsContainer,
+                                    false);
+            permissionTextView.setText(permissionText);
+            permissionsContainer.addView(permissionTextView);
+            // TODO(crbug.com/424010795): Add permissionsDetails as a collapsible view, if
+            // existent.
         }
+    }
 
-        if (justificationHeading.length() > 0) {
-            LinearLayout justificationContainer =
-                    scrollViewContainer.findViewById(R.id.justification_container);
-            TextView justificationHeadingView =
-                    justificationContainer.findViewById(R.id.justification_heading);
-            justificationHeadingView.setText(justificationHeading);
+    /**
+     * Populates the justification request section of the dialog.
+     *
+     * @param justificationHeading The heading text for the justification section.
+     * @param justificationPlaceholderText The hint text displayed inside the input field.
+     */
+    @CalledByNative
+    public void withJustification(
+            String justificationHeading, String justificationPlaceholderText) {
+        View contentView = getContentView();
+        LinearLayout scrollViewContainer = contentView.findViewById(R.id.scroll_view_container);
 
-            TextInputLayout justificationInputLayout =
-                    justificationContainer.findViewById(R.id.justification_input_layout);
-            justificationInputLayout.setHint(justificationPlaceholderText);
+        LinearLayout justificationContainer =
+                scrollViewContainer.findViewById(R.id.justification_container);
+        justificationContainer.setVisibility(View.VISIBLE);
 
-            mJustificationInputText =
-                    justificationContainer.findViewById(R.id.justification_input_text);
-            mJustificationInputText.addTextChangedListener(
-                    new EmptyTextWatcher() {
-                        @Override
-                        public void afterTextChanged(Editable s) {
-                            int maxInputLength =
-                                    mContext.getResources()
-                                            .getInteger(
-                                                    R.integer
-                                                            .extension_install_dialog_justification_max_input);
-                            boolean isTextTooLong = s.length() > maxInputLength;
-                            setPositiveButtonDisabled(isTextTooLong);
-                        }
-                    });
+        TextView justificationHeadingView =
+                justificationContainer.findViewById(R.id.justification_heading);
+        justificationHeadingView.setText(justificationHeading);
 
-            justificationContainer.setVisibility(View.VISIBLE);
-        }
+        TextInputLayout justificationInputLayout =
+                justificationContainer.findViewById(R.id.justification_input_layout);
+        justificationInputLayout.setHint(justificationPlaceholderText);
 
-        mPropertyModelBuilder
-                .with(ModalDialogProperties.CUSTOM_VIEW, contentView)
-                .with(ModalDialogProperties.WRAP_CUSTOM_VIEW_IN_SCROLLABLE, true);
+        mJustificationInputText =
+                justificationContainer.findViewById(R.id.justification_input_text);
+        mJustificationInputText.addTextChangedListener(
+                new EmptyTextWatcher() {
+                    @Override
+                    public void afterTextChanged(Editable s) {
+                        int maxInputLength =
+                                mContext.getResources()
+                                        .getInteger(
+                                                R.integer
+                                                        .extension_install_dialog_justification_max_input);
+                        boolean isTextTooLong = s.length() > maxInputLength;
+                        setPositiveButtonDisabled(isTextTooLong);
+                    }
+                });
     }
 
     /** Shows the extension install dialog. */
@@ -228,6 +232,15 @@
         mDialogModel.set(ModalDialogProperties.POSITIVE_BUTTON_DISABLED, disabled);
     }
 
+    /** Returns the content view for the dialog, inflating it if necessary. */
+    private View getContentView() {
+        if (mContentView == null) {
+            mContentView =
+                    LayoutInflater.from(mContext).inflate(R.layout.extension_install_dialog, null);
+        }
+        return mContentView;
+    }
+
     @NativeMethods
     interface Natives {
         void onDialogAccepted(
diff --git a/chrome/browser/ui/android/extensions/javatests/src/org/chromium/chrome/browser/ui/extensions/ExtensionInstallDialogBridgeTest.java b/chrome/browser/ui/android/extensions/javatests/src/org/chromium/chrome/browser/ui/extensions/ExtensionInstallDialogBridgeTest.java
index 59d639c7..c42120dc 100644
--- a/chrome/browser/ui/android/extensions/javatests/src/org/chromium/chrome/browser/ui/extensions/ExtensionInstallDialogBridgeTest.java
+++ b/chrome/browser/ui/android/extensions/javatests/src/org/chromium/chrome/browser/ui/extensions/ExtensionInstallDialogBridgeTest.java
@@ -81,15 +81,20 @@
         mExtensionInstallDialogBridge =
                 new ExtensionInstallDialogBridge(
                         NATIVE_INSTALL_EXTENSION_DIALOG_VIEW, mActivity, mModalDialogManager);
-        mExtensionInstallDialogBridge.withTitleAndButtons(
+    }
+
+    /** Helper method to build and show the dialog with standard test data. */
+    private void buildAndShowDialog() {
+        mExtensionInstallDialogBridge.buildDialog(
                 TITLE, ICON, ACCEPT_BUTTON_LABEL, CANCEL_BUTTON_LABEL);
+        mExtensionInstallDialogBridge.showDialog();
     }
 
     /** Tests that the basic dialog only contains the title and buttons */
     @Test
     @SmallTest
     public void testBasicDialog() throws Exception {
-        mExtensionInstallDialogBridge.showDialog();
+        buildAndShowDialog();
         PropertyModel dialogModel = mModalDialogManager.getShownDialogModel();
 
         Assert.assertEquals(
@@ -117,13 +122,9 @@
     @Test
     @SmallTest
     public void testDialogWithPermissions() throws Exception {
-        mExtensionInstallDialogBridge.withCustomView(
-                PERMISSIONS_HEADING,
-                PERMISSIONS_TEXT,
-                PERMISSIONS_DETAILS,
-                /* justificationHeading= */ "",
-                /* justificationPlaceholderText= */ "");
-        mExtensionInstallDialogBridge.showDialog();
+        mExtensionInstallDialogBridge.withPermissions(
+                PERMISSIONS_HEADING, PERMISSIONS_TEXT, PERMISSIONS_DETAILS);
+        buildAndShowDialog();
         PropertyModel dialogModel = mModalDialogManager.getShownDialogModel();
 
         View customView = dialogModel.get(ModalDialogProperties.CUSTOM_VIEW);
@@ -152,14 +153,9 @@
     @Test
     @SmallTest
     public void testDialogWithJustificationAndAcceptsText() throws Exception {
-        // Setup the dialog with justification but no permissions.
-        mExtensionInstallDialogBridge.withCustomView(
-                /* permissionsHeading= */ "",
-                /* permissionsText= */ new String[0],
-                /* permissionsDetails= */ new String[0],
-                JUSTIFICATION_HEADING,
-                JUSTIFICATION_PLACEHOLDER);
-        mExtensionInstallDialogBridge.showDialog();
+        mExtensionInstallDialogBridge.withJustification(
+                JUSTIFICATION_HEADING, JUSTIFICATION_PLACEHOLDER);
+        buildAndShowDialog();
         PropertyModel dialogModel = mModalDialogManager.getShownDialogModel();
         View customView = dialogModel.get(ModalDialogProperties.CUSTOM_VIEW);
 
@@ -193,14 +189,9 @@
     @Test
     @SmallTest
     public void testPositiveButtonDisabledWhenJustificationTextIsTooLong() throws Exception {
-        // Setup the dialog with justification.
-        mExtensionInstallDialogBridge.withCustomView(
-                /* permissionsHeading= */ "",
-                /* permissionsText= */ new String[0],
-                /* permissionsDetails= */ new String[0],
-                JUSTIFICATION_HEADING,
-                JUSTIFICATION_PLACEHOLDER);
-        mExtensionInstallDialogBridge.showDialog();
+        mExtensionInstallDialogBridge.withJustification(
+                JUSTIFICATION_HEADING, JUSTIFICATION_PLACEHOLDER);
+        buildAndShowDialog();
         PropertyModel dialogModel = mModalDialogManager.getShownDialogModel();
         View customView = dialogModel.get(ModalDialogProperties.CUSTOM_VIEW);
 
@@ -244,7 +235,7 @@
     @Test
     @SmallTest
     public void testOnAcceptButtonClicked() throws Exception {
-        mExtensionInstallDialogBridge.showDialog();
+        buildAndShowDialog();
 
         mModalDialogManager.clickPositiveButton();
 
@@ -261,7 +252,7 @@
     @Test
     @SmallTest
     public void testOnCancelButtonClicked() throws Exception {
-        mExtensionInstallDialogBridge.showDialog();
+        buildAndShowDialog();
 
         mModalDialogManager.clickNegativeButton();
 
@@ -276,7 +267,7 @@
     @Test
     @SmallTest
     public void testOnDialogDismissed() throws Exception {
-        mExtensionInstallDialogBridge.showDialog();
+        buildAndShowDialog();
 
         PropertyModel model = mModalDialogManager.getShownDialogModel();
         Assert.assertNotNull(model);
diff --git a/chrome/browser/ui/android/multiwindow/java/src/org/chromium/chrome/browser/multiwindow/InstanceSwitcherCoordinator.java b/chrome/browser/ui/android/multiwindow/java/src/org/chromium/chrome/browser/multiwindow/InstanceSwitcherCoordinator.java
index 70498d0a..04ba5420 100644
--- a/chrome/browser/ui/android/multiwindow/java/src/org/chromium/chrome/browser/multiwindow/InstanceSwitcherCoordinator.java
+++ b/chrome/browser/ui/android/multiwindow/java/src/org/chromium/chrome/browser/multiwindow/InstanceSwitcherCoordinator.java
@@ -547,7 +547,7 @@
         int instanceId = clickedItem.instanceId;
         boolean wasSelected = mSelectedItems.containsKey(instanceId);
 
-        if (UiUtils.isRobustWindowManagementEnabled()) {
+        if (UiUtils.isRobustWindowManagementBulkCloseEnabled()) {
             // Multi-selection is allowed. Toggle the clicked item.
             if (wasSelected) {
                 mSelectedItems.remove(instanceId);
@@ -579,7 +579,7 @@
         // 1. Update positive button state.
         boolean positiveButtonDisabled = true;
         if (selectionCount > 0) {
-            if (UiUtils.isRobustWindowManagementEnabled()) {
+            if (UiUtils.isRobustWindowManagementBulkCloseEnabled()) {
                 if (selectionCount == 1 && mActiveModelList.size() < mMaxInstanceCount) {
                     positiveButtonDisabled = false;
                 }
@@ -592,7 +592,7 @@
         mDialog.set(ModalDialogProperties.POSITIVE_BUTTON_DISABLED, positiveButtonDisabled);
 
         // 2. Update per-item buttons (for robust mode).
-        if (!UiUtils.isRobustWindowManagementEnabled()) return;
+        if (!UiUtils.isRobustWindowManagementBulkCloseEnabled()) return;
         boolean itemButtonsEnabled = selectionCount <= 1;
         for (ListItem li : getCurrentList()) {
             if (mIsInactiveListShowing) {
diff --git a/chrome/browser/ui/android/multiwindow/java/src/org/chromium/chrome/browser/multiwindow/InstanceSwitcherCoordinatorTest.java b/chrome/browser/ui/android/multiwindow/java/src/org/chromium/chrome/browser/multiwindow/InstanceSwitcherCoordinatorTest.java
index 7c5fcc6..1729cac 100644
--- a/chrome/browser/ui/android/multiwindow/java/src/org/chromium/chrome/browser/multiwindow/InstanceSwitcherCoordinatorTest.java
+++ b/chrome/browser/ui/android/multiwindow/java/src/org/chromium/chrome/browser/multiwindow/InstanceSwitcherCoordinatorTest.java
@@ -1312,7 +1312,7 @@
     @SmallTest
     @EnableFeatures({
         ChromeFeatureList.INSTANCE_SWITCHER_V2,
-        ChromeFeatureList.ROBUST_WINDOW_MANAGEMENT
+        ChromeFeatureList.ROBUST_WINDOW_MANAGEMENT + ":bulk_close/true"
     })
     public void testMultiSelectInactiveWindows_robustWindowManagement() throws Exception {
         // Initialize instance list with 2 active instances and 3 inactive instances.
diff --git a/chrome/browser/ui/android/multiwindow/java/src/org/chromium/chrome/browser/multiwindow/UiUtils.java b/chrome/browser/ui/android/multiwindow/java/src/org/chromium/chrome/browser/multiwindow/UiUtils.java
index 8f69c3da..a82bb5e 100644
--- a/chrome/browser/ui/android/multiwindow/java/src/org/chromium/chrome/browser/multiwindow/UiUtils.java
+++ b/chrome/browser/ui/android/multiwindow/java/src/org/chromium/chrome/browser/multiwindow/UiUtils.java
@@ -95,6 +95,15 @@
     }
 
     /**
+     * Checks whether the bulk close feature of Robust Window Management is enabled.
+     *
+     * @return {@code true} if bulk close is enabled, {@code false} otherwise.
+     */
+    public static boolean isRobustWindowManagementBulkCloseEnabled() {
+        return ChromeFeatureList.sRobustWindowManagementBulkClose.getValue();
+    }
+
+    /**
      * Checks whether the Recently Closed Tabs and Windows feature is enabled.
      *
      * @return {@code true} if the Recently Closed Tabs and Windows feature is enabled, {@code
diff --git a/chrome/browser/ui/android/omnibox/BUILD.gn b/chrome/browser/ui/android/omnibox/BUILD.gn
index cc1eeca9..5b2cc46 100644
--- a/chrome/browser/ui/android/omnibox/BUILD.gn
+++ b/chrome/browser/ui/android/omnibox/BUILD.gn
@@ -333,8 +333,10 @@
     "java/res/drawable-xxxhdpi/bookmark_edit_active.png",
     "java/res/drawable-xxxhdpi/ic_history_googblue_24dp.png",
     "java/res/drawable-xxxhdpi/ic_suggestion_magnifier.png",
+    "java/res/drawable/action_button_hovered.xml",
+    "java/res/drawable/action_button_selected.xml",
+    "java/res/drawable/action_button_selected_hovered.xml",
     "java/res/drawable/create_image_24dp.xml",
-    "java/res/drawable/hairline_circle.xml",
     "java/res/drawable/ic_attach_file_24dp.xml",
     "java/res/drawable/ic_book_round.xml",
     "java/res/drawable/ic_equals_sign_round.xml",
diff --git a/chrome/browser/ui/android/omnibox/java/res/drawable/action_button_hovered.xml b/chrome/browser/ui/android/omnibox/java/res/drawable/action_button_hovered.xml
new file mode 100644
index 0000000..d0da771
--- /dev/null
+++ b/chrome/browser/ui/android/omnibox/java/res/drawable/action_button_hovered.xml
@@ -0,0 +1,13 @@
+<!--
+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.
+-->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+  <item android:gravity="center">
+    <shape android:shape="oval">
+      <solid android:color="@color/color_on_surface_with_alpha_12"/>
+      <size android:width="32dp" android:height="32dp" />
+    </shape>
+  </item>
+</layer-list>
diff --git a/chrome/browser/ui/android/omnibox/java/res/drawable/hairline_circle.xml b/chrome/browser/ui/android/omnibox/java/res/drawable/action_button_selected.xml
similarity index 100%
rename from chrome/browser/ui/android/omnibox/java/res/drawable/hairline_circle.xml
rename to chrome/browser/ui/android/omnibox/java/res/drawable/action_button_selected.xml
diff --git a/chrome/browser/ui/android/omnibox/java/res/drawable/action_button_selected_hovered.xml b/chrome/browser/ui/android/omnibox/java/res/drawable/action_button_selected_hovered.xml
new file mode 100644
index 0000000..bb8a2d1
--- /dev/null
+++ b/chrome/browser/ui/android/omnibox/java/res/drawable/action_button_selected_hovered.xml
@@ -0,0 +1,14 @@
+<!--
+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.
+-->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+  <item android:gravity="center">
+    <shape android:shape="oval">
+      <solid android:color="@color/color_on_surface_with_alpha_12"/>
+      <stroke android:width="2dp" android:color="@android:color/black"/>
+      <size android:width="32dp" android:height="32dp" />
+    </shape>
+  </item>
+</layer-list>
diff --git a/chrome/browser/ui/android/omnibox/java/res/layout/fusebox_context_popup.xml b/chrome/browser/ui/android/omnibox/java/res/layout/fusebox_context_popup.xml
index 954a8f6..d3a8f15 100644
--- a/chrome/browser/ui/android/omnibox/java/res/layout/fusebox_context_popup.xml
+++ b/chrome/browser/ui/android/omnibox/java/res/layout/fusebox_context_popup.xml
@@ -4,87 +4,95 @@
 Use of this source code is governed by a BSD-style license that can be
 found in the LICENSE file.
 -->
-<androidx.constraintlayout.widget.ConstraintLayout
+<ScrollView
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:paddingStart="8dp"
-    android:paddingEnd="8dp"
-    android:paddingTop="16dp"
-    tools:ignore="MissingConstraints">
-    <!-- Horizontal placement constraints supplied by styles for uniformity. -->
-    <!--
-    TODO(crbug.com/436888404): either drop clipboard or use proper strings.
-    -->
-    <org.chromium.ui.widget.ButtonCompat
-        style="@style/Fusebox.PopupMenu.Item"
-        android:id="@+id/fusebox_add_current_tab"
-        android:visibility="gone"
-        android:text="@string/fusebox_add_current_tab"
-        app:layout_constraintTop_toTopOf="parent"/>
+    android:layout_height="match_parent">
 
-    <org.chromium.ui.widget.ButtonCompat
-        style="@style/Fusebox.PopupMenu.Item"
-        android:id="@+id/fusebox_paste_from_clipboard_button"
-        android:visibility="gone"
-        android:drawableStart="@drawable/ic_content_copy"
-        android:text="@string/clipboard_permission_title"
-        app:layout_constraintTop_toBottomOf="@id/fusebox_add_current_tab"/>
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingStart="8dp"
+        android:paddingEnd="8dp"
+        android:paddingTop="16dp"
+        tools:ignore="MissingConstraints">
+        <!--
+        Horizontal placement constraints supplied by styles for uniformity.
+        -->
+        <org.chromium.ui.widget.ButtonCompat
+            style="@style/Fusebox.PopupMenu.Item"
+            android:id="@+id/fusebox_add_current_tab"
+            android:visibility="gone"
+            android:text="@string/fusebox_add_current_tab"
+            app:layout_constraintTop_toTopOf="parent"/>
 
-    <org.chromium.ui.widget.ButtonCompat
-        style="@style/Fusebox.PopupMenu.Item"
-        android:id="@+id/fusebox_pick_tabs_button"
-        android:visibility="gone"
-        android:drawableStart="@drawable/ic_tabs_24dp"
-        android:text="@string/omnibox_navattach_tabs"
-        android:contentDescription="@string/accessibility_omnibox_add_tabs"
-        app:layout_constraintTop_toBottomOf="@id/fusebox_paste_from_clipboard_button"/>
+        <!--
+        TODO(crbug.com/436888404): either drop clipboard or use proper strings.
+        -->
+        <org.chromium.ui.widget.ButtonCompat
+            style="@style/Fusebox.PopupMenu.Item"
+            android:id="@+id/fusebox_paste_from_clipboard_button"
+            android:visibility="gone"
+            android:drawableStart="@drawable/ic_content_copy"
+            android:text="@string/clipboard_permission_title"
+            app:layout_constraintTop_toBottomOf="@id/fusebox_add_current_tab"/>
 
-    <org.chromium.ui.widget.ButtonCompat
-        style="@style/Fusebox.PopupMenu.Item"
-        android:id="@+id/fusebox_camera_button"
-        android:drawableStart="@drawable/ic_photo_camera"
-        android:text="@string/photo_picker_camera"
-        android:contentDescription="@string/accessibility_omnibox_add_camera_picture"
-        app:layout_constraintTop_toBottomOf="@id/fusebox_pick_tabs_button"/>
+        <org.chromium.ui.widget.ButtonCompat
+            style="@style/Fusebox.PopupMenu.Item"
+            android:id="@+id/fusebox_pick_tabs_button"
+            android:visibility="gone"
+            android:drawableStart="@drawable/ic_tabs_24dp"
+            android:text="@string/omnibox_navattach_tabs"
+            android:contentDescription="@string/accessibility_omnibox_add_tabs"
+            app:layout_constraintTop_toBottomOf="@id/fusebox_paste_from_clipboard_button"/>
 
-    <org.chromium.ui.widget.ButtonCompat
-        style="@style/Fusebox.PopupMenu.Item"
-        android:id="@+id/fusebox_pick_picture_button"
-        android:drawableStart="@drawable/ic_collections_grey"
-        android:text="@string/omnibox_navattach_gallery"
-        android:contentDescription="@string/accessibility_omnibox_add_images"
-        app:layout_constraintTop_toBottomOf="@id/fusebox_camera_button"/>
+        <org.chromium.ui.widget.ButtonCompat
+            style="@style/Fusebox.PopupMenu.Item"
+            android:id="@+id/fusebox_camera_button"
+            android:drawableStart="@drawable/ic_photo_camera"
+            android:text="@string/photo_picker_camera"
+            android:contentDescription="@string/accessibility_omnibox_add_camera_picture"
+            app:layout_constraintTop_toBottomOf="@id/fusebox_pick_tabs_button"/>
 
-    <org.chromium.ui.widget.ButtonCompat
-        style="@style/Fusebox.PopupMenu.Item"
-        android:id="@+id/fusebox_pick_file_button"
-        android:drawableStart="@drawable/ic_attach_file_24dp"
-        android:text="@string/omnibox_navattach_files"
-        android:contentDescription="@string/accessibility_omnibox_add_files"
-        app:layout_constraintTop_toBottomOf="@id/fusebox_pick_picture_button"/>
+        <org.chromium.ui.widget.ButtonCompat
+            style="@style/Fusebox.PopupMenu.Item"
+            android:id="@+id/fusebox_pick_picture_button"
+            android:drawableStart="@drawable/ic_collections_grey"
+            android:text="@string/omnibox_navattach_gallery"
+            android:contentDescription="@string/accessibility_omnibox_add_images"
+            app:layout_constraintTop_toBottomOf="@id/fusebox_camera_button"/>
 
-    <View
-        style="@style/Fusebox.PopupMenu.Divider"
-        android:id="@+id/fusebox_request_type_divider"
-        app:layout_constraintTop_toBottomOf="@id/fusebox_pick_file_button"/>
+        <org.chromium.ui.widget.ButtonCompat
+            style="@style/Fusebox.PopupMenu.Item"
+            android:id="@+id/fusebox_pick_file_button"
+            android:drawableStart="@drawable/ic_attach_file_24dp"
+            android:text="@string/omnibox_navattach_files"
+            android:contentDescription="@string/accessibility_omnibox_add_files"
+            app:layout_constraintTop_toBottomOf="@id/fusebox_pick_picture_button"/>
 
-    <org.chromium.ui.widget.ButtonCompat
-        style="@style/Fusebox.PopupMenu.Item"
-        android:id="@+id/fusebox_ai_mode_button"
-        android:drawableStart="@drawable/search_spark_black_24dp"
-        android:text="@string/ai_mode_entrypoint_label"
-        android:contentDescription="@string/accessibility_omnibox_enable_ai_mode"
-        app:layout_constraintTop_toBottomOf="@id/fusebox_request_type_divider"/>
+        <View
+            style="@style/Fusebox.PopupMenu.Divider"
+            android:id="@+id/fusebox_request_type_divider"
+            app:layout_constraintTop_toBottomOf="@id/fusebox_pick_file_button"/>
 
-    <org.chromium.ui.widget.ButtonCompat
-        style="@style/Fusebox.PopupMenu.Item"
-        android:id="@+id/fusebox_create_image_button"
-        android:drawableStart="@drawable/create_image_24dp"
-        android:text="@string/omnibox_create_image"
-        android:contentDescription="@string/accessibility_omnibox_create_image"
-        app:layout_constraintTop_toBottomOf="@id/fusebox_ai_mode_button"/>
+        <org.chromium.ui.widget.ButtonCompat
+            style="@style/Fusebox.PopupMenu.Item"
+            android:id="@+id/fusebox_ai_mode_button"
+            android:drawableStart="@drawable/search_spark_black_24dp"
+            android:text="@string/ai_mode_entrypoint_label"
+            android:contentDescription="@string/accessibility_omnibox_enable_ai_mode"
+            app:layout_constraintTop_toBottomOf="@id/fusebox_request_type_divider"/>
 
-</androidx.constraintlayout.widget.ConstraintLayout>
+        <org.chromium.ui.widget.ButtonCompat
+            style="@style/Fusebox.PopupMenu.Item"
+            android:id="@+id/fusebox_create_image_button"
+            android:drawableStart="@drawable/create_image_24dp"
+            android:text="@string/omnibox_create_image"
+            android:contentDescription="@string/accessibility_omnibox_create_image"
+            app:layout_constraintTop_toBottomOf="@id/fusebox_ai_mode_button"/>
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+</ScrollView>
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 bd950471..0478154 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
@@ -93,7 +93,7 @@
         assert !hasToken() : "Attachment should not have a token when uploaded";
 
         if (type == FuseboxAttachmentType.ATTACHMENT_TAB) {
-            if (assumeNonNull(tab).getWebContents() != null) {
+            if (FuseboxTabUtils.isTabActive(assumeNonNull(tab))) {
                 mToken = bridge.addTabContext(tab);
             } else {
                 mToken = bridge.addTabContextFromCache(assumeNonNull(tab).getId());
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 cb72e0c..3fbf6d66 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
@@ -31,6 +31,7 @@
     static final int MAX_ATTACHMENTS = 10;
     private @Nullable ComposeBoxQueryControllerBridge mComposeBoxQueryControllerBridge;
     private @BrandedColorScheme int mBrandedColorScheme;
+    private @Nullable Runnable mAttachmentUploadFailedListener;
 
     /**
      * @param composeBoxQueryControllerBridge The bridge to use for backend operations
@@ -48,9 +49,15 @@
         }
     }
 
+    public void setAttachmentUploadFailedListener(
+            @Nullable Runnable attachmentUploadFailedListener) {
+        mAttachmentUploadFailedListener = attachmentUploadFailedListener;
+    }
+
     /** Release all resources and mark this instance ready for recycling. */
     void destroy() {
         setComposeBoxQueryControllerBridge(null);
+        mAttachmentUploadFailedListener = null;
     }
 
     /**
@@ -190,6 +197,7 @@
             case FileUploadStatus.VALIDATION_FAILED:
             case FileUploadStatus.UPLOAD_FAILED:
             case FileUploadStatus.UPLOAD_EXPIRED:
+                notifyAttachmentUploadFailed();
                 pendingAttachment.setUploadIsComplete();
                 remove(pendingAttachment);
                 break;
@@ -213,4 +221,10 @@
     private int getRemainingAttachments() {
         return MAX_ATTACHMENTS - size();
     }
+
+    private void notifyAttachmentUploadFailed() {
+        if (mAttachmentUploadFailedListener != null) {
+            mAttachmentUploadFailedListener.run();
+        }
+    }
 }
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/fusebox/FuseboxAttachmentModelListUnitTest.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/fusebox/FuseboxAttachmentModelListUnitTest.java
index 2b73935f..7a135e9 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/fusebox/FuseboxAttachmentModelListUnitTest.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/fusebox/FuseboxAttachmentModelListUnitTest.java
@@ -26,6 +26,8 @@
 import org.chromium.base.test.BaseRobolectricTestRunner;
 import org.chromium.components.contextual_search.FileUploadStatus;
 
+import java.util.concurrent.atomic.AtomicBoolean;
+
 @RunWith(BaseRobolectricTestRunner.class)
 public class FuseboxAttachmentModelListUnitTest {
     @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
@@ -329,7 +331,9 @@
 
         FuseboxAttachment preTokenizedAttachment = createTestAttachment("pretokenized");
         FuseboxAttachment uploadedAttachment = createTestAttachment("uploaded");
-
+        AtomicBoolean uploadFailedNotified = new AtomicBoolean(false);
+        mFuseboxAttachmentModelList.setAttachmentUploadFailedListener(
+                () -> uploadFailedNotified.set(true));
         mFuseboxAttachmentModelList.add(preTokenizedAttachment);
         mFuseboxAttachmentModelList.add(uploadedAttachment);
         assertEquals(2, mFuseboxAttachmentModelList.size());
@@ -352,6 +356,7 @@
                 "pretokenized-token", FileUploadStatus.UPLOAD_SUCCESSFUL);
         mFuseboxAttachmentModelList.onFileUploadStatusChanged(
                 "uploaded-token", FileUploadStatus.UPLOAD_FAILED);
+        assertTrue(uploadFailedNotified.get());
 
         assertTrue(preTokenizedAttachment.isUploadComplete());
         assertTrue(uploadedAttachment.isUploadComplete());
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/fusebox/FuseboxCoordinator.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/fusebox/FuseboxCoordinator.java
index 6b2e626..220f9e7 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/fusebox/FuseboxCoordinator.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/fusebox/FuseboxCoordinator.java
@@ -173,6 +173,7 @@
         if (mLastBrandedColorScheme != null) {
             mMediator.updateVisualsForState(mLastBrandedColorScheme);
         }
+        mModelList.setAttachmentUploadFailedListener(mMediator::onAttachmentUploadFailed);
     }
 
     public void destroy() {
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 d511fa3ca..973d601 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
@@ -87,6 +87,7 @@
     private boolean mUseCompactUi;
     private final SnackbarManager mSnackbarManager;
     private final Snackbar mAttachmentLimitSnackbar;
+    private final Snackbar mAttachmentUploadFailedSnackbar;
 
     FuseboxMediator(
             Context context,
@@ -116,13 +117,20 @@
 
         mAutocompleteRequestTypeSupplier.addObserver(mOnAutocompleteRequestTypeChanged);
 
-        CharSequence snackbarText = context.getText(R.string.fusebox_max_attachments);
+        CharSequence snackbarLimitText = context.getText(R.string.fusebox_max_attachments);
         mAttachmentLimitSnackbar =
                 Snackbar.make(
-                        snackbarText,
+                        snackbarLimitText,
                         null,
                         Snackbar.TYPE_NOTIFICATION,
                         Snackbar.UMA_FUSEBOX_MAX_ATTACHMENTS);
+        CharSequence snackbarUploadFailedText = context.getText(R.string.fusebox_upload_failed);
+        mAttachmentUploadFailedSnackbar =
+                Snackbar.make(
+                        snackbarUploadFailedText,
+                        null,
+                        Snackbar.TYPE_NOTIFICATION,
+                        Snackbar.UMA_FUSEBOX_UPLOAD_FAILED);
 
         mModel.set(FuseboxProperties.BUTTON_ADD_CLICKED, this::onToggleAttachmentsPopup);
         mModel.set(FuseboxProperties.POPUP_CAMERA_CLICKED, this::onCameraClicked);
@@ -278,6 +286,7 @@
         Tab currentTab = assumeNonNull(tabModelSelector.getCurrentTab());
         boolean tabIsEligible =
                 FuseboxTabUtils.isTabEligibleForAttachment(currentTab)
+                        && FuseboxTabUtils.isTabActive(currentTab)
                         && !currentTab.isIncognitoBranded();
 
         if (tabIsEligible) {
@@ -355,6 +364,10 @@
         }
     }
 
+    void onAttachmentUploadFailed() {
+        mSnackbarManager.showSnackbar(mAttachmentUploadFailedSnackbar);
+    }
+
     /**
      * 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.
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 c80ab94c..0314f50 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
@@ -10,7 +10,6 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.atLeastOnce;
@@ -67,6 +66,7 @@
 import org.chromium.chrome.browser.ui.theme.BrandedColorScheme;
 import org.chromium.components.omnibox.AutocompleteRequestType;
 import org.chromium.components.omnibox.OmniboxFeatures;
+import org.chromium.content_public.browser.RenderWidgetHostView;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.ui.base.Clipboard;
 import org.chromium.ui.base.MimeTypeUtils;
@@ -99,6 +99,7 @@
     @Mock private Tab mTab1;
     @Mock private Tab mTab2;
     @Mock private WebContents mWebContents;
+    @Mock private RenderWidgetHostView mRenderWidgetHostView;
     @Mock private Function<Tab, @Nullable Bitmap> mTabFaviconFactory;
     @Mock private ProfileResolver.Natives mProfileResolverNatives;
     @Mock private SnackbarManager mSnackbarManager;
@@ -307,6 +308,9 @@
         doReturn(new GURL("https://www.google.com")).when(mTab1).getUrl();
         doReturn(true).when(mTab1).isInitialized();
         doReturn(100L).when(mTab1).getTimestampMillis();
+        doReturn(mWebContents).when(mTab1).getWebContents();
+        doReturn(false).when(mWebContents).isLoading();
+        doReturn(mRenderWidgetHostView).when(mWebContents).getRenderWidgetHostView();
 
         doReturn("Title3").when(mTab2).getTitle();
         doReturn(new GURL("chrome://flags")).when(mTab2).getUrl();
@@ -325,7 +329,6 @@
         assertNull(mModel.get(FuseboxProperties.CURRENT_TAB_BUTTON_FAVICON));
 
         doReturn(mBitmap).when(mTabFaviconFactory).apply(any());
-        doReturn(mWebContents).when(mTab1).getWebContents();
         doReturn("token").when(mComposeBoxQueryControllerBridge).addTabContext(mTab1);
         mModel.get(FuseboxProperties.CURRENT_TAB_BUTTON_CLICKED).run();
         verify(mComposeBoxQueryControllerBridge).addTabContext(mTab1);
@@ -421,6 +424,9 @@
         doReturn(new GURL("https://www.google.com")).when(mTab1).getUrl();
         doReturn(true).when(mTab1).isInitialized();
         doReturn(100L).when(mTab1).getTimestampMillis();
+        doReturn(mWebContents).when(mTab1).getWebContents();
+        doReturn(false).when(mWebContents).isLoading();
+        doReturn(mRenderWidgetHostView).when(mWebContents).getRenderWidgetHostView();
 
         mAutocompleteRequestTypeSupplier.set(AutocompleteRequestType.SEARCH);
         ShadowLooper.idleMainLooper();
@@ -646,14 +652,16 @@
 
     @Test
     public void testAddAttachment_disablesCreateImage() {
-        doReturn("token-tab1")
-                .when(mComposeBoxQueryControllerBridge)
-                .addTabContextFromCache(anyLong());
+        doReturn("token-tab1").when(mComposeBoxQueryControllerBridge).addTabContext(mTab1);
         doReturn(mTab1).when(mTabModelSelector).getCurrentTab();
         doReturn("Title1").when(mTab1).getTitle();
         doReturn(new GURL("https://www.google.com")).when(mTab1).getUrl();
         doReturn(true).when(mTab1).isInitialized();
+        doReturn(false).when(mTab1).isFrozen();
         doReturn(100L).when(mTab1).getTimestampMillis();
+        doReturn(mWebContents).when(mTab1).getWebContents();
+        doReturn(false).when(mWebContents).isLoading();
+        doReturn(mRenderWidgetHostView).when(mWebContents).getRenderWidgetHostView();
 
         mModel.get(FuseboxProperties.BUTTON_ADD_CLICKED).run();
         assertTrue(mModel.get(FuseboxProperties.POPUP_CREATE_IMAGE_BUTTON_ENABLED));
@@ -798,4 +806,10 @@
         // AI Mode is NOT activated and AutocompleteRequestType remains SEARCH.
         assertEquals(AutocompleteRequestType.SEARCH, (int) mAutocompleteRequestTypeSupplier.get());
     }
+
+    @Test
+    public void testFailedUpload() {
+        mMediator.onAttachmentUploadFailed();
+        verify(mSnackbarManager).showSnackbar(any());
+    }
 }
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/fusebox/FuseboxPopup.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/fusebox/FuseboxPopup.java
index 9f9b1aa..354dbaa9 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/fusebox/FuseboxPopup.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/fusebox/FuseboxPopup.java
@@ -13,11 +13,12 @@
 import org.chromium.chrome.browser.omnibox.R;
 import org.chromium.ui.widget.AnchoredPopupWindow;
 
+import java.util.List;
+
 /** A popup for the Fusebox component. */
 @NullMarked
 class FuseboxPopup {
-    private final AnchoredPopupWindow mPopupWindow;
-    private final View mContentView;
+    /* package */ final AnchoredPopupWindow mPopupWindow;
     /* package */ final Button mAddCurrentTab;
     /* package */ final Button mTabButton;
     /* package */ final Button mCameraButton;
@@ -28,9 +29,11 @@
     /* package */ final Button mCreateImageButton;
     /* package */ final View mRequestTypeDivider;
 
+    /* package */ final List<Button> mButtons;
+    /* package */ final List<View> mDividers;
+
     FuseboxPopup(Context context, AnchoredPopupWindow popupWindow, View contentView) {
         mPopupWindow = popupWindow;
-        mContentView = contentView;
         // `match_parent` and `wrap_content` don't exactly work well in our case.
         // Marking buttons `wrap_content` always narrows down button area, producing inconsistent
         // sizing, and asking for `match_parent` results in text wrapping, as the parent is unable
@@ -38,18 +41,30 @@
         mPopupWindow.setDesiredContentSize(
                 context.getResources().getDimensionPixelSize(R.dimen.fusebox_popup_width), 0);
         mPopupWindow.setHorizontalOverlapAnchor(true);
-        mTabButton = mContentView.findViewById(R.id.fusebox_pick_tabs_button);
+        mTabButton = contentView.findViewById(R.id.fusebox_pick_tabs_button);
         if (ChromeFeatureList.sChromeItemPickerUi.isEnabled()) {
             mTabButton.setVisibility(View.VISIBLE);
         }
-        mCameraButton = mContentView.findViewById(R.id.fusebox_camera_button);
-        mGalleryButton = mContentView.findViewById(R.id.fusebox_pick_picture_button);
-        mFileButton = mContentView.findViewById(R.id.fusebox_pick_file_button);
-        mClipboardButton = mContentView.findViewById(R.id.fusebox_paste_from_clipboard_button);
-        mAiModeButton = mContentView.findViewById(R.id.fusebox_ai_mode_button);
-        mCreateImageButton = mContentView.findViewById(R.id.fusebox_create_image_button);
-        mAddCurrentTab = mContentView.findViewById(R.id.fusebox_add_current_tab);
-        mRequestTypeDivider = mContentView.findViewById(R.id.fusebox_request_type_divider);
+        mCameraButton = contentView.findViewById(R.id.fusebox_camera_button);
+        mGalleryButton = contentView.findViewById(R.id.fusebox_pick_picture_button);
+        mFileButton = contentView.findViewById(R.id.fusebox_pick_file_button);
+        mClipboardButton = contentView.findViewById(R.id.fusebox_paste_from_clipboard_button);
+        mAiModeButton = contentView.findViewById(R.id.fusebox_ai_mode_button);
+        mCreateImageButton = contentView.findViewById(R.id.fusebox_create_image_button);
+        mAddCurrentTab = contentView.findViewById(R.id.fusebox_add_current_tab);
+        mRequestTypeDivider = contentView.findViewById(R.id.fusebox_request_type_divider);
+
+        mButtons =
+                List.of(
+                        mAddCurrentTab,
+                        mClipboardButton,
+                        mTabButton,
+                        mCameraButton,
+                        mGalleryButton,
+                        mFileButton,
+                        mAiModeButton,
+                        mCreateImageButton);
+        mDividers = List.of(mRequestTypeDivider);
     }
 
     void show() {
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/fusebox/FuseboxTabUtils.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/fusebox/FuseboxTabUtils.java
index c7c3fb9..cd60dd6 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/fusebox/FuseboxTabUtils.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/fusebox/FuseboxTabUtils.java
@@ -22,9 +22,21 @@
         // TODO: This also has to check the eligibility here:
         // components/optimization_guide/content/browser/page_context_eligibility.h
         return tab != null
-                && tab.isInitialized()
-                && !tab.isFrozen()
                 && (tab.getUrl().getScheme().equals(UrlConstants.HTTP_SCHEME)
                         || tab.getUrl().getScheme().equals(UrlConstants.HTTPS_SCHEME));
     }
+
+    /**
+     * Returns the whether a tab is active.
+     *
+     * @param tab The tab to be checked.
+     */
+    public static boolean isTabActive(@Nullable Tab tab) {
+        return tab != null
+                && tab.isInitialized()
+                && !tab.isFrozen()
+                && tab.getWebContents() != null
+                && !tab.getWebContents().isLoading()
+                && tab.getWebContents().getRenderWidgetHostView() != null;
+    }
 }
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/fusebox/FuseboxViewBinder.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/fusebox/FuseboxViewBinder.java
index f3b05e39..5a82549 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/fusebox/FuseboxViewBinder.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/fusebox/FuseboxViewBinder.java
@@ -50,6 +50,11 @@
             updateToolDrawables(model.get(FuseboxProperties.AUTOCOMPLETE_REQUEST_TYPE), view);
         } else if (propertyKey == FuseboxProperties.COLOR_SCHEME) {
             updateButtonsVisibilityAndStyling(model, view);
+            Context context = view.parentView.getContext();
+            @BrandedColorScheme int brandedColorScheme = model.get(FuseboxProperties.COLOR_SCHEME);
+            Drawable background =
+                    OmniboxResourceProvider.getPopupBackgroundDrawable(context, brandedColorScheme);
+            view.popup.mPopupWindow.setBackgroundDrawable(background);
         } else if (propertyKey == FuseboxProperties.COMPACT_UI) {
             reanchorViewsForCompactFusebox(model, view);
         } else if (propertyKey == FuseboxProperties.AUTOCOMPLETE_REQUEST_TYPE_CLICKED) {
@@ -228,8 +233,7 @@
                 startDrawable = context.getDrawable(R.drawable.create_image_24dp);
                 endDrawable = assumeNonNull(context.getDrawable(R.drawable.btn_close)).mutate();
                 endDrawable.setTint(
-                        OmniboxResourceProvider.getImageGenIconTintColor(
-                                context, brandedColorScheme));
+                        OmniboxResourceProvider.getDefaultIconColor(context, brandedColorScheme));
             } else if (showTryAiModeHintInDedicatedModeButton) {
                 text = res.getString(R.string.ai_mode_entrypoint_hint);
                 description = text;
@@ -285,6 +289,24 @@
         views.popup.mRequestTypeDivider.setVisibility(
                 isAiModeButtonVisible || isCreateImageButtonVisible ? View.VISIBLE : View.GONE);
         views.popup.mFileButton.setEnabled(!isImageGenerationUsed);
+
+        @StyleRes
+        int textAppearance = OmniboxResourceProvider.getPopupButtonTextRes(brandedColorScheme);
+        ColorStateList iconTint =
+                ColorStateList.valueOf(
+                        OmniboxResourceProvider.getDefaultIconColor(context, brandedColorScheme));
+        for (Button button : views.popup.mButtons) {
+            button.setTextAppearance(textAppearance);
+            // Tints directly applied to drawables will take precedence over this tint.
+            button.setCompoundDrawableTintList(iconTint);
+        }
+
+        @ColorInt
+        int dividerLineColor =
+                OmniboxResourceProvider.getPopupDividerLineColor(context, brandedColorScheme);
+        for (View divider : views.popup.mDividers) {
+            divider.setBackgroundColor(dividerLineColor);
+        }
     }
 
     static void reanchorViewsForCompactFusebox(PropertyModel model, FuseboxViewHolder views) {
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/styles/OmniboxResourceProvider.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/styles/OmniboxResourceProvider.java
index 807b5e9..a5e2c22 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/styles/OmniboxResourceProvider.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/styles/OmniboxResourceProvider.java
@@ -688,7 +688,7 @@
     }
 
     /** Resolves the icon tint to be used for secondary image gen icons, not the banana. */
-    public static @ColorInt int getImageGenIconTintColor(
+    public static @ColorInt int getDefaultIconColor(
             Context context, @BrandedColorScheme int brandedColorScheme) {
         boolean isIncognito =
                 convertBrandedColorSchemeToIncognitoOrDayNightAdaptive(brandedColorScheme);
@@ -719,6 +719,14 @@
         return ChromeColors.getPrimaryIconTint(context, isIncognito);
     }
 
+    /** Resolves the icon tint color for the icons that should be vivid, such as the + button. */
+    public static @ColorInt int getPopupDividerLineColor(
+            Context context, @BrandedColorScheme int brandedColorScheme) {
+        boolean isIncognito =
+                convertBrandedColorSchemeToIncognitoOrDayNightAdaptive(brandedColorScheme);
+        return IncognitoColors.getDividerLineBgColor(context, isIncognito);
+    }
+
     /** Resolves the text appearance for the image gen chip. */
     public static @StyleRes int getImageGenButtonTextRes(
             @BrandedColorScheme int brandedColorScheme) {
@@ -741,6 +749,13 @@
         return IncognitoColors.getTextSmallSecondary(isIncognito);
     }
 
+    /** Resolves the text appearance for menu items in the popup. */
+    public static @StyleRes int getPopupButtonTextRes(@BrandedColorScheme int brandedColorScheme) {
+        boolean isIncognito =
+                convertBrandedColorSchemeToIncognitoOrDayNightAdaptive(brandedColorScheme);
+        return IncognitoColors.getTextMediumPrimary(isIncognito);
+    }
+
     /** Returns the drawable that is to go behind the + button in the search box. */
     public static Drawable getSearchBoxIconBackground(
             Context context, @BrandedColorScheme int brandedColorScheme) {
@@ -754,6 +769,16 @@
         return getDrawable(context, resId);
     }
 
+    /** Returns the drawable for the popup menu that shows menu items for context and tools. */
+    public static Drawable getPopupBackgroundDrawable(
+            Context context, @BrandedColorScheme int brandedColorScheme) {
+        boolean isIncognito =
+                convertBrandedColorSchemeToIncognitoOrDayNightAdaptive(brandedColorScheme);
+        @DrawableRes
+        int resId = isIncognito ? R.drawable.menu_bg_tinted_on_dark_bg : R.drawable.menu_bg_tinted;
+        return getDrawable(context, resId);
+    }
+
     public static void setTabFaviconFactory(Function<Tab, @Nullable Bitmap> tabFaviconFactory) {
         sTabFaviconFactory = tabFaviconFactory;
     }
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/ActionButtonView.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/ActionButtonView.java
index fee21c0..4b34465a 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/ActionButtonView.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/ActionButtonView.java
@@ -8,11 +8,14 @@
 import android.view.MotionEvent;
 import android.view.View;
 
+import androidx.annotation.DrawableRes;
 import androidx.appcompat.widget.AppCompatImageView;
 
 import org.chromium.base.task.PostTask;
 import org.chromium.base.task.TaskTraits;
 import org.chromium.build.annotations.NullMarked;
+import org.chromium.chrome.browser.omnibox.R;
+import org.chromium.chrome.browser.omnibox.styles.OmniboxResourceProvider;
 
 /**
  * A View that is displayed as an action button.
@@ -25,6 +28,7 @@
     private boolean mParentHovered;
     private boolean mParentSelected;
     private boolean mHovered;
+    private boolean mSelected;
 
     public ActionButtonView(Context context) {
         super(context);
@@ -85,11 +89,19 @@
         if (action == MotionEvent.ACTION_HOVER_ENTER || action == MotionEvent.ACTION_HOVER_EXIT) {
             mHovered = action == MotionEvent.ACTION_HOVER_ENTER;
             updateVisibility();
+            updateVisualStyle();
         }
 
         return result;
     }
 
+    @Override
+    public void setSelected(boolean selected) {
+        super.setSelected(selected);
+        mSelected = selected;
+        updateVisualStyle();
+    }
+
     public boolean isActionButtonHovered() {
         return mHovered;
     }
@@ -97,4 +109,17 @@
     void dispatchHoverEventForTesting(MotionEvent event) {
         dispatchHoverEvent(event);
     }
+
+    private void updateVisualStyle() {
+        if (getVisibility() != View.VISIBLE) return;
+        @DrawableRes
+        int resId =
+                mSelected
+                        ? (mHovered
+                                ? R.drawable.action_button_selected_hovered
+                                : R.drawable.action_button_selected)
+                        : (mHovered ? R.drawable.action_button_hovered : 0);
+        setForeground(
+                (resId != 0) ? OmniboxResourceProvider.getDrawable(getContext(), resId) : null);
+    }
 }
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionView.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionView.java
index ba22359..9ee52d4 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionView.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionView.java
@@ -17,12 +17,10 @@
 
 import androidx.annotation.LayoutRes;
 import androidx.annotation.VisibleForTesting;
-import androidx.appcompat.content.res.AppCompatResources;
 
 import org.chromium.build.annotations.CheckDiscard;
 import org.chromium.build.annotations.NullMarked;
 import org.chromium.build.annotations.Nullable;
-import org.chromium.chrome.browser.omnibox.R;
 import org.chromium.chrome.browser.omnibox.suggestions.SimpleSelectionController;
 import org.chromium.components.browser_ui.widget.RoundedCornerOutlineProvider;
 import org.chromium.ui.base.KeyNavigationUtil;
@@ -126,14 +124,7 @@
      * @param isSelected whether to apply hairline
      */
     private void highlightActionButton(int buttonIndex, boolean isHighlighted) {
-        highlightActionButton(mActionButtons.get(buttonIndex), isHighlighted);
-    }
-
-    private void highlightActionButton(ActionButtonView actionButtonView, boolean isHighlighted) {
-        actionButtonView.setForeground(
-                isHighlighted
-                        ? AppCompatResources.getDrawable(getContext(), R.drawable.hairline_circle)
-                        : null);
+        ActionButtonView actionButtonView = mActionButtons.get(buttonIndex);
         actionButtonView.setSelected(isHighlighted);
         if (isHighlighted) {
             actionButtonView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
@@ -255,10 +246,6 @@
             }
         }
         setHovered(mIsHovered || isAnyActionButtonHovered);
-
-        if (actionButtonView != null) {
-            highlightActionButton(actionButtonView, isActionButtonHovered);
-        }
     }
 
     @Override
diff --git a/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/SigninBottomSheetCoordinator.java b/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/SigninBottomSheetCoordinator.java
index ef6c2c4..59a5825 100644
--- a/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/SigninBottomSheetCoordinator.java
+++ b/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/SigninBottomSheetCoordinator.java
@@ -244,6 +244,18 @@
     }
 
     /**
+     * TODO(crbug.com/464507068): This method name is temporary and linked to a specific
+     * implementation. The interface should be improved to use a generic `onSignInCancel()` from the
+     * delegate.
+     */
+    @Override
+    public void onSeamlessSigninAbandoned() {
+        assert mSeamlessSigninCoordinator != null;
+        assert mAccountPickerBottomSheetCoordinator == null;
+        mDelegate.onSignInCancel();
+    }
+
+    /**
      * Called by the embedder to dismiss the bottom sheet. This method is different from
      * `onAccountPickerDestroy` since the latter is called by the account picker coordinator, and
      * only after the bottom sheet's dismissal.
diff --git a/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/account_picker/AccountPickerBottomSheetCoordinator.java b/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/account_picker/AccountPickerBottomSheetCoordinator.java
index aae75b04..4fcf692 100644
--- a/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/account_picker/AccountPickerBottomSheetCoordinator.java
+++ b/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/account_picker/AccountPickerBottomSheetCoordinator.java
@@ -134,6 +134,7 @@
     public void dismiss() {
         logMetricAndIncrementActiveDismissalCountIfWebSignin(
                 AccountConsistencyPromoAction.DISMISSED_BUTTON);
+        // The observer calls destroy() after the sheet is hidden.
         mBottomSheetController.hideContent(mView, true);
     }
 
diff --git a/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/account_picker/AccountPickerBottomSheetMediator.java b/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/account_picker/AccountPickerBottomSheetMediator.java
index 509895b..31be49f 100644
--- a/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/account_picker/AccountPickerBottomSheetMediator.java
+++ b/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/account_picker/AccountPickerBottomSheetMediator.java
@@ -5,7 +5,6 @@
 package org.chromium.chrome.browser.ui.signin.account_picker;
 
 import static org.chromium.build.NullUtil.assertNonNull;
-import static org.chromium.build.NullUtil.assumeNonNull;
 
 import android.accounts.AccountManager;
 import android.app.Activity;
@@ -63,7 +62,6 @@
     private final IdentityManager mIdentityManager;
     private final SigninManager mSigninManager;
     private final AccountPickerDelegate mAccountPickerDelegate;
-    private final @Nullable Runnable mRequestDisplayBottomSheet;
     private final Runnable mDismissBottomSheet;
     private final DeviceLockActivityLauncher mDeviceLockActivityLauncher;
     private final @ViewState int mInitialViewState;
@@ -76,6 +74,7 @@
     private final AccountManagerFacade mAccountManagerFacade;
     private final boolean mIsSeamlessSignin;
 
+    private @Nullable Runnable mRequestDisplayBottomSheet;
     private @Nullable SigninFlowTimestampsLogger mSigninTimestampsLogger;
     private @Nullable CoreAccountInfo mSelectedAccount;
     private @Nullable CoreAccountInfo mDefaultAccount;
@@ -346,12 +345,6 @@
     /** Implements {@link AccountsChangeObserver}. */
     @Override
     public void onCoreAccountInfosChanged() {
-        if (mIsSeamlessSignin) {
-            // TODO(crbug.com/437038737): Handle selected account disappearance in seamless sign-in
-            // when bottom sheet is shown.
-            throw new UnsupportedOperationException(
-                    "Account changes are not yet supported in the seamless sign-in flow.");
-        }
         mAccountManagerFacade.getAccounts().then(this::updateAccounts);
     }
 
@@ -372,8 +365,8 @@
             mSigninManager.setUserAcceptedAccountManagement(false);
         }
         mModel.set(AccountPickerBottomSheetProperties.VIEW_STATE, ViewState.SIGNIN_GENERAL_ERROR);
-        if (mIsSeamlessSignin) {
-            assumeNonNull(mRequestDisplayBottomSheet).run();
+        if (mIsSeamlessSignin && mRequestDisplayBottomSheet != null) {
+            mRequestDisplayBottomSheet.run();
         }
     }
 
@@ -451,6 +444,16 @@
     }
 
     private void updateAccounts(List<AccountInfo> accounts) {
+        if (mIsSeamlessSignin) {
+            if (mSelectedAccount != null
+                    && AccountUtils.findAccountByAccountId(accounts, mSelectedAccount.getId())
+                            == null) {
+                // Account has been removed.
+                abandonSeamlessSignin();
+            }
+            return;
+        }
+
         if (accounts.isEmpty()) {
             // If all accounts disappeared, no matter if the account list is collapsed or expanded,
             // we will go to the zero account screen.
@@ -579,17 +582,8 @@
     }
 
     private void signIn() {
-        // If the account is not available or disappears right after the user adds it, the sign-in
-        // can't be done and a general error view with retry button is shown.
         if (mSelectedAccount == null) {
-            if (mIsSeamlessSignin) {
-                // TODO(crbug.com/437038737): Confirm if error screen should be shown or sign-in
-                // should be abandoned.
-                throw new UnsupportedOperationException(
-                        "Account being unavailable during sign-in is not supported.");
-            }
-            mModel.set(
-                    AccountPickerBottomSheetProperties.VIEW_STATE, ViewState.SIGNIN_GENERAL_ERROR);
+            handleMissingSelectedAccountForSignIn();
             return;
         }
 
@@ -614,23 +608,14 @@
 
     private void shownConfirmManagementSheet() {
         mModel.set(AccountPickerBottomSheetProperties.VIEW_STATE, ViewState.CONFIRM_MANAGEMENT);
-        if (mIsSeamlessSignin) {
-            assumeNonNull(mRequestDisplayBottomSheet).run();
+        if (mIsSeamlessSignin && mRequestDisplayBottomSheet != null) {
+            mRequestDisplayBottomSheet.run();
         }
     }
 
     private void signInAfterCheckingManagement() {
-        // If the account is not available or disappears right after the user adds it, the sign-in
-        // can't be done and a general error view with retry button is shown.
         if (mSelectedAccount == null) {
-            if (mIsSeamlessSignin) {
-                // TODO(crbug.com/437038737): Confirm if error screen should be shown or sign-in
-                // should be abandoned.
-                throw new UnsupportedOperationException(
-                        "Account being unavailable during sign-in is not supported.");
-            }
-            mModel.set(
-                    AccountPickerBottomSheetProperties.VIEW_STATE, ViewState.SIGNIN_GENERAL_ERROR);
+            handleMissingSelectedAccountForSignIn();
             return;
         }
 
@@ -682,6 +667,33 @@
                 });
     }
 
+    /** Handles a missing selected account during sign-in. */
+    private void handleMissingSelectedAccountForSignIn() {
+        if (mIsSeamlessSignin) {
+            abandonSeamlessSignin();
+        } else {
+            mModel.set(
+                    AccountPickerBottomSheetProperties.VIEW_STATE, ViewState.SIGNIN_GENERAL_ERROR);
+        }
+    }
+
+    private void abandonSeamlessSignin() {
+        assert mIsSeamlessSignin;
+
+        if (mSelectedAccount == null) {
+            // The seamless sign-in flow has already been abandoned.
+            return;
+        }
+        // Permanently hide the bottom sheet and prevent any further sign-in attempts with the
+        // now-invalid account.
+        mSelectedAccount = null;
+        mDefaultAccount = null;
+        mRequestDisplayBottomSheet = null;
+        // TODO(crbug.com/437038737): Log Event.SIGNIN_ABORTED timestamp.
+
+        mAccountPickerDelegate.onSeamlessSigninAbandoned();
+    }
+
     private void updateCredentials() {
         final Callback<Boolean> onUpdateCredentialsCompleted =
                 isSuccess -> {
diff --git a/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/account_picker/AccountPickerDelegate.java b/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/account_picker/AccountPickerDelegate.java
index 1c60075f..bd456ae 100644
--- a/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/account_picker/AccountPickerDelegate.java
+++ b/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/account_picker/AccountPickerDelegate.java
@@ -51,6 +51,16 @@
     void onSignInComplete(
             CoreAccountInfo accountInfo, AccountPickerDelegate.SigninStateController controller);
 
+    /**
+     * Called when the seamless sign-in process cannot proceed, for example, if the target account
+     * is removed. Implementers should use this to clean up resources and ensure any associated UI
+     * is dismissed.
+     * TODO(crbug.com/464507068): This method name is temporary and linked to a specific
+     * implementation. The interface should be improved to use a generic `onSignInCancel()` from the
+     * delegate.
+     */
+    default void onSeamlessSigninAbandoned() {}
+
     default @FlowVariant String getSigninFlowVariant() {
         return FlowVariant.OTHER;
     }
diff --git a/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/account_picker/SeamlessSigninCoordinator.java b/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/account_picker/SeamlessSigninCoordinator.java
index 22574ee..69311571 100644
--- a/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/account_picker/SeamlessSigninCoordinator.java
+++ b/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/account_picker/SeamlessSigninCoordinator.java
@@ -93,7 +93,7 @@
     }
 
     @MainThread
-    public void destroy() {
+    void destroy() {
         mAccountPickerBottomSheetMediator.destroy();
         mBottomSheetController.removeObserver(mBottomSheetObserver);
     }
@@ -103,7 +103,7 @@
      * confirmation.
      */
     @MainThread
-    public void requestDisplayBottomSheet() {
+    void requestDisplayBottomSheet() {
         if (mView == null) {
             // Bottom sheet initialized lazily, in most cases no bottom sheet will be shown
             mView = new AccountPickerBottomSheetView(mActivity, mAccountPickerBottomSheetMediator);
@@ -123,9 +123,12 @@
     @MainThread
     public void dismissBottomSheet() {
         if (mView != null) {
-            // TODO(crbug.com/437038737): log AccountConsistencyPromoAction.DISMISSED_BUTTON
-            // histogram
+            // TODO(crbug.com/437038737): Log AccountConsistencyPromoAction.DISMISSED_BUTTON
+            // histogram.
+            // The observer calls destroy() after the sheet is hidden.
             mBottomSheetController.hideContent(mView, true);
+        } else {
+            destroy();
         }
     }
 
diff --git a/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/account_picker/SeamlessSigninTest.java b/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/account_picker/SeamlessSigninTest.java
index 92ac503..7b73f6e 100644
--- a/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/account_picker/SeamlessSigninTest.java
+++ b/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/account_picker/SeamlessSigninTest.java
@@ -17,8 +17,10 @@
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -122,6 +124,13 @@
                 .isAccountManaged(eq(TestAccounts.ACCOUNT1), any());
         when(mSigninManagerMock.extractDomainName(TestAccounts.ACCOUNT1.getEmail()))
                 .thenReturn(TEST_DOMAIN);
+        doAnswer(
+                        (invocation) -> {
+                            mCoordinator.dismissBottomSheet();
+                            return null;
+                        })
+                .when(mAccountPickerDelegateMock)
+                .onSeamlessSigninAbandoned();
 
         mBottomSheetController =
                 mActivityTestRule
@@ -142,6 +151,8 @@
      *
      * <p>|AccountConsistencyPromoAction.SIGNED_IN_WITH_DEFAULT_ACCOUNT| is recorded correctly.
      * |AccountConsistencyPromoAction.SHOWN| is not recorded when the bottom sheet is not shown.
+     *
+     * <p>Also add coverage for |Event.SIGNIN_ABORTED| when sign-in is aborted.
      */
     @Test
     @MediumTest
@@ -260,16 +271,7 @@
         calledInOrder.verify(mSigninManagerMock).signin(eq(TestAccounts.ACCOUNT1), anyInt(), any());
     }
 
-    /**
-     * TODO(crbug.com/435381574): Add coverage for removing account during initialization, sign-in,
-     * or when waiting for confirmation on management notice:
-
-     * testSignInDefaultAccount_removeAccountDuringInitialization
-     * testSignInManagedAccount_removeAccountDuringInitialization
-     * testSignInManagedAccount_removeAccountWhileOnManagementNoticeSheet
-     *
-     * <p>Investigate if necessary to handle account removal while loading spinner is shown
-     */
+    /** TODO(crbug.com/435381574): Add coverage for removing account during initialization */
     @Test
     @MediumTest
     public void testFailedSignInDefaultAccount_errorScreenShown() {
@@ -328,6 +330,65 @@
 
     @Test
     @MediumTest
+    public void testDuringSignIn_removingAccountAbandonsSignInFlow() {
+        emulateLongSignin();
+        createCoordinator();
+
+        // Remove the account while signin() is executing.
+        mAccountManagerTestRule.removeAccount(TestAccounts.ACCOUNT1.getId());
+
+        verify(mAccountPickerDelegateMock, timeout(CriteriaHelper.DEFAULT_MAX_TIME_TO_POLL))
+                .onSeamlessSigninAbandoned();
+        assertBottomSheetNeverShown();
+    }
+
+    @Test
+    @MediumTest
+    public void testWhileOnErrorSheetForDefaultAccount_removingAccountAbandonsSignInFlow() {
+        mIsNextSigninSuccessful.set(false);
+        createCoordinator();
+        waitForErrorSheet();
+
+        // Remove the account while the error sheet is shown.
+        mAccountManagerTestRule.removeAccount(TestAccounts.ACCOUNT1.getId());
+
+        verify(mAccountPickerDelegateMock, timeout(CriteriaHelper.DEFAULT_MAX_TIME_TO_POLL))
+                .onSeamlessSigninAbandoned();
+        CriteriaHelper.pollUiThread(() -> !mBottomSheetController.isSheetOpen());
+    }
+
+    @Test
+    @MediumTest
+    public void testWaitingOnManagementConfirmation_removingAccountAbandonsSignInFlow() {
+        mIsAccountManaged = true;
+        createCoordinator();
+        waitForManagementNoticeSheet();
+
+        // Remove the account while the management notice sheet is shown.
+        mAccountManagerTestRule.removeAccount(TestAccounts.ACCOUNT1.getId());
+
+        verify(mAccountPickerDelegateMock, timeout(CriteriaHelper.DEFAULT_MAX_TIME_TO_POLL))
+                .onSeamlessSigninAbandoned();
+        CriteriaHelper.pollUiThread(() -> !mBottomSheetController.isSheetOpen());
+    }
+
+    @Test
+    @MediumTest
+    public void testOnDeviceLockActivity_removingAccountAbandonsSignInFlow() {
+        mAutoTestRule.setIsAutomotive(true);
+        createCoordinator();
+
+        // Remove the account before user completes the device lock.
+        mAccountManagerTestRule.removeAccount(TestAccounts.ACCOUNT1.getId());
+        SigninTestUtil.completeDeviceLock(mDeviceLockActivityLauncher, true);
+
+        verify(mAccountPickerDelegateMock, timeout(CriteriaHelper.DEFAULT_MAX_TIME_TO_POLL))
+                .onSeamlessSigninAbandoned();
+        assertBottomSheetNeverShown();
+    }
+
+    @Test
+    @MediumTest
     public void testTryAgainButton_withDefaultAccount_spinnerShown() {
         mIsNextSigninSuccessful.set(false);
         createCoordinator();
@@ -390,6 +451,33 @@
         verify(mAccountPickerDelegateMock).onSignInComplete(eq(TestAccounts.ACCOUNT1), any());
     }
 
+    @Test
+    @MediumTest
+    public void testBottomErrorSheetDismissalTriggersDestruction() {
+        mIsNextSigninSuccessful.set(false);
+        createCoordinator();
+        waitForErrorSheet();
+
+        // Dismissing the error sheet should trigger destroy() in the mediator.
+        ThreadUtils.runOnUiThreadBlocking(() -> mCoordinator.dismissBottomSheet());
+
+        CriteriaHelper.pollUiThread(() -> !mBottomSheetController.isSheetOpen());
+        verify(mAccountPickerDelegateMock).onAccountPickerDestroy();
+    }
+
+    @Test
+    @MediumTest
+    public void testDismissalWithoutVisibleBottomSheetTriggersDestruction() {
+        createCoordinator();
+        assertBottomSheetNeverShown();
+
+        // In the successful scenario where the bottom sheet is never shown, calling dismiss
+        // should still trigger destroy() in the mediator.
+        ThreadUtils.runOnUiThreadBlocking(() -> mCoordinator.dismissBottomSheet());
+
+        verify(mAccountPickerDelegateMock).onAccountPickerDestroy();
+    }
+
     private void assertBottomSheetNeverShown() {
         // View should never have been initialized
         assertNull(mCoordinator.getBottomSheetViewForTesting());
diff --git a/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/signin_promo/RecentTabsSigninPromoDelegate.java b/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/signin_promo/RecentTabsSigninPromoDelegate.java
index b6902b6..3b6b2ed 100644
--- a/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/signin_promo/RecentTabsSigninPromoDelegate.java
+++ b/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/signin_promo/RecentTabsSigninPromoDelegate.java
@@ -183,9 +183,6 @@
 
     @Override
     boolean shouldHideDismissButton() {
-        if (SigninFeatureMap.isEnabled(SigninFeatures.ENABLE_SEAMLESS_SIGNIN)) {
-            return mPromoState != PromoState.SIGNIN;
-        }
         return true;
     }
 
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chrome/browser/ui/android/strings/android_chrome_strings.grd
index 8e188a6..248ade81 100644
--- a/chrome/browser/ui/android/strings/android_chrome_strings.grd
+++ b/chrome/browser/ui/android/strings/android_chrome_strings.grd
@@ -7423,6 +7423,9 @@
       <message name="IDS_TIPS_PROMO_BOTTOM_SHEET_THIRD_STEP_BOTTOM_OMNIBOX" desc="Description of the Bottom Omnibox feature tip third step detail page promo.">
         Choose your preferred position
       </message>
+      <message name="IDS_TIPS_PROMO_BOTTOM_SHEET_TITLE_QUICK_DELETE_SHORT" desc="Title of the Quick Delete feature tip detail page promo. [CHAR_LIMIT=32]">
+        Quickly delete browsing data
+      </message>
       <message name="IDS_TIPS_PROMO_BOTTOM_SHEET_TITLE_BOTTOM_OMNIBOX_SHORT" desc="Title of the Bottom Omnibox feature tip detail page promo. [CHAR_LIMIT=32]">
         Choose address bar position
       </message>
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_TIPS_PROMO_BOTTOM_SHEET_TITLE_QUICK_DELETE_SHORT.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_TIPS_PROMO_BOTTOM_SHEET_TITLE_QUICK_DELETE_SHORT.png.sha1
new file mode 100644
index 0000000..a51166a2
--- /dev/null
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_TIPS_PROMO_BOTTOM_SHEET_TITLE_QUICK_DELETE_SHORT.png.sha1
@@ -0,0 +1 @@
+6a85489cb67cfd8347a8954675fe8e491075cab9
\ No newline at end of file
diff --git a/chrome/browser/ui/ash/back_gesture/back_gesture_contextual_nudge_delegate.cc b/chrome/browser/ui/ash/back_gesture/back_gesture_contextual_nudge_delegate.cc
index c0eafd654..d568b74d 100644
--- a/chrome/browser/ui/ash/back_gesture/back_gesture_contextual_nudge_delegate.cc
+++ b/chrome/browser/ui/ash/back_gesture/back_gesture_contextual_nudge_delegate.cc
@@ -85,8 +85,9 @@
   if (window_) {
     ash::BrowserDelegate* browser =
         ash::BrowserController::GetInstance()->GetBrowserForWindow(window_);
-    CHECK(browser);
-    browser->GetBrowser().tab_strip_model()->RemoveObserver(this);
+    if (browser) {
+      browser->GetBrowser().tab_strip_model()->RemoveObserver(this);
+    }
 
     window_->RemoveObserver(this);
     window_ = nullptr;
diff --git a/chrome/browser/ui/browser_actions.cc b/chrome/browser/ui/browser_actions.cc
index 936e13d..6d84c2a 100644
--- a/chrome/browser/ui/browser_actions.cc
+++ b/chrome/browser/ui/browser_actions.cc
@@ -1350,7 +1350,7 @@
                 IDS_CONTEXTUAL_TASKS_CONTEXTUAL_TASKS_TITLE))
             .SetTooltipText(l10n_util::GetStringUTF16(
                 IDS_CONTEXTUAL_TASKS_CONTEXTUAL_TASKS_TITLE))
-            .SetImage(ui::ImageModel::FromVectorIcon(vector_icons::kChatIcon,
+            .SetImage(ui::ImageModel::FromVectorIcon(kDockToRightSparkIcon,
                                                      ui::kColorIcon))
             .SetProperty(
                 actions::kActionItemPinnableKey,
diff --git a/chrome/browser/ui/browser_finder.cc b/chrome/browser/ui/browser_finder.cc
index 8382dc8..64f27bd 100644
--- a/chrome/browser/ui/browser_finder.cc
+++ b/chrome/browser/ui/browser_finder.cc
@@ -383,4 +383,22 @@
       profile, kMatchNormal | kIncludeBrowsersScheduledForDeletion);
 }
 
+size_t GetOffTheRecordBrowsersActiveForProfile(Profile* profile) {
+  if (!profile) {
+    return 0;
+  }
+
+  size_t incognito_window_count = 0;
+  ForEachCurrentBrowserWindowInterfaceOrderedByActivation(
+      [profile, &incognito_window_count](BrowserWindowInterface* browser) {
+        if (browser->GetProfile()->IsSameOrParent(profile) &&
+            browser->GetProfile()->IsOffTheRecord() &&
+            browser->GetType() != BrowserWindowInterface::Type::TYPE_DEVTOOLS) {
+          ++incognito_window_count;
+        }
+        return true;
+      });
+  return incognito_window_count;
+}
+
 }  // namespace chrome
diff --git a/chrome/browser/ui/browser_finder.h b/chrome/browser/ui/browser_finder.h
index db7ed2d8..dfb5b57 100644
--- a/chrome/browser/ui/browser_finder.h
+++ b/chrome/browser/ui/browser_finder.h
@@ -201,6 +201,10 @@
 // the majority of other functions do not.
 size_t GetTabbedBrowserCount(Profile* profile);
 
+// Returns the number of off-the-record browser windows associated with
+// `profile`, excluding DevTools windows.
+size_t GetOffTheRecordBrowsersActiveForProfile(Profile* profile);
+
 }  // namespace chrome
 
 #endif  // CHROME_BROWSER_UI_BROWSER_FINDER_H_
diff --git a/chrome/browser/ui/browser_manager_service.cc b/chrome/browser/ui/browser_manager_service.cc
index d53209d..fc004990 100644
--- a/chrome/browser/ui/browser_manager_service.cc
+++ b/chrome/browser/ui/browser_manager_service.cc
@@ -4,6 +4,8 @@
 
 #include "chrome/browser/ui/browser_manager_service.h"
 
+#include <algorithm>
+
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_window/public/browser_collection_observer.h"
@@ -21,6 +23,7 @@
 }
 
 void BrowserManagerService::AddBrowser(std::unique_ptr<Browser> browser) {
+  BrowserWindowInterface* const browser_ptr = browser.get();
   browsers_and_subscriptions_.push_back(std::pair(
       std::move(browser),
       std::pair(browser->RegisterDidBecomeActive(base::BindRepeating(
@@ -30,8 +33,13 @@
                     &BrowserManagerService::OnBrowserDeactivated,
                     base::Unretained(this))))));
 
+  // Push the browser to the back of the activation order list. It will be moved
+  // to the front when the browser is eventually activated (which may or may
+  // not happen immediately after creation).
+  browsers_activation_order_.push_back(browser_ptr);
+
   base::WeakPtr<BrowserWindowInterface> browser_weak_ptr =
-      browsers_and_subscriptions_.back().first->GetWeakPtr();
+      browser_ptr->GetWeakPtr();
   for (BrowserCollectionObserver& observer : observers()) {
     if (browser_weak_ptr) {
       observer.OnBrowserCreated(browser_weak_ptr.get());
@@ -50,6 +58,7 @@
         return browser_and_subscriptions.first.get() == removed_browser;
       });
   if (it != browsers_and_subscriptions_.end()) {
+    std::erase(browsers_activation_order_, it->first.get());
     target_browser_and_subscriptions = std::move(*it);
     browsers_and_subscriptions_.erase(it);
   } else {
@@ -64,7 +73,11 @@
 
 BrowserCollection::BrowserVector BrowserManagerService::GetBrowsers(
     Order order) {
-  CHECK_EQ(order, Order::kCreation);
+  CHECK(order == Order::kCreation || order == Order::kActivation);
+  if (order == Order::kActivation) {
+    return browsers_activation_order_;
+  }
+
   BrowserCollection::BrowserVector browsers;
   browsers.reserve(browsers_and_subscriptions_.size());
   std::ranges::transform(browsers_and_subscriptions_,
@@ -75,6 +88,11 @@
 
 void BrowserManagerService::OnBrowserActivated(
     BrowserWindowInterface* browser) {
+  // Move `browser` to the front of the activation list.
+  auto it = std::ranges::find(browsers_activation_order_, browser);
+  CHECK(it != browsers_activation_order_.end());
+  std::rotate(browsers_activation_order_.begin(), it, it + 1);
+
   for (BrowserCollectionObserver& observer : observers()) {
     observer.OnBrowserActivated(browser);
   }
diff --git a/chrome/browser/ui/browser_manager_service.h b/chrome/browser/ui/browser_manager_service.h
index 78c7d20..4f9e6da 100644
--- a/chrome/browser/ui/browser_manager_service.h
+++ b/chrome/browser/ui/browser_manager_service.h
@@ -52,8 +52,13 @@
   // Profile associated with this service.
   const raw_ptr<Profile> profile_;
 
+  // References to browsers owned by the service in activation order, with the
+  // most recently activated browser appearing at the front of the vector.
+  std::vector<raw_ptr<BrowserWindowInterface>> browsers_activation_order_;
+
   // We need to hold 2 subscriptions for each Browser: one for DidBecomeActive
-  // and one for DidBecomeInactive. Stores Browsers in creation order.
+  // and one for DidBecomeInactive. Stores the browser in creation order, with
+  // the least recently created browser appearing at the front of the vector.
   using BrowserAndSubscriptions =
       std::pair<std::unique_ptr<Browser>,
                 std::pair<base::CallbackListSubscription,
diff --git a/chrome/browser/ui/browser_window/BUILD.gn b/chrome/browser/ui/browser_window/BUILD.gn
index 6ac5cfe7..73161aa 100644
--- a/chrome/browser/ui/browser_window/BUILD.gn
+++ b/chrome/browser/ui/browser_window/BUILD.gn
@@ -52,6 +52,7 @@
     sources = [
       "internal/create_browser_window_browsertest.cc",
       "internal/global_browser_collection_browsertest.cc",
+      "internal/profile_browser_collection_browsertest.cc",
       "public/browser_window_interface_iterator_browsertest.cc",
     ]
     defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ]
diff --git a/chrome/browser/ui/browser_window/internal/android/java/src/org/chromium/chrome/browser/ui/browser_window/ChromeAndroidTaskIntegrationTest.java b/chrome/browser/ui/browser_window/internal/android/java/src/org/chromium/chrome/browser/ui/browser_window/ChromeAndroidTaskIntegrationTest.java
index a081d0d1..a95fc20 100644
--- a/chrome/browser/ui/browser_window/internal/android/java/src/org/chromium/chrome/browser/ui/browser_window/ChromeAndroidTaskIntegrationTest.java
+++ b/chrome/browser/ui/browser_window/internal/android/java/src/org/chromium/chrome/browser/ui/browser_window/ChromeAndroidTaskIntegrationTest.java
@@ -436,72 +436,79 @@
 
     @Test
     @MediumTest
-    @DisableFeatures(ChromeFeatureList.ROBUST_WINDOW_MANAGEMENT_EXPERIMENTAL)
-    @Restriction(DeviceFormFactor.TABLET_OR_DESKTOP /* test needs "new window" in app menu */)
-    public void show_activateVisibleInactiveTask() {
+    @Restriction(DeviceFormFactor.DESKTOP_FREEFORM /* test needs freeform windows */)
+    public void show_taskIsVisibleButInActive_activateTask() {
         // Arrange
         WebPageStation webPageStation = mFreshCtaTransitTestRule.startOnBlankPage();
-        int firstTaskId = mFreshCtaTransitTestRule.getActivity().getTaskId();
-        var chromeAndroidTask = getChromeAndroidTask(firstTaskId);
+        var firstChromeTabbedActivity = webPageStation.getActivity();
+        int firstTaskId = firstChromeTabbedActivity.getTaskId();
+        var firstChromeAndroidTask = getChromeAndroidTask(firstTaskId);
+        var firstWindowAndroid = firstChromeTabbedActivity.getWindowAndroid();
+        assertNotNull(firstChromeAndroidTask);
+        assertNotNull(firstWindowAndroid);
 
         RegularNewTabPageStation ntpStation =
                 webPageStation.openRegularTabAppMenu().openNewWindow();
         int secondTaskId = ntpStation.getActivity().getTaskId();
         var secondChromeAndroidTask = getChromeAndroidTask(secondTaskId);
-        assertNotNull(chromeAndroidTask);
         assertNotNull(secondChromeAndroidTask);
-        assertTrue(chromeAndroidTask.isVisible());
-        assertFalse(chromeAndroidTask.isActive());
+
+        assertTrue(firstChromeAndroidTask.isVisible());
+        assertFalse(firstChromeAndroidTask.isActive());
         assertTrue(secondChromeAndroidTask.isActive());
 
         // Act
-        chromeAndroidTask.show();
+        firstChromeAndroidTask.show();
 
         // Assert
         Assert.assertTrue(
-                "Show should make isActive true immediately", chromeAndroidTask.isActive());
-        CriteriaHelper.pollUiThread(
-                assumeNonNull(webPageStation.getActivity().getWindowAndroid())
-                        ::isTopResumedActivity);
+                "Show() should make isActive() true immediately",
+                firstChromeAndroidTask.isActive());
+        CriteriaHelper.pollUiThread(firstWindowAndroid::isTopResumedActivity);
         Assert.assertTrue(
-                "Show should make isActive true eventually", chromeAndroidTask.isActive());
+                "Show() should make isActive() true eventually", firstChromeAndroidTask.isActive());
         assertFalse(secondChromeAndroidTask.isActive());
+
         // Cleanup
         ntpStation.getActivity().finish();
     }
 
     @Test
     @MediumTest
-    @DisableFeatures(ChromeFeatureList.ROBUST_WINDOW_MANAGEMENT_EXPERIMENTAL)
-    @Restriction(DeviceFormFactor.TABLET_OR_DESKTOP /* test needs "new window" in app menu */)
-    public void showInactive_activateVisibleInactiveTask() {
+    @Restriction(DeviceFormFactor.DESKTOP_FREEFORM /* test needs freeform windows */)
+    public void showInactive_taskIsActive_activateAnotherTask() {
         // Arrange
         WebPageStation webPageStation = mFreshCtaTransitTestRule.startOnBlankPage();
-        int firstTaskId = mFreshCtaTransitTestRule.getActivity().getTaskId();
-        var chromeAndroidTask = getChromeAndroidTask(firstTaskId);
+        var firstChromeTabbedActivity = webPageStation.getActivity();
+        int firstTaskId = firstChromeTabbedActivity.getTaskId();
+        var firstChromeAndroidTask = getChromeAndroidTask(firstTaskId);
+        var firstWindowAndroid = firstChromeTabbedActivity.getWindowAndroid();
+        assertNotNull(firstChromeAndroidTask);
+        assertNotNull(firstWindowAndroid);
 
         RegularNewTabPageStation ntpStation =
                 webPageStation.openRegularTabAppMenu().openNewWindow();
         int secondTaskId = ntpStation.getActivity().getTaskId();
         var secondChromeAndroidTask = getChromeAndroidTask(secondTaskId);
-        assertNotNull(chromeAndroidTask);
         assertNotNull(secondChromeAndroidTask);
-        assertTrue(chromeAndroidTask.isVisible());
-        assertFalse(chromeAndroidTask.isActive());
+
+        assertTrue(firstChromeAndroidTask.isVisible());
+        assertFalse(firstChromeAndroidTask.isActive());
         assertTrue(secondChromeAndroidTask.isActive());
 
         // Act
         secondChromeAndroidTask.showInactive();
 
         // Assert
-        Assert.assertTrue(
-                "showInactive should make isActive true immediately", chromeAndroidTask.isActive());
-        CriteriaHelper.pollUiThread(
-                assumeNonNull(webPageStation.getActivity().getWindowAndroid())
-                        ::isTopResumedActivity);
-        Assert.assertTrue(
-                "showInactive should make isActive true eventually", chromeAndroidTask.isActive());
-        CriteriaHelper.pollUiThread(() -> !secondChromeAndroidTask.isActive());
+        assertTrue(
+                "2nd window's showInactive() should make 1st window's isActive() true immediately",
+                firstChromeAndroidTask.isActive());
+        CriteriaHelper.pollUiThread(firstWindowAndroid::isTopResumedActivity);
+        assertTrue(
+                "2nd window's showInactive() should make 1st window's isActive() true eventually",
+                firstChromeAndroidTask.isActive());
+        assertFalse(secondChromeAndroidTask.isActive());
+
         // Cleanup
         ntpStation.getActivity().finish();
     }
diff --git a/chrome/browser/ui/browser_window/internal/global_browser_collection.cc b/chrome/browser/ui/browser_window/internal/global_browser_collection.cc
index 62251fb..5822f77 100644
--- a/chrome/browser/ui/browser_window/internal/global_browser_collection.cc
+++ b/chrome/browser/ui/browser_window/internal/global_browser_collection.cc
@@ -20,13 +20,20 @@
 
 BrowserCollection::BrowserVector GlobalBrowserCollection::GetBrowsers(
     Order order) {
-  CHECK_EQ(order, Order::kCreation);
-  return browsers_creation_order_;
+  CHECK(order == Order::kCreation || order == Order::kActivation);
+  return order == Order::kCreation ? browsers_creation_order_
+                                   : browsers_activation_order_;
 }
 
 void GlobalBrowserCollection::OnBrowserCreated(
     BrowserWindowInterface* browser) {
   browsers_creation_order_.push_back(browser);
+
+  // Push the browser to the back of the activation order list. It will be moved
+  // to the front when the browser is eventually activated (which may or may
+  // not happen immediately after creation).
+  browsers_activation_order_.push_back(browser);
+
   for (BrowserCollectionObserver& observer : observers()) {
     observer.OnBrowserCreated(browser);
   }
@@ -36,11 +43,17 @@
   for (BrowserCollectionObserver& observer : observers()) {
     observer.OnBrowserClosed(browser);
   }
+  std::erase(browsers_activation_order_, browser);
   std::erase(browsers_creation_order_, browser);
 }
 
 void GlobalBrowserCollection::OnBrowserActivated(
     BrowserWindowInterface* browser) {
+  // Move `browser` to the front of the activation list.
+  auto it = std::ranges::find(browsers_activation_order_, browser);
+  CHECK(it != browsers_activation_order_.end());
+  std::rotate(browsers_activation_order_.begin(), it, it + 1);
+
   for (BrowserCollectionObserver& observer : observers()) {
     observer.OnBrowserActivated(browser);
   }
diff --git a/chrome/browser/ui/browser_window/internal/global_browser_collection_browsertest.cc b/chrome/browser/ui/browser_window/internal/global_browser_collection_browsertest.cc
index 7121eb32..a22cb0d 100644
--- a/chrome/browser/ui/browser_window/internal/global_browser_collection_browsertest.cc
+++ b/chrome/browser/ui/browser_window/internal/global_browser_collection_browsertest.cc
@@ -18,7 +18,8 @@
 
 using testing::_;
 
-class MockBrowserCollectionObserver : public BrowserCollectionObserver {
+class MockBrowserCollectionObserver
+    : public testing::NiceMock<BrowserCollectionObserver> {
  public:
   MOCK_METHOD(void, OnBrowserCreated, (BrowserWindowInterface * browser));
   MOCK_METHOD(void, OnBrowserClosed, (BrowserWindowInterface * browser));
@@ -28,23 +29,29 @@
 
 class GlobalBrowserCollectionTest : public InProcessBrowserTest {
  protected:
+  // Creates a new Profile and an associated browser. Reuses the default test
+  // profile on ChromeOS as multi-profile is not supported.
+  BrowserWindowInterface* CreateBrowserWithNewProfile() {
+    Profile* new_profile = GetProfile();
 #if !BUILDFLAG(IS_CHROMEOS)
-  Profile& CreateSecondaryProfile() {
-    ProfileManager* profile_manager = g_browser_process->profile_manager();
-    return profiles::testing::CreateProfileSync(
+    ProfileManager* const profile_manager =
+        g_browser_process->profile_manager();
+    new_profile = &profiles::testing::CreateProfileSync(
         profile_manager, profile_manager->GenerateNextProfileDirectoryPath());
-  }
 #endif  // !BUILDFLAG(IS_CHROMEOS)
+    return CreateBrowser(new_profile);
+  }
 
   // TODO(crbug.com/356183782): Consider rewriting this test as an interactive
   // ui test and using ui_test_utils::BringBrowserWindowToFront() instead.
-  void ActivatePrimaryBrowser(Browser* const secondary_browser) {
+  void ActivatePrimaryBrowser(BrowserWindowInterface* const secondary_browser) {
     browser()->DidBecomeActive();
-    secondary_browser->DidBecomeInactive();
+    secondary_browser->GetBrowserForMigrationOnly()->DidBecomeInactive();
   }
 
-  void ActivateSecondaryBrowser(Browser* const secondary_browser) {
-    secondary_browser->DidBecomeActive();
+  void ActivateSecondaryBrowser(
+      BrowserWindowInterface* const secondary_browser) {
+    secondary_browser->GetBrowserForMigrationOnly()->DidBecomeActive();
     browser()->DidBecomeInactive();
   }
 };
@@ -59,7 +66,7 @@
 
   // Create secondary browser and expect events.
   EXPECT_CALL(observer, OnBrowserCreated(_)).Times(1);
-  Browser* secondary_browser = CreateBrowser(GetProfile());
+  BrowserWindowInterface* const secondary_browser = CreateBrowser(GetProfile());
   testing::Mock::VerifyAndClearExpectations(&observer);
 
   // Start with secondary browser active.
@@ -92,9 +99,9 @@
   observation.Observe(GlobalBrowserCollection::GetInstance());
 
   // Create secondary profile and browser and expect events.
-  Profile& secondary_profile = CreateSecondaryProfile();
   EXPECT_CALL(observer, OnBrowserCreated(_)).Times(1);
-  Browser* secondary_browser = CreateBrowser(&secondary_profile);
+  BrowserWindowInterface* const secondary_browser =
+      CreateBrowserWithNewProfile();
   testing::Mock::VerifyAndClearExpectations(&observer);
 
   // Start with secondary browser active.
@@ -117,3 +124,115 @@
   CloseBrowserSynchronously(secondary_browser);
 }
 #endif  // !BUILDFLAG(IS_CHROMEOS)
+
+// Fixture that sets up 3 browsers.
+class GlobalBrowserCollectionTestWithOrder
+    : public GlobalBrowserCollectionTest,
+      public testing::WithParamInterface<BrowserCollection::Order> {
+ protected:
+  // GlobalBrowserCollectionTest:
+  void SetUpOnMainThread() override {
+    GlobalBrowserCollectionTest::SetUpOnMainThread();
+    // Browsers are activated in the order they are created, resulting in an
+    // activation order the reverse of creation order.
+    browsers_.push_back(browser());
+    browsers_.push_back(CreateBrowserWithNewProfile());
+    browsers_.push_back(CreateBrowserWithNewProfile());
+  }
+  void TearDownOnMainThread() override {
+    browsers_.clear();
+    GlobalBrowserCollectionTest::TearDownOnMainThread();
+  }
+
+  BrowserWindowInterface* GetBrowser(int index) { return browsers_.at(index); }
+
+  BrowserWindowInterface* GetAndClearBrowser(int index) {
+    BrowserWindowInterface* tmp = browsers_.at(index);
+    browsers_.at(index) = nullptr;
+    return tmp;
+  }
+
+ private:
+  // Browser instances in creation order.
+  std::vector<raw_ptr<BrowserWindowInterface>> browsers_;
+};
+
+IN_PROC_BROWSER_TEST_P(GlobalBrowserCollectionTestWithOrder,
+                       ForEachIteratesOverAllBrowsers) {
+  std::vector<BrowserWindowInterface*> visited;
+  GlobalBrowserCollection::GetInstance()->ForEach(
+      [&](BrowserWindowInterface* b) {
+        visited.push_back(b);
+        return true;
+      },
+      GetParam());
+
+  EXPECT_EQ(visited.size(), 3u);
+  if (GetParam() == BrowserCollection::Order::kCreation) {
+    EXPECT_EQ(visited[0], GetBrowser(0));
+    EXPECT_EQ(visited[1], GetBrowser(1));
+    EXPECT_EQ(visited[2], GetBrowser(2));
+  } else {
+    EXPECT_EQ(visited[0], GetBrowser(2));
+    EXPECT_EQ(visited[1], GetBrowser(1));
+    EXPECT_EQ(visited[2], GetBrowser(0));
+  }
+}
+
+IN_PROC_BROWSER_TEST_P(GlobalBrowserCollectionTestWithOrder,
+                       ForEachStopsWhenCallbackReturnsFalse) {
+  std::vector<BrowserWindowInterface*> visited;
+  GlobalBrowserCollection::GetInstance()->ForEach(
+      [&](BrowserWindowInterface* b) {
+        visited.push_back(b);
+        return false;
+      },
+      GetParam());
+
+  EXPECT_EQ(visited.size(), 1u);
+  if (GetParam() == BrowserCollection::Order::kCreation) {
+    EXPECT_EQ(visited[0], GetBrowser(0));
+  } else {
+    EXPECT_EQ(visited[0], GetBrowser(2));
+  }
+}
+
+IN_PROC_BROWSER_TEST_P(GlobalBrowserCollectionTestWithOrder,
+                       ForEachResilientToBrowserDestruction) {
+  std::vector<BrowserWindowInterface*> visited;
+  GlobalBrowserCollection::GetInstance()->ForEach(
+      [&](BrowserWindowInterface* b) {
+        visited.push_back(b);
+        if (visited.size() == 1) {
+          // Close the second browser mid-iteration.
+          CloseBrowserSynchronously(GetAndClearBrowser(1));
+        }
+        return true;
+      },
+      GetParam());
+
+  // Should visit browser 0, skip browser 1 (because it was closed), and visit
+  // browser 2.
+  EXPECT_EQ(visited.size(), 2u);
+  if (GetParam() == BrowserCollection::Order::kCreation) {
+    EXPECT_EQ(visited[0], GetBrowser(0));
+    EXPECT_EQ(visited[1], GetBrowser(2));
+  } else {
+    EXPECT_EQ(visited[0], GetBrowser(2));
+    EXPECT_EQ(visited[1], GetBrowser(0));
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    ,
+    GlobalBrowserCollectionTestWithOrder,
+    ::testing::Values(BrowserCollection::Order::kCreation,
+                      BrowserCollection::Order::kActivation),
+    [](const testing::TestParamInfo<BrowserCollection::Order>& param) {
+      switch (param.param) {
+        case BrowserCollection::Order::kCreation:
+          return "CreationOrder";
+        case BrowserCollection::Order::kActivation:
+          return "ActivationOrder";
+      }
+    });
diff --git a/chrome/browser/ui/browser_window/internal/profile_browser_collection_browsertest.cc b/chrome/browser/ui/browser_window/internal/profile_browser_collection_browsertest.cc
new file mode 100644
index 0000000..cfd966f3
--- /dev/null
+++ b/chrome/browser/ui/browser_window/internal/profile_browser_collection_browsertest.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 "chrome/browser/ui/browser_window/public/profile_browser_collection.h"
+
+#include "chrome/test/base/in_process_browser_test.h"
+#include "content/public/test/browser_test.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+// Fixture that sets up 3 browsers.
+class ProfileBrowserCollectionTest
+    : public InProcessBrowserTest,
+      public testing::WithParamInterface<BrowserCollection::Order> {
+ protected:
+  // InProcessBrowserTest:
+  void SetUpOnMainThread() override {
+    InProcessBrowserTest::SetUpOnMainThread();
+    // Browsers are activated in the order they are created, resulting in an
+    // activation order the reverse of creation order.
+    browsers_.push_back(browser());
+    browsers_.push_back(CreateBrowser(GetProfile()));
+    browsers_.push_back(CreateBrowser(GetProfile()));
+  }
+  void TearDownOnMainThread() override {
+    browsers_.clear();
+    InProcessBrowserTest::TearDownOnMainThread();
+  }
+
+  BrowserWindowInterface* GetBrowser(int index) { return browsers_.at(index); }
+
+  BrowserWindowInterface* GetAndClearBrowser(int index) {
+    BrowserWindowInterface* tmp = browsers_.at(index);
+    browsers_.at(index) = nullptr;
+    return tmp;
+  }
+
+ private:
+  // Browser instances in creation order.
+  std::vector<raw_ptr<BrowserWindowInterface>> browsers_;
+};
+
+IN_PROC_BROWSER_TEST_P(ProfileBrowserCollectionTest,
+                       ForEachIteratesOverAllBrowsers) {
+  std::vector<BrowserWindowInterface*> visited;
+  ProfileBrowserCollection::GetForProfile(GetProfile())
+      ->ForEach(
+          [&](BrowserWindowInterface* b) {
+            visited.push_back(b);
+            return true;
+          },
+          GetParam());
+
+  EXPECT_EQ(visited.size(), 3u);
+  if (GetParam() == BrowserCollection::Order::kCreation) {
+    EXPECT_EQ(visited[0], GetBrowser(0));
+    EXPECT_EQ(visited[1], GetBrowser(1));
+    EXPECT_EQ(visited[2], GetBrowser(2));
+  } else {
+    EXPECT_EQ(visited[0], GetBrowser(2));
+    EXPECT_EQ(visited[1], GetBrowser(1));
+    EXPECT_EQ(visited[2], GetBrowser(0));
+  }
+}
+
+IN_PROC_BROWSER_TEST_P(ProfileBrowserCollectionTest,
+                       ForEachStopsWhenCallbackReturnsFalse) {
+  std::vector<BrowserWindowInterface*> visited;
+  ProfileBrowserCollection::GetForProfile(GetProfile())
+      ->ForEach(
+          [&](BrowserWindowInterface* b) {
+            visited.push_back(b);
+            return false;
+          },
+          GetParam());
+
+  EXPECT_EQ(visited.size(), 1u);
+  if (GetParam() == BrowserCollection::Order::kCreation) {
+    EXPECT_EQ(visited[0], GetBrowser(0));
+  } else {
+    EXPECT_EQ(visited[0], GetBrowser(2));
+  }
+}
+
+IN_PROC_BROWSER_TEST_P(ProfileBrowserCollectionTest,
+                       ForEachResilientToBrowserDestruction) {
+  std::vector<BrowserWindowInterface*> visited;
+  ProfileBrowserCollection::GetForProfile(GetProfile())
+      ->ForEach(
+          [&](BrowserWindowInterface* b) {
+            visited.push_back(b);
+            if (visited.size() == 1) {
+              // Close the second browser mid-iteration.
+              CloseBrowserSynchronously(GetAndClearBrowser(1));
+            }
+            return true;
+          },
+          GetParam());
+
+  // Should visit browser 0, skip browser 1 (because it was closed), and visit
+  // browser 2.
+  EXPECT_EQ(visited.size(), 2u);
+  if (GetParam() == BrowserCollection::Order::kCreation) {
+    EXPECT_EQ(visited[0], GetBrowser(0));
+    EXPECT_EQ(visited[1], GetBrowser(2));
+  } else {
+    EXPECT_EQ(visited[0], GetBrowser(2));
+    EXPECT_EQ(visited[1], GetBrowser(0));
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    ,
+    ProfileBrowserCollectionTest,
+    ::testing::Values(BrowserCollection::Order::kCreation,
+                      BrowserCollection::Order::kActivation),
+    [](const testing::TestParamInfo<BrowserCollection::Order>& param) {
+      switch (param.param) {
+        case BrowserCollection::Order::kCreation:
+          return "CreationOrder";
+        case BrowserCollection::Order::kActivation:
+          return "ActivationOrder";
+      }
+    });
diff --git a/chrome/browser/ui/browser_window/public/browser_collection.h b/chrome/browser/ui/browser_window/public/browser_collection.h
index 658b15d..6d54247 100644
--- a/chrome/browser/ui/browser_window/public/browser_collection.h
+++ b/chrome/browser/ui/browser_window/public/browser_collection.h
@@ -21,9 +21,13 @@
   using BrowserVector = std::vector<raw_ptr<BrowserWindowInterface>>;
 
   // Defines the order in which browsers are iterated.
-  // TODO(crbug.com/431671320): Update to support activation order.
+  // `kCreation` will iterate over browsers starting from the least recently
+  // created to the most recently created.
+  // `kActivation` will iterate over browsers starting from the most recently
+  // activated to the least recently activated.
   enum class Order {
     kCreation,
+    kActivation,
   };
 
   // Iterates over all BrowserWindowInstances in this collection present at
diff --git a/chrome/browser/ui/browser_window/public/global_browser_collection.h b/chrome/browser/ui/browser_window/public/global_browser_collection.h
index 08518e694..f72be285 100644
--- a/chrome/browser/ui/browser_window/public/global_browser_collection.h
+++ b/chrome/browser/ui/browser_window/public/global_browser_collection.h
@@ -45,8 +45,13 @@
   void OnBrowserActivated(BrowserWindowInterface* browser) override;
   void OnBrowserDeactivated(BrowserWindowInterface* browser) override;
 
-  // References to the global set of browsers in creation order.
+  // References to the global set of browsers in creation order, with the
+  // least recently created browser appearing at the front of the vector.
   std::vector<raw_ptr<BrowserWindowInterface>> browsers_creation_order_;
+
+  // References to the global set of browsers in activation order, with the
+  // most recently activated browser appearing at the front of the vector.
+  std::vector<raw_ptr<BrowserWindowInterface>> browsers_activation_order_;
 };
 
 #endif  // CHROME_BROWSER_UI_BROWSER_WINDOW_PUBLIC_GLOBAL_BROWSER_COLLECTION_H_
diff --git a/chrome/browser/ui/cocoa/applescript/apple_event_util_unittest.mm b/chrome/browser/ui/cocoa/applescript/apple_event_util_unittest.mm
index 2726714..1e03798 100644
--- a/chrome/browser/ui/cocoa/applescript/apple_event_util_unittest.mm
+++ b/chrome/browser/ui/cocoa/applescript/apple_event_util_unittest.mm
@@ -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 "chrome/browser/ui/cocoa/applescript/apple_event_util.h"
 
 #include <CoreServices/CoreServices.h>
@@ -226,16 +221,18 @@
        typeAEList},
   };
 
-  for (size_t i = 0; i < std::size(cases); ++i) {
+  int i = 0;
+  for (const auto& test_case : cases) {
     std::optional<base::Value> value = base::JSONReader::Read(
-        cases[i].json_input, base::JSON_PARSE_CHROMIUM_EXTENSIONS);
+        test_case.json_input, base::JSON_PARSE_CHROMIUM_EXTENSIONS);
     NSAppleEventDescriptor* descriptor =
         chrome::mac::ValueToAppleEventDescriptor(value.value());
 
-    EXPECT_EQ(cases[i].expected_aedesc_dump, AEDescToString(descriptor.aeDesc))
+    EXPECT_EQ(test_case.expected_aedesc_dump, AEDescToString(descriptor.aeDesc))
         << "i: " << i;
-    EXPECT_EQ(cases[i].expected_aedesc_type, descriptor.descriptorType)
+    EXPECT_EQ(test_case.expected_aedesc_type, descriptor.descriptorType)
         << "i: " << i;
+    ++i;
   }
 
   // Test boolean values separately because boolean NSAppleEventDescriptors
diff --git a/chrome/browser/ui/cocoa/history_menu_bridge_unittest.mm b/chrome/browser/ui/cocoa/history_menu_bridge_unittest.mm
index 25cd14f..28e37de 100644
--- a/chrome/browser/ui/cocoa/history_menu_bridge_unittest.mm
+++ b/chrome/browser/ui/cocoa/history_menu_bridge_unittest.mm
@@ -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
-
 #include "chrome/browser/ui/cocoa/history_menu_bridge.h"
 
 #import <Cocoa/Cocoa.h>
@@ -234,10 +229,10 @@
 void CheckMenuItemVisibility(HistoryMenuBridgeTest* test, bool is_incognito) {
   // Make sure the items belong to both original and incognito mode are visible.
   NSInteger always_visible_items[] = {IDC_HOME, IDC_BACK, IDC_FORWARD};
-  for (size_t i = 0; i < std::size(always_visible_items); i++) {
+  for (NSInteger tag : always_visible_items) {
     // Create a fake item with tag.
     NSMenuItem* item = [[NSMenuItem alloc] init];
-    item.tag = always_visible_items[i];
+    item.tag = tag;
     EXPECT_TRUE(test->ShouldMenuItemBeVisible(item));
   }
 
@@ -250,10 +245,10 @@
       HistoryMenuBridge::kVisitedTitle,
       HistoryMenuBridge::kShowFullSeparator,
       IDC_SHOW_HISTORY};
-  for (size_t i = 0; i < std::size(regular_visible_items); i++) {
+  for (NSInteger tag : regular_visible_items) {
     // Create a fake item with tag.
     NSMenuItem* item = [[NSMenuItem alloc] init];
-    item.tag = regular_visible_items[i];
+    item.tag = tag;
     EXPECT_EQ(!is_incognito, test->ShouldMenuItemBeVisible(item));
   }
 }
diff --git a/chrome/browser/ui/extensions/extensions_menu_view_model.h b/chrome/browser/ui/extensions/extensions_menu_view_model.h
index 70f2fa0..5fc8411 100644
--- a/chrome/browser/ui/extensions/extensions_menu_view_model.h
+++ b/chrome/browser/ui/extensions/extensions_menu_view_model.h
@@ -24,9 +24,7 @@
 class ExtensionsMenuViewPlatformDelegate;
 class ToolbarActionViewModel;
 
-// The platform agnostic controller for the extensions menu.
-// TODO(crbug.com/449814184): Move the observers from
-// ExtensionsMenuViewController here.
+// The platform agnostic model for the extensions menu.
 class ExtensionsMenuViewModel : public extensions::PermissionsManager::Observer,
                                 public ToolbarActionsModel::Observer,
                                 public TabListInterfaceObserver,
diff --git a/chrome/browser/ui/hats/survey_config.cc b/chrome/browser/ui/hats/survey_config.cc
index 490c4d32..e8e3a7e 100644
--- a/chrome/browser/ui/hats/survey_config.cc
+++ b/chrome/browser/ui/hats/survey_config.cc
@@ -103,6 +103,7 @@
     "identity-switch-profile-profile-picker";
 constexpr char kHatsSurveyTriggerLensOverlayResults[] = "lens-overlay-results";
 constexpr char kHatsSurveyTriggerNtpModules[] = "ntp-modules";
+constexpr char kHatsSurveyTriggerNextPanel[] = "next-panel";
 constexpr char kHatsSurveyTriggerNtpPhotosModuleOptOut[] =
     "ntp-photos-module-opt-out";
 constexpr char kHatsSurveyTriggerPasswordChangeCanceled[] =
@@ -324,6 +325,15 @@
       &features::kHappinessTrackingSurveysForDesktopNtpModules,
       kHatsSurveyTriggerNtpModules);
 
+  // Next Panel survey.
+  survey_configs.emplace_back(
+      &features::kHappinessTrackingSurveysForDesktopNextPanel,
+      kHatsSurveyTriggerNextPanel,
+      /*presupplied_trigger_id=*/"XWXw3UM1k0ugnJ3q1cK0PKSCtgF3",
+      /*product_specific_bits_data_fields=*/std::vector<std::string>{},
+      /*product_specific_string_data_fields=*/
+      std::vector<std::string>{"Experiment ID"});
+
   // History embeddings survey.
   survey_configs.emplace_back(
       &features::kHappinessTrackingSurveysForHistoryEmbeddings,
diff --git a/chrome/browser/ui/hats/survey_config.h b/chrome/browser/ui/hats/survey_config.h
index c35f77c..b8d74a1 100644
--- a/chrome/browser/ui/hats/survey_config.h
+++ b/chrome/browser/ui/hats/survey_config.h
@@ -48,6 +48,7 @@
 extern const char kHatsSurveyTriggerIdentitySwitchProfileFromProfilePicker[];
 extern const char kHatsSurveyTriggerLensOverlayResults[];
 extern const char kHatsSurveyTriggerNtpModules[];
+extern const char kHatsSurveyTriggerNextPanel[];
 extern const char kHatsSurveyTriggerNtpPhotosModuleOptOut[];
 extern const char kHatsSurveyTriggerPasswordChangeCanceled[];
 extern const char kHatsSurveyTriggerPasswordChangeDelayed[];
diff --git a/chrome/browser/ui/messages/android/java/src/org/chromium/chrome/browser/ui/messages/snackbar/Snackbar.java b/chrome/browser/ui/messages/android/java/src/org/chromium/chrome/browser/ui/messages/snackbar/Snackbar.java
index ca6508d..1f137fa5 100644
--- a/chrome/browser/ui/messages/android/java/src/org/chromium/chrome/browser/ui/messages/snackbar/Snackbar.java
+++ b/chrome/browser/ui/messages/android/java/src/org/chromium/chrome/browser/ui/messages/snackbar/Snackbar.java
@@ -133,6 +133,8 @@
     public static final int UMA_SIGN_IN = 76;
     public static final int UMA_TAB_PICKER_LIMIT_REACHED = 77;
     public static final int UMA_FUSEBOX_MAX_ATTACHMENTS = 78;
+    public static final int UMA_FUSEBOX_UPLOAD_FAILED = 79;
+
     private final @Nullable SnackbarController mController;
     private final CharSequence mText;
     private @Nullable String mTemplateText;
diff --git a/chrome/browser/ui/signin/cookie_clear_on_exit_migration_notice_browsertest.cc b/chrome/browser/ui/signin/cookie_clear_on_exit_migration_notice_browsertest.cc
index a1ffcc8..5f16abc2 100644
--- a/chrome/browser/ui/signin/cookie_clear_on_exit_migration_notice_browsertest.cc
+++ b/chrome/browser/ui/signin/cookie_clear_on_exit_migration_notice_browsertest.cc
@@ -14,6 +14,7 @@
 #include "chrome/browser/signin/chrome_signin_client_factory.h"
 #include "chrome/browser/signin/identity_manager_factory.h"
 #include "chrome/browser/signin/signin_browser_test_base.h"
+#include "chrome/browser/sync/sync_service_factory.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_list.h"
 #include "chrome/browser/ui/browser_window/public/desktop_browser_window_capabilities.h"
@@ -28,6 +29,7 @@
 #include "components/signin/public/base/signin_pref_names.h"
 #include "components/signin/public/base/signin_switches.h"
 #include "components/signin/public/identity_manager/identity_test_utils.h"
+#include "components/sync/service/sync_user_settings.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/test_launcher.h"
 #include "google_apis/gaia/gaia_urls.h"
@@ -217,6 +219,13 @@
   SetGaiaCookieClearedOnExit(/*cleared=*/true);
   SetPrimaryAccount(signin::ConsentLevel::kSync,
                     /*is_explicit_signin=*/false);
+  // TODO(crbug.com/464457988): Mark sync setup as complete by default in the
+  // sign-in helper method.
+  syncer::SyncService* sync_service =
+      SyncServiceFactory::GetForProfile(GetProfile());
+  sync_service->GetUserSettings()->SetInitialSyncFeatureSetupComplete(
+      syncer::SyncFirstSetupCompleteSource::BASIC_FLOW);
+  ASSERT_TRUE(sync_service->IsSyncFeatureEnabled());
   GetProfile()->GetPrefs()->ClearPref(
       prefs::kCookieClearOnExitMigrationNoticeComplete);
 }
diff --git a/chrome/browser/ui/signin/dice_migration_service_browsertest.cc b/chrome/browser/ui/signin/dice_migration_service_browsertest.cc
index 8dbdc1e..b254226 100644
--- a/chrome/browser/ui/signin/dice_migration_service_browsertest.cc
+++ b/chrome/browser/ui/signin/dice_migration_service_browsertest.cc
@@ -179,6 +179,11 @@
 IN_PROC_BROWSER_TEST_F(DiceMigrationServiceBrowserTest, PRE_Syncing) {
   signin::MakePrimaryAccountAvailable(GetIdentityManager(), kTestEmail,
                                       signin::ConsentLevel::kSync);
+  // TODO(crbug.com/464457988): Mark sync setup as complete by default in the
+  // sign-in helper method.
+  GetSyncService()->GetUserSettings()->SetInitialSyncFeatureSetupComplete(
+      syncer::SyncFirstSetupCompleteSource::BASIC_FLOW);
+  ASSERT_TRUE(GetSyncService()->IsSyncFeatureEnabled());
 }
 
 IN_PROC_BROWSER_TEST_F(DiceMigrationServiceBrowserTest, Syncing) {
@@ -1629,6 +1634,11 @@
                        PRE_DoesNotMigrateSyncingUser) {
   signin::MakePrimaryAccountAvailable(GetIdentityManager(), kTestEmail,
                                       signin::ConsentLevel::kSync);
+  // TODO(crbug.com/464457988): Mark sync setup as complete by default in the
+  // sign-in helper method.
+  GetSyncService()->GetUserSettings()->SetInitialSyncFeatureSetupComplete(
+      syncer::SyncFirstSetupCompleteSource::BASIC_FLOW);
+  ASSERT_TRUE(GetSyncService()->IsSyncFeatureEnabled());
 }
 
 IN_PROC_BROWSER_TEST_F(DiceMigrationServiceForcedMigrationBrowserTest,
diff --git a/chrome/browser/ui/tab_helpers.cc b/chrome/browser/ui/tab_helpers.cc
index 4fa5979..35b539a 100644
--- a/chrome/browser/ui/tab_helpers.cc
+++ b/chrome/browser/ui/tab_helpers.cc
@@ -175,7 +175,6 @@
 #include "chrome/browser/ui/android/context_menu_helper.h"
 #include "chrome/browser/ui/javascript_dialogs/javascript_tab_modal_dialog_manager_delegate_android.h"
 #include "components/facilitated_payments/core/features/features.h"
-#include "components/ip_protection/common/ip_protection_status.h"
 #include "components/page_load_metrics/browser/features.h"
 #include "components/sensitive_content/android/android_sensitive_content_client.h"
 #include "components/sensitive_content/features.h"
@@ -363,10 +362,6 @@
         web_contents, "SensitiveContent.Chrome.");
   }
 
-  // Only create the IpProtectionStatus if the User Bypass feature is enabled.
-  if (net::features::kIpPrivacyEnableUserBypass.Get()) {
-    ip_protection::IpProtectionStatus::CreateForWebContents(web_contents);
-  }
   // Create the HttpAuthCacheStatus to start observing resource load
   // completions.
   HttpAuthCacheStatus::HttpAuthCacheStatus::CreateForWebContents(web_contents);
diff --git a/chrome/browser/ui/tabs/BUILD.gn b/chrome/browser/ui/tabs/BUILD.gn
index 324097a..900194e5 100644
--- a/chrome/browser/ui/tabs/BUILD.gn
+++ b/chrome/browser/ui/tabs/BUILD.gn
@@ -565,7 +565,6 @@
       "//components/favicon/content",
       "//components/favicon/core",
       "//components/image_fetcher/core",
-      "//components/ip_protection/common:ip_protection_status",
       "//components/lens",
       "//components/metrics:content",
       "//components/passage_embeddings",
diff --git a/chrome/browser/ui/tabs/glic_actor_nudge_controller.cc b/chrome/browser/ui/tabs/glic_actor_nudge_controller.cc
index f9eb8a4..0c5702b4 100644
--- a/chrome/browser/ui/tabs/glic_actor_nudge_controller.cc
+++ b/chrome/browser/ui/tabs/glic_actor_nudge_controller.cc
@@ -7,6 +7,7 @@
 #include "base/functional/bind.h"
 #include "base/task/sequenced_task_runner.h"
 #include "chrome/browser/actor/resources/grit/actor_browser_resources.h"
+#include "chrome/browser/actor/ui/actor_ui_metrics.h"
 #include "chrome/browser/actor/ui/task_list_bubble/actor_task_list_bubble_controller.h"
 #include "chrome/browser/glic/public/glic_keyed_service.h"
 #include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
@@ -84,6 +85,11 @@
       }
       break;
   }
+
+  if (base::FeatureList::IsEnabled(features::kGlicActorUiNudgeRedesign) &&
+      tab_strip_action_container_->GetIsShowingGlicActorTaskIconNudge()) {
+    actor::ui::RecordTaskNudgeShown(actor_task_nudge_state);
+  }
 }
 
 void GlicActorNudgeController::UpdateNudgeLabelOrRetrigger(
diff --git a/chrome/browser/ui/tabs/tab_features.cc b/chrome/browser/ui/tabs/tab_features.cc
index 057a175..58bb4ffd2 100644
--- a/chrome/browser/ui/tabs/tab_features.cc
+++ b/chrome/browser/ui/tabs/tab_features.cc
@@ -98,7 +98,6 @@
 #include "components/browsing_topics/browsing_topics_service.h"
 #include "components/favicon/content/content_favicon_driver.h"
 #include "components/image_fetcher/core/image_fetcher_service.h"
-#include "components/ip_protection/common/ip_protection_status.h"
 #include "components/lens/tab_contextualization_controller.h"
 #include "components/passage_embeddings/passage_embeddings_features.h"
 #include "components/permissions/permission_indicators_tab_data.h"
@@ -409,11 +408,6 @@
       std::make_unique<ReadAnythingSidePanelController>(
           &tab, side_panel_registry_.get());
 
-  // Only create the IpProtectionStatus if the User Bypass feature is enabled.
-  if (net::features::kIpPrivacyEnableUserBypass.Get()) {
-    ip_protection::IpProtectionStatus::CreateForWebContents(tab.GetContents());
-  }
-
   // Create the HttpAuthCacheStatus to start observing resource load
   // completions.
   HttpAuthCacheStatus::HttpAuthCacheStatus::CreateForWebContents(
diff --git a/chrome/browser/ui/toolbar/app_menu_model.cc b/chrome/browser/ui/toolbar/app_menu_model.cc
index 2934bdc..a3fe5c3 100644
--- a/chrome/browser/ui/toolbar/app_menu_model.cc
+++ b/chrome/browser/ui/toolbar/app_menu_model.cc
@@ -1024,7 +1024,7 @@
   if (base::FeatureList::IsEnabled(contextual_tasks::kContextualTasks)) {
     AddItemWithStringIdAndVectorIcon(
         this, IDC_SHOW_CONTEXTUAL_TASKS_SIDE_PANEL,
-        IDS_CONTEXTUAL_TASKS_CONTEXTUAL_TASKS_TITLE, vector_icons::kChatIcon);
+        IDS_CONTEXTUAL_TASKS_CONTEXTUAL_TASKS_TITLE, kDockToRightSparkIcon);
   }
 
   AddSeparator(ui::NORMAL_SEPARATOR);
diff --git a/chrome/browser/ui/views/chrome_layout_provider.cc b/chrome/browser/ui/views/chrome_layout_provider.cc
index e4de50e..16fbc760 100644
--- a/chrome/browser/ui/views/chrome_layout_provider.cc
+++ b/chrome/browser/ui/views/chrome_layout_provider.cc
@@ -161,8 +161,10 @@
     case DISTANCE_INFOBAR_HEIGHT:
       // Spec says height of button should be 36dp, vertical padding on both
       // top and bottom should be 8dp.
+      // The new refreshed button height is 20 + (2 * 6) = 32dp.
+      // Therefore, the total infobar height is 32dp + 2 * 12.
       return base::FeatureList::IsEnabled(features::kInfobarRefresh)
-                 ? 36 + 2 * 12
+                 ? 32 + 2 * 12
                  : 36 + 2 * 8;
     case DISTANCE_PERMISSION_PROMPT_HORIZONTAL_ICON_LABEL_PADDING:
       return 8;
diff --git a/chrome/browser/ui/views/contextual_tasks/contextual_tasks_button.cc b/chrome/browser/ui/views/contextual_tasks/contextual_tasks_button.cc
index dc726be..5dccb1f 100644
--- a/chrome/browser/ui/views/contextual_tasks/contextual_tasks_button.cc
+++ b/chrome/browser/ui/views/contextual_tasks/contextual_tasks_button.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/ui/views/contextual_tasks/contextual_tasks_button.h"
 
 #include "base/functional/bind.h"
+#include "chrome/app/vector_icons/vector_icons.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/actions/chrome_action_id.h"
 #include "chrome/browser/ui/browser.h"
@@ -18,7 +19,6 @@
 #include "components/prefs/pref_member.h"
 #include "components/prefs/pref_service.h"
 #include "components/strings/grit/components_strings.h"
-#include "components/vector_icons/vector_icons.h"
 #include "ui/actions/actions.h"
 #include "ui/base/interaction/element_identifier.h"
 #include "ui/base/l10n/l10n_util.h"
@@ -37,7 +37,7 @@
                     nullptr,
                     nullptr),
       browser_window_interface_(browser_window_interface) {
-  SetVectorIcon(vector_icons::kChatIcon);
+  SetVectorIcon(kDockToRightSparkIcon);
   SetProperty(views::kElementIdentifierKey, kContextualTasksToolbarButton);
   GetViewAccessibility().SetName(
       l10n_util::GetStringUTF16(IDS_CONTEXTUAL_TASKS_CONTEXTUAL_TASKS_TITLE));
diff --git a/chrome/browser/ui/views/frame/top_controls_slide_controller_chromeos.cc b/chrome/browser/ui/views/frame/top_controls_slide_controller_chromeos.cc
index 3898dafa..53baaf5 100644
--- a/chrome/browser/ui/views/frame/top_controls_slide_controller_chromeos.cc
+++ b/chrome/browser/ui/views/frame/top_controls_slide_controller_chromeos.cc
@@ -294,8 +294,6 @@
 
   browser_view_->browser()->tab_strip_model()->AddObserver(this);
 
-  browser_view->browser_widget()->GetLayer()->EnableLayerDestructionCheck();
-
   auto* accessibility_manager = ash::AccessibilityManager::Get();
   if (accessibility_manager) {
     accessibility_status_subscription_ =
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 f949ab4..f2cdc3c 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
@@ -38,6 +38,8 @@
 OmniboxAimPopupWebUIContent::~OmniboxAimPopupWebUIContent() = default;
 
 void OmniboxAimPopupWebUIContent::ShowUI() {
+  OmniboxPopupWebUIBaseContent::ShowUI();
+
   auto* web_contents = contents_wrapper()->web_contents();
   auto* browser_window = webui::GetBrowserWindowInterface(web_contents);
   auto* context_data = browser_window->GetFeatures().searchbox_context_data();
@@ -57,22 +59,10 @@
       omnibox_popup_ui->popup_aim_handler()->OnShow(std::move(context));
     }
   }
-
-  is_shown_ = true;
 }
 
 void OmniboxAimPopupWebUIContent::CloseUI() {
-  // If the popup state is not shown, don't take any action. Closing the UI
-  // multiple times can result in incorrect state transitions from OnClose.
-  if (!is_shown_) {
-    return;
-  }
-
-  is_shown_ = false;
-
-  // Update the popup state manager that the aim popup is closing.
-  // LocationBarView is subscribed to state changes and will close the widget.
-  controller()->popup_state_manager()->SetPopupState(OmniboxPopupState::kNone);
+  OmniboxPopupWebUIBaseContent::CloseUI();
 
   auto* webui_controller = contents_wrapper()->GetWebUIController();
   if (webui_controller) {
diff --git a/chrome/browser/ui/views/omnibox/omnibox_aim_popup_webui_content.h b/chrome/browser/ui/views/omnibox/omnibox_aim_popup_webui_content.h
index 0d0ba39b..2223b88f 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_aim_popup_webui_content.h
+++ b/chrome/browser/ui/views/omnibox/omnibox_aim_popup_webui_content.h
@@ -36,7 +36,6 @@
   void OnClosedWithInput(const std::string& input);
 
   private:
-  bool is_shown_ = false;
 };
 
 BEGIN_VIEW_BUILDER(/* no export */,
diff --git a/chrome/browser/ui/views/omnibox/omnibox_popup_presenter_base.cc b/chrome/browser/ui/views/omnibox/omnibox_popup_presenter_base.cc
index e2d4f75a3..6c9477d6 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_popup_presenter_base.cc
+++ b/chrome/browser/ui/views/omnibox/omnibox_popup_presenter_base.cc
@@ -49,8 +49,8 @@
       widget_->Activate();
       content->RequestFocus();
       content->GetWebContents()->Focus();
-      content->ShowUI();
     }
+    content->ShowUI();
   }
 }
 
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 f8a9294..612b43056 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
@@ -59,20 +59,6 @@
       top_rounded_corners_ ? corner_radius : 0,
       top_rounded_corners_ ? corner_radius : 0, corner_radius, corner_radius);
   holder()->SetCornerRadii(rounded_corner_radii);
-
-  // Manually set zoom level, since any zooming is undesirable in the omnibox.
-  auto* zoom_controller =
-      zoom::ZoomController::FromWebContents(GetWebContents());
-  if (!zoom_controller) {
-    // Create ZoomController manually, if not already exists, because it is
-    // not automatically created when the WebUI has not been opened in a tab.
-    zoom_controller =
-        zoom::ZoomController::CreateForWebContents(GetWebContents());
-  }
-  zoom_controller->SetZoomMode(zoom::ZoomController::ZOOM_MODE_ISOLATED);
-  zoom_controller->SetZoomLevel(0);
-
-  OnViewBoundsChanged(location_bar_view_);
 }
 
 void OmniboxPopupWebUIBaseContent::OnViewBoundsChanged(
@@ -90,11 +76,28 @@
 }
 
 void OmniboxPopupWebUIBaseContent::CloseUI() {
-  // Must implement this pure-virtual abstract function.
+  // If the popup state is not shown, don't take any action. Closing the UI
+  // multiple times can result in incorrect state transitions from OnClose.
+  if (!is_shown_) {
+    return;
+  }
+
+  is_shown_ = false;
+
+  // Update the popup state manager that the popup is closing.
+  // LocationBarView is subscribed to state changes and will close the widget.
+  controller()->popup_state_manager()->SetPopupState(OmniboxPopupState::kNone);
 }
 
 void OmniboxPopupWebUIBaseContent::ShowUI() {
-  // Must implement this pure-virtual abstract function.
+  // This is a signal from the WebUIContentsWrapper::Host. We use this signal to
+  // check if the renderer crashes. If the renderer process has crashed, reset
+  // the content URL and create a new renderer.
+  if (GetWebContents() && GetWebContents()->IsCrashed()) {
+    LoadContent();
+  }
+
+  is_shown_ = true;
 }
 
 void OmniboxPopupWebUIBaseContent::ShowCustomContextMenu(
@@ -132,8 +135,14 @@
 }
 
 void OmniboxPopupWebUIBaseContent::SetContentURL(std::string_view url) {
+  content_url_ = GURL(url);
+  LoadContent();
+}
+
+void OmniboxPopupWebUIBaseContent::LoadContent() {
+  DCHECK(!content_url_.is_empty());
   contents_wrapper_ = std::make_unique<WebUIContentsWrapperT<OmniboxPopupUI>>(
-      GURL(url), location_bar_view_->profile(), IDS_TASK_MANAGER_OMNIBOX);
+      content_url_, location_bar_view_->profile(), IDS_TASK_MANAGER_OMNIBOX);
   contents_wrapper_->SetHost(weak_factory_.GetWeakPtr());
   SetWebContents(contents_wrapper_->web_contents());
   webui::SetBrowserWindowInterface(contents_wrapper_->web_contents(),
@@ -142,6 +151,20 @@
   OmniboxPopupWebContentsHelper::CreateForWebContents(GetWebContents());
   OmniboxPopupWebContentsHelper::FromWebContents(GetWebContents())
       ->set_omnibox_controller(controller_);
+
+  // Manually set zoom level, since any zooming is undesirable in the omnibox.
+  auto* zoom_controller =
+      zoom::ZoomController::FromWebContents(GetWebContents());
+  if (!zoom_controller) {
+    // Create ZoomController manually, if not already exists, because it is
+    // not automatically created when the WebUI has not been opened in a tab.
+    zoom_controller =
+        zoom::ZoomController::CreateForWebContents(GetWebContents());
+  }
+  zoom_controller->SetZoomMode(zoom::ZoomController::ZOOM_MODE_ISOLATED);
+  zoom_controller->SetZoomLevel(0);
+
+  OnViewBoundsChanged(location_bar_view_);
 }
 
 bool OmniboxPopupWebUIBaseContent::PreHandleGestureEvent(
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 5b540c1..addccfd 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
@@ -79,6 +79,11 @@
   bool top_rounded_corners() const { return top_rounded_corners_; }
 
  private:
+  // Loads the WebUI content using the cached `content_url`. Creates a new
+  // content wrapper (also destroying previous one if it exists) and initializes
+  // the renderer.
+  void LoadContent();
+
   raw_ptr<OmniboxPopupPresenterBase> popup_presenter_ = nullptr;
   raw_ptr<LocationBarView> location_bar_view_ = nullptr;
   raw_ptr<OmniboxPopupPresenterBase> omnibox_popup_presenter_ = nullptr;
@@ -91,6 +96,14 @@
   std::unique_ptr<WebUIContentsWrapperT<OmniboxPopupUI>> contents_wrapper_;
   std::unique_ptr<OmniboxContextMenu> context_menu_;
 
+  // The URL used to load the WebUI. Cached here so the content can be reloaded
+  // if the renderer crashes.
+  GURL content_url_;
+
+  // Tracks the visible state of the WebUI. This is distinct from the
+  // View's visibility (GetVisible()) to handle lifecycle timing differences.
+  bool is_shown_ = false;
+
   // A handler to handle unhandled keyboard messages coming back from the
   // renderer process.
   views::UnhandledKeyboardEventHandler unhandled_keyboard_event_handler_;
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 808b268..b32493a 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
@@ -269,7 +269,8 @@
   std::u16string GetText() const override {
     return l10n_util::GetPluralStringFUTF16(
         IDS_AVATAR_BUTTON_INCOGNITO,
-        BrowserList::GetOffTheRecordBrowsersActiveForProfile(&profile()));
+        static_cast<int>(
+            chrome::GetOffTheRecordBrowsersActiveForProfile(&profile())));
   }
 
   std::optional<SkColor> GetHighlightColor(
diff --git a/chrome/browser/ui/views/profiles/incognito_menu_view.cc b/chrome/browser/ui/views/profiles/incognito_menu_view.cc
index 1e970368..1a3d8d1 100644
--- a/chrome/browser/ui/views/profiles/incognito_menu_view.cc
+++ b/chrome/browser/ui/views/profiles/incognito_menu_view.cc
@@ -15,6 +15,7 @@
 #include "build/build_config.h"
 #include "chrome/app/vector_icons/vector_icons.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser_finder.h"
 #include "chrome/browser/ui/browser_list.h"
 #include "chrome/browser/ui/color/chrome_color_id.h"
 #include "chrome/browser/ui/ui_features.h"
@@ -40,8 +41,8 @@
 IncognitoMenuView::~IncognitoMenuView() = default;
 
 void IncognitoMenuView::BuildMenu() {
-  int incognito_window_count =
-      BrowserList::GetOffTheRecordBrowsersActiveForProfile(&profile());
+  int incognito_window_count = static_cast<int>(
+      chrome::GetOffTheRecordBrowsersActiveForProfile(&profile()));
   std::u16string close_button_title = l10n_util::GetPluralStringFUTF16(
       IDS_INCOGNITO_PROFILE_MENU_CLOSE_X_WINDOWS_BUTTON,
       incognito_window_count);
@@ -65,7 +66,8 @@
 std::u16string IncognitoMenuView::GetAccessibleWindowTitle() const {
   return l10n_util::GetPluralStringFUTF16(
       IDS_INCOGNITO_BUBBLE_ACCESSIBLE_TITLE,
-      BrowserList::GetOffTheRecordBrowsersActiveForProfile(&profile()));
+      static_cast<int>(
+          chrome::GetOffTheRecordBrowsersActiveForProfile(&profile())));
 }
 
 void IncognitoMenuView::OnExitButtonClicked() {
diff --git a/chrome/browser/ui/views/side_panel/side_panel.cc b/chrome/browser/ui/views/side_panel/side_panel.cc
index 184f533..4560769 100644
--- a/chrome/browser/ui/views/side_panel/side_panel.cc
+++ b/chrome/browser/ui/views/side_panel/side_panel.cc
@@ -398,18 +398,20 @@
 
   animation_coordinator_ =
       std::make_unique<SidePanelAnimationCoordinator>(this);
-  animation_coordinator_->AddObserver(kSidePanelBoundsAnimation, this);
   animation_coordinator_->AddObserver(
       SidePanelAnimationCoordinator::AnimationType::kOpen, this);
   animation_coordinator_->AddObserver(
       SidePanelAnimationCoordinator::AnimationType::kClose, this);
-  animation_coordinator_->AddObserver(kSidePanelContentOpacityAnimation, this);
-  animation_coordinator_->AddObserver(kSidePanelContentCornerRadiusAnimation,
-                                      this);
   animation_coordinator_->AddObserver(
       SidePanelAnimationCoordinator::AnimationType::kOpenWithContentTransition,
       this);
 
+  animation_coordinator_->AddObserver(kSidePanelBoundsAnimation, this);
+
+  animation_coordinator_->AddObserver(kSidePanelContentOpacityAnimation, this);
+  animation_coordinator_->AddObserver(kSidePanelContentCornerRadiusAnimation,
+                                      this);
+
   SetVisible(false);
   SetLayoutManager(std::make_unique<views::FillLayout>());
 
@@ -425,22 +427,20 @@
 }
 
 SidePanel::~SidePanel() {
-  animation_coordinator_->RemoveObserver(kSidePanelBoundsAnimation, this);
   animation_coordinator_->RemoveObserver(
       SidePanelAnimationCoordinator::AnimationType::kOpen, this);
   animation_coordinator_->RemoveObserver(
       SidePanelAnimationCoordinator::AnimationType::kClose, this);
+  animation_coordinator_->RemoveObserver(
+      SidePanelAnimationCoordinator::AnimationType::kOpenWithContentTransition,
+      this);
 
-  if (type_ == SidePanelEntry::PanelType::kToolbar) {
-    animation_coordinator_->RemoveObserver(kSidePanelContentOpacityAnimation,
-                                           this);
-    animation_coordinator_->RemoveObserver(
-        kSidePanelContentCornerRadiusAnimation, this);
-    animation_coordinator_->RemoveObserver(
-        SidePanelAnimationCoordinator::AnimationType::
-            kOpenWithContentTransition,
-        this);
-  }
+  animation_coordinator_->RemoveObserver(kSidePanelBoundsAnimation, this);
+
+  animation_coordinator_->RemoveObserver(kSidePanelContentOpacityAnimation,
+                                         this);
+  animation_coordinator_->RemoveObserver(kSidePanelContentCornerRadiusAnimation,
+                                         this);
 }
 
 void SidePanel::SetPanelWidth(int width) {
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 cb8705fd..f3de6bde 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
@@ -46,7 +46,7 @@
 //    properties (e.g., position, opacity).
 // 5. The SidePanel automatically calls `Start()` or `Reset()` on the
 //    coordinator when its visibility changes, driving the animation.
-class SidePanelAnimationCoordinator : views::AnimationDelegateViews {
+class SidePanelAnimationCoordinator : public views::AnimationDelegateViews {
  public:
   using SidePanelAnimationId = ui::ElementIdentifier;
   enum class AnimationType { kOpen, kOpenWithContentTransition, kClose };
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 324bf8fd..178791b 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
@@ -128,15 +128,8 @@
   EXPECT_TRUE(control_animation_ended.Wait());
 }
 
-#if BUILDFLAG(IS_WIN) && defined(ADDRESS_SANITIZER)
-#define MAYBE_AnimationIdObserversDoNotFireTypeCallbacks \
-  DISABLED_AnimationIdObserversDoNotFireTypeCallbacks
-#else
-#define MAYBE_AnimationIdObserversDoNotFireTypeCallbacks \
-  AnimationIdObserversDoNotFireTypeCallbacks
-#endif
 IN_PROC_BROWSER_TEST_F(SidePanelAnimationCoordinatorBrowserTest,
-                       MAYBE_AnimationIdObserversDoNotFireTypeCallbacks) {
+                       AnimationIdObserversDoNotFireTypeCallbacks) {
   SidePanelAnimationCoordinator* animation_coordinator =
       GetAnimationCoordinator();
 
@@ -144,28 +137,15 @@
   AddAnimationSequence(SidePanelAnimationCoordinator::AnimationType::kOpen,
                        {.animation_id = kTestAnimationId,
                         .start = base::Milliseconds(0),
-                        .duration = base::Milliseconds(200)});
+                        .duration = base::Milliseconds(100)});
   AddAnimationSequence(SidePanelAnimationCoordinator::AnimationType::kClose,
                        {.animation_id = kTestAnimationId,
                         .start = base::Milliseconds(0),
-                        .duration = base::Milliseconds(200)});
+                        .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);
@@ -175,14 +155,6 @@
   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);
 
@@ -193,9 +165,31 @@
         control_animation_finished.SetValue();
       });
 
+  // Manually drive the animation to avoid flakiness.
+  auto* animation = static_cast<gfx::SlideAnimation*>(
+      animation_coordinator->animation_for_testing());
+  auto* delegate = static_cast<gfx::AnimationDelegate*>(animation_coordinator);
+
   animation_coordinator->Start(
       SidePanelAnimationCoordinator::AnimationType::kOpen);
 
+  // Progress the animation and verify the observer is fired at least once.
+  EXPECT_CALL(target_observer,
+              OnAnimationSequenceProgressed(kTestAnimationId, _))
+      .Times(testing::AtLeast(1));
+
+  // Manually drive the animation to 50% progress.
+  animation->SetCurrentValue(0.5);
+  delegate->AnimationProgressed(animation);
+
+  // Go to the end of the test animation and verify it is observed properly.
+  EXPECT_CALL(target_observer, OnAnimationSequenceEnded(kTestAnimationId))
+      .WillOnce([&target_animation_finished]() {
+        target_animation_finished.SetValue();
+      });
+
+  animation->End();
+
   EXPECT_TRUE(target_animation_finished.Wait());
   EXPECT_TRUE(control_animation_finished.Wait());
 
@@ -210,8 +204,6 @@
         target_animation_finished.SetValue();
       });
 
-  EXPECT_CALL(target_observer, OnAnimationTypeStarted(_)).Times(0);
-
   EXPECT_CALL(control_observer,
               OnAnimationTypeEnded(
                   SidePanelAnimationCoordinator::AnimationType::kClose))
@@ -222,6 +214,12 @@
   animation_coordinator->Start(
       SidePanelAnimationCoordinator::AnimationType::kClose);
 
+  // Manually drive the animation to 50% progress.
+  animation->SetCurrentValue(0.5);
+  delegate->AnimationProgressed(animation);
+
+  animation->End();
+
   EXPECT_TRUE(target_animation_finished.Wait());
   EXPECT_TRUE(control_animation_finished.Wait());
 }
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 98ee380..4c471a2 100644
--- a/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc
+++ b/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc
@@ -589,11 +589,6 @@
   BrowserView* source_browser_view = GetSourceBrowserViewInTabDragging();
   if (source_browser_view && source_browser_view != browser_view_) {
     source_browser_view->browser_widget()->SetTabDragKind(TabDragKind::kTab);
-#if BUILDFLAG(IS_CHROMEOS)
-    browser_view_->GetWidget()->GetNativeWindow()->SetProperty(
-        ash::kTabDraggingSourceWindowKey,
-        source_browser_view->GetNativeWindow());
-#endif  // BUILDFLAG(IS_CHROMEOS)
   }
 
 #if BUILDFLAG(IS_CHROMEOS)
diff --git a/chrome/browser/ui/views/tabs/dragging/tab_drag_controller.cc b/chrome/browser/ui/views/tabs/dragging/tab_drag_controller.cc
index b3baf3b8..a1b0477 100644
--- a/chrome/browser/ui/views/tabs/dragging/tab_drag_controller.cc
+++ b/chrome/browser/ui/views/tabs/dragging/tab_drag_controller.cc
@@ -1534,6 +1534,10 @@
 #endif
 
 #if BUILDFLAG(IS_CHROMEOS)
+  dragged_widget->GetNativeWindow()->SetProperty(
+      ash::kTabDraggingSourceWindowKey,
+      attached_context_->GetWidget()->GetNativeWindow());
+
   // On ChromeOS, Detach should release capture; `can_release_capture_` is
   // false on ChromeOS because it can cancel touches, but for this cases
   // the touches are already transferred, so releasing is fine. Without
diff --git a/chrome/browser/ui/views/tabs/dragging/tab_drag_controller_interactive_uitest.cc b/chrome/browser/ui/views/tabs/dragging/tab_drag_controller_interactive_uitest.cc
index f8b8a78..aba787c 100644
--- a/chrome/browser/ui/views/tabs/dragging/tab_drag_controller_interactive_uitest.cc
+++ b/chrome/browser/ui/views/tabs/dragging/tab_drag_controller_interactive_uitest.cc
@@ -122,6 +122,7 @@
 #include "ash/wm/splitview/split_view_controller.h"
 #include "ash/wm/splitview/split_view_types.h"
 #include "ash/wm/window_state.h"
+#include "ash/wm/window_util.h"
 #include "base/test/simple_test_tick_clock.h"
 #include "chrome/browser/ash/system_web_apps/test_support/test_system_web_app_installation.h"
 #include "chrome/browser/ui/views/frame/immersive_mode_controller.h"
@@ -6171,30 +6172,51 @@
   // Invoked from TabDragController's nested run loop.
   void MaybeDragThenRelease(TabStrip* source_tab_strip,
                             std::optional<gfx::Point> destination_in_screen) {
-    ASSERT_TRUE(TabDragController::IsActive());
+    MaybeDragThenMaybeRelease(source_tab_strip, destination_in_screen, true);
+  }
+  void MaybeDragThenHold(TabStrip* source_tab_strip,
+                         std::optional<gfx::Point> destination_in_screen) {
+    MaybeDragThenMaybeRelease(source_tab_strip, destination_in_screen, false);
+  }
+
+  void MaybeDragThenMaybeRelease(
+      TabStrip* source_tab_strip,
+      std::optional<gfx::Point> destination_in_screen,
+      bool release = true) {
+    EXPECT_TRUE(TabDragController::IsActive());
 
     // Source tab strip drag session is already finished.
-    ASSERT_FALSE(source_tab_strip->GetDragContext()->IsDragSessionActive());
-    ASSERT_TRUE(IsTabDraggingInfoCleared(source_tab_strip));
+    EXPECT_FALSE(source_tab_strip->GetDragContext()->IsDragSessionActive());
+    EXPECT_TRUE(IsTabDraggingInfoCleared(source_tab_strip));
 
     BrowserWindowInterface* source_browser =
         source_tab_strip->GetBrowserWindowInterface();
     BrowserWindowInterface* drag_browser =
         GetLastActiveBrowserWindowInterfaceWithAnyProfile();
-    ASSERT_NE(source_browser, drag_browser);
+    EXPECT_NE(source_browser, drag_browser);
+
+    aura::Window* drag_window = drag_browser->GetWindow()->GetNativeWindow();
+    aura::Window* source_window =
+        source_browser->GetWindow()->GetNativeWindow();
+    EXPECT_EQ(source_window,
+              ash::window_util::GetTabDraggingSourceWindowState(drag_window)
+                  ->window());
 
     TabStrip* drag_tab_strip =
         GetTabStripForBrowser(drag_browser->GetBrowserForMigrationOnly());
-    ASSERT_TRUE(drag_tab_strip->GetDragContext()->IsDragSessionActive());
-    ASSERT_TRUE(IsTabDraggingInfoSet(drag_tab_strip));
+    EXPECT_TRUE(drag_tab_strip->GetDragContext()->IsDragSessionActive());
+    EXPECT_TRUE(IsTabDraggingInfoSet(drag_tab_strip));
 
-    ASSERT_EQ(source_browser->GetTabStripModel()->count(), 1);
-    ASSERT_EQ(drag_browser->GetTabStripModel()->count(), 1);
+    EXPECT_EQ(source_browser->GetTabStripModel()->count(), 1);
+    EXPECT_EQ(drag_browser->GetTabStripModel()->count(), 1);
 
     if (destination_in_screen.has_value()) {
       ASSERT_TRUE(DragInputTo(*destination_in_screen, gfx::NativeWindow()));
     }
-    ASSERT_TRUE(ReleaseInput());
+
+    if (release) {
+      ASSERT_TRUE(ReleaseInput());
+    }
   }
 };
 }  // namespace
@@ -6203,11 +6225,11 @@
 // tab strip. The drag browser persists and becomes maximized.
 IN_PROC_BROWSER_TEST_P(TabDragControllerTabletModeTest, DragFromMaximized) {
   AddTabsAndResetBrowser(browser(), 1);
-  ASSERT_EQ(IDString(browser()->GetTabStripModel()), "0 1");
+  EXPECT_EQ(IDString(browser()->GetTabStripModel()), "0 1");
 
   TabStrip* tab_strip = GetTabStripForBrowser(browser());
   Tab* first_tab = tab_strip->tab_at(0);
-  ASSERT_TRUE(browser()->IsActive());
+  EXPECT_TRUE(browser()->IsActive());
   ASSERT_TRUE(PressInputAtCenter(first_tab));
 
   ASSERT_TRUE(DragInputToCenterNotifyWhenDone(
@@ -6217,26 +6239,25 @@
       gfx::Vector2d(0, GetDetachY(tab_strip))));
   ASSERT_TRUE(base::test::RunUntil([&] { return DragInactive(2); }));
 
-  ASSERT_EQ(GetAllBrowserWindowInterfaces().size(), 2u);
+  EXPECT_EQ(GetAllBrowserWindowInterfaces().size(), 2u);
   BrowserWindowInterface* drag_browser =
-      GetLastActiveBrowserWindowInterfaceWithAnyProfile();
-  ASSERT_NE(browser(), drag_browser);
-  ASSERT_TRUE(IsDraggingInfoCleared(drag_browser));
-  ASSERT_TRUE(browser()->GetWindow()->IsMaximized());
-  ASSERT_TRUE(drag_browser->GetWindow()->IsMaximized());
-  ASSERT_TRUE(drag_browser->IsActive());
-  ASSERT_EQ(IDString(browser()->GetTabStripModel()), "1");
-  ASSERT_EQ(IDString(drag_browser->GetTabStripModel()), "0");
+      ui_test_utils::GetBrowserNotInSet({browser()});
+  EXPECT_TRUE(IsDraggingInfoCleared(drag_browser));
+  EXPECT_TRUE(browser()->GetWindow()->IsMaximized());
+  EXPECT_TRUE(drag_browser->GetWindow()->IsMaximized());
+  EXPECT_TRUE(drag_browser->IsActive());
+  EXPECT_EQ(IDString(browser()->GetTabStripModel()), "1");
+  EXPECT_EQ(IDString(drag_browser->GetTabStripModel()), "0");
 }
 
 // In tablet mode, drag a tab out of a fullscreen browser and drop it below the
 // tab strip. The drag browser persists and becomes fullscreen.
 IN_PROC_BROWSER_TEST_P(TabDragControllerTabletModeTest, DragFromFullscreen) {
   AddTabsAndResetBrowser(browser(), 1);
-  ASSERT_EQ(IDString(browser()->GetTabStripModel()), "0 1");
+  EXPECT_EQ(IDString(browser()->GetTabStripModel()), "0 1");
 
   chrome::ToggleFullscreenMode(browser());
-  ASSERT_TRUE(browser()->GetWindow()->IsFullscreen());
+  EXPECT_TRUE(browser()->GetWindow()->IsFullscreen());
 
   // Forcibly reveal the tabstrip immediately.
   // TODO(crbug.com/464354169): Use focus to reveal the immersive frame.
@@ -6250,7 +6271,7 @@
   TabStrip* tab_strip = GetTabStripForBrowser(browser());
   EXPECT_TRUE(tab_strip->GetVisible());
   Tab* first_tab = tab_strip->tab_at(0);
-  ASSERT_TRUE(browser()->IsActive());
+  EXPECT_TRUE(browser()->IsActive());
   ASSERT_TRUE(PressInputAtCenter(first_tab));
 
   ASSERT_TRUE(DragInputToCenterNotifyWhenDone(
@@ -6258,33 +6279,32 @@
       base::BindOnce(&TabDragControllerTabletModeTest::MaybeDragThenRelease,
                      base::Unretained(this), tab_strip, std::nullopt),
       gfx::Vector2d(0, GetDetachY(tab_strip))));
-  ASSERT_TRUE(base::test::RunUntil([&] { return DragInactive(2); }));
+  EXPECT_TRUE(base::test::RunUntil([&] { return DragInactive(2); }));
 
-  ASSERT_EQ(GetAllBrowserWindowInterfaces().size(), 2u);
+  EXPECT_EQ(GetAllBrowserWindowInterfaces().size(), 2u);
   BrowserWindowInterface* drag_browser =
-      GetLastActiveBrowserWindowInterfaceWithAnyProfile();
-  ASSERT_NE(browser(), drag_browser);
-  ASSERT_TRUE(IsDraggingInfoCleared(drag_browser));
-  ASSERT_TRUE(browser()->GetWindow()->IsFullscreen());
-  ASSERT_TRUE(drag_browser->GetWindow()->IsFullscreen());
-  ASSERT_TRUE(drag_browser->IsActive());
-  ASSERT_EQ(IDString(browser()->GetTabStripModel()), "1");
-  ASSERT_EQ(IDString(drag_browser->GetTabStripModel()), "0");
+      ui_test_utils::GetBrowserNotInSet({browser()});
+  EXPECT_TRUE(IsDraggingInfoCleared(drag_browser));
+  EXPECT_TRUE(browser()->GetWindow()->IsFullscreen());
+  EXPECT_TRUE(drag_browser->GetWindow()->IsFullscreen());
+  EXPECT_TRUE(drag_browser->IsActive());
+  EXPECT_EQ(IDString(browser()->GetTabStripModel()), "1");
+  EXPECT_EQ(IDString(drag_browser->GetTabStripModel()), "0");
 }
 
 // In tablet mode, drag a tab out of a floated browser and drop it below the tab
 // strip. The drag browser persists and becomes maximized.
 IN_PROC_BROWSER_TEST_P(TabDragControllerTabletModeTest, DragFromFloated) {
   AddTabsAndResetBrowser(browser(), 1);
-  ASSERT_EQ(IDString(browser()->GetTabStripModel()), "0 1");
+  EXPECT_EQ(IDString(browser()->GetTabStripModel()), "0 1");
 
   ash::Shell::Get()->float_controller()->ToggleFloat(
       browser()->GetWindow()->GetNativeWindow());
-  ASSERT_TRUE(IsFloated(browser()));
+  EXPECT_TRUE(IsFloated(browser()));
 
   TabStrip* tab_strip = GetTabStripForBrowser(browser());
   Tab* first_tab = tab_strip->tab_at(0);
-  ASSERT_TRUE(browser()->IsActive());
+  EXPECT_TRUE(browser()->IsActive());
   ASSERT_TRUE(PressInputAtCenter(first_tab));
 
   ASSERT_TRUE(DragInputToCenterNotifyWhenDone(
@@ -6294,16 +6314,15 @@
       gfx::Vector2d(0, GetDetachY(tab_strip))));
   ASSERT_TRUE(base::test::RunUntil([&] { return DragInactive(2); }));
 
-  ASSERT_EQ(GetAllBrowserWindowInterfaces().size(), 2u);
+  EXPECT_EQ(GetAllBrowserWindowInterfaces().size(), 2u);
   BrowserWindowInterface* drag_browser =
-      GetLastActiveBrowserWindowInterfaceWithAnyProfile();
-  ASSERT_NE(browser(), drag_browser);
-  ASSERT_TRUE(IsDraggingInfoCleared(drag_browser));
-  ASSERT_TRUE(IsFloated(browser()));
-  ASSERT_TRUE(drag_browser->GetWindow()->IsMaximized());
-  ASSERT_TRUE(drag_browser->IsActive());
-  ASSERT_EQ(IDString(browser()->GetTabStripModel()), "1");
-  ASSERT_EQ(IDString(drag_browser->GetTabStripModel()), "0");
+      ui_test_utils::GetBrowserNotInSet({browser()});
+  EXPECT_TRUE(IsDraggingInfoCleared(drag_browser));
+  EXPECT_TRUE(IsFloated(browser()));
+  EXPECT_TRUE(drag_browser->GetWindow()->IsMaximized());
+  EXPECT_TRUE(drag_browser->IsActive());
+  EXPECT_EQ(IDString(browser()->GetTabStripModel()), "1");
+  EXPECT_EQ(IDString(drag_browser->GetTabStripModel()), "0");
 }
 
 // In tablet mode, drag a tab out of a maximized browser and drop it on the edge
@@ -6312,11 +6331,11 @@
 IN_PROC_BROWSER_TEST_P(TabDragControllerTabletModeTest,
                        DragFromMaximizedToSnapTriggerArea) {
   AddTabsAndResetBrowser(browser(), 1);
-  ASSERT_EQ(IDString(browser()->GetTabStripModel()), "0 1");
+  EXPECT_EQ(IDString(browser()->GetTabStripModel()), "0 1");
 
   TabStrip* tab_strip = GetTabStripForBrowser(browser());
   Tab* first_tab = tab_strip->tab_at(0);
-  ASSERT_TRUE(browser()->IsActive());
+  EXPECT_TRUE(browser()->IsActive());
   ASSERT_TRUE(PressInputAtCenter(first_tab));
 
   gfx::Rect work_area =
@@ -6331,20 +6350,19 @@
       gfx::Vector2d(0, GetDetachY(tab_strip))));
   ASSERT_TRUE(base::test::RunUntil([&] { return DragInactive(2); }));
 
-  ASSERT_EQ(GetAllBrowserWindowInterfaces().size(), 2u);
+  EXPECT_EQ(GetAllBrowserWindowInterfaces().size(), 2u);
   BrowserWindowInterface* drag_browser =
-      GetLastActiveBrowserWindowInterfaceWithAnyProfile();
-  ASSERT_NE(browser(), drag_browser);
-  ASSERT_TRUE(IsDraggingInfoCleared(drag_browser));
+      ui_test_utils::GetBrowserNotInSet({browser()});
+  EXPECT_TRUE(IsDraggingInfoCleared(drag_browser));
 
-  ASSERT_TRUE(browser()->GetWindow()->IsMaximized());
-  ASSERT_TRUE(drag_browser->IsActive());
+  EXPECT_TRUE(browser()->GetWindow()->IsMaximized());
+  EXPECT_TRUE(drag_browser->IsActive());
   ASSERT_TRUE(base::test::RunUntil([&] {
     return InTabletSplitViewModeWithOverviewAsSecondary(drag_browser);
   }));
-  ASSERT_TRUE(IsInOverview(browser()));
-  ASSERT_EQ(IDString(browser()->GetTabStripModel()), "1");
-  ASSERT_EQ(IDString(drag_browser->GetTabStripModel()), "0");
+  EXPECT_TRUE(IsInOverview(browser()));
+  EXPECT_EQ(IDString(browser()->GetTabStripModel()), "1");
+  EXPECT_EQ(IDString(drag_browser->GetTabStripModel()), "0");
 }
 
 // In tablet mode, drag a tab out of a fullscreen browser and drop it on the
@@ -6353,10 +6371,10 @@
 IN_PROC_BROWSER_TEST_P(TabDragControllerTabletModeTest,
                        DragFromFullscreenToSnapTriggerArea) {
   AddTabsAndResetBrowser(browser(), 1);
-  ASSERT_EQ(IDString(browser()->GetTabStripModel()), "0 1");
+  EXPECT_EQ(IDString(browser()->GetTabStripModel()), "0 1");
 
   chrome::ToggleFullscreenMode(browser());
-  ASSERT_TRUE(browser()->GetWindow()->IsFullscreen());
+  EXPECT_TRUE(browser()->GetWindow()->IsFullscreen());
 
   // Forcibly reveal the tabstrip immediately.
   ImmersiveModeController* immersive_controller =
@@ -6369,7 +6387,7 @@
   TabStrip* tab_strip = GetTabStripForBrowser(browser());
   EXPECT_TRUE(tab_strip->GetVisible());
   Tab* first_tab = tab_strip->tab_at(0);
-  ASSERT_TRUE(browser()->IsActive());
+  EXPECT_TRUE(browser()->IsActive());
   ASSERT_TRUE(PressInputAtCenter(first_tab));
 
   gfx::Rect work_area =
@@ -6384,20 +6402,19 @@
       gfx::Vector2d(0, GetDetachY(tab_strip))));
   ASSERT_TRUE(base::test::RunUntil([&] { return DragInactive(2); }));
 
-  ASSERT_EQ(GetAllBrowserWindowInterfaces().size(), 2u);
+  EXPECT_EQ(GetAllBrowserWindowInterfaces().size(), 2u);
   BrowserWindowInterface* drag_browser =
-      GetLastActiveBrowserWindowInterfaceWithAnyProfile();
-  ASSERT_NE(browser(), drag_browser);
-  ASSERT_TRUE(IsDraggingInfoCleared(drag_browser));
+      ui_test_utils::GetBrowserNotInSet({browser()});
+  EXPECT_TRUE(IsDraggingInfoCleared(drag_browser));
 
-  ASSERT_TRUE(browser()->GetWindow()->IsFullscreen());
-  ASSERT_TRUE(drag_browser->IsActive());
+  EXPECT_TRUE(browser()->GetWindow()->IsFullscreen());
+  EXPECT_TRUE(drag_browser->IsActive());
   ASSERT_TRUE(base::test::RunUntil([&] {
     return InTabletSplitViewModeWithOverviewAsSecondary(drag_browser);
   }));
-  ASSERT_TRUE(IsInOverview(browser()));
-  ASSERT_EQ(IDString(browser()->GetTabStripModel()), "1");
-  ASSERT_EQ(IDString(drag_browser->GetTabStripModel()), "0");
+  EXPECT_TRUE(IsInOverview(browser()));
+  EXPECT_EQ(IDString(browser()->GetTabStripModel()), "1");
+  EXPECT_EQ(IDString(drag_browser->GetTabStripModel()), "0");
 }
 
 // In tablet mode, drag a tab out of a floated browser and drop it on the
@@ -6406,16 +6423,16 @@
 IN_PROC_BROWSER_TEST_P(TabDragControllerTabletModeTest,
                        DragFromFloatedToSnapTriggerArea) {
   AddTabsAndResetBrowser(browser(), 1);
-  ASSERT_EQ(IDString(browser()->GetTabStripModel()), "0 1");
+  EXPECT_EQ(IDString(browser()->GetTabStripModel()), "0 1");
 
   ash::Shell::Get()->float_controller()->ToggleFloat(
       browser()->GetWindow()->GetNativeWindow());
-  ASSERT_TRUE(IsFloated(browser()));
+  EXPECT_TRUE(IsFloated(browser()));
 
   TabStrip* tab_strip = GetTabStripForBrowser(browser());
   EXPECT_TRUE(tab_strip->GetVisible());
   Tab* first_tab = tab_strip->tab_at(0);
-  ASSERT_TRUE(browser()->IsActive());
+  EXPECT_TRUE(browser()->IsActive());
   ASSERT_TRUE(PressInputAtCenter(first_tab));
 
   gfx::Rect work_area =
@@ -6430,20 +6447,19 @@
       gfx::Vector2d(0, GetDetachY(tab_strip))));
   ASSERT_TRUE(base::test::RunUntil([&] { return DragInactive(2); }));
 
-  ASSERT_EQ(GetAllBrowserWindowInterfaces().size(), 2u);
+  EXPECT_EQ(GetAllBrowserWindowInterfaces().size(), 2u);
   BrowserWindowInterface* drag_browser =
-      GetLastActiveBrowserWindowInterfaceWithAnyProfile();
-  ASSERT_NE(browser(), drag_browser);
-  ASSERT_TRUE(IsDraggingInfoCleared(drag_browser));
+      ui_test_utils::GetBrowserNotInSet({browser()});
+  EXPECT_TRUE(IsDraggingInfoCleared(drag_browser));
 
-  ASSERT_TRUE(IsFloated(browser()));
-  ASSERT_TRUE(drag_browser->IsActive());
+  EXPECT_TRUE(IsFloated(browser()));
+  EXPECT_TRUE(drag_browser->IsActive());
   ASSERT_TRUE(base::test::RunUntil([&] {
     return InTabletSplitViewModeWithOverviewAsSecondary(drag_browser);
   }));
-  ASSERT_TRUE(IsInOverview(browser()));
-  ASSERT_EQ(IDString(browser()->GetTabStripModel()), "1");
-  ASSERT_EQ(IDString(drag_browser->GetTabStripModel()), "0");
+  EXPECT_TRUE(IsInOverview(browser()));
+  EXPECT_EQ(IDString(browser()->GetTabStripModel()), "1");
+  EXPECT_EQ(IDString(drag_browser->GetTabStripModel()), "0");
 }
 
 // In tablet mode, drag a tab out of a maximized browser and drop it on an
@@ -6452,15 +6468,15 @@
 IN_PROC_BROWSER_TEST_P(TabDragControllerTabletModeTest,
                        DragFromMaximizedToFloatedTabStrip) {
   AddTabsAndResetBrowser(browser(), 1);
-  ASSERT_EQ(IDString(browser()->GetTabStripModel()), "0 1");
-  ASSERT_TRUE(browser()->IsActive());
+  EXPECT_EQ(IDString(browser()->GetTabStripModel()), "0 1");
+  EXPECT_TRUE(browser()->IsActive());
 
   Browser* browser2 = CreateBrowser(browser()->profile());
   ResetIDs(browser2->GetTabStripModel(), 10);
-  ASSERT_EQ(IDString(browser2->GetTabStripModel()), "10");
+  EXPECT_EQ(IDString(browser2->GetTabStripModel()), "10");
   ash::Shell::Get()->float_controller()->ToggleFloat(
       browser2->GetWindow()->GetNativeWindow());
-  ASSERT_TRUE(IsFloated(browser2));
+  EXPECT_TRUE(IsFloated(browser2));
 
   TabStrip* tab_strip = GetTabStripForBrowser(browser());
   Tab* first_tab = tab_strip->tab_at(0);
@@ -6481,12 +6497,12 @@
       gfx::Vector2d(0, GetDetachY(tab_strip))));
   ASSERT_TRUE(base::test::RunUntil([&] { return DragInactive(2); }));
 
-  ASSERT_EQ(GetAllBrowserWindowInterfaces().size(), 2u);
-  ASSERT_TRUE(browser()->GetWindow()->IsMaximized());
-  ASSERT_TRUE(IsFloated(browser2));
-  ASSERT_TRUE(browser2->IsActive());
-  ASSERT_EQ(IDString(browser()->GetTabStripModel()), "1");
-  ASSERT_EQ(IDString(browser2->GetTabStripModel()), "0 10");
+  EXPECT_EQ(GetAllBrowserWindowInterfaces().size(), 2u);
+  EXPECT_TRUE(browser()->GetWindow()->IsMaximized());
+  EXPECT_TRUE(IsFloated(browser2));
+  EXPECT_TRUE(browser2->IsActive());
+  EXPECT_EQ(IDString(browser()->GetTabStripModel()), "1");
+  EXPECT_EQ(IDString(browser2->GetTabStripModel()), "0 10");
 }
 
 // In tablet mode, drag a tab out of a fullscreen browser and drop it on an
@@ -6495,16 +6511,16 @@
 IN_PROC_BROWSER_TEST_P(TabDragControllerTabletModeTest,
                        DragFromFullscreenToFloatedTabStrip) {
   AddTabsAndResetBrowser(browser(), 1);
-  ASSERT_EQ(IDString(browser()->GetTabStripModel()), "0 1");
+  EXPECT_EQ(IDString(browser()->GetTabStripModel()), "0 1");
 
   Browser* browser2 = CreateBrowser(browser()->profile());
   ResetIDs(browser2->GetTabStripModel(), 10);
   ash::Shell::Get()->float_controller()->ToggleFloat(
       browser2->GetWindow()->GetNativeWindow());
-  ASSERT_TRUE(IsFloated(browser2));
+  EXPECT_TRUE(IsFloated(browser2));
 
   chrome::ToggleFullscreenMode(browser());
-  ASSERT_TRUE(browser()->GetWindow()->IsFullscreen());
+  EXPECT_TRUE(browser()->GetWindow()->IsFullscreen());
 
   ui_test_utils::BrowserActivationWaiter activation_waiter(browser());
   browser()->window()->Activate();
@@ -6521,7 +6537,7 @@
   TabStrip* tab_strip = GetTabStripForBrowser(browser());
   EXPECT_TRUE(tab_strip->GetVisible());
   Tab* first_tab = tab_strip->tab_at(0);
-  ASSERT_TRUE(browser()->IsActive());
+  EXPECT_TRUE(browser()->IsActive());
   ASSERT_TRUE(PressInputAtCenter(first_tab));
 
   TabStrip* tab_strip2 = GetTabStripForBrowser(browser2);
@@ -6535,12 +6551,12 @@
       gfx::Vector2d(0, GetDetachY(tab_strip))));
   ASSERT_TRUE(base::test::RunUntil([&] { return DragInactive(2); }));
 
-  ASSERT_EQ(GetAllBrowserWindowInterfaces().size(), 2u);
-  ASSERT_TRUE(browser()->GetWindow()->IsFullscreen());
-  ASSERT_TRUE(IsFloated(browser2));
-  ASSERT_TRUE(browser2->IsActive());
-  ASSERT_EQ(IDString(browser()->GetTabStripModel()), "1");
-  ASSERT_EQ(IDString(browser2->GetTabStripModel()), "0 10");
+  EXPECT_EQ(GetAllBrowserWindowInterfaces().size(), 2u);
+  EXPECT_TRUE(browser()->GetWindow()->IsFullscreen());
+  EXPECT_TRUE(IsFloated(browser2));
+  EXPECT_TRUE(browser2->IsActive());
+  EXPECT_EQ(IDString(browser()->GetTabStripModel()), "1");
+  EXPECT_EQ(IDString(browser2->GetTabStripModel()), "0 10");
 }
 
 // In tablet mode, drag a tab out of a snapped browser and drop it below the
@@ -6548,17 +6564,17 @@
 // source browser in the split view.
 IN_PROC_BROWSER_TEST_P(TabDragControllerTabletModeTest, DragFromSnapped) {
   AddTabsAndResetBrowser(browser(), 1);
-  ASSERT_EQ(IDString(browser()->GetTabStripModel()), "0 1");
+  EXPECT_EQ(IDString(browser()->GetTabStripModel()), "0 1");
 
   Browser* browser2 = CreateBrowser(browser()->profile());
   ResetIDs(browser2->GetTabStripModel(), 10);
-  ASSERT_EQ(IDString(browser2->GetTabStripModel()), "10");
+  EXPECT_EQ(IDString(browser2->GetTabStripModel()), "10");
 
   split_view_controller()->SnapWindow(browser()->GetWindow()->GetNativeWindow(),
                                       ash::SnapPosition::kPrimary);
   split_view_controller()->SnapWindow(browser2->GetWindow()->GetNativeWindow(),
                                       ash::SnapPosition::kSecondary);
-  ASSERT_TRUE(InTabletSplitViewMode(browser(), browser2));
+  EXPECT_TRUE(InTabletSplitViewMode(browser(), browser2));
 
   TabStrip* tab_strip = GetTabStripForBrowser(browser());
   Tab* first_tab = tab_strip->tab_at(0);
@@ -6575,16 +6591,15 @@
       gfx::Vector2d(0, GetDetachY(tab_strip))));
   ASSERT_TRUE(base::test::RunUntil([&] { return DragInactive(3); }));
 
+  EXPECT_EQ(GetAllBrowserWindowInterfaces().size(), 3u);
   BrowserWindowInterface* drag_browser =
-      GetLastActiveBrowserWindowInterfaceWithAnyProfile();
-  ASSERT_NE(browser(), drag_browser);
-  ASSERT_TRUE(IsDraggingInfoCleared(drag_browser));
-  ASSERT_EQ(GetAllBrowserWindowInterfaces().size(), 3u);
-  ASSERT_TRUE(InTabletSplitViewMode(drag_browser, browser2));
-  ASSERT_TRUE(drag_browser->IsActive());
-  ASSERT_EQ(IDString(browser()->GetTabStripModel()), "1");
-  ASSERT_EQ(IDString(browser2->GetTabStripModel()), "10");
-  ASSERT_EQ(IDString(drag_browser->GetTabStripModel()), "0");
+      ui_test_utils::GetBrowserNotInSet({browser(), browser2});
+  EXPECT_TRUE(IsDraggingInfoCleared(drag_browser));
+  EXPECT_TRUE(InTabletSplitViewMode(drag_browser, browser2));
+  EXPECT_TRUE(drag_browser->IsActive());
+  EXPECT_EQ(IDString(browser()->GetTabStripModel()), "1");
+  EXPECT_EQ(IDString(browser2->GetTabStripModel()), "10");
+  EXPECT_EQ(IDString(drag_browser->GetTabStripModel()), "0");
 }
 
 // In tablet mode, drag a tab out of a snapped browser and drop it on the other
@@ -6594,17 +6609,17 @@
 IN_PROC_BROWSER_TEST_P(TabDragControllerTabletModeTest,
                        DragFromSnappedToOtherSnapped) {
   AddTabsAndResetBrowser(browser(), 1);
-  ASSERT_EQ(IDString(browser()->GetTabStripModel()), "0 1");
+  EXPECT_EQ(IDString(browser()->GetTabStripModel()), "0 1");
 
   Browser* browser2 = CreateBrowser(browser()->profile());
   ResetIDs(browser2->GetTabStripModel(), 10);
-  ASSERT_EQ(IDString(browser2->GetTabStripModel()), "10");
+  EXPECT_EQ(IDString(browser2->GetTabStripModel()), "10");
 
   split_view_controller()->SnapWindow(browser()->GetWindow()->GetNativeWindow(),
                                       ash::SnapPosition::kPrimary);
   split_view_controller()->SnapWindow(browser2->GetWindow()->GetNativeWindow(),
                                       ash::SnapPosition::kSecondary);
-  ASSERT_TRUE(InTabletSplitViewMode(browser(), browser2));
+  EXPECT_TRUE(InTabletSplitViewMode(browser(), browser2));
 
   TabStrip* tab_strip = GetTabStripForBrowser(browser());
   Tab* first_tab = tab_strip->tab_at(0);
@@ -6628,16 +6643,15 @@
       gfx::Vector2d(0, GetDetachY(tab_strip))));
   ASSERT_TRUE(base::test::RunUntil([&] { return DragInactive(3); }));
 
-  ASSERT_EQ(GetAllBrowserWindowInterfaces().size(), 3u);
+  EXPECT_EQ(GetAllBrowserWindowInterfaces().size(), 3u);
   BrowserWindowInterface* drag_browser =
-      GetLastActiveBrowserWindowInterfaceWithAnyProfile();
-  ASSERT_NE(browser(), drag_browser);
-  ASSERT_TRUE(IsDraggingInfoCleared(drag_browser));
-  ASSERT_TRUE(InTabletSplitViewMode(browser(), drag_browser));
-  ASSERT_TRUE(drag_browser->IsActive());
-  ASSERT_EQ(IDString(browser()->GetTabStripModel()), "1");
-  ASSERT_EQ(IDString(browser2->GetTabStripModel()), "10");
-  ASSERT_EQ(IDString(drag_browser->GetTabStripModel()), "0");
+      ui_test_utils::GetBrowserNotInSet({browser(), browser2});
+  EXPECT_TRUE(IsDraggingInfoCleared(drag_browser));
+  EXPECT_TRUE(InTabletSplitViewMode(browser(), drag_browser));
+  EXPECT_TRUE(drag_browser->IsActive());
+  EXPECT_EQ(IDString(browser()->GetTabStripModel()), "1");
+  EXPECT_EQ(IDString(browser2->GetTabStripModel()), "10");
+  EXPECT_EQ(IDString(drag_browser->GetTabStripModel()), "0");
 }
 
 // In tablet mode, drag a tab out of a snapped browser and drop it on the other
@@ -6646,16 +6660,16 @@
 IN_PROC_BROWSER_TEST_P(TabDragControllerTabletModeTest,
                        DragFromSnappedToOverview) {
   AddTabsAndResetBrowser(browser(), 1);
-  ASSERT_EQ(IDString(browser()->GetTabStripModel()), "0 1");
+  EXPECT_EQ(IDString(browser()->GetTabStripModel()), "0 1");
 
   Browser* browser2 = CreateBrowser(browser()->profile());
   ResetIDs(browser2->GetTabStripModel(), 10);
-  ASSERT_EQ(IDString(browser2->GetTabStripModel()), "10");
+  EXPECT_EQ(IDString(browser2->GetTabStripModel()), "10");
 
   split_view_controller()->SnapWindow(browser()->GetWindow()->GetNativeWindow(),
                                       ash::SnapPosition::kPrimary);
-  ASSERT_TRUE(InTabletSplitViewModeWithOverviewAsSecondary(browser()));
-  ASSERT_TRUE(IsInOverview(browser2));
+  EXPECT_TRUE(InTabletSplitViewModeWithOverviewAsSecondary(browser()));
+  EXPECT_TRUE(IsInOverview(browser2));
 
   TabStrip* tab_strip = GetTabStripForBrowser(browser());
   Tab* first_tab = tab_strip->tab_at(0);
@@ -6679,19 +6693,17 @@
       gfx::Vector2d(0, GetDetachY(tab_strip))));
   ASSERT_TRUE(base::test::RunUntil([&] { return DragInactive(3); }));
 
+  EXPECT_EQ(GetAllBrowserWindowInterfaces().size(), 3u);
   BrowserWindowInterface* drag_browser =
-      GetLastActiveBrowserWindowInterfaceWithAnyProfile();
-  ASSERT_NE(browser(), drag_browser);
-  ASSERT_TRUE(IsDraggingInfoCleared(drag_browser));
-
-  ASSERT_EQ(GetAllBrowserWindowInterfaces().size(), 3u);
-  ASSERT_TRUE(split_view_controller()->InTabletSplitViewMode());
-  ASSERT_TRUE(InTabletSplitViewMode(browser(), drag_browser));
-  ASSERT_TRUE(drag_browser->IsActive());
-  ASSERT_FALSE(ash::GetOverviewSession());
-  ASSERT_EQ(IDString(browser()->GetTabStripModel()), "1");
-  ASSERT_EQ(IDString(browser2->GetTabStripModel()), "10");
-  ASSERT_EQ(IDString(drag_browser->GetTabStripModel()), "0");
+      ui_test_utils::GetBrowserNotInSet({browser(), browser2});
+  EXPECT_TRUE(IsDraggingInfoCleared(drag_browser));
+  EXPECT_TRUE(split_view_controller()->InTabletSplitViewMode());
+  EXPECT_TRUE(InTabletSplitViewMode(browser(), drag_browser));
+  EXPECT_TRUE(drag_browser->IsActive());
+  EXPECT_FALSE(ash::GetOverviewSession());
+  EXPECT_EQ(IDString(browser()->GetTabStripModel()), "1");
+  EXPECT_EQ(IDString(browser2->GetTabStripModel()), "10");
+  EXPECT_EQ(IDString(drag_browser->GetTabStripModel()), "0");
 }
 
 // In tablet mode, drag a tab out of a snapped browser and drop it on the tab
@@ -6700,17 +6712,17 @@
 IN_PROC_BROWSER_TEST_P(TabDragControllerTabletModeTest,
                        DragFromSnappedToOtherSnappedTabStrip) {
   AddTabsAndResetBrowser(browser(), 1);
-  ASSERT_EQ(IDString(browser()->GetTabStripModel()), "0 1");
+  EXPECT_EQ(IDString(browser()->GetTabStripModel()), "0 1");
 
   Browser* browser2 = CreateBrowser(browser()->profile());
   ResetIDs(browser2->GetTabStripModel(), 10);
-  ASSERT_EQ(IDString(browser2->GetTabStripModel()), "10");
+  EXPECT_EQ(IDString(browser2->GetTabStripModel()), "10");
 
   split_view_controller()->SnapWindow(browser()->GetWindow()->GetNativeWindow(),
                                       ash::SnapPosition::kPrimary);
   split_view_controller()->SnapWindow(browser2->GetWindow()->GetNativeWindow(),
                                       ash::SnapPosition::kSecondary);
-  ASSERT_TRUE(InTabletSplitViewMode(browser(), browser2));
+  EXPECT_TRUE(InTabletSplitViewMode(browser(), browser2));
 
   TabStrip* tab_strip = GetTabStripForBrowser(browser());
   Tab* first_tab = tab_strip->tab_at(0);
@@ -6731,11 +6743,11 @@
       gfx::Vector2d(0, GetDetachY(tab_strip))));
   ASSERT_TRUE(base::test::RunUntil([&] { return DragInactive(2); }));
 
-  ASSERT_EQ(GetAllBrowserWindowInterfaces().size(), 2u);
-  ASSERT_TRUE(InTabletSplitViewMode(browser(), browser2));
-  ASSERT_TRUE(browser2->IsActive());
-  ASSERT_EQ(IDString(browser()->GetTabStripModel()), "1");
-  ASSERT_EQ(IDString(browser2->GetTabStripModel()), "0 10");
+  EXPECT_EQ(GetAllBrowserWindowInterfaces().size(), 2u);
+  EXPECT_TRUE(InTabletSplitViewMode(browser(), browser2));
+  EXPECT_TRUE(browser2->IsActive());
+  EXPECT_EQ(IDString(browser()->GetTabStripModel()), "1");
+  EXPECT_EQ(IDString(browser2->GetTabStripModel()), "0 10");
 }
 
 // In tablet mode, drag a tab group out of a snapped browser and drop it on the
@@ -6744,17 +6756,17 @@
 IN_PROC_BROWSER_TEST_P(TabDragControllerTabletModeTest,
                        DragGroupFromSnappedToOtherSnappedTabStrip) {
   AddTabsAndResetBrowser(browser(), 1);
-  ASSERT_EQ(IDString(browser()->GetTabStripModel()), "0 1");
+  EXPECT_EQ(IDString(browser()->GetTabStripModel()), "0 1");
 
   Browser* browser2 = CreateBrowser(browser()->profile());
   ResetIDs(browser2->GetTabStripModel(), 10);
-  ASSERT_EQ(IDString(browser2->GetTabStripModel()), "10");
+  EXPECT_EQ(IDString(browser2->GetTabStripModel()), "10");
 
   split_view_controller()->SnapWindow(browser()->GetWindow()->GetNativeWindow(),
                                       ash::SnapPosition::kPrimary);
   split_view_controller()->SnapWindow(browser2->GetWindow()->GetNativeWindow(),
                                       ash::SnapPosition::kSecondary);
-  ASSERT_TRUE(InTabletSplitViewMode(browser(), browser2));
+  EXPECT_TRUE(InTabletSplitViewMode(browser(), browser2));
 
   TabStrip* tab_strip = GetTabStripForBrowser(browser());
   Tab* first_tab = tab_strip->tab_at(0);
@@ -6762,7 +6774,7 @@
   ui_test_utils::BrowserActivationWaiter activation_waiter(browser());
   browser()->window()->Activate();
   activation_waiter.WaitForActivation();
-  ASSERT_TRUE(PressInputAtCenter(first_tab));
+  EXPECT_TRUE(PressInputAtCenter(first_tab));
 
   TabStrip* tab_strip2 = GetTabStripForBrowser(browser2);
   Tab* first_tab2 = tab_strip2->tab_at(0);
@@ -6775,11 +6787,65 @@
       gfx::Vector2d(0, GetDetachY(tab_strip))));
   ASSERT_TRUE(base::test::RunUntil([&] { return DragInactive(2); }));
 
-  ASSERT_EQ(GetAllBrowserWindowInterfaces().size(), 2u);
-  ASSERT_TRUE(InTabletSplitViewMode(browser(), browser2));
-  ASSERT_TRUE(browser2->IsActive());
-  ASSERT_EQ(IDString(browser()->GetTabStripModel()), "1");
-  ASSERT_EQ(IDString(browser2->GetTabStripModel()), "0 10");
+  EXPECT_EQ(GetAllBrowserWindowInterfaces().size(), 2u);
+  EXPECT_TRUE(InTabletSplitViewMode(browser(), browser2));
+  EXPECT_TRUE(browser2->IsActive());
+  EXPECT_EQ(IDString(browser()->GetTabStripModel()), "1");
+  EXPECT_EQ(IDString(browser2->GetTabStripModel()), "0 10");
+}
+
+IN_PROC_BROWSER_TEST_P(TabDragControllerTabletModeTest, DoubleDetach) {
+  AddTabsAndResetBrowser(browser(), 1);
+  EXPECT_EQ(IDString(browser()->GetTabStripModel()), "0 1");
+
+  Browser* browser2 = CreateBrowser(browser()->profile());
+  ResetIDs(browser2->GetTabStripModel(), 10);
+  EXPECT_EQ(IDString(browser2->GetTabStripModel()), "10");
+
+  split_view_controller()->SnapWindow(browser()->GetWindow()->GetNativeWindow(),
+                                      ash::SnapPosition::kPrimary);
+  split_view_controller()->SnapWindow(browser2->GetWindow()->GetNativeWindow(),
+                                      ash::SnapPosition::kSecondary);
+  EXPECT_TRUE(InTabletSplitViewMode(browser(), browser2));
+
+  TabStrip* tab_strip = GetTabStripForBrowser(browser());
+  Tab* first_tab = tab_strip->tab_at(0);
+  gfx::Point first_tab_center = first_tab->GetBoundsInScreen().CenterPoint();
+
+  ui_test_utils::BrowserActivationWaiter activation_waiter(browser());
+  browser()->window()->Activate();
+  activation_waiter.WaitForActivation();
+  ASSERT_TRUE(PressInputAtCenter(first_tab));
+
+  TabStrip* tab_strip2 = GetTabStripForBrowser(browser2);
+  Tab* first_tab2 = tab_strip2->tab_at(0);
+  gfx::Point first_tab2_center = first_tab2->GetBoundsInScreen().CenterPoint();
+
+  // Drag from browser() into browser2, but don't release.
+  ASSERT_TRUE(DragInputToCenterNotifyWhenDone(
+      first_tab,
+      base::BindOnce(&TabDragControllerTabletModeTest::MaybeDragThenHold,
+                     base::Unretained(this), tab_strip, first_tab2_center),
+      gfx::Vector2d(0, GetDetachY(tab_strip))));
+  ASSERT_TRUE(base::test::RunUntil(
+      [&] { return IDString(browser2->GetTabStripModel()) == "0 10"; }));
+  EXPECT_TRUE(TabDragController::IsActive());
+  EXPECT_EQ(GetAllBrowserWindowInterfaces().size(), 2u);
+  EXPECT_TRUE(InTabletSplitViewMode(browser(), browser2));
+  EXPECT_TRUE(browser2->IsActive());
+  EXPECT_EQ(IDString(browser()->GetTabStripModel()), "1");
+
+  // Drag out of browser2 back into browser(), and release there.
+  ASSERT_TRUE(DragInputToCenterNotifyWhenDone(
+      tab_strip2->tab_at(0),
+      base::BindOnce(&TabDragControllerTabletModeTest::MaybeDragThenRelease,
+                     base::Unretained(this), tab_strip2, first_tab_center),
+      gfx::Vector2d(0, GetDetachY(tab_strip2))));
+  ASSERT_TRUE(base::test::RunUntil([&] { return DragInactive(2); }));
+  EXPECT_TRUE(InTabletSplitViewMode(browser(), browser2));
+  EXPECT_TRUE(browser()->IsActive());
+  EXPECT_EQ(IDString(browser()->GetTabStripModel()), "0 1");
+  EXPECT_EQ(IDString(browser2->GetTabStripModel()), "10");
 }
 
 INSTANTIATE_TEST_SUITE_P(
diff --git a/chrome/browser/ui/views/tabs/glic_button.cc b/chrome/browser/ui/views/tabs/glic_button.cc
index 041dded..685677447 100644
--- a/chrome/browser/ui/views/tabs/glic_button.cc
+++ b/chrome/browser/ui/views/tabs/glic_button.cc
@@ -76,6 +76,8 @@
 constexpr gfx::Tween::Type kSlidingTextTween =
     gfx::Tween::Type::ACCEL_20_DECEL_100;
 
+constexpr int kMinTargetWidthForAnimatingText = 41;
+
 bool EntrypointVariationsEnabled() {
   return base::FeatureList::IsEnabled(features::kGlicEntrypointVariations);
 }
@@ -271,6 +273,8 @@
     return;
   }
 
+  is_animating_text_ = true;
+
   StartSlidingTextAnimation(/*show=*/false);
 
   label()->SetPaintToLayer();
@@ -345,9 +349,8 @@
           .height();
 
   if (is_animating_text_) {
-    const int min_target_width = 41;
-    const int width =
-        std::lerp(min_target_width, default_label_width_, GetWidthFactor());
+    const int width = std::lerp(kMinTargetWidthForAnimatingText,
+                                default_label_width_, GetWidthFactor());
     return gfx::Size(width, height);
   }
 
@@ -474,7 +477,14 @@
 
     expansion_animation_done_callback_.Run();
   }
-  is_animating_text_ = false;
+  if (is_animating_text_) {
+    is_animating_text_ = false;
+
+    // Makes sure the transition of the frames from is_animating_text_ to
+    // !is_animating_text_ in CalculatePreferredSize() is smooth.
+    initial_width_ = kMinTargetWidthForAnimatingText;
+    expanded_width_ = CalculateExpandedWidth();
+  }
 }
 
 void GlicButton::AnimationCanceled(const gfx::Animation* animation) {
@@ -739,8 +749,13 @@
   }
   expansion_animation_->SetSlideDuration(overall_duration);
   if (show) {
+    // Makes sure the animation value always goes from 0 to 1 for show and 1 to
+    // 0 for hide.
+    SetWidthFactor(0.f);
+    expansion_animation_->Reset(0);
     expansion_animation_->Show();
   } else {
+    SetWidthFactor(1.f);
     expansion_animation_->Reset(1);
     expansion_animation_->Hide();
   }
diff --git a/chrome/browser/ui/views/tabs/tab_icon.cc b/chrome/browser/ui/views/tabs/tab_icon.cc
index bd71c804..3fb6228 100644
--- a/chrome/browser/ui/views/tabs/tab_icon.cc
+++ b/chrome/browser/ui/views/tabs/tab_icon.cc
@@ -376,12 +376,10 @@
   }
 
   std::unique_ptr<gfx::ScopedCanvas> scoped_canvas;
-  bool use_scale_filter = false;
 
   if (GetShowingLoadingAnimation() || favicon_size_animation_.is_animating() ||
       was_discard_indicator_shown_) {
     scoped_canvas = std::make_unique<gfx::ScopedCanvas>(canvas);
-    use_scale_filter = true;
     // The favicon is initially inset with the width of the loading-animation
     // stroke + an additional dp to create some visual separation.
     const float kInitialFaviconInsetDp = 1 + kLoadingAnimationStrokeWidthDp;
@@ -424,7 +422,7 @@
 
   canvas->DrawImageInt(icon, 0, 0, bounds.width(), bounds.height(), bounds.x(),
                        bounds.y(), bounds.width(), bounds.height(),
-                       use_scale_filter);
+                       /*filter=*/true);
 
   // Emits a custom event when the favicon finishes shrinking and the discard
   // ring gets painted
diff --git a/chrome/browser/ui/views/tabs/tab_strip_action_container.cc b/chrome/browser/ui/views/tabs/tab_strip_action_container.cc
index 418f5b1f..b586214 100644
--- a/chrome/browser/ui/views/tabs/tab_strip_action_container.cc
+++ b/chrome/browser/ui/views/tabs/tab_strip_action_container.cc
@@ -719,8 +719,17 @@
     const std::u16string nudge_text) {
   CHECK(glic_actor_task_icon_);
   if (GetIsShowingGlicNudge()) {
+    // If the glic button is showing, start the hide animation in parallel to
+    // the show actor nudge animation.
     HideTabStripNudge(glic_button_);
+    OnGlicButtonAnimationEnded();
   }
+  ShowGlicActorNudge(nudge_text);
+}
+
+void TabStripActionContainer::ShowGlicActorNudge(
+    const std::u16string nudge_text) {
+  CHECK(glic_actor_task_icon_);
   // Start animation for clearing text on the glic button.
   glic_button_->SuppressLabel();
   ShowGlicActorTaskIcon();
diff --git a/chrome/browser/ui/views/tabs/tab_strip_action_container.h b/chrome/browser/ui/views/tabs/tab_strip_action_container.h
index 50e4f5ee..7f7849b 100644
--- a/chrome/browser/ui/views/tabs/tab_strip_action_container.h
+++ b/chrome/browser/ui/views/tabs/tab_strip_action_container.h
@@ -149,6 +149,7 @@
   bool GetIsShowingGlicActorTaskIconNudge();
 #if BUILDFLAG(ENABLE_GLIC)
   void TriggerGlicActorNudge(const std::u16string nudge_text);
+  void ShowGlicActorNudge(const std::u16string nudge_text);
 #endif
 
   // UI controls for updating buttons based on the floaty view state:
diff --git a/chrome/browser/ui/views/tabs/tab_strip_action_container_browsertest.cc b/chrome/browser/ui/views/tabs/tab_strip_action_container_browsertest.cc
index 2d3b63a..3b55df0 100644
--- a/chrome/browser/ui/views/tabs/tab_strip_action_container_browsertest.cc
+++ b/chrome/browser/ui/views/tabs/tab_strip_action_container_browsertest.cc
@@ -659,7 +659,9 @@
 
 // TODO(crbug.com/451697169): Fix this test for Windows and Linux.
 // TODO(crbug.com/461145884): Enable on ChromeOS
-#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+// TODO(crbug.com/465247286): Fix this for Mac.
+#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || \
+    BUILDFLAG(IS_MAC)
 #define MAYBE_GlicLabelEnablementFollowsWindowActivation \
   DISABLED_GlicLabelEnablementFollowsWindowActivation
 #else
@@ -693,6 +695,7 @@
 
 IN_PROC_BROWSER_TEST_F(TabStripActionContainerBrowserTest,
                        LogsWhenGlicActorTaskNudgeClicked) {
+  base::HistogramTester histogram_tester;
   EXPECT_FALSE(GlicActorButtonContainer()->GetVisible());
   ASSERT_THAT(GlicActorButtonContainer()->children(), SizeIs(1));
 
@@ -712,6 +715,12 @@
   EXPECT_TRUE(GlicActorButtonContainer()->GetVisible());
   EXPECT_TRUE(GlicActorTaskIcon()->GetIsShowingNudge());
 
+  ASSERT_TRUE(base::test::RunUntil([&]() {
+    return histogram_tester.GetBucketCount(
+               "Actor.Ui.TaskNudge.Shown",
+               ActorTaskNudgeState::Text::kNeedsAttention) == 1;
+  }));
+
   base::UserActionTester user_action_tester;
   OnButtonClicked(GlicActorTaskIcon());
   EXPECT_EQ(1, user_action_tester.GetActionCount(
@@ -720,6 +729,7 @@
 
 IN_PROC_BROWSER_TEST_F(TabStripActionContainerBrowserTest,
                        GlicActorCompleteDoesNotShowTaskNudge) {
+  base::HistogramTester histogram_tester;
   EXPECT_EQ(GlicActorTaskIcon()->GetText(), std::u16string());
   ASSERT_FALSE(tab_strip_action_container()->animation_session_for_testing());
   EXPECT_FALSE(GlicActorButtonContainer()->GetVisible());
@@ -733,6 +743,12 @@
   EXPECT_EQ(GlicActorTaskIcon()->GetText(), std::u16string());
   EXPECT_FALSE(GlicActorButtonContainer()->GetVisible());
   EXPECT_FALSE(GlicActorTaskIcon()->GetIsShowingNudge());
+
+  EXPECT_TRUE(RunUntil([&]() { return !GlicActorTaskIcon()->GetVisible(); }));
+  EXPECT_EQ(histogram_tester.GetBucketCount(
+                "Actor.Ui.TaskNudge.Shown",
+                ActorTaskNudgeState::Text::kCompleteTasks),
+            0);
 }
 
 IN_PROC_BROWSER_TEST_F(TabStripActionContainerBrowserTest,
@@ -772,6 +788,7 @@
 
 IN_PROC_BROWSER_TEST_F(TabStripActionContainerBrowserTest,
                        ResetGlicActorTaskNudgeOnCheckTaskToActiveStateChange) {
+  base::HistogramTester histogram_tester;
   auto* actor_nudge_controller =
       tabs::GlicActorNudgeController::From(browser());
   auto actor_task_nudge_state = ActorTaskNudgeState();
@@ -798,6 +815,12 @@
 
   EXPECT_TRUE(RunUntil([&]() { return !GlicActorTaskIcon()->GetVisible(); }));
   EXPECT_FALSE(GlicActorButtonContainer()->GetVisible());
+  // Ensure the shown histogram was not recorded as the default state doesn't
+  // show a nudge.
+  EXPECT_EQ(
+      histogram_tester.GetBucketCount("Actor.Ui.TaskNudge.Shown",
+                                      ActorTaskNudgeState::Text::kDefault),
+      0);
   // Check that GlicButton was removed from the GlicActorButtonContainer.
   ASSERT_THAT(GlicActorButtonContainer()->children(), SizeIs(1));
   EXPECT_EQ(GlicActorTaskIcon(), GlicActorButtonContainer()->children()[0]);
diff --git a/chrome/browser/ui/views/toolbar/toolbar_button_menu_highlighter.h b/chrome/browser/ui/views/toolbar/toolbar_button_menu_highlighter.h
index 02905d2..f92117c6d 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_button_menu_highlighter.h
+++ b/chrome/browser/ui/views/toolbar/toolbar_button_menu_highlighter.h
@@ -10,6 +10,8 @@
 #include "components/user_education/common/menu/highlighting_menu_button_helper.h"
 #include "components/user_education/common/menu/highlighting_simple_menu_model_delegate.h"
 
+class Browser;
+
 // In order to have automatic toolbar button menu highlighting:
 //  - Derive your model from user_education::HighlightingSimpleMenuModelDelegate
 //    instead of ui::SimpleMenuModel::Delegate.
diff --git a/chrome/browser/ui/wallet/walletable_pass_consent_bubble_controller.cc b/chrome/browser/ui/wallet/walletable_pass_consent_bubble_controller.cc
index 996ae582..89c0d55 100644
--- a/chrome/browser/ui/wallet/walletable_pass_consent_bubble_controller.cc
+++ b/chrome/browser/ui/wallet/walletable_pass_consent_bubble_controller.cc
@@ -32,15 +32,14 @@
 }
 
 void WalletablePassConsentBubbleController::SetUpAndShowConsentBubble(
-    optimization_guide::proto::PassCategory pass_category,
+    PassCategory pass_category,
     WalletablePassClient::WalletablePassBubbleResultCallback callback) {
   pass_category_ = pass_category;
   SetCallback(std::move(callback));
   QueueOrShowBubble();
 }
 
-optimization_guide::proto::PassCategory
-WalletablePassConsentBubbleController::pass_category() const {
+PassCategory WalletablePassConsentBubbleController::pass_category() const {
   CHECK(pass_category_.has_value());
   return *pass_category_;
 }
diff --git a/chrome/browser/ui/wallet/walletable_pass_consent_bubble_controller.h b/chrome/browser/ui/wallet/walletable_pass_consent_bubble_controller.h
index 06d914c..0db5f9929 100644
--- a/chrome/browser/ui/wallet/walletable_pass_consent_bubble_controller.h
+++ b/chrome/browser/ui/wallet/walletable_pass_consent_bubble_controller.h
@@ -8,6 +8,7 @@
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
 #include "chrome/browser/ui/wallet/walletable_pass_bubble_controller_base.h"
+#include "components/wallet/core/browser/data_models/walletable_pass.h"
 
 namespace tabs {
 class TabInterface;
@@ -37,10 +38,10 @@
   // Shows the consent bubble. `callback` will be run when the user makes a
   // decision.
   void SetUpAndShowConsentBubble(
-      optimization_guide::proto::PassCategory pass_category,
+      PassCategory pass_category,
       WalletablePassClient::WalletablePassBubbleResultCallback callback);
 
-  optimization_guide::proto::PassCategory pass_category() const;
+  PassCategory pass_category() const;
 
   base::WeakPtr<WalletablePassConsentBubbleController> GetWeakPtr();
 
@@ -50,7 +51,7 @@
   void ShowBubble() override;
 
  private:
-  std::optional<optimization_guide::proto::PassCategory> pass_category_;
+  std::optional<PassCategory> pass_category_;
 
   base::WeakPtrFactory<WalletablePassConsentBubbleController> weak_ptr_factory_{
       this};
diff --git a/chrome/browser/ui/wallet/walletable_pass_consent_bubble_view.cc b/chrome/browser/ui/wallet/walletable_pass_consent_bubble_view.cc
index 0e94424f..f2d84dddb 100644
--- a/chrome/browser/ui/wallet/walletable_pass_consent_bubble_view.cc
+++ b/chrome/browser/ui/wallet/walletable_pass_consent_bubble_view.cc
@@ -26,8 +26,6 @@
 namespace wallet {
 namespace {
 
-using enum optimization_guide::proto::PassCategory;
-
 constexpr int kBubbleWidth = 320;
 constexpr int kSubTitleBottomMargin = 16;
 
@@ -134,16 +132,18 @@
 
 int WalletablePassConsentBubbleView::GetHeaderImageResourceId() const {
   switch (pass_category_) {
-    case PASS_CATEGORY_LOYALTY_CARD:
+    case PassCategory::kLoyaltyCard:
       return IDR_WALLET_PASS_SAVE_LOYALTY_CARD_LOTTIE;
-    case PASS_CATEGORY_EVENT_PASS:
+    case PassCategory::kEventPass:
       return IDR_WALLET_PASS_SAVE_EVENT_TICKET_LOTTIE;
-    case PASS_CATEGORY_TRANSIT_TICKET:
+    case PassCategory::kTransitTicket:
       return IDR_WALLET_PASS_SAVE_TRANSPORT_TICKET_LOTTIE;
-    case PASS_CATEGORY_UNSPECIFIED:
+    case PassCategory::kBoardingPass:
+      return IDR_WALLET_PASS_SAVE_BOARDING_PASS_LOTTIE;
+    case PassCategory::kUnspecified:
     default:
       NOTREACHED() << "Not supported walletable pass category: "
-                   << pass_category_;
+                   << static_cast<int>(pass_category_);
   }
 }
 
diff --git a/chrome/browser/ui/wallet/walletable_pass_consent_bubble_view.h b/chrome/browser/ui/wallet/walletable_pass_consent_bubble_view.h
index 3ca6718..a66e277 100644
--- a/chrome/browser/ui/wallet/walletable_pass_consent_bubble_view.h
+++ b/chrome/browser/ui/wallet/walletable_pass_consent_bubble_view.h
@@ -6,7 +6,7 @@
 #define CHROME_BROWSER_UI_WALLET_WALLETABLE_PASS_CONSENT_BUBBLE_VIEW_H_
 
 #include "chrome/browser/ui/wallet/walletable_pass_bubble_view_base.h"
-#include "components/optimization_guide/proto/features/walletable_pass_extraction.pb.h"
+#include "components/wallet/core/browser/data_models/walletable_pass.h"
 #include "ui/base/metadata/metadata_header_macros.h"
 
 namespace content {
@@ -42,7 +42,7 @@
 
   int GetHeaderImageResourceId() const;
 
-  optimization_guide::proto::PassCategory pass_category_;
+  PassCategory pass_category_;
 
   base::WeakPtr<WalletablePassConsentBubbleController> controller_;
 };
diff --git a/chrome/browser/ui/webui/ash/settings/pages/device/device_section.cc b/chrome/browser/ui/webui/ash/settings/pages/device/device_section.cc
index bcb8527f..ac36b95 100644
--- a/chrome/browser/ui/webui/ash/settings/pages/device/device_section.cc
+++ b/chrome/browser/ui/webui/ash/settings/pages/device/device_section.cc
@@ -506,10 +506,6 @@
          display::HasExternalTouchscreenDevice();
 }
 
-bool IsListAllDisplayModesEnabled() {
-  return display::features::IsListAllDisplayModesEnabled();
-}
-
 bool IsExcludeDisplayInMirrorModeEnabled() {
   return display::features::IsExcludeDisplayInMirrorModeEnabled();
 }
@@ -1306,7 +1302,7 @@
   }
 
   // Refresh Rate dropdown.
-  if (has_external_display && IsListAllDisplayModesEnabled()) {
+  if (has_external_display) {
     updater.AddSearchTags(GetDisplayExternalWithRefreshSearchConcepts());
   } else {
     updater.RemoveSearchTags(GetDisplayExternalWithRefreshSearchConcepts());
@@ -1652,9 +1648,6 @@
   html_source->AddBoolean("unifiedDesktopAvailable",
                           IsUnifiedDesktopAvailable());
 
-  html_source->AddBoolean("listAllDisplayModes",
-                          IsListAllDisplayModesEnabled());
-
   html_source->AddBoolean("deviceSupportsAmbientColor",
                           DoesDeviceSupportAmbientColor());
 
diff --git a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_toolbar/customize_toolbar_handler.cc b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_toolbar/customize_toolbar_handler.cc
index 4599133..d53f182 100644
--- a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_toolbar/customize_toolbar_handler.cc
+++ b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_toolbar/customize_toolbar_handler.cc
@@ -29,6 +29,7 @@
 #include "mojo/public/cpp/bindings/remote.h"
 #include "ui/actions/actions.h"
 #include "ui/base/l10n/l10n_util.h"
+#include "ui/base/models/image_model.h"
 #include "ui/display/screen.h"
 
 namespace {
@@ -261,7 +262,7 @@
             prefs()->GetBoolean(prefs::kPinContextualTaskButton), false,
             side_panel::customize_chrome::mojom::CategoryId::kNavigation,
             GURL(webui::EncodePNGAndMakeDataURI(
-                ui::ImageModel::FromVectorIcon(vector_icons::kChatIcon,
+                ui::ImageModel::FromVectorIcon(kDockToRightSparkIcon,
                                                icon_color_id)
                     .Rasterize(&provider),
                 scale_factor)));
diff --git a/chrome/browser/ui/webui/signin/history_sync_optin_service.cc b/chrome/browser/ui/webui/signin/history_sync_optin_service.cc
index 9e0bad3c..44a1f4ed 100644
--- a/chrome/browser/ui/webui/signin/history_sync_optin_service.cc
+++ b/chrome/browser/ui/webui/signin/history_sync_optin_service.cc
@@ -285,6 +285,7 @@
         kEnterpriseManagementDisclaimerAfterSignin:
     case signin_metrics::AccessPoint::kNtpFeaturePromo:
     case signin_metrics::AccessPoint::kEnterpriseDialogAfterSigninInterception:
+    case signin_metrics::AccessPoint::kCredentialExchangeImport:
       return;
   }
 
diff --git a/chrome/browser/wallet/chrome_walletable_pass_client.cc b/chrome/browser/wallet/chrome_walletable_pass_client.cc
index 413fd00..cbd6ae67 100644
--- a/chrome/browser/wallet/chrome_walletable_pass_client.cc
+++ b/chrome/browser/wallet/chrome_walletable_pass_client.cc
@@ -71,7 +71,7 @@
 }
 
 void ChromeWalletablePassClient::ShowWalletablePassConsentBubble(
-    optimization_guide::proto::PassCategory pass_category,
+    PassCategory pass_category,
     WalletablePassBubbleResultCallback callback) {
   if (!consent_bubble_controller_) {
     consent_bubble_controller_ =
diff --git a/chrome/browser/wallet/chrome_walletable_pass_client.h b/chrome/browser/wallet/chrome_walletable_pass_client.h
index 11e620d..3c0362b 100644
--- a/chrome/browser/wallet/chrome_walletable_pass_client.h
+++ b/chrome/browser/wallet/chrome_walletable_pass_client.h
@@ -58,7 +58,7 @@
   GeoIpCountryCode GetGeoIpCountryCode() override;
 
   void ShowWalletablePassConsentBubble(
-      optimization_guide::proto::PassCategory pass_category,
+      PassCategory pass_category,
       WalletablePassBubbleResultCallback callback) override;
   void ShowWalletablePassSaveBubble(
       WalletablePass pass,
diff --git a/chrome/browser/web_applications/isolated_web_apps/commands/isolated_web_app_install_command_helper_unittest.cc b/chrome/browser/web_applications/isolated_web_apps/commands/isolated_web_app_install_command_helper_unittest.cc
index 687197a9..c39523b3 100644
--- a/chrome/browser/web_applications/isolated_web_apps/commands/isolated_web_app_install_command_helper_unittest.cc
+++ b/chrome/browser/web_applications/isolated_web_apps/commands/isolated_web_app_install_command_helper_unittest.cc
@@ -575,12 +575,14 @@
 };
 
 TEST_F(InstallIsolatedWebAppCommandHelperManifestIconsTest,
-       ManifestIconIsDownloaded) {
+       ManifestIconIsDownloadedAndUpdateManifestIsParsed) {
   IsolatedWebAppUrlInfo url_info = CreateRandomIsolatedWebAppUrlInfo();
   GURL image_url = GetImageUrl(url_info);
 
   blink::mojom::ManifestPtr manifest = CreateManifest();
   manifest->icons = {CreateImageResourceForAnyPurpose(image_url)};
+  manifest->update_manifest_url =
+      GURL("https://otters.com/update_manifest.json");
 
   auto command_helper = std::make_unique<IsolatedWebAppInstallCommandHelper>(
       url_info, GetDataRetrieverForSuccessfulDownloads(url_info));
@@ -608,6 +610,9 @@
       ValueIs(Field(
           "manifest_icons", &WebAppInstallInfo::manifest_icons,
           UnorderedElementsAre(Field(&apps::IconInfo::url, Eq(image_url))))));
+
+  EXPECT_THAT(result->iwa_update_manifest_url,
+              Optional(Eq(manifest->update_manifest_url.value())));
 }
 
 TEST_F(InstallIsolatedWebAppCommandHelperManifestIconsTest,
diff --git a/chrome/browser/web_applications/isolated_web_apps/isolation_data.cc b/chrome/browser/web_applications/isolated_web_apps/isolation_data.cc
index 58067cb..db31eb0d 100644
--- a/chrome/browser/web_applications/isolated_web_apps/isolation_data.cc
+++ b/chrome/browser/web_applications/isolated_web_apps/isolation_data.cc
@@ -216,9 +216,6 @@
 
 IsolationData::Builder& IsolationData::Builder::SetUpdateManifestUrl(
     GURL update_manifest_url) & {
-  CHECK(location_.dev_mode())
-      << "This field is supposed to be used only with dev mode installs via "
-         "chrome://web-app-internals.";
   CHECK(update_manifest_url.is_valid(), base::NotFatalUntil::M138);
   update_manifest_url_ = std::move(update_manifest_url);
   return *this;
@@ -226,9 +223,6 @@
 
 IsolationData::Builder&& IsolationData::Builder::SetUpdateManifestUrl(
     GURL update_manifest_url) && {
-  CHECK(location_.dev_mode())
-      << "This field is supposed to be used only with dev mode installs via "
-         "chrome://web-app-internals.";
   CHECK(update_manifest_url.is_valid(), base::NotFatalUntil::M138);
   update_manifest_url_ = std::move(update_manifest_url);
   return std::move(*this);
diff --git a/chrome/browser/web_applications/isolated_web_apps/isolation_data.h b/chrome/browser/web_applications/isolated_web_apps/isolation_data.h
index 5079bd7..29107d86 100644
--- a/chrome/browser/web_applications/isolated_web_apps/isolation_data.h
+++ b/chrome/browser/web_applications/isolated_web_apps/isolation_data.h
@@ -191,8 +191,6 @@
     Builder&& SetIntegrityBlockData(
         IsolatedWebAppIntegrityBlockData integrity_block_data) &&;
 
-    // Update manifest is supposed to be set only for selected dev-mode
-    // installs. Will `CHECK`-fail if applied to a prod-mode location.
     Builder& SetUpdateManifestUrl(GURL update_manifest_url) &;
     Builder&& SetUpdateManifestUrl(GURL update_manifest_url) &&;
 
diff --git a/chrome/browser/web_applications/isolated_web_apps/test/isolated_web_app_builder.cc b/chrome/browser/web_applications/isolated_web_apps/test/isolated_web_app_builder.cc
index 96e8ba2b..52abcba 100644
--- a/chrome/browser/web_applications/isolated_web_apps/test/isolated_web_app_builder.cc
+++ b/chrome/browser/web_applications/isolated_web_apps/test/isolated_web_app_builder.cc
@@ -295,6 +295,12 @@
   return *this;
 }
 
+ManifestBuilder& ManifestBuilder::SetUpdateManifestUrl(
+    const GURL& update_manifest_url) {
+  update_manifest_url_ = update_manifest_url;
+  return *this;
+}
+
 ManifestBuilder& ManifestBuilder::SetDisplayMode(
     blink::mojom::DisplayMode display_mode) {
   display_mode_ = display_mode;
@@ -363,6 +369,10 @@
   return start_url_;
 }
 
+const std::optional<GURL>& ManifestBuilder::update_manifest_url() const {
+  return update_manifest_url_;
+}
+
 const std::vector<ManifestBuilder::IconMetadata>& ManifestBuilder::icons()
     const {
   return icons_;
@@ -383,6 +393,9 @@
                   .Set("display_override",
                        base::ToValueList(display_mode_override_,
                                          &blink::DisplayModeToString));
+  if (update_manifest_url_) {
+    json.Set("update_manifest_url", update_manifest_url_->spec());
+  }
 
   if (launch_handler_client_mode_) {
     json.SetByDottedPath("launch_handler.client_mode", [&] {
@@ -471,6 +484,9 @@
   manifest->id = base_url;
   manifest->scope = base_url;
   manifest->start_url = base_url.Resolve(start_url_);
+  if (update_manifest_url_) {
+    manifest->update_manifest_url = update_manifest_url_;
+  }
   manifest->display = display_mode_;
   manifest->display_override = display_mode_override_;
   if (launch_handler_client_mode_) {
diff --git a/chrome/browser/web_applications/isolated_web_apps/test/isolated_web_app_builder.h b/chrome/browser/web_applications/isolated_web_apps/test/isolated_web_app_builder.h
index 6fc48b11..243878f 100644
--- a/chrome/browser/web_applications/isolated_web_apps/test/isolated_web_app_builder.h
+++ b/chrome/browser/web_applications/isolated_web_apps/test/isolated_web_app_builder.h
@@ -106,6 +106,7 @@
   ManifestBuilder& SetName(std::string_view name);
   ManifestBuilder& SetVersion(std::string_view version);
   ManifestBuilder& SetStartUrl(std::string_view start_url);
+  ManifestBuilder& SetUpdateManifestUrl(const GURL& update_manifest_url);
   ManifestBuilder& SetDisplayMode(blink::mojom::DisplayMode display_mode);
   ManifestBuilder& SetLaunchHandlerClientMode(
       ClientMode launch_handler_client_mode);
@@ -134,6 +135,7 @@
   ManifestBuilder& AddBorderlessUrlPattern(blink::SafeUrlPattern pattern);
 
   const std::string& start_url() const;
+  const std::optional<GURL>& update_manifest_url() const;
   const std::vector<IconMetadata>& icons() const;
   const IwaVersion& version() const;
 
@@ -145,6 +147,7 @@
   std::string name_;
   IwaVersion version_;
   std::string start_url_;
+  std::optional<GURL> update_manifest_url_;
   blink::mojom::DisplayMode display_mode_ =
       blink::mojom::DisplayMode::kStandalone;
   std::vector<blink::mojom::DisplayMode> display_mode_override_;
diff --git a/chrome/browser/web_applications/jobs/manifest_to_web_app_install_info_job.cc b/chrome/browser/web_applications/jobs/manifest_to_web_app_install_info_job.cc
index 00f2ee5..ebe0ee3c 100644
--- a/chrome/browser/web_applications/jobs/manifest_to_web_app_install_info_job.cc
+++ b/chrome/browser/web_applications/jobs/manifest_to_web_app_install_info_job.cc
@@ -689,6 +689,12 @@
     install_info().manifest_url = manifest_->manifest_url;
   }
 
+  if (manifest_->update_manifest_url &&
+      manifest_->update_manifest_url->is_valid()) {
+    install_info().iwa_update_manifest_url =
+        manifest_->update_manifest_url.value();
+  }
+
   install_info().launch_handler = manifest_->launch_handler;
   if (manifest_->description.has_value()) {
     install_info().description = manifest_->description.value();
diff --git a/chrome/browser/web_applications/web_app_install_finalizer.cc b/chrome/browser/web_applications/web_app_install_finalizer.cc
index 9dd8d73..85722487f 100644
--- a/chrome/browser/web_applications/web_app_install_finalizer.cc
+++ b/chrome/browser/web_applications/web_app_install_finalizer.cc
@@ -398,6 +398,7 @@
     UpdateIsolationDataAndResetPendingUpdateInfo(
         web_app.get(), options.iwa_options->location,
         web_app_info.isolated_web_app_version(),
+        web_app_info.iwa_update_manifest_url,
         options.iwa_options->integrity_block_data);
 
     HostContentSettingsMap* const host_content_settings_map =
@@ -517,12 +518,18 @@
     WebApp* web_app,
     const IsolatedWebAppStorageLocation& location,
     const IwaVersion& version,
+    const std::optional<GURL>& iwa_update_manifest_url,
     std::optional<IsolatedWebAppIntegrityBlockData> integrity_block_data) {
   IsolationData::Builder builder(location, version);
+
   if (web_app->isolation_data()) {
     builder.PersistFieldsForUpdate(*web_app->isolation_data());
   }
 
+  if (iwa_update_manifest_url) {
+    builder.SetUpdateManifestUrl(*iwa_update_manifest_url);
+  }
+
   if (integrity_block_data) {
     builder.SetIntegrityBlockData(std::move(*integrity_block_data));
   }
@@ -567,7 +574,7 @@
              pending_update_info->version);
     UpdateIsolationDataAndResetPendingUpdateInfo(
         web_app.get(), pending_update_info->location,
-        pending_update_info->version,
+        pending_update_info->version, web_app_info.iwa_update_manifest_url,
         pending_update_info->integrity_block_data);
   }
 
diff --git a/chrome/browser/web_applications/web_app_install_finalizer.h b/chrome/browser/web_applications/web_app_install_finalizer.h
index 2f0e2ff..c7919f6 100644
--- a/chrome/browser/web_applications/web_app_install_finalizer.h
+++ b/chrome/browser/web_applications/web_app_install_finalizer.h
@@ -151,6 +151,7 @@
       WebApp* web_app,
       const IsolatedWebAppStorageLocation& location,
       const IwaVersion& version,
+      const std::optional<GURL>& iwa_update_manifest_url,
       std::optional<IsolatedWebAppIntegrityBlockData> integrity_block_data);
 
   void OnOriginAssociationValidatedForUpdate(
diff --git a/chrome/browser/web_applications/web_app_install_info.h b/chrome/browser/web_applications/web_app_install_info.h
index 4a0009d..ff991a0 100644
--- a/chrome/browser/web_applications/web_app_install_info.h
+++ b/chrome/browser/web_applications/web_app_install_info.h
@@ -515,6 +515,11 @@
   // background document installs.
   std::optional<GURL> installed_by;
 
+  // The URL of the Isolated Web App's update manifest. Used to detect updates.
+  // Note that the `update_manifest_url` specified in the
+  // IsolatedWebAppInstallForceList policy takes precedence over this value.
+  std::optional<GURL> iwa_update_manifest_url;
+
  private:
   // Used this method in Clone() method. Use Clone() to deep copy explicitly.
   WebAppInstallInfo(const WebAppInstallInfo& other);
diff --git a/chrome/browser/webauthn/android/cable_module_android_unittest.cc b/chrome/browser/webauthn/android/cable_module_android_unittest.cc
index d5da2595..86e0bc68 100644
--- a/chrome/browser/webauthn/android/cable_module_android_unittest.cc
+++ b/chrome/browser/webauthn/android/cable_module_android_unittest.cc
@@ -39,7 +39,7 @@
     0x20, 0x36, 0x61, 0x19, 0x03, 0xe7, 0xf5,
 };
 
-}
+}  // namespace
 
 TEST(CableModuleAndroidTest, PaaskInfoFromCBOR) {
   // This CBOR was captured from Play Services.
diff --git a/chrome/browser/webnn/win_app_runtime_installer.cc b/chrome/browser/webnn/win_app_runtime_installer.cc
index dd948fd..dc9d04c8 100644
--- a/chrome/browser/webnn/win_app_runtime_installer.cc
+++ b/chrome/browser/webnn/win_app_runtime_installer.cc
@@ -14,7 +14,6 @@
 #include <string_view>
 
 #include "base/files/file_path.h"
-#include "base/functional/concurrent_callbacks.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/path_service.h"
 #include "base/strings/utf_string_conversions.h"
@@ -100,14 +99,14 @@
                          kWinAppRuntimePackageMinVersionString);
 }
 
-// Called after all `AppInstallItems` reach the complete state (succeeded,
+// Called after `AppInstallItem` reaches the complete state (succeeded,
 // canceled or failed). `app_install_manager` is kept alive by this function to
 // ensure the registered status change callbacks will be invoked.
 void OnInstallationCompleted(
     Microsoft::WRL::ComPtr<
         abi_install::IAppInstallManager> /*app_install_manager*/,
-    std::vector<bool> results) {
-  if (std::ranges::any_of(results, [](bool success) { return !success; })) {
+    bool success) {
+  if (!success) {
     return;
   }
 
@@ -119,39 +118,41 @@
   UpdatePrefs(dependency_id);
 }
 
-// Called after `StartProductInstallAsync()` completes. Adds a callback for each
-// `AppInstallItem` to track their status change.
+// Called after `StartProductInstallAsync()` completes. Adds a callback for
+// `AppInstallItem` to track the installation status.
 void OnInstallationStarted(
     Microsoft::WRL::ComPtr<abi_install::IAppInstallManager> app_install_manager,
     Microsoft::WRL::ComPtr<AppInstallItems> items) {
   uint32_t count = 0;
   HRESULT hr = items->get_Size(&count);
   CHECK_EQ(hr, S_OK);
-  if (count == 0) {
-    RecordInstallState(
-        WinAppRuntimeInstallStateUma::kInstallationFailedToStart);
-    return;
-  }
-
-  // Wait for all the app install items to complete.
-  base::ConcurrentCallbacks<bool> concurrent_callbacks;
 
   for (uint32_t i = 0; i < count; ++i) {
     Microsoft::WRL::ComPtr<abi_install::IAppInstallItem> item;
     hr = items->GetAt(i, &item);
     CHECK_EQ(hr, S_OK);
 
+    // Checks if the install item is the package that we want.
+    base::win::ScopedHString product_id(nullptr);
+    hr = item->get_ProductId(
+        base::win::ScopedHString::Receiver(product_id).get());
+    CHECK_EQ(hr, S_OK);
+    if (product_id.Get() != kWinAppRuntimeProductId) {
+      continue;
+    }
+
     // `token` receives the value assigned by `add_StatusChanged()` below.
     // It is used to unregister the status change event handler.
     auto token = std::make_unique<EventRegistrationToken>();
     auto* token_ptr = token.get();
 
+    auto callback = base::BindPostTaskToCurrentDefault(base::BindOnce(
+        &OnInstallationCompleted, std::move(app_install_manager)));
+
     // Register the status change event handler for `item`.
     item->add_StatusChanged(
         Microsoft::WRL::Callback<AppInstallStatusChangedHandler>(
-            [token = std::move(token),
-             callback = base::BindPostTaskToCurrentDefault(
-                 concurrent_callbacks.CreateCallback())](
+            [token = std::move(token), callback = std::move(callback)](
                 abi_install::IAppInstallItem* item,
                 IInspectable* args) mutable {
               Microsoft::WRL::ComPtr<abi_install::IAppInstallStatus> status;
@@ -216,11 +217,11 @@
             })
             .Get(),
         token_ptr);
+
+    return;
   }
 
-  std::move(concurrent_callbacks)
-      .Done(base::BindOnce(&OnInstallationCompleted,
-                           std::move(app_install_manager)));
+  RecordInstallState(WinAppRuntimeInstallStateUma::kInstallationFailedToStart);
 }
 
 // Activates and returns the IAppInstallManager instance.
diff --git a/chrome/browser_exposed_mojom_targets.gni b/chrome/browser_exposed_mojom_targets.gni
index fd8c478d..72982a97 100644
--- a/chrome/browser_exposed_mojom_targets.gni
+++ b/chrome/browser_exposed_mojom_targets.gni
@@ -279,7 +279,7 @@
   "//services/viz/privileged/mojom/compositing:compositing",
   "//services/viz/privileged/mojom/gl:gl",
   "//services/viz/public/mojom:mojom",
-  "//services/viz/public/mojom:shared_image_format",
+  "//services/viz/public/mojom/compositing:shared_image_format",
   "//services/viz/public/mojom/compositing/internal:singleplanar_format",
   "//services/webnn/public/mojom:mojom",
   "//skia/public/mojom:mojom",
diff --git a/chrome/build/android-arm32.pgo.txt b/chrome/build/android-arm32.pgo.txt
index d9686d8..6ca09c6 100644
--- a/chrome/build/android-arm32.pgo.txt
+++ b/chrome/build/android-arm32.pgo.txt
@@ -1 +1 @@
-chrome-android32-main-1764611977-c8f138cb929c9626f98c1da00509266cb9630761-e09c7a38809a46a587ad2546eda65719046870de.profdata
+chrome-android32-main-1764655003-4baa9886ff423b0a8a017c35eb5e587a59d822b4-2482dcff2b694ed29dbf3277bc9a373bf7f6b18e.profdata
diff --git a/chrome/build/android-desktop-x64.pgo.txt b/chrome/build/android-desktop-x64.pgo.txt
index 41c94e2..f47b5fb 100644
--- a/chrome/build/android-desktop-x64.pgo.txt
+++ b/chrome/build/android-desktop-x64.pgo.txt
@@ -1 +1 @@
-chrome-android-desktop-x64-main-1764611977-0292fe4c7201d54e0c3278c4415860e69f2f05cd-e09c7a38809a46a587ad2546eda65719046870de.profdata
+chrome-android-desktop-x64-main-1764655003-68991c8a7b8233cf3f58fc99b0f9ee40aa7d704b-2482dcff2b694ed29dbf3277bc9a373bf7f6b18e.profdata
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt
index 74f83ec..55a4401 100644
--- a/chrome/build/linux.pgo.txt
+++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@
-chrome-linux-main-1764546996-c32475c89821d497cd549de91d599dacad6d815b-7997aa0aed2bf595d0c7bd4f2c00bc620dd3925a.profdata
+chrome-linux-main-1764655003-f94f0231e616ed0b188e1485d1c1feaa4a443b51-2482dcff2b694ed29dbf3277bc9a373bf7f6b18e.profdata
diff --git a/chrome/build/mac-arm.pgo.txt b/chrome/build/mac-arm.pgo.txt
index c4318f6..ef3bd39 100644
--- a/chrome/build/mac-arm.pgo.txt
+++ b/chrome/build/mac-arm.pgo.txt
@@ -1 +1 @@
-chrome-mac-arm-main-1764611977-08697ce89574654ecefe2e6b3c9d582425c32e8a-e09c7a38809a46a587ad2546eda65719046870de.profdata
+chrome-mac-arm-main-1764669011-8d66c96262b8e7e893cd43fbb1ca3557acae9d37-35842ffebde86a760310e7f44270882a1f14cd72.profdata
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt
index 540c12f..c2c111c 100644
--- a/chrome/build/mac.pgo.txt
+++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@
-chrome-mac-main-1764590368-2bf7527a6c512f61f1fa53d5d01ea76bb598ce1f-817b6ec109ffba8c6bd0e0d19a687c89e13d3691.profdata
+chrome-mac-main-1764655003-3ca0b6827e08d1ce73d50147b9f450079c6ed072-2482dcff2b694ed29dbf3277bc9a373bf7f6b18e.profdata
diff --git a/chrome/build/win-arm64.pgo.txt b/chrome/build/win-arm64.pgo.txt
index 3c10afc6..c673948 100644
--- a/chrome/build/win-arm64.pgo.txt
+++ b/chrome/build/win-arm64.pgo.txt
@@ -1 +1 @@
-chrome-win-arm64-main-1764590368-78500067987a1e4e2b031df1b754449af1f3aaa8-817b6ec109ffba8c6bd0e0d19a687c89e13d3691.profdata
+chrome-win-arm64-main-1764655003-dce61f5fd564e11bb312a479b305d6fa9b88aa7d-2482dcff2b694ed29dbf3277bc9a373bf7f6b18e.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index 2aa29ba..66b7a1a 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1764579267-35226c3059f29b6a2c67542d75e5a1f471be4d55-21bed0d033f19cab0365fb933d674ec922a68334.profdata
+chrome-win32-main-1764644297-ccb2736363d2668ca09ea06521ffec60dd222545-351cbac9606375bf4c111da64487f5eccbaf94e3.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index 4c434eb..58299aaa 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1764579267-94f0c5bfe01eeec6e042852d78ea7e604575de6a-21bed0d033f19cab0365fb933d674ec922a68334.profdata
+chrome-win64-main-1764644297-d683933b16407beee7cd9d635df9b76c3ccba494-351cbac9606375bf4c111da64487f5eccbaf94e3.profdata
diff --git a/chrome/common/chrome_features.cc b/chrome/common/chrome_features.cc
index f1dec7d..14d76f0f 100644
--- a/chrome/common/chrome_features.cc
+++ b/chrome/common/chrome_features.cc
@@ -454,9 +454,6 @@
     &kGlicActorMoveBeforeClick, "glic-actor-move-before-click-delay",
     base::Milliseconds(5)};
 
-// Kill switch for fix to UninterruptActorTask.
-BASE_FEATURE(kGlicActorUninterruptDuringAct, base::FEATURE_ENABLED_BY_DEFAULT);
-
 // Controls whether the Glic's act-on-web capability is checked for managed
 // trial clients.
 BASE_FEATURE(kGlicActOnWebCapabilityForManagedTrials,
@@ -1028,6 +1025,11 @@
 BASE_FEATURE(kHappinessTrackingSurveysForDesktopNtpModules,
              base::FEATURE_DISABLED_BY_DEFAULT);
 
+// Enables or disables the Happiness Tracking System for Desktop Chrome
+// Next Panel.
+BASE_FEATURE(kHappinessTrackingSurveysForDesktopNextPanel,
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 // Enables or disables the Happiness Tracking System for History Embeddings.
 BASE_FEATURE(kHappinessTrackingSurveysForHistoryEmbeddings,
              base::FEATURE_DISABLED_BY_DEFAULT);
diff --git a/chrome/common/chrome_features.h b/chrome/common/chrome_features.h
index da2c9b7..d2f17268 100644
--- a/chrome/common/chrome_features.h
+++ b/chrome/common/chrome_features.h
@@ -339,9 +339,6 @@
 extern const base::FeatureParam<base::TimeDelta> kGlicActorMoveBeforeClickDelay;
 
 COMPONENT_EXPORT(CHROME_FEATURES)
-BASE_DECLARE_FEATURE(kGlicActorUninterruptDuringAct);
-
-COMPONENT_EXPORT(CHROME_FEATURES)
 BASE_DECLARE_FEATURE(kGlicActOnWebCapabilityForManagedTrials);
 
 COMPONENT_EXPORT(CHROME_FEATURES)
@@ -727,6 +724,9 @@
 BASE_DECLARE_FEATURE(kHappinessTrackingSurveysForDesktopNtpModules);
 
 COMPONENT_EXPORT(CHROME_FEATURES)
+BASE_DECLARE_FEATURE(kHappinessTrackingSurveysForDesktopNextPanel);
+
+COMPONENT_EXPORT(CHROME_FEATURES)
 BASE_DECLARE_FEATURE(kHappinessTrackingSurveysForHistoryEmbeddings);
 COMPONENT_EXPORT(CHROME_FEATURES)
 extern const base::FeatureParam<base::TimeDelta>
diff --git a/chrome/common/extensions/api/autofill_private.idl b/chrome/common/extensions/api/autofill_private.idl
index 8be82055..66bcdcd 100644
--- a/chrome/common/extensions/api/autofill_private.idl
+++ b/chrome/common/extensions/api/autofill_private.idl
@@ -473,7 +473,7 @@
   callback GetAutofillAiOptInStatusCallback = void(boolean optedIn);
   callback SetAutofillAiOptInStatusCallback = void(boolean success);
   callback GetWalletablePassDetectionOptInStatusCallback = void(boolean optedIn);
-  callback SetWalletablePassDetectionOptInStatusCallback = void();
+  callback SetWalletablePassDetectionOptInStatusCallback = void(boolean success);
   callback GetPayOverTimeIssuerListCallback = void(PayOverTimeIssuerEntry[] entries);
 
   interface Functions {
diff --git a/chrome/common/pref_names.h b/chrome/common/pref_names.h
index 7b13ee2..595d550 100644
--- a/chrome/common/pref_names.h
+++ b/chrome/common/pref_names.h
@@ -1325,6 +1325,10 @@
 inline constexpr char kPinContextualTaskButton[] =
     "browser.pin_contextual_task_button";
 
+// An integer pref that tracks how many times the next panel has been opened.
+inline constexpr char kContextualTasksNextPanelOpenCount[] =
+    "contextual_tasks.next_panel_open_count";
+
 // A boolean pref set to true if links/tabs can be dragged to create split tabs.
 inline constexpr char kSplitViewDragAndDropEnabled[] =
     "browser.split_view_drag_and_drop_enabled";
diff --git a/chrome/services/file_util/BUILD.gn b/chrome/services/file_util/BUILD.gn
index 76be56f..17766d6 100644
--- a/chrome/services/file_util/BUILD.gn
+++ b/chrome/services/file_util/BUILD.gn
@@ -51,6 +51,8 @@
 
   if (safe_browsing_mode == 1) {
     sources += [
+      "regular_archive_analysis_delegate.cc",
+      "regular_archive_analysis_delegate.h",
       "safe_archive_analyzer.cc",
       "safe_archive_analyzer.h",
     ]
diff --git a/chrome/services/file_util/regular_archive_analysis_delegate.cc b/chrome/services/file_util/regular_archive_analysis_delegate.cc
new file mode 100644
index 0000000..a329b16
--- /dev/null
+++ b/chrome/services/file_util/regular_archive_analysis_delegate.cc
@@ -0,0 +1,98 @@
+// 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/services/file_util/regular_archive_analysis_delegate.h"
+
+#include <optional>
+
+#include "base/files/file.h"
+#include "chrome/utility/safe_browsing/zip_writer_delegate.h"
+#include "third_party/zlib/google/zip_reader.h"
+
+namespace safe_browsing {
+
+namespace {
+
+class FileZipReaderDelegate : public zip::ReaderDelegate {
+ public:
+  explicit FileZipReaderDelegate(base::File file) : file_(std::move(file)) {}
+  ~FileZipReaderDelegate() override = default;
+
+  // zip::ReaderDelegate:
+  int64_t ReadBytes(base::span<uint8_t> data) override {
+    std::optional<size_t> bytes_read = file_.ReadAtCurrentPos(data);
+    return bytes_read.has_value() ? static_cast<int64_t>(*bytes_read) : -1;
+  }
+
+  bool Seek(int64_t offset) override {
+    return file_.Seek(base::File::FROM_BEGIN, offset) == offset;
+  }
+
+  int64_t Tell() override {
+    return file_.GetLength() < 0 ? -1 : file_.Seek(base::File::FROM_CURRENT, 0);
+  }
+
+  int64_t GetLength() override { return file_.GetLength(); }
+
+ private:
+  base::File file_;
+};
+
+class ZipWriterDelegate : public zip::FileWriterDelegate,
+                          public SafeBrowsingZipWriterDelegate {
+ public:
+  explicit ZipWriterDelegate(base::File file)
+      : zip::FileWriterDelegate(std::move(file)) {}
+  ZipWriterDelegate(const ZipWriterDelegate&) = delete;
+  ZipWriterDelegate& operator=(const ZipWriterDelegate&) = delete;
+
+  ~ZipWriterDelegate() override = default;
+
+  // zip::FileWriterDelegate overrides:
+  bool PrepareOutput() override {
+    bool success = zip::FileWriterDelegate::PrepareOutput();
+    has_disk_error_ |= !success;
+    return success;
+  }
+
+  bool WriteBytes(const char* data, int num_bytes) override {
+    bool success = zip::FileWriterDelegate::WriteBytes(data, num_bytes);
+    has_disk_error_ |= !success;
+    return success;
+  }
+
+  // SafeBrowsingZipWriterDelegate overrides:
+  bool has_disk_error() const override { return has_disk_error_; }
+
+  int64_t file_length() const override {
+    return zip::FileWriterDelegate::file_length_;
+  }
+
+ private:
+  bool has_disk_error_ = false;
+};
+
+}  // namespace
+
+RegularArchiveAnalysisDelegate::RegularArchiveAnalysisDelegate() = default;
+
+RegularArchiveAnalysisDelegate::~RegularArchiveAnalysisDelegate() = default;
+
+std::unique_ptr<zip::ReaderDelegate>
+RegularArchiveAnalysisDelegate::CreateZipReaderDelegate(base::File file) {
+  return std::make_unique<FileZipReaderDelegate>(std::move(file));
+}
+
+std::unique_ptr<SafeBrowsingZipWriterDelegate>
+RegularArchiveAnalysisDelegate::CreateZipWriterDelegate(base::File file) {
+  return std::make_unique<ZipWriterDelegate>(std::move(file));
+}
+
+std::unique_ptr<ArchiveAnalysisDelegate>
+RegularArchiveAnalysisDelegate::CreateNestedDelegate(
+    base::File extracted_file) {
+  return std::make_unique<RegularArchiveAnalysisDelegate>();
+}
+
+}  // namespace safe_browsing
diff --git a/chrome/services/file_util/regular_archive_analysis_delegate.h b/chrome/services/file_util/regular_archive_analysis_delegate.h
new file mode 100644
index 0000000..30b7f951
--- /dev/null
+++ b/chrome/services/file_util/regular_archive_analysis_delegate.h
@@ -0,0 +1,28 @@
+// 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_SERVICES_FILE_UTIL_REGULAR_ARCHIVE_ANALYSIS_DELEGATE_H_
+#define CHROME_SERVICES_FILE_UTIL_REGULAR_ARCHIVE_ANALYSIS_DELEGATE_H_
+
+#include "chrome/utility/safe_browsing/archive_analysis_delegate.h"
+
+namespace safe_browsing {
+
+class RegularArchiveAnalysisDelegate : public ArchiveAnalysisDelegate {
+ public:
+  RegularArchiveAnalysisDelegate();
+  ~RegularArchiveAnalysisDelegate() override;
+
+  // ArchiveAnalysisDelegate:
+  std::unique_ptr<zip::ReaderDelegate> CreateZipReaderDelegate(
+      base::File file) override;
+  std::unique_ptr<SafeBrowsingZipWriterDelegate> CreateZipWriterDelegate(
+      base::File file) override;
+  std::unique_ptr<ArchiveAnalysisDelegate> CreateNestedDelegate(
+      base::File extracted_file) override;
+};
+
+}  // namespace safe_browsing
+
+#endif  // CHROME_SERVICES_FILE_UTIL_REGULAR_ARCHIVE_ANALYSIS_DELEGATE_H_
diff --git a/chrome/services/file_util/safe_archive_analyzer.cc b/chrome/services/file_util/safe_archive_analyzer.cc
index f5a860c8..a8ba6c7 100644
--- a/chrome/services/file_util/safe_archive_analyzer.cc
+++ b/chrome/services/file_util/safe_archive_analyzer.cc
@@ -7,6 +7,8 @@
 #include "base/functional/callback.h"
 #include "build/build_config.h"
 #include "chrome/common/safe_browsing/archive_analyzer_results.h"
+#include "chrome/services/file_util/regular_archive_analysis_delegate.h"
+#include "chrome/utility/safe_browsing/archive_analysis_delegate.h"
 
 namespace {
 // The maximum duration of analysis, in milliseconds.
@@ -35,6 +37,8 @@
                               weak_factory_.GetWeakPtr());
   timeout_timer_.Start(FROM_HERE, kArchiveAnalysisTimeout, this,
                        &SafeArchiveAnalyzer::Timeout);
+  zip_analyzer_.SetAnalysisDelegate(
+      std::make_unique<safe_browsing::RegularArchiveAnalysisDelegate>());
   zip_analyzer_.Analyze(std::move(zip_file), base::FilePath(), password,
                         std::move(analysis_finished_callback),
                         std::move(temp_file_getter_callback), &results_);
@@ -58,6 +62,8 @@
                               weak_factory_.GetWeakPtr());
   timeout_timer_.Start(FROM_HERE, kArchiveAnalysisTimeout, this,
                        &SafeArchiveAnalyzer::Timeout);
+  dmg_analyzer_.SetAnalysisDelegate(
+      std::make_unique<safe_browsing::RegularArchiveAnalysisDelegate>());
   // TODO(crbug.com/40923881): Update DMG analyzer to use passwords and provide
   // the password here.
   dmg_analyzer_.Analyze(std::move(dmg_file), base::FilePath(),
@@ -87,6 +93,8 @@
                               weak_factory_.GetWeakPtr());
   timeout_timer_.Start(FROM_HERE, kArchiveAnalysisTimeout, this,
                        &SafeArchiveAnalyzer::Timeout);
+  rar_analyzer_.SetAnalysisDelegate(
+      std::make_unique<safe_browsing::RegularArchiveAnalysisDelegate>());
   rar_analyzer_.Analyze(std::move(rar_file), base::FilePath(),
                         /*password=*/password,
                         std::move(analysis_finished_callback),
@@ -114,6 +122,8 @@
                        &SafeArchiveAnalyzer::Timeout);
   // TODO(crbug.com/40923881): Update 7Z analyzer to use passwords and provide
   // the password here.
+  seven_zip_analyzer_.SetAnalysisDelegate(
+      std::make_unique<safe_browsing::RegularArchiveAnalysisDelegate>());
   seven_zip_analyzer_.Analyze(std::move(seven_zip_file), base::FilePath(),
                               /*password=*/std::nullopt,
                               std::move(analysis_finished_callback),
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index f5a7ddd2..0884b528 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -1908,7 +1908,6 @@
       "//chrome/browser/fast_checkout:test_support",
       "//chrome/browser/flags:flags_android",
       "//chrome/browser/history",
-      "//chrome/browser/ip_protection:browser_tests",
       "//chrome/browser/keyboard_accessory/android:public",
       "//chrome/browser/metrics:test_support",
       "//chrome/browser/optimization_guide",
@@ -2451,7 +2450,6 @@
       "//chrome/browser/image_fetcher",
       "//chrome/browser/importer",
       "//chrome/browser/importer:browser_tests",
-      "//chrome/browser/ip_protection:browser_tests",
       "//chrome/browser/legion:browser_tests",
       "//chrome/browser/lens:lens_tab_contextualization_test",
       "//chrome/browser/lifetime:termination_notification",
@@ -3354,6 +3352,7 @@
       "../browser/local_network_access/local_network_access_browsertest.cc",
       "../browser/local_network_access/local_network_access_browsertest_base.cc",
       "../browser/local_network_access/local_network_access_browsertest_base.h",
+      "../browser/local_network_access/local_network_access_counters_browsertest.cc",
       "../browser/local_network_access/local_network_access_deprecation_browsertest.cc",
       "../browser/local_network_access/local_network_access_iframe_browsertest.cc",
       "../browser/local_network_access/local_network_access_policies_browsertest.cc",
@@ -3448,7 +3447,6 @@
       "../browser/net/network_quality_tracker_browsertest.cc",
       "../browser/net/network_request_metrics_browsertest.cc",
       "../browser/net/nss_context_chromeos_browsertest.cc",
-      "../browser/net/private_network_access_browsertest.cc",
       "../browser/net/profile_network_context_service_browsertest.cc",
       "../browser/net/proxy_browsertest.cc",
       "../browser/net/storage_test_utils.cc",
@@ -5017,6 +5015,7 @@
         "//components/media_router/browser:test_support",
         "//components/media_router/common:test_support",
         "//components/optimization_guide/optimization_guide_internals/webui",
+        "//components/wallet/core/browser",
         "//components/wallet/core/common:features",
         "//components/wallet/core/common:prefs",
         "//components/web_package",
@@ -6959,7 +6958,6 @@
     "//chrome/browser/file_system_access:unit_tests",
     "//chrome/browser/first_party_sets",
     "//chrome/browser/google:unit_tests",
-    "//chrome/browser/ip_protection:unit_tests",
     "//chrome/browser/media:unittests",
     "//chrome/browser/media/prefs",
     "//chrome/browser/media/prefs:unit_tests",
@@ -7161,7 +7159,6 @@
     "//components/infobars/content",
     "//components/input",
     "//components/invalidation",
-    "//components/ip_protection/common:masked_domain_list_manager",
     "//components/language/core/browser",
     "//components/language_detection/core",
     "//components/lens",
diff --git a/chrome/test/DEPS b/chrome/test/DEPS
index ff438c5..b3ed8536 100644
--- a/chrome/test/DEPS
+++ b/chrome/test/DEPS
@@ -49,7 +49,6 @@
   "+components/infobars/content",
   "+components/infobars/core",
   "+components/input",
-  "+components/ip_protection",
   "+components/javascript_dialogs",
   "+components/keep_alive_registry",
   "+components/keyed_service/content",
diff --git a/chrome/test/data/actor/page_stability.html b/chrome/test/data/actor/page_stability.html
index a120c2d..c1566090 100644
--- a/chrome/test/data/actor/page_stability.html
+++ b/chrome/test/data/actor/page_stability.html
@@ -63,8 +63,10 @@
           doBusyWork(10000);
         };
 
+        let paint_count = 0;
         paint_button.onclick = () => {
-          output.innerText = 'PAINT';
+          paint_count++;
+          output.innerText = 'PAINT ' + paint_count;
         };
 
         // Add a handler for new same-document navigations. This will cause a
diff --git a/chrome/test/data/extensions/api_test/autofill_private/test.js b/chrome/test/data/extensions/api_test/autofill_private/test.js
index 29fe7ed0..4729b2fa 100644
--- a/chrome/test/data/extensions/api_test/autofill_private/test.js
+++ b/chrome/test/data/extensions/api_test/autofill_private/test.js
@@ -1417,12 +1417,23 @@
   },
 
   async function optIntoWalletablePassDetection() {
-    await chrome.autofillPrivate.setWalletablePassDetectionOptInStatus(true);
+    chrome.test.assertTrue(
+        await chrome.autofillPrivate.setWalletablePassDetectionOptInStatus(
+            true));
     chrome.test.succeed();
   },
 
   async function optOutOfWalletablePassDetection() {
-    await chrome.autofillPrivate.setWalletablePassDetectionOptInStatus(false);
+    chrome.test.assertTrue(
+        await chrome.autofillPrivate.setWalletablePassDetectionOptInStatus(
+            false));
+    chrome.test.succeed();
+  },
+
+  async function optIntoWalletablePassDetectionExpectingFailure() {
+    chrome.test.assertFalse(
+        await chrome.autofillPrivate.setWalletablePassDetectionOptInStatus(
+            true));
     chrome.test.succeed();
   },
 
@@ -1493,7 +1504,7 @@
   'getEntityInstanceByGuid': ['getEntityInstanceByGuid'],
   'getWritableEntityTypes': ['getWritableEntityTypes'],
   'verifyWritableEntityTypesDoesNotIncludeReadOnlyTypes':
-    ['verifyWritableEntityTypesDoesNotIncludeReadOnlyTypes'],
+      ['verifyWritableEntityTypesDoesNotIncludeReadOnlyTypes'],
   'getAllAttributeTypesForEntityTypeName':
       ['getAllAttributeTypesForEntityTypeName'],
   'testExpectedLabelsAreGenerated': ['testExpectedLabelsAreGenerated'],
@@ -1504,6 +1515,8 @@
   'optOutOfAutofillAi': ['optOutOfAutofillAi'],
   'verifyUserOptedIntoAutofillAi': ['verifyUserOptedIntoAutofillAi'],
   'verifyUserOptedOutOfAutofillAi': ['verifyUserOptedOutOfAutofillAi'],
+  'optIntoWalletablePassDetectionExpectingFailure':
+      ['optIntoWalletablePassDetectionExpectingFailure'],
 };
 
 var testConfig = window.location.search.substring(1);
diff --git a/chrome/test/data/web_apps/simple_isolated_app/.well-known/manifest.webmanifest b/chrome/test/data/web_apps/simple_isolated_app/.well-known/manifest.webmanifest
index d47904a..65349293 100644
--- a/chrome/test/data/web_apps/simple_isolated_app/.well-known/manifest.webmanifest
+++ b/chrome/test/data/web_apps/simple_isolated_app/.well-known/manifest.webmanifest
@@ -4,6 +4,7 @@
   "id": "/",
   "scope": "/",
   "start_url": "/index.html",
+  "update_manifest_url": "https://otterly-awesome-iwa.com/update_manifest.json",
   "permissions_policy": {
     "clipboard-read": [ "*" ],
     "clipboard-write": [ "*" ],
diff --git a/chrome/test/data/webui/glic/browser_tests/glic_api_browsertest.ts b/chrome/test/data/webui/glic/browser_tests/glic_api_browsertest.ts
index ad4bb10..9d2cf68 100644
--- a/chrome/test/data/webui/glic/browser_tests/glic_api_browsertest.ts
+++ b/chrome/test/data/webui/glic/browser_tests/glic_api_browsertest.ts
@@ -1653,18 +1653,61 @@
     }
   }
 
-
-  async testSwitchConversationToSpecific() {
+  async testSwitchConversationToOldConversationNewInstance() {
     assertDefined(this.host.switchConversation);
     await this.host.switchConversation(
         {conversationId: 'A', conversationTitle: 'Title A'});
   }
 
-  async testSwitchConversationToNew() {
+  async testSwitchConversationToNewConversationNewInstance() {
     assertDefined(this.host.switchConversation);
     await this.host.switchConversation();
   }
 
+  async testSwitchConversationToLastActiveConversation() {
+    assertDefined(this.host.registerConversation);
+    assertDefined(this.host.switchConversation);
+    if (this.testParams === 'step1') {
+      await this.host.registerConversation(
+          {conversationId: 'A', conversationTitle: 'Title A'});
+      await this.advanceToNextStep();
+    } else if (this.testParams === 'step2') {
+      // Return and then switch conversation to ensure that ExecuteJsTest
+      // completes before the instance is deleted. The instance is deleted
+      // during the `switchConversation` call.
+      sleep(100).then(() => {
+        assertDefined(this.host.switchConversation);
+        this.host.switchConversation(
+            {conversationId: 'A', conversationTitle: 'Title A'});
+      });
+    }
+  }
+
+  async testSwitchConversationToOldConversationInOldInstance() {
+    assertDefined(this.host.registerConversation);
+    assertDefined(this.host.switchConversation);
+    if (this.testParams === 'step1') {
+      await this.host.registerConversation(
+          {conversationId: 'A', conversationTitle: 'Title A'});
+      await this.advanceToNextStep();
+    } else if (this.testParams === 'step2') {
+      sleep(100).then(() => {
+        assertDefined(this.host.switchConversation);
+        this.host.switchConversation(
+            {conversationId: 'B', conversationTitle: 'Title B'});
+      });
+    } else if (this.testParams === 'step3') {
+      // Return and then switch conversation to ensure that ExecuteJsTest
+      // completes before the instance is deleted. The instance is deleted
+      // during the `switchConversation` call.
+      sleep(100).then(() => {
+        assertDefined(this.host.switchConversation);
+        this.host.switchConversation(
+            {conversationId: 'A', conversationTitle: 'Title A'});
+      });
+    }
+  }
+
   async testTabSwitchDoesNotLogActivationMetric() {
     assertDefined(this.host.registerConversation);
     assertDefined(this.host.switchConversation);
diff --git a/chrome/test/data/webui/settings/security_page_v2_test.ts b/chrome/test/data/webui/settings/security_page_v2_test.ts
index 2382d00c..baa744d 100644
--- a/chrome/test/data/webui/settings/security_page_v2_test.ts
+++ b/chrome/test/data/webui/settings/security_page_v2_test.ts
@@ -11,7 +11,7 @@
 import {CrSettingsPrefs, HatsBrowserProxyImpl, Router, routes, SecurityPageV2Interaction} from 'chrome://settings/settings.js';
 import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
 import {flushTasks} from 'chrome://webui-test/polymer_test_util.js';
-import {isChildVisible, isVisible, microtasksFinished} from 'chrome://webui-test/test_util.js';
+import {isChildVisible, isVisible} from 'chrome://webui-test/test_util.js';
 
 import {TestHatsBrowserProxy} from './test_hats_browser_proxy.js';
 
@@ -49,7 +49,7 @@
 
     // Click on Enhanced bundle.
     page.$.securitySettingsBundleEnhanced.click();
-    await microtasksFinished();
+    await flushTasks();
     assertEquals(
         SecuritySettingsBundleSetting.ENHANCED,
         page.prefs.generated.security_settings_bundle.value);
@@ -66,13 +66,13 @@
     // Click on the expand button, expands content and we can see the radio
     // group.
     expandButton.click();
-    await microtasksFinished();
+    await flushTasks();
     assertTrue(isChildVisible(page, '#safeBrowsingRadioGroup'));
 
     // Click on the expand button, collapses content and we can't see the radio
     // group.
     expandButton.click();
-    await microtasksFinished();
+    await flushTasks();
     assertFalse(isChildVisible(page, '#safeBrowsingRadioGroup', true));
   });
 
@@ -276,7 +276,6 @@
             '#expandButton')!;
     expandButton.click();
     await flushTasks();
-    await microtasksFinished();
 
     const radioGroup =
         page.shadowRoot!.querySelector<HTMLElement>('#safeBrowsingRadioGroup');
diff --git a/chrome/test/media/performance/openscreen_cast_performance_test.py b/chrome/test/media/performance/openscreen_cast_performance_test.py
index a8112ae..3543032 100644
--- a/chrome/test/media/performance/openscreen_cast_performance_test.py
+++ b/chrome/test/media/performance/openscreen_cast_performance_test.py
@@ -49,7 +49,9 @@
     # Skips the first-run experience modal.
     "--no-first-run",
     # Prevents the "Set as default browser" prompt from appearing.
-    "--no-default-browser-check"
+    "--no-default-browser-check",
+    # Launches Chrome in fullscreen mode to prevent scrollbar clipping.
+    "--start-fullscreen",
 ]
 
 
diff --git a/chrome/test/media/performance/videostack_performance_test.py b/chrome/test/media/performance/videostack_performance_test.py
index 83981259..d3f4642 100755
--- a/chrome/test/media/performance/videostack_performance_test.py
+++ b/chrome/test/media/performance/videostack_performance_test.py
@@ -38,8 +38,12 @@
     "--enable-logging=stderr",
     # Sets the default verbose logging level to 1.
     "--v=1",
+    # Disables the sandbox, often necessary in automated test environments.
     "--no-sandbox",
+    # Disables the GPU sandbox, used to prevent issues with GPU crashes.
     "--disable-gpu-sandbox",
+    # Launches Chrome in fullscreen mode to prevent scrollbar clipping.
+    "--start-fullscreen",
 ]
 
 def connect_to_remote_driver(chrome_options, binary_location):
diff --git a/chrome/updater/app/app_utils.cc b/chrome/updater/app/app_utils.cc
index 599636f..234f359 100644
--- a/chrome/updater/app/app_utils.cc
+++ b/chrome/updater/app/app_utils.cc
@@ -22,6 +22,12 @@
          base::EqualsCaseInsensitiveASCII(app_id, LEGACY_GOOGLE_UPDATE_APPID);
 }
 
+bool IsRemoteEventLoggingPermissionExempt(const std::string& app_id) {
+  return IsUpdaterOrCompanionApp(app_id) ||
+         base::EqualsCaseInsensitiveASCII(app_id,
+                                          kPlatformExperienceHelperAppId);
+}
+
 bool ShouldUninstall(const std::vector<std::string>& app_ids,
                      int server_starts,
                      bool had_apps) {
diff --git a/chrome/updater/app/app_utils.h b/chrome/updater/app/app_utils.h
index 84299bb..b22a8c5 100644
--- a/chrome/updater/app/app_utils.h
+++ b/chrome/updater/app/app_utils.h
@@ -14,6 +14,10 @@
 // legacy updater.
 bool IsUpdaterOrCompanionApp(const std::string& app_id);
 
+// Returns true if app id corresponds to to app which does not influence the
+// remote event logging usage stats permission bit.
+bool IsRemoteEventLoggingPermissionExempt(const std::string& app_id);
+
 // Returns true if the updater should uninstall itself. `app_ids` is the set of
 // registered applications, `server_starts` is the number of times the server
 // has launched, and `had_apps` is a bool indicating whether there has ever been
diff --git a/chrome/updater/branded_constants.h b/chrome/updater/branded_constants.h
index 0b96464..0596cd2 100644
--- a/chrome/updater/branded_constants.h
+++ b/chrome/updater/branded_constants.h
@@ -12,6 +12,8 @@
 // App ids.
 inline constexpr char kUpdaterAppId[] = UPDATER_APPID;
 inline constexpr char kQualificationAppId[] = QUALIFICATION_APPID;
+inline constexpr char kPlatformExperienceHelperAppId[] =
+    PLATFORM_EXPERIENCE_HELPER_APPID;
 
 // Environment variables.
 inline constexpr char kUsageStatsEnabled[] =
diff --git a/chrome/updater/branding.gni b/chrome/updater/branding.gni
index 40719fd..2b6f9e23 100644
--- a/chrome/updater/branding.gni
+++ b/chrome/updater/branding.gni
@@ -34,6 +34,7 @@
   updater_appid = "{6e8ffa8f-e7e2-4000-9884-589283c27015}"
   browser_appid = "{5d8d08af-2df9-4da2-86c1-eac353a0ca32}"
   qualification_appid = "{43f3a046-04b3-4443-a770-d67dae90e440}"
+  platform_experience_helper_appid = "{b84ca560-bb0c-4ec9-a288-df67e15354c7}"
   crx_pkhash = ""
   legacy_service_name_prefix = "cupdate"
   prefs_access_mutex = "{A6B9ECD5-772A-4D3F-BFEB-CF9340534A3E}"
diff --git a/chrome/updater/internal b/chrome/updater/internal
index db5d0ba..f036eed 160000
--- a/chrome/updater/internal
+++ b/chrome/updater/internal
@@ -1 +1 @@
-Subproject commit db5d0ba0230a45fd9b60e140b553aebb121ee31b
+Subproject commit f036eed064b40f7cc3651cb666b2551d7e4f73ae
diff --git a/chrome/updater/updater_branding.h.in b/chrome/updater/updater_branding.h.in
index e048efbf..0a9f851 100644
--- a/chrome/updater/updater_branding.h.in
+++ b/chrome/updater/updater_branding.h.in
@@ -31,3 +31,4 @@
 #define LEGACY_SERVICE_NAME_PREFIX "@LEGACY_SERVICE_NAME_PREFIX@"
 #define PREFS_ACCESS_MUTEX "@PREFS_ACCESS_MUTEX@"
 #define SETUP_MUTEX "@SETUP_MUTEX@"
+#define PLATFORM_EXPERIENCE_HELPER_APPID "@PLATFORM_EXPERIENCE_HELPER_APPID@"
diff --git a/chrome/updater/usage_stats_permissions_mac.mm b/chrome/updater/usage_stats_permissions_mac.mm
index 94e3922..790750b 100644
--- a/chrome/updater/usage_stats_permissions_mac.mm
+++ b/chrome/updater/usage_stats_permissions_mac.mm
@@ -77,7 +77,7 @@
 
   bool manages_additional_apps =
       std::ranges::any_of(installed_app_ids, [&](const std::string& app_id) {
-        return !IsUpdaterOrCompanionApp(app_id) &&
+        return !IsRemoteEventLoggingPermissionExempt(app_id) &&
                !base::EqualsCaseInsensitiveASCII(
                    app_id, event_logging_permission_provider->app_id);
       });
diff --git a/chrome/updater/usage_stats_permissions_unittest.cc b/chrome/updater/usage_stats_permissions_unittest.cc
index 108a01853..abd7d14e 100644
--- a/chrome/updater/usage_stats_permissions_unittest.cc
+++ b/chrome/updater/usage_stats_permissions_unittest.cc
@@ -136,12 +136,10 @@
 #endif  // BUILDFLAG(IS_WIN)
 
 #if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
-  void SetUpdaterUsageStats(bool enabled, UpdaterScope scope) {
+  void SetExemptAppsUsageStats(bool enabled, UpdaterScope scope) {
     SetAppUsageStats(kUpdaterAppId, enabled, scope);
-  }
-
-  void SetCECAUsageStats(bool enabled, UpdaterScope scope) {
     SetAppUsageStats(enterprise_companion::kCompanionAppId, enabled, scope);
+    SetAppUsageStats(kPlatformExperienceHelperAppId, enabled, scope);
   }
 #endif  // BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
 
@@ -225,24 +223,21 @@
 }
 
 TEST_F(UsageStatsPermissionsTest,
-       PermissionProviderAllowsRemoteLoggingWithCECAAndUpdater) {
-  SetUpdaterUsageStats(true, scope_);
-  SetCECAUsageStats(true, scope_);
+       PermissionProviderAllowsRemoteLoggingWithExemptApps) {
+  SetExemptAppsUsageStats(true, scope_);
   SetAppUsageStats(fake_permission_provider_.app_id, true, scope_);
   ASSERT_TRUE(RemoteEventLoggingAllowed());
 }
 
 TEST_F(UsageStatsPermissionsTest, UsageStatsProviderChecksPermissionProvider) {
-  SetUpdaterUsageStats(true, scope_);
-  SetCECAUsageStats(true, scope_);
+  SetExemptAppsUsageStats(true, scope_);
   SetAppUsageStats(fake_permission_provider_.app_id, false, scope_);
   ASSERT_FALSE(RemoteEventLoggingAllowed());
 }
 
 TEST_F(UsageStatsPermissionsTest,
        PermissionProviderDisallowsRemoteLoggingWithOtherAppDisabled) {
-  SetUpdaterUsageStats(true, scope_);
-  SetCECAUsageStats(true, scope_);
+  SetExemptAppsUsageStats(true, scope_);
   SetAppUsageStats(fake_permission_provider_.app_id, true, scope_);
   SetAppUsageStats("unsupported_app", false, scope_);
   ASSERT_FALSE(RemoteEventLoggingAllowed());
@@ -250,8 +245,7 @@
 
 TEST_F(UsageStatsPermissionsTest,
        PermissionProviderDisallowsRemoteLoggingWithOtherAppEnabled) {
-  SetUpdaterUsageStats(true, scope_);
-  SetCECAUsageStats(true, scope_);
+  SetExemptAppsUsageStats(true, scope_);
   SetAppUsageStats(fake_permission_provider_.app_id, true, scope_);
   SetAppUsageStats("unsupported_app", true, scope_);
   ASSERT_FALSE(RemoteEventLoggingAllowed());
@@ -262,8 +256,7 @@
   if (!IsSystemInstall(scope_)) {
     GTEST_SKIP() << "Not applicable to user-scoped installs";
   }
-  SetUpdaterUsageStats(true, scope_);
-  SetCECAUsageStats(true, scope_);
+  SetExemptAppsUsageStats(true, scope_);
   SetAppUsageStats(fake_permission_provider_.app_id, true, UpdaterScope::kUser);
   SetAppUsageStats(fake_permission_provider_.app_id, false,
                    UpdaterScope::kSystem);
diff --git a/chrome/updater/usage_stats_permissions_win.cc b/chrome/updater/usage_stats_permissions_win.cc
index 5e5f7b7..f3ab7a0 100644
--- a/chrome/updater/usage_stats_permissions_win.cc
+++ b/chrome/updater/usage_stats_permissions_win.cc
@@ -99,7 +99,7 @@
 
   bool manages_additional_apps =
       std::ranges::any_of(installed_app_ids, [&](const std::string& app_id) {
-        return !IsUpdaterOrCompanionApp(app_id) &&
+        return !IsRemoteEventLoggingPermissionExempt(app_id) &&
                !base::EqualsCaseInsensitiveASCII(
                    app_id, event_logging_permission_provider->app_id);
       });
diff --git a/chrome/utility/image_writer/image_writer.cc b/chrome/utility/image_writer/image_writer.cc
index 803042b..f5f8e18 100644
--- a/chrome/utility/image_writer/image_writer.cc
+++ b/chrome/utility/image_writer/image_writer.cc
@@ -6,6 +6,9 @@
 
 #include <string.h>
 
+#include <algorithm>
+#include <optional>
+
 #include "base/compiler_specific.h"
 #include "base/containers/heap_array.h"
 #include "base/location.h"
@@ -22,10 +25,14 @@
 
 namespace image_writer {
 
+namespace {
+
 // Since block devices like large sequential access and IPC is expensive we're
 // doing work in 1MB chunks.
-const int kBurningBlockSize = 1 << 20;  // 1 MB
-const int kMemoryAlignment = 4096;
+constexpr size_t kBurningBlockSize = 1 << 20;  // 1 MB
+constexpr size_t kMemoryAlignment = 4096;
+
+}  // namespace
 
 ImageWriter::ImageWriter(ImageWriterHandler* handler,
                          const base::FilePath& image_path,
@@ -121,40 +128,42 @@
   }
 
   // DASD buffers require memory alignment on some systems.
-  std::unique_ptr<char, base::AlignedFreeDeleter> buffer(static_cast<char*>(
-      base::AlignedAlloc(kBurningBlockSize, kMemoryAlignment)));
-  UNSAFE_TODO(memset(buffer.get(), 0, kBurningBlockSize));
+  base::AlignedHeapArray<uint8_t> buffer =
+      base::AlignedUninit<uint8_t>(kBurningBlockSize, kMemoryAlignment);
+  std::ranges::fill(buffer, 0);
 
-  int bytes_read = UNSAFE_TODO(
-      image_file_.Read(bytes_processed_, buffer.get(), kBurningBlockSize));
+  const std::optional<size_t> bytes_read =
+      image_file_.Read(bytes_processed_, buffer.as_span());
+  if (!bytes_read) {
+    Error(error::kReadImage);
+    return;
+  }
 
-  if (bytes_read > 0) {
-    // Always attempt to write a whole block, as writing DASD requires sector-
-    // aligned writes to devices.
-    int bytes_to_write = bytes_read + (kMemoryAlignment - 1) -
-                         (bytes_read - 1) % kMemoryAlignment;
-    DCHECK_EQ(0, bytes_to_write % kMemoryAlignment);
-    int bytes_written = UNSAFE_TODO(
-        device_file_.Write(bytes_processed_, buffer.get(), bytes_to_write));
-
-    if (bytes_written < bytes_read) {
-      Error(error::kWriteImage);
-      return;
-    }
-
-    bytes_processed_ += bytes_read;
-    PostProgress(bytes_processed_);
-
-    PostTask(base::BindOnce(&ImageWriter::WriteChunk, AsWeakPtr()));
-  } else if (bytes_read == 0) {
+  if (*bytes_read == 0) {
     // End of file.
     device_file_.Flush();
     running_ = false;
     handler_->SendSucceeded();
-  } else {
-    // Unable to read entire file.
-    Error(error::kReadImage);
+    return;
   }
+
+  // Always attempt to write a whole block, as writing DASD requires sector-
+  // aligned writes to devices.
+  const size_t bytes_to_write = *bytes_read + (kMemoryAlignment - 1) -
+                                (*bytes_read - 1) % kMemoryAlignment;
+  DCHECK_EQ(0u, bytes_to_write % kMemoryAlignment);
+  std::optional<size_t> bytes_written =
+      device_file_.Write(bytes_processed_, buffer.first(bytes_to_write));
+
+  if (!bytes_written || *bytes_written < *bytes_read) {
+    Error(error::kWriteImage);
+    return;
+  }
+
+  bytes_processed_ += base::checked_cast<int64_t>(*bytes_read);
+  PostProgress(bytes_processed_);
+
+  PostTask(base::BindOnce(&ImageWriter::WriteChunk, AsWeakPtr()));
 }
 
 void ImageWriter::VerifyChunk() {
@@ -162,46 +171,47 @@
     return;
   }
 
-  auto image_buffer = base::HeapArray<char>::Uninit(kBurningBlockSize);
+  auto image_buffer = base::HeapArray<uint8_t>::Uninit(kBurningBlockSize);
   // DASD buffers require memory alignment on some systems.
-  std::unique_ptr<char, base::AlignedFreeDeleter> device_buffer(
-      static_cast<char*>(
-          base::AlignedAlloc(kBurningBlockSize, kMemoryAlignment)));
+  base::AlignedHeapArray<uint8_t> device_buffer =
+      base::AlignedUninit<uint8_t>(kBurningBlockSize, kMemoryAlignment);
 
-  int bytes_read = UNSAFE_TODO(image_file_.Read(
-      bytes_processed_, image_buffer.data(), kBurningBlockSize));
-
-  if (bytes_read > 0) {
-    if (UNSAFE_TODO(device_file_.Read(bytes_processed_, device_buffer.get(),
-                                      kBurningBlockSize)) < bytes_read) {
-      LOG(ERROR) << "Failed to read " << bytes_read << " bytes of "
-                 << "device at offset " << bytes_processed_;
-      Error(error::kReadDevice);
-      return;
-    }
-
-    if (UNSAFE_TODO(memcmp(image_buffer.data(), device_buffer.get(),
-                           bytes_read)) != 0) {
-      LOG(ERROR) << "Write verification failed when comparing " << bytes_read
-                 << " bytes at " << bytes_processed_;
-      Error(error::kVerificationFailed);
-      return;
-    }
-
-    bytes_processed_ += bytes_read;
-    PostProgress(bytes_processed_);
-
-    PostTask(base::BindOnce(&ImageWriter::VerifyChunk, AsWeakPtr()));
-  } else if (bytes_read == 0) {
-    // End of file.
-    handler_->SendSucceeded();
-    running_ = false;
-  } else {
+  const std::optional<size_t> bytes_read =
+      image_file_.Read(bytes_processed_, image_buffer.as_span());
+  if (!bytes_read) {
     // Unable to read entire file.
     LOG(ERROR) << "Failed to read " << kBurningBlockSize << " bytes of image "
                << "at offset " << bytes_processed_;
     Error(error::kReadImage);
+    return;
   }
+
+  if (*bytes_read == 0) {
+    // End of file.
+    handler_->SendSucceeded();
+    running_ = false;
+    return;
+  }
+
+  if (device_file_.Read(bytes_processed_, device_buffer.as_span()) <
+      *bytes_read) {
+    LOG(ERROR) << "Failed to read " << *bytes_read << " bytes of "
+               << "device at offset " << bytes_processed_;
+    Error(error::kReadDevice);
+    return;
+  }
+
+  if (image_buffer.first(*bytes_read) != device_buffer.first(*bytes_read)) {
+    LOG(ERROR) << "Write verification failed when comparing " << *bytes_read
+               << " bytes at " << bytes_processed_;
+    Error(error::kVerificationFailed);
+    return;
+  }
+
+  bytes_processed_ += base::checked_cast<int64_t>(*bytes_read);
+  PostProgress(bytes_processed_);
+
+  PostTask(base::BindOnce(&ImageWriter::VerifyChunk, AsWeakPtr()));
 }
 
 }  // namespace image_writer
diff --git a/chrome/utility/safe_browsing/BUILD.gn b/chrome/utility/safe_browsing/BUILD.gn
index 8437207..fd0c7d1 100644
--- a/chrome/utility/safe_browsing/BUILD.gn
+++ b/chrome/utility/safe_browsing/BUILD.gn
@@ -9,12 +9,14 @@
 
   if (safe_browsing_mode == 1) {
     sources = [
+      "archive_analysis_delegate.h",
       "archive_analyzer.cc",
       "archive_analyzer.h",
       "seven_zip_analyzer.cc",
       "seven_zip_analyzer.h",
       "zip_analyzer.cc",
       "zip_analyzer.h",
+      "zip_writer_delegate.h",
     ]
 
     if (is_mac) {
diff --git a/chrome/utility/safe_browsing/archive_analysis_delegate.h b/chrome/utility/safe_browsing/archive_analysis_delegate.h
new file mode 100644
index 0000000..801fbb2
--- /dev/null
+++ b/chrome/utility/safe_browsing/archive_analysis_delegate.h
@@ -0,0 +1,39 @@
+// 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_UTILITY_SAFE_BROWSING_ARCHIVE_ANALYSIS_DELEGATE_H_
+#define CHROME_UTILITY_SAFE_BROWSING_ARCHIVE_ANALYSIS_DELEGATE_H_
+
+#include <memory>
+
+#include "base/files/file.h"
+#include "chrome/utility/safe_browsing/zip_writer_delegate.h"
+#include "third_party/zlib/google/zip_reader.h"
+
+namespace safe_browsing {
+
+// Delegate interface for abstracting archive analysis operations, specifically
+// for handling obfuscated archives where custom readers and writers are needed.
+class ArchiveAnalysisDelegate {
+ public:
+  virtual ~ArchiveAnalysisDelegate() = default;
+
+  // Creates a reader delegate for reading the ZIP archive.
+  virtual std::unique_ptr<zip::ReaderDelegate> CreateZipReaderDelegate(
+      base::File file) = 0;
+
+  // Creates a writer delegate for writing extracted ZIP entries.
+  virtual std::unique_ptr<SafeBrowsingZipWriterDelegate>
+  CreateZipWriterDelegate(base::File file) = 0;
+
+  // Creates a delegate for analyzing a nested archive extracted from the
+  // current archive. Returns nullptr if the nested archive cannot be handled
+  // (e.g. header parsing fails).
+  virtual std::unique_ptr<ArchiveAnalysisDelegate> CreateNestedDelegate(
+      base::File extracted_file) = 0;
+};
+
+}  // namespace safe_browsing
+
+#endif  // CHROME_UTILITY_SAFE_BROWSING_ARCHIVE_ANALYSIS_DELEGATE_H_
diff --git a/chrome/utility/safe_browsing/archive_analyzer.cc b/chrome/utility/safe_browsing/archive_analyzer.cc
index b1302b7..074c463 100644
--- a/chrome/utility/safe_browsing/archive_analyzer.cc
+++ b/chrome/utility/safe_browsing/archive_analyzer.cc
@@ -76,6 +76,12 @@
   finished_analysis_callback_ = std::move(callback);
 }
 
+void ArchiveAnalyzer::SetAnalysisDelegate(
+    std::unique_ptr<ArchiveAnalysisDelegate> analysis_delegate) {
+  CHECK(analysis_delegate);
+  analysis_delegate_ = std::move(analysis_delegate);
+}
+
 base::File& ArchiveAnalyzer::GetArchiveFile() {
   return archive_file_;
 }
@@ -99,6 +105,11 @@
       // Archive analyzers expect to start at the beginning of the
       // archive, but we may be at the end.
       entry.Seek(base::File::FROM_BEGIN, 0);
+      if (analysis_delegate_) {
+        nested_analyzer_->SetAnalysisDelegate(
+            analysis_delegate_->CreateNestedDelegate(entry.Duplicate()));
+      }
+
       nested_analyzer_->Analyze(
           entry.Duplicate(), path, password(),
           base::BindOnce(&ArchiveAnalyzer::NestedAnalysisFinished, GetWeakPtr(),
diff --git a/chrome/utility/safe_browsing/archive_analyzer.h b/chrome/utility/safe_browsing/archive_analyzer.h
index f47db42..bc9a966 100644
--- a/chrome/utility/safe_browsing/archive_analyzer.h
+++ b/chrome/utility/safe_browsing/archive_analyzer.h
@@ -11,6 +11,7 @@
 #include "base/functional/callback.h"
 #include "base/memory/raw_ptr.h"
 #include "chrome/common/safe_browsing/archive_analyzer_results.h"
+#include "chrome/utility/safe_browsing/archive_analysis_delegate.h"
 #include "components/safe_browsing/content/common/proto/download_file_types.pb.h"
 
 namespace safe_browsing {
@@ -45,6 +46,9 @@
   void SetResultsForTesting(ArchiveAnalyzerResults* results);
   void SetFinishedCallbackForTesting(FinishedAnalysisCallback callback);
 
+  void SetAnalysisDelegate(
+      std::unique_ptr<ArchiveAnalysisDelegate> analysis_delegate);
+
  protected:
   // Called when starting extraction. Subclasses should call `InitComplete` when
   // finished.
@@ -90,6 +94,8 @@
   // Returns whether we're currently unpacking the top-level archive.
   bool IsTopLevelArchive() const;
 
+  std::unique_ptr<ArchiveAnalysisDelegate> analysis_delegate_;
+
  private:
   // Tracks the relative path of the current archive within the overall archive
   // being analyzer. The top-level archive will have an empty path, but nested
diff --git a/chrome/utility/safe_browsing/zip_analyzer.cc b/chrome/utility/safe_browsing/zip_analyzer.cc
index d557b1c3..d1fe02f0f 100644
--- a/chrome/utility/safe_browsing/zip_analyzer.cc
+++ b/chrome/utility/safe_browsing/zip_analyzer.cc
@@ -18,6 +18,8 @@
 #include "base/time/time.h"
 #include "build/build_config.h"
 #include "chrome/common/safe_browsing/archive_analyzer_results.h"
+#include "chrome/utility/safe_browsing/archive_analysis_delegate.h"
+#include "chrome/utility/safe_browsing/zip_writer_delegate.h"
 #include "components/safe_browsing/content/common/file_type_policies.h"
 #include "components/safe_browsing/core/common/features.h"
 #include "components/safe_browsing/core/common/proto/csd.pb.h"
@@ -25,36 +27,6 @@
 
 namespace safe_browsing {
 
-namespace {
-
-class WriterDelegate : public zip::FileWriterDelegate {
- public:
-  explicit WriterDelegate(base::File* file)
-      : zip::FileWriterDelegate(file), has_disk_error_(false) {}
-  WriterDelegate(const WriterDelegate&) = delete;
-  WriterDelegate& operator=(const WriterDelegate&) = delete;
-
-  ~WriterDelegate() override = default;
-
-  bool PrepareOutput() override {
-    bool success = zip::FileWriterDelegate::PrepareOutput();
-    has_disk_error_ |= !success;
-    return success;
-  }
-  bool WriteBytes(const char* data, int num_bytes) override {
-    bool success = zip::FileWriterDelegate::WriteBytes(data, num_bytes);
-    has_disk_error_ |= !success;
-    return success;
-  }
-
-  bool has_disk_error() const { return has_disk_error_; }
-
- private:
-  bool has_disk_error_;
-};
-
-}  // namespace
-
 ZipAnalyzer::ZipAnalyzer() = default;
 ZipAnalyzer::~ZipAnalyzer() = default;
 
@@ -77,13 +49,19 @@
     if (!temp_file_.SetLength(0)) {
       PLOG(WARNING) << "Failed truncate";
     }
-    WriterDelegate writer(&temp_file_);
+
+    CHECK(analysis_delegate_);
+
+    std::unique_ptr<SafeBrowsingZipWriterDelegate> writer =
+        analysis_delegate_->CreateZipWriterDelegate(temp_file_.Duplicate());
+
     bool extract_success = reader_.ExtractCurrentEntry(
-        &writer, std::numeric_limits<uint64_t>::max());
+        writer.get(), std::numeric_limits<uint64_t>::max());
 
     has_encrypted_ |= entry->is_encrypted;
     has_aes_encrypted_ |= entry->uses_aes_encryption;
-    has_disk_error_ |= writer.has_disk_error();
+
+    has_disk_error_ |= writer->has_disk_error();
 
     if (!extract_success && entry->is_encrypted) {
       results()->encryption_info.password_status =
@@ -92,7 +70,7 @@
 
     if (!UpdateResultsForEntry(temp_file_.Duplicate(),
                                GetRootPath().Append(entry->path),
-                               writer.file_length(), entry->is_encrypted,
+                               writer->file_length(), entry->is_encrypted,
                                entry->is_directory, extract_success)) {
       return false;
     }
@@ -130,7 +108,12 @@
     return;
   }
 
-  if (!reader_.OpenFromPlatformFile(GetArchiveFile().GetPlatformFile())) {
+  CHECK(analysis_delegate_);
+  reader_delegate_ =
+      analysis_delegate_->CreateZipReaderDelegate(GetArchiveFile().Duplicate());
+
+  if (!reader_delegate_ ||
+      !reader_.OpenFromReaderDelegate(reader_delegate_.get())) {
     InitComplete(ArchiveAnalysisResult::kUnknown);
     return;
   }
diff --git a/chrome/utility/safe_browsing/zip_analyzer.h b/chrome/utility/safe_browsing/zip_analyzer.h
index 99b0ff9..323fbb3 100644
--- a/chrome/utility/safe_browsing/zip_analyzer.h
+++ b/chrome/utility/safe_browsing/zip_analyzer.h
@@ -32,6 +32,9 @@
   void OnGetTempFile(base::File temp_file);
 
   base::File temp_file_;
+  // reader_delegate_ is held by ZipReader, but ZipReader doesn't own it.
+  // We need to keep the delegate alive while the reader is using it.
+  std::unique_ptr<zip::ReaderDelegate> reader_delegate_;
   zip::ZipReader reader_;
 
   bool has_encrypted_ = false;
diff --git a/chrome/utility/safe_browsing/zip_writer_delegate.h b/chrome/utility/safe_browsing/zip_writer_delegate.h
new file mode 100644
index 0000000..914921b0
--- /dev/null
+++ b/chrome/utility/safe_browsing/zip_writer_delegate.h
@@ -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.
+
+#ifndef CHROME_UTILITY_SAFE_BROWSING_ZIP_WRITER_DELEGATE_H_
+#define CHROME_UTILITY_SAFE_BROWSING_ZIP_WRITER_DELEGATE_H_
+
+#include "base/files/file.h"
+#include "third_party/zlib/google/zip_reader.h"
+
+namespace safe_browsing {
+
+class SafeBrowsingZipWriterDelegate : public zip::WriterDelegate {
+ public:
+  virtual bool has_disk_error() const = 0;
+  virtual int64_t file_length() const = 0;
+};
+
+}  // namespace safe_browsing
+
+#endif  // CHROME_UTILITY_SAFE_BROWSING_ZIP_WRITER_DELEGATE_H_
diff --git a/chromeos/CHROMEOS_LKGM b/chromeos/CHROMEOS_LKGM
index 0a8bc5f..8ef86d1 100644
--- a/chromeos/CHROMEOS_LKGM
+++ b/chromeos/CHROMEOS_LKGM
@@ -1 +1 @@
-16502.0.0-1073499
\ No newline at end of file
+16504.0.0-1073524
\ No newline at end of file
diff --git a/chromeos/ash/components/geolocation/DEPS b/chromeos/ash/components/geolocation/DEPS
index 8af740d..e01f4776 100644
--- a/chromeos/ash/components/geolocation/DEPS
+++ b/chromeos/ash/components/geolocation/DEPS
@@ -1,6 +1,7 @@
 noparent = True
 
 include_rules = [
+  "+chromeos/constants",
   "+ash/constants",
   "+base",
   "+chromeos/ash/components/network",
diff --git a/chromeos/ash/components/geolocation/cache_eviction_options.cc b/chromeos/ash/components/geolocation/cache_eviction_options.cc
index a8159d8..1310791 100644
--- a/chromeos/ash/components/geolocation/cache_eviction_options.cc
+++ b/chromeos/ash/components/geolocation/cache_eviction_options.cc
@@ -4,12 +4,29 @@
 
 #include "chromeos/ash/components/geolocation/cache_eviction_options.h"
 
+#include "base/notreached.h"
+#include "base/strings/strcat.h"
 #include "chromeos/ash/components/geolocation/cached_location_provider.h"
 #include "chromeos/ash/components/network/network_util.h"
 
 namespace ash::geolocation {
 namespace {
 
+// Returns the percentage of overlap (0.0 to 1.0) required to consider
+// two scans "similar" enough to prevent eviction.
+constexpr double GetSimilarityThreshold(
+    const SimilarityDegree similarity_degree) {
+  switch (similarity_degree) {
+    case SimilarityDegree::kLoose:
+      return 0.5;  // 50% overlap required
+    case SimilarityDegree::kModerate:
+      return 0.7;  // 70% overlap required
+    case SimilarityDegree::kStrict:
+      return 0.9;  // 90% overlap required
+  }
+  NOTREACHED();
+}
+
 template <typename T, typename TComparator>
   requires std::strict_weak_order<TComparator, const T&, const T&>
 std::vector<T> GetIntersection(const std::vector<T>& scan_a,
@@ -56,7 +73,8 @@
                       });
 
   return wifi_scan_intersection.size() <
-         std::max(wifi_scan_a.size(), wifi_scan_b.size()) * tolerance_;
+         std::max(wifi_scan_a.size(), wifi_scan_b.size()) *
+             GetSimilarityThreshold(similarity_degree_);
 }
 
 bool HasCommonWifiAP::IsSignificantDisplacementIndicated(
@@ -93,7 +111,8 @@
       [](const CellTower& a, const CellTower& b) { return a.ci < b.ci; });
 
   return cellular_scan_intersection.size() <
-         std::max(cellular_scan_a.size(), cellular_scan_b.size()) * tolerance_;
+         std::max(cellular_scan_a.size(), cellular_scan_b.size()) *
+             GetSimilarityThreshold(similarity_degree_);
 }
 
 bool HasCommonCellTower::IsSignificantDisplacementIndicated(
diff --git a/chromeos/ash/components/geolocation/cache_eviction_options.h b/chromeos/ash/components/geolocation/cache_eviction_options.h
index b68b911..a24396d6 100644
--- a/chromeos/ash/components/geolocation/cache_eviction_options.h
+++ b/chromeos/ash/components/geolocation/cache_eviction_options.h
@@ -5,13 +5,34 @@
 #ifndef CHROMEOS_ASH_COMPONENTS_GEOLOCATION_CACHE_EVICTION_OPTIONS_H_
 #define CHROMEOS_ASH_COMPONENTS_GEOLOCATION_CACHE_EVICTION_OPTIONS_H_
 
-#include "chromeos/ash/components/geolocation/cached_location_provider.h"
 #include "chromeos/ash/components/geolocation/geoposition_context.h"
 
 namespace ash::geolocation {
 
+// The full list of strategies considered for the `CachedLocationProvided`.
+enum class CacheEvictionStrategy {
+  kWifiTolerance = 0,
+  kCommonWifi,
+  kCellularTolerance,
+  kCommonCell,
+  kCommonWifiAndCell,
+  kMaxValue = kCommonWifiAndCell,
+};
+
+// Similarity degree needed for two network scans (e.g. Wifi AP scans) to be
+// considered equal. Used by the `CacheEviction` classes with the tolerance
+// parameter.
+enum class SimilarityDegree {
+  kLoose = 0,
+  kModerate,
+  kStrict,
+  kMaxValue = kStrict,
+};
+
 class COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_GEOLOCATION) CacheEviction {
  public:
+  explicit CacheEviction(CacheEvictionStrategy strategy)
+      : strategy_(strategy) {}
   virtual ~CacheEviction() = default;
 
   // Determines if the significant physical displacement is implied by the
@@ -24,21 +45,26 @@
   virtual bool IsSignificantDisplacementIndicated(
       const GeopositionContext& context_a,
       const GeopositionContext& context_b) const = 0;
+
+  CacheEvictionStrategy strategy() const { return strategy_; }
+
+ private:
+  CacheEvictionStrategy strategy_;
 };
 
 // Eviction strategy based on the overlap of visible WiFi Access Points (APs).
 // Calculates the intersection of APs between two scans. If the intersection
-// is large enough (determined by `tolerance`), the device is assumed to be
-// stationary (no eviction).
+// is large enough (determined by `similarity_degree`), the device is assumed to
+// be stationary (no eviction).
 class COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_GEOLOCATION)
     WifiEquivalenceWithTolerance : public CacheEviction {
  public:
-  // `tolerance` represents the required overlap ratio (0.0 to 1.0) to assume
-  // stillness.
-  // A higher `tolerance` requires a larger overlap to keep the cache valid,
-  // meaning the cache expires more easily.
-  explicit WifiEquivalenceWithTolerance(double tolerance)
-      : tolerance_(tolerance) {}
+  // `similarity_degree` represents the required overlap ratio (0.0 to 1.0) to
+  // assume stillness. A higher `similarity_degree` requires a larger overlap to
+  // keep the cache valid, meaning the cache expires more easily.
+  explicit WifiEquivalenceWithTolerance(SimilarityDegree similarity_degree)
+      : CacheEviction(CacheEvictionStrategy::kWifiTolerance),
+        similarity_degree_(similarity_degree) {}
 
   // CacheEviction:
   bool IsSignificantDisplacementIndicated(
@@ -46,7 +72,7 @@
       const GeopositionContext& context_b) const override;
 
  private:
-  const double tolerance_;
+  SimilarityDegree similarity_degree_;
 };
 
 // Minimalist strategy: Considers the cached location valid as long as there is
@@ -55,6 +81,7 @@
 class COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_GEOLOCATION) HasCommonWifiAP
     : public CacheEviction {
  public:
+  HasCommonWifiAP() : CacheEviction(CacheEvictionStrategy::kCommonWifi) {}
   // CacheEviction:
   bool IsSignificantDisplacementIndicated(
       const GeopositionContext& context_a,
@@ -63,17 +90,17 @@
 
 // Eviction strategy based on the overlap of visible Cell Towers.
 // Calculates the intersection of Cell Towers between two scans. If the
-// intersection is large enough (determined by `tolerance`), the device is
-// assumed to be stationary (no eviction).
+// intersection is large enough (determined by `similarity_degree`), the device
+// is assumed to be stationary (no eviction).
 class COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_GEOLOCATION)
     CellularEquivalenceWithTolerance : public CacheEviction {
  public:
-  // `tolerance` represents the required overlap ratio (0.0 to 1.0) to assume
-  // stillness.
-  // A higher `tolerance` requires a larger overlap to keep the cache valid,
-  // meaning the cache expires more easily.
-  explicit CellularEquivalenceWithTolerance(double tolerance)
-      : tolerance_(tolerance) {}
+  // `similarity_degree` represents the required overlap ratio (0.0 to 1.0) to
+  // assume stillness. A higher `similarity_degree` requires a larger overlap to
+  // keep the cache valid, meaning the cache expires more easily.
+  explicit CellularEquivalenceWithTolerance(SimilarityDegree similarity_degree)
+      : CacheEviction(CacheEvictionStrategy::kCellularTolerance),
+        similarity_degree_(similarity_degree) {}
 
   // CacheEviction:
   bool IsSignificantDisplacementIndicated(
@@ -81,7 +108,7 @@
       const GeopositionContext& context_b) const override;
 
  private:
-  const double tolerance_;
+  SimilarityDegree similarity_degree_;
 };
 
 // Minimalist strategy: Considers the cached location valid as long as there is
@@ -90,6 +117,7 @@
 class COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_GEOLOCATION) HasCommonCellTower
     : public CacheEviction {
  public:
+  HasCommonCellTower() : CacheEviction(CacheEvictionStrategy::kCommonCell) {}
   // CacheEviction:
   bool IsSignificantDisplacementIndicated(
       const GeopositionContext& context_a,
@@ -103,6 +131,8 @@
 class COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_GEOLOCATION)
     HasCommonWifiApAndCellTower : public CacheEviction {
  public:
+  HasCommonWifiApAndCellTower()
+      : CacheEviction(CacheEvictionStrategy::kCommonWifiAndCell) {}
   // CacheEviction:
   bool IsSignificantDisplacementIndicated(
       const GeopositionContext& context_a,
diff --git a/chromeos/ash/components/geolocation/cache_eviction_options_unittest.cc b/chromeos/ash/components/geolocation/cache_eviction_options_unittest.cc
index 80cbb28..fbe1a4d 100644
--- a/chromeos/ash/components/geolocation/cache_eviction_options_unittest.cc
+++ b/chromeos/ash/components/geolocation/cache_eviction_options_unittest.cc
@@ -4,6 +4,7 @@
 
 #include "chromeos/ash/components/geolocation/cache_eviction_options.h"
 
+#include "base/notreached.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/stringprintf.h"
 #include "chromeos/ash/components/network/network_util.h"
@@ -67,14 +68,29 @@
   }
 }
 
+// MUST BE in line with the thresholds used in `cache_eviction_options.cc`.
+constexpr double GetSimilarityThreshold(
+    geolocation::SimilarityDegree similarity_degree) {
+  switch (similarity_degree) {
+    case geolocation::SimilarityDegree::kLoose:
+      return 0.5;  // 50% overlap required
+    case geolocation::SimilarityDegree::kModerate:
+      return 0.7;  // 70% overlap required
+    case geolocation::SimilarityDegree::kStrict:
+      return 0.9;  // 90% overlap required
+  }
+  NOTREACHED();
+}
+
 }  // namespace
 
 using CacheEvictionOptions_WifiEquivalenceWithToleranceTestBaseCase =
-    testing::TestWithParam<double>;
+    testing::TestWithParam<geolocation::SimilarityDegree>;
 TEST_P(CacheEvictionOptions_WifiEquivalenceWithToleranceTestBaseCase,
        CheckEmpty) {
-  auto tolerance = GetParam();
-  geolocation::WifiEquivalenceWithTolerance eviction_strategy(tolerance);
+  auto similarity_degree = GetParam();
+  geolocation::WifiEquivalenceWithTolerance eviction_strategy(
+      similarity_degree);
 
   geolocation::GeopositionContext old_context;
   // Combination of empty contexts don't flag displacement.
@@ -99,16 +115,20 @@
 INSTANTIATE_TEST_SUITE_P(
     All,
     CacheEvictionOptions_WifiEquivalenceWithToleranceTestBaseCase,
-    testing::Values(.5, .7, .9));
+    testing::Values(geolocation::SimilarityDegree::kLoose,
+                    geolocation::SimilarityDegree::kModerate,
+                    geolocation::SimilarityDegree::kStrict));
 
 using CacheEvictionOptions_WifiEquivalenceWithToleranceTest =
-    testing::TestWithParam<std::tuple<double, bool, bool>>;
+    testing::TestWithParam<
+        std::tuple<geolocation::SimilarityDegree, bool, bool>>;
 TEST_P(CacheEvictionOptions_WifiEquivalenceWithToleranceTest,
        CheckToleranceLimits) {
-  auto tolerance = std::get<0>(GetParam());
+  auto similarity_degree = std::get<0>(GetParam());
   auto shuffle_ssid = std::get<1>(GetParam());
   auto shuffle_signal_strength = std::get<2>(GetParam());
-  geolocation::WifiEquivalenceWithTolerance eviction_strategy(tolerance);
+  geolocation::WifiEquivalenceWithTolerance eviction_strategy(
+      similarity_degree);
 
   geolocation::GeopositionContext old_context;
   geolocation::GeopositionContext new_context;
@@ -118,8 +138,9 @@
   // elements to check the boundary condition.
   PopulateWifiAPs(old_context, kNumScans, shuffle_ssid,
                   shuffle_signal_strength);
-  PopulateWifiAPs(new_context, kNumScans * tolerance, shuffle_ssid,
-                  shuffle_signal_strength);
+  PopulateWifiAPs(new_context,
+                  kNumScans * GetSimilarityThreshold(similarity_degree),
+                  shuffle_ssid, shuffle_signal_strength);
 
   // Scans should be similar:
   EXPECT_FALSE(eviction_strategy.IsSignificantDisplacementIndicated(
@@ -138,11 +159,14 @@
       new_context, old_context));
 }
 
-INSTANTIATE_TEST_SUITE_P(All,
-                         CacheEvictionOptions_WifiEquivalenceWithToleranceTest,
-                         testing::Combine(testing::Values(.5, .7, .9),
-                                          testing::Bool(),
-                                          testing::Bool()));
+INSTANTIATE_TEST_SUITE_P(
+    All,
+    CacheEvictionOptions_WifiEquivalenceWithToleranceTest,
+    testing::Combine(testing::Values(geolocation::SimilarityDegree::kLoose,
+                                     geolocation::SimilarityDegree::kModerate,
+                                     geolocation::SimilarityDegree::kStrict),
+                     testing::Bool(),
+                     testing::Bool()));
 
 using CacheEvictionOptions_HasCommonWifiAP = testing::Test;
 
@@ -204,11 +228,12 @@
 }
 
 using CacheEvictionOptions_CellularEquivalenceWithToleranceBaseCaseTest =
-    testing::TestWithParam<double>;
+    testing::TestWithParam<geolocation::SimilarityDegree>;
 TEST_P(CacheEvictionOptions_CellularEquivalenceWithToleranceBaseCaseTest,
        CheckEmpty) {
-  auto tolerance = GetParam();
-  geolocation::CellularEquivalenceWithTolerance eviction_strategy(tolerance);
+  auto similarity_degree = GetParam();
+  geolocation::CellularEquivalenceWithTolerance eviction_strategy(
+      similarity_degree);
 
   geolocation::GeopositionContext old_context;
   // Combination of empty contexts don't flag displacement.
@@ -233,15 +258,18 @@
 INSTANTIATE_TEST_SUITE_P(
     All,
     CacheEvictionOptions_CellularEquivalenceWithToleranceBaseCaseTest,
-    testing::Values(.5, .7, .9));
+    testing::Values(geolocation::SimilarityDegree::kLoose,
+                    geolocation::SimilarityDegree::kModerate,
+                    geolocation::SimilarityDegree::kStrict));
 
 using CacheEvictionOptions_CellularEquivalenceWithToleranceTest =
-    testing::TestWithParam<std::tuple<double, bool>>;
+    testing::TestWithParam<std::tuple<geolocation::SimilarityDegree, bool>>;
 TEST_P(CacheEvictionOptions_CellularEquivalenceWithToleranceTest,
        CheckToleranceLimits) {
-  auto tolerance = std::get<0>(GetParam());
+  auto similarity_degree = std::get<0>(GetParam());
   auto shuffle_lac = std::get<1>(GetParam());
-  geolocation::CellularEquivalenceWithTolerance eviction_strategy(tolerance);
+  geolocation::CellularEquivalenceWithTolerance eviction_strategy(
+      similarity_degree);
 
   geolocation::GeopositionContext old_context;
   geolocation::GeopositionContext new_context;
@@ -250,7 +278,9 @@
   // New context will be the subset of first `kNumScans*tolerance`
   // elements to check the boundary condition.
   PopulateCellTowers(old_context, kNumScans, shuffle_lac);
-  PopulateCellTowers(new_context, kNumScans * tolerance, shuffle_lac);
+  PopulateCellTowers(new_context,
+                     kNumScans * GetSimilarityThreshold(similarity_degree),
+                     shuffle_lac);
 
   // Scans should be similar:
   EXPECT_FALSE(eviction_strategy.IsSignificantDisplacementIndicated(
@@ -272,7 +302,10 @@
 INSTANTIATE_TEST_SUITE_P(
     All,
     CacheEvictionOptions_CellularEquivalenceWithToleranceTest,
-    testing::Combine(testing::Values(.5, .7, .9), testing::Bool()));
+    testing::Combine(testing::Values(geolocation::SimilarityDegree::kLoose,
+                                     geolocation::SimilarityDegree::kModerate,
+                                     geolocation::SimilarityDegree::kStrict),
+                     testing::Bool()));
 
 using CacheEvictionOptions_HasCommonCellTowerTest = testing::Test;
 
diff --git a/chromeos/ash/components/geolocation/cached_location_provider.cc b/chromeos/ash/components/geolocation/cached_location_provider.cc
index adb9f79..5440822 100644
--- a/chromeos/ash/components/geolocation/cached_location_provider.cc
+++ b/chromeos/ash/components/geolocation/cached_location_provider.cc
@@ -5,16 +5,177 @@
 #include "chromeos/ash/components/geolocation/cached_location_provider.h"
 
 #include <algorithm>
+#include <cmath>
 #include <memory>
+#include <numbers>
 #include <optional>
+#include <string_view>
 
 #include "base/functional/bind.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/strings/strcat.h"
 #include "base/time/time.h"
 #include "chromeos/ash/components/geolocation/cache_eviction_options.h"
 #include "chromeos/ash/components/geolocation/location_fetcher.h"
 #include "chromeos/ash/components/network/network_util.h"
+#include "chromeos/constants/chromeos_features.h"
 
 namespace ash {
+namespace {
+
+constexpr char kCacheEvictionHistogramPrefix[] =
+    "ChromeOS.Geolocation.CacheEviction";
+
+// Converts degrees to radians.
+inline double ToRadians(double degrees) {
+  return degrees * std::numbers::pi / 180.0;
+}
+
+// Calculate the travel distance using the Haversine Formula.
+// See https://en.wikipedia.org/wiki/Haversine_formula.
+double CalculateDistance(double lat1, double lon1, double lat2, double lon2) {
+  constexpr double kEarthRadiusMeters = 6371000.0;
+
+  const double lat1_rad = ToRadians(lat1);
+  const double lat2_rad = ToRadians(lat2);
+  const double delta_lat_rad = ToRadians(lat2 - lat1);
+  const double delta_lon_rad = ToRadians(lon2 - lon1);
+
+  // hav(theta) = hav(delta_lat) + cos(lat1)*cos(lat2)*hav(delta_lon)
+  double hav_theta = std::sin(delta_lat_rad / 2) * std::sin(delta_lat_rad / 2) +
+                     std::cos(lat1_rad) * std::cos(lat2_rad) *
+                         std::sin(delta_lon_rad / 2) *
+                         std::sin(delta_lon_rad / 2);
+
+  // Clamp hav(theta) to [0,1], otherwise floating point error could breach this
+  // segment and result in NaN below.
+  hav_theta = std::clamp(hav_theta, 0.0, 1.0);
+
+  // Calculate theta from hav_theta.
+  const double theta = 2 * std::asin(std::sqrt(hav_theta));
+
+  // d = r * theta
+  return kEarthRadiusMeters * theta;
+}
+
+constexpr std::string_view EvictionStrategyToString(
+    const geolocation::CacheEvictionStrategy strategy) {
+  switch (strategy) {
+    case geolocation::CacheEvictionStrategy::kWifiTolerance:
+      return "WifiTolerance";
+    case geolocation::CacheEvictionStrategy::kCommonWifi:
+      return "CommonWifi";
+    case geolocation::CacheEvictionStrategy::kCellularTolerance:
+      return "CellularTolerance";
+    case geolocation::CacheEvictionStrategy::kCommonCell:
+      return "CommonCell";
+    case geolocation::CacheEvictionStrategy::kCommonWifiAndCell:
+      return "CommonWifiAndCell";
+  }
+  NOTREACHED();
+}
+
+constexpr std::string_view SimilarityDegreeToString(
+    const geolocation::SimilarityDegree similarity_degree) {
+  switch (similarity_degree) {
+    case geolocation::SimilarityDegree::kLoose:
+      return "Loose";
+    case geolocation::SimilarityDegree::kModerate:
+      return "Moderate";
+    case geolocation::SimilarityDegree::kStrict:
+      return "Strict";
+  }
+  NOTREACHED();
+}
+
+// Constructs the full UMA histogram name used to record location displacement.
+// e.g. ChromeOS.Geolocation.CacheEviction.CommonWifi.PredictedYes
+std::string GetHistogramName(
+    geolocation::CacheEvictionStrategy strategy,
+    std::optional<geolocation::SimilarityDegree> similarity_degree,
+    bool prediction) {
+  switch (strategy) {
+    case geolocation::CacheEvictionStrategy::kWifiTolerance:
+    case geolocation::CacheEvictionStrategy::kCellularTolerance:
+      CHECK(similarity_degree);
+      break;
+    case geolocation::CacheEvictionStrategy::kCommonWifi:
+    case geolocation::CacheEvictionStrategy::kCommonCell:
+    case geolocation::CacheEvictionStrategy::kCommonWifiAndCell:
+      CHECK(!similarity_degree);
+      break;
+    default:
+      NOTREACHED();
+  }
+
+  std::string_view eviction_name = EvictionStrategyToString(strategy);
+  std::string_view eviction_similarity_degree =
+      similarity_degree ? SimilarityDegreeToString(*similarity_degree) : "";
+  std::string_view prediction_token =
+      prediction ? "PredictedYes" : "PredictedNo";
+
+  return base::StrCat({kCacheEvictionHistogramPrefix, ".", eviction_name,
+                       eviction_similarity_degree, ".", prediction_token});
+}
+
+constexpr base::FeatureParam<geolocation::CacheEvictionStrategy>::Option
+    kEvictionStrategyOptions[] = {
+        {geolocation::CacheEvictionStrategy::kWifiTolerance, "wifi_tolerance"},
+        {geolocation::CacheEvictionStrategy::kCommonWifi, "common_wifi"},
+        {geolocation::CacheEvictionStrategy::kCellularTolerance,
+         "cellular_tolerance"},
+        {geolocation::CacheEvictionStrategy::kCommonCell, "common_cell"},
+        {geolocation::CacheEvictionStrategy::kCommonWifiAndCell,
+         "common_wifi_and_cell"},
+};
+
+const base::FeatureParam<bool> kCacheEvictionFieldTrialModeParam{
+    &chromeos::features::kCachedLocationProvider, "field_trial_phase",
+    false  // Disabled by default
+};
+
+const base::FeatureParam<geolocation::CacheEvictionStrategy>
+    kCacheEvictionStrategyParam{
+        &chromeos::features::kCachedLocationProvider, "strategy",
+        geolocation::CacheEvictionStrategy::kWifiTolerance,
+        &kEvictionStrategyOptions};
+
+constexpr base::FeatureParam<geolocation::SimilarityDegree>::Option
+    kEvictionStrategyToleranceOptions[] = {
+        {geolocation::SimilarityDegree::kLoose, "loose_similarity"},
+        {geolocation::SimilarityDegree::kModerate, "moderate_similarity"},
+        {geolocation::SimilarityDegree::kStrict, "strict_similarity"},
+};
+
+const base::FeatureParam<geolocation::SimilarityDegree>
+    kCacheEvictionToleranceParam{&chromeos::features::kCachedLocationProvider,
+                                 "tolerance",
+                                 geolocation::SimilarityDegree::kLoose,
+                                 &kEvictionStrategyToleranceOptions};
+
+// Returns the `CacheEviction` strategy configured by the feature parameter -
+// `kCacheEvictionStrategyParam`.
+std::unique_ptr<geolocation::CacheEviction> CreateEvictionStrategy() {
+  geolocation::SimilarityDegree tolerance = kCacheEvictionToleranceParam.Get();
+
+  switch (kCacheEvictionStrategyParam.Get()) {
+    case geolocation::CacheEvictionStrategy::kCommonWifi:
+      return std::make_unique<geolocation::HasCommonWifiAP>();
+    case geolocation::CacheEvictionStrategy::kCellularTolerance:
+      return std::make_unique<geolocation::CellularEquivalenceWithTolerance>(
+          tolerance);
+    case geolocation::CacheEvictionStrategy::kCommonCell:
+      return std::make_unique<geolocation::HasCommonCellTower>();
+    case geolocation::CacheEvictionStrategy::kCommonWifiAndCell:
+      return std::make_unique<geolocation::HasCommonWifiApAndCellTower>();
+    case geolocation::CacheEvictionStrategy::kWifiTolerance:
+    default:
+      return std::make_unique<geolocation::WifiEquivalenceWithTolerance>(
+          tolerance);
+  }
+}
+
+}  // namespace
 
 CachedLocationProvider::GeopositionCache::GeopositionCache() = default;
 
@@ -36,8 +197,7 @@
 CachedLocationProvider::CachedLocationProvider(
     std::unique_ptr<LocationFetcher> location_fetcher)
     : LocationProvider(std::move(location_fetcher)),
-      cache_eviction_method_(
-          std::make_unique<geolocation::HasCommonWifiApAndCellTower>()) {}
+      cache_eviction_method_(CreateEvictionStrategy()) {}
 
 CachedLocationProvider::~CachedLocationProvider() = default;
 
@@ -62,10 +222,12 @@
 
   // Return cached position whenever possible.
   if (IsCacheUsable(*cache_to_use, context_to_use, eviction_to_use)) {
+    VLOG(1) << "Cache hit";
     std::move(callback).Run(cache_to_use->value().position, false,
                             base::Seconds(0));
     return;
   }
+  VLOG(1) << "Cache miss!";
 
   // Initiate a new network request. We bind `cache_to_use` (pointer to member)
   // so the correct cache is updated when the request completes.
@@ -76,10 +238,53 @@
                      std::move(context_to_use), std::move(callback)));
 }
 
+const std::vector<CachedLocationProvider::EvictionStrategyPair>*
+CachedLocationProvider::GetEvictionStrategiesUnderTest() {
+  static const base::NoDestructor<std::vector<EvictionStrategyPair>>
+      kEvictionList([] {
+        std::vector<EvictionStrategyPair> eviction_strategies;
+
+        // Pre-allocate to avoid resize overhead
+        eviction_strategies.reserve(9);
+
+        for (auto similarity_degree :
+             {geolocation::SimilarityDegree::kLoose,
+              geolocation::SimilarityDegree::kModerate,
+              geolocation::SimilarityDegree::kStrict}) {
+          eviction_strategies.emplace_back(
+              std::make_unique<geolocation::WifiEquivalenceWithTolerance>(
+                  similarity_degree),
+              std::make_optional(similarity_degree));
+          eviction_strategies.emplace_back(
+              std::make_unique<geolocation::CellularEquivalenceWithTolerance>(
+                  similarity_degree),
+              std::make_optional(similarity_degree));
+        }
+
+        eviction_strategies.emplace_back(
+            std::make_unique<geolocation::HasCommonWifiAP>(), std::nullopt);
+
+        eviction_strategies.emplace_back(
+            std::make_unique<geolocation::HasCommonCellTower>(), std::nullopt);
+
+        eviction_strategies.emplace_back(
+            std::make_unique<geolocation::HasCommonWifiApAndCellTower>(),
+            std::nullopt);
+
+        return eviction_strategies;
+      }());
+
+  return kEvictionList.get();
+}
+
 bool CachedLocationProvider::IsCacheUsable(
     const std::optional<GeopositionCache>& location_cache,
     const std::optional<geolocation::GeopositionContext>& new_context,
     const std::optional<geolocation::CacheEviction*> eviction_strategy) {
+  if (IsFieldTrialPhase()) {
+    return false;
+  }
+
   if (!location_cache) {
     return false;
   }
@@ -111,8 +316,15 @@
     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)));
+    VLOG(1) << (request_context ? "Precise " : "Coarse ") << "cache updated!";
+    GeopositionCache new_cache{position, base::TimeTicks::Now(),
+                               std::move(request_context)};
+
+    if (IsFieldTrialPhase() && *cache_to_use) {
+      ReportFieldTrialMetrics(cache_to_use->value(), new_cache);
+    }
+
+    *cache_to_use = std::move(new_cache);
   }
 
   std::move(callback).Run(position, server_error, elapsed);
@@ -127,4 +339,44 @@
   return current_context;
 }
 
+bool CachedLocationProvider::IsFieldTrialPhase() {
+  return kCacheEvictionFieldTrialModeParam.Get();
+}
+
+void CachedLocationProvider::ReportFieldTrialMetrics(
+    const GeopositionCache& old_cache,
+    const GeopositionCache& new_cache) {
+  if (!old_cache.context || !new_cache.context) {
+    return;
+  }
+
+  // Distance travelled between the requests.
+  auto distance_meters = CalculateDistance(
+      old_cache.position.latitude, old_cache.position.longitude,
+      new_cache.position.latitude, new_cache.position.longitude);
+
+  // Now that we know the distance travelled, collect fitness metrics on all
+  // eviction strategies under test.
+  for (const auto& strategy_pair : *GetEvictionStrategiesUnderTest()) {
+    const auto& strategy = strategy_pair.first;
+    const auto similarity_degree = strategy_pair.second;
+
+    bool prediction = strategy->IsSignificantDisplacementIndicated(
+        old_cache.context.value(), new_cache.context.value());
+
+    std::string histogram_name =
+        GetHistogramName(strategy->strategy(), similarity_degree, prediction);
+
+    // Use CustomHistogram for exact boundary control.
+    static const base::NoDestructor<std::vector<int>> kCustomRanges(
+        {1, 10, 100, 1000, 10000, 100000, 1000000});
+
+    base::HistogramBase* histogram = base::CustomHistogram::FactoryGet(
+        histogram_name, *kCustomRanges,
+        base::HistogramBase::kUmaTargetedHistogramFlag);
+
+    histogram->Add(static_cast<int>(distance_meters));
+  }
+}
+
 }  // namespace ash
diff --git a/chromeos/ash/components/geolocation/cached_location_provider.h b/chromeos/ash/components/geolocation/cached_location_provider.h
index 1fab222..ee54b73b 100644
--- a/chromeos/ash/components/geolocation/cached_location_provider.h
+++ b/chromeos/ash/components/geolocation/cached_location_provider.h
@@ -9,6 +9,7 @@
 
 #include "base/memory/weak_ptr.h"
 #include "base/time/time.h"
+#include "chromeos/ash/components/geolocation/cache_eviction_options.h"
 #include "chromeos/ash/components/geolocation/geoposition.h"
 #include "chromeos/ash/components/geolocation/geoposition_context.h"
 #include "chromeos/ash/components/geolocation/location_provider.h"
@@ -32,11 +33,22 @@
 // precise location calls, cached data is returned if the underlying wireless
 // signals have not changed significantly, based on defined displacement
 // criteria.
+//
+// NOTE: While `IsFieldTrialPhase()`, this class will behave just like
+// `LiveLocationProvider`, applying NO optimizations and serving all requests
+// with a real-time (live) location through the remote API calls.
+// During this phase fitness metrics on the selected `CacheEviction` methods
+// will be collected.
+//
 // 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:
+  using EvictionStrategyPair =
+      std::pair<std::unique_ptr<geolocation::CacheEviction>,
+                std::optional<geolocation::SimilarityDegree>>;
+
   explicit CachedLocationProvider(
       std::unique_ptr<LocationFetcher> location_fetcher);
   ~CachedLocationProvider() override;
@@ -47,11 +59,18 @@
                        bool use_cell_towers,
                        ResponseCallback callback) override;
 
+  // Exposes the list of eviction strategies used in the field trial.
+  static const std::vector<EvictionStrategyPair>*
+  GetEvictionStrategiesUnderTest();
+
   base::TimeDelta GetRateLimitForTesting() { return rate_limit_; }
+  geolocation::CacheEviction* GetEvictionStrategyForTesting() {
+    return cache_eviction_method_.get();
+  }
 
  private:
-  // Represents a single location cache entry. It combines the computed position
-  // with the context used to generate it.
+  // Represents a single location cache entry. It combines the computed
+  // position with the context used to generate it.
   struct GeopositionCache {
     // The actual latitude, longitude, and accuracy of the position.
     Geoposition position;
@@ -80,8 +99,8 @@
     ~GeopositionCache();
   };
 
-  // Checks if the specified `location_cache` is valid and fresh enough to serve
-  // the current request.
+  // 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
@@ -110,6 +129,28 @@
   // Retrieves the current Wi-Fi and cell tower data for precise location.
   geolocation::GeopositionContext GetPreciseLocationContext();
 
+  // Returns true if the provider is currently running a field trial
+  // experiment to measure fitness for each eviction strategy under test.
+  // During this phase, the caching optimizations are disabled and this class
+  // effectively mimics the `LiveLocationProvider` to collect the ground truth
+  // data.
+  // TODO(crbug.com/465074906): Rename to InMetricsCollectionMode().
+  bool IsFieldTrialPhase();
+
+  // Collects metrics for the selected cache eviction methods.
+  // For each `CacheEviction` strategy under test, populates 2 different UMA
+  // histograms:
+  // "<EvictionName>.PredictedYes" - emits actual displacement (in meters) if
+  //        the strategy predicts "Significant Displacement" on the
+  //        {`old_cache`,`new_cache`} pair.
+  // "<EvictionName>.PredictedNo" - emits actual displacement (in meters) if
+  //        the strategy DOES NOT predict "Significant Displacement" on the
+  //        {`old_cache`, `new_cache`} pair.
+  //
+  //  `CacheEvictionStrategy` enum lists all eviction strategies being tested.
+  void ReportFieldTrialMetrics(const GeopositionCache& old_cache,
+                               const GeopositionCache& new_cache);
+
   // 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
diff --git a/chromeos/ash/components/geolocation/cached_location_provider_unittest.cc b/chromeos/ash/components/geolocation/cached_location_provider_unittest.cc
index e3245c6f..ffcc8c1 100644
--- a/chromeos/ash/components/geolocation/cached_location_provider_unittest.cc
+++ b/chromeos/ash/components/geolocation/cached_location_provider_unittest.cc
@@ -4,15 +4,20 @@
 
 #include "chromeos/ash/components/geolocation/cached_location_provider.h"
 
+#include "base/containers/fixed_flat_map.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/strings/stringprintf.h"
+#include "base/test/metrics/histogram_tester.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/geolocation/cache_eviction_options.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 "chromeos/constants/chromeos_features.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"
@@ -47,17 +52,80 @@
   return cell_tower;
 }
 
+// MUST BE in line with the `kEvictionStrategyOptions` in
+// `cached_location_provider.cc`.
+constexpr std::string_view EvictionStrategyToParamString(
+    const geolocation::CacheEvictionStrategy strategy) {
+  switch (strategy) {
+    case geolocation::CacheEvictionStrategy::kWifiTolerance:
+      return "wifi_tolerance";
+    case geolocation::CacheEvictionStrategy::kCommonWifi:
+      return "common_wifi";
+    case geolocation::CacheEvictionStrategy::kCellularTolerance:
+      return "cellular_tolerance";
+    case geolocation::CacheEvictionStrategy::kCommonCell:
+      return "common_cell";
+    case geolocation::CacheEvictionStrategy::kCommonWifiAndCell:
+      return "common_wifi_and_cell";
+  }
+  NOTREACHED();
+}
+
+constexpr std::string_view EvictionStrategyToString(
+    const geolocation::CacheEvictionStrategy strategy) {
+  switch (strategy) {
+    case geolocation::CacheEvictionStrategy::kWifiTolerance:
+      return "WifiTolerance";
+    case geolocation::CacheEvictionStrategy::kCommonWifi:
+      return "CommonWifi";
+    case geolocation::CacheEvictionStrategy::kCellularTolerance:
+      return "CellularTolerance";
+    case geolocation::CacheEvictionStrategy::kCommonCell:
+      return "CommonCell";
+    case geolocation::CacheEvictionStrategy::kCommonWifiAndCell:
+      return "CommonWifiAndCell";
+  }
+  NOTREACHED();
+}
+
+constexpr std::string_view SimilarityDegreeToString(
+    const geolocation::SimilarityDegree similarity_degree) {
+  switch (similarity_degree) {
+    case geolocation::SimilarityDegree::kLoose:
+      return "Loose";
+    case geolocation::SimilarityDegree::kModerate:
+      return "Moderate";
+    case geolocation::SimilarityDegree::kStrict:
+      return "Strict";
+  }
+  NOTREACHED();
+}
+
+// Must be in line with the `kEvictionStrategyToleranceOptions` in
+// `cached_location_provider.cc`.
+constexpr std::string_view SimilarityDegreeToParamString(
+    const geolocation::SimilarityDegree similarity_degree) {
+  switch (similarity_degree) {
+    case geolocation::SimilarityDegree::kLoose:
+      return "loose_similarity";
+    case geolocation::SimilarityDegree::kModerate:
+      return "moderate_similarity";
+    case geolocation::SimilarityDegree::kStrict:
+      return "strict_similarity";
+  }
+  NOTREACHED();
+}
+
 }  // namespace
 
 namespace utils = geolocation::test_utils;
 
-class CachedLocationProviderTestBase
-    : public testing::TestWithParam<std::tuple<bool, bool>> {
+class CachedLocationProviderTestBase : public testing::Test {
  public:
-  CachedLocationProviderTestBase() : interceptor(&url_factory_) {
-    use_wifi_scan = std::get<0>(GetParam());
-    use_cellular_scan = std::get<1>(GetParam());
-  }
+  CachedLocationProviderTestBase(bool use_wifi_scan, bool use_cellular_scan)
+      : use_wifi_scan(use_wifi_scan),
+        use_cellular_scan(use_cellular_scan),
+        interceptor(&url_factory_) {}
 
   void SetUp() override {
     network_delegate.AddWifiAP(GetTestWifiAP());
@@ -131,7 +199,14 @@
   }
 };
 
-class CachedLocationProviderTest : public CachedLocationProviderTestBase {
+class CachedLocationProviderTest
+    : public CachedLocationProviderTestBase,
+      public testing::WithParamInterface<std::tuple<bool, bool>> {
+ public:
+  CachedLocationProviderTest()
+      : CachedLocationProviderTestBase(std::get<0>(GetParam()),
+                                       std::get<1>(GetParam())) {}
+
  private:
   base::test::SingleThreadTaskEnvironment task_environment;
 };
@@ -239,8 +314,312 @@
                                 std::get<1>(info.param));
     });
 
-class CachedLocationProviderMockTimeTest
+class CachedLocationProviderFieldTrialTestBase
     : public CachedLocationProviderTestBase {
+ public:
+  CachedLocationProviderFieldTrialTestBase(
+      bool use_wifi_scan,
+      bool use_cellular_scan,
+      bool is_field_trial_mode,
+      geolocation::CacheEvictionStrategy eviction_strategy =
+          geolocation::CacheEvictionStrategy::kWifiTolerance,
+      geolocation::SimilarityDegree similarity_degree =
+          geolocation::SimilarityDegree::kLoose)
+      : CachedLocationProviderTestBase(use_wifi_scan, use_cellular_scan),
+        is_field_trial_mode(is_field_trial_mode),
+        eviction_strategy(eviction_strategy),
+        similarity_degree(similarity_degree) {
+    feature_list_.InitAndEnableFeatureWithParameters(
+        chromeos::features::kCachedLocationProvider,
+        {
+            {"field_trial_phase", is_field_trial_mode ? "true" : "false"},
+            {"strategy",
+             std::string(EvictionStrategyToParamString(eviction_strategy))},
+            {"tolerance",
+             std::string(SimilarityDegreeToParamString(similarity_degree))},
+        });
+  }
+
+ protected:
+  const bool is_field_trial_mode;
+  const geolocation::CacheEvictionStrategy eviction_strategy;
+  const geolocation::SimilarityDegree similarity_degree;
+
+ private:
+  base::test::SingleThreadTaskEnvironment task_environment;
+  base::test::ScopedFeatureList feature_list_;
+};
+
+class CachedLocationProviderFieldTrialHistogramsTester
+    : public CachedLocationProviderFieldTrialTestBase,
+      public testing::WithParamInterface<std::tuple<bool, bool>> {
+ public:
+  CachedLocationProviderFieldTrialHistogramsTester()
+      : CachedLocationProviderFieldTrialTestBase(std::get<0>(GetParam()),
+                                                 std::get<1>(GetParam()),
+                                                 /*is_field_trial_mode=*/true) {
+  }
+
+ protected:
+  base::HistogramTester histogram_tester;
+
+  static constexpr char kCacheEvictionHistogramPrefix[] =
+      "ChromeOS.Geolocation.CacheEviction";
+
+  std::vector<std::string> GetFieldTrialHistogramNames() {
+    std::vector<std::string> histogram_names;
+    for (auto& strategy_pair :
+         *cached_location_provider->GetEvictionStrategiesUnderTest()) {
+      histogram_names.emplace_back(base::StrCat(
+          {kCacheEvictionHistogramPrefix, ".",
+           EvictionStrategyToString(strategy_pair.first->strategy()),
+           (strategy_pair.second
+                ? SimilarityDegreeToString(*strategy_pair.second)
+                : "")}));
+    }
+
+    return histogram_names;
+  }
+
+  std::string GetPredictedYesHistogramName(std::string_view histogram) {
+    return base::StrCat({histogram, ".", "PredictedYes"});
+  }
+  std::string GetPredictedNoHistogramName(std::string_view histogram) {
+    return base::StrCat({histogram, ".", "PredictedNo"});
+  }
+};
+
+TEST_P(CachedLocationProviderFieldTrialHistogramsTester,
+       CheckFieldTrialMetrics_SameLocation) {
+  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());
+  }
+
+  // First location fetch doesn't populate histograms.
+  EXPECT_TRUE(
+      histogram_tester.GetTotalCountsForPrefix(kCacheEvictionHistogramPrefix)
+          .empty());
+
+  // 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 made but expect the same position.
+    EXPECT_EQ(2U, interceptor.attempts());
+    EXPECT_EQ(first_position.latitude, second_position.latitude);
+    EXPECT_EQ(first_position.longitude, second_position.longitude);
+    EXPECT_EQ(first_position.accuracy, second_position.accuracy);
+  }
+
+  if (!IsForPreciseResolution(use_wifi_scan, use_cellular_scan)) {
+    // Coarse requests don't populate the eviction histograms.
+    EXPECT_TRUE(
+        histogram_tester.GetTotalCountsForPrefix(kCacheEvictionHistogramPrefix)
+            .empty());
+  } else {
+    for (auto histogram : GetFieldTrialHistogramNames()) {
+      auto predicted_no = GetPredictedNoHistogramName(histogram);
+      auto predicted_yes = GetPredictedYesHistogramName(histogram);
+
+      histogram_tester.ExpectTotalCount(predicted_no, 1);
+      histogram_tester.ExpectBucketCount(predicted_no, 0, 1);
+
+      histogram_tester.ExpectTotalCount(predicted_yes, 0);
+    }
+  }
+}
+
+TEST_P(CachedLocationProviderFieldTrialHistogramsTester,
+       CheckFieldTrialMetrics_DifferentLocation) {
+  // Configure Remote API to return the following location:
+  auto expected_position = Geoposition();
+  expected_position.latitude = 10;
+  expected_position.longitude = 10;
+  expected_position.accuracy = 100;
+  expected_position.status = Geoposition::STATUS_OK;
+  expected_position.timestamp = base::Time::Now();
+  ConfigureLocationResponse(expected_position);
+
+  Geoposition first_position;
+  // First request.
+  {
+    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());
+  }
+
+  // First location fetch doesn't populate histograms (the cache is empty).
+  EXPECT_TRUE(
+      histogram_tester.GetTotalCountsForPrefix(kCacheEvictionHistogramPrefix)
+          .empty());
+
+  // Configure Remote API to return diametrical position.
+  expected_position.latitude *= -1;
+  expected_position.longitude *= -1;
+  ConfigureLocationResponse(expected_position);
+
+  // 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 made.
+    EXPECT_EQ(2U, interceptor.attempts());
+    EXPECT_EQ(expected_position.latitude, second_position.latitude);
+    EXPECT_EQ(expected_position.longitude, second_position.longitude);
+  }
+
+  if (!IsForPreciseResolution(use_wifi_scan, use_cellular_scan)) {
+    // Coarse requests don't populate the eviction histograms.
+    EXPECT_TRUE(
+        histogram_tester.GetTotalCountsForPrefix(kCacheEvictionHistogramPrefix)
+            .empty());
+  } else {
+    for (auto histogram : GetFieldTrialHistogramNames()) {
+      auto predicted_no = GetPredictedNoHistogramName(histogram);
+      auto predicted_yes = GetPredictedYesHistogramName(histogram);
+
+      histogram_tester.ExpectTotalCount(predicted_no, 1);
+      histogram_tester.ExpectBucketCount(predicted_no, 1000000, 1);
+
+      histogram_tester.ExpectTotalCount(predicted_yes, 0);
+    }
+  }
+
+  // Empty out network context, this would lead to all eviction methods
+  // predicting significant displacement (emitting to *PredictedYes histograms).
+  network_delegate.RemoveWifiAP(GetTestWifiAP());
+  network_delegate.RemoveCellTower(GetTestCellTower());
+
+  // Third request.
+  {
+    base::test::TestFuture<const Geoposition&, bool, base::TimeDelta> future;
+    cached_location_provider->RequestLocation(kRequestTimeout, use_wifi_scan,
+                                              use_cellular_scan,
+                                              future.GetCallback());
+    auto third_position = future.Get<0>();
+
+    EXPECT_EQ(3U, interceptor.attempts());
+    if (!IsForPreciseResolution(use_wifi_scan, use_cellular_scan)) {
+      // Coarse requests don't populate the eviction histograms.
+      EXPECT_TRUE(histogram_tester
+                      .GetTotalCountsForPrefix(kCacheEvictionHistogramPrefix)
+                      .empty());
+    } else {
+      for (auto histogram : GetFieldTrialHistogramNames()) {
+        auto predicted_no = GetPredictedNoHistogramName(histogram);
+        auto predicted_yes = GetPredictedYesHistogramName(histogram);
+        // Check *PredictedNo stayed the same.
+        histogram_tester.ExpectTotalCount(predicted_no, 1);
+        histogram_tester.ExpectBucketCount(predicted_no, 1000000, 1);
+
+        // Check *PredictedYes was populated by this request.
+        histogram_tester.ExpectTotalCount(predicted_yes, 1);
+        histogram_tester.ExpectBucketCount(predicted_yes, 1000000, 1);
+      }
+    }
+  }
+}
+INSTANTIATE_TEST_SUITE_P(All,
+                         CachedLocationProviderFieldTrialHistogramsTester,
+                         testing::Combine(testing::Bool(), testing::Bool()));
+
+class CachedLocationProviderFieldTrialTest
+    : public CachedLocationProviderFieldTrialTestBase,
+      public testing::WithParamInterface<std::tuple<bool, bool>> {
+ public:
+  CachedLocationProviderFieldTrialTest()
+      : CachedLocationProviderFieldTrialTestBase(std::get<0>(GetParam()),
+                                                 std::get<1>(GetParam()),
+                                                 /*is_field_trial_mode=*/true) {
+  }
+};
+
+TEST_P(CachedLocationProviderFieldTrialTest, OptimizationsAreDisabled) {
+  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 made but expect the same position, only
+    // timestamp could differ.
+    EXPECT_EQ(2U, interceptor.attempts());
+    EXPECT_EQ(first_position.latitude, second_position.latitude);
+    EXPECT_EQ(first_position.longitude, second_position.longitude);
+    EXPECT_EQ(first_position.accuracy, second_position.accuracy);
+    EXPECT_EQ(first_position.status, second_position.status);
+    EXPECT_EQ(first_position.error_code, second_position.error_code);
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(All,
+                         CachedLocationProviderFieldTrialTest,
+                         testing::Combine(testing::Bool(), testing::Bool()));
+
+class CachedLocationProviderFieldTrialDisabledTest
+    : public CachedLocationProviderFieldTrialTestBase,
+      public testing::WithParamInterface<geolocation::CacheEvictionStrategy> {
+ public:
+  CachedLocationProviderFieldTrialDisabledTest()
+      : CachedLocationProviderFieldTrialTestBase(
+            /*use_wifi_scan=*/true,
+            /*use_cellular_scan=*/true,
+            /*is_field_trial_mode=*/false,
+            GetParam()) {}
+};
+
+TEST_P(CachedLocationProviderFieldTrialDisabledTest, CorrectEvictionUsed) {
+  CHECK_EQ(
+      eviction_strategy,
+      cached_location_provider->GetEvictionStrategyForTesting()->strategy());
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    All,
+    CachedLocationProviderFieldTrialDisabledTest,
+    testing::Values(geolocation::CacheEvictionStrategy::kWifiTolerance,
+                    geolocation::CacheEvictionStrategy::kCommonWifi,
+                    geolocation::CacheEvictionStrategy::kCellularTolerance,
+                    geolocation::CacheEvictionStrategy::kCommonCell,
+                    geolocation::CacheEvictionStrategy::kCommonWifiAndCell));
+
+class CachedLocationProviderMockTimeTest
+    : public CachedLocationProviderTestBase,
+      public testing::WithParamInterface<std::tuple<bool, bool>> {
+ public:
+  CachedLocationProviderMockTimeTest()
+      : CachedLocationProviderTestBase(std::get<0>(GetParam()),
+                                       std::get<1>(GetParam())) {}
+
  protected:
   base::test::SingleThreadTaskEnvironment task_environment{
       base::test::TaskEnvironment::TimeSource::MOCK_TIME};
diff --git a/chromeos/ash/experiences/arc/arc_features.cc b/chromeos/ash/experiences/arc/arc_features.cc
index 3dde1f89..aca283e 100644
--- a/chromeos/ash/experiences/arc/arc_features.cc
+++ b/chromeos/ash/experiences/arc/arc_features.cc
@@ -304,9 +304,6 @@
              "ArcVideoDecoder",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
-// Controls whether ARC uses MappableSharedImage for video encoding.
-BASE_FEATURE(kVideoEncodeUseMappableSI, base::FEATURE_ENABLED_BY_DEFAULT);
-
 // Feature to continuously log PSI memory pressure data to Chrome.
 BASE_FEATURE(kVmMemoryPSIReports,
              "ArcVmMemoryPSIReports",
diff --git a/chromeos/ash/experiences/arc/arc_features.h b/chromeos/ash/experiences/arc/arc_features.h
index cc58be386..029a4ca8 100644
--- a/chromeos/ash/experiences/arc/arc_features.h
+++ b/chromeos/ash/experiences/arc/arc_features.h
@@ -63,7 +63,6 @@
 BASE_DECLARE_FEATURE(kSyncInstallPriority);
 BASE_DECLARE_FEATURE(kUnthrottleOnActiveAudioV2);
 BASE_DECLARE_FEATURE(kVideoDecoder);
-BASE_DECLARE_FEATURE(kVideoEncodeUseMappableSI);
 BASE_DECLARE_FEATURE(kVmMemoryPSIReports);
 BASE_DECLARE_FEATURE_PARAM(int, kVmMemoryPSIReportsPeriod);
 BASE_DECLARE_FEATURE(kVmMemorySize);
diff --git a/chromeos/ash/experiences/arc/video_accelerator/gpu_arc_video_encode_accelerator.cc b/chromeos/ash/experiences/arc/video_accelerator/gpu_arc_video_encode_accelerator.cc
index 9f1413c7..8f71073 100644
--- a/chromeos/ash/experiences/arc/video_accelerator/gpu_arc_video_encode_accelerator.cc
+++ b/chromeos/ash/experiences/arc/video_accelerator/gpu_arc_video_encode_accelerator.cc
@@ -149,7 +149,7 @@
     return mojom::VideoEncodeAccelerator::Result::kInsufficientResourcesError;
   }
 
-  if (base::FeatureList::IsEnabled(kVideoEncodeUseMappableSI) && !sii_) {
+  if (!sii_) {
     DLOG(ERROR) << "Was passed null SharedImageInterface on construction";
     return mojom::VideoEncodeAccelerator::Result::kPlatformFailureError;
   }
@@ -241,31 +241,22 @@
     return;
   }
   scoped_refptr<media::VideoFrame> frame;
-  if (base::FeatureList::IsEnabled(kVideoEncodeUseMappableSI)) {
-    auto shared_image = sii_->CreateSharedImage(
-        {*si_format, visible_size_, gfx::ColorSpace(),
-         gpu::SHARED_IMAGE_USAGE_CPU_ONLY_READ_WRITE,
-         "GpuArcVideoEncodeAccelerator"},
-        gpu::kNullSurfaceHandle,
-        gfx::BufferUsage::VEA_READ_CAMERA_AND_CPU_READ_WRITE,
-        std::move(gmb_handle).value());
-    if (!shared_image) {
-      DLOG(ERROR) << "Failed to create mappable SharedImage";
-      client_->NotifyError(Error::kInvalidArgumentError);
-    }
-
-    frame = media::VideoFrame::WrapMappableSharedImage(
-        std::move(shared_image), gpu::SyncToken(), base::NullCallback(),
-        gfx::Rect(visible_size_), visible_size_, base::Microseconds(timestamp));
-  } else {
-    frame = media::VideoFrame::WrapExternalGpuMemoryBufferHandle(
-        gfx::Rect(visible_size_), visible_size_,
-        client_native_pixmap_factory_.get(), std::move(gmb_handle).value(),
-        coded_size_, *si_format,
-        gfx::BufferUsage::VEA_READ_CAMERA_AND_CPU_READ_WRITE,
-        base::Microseconds(timestamp));
+  auto shared_image = sii_->CreateSharedImage(
+      {*si_format, visible_size_, gfx::ColorSpace(),
+       gpu::SHARED_IMAGE_USAGE_CPU_ONLY_READ_WRITE,
+       "GpuArcVideoEncodeAccelerator"},
+      gpu::kNullSurfaceHandle,
+      gfx::BufferUsage::VEA_READ_CAMERA_AND_CPU_READ_WRITE,
+      std::move(gmb_handle).value());
+  if (!shared_image) {
+    DLOG(ERROR) << "Failed to create mappable SharedImage";
+    client_->NotifyError(Error::kInvalidArgumentError);
   }
 
+  frame = media::VideoFrame::WrapMappableSharedImage(
+      std::move(shared_image), gpu::SyncToken(), base::NullCallback(),
+      gfx::Rect(visible_size_), visible_size_, base::Microseconds(timestamp));
+
   if (!frame) {
     DLOG(ERROR) << "Failed to create VideoFrame";
     client_->NotifyError(Error::kInvalidArgumentError);
diff --git a/chromeos/components/cdm_factory_daemon/cdm_storage_adapter_unittest.cc b/chromeos/components/cdm_factory_daemon/cdm_storage_adapter_unittest.cc
index 470a862..ef4da94 100644
--- a/chromeos/components/cdm_factory_daemon/cdm_storage_adapter_unittest.cc
+++ b/chromeos/components/cdm_factory_daemon/cdm_storage_adapter_unittest.cc
@@ -12,6 +12,7 @@
 #include "chromeos/components/cdm_factory_daemon/mojom/cdm_storage.mojom.h"
 #include "media/mojo/mojom/cdm_storage.mojom.h"
 #include "media/mojo/mojom/frame_interface_factory.mojom.h"
+#include "mojo/public/cpp/bindings/receiver.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
diff --git a/chromeos/profiles/arm.afdo.newest.txt b/chromeos/profiles/arm.afdo.newest.txt
index d1b94d1..4772b93 100644
--- a/chromeos/profiles/arm.afdo.newest.txt
+++ b/chromeos/profiles/arm.afdo.newest.txt
@@ -1 +1 @@
-chromeos-chrome-arm-none-144-7531.0-1763955694-benchmark-144.0.7552.0_pre1551373-r1-redacted.afdo.xz
+chromeos-chrome-arm-none-144-7545.0-1764558928-benchmark-144.0.7558.0_pre1552116-r1-redacted.afdo.xz
diff --git a/chromeos/profiles/atom.afdo.newest.txt b/chromeos/profiles/atom.afdo.newest.txt
index 2b5e88f..cfdfc6650 100644
--- a/chromeos/profiles/atom.afdo.newest.txt
+++ b/chromeos/profiles/atom.afdo.newest.txt
@@ -1 +1 @@
-chromeos-chrome-amd64-atom-144-7531.0-1763955623-benchmark-144.0.7552.0_pre1551373-r1-redacted.afdo.xz
+chromeos-chrome-amd64-atom-144-7545.0-1764562669-benchmark-144.0.7558.0_pre1552116-r1-redacted.afdo.xz
diff --git a/chromeos/profiles/bigcore.afdo.newest.txt b/chromeos/profiles/bigcore.afdo.newest.txt
index fefc699..19c045c 100644
--- a/chromeos/profiles/bigcore.afdo.newest.txt
+++ b/chromeos/profiles/bigcore.afdo.newest.txt
@@ -1 +1 @@
-chromeos-chrome-amd64-bigcore-144-7531.0-1763956029-benchmark-144.0.7552.0_pre1551373-r1-redacted.afdo.xz
+chromeos-chrome-amd64-bigcore-144-7545.0-1764560377-benchmark-144.0.7558.0_pre1552116-r1-redacted.afdo.xz
diff --git a/clank b/clank
index 0072ba4..9c5c23a 160000
--- a/clank
+++ b/clank
@@ -1 +1 @@
-Subproject commit 0072ba49c188b839f19271436781d4d0ceeb77cb
+Subproject commit 9c5c23a8cfe0538d87debe05f72e848c017b3121
diff --git a/components/BUILD.gn b/components/BUILD.gn
index 53d0bf10..6558eb4 100644
--- a/components/BUILD.gn
+++ b/components/BUILD.gn
@@ -1337,7 +1337,6 @@
       "autofill/core/browser/ml_model/field_classification_model_executor_perftest.cc",
       "discardable_memory/common/discardable_shared_memory_heap_perftest.cc",
       "omnibox/browser/history_quick_provider_performance_unittest.cc",
-      "persistent_cache/persistent_cache_perftest.cc",
       "subresource_filter/core/common/perftests/indexed_ruleset_perftest.cc",
       "test/run_all_perftests.cc",
       "visitedlink/test/visitedlink_perftest.cc",
@@ -1353,8 +1352,6 @@
       "//components/history/core/test",
       "//components/omnibox/browser",
       "//components/omnibox/browser:test_support",
-      "//components/persistent_cache",
-      "//components/persistent_cache:test_support",
       "//components/subresource_filter/core/common",
       "//components/subresource_filter/tools:tools_lib",
       "//components/test:test_support",
@@ -1396,6 +1393,10 @@
       ]
     }
 
+    if (!is_fuchsia) {
+      deps += [ "//components/persistent_cache:perf_tests" ]
+    }
+
     # TODO(crbug.com/40031409): Fix code that adds exit-time destructors and
     # enable the diagnostic by removing this line.
     configs += [ "//build/config/compiler:no_exit_time_destructors" ]
diff --git a/components/autofill/core/browser/crowdsourcing/autofill_crowdsourcing_manager_unittest.cc b/components/autofill/core/browser/crowdsourcing/autofill_crowdsourcing_manager_unittest.cc
index 7c72f24..ed93e7e 100644
--- a/components/autofill/core/browser/crowdsourcing/autofill_crowdsourcing_manager_unittest.cc
+++ b/components/autofill/core/browser/crowdsourcing/autofill_crowdsourcing_manager_unittest.cc
@@ -1557,7 +1557,13 @@
 
 using AutofillUploadTest = AutofillServerCommunicationTest;
 
-TEST_P(AutofillUploadTest, RichMetadata) {
+// Flaky on fuchsia bots, see crbug.com/446943496.
+#if BUILDFLAG(IS_FUCHSIA)
+#define MAYBE_RichMetadata DISABLED_RichMetadata
+#else
+#define MAYBE_RichMetadata RichMetadata
+#endif
+TEST_P(AutofillUploadTest, MAYBE_RichMetadata) {
   base::test::ScopedFeatureList local_feature;
 
   FormData form;
@@ -1679,7 +1685,13 @@
   }
 }
 
-TEST_P(AutofillUploadTest, Throttling) {
+// Flaky on fuchsia bots, see crbug.com/446943496.
+#if BUILDFLAG(IS_FUCHSIA)
+#define MAYBE_Throttling DISABLED_Throttling
+#else
+#define MAYBE_Throttling Throttling
+#endif
+TEST_P(AutofillUploadTest, MAYBE_Throttling) {
   ASSERT_NE(DISABLED, GetParam());
 
   AutofillCrowdsourcingManager crowdsourcing_manager(
@@ -1727,7 +1739,15 @@
   }
 }
 
-TEST_P(AutofillUploadTest, ThrottlingStructuralFormSignatures) {
+// Flaky on fuchsia bots, see crbug.com/446943496.
+#if BUILDFLAG(IS_FUCHSIA)
+#define MAYBE_ThrottlingStructuralFormSignatures \
+  DISABLED_ThrottlingStructuralFormSignatures
+#else
+#define MAYBE_ThrottlingStructuralFormSignatures \
+  ThrottlingStructuralFormSignatures
+#endif
+TEST_P(AutofillUploadTest, MAYBE_ThrottlingStructuralFormSignatures) {
   ASSERT_NE(DISABLED, GetParam());
 
   AutofillCrowdsourcingManager crowdsourcing_manager(
@@ -1784,7 +1804,15 @@
 // Tests that votes are not throttled with
 // `features::debug::kAutofillUploadThrottling` disabled, but metadata is
 // throttled regardless of the feature state.
-TEST_P(AutofillUploadTest, SuccessfulSubmissionOnDisabledThrottling) {
+// Flaky on fuchsia bots, see crbug.com/446943496.
+#if BUILDFLAG(IS_FUCHSIA)
+#define MAYBE_SuccessfulSubmissionOnDisabledThrottling \
+  DISABLED_SuccessfulSubmissionOnDisabledThrottling
+#else
+#define MAYBE_SuccessfulSubmissionOnDisabledThrottling \
+  SuccessfulSubmissionOnDisabledThrottling
+#endif
+TEST_P(AutofillUploadTest, MAYBE_SuccessfulSubmissionOnDisabledThrottling) {
   base::test::ScopedFeatureList feature_list;
   feature_list.InitWithFeatures(
       /*enabled_features=*/{},
@@ -1854,7 +1882,13 @@
       request.upload().field_data(2).randomized_field_metadata().has_label());
 }
 
-TEST_P(AutofillUploadTest, PeriodicReset) {
+// Flaky on fuchsia bots, see crbug.com/446943496.
+#if BUILDFLAG(IS_FUCHSIA)
+#define MAYBE_PeriodicReset DISABLED_PeriodicReset
+#else
+#define MAYBE_PeriodicReset PeriodicReset
+#endif
+TEST_P(AutofillUploadTest, MAYBE_PeriodicReset) {
   ASSERT_NE(DISABLED, GetParam());
 
   base::test::ScopedFeatureList local_feature;
@@ -1964,7 +1998,15 @@
 
 // Tests that password manager uploads will have metadata part of the upload
 // throttled, but the vote part of the upload will be sent to the server.
-TEST_P(AutofillUploadTest, ThrottleMetadataOnPasswordManagerUploads) {
+// Flaky on fuchsia bots, see crbug.com/446943496.
+#if BUILDFLAG(IS_FUCHSIA)
+#define MAYBE_ThrottleMetadataOnPasswordManagerUploads \
+  DISABLED_ThrottleMetadataOnPasswordManagerUploads
+#else
+#define MAYBE_ThrottleMetadataOnPasswordManagerUploads \
+  ThrottleMetadataOnPasswordManagerUploads
+#endif
+TEST_P(AutofillUploadTest, MAYBE_ThrottleMetadataOnPasswordManagerUploads) {
   FormStructure form_structure(
       test::GetFormData({.fields = {{.role = USERNAME}, {.role = PASSWORD}}}));
   SetCorrectFieldHostFormSignatures(form_structure);
diff --git a/components/autofill/core/browser/data_manager/payments/payments_data_manager.cc b/components/autofill/core/browser/data_manager/payments/payments_data_manager.cc
index b2486fa95..e3561d3 100644
--- a/components/autofill/core/browser/data_manager/payments/payments_data_manager.cc
+++ b/components/autofill/core/browser/data_manager/payments/payments_data_manager.cc
@@ -269,7 +269,7 @@
 #if !BUILDFLAG(IS_IOS)
     // Clean up for crbug.com/411681430.
     if (!IsPaymentCvcStorageEnabled()) {
-      CleanupForCrbug411681430();
+      ClearLocalCvcsUpToMay2025();
     }
 #endif
   }
@@ -1589,12 +1589,12 @@
   Refresh();
 }
 
-void PaymentsDataManager::CleanupForCrbug411681430() {
+void PaymentsDataManager::ClearLocalCvcsUpToMay2025() {
   if (!GetLocalDatabase()) {
     return;
   }
 
-  GetLocalDatabase()->CleanupForCrbug411681430();
+  GetLocalDatabase()->ClearLocalCvcsUpToMay2025();
 
   // Refresh our local cache and send notifications to observers.
   Refresh();
diff --git a/components/autofill/core/browser/data_manager/payments/payments_data_manager.h b/components/autofill/core/browser/data_manager/payments/payments_data_manager.h
index ef51e38..3e8f561 100644
--- a/components/autofill/core/browser/data_manager/payments/payments_data_manager.h
+++ b/components/autofill/core/browser/data_manager/payments/payments_data_manager.h
@@ -310,8 +310,9 @@
   // Method to clear all local CVCs from the local web database.
   virtual void ClearLocalCvcs();
 
-  // Method to clean up for crbug.com/411681430.
-  virtual void CleanupForCrbug411681430();
+  // Method to clear all local CVCs created before mid-May 2025. For more
+  // information, see crbug.com/411681430.
+  virtual void ClearLocalCvcsUpToMay2025();
 
 #if BUILDFLAG(IS_IOS)
   // Method to clean up for crbug.com/445879524.
diff --git a/components/autofill/core/browser/data_manager/payments/payments_data_manager_unittest.cc b/components/autofill/core/browser/data_manager/payments/payments_data_manager_unittest.cc
index ddc4791..eb9d168 100644
--- a/components/autofill/core/browser/data_manager/payments/payments_data_manager_unittest.cc
+++ b/components/autofill/core/browser/data_manager/payments/payments_data_manager_unittest.cc
@@ -268,7 +268,7 @@
 class PaymentsDataManagerTest : public PaymentsDataManagerHelper,
                                 public testing::Test {
  public:
-  long kCleanupForCrbug411681430LongTimestamp = 1747828800;
+  long kClearTimestampForLocalCvcs = 1747828800;  // May 21, 2025.
 
   PaymentsDataManagerTest() {
     scoped_feature_list_.InitWithFeatures(
@@ -739,8 +739,8 @@
 }
 
 #if !BUILDFLAG(IS_IOS)
-// Test that clean up for crbug.com/411681430 is working as expected.
-TEST_F(PaymentsDataManagerTest, CleanupForCrbug411681430Test) {
+// Test that cleanup for crbug.com/411681430 is working as expected.
+TEST_F(PaymentsDataManagerTest, ClearLocalCvcsUpToMay2025) {
   base::test::ScopedFeatureList features(
       features::kAutofillEnableCvcStorageAndFilling);
 
@@ -754,11 +754,11 @@
   payments_data_manager().AddCreditCard(credit_card_1);
   WaitForOnPaymentsDataChanged();
 
-  AdvanceClock((base::Time::FromSecondsSinceUnixEpoch(
-                   kCleanupForCrbug411681430LongTimestamp + 1)) -
-               base::Time::Now());
+  AdvanceClock(
+      (base::Time::FromSecondsSinceUnixEpoch(kClearTimestampForLocalCvcs + 1)) -
+      base::Time::Now());
   // Add another credit card with timestamp later than
-  // `kCleanupForCrbug411681430` timestamp to the database.
+  // `kClearTimestampForLocalCvcs` timestamp to the database.
   CreditCard credit_card_2(base::Uuid::GenerateRandomV4().AsLowercaseString(),
                            test::kEmptyOrigin);
   test::SetCreditCardInfo(&credit_card_2, "John Doe",
diff --git a/components/autofill/core/browser/single_field_fillers/payments/merchant_promo_code_manager.cc b/components/autofill/core/browser/single_field_fillers/payments/merchant_promo_code_manager.cc
index 4ecad5cb1..25372b3 100644
--- a/components/autofill/core/browser/single_field_fillers/payments/merchant_promo_code_manager.cc
+++ b/components/autofill/core/browser/single_field_fillers/payments/merchant_promo_code_manager.cc
@@ -63,23 +63,4 @@
   return suggestions_generated;
 }
 
-void MerchantPromoCodeManager::SendPromoCodeSuggestions(
-    std::vector<const AutofillOfferData*> promo_code_offers,
-    const FormFieldData& field,
-    SingleFieldFillRouter::OnSuggestionsReturnedCallback
-        on_suggestions_returned) {
-  // If the input box content equals any of the available promo codes, then
-  // assume the promo code has been filled, and don't show any suggestions.
-  for (const AutofillOfferData* promo_code_offer : promo_code_offers) {
-    if (field.value() == base::ASCIIToUTF16(promo_code_offer->GetPromoCode())) {
-      std::move(on_suggestions_returned).Run(field.global_id(), {});
-      return;
-    }
-  }
-
-  std::move(on_suggestions_returned)
-      .Run(field.global_id(),
-           GetPromoCodeSuggestionsFromPromoCodeOffers(promo_code_offers));
-}
-
 }  // namespace autofill
diff --git a/components/autofill/core/browser/single_field_fillers/payments/merchant_promo_code_manager.h b/components/autofill/core/browser/single_field_fillers/payments/merchant_promo_code_manager.h
index fdc34d2..b70ced22 100644
--- a/components/autofill/core/browser/single_field_fillers/payments/merchant_promo_code_manager.h
+++ b/components/autofill/core/browser/single_field_fillers/payments/merchant_promo_code_manager.h
@@ -70,18 +70,6 @@
       MerchantPromoCodeManagerTest,
       DoesNotShowPromoCodeOffersIfPaymentsDataManagerDoesNotExist);
 
-  // Generates suggestions from the `promo_code_offers` and return them via
-  // `on_suggestions_returned`. If suggestions were sent, this function also
-  // logs metrics for promo code suggestions shown. Data is filtered based on
-  // the `field`'s value`. For metrics, this ensures we log the correct
-  // histogram, as we have separate histograms for unique shows and repetitive
-  // shows.
-  void SendPromoCodeSuggestions(
-      std::vector<const AutofillOfferData*> promo_code_offers,
-      const FormFieldData& field,
-      SingleFieldFillRouter::OnSuggestionsReturnedCallback
-          on_suggestions_returned);
-
   raw_ptr<PaymentsDataManager> payments_data_manager_;
   bool is_off_the_record_;
 };
diff --git a/components/autofill/core/browser/suggestions/payments/iban_suggestion_generator.cc b/components/autofill/core/browser/suggestions/payments/iban_suggestion_generator.cc
index cd84f90b..cae1146 100644
--- a/components/autofill/core/browser/suggestions/payments/iban_suggestion_generator.cc
+++ b/components/autofill/core/browser/suggestions/payments/iban_suggestion_generator.cc
@@ -13,6 +13,8 @@
 #include "components/autofill/core/browser/integrators/optimization_guide/autofill_optimization_guide_decider.h"
 #include "components/autofill/core/browser/payments/iban_manager.h"
 #include "components/autofill/core/browser/suggestions/payments/payments_suggestion_generator.h"
+#include "components/grit/components_scaled_resources.h"
+#include "ui/base/resource/resource_bundle.h"
 
 namespace autofill {
 namespace {
@@ -20,6 +22,69 @@
 // absent and the length of the input field is less than
 // `kFieldLengthLimitOnServerIbanSuggestion` characters.
 constexpr int kFieldLengthLimitOnServerIbanSuggestion = 6;
+
+// Generates a footer suggestion "Manage payment methods..." menu item which
+// will redirect to Chrome payment settings page.
+//
+// The difference between `CreateManageCreditCardsSuggestion()` and
+// `CreateManageIbansSuggestion()` is that they use a different
+// `SuggestionType`. This distinction is needed for metrics recording.
+Suggestion CreateManageIbansSuggestion() {
+  return CreateManagePaymentMethodsEntry(SuggestionType::kManageIban,
+                                         /*with_gpay_logo=*/false);
+}
+
+// Generates suggestions for all available IBANs.
+std::vector<Suggestion> GetSuggestionsForIbans(const std::vector<Iban>& ibans) {
+  if (ibans.empty()) {
+    return {};
+  }
+  std::vector<Suggestion> suggestions;
+  suggestions.reserve(ibans.size() + 2);
+  for (const Iban& iban : ibans) {
+    Suggestion suggestion(SuggestionType::kIbanEntry);
+    suggestion.custom_icon =
+        ui::ResourceBundle::GetSharedInstance().GetImageNamed(
+            ShouldUseNewFopDisplay() ? IDR_AUTOFILL_IBAN
+                                     : IDR_AUTOFILL_IBAN_OLD);
+    suggestion.icon = Suggestion::Icon::kIban;
+    if (iban.record_type() == Iban::kLocalIban) {
+      suggestion.payload = Suggestion::Guid(iban.guid());
+    } else {
+      CHECK(iban.record_type() == Iban::kServerIban);
+      suggestion.payload = Suggestion::InstrumentId(iban.instrument_id());
+    }
+
+    std::u16string iban_identifier =
+        iban.GetIdentifierStringForAutofillDisplay();
+    if constexpr (BUILDFLAG(IS_ANDROID)) {
+      // For Android keyboard accessory, the displayed value will be nickname +
+      // identifier string, if the nickname is too long to fit due to bubble
+      // width limitation, it will be truncated.
+      if (!iban.nickname().empty()) {
+        suggestion.main_text.value = iban.nickname();
+        suggestion.minor_texts.emplace_back(iban_identifier);
+      } else {
+        suggestion.main_text.value = std::move(iban_identifier);
+      }
+    } else {
+      if (iban.nickname().empty()) {
+        suggestion.main_text = Suggestion::Text(
+            iban_identifier, Suggestion::Text::IsPrimary(true));
+      } else {
+        suggestion.main_text = Suggestion::Text(
+            iban.nickname(), Suggestion::Text::IsPrimary(true));
+        suggestion.labels = {{Suggestion::Text(iban_identifier)}};
+      }
+    }
+    suggestions.push_back(suggestion);
+  }
+
+  suggestions.emplace_back(SuggestionType::kSeparator);
+  suggestions.push_back(CreateManageIbansSuggestion());
+  return suggestions;
+}
+
 }  // namespace
 
 IbanSuggestionGenerator::IbanSuggestionGenerator() = default;
diff --git a/components/autofill/core/browser/suggestions/payments/iban_suggestion_generator_unittest.cc b/components/autofill/core/browser/suggestions/payments/iban_suggestion_generator_unittest.cc
index ab47746..599936c8 100644
--- a/components/autofill/core/browser/suggestions/payments/iban_suggestion_generator_unittest.cc
+++ b/components/autofill/core/browser/suggestions/payments/iban_suggestion_generator_unittest.cc
@@ -12,6 +12,7 @@
 #include "components/autofill/core/browser/form_structure_test_api.h"
 #include "components/autofill/core/browser/foundations/test_autofill_client.h"
 #include "components/autofill/core/browser/payments/test/mock_iban_manager.h"
+#include "components/autofill/core/browser/suggestions/suggestion_generator.h"
 #include "components/autofill/core/browser/suggestions/suggestion_type.h"
 #include "components/autofill/core/browser/test_utils/autofill_test_utils.h"
 #include "components/autofill/core/common/autofill_features.h"
@@ -27,10 +28,47 @@
 namespace {
 
 using ::testing::_;
+using ::testing::AllOf;
+using ::testing::Field;
+using ::testing::Matcher;
 
 constexpr char kNickname_0[] = "Nickname 0";
 constexpr char kNickname_1[] = "Nickname 1";
 
+Matcher<Suggestion> EqualsIbanSuggestion(
+    const std::u16string& identifier_string,
+    const Suggestion::Payload& payload,
+    const std::u16string& nickname) {
+  if constexpr (BUILDFLAG(IS_ANDROID)) {
+    if (nickname.empty()) {
+      return AllOf(
+          Field(&Suggestion::type, SuggestionType::kIbanEntry),
+          Field(&Suggestion::main_text, Suggestion::Text(identifier_string)),
+          Field(&Suggestion::payload, payload));
+    }
+    return AllOf(Field(&Suggestion::type, SuggestionType::kIbanEntry),
+                 Field(&Suggestion::main_text, Suggestion::Text(nickname)),
+                 Field(&Suggestion::minor_texts,
+                       std::vector<Suggestion::Text>{
+                           Suggestion::Text(identifier_string)}),
+                 Field(&Suggestion::payload, payload));
+  }
+  if (nickname.empty()) {
+    return AllOf(Field(&Suggestion::type, SuggestionType::kIbanEntry),
+                 Field(&Suggestion::main_text,
+                       Suggestion::Text(identifier_string,
+                                        Suggestion::Text::IsPrimary(true))),
+                 Field(&Suggestion::payload, payload));
+  }
+  return AllOf(
+      Field(&Suggestion::type, SuggestionType::kIbanEntry),
+      Field(&Suggestion::main_text,
+            Suggestion::Text(nickname, Suggestion::Text::IsPrimary(true))),
+      Field(&Suggestion::payload, payload),
+      Field(&Suggestion::labels, std::vector<std::vector<Suggestion::Text>>{
+                                     {Suggestion::Text(identifier_string)}}));
+}
+
 class IbanSuggestionGeneratorTest : public testing::Test,
                                     public testing::WithParamInterface<bool> {
  protected:
@@ -134,6 +172,29 @@
     return footer_suggestion;
   }
 
+  std::vector<Suggestion> GetSuggestionsForIbans(
+      const std::vector<Iban>& ibans) {
+    IbanSuggestionGenerator generator;
+    std::vector<Suggestion> suggestions;
+
+    auto on_suggestions_generated =
+        [&suggestions](
+            SuggestionGenerator::ReturnedSuggestions returned_suggestions) {
+          suggestions = returned_suggestions.second;
+        };
+
+    std::vector<SuggestionGenerator::SuggestionData> suggestion_data(
+        ibans.begin(), ibans.end());
+    base::flat_map<SuggestionGenerator::SuggestionDataSource,
+                   std::vector<SuggestionGenerator::SuggestionData>>
+        fetched_data = {{SuggestionGenerator::SuggestionDataSource::kIban,
+                         std::move(suggestion_data)}};
+    generator.GenerateSuggestions(form().ToFormData(), field(), &form(),
+                                  &field(), client(), fetched_data,
+                                  on_suggestions_generated);
+    return suggestions;
+  }
+
  private:
   base::test::SingleThreadTaskEnvironment task_environment_;
   test::AutofillUnitTestEnvironment autofill_test_environment_;
@@ -205,5 +266,134 @@
   task_environment().RunUntilIdle();
 }
 
+TEST_P(IbanSuggestionGeneratorTest, GetLocalIbanSuggestions) {
+  auto MakeLocalIban = [](const std::u16string& value,
+                          const std::u16string& nickname) {
+    Iban iban(Iban::Guid(base::Uuid::GenerateRandomV4().AsLowercaseString()));
+    iban.set_value(value);
+    if (!nickname.empty()) {
+      iban.set_nickname(nickname);
+    }
+    return iban;
+  };
+  Iban iban0 =
+      MakeLocalIban(u"CH56 0483 5012 3456 7800 9", u"My doctor's IBAN");
+  Iban iban1 =
+      MakeLocalIban(u"DE91 1000 0000 0123 4567 89", u"My brother's IBAN");
+  Iban iban2 =
+      MakeLocalIban(u"GR96 0810 0010 0000 0123 4567 890", u"My teacher's IBAN");
+  Iban iban3 = MakeLocalIban(u"PK70 BANK 0000 1234 5678 9000", u"");
+
+  std::vector<Suggestion> iban_suggestions =
+      GetSuggestionsForIbans({iban0, iban1, iban2, iban3});
+
+  // There are 6 suggestions, 4 for IBAN suggestions, followed by a separator,
+  // and followed by "Manage payment methods..." which redirects to the Chrome
+  // payment methods settings page.
+  ASSERT_EQ(iban_suggestions.size(), 6u);
+
+  EXPECT_THAT(
+      iban_suggestions[0],
+      EqualsIbanSuggestion(iban0.GetIdentifierStringForAutofillDisplay(),
+                           Suggestion::Guid(iban0.guid()), iban0.nickname()));
+
+  EXPECT_THAT(
+      iban_suggestions[1],
+      EqualsIbanSuggestion(iban1.GetIdentifierStringForAutofillDisplay(),
+                           Suggestion::Guid(iban1.guid()), iban1.nickname()));
+
+  EXPECT_THAT(
+      iban_suggestions[2],
+      EqualsIbanSuggestion(iban2.GetIdentifierStringForAutofillDisplay(),
+                           Suggestion::Guid(iban2.guid()), iban2.nickname()));
+
+  EXPECT_THAT(
+      iban_suggestions[3],
+      EqualsIbanSuggestion(iban3.GetIdentifierStringForAutofillDisplay(),
+                           Suggestion::Guid(iban3.guid()), iban3.nickname()));
+
+  EXPECT_EQ(iban_suggestions[4].type, SuggestionType::kSeparator);
+
+  EXPECT_EQ(iban_suggestions[5].main_text.value,
+            l10n_util::GetStringUTF16(IDS_AUTOFILL_MANAGE_PAYMENT_METHODS));
+  EXPECT_EQ(iban_suggestions[5].type, SuggestionType::kManageIban);
+}
+
+TEST_P(IbanSuggestionGeneratorTest, GetServerIbanSuggestions) {
+  Iban server_iban1 = test::GetServerIban();
+  Iban server_iban2 = test::GetServerIban2();
+  Iban server_iban3 = test::GetServerIban3();
+
+  std::vector<Suggestion> iban_suggestions =
+      GetSuggestionsForIbans({server_iban1, server_iban2, server_iban3});
+
+  // There are 5 suggestions, 3 for IBAN suggestions, followed by a separator,
+  // and followed by "Manage payment methods..." which redirects to the Chrome
+  // payment methods settings page.
+  ASSERT_EQ(iban_suggestions.size(), 5u);
+
+  EXPECT_THAT(iban_suggestions[0],
+              EqualsIbanSuggestion(
+                  server_iban1.GetIdentifierStringForAutofillDisplay(),
+                  Suggestion::InstrumentId(server_iban1.instrument_id()),
+                  server_iban1.nickname()));
+
+  EXPECT_THAT(iban_suggestions[1],
+              EqualsIbanSuggestion(
+                  server_iban2.GetIdentifierStringForAutofillDisplay(),
+                  Suggestion::InstrumentId(server_iban2.instrument_id()),
+                  server_iban2.nickname()));
+
+  EXPECT_THAT(iban_suggestions[2],
+              EqualsIbanSuggestion(
+                  server_iban3.GetIdentifierStringForAutofillDisplay(),
+                  Suggestion::InstrumentId(server_iban3.instrument_id()),
+                  server_iban3.nickname()));
+
+  EXPECT_EQ(iban_suggestions[3].type, SuggestionType::kSeparator);
+
+  EXPECT_EQ(iban_suggestions[4].main_text.value,
+            l10n_util::GetStringUTF16(IDS_AUTOFILL_MANAGE_PAYMENT_METHODS));
+  EXPECT_EQ(iban_suggestions[4].type, SuggestionType::kManageIban);
+}
+
+TEST_P(IbanSuggestionGeneratorTest, GetLocalAndServerIbanSuggestions) {
+  Iban server_iban1 = test::GetServerIban();
+  Iban server_iban2 = test::GetServerIban2();
+  Iban local_iban1 = test::GetLocalIban();
+
+  std::vector<Suggestion> iban_suggestions =
+      GetSuggestionsForIbans({server_iban1, server_iban2, local_iban1});
+
+  // There are 5 suggestions, 3 for IBAN suggestions, followed by a separator,
+  // and followed by "Manage payment methods..." which redirects to the Chrome
+  // payment methods settings page.
+  ASSERT_EQ(iban_suggestions.size(), 5u);
+
+  EXPECT_THAT(iban_suggestions[0],
+              EqualsIbanSuggestion(
+                  server_iban1.GetIdentifierStringForAutofillDisplay(),
+                  Suggestion::InstrumentId(server_iban1.instrument_id()),
+                  server_iban1.nickname()));
+
+  EXPECT_THAT(iban_suggestions[1],
+              EqualsIbanSuggestion(
+                  server_iban2.GetIdentifierStringForAutofillDisplay(),
+                  Suggestion::InstrumentId(server_iban2.instrument_id()),
+                  server_iban2.nickname()));
+
+  EXPECT_THAT(
+      iban_suggestions[2],
+      EqualsIbanSuggestion(local_iban1.GetIdentifierStringForAutofillDisplay(),
+                           Suggestion::Guid(local_iban1.guid()),
+                           local_iban1.nickname()));
+
+  EXPECT_EQ(iban_suggestions[3].type, SuggestionType::kSeparator);
+
+  EXPECT_EQ(iban_suggestions[4].main_text.value,
+            l10n_util::GetStringUTF16(IDS_AUTOFILL_MANAGE_PAYMENT_METHODS));
+  EXPECT_EQ(iban_suggestions[4].type, SuggestionType::kManageIban);
+}
+
 }  // namespace
 }  // namespace autofill
diff --git a/components/autofill/core/browser/suggestions/payments/merchant_promo_code_suggestion_generator.cc b/components/autofill/core/browser/suggestions/payments/merchant_promo_code_suggestion_generator.cc
index 1ce0a1c0..ff7a4c60e 100644
--- a/components/autofill/core/browser/suggestions/payments/merchant_promo_code_suggestion_generator.cc
+++ b/components/autofill/core/browser/suggestions/payments/merchant_promo_code_suggestion_generator.cc
@@ -6,12 +6,73 @@
 
 #include "base/containers/to_vector.h"
 #include "base/functional/function_ref.h"
+#include "base/strings/string_number_conversions.h"
 #include "base/strings/utf_string_conversions.h"
 #include "components/autofill/core/browser/data_manager/payments/payments_data_manager.h"
 #include "components/autofill/core/browser/form_structure.h"
 #include "components/autofill/core/browser/suggestions/payments/payments_suggestion_generator.h"
+#include "components/strings/grit/components_strings.h"
+#include "ui/base/l10n/l10n_util.h"
 
 namespace autofill {
+namespace {
+
+// Converts the vector of promo code offers that is passed in to a vector of
+// suggestions that can be displayed to the user for a promo code field.
+std::vector<Suggestion> GetPromoCodeSuggestionsFromPromoCodeOffers(
+    const std::vector<const AutofillOfferData*>& promo_code_offers) {
+  std::vector<Suggestion> suggestions;
+  GURL footer_offer_details_url;
+  for (const AutofillOfferData* promo_code_offer : promo_code_offers) {
+    // For each promo code, create a suggestion.
+    suggestions.emplace_back(
+        base::ASCIIToUTF16(promo_code_offer->GetPromoCode()),
+        SuggestionType::kMerchantPromoCodeEntry);
+    Suggestion& suggestion = suggestions.back();
+    if (!promo_code_offer->GetDisplayStrings().value_prop_text.empty()) {
+      suggestion.labels = {{Suggestion::Text(base::ASCIIToUTF16(
+          promo_code_offer->GetDisplayStrings().value_prop_text))}};
+    }
+    suggestion.payload =
+        Suggestion::Guid(base::NumberToString(promo_code_offer->GetOfferId()));
+
+    // Every offer for a given merchant leads to the same GURL, so we grab the
+    // first offer's offer details url as the payload for the footer to set
+    // later.
+    if (footer_offer_details_url.is_empty() &&
+        !promo_code_offer->GetOfferDetailsUrl().is_empty() &&
+        promo_code_offer->GetOfferDetailsUrl().is_valid()) {
+      footer_offer_details_url = promo_code_offer->GetOfferDetailsUrl();
+    }
+  }
+
+  // Ensure that there are suggestions and that we were able to find at least
+  // one suggestion with a valid offer details url before adding the footer.
+  DCHECK(suggestions.size() > 0);
+  if (!footer_offer_details_url.is_empty()) {
+    // Add the footer separator since we will now have a footer in the offers
+    // suggestions popup.
+    suggestions.emplace_back(SuggestionType::kSeparator);
+
+    // Add the footer suggestion that navigates the user to the promo code
+    // details page in the offers suggestions popup.
+    suggestions.emplace_back(
+        l10n_util::GetStringUTF16(
+            IDS_AUTOFILL_PROMO_CODE_SUGGESTIONS_FOOTER_TEXT),
+        SuggestionType::kSeePromoCodeDetails);
+    Suggestion& suggestion = suggestions.back();
+
+    // We set the payload for the footer as |footer_offer_details_url|, which is
+    // the offer details url of the first offer we had for this merchant. We
+    // will navigate to the url in |footer_offer_details_url| if the footer is
+    // selected in AutofillExternalDelegate::DidAcceptSuggestion().
+    suggestion.payload = std::move(footer_offer_details_url);
+    suggestion.trailing_icon = Suggestion::Icon::kGoogle;
+  }
+  return suggestions;
+}
+
+}  // namespace
 
 MerchantPromoCodeSuggestionGenerator::MerchantPromoCodeSuggestionGenerator() =
     default;
diff --git a/components/autofill/core/browser/suggestions/payments/merchant_promo_code_suggestion_generator_unittest.cc b/components/autofill/core/browser/suggestions/payments/merchant_promo_code_suggestion_generator_unittest.cc
index 44620b7..0aa72f1 100644
--- a/components/autofill/core/browser/suggestions/payments/merchant_promo_code_suggestion_generator_unittest.cc
+++ b/components/autofill/core/browser/suggestions/payments/merchant_promo_code_suggestion_generator_unittest.cc
@@ -4,6 +4,7 @@
 
 #include "components/autofill/core/browser/suggestions/payments/merchant_promo_code_suggestion_generator.h"
 
+#include "base/containers/to_vector.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/mock_callback.h"
 #include "base/test/scoped_feature_list.h"
@@ -51,6 +52,33 @@
         .test_payments_data_manager();
   }
 
+  std::vector<Suggestion> GetPromoCodeSuggestionsFromPromoCodeOffers(
+      const std::vector<const AutofillOfferData*>& promo_code_offers) {
+    MerchantPromoCodeSuggestionGenerator generator;
+    std::vector<Suggestion> suggestions;
+
+    auto on_suggestions_generated =
+        [&suggestions](
+            SuggestionGenerator::ReturnedSuggestions returned_suggestions) {
+          suggestions = returned_suggestions.second;
+        };
+
+    std::vector<SuggestionGenerator::SuggestionData> suggestion_data =
+        base::ToVector(std::move(promo_code_offers),
+                       [](const AutofillOfferData* offer) {
+                         return SuggestionGenerator::SuggestionData(*offer);
+                       });
+    base::flat_map<SuggestionGenerator::SuggestionDataSource,
+                   std::vector<SuggestionGenerator::SuggestionData>>
+        fetched_data = {
+            {SuggestionGenerator::SuggestionDataSource::kMerchantPromoCode,
+             std::move(suggestion_data)}};
+    generator.GenerateSuggestions(form().ToFormData(), field(), &form(),
+                                  &field(), client(), fetched_data,
+                                  on_suggestions_generated);
+    return suggestions;
+  }
+
  private:
   base::test::SingleThreadTaskEnvironment task_environment_;
   test::AutofillUnitTestEnvironment autofill_test_environment_;
@@ -118,5 +146,93 @@
                                 suggestions_generated_callback.Get());
 }
 
+TEST_F(MerchantPromoCodeSuggestionGeneratorTest,
+       GetPromoCodeSuggestionsFromPromoCodeOffers_ValidPromoCodes) {
+  std::vector<const AutofillOfferData*> promo_code_offers;
+
+  base::Time expiry = base::Time::Now() + base::Days(2);
+  std::vector<GURL> merchant_origins;
+  DisplayStrings display_strings;
+  display_strings.value_prop_text = "test_value_prop_text_1";
+  std::string promo_code = "test_promo_code_1";
+  AutofillOfferData offer1 = AutofillOfferData::GPayPromoCodeOffer(
+      /*offer_id=*/1, expiry, merchant_origins,
+      /*offer_details_url=*/GURL("https://offer-details-url.com/"),
+      display_strings, promo_code);
+
+  promo_code_offers.push_back(&offer1);
+
+  DisplayStrings display_strings2;
+  display_strings2.value_prop_text = "test_value_prop_text_2";
+  std::string promo_code2 = "test_promo_code_2";
+  AutofillOfferData offer2 = AutofillOfferData::GPayPromoCodeOffer(
+      /*offer_id=*/2, expiry, merchant_origins,
+      /*offer_details_url=*/GURL("https://offer-details-url.com/"),
+      display_strings2, promo_code2);
+
+  promo_code_offers.push_back(&offer2);
+
+  std::vector<Suggestion> promo_code_suggestions =
+      GetPromoCodeSuggestionsFromPromoCodeOffers(promo_code_offers);
+  EXPECT_TRUE(promo_code_suggestions.size() == 4);
+
+  EXPECT_EQ(promo_code_suggestions[0].main_text.value, u"test_promo_code_1");
+  EXPECT_EQ(promo_code_suggestions[0].GetPayload<Suggestion::Guid>(),
+            Suggestion::Guid("1"));
+  EXPECT_THAT(promo_code_suggestions[0],
+              Field(&Suggestion::labels,
+                    std::vector<std::vector<Suggestion::Text>>{
+                        {Suggestion::Text(u"test_value_prop_text_1")}}));
+  EXPECT_EQ(promo_code_suggestions[0].GetPayload<Suggestion::Guid>(),
+            Suggestion::Guid("1"));
+  EXPECT_EQ(promo_code_suggestions[0].type,
+            SuggestionType::kMerchantPromoCodeEntry);
+  EXPECT_EQ(promo_code_suggestions[1].main_text.value, u"test_promo_code_2");
+  EXPECT_EQ(promo_code_suggestions[1].GetPayload<Suggestion::Guid>(),
+            Suggestion::Guid("2"));
+  EXPECT_THAT(promo_code_suggestions[1],
+              Field(&Suggestion::labels,
+                    std::vector<std::vector<Suggestion::Text>>{
+                        {Suggestion::Text(u"test_value_prop_text_2")}}));
+  EXPECT_EQ(promo_code_suggestions[1].GetPayload<Suggestion::Guid>(),
+            Suggestion::Guid("2"));
+  EXPECT_EQ(promo_code_suggestions[1].type,
+            SuggestionType::kMerchantPromoCodeEntry);
+
+  EXPECT_EQ(promo_code_suggestions[2].type, SuggestionType::kSeparator);
+
+  EXPECT_EQ(promo_code_suggestions[3].main_text.value,
+            l10n_util::GetStringUTF16(
+                IDS_AUTOFILL_PROMO_CODE_SUGGESTIONS_FOOTER_TEXT));
+  EXPECT_EQ(promo_code_suggestions[3].GetPayload<GURL>(),
+            offer1.GetOfferDetailsUrl().spec());
+  EXPECT_EQ(promo_code_suggestions[3].type,
+            SuggestionType::kSeePromoCodeDetails);
+}
+
+TEST_F(MerchantPromoCodeSuggestionGeneratorTest,
+       GetPromoCodeSuggestionsFromPromoCodeOffers_InvalidPromoCodeURL) {
+  std::vector<const AutofillOfferData*> promo_code_offers;
+  AutofillOfferData offer;
+  offer.SetPromoCode("test_promo_code_1");
+  offer.SetValuePropTextInDisplayStrings("test_value_prop_text_1");
+  offer.SetOfferIdForTesting(1);
+  offer.SetOfferDetailsUrl(GURL("invalid-url"));
+  promo_code_offers.push_back(&offer);
+
+  std::vector<Suggestion> promo_code_suggestions =
+      GetPromoCodeSuggestionsFromPromoCodeOffers(promo_code_offers);
+  EXPECT_TRUE(promo_code_suggestions.size() == 1);
+
+  EXPECT_EQ(promo_code_suggestions[0].main_text.value, u"test_promo_code_1");
+  EXPECT_THAT(promo_code_suggestions[0],
+              Field(&Suggestion::labels,
+                    std::vector<std::vector<Suggestion::Text>>{
+                        {Suggestion::Text(u"test_value_prop_text_1")}}));
+  EXPECT_FALSE(std::holds_alternative<GURL>(promo_code_suggestions[0].payload));
+  EXPECT_EQ(promo_code_suggestions[0].type,
+            SuggestionType::kMerchantPromoCodeEntry);
+}
+
 }  // namespace
 }  // namespace autofill
diff --git a/components/autofill/core/browser/suggestions/payments/payments_suggestion_generator.cc b/components/autofill/core/browser/suggestions/payments/payments_suggestion_generator.cc
index 7e2f726..b84cc56 100644
--- a/components/autofill/core/browser/suggestions/payments/payments_suggestion_generator.cc
+++ b/components/autofill/core/browser/suggestions/payments/payments_suggestion_generator.cc
@@ -32,7 +32,6 @@
 #include "components/autofill/core/browser/data_model/payments/bnpl_issuer.h"
 #include "components/autofill/core/browser/data_model/payments/credit_card.h"
 #include "components/autofill/core/browser/data_model/payments/credit_card_benefit.h"
-#include "components/autofill/core/browser/data_model/payments/iban.h"
 #include "components/autofill/core/browser/data_quality/autofill_data_util.h"
 #include "components/autofill/core/browser/field_type_utils.h"
 #include "components/autofill/core/browser/field_types.h"
@@ -63,7 +62,6 @@
 #include "components/grit/components_scaled_resources.h"
 #include "components/strings/grit/components_strings.h"
 #include "ui/base/l10n/l10n_util.h"
-#include "ui/base/resource/resource_bundle.h"
 
 namespace autofill {
 
@@ -1466,11 +1464,6 @@
                                          with_gpay_logo);
 }
 
-Suggestion CreateManageIbansSuggestion() {
-  return CreateManagePaymentMethodsEntry(SuggestionType::kManageIban,
-                                         /*with_gpay_logo=*/false);
-}
-
 Suggestion CreateSaveAndFillSuggestion(const AutofillClient& client,
                                        bool& display_gpay_logo) {
   Suggestion save_and_fill(
@@ -1488,110 +1481,6 @@
   return save_and_fill;
 }
 
-std::vector<Suggestion> GetSuggestionsForIbans(const std::vector<Iban>& ibans) {
-  if (ibans.empty()) {
-    return {};
-  }
-  std::vector<Suggestion> suggestions;
-  suggestions.reserve(ibans.size() + 2);
-  for (const Iban& iban : ibans) {
-    Suggestion suggestion(SuggestionType::kIbanEntry);
-    suggestion.custom_icon =
-        ui::ResourceBundle::GetSharedInstance().GetImageNamed(
-            ShouldUseNewFopDisplay() ? IDR_AUTOFILL_IBAN
-                                     : IDR_AUTOFILL_IBAN_OLD);
-    suggestion.icon = Suggestion::Icon::kIban;
-    if (iban.record_type() == Iban::kLocalIban) {
-      suggestion.payload = Suggestion::Guid(iban.guid());
-    } else {
-      CHECK(iban.record_type() == Iban::kServerIban);
-      suggestion.payload = Suggestion::InstrumentId(iban.instrument_id());
-    }
-
-    std::u16string iban_identifier =
-        iban.GetIdentifierStringForAutofillDisplay();
-    if constexpr (BUILDFLAG(IS_ANDROID)) {
-      // For Android keyboard accessory, the displayed value will be nickname +
-      // identifier string, if the nickname is too long to fit due to bubble
-      // width limitation, it will be truncated.
-      if (!iban.nickname().empty()) {
-        suggestion.main_text.value = iban.nickname();
-        suggestion.minor_texts.emplace_back(iban_identifier);
-      } else {
-        suggestion.main_text.value = std::move(iban_identifier);
-      }
-    } else {
-      if (iban.nickname().empty()) {
-        suggestion.main_text = Suggestion::Text(
-            iban_identifier, Suggestion::Text::IsPrimary(true));
-      } else {
-        suggestion.main_text = Suggestion::Text(
-            iban.nickname(), Suggestion::Text::IsPrimary(true));
-        suggestion.labels = {{Suggestion::Text(iban_identifier)}};
-      }
-    }
-    suggestions.push_back(suggestion);
-  }
-
-  suggestions.emplace_back(SuggestionType::kSeparator);
-  suggestions.push_back(CreateManageIbansSuggestion());
-  return suggestions;
-}
-
-// static
-std::vector<Suggestion> GetPromoCodeSuggestionsFromPromoCodeOffers(
-    const std::vector<const AutofillOfferData*>& promo_code_offers) {
-  std::vector<Suggestion> suggestions;
-  GURL footer_offer_details_url;
-  for (const AutofillOfferData* promo_code_offer : promo_code_offers) {
-    // For each promo code, create a suggestion.
-    suggestions.emplace_back(
-        base::ASCIIToUTF16(promo_code_offer->GetPromoCode()),
-        SuggestionType::kMerchantPromoCodeEntry);
-    Suggestion& suggestion = suggestions.back();
-    if (!promo_code_offer->GetDisplayStrings().value_prop_text.empty()) {
-      suggestion.labels = {{Suggestion::Text(base::ASCIIToUTF16(
-          promo_code_offer->GetDisplayStrings().value_prop_text))}};
-    }
-    suggestion.payload =
-        Suggestion::Guid(base::NumberToString(promo_code_offer->GetOfferId()));
-
-    // Every offer for a given merchant leads to the same GURL, so we grab the
-    // first offer's offer details url as the payload for the footer to set
-    // later.
-    if (footer_offer_details_url.is_empty() &&
-        !promo_code_offer->GetOfferDetailsUrl().is_empty() &&
-        promo_code_offer->GetOfferDetailsUrl().is_valid()) {
-      footer_offer_details_url = promo_code_offer->GetOfferDetailsUrl();
-    }
-  }
-
-  // Ensure that there are suggestions and that we were able to find at least
-  // one suggestion with a valid offer details url before adding the footer.
-  DCHECK(suggestions.size() > 0);
-  if (!footer_offer_details_url.is_empty()) {
-    // Add the footer separator since we will now have a footer in the offers
-    // suggestions popup.
-    suggestions.emplace_back(SuggestionType::kSeparator);
-
-    // Add the footer suggestion that navigates the user to the promo code
-    // details page in the offers suggestions popup.
-    suggestions.emplace_back(
-        l10n_util::GetStringUTF16(
-            IDS_AUTOFILL_PROMO_CODE_SUGGESTIONS_FOOTER_TEXT),
-        SuggestionType::kSeePromoCodeDetails);
-    Suggestion& suggestion = suggestions.back();
-
-    // We set the payload for the footer as |footer_offer_details_url|, which is
-    // the offer details url of the first offer we had for this merchant. We
-    // will navigate to the url in |footer_offer_details_url| if the footer is
-    // selected in AutofillExternalDelegate::DidAcceptSuggestion().
-    suggestion.payload = std::move(footer_offer_details_url);
-    suggestion.trailing_icon = Suggestion::Icon::kGoogle;
-  }
-  return suggestions;
-}
-
 bool IsCardSuggestionAcceptable(const CreditCard& card,
                                 const AutofillClient& client) {
   if (card.record_type() == CreditCard::RecordType::kVirtualCard) {
diff --git a/components/autofill/core/browser/suggestions/payments/payments_suggestion_generator.h b/components/autofill/core/browser/suggestions/payments/payments_suggestion_generator.h
index 6cfb75f..234beeae 100644
--- a/components/autofill/core/browser/suggestions/payments/payments_suggestion_generator.h
+++ b/components/autofill/core/browser/suggestions/payments/payments_suggestion_generator.h
@@ -31,7 +31,6 @@
 class BrowserAutofillManager;
 class CreditCard;
 class FormFieldData;
-class Iban;
 
 // Describes the suggestions returned by
 // `GetCreditCardOrCvcFieldSuggestions()`.
@@ -162,14 +161,6 @@
 // `SuggestionType`. This distinction is needed for metrics recording.
 Suggestion CreateManageCreditCardsSuggestion(bool with_gpay_logo);
 
-// Generates a footer suggestion "Manage payment methods..." menu item which
-// will redirect to Chrome payment settings page.
-//
-// The difference between `CreateManageCreditCardsSuggestion()` and
-// `CreateManageIbansSuggestion()` is that they use a different
-// `SuggestionType`. This distinction is needed for metrics recording.
-Suggestion CreateManageIbansSuggestion();
-
 // Generates a "Save and Fill" suggestion for users who don't have any cards
 // saved in Autofill. This suggestion is shown above the footer.
 // `display_gpay_logo` is an  output parameter that is set to true if credit
@@ -178,14 +169,6 @@
 Suggestion CreateSaveAndFillSuggestion(const AutofillClient& client,
                                        bool& display_gpay_logo);
 
-// Generates suggestions for all available IBANs.
-std::vector<Suggestion> GetSuggestionsForIbans(const std::vector<Iban>& ibans);
-
-// Converts the vector of promo code offers that is passed in to a vector of
-// suggestions that can be displayed to the user for a promo code field.
-std::vector<Suggestion> GetPromoCodeSuggestionsFromPromoCodeOffers(
-    const std::vector<const AutofillOfferData*>& promo_code_offers);
-
 // Returns true if the suggestion created from the card can be accepted by the
 // user. Returns false when merchant does not accept the given card for example
 // when merchants opt-out of VCNs.
diff --git a/components/autofill/core/browser/suggestions/payments/payments_suggestion_generator_unittest.cc b/components/autofill/core/browser/suggestions/payments/payments_suggestion_generator_unittest.cc
index abf2ce9..610135e 100644
--- a/components/autofill/core/browser/suggestions/payments/payments_suggestion_generator_unittest.cc
+++ b/components/autofill/core/browser/suggestions/payments/payments_suggestion_generator_unittest.cc
@@ -117,40 +117,6 @@
 #endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) ||
         // BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID)
 
-Matcher<Suggestion> EqualsIbanSuggestion(
-    const std::u16string& identifier_string,
-    const Suggestion::Payload& payload,
-    const std::u16string& nickname) {
-  if constexpr (BUILDFLAG(IS_ANDROID)) {
-    if (nickname.empty()) {
-      return AllOf(
-          Field(&Suggestion::type, SuggestionType::kIbanEntry),
-          Field(&Suggestion::main_text, Suggestion::Text(identifier_string)),
-          Field(&Suggestion::payload, payload));
-    }
-    return AllOf(Field(&Suggestion::type, SuggestionType::kIbanEntry),
-                 Field(&Suggestion::main_text, Suggestion::Text(nickname)),
-                 Field(&Suggestion::minor_texts,
-                       std::vector<Suggestion::Text>{
-                           Suggestion::Text(identifier_string)}),
-                 Field(&Suggestion::payload, payload));
-  }
-  if (nickname.empty()) {
-    return AllOf(Field(&Suggestion::type, SuggestionType::kIbanEntry),
-                 Field(&Suggestion::main_text,
-                       Suggestion::Text(identifier_string,
-                                        Suggestion::Text::IsPrimary(true))),
-                 Field(&Suggestion::payload, payload));
-  }
-  return AllOf(
-      Field(&Suggestion::type, SuggestionType::kIbanEntry),
-      Field(&Suggestion::main_text,
-            Suggestion::Text(nickname, Suggestion::Text::IsPrimary(true))),
-      Field(&Suggestion::payload, payload),
-      EqualLabels(std::vector<std::vector<Suggestion::Text>>{
-          {Suggestion::Text(identifier_string)}}));
-}
-
 #if !BUILDFLAG(IS_IOS)
 Matcher<Suggestion> EqualsUndoAutofillSuggestion() {
   return EqualsSuggestion(SuggestionType::kUndoOrClear,
@@ -2576,88 +2542,6 @@
 }
 
 TEST_F(PaymentsSuggestionGeneratorTest,
-       GetPromoCodeSuggestionsFromPromoCodeOffers_ValidPromoCodes) {
-  std::vector<const AutofillOfferData*> promo_code_offers;
-
-  base::Time expiry = AutofillClock::Now() + base::Days(2);
-  std::vector<GURL> merchant_origins;
-  DisplayStrings display_strings;
-  display_strings.value_prop_text = "test_value_prop_text_1";
-  std::string promo_code = "test_promo_code_1";
-  AutofillOfferData offer1 = AutofillOfferData::GPayPromoCodeOffer(
-      /*offer_id=*/1, expiry, merchant_origins,
-      /*offer_details_url=*/GURL("https://offer-details-url.com/"),
-      display_strings, promo_code);
-
-  promo_code_offers.push_back(&offer1);
-
-  DisplayStrings display_strings2;
-  display_strings2.value_prop_text = "test_value_prop_text_2";
-  std::string promo_code2 = "test_promo_code_2";
-  AutofillOfferData offer2 = AutofillOfferData::GPayPromoCodeOffer(
-      /*offer_id=*/2, expiry, merchant_origins,
-      /*offer_details_url=*/GURL("https://offer-details-url.com/"),
-      display_strings2, promo_code2);
-
-  promo_code_offers.push_back(&offer2);
-
-  std::vector<Suggestion> promo_code_suggestions =
-      GetPromoCodeSuggestionsFromPromoCodeOffers(promo_code_offers);
-  EXPECT_TRUE(promo_code_suggestions.size() == 4);
-
-  EXPECT_EQ(promo_code_suggestions[0].main_text.value, u"test_promo_code_1");
-  EXPECT_EQ(promo_code_suggestions[0].GetPayload<Suggestion::Guid>(),
-            Suggestion::Guid("1"));
-  EXPECT_THAT(promo_code_suggestions[0],
-              EqualLabels({{u"test_value_prop_text_1"}}));
-  EXPECT_EQ(promo_code_suggestions[0].GetPayload<Suggestion::Guid>(),
-            Suggestion::Guid("1"));
-  EXPECT_EQ(promo_code_suggestions[0].type,
-            SuggestionType::kMerchantPromoCodeEntry);
-  EXPECT_EQ(promo_code_suggestions[1].main_text.value, u"test_promo_code_2");
-  EXPECT_EQ(promo_code_suggestions[1].GetPayload<Suggestion::Guid>(),
-            Suggestion::Guid("2"));
-  EXPECT_THAT(promo_code_suggestions[1],
-              EqualLabels({{u"test_value_prop_text_2"}}));
-  EXPECT_EQ(promo_code_suggestions[1].GetPayload<Suggestion::Guid>(),
-            Suggestion::Guid("2"));
-  EXPECT_EQ(promo_code_suggestions[1].type,
-            SuggestionType::kMerchantPromoCodeEntry);
-
-  EXPECT_EQ(promo_code_suggestions[2].type, SuggestionType::kSeparator);
-
-  EXPECT_EQ(promo_code_suggestions[3].main_text.value,
-            l10n_util::GetStringUTF16(
-                IDS_AUTOFILL_PROMO_CODE_SUGGESTIONS_FOOTER_TEXT));
-  EXPECT_EQ(promo_code_suggestions[3].GetPayload<GURL>(),
-            offer1.GetOfferDetailsUrl().spec());
-  EXPECT_EQ(promo_code_suggestions[3].type,
-            SuggestionType::kSeePromoCodeDetails);
-}
-
-TEST_F(PaymentsSuggestionGeneratorTest,
-       GetPromoCodeSuggestionsFromPromoCodeOffers_InvalidPromoCodeURL) {
-  std::vector<const AutofillOfferData*> promo_code_offers;
-  AutofillOfferData offer;
-  offer.SetPromoCode("test_promo_code_1");
-  offer.SetValuePropTextInDisplayStrings("test_value_prop_text_1");
-  offer.SetOfferIdForTesting(1);
-  offer.SetOfferDetailsUrl(GURL("invalid-url"));
-  promo_code_offers.push_back(&offer);
-
-  std::vector<Suggestion> promo_code_suggestions =
-      GetPromoCodeSuggestionsFromPromoCodeOffers(promo_code_offers);
-  EXPECT_TRUE(promo_code_suggestions.size() == 1);
-
-  EXPECT_EQ(promo_code_suggestions[0].main_text.value, u"test_promo_code_1");
-  EXPECT_THAT(promo_code_suggestions[0],
-              EqualLabels({{u"test_value_prop_text_1"}}));
-  EXPECT_FALSE(std::holds_alternative<GURL>(promo_code_suggestions[0].payload));
-  EXPECT_EQ(promo_code_suggestions[0].type,
-            SuggestionType::kMerchantPromoCodeEntry);
-}
-
-TEST_F(PaymentsSuggestionGeneratorTest,
        GenerateLocalSaveAndFillSuggestion_CreditCardUploadDisabled) {
   base::test::ScopedFeatureList scoped_feature_list(
       features::kAutofillEnableSaveAndFill);
@@ -2924,165 +2808,6 @@
           EqualsManagePaymentsMethodsSuggestion(/*with_gpay_logo=*/true)));
 }
 
-// This class helps test the IBAN suggestion contents that are displayed in
-// Autofill suggestions.
-class AutofillIbanSuggestionContentTest
-    : public PaymentsSuggestionGeneratorTest,
-      public testing::WithParamInterface<bool> {
- public:
-  AutofillIbanSuggestionContentTest() {
-    feature_list_metadata_.InitWithFeatureStates(
-        {{features::kAutofillEnableNewFopDisplayDesktop,
-          IsNewFopDisplayEnabled()}});
-  }
-
-  ~AutofillIbanSuggestionContentTest() override = default;
-
-  bool IsNewFopDisplayEnabled() const {
-#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
-    return false;
-#else
-    return GetParam();
-#endif
-  }
-
- private:
-  base::test::ScopedFeatureList feature_list_metadata_;
-};
-
-INSTANTIATE_TEST_SUITE_P(PaymentsSuggestionGeneratorTest,
-                         AutofillIbanSuggestionContentTest,
-                         ::testing::Bool());
-
-TEST_P(AutofillIbanSuggestionContentTest, GetLocalIbanSuggestions) {
-  auto MakeLocalIban = [](const std::u16string& value,
-                          const std::u16string& nickname) {
-    Iban iban(Iban::Guid(base::Uuid::GenerateRandomV4().AsLowercaseString()));
-    iban.set_value(value);
-    if (!nickname.empty()) {
-      iban.set_nickname(nickname);
-    }
-    return iban;
-  };
-  Iban iban0 =
-      MakeLocalIban(u"CH56 0483 5012 3456 7800 9", u"My doctor's IBAN");
-  Iban iban1 =
-      MakeLocalIban(u"DE91 1000 0000 0123 4567 89", u"My brother's IBAN");
-  Iban iban2 =
-      MakeLocalIban(u"GR96 0810 0010 0000 0123 4567 890", u"My teacher's IBAN");
-  Iban iban3 = MakeLocalIban(u"PK70 BANK 0000 1234 5678 9000", u"");
-
-  std::vector<Suggestion> iban_suggestions =
-      GetSuggestionsForIbans({iban0, iban1, iban2, iban3});
-
-  // There are 6 suggestions, 4 for IBAN suggestions, followed by a separator,
-  // and followed by "Manage payment methods..." which redirects to the Chrome
-  // payment methods settings page.
-  ASSERT_EQ(iban_suggestions.size(), 6u);
-
-  EXPECT_THAT(
-      iban_suggestions[0],
-      EqualsIbanSuggestion(iban0.GetIdentifierStringForAutofillDisplay(),
-                           Suggestion::Guid(iban0.guid()), iban0.nickname()));
-
-  EXPECT_THAT(
-      iban_suggestions[1],
-      EqualsIbanSuggestion(iban1.GetIdentifierStringForAutofillDisplay(),
-                           Suggestion::Guid(iban1.guid()), iban1.nickname()));
-
-  EXPECT_THAT(
-      iban_suggestions[2],
-      EqualsIbanSuggestion(iban2.GetIdentifierStringForAutofillDisplay(),
-                           Suggestion::Guid(iban2.guid()), iban2.nickname()));
-
-  EXPECT_THAT(
-      iban_suggestions[3],
-      EqualsIbanSuggestion(iban3.GetIdentifierStringForAutofillDisplay(),
-                           Suggestion::Guid(iban3.guid()), iban3.nickname()));
-
-  EXPECT_EQ(iban_suggestions[4].type, SuggestionType::kSeparator);
-
-  EXPECT_EQ(iban_suggestions[5].main_text.value,
-            l10n_util::GetStringUTF16(IDS_AUTOFILL_MANAGE_PAYMENT_METHODS));
-  EXPECT_EQ(iban_suggestions[5].type, SuggestionType::kManageIban);
-}
-
-TEST_P(AutofillIbanSuggestionContentTest, GetServerIbanSuggestions) {
-  Iban server_iban1 = test::GetServerIban();
-  Iban server_iban2 = test::GetServerIban2();
-  Iban server_iban3 = test::GetServerIban3();
-
-  std::vector<Suggestion> iban_suggestions =
-      GetSuggestionsForIbans({server_iban1, server_iban2, server_iban3});
-
-  // There are 5 suggestions, 3 for IBAN suggestions, followed by a separator,
-  // and followed by "Manage payment methods..." which redirects to the Chrome
-  // payment methods settings page.
-  ASSERT_EQ(iban_suggestions.size(), 5u);
-
-  EXPECT_THAT(iban_suggestions[0],
-              EqualsIbanSuggestion(
-                  server_iban1.GetIdentifierStringForAutofillDisplay(),
-                  Suggestion::InstrumentId(server_iban1.instrument_id()),
-                  server_iban1.nickname()));
-
-  EXPECT_THAT(iban_suggestions[1],
-              EqualsIbanSuggestion(
-                  server_iban2.GetIdentifierStringForAutofillDisplay(),
-                  Suggestion::InstrumentId(server_iban2.instrument_id()),
-                  server_iban2.nickname()));
-
-  EXPECT_THAT(iban_suggestions[2],
-              EqualsIbanSuggestion(
-                  server_iban3.GetIdentifierStringForAutofillDisplay(),
-                  Suggestion::InstrumentId(server_iban3.instrument_id()),
-                  server_iban3.nickname()));
-
-  EXPECT_EQ(iban_suggestions[3].type, SuggestionType::kSeparator);
-
-  EXPECT_EQ(iban_suggestions[4].main_text.value,
-            l10n_util::GetStringUTF16(IDS_AUTOFILL_MANAGE_PAYMENT_METHODS));
-  EXPECT_EQ(iban_suggestions[4].type, SuggestionType::kManageIban);
-}
-
-TEST_P(AutofillIbanSuggestionContentTest, GetLocalAndServerIbanSuggestions) {
-  Iban server_iban1 = test::GetServerIban();
-  Iban server_iban2 = test::GetServerIban2();
-  Iban local_iban1 = test::GetLocalIban();
-
-  std::vector<Suggestion> iban_suggestions =
-      GetSuggestionsForIbans({server_iban1, server_iban2, local_iban1});
-
-  // There are 5 suggestions, 3 for IBAN suggestions, followed by a separator,
-  // and followed by "Manage payment methods..." which redirects to the Chrome
-  // payment methods settings page.
-  ASSERT_EQ(iban_suggestions.size(), 5u);
-
-  EXPECT_THAT(iban_suggestions[0],
-              EqualsIbanSuggestion(
-                  server_iban1.GetIdentifierStringForAutofillDisplay(),
-                  Suggestion::InstrumentId(server_iban1.instrument_id()),
-                  server_iban1.nickname()));
-
-  EXPECT_THAT(iban_suggestions[1],
-              EqualsIbanSuggestion(
-                  server_iban2.GetIdentifierStringForAutofillDisplay(),
-                  Suggestion::InstrumentId(server_iban2.instrument_id()),
-                  server_iban2.nickname()));
-
-  EXPECT_THAT(
-      iban_suggestions[2],
-      EqualsIbanSuggestion(local_iban1.GetIdentifierStringForAutofillDisplay(),
-                           Suggestion::Guid(local_iban1.guid()),
-                           local_iban1.nickname()));
-
-  EXPECT_EQ(iban_suggestions[3].type, SuggestionType::kSeparator);
-
-  EXPECT_EQ(iban_suggestions[4].main_text.value,
-            l10n_util::GetStringUTF16(IDS_AUTOFILL_MANAGE_PAYMENT_METHODS));
-  EXPECT_EQ(iban_suggestions[4].type, SuggestionType::kManageIban);
-}
-
 // This class helps test the credit card contents that are displayed in
 // Autofill suggestions. It covers suggestions on Desktop/Android dropdown,
 // and on Android keyboard accessory.
diff --git a/components/autofill/core/browser/ui/payments/card_unmask_prompt_controller_impl.h b/components/autofill/core/browser/ui/payments/card_unmask_prompt_controller_impl.h
index 6db775ee..5b3b080 100644
--- a/components/autofill/core/browser/ui/payments/card_unmask_prompt_controller_impl.h
+++ b/components/autofill/core/browser/ui/payments/card_unmask_prompt_controller_impl.h
@@ -108,7 +108,7 @@
   void LogOnCloseEvents();
   AutofillMetrics::UnmaskPromptEvent GetCloseReasonEvent();
 
-  const raw_ref<PrefService, DanglingUntriaged> pref_service_;
+  const raw_ref<PrefService> pref_service_;
   CreditCard card_;
   CardUnmaskPromptOptions card_unmask_prompt_options_;
   base::WeakPtr<CardUnmaskDelegate> delegate_;
diff --git a/components/autofill/core/browser/webdata/addresses/address_autofill_table.cc b/components/autofill/core/browser/webdata/addresses/address_autofill_table.cc
index 0d96c025..1adadd4 100644
--- a/components/autofill/core/browser/webdata/addresses/address_autofill_table.cc
+++ b/components/autofill/core/browser/webdata/addresses/address_autofill_table.cc
@@ -1200,30 +1200,4 @@
                                 /*composite_primary_key=*/{kGuid, kType});
 }
 
-AddressAutofillTable::Dropper::Dropper() = default;
-AddressAutofillTable::Dropper::~Dropper() = default;
-
-WebDatabaseTable::TypeKey AddressAutofillTable::Dropper::GetTypeKey() const {
-  static int table_key = 0;
-  return reinterpret_cast<void*>(&table_key);
-}
-
-bool AddressAutofillTable::Dropper::CreateTablesIfNecessary() {
-  return true;
-}
-
-bool AddressAutofillTable::Dropper::MigrateToVersion(
-    int version,
-    bool* update_compatible_version) {
-  static constexpr auto kTables = std::to_array<std::string_view>(
-      {kAddressesTable, kAddressTypeTokensTable, kAutofillProfileAddressesTable,
-       kAutofillProfileBirthdatesTable, kAutofillProfileEmailsTable,
-       kAutofillProfileNamesTable, kAutofillProfilePhonesTable,
-       kAutofillProfilesTable, kContactInfoTable, kContactInfoTypeTokensTable,
-       kLocalAddressesTable, kLocalAddressesTypeTokensTable});
-  return std::ranges::all_of(kTables, [this](std::string_view table_name) {
-    return DropTableIfExists(db(), table_name);
-  });
-}
-
 }  // namespace autofill
diff --git a/components/autofill/core/browser/webdata/addresses/address_autofill_table.h b/components/autofill/core/browser/webdata/addresses/address_autofill_table.h
index a2efc97..b8cd689c 100644
--- a/components/autofill/core/browser/webdata/addresses/address_autofill_table.h
+++ b/components/autofill/core/browser/webdata/addresses/address_autofill_table.h
@@ -68,10 +68,6 @@
 // -----------------------------------------------------------------------------
 class AddressAutofillTable : public WebDatabaseTable {
  public:
-  // Drops the tables created by AddressAutofillTable.
-  // TODO(crbug.com/390473673): Remove after M143.
-  class Dropper;
-
   AddressAutofillTable();
 
   AddressAutofillTable(const AddressAutofillTable&) = delete;
@@ -145,18 +141,6 @@
   bool InitAddressTypeTokensTable();
 };
 
-class AddressAutofillTable::Dropper : public WebDatabaseTable {
- public:
-  Dropper();
-  Dropper(const Dropper&) = delete;
-  Dropper& operator=(const Dropper&) = delete;
-  ~Dropper() override;
-
-  TypeKey GetTypeKey() const override;
-  bool CreateTablesIfNecessary() override;
-  bool MigrateToVersion(int version, bool* update_compatible_version) override;
-};
-
 }  // namespace autofill
 
 #endif  // COMPONENTS_AUTOFILL_CORE_BROWSER_WEBDATA_ADDRESSES_ADDRESS_AUTOFILL_TABLE_H_
diff --git a/components/autofill/core/browser/webdata/autocomplete/autocomplete_table.cc b/components/autofill/core/browser/webdata/autocomplete/autocomplete_table.cc
index a6e3eea..7b08ce8b 100644
--- a/components/autofill/core/browser/webdata/autocomplete/autocomplete_table.cc
+++ b/components/autofill/core/browser/webdata/autocomplete/autocomplete_table.cc
@@ -448,22 +448,4 @@
   return true;
 }
 
-AutocompleteTable::Dropper::Dropper() = default;
-AutocompleteTable::Dropper::~Dropper() = default;
-
-WebDatabaseTable::TypeKey AutocompleteTable::Dropper::GetTypeKey() const {
-  static int table_key = 0;
-  return reinterpret_cast<void*>(&table_key);
-}
-
-bool AutocompleteTable::Dropper::CreateTablesIfNecessary() {
-  return true;
-}
-
-bool AutocompleteTable::Dropper::MigrateToVersion(
-    int version,
-    bool* update_compatible_version) {
-  return DropTableIfExists(db(), kAutocompleteTable);
-}
-
 }  // namespace autofill
diff --git a/components/autofill/core/browser/webdata/autocomplete/autocomplete_table.h b/components/autofill/core/browser/webdata/autocomplete/autocomplete_table.h
index 54c781b..3330988 100644
--- a/components/autofill/core/browser/webdata/autocomplete/autocomplete_table.h
+++ b/components/autofill/core/browser/webdata/autocomplete/autocomplete_table.h
@@ -43,10 +43,6 @@
 // -----------------------------------------------------------------------------
 class AutocompleteTable : public WebDatabaseTable {
  public:
-  // Drops the table created by AutocompleteTable.
-  // TODO(crbug.com/390473673): Remove after M143.
-  class Dropper;
-
   AutocompleteTable();
 
   AutocompleteTable(const AutocompleteTable&) = delete;
@@ -126,18 +122,6 @@
   bool InitMainTable();
 };
 
-class AutocompleteTable::Dropper : public WebDatabaseTable {
- public:
-  Dropper();
-  Dropper(const Dropper&) = delete;
-  Dropper& operator=(const Dropper&) = delete;
-  ~Dropper() override;
-
-  TypeKey GetTypeKey() const override;
-  bool CreateTablesIfNecessary() override;
-  bool MigrateToVersion(int version, bool* update_compatible_version) override;
-};
-
 }  // namespace autofill
 
 #endif  // COMPONENTS_AUTOFILL_CORE_BROWSER_WEBDATA_AUTOCOMPLETE_AUTOCOMPLETE_TABLE_H_
diff --git a/components/autofill/core/browser/webdata/autofill_webdata_backend_impl.cc b/components/autofill/core/browser/webdata/autofill_webdata_backend_impl.cc
index f04e6ce1..dc7b92de 100644
--- a/components/autofill/core/browser/webdata/autofill_webdata_backend_impl.cc
+++ b/components/autofill/core/browser/webdata/autofill_webdata_backend_impl.cc
@@ -133,8 +133,8 @@
   kRemoveEntityInstance_Failure = 301,
   kRemoveEntityInstancesModifiedBetween_Success = 310,
   kRemoveEntityInstancesModifiedBetween_Failure = 311,
-  kCleanupForCrbug411681430_Success = 312,
-  kCleanupForCrbug411681430_Failure = 313,
+  kClearLocalCvcsUpToMay2025_Success = 312,
+  kClearLocalCvcsUpToMay2025_Failure = 313,
   kCleanupForCrbug445879524_Success = 314,
   kCleanupForCrbug445879524_Failure = 315,
   kMaxValue = kCleanupForCrbug445879524_Failure,
@@ -976,14 +976,14 @@
   return WebDatabase::COMMIT_NOT_NEEDED;
 }
 
-WebDatabase::State AutofillWebDataBackendImpl::CleanupForCrbug411681430(
+WebDatabase::State AutofillWebDataBackendImpl::ClearLocalCvcsUpToMay2025(
     WebDatabase* db) {
   DCHECK(owning_task_runner()->RunsTasksInCurrentSequence());
-  if (PaymentsAutofillTable::FromWebDatabase(db)->CleanupForCrbug411681430()) {
-    ReportResult(Result::kCleanupForCrbug411681430_Success);
+  if (PaymentsAutofillTable::FromWebDatabase(db)->ClearLocalCvcsUpToMay2025()) {
+    ReportResult(Result::kClearLocalCvcsUpToMay2025_Success);
     return WebDatabase::COMMIT_NEEDED;
   }
-  ReportResult(Result::kCleanupForCrbug411681430_Failure);
+  ReportResult(Result::kClearLocalCvcsUpToMay2025_Failure);
   return WebDatabase::COMMIT_NOT_NEEDED;
 }
 
diff --git a/components/autofill/core/browser/webdata/autofill_webdata_backend_impl.h b/components/autofill/core/browser/webdata/autofill_webdata_backend_impl.h
index 328c659..987fa62 100644
--- a/components/autofill/core/browser/webdata/autofill_webdata_backend_impl.h
+++ b/components/autofill/core/browser/webdata/autofill_webdata_backend_impl.h
@@ -246,8 +246,9 @@
   // Method to clear all the local CVCs from the web database.
   WebDatabase::State ClearLocalCvcs(WebDatabase* db);
 
-  // Method to clean up for crbug.com/411681430.
-  WebDatabase::State CleanupForCrbug411681430(WebDatabase* db);
+  // Method to clear all local CVCs created before mid-May 2025. For more
+  // information, see crbug.com/411681430.
+  WebDatabase::State ClearLocalCvcsUpToMay2025(WebDatabase* db);
 
 #if BUILDFLAG(IS_IOS)
   // Method to clean up for crbug.com/445879524.
diff --git a/components/autofill/core/browser/webdata/autofill_webdata_service.cc b/components/autofill/core/browser/webdata/autofill_webdata_service.cc
index 7f83f04..50c8adb 100644
--- a/components/autofill/core/browser/webdata/autofill_webdata_service.cc
+++ b/components/autofill/core/browser/webdata/autofill_webdata_service.cc
@@ -297,10 +297,10 @@
                                 autofill_backend_));
 }
 
-void AutofillWebDataService::CleanupForCrbug411681430() {
+void AutofillWebDataService::ClearLocalCvcsUpToMay2025() {
   wdbs_->ScheduleDBTask(
       FROM_HERE,
-      base::BindOnce(&AutofillWebDataBackendImpl::CleanupForCrbug411681430,
+      base::BindOnce(&AutofillWebDataBackendImpl::ClearLocalCvcsUpToMay2025,
                      autofill_backend_));
 }
 
diff --git a/components/autofill/core/browser/webdata/autofill_webdata_service.h b/components/autofill/core/browser/webdata/autofill_webdata_service.h
index b6df47c..2163931 100644
--- a/components/autofill/core/browser/webdata/autofill_webdata_service.h
+++ b/components/autofill/core/browser/webdata/autofill_webdata_service.h
@@ -178,8 +178,9 @@
   // Method to clear all the local CVCs from the web database.
   void ClearLocalCvcs();
 
-  // Method to clean up for crbug.com/411681430.
-  void CleanupForCrbug411681430();
+  // Method to clear all local CVCs created before mid-May 2025. For more
+  // information, see crbug.com/411681430.
+  void ClearLocalCvcsUpToMay2025();
 
 #if BUILDFLAG(IS_IOS)
   // Method to clean up for crbug.com/445879524.
diff --git a/components/autofill/core/browser/webdata/payments/payments_autofill_table.cc b/components/autofill/core/browser/webdata/payments/payments_autofill_table.cc
index e19a93a2..795e12d 100644
--- a/components/autofill/core/browser/webdata/payments/payments_autofill_table.cc
+++ b/components/autofill/core/browser/webdata/payments/payments_autofill_table.cc
@@ -255,7 +255,8 @@
 constexpr std::string_view kPaymentInstrumentCreationOptionsTable =
     "payment_instrument_creation_options";
 
-constexpr std::string_view kCleanupForCrbug411681430Timestamp = "1747828800";
+constexpr std::string_view kClearTimestampForLocalCvcs =
+    "1747828800";  // May 21, 2025.
 
 void BindEncryptedStringToColumn(sql::Statement* s,
                                  int column_index,
@@ -1106,10 +1107,10 @@
   return db()->GetLastChangeCount() > 0;
 }
 
-bool PaymentsAutofillTable::CleanupForCrbug411681430() {
-  Delete(db(), kLocalStoredCvcTable,
-         base::StrCat(
-             {kLastUpdatedTimestamp, "<", kCleanupForCrbug411681430Timestamp}));
+bool PaymentsAutofillTable::ClearLocalCvcsUpToMay2025() {
+  Delete(
+      db(), kLocalStoredCvcTable,
+      base::StrCat({kLastUpdatedTimestamp, "<", kClearTimestampForLocalCvcs}));
   return db()->GetLastChangeCount() > 0;
 }
 
@@ -2429,47 +2430,4 @@
        {kSerializedValueEncrypted, "VARCHAR NOT NULL"}});
 }
 
-PaymentsAutofillTable::Dropper::Dropper() = default;
-PaymentsAutofillTable::Dropper::~Dropper() = default;
-
-WebDatabaseTable::TypeKey PaymentsAutofillTable::Dropper::GetTypeKey() const {
-  static int table_key = 0;
-  return reinterpret_cast<void*>(&table_key);
-}
-
-bool PaymentsAutofillTable::Dropper::CreateTablesIfNecessary() {
-  return true;
-}
-
-bool PaymentsAutofillTable::Dropper::MigrateToVersion(
-    int version,
-    bool* update_compatible_version) {
-  static constexpr auto kTables =
-      std::to_array<std::string_view>({kBenefitMerchantDomainsTable,
-                                       kCreditCardsTable,
-                                       kGenericPaymentInstrumentsTable,
-                                       kIbansTable,
-                                       kLocalIbansTable,
-                                       kLocalStoredCvcTable,
-                                       kMaskedBankAccountsMetadataTable,
-                                       kMaskedBankAccountsTable,
-                                       kMaskedCreditCardBenefitsTable,
-                                       kMaskedCreditCardsTable,
-                                       kMaskedIbansMetadataTable,
-                                       kMaskedIbansTable,
-                                       kOfferDataTable,
-                                       kOfferEligibleInstrumentTable,
-                                       kOfferMerchantDomainTable,
-                                       kPaymentInstrumentCreationOptionsTable,
-                                       kPaymentsCustomerDataTable,
-                                       kPaymentsUpiVpaTable,
-                                       kServerCardCloudTokenDataTable,
-                                       kServerCardMetadataTable,
-                                       kServerStoredCvcTable,
-                                       kVirtualCardUsageDataTable});
-  return std::ranges::all_of(kTables, [this](std::string_view table_name) {
-    return DropTableIfExists(db(), table_name);
-  });
-}
-
 }  // namespace autofill
diff --git a/components/autofill/core/browser/webdata/payments/payments_autofill_table.h b/components/autofill/core/browser/webdata/payments/payments_autofill_table.h
index 6390756..0343d651 100644
--- a/components/autofill/core/browser/webdata/payments/payments_autofill_table.h
+++ b/components/autofill/core/browser/webdata/payments/payments_autofill_table.h
@@ -360,10 +360,6 @@
 // -----------------------------------------------------------------------------
 class PaymentsAutofillTable : public WebDatabaseTable {
  public:
-  // Drops the tables created by PaymentsAutofillTable.
-  // TODO(crbug.com/390473673): Remove after M143.
-  class Dropper;
-
   PaymentsAutofillTable();
 
   PaymentsAutofillTable(const PaymentsAutofillTable&) = delete;
@@ -455,8 +451,9 @@
   // This will clear all the local cvcs.
   bool ClearLocalCvcs();
 
-  // Method to clean up for crbug.com/411681430.
-  bool CleanupForCrbug411681430();
+  // Method to clear all local CVCs created before mid-May 2025. For more
+  // information, see crbug.com/411681430.
+  bool ClearLocalCvcsUpToMay2025();
 
 #if BUILDFLAG(IS_IOS)
   // Method to clean up for crbug.com/445879524.
@@ -639,18 +636,6 @@
   bool InitPaymentInstrumentCreationOptionsTable();
 };
 
-class PaymentsAutofillTable::Dropper : public WebDatabaseTable {
- public:
-  Dropper();
-  Dropper(const Dropper&) = delete;
-  Dropper& operator=(const Dropper&) = delete;
-  ~Dropper() override;
-
-  TypeKey GetTypeKey() const override;
-  bool CreateTablesIfNecessary() override;
-  bool MigrateToVersion(int version, bool* update_compatible_version) override;
-};
-
 }  // namespace autofill
 
 #endif  // COMPONENTS_AUTOFILL_CORE_BROWSER_WEBDATA_PAYMENTS_PAYMENTS_AUTOFILL_TABLE_H_
diff --git a/components/autofill/core/browser/webdata/payments/payments_autofill_table_unittest.cc b/components/autofill/core/browser/webdata/payments/payments_autofill_table_unittest.cc
index 40fa3f11..e70b06c 100644
--- a/components/autofill/core/browser/webdata/payments/payments_autofill_table_unittest.cc
+++ b/components/autofill/core/browser/webdata/payments/payments_autofill_table_unittest.cc
@@ -68,7 +68,7 @@
  public:
   PaymentsAutofillTableTest() = default;
 
-  long kCleanupForCrbug411681430LongTimestamp = 1747828800;
+  long kClearTimestampForLocalCvcs = 1747828800;  // May 21, 2025.
 
  protected:
   void SetUp() override {
@@ -512,7 +512,7 @@
   EXPECT_FALSE(cvc_statement.Step());
 }
 
-TEST_F(PaymentsAutofillTableTest, CleanupForCrbug411681430_Test) {
+TEST_F(PaymentsAutofillTableTest, ClearLocalCvcsUpToMay2025_Test) {
   base::test::ScopedFeatureList features(
       features::kAutofillEnableCvcStorageAndFilling);
   CreditCard card_1 = test::WithCvc(test::GetCreditCard());
@@ -532,13 +532,13 @@
           "UPDATE local_stored_cvc SET last_updated_timestamp = ? "
           "WHERE guid=?"));
   update_cvc_statement.BindString(
-      0, std::string_view(
-             base::NumberToString(kCleanupForCrbug411681430LongTimestamp + 1)));
+      0,
+      std::string_view(base::NumberToString(kClearTimestampForLocalCvcs + 1)));
   update_cvc_statement.BindString(1, card_2.guid());
   ASSERT_TRUE(update_cvc_statement.is_valid());
   EXPECT_TRUE(update_cvc_statement.Run());
 
-  table_->CleanupForCrbug411681430();
+  table_->ClearLocalCvcsUpToMay2025();
 
   sql::Statement cvc_statement(db_->GetSQLConnection()->GetUniqueStatement(
       "SELECT guid FROM local_stored_cvc WHERE guid=?"));
diff --git a/components/autofill/ios/browser/autofill_agent.mm b/components/autofill/ios/browser/autofill_agent.mm
index 5062b03..42141bc 100644
--- a/components/autofill/ios/browser/autofill_agent.mm
+++ b/components/autofill/ios/browser/autofill_agent.mm
@@ -161,7 +161,7 @@
       _webFramesManagerObserverBridge;
 
   // The pref service for which this agent was created.
-  raw_ptr<PrefService, DanglingUntriaged> _prefService;
+  raw_ptr<PrefService> _prefService;
 
   // The unique renderer ID of the most recent autocomplete field;
   // tracks the currently-focused form element in order to force filling of
diff --git a/components/browser_sync/browser_sync_switches.cc b/components/browser_sync/browser_sync_switches.cc
index cbbb60a..e53ad0cf 100644
--- a/components/browser_sync/browser_sync_switches.cc
+++ b/components/browser_sync/browser_sync_switches.cc
@@ -32,4 +32,9 @@
              base::FEATURE_DISABLED_BY_DEFAULT);
 #endif
 
+#if !BUILDFLAG(IS_CHROMEOS)
+BASE_FEATURE(kMigrateOutOfSyncSetupIncompleteState,
+             base::FEATURE_ENABLED_BY_DEFAULT);
+#endif  // !BUILDFLAG(IS_CHROMEOS)
+
 }  // namespace switches
diff --git a/components/browser_sync/browser_sync_switches.h b/components/browser_sync/browser_sync_switches.h
index 6dcd2a6..2644069 100644
--- a/components/browser_sync/browser_sync_switches.h
+++ b/components/browser_sync/browser_sync_switches.h
@@ -48,6 +48,12 @@
 // requirements.
 BASE_DECLARE_FEATURE(kForceMigrateSyncingUserToSignedIn);
 
+#if !BUILDFLAG(IS_CHROMEOS)
+// Kill-switch for migration flow of all the users in the sync setup incomplete
+// state to signed-in history-off state.
+BASE_DECLARE_FEATURE(kMigrateOutOfSyncSetupIncompleteState);
+#endif  // !BUILDFLAG(IS_CHROMEOS)
+
 }  // namespace switches
 
 #endif  // COMPONENTS_BROWSER_SYNC_BROWSER_SYNC_SWITCHES_H_
diff --git a/components/browser_sync/sync_to_signin_migration.cc b/components/browser_sync/sync_to_signin_migration.cc
index 016491c..f739137 100644
--- a/components/browser_sync/sync_to_signin_migration.cc
+++ b/components/browser_sync/sync_to_signin_migration.cc
@@ -39,7 +39,7 @@
 
 // These values are persisted to logs. Entries should not be renumbered and
 // numeric values should never be reused.
-// LINT.IfChange(SyncToSigninMigrationDecisionOverall)
+// LINT.IfChange(SyncToSigninMigrationDecision)
 enum class SyncToSigninMigrationDecision {
   kMigrate = 0,
   kDontMigrateNotSignedIn = 1,
@@ -55,6 +55,21 @@
 };
 // LINT.ThenChange(/tools/metrics/histograms/metadata/sync/enums.xml:SyncToSigninMigrationDecisionOverall)
 
+#if !BUILDFLAG(IS_CHROMEOS)
+// These values are persisted to logs. Entries should not be renumbered and
+// numeric values should never be reused.
+// LINT.IfChange(SyncSetupIncompleteMigrationDecision)
+enum class SyncSetupIncompleteMigrationDecision {
+  kMigrate = 0,
+  kDontMigrateNotSignedIn = 1,
+  kDontMigrateNotSyncing = 2,
+  kDontMigrateSyncSetupComplete = 3,
+  kDontMigrateFlagDisabled = 4,
+  kMaxValue = kDontMigrateFlagDisabled
+};
+// LINT.ThenChange(/tools/metrics/histograms/metadata/sync/enums.xml:SyncSetupIncompleteMigrationDecision)
+#endif  // !BUILDFLAG(IS_CHROMEOS)
+
 // See docs of the kFirstTimeTriedToMigrateSyncFeaturePausedToSignin pref.
 void SetFirstTimeTriedToMigrateSyncPaused(PrefService* pref_service) {
   const char* pref_name = syncer::prefs::internal::
@@ -477,6 +492,57 @@
                                               std::move(blocking_operations)));
 }
 
+// On ChromeOS, there exists no sync setup incomplete state.
+#if !BUILDFLAG(IS_CHROMEOS)
+SyncSetupIncompleteMigrationDecision GetSyncSetupIncompleteMigrationDecision(
+    PrefService* pref_service) {
+  if (pref_service->GetString(prefs::kGoogleServicesAccountId).empty()) {
+    // Signed-out user, nothing to migrate.
+    return SyncSetupIncompleteMigrationDecision::kDontMigrateNotSignedIn;
+  }
+
+  if (!pref_service->GetBoolean(prefs::kGoogleServicesConsentedToSync)) {
+    // Not a syncing user, nothing to migrate.
+    return SyncSetupIncompleteMigrationDecision::kDontMigrateNotSyncing;
+  }
+
+  if (pref_service->GetBoolean(
+          syncer::prefs::internal::kSyncInitialSyncFeatureSetupComplete)) {
+    // Sync setup is complete, nothing to migrate.
+    return SyncSetupIncompleteMigrationDecision::kDontMigrateSyncSetupComplete;
+  }
+
+  return base::FeatureList::IsEnabled(syncer::kUnoPhase2FollowUp) &&
+                 base::FeatureList::IsEnabled(
+                     switches::kMigrateOutOfSyncSetupIncompleteState)
+             ? SyncSetupIncompleteMigrationDecision::kMigrate
+             : SyncSetupIncompleteMigrationDecision::kDontMigrateFlagDisabled;
+}
+
+void MaybeMigrateUserWithSyncSetupIncomplete(const base::FilePath& profile_path,
+                                             PrefService* pref_service) {
+  const SyncSetupIncompleteMigrationDecision decision =
+      GetSyncSetupIncompleteMigrationDecision(pref_service);
+  base::UmaHistogramEnumeration("Sync.SyncSetupIncompleteMigrationDecision",
+                                decision);
+  if (decision != SyncSetupIncompleteMigrationDecision::kMigrate) {
+    return;
+  }
+  // Remove ConsentLevel::kSync. This also ensures that the whole migration will
+  // not run a second time.
+  // Note that it's important to explicitly set this pref to false (not just
+  // clear it), since the signin code treats "unset" differently.
+  pref_service->SetBoolean(prefs::kGoogleServicesConsentedToSync, false);
+  // Clear the "previously syncing user" prefs, to prevent accidental misuse.
+  pref_service->ClearPref(prefs::kGoogleServicesLastSyncingGaiaId);
+  pref_service->ClearPref(prefs::kGoogleServicesLastSyncingUsername);
+  // Clear the "InitialSyncFeatureSetup" pref. It's not needed post-migration,
+  // given that users should not be able to enter the sync state anymore.
+  pref_service->ClearPref(
+      syncer::prefs::internal::kSyncInitialSyncFeatureSetupComplete);
+}
+#endif  // !BUILDFLAG(IS_CHROMEOS)
+
 }  // namespace
 
 SyncToSigninMigrationDataTypeDecision GetSyncToSigninMigrationDataTypeDecision(
@@ -511,6 +577,10 @@
 
 void MaybeMigrateSyncingUserToSignedIn(const base::FilePath& profile_path,
                                        PrefService* pref_service) {
+  // On ChromeOS, there exists no sync setup incomplete state.
+#if !BUILDFLAG(IS_CHROMEOS)
+  MaybeMigrateUserWithSyncSetupIncomplete(profile_path, pref_service);
+#endif  // !BUILDFLAG(IS_CHROMEOS)
   MaybeMigrateSyncingUserToSignedInInternal(profile_path, pref_service, {});
 }
 
diff --git a/components/browser_sync/sync_to_signin_migration_unittest.cc b/components/browser_sync/sync_to_signin_migration_unittest.cc
index 40380c8..4bfbf1b 100644
--- a/components/browser_sync/sync_to_signin_migration_unittest.cc
+++ b/components/browser_sync/sync_to_signin_migration_unittest.cc
@@ -1654,5 +1654,262 @@
       });
     });
 
+#if !BUILDFLAG(IS_CHROMEOS)
+class SyncSetupIncompleteMigrationTest : public SyncToSigninMigrationTestBase,
+                                         public testing::Test {
+ public:
+  SyncSetupIncompleteMigrationTest()
+      : SyncToSigninMigrationTestBase(
+            /*migration_feature_enabled=*/false,
+            /*force_migration_feature_enabled=*/false) {
+    feature_list_.InitWithFeatures(
+        {syncer::kUnoPhase2FollowUp,
+         switches::kMigrateOutOfSyncSetupIncompleteState},
+        {});
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+TEST_F(SyncSetupIncompleteMigrationTest, Migrate) {
+  ASSERT_TRUE(sync_service_.HasSyncConsent());
+
+  const GaiaId gaia_id = sync_service_.GetAccountInfo().gaia;
+  const std::string email = sync_service_.GetAccountInfo().email;
+
+  // Save the above state to prefs.
+  RecordStateToPrefs();
+  // Mark sync setup incomplete.
+  sync_prefs_->ClearInitialSyncFeatureSetupComplete();
+
+  ASSERT_EQ(pref_service_.GetString(prefs::kGoogleServicesAccountId),
+            gaia_id.ToString());
+  ASSERT_TRUE(pref_service_.GetBoolean(prefs::kGoogleServicesConsentedToSync));
+  ASSERT_EQ(pref_service_.GetString(prefs::kGoogleServicesLastSyncingGaiaId),
+            gaia_id.ToString());
+  ASSERT_EQ(pref_service_.GetString(prefs::kGoogleServicesLastSyncingUsername),
+            email);
+  ASSERT_FALSE(sync_prefs_->IsInitialSyncFeatureSetupComplete());
+
+  base::HistogramTester histogram_tester;
+  MaybeMigrateSyncingUserToSignedIn(fake_profile_dir_.GetPath(),
+                                    &pref_service_);
+
+  histogram_tester.ExpectUniqueSample(
+      "Sync.SyncSetupIncompleteMigrationDecision",
+      /*kMigrate*/ 0, 1);
+  histogram_tester.ExpectUniqueSample(
+      "Sync.SyncToSigninMigrationDecision",
+      /*SyncToSigninMigrationDecision::kDontMigrateNotSyncing*/ 2, 1);
+
+  // Note that TestSyncService doesn't consume the prefs, so verify the prefs
+  // directly here.
+  // The user should still be signed in.
+  EXPECT_EQ(pref_service_.GetString(prefs::kGoogleServicesAccountId),
+            gaia_id.ToString());
+  // But not syncing anymore.
+  EXPECT_FALSE(pref_service_.GetBoolean(prefs::kGoogleServicesConsentedToSync));
+  EXPECT_FALSE(
+      pref_service_.GetUserPrefValue(prefs::kGoogleServicesLastSyncingGaiaId));
+  EXPECT_FALSE(pref_service_.GetUserPrefValue(
+      prefs::kGoogleServicesLastSyncingUsername));
+  EXPECT_FALSE(pref_service_.GetUserPrefValue(
+      syncer::prefs::internal::kSyncInitialSyncFeatureSetupComplete));
+}
+
+TEST_F(SyncSetupIncompleteMigrationTest, ShouldNotMigrateIfNotSignedIn) {
+  // Signed-out user.
+  sync_service_.SetSignedOut();
+  // Save the above state to prefs.
+  RecordStateToPrefs();
+
+  base::HistogramTester histogram_tester;
+  MaybeMigrateSyncingUserToSignedIn(fake_profile_dir_.GetPath(),
+                                    &pref_service_);
+
+  histogram_tester.ExpectUniqueSample(
+      "Sync.SyncSetupIncompleteMigrationDecision",
+      /*kDontMigrateNotSignedIn*/ 1, 1);
+  histogram_tester.ExpectUniqueSample(
+      "Sync.SyncToSigninMigrationDecision",
+      /*SyncToSigninMigrationDecision::kDontMigrateNotSignedIn*/ 1, 1);
+}
+
+TEST_F(SyncSetupIncompleteMigrationTest, ShouldNotMigrateIfNotSyncing) {
+  // Signed-in non-syncing user.
+  sync_service_.SetSignedIn(signin::ConsentLevel::kSignin);
+  // Save the above state to prefs.
+  RecordStateToPrefs();
+
+  base::HistogramTester histogram_tester;
+  MaybeMigrateSyncingUserToSignedIn(fake_profile_dir_.GetPath(),
+                                    &pref_service_);
+
+  histogram_tester.ExpectUniqueSample(
+      "Sync.SyncSetupIncompleteMigrationDecision",
+      /*kDontMigrateNotSyncing*/ 2, 1);
+  histogram_tester.ExpectUniqueSample(
+      "Sync.SyncToSigninMigrationDecision",
+      /*SyncToSigninMigrationDecision::kDontMigrateNotSyncing*/ 2, 1);
+}
+
+TEST_F(SyncSetupIncompleteMigrationTest, ShouldNotMigrateIfSyncSetupComplete) {
+  ASSERT_TRUE(sync_service_.HasSyncConsent());
+
+  const GaiaId gaia_id = sync_service_.GetAccountInfo().gaia;
+  const std::string email = sync_service_.GetAccountInfo().email;
+
+  // Save the above state to prefs.
+  RecordStateToPrefs();
+
+  ASSERT_EQ(pref_service_.GetString(prefs::kGoogleServicesAccountId),
+            gaia_id.ToString());
+  ASSERT_TRUE(pref_service_.GetBoolean(prefs::kGoogleServicesConsentedToSync));
+  ASSERT_EQ(pref_service_.GetString(prefs::kGoogleServicesLastSyncingGaiaId),
+            gaia_id.ToString());
+  ASSERT_EQ(pref_service_.GetString(prefs::kGoogleServicesLastSyncingUsername),
+            email);
+  ASSERT_TRUE(sync_prefs_->IsInitialSyncFeatureSetupComplete());
+
+  base::HistogramTester histogram_tester;
+  MaybeMigrateSyncingUserToSignedIn(fake_profile_dir_.GetPath(),
+                                    &pref_service_);
+
+  histogram_tester.ExpectUniqueSample(
+      "Sync.SyncSetupIncompleteMigrationDecision",
+      /*kDontMigrateSyncSetupComplete*/ 3, 1);
+  histogram_tester.ExpectUniqueSample(
+      "Sync.SyncToSigninMigrationDecision",
+      /*SyncToSigninMigrationDecision::kDontMigrateFlagDisabled*/ 5, 1);
+
+  // Note that TestSyncService doesn't consume the prefs, so verify the prefs
+  // directly here.
+  EXPECT_EQ(pref_service_.GetString(prefs::kGoogleServicesAccountId),
+            gaia_id.ToString());
+  EXPECT_TRUE(pref_service_.GetBoolean(prefs::kGoogleServicesConsentedToSync));
+  EXPECT_EQ(pref_service_.GetString(prefs::kGoogleServicesLastSyncingGaiaId),
+            gaia_id.ToString());
+  EXPECT_EQ(pref_service_.GetString(prefs::kGoogleServicesLastSyncingUsername),
+            email);
+  EXPECT_TRUE(sync_prefs_->IsInitialSyncFeatureSetupComplete());
+}
+
+TEST_F(SyncSetupIncompleteMigrationTest, ShouldNotMigrateIfFlagDisabled) {
+  ASSERT_TRUE(sync_service_.HasSyncConsent());
+
+  const GaiaId gaia_id = sync_service_.GetAccountInfo().gaia;
+  const std::string email = sync_service_.GetAccountInfo().email;
+
+  // Save the above state to prefs.
+  RecordStateToPrefs();
+  // Mark sync setup incomplete.
+  sync_prefs_->ClearInitialSyncFeatureSetupComplete();
+
+  ASSERT_EQ(pref_service_.GetString(prefs::kGoogleServicesAccountId),
+            gaia_id.ToString());
+  ASSERT_TRUE(pref_service_.GetBoolean(prefs::kGoogleServicesConsentedToSync));
+  ASSERT_EQ(pref_service_.GetString(prefs::kGoogleServicesLastSyncingGaiaId),
+            gaia_id.ToString());
+  ASSERT_EQ(pref_service_.GetString(prefs::kGoogleServicesLastSyncingUsername),
+            email);
+  ASSERT_FALSE(sync_prefs_->IsInitialSyncFeatureSetupComplete());
+
+  // Kill-switch active.
+  {
+    base::test::ScopedFeatureList feature_list;
+    feature_list.InitWithFeatures(
+        /*enabled_features=*/{syncer::kUnoPhase2FollowUp},
+        /*disabled_features=*/
+        {switches::kMigrateOutOfSyncSetupIncompleteState});
+
+    base::HistogramTester histogram_tester;
+    MaybeMigrateSyncingUserToSignedIn(fake_profile_dir_.GetPath(),
+                                      &pref_service_);
+
+    histogram_tester.ExpectUniqueSample(
+        "Sync.SyncSetupIncompleteMigrationDecision",
+        /*kDontMigrateFlagDisabled*/ 4, 1);
+    histogram_tester.ExpectUniqueSample(
+        "Sync.SyncToSigninMigrationDecision",
+        /*SyncToSigninMigrationDecision::kDontMigrateFlagDisabled*/ 5, 1);
+
+    // Note that TestSyncService doesn't consume the prefs, so verify the prefs
+    // directly here.
+    EXPECT_EQ(pref_service_.GetString(prefs::kGoogleServicesAccountId),
+              gaia_id.ToString());
+    EXPECT_TRUE(
+        pref_service_.GetBoolean(prefs::kGoogleServicesConsentedToSync));
+    EXPECT_EQ(pref_service_.GetString(prefs::kGoogleServicesLastSyncingGaiaId),
+              gaia_id.ToString());
+    EXPECT_EQ(
+        pref_service_.GetString(prefs::kGoogleServicesLastSyncingUsername),
+        email);
+  }
+  // Fast-follows flag disabled.
+  {
+    base::test::ScopedFeatureList feature_list;
+    feature_list.InitWithFeatures(
+        /*enabled_features=*/{switches::kMigrateOutOfSyncSetupIncompleteState},
+        /*disabled_features=*/{syncer::kUnoPhase2FollowUp});
+
+    base::HistogramTester histogram_tester;
+    MaybeMigrateSyncingUserToSignedIn(fake_profile_dir_.GetPath(),
+                                      &pref_service_);
+
+    histogram_tester.ExpectUniqueSample(
+        "Sync.SyncSetupIncompleteMigrationDecision",
+        /*kDontMigrateFlagDisabled*/ 4, 1);
+    histogram_tester.ExpectUniqueSample(
+        "Sync.SyncToSigninMigrationDecision",
+        /*SyncToSigninMigrationDecision::kDontMigrateFlagDisabled*/ 5, 1);
+
+    // Note that TestSyncService doesn't consume the prefs, so verify the prefs
+    // directly here.
+    EXPECT_EQ(pref_service_.GetString(prefs::kGoogleServicesAccountId),
+              gaia_id.ToString());
+    EXPECT_TRUE(
+        pref_service_.GetBoolean(prefs::kGoogleServicesConsentedToSync));
+    EXPECT_EQ(pref_service_.GetString(prefs::kGoogleServicesLastSyncingGaiaId),
+              gaia_id.ToString());
+    EXPECT_EQ(
+        pref_service_.GetString(prefs::kGoogleServicesLastSyncingUsername),
+        email);
+  }
+  // Both flags enabled.
+  {
+    base::test::ScopedFeatureList feature_list;
+    feature_list.InitWithFeatures(
+        /*enabled_features=*/{syncer::kUnoPhase2FollowUp,
+                              switches::kMigrateOutOfSyncSetupIncompleteState},
+        /*disabled_features=*/{});
+
+    base::HistogramTester histogram_tester;
+    MaybeMigrateSyncingUserToSignedIn(fake_profile_dir_.GetPath(),
+                                      &pref_service_);
+
+    histogram_tester.ExpectUniqueSample(
+        "Sync.SyncSetupIncompleteMigrationDecision",
+        /*kMigrate*/ 0, 1);
+    histogram_tester.ExpectUniqueSample(
+        "Sync.SyncToSigninMigrationDecision",
+        /*SyncToSigninMigrationDecision::kDontMigrateNotSyncing*/ 2, 1);
+
+    // Note that TestSyncService doesn't consume the prefs, so verify the prefs
+    // directly here.
+    EXPECT_EQ(pref_service_.GetString(prefs::kGoogleServicesAccountId),
+              gaia_id.ToString());
+    EXPECT_FALSE(
+        pref_service_.GetBoolean(prefs::kGoogleServicesConsentedToSync));
+    EXPECT_FALSE(pref_service_.GetUserPrefValue(
+        prefs::kGoogleServicesLastSyncingGaiaId));
+    EXPECT_FALSE(pref_service_.GetUserPrefValue(
+        prefs::kGoogleServicesLastSyncingUsername));
+    EXPECT_FALSE(pref_service_.GetUserPrefValue(
+        syncer::prefs::internal::kSyncInitialSyncFeatureSetupComplete));
+  }
+}
+#endif  // !BUILDFLAG(IS_CHROMEOS)
+
 }  // namespace
 }  // namespace browser_sync
diff --git a/components/browser_ui/bottomsheet/android/internal/java/src/org/chromium/components/browser_ui/bottomsheet/BottomSheet.java b/components/browser_ui/bottomsheet/android/internal/java/src/org/chromium/components/browser_ui/bottomsheet/BottomSheet.java
index 36a91f2a..146d220 100644
--- a/components/browser_ui/bottomsheet/android/internal/java/src/org/chromium/components/browser_ui/bottomsheet/BottomSheet.java
+++ b/components/browser_ui/bottomsheet/android/internal/java/src/org/chromium/components/browser_ui/bottomsheet/BottomSheet.java
@@ -658,7 +658,7 @@
     }
 
     /** Cancels and nulls the height animation if it exists. */
-    void cancelAnimation() {
+    private void cancelAnimation() {
         if (mSettleAnimator == null) return;
         mSettleAnimator.cancel();
         mSettleAnimator = null;
@@ -666,11 +666,10 @@
 
     /**
      * Creates the sheet's animation to a target state.
-     *
      * @param targetState The target state.
      * @param reason The reason the sheet started animation.
      */
-    void createSettleAnimation(
+    private void createSettleAnimation(
             @SheetState final int targetState, @StateChangeReason final int reason) {
         mTargetState = targetState;
         mSettleAnimator =
@@ -818,10 +817,8 @@
         return mSheetContent != null && mSheetContent.getPeekHeight() != HeightMode.DISABLED;
     }
 
-    /**
-     * @return Whether the half-height of the sheet is enabled.
-     */
-    boolean isHalfStateEnabled() {
+    /** @return Whether the half-height of the sheet is enabled. */
+    private boolean isHalfStateEnabled() {
         if (mSheetContent == null) return false;
 
         // Half state is invalid on small screens, when wrapping content at full height, and when
diff --git a/components/browser_ui/bottomsheet/android/internal/java/src/org/chromium/components/browser_ui/bottomsheet/BottomSheetControllerImpl.java b/components/browser_ui/bottomsheet/android/internal/java/src/org/chromium/components/browser_ui/bottomsheet/BottomSheetControllerImpl.java
index a8768231..c7f840e3 100644
--- a/components/browser_ui/bottomsheet/android/internal/java/src/org/chromium/components/browser_ui/bottomsheet/BottomSheetControllerImpl.java
+++ b/components/browser_ui/bottomsheet/android/internal/java/src/org/chromium/components/browser_ui/bottomsheet/BottomSheetControllerImpl.java
@@ -624,43 +624,6 @@
     }
 
     @Override
-    public void updateSheetHeight() {
-        if (mBottomSheet == null) {
-            return;
-        }
-
-        updateSheetHeight(mBottomSheet.getOpeningState());
-    }
-
-    @Override
-    public void updateSheetHeight(@SheetState int sheetState) {
-        if (mBottomSheet == null
-                || mSuppressionTokens.hasTokens()
-                || mBottomSheet.isHiding()
-                || mBottomSheet.getCurrentSheetContent() == null) {
-            return;
-        }
-
-        if (sheetState == SheetState.NONE || sheetState == SheetState.SCROLLING) {
-            return;
-        }
-
-        // Update the sheet to full state if the target state is half, but the sheet has half state
-        // disabled.
-        if (sheetState == SheetState.HALF && !mBottomSheet.isHalfStateEnabled()) {
-            sheetState = SheetState.FULL;
-        }
-
-        if (sheetState != mBottomSheet.getSheetState()) {
-            mBottomSheet.setSheetState(sheetState, true);
-            return;
-        }
-
-        mBottomSheet.cancelAnimation();
-        mBottomSheet.createSettleAnimation(sheetState, StateChangeReason.NONE);
-    }
-
-    @Override
     public boolean collapseSheet(boolean animate) {
         if (mBottomSheet == null || mSuppressionTokens.hasTokens() || mBottomSheet.isHiding()) {
             return false;
diff --git a/components/browser_ui/bottomsheet/android/java/src/org/chromium/components/browser_ui/bottomsheet/BottomSheetController.java b/components/browser_ui/bottomsheet/android/java/src/org/chromium/components/browser_ui/bottomsheet/BottomSheetController.java
index f3ecf77..8b1f00b1 100644
--- a/components/browser_ui/bottomsheet/android/java/src/org/chromium/components/browser_ui/bottomsheet/BottomSheetController.java
+++ b/components/browser_ui/bottomsheet/android/java/src/org/chromium/components/browser_ui/bottomsheet/BottomSheetController.java
@@ -123,23 +123,8 @@
     void expandSheet();
 
     /**
-     * Update the sheet height based on sheet content. If there is no content in the sheet, this is
-     * a noop.
-     */
-    void updateSheetHeight();
-
-    /**
-     * Update the sheet height based on target state, and update the sheet state if the target is
-     * different from the current state.
-     *
-     * @param sheetState The tartget state after updating the sheet height.
-     */
-    void updateSheetHeight(@SheetState int sheetState);
-
-    /**
-     * Collapse the current sheet to peek state. Sheet may not change the state if the state is not
-     * allowed.
-     *
+     * Collapse the current sheet to peek state. Sheet may not change the state if the state
+     * is not allowed.
      * @param animate {@code true} for animation effect.
      * @return {@code true} if the sheet could go to the peek state.
      */
diff --git a/components/browser_ui/styles/android/java/src/org/chromium/components/browser_ui/styles/IncognitoColors.java b/components/browser_ui/styles/android/java/src/org/chromium/components/browser_ui/styles/IncognitoColors.java
index c8b2994c..af44d67 100644
--- a/components/browser_ui/styles/android/java/src/org/chromium/components/browser_ui/styles/IncognitoColors.java
+++ b/components/browser_ui/styles/android/java/src/org/chromium/components/browser_ui/styles/IncognitoColors.java
@@ -75,6 +75,20 @@
         return ColorUtils.setAlphaComponentWithFloat(colorOnSurface, /* alpha= */ 0.16f);
     }
 
+    /** {@see SemanticColorUtils#getDividerLineBgColor} */
+    public static @ColorInt int getDividerLineBgColor(Context context, boolean isIncognito) {
+        return isIncognito
+                ? context.getColor(R.color.divider_line_bg_color_light)
+                : SemanticColorUtils.getDividerLineBgColor(context);
+    }
+
+    /** Returns the correct text appearance style res for primary colored medium text. */
+    public static @StyleRes int getTextMediumPrimary(boolean isIncognito) {
+        return isIncognito
+                ? R.style.TextAppearance_TextMedium_Primary_Baseline_Light
+                : R.style.TextAppearance_TextMedium_Primary;
+    }
+
     /** Returns the correct text appearance style res for primary colored small text. */
     public static @StyleRes int getTextSmallPrimary(boolean isIncognito) {
         return isIncognito
diff --git a/components/contextual_search/BUILD.gn b/components/contextual_search/BUILD.gn
index bf2f79b..0890d04d 100644
--- a/components/contextual_search/BUILD.gn
+++ b/components/contextual_search/BUILD.gn
@@ -33,9 +33,7 @@
   ]
 
   deps = [
-    "//base",
     "//components/keyed_service/core",
-    "//components/lens",
     "//components/lens/proto/server:proto",
     "//components/search_engines",
     "//components/sessions",
@@ -44,9 +42,14 @@
     "//components/version_info:channel",
     "//mojo/public/mojom/base",
     "//services/network/public/cpp",
-    "//third_party/lens_server_proto:lens_overlay_proto",
     "//url",
   ]
+
+  public_deps = [
+    "//base",
+    "//components/lens",
+    "//third_party/lens_server_proto:lens_overlay_proto",
+  ]
 }
 
 # Public implementation target - contains the service implementation.
@@ -79,17 +82,21 @@
   sources = [
     "contextual_search_metrics_recorder_unittest.cc",
     "contextual_search_service_unittest.cc",
+    "mock_contextual_search_context_controller.cc",
+    "mock_contextual_search_context_controller.h",
   ]
   deps = [
     ":impl",
     ":public",
     "//base/test:test_support",
     "//components/lens",
+    "//components/lens/proto/server:proto",
     "//components/search_engines:test_support",
     "//components/signin/public/identity_manager:test_support",
     "//components/variations",
     "//services/network:test_support",
     "//services/network/public/cpp",
+    "//testing/gmock",
     "//testing/gtest",
   ]
 }
diff --git a/components/contextual_search/contextual_search_service_unittest.cc b/components/contextual_search/contextual_search_service_unittest.cc
index b0142c01..82665fc9 100644
--- a/components/contextual_search/contextual_search_service_unittest.cc
+++ b/components/contextual_search/contextual_search_service_unittest.cc
@@ -5,6 +5,7 @@
 #include "components/contextual_search/contextual_search_service.h"
 
 #include "base/test/task_environment.h"
+#include "components/contextual_search/mock_contextual_search_context_controller.h"
 #include "components/search_engines/search_engines_test_environment.h"
 #include "components/signin/public/identity_manager/identity_test_environment.h"
 #include "components/variations/variations_client.h"
@@ -18,6 +19,7 @@
 
 namespace {
 
+using testing::_;
 using testing::IsNull;
 using testing::NotNull;
 
@@ -50,6 +52,20 @@
         &fake_variations_client_, version_info::Channel::UNKNOWN, "en-US");
   }
 
+  lens::ClientToAimMessage CaptureClientToAimRequest(
+      std::unique_ptr<
+          ContextualSearchContextController::CreateClientToAimRequestInfo>
+          create_client_to_aim_request_info) {
+    captured_client_to_aim_message_ =
+        std::move(create_client_to_aim_request_info);
+    return lens::ClientToAimMessage();
+  }
+
+  ContextualSearchContextController::CreateClientToAimRequestInfo*
+  GetCapturedClientToAimRequest() {
+    return captured_client_to_aim_message_.get();
+  }
+
  protected:
   base::test::TaskEnvironment task_environment_;
   signin::IdentityTestEnvironment identity_test_env_;
@@ -58,6 +74,9 @@
   search_engines::SearchEnginesTestEnvironment search_engines_test_environment_;
   FakeVariationsClient fake_variations_client_;
   std::unique_ptr<ContextualSearchService> service_;
+  std::unique_ptr<
+      ContextualSearchContextController::CreateClientToAimRequestInfo>
+      captured_client_to_aim_message_;
 };
 
 TEST_F(ContextualSearchServiceTest, Session) {
@@ -129,4 +148,86 @@
             session1_handle1->GetMetricsRecorder());
 }
 
+TEST_F(ContextualSearchServiceTest, PendingContextTokens) {
+  auto mock_controller =
+      std::make_unique<MockContextualSearchContextController>();
+  auto metrics_recorder = std::make_unique<ContextualSearchMetricsRecorder>(
+      ContextualSearchSource::kUnknown);
+
+  MockContextualSearchContextController* mock_controller_ptr =
+      mock_controller.get();
+
+  auto session_handle = service_->CreateSessionForTesting(
+      std::move(mock_controller), std::move(metrics_recorder));
+
+  // Add some dummy tokens.
+  base::UnguessableToken token1 = base::UnguessableToken::Create();
+  base::UnguessableToken token2 = base::UnguessableToken::Create();
+  session_handle->GetUploadedContextTokensForTesting().push_back(token1);
+  session_handle->GetUploadedContextTokensForTesting().push_back(token2);
+
+  // Capture the tokens before they are moved.
+  std::vector<base::UnguessableToken> expected_request_tokens1;
+  expected_request_tokens1.push_back(token1);
+  expected_request_tokens1.push_back(token2);
+
+  // Expect CreateClientToAimRequest to be called and capture the message.
+  EXPECT_CALL(*mock_controller_ptr, CreateClientToAimRequest(_))
+      .WillOnce(testing::Invoke(
+          this, &ContextualSearchServiceTest::CaptureClientToAimRequest));
+
+  // Call CreateClientToAimRequest to move tokens to pending.
+  auto create_client_to_aim_request_info = std::make_unique<
+      ContextualSearchContextController::CreateClientToAimRequestInfo>();
+  session_handle->CreateClientToAimRequest(
+      std::move(create_client_to_aim_request_info));
+
+  // Verify that the request contains the correct unguessable tokens.
+  EXPECT_THAT(GetCapturedClientToAimRequest()->file_tokens,
+              testing::UnorderedElementsAreArray(expected_request_tokens1));
+
+  // Verify that submitted context tokens contains the moved tokens.
+  std::vector<base::UnguessableToken> expected_submitted_tokens;
+  expected_submitted_tokens.push_back(token1);
+  expected_submitted_tokens.push_back(token2);
+  EXPECT_THAT(session_handle->GetSubmittedContextTokens(),
+              testing::UnorderedElementsAreArray(expected_submitted_tokens));
+  EXPECT_TRUE(session_handle->GetUploadedContextTokens().empty());
+
+  // Add more tokens and call CreateClientToAimRequest again.
+  base::UnguessableToken token3 = base::UnguessableToken::Create();
+  session_handle->GetUploadedContextTokensForTesting().push_back(token3);
+
+  // Capture the tokens for the second request.
+  std::vector<base::UnguessableToken> expected_request_tokens2;
+  expected_request_tokens2.push_back(token3);
+
+  // Expect CreateClientToAimRequest to be called again and capture the message.
+  EXPECT_CALL(*mock_controller_ptr, CreateClientToAimRequest(_))
+      .WillOnce(testing::Invoke(
+          this, &ContextualSearchServiceTest::CaptureClientToAimRequest));
+
+  auto create_client_to_aim_request_info2 = std::make_unique<
+      ContextualSearchContextController::CreateClientToAimRequestInfo>();
+  session_handle->CreateClientToAimRequest(
+      std::move(create_client_to_aim_request_info2));
+
+  // Verify that the second request contains only the newly added token.
+  EXPECT_THAT(GetCapturedClientToAimRequest()->file_tokens,
+              testing::UnorderedElementsAreArray(expected_request_tokens2));
+
+  // Verify that the submitted context tokens has both the previous and the new
+  // tokens.
+  expected_submitted_tokens.push_back(token3);
+  EXPECT_THAT(session_handle->GetSubmittedContextTokens(),
+              testing::UnorderedElementsAreArray(expected_submitted_tokens));
+  EXPECT_TRUE(session_handle->GetUploadedContextTokens().empty());
+
+  // Clear the pending tokens.
+  session_handle->ClearSubmittedContextTokens();
+
+  // Verify that the submitted context tokens are empty.
+  EXPECT_TRUE(session_handle->GetSubmittedContextTokens().empty());
+}
+
 }  // namespace contextual_search
diff --git a/components/contextual_search/contextual_search_session_handle.cc b/components/contextual_search/contextual_search_session_handle.cc
index 4f507150..187822a 100644
--- a/components/contextual_search/contextual_search_session_handle.cc
+++ b/components/contextual_search/contextual_search_session_handle.cc
@@ -238,7 +238,16 @@
     return lens::ClientToAimMessage();
   }
 
-  create_client_to_aim_request_info->file_tokens = uploaded_context_tokens_;
+  // Move the uploaded tokens to the request's file_tokens.
+  create_client_to_aim_request_info->file_tokens =
+      std::exchange(uploaded_context_tokens_, {});
+
+  // Copy the tokens from this request to the list of all submitted tokens.
+  submitted_context_tokens_.insert(
+      submitted_context_tokens_.end(),
+      create_client_to_aim_request_info->file_tokens.begin(),
+      create_client_to_aim_request_info->file_tokens.end());
+
   // TODO(crbug.com/463705266): Add metrics recording.
 
   return context_controller->CreateClientToAimRequest(
@@ -250,6 +259,15 @@
   return uploaded_context_tokens_;
 }
 
+std::vector<base::UnguessableToken>
+ContextualSearchSessionHandle::GetSubmittedContextTokens() const {
+  return submitted_context_tokens_;
+}
+
+void ContextualSearchSessionHandle::ClearSubmittedContextTokens() {
+  submitted_context_tokens_.clear();
+}
+
 base::WeakPtr<ContextualSearchSessionHandle>
 ContextualSearchSessionHandle::AsWeakPtr() {
   return weak_ptr_factory_.GetWeakPtr();
diff --git a/components/contextual_search/contextual_search_session_handle.h b/components/contextual_search/contextual_search_session_handle.h
index c7dce26d..8d5ee64 100644
--- a/components/contextual_search/contextual_search_session_handle.h
+++ b/components/contextual_search/contextual_search_session_handle.h
@@ -119,6 +119,16 @@
     return uploaded_context_tokens_;
   }
 
+  // Returns the list of submitted context tokens for this particular instance
+  // of the session. These are uploaded and submitted, but we have not received
+  // confirmation that they are available on the server.
+  std::vector<base::UnguessableToken> GetSubmittedContextTokens() const;
+
+  // Clears the list of submitted context tokens for this particular instance of
+  // the session. This is intended to be invoked when the server has responded
+  // that it has received the submitted context.
+  void ClearSubmittedContextTokens();
+
  private:
   friend class ContextualSearchService;
 
@@ -131,6 +141,12 @@
   // contextual tasks ui.
   std::vector<base::UnguessableToken> uploaded_context_tokens_;
 
+  // The list of uploaded and submitted, but not yet committed context tokens
+  // for this particular instance of the session. This list is unique to this
+  // instance of the session handle, meaning that it is unique per instance of
+  // the contextual tasks ui.
+  std::vector<base::UnguessableToken> submitted_context_tokens_;
+
   // The service that vended this handle. This is a weak pointer because a
   // handle may outlive the service.
   const base::WeakPtr<ContextualSearchService> service_;
diff --git a/components/contextual_search/mock_contextual_search_context_controller.cc b/components/contextual_search/mock_contextual_search_context_controller.cc
new file mode 100644
index 0000000..8f3e762
--- /dev/null
+++ b/components/contextual_search/mock_contextual_search_context_controller.cc
@@ -0,0 +1,14 @@
+// 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/contextual_search/mock_contextual_search_context_controller.h"
+
+namespace contextual_search {
+
+MockContextualSearchContextController::MockContextualSearchContextController() =
+    default;
+MockContextualSearchContextController::
+    ~MockContextualSearchContextController() = default;
+
+}  // namespace contextual_search
diff --git a/components/contextual_search/mock_contextual_search_context_controller.h b/components/contextual_search/mock_contextual_search_context_controller.h
new file mode 100644
index 0000000..fc62a998
--- /dev/null
+++ b/components/contextual_search/mock_contextual_search_context_controller.h
@@ -0,0 +1,67 @@
+// 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_CONTEXTUAL_SEARCH_MOCK_CONTEXTUAL_SEARCH_CONTEXT_CONTROLLER_H_
+#define COMPONENTS_CONTEXTUAL_SEARCH_MOCK_CONTEXTUAL_SEARCH_CONTEXT_CONTROLLER_H_
+
+#include <memory>
+#include <optional>
+
+#include "base/unguessable_token.h"
+#include "components/contextual_search/contextual_search_context_controller.h"
+#include "components/lens/contextual_input.h"
+#include "components/lens/proto/server/lens_overlay_response.pb.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "third_party/lens_server_proto/aim_communication.pb.h"
+
+namespace contextual_search {
+
+class MockContextualSearchContextController
+    : public ContextualSearchContextController {
+ public:
+  MockContextualSearchContextController();
+  ~MockContextualSearchContextController() override;
+
+  MOCK_METHOD(void, InitializeIfNeeded, (), (override));
+  MOCK_METHOD(
+      GURL,
+      CreateSearchUrl,
+      (std::unique_ptr<CreateSearchUrlRequestInfo> search_url_request_info),
+      (override));
+  MOCK_METHOD(lens::ClientToAimMessage,
+              CreateClientToAimRequest,
+              (std::unique_ptr<CreateClientToAimRequestInfo>
+                   create_client_to_aim_request_info),
+              (override));
+  MOCK_METHOD(void, AddObserver, (FileUploadStatusObserver * obs), (override));
+  MOCK_METHOD(void,
+              RemoveObserver,
+              (FileUploadStatusObserver * obs),
+              (override));
+  MOCK_METHOD(void,
+              StartFileUploadFlow,
+              (const base::UnguessableToken& file_token,
+               std::unique_ptr<lens::ContextualInputData> contextual_input_data,
+               std::optional<lens::ImageEncodingOptions> image_options),
+              (override));
+  MOCK_METHOD(bool,
+              DeleteFile,
+              (const base::UnguessableToken& file_token),
+              (override));
+  MOCK_METHOD(void, ClearFiles, (), (override));
+  MOCK_METHOD(
+      std::unique_ptr<lens::proto::LensOverlaySuggestInputs>,
+      CreateSuggestInputs,
+      (const std::vector<base::UnguessableToken>& attached_context_tokens),
+      (override));
+  MOCK_METHOD(const FileInfo*,
+              GetFileInfo,
+              (const base::UnguessableToken& file_token),
+              (override));
+  MOCK_METHOD(std::vector<const FileInfo*>, GetFileInfoList, (), (override));
+};
+
+}  // namespace contextual_search
+
+#endif  // COMPONENTS_CONTEXTUAL_SEARCH_MOCK_CONTEXTUAL_SEARCH_CONTEXT_CONTROLLER_H_
diff --git a/components/contextual_tasks/internal/pending_context_decorator.cc b/components/contextual_tasks/internal/pending_context_decorator.cc
index 22d5b62..c1dc42b1 100644
--- a/components/contextual_tasks/internal/pending_context_decorator.cc
+++ b/components/contextual_tasks/internal/pending_context_decorator.cc
@@ -40,6 +40,8 @@
   }
 
   auto tokens = handle->GetUploadedContextTokens();
+  auto submitted_tokens = handle->GetSubmittedContextTokens();
+  tokens.insert(tokens.end(), submitted_tokens.begin(), submitted_tokens.end());
   for (const auto& token : tokens) {
     const auto* file_info = controller->GetFileInfo(token);
     if (!file_info || !file_info->tab_url.has_value()) {
diff --git a/components/contextual_tasks/internal/pending_context_decorator_unittest.cc b/components/contextual_tasks/internal/pending_context_decorator_unittest.cc
index 8e55a5c..e3bb11f2 100644
--- a/components/contextual_tasks/internal/pending_context_decorator_unittest.cc
+++ b/components/contextual_tasks/internal/pending_context_decorator_unittest.cc
@@ -113,6 +113,21 @@
                },
                &token));
 
+  // Move the token to the submitted state.
+  session_handle->CreateClientToAimRequest(
+      std::make_unique<contextual_search::ContextualSearchContextController::
+                           CreateClientToAimRequestInfo>());
+
+  // Add a second tab context that will remain in the uploaded state.
+  base::UnguessableToken token2;
+  session_handle->AddTabContext(
+      456, base::BindOnce(
+               [](base::UnguessableToken* out_token,
+                  const base::UnguessableToken& new_token) {
+                 *out_token = new_token;
+               },
+               &token2));
+
   // Mock the controller to return valid file info for the token.
   contextual_search::FileInfo file_info;
   file_info.tab_url = GURL("https://example.com/");
@@ -121,6 +136,13 @@
   EXPECT_CALL(*mock_controller_ptr, GetFileInfo(token))
       .WillOnce(testing::Return(&file_info));
 
+  contextual_search::FileInfo file_info2;
+  file_info2.tab_url = GURL("https://example2.com/");
+  file_info2.tab_title = "Test Title 2";
+  file_info2.tab_session_id = SessionID::FromSerializedValue(456);
+  EXPECT_CALL(*mock_controller_ptr, GetFileInfo(token2))
+      .WillOnce(testing::Return(&file_info2));
+
   // Set up the decoration params with the session handle.
   ContextDecorationParams params;
   params.contextual_search_session_handle = session_handle->AsWeakPtr();
@@ -137,12 +159,18 @@
       base::BindOnce(
           [](base::OnceClosure quit_closure,
              std::unique_ptr<ContextualTaskContext> context) {
-            ASSERT_EQ(1u, context->GetUrlAttachments().size());
+            ASSERT_EQ(2u, context->GetUrlAttachments().size());
             auto& attachment = context->GetMutableUrlAttachmentsForTesting()[0];
-            EXPECT_EQ("https://example.com/", attachment.GetURL());
-            EXPECT_EQ(u"Test Title", attachment.GetTitle());
-            EXPECT_EQ(SessionID::FromSerializedValue(123),
+            EXPECT_EQ("https://example2.com/", attachment.GetURL());
+            EXPECT_EQ(u"Test Title 2", attachment.GetTitle());
+            EXPECT_EQ(SessionID::FromSerializedValue(456),
                       attachment.GetTabSessionId());
+            auto& attachment2 =
+                context->GetMutableUrlAttachmentsForTesting()[1];
+            EXPECT_EQ("https://example.com/", attachment2.GetURL());
+            EXPECT_EQ(u"Test Title", attachment2.GetTitle());
+            EXPECT_EQ(SessionID::FromSerializedValue(123),
+                      attachment2.GetTabSessionId());
             std::move(quit_closure).Run();
           },
           run_loop.QuitClosure()));
diff --git a/components/contextual_tasks/public/features.cc b/components/contextual_tasks/public/features.cc
index 21d94f02..e90c8b8 100644
--- a/components/contextual_tasks/public/features.cc
+++ b/components/contextual_tasks/public/features.cc
@@ -8,6 +8,7 @@
 #include <vector>
 
 #include "base/metrics/field_trial_params.h"
+#include "base/rand_util.h"
 #include "base/strings/string_split.h"
 
 namespace contextual_tasks {
@@ -18,6 +19,10 @@
 // Enables relevant context determination for contextual tasks.
 BASE_FEATURE(kContextualTasksContext, base::FEATURE_DISABLED_BY_DEFAULT);
 
+// Enables quality logging for relevant context determination for contextual
+// tasks.
+BASE_FEATURE(kContextualTasksContextLogging, base::FEATURE_DISABLED_BY_DEFAULT);
+
 // Enables context menu settings for contextual tasks.
 BASE_FEATURE(kContextualTasksContextMenu,
              "ContextualTasksContextMenu",
@@ -28,6 +33,22 @@
              "ContextualTasksSuggestionsEnabled",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
+const base::FeatureParam<double> kMinEmbeddingSimilarityScore{
+    &kContextualTasksContext, "ContextualTasksContextEmbeddingSimilarityScore",
+    0.8};
+
+const base::FeatureParam<bool> kOnlyUseTitlesForSimilarity(
+    &kContextualTasksContext,
+    "ContextualTasksContextOnlyUseTitles",
+    false);
+
+const base::FeatureParam<double> kMinMultiSignalScore{
+    &kContextualTasksContext, "ContextualTasksContextMinMultiSignalScore", 0.8};
+
+const base::FeatureParam<double> kContextualTasksContextLoggingSampleRate{
+    &kContextualTasksContextLogging, "ContextualTasksContextLoggingSampleRate",
+    1.0};
+
 // The base URL for the AI page.
 const base::FeatureParam<std::string> kContextualTasksAiPageUrl{
     &kContextualTasksContext, "ai-page-url",
@@ -38,18 +59,6 @@
     &kContextualTasksContext, "sign-in-domains",
     "accounts.google.com,login.corp.google.com"};
 
-const base::FeatureParam<double> kMinEmbeddingSimilarityScore{
-    &kContextualTasksContext, "ContextualTasksContextEmbeddingSimilarityScore",
-    0.8};
-
-const base::FeatureParam<double> kMinMultiSignalScore{
-    &kContextualTasksContext, "ContextualTasksContextMinMultiSignalScore", 0.8};
-
-const base::FeatureParam<bool> kOnlyUseTitlesForSimilarity(
-    &kContextualTasksContext,
-    "ContextualTasksContextOnlyUseTitles",
-    false);
-
 constexpr base::FeatureParam<EntryPointOption>::Option kEntryPointOptions[] = {
     {EntryPointOption::kNoEntryPoint, "no-entry-point"},
     {EntryPointOption::kPageActionRevisit, "page-action-revisit"},
@@ -119,6 +128,13 @@
   return kContextualTasksUserAgentSuffix.Get();
 }
 
+bool ShouldLogContextualTasksContextQuality() {
+  if (!base::FeatureList::IsEnabled(kContextualTasksContextLogging)) {
+    return false;
+  }
+  return base::RandDouble() <= kContextualTasksContextLoggingSampleRate.Get();
+}
+
 namespace flag_descriptions {
 
 const char kContextualTasksName[] = "Contextual Tasks";
diff --git a/components/contextual_tasks/public/features.h b/components/contextual_tasks/public/features.h
index aac082e..0f5b208 100644
--- a/components/contextual_tasks/public/features.h
+++ b/components/contextual_tasks/public/features.h
@@ -15,6 +15,7 @@
 
 BASE_DECLARE_FEATURE(kContextualTasks);
 BASE_DECLARE_FEATURE(kContextualTasksContext);
+BASE_DECLARE_FEATURE(kContextualTasksContextLogging);
 
 // Enables context menu settings for contextual tasks.
 BASE_DECLARE_FEATURE(kContextualTasksContextMenu);
@@ -34,12 +35,16 @@
 extern const base::FeatureParam<double> kMinEmbeddingSimilarityScore;
 // Whether to only consider titles for similarity.
 extern const base::FeatureParam<bool> kOnlyUseTitlesForSimilarity;
-// Controls whether the contextual task page action should show
-extern const base::FeatureParam<EntryPointOption, true> kShowEntryPoint;
-
 // Minimum score, computed using multiple signals, to consider a tab relevant.
 extern const base::FeatureParam<double> kMinMultiSignalScore;
 
+// The sample rate for logging contextual tasks context quality.
+extern const base::FeatureParam<double>
+    kContextualTasksContextLoggingSampleRate;
+
+// Controls whether the contextual task page action should show
+extern const base::FeatureParam<EntryPointOption, true> kShowEntryPoint;
+
 // If true, the side panel is task scoped. Meaning that for all tabs associated
 // with the same task, they will share the same side panel. If the side panel
 // changed to another task for one tab, all tabs associated with the former task
@@ -81,6 +86,9 @@
 // Returns the user agent suffix to use for requests.
 extern std::string GetContextualTasksUserAgentSuffix();
 
+// Whether the contextual tasks context quality should be logged.
+extern bool ShouldLogContextualTasksContextQuality();
+
 namespace flag_descriptions {
 
 extern const char kContextualTasksName[];
diff --git a/components/enterprise/connectors/core/BUILD.gn b/components/enterprise/connectors/core/BUILD.gn
index 1e14281..98ec4042 100644
--- a/components/enterprise/connectors/core/BUILD.gn
+++ b/components/enterprise/connectors/core/BUILD.gn
@@ -99,6 +99,7 @@
 
 source_set("cloud_content_scanning") {
   sources = [
+    "cloud_content_scanning/browser_thread_guard.h",
     "cloud_content_scanning/common.cc",
     "cloud_content_scanning/common.h",
     "cloud_content_scanning/connector_data_pipe_getter.cc",
@@ -107,9 +108,12 @@
     "cloud_content_scanning/connector_upload_request.h",
     "cloud_content_scanning/multipart_uploader_base.cc",
     "cloud_content_scanning/multipart_uploader_base.h",
+    "cloud_content_scanning/resumable_uploader_base.cc",
+    "cloud_content_scanning/resumable_uploader_base.h",
   ]
   deps = [
     "//base",
+    "//components/enterprise/connectors/core",
     "//components/enterprise/obfuscation/core:enterprise_obfuscation",
     "//components/file_access",
     "//components/safe_browsing/core/common",
diff --git a/components/enterprise/connectors/core/cloud_content_scanning/browser_thread_guard.h b/components/enterprise/connectors/core/cloud_content_scanning/browser_thread_guard.h
new file mode 100644
index 0000000..8ae8b2ad
--- /dev/null
+++ b/components/enterprise/connectors/core/cloud_content_scanning/browser_thread_guard.h
@@ -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.
+
+#ifndef COMPONENTS_ENTERPRISE_CONNECTORS_CORE_CLOUD_CONTENT_SCANNING_BROWSER_THREAD_GUARD_H_
+#define COMPONENTS_ENTERPRISE_CONNECTORS_CORE_CLOUD_CONTENT_SCANNING_BROWSER_THREAD_GUARD_H_
+
+namespace enterprise_connectors {
+
+class BrowserThreadGuard {
+ public:
+  virtual void AssertCalledOnUIThread() = 0;
+  virtual ~BrowserThreadGuard() = default;
+};
+
+}  // namespace enterprise_connectors
+
+#endif  // COMPONENTS_ENTERPRISE_CONNECTORS_CORE_CLOUD_CONTENT_SCANNING_BROWSER_THREAD_GUARD_H_
diff --git a/components/enterprise/connectors/core/cloud_content_scanning/multipart_uploader_base.cc b/components/enterprise/connectors/core/cloud_content_scanning/multipart_uploader_base.cc
index f6887aab..c678993 100644
--- a/components/enterprise/connectors/core/cloud_content_scanning/multipart_uploader_base.cc
+++ b/components/enterprise/connectors/core/cloud_content_scanning/multipart_uploader_base.cc
@@ -17,6 +17,7 @@
 #include "base/task/thread_pool.h"
 #include "base/threading/thread_restrictions.h"
 #include "base/time/time.h"
+#include "components/enterprise/connectors/core/cloud_content_scanning/browser_thread_guard.h"
 #include "components/file_access/scoped_file_access.h"
 #include "components/file_access/scoped_file_access_delegate.h"
 #include "components/safe_browsing/core/common/features.h"
diff --git a/components/enterprise/connectors/core/cloud_content_scanning/multipart_uploader_base.h b/components/enterprise/connectors/core/cloud_content_scanning/multipart_uploader_base.h
index 55ca34e8..48b6e09 100644
--- a/components/enterprise/connectors/core/cloud_content_scanning/multipart_uploader_base.h
+++ b/components/enterprise/connectors/core/cloud_content_scanning/multipart_uploader_base.h
@@ -24,11 +24,7 @@
 
 namespace enterprise_connectors {
 
-class BrowserThreadGuard {
- public:
-  virtual void AssertCalledOnUIThread() = 0;
-  virtual ~BrowserThreadGuard() = default;
-};
+class BrowserThreadGuard;
 
 // This class encapsulates the upload of a file with metadata using the
 // multipart protocol. This class is neither movable nor copyable.
@@ -111,6 +107,14 @@
   void set_boundary(const std::string& boundary) { boundary_ = boundary; }
 
  private:
+  FRIEND_TEST_ALL_PREFIXES(MultipartUploadRequestBaseTest,
+                           GeneratesCorrectBody);
+  FRIEND_TEST_ALL_PREFIXES(MultipartUploadRequestBaseTest, RetriesCorrectly);
+  FRIEND_TEST_ALL_PREFIXES(MultipartUploadDataPipeRequestTest, Retries);
+  FRIEND_TEST_ALL_PREFIXES(MultipartUploadDataPipeRequestTest, DataControls);
+  FRIEND_TEST_ALL_PREFIXES(MultipartUploadDataPipeRequestTest,
+                           EquivalentToStringRequest);
+
   virtual scoped_refptr<base::TaskRunner> GetTaskRunner() = 0;
 
   // Called by SendFileRequest and SendPageRequest after `data_pipe_getter_`
diff --git a/components/enterprise/connectors/core/cloud_content_scanning/multipart_uploader_base_unittest.cc b/components/enterprise/connectors/core/cloud_content_scanning/multipart_uploader_base_unittest.cc
new file mode 100644
index 0000000..981cd0a7
--- /dev/null
+++ b/components/enterprise/connectors/core/cloud_content_scanning/multipart_uploader_base_unittest.cc
@@ -0,0 +1,450 @@
+// 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/enterprise/connectors/core/cloud_content_scanning/multipart_uploader_base.h"
+
+#include <memory>
+
+#include "base/compiler_specific.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/functional/callback_helpers.h"
+#include "base/task/single_thread_task_runner.h"
+#include "base/test/bind.h"
+#include "base/test/gmock_callback_support.h"
+#include "base/test/task_environment.h"
+#include "components/enterprise/connectors/core/cloud_content_scanning/browser_thread_guard.h"
+#include "components/enterprise/connectors/core/uploader_test_utils.h"
+#include "components/file_access/test/mock_scoped_file_access_delegate.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 "services/network/test/test_utils.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace enterprise_connectors {
+
+using ::testing::_;
+using ::testing::Return;
+
+namespace {
+
+class TestBrowserThreadGuard : public BrowserThreadGuard {
+ public:
+  void AssertCalledOnUIThread() override {}
+};
+
+class MockMultipartUploadRequestBase : public MultipartUploadRequestBase {
+ public:
+  using MultipartUploadRequestBase::MultipartUploadRequestBase;
+
+  MockMultipartUploadRequestBase()
+      : MultipartUploadRequestBase(nullptr,
+                                   GURL(),
+                                   std::string(),
+                                   std::string(),
+                                   std::string(),
+                                   TRAFFIC_ANNOTATION_FOR_TESTS,
+                                   base::DoNothing(),
+                                   std::make_unique<TestBrowserThreadGuard>()) {
+    ON_CALL(*this, GetTaskRunner())
+        .WillByDefault(
+            Return(base::SingleThreadTaskRunner::GetCurrentDefault()));
+  }
+
+  MOCK_METHOD0(SendRequest, void());
+  MOCK_METHOD0(GetTaskRunner, scoped_refptr<base::TaskRunner>());
+};
+
+class MockMultipartUploadDataPipeRequest : public MultipartUploadRequestBase {
+ public:
+  MockMultipartUploadDataPipeRequest(
+      const base::FilePath& path,
+      MultipartUploadRequestBase::Callback callback)
+      : MultipartUploadRequestBase(nullptr,
+                                   GURL(),
+                                   "metadata",
+                                   path,
+                                   123,
+                                   false,
+                                   "histogram_suffix",
+                                   TRAFFIC_ANNOTATION_FOR_TESTS,
+                                   std::move(callback),
+                                   std::make_unique<TestBrowserThreadGuard>()) {
+    ON_CALL(*this, GetTaskRunner())
+        .WillByDefault(
+            Return(base::SingleThreadTaskRunner::GetCurrentDefault()));
+  }
+
+  MockMultipartUploadDataPipeRequest(
+      base::ReadOnlySharedMemoryRegion page_region,
+      MultipartUploadRequestBase::Callback callback)
+      : MultipartUploadRequestBase(nullptr,
+                                   GURL(),
+                                   "metadata",
+                                   std::move(page_region),
+                                   "histogram_suffix",
+                                   TRAFFIC_ANNOTATION_FOR_TESTS,
+                                   std::move(callback),
+                                   std::make_unique<TestBrowserThreadGuard>()) {
+    ON_CALL(*this, GetTaskRunner())
+        .WillByDefault(
+            Return(base::SingleThreadTaskRunner::GetCurrentDefault()));
+  }
+
+  MOCK_METHOD1(CompleteSendRequest,
+               void(std::unique_ptr<network::ResourceRequest> request));
+  MOCK_METHOD0(GetTaskRunner, scoped_refptr<base::TaskRunner>());
+};
+
+}  // namespace
+
+class MultipartUploadRequestBaseTest : public testing::Test {
+ public:
+  MultipartUploadRequestBaseTest() = default;
+
+  void SetUp() override {
+    testing::Test::SetUp();
+
+    test_shared_loader_factory_ =
+        base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
+            &test_url_loader_factory_);
+  }
+
+  base::FilePath CreateFile(const std::string& file_name,
+                            const std::string& content) {
+    if (!temp_dir_.IsValid()) {
+      EXPECT_TRUE(temp_dir_.CreateUniqueTempDir());
+    }
+
+    base::FilePath path = temp_dir_.GetPath().AppendASCII(file_name);
+    base::File file(path, base::File::FLAG_CREATE_ALWAYS |
+                              base::File::FLAG_READ | base::File::FLAG_WRITE);
+    file.WriteAtCurrentPos(base::as_byte_span(content));
+    return path;
+  }
+
+  base::ReadOnlySharedMemoryRegion CreatePage(const std::string& content) {
+    base::MappedReadOnlyRegion region =
+        base::ReadOnlySharedMemoryRegion::Create(content.size());
+    EXPECT_TRUE(region.IsValid());
+    region.mapping.GetMemoryAsSpan<char>().copy_from(content);
+    return std::move(region.region);
+  }
+
+ protected:
+  base::test::TaskEnvironment task_environment_{
+      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
+  network::TestURLLoaderFactory test_url_loader_factory_;
+  scoped_refptr<network::SharedURLLoaderFactory> test_shared_loader_factory_;
+  base::ScopedTempDir temp_dir_;
+};
+
+TEST_F(MultipartUploadRequestBaseTest, GeneratesCorrectBody) {
+  auto request = std::make_unique<MockMultipartUploadRequestBase>(
+      nullptr, GURL(), "metadata", "data", "histogram_suffix",
+      TRAFFIC_ANNOTATION_FOR_TESTS, base::DoNothing(),
+      std::make_unique<TestBrowserThreadGuard>());
+
+  std::string expected_body =
+      "--boundary\r\n"
+      "Content-Type: application/octet-stream\r\n"
+      "\r\n"
+      "metadata\r\n"
+      "--boundary\r\n"
+      "Content-Type: application/octet-stream\r\n"
+      "\r\n"
+      "file data\r\n"
+      "--boundary--\r\n";
+
+  request->set_boundary("boundary");
+  EXPECT_EQ(request->GenerateRequestBody("metadata", "file data"),
+            expected_body);
+}
+
+TEST_F(MultipartUploadRequestBaseTest, RetriesCorrectly) {
+  {
+    MockMultipartUploadRequestBase mock_request;
+
+    EXPECT_CALL(mock_request, SendRequest())
+        .Times(1)
+        .WillRepeatedly([&mock_request]() {
+          mock_request.RetryOrFinish(net::OK, net::HTTP_BAD_REQUEST,
+                                     "response");
+        });
+    mock_request.Start();
+    task_environment_.FastForwardUntilNoTasksRemain();
+  }
+  {
+    MockMultipartUploadRequestBase mock_request;
+
+    EXPECT_CALL(mock_request, SendRequest())
+        .Times(3)
+        .WillRepeatedly([&mock_request]() {
+          mock_request.RetryOrFinish(net::OK, net::HTTP_SERVICE_UNAVAILABLE,
+                                     "response");
+        });
+    mock_request.Start();
+    task_environment_.FastForwardUntilNoTasksRemain();
+  }
+}
+
+class MultipartUploadDataPipeRequestTest
+    : public MultipartUploadRequestBaseTest,
+      public testing::WithParamInterface<bool> {
+ public:
+  bool is_file_request() { return GetParam(); }
+
+  std::unique_ptr<MockMultipartUploadDataPipeRequest> CreateRequest(
+      const std::string& content,
+      base::OnceCallback<void(bool success,
+                              int http_status,
+                              const std::string& response_data)> callback) {
+    if (is_file_request()) {
+      return std::make_unique<MockMultipartUploadDataPipeRequest>(
+          CreateFile("text.txt", content), std::move(callback));
+    } else {
+      return std::make_unique<MockMultipartUploadDataPipeRequest>(
+          CreatePage(content), std::move(callback));
+    }
+  }
+};
+
+INSTANTIATE_TEST_SUITE_P(, MultipartUploadDataPipeRequestTest, testing::Bool());
+
+// Disabled due to flakiness on Windows https://crbug.com/1286638
+#if BUILDFLAG(IS_WIN)
+#define MAYBE_Retries DISABLED_Retries
+#else
+#define MAYBE_Retries Retries
+#endif
+TEST_P(MultipartUploadDataPipeRequestTest, MAYBE_Retries) {
+  std::string expected_body =
+      "--boundary\r\n"
+      "Content-Type: application/octet-stream\r\n"
+      "\r\n"
+      "metadata\r\n"
+      "--boundary\r\n"
+      "Content-Type: application/octet-stream\r\n"
+      "\r\n"
+      "file content\r\n"
+      "--boundary--\r\n";
+  {
+    base::RunLoop run_loop;
+    std::unique_ptr<MockMultipartUploadDataPipeRequest> mock_request =
+        CreateRequest("file content",
+                      base::BindLambdaForTesting(
+                          [&run_loop](bool success, int http_status,
+                                      const std::string& response_data) {
+                            EXPECT_TRUE(success);
+                            EXPECT_EQ(net::HTTP_OK, http_status);
+                            EXPECT_EQ("response", response_data);
+                            run_loop.Quit();
+                          }));
+    mock_request->set_boundary("boundary");
+
+    EXPECT_CALL(*mock_request, CompleteSendRequest(_))
+        .WillOnce([&mock_request, &expected_body](
+                      std::unique_ptr<network::ResourceRequest> request) {
+          EXPECT_EQ(expected_body,
+                    enterprise_connectors::GetBodyFromFileOrPageRequest(
+                        mock_request->data_pipe_getter_for_testing()));
+          mock_request->RetryOrFinish(net::OK, net::HTTP_OK, "response");
+          mock_request->MarkScanAsCompleteForTesting();
+        });
+    mock_request->Start();
+    task_environment_.FastForwardUntilNoTasksRemain();
+    run_loop.Run();
+    EXPECT_EQ(mock_request->GetUploadInfo(), "Multipart - Complete");
+  }
+  {
+    int retry_count = 0;
+    base::RunLoop run_loop;
+    std::unique_ptr<MockMultipartUploadDataPipeRequest> mock_request =
+        CreateRequest(
+            "file content",
+            base::BindLambdaForTesting(
+                [&run_loop, &retry_count](bool success, int http_status,
+                                          const std::string& response_data) {
+                  EXPECT_TRUE(success);
+                  EXPECT_EQ(net::HTTP_OK, http_status);
+                  EXPECT_EQ("response", response_data);
+                  EXPECT_EQ(3, retry_count);
+                  run_loop.Quit();
+                }));
+    mock_request->set_boundary("boundary");
+
+    EXPECT_CALL(*mock_request, CompleteSendRequest(_))
+        .Times(3)
+        .WillRepeatedly([&mock_request, &expected_body, &retry_count](
+                            std::unique_ptr<network::ResourceRequest> request) {
+          // Every call to CompleteSendRequest should be able to get the
+          // same body from the request's data pipe getter.
+          EXPECT_EQ(expected_body,
+                    enterprise_connectors::GetBodyFromFileOrPageRequest(
+                        mock_request->data_pipe_getter_for_testing()));
+
+          ++retry_count;
+          mock_request->RetryOrFinish(
+              net::OK,
+              retry_count < 3 ? net::HTTP_SERVICE_UNAVAILABLE : net::HTTP_OK,
+              "response");
+          if (retry_count == 3) {
+            mock_request->MarkScanAsCompleteForTesting();
+          }
+        });
+    mock_request->Start();
+    task_environment_.FastForwardUntilNoTasksRemain();
+    run_loop.Run();
+    EXPECT_EQ(mock_request->GetUploadInfo(), "Multipart - Complete");
+  }
+}
+
+TEST_P(MultipartUploadDataPipeRequestTest, DataControls) {
+  std::string expected_body =
+      "--boundary\r\n"
+      "Content-Type: application/octet-stream\r\n"
+      "\r\n"
+      "metadata\r\n"
+      "--boundary\r\n"
+      "Content-Type: application/octet-stream\r\n"
+      "\r\n"
+      "file content\r\n"
+      "--boundary--\r\n";
+  file_access::MockScopedFileAccessDelegate scoped_files_access_delegate;
+
+  if (is_file_request()) {
+    EXPECT_CALL(scoped_files_access_delegate, RequestFilesAccessForSystem)
+        .WillOnce(base::test::RunOnceCallback<1>(
+            file_access::ScopedFileAccess::Allowed()));
+  } else {
+    EXPECT_CALL(scoped_files_access_delegate, RequestFilesAccessForSystem)
+        .Times(0);
+  }
+
+  base::RunLoop run_loop;
+  std::unique_ptr<MockMultipartUploadDataPipeRequest> mock_request =
+      CreateRequest("file content",
+                    base::BindLambdaForTesting(
+                        [&run_loop](bool success, int http_status,
+                                    const std::string& response_data) {
+                          EXPECT_TRUE(success);
+                          EXPECT_EQ(net::HTTP_OK, http_status);
+                          EXPECT_EQ("response", response_data);
+                          run_loop.Quit();
+                        }));
+  mock_request->set_boundary("boundary");
+
+  EXPECT_CALL(*mock_request, CompleteSendRequest(_))
+      .WillOnce([&mock_request, &expected_body](
+                    std::unique_ptr<network::ResourceRequest> request) {
+        EXPECT_EQ(expected_body,
+                  enterprise_connectors::GetBodyFromFileOrPageRequest(
+                      mock_request->data_pipe_getter_for_testing()));
+        mock_request->RetryOrFinish(net::OK, net::HTTP_OK, "response");
+        mock_request->MarkScanAsCompleteForTesting();
+      });
+  mock_request->Start();
+  task_environment_.FastForwardUntilNoTasksRemain();
+  run_loop.Run();
+  EXPECT_EQ(mock_request->GetUploadInfo(), "Multipart - Complete");
+}
+
+TEST_P(MultipartUploadDataPipeRequestTest, EquivalentToStringRequest) {
+  // The request body should be identical when obtained through a string request
+  // and a data pipe request with equivalent content.
+  std::unique_ptr<MockMultipartUploadDataPipeRequest> data_pipe_request =
+      CreateRequest("data", base::DoNothing());
+  data_pipe_request->set_boundary("boundary");
+
+  // Start the data pipe request to initialize the internal data pipe getter.
+  ASSERT_FALSE(data_pipe_request->data_pipe_getter_for_testing());
+  EXPECT_CALL(*data_pipe_request, CompleteSendRequest(_)).Times(1);
+  data_pipe_request->Start();
+  task_environment_.FastForwardUntilNoTasksRemain();
+  ASSERT_TRUE(data_pipe_request->data_pipe_getter_for_testing());
+
+  MockMultipartUploadRequestBase string_request;
+  string_request.set_boundary("boundary");
+
+  std::string expected_body =
+      "--boundary\r\n"
+      "Content-Type: application/octet-stream\r\n"
+      "\r\n"
+      "metadata\r\n"
+      "--boundary\r\n"
+      "Content-Type: application/octet-stream\r\n"
+      "\r\n"
+      "data\r\n"
+      "--boundary--\r\n";
+
+  EXPECT_EQ(expected_body,
+            string_request.GenerateRequestBody("metadata", "data"));
+  EXPECT_EQ(expected_body,
+            enterprise_connectors::GetBodyFromFileOrPageRequest(
+                data_pipe_request->data_pipe_getter_for_testing()));
+}
+
+TEST_F(MultipartUploadRequestBaseTest, GeneratesCorrectHeaders_StringRequest) {
+  network::ResourceRequest resource_request;
+
+  auto request = std::make_unique<MockMultipartUploadRequestBase>(
+      nullptr, GURL(), "metadata", "data", "histogram_suffix",
+      TRAFFIC_ANNOTATION_FOR_TESTS, base::DoNothing(),
+      std::make_unique<TestBrowserThreadGuard>());
+
+  request->SetRequestHeaders(&resource_request);
+  ASSERT_TRUE(resource_request.headers.HasHeader("X-Goog-Upload-Protocol"));
+  ASSERT_THAT(resource_request.headers.GetHeader("X-Goog-Upload-Protocol"),
+              testing::Optional(std::string("multipart")));
+  ASSERT_TRUE(resource_request.headers.HasHeader(
+      "X-Goog-Upload-Header-Content-Length"));
+  ASSERT_THAT(
+      resource_request.headers.GetHeader("X-Goog-Upload-Header-Content-Length"),
+      testing::Optional(std::string("4")));
+  EXPECT_EQ(request->GetUploadInfo(), "Multipart - Pending");
+}
+
+TEST_F(MultipartUploadRequestBaseTest, GeneratesCorrectHeaders_FileRequest) {
+  network::ResourceRequest resource_request;
+
+  auto request = std::make_unique<MockMultipartUploadRequestBase>(
+      nullptr, GURL(), "metadata", CreateFile("my_file_name.foo", "file_data"),
+      9, false, "histogram_suffix", TRAFFIC_ANNOTATION_FOR_TESTS,
+      base::DoNothing(), std::make_unique<TestBrowserThreadGuard>());
+
+  request->SetRequestHeaders(&resource_request);
+  ASSERT_TRUE(resource_request.headers.HasHeader("X-Goog-Upload-Protocol"));
+  ASSERT_THAT(resource_request.headers.GetHeader("X-Goog-Upload-Protocol"),
+              testing::Optional(std::string("multipart")));
+  ASSERT_TRUE(resource_request.headers.HasHeader(
+      "X-Goog-Upload-Header-Content-Length"));
+  ASSERT_THAT(
+      resource_request.headers.GetHeader("X-Goog-Upload-Header-Content-Length"),
+      testing::Optional(std::string("9")));
+  EXPECT_EQ(request->GetUploadInfo(), "Multipart - Pending");
+}
+
+TEST_F(MultipartUploadRequestBaseTest, GeneratesCorrectHeaders_PageRequest) {
+  network::ResourceRequest resource_request;
+
+  auto request = std::make_unique<MockMultipartUploadRequestBase>(
+      nullptr, GURL(), "metadata", CreatePage("print_data"), "histogram_suffix",
+      TRAFFIC_ANNOTATION_FOR_TESTS, base::DoNothing(),
+      std::make_unique<TestBrowserThreadGuard>());
+
+  request->SetRequestHeaders(&resource_request);
+  ASSERT_TRUE(resource_request.headers.HasHeader("X-Goog-Upload-Protocol"));
+  ASSERT_THAT(resource_request.headers.GetHeader("X-Goog-Upload-Protocol"),
+              testing::Optional(std::string("multipart")));
+  ASSERT_TRUE(resource_request.headers.HasHeader(
+      "X-Goog-Upload-Header-Content-Length"));
+  ASSERT_THAT(
+      resource_request.headers.GetHeader("X-Goog-Upload-Header-Content-Length"),
+      testing::Optional(std::string("10")));
+  EXPECT_EQ(request->GetUploadInfo(), "Multipart - Pending");
+}
+
+}  // namespace enterprise_connectors
diff --git a/components/enterprise/connectors/core/cloud_content_scanning/resumable_uploader_base.cc b/components/enterprise/connectors/core/cloud_content_scanning/resumable_uploader_base.cc
new file mode 100644
index 0000000..956be2b
--- /dev/null
+++ b/components/enterprise/connectors/core/cloud_content_scanning/resumable_uploader_base.cc
@@ -0,0 +1,333 @@
+// 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/enterprise/connectors/core/cloud_content_scanning/resumable_uploader_base.h"
+
+#include "base/strings/strcat.h"
+#include "base/strings/string_number_conversions.h"
+#include "components/enterprise/connectors/core/features.h"
+#include "components/file_access/scoped_file_access_delegate.h"
+#include "components/safe_browsing/core/common/utils.h"
+#include "net/http/http_status_code.h"
+#include "services/network/public/cpp/resource_request.h"
+
+namespace enterprise_connectors {
+
+namespace {
+
+using ::safe_browsing::RecordHttpResponseOrErrorCode;
+using ::safe_browsing::SafeBrowsingAuthenticatedEndpoint;
+using ::safe_browsing::SetAccessToken;
+
+// HTTP headers for resumable upload requests
+constexpr char kUploadProtocolHeader[] = "X-Goog-Upload-Protocol";
+constexpr char kUploadCommandHeader[] = "X-Goog-Upload-Command";
+constexpr char kUploadHeaderContentLengthHeader[] =
+    "X-Goog-Upload-Header-Content-Length";
+constexpr char kUploadHeaderContentTypeHeader[] =
+    "X-Goog-Upload-Header-Content-Type";
+constexpr char kUploadStatusHeader[] = "X-Goog-Upload-Status";
+constexpr char kUploadUrlHeader[] = "X-Goog-Upload-Url";
+constexpr char kUploadOffsetHeader[] = "X-Goog-Upload-Offset";
+
+// Content type of the upload contents.
+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";
+
+bool IsSuccess(int net_error, int response_code) {
+  return net_error == net::OK && response_code == net::HTTP_OK;
+}
+
+std::unique_ptr<ConnectorDataPipeGetter> CreateFileDataPipeGetterBlocking(
+    const base::FilePath& path,
+    bool is_obfuscated) {
+  // FLAG_WIN_SHARE_DELETE is necessary to allow the file to be renamed by the
+  // user clicking "Open Now" without causing download errors.
+  base::File file(path, base::File::FLAG_OPEN | base::File::FLAG_READ |
+                            base::File::FLAG_WIN_SHARE_DELETE);
+
+  return ConnectorDataPipeGetter::CreateResumablePipeGetter(std::move(file),
+                                                            is_obfuscated);
+}
+
+}  // namespace
+
+ResumableUploadRequestBase::ResumableUploadRequestBase(
+    scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
+    const GURL& base_url,
+    const std::string& metadata,
+    enterprise_connectors::ScanRequestUploadResult get_data_result,
+    const base::FilePath& path,
+    uint64_t file_size,
+    bool is_obfuscated,
+    const std::string& histogram_suffix,
+    const net::NetworkTrafficAnnotationTag& traffic_annotation,
+    VerdictReceivedCallback verdict_received_callback,
+    ContentUploadedCallback content_uploaded_callback,
+    bool force_sync_upload)
+    : ConnectorUploadRequest(std::move(url_loader_factory),
+                             base_url,
+                             metadata,
+                             path,
+                             file_size,
+                             is_obfuscated,
+                             histogram_suffix,
+                             traffic_annotation,
+                             base::DoNothing()),
+      verdict_received_callback_(std::move(verdict_received_callback)),
+      content_uploaded_callback_(std::move(content_uploaded_callback)),
+      get_data_result_(get_data_result),
+      is_obfuscated_(is_obfuscated),
+      force_sync_upload_(force_sync_upload) {}
+
+ResumableUploadRequestBase::ResumableUploadRequestBase(
+    scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
+    const GURL& base_url,
+    const std::string& metadata,
+    enterprise_connectors::ScanRequestUploadResult get_data_result,
+    base::ReadOnlySharedMemoryRegion page_region,
+    const std::string& histogram_suffix,
+    const net::NetworkTrafficAnnotationTag& traffic_annotation,
+    VerdictReceivedCallback verdict_received_callback,
+    ContentUploadedCallback content_uploaded_callback,
+    bool force_sync_upload)
+    : ConnectorUploadRequest(std::move(url_loader_factory),
+                             base_url,
+                             metadata,
+                             std::move(page_region),
+                             histogram_suffix,
+                             traffic_annotation,
+                             base::DoNothing()),
+      verdict_received_callback_(std::move(verdict_received_callback)),
+      content_uploaded_callback_(std::move(content_uploaded_callback)),
+      get_data_result_(get_data_result),
+      force_sync_upload_(force_sync_upload) {}
+
+ResumableUploadRequestBase::ResumableUploadRequestBase(
+    scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
+    const GURL& base_url,
+    const std::string& metadata,
+    const std::string& data,
+    const std::string& histogram_suffix,
+    const net::NetworkTrafficAnnotationTag& traffic_annotation,
+    VerdictReceivedCallback verdict_received_callback,
+    ContentUploadedCallback content_uploaded_callback,
+    bool force_sync_upload)
+    : ConnectorUploadRequest(std::move(url_loader_factory),
+                             base_url,
+                             metadata,
+                             data,
+                             histogram_suffix,
+                             traffic_annotation,
+                             base::DoNothing()),
+      verdict_received_callback_(std::move(verdict_received_callback)),
+      content_uploaded_callback_(std::move(content_uploaded_callback)),
+      get_data_result_(enterprise_connectors::ScanRequestUploadResult::SUCCESS),
+      force_sync_upload_(force_sync_upload) {}
+
+ResumableUploadRequestBase::~ResumableUploadRequestBase() = default;
+
+void ResumableUploadRequestBase::SetMetadataRequestHeaders(
+    network::ResourceRequest* request) {
+  CHECK(request);
+
+  // Page, string and file requests should have non-zero `data_size_`.
+  DCHECK_GT(data_size_, (uint64_t)0);
+
+  request->headers.SetHeader(kUploadProtocolHeader, "resumable");
+  request->headers.SetHeader(kUploadCommandHeader, "start");
+  request->headers.SetHeader(kUploadHeaderContentLengthHeader,
+                             base::NumberToString(data_size_));
+  // `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);
+    SetAccessToken(request, access_token_);
+  }
+  request->credentials_mode = network::mojom::CredentialsMode::kOmit;
+}
+
+std::string ResumableUploadRequestBase::GetUploadInfo() {
+  std::string scan_info;
+  switch (scan_type_) {
+    case PENDING:
+      scan_info = "Pending";
+      break;
+    case FULL_CONTENT:
+      scan_info = "Full content scan";
+      break;
+    case METADATA_ONLY:
+      scan_info = "Metadata only scan";
+      break;
+    case ASYNC:
+      scan_info = "Async content upload";
+      break;
+  }
+
+  return base::StrCat({"Resumable - ", scan_info});
+}
+
+std::string ResumableUploadRequestBase::GetRequestType() {
+  switch (data_source_) {
+    case FILE:
+      return "File";
+    case STRING:
+      return "Text";
+    case PAGE:
+      return "Print";
+  }
+}
+
+void ResumableUploadRequestBase::SendMetadataRequest() {
+  auto resource_request = std::make_unique<network::ResourceRequest>();
+  resource_request->url = base_url_;
+  resource_request->method = "POST";
+  SetMetadataRequestHeaders(resource_request.get());
+
+  url_loader_ = network::SimpleURLLoader::Create(std::move(resource_request),
+                                                 traffic_annotation_);
+  url_loader_->SetAllowHttpErrorResults(true);
+  url_loader_->AttachStringForUpload(base::StrCat({metadata_, "\r\n"}),
+                                     kMetadataContentType);
+  url_loader_->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
+      url_loader_factory_.get(),
+      base::BindOnce(&ResumableUploadRequestBase::OnMetadataUploadCompleted,
+                     weak_factory_.GetWeakPtr(), base::TimeTicks::Now()));
+}
+
+void ResumableUploadRequestBase::Finish(
+    int net_error,
+    int response_code,
+    std::optional<std::string> response_body) {
+  if (!histogram_suffix_.empty()) {
+    std::string histogram = base::StrCat(
+        {"SafeBrowsing.ResumableUploader.NetworkResult.", histogram_suffix_});
+    RecordHttpResponseOrErrorCode(histogram.c_str(), net_error, response_code);
+  }
+
+  // The callback may have been invoked when the metadata verdict was received
+  // with the CEP header, to unblock the user initiate an async upload.
+  if (!verdict_received_callback_.is_null()) {
+    std::move(verdict_received_callback_)
+        .Run(/*success=*/IsSuccess(net_error, response_code), response_code,
+             response_body.value_or(""));
+  }
+  std::move(content_uploaded_callback_).Run();
+}
+
+void ResumableUploadRequestBase::SendContentSoon(
+    const std::string& upload_url) {
+  auto request = std::make_unique<network::ResourceRequest>();
+  request->method = "POST";
+  request->url = GURL(upload_url);
+  // Only sends content smaller than 50MB, in a single request.
+  request->headers.SetHeader(kUploadCommandHeader, "upload, finalize");
+  request->headers.SetHeader(kUploadOffsetHeader, "0");
+
+  // TODO(crbug.com/322005992): Add retry logics.
+  switch (data_source_) {
+    case FILE:
+      file_access::RequestFilesAccessForSystem(
+          {path_},
+          base::BindOnce(&ResumableUploadRequestBase::CreateDatapipe,
+                         weak_factory_.GetWeakPtr(), std::move(request)));
+      break;
+    case PAGE:
+      OnDataPipeCreated(std::move(request),
+                        ConnectorDataPipeGetter::CreateResumablePipeGetter(
+                            std::move(page_region_)));
+      break;
+    // Resumable uploads are used for pasted images, which are handled as string
+    // data. Using resumable uploads for pasted images is enabled by the
+    // `enterprise_connectors::kDlpScanPastedImages` feature flag. Text pastes
+    // use multipart uploads.
+    case STRING:
+      SendContentNow(std::move(request));
+      break;
+    default:
+      NOTREACHED();
+  }
+}
+
+// TODO(crbug.com/328415950): Move the data pipe creation logics to
+// connector_upload_request.
+void ResumableUploadRequestBase::CreateDatapipe(
+    std::unique_ptr<network::ResourceRequest> request,
+    file_access::ScopedFileAccess file_access) {
+  scoped_file_access_ =
+      std::make_unique<file_access::ScopedFileAccess>(std::move(file_access));
+  base::ThreadPool::PostTaskAndReplyWithResult(
+      FROM_HERE, {base::TaskPriority::USER_VISIBLE, base::MayBlock()},
+      base::BindOnce(&CreateFileDataPipeGetterBlocking, path_, is_obfuscated_),
+      base::BindOnce(&ResumableUploadRequestBase::OnDataPipeCreated,
+                     weak_factory_.GetWeakPtr(), std::move(request)));
+}
+
+void ResumableUploadRequestBase::OnDataPipeCreated(
+    std::unique_ptr<network::ResourceRequest> request,
+    std::unique_ptr<ConnectorDataPipeGetter> data_pipe_getter) {
+  scoped_file_access_.reset();
+  if (!data_pipe_getter) {
+    // TODO(329293309): Replace with meaningful net_error value since 0 does not
+    // indicate an error.
+    Finish(0, 0, std::nullopt);
+    return;
+  }
+
+  data_pipe_getter_ = std::move(data_pipe_getter);
+  SendContentNow(std::move(request));
+}
+
+void ResumableUploadRequestBase::SendContentNow(
+    std::unique_ptr<network::ResourceRequest> request) {
+  // `data_pipe_getter_` is null for STRING requests, which are handled by
+  // attaching the string data directly to the URL loader. For FILE and PAGE
+  // requests, `data_pipe_getter_` will be non-null.
+  if (data_pipe_getter_) {
+    mojo::PendingRemote<network::mojom::DataPipeGetter> data_pipe_getter;
+    data_pipe_getter_->Clone(data_pipe_getter.InitWithNewPipeAndPassReceiver());
+    request->request_body = new network::ResourceRequestBody();
+    request->request_body->AppendDataPipe(std::move(data_pipe_getter));
+  }
+
+  url_loader_ =
+      network::SimpleURLLoader::Create(std::move(request), traffic_annotation_);
+  url_loader_->SetAllowHttpErrorResults(true);
+
+  if (!data_pipe_getter_) {
+    url_loader_->AttachStringForUpload(data_, kImageContentType);
+  }
+
+  url_loader_->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
+      url_loader_factory_.get(),
+      base::BindOnce(&ResumableUploadRequestBase::OnSendContentCompleted,
+                     weak_factory_.GetWeakPtr(), base::TimeTicks::Now()));
+}
+
+bool ResumableUploadRequestBase::CanUploadContent(
+    const scoped_refptr<net::HttpResponseHeaders>& headers) {
+  if (headers->response_code() != net::HTTP_OK) {
+    return false;
+  }
+  std::optional<std::string> upload_status =
+      headers->GetNormalizedHeader(kUploadStatusHeader);
+  if (!upload_status || !headers->HasHeader(kUploadUrlHeader)) {
+    return false;
+  }
+  return base::EqualsCaseInsensitiveASCII(upload_status.value_or(std::string()),
+                                          "active");
+}
+
+bool ResumableUploadRequestBase::ShouldUploadEncryptedFile() {
+  return base::FeatureList::IsEnabled(kEnableEncryptedFileUpload) &&
+         scan_type_ == ASYNC;
+}
+
+}  // namespace enterprise_connectors
diff --git a/components/enterprise/connectors/core/cloud_content_scanning/resumable_uploader_base.h b/components/enterprise/connectors/core/cloud_content_scanning/resumable_uploader_base.h
new file mode 100644
index 0000000..89bb992
--- /dev/null
+++ b/components/enterprise/connectors/core/cloud_content_scanning/resumable_uploader_base.h
@@ -0,0 +1,158 @@
+// 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_ENTERPRISE_CONNECTORS_CORE_CLOUD_CONTENT_SCANNING_RESUMABLE_UPLOADER_BASE_H_
+#define COMPONENTS_ENTERPRISE_CONNECTORS_CORE_CLOUD_CONTENT_SCANNING_RESUMABLE_UPLOADER_BASE_H_
+
+#include "components/enterprise/connectors/core/cloud_content_scanning/connector_upload_request.h"
+
+namespace enterprise_connectors {
+
+class ResumableUploadRequestBase : public ConnectorUploadRequest {
+ public:
+  using ContentUploadedCallback = base::OnceClosure;
+  using VerdictReceivedCallback =
+      enterprise_connectors::ConnectorUploadRequest::Callback;
+  using ConnectorUploadRequest::ConnectorUploadRequest;
+
+  // Creates a ResumableUploadRequestBase, which will upload the `metadata` of
+  // the file corresponding to the provided `path` to the given `base_url`, and
+  // then the file content to the `path` if necessary.
+  //
+  // `get_data_result` is the result when getting basic information about the
+  // file or page.  It lets the ResumableUploadRequestBase know if the data is
+  // considered too large or is encrypted.
+  ResumableUploadRequestBase(
+      scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
+      const GURL& base_url,
+      const std::string& metadata,
+      enterprise_connectors::ScanRequestUploadResult get_data_result,
+      const base::FilePath& path,
+      uint64_t file_size,
+      bool is_obfuscated,
+      const std::string& histogram_suffix,
+      const net::NetworkTrafficAnnotationTag& traffic_annotation,
+      VerdictReceivedCallback verdict_received_callback,
+      ContentUploadedCallback content_uploaded_callback,
+      bool force_sync_upload);
+
+  // Creates a ResumableUploadRequestBase, which will upload the `metadata` of
+  // the page to the given `base_url`, and then the content of `page_region` if
+  // necessary.
+  ResumableUploadRequestBase(
+      scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
+      const GURL& base_url,
+      const std::string& metadata,
+      enterprise_connectors::ScanRequestUploadResult get_data_result,
+      base::ReadOnlySharedMemoryRegion page_region,
+      const std::string& histogram_suffix,
+      const net::NetworkTrafficAnnotationTag& traffic_annotation,
+      VerdictReceivedCallback verdict_received_callback,
+      ContentUploadedCallback content_uploaded_callback,
+      bool force_sync_upload);
+
+  // Creates a ResumableUploadRequestBase, which will upload the `metadata` of a
+  // pasted image to the given `base_url`, and then the `data` if necessary.
+  ResumableUploadRequestBase(
+      scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
+      const GURL& base_url,
+      const std::string& metadata,
+      const std::string& data,
+      const std::string& histogram_suffix,
+      const net::NetworkTrafficAnnotationTag& traffic_annotation,
+      VerdictReceivedCallback verdict_received_callback,
+      ContentUploadedCallback content_uploaded_callback,
+      bool force_sync_upload);
+
+  ResumableUploadRequestBase(const ResumableUploadRequestBase&) = delete;
+  ResumableUploadRequestBase& operator=(const ResumableUploadRequestBase&) =
+      delete;
+  ResumableUploadRequestBase(ResumableUploadRequestBase&&) = delete;
+  ResumableUploadRequestBase& operator=(ResumableUploadRequestBase&&) = delete;
+
+  ~ResumableUploadRequestBase() override;
+
+  // Called whenever a content request finishes (on success or failure).
+  virtual void OnSendContentCompleted(
+      base::TimeTicks start_time,
+      std::optional<std::string> response_body) = 0;
+
+  // Set the headers for the given metadata `request`.
+  void SetMetadataRequestHeaders(network::ResourceRequest* request);
+
+  std::string GetUploadInfo() override;
+
+ protected:
+  // Called after a metadata request finishes successfully. Virtual for testing.
+  virtual void SendContentSoon(const std::string& upload_url);
+
+  // Called whenever a metadata request finishes (on success or failure).
+  virtual void OnMetadataUploadCompleted(
+      base::TimeTicks start_time,
+      std::optional<std::string> response_body) = 0;
+
+  // Called whenever a net request finishes (on success or failure). Protected
+  // for testing
+  void Finish(int net_error,
+              int response_code,
+              std::optional<std::string> response_body);
+
+  // Initialize `data_pipe_getter_`
+  void CreateDatapipe(std::unique_ptr<network::ResourceRequest> request,
+                      file_access::ScopedFileAccess file_access);
+
+  // Called after `data_pipe_getter_` has be
+  void OnDataPipeCreated(
+      std::unique_ptr<network::ResourceRequest> request,
+      std::unique_ptr<enterprise_connectors::ConnectorDataPipeGetter>
+          data_pipe_getter);
+
+  // Called after `data_pipe_getter_` is known to be initialized to a correct
+  // state.
+  void SendContentNow(std::unique_ptr<network::ResourceRequest> request);
+
+  // Send the metadata information about the file/page to the server.
+  void SendMetadataRequest();
+
+  // Returns true if all of the following conditions are met:
+  //    1. The HTTP status is OK.
+  //    2. The `headers` have `upload_status` and `upload_url`.
+  //    3. The `upload_status` is "active".
+  // This method also has the side effect of setting upload_url_.
+  bool CanUploadContent(const scoped_refptr<net::HttpResponseHeaders>& headers);
+
+  // Returns true if `kEnableEncryptedFileUpload`
+  // feature is enabled and the `scan_type_` is ASYNC.
+  bool ShouldUploadEncryptedFile();
+
+  bool force_sync_upload() const { return force_sync_upload_; }
+
+  // Helper used by metrics logging code.
+  std::string GetRequestType();
+
+  enum {
+    PENDING = 0,
+    METADATA_ONLY = 1,
+    FULL_CONTENT = 2,
+    ASYNC = 3
+  } scan_type_ = PENDING;
+
+  VerdictReceivedCallback verdict_received_callback_;
+  ContentUploadedCallback content_uploaded_callback_;
+  std::string access_token_;
+
+  // The result returned by BinaryUploadService::Request::GetRequestData() when
+  // retrieving the data.
+  ScanRequestUploadResult get_data_result_;
+
+  bool is_obfuscated_ = false;
+
+  bool force_sync_upload_ = false;
+
+  base::WeakPtrFactory<ResumableUploadRequestBase> weak_factory_{this};
+};
+
+}  // namespace enterprise_connectors
+
+#endif  // COMPONENTS_ENTERPRISE_CONNECTORS_CORE_CLOUD_CONTENT_SCANNING_RESUMABLE_UPLOADER_BASE_H_
diff --git a/components/heap_profiling/in_process/heap_profiler_controller.cc b/components/heap_profiling/in_process/heap_profiler_controller.cc
index db38969..e5e8d12 100644
--- a/components/heap_profiling/in_process/heap_profiler_controller.cc
+++ b/components/heap_profiling/in_process/heap_profiler_controller.cc
@@ -220,7 +220,7 @@
 
   if (base::FeatureList::IsEnabled(base::kUseLockFreeBloomFilter)) {
     const size_t kMaxSaturationSize = 65;
-    static_assert(kMaxSaturationSize == base::LockFreeBloomFilter::kMaxBits + 1,
+    static_assert(kMaxSaturationSize == base::kMaxLockFreeBloomFilterBits + 1,
                   "LockFreeBloomFilter's max bits has changed. Need to update "
                   "the metric.");
 
diff --git a/components/heap_profiling/in_process/heap_profiler_controller_unittest.cc b/components/heap_profiling/in_process/heap_profiler_controller_unittest.cc
index f5c9464..86f700e 100644
--- a/components/heap_profiling/in_process/heap_profiler_controller_unittest.cc
+++ b/components/heap_profiling/in_process/heap_profiler_controller_unittest.cc
@@ -29,6 +29,7 @@
 #include "base/metrics/metrics_hashes.h"
 #include "base/process/launch.h"
 #include "base/process/process.h"
+#include "base/sampling_heap_profiler/lock_free_address_hash_set.h"
 #include "base/sampling_heap_profiler/poisson_allocation_sampler.h"
 #include "base/sampling_heap_profiler/sampling_heap_profiler.h"
 #include "base/strings/string_number_conversions.h"
@@ -684,6 +685,8 @@
   int network_snapshot_prob = 100;
   int renderer_snapshot_prob = 100;
   int utility_snapshot_prob = 100;
+  // Whether the UseLockFreeBloomFilter optimization is enabled.
+  bool bloom_filter_enabled = false;
 
   base::FieldTrialParams ToFieldTrialParams() const;
 
@@ -727,8 +730,11 @@
 std::vector<FeatureRefAndParams> FeatureTestParams::GetEnabledFeatures() const {
   std::vector<FeatureRefAndParams> enabled_features;
   if (feature_enabled) {
-    enabled_features.push_back(
-        FeatureRefAndParams(kHeapProfilerReporting, ToFieldTrialParams()));
+    enabled_features.emplace_back(kHeapProfilerReporting, ToFieldTrialParams());
+  }
+  if (bloom_filter_enabled) {
+    enabled_features.emplace_back(base::kUseLockFreeBloomFilter,
+                                  base::FieldTrialParams());
   }
   return enabled_features;
 }
@@ -736,7 +742,10 @@
 std::vector<FeatureRef> FeatureTestParams::GetDisabledFeatures() const {
   std::vector<FeatureRef> disabled_features;
   if (!feature_enabled) {
-    disabled_features.push_back(FeatureRef(kHeapProfilerReporting));
+    disabled_features.emplace_back(kHeapProfilerReporting);
+  }
+  if (!bloom_filter_enabled) {
+    disabled_features.emplace_back(base::kUseLockFreeBloomFilter);
   }
   return disabled_features;
 }
@@ -1060,6 +1069,14 @@
         .stable = {.probability = 0.0, .expect_browser_sample = false},
         .nonstable = {.probability = 1.0, .expect_browser_sample = true},
     },
+    // Enabled on all channels, with the LockFreeBloomFilter optimization.
+    // Ensures test coverage of the code that calculates bloom filter metrics.
+    {
+        .feature_enabled = true,
+        .stable = {.probability = 1.0, .expect_browser_sample = true},
+        .nonstable = {.probability = 1.0, .expect_browser_sample = true},
+        .bloom_filter_enabled = true,
+    },
 };
 
 using HeapProfilerControllerChannelTest = HeapProfilerControllerTest;
diff --git a/components/ip_protection/common/BUILD.gn b/components/ip_protection/common/BUILD.gn
index df29c66..479e2906 100644
--- a/components/ip_protection/common/BUILD.gn
+++ b/components/ip_protection/common/BUILD.gn
@@ -83,6 +83,7 @@
   sources = [ "ip_protection_core_impl_unittest.cc" ]
   deps = [
     ":ip_protection_core_impl",
+    ":ip_protection_data_types",
     ":ip_protection_proxy_config_manager_impl",
     "//base",
     "//base/test:test_support",
@@ -106,6 +107,7 @@
   ]
   deps = [
     ":ip_protection_core_host_remote",
+    ":ip_protection_data_types",
     ":ip_protection_proxy_config_manager_impl",
     ":ip_protection_proxy_config_mojo_fetcher",
     ":ip_protection_token_manager_impl",
@@ -135,8 +137,8 @@
     "ip_protection_data_types.cc",
     "ip_protection_data_types.h",
   ]
-  public_deps = [
-    "//base",
+  public_deps = [ "//base" ]
+  deps = [
     "//components/privacy_sandbox/masked_domain_list:masked_domain_list_proto",
   ]
 }
@@ -198,6 +200,7 @@
   ]
   deps = [
     ":ip_protection_core_host_remote",
+    ":ip_protection_data_types",
     "//components/ip_protection/mojom",
     "//mojo/public/cpp/bindings",
     "//services/network/public/cpp",
@@ -517,15 +520,15 @@
     "masked_domain_list_manager.h",
   ]
   public_deps = [
+    ":ip_protection_data_types",
     ":masked_domain_list",
     "//base",
-    "//components/privacy_sandbox/masked_domain_list:masked_domain_list_proto",
     "//net",
     "//services/network/public/mojom",
   ]
   deps = [
-    ":ip_protection_data_types",
     ":ip_protection_telemetry",
+    "//components/privacy_sandbox/masked_domain_list:masked_domain_list_proto",
     "//services/network/public/cpp",
   ]
 }
@@ -534,6 +537,7 @@
   testonly = true
   sources = [ "masked_domain_list_manager_unittest.cc" ]
   deps = [
+    ":ip_protection_data_types",
     ":masked_domain_list_manager",
     "//base",
     "//base/test:test_support",
@@ -547,18 +551,18 @@
   ]
 }
 
+
 source_set("masked_domain_list") {
   sources = [
     "masked_domain_list.cc",
     "masked_domain_list.h",
   ]
-  public_deps = [
-    "//components/ip_protection/common/flat:masked_domain_list",
-    "//third_party/flatbuffers",
-  ]
+  public_deps = [ "//third_party/flatbuffers" ]
   deps = [
     ":ip_protection_data_types",
     "//base",
+    "//components/privacy_sandbox/masked_domain_list:masked_domain_list_proto",
+    "//components/ip_protection/common/flat:masked_domain_list",
   ]
 }
 
diff --git a/components/ip_protection/common/ip_protection_data_types.h b/components/ip_protection/common/ip_protection_data_types.h
index f6b9e229..c701c31 100644
--- a/components/ip_protection/common/ip_protection_data_types.h
+++ b/components/ip_protection/common/ip_protection_data_types.h
@@ -11,10 +11,7 @@
 #include <vector>
 
 #include "base/time/time.h"
-
-namespace masked_domain_list {
-class Resource;
-}
+#include "components/privacy_sandbox/masked_domain_list/masked_domain_list.pb.h"
 
 namespace ip_protection {
 
diff --git a/components/omnibox/browser/autocomplete_grouper_sections.cc b/components/omnibox/browser/autocomplete_grouper_sections.cc
index 3a2bb43..52ca3ea 100644
--- a/components/omnibox/browser/autocomplete_grouper_sections.cc
+++ b/components/omnibox/browser/autocomplete_grouper_sections.cc
@@ -225,6 +225,13 @@
   above_keyboard_group.set_limit(above_keyboard_group.limit() - 1);
 }
 
+AndroidComposeboxNonZPSSection::AndroidComposeboxNonZPSSection(
+    omnibox::GroupConfigMap& group_configs)
+    : Section(1,
+              {// Default match Group only
+               Group(1, {{omnibox::GROUP_SEARCH, 1}})},
+              group_configs) {}
+
 AndroidHubZPSSection::AndroidHubZPSSection(
     omnibox::GroupConfigMap& group_configs)
     : Section(5,
diff --git a/components/omnibox/browser/autocomplete_grouper_sections.h b/components/omnibox/browser/autocomplete_grouper_sections.h
index 271d44d2..53da708 100644
--- a/components/omnibox/browser/autocomplete_grouper_sections.h
+++ b/components/omnibox/browser/autocomplete_grouper_sections.h
@@ -118,6 +118,14 @@
   static size_t num_visible_matches_;
 };
 
+// Android section for a single default match suggestion when there is one or
+// more composebox attachment.
+class AndroidComposeboxNonZPSSection : public Section {
+ public:
+  explicit AndroidComposeboxNonZPSSection(
+      omnibox::GroupConfigMap& group_configs);
+};
+
 // Android prefix section for Hub search (ZPS).
 class AndroidHubZPSSection : public Section {
  public:
diff --git a/components/omnibox/browser/autocomplete_result.cc b/components/omnibox/browser/autocomplete_result.cc
index 9999672..d6415bb 100644
--- a/components/omnibox/browser/autocomplete_result.cc
+++ b/components/omnibox/browser/autocomplete_result.cc
@@ -673,6 +673,10 @@
       if (omnibox::IsAndroidHub(page_classification)) {
         sections.push_back(
             std::make_unique<AndroidHubNonZPSSection>(suggestion_groups_map_));
+      } else if (omnibox::IsComposebox(page_classification) &&
+                 input.lens_overlay_suggest_inputs()) {
+        sections.push_back(std::make_unique<AndroidComposeboxNonZPSSection>(
+            suggestion_groups_map_));
       } else {
         bool show_only_search_suggestions =
             omnibox::IsCustomTab(page_classification);
diff --git a/components/omnibox_strings.grdp b/components/omnibox_strings.grdp
index 58197fe..3a524542 100644
--- a/components/omnibox_strings.grdp
+++ b/components/omnibox_strings.grdp
@@ -173,6 +173,9 @@
       <message name="IDS_FUSEBOX_MAX_ATTACHMENTS" desc="The text displayed in a snackbar when the max number of omnibox attachments is reached" formatter_data="android_java">
         Add up to 10 files, tabs, or images
       </message>
+      <message name="IDS_FUSEBOX_UPLOAD_FAILED" desc="The text displayed in a snackbar when an attachment fails to upload" formatter_data="android_java">
+        Unable to add attachments
+      </message>
       <message name="IDS_OMNIBOX_CREATE_IMAGE" desc="The text displayed in the omnibox attachments popup. When clicked - enables image creation." formatter_data="android_java">
         Create Image
       </message>
diff --git a/components/omnibox_strings_grdp/IDS_FUSEBOX_UPLOAD_FAILED.png.sha1 b/components/omnibox_strings_grdp/IDS_FUSEBOX_UPLOAD_FAILED.png.sha1
new file mode 100644
index 0000000..8da8621
--- /dev/null
+++ b/components/omnibox_strings_grdp/IDS_FUSEBOX_UPLOAD_FAILED.png.sha1
@@ -0,0 +1 @@
+e78fecfb2310cd69e276050d5e467cb535583171
\ No newline at end of file
diff --git a/components/optimization_guide/core/feature_registry/feature_registration.cc b/components/optimization_guide/core/feature_registry/feature_registration.cc
index 21b8c27..2470a79c0 100644
--- a/components/optimization_guide/core/feature_registry/feature_registration.cc
+++ b/components/optimization_guide/core/feature_registry/feature_registration.cc
@@ -53,6 +53,11 @@
 const char kBlingPrototypingEnterprisePolicyAllowed[] =
     "optimization_guide.model_execution.bling_prototyping_enterprise_policy_"
     "allowed";
+
+const char kContextualTasksContextEnterprisePolicyAllowed[] =
+    "optimization_guide.model_execution.contextual_tasks_context_enterprise_"
+    "policy_allowed";
+
 }  // namespace prefs
 
 namespace features {
@@ -83,6 +88,9 @@
 
 BASE_FEATURE(kBlingPrototypingMqlsLogging, base::FEATURE_DISABLED_BY_DEFAULT);
 
+BASE_FEATURE(kContextualTasksContextMqlsLogging,
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 }  // namespace features
 
 namespace {
@@ -279,6 +287,17 @@
           &features::kBlingPrototypingMqlsLogging, FeedbackUnspecified()));
 }
 
+void RegisterContextualTasksContext() {
+  MqlsFeatureRegistry::GetInstance().Register(
+      std::make_unique<MqlsFeatureMetadata>(
+          "ContextualTasksContext",
+          proto::LogAiDataRequest::FeatureCase::kContextualTasksContext,
+          EnterprisePolicyRegistry::GetInstance().Register(
+              prefs::kContextualTasksContextEnterprisePolicyAllowed),
+          &features::kContextualTasksContextMqlsLogging,
+          FeedbackUnspecified()));
+}
+
 }  // anonymous namespace
 
 void RegisterGenAiFeatures(PrefRegistrySimple* pref_registry) {
@@ -299,6 +318,7 @@
     RegisterPasswordChangeSubmission();
     RegisterNotificationContentDetection();
     RegisterBlingPrototyping();
+    RegisterContextualTasksContext();
     features_registered = true;
   }
   EnterprisePolicyRegistry::GetInstance().RegisterProfilePrefs(pref_registry);
diff --git a/components/optimization_guide/core/feature_registry/feature_registration.h b/components/optimization_guide/core/feature_registry/feature_registration.h
index 7c9ca339..08e8cfa 100644
--- a/components/optimization_guide/core/feature_registry/feature_registration.h
+++ b/components/optimization_guide/core/feature_registry/feature_registration.h
@@ -28,6 +28,8 @@
 extern const char kAutofillPredictionImprovementsEnterprisePolicyAllowed[];
 COMPONENT_EXPORT(OPTIMIZATION_GUIDE_FEATURES)
 extern const char kBlingPrototypingEnterprisePolicyAllowed[];
+COMPONENT_EXPORT(OPTIMIZATION_GUIDE_FEATURES)
+extern const char kContextualTasksContextEnterprisePolicyAllowed[];
 }  // namespace prefs
 
 namespace features {
@@ -49,6 +51,8 @@
 BASE_DECLARE_FEATURE(kPasswordChangeSubmissionMqlsLogging);
 COMPONENT_EXPORT(OPTIMIZATION_GUIDE_FEATURES)
 BASE_DECLARE_FEATURE(kBlingPrototypingMqlsLogging);
+COMPONENT_EXPORT(OPTIMIZATION_GUIDE_FEATURES)
+BASE_DECLARE_FEATURE(kContextualTasksContextMqlsLogging);
 }  // namespace features
 
 void RegisterGenAiFeatures(PrefRegistrySimple* registry);
diff --git a/components/optimization_guide/internal b/components/optimization_guide/internal
index fb463d9..ad72f8d 160000
--- a/components/optimization_guide/internal
+++ b/components/optimization_guide/internal
@@ -1 +1 @@
-Subproject commit fb463d9b64048748ee57dafb48dd3f9450d6ebee
+Subproject commit ad72f8dcbf4b3f29798846d1746b02c3e3a7b3ff
diff --git a/components/payments/content/BUILD.gn b/components/payments/content/BUILD.gn
index 88f7cc55..89ee3bc 100644
--- a/components/payments/content/BUILD.gn
+++ b/components/payments/content/BUILD.gn
@@ -299,6 +299,7 @@
     "//components/webauthn/core/browser:test_support",
     "//components/webdata/common",
     "//content/test:test_support",
+    "//crypto:test_support",
     "//net:test_support",
     "//services/metrics/public/cpp:metrics_cpp",
     "//services/network:test_support",
diff --git a/components/payments/content/secure_payment_confirmation_app_factory_unittest.cc b/components/payments/content/secure_payment_confirmation_app_factory_unittest.cc
index 0e073df..e16221b 100644
--- a/components/payments/content/secure_payment_confirmation_app_factory_unittest.cc
+++ b/components/payments/content/secure_payment_confirmation_app_factory_unittest.cc
@@ -28,6 +28,7 @@
 #include "content/public/test/test_browser_context.h"
 #include "content/public/test/test_web_contents_factory.h"
 #include "content/test/test_web_contents.h"
+#include "crypto/scoped_fake_unexportable_key_provider.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/common/features.h"
@@ -36,6 +37,10 @@
 #include "url/gurl.h"
 #include "url/origin.h"
 
+#if !BUILDFLAG(IS_ANDROID)
+#include "components/payments/content/mock_content_payment_request_delegate.h"
+#endif
+
 namespace payments {
 namespace {
 
@@ -107,6 +112,11 @@
         .WillOnce(Return(ByMove(std::move(mock_authenticator_))));
     EXPECT_CALL(*mock_delegate, GetWebPaymentsWebDataService())
         .WillRepeatedly(Return(mock_service_));
+#if !BUILDFLAG(IS_ANDROID)
+    ON_CALL(*mock_delegate, GetPaymentRequestDelegate())
+        .WillByDefault(testing::Return(
+            mock_content_payment_request_delegate_.GetContentWeakPtr()));
+#endif
 
     return mock_delegate;
   }
@@ -176,6 +186,13 @@
   // after it to avoid a dangling raw_ptr during destruction.
   raw_ptr<MockSecurePaymentConfirmationCredentialFinder>
       mock_credential_finder_;
+
+ private:
+  crypto::ScopedFakeUnexportableKeyProvider scoped_key_provider_;
+  // MockContentPaymentRequestDelegate is not available on Android.
+#if !BUILDFLAG(IS_ANDROID)
+  MockContentPaymentRequestDelegate mock_content_payment_request_delegate_;
+#endif
 };
 
 // Test that parsing a valid SecureConfirmationPaymentRequest succeeds.
@@ -788,8 +805,6 @@
 // Test that the browser bound key is retrieved
 TEST_F(SecurePaymentConfirmationAppFactoryBrowserBoundKeysTest,
        ProvidesBrowserBoundingToSecurePaymentConfirmationApp) {
-  base::test::ScopedFeatureList feature_list{
-      blink::features::kSecurePaymentConfirmationBrowserBoundKeys};
   url::Origin caller_origin = url::Origin::Create(GURL("https://site.example"));
   std::vector<uint8_t> browser_bound_key_id({0x11, 0x12, 0x13, 0x14});
   auto method_data = mojom::PaymentMethodData::New();
diff --git a/components/permissions/PERMISSIONS_OWNERS b/components/permissions/PERMISSIONS_OWNERS
index b077da8..63da5a6 100644
--- a/components/permissions/PERMISSIONS_OWNERS
+++ b/components/permissions/PERMISSIONS_OWNERS
@@ -2,6 +2,7 @@
 andypaicu@chromium.org
 elklm@chromium.org
 engedy@chromium.org
+hempjudith@google.com
 ravjit@chromium.org
 tungnh@chromium.org
 
diff --git a/components/persistent_cache/BUILD.gn b/components/persistent_cache/BUILD.gn
index 953f1f6b..29344c77 100644
--- a/components/persistent_cache/BUILD.gn
+++ b/components/persistent_cache/BUILD.gn
@@ -58,6 +58,7 @@
   public_deps = [ "//base" ]
 
   friend = [
+    ":perf_tests",
     ":test_support",
     ":unit_tests",
   ]
@@ -120,3 +121,18 @@
   # enable the diagnostic by removing this line.
   configs += [ "//build/config/compiler:no_exit_time_destructors" ]
 }
+
+if (!is_fuchsia) {
+  source_set("perf_tests") {
+    testonly = true
+    sources = [ "persistent_cache_perftest.cc" ]
+    deps = [
+      ":persistent_cache",
+      ":test_support",
+      "//base",
+      "//base/test:test_support",
+      "//testing/gtest",
+      "//testing/perf",
+    ]
+  }
+}
diff --git a/components/persistent_cache/persistent_cache_perftest.cc b/components/persistent_cache/persistent_cache_perftest.cc
index b118d3e..159e293e 100644
--- a/components/persistent_cache/persistent_cache_perftest.cc
+++ b/components/persistent_cache/persistent_cache_perftest.cc
@@ -12,32 +12,76 @@
 #include "base/files/file_path.h"
 #include "base/files/scoped_temp_dir.h"
 #include "base/functional/function_ref.h"
+#include "base/strings/strcat.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/test/gmock_expected_support.h"
 #include "base/time/time.h"
 #include "base/timer/elapsed_timer.h"
-#include "build/buildflag.h"
+#include "build/build_config.h"
 #include "components/persistent_cache/backend_storage.h"
 #include "components/persistent_cache/backend_type.h"
 #include "components/persistent_cache/pending_backend.h"
+#include "components/persistent_cache/sqlite/sqlite_backend_impl.h"
 #include "components/persistent_cache/test_utils.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "testing/perf/perf_result_reporter.h"
 
+#if BUILDFLAG(IS_APPLE)
+#include "base/mac/mac_util.h"
+#endif
+
 namespace persistent_cache {
 
-class PersistentCachePerftest : public testing::Test {
- protected:
-  static constexpr base::FilePath::StringViewType kBaseName =
-      FILE_PATH_LITERAL("perftest");
+// The variations of cache options available for creating/testing
+// PersistentCache.
+enum class CacheOption {
+  kMultipleConnections,
+  kSingleConnection,
+  kJournalModeWal,
+};
 
+// A printer for `CacheOption`; used by GoogleTest for more friendly output and
+// to suffix the story name for performance measurements.
+void PrintTo(CacheOption cache_option, std::ostream* os) {
+  switch (cache_option) {
+    case CacheOption::kMultipleConnections:
+      *os << "MultipleConnections";
+      break;
+    case CacheOption::kSingleConnection:
+      *os << "SingleConnection";
+      break;
+    case CacheOption::kJournalModeWal:
+      *os << "JournalModeWal";
+      break;
+  }
+}
+
+// A test harness parameterized on the options for creating a PersistentCache.
+class PersistentCachePerftest : public testing::TestWithParam<CacheOption> {
+ protected:
   void SetUp() override {
     ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
     backend_storage_.emplace(BackendType::kSqlite, temp_dir_.GetPath());
   }
 
-  std::unique_ptr<PersistentCache> CreateCache(bool single_connection = false,
-                                               bool journal_mode_wal = false) {
+  // Returns a new cache configured according to the test's parameter.
+  std::unique_ptr<PersistentCache> MakeCache() {
+    switch (GetParam()) {
+      case CacheOption::kMultipleConnections:
+        return CreateCache(/*single_connection=*/false,
+                           /*journal_mode_wal=*/false);
+      case CacheOption::kSingleConnection:
+        return CreateCache(/*single_connection=*/true,
+                           /*journal_mode_wal=*/false);
+      case CacheOption::kJournalModeWal:
+        return CreateCache(/*single_connection=*/true,
+                           /*journal_mode_wal=*/true);
+    }
+  }
+
+  // Returns a new cache with the given options.
+  std::unique_ptr<PersistentCache> CreateCache(bool single_connection,
+                                               bool journal_mode_wal) {
     if (auto pending_backend = backend_storage_->MakePendingBackend(
             base::FilePath(kBaseName), single_connection, journal_mode_wal);
         pending_backend.has_value()) {
@@ -47,13 +91,19 @@
     return nullptr;
   }
 
+  // Returns true if caches created in this configuration can be shared across
+  // multiple connections.
+  static bool CanShareConnections() {
+    return GetParam() == CacheOption::kMultipleConnections;
+  }
+
   std::optional<PendingBackend> ShareReadWriteConnection(
       PersistentCache& cache) {
     return backend_storage_->ShareReadWriteConnection(base::FilePath(kBaseName),
                                                       cache);
   }
 
-  void RunAndTimeTest(std::string operation_name,
+  void RunAndTimeTest(std::string_view operation_name,
                       int iteration_count,
                       base::FunctionRef<void()> test_body) {
     base::AutoReset<bool> resetter(&under_measurment_, true);
@@ -62,8 +112,10 @@
 
     test_body();
 
-    ReportMeasurment(operation_name, iteration_count, elapsed_timer.Elapsed(),
-                     elapsed_thread_timer.Elapsed());
+    ReportMeasurment(
+        base::StrCat({operation_name, testing::PrintToString(GetParam())}),
+        iteration_count, elapsed_timer.Elapsed(),
+        elapsed_thread_timer.Elapsed());
   }
 
   // Pregenerates keys. Use to avoid timing allocation overhead.
@@ -92,7 +144,25 @@
     return value;
   }
 
+  // Returns true if this platform has expensive database commits.
+  static bool HasExpensiveCommits() {
+#if BUILDFLAG(IS_APPLE)
+    // Commits are slow on macOS 12. Speculation: perhaps it does not benefit
+    // from F_BARRIERFSYNC.
+    return base::mac::MacOSMajorVersion() < 13;
+#elif BUILDFLAG(IS_WIN)
+    return true;
+#else
+    // Android and other POSIX systems appear to benefit from batch atomic
+    // writes.
+    return false;
+#endif
+  }
+
  private:
+  static constexpr base::FilePath::StringViewType kBaseName =
+      FILE_PATH_LITERAL("perftest");
+
   void ReportMeasurment(std::string operation_name,
                         int iteration_count,
                         base::TimeDelta elapsed_time,
@@ -115,102 +185,86 @@
   bool under_measurment_ = false;
 };
 
-// Only compile and run these tests on configurations that are monitored.
-#if (BUILDFLAG(IS_WIN) || BUILDFLAG(IS_ANDROID)) && \
-    !defined(ADDRESS_SANITIZER) && !defined(THREAD_SANITIZER)
-
-TEST_F(PersistentCachePerftest, OpenClose) {
-  auto persistent_cache = CreateCache();
+TEST_P(PersistentCachePerftest, OpenClose) {
+  if (!CanShareConnections()) {
+    // TODO(crbug.com/377475540): Switch from sharing a connection below to
+    // Bind/Unbind so that the same file handles are used repeatedly to
+    // open/close the database.
+    GTEST_SKIP();
+  }
 
   static constexpr int kIterationCount = 1024;
 
+  std::unique_ptr<PersistentCache> cache = MakeCache();
+
   int success_count = 0;
-  RunAndTimeTest("OpenClose", kIterationCount, [&] {
-    for (size_t i = 0; i < kIterationCount; ++i) {
-      auto persistent_cache_under_test =
-          PersistentCache::Bind(*ShareReadWriteConnection(*persistent_cache));
-      if (persistent_cache_under_test) {
-        ++success_count;
-      }
-    }
-  });
+  RunAndTimeTest(
+      "OpenClose", kIterationCount, [this, &cache = *cache, &success_count] {
+        for (size_t i = 0; i < kIterationCount; ++i) {
+          auto persistent_cache_under_test =
+              PersistentCache::Bind(*ShareReadWriteConnection(cache));
+          if (persistent_cache_under_test) {
+            ++success_count;
+          }
+        }
+      });
 
   ASSERT_EQ(success_count, kIterationCount);
 }
 
-TEST_F(PersistentCachePerftest, Insert) {
-  auto persistent_cache = CreateCache();
+TEST_P(PersistentCachePerftest, Insert) {
+  int kIterationCount = 1024;
 
-  static constexpr int kIterationCount = 1024;
+  if (GetParam() != CacheOption::kJournalModeWal && HasExpensiveCommits()) {
+    // Insertions take an egregiously long time when commits are expensive.
+    // Scale back the number of iterations in that case.
+    kIterationCount /= 4;
+  }
+
+  std::unique_ptr<PersistentCache> cache = MakeCache();
   std::vector<std::string> keys = GenerateKeys(kIterationCount);
   base::HeapArray<uint8_t> value = MakeValue();
 
   int success_count = 0;
   RunAndTimeTest("Insert", kIterationCount, [&] {
-    success_count = std::ranges::count_if(
-        keys, [&cache = *persistent_cache, &value](const auto& key) {
+    success_count =
+        std::ranges::count_if(keys, [&cache = *cache, &value](const auto& key) {
           return cache.Insert(key, value.as_span()).has_value();
         });
   });
   ASSERT_EQ(success_count, kIterationCount);
 }
 
-TEST_F(PersistentCachePerftest, InsertSingleConnection) {
-  auto persistent_cache = CreateCache(/*single_connection=*/true);
-
+TEST_P(PersistentCachePerftest, Find) {
   static constexpr int kIterationCount = 1024;
-  std::vector<std::string> keys = GenerateKeys(kIterationCount);
-  base::HeapArray<uint8_t> value = MakeValue();
 
-  int success_count = 0;
-  RunAndTimeTest("InsertSingleConnection", kIterationCount, [&] {
-    success_count = std::ranges::count_if(
-        keys, [&cache = *persistent_cache, &value](const auto& key) {
-          return cache.Insert(key, value.as_span()).has_value();
-        });
-  });
-  ASSERT_EQ(success_count, kIterationCount);
-}
-
-TEST_F(PersistentCachePerftest, InsertWal) {
-  auto persistent_cache =
+  // Open the cache in WAL mode and fill it.
+  std::unique_ptr<PersistentCache> cache =
       CreateCache(/*single_connection=*/true, /*journal_mode_wal=*/true);
-
-  static constexpr int kIterationCount = 1024;
-  std::vector<std::string> keys = GenerateKeys(kIterationCount);
-  base::HeapArray<uint8_t> value = MakeValue();
-
-  int success_count = 0;
-  RunAndTimeTest("InsertWal", kIterationCount, [&] {
-    success_count = std::ranges::count_if(
-        keys, [&cache = *persistent_cache, &value](const auto& key) {
-          return cache.Insert(key, value.as_span()).has_value();
-        });
-  });
-  ASSERT_EQ(success_count, kIterationCount);
-}
-
-TEST_F(PersistentCachePerftest, Find) {
-  auto persistent_cache = CreateCache();
-
-  static constexpr int kIterationCount = 1024;
   std::vector<std::string> keys = GenerateKeys(kIterationCount);
   base::HeapArray<uint8_t> value = MakeValue();
 
   // Fill the cache.
-  for (const std::string& key : keys) {
-    ASSERT_THAT(persistent_cache->Insert(key, value.as_span()),
-                base::test::HasValue());
+  for (const auto& key : keys) {
+    ASSERT_OK(cache->Insert(key, value, {}));
   }
 
+  // Switch the cache back to using a rollback journal and close it. This will
+  // perform a checkpoint and allow the database to be opened without the
+  // write-ahead log file.
+  ASSERT_OK(static_cast<SqliteBackendImpl*>(cache->GetBackendForTesting())
+                ->ExecuteStatementForTesting("PRAGMA journal_mode=TRUNCATE"));
+  cache.reset();
+  cache = MakeCache();
+
   // Shuffle the keys around to avoid taking advantage of file-system caching
   // behavior.
   base::RandomShuffle(keys.begin(), keys.end());
 
   int success_count = 0;
   RunAndTimeTest("Find", kIterationCount, [&] {
-    success_count = std::ranges::count_if(keys, [&cache = *persistent_cache](
-                                                    const auto& key) {
+    success_count = std::ranges::count_if(keys, [&cache =
+                                                     *cache](const auto& key) {
       return cache
           .Find(key, [](size_t content_size) { return base::span<uint8_t>(); })
           .has_value();
@@ -219,66 +273,11 @@
   ASSERT_EQ(success_count, kIterationCount);
 }
 
-TEST_F(PersistentCachePerftest, FindSingleConnection) {
-  auto persistent_cache = CreateCache(/*single_connection=*/true);
-
-  static constexpr int kIterationCount = 1024;
-  std::vector<std::string> keys = GenerateKeys(kIterationCount);
-  base::HeapArray<uint8_t> value = MakeValue();
-
-  // Fill the cache.
-  for (const std::string& key : keys) {
-    ASSERT_THAT(persistent_cache->Insert(key, value.as_span()),
-                base::test::HasValue());
-  }
-
-  // Shuffle the keys around to avoid taking advantage of file-system caching
-  // behavior.
-  base::RandomShuffle(keys.begin(), keys.end());
-
-  int success_count = 0;
-  RunAndTimeTest("FindSingleConnection", kIterationCount, [&] {
-    success_count = std::ranges::count_if(keys, [&cache = *persistent_cache](
-                                                    const auto& key) {
-      return cache
-          .Find(key, [](size_t content_size) { return base::span<uint8_t>(); })
-          .has_value();
-    });
-  });
-  ASSERT_EQ(success_count, kIterationCount);
-}
-
-TEST_F(PersistentCachePerftest, FindWal) {
-  auto persistent_cache =
-      CreateCache(/*single_connection=*/true, /*journal_mode_wal=*/true);
-
-  static constexpr int kIterationCount = 1024;
-  std::vector<std::string> keys = GenerateKeys(kIterationCount);
-  base::HeapArray<uint8_t> value = MakeValue();
-
-  // Fill the cache.
-  for (const std::string& key : keys) {
-    ASSERT_THAT(persistent_cache->Insert(key, value.as_span()),
-                base::test::HasValue());
-  }
-
-  // Shuffle the keys around to avoid taking advantage of file-system caching
-  // behavior.
-  base::RandomShuffle(keys.begin(), keys.end());
-
-  int success_count = 0;
-  RunAndTimeTest("FindWal", kIterationCount, [&] {
-    success_count = std::ranges::count_if(keys, [&cache = *persistent_cache](
-                                                    const auto& key) {
-      return cache
-          .Find(key, [](size_t content_size) { return base::span<uint8_t>(); })
-          .has_value();
-    });
-  });
-  ASSERT_EQ(success_count, kIterationCount);
-}
-
-#endif  // (BUILDFLAG(IS_WIN) || BUILDFLAG(IS_ANDROID)) &&
-        // !defined(ADDRESS_SANITIZER) && !defined(THREAD_SANITIZER)
+INSTANTIATE_TEST_SUITE_P(,
+                         PersistentCachePerftest,
+                         testing::Values(CacheOption::kMultipleConnections,
+                                         CacheOption::kSingleConnection,
+                                         CacheOption::kJournalModeWal),
+                         testing::PrintToStringParamName());
 
 }  // namespace persistent_cache
diff --git a/components/persistent_cache/sqlite/sqlite_backend_impl.cc b/components/persistent_cache/sqlite/sqlite_backend_impl.cc
index c435e26..73389126 100644
--- a/components/persistent_cache/sqlite/sqlite_backend_impl.cc
+++ b/components/persistent_cache/sqlite/sqlite_backend_impl.cc
@@ -199,6 +199,17 @@
   return base::ok();
 }
 
+base::expected<void, int> SqliteBackendImpl::ExecuteStatementForTesting(
+    base::cstring_view statement) {
+  base::AutoLock lock(lock_, base::subtle::LockTracking::kEnabled);
+
+  if (!db_->Execute(statement)) {
+    return base::unexpected(db_->GetErrorCode());
+  }
+
+  return base::ok();
+}
+
 base::expected<std::optional<EntryMetadata>, int> SqliteBackendImpl::FindImpl(
     std::string_view key,
     BufferProvider buffer_provider) {
diff --git a/components/persistent_cache/sqlite/sqlite_backend_impl.h b/components/persistent_cache/sqlite/sqlite_backend_impl.h
index 53eb693..b127a57 100644
--- a/components/persistent_cache/sqlite/sqlite_backend_impl.h
+++ b/components/persistent_cache/sqlite/sqlite_backend_impl.h
@@ -53,6 +53,11 @@
   static std::optional<SqliteVfsFileSet> BindToFileSet(
       PendingBackend pending_backend);
 
+  // Executes `statement` on the underlying database. Returns a SQLite result
+  // code in case of error.
+  base::expected<void, int> ExecuteStatementForTesting(
+      base::cstring_view statement);
+
  private:
   FRIEND_TEST_ALL_PREFIXES(PersistentCacheTest, RecoveryFromTransientError);
 
diff --git a/components/services/storage/dom_storage/session_storage_data_map.cc b/components/services/storage/dom_storage/session_storage_data_map.cc
index 47336e0c..4b4f434 100644
--- a/components/services/storage/dom_storage/session_storage_data_map.cc
+++ b/components/services/storage/dom_storage/session_storage_data_map.cc
@@ -94,7 +94,7 @@
   storage_area()->ScheduleImmediateCommit();
 }
 
-void SessionStorageDataMap::OnMapLoaded(DbStatus) {
+void SessionStorageDataMap::OnMapLoaded() {
   clone_from_data_map_.reset();
 }
 
diff --git a/components/services/storage/dom_storage/session_storage_data_map.h b/components/services/storage/dom_storage/session_storage_data_map.h
index 751c9ed..c275d17 100644
--- a/components/services/storage/dom_storage/session_storage_data_map.h
+++ b/components/services/storage/dom_storage/session_storage_data_map.h
@@ -93,7 +93,7 @@
       scoped_refptr<SessionStorageDataMap> forking_from);
   ~SessionStorageDataMap() override;
 
-  void OnMapLoaded(DbStatus status) override;
+  void OnMapLoaded() override;
 
   static StorageAreaImpl::Options GetOptions();
 
diff --git a/components/services/storage/dom_storage/storage_area_impl.cc b/components/services/storage/dom_storage/storage_area_impl.cc
index 715b3b2..222b19a 100644
--- a/components/services/storage/dom_storage/storage_area_impl.cc
+++ b/components/services/storage/dom_storage/storage_area_impl.cc
@@ -7,6 +7,7 @@
 #include <memory>
 
 #include "base/containers/span.h"
+#include "base/containers/to_vector.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback_helpers.h"
 #include "base/metrics/histogram_functions.h"
@@ -24,7 +25,7 @@
     std::vector<DomStorageDatabase::KeyValuePair>* extra_entries_to_add,
     std::vector<DomStorageDatabase::Key>* extra_keys_to_delete) {}
 
-void StorageAreaImpl::Delegate::OnMapLoaded(DbStatus) {}
+void StorageAreaImpl::Delegate::OnMapLoaded() {}
 
 bool StorageAreaImpl::s_aggressive_flushing_enabled_ = false;
 
@@ -93,7 +94,7 @@
 void StorageAreaImpl::InitializeAsEmpty() {
   DCHECK_EQ(map_state_, MapState::UNLOADED);
   map_state_ = MapState::LOADING_FROM_DATABASE;
-  OnMapLoaded(DbStatus::OK(), {});
+  OnMapLoaded(std::vector<DomStorageDatabase::KeyValuePair>());
 }
 
 void StorageAreaImpl::Bind(
@@ -588,32 +589,22 @@
   map_state_ = MapState::LOADING_FROM_DATABASE;
 
   if (!database_) {
-    OnMapLoaded(DbStatus::IOError("Database no longer valid."), {});
+    OnMapLoaded(
+        base::unexpected(DbStatus::IOError("Database no longer valid.")));
     return;
   }
 
   database_->RunDatabaseTask(
       base::BindOnce(
           [](const DomStorageDatabase::Key& prefix,
-             DomStorageDatabaseLevelDB& db) {
-            StatusOr<std::vector<DomStorageDatabase::KeyValuePair>> data =
-                db.GetPrefixed(prefix);
-            if (data.has_value()) {
-              return std::make_tuple(DbStatus::OK(), *std::move(data));
-            }
-
-            return std::make_tuple(
-                std::move(data).error(),
-                std::vector<DomStorageDatabase::KeyValuePair>());
-          },
+             DomStorageDatabaseLevelDB& db) { return db.GetPrefixed(prefix); },
           prefix_),
       base::BindOnce(&StorageAreaImpl::OnMapLoaded,
                      weak_ptr_factory_.GetWeakPtr()));
 }
 
 void StorageAreaImpl::OnMapLoaded(
-    DbStatus status,
-    std::vector<DomStorageDatabase::KeyValuePair> data) {
+    StatusOr<std::vector<DomStorageDatabase::KeyValuePair>> data) {
   DCHECK(keys_values_map_.empty());
   DCHECK_EQ(map_state_, MapState::LOADING_FROM_DATABASE);
 
@@ -621,25 +612,22 @@
   map_state_ = MapState::LOADED_KEYS_AND_VALUES;
 
   keys_values_map_.clear();
-  for (auto& entry : data) {
-    DCHECK_GE(entry.key.size(), prefix_.size());
-    keys_values_map_[DomStorageDatabase::Key(entry.key.begin() + prefix_.size(),
-                                             entry.key.end())] =
-        std::move(entry.value);
-  }
-  CalculateStorageAndMemoryUsed();
-
-  // We proceed without using a backing store, nothing will be persisted but the
-  // class is functional for the lifetime of the object.
-  delegate_->OnMapLoaded(status);
-  if (!status.ok()) {
+  if (data.has_value()) {
+    for (DomStorageDatabase::KeyValuePair& entry : *data) {
+      keys_values_map_[base::ToVector(base::span(entry.key).subspan(
+          prefix_.size()))] = std::move(entry.value);
+    }
+  } else {
+    // We proceed without using a backing store, nothing will be persisted but
+    // the class is functional for the lifetime of the object.
     if (database_) {
       database_->RemoveCommitter(this);
+      database_ = nullptr;
     }
-    database_ = nullptr;
     SetCacheMode(CacheMode::KEYS_AND_VALUES);
   }
-
+  CalculateStorageAndMemoryUsed();
+  delegate_->OnMapLoaded();
   if (on_load_callback_for_testing_)
     std::move(on_load_callback_for_testing_).Run();
 
diff --git a/components/services/storage/dom_storage/storage_area_impl.h b/components/services/storage/dom_storage/storage_area_impl.h
index 838f3cf2..df56cc2 100644
--- a/components/services/storage/dom_storage/storage_area_impl.h
+++ b/components/services/storage/dom_storage/storage_area_impl.h
@@ -61,7 +61,7 @@
         std::vector<DomStorageDatabase::KeyValuePair>* extra_entries_to_add,
         std::vector<DomStorageDatabase::Key>* extra_keys_to_delete);
     virtual void DidCommit(DbStatus error) = 0;
-    virtual void OnMapLoaded(DbStatus status);
+    virtual void OnMapLoaded();
   };
 
   enum class CacheMode {
@@ -289,8 +289,8 @@
   // Then if the |cache_mode_| is keys-only, it unloads the map to the
   // |keys_only_map_| and sets the |map_state_| to LOADED_KEYS_ONLY
   void LoadMap(base::OnceClosure completion_callback);
-  void OnMapLoaded(DbStatus status,
-                   std::vector<DomStorageDatabase::KeyValuePair> data);
+  void OnMapLoaded(
+      StatusOr<std::vector<DomStorageDatabase::KeyValuePair>> data);
   void CalculateStorageAndMemoryUsed();
   void OnLoadComplete();
 
diff --git a/components/services/storage/dom_storage/storage_area_impl_unittest.cc b/components/services/storage/dom_storage/storage_area_impl_unittest.cc
index d467dbe..000af98e 100644
--- a/components/services/storage/dom_storage/storage_area_impl_unittest.cc
+++ b/components/services/storage/dom_storage/storage_area_impl_unittest.cc
@@ -96,7 +96,7 @@
     if (committed_)
       std::move(committed_).Run();
   }
-  void OnMapLoaded(DbStatus) override { map_load_count_++; }
+  void OnMapLoaded() override { ++map_load_count_; }
 
   int map_load_count() const { return map_load_count_; }
 
diff --git a/components/services/storage/sandboxed_vfs_file_impl.cc b/components/services/storage/sandboxed_vfs_file_impl.cc
index 056937b..76b6b35 100644
--- a/components/services/storage/sandboxed_vfs_file_impl.cc
+++ b/components/services/storage/sandboxed_vfs_file_impl.cc
@@ -4,10 +4,14 @@
 
 #include "components/services/storage/sandboxed_vfs_file_impl.h"
 
+#include <algorithm>
 #include <cstring>
+#include <optional>
 
 #include "base/compiler_specific.h"
+#include "base/containers/span.h"
 #include "base/notreached.h"
+#include "base/numerics/safe_conversions.h"
 #include "sql/sandboxed_vfs.h"
 
 namespace storage {
@@ -49,19 +53,19 @@
       << "Read from database file with lock mode " << sqlite_lock_mode_
       << "of size" << size << " at offset " << offset;
 
-  char* data = reinterpret_cast<char*>(buffer);
+  const base::span<uint8_t> data = UNSAFE_TODO(base::span(
+      reinterpret_cast<uint8_t*>(buffer), base::checked_cast<size_t>(size)));
 
   // If we supported mmap()ed files, we'd check for a memory mapping here,
   // and try to fill as much of the request as possible from the mmap()ed
   // region.
 
-  int bytes_read = UNSAFE_TODO(file_.Read(offset, data, size));
-  DCHECK_LE(bytes_read, size);
+  const std::optional<size_t> bytes_read = file_.Read(offset, data);
   if (bytes_read == size) {
     return SQLITE_OK;
   }
 
-  if (bytes_read < 0) {
+  if (!bytes_read) {
     // SQLite first reads the database header without locking the file. On
     // Windows, this read will fail if there is an exclusive lock on the file,
     // even if the current process owns that lock.
@@ -69,7 +73,7 @@
       // The unlocked read is considered an optimization. SQLite can continue
       // even if the read fails, as long as failure is communicated by zeroing
       // out the output buffer.
-      UNSAFE_TODO(std::memset(data, 0, size));
+      std::ranges::fill(data, 0);
       return SQLITE_OK;
     }
 
@@ -78,7 +82,7 @@
   }
 
   // SQLite requires that we fill the unread bytes in the buffer with zeros.
-  UNSAFE_TODO(std::memset(data + bytes_read, 0, size - bytes_read));
+  std::ranges::fill(data.subspan(*bytes_read), 0);
   return SQLITE_IOERR_SHORT_READ;
 }
 
@@ -95,15 +99,15 @@
          file_type_ != sql::SandboxedVfsFileType::kDatabase)
       << "Write to database file with lock mode " << sqlite_lock_mode_;
 
-  const char* data = reinterpret_cast<const char*>(buffer);
+  base::span<const uint8_t> data =
+      UNSAFE_TODO(base::span(reinterpret_cast<const uint8_t*>(buffer),
+                             base::checked_cast<size_t>(size)));
 
   // If we supported mmap()ed files, we'd check for a memory mapping here,
   // and try to fill as much of the request as possible by copying to the
   // mmap()ed region.
 
-  int bytes_written = UNSAFE_TODO(file_.Write(offset, data, size));
-  DCHECK_LE(bytes_written, size);
-  if (bytes_written >= size) {
+  if (file_.Write(offset, data) >= size) {
     return SQLITE_OK;
   }
 
diff --git a/components/signin/core/browser/signin_metrics_service.cc b/components/signin/core/browser/signin_metrics_service.cc
index d326fb7d..190eb112 100644
--- a/components/signin/core/browser/signin_metrics_service.cc
+++ b/components/signin/core/browser/signin_metrics_service.cc
@@ -199,6 +199,7 @@
         kEnterpriseManagementDisclaimerAfterSignin:
     case signin_metrics::AccessPoint::kNtpFeaturePromo:
     case signin_metrics::AccessPoint::kEnterpriseDialogAfterSigninInterception:
+    case signin_metrics::AccessPoint::kCredentialExchangeImport:
       return;
   }
 
diff --git a/components/signin/public/base/signin_metrics.cc b/components/signin/public/base/signin_metrics.cc
index aebb81c..8440455 100644
--- a/components/signin/public/base/signin_metrics.cc
+++ b/components/signin/public/base/signin_metrics.cc
@@ -669,6 +669,10 @@
       base::RecordAction(base::UserMetricsAction(
           "Signin_Signin_FromEnterpriseDialogAfterSigninInterception"));
       break;
+    case AccessPoint::kCredentialExchangeImport:
+      base::RecordAction(base::UserMetricsAction(
+          "Signin_Signin_FromCredentialExchangeImport"));
+      break;
   }
 }
 
@@ -812,6 +816,10 @@
       base::RecordAction(base::UserMetricsAction(
           "Signin_Impression_FromEnterpriseDialogAfterSigninInterception"));
       break;
+    case AccessPoint::kCredentialExchangeImport:
+      base::RecordAction(base::UserMetricsAction(
+          "Signin_Impression_FromCredentialExchangeImport"));
+      break;
     case AccessPoint::kEnterpriseSignoutCoordinator:
     case AccessPoint::kExtensions:
     case AccessPoint::kSupervisedUser:
diff --git a/components/signin/public/base/signin_metrics.h b/components/signin/public/base/signin_metrics.h
index 9e6699a..2f89597 100644
--- a/components/signin/public/base/signin_metrics.h
+++ b/components/signin/public/base/signin_metrics.h
@@ -310,10 +310,13 @@
   kEnterpriseDialogAfterSigninInterception = 90,
   // "Your saved info" settings page.
   kSettingsYourSavedInfo = 91,
+  // Triggered when the user attempts to import credentials through the
+  // ASCredentialImportManager without being signed in.
+  kCredentialExchangeImport = 92,
   // Add values above this line with a corresponding label to the
   // "SigninAccessPoint" enum in
   // tools/metrics/histograms/metadata/signin/enums.xml.
-  kMaxValue = kSettingsYourSavedInfo,  // This must be last.
+  kMaxValue = kCredentialExchangeImport,  // This must be last.
 };
 // LINT.ThenChange(/tools/metrics/histograms/metadata/signin/enums.xml)
 
diff --git a/components/signin/public/base/signin_metrics_unittest.cc b/components/signin/public/base/signin_metrics_unittest.cc
index 404d980a..4f016c0c 100644
--- a/components/signin/public/base/signin_metrics_unittest.cc
+++ b/components/signin/public/base/signin_metrics_unittest.cc
@@ -55,6 +55,7 @@
     AccessPoint::kNonModalSigninBookmarkPromo,
     AccessPoint::kUserManagerWithPrefilledEmail,
     AccessPoint::kEnterpriseDialogAfterSigninInterception,
+    AccessPoint::kCredentialExchangeImport,
 };
 
 const AccessPoint kAccessPointsThatSupportImpression[] = {
@@ -85,6 +86,7 @@
     AccessPoint::kNotificationsOptInScreenContentToggle,
     AccessPoint::kAddressBubble,
     AccessPoint::kEnterpriseDialogAfterSigninInterception,
+    AccessPoint::kCredentialExchangeImport,
 };
 
 class SigninMetricsTest : public ::testing::Test {
@@ -257,6 +259,8 @@
         return "NtpFeaturePromo";
       case AccessPoint::kEnterpriseDialogAfterSigninInterception:
         return "EnterpriseDialogAfterSigninInterception";
+      case AccessPoint::kCredentialExchangeImport:
+        return "CredentialExchangeImport";
     }
   }
 };
diff --git a/components/unexportable_keys/mojom/unexportable_key_service_proxied.cc b/components/unexportable_keys/mojom/unexportable_key_service_proxied.cc
index 9412bbb..e927e1b 100644
--- a/components/unexportable_keys/mojom/unexportable_key_service_proxied.cc
+++ b/components/unexportable_keys/mojom/unexportable_key_service_proxied.cc
@@ -4,12 +4,14 @@
 
 #include "components/unexportable_keys/mojom/unexportable_key_service_proxied.h"
 
+#include <cstdint>
+#include <optional>
 #include <utility>
 #include <vector>
 
 #include "base/containers/to_vector.h"
 #include "base/functional/bind.h"
-#include "base/notimplemented.h"
+#include "base/numerics/safe_conversions.h"
 #include "base/types/expected.h"
 #include "base/unguessable_token.h"
 #include "components/unexportable_keys/background_task_priority.h"
@@ -21,6 +23,21 @@
 #include "mojo/public/cpp/bindings/pending_remote.h"
 
 namespace unexportable_keys {
+namespace {
+ServiceErrorOr<void> AdaptErrorOrVoid(
+    const std::optional<ServiceError> result) {
+  if (result.has_value()) {
+    return base::unexpected(*result);
+  } else {
+    return base::ok();
+  }
+}
+
+ServiceErrorOr<size_t> AdaptSizeType(ServiceErrorOr<uint64_t> result) {
+  return result.transform(
+      [](uint64_t r) { return base::checked_cast<size_t>(r); });
+}
+}  // namespace
 
 UnexportableKeyServiceProxied::CachedKeyData::CachedKeyData() = default;
 
@@ -123,7 +140,15 @@
     UnexportableKeyId key_id_from_other_service,
     BackgroundTaskPriority priority,
     base::OnceCallback<void(ServiceErrorOr<UnexportableKeyId>)> callback) {
-  NOTIMPLEMENTED();
+  ServiceErrorOr<std::vector<uint8_t>> wrapped_key =
+      other_service.GetWrappedKey(key_id_from_other_service);
+  if (!wrapped_key.has_value()) {
+    std::move(callback).Run(base::unexpected(wrapped_key.error()));
+    return;
+  }
+
+  // TODO(crbug.com/455538141): - Implement key copy in the task manager.
+  FromWrappedSigningKeySlowlyAsync(*wrapped_key, priority, std::move(callback));
 }
 
 void UnexportableKeyServiceProxied::SignSlowlyAsync(
@@ -166,13 +191,24 @@
     UnexportableKeyId key_id,
     BackgroundTaskPriority priority,
     base::OnceCallback<void(ServiceErrorOr<void>)> callback) {
-  NOTIMPLEMENTED();
+  if (!key_cache_.contains(key_id)) {
+    std::move(callback).Run(base::unexpected(ServiceError::kKeyNotFound));
+    return;
+  }
+  key_cache_.erase(key_id);
+
+  remote_->DeleteKey(
+      key_id, priority,
+      base::BindOnce(&AdaptErrorOrVoid).Then(std::move(callback)));
 }
 
 void UnexportableKeyServiceProxied::DeleteAllKeysSlowlyAsync(
     BackgroundTaskPriority priority,
     base::OnceCallback<void(ServiceErrorOr<size_t>)> callback) {
-  NOTIMPLEMENTED();
+  key_cache_.clear();
+
+  remote_->DeleteAllKeys(
+      priority, base::BindOnce(&AdaptSizeType).Then(std::move(callback)));
 }
 
 void UnexportableKeyServiceProxied::
@@ -180,7 +216,10 @@
         BackgroundTaskPriority priority,
         base::OnceCallback<void(ServiceErrorOr<std::vector<UnexportableKeyId>>)>
             callback) {
-  NOTIMPLEMENTED();
+  // remote_ will not call any pending callbacks after it is destroyed.
+  // Since we own remote_, it is guaranteed that this will be alive when a
+  // callback is called.
+  remote_->GetAllSigningKeysForGarbageCollection(priority, std::move(callback));
 }
 
 }  // namespace unexportable_keys
diff --git a/components/unexportable_keys/mojom/unexportable_key_service_proxied.h b/components/unexportable_keys/mojom/unexportable_key_service_proxied.h
index 658f6fd..db6556d 100644
--- a/components/unexportable_keys/mojom/unexportable_key_service_proxied.h
+++ b/components/unexportable_keys/mojom/unexportable_key_service_proxied.h
@@ -5,6 +5,11 @@
 #ifndef COMPONENTS_UNEXPORTABLE_KEYS_MOJOM_UNEXPORTABLE_KEY_SERVICE_PROXIED_H_
 #define COMPONENTS_UNEXPORTABLE_KEYS_MOJOM_UNEXPORTABLE_KEY_SERVICE_PROXIED_H_
 
+#include <cstddef>
+#include <cstdint>
+#include <optional>
+#include <vector>
+
 #include "components/unexportable_keys/background_task_priority.h"
 #include "components/unexportable_keys/mojom/unexportable_key_service.mojom.h"
 #include "components/unexportable_keys/ref_counted_unexportable_signing_key.h"
diff --git a/components/unexportable_keys/mojom/unexportable_key_service_proxied_unittest.cc b/components/unexportable_keys/mojom/unexportable_key_service_proxied_unittest.cc
index f308dc5..118bcd18 100644
--- a/components/unexportable_keys/mojom/unexportable_key_service_proxied_unittest.cc
+++ b/components/unexportable_keys/mojom/unexportable_key_service_proxied_unittest.cc
@@ -32,6 +32,8 @@
 using ::base::test::ErrorIs;
 using ::base::test::ValueIs;
 using ::testing::ElementsAreArray;
+using ::testing::IsEmpty;
+using ::testing::UnorderedElementsAreArray;
 
 namespace {
 
@@ -175,6 +177,16 @@
 
 class UnexportableKeyServiceProxiedTest : public ::testing::Test {
  protected:
+  UnexportableKeyId GenerateKeyOrDie() {
+    base::test::TestFuture<ServiceErrorOr<UnexportableKeyId>> future;
+    std::vector<crypto::SignatureVerifier::SignatureAlgorithm> algos = {
+        crypto::SignatureVerifier::SignatureAlgorithm::RSA_PKCS1_SHA256};
+    proxied_service_.GenerateSigningKeySlowlyAsync(
+        algos, BackgroundTaskPriority::kUserVisible, future.GetCallback());
+    const ServiceErrorOr<UnexportableKeyId>& result = future.Get();
+    return result.value();
+  }
+
   base::test::TaskEnvironment task_environment_;
   FakeUnexportableKeyServiceProxy fake_service_;
   mojo::Receiver<mojom::UnexportableKeyService> receiver_{&fake_service_};
@@ -384,6 +396,120 @@
   EXPECT_THAT(proxied_service_.GetAlgorithm(unknown_key_id),
               ErrorIs(ServiceError::kKeyNotFound));
 }
+TEST_F(UnexportableKeyServiceProxiedTest, DeleteKeySuccess) {
+  UnexportableKeyId key_id = GenerateKeyOrDie();
+  ASSERT_TRUE(proxied_service_.GetSubjectPublicKeyInfo(key_id).has_value());
+
+  // Empty optional returned in success.
+  fake_service_.SetDeleteKeyResponse(std::nullopt);
+
+  base::test::TestFuture<ServiceErrorOr<void>> delete_future;
+  proxied_service_.DeleteKeySlowlyAsync(key_id,
+                                        BackgroundTaskPriority::kUserVisible,
+                                        delete_future.GetCallback());
+
+  ASSERT_TRUE(delete_future.Get().has_value());
+  EXPECT_THAT(proxied_service_.GetSubjectPublicKeyInfo(key_id),
+              ErrorIs(ServiceError::kKeyNotFound));
+}
+
+TEST_F(UnexportableKeyServiceProxiedTest, DeleteKeyNotFoundInCache) {
+  UnexportableKeyId unknown_key_id(base::UnguessableToken::Create());
+
+  base::test::TestFuture<ServiceErrorOr<void>> delete_future;
+  proxied_service_.DeleteKeySlowlyAsync(unknown_key_id,
+                                        BackgroundTaskPriority::kUserVisible,
+                                        delete_future.GetCallback());
+
+  EXPECT_THAT(delete_future.Get(), ErrorIs(ServiceError::kKeyNotFound));
+}
+
+TEST_F(UnexportableKeyServiceProxiedTest, DeleteKeyErrorFromService) {
+  UnexportableKeyId key_id = GenerateKeyOrDie();
+  ASSERT_TRUE(proxied_service_.GetSubjectPublicKeyInfo(key_id).has_value());
+
+  fake_service_.SetDeleteKeyResponse(ServiceError::kCryptoApiFailed);
+
+  base::test::TestFuture<ServiceErrorOr<void>> delete_future;
+  proxied_service_.DeleteKeySlowlyAsync(key_id,
+                                        BackgroundTaskPriority::kUserVisible,
+                                        delete_future.GetCallback());
+
+  EXPECT_THAT(delete_future.Get(), ErrorIs(ServiceError::kCryptoApiFailed));
+  EXPECT_FALSE(proxied_service_.GetSubjectPublicKeyInfo(key_id).has_value());
+}
+
+TEST_F(UnexportableKeyServiceProxiedTest, DeleteAllKeysSuccess) {
+  UnexportableKeyId key_id1 = GenerateKeyOrDie();
+  UnexportableKeyId key_id2 = GenerateKeyOrDie();
+  ASSERT_TRUE(proxied_service_.GetSubjectPublicKeyInfo(key_id1).has_value());
+  ASSERT_TRUE(proxied_service_.GetSubjectPublicKeyInfo(key_id2).has_value());
+
+  fake_service_.SetDeleteAllKeysResponse(base::ok(2));
+
+  base::test::TestFuture<ServiceErrorOr<size_t>> delete_all_future;
+  proxied_service_.DeleteAllKeysSlowlyAsync(
+      BackgroundTaskPriority::kUserVisible, delete_all_future.GetCallback());
+
+  EXPECT_THAT(delete_all_future.Get(), ValueIs(2));
+  EXPECT_THAT(proxied_service_.GetSubjectPublicKeyInfo(key_id1),
+              ErrorIs(ServiceError::kKeyNotFound));
+  EXPECT_THAT(proxied_service_.GetSubjectPublicKeyInfo(key_id2),
+              ErrorIs(ServiceError::kKeyNotFound));
+}
+
+TEST_F(UnexportableKeyServiceProxiedTest, DeleteAllKeysErrorFromService) {
+  fake_service_.SetDeleteAllKeysResponse(
+      base::unexpected(ServiceError::kCryptoApiFailed));
+
+  const UnexportableKeyId key_id = GenerateKeyOrDie();
+
+  base::test::TestFuture<ServiceErrorOr<size_t>> delete_all_future;
+  proxied_service_.DeleteAllKeysSlowlyAsync(
+      BackgroundTaskPriority::kUserVisible, delete_all_future.GetCallback());
+
+  EXPECT_THAT(delete_all_future.Get(), ErrorIs(ServiceError::kCryptoApiFailed));
+  EXPECT_FALSE(proxied_service_.GetSubjectPublicKeyInfo(key_id).has_value());
+}
+
+TEST_F(UnexportableKeyServiceProxiedTest,
+       GetAllSigningKeysForGarbageCollectionSuccess) {
+  std::vector<UnexportableKeyId> key_ids = {
+      UnexportableKeyId(base::UnguessableToken::Create()),
+      UnexportableKeyId(base::UnguessableToken::Create())};
+  fake_service_.SetGetAllSigningKeysForGarbageCollectionResponse(
+      base::ok(key_ids));
+
+  base::test::TestFuture<ServiceErrorOr<std::vector<UnexportableKeyId>>> future;
+  proxied_service_.GetAllSigningKeysForGarbageCollectionSlowlyAsync(
+      BackgroundTaskPriority::kUserVisible, future.GetCallback());
+
+  EXPECT_THAT(future.Get(), ValueIs(UnorderedElementsAreArray(key_ids)));
+}
+
+TEST_F(UnexportableKeyServiceProxiedTest,
+       GetAllSigningKeysForGarbageCollectionEmpty) {
+  fake_service_.SetGetAllSigningKeysForGarbageCollectionResponse(
+      base::ok(std::vector<UnexportableKeyId>()));
+
+  base::test::TestFuture<ServiceErrorOr<std::vector<UnexportableKeyId>>> future;
+  proxied_service_.GetAllSigningKeysForGarbageCollectionSlowlyAsync(
+      BackgroundTaskPriority::kUserVisible, future.GetCallback());
+
+  EXPECT_THAT(future.Get(), ValueIs(IsEmpty()));
+}
+
+TEST_F(UnexportableKeyServiceProxiedTest,
+       GetAllSigningKeysForGarbageCollectionError) {
+  fake_service_.SetGetAllSigningKeysForGarbageCollectionResponse(
+      base::unexpected(ServiceError::kCryptoApiFailed));
+
+  base::test::TestFuture<ServiceErrorOr<std::vector<UnexportableKeyId>>> future;
+  proxied_service_.GetAllSigningKeysForGarbageCollectionSlowlyAsync(
+      BackgroundTaskPriority::kUserVisible, future.GetCallback());
+
+  EXPECT_THAT(future.Get(), ErrorIs(ServiceError::kCryptoApiFailed));
+}
 
 }  // namespace
 }  // namespace unexportable_keys
diff --git a/components/viz/common/features.cc b/components/viz/common/features.cc
index 230ac39e6c1..100359f 100644
--- a/components/viz/common/features.cc
+++ b/components/viz/common/features.cc
@@ -272,14 +272,6 @@
     &kEnableADPFScrollBoost, "adpf_boost_mode_timeout",
     base::Milliseconds(200)};
 
-// If enabled, Chrome's ADPF(Android Dynamic Performance Framework) hint
-// session includes Renderer threads only if:
-// - The Renderer is handling an interacton
-// - The Renderer is the main frame's Renderer, and there no Renderers handling
-//   an interaction.
-BASE_FEATURE(kEnableInteractiveOnlyADPFRenderer,
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
 // If enabled, Chrome puts Renderer Main threads into a separate
 // ADPF(Android Dynamic Performance Framework) hint session, and does not
 // report any timing hints from this session.
diff --git a/components/viz/common/features.h b/components/viz/common/features.h
index b47b1b8..3ddad537 100644
--- a/components/viz/common/features.h
+++ b/components/viz/common/features.h
@@ -117,7 +117,6 @@
     kADPFBoostTimeout;
 VIZ_COMMON_EXPORT extern const base::FeatureParam<double>
     kADPFMidFrameBoostDurationMultiplier;
-VIZ_COMMON_EXPORT BASE_DECLARE_FEATURE(kEnableInteractiveOnlyADPFRenderer);
 VIZ_COMMON_EXPORT BASE_DECLARE_FEATURE(kEnableADPFSeparateRendererMainSession);
 VIZ_COMMON_EXPORT BASE_DECLARE_FEATURE(kEnableADPFSetThreads);
 VIZ_COMMON_EXPORT BASE_DECLARE_FEATURE(kEnableADPFWorkloadIncreaseOnPageLoad);
diff --git a/components/viz/service/display/display.cc b/components/viz/service/display/display.cc
index 5490c97..633304dd 100644
--- a/components/viz/service/display/display.cc
+++ b/components/viz/service/display/display.cc
@@ -1110,18 +1110,13 @@
     const auto& main_surfaces =
         surface_manager_->GetSurfacesReferencedByParent(current_surface_id_);
 
-    const bool interactive_only_adpf_renderer = base::FeatureList::IsEnabled(
-        features::kEnableInteractiveOnlyADPFRenderer);
     bool has_interactive_surface = false;
-    if (interactive_only_adpf_renderer) {
-      for (const auto& surface_id :
-           aggregator_->previous_contained_surfaces()) {
-        surface = surface_manager_->GetSurfaceForId(surface_id);
-        if (surface && surface->HasActiveFrame() &&
-            surface->GetActiveFrameMetadata().is_handling_interaction) {
-          has_interactive_surface = true;
-          break;
-        }
+    for (const auto& surface_id : aggregator_->previous_contained_surfaces()) {
+      surface = surface_manager_->GetSurfaceForId(surface_id);
+      if (surface && surface->HasActiveFrame() &&
+          surface->GetActiveFrameMetadata().is_handling_interaction) {
+        has_interactive_surface = true;
+        break;
       }
     }
 
@@ -1137,8 +1132,7 @@
         const bool is_for_main_frame =
             surface_id == current_surface_id_ ||
             main_surfaces.find(surface_id) != main_surfaces.end();
-        if (interactive_only_adpf_renderer &&
-            surface_id != current_surface_id_) {
+        if (surface_id != current_surface_id_) {
           const bool is_handling_interaction =
               surface->HasActiveFrame() &&
               surface->GetActiveFrameMetadata().is_handling_interaction;
diff --git a/components/viz/service/gl/gpu_service_impl.cc b/components/viz/service/gl/gpu_service_impl.cc
index 2116683..413f1253 100644
--- a/components/viz/service/gl/gpu_service_impl.cc
+++ b/components/viz/service/gl/gpu_service_impl.cc
@@ -235,9 +235,9 @@
             gpu::GetDefaultGpuDiskCacheSize();
         caching_interface = dawn_caching_interface_factory_->CreateInstance(
             gpu::kGraphiteDawnGpuDiskCacheHandle,
-            std::make_unique<gpu::GpuPersistentCache>("GraphiteDawn",
-                                                      std::move(memory_cache),
-                                                      std::move(async_opts)));
+            base::MakeRefCounted<gpu::GpuPersistentCache>(
+                "GraphiteDawn", std::move(memory_cache),
+                std::move(async_opts)));
       } else {
         auto cache_blob_callback = base::BindRepeating(
             [](GpuServiceImpl* self, const std::string& key,
diff --git a/components/wallet/core/browser/BUILD.gn b/components/wallet/core/browser/BUILD.gn
index cf6ac89c..972f4b3 100644
--- a/components/wallet/core/browser/BUILD.gn
+++ b/components/wallet/core/browser/BUILD.gn
@@ -6,6 +6,8 @@
 
 static_library("browser") {
   sources = [
+    "data_models/boarding_pass.cc",
+    "data_models/boarding_pass.h",
     "data_models/country_type.h",
     "data_models/wallet_barcode.h",
     "data_models/walletable_pass.cc",
@@ -49,6 +51,7 @@
 source_set("unit_tests") {
   testonly = true
   sources = [
+    "data_models/boarding_pass_unittest.cc",
     "data_models/walletable_pass_unittest.cc",
     "walletable_pass_service_unittest.cc",
     "walletable_permission_utils_unittest.cc",
diff --git a/components/wallet/core/browser/data_models/boarding_pass.cc b/components/wallet/core/browser/data_models/boarding_pass.cc
new file mode 100644
index 0000000..4856d91
--- /dev/null
+++ b/components/wallet/core/browser/data_models/boarding_pass.cc
@@ -0,0 +1,143 @@
+// 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/wallet/core/browser/data_models/boarding_pass.h"
+
+#include <string_view>
+
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "components/wallet/core/browser/data_models/wallet_barcode.h"
+
+namespace wallet {
+
+namespace {
+
+// Encapsulates a string value and provides safe sequential access to it.
+// Ensures that no access is made past the string buffer.
+class StringStream {
+ public:
+  explicit StringStream(std::string_view value) : value_(value) {}
+
+  // Returns 'count' chars from the stream.
+  std::string_view Get(int count) {
+    if (count < 0) {
+      error_ = true;
+      return std::string_view();
+    }
+
+    if (position_ + count > value_.size()) {
+      error_ = true;
+      return std::string_view();
+    }
+    std::string_view result = value_.substr(position_, count);
+    position_ += count;
+    return result;
+  }
+
+  // Returns 'count' chars from the stream, trimming whitespace.
+  std::string GetStripped(int count) {
+    std::string result;
+    base::TrimWhitespaceASCII(Get(count), base::TRIM_ALL, &result);
+    return result;
+  }
+
+  // Returns 'count' chars from the stream, converted to int.
+  // Returns -1 on error.
+  int GetInt(int count) {
+    std::string input = GetStripped(count);
+    if (error_) {
+      return -1;
+    }
+    int result = -1;
+    if (!base::StringToInt(input, &result)) {
+      error_ = true;
+      return -1;
+    }
+    return result;
+  }
+
+  // Skips 'count' chars. Skipping past the value size is not an error on its
+  // own, but the subsequent Get() will fail.
+  void Skip(int count) {
+    if (count < 0) {
+      error_ = true;
+      return;
+    }
+    position_ += count;
+  }
+
+  bool HasError() const { return error_; }
+
+ private:
+  const std::string_view value_;
+  size_t position_ = 0;
+  bool error_ = false;
+};
+
+// See
+// https://tinkrmind.files.wordpress.com/2017/09/bcbp-implementation-guide-5th-edition-june-2016.pdf
+// for more information on boarding pass fields spec.
+constexpr int kFormatIndicatorLength = 1;
+constexpr int kLegsCountLength = 1;
+constexpr int kNameLength = 20;
+constexpr int kElectronicTicketIndicatorLength = 1;
+constexpr int kPnrCodeLength = 7;
+constexpr int kOriginLength = 3;
+constexpr int kDestinationLength = 3;
+constexpr int kAirlineLength = 3;
+constexpr int kFlightCodeLength = 5;
+constexpr int kDateLength = 3;
+
+}  // namespace
+
+// static
+std::optional<BoardingPass> BoardingPass::FromBarcode(
+    const WalletBarcode& barcode) {
+  StringStream value(barcode.raw_value);
+
+  // Field 1: Format Indicator (Length 1)
+  if (value.Get(kFormatIndicatorLength) != "M") {
+    return std::nullopt;
+  }
+
+  // Field 2: Number of Flight Legs (Length 1)
+  const int legs_count = value.GetInt(kLegsCountLength);
+  // The IATA spec allows 1-9 legs, but we restrict to 4 to align with
+  // legacy ticketing (max 4 coupons/ticket). More legs usually require
+  // multiple boarding passes.
+  if (value.HasError() || legs_count < 1 || legs_count > 4) {
+    return std::nullopt;
+  }
+
+  // Field 3: Passenger Name (Length 20) - Skipping.
+  // Field 4: Electronic Ticket Indicator (Length 1) - Skipping.
+  value.Skip(kNameLength + kElectronicTicketIndicatorLength);
+
+  BoardingPass pass;
+  // First Leg Data:
+  // Field 5: PNR Code (Length 7) - Skipping.
+  value.Skip(kPnrCodeLength);
+  pass.origin = value.GetStripped(kOriginLength);
+  pass.destination = value.GetStripped(kDestinationLength);
+  pass.airline = value.GetStripped(kAirlineLength);
+  pass.flight_code = value.GetStripped(kFlightCodeLength);
+  pass.date = value.GetStripped(kDateLength);
+  pass.barcode = barcode;
+
+  if (value.HasError()) {
+    return std::nullopt;
+  }
+
+  return pass;
+}
+
+BoardingPass::BoardingPass() = default;
+BoardingPass::BoardingPass(const BoardingPass&) = default;
+BoardingPass& BoardingPass::operator=(const BoardingPass&) = default;
+BoardingPass::BoardingPass(BoardingPass&&) = default;
+BoardingPass& BoardingPass::operator=(BoardingPass&&) = default;
+BoardingPass::~BoardingPass() = default;
+
+}  // namespace wallet
diff --git a/components/wallet/core/browser/data_models/boarding_pass.h b/components/wallet/core/browser/data_models/boarding_pass.h
new file mode 100644
index 0000000..ccc9d10
--- /dev/null
+++ b/components/wallet/core/browser/data_models/boarding_pass.h
@@ -0,0 +1,41 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_WALLET_CORE_BROWSER_DATA_MODELS_BOARDING_PASS_H_
+#define COMPONENTS_WALLET_CORE_BROWSER_DATA_MODELS_BOARDING_PASS_H_
+
+#include <optional>
+#include <string>
+
+#include "components/wallet/core/browser/data_models/wallet_barcode.h"
+
+namespace wallet {
+
+// Represents a simplified boarding pass.
+struct BoardingPass {
+  // Parses a BCBP barcode string into a BoardingPass object.
+  static std::optional<BoardingPass> FromBarcode(const WalletBarcode& barcode);
+
+  BoardingPass();
+  BoardingPass(const BoardingPass&);
+  BoardingPass& operator=(const BoardingPass&);
+  BoardingPass(BoardingPass&&);
+  BoardingPass& operator=(BoardingPass&&);
+  ~BoardingPass();
+
+  bool operator==(const BoardingPass& other) const = default;
+
+  std::string airline;
+  std::string flight_code;
+  std::string origin;
+  std::string destination;
+  std::string date;
+
+  // The detected barcode.
+  std::optional<WalletBarcode> barcode;
+};
+
+}  // namespace wallet
+
+#endif  // COMPONENTS_WALLET_CORE_BROWSER_DATA_MODELS_BOARDING_PASS_H_
diff --git a/components/wallet/core/browser/data_models/boarding_pass_unittest.cc b/components/wallet/core/browser/data_models/boarding_pass_unittest.cc
new file mode 100644
index 0000000..f8e83ff
--- /dev/null
+++ b/components/wallet/core/browser/data_models/boarding_pass_unittest.cc
@@ -0,0 +1,99 @@
+// 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/wallet/core/browser/data_models/boarding_pass.h"
+
+#include <string_view>
+
+#include "components/wallet/core/browser/data_models/wallet_barcode.h"
+#include "components/wallet/core/browser/data_models/walletable_pass.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace wallet {
+
+namespace {
+
+struct ExpectedBoardingPass {
+  std::string origin;
+  std::string destination;
+  std::string airline;
+  std::string flight_code;
+  std::string date;
+};
+
+void TestValue(std::string_view raw_value,
+               const ExpectedBoardingPass& expected) {
+  WalletBarcode barcode;
+  barcode.raw_value = std::string(raw_value);
+  barcode.format = WalletBarcodeFormat::PDF417;
+
+  std::optional<BoardingPass> result = BoardingPass::FromBarcode(barcode);
+
+  ASSERT_TRUE(result.has_value()) << "Failed to parse: " << raw_value;
+  EXPECT_EQ(result->origin, expected.origin);
+  EXPECT_EQ(result->destination, expected.destination);
+  EXPECT_EQ(result->airline, expected.airline);
+  EXPECT_EQ(result->flight_code, expected.flight_code);
+  EXPECT_EQ(result->date, expected.date);
+}
+
+void TestFailure(std::string_view raw_value) {
+  WalletBarcode barcode;
+  barcode.raw_value = std::string(raw_value);
+  barcode.format = WalletBarcodeFormat::PDF417;
+
+  std::optional<BoardingPass> result = BoardingPass::FromBarcode(barcode);
+  EXPECT_FALSE(result.has_value()) << "Should fail to parse: " << raw_value;
+}
+
+}  // namespace
+
+TEST(BoardingPassTest, ParseBoardingPass_ValidBCBP) {
+  TestValue("M1PASSENGER NAME      EABCDEFGSFOJFKUA 1234 123Y12A 00001100",
+            ExpectedBoardingPass{.origin = "SFO",
+                                 .destination = "JFK",
+                                 .airline = "UA",
+                                 .flight_code = "1234",
+                                 .date = "123"});
+}
+
+TEST(BoardingPassTest, ParseBoardingPass_InvalidBCBP) {
+  TestFailure("InvalidBarcode");
+}
+
+TEST(BoardingPassTest, ParseEmpty) {
+  TestFailure("");
+}
+
+TEST(BoardingPassTest, ParseBadMagicInitial) {
+  TestFailure("X1SCHUMANN/CLARA      EX37469 NUEAYTXQ 0167 118Y006D0010 33a");
+}
+
+TEST(BoardingPassTest, ParseBadLegCount) {
+  // BCBP requires at least 1 leg.
+  TestFailure("M0SCHUMANN/CLARA      EX37469 NUEAYTXQ 0167 118Y006D0010 33a");
+}
+
+TEST(BoardingPassTest, ParseInvalidLegCountFormat) {
+  // Legs count must be numeric.
+  TestFailure("MASCHUMANN/CLARA      EX37469 NUEAYTXQ 0167 118Y006D0010 33a");
+}
+
+TEST(BoardingPassTest, ParseTooShort) {
+  TestFailure("M1SOME MORE STUFF BUT NOT ENOUGH");
+}
+
+TEST(BoardingPassTest, TestGenericTwoLegs) {
+  // Only the first leg is parsed.
+  TestValue(
+      "M2MOZART/WOLFGANG AMADE4CWX3W PPTCDGAF 0077 137Y022J0048 3004CWX3W "
+      "CDGTLSAF 7788 138Y001A0001 300",
+      ExpectedBoardingPass{.origin = "PPT",
+                           .destination = "CDG",
+                           .airline = "AF",
+                           .flight_code = "0077",
+                           .date = "137"});
+}
+
+}  // namespace wallet
diff --git a/components/wallet/core/browser/data_models/walletable_pass.cc b/components/wallet/core/browser/data_models/walletable_pass.cc
index 49750dd..0adc591 100644
--- a/components/wallet/core/browser/data_models/walletable_pass.cc
+++ b/components/wallet/core/browser/data_models/walletable_pass.cc
@@ -4,10 +4,27 @@
 
 #include "components/wallet/core/browser/data_models/walletable_pass.h"
 
+#include "base/notreached.h"
 #include "components/optimization_guide/proto/features/walletable_pass_extraction.pb.h"
 
 namespace wallet {
 
+std::string PassCategoryToString(PassCategory category) {
+  switch (category) {
+    case PassCategory::kLoyaltyCard:
+      return "LoyaltyCard";
+    case PassCategory::kEventPass:
+      return "EventPass";
+    case PassCategory::kTransitTicket:
+      return "TransitTicket";
+    case PassCategory::kBoardingPass:
+      return "BoardingPass";
+    case PassCategory::kUnspecified:
+      return "Unspecified";
+  }
+  NOTREACHED();
+}
+
 // static
 LoyaltyCard LoyaltyCard::FromProto(
     const optimization_guide::proto::LoyaltyCard& proto,
@@ -88,19 +105,6 @@
 TransitTicket& TransitTicket::operator=(TransitTicket&&) = default;
 TransitTicket::~TransitTicket() = default;
 
-// static
-std::optional<BoardingPass> BoardingPass::FromBCBP(
-    const WalletBarcode& barcode) {
-  // TODO(crbug.com/463515055): Decode BCBP barcode to boarding pass.
-  return std::nullopt;
-}
-
-BoardingPass::BoardingPass() = default;
-BoardingPass::BoardingPass(const BoardingPass&) = default;
-BoardingPass& BoardingPass::operator=(const BoardingPass&) = default;
-BoardingPass::BoardingPass(BoardingPass&&) = default;
-BoardingPass& BoardingPass::operator=(BoardingPass&&) = default;
-BoardingPass::~BoardingPass() = default;
 
 // static
 std::optional<WalletablePass> WalletablePass::FromProto(
@@ -131,7 +135,8 @@
 // static
 std::optional<WalletablePass> WalletablePass::CreateBoardingPass(
     const WalletBarcode& barcode) {
-  std::optional<BoardingPass> boarding_pass = BoardingPass::FromBCBP(barcode);
+  std::optional<BoardingPass> boarding_pass =
+      BoardingPass::FromBarcode(barcode);
   if (boarding_pass) {
     WalletablePass pass;
     pass.pass_data = std::move(*boarding_pass);
diff --git a/components/wallet/core/browser/data_models/walletable_pass.h b/components/wallet/core/browser/data_models/walletable_pass.h
index 9e1dee4..716480e 100644
--- a/components/wallet/core/browser/data_models/walletable_pass.h
+++ b/components/wallet/core/browser/data_models/walletable_pass.h
@@ -9,6 +9,7 @@
 #include <string>
 #include <variant>
 
+#include "components/wallet/core/browser/data_models/boarding_pass.h"
 #include "components/wallet/core/browser/data_models/wallet_barcode.h"
 
 namespace optimization_guide::proto {
@@ -20,6 +21,19 @@
 
 namespace wallet {
 
+// Represents the category of a walletable pass.
+enum class PassCategory {
+  kUnspecified = 0,
+  kLoyaltyCard = 1,
+  kEventPass = 2,
+  kTransitTicket = 3,
+  kBoardingPass = 4,
+  kMaxValue = kBoardingPass,
+};
+
+// Returns the string name of the pass category.
+std::string PassCategoryToString(PassCategory category);
+
 // Represents a loyalty card with its relevant details.
 struct LoyaltyCard {
   static LoyaltyCard FromProto(
@@ -74,29 +88,6 @@
   std::optional<WalletBarcode> barcode;
 };
 
-// Represents a simplified boarding pass.
-struct BoardingPass {
-  static std::optional<BoardingPass> FromBCBP(const WalletBarcode& barcode);
-
-  BoardingPass();
-  BoardingPass(const BoardingPass&);
-  BoardingPass& operator=(const BoardingPass&);
-  BoardingPass(BoardingPass&&);
-  BoardingPass& operator=(BoardingPass&&);
-  ~BoardingPass();
-
-  bool operator==(const BoardingPass& other) const = default;
-
-  std::string airline;
-  std::string flight_code;
-  std::string origin;
-  std::string destination;
-  std::string date;
-
-  // The detected barcode.
-  std::optional<WalletBarcode> barcode;
-};
-
 // Represents a transit ticket with its relevant details.
 struct TransitTicket {
   static TransitTicket FromProto(
diff --git a/components/wallet/core/browser/data_models/walletable_pass_unittest.cc b/components/wallet/core/browser/data_models/walletable_pass_unittest.cc
index 1b0dad1..87f9a92e 100644
--- a/components/wallet/core/browser/data_models/walletable_pass_unittest.cc
+++ b/components/wallet/core/browser/data_models/walletable_pass_unittest.cc
@@ -90,4 +90,13 @@
   EXPECT_FALSE(result.has_value());
 }
 
+TEST(WalletablePassTest, PassCategoryToString) {
+  EXPECT_EQ(PassCategoryToString(PassCategory::kLoyaltyCard), "LoyaltyCard");
+  EXPECT_EQ(PassCategoryToString(PassCategory::kEventPass), "EventPass");
+  EXPECT_EQ(PassCategoryToString(PassCategory::kTransitTicket),
+            "TransitTicket");
+  EXPECT_EQ(PassCategoryToString(PassCategory::kBoardingPass), "BoardingPass");
+  EXPECT_EQ(PassCategoryToString(PassCategory::kUnspecified), "Unspecified");
+}
+
 }  // namespace wallet
diff --git a/components/wallet/core/browser/walletable_pass_client.h b/components/wallet/core/browser/walletable_pass_client.h
index 9b32e51..8f9f03b 100644
--- a/components/wallet/core/browser/walletable_pass_client.h
+++ b/components/wallet/core/browser/walletable_pass_client.h
@@ -69,7 +69,7 @@
   virtual GeoIpCountryCode GetGeoIpCountryCode() = 0;
 
   virtual void ShowWalletablePassConsentBubble(
-      optimization_guide::proto::PassCategory pass_category,
+      PassCategory pass_category,
       WalletablePassBubbleResultCallback callback) = 0;
 
   virtual void ShowWalletablePassSaveBubble(
diff --git a/components/wallet/core/browser/walletable_pass_ingestion_controller.cc b/components/wallet/core/browser/walletable_pass_ingestion_controller.cc
index 41298fd2..8b560dca 100644
--- a/components/wallet/core/browser/walletable_pass_ingestion_controller.cc
+++ b/components/wallet/core/browser/walletable_pass_ingestion_controller.cc
@@ -19,41 +19,34 @@
 namespace wallet {
 namespace {
 
-using optimization_guide::proto::PassCategory;
 using enum WalletablePassClient::WalletablePassBubbleResult;
-using enum optimization_guide::proto::PassCategory;
 
 PassCategory GetPassCategory(const WalletablePass& walletable_pass) {
   return std::visit(
       absl::Overload(
-          [](const LoyaltyCard&) { return PASS_CATEGORY_LOYALTY_CARD; },
-          [](const EventPass&) { return PASS_CATEGORY_EVENT_PASS; },
-          [](const TransitTicket&) { return PASS_CATEGORY_TRANSIT_TICKET; },
-          [](const BoardingPass&) {
-            // TODO(crbug.com/463515055): Create enum for boarding pass.
-            return PASS_CATEGORY_UNSPECIFIED;
-          }),
+          [](const LoyaltyCard&) { return PassCategory::kLoyaltyCard; },
+          [](const EventPass&) { return PassCategory::kEventPass; },
+          [](const TransitTicket&) { return PassCategory::kTransitTicket; },
+          [](const BoardingPass&) { return PassCategory::kBoardingPass; }),
       walletable_pass.pass_data);
 }
 
-std::string GetPassCategoryString(PassCategory pass_category) {
+optimization_guide::proto::PassCategory ToProtoPassCategory(
+    PassCategory pass_category) {
   switch (pass_category) {
-    case PASS_CATEGORY_LOYALTY_CARD:
-      return "LoyaltyCard";
-    case PASS_CATEGORY_EVENT_PASS:
-      return "EventPass";
-    case PASS_CATEGORY_TRANSIT_TICKET:
-      return "TransitTicket";
-    case PASS_CATEGORY_UNSPECIFIED:
-    default:
-      NOTREACHED();
+    case PassCategory::kLoyaltyCard:
+      return optimization_guide::proto::PASS_CATEGORY_LOYALTY_CARD;
+    case PassCategory::kEventPass:
+      return optimization_guide::proto::PASS_CATEGORY_EVENT_PASS;
+    case PassCategory::kTransitTicket:
+      return optimization_guide::proto::PASS_CATEGORY_TRANSIT_TICKET;
+    // Boarding pass is not supported by optimization_guide proto.
+    case PassCategory::kBoardingPass:
+    case PassCategory::kUnspecified:
+      return optimization_guide::proto::PASS_CATEGORY_UNSPECIFIED;
   }
 }
 
-std::string GetPassCategoryString(const WalletablePass& walletable_pass) {
-  return GetPassCategoryString(GetPassCategory(walletable_pass));
-}
-
 }  // namespace
 
 WalletablePassIngestionController::WalletablePassIngestionController(
@@ -109,7 +102,7 @@
               WALLETABLE_PASS_DETECTION_LOYALTY_ALLOWLIST,
           /*optimization_metadata=*/nullptr) ==
       optimization_guide::OptimizationGuideDecision::kTrue) {
-    return PASS_CATEGORY_LOYALTY_CARD;
+    return PassCategory::kLoyaltyCard;
   }
 
   // TODO(crbug.com/455680372): Check more allowlists.
@@ -137,6 +130,7 @@
     case kAccepted:
       SetWalletablePassDetectionOptInStatus(client_->GetPrefService(),
                                             client_->GetIdentityManager(),
+                                            client_->GetGeoIpCountryCode(),
                                             /*opt_in_status=*/true);
       consent_strike_db_->ClearStrikes();
       MaybeStartExtraction(url, pass_category);
@@ -162,7 +156,7 @@
     PassCategory pass_category) {
   if (save_strike_db_->ShouldBlockFeature(
           WalletablePassSaveStrikeDatabaseByHost::GetId(
-              GetPassCategoryString(pass_category), url.GetHost()))) {
+              PassCategoryToString(pass_category), url.GetHost()))) {
     // TODO(crbug.com/452779539): Report save bubble blocked to UMA
     return;
   }
@@ -191,7 +185,7 @@
     optimization_guide::proto::AnnotatedPageContent annotated_page_content) {
   // Construct request
   optimization_guide::proto::WalletablePassExtractionRequest request;
-  request.set_pass_category(pass_category);
+  request.set_pass_category(ToProtoPassCategory(pass_category));
   request.mutable_page_context()->set_url(url.spec());
   request.mutable_page_context()->set_title(GetPageTitle());
   *request.mutable_page_context()->mutable_annotated_page_content() =
@@ -247,7 +241,8 @@
 void WalletablePassIngestionController::ShowSaveBubble(
     const GURL& url,
     WalletablePass walletable_pass) {
-  const std::string category = GetPassCategoryString(walletable_pass);
+  const std::string category =
+      PassCategoryToString(GetPassCategory(walletable_pass));
 
   // Create a copy of walletable_pass for the callback to avoid use-after-move.
   WalletablePass walletable_pass_for_callback = walletable_pass;
@@ -263,7 +258,8 @@
     const GURL& url,
     WalletablePass walletable_pass,
     WalletablePassClient::WalletablePassBubbleResult result) {
-  const std::string category = GetPassCategoryString(walletable_pass);
+  const std::string category =
+      PassCategoryToString(GetPassCategory(walletable_pass));
   switch (result) {
     case kAccepted:
       // TODO(crbug.com/452579752): Save pass to Wallet.
diff --git a/components/wallet/core/browser/walletable_pass_ingestion_controller.h b/components/wallet/core/browser/walletable_pass_ingestion_controller.h
index d9cb397..47e8e16 100644
--- a/components/wallet/core/browser/walletable_pass_ingestion_controller.h
+++ b/components/wallet/core/browser/walletable_pass_ingestion_controller.h
@@ -60,8 +60,7 @@
   //
   // Returns the matching PassCategory if found, or std::nullopt if the `url`
   // is not in any pass allowlist.
-  std::optional<optimization_guide::proto::PassCategory> GetPassCategoryForURL(
-      const GURL& url) const;
+  std::optional<PassCategory> GetPassCategoryForURL(const GURL& url) const;
 
   // Gets the title of current page.
   virtual std::string GetPageTitle() const = 0;
@@ -75,13 +74,12 @@
   // invokes the Optimization Guide's model executor to perform the extraction.
   void ExtractWalletablePass(
       const GURL& url,
-      optimization_guide::proto::PassCategory pass_category,
+      PassCategory pass_category,
       optimization_guide::proto::AnnotatedPageContent annotated_page_content);
 
   // Shows the "Consent" bubble to the user, allowing them to agree to use the
   // feature.
-  void ShowConsentBubble(const GURL& url,
-                         optimization_guide::proto::PassCategory pass_category);
+  void ShowConsentBubble(const GURL& url, PassCategory pass_category);
 
   // Shows the "Save" bubble to the user, allowing them to save the provided
   // pass.
@@ -89,14 +87,12 @@
 
  private:
   friend class WalletablePassIngestionControllerTestApi;
-  void MaybeStartExtraction(
-      const GURL& url,
-      optimization_guide::proto::PassCategory pass_category);
+  void MaybeStartExtraction(const GURL& url, PassCategory pass_category);
 
   // Callback for when the annotated page content is available.
   void OnGetAnnotatedPageContent(
       const GURL& url,
-      optimization_guide::proto::PassCategory pass_category,
+      PassCategory pass_category,
       std::optional<optimization_guide::proto::AnnotatedPageContent>
           annotated_page_content);
 
@@ -110,7 +106,7 @@
   // accepts, declines, or dismisses).
   void OnGetConsentBubbleResult(
       const GURL& url,
-      optimization_guide::proto::PassCategory pass_category,
+      PassCategory pass_category,
       WalletablePassClient::WalletablePassBubbleResult result);
 
   // Callback invoked when the user interacts with the save bubble (e.g.,
diff --git a/components/wallet/core/browser/walletable_pass_ingestion_controller_test_api.h b/components/wallet/core/browser/walletable_pass_ingestion_controller_test_api.h
index 1af13de..c55c20b 100644
--- a/components/wallet/core/browser/walletable_pass_ingestion_controller_test_api.h
+++ b/components/wallet/core/browser/walletable_pass_ingestion_controller_test_api.h
@@ -25,14 +25,13 @@
       WalletablePassIngestionController* controller)
       : controller_(CHECK_DEREF(controller)) {}
 
-  std::optional<optimization_guide::proto::PassCategory> GetPassCategoryForURL(
-      const GURL& url) {
+  std::optional<PassCategory> GetPassCategoryForURL(const GURL& url) {
     return controller_->GetPassCategoryForURL(url);
   }
 
   void ExtractWalletablePass(
       const GURL& url,
-      optimization_guide::proto::PassCategory pass_category,
+      PassCategory pass_category,
       const optimization_guide::proto::AnnotatedPageContent&
           annotated_page_content) {
     controller_->ExtractWalletablePass(url, pass_category,
@@ -43,9 +42,7 @@
     controller_->StartWalletablePassDetectionFlow(url);
   }
 
-  void ShowConsentBubble(
-      const GURL& url,
-      optimization_guide::proto::PassCategory pass_category) {
+  void ShowConsentBubble(const GURL& url, PassCategory pass_category) {
     controller_->ShowConsentBubble(url, pass_category);
   }
 
@@ -53,9 +50,7 @@
     controller_->ShowSaveBubble(url, std::move(walletable_pass));
   }
 
-  void MaybeStartExtraction(
-      const GURL& url,
-      optimization_guide::proto::PassCategory pass_category) {
+  void MaybeStartExtraction(const GURL& url, PassCategory pass_category) {
     controller_->MaybeStartExtraction(url, pass_category);
   }
 
diff --git a/components/wallet/core/browser/walletable_pass_ingestion_controller_unittest.cc b/components/wallet/core/browser/walletable_pass_ingestion_controller_unittest.cc
index 0e2039ac..76764f4 100644
--- a/components/wallet/core/browser/walletable_pass_ingestion_controller_unittest.cc
+++ b/components/wallet/core/browser/walletable_pass_ingestion_controller_unittest.cc
@@ -35,7 +35,6 @@
 using testing::Eq;
 using testing::Return;
 using testing::WithArgs;
-using enum optimization_guide::proto::PassCategory;
 
 namespace wallet {
 namespace {
@@ -53,7 +52,7 @@
   MOCK_METHOD(
       void,
       ShowWalletablePassConsentBubble,
-      (optimization_guide::proto::PassCategory pass_category,
+      (PassCategory pass_category,
        WalletablePassClient::WalletablePassBubbleResultCallback callback),
       (override));
   MOCK_METHOD(
@@ -155,7 +154,7 @@
   }
 
   void ExpectConsentBubbleOnClient(
-      optimization_guide::proto::PassCategory expected_category,
+      PassCategory expected_category,
       WalletablePassClient::WalletablePassBubbleResultCallback* out_callback) {
     EXPECT_CALL(mock_client(),
                 ShowWalletablePassConsentBubble(Eq(expected_category), _))
@@ -201,7 +200,7 @@
       .WillOnce(Return(kTrue));
 
   EXPECT_EQ(test_api(controller()).GetPassCategoryForURL(https_url),
-            PASS_CATEGORY_LOYALTY_CARD);
+            PassCategory::kLoyaltyCard);
 }
 
 TEST_F(WalletablePassIngestionControllerTest,
@@ -224,7 +223,8 @@
   content.set_tab_id(123);
 
   optimization_guide::proto::WalletablePassExtractionRequest expected_request;
-  expected_request.set_pass_category(PASS_CATEGORY_LOYALTY_CARD);
+  expected_request.set_pass_category(
+      optimization_guide::proto::PASS_CATEGORY_LOYALTY_CARD);
   expected_request.mutable_page_context()->set_url(url.spec());
   expected_request.mutable_page_context()->set_title("title");
   *expected_request.mutable_page_context()->mutable_annotated_page_content() =
@@ -236,7 +236,7 @@
                            EqualsProto(expected_request), _, _));
 
   test_api(controller())
-      .ExtractWalletablePass(url, PASS_CATEGORY_LOYALTY_CARD, content);
+      .ExtractWalletablePass(url, PassCategory::kLoyaltyCard, content);
 }
 
 TEST_F(WalletablePassIngestionControllerTest,
@@ -251,7 +251,7 @@
 
   // Expect ShowWalletablePassConsentBubble to be called.
   WalletablePassClient::WalletablePassBubbleResultCallback consent_callback;
-  ExpectConsentBubbleOnClient(PASS_CATEGORY_LOYALTY_CARD, &consent_callback);
+  ExpectConsentBubbleOnClient(PassCategory::kLoyaltyCard, &consent_callback);
 
   test_api(controller()).StartWalletablePassDetectionFlow(url);
   ASSERT_TRUE(consent_callback);
@@ -289,7 +289,7 @@
   // Set OptIn status to true.
   SetWalletablePassDetectionOptInStatus(
       &test_pref_service(), test_identity_environment().identity_manager(),
-      true);
+      GeoIpCountryCode("US"), true);
 
   // Expect GetAnnotatedPageContent to be called directly.
   EXPECT_CALL(*controller(), GetAnnotatedPageContent)
@@ -359,9 +359,9 @@
 
   // Expect ShowWalletablePassConsentBubble to be called.
   WalletablePassClient::WalletablePassBubbleResultCallback consent_callback;
-  ExpectConsentBubbleOnClient(PASS_CATEGORY_LOYALTY_CARD, &consent_callback);
+  ExpectConsentBubbleOnClient(PassCategory::kLoyaltyCard, &consent_callback);
 
-  test_api(controller()).ShowConsentBubble(url, PASS_CATEGORY_LOYALTY_CARD);
+  test_api(controller()).ShowConsentBubble(url, PassCategory::kLoyaltyCard);
   ASSERT_TRUE(consent_callback);
 
   // Expect GetAnnotatedPageContent to be called when consent is accepted.
@@ -379,7 +379,7 @@
 
   EXPECT_CALL(mock_client(), ShowWalletablePassConsentBubble(_, _)).Times(0);
 
-  test_api(controller()).ShowConsentBubble(url, PASS_CATEGORY_LOYALTY_CARD);
+  test_api(controller()).ShowConsentBubble(url, PassCategory::kLoyaltyCard);
 }
 
 TEST_F(WalletablePassIngestionControllerTest,
@@ -388,9 +388,9 @@
   test_strike_database().SetStrikeData("WalletablePassConsent__shared_id", 1);
 
   WalletablePassClient::WalletablePassBubbleResultCallback consent_callback;
-  ExpectConsentBubbleOnClient(PASS_CATEGORY_LOYALTY_CARD, &consent_callback);
+  ExpectConsentBubbleOnClient(PassCategory::kLoyaltyCard, &consent_callback);
 
-  test_api(controller()).ShowConsentBubble(url, PASS_CATEGORY_LOYALTY_CARD);
+  test_api(controller()).ShowConsentBubble(url, PassCategory::kLoyaltyCard);
   ASSERT_TRUE(consent_callback);
 
   EXPECT_CALL(*controller(), GetAnnotatedPageContent(_));
@@ -407,9 +407,9 @@
   test_strike_database().SetStrikeData("WalletablePassConsent__shared_id", 0);
 
   WalletablePassClient::WalletablePassBubbleResultCallback consent_callback;
-  ExpectConsentBubbleOnClient(PASS_CATEGORY_LOYALTY_CARD, &consent_callback);
+  ExpectConsentBubbleOnClient(PassCategory::kLoyaltyCard, &consent_callback);
 
-  test_api(controller()).ShowConsentBubble(url, PASS_CATEGORY_LOYALTY_CARD);
+  test_api(controller()).ShowConsentBubble(url, PassCategory::kLoyaltyCard);
   ASSERT_TRUE(consent_callback);
 
   std::move(consent_callback)
@@ -425,9 +425,9 @@
   test_strike_database().SetStrikeData("WalletablePassConsent__shared_id", 0);
 
   WalletablePassClient::WalletablePassBubbleResultCallback consent_callback;
-  ExpectConsentBubbleOnClient(PASS_CATEGORY_LOYALTY_CARD, &consent_callback);
+  ExpectConsentBubbleOnClient(PassCategory::kLoyaltyCard, &consent_callback);
 
-  test_api(controller()).ShowConsentBubble(url, PASS_CATEGORY_LOYALTY_CARD);
+  test_api(controller()).ShowConsentBubble(url, PassCategory::kLoyaltyCard);
   ASSERT_TRUE(consent_callback);
 
   std::move(consent_callback)
@@ -443,9 +443,9 @@
   test_strike_database().SetStrikeData("WalletablePassConsent__shared_id", 0);
 
   WalletablePassClient::WalletablePassBubbleResultCallback consent_callback;
-  ExpectConsentBubbleOnClient(PASS_CATEGORY_LOYALTY_CARD, &consent_callback);
+  ExpectConsentBubbleOnClient(PassCategory::kLoyaltyCard, &consent_callback);
 
-  test_api(controller()).ShowConsentBubble(url, PASS_CATEGORY_LOYALTY_CARD);
+  test_api(controller()).ShowConsentBubble(url, PassCategory::kLoyaltyCard);
   ASSERT_TRUE(consent_callback);
 
   std::move(consent_callback)
@@ -461,9 +461,9 @@
   test_strike_database().SetStrikeData("WalletablePassConsent__shared_id", 0);
 
   WalletablePassClient::WalletablePassBubbleResultCallback consent_callback;
-  ExpectConsentBubbleOnClient(PASS_CATEGORY_LOYALTY_CARD, &consent_callback);
+  ExpectConsentBubbleOnClient(PassCategory::kLoyaltyCard, &consent_callback);
 
-  test_api(controller()).ShowConsentBubble(url, PASS_CATEGORY_LOYALTY_CARD);
+  test_api(controller()).ShowConsentBubble(url, PassCategory::kLoyaltyCard);
   ASSERT_TRUE(consent_callback);
 
   std::move(consent_callback)
@@ -479,9 +479,9 @@
 
   // Expect ShowWalletablePassConsentBubble to be called.
   WalletablePassClient::WalletablePassBubbleResultCallback consent_callback;
-  ExpectConsentBubbleOnClient(PASS_CATEGORY_LOYALTY_CARD, &consent_callback);
+  ExpectConsentBubbleOnClient(PassCategory::kLoyaltyCard, &consent_callback);
 
-  test_api(controller()).ShowConsentBubble(url, PASS_CATEGORY_LOYALTY_CARD);
+  test_api(controller()).ShowConsentBubble(url, PassCategory::kLoyaltyCard);
   ASSERT_TRUE(consent_callback);
 
   // Expect GetAnnotatedPageContent NOT to be called when consent is declined.
@@ -500,14 +500,14 @@
 
   EXPECT_CALL(*controller(), GetAnnotatedPageContent).Times(0);
 
-  test_api(controller()).MaybeStartExtraction(url, PASS_CATEGORY_LOYALTY_CARD);
+  test_api(controller()).MaybeStartExtraction(url, PassCategory::kLoyaltyCard);
 }
 
 TEST_F(WalletablePassIngestionControllerTest,
        MaybeStartExtraction_NoStrikes_ExtractionStarted) {
   GURL url("https://example.com");
   EXPECT_CALL(*controller(), GetAnnotatedPageContent);
-  test_api(controller()).MaybeStartExtraction(url, PASS_CATEGORY_LOYALTY_CARD);
+  test_api(controller()).MaybeStartExtraction(url, PassCategory::kLoyaltyCard);
 }
 
 TEST_F(WalletablePassIngestionControllerTest,
diff --git a/components/wallet/core/browser/walletable_permission_utils.cc b/components/wallet/core/browser/walletable_permission_utils.cc
index f844610..6c7f53c 100644
--- a/components/wallet/core/browser/walletable_permission_utils.cc
+++ b/components/wallet/core/browser/walletable_permission_utils.cc
@@ -106,20 +106,26 @@
   return value && value->GetIfBool().value_or(false);
 }
 
-void SetWalletablePassDetectionOptInStatus(
+bool SetWalletablePassDetectionOptInStatus(
     PrefService* prefs,
     const IdentityManager* identity_manager,
+    const GeoIpCountryCode& country_code,
     bool opt_in_status) {
-  if (!prefs) {
-    return;
+  if (!prefs ||
+      !IsEligibleForWalletablePassDetection(identity_manager, country_code)) {
+    return false;
   }
+
   const std::optional<GaiaIdHash> signed_in_hash =
       GetAccountGaiaIdHash(identity_manager);
-  if (signed_in_hash) {
-    syncer::SetAccountKeyedPrefValue(
-        prefs, prefs::kWalletablePassDetectionOptInStatus, *signed_in_hash,
-        base::Value(opt_in_status));
-  }
+  // IsEligibleForWalletablePassDetection guarantees that the account hash is
+  // present.
+  CHECK(signed_in_hash);
+
+  syncer::SetAccountKeyedPrefValue(prefs,
+                                   prefs::kWalletablePassDetectionOptInStatus,
+                                   *signed_in_hash, base::Value(opt_in_status));
+  return true;
 }
 
 }  // namespace wallet
diff --git a/components/wallet/core/browser/walletable_permission_utils.h b/components/wallet/core/browser/walletable_permission_utils.h
index f8b0fa01..f3a894b 100644
--- a/components/wallet/core/browser/walletable_permission_utils.h
+++ b/components/wallet/core/browser/walletable_permission_utils.h
@@ -24,9 +24,10 @@
     const signin::IdentityManager* identity_manager);
 
 // Sets the walletable pass detection opt-in status for the profile and account.
-void SetWalletablePassDetectionOptInStatus(
+bool SetWalletablePassDetectionOptInStatus(
     PrefService* pref_service,
     const signin::IdentityManager* identity_manager,
+    const GeoIpCountryCode& country_code,
     bool opt_in_status);
 
 // Checks whether the user is eligible for walletable pass detection.
diff --git a/components/wallet/core/browser/walletable_permission_utils_unittest.cc b/components/wallet/core/browser/walletable_permission_utils_unittest.cc
index 6c9c3c9..35c754f7 100644
--- a/components/wallet/core/browser/walletable_permission_utils_unittest.cc
+++ b/components/wallet/core/browser/walletable_permission_utils_unittest.cc
@@ -20,6 +20,10 @@
 class WalletablePermissionUtilsTest : public testing::Test {
  public:
   WalletablePermissionUtilsTest() {
+    feature_list_.InitWithFeaturesAndParameters(
+        {{kWalletablePassDetection,
+          {{"walletable_supported_country_allowlist", "US"}}}},
+        {});
     wallet::prefs::RegisterProfilePrefs(prefs_.registry());
   }
 
@@ -34,6 +38,7 @@
   }
 
  private:
+  base::test::ScopedFeatureList feature_list_;
   base::test::TaskEnvironment task_environment_;
   sync_preferences::TestingPrefServiceSyncable prefs_;
   signin::IdentityTestEnvironment identity_test_env_;
@@ -48,7 +53,8 @@
   EXPECT_FALSE(GetWalletablePassDetectionOptInStatus(prefs(), nullptr));
 
   // Should not crash.
-  SetWalletablePassDetectionOptInStatus(prefs(), nullptr, true);
+  EXPECT_FALSE(SetWalletablePassDetectionOptInStatus(
+      prefs(), nullptr, GeoIpCountryCode("US"), true));
   EXPECT_FALSE(GetWalletablePassDetectionOptInStatus(prefs(), nullptr));
 }
 
@@ -63,11 +69,13 @@
   identity_test_env().MakePrimaryAccountAvailable(
       "test@gmail.com", signin::ConsentLevel::kSignin);
 
-  SetWalletablePassDetectionOptInStatus(prefs(), identity_manager(), true);
+  SetWalletablePassDetectionOptInStatus(prefs(), identity_manager(),
+                                        GeoIpCountryCode("US"), true);
   EXPECT_TRUE(
       GetWalletablePassDetectionOptInStatus(prefs(), identity_manager()));
 
-  SetWalletablePassDetectionOptInStatus(prefs(), identity_manager(), false);
+  SetWalletablePassDetectionOptInStatus(prefs(), identity_manager(),
+                                        GeoIpCountryCode("US"), false);
   EXPECT_FALSE(
       GetWalletablePassDetectionOptInStatus(prefs(), identity_manager()));
 }
@@ -76,7 +84,8 @@
 TEST_F(WalletablePermissionUtilsTest, OptInStatus_TiedToAccount) {
   AccountInfo account1 = identity_test_env().MakePrimaryAccountAvailable(
       "test1@gmail.com", signin::ConsentLevel::kSignin);
-  SetWalletablePassDetectionOptInStatus(prefs(), identity_manager(), true);
+  SetWalletablePassDetectionOptInStatus(prefs(), identity_manager(),
+                                        GeoIpCountryCode("US"), true);
   EXPECT_TRUE(
       GetWalletablePassDetectionOptInStatus(prefs(), identity_manager()));
 
@@ -98,6 +107,24 @@
   EXPECT_TRUE(
       GetWalletablePassDetectionOptInStatus(prefs(), identity_manager()));
 }
+
+TEST_F(WalletablePermissionUtilsTest,
+       OptInStatus_SetWhenSignedOutReturnsFalse) {
+  // Ensure no account is signed in.
+  identity_test_env().ClearPrimaryAccount();
+  EXPECT_FALSE(
+      identity_manager()->HasPrimaryAccount(signin::ConsentLevel::kSignin));
+
+  // Attempt to set opt-in status when signed out. The country code is eligible
+  // but it should still fail because no account is signed in.
+  EXPECT_FALSE(SetWalletablePassDetectionOptInStatus(
+      prefs(), identity_manager(), GeoIpCountryCode("US"), true));
+
+  // Verify that the status remains false.
+  EXPECT_FALSE(
+      GetWalletablePassDetectionOptInStatus(prefs(), identity_manager()));
+}
+
 #endif  // !BUILDFLAG(IS_CHROMEOS)
 
 // Test fixture for WalletablePermissionUtils eligibility, initializing the
diff --git a/components/webapps/browser/android/add_to_homescreen_coordinator.cc b/components/webapps/browser/android/add_to_homescreen_coordinator.cc
index a4d12e4..bfbacda 100644
--- a/components/webapps/browser/android/add_to_homescreen_coordinator.cc
+++ b/components/webapps/browser/android/add_to_homescreen_coordinator.cc
@@ -48,8 +48,12 @@
     const std::u16string& user_title,
     const GURL& url,
     AddToHomescreenParams::AppType app_type) {
-  // TODO: crbug.com/449581904 - Skip the creation of mediator and view if
-  // auto-minted TWA will be installed.
+  if (app_type == AddToHomescreenParams::AppType::TWA) {
+    // When the auto-minted TWA will be installed, skip creating the mediator
+    // and the view, as the install dialog will be presented by the Android
+    // side (WebApp mainline module).
+    return;
+  }
 
   JNIEnv* env = base::android::AttachCurrentThread();
   mediator_ = reinterpret_cast<AddToHomescreenMediator*>(
@@ -66,7 +70,10 @@
     app_type = AppType::SHORTCUT;
   }
 
-  mediator_->OnAppMetadataAvailable(user_title, url, app_type);
+  mediator_->OnAppMetadataAvailable(
+      user_title, url, app_type,
+      base::BindRepeating(&AddToHomescreenCoordinator::RecordEventForAppMenu,
+                          data_fetcher_->web_contents()));
 }
 
 void AddToHomescreenCoordinator::OnDataAvailable(
@@ -74,14 +81,23 @@
     const SkBitmap& display_icon,
     AddToHomescreenParams::AppType app_type,
     InstallableStatusCode status_code) {
-  // OnUserTitleAvailable should be called beforehand.
-  CHECK(mediator_);
-
   auto params = std::make_unique<AddToHomescreenParams>(
       app_type, std::make_unique<ShortcutInfo>(info), display_icon, status_code,
       InstallableMetrics::GetInstallSource(data_fetcher_->web_contents(),
                                            InstallTrigger::MENU));
 
+  if (app_type == AddToHomescreenParams::AppType::TWA) {
+    CHECK(!mediator_);
+
+    // TODO(crbug.com/449581904): Start TWA install flow from here.
+
+    JNIEnv* env = base::android::AttachCurrentThread();
+    Java_AddToHomescreenCoordinator_onFlowCompleted(env, java_coordinator_);
+    return;
+  }
+
+  // OnUserTitleAvailable should be called beforehand.
+  CHECK(mediator_);
   mediator_->OnFullAppDataAvailable(std::move(params));
 }
 
@@ -95,17 +111,20 @@
 bool AddToHomescreenCoordinator::ShowForAppBanner(
     base::WeakPtr<AppBannerManager> weak_manager,
     std::unique_ptr<AddToHomescreenParams> params,
-    base::RepeatingCallback<void(AddToHomescreenInstaller::Event,
-                                 const AddToHomescreenParams&)>
-        event_callback) {
+    AddToHomescreenEventCallback event_callback) {
   // Don't start if app info is not available.
   if ((params->app_type == AddToHomescreenParams::AppType::NATIVE &&
        params->native_app_data.is_null()) ||
-      (params->app_type == AddToHomescreenParams::AppType::WEBAPK &&
+      (params->app_type != AddToHomescreenParams::AppType::NATIVE &&
        !params->shortcut_info)) {
     return false;
   }
 
+  if (params->app_type == AddToHomescreenParams::AppType::TWA) {
+    // TODO(crbug.com/449581904): Start TWA install flow from here.
+    return true;
+  }
+
   JNIEnv* env = base::android::AttachCurrentThread();
   AddToHomescreenMediator* mediator = (AddToHomescreenMediator*)
       Java_AddToHomescreenCoordinator_initMvcAndReturnMediator(
@@ -118,6 +137,27 @@
 }
 
 // static
+void AddToHomescreenCoordinator::RecordEventForAppMenu(
+    content::WebContents* web_contents,
+    AddToHomescreenEvent event,
+    const AddToHomescreenParams& a2hs_params) {
+  if (!web_contents || a2hs_params.app_type == AppType::NATIVE) {
+    return;
+  }
+
+  if (event == AddToHomescreenEvent::INSTALL_REQUEST_FINISHED) {
+    AppBannerManager* app_banner_manager =
+        AppBannerManager::FromWebContents(web_contents);
+    // Fire the appinstalled event and do install time logging.
+    if (app_banner_manager) {
+      app_banner_manager->OnInstall(
+          a2hs_params.shortcut_info->display,
+          /*set_current_web_app_not_installable=*/false);
+    }
+  }
+}
+
+// static
 jlong JNI_AddToHomescreenCoordinator_StartForAppMenu(
     JNIEnv* env,
     const JavaParamRef<jobject>& java_coordinator,
diff --git a/components/webapps/browser/android/add_to_homescreen_coordinator.h b/components/webapps/browser/android/add_to_homescreen_coordinator.h
index 9089c4f..cd623b6 100644
--- a/components/webapps/browser/android/add_to_homescreen_coordinator.h
+++ b/components/webapps/browser/android/add_to_homescreen_coordinator.h
@@ -36,12 +36,9 @@
   void Destroy(JNIEnv* env);
 
   // Called for showing the add-to-homescreen UI for AppBannerManager.
-  static bool ShowForAppBanner(
-      base::WeakPtr<AppBannerManager> weak_manager,
-      std::unique_ptr<AddToHomescreenParams> params,
-      base::RepeatingCallback<void(AddToHomescreenInstaller::Event,
-                                   const AddToHomescreenParams&)>
-          event_callback);
+  static bool ShowForAppBanner(base::WeakPtr<AppBannerManager> weak_manager,
+                               std::unique_ptr<AddToHomescreenParams> params,
+                               AddToHomescreenEventCallback event_callback);
 
   AddToHomescreenCoordinator() = delete;
   AddToHomescreenCoordinator(const AddToHomescreenCoordinator&) = delete;
@@ -60,6 +57,10 @@
                        AddToHomescreenParams::AppType app_type,
                        InstallableStatusCode status_code) override;
 
+  static void RecordEventForAppMenu(content::WebContents* web_contents,
+                                    AddToHomescreenEvent event,
+                                    const AddToHomescreenParams& a2hs_params);
+
   base::android::ScopedJavaGlobalRef<jobject> java_coordinator_;
 
   // These are used only in the startForAppMenu() flow.
diff --git a/components/webapps/browser/android/add_to_homescreen_data_fetcher.cc b/components/webapps/browser/android/add_to_homescreen_data_fetcher.cc
index 131aed49..f8b734c 100644
--- a/components/webapps/browser/android/add_to_homescreen_data_fetcher.cc
+++ b/components/webapps/browser/android/add_to_homescreen_data_fetcher.cc
@@ -219,8 +219,8 @@
   shortcut_info_.UpdateDisplayMode(webapk_compatible);
 
   AddToHomescreenParams::AppType app_type =
-      data.manifest_url->is_empty() ? AddToHomescreenParams::AppType::WEBAPK_DIY
-                                    : AddToHomescreenParams::AppType::WEBAPK;
+      AddToHomescreenParams::GetWebAppInstallType(
+          /*has_manifest=*/!data.manifest_url->is_empty());
 
   observer_->OnUserTitleAvailable(
       webapk_compatible ? shortcut_info_.name : shortcut_info_.user_title,
diff --git a/components/webapps/browser/android/add_to_homescreen_installer.cc b/components/webapps/browser/android/add_to_homescreen_installer.cc
index 404760bb..06923fe 100644
--- a/components/webapps/browser/android/add_to_homescreen_installer.cc
+++ b/components/webapps/browser/android/add_to_homescreen_installer.cc
@@ -7,6 +7,7 @@
 #include <utility>
 
 #include "base/functional/callback.h"
+#include "base/notreached.h"
 #include "components/webapps/browser/webapps_client.h"
 #include "content/public/browser/web_contents.h"
 
@@ -19,14 +20,13 @@
 void AddToHomescreenInstaller::Install(
     content::WebContents* web_contents,
     const AddToHomescreenParams& params,
-    const base::RepeatingCallback<void(Event, const AddToHomescreenParams&)>&
-        event_callback) {
+    const AddToHomescreenEventCallback& event_callback) {
   if (!web_contents) {
-    event_callback.Run(Event::INSTALL_FAILED, params);
+    event_callback.Run(AddToHomescreenEvent::INSTALL_FAILED, params);
     return;
   }
 
-  event_callback.Run(Event::INSTALL_STARTED, params);
+  event_callback.Run(AddToHomescreenEvent::INSTALL_STARTED, params);
   switch (params.app_type) {
     case AddToHomescreenParams::AppType::NATIVE:
       InstallOrOpenNativeApp(web_contents, params, event_callback);
@@ -38,23 +38,27 @@
     case AddToHomescreenParams::AppType::SHORTCUT:
       WebappsClient::Get()->InstallShortcut(web_contents, params);
       break;
+    case AddToHomescreenParams::AppType::TWA:
+      // Auto-minted TWAs will be handled separately, as the install UX is shown
+      // by the WebApp service on the Android side.
+      NOTREACHED();
   }
-  event_callback.Run(Event::INSTALL_REQUEST_FINISHED, params);
+  event_callback.Run(AddToHomescreenEvent::INSTALL_REQUEST_FINISHED, params);
 }
 
 // static
 void AddToHomescreenInstaller::InstallOrOpenNativeApp(
     content::WebContents* web_contents,
     const AddToHomescreenParams& params,
-    const base::RepeatingCallback<void(Event, const AddToHomescreenParams&)>&
-        event_callback) {
+    const AddToHomescreenEventCallback& event_callback) {
   JNIEnv* env = base::android::AttachCurrentThread();
 
   bool was_successful = Java_AddToHomescreenInstaller_installOrOpenNativeApp(
       env, web_contents->GetJavaWebContents(), params.native_app_data);
-  event_callback.Run(was_successful ? Event::NATIVE_INSTALL_OR_OPEN_SUCCEEDED
-                                    : Event::NATIVE_INSTALL_OR_OPEN_FAILED,
-                     params);
+  event_callback.Run(
+      was_successful ? AddToHomescreenEvent::NATIVE_INSTALL_OR_OPEN_SUCCEEDED
+                     : AddToHomescreenEvent::NATIVE_INSTALL_OR_OPEN_FAILED,
+      params);
 }
 
 }  // namespace webapps
diff --git a/components/webapps/browser/android/add_to_homescreen_installer.h b/components/webapps/browser/android/add_to_homescreen_installer.h
index 781e4c26..d23e9d5 100644
--- a/components/webapps/browser/android/add_to_homescreen_installer.h
+++ b/components/webapps/browser/android/add_to_homescreen_installer.h
@@ -14,37 +14,38 @@
 
 namespace webapps {
 
+enum class AddToHomescreenEvent {
+  INSTALL_STARTED,
+  INSTALL_FAILED,
+  INSTALL_REQUEST_FINISHED,
+  NATIVE_INSTALL_OR_OPEN_FAILED,
+  NATIVE_INSTALL_OR_OPEN_SUCCEEDED,
+  NATIVE_DETAILS_SHOWN,
+  UI_SHOWN,
+  UI_CANCELLED,
+};
+
+using AddToHomescreenEventCallback =
+    base::RepeatingCallback<void(AddToHomescreenEvent,
+                                 const AddToHomescreenParams&)>;
+
 // Helper class for installing a web app or an Android native app and recording
 // related UMA.
 class AddToHomescreenInstaller {
  public:
-  enum class Event {
-    INSTALL_STARTED,
-    INSTALL_FAILED,
-    INSTALL_REQUEST_FINISHED,
-    NATIVE_INSTALL_OR_OPEN_FAILED,
-    NATIVE_INSTALL_OR_OPEN_SUCCEEDED,
-    NATIVE_DETAILS_SHOWN,
-    UI_SHOWN,
-    UI_CANCELLED,
-  };
-
   // Installs the app referenced by the data in this object.
   // |event_callback| will be run to inform the caller of the progress of the
   // installation. It is guaranteed that |event_callback| will only be called
   // before the add-to-homescreen prompt is dismissed.
-  static void Install(
-      content::WebContents* web_contents,
-      const AddToHomescreenParams& params,
-      const base::RepeatingCallback<void(Event, const AddToHomescreenParams&)>&
-          event_callback);
+  static void Install(content::WebContents* web_contents,
+                      const AddToHomescreenParams& params,
+                      const AddToHomescreenEventCallback& event_callback);
 
  private:
   static void InstallOrOpenNativeApp(
       content::WebContents* web_contents,
       const AddToHomescreenParams& params,
-      const base::RepeatingCallback<void(Event, const AddToHomescreenParams&)>&
-          event_callback);
+      const AddToHomescreenEventCallback& event_callback);
 
   AddToHomescreenInstaller() = delete;
   AddToHomescreenInstaller(const AddToHomescreenInstaller&) = delete;
diff --git a/components/webapps/browser/android/add_to_homescreen_mediator.cc b/components/webapps/browser/android/add_to_homescreen_mediator.cc
index 04c078e..2cf9e97d0 100644
--- a/components/webapps/browser/android/add_to_homescreen_mediator.cc
+++ b/components/webapps/browser/android/add_to_homescreen_mediator.cc
@@ -47,14 +47,12 @@
 
 void AddToHomescreenMediator::StartForAppBanner(
     std::unique_ptr<AddToHomescreenParams> params,
-    base::RepeatingCallback<void(AddToHomescreenInstaller::Event,
-                                 const AddToHomescreenParams&)>
-        event_callback) {
+    AddToHomescreenEventCallback event_callback) {
   params_ = std::move(params);
   event_callback_ = std::move(event_callback);
   // Call UI_SHOWN early since the UI is already shown on Java coordinator
   // initialization.
-  event_callback_.Run(AddToHomescreenInstaller::Event::UI_SHOWN, *params_);
+  event_callback_.Run(AddToHomescreenEvent::UI_SHOWN, *params_);
 
   if (params_->app_type == AppType::NATIVE) {
     JNIEnv* env = base::android::AttachCurrentThread();
@@ -72,13 +70,9 @@
 void AddToHomescreenMediator::OnAppMetadataAvailable(
     const std::u16string& user_title,
     const GURL& url,
-    AddToHomescreenParams::AppType app_type) {
-  // base::Unretained() is safe because the lifetime of this object is
-  // controlled by its Java counterpart. It will be destroyed when the add to
-  // home screen prompt is dismissed, which occurs after the last time
-  // RecordEventForAppMenu() can be called.
-  event_callback_ = base::BindRepeating(
-      &AddToHomescreenMediator::RecordEventForAppMenu, base::Unretained(this));
+    AddToHomescreenParams::AppType app_type,
+    AddToHomescreenEventCallback event_callback) {
+  event_callback_ = std::move(event_callback);
 
   SetWebAppInfo(user_title, url, app_type);
 }
@@ -125,14 +119,12 @@
 
 void AddToHomescreenMediator::OnUiDismissed(JNIEnv* env) {
   if (params_) {
-    event_callback_.Run(AddToHomescreenInstaller::Event::UI_CANCELLED,
-                        *params_);
+    event_callback_.Run(AddToHomescreenEvent::UI_CANCELLED, *params_);
   }
 }
 
 void AddToHomescreenMediator::OnNativeDetailsShown(JNIEnv* env) {
-  event_callback_.Run(AddToHomescreenInstaller::Event::NATIVE_DETAILS_SHOWN,
-                      *params_);
+  event_callback_.Run(AddToHomescreenEvent::NATIVE_DETAILS_SHOWN, *params_);
 }
 
 void AddToHomescreenMediator::Destroy(JNIEnv* env) {
@@ -166,25 +158,6 @@
                                              j_url, static_cast<int>(app_type));
 }
 
-void AddToHomescreenMediator::RecordEventForAppMenu(
-    AddToHomescreenInstaller::Event event,
-    const AddToHomescreenParams& a2hs_params) {
-  if (!web_contents_ || a2hs_params.app_type == AppType::NATIVE) {
-    return;
-  }
-
-  if (event == AddToHomescreenInstaller::Event::INSTALL_REQUEST_FINISHED) {
-    AppBannerManager* app_banner_manager =
-        AppBannerManager::FromWebContents(web_contents_.get());
-    // Fire the appinstalled event and do install time logging.
-    if (app_banner_manager) {
-      app_banner_manager->OnInstall(
-          a2hs_params.shortcut_info->display,
-          /*set_current_web_app_not_installable=*/false);
-    }
-  }
-}
-
 }  // namespace webapps
 
 DEFINE_JNI(AddToHomescreenMediator)
diff --git a/components/webapps/browser/android/add_to_homescreen_mediator.h b/components/webapps/browser/android/add_to_homescreen_mediator.h
index 983d8dbe..ee79fac 100644
--- a/components/webapps/browser/android/add_to_homescreen_mediator.h
+++ b/components/webapps/browser/android/add_to_homescreen_mediator.h
@@ -42,17 +42,15 @@
       const base::android::JavaParamRef<jobject>& java_ref,
       const base::android::JavaParamRef<jobject>& java_web_contents);
 
-  void StartForAppBanner(
-      std::unique_ptr<AddToHomescreenParams> params,
-      base::RepeatingCallback<void(AddToHomescreenInstaller::Event,
-                                   const AddToHomescreenParams&)>
-          event_callback);
+  void StartForAppBanner(std::unique_ptr<AddToHomescreenParams> params,
+                         AddToHomescreenEventCallback event_callback);
 
   // These 2 methods are called from the coordinator when the current flow
   // started with startForAppMenu.
   void OnAppMetadataAvailable(const std::u16string& user_title,
                               const GURL& url,
-                              AddToHomescreenParams::AppType app_type);
+                              AddToHomescreenParams::AppType app_type,
+                              AddToHomescreenEventCallback event_callback);
   void OnFullAppDataAvailable(std::unique_ptr<AddToHomescreenParams> params);
 
   // Called from the Java side when the user accepts app installation from the
@@ -82,9 +80,6 @@
                      const GURL& url,
                      AddToHomescreenParams::AppType app_type);
 
-  void RecordEventForAppMenu(AddToHomescreenInstaller::Event event,
-                             const AddToHomescreenParams& a2hs_params);
-
   // Points to the Java reference.
   base::android::ScopedJavaGlobalRef<jobject> java_ref_;
 
@@ -92,9 +87,7 @@
 
   std::unique_ptr<AddToHomescreenParams> params_;
 
-  base::RepeatingCallback<void(AddToHomescreenInstaller::Event,
-                               const AddToHomescreenParams&)>
-      event_callback_;
+  AddToHomescreenEventCallback event_callback_;
 
   AddToHomescreenMediator(const AddToHomescreenMediator&) = delete;
   AddToHomescreenMediator& operator=(const AddToHomescreenMediator&) = delete;
diff --git a/components/webapps/browser/android/add_to_homescreen_params.cc b/components/webapps/browser/android/add_to_homescreen_params.cc
index 9ad23d5..72cb724 100644
--- a/components/webapps/browser/android/add_to_homescreen_params.cc
+++ b/components/webapps/browser/android/add_to_homescreen_params.cc
@@ -5,6 +5,7 @@
 #include "components/webapps/browser/android/add_to_homescreen_params.h"
 
 #include "components/webapps/browser/android/shortcut_info.h"
+#include "components/webapps/browser/android/webapps_utils.h"
 
 namespace webapps {
 
@@ -19,7 +20,7 @@
       shortcut_info(std::move(info)),
       install_source(source),
       installable_status(status_code) {
-  CHECK(IsWebApk() || app_type == AppType::SHORTCUT);
+  CHECK(app_type != AppType::NATIVE);
 }
 
 AddToHomescreenParams::AddToHomescreenParams(
@@ -48,4 +49,16 @@
   return type == AppType::WEBAPK || type == AppType::WEBAPK_DIY;
 }
 
+// static
+AddToHomescreenParams::AppType AddToHomescreenParams::GetWebAppInstallType(
+    bool has_manifest) {
+  if (!has_manifest) {
+    return AppType::WEBAPK_DIY;
+  }
+  if (WebappsUtils::IsAutoMintedTwaEnabled()) {
+    return AppType::TWA;
+  }
+  return AppType::WEBAPK;
+}
+
 }  // namespace webapps
diff --git a/components/webapps/browser/android/add_to_homescreen_params.h b/components/webapps/browser/android/add_to_homescreen_params.h
index 19ecad8f..a042a809d 100644
--- a/components/webapps/browser/android/add_to_homescreen_params.h
+++ b/components/webapps/browser/android/add_to_homescreen_params.h
@@ -22,11 +22,17 @@
   // A Java counterpart will be generated for this enum.
   // GENERATED_JAVA_ENUM_PACKAGE: org.chromium.components.webapps
   enum class AppType {
+    // Native Android app.
     NATIVE,
+    // WebAPK installed from websites with PWA manifest.
     WEBAPK,
+    // Shortcut, which opens in a new tab in Chrome browser app.
     SHORTCUT,
+    // WebAPK installed from websites without PWA manifest.
     WEBAPK_DIY,
-    kMaxValue = WEBAPK_DIY,
+    // Auto-minted TWA installed from websites with PWA manifest.
+    TWA,
+    kMaxValue = TWA,
   };
 
   AddToHomescreenParams() = delete;
@@ -46,6 +52,11 @@
   bool IsWebApk() const;
   static bool IsWebApk(AppType type);
 
+  // Returns the AppType that should be used to install a web app that was
+  // determined to be installable. The argument `has_manifest` specifies whether
+  // the web app has PWA manifest.
+  static AppType GetWebAppInstallType(bool has_manifest);
+
   AppType app_type;
   SkBitmap primary_icon;
   std::unique_ptr<ShortcutInfo> shortcut_info;
diff --git a/components/webapps/browser/android/ambient_badge_manager.cc b/components/webapps/browser/android/ambient_badge_manager.cc
index 5bfd6a8..b5370ecc 100644
--- a/components/webapps/browser/android/ambient_badge_manager.cc
+++ b/components/webapps/browser/android/ambient_badge_manager.cc
@@ -239,9 +239,9 @@
     return;
   }
 
-  GURL url = a2hs_params_->app_type == AddToHomescreenParams::AppType::WEBAPK
-                 ? a2hs_params_->shortcut_info->url
-                 : validated_url_;
+  GURL url = a2hs_params_->app_type == AddToHomescreenParams::AppType::NATIVE
+                 ? validated_url_
+                 : a2hs_params_->shortcut_info->url;
   message_controller_.EnqueueMessage(
       web_contents(), app_name_, a2hs_params_->primary_icon,
       a2hs_params_->HasMaskablePrimaryIcon(), url);
diff --git a/components/webapps/browser/android/app_banner_manager_android.cc b/components/webapps/browser/android/app_banner_manager_android.cc
index 3cfe628..b7e0d14 100644
--- a/components/webapps/browser/android/app_banner_manager_android.cc
+++ b/components/webapps/browser/android/app_banner_manager_android.cc
@@ -203,7 +203,7 @@
     CHECK(config.mode == AppBannerMode::kWebApp);
     const WebAppBannerData& web_app_data = config.web_app_data;
     return std::make_unique<AddToHomescreenParams>(
-        AddToHomescreenParams::AppType::WEBAPK,
+        AddToHomescreenParams::GetWebAppInstallType(/* has_manifest= */ true),
         ShortcutInfo::CreateShortcutInfo(
             config.validated_url, web_app_data.manifest_url,
             web_app_data.manifest(), web_app_data.web_page_metadata(),
@@ -423,7 +423,7 @@
 
 void AppBannerManagerAndroid::OnInstallEvent(
     GURL validated_url,
-    AddToHomescreenInstaller::Event event,
+    AddToHomescreenEvent event,
     const AddToHomescreenParams& a2hs_params) {
   delegate_->RecordExtraMetricsForInstallEvent(event, a2hs_params);
 
@@ -433,12 +433,12 @@
       (a2hs_params.install_source == WebappInstallSource::MENU_BROWSER_TAB ||
        a2hs_params.install_source == WebappInstallSource::MENU_CUSTOM_TAB)) {
     switch (event) {
-      case AddToHomescreenInstaller::Event::INSTALL_REQUEST_FINISHED:
+      case AddToHomescreenEvent::INSTALL_REQUEST_FINISHED:
         SendBannerAccepted();
         OnInstall(a2hs_params.shortcut_info->display,
                   /*set_current_web_app_not_installable=*/false);
         break;
-      case AddToHomescreenInstaller::Event::UI_CANCELLED:
+      case AddToHomescreenEvent::UI_CANCELLED:
         // Collapsing the bottom sheet installer UI does not count as
         // UI_CANCELLED as it is still visible to the user and they can expand
         // it later.
@@ -473,13 +473,14 @@
   }
 
   switch (event) {
-    case AddToHomescreenInstaller::Event::INSTALL_STARTED:
+    case AddToHomescreenEvent::INSTALL_STARTED:
       TrackDismissEvent(DISMISS_EVENT_DISMISSED);
       switch (a2hs_params.app_type) {
         case AddToHomescreenParams::AppType::NATIVE:
           TrackUserResponse(USER_RESPONSE_NATIVE_APP_ACCEPTED);
           break;
         case AddToHomescreenParams::AppType::WEBAPK:
+        case AddToHomescreenParams::AppType::TWA:
           TrackUserResponse(USER_RESPONSE_WEB_APP_ACCEPTED);
           AppBannerSettingsHelper::RecordBannerInstallEvent(
               web_contents(), a2hs_params.shortcut_info->url.spec());
@@ -487,40 +488,40 @@
         default:
           // a2hs_params should be the one created by
           // CreateAddToHomescreenParams(), which only returns
-          // AddToHomescreenParams::AppType::NATIVE or
-          // AddToHomescreenParams::AppType::WEBAPK, so this shouldn't be
-          // reached.
+          // AddToHomescreenParams::AppType::NATIVE,
+          // AddToHomescreenParams::AppType::WEBAPK, or
+          // AddToHomescreenParams::AppType::TWA, so this shouldn't be reached.
           NOTREACHED();
       }
       break;
 
-    case AddToHomescreenInstaller::Event::INSTALL_FAILED:
+    case AddToHomescreenEvent::INSTALL_FAILED:
       TrackDismissEvent(DISMISS_EVENT_ERROR);
       break;
 
-    case AddToHomescreenInstaller::Event::NATIVE_INSTALL_OR_OPEN_FAILED:
+    case AddToHomescreenEvent::NATIVE_INSTALL_OR_OPEN_FAILED:
       DCHECK_EQ(a2hs_params.app_type, AddToHomescreenParams::AppType::NATIVE);
       TrackInstallEvent(INSTALL_EVENT_NATIVE_APP_INSTALL_TRIGGERED);
       break;
 
-    case AddToHomescreenInstaller::Event::NATIVE_INSTALL_OR_OPEN_SUCCEEDED:
+    case AddToHomescreenEvent::NATIVE_INSTALL_OR_OPEN_SUCCEEDED:
       DCHECK_EQ(a2hs_params.app_type, AddToHomescreenParams::AppType::NATIVE);
       TrackDismissEvent(DISMISS_EVENT_APP_OPEN);
       break;
 
-    case AddToHomescreenInstaller::Event::INSTALL_REQUEST_FINISHED:
+    case AddToHomescreenEvent::INSTALL_REQUEST_FINISHED:
       SendBannerAccepted();
-      if (a2hs_params.app_type == AddToHomescreenParams::AppType::WEBAPK) {
+      if (a2hs_params.app_type != AddToHomescreenParams::AppType::NATIVE) {
         OnInstall(a2hs_params.shortcut_info->display,
                   /*set_current_web_app_not_installable=*/false);
       }
       break;
 
-    case AddToHomescreenInstaller::Event::NATIVE_DETAILS_SHOWN:
+    case AddToHomescreenEvent::NATIVE_DETAILS_SHOWN:
       TrackDismissEvent(DISMISS_EVENT_BANNER_CLICK);
       break;
 
-    case AddToHomescreenInstaller::Event::UI_SHOWN:
+    case AddToHomescreenEvent::UI_SHOWN:
       AppBannerSettingsHelper::RecordBannerEvent(
           web_contents(), validated_url, identifier,
           AppBannerSettingsHelper::APP_BANNER_EVENT_DID_SHOW, GetCurrentTime());
@@ -530,7 +531,7 @@
                             : DISPLAY_EVENT_WEB_APP_BANNER_CREATED);
       break;
 
-    case AddToHomescreenInstaller::Event::UI_CANCELLED:
+    case AddToHomescreenEvent::UI_CANCELLED:
       TrackDismissEvent(DISMISS_EVENT_DISMISSED);
 
       SendBannerDismissed();
@@ -677,15 +678,6 @@
   // TODO(crbug.com/40269982): Implement.
 }
 
-void AppBannerManagerAndroid::Install(
-    const AddToHomescreenParams& a2hs_params,
-    base::RepeatingCallback<void(AddToHomescreenInstaller::Event,
-                                 const AddToHomescreenParams&)>
-        a2hs_event_callback) {
-  AddToHomescreenInstaller::Install(web_contents(), a2hs_params,
-                                    std::move(a2hs_event_callback));
-}
-
 base::WeakPtr<AppBannerManager>
 AppBannerManagerAndroid::GetWeakPtrForThisNavigation() {
   return weak_factory_.GetWeakPtr();
diff --git a/components/webapps/browser/android/app_banner_manager_android.h b/components/webapps/browser/android/app_banner_manager_android.h
index 00c11fc..72ebb39 100644
--- a/components/webapps/browser/android/app_banner_manager_android.h
+++ b/components/webapps/browser/android/app_banner_manager_android.h
@@ -82,7 +82,7 @@
     // Called when an install event occurs, allowing specializations to record
     // additional metrics.
     virtual void RecordExtraMetricsForInstallEvent(
-        AddToHomescreenInstaller::Event event,
+        AddToHomescreenEvent event,
         const AddToHomescreenParams& a2hs_params) = 0;
   };
 
@@ -130,14 +130,6 @@
 
   void ShowBannerFromBadge(const InstallBannerConfig& config);
 
-  // Installs the app referenced by the data in |a2hs_params|.
-  // |a2hs_event_callback| will be run to inform the caller of the progress of
-  // the installation.
-  void Install(const AddToHomescreenParams& a2hs_params,
-               base::RepeatingCallback<void(AddToHomescreenInstaller::Event,
-                                            const AddToHomescreenParams&)>
-                   a2hs_event_callback);
-
   // Returns false if the bottom sheet can't be shown. In that case an
   // alternative UI should be shown.
   bool MaybeShowPwaBottomSheetController(bool expand_sheet,
@@ -190,7 +182,7 @@
   // Use as a callback to notify |this| after an install event such as a dialog
   // being cancelled or an app being installed has occurred.
   void OnInstallEvent(GURL validated_url,
-                      AddToHomescreenInstaller::Event event,
+                      AddToHomescreenEvent event,
                       const AddToHomescreenParams& a2hs_params);
 
   base::WeakPtr<AppBannerManagerAndroid> GetAndroidWeakPtr();
diff --git a/components/webapps/browser/android/bottomsheet/pwa_bottom_sheet_controller.cc b/components/webapps/browser/android/bottomsheet/pwa_bottom_sheet_controller.cc
index 5c27ea7..4d78b68 100644
--- a/components/webapps/browser/android/bottomsheet/pwa_bottom_sheet_controller.cc
+++ b/components/webapps/browser/android/bottomsheet/pwa_bottom_sheet_controller.cc
@@ -82,9 +82,7 @@
     content::WebContents* web_contents,
     const WebAppBannerData& web_app_banner_data,
     bool expand_sheet,
-    base::RepeatingCallback<void(AddToHomescreenInstaller::Event,
-                                 const AddToHomescreenParams&)>
-        a2hs_event_callback,
+    AddToHomescreenEventCallback a2hs_event_callback,
     std::unique_ptr<AddToHomescreenParams> a2hs_params) {
   if (!CanShowBottomSheet(web_contents, web_app_banner_data.screenshots)) {
     return false;
@@ -111,9 +109,7 @@
 PwaBottomSheetController::PwaBottomSheetController(
     const WebAppBannerData& web_app_banner_data,
     std::unique_ptr<AddToHomescreenParams> a2hs_params,
-    base::RepeatingCallback<void(AddToHomescreenInstaller::Event,
-                                 const AddToHomescreenParams&)>
-        a2hs_event_callback)
+    AddToHomescreenEventCallback a2hs_event_callback)
     : web_app_banner_data_(web_app_banner_data),
       a2hs_params_(std::move(a2hs_params)),
       a2hs_event_callback_(a2hs_event_callback) {}
@@ -124,8 +120,7 @@
   // to the regular install dialog prompt. Therefore, we send UI_CANCELLED
   // only if the bottom sheet was ever expanded and not closed.
   if (!install_triggered_ && !sheet_closed_ && sheet_expanded_) {
-    a2hs_event_callback_.Run(AddToHomescreenInstaller::Event::UI_CANCELLED,
-                             *a2hs_params_);
+    a2hs_event_callback_.Run(AddToHomescreenEvent::UI_CANCELLED, *a2hs_params_);
   }
   delete this;
 }
@@ -137,14 +132,12 @@
 }
 
 void PwaBottomSheetController::OnSheetClosedWithSwipe(JNIEnv* env) {
-  a2hs_event_callback_.Run(AddToHomescreenInstaller::Event::UI_CANCELLED,
-                           *a2hs_params_);
+  a2hs_event_callback_.Run(AddToHomescreenEvent::UI_CANCELLED, *a2hs_params_);
   sheet_closed_ = true;
 }
 
 void PwaBottomSheetController::OnSheetExpanded(JNIEnv* env) {
-  a2hs_event_callback_.Run(AddToHomescreenInstaller::Event::UI_SHOWN,
-                           *a2hs_params_);
+  a2hs_event_callback_.Run(AddToHomescreenEvent::UI_SHOWN, *a2hs_params_);
   sheet_expanded_ = true;
 }
 
diff --git a/components/webapps/browser/android/bottomsheet/pwa_bottom_sheet_controller.h b/components/webapps/browser/android/bottomsheet/pwa_bottom_sheet_controller.h
index a67be92..c520d748 100644
--- a/components/webapps/browser/android/bottomsheet/pwa_bottom_sheet_controller.h
+++ b/components/webapps/browser/android/bottomsheet/pwa_bottom_sheet_controller.h
@@ -34,14 +34,11 @@
  public:
   // If possible, shows/expand the PWA Bottom Sheet installer and returns true.
   // Otherwise does nothing and returns false.
-  static bool MaybeShow(
-      content::WebContents* web_contents,
-      const WebAppBannerData& web_app_banner_data,
-      bool expand_sheet,
-      base::RepeatingCallback<void(AddToHomescreenInstaller::Event,
-                                   const AddToHomescreenParams&)>
-          a2hs_event_callback,
-      std::unique_ptr<AddToHomescreenParams> a2hs_params);
+  static bool MaybeShow(content::WebContents* web_contents,
+                        const WebAppBannerData& web_app_banner_data,
+                        bool expand_sheet,
+                        AddToHomescreenEventCallback a2hs_event_callback,
+                        std::unique_ptr<AddToHomescreenParams> a2hs_params);
 
   PwaBottomSheetController(const PwaBottomSheetController&) = delete;
   PwaBottomSheetController& operator=(const PwaBottomSheetController&) = delete;
@@ -68,12 +65,9 @@
       const base::android::JavaParamRef<jobject>& jweb_contents);
 
  private:
-  PwaBottomSheetController(
-      const WebAppBannerData& web_app_banner_data,
-      std::unique_ptr<AddToHomescreenParams> a2hs_params,
-      base::RepeatingCallback<void(AddToHomescreenInstaller::Event,
-                                   const AddToHomescreenParams&)>
-          a2hs_event_callback);
+  PwaBottomSheetController(const WebAppBannerData& web_app_banner_data,
+                           std::unique_ptr<AddToHomescreenParams> a2hs_params,
+                           AddToHomescreenEventCallback a2hs_event_callback);
 
   // Shows the Bottom Sheet installer UI for a given |web_contents|.
   void ShowBottomSheetInstaller(content::WebContents* web_contents,
@@ -89,9 +83,7 @@
   // will be passed to |a2hs_event_callback_| eventually.
   std::unique_ptr<AddToHomescreenParams> a2hs_params_;
   // Called to provide input into the state of the installation process.
-  base::RepeatingCallback<void(AddToHomescreenInstaller::Event,
-                               const AddToHomescreenParams&)>
-      a2hs_event_callback_;
+  AddToHomescreenEventCallback a2hs_event_callback_;
   // Whether the bottom sheet has been expanded.
   bool sheet_expanded_ = false;
   // Whether the bottom sheet has been closed.
diff --git a/components/webapps/browser/android/java/src/org/chromium/components/webapps/AddToHomescreenCoordinator.java b/components/webapps/browser/android/java/src/org/chromium/components/webapps/AddToHomescreenCoordinator.java
index a1c9d42..044c674d 100644
--- a/components/webapps/browser/android/java/src/org/chromium/components/webapps/AddToHomescreenCoordinator.java
+++ b/components/webapps/browser/android/java/src/org/chromium/components/webapps/AddToHomescreenCoordinator.java
@@ -120,6 +120,7 @@
         return addToHomescreenMediator.getNativeMediator();
     }
 
+    @CalledByNative
     private void onFlowCompleted() {
         if (mNativeCoordinator != 0) {
             AddToHomescreenCoordinatorJni.get().destroy(mNativeCoordinator);
diff --git a/components/webapps/browser/android/java/src/org/chromium/components/webapps/AddToHomescreenDialogView.java b/components/webapps/browser/android/java/src/org/chromium/components/webapps/AddToHomescreenDialogView.java
index 6b178781..40d98f6 100644
--- a/components/webapps/browser/android/java/src/org/chromium/components/webapps/AddToHomescreenDialogView.java
+++ b/components/webapps/browser/android/java/src/org/chromium/components/webapps/AddToHomescreenDialogView.java
@@ -254,6 +254,9 @@
                 mAppOriginView.setVisibility(View.VISIBLE);
                 mAppOriginView.setVisibility(View.VISIBLE);
                 break;
+            case AppType.TWA:
+                // This dialog should not be created for auto-minted TWAs.
+                assert false;
         }
 
         if (mAppType == AppType.SHORTCUT) {
@@ -358,6 +361,7 @@
             case AppType.NATIVE:
                 return mAppNameView;
             default:
+                // This dialog should not be created for auto-minted TWAs.
                 assert false;
                 return assumeNonNull(null);
         }
diff --git a/components/webapps/browser/android/java/src/org/chromium/components/webapps/AddToHomescreenDialogViewTest.java b/components/webapps/browser/android/java/src/org/chromium/components/webapps/AddToHomescreenDialogViewTest.java
index 4568beb..3c4d57b5 100644
--- a/components/webapps/browser/android/java/src/org/chromium/components/webapps/AddToHomescreenDialogViewTest.java
+++ b/components/webapps/browser/android/java/src/org/chromium/components/webapps/AddToHomescreenDialogViewTest.java
@@ -269,6 +269,8 @@
         PropertyModel shownDialogModel = mModalDialogManager.getShownDialogModel();
 
         for (int i = 0; i <= AppType.MAX_VALUE; i++) {
+            if (!isDialogSupportedForAppType(i)) continue;
+
             mAddToHomescreenDialogView.setType(i);
 
             mAddToHomescreenDialogView.setTitle("");
@@ -294,6 +296,8 @@
         mAddToHomescreenDialogView.setTitle(TEST_TITLE);
 
         for (int i = 0; i <= AppType.MAX_VALUE; i++) {
+            if (!isDialogSupportedForAppType(i)) continue;
+
             mAddToHomescreenDialogView.setType(i);
             TextView titleText = mAddToHomescreenDialogView.getAppNameView();
             // Only run when title is editable.
@@ -414,4 +418,9 @@
                 .getText()
                 .toString();
     }
+
+    private boolean isDialogSupportedForAppType(@AppType int appType) {
+        // AddToHomescreenDialog is not supposed to be shown for auto-minted TWAs.
+        return appType != AppType.TWA;
+    }
 }
diff --git a/components/webapps/browser/android/java/src/org/chromium/components/webapps/pwa_universal_install/PwaUniversalInstallBottomSheetCoordinator.java b/components/webapps/browser/android/java/src/org/chromium/components/webapps/pwa_universal_install/PwaUniversalInstallBottomSheetCoordinator.java
index 1e844735..82cbeb8 100644
--- a/components/webapps/browser/android/java/src/org/chromium/components/webapps/pwa_universal_install/PwaUniversalInstallBottomSheetCoordinator.java
+++ b/components/webapps/browser/android/java/src/org/chromium/components/webapps/pwa_universal_install/PwaUniversalInstallBottomSheetCoordinator.java
@@ -158,6 +158,7 @@
             switch (mAppType) {
                 case AppType.WEBAPK:
                 case AppType.WEBAPK_DIY:
+                case AppType.TWA:
                     mInstallCallback.run();
                     break;
                 case AppType.SHORTCUT:
@@ -239,6 +240,9 @@
     private void logFetchTimeMetrics(@AppType int appType, long fetchDuration) {
         switch (appType) {
             case AppType.WEBAPK:
+            case AppType.TWA:
+                // Since the install criteria of these two app types are the same, logging to the
+                // same histogram.
                 RecordHistogram.recordLongTimesHistogram(
                         "WebApk.UniversalInstall.WebApk.AppDataFetchTime", fetchDuration);
                 break;
@@ -274,7 +278,7 @@
                     .getModel()
                     .set(
                             PwaUniversalInstallProperties.VIEW_STATE,
-                            (appType == AppType.WEBAPK || appType == AppType.WEBAPK_DIY)
+                            isInstallable(appType)
                                     ? PwaUniversalInstallProperties.ViewState.APP_IS_INSTALLABLE
                                     : PwaUniversalInstallProperties.ViewState
                                             .APP_IS_NOT_INSTALLABLE);
@@ -296,8 +300,7 @@
 
         // We haven't shown the dialog yet, so there's an opportunity to skip this dialog and
         // redirect straight to the Install App/Create Shortcut dialog.
-        if (mAppType == AppType.SHORTCUT
-                || (mIsRoot && (mAppType == AppType.WEBAPK || mAppType == AppType.WEBAPK_DIY))) {
+        if (mAppType == AppType.SHORTCUT || (mIsRoot && isInstallable(mAppType))) {
             switch (mAppType) {
                 case AppType.SHORTCUT:
                     mAddShortcutCallback.run();
@@ -332,6 +335,17 @@
         show(/* wasTimeout= */ false);
     }
 
+    private static boolean isInstallable(@AppType int appType) {
+        switch (appType) {
+            case AppType.WEBAPK:
+            case AppType.WEBAPK_DIY:
+            case AppType.TWA:
+                return true;
+            default:
+                return false;
+        }
+    }
+
     @NativeMethods
     interface Natives {
         void fetchAppData(PwaUniversalInstallBottomSheetCoordinator self, WebContents webContents);
diff --git a/components/webauthn/android/BUILD.gn b/components/webauthn/android/BUILD.gn
index c8caf6b..9d6a605a 100644
--- a/components/webauthn/android/BUILD.gn
+++ b/components/webauthn/android/BUILD.gn
@@ -38,7 +38,6 @@
     "java/src/org/chromium/components/webauthn/AuthenticatorErrorResponseCallback.java",
     "java/src/org/chromium/components/webauthn/AuthenticatorFactory.java",
     "java/src/org/chromium/components/webauthn/AuthenticatorImpl.java",
-    "java/src/org/chromium/components/webauthn/AuthenticatorReportResponseCallback.java",
     "java/src/org/chromium/components/webauthn/Barrier.java",
     "java/src/org/chromium/components/webauthn/CreateConfirmationUiDelegate.java",
     "java/src/org/chromium/components/webauthn/Fido2Api.java",
@@ -46,15 +45,13 @@
     "java/src/org/chromium/components/webauthn/Fido2ApiCallHelper.java",
     "java/src/org/chromium/components/webauthn/Fido2CredentialRequest.java",
     "java/src/org/chromium/components/webauthn/FidoIntentSender.java",
-    "java/src/org/chromium/components/webauthn/GetCredentialResponseCallback.java",
-    "java/src/org/chromium/components/webauthn/GetMatchingCredentialIdsResponseCallback.java",
+    "java/src/org/chromium/components/webauthn/GetMatchingCredentialIdsDelegate.java",
     "java/src/org/chromium/components/webauthn/GmsCoreGetCredentialsHelper.java",
     "java/src/org/chromium/components/webauthn/GmsCoreUtils.java",
     "java/src/org/chromium/components/webauthn/GpmBrowserOptionsHelper.java",
     "java/src/org/chromium/components/webauthn/IdentityCredentialsHelper.java",
     "java/src/org/chromium/components/webauthn/InternalAuthenticator.java",
     "java/src/org/chromium/components/webauthn/IsUvpaaResponseCallback.java",
-    "java/src/org/chromium/components/webauthn/MakeCredentialResponseCallback.java",
     "java/src/org/chromium/components/webauthn/RecordOutcomeCallback.java",
     "java/src/org/chromium/components/webauthn/WebauthnBrowserBridge.java",
     "java/src/org/chromium/components/webauthn/WebauthnCredentialDetails.java",
@@ -62,6 +59,8 @@
     "java/src/org/chromium/components/webauthn/WebauthnFeatures.java",
     "java/src/org/chromium/components/webauthn/WebauthnLogger.java",
     "java/src/org/chromium/components/webauthn/WebauthnModeProvider.java",
+    "java/src/org/chromium/components/webauthn/WebauthnRequestCallback.java",
+    "java/src/org/chromium/components/webauthn/WebauthnRequestResponse.java",
     "java/src/org/chromium/components/webauthn/cred_man/AppCredManRequestDecorator.java",
     "java/src/org/chromium/components/webauthn/cred_man/BrowserCredManRequestDecorator.java",
     "java/src/org/chromium/components/webauthn/cred_man/CredManCreateCredentialRequestHelper.java",
@@ -205,6 +204,7 @@
     "junit/src/org/chromium/components/webauthn/GmsCoreGetCredentialsHelperRobolectricTest.java",
     "junit/src/org/chromium/components/webauthn/IdentityCredentialsHelperRobolectricTest.java",
     "junit/src/org/chromium/components/webauthn/WebauthnModeProviderRobolectricTest.java",
+    "junit/src/org/chromium/components/webauthn/WebauthnRequestCallbackRobolectricTest.java",
     "junit/src/org/chromium/components/webauthn/cred_man/CredManCreateCredentialRequestHelperRobolectricTest.java",
     "junit/src/org/chromium/components/webauthn/cred_man/CredManGetCredentialRequestHelperRobolectricTest.java",
     "junit/src/org/chromium/components/webauthn/cred_man/CredManHelperRobolectricTest.java",
diff --git a/components/webauthn/android/java/src/org/chromium/components/webauthn/AuthenticationContextProvider.java b/components/webauthn/android/java/src/org/chromium/components/webauthn/AuthenticationContextProvider.java
index 230ab59..91e2bd1 100644
--- a/components/webauthn/android/java/src/org/chromium/components/webauthn/AuthenticationContextProvider.java
+++ b/components/webauthn/android/java/src/org/chromium/components/webauthn/AuthenticationContextProvider.java
@@ -20,4 +20,13 @@
     FidoIntentSender getIntentSender();
 
     @Nullable WebContents getWebContents();
+
+    /**
+     * Returns the request callback for the current WebAuthn operation.
+     *
+     * <p>This is used to access the request callback for the current WebAuthn operation. It is not
+     * meant to be cached since the Authenticator interface can be reset and hence the callback will
+     * be invalid.
+     */
+    @Nullable WebauthnRequestCallback getRequestCallback();
 }
diff --git a/components/webauthn/android/java/src/org/chromium/components/webauthn/AuthenticatorImpl.java b/components/webauthn/android/java/src/org/chromium/components/webauthn/AuthenticatorImpl.java
index fc5f8dc..a091a4f 100644
--- a/components/webauthn/android/java/src/org/chromium/components/webauthn/AuthenticatorImpl.java
+++ b/components/webauthn/android/java/src/org/chromium/components/webauthn/AuthenticatorImpl.java
@@ -21,12 +21,7 @@
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.blink.mojom.Authenticator;
 import org.chromium.blink.mojom.AuthenticatorStatus;
-import org.chromium.blink.mojom.CredentialInfo;
-import org.chromium.blink.mojom.GetAssertionAuthenticatorResponse;
-import org.chromium.blink.mojom.GetAssertionResponse;
 import org.chromium.blink.mojom.GetCredentialOptions;
-import org.chromium.blink.mojom.GetCredentialResponse;
-import org.chromium.blink.mojom.MakeCredentialAuthenticatorResponse;
 import org.chromium.blink.mojom.Mediation;
 import org.chromium.blink.mojom.PaymentOptions;
 import org.chromium.blink.mojom.PublicKeyCredentialCreationOptions;
@@ -58,9 +53,6 @@
     private final @Nullable RenderFrameHost mRenderFrameHost;
     private final @Nullable CreateConfirmationUiDelegate mCreateConfirmationUiDelegate;
 
-    /** Ensures only one request is processed at a time. */
-    private boolean mIsOperationPending;
-
     /**
      * The origin of the request. This may be overridden by an internal request from the browser
      * process. <code>mOrigin</code> will be set when a RenderFrameHost is provided at construction
@@ -74,9 +66,7 @@
     /** The payment information to be added to the "clientDataJson". */
     private @Nullable PaymentOptions mPayment;
 
-    private @Nullable MakeCredential_Response mMakeCredentialCallback;
-    private @Nullable GetCredential_Response mGetCredentialCallback;
-    private @Nullable Report_Response mReportCallback;
+    private @Nullable WebauthnRequestCallback mRequestCallback;
     private @Nullable Fido2CredentialRequest mPendingFido2CredentialRequest;
     private final Set<Fido2CredentialRequest> mUnclosedFido2CredentialRequests = new HashSet<>();
 
@@ -87,7 +77,7 @@
 
     // StaticFieldLeak complains that this is a memory leak because
     // `Fido2CredentialRequest` contains a `Context`. But this field is only
-    // used in tests so a memory leak is irrelevent.
+    // used in tests so a memory leak is irrelevant.
     @SuppressLint("StaticFieldLeak")
     private static @Nullable Fido2CredentialRequest sFido2CredentialRequestOverrideForTesting;
 
@@ -131,6 +121,7 @@
 
     private Fido2CredentialRequest getFido2CredentialRequest() {
         if (sFido2CredentialRequestOverrideForTesting != null) {
+            sFido2CredentialRequestOverrideForTesting.setAuthenticationContextProvider(this);
             return sFido2CredentialRequestOverrideForTesting;
         }
         Fido2CredentialRequest request = new Fido2CredentialRequest(this);
@@ -160,20 +151,25 @@
             PublicKeyCredentialCreationOptions options, MakeCredential_Response callback) {
         assert mIntentSender != null;
         assert mRenderFrameHost != null;
-        if (mIsOperationPending) {
-            callback.call(AuthenticatorStatus.PENDING_REQUEST, null, null);
+        WebauthnRequestCallback requestCallback =
+                WebauthnRequestCallback.forMakeCredential(callback, this::recordOutcomeEvent);
+        if (mRequestCallback != null) {
+            requestCallback.onComplete(
+                    WebauthnRequestResponse.forFailedMakeCredential(
+                            AuthenticatorStatus.PENDING_REQUEST, null));
             return;
         }
-
         log(TAG, "makeCredential");
 
         mIsPaymentRequest = options.isPaymentCredentialCreation;
-        mMakeCredentialCallback = callback;
-        mIsOperationPending = true;
+        mRequestCallback = requestCallback;
+        mRequestCallback.setCompletionCallback(this::cleanupRequest);
         if (!GmsCoreUtils.isWebauthnSupported()
                 || (!isChrome(mWebContents) && !GmsCoreUtils.isResultReceiverSupported())) {
-            recordOutcomeEvent(MakeCredentialOutcome.OTHER_FAILURE);
-            onError(AuthenticatorStatus.NOT_IMPLEMENTED);
+            mRequestCallback.onComplete(
+                    WebauthnRequestResponse.forFailedMakeCredential(
+                            AuthenticatorStatus.NOT_IMPLEMENTED,
+                            MakeCredentialOutcome.OTHER_FAILURE));
             return;
         }
 
@@ -185,8 +181,11 @@
             if (!mCreateConfirmationUiDelegate.show(
                     () -> continueMakeCredential(options),
                     () -> {
-                        recordOutcomeEvent(MakeCredentialOutcome.USER_CANCELLATION);
-                        onError(AuthenticatorStatus.NOT_ALLOWED_ERROR);
+                        assumeNonNull(mRequestCallback)
+                                .onComplete(
+                                        WebauthnRequestResponse.forFailedMakeCredential(
+                                                AuthenticatorStatus.NOT_ALLOWED_ERROR,
+                                                MakeCredentialOutcome.USER_CANCELLATION));
                     })) {
                 continueMakeCredential(options);
             }
@@ -199,14 +198,7 @@
         log(TAG, "continueMakeCredential");
         mPendingFido2CredentialRequest = getFido2CredentialRequest();
         mPendingFido2CredentialRequest.handleMakeCredentialRequest(
-                options,
-                maybeCreateBrowserOptions(),
-                assertNonNull(mOrigin),
-                mTopOrigin,
-                mPayment,
-                this::onRegisterResponse,
-                this::onError,
-                this::recordOutcomeEvent);
+                options, maybeCreateBrowserOptions(), assertNonNull(mOrigin), mTopOrigin, mPayment);
     }
 
     private @Nullable Bundle maybeCreateBrowserOptions() {
@@ -222,15 +214,18 @@
     public void getCredential(GetCredentialOptions options, GetCredential_Response callback) {
         assert mIntentSender != null;
         assert mRenderFrameHost != null;
-        if (mIsOperationPending) {
-            callback.call(
-                    getCredentialResponseForAssertion(AuthenticatorStatus.PENDING_REQUEST, null));
+        WebauthnRequestCallback requestCallback =
+                WebauthnRequestCallback.forGetCredential(callback, this::recordOutcomeEvent);
+        if (mRequestCallback != null) {
+            requestCallback.onComplete(
+                    WebauthnRequestResponse.forFailedGetCredential(
+                            AuthenticatorStatus.PENDING_REQUEST, null));
             return;
         }
         log(TAG, "getCredential");
 
-        mGetCredentialCallback = callback;
-        mIsOperationPending = true;
+        mRequestCallback = requestCallback;
+        mRequestCallback.setCompletionCallback(this::cleanupRequest);
         mIsPaymentRequest = mPayment != null;
         mIsConditionalRequest = options.mediation == Mediation.CONDITIONAL;
         mIsImmediateRequest = options.mediation == Mediation.IMMEDIATE;
@@ -238,21 +233,17 @@
         if (!GmsCoreUtils.isWebauthnSupported()
                 || (!isChrome(mWebContents) && !GmsCoreUtils.isResultReceiverSupported())
                 || options.publicKey == null) {
-            recordOutcomeEvent(GetAssertionOutcome.OTHER_FAILURE);
-            onError(AuthenticatorStatus.NOT_IMPLEMENTED);
+            mRequestCallback.onComplete(
+                    WebauthnRequestResponse.forFailedGetCredential(
+                            AuthenticatorStatus.NOT_IMPLEMENTED,
+                            GetAssertionOutcome.OTHER_FAILURE));
             return;
         }
         assumeNonNull(options.publicKey);
 
         mPendingFido2CredentialRequest = getFido2CredentialRequest();
         mPendingFido2CredentialRequest.handleGetCredentialRequest(
-                options,
-                assertNonNull(mOrigin),
-                mTopOrigin,
-                mPayment,
-                this::onCredentialResponse,
-                this::onError,
-                this::recordOutcomeEvent);
+                options, assertNonNull(mOrigin), mTopOrigin, mPayment);
     }
 
     @Override
@@ -263,17 +254,18 @@
             return;
         }
 
-        if (mIsOperationPending) {
-            callback.call(AuthenticatorStatus.PENDING_REQUEST, null);
+        WebauthnRequestCallback requestCallback = WebauthnRequestCallback.forReport(callback);
+        if (mRequestCallback != null) {
+            requestCallback.onComplete(
+                    WebauthnRequestResponse.forReport(AuthenticatorStatus.PENDING_REQUEST));
             return;
         }
 
-        mReportCallback = callback;
-        mIsOperationPending = true;
+        mRequestCallback = requestCallback;
+        mRequestCallback.setCompletionCallback(this::cleanupRequest);
 
         mPendingFido2CredentialRequest = getFido2CredentialRequest();
-        mPendingFido2CredentialRequest.handleReportRequest(
-                options, assertNonNull(mOrigin), this::onReportComplete);
+        mPendingFido2CredentialRequest.handleReportRequest(options, assertNonNull(mOrigin));
     }
 
     private boolean couldSupportConditionalMediation() {
@@ -421,20 +413,12 @@
             String relyingPartyId,
             byte[][] credentialIds,
             boolean requireThirdPartyPayment,
-            GetMatchingCredentialIdsResponseCallback callback) {
+            GetMatchingCredentialIdsDelegate.ResponseCallback callback) {
         log(TAG, "getMatchingCredentialIds");
-        if (!GmsCoreUtils.isGetMatchingCredentialIdsSupported()) {
-            callback.onResponse(new ArrayList<byte[]>());
-            return;
-        }
 
-        getFido2CredentialRequest()
-                .handleGetMatchingCredentialIdsRequest(
-                        relyingPartyId,
-                        credentialIds,
-                        requireThirdPartyPayment,
-                        callback,
-                        this::onError);
+        GetMatchingCredentialIdsDelegate.getInstance()
+                .getMatchingCredentialIds(
+                        this, relyingPartyId, credentialIds, requireThirdPartyPayment, callback);
     }
 
     @Override
@@ -460,7 +444,9 @@
         // This is not implemented for anything other than getAssertion requests, since there is
         // no way to cancel a request that has already triggered GMSCore or CredMan UI. Get
         // requests can be cancelled in situations such as while pending credential enumeration.
-        if (!mIsOperationPending || mGetCredentialCallback == null) {
+        if (mRequestCallback == null
+                || mRequestCallback.getCallbackType()
+                        != WebauthnRequestCallback.CallbackType.GET_CREDENTIAL) {
             return;
         }
 
@@ -468,65 +454,6 @@
         mPendingFido2CredentialRequest.cancelGetAssertion();
     }
 
-    /** Callbacks for receiving responses from the internal handlers. */
-    public void onRegisterResponse(int status, MakeCredentialAuthenticatorResponse response) {
-        log(TAG, "makeCredential completed with status: " + status);
-        // In case mojo pipe is closed due to the page begin destroyed while waiting for response.
-        if (!mIsOperationPending) return;
-
-        assert mMakeCredentialCallback != null;
-        assert status == AuthenticatorStatus.SUCCESS;
-        mMakeCredentialCallback.call(AuthenticatorStatus.SUCCESS, response, null);
-        cleanupRequest();
-    }
-
-    public void onCredentialResponse(
-            @Nullable GetAssertionAuthenticatorResponse assertionResponse,
-            @Nullable CredentialInfo passwordCredential) {
-        log(TAG, "getCredential completed.");
-        assert assertionResponse == null ^ passwordCredential == null;
-
-        // In case mojo pipe is closed due to the page begin destroyed while waiting for response.
-        if (!mIsOperationPending) return;
-
-        assert mGetCredentialCallback != null;
-        if (assertionResponse != null) {
-            mGetCredentialCallback.call(
-                    getCredentialResponseForAssertion(
-                            AuthenticatorStatus.SUCCESS, assertionResponse));
-        } else {
-            assumeNonNull(passwordCredential);
-            mGetCredentialCallback.call(getCredentialResponseForPassword(passwordCredential));
-        }
-        cleanupRequest();
-    }
-
-    public void onReportComplete(int status) {
-        log(TAG, "onReportComplete completed with status: " + status);
-        // In case mojo pipe is closed due to the page begin destroyed while waiting for response.
-        if (!mIsOperationPending || mReportCallback == null) return;
-
-        mReportCallback.call(status, null);
-        cleanupRequest();
-    }
-
-    public void onError(Integer status) {
-        log(TAG, "Request completed with error: " + status);
-        // In case mojo pipe is closed due to the page begin destroyed while waiting for response.
-        if (!mIsOperationPending) return;
-
-        assert ((mMakeCredentialCallback != null && mGetCredentialCallback == null)
-                || (mMakeCredentialCallback == null && mGetCredentialCallback != null));
-        assert status != AuthenticatorStatus.ERROR_WITH_DOM_EXCEPTION_DETAILS;
-        if (mMakeCredentialCallback != null) {
-            mMakeCredentialCallback.call(status, null, null);
-        } else if (mGetCredentialCallback != null) {
-            mGetCredentialCallback.call(getCredentialResponseForAssertion(status, null));
-        }
-        if (mPendingFido2CredentialRequest != null) mPendingFido2CredentialRequest.destroyBridge();
-        cleanupRequest();
-    }
-
     /** Record outcome UKM at the request's completion time. */
     private void recordOutcomeEvent(int resultMetricValue) {
         // mWebContents can be null in tests.
@@ -535,10 +462,14 @@
         }
         String event;
         String resultMetricName;
-        if (mGetCredentialCallback != null) {
+        if (mRequestCallback != null
+                && mRequestCallback.getCallbackType()
+                        == WebauthnRequestCallback.CallbackType.GET_CREDENTIAL) {
             event = "WebAuthn.SignCompletion";
             resultMetricName = "SignCompletionResult";
-        } else if (mMakeCredentialCallback != null) {
+        } else if (mRequestCallback != null
+                && mRequestCallback.getCallbackType()
+                        == WebauthnRequestCallback.CallbackType.MAKE_CREDENTIAL) {
             event = "WebAuthn.RegisterCompletion";
             resultMetricName = "RegisterCompletionResult";
         } else {
@@ -560,9 +491,7 @@
     }
 
     private void cleanupRequest() {
-        mIsOperationPending = false;
-        mMakeCredentialCallback = null;
-        mGetCredentialCallback = null;
+        mRequestCallback = null;
         mPendingFido2CredentialRequest = null;
     }
 
@@ -600,6 +529,11 @@
         return mWebContents;
     }
 
+    @Override
+    public @Nullable WebauthnRequestCallback getRequestCallback() {
+        return mRequestCallback;
+    }
+
     /** Implements {@link IntentSender} using a {@link WindowAndroid}. */
     public static class WindowIntentSender implements FidoIntentSender {
         private final @Nullable WindowAndroid mWindow;
@@ -630,21 +564,4 @@
             }
         }
     }
-
-    private GetCredentialResponse getCredentialResponseForAssertion(
-            int status, @Nullable GetAssertionAuthenticatorResponse response) {
-        GetCredentialResponse finalResponse = new GetCredentialResponse();
-        GetAssertionResponse assertionResponse = new GetAssertionResponse();
-        assertionResponse.credential = response;
-        assertionResponse.status = status;
-        finalResponse.setGetAssertionResponse(assertionResponse);
-        return finalResponse;
-    }
-
-    private GetCredentialResponse getCredentialResponseForPassword(
-            CredentialInfo passwordCredential) {
-        GetCredentialResponse response = new GetCredentialResponse();
-        response.setPasswordResponse(passwordCredential);
-        return response;
-    }
 }
diff --git a/components/webauthn/android/java/src/org/chromium/components/webauthn/AuthenticatorReportResponseCallback.java b/components/webauthn/android/java/src/org/chromium/components/webauthn/AuthenticatorReportResponseCallback.java
deleted file mode 100644
index d048f40..0000000
--- a/components/webauthn/android/java/src/org/chromium/components/webauthn/AuthenticatorReportResponseCallback.java
+++ /dev/null
@@ -1,13 +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.
-
-package org.chromium.components.webauthn;
-
-import org.chromium.build.annotations.NullMarked;
-
-/** Callback interface for handling any errors from register or sign requests. */
-@NullMarked
-public interface AuthenticatorReportResponseCallback {
-    void onComplete(int status);
-}
diff --git a/components/webauthn/android/java/src/org/chromium/components/webauthn/Barrier.java b/components/webauthn/android/java/src/org/chromium/components/webauthn/Barrier.java
index fc25e728..c1dc845 100644
--- a/components/webauthn/android/java/src/org/chromium/components/webauthn/Barrier.java
+++ b/components/webauthn/android/java/src/org/chromium/components/webauthn/Barrier.java
@@ -8,7 +8,6 @@
 
 import androidx.annotation.IntDef;
 
-import org.chromium.base.Callback;
 import org.chromium.blink.mojom.AuthenticatorStatus;
 import org.chromium.build.annotations.NullMarked;
 import org.chromium.build.annotations.Nullable;
@@ -57,7 +56,7 @@
         int RUN_BOTH = 2;
     }
 
-    private final Callback<Integer> mErrorCallback;
+    private final AuthenticationContextProvider mAuthenticationContextProvider;
     @Nullable private Runnable mFido2ApiRunnable;
     @Nullable private Runnable mCredManRunnable;
     private @Status int mFido2ApiStatus;
@@ -67,8 +66,8 @@
     private boolean mCredManCancelled;
     private boolean mIsImmediateIncognito;
 
-    public Barrier(Callback<Integer> errorCallback) {
-        mErrorCallback = errorCallback;
+    public Barrier(AuthenticationContextProvider authenticationContextProvider) {
+        mAuthenticationContextProvider = authenticationContextProvider;
         mFido2ApiStatus = Status.NONE;
         mCredManStatus = Status.NONE;
     }
@@ -116,10 +115,10 @@
                 mCredManStatus = Status.FAILURE;
                 break;
             case Status.NONE:
-                mErrorCallback.onResult(error);
+                onError(error);
                 break;
             case Status.FAILURE:
-                mErrorCallback.onResult(mFido2ApiError);
+                onError(mFido2ApiError);
                 break;
         }
     }
@@ -151,14 +150,14 @@
                 break;
             case Status.NONE:
             case Status.FAILURE:
-                mErrorCallback.onResult(error);
+                onError(error);
                 break;
         }
     }
 
     public void onCredManCancelled(int error) {
         if (mFido2ApiStatus == Status.NONE || mFido2ApiCancelled) {
-            mErrorCallback.onResult(error);
+            onError(error);
             mFido2ApiCancelled = false;
             return;
         }
@@ -175,7 +174,7 @@
      */
     public void onFido2ApiCancelled(int error) {
         if (mCredManStatus == Status.NONE || mCredManCancelled) {
-            mErrorCallback.onResult(error);
+            onError(error);
             mCredManCancelled = false;
             return;
         }
@@ -196,7 +195,7 @@
 
     private void maybeRunSuccessCallbacks(@CallbacksToRun int callbacks) {
         if (mIsImmediateIncognito) {
-            mErrorCallback.onResult(AuthenticatorStatus.NOT_ALLOWED_ERROR);
+            onError(AuthenticatorStatus.NOT_ALLOWED_ERROR);
             return;
         }
 
@@ -217,4 +216,11 @@
         mCredManCancelled = false;
         mFido2ApiCancelled = false;
     }
+
+    private void onError(int error) {
+        if (mAuthenticationContextProvider.getRequestCallback() == null) return;
+        mAuthenticationContextProvider
+                .getRequestCallback()
+                .onComplete(WebauthnRequestResponse.forFailedGetCredential(error, null));
+    }
 }
diff --git a/components/webauthn/android/java/src/org/chromium/components/webauthn/Fido2ApiCallHelper.java b/components/webauthn/android/java/src/org/chromium/components/webauthn/Fido2ApiCallHelper.java
index db4e182..8c68c4b 100644
--- a/components/webauthn/android/java/src/org/chromium/components/webauthn/Fido2ApiCallHelper.java
+++ b/components/webauthn/android/java/src/org/chromium/components/webauthn/Fido2ApiCallHelper.java
@@ -173,4 +173,41 @@
         task.addOnSuccessListener(GmsCoreUtils.wrapSuccessCallback(successCallback));
         task.addOnFailureListener(GmsCoreUtils.wrapFailureCallback(failureCallback));
     }
+
+    public void invokeFido2HybridGetAssertion(
+            AuthenticationContextProvider authenticationContextProvider,
+            PublicKeyCredentialRequestOptions options,
+            Uri uri,
+            byte @Nullable [] clientDataHash,
+            OnSuccessListener<PendingIntent> successCallback,
+            OnFailureListener failureCallback) {
+        log(TAG, "invokeFido2HybridGetAssertion");
+        Fido2ApiCallParams params =
+                WebauthnModeProvider.getInstance()
+                        .getFido2ApiCallParams(authenticationContextProvider.getWebContents());
+        assertNonNull(authenticationContextProvider.getContext());
+        assertNonNull(params);
+        Fido2ApiCall call = new Fido2ApiCall(authenticationContextProvider.getContext(), params);
+        Parcel args = call.start();
+        String callbackDescriptor = params.mCallbackDescriptor;
+        Fido2ApiCall.PendingIntentResult result =
+                new Fido2ApiCall.PendingIntentResult(callbackDescriptor);
+        args.writeStrongBinder(result);
+        args.writeInt(1); // This indicates that the following options are present.
+        Fido2Api.appendBrowserGetAssertionOptionsToParcel(
+                options,
+                uri,
+                clientDataHash,
+                /* tunnelId= */ null,
+                /* resultReceiver= */ null,
+                args);
+        Task<PendingIntent> task =
+                call.run(
+                        Fido2ApiCall.METHOD_BROWSER_HYBRID_SIGN,
+                        Fido2ApiCall.TRANSACTION_HYBRID_SIGN,
+                        args,
+                        result);
+        task.addOnSuccessListener(GmsCoreUtils.wrapSuccessCallback(successCallback));
+        task.addOnFailureListener(GmsCoreUtils.wrapFailureCallback(failureCallback));
+    }
 }
diff --git a/components/webauthn/android/java/src/org/chromium/components/webauthn/Fido2CredentialRequest.java b/components/webauthn/android/java/src/org/chromium/components/webauthn/Fido2CredentialRequest.java
index ebf448a..7ed2c9ad 100644
--- a/components/webauthn/android/java/src/org/chromium/components/webauthn/Fido2CredentialRequest.java
+++ b/components/webauthn/android/java/src/org/chromium/components/webauthn/Fido2CredentialRequest.java
@@ -90,11 +90,7 @@
     // mPlayServicesAvailable caches whether the Play Services FIDO API is
     // available.
     private final boolean mPlayServicesAvailable;
-    private final AuthenticationContextProvider mAuthenticationContextProvider;
-    private @Nullable GetCredentialResponseCallback mGetCredentialCallback;
-    private @Nullable MakeCredentialResponseCallback mMakeCredentialCallback;
-    private @Nullable AuthenticatorErrorResponseCallback mErrorCallback;
-    private @Nullable RecordOutcomeCallback mRecordingCallback;
+    private AuthenticationContextProvider mAuthenticationContextProvider;
     private CredManHelper mCredManHelper;
     private final IdentityCredentialsHelper mIdentityCredentialsHelper;
     private Barrier mBarrier;
@@ -104,10 +100,7 @@
     private boolean mAppIdExtensionUsed;
     private boolean mEchoCredProps;
     private @Nullable WebauthnBrowserBridge mBrowserBridge;
-    // Values set when errors occur, for metrics recording.
-    private @GetAssertionOutcome int mGetAssertionErrorOutcome = GetAssertionOutcome.OTHER_FAILURE;
-    private @MakeCredentialOutcome int mMakeCredentialErrorOutcome =
-            MakeCredentialOutcome.OTHER_FAILURE;
+
     private RunnableTimer mImmediateTimer = new RunnableTimer();
 
     // Some modes do credential enumeration in advance of calling a platform API to get a passkey
@@ -156,47 +149,36 @@
         mCredManHelper =
                 new CredManHelper(mAuthenticationContextProvider, this, mPlayServicesAvailable);
         mIdentityCredentialsHelper = new IdentityCredentialsHelper(mAuthenticationContextProvider);
-        mBarrier = new Barrier(this::returnErrorAndResetCallback);
+        mBarrier = new Barrier(mAuthenticationContextProvider);
     }
 
-    private void recordOutcomeMetric() {
-        if (mRecordingCallback != null) {
-            int resultValue;
-            if (mGetCredentialCallback != null) {
-                resultValue = mGetAssertionErrorOutcome;
-            } else {
-                assert mMakeCredentialCallback != null;
-                resultValue = mMakeCredentialErrorOutcome;
-            }
-            mRecordingCallback.record(resultValue);
-            mRecordingCallback = null;
+    public void setAuthenticationContextProvider(
+            AuthenticationContextProvider authenticationContextProvider) {
+        mAuthenticationContextProvider = authenticationContextProvider;
+    }
+
+    private void returnErrorAndResetCallback(int errorCode, @Nullable Integer response) {
+        log(TAG, "returnErrorAndResetCallback: %d", errorCode);
+        WebauthnRequestCallback requestCallback =
+                mAuthenticationContextProvider.getRequestCallback();
+        if (requestCallback == null) return;
+        switch (requestCallback.getCallbackType()) {
+            case WebauthnRequestCallback.CallbackType.MAKE_CREDENTIAL:
+                requestCallback.onComplete(
+                        WebauthnRequestResponse.forFailedMakeCredential(errorCode, response));
+                break;
+            case WebauthnRequestCallback.CallbackType.GET_CREDENTIAL:
+                requestCallback.onComplete(
+                        WebauthnRequestResponse.forFailedGetCredential(errorCode, response));
+                break;
+            case WebauthnRequestCallback.CallbackType.REPORT:
+                requestCallback.onComplete(WebauthnRequestResponse.forReport(errorCode));
+                break;
+            default:
+                assert false : "Unknown callback type";
         }
     }
 
-    // Used by CredManHelper to record a specific outcome before calling
-    // returnErrorAndResetCallback.
-    private void setOutcomeAndReturnError(int error, @Nullable Integer metricsOutcome) {
-        if (metricsOutcome != null) {
-            if (mGetCredentialCallback != null) {
-                mGetAssertionErrorOutcome = metricsOutcome;
-            } else if (mMakeCredentialCallback != null) {
-                mMakeCredentialErrorOutcome = metricsOutcome;
-            }
-        }
-        returnErrorAndResetCallback(error);
-    }
-
-    private void returnErrorAndResetCallback(int error) {
-        recordOutcomeMetric();
-        stopImmediateTimer();
-        assert mErrorCallback != null;
-        if (mErrorCallback == null) return;
-        mErrorCallback.onError(error);
-        mErrorCallback = null;
-        mGetCredentialCallback = null;
-        mMakeCredentialCallback = null;
-    }
-
     private @Barrier.Mode int getBarrierMode() {
         @CredManSupport int support = CredManSupportProvider.getCredManSupport();
         @Barrier.Mode int mode;
@@ -228,33 +210,21 @@
      * @param origin The origin that made the WebAuthn call.
      * @param topOrigin The origin of the main frame.
      * @param paymentOptions The options set by Secure Payment Confirmation.
-     * @param callback Success callback.
-     * @param errorCallback Failure callback.
-     * @param recordingCallback Called for reporting error metrics with detailed reasons. This
-     *     should not be called when the operation is successful, because the Success callback does
-     *     this implicitly.
      */
-    @SuppressWarnings("NewApi")
     public void handleMakeCredentialRequest(
             PublicKeyCredentialCreationOptions options,
             @Nullable Bundle maybeBrowserOptions,
             Origin origin,
             @Nullable Origin topOrigin,
-            @Nullable PaymentOptions paymentOptions,
-            MakeCredentialResponseCallback callback,
-            AuthenticatorErrorResponseCallback errorCallback,
-            RecordOutcomeCallback recordingCallback) {
+            @Nullable PaymentOptions paymentOptions) {
         log(TAG, "handleMakeCredentialRequest");
         RenderFrameHost frameHost = mAuthenticationContextProvider.getRenderFrameHost();
         assert frameHost != null;
-        assert mMakeCredentialCallback == null && mErrorCallback == null;
+        assert mAuthenticationContextProvider.getRequestCallback() != null;
         assert (paymentOptions != null)
                 == (options.isPaymentCredentialCreation
                         && ContentFeatureMap.isEnabled(
                                 BlinkFeatures.SECURE_PAYMENT_CONFIRMATION_BROWSER_BOUND_KEYS));
-        mMakeCredentialCallback = callback;
-        mErrorCallback = errorCallback;
-        mRecordingCallback = recordingCallback;
         @Nullable Origin remoteDesktopOrigin = null;
         if (options.remoteDesktopClientOverride != null
                 && isChrome(mAuthenticationContextProvider.getWebContents())) {
@@ -272,8 +242,8 @@
                 remoteDesktopOrigin,
                 (result) -> {
                     if (result.securityCheckResult != AuthenticatorStatus.SUCCESS) {
-                        mMakeCredentialErrorOutcome = MakeCredentialOutcome.SECURITY_ERROR;
-                        returnErrorAndResetCallback(result.securityCheckResult);
+                        returnErrorAndResetCallback(
+                                result.securityCheckResult, MakeCredentialOutcome.SECURITY_ERROR);
                         return;
                     }
                     continueMakeCredentialRequestAfterRpIdValidation(
@@ -325,19 +295,14 @@
                             options.relyingParty.name,
                             topOrigin);
             if (clientDataHash == null) {
-                returnErrorAndResetCallback(AuthenticatorStatus.NOT_ALLOWED_ERROR);
+                returnErrorAndResetCallback(AuthenticatorStatus.NOT_ALLOWED_ERROR, null);
                 return;
             }
         }
 
         if (options.isConditional) {
             mIdentityCredentialsHelper.handleConditionalCreateRequest(
-                    options,
-                    convertOriginToString(origin),
-                    mClientDataJson,
-                    clientDataHash,
-                    assertNonNull(mMakeCredentialCallback),
-                    this::setOutcomeAndReturnError);
+                    options, convertOriginToString(origin), mClientDataJson, clientDataHash);
             return;
         }
 
@@ -345,7 +310,7 @@
             if (CredManSupportProvider.getCredManSupportForWebView() == CredManSupport.DISABLED) {
                 if (!mPlayServicesAvailable) {
                     logError(TAG, "Google Play Services' Fido2 API is not available.");
-                    returnErrorAndResetCallback(AuthenticatorStatus.UNKNOWN_ERROR);
+                    returnErrorAndResetCallback(AuthenticatorStatus.UNKNOWN_ERROR, null);
                     return;
                 }
                 try {
@@ -360,8 +325,9 @@
                                     this::onGotPendingIntent,
                                     this::onBinderCallException);
                 } catch (NoSuchAlgorithmException e) {
-                    mMakeCredentialErrorOutcome = MakeCredentialOutcome.ALGORITHM_NOT_SUPPORTED;
-                    returnErrorAndResetCallback(AuthenticatorStatus.ALGORITHM_UNSUPPORTED);
+                    returnErrorAndResetCallback(
+                            AuthenticatorStatus.ALGORITHM_UNSUPPORTED,
+                            MakeCredentialOutcome.ALGORITHM_NOT_SUPPORTED);
                     return;
                 }
                 return;
@@ -371,10 +337,10 @@
                             options,
                             convertOriginToString(origin),
                             mClientDataJson,
-                            clientDataHash,
-                            mMakeCredentialCallback,
-                            this::setOutcomeAndReturnError);
-            if (result != AuthenticatorStatus.SUCCESS) returnErrorAndResetCallback(result);
+                            clientDataHash);
+            if (result != AuthenticatorStatus.SUCCESS) {
+                returnErrorAndResetCallback(result, null);
+            }
             return;
         }
 
@@ -399,16 +365,16 @@
                             options,
                             convertOriginToString(origin),
                             mClientDataJson,
-                            clientDataHash,
-                            mMakeCredentialCallback,
-                            this::setOutcomeAndReturnError);
-            if (result != AuthenticatorStatus.SUCCESS) returnErrorAndResetCallback(result);
+                            clientDataHash);
+            if (result != AuthenticatorStatus.SUCCESS) {
+                returnErrorAndResetCallback(result, null);
+            }
             return;
         }
 
         if (!mPlayServicesAvailable) {
             logError(TAG, "Google Play Services' Fido2PrivilegedApi is not available.");
-            returnErrorAndResetCallback(AuthenticatorStatus.UNKNOWN_ERROR);
+            returnErrorAndResetCallback(AuthenticatorStatus.UNKNOWN_ERROR, null);
             return;
         }
 
@@ -424,8 +390,9 @@
                             this::onGotPendingIntent,
                             this::onBinderCallException);
         } catch (NoSuchAlgorithmException e) {
-            mMakeCredentialErrorOutcome = MakeCredentialOutcome.ALGORITHM_NOT_SUPPORTED;
-            returnErrorAndResetCallback(AuthenticatorStatus.ALGORITHM_UNSUPPORTED);
+            returnErrorAndResetCallback(
+                    AuthenticatorStatus.ALGORITHM_UNSUPPORTED,
+                    MakeCredentialOutcome.ALGORITHM_NOT_SUPPORTED);
             return;
         }
     }
@@ -439,34 +406,21 @@
      * @param topOrigin The origin of the main frame.
      * @param payment Options for Secure Payment Confirmation. May only be non-null if `frameHost`
      *     is non-null.
-     * @param callback Success callback.
-     * @param errorCallback Failure callback.
-     * @param recordingCallback Called for reporting error metrics with detailed reasons. This
-     *     should not be called when the operation is successful, because the Success callback does
-     *     this implicitly.
      */
     @SuppressWarnings("NewApi")
     public void handleGetCredentialRequest(
             GetCredentialOptions options,
             Origin origin,
             @Nullable Origin topOrigin,
-            @Nullable PaymentOptions payment,
-            GetCredentialResponseCallback callback,
-            AuthenticatorErrorResponseCallback errorCallback,
-            RecordOutcomeCallback recordingCallback) {
+            @Nullable PaymentOptions payment) {
         log(TAG, "handleGetCredentialRequest");
         RenderFrameHost frameHost = mAuthenticationContextProvider.getRenderFrameHost();
         assert frameHost != null;
-        assert mGetCredentialCallback == null && mErrorCallback == null;
-        mGetCredentialCallback = callback;
-        mErrorCallback = errorCallback;
-        mRecordingCallback = recordingCallback;
-
         PublicKeyCredentialRequestOptions publicKeyOptions = assumeNonNull(options.publicKey);
 
         // TODO(https://crbug.com/381219428): Handle challenge_url.
         if (publicKeyOptions.challenge == null) {
-            returnErrorAndResetCallback(AuthenticatorStatus.NOT_IMPLEMENTED);
+            returnErrorAndResetCallback(AuthenticatorStatus.NOT_IMPLEMENTED, null);
             return;
         }
 
@@ -475,8 +429,8 @@
             if (publicKeyOptions.allowCredentials != null
                     && publicKeyOptions.allowCredentials.length != 0) {
                 log(TAG, "Immediate Get called with non-empty allowCredentials");
-                mGetAssertionErrorOutcome = GetAssertionOutcome.SECURITY_ERROR;
-                returnErrorAndResetCallback(AuthenticatorStatus.NOT_ALLOWED_ERROR);
+                returnErrorAndResetCallback(
+                        AuthenticatorStatus.NOT_ALLOWED_ERROR, GetAssertionOutcome.SECURITY_ERROR);
                 return;
             }
             if (webContents != null && webContents.isIncognito()) {
@@ -507,13 +461,13 @@
                             == CancellableUiState.CANCEL_PENDING_RP_ID_VALIDATION_COMPLETE) {
                         // This request was canceled while waiting for RP ID validation to
                         // complete.
-                        returnErrorAndResetCallback(AuthenticatorStatus.ABORT_ERROR);
+                        returnErrorAndResetCallback(AuthenticatorStatus.ABORT_ERROR, null);
                         return;
                     }
                     mCancellableUiState = CancellableUiState.NONE;
                     if (results.securityCheckResult != AuthenticatorStatus.SUCCESS) {
-                        mGetAssertionErrorOutcome = GetAssertionOutcome.SECURITY_ERROR;
-                        returnErrorAndResetCallback(results.securityCheckResult);
+                        returnErrorAndResetCallback(
+                                results.securityCheckResult, GetAssertionOutcome.SECURITY_ERROR);
                         return;
                     }
                     continueGetCredentialRequestAfterRpIdValidation(
@@ -579,7 +533,7 @@
                             publicKeyOptions.relyingPartyId,
                             topOrigin);
             if (clientDataHash == null) {
-                returnErrorAndResetCallback(AuthenticatorStatus.NOT_ALLOWED_ERROR);
+                returnErrorAndResetCallback(AuthenticatorStatus.NOT_ALLOWED_ERROR, null);
                 return;
             }
         } else {
@@ -588,13 +542,13 @@
 
         if (!isChrome(mAuthenticationContextProvider.getWebContents())) {
             if (options.mediation == Mediation.CONDITIONAL) {
-                returnErrorAndResetCallback(AuthenticatorStatus.NOT_IMPLEMENTED);
+                returnErrorAndResetCallback(AuthenticatorStatus.NOT_IMPLEMENTED, null);
                 return;
             }
             if (CredManSupportProvider.getCredManSupportForWebView() == CredManSupport.DISABLED) {
                 if (!mPlayServicesAvailable) {
                     logError(TAG, "Google Play Services' Fido2 Api is not available.");
-                    returnErrorAndResetCallback(AuthenticatorStatus.UNKNOWN_ERROR);
+                    returnErrorAndResetCallback(AuthenticatorStatus.UNKNOWN_ERROR, null);
                     return;
                 }
                 maybeDispatchGetAssertionRequest(options, callerOriginString, clientDataHash, null);
@@ -606,10 +560,10 @@
                             callerOriginString,
                             mClientDataJson,
                             clientDataHash,
-                            mGetCredentialCallback,
-                            this::setOutcomeAndReturnError,
                             /* ignoreGpm= */ false);
-            if (result != AuthenticatorStatus.SUCCESS) returnErrorAndResetCallback(result);
+            if (result != AuthenticatorStatus.SUCCESS) {
+                returnErrorAndResetCallback(result, null);
+            }
             return;
         }
 
@@ -627,8 +581,6 @@
                         convertOriginToString(origin),
                         mClientDataJson,
                         clientDataHash,
-                        mGetCredentialCallback,
-                        this::setOutcomeAndReturnError,
                         mBarrier,
                         this::stopImmediateTimer,
                         /* ignoreGpm= */ false);
@@ -664,17 +616,17 @@
                                 convertOriginToString(origin),
                                 mClientDataJson,
                                 clientDataHash,
-                                mGetCredentialCallback,
-                                this::setOutcomeAndReturnError,
                                 /* ignoreGpm= */ false);
-                if (response != AuthenticatorStatus.SUCCESS) returnErrorAndResetCallback(response);
+                if (response != AuthenticatorStatus.SUCCESS) {
+                    returnErrorAndResetCallback(response, null);
+                }
             }
             return;
         }
 
         if (!mPlayServicesAvailable) {
             logError(TAG, "Google Play Services' Fido2PrivilegedApi is not available.");
-            returnErrorAndResetCallback(AuthenticatorStatus.UNKNOWN_ERROR);
+            returnErrorAndResetCallback(AuthenticatorStatus.UNKNOWN_ERROR, null);
             return;
         }
 
@@ -683,7 +635,7 @@
         WebContents webContents = mAuthenticationContextProvider.getWebContents();
         if (options.mediation == Mediation.CONDITIONAL
                 && is(webContents, WebauthnMode.CHROME_3PP_ENABLED)) {
-            returnErrorAndResetCallback(AuthenticatorStatus.NOT_IMPLEMENTED);
+            returnErrorAndResetCallback(AuthenticatorStatus.NOT_IMPLEMENTED, null);
             return;
         }
 
@@ -699,8 +651,6 @@
                         callerOriginString,
                         mClientDataJson,
                         clientDataHash,
-                        mGetCredentialCallback,
-                        this::setOutcomeAndReturnError,
                         mBarrier,
                         null,
                         /* ignoreGpm= */ true);
@@ -824,17 +774,14 @@
                 });
     }
 
-    public void handleReportRequest(
-            PublicKeyCredentialReportOptions options,
-            Origin origin,
-            AuthenticatorReportResponseCallback callback) {
+    public void handleReportRequest(PublicKeyCredentialReportOptions options, Origin origin) {
         RenderFrameHost frameHost = mAuthenticationContextProvider.getRenderFrameHost();
         assert frameHost != null;
 
         if (options.unknownCredentialId == null
                 && options.allAcceptedCredentials == null
                 && options.currentUserDetails == null) {
-            callback.onComplete(AuthenticatorStatus.UNKNOWN_ERROR);
+            returnErrorAndResetCallback(AuthenticatorStatus.UNKNOWN_ERROR, null);
             return;
         }
 
@@ -842,66 +789,26 @@
                 options.relyingPartyId,
                 origin,
                 (results) -> {
+                    log(
+                            TAG,
+                            "handleReportRequest.onSecurityCheckComplete, isCrossOrigin: %b,"
+                                    + " securityCheckResult: %d",
+                            results.isCrossOrigin,
+                            results.securityCheckResult);
+
+                    WebauthnRequestCallback callback =
+                            assumeNonNull(mAuthenticationContextProvider.getRequestCallback());
                     if (results.securityCheckResult != AuthenticatorStatus.SUCCESS) {
-                        callback.onComplete(results.securityCheckResult);
+                        returnErrorAndResetCallback(results.securityCheckResult, null);
                         return;
                     }
                     mIdentityCredentialsHelper.handleReportRequest(
                             options, convertOriginToString(origin));
-                    callback.onComplete(AuthenticatorStatus.SUCCESS);
+                    callback.onComplete(
+                            WebauthnRequestResponse.forReport(AuthenticatorStatus.SUCCESS));
                 });
     }
 
-    public void handleGetMatchingCredentialIdsRequest(
-            String relyingPartyId,
-            byte[][] allowCredentialIds,
-            boolean requireThirdPartyPayment,
-            GetMatchingCredentialIdsResponseCallback callback,
-            AuthenticatorErrorResponseCallback errorCallback) {
-        log(TAG, "handleGetMatchingCredentialIdsRequest");
-        assert mErrorCallback == null;
-        mErrorCallback = errorCallback;
-
-        if (!mPlayServicesAvailable) {
-            logError(TAG, "Google Play Services' Fido2PrivilegedApi is not available.");
-            returnErrorAndResetCallback(AuthenticatorStatus.UNKNOWN_ERROR);
-            return;
-        }
-
-        GmsCoreGetCredentialsHelper.getInstance()
-                .getCredentials(
-                        mAuthenticationContextProvider,
-                        relyingPartyId,
-                        GmsCoreGetCredentialsHelper.Reason.GET_MATCHING_CREDENTIAL_IDS,
-                        (credentials) ->
-                                onGetMatchingCredentialIdsListReceived(
-                                        credentials,
-                                        allowCredentialIds,
-                                        requireThirdPartyPayment,
-                                        callback),
-                        this::onBinderCallException);
-    }
-
-    private void onGetMatchingCredentialIdsListReceived(
-            List<WebauthnCredentialDetails> retrievedCredentials,
-            byte[][] allowCredentialIds,
-            boolean requireThirdPartyPayment,
-            GetMatchingCredentialIdsResponseCallback callback) {
-        log(TAG, "onGetMatchingCredentialIdsListReceived");
-        List<byte[]> matchingCredentialIds = new ArrayList<>();
-        for (WebauthnCredentialDetails credential : retrievedCredentials) {
-            if (requireThirdPartyPayment && !credential.mIsPayment) continue;
-
-            for (byte[] allowedId : allowCredentialIds) {
-                if (Arrays.equals(allowedId, credential.mCredentialId)) {
-                    matchingCredentialIds.add(credential.mCredentialId);
-                    break;
-                }
-            }
-        }
-        callback.onResponse(matchingCredentialIds);
-    }
-
     public void overrideBrowserBridgeForTesting(WebauthnBrowserBridge bridge) {
         mBrowserBridge = bridge;
     }
@@ -979,7 +886,10 @@
             hybridCallback =
                     () ->
                             dispatchHybridGetAssertionRequest(
-                                    publicKeyOptions, callerOriginString, clientDataHash);
+                                    publicKeyOptions,
+                                    callerOriginString,
+                                    clientDataHash,
+                                    isConditionalRequest);
         }
 
         @AssertionMediationType int mediationType = AssertionMediationType.MODAL;
@@ -994,7 +904,7 @@
                     // Since passwords were not requested as a part of this immediate request, we
                     // already know there are no credentials to provide, so the request can be
                     // rejected now.
-                    returnErrorAndResetCallback(AuthenticatorStatus.NOT_ALLOWED_ERROR);
+                    returnErrorAndResetCallback(AuthenticatorStatus.NOT_ALLOWED_ERROR, null);
                     return;
                 }
                 mediationType = AssertionMediationType.IMMEDIATE_PASSKEYS_ONLY;
@@ -1017,10 +927,12 @@
                                         selectedCredential.webAuthnCredential());
                             } else {
                                 assertNonNull(selectedCredential.passwordCredential());
-                                if (mGetCredentialCallback != null) {
-                                    mGetCredentialCallback.onCredentialResponse(
-                                            /* assertionResponse= */ null,
-                                            selectedCredential.passwordCredential());
+                                WebauthnRequestCallback callback =
+                                        mAuthenticationContextProvider.getRequestCallback();
+                                if (callback != null) {
+                                    callback.onComplete(
+                                            WebauthnRequestResponse.forSuccessfulPassword(
+                                                    selectedCredential.passwordCredential()));
                                 }
                             }
                         },
@@ -1075,8 +987,6 @@
                                     convertOriginToString(callerOrigin),
                                     mClientDataJson,
                                     clientDataHash,
-                                    mGetCredentialCallback,
-                                    this::setOutcomeAndReturnError,
                                     mode == Barrier.Mode.BOTH);
                         });
     }
@@ -1139,8 +1049,6 @@
                 convertOriginToString(callerOrigin),
                 mClientDataJson,
                 clientDataHash,
-                mGetCredentialCallback,
-                this::setOutcomeAndReturnError,
                 mode == Barrier.Mode.BOTH);
     }
 
@@ -1196,7 +1104,7 @@
             // TODO(https://crbug.com/433543129): Add metrics for the rejection reason
             // in order to distinguish user dismissal from no credentials being
             // available.
-            returnErrorAndResetCallback(AuthenticatorStatus.NOT_ALLOWED_ERROR);
+            returnErrorAndResetCallback(AuthenticatorStatus.NOT_ALLOWED_ERROR, null);
             return;
         }
 
@@ -1204,21 +1112,22 @@
             logError(TAG, "Bottom sheet not displayed due to an error.");
             assumeNonNull(getBridge());
             getBridge().cleanupRequest(mAuthenticationContextProvider.getRenderFrameHost());
-            returnErrorAndResetCallback(AuthenticatorStatus.UNKNOWN_ERROR);
+            returnErrorAndResetCallback(AuthenticatorStatus.UNKNOWN_ERROR, null);
             return;
         }
 
         // Conditional mediation should never return from the user closing the sheet.
         assert (options.mediation != Mediation.CONDITIONAL);
         assert (reason == NonCredentialReturnReason.USER_DISMISSED);
-        mGetAssertionErrorOutcome = GetAssertionOutcome.USER_CANCELLATION;
-        returnErrorAndResetCallback(AuthenticatorStatus.NOT_ALLOWED_ERROR);
+        returnErrorAndResetCallback(
+                AuthenticatorStatus.NOT_ALLOWED_ERROR, GetAssertionOutcome.USER_CANCELLATION);
     }
 
     private void dispatchHybridGetAssertionRequest(
             PublicKeyCredentialRequestOptions options,
             String callerOriginString,
-            byte @Nullable [] clientDataHash) {
+            byte @Nullable [] clientDataHash,
+            boolean isConditional) {
         log(TAG, "dispatchHybridGetAssertionRequest");
         assert mCancellableUiState == CancellableUiState.NONE
                 || mCancellableUiState == CancellableUiState.REQUEST_SENT_TO_PLATFORM
@@ -1230,35 +1139,20 @@
                     "Received a second credential selection while the first still in progress.");
             return;
         }
-        mCancellableUiState = CancellableUiState.REQUEST_SENT_TO_PLATFORM;
+        if (isConditional) {
+            mCancellableUiState = CancellableUiState.REQUEST_SENT_TO_PLATFORM;
+        } else {
+            mCancellableUiState = CancellableUiState.NONE;
+        }
 
-        Fido2ApiCallParams params =
-                WebauthnModeProvider.getInstance()
-                        .getFido2ApiCallParams(mAuthenticationContextProvider.getWebContents());
-        assertNonNull(mAuthenticationContextProvider.getContext());
-        assertNonNull(params);
-        Fido2ApiCall call = new Fido2ApiCall(mAuthenticationContextProvider.getContext(), params);
-        Parcel args = call.start();
-        String callbackDescriptor = params.mCallbackDescriptor;
-        Fido2ApiCall.PendingIntentResult result =
-                new Fido2ApiCall.PendingIntentResult(callbackDescriptor);
-        args.writeStrongBinder(result);
-        args.writeInt(1); // This indicates that the following options are present.
-        Fido2Api.appendBrowserGetAssertionOptionsToParcel(
-                options,
-                Uri.parse(callerOriginString),
-                clientDataHash,
-                /* tunnelId= */ null,
-                /* resultReceiver= */ null,
-                args);
-        Task<PendingIntent> task =
-                call.run(
-                        Fido2ApiCall.METHOD_BROWSER_HYBRID_SIGN,
-                        Fido2ApiCall.TRANSACTION_HYBRID_SIGN,
-                        args,
-                        result);
-        task.addOnSuccessListener(this::onGotPendingIntent);
-        task.addOnFailureListener(this::onBinderCallException);
+        Fido2ApiCallHelper.getInstance()
+                .invokeFido2HybridGetAssertion(
+                        mAuthenticationContextProvider,
+                        options,
+                        Uri.parse(callerOriginString),
+                        clientDataHash,
+                        this::onGotPendingIntent,
+                        this::onBinderCallException);
     }
 
     // Handles a PendingIntent from the GMSCore FIDO library.
@@ -1266,20 +1160,20 @@
         log(TAG, "onGotPendingIntent");
         if (pendingIntent == null) {
             logError(TAG, "Didn't receive a pending intent.");
-            returnErrorAndResetCallback(AuthenticatorStatus.UNKNOWN_ERROR);
+            returnErrorAndResetCallback(AuthenticatorStatus.UNKNOWN_ERROR, null);
             return;
         }
 
         if (!mAuthenticationContextProvider.getIntentSender().showIntent(pendingIntent, this)) {
             logError(TAG, "Failed to send intent to FIDO API");
-            returnErrorAndResetCallback(AuthenticatorStatus.UNKNOWN_ERROR);
+            returnErrorAndResetCallback(AuthenticatorStatus.UNKNOWN_ERROR, null);
             return;
         }
     }
 
     private void onBinderCallException(Exception e) {
         logError(TAG, "FIDO2 API call failed", e);
-        returnErrorAndResetCallback(AuthenticatorStatus.NOT_ALLOWED_ERROR);
+        returnErrorAndResetCallback(AuthenticatorStatus.NOT_ALLOWED_ERROR, null);
     }
 
     private @Nullable ResultReceiver getMaybeResultReceiver() {
@@ -1316,7 +1210,11 @@
             }
         }
 
-        handleFido2Response(errorCode, response);
+        handleFido2Response(
+                errorCode,
+                response,
+                /* getAssertionOutcome= */ null,
+                /* makeCredentialOutcome= */ null);
     }
 
     // Handles the result.
@@ -1332,6 +1230,8 @@
                 || mCancellableUiState == CancellableUiState.REQUEST_SENT_TO_PLATFORM
                 || mCancellableUiState == CancellableUiState.CANCEL_PENDING;
 
+        @GetAssertionOutcome Integer getAssertionOutcome = null;
+        @MakeCredentialOutcome Integer makeCredentialOutcome = null;
         switch (resultCode) {
             case Activity.RESULT_OK:
                 if (data == null) {
@@ -1346,10 +1246,18 @@
                 break;
 
             case Activity.RESULT_CANCELED:
-                if (mGetCredentialCallback != null) {
-                    mGetAssertionErrorOutcome = GetAssertionOutcome.USER_CANCELLATION;
-                } else if (mMakeCredentialCallback != null) {
-                    mMakeCredentialErrorOutcome = MakeCredentialOutcome.USER_CANCELLATION;
+                WebauthnRequestCallback callback =
+                        assumeNonNull(mAuthenticationContextProvider.getRequestCallback());
+                switch (callback.getCallbackType()) {
+                    case WebauthnRequestCallback.CallbackType.GET_CREDENTIAL:
+                        getAssertionOutcome = GetAssertionOutcome.USER_CANCELLATION;
+                        break;
+                    case WebauthnRequestCallback.CallbackType.MAKE_CREDENTIAL:
+                        makeCredentialOutcome = MakeCredentialOutcome.USER_CANCELLATION;
+                        break;
+                    case WebauthnRequestCallback.CallbackType.REPORT:
+                        assert false;
+                        break;
                 }
                 errorCode = AuthenticatorStatus.NOT_ALLOWED_ERROR;
                 break;
@@ -1359,10 +1267,14 @@
                 break;
         }
 
-        handleFido2Response(errorCode, response);
+        handleFido2Response(errorCode, response, getAssertionOutcome, makeCredentialOutcome);
     }
 
-    private void handleFido2Response(int errorCode, @Nullable Object response) {
+    private void handleFido2Response(
+            int errorCode,
+            @Nullable Object response,
+            @Nullable @GetAssertionOutcome Integer getAssertionOutcome,
+            @Nullable @MakeCredentialOutcome Integer makeCredentialOutcome) {
         log(TAG, "handleFido2Response");
         RenderFrameHost frameHost = mAuthenticationContextProvider.getRenderFrameHost();
         if (mCancellableUiState != CancellableUiState.NONE) {
@@ -1394,6 +1306,13 @@
             getBridge().cleanupRequest(frameHost);
         }
 
+        WebauthnRequestCallback requestCallback =
+                mAuthenticationContextProvider.getRequestCallback();
+        if (requestCallback == null) {
+            return;
+        }
+        @WebauthnRequestCallback.CallbackType
+        int requestCallbackType = assumeNonNull(requestCallback).getCallbackType();
         if (response == null) {
             // Use the error already set.
         } else if (response instanceof Pair) {
@@ -1405,12 +1324,14 @@
                             + " "
                             + (error.second != null ? error.second : ""));
             errorCode = convertError(error);
-            if (mGetCredentialCallback != null) {
-                mGetAssertionErrorOutcome = getAssertionOutcomeCodeFromFidoError(error);
-            } else if (mMakeCredentialCallback != null) {
-                mMakeCredentialErrorOutcome = makeCredentialOutcomeCodeFromFidoError(error);
+            if (requestCallbackType == WebauthnRequestCallback.CallbackType.GET_CREDENTIAL
+                    && getAssertionOutcome == null) {
+                getAssertionOutcome = getAssertionOutcomeCodeFromFidoError(error);
+            } else if (requestCallbackType == WebauthnRequestCallback.CallbackType.MAKE_CREDENTIAL
+                    && makeCredentialOutcome == null) {
+                makeCredentialOutcome = makeCredentialOutcomeCodeFromFidoError(error);
             }
-        } else if (mMakeCredentialCallback != null) {
+        } else if (requestCallbackType == WebauthnRequestCallback.CallbackType.MAKE_CREDENTIAL) {
             if (response instanceof MakeCredentialAuthenticatorResponse) {
                 MakeCredentialAuthenticatorResponse creationResponse =
                         (MakeCredentialAuthenticatorResponse) response;
@@ -1423,12 +1344,11 @@
                 if (mClientDataJson != null) {
                     creationResponse.info.clientDataJson = mClientDataJson;
                 }
-                mMakeCredentialCallback.onRegisterResponse(
-                        AuthenticatorStatus.SUCCESS, creationResponse);
-                mMakeCredentialCallback = null;
+                requestCallback.onComplete(
+                        WebauthnRequestResponse.forSuccessfulMakeCredential(creationResponse));
                 return;
             }
-        } else if (mGetCredentialCallback != null) {
+        } else if (requestCallbackType == WebauthnRequestCallback.CallbackType.GET_CREDENTIAL) {
             if (response instanceof GetAssertionAuthenticatorResponse) {
                 GetAssertionAuthenticatorResponse r = (GetAssertionAuthenticatorResponse) response;
                 if (mClientDataJson != null) {
@@ -1437,13 +1357,18 @@
                     frameHost.notifyWebAuthnAssertionRequestSucceeded();
                 }
                 r.extensions.echoAppidExtension = mAppIdExtensionUsed;
-                mGetCredentialCallback.onCredentialResponse(r, /* passwordCredential= */ null);
-                mGetCredentialCallback = null;
+                requestCallback.onComplete(WebauthnRequestResponse.forSuccessfulGetAssertion(r));
                 return;
             }
         }
 
-        returnErrorAndResetCallback(errorCode);
+        if (requestCallbackType == WebauthnRequestCallback.CallbackType.MAKE_CREDENTIAL) {
+            returnErrorAndResetCallback(errorCode, makeCredentialOutcome);
+        } else if (requestCallbackType == WebauthnRequestCallback.CallbackType.GET_CREDENTIAL) {
+            returnErrorAndResetCallback(errorCode, getAssertionOutcome);
+        } else {
+            returnErrorAndResetCallback(errorCode, /* response= */ null);
+        }
     }
 
     @MakeCredentialOutcome
@@ -1470,7 +1395,7 @@
                 if (CREDENTIAL_EXISTS_ERROR_MSG.equals(errorMsg)) {
                     return MakeCredentialOutcome.CREDENTIAL_EXCLUDED;
                 }
-                // else fallthrough.
+            // else fallthrough.
             default:
                 return MakeCredentialOutcome.OTHER_FAILURE;
         }
@@ -1500,7 +1425,7 @@
                 if (LOW_LEVEL_ERROR_MSG.equals(errorMsg)) {
                     return GetAssertionOutcome.CREDENTIAL_NOT_RECOGNIZED;
                 }
-                // else fallthrough.
+            // else fallthrough.
             default:
                 return GetAssertionOutcome.OTHER_FAILURE;
         }
@@ -1533,7 +1458,7 @@
                 // The request is not allowed, possibly because the user denied permission.
                 return AuthenticatorStatus.NOT_ALLOWED_ERROR;
             case Fido2Api.DATA_ERR:
-                // Incoming requests were malformed/inadequate. Fallthrough.
+            // Incoming requests were malformed/inadequate. Fallthrough.
             case Fido2Api.NOT_SUPPORTED_ERR:
                 // Request parameters were not supported.
                 return AuthenticatorStatus.ANDROID_NOT_SUPPORTED_ERROR;
@@ -1546,14 +1471,14 @@
                 if (CREDENTIAL_EXISTS_ERROR_MSG.equals(errorMsg)) {
                     return AuthenticatorStatus.CREDENTIAL_EXCLUDED;
                 }
-                // else fallthrough.
+            // else fallthrough.
             case Fido2Api.UNKNOWN_ERR:
                 if (LOW_LEVEL_ERROR_MSG.equals(errorMsg)) {
                     // The error message returned from GmsCore when the user attempted to use a
                     // credential that is not registered with a U2F security key.
                     return AuthenticatorStatus.NOT_ALLOWED_ERROR;
                 }
-                // fall through
+            // fall through
             default:
                 return AuthenticatorStatus.UNKNOWN_ERROR;
         }
@@ -1608,9 +1533,13 @@
     }
 
     private void onImmediateTimeout() {
-        if (mGetCredentialCallback == null) {
+        WebauthnRequestCallback requestCallback =
+                mAuthenticationContextProvider.getRequestCallback();
+        if (requestCallback == null) {
             return;
         }
+        assert requestCallback.getCallbackType()
+                == WebauthnRequestCallback.CallbackType.GET_CREDENTIAL;
         logError(TAG, "Timed out waiting for immediate request");
         mCredManHelper.cancelGetAssertion(AuthenticatorStatus.NOT_ALLOWED_ERROR);
         mBarrier.onFido2ApiCancelled(AuthenticatorStatus.NOT_ALLOWED_ERROR);
diff --git a/components/webauthn/android/java/src/org/chromium/components/webauthn/GetCredentialResponseCallback.java b/components/webauthn/android/java/src/org/chromium/components/webauthn/GetCredentialResponseCallback.java
deleted file mode 100644
index 5af41826..0000000
--- a/components/webauthn/android/java/src/org/chromium/components/webauthn/GetCredentialResponseCallback.java
+++ /dev/null
@@ -1,21 +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.
-
-package org.chromium.components.webauthn;
-
-import org.chromium.blink.mojom.CredentialInfo;
-import org.chromium.blink.mojom.GetAssertionAuthenticatorResponse;
-import org.chromium.build.annotations.NullMarked;
-import org.chromium.build.annotations.Nullable;
-
-/**
- * Callback interface for receiving a response from a request to produce a signed assertion from an
- * authenticator, or a username/password `CredentialInfo` in some cases.
- */
-@NullMarked
-public interface GetCredentialResponseCallback {
-    void onCredentialResponse(
-            @Nullable GetAssertionAuthenticatorResponse assertionResponse,
-            @Nullable CredentialInfo passwordCredential);
-}
diff --git a/components/webauthn/android/java/src/org/chromium/components/webauthn/GetMatchingCredentialIdsDelegate.java b/components/webauthn/android/java/src/org/chromium/components/webauthn/GetMatchingCredentialIdsDelegate.java
new file mode 100644
index 0000000..05ba2a4
--- /dev/null
+++ b/components/webauthn/android/java/src/org/chromium/components/webauthn/GetMatchingCredentialIdsDelegate.java
@@ -0,0 +1,125 @@
+// 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.components.webauthn;
+
+import static org.chromium.components.webauthn.WebauthnLogger.log;
+import static org.chromium.components.webauthn.WebauthnLogger.logError;
+
+import org.chromium.base.ResettersForTesting;
+import org.chromium.build.annotations.NullMarked;
+import org.chromium.build.annotations.Nullable;
+import org.chromium.components.externalauth.ExternalAuthUtils;
+import org.chromium.components.externalauth.UserRecoverableErrorHandler;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * This class is responsible for getting matching credential ids from GMS Core. This is for payment
+ * requests with an allowlist.
+ */
+@NullMarked
+public class GetMatchingCredentialIdsDelegate {
+    /**
+     * Callback interface for receiving a response from a request to retrieve matching credential
+     * ids from an authenticator. If the request is successful, the response will be a list of
+     * credential ids that match the requested credential ids. If the request fails, the response
+     * will be null.
+     */
+    @NullMarked
+    public interface ResponseCallback {
+        void onResponse(@Nullable List<byte[]> matchingCredentialIds);
+    }
+
+    private static final String TAG = "GetMatchingCredentialIdsDelegate";
+
+    private static @Nullable GetMatchingCredentialIdsDelegate sInstance;
+    private final boolean mIsGetMatchingCredentialIdsSupported;
+
+    public static GetMatchingCredentialIdsDelegate getInstance() {
+        if (sInstance == null) {
+            sInstance = new GetMatchingCredentialIdsDelegate();
+        }
+        return sInstance;
+    }
+
+    public void getMatchingCredentialIds(
+            AuthenticationContextProvider authenticationContextProvider,
+            String relyingPartyId,
+            byte[][] allowCredentialIds,
+            boolean requireThirdPartyPayment,
+            ResponseCallback callback) {
+        log(TAG, "getMatchingCredentialIds");
+
+        if (!mIsGetMatchingCredentialIdsSupported) {
+            logError(TAG, "GetMatchingCredentialIds is not supported.");
+            callback.onResponse(null);
+            return;
+        }
+
+        GmsCoreGetCredentialsHelper.getInstance()
+                .getCredentials(
+                        authenticationContextProvider,
+                        relyingPartyId,
+                        GmsCoreGetCredentialsHelper.Reason.GET_MATCHING_CREDENTIAL_IDS,
+                        (credentials) ->
+                                onGetMatchingCredentialIdsListReceived(
+                                        credentials,
+                                        allowCredentialIds,
+                                        requireThirdPartyPayment,
+                                        callback),
+                        (exception) -> onException(exception, callback));
+    }
+
+    public static void setInstanceForTesting(GetMatchingCredentialIdsDelegate instance) {
+        sInstance = instance;
+        ResettersForTesting.register(() -> sInstance = null);
+    }
+
+    private GetMatchingCredentialIdsDelegate() {
+        boolean isGooglePlayServicesAvailable =
+                ExternalAuthUtils.getInstance()
+                        .canUseGooglePlayServices(new UserRecoverableErrorHandler.Silent());
+        if (!isGooglePlayServicesAvailable) {
+            logError(TAG, "Google Play Services' Fido2PrivilegedApi is not available.");
+            mIsGetMatchingCredentialIdsSupported = false;
+            return;
+        }
+
+        if (!GmsCoreUtils.isGetMatchingCredentialIdsSupported()) {
+            logError(TAG, "GetMatchingCredentialIds is not supported in Google Play Services.");
+            mIsGetMatchingCredentialIdsSupported = false;
+            return;
+        }
+
+        mIsGetMatchingCredentialIdsSupported = true;
+    }
+
+    private void onGetMatchingCredentialIdsListReceived(
+            List<WebauthnCredentialDetails> retrievedCredentials,
+            byte[][] allowCredentialIds,
+            boolean requireThirdPartyPayment,
+            ResponseCallback callback) {
+        log(TAG, "onGetMatchingCredentialIdsListReceived");
+        List<byte[]> matchingCredentialIds = new ArrayList<>();
+        for (WebauthnCredentialDetails credential : retrievedCredentials) {
+            if (requireThirdPartyPayment && !credential.mIsPayment) continue;
+
+            for (byte[] allowedId : allowCredentialIds) {
+                if (Arrays.equals(allowedId, credential.mCredentialId)) {
+                    matchingCredentialIds.add(credential.mCredentialId);
+                    break;
+                }
+            }
+        }
+        callback.onResponse(matchingCredentialIds);
+    }
+
+    private void onException(Exception e, ResponseCallback callback) {
+        logError(TAG, "FIDO2 API call failed", e);
+        callback.onResponse(null);
+    }
+}
diff --git a/components/webauthn/android/java/src/org/chromium/components/webauthn/GetMatchingCredentialIdsResponseCallback.java b/components/webauthn/android/java/src/org/chromium/components/webauthn/GetMatchingCredentialIdsResponseCallback.java
deleted file mode 100644
index 13d9bbdf..0000000
--- a/components/webauthn/android/java/src/org/chromium/components/webauthn/GetMatchingCredentialIdsResponseCallback.java
+++ /dev/null
@@ -1,18 +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.
-
-package org.chromium.components.webauthn;
-
-import org.chromium.build.annotations.NullMarked;
-
-import java.util.List;
-
-/**
- * Callback interface for receiving a response from a request to retrieve matching credential ids
- * from an authenticator.
- */
-@NullMarked
-public interface GetMatchingCredentialIdsResponseCallback {
-    void onResponse(List<byte[]> matchingCredentialIds);
-}
diff --git a/components/webauthn/android/java/src/org/chromium/components/webauthn/IdentityCredentialsHelper.java b/components/webauthn/android/java/src/org/chromium/components/webauthn/IdentityCredentialsHelper.java
index 9f7a5a5b..ed79ba8 100644
--- a/components/webauthn/android/java/src/org/chromium/components/webauthn/IdentityCredentialsHelper.java
+++ b/components/webauthn/android/java/src/org/chromium/components/webauthn/IdentityCredentialsHelper.java
@@ -5,6 +5,7 @@
 package org.chromium.components.webauthn;
 
 import static org.chromium.build.NullUtil.assertNonNull;
+import static org.chromium.build.NullUtil.assumeNonNull;
 import static org.chromium.components.webauthn.WebauthnLogger.log;
 import static org.chromium.components.webauthn.WebauthnLogger.logError;
 
@@ -52,10 +53,10 @@
             PublicKeyCredentialCreationOptions options,
             String origin,
             byte @Nullable [] clientDataJson,
-            byte @Nullable [] clientDataHash,
-            MakeCredentialResponseCallback responseCallback,
-            ErrorCallback errorCallback) {
+            byte @Nullable [] clientDataHash) {
         log(TAG, "handleConditionalCreateRequest");
+        WebauthnRequestCallback callback =
+                assertNonNull(mAuthenticationContextProvider.getRequestCallback());
         try {
             IdentityCredentialClient client =
                     IdentityCredentialManager.Companion.getClient(
@@ -65,19 +66,15 @@
                             GmsCoreUtils.wrapSuccessCallback(
                                     (handle) ->
                                             onConditionalCreateSuccess(
-                                                    clientDataJson,
-                                                    options,
-                                                    responseCallback,
-                                                    errorCallback,
-                                                    handle)))
+                                                    clientDataJson, options, handle)))
                     .addOnFailureListener(
-                            GmsCoreUtils.wrapFailureCallback(
-                                    (exception) ->
-                                            onConditionalCreateFailure(errorCallback, exception)));
+                            GmsCoreUtils.wrapFailureCallback(this::onConditionalCreateFailure));
         } catch (Exception e) {
             logError(TAG, "CreateCredential failed ", e);
-            errorCallback.onResult(
-                    AuthenticatorStatus.NOT_ALLOWED_ERROR, MakeCredentialOutcome.OTHER_FAILURE);
+            callback.onComplete(
+                    WebauthnRequestResponse.forFailedMakeCredential(
+                            AuthenticatorStatus.NOT_ALLOWED_ERROR,
+                            MakeCredentialOutcome.OTHER_FAILURE));
             return;
         }
     }
@@ -85,8 +82,6 @@
     private void onConditionalCreateSuccess(
             byte @Nullable [] clientDataJson,
             PublicKeyCredentialCreationOptions options,
-            MakeCredentialResponseCallback responseCallback,
-            ErrorCallback errorCallback,
             CreateCredentialHandle handle) {
         log(TAG, "onConditionalCreateSuccess");
         Bundle data = assertNonNull(handle.getCreateCredentialResponse()).getData();
@@ -94,22 +89,31 @@
                 CredManHelper.parseCreateCredentialResponseData(data);
         if (response == null) {
             log(TAG, "parseCreateCredentialResponseData() failed");
-            errorCallback.onResult(
-                    AuthenticatorStatus.NOT_ALLOWED_ERROR, MakeCredentialOutcome.OTHER_FAILURE);
+            assumeNonNull(mAuthenticationContextProvider.getRequestCallback())
+                    .onComplete(
+                            WebauthnRequestResponse.forFailedMakeCredential(
+                                    AuthenticatorStatus.NOT_ALLOWED_ERROR,
+                                    MakeCredentialOutcome.OTHER_FAILURE));
             return;
         }
         if (clientDataJson != null) {
             response.info.clientDataJson = clientDataJson;
         }
         response.echoCredProps = options.credProps;
-        responseCallback.onRegisterResponse(AuthenticatorStatus.SUCCESS, response);
+        assumeNonNull(mAuthenticationContextProvider.getRequestCallback())
+                .onComplete(WebauthnRequestResponse.forSuccessfulMakeCredential(response));
     }
 
-    private void onConditionalCreateFailure(ErrorCallback errorCallback, Exception e) {
+    private void onConditionalCreateFailure(Exception e) {
         log(TAG, "CreateCredential request failed ", e);
-        errorCallback.onResult(
-                AuthenticatorStatus.NOT_ALLOWED_ERROR,
-                MakeCredentialOutcome.CONDITIONAL_CREATE_FAILURE);
+        WebauthnRequestCallback callback = mAuthenticationContextProvider.getRequestCallback();
+        if (callback == null) {
+            return;
+        }
+        callback.onComplete(
+                WebauthnRequestResponse.forFailedMakeCredential(
+                        AuthenticatorStatus.NOT_ALLOWED_ERROR,
+                        MakeCredentialOutcome.CONDITIONAL_CREATE_FAILURE));
     }
 
     private Bundle requestBundle(String requestJson, byte @Nullable [] clientDataHash) {
diff --git a/components/webauthn/android/java/src/org/chromium/components/webauthn/InternalAuthenticator.java b/components/webauthn/android/java/src/org/chromium/components/webauthn/InternalAuthenticator.java
index 577d25d..2b326f32 100644
--- a/components/webauthn/android/java/src/org/chromium/components/webauthn/InternalAuthenticator.java
+++ b/components/webauthn/android/java/src/org/chromium/components/webauthn/InternalAuthenticator.java
@@ -29,6 +29,7 @@
 import org.chromium.url.Origin;
 
 import java.nio.ByteBuffer;
+import java.util.List;
 
 /**
  * Acts as a bridge from InternalAuthenticator declared in
@@ -209,14 +210,18 @@
                 relyingPartyId,
                 credentialIds,
                 requireThirdPartyPayment,
-                (matchingCredentialIds) -> {
-                    if (mNativeInternalAuthenticatorAndroid != 0) {
-                        InternalAuthenticatorJni.get()
-                                .invokeGetMatchingCredentialIdsResponse(
-                                        mNativeInternalAuthenticatorAndroid,
-                                        matchingCredentialIds.toArray(new byte[0][]));
-                    }
-                });
+                this::handleGetMatchingCredentialIdsResponse);
+    }
+
+    private void handleGetMatchingCredentialIdsResponse(
+            @Nullable List<byte[]> matchingCredentialIds) {
+        if (matchingCredentialIds == null || mNativeInternalAuthenticatorAndroid == 0) {
+            return;
+        }
+        InternalAuthenticatorJni.get()
+                .invokeGetMatchingCredentialIdsResponse(
+                        mNativeInternalAuthenticatorAndroid,
+                        matchingCredentialIds.toArray(new byte[0][]));
     }
 
     @CalledByNative
diff --git a/components/webauthn/android/java/src/org/chromium/components/webauthn/MakeCredentialResponseCallback.java b/components/webauthn/android/java/src/org/chromium/components/webauthn/MakeCredentialResponseCallback.java
deleted file mode 100644
index f212bf1..0000000
--- a/components/webauthn/android/java/src/org/chromium/components/webauthn/MakeCredentialResponseCallback.java
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright 2021 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.components.webauthn;
-
-import org.chromium.blink.mojom.MakeCredentialAuthenticatorResponse;
-import org.chromium.build.annotations.NullMarked;
-
-/**
- * Callback interface for receiving a response from a request to register a credential with an
- * authenticator.
- */
-@NullMarked
-public interface MakeCredentialResponseCallback {
-    void onRegisterResponse(int status, MakeCredentialAuthenticatorResponse response);
-}
diff --git a/components/webauthn/android/java/src/org/chromium/components/webauthn/WebauthnRequestCallback.java b/components/webauthn/android/java/src/org/chromium/components/webauthn/WebauthnRequestCallback.java
new file mode 100644
index 0000000..cd8233f
--- /dev/null
+++ b/components/webauthn/android/java/src/org/chromium/components/webauthn/WebauthnRequestCallback.java
@@ -0,0 +1,207 @@
+// 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.components.webauthn;
+
+import static org.chromium.build.NullUtil.assumeNonNull;
+import static org.chromium.components.webauthn.WebauthnLogger.log;
+import static org.chromium.components.webauthn.WebauthnLogger.logError;
+
+import androidx.annotation.IntDef;
+
+import org.chromium.blink.mojom.Authenticator.GetCredential_Response;
+import org.chromium.blink.mojom.Authenticator.MakeCredential_Response;
+import org.chromium.blink.mojom.Authenticator.Report_Response;
+import org.chromium.blink.mojom.AuthenticatorStatus;
+import org.chromium.blink.mojom.GetCredentialResponse;
+import org.chromium.build.annotations.NullMarked;
+import org.chromium.build.annotations.Nullable;
+
+/**
+ * Wrapper to manage the set of callbacks for a WebAuthn operation, enforcing that only one of
+ * GetCredential, MakeCredential, or Report callbacks can be active.
+ */
+@NullMarked
+public class WebauthnRequestCallback {
+    private static final String TAG = "WebauthnRequestCallback";
+
+    @IntDef({
+        CallbackType.GET_CREDENTIAL,
+        CallbackType.MAKE_CREDENTIAL,
+        CallbackType.REPORT,
+    })
+    public @interface CallbackType {
+        int GET_CREDENTIAL = 0;
+        int MAKE_CREDENTIAL = 1;
+        int REPORT = 2;
+    }
+
+    private final @CallbackType int mCallbackType;
+    private @Nullable Runnable mCompletionCallback;
+
+    // Only one of the following callbacks should be non-null at any given time.
+    private @Nullable GetCredential_Response mGetCredentialCallback;
+    private @Nullable MakeCredential_Response mMakeCredentialCallback;
+    private @Nullable Report_Response mReportCallback;
+
+    private @Nullable RecordOutcomeCallback mRecordingCallback;
+
+    // Private constructor to enforce creation via static factory methods.
+    private WebauthnRequestCallback(
+            @CallbackType int callbackType,
+            @Nullable GetCredential_Response getCredentialCallback,
+            @Nullable MakeCredential_Response makeCredentialCallback,
+            @Nullable Report_Response reportCallback,
+            @Nullable RecordOutcomeCallback recordingCallback) {
+        mCallbackType = callbackType;
+        if (callbackType == CallbackType.GET_CREDENTIAL) {
+            assert getCredentialCallback != null;
+            assert makeCredentialCallback == null;
+            assert reportCallback == null;
+        } else if (callbackType == CallbackType.MAKE_CREDENTIAL) {
+            assert getCredentialCallback == null;
+            assert makeCredentialCallback != null;
+            assert reportCallback == null;
+        } else if (callbackType == CallbackType.REPORT) {
+            assert getCredentialCallback == null;
+            assert makeCredentialCallback == null;
+            assert reportCallback != null;
+        } else {
+            assert false;
+        }
+        mGetCredentialCallback = getCredentialCallback;
+        mMakeCredentialCallback = makeCredentialCallback;
+        mReportCallback = reportCallback;
+        mRecordingCallback = recordingCallback;
+    }
+
+    // Static factory for GetCredential
+    public static WebauthnRequestCallback forGetCredential(
+            GetCredential_Response getCredentialCallback,
+            @Nullable RecordOutcomeCallback recordingCallback) {
+        log(TAG, "forGetCredential");
+        return new WebauthnRequestCallback(
+                CallbackType.GET_CREDENTIAL, getCredentialCallback, null, null, recordingCallback);
+    }
+
+    // Static factory for MakeCredential
+    public static WebauthnRequestCallback forMakeCredential(
+            MakeCredential_Response makeCredentialCallback,
+            @Nullable RecordOutcomeCallback recordingCallback) {
+        log(TAG, "forMakeCredential");
+        return new WebauthnRequestCallback(
+                CallbackType.MAKE_CREDENTIAL,
+                null,
+                makeCredentialCallback,
+                null,
+                recordingCallback);
+    }
+
+    // Static factory for Report
+    public static WebauthnRequestCallback forReport(Report_Response reportCallback) {
+        log(TAG, "forReport");
+        return new WebauthnRequestCallback(CallbackType.REPORT, null, null, reportCallback, null);
+    }
+
+    public @CallbackType int getCallbackType() {
+        return mCallbackType;
+    }
+
+    public void onComplete(WebauthnRequestResponse response) {
+        log(TAG, "onComplete, callbackType: %d", mCallbackType);
+        if (isRequestComplete()) {
+            logError(TAG, "No callbacks to handle response.");
+            return;
+        }
+        if (mCallbackType == CallbackType.MAKE_CREDENTIAL
+                && response.getMakeCredentialOutcomeMetricValue() != null) {
+            recordOutcome(response.getMakeCredentialOutcomeMetricValue());
+        }
+        if (mCallbackType == CallbackType.GET_CREDENTIAL
+                && response.getGetAssertionOutcomeMetricValue() != null) {
+            recordOutcome(response.getGetAssertionOutcomeMetricValue());
+        }
+
+        switch (mCallbackType) {
+            case CallbackType.GET_CREDENTIAL:
+                assert mGetCredentialCallback != null;
+                if (response.getGetCredentialResponse() != null) {
+                    handleGetCredentialResponse(response);
+                }
+                break;
+            case CallbackType.MAKE_CREDENTIAL:
+                assert mMakeCredentialCallback != null;
+                // A successful getCredential response has a null makeCredentialCallback and a
+                // status of SUCCESS. So this check prevents a mismatched response from being
+                // processed.
+                if (response.getMakeCredentialResponse() != null
+                        || response.getAuthenticatorStatus() != AuthenticatorStatus.SUCCESS) {
+                    handleMakeCredentialResponse(response);
+                }
+                break;
+            case CallbackType.REPORT:
+                assert mReportCallback != null;
+                mReportCallback.call(response.getAuthenticatorStatus(), null);
+                break;
+            default:
+                assert false;
+        }
+        clearCallbacks();
+        if (mCompletionCallback != null) {
+            mCompletionCallback.run();
+            mCompletionCallback = null;
+        }
+    }
+
+    public void setCompletionCallback(Runnable completionCallback) {
+        mCompletionCallback = completionCallback;
+    }
+
+    private void recordOutcome(int resultMetricValue) {
+        log(TAG, "recordOutcome, resultMetricValue: %d", resultMetricValue);
+        if (mRecordingCallback != null) {
+            mRecordingCallback.record(resultMetricValue);
+        }
+    }
+
+    private void handleGetCredentialResponse(WebauthnRequestResponse response) {
+        assumeNonNull(mGetCredentialCallback);
+        GetCredentialResponse getCredentialResponse = response.getGetCredentialResponse();
+        assumeNonNull(getCredentialResponse);
+        if (getCredentialResponse.which() == GetCredentialResponse.Tag.GetAssertionResponse) {
+            log(
+                    TAG,
+                    "handleGetCredentialResponse: status=%d",
+                    getCredentialResponse.getGetAssertionResponse().status);
+        } else {
+            log(TAG, "handleGetCredentialResponse: called with password credential");
+        }
+        mGetCredentialCallback.call(response.getGetCredentialResponse());
+    }
+
+    private void handleMakeCredentialResponse(WebauthnRequestResponse response) {
+        log(TAG, "handleMakeCredentialResponse: status=%d", response.getAuthenticatorStatus());
+        assumeNonNull(mMakeCredentialCallback);
+        if (response.getAuthenticatorStatus() == AuthenticatorStatus.SUCCESS) {
+            mMakeCredentialCallback.call(
+                    AuthenticatorStatus.SUCCESS, response.getMakeCredentialResponse(), null);
+        } else {
+            mMakeCredentialCallback.call(response.getAuthenticatorStatus(), null, null);
+        }
+    }
+
+    private boolean isRequestComplete() {
+        return mGetCredentialCallback == null
+                && mMakeCredentialCallback == null
+                && mReportCallback == null;
+    }
+
+    // Clears all response callbacks to prevent multiple invocations.
+    private void clearCallbacks() {
+        mGetCredentialCallback = null;
+        mMakeCredentialCallback = null;
+        mReportCallback = null;
+        mRecordingCallback = null;
+    }
+}
diff --git a/components/webauthn/android/java/src/org/chromium/components/webauthn/WebauthnRequestResponse.java b/components/webauthn/android/java/src/org/chromium/components/webauthn/WebauthnRequestResponse.java
new file mode 100644
index 0000000..f6f4bc7
--- /dev/null
+++ b/components/webauthn/android/java/src/org/chromium/components/webauthn/WebauthnRequestResponse.java
@@ -0,0 +1,125 @@
+// 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.components.webauthn;
+
+import org.chromium.blink.mojom.AuthenticatorStatus;
+import org.chromium.blink.mojom.CredentialInfo;
+import org.chromium.blink.mojom.GetAssertionAuthenticatorResponse;
+import org.chromium.blink.mojom.GetAssertionResponse;
+import org.chromium.blink.mojom.GetCredentialResponse;
+import org.chromium.blink.mojom.MakeCredentialAuthenticatorResponse;
+import org.chromium.build.annotations.NullMarked;
+import org.chromium.build.annotations.Nullable;
+
+/** Wrapper to manage the set of responses for a WebAuthn operation. */
+@NullMarked
+public class WebauthnRequestResponse {
+    // Note: This is only used for `MakeCredential` and `Report` operations.
+    // `GetCredential` uses `GetCredentialResponse.getAssertionResponse.status` instead.
+    private int mAuthenticatorStatus;
+
+    private @Nullable MakeCredentialAuthenticatorResponse mMakeCredentialResponse;
+    private @Nullable @MakeCredentialOutcome Integer mMakeCredentialOutcomeMetricValue;
+
+    private @Nullable GetCredentialResponse mGetCredentialResponse;
+    private @Nullable @GetAssertionOutcome Integer mGetAssertionOutcomeMetricValue;
+
+    private WebauthnRequestResponse() {}
+
+    public static WebauthnRequestResponse forSuccessfulMakeCredential(
+            MakeCredentialAuthenticatorResponse makeCredentialResponse) {
+        WebauthnRequestResponse response = new WebauthnRequestResponse();
+        response.mAuthenticatorStatus = AuthenticatorStatus.SUCCESS;
+        response.mMakeCredentialResponse = makeCredentialResponse;
+        response.mMakeCredentialOutcomeMetricValue = MakeCredentialOutcome.SUCCESS;
+        return response;
+    }
+
+    public static WebauthnRequestResponse forFailedMakeCredential(
+            int makeCredentialStatus,
+            @Nullable @MakeCredentialOutcome Integer makeCredentialOutcome) {
+        WebauthnRequestResponse response = new WebauthnRequestResponse();
+        response.mAuthenticatorStatus = makeCredentialStatus;
+        response.mMakeCredentialOutcomeMetricValue =
+                makeCredentialOutcome != null
+                        ? makeCredentialOutcome
+                        : MakeCredentialOutcome.OTHER_FAILURE;
+        return response;
+    }
+
+    public static WebauthnRequestResponse forSuccessfulGetCredential(
+            GetCredentialResponse getCredentialResponse) {
+        WebauthnRequestResponse response = new WebauthnRequestResponse();
+        response.mGetCredentialResponse = getCredentialResponse;
+        response.mGetAssertionOutcomeMetricValue = GetAssertionOutcome.SUCCESS;
+        return response;
+    }
+
+    public static WebauthnRequestResponse forSuccessfulGetAssertion(
+            GetAssertionAuthenticatorResponse getAssertionAuthenticatorResponse) {
+        GetAssertionResponse getAssertionResponse = new GetAssertionResponse();
+        getAssertionResponse.status = AuthenticatorStatus.SUCCESS;
+        getAssertionResponse.credential = getAssertionAuthenticatorResponse;
+
+        GetCredentialResponse getCredentialResponse = new GetCredentialResponse();
+        getCredentialResponse.setGetAssertionResponse(getAssertionResponse);
+
+        WebauthnRequestResponse response = new WebauthnRequestResponse();
+        response.mGetCredentialResponse = getCredentialResponse;
+        response.mGetAssertionOutcomeMetricValue = GetAssertionOutcome.SUCCESS;
+        return response;
+    }
+
+    public static WebauthnRequestResponse forSuccessfulPassword(CredentialInfo credentialInfo) {
+        WebauthnRequestResponse response = new WebauthnRequestResponse();
+        response.mGetCredentialResponse = new GetCredentialResponse();
+        response.mGetCredentialResponse.setPasswordResponse(credentialInfo);
+        return response;
+    }
+
+    public static WebauthnRequestResponse forFailedGetCredential(
+            Integer getCredentialStatus,
+            @Nullable @GetAssertionOutcome Integer getAssertionOutcome) {
+        GetAssertionResponse assertionResponse = new GetAssertionResponse();
+        assertionResponse.status = getCredentialStatus;
+
+        GetCredentialResponse getCredentialResponse = new GetCredentialResponse();
+        getCredentialResponse.setGetAssertionResponse(assertionResponse);
+
+        WebauthnRequestResponse response = new WebauthnRequestResponse();
+        response.mGetCredentialResponse = getCredentialResponse;
+        response.mGetAssertionOutcomeMetricValue =
+                getAssertionOutcome != null
+                        ? getAssertionOutcome
+                        : GetAssertionOutcome.OTHER_FAILURE;
+        return response;
+    }
+
+    public static WebauthnRequestResponse forReport(int status) {
+        WebauthnRequestResponse response = new WebauthnRequestResponse();
+        response.mAuthenticatorStatus = status;
+        return response;
+    }
+
+    public int getAuthenticatorStatus() {
+        return mAuthenticatorStatus;
+    }
+
+    public @Nullable MakeCredentialAuthenticatorResponse getMakeCredentialResponse() {
+        return mMakeCredentialResponse;
+    }
+
+    public @Nullable GetCredentialResponse getGetCredentialResponse() {
+        return mGetCredentialResponse;
+    }
+
+    public @Nullable @MakeCredentialOutcome Integer getMakeCredentialOutcomeMetricValue() {
+        return mMakeCredentialOutcomeMetricValue;
+    }
+
+    public @Nullable @GetAssertionOutcome Integer getGetAssertionOutcomeMetricValue() {
+        return mGetAssertionOutcomeMetricValue;
+    }
+}
diff --git a/components/webauthn/android/java/src/org/chromium/components/webauthn/cred_man/CredManHelper.java b/components/webauthn/android/java/src/org/chromium/components/webauthn/cred_man/CredManHelper.java
index 30fcf57..647d2b9 100644
--- a/components/webauthn/android/java/src/org/chromium/components/webauthn/cred_man/CredManHelper.java
+++ b/components/webauthn/android/java/src/org/chromium/components/webauthn/cred_man/CredManHelper.java
@@ -43,12 +43,12 @@
 import org.chromium.components.webauthn.Fido2CredentialRequest.CancellableUiState;
 import org.chromium.components.webauthn.Fido2CredentialRequestJni;
 import org.chromium.components.webauthn.GetAssertionOutcome;
-import org.chromium.components.webauthn.GetCredentialResponseCallback;
 import org.chromium.components.webauthn.MakeCredentialOutcome;
-import org.chromium.components.webauthn.MakeCredentialResponseCallback;
 import org.chromium.components.webauthn.WebauthnBrowserBridge;
 import org.chromium.components.webauthn.WebauthnMode;
 import org.chromium.components.webauthn.WebauthnModeProvider;
+import org.chromium.components.webauthn.WebauthnRequestCallback;
+import org.chromium.components.webauthn.WebauthnRequestResponse;
 import org.chromium.components.webauthn.cred_man.CredManMetricsHelper.CredManCreateRequestEnum;
 import org.chromium.components.webauthn.cred_man.CredManMetricsHelper.CredManGetRequestEnum;
 import org.chromium.components.webauthn.cred_man.CredManMetricsHelper.CredManPrepareRequestEnum;
@@ -82,12 +82,6 @@
     private CredManMetricsHelper mMetricsHelper;
     private @Nullable Runnable mNoCredentialsFallback;
 
-    // A callback that provides an AuthenticatorStatus error in the first argument, and optionally a
-    // metrics recording outcome in the second.
-    public interface ErrorCallback {
-        void onResult(int error, @Nullable Integer metricsOutcome);
-    }
-
     public CredManHelper(
             AuthenticationContextProvider authenticationContextProvider,
             WebauthnBrowserBridge.Provider bridgeProvider,
@@ -107,9 +101,7 @@
             PublicKeyCredentialCreationOptions options,
             String originString,
             byte @Nullable [] clientDataJson,
-            byte @Nullable [] clientDataHash,
-            @Nullable MakeCredentialResponseCallback makeCallback,
-            ErrorCallback errorCallback) {
+            byte @Nullable [] clientDataHash) {
         log(TAG, "startMakeRequest");
         mClientDataJson = clientDataJson;
         final String requestAsJson =
@@ -127,17 +119,25 @@
                                         + " ("
                                         + exception.getMessage()
                                         + ")");
+                        WebauthnRequestCallback callback =
+                                mAuthenticationContextProvider.getRequestCallback();
+                        if (callback == null) {
+                            logError(TAG, "No request callback for makeCredential request.");
+                            return;
+                        }
                         if (errorType.equals(CreateCredentialException.TYPE_USER_CANCELED)) {
-                            errorCallback.onResult(
-                                    AuthenticatorStatus.NOT_ALLOWED_ERROR,
-                                    MakeCredentialOutcome.USER_CANCELLATION);
+                            callback.onComplete(
+                                    WebauthnRequestResponse.forFailedMakeCredential(
+                                            AuthenticatorStatus.NOT_ALLOWED_ERROR,
+                                            MakeCredentialOutcome.USER_CANCELLATION));
                             mMetricsHelper.recordCredManCreateRequestHistogram(
                                     CredManCreateRequestEnum.CANCELLED);
                         } else if (errorType.equals(
                                 CRED_MAN_EXCEPTION_CREATE_CREDENTIAL_TYPE_INVALID_STATE_ERROR)) {
-                            errorCallback.onResult(
-                                    AuthenticatorStatus.CREDENTIAL_EXCLUDED,
-                                    MakeCredentialOutcome.CREDENTIAL_EXCLUDED);
+                            callback.onComplete(
+                                    WebauthnRequestResponse.forFailedMakeCredential(
+                                            AuthenticatorStatus.CREDENTIAL_EXCLUDED,
+                                            MakeCredentialOutcome.CREDENTIAL_EXCLUDED));
                             // This is successful from the point of view of the user.
                             mMetricsHelper.recordCredManCreateRequestHistogram(
                                     CredManCreateRequestEnum.SUCCESS);
@@ -146,7 +146,9 @@
                             //  * CreateCredentialException.TYPE_UNKNOWN
                             //  * CreateCredentialException.TYPE_NO_CREATE_OPTIONS
                             //  * CreateCredentialException.TYPE_INTERRUPTED
-                            errorCallback.onResult(AuthenticatorStatus.UNKNOWN_ERROR, null);
+                            callback.onComplete(
+                                    WebauthnRequestResponse.forFailedMakeCredential(
+                                            AuthenticatorStatus.UNKNOWN_ERROR, null));
                             mMetricsHelper.recordCredManCreateRequestHistogram(
                                     CredManCreateRequestEnum.FAILURE);
                         }
@@ -158,8 +160,16 @@
                         Bundle data = createCredentialResponse.getData();
                         MakeCredentialAuthenticatorResponse response =
                                 parseCreateCredentialResponseData(data);
+                        WebauthnRequestCallback callback =
+                                mAuthenticationContextProvider.getRequestCallback();
+                        if (callback == null) {
+                            logError(TAG, "No request callback for makeCredential request.");
+                            return;
+                        }
                         if (response == null) {
-                            errorCallback.onResult(AuthenticatorStatus.UNKNOWN_ERROR, null);
+                            callback.onComplete(
+                                    WebauthnRequestResponse.forFailedMakeCredential(
+                                            AuthenticatorStatus.UNKNOWN_ERROR, null));
                             mMetricsHelper.recordCredManCreateRequestHistogram(
                                     CredManCreateRequestEnum.FAILURE);
                             return;
@@ -168,8 +178,8 @@
                             response.info.clientDataJson = mClientDataJson;
                         }
                         response.echoCredProps = options.credProps;
-                        assumeNonNull(makeCallback);
-                        makeCallback.onRegisterResponse(AuthenticatorStatus.SUCCESS, response);
+                        callback.onComplete(
+                                WebauthnRequestResponse.forSuccessfulMakeCredential(response));
                         mMetricsHelper.recordCredManCreateRequestHistogram(
                                 CredManCreateRequestEnum.SUCCESS);
                     }
@@ -198,15 +208,12 @@
             String originString,
             byte @Nullable [] clientDataJson,
             byte @Nullable [] clientDataHash,
-            @Nullable GetCredentialResponseCallback getCallback,
-            ErrorCallback errorCallback,
             Barrier barrier,
             @Nullable Runnable stopImmediateTimer,
             boolean ignoreGpm) {
         log(TAG, "startPrefetchRequest");
         long startTimeMs = SystemClock.elapsedRealtime();
         mBarrier = barrier; // Store this for any cancellation requests.
-        final ErrorCallback localErrorCallback = errorCallback;
         final Barrier localBarrier = barrier;
         final WebauthnBrowserBridge localBridge = assumeNonNull(mBridgeProvider.getBridge());
         assumeNonNull(options.publicKey);
@@ -266,6 +273,12 @@
                         mCancellableUiState = CancellableUiState.WAITING_FOR_SELECTION;
 
                         Runnable barrierCallback;
+                        WebauthnRequestCallback callback =
+                                mAuthenticationContextProvider.getRequestCallback();
+                        if (callback == null) {
+                            logError(TAG, "No request callback for getCredential request.");
+                            return;
+                        }
                         if (options.mediation == Mediation.IMMEDIATE
                                 && CredManSupportProvider.getCredManSupport()
                                         == CredManSupport.FULL_UNLESS_INAPPLICABLE) {
@@ -281,9 +294,10 @@
                             if (!hasPublicKeyCredentials && !mRequestPasswords) {
                                 // TODO(https://crbug.com/408002783): This should have a distinct
                                 // GetAssertionOutcome for logging.
-                                localErrorCallback.onResult(
-                                        AuthenticatorStatus.NOT_ALLOWED_ERROR,
-                                        GetAssertionOutcome.OTHER_FAILURE);
+                                callback.onComplete(
+                                        WebauthnRequestResponse.forFailedGetCredential(
+                                                AuthenticatorStatus.NOT_ALLOWED_ERROR,
+                                                GetAssertionOutcome.OTHER_FAILURE));
                                 return;
                             }
                             // This fallback should not be used because the prefetch identified
@@ -292,9 +306,10 @@
                             // shown when it should not be.
                             setNoCredentialsFallback(
                                     () ->
-                                            localErrorCallback.onResult(
-                                                    AuthenticatorStatus.NOT_ALLOWED_ERROR,
-                                                    GetAssertionOutcome.OTHER_FAILURE));
+                                            callback.onComplete(
+                                                    WebauthnRequestResponse.forFailedGetCredential(
+                                                            AuthenticatorStatus.NOT_ALLOWED_ERROR,
+                                                            GetAssertionOutcome.OTHER_FAILURE)));
                             barrierCallback =
                                     () ->
                                             startGetRequest(
@@ -302,8 +317,6 @@
                                                     originString,
                                                     clientDataJson,
                                                     clientDataHash,
-                                                    getCallback,
-                                                    localErrorCallback,
                                                     ignoreGpm);
                         } else {
                             barrierCallback =
@@ -318,8 +331,6 @@
                                                             originString,
                                                             clientDataJson,
                                                             clientDataHash,
-                                                            getCallback,
-                                                            localErrorCallback,
                                                             ignoreGpm);
                                                 });
                                     };
@@ -373,13 +384,10 @@
             String originString,
             byte @Nullable [] clientDataJson,
             byte @Nullable [] clientDataHash,
-            @Nullable GetCredentialResponseCallback getCallback,
-            ErrorCallback errorCallback,
             boolean ignoreGpm) {
         log(TAG, "startGetRequest");
         mClientDataJson = clientDataJson;
         RenderFrameHost frameHost = mAuthenticationContextProvider.getRenderFrameHost();
-        final ErrorCallback localErrorCallback = errorCallback;
         final WebauthnBrowserBridge localBridge = assumeNonNull(mBridgeProvider.getBridge());
         assumeNonNull(options.publicKey);
 
@@ -402,11 +410,18 @@
                             mCancellableUiState = CancellableUiState.NONE;
                             return;
                         }
+                        WebauthnRequestCallback callback =
+                                mAuthenticationContextProvider.getRequestCallback();
+                        if (callback == null) {
+                            logError(TAG, "No request callback for getCredential request");
+                            return;
+                        }
                         if (errorType.equals(GetCredentialException.TYPE_USER_CANCELED)) {
                             if (mCancellableUiState == CancellableUiState.NONE) {
-                                localErrorCallback.onResult(
-                                        AuthenticatorStatus.NOT_ALLOWED_ERROR,
-                                        GetAssertionOutcome.USER_CANCELLATION);
+                                callback.onComplete(
+                                        WebauthnRequestResponse.forFailedGetCredential(
+                                                AuthenticatorStatus.NOT_ALLOWED_ERROR,
+                                                GetAssertionOutcome.USER_CANCELLATION));
                             }
 
                             mMetricsHelper.reportGetCredentialMetrics(
@@ -425,16 +440,19 @@
                             if (mNoCredentialsFallback != null) {
                                 mNoCredentialsFallback.run();
                             } else if (mCancellableUiState == CancellableUiState.NONE) {
-                                localErrorCallback.onResult(
-                                        AuthenticatorStatus.NOT_ALLOWED_ERROR,
-                                        GetAssertionOutcome.CREDENTIAL_NOT_RECOGNIZED);
+                                callback.onComplete(
+                                        WebauthnRequestResponse.forFailedGetCredential(
+                                                AuthenticatorStatus.NOT_ALLOWED_ERROR,
+                                                GetAssertionOutcome.CREDENTIAL_NOT_RECOGNIZED));
                             }
                         } else {
                             // Includes:
                             //  * GetCredentialException.TYPE_UNKNOWN
                             //  * GetCredentialException.TYPE_NO_CREATE_OPTIONS
                             //  * GetCredentialException.TYPE_INTERRUPTED
-                            localErrorCallback.onResult(AuthenticatorStatus.UNKNOWN_ERROR, null);
+                            callback.onComplete(
+                                    WebauthnRequestResponse.forFailedGetCredential(
+                                            AuthenticatorStatus.UNKNOWN_ERROR, null));
                             mMetricsHelper.reportGetCredentialMetrics(
                                     CredManGetRequestEnum.FAILURE, mCancellableUiState);
                         }
@@ -456,6 +474,12 @@
                         Bundle data = getCredentialResponse.getCredential().getData();
                         String type = getCredentialResponse.getCredential().getType();
 
+                        WebauthnRequestCallback callback =
+                                mAuthenticationContextProvider.getRequestCallback();
+                        if (callback == null) {
+                            logError(TAG, "No request callback for getCredential request");
+                            return;
+                        }
                         if (!TYPE_PASSKEY.equals(type)) {
                             if (options.mediation == Mediation.IMMEDIATE) {
                                 CredentialInfo passwordCredential =
@@ -467,9 +491,9 @@
                                                         data.getString(
                                                                 CRED_MAN_PREFIX
                                                                         + "BUNDLE_KEY_PASSWORD")));
-                                assumeNonNull(getCallback);
-                                getCallback.onCredentialResponse(
-                                        /* assertionResponse= */ null, passwordCredential);
+                                callback.onComplete(
+                                        WebauthnRequestResponse.forSuccessfulPassword(
+                                                passwordCredential));
                                 return;
                             }
 
@@ -502,7 +526,9 @@
                                             ? CancellableUiState.WAITING_FOR_SELECTION
                                             : CancellableUiState.NONE;
                             notifyBrowserOnCredManClosed(false);
-                            localErrorCallback.onResult(AuthenticatorStatus.UNKNOWN_ERROR, null);
+                            callback.onComplete(
+                                    WebauthnRequestResponse.forFailedGetCredential(
+                                            AuthenticatorStatus.UNKNOWN_ERROR, null));
                             return;
                         }
 
@@ -520,7 +546,9 @@
                                             ? CancellableUiState.WAITING_FOR_SELECTION
                                             : CancellableUiState.NONE;
                             notifyBrowserOnCredManClosed(false);
-                            localErrorCallback.onResult(AuthenticatorStatus.UNKNOWN_ERROR, null);
+                            callback.onComplete(
+                                    WebauthnRequestResponse.forFailedGetCredential(
+                                            AuthenticatorStatus.UNKNOWN_ERROR, null));
                             return;
                         }
                         if (mClientDataJson != null) {
@@ -538,8 +566,9 @@
                         if (frameHost != null) {
                             frameHost.notifyWebAuthnAssertionRequestSucceeded();
                         }
-                        assumeNonNull(getCallback);
-                        getCallback.onCredentialResponse(response, /* passwordCredential= */ null);
+                        assumeNonNull(callback);
+                        callback.onComplete(
+                                WebauthnRequestResponse.forSuccessfulGetAssertion(response));
                     }
                 };
 
diff --git a/components/webauthn/android/junit/src/org/chromium/components/webauthn/BarrierTest.java b/components/webauthn/android/junit/src/org/chromium/components/webauthn/BarrierTest.java
index 184e4fe..08e7a95 100644
--- a/components/webauthn/android/junit/src/org/chromium/components/webauthn/BarrierTest.java
+++ b/components/webauthn/android/junit/src/org/chromium/components/webauthn/BarrierTest.java
@@ -4,9 +4,10 @@
 
 package org.chromium.components.webauthn;
 
-import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import androidx.annotation.IntDef;
 
@@ -20,7 +21,6 @@
 import org.robolectric.ParameterizedRobolectricTestRunner.Parameter;
 import org.robolectric.ParameterizedRobolectricTestRunner.Parameters;
 
-import org.chromium.base.Callback;
 import org.chromium.base.test.BaseRobolectricTestRule;
 
 import java.lang.annotation.Retention;
@@ -204,16 +204,18 @@
 
     @Mock Runnable mCredManSuccesfulRunnable;
     @Mock Runnable mFido2ApiSuccessfulRunnable;
-    @Mock Callback<Integer> mErrorCallback;
+    @Mock AuthenticationContextProvider mAuthenticationContextProvider;
+    @Mock WebauthnRequestCallback mRequestCallback;
 
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.openMocks(this);
+        when(mAuthenticationContextProvider.getRequestCallback()).thenReturn(mRequestCallback);
     }
 
     @Test
     public void testScenarios() {
-        Barrier barrier = new Barrier(mErrorCallback);
+        Barrier barrier = new Barrier(mAuthenticationContextProvider);
         barrier.resetAndSetWaitStatus(mMode);
 
         if (mFirstCompletedApi == ApiCallType.CRED_MAN
@@ -247,27 +249,27 @@
             case Expectation.BOTH_RAN:
                 verify(mFido2ApiSuccessfulRunnable, times(1)).run();
                 verify(mCredManSuccesfulRunnable, times(1)).run();
-                verify(mErrorCallback, times(0)).onResult(anyInt());
+                verify(mRequestCallback, times(0)).onComplete(any());
                 break;
             case Expectation.ERROR_RAN:
                 verify(mFido2ApiSuccessfulRunnable, times(0)).run();
                 verify(mCredManSuccesfulRunnable, times(0)).run();
-                verify(mErrorCallback, times(1)).onResult(anyInt());
+                verify(mRequestCallback, times(1)).onComplete(any());
                 break;
             case Expectation.FIDO_2_API_RAN:
                 verify(mFido2ApiSuccessfulRunnable, times(1)).run();
                 verify(mCredManSuccesfulRunnable, times(0)).run();
-                verify(mErrorCallback, times(0)).onResult(anyInt());
+                verify(mRequestCallback, times(0)).onComplete(any());
                 break;
             case Expectation.CRED_MAN_RAN:
                 verify(mFido2ApiSuccessfulRunnable, times(0)).run();
                 verify(mCredManSuccesfulRunnable, times(1)).run();
-                verify(mErrorCallback, times(0)).onResult(anyInt());
+                verify(mRequestCallback, times(0)).onComplete(any());
                 break;
             case Expectation.NONE:
                 verify(mFido2ApiSuccessfulRunnable, times(0)).run();
                 verify(mCredManSuccesfulRunnable, times(0)).run();
-                verify(mErrorCallback, times(0)).onResult(anyInt());
+                verify(mRequestCallback, times(0)).onComplete(any());
                 break;
             default:
                 throw new AssertionError(mExpectation);
diff --git a/components/webauthn/android/junit/src/org/chromium/components/webauthn/Fido2CredentialRequestRobolectricTest.java b/components/webauthn/android/junit/src/org/chromium/components/webauthn/Fido2CredentialRequestRobolectricTest.java
index e96ca5df..7f457e0 100644
--- a/components/webauthn/android/junit/src/org/chromium/components/webauthn/Fido2CredentialRequestRobolectricTest.java
+++ b/components/webauthn/android/junit/src/org/chromium/components/webauthn/Fido2CredentialRequestRobolectricTest.java
@@ -28,6 +28,7 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.ResultReceiver;
+import android.util.Pair;
 
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.filters.SmallTest;
@@ -52,7 +53,9 @@
 import org.chromium.base.test.BaseRobolectricTestRunner;
 import org.chromium.base.test.util.Features.DisableFeatures;
 import org.chromium.blink.mojom.AuthenticatorStatus;
+import org.chromium.blink.mojom.GetAssertionResponse;
 import org.chromium.blink.mojom.GetCredentialOptions;
+import org.chromium.blink.mojom.GetCredentialResponse;
 import org.chromium.blink.mojom.Mediation;
 import org.chromium.blink.mojom.PublicKeyCredentialCreationOptions;
 import org.chromium.blink.mojom.PublicKeyCredentialDescriptor;
@@ -60,6 +63,7 @@
 import org.chromium.blink.mojom.PublicKeyCredentialRequestOptions;
 import org.chromium.blink.mojom.ResidentKeyRequirement;
 import org.chromium.blink_public.common.BlinkFeatures;
+import org.chromium.build.annotations.Nullable;
 import org.chromium.components.webauthn.cred_man.CredManHelper;
 import org.chromium.components.webauthn.cred_man.CredManSupportProvider;
 import org.chromium.components.webauthn.cred_man.ShadowCredentialManager;
@@ -195,11 +199,13 @@
         mRequest.overrideBrowserBridgeForTesting(mBrowserBridgeMock);
         mRequest.setCredManHelperForTesting(mCredManHelperMock);
         mRequest.setBarrierForTesting(mBarrierMock);
+        GmsCoreUtils.setGmsCoreVersionForTesting(2024000000);
     }
 
     @After
     public void tearDown() {
         WebauthnModeProvider.setInstanceForTesting(null);
+        GmsCoreUtils.setGmsCoreVersionForTesting(0);
     }
 
     @Test
@@ -207,8 +213,7 @@
     public void testMakeCredential() {
         handleMakeCredentialRequest(/* browserOptions= */ null);
 
-        verify(mCredManHelperMock, times(1))
-                .startMakeRequest(any(), any(), any(), any(), any(), any());
+        verify(mCredManHelperMock, times(1)).startMakeRequest(any(), any(), any(), any());
     }
 
     @Test
@@ -221,8 +226,7 @@
         assertThat(mFido2ApiCallHelper.mMakeCredentialCalled).isTrue();
         assertThat(mFido2ApiCallHelper.getChannelExtraOrNull()).isEqualTo(TEST_CHANNEL_EXTRA);
         assertThat(mFido2ApiCallHelper.getIncognitoExtraOrNull()).isTrue();
-        verify(mCredManHelperMock, times(0))
-                .startMakeRequest(any(), any(), any(), any(), any(), any());
+        verify(mCredManHelperMock, times(0)).startMakeRequest(any(), any(), any(), any());
     }
 
     @Test
@@ -265,9 +269,7 @@
                         any(),
                         any(),
                         /* clientDataJson= */ eq(null),
-                        /* clientDataHash= */ eq(null),
-                        any(),
-                        any());
+                        /* clientDataHash= */ eq(null));
     }
 
     @Test
@@ -291,8 +293,6 @@
                         eq(originString),
                         eq(TEST_CLIENT_DATA_JSON.getBytes()),
                         /* clientDataHash= */ notNull(),
-                        /* getCallback= */ any(),
-                        /* errorCallback= */ any(),
                         /* ignoreGpm= */ eq(false));
 
         assertThat(mFido2ApiCallHelper.mPasskeyCacheGetCredentialsCalled).isFalse();
@@ -321,8 +321,6 @@
                         eq(originString),
                         eq(TEST_CLIENT_DATA_JSON.getBytes()),
                         /* clientDataHash= */ any(),
-                        /* getCallback= */ any(),
-                        /* errorCallback= */ any(),
                         /* barrier= */ any(),
                         /* stopImmediateTimer= */ any(),
                         /* ignoreGpm= */ eq(true));
@@ -339,6 +337,45 @@
 
     @Test
     @SmallTest
+    public void testGetCredential_hybridCancel_parallelMode_failsRequest() {
+        setGetCredentialRequestOptions(/* hasAllowList= */ false);
+        CredManSupportProvider.setupForTesting(
+                /* overrideAndroidVersion= */ Build.VERSION_CODES.UPSIDE_DOWN_CAKE,
+                /* overrideForcesGpm= */ false);
+
+        handleGetCredentialRequest();
+
+        // Verify parallel execution
+        verifyGetCredentialsAndTriggerSuccess(
+                GmsCoreGetCredentialsHelper.Reason.GET_ASSERTION_NON_GOOGLE,
+                Collections.emptyList());
+
+        runFido2ApiSuccessfulCallback();
+
+        ArgumentCaptor<Runnable> hybridCallbackCaptor = ArgumentCaptor.forClass(Runnable.class);
+        verify(mBrowserBridgeMock)
+                .onCredentialsDetailsListReceived(
+                        eq(mFrameHost),
+                        eq(Collections.emptyList()),
+                        eq(AssertionMediationType.MODAL),
+                        any(),
+                        hybridCallbackCaptor.capture(),
+                        any());
+
+        // Trigger hybrid
+        hybridCallbackCaptor.getValue().run();
+        assertThat(mFido2ApiCallHelper.mHybridGetAssertionCalled).isTrue();
+
+        // Simulate Cancel
+        mRequest.onResult(new Pair<>(Activity.RESULT_CANCELED, null));
+
+        // Should return error
+        assertThat(mCallback.getStatus())
+                .isEqualTo(Integer.valueOf(AuthenticatorStatus.NOT_ALLOWED_ERROR));
+    }
+
+    @Test
+    @SmallTest
     public void testGetCredential_allowListMatchWithExplicitHash_goesToGmsCore() {
         setGetCredentialRequestOptions(/* hasAllowList= */ true);
 
@@ -372,8 +409,7 @@
                 ArgumentCaptor.forClass(Runnable.class);
         verify(mCredManHelperMock).setNoCredentialsFallback(setNoCredentialsParamCaptor.capture());
         verify(mCredManHelperMock)
-                .startGetRequest(
-                        any(), any(), any(), any(), any(), any(), /* ignoreGpm= */ eq(false));
+                .startGetRequest(any(), any(), any(), any(), /* ignoreGpm= */ eq(false));
 
         // Now run the no credentials fallback action:
         setNoCredentialsParamCaptor.getValue().run();
@@ -394,8 +430,7 @@
                 GmsCoreGetCredentialsHelper.Reason.CHECK_FOR_MATCHING_CREDENTIALS,
                 Collections.emptyList());
         verify(mCredManHelperMock)
-                .startGetRequest(
-                        any(), any(), any(), any(), any(), any(), /* ignoreGpm= */ eq(false));
+                .startGetRequest(any(), any(), any(), any(), /* ignoreGpm= */ eq(false));
         assertThat(mFido2ApiCallHelper.mGetAssertionCalled).isFalse();
     }
 
@@ -410,8 +445,7 @@
                 new IllegalStateException("injected error"));
 
         verify(mCredManHelperMock)
-                .startGetRequest(
-                        any(), any(), any(), any(), any(), any(), /* ignoreGpm= */ eq(false));
+                .startGetRequest(any(), any(), any(), any(), /* ignoreGpm= */ eq(false));
         verify(mCredManHelperMock).setNoCredentialsFallback(any());
         assertThat(mFido2ApiCallHelper.mGetAssertionCalled).isFalse();
     }
@@ -445,8 +479,7 @@
                 GmsCoreGetCredentialsHelper.Reason.CHECK_FOR_MATCHING_CREDENTIALS,
                 Collections.emptyList());
         verify(mCredManHelperMock)
-                .startGetRequest(
-                        any(), any(), any(), any(), any(), any(), /* ignoreGpm= */ eq(true));
+                .startGetRequest(any(), any(), any(), any(), /* ignoreGpm= */ eq(true));
         verify(mCredManHelperMock).setNoCredentialsFallback(any());
         assertThat(mFido2ApiCallHelper.mGetAssertionCalled).isFalse();
     }
@@ -482,14 +515,8 @@
         Fido2CredentialRequest request =
                 new Fido2CredentialRequest(mAuthenticationContextProviderMock);
 
-        request.handleGetCredentialRequest(
-                mRequestOptions,
-                mOrigin,
-                mOrigin,
-                /* payment= */ null,
-                mCallback::onSignResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        setUpGetCredentialCallback();
+        request.handleGetCredentialRequest(mRequestOptions, mOrigin, mOrigin, /* payment= */ null);
 
         verifyNoInteractions(mCredManHelperMock);
         assertThat(mFido2ApiCallHelper.mGetAssertionCalled).isFalse();
@@ -512,8 +539,6 @@
                         eq(originString),
                         eq(TEST_CLIENT_DATA_JSON.getBytes()),
                         /* clientDataHash= */ notNull(),
-                        /* getCallback= */ any(),
-                        /* errorCallback= */ any(),
                         /* barrier= */ any(),
                         /* stopImmediateTimer= */ any(),
                         /* ignoreGpm= */ eq(false));
@@ -543,8 +568,6 @@
                         eq(originString),
                         eq(TEST_CLIENT_DATA_JSON.getBytes()),
                         /* clientDataHash= */ notNull(),
-                        /* getCallback= */ any(),
-                        /* errorCallback= */ any(),
                         /* barrier= */ any(),
                         /* stopImmediateTimer= */ any(),
                         /* ignoreGpm= */ eq(true));
@@ -660,8 +683,7 @@
 
         assertThat(mCallback.getStatus()).isNull();
         verify(mCredManHelperMock, times(1))
-                .startPrefetchRequest(
-                        any(), any(), any(), any(), any(), any(), any(), any(), anyBoolean());
+                .startPrefetchRequest(any(), any(), any(), any(), any(), any(), anyBoolean());
     }
 
     @Test
@@ -676,7 +698,7 @@
         handleGetCredentialRequest();
 
         verify(mCredManHelperMock, times(1))
-                .startGetRequest(any(), any(), any(), any(), any(), any(), anyBoolean());
+                .startGetRequest(any(), any(), any(), any(), anyBoolean());
         assertThat(mFido2ApiCallHelper.mGetAssertionCalled).isFalse();
     }
 
@@ -695,8 +717,6 @@
                         eq(originString),
                         eq(TEST_CLIENT_DATA_JSON.getBytes()),
                         /* clientDataHash= */ any(),
-                        /* getCallback= */ any(),
-                        /* errorCallback= */ any(),
                         /* barrier= */ any(),
                         /* stopImmediateTimer= */ notNull(),
                         /* ignoreGpm= */ eq(false));
@@ -712,7 +732,7 @@
         handleGetCredentialRequest();
 
         verify(mCredManHelperMock, never())
-                .startGetRequest(any(), any(), any(), any(), any(), any(), anyBoolean());
+                .startGetRequest(any(), any(), any(), any(), anyBoolean());
         assertThat(mCallback.getStatus())
                 .isEqualTo(Integer.valueOf(AuthenticatorStatus.NOT_ALLOWED_ERROR));
     }
@@ -747,32 +767,61 @@
     public void testReportRequest_noSignalArgumentsSet_unknownError() {
         PublicKeyCredentialReportOptions options = new PublicKeyCredentialReportOptions();
         options.relyingPartyId = "rpId";
-        mRequest.handleReportRequest(options, mOrigin, mCallback::onReportOutcome);
+        setUpReportCallback();
+        mRequest.handleReportRequest(options, mOrigin);
         assertThat(mCallback.getStatus())
                 .isEqualTo(Integer.valueOf(AuthenticatorStatus.UNKNOWN_ERROR));
     }
 
     private void handleMakeCredentialRequest(Bundle browserOptions) {
+        setUpMakeCredentialCallback();
         mRequest.handleMakeCredentialRequest(
-                mCreationOptions,
-                browserOptions,
-                mOrigin,
-                mOrigin,
-                /* paymentOptions= */ null,
-                mCallback::onRegisterResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+                mCreationOptions, browserOptions, mOrigin, mOrigin, /* paymentOptions= */ null);
     }
 
     private void handleGetCredentialRequest() {
-        mRequest.handleGetCredentialRequest(
-                mRequestOptions,
-                mOrigin,
-                mOrigin,
-                /* payment= */ null,
-                mCallback::onSignResponse,
-                mCallback::onError,
-                mCallback::onRequestOutcome);
+        setUpGetCredentialCallback();
+        mRequest.handleGetCredentialRequest(mRequestOptions, mOrigin, mOrigin, /* payment= */ null);
+    }
+
+    private void setUpMakeCredentialCallback() {
+        WebauthnRequestCallback callback =
+                WebauthnRequestCallback.forMakeCredential(
+                        (status, response, domException) -> {
+                            if (status == AuthenticatorStatus.SUCCESS) {
+                                mCallback.onRegisterResponse(status, response);
+                            } else {
+                                mCallback.onError(status);
+                            }
+                        },
+                        mCallback::onRequestOutcome);
+        Mockito.when(mAuthenticationContextProviderMock.getRequestCallback()).thenReturn(callback);
+    }
+
+    private void setUpGetCredentialCallback() {
+        WebauthnRequestCallback callback =
+                WebauthnRequestCallback.forGetCredential(
+                        (response) -> {
+                            if (response.which() == GetCredentialResponse.Tag.PasswordResponse) {
+                                mCallback.onSignResponse(null, response.getPasswordResponse());
+                            } else {
+                                GetAssertionResponse assertion = response.getGetAssertionResponse();
+                                if (assertion.status == AuthenticatorStatus.SUCCESS) {
+                                    mCallback.onSignResponse(assertion.credential, null);
+                                } else {
+                                    mCallback.onError(assertion.status);
+                                }
+                            }
+                        },
+                        (outcome) -> mCallback.onRequestOutcome(outcome));
+        Mockito.when(mAuthenticationContextProviderMock.getRequestCallback()).thenReturn(callback);
+    }
+
+    private void setUpReportCallback() {
+        WebauthnRequestCallback callback =
+                WebauthnRequestCallback.forReport(
+                        (status, unused) -> mCallback.onReportOutcome(status));
+        Mockito.when(mAuthenticationContextProviderMock.getRequestCallback()).thenReturn(callback);
     }
 
     private void verifyGetCredentialsAndTriggerSuccess(
@@ -846,6 +895,7 @@
         public Bundle mBrowserOptions;
         public boolean mFido2GetCredentialsCalled;
         public boolean mPasskeyCacheGetCredentialsCalled;
+        public boolean mHybridGetAssertionCalled;
 
         private boolean mArePlayServicesAvailable = true;
 
@@ -952,5 +1002,23 @@
             }
             // Don't make any actual calls to Play Services.
         }
+
+        @Override
+        public void invokeFido2HybridGetAssertion(
+                AuthenticationContextProvider authenticationContextProvider,
+                PublicKeyCredentialRequestOptions options,
+                Uri uri,
+                byte @Nullable [] clientDataHash,
+                OnSuccessListener<PendingIntent> successCallback,
+                OnFailureListener failureCallback) {
+            mHybridGetAssertionCalled = true;
+            mClientDataHash = clientDataHash;
+
+            if (mCredentialsError != null) {
+                failureCallback.onFailure(mCredentialsError);
+                return;
+            }
+            // Don't make any actual calls to Play Services.
+        }
     }
 }
diff --git a/components/webauthn/android/junit/src/org/chromium/components/webauthn/WebauthnRequestCallbackRobolectricTest.java b/components/webauthn/android/junit/src/org/chromium/components/webauthn/WebauthnRequestCallbackRobolectricTest.java
new file mode 100644
index 0000000..8b8c6d7
--- /dev/null
+++ b/components/webauthn/android/junit/src/org/chromium/components/webauthn/WebauthnRequestCallbackRobolectricTest.java
@@ -0,0 +1,176 @@
+// 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.components.webauthn;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.robolectric.annotation.Config;
+
+import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.blink.mojom.Authenticator.GetCredential_Response;
+import org.chromium.blink.mojom.Authenticator.MakeCredential_Response;
+import org.chromium.blink.mojom.Authenticator.Report_Response;
+import org.chromium.blink.mojom.AuthenticatorStatus;
+import org.chromium.blink.mojom.GetAssertionAuthenticatorResponse;
+import org.chromium.blink.mojom.GetCredentialResponse;
+import org.chromium.blink.mojom.MakeCredentialAuthenticatorResponse;
+
+/** Robolectric tests for {@link WebauthnRequestCallback}. */
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+public class WebauthnRequestCallbackRobolectricTest {
+    @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+    @Mock private MakeCredential_Response mMakeCredentialCallback;
+    @Mock private GetCredential_Response mGetCredentialCallback;
+    @Mock private Report_Response mReportCallback;
+    @Mock private RecordOutcomeCallback mRecordOutcomeCallback;
+    @Mock private Runnable mCompletionCallback;
+
+    @Test
+    @SmallTest
+    public void testMakeCredential_success() {
+        WebauthnRequestCallback callback =
+                WebauthnRequestCallback.forMakeCredential(
+                        mMakeCredentialCallback, mRecordOutcomeCallback);
+        callback.setCompletionCallback(mCompletionCallback);
+
+        MakeCredentialAuthenticatorResponse responseData =
+                new MakeCredentialAuthenticatorResponse();
+        WebauthnRequestResponse response =
+                WebauthnRequestResponse.forSuccessfulMakeCredential(responseData);
+        callback.onComplete(response);
+
+        verify(mMakeCredentialCallback).call(AuthenticatorStatus.SUCCESS, responseData, null);
+        verify(mRecordOutcomeCallback).record(MakeCredentialOutcome.SUCCESS);
+        verify(mCompletionCallback).run();
+    }
+
+    @Test
+    @SmallTest
+    public void testMakeCredential_failure() {
+        WebauthnRequestCallback callback =
+                WebauthnRequestCallback.forMakeCredential(
+                        mMakeCredentialCallback, mRecordOutcomeCallback);
+        callback.setCompletionCallback(mCompletionCallback);
+
+        WebauthnRequestResponse response =
+                WebauthnRequestResponse.forFailedMakeCredential(
+                        AuthenticatorStatus.NOT_ALLOWED_ERROR,
+                        MakeCredentialOutcome.USER_CANCELLATION);
+        callback.onComplete(response);
+
+        verify(mMakeCredentialCallback).call(AuthenticatorStatus.NOT_ALLOWED_ERROR, null, null);
+        verify(mRecordOutcomeCallback).record(MakeCredentialOutcome.USER_CANCELLATION);
+        verify(mCompletionCallback).run();
+    }
+
+    @Test
+    @SmallTest
+    public void testGetCredential_success() {
+        WebauthnRequestCallback callback =
+                WebauthnRequestCallback.forGetCredential(
+                        mGetCredentialCallback, mRecordOutcomeCallback);
+        callback.setCompletionCallback(mCompletionCallback);
+
+        WebauthnRequestResponse response =
+                WebauthnRequestResponse.forSuccessfulGetAssertion(
+                        new GetAssertionAuthenticatorResponse());
+        callback.onComplete(response);
+
+        verify(mGetCredentialCallback).call(response.getGetCredentialResponse());
+        verify(mRecordOutcomeCallback).record(GetAssertionOutcome.SUCCESS);
+        verify(mCompletionCallback).run();
+    }
+
+    @Test
+    @SmallTest
+    public void testGetCredential_failure() {
+        WebauthnRequestCallback callback =
+                WebauthnRequestCallback.forGetCredential(
+                        mGetCredentialCallback, mRecordOutcomeCallback);
+        callback.setCompletionCallback(mCompletionCallback);
+
+        WebauthnRequestResponse response =
+                WebauthnRequestResponse.forFailedGetCredential(
+                        AuthenticatorStatus.NOT_ALLOWED_ERROR,
+                        GetAssertionOutcome.USER_CANCELLATION);
+        callback.onComplete(response);
+
+        verify(mGetCredentialCallback).call(response.getGetCredentialResponse());
+        verify(mRecordOutcomeCallback).record(GetAssertionOutcome.USER_CANCELLATION);
+        verify(mCompletionCallback).run();
+    }
+
+    @Test
+    @SmallTest
+    public void testReport() {
+        WebauthnRequestCallback callback = WebauthnRequestCallback.forReport(mReportCallback);
+        callback.setCompletionCallback(mCompletionCallback);
+
+        WebauthnRequestResponse response =
+                WebauthnRequestResponse.forReport(AuthenticatorStatus.SUCCESS);
+        callback.onComplete(response);
+
+        verify(mReportCallback).call(AuthenticatorStatus.SUCCESS, null);
+        verify(mRecordOutcomeCallback, never()).record(0);
+        verify(mCompletionCallback).run();
+    }
+
+    @Test
+    @SmallTest
+    public void testOnComplete_multipleCallsIgnored() {
+        WebauthnRequestCallback callback =
+                WebauthnRequestCallback.forMakeCredential(
+                        mMakeCredentialCallback, mRecordOutcomeCallback);
+        callback.setCompletionCallback(mCompletionCallback);
+
+        MakeCredentialAuthenticatorResponse responseData =
+                new MakeCredentialAuthenticatorResponse();
+        WebauthnRequestResponse response =
+                WebauthnRequestResponse.forSuccessfulMakeCredential(responseData);
+        callback.onComplete(response);
+        // Second call should be ignored.
+        callback.onComplete(response);
+
+        verify(mMakeCredentialCallback, times(1))
+                .call(AuthenticatorStatus.SUCCESS, responseData, null);
+        verify(mRecordOutcomeCallback, times(1)).record(MakeCredentialOutcome.SUCCESS);
+        verify(mCompletionCallback, times(1)).run();
+    }
+
+    @Test
+    @SmallTest
+    public void testOnComplete_mismatchedResponseIsIgnored() {
+        WebauthnRequestCallback callback =
+                WebauthnRequestCallback.forMakeCredential(
+                        mMakeCredentialCallback, mRecordOutcomeCallback);
+        callback.setCompletionCallback(mCompletionCallback);
+
+        // This is a get credential response.
+        GetCredentialResponse getCredentialResponseData = new GetCredentialResponse();
+        WebauthnRequestResponse response =
+                WebauthnRequestResponse.forSuccessfulGetCredential(getCredentialResponseData);
+
+        // The callback is for make credential, so it should ignore a get credential response.
+        callback.onComplete(response);
+
+        verify(mMakeCredentialCallback, never()).call(anyInt(), any(), any());
+        verify(mRecordOutcomeCallback, never()).record(anyInt());
+        verify(mCompletionCallback).run();
+    }
+}
diff --git a/components/webauthn/android/junit/src/org/chromium/components/webauthn/cred_man/CredManHelperRobolectricTest.java b/components/webauthn/android/junit/src/org/chromium/components/webauthn/cred_man/CredManHelperRobolectricTest.java
index 24cb0dac..e5f5087 100644
--- a/components/webauthn/android/junit/src/org/chromium/components/webauthn/cred_man/CredManHelperRobolectricTest.java
+++ b/components/webauthn/android/junit/src/org/chromium/components/webauthn/cred_man/CredManHelperRobolectricTest.java
@@ -46,6 +46,8 @@
 import org.chromium.base.Callback;
 import org.chromium.base.test.BaseRobolectricTestRunner;
 import org.chromium.base.test.util.Features.DisableFeatures;
+import org.chromium.blink.mojom.Authenticator.GetCredential_Response;
+import org.chromium.blink.mojom.Authenticator.MakeCredential_Response;
 import org.chromium.blink.mojom.AuthenticatorStatus;
 import org.chromium.blink.mojom.GetCredentialOptions;
 import org.chromium.blink.mojom.Mediation;
@@ -55,11 +57,10 @@
 import org.chromium.components.webauthn.AuthenticationContextProvider;
 import org.chromium.components.webauthn.Barrier;
 import org.chromium.components.webauthn.Fido2ApiTestHelper;
-import org.chromium.components.webauthn.GetAssertionOutcome;
-import org.chromium.components.webauthn.MakeCredentialOutcome;
 import org.chromium.components.webauthn.WebauthnBrowserBridge;
 import org.chromium.components.webauthn.WebauthnFeatures;
 import org.chromium.components.webauthn.WebauthnModeProvider;
+import org.chromium.components.webauthn.WebauthnRequestCallback;
 import org.chromium.components.webauthn.cred_man.CredManMetricsHelper.CredManCreateRequestEnum;
 import org.chromium.components.webauthn.cred_man.CredManMetricsHelper.CredManGetRequestEnum;
 import org.chromium.components.webauthn.cred_man.CredManMetricsHelper.CredManPrepareRequestEnum;
@@ -88,7 +89,6 @@
 @DisableFeatures({WebauthnFeatures.WEBAUTHN_ANDROID_CRED_MAN_FOR_DEV})
 public class CredManHelperRobolectricTest {
     private CredManHelper mCredManHelper;
-    private Fido2ApiTestHelper.AuthenticatorCallback mCallback;
     private PublicKeyCredentialCreationOptions mCreationOptions;
     private GetCredentialOptions mRequestOptions;
     private final String mOriginString = "https://subdomain.coolwebsitekayserispor.com";
@@ -100,7 +100,6 @@
     @Mock private RenderFrameHost mFrameHost;
     @Mock private CredManMetricsHelper mMetricsHelper;
     @Mock private WebauthnBrowserBridge mBrowserBridge;
-    @Mock private CredManHelper.ErrorCallback mErrorCallback;
     @Mock private Barrier mBarrier;
     @Mock private CredManRequestDecorator mRequestDecorator;
     @Mock private WebauthnModeProvider mWebauthnModeProvider;
@@ -109,6 +108,10 @@
     @Mock private CredManGetCredentialRequestHelper mCredManGetCredentialRequestHelper;
     @Mock private GetCredentialRequest mGetCredentialRequest;
     @Mock private AuthenticationContextProvider mAuthenticationContextProviderMock;
+    @Mock private MakeCredential_Response mMakeCredentialResponseCallback;
+    @Mock private GetCredential_Response mGetCredentialResponseCallback;
+    private WebauthnRequestCallback mRequestCallback;
+
     private final WebauthnBrowserBridge.Provider mBridgeProvider =
             new WebauthnBrowserBridge.Provider() {
                 @Override
@@ -131,8 +134,6 @@
         Fido2ApiTestHelper.mockFido2CredentialRequestJni();
         Fido2ApiTestHelper.mockClientDataJson("{}");
 
-        mCallback = Fido2ApiTestHelper.getAuthenticatorCallback();
-
         CredManCreateCredentialRequestHelper.setInstanceForTesting(
                 mCredManCreateCredentialRequestHelper);
         when(mCredManCreateCredentialRequestHelper.getCreateCredentialRequest(any()))
@@ -162,14 +163,15 @@
     @Test
     @SmallTest
     public void testStartMakeRequest_default_success() {
+        mRequestCallback =
+                WebauthnRequestCallback.forMakeCredential(mMakeCredentialResponseCallback, null);
+        when(mAuthenticationContextProviderMock.getRequestCallback()).thenReturn(mRequestCallback);
         int result =
                 mCredManHelper.startMakeRequest(
                         mCreationOptions,
                         mOriginString,
                         /* clientDataJson= */ null,
-                        mClientDataHash,
-                        mCallback::onRegisterResponse,
-                        mErrorCallback);
+                        mClientDataHash);
 
         assertThat(result).isEqualTo(AuthenticatorStatus.SUCCESS);
 
@@ -183,7 +185,7 @@
         shadowCredentialManager
                 .getCreateCredentialCallback()
                 .onResult(Shadow.newInstanceOf(CreateCredentialResponse.class));
-        assertThat(mCallback.getStatus()).isEqualTo(Integer.valueOf(AuthenticatorStatus.SUCCESS));
+        verify(mMakeCredentialResponseCallback).call(eq(AuthenticatorStatus.SUCCESS), any(), any());
 
         verify(mMetricsHelper, times(1))
                 .recordCredManCreateRequestHistogram(CredManCreateRequestEnum.SUCCESS);
@@ -192,14 +194,15 @@
     @Test
     @SmallTest
     public void testStartMakeRequest_withExplicitHash_success() {
+        mRequestCallback =
+                WebauthnRequestCallback.forMakeCredential(mMakeCredentialResponseCallback, null);
+        when(mAuthenticationContextProviderMock.getRequestCallback()).thenReturn(mRequestCallback);
         int result =
                 mCredManHelper.startMakeRequest(
                         mCreationOptions,
                         mOriginString,
                         /* clientDataJson= */ null,
-                        mClientDataHash,
-                        mCallback::onRegisterResponse,
-                        mErrorCallback);
+                        mClientDataHash);
 
         assertThat(result).isEqualTo(AuthenticatorStatus.SUCCESS);
 
@@ -213,20 +216,21 @@
         shadowCredentialManager
                 .getCreateCredentialCallback()
                 .onResult(Shadow.newInstanceOf(CreateCredentialResponse.class));
-        assertThat(mCallback.getStatus()).isEqualTo(Integer.valueOf(AuthenticatorStatus.SUCCESS));
+        verify(mMakeCredentialResponseCallback).call(eq(AuthenticatorStatus.SUCCESS), any(), any());
     }
 
     @Test
     @SmallTest
     public void testStartMakeRequest_userCancel_notAllowedError() {
+        mRequestCallback =
+                WebauthnRequestCallback.forMakeCredential(mMakeCredentialResponseCallback, null);
+        when(mAuthenticationContextProviderMock.getRequestCallback()).thenReturn(mRequestCallback);
         int result =
                 mCredManHelper.startMakeRequest(
                         mCreationOptions,
                         mOriginString,
                         /* clientDataJson= */ null,
-                        mClientDataHash,
-                        mCallback::onRegisterResponse,
-                        mErrorCallback);
+                        mClientDataHash);
 
         assertThat(result).isEqualTo(AuthenticatorStatus.SUCCESS);
 
@@ -236,10 +240,8 @@
         shadowException.setType("android.credentials.CreateCredentialException.TYPE_USER_CANCELED");
         shadowCredentialManager.getCreateCredentialCallback().onError(exception);
 
-        verify(mErrorCallback, times(1))
-                .onResult(
-                        AuthenticatorStatus.NOT_ALLOWED_ERROR,
-                        MakeCredentialOutcome.USER_CANCELLATION);
+        verify(mMakeCredentialResponseCallback)
+                .call(eq(AuthenticatorStatus.NOT_ALLOWED_ERROR), any(), any());
         verify(mMetricsHelper, times(1))
                 .recordCredManCreateRequestHistogram(CredManCreateRequestEnum.CANCELLED);
     }
@@ -247,14 +249,15 @@
     @Test
     @SmallTest
     public void testStartMakeRequest_invalidStateError_credentialExcluded() {
+        mRequestCallback =
+                WebauthnRequestCallback.forMakeCredential(mMakeCredentialResponseCallback, null);
+        when(mAuthenticationContextProviderMock.getRequestCallback()).thenReturn(mRequestCallback);
         int result =
                 mCredManHelper.startMakeRequest(
                         mCreationOptions,
                         mOriginString,
                         /* clientDataJson= */ null,
-                        mClientDataHash,
-                        mCallback::onRegisterResponse,
-                        mErrorCallback);
+                        mClientDataHash);
 
         assertThat(result).isEqualTo(AuthenticatorStatus.SUCCESS);
 
@@ -265,10 +268,8 @@
                 CredManHelper.CRED_MAN_EXCEPTION_CREATE_CREDENTIAL_TYPE_INVALID_STATE_ERROR);
         shadowCredentialManager.getCreateCredentialCallback().onError(exception);
 
-        verify(mErrorCallback, times(1))
-                .onResult(
-                        AuthenticatorStatus.CREDENTIAL_EXCLUDED,
-                        MakeCredentialOutcome.CREDENTIAL_EXCLUDED);
+        verify(mMakeCredentialResponseCallback)
+                .call(eq(AuthenticatorStatus.CREDENTIAL_EXCLUDED), any(), any());
         verify(mMetricsHelper, times(1))
                 .recordCredManCreateRequestHistogram(CredManCreateRequestEnum.SUCCESS);
     }
@@ -276,14 +277,15 @@
     @Test
     @SmallTest
     public void testStartMakeRequest_unknownError_unknownError() {
+        mRequestCallback =
+                WebauthnRequestCallback.forMakeCredential(mMakeCredentialResponseCallback, null);
+        when(mAuthenticationContextProviderMock.getRequestCallback()).thenReturn(mRequestCallback);
         int result =
                 mCredManHelper.startMakeRequest(
                         mCreationOptions,
                         mOriginString,
                         /* clientDataJson= */ null,
-                        mClientDataHash,
-                        mCallback::onRegisterResponse,
-                        mErrorCallback);
+                        mClientDataHash);
 
         assertThat(result).isEqualTo(AuthenticatorStatus.SUCCESS);
 
@@ -293,7 +295,8 @@
         shadowException.setType("android.credentials.CreateCredentialException.TYPE_UNKNOWN");
         shadowCredentialManager.getCreateCredentialCallback().onError(exception);
 
-        verify(mErrorCallback, times(1)).onResult(AuthenticatorStatus.UNKNOWN_ERROR, null);
+        verify(mMakeCredentialResponseCallback)
+                .call(eq(AuthenticatorStatus.UNKNOWN_ERROR), any(), any());
         verify(mMetricsHelper, times(1))
                 .recordCredManCreateRequestHistogram(CredManCreateRequestEnum.FAILURE);
     }
@@ -301,14 +304,15 @@
     @Test
     @SmallTest
     public void testStartGetRequest_default_success() {
+        mRequestCallback =
+                WebauthnRequestCallback.forGetCredential(mGetCredentialResponseCallback, null);
+        when(mAuthenticationContextProviderMock.getRequestCallback()).thenReturn(mRequestCallback);
         int result =
                 mCredManHelper.startGetRequest(
                         mRequestOptions,
                         mOriginString,
                         /* clientDataJson= */ null,
                         mClientDataHash,
-                        mCallback::onSignResponse,
-                        mErrorCallback,
                         /* ignoreGpm= */ false);
 
         assertThat(result).isEqualTo(AuthenticatorStatus.SUCCESS);
@@ -321,7 +325,7 @@
         GetCredentialResponse response = new GetCredentialResponse(createPasskeyCredential());
         shadowCredentialManager.getGetCredentialCallback().onResult(response);
 
-        assertThat(mCallback.getStatus()).isEqualTo(Integer.valueOf(AuthenticatorStatus.SUCCESS));
+        verify(mGetCredentialResponseCallback).call(any());
         verify(mBrowserBridge, times(1)).onCredManUiClosed(any(), anyBoolean());
         verify(mMetricsHelper, times(1))
                 .reportGetCredentialMetrics(eq(CredManGetRequestEnum.SENT_REQUEST), anyInt());
@@ -332,14 +336,15 @@
     @Test
     @SmallTest
     public void testStartGetRequest_withExplicitHash_success() {
+        mRequestCallback =
+                WebauthnRequestCallback.forGetCredential(mGetCredentialResponseCallback, null);
+        when(mAuthenticationContextProviderMock.getRequestCallback()).thenReturn(mRequestCallback);
         int result =
                 mCredManHelper.startGetRequest(
                         mRequestOptions,
                         mOriginString,
                         /* clientDataJson= */ null,
                         mClientDataHash,
-                        mCallback::onSignResponse,
-                        mErrorCallback,
                         /* ignoreGpm= */ false);
 
         assertThat(result).isEqualTo(AuthenticatorStatus.SUCCESS);
@@ -356,14 +361,15 @@
         Runnable noCredentialsFallback = Mockito.mock(Runnable.class);
         mCredManHelper.setNoCredentialsFallback(noCredentialsFallback);
 
+        mRequestCallback =
+                WebauthnRequestCallback.forGetCredential(mGetCredentialResponseCallback, null);
+        when(mAuthenticationContextProviderMock.getRequestCallback()).thenReturn(mRequestCallback);
         int result =
                 mCredManHelper.startGetRequest(
                         mRequestOptions,
                         mOriginString,
                         /* clientDataJson= */ null,
                         mClientDataHash,
-                        mCallback::onSignResponse,
-                        mErrorCallback,
                         /* ignoreGpm= */ false);
 
         assertThat(result).isEqualTo(AuthenticatorStatus.SUCCESS);
@@ -382,15 +388,15 @@
     @Test
     @SmallTest
     public void testStartGetRequest_noCredentials_errorHandlerCalledIfNoFallbackSet() {
-
+        mRequestCallback =
+                WebauthnRequestCallback.forGetCredential(mGetCredentialResponseCallback, null);
+        when(mAuthenticationContextProviderMock.getRequestCallback()).thenReturn(mRequestCallback);
         int result =
                 mCredManHelper.startGetRequest(
                         mRequestOptions,
                         mOriginString,
                         /* clientDataJson= */ null,
                         mClientDataHash,
-                        mCallback::onSignResponse,
-                        mErrorCallback,
                         /* ignoreGpm= */ false);
 
         assertThat(result).isEqualTo(AuthenticatorStatus.SUCCESS);
@@ -399,23 +405,21 @@
         GetCredentialException exception =
                 new GetCredentialException(GetCredentialException.TYPE_NO_CREDENTIAL, "Message");
         shadowCredentialManager.getGetCredentialCallback().onError(exception);
-        verify(mErrorCallback, times(1))
-                .onResult(
-                        AuthenticatorStatus.NOT_ALLOWED_ERROR,
-                        GetAssertionOutcome.CREDENTIAL_NOT_RECOGNIZED);
+        verify(mGetCredentialResponseCallback).call(any());
     }
 
     @Test
     @SmallTest
     public void testStartGetRequest_userCancel_notAllowedError() {
+        mRequestCallback =
+                WebauthnRequestCallback.forGetCredential(mGetCredentialResponseCallback, null);
+        when(mAuthenticationContextProviderMock.getRequestCallback()).thenReturn(mRequestCallback);
         int result =
                 mCredManHelper.startGetRequest(
                         mRequestOptions,
                         mOriginString,
                         /* clientDataJson= */ null,
                         mClientDataHash,
-                        mCallback::onSignResponse,
-                        mErrorCallback,
                         /* ignoreGpm= */ false);
 
         assertThat(result).isEqualTo(AuthenticatorStatus.SUCCESS);
@@ -425,10 +429,7 @@
                 new GetCredentialException(GetCredentialException.TYPE_USER_CANCELED, "Message");
         shadowCredentialManager.getGetCredentialCallback().onError(exception);
 
-        verify(mErrorCallback, times(1))
-                .onResult(
-                        AuthenticatorStatus.NOT_ALLOWED_ERROR,
-                        GetAssertionOutcome.USER_CANCELLATION);
+        verify(mGetCredentialResponseCallback).call(any());
         verify(mBrowserBridge, times(1)).onCredManUiClosed(any(), anyBoolean());
         verify(mMetricsHelper, times(1))
                 .reportGetCredentialMetrics(eq(CredManGetRequestEnum.CANCELLED), anyInt());
@@ -437,14 +438,15 @@
     @Test
     @SmallTest
     public void testStartGetRequest_unknownError_unknownError() {
+        mRequestCallback =
+                WebauthnRequestCallback.forGetCredential(mGetCredentialResponseCallback, null);
+        when(mAuthenticationContextProviderMock.getRequestCallback()).thenReturn(mRequestCallback);
         int result =
                 mCredManHelper.startGetRequest(
                         mRequestOptions,
                         mOriginString,
                         /* clientDataJson= */ null,
                         mClientDataHash,
-                        mCallback::onSignResponse,
-                        mErrorCallback,
                         /* ignoreGpm= */ false);
 
         assertThat(result).isEqualTo(AuthenticatorStatus.SUCCESS);
@@ -454,7 +456,7 @@
                 new GetCredentialException(GetCredentialException.TYPE_UNKNOWN, "Message");
         shadowCredentialManager.getGetCredentialCallback().onError(exception);
 
-        verify(mErrorCallback, times(1)).onResult(AuthenticatorStatus.UNKNOWN_ERROR, null);
+        verify(mGetCredentialResponseCallback).call(any());
         verify(mBrowserBridge, times(1)).onCredManUiClosed(any(), anyBoolean());
         verify(mMetricsHelper, times(1))
                 .reportGetCredentialMetrics(eq(CredManGetRequestEnum.FAILURE), anyInt());
@@ -464,14 +466,15 @@
     @SmallTest
     public void testStartPrefetchRequest_default_success() {
         mRequestOptions.mediation = Mediation.CONDITIONAL;
+        mRequestCallback =
+                WebauthnRequestCallback.forGetCredential(mGetCredentialResponseCallback, null);
+        when(mAuthenticationContextProviderMock.getRequestCallback()).thenReturn(mRequestCallback);
 
         mCredManHelper.startPrefetchRequest(
                 mRequestOptions,
                 mOriginString,
                 /* clientDataJson= */ null,
                 mClientDataHash,
-                mCallback::onSignResponse,
-                mErrorCallback,
                 mBarrier,
                 /* stopImmediateTimer= */ null,
                 /* ignoreGpm= */ false);
@@ -493,7 +496,7 @@
 
         credManCallSuccessfulRunback.getValue().run();
 
-        assertThat(mCallback.getStatus()).isNull();
+        verify(mGetCredentialResponseCallback, never()).call(any());
         verify(mBrowserBridge, times(1))
                 .onCredManConditionalRequestPending(any(), anyBoolean(), any());
         verify(mBrowserBridge, never()).onCredManUiClosed(any(), anyBoolean());
@@ -508,14 +511,15 @@
     @SmallTest
     public void testStartPrefetchRequest_unknownError_unknownError() {
         mRequestOptions.mediation = Mediation.CONDITIONAL;
+        mRequestCallback =
+                WebauthnRequestCallback.forGetCredential(mGetCredentialResponseCallback, null);
+        when(mAuthenticationContextProviderMock.getRequestCallback()).thenReturn(mRequestCallback);
 
         mCredManHelper.startPrefetchRequest(
                 mRequestOptions,
                 mOriginString,
                 /* clientDataJson= */ null,
                 mClientDataHash,
-                mCallback::onSignResponse,
-                mErrorCallback,
                 mBarrier,
                 /* stopImmediateTimer= */ null,
                 /* ignoreGpm= */ false);
@@ -540,14 +544,15 @@
     @SmallTest
     public void testCancelConditionalGetAssertion_whileWaitingForSelection_notAllowedError() {
         mRequestOptions.mediation = Mediation.CONDITIONAL;
+        mRequestCallback =
+                WebauthnRequestCallback.forGetCredential(mGetCredentialResponseCallback, null);
+        when(mAuthenticationContextProviderMock.getRequestCallback()).thenReturn(mRequestCallback);
 
         mCredManHelper.startPrefetchRequest(
                 mRequestOptions,
                 mOriginString,
                 /* clientDataJson= */ null,
                 mClientDataHash,
-                mCallback::onSignResponse,
-                mErrorCallback,
                 mBarrier,
                 /* stopImmediateTimer= */ null,
                 /* ignoreGpm= */ false);
@@ -578,14 +583,15 @@
             testStartGetRequestAfterStartPrefetchRequest_userCancelWhileWaitingForSelection_doesNotCancelConditionalRequest() {
         ArgumentCaptor<Callback<Boolean>> callbackCaptor = ArgumentCaptor.forClass(Callback.class);
         mRequestOptions.mediation = Mediation.CONDITIONAL;
+        mRequestCallback =
+                WebauthnRequestCallback.forGetCredential(mGetCredentialResponseCallback, null);
+        when(mAuthenticationContextProviderMock.getRequestCallback()).thenReturn(mRequestCallback);
 
         mCredManHelper.startPrefetchRequest(
                 mRequestOptions,
                 mOriginString,
                 /* clientDataJson= */ null,
                 mClientDataHash,
-                mCallback::onSignResponse,
-                mErrorCallback,
                 mBarrier,
                 /* stopImmediateTimer= */ null,
                 /* ignoreGpm= */ false);
@@ -618,7 +624,7 @@
                         new GetCredentialException(
                                 GetCredentialException.TYPE_USER_CANCELED, "Message"));
 
-        assertThat(mCallback.getStatus()).isNull();
+        verify(mGetCredentialResponseCallback, never()).call(any());
         verify(mBrowserBridge, never()).cleanupRequest(any());
         verify(mBrowserBridge, never()).cleanupCredManRequest(any());
         verify(mBrowserBridge, times(1)).onCredManUiClosed(any(), anyBoolean());
@@ -632,14 +638,15 @@
             testStartGetRequestAfterStartPrefetchRequest_userSelectsPassword_canHavePasswordResponse() {
         ArgumentCaptor<Callback<Boolean>> callbackCaptor = ArgumentCaptor.forClass(Callback.class);
         mRequestOptions.mediation = Mediation.CONDITIONAL;
+        mRequestCallback =
+                WebauthnRequestCallback.forGetCredential(mGetCredentialResponseCallback, null);
+        when(mAuthenticationContextProviderMock.getRequestCallback()).thenReturn(mRequestCallback);
 
         mCredManHelper.startPrefetchRequest(
                 mRequestOptions,
                 mOriginString,
                 /* clientDataJson= */ null,
                 mClientDataHash,
-                mCallback::onSignResponse,
-                mErrorCallback,
                 mBarrier,
                 /* stopImmediateTimer= */ null,
                 /* ignoreGpm= */ false);
@@ -673,7 +680,7 @@
 
         verify(mBrowserBridge, never()).onCredManUiClosed(any(), anyBoolean());
         // A password is selected, the callback will not be signed.
-        assertThat(mCallback.getStatus()).isNull();
+        verify(mGetCredentialResponseCallback, never()).call(any());
 
         verify(mBrowserBridge, times(1))
                 .onPasswordCredentialReceived(any(), eq(username), eq(password));
@@ -684,13 +691,14 @@
     @Test
     @SmallTest
     public void testStartGetRequest_ignoreGpm_DisablesBrandingAndHasBooleanInBundle() {
+        mRequestCallback =
+                WebauthnRequestCallback.forGetCredential(mGetCredentialResponseCallback, null);
+        when(mAuthenticationContextProviderMock.getRequestCallback()).thenReturn(mRequestCallback);
         mCredManHelper.startGetRequest(
                 mRequestOptions,
                 mOriginString,
                 /* clientDataJson= */ null,
                 mClientDataHash,
-                mCallback::onSignResponse,
-                mErrorCallback,
                 /* ignoreGpm= */ true);
 
         ShadowCredentialManager shadowCredentialManager = Shadow.extract(mCredentialManager);
@@ -702,6 +710,9 @@
     @SmallTest
     public void testImmediateGetCredential_credManOnly_success() {
         mRequestOptions.mediation = Mediation.IMMEDIATE;
+        mRequestCallback =
+                WebauthnRequestCallback.forGetCredential(mGetCredentialResponseCallback, null);
+        when(mAuthenticationContextProviderMock.getRequestCallback()).thenReturn(mRequestCallback);
 
         CredManSupportProvider.setupForTesting(
                 /* overrideAndroidVersion= */ Build.VERSION_CODES.UPSIDE_DOWN_CAKE,
@@ -714,8 +725,6 @@
                 mOriginString,
                 /* clientDataJson= */ null,
                 mClientDataHash,
-                mCallback::onSignResponse,
-                mErrorCallback,
                 mBarrier,
                 stopImmediateTimer,
                 /* ignoreGpm= */ false);
@@ -739,7 +748,7 @@
         credManCallSuccessfulRunback.getValue().run();
 
         verify(stopImmediateTimer, times(1)).run();
-        assertThat(mCallback.getStatus()).isNull();
+        verify(mGetCredentialResponseCallback, never()).call(any());
         verify(mBrowserBridge, never())
                 .onCredManConditionalRequestPending(any(), anyBoolean(), any());
         verify(mMetricsHelper, times(1))
@@ -758,7 +767,7 @@
         GetCredentialResponse response = new GetCredentialResponse(createPasskeyCredential());
         shadowCredentialManager.getGetCredentialCallback().onResult(response);
 
-        assertThat(mCallback.getStatus()).isEqualTo(Integer.valueOf(AuthenticatorStatus.SUCCESS));
+        verify(mGetCredentialResponseCallback).call(any());
         verify(mBrowserBridge, times(1)).onCredManUiClosed(any(), anyBoolean());
         verify(mMetricsHelper, times(1))
                 .reportGetCredentialMetrics(eq(CredManGetRequestEnum.SENT_REQUEST), anyInt());
@@ -770,6 +779,9 @@
     @SmallTest
     public void testImmediateMediation_userSelectsPassword_canHavePasswordResponse() {
         mRequestOptions.mediation = Mediation.IMMEDIATE;
+        mRequestCallback =
+                WebauthnRequestCallback.forGetCredential(mGetCredentialResponseCallback, null);
+        when(mAuthenticationContextProviderMock.getRequestCallback()).thenReturn(mRequestCallback);
 
         int result =
                 mCredManHelper.startGetRequest(
@@ -777,8 +789,6 @@
                         mOriginString,
                         /* clientDataJson= */ null,
                         mClientDataHash,
-                        mCallback::onSignResponse,
-                        mErrorCallback,
                         /* ignoreGpm= */ false);
 
         assertThat(result).isEqualTo(AuthenticatorStatus.SUCCESS);
@@ -793,13 +803,15 @@
         verify(mBrowserBridge, never()).onCredManUiClosed(any(), anyBoolean());
         verify(mBrowserBridge, never()).onPasswordCredentialReceived(any(), any(), any());
 
-        assertThat(mCallback.getStatus()).isEqualTo(Integer.valueOf(AuthenticatorStatus.SUCCESS));
-        Assert.assertNotNull(mCallback.getGetAssertionPasswordCredential());
-        assertThat(mojoStringToJavaString(mCallback.getGetAssertionPasswordCredential().name))
+        ArgumentCaptor<org.chromium.blink.mojom.GetCredentialResponse> responseCaptor =
+                ArgumentCaptor.forClass(org.chromium.blink.mojom.GetCredentialResponse.class);
+        verify(mGetCredentialResponseCallback).call(responseCaptor.capture());
+        Assert.assertNotNull(responseCaptor.getValue().getPasswordResponse());
+        assertThat(mojoStringToJavaString(responseCaptor.getValue().getPasswordResponse().name))
                 .isEqualTo(username);
-        assertThat(mojoStringToJavaString(mCallback.getGetAssertionPasswordCredential().id))
+        assertThat(mojoStringToJavaString(responseCaptor.getValue().getPasswordResponse().id))
                 .isEqualTo(username);
-        assertThat(mojoStringToJavaString(mCallback.getGetAssertionPasswordCredential().password))
+        assertThat(mojoStringToJavaString(responseCaptor.getValue().getPasswordResponse().password))
                 .isEqualTo(password);
     }
 
@@ -807,14 +819,15 @@
     @SmallTest
     public void testImmediateGetCredential_timeout_notAllowed() {
         mRequestOptions.mediation = Mediation.IMMEDIATE;
+        mRequestCallback =
+                WebauthnRequestCallback.forGetCredential(mGetCredentialResponseCallback, null);
+        when(mAuthenticationContextProviderMock.getRequestCallback()).thenReturn(mRequestCallback);
 
         mCredManHelper.startPrefetchRequest(
                 mRequestOptions,
                 mOriginString,
                 /* clientDataJson= */ null,
                 mClientDataHash,
-                mCallback::onSignResponse,
-                mErrorCallback,
                 mBarrier,
                 /* stopImmediateTimer= */ null,
                 /* ignoreGpm= */ false);
diff --git a/components/webauthn/ios/passkey_tab_helper.h b/components/webauthn/ios/passkey_tab_helper.h
index 0ab8cbcd..cc1698a 100644
--- a/components/webauthn/ios/passkey_tab_helper.h
+++ b/components/webauthn/ios/passkey_tab_helper.h
@@ -62,18 +62,16 @@
   bool HasCredential(const std::string& rp_id,
                      const std::string& credential_id) const;
 
-  // Requests a passkey to be created given the provided params.
-  // Fetches the shared keys list and calls the CompletePasskeyCreation
-  // callback.
+  // Requests a passkey to be created given the provided request ID. Fetches the
+  // shared keys list and calls the CompletePasskeyCreation callback.
   // TODO(crbug.com/460485333): Test passkey creation flow.
   void StartPasskeyCreation(std::string request_id);
 
-  // Requests that the provided passkey be used for passkey assertion given the
-  // provided params. Fetches the shared keys list and calls the
-  // CompletePasskeyAssertion callback.
+  // Requests that the passkey matching the provided credential ID be used for
+  // passkey assertion given the provided request ID. Fetches the shared keys
+  // list and calls the CompletePasskeyAssertion callback.
   // TODO(crbug.com/460485333): Test passkey assertion flow.
-  void StartPasskeyAssertion(std::string request_id,
-                             sync_pb::WebauthnCredentialSpecifics passkey);
+  void StartPasskeyAssertion(std::string request_id, std::string credential_id);
 
  private:
   friend class web::WebStateUserData<PasskeyTabHelper>;
diff --git a/components/webauthn/ios/passkey_tab_helper.mm b/components/webauthn/ios/passkey_tab_helper.mm
index 1ef89544..785d322 100644
--- a/components/webauthn/ios/passkey_tab_helper.mm
+++ b/components/webauthn/ios/passkey_tab_helper.mm
@@ -132,6 +132,26 @@
       std::move(client_data_json));
 }
 
+// Attempts to find a passkey matching the provided credential ID in a list of
+// passkeys. Returns the passkey on success and std::nullopt on failure.
+std::optional<sync_pb::WebauthnCredentialSpecifics> FindPasskey(
+    std::vector<sync_pb::WebauthnCredentialSpecifics> passkeys,
+    std::string credential_id) {
+  if (passkeys.empty()) {
+    return std::nullopt;
+  }
+
+  auto it = std::find_if(
+      passkeys.begin(), passkeys.end(),
+      [&credential_id](const sync_pb::WebauthnCredentialSpecifics& passkey) {
+        return passkey.credential_id() == credential_id;
+      });
+  if (it == passkeys.end()) {
+    return std::nullopt;
+  }
+  return *it;
+}
+
 }  // namespace
 
 PasskeyTabHelper::~PasskeyTabHelper() = default;
@@ -389,9 +409,8 @@
       std::move(attestation_data));
 }
 
-void PasskeyTabHelper::StartPasskeyAssertion(
-    std::string request_id,
-    sync_pb::WebauthnCredentialSpecifics passkey) {
+void PasskeyTabHelper::StartPasskeyAssertion(std::string request_id,
+                                             std::string credential_id) {
   std::optional<AssertionRequestParams> optional_params =
       ExtractParamsFromAssertionRequestsMap(request_id);
   if (!optional_params.has_value()) {
@@ -405,6 +424,13 @@
     return;
   }
 
+  std::optional<sync_pb::WebauthnCredentialSpecifics> passkey =
+      FindPasskey(GetFilteredPasskeys(params), std::move(credential_id));
+  if (!passkey.has_value()) {
+    DeferToRenderer(web_frame, params);
+    return;
+  }
+
   // TODO(crbug.com/460485333): Use proper top origin.
   std::string client_data_json = BuildClientDataJson(
       {ClientDataRequestType::kWebAuthnGet, web_frame->GetSecurityOrigin(),
@@ -415,7 +441,7 @@
   client_->FetchKeys(
       ReauthenticatePurpose::kDecrypt,
       base::BindOnce(&PasskeyTabHelper::CompletePasskeyAssertion,
-                     this->AsWeakPtr(), std::move(params), std::move(passkey),
+                     this->AsWeakPtr(), std::move(params), std::move(*passkey),
                      std::move(client_data_json)));
 }
 
diff --git a/content/app_shim_remote_cocoa/render_widget_host_ns_view_bridge.mm b/content/app_shim_remote_cocoa/render_widget_host_ns_view_bridge.mm
index 4b015ed..b40dca69 100644
--- a/content/app_shim_remote_cocoa/render_widget_host_ns_view_bridge.mm
+++ b/content/app_shim_remote_cocoa/render_widget_host_ns_view_bridge.mm
@@ -19,6 +19,7 @@
 #include "content/app_shim_remote_cocoa/render_widget_host_ns_view_host_helper.h"
 #import "content/app_shim_remote_cocoa/web_menu_runner_mac.h"
 #include "content/common/mac/attributed_string_type_converters.h"
+#include "mojo/public/cpp/bindings/receiver.h"
 #include "net/base/apple/url_conversions.h"
 #import "skia/ext/skia_utils_mac.h"
 #include "third_party/abseil-cpp/absl/cleanup/cleanup.h"
diff --git a/content/browser/accessibility/dump_accessibility_tree_browsertest.cc b/content/browser/accessibility/dump_accessibility_tree_browsertest.cc
index 160bdab..1a4b634 100644
--- a/content/browser/accessibility/dump_accessibility_tree_browsertest.cc
+++ b/content/browser/accessibility/dump_accessibility_tree_browsertest.cc
@@ -63,8 +63,6 @@
   // Enable aria-actions.
   command_line->AppendSwitchASCII(switches::kEnableBlinkFeatures,
                                   "AriaActions");
-  // Enable CSSInert, used by AccessibilityCSSInteractivityInert.
-  command_line->AppendSwitchASCII(switches::kEnableBlinkFeatures, "CSSInert");
   // Enable custom elements to have a default role of "none", removing them
   // from the accessibility tree.
   base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
diff --git a/content/browser/browser_interface_binders.cc b/content/browser/browser_interface_binders.cc
index 8d3a262..b341cd67 100644
--- a/content/browser/browser_interface_binders.cc
+++ b/content/browser/browser_interface_binders.cc
@@ -1122,7 +1122,7 @@
   if (BrowserMainLoop::GetInstance()) {
     map->Add<midi::mojom::MidiSessionProvider>(
         base::BindRepeating(&MidiHost::BindReceiver,
-                            host->GetProcess()->GetDeprecatedID(),
+                            host->GetProcess()->GetID(),
                             BrowserMainLoop::GetInstance()->midi_service()),
         GetIOThreadTaskRunner({}));
   }
diff --git a/content/browser/child_process_security_policy_impl.cc b/content/browser/child_process_security_policy_impl.cc
index 46945c1..c5a1a62 100644
--- a/content/browser/child_process_security_policy_impl.cc
+++ b/content/browser/child_process_security_policy_impl.cc
@@ -2541,10 +2541,11 @@
   file_system_policy_map_[type] = policy;
 }
 
-bool ChildProcessSecurityPolicyImpl::CanSendMidiMessage(int child_id) {
+bool ChildProcessSecurityPolicyImpl::CanSendMidiMessage(
+    ChildProcessId child_id) {
   base::AutoLock lock(lock_);
 
-  auto state = security_state_.find(child_id);
+  auto state = security_state_.find(child_id.GetUnsafeValue());
   if (state == security_state_.end()) {
     return false;
   }
@@ -2552,10 +2553,11 @@
   return state->second->CanSendMidi();
 }
 
-bool ChildProcessSecurityPolicyImpl::CanSendMidiSysExMessage(int child_id) {
+bool ChildProcessSecurityPolicyImpl::CanSendMidiSysExMessage(
+    ChildProcessId child_id) {
   base::AutoLock lock(lock_);
 
-  auto state = security_state_.find(child_id);
+  auto state = security_state_.find(child_id.GetUnsafeValue());
   if (state == security_state_.end()) {
     return false;
   }
diff --git a/content/browser/child_process_security_policy_impl.h b/content/browser/child_process_security_policy_impl.h
index f046524..b30aa44 100644
--- a/content/browser/child_process_security_policy_impl.h
+++ b/content/browser/child_process_security_policy_impl.h
@@ -26,6 +26,7 @@
 #include "content/browser/isolation_context.h"
 #include "content/browser/origin_agent_cluster_isolation_state.h"
 #include "content/common/content_export.h"
+#include "content/public/browser/child_process_id.h"
 #include "content/public/browser/child_process_security_policy.h"
 #include "content/public/common/bindings_policy.h"
 #include "storage/common/file_system/file_system_types.h"
@@ -509,10 +510,10 @@
                                           int policy);
 
   // Returns true if sending MIDI messages is allowed.
-  bool CanSendMidiMessage(int child_id);
+  bool CanSendMidiMessage(ChildProcessId child_id);
 
   // Returns true if sending system exclusive (SysEx) MIDI messages is allowed.
-  bool CanSendMidiSysExMessage(int child_id);
+  bool CanSendMidiSysExMessage(ChildProcessId child_id);
 
   // Remove all isolated origins associated with |browser_context| and clear any
   // pointers that may reference |browser_context|.  This is
diff --git a/content/browser/devtools/devtools_instrumentation.cc b/content/browser/devtools/devtools_instrumentation.cc
index 739c8da3..88a4302 100644
--- a/content/browser/devtools/devtools_instrumentation.cc
+++ b/content/browser/devtools/devtools_instrumentation.cc
@@ -1494,7 +1494,9 @@
     const base::UnguessableToken& frame_token,
     bool is_navigation,
     bool is_download,
-    network::mojom::URLLoaderFactoryOverride* agent_override) {
+    network::mojom::URLLoaderFactoryOverride* agent_override,
+    mojo::PendingRemote<network::mojom::TrustedURLLoaderHeaderClient>*
+        header_client) {
   if (!agent_host) {
     return false;
   }
@@ -1503,7 +1505,7 @@
   for (const auto& handler : base::Reversed(handlers)) {
     had_interceptors |= handler->MaybeCreateProxyForInterception(
         process_id, storage_partition, frame_token, is_navigation, is_download,
-        agent_override);
+        agent_override, header_client);
   }
   return had_interceptors;
 }
@@ -1514,7 +1516,9 @@
     bool is_navigation,
     bool is_download,
     network::URLLoaderFactoryBuilder& factory_builder,
-    network::mojom::URLLoaderFactoryOverridePtr* factory_override) {
+    network::mojom::URLLoaderFactoryOverridePtr* factory_override,
+    mojo::PendingRemote<network::mojom::TrustedURLLoaderHeaderClient>*
+        header_client) {
   CHECK(!is_download || is_navigation);
 
   network::mojom::URLLoaderFactoryOverride devtools_override;
@@ -1532,17 +1536,17 @@
   bool had_interceptors =
       MaybeCreateProxyForInterception<protocol::NetworkHandler>(
           agent_host_, process_id_, storage_partition_, devtools_token_,
-          is_navigation, is_download, handler_override);
+          is_navigation, is_download, handler_override, header_client);
 
   had_interceptors |= MaybeCreateProxyForInterception<protocol::FetchHandler>(
       agent_host_, process_id_, storage_partition_, devtools_token_,
-      is_navigation, is_download, handler_override);
+      is_navigation, is_download, handler_override, header_client);
 
   // TODO(caseq): assure deterministic order of browser agents (or sessions).
   for (auto* browser_agent_host : BrowserDevToolsAgentHost::Instances()) {
     had_interceptors |= MaybeCreateProxyForInterception<protocol::FetchHandler>(
         browser_agent_host, process_id_, storage_partition_, devtools_token_,
-        is_navigation, is_download, handler_override);
+        is_navigation, is_download, handler_override, header_client);
   }
   if (!had_interceptors) {
     return false;
diff --git a/content/browser/devtools/devtools_instrumentation.h b/content/browser/devtools/devtools_instrumentation.h
index 313dd72..b948e71 100644
--- a/content/browser/devtools/devtools_instrumentation.h
+++ b/content/browser/devtools/devtools_instrumentation.h
@@ -176,7 +176,9 @@
   bool Run(bool is_navigation,
            bool is_download,
            network::URLLoaderFactoryBuilder& factory_builder,
-           network::mojom::URLLoaderFactoryOverridePtr* factory_override);
+           network::mojom::URLLoaderFactoryOverridePtr* factory_override,
+           mojo::PendingRemote<network::mojom::TrustedURLLoaderHeaderClient>*
+               header_client);
 
   DevToolsAgentHostImpl* agent_host() { return agent_host_; }
 
diff --git a/content/browser/devtools/devtools_url_loader_interceptor.cc b/content/browser/devtools/devtools_url_loader_interceptor.cc
index 243bf70f..b75595e7 100644
--- a/content/browser/devtools/devtools_url_loader_interceptor.cc
+++ b/content/browser/devtools/devtools_url_loader_interceptor.cc
@@ -33,6 +33,7 @@
 #include "content/public/common/content_client.h"
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "mojo/public/cpp/bindings/receiver_set.h"
+#include "mojo/public/cpp/bindings/self_owned_receiver.h"
 #include "mojo/public/cpp/system/data_pipe_drainer.h"
 #include "net/base/load_flags.h"
 #include "net/base/mime_sniffer.h"
@@ -308,6 +309,10 @@
         request.referrer_policy = net::ReferrerPolicy::NEVER_CLEAR;
       } else {
         request.headers.SetHeader(entry.first, entry.second);
+        if (base::EqualsCaseInsensitiveASCII(
+                entry.first, net::HttpRequestHeaders::kCookie)) {
+          instance->overridden_cookie_ = entry.second;
+        }
       }
     }
     return instance;
@@ -317,6 +322,11 @@
     instance->request_->headers = std::move(instance->original_headers_);
     instance->request_->referrer = instance->original_referrer_;
     instance->request_->referrer_policy = instance->original_referrer_policy_;
+    instance->overridden_cookie_.reset();
+  }
+
+  const std::optional<std::string>& overridden_cookie() const {
+    return overridden_cookie_;
   }
 
   void RemoveUnsafeOriginalHeadersOnRedirect() {
@@ -358,12 +368,43 @@
   net::HttpRequestHeaders original_headers_;
   GURL original_referrer_;
   net::ReferrerPolicy original_referrer_policy_;
+  std::optional<std::string> overridden_cookie_;
+};
+
+// A no-op implementation of network::mojom::TrustedHeaderClient.
+//
+// The kURLLoadOptionUseHeaderClient option requires that the URLLoader's
+// header client receiver is bound to an implementation. If the receiver is
+// dropped, the URLLoader's remote disconnects, causing the request to fail
+// with net::ERR_FAILED.
+//
+// This class is used to fulfill that contract when no real interception is
+// active, ensuring the pipe is safely terminated and the request can proceed.
+class NoOpHeaderClient final : public network::mojom::TrustedHeaderClient {
+ public:
+  NoOpHeaderClient() = default;
+  NoOpHeaderClient(const NoOpHeaderClient&) = delete;
+  NoOpHeaderClient& operator=(const NoOpHeaderClient&) = delete;
+  ~NoOpHeaderClient() override = default;
+
+  void OnBeforeSendHeaders(const net::HttpRequestHeaders& headers,
+                           OnBeforeSendHeadersCallback callback) override {
+    std::move(callback).Run(net::OK, std::nullopt);
+  }
+
+  void OnHeadersReceived(const std::string& headers,
+                         const net::IPEndPoint& endpoint,
+                         const std::optional<net::SSLInfo>& ssl_info,
+                         OnHeadersReceivedCallback callback) override {
+    std::move(callback).Run(net::OK, std::nullopt, std::nullopt);
+  }
 };
 
 }  // namespace
 
 class InterceptionJob : public network::mojom::URLLoaderClient,
-                        public network::mojom::URLLoader {
+                        public network::mojom::URLLoader,
+                        public network::mojom::TrustedHeaderClient {
  public:
   static InterceptionJob* FindByRequestId(
       const GlobalRequestID& global_req_id) {
@@ -388,6 +429,11 @@
   InterceptionJob(const InterceptionJob&) = delete;
   InterceptionJob& operator=(const InterceptionJob&) = delete;
 
+  void OnLoaderCreated(
+      mojo::PendingReceiver<network::mojom::TrustedHeaderClient>
+          header_client_receiver,
+      mojo::PendingRemote<network::mojom::TrustedHeaderClient> header_client);
+
   void GetResponseBody(std::unique_ptr<GetResponseBodyCallback> callback);
   void TakeResponseBodyPipe(TakeResponseBodyPipeCallback callback);
   void ContinueInterceptedRequest(
@@ -480,6 +526,21 @@
   void OnTransferSizeUpdated(int32_t transfer_size_diff) override;
   void OnComplete(const network::URLLoaderCompletionStatus& status) override;
 
+  // network::mojom::TrustedHeaderClient methods
+  void OnBeforeSendHeaders(const net::HttpRequestHeaders& headers,
+                           OnBeforeSendHeadersCallback callback) override;
+  void OnHeadersReceived(const std::string& headers,
+                         const net::IPEndPoint& endpoint,
+                         const std::optional<net::SSLInfo>& ssl_info,
+                         OnHeadersReceivedCallback callback) override;
+
+  void OnTargetHeaderClientDisconnect();
+  void OnTargetHeaderClientBeforeSendHeadersComplete(
+      const net::HttpRequestHeaders& original_headers,
+      OnBeforeSendHeadersCallback original_callback,
+      int result_from_target,
+      const std::optional<net::HttpRequestHeaders>& headers_from_target);
+
   void StartLoadingResponseBody(mojo::ScopedDataPipeConsumerHandle body);
 
   bool CanGetResponseBody(std::string* error_reason);
@@ -502,11 +563,14 @@
 
   mojo::Receiver<network::mojom::URLLoaderClient> client_receiver_{this};
   mojo::Receiver<network::mojom::URLLoader> loader_receiver_{this};
+  mojo::Receiver<network::mojom::TrustedHeaderClient> header_client_receiver_{
+      this};
 
   mojo::Remote<network::mojom::URLLoaderClient> client_;
   mojo::Remote<network::mojom::URLLoader> loader_;
   mojo::Remote<network::mojom::URLLoaderFactory> target_factory_;
   mojo::Remote<network::mojom::CookieManager> cookie_manager_;
+  mojo::Remote<network::mojom::TrustedHeaderClient> header_client_;
 
   enum State {
     kNotStarted,
@@ -564,6 +628,8 @@
   std::unique_ptr<RequestBodyCollector> request_body_collector_;
 
   SEQUENCE_CHECKER(sequence_checker_);
+
+  base::WeakPtrFactory<InterceptionJob> weak_ptr_factory_{this};
 };
 
 void DevToolsURLLoaderInterceptor::CreateJob(
@@ -602,7 +668,9 @@
   return stages;
 }
 
-class DevToolsURLLoaderFactoryProxy : public network::mojom::URLLoaderFactory {
+class DevToolsURLLoaderFactoryProxy
+    : public network::mojom::URLLoaderFactory,
+      public network::mojom::TrustedURLLoaderHeaderClient {
  public:
   DevToolsURLLoaderFactoryProxy(
       const base::UnguessableToken& frame_token,
@@ -611,6 +679,10 @@
       mojo::PendingReceiver<network::mojom::URLLoaderFactory> loader_receiver,
       mojo::PendingRemote<network::mojom::URLLoaderFactory>
           target_factory_remote,
+      mojo::PendingReceiver<network::mojom::TrustedURLLoaderHeaderClient>
+          header_client_receiver,
+      mojo::PendingRemote<network::mojom::TrustedURLLoaderHeaderClient>
+          target_header_client_remote,
       mojo::PendingRemote<network::mojom::CookieManager> cookie_manager,
       base::WeakPtr<DevToolsURLLoaderInterceptor> interceptor);
   ~DevToolsURLLoaderFactoryProxy() override;
@@ -628,19 +700,36 @@
   void Clone(mojo::PendingReceiver<network::mojom::URLLoaderFactory> receiver)
       override;
 
+  // network::mojom::TrustedURLLoaderHeaderClient:
+  void OnLoaderCreated(
+      int32_t request_id,
+      mojo::PendingReceiver<network::mojom::TrustedHeaderClient> receiver)
+      override;
+  void OnLoaderForCorsPreflightCreated(
+      const network::ResourceRequest& request,
+      mojo::PendingReceiver<network::mojom::TrustedHeaderClient> receiver)
+      override;
+
   void OnProxyBindingError();
   void OnTargetFactoryError();
+  void OnTargetHeaderClientError();
 
   const base::UnguessableToken frame_token_;
   const int32_t process_id_;
   const bool is_download_;
 
   mojo::Remote<network::mojom::URLLoaderFactory> target_factory_;
+  mojo::Remote<network::mojom::TrustedURLLoaderHeaderClient>
+      target_url_loader_header_client_;
+  mojo::Receiver<network::mojom::TrustedURLLoaderHeaderClient>
+      url_loader_header_client_receiver_{this};
   mojo::Remote<network::mojom::CookieManager> cookie_manager_;
   base::WeakPtr<DevToolsURLLoaderInterceptor> interceptor_;
   mojo::ReceiverSet<network::mojom::URLLoaderFactory> receivers_;
 
   SEQUENCE_CHECKER(sequence_checker_);
+
+  base::WeakPtrFactory<DevToolsURLLoaderFactoryProxy> weak_ptr_factory_{this};
 };
 
 // This class owns itself and will delete self when any mojo
@@ -651,6 +740,10 @@
     bool is_download,
     mojo::PendingReceiver<network::mojom::URLLoaderFactory> loader_receiver,
     mojo::PendingRemote<network::mojom::URLLoaderFactory> target_factory_remote,
+    mojo::PendingReceiver<network::mojom::TrustedURLLoaderHeaderClient>
+        header_client_receiver,
+    mojo::PendingRemote<network::mojom::TrustedURLLoaderHeaderClient>
+        target_header_client_remote,
     mojo::PendingRemote<network::mojom::CookieManager> cookie_manager,
     base::WeakPtr<DevToolsURLLoaderInterceptor> interceptor)
     : frame_token_(frame_token),
@@ -667,6 +760,21 @@
       base::BindRepeating(&DevToolsURLLoaderFactoryProxy::OnProxyBindingError,
                           base::Unretained(this)));
 
+  if (header_client_receiver) {
+    url_loader_header_client_receiver_.Bind(std::move(header_client_receiver));
+  }
+  if (target_header_client_remote) {
+    target_url_loader_header_client_.Bind(
+        std::move(target_header_client_remote));
+
+    // Use a WeakPtr for safety. The disconnection of this ancillary header
+    // client pipe is a separate asynchronous event from the disconnection of
+    // the main factory pipe that controls this proxy's lifetime.
+    target_url_loader_header_client_.set_disconnect_handler(base::BindOnce(
+        &DevToolsURLLoaderFactoryProxy::OnTargetHeaderClientError,
+        weak_ptr_factory_.GetWeakPtr()));
+  }
+
   cookie_manager_.Bind(std::move(cookie_manager));
   cookie_manager_.set_disconnect_handler(
       base::BindOnce(&DevToolsURLLoaderFactoryProxy::OnTargetFactoryError,
@@ -710,6 +818,63 @@
   receivers_.Add(this, std::move(receiver));
 }
 
+void DevToolsURLLoaderFactoryProxy::OnLoaderCreated(
+    int32_t request_id,
+    mojo::PendingReceiver<network::mojom::TrustedHeaderClient> receiver) {
+  DevToolsURLLoaderInterceptor* interceptor = interceptor_.get();
+  if (interceptor) {
+    if (InterceptionJob* job = interceptor->FindJobByRequestId(request_id)) {
+      // An InterceptionJob exists for this request, so establish the
+      // per-request proxy chain. A new pipe is created for the downstream
+      // client (if any). The job's receiver and the new target remote are then
+      // passed to the job for binding.
+      mojo::PendingRemote<network::mojom::TrustedHeaderClient>
+          target_header_client;
+      if (target_url_loader_header_client_) {
+        target_url_loader_header_client_->OnLoaderCreated(
+            request_id, target_header_client.InitWithNewPipeAndPassReceiver());
+      }
+      job->OnLoaderCreated(std::move(receiver),
+                           std::move(target_header_client));
+      return;
+    }
+  }
+
+  // Fallback path when interception is disabled (e.g., via Fetch.disable)
+  // or a job was not found. The request should proceed as if DevTools was
+  // not attached, by forwarding the header client call transparently.
+  if (target_url_loader_header_client_) {
+    target_url_loader_header_client_->OnLoaderCreated(request_id,
+                                                      std::move(receiver));
+  } else {
+    // There is no downstream client. To prevent the request from failing with
+    // net::ERR_FAILED due to a disconnected header client pipe, bind the
+    // receiver to a self-owned no-op implementation. This safely terminates the
+    // channel and allows the request to proceed unmodified.
+    mojo::MakeSelfOwnedReceiver(std::make_unique<NoOpHeaderClient>(),
+                                std::move(receiver));
+  }
+}
+
+void DevToolsURLLoaderFactoryProxy::OnLoaderForCorsPreflightCreated(
+    const network::ResourceRequest& request,
+    mojo::PendingReceiver<network::mojom::TrustedHeaderClient> receiver) {
+  // CORS preflight requests are handled by the browser and are not currently
+  // intercepted by the DevTools Fetch API. The call must be forwarded or
+  // handled to maintain the integrity of the header client channel.
+  if (target_url_loader_header_client_) {
+    target_url_loader_header_client_->OnLoaderForCorsPreflightCreated(
+        request, std::move(receiver));
+  } else {
+    // There is no downstream client. To prevent the preflight request from
+    // failing with net::ERR_FAILED due to a disconnected pipe, the receiver
+    // must be bound. A self-owned no-op implementation fulfills this contract
+    // and allows the preflight to proceed unmodified.
+    mojo::MakeSelfOwnedReceiver(std::make_unique<NoOpHeaderClient>(),
+                                std::move(receiver));
+  }
+}
+
 void DevToolsURLLoaderFactoryProxy::OnTargetFactoryError() {
   delete this;
 }
@@ -719,6 +884,14 @@
     delete this;
 }
 
+void DevToolsURLLoaderFactoryProxy::OnTargetHeaderClientError() {
+  // The downstream header client factory has disconnected.
+  // The remote is reset to prevent further calls. This proxy is not
+  // destroyed here, as its lifecycle is tied to the main URLLoaderFactory
+  // pipe (`target_factory_`).
+  target_url_loader_header_client_.reset();
+}
+
 // static
 void DevToolsURLLoaderInterceptor::HandleAuthRequest(
     GlobalRequestID req_id,
@@ -783,7 +956,9 @@
     const base::UnguessableToken& frame_token,
     bool is_navigation,
     bool is_download,
-    network::mojom::URLLoaderFactoryOverride* intercepting_factory) {
+    network::mojom::URLLoaderFactoryOverride* intercepting_factory,
+    mojo::PendingRemote<network::mojom::TrustedURLLoaderHeaderClient>*
+        header_client) {
   DCHECK(storage_partition);
 
   if (patterns_.empty())
@@ -803,6 +978,21 @@
       target_remote.InitWithNewPipeAndPassReceiver();
   mojo::PendingRemote<network::mojom::CookieManager> cookie_manager;
 
+  mojo::PendingRemote<network::mojom::TrustedURLLoaderHeaderClient>
+      target_header_client;
+  mojo::PendingReceiver<network::mojom::TrustedURLLoaderHeaderClient>
+      header_client_receiver;
+
+  // Intercept the TrustedURLLoaderHeaderClient channel to act as a proxy.
+  // The original `header_client` remote, which points to the downstream
+  // client, is moved to become the proxy's target. The caller's `header_client`
+  // is then replaced with a new remote that points to this proxy's receiver,
+  // effectively inserting DevTools into the chain.
+  if (header_client) {
+    target_header_client = std::move(*header_client);
+    *header_client = header_client_receiver.InitWithNewPipeAndPassRemote();
+  }
+
   // TODO(crbug.com/40276949): Using 0 as the process id for navigations
   // can lead to collisions between multiple navigations/service workers main
   // script fetch. It should be replaced by the more robust
@@ -816,7 +1006,8 @@
   new DevToolsURLLoaderFactoryProxy(
       frame_token, process_id_override, is_download,
       std::move(intercepting_factory->overridden_factory_receiver),
-      std::move(target_remote), std::move(cookie_manager),
+      std::move(target_remote), std::move(header_client_receiver),
+      std::move(target_header_client), std::move(cookie_manager),
       weak_factory_.GetWeakPtr());
   intercepting_factory->overridden_factory_receiver =
       std::move(overridden_factory_receiver);
@@ -873,7 +1064,7 @@
   start_time_ = base::Time::Now();
 
   current_id_ = id_prefix_ + base::StringPrintf(".%d", redirect_count_);
-  interceptor_->AddJob(current_id_, this);
+  interceptor_->AddJob(create_loader_params_->request_id, current_id_, this);
 
   const network::ResourceRequest& request = create_loader_params_->request;
   stages_ = interceptor_->GetInterceptionStages(
@@ -1431,9 +1622,15 @@
 
   state_ = State::kRequestSent;
 
+  uint32_t options = create_loader_params_->options;
+  // Always enable the header client for intercepted requests. Ensures the
+  // header client channel is always available, making the cookie override logic
+  // robust across redirects.
+  options |= network::mojom::kURLLoadOptionUseHeaderClient;
+
   target_factory_->CreateLoaderAndStart(
       loader_.BindNewPipeAndPassReceiver(), create_loader_params_->request_id,
-      create_loader_params_->options, create_loader_params_->request,
+      options, create_loader_params_->request,
       client_receiver_.BindNewPipeAndPassRemote(),
       create_loader_params_->traffic_annotation);
   client_receiver_.set_disconnect_handler(
@@ -1589,7 +1786,7 @@
 
 void InterceptionJob::Shutdown() {
   if (interceptor_)
-    interceptor_->RemoveJob(current_id_);
+    interceptor_->RemoveJob(create_loader_params_->request_id, current_id_);
   delete this;
 }
 
@@ -1656,7 +1853,7 @@
     redirected_request_id_ = current_id_;
     // Pretend that each redirect hop is a new request -- this is for
     // compatibilty with URLRequestJob-based interception implementation.
-    interceptor_->RemoveJob(current_id_);
+    interceptor_->RemoveJob(create_loader_params_->request_id, current_id_);
     redirect_count_++;
     if (StartJobAndMaybeNotify())
       return;
@@ -1810,6 +2007,116 @@
   response_metadata_->status = status;
 }
 
+void InterceptionJob::OnLoaderCreated(
+    mojo::PendingReceiver<network::mojom::TrustedHeaderClient>
+        header_client_receiver,
+    mojo::PendingRemote<network::mojom::TrustedHeaderClient> header_client) {
+  header_client_receiver_.reset();
+  header_client_receiver_.Bind(std::move(header_client_receiver));
+  if (header_client) {
+    header_client_.reset();
+    header_client_.Bind(std::move(header_client));
+
+    // Use a WeakPtr for safety. A race is possible where the main request is
+    // completed or cancelled (destroying this job) before the disconnect
+    // handler for this separate pipe has a chance to run.
+    header_client_.set_disconnect_handler(
+        base::BindOnce(&InterceptionJob::OnTargetHeaderClientDisconnect,
+                       weak_ptr_factory_.GetWeakPtr()));
+  }
+}
+
+void InterceptionJob::OnTargetHeaderClientDisconnect() {
+  // The downstream per-request client has disconnected. Forwarding
+  // OnBeforeSendHeaders events to it is no longer possible. The remote is
+  // simply reset, as the job's lifecycle is bound to the main URLLoader
+  // and URLLoaderClient pipes.
+  header_client_.reset();
+}
+
+void InterceptionJob::OnBeforeSendHeaders(
+    const net::HttpRequestHeaders& headers,
+    OnBeforeSendHeadersCallback callback) {
+  if (header_client_) {
+    // Use a WeakPtr for safety. The InterceptionJob can be destroyed if the
+    // main request is cancelled while waiting for this asynchronous callback
+    // from the downstream client.
+    OnBeforeSendHeadersCallback wrapped_callback = base::BindOnce(
+        &InterceptionJob::OnTargetHeaderClientBeforeSendHeadersComplete,
+        weak_ptr_factory_.GetWeakPtr(), headers, std::move(callback));
+    header_client_->OnBeforeSendHeaders(headers, std::move(wrapped_callback));
+    return;
+  }
+
+  if (!headers_override_ ||
+      !headers_override_->overridden_cookie().has_value()) {
+    std::move(callback).Run(net::OK, headers);
+    return;
+  }
+
+  // A targeted override is applied for the Cookie header only. Other headers
+  // from the DevTools `continueRequest` command are not reapplied at this
+  // stage. Modifications to general headers are correctly propagated through
+  // the network stack. The Cookie header is a unique exception, as the network
+  // stack unconditionally overwrites it with values from the browser's cookie
+  // store. This late-stage override ensures the DevTools-provided cookie value
+  // has the final authority.
+  net::HttpRequestHeaders final_headers = headers;
+  final_headers.SetHeader(net::HttpRequestHeaders::kCookie,
+                          headers_override_->overridden_cookie().value());
+  std::move(callback).Run(net::OK, final_headers);
+}
+
+void InterceptionJob::OnHeadersReceived(
+    const std::string& headers,
+    const net::IPEndPoint& endpoint,
+    const std::optional<net::SSLInfo>& ssl_info,
+    OnHeadersReceivedCallback callback) {
+  // If a downstream client exists, delegate the responsibility of handling the
+  // event and invoking the callback entirely to it.
+  if (header_client_) {
+    header_client_->OnHeadersReceived(headers, endpoint, ssl_info,
+                                      std::move(callback));
+    return;
+  }
+
+  // If there is no downstream client, this job is responsible for un-pausing
+  // the request.
+  std::move(callback).Run(net::OK, std::nullopt, std::nullopt);
+}
+
+void InterceptionJob::OnTargetHeaderClientBeforeSendHeadersComplete(
+    const net::HttpRequestHeaders& original_headers,
+    OnBeforeSendHeadersCallback original_callback,
+    int result_from_target,
+    const std::optional<net::HttpRequestHeaders>& headers_from_target) {
+  // If the downstream client (e.g., an extension) blocked or cancelled the
+  // request, we must respect that decision and forward the result immediately.
+  if (result_from_target != net::OK) {
+    std::move(original_callback).Run(result_from_target, headers_from_target);
+    return;
+  }
+
+  if (!headers_override_ ||
+      !headers_override_->overridden_cookie().has_value()) {
+    std::move(original_callback).Run(result_from_target, headers_from_target);
+    return;
+  }
+
+  // A targeted override is applied for the Cookie header only. Other headers
+  // from the DevTools `continueRequest` command are not reapplied at this
+  // stage. Modifications to general headers are correctly propagated through
+  // the network stack. The Cookie header is a unique exception, as the network
+  // stack unconditionally overwrites it with values from the browser's cookie
+  // store. This late-stage override ensures the DevTools-provided cookie value
+  // has the final authority.
+  net::HttpRequestHeaders final_headers =
+      headers_from_target.value_or(original_headers);
+  final_headers.SetHeader(net::HttpRequestHeaders::kCookie,
+                          headers_override_->overridden_cookie().value());
+  std::move(original_callback).Run(result_from_target, final_headers);
+}
+
 void InterceptionJob::OnAuthRequest(
     const net::AuthChallengeInfo& auth_info,
     DevToolsURLLoaderInterceptor::HandleAuthRequestCallback callback) {
diff --git a/content/browser/devtools/devtools_url_loader_interceptor.h b/content/browser/devtools/devtools_url_loader_interceptor.h
index 2ac44501..c2360e79 100644
--- a/content/browser/devtools/devtools_url_loader_interceptor.h
+++ b/content/browser/devtools/devtools_url_loader_interceptor.h
@@ -32,6 +32,7 @@
 namespace network {
 namespace mojom {
 class URLLoaderFactoryOverride;
+class TrustedURLLoaderHeaderClient;
 }
 }  // namespace network
 
@@ -208,7 +209,9 @@
       const base::UnguessableToken& frame_token,
       bool is_navigation,
       bool is_download,
-      network::mojom::URLLoaderFactoryOverride* intercepting_factory);
+      network::mojom::URLLoaderFactoryOverride* intercepting_factory,
+      mojo::PendingRemote<network::mojom::TrustedURLLoaderHeaderClient>*
+          header_client);
 
  private:
   friend class InterceptionJob;
@@ -244,9 +247,24 @@
     return nullptr;
   }
 
-  void RemoveJob(const std::string& id) { jobs_.erase(id); }
-  void AddJob(const std::string& id, InterceptionJob* job) {
+  InterceptionJob* FindJobByRequestId(int32_t request_id) {
+    auto it = network_request_id_to_interception_id_.find(request_id);
+    if (it == network_request_id_to_interception_id_.end()) {
+      return nullptr;
+    }
+
+    auto job_it = jobs_.find(it->second);
+    CHECK(job_it != jobs_.end());
+    return job_it->second;
+  }
+
+  void RemoveJob(int32_t request_id, const std::string& id) {
+    network_request_id_to_interception_id_.erase(request_id);
+    jobs_.erase(id);
+  }
+  void AddJob(int32_t request_id, const std::string& id, InterceptionJob* job) {
     jobs_.emplace(id, job);
+    network_request_id_to_interception_id_.emplace(request_id, id);
   }
 
   const RequestInterceptedCallback request_intercepted_callback_;
@@ -254,6 +272,7 @@
   std::vector<Pattern> patterns_;
   bool handle_auth_ = false;
   std::map<std::string, raw_ptr<InterceptionJob, CtnExperimental>> jobs_;
+  std::map<int32_t, std::string> network_request_id_to_interception_id_;
 
   base::WeakPtrFactory<DevToolsURLLoaderInterceptor> weak_factory_;
 };
diff --git a/content/browser/devtools/protocol/emulation_handler.h b/content/browser/devtools/protocol/emulation_handler.h
index 5f6fa0a..f0bbfe5 100644
--- a/content/browser/devtools/protocol/emulation_handler.h
+++ b/content/browser/devtools/protocol/emulation_handler.h
@@ -8,6 +8,7 @@
 #include <memory>
 
 #include "base/containers/flat_map.h"
+#include "base/functional/callback_helpers.h"
 #include "base/memory/raw_ptr.h"
 #include "content/browser/devtools/protocol/devtools_domain_handler.h"
 #include "content/browser/devtools/protocol/emulation.h"
diff --git a/content/browser/devtools/protocol/fetch_handler.cc b/content/browser/devtools/protocol/fetch_handler.cc
index aa9a7a3..60a4cec 100644
--- a/content/browser/devtools/protocol/fetch_handler.cc
+++ b/content/browser/devtools/protocol/fetch_handler.cc
@@ -98,10 +98,13 @@
     const base::UnguessableToken& frame_token,
     bool is_navigation,
     bool is_download,
-    network::mojom::URLLoaderFactoryOverride* intercepting_factory) {
-  return interceptor_ && interceptor_->CreateProxyForInterception(
-                             process_id, storage_partition, frame_token,
-                             is_navigation, is_download, intercepting_factory);
+    network::mojom::URLLoaderFactoryOverride* intercepting_factory,
+    mojo::PendingRemote<network::mojom::TrustedURLLoaderHeaderClient>*
+        header_client) {
+  return interceptor_ &&
+         interceptor_->CreateProxyForInterception(
+             process_id, storage_partition, frame_token, is_navigation,
+             is_download, intercepting_factory, header_client);
 }
 
 void FetchHandler::Enable(
diff --git a/content/browser/devtools/protocol/fetch_handler.h b/content/browser/devtools/protocol/fetch_handler.h
index f27636a6..964a92e 100644
--- a/content/browser/devtools/protocol/fetch_handler.h
+++ b/content/browser/devtools/protocol/fetch_handler.h
@@ -11,11 +11,13 @@
 #include "base/unguessable_token.h"
 #include "content/browser/devtools/protocol/devtools_domain_handler.h"
 #include "content/browser/devtools/protocol/fetch.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/system/data_pipe.h"
 
 namespace network {
 namespace mojom {
 class URLLoaderFactoryOverride;
+class TrustedURLLoaderHeaderClient;
 }
 }  // namespace network
 
@@ -51,7 +53,9 @@
       const base::UnguessableToken& frame_token,
       bool is_navigation,
       bool is_download,
-      network::mojom::URLLoaderFactoryOverride* intercepting_factory);
+      network::mojom::URLLoaderFactoryOverride* intercepting_factory,
+      mojo::PendingRemote<network::mojom::TrustedURLLoaderHeaderClient>*
+          header_client);
 
  private:
   // DevToolsDomainHandler
diff --git a/content/browser/devtools/protocol/network_handler.cc b/content/browser/devtools/protocol/network_handler.cc
index 174eeaa..ff3e5f4b 100644
--- a/content/browser/devtools/protocol/network_handler.cc
+++ b/content/browser/devtools/protocol/network_handler.cc
@@ -3334,11 +3334,13 @@
     const base::UnguessableToken& frame_token,
     bool is_navigation,
     bool is_download,
-    network::mojom::URLLoaderFactoryOverride* intercepting_factory) {
+    network::mojom::URLLoaderFactoryOverride* intercepting_factory,
+    mojo::PendingRemote<network::mojom::TrustedURLLoaderHeaderClient>*
+        header_client) {
   return url_loader_interceptor_ &&
          url_loader_interceptor_->CreateProxyForInterception(
              process_id, storage_partition, frame_token, is_navigation,
-             is_download, intercepting_factory);
+             is_download, intercepting_factory, header_client);
 }
 
 void NetworkHandler::ApplyOverrides(
diff --git a/content/browser/devtools/protocol/network_handler.h b/content/browser/devtools/protocol/network_handler.h
index 30a980b4..7a4cbcb 100644
--- a/content/browser/devtools/protocol/network_handler.h
+++ b/content/browser/devtools/protocol/network_handler.h
@@ -48,6 +48,7 @@
 struct URLLoaderCompletionStatus;
 namespace mojom {
 class URLLoaderFactoryOverride;
+class TrustedURLLoaderHeaderClient;
 }
 }  // namespace network
 
@@ -227,7 +228,9 @@
       const base::UnguessableToken& frame_token,
       bool is_navigation,
       bool is_download,
-      network::mojom::URLLoaderFactoryOverride* intercepting_factory);
+      network::mojom::URLLoaderFactoryOverride* intercepting_factory,
+      mojo::PendingRemote<network::mojom::TrustedURLLoaderHeaderClient>*
+          header_client);
 
   void ApplyOverrides(
       net::HttpRequestHeaders* headers,
diff --git a/content/browser/digital_credentials/cross_device_transaction_impl.cc b/content/browser/digital_credentials/cross_device_transaction_impl.cc
index 6298771c..17ddcbf1 100644
--- a/content/browser/digital_credentials/cross_device_transaction_impl.cc
+++ b/content/browser/digital_credentials/cross_device_transaction_impl.cc
@@ -8,6 +8,7 @@
 #include <variant>
 
 #include "base/functional/bind.h"
+#include "base/functional/callback_helpers.h"
 #include "base/task/single_thread_task_runner.h"
 #include "build/build_config.h"
 #include "components/device_event_log/device_event_log.h"
diff --git a/content/browser/direct_sockets/firewall_hole_delegate.cc b/content/browser/direct_sockets/firewall_hole_delegate.cc
index 49ee8434..76909f9 100644
--- a/content/browser/direct_sockets/firewall_hole_delegate.cc
+++ b/content/browser/direct_sockets/firewall_hole_delegate.cc
@@ -10,6 +10,7 @@
 #include "base/containers/map_util.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback.h"
+#include "base/functional/callback_helpers.h"
 #include "base/no_destructor.h"
 #include "chromeos/components/firewall_hole/firewall_hole.h"
 #include "net/base/ip_address.h"
diff --git a/content/browser/download/download_manager_impl.cc b/content/browser/download/download_manager_impl.cc
index cf5f7bc66..6b1884a 100644
--- a/content/browser/download/download_manager_impl.cc
+++ b/content/browser/download/download_manager_impl.cc
@@ -234,7 +234,7 @@
         static_cast<RenderFrameHostImpl*>(rfh))
         .Run(/*is_navigation=*/true,
              /*is_download=*/true, factory_builder,
-             nullptr /* factory_override */);
+             nullptr /* factory_override */, /*header_client=*/nullptr);
 
     // Also allow the Content embedder to inject itself if it wants to.
     GetContentClient()->browser()->WillCreateURLLoaderFactory(
diff --git a/content/browser/file_system_access/file_path_watcher/file_path_watcher_fsevents.cc b/content/browser/file_system_access/file_path_watcher/file_path_watcher_fsevents.cc
index 62583d0..1a5a4b3d 100644
--- a/content/browser/file_system_access/file_path_watcher/file_path_watcher_fsevents.cc
+++ b/content/browser/file_system_access/file_path_watcher/file_path_watcher_fsevents.cc
@@ -17,6 +17,7 @@
 #include "base/containers/span.h"
 #include "base/files/file_util.h"
 #include "base/functional/bind.h"
+#include "base/functional/callback_helpers.h"
 #include "base/lazy_instance.h"
 #include "base/strings/stringprintf.h"
 #include "base/strings/sys_string_conversions.h"
diff --git a/content/browser/geolocation/geolocation_service_impl.cc b/content/browser/geolocation/geolocation_service_impl.cc
index ce7daa28d..0de8d6c 100644
--- a/content/browser/geolocation/geolocation_service_impl.cc
+++ b/content/browser/geolocation/geolocation_service_impl.cc
@@ -99,11 +99,8 @@
 }
 
 GeolocationServiceImpl::GeolocationServiceImpl(
-    device::mojom::GeolocationContext* geolocation_context,
     RenderFrameHost* render_frame_host)
-    : geolocation_context_(geolocation_context),
-      render_frame_host_(render_frame_host) {
-  DCHECK(geolocation_context);
+    : render_frame_host_(render_frame_host) {
   DCHECK(render_frame_host);
 }
 
@@ -159,7 +156,11 @@
     PermissionResult permission_result) {
   GeolocationPermissionLevel permission_level =
       GetPermissionLevel(permission_result);
-  if (permission_level == GeolocationPermissionLevel::kDenied) {
+
+  device::mojom::GeolocationContext* geolocation_context =
+      GetGeolocationContext();
+  if (permission_level == GeolocationPermissionLevel::kDenied ||
+      !geolocation_context) {
     std::move(callback).Run(blink::mojom::PermissionStatus::DENIED);
     return;
   }
@@ -174,7 +175,7 @@
 
   bool has_precise_permission =
       permission_level == GeolocationPermissionLevel::kPrecise;
-  geolocation_context_->BindGeolocation(
+  geolocation_context->BindGeolocation(
       std::move(receiver), requesting_url,
       device::mojom::GeolocationClientId::kGeolocationServiceImpl,
       has_precise_permission);
@@ -195,6 +196,12 @@
 
 void GeolocationServiceImpl::HandlePermissionResultChange(
     PermissionResult permission_result) {
+  device::mojom::GeolocationContext* geolocation_context =
+      GetGeolocationContext();
+  if (!geolocation_context) {
+    return;
+  }
+
   GeolocationPermissionLevel permission_level =
       GetPermissionLevel(permission_result);
   if (permission_level == GeolocationPermissionLevel::kDenied &&
@@ -204,8 +211,8 @@
         ->UnsubscribeFromPermissionResultChange(subscription_id_);
     DecrementActivityCount();
   }
-  geolocation_context_->OnPermissionUpdated(requesting_origin_,
-                                            permission_level);
+  geolocation_context->OnPermissionUpdated(requesting_origin_,
+                                           permission_level);
 }
 
 void GeolocationServiceImpl::OnDisconnected() {
@@ -230,4 +237,20 @@
   }
 }
 
+// Fetches the GeolocationContext from the WebContents. This is done on-demand
+// to avoid a potential Use-After-Free of the GeolocationContext, which has a
+// shorter lifetime than the RenderFrameHost. Refer to crbug.com/396303129 for
+// more information.
+device::mojom::GeolocationContext*
+GeolocationServiceImpl::GetGeolocationContext() {
+  content::WebContents* web_contents =
+      content::WebContents::FromRenderFrameHost(render_frame_host_);
+
+  if (!web_contents) {
+    return nullptr;
+  }
+
+  return static_cast<WebContentsImpl*>(web_contents)->GetGeolocationContext();
+}
+
 }  // namespace content
diff --git a/content/browser/geolocation/geolocation_service_impl.h b/content/browser/geolocation/geolocation_service_impl.h
index 5481647..a7a841a 100644
--- a/content/browser/geolocation/geolocation_service_impl.h
+++ b/content/browser/geolocation/geolocation_service_impl.h
@@ -52,8 +52,7 @@
 class CONTENT_EXPORT GeolocationServiceImpl
     : public blink::mojom::GeolocationService {
  public:
-  GeolocationServiceImpl(device::mojom::GeolocationContext* geolocation_context,
-                         RenderFrameHost* render_frame_host);
+  explicit GeolocationServiceImpl(RenderFrameHost* render_frame_host);
 
   GeolocationServiceImpl(const GeolocationServiceImpl&) = delete;
   GeolocationServiceImpl& operator=(const GeolocationServiceImpl&) = delete;
@@ -85,8 +84,7 @@
   void IncrementActivityCount();
   void DecrementActivityCount();
 
-  const raw_ptr<device::mojom::GeolocationContext, DanglingUntriaged>
-      geolocation_context_;
+  device::mojom::GeolocationContext* GetGeolocationContext();
 
   // Used to subscribe to permission status changes.
   PermissionController::SubscriptionId subscription_id_;
diff --git a/content/browser/geolocation/geolocation_service_impl_unittest.cc b/content/browser/geolocation/geolocation_service_impl_unittest.cc
index 54f9e6c..63ef2ed 100644
--- a/content/browser/geolocation/geolocation_service_impl_unittest.cc
+++ b/content/browser/geolocation/geolocation_service_impl_unittest.cc
@@ -129,8 +129,7 @@
         kEmbeddedUrl, embedded_rfh);
     navigation_simulator->Commit();
     embedded_rfh = navigation_simulator->GetFinalRenderFrameHost();
-    service_ =
-        std::make_unique<GeolocationServiceImpl>(context_.get(), embedded_rfh);
+    service_ = std::make_unique<GeolocationServiceImpl>(embedded_rfh);
     service_->Bind(service_remote_.BindNewPipeAndPassReceiver());
   }
 
diff --git a/content/browser/gpu/gpu_data_manager_impl_private.cc b/content/browser/gpu/gpu_data_manager_impl_private.cc
index f42e684..cab2477a 100644
--- a/content/browser/gpu/gpu_data_manager_impl_private.cc
+++ b/content/browser/gpu/gpu_data_manager_impl_private.cc
@@ -980,6 +980,7 @@
 #endif
   gpu_info_ = gpu_info;
   RecordDiscreteGpuHistograms(gpu_info_);
+  RecordNpuHistograms(gpu_info_);
 #if BUILDFLAG(ENABLE_VULKAN)
   // Remember the initial hardware_supports_vulkan value so it doesn't change
   // if GPU process restarts as Vulkan might get disabled by GPU mode fallback.
diff --git a/content/browser/gpu/gpu_ipc_browsertests.cc b/content/browser/gpu/gpu_ipc_browsertests.cc
index adb6139..5ec1f437 100644
--- a/content/browser/gpu/gpu_ipc_browsertests.cc
+++ b/content/browser/gpu/gpu_ipc_browsertests.cc
@@ -238,7 +238,7 @@
   ASSERT_EQ(impl->Initialize(content::kGpuStreamPriorityDefault,
                              gpu::mojom::ContextCreationAttribs::NewGles(
                                  gpu::mojom::GLESCreationAttribs::New()),
-                             GURL()),
+                             /*enable_gpu_rasterization=*/false, GURL()),
             gpu::ContextResult::kSuccess);
 
   // Creating a transfer buffer works normally.
diff --git a/content/browser/indexed_db/blob_reader.cc b/content/browser/indexed_db/blob_reader.cc
index 3c840f00..7efb7ff 100644
--- a/content/browser/indexed_db/blob_reader.cc
+++ b/content/browser/indexed_db/blob_reader.cc
@@ -11,6 +11,7 @@
 
 #include "base/files/file_util.h"
 #include "base/functional/bind.h"
+#include "base/functional/callback_helpers.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/uuid.h"
 #include "content/browser/indexed_db/file_stream_reader_to_data_pipe.h"
diff --git a/content/browser/indexed_db/instance/backing_store_util.cc b/content/browser/indexed_db/instance/backing_store_util.cc
index 483b223..6ff4d7ab 100644
--- a/content/browser/indexed_db/instance/backing_store_util.cc
+++ b/content/browser/indexed_db/instance/backing_store_util.cc
@@ -5,6 +5,7 @@
 #include "content/browser/indexed_db/instance/backing_store_util.h"
 
 #include "base/containers/to_vector.h"
+#include "base/functional/callback_helpers.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/values.h"
 #include "content/browser/indexed_db/indexed_db_value.h"
diff --git a/content/browser/indexed_db/instance/bucket_context.h b/content/browser/indexed_db/instance/bucket_context.h
index ff017ec2..84c088c 100644
--- a/content/browser/indexed_db/instance/bucket_context.h
+++ b/content/browser/indexed_db/instance/bucket_context.h
@@ -17,6 +17,7 @@
 #include "base/files/file_path.h"
 #include "base/functional/callback.h"
 #include "base/functional/callback_forward.h"
+#include "base/functional/callback_helpers.h"
 #include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
 #include "base/sequence_checker.h"
diff --git a/content/browser/indexed_db/instance/mock_blob_storage_context.h b/content/browser/indexed_db/instance/mock_blob_storage_context.h
index 0efb7ac1..8f5107e 100644
--- a/content/browser/indexed_db/instance/mock_blob_storage_context.h
+++ b/content/browser/indexed_db/instance/mock_blob_storage_context.h
@@ -5,6 +5,7 @@
 #ifndef CONTENT_BROWSER_INDEXED_DB_INSTANCE_MOCK_BLOB_STORAGE_CONTEXT_H_
 #define CONTENT_BROWSER_INDEXED_DB_INSTANCE_MOCK_BLOB_STORAGE_CONTEXT_H_
 
+#include "base/functional/callback_helpers.h"
 #include "components/services/storage/public/mojom/blob_storage_context.mojom.h"
 #include "content/browser/indexed_db/indexed_db_external_object_storage.h"
 #include "mojo/public/cpp/bindings/receiver_set.h"
diff --git a/content/browser/indexed_db/instance/sqlite/active_blob_streamer.cc b/content/browser/indexed_db/instance/sqlite/active_blob_streamer.cc
index ae94d44..d347564 100644
--- a/content/browser/indexed_db/instance/sqlite/active_blob_streamer.cc
+++ b/content/browser/indexed_db/instance/sqlite/active_blob_streamer.cc
@@ -11,6 +11,7 @@
 
 #include "base/check.h"
 #include "base/functional/bind.h"
+#include "base/functional/callback_helpers.h"
 #include "base/numerics/clamped_math.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/uuid.h"
diff --git a/content/browser/indexed_db/instance/sqlite/database_connection_unittest.cc b/content/browser/indexed_db/instance/sqlite/database_connection_unittest.cc
index 5abdf7a0..09b4a7e 100644
--- a/content/browser/indexed_db/instance/sqlite/database_connection_unittest.cc
+++ b/content/browser/indexed_db/instance/sqlite/database_connection_unittest.cc
@@ -11,6 +11,7 @@
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/files/scoped_temp_dir.h"
+#include "base/functional/callback_helpers.h"
 #include "base/test/bind.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/task_environment.h"
diff --git a/content/browser/loader/navigation_url_loader_impl.cc b/content/browser/loader/navigation_url_loader_impl.cc
index cdf8e7f..86f00a18 100644
--- a/content/browser/loader/navigation_url_loader_impl.cc
+++ b/content/browser/loader/navigation_url_loader_impl.cc
@@ -2250,7 +2250,8 @@
           frame_tree_node->current_frame_host());
   devtools_params.Run(/*is_navigation=*/true,
                       /*is_download=*/false, factory_builder,
-                      /*factory_override=*/nullptr);
+                      /*factory_override=*/nullptr, &header_client);
+
   net::CookieSettingOverrides devtools_cookie_overrides;
   devtools_instrumentation::ApplyNetworkCookieControlsOverrides(
       devtools_params.agent_host(), devtools_cookie_overrides);
diff --git a/content/browser/loader/url_loader_factory_utils.cc b/content/browser/loader/url_loader_factory_utils.cc
index 21e2774f..7e9685d2 100644
--- a/content/browser/loader/url_loader_factory_utils.cc
+++ b/content/browser/loader/url_loader_factory_utils.cc
@@ -245,8 +245,11 @@
 
   if (devtools_params) {
     auto [is_navigation, is_download] = GetIsNavigationAndDownload(type);
-    devtools_params->Run(is_navigation, is_download, factory_builder,
-                         factory_override_ptr);
+    devtools_params->Run(
+        is_navigation, is_download, factory_builder, factory_override_ptr,
+        terminal_params.header_client_option() == HeaderClientOption::kAllow
+            ? &header_client
+            : nullptr);
   }
 
   if (auto terminal_url_loader_factory =
diff --git a/content/browser/media/cdm_storage_database_unittest.cc b/content/browser/media/cdm_storage_database_unittest.cc
index 5e858df..7d528be8 100644
--- a/content/browser/media/cdm_storage_database_unittest.cc
+++ b/content/browser/media/cdm_storage_database_unittest.cc
@@ -11,6 +11,7 @@
 
 #include "base/files/file_path.h"
 #include "base/files/scoped_temp_dir.h"
+#include "base/functional/callback_helpers.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "content/public/browser/browsing_data_filter_builder.h"
 #include "media/cdm/cdm_type.h"
diff --git a/content/browser/media/cdm_storage_manager.cc b/content/browser/media/cdm_storage_manager.cc
index 9c9d7d6..5684d11 100644
--- a/content/browser/media/cdm_storage_manager.cc
+++ b/content/browser/media/cdm_storage_manager.cc
@@ -4,6 +4,7 @@
 
 #include "content/browser/media/cdm_storage_manager.h"
 
+#include "base/functional/callback_helpers.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/notreached.h"
 #include "base/task/thread_pool.h"
diff --git a/content/browser/media/forwarding_audio_stream_factory.h b/content/browser/media/forwarding_audio_stream_factory.h
index b129166..0a12aaa7 100644
--- a/content/browser/media/forwarding_audio_stream_factory.h
+++ b/content/browser/media/forwarding_audio_stream_factory.h
@@ -12,6 +12,7 @@
 
 #include "base/containers/flat_set.h"
 #include "base/containers/unique_ptr_adapters.h"
+#include "base/functional/callback_helpers.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
 #include "base/unguessable_token.h"
diff --git a/content/browser/media/midi_host.cc b/content/browser/media/midi_host.cc
index dba475be..431cf34 100644
--- a/content/browser/media/midi_host.cc
+++ b/content/browser/media/midi_host.cc
@@ -44,7 +44,8 @@
 using midi::mojom::PortState;
 using midi::mojom::Result;
 
-MidiHost::MidiHost(int renderer_process_id, midi::MidiService* midi_service)
+MidiHost::MidiHost(ChildProcessId renderer_process_id,
+                   midi::MidiService* midi_service)
     : renderer_process_id_(renderer_process_id),
       has_midi_permission_(false),
       has_midi_sysex_permission_(false),
@@ -64,7 +65,7 @@
 
 // static
 void MidiHost::BindReceiver(
-    int render_process_id,
+    ChildProcessId render_process_id,
     midi::MidiService* midi_service,
     RenderFrameHost*,  // Required for the BinderMapWithContext interface.
     mojo::PendingReceiver<midi::mojom::MidiSessionProvider> receiver) {
diff --git a/content/browser/media/midi_host.h b/content/browser/media/midi_host.h
index 265bfe47..e4320f8 100644
--- a/content/browser/media/midi_host.h
+++ b/content/browser/media/midi_host.h
@@ -19,6 +19,7 @@
 #include "content/common/content_export.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
+#include "content/public/browser/child_process_id.h"
 #include "media/midi/midi_manager.h"
 #include "media/midi/midi_service.mojom.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
@@ -47,7 +48,7 @@
   // Creates an instance of MidiHost and binds |receiver| to the instance using
   // a self owned receiver. Should be called on the IO thread.
   static void BindReceiver(
-      int render_process_id,
+      ChildProcessId render_process_id,
       midi::MidiService* midi_service,
       RenderFrameHost* host,
       mojo::PendingReceiver<midi::mojom::MidiSessionProvider> receiver);
@@ -78,7 +79,7 @@
                 base::TimeTicks timestamp) override;
 
  protected:
-  MidiHost(int renderer_process_id, midi::MidiService* midi_service);
+  MidiHost(ChildProcessId renderer_process_id, midi::MidiService* midi_service);
 
   void SetHasMidiPermissionForTesting(bool value) {
     has_midi_permission_ = value;
@@ -92,7 +93,7 @@
 
   void EndSession();
 
-  const int renderer_process_id_;
+  const ChildProcessId renderer_process_id_;
 
   // Represents if the renderer has a permission to send/receive MIDI messages.
   bool has_midi_permission_;
diff --git a/content/browser/media/midi_host_unittest.cc b/content/browser/media/midi_host_unittest.cc
index 7b87724..4e11f3a 100644
--- a/content/browser/media/midi_host_unittest.cc
+++ b/content/browser/media/midi_host_unittest.cc
@@ -105,7 +105,8 @@
 
 class MidiHostForTesting : public MidiHost {
  public:
-  MidiHostForTesting(int renderer_process_id, midi::MidiService* midi_service)
+  MidiHostForTesting(ChildProcessId renderer_process_id,
+                     midi::MidiService* midi_service)
       : MidiHost(renderer_process_id, midi_service) {
     SetHasMidiPermissionForTesting(true);
   }
@@ -141,8 +142,7 @@
         std::make_unique<FakeMidiManagerFactory>();
     factory_ = factory->GetWeakPtr();
     service_ = std::make_unique<midi::MidiService>(std::move(factory));
-    host_ = std::make_unique<MidiHostForTesting>(rph_->GetDeprecatedID(),
-                                                 service_.get());
+    host_ = std::make_unique<MidiHostForTesting>(rph_->GetID(), service_.get());
     mojo::PendingRemote<midi::mojom::MidiSessionClient> client_remote;
     mojo::MakeSelfOwnedReceiver(std::make_unique<MidiSessionClientForTesting>(),
                                 client_remote.InitWithNewPipeAndPassReceiver());
diff --git a/content/browser/network_service_instance_impl.cc b/content/browser/network_service_instance_impl.cc
index 85f7e98..4a66de81 100644
--- a/content/browser/network_service_instance_impl.cc
+++ b/content/browser/network_service_instance_impl.cc
@@ -429,8 +429,6 @@
   }
 #endif  // BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_LINUX)
 
-  network_service_params->ip_protection_proxy_bypass_policy =
-      GetContentClient()->browser()->GetIpProtectionProxyBypassPolicy();
   return network_service_params;
 }
 
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc
index f843401..e44bcfa 100644
--- a/content/browser/renderer_host/render_frame_host_impl.cc
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
@@ -14632,8 +14632,7 @@
     if (!geolocation_context) {
       return;
     }
-    geolocation_service_ =
-        std::make_unique<GeolocationServiceImpl>(geolocation_context, this);
+    geolocation_service_ = std::make_unique<GeolocationServiceImpl>(this);
   }
   geolocation_service_->Bind(std::move(receiver));
 }
@@ -18396,7 +18395,7 @@
       // happens after commit.
       DCHECK(children_.empty());
     }
-    if (GetOutermostMainFrameOrEmbedder() == this &&
+    if (IsOutermostMainFrame() &&
         (lifecycle_state() == LifecycleStateImpl::kInBackForwardCache ||
          lifecycle_state() == LifecycleStateImpl::kPrerendering)) {
       // We mark all the children, including inner FrameTrees and delegate
diff --git a/content/browser/service_worker/service_worker_context_wrapper.cc b/content/browser/service_worker/service_worker_context_wrapper.cc
index 7f7bc89..2906184 100644
--- a/content/browser/service_worker/service_worker_context_wrapper.cc
+++ b/content/browser/service_worker/service_worker_context_wrapper.cc
@@ -1896,7 +1896,7 @@
           ForServiceWorkerMainScript(this, version_id)) {
     params->Run(
         /*is_navigation=*/true, /*is_download=*/false, factory_builder,
-        /*factory_override=*/nullptr);
+        /*factory_override=*/nullptr, &header_client);
   }
 
   bool use_client_header_factory = header_client.is_valid();
diff --git a/content/browser/tracing/traces_internals/traces_internals_handler_unittest.cc b/content/browser/tracing/traces_internals/traces_internals_handler_unittest.cc
index 3e7e244..2b3bfb1 100644
--- a/content/browser/tracing/traces_internals/traces_internals_handler_unittest.cc
+++ b/content/browser/tracing/traces_internals/traces_internals_handler_unittest.cc
@@ -5,6 +5,7 @@
 #include "content/browser/tracing/traces_internals/traces_internals_handler.h"
 
 #include "base/base_paths.h"
+#include "base/functional/callback_helpers.h"
 #include "base/path_service.h"
 #include "base/strings/string_view_util.h"
 #include "base/test/gmock_callback_support.h"
diff --git a/content/browser/web_contents/file_chooser_impl.h b/content/browser/web_contents/file_chooser_impl.h
index a4c5cc92..e023c9c6 100644
--- a/content/browser/web_contents/file_chooser_impl.h
+++ b/content/browser/web_contents/file_chooser_impl.h
@@ -6,6 +6,7 @@
 #define CONTENT_BROWSER_WEB_CONTENTS_FILE_CHOOSER_IMPL_H_
 
 #include "base/functional/callback.h"
+#include "base/functional/callback_helpers.h"
 #include "base/memory/weak_ptr.h"
 #include "content/common/content_export.h"
 #include "content/public/browser/file_select_listener.h"
diff --git a/content/browser/webid/delegation/email_verifier_network_request_manager_unittest.cc b/content/browser/webid/delegation/email_verifier_network_request_manager_unittest.cc
index cd226df..d5036546 100644
--- a/content/browser/webid/delegation/email_verifier_network_request_manager_unittest.cc
+++ b/content/browser/webid/delegation/email_verifier_network_request_manager_unittest.cc
@@ -4,6 +4,7 @@
 
 #include "content/browser/webid/delegation/email_verifier_network_request_manager.h"
 
+#include "base/functional/callback_helpers.h"
 #include "base/test/bind.h"
 #include "base/test/task_environment.h"
 #include "content/public/test/browser_task_environment.h"
diff --git a/content/browser/webid/request_service.cc b/content/browser/webid/request_service.cc
index 6671138f..c1fd494 100644
--- a/content/browser/webid/request_service.cc
+++ b/content/browser/webid/request_service.cc
@@ -578,7 +578,7 @@
   fedcm_metrics_->RecordIdentityProvidersCount(idp_order_.size());
 
   CHECK(!unique_idps.empty());
-  if (rp_mode_ == RpMode::kPassive) {
+  if (rp_mode_ == RpMode::kPassive && idp_order_.size() == 1u) {
     request_dialog_controller_->ShouldShowAccountsPassiveDialog(
         base::BindOnce(&RequestService::OnShouldShowAccountsPassiveDialogResult,
                        weak_ptr_factory_.GetWeakPtr(), std::move(unique_idps)));
diff --git a/content/browser/webid/request_service_unittest.cc b/content/browser/webid/request_service_unittest.cc
index cd43190..b2388b69d 100644
--- a/content/browser/webid/request_service_unittest.cc
+++ b/content/browser/webid/request_service_unittest.cc
@@ -8465,4 +8465,27 @@
       LifecycleStateFailureReason::kInBackForwardCache, 1);
 }
 
+// Test that if there are multiple IdPs, the UI should not be suppressed even if
+// configuration.suppressed_by_segmentation_platform is set to true.
+TEST_F(RequestServiceTest, SuppressedBySegmentationPlatformButMultipleIdps) {
+  // Use IdpNetworkRequestManagerParamChecker to validate passed-in parameters
+  // to IdpNetworkRequestManager methods.
+  std::unique_ptr<IdpNetworkRequestManagerParamChecker> checker =
+      std::make_unique<IdpNetworkRequestManagerParamChecker>();
+  SetNetworkRequestManager(std::move(checker));
+
+  RequestExpectations expectations = kExpectationSuccess;
+  // Since the first IDP does not set the login state of the account but the
+  // second IDP has one with state set to SignIn, selecting the first account
+  // means that the second IDP is the one that is selected.
+  expectations.selected_idp_config_url = kProviderTwoUrlFull;
+  MockConfiguration config = kConfigurationMultiIdpValid;
+  config.suppressed_by_segmentation_platform = true;
+  RunAuthTest(kDefaultMultiIdpRequestParameters, expectations, config);
+
+  EXPECT_TRUE(DidFetch(FetchedEndpoint::ACCOUNTS));
+  histogram_tester_.ExpectUniqueSample("Blink.FedCm.DidShowUI", true, 1);
+  ExpectUkmValueInEntry("DidShowUI", FedCmEntry::kEntryName, true);
+}
+
 }  // namespace content::webid
diff --git a/content/public/browser/content_browser_client.cc b/content/public/browser/content_browser_client.cc
index 105acbc..a8ea6bdd 100644
--- a/content/public/browser/content_browser_client.cc
+++ b/content/public/browser/content_browser_client.cc
@@ -1863,11 +1863,6 @@
     BrowserContext* browser_context,
     blink::WebMediaDeviceInfoArray& infos) {}
 
-network::mojom::IpProtectionProxyBypassPolicy
-ContentBrowserClient::GetIpProtectionProxyBypassPolicy() {
-  return network::mojom::IpProtectionProxyBypassPolicy::kNone;
-}
-
 void ContentBrowserClient::MaybePrewarmHttpDiskCache(
     BrowserContext& browser_context,
     const std::optional<url::Origin>& initiator_origin,
diff --git a/content/public/browser/content_browser_client.h b/content/public/browser/content_browser_client.h
index 8011586..298cf36 100644
--- a/content/public/browser/content_browser_client.h
+++ b/content/public/browser/content_browser_client.h
@@ -3156,15 +3156,6 @@
       BrowserContext* browser_context,
       blink::WebMediaDeviceInfoArray& infos);
 
-  // Allows the embedder to override the proxy bypass policy used for IP
-  // Protection.
-  // Even if a domain is part of the masked domain list and is
-  // eligible for IP Protection, the embedder can use a certain policy to bypass
-  // certain network requests from IP Protection.
-  // By default, there is no bypass policy used.
-  virtual network::mojom::IpProtectionProxyBypassPolicy
-  GetIpProtectionProxyBypassPolicy();
-
   // Prewarms the HTTP disk cache entries for the given URL and the
   // subresources if possible.
   // `initiator_origin` is the origin that triggers the prewarm request,
diff --git a/content/public/common/content_features.cc b/content/public/common/content_features.cc
index e269045..d174ee7c 100644
--- a/content/public/common/content_features.cc
+++ b/content/public/common/content_features.cc
@@ -734,7 +734,7 @@
 // the embedder to provide conditions that may delay the final process selection
 // until the conditions have their results.
 BASE_FEATURE(kProcessSelectionDeferringConditions,
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 
 // Enables origin-keyed processes by default, unless origins opt out using
 // Origin-Agent-Cluster: ?0. This feature only takes effect if the Blink feature
diff --git a/content/public/test/content_browser_test.cc b/content/public/test/content_browser_test.cc
index 69060cb8..920b682f 100644
--- a/content/public/test/content_browser_test.cc
+++ b/content/public/test/content_browser_test.cc
@@ -30,7 +30,7 @@
 
 #if BUILDFLAG(IS_MAC)
 #include "base/apple/foundation_util.h"
-#include "content/shell/app/paths_mac.h"
+#include "content/shell/app/paths_apple.h"
 #endif
 
 #if BUILDFLAG(IS_LINUX)
diff --git a/content/public/test/fake_render_widget_host.h b/content/public/test/fake_render_widget_host.h
index e74b3f3..85cd97b 100644
--- a/content/public/test/fake_render_widget_host.h
+++ b/content/public/test/fake_render_widget_host.h
@@ -9,6 +9,7 @@
 
 #include "mojo/public/cpp/bindings/associated_receiver.h"
 #include "mojo/public/cpp/bindings/associated_remote.h"
+#include "mojo/public/cpp/bindings/receiver.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "third_party/blink/public/mojom/frame/intrinsic_sizing_info.mojom-forward.h"
 #include "third_party/blink/public/mojom/input/input_handler.mojom.h"
diff --git a/content/public/test/mock_captured_surface_controller.cc b/content/public/test/mock_captured_surface_controller.cc
index c6c17b9..d76baaff 100644
--- a/content/public/test/mock_captured_surface_controller.cc
+++ b/content/public/test/mock_captured_surface_controller.cc
@@ -4,6 +4,7 @@
 
 #include "content/public/test/mock_captured_surface_controller.h"
 
+#include "base/functional/callback_helpers.h"
 #include "content/browser/media/captured_surface_control_permission_manager.h"
 #include "third_party/blink/public/mojom/mediastream/media_stream.mojom.h"
 
diff --git a/content/renderer/media/audio_decoder.cc b/content/renderer/media/audio_decoder.cc
index b7981cf4..0394f25 100644
--- a/content/renderer/media/audio_decoder.cc
+++ b/content/renderer/media/audio_decoder.cc
@@ -13,10 +13,12 @@
 #include "base/containers/span_writer.h"
 #include "base/logging.h"
 #include "base/memory/ptr_util.h"
+#include "base/metrics/histogram_functions.h"
 #include "base/strings/string_util.h"
 #include "base/strings/to_string.h"
 #include "base/time/time.h"
 #include "media/base/audio_bus.h"
+#include "media/base/audio_timestamp_helper.h"
 #include "media/base/limits.h"
 #include "media/base/media_switches.h"
 #include "media/filters/audio_file_reader.h"
@@ -120,26 +122,29 @@
 // Decode in-memory audio file data.
 bool DecodeAudioFileData(blink::WebAudioBus* destination_bus,
                          base::span<const char> data) {
+  const base::TimeTicks start_time = base::TimeTicks::Now();
   DCHECK(destination_bus);
   if (!destination_bus) {
     return false;
   }
 
 #if BUILDFLAG(ENABLE_FFMPEG)
-  auto reader = Reader::Create(data);
+  std::unique_ptr<Reader> reader = Reader::Create(data);
+  base::UmaHistogramBoolean("Media.ContentAudioDecoder.CreateReaderSuccess",
+                            reader != nullptr);
   if (!reader) {
     return false;
   }
 
   const size_t number_of_channels = reader->channels();
-  const double file_sample_rate = reader->sample_rate();
+  const double sample_rate = reader->sample_rate();
 
   // Apply sanity checks to make sure crazy values aren't coming out of
   // FFmpeg.
   if (!number_of_channels ||
       number_of_channels > static_cast<size_t>(media::limits::kMaxChannels) ||
-      file_sample_rate < media::limits::kMinSampleRate ||
-      file_sample_rate > media::limits::kMaxSampleRate) {
+      sample_rate < media::limits::kMinSampleRate ||
+      sample_rate > media::limits::kMaxSampleRate) {
     return false;
   }
 
@@ -152,7 +157,7 @@
   // Allocate and configure the output audio channel data and then
   // copy the decoded data to the destination.
   destination_bus->Initialize(number_of_channels, number_of_frames,
-                              file_sample_rate);
+                              sample_rate);
 
   std::vector<base::SpanWriter<float>> dest_channels;
   dest_channels.reserve(number_of_channels);
@@ -168,15 +173,24 @@
     }
   }
 
-  DVLOG(1) << "Decoded file data (unknown duration)-"
+  const auto duration =
+      media::AudioTimestampHelper::FramesToTime(number_of_frames, sample_rate);
+  DVLOG(1) << "Successfully decoded an audio file."
            << " data: " << base::ToString(data) << " data size: " << data.size()
-           << ", decoded duration: " << (number_of_frames / file_sample_rate)
+           << ", decoded duration: " << duration
            << ", number of frames: " << number_of_frames
            << ", estimated frames (if available): "
-           << reader->estimated_frames()
-           << ", sample rate: " << file_sample_rate
+           << reader->estimated_frames() << ", sample rate: " << sample_rate
            << ", number of channels: " << number_of_channels;
 
+  // NOTE: using the "medium timings" function to get better visibility into
+  // behavior in the [0, 3] minute range (although the distribution tail is
+  // likely to be cut off in this histogram scheme).
+  base::UmaHistogramMediumTimes("Media.ContentAudioDecoder.Duration", duration);
+  base::UmaHistogramTimes(
+      "Media.ContentAudioDecoder.DecodeTimePerFrame",
+      (base::TimeTicks::Now() - start_time) / number_of_frames);
+
   return number_of_frames > 0;
 #else
   return false;
diff --git a/content/renderer/media/win/dcomp_texture_wrapper_impl.cc b/content/renderer/media/win/dcomp_texture_wrapper_impl.cc
index 2fee4e0..6e92e32c 100644
--- a/content/renderer/media/win/dcomp_texture_wrapper_impl.cc
+++ b/content/renderer/media/win/dcomp_texture_wrapper_impl.cc
@@ -5,6 +5,7 @@
 #include "content/renderer/media/win/dcomp_texture_wrapper_impl.h"
 
 #include "base/functional/callback.h"
+#include "base/functional/callback_helpers.h"
 #include "base/memory/ptr_util.h"
 #include "base/task/bind_post_task.h"
 #include "base/task/sequenced_task_runner.h"
diff --git a/content/shell/BUILD.gn b/content/shell/BUILD.gn
index 3e599f6..5dd8e75 100644
--- a/content/shell/BUILD.gn
+++ b/content/shell/BUILD.gn
@@ -113,8 +113,8 @@
   }
   if (is_apple) {
     sources += [
-      "app/paths_mac.h",
-      "app/paths_mac.mm",
+      "app/paths_apple.h",
+      "app/paths_apple.mm",
     ]
   }
   if (is_mac) {
diff --git a/content/shell/app/paths_mac.h b/content/shell/app/paths_apple.h
similarity index 86%
rename from content/shell/app/paths_mac.h
rename to content/shell/app/paths_apple.h
index 4813aeb..cd88583f 100644
--- a/content/shell/app/paths_mac.h
+++ b/content/shell/app/paths_apple.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CONTENT_SHELL_APP_PATHS_MAC_H_
-#define CONTENT_SHELL_APP_PATHS_MAC_H_
+#ifndef CONTENT_SHELL_APP_PATHS_APPLE_H_
+#define CONTENT_SHELL_APP_PATHS_APPLE_H_
 
 namespace base {
 class FilePath;
@@ -31,4 +31,4 @@
 // Sets up base::apple::BaseBundleID.
 void OverrideBundleID();
 
-#endif  // CONTENT_SHELL_APP_PATHS_MAC_H_
+#endif  // CONTENT_SHELL_APP_PATHS_APPLE_H_
diff --git a/content/shell/app/paths_mac.mm b/content/shell/app/paths_apple.mm
similarity index 98%
rename from content/shell/app/paths_mac.mm
rename to content/shell/app/paths_apple.mm
index 97428aa..98c49f8 100644
--- a/content/shell/app/paths_mac.mm
+++ b/content/shell/app/paths_apple.mm
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "content/shell/app/paths_mac.h"
+#include "content/shell/app/paths_apple.h"
 
 #include "base/apple/bundle_locations.h"
 #include "base/apple/foundation_util.h"
diff --git a/content/shell/app/shell_content_main.cc b/content/shell/app/shell_content_main.cc
index 41226754..527d6bd 100644
--- a/content/shell/app/shell_content_main.cc
+++ b/content/shell/app/shell_content_main.cc
@@ -8,7 +8,7 @@
 #include "build/build_config.h"
 #include "content/public/app/content_main.h"
 #include "content/public/common/content_switches.h"
-#include "content/shell/app/paths_mac.h"
+#include "content/shell/app/paths_apple.h"
 #include "content/shell/app/shell_main_delegate.h"
 
 #if BUILDFLAG(IS_MAC)
diff --git a/content/shell/app/shell_main_delegate.cc b/content/shell/app/shell_main_delegate.cc
index 3cd7013..7d1fad8 100644
--- a/content/shell/app/shell_main_delegate.cc
+++ b/content/shell/app/shell_main_delegate.cc
@@ -63,7 +63,7 @@
 #endif
 
 #if BUILDFLAG(IS_APPLE)
-#include "content/shell/app/paths_mac.h"
+#include "content/shell/app/paths_apple.h"
 #endif
 
 #if BUILDFLAG(IS_MAC)
diff --git a/content/shell/app/shell_main_delegate_mac.mm b/content/shell/app/shell_main_delegate_mac.mm
index cfc10257..0822b7f 100644
--- a/content/shell/app/shell_main_delegate_mac.mm
+++ b/content/shell/app/shell_main_delegate_mac.mm
@@ -14,7 +14,7 @@
 #include "base/files/file_path.h"
 #include "base/strings/sys_string_conversions.h"
 #include "content/public/common/content_switches.h"
-#include "content/shell/app/paths_mac.h"
+#include "content/shell/app/paths_apple.h"
 #include "content/shell/browser/shell_application_mac.h"
 #include "content/shell/common/shell_switches.h"
 
diff --git a/content/test/data/accessibility/css/anchor-positioning-changed-expected-blink.txt b/content/test/data/accessibility/css/anchor-positioning-changed-expected-blink.txt
index 819b87e1..b7907df 100644
--- a/content/test/data/accessibility/css/anchor-positioning-changed-expected-blink.txt
+++ b/content/test/data/accessibility/css/anchor-positioning-changed-expected-blink.txt
@@ -7,7 +7,7 @@
 ++++++tooltip name='target2'
 ++++++++staticText name='target2'
 ++++++++++inlineTextBox name='target2'
-++++++button name='anchor1' detailsFrom=cssAnchor detailsIds=paragraph
+++++++button name='anchor1'
 ++++++++staticText name='anchor1'
 ++++++++++inlineTextBox name='anchor1'
 ++++++button name='anchor2'
@@ -42,7 +42,7 @@
 ++++++button name='anchor1' controlsIds=paragraph
 ++++++++staticText name='anchor1'
 ++++++++++inlineTextBox name='anchor1'
-++++++button name='anchor2' detailsFrom=cssAnchor detailsIds=checkBox
+++++++button name='anchor2'
 ++++++++staticText name='anchor2'
 ++++++++++inlineTextBox name='anchor2'
 === Start Continuation ===
@@ -61,7 +61,7 @@
 ++++++button name='anchor2'
 ++++++++staticText name='anchor2'
 ++++++++++inlineTextBox name='anchor2'
-++++++heading name='my-heading' detailsFrom=cssAnchor detailsIds=checkBox
+++++++heading name='my-heading'
 ++++++++staticText name='my-heading'
 ++++++++++inlineTextBox name='my-heading'
 === Start Continuation ===
@@ -80,7 +80,7 @@
 ++++++button name='anchor2'
 ++++++++staticText name='anchor2'
 ++++++++++inlineTextBox name='anchor2'
-++++++heading name='my-heading' detailsFrom=cssAnchor detailsIds=checkBox
+++++++heading name='my-heading'
 ++++++++staticText name='my-heading'
 ++++++++++inlineTextBox name='my-heading'
 === Start Continuation ===
@@ -99,6 +99,6 @@
 ++++++button name='anchor2'
 ++++++++staticText name='anchor2'
 ++++++++++inlineTextBox name='anchor2'
-++++++heading name='my-heading' detailsFrom=cssAnchor detailsIds=checkBox
+++++++heading name='my-heading'
 ++++++++staticText name='my-heading'
 ++++++++++inlineTextBox name='my-heading'
diff --git a/content/test/data/accessibility/css/anchor-positioning-changed-simple-expected-blink.txt b/content/test/data/accessibility/css/anchor-positioning-changed-simple-expected-blink.txt
index 3b17926..2f06d71f 100644
--- a/content/test/data/accessibility/css/anchor-positioning-changed-simple-expected-blink.txt
+++ b/content/test/data/accessibility/css/anchor-positioning-changed-simple-expected-blink.txt
@@ -4,7 +4,7 @@
 ++++++button name='anchor1'
 ++++++++staticText name='anchor1'
 ++++++++++inlineTextBox name='anchor1'
-++++++button name='anchor2' detailsFrom=cssAnchor detailsIds=paragraph
+++++++button name='anchor2'
 ++++++++staticText name='anchor2'
 ++++++++++inlineTextBox name='anchor2'
 ++++++genericContainer
diff --git a/content/test/data/accessibility/css/anchor-positioning-expected-auralinux.txt b/content/test/data/accessibility/css/anchor-positioning-expected-auralinux.txt
index 835ad40..e3527aa6 100644
--- a/content/test/data/accessibility/css/anchor-positioning-expected-auralinux.txt
+++ b/content/test/data/accessibility/css/anchor-positioning-expected-auralinux.txt
@@ -1,5 +1,5 @@
 [document web]
 ++[section]
-++++[section] details-for=[push button]
+++++[section]
 ++++++[static] name='target'
-++++[push button] name='anchor' details=[section] details-from:css-anchor details-roles:*
+++++[push button] name='anchor'
diff --git a/content/test/data/accessibility/css/anchor-positioning-expected-blink.txt b/content/test/data/accessibility/css/anchor-positioning-expected-blink.txt
index c19bec2..c16fe53 100644
--- a/content/test/data/accessibility/css/anchor-positioning-expected-blink.txt
+++ b/content/test/data/accessibility/css/anchor-positioning-expected-blink.txt
@@ -4,6 +4,6 @@
 ++++++genericContainer
 ++++++++staticText name='target'
 ++++++++++inlineTextBox name='target'
-++++++button name='anchor' detailsFrom=cssAnchor detailsIds=genericContainer
+++++++button name='anchor'
 ++++++++staticText name='anchor'
 ++++++++++inlineTextBox name='anchor'
diff --git a/content/test/data/accessibility/css/anchor-positioning-expected-win.txt b/content/test/data/accessibility/css/anchor-positioning-expected-win.txt
index ba86701..2d18c3a 100644
--- a/content/test/data/accessibility/css/anchor-positioning-expected-win.txt
+++ b/content/test/data/accessibility/css/anchor-positioning-expected-win.txt
@@ -1,5 +1,5 @@
 ROLE_SYSTEM_DOCUMENT READONLY FOCUSABLE
 ++IA2_ROLE_SECTION
-++++IA2_ROLE_SECTION detailsFor='ROLE_SYSTEM_PUSHBUTTON'
+++++IA2_ROLE_SECTION
 ++++++ROLE_SYSTEM_STATICTEXT name='target'
-++++ROLE_SYSTEM_PUSHBUTTON name='anchor' FOCUSABLE details-from:css-anchor details-roles:* details='IA2_ROLE_SECTION'
+++++ROLE_SYSTEM_PUSHBUTTON name='anchor' FOCUSABLE
diff --git a/content/test/data/accessibility/css/anchor-positioning-multiple-anchors-expected-blink.txt b/content/test/data/accessibility/css/anchor-positioning-multiple-anchors-expected-blink.txt
index 3a96ca21..f7b77c2 100644
--- a/content/test/data/accessibility/css/anchor-positioning-multiple-anchors-expected-blink.txt
+++ b/content/test/data/accessibility/css/anchor-positioning-multiple-anchors-expected-blink.txt
@@ -4,7 +4,7 @@
 ++++++paragraph
 ++++++++staticText name='target1'
 ++++++++++inlineTextBox name='target1'
-++++++button name='anchor1' detailsFrom=cssAnchor detailsIds=paragraph
+++++++button name='anchor1'
 ++++++++staticText name='anchor1'
 ++++++++++inlineTextBox name='anchor1'
 ++++++heading name='anchor2'
@@ -30,6 +30,6 @@
 ++++++paragraph
 ++++++++staticText name='target1'
 ++++++++++inlineTextBox name='target1'
-++++++heading name='anchor2' detailsFrom=cssAnchor detailsIds=paragraph
+++++++heading name='anchor2'
 ++++++++staticText name='anchor2'
 ++++++++++inlineTextBox name='anchor2'
diff --git a/content/test/data/accessibility/css/anchor-positioning-position-fallback-expected-blink.txt b/content/test/data/accessibility/css/anchor-positioning-position-fallback-expected-blink.txt
index 36b836c5c..f91af0f 100644
--- a/content/test/data/accessibility/css/anchor-positioning-position-fallback-expected-blink.txt
+++ b/content/test/data/accessibility/css/anchor-positioning-position-fallback-expected-blink.txt
@@ -4,7 +4,7 @@
 ++++++genericContainer
 ++++++++staticText name='target'
 ++++++++++inlineTextBox name='target'
-++++++button name='anchor1' detailsFrom=cssAnchor detailsIds=genericContainer
+++++++button name='anchor1'
 ++++++++staticText name='anchor1'
 ++++++++++inlineTextBox name='anchor1'
 ++++++button name='anchor2'
@@ -20,6 +20,6 @@
 ++++++button name='anchor1'
 ++++++++staticText name='anchor1'
 ++++++++++inlineTextBox name='anchor1'
-++++++button name='anchor2' detailsFrom=cssAnchor detailsIds=genericContainer
+++++++button name='anchor2'
 ++++++++staticText name='anchor2'
 ++++++++++inlineTextBox name='anchor2'
\ No newline at end of file
diff --git a/content/test/data/accessibility/css/carousel-positioned-buttons-expected-blink.txt b/content/test/data/accessibility/css/carousel-positioned-buttons-expected-blink.txt
index 3fcd949..4d104a0 100644
--- a/content/test/data/accessibility/css/carousel-positioned-buttons-expected-blink.txt
+++ b/content/test/data/accessibility/css/carousel-positioned-buttons-expected-blink.txt
@@ -3,7 +3,7 @@
 ++++genericContainer ignored
 ++++++button name='Scroll left' restriction=disabled controlsIds=genericContainer
 ++++++button focusable name='Scroll right' controlsIds=genericContainer
-++++++genericContainer focusable detailsIds=button
+++++++genericContainer focusable
 ++++++++genericContainer
 ++++++++++staticText name='Item'
 ++++++++++++inlineTextBox name='Item'
diff --git a/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt
index 34df13b..634d6d9 100644
--- a/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt
@@ -582,6 +582,7 @@
 crbug.com/1288590 [ android android-pixel-6 ] conformance/misc/shader-precision-format.html [ Failure ]
 crbug.com/1288603 [ android android-pixel-6 passthrough angle-opengles ] conformance/extensions/angle-instanced-arrays-out-of-bounds.html [ Failure ]
 crbug.com/40236526 [ android android-pixel-6 no-passthrough ] conformance/rendering/blending.html [ Failure ]
+crbug.com/465162387 [ android android-pixel-6 ] conformance/extensions/oes-element-index-uint.html [ Failure ]
 
 ## Pixel 10 ##
 
diff --git a/content/test/mock_widget.h b/content/test/mock_widget.h
index ec1d772a..a60f8070 100644
--- a/content/test/mock_widget.h
+++ b/content/test/mock_widget.h
@@ -11,6 +11,7 @@
 #include <utility>
 
 #include "mojo/public/cpp/bindings/associated_receiver.h"
+#include "mojo/public/cpp/bindings/receiver.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "third_party/blink/public/common/widget/visual_properties.h"
 #include "third_party/blink/public/mojom/input/input_handler.mojom.h"
diff --git a/content/web_test/renderer/gamepad_controller.cc b/content/web_test/renderer/gamepad_controller.cc
index e482a6c..0b58c9a8 100644
--- a/content/web_test/renderer/gamepad_controller.cc
+++ b/content/web_test/renderer/gamepad_controller.cc
@@ -70,6 +70,7 @@
   void Connect(int index);
   void DispatchConnected(int index);
   void Disconnect(int index);
+  void DispatchRawInputChanged(int index);
   void SetId(int index, const std::u16string& src);
   void SetButtonCount(int index, int buttons);
   void SetButtonData(int index, int button, double data);
@@ -120,6 +121,8 @@
       .SetMethod("dispatchConnected",
                  &GamepadControllerBindings::DispatchConnected)
       .SetMethod("disconnect", &GamepadControllerBindings::Disconnect)
+      .SetMethod("dispatchRawInputChanged",
+                 &GamepadControllerBindings::DispatchRawInputChanged)
       .SetMethod("setId", &GamepadControllerBindings::SetId)
       .SetMethod("setButtonCount", &GamepadControllerBindings::SetButtonCount)
       .SetMethod("setButtonData", &GamepadControllerBindings::SetButtonData)
@@ -148,6 +151,12 @@
     controller_->Disconnect(index);
 }
 
+void GamepadControllerBindings::DispatchRawInputChanged(int index) {
+  if (controller_) {
+    controller_->DispatchRawInputChanged(index);
+  }
+}
+
 void GamepadControllerBindings::SetId(int index, const std::u16string& src) {
   if (controller_)
     controller_->SetId(index, src);
@@ -264,6 +273,14 @@
     observer_remote_->GamepadDisconnected(index, pad);
 }
 
+void GamepadController::MonitorImpl::DispatchRawInputChanged(
+    int index,
+    const device::Gamepad& pad) {
+  if (observer_remote_) {
+    observer_remote_->GamepadRawInputChanged(index, pad);
+  }
+}
+
 void GamepadController::MonitorImpl::Reset() {
   missed_dispatches_.reset();
 }
@@ -380,6 +397,22 @@
   gamepads_->seqlock.WriteEnd();
 }
 
+void GamepadController::DispatchRawInputChanged(int index) {
+  if (index < 0 || index >= static_cast<int>(Gamepads::kItemsLengthCap)) {
+    return;
+  }
+
+  const int64_t now = CurrentTimeInMicroseconds();
+  gamepads_->seqlock.WriteBegin();
+  Gamepad& pad = gamepads_->data.items[index];
+  pad.timestamp = now;
+
+  for (auto& monitor : monitors_) {
+    monitor->DispatchRawInputChanged(index, pad);
+  }
+  gamepads_->seqlock.WriteEnd();
+}
+
 void GamepadController::SetId(int index, const std::u16string& u16str) {
   if (index < 0 || index >= static_cast<int>(Gamepads::kItemsLengthCap))
     return;
diff --git a/content/web_test/renderer/gamepad_controller.h b/content/web_test/renderer/gamepad_controller.h
index e0481ee..b91d44d 100644
--- a/content/web_test/renderer/gamepad_controller.h
+++ b/content/web_test/renderer/gamepad_controller.h
@@ -49,6 +49,7 @@
     void Reset();
     void DispatchConnected(int index, const device::Gamepad& pad);
     void DispatchDisconnected(int index, const device::Gamepad& pad);
+    void DispatchRawInputChanged(int index, const device::Gamepad& pad);
 
     // GamepadMonitor implementation.
     void GamepadStartPolling(GamepadStartPollingCallback callback) override;
@@ -74,6 +75,7 @@
   void Connect(int index);
   void DispatchConnected(int index);
   void Disconnect(int index);
+  void DispatchRawInputChanged(int index);
 
   void SetId(int index, const std::u16string& src);
   void SetButtonCount(int index, int buttons);
diff --git a/device/gamepad/gamepad_monitor.cc b/device/gamepad/gamepad_monitor.cc
index 3134946..4c56043 100644
--- a/device/gamepad/gamepad_monitor.cc
+++ b/device/gamepad/gamepad_monitor.cc
@@ -42,7 +42,9 @@
 
 void GamepadMonitor::OnGamepadRawInputChanged(uint32_t index,
                                               const Gamepad& gamepad) {
-  // TODO(crbug.com/40582297): Add events for gamepad button and axis inputs.
+  if (gamepad_observer_remote_) {
+    gamepad_observer_remote_->GamepadRawInputChanged(index, gamepad);
+  }
 }
 
 void GamepadMonitor::GamepadStartPolling(GamepadStartPollingCallback callback) {
diff --git a/device/gamepad/gamepad_service_unittest.cc b/device/gamepad/gamepad_service_unittest.cc
index 1445df3..2ee26fa3 100644
--- a/device/gamepad/gamepad_service_unittest.cc
+++ b/device/gamepad/gamepad_service_unittest.cc
@@ -14,10 +14,12 @@
 #include "base/memory/raw_ptr.h"
 #include "base/run_loop.h"
 #include "base/test/gmock_callback_support.h"
+#include "base/test/scoped_feature_list.h"
 #include "base/test/task_environment.h"
 #include "base/test/test_future.h"
 #include "device/gamepad/gamepad_consumer.h"
 #include "device/gamepad/gamepad_test_helpers.h"
+#include "device/gamepad/public/cpp/gamepad_features.h"
 #include "device/gamepad/simulated_gamepad_data_fetcher.h"
 #include "device/gamepad/simulated_gamepad_params.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -577,6 +579,40 @@
     poll_loop_.reset();
   }
 
+ protected:
+  // Helper to create a gamepad with specified capabilities and establish user
+  // gesture.
+  base::UnguessableToken SetupRawInputGamepad(MockGamepadConsumer* consumer,
+                                              SimulatedGamepadParams params) {
+    params.name = "Raw input test gamepad";
+    auto token = service()->AddSimulatedGamepad(std::move(params));
+
+    // Establish user gesture with initial button press.
+    TestFuture<uint32_t, const Gamepad&> connected_future;
+    EXPECT_CALL(*consumer, OnGamepadConnected)
+        .WillOnce(InvokeFuture(connected_future));
+    service()->SimulateButtonInput(token, /*index=*/0, /*logical_value=*/1.0,
+                                   /*pressed=*/std::nullopt,
+                                   /*touched=*/std::nullopt);
+    service()->SimulateInputFrame(token);
+
+    EXPECT_EQ(connected_future.Get<0>(), 0u);
+    EXPECT_TRUE(connected_future.Get<1>().connected);
+
+    return token;
+  }
+
+  // Helper to clean up gamepad.
+  void CleanupRawInputGamepad(MockGamepadConsumer* consumer,
+                              base::UnguessableToken token) {
+    TestFuture<uint32_t, const Gamepad&> disconnected_future;
+    EXPECT_CALL(*consumer, OnGamepadDisconnected)
+        .WillOnce(InvokeFuture(disconnected_future));
+    service()->RemoveSimulatedGamepad(token);
+    EXPECT_EQ(disconnected_future.Get<0>(), 0u);
+    EXPECT_FALSE(disconnected_future.Get<1>().connected);
+  }
+
  private:
   std::optional<base::RunLoop> poll_loop_;
 };
@@ -1216,4 +1252,267 @@
   EXPECT_EQ(disconnected_gamepad.touch_events_length, 0u);
 }
 
+TEST_F(GamepadServiceSimulationTest, RawInputChangeDetectionButton) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndEnableFeature(
+      features::kGamepadRawInputChangeEvent);
+
+  auto* consumer = CreateConsumer();
+  EXPECT_TRUE(service()->ConsumerBecameActive(consumer));
+
+  SimulatedGamepadParams params;
+  params.name = "Raw input test gamepad";
+  params.button_bounds = {std::nullopt};
+  auto token = SetupRawInputGamepad(consumer, std::move(params));
+
+  TestFuture<uint32_t, const Gamepad&> future;
+  EXPECT_CALL(*consumer, OnGamepadRawInputChanged)
+      .WillOnce(InvokeFuture(future));
+
+  service()->SimulateButtonInput(token, /*index=*/0, /*logical_value=*/0.5,
+                                 /*pressed=*/std::nullopt,
+                                 /*touched=*/std::nullopt);
+  service()->SimulateInputFrame(token);
+
+  const auto [id, gamepad] = future.Take();
+  EXPECT_EQ(gamepad.buttons_length, 1u);
+  EXPECT_EQ(gamepad.buttons[0].value, 0.5);
+
+  CleanupRawInputGamepad(consumer, token);
+}
+
+TEST_F(GamepadServiceSimulationTest, RawInputChangeDetectionAxis) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndEnableFeature(
+      features::kGamepadRawInputChangeEvent);
+
+  auto* consumer = CreateConsumer();
+  EXPECT_TRUE(service()->ConsumerBecameActive(consumer));
+
+  // Set up a simulated gamepad with axis.
+  SimulatedGamepadParams params;
+  params.name = "Raw input test gamepad";
+  params.button_bounds = {std::nullopt};
+  params.axis_bounds = {GamepadLogicalBounds(-1.0, 1.0)};
+  params.touch_surface_bounds = {std::nullopt};
+  auto token = SetupRawInputGamepad(consumer, std::move(params));
+
+  TestFuture<uint32_t, const Gamepad&> future;
+  EXPECT_CALL(*consumer, OnGamepadRawInputChanged)
+      .WillOnce(InvokeFuture(future));
+
+  service()->SimulateAxisInput(token, /*index=*/0, /*logical_value=*/0.5);
+  service()->SimulateInputFrame(token);
+
+  const auto [id, gamepad] = future.Take();
+  EXPECT_EQ(gamepad.axes_length, 1u);
+  EXPECT_EQ(gamepad.axes[0], 0.5);
+
+  CleanupRawInputGamepad(consumer, token);
+}
+
+TEST_F(GamepadServiceSimulationTest, RawInputChangeDetectionTouch) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndEnableFeature(
+      features::kGamepadRawInputChangeEvent);
+
+  auto* consumer = CreateConsumer();
+  EXPECT_TRUE(service()->ConsumerBecameActive(consumer));
+
+  SimulatedGamepadParams params;
+  params.name = "Raw input test gamepad";
+  params.button_bounds = {std::nullopt};
+  params.axis_bounds = {};
+  params.touch_surface_bounds = {std::nullopt};
+  auto token = SetupRawInputGamepad(consumer, std::move(params));
+
+  // Test touch add.
+  std::optional<uint32_t> touch_id;
+  {
+    TestFuture<uint32_t, const Gamepad&> future;
+    EXPECT_CALL(*consumer, OnGamepadRawInputChanged)
+        .WillOnce(InvokeFuture(future));
+
+    touch_id = service()->SimulateTouchInput(token, /*surface_id=*/0,
+                                             /*logical_x=*/0.3,
+                                             /*logical_y=*/0.7);
+    ASSERT_TRUE(touch_id.has_value());
+    service()->SimulateInputFrame(token);
+
+    const auto [id, gamepad] = future.Take();
+    EXPECT_EQ(gamepad.touch_events_length, 1u);
+    EXPECT_EQ(gamepad.touch_events[0].touch_id, touch_id.value());
+    EXPECT_FLOAT_EQ(gamepad.touch_events[0].x, 0.3f);
+    EXPECT_FLOAT_EQ(gamepad.touch_events[0].y, 0.7f);
+  }
+  WaitForPoll();
+
+  // Test touch move.
+  {
+    TestFuture<uint32_t, const Gamepad&> future;
+    EXPECT_CALL(*consumer, OnGamepadRawInputChanged)
+        .WillOnce(InvokeFuture(future));
+
+    service()->SimulateTouchMove(token, touch_id.value(), /*logical_x=*/0.6,
+                                 /*logical_y=*/0.4);
+    service()->SimulateInputFrame(token);
+
+    const auto [id, gamepad] = future.Take();
+    EXPECT_EQ(gamepad.touch_events_length, 1u);
+    EXPECT_EQ(gamepad.touch_events[0].touch_id, touch_id.value());
+    EXPECT_FLOAT_EQ(gamepad.touch_events[0].x, 0.6f);
+    EXPECT_FLOAT_EQ(gamepad.touch_events[0].y, 0.4f);
+  }
+  WaitForPoll();
+
+  // Test touch end.
+  {
+    TestFuture<uint32_t, const Gamepad&> future;
+    EXPECT_CALL(*consumer, OnGamepadRawInputChanged)
+        .WillOnce(InvokeFuture(future));
+
+    service()->SimulateTouchEnd(token, touch_id.value());
+    service()->SimulateInputFrame(token);
+
+    const auto [id, gamepad] = future.Take();
+    EXPECT_EQ(gamepad.touch_events_length, 0u);
+  }
+  WaitForPoll();
+
+  CleanupRawInputGamepad(consumer, token);
+}
+
+TEST_F(GamepadServiceSimulationTest,
+       RawInputChangeDetectionMultipleInputTypes) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndEnableFeature(
+      features::kGamepadRawInputChangeEvent);
+
+  auto* consumer = CreateConsumer();
+  EXPECT_TRUE(service()->ConsumerBecameActive(consumer));
+
+  SimulatedGamepadParams params;
+  params.button_bounds = {std::nullopt, std::nullopt};
+  params.axis_bounds = {GamepadLogicalBounds(-1.0, 1.0)};
+  params.touch_surface_bounds = {std::nullopt};
+
+  auto token = SetupRawInputGamepad(consumer, std::move(params));
+
+  std::optional<uint32_t> touch_id;
+  base::RunLoop loop;
+  TestFuture<uint32_t, const Gamepad&> future;
+  EXPECT_CALL(*consumer, OnGamepadRawInputChanged)
+      .WillOnce(InvokeFuture(future));
+
+  // Change multiple inputs in the same frame.
+  service()->SimulateButtonInput(token, /*index=*/1, /*logical_value=*/0.8,
+                                 /*pressed=*/std::nullopt,
+                                 /*touched=*/std::nullopt);
+  service()->SimulateAxisInput(token, /*index=*/0, /*logical_value=*/0.5);
+  touch_id = service()->SimulateTouchInput(token, /*surface_id=*/0,
+                                           /*logical_x=*/0.2,
+                                           /*logical_y=*/0.9);
+  ASSERT_TRUE(touch_id.has_value());
+
+  service()->SimulateInputFrame(token);
+
+  const auto [id, gamepad] = future.Take();
+  EXPECT_EQ(gamepad.buttons_length, 2u);
+  EXPECT_EQ(gamepad.buttons[1].value, 0.8);
+  EXPECT_EQ(gamepad.axes_length, 1u);
+  EXPECT_EQ(gamepad.axes[0], 0.5);
+  EXPECT_EQ(gamepad.touch_events_length, 1u);
+  EXPECT_EQ(gamepad.touch_events[0].touch_id, touch_id.value());
+  EXPECT_FLOAT_EQ(gamepad.touch_events[0].x, 0.2f);
+  EXPECT_FLOAT_EQ(gamepad.touch_events[0].y, 0.9f);
+  CleanupRawInputGamepad(consumer, token);
+}
+
+TEST_F(GamepadServiceSimulationTest, RawInputChangeRequiresUserGesture) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndEnableFeature(
+      features::kGamepadRawInputChangeEvent);
+
+  auto* consumer = CreateConsumer();
+  EXPECT_TRUE(service()->ConsumerBecameActive(consumer));
+
+  // Create gamepad without establishing user gesture.
+  SimulatedGamepadParams params;
+  params.name = "1 button";
+  params.button_bounds = {std::nullopt};
+  auto token = service()->AddSimulatedGamepad(std::move(params));
+
+  // Expect no callbacks without user gesture.
+  EXPECT_CALL(*consumer, OnGamepadRawInputChanged).Times(0);
+  EXPECT_CALL(*consumer, OnGamepadConnected).Times(0);
+
+  service()->SimulateButtonInput(token, /*index=*/0, /*logical_value=*/1.0,
+                                 /*pressed=*/std::nullopt,
+                                 /*touched=*/std::nullopt);
+  service()->SimulateInputFrame(token);
+
+  service()->SimulateButtonInput(token, /*index=*/0, /*logical_value=*/0.5,
+                                 /*pressed=*/std::nullopt,
+                                 /*touched=*/std::nullopt);
+  service()->SimulateInputFrame(token);
+
+  // Establish user gesture.
+  TestFuture<uint32_t, const Gamepad&> connected_future;
+  EXPECT_CALL(*consumer, OnGamepadConnected)
+      .WillOnce(InvokeFuture(connected_future));
+
+  service()->SimulateButtonInput(token, /*index=*/0, /*logical_value=*/1.0,
+                                 /*pressed=*/std::nullopt,
+                                 /*touched=*/std::nullopt);
+  service()->SimulateInputFrame(token);
+  EXPECT_EQ(connected_future.Get<0>(), 0u);
+
+  WaitForPoll();
+
+  TestFuture<uint32_t, const Gamepad&> input_future;
+  EXPECT_CALL(*consumer, OnGamepadRawInputChanged)
+      .WillOnce(InvokeFuture(input_future));
+
+  service()->SimulateButtonInput(token, /*index=*/0, /*logical_value=*/0.7,
+                                 /*pressed=*/std::nullopt,
+                                 /*touched=*/std::nullopt);
+  service()->SimulateInputFrame(token);
+
+  const auto [id, gamepad] = input_future.Take();
+  EXPECT_EQ(gamepad.buttons[0].value, 0.7);
+
+  CleanupRawInputGamepad(consumer, token);
+}
+
+TEST_F(GamepadServiceSimulationTest, RawInputChangeDisabledByFeatureFlag) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndDisableFeature(
+      features::kGamepadRawInputChangeEvent);
+
+  auto* consumer = CreateConsumer();
+  EXPECT_TRUE(service()->ConsumerBecameActive(consumer));
+
+  SimulatedGamepadParams params;
+  params.name = "Raw input test gamepad";
+  params.button_bounds = {std::nullopt};
+  auto token = SetupRawInputGamepad(consumer, std::move(params));
+
+  // Even with user gesture, raw input changes should not trigger.
+  EXPECT_CALL(*consumer, OnGamepadRawInputChanged).Times(0);
+
+  service()->SimulateButtonInput(token, /*index=*/0, /*logical_value=*/0.5,
+                                 /*pressed=*/std::nullopt,
+                                 /*touched=*/std::nullopt);
+  service()->SimulateInputFrame(token);
+
+  service()->SimulateButtonInput(token, /*index=*/0, /*logical_value=*/0.8,
+                                 /*pressed=*/std::nullopt,
+                                 /*touched=*/std::nullopt);
+  service()->SimulateInputFrame(token);
+
+  WaitForPoll();
+
+  CleanupRawInputGamepad(consumer, token);
+}
+
 }  // namespace device
diff --git a/device/gamepad/public/mojom/gamepad.mojom b/device/gamepad/public/mojom/gamepad.mojom
index 99ba662..5a9e8fa 100644
--- a/device/gamepad/public/mojom/gamepad.mojom
+++ b/device/gamepad/public/mojom/gamepad.mojom
@@ -94,6 +94,12 @@
   // gamepad in the gamepad array, and |gamepad| is a reference to the
   // connected gamepad.
   GamepadDisconnected(uint32 index, Gamepad gamepad);
+
+  // Called when a gamepad's input state changes, such as button presses,
+  // axis movements, or touch events, but not connection or disconnection.
+  // |index| is the index of the gamepad in the gamepad array, and |gamepad|
+  // is a reference to the gamepad with updated input data.
+  GamepadRawInputChanged(uint32 index, Gamepad gamepad);
 };
 
 // Asks the browser process to start polling, and return a shared memory
diff --git a/device/vr/android/mailbox_to_surface_bridge.h b/device/vr/android/mailbox_to_surface_bridge.h
index f32c66777..41cc96fd 100644
--- a/device/vr/android/mailbox_to_surface_bridge.h
+++ b/device/vr/android/mailbox_to_surface_bridge.h
@@ -9,7 +9,6 @@
 #include "gpu/command_buffer/common/shared_image_usage.h"
 
 namespace gfx {
-enum class BufferFormat : uint8_t;
 class ColorSpace;
 class GpuFence;
 struct GpuMemoryBufferHandle;
diff --git a/device/vr/public/mojom/BUILD.gn b/device/vr/public/mojom/BUILD.gn
index 4378cc6c..36be93a 100644
--- a/device/vr/public/mojom/BUILD.gn
+++ b/device/vr/public/mojom/BUILD.gn
@@ -19,7 +19,7 @@
     "//device/gamepad/public/mojom",
     "//gpu/ipc/common:interfaces",
     "//mojo/public/mojom/base",
-    "//services/viz/public/mojom:shared_image_format",
+    "//services/viz/public/mojom/compositing:shared_image_format",
     "//ui/display/mojom",
     "//ui/gfx/geometry/mojom",
     "//ui/gfx/mojom",
diff --git a/docs/code_reviews.md b/docs/code_reviews.md
index e090b067..0e34979 100644
--- a/docs/code_reviews.md
+++ b/docs/code_reviews.md
@@ -45,11 +45,12 @@
     are 3 review iterations. If there is a time zone divide, aim for 2 review
     iterations.
 
-*   Use the status field in Gerrit settings to indicate if you're away and when
-    you'll be back.
+*   Use the "Display name" and "About me" fields in Gerrit settings to indicate
+    if you're away and when you'll be back.
 
 *   Don't generally discourage people from sending you code reviews. This
-    includes using a blanket "slow" in your status field.
+    includes using a blanket "slow" in your "Display name" or "About me"
+    fields.
 
 #### Expectations for all authors
 
diff --git a/extensions/browser/api/web_request/extension_web_request_event_router.cc b/extensions/browser/api/web_request/extension_web_request_event_router.cc
index d04cecc..f35c3944 100644
--- a/extensions/browser/api/web_request/extension_web_request_event_router.cc
+++ b/extensions/browser/api/web_request/extension_web_request_event_router.cc
@@ -2170,6 +2170,20 @@
   const base::Value::List* existing_listeners =
       prefs->ReadPrefAsList(extension_id, kFilteredLazyListeners);
 
+  // Ensure we don't add duplicate listeners.
+  if (existing_listeners) {
+    for (const auto& entry : *existing_listeners) {
+      if (!entry.is_dict()) {
+        continue;
+      }
+      const std::string* name =
+          entry.GetDict().FindString(kListenerSubEventNameKey);
+      if (name && *name == listener.id.sub_event_name) {
+        return;
+      }
+    }
+  }
+
   base::Value::List new_listeners;
   if (existing_listeners) {
     new_listeners = existing_listeners->Clone();
diff --git a/gpu/command_buffer/service/dawn_caching_interface.cc b/gpu/command_buffer/service/dawn_caching_interface.cc
index 25b4376..05ea78e 100644
--- a/gpu/command_buffer/service/dawn_caching_interface.cc
+++ b/gpu/command_buffer/service/dawn_caching_interface.cc
@@ -29,7 +29,7 @@
 
 DawnCachingInterface::DawnCachingInterface(
     scoped_refptr<MemoryCache> backend,
-    std::unique_ptr<GpuPersistentCache> persistent_cache)
+    scoped_refptr<GpuPersistentCache> persistent_cache)
     : memory_cache_backend_(std::move(backend)),
       persistent_cache_(std::move(persistent_cache)) {}
 
@@ -123,7 +123,7 @@
 std::unique_ptr<DawnCachingInterface>
 DawnCachingInterfaceFactory::CreateInstance(
     const gpu::GpuDiskCacheHandle& handle,
-    std::unique_ptr<GpuPersistentCache> persistent_cache) {
+    scoped_refptr<GpuPersistentCache> persistent_cache) {
   return base::WrapUnique(new DawnCachingInterface(
       GetOrCreateMemoryCache(handle), std::move(persistent_cache)));
 }
diff --git a/gpu/command_buffer/service/dawn_caching_interface.h b/gpu/command_buffer/service/dawn_caching_interface.h
index b5878c2..e3a28c4f 100644
--- a/gpu/command_buffer/service/dawn_caching_interface.h
+++ b/gpu/command_buffer/service/dawn_caching_interface.h
@@ -70,7 +70,7 @@
                                 CacheBlobCallback callback = {});
   explicit DawnCachingInterface(
       scoped_refptr<MemoryCache> backend,
-      std::unique_ptr<GpuPersistentCache> persistent_cache);
+      scoped_refptr<GpuPersistentCache> persistent_cache);
 
   // Caching interface owns a reference to the backend.
   scoped_refptr<MemoryCache> memory_cache_backend_ = nullptr;
@@ -82,7 +82,7 @@
   // to/from disk.
   // TODO(crbug.com/399642827): Remove the above callback once we migrate
   // everything to use GpuPersistentCache API.
-  std::unique_ptr<GpuPersistentCache> persistent_cache_;
+  scoped_refptr<GpuPersistentCache> persistent_cache_;
 };
 
 // Factory class for producing and managing DawnCachingInterfaces.
@@ -108,7 +108,7 @@
 
   std::unique_ptr<DawnCachingInterface> CreateInstance(
       const gpu::GpuDiskCacheHandle& handle,
-      std::unique_ptr<GpuPersistentCache> persistent_cache);
+      scoped_refptr<GpuPersistentCache> persistent_cache);
 
   // Returns a pointer to a DawnCachingInterface that owns the in memory
   // backend. This is used for incognito cases where the cache should not be
diff --git a/gpu/command_buffer/service/gpu_persistent_cache.h b/gpu/command_buffer/service/gpu_persistent_cache.h
index 9b0c716..9b52ce1 100644
--- a/gpu/command_buffer/service/gpu_persistent_cache.h
+++ b/gpu/command_buffer/service/gpu_persistent_cache.h
@@ -35,7 +35,8 @@
 // initialized are copied into it on initialization.
 class GPU_GLES2_EXPORT GpuPersistentCache
     : public dawn::platform::CachingInterface,
-      public GrContextOptions::PersistentCache {
+      public GrContextOptions::PersistentCache,
+      public base::RefCountedThreadSafe<GpuPersistentCache> {
  public:
   struct GPU_GLES2_EXPORT AsyncDiskWriteOpts {
     AsyncDiskWriteOpts();
@@ -59,7 +60,6 @@
   explicit GpuPersistentCache(std::string_view cache_prefix,
                               scoped_refptr<MemoryCache> memory_cache,
                               AsyncDiskWriteOpts async_write_options = {});
-  ~GpuPersistentCache() override;
 
   GpuPersistentCache(const GpuPersistentCache&) = delete;
   GpuPersistentCache& operator=(const GpuPersistentCache&) = delete;
@@ -101,6 +101,10 @@
   const persistent_cache::PersistentCache& GetPersistentCacheForTesting() const;
 
  private:
+  friend class base::RefCountedThreadSafe<GpuPersistentCache>;
+
+  ~GpuPersistentCache() override;
+
   struct DiskCache;
 
   // Values are mirrored in tools/metrics/histograms/metadata/gpu/enums.xml
diff --git a/gpu/command_buffer/service/gpu_persistent_cache_unittest.cc b/gpu/command_buffer/service/gpu_persistent_cache_unittest.cc
index 912f140..9ff2ee9 100644
--- a/gpu/command_buffer/service/gpu_persistent_cache_unittest.cc
+++ b/gpu/command_buffer/service/gpu_persistent_cache_unittest.cc
@@ -32,6 +32,8 @@
 class GpuPersistentCacheTest : public testing::Test {
  public:
   void SetUp() override {
+    cache_ = base::MakeRefCounted<GpuPersistentCache>("Test",
+                                                      MakeDefaultMemoryCache());
     ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
     backend_storage_.emplace(persistent_cache::BackendType::kSqlite,
                              temp_dir_.GetPath());
@@ -48,7 +50,7 @@
         backend_storage_->MakePendingBackend(
             base::FilePath(FILE_PATH_LITERAL("test")),
             /*single_connection=*/true, /*journal_mode_wal=*/true));
-    cache_.InitializeCache(std::move(pending_backend));
+    cache_->InitializeCache(std::move(pending_backend));
   }
 
   void RunStoreAndLoadDataMultiThreaded(int num_threads);
@@ -57,23 +59,24 @@
       base::test::TaskEnvironment::TimeSource::MOCK_TIME};
   base::ScopedTempDir temp_dir_;
   std::optional<persistent_cache::BackendStorage> backend_storage_;
-  GpuPersistentCache cache_{"Test", MakeDefaultMemoryCache()};
+  scoped_refptr<GpuPersistentCache> cache_;
 };
 
 TEST_F(GpuPersistentCacheTest,
        StoreAndLoadDataBeforeInitializeWithNoMemoryCache) {
   // Don't initialize cache.
-  GpuPersistentCache cache_with_no_memory_cache{"Test", nullptr};
+  auto cache_with_no_memory_cache =
+      base::MakeRefCounted<GpuPersistentCache>("Test", nullptr);
   const std::string key = "my_key";
   const std::string value = "my_value";
 
   // StoreData() won't do anything but also won't crash.
-  cache_with_no_memory_cache.StoreData(key.c_str(), key.size(), value.c_str(),
-                                       value.size());
+  cache_with_no_memory_cache->StoreData(key.c_str(), key.size(), value.c_str(),
+                                        value.size());
 
   // LoadData() will return zero size since there is no cache yet.
   EXPECT_EQ(
-      cache_with_no_memory_cache.LoadData(key.c_str(), key.size(), nullptr, 0),
+      cache_with_no_memory_cache->LoadData(key.c_str(), key.size(), nullptr, 0),
       0u);
 }
 
@@ -83,11 +86,11 @@
 
   const std::string key = "my_key";
   const std::string value = "my_value";
-  cache_.StoreData(key.c_str(), key.size(), value.c_str(), value.size());
+  cache_->StoreData(key.c_str(), key.size(), value.c_str(), value.size());
 
   std::vector<char> buffer(value.size());
   size_t loaded_size =
-      cache_.LoadData(key.c_str(), key.size(), buffer.data(), buffer.size());
+      cache_->LoadData(key.c_str(), key.size(), buffer.data(), buffer.size());
 
   EXPECT_EQ(loaded_size, value.size());
   EXPECT_EQ(std::string(buffer.begin(), buffer.end()), value);
@@ -100,8 +103,8 @@
   // Insert 3 key/value pairs with the 3 caching interfaces.
   const std::string key_dawn = "my_key_dawn";
   const std::string value_dawn = "my_value_dawn";
-  cache_.StoreData(key_dawn.c_str(), key_dawn.size(), value_dawn.c_str(),
-                   value_dawn.size());
+  cache_->StoreData(key_dawn.c_str(), key_dawn.size(), value_dawn.c_str(),
+                    value_dawn.size());
 
   const std::string key_gr = "my_key_gr";
   sk_sp<SkData> key_gr_data =
@@ -109,20 +112,20 @@
   const std::string value_gr = "my_value_gr";
   sk_sp<SkData> value_gr_data =
       SkData::MakeWithoutCopy(value_gr.c_str(), value_gr.size());
-  cache_.store(*key_gr_data, *value_gr_data);
+  cache_->store(*key_gr_data, *value_gr_data);
 
   const std::string key_gl = "my_key_gl";
   const std::string value_gl = "my_value_gl";
-  cache_.GLBlobCacheSet(key_gl.c_str(), static_cast<int64_t>(key_gl.size()),
-                        value_gl.c_str(),
-                        static_cast<int64_t>(value_gl.size()));
+  cache_->GLBlobCacheSet(key_gl.c_str(), static_cast<int64_t>(key_gl.size()),
+                         value_gl.c_str(),
+                         static_cast<int64_t>(value_gl.size()));
 
   // Load with dawn::Platform::CachingInterface
   auto test_load_dawn = [this](const std::string& key,
                                const std::string& value) {
     std::vector<char> buffer(value.size());
     size_t loaded_size =
-        cache_.LoadData(key.c_str(), key.size(), buffer.data(), buffer.size());
+        cache_->LoadData(key.c_str(), key.size(), buffer.data(), buffer.size());
 
     EXPECT_EQ(loaded_size, value.size());
     EXPECT_EQ(std::string(buffer.begin(), buffer.end()), value);
@@ -134,7 +137,7 @@
   // Load with GrContextOptions::PersistentCache
   auto test_load_gr = [this](const std::string& key, const std::string& value) {
     sk_sp<SkData> key_data = SkData::MakeWithoutCopy(key.c_str(), key.size());
-    sk_sp buffer = cache_.load(*key_data);
+    sk_sp buffer = cache_->load(*key_data);
 
     EXPECT_EQ(buffer->size(), value.size());
     EXPECT_EQ(
@@ -148,8 +151,8 @@
   // Load with GL_ANGLE_blob_cache
   auto test_load_gl = [this](const std::string& key, const std::string& value) {
     std::vector<char> buffer(value.size());
-    int64_t loaded_size = cache_.GLBlobCacheGet(key.c_str(), key.size(),
-                                                buffer.data(), buffer.size());
+    int64_t loaded_size = cache_->GLBlobCacheGet(key.c_str(), key.size(),
+                                                 buffer.data(), buffer.size());
 
     EXPECT_EQ(loaded_size, static_cast<int64_t>(value.size()));
     EXPECT_EQ(std::string(buffer.begin(), buffer.end()), value);
@@ -166,7 +169,7 @@
   const std::string key = "non_existent_key";
   std::vector<char> buffer(16);
   size_t loaded_size =
-      cache_.LoadData(key.c_str(), key.size(), buffer.data(), buffer.size());
+      cache_->LoadData(key.c_str(), key.size(), buffer.data(), buffer.size());
   EXPECT_EQ(loaded_size, 0u);
 }
 
@@ -181,7 +184,7 @@
     base::ThreadPool::PostTask(
         FROM_HERE, {base::MayBlock()},
         base::BindOnce(
-            [](GpuPersistentCache* cache, int thread_id,
+            [](scoped_refptr<GpuPersistentCache> cache, int thread_id,
                base::OnceClosure done_closure) {
               for (int j = 0; j < kNumOperationsPerThread; ++j) {
                 std::string key = "key_" + base::NumberToString(thread_id) +
@@ -200,7 +203,7 @@
               }
               std::move(done_closure).Run();
             },
-            &cache_, i, barrier));
+            cache_, i, barrier));
   }
 
   // Wait for all threads to complete.
@@ -216,8 +219,8 @@
       std::string value =
           "value_" + base::NumberToString(i) + "_" + base::NumberToString(j);
       std::vector<char> buffer(value.size());
-      size_t loaded_size = cache_.LoadData(key.c_str(), key.size(),
-                                           buffer.data(), buffer.size());
+      size_t loaded_size = cache_->LoadData(key.c_str(), key.size(),
+                                            buffer.data(), buffer.size());
       EXPECT_EQ(loaded_size, value.size());
       EXPECT_EQ(std::string(buffer.begin(), buffer.end()), value);
     }
@@ -251,7 +254,7 @@
 
 class GpuPersistentCacheAsyncTest : public GpuPersistentCacheTest {
  protected:
-  std::unique_ptr<GpuPersistentCache> OpenAsyncCache(
+  scoped_refptr<GpuPersistentCache> OpenAsyncCache(
       size_t max_pending_bytes_to_write = std::numeric_limits<size_t>::max()) {
     auto pending_backend = backend_storage_->MakePendingBackend(
         base::FilePath(FILE_PATH_LITERAL("test")),
@@ -264,7 +267,7 @@
     GpuPersistentCache::AsyncDiskWriteOpts options;
     options.task_runner = base::SingleThreadTaskRunner::GetCurrentDefault();
     options.max_pending_bytes_to_write = max_pending_bytes_to_write;
-    auto async_cache = std::make_unique<GpuPersistentCache>(
+    auto async_cache = base::MakeRefCounted<GpuPersistentCache>(
         "TestAsync", MakeDefaultMemoryCache(), std::move(options));
     async_cache->InitializeCache(*std::move(pending_backend));
     return async_cache;
@@ -278,7 +281,7 @@
   const std::string key = "my_key";
   const std::string value = "my_value";
 
-  std::unique_ptr<GpuPersistentCache> async_cache = OpenAsyncCache();
+  scoped_refptr<GpuPersistentCache> async_cache = OpenAsyncCache();
 
   base::HistogramTester histogram_tester;
 
@@ -313,7 +316,7 @@
   const std::string key = "my_key";
   const std::string value = "my_value";
 
-  std::unique_ptr<GpuPersistentCache> async_cache = OpenAsyncCache();
+  scoped_refptr<GpuPersistentCache> async_cache = OpenAsyncCache();
 
   base::HistogramTester histogram_tester;
 
@@ -356,7 +359,7 @@
 TEST_F(GpuPersistentCacheAsyncTest,
        StoreAndLoadDataAsync_ExceedMaxPendingBytes) {
   // Create the cache with a pending byte limit.
-  std::unique_ptr<GpuPersistentCache> async_cache =
+  scoped_refptr<GpuPersistentCache> async_cache =
       OpenAsyncCache(/*max_pending_bytes_to_write=*/10);
 
   const std::string key = "my_key";
@@ -403,12 +406,12 @@
 TEST_F(GpuPersistentCacheTest, MemoryBackingOnly) {
   const std::string key = "my_key";
   const std::string value = "my_value";
-  cache_.StoreData(key.c_str(), key.size(), value.c_str(), value.size());
+  cache_->StoreData(key.c_str(), key.size(), value.c_str(), value.size());
 
   // Check that the entry exists in the cache.
   std::vector<char> buffer(value.size());
   size_t loaded_size =
-      cache_.LoadData(key.c_str(), key.size(), buffer.data(), buffer.size());
+      cache_->LoadData(key.c_str(), key.size(), buffer.data(), buffer.size());
 
   EXPECT_EQ(loaded_size, value.size());
   EXPECT_EQ(std::string(buffer.begin(), buffer.end()), value);
@@ -422,8 +425,9 @@
 
   {
     // Store the data to the cache without initializing the database files
-    GpuPersistentCache cache{"Test", MakeDefaultMemoryCache()};
-    cache.StoreData(key.c_str(), key.size(), value.c_str(), value.size());
+    auto cache = base::MakeRefCounted<GpuPersistentCache>(
+        "Test", MakeDefaultMemoryCache());
+    cache->StoreData(key.c_str(), key.size(), value.c_str(), value.size());
 
     // Initialize the cache, the memory storage will be written to disk.
     ASSERT_OK_AND_ASSIGN(
@@ -432,23 +436,24 @@
             base::FilePath(FILE_PATH_LITERAL("MemoryBackingSyncedToDisk")),
             /*single_connection=*/true, /*journal_mode_wal=*/true));
 
-    cache.InitializeCache(std::move(pending_backend));
+    cache->InitializeCache(std::move(pending_backend));
   }
 
   // Reload the same persistent cache from disk
   {
-    GpuPersistentCache cache{"Test", MakeDefaultMemoryCache()};
+    auto cache = base::MakeRefCounted<GpuPersistentCache>(
+        "Test", MakeDefaultMemoryCache());
     ASSERT_OK_AND_ASSIGN(
         auto pending_backend,
         backend_storage_->MakePendingBackend(
             base::FilePath(FILE_PATH_LITERAL("MemoryBackingSyncedToDisk")),
             /*single_connection=*/true, /*journal_mode_wal=*/true));
-    cache.InitializeCache(std::move(pending_backend));
+    cache->InitializeCache(std::move(pending_backend));
 
     // Check that the entry exists in the cache.
     std::vector<char> buffer(value.size());
     size_t loaded_size =
-        cache.LoadData(key.c_str(), key.size(), buffer.data(), buffer.size());
+        cache->LoadData(key.c_str(), key.size(), buffer.data(), buffer.size());
 
     EXPECT_EQ(loaded_size, value.size());
     EXPECT_EQ(std::string(buffer.begin(), buffer.end()), value);
@@ -464,15 +469,15 @@
   {
     scoped_refptr<MemoryCache> memory_cache =
         base::MakeRefCounted<MemoryCache>(1024);
-    GpuPersistentCache cache{"Test", memory_cache};
+    auto cache = base::MakeRefCounted<GpuPersistentCache>("Test", memory_cache);
     ASSERT_OK_AND_ASSIGN(
         auto pending_backend,
         backend_storage_->MakePendingBackend(
             base::FilePath(FILE_PATH_LITERAL("ReOpenCacheFromFile")),
             /*single_connection=*/true, /*journal_mode_wal=*/true));
-    cache.InitializeCache(std::move(pending_backend));
+    cache->InitializeCache(std::move(pending_backend));
 
-    cache.StoreData(key.c_str(), key.size(), value.c_str(), value.size());
+    cache->StoreData(key.c_str(), key.size(), value.c_str(), value.size());
 
     // Check that the entry exists in the memory cache.
     auto memory_entry = memory_cache->Find(key);
@@ -485,7 +490,7 @@
     // Check that the entry exists in the persistent cache.
     std::vector<char> buffer(value.size());
     size_t loaded_size =
-        cache.LoadData(key.c_str(), key.size(), buffer.data(), buffer.size());
+        cache->LoadData(key.c_str(), key.size(), buffer.data(), buffer.size());
 
     EXPECT_EQ(loaded_size, value.size());
     EXPECT_EQ(std::string(buffer.begin(), buffer.end()), value);
@@ -495,18 +500,18 @@
   {
     scoped_refptr<MemoryCache> memory_cache =
         base::MakeRefCounted<MemoryCache>(1024);
-    GpuPersistentCache cache{"Test", memory_cache};
+    auto cache = base::MakeRefCounted<GpuPersistentCache>("Test", memory_cache);
     ASSERT_OK_AND_ASSIGN(
         auto pending_backend,
         backend_storage_->MakePendingBackend(
             base::FilePath(FILE_PATH_LITERAL("ReOpenCacheFromFile")),
             /*single_connection=*/true, /*journal_mode_wal=*/true));
-    cache.InitializeCache(std::move(pending_backend));
+    cache->InitializeCache(std::move(pending_backend));
 
     // Check that the entry exists in the persistent cache.
     std::vector<char> buffer(value.size());
     size_t loaded_size =
-        cache.LoadData(key.c_str(), key.size(), buffer.data(), buffer.size());
+        cache->LoadData(key.c_str(), key.size(), buffer.data(), buffer.size());
 
     EXPECT_EQ(loaded_size, value.size());
     EXPECT_EQ(std::string(buffer.begin(), buffer.end()), value);
diff --git a/gpu/command_buffer/service/raster_decoder.cc b/gpu/command_buffer/service/raster_decoder.cc
index 5f61aac4..1af95a1 100644
--- a/gpu/command_buffer/service/raster_decoder.cc
+++ b/gpu/command_buffer/service/raster_decoder.cc
@@ -482,8 +482,7 @@
   ~RasterDecoderImpl() override;
 
   // RasterDecoder implementation.
-  ContextResult Initialize(bool enable_gpu_rasterization,
-                           bool lose_context_when_out_of_memory) override;
+  ContextResult Initialize(bool lose_context_when_out_of_memory) override;
   int GetRasterDecoderId() const override;
   int DecoderIdForTest() override;
   ServiceTransferCache* GetTransferCacheForTest() override;
@@ -1047,7 +1046,6 @@
 }
 
 ContextResult RasterDecoderImpl::Initialize(
-    bool enable_gpu_rasterization,
     bool lose_context_when_out_of_memory) {
   TRACE_EVENT0("gpu", "RasterDecoderImpl::Initialize");
   DCHECK(shared_context_state_->IsCurrent(nullptr));
diff --git a/gpu/command_buffer/service/raster_decoder.h b/gpu/command_buffer/service/raster_decoder.h
index 2593bdc3..c3f272272 100644
--- a/gpu/command_buffer/service/raster_decoder.h
+++ b/gpu/command_buffer/service/raster_decoder.h
@@ -83,7 +83,6 @@
   bool log_commands() const { return log_commands_; }
 
   virtual gpu::ContextResult Initialize(
-      bool enable_gpu_rasterization,
       bool lose_context_when_out_of_memory) = 0;
 
   virtual int DecoderIdForTest() = 0;
diff --git a/gpu/command_buffer/service/raster_decoder_unittest.cc b/gpu/command_buffer/service/raster_decoder_unittest.cc
index d64b84a..d438dc06 100644
--- a/gpu/command_buffer/service/raster_decoder_unittest.cc
+++ b/gpu/command_buffer/service/raster_decoder_unittest.cc
@@ -263,8 +263,7 @@
         this, command_buffer_service_.get(), &outputter_, gpu_feature_info_,
         GpuPreferences(), /*memory_tracker=*/nullptr, &shared_image_manager_,
         context_state_, /*is_privileged=*/true);
-    CHECK_EQ(decoder->Initialize(/*enable_gpu_rasterization=*/true,
-                                 /*lose_context_when_out_of_memory=*/false),
+    CHECK_EQ(decoder->Initialize(/*lose_context_when_out_of_memory=*/false),
              ContextResult::kSuccess);
     return decoder;
   }
diff --git a/gpu/command_buffer/service/raster_decoder_unittest_base.cc b/gpu/command_buffer/service/raster_decoder_unittest_base.cc
index cf87c0b..c7803fe7 100644
--- a/gpu/command_buffer/service/raster_decoder_unittest_base.cc
+++ b/gpu/command_buffer/service/raster_decoder_unittest_base.cc
@@ -166,8 +166,7 @@
   decoder_->DisableFlushWorkaroundForTest();
   decoder_->GetLogger()->set_log_synthesized_gl_errors(false);
 
-  ASSERT_EQ(decoder_->Initialize(/*enable_gpu_rasterization=*/false,
-                                 /*lose_context_when_out_of_memory=*/init
+  ASSERT_EQ(decoder_->Initialize(/*lose_context_when_out_of_memory=*/init
                                      .lose_context_when_out_of_memory),
             gpu::ContextResult::kSuccess);
 
diff --git a/gpu/command_buffer/tests/fuzzer_main.cc b/gpu/command_buffer/tests/fuzzer_main.cc
index 5a6f8e8..a49141c2 100644
--- a/gpu/command_buffer/tests/fuzzer_main.cc
+++ b/gpu/command_buffer/tests/fuzzer_main.cc
@@ -241,7 +241,6 @@
       context_type = CONTEXT_TYPE_OPENGLES2;
     }
 #endif
-    enable_gpu_rasterization = it.GetBit();
 
 #if defined(GPU_FUZZER_USE_STUB)
     std::vector<std::string_view> enabled_extensions;
@@ -283,7 +282,6 @@
   }
 
   ContextType context_type = CONTEXT_TYPE_OPENGLES2;
-  bool enable_gpu_rasterization = false;
   GpuDriverBugWorkarounds workarounds;
   gl::GLContextAttribs gl_context_attribs;
 #if defined(GPU_FUZZER_USE_STUB)
@@ -431,8 +429,7 @@
     decoder_->GetLogger()->set_log_synthesized_gl_errors(false);
 
     auto result =
-        decoder_->Initialize(config_.enable_gpu_rasterization,
-                             /*lose_context_when_out_of_memory=*/false);
+        decoder_->Initialize(/*lose_context_when_out_of_memory=*/false);
     if (result != gpu::ContextResult::kSuccess) {
       return false;
     }
diff --git a/gpu/config/gpu_crash_keys.cc b/gpu/config/gpu_crash_keys.cc
index 70de8fb..7d33c17 100644
--- a/gpu/config/gpu_crash_keys.cc
+++ b/gpu/config/gpu_crash_keys.cc
@@ -11,14 +11,18 @@
 
 crash_reporter::CrashKeyString<16> gpu_vendor_id("gpu-venid");
 crash_reporter::CrashKeyString<16> gpu_device_id("gpu-devid");
+crash_reporter::CrashKeyString<16> npu_vendor_id("npu-venid");
+crash_reporter::CrashKeyString<16> npu_device_id("npu-devid");
 #if !BUILDFLAG(IS_ANDROID)
 crash_reporter::CrashKeyString<16> gpu_count("gpu_count");
+crash_reporter::CrashKeyString<16> npu_count("npu_count");
 #endif  // !BUILDFLAG(IS_ANDROID)
 #if BUILDFLAG(IS_WIN)
 crash_reporter::CrashKeyString<16> gpu_sub_sys_id("gpu-subid");
 crash_reporter::CrashKeyString<16> gpu_revision("gpu-rev");
 #endif  // BUILDFLAG(IS_WIN)
 crash_reporter::CrashKeyString<64> gpu_driver_version("gpu-driver");
+crash_reporter::CrashKeyString<64> npu_driver_version("npu-driver");
 crash_reporter::CrashKeyString<16> gpu_pixel_shader_version("gpu-psver");
 crash_reporter::CrashKeyString<16> gpu_vertex_shader_version("gpu-vsver");
 crash_reporter::CrashKeyString<16> gpu_generation_intel("gpu-generation-intel");
diff --git a/gpu/config/gpu_crash_keys.h b/gpu/config/gpu_crash_keys.h
index 542bc540..cc9e864 100644
--- a/gpu/config/gpu_crash_keys.h
+++ b/gpu/config/gpu_crash_keys.h
@@ -15,14 +15,18 @@
 // Keys that can be used for crash reporting.
 extern GPU_CONFIG_EXPORT crash_reporter::CrashKeyString<16> gpu_vendor_id;
 extern GPU_CONFIG_EXPORT crash_reporter::CrashKeyString<16> gpu_device_id;
+extern GPU_CONFIG_EXPORT crash_reporter::CrashKeyString<16> npu_vendor_id;
+extern GPU_CONFIG_EXPORT crash_reporter::CrashKeyString<16> npu_device_id;
 #if !BUILDFLAG(IS_ANDROID)
 extern GPU_CONFIG_EXPORT crash_reporter::CrashKeyString<16> gpu_count;
+extern GPU_CONFIG_EXPORT crash_reporter::CrashKeyString<16> npu_count;
 #endif  // !BUILDFLAG(IS_ANDROID)
 #if BUILDFLAG(IS_WIN)
 extern GPU_CONFIG_EXPORT crash_reporter::CrashKeyString<16> gpu_sub_sys_id;
 extern GPU_CONFIG_EXPORT crash_reporter::CrashKeyString<16> gpu_revision;
 #endif  // BUILDFLAG(IS_WIN)
 extern GPU_CONFIG_EXPORT crash_reporter::CrashKeyString<64> gpu_driver_version;
+extern GPU_CONFIG_EXPORT crash_reporter::CrashKeyString<64> npu_driver_version;
 extern GPU_CONFIG_EXPORT crash_reporter::CrashKeyString<16>
     gpu_pixel_shader_version;
 extern GPU_CONFIG_EXPORT crash_reporter::CrashKeyString<16>
diff --git a/gpu/config/gpu_util.cc b/gpu/config/gpu_util.cc
index 10a8fc20..3a50b090 100644
--- a/gpu/config/gpu_util.cc
+++ b/gpu/config/gpu_util.cc
@@ -422,6 +422,20 @@
   }
 }
 
+// Only record Intel NPU.
+void RecordNpuHistogram(uint32_t vendor_id, uint32_t device_id) {
+  switch (vendor_id) {
+    case 0x8086:
+      base::SparseHistogram::FactoryGet(
+          "NPU.Intel.DeviceId", base::HistogramBase::kUmaTargetedHistogramFlag)
+          ->Add(device_id);
+      break;
+    default:
+      // Do nothing if vendor is not recognized.
+      break;
+  }
+}
+
 #if BUILDFLAG(IS_WIN)
 uint32_t GetSystemCommitLimitMb() {
   PERFORMANCE_INFORMATION perf_info = {sizeof(perf_info)};
@@ -440,6 +454,27 @@
 GpuFeatureInfo* g_gpu_feature_info_cache = nullptr;
 #endif  // BUILDFLAG(IS_ANDROID)
 
+void SetKeysForCrashLoggingForNpu(const GPUInfo& gpu_info) {
+  if (gpu_info.npus.empty()) {
+    return;
+  }
+
+  // For now, only log the first NPU device.
+  const GPUInfo::GPUDevice& npu = gpu_info.npus[0];
+  if (npu.vendor_id) {
+    crash_keys::npu_vendor_id.Set(base::StringPrintf("0x%04x", npu.vendor_id));
+  }
+  if (npu.device_id) {
+    crash_keys::npu_device_id.Set(base::StringPrintf("0x%04x", npu.device_id));
+  }
+
+#if !BUILDFLAG(IS_ANDROID)
+  crash_keys::npu_count.Set(base::StringPrintf("%d", gpu_info.npus.size()));
+#endif  // !BUILDFLAG(IS_ANDROID)
+
+  crash_keys::npu_driver_version.Set(npu.driver_version);
+}
+
 }  // namespace
 
 GpuFeatureInfo ComputeGpuFeatureInfoWithNoGpu() {
@@ -689,7 +724,10 @@
 }
 
 void SetKeysForCrashLogging(const GPUInfo& gpu_info) {
+  SetKeysForCrashLoggingForNpu(gpu_info);
+
   const GPUInfo::GPUDevice& active_gpu = gpu_info.active_gpu();
+
   // Don't record vendor/device ids on Android when running with GL.
   constexpr bool record_zero_ids = !BUILDFLAG(IS_ANDROID);
   if (record_zero_ids || active_gpu.vendor_id) {
@@ -1073,6 +1111,12 @@
     RecordGpuHistogram(gpu.vendor_id, gpu.device_id);
 }
 
+void RecordNpuHistograms(const GPUInfo& gpu_info) {
+  for (const auto& npu : gpu_info.npus) {
+    RecordNpuHistogram(npu.vendor_id, npu.device_id);
+  }
+}
+
 #if BUILDFLAG(IS_WIN)
 std::string DirectMLFeatureLevelToString(uint32_t directml_feature_level) {
   if (directml_feature_level == 0) {
diff --git a/gpu/config/gpu_util.h b/gpu/config/gpu_util.h
index bd9fdbf9..9ee3442 100644
--- a/gpu/config/gpu_util.h
+++ b/gpu/config/gpu_util.h
@@ -96,6 +96,10 @@
 // Currently only record for AMD/Nvidia GPUs.
 GPU_CONFIG_EXPORT void RecordDiscreteGpuHistograms(const GPUInfo& gpu_info);
 
+// Record histograms for NPU device id.
+// Currently only record for Intel NPUs.
+GPU_CONFIG_EXPORT void RecordNpuHistograms(const GPUInfo& gpu_info);
+
 #if BUILDFLAG(IS_WIN)
 GPU_CONFIG_EXPORT std::string DirectMLFeatureLevelToString(
     uint32_t directml_feature_level);
diff --git a/gpu/ipc/client/command_buffer_proxy_impl.cc b/gpu/ipc/client/command_buffer_proxy_impl.cc
index a361354a..12bf55b 100644
--- a/gpu/ipc/client/command_buffer_proxy_impl.cc
+++ b/gpu/ipc/client/command_buffer_proxy_impl.cc
@@ -68,6 +68,7 @@
 ContextResult CommandBufferProxyImpl::Initialize(
     gpu::SchedulingPriority stream_priority,
     mojom::ContextCreationAttribsPtr attribs,
+    bool enable_gpu_rasterization,
     const GURL& active_url,
     const std::string_view label) {
   TRACE_EVENT0("gpu", "GpuChannelHost::CreateViewCommandBuffer");
@@ -76,11 +77,6 @@
   // prevent cleanup on destruction.
   auto channel = std::move(channel_);
 
-  bool enable_gpu_rasterization = false;
-  if (attribs->which() == mojom::ContextCreationAttribs::Tag::kRaster) {
-    enable_gpu_rasterization = attribs->get_raster()->enable_gpu_rasterization;
-  }
-
   auto params = mojom::CreateCommandBufferParams::New();
   params->stream_id = stream_id_;
   params->stream_priority = stream_priority;
diff --git a/gpu/ipc/client/command_buffer_proxy_impl.h b/gpu/ipc/client/command_buffer_proxy_impl.h
index bcd432b..3f34248 100644
--- a/gpu/ipc/client/command_buffer_proxy_impl.h
+++ b/gpu/ipc/client/command_buffer_proxy_impl.h
@@ -92,6 +92,7 @@
   // Connect to a command buffer in the GPU process.
   ContextResult Initialize(gpu::SchedulingPriority stream_priority,
                            mojom::ContextCreationAttribsPtr attribs,
+                           bool enable_gpu_rasterization,
                            const GURL& active_url = GURL(),
                            const std::string_view label = "");
 
diff --git a/gpu/ipc/client/command_buffer_proxy_impl_unittest.cc b/gpu/ipc/client/command_buffer_proxy_impl_unittest.cc
index 942b5abd..c637988 100644
--- a/gpu/ipc/client/command_buffer_proxy_impl_unittest.cc
+++ b/gpu/ipc/client/command_buffer_proxy_impl_unittest.cc
@@ -138,7 +138,7 @@
     proxy->Initialize(SchedulingPriority::kNormal,
                       mojom::ContextCreationAttribs::NewGles(
                           mojom::GLESCreationAttribs::New()),
-                      GURL());
+                      /*enable_gpu_rasterization=*/false, GURL());
     // Use an arbitrary valid shm_id. The command buffer doesn't use this
     // directly, but not setting it triggers DCHECKs.
     proxy->SetGetBuffer(1 /* shm_id */);
diff --git a/gpu/ipc/common/BUILD.gn b/gpu/ipc/common/BUILD.gn
index 45922b3..3329d45 100644
--- a/gpu/ipc/common/BUILD.gn
+++ b/gpu/ipc/common/BUILD.gn
@@ -205,7 +205,7 @@
     ":shared_image_pool_client_interface",
     ":surface_handle",
     "//mojo/public/mojom/base",
-    "//services/viz/public/mojom:shared_image_format",
+    "//services/viz/public/mojom/compositing:shared_image_format",
     "//skia/public/mojom",
     "//ui/gfx/geometry/mojom",
     "//ui/gfx/mojom",
@@ -336,7 +336,7 @@
     ":shared_image_pool_client_interface",
     ":surface_handle",
     "//mojo/public/mojom/base",
-    "//services/viz/public/mojom:shared_image_format",
+    "//services/viz/public/mojom/compositing:shared_image_format",
     "//skia/public/mojom",
     "//ui/gfx/geometry/mojom",
     "//ui/gfx/mojom",
@@ -644,7 +644,8 @@
         },
       ]
       traits_headers = [ "shared_image_metadata_mojom_traits.h" ]
-      traits_public_deps = [ "//services/viz/public/mojom:shared_image_format" ]
+      traits_public_deps =
+          [ "//services/viz/public/mojom/compositing:shared_image_format" ]
     },
     {
       types = [
diff --git a/gpu/ipc/common/gpu_channel.mojom b/gpu/ipc/common/gpu_channel.mojom
index 27334b5f..ec8c530 100644
--- a/gpu/ipc/common/gpu_channel.mojom
+++ b/gpu/ipc/common/gpu_channel.mojom
@@ -51,7 +51,6 @@
 };
 
 struct RasterCreationAttribs {
-  bool enable_gpu_rasterization = false;
   bool lose_context_when_out_of_memory = false;
 };
 
diff --git a/gpu/ipc/gl_in_process_context.cc b/gpu/ipc/gl_in_process_context.cc
index ccc527de..fa7ac5e 100644
--- a/gpu/ipc/gl_in_process_context.cc
+++ b/gpu/ipc/gl_in_process_context.cc
@@ -64,7 +64,8 @@
       mojom::ContextCreationAttribs::NewGles(mojom::GLESCreationAttribs::New());
 
   auto result = command_buffer_->Initialize(
-      std::move(attribs), base::SingleThreadTaskRunner::GetCurrentDefault(),
+      std::move(attribs), /*enable_gpu_rasterization=*/false,
+      base::SingleThreadTaskRunner::GetCurrentDefault(),
       /*gr_shader_cache=*/nullptr,
       /*use_shader_cache_shm_count=*/nullptr);
   if (result != ContextResult::kSuccess) {
diff --git a/gpu/ipc/in_process_command_buffer.cc b/gpu/ipc/in_process_command_buffer.cc
index 6e2f798..e00cbed 100644
--- a/gpu/ipc/in_process_command_buffer.cc
+++ b/gpu/ipc/in_process_command_buffer.cc
@@ -97,11 +97,13 @@
 
 InProcessCommandBuffer::InitializeOnGpuThreadParams::
     InitializeOnGpuThreadParams(mojom::ContextCreationAttribsPtr attribs,
+                                bool enable_gpu_rasterization,
                                 Capabilities* capabilities,
                                 GLCapabilities* gl_capabilities,
                                 gpu::raster::GrShaderCache* gr_shader_cache,
                                 GpuProcessShmCount* use_shader_cache_shm_count)
     : attribs(std::move(attribs)),
+      enable_gpu_rasterization(enable_gpu_rasterization),
       capabilities(capabilities),
       gl_capabilities(gl_capabilities),
       gr_shader_cache(gr_shader_cache),
@@ -189,6 +191,7 @@
 
 gpu::ContextResult InProcessCommandBuffer::Initialize(
     mojom::ContextCreationAttribsPtr attribs,
+    bool enable_gpu_rasterization,
     scoped_refptr<base::SingleThreadTaskRunner> task_runner,
     gpu::raster::GrShaderCache* gr_shader_cache,
     GpuProcessShmCount* use_shader_cache_shm_count) {
@@ -202,9 +205,9 @@
 
   Capabilities capabilities;
   GLCapabilities gl_capabilities;
-  InitializeOnGpuThreadParams params(std::move(attribs), &capabilities,
-                                     &gl_capabilities, gr_shader_cache,
-                                     use_shader_cache_shm_count);
+  InitializeOnGpuThreadParams params(
+      std::move(attribs), enable_gpu_rasterization, &capabilities,
+      &gl_capabilities, gr_shader_cache, use_shader_cache_shm_count);
 
   base::OnceCallback<gpu::ContextResult(void)> init_task =
       base::BindOnce(&InProcessCommandBuffer::InitializeOnGpuThread,
@@ -322,7 +325,6 @@
     gl_share_group_ = task_executor_->GetShareGroup();
   }
 
-  bool enable_gpu_rasterization = false;
   switch (params.attribs->which()) {
     case mojom::ContextCreationAttribs::Tag::kWebgpu: {
       if (!task_executor_->gpu_preferences().enable_webgpu) {
@@ -375,9 +377,8 @@
               /*is_privileged=*/true);
 
       const auto& attribs = params.attribs->get_raster();
-      enable_gpu_rasterization = attribs->enable_gpu_rasterization;
-      auto result = raster_decoder->Initialize(
-          enable_gpu_rasterization, attribs->lose_context_when_out_of_memory);
+      auto result =
+          raster_decoder->Initialize(attribs->lose_context_when_out_of_memory);
       if (result != gpu::ContextResult::kSuccess) {
         DestroyOnGpuThread();
         DLOG(ERROR) << "Failed to initialize decoder.";
@@ -501,7 +502,7 @@
   }
 
   *params.capabilities = decoder_->GetCapabilities();
-  params.capabilities->gpu_rasterization = enable_gpu_rasterization;
+  params.capabilities->gpu_rasterization = params.enable_gpu_rasterization;
   *params.gl_capabilities = decoder_->GetGLCapabilities();
 
   return gpu::ContextResult::kSuccess;
diff --git a/gpu/ipc/in_process_command_buffer.h b/gpu/ipc/in_process_command_buffer.h
index 9fd63bc..b6d4572 100644
--- a/gpu/ipc/in_process_command_buffer.h
+++ b/gpu/ipc/in_process_command_buffer.h
@@ -97,6 +97,7 @@
 
   gpu::ContextResult Initialize(
       mojom::ContextCreationAttribsPtr attribs,
+      bool enable_gpu_rasterization,
       scoped_refptr<base::SingleThreadTaskRunner> task_runner,
       gpu::raster::GrShaderCache* gr_shader_cache,
       GpuProcessShmCount* use_shader_cache_shm_count);
@@ -177,12 +178,14 @@
  private:
   struct InitializeOnGpuThreadParams {
     mojom::ContextCreationAttribsPtr attribs;
+    bool enable_gpu_rasterization = false;
     raw_ptr<Capabilities> capabilities;       // Output.
     raw_ptr<GLCapabilities> gl_capabilities;  // Output.
     raw_ptr<gpu::raster::GrShaderCache> gr_shader_cache;
     raw_ptr<GpuProcessShmCount> use_shader_cache_shm_count;
 
     InitializeOnGpuThreadParams(mojom::ContextCreationAttribsPtr attribs,
+                                bool enable_gpu_rasterization,
                                 Capabilities* capabilities,
                                 GLCapabilities* gl_capabilities,
                                 gpu::raster::GrShaderCache* gr_shader_cache,
diff --git a/gpu/ipc/raster_in_process_context.cc b/gpu/ipc/raster_in_process_context.cc
index 870165c..08630f74 100644
--- a/gpu/ipc/raster_in_process_context.cc
+++ b/gpu/ipc/raster_in_process_context.cc
@@ -49,14 +49,14 @@
   constexpr bool lose_context_when_out_of_memory = false;
   auto attribs = mojom::ContextCreationAttribs::NewRaster(
       mojom::RasterCreationAttribs::New(
-          /*enable_gpu_rasterization=*/enable_gpu_rasterization,
           /*lose_context_when_out_of_memory=*/lose_context_when_out_of_memory));
 
   command_buffer_ =
       std::make_unique<InProcessCommandBuffer>(task_executor, GURL());
   auto result = command_buffer_->Initialize(
-      std::move(attribs), base::SingleThreadTaskRunner::GetCurrentDefault(),
-      gr_shader_cache, use_shader_cache_shm_count);
+      std::move(attribs), enable_gpu_rasterization,
+      base::SingleThreadTaskRunner::GetCurrentDefault(), gr_shader_cache,
+      use_shader_cache_shm_count);
   if (result != ContextResult::kSuccess) {
     DLOG(ERROR) << "Failed to initialize InProcessCommmandBuffer";
     return result;
diff --git a/gpu/ipc/service/arc_shared_image_interface.cc b/gpu/ipc/service/arc_shared_image_interface.cc
index d9b239e..850cbe5 100644
--- a/gpu/ipc/service/arc_shared_image_interface.cc
+++ b/gpu/ipc/service/arc_shared_image_interface.cc
@@ -42,10 +42,6 @@
 scoped_refptr<ArcSharedImageInterface> ArcSharedImageInterface::Create(
     GpuChannelManager* gpu_channel_manager,
     scoped_refptr<base::SingleThreadTaskRunner> gpu_task_runner) {
-  if (!base::FeatureList::IsEnabled(arc::kVideoEncodeUseMappableSI)) {
-    return nullptr;
-  }
-
   CHECK(gpu_task_runner->BelongsToCurrentThread());
 
   gpu::ContextResult result;
diff --git a/gpu/ipc/service/raster_command_buffer_stub.cc b/gpu/ipc/service/raster_command_buffer_stub.cc
index 92574743..5a1b5ac5 100644
--- a/gpu/ipc/service/raster_command_buffer_stub.cc
+++ b/gpu/ipc/service/raster_command_buffer_stub.cc
@@ -107,8 +107,7 @@
   }
 
   // Initialize the decoder with either the view or pbuffer GLContext.
-  result = decoder->Initialize(attribs.enable_gpu_rasterization,
-                               attribs.lose_context_when_out_of_memory);
+  result = decoder->Initialize(attribs.lose_context_when_out_of_memory);
   if (result != gpu::ContextResult::kSuccess) {
     DLOG(ERROR) << "Failed to initialize decoder.";
     return result;
diff --git a/gpu/ipc/webgpu_in_process_context.cc b/gpu/ipc/webgpu_in_process_context.cc
index 021687f..01306e2 100644
--- a/gpu/ipc/webgpu_in_process_context.cc
+++ b/gpu/ipc/webgpu_in_process_context.cc
@@ -47,10 +47,11 @@
   command_buffer_ =
       std::make_unique<InProcessCommandBuffer>(task_executor, GURL());
 
-  auto result =
-      command_buffer_->Initialize(std::move(attribs), client_task_runner_,
-                                  /*gr_shader_cache=*/nullptr,
-                                  /*use_shader_cache_shm_count=*/nullptr);
+  auto result = command_buffer_->Initialize(
+      std::move(attribs),
+      /*enable_gpu_rasterization=*/false, client_task_runner_,
+      /*gr_shader_cache=*/nullptr,
+      /*use_shader_cache_shm_count=*/nullptr);
   if (result != ContextResult::kSuccess) {
     DLOG(ERROR) << "Failed to initialize InProcessCommmandBuffer";
     return result;
diff --git a/infra/config/generated/builders/build/Mac Builder Siso FYI/targets/chromium.build.fyi.json b/infra/config/generated/builders/build/Mac Builder Siso FYI/targets/chromium.build.fyi.json
index 20dc0092..f1573cc 100644
--- a/infra/config/generated/builders/build/Mac Builder Siso FYI/targets/chromium.build.fyi.json
+++ b/infra/config/generated/builders/build/Mac Builder Siso FYI/targets/chromium.build.fyi.json
@@ -1668,6 +1668,7 @@
           "script": "//tools/perf/process_perf_results.py"
         },
         "module_name": "//components:components_perftests",
+        "module_scheme": "flat",
         "name": "components_perftests",
         "swarming": {
           "dimensions": {
diff --git a/infra/config/generated/builders/build/Mac Tests Siso FYI/targets/chromium.build.fyi.json b/infra/config/generated/builders/build/Mac Tests Siso FYI/targets/chromium.build.fyi.json
index 62d4943..67f0b01 100644
--- a/infra/config/generated/builders/build/Mac Tests Siso FYI/targets/chromium.build.fyi.json
+++ b/infra/config/generated/builders/build/Mac Tests Siso FYI/targets/chromium.build.fyi.json
@@ -1640,6 +1640,7 @@
           "script": "//tools/perf/process_perf_results.py"
         },
         "module_name": "//components:components_perftests",
+        "module_scheme": "flat",
         "name": "components_perftests",
         "swarming": {
           "dimensions": {
diff --git a/infra/config/generated/builders/build/android-build-perf-developer/gn-args.json b/infra/config/generated/builders/build/android-build-perf-developer/gn-args.json
index 55a50be3..4d47323 100644
--- a/infra/config/generated/builders/build/android-build-perf-developer/gn-args.json
+++ b/infra/config/generated/builders/build/android-build-perf-developer/gn-args.json
@@ -27,6 +27,21 @@
         "use_remoteexec": true,
         "use_siso": true
       }
+    },
+    "siso_no_clang_modules": {
+      "gn_args": {
+        "android_static_analysis": "off",
+        "enable_r8_tracerefs": true,
+        "is_component_build": true,
+        "is_debug": true,
+        "symbol_level": 2,
+        "target_cpu": "arm64",
+        "target_os": "android",
+        "use_clang_modules": false,
+        "use_reclient": false,
+        "use_remoteexec": true,
+        "use_siso": true
+      }
     }
   }
 }
\ No newline at end of file
diff --git a/infra/config/generated/builders/build/ios-build-perf-developer/gn-args.json b/infra/config/generated/builders/build/ios-build-perf-developer/gn-args.json
index 3b0908ce..46366ab 100644
--- a/infra/config/generated/builders/build/ios-build-perf-developer/gn-args.json
+++ b/infra/config/generated/builders/build/ios-build-perf-developer/gn-args.json
@@ -21,6 +21,18 @@
         "use_remoteexec": true,
         "use_siso": true
       }
+    },
+    "siso_no_clang_modules": {
+      "gn_args": {
+        "is_debug": true,
+        "target_cpu": "arm64",
+        "target_environment": "simulator",
+        "target_os": "ios",
+        "use_clang_modules": false,
+        "use_reclient": false,
+        "use_remoteexec": true,
+        "use_siso": true
+      }
     }
   }
 }
\ No newline at end of file
diff --git a/infra/config/generated/builders/build/linux-build-perf-developer/gn-args.json b/infra/config/generated/builders/build/linux-build-perf-developer/gn-args.json
index 19d9dd02..a5cf9ef 100644
--- a/infra/config/generated/builders/build/linux-build-perf-developer/gn-args.json
+++ b/infra/config/generated/builders/build/linux-build-perf-developer/gn-args.json
@@ -23,6 +23,19 @@
         "use_remoteexec": true,
         "use_siso": true
       }
+    },
+    "siso_no_clang_modules": {
+      "gn_args": {
+        "is_component_build": true,
+        "is_debug": true,
+        "symbol_level": 2,
+        "target_cpu": "x64",
+        "target_os": "linux",
+        "use_clang_modules": false,
+        "use_reclient": false,
+        "use_remoteexec": true,
+        "use_siso": true
+      }
     }
   }
 }
\ No newline at end of file
diff --git a/infra/config/generated/builders/build/mac-build-perf-developer/gn-args.json b/infra/config/generated/builders/build/mac-build-perf-developer/gn-args.json
index 7d217e22..7b1f26b 100644
--- a/infra/config/generated/builders/build/mac-build-perf-developer/gn-args.json
+++ b/infra/config/generated/builders/build/mac-build-perf-developer/gn-args.json
@@ -23,6 +23,19 @@
         "use_remoteexec": true,
         "use_siso": true
       }
+    },
+    "siso_no_clang_modules": {
+      "gn_args": {
+        "is_component_build": true,
+        "is_debug": true,
+        "symbol_level": 2,
+        "target_cpu": "arm64",
+        "target_os": "mac",
+        "use_clang_modules": false,
+        "use_reclient": false,
+        "use_remoteexec": true,
+        "use_siso": true
+      }
     }
   }
 }
\ No newline at end of file
diff --git a/infra/config/generated/builders/ci/Dawn Linux TSAN Release/gn-args.json b/infra/config/generated/builders/ci/Dawn Linux TSAN Release/gn-args.json
index 510b699..f879383 100644
--- a/infra/config/generated/builders/ci/Dawn Linux TSAN Release/gn-args.json
+++ b/infra/config/generated/builders/ci/Dawn Linux TSAN Release/gn-args.json
@@ -1,7 +1,6 @@
 {
   "gn_args": {
     "dawn_enable_opengles": true,
-    "dawn_use_swiftshader": true,
     "dcheck_always_on": true,
     "ffmpeg_branding": "Chrome",
     "is_component_build": false,
diff --git a/infra/config/generated/builders/ci/Dawn Linux TSAN Release/targets/chromium.dawn.json b/infra/config/generated/builders/ci/Dawn Linux TSAN Release/targets/chromium.dawn.json
index f21b37f..9e7937c 100644
--- a/infra/config/generated/builders/ci/Dawn Linux TSAN Release/targets/chromium.dawn.json
+++ b/infra/config/generated/builders/ci/Dawn Linux TSAN Release/targets/chromium.dawn.json
@@ -8,8 +8,7 @@
           "--exclusive-device-type-preference=discrete,integrated",
           "--test-launcher-retry-limit=0",
           "--test-launcher-batch-limit=512",
-          "--no-xvfb",
-          "--adapter-vendor-id=0x1AE0"
+          "--no-xvfb"
         ],
         "ci_only": true,
         "merge": {
@@ -20,15 +19,14 @@
         "name": "dawn_end2end_implicit_device_sync_tests",
         "swarming": {
           "dimensions": {
-            "cpu": "x86-64",
-            "gpu": "none",
-            "os": "Ubuntu-22.04",
+            "gpu": "10de:2184-440.100",
+            "os": "Ubuntu-18.04",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
           "io_timeout": 1800,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 4
+          "shards": 2
         },
         "test": "dawn_end2end_tests",
         "test_id_prefix": "ninja://third_party/dawn/src/dawn/tests:dawn_end2end_tests/"
@@ -40,8 +38,7 @@
           "--exclusive-device-type-preference=discrete,integrated",
           "--test-launcher-retry-limit=0",
           "--test-launcher-batch-limit=512",
-          "--no-xvfb",
-          "--adapter-vendor-id=0x1AE0"
+          "--no-xvfb"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -51,15 +48,14 @@
         "name": "dawn_end2end_skip_validation_tests",
         "swarming": {
           "dimensions": {
-            "cpu": "x86-64",
-            "gpu": "none",
-            "os": "Ubuntu-22.04",
+            "gpu": "10de:2184-440.100",
+            "os": "Ubuntu-18.04",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
           "io_timeout": 1800,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 4
+          "shards": 2
         },
         "test": "dawn_end2end_tests",
         "test_id_prefix": "ninja://third_party/dawn/src/dawn/tests:dawn_end2end_tests/"
@@ -70,8 +66,7 @@
           "--exclusive-device-type-preference=discrete,integrated",
           "--test-launcher-retry-limit=0",
           "--test-launcher-batch-limit=512",
-          "--no-xvfb",
-          "--adapter-vendor-id=0x1AE0"
+          "--no-xvfb"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -81,15 +76,14 @@
         "name": "dawn_end2end_tests",
         "swarming": {
           "dimensions": {
-            "cpu": "x86-64",
-            "gpu": "none",
-            "os": "Ubuntu-22.04",
+            "gpu": "10de:2184-440.100",
+            "os": "Ubuntu-18.04",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
           "io_timeout": 1800,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 4
+          "shards": 2
         },
         "test": "dawn_end2end_tests",
         "test_id_prefix": "ninja://third_party/dawn/src/dawn/tests:dawn_end2end_tests/"
@@ -101,8 +95,7 @@
           "--exclusive-device-type-preference=discrete,integrated",
           "--test-launcher-retry-limit=0",
           "--test-launcher-batch-limit=512",
-          "--no-xvfb",
-          "--adapter-vendor-id=0x1AE0"
+          "--no-xvfb"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -112,15 +105,14 @@
         "name": "dawn_end2end_wire_tests",
         "swarming": {
           "dimensions": {
-            "cpu": "x86-64",
-            "gpu": "none",
-            "os": "Ubuntu-22.04",
+            "gpu": "10de:2184-440.100",
+            "os": "Ubuntu-18.04",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
           "io_timeout": 1800,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 4
+          "shards": 2
         },
         "test": "dawn_end2end_tests",
         "test_id_prefix": "ninja://third_party/dawn/src/dawn/tests:dawn_end2end_tests/"
diff --git "a/infra/config/generated/builders/ci/GPU FYI Win x64 Builder \050dbg\051/targets/chromium.gpu.fyi.json" "b/infra/config/generated/builders/ci/GPU FYI Win x64 Builder \050dbg\051/targets/chromium.gpu.fyi.json"
index 29b081d..cffd6ab 100644
--- "a/infra/config/generated/builders/ci/GPU FYI Win x64 Builder \050dbg\051/targets/chromium.gpu.fyi.json"
+++ "b/infra/config/generated/builders/ci/GPU FYI Win x64 Builder \050dbg\051/targets/chromium.gpu.fyi.json"
@@ -481,7 +481,8 @@
           "hard_timeout": 1800,
           "idempotent": false,
           "io_timeout": 1800,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 2
         },
         "test": "telemetry_gpu_integration_test",
         "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
diff --git a/infra/config/generated/builders/ci/Linux FYI GPU TSAN Release/targets/chromium.gpu.fyi.json b/infra/config/generated/builders/ci/Linux FYI GPU TSAN Release/targets/chromium.gpu.fyi.json
index a4ce1c29..20ee03e 100644
--- a/infra/config/generated/builders/ci/Linux FYI GPU TSAN Release/targets/chromium.gpu.fyi.json
+++ b/infra/config/generated/builders/ci/Linux FYI GPU TSAN Release/targets/chromium.gpu.fyi.json
@@ -5,7 +5,8 @@
         "args": [
           "--use-cmd-decoder=passthrough",
           "--use-gl=angle",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/linux.swiftshader.tsan.gl_tests_passthrough.filter"
+          "--use-gpu-in-tests",
+          "--no-xvfb"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -16,9 +17,8 @@
         "swarming": {
           "containment_type": "AUTO",
           "dimensions": {
-            "cpu": "x86-64",
-            "gpu": "none",
-            "os": "Ubuntu-22.04",
+            "gpu": "10de:2184-440.100",
+            "os": "Ubuntu-18.04",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -31,6 +31,8 @@
       },
       {
         "args": [
+          "--use-gpu-in-tests",
+          "--no-xvfb",
           "--git-revision=${got_revision}"
         ],
         "merge": {
@@ -47,9 +49,8 @@
         "swarming": {
           "containment_type": "AUTO",
           "dimensions": {
-            "cpu": "x86-64",
-            "gpu": "none",
-            "os": "Ubuntu-22.04",
+            "gpu": "10de:2184-440.100",
+            "os": "Ubuntu-18.04",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
diff --git a/infra/config/generated/builders/ci/Linux TSan Builder/targets/chromium.memory.json b/infra/config/generated/builders/ci/Linux TSan Builder/targets/chromium.memory.json
index 72ab333..07118a16 100644
--- a/infra/config/generated/builders/ci/Linux TSan Builder/targets/chromium.memory.json
+++ b/infra/config/generated/builders/ci/Linux TSan Builder/targets/chromium.memory.json
@@ -781,7 +781,8 @@
         "args": [
           "--use-cmd-decoder=passthrough",
           "--test-launcher-print-test-stdio=always",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/linux.swiftshader.tsan.gl_tests_passthrough.filter"
+          "--use-gpu-in-tests",
+          "--no-xvfb"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -792,9 +793,8 @@
         "retry_only_failed_tests": true,
         "swarming": {
           "dimensions": {
-            "cpu": "x86-64",
-            "gpu": "none",
-            "os": "Ubuntu-22.04",
+            "gpu": "10de:2184-440.100",
+            "os": "Ubuntu-18.04",
             "pool": "chromium.tests.gpu"
           },
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
diff --git a/infra/config/generated/builders/ci/Linux TSan Tests/targets/chromium.memory.json b/infra/config/generated/builders/ci/Linux TSan Tests/targets/chromium.memory.json
index 72ab333..07118a16 100644
--- a/infra/config/generated/builders/ci/Linux TSan Tests/targets/chromium.memory.json
+++ b/infra/config/generated/builders/ci/Linux TSan Tests/targets/chromium.memory.json
@@ -781,7 +781,8 @@
         "args": [
           "--use-cmd-decoder=passthrough",
           "--test-launcher-print-test-stdio=always",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/linux.swiftshader.tsan.gl_tests_passthrough.filter"
+          "--use-gpu-in-tests",
+          "--no-xvfb"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -792,9 +793,8 @@
         "retry_only_failed_tests": true,
         "swarming": {
           "dimensions": {
-            "cpu": "x86-64",
-            "gpu": "none",
-            "os": "Ubuntu-22.04",
+            "gpu": "10de:2184-440.100",
+            "os": "Ubuntu-18.04",
             "pool": "chromium.tests.gpu"
           },
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
diff --git a/infra/config/generated/builders/ci/Mac Builder Next/targets/chromium.fyi.json b/infra/config/generated/builders/ci/Mac Builder Next/targets/chromium.fyi.json
index e9f23afb..5efef76 100644
--- a/infra/config/generated/builders/ci/Mac Builder Next/targets/chromium.fyi.json
+++ b/infra/config/generated/builders/ci/Mac Builder Next/targets/chromium.fyi.json
@@ -1443,6 +1443,7 @@
           "script": "//tools/perf/process_perf_results.py"
         },
         "module_name": "//components:components_perftests",
+        "module_scheme": "flat",
         "name": "components_perftests",
         "swarming": {
           "dimensions": {
diff --git a/infra/config/generated/builders/ci/Mac Builder/targets/chromium.mac.json b/infra/config/generated/builders/ci/Mac Builder/targets/chromium.mac.json
index 26d810f..fe411a46 100644
--- a/infra/config/generated/builders/ci/Mac Builder/targets/chromium.mac.json
+++ b/infra/config/generated/builders/ci/Mac Builder/targets/chromium.mac.json
@@ -1669,6 +1669,7 @@
           "script": "//tools/perf/process_perf_results.py"
         },
         "module_name": "//components:components_perftests",
+        "module_scheme": "flat",
         "name": "components_perftests",
         "swarming": {
           "dimensions": {
@@ -3563,6 +3564,7 @@
           "script": "//tools/perf/process_perf_results.py"
         },
         "module_name": "//components:components_perftests",
+        "module_scheme": "flat",
         "name": "components_perftests",
         "swarming": {
           "dimensions": {
@@ -5539,6 +5541,7 @@
           "script": "//tools/perf/process_perf_results.py"
         },
         "module_name": "//components:components_perftests",
+        "module_scheme": "flat",
         "name": "components_perftests",
         "swarming": {
           "dimensions": {
@@ -7542,6 +7545,7 @@
           "script": "//tools/perf/process_perf_results.py"
         },
         "module_name": "//components:components_perftests",
+        "module_scheme": "flat",
         "name": "components_perftests",
         "retry_only_failed_tests": true,
         "swarming": {
diff --git a/infra/config/generated/builders/ci/Mac12 Tests/targets/chromium.mac.json b/infra/config/generated/builders/ci/Mac12 Tests/targets/chromium.mac.json
index f38c92f..3e2d8ce 100644
--- a/infra/config/generated/builders/ci/Mac12 Tests/targets/chromium.mac.json
+++ b/infra/config/generated/builders/ci/Mac12 Tests/targets/chromium.mac.json
@@ -1641,6 +1641,7 @@
           "script": "//tools/perf/process_perf_results.py"
         },
         "module_name": "//components:components_perftests",
+        "module_scheme": "flat",
         "name": "components_perftests",
         "swarming": {
           "dimensions": {
diff --git a/infra/config/generated/builders/ci/Mac13 Tests/targets/chromium.mac.json b/infra/config/generated/builders/ci/Mac13 Tests/targets/chromium.mac.json
index 42bd8e41..b1d67dc 100644
--- a/infra/config/generated/builders/ci/Mac13 Tests/targets/chromium.mac.json
+++ b/infra/config/generated/builders/ci/Mac13 Tests/targets/chromium.mac.json
@@ -1640,6 +1640,7 @@
           "script": "//tools/perf/process_perf_results.py"
         },
         "module_name": "//components:components_perftests",
+        "module_scheme": "flat",
         "name": "components_perftests",
         "swarming": {
           "dimensions": {
diff --git "a/infra/config/generated/builders/ci/Win x64 Builder \050dbg\051/targets/chromium.win.json" "b/infra/config/generated/builders/ci/Win x64 Builder \050dbg\051/targets/chromium.win.json"
index b66ac1d..c9e52a42 100644
--- "a/infra/config/generated/builders/ci/Win x64 Builder \050dbg\051/targets/chromium.win.json"
+++ "b/infra/config/generated/builders/ci/Win x64 Builder \050dbg\051/targets/chromium.win.json"
@@ -1690,6 +1690,7 @@
           "script": "//tools/perf/process_perf_results.py"
         },
         "module_name": "//components:components_perftests",
+        "module_scheme": "flat",
         "name": "components_perftests",
         "swarming": {
           "dimensions": {
diff --git a/infra/config/generated/builders/ci/Win x64 Builder/targets/chromium.win.json b/infra/config/generated/builders/ci/Win x64 Builder/targets/chromium.win.json
index c4ee130..db2b860 100644
--- a/infra/config/generated/builders/ci/Win x64 Builder/targets/chromium.win.json
+++ b/infra/config/generated/builders/ci/Win x64 Builder/targets/chromium.win.json
@@ -2248,6 +2248,7 @@
           "script": "//tools/perf/process_perf_results.py"
         },
         "module_name": "//components:components_perftests",
+        "module_scheme": "flat",
         "name": "components_perftests",
         "retry_only_failed_tests": true,
         "swarming": {
@@ -4608,6 +4609,7 @@
           "script": "//tools/perf/process_perf_results.py"
         },
         "module_name": "//components:components_perftests",
+        "module_scheme": "flat",
         "name": "components_perftests",
         "retry_only_failed_tests": true,
         "swarming": {
diff --git "a/infra/config/generated/builders/ci/Win10 FYI x64 Debug \050NVIDIA\051/targets/chromium.gpu.fyi.json" "b/infra/config/generated/builders/ci/Win10 FYI x64 Debug \050NVIDIA\051/targets/chromium.gpu.fyi.json"
index f5a9c54..00b4989 100644
--- "a/infra/config/generated/builders/ci/Win10 FYI x64 Debug \050NVIDIA\051/targets/chromium.gpu.fyi.json"
+++ "b/infra/config/generated/builders/ci/Win10 FYI x64 Debug \050NVIDIA\051/targets/chromium.gpu.fyi.json"
@@ -480,7 +480,8 @@
           "hard_timeout": 1800,
           "idempotent": false,
           "io_timeout": 1800,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 2
         },
         "test": "telemetry_gpu_integration_test",
         "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
diff --git "a/infra/config/generated/builders/ci/Win10 Tests x64 \050dbg\051/targets/chromium.win.json" "b/infra/config/generated/builders/ci/Win10 Tests x64 \050dbg\051/targets/chromium.win.json"
index 3cc65b2..747160d 100644
--- "a/infra/config/generated/builders/ci/Win10 Tests x64 \050dbg\051/targets/chromium.win.json"
+++ "b/infra/config/generated/builders/ci/Win10 Tests x64 \050dbg\051/targets/chromium.win.json"
@@ -1685,6 +1685,7 @@
           "script": "//tools/perf/process_perf_results.py"
         },
         "module_name": "//components:components_perftests",
+        "module_scheme": "flat",
         "name": "components_perftests",
         "swarming": {
           "dimensions": {
diff --git a/infra/config/generated/builders/ci/Win10 Tests x64/targets/chromium.win.json b/infra/config/generated/builders/ci/Win10 Tests x64/targets/chromium.win.json
index 8ba5157..cd15bc6 100644
--- a/infra/config/generated/builders/ci/Win10 Tests x64/targets/chromium.win.json
+++ b/infra/config/generated/builders/ci/Win10 Tests x64/targets/chromium.win.json
@@ -2219,6 +2219,7 @@
           "script": "//tools/perf/process_perf_results.py"
         },
         "module_name": "//components:components_perftests",
+        "module_scheme": "flat",
         "name": "components_perftests",
         "retry_only_failed_tests": true,
         "swarming": {
diff --git a/infra/config/generated/builders/ci/Win11 Tests x64/targets/chromium.win.json b/infra/config/generated/builders/ci/Win11 Tests x64/targets/chromium.win.json
index 2afb775..b46a3c74 100644
--- a/infra/config/generated/builders/ci/Win11 Tests x64/targets/chromium.win.json
+++ b/infra/config/generated/builders/ci/Win11 Tests x64/targets/chromium.win.json
@@ -2039,6 +2039,7 @@
           "script": "//tools/perf/process_perf_results.py"
         },
         "module_name": "//components:components_perftests",
+        "module_scheme": "flat",
         "name": "components_perftests",
         "retry_only_failed_tests": true,
         "swarming": {
diff --git a/infra/config/generated/builders/ci/android-10-x86-nofieldtrial-rel/targets/chromium.android.json b/infra/config/generated/builders/ci/android-10-x86-nofieldtrial-rel/targets/chromium.android.json
index a98da81..da88a0c0 100644
--- a/infra/config/generated/builders/ci/android-10-x86-nofieldtrial-rel/targets/chromium.android.json
+++ b/infra/config/generated/builders/ci/android-10-x86-nofieldtrial-rel/targets/chromium.android.json
@@ -3793,6 +3793,7 @@
           "script": "//tools/perf/process_perf_results.py"
         },
         "module_name": "//components:components_perftests",
+        "module_scheme": "flat",
         "name": "components_perftests",
         "resultdb": {
           "enable": true,
diff --git a/infra/config/generated/builders/ci/android-10-x86-rel/targets/chromium.android.json b/infra/config/generated/builders/ci/android-10-x86-rel/targets/chromium.android.json
index da4aa63..f64a8bc 100644
--- a/infra/config/generated/builders/ci/android-10-x86-rel/targets/chromium.android.json
+++ b/infra/config/generated/builders/ci/android-10-x86-rel/targets/chromium.android.json
@@ -3861,6 +3861,7 @@
           "script": "//tools/perf/process_perf_results.py"
         },
         "module_name": "//components:components_perftests",
+        "module_scheme": "flat",
         "name": "components_perftests",
         "resultdb": {
           "enable": true,
diff --git a/infra/config/generated/builders/ci/android-canary-x64-fyi-rel/targets/chromium.android.fyi.json b/infra/config/generated/builders/ci/android-canary-x64-fyi-rel/targets/chromium.android.fyi.json
index 2dbf76c..78761a3 100644
--- a/infra/config/generated/builders/ci/android-canary-x64-fyi-rel/targets/chromium.android.fyi.json
+++ b/infra/config/generated/builders/ci/android-canary-x64-fyi-rel/targets/chromium.android.fyi.json
@@ -3995,6 +3995,7 @@
           "script": "//tools/perf/process_perf_results.py"
         },
         "module_name": "//components:components_perftests",
+        "module_scheme": "flat",
         "name": "components_perftests",
         "resultdb": {
           "enable": true,
diff --git a/infra/config/generated/builders/ci/android-x86-code-coverage/targets/chromium.coverage.json b/infra/config/generated/builders/ci/android-x86-code-coverage/targets/chromium.coverage.json
index 22e9e2b..87796f4 100644
--- a/infra/config/generated/builders/ci/android-x86-code-coverage/targets/chromium.coverage.json
+++ b/infra/config/generated/builders/ci/android-x86-code-coverage/targets/chromium.coverage.json
@@ -3911,6 +3911,7 @@
           "script": "//tools/perf/process_perf_results.py"
         },
         "module_name": "//components:components_perftests",
+        "module_scheme": "flat",
         "name": "components_perftests",
         "resultdb": {
           "enable": true,
diff --git a/infra/config/generated/builders/ci/linux-win-cross-rel/targets/chromium.win.json b/infra/config/generated/builders/ci/linux-win-cross-rel/targets/chromium.win.json
index b7161363..669e7c5 100644
--- a/infra/config/generated/builders/ci/linux-win-cross-rel/targets/chromium.win.json
+++ b/infra/config/generated/builders/ci/linux-win-cross-rel/targets/chromium.win.json
@@ -1951,6 +1951,7 @@
           "script": "//tools/perf/process_perf_results.py"
         },
         "module_name": "//components:components_perftests",
+        "module_scheme": "flat",
         "name": "components_perftests",
         "swarming": {
           "dimensions": {
diff --git a/infra/config/generated/builders/ci/mac-arm64-rel/targets/chromium.mac.json b/infra/config/generated/builders/ci/mac-arm64-rel/targets/chromium.mac.json
index 35a6621..d63b10f 100644
--- a/infra/config/generated/builders/ci/mac-arm64-rel/targets/chromium.mac.json
+++ b/infra/config/generated/builders/ci/mac-arm64-rel/targets/chromium.mac.json
@@ -1639,6 +1639,7 @@
           "script": "//tools/perf/process_perf_results.py"
         },
         "module_name": "//components:components_perftests",
+        "module_scheme": "flat",
         "name": "components_perftests",
         "swarming": {
           "dimensions": {
@@ -3449,6 +3450,7 @@
           "script": "//tools/perf/process_perf_results.py"
         },
         "module_name": "//components:components_perftests",
+        "module_scheme": "flat",
         "name": "components_perftests",
         "swarming": {
           "dimensions": {
@@ -5323,6 +5325,7 @@
           "script": "//tools/perf/process_perf_results.py"
         },
         "module_name": "//components:components_perftests",
+        "module_scheme": "flat",
         "name": "components_perftests",
         "swarming": {
           "dimensions": {
@@ -7543,6 +7546,7 @@
           "script": "//tools/perf/process_perf_results.py"
         },
         "module_name": "//components:components_perftests",
+        "module_scheme": "flat",
         "name": "components_perftests",
         "retry_only_failed_tests": true,
         "swarming": {
diff --git a/infra/config/generated/builders/ci/mac-osxbeta-rel/targets/chromium.fyi.json b/infra/config/generated/builders/ci/mac-osxbeta-rel/targets/chromium.fyi.json
index 0db1b296..f0d6011 100644
--- a/infra/config/generated/builders/ci/mac-osxbeta-rel/targets/chromium.fyi.json
+++ b/infra/config/generated/builders/ci/mac-osxbeta-rel/targets/chromium.fyi.json
@@ -1524,6 +1524,7 @@
           "script": "//tools/perf/process_perf_results.py"
         },
         "module_name": "//components:components_perftests",
+        "module_scheme": "flat",
         "name": "components_perftests",
         "swarming": {
           "dimensions": {
diff --git a/infra/config/generated/builders/ci/mac-rel-cft/targets/chromium.cft.json b/infra/config/generated/builders/ci/mac-rel-cft/targets/chromium.cft.json
index 332d6cd..c0aa48e 100644
--- a/infra/config/generated/builders/ci/mac-rel-cft/targets/chromium.cft.json
+++ b/infra/config/generated/builders/ci/mac-rel-cft/targets/chromium.cft.json
@@ -1613,6 +1613,7 @@
           "script": "//tools/perf/process_perf_results.py"
         },
         "module_name": "//components:components_perftests",
+        "module_scheme": "flat",
         "name": "components_perftests",
         "swarming": {
           "dimensions": {
diff --git a/infra/config/generated/builders/ci/mac12-arm64-rel-tests/targets/chromium.mac.json b/infra/config/generated/builders/ci/mac12-arm64-rel-tests/targets/chromium.mac.json
index 7d81f4e..fa9b0f0 100644
--- a/infra/config/generated/builders/ci/mac12-arm64-rel-tests/targets/chromium.mac.json
+++ b/infra/config/generated/builders/ci/mac12-arm64-rel-tests/targets/chromium.mac.json
@@ -1531,6 +1531,7 @@
           "script": "//tools/perf/process_perf_results.py"
         },
         "module_name": "//components:components_perftests",
+        "module_scheme": "flat",
         "name": "components_perftests",
         "swarming": {
           "dimensions": {
diff --git a/infra/config/generated/builders/ci/mac13-arm64-rel-tests/targets/chromium.mac.json b/infra/config/generated/builders/ci/mac13-arm64-rel-tests/targets/chromium.mac.json
index 5b1f9fc6..f78be25f 100644
--- a/infra/config/generated/builders/ci/mac13-arm64-rel-tests/targets/chromium.mac.json
+++ b/infra/config/generated/builders/ci/mac13-arm64-rel-tests/targets/chromium.mac.json
@@ -1613,6 +1613,7 @@
           "script": "//tools/perf/process_perf_results.py"
         },
         "module_name": "//components:components_perftests",
+        "module_scheme": "flat",
         "name": "components_perftests",
         "swarming": {
           "dimensions": {
diff --git a/infra/config/generated/builders/ci/mac14-arm64-rel-tests/targets/chromium.mac.json b/infra/config/generated/builders/ci/mac14-arm64-rel-tests/targets/chromium.mac.json
index 4b3a7e5..ae7a3981 100644
--- a/infra/config/generated/builders/ci/mac14-arm64-rel-tests/targets/chromium.mac.json
+++ b/infra/config/generated/builders/ci/mac14-arm64-rel-tests/targets/chromium.mac.json
@@ -1669,6 +1669,7 @@
           "script": "//tools/perf/process_perf_results.py"
         },
         "module_name": "//components:components_perftests",
+        "module_scheme": "flat",
         "name": "components_perftests",
         "swarming": {
           "dimensions": {
diff --git a/infra/config/generated/builders/ci/mac14-tests/targets/chromium.mac.json b/infra/config/generated/builders/ci/mac14-tests/targets/chromium.mac.json
index 7d88d3d..b9c416b 100644
--- a/infra/config/generated/builders/ci/mac14-tests/targets/chromium.mac.json
+++ b/infra/config/generated/builders/ci/mac14-tests/targets/chromium.mac.json
@@ -1662,6 +1662,7 @@
           "script": "//tools/perf/process_perf_results.py"
         },
         "module_name": "//components:components_perftests",
+        "module_scheme": "flat",
         "name": "components_perftests",
         "swarming": {
           "dimensions": {
diff --git a/infra/config/generated/builders/ci/mac15-arm64-rel-tests/targets/chromium.mac.json b/infra/config/generated/builders/ci/mac15-arm64-rel-tests/targets/chromium.mac.json
index 891bedc5..b8e8571 100644
--- a/infra/config/generated/builders/ci/mac15-arm64-rel-tests/targets/chromium.mac.json
+++ b/infra/config/generated/builders/ci/mac15-arm64-rel-tests/targets/chromium.mac.json
@@ -2015,6 +2015,7 @@
           "script": "//tools/perf/process_perf_results.py"
         },
         "module_name": "//components:components_perftests",
+        "module_scheme": "flat",
         "name": "components_perftests",
         "retry_only_failed_tests": true,
         "swarming": {
diff --git a/infra/config/generated/builders/ci/mac15-x64-rel-tests/targets/chromium.mac.json b/infra/config/generated/builders/ci/mac15-x64-rel-tests/targets/chromium.mac.json
index 8ee59b1..f98ba07b 100644
--- a/infra/config/generated/builders/ci/mac15-x64-rel-tests/targets/chromium.mac.json
+++ b/infra/config/generated/builders/ci/mac15-x64-rel-tests/targets/chromium.mac.json
@@ -1749,6 +1749,7 @@
           "script": "//tools/perf/process_perf_results.py"
         },
         "module_name": "//components:components_perftests",
+        "module_scheme": "flat",
         "name": "components_perftests",
         "retry_only_failed_tests": true,
         "swarming": {
diff --git a/infra/config/generated/builders/ci/win-arm64-dbg/targets/chromium.win.json b/infra/config/generated/builders/ci/win-arm64-dbg/targets/chromium.win.json
index 9233e2d2..78a25df 100644
--- a/infra/config/generated/builders/ci/win-arm64-dbg/targets/chromium.win.json
+++ b/infra/config/generated/builders/ci/win-arm64-dbg/targets/chromium.win.json
@@ -1904,6 +1904,7 @@
           "script": "//tools/perf/process_perf_results.py"
         },
         "module_name": "//components:components_perftests",
+        "module_scheme": "flat",
         "name": "components_perftests",
         "swarming": {
           "dimensions": {
diff --git a/infra/config/generated/builders/ci/win-arm64-rel/targets/chromium.win.json b/infra/config/generated/builders/ci/win-arm64-rel/targets/chromium.win.json
index bdbbfec..5cff7ee 100644
--- a/infra/config/generated/builders/ci/win-arm64-rel/targets/chromium.win.json
+++ b/infra/config/generated/builders/ci/win-arm64-rel/targets/chromium.win.json
@@ -2145,6 +2145,7 @@
           "script": "//tools/perf/process_perf_results.py"
         },
         "module_name": "//components:components_perftests",
+        "module_scheme": "flat",
         "name": "components_perftests",
         "retry_only_failed_tests": true,
         "swarming": {
diff --git a/infra/config/generated/builders/ci/win-rel-cft/targets/chromium.cft.json b/infra/config/generated/builders/ci/win-rel-cft/targets/chromium.cft.json
index 54c821f..e9735b46 100644
--- a/infra/config/generated/builders/ci/win-rel-cft/targets/chromium.cft.json
+++ b/infra/config/generated/builders/ci/win-rel-cft/targets/chromium.cft.json
@@ -1918,6 +1918,7 @@
           "script": "//tools/perf/process_perf_results.py"
         },
         "module_name": "//components:components_perftests",
+        "module_scheme": "flat",
         "name": "components_perftests",
         "swarming": {
           "dimensions": {
diff --git a/infra/config/generated/builders/ci/win10-rel-no-external-ip/targets/chromium.fyi.json b/infra/config/generated/builders/ci/win10-rel-no-external-ip/targets/chromium.fyi.json
index 581e771..4ccebd0 100644
--- a/infra/config/generated/builders/ci/win10-rel-no-external-ip/targets/chromium.fyi.json
+++ b/infra/config/generated/builders/ci/win10-rel-no-external-ip/targets/chromium.fyi.json
@@ -2204,6 +2204,7 @@
           "script": "//tools/perf/process_perf_results.py"
         },
         "module_name": "//components:components_perftests",
+        "module_scheme": "flat",
         "name": "components_perftests",
         "swarming": {
           "dimensions": {
diff --git a/infra/config/generated/builders/ci/win11-arm64-dbg-tests/targets/chromium.win.json b/infra/config/generated/builders/ci/win11-arm64-dbg-tests/targets/chromium.win.json
index b2b18b9..d886c3a8 100644
--- a/infra/config/generated/builders/ci/win11-arm64-dbg-tests/targets/chromium.win.json
+++ b/infra/config/generated/builders/ci/win11-arm64-dbg-tests/targets/chromium.win.json
@@ -1899,6 +1899,7 @@
           "script": "//tools/perf/process_perf_results.py"
         },
         "module_name": "//components:components_perftests",
+        "module_scheme": "flat",
         "name": "components_perftests",
         "swarming": {
           "dimensions": {
diff --git a/infra/config/generated/builders/ci/win11-arm64-rel-tests/targets/chromium.win.json b/infra/config/generated/builders/ci/win11-arm64-rel-tests/targets/chromium.win.json
index 33cbd7e..a23b881 100644
--- a/infra/config/generated/builders/ci/win11-arm64-rel-tests/targets/chromium.win.json
+++ b/infra/config/generated/builders/ci/win11-arm64-rel-tests/targets/chromium.win.json
@@ -2139,6 +2139,7 @@
           "script": "//tools/perf/process_perf_results.py"
         },
         "module_name": "//components:components_perftests",
+        "module_scheme": "flat",
         "name": "components_perftests",
         "retry_only_failed_tests": true,
         "swarming": {
diff --git a/infra/config/generated/builders/try/android-10-x86-nofieldtrial-rel/targets/chromium.android.json b/infra/config/generated/builders/try/android-10-x86-nofieldtrial-rel/targets/chromium.android.json
index a98da81..da88a0c0 100644
--- a/infra/config/generated/builders/try/android-10-x86-nofieldtrial-rel/targets/chromium.android.json
+++ b/infra/config/generated/builders/try/android-10-x86-nofieldtrial-rel/targets/chromium.android.json
@@ -3793,6 +3793,7 @@
           "script": "//tools/perf/process_perf_results.py"
         },
         "module_name": "//components:components_perftests",
+        "module_scheme": "flat",
         "name": "components_perftests",
         "resultdb": {
           "enable": true,
diff --git a/infra/config/generated/builders/try/android-10-x86-rel/targets/chromium.android.json b/infra/config/generated/builders/try/android-10-x86-rel/targets/chromium.android.json
index da4aa63..f64a8bc 100644
--- a/infra/config/generated/builders/try/android-10-x86-rel/targets/chromium.android.json
+++ b/infra/config/generated/builders/try/android-10-x86-rel/targets/chromium.android.json
@@ -3861,6 +3861,7 @@
           "script": "//tools/perf/process_perf_results.py"
         },
         "module_name": "//components:components_perftests",
+        "module_scheme": "flat",
         "name": "components_perftests",
         "resultdb": {
           "enable": true,
diff --git a/infra/config/generated/builders/try/android-canary-x64-fyi-rel/targets/chromium.android.fyi.json b/infra/config/generated/builders/try/android-canary-x64-fyi-rel/targets/chromium.android.fyi.json
index 2dbf76c..78761a3 100644
--- a/infra/config/generated/builders/try/android-canary-x64-fyi-rel/targets/chromium.android.fyi.json
+++ b/infra/config/generated/builders/try/android-canary-x64-fyi-rel/targets/chromium.android.fyi.json
@@ -3995,6 +3995,7 @@
           "script": "//tools/perf/process_perf_results.py"
         },
         "module_name": "//components:components_perftests",
+        "module_scheme": "flat",
         "name": "components_perftests",
         "resultdb": {
           "enable": true,
diff --git a/infra/config/generated/builders/try/android-x86-code-coverage/targets/chromium.coverage.json b/infra/config/generated/builders/try/android-x86-code-coverage/targets/chromium.coverage.json
index 22e9e2b..87796f4 100644
--- a/infra/config/generated/builders/try/android-x86-code-coverage/targets/chromium.coverage.json
+++ b/infra/config/generated/builders/try/android-x86-code-coverage/targets/chromium.coverage.json
@@ -3911,6 +3911,7 @@
           "script": "//tools/perf/process_perf_results.py"
         },
         "module_name": "//components:components_perftests",
+        "module_scheme": "flat",
         "name": "components_perftests",
         "resultdb": {
           "enable": true,
diff --git a/infra/config/generated/builders/try/android-x86-rel/targets/chromium.android.json b/infra/config/generated/builders/try/android-x86-rel/targets/chromium.android.json
index da4aa63..f64a8bc 100644
--- a/infra/config/generated/builders/try/android-x86-rel/targets/chromium.android.json
+++ b/infra/config/generated/builders/try/android-x86-rel/targets/chromium.android.json
@@ -3861,6 +3861,7 @@
           "script": "//tools/perf/process_perf_results.py"
         },
         "module_name": "//components:components_perftests",
+        "module_scheme": "flat",
         "name": "components_perftests",
         "resultdb": {
           "enable": true,
diff --git a/infra/config/generated/builders/try/dawn-try-linux-tsan-rel/gn-args.json b/infra/config/generated/builders/try/dawn-try-linux-tsan-rel/gn-args.json
index 510b699..f879383 100644
--- a/infra/config/generated/builders/try/dawn-try-linux-tsan-rel/gn-args.json
+++ b/infra/config/generated/builders/try/dawn-try-linux-tsan-rel/gn-args.json
@@ -1,7 +1,6 @@
 {
   "gn_args": {
     "dawn_enable_opengles": true,
-    "dawn_use_swiftshader": true,
     "dcheck_always_on": true,
     "ffmpeg_branding": "Chrome",
     "is_component_build": false,
diff --git a/infra/config/generated/builders/try/dawn-try-linux-tsan-rel/targets/chromium.dawn.json b/infra/config/generated/builders/try/dawn-try-linux-tsan-rel/targets/chromium.dawn.json
index f21b37f..9e7937c 100644
--- a/infra/config/generated/builders/try/dawn-try-linux-tsan-rel/targets/chromium.dawn.json
+++ b/infra/config/generated/builders/try/dawn-try-linux-tsan-rel/targets/chromium.dawn.json
@@ -8,8 +8,7 @@
           "--exclusive-device-type-preference=discrete,integrated",
           "--test-launcher-retry-limit=0",
           "--test-launcher-batch-limit=512",
-          "--no-xvfb",
-          "--adapter-vendor-id=0x1AE0"
+          "--no-xvfb"
         ],
         "ci_only": true,
         "merge": {
@@ -20,15 +19,14 @@
         "name": "dawn_end2end_implicit_device_sync_tests",
         "swarming": {
           "dimensions": {
-            "cpu": "x86-64",
-            "gpu": "none",
-            "os": "Ubuntu-22.04",
+            "gpu": "10de:2184-440.100",
+            "os": "Ubuntu-18.04",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
           "io_timeout": 1800,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 4
+          "shards": 2
         },
         "test": "dawn_end2end_tests",
         "test_id_prefix": "ninja://third_party/dawn/src/dawn/tests:dawn_end2end_tests/"
@@ -40,8 +38,7 @@
           "--exclusive-device-type-preference=discrete,integrated",
           "--test-launcher-retry-limit=0",
           "--test-launcher-batch-limit=512",
-          "--no-xvfb",
-          "--adapter-vendor-id=0x1AE0"
+          "--no-xvfb"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -51,15 +48,14 @@
         "name": "dawn_end2end_skip_validation_tests",
         "swarming": {
           "dimensions": {
-            "cpu": "x86-64",
-            "gpu": "none",
-            "os": "Ubuntu-22.04",
+            "gpu": "10de:2184-440.100",
+            "os": "Ubuntu-18.04",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
           "io_timeout": 1800,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 4
+          "shards": 2
         },
         "test": "dawn_end2end_tests",
         "test_id_prefix": "ninja://third_party/dawn/src/dawn/tests:dawn_end2end_tests/"
@@ -70,8 +66,7 @@
           "--exclusive-device-type-preference=discrete,integrated",
           "--test-launcher-retry-limit=0",
           "--test-launcher-batch-limit=512",
-          "--no-xvfb",
-          "--adapter-vendor-id=0x1AE0"
+          "--no-xvfb"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -81,15 +76,14 @@
         "name": "dawn_end2end_tests",
         "swarming": {
           "dimensions": {
-            "cpu": "x86-64",
-            "gpu": "none",
-            "os": "Ubuntu-22.04",
+            "gpu": "10de:2184-440.100",
+            "os": "Ubuntu-18.04",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
           "io_timeout": 1800,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 4
+          "shards": 2
         },
         "test": "dawn_end2end_tests",
         "test_id_prefix": "ninja://third_party/dawn/src/dawn/tests:dawn_end2end_tests/"
@@ -101,8 +95,7 @@
           "--exclusive-device-type-preference=discrete,integrated",
           "--test-launcher-retry-limit=0",
           "--test-launcher-batch-limit=512",
-          "--no-xvfb",
-          "--adapter-vendor-id=0x1AE0"
+          "--no-xvfb"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -112,15 +105,14 @@
         "name": "dawn_end2end_wire_tests",
         "swarming": {
           "dimensions": {
-            "cpu": "x86-64",
-            "gpu": "none",
-            "os": "Ubuntu-22.04",
+            "gpu": "10de:2184-440.100",
+            "os": "Ubuntu-18.04",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
           "io_timeout": 1800,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 4
+          "shards": 2
         },
         "test": "dawn_end2end_tests",
         "test_id_prefix": "ninja://third_party/dawn/src/dawn/tests:dawn_end2end_tests/"
diff --git a/infra/config/generated/builders/try/gpu-fyi-try-linux-nvidia-tsn/targets/chromium.gpu.fyi.json b/infra/config/generated/builders/try/gpu-fyi-try-linux-nvidia-tsn/targets/chromium.gpu.fyi.json
index a4ce1c29..20ee03e 100644
--- a/infra/config/generated/builders/try/gpu-fyi-try-linux-nvidia-tsn/targets/chromium.gpu.fyi.json
+++ b/infra/config/generated/builders/try/gpu-fyi-try-linux-nvidia-tsn/targets/chromium.gpu.fyi.json
@@ -5,7 +5,8 @@
         "args": [
           "--use-cmd-decoder=passthrough",
           "--use-gl=angle",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/linux.swiftshader.tsan.gl_tests_passthrough.filter"
+          "--use-gpu-in-tests",
+          "--no-xvfb"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -16,9 +17,8 @@
         "swarming": {
           "containment_type": "AUTO",
           "dimensions": {
-            "cpu": "x86-64",
-            "gpu": "none",
-            "os": "Ubuntu-22.04",
+            "gpu": "10de:2184-440.100",
+            "os": "Ubuntu-18.04",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -31,6 +31,8 @@
       },
       {
         "args": [
+          "--use-gpu-in-tests",
+          "--no-xvfb",
           "--git-revision=${got_revision}"
         ],
         "merge": {
@@ -47,9 +49,8 @@
         "swarming": {
           "containment_type": "AUTO",
           "dimensions": {
-            "cpu": "x86-64",
-            "gpu": "none",
-            "os": "Ubuntu-22.04",
+            "gpu": "10de:2184-440.100",
+            "os": "Ubuntu-18.04",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
diff --git a/infra/config/generated/builders/try/gpu-fyi-try-win10-nvidia-dbg-64/targets/chromium.gpu.fyi.json b/infra/config/generated/builders/try/gpu-fyi-try-win10-nvidia-dbg-64/targets/chromium.gpu.fyi.json
index 29b081d..cffd6ab 100644
--- a/infra/config/generated/builders/try/gpu-fyi-try-win10-nvidia-dbg-64/targets/chromium.gpu.fyi.json
+++ b/infra/config/generated/builders/try/gpu-fyi-try-win10-nvidia-dbg-64/targets/chromium.gpu.fyi.json
@@ -481,7 +481,8 @@
           "hard_timeout": 1800,
           "idempotent": false,
           "io_timeout": 1800,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 2
         },
         "test": "telemetry_gpu_integration_test",
         "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
diff --git a/infra/config/generated/builders/try/linux-win-cross-rel/targets/chromium.win.json b/infra/config/generated/builders/try/linux-win-cross-rel/targets/chromium.win.json
index b7161363..669e7c5 100644
--- a/infra/config/generated/builders/try/linux-win-cross-rel/targets/chromium.win.json
+++ b/infra/config/generated/builders/try/linux-win-cross-rel/targets/chromium.win.json
@@ -1951,6 +1951,7 @@
           "script": "//tools/perf/process_perf_results.py"
         },
         "module_name": "//components:components_perftests",
+        "module_scheme": "flat",
         "name": "components_perftests",
         "swarming": {
           "dimensions": {
diff --git a/infra/config/generated/builders/try/linux_chromium_tsan_rel_ng/targets/chromium.memory.json b/infra/config/generated/builders/try/linux_chromium_tsan_rel_ng/targets/chromium.memory.json
index 72ab333..07118a16 100644
--- a/infra/config/generated/builders/try/linux_chromium_tsan_rel_ng/targets/chromium.memory.json
+++ b/infra/config/generated/builders/try/linux_chromium_tsan_rel_ng/targets/chromium.memory.json
@@ -781,7 +781,8 @@
         "args": [
           "--use-cmd-decoder=passthrough",
           "--test-launcher-print-test-stdio=always",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/linux.swiftshader.tsan.gl_tests_passthrough.filter"
+          "--use-gpu-in-tests",
+          "--no-xvfb"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -792,9 +793,8 @@
         "retry_only_failed_tests": true,
         "swarming": {
           "dimensions": {
-            "cpu": "x86-64",
-            "gpu": "none",
-            "os": "Ubuntu-22.04",
+            "gpu": "10de:2184-440.100",
+            "os": "Ubuntu-18.04",
             "pool": "chromium.tests.gpu"
           },
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
diff --git a/infra/config/generated/builders/try/mac-builder-next/targets/chromium.fyi.json b/infra/config/generated/builders/try/mac-builder-next/targets/chromium.fyi.json
index e9f23afb..5efef76 100644
--- a/infra/config/generated/builders/try/mac-builder-next/targets/chromium.fyi.json
+++ b/infra/config/generated/builders/try/mac-builder-next/targets/chromium.fyi.json
@@ -1443,6 +1443,7 @@
           "script": "//tools/perf/process_perf_results.py"
         },
         "module_name": "//components:components_perftests",
+        "module_scheme": "flat",
         "name": "components_perftests",
         "swarming": {
           "dimensions": {
diff --git a/infra/config/generated/builders/try/mac-osxbeta-rel/targets/chromium.fyi.json b/infra/config/generated/builders/try/mac-osxbeta-rel/targets/chromium.fyi.json
index 0db1b296..f0d6011 100644
--- a/infra/config/generated/builders/try/mac-osxbeta-rel/targets/chromium.fyi.json
+++ b/infra/config/generated/builders/try/mac-osxbeta-rel/targets/chromium.fyi.json
@@ -1524,6 +1524,7 @@
           "script": "//tools/perf/process_perf_results.py"
         },
         "module_name": "//components:components_perftests",
+        "module_scheme": "flat",
         "name": "components_perftests",
         "swarming": {
           "dimensions": {
diff --git a/infra/config/generated/builders/try/mac-rel-cft/targets/chromium.cft.json b/infra/config/generated/builders/try/mac-rel-cft/targets/chromium.cft.json
index 332d6cd..c0aa48e 100644
--- a/infra/config/generated/builders/try/mac-rel-cft/targets/chromium.cft.json
+++ b/infra/config/generated/builders/try/mac-rel-cft/targets/chromium.cft.json
@@ -1613,6 +1613,7 @@
           "script": "//tools/perf/process_perf_results.py"
         },
         "module_name": "//components:components_perftests",
+        "module_scheme": "flat",
         "name": "components_perftests",
         "swarming": {
           "dimensions": {
diff --git a/infra/config/generated/builders/try/mac-rel/targets/chromium.mac.json b/infra/config/generated/builders/try/mac-rel/targets/chromium.mac.json
index 8edf567e..88d8974f 100644
--- a/infra/config/generated/builders/try/mac-rel/targets/chromium.mac.json
+++ b/infra/config/generated/builders/try/mac-rel/targets/chromium.mac.json
@@ -1777,6 +1777,7 @@
           "script": "//tools/perf/process_perf_results.py"
         },
         "module_name": "//components:components_perftests",
+        "module_scheme": "flat",
         "name": "components_perftests",
         "retry_only_failed_tests": true,
         "swarming": {
diff --git a/infra/config/generated/builders/try/mac12-arm64-rel/targets/chromium.mac.json b/infra/config/generated/builders/try/mac12-arm64-rel/targets/chromium.mac.json
index 6724be5..33a9ef3e 100644
--- a/infra/config/generated/builders/try/mac12-arm64-rel/targets/chromium.mac.json
+++ b/infra/config/generated/builders/try/mac12-arm64-rel/targets/chromium.mac.json
@@ -1536,6 +1536,7 @@
           "script": "//tools/perf/process_perf_results.py"
         },
         "module_name": "//components:components_perftests",
+        "module_scheme": "flat",
         "name": "components_perftests",
         "swarming": {
           "dimensions": {
diff --git a/infra/config/generated/builders/try/mac12-tests/targets/chromium.mac.json b/infra/config/generated/builders/try/mac12-tests/targets/chromium.mac.json
index bca64c4..16c4306 100644
--- a/infra/config/generated/builders/try/mac12-tests/targets/chromium.mac.json
+++ b/infra/config/generated/builders/try/mac12-tests/targets/chromium.mac.json
@@ -1669,6 +1669,7 @@
           "script": "//tools/perf/process_perf_results.py"
         },
         "module_name": "//components:components_perftests",
+        "module_scheme": "flat",
         "name": "components_perftests",
         "swarming": {
           "dimensions": {
diff --git a/infra/config/generated/builders/try/mac13-arm64-rel/targets/chromium.mac.json b/infra/config/generated/builders/try/mac13-arm64-rel/targets/chromium.mac.json
index 579e9ac8..60753611 100644
--- a/infra/config/generated/builders/try/mac13-arm64-rel/targets/chromium.mac.json
+++ b/infra/config/generated/builders/try/mac13-arm64-rel/targets/chromium.mac.json
@@ -1618,6 +1618,7 @@
           "script": "//tools/perf/process_perf_results.py"
         },
         "module_name": "//components:components_perftests",
+        "module_scheme": "flat",
         "name": "components_perftests",
         "swarming": {
           "dimensions": {
diff --git a/infra/config/generated/builders/try/mac13-tests/targets/chromium.mac.json b/infra/config/generated/builders/try/mac13-tests/targets/chromium.mac.json
index 0af9b5017..ae79074 100644
--- a/infra/config/generated/builders/try/mac13-tests/targets/chromium.mac.json
+++ b/infra/config/generated/builders/try/mac13-tests/targets/chromium.mac.json
@@ -1668,6 +1668,7 @@
           "script": "//tools/perf/process_perf_results.py"
         },
         "module_name": "//components:components_perftests",
+        "module_scheme": "flat",
         "name": "components_perftests",
         "swarming": {
           "dimensions": {
diff --git a/infra/config/generated/builders/try/mac14-arm64-rel/targets/chromium.mac.json b/infra/config/generated/builders/try/mac14-arm64-rel/targets/chromium.mac.json
index 9d3a7083..e8bd478 100644
--- a/infra/config/generated/builders/try/mac14-arm64-rel/targets/chromium.mac.json
+++ b/infra/config/generated/builders/try/mac14-arm64-rel/targets/chromium.mac.json
@@ -1674,6 +1674,7 @@
           "script": "//tools/perf/process_perf_results.py"
         },
         "module_name": "//components:components_perftests",
+        "module_scheme": "flat",
         "name": "components_perftests",
         "swarming": {
           "dimensions": {
diff --git a/infra/config/generated/builders/try/mac14-tests/targets/chromium.mac.json b/infra/config/generated/builders/try/mac14-tests/targets/chromium.mac.json
index 51fe750a..acc3dc56 100644
--- a/infra/config/generated/builders/try/mac14-tests/targets/chromium.mac.json
+++ b/infra/config/generated/builders/try/mac14-tests/targets/chromium.mac.json
@@ -1690,6 +1690,7 @@
           "script": "//tools/perf/process_perf_results.py"
         },
         "module_name": "//components:components_perftests",
+        "module_scheme": "flat",
         "name": "components_perftests",
         "swarming": {
           "dimensions": {
diff --git a/infra/config/generated/builders/try/mac15-arm64-rel/targets/chromium.mac.json b/infra/config/generated/builders/try/mac15-arm64-rel/targets/chromium.mac.json
index b1e5d2fe..4447cef 100644
--- a/infra/config/generated/builders/try/mac15-arm64-rel/targets/chromium.mac.json
+++ b/infra/config/generated/builders/try/mac15-arm64-rel/targets/chromium.mac.json
@@ -2020,6 +2020,7 @@
           "script": "//tools/perf/process_perf_results.py"
         },
         "module_name": "//components:components_perftests",
+        "module_scheme": "flat",
         "name": "components_perftests",
         "retry_only_failed_tests": true,
         "swarming": {
diff --git a/infra/config/generated/builders/try/mac15-x64-rel-tests/targets/chromium.mac.json b/infra/config/generated/builders/try/mac15-x64-rel-tests/targets/chromium.mac.json
index 8edf567e..88d8974f 100644
--- a/infra/config/generated/builders/try/mac15-x64-rel-tests/targets/chromium.mac.json
+++ b/infra/config/generated/builders/try/mac15-x64-rel-tests/targets/chromium.mac.json
@@ -1777,6 +1777,7 @@
           "script": "//tools/perf/process_perf_results.py"
         },
         "module_name": "//components:components_perftests",
+        "module_scheme": "flat",
         "name": "components_perftests",
         "retry_only_failed_tests": true,
         "swarming": {
diff --git a/infra/config/generated/builders/try/mac_chromium_compile_rel_ng/targets/chromium.mac.json b/infra/config/generated/builders/try/mac_chromium_compile_rel_ng/targets/chromium.mac.json
index 26d810f..fe411a46 100644
--- a/infra/config/generated/builders/try/mac_chromium_compile_rel_ng/targets/chromium.mac.json
+++ b/infra/config/generated/builders/try/mac_chromium_compile_rel_ng/targets/chromium.mac.json
@@ -1669,6 +1669,7 @@
           "script": "//tools/perf/process_perf_results.py"
         },
         "module_name": "//components:components_perftests",
+        "module_scheme": "flat",
         "name": "components_perftests",
         "swarming": {
           "dimensions": {
@@ -3563,6 +3564,7 @@
           "script": "//tools/perf/process_perf_results.py"
         },
         "module_name": "//components:components_perftests",
+        "module_scheme": "flat",
         "name": "components_perftests",
         "swarming": {
           "dimensions": {
@@ -5539,6 +5541,7 @@
           "script": "//tools/perf/process_perf_results.py"
         },
         "module_name": "//components:components_perftests",
+        "module_scheme": "flat",
         "name": "components_perftests",
         "swarming": {
           "dimensions": {
@@ -7542,6 +7545,7 @@
           "script": "//tools/perf/process_perf_results.py"
         },
         "module_name": "//components:components_perftests",
+        "module_scheme": "flat",
         "name": "components_perftests",
         "retry_only_failed_tests": true,
         "swarming": {
diff --git a/infra/config/generated/builders/try/win-arm64-compile-dbg/targets/chromium.win.json b/infra/config/generated/builders/try/win-arm64-compile-dbg/targets/chromium.win.json
index 9233e2d2..78a25df 100644
--- a/infra/config/generated/builders/try/win-arm64-compile-dbg/targets/chromium.win.json
+++ b/infra/config/generated/builders/try/win-arm64-compile-dbg/targets/chromium.win.json
@@ -1904,6 +1904,7 @@
           "script": "//tools/perf/process_perf_results.py"
         },
         "module_name": "//components:components_perftests",
+        "module_scheme": "flat",
         "name": "components_perftests",
         "swarming": {
           "dimensions": {
diff --git a/infra/config/generated/builders/try/win-arm64-dbg/targets/chromium.win.json b/infra/config/generated/builders/try/win-arm64-dbg/targets/chromium.win.json
index 9233e2d2..78a25df 100644
--- a/infra/config/generated/builders/try/win-arm64-dbg/targets/chromium.win.json
+++ b/infra/config/generated/builders/try/win-arm64-dbg/targets/chromium.win.json
@@ -1904,6 +1904,7 @@
           "script": "//tools/perf/process_perf_results.py"
         },
         "module_name": "//components:components_perftests",
+        "module_scheme": "flat",
         "name": "components_perftests",
         "swarming": {
           "dimensions": {
diff --git a/infra/config/generated/builders/try/win-arm64-rel/targets/chromium.win.json b/infra/config/generated/builders/try/win-arm64-rel/targets/chromium.win.json
index bdbbfec..5cff7ee 100644
--- a/infra/config/generated/builders/try/win-arm64-rel/targets/chromium.win.json
+++ b/infra/config/generated/builders/try/win-arm64-rel/targets/chromium.win.json
@@ -2145,6 +2145,7 @@
           "script": "//tools/perf/process_perf_results.py"
         },
         "module_name": "//components:components_perftests",
+        "module_scheme": "flat",
         "name": "components_perftests",
         "retry_only_failed_tests": true,
         "swarming": {
diff --git a/infra/config/generated/builders/try/win-rel-cft/targets/chromium.cft.json b/infra/config/generated/builders/try/win-rel-cft/targets/chromium.cft.json
index 54c821f..e9735b46 100644
--- a/infra/config/generated/builders/try/win-rel-cft/targets/chromium.cft.json
+++ b/infra/config/generated/builders/try/win-rel-cft/targets/chromium.cft.json
@@ -1918,6 +1918,7 @@
           "script": "//tools/perf/process_perf_results.py"
         },
         "module_name": "//components:components_perftests",
+        "module_scheme": "flat",
         "name": "components_perftests",
         "swarming": {
           "dimensions": {
diff --git a/infra/config/generated/builders/try/win-rel/targets/chromium.win.json b/infra/config/generated/builders/try/win-rel/targets/chromium.win.json
index d0f0168..2900f94 100644
--- a/infra/config/generated/builders/try/win-rel/targets/chromium.win.json
+++ b/infra/config/generated/builders/try/win-rel/targets/chromium.win.json
@@ -2248,6 +2248,7 @@
           "script": "//tools/perf/process_perf_results.py"
         },
         "module_name": "//components:components_perftests",
+        "module_scheme": "flat",
         "name": "components_perftests",
         "retry_only_failed_tests": true,
         "swarming": {
diff --git a/infra/config/generated/builders/try/win10-dbg/targets/chromium.win.json b/infra/config/generated/builders/try/win10-dbg/targets/chromium.win.json
index b66ac1d..c9e52a42 100644
--- a/infra/config/generated/builders/try/win10-dbg/targets/chromium.win.json
+++ b/infra/config/generated/builders/try/win10-dbg/targets/chromium.win.json
@@ -1690,6 +1690,7 @@
           "script": "//tools/perf/process_perf_results.py"
         },
         "module_name": "//components:components_perftests",
+        "module_scheme": "flat",
         "name": "components_perftests",
         "swarming": {
           "dimensions": {
diff --git a/infra/config/generated/builders/try/win11-rel/targets/chromium.win.json b/infra/config/generated/builders/try/win11-rel/targets/chromium.win.json
index 2014842..4374aa7 100644
--- a/infra/config/generated/builders/try/win11-rel/targets/chromium.win.json
+++ b/infra/config/generated/builders/try/win11-rel/targets/chromium.win.json
@@ -2068,6 +2068,7 @@
           "script": "//tools/perf/process_perf_results.py"
         },
         "module_name": "//components:components_perftests",
+        "module_scheme": "flat",
         "name": "components_perftests",
         "retry_only_failed_tests": true,
         "swarming": {
diff --git a/infra/config/generated/luci/cr-buildbucket.cfg b/infra/config/generated/luci/cr-buildbucket.cfg
index 0c81a73..ee2ff7a 100644
--- a/infra/config/generated/luci/cr-buildbucket.cfg
+++ b/infra/config/generated/luci/cr-buildbucket.cfg
@@ -262,7 +262,7 @@
         key: "chromium.use_per_builder_build_dir_name"
         value: 100
       }
-      description_html: "This builder measures build performance for Android developer builds, by simulating developer build scenarios on a high spec bot.<br>Build stats are shown in <a href=\"http://shortn/_gaAdI3x6o6\">http://shortn/_gaAdI3x6o6</a>.<br/>Builder owner: <a href=mailto:chrome-build-team@google.com>chrome-build-team@google.com</a>"
+      description_html: "This builder measures build performance for Android developer builds, by simulating developer build scenarios on a high spec bot.<br>Build stats are shown in <a href=\"http://shortn/_gaAdI3x6o6\">http://shortn/_gaAdI3x6o6</a>.<br>This is also used to compare build performance between w/ and w/o clang modules.<br/>Builder owner: <a href=mailto:chrome-build-team@google.com>chrome-build-team@google.com</a>"
       contact_team_email: "chrome-build-team@google.com"
       custom_metric_definitions {
         name: "/chrome/infra/browser/builds/cached_count"
@@ -462,7 +462,7 @@
         key: "chromium.use_per_builder_build_dir_name"
         value: 100
       }
-      description_html: "This builder measures build performance for iOS developer builds, by simulating developer build scenarios on a bot.<br>Build stats are shown in <a href=\"http://shortn/_gaAdI3x6o6\">http://shortn/_gaAdI3x6o6</a>.<br/>Builder owner: <a href=mailto:chrome-build-team@google.com>chrome-build-team@google.com</a>"
+      description_html: "This builder measures build performance for iOS developer builds, by simulating developer build scenarios on a bot.<br>Build stats are shown in <a href=\"http://shortn/_gaAdI3x6o6\">http://shortn/_gaAdI3x6o6</a>.<br>This is also used to compare build performance between w/ and w/o clang modules.<br/>Builder owner: <a href=mailto:chrome-build-team@google.com>chrome-build-team@google.com</a>"
       contact_team_email: "chrome-build-team@google.com"
       custom_metric_definitions {
         name: "/chrome/infra/browser/builds/cached_count"
@@ -667,7 +667,7 @@
         key: "chromium.use_per_builder_build_dir_name"
         value: 100
       }
-      description_html: "This builder measures build performance for Linux developer builds, by simulating developer build scenarios on a high spec bot.<br>Build stats are shown in <a href=\"http://shortn/_gaAdI3x6o6\">http://shortn/_gaAdI3x6o6</a>.<br/>Builder owner: <a href=mailto:chrome-build-team@google.com>chrome-build-team@google.com</a>"
+      description_html: "This builder measures build performance for Linux developer builds, by simulating developer build scenarios on a high spec bot.<br>Build stats are shown in <a href=\"http://shortn/_gaAdI3x6o6\">http://shortn/_gaAdI3x6o6</a>.<br>This is also used to compare build performance between w/ and w/o clang modules.<br/>Builder owner: <a href=mailto:chrome-build-team@google.com>chrome-build-team@google.com</a>"
       contact_team_email: "chrome-build-team@google.com"
       custom_metric_definitions {
         name: "/chrome/infra/browser/builds/cached_count"
@@ -1190,7 +1190,7 @@
         key: "chromium.use_per_builder_build_dir_name"
         value: 100
       }
-      description_html: "This builder measures build performance for Mac developer builds, by simulating developer build scenarios on a bot.<br>Build stats are shown in <a href=\"http://shortn/_gaAdI3x6o6\">http://shortn/_gaAdI3x6o6</a>.<br/>Builder owner: <a href=mailto:chrome-build-team@google.com>chrome-build-team@google.com</a>"
+      description_html: "This builder measures build performance for Mac developer builds, by simulating developer build scenarios on a bot.<br>Build stats are shown in <a href=\"http://shortn/_gaAdI3x6o6\">http://shortn/_gaAdI3x6o6</a>.<br>This is also used to compare build performance between w/ and w/o clang modules.<br/>Builder owner: <a href=mailto:chrome-build-team@google.com>chrome-build-team@google.com</a>"
       contact_team_email: "chrome-build-team@google.com"
       custom_metric_definitions {
         name: "/chrome/infra/browser/builds/cached_count"
@@ -1452,7 +1452,7 @@
         key: "chromium.use_per_builder_build_dir_name"
         value: 100
       }
-      description_html: "This builder measures build performance for Windows developer builds, by simulating developer build scenarios on a high spec bot.<br>Build stats are shown in <a href=\"http://shortn/_gaAdI3x6o6\">http://shortn/_gaAdI3x6o6</a>.<br/>Builder owner: <a href=mailto:chrome-build-team@google.com>chrome-build-team@google.com</a>"
+      description_html: "This builder measures build performance for Windows developer builds, by simulating developer build scenarios on a high spec bot.<br>Build stats are shown in <a href=\"http://shortn/_gaAdI3x6o6\">http://shortn/_gaAdI3x6o6</a>.<br>This is also used to compare build performance between w/ and w/o clang modules.<br/>Builder owner: <a href=mailto:chrome-build-team@google.com>chrome-build-team@google.com</a>"
       contact_team_email: "chrome-build-team@google.com"
       custom_metric_definitions {
         name: "/chrome/infra/browser/builds/cached_count"
@@ -7562,7 +7562,7 @@
           use_invocation_timestamp: true
         }
       }
-      description_html: "Runs ToT Dawn tests on SwiftShader with TSan enabled<br/>This builder is mirrored by any of the following try builders:<br/><ul><li><a href=\"https://ci.chromium.org/p/chromium/builders/try/dawn-try-linux-tsan-rel\">dawn-try-linux-tsan-rel</a></li></ul><br/>Builder owner: <a href=mailto:chrome-gpu-infra@google.com>chrome-gpu-infra@google.com</a>"
+      description_html: "Runs ToT Dawn tests on stable Linux/NVIDIA GTX 1660 configs with TSan enabled<br/>This builder is mirrored by any of the following try builders:<br/><ul><li><a href=\"https://ci.chromium.org/p/chromium/builders/try/dawn-try-linux-tsan-rel\">dawn-try-linux-tsan-rel</a></li></ul><br/>Builder owner: <a href=mailto:chrome-gpu-infra@google.com>chrome-gpu-infra@google.com</a>"
       shadow_builder_adjustments {
         service_account: "chromium-try-gpu-builder@chops-service-accounts.iam.gserviceaccount.com"
         pool: "luci.chromium.try"
@@ -97770,7 +97770,7 @@
           use_invocation_timestamp: true
         }
       }
-      description_html: "Runs ToT Dawn tests on SwiftShader with TSan enabled<br/><br/>This builder mirrors the following CI builders:<br/><ul><li><a href=\"https://ci.chromium.org/p/chromium/builders/ci/Dawn Linux TSAN Release\">Dawn Linux TSAN Release</a></li></ul><br/>Builder owner: <a href=mailto:chrome-gpu-infra@google.com>chrome-gpu-infra@google.com</a><br/><h3>NOTE: This builder only runs 1 max concurrent builds.</h3>"
+      description_html: "Runs ToT Dawn tests on stable Linux/NVIDIA GTX 1660 configs with TSan enabled<br/><br/>This builder mirrors the following CI builders:<br/><ul><li><a href=\"https://ci.chromium.org/p/chromium/builders/ci/Dawn Linux TSAN Release\">Dawn Linux TSAN Release</a></li></ul><br/>Builder owner: <a href=mailto:chrome-gpu-infra@google.com>chrome-gpu-infra@google.com</a><br/><h3>NOTE: This builder only runs 1 max concurrent builds.</h3>"
       contact_team_email: "chrome-gpu-infra@google.com"
       custom_metric_definitions {
         name: "/chrome/infra/browser/builds/alerts_enabled_count"
diff --git a/infra/config/generated/testing/gn_isolate_map.pyl b/infra/config/generated/testing/gn_isolate_map.pyl
index cdc5b57..010b92781 100644
--- a/infra/config/generated/testing/gn_isolate_map.pyl
+++ b/infra/config/generated/testing/gn_isolate_map.pyl
@@ -787,6 +787,7 @@
   "components_perftests": {
     "label": "//components:components_perftests",
     "type": "script",
+    "module_scheme": "flat",
     "script": "//testing/scripts/run_performance_tests.py",
     "skip_usage_check": True,
     "args": [
diff --git a/infra/config/generated/testing/mixins.pyl b/infra/config/generated/testing/mixins.pyl
index ed6e9511..9643879a 100644
--- a/infra/config/generated/testing/mixins.pyl
+++ b/infra/config/generated/testing/mixins.pyl
@@ -582,6 +582,16 @@
       },
     },
   },
+  'linux_nvidia_gtx_1660_obsolete': {
+    'fail_if_unused': False,
+    'swarming': {
+      'dimensions': {
+        'gpu': '10de:2184-440.100',
+        'os': 'Ubuntu-18.04',
+        'pool': 'chromium.tests.gpu',
+      },
+    },
+  },
   'linux_nvidia_gtx_1660_stable': {
     'fail_if_unused': False,
     'swarming': {
diff --git a/infra/config/gn_args/gn_args.star b/infra/config/gn_args/gn_args.star
index d6275b5..6dd1612 100644
--- a/infra/config/gn_args/gn_args.star
+++ b/infra/config/gn_args/gn_args.star
@@ -490,13 +490,6 @@
 )
 
 gn_args.config(
-    name = "dawn_use_swiftshader",
-    args = {
-        "dawn_use_swiftshader": True,
-    },
-)
-
-gn_args.config(
     name = "dcheck_always_on",
     args = {
         "dcheck_always_on": True,
diff --git a/infra/config/migration/lib/migrate_targets.py b/infra/config/migration/lib/migrate_targets.py
index 8c68eb9..a0edd37 100755
--- a/infra/config/migration/lib/migrate_targets.py
+++ b/infra/config/migration/lib/migrate_targets.py
@@ -113,7 +113,6 @@
 
 _BROWSER_CONFIG_MAPPING = {
     'android-chromium': 'ANDROID_CHROMIUM',
-    'android-chromium-monochrome': 'ANDROID_CHROMIUM_MONOCHROME',
     'android-webview': 'ANDROID_WEBVIEW',
     'cros-chrome': 'CROS_CHROME',
     'debug': 'DEBUG',
diff --git a/infra/config/subprojects/build/build.star b/infra/config/subprojects/build/build.star
index 7899229..2a91761 100644
--- a/infra/config/subprojects/build/build.star
+++ b/infra/config/subprojects/build/build.star
@@ -575,7 +575,8 @@
         },
     }
     return ci.builder(
-        description_html = description_html + "<br>Build stats are shown in <a href=\"http://shortn/_gaAdI3x6o6\">http://shortn/_gaAdI3x6o6</a>.",
+        description_html = description_html + "<br>Build stats are shown in <a href=\"http://shortn/_gaAdI3x6o6\">http://shortn/_gaAdI3x6o6</a>." +
+                           "<br>This is also used to compare build performance between w/ and w/o clang modules.",
         executable = "recipe:chrome_build/build_perf_developer",
         # developer build usually interactive and not-batch build.
         siso_disable_batch_mode = True,
@@ -616,6 +617,7 @@
     gn_args = {
         "ninja": gn_args.config(configs = ["android_developer", "android_fastbuild", "remoteexec", "no_siso", "reclient"]),
         "siso_native": gn_args.config(configs = ["android_developer", "android_fastbuild", "remoteexec", "no_reclient"]),
+        "siso_no_clang_modules": gn_args.config(configs = ["android_developer", "android_fastbuild", "remoteexec", "no_reclient", "no_clang_modules"]),
     },
     os = os.LINUX_DEFAULT,
     console_view_entry = consoles.console_view_entry(
@@ -649,6 +651,7 @@
     gn_args = {
         "ninja": gn_args.config(configs = ["developer", "remoteexec", "no_siso", "reclient", "linux", "x64"]),
         "siso_native": gn_args.config(configs = ["developer", "remoteexec", "no_reclient", "linux", "x64"]),
+        "siso_no_clang_modules": gn_args.config(configs = ["developer", "remoteexec", "no_reclient", "linux", "x64", "no_clang_modules"]),
     },
     os = os.LINUX_DEFAULT,
     console_view_entry = consoles.console_view_entry(
@@ -682,6 +685,7 @@
     gn_args = {
         "ninja": gn_args.config(configs = ["developer", "remoteexec", "no_siso", "reclient", "win", "x64"]),
         "siso_native": gn_args.config(configs = ["developer", "remoteexec", "no_reclient", "win", "x64"]),
+        # TODO(https://crbug.com/425537956): Add no clang modules build config after enabling clang modules on Windows.
     },
     os = os.WINDOWS_DEFAULT,
     console_view_entry = consoles.console_view_entry(
@@ -715,6 +719,7 @@
     gn_args = {
         "ninja": gn_args.config(configs = ["developer", "remoteexec", "no_siso", "reclient", "mac", "arm64"]),
         "siso_native": gn_args.config(configs = ["developer", "remoteexec", "no_reclient", "mac", "arm64"]),
+        "siso_no_clang_modules": gn_args.config(configs = ["developer", "remoteexec", "no_reclient", "mac", "arm64", "no_clang_modules"]),
     },
     os = os.MAC_DEFAULT,
     cpu = cpu.ARM64,
@@ -753,6 +758,7 @@
     gn_args = {
         "ninja": gn_args.config(configs = ["ios_developer", "remoteexec", "no_siso", "reclient", "arm64"]),
         "siso_native": gn_args.config(configs = ["ios_developer", "remoteexec", "no_reclient", "arm64"]),
+        "siso_no_clang_modules": gn_args.config(configs = ["ios_developer", "remoteexec", "no_reclient", "arm64", "no_clang_modules"]),
     },
     os = os.MAC_DEFAULT,
     cpu = cpu.ARM64,
diff --git a/infra/config/subprojects/chromium/ci/chromium.dawn.star b/infra/config/subprojects/chromium/ci/chromium.dawn.star
index 2c719f79..90dbfe3 100644
--- a/infra/config/subprojects/chromium/ci/chromium.dawn.star
+++ b/infra/config/subprojects/chromium/ci/chromium.dawn.star
@@ -564,7 +564,7 @@
 
 gpu.ci.linux_builder(
     name = "Dawn Linux TSAN Release",
-    description_html = "Runs ToT Dawn tests on SwiftShader with TSan enabled",
+    description_html = "Runs ToT Dawn tests on stable Linux/NVIDIA GTX 1660 configs with TSan enabled",
     builder_spec = builder_config.builder_spec(
         gclient_config = builder_config.gclient_config(
             config = "chromium",
@@ -586,7 +586,6 @@
     gn_args = gn_args.config(
         configs = [
             "dawn_enable_opengles",
-            "dawn_use_swiftshader",
             "tsan",
             "release_try_builder",
             "minimal_symbols",
@@ -602,53 +601,8 @@
             "gpu_dawn_tsan_gtests",
         ],
         mixins = [
-            "gpu-swarming-pool",
-            "no_gpu",
-            "linux-jammy",
-            "x86-64",
+            "linux_nvidia_gtx_1660_obsolete",
         ],
-        per_test_modifications = {
-            "dawn_end2end_implicit_device_sync_tests": targets.mixin(
-                args = [
-                    # Only interested in direct SwiftShader testing at the
-                    # moment.
-                    "--adapter-vendor-id=0x1AE0",
-                ],
-                swarming = targets.swarming(
-                    shards = 4,
-                ),
-            ),
-            "dawn_end2end_skip_validation_tests": targets.mixin(
-                args = [
-                    # Only interested in direct SwiftShader testing at the
-                    # moment.
-                    "--adapter-vendor-id=0x1AE0",
-                ],
-                swarming = targets.swarming(
-                    shards = 4,
-                ),
-            ),
-            "dawn_end2end_tests": targets.mixin(
-                args = [
-                    # Only interested in direct SwiftShader testing at the
-                    # moment.
-                    "--adapter-vendor-id=0x1AE0",
-                ],
-                swarming = targets.swarming(
-                    shards = 4,
-                ),
-            ),
-            "dawn_end2end_wire_tests": targets.mixin(
-                args = [
-                    # Only interested in direct SwiftShader testing at the
-                    # moment.
-                    "--adapter-vendor-id=0x1AE0",
-                ],
-                swarming = targets.swarming(
-                    shards = 4,
-                ),
-            ),
-        },
     ),
     targets_settings = targets.settings(
         os_type = targets.os_type.LINUX,
diff --git a/infra/config/subprojects/chromium/ci/chromium.gpu.fyi.star b/infra/config/subprojects/chromium/ci/chromium.gpu.fyi.star
index 73c20988..5d1d75e3 100644
--- a/infra/config/subprojects/chromium/ci/chromium.gpu.fyi.star
+++ b/infra/config/subprojects/chromium/ci/chromium.gpu.fyi.star
@@ -981,21 +981,11 @@
     targets = targets.bundle(
         # This bot doesn't run any browser-based tests (tab_capture_end2end_tests)
         targets = [
-            "gpu_common_gtests_passthrough_swiftshader",
+            "gpu_fyi_linux_debug_gtests",
         ],
         mixins = [
-            "gpu-swarming-pool",
-            "no_gpu",
-            "linux-jammy",
-            "x86-64",
+            "linux_nvidia_gtx_1660_obsolete",
         ],
-        per_test_modifications = {
-            "gl_tests_passthrough": targets.mixin(
-                args = [
-                    "--test-launcher-filter-file=../../testing/buildbot/filters/linux.swiftshader.tsan.gl_tests_passthrough.filter",
-                ],
-            ),
-        },
     ),
     # This bot doesn't run any Telemetry-based tests so doesn't
     # need the browser_config parameter.
@@ -2640,6 +2630,13 @@
                     },
                 ),
             ),
+            "trace_test": targets.per_test_modification(
+                mixins = targets.mixin(
+                    swarming = targets.swarming(
+                        shards = 2,
+                    ),
+                ),
+            ),
         },
     ),
     targets_settings = targets.settings(
diff --git a/infra/config/subprojects/chromium/ci/chromium.memory.star b/infra/config/subprojects/chromium/ci/chromium.memory.star
index b5de044..c46bd4f 100644
--- a/infra/config/subprojects/chromium/ci/chromium.memory.star
+++ b/infra/config/subprojects/chromium/ci/chromium.memory.star
@@ -837,15 +837,13 @@
                 reason = "https://crbug.com/crashpad/304",
             ),
             "gl_tests_passthrough": [
-                "gpu-swarming-pool",
-                "no_gpu",
-                "linux-jammy",
-                "x86-64",
                 targets.mixin(
                     args = [
-                        "--test-launcher-filter-file=../../testing/buildbot/filters/linux.swiftshader.tsan.gl_tests_passthrough.filter",
+                        "--use-gpu-in-tests",
+                        "--no-xvfb",
                     ],
                 ),
+                "linux_nvidia_gtx_1660_obsolete",
             ],
             "interactive_ui_tests": targets.mixin(
                 # https://crbug.com/1498240
diff --git a/infra/config/targets/binaries.star b/infra/config/targets/binaries.star
index 9aa50c33..1342bec 100644
--- a/infra/config/targets/binaries.star
+++ b/infra/config/targets/binaries.star
@@ -803,6 +803,7 @@
             "--smoke-test-mode",
         ],
     ),
+    module_scheme = "flat",
 )
 
 targets.binaries.windowed_test_launcher(
diff --git a/infra/config/targets/bundles.star b/infra/config/targets/bundles.star
index ef67968..41f81fe 100644
--- a/infra/config/targets/bundles.star
+++ b/infra/config/targets/bundles.star
@@ -3490,27 +3490,6 @@
 )
 
 targets.bundle(
-    name = "gpu_common_gtests_passthrough_swiftshader",
-    targets = [
-        "gl_tests_passthrough",
-        "gl_unittests",
-    ],
-    per_test_modifications = {
-        "gl_tests_passthrough": targets.mixin(
-            args = [
-                "--use-gl=angle",
-            ],
-            swarming = targets.swarming(
-                shards = 2,
-            ),
-        ),
-        "gl_unittests": [
-            "skia_gold_test",
-        ],
-    },
-)
-
-targets.bundle(
     name = "gpu_common_gtests_validating",
     targets = [
         "gl_tests_validating",
diff --git a/infra/config/targets/mixins.star b/infra/config/targets/mixins.star
index 8e78de5..565ff0e 100644
--- a/infra/config/targets/mixins.star
+++ b/infra/config/targets/mixins.star
@@ -1704,6 +1704,20 @@
 )
 
 targets.mixin(
+    name = "linux_nvidia_gtx_1660_obsolete",
+    # We always need this entry to be generated since it is used by
+    # //content/test/gpu/find_bad_machines.py.
+    generate_pyl_entry = targets.IGNORE_UNUSED,
+    swarming = targets.swarming(
+        dimensions = {
+            "gpu": "10de:2184-440.100",
+            "os": "Ubuntu-18.04",
+            "pool": "chromium.tests.gpu",
+        },
+    ),
+)
+
+targets.mixin(
     name = "linux_nvidia_gtx_1660_stable",
     # We always need this entry to be generated since it is used by
     # //content/test/gpu/find_bad_machines.py.
diff --git a/internal b/internal
index 7dfea0b..2098733 160000
--- a/internal
+++ b/internal
@@ -1 +1 @@
-Subproject commit 7dfea0b6b4f4cdf12c2f7c5df4790b03d0be142c
+Subproject commit 20987331c080610f354e4a0a7fbd4a5b71fd6624
diff --git a/ios/chrome/app/strings/ios_strings.grd b/ios/chrome/app/strings/ios_strings.grd
index a458c49..4ac9587 100644
--- a/ios/chrome/app/strings/ios_strings.grd
+++ b/ios/chrome/app/strings/ios_strings.grd
@@ -6016,6 +6016,12 @@
       <message name="IDS_IOS_SAFETY_CHECK_TITLE_UPDATE_CHROME" desc="Tag label title displayed for the Safety Check module in the Magic Stack when the Update Chrome check finds an issue. [iOS only]">
         Update your Browser
       </message>
+      <message name="IDS_IOS_SAFE_BROWSING_ENHANCED_OFF_INFOBAR_TITLE" desc="The title to display in an infobar when Enhanced protection is disabled by sync.">
+        Enhanced protection is off
+      </message>
+      <message name="IDS_IOS_SAFE_BROWSING_ENHANCED_ON_INFOBAR_TITLE" desc="The title to display in an infobar when Enhanced protection is enabled by sync.">
+        Enhanced protection is on
+      </message>
       <message name="IDS_IOS_SAFE_BROWSING_ENHANCED_PROTECTION_ACCOUNT_ICON_DESCRIPTION" desc="Description for Safe Browsing enhanced protection account icon.">
         When you're signed in, this data is linked to your Google Account to protect you across Google services, for example increasing protection in Gmail after a security incident.
       </message>
@@ -6038,7 +6044,9 @@
         When you're signed in, protects you across Google services.
       </message>
       <message name="IDS_IOS_SAFE_BROWSING_ENHANCED_PROTECTION_INFOBAR_BUTTON_TEXT" desc="The Enhanced Safe Browsing infobar promo button's text. When the button is clicked, the user is navigated to the Safe Browsing settings page.">Turn on…</message>
-      <message name="IDS_IOS_SAFE_BROWSING_ENHANCED_PROTECTION_INFOBAR_TITLE" desc="The Enhanced Safe Browsing infobar promo's title.">Get more security</message>
+      <message name="IDS_IOS_SAFE_BROWSING_ENHANCED_PROTECTION_INFOBAR_TITLE" desc="The Enhanced Safe Browsing infobar promo's title.">
+        Get more security
+      </message>
       <message name="IDS_IOS_SAFE_BROWSING_ENHANCED_PROTECTION_INLINE_PROMO_BUTTON_TEXT" desc="The Enhanced Safe Browsing inline promo button's text. When the button is clicked, the user isnavigated to the Safe Browsing settings page.">
         Turn on Enhanced Protection…
       </message>
diff --git a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_SAFE_BROWSING_ENHANCED_OFF_INFOBAR_TITLE.png.sha1 b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_SAFE_BROWSING_ENHANCED_OFF_INFOBAR_TITLE.png.sha1
new file mode 100644
index 0000000..2e8ce6f
--- /dev/null
+++ b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_SAFE_BROWSING_ENHANCED_OFF_INFOBAR_TITLE.png.sha1
@@ -0,0 +1 @@
+876816d08745178bc821ab7ac2340da94a595cee
\ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_SAFE_BROWSING_ENHANCED_ON_INFOBAR_TITLE.png.sha1 b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_SAFE_BROWSING_ENHANCED_ON_INFOBAR_TITLE.png.sha1
new file mode 100644
index 0000000..8cbbc6d
--- /dev/null
+++ b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_SAFE_BROWSING_ENHANCED_ON_INFOBAR_TITLE.png.sha1
@@ -0,0 +1 @@
+a93c0b11eacb84b339257c77bb290f8fb6408317
\ No newline at end of file
diff --git a/ios/chrome/browser/authentication/ui_bundled/signin/consistency_promo_signin/consistency_default_account/consistency_default_account_mediator.mm b/ios/chrome/browser/authentication/ui_bundled/signin/consistency_promo_signin/consistency_default_account/consistency_default_account_mediator.mm
index d5f11fa..5f9a8823 100644
--- a/ios/chrome/browser/authentication/ui_bundled/signin/consistency_promo_signin/consistency_default_account/consistency_default_account_mediator.mm
+++ b/ios/chrome/browser/authentication/ui_bundled/signin/consistency_promo_signin/consistency_default_account/consistency_default_account_mediator.mm
@@ -168,6 +168,7 @@
         kEnterpriseManagementDisclaimerAfterSignin:
     case signin_metrics::AccessPoint::kNtpFeaturePromo:
     case signin_metrics::AccessPoint::kEnterpriseDialogAfterSigninInterception:
+    case signin_metrics::AccessPoint::kCredentialExchangeImport:
       // Nothing prevents instantiating ConsistencyDefaultAccountViewController
       // with an arbitrary entry point, API-wise. In doubt, no label is a good,
       // generic default that fits all entry points.
diff --git a/ios/chrome/browser/authentication/ui_bundled/signin_promo_view_mediator.mm b/ios/chrome/browser/authentication/ui_bundled/signin_promo_view_mediator.mm
index a1d21ce..eba6cb56 100644
--- a/ios/chrome/browser/authentication/ui_bundled/signin_promo_view_mediator.mm
+++ b/ios/chrome/browser/authentication/ui_bundled/signin_promo_view_mediator.mm
@@ -150,6 +150,7 @@
         kEnterpriseManagementDisclaimerAfterSignin:
     case signin_metrics::AccessPoint::kNtpFeaturePromo:
     case signin_metrics::AccessPoint::kEnterpriseDialogAfterSigninInterception:
+    case signin_metrics::AccessPoint::kCredentialExchangeImport:
       return false;
   }
 }
@@ -257,6 +258,7 @@
         kEnterpriseManagementDisclaimerAfterSignin:
     case signin_metrics::AccessPoint::kNtpFeaturePromo:
     case signin_metrics::AccessPoint::kEnterpriseDialogAfterSigninInterception:
+    case signin_metrics::AccessPoint::kCredentialExchangeImport:
       NOTREACHED() << "Unexpected value for access point "
                    << static_cast<int>(access_point);
   }
@@ -365,6 +367,7 @@
         kEnterpriseManagementDisclaimerAfterSignin:
     case signin_metrics::AccessPoint::kNtpFeaturePromo:
     case signin_metrics::AccessPoint::kEnterpriseDialogAfterSigninInterception:
+    case signin_metrics::AccessPoint::kCredentialExchangeImport:
       NOTREACHED() << "Unexpected value for access point "
                    << static_cast<int>(access_point);
   }
@@ -462,6 +465,7 @@
         kEnterpriseManagementDisclaimerAfterSignin:
     case signin_metrics::AccessPoint::kNtpFeaturePromo:
     case signin_metrics::AccessPoint::kEnterpriseDialogAfterSigninInterception:
+    case signin_metrics::AccessPoint::kCredentialExchangeImport:
       return nullptr;
   }
 }
@@ -558,6 +562,7 @@
         kEnterpriseManagementDisclaimerAfterSignin:
     case signin_metrics::AccessPoint::kNtpFeaturePromo:
     case signin_metrics::AccessPoint::kEnterpriseDialogAfterSigninInterception:
+    case signin_metrics::AccessPoint::kCredentialExchangeImport:
       return nullptr;
   }
 }
diff --git a/ios/chrome/browser/autofill/ui_bundled/bottom_sheet/save_card_bottom_sheet_view_controller.mm b/ios/chrome/browser/autofill/ui_bundled/bottom_sheet/save_card_bottom_sheet_view_controller.mm
index 50b2215..da4e45ea 100644
--- a/ios/chrome/browser/autofill/ui_bundled/bottom_sheet/save_card_bottom_sheet_view_controller.mm
+++ b/ios/chrome/browser/autofill/ui_bundled/bottom_sheet/save_card_bottom_sheet_view_controller.mm
@@ -228,20 +228,9 @@
 
 #pragma mark - UITextViewDelegate
 
-#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_17_0
-- (BOOL)textView:(UITextView*)textView
-    shouldInteractWithURL:(NSURL*)URL
-                  inRange:(NSRange)characterRange
-              interaction:(UITextItemInteraction)interaction {
-  // A link in legal message was clicked.
-  [self.delegate didTapLinkURL:[[CrURL alloc] initWithNSURL:URL]];
-  return NO;
-}
-#endif
-
 - (UIAction*)textView:(UITextView*)textView
     primaryActionForTextItem:(UITextItem*)textItem
-               defaultAction:(UIAction*)defaultAction API_AVAILABLE(ios(17.0)) {
+               defaultAction:(UIAction*)defaultAction {
   // A link in legal message was clicked.
   __weak __typeof__(self) weakSelf = self;
   return [UIAction actionWithHandler:^(UIAction* action) {
diff --git a/ios/chrome/browser/autofill/ui_bundled/bottom_sheet/virtual_card_enrollment_bottom_sheet_view_controller.mm b/ios/chrome/browser/autofill/ui_bundled/bottom_sheet/virtual_card_enrollment_bottom_sheet_view_controller.mm
index cfbcfca..8d27210 100644
--- a/ios/chrome/browser/autofill/ui_bundled/bottom_sheet/virtual_card_enrollment_bottom_sheet_view_controller.mm
+++ b/ios/chrome/browser/autofill/ui_bundled/bottom_sheet/virtual_card_enrollment_bottom_sheet_view_controller.mm
@@ -306,32 +306,9 @@
 
 #pragma mark - UITextViewDelegate
 
-#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_17_0
-- (BOOL)textView:(UITextView*)textView
-    shouldInteractWithURL:(NSURL*)URL
-                  inRange:(NSRange)characterRange
-              interaction:(UITextItemInteraction)interaction {
-  if (textView == _explanatoryMessageView) {
-    // The learn more link was clicked.
-    [self.delegate
-        didTapLinkURL:[[CrURL alloc]
-                          initWithGURL:autofill::payments::
-                                           GetVirtualCardEnrollmentSupportUrl()]
-                 text:[textView.text substringWithRange:characterRange]];
-    return NO;
-  } else {
-    // A link in a legal message was clicked.
-    [self.delegate
-        didTapLinkURL:[[CrURL alloc] initWithNSURL:URL]
-                 text:[textView.text substringWithRange:characterRange]];
-    return NO;
-  }
-}
-#endif
-
 - (UIAction*)textView:(UITextView*)textView
     primaryActionForTextItem:(UITextItem*)textItem
-               defaultAction:(UIAction*)defaultAction API_AVAILABLE(ios(17.0)) {
+               defaultAction:(UIAction*)defaultAction {
   CrURL* URL = nil;
   if (textView == _explanatoryMessageView) {
     URL = [[CrURL alloc]
diff --git a/ios/chrome/browser/autofill/ui_bundled/card_unmask_prompt_view_controller_unittest.mm b/ios/chrome/browser/autofill/ui_bundled/card_unmask_prompt_view_controller_unittest.mm
index 44162883..f0e7d3a0 100644
--- a/ios/chrome/browser/autofill/ui_bundled/card_unmask_prompt_view_controller_unittest.mm
+++ b/ios/chrome/browser/autofill/ui_bundled/card_unmask_prompt_view_controller_unittest.mm
@@ -336,13 +336,13 @@
                 accessibility_identifier);
   }
 
+  std::unique_ptr<TestingPrefServiceSimple> pref_service_;
+  std::unique_ptr<autofill::TestPersonalDataManager> personal_data_manager_;
   std::unique_ptr<NiceMock<MockCardUnmaskPromptViewBridge>>
       card_unmask_prompt_bridge_;
   std::unique_ptr<NiceMock<MockCardUnmaskPromptController>>
       card_unmask_prompt_controller_;
   autofill::CardUnmaskPromptOptions challenge_option_;
-  std::unique_ptr<TestingPrefServiceSimple> pref_service_;
-  std::unique_ptr<autofill::TestPersonalDataManager> personal_data_manager_;
   ScopedKeyWindow scoped_window_;
   UIViewController* root_view_controller_;
   autofill::CreditCard card_ = autofill::test::GetMaskedServerCard();
diff --git a/ios/chrome/browser/autofill/ui_bundled/manual_fill/manual_fill_card_cell.mm b/ios/chrome/browser/autofill/ui_bundled/manual_fill/manual_fill_card_cell.mm
index 9367d00a..0df779dc 100644
--- a/ios/chrome/browser/autofill/ui_bundled/manual_fill/manual_fill_card_cell.mm
+++ b/ios/chrome/browser/autofill/ui_bundled/manual_fill/manual_fill_card_cell.mm
@@ -784,26 +784,9 @@
   }
 }
 
-#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_17_0
-- (BOOL)textView:(UITextView*)textView
-    shouldInteractWithURL:(NSURL*)URL
-                  inRange:(NSRange)characterRange
-              interaction:(UITextItemInteraction)interaction {
-  if (textView == self.cardInstructionTextView) {
-    // The learn more link was clicked.
-    [self.navigationDelegate
-          openURL:[[CrURL alloc]
-                      initWithGURL:autofill::payments::
-                                       GetVirtualCardEnrollmentSupportUrl()]
-        withTitle:[textView.text substringWithRange:characterRange]];
-  }
-  return NO;
-}
-#endif
-
 - (UIAction*)textView:(UITextView*)textView
     primaryActionForTextItem:(UITextItem*)textItem
-               defaultAction:(UIAction*)defaultAction API_AVAILABLE(ios(17.0)) {
+               defaultAction:(UIAction*)defaultAction {
   if (textView != self.cardInstructionTextView) {
     return defaultAction;
   }
diff --git a/ios/chrome/browser/bookmarks/ui_bundled/home/bookmarks_home_node_item.mm b/ios/chrome/browser/bookmarks/ui_bundled/home/bookmarks_home_node_item.mm
index 414e1fd4..0d6d5b8 100644
--- a/ios/chrome/browser/bookmarks/ui_bundled/home/bookmarks_home_node_item.mm
+++ b/ios/chrome/browser/bookmarks/ui_bundled/home/bookmarks_home_node_item.mm
@@ -31,7 +31,7 @@
     if (node->is_folder()) {
       self.cellClass = [TableViewBookmarksFolderCell class];
     } else {
-      self.cellClass = LegacyTableViewCell.class;
+      self.cellClass = [LegacyTableViewCell class];
     }
     _bookmarkNode = node;
   }
diff --git a/ios/chrome/browser/composebox/coordinator/BUILD.gn b/ios/chrome/browser/composebox/coordinator/BUILD.gn
index 170ac62..bd1fef93 100644
--- a/ios/chrome/browser/composebox/coordinator/BUILD.gn
+++ b/ios/chrome/browser/composebox/coordinator/BUILD.gn
@@ -40,6 +40,7 @@
     "//components/omnibox/common",
     "//components/omnibox/composebox/ios",
     "//components/prefs",
+    "//components/search",
     "//components/search_engines",
     "//ios/chrome/app/strings",
     "//ios/chrome/browser/autocomplete/model",
@@ -68,6 +69,7 @@
     "//ios/chrome/browser/omnibox/ui",
     "//ios/chrome/browser/omnibox/ui:omnibox_internal",
     "//ios/chrome/browser/prerender/model",
+    "//ios/chrome/browser/search_engines/model",
     "//ios/chrome/browser/search_engines/model:template_url_service_factory",
     "//ios/chrome/browser/shared/coordinator/chrome_coordinator",
     "//ios/chrome/browser/shared/coordinator/layout_guide",
@@ -116,3 +118,28 @@
   ]
   deps = [ "//base" ]
 }
+
+source_set("unit_tests") {
+  testonly = true
+  sources = [ "composebox_input_plate_mediator_unittest.mm" ]
+  deps = [
+    ":coordinator",
+    "//base",
+    "//base/test:test_support",
+    "//components/contextual_search:public",
+    "//components/contextual_search/internal:test_support",
+    "//components/omnibox/composebox/ios",
+    "//components/search_engines",
+    "//components/search_engines:test_support",
+    "//components/signin/public/identity_manager",
+    "//components/variations",
+    "//components/version_info:channel",
+    "//ios/chrome/browser/composebox/ui",
+    "//ios/chrome/browser/shared/model/profile/test",
+    "//ios/chrome/browser/signin/model",
+    "//services/network:test_support",
+    "//services/network/public/cpp",
+    "//testing/gmock",
+    "//testing/gtest",
+  ]
+}
diff --git a/ios/chrome/browser/composebox/coordinator/DEPS b/ios/chrome/browser/composebox/coordinator/DEPS
index 0d95b7a..fdd01305 100644
--- a/ios/chrome/browser/composebox/coordinator/DEPS
+++ b/ios/chrome/browser/composebox/coordinator/DEPS
@@ -1,4 +1,5 @@
 include_rules = [
+  "+components/contextual_search",
   "+components/omnibox/composebox",
   "+ios/chrome/browser/autocomplete/model",
   "+ios/chrome/browser/bookmarks/model",
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 6829c26..aea49d6 100644
--- a/ios/chrome/browser/composebox/coordinator/composebox_input_plate_coordinator.mm
+++ b/ios/chrome/browser/composebox/coordinator/composebox_input_plate_coordinator.mm
@@ -137,22 +137,24 @@
   _voiceSearchController =
       ios::provider::CreateVoiceSearchController(self.browser);
 
-  auto query_contoller_config_params = std::make_unique<
+  auto query_controller_config_params = std::make_unique<
       contextual_search::ContextualSearchContextController::ConfigParams>();
-  query_contoller_config_params->send_lns_surface = false;
-  query_contoller_config_params->enable_multi_context_input_flow = true;
-  query_contoller_config_params->enable_viewport_images = true;
+  query_controller_config_params->send_lns_surface = false;
+  query_controller_config_params->enable_multi_context_input_flow = true;
+  query_controller_config_params->enable_viewport_images = true;
 
   _contextualService =
       ContextualSearchServiceFactory::GetForProfile(self.profile);
 
   std::unique_ptr<contextual_search::ContextualSearchSessionHandle>
       contextualSearchSession = _contextualService->CreateSession(
-          std::move(query_contoller_config_params),
+          std::move(query_controller_config_params),
           contextual_search::ContextualSearchSource::kOmnibox);
 
   FaviconLoader* faviconLoader =
       IOSChromeFaviconLoaderFactory::GetForProfile(self.profile);
+  TemplateURLService* templateURLService =
+      ios::TemplateURLServiceFactory::GetForProfile(self.profile);
   _mediator = [[ComposeboxInputPlateMediator alloc]
       initWithContextualSearchSession:std::move(contextualSearchSession)
                          webStateList:self.browser->GetWebStateList()
@@ -160,7 +162,8 @@
                persistTabContextAgent:PersistTabContextBrowserAgent::
                                           FromBrowser(self.browser)
                           isIncognito:self.isOffTheRecord
-                           modeHolder:_modeHolder];
+                           modeHolder:_modeHolder
+                   templateURLService:templateURLService];
   _mediator.URLLoader = _URLLoader;
   _mediator.consumer = _viewController;
   _mediator.delegate = self;
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 073fe6878..7e6060f 100644
--- a/ios/chrome/browser/composebox/coordinator/composebox_input_plate_mediator.h
+++ b/ios/chrome/browser/composebox/coordinator/composebox_input_plate_mediator.h
@@ -23,6 +23,7 @@
 class FaviconLoader;
 class GURL;
 class PersistTabContextBrowserAgent;
+class TemplateURLService;
 class WebStateList;
 
 namespace contextual_search {
@@ -65,7 +66,8 @@
              persistTabContextAgent:
                  (PersistTabContextBrowserAgent*)persistTabContextAgent
                         isIncognito:(BOOL)isIncognito
-                         modeHolder:(ComposeboxModeHolder*)modeHolder;
+                         modeHolder:(ComposeboxModeHolder*)modeHolder
+                 templateURLService:(TemplateURLService*)templateURLService;
 
 - (void)disconnect;
 
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 425aa87..9b26a2a3 100644
--- a/ios/chrome/browser/composebox/coordinator/composebox_input_plate_mediator.mm
+++ b/ios/chrome/browser/composebox/coordinator/composebox_input_plate_mediator.mm
@@ -35,6 +35,7 @@
 #import "components/omnibox/common/omnibox_features.h"
 #import "components/omnibox/composebox/ios/composebox_file_upload_observer_bridge.h"
 #import "components/omnibox/composebox/ios/composebox_query_controller_ios.h"
+#import "components/search/search.h"
 #import "components/search_engines/template_url_service.h"
 #import "components/search_engines/util.h"
 #import "ios/chrome/browser/composebox/coordinator/composebox_constants.h"
@@ -46,6 +47,7 @@
 #import "ios/chrome/browser/favicon/model/favicon_loader.h"
 #import "ios/chrome/browser/intelligence/persist_tab_context/model/persist_tab_context_browser_agent.h"
 #import "ios/chrome/browser/intelligence/proto_wrappers/page_context_wrapper.h"
+#import "ios/chrome/browser/search_engines/model/search_engine_observer_bridge.h"
 #import "ios/chrome/browser/shared/model/url/url_util.h"
 #import "ios/chrome/browser/shared/model/utils/mime_type_util.h"
 #import "ios/chrome/browser/shared/model/web_state_list/web_state_list.h"
@@ -135,6 +137,9 @@
 
 }  // namespace
 
+@interface ComposeboxInputPlateMediator () <SearchEngineObserving>
+@end
+
 @implementation ComposeboxInputPlateMediator {
   // The ordered list of items for display.
   NSMutableArray<ComposeboxInputItem*>* _items;
@@ -151,6 +156,10 @@
   raw_ptr<FaviconLoader> _faviconLoader;
   // A browser agent for retrieving APC from the cache.
   raw_ptr<PersistTabContextBrowserAgent> _persistTabContextAgent;
+  // A template URL service.
+  raw_ptr<TemplateURLService> _templateURLService;
+  // Observer for the TemplateURLService.
+  std::unique_ptr<SearchEngineObserverBridge> _searchEngineObserver;
 
   // Stores the page context wrappers for the duration of the APC retrieval.
   std::unordered_map<web::WebStateID, PageContextWrapper*> _pageContextWrappers;
@@ -183,7 +192,8 @@
              persistTabContextAgent:
                  (PersistTabContextBrowserAgent*)persistTabContextAgent
                         isIncognito:(BOOL)isIncognito
-                         modeHolder:(ComposeboxModeHolder*)modeHolder {
+                         modeHolder:(ComposeboxModeHolder*)modeHolder
+                 templateURLService:(TemplateURLService*)templateURLService {
   self = [super init];
   if (self) {
     _items = [NSMutableArray array];
@@ -200,6 +210,9 @@
     _isIncognito = isIncognito;
     _modeHolder = modeHolder;
     [_modeHolder addObserver:self];
+    _templateURLService = templateURLService;
+    _searchEngineObserver =
+        std::make_unique<SearchEngineObserverBridge>(self, _templateURLService);
   }
   return self;
 }
@@ -211,6 +224,8 @@
   _faviconLoader = nullptr;
   _webStateDeferredExecutor = nil;
   _persistTabContextAgent = nullptr;
+  _searchEngineObserver.reset();
+  _templateURLService = nullptr;
   _composeboxObserverBridge.reset();
   if (_contextualSearchSession) {
     _contextualSearchSession->NotifySessionAbandoned();
@@ -283,6 +298,7 @@
   }
 
   [self updateCompactModeIfNeeded];
+  [self updateShowsExtendedControls];
 }
 
 - (void)processPDFFileURL:(GURL)PDFFileURL {
@@ -983,6 +999,16 @@
   return NO;
 }
 
+// Updates the consumer about whether the extended controls should be shown.
+- (void)updateShowsExtendedControls {
+  if (!_templateURLService) {
+    return;
+  }
+  const BOOL showsExtendedControls =
+      search::DefaultSearchProviderIsGoogle(_templateURLService);
+  [self.consumer setShowsExtendedControls:showsExtendedControls];
+}
+
 #pragma mark - ComposeboxOmniboxClientDelegate
 
 - (std::optional<lens::proto::LensOverlaySuggestInputs>)suggestInputs {
@@ -1022,9 +1048,7 @@
                isSearchQuery:(BOOL)isSearchQuery
          userInputInProgress:(BOOL)userInputInProgress {
   DCHECK_CALLED_ON_VALID_SEQUENCE(_sequenceChecker);
-  // Update send, lens and mic button visibility.
-  [self.consumer hideLensAndMicButton:text.length()];
-  [self.consumer hideSendButton:!text.length()];
+  [self.consumer setShowsSendButton:text.length()];
 }
 
 - (ComposeboxMode)composeboxMode {
@@ -1086,6 +1110,7 @@
   }
 }
 
+/// Updates the consumer whether to show in compact mode.
 - (void)updateCompactModeIfNeeded {
   BOOL compactModeAllowed = IsComposeboxCompactModeEnabled();
   BOOL requiresExpansion = _isMultiline ||
@@ -1095,6 +1120,17 @@
   [self.consumer setCompact:compact];
 }
 
+#pragma mark - SearchEngineObserving
+
+- (void)searchEngineChanged {
+  [self updateShowsExtendedControls];
+}
+
+- (void)templateURLServiceShuttingDown:(TemplateURLService*)urlService {
+  CHECK_EQ(urlService, _templateURLService);
+  _templateURLService = nullptr;
+}
+
 #pragma mark - TextFieldViewContainingHeightDelegate
 
 - (void)textFieldViewContaining:(UIView<TextFieldViewContaining>*)sender
diff --git a/ios/chrome/browser/composebox/coordinator/composebox_input_plate_mediator_unittest.mm b/ios/chrome/browser/composebox/coordinator/composebox_input_plate_mediator_unittest.mm
new file mode 100644
index 0000000..a4b2095
--- /dev/null
+++ b/ios/chrome/browser/composebox/coordinator/composebox_input_plate_mediator_unittest.mm
@@ -0,0 +1,185 @@
+// 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/composebox/coordinator/composebox_input_plate_mediator.h"
+
+#import "base/run_loop.h"
+#import "base/test/task_environment.h"
+#import "components/contextual_search/contextual_search_context_controller.h"
+#import "components/contextual_search/contextual_search_service.h"
+#import "components/contextual_search/internal/test_composebox_query_controller.h"
+#import "components/omnibox/composebox/ios/composebox_query_controller_ios.h"
+#import "components/search_engines/search_engines_test_environment.h"
+#import "components/search_engines/template_url_service.h"
+#import "components/search_engines/template_url_service_test_util.h"
+#import "components/signin/public/identity_manager/identity_manager.h"
+#import "components/variations/variations_client.h"
+#import "components/version_info/channel.h"
+#import "ios/chrome/browser/composebox/coordinator/composebox_mode_holder.h"
+#import "ios/chrome/browser/composebox/ui/composebox_input_plate_consumer.h"
+#import "ios/chrome/browser/shared/model/profile/test/test_profile_ios.h"
+#import "services/network/public/cpp/shared_url_loader_factory.h"
+#import "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
+#import "services/network/test/test_url_loader_factory.h"
+#import "testing/gmock/include/gmock/gmock.h"
+#import "testing/gtest/include/gtest/gtest.h"
+#import "testing/platform_test.h"
+
+// Mock consumer for the mediator.
+@interface TestComposeboxInputPlateConsumer
+    : NSObject <ComposeboxInputPlateConsumer>
+
+@property(nonatomic, assign) BOOL showsSendButton;
+@property(nonatomic, assign) BOOL showsExtendedControls;
+
+@end
+
+@implementation TestComposeboxInputPlateConsumer
+
+- (void)setItems:(NSArray<ComposeboxInputItem*>*)items {
+}
+- (void)updateState:(ComposeboxInputItemState)state
+    forItemWithIdentifier:(const base::UnguessableToken&)identifier {
+}
+
+- (void)setShowsSendButton:(BOOL)showsSendButton {
+  _showsSendButton = showsSendButton;
+}
+
+- (void)setShowsExtendedControls:(BOOL)showsExtendedControls {
+  _showsExtendedControls = showsExtendedControls;
+}
+
+- (void)setAIModeEnabled:(BOOL)enabled {
+}
+- (void)setImageGenerationEnabled:(BOOL)enabled {
+}
+- (void)setCompact:(BOOL)compact {
+}
+- (void)setCurrentTabFavicon:(UIImage*)favicon {
+}
+- (void)hideAttachCurrentTabAction:(BOOL)hidden {
+}
+- (void)hideAttachTabActions:(BOOL)hidden {
+}
+- (void)disableAttachTabActions:(BOOL)disabled {
+}
+- (void)hideAttachFileActions:(BOOL)hidden {
+}
+- (void)disableAttachFileActions:(BOOL)disabled {
+}
+- (void)hideCreateImageActions:(BOOL)hidden {
+}
+- (void)disableCreateImageActions:(BOOL)disabled {
+}
+
+@end
+
+namespace {
+
+class ComposeboxInputPlateMediatorTest : public PlatformTest {
+ protected:
+  void SetUp() override {
+    PlatformTest::SetUp();
+    profile_ = TestProfileIOS::Builder().Build();
+    shared_url_loader_factory_ =
+        base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
+            &test_factory_);
+    fake_variations_client_ = std::make_unique<FakeVariationsClient>();
+    service_ = std::make_unique<contextual_search::ContextualSearchService>(
+        nullptr, shared_url_loader_factory_, template_url_service(),
+        fake_variations_client_.get(), version_info::Channel::STABLE, "en-US");
+    auto config_params = std::make_unique<
+        contextual_search::ContextualSearchContextController::ConfigParams>();
+    mediator_ = [[ComposeboxInputPlateMediator alloc]
+        initWithContextualSearchSession:service_->CreateSession(
+                                            std::move(config_params),
+                                            contextual_search::ContextualSearchSource::kUnknown)
+                           webStateList:nullptr
+                          faviconLoader:nullptr
+                 persistTabContextAgent:nullptr
+                            isIncognito:NO
+                             modeHolder:[[ComposeboxModeHolder alloc] init]
+                     templateURLService:template_url_service()];
+    consumer_ = [[TestComposeboxInputPlateConsumer alloc] init];
+    mediator_.consumer = consumer_;
+
+    template_url_service()->Load();
+    TemplateURLServiceLoadWaiter waiter;
+    waiter.WaitForLoadComplete(*template_url_service());
+  }
+
+  void TearDown() override {
+    [mediator_ disconnect];
+    mediator_ = nil;
+    consumer_ = nil;
+    service_.reset();
+    fake_variations_client_.reset();
+    shared_url_loader_factory_.reset();
+    profile_.reset();
+    PlatformTest::TearDown();
+  }
+
+ protected:
+  TemplateURLService* template_url_service() {
+    return search_engines_test_environment_.template_url_service();
+  }
+
+  base::test::TaskEnvironment task_environment_;
+  std::unique_ptr<TestProfileIOS> profile_;
+  network::TestURLLoaderFactory test_factory_;
+  scoped_refptr<network::SharedURLLoaderFactory> shared_url_loader_factory_;
+  std::unique_ptr<FakeVariationsClient> fake_variations_client_;
+  search_engines::SearchEnginesTestEnvironment search_engines_test_environment_;
+  std::unique_ptr<contextual_search::ContextualSearchService> service_;
+  TestComposeboxInputPlateConsumer* consumer_;
+  ComposeboxInputPlateMediator* mediator_;
+};
+
+// Tests that extended controls are shown when Google is the default search
+// engine.
+TEST_F(ComposeboxInputPlateMediatorTest, ShowsExtendedControlsWithGoogleDSE) {
+  TemplateURLService* template_url_service = this->template_url_service();
+  TemplateURLData data;
+  data.SetURL("https://www.google.com/search?q={searchTerms}");
+  data.safe_for_autoreplace = true;
+  data.prepopulate_id = 1;
+  TemplateURL* template_url =
+      template_url_service->Add(std::make_unique<TemplateURL>(data));
+  template_url_service->SetUserSelectedDefaultSearchProvider(template_url);
+
+  EXPECT_TRUE(consumer_.showsExtendedControls);
+}
+
+// Tests that extended controls are hidden when Google is not the default search
+// engine.
+TEST_F(ComposeboxInputPlateMediatorTest,
+       HidesExtendedControlsWithNonGoogleDSE) {
+  TemplateURLService* template_url_service = this->template_url_service();
+  TemplateURLData data;
+  data.SetURL("https://www.bing.com/search?q={searchTerms}");
+  data.safe_for_autoreplace = false;
+  data.prepopulate_id = 2;
+  TemplateURL* template_url =
+      template_url_service->Add(std::make_unique<TemplateURL>(data));
+  template_url_service->SetUserSelectedDefaultSearchProvider(template_url);
+
+  EXPECT_FALSE(consumer_.showsExtendedControls);
+}
+
+// Tests that the send button is shown when there is text in the omnibox.
+TEST_F(ComposeboxInputPlateMediatorTest, ShowsSendButtonWithText) {
+  [mediator_ omniboxDidChangeText:u"some text"
+                    isSearchQuery:NO
+              userInputInProgress:NO];
+  EXPECT_TRUE(consumer_.showsSendButton);
+}
+
+// Tests that the send button is hidden when there is no text in the omnibox.
+TEST_F(ComposeboxInputPlateMediatorTest, HidesSendButtonWithoutText) {
+  [mediator_ omniboxDidChangeText:u"" isSearchQuery:NO userInputInProgress:NO];
+  EXPECT_FALSE(consumer_.showsSendButton);
+}
+
+}  // namespace
diff --git a/ios/chrome/browser/composebox/ui/composebox_input_plate_consumer.h b/ios/chrome/browser/composebox/ui/composebox_input_plate_consumer.h
index a27f1514..4082275 100644
--- a/ios/chrome/browser/composebox/ui/composebox_input_plate_consumer.h
+++ b/ios/chrome/browser/composebox/ui/composebox_input_plate_consumer.h
@@ -19,11 +19,11 @@
 - (void)updateState:(ComposeboxInputItemState)state
     forItemWithIdentifier:(const base::UnguessableToken&)identifier;
 
-// Updates the mic and lens button visibility.
-- (void)hideLensAndMicButton:(BOOL)hidden;
+// Sets whether to show the Send button vs the other enabled controls.
+- (void)setShowsSendButton:(BOOL)showsSendButton;
 
-// Updates the send button visibility.
-- (void)hideSendButton:(BOOL)hidden;
+// Sets whether to show the extended controls (Plus button and Lens button).
+- (void)setShowsExtendedControls:(BOOL)showsExtendedControls;
 
 // Sets whether AI mode is enabled.
 - (void)setAIModeEnabled:(BOOL)enabled;
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 958804d3..1de414f 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
@@ -167,6 +167,10 @@
   UIView* _trailingCarouselFadeView;
   /// The carousel container.
   UIView* _carouselContainer;
+  /// Whether the Send button should be shown, hiding the other controls.
+  BOOL _showsSendButton;
+  /// Whether the extended controls (Plus and Lens button) should be shown.
+  BOOL _showsExtendedControls;
   /// Attach current tab action state.
   BOOL _attachCurrentTabActionHidden;
   /// Attach tabs actions state.
@@ -237,6 +241,7 @@
   _plusButton = [self createPlusButton];
   _sendButton = [self createSendButton];
   [self updatePlusButtonItems];
+  [self updateButtonsVisibility];
   [self setupCarouselContainer];
 
   _inputPlateStackView =
@@ -373,13 +378,20 @@
   }
 }
 
-- (void)hideLensAndMicButton:(BOOL)hidden {
-  _micButton.hidden = hidden;
-  _lensButton.hidden = hidden;
+- (void)setShowsSendButton:(BOOL)showsSendButton {
+  if (_showsSendButton == showsSendButton) {
+    return;
+  }
+  _showsSendButton = showsSendButton;
+  [self updateButtonsVisibility];
 }
 
-- (void)hideSendButton:(BOOL)hidden {
-  _sendButton.hidden = hidden;
+- (void)setShowsExtendedControls:(BOOL)showsExtendedControls {
+  if (_showsExtendedControls == showsExtendedControls) {
+    return;
+  }
+  _showsExtendedControls = showsExtendedControls;
+  [self updateButtonsVisibility];
 }
 
 - (void)setCompact:(BOOL)compact {
@@ -403,6 +415,7 @@
   [self updatePlaceholderText];
   [self updateAIMButtonAppearance];
   [self updatePlusButtonItems];
+  [self updateButtonsVisibility];
   [self triggerGlowEffect];
 }
 
@@ -679,6 +692,14 @@
   [_inputPlateContainerView.layer setNeedsDisplay];
 }
 
+- (void)updateButtonsVisibility {
+  _plusButton.hidden = !_showsExtendedControls;
+  _aimButton.hidden = !self.AIModeEnabled;
+  _micButton.hidden = _showsSendButton;
+  _lensButton.hidden = _showsSendButton || !_showsExtendedControls;
+  _sendButton.hidden = !_showsSendButton;
+}
+
 /// Updates the AIM button taking into account if the button should be minimize
 /// or not or if the mode is enable or not.
 - (void)updateAIMButtonAppearance {
@@ -711,8 +732,6 @@
     config.background.backgroundColor =
         [_theme aimButtonBackgroundColorWithAIMEnabled:YES];
     config.baseForegroundColor = [_theme aimButtonTextColorWithAIMEnabled:YES];
-
-    _aimButton.hidden = NO;
   } else {
     config.contentInsets = NSDirectionalEdgeInsetsMake(5, 8, 5, 8);
     config.background.backgroundColor =
@@ -723,9 +742,8 @@
       _aimButton.layer.borderWidth = 1;
       _aimButton.layer.borderColor = [UIColor colorNamed:kGrey200Color].CGColor;
     }
-
-    _aimButton.hidden = YES;
   }
+  [self updateButtonsVisibility];
 
   _aimButton.configuration = config;
 
diff --git a/ios/chrome/browser/content_suggestions/ui_bundled/tips/coordinator/tips_passwords_coordinator.mm b/ios/chrome/browser/content_suggestions/ui_bundled/tips/coordinator/tips_passwords_coordinator.mm
index dabe895..37bb659 100644
--- a/ios/chrome/browser/content_suggestions/ui_bundled/tips/coordinator/tips_passwords_coordinator.mm
+++ b/ios/chrome/browser/content_suggestions/ui_bundled/tips/coordinator/tips_passwords_coordinator.mm
@@ -57,6 +57,12 @@
   UINavigationController* navigationController = [[UINavigationController alloc]
       initWithRootViewController:_tipsInstructionalViewController];
 
+  _tipsInstructionalViewController.navigationItem.rightBarButtonItem =
+      [[UIBarButtonItem alloc]
+          initWithBarButtonSystemItem:UIBarButtonSystemItemClose
+                               target:self
+                               action:@selector(dismissSheet)];
+
   navigationController.modalPresentationStyle = UIModalPresentationFormSheet;
 
   [self.baseViewController presentViewController:navigationController
@@ -81,7 +87,7 @@
 
 - (void)presentationControllerDidDismiss:
     (UIPresentationController*)presentationController {
-  [_delegate tipsPasswordsCoordinatorDidFinish:self];
+  [self dismissSheet];
 }
 
 #pragma mark - ConfirmationAlertActionHandler
@@ -89,7 +95,7 @@
 - (void)confirmationAlertPrimaryAction {
   // TODO(crbug.com/369457289): Track user interactions with the Tips module
   // using new metrics.
-  [_delegate tipsPasswordsCoordinatorDidFinish:self];
+  [self dismissSheet];
 }
 
 - (void)confirmationAlertSecondaryAction {
@@ -120,12 +126,13 @@
   }
 }
 
-- (void)confirmationAlertDismissAction {
+#pragma mark - Private
+
+// Dismisses the sheet.
+- (void)dismissSheet {
   [_delegate tipsPasswordsCoordinatorDidFinish:self];
 }
 
-#pragma mark - Private
-
 // Dismisses the instructions.
 - (void)dismissInstructions {
   [_instructionsNavigationController.presentingViewController
diff --git a/ios/chrome/browser/credential_exchange/coordinator/BUILD.gn b/ios/chrome/browser/credential_exchange/coordinator/BUILD.gn
index 1ee9ee9a..118b85a3 100644
--- a/ios/chrome/browser/credential_exchange/coordinator/BUILD.gn
+++ b/ios/chrome/browser/credential_exchange/coordinator/BUILD.gn
@@ -60,6 +60,7 @@
     "//ios/chrome/browser/settings/ui_bundled/password:ui",
     "//ios/chrome/browser/shared/model/application_context",
     "//ios/chrome/browser/signin/model",
+    "//ios/chrome/browser/webauthn/model",
     "//ios/chrome/browser/webauthn/public",
     "//ios/chrome/common/credential_provider:passkey_keychain_provider_bridge_chrome",
     "//ios/chrome/common/credential_provider/ui",
diff --git a/ios/chrome/browser/credential_exchange/coordinator/credential_export_coordinator.h b/ios/chrome/browser/credential_exchange/coordinator/credential_export_coordinator.h
index 28d1c6b7..4bb1ba1 100644
--- a/ios/chrome/browser/credential_exchange/coordinator/credential_export_coordinator.h
+++ b/ios/chrome/browser/credential_exchange/coordinator/credential_export_coordinator.h
@@ -5,35 +5,29 @@
 #ifndef IOS_CHROME_BROWSER_CREDENTIAL_EXCHANGE_COORDINATOR_CREDENTIAL_EXPORT_COORDINATOR_H_
 #define IOS_CHROME_BROWSER_CREDENTIAL_EXCHANGE_COORDINATOR_CREDENTIAL_EXPORT_COORDINATOR_H_
 
+#import <vector>
+
 #import "ios/chrome/browser/shared/coordinator/chrome_coordinator/chrome_coordinator.h"
 
 namespace password_manager {
-class SavedPasswordsPresenter;
+class AffiliatedGroup;
 }  // namespace password_manager
 
-namespace webauthn {
-class PasskeyModel;
-}  // namespace webauthn
-
 API_AVAILABLE(ios(26.0))
 // Coordinator for the credential exchange export flow.
 @interface CredentialExportCoordinator : ChromeCoordinator
 
-// Passing `savedPasswordsPresenter` in the constructor instead of accessing it
-// from the `browser` is not the best practice, but the initialization of this
-// object is quite heavy and it is a common exception in password settings code.
-// TODO(crbug.com/444112223): In case `savedPasswordsPresenter` will end up
-// being used only one time to access credentials, just pass the credentials
-// instead.
-- (instancetype)initWithBaseNavigationController:
-                    (UINavigationController*)navigationController
-                                         browser:(Browser*)browser
-                         savedPasswordsPresenter:
-                             (password_manager::SavedPasswordsPresenter*)
-                                 savedPasswordsPresenter
-                                    passkeyModel:
-                                        (webauthn::PasskeyModel*)passkeyModel
-    NS_DESIGNATED_INITIALIZER;
+// Passing `affiliatedGroups` in the constructor instead of accessing it from
+// the `browser` is not the best practice, but it can only be accessed from
+// `password_manager::SavedPasswordPresenter`, which is heavy to initialize.
+// It is a common exception in password settings code.
+- (instancetype)
+    initWithBaseNavigationController:
+        (UINavigationController*)navigationController
+                             browser:(Browser*)browser
+                    affiliatedGroups:
+                        (std::vector<password_manager::AffiliatedGroup>)
+                            affiliatedGroups NS_DESIGNATED_INITIALIZER;
 
 - (instancetype)initWithBaseViewController:(UIViewController*)viewController
                                    browser:(Browser*)browser NS_UNAVAILABLE;
diff --git a/ios/chrome/browser/credential_exchange/coordinator/credential_export_coordinator.mm b/ios/chrome/browser/credential_exchange/coordinator/credential_export_coordinator.mm
index 80b926d..dea2088c4 100644
--- a/ios/chrome/browser/credential_exchange/coordinator/credential_export_coordinator.mm
+++ b/ios/chrome/browser/credential_exchange/coordinator/credential_export_coordinator.mm
@@ -9,7 +9,7 @@
 
 #import "base/memory/raw_ptr.h"
 #import "components/metrics/metrics_pref_names.h"
-#import "components/password_manager/core/browser/ui/saved_passwords_presenter.h"
+#import "components/password_manager/core/browser/ui/affiliated_group.h"
 #import "components/prefs/pref_service.h"
 #import "components/signin/public/identity_manager/account_info.h"
 #import "components/signin/public/identity_manager/identity_manager.h"
@@ -19,6 +19,7 @@
 #import "ios/chrome/browser/settings/ui_bundled/password/create_password_manager_title_view.h"
 #import "ios/chrome/browser/shared/model/application_context/application_context.h"
 #import "ios/chrome/browser/signin/model/identity_manager_factory.h"
+#import "ios/chrome/browser/webauthn/model/ios_passkey_model_factory.h"
 #import "ios/chrome/browser/webauthn/public/passkey_welcome_screen_util.h"
 #import "ios/chrome/common/credential_provider/passkey_keychain_provider_bridge.h"
 #import "ios/chrome/common/credential_provider/ui/passkey_welcome_screen_strings.h"
@@ -40,14 +41,12 @@
   // Handles interaction with the credential export OS libraries.
   CredentialExportMediator* _mediator;
 
-  // Used to fetch the user's saved passwords for export.
-  raw_ptr<password_manager::SavedPasswordsPresenter> _savedPasswordsPresenter;
-
   // Bridge to the PasskeyKeychainProvider that manages passkey vault keys.
   PasskeyKeychainProviderBridge* _passkeyKeychainProviderBridge;
 
-  // Provides access to stored WebAuthn credentials.
-  raw_ptr<webauthn::PasskeyModel> _passkeyModel;
+  // All credential groups that can be exported. Only valid until `start`, at
+  // which point it is moved from and should not be accessed.
+  std::vector<password_manager::AffiliatedGroup> _affiliatedGroups;
 
   // Email of the signed in user account.
   std::string _userEmail;
@@ -55,20 +54,18 @@
 
 @synthesize baseNavigationController = _baseNavigationController;
 
-- (instancetype)initWithBaseNavigationController:
-                    (UINavigationController*)navigationController
-                                         browser:(Browser*)browser
-                         savedPasswordsPresenter:
-                             (password_manager::SavedPasswordsPresenter*)
-                                 savedPasswordsPresenter
-                                    passkeyModel:
-                                        (webauthn::PasskeyModel*)passkeyModel {
+- (instancetype)
+    initWithBaseNavigationController:
+        (UINavigationController*)navigationController
+                             browser:(Browser*)browser
+                    affiliatedGroups:
+                        (std::vector<password_manager::AffiliatedGroup>)
+                            affiliatedGroups {
   self = [super initWithBaseViewController:navigationController
                                    browser:browser];
   if (self) {
     _baseNavigationController = navigationController;
-    _savedPasswordsPresenter = savedPasswordsPresenter;
-    _passkeyModel = passkeyModel;
+    _affiliatedGroups = std::move(affiliatedGroups);
   }
   return self;
 }
@@ -77,9 +74,10 @@
   _viewController = [[CredentialExportViewController alloc] init];
 
   _mediator = [[CredentialExportMediator alloc]
-               initWithWindow:_baseNavigationController.view.window
-      savedPasswordsPresenter:_savedPasswordsPresenter
-                 passkeyModel:_passkeyModel];
+        initWithWindow:_baseNavigationController.view.window
+      affiliatedGroups:std::move(_affiliatedGroups)
+          passkeyModel:IOSPasskeyModelFactory::GetForProfile(self.profile)];
+  _affiliatedGroups = {};
   _viewController.delegate = _mediator;
   _mediator.delegate = self;
   _mediator.consumer = _viewController;
diff --git a/ios/chrome/browser/credential_exchange/coordinator/credential_export_mediator.h b/ios/chrome/browser/credential_exchange/coordinator/credential_export_mediator.h
index 643224b3..ebcc349 100644
--- a/ios/chrome/browser/credential_exchange/coordinator/credential_export_mediator.h
+++ b/ios/chrome/browser/credential_exchange/coordinator/credential_export_mediator.h
@@ -10,12 +10,11 @@
 
 #import <vector>
 
-#import "components/password_manager/core/browser/ui/affiliated_group.h"
 #import "ios/chrome/browser/credential_exchange/ui/credential_export_consumer.h"
 #import "ios/chrome/browser/credential_exchange/ui/credential_export_view_controller_presentation_delegate.h"
 
 namespace password_manager {
-class SavedPasswordsPresenter;
+class AffiliatedGroup;
 }  // namespace password_manager
 
 namespace webauthn {
@@ -43,8 +42,8 @@
 @property(nonatomic, weak) id<CredentialExportMediatorDelegate> delegate;
 
 - (instancetype)initWithWindow:(UIWindow*)window
-       savedPasswordsPresenter:
-           (password_manager::SavedPasswordsPresenter*)savedPasswordsPresenter
+              affiliatedGroups:(std::vector<password_manager::AffiliatedGroup>)
+                                   affiliatedGroups
                   passkeyModel:(webauthn::PasskeyModel*)passkeyModel
     NS_DESIGNATED_INITIALIZER;
 
diff --git a/ios/chrome/browser/credential_exchange/coordinator/credential_export_mediator.mm b/ios/chrome/browser/credential_exchange/coordinator/credential_export_mediator.mm
index 1c04bcb..521a497 100644
--- a/ios/chrome/browser/credential_exchange/coordinator/credential_export_mediator.mm
+++ b/ios/chrome/browser/credential_exchange/coordinator/credential_export_mediator.mm
@@ -17,24 +17,25 @@
   // Used as a presentation anchor for OS views. Must not be nil.
   UIWindow* _window;
 
-  // Used to fetch the user's saved passwords for export.
-  raw_ptr<password_manager::SavedPasswordsPresenter> _savedPasswordsPresenter;
-
   // Responsible for interaction with the credential export OS libraries.
   CredentialExporter* _credentialExporter;
 
+  // All credential groups that can be exported. Only valid until `setConsumer`,
+  // at which point it is moved from and should not be accessed.
+  std::vector<password_manager::AffiliatedGroup> _affiliatedGroups;
+
   // Provides access to stored WebAuthn credentials.
   raw_ptr<webauthn::PasskeyModel> _passkeyModel;
 }
 
 - (instancetype)initWithWindow:(UIWindow*)window
-       savedPasswordsPresenter:
-           (password_manager::SavedPasswordsPresenter*)savedPasswordsPresenter
+              affiliatedGroups:(std::vector<password_manager::AffiliatedGroup>)
+                                   affiliatedGroups
                   passkeyModel:(webauthn::PasskeyModel*)passkeyModel {
   self = [super init];
   if (self) {
     _window = window;
-    _savedPasswordsPresenter = savedPasswordsPresenter;
+    _affiliatedGroups = std::move(affiliatedGroups);
     _passkeyModel = passkeyModel;
   }
   return self;
@@ -46,8 +47,8 @@
   }
 
   _consumer = consumer;
-  [_consumer
-      setAffiliatedGroups:_savedPasswordsPresenter->GetAffiliatedGroups()];
+  [_consumer setAffiliatedGroups:std::move(_affiliatedGroups)];
+  _affiliatedGroups = {};
 }
 
 #pragma mark - CredentialExportViewControllerPresentationDelegate
diff --git a/ios/chrome/browser/default_promo/ui_bundled/base_default_browser_promo_view_provider.mm b/ios/chrome/browser/default_promo/ui_bundled/base_default_browser_promo_view_provider.mm
index 234d5a7..1666e512 100644
--- a/ios/chrome/browser/default_promo/ui_bundled/base_default_browser_promo_view_provider.mm
+++ b/ios/chrome/browser/default_promo/ui_bundled/base_default_browser_promo_view_provider.mm
@@ -81,7 +81,6 @@
   LogUserInteractionWithTailoredFullscreenPromo();
 
   OpenIOSDefaultBrowserSettingsPage();
-  [self dissmissPromo];
 }
 
 // The "Secondary Action" was touched.
@@ -92,8 +91,6 @@
   LogDefaultBrowserPromoHistogramForAction(
       self.defaultBrowserPromoType, IOSDefaultBrowserPromoAction::kCancel);
   LogUserInteractionWithTailoredFullscreenPromo();
-
-  [self dissmissPromo];
 }
 
 // Gesture-based actions.
@@ -155,14 +152,6 @@
                                    completion:nil];
 }
 
-// Dismiss promo.
-- (void)dissmissPromo {
-  if ([_promoViewController.actionHandler
-          respondsToSelector:@selector(confirmationAlertDismissAction)]) {
-    [_promoViewController.actionHandler confirmationAlertDismissAction];
-  }
-}
-
 // Sets resources for promo view.
 - (void)setupPromoView {
   _promoViewController.customSpacingAfterImage = 30;
diff --git a/ios/chrome/browser/default_promo/ui_bundled/default_browser_instructions_view_controller.h b/ios/chrome/browser/default_promo/ui_bundled/default_browser_instructions_view_controller.h
index e38dcbfe..ad7380bd 100644
--- a/ios/chrome/browser/default_promo/ui_bundled/default_browser_instructions_view_controller.h
+++ b/ios/chrome/browser/default_promo/ui_bundled/default_browser_instructions_view_controller.h
@@ -24,7 +24,6 @@
 // Creates the view with specified `titleText` based on provided parameters.
 // If `titleText` is nil, default title will be used.
 - (instancetype)initWithDismissButton:(BOOL)hasDismissButton
-                       hasCloseButton:(BOOL)hasCloseButton
                      hasRemindMeLater:(BOOL)hasRemindMeLater
             useDefaultAppsDestination:(BOOL)useDefaultAppsDestination
                              hasSteps:(BOOL)hasSteps
diff --git a/ios/chrome/browser/default_promo/ui_bundled/default_browser_instructions_view_controller.mm b/ios/chrome/browser/default_promo/ui_bundled/default_browser_instructions_view_controller.mm
index ece3034..5451e5b 100644
--- a/ios/chrome/browser/default_promo/ui_bundled/default_browser_instructions_view_controller.mm
+++ b/ios/chrome/browser/default_promo/ui_bundled/default_browser_instructions_view_controller.mm
@@ -55,9 +55,6 @@
 // Spacing used in the bottom alert view.
 constexpr CGFloat kSpacing = 24;
 
-// The spacing around the close button.
-constexpr CGFloat kCloseButtonSpacing = 20;
-
 // Vertical center offset for tablets.
 constexpr CGFloat kTabletCenterOffset = 40;
 }  // namespace
@@ -73,9 +70,6 @@
 // Subview for information and action part of the view.
 @property(nonatomic, strong) ConfirmationAlertViewController* alertScreen;
 
-// The navigation bar for the close button, if present.
-@property(nonatomic, strong) UINavigationBar* navigationBar;
-
 // The action handler for interactions in this View Controller.
 @property(nonatomic, weak) id<ConfirmationAlertActionHandler> actionHandler;
 
@@ -92,7 +86,6 @@
 }
 
 - (instancetype)initWithDismissButton:(BOOL)hasDismissButton
-                       hasCloseButton:(BOOL)hasCloseButton
                      hasRemindMeLater:(BOOL)hasRemindMeLater
             useDefaultAppsDestination:(BOOL)useDefaultAppsDestination
                              hasSteps:(BOOL)hasSteps
@@ -105,9 +98,6 @@
     _useDefaultAppsDestination |= IsDefaultAppsDestinationAvailable() &&
                                   IsUseDefaultAppsDestinationForPromosEnabled();
     [self addVideoSection];
-    if (hasCloseButton) {
-      [self addNavigationBarAndCloseButton];
-    }
     [self addInformationSectionWithDismissButton:hasDismissButton
                                 hasRemindMeLater:hasRemindMeLater
                        useDefaultAppsDestination:useDefaultAppsDestination
@@ -316,48 +306,6 @@
   return 0;
 }
 
-// Helper to create the navigation bar.
-- (void)addNavigationBarAndCloseButton {
-  UINavigationBar* navigationBar = [[UINavigationBar alloc] init];
-  self.navigationBar = navigationBar;
-  navigationBar.translucent = YES;
-
-  UIBarButtonSystemItem buttonType = UIBarButtonSystemItemDone;
-  if (@available(iOS 26, *)) {
-    buttonType = UIBarButtonSystemItemCancel;
-  }
-
-  UINavigationItem* navigationItem = [[UINavigationItem alloc] init];
-  UIBarButtonItem* dismissButton = [[UIBarButtonItem alloc]
-      initWithBarButtonSystemItem:buttonType
-                           target:self
-                           action:@selector(didTapNavigationBarCloseButton)];
-  navigationItem.rightBarButtonItem = dismissButton;
-
-  navigationBar.translatesAutoresizingMaskIntoConstraints = NO;
-  [navigationBar setItems:@[ navigationItem ]];
-
-  [self.view addSubview:self.navigationBar];
-
-  [NSLayoutConstraint activateConstraints:@[
-    [self.navigationBar.topAnchor
-        constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor
-                       constant:kCloseButtonSpacing],
-    [navigationBar.leadingAnchor
-        constraintEqualToAnchor:self.view.leadingAnchor],
-    [navigationBar.trailingAnchor
-        constraintEqualToAnchor:self.view.trailingAnchor]
-  ]];
-}
-
-// Handle taps on the dismiss button.
-- (void)didTapNavigationBarCloseButton {
-  if ([self.actionHandler
-          respondsToSelector:@selector(confirmationAlertDismissAction)]) {
-    [self.actionHandler confirmationAlertDismissAction];
-  }
-}
-
 - (NSString*)animationAssetName {
   if (IsDefaultBrowserPromoIpadInstructions() &&
       [[UIDevice currentDevice] userInterfaceIdiom] ==
diff --git a/ios/chrome/browser/default_promo/ui_bundled/default_browser_instructions_view_controller_unittest.mm b/ios/chrome/browser/default_promo/ui_bundled/default_browser_instructions_view_controller_unittest.mm
index 8b2cdf74..53925d5 100644
--- a/ios/chrome/browser/default_promo/ui_bundled/default_browser_instructions_view_controller_unittest.mm
+++ b/ios/chrome/browser/default_promo/ui_bundled/default_browser_instructions_view_controller_unittest.mm
@@ -77,7 +77,6 @@
   DefaultBrowserInstructionsViewController* instructionsViewController =
       [[DefaultBrowserInstructionsViewController alloc]
               initWithDismissButton:NO
-                     hasCloseButton:NO
                    hasRemindMeLater:NO
           useDefaultAppsDestination:NO
                            hasSteps:NO
@@ -98,7 +97,6 @@
   DefaultBrowserInstructionsViewController* instructionsViewController =
       [[DefaultBrowserInstructionsViewController alloc]
               initWithDismissButton:NO
-                     hasCloseButton:NO
                    hasRemindMeLater:NO
           useDefaultAppsDestination:NO
                            hasSteps:YES
@@ -120,7 +118,6 @@
   DefaultBrowserInstructionsViewController* instructionsViewController =
       [[DefaultBrowserInstructionsViewController alloc]
               initWithDismissButton:YES
-                     hasCloseButton:NO
                    hasRemindMeLater:NO
           useDefaultAppsDestination:NO
                            hasSteps:NO
@@ -142,7 +139,6 @@
   DefaultBrowserInstructionsViewController* instructionsViewController =
       [[DefaultBrowserInstructionsViewController alloc]
               initWithDismissButton:NO
-                     hasCloseButton:NO
                    hasRemindMeLater:YES
           useDefaultAppsDestination:NO
                            hasSteps:NO
@@ -163,7 +159,6 @@
   DefaultBrowserInstructionsViewController* instructionsViewController =
       [[DefaultBrowserInstructionsViewController alloc]
               initWithDismissButton:YES
-                     hasCloseButton:NO
                    hasRemindMeLater:NO
           useDefaultAppsDestination:NO
                            hasSteps:NO
diff --git a/ios/chrome/browser/default_promo/ui_bundled/features.h b/ios/chrome/browser/default_promo/ui_bundled/features.h
index 7a4382a..d29be08 100644
--- a/ios/chrome/browser/default_promo/ui_bundled/features.h
+++ b/ios/chrome/browser/default_promo/ui_bundled/features.h
@@ -7,10 +7,27 @@
 
 #import "base/feature_list.h"
 
+// Variations of Default Browser Promo Refresh.
+extern const char kDefaultBrowserPromoRefreshParam[];
+extern const char kDefaultBrowserPromoRefreshParamNoInstructions[];
+extern const char kDefaultBrowserPromoRefreshParamSystemAlertInstructions[];
+extern const char
+    kDefaultBrowserPromoRefreshParamPictureInPictureInstructions[];
+extern const char kDefaultBrowserPromoRefreshParamCarouselInstructions[];
+
 // Feature flag to enable default browser iPad specific instructions.
 BASE_DECLARE_FEATURE(kDefaultBrowserPromoIpadInstructions);
 
+// Feature flag to enable the default browser promo refresh.
+BASE_DECLARE_FEATURE(kDefaultBrowserPromoRefresh);
+
 // Returns true if the default browser iPad specific instructions are enabled.
 bool IsDefaultBrowserPromoIpadInstructions();
 
+// Returns true if the default browser promo refresh is enabled.
+bool IsDefaultBrowserPromoRefreshEnabled();
+
+// Returns the default browser promo refresh param.
+std::string DefaultBrowserPromoRefreshParam();
+
 #endif  // IOS_CHROME_BROWSER_DEFAULT_PROMO_UI_BUNDLED_FEATURES_H_
diff --git a/ios/chrome/browser/default_promo/ui_bundled/features.mm b/ios/chrome/browser/default_promo/ui_bundled/features.mm
index 4e96a20ba..56bb2fa 100644
--- a/ios/chrome/browser/default_promo/ui_bundled/features.mm
+++ b/ios/chrome/browser/default_promo/ui_bundled/features.mm
@@ -4,9 +4,33 @@
 
 #import "ios/chrome/browser/default_promo/ui_bundled/features.h"
 
+#import "base/metrics/field_trial_params.h"
+
+const char kDefaultBrowserPromoRefreshParam[] =
+    "DefaultBrowserPromoRefreshParam";
+const char kDefaultBrowserPromoRefreshParamNoInstructions[] = "NoInstructions";
+const char kDefaultBrowserPromoRefreshParamSystemAlertInstructions[] =
+    "SystemAlertInstructions";
+const char kDefaultBrowserPromoRefreshParamPictureInPictureInstructions[] =
+    "PictureInPictureInstructions";
+const char kDefaultBrowserPromoRefreshParamCarouselInstructions[] =
+    "CarouselInstructions";
+
 BASE_FEATURE(kDefaultBrowserPromoIpadInstructions,
              base::FEATURE_DISABLED_BY_DEFAULT);
 
+BASE_FEATURE(kDefaultBrowserPromoRefresh, base::FEATURE_DISABLED_BY_DEFAULT);
+
 bool IsDefaultBrowserPromoIpadInstructions() {
   return base::FeatureList::IsEnabled(kDefaultBrowserPromoIpadInstructions);
 }
+
+bool IsDefaultBrowserPromoRefreshEnabled() {
+  return base::FeatureList::IsEnabled(kDefaultBrowserPromoRefresh);
+}
+
+std::string DefaultBrowserPromoRefreshParam() {
+  return base::GetFieldTrialParamByFeatureAsString(
+      kDefaultBrowserPromoRefresh, kDefaultBrowserPromoRefreshParam,
+      kDefaultBrowserPromoRefreshParamNoInstructions);
+}
diff --git a/ios/chrome/browser/default_promo/ui_bundled/generic/default_browser_generic_promo_coordinator.mm b/ios/chrome/browser/default_promo/ui_bundled/generic/default_browser_generic_promo_coordinator.mm
index 1494c704..9f01ae1 100644
--- a/ios/chrome/browser/default_promo/ui_bundled/generic/default_browser_generic_promo_coordinator.mm
+++ b/ios/chrome/browser/default_promo/ui_bundled/generic/default_browser_generic_promo_coordinator.mm
@@ -137,21 +137,6 @@
   [_handler hidePromo];
 }
 
-- (void)confirmationAlertDismissAction {
-  if (!_firstInteractionRecorded) {
-    _firstInteractionRecorded = YES;
-    RecordDefaultBrowserPromoLastAction(IOSDefaultBrowserPromoAction::kDismiss);
-    // TODO(crbug.com/443766830): Instead of kSwipeDown, use a dedicated value
-    // for the close button.
-    base::UmaHistogramEnumeration(
-        "IOS.DefaultBrowserVideoPromo.Fullscreen",
-        IOSDefaultBrowserVideoPromoAction::kSwipeDown);
-    RecordAction(
-        UserMetricsAction("IOS.DefaultBrowserVideoPromo.Fullscreen.Dismiss"));
-  }
-  [self hidePromoAndRecordDismissal];
-}
-
 #pragma mark - UIAdaptivePresentationControllerDelegate
 
 - (void)presentationControllerDidDismiss:
@@ -179,26 +164,52 @@
 
 #pragma mark - Private
 
+// Dismisses the promo.
+- (void)dismissPromo {
+  if (!_firstInteractionRecorded) {
+    _firstInteractionRecorded = YES;
+    RecordDefaultBrowserPromoLastAction(IOSDefaultBrowserPromoAction::kDismiss);
+    // TODO(crbug.com/443766830): Instead of kSwipeDown, use a dedicated value
+    // for the close button.
+    base::UmaHistogramEnumeration(
+        "IOS.DefaultBrowserVideoPromo.Fullscreen",
+        IOSDefaultBrowserVideoPromoAction::kSwipeDown);
+    RecordAction(
+        UserMetricsAction("IOS.DefaultBrowserVideoPromo.Fullscreen.Dismiss"));
+  }
+  [self hidePromoAndRecordDismissal];
+}
+
 - (void)showPromo {
   BOOL hasRemindMeLater =
       base::FeatureList::IsEnabled(
           feature_engagement::kIPHiOSPromoDefaultBrowserReminderFeature) &&
       !_promoWasFromRemindMeLater;
-  BOOL hasCloseButton = IsPersistentDefaultBrowserPromoEnabled();
   _viewController = [[DefaultBrowserInstructionsViewController alloc]
           initWithDismissButton:YES
-                 hasCloseButton:hasCloseButton
                hasRemindMeLater:hasRemindMeLater
       useDefaultAppsDestination:_promoWasFromOffCycleTrigger
                        hasSteps:NO
                   actionHandler:self
                       titleText:nil];
 
-  CHECK(_viewController);
+  UIViewController* viewControllerToPresent = _viewController;
+  if (IsPersistentDefaultBrowserPromoEnabled()) {
+    UINavigationController* navigationController =
+        [[UINavigationController alloc]
+            initWithRootViewController:_viewController];
+    _viewController.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc]
+        initWithBarButtonSystemItem:UIBarButtonSystemItemClose
+                             target:self
+                             action:@selector(dismissPromo)];
+    viewControllerToPresent = navigationController;
+  }
+
+  CHECK(viewControllerToPresent);
   RecordAction(
       UserMetricsAction("IOS.DefaultBrowserVideoPromo.Fullscreen.Impression"));
-  _viewController.presentationController.delegate = self;
-  [self.baseViewController presentViewController:_viewController
+  viewControllerToPresent.presentationController.delegate = self;
+  [self.baseViewController presentViewController:viewControllerToPresent
                                         animated:YES
                                       completion:nil];
 }
diff --git a/ios/chrome/browser/default_promo/ui_bundled/generic/default_browser_generic_promo_coordinator_unittest.mm b/ios/chrome/browser/default_promo/ui_bundled/generic/default_browser_generic_promo_coordinator_unittest.mm
index 59e4b94a..1014e9a 100644
--- a/ios/chrome/browser/default_promo/ui_bundled/generic/default_browser_generic_promo_coordinator_unittest.mm
+++ b/ios/chrome/browser/default_promo/ui_bundled/generic/default_browser_generic_promo_coordinator_unittest.mm
@@ -164,9 +164,12 @@
       "IOS.DefaultBrowserVideoPromo.PersistedDuration", 0);
 
   // Tap the primary button.
+  UINavigationController* navigation_controller =
+      base::apple::ObjCCastStrict<UINavigationController>(
+          view_controller_.presentedViewController);
   DefaultBrowserInstructionsViewController* promo_view_controller =
       base::apple::ObjCCastStrict<DefaultBrowserInstructionsViewController>(
-          view_controller_.presentedViewController);
+          navigation_controller.topViewController);
   UIView* primary_button_view =
       FindByID(promo_view_controller.view,
                kButtonStackPrimaryActionAccessibilityIdentifier);
diff --git a/ios/chrome/browser/flags/about_flags.mm b/ios/chrome/browser/flags/about_flags.mm
index cbf22b4..6e3b7136 100644
--- a/ios/chrome/browser/flags/about_flags.mm
+++ b/ios/chrome/browser/flags/about_flags.mm
@@ -416,6 +416,30 @@
      std::size(kDownloadListCustomUIParam), nullptr},
 };
 
+// Default browser promo refresh feature flag parameters.
+const FeatureEntry::FeatureParam kDefaultBrowserPromoRefreshArm1[] = {
+    {kDefaultBrowserPromoRefreshParam,
+     kDefaultBrowserPromoRefreshParamNoInstructions}};
+const FeatureEntry::FeatureParam kDefaultBrowserPromoRefreshArm2[] = {
+    {kDefaultBrowserPromoRefreshParam,
+     kDefaultBrowserPromoRefreshParamSystemAlertInstructions}};
+const FeatureEntry::FeatureParam kDefaultBrowserPromoRefreshArm3[] = {
+    {kDefaultBrowserPromoRefreshParam,
+     kDefaultBrowserPromoRefreshParamPictureInPictureInstructions}};
+const FeatureEntry::FeatureParam kDefaultBrowserPromoRefreshArm4[] = {
+    {kDefaultBrowserPromoRefreshParam,
+     kDefaultBrowserPromoRefreshParamCarouselInstructions}};
+const FeatureEntry::FeatureVariation kDefaultBrowserPromoRefreshVariations[] = {
+    {"No instructions", kDefaultBrowserPromoRefreshArm1,
+     std::size(kDefaultBrowserPromoRefreshArm1), nullptr},
+    {"Instructions in Systems Alert", kDefaultBrowserPromoRefreshArm2,
+     std::size(kDefaultBrowserPromoRefreshArm2), nullptr},
+    {"Instructions in Picture-in-picture", kDefaultBrowserPromoRefreshArm3,
+     std::size(kDefaultBrowserPromoRefreshArm3), nullptr},
+    {"Instructions in Carousel", kDefaultBrowserPromoRefreshArm4,
+     std::size(kDefaultBrowserPromoRefreshArm4), nullptr},
+};
+
 const FeatureEntry::FeatureParam kIOSDockingPromoDisplayedAfterFRE[] = {
     {kIOSDockingPromoExperimentType, "0"}};
 const FeatureEntry::FeatureParam kIOSDockingPromoDisplayedAtAppLaunch[] = {
@@ -1773,6 +1797,13 @@
      flag_descriptions::kDefaultBrowserPromoIpadInstructionsDescription,
      flags_ui::kOsIos,
      FEATURE_VALUE_TYPE(kDefaultBrowserPromoIpadInstructions)},
+    {"default-browser-promo-refresh",
+     flag_descriptions::kDefaultBrowserPromoRefreshName,
+     flag_descriptions::kDefaultBrowserPromoRefreshDescription,
+     flags_ui::kOsIos,
+     FEATURE_WITH_PARAMS_VALUE_TYPE(kDefaultBrowserPromoRefresh,
+                                    kDefaultBrowserPromoRefreshVariations,
+                                    "DefaultBrowserPromoRefresh")},
 #if BUILDFLAG(IOS_BACKGROUND_MODE_ENABLED)
     {"feed-background-refresh-ios",
      flag_descriptions::kFeedBackgroundRefreshName,
@@ -2236,9 +2267,6 @@
      flag_descriptions::kLensGestureTextSelectionDisabledName,
      flag_descriptions::kLensGestureTextSelectionDisabledDescription,
      flags_ui::kOsIos, FEATURE_VALUE_TYPE(kLensGestureTextSelectionDisabled)},
-    {"ios-new-share-extension", flag_descriptions::kNewShareExtensionName,
-     flag_descriptions::kNewShareExtensionDescription, flags_ui::kOsIos,
-     FEATURE_VALUE_TYPE(kNewShareExtension)},
     {"ios-best-features-screen",
      flag_descriptions::kBestFeaturesScreenInFirstRunName,
      flag_descriptions::kBestFeaturesScreenInFirstRunDescription,
@@ -2355,6 +2383,9 @@
      FEATURE_WITH_PARAMS_VALUE_TYPE(kAskGeminiChip,
                                     kAskGeminiChipVariations,
                                     "IOSAskGeminiChip")},
+    {"gemini-copresence", flag_descriptions::kGeminiCopresenceName,
+     flag_descriptions::kGeminiCopresenceDescription, flags_ui::kOsIos,
+     FEATURE_VALUE_TYPE(kGeminiCopresence)},
     {"gemini-cross-tab", flag_descriptions::kGeminiCrossTabName,
      flag_descriptions::kGeminiCrossTabDescription, flags_ui::kOsIos,
      FEATURE_VALUE_TYPE(kGeminiCrossTab)},
@@ -2839,6 +2870,11 @@
      flag_descriptions::kLensOmnientShaderV2EnabledName,
      flag_descriptions::kLensOmnientShaderV2EnabledDescription,
      flags_ui::kOsIos, FEATURE_VALUE_TYPE(kLensOmnientShaderV2Enabled)},
+    {"lens-stream-service-web-channel-transport-enabled",
+     flag_descriptions::kLensStreamServiceWebChannelTransportEnabledName,
+     flag_descriptions::kLensStreamServiceWebChannelTransportEnabledDescription,
+     flags_ui::kOsIos,
+     FEATURE_VALUE_TYPE(kLensStreamServiceWebChannelTransportEnabled)},
     {"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 958437bc..bd5d5b1 100644
--- a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
+++ b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
@@ -445,6 +445,10 @@
     "When enabled, a propensity model will help make the determination of "
     "whether to show a default browser promo";
 
+const char kDefaultBrowserPromoRefreshName[] = "Default Browser Promo Refresh";
+const char kDefaultBrowserPromoRefreshDescription[] =
+    "When enabled, the default browser promo will be updated.";
+
 const char kDetectMainThreadFreezeName[] = "Detect freeze in the main thread.";
 const char kDetectMainThreadFreezeDescription[] =
     "A crash report will be uploaded if the main thread is frozen more than "
@@ -649,6 +653,11 @@
     "When enabled, the speed of the fullscreen' transition is "
     "increased-decreased.";
 
+const char kGeminiCopresenceName[] = "Gemini Copresence";
+const char kGeminiCopresenceDescription[] =
+    "Enables the Gemini Copresence feature, which provides a persistent Gemini "
+    "overlay.";
+
 const char kGeminiCrossTabName[] = "Gemini Cross Tab";
 const char kGeminiCrossTabDescription[] =
     "When enabled, the Gemini floaty conversation persists across all tabs.";
@@ -1019,6 +1028,11 @@
 const char kLensSingleTapTextSelectionDisabledDescription[] =
     "When disabled, single taps do not trigger text selections.";
 
+const char kLensStreamServiceWebChannelTransportEnabledName[] =
+    "Lens stream service web channel transport";
+const char kLensStreamServiceWebChannelTransportEnabledDescription[] =
+    "When enabled, uses web channel transport for the stream service.";
+
 const char kLensTranslateToggleModeEnabledName[] =
     "Lens translate toggle mode enabled";
 const char kLensTranslateToggleModeEnabledDescription[] =
@@ -1130,10 +1144,6 @@
     "is replaced with a native implementation which also enables searching "
     "text in PDF files. Available for iOS 16 or later.";
 
-const char kNewShareExtensionName[] = "New Share Extension for iOS";
-const char kNewShareExtensionDescription[] =
-    "Update the share extension UI and add new share entries";
-
 const char kNewTabPageFieldTrialName[] =
     "New tab page features that target new users";
 const char kNewTabPageFieldTrialDescription[] =
diff --git a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
index 23d53bf..f5f71c080 100644
--- a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
+++ b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
@@ -266,6 +266,9 @@
 extern const char kDefaultBrowserPromoPropensityModelName[];
 extern const char kDefaultBrowserPromoPropensityModelDescription[];
 
+extern const char kDefaultBrowserPromoRefreshName[];
+extern const char kDefaultBrowserPromoRefreshDescription[];
+
 extern const char kDetectMainThreadFreezeName[];
 extern const char kDetectMainThreadFreezeDescription[];
 
@@ -389,6 +392,9 @@
 extern const char kFullscreenTransitionSpeedName[];
 extern const char kFullscreenTransitionSpeedDescription[];
 
+extern const char kGeminiCopresenceName[];
+extern const char kGeminiCopresenceDescription[];
+
 extern const char kGeminiCrossTabName[];
 extern const char kGeminiCrossTabDescription[];
 
@@ -611,6 +617,9 @@
 extern const char kLensSingleTapTextSelectionDisabledName[];
 extern const char kLensSingleTapTextSelectionDisabledDescription[];
 
+extern const char kLensStreamServiceWebChannelTransportEnabledName[];
+extern const char kLensStreamServiceWebChannelTransportEnabledDescription[];
+
 extern const char kLensTranslateToggleModeEnabledName[];
 extern const char kLensTranslateToggleModeEnabledDescription[];
 
@@ -680,9 +689,6 @@
 extern const char kNativeFindInPageName[];
 extern const char kNativeFindInPageDescription[];
 
-extern const char kNewShareExtensionName[];
-extern const char kNewShareExtensionDescription[];
-
 extern const char kNewTabPageFieldTrialName[];
 extern const char kNewTabPageFieldTrialDescription[];
 
diff --git a/ios/chrome/browser/history/ui_bundled/history_entry_item.mm b/ios/chrome/browser/history/ui_bundled/history_entry_item.mm
index 9775c0bb..cd6a886 100644
--- a/ios/chrome/browser/history/ui_bundled/history_entry_item.mm
+++ b/ios/chrome/browser/history/ui_bundled/history_entry_item.mm
@@ -35,7 +35,7 @@
        accessibilityDelegate:(id<HistoryEntryItemDelegate>)delegate {
   self = [super initWithType:type];
   if (self) {
-    self.cellClass = LegacyTableViewCell.class;
+    self.cellClass = [LegacyTableViewCell class];
     _accessibilityDelegate = delegate;
   }
   return self;
diff --git a/ios/chrome/browser/infobars/ui_bundled/OWNERS b/ios/chrome/browser/infobars/ui_bundled/OWNERS
index 9a7435f4..be95fdf 100644
--- a/ios/chrome/browser/infobars/ui_bundled/OWNERS
+++ b/ios/chrome/browser/infobars/ui_bundled/OWNERS
@@ -1,2 +1,3 @@
 sczs@chromium.org
 thegreenfrog@chromium.org
+fernandex@google.com
diff --git a/ios/chrome/browser/intelligence/features/features.h b/ios/chrome/browser/intelligence/features/features.h
index 8e3d585..07d47b1 100644
--- a/ios/chrome/browser/intelligence/features/features.h
+++ b/ios/chrome/browser/intelligence/features/features.h
@@ -231,4 +231,8 @@
 BASE_DECLARE_FEATURE(kGeminiPersonalization);
 bool IsGeminiPersonalizationEnabled();
 
+// Feature flag for Gemini Copresence.
+BASE_DECLARE_FEATURE(kGeminiCopresence);
+bool IsGeminiCopresenceEnabled();
+
 #endif  // IOS_CHROME_BROWSER_INTELLIGENCE_FEATURES_FEATURES_H_
diff --git a/ios/chrome/browser/intelligence/features/features.mm b/ios/chrome/browser/intelligence/features/features.mm
index dcb3f6b..93395cb1 100644
--- a/ios/chrome/browser/intelligence/features/features.mm
+++ b/ios/chrome/browser/intelligence/features/features.mm
@@ -297,3 +297,8 @@
 bool IsGeminiPersonalizationEnabled() {
   return base::FeatureList::IsEnabled(kGeminiPersonalization);
 }
+BASE_FEATURE(kGeminiCopresence, base::FEATURE_DISABLED_BY_DEFAULT);
+
+bool IsGeminiCopresenceEnabled() {
+  return base::FeatureList::IsEnabled(kGeminiCopresence);
+}
diff --git a/ios/chrome/browser/lens/ui_bundled/features.h b/ios/chrome/browser/lens/ui_bundled/features.h
index d292d75f..bc54c4c 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 Shader V2 for Lens Omnient.
 BASE_DECLARE_FEATURE(kLensOmnientShaderV2Enabled);
 
+// Whether to enable the stream service web channel transport.
+BASE_DECLARE_FEATURE(kLensStreamServiceWebChannelTransportEnabled);
+
 #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 2266c33..e7491c15 100644
--- a/ios/chrome/browser/lens/ui_bundled/features.mm
+++ b/ios/chrome/browser/lens/ui_bundled/features.mm
@@ -71,3 +71,6 @@
 BASE_FEATURE(kLensStrokesAPIEnabled, base::FEATURE_DISABLED_BY_DEFAULT);
 
 BASE_FEATURE(kLensOmnientShaderV2Enabled, base::FEATURE_DISABLED_BY_DEFAULT);
+
+BASE_FEATURE(kLensStreamServiceWebChannelTransportEnabled,
+             base::FEATURE_DISABLED_BY_DEFAULT);
diff --git a/ios/chrome/browser/overlays/ui_bundled/infobar_banner/safe_browsing/enhanced_safe_browsing_infobar_overlay_mediator.mm b/ios/chrome/browser/overlays/ui_bundled/infobar_banner/safe_browsing/enhanced_safe_browsing_infobar_overlay_mediator.mm
index dcdf2790..4385cc9 100644
--- a/ios/chrome/browser/overlays/ui_bundled/infobar_banner/safe_browsing/enhanced_safe_browsing_infobar_overlay_mediator.mm
+++ b/ios/chrome/browser/overlays/ui_bundled/infobar_banner/safe_browsing/enhanced_safe_browsing_infobar_overlay_mediator.mm
@@ -4,14 +4,13 @@
 
 #import "ios/chrome/browser/overlays/ui_bundled/infobar_banner/safe_browsing/enhanced_safe_browsing_infobar_overlay_mediator.h"
 
+#import "base/strings/sys_string_conversions.h"
 #import "ios/chrome/browser/infobars/ui_bundled/banners/infobar_banner_consumer.h"
 #import "ios/chrome/browser/overlays/model/public/default/default_infobar_overlay_request_config.h"
 #import "ios/chrome/browser/overlays/ui_bundled/infobar_banner/infobar_banner_overlay_mediator+consumer_support.h"
 #import "ios/chrome/browser/overlays/ui_bundled/overlay_request_mediator+subclassing.h"
 #import "ios/chrome/browser/safe_browsing/model/enhanced_safe_browsing_infobar_delegate.h"
 #import "ios/chrome/browser/shared/ui/symbols/symbols.h"
-#import "ios/chrome/grit/ios_branded_strings.h"
-#import "ios/chrome/grit/ios_strings.h"
 #import "ui/base/l10n/l10n_util_mac.h"
 
 @interface EnhancedSafeBrowsingBannerOverlayMediator ()
@@ -54,9 +53,9 @@
     return;
   }
 
-  [self dismissOverlay];
-
-  delegate->ShowSafeBrowsingSettings();
+  if (delegate->Accept()) {
+    [self dismissOverlay];
+  }
 }
 
 @end
@@ -64,24 +63,33 @@
 @implementation EnhancedSafeBrowsingBannerOverlayMediator (ConsumerSupport)
 
 - (void)configureConsumer {
-  NSString* title = l10n_util::GetNSString(
-      IDS_IOS_SAFE_BROWSING_ENHANCED_PROTECTION_INFOBAR_TITLE);
-  NSString* subtitle = l10n_util::GetNSString(
-      IDS_IOS_SAFE_BROWSING_ENHANCED_PROTECTION_INFOBAR_DESCRIPTION);
-  NSString* buttonText = l10n_util::GetNSString(
-      IDS_IOS_SAFE_BROWSING_ENHANCED_PROTECTION_INFOBAR_BUTTON_TEXT);
-#if BUILDFLAG(IOS_USE_BRANDED_SYMBOLS)
-  UIImage* gIcon =
-      CustomSymbolWithPointSize(kGoogleShieldSymbol, kInfobarSymbolPointSize);
-#else
-  UIImage* gIcon =
+  EnhancedSafeBrowsingInfobarDelegate* delegate =
+      self.enhancedSafeBrowsingInfobarDelegate;
+  if (!delegate) {
+    return;
+  }
+
+  NSString* title = base::SysUTF16ToNSString(delegate->GetTitleText());
+  NSString* subtitle = base::SysUTF16ToNSString(delegate->GetMessageText());
+  NSString* buttonText = base::SysUTF16ToNSString(
+      delegate->GetButtonLabel(ConfirmInfoBarDelegate::BUTTON_OK));
+
+  // Default to the info icon.
+  UIImage* icon =
       DefaultSymbolWithPointSize(kInfoCircleSymbol, kInfobarSymbolPointSize);
+
+  // Use the shield icon only for the shield type on branded builds.
+  if (delegate->GetIconType() == EnhancedSafeBrowsingIconType::kShield) {
+#if BUILDFLAG(IOS_USE_BRANDED_SYMBOLS)
+    icon =
+        CustomSymbolWithPointSize(kGoogleShieldSymbol, kInfobarSymbolPointSize);
 #endif
+  }
 
   [self.consumer setTitleText:title];
   [self.consumer setSubtitleText:subtitle];
   [self.consumer setButtonText:buttonText];
-  [self.consumer setIconImage:gIcon];
+  [self.consumer setIconImage:icon];
   [self.consumer setPresentsModal:NO];
 }
 
diff --git a/ios/chrome/browser/passwords/ui_bundled/password_breach_mediator.mm b/ios/chrome/browser/passwords/ui_bundled/password_breach_mediator.mm
index 58ac678..39fbe143 100644
--- a/ios/chrome/browser/passwords/ui_bundled/password_breach_mediator.mm
+++ b/ios/chrome/browser/passwords/ui_bundled/password_breach_mediator.mm
@@ -86,11 +86,6 @@
 
 #pragma mark - ConfirmationAlertActionHandler
 
-- (void)confirmationAlertDismissAction {
-  self.dismissReason = LeakDialogDismissalReason::kClickedOk;
-  [self.presenter stop];
-}
-
 - (void)confirmationAlertPrimaryAction {
   if (ShouldCheckPasswords(self.credentialLeakType)) {
     self.dismissReason = LeakDialogDismissalReason::kClickedCheckPasswords;
@@ -106,12 +101,19 @@
       [self.presenter openPasswordManager];
     }
   } else {
-    [self confirmationAlertDismissAction];
+    [self dismissSheet];
   }
 }
 
 - (void)confirmationAlertSecondaryAction {
-  [self confirmationAlertDismissAction];
+  [self dismissSheet];
+}
+
+#pragma mark - Private
+
+- (void)dismissSheet {
+  self.dismissReason = LeakDialogDismissalReason::kClickedOk;
+  [self.presenter stop];
 }
 
 @end
diff --git a/ios/chrome/browser/promos_manager/ui_bundled/promos_manager_coordinator+Testing.h b/ios/chrome/browser/promos_manager/ui_bundled/promos_manager_coordinator+Testing.h
index dffba00a..b0816f9 100644
--- a/ios/chrome/browser/promos_manager/ui_bundled/promos_manager_coordinator+Testing.h
+++ b/ios/chrome/browser/promos_manager/ui_bundled/promos_manager_coordinator+Testing.h
@@ -22,8 +22,8 @@
 - (void)presentationControllerDidDismiss:
     (UIPresentationController*)presentationController;
 
-// ConfirmationAlertActionHandler.
-- (void)confirmationAlertDismissAction;
+// Dismisses the promo.
+- (void)dismissPromo;
 
 // Display promo after tracker is ready.
 - (void)displayPromoCallback;
diff --git a/ios/chrome/browser/promos_manager/ui_bundled/promos_manager_coordinator.mm b/ios/chrome/browser/promos_manager/ui_bundled/promos_manager_coordinator.mm
index 6c46169c..0828a712 100644
--- a/ios/chrome/browser/promos_manager/ui_bundled/promos_manager_coordinator.mm
+++ b/ios/chrome/browser/promos_manager/ui_bundled/promos_manager_coordinator.mm
@@ -443,6 +443,7 @@
   }
 
   [self.banneredProvider standardPromoPrimaryAction];
+  [self dismissPromo];
 }
 
 // Invoked when the secondary action button is tapped.
@@ -458,6 +459,7 @@
   } else if ([self.banneredProvider
                  respondsToSelector:@selector(standardPromoSecondaryAction)]) {
     [self.banneredProvider standardPromoSecondaryAction];
+    [self dismissPromo];
   }
 }
 
@@ -489,6 +491,7 @@
   }
 
   [self.provider standardPromoPrimaryAction];
+  [self dismissPromo];
 }
 
 - (void)confirmationAlertSecondaryAction {
@@ -500,6 +503,7 @@
   }
 
   [self.provider standardPromoSecondaryAction];
+  [self dismissPromo];
 }
 
 - (void)confirmationAlertTertiaryAction {
@@ -513,20 +517,6 @@
   [self.provider standardPromoTertiaryAction];
 }
 
-- (void)confirmationAlertDismissAction {
-  DCHECK(self.provider || self.banneredProvider);
-
-  if ([self.provider
-          respondsToSelector:@selector(standardPromoDismissAction)]) {
-    [self.provider standardPromoDismissAction];
-  } else if ([self.banneredProvider
-                 respondsToSelector:@selector(standardPromoDismissAction)]) {
-    [self.banneredProvider standardPromoDismissAction];
-  }
-
-  [self dismissViewControllers];
-}
-
 #pragma mark - UIAdaptivePresentationControllerDelegate
 
 - (void)presentationControllerDidDismiss:
@@ -541,12 +531,27 @@
     [self.banneredProvider standardPromoDismissSwipe];
     [self dismissViewControllers];
   } else {
-    [self confirmationAlertDismissAction];
+    [self dismissPromo];
   }
 }
 
 #pragma mark - Private
 
+// Dismisses the promo.
+- (void)dismissPromo {
+  DCHECK(self.provider || self.banneredProvider);
+
+  if ([self.provider
+          respondsToSelector:@selector(standardPromoDismissAction)]) {
+    [self.provider standardPromoDismissAction];
+  } else if ([self.banneredProvider
+                 respondsToSelector:@selector(standardPromoDismissAction)]) {
+    [self.banneredProvider standardPromoDismissAction];
+  }
+
+  [self dismissViewControllers];
+}
+
 - (void)dismissViewControllers {
   if (self.viewController) {
     [self.viewController.presentingViewController
diff --git a/ios/chrome/browser/promos_manager/ui_bundled/promos_manager_coordinator_unittest.mm b/ios/chrome/browser/promos_manager/ui_bundled/promos_manager_coordinator_unittest.mm
index f0b4836..5622041 100644
--- a/ios/chrome/browser/promos_manager/ui_bundled/promos_manager_coordinator_unittest.mm
+++ b/ios/chrome/browser/promos_manager/ui_bundled/promos_manager_coordinator_unittest.mm
@@ -129,7 +129,7 @@
 
   OCMExpect([provider standardPromoDismissAction]);
 
-  [coordinator_ confirmationAlertDismissAction];
+  [coordinator_ dismissPromo];
 
   [provider verify];
 }
@@ -145,7 +145,7 @@
 
   OCMExpect([banneredProvider standardPromoDismissAction]);
 
-  [coordinator_ confirmationAlertDismissAction];
+  [coordinator_ dismissPromo];
 
   [banneredProvider verify];
 }
diff --git a/ios/chrome/browser/safari_data_import/coordinator/safari_data_import_export_coordinator.mm b/ios/chrome/browser/safari_data_import/coordinator/safari_data_import_export_coordinator.mm
index d401adc..c83bd51 100644
--- a/ios/chrome/browser/safari_data_import/coordinator/safari_data_import_export_coordinator.mm
+++ b/ios/chrome/browser/safari_data_import/coordinator/safari_data_import_export_coordinator.mm
@@ -32,6 +32,10 @@
   viewController.actionHandler = self;
   _navigationController = [[UINavigationController alloc]
       initWithRootViewController:viewController];
+  viewController.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc]
+      initWithBarButtonSystemItem:UIBarButtonSystemItemCancel
+                           target:self
+                           action:@selector(didTapCancelButton)];
   _navigationController.delegate = self;
   _navigationController.modalInPresentation = YES;
   [self.baseViewController presentViewController:_navigationController
@@ -70,12 +74,6 @@
   [_importCoordinator start];
 }
 
-- (void)confirmationAlertDismissAction {
-  RecordActionOnSafariExportEducationScreen(
-      SafariDataImportExportEducationAction::kCancel);
-  [self.delegate safariDataImportCoordinatorWillDismissWorkflow:self];
-}
-
 #pragma mark - UINavigationControllerDelegate
 
 - (void)navigationController:(UINavigationController*)navigationController
@@ -91,4 +89,13 @@
   }
 }
 
+#pragma mark - Private
+
+// Dismisses the sheet.
+- (void)didTapCancelButton {
+  RecordActionOnSafariExportEducationScreen(
+      SafariDataImportExportEducationAction::kCancel);
+  [self.delegate safariDataImportCoordinatorWillDismissWorkflow:self];
+}
+
 @end
diff --git a/ios/chrome/browser/safari_data_import/ui/safari_data_import_export_view_controller.mm b/ios/chrome/browser/safari_data_import/ui/safari_data_import_export_view_controller.mm
index 843749f..e74a342 100644
--- a/ios/chrome/browser/safari_data_import/ui/safari_data_import_export_view_controller.mm
+++ b/ios/chrome/browser/safari_data_import/ui/safari_data_import_export_view_controller.mm
@@ -33,8 +33,6 @@
                    : kSafariDataExportEducationAnimation;
 }
 
-/// Vertical spacing of the instruction view.
-const CGFloat kInstructionViewVerticalSpacing = 16;
 
 /// Static text instructions to export data from Safari, formatted with
 /// paddings.
@@ -53,17 +51,7 @@
         IDS_IOS_SAFARI_IMPORT_EXPORT_STATIC_INSTRUCTION_STEP_5),
   ]];
   instruction_view.translatesAutoresizingMaskIntoConstraints = NO;
-  /// Wraps the instructions with paddings, since its superview will be the
-  /// first item in a  stack view and `directionalLayoutMargins` would not be
-  /// honored.
-  UIView* wrapper = [[UIView alloc] initWithFrame:CGRectZero];
-  wrapper.translatesAutoresizingMaskIntoConstraints = NO;
-  [wrapper addSubview:instruction_view];
-  AddSameConstraintsWithInsets(
-      instruction_view, wrapper,
-      NSDirectionalEdgeInsetsMake(kInstructionViewVerticalSpacing, 0,
-                                  kInstructionViewVerticalSpacing, 0));
-  return wrapper;
+  return instruction_view;
 }
 
 /// Text provider for the animated promo.
@@ -92,11 +80,6 @@
 @implementation SafariDataImportExportViewController
 
 - (void)viewDidLoad {
-  /// Sets up navigation bar.
-  self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc]
-      initWithBarButtonSystemItem:UIBarButtonSystemItemCancel
-                           target:self
-                           action:@selector(didTapCancelButton)];
   /// Sets up the safari data import item.
   self.animationName = GetAnimationName(/*dark_mode=*/NO);
   self.animationNameDarkMode = GetAnimationName(/*dark_mode=*/YES);
@@ -110,11 +93,4 @@
   [super viewDidLoad];
 }
 
-#pragma mark - Private
-
-/// Handles tapping the "Cancel" button in navigation.
-- (void)didTapCancelButton {
-  [self.actionHandler confirmationAlertDismissAction];
-}
-
 @end
diff --git a/ios/chrome/browser/safe_browsing/model/BUILD.gn b/ios/chrome/browser/safe_browsing/model/BUILD.gn
index b118f47ba..52d9735 100644
--- a/ios/chrome/browser/safe_browsing/model/BUILD.gn
+++ b/ios/chrome/browser/safe_browsing/model/BUILD.gn
@@ -119,7 +119,10 @@
 
   deps = [
     "//components/infobars/core",
+    "//components/strings",
+    "//ios/chrome/app/strings",
     "//ios/chrome/browser/shared/public/commands",
+    "//ui/base",
   ]
 }
 
diff --git a/ios/chrome/browser/safe_browsing/model/DEPS b/ios/chrome/browser/safe_browsing/model/DEPS
index 10d1b9a..8392449 100644
--- a/ios/chrome/browser/safe_browsing/model/DEPS
+++ b/ios/chrome/browser/safe_browsing/model/DEPS
@@ -1,4 +1,5 @@
 include_rules = [
+  "+components/strings",
   "+ios/chrome/browser/history/model",
   "+ios/chrome/browser/infobars/model",
   "+ios/chrome/browser/signin/model",
diff --git a/ios/chrome/browser/safe_browsing/model/enhanced_safe_browsing_infobar_delegate.h b/ios/chrome/browser/safe_browsing/model/enhanced_safe_browsing_infobar_delegate.h
index aeca821..6d84272f 100644
--- a/ios/chrome/browser/safe_browsing/model/enhanced_safe_browsing_infobar_delegate.h
+++ b/ios/chrome/browser/safe_browsing/model/enhanced_safe_browsing_infobar_delegate.h
@@ -5,6 +5,8 @@
 #ifndef IOS_CHROME_BROWSER_SAFE_BROWSING_MODEL_ENHANCED_SAFE_BROWSING_INFOBAR_DELEGATE_H_
 #define IOS_CHROME_BROWSER_SAFE_BROWSING_MODEL_ENHANCED_SAFE_BROWSING_INFOBAR_DELEGATE_H_
 
+#include <string>
+
 #import "components/infobars/core/confirm_infobar_delegate.h"
 
 @protocol SettingsCommands;
@@ -12,43 +14,70 @@
 class WebState;
 }
 
+// Scenarios for when to show the Enhanced Safe Browsing infobar.
+enum class EnhancedSafeBrowsingInfobarScenario {
+  // Shown when account sync occurs.
+  kAccountSync,
+  // Shown when ESB is enabled by client-sync.
+  kClientSyncEnabled,
+  // Shown when ESB is disabled by client-sync, with a "Settings" button.
+  kClientSyncDisabledWithButton,
+};
+
 // Used as enum for the IOS.SafeBrowsing.Enhanced.Infobar.Interaction histogram.
 // Keep in sync with "IOSEnhancedSafeBrowseringInfobarInteraction"
 // in tools/metrics/histograms/metadata/ios/enums.xml.
 // Entries should not be renumbered and numeric values should never be reused.
 // LINT.IfChange
-enum EnhancedSafeBrowsingInfobarInteraction {
+enum class EnhancedSafeBrowsingInfobarInteraction {
   kViewed = 0,
   kTapped = 1,
   kMaxValue = kTapped,
 };
 // LINT.ThenChange(/tools/metrics/histograms/metadata/ios/enums.xml)
 
+// Icon types for the Enhanced Safe Browsing infobar.
+enum class EnhancedSafeBrowsingIconType {
+  kShield,
+  kInfo,
+};
+
 // Delegate for infobar that prompts users to learn more about Enhanced Safe
-// Browsing and navgiates them to the Enhanced Safe Browsing settings page. when
+// Browsing and navgiates them to the Enhanced Safe Browsing settings page when
 // the package(s) are tracked or untracked.
 class EnhancedSafeBrowsingInfobarDelegate : public ConfirmInfoBarDelegate {
  public:
   EnhancedSafeBrowsingInfobarDelegate(
       web::WebState* web_state,
-      id<SettingsCommands> settings_commands_handler);
+      id<SettingsCommands> settings_commands_handler,
+      EnhancedSafeBrowsingInfobarScenario scenario,
+      const std::string& email);
 
   ~EnhancedSafeBrowsingInfobarDelegate() override;
 
-  // Navigates the user to the Safe Browsing settings menu page.
-  void ShowSafeBrowsingSettings();
-
   // Records interactions with the infobar to an UMA histogram.
   void RecordInteraction(EnhancedSafeBrowsingInfobarInteraction interaction);
 
+  // Returns the icon type that should be displayed for the current scenario.
+  EnhancedSafeBrowsingIconType GetIconType() const;
+
   // ConfirmInfoBarDelegate implementation.
   InfoBarIdentifier GetIdentifier() const override;
+  std::u16string GetTitleText() const override;
   std::u16string GetMessageText() const override;
+  int GetButtons() const override;
+  std::u16string GetButtonLabel(InfoBarButton button) const override;
   bool EqualsDelegate(infobars::InfoBarDelegate* delegate) const override;
+  bool Accept() override;
 
  private:
+  // Navigates the user to the Safe Browsing settings menu page.
+  void ShowSafeBrowsingSettings();
+
   raw_ptr<web::WebState> web_state_ = nullptr;
   id<SettingsCommands> settings_commands_handler_;
+  EnhancedSafeBrowsingInfobarScenario scenario_;
+  std::string email_;
 };
 
 #endif  // IOS_CHROME_BROWSER_SAFE_BROWSING_MODEL_ENHANCED_SAFE_BROWSING_INFOBAR_DELEGATE_H_
diff --git a/ios/chrome/browser/safe_browsing/model/enhanced_safe_browsing_infobar_delegate.mm b/ios/chrome/browser/safe_browsing/model/enhanced_safe_browsing_infobar_delegate.mm
index 2b4d644..157c8d7 100644
--- a/ios/chrome/browser/safe_browsing/model/enhanced_safe_browsing_infobar_delegate.mm
+++ b/ios/chrome/browser/safe_browsing/model/enhanced_safe_browsing_infobar_delegate.mm
@@ -7,14 +7,23 @@
 #import <UIKit/UIKit.h>
 
 #import "base/metrics/histogram_functions.h"
+#import "base/strings/utf_string_conversions.h"
 #import "components/infobars/core/infobar_delegate.h"
+#import "components/strings/grit/components_strings.h"
 #import "ios/chrome/browser/shared/public/commands/settings_commands.h"
+#import "ios/chrome/grit/ios_branded_strings.h"
+#import "ios/chrome/grit/ios_strings.h"
+#import "ui/base/l10n/l10n_util.h"
 
 EnhancedSafeBrowsingInfobarDelegate::EnhancedSafeBrowsingInfobarDelegate(
     web::WebState* web_state,
-    id<SettingsCommands> settings_commands_handler)
+    id<SettingsCommands> settings_commands_handler,
+    EnhancedSafeBrowsingInfobarScenario scenario,
+    const std::string& email)
     : web_state_(web_state),
-      settings_commands_handler_(settings_commands_handler) {}
+      settings_commands_handler_(settings_commands_handler),
+      scenario_(scenario),
+      email_(email) {}
 
 EnhancedSafeBrowsingInfobarDelegate::~EnhancedSafeBrowsingInfobarDelegate() =
     default;
@@ -30,6 +39,41 @@
                                 interaction);
 }
 
+EnhancedSafeBrowsingIconType EnhancedSafeBrowsingInfobarDelegate::GetIconType()
+    const {
+  switch (scenario_) {
+    case EnhancedSafeBrowsingInfobarScenario::kAccountSync:
+    case EnhancedSafeBrowsingInfobarScenario::kClientSyncEnabled:
+      return EnhancedSafeBrowsingIconType::kShield;
+    case EnhancedSafeBrowsingInfobarScenario::kClientSyncDisabledWithButton:
+      return EnhancedSafeBrowsingIconType::kInfo;
+  }
+}
+
+int EnhancedSafeBrowsingInfobarDelegate::GetButtons() const {
+  // Always show a button, even if it's a no-op dismissal button, because the UI
+  // always reserves space for a button.
+  return BUTTON_OK;
+}
+
+std::u16string EnhancedSafeBrowsingInfobarDelegate::GetButtonLabel(
+    InfoBarButton button) const {
+  if (button != BUTTON_OK) {
+    return std::u16string();
+  }
+
+  switch (scenario_) {
+    case EnhancedSafeBrowsingInfobarScenario::kAccountSync:
+    case EnhancedSafeBrowsingInfobarScenario::kClientSyncDisabledWithButton:
+      // Button navigates to Settings.
+      return l10n_util::GetStringUTF16(
+          IDS_IOS_SAFE_BROWSING_ENHANCED_PROTECTION_INFOBAR_BUTTON_TEXT);
+    case EnhancedSafeBrowsingInfobarScenario::kClientSyncEnabled:
+      // No-op "Okay" button to dismiss.
+      return l10n_util::GetStringUTF16(IDS_OK);
+  }
+}
+
 #pragma mark - ConfirmInfoBarDelegate
 
 infobars::InfoBarDelegate::InfoBarIdentifier
@@ -37,13 +81,46 @@
   return ENHANCED_SAFE_BROWSING_INFOBAR_DELEGATE;
 }
 
-// Returns an empty message to satisfy implementation requirement for
-// ConfirmInfoBarDelegate.
+std::u16string EnhancedSafeBrowsingInfobarDelegate::GetTitleText() const {
+  switch (scenario_) {
+    case EnhancedSafeBrowsingInfobarScenario::kAccountSync:
+      return l10n_util::GetStringUTF16(
+          IDS_IOS_SAFE_BROWSING_ENHANCED_PROTECTION_INFOBAR_TITLE);
+    case EnhancedSafeBrowsingInfobarScenario::kClientSyncEnabled:
+      return l10n_util::GetStringUTF16(
+          IDS_IOS_SAFE_BROWSING_ENHANCED_ON_INFOBAR_TITLE);
+    case EnhancedSafeBrowsingInfobarScenario::kClientSyncDisabledWithButton:
+      return l10n_util::GetStringUTF16(
+          IDS_IOS_SAFE_BROWSING_ENHANCED_OFF_INFOBAR_TITLE);
+  }
+}
+
 std::u16string EnhancedSafeBrowsingInfobarDelegate::GetMessageText() const {
-  return std::u16string();
+  switch (scenario_) {
+    case EnhancedSafeBrowsingInfobarScenario::kAccountSync:
+      return l10n_util::GetStringUTF16(
+          IDS_IOS_SAFE_BROWSING_ENHANCED_PROTECTION_INFOBAR_DESCRIPTION);
+    case EnhancedSafeBrowsingInfobarScenario::kClientSyncEnabled:
+      return base::UTF8ToUTF16(email_);
+    case EnhancedSafeBrowsingInfobarScenario::kClientSyncDisabledWithButton:
+      return std::u16string();
+  }
 }
 
 bool EnhancedSafeBrowsingInfobarDelegate::EqualsDelegate(
     infobars::InfoBarDelegate* delegate) const {
   return delegate->GetIdentifier() == GetIdentifier();
 }
+
+bool EnhancedSafeBrowsingInfobarDelegate::Accept() {
+  switch (scenario_) {
+    case EnhancedSafeBrowsingInfobarScenario::kAccountSync:
+    case EnhancedSafeBrowsingInfobarScenario::kClientSyncDisabledWithButton:
+      // Action: Navigate to settings.
+      ShowSafeBrowsingSettings();
+      return true;
+    case EnhancedSafeBrowsingInfobarScenario::kClientSyncEnabled:
+      // Action: No-op, just allow the banner to be dismissed.
+      return true;
+  }
+}
diff --git a/ios/chrome/browser/safe_browsing/ui_bundled/BUILD.gn b/ios/chrome/browser/safe_browsing/ui_bundled/BUILD.gn
index e25f0ea..7146861 100644
--- a/ios/chrome/browser/safe_browsing/ui_bundled/BUILD.gn
+++ b/ios/chrome/browser/safe_browsing/ui_bundled/BUILD.gn
@@ -8,16 +8,24 @@
     "safe_browsing_coordinator.mm",
   ]
   deps = [
+    "//components/prefs",
     "//components/safe_browsing/core/common",
+    "//components/safe_browsing/core/common:safe_browsing_prefs",
+    "//components/signin/public/identity_manager",
     "//ios/chrome/browser/infobars/model",
     "//ios/chrome/browser/safe_browsing/model",
     "//ios/chrome/browser/safe_browsing/model:infobar_delegate",
+    "//ios/chrome/browser/settings/ui_bundled:settings_root",
     "//ios/chrome/browser/settings/ui_bundled/privacy:ui",
     "//ios/chrome/browser/shared/coordinator/chrome_coordinator",
+    "//ios/chrome/browser/shared/coordinator/scene:scene_state_header",
     "//ios/chrome/browser/shared/model/browser",
+    "//ios/chrome/browser/shared/model/profile",
     "//ios/chrome/browser/shared/model/web_state_list",
     "//ios/chrome/browser/shared/public/commands",
+    "//ios/chrome/browser/signin/model",
     "//ios/chrome/browser/tabs/model:dependency_installer",
     "//ios/components/security_interstitials/safe_browsing",
+    "//ios/web/public",
   ]
 }
diff --git a/ios/chrome/browser/safe_browsing/ui_bundled/DEPS b/ios/chrome/browser/safe_browsing/ui_bundled/DEPS
index 152b930c..fa57c704 100644
--- a/ios/chrome/browser/safe_browsing/ui_bundled/DEPS
+++ b/ios/chrome/browser/safe_browsing/ui_bundled/DEPS
@@ -1,5 +1,6 @@
 include_rules = [
   "+ios/chrome/browser/infobars/model",
-  "+ios/chrome/browser/settings/ui_bundled/privacy/privacy_safe_browsing_coordinator.h",
+  "+ios/chrome/browser/settings/ui_bundled",
+  "+ios/chrome/browser/signin/model",
   "+ios/chrome/browser/tabs/model/tabs_dependency_installer_bridge.h",
 ]
diff --git a/ios/chrome/browser/safe_browsing/ui_bundled/safe_browsing_coordinator.h b/ios/chrome/browser/safe_browsing/ui_bundled/safe_browsing_coordinator.h
index d7dec7c..68d4f1d 100644
--- a/ios/chrome/browser/safe_browsing/ui_bundled/safe_browsing_coordinator.h
+++ b/ios/chrome/browser/safe_browsing/ui_bundled/safe_browsing_coordinator.h
@@ -6,10 +6,12 @@
 #define IOS_CHROME_BROWSER_SAFE_BROWSING_UI_BUNDLED_SAFE_BROWSING_COORDINATOR_H_
 
 #import "ios/chrome/browser/shared/coordinator/chrome_coordinator/chrome_coordinator.h"
+#import "ios/components/security_interstitials/safe_browsing/safe_browsing_tab_helper_delegate.h"
 
 // Used as a delegate for SafeBrowsingTabHelper and makes initial call to open
 // safe browsing settings from interstitial page.
-@interface SafeBrowsingCoordinator : ChromeCoordinator
+@interface SafeBrowsingCoordinator
+    : ChromeCoordinator <SafeBrowsingTabHelperDelegate>
 
 @end
 
diff --git a/ios/chrome/browser/safe_browsing/ui_bundled/safe_browsing_coordinator.mm b/ios/chrome/browser/safe_browsing/ui_bundled/safe_browsing_coordinator.mm
index 238567a..612505c7 100644
--- a/ios/chrome/browser/safe_browsing/ui_bundled/safe_browsing_coordinator.mm
+++ b/ios/chrome/browser/safe_browsing/ui_bundled/safe_browsing_coordinator.mm
@@ -5,30 +5,45 @@
 #import "ios/chrome/browser/safe_browsing/ui_bundled/safe_browsing_coordinator.h"
 
 #import "base/feature_list.h"
+#import "components/prefs/pref_change_registrar.h"
+#import "components/prefs/pref_service.h"
 #import "components/safe_browsing/core/common/features.h"
+#import "components/safe_browsing/core/common/safe_browsing_prefs.h"
+#import "components/signin/public/identity_manager/identity_manager.h"
 #import "ios/chrome/browser/infobars/model/infobar_ios.h"
 #import "ios/chrome/browser/infobars/model/infobar_manager_impl.h"
 #import "ios/chrome/browser/safe_browsing/model/enhanced_safe_browsing_infobar_delegate.h"
 #import "ios/chrome/browser/settings/ui_bundled/privacy/privacy_safe_browsing_coordinator.h"
+#import "ios/chrome/browser/settings/ui_bundled/settings_navigation_controller.h"
+#import "ios/chrome/browser/shared/coordinator/scene/scene_state.h"
 #import "ios/chrome/browser/shared/model/browser/browser.h"
+#import "ios/chrome/browser/shared/model/profile/profile_ios.h"
 #import "ios/chrome/browser/shared/model/web_state_list/web_state_list.h"
 #import "ios/chrome/browser/shared/public/commands/command_dispatcher.h"
 #import "ios/chrome/browser/shared/public/commands/settings_commands.h"
+#import "ios/chrome/browser/signin/model/identity_manager_factory.h"
 #import "ios/chrome/browser/tabs/model/tabs_dependency_installer_bridge.h"
 #import "ios/components/security_interstitials/safe_browsing/safe_browsing_tab_helper.h"
 #import "ios/components/security_interstitials/safe_browsing/safe_browsing_tab_helper_delegate.h"
+#import "ios/web/public/web_state.h"
 
-@interface SafeBrowsingCoordinator () <SafeBrowsingTabHelperDelegate,
-                                       TabsDependencyInstalling>
+// Forward-declaration of the private method so subsequent code can call it.
+@interface SafeBrowsingCoordinator () <TabsDependencyInstalling>
 
 // The WebStateList that this mediator listens for any changes on the active web
 // state.
 @property(nonatomic, readonly) WebStateList* webStateList;
 
+- (void)handlePreferenceChange;
+- (void)showSafeBrowsingSyncInfobarForState:(BOOL)isEnhancedProtectionEnabled
+                                  withEmail:(const std::string&)email;
+
 @end
 
 @implementation SafeBrowsingCoordinator {
   TabsDependencyInstallerBridge _dependencyInstallerBridge;
+  PrefChangeRegistrar _prefChangeRegistrar;
+  BOOL _pendingBannerPreferenceChange;
 }
 
 - (instancetype)initWithBaseViewController:(UIViewController*)viewController
@@ -38,6 +53,17 @@
     _webStateList = browser->GetWebStateList();
     _dependencyInstallerBridge.StartObserving(
         self, browser, TabsDependencyInstaller::Policy::kAccordingToFeature);
+
+    // Initialize observations.
+    _prefChangeRegistrar.Init(browser->GetProfile()->GetPrefs());
+    __weak __typeof(self) weakSelf = self;
+    _prefChangeRegistrar.Add(prefs::kSafeBrowsingEnabled, base::BindRepeating(^{
+                               [weakSelf handlePreferenceChange];
+                             }));
+    _prefChangeRegistrar.Add(prefs::kSafeBrowsingEnhanced,
+                             base::BindRepeating(^{
+                               [weakSelf handlePreferenceChange];
+                             }));
   }
   return self;
 }
@@ -63,8 +89,9 @@
   id<SettingsCommands> settingsHandler = HandlerForProtocol(
       self.browser->GetCommandDispatcher(), SettingsCommands);
   std::unique_ptr<EnhancedSafeBrowsingInfobarDelegate> delegate =
-      std::make_unique<EnhancedSafeBrowsingInfobarDelegate>(activeWebState,
-                                                            settingsHandler);
+      std::make_unique<EnhancedSafeBrowsingInfobarDelegate>(
+          activeWebState, settingsHandler,
+          EnhancedSafeBrowsingInfobarScenario::kAccountSync, /*email=*/"");
   delegate->RecordInteraction(EnhancedSafeBrowsingInfobarInteraction::kViewed);
 
   infobars::InfoBarManager* infobar_manager =
@@ -75,6 +102,79 @@
   infobar_manager->AddInfoBar(std::move(infobar), /*replace_existing=*/true);
 }
 
+#pragma mark - Private
+
+- (void)handlePreferenceChange {
+  Browser* browser = self.browser;
+  if (!browser) {
+    return;
+  }
+  ProfileIOS* profile = browser->GetProfile();
+  if (!profile) {
+    return;
+  }
+
+  web::WebState* webState = browser->GetWebStateList()->GetActiveWebState();
+  if (!webState) {
+    // No active WebState to show the banner on. Set a flag to try again
+    // when a tab is next activated.
+    _pendingBannerPreferenceChange = YES;
+    return;
+  }
+
+  // If we've reached this point, we are about to show a banner (or explicitly
+  // not show one), so reset the pending flag.
+  _pendingBannerPreferenceChange = NO;
+
+  signin::IdentityManager* identityManager =
+      IdentityManagerFactory::GetForProfile(profile);
+  std::string email;
+  if (identityManager &&
+      identityManager->HasPrimaryAccount(signin::ConsentLevel::kSignin)) {
+    email =
+        identityManager->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin)
+            .email;
+  }
+
+  BOOL isSettingsVisible = [self.baseViewController.presentedViewController
+      isKindOfClass:[SettingsNavigationController class]];
+
+  PrefService* prefService = profile->GetPrefs();
+  bool isEnhancedProtectionEnabled =
+      safe_browsing::IsEnhancedProtectionEnabled(*prefService);
+
+  if (isEnhancedProtectionEnabled) {
+    [self showSafeBrowsingSyncInfobarForState:YES withEmail:email];
+  } else if (!isSettingsVisible) {
+    // Only show the "off" banner if the user is not on the settings page.
+    [self showSafeBrowsingSyncInfobarForState:NO withEmail:""];
+  }
+}
+
+- (void)showSafeBrowsingSyncInfobarForState:(BOOL)isEnhancedProtectionEnabled
+                                  withEmail:(const std::string&)email {
+  EnhancedSafeBrowsingInfobarScenario scenario;
+  if (isEnhancedProtectionEnabled) {
+    scenario = EnhancedSafeBrowsingInfobarScenario::kClientSyncEnabled;
+  } else {
+    scenario =
+        EnhancedSafeBrowsingInfobarScenario::kClientSyncDisabledWithButton;
+  }
+
+  web::WebState* activeWebState = _webStateList->GetActiveWebState();
+  id<SettingsCommands> settingsHandler = HandlerForProtocol(
+      self.browser->GetCommandDispatcher(), SettingsCommands);
+
+  auto delegate = std::make_unique<EnhancedSafeBrowsingInfobarDelegate>(
+      activeWebState, settingsHandler, scenario, email);
+
+  infobars::InfoBarManager* infobar_manager =
+      InfoBarManagerImpl::FromWebState(activeWebState);
+  auto infobar = std::make_unique<InfoBarIOS>(
+      InfobarType::kInfobarTypeEnhancedSafeBrowsing, std::move(delegate));
+  infobar_manager->AddInfoBar(std::move(infobar), /*replace_existing=*/true);
+}
+
 #pragma mark - TabsDependencyInstalling
 
 - (void)webStateInserted:(web::WebState*)webState {
@@ -91,7 +191,9 @@
 
 - (void)newWebStateActivated:(web::WebState*)newActive
            oldActiveWebState:(web::WebState*)oldActive {
-  // Nothing to do.
+  if (_pendingBannerPreferenceChange) {
+    [self handlePreferenceChange];
+  }
 }
 
 @end
diff --git a/ios/chrome/browser/scanner/ui_bundled/camera_controller.mm b/ios/chrome/browser/scanner/ui_bundled/camera_controller.mm
index 33a0c06..a661680 100644
--- a/ios/chrome/browser/scanner/ui_bundled/camera_controller.mm
+++ b/ios/chrome/browser/scanner/ui_bundled/camera_controller.mm
@@ -44,11 +44,6 @@
 - (void)stopReceivingNotifications;
 // Returns the camera attached to `_captureSession`.
 - (AVCaptureDevice*)camera;
-#if !defined(__IPHONE_17_0) || __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_17_0
-// Returns the AVCaptureVideoOrientation to compensate for the current
-// UIInterfaceOrientation. Defaults to AVCaptureVideoOrientationPortrait.
-- (AVCaptureVideoOrientation)videoOrientationForCurrentInterfaceOrientation;
-#endif
 
 @end
 
@@ -330,18 +325,6 @@
   return [captureSessionInput device];
 }
 
-#if !defined(__IPHONE_17_0) || __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_17_0
-- (AVCaptureVideoOrientation)videoOrientationForCurrentInterfaceOrientation {
-  UIInterfaceOrientation orientation = GetInterfaceOrientation();
-  switch (orientation) {
-    case UIInterfaceOrientationUnknown:
-      return AVCaptureVideoOrientationPortrait;
-    default:
-      return static_cast<AVCaptureVideoOrientation>(orientation);
-  }
-}
-#endif
-
 #pragma mark - Notification Handlers
 
 - (void)handleAVCaptureSessionRuntimeError:(NSNotification*)notification {
diff --git a/ios/chrome/browser/search_engine_choice/ui/search_engine_choice_view_controller.mm b/ios/chrome/browser/search_engine_choice/ui/search_engine_choice_view_controller.mm
index 7cd79e37..6199de0 100644
--- a/ios/chrome/browser/search_engine_choice/ui/search_engine_choice_view_controller.mm
+++ b/ios/chrome/browser/search_engine_choice/ui/search_engine_choice_view_controller.mm
@@ -1069,25 +1069,14 @@
 
 #pragma mark - UITextViewDelegate
 
-#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_17_0
-- (BOOL)textView:(UITextView*)textView
-    shouldInteractWithURL:(NSURL*)URL
-                  inRange:(NSRange)characterRange
-              interaction:(UITextItemInteraction)interaction {
-  [self.actionDelegate showLearnMore];
-  return NO;
-}
-
-#else
 - (UIAction*)textView:(UITextView*)textView
     primaryActionForTextItem:(UITextItem*)textItem
-               defaultAction:(UIAction*)defaultAction API_AVAILABLE(ios(17.0)) {
+               defaultAction:(UIAction*)defaultAction {
   __weak __typeof(self) weakSelf = self;
   return [UIAction actionWithHandler:^(UIAction* action) {
     [weakSelf.actionDelegate showLearnMore];
   }];
 }
-#endif
 
 - (void)textViewDidChangeSelection:(UITextView*)textView {
   // Always force the `selectedTextRange` to `nil` to prevent users from
diff --git a/ios/chrome/browser/settings/ui_bundled/default_browser/default_browser_settings_table_view_controller.mm b/ios/chrome/browser/settings/ui_bundled/default_browser/default_browser_settings_table_view_controller.mm
index 25a6b8f..00c462bb 100644
--- a/ios/chrome/browser/settings/ui_bundled/default_browser/default_browser_settings_table_view_controller.mm
+++ b/ios/chrome/browser/settings/ui_bundled/default_browser/default_browser_settings_table_view_controller.mm
@@ -135,7 +135,6 @@
   _instructionsViewController =
       [[DefaultBrowserInstructionsViewController alloc]
               initWithDismissButton:NO
-                     hasCloseButton:NO
                    hasRemindMeLater:NO
           useDefaultAppsDestination:_useDefaultAppsDestination
                            hasSteps:YES
diff --git a/ios/chrome/browser/settings/ui_bundled/google_services/manage_accounts/manage_accounts_table_egtest.mm b/ios/chrome/browser/settings/ui_bundled/google_services/manage_accounts/manage_accounts_table_egtest.mm
index 0fd4feae..a71cf74 100644
--- a/ios/chrome/browser/settings/ui_bundled/google_services/manage_accounts/manage_accounts_table_egtest.mm
+++ b/ios/chrome/browser/settings/ui_bundled/google_services/manage_accounts/manage_accounts_table_egtest.mm
@@ -196,8 +196,7 @@
 // identity. And finally the remove identity confirmation dialog is opened a
 // third time to remove a second identity.
 // The goal of this test is to confirm the dialog can be opened several times.
-// TODO(crbug.com/460742009): Test is flaky.
-- (void)FLAKY_testRemoveAccountSeveralTime {
+- (void)testRemoveAccountSeveralTime {
   FakeSystemIdentity* fakeIdentity1 = [FakeSystemIdentity fakeIdentity1];
   FakeSystemIdentity* fakeIdentity2 = [FakeSystemIdentity fakeIdentity2];
   FakeSystemIdentity* fakeIdentity3 = [FakeSystemIdentity fakeIdentity3];
@@ -219,13 +218,12 @@
 
   // Cancel it.
   if ([ChromeEarlGrey isIPadIdiom] || iOS26_OR_ABOVE()) {
-    // There is no Cancel button on ipad and on newer iOS versions.
-    // Tap on Remove fakeIdentity1 button to dismiss the alert.
+    // Cancel it by tapping on the backgrounded item.
     [[EarlGrey
-        selectElementWithMatcher:
-            grey_accessibilityID(
-                [kSettingsAccountsRemoveAccountButtonAccessibilityIdentifier
-                    stringByAppendingString:fakeIdentity1.userEmail])]
+        selectElementWithMatcher:grey_allOf(grey_accessibilityLabel(
+                                                fakeIdentity1.userEmail),
+                                            grey_text(fakeIdentity1.userEmail),
+                                            grey_sufficientlyVisible(), nil)]
         performAction:grey_tap()];
   } else {
     [[EarlGrey selectElementWithMatcher:chrome_test_util::CancelButton()]
diff --git a/ios/chrome/browser/settings/ui_bundled/password/BUILD.gn b/ios/chrome/browser/settings/ui_bundled/password/BUILD.gn
index 5f8e79f..aac025e1 100644
--- a/ios/chrome/browser/settings/ui_bundled/password/BUILD.gn
+++ b/ios/chrome/browser/settings/ui_bundled/password/BUILD.gn
@@ -20,6 +20,9 @@
     "//components/sync/service",
     "//ios/chrome/app/strings",
     "//ios/chrome/browser/authentication/trusted_vault_reauthentication/coordinator",
+    "//ios/chrome/browser/authentication/ui_bundled:continuation",
+    "//ios/chrome/browser/authentication/ui_bundled/signin:constants",
+    "//ios/chrome/browser/authentication/ui_bundled/signin:signin_headers",
     "//ios/chrome/browser/credential_exchange/coordinator:import_coordinator",
     "//ios/chrome/browser/favicon/model",
     "//ios/chrome/browser/feature_engagement/model",
diff --git a/ios/chrome/browser/settings/ui_bundled/password/password_settings/password_settings_coordinator.mm b/ios/chrome/browser/settings/ui_bundled/password/password_settings/password_settings_coordinator.mm
index 93ec046..9b1c677 100644
--- a/ios/chrome/browser/settings/ui_bundled/password/password_settings/password_settings_coordinator.mm
+++ b/ios/chrome/browser/settings/ui_bundled/password/password_settings/password_settings_coordinator.mm
@@ -374,9 +374,8 @@
       _credentialExportCoordinator = [[CredentialExportCoordinator alloc]
           initWithBaseNavigationController:_settingsNavigationController
                                    browser:self.browser
-                   savedPasswordsPresenter:_savedPasswordsPresenter.get()
-                              passkeyModel:IOSPasskeyModelFactory::
-                                               GetForProfile(self.profile)];
+                          affiliatedGroups:_savedPasswordsPresenter
+                                               ->GetAffiliatedGroups()];
       [_credentialExportCoordinator start];
       return;
     }
diff --git a/ios/chrome/browser/settings/ui_bundled/password/passwords_coordinator.mm b/ios/chrome/browser/settings/ui_bundled/password/passwords_coordinator.mm
index b4bcb22b5..42f15f4 100644
--- a/ios/chrome/browser/settings/ui_bundled/password/passwords_coordinator.mm
+++ b/ios/chrome/browser/settings/ui_bundled/password/passwords_coordinator.mm
@@ -10,10 +10,15 @@
 #import "components/feature_engagement/public/tracker.h"
 #import "components/keyed_service/core/service_access_type.h"
 #import "components/password_manager/core/browser/ui/credential_ui_entry.h"
+#import "components/signin/public/base/signin_metrics.h"
+#import "components/signin/public/identity_manager/identity_manager.h"
 #import "components/sync/service/sync_service_utils.h"
 #import "components/trusted_vault/trusted_vault_server_constants.h"
 #import "ios/chrome/browser/authentication/trusted_vault_reauthentication/coordinator/trusted_vault_reauthentication_coordinator.h"
 #import "ios/chrome/browser/authentication/trusted_vault_reauthentication/coordinator/trusted_vault_reauthentication_coordinator_delegate.h"
+#import "ios/chrome/browser/authentication/ui_bundled/continuation.h"
+#import "ios/chrome/browser/authentication/ui_bundled/signin/signin_context_style.h"
+#import "ios/chrome/browser/authentication/ui_bundled/signin/signin_coordinator.h"
 #import "ios/chrome/browser/credential_exchange/coordinator/credential_import_coordinator.h"
 #import "ios/chrome/browser/favicon/model/favicon_loader.h"
 #import "ios/chrome/browser/favicon/model/ios_chrome_favicon_loader_factory.h"
@@ -117,6 +122,9 @@
 
   // The coordinator for the Credential Exchange feature handling the import.
   CredentialImportCoordinator* _credentialImportCoordinator;
+
+  // If needed, used for sign-in during the Credential Exchange import flow.
+  SigninCoordinator* _signinCoordinator;
 }
 
 @synthesize baseNavigationController = _baseNavigationController;
@@ -229,6 +237,7 @@
   self.addPasswordCoordinator = nil;
 
   [self dismissCredentialImportCoordinator];
+  [self dismissSigninCoordinator];
 
   [self.reauthCoordinator stop];
   self.reauthCoordinator.delegate = nil;
@@ -636,11 +645,55 @@
   _trustedVaultReauthenticationCoordinator = nil;
 }
 
-// Starts the credential import coordinator.
+// Starts the credential import. If the user is signed-in, then displays the
+// credential import sheet. Otherwise, display a sign-in sheet.
 - (void)startCredentialImport {
   CHECK(self.credentialImportUUID);
+  signin::IdentityManager* identityManager =
+      IdentityManagerFactory::GetForProfile(self.profile);
+  if (identityManager->HasPrimaryAccount(signin::ConsentLevel::kSignin)) {
+    [self startCredentialImportCoordinator];
+    return;
+  }
 
-  // TODO(crbug.com/464469872): Display sign-in sheet when no user signed-in.
+  CHECK(!_signinCoordinator, base::NotFatalUntil::M151);
+  signin_metrics::AccessPoint accessPoint =
+      signin_metrics::AccessPoint::kSettings;
+  _signinCoordinator = [SigninCoordinator
+      consistencyPromoSigninCoordinatorWithBaseViewController:
+          self.viewController
+                                                      browser:self.browser
+                                                 contextStyle:
+                                                     SigninContextStyle::
+                                                         kDefault
+                                                  accessPoint:accessPoint
+                                         prepareChangeProfile:nil
+                                         continuationProvider:
+                                             DoNothingContinuationProvider()];
+  __weak __typeof(self) weakSelf = self;
+  _signinCoordinator.signinCompletion =
+      ^(SigninCoordinator* coordinator, SigninCoordinatorResult result,
+        id<SystemIdentity> identity) {
+        [weakSelf signinForImportFinishedWithCoordinator:coordinator
+                                                identity:identity];
+      };
+  [_signinCoordinator start];
+}
+
+// Handles signin completion for credential import. If user successfully signed
+// in, starts the credential import coordinator. Otherwise, just returns as the
+// import should not start.
+- (void)signinForImportFinishedWithCoordinator:(SigninCoordinator*)coordinator
+                                      identity:(id<SystemIdentity>)identity {
+  CHECK_EQ(coordinator, _signinCoordinator);
+  [self dismissSigninCoordinator];
+  if (identity) {
+    [self startCredentialImportCoordinator];
+  }
+}
+
+// Starts the credential import coordinator.
+- (void)startCredentialImportCoordinator {
   // TODO(crbug.com/450982128): Dismiss reauth coordinator before starting.
   _credentialImportCoordinator = [[CredentialImportCoordinator alloc]
       initWithBaseViewController:self.viewController
@@ -658,4 +711,10 @@
   _credentialImportCoordinator = nil;
 }
 
+// Stops the sign-in coordinator.
+- (void)dismissSigninCoordinator {
+  [_signinCoordinator stop];
+  _signinCoordinator = nil;
+}
+
 @end
diff --git a/ios/chrome/browser/shared/public/features/features.h b/ios/chrome/browser/shared/public/features/features.h
index ed6e48f..9701f6ed 100644
--- a/ios/chrome/browser/shared/public/features/features.h
+++ b/ios/chrome/browser/shared/public/features/features.h
@@ -900,9 +900,6 @@
 
 bool IsRefactorToolbarsSize();
 
-// Feature flag to enable the new share extension UI and entries.
-BASE_DECLARE_FEATURE(kNewShareExtension);
-
 // Feature that disables all IPH messages.
 BASE_DECLARE_FEATURE(kIPHAblation);
 
diff --git a/ios/chrome/browser/shared/public/features/features.mm b/ios/chrome/browser/shared/public/features/features.mm
index 60a105d5..ba243f3 100644
--- a/ios/chrome/browser/shared/public/features/features.mm
+++ b/ios/chrome/browser/shared/public/features/features.mm
@@ -887,8 +887,6 @@
   return base::FeatureList::IsEnabled(kRefactorToolbarsSize);
 }
 
-BASE_FEATURE(kNewShareExtension, base::FEATURE_DISABLED_BY_DEFAULT);
-
 BASE_FEATURE(kIPHAblation, base::FEATURE_DISABLED_BY_DEFAULT);
 
 BASE_FEATURE(kLensOverlayDisableIPHPanGesture,
diff --git a/ios/chrome/browser/shared/ui/table_view/cells/legacy_table_view_cell.mm b/ios/chrome/browser/shared/ui/table_view/cells/legacy_table_view_cell.mm
index 111c9f33..b92947a 100644
--- a/ios/chrome/browser/shared/ui/table_view/cells/legacy_table_view_cell.mm
+++ b/ios/chrome/browser/shared/ui/table_view/cells/legacy_table_view_cell.mm
@@ -84,6 +84,7 @@
         self.accessoryView != nil;
     [configuration setHasAccessoryView:hasAccessoryView];
     self.contentConfiguration = configuration;
+    self.separatorInset = [configuration separatorInsets];
   }
   [super updateConfigurationUsingState:state];
 }
diff --git a/ios/chrome/browser/shared/ui/table_view/cells/table_view_cell.mm b/ios/chrome/browser/shared/ui/table_view/cells/table_view_cell.mm
index 3167fc7c..87dba1e 100644
--- a/ios/chrome/browser/shared/ui/table_view/cells/table_view_cell.mm
+++ b/ios/chrome/browser/shared/ui/table_view/cells/table_view_cell.mm
@@ -35,6 +35,7 @@
         self.accessoryView != nil;
     [configuration setHasAccessoryView:hasAccessoryView];
     self.contentConfiguration = configuration;
+    self.separatorInset = [configuration separatorInsets];
   }
   [super updateConfigurationUsingState:state];
 }
diff --git a/ios/chrome/browser/shared/ui/table_view/cells/table_view_text_header_footer_item.mm b/ios/chrome/browser/shared/ui/table_view/cells/table_view_text_header_footer_item.mm
index be6d5ac8..065f3c76 100644
--- a/ios/chrome/browser/shared/ui/table_view/cells/table_view_text_header_footer_item.mm
+++ b/ios/chrome/browser/shared/ui/table_view/cells/table_view_text_header_footer_item.mm
@@ -236,24 +236,9 @@
 
 #pragma mark - UITextViewDelegate
 
-#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_17_0
-- (BOOL)textView:(UITextView*)textView
-    shouldInteractWithURL:(NSURL*)URL
-                  inRange:(NSRange)characterRange
-              interaction:(UITextItemInteraction)interaction {
-  DCHECK(self.subtitleView == textView);
-  CrURL* crurl = [[CrURL alloc] initWithNSURL:URL];
-  DCHECK(crurl.gurl.is_valid());
-
-  [self.delegate view:self didTapLinkURL:crurl];
-  // Returns NO as the app is handling the opening of the URL.
-  return NO;
-}
-#endif
-
 - (UIAction*)textView:(UITextView*)textView
     primaryActionForTextItem:(UITextItem*)textItem
-               defaultAction:(UIAction*)defaultAction API_AVAILABLE(ios(17.0)) {
+               defaultAction:(UIAction*)defaultAction {
   CHECK(self.subtitleView == textView);
   NSURL* URL = textItem.link;
   CrURL* crurl = [[CrURL alloc] initWithNSURL:URL];
diff --git a/ios/chrome/browser/shared/ui/table_view/content_configuration/BUILD.gn b/ios/chrome/browser/shared/ui/table_view/content_configuration/BUILD.gn
index 41fa4d51..18d00ca 100644
--- a/ios/chrome/browser/shared/ui/table_view/content_configuration/BUILD.gn
+++ b/ios/chrome/browser/shared/ui/table_view/content_configuration/BUILD.gn
@@ -50,3 +50,19 @@
     "chrome_main_content_configuration.h",
   ]
 }
+
+source_set("unit_tests") {
+  testonly = true
+  sources = [
+    "colorful_symbol_content_configuration_unittest.mm",
+    "favicon_content_configuration_unittest.mm",
+    "image_content_configuration_unittest.mm",
+    "info_button_content_configuration_unittest.mm",
+    "switch_content_configuration_unittest.mm",
+  ]
+  deps = [
+    ":content_configuration",
+    "//ios/chrome/common/ui/table_view:cells_constants",
+    "//testing/gtest",
+  ]
+}
diff --git a/ios/chrome/browser/shared/ui/table_view/content_configuration/chrome_content_configuration.h b/ios/chrome/browser/shared/ui/table_view/content_configuration/chrome_content_configuration.h
index 8e0b37f..5e7ccff 100644
--- a/ios/chrome/browser/shared/ui/table_view/content_configuration/chrome_content_configuration.h
+++ b/ios/chrome/browser/shared/ui/table_view/content_configuration/chrome_content_configuration.h
@@ -15,6 +15,9 @@
 // This is the same method as `makeContentView`, but with a more specific type.
 - (UIView<ChromeContentView>*)makeChromeContentView;
 
+// Returns the size of the content of this view.
+- (CGSize)contentSize;
+
 @end
 
 #endif  // IOS_CHROME_BROWSER_SHARED_UI_TABLE_VIEW_CONTENT_CONFIGURATION_CHROME_CONTENT_CONFIGURATION_H_
diff --git a/ios/chrome/browser/shared/ui/table_view/content_configuration/chrome_main_content_configuration.h b/ios/chrome/browser/shared/ui/table_view/content_configuration/chrome_main_content_configuration.h
index a456380..13d6ee6b 100644
--- a/ios/chrome/browser/shared/ui/table_view/content_configuration/chrome_main_content_configuration.h
+++ b/ios/chrome/browser/shared/ui/table_view/content_configuration/chrome_main_content_configuration.h
@@ -14,6 +14,9 @@
 // accessory view.
 - (void)setHasAccessoryView:(BOOL)hasAccessoryView;
 
+// Returns the insets for the separator.
+- (UIEdgeInsets)separatorInsets;
+
 @end
 
 #endif  // IOS_CHROME_BROWSER_SHARED_UI_TABLE_VIEW_CONTENT_CONFIGURATION_CHROME_MAIN_CONTENT_CONFIGURATION_H_
diff --git a/ios/chrome/browser/shared/ui/table_view/content_configuration/colorful_symbol_content_configuration.mm b/ios/chrome/browser/shared/ui/table_view/content_configuration/colorful_symbol_content_configuration.mm
index 732ba464..62ea5ba0 100644
--- a/ios/chrome/browser/shared/ui/table_view/content_configuration/colorful_symbol_content_configuration.mm
+++ b/ios/chrome/browser/shared/ui/table_view/content_configuration/colorful_symbol_content_configuration.mm
@@ -5,6 +5,7 @@
 #import "ios/chrome/browser/shared/ui/table_view/content_configuration/colorful_symbol_content_configuration.h"
 
 #import "ios/chrome/browser/shared/ui/table_view/content_configuration/colorful_symbol_content_view.h"
+#import "ios/chrome/common/ui/table_view/table_view_cells_constants.h"
 
 @implementation ColorfulSymbolContentConfiguration
 
@@ -14,6 +15,10 @@
   return [[ColorfulSymbolContentView alloc] initWithConfiguration:self];
 }
 
+- (CGSize)contentSize {
+  return CGSizeMake(kTableViewIconImageSize, kTableViewIconImageSize);
+}
+
 #pragma mark - UIContentConfiguration
 
 - (UIView*)makeContentView {
diff --git a/ios/chrome/browser/shared/ui/table_view/content_configuration/colorful_symbol_content_configuration_unittest.mm b/ios/chrome/browser/shared/ui/table_view/content_configuration/colorful_symbol_content_configuration_unittest.mm
new file mode 100644
index 0000000..ea72e52
--- /dev/null
+++ b/ios/chrome/browser/shared/ui/table_view/content_configuration/colorful_symbol_content_configuration_unittest.mm
@@ -0,0 +1,32 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/chrome/browser/shared/ui/table_view/content_configuration/colorful_symbol_content_configuration.h"
+
+#import "ios/chrome/common/ui/table_view/table_view_cells_constants.h"
+#import "testing/gtest/include/gtest/gtest.h"
+#import "testing/platform_test.h"
+
+namespace {
+
+using ColorfulSymbolContentConfigurationTest = PlatformTest;
+
+// Tests that the content size is correct for
+// ColorfulSymbolContentConfiguration.
+TEST_F(ColorfulSymbolContentConfigurationTest, ContentSize) {
+  ColorfulSymbolContentConfiguration* config =
+      [[ColorfulSymbolContentConfiguration alloc] init];
+
+  CGSize content_size = [config contentSize];
+  UIView* content_view = [config makeContentView];
+  [content_view setNeedsLayout];
+  [content_view layoutIfNeeded];
+
+  EXPECT_EQ(content_size.width, kTableViewIconImageSize);
+  EXPECT_EQ(content_size.height, kTableViewIconImageSize);
+  EXPECT_EQ(content_size.width, content_view.bounds.size.width);
+  EXPECT_EQ(content_size.height, content_view.bounds.size.height);
+}
+
+}  // namespace
diff --git a/ios/chrome/browser/shared/ui/table_view/content_configuration/colorful_symbol_content_view.mm b/ios/chrome/browser/shared/ui/table_view/content_configuration/colorful_symbol_content_view.mm
index 81bd239d..59bd3de 100644
--- a/ios/chrome/browser/shared/ui/table_view/content_configuration/colorful_symbol_content_view.mm
+++ b/ios/chrome/browser/shared/ui/table_view/content_configuration/colorful_symbol_content_view.mm
@@ -26,6 +26,7 @@
     (ColorfulSymbolContentConfiguration*)configuration {
   self = [super initWithFrame:CGRectZero];
   if (self) {
+    self.translatesAutoresizingMaskIntoConstraints = NO;
     _configuration = [configuration copy];
 
     _symbolImageView = [[UIImageView alloc] init];
diff --git a/ios/chrome/browser/shared/ui/table_view/content_configuration/favicon_content_configuration.mm b/ios/chrome/browser/shared/ui/table_view/content_configuration/favicon_content_configuration.mm
index 555137f..697b41f 100644
--- a/ios/chrome/browser/shared/ui/table_view/content_configuration/favicon_content_configuration.mm
+++ b/ios/chrome/browser/shared/ui/table_view/content_configuration/favicon_content_configuration.mm
@@ -7,6 +7,10 @@
 #import "ios/chrome/browser/shared/ui/table_view/content_configuration/favicon_content_view.h"
 #import "ios/chrome/common/ui/favicon/favicon_attributes.h"
 
+namespace {
+constexpr CGFloat kFaviconContainerWidth = 30;
+}
+
 @implementation FaviconContentConfiguration
 
 #pragma mark - ChromeContentConfiguration
@@ -15,6 +19,10 @@
   return [[FaviconContentView alloc] initWithConfiguration:self];
 }
 
+- (CGSize)contentSize {
+  return CGSizeMake(kFaviconContainerWidth, kFaviconContainerWidth);
+}
+
 #pragma mark - UIContentConfiguration
 
 - (UIView*)makeContentView {
diff --git a/ios/chrome/browser/shared/ui/table_view/content_configuration/favicon_content_configuration_unittest.mm b/ios/chrome/browser/shared/ui/table_view/content_configuration/favicon_content_configuration_unittest.mm
new file mode 100644
index 0000000..315f75d
--- /dev/null
+++ b/ios/chrome/browser/shared/ui/table_view/content_configuration/favicon_content_configuration_unittest.mm
@@ -0,0 +1,32 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/chrome/browser/shared/ui/table_view/content_configuration/favicon_content_configuration.h"
+
+#import "testing/gtest/include/gtest/gtest.h"
+#import "testing/platform_test.h"
+
+namespace {
+
+using FaviconContentConfigurationTest = PlatformTest;
+
+// Tests that the content size is correct for FaviconContentConfiguration.
+TEST_F(FaviconContentConfigurationTest, ContentSize) {
+  FaviconContentConfiguration* config =
+      [[FaviconContentConfiguration alloc] init];
+
+  CGSize content_size = [config contentSize];
+
+  UIView* content_view = [config makeContentView];
+  [content_view setNeedsLayout];
+  [content_view layoutIfNeeded];
+
+  // kFaviconContainerWidth is internal to the implementation (30).
+  EXPECT_EQ(content_size.width, 30);
+  EXPECT_EQ(content_size.height, 30);
+  EXPECT_EQ(content_size.width, content_view.bounds.size.width);
+  EXPECT_EQ(content_size.height, content_view.bounds.size.height);
+}
+
+}  // namespace
diff --git a/ios/chrome/browser/shared/ui/table_view/content_configuration/image_content_configuration.mm b/ios/chrome/browser/shared/ui/table_view/content_configuration/image_content_configuration.mm
index 9c1b5a6..595f49e 100644
--- a/ios/chrome/browser/shared/ui/table_view/content_configuration/image_content_configuration.mm
+++ b/ios/chrome/browser/shared/ui/table_view/content_configuration/image_content_configuration.mm
@@ -23,6 +23,13 @@
   return [[ImageContentView alloc] initWithConfiguration:self];
 }
 
+- (CGSize)contentSize {
+  if (!CGSizeEqualToSize(_imageSize, CGSizeZero)) {
+    return _imageSize;
+  }
+  return _image.size;
+}
+
 #pragma mark - UIContentConfiguration
 
 - (id<UIContentView>)makeContentView {
diff --git a/ios/chrome/browser/shared/ui/table_view/content_configuration/image_content_configuration_unittest.mm b/ios/chrome/browser/shared/ui/table_view/content_configuration/image_content_configuration_unittest.mm
new file mode 100644
index 0000000..fcf2d132
--- /dev/null
+++ b/ios/chrome/browser/shared/ui/table_view/content_configuration/image_content_configuration_unittest.mm
@@ -0,0 +1,60 @@
+// 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/shared/ui/table_view/content_configuration/image_content_configuration.h"
+
+#import "testing/gtest/include/gtest/gtest.h"
+#import "testing/platform_test.h"
+
+namespace {
+
+using ImageContentConfigurationTest = PlatformTest;
+
+// Tests that the content size is correct for ImageContentConfiguration when no
+// size is specified.
+TEST_F(ImageContentConfigurationTest, ContentSizeNoSize) {
+  ImageContentConfiguration* config = [[ImageContentConfiguration alloc] init];
+
+  UIGraphicsBeginImageContext(CGSizeMake(10, 20));
+  UIImage* image = UIGraphicsGetImageFromCurrentImageContext();
+  UIGraphicsEndImageContext();
+  config.image = image;
+
+  UIView* content_view = [config makeContentView];
+  [content_view setNeedsLayout];
+  [content_view layoutIfNeeded];
+
+  CGSize content_size = [config contentSize];
+  EXPECT_EQ(content_size.width, 10);
+  EXPECT_EQ(content_size.height, 20);
+  EXPECT_EQ(content_size.width, content_view.intrinsicContentSize.width);
+  EXPECT_EQ(content_size.height, content_view.intrinsicContentSize.height);
+}
+
+// Tests that the content size is correct for ImageContentConfiguration when
+// asking for specific size.
+TEST_F(ImageContentConfigurationTest, ContentSizeForcedSize) {
+  ImageContentConfiguration* config = [[ImageContentConfiguration alloc] init];
+
+  UIGraphicsBeginImageContext(CGSizeMake(10, 20));
+  UIImage* image = UIGraphicsGetImageFromCurrentImageContext();
+  UIGraphicsEndImageContext();
+  config.image = image;
+
+  // Test with explicit image size.
+  config.imageSize = CGSizeMake(50, 60);
+
+  UIView* content_view = [config makeContentView];
+
+  CGSize compressed_size =
+      [content_view systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
+
+  CGSize content_size = [config contentSize];
+  EXPECT_EQ(content_size.width, 50);
+  EXPECT_EQ(content_size.height, 60);
+  EXPECT_EQ(content_size.width, compressed_size.width);
+  EXPECT_EQ(content_size.height, compressed_size.height);
+}
+
+}  // namespace
diff --git a/ios/chrome/browser/shared/ui/table_view/content_configuration/image_content_view.mm b/ios/chrome/browser/shared/ui/table_view/content_configuration/image_content_view.mm
index fa453f31..e32e555 100644
--- a/ios/chrome/browser/shared/ui/table_view/content_configuration/image_content_view.mm
+++ b/ios/chrome/browser/shared/ui/table_view/content_configuration/image_content_view.mm
@@ -20,6 +20,7 @@
     (ImageContentConfiguration*)configuration {
   self = [super initWithFrame:CGRectZero];
   if (self) {
+    self.translatesAutoresizingMaskIntoConstraints = NO;
     _widthConstraint = [self.widthAnchor
         constraintEqualToConstant:configuration.imageSize.width];
     _heightConstraint = [self.heightAnchor
diff --git a/ios/chrome/browser/shared/ui/table_view/content_configuration/info_button_content_configuration.mm b/ios/chrome/browser/shared/ui/table_view/content_configuration/info_button_content_configuration.mm
index 7ddd8b9..3f7dfdf 100644
--- a/ios/chrome/browser/shared/ui/table_view/content_configuration/info_button_content_configuration.mm
+++ b/ios/chrome/browser/shared/ui/table_view/content_configuration/info_button_content_configuration.mm
@@ -8,6 +8,10 @@
 #import "ios/chrome/grit/ios_strings.h"
 #import "ui/base/l10n/l10n_util_mac.h"
 
+namespace {
+const CGFloat kButtonSize = 27;
+}
+
 @implementation InfoButtonContentConfiguration
 
 - (instancetype)init {
@@ -25,6 +29,10 @@
   return [[InfoButtonContentView alloc] initWithConfiguration:self];
 }
 
+- (CGSize)contentSize {
+  return CGSizeMake(kButtonSize, kButtonSize);
+}
+
 #pragma mark - UIContentConfiguration
 
 - (id<UIContentView>)makeContentView {
diff --git a/ios/chrome/browser/shared/ui/table_view/content_configuration/info_button_content_configuration_unittest.mm b/ios/chrome/browser/shared/ui/table_view/content_configuration/info_button_content_configuration_unittest.mm
new file mode 100644
index 0000000..8cd299c
--- /dev/null
+++ b/ios/chrome/browser/shared/ui/table_view/content_configuration/info_button_content_configuration_unittest.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/shared/ui/table_view/content_configuration/info_button_content_configuration.h"
+
+#import "testing/gtest/include/gtest/gtest.h"
+#import "testing/platform_test.h"
+
+namespace {
+
+using InfoButtonContentConfigurationTest = PlatformTest;
+
+// Tests that the content size is correct for InfoButtonContentConfiguration.
+TEST_F(InfoButtonContentConfigurationTest, ContentSize) {
+  InfoButtonContentConfiguration* config =
+      [[InfoButtonContentConfiguration alloc] init];
+
+  CGSize content_size = [config contentSize];
+
+  UIView* content_view = [config makeContentView];
+  [content_view setNeedsLayout];
+  [content_view layoutIfNeeded];
+
+  CGSize compressed_size =
+      [content_view systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
+
+  // kButtonSize is internal to the implementation (27).
+  EXPECT_EQ(content_size.width, 27);
+  EXPECT_EQ(content_size.height, 27);
+  EXPECT_EQ(content_size.width, compressed_size.width);
+  EXPECT_EQ(content_size.height, compressed_size.height);
+}
+
+}  // namespace
diff --git a/ios/chrome/browser/shared/ui/table_view/content_configuration/info_button_content_view.mm b/ios/chrome/browser/shared/ui/table_view/content_configuration/info_button_content_view.mm
index 05a69da4..79f7f129 100644
--- a/ios/chrome/browser/shared/ui/table_view/content_configuration/info_button_content_view.mm
+++ b/ios/chrome/browser/shared/ui/table_view/content_configuration/info_button_content_view.mm
@@ -13,6 +13,7 @@
 
 namespace {
 const CGFloat kInfoSymbolSize = 22;
+const CGFloat kButtonSize = 27;
 }
 
 @implementation InfoButtonContentView {
@@ -47,7 +48,12 @@
     _configuration = [configuration copy];
     [self applyConfiguration];
 
-    AddSameConstraints(_infoButton, self);
+    [NSLayoutConstraint activateConstraints:@[
+      [self.widthAnchor constraintEqualToConstant:kButtonSize],
+      [self.heightAnchor constraintEqualToAnchor:self.widthAnchor],
+      [_infoButton.centerXAnchor constraintEqualToAnchor:self.centerXAnchor],
+      [_infoButton.centerYAnchor constraintEqualToAnchor:self.centerYAnchor],
+    ]];
   }
   return self;
 }
diff --git a/ios/chrome/browser/shared/ui/table_view/content_configuration/switch_content_configuration.mm b/ios/chrome/browser/shared/ui/table_view/content_configuration/switch_content_configuration.mm
index 26f5adc..56a5c2a 100644
--- a/ios/chrome/browser/shared/ui/table_view/content_configuration/switch_content_configuration.mm
+++ b/ios/chrome/browser/shared/ui/table_view/content_configuration/switch_content_configuration.mm
@@ -24,6 +24,20 @@
   return [[SwitchContentView alloc] initWithConfiguration:self];
 }
 
+- (CGSize)contentSize {
+  // Static variable to store the size once calculated
+  static CGSize _cachedSize;
+  static dispatch_once_t onceToken;
+
+  dispatch_once(&onceToken, ^{
+    UIView* view = [self makeContentView];
+    _cachedSize =
+        [view systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
+  });
+
+  return _cachedSize;
+}
+
 #pragma mark - UIContentConfiguration
 
 - (id<UIContentView>)makeContentView {
diff --git a/ios/chrome/browser/shared/ui/table_view/content_configuration/switch_content_configuration_unittest.mm b/ios/chrome/browser/shared/ui/table_view/content_configuration/switch_content_configuration_unittest.mm
new file mode 100644
index 0000000..faba66d
--- /dev/null
+++ b/ios/chrome/browser/shared/ui/table_view/content_configuration/switch_content_configuration_unittest.mm
@@ -0,0 +1,38 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/chrome/browser/shared/ui/table_view/content_configuration/switch_content_configuration.h"
+
+#import "testing/gtest/include/gtest/gtest.h"
+#import "testing/platform_test.h"
+
+namespace {
+
+using SwitchContentConfigurationTest = PlatformTest;
+
+// Tests that the content size is correct for SwitchContentConfiguration.
+TEST_F(SwitchContentConfigurationTest, ContentSize) {
+  SwitchContentConfiguration* config =
+      [[SwitchContentConfiguration alloc] init];
+
+  CGSize content_size = [config contentSize];
+
+  UIView* content_view = [config makeContentView];
+  [content_view setNeedsLayout];
+  [content_view layoutIfNeeded];
+
+  CGSize compressed_size =
+      [content_view systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
+
+  if (@available(iOS 26, *)) {
+    // Those are used as golden value for iOS 26. If the value changes because
+    // the design of the switch change, update them.
+    EXPECT_EQ(content_size.width, 61);
+    EXPECT_EQ(content_size.height, 28);
+  }
+  EXPECT_EQ(content_size.width, compressed_size.width);
+  EXPECT_EQ(content_size.height, compressed_size.height);
+}
+
+}  // namespace
diff --git a/ios/chrome/browser/shared/ui/table_view/content_configuration/table_view_cell_content_configuration.mm b/ios/chrome/browser/shared/ui/table_view/content_configuration/table_view_cell_content_configuration.mm
index 0e113064..3c96bc65 100644
--- a/ios/chrome/browser/shared/ui/table_view/content_configuration/table_view_cell_content_configuration.mm
+++ b/ios/chrome/browser/shared/ui/table_view/content_configuration/table_view_cell_content_configuration.mm
@@ -8,6 +8,7 @@
 #import "ios/chrome/browser/shared/ui/table_view/cells/table_view_cell.h"
 #import "ios/chrome/browser/shared/ui/table_view/content_configuration/chrome_content_configuration.h"
 #import "ios/chrome/browser/shared/ui/table_view/content_configuration/table_view_cell_content_view.h"
+#import "ios/chrome/common/ui/table_view/table_view_cells_constants.h"
 
 @implementation TableViewCellContentConfiguration
 
@@ -69,6 +70,15 @@
   return contentView;
 }
 
+- (UIEdgeInsets)separatorInsets {
+  CGFloat leadingInset = kTableViewHorizontalSpacing;
+  if (_leadingConfiguration) {
+    leadingInset += kTableViewHorizontalSpacing;
+    leadingInset += [_leadingConfiguration contentSize].width;
+  }
+  return UIEdgeInsetsMake(0, leadingInset, 0, 0);
+}
+
 #pragma mark - UIContentConfiguration
 
 - (UIView<UIContentView>*)makeContentView {
diff --git a/ios/chrome/browser/shared/ui/table_view/table_view_illustrated_empty_view.mm b/ios/chrome/browser/shared/ui/table_view/table_view_illustrated_empty_view.mm
index af5f7bf..5c7c833 100644
--- a/ios/chrome/browser/shared/ui/table_view/table_view_illustrated_empty_view.mm
+++ b/ios/chrome/browser/shared/ui/table_view/table_view_illustrated_empty_view.mm
@@ -117,20 +117,9 @@
 
 #pragma mark - UITextViewDelegate
 
-#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_17_0
-- (BOOL)textView:(UITextView*)textView
-    shouldInteractWithURL:(NSURL*)URL
-                  inRange:(NSRange)characterRange
-              interaction:(UITextItemInteraction)interaction {
-  [self.delegate tableViewIllustratedEmptyView:self didTapSubtitleLink:URL];
-
-  return NO;
-}
-#endif
-
 - (UIAction*)textView:(UITextView*)textView
     primaryActionForTextItem:(UITextItem*)textItem
-               defaultAction:(UIAction*)defaultAction API_AVAILABLE(ios(17.0)) {
+               defaultAction:(UIAction*)defaultAction {
   __weak __typeof(self) weakSelf = self;
   NSURL* URL = textItem.link;
   return [UIAction actionWithHandler:^(UIAction* action) {
diff --git a/ios/chrome/browser/start_surface/ui_bundled/BUILD.gn b/ios/chrome/browser/start_surface/ui_bundled/BUILD.gn
index 6f0e79b..7b7bb368 100644
--- a/ios/chrome/browser/start_surface/ui_bundled/BUILD.gn
+++ b/ios/chrome/browser/start_surface/ui_bundled/BUILD.gn
@@ -104,11 +104,14 @@
   deps = [
     ":feature_flags",
     "//build:branding_buildflags",
+    "//components/strings",
     "//ios/chrome/app/strings",
     "//ios/chrome/browser/content_suggestions/ui_bundled:constants",
     "//ios/chrome/browser/content_suggestions/ui_bundled/tab_resumption/public:constants",
     "//ios/chrome/browser/shared/public/features",
     "//ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups:eg2_utils",
+    "//ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups:tab_groups_constant",
+    "//ios/chrome/browser/tab_switcher/ui_bundled/test:utils",
     "//ios/chrome/test/earl_grey:eg_test_support+eg2",
     "//ios/testing/earl_grey:eg_test_support+eg2",
     "//net:test_support",
diff --git a/ios/chrome/browser/start_surface/ui_bundled/start_surface_egtest.mm b/ios/chrome/browser/start_surface/ui_bundled/start_surface_egtest.mm
index a9b9f531..aa4dcdd2 100644
--- a/ios/chrome/browser/start_surface/ui_bundled/start_surface_egtest.mm
+++ b/ios/chrome/browser/start_surface/ui_bundled/start_surface_egtest.mm
@@ -8,11 +8,14 @@
 #import "base/test/ios/wait_util.h"
 #import "base/time/time.h"
 #import "build/branding_buildflags.h"
+#import "components/strings/grit/components_strings.h"
 #import "ios/chrome/browser/content_suggestions/ui_bundled/content_suggestions_constants.h"
 #import "ios/chrome/browser/content_suggestions/ui_bundled/tab_resumption/public/tab_resumption_constants.h"
 #import "ios/chrome/browser/shared/public/features/features.h"
 #import "ios/chrome/browser/start_surface/ui_bundled/start_surface_features.h"
+#import "ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/tab_groups_constants.h"
 #import "ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/tab_groups_eg_utils.h"
+#import "ios/chrome/browser/tab_switcher/ui_bundled/test/tabs_egtest_util.h"
 #import "ios/chrome/grit/ios_strings.h"
 #import "ios/chrome/test/earl_grey/chrome_earl_grey.h"
 #import "ios/chrome/test/earl_grey/chrome_earl_grey_ui.h"
@@ -51,9 +54,9 @@
   }
 }
 
-NSString* const kGroupName = @"group";
-const char kZeroSecondsTreshold[] = "0";
-const char kThreeSecondsTreshold[] = "3";
+NSString* const kGroupName = @"1group";
+const char kZeroSecondsThreshold[] = "0";
+const char kThreeSecondsThreshold[] = "3";
 
 }  // namespace
 
@@ -77,18 +80,18 @@
     config.features_enabled_and_params.push_back(
         {kShowTabGroupInGridOnStart,
          {{{kShowTabGroupInGridInactiveDurationInSeconds,
-            kZeroSecondsTreshold}}}});
+            kZeroSecondsThreshold}}}});
     config.features_enabled_and_params.push_back(
         {kStartSurface,
          {{{kReturnToStartSurfaceInactiveDurationInSeconds,
-            kThreeSecondsTreshold}}}});
+            kThreeSecondsThreshold}}}});
     return config;
   }
 
   config.features_enabled_and_params.push_back(
       {kStartSurface,
        {{{kReturnToStartSurfaceInactiveDurationInSeconds,
-          kZeroSecondsTreshold}}}});
+          kZeroSecondsThreshold}}}});
 
   return config;
 }
@@ -99,6 +102,15 @@
   [ChromeEarlGrey openNewTab];
 }
 
+// Loads the first tab with an URL.
+- (void)loadFirstTabURL {
+  GREYAssertTrue(self.testServer->Start(), @"Test server failed to start.");
+  const GURL destinationURL = self.testServer->GetURL("/pony.html");
+  [ChromeEarlGrey loadURL:destinationURL];
+}
+
+#pragma mark - Tests
+
 // Tests that navigating to a page and restarting upon cold start, an NTP page
 // is opened with the Return to Recent Tab tile.
 // TODO(crbug.com/443695878): Test disabled on simulator.
@@ -115,9 +127,7 @@
     EARL_GREY_TEST_DISABLED(@"This test is flaky on iPad device.");
   }
 #endif
-  GREYAssertTrue(self.testServer->Start(), @"Test server failed to start.");
-  const GURL destinationUrl = self.testServer->GetURL("/pony.html");
-  [ChromeEarlGrey loadURL:destinationUrl];
+  [self loadFirstTabURL];
 
   [[AppLaunchManager sharedManager]
       ensureAppLaunchedWithConfiguration:[self appConfigurationForTestCase]];
@@ -140,9 +150,8 @@
 #define MAYBE_testWarmStartOpenStartSurface testWarmStartOpenStartSurface
 #endif
 - (void)MAYBE_testWarmStartOpenStartSurface {
-  GREYAssertTrue(self.testServer->Start(), @"Test server failed to start.");
-  const GURL destinationUrl = self.testServer->GetURL("/pony.html");
-  [ChromeEarlGrey loadURL:destinationUrl];
+  [self loadFirstTabURL];
+
   [ChromeEarlGrey
       waitForWebStateContainingText:"Anyone know any good pony jokes?"];
 
@@ -161,9 +170,7 @@
 // also removes the tile while that NTP is still being shown.
 // TODO(crbug.com/441260657): Re-enable when fixed.
 - (void)DISABLED_testRemoveRecentTabRemovesReturnToRecentTabTile {
-  GREYAssertTrue(self.testServer->Start(), @"Test server failed to start.");
-  const GURL destinationUrl = self.testServer->GetURL("/pony.html");
-  [ChromeEarlGrey loadURL:destinationUrl];
+  [self loadFirstTabURL];
 
   int non_start_tab_index = [ChromeEarlGrey indexOfActiveNormalTab];
   [[AppLaunchManager sharedManager] backgroundAndForegroundApp];
@@ -294,4 +301,47 @@
       assertWithMatcher:grey_notVisible()];
 }
 
+// Tests that the created NTP is ungrouped, even if a group was active when
+// backgrounded.
+- (void)testOpenNTPOutsideTheActiveGroupAfterFourHoursInBackground {
+  [self loadFirstTabURL];
+
+  [ChromeEarlGreyUI openTabGrid];
+
+  chrome_test_util::CreateTabGroupAtIndex(0, kGroupName);
+
+  // Open the group.
+  [[EarlGrey selectElementWithMatcher:chrome_test_util::TabGridGroupCellAtIndex(
+                                          0)] performAction:grey_tap()];
+
+  // Open the tab.
+  [[EarlGrey selectElementWithMatcher:chrome_test_util::TabGridCellAtIndex(0)]
+      performAction:grey_tap()];
+
+  GREYAssertEqual([ChromeEarlGrey mainTabCount], 1UL,
+                  @"One tab was expected to be open");
+
+  // Simulate background then foreground activation.
+  [[AppLaunchManager sharedManager] backgroundAndForegroundApp];
+
+  // Assert NTP is visible by checking that the fake omnibox is here.
+  [[EarlGrey selectElementWithMatcher:chrome_test_util::FakeOmnibox()]
+      assertWithMatcher:grey_sufficientlyVisible()];
+  GREYAssertEqual([ChromeEarlGrey mainTabCount], 2UL,
+                  @"Two tabs were expected to be open");
+
+  [ChromeEarlGreyUI openTabGrid];
+
+  // Check that the NTP is not in the group and visible in the Tab Grid view.
+  [[EarlGrey selectElementWithMatcher:TabWithTitle(l10n_util::GetNSString(
+                                          IDS_NEW_TAB_TITLE))]
+      assertWithMatcher:grey_sufficientlyVisible()];
+
+  // Check that the group has only 1 tab.
+  [[EarlGrey
+      selectElementWithMatcher:chrome_test_util::TabGridGroupCellWithName(
+                                   kGroupName, 1)]
+      assertWithMatcher:grey_notNil()];
+}
+
 @end
diff --git a/ios/chrome/browser/start_surface/ui_bundled/start_surface_scene_agent.mm b/ios/chrome/browser/start_surface/ui_bundled/start_surface_scene_agent.mm
index 65c707c..0b79748 100644
--- a/ios/chrome/browser/start_surface/ui_bundled/start_surface_scene_agent.mm
+++ b/ios/chrome/browser/start_surface/ui_bundled/start_surface_scene_agent.mm
@@ -204,13 +204,14 @@
     }
   } else if (startUpRemediationFeatureType ==
              StartupRemediationsType::kSaveNewNTPWebState) {
-    // If the tab at index kIOSLastKnownNTPWebStateIndex is still a valid NTP
-    // page, activate it and return early.
+    // If the tab at index kIOSLastKnownNTPWebStateIndex is still a valid
+    // ungrouped NTP page, activate it and return early.
     PrefService* prefService = browser->GetProfile()->GetPrefs();
     int knownNTPWebStateIndex =
         prefService->GetInteger(prefs::kIOSLastKnownNTPWebStateIndex);
     prefService->ClearPref(prefs::kIOSLastKnownNTPWebStateIndex);
-    if (webStateList->ContainsIndex(knownNTPWebStateIndex)) {
+    if (webStateList->ContainsIndex(knownNTPWebStateIndex) &&
+        !webStateList->GetGroupOfWebStateAt(knownNTPWebStateIndex)) {
       if ([self activateNTPForWebStateList:webStateList
                                    atIndex:knownNTPWebStateIndex]) {
         return;
diff --git a/ios/chrome/browser/start_surface/ui_bundled/start_surface_scene_agent_unittest.mm b/ios/chrome/browser/start_surface/ui_bundled/start_surface_scene_agent_unittest.mm
index ea9fee5..a7ecd58 100644
--- a/ios/chrome/browser/start_surface/ui_bundled/start_surface_scene_agent_unittest.mm
+++ b/ios/chrome/browser/start_surface/ui_bundled/start_surface_scene_agent_unittest.mm
@@ -503,9 +503,9 @@
       /*disabled_features=*/{});
 
   // Within the interval.
-  base::Time TimeLastBackground = base::Time::Now() - base::Hours(2);
-  test::SetStartSurfaceSessionObjectForSceneStateForTesting(scene_state_,
-                                                            TimeLastBackground);
+  base::Time time_last_background = base::Time::Now() - base::Hours(2);
+  test::SetStartSurfaceSessionObjectForSceneStateForTesting(
+      scene_state_, time_last_background);
 
   [dispatcher_ startDispatchingToTarget:application_handler_
                             forProtocol:@protocol(ApplicationCommands)];
@@ -543,9 +543,9 @@
       /*disabled_features=*/{});
 
   // Within the interval
-  base::Time TimeLastBackground = base::Time::Now() - base::Hours(2);
-  test::SetStartSurfaceSessionObjectForSceneStateForTesting(scene_state_,
-                                                            TimeLastBackground);
+  base::Time time_last_background = base::Time::Now() - base::Hours(2);
+  test::SetStartSurfaceSessionObjectForSceneStateForTesting(
+      scene_state_, time_last_background);
 
   // Forcing the current BrowserProvider to be incognito.
   scene_state_.browserProviderInterface.currentBrowserProvider =
@@ -590,9 +590,9 @@
       /*disabled_features=*/{});
 
   // Not in interval.
-  base::Time TimeLastBackground = base::Time::Now() - base::Minutes(30);
-  test::SetStartSurfaceSessionObjectForSceneStateForTesting(scene_state_,
-                                                            TimeLastBackground);
+  base::Time time_last_background = base::Time::Now() - base::Minutes(30);
+  test::SetStartSurfaceSessionObjectForSceneStateForTesting(
+      scene_state_, time_last_background);
 
   [dispatcher_ startDispatchingToTarget:application_handler_
                             forProtocol:@protocol(ApplicationCommands)];
@@ -630,9 +630,9 @@
       /*disabled_features=*/{});
 
   // Not in interval.
-  base::Time TimeLastBackground = base::Time::Now() - base::Hours(5);
-  test::SetStartSurfaceSessionObjectForSceneStateForTesting(scene_state_,
-                                                            TimeLastBackground);
+  base::Time time_last_background = base::Time::Now() - base::Hours(5);
+  test::SetStartSurfaceSessionObjectForSceneStateForTesting(
+      scene_state_, time_last_background);
 
   [dispatcher_ startDispatchingToTarget:application_handler_
                             forProtocol:@protocol(ApplicationCommands)];
@@ -673,9 +673,9 @@
       /*disabled_features=*/{});
 
   // Within the interval but no group.
-  base::Time TimeLastBackground = base::Time::Now() - base::Hours(2);
-  test::SetStartSurfaceSessionObjectForSceneStateForTesting(scene_state_,
-                                                            TimeLastBackground);
+  base::Time time_last_background = base::Time::Now() - base::Hours(2);
+  test::SetStartSurfaceSessionObjectForSceneStateForTesting(
+      scene_state_, time_last_background);
 
   [dispatcher_ startDispatchingToTarget:application_handler_
                             forProtocol:@protocol(ApplicationCommands)];
@@ -713,9 +713,9 @@
       /*disabled_features=*/{});
 
   // Within the interval.
-  base::Time TimeLastBackground = base::Time::Now() - base::Hours(2);
-  test::SetStartSurfaceSessionObjectForSceneStateForTesting(scene_state_,
-                                                            TimeLastBackground);
+  base::Time time_last_background = base::Time::Now() - base::Hours(2);
+  test::SetStartSurfaceSessionObjectForSceneStateForTesting(
+      scene_state_, time_last_background);
 
   [dispatcher_ startDispatchingToTarget:application_handler_
                             forProtocol:@protocol(ApplicationCommands)];
@@ -735,3 +735,77 @@
 
   [dispatcher_ stopDispatchingToTarget:application_handler_];
 }
+
+// Tests that a NTP is created when Chrome is foregrounded after being +4 hours
+// in background.
+TEST_F(StartSurfaceSceneAgentTest, OpenNTPAfterFourHours) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  // Setting the ReturnToStartSurfaceInactiveDuration to 4 hours and the
+  // ShowTabGroupInGridInactiveDuration to 1 hour.
+  base::FieldTrialParams start_time_params = {
+      {kReturnToStartSurfaceInactiveDurationInSeconds, kFourHoursTreshold}};
+  base::FieldTrialParams show_tab_grid_treshold = {
+      {kShowTabGroupInGridInactiveDurationInSeconds, kOneHourTreshold}};
+  scoped_feature_list.InitWithFeaturesAndParameters(
+      /*enabled_features=*/
+      {{kStartSurface, start_time_params},
+       {kShowTabGroupInGridOnStart, show_tab_grid_treshold}},
+      /*disabled_features=*/{});
+
+  base::Time time_last_background = base::Time::Now() - base::Hours(5);
+  test::SetStartSurfaceSessionObjectForSceneStateForTesting(
+      scene_state_, time_last_background);
+
+  [dispatcher_ startDispatchingToTarget:application_handler_
+                            forProtocol:@protocol(ApplicationCommands)];
+
+  InsertNewWebState(0, GURL(kURL));
+  WebStateList* web_state_list = GetWebStateList();
+  web_state_list->ActivateWebStateAt(0);
+  favicon::WebFaviconDriver::CreateForWebState(
+      web_state_list->GetActiveWebState(),
+      /*favicon_service=*/nullptr);
+
+  scene_state_.activationLevel = SceneActivationLevelForegroundActive;
+  ASSERT_EQ(2, web_state_list->count());
+
+  [dispatcher_ stopDispatchingToTarget:application_handler_];
+}
+
+// Tests that a NTP is created outside the active group when Chrome is
+// foregrounded after being +4 hours in background.
+TEST_F(StartSurfaceSceneAgentTest, OpenNTPAfterFourHoursOutsideActiveGroup) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  // Setting the ReturnToStartSurfaceInactiveDuration to 4 hours and the
+  // ShowTabGroupInGridInactiveDuration to 1 hour.
+  base::FieldTrialParams start_time_params = {
+      {kReturnToStartSurfaceInactiveDurationInSeconds, kFourHoursTreshold}};
+  base::FieldTrialParams show_tab_grid_treshold = {
+      {kShowTabGroupInGridInactiveDurationInSeconds, kOneHourTreshold}};
+  scoped_feature_list.InitWithFeaturesAndParameters(
+      /*enabled_features=*/
+      {{kStartSurface, start_time_params},
+       {kShowTabGroupInGridOnStart, show_tab_grid_treshold}},
+      /*disabled_features=*/{});
+
+  base::Time time_last_background = base::Time::Now() - base::Hours(5);
+  test::SetStartSurfaceSessionObjectForSceneStateForTesting(
+      scene_state_, time_last_background);
+
+  [dispatcher_ startDispatchingToTarget:application_handler_
+                            forProtocol:@protocol(ApplicationCommands)];
+
+  InsertNewWebState(0, GURL(kURL));
+  WebStateList* web_state_list = GetWebStateList();
+  web_state_list->CreateGroup({0}, {}, TabGroupId::GenerateNew());
+  web_state_list->ActivateWebStateAt(0);
+  favicon::WebFaviconDriver::CreateForWebState(
+      web_state_list->GetActiveWebState(),
+      /*favicon_service=*/nullptr);
+
+  scene_state_.activationLevel = SceneActivationLevelForegroundActive;
+  ASSERT_EQ(2, web_state_list->count());
+  ASSERT_FALSE(web_state_list->GetGroupOfWebStateAt(1));
+
+  [dispatcher_ stopDispatchingToTarget:application_handler_];
+}
diff --git a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/grid/disabled_grid_view_controller.mm b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/grid/disabled_grid_view_controller.mm
index 146cd92..1109fc1c 100644
--- a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/grid/disabled_grid_view_controller.mm
+++ b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/grid/disabled_grid_view_controller.mm
@@ -137,22 +137,9 @@
 
 #pragma mark - UITextViewDelegate
 
-#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_17_0
-- (BOOL)textView:(UITextView*)textView
-    shouldInteractWithURL:(NSURL*)URL
-                  inRange:(NSRange)characterRange
-              interaction:(UITextItemInteraction)interaction {
-  if (URL) {
-    [self.delegate didTapLinkWithURL:net::GURLWithNSURL(URL)];
-  }
-  // Return NO as the app is handling the opening of the URL.
-  return NO;
-}
-#endif
-
 - (UIAction*)textView:(UITextView*)textView
     primaryActionForTextItem:(UITextItem*)textItem
-               defaultAction:(UIAction*)defaultAction API_AVAILABLE(ios(17.0)) {
+               defaultAction:(UIAction*)defaultAction {
   NSURL* URL = textItem.link;
   if (!URL) {
     return defaultAction;
diff --git a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/inactive_tabs/inactive_tabs_preamble_header.mm b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/inactive_tabs/inactive_tabs_preamble_header.mm
index 0e5be2eb..6c65b818 100644
--- a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/inactive_tabs/inactive_tabs_preamble_header.mm
+++ b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/inactive_tabs/inactive_tabs_preamble_header.mm
@@ -76,23 +76,9 @@
 
 #pragma mark - UITextViewDelegate
 
-#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_17_0
-- (BOOL)textView:(UITextView*)textView
-    shouldInteractWithURL:(NSURL*)URL
-                  inRange:(NSRange)characterRange
-              interaction:(UITextItemInteraction)interaction {
-  DCHECK_EQ(textView, _textView);
-  if (interaction == UITextItemInteractionInvokeDefaultAction &&
-      _settingsLinkAction) {
-    _settingsLinkAction();
-  }
-  return NO;
-}
-#endif
-
 - (UIAction*)textView:(UITextView*)textView
     primaryActionForTextItem:(UITextItem*)textItem
-               defaultAction:(UIAction*)defaultAction API_AVAILABLE(ios(17.0)) {
+               defaultAction:(UIAction*)defaultAction {
   DCHECK_EQ(textView, _textView);
   if (!_settingsLinkAction) {
     return defaultAction;
diff --git a/ios/chrome/browser/webauthn/model/ios_chrome_passkey_client.mm b/ios/chrome/browser/webauthn/model/ios_chrome_passkey_client.mm
index 2320f4a47..4fc13e0 100644
--- a/ios/chrome/browser/webauthn/model/ios_chrome_passkey_client.mm
+++ b/ios/chrome/browser/webauthn/model/ios_chrome_passkey_client.mm
@@ -31,11 +31,11 @@
   CHECK(web_state);
   web_state_ = web_state->GetWeakPtr();
   profile_ = ProfileIOS::FromBrowserState(web_state_->GetBrowserState());
-  bool metricsReportingEnabled =
+  bool metrics_reporting_enabled =
       GetApplicationContext()->GetLocalState()->GetBoolean(
           metrics::prefs::kMetricsReportingEnabled);
   passkey_keychain_provider_ =
-      std::make_unique<PasskeyKeychainProvider>(metricsReportingEnabled);
+      std::make_unique<PasskeyKeychainProvider>(metrics_reporting_enabled);
 }
 
 IOSChromePasskeyClient::~IOSChromePasskeyClient() {}
@@ -69,11 +69,11 @@
     return;
   }
 
-  // TODO(crbug.com/460485496): Use an empty passkey for now.
-  // The real passkey will come from WebAuthnCredentialsDelegate.
-  sync_pb::WebauthnCredentialSpecifics passkey;
+  // TODO(crbug.com/460485496): Use an empty credential ID for now.
+  // The real credential ID will come from WebAuthnCredentialsDelegate.
+  std::string credential_id;
   passkey_tab_helper->StartPasskeyAssertion(request_info.request_id,
-                                            std::move(passkey));
+                                            std::move(credential_id));
 }
 
 void IOSChromePasskeyClient::ShowCreationBottomSheet(RequestInfo request_info) {
diff --git a/ios/chrome/common/ui/confirmation_alert/confirmation_alert_action_handler.h b/ios/chrome/common/ui/confirmation_alert/confirmation_alert_action_handler.h
index 326cf91e..9a19cbf 100644
--- a/ios/chrome/common/ui/confirmation_alert/confirmation_alert_action_handler.h
+++ b/ios/chrome/common/ui/confirmation_alert/confirmation_alert_action_handler.h
@@ -14,9 +14,6 @@
 
 @optional
 
-// The "Dismiss" button was touched.
-- (void)confirmationAlertDismissAction;
-
 // The "Secondary Action" was touched.
 - (void)confirmationAlertSecondaryAction;
 
diff --git a/ios/chrome/common/ui/elements/popover_label_view_controller.mm b/ios/chrome/common/ui/elements/popover_label_view_controller.mm
index 52da135d..1faf05af 100644
--- a/ios/chrome/common/ui/elements/popover_label_view_controller.mm
+++ b/ios/chrome/common/ui/elements/popover_label_view_controller.mm
@@ -365,22 +365,9 @@
 
 #pragma mark - UITextViewDelegate
 
-#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_17_0
-- (BOOL)textView:(UITextView*)textView
-    shouldInteractWithURL:(NSURL*)URL
-                  inRange:(NSRange)characterRange
-              interaction:(UITextItemInteraction)interaction {
-  if (URL) {
-    [self.delegate didTapLinkURL:URL];
-  }
-  // Returns NO as the app is handling the opening of the URL.
-  return NO;
-}
-#endif
-
 - (UIAction*)textView:(UITextView*)textView
     primaryActionForTextItem:(UITextItem*)textItem
-               defaultAction:(UIAction*)defaultAction API_AVAILABLE(ios(17.0)) {
+               defaultAction:(UIAction*)defaultAction {
   NSURL* URL = textItem.link;
   if (URL) {
     __weak __typeof(self) weakSelf = self;
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 fdd61855..7595a11 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
@@ -1238,22 +1238,9 @@
 
 #pragma mark - UITextViewDelegate
 
-#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_17_0
-- (BOOL)textView:(UITextView*)textView
-    shouldInteractWithURL:(NSURL*)URL
-                  inRange:(NSRange)characterRange
-              interaction:(UITextItemInteraction)interaction {
-  if (textView == self.disclaimerView &&
-      [self.delegate respondsToSelector:@selector(didTapURLInDisclaimer:)]) {
-    [self.delegate didTapURLInDisclaimer:URL];
-  }
-  return NO;
-}
-#endif
-
 - (UIAction*)textView:(UITextView*)textView
     primaryActionForTextItem:(UITextItem*)textItem
-               defaultAction:(UIAction*)defaultAction API_AVAILABLE(ios(17.0)) {
+               defaultAction:(UIAction*)defaultAction {
   if (!(textView == self.disclaimerView &&
         [self.delegate respondsToSelector:@selector(didTapURLInDisclaimer:)])) {
     return defaultAction;
diff --git a/ios/chrome/test/BUILD.gn b/ios/chrome/test/BUILD.gn
index c5180d9a..12507c23 100644
--- a/ios/chrome/test/BUILD.gn
+++ b/ios/chrome/test/BUILD.gn
@@ -239,6 +239,7 @@
     "//ios/chrome/browser/commerce/model:unit_tests",
     "//ios/chrome/browser/commerce/model/push_notification:unit_tests",
     "//ios/chrome/browser/complex_tasks/model:unit_tests",
+    "//ios/chrome/browser/composebox/coordinator:unit_tests",
     "//ios/chrome/browser/content_notification/model:unit_tests",
     "//ios/chrome/browser/content_suggestions/ui_bundled:unit_tests",
     "//ios/chrome/browser/content_suggestions/ui_bundled/app_bundle_promo/coordinator:unit_tests",
@@ -484,6 +485,7 @@
     "//ios/chrome/browser/shared/ui/list_model:unit_tests",
     "//ios/chrome/browser/shared/ui/table_view:unit_tests",
     "//ios/chrome/browser/shared/ui/table_view/cells:unit_tests",
+    "//ios/chrome/browser/shared/ui/table_view/content_configuration:unit_tests",
     "//ios/chrome/browser/shared/ui/util:unit_tests",
     "//ios/chrome/browser/sharing/ui_bundled:unit_tests",
     "//ios/chrome/browser/sharing/ui_bundled/activity_services:unit_tests",
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 a21f71e..3767ac8 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 @@
-b0beb6e0b0adaff5fee6ec367b9b20ddc1a27801
\ No newline at end of file
+f9640766bbe7900e5437af495866d0d7789cab01
\ 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 ec028f1..23d1dc3b 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 @@
-f0229a0aa56cd0adf8adbb111ad60ea1a89b2203
\ No newline at end of file
+995e69ea455b04ea9af4f6105f5be221caac8180
\ 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 b7faf90..8bda089 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 @@
-e20c36e5176c90b20f7cfd7bccf3bc6e5d3ac901
\ No newline at end of file
+4b7151b259ac8aa66351d62e32e8f8482cd66754
\ 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 c8145a4e..8f73dc9 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 @@
-e536dff9c795bbf58fa1e1f24bb87c9e927525be
\ No newline at end of file
+89aad4883b343bcf4fe39f7ca7b9ccb240a9c6f6
\ 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 c29bcb7b..48a8de3 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 @@
-b2852457ce00ff79deffd72ab25218c86d345790
\ No newline at end of file
+9a1a5b5debd02d756b8695d98976b766a6c0363f
\ 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 e791894..7395184 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 @@
-a496f5db7d2d7e9f6ab24aa20519397967ccbd5c
\ No newline at end of file
+d01fcfa7fc0cf62b47092571ec8e8ee0c6d6ed2b
\ 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 4a1e5a8..4f6398a 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 @@
-b0a177fe3efef76e3974f1105f88632bd4422c75
\ No newline at end of file
+e7ba19a606c8af07224fc3267cd02dff9eca49c4
\ 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 8b46488..b47919c 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 @@
-289755144b44e9b819502aa385ae47a677a0675a
\ No newline at end of file
+1775b0ec3776a696fcdae8fef2a02dc8eb6ec1bd
\ 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 250ebee19..c94da1ed 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 @@
-d7cc47dd8cba1540c5f004b01c46d0553fb03a81
\ No newline at end of file
+7f11c56fdfc7509232463076917aaca49cf4a394
\ 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 db461ed0..1af3fe2 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 @@
-5fab731adb46464b4ec215cb4187abda2af13c59
\ No newline at end of file
+c2a7eafef78eb887cd61868d535cd790359a8f60
\ 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 9a17caf..6f828422 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 @@
-a1c723de96601054582a02908d94022555d685e5
\ No newline at end of file
+88adaeb379627ef6c513cb5175d43dd8e5fa9f67
\ 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 6768a84..19227337 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 @@
-1935641c3591d7d1ce2e6b8c79778ad958d68d73
\ No newline at end of file
+6ed32ad6636cf492e9746157310a47694e9fc639
\ 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 ee909e4..1990f5547 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 @@
-6d2e0441650f939ba58e0a87beb191292de2a0c9
\ No newline at end of file
+5aca73313d5580d0a61af59c050ca6027efe5a7b
\ 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 f42e70a6..72640f4 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 @@
-52759b0414b26182ea97c5d239b912798723a7ab
\ No newline at end of file
+e470ba469421eefbde9f7d7ff5747506192bce24
\ 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 49635e6..c7603fa7 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 @@
-c8fd486b82f9eef63ba2c48d0020cfb762958d01
\ No newline at end of file
+811c3129466a26dd37d4afdea14637929ce8fde0
\ 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 e07b241..65bbb84 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 @@
-e5426beb4ebfd2cee6e4bc2dacc878229d303e7d
\ No newline at end of file
+e198bc3cd6551cd0bd42897601d659c2c362ebfe
\ 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 30b66a9..ec7fb1d 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 @@
-022aaab6c21e4dfe941b41f29d5a7f14fe99b54d
\ No newline at end of file
+6862703b003efa46bdf5afbf3b2c48bf6d9edb8a
\ 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 edb95c6..78d03bd4 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 @@
-ab4c95c0352f8d81a84465701cbc4a3104416cf4
\ No newline at end of file
+8bce95ae6c40ca761731d64ee65f600cf6758f84
\ 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 0c0ec7c..f28d647 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 @@
-1fddd130bddebb6ddfbee1107f93b5856671e292
\ No newline at end of file
+07cf6fe2e08520e9bf7a8404fa2608b18a2f54fb
\ 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 aa397fd8..78e2b0b1 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 @@
-940a69b8a75c46813e2e49c8df1dfec6916c5347
\ No newline at end of file
+1e433e7a0010344ba4eb4b45366157206f42247a
\ 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 b7117c3..c57768a 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 @@
-5a226da765e8fc224782bcf07118d687db20a6a5
\ No newline at end of file
+ef8c655d2e74ce0027c67bd934fe10a029143340
\ 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 15eead4..b96b3da 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 @@
-16d15c33f1c5607325c74fdd468f1fc168cab09b
\ No newline at end of file
+996b11f7b97132771278100a9be4ee8d3d2a1058
\ 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 9bb4804..8b97b7fd 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 @@
-db41927b35dda28eb944f630a91c2381725ff605
\ No newline at end of file
+e74107c4da1631777e4fa11acda1505b78cd3969
\ 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 c34d874..a3f8b8fc 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 @@
-52e3ea4c380a2b4da5635802c7961299a76f2eca
\ No newline at end of file
+0f4dc49b14cd296244b37222e7e30c53b5a03d9e
\ 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 a752208..b5044fa 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 @@
-e3f2de4ae8a66212ecf90aff608d68a28a98546f
\ No newline at end of file
+73311848610cab0f421e640a070d12cfc2bb96df
\ 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 b2eb920..bd2d0f2 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 @@
-cf82be26eb0a048b200e4e6ddbb7e7cb31644404
\ No newline at end of file
+d44e5e4c9394aba982c5b6804d00721df32541b1
\ 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 d481841c..fc6478b 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 @@
-869dc18a646c12e99ad68d43c67b96f11e941fc8
\ No newline at end of file
+5d4d7785fa45aeb01c0627bc031d475e569a3e41
\ 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 fad7003..6bdc6ef 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 @@
-022d8321e22c0bcf7660209b850b60ef8af80a57
\ No newline at end of file
+068a6c71cb42cc9923f63aced04c49ea3a81baf2
\ No newline at end of file
diff --git a/ios_internal b/ios_internal
index 697336e..7beeb0a 160000
--- a/ios_internal
+++ b/ios_internal
@@ -1 +1 @@
-Subproject commit 697336ed8df2b6bb7d4858c2ce9b16d62e4e2bcf
+Subproject commit 7beeb0ac6c8ff93ab6be32ab8d6523c9196db833
diff --git a/media/base/BUILD.gn b/media/base/BUILD.gn
index 666aa2b..795e2db 100644
--- a/media/base/BUILD.gn
+++ b/media/base/BUILD.gn
@@ -311,6 +311,7 @@
     "scoped_async_trace.h",
     "seekable_buffer.cc",
     "seekable_buffer.h",
+    "sequence.h",
     "serial_runner.cc",
     "serial_runner.h",
     "silent_sink_suspender.cc",
@@ -656,6 +657,7 @@
     "reentrancy_checker_unittest.cc",
     "renderer_factory_selector_unittest.cc",
     "seekable_buffer_unittest.cc",
+    "sequence_unittest.cc",
     "serial_runner_unittest.cc",
     "silent_sink_suspender_unittest.cc",
     "sinc_resampler_unittest.cc",
diff --git a/media/base/sequence.h b/media/base/sequence.h
new file mode 100644
index 0000000..9bb258aa
--- /dev/null
+++ b/media/base/sequence.h
@@ -0,0 +1,279 @@
+// 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 MEDIA_BASE_SEQUENCE_H_
+#define MEDIA_BASE_SEQUENCE_H_
+
+#include <cstddef>
+#include <optional>
+#include <ranges>
+#include <utility>
+#include <variant>
+
+#include "base/memory/raw_ptr.h"
+#include "base/memory/raw_ref.h"
+
+namespace media::sequence {
+
+template <typename C, typename T>
+concept Sequence = std::ranges::forward_range<C> &&
+                   std::same_as<std::ranges::range_value_t<C>, T>;
+
+namespace detail {
+
+template <typename Sequenceable>
+class ReferenceSequence {
+ public:
+  using value_type = typename std::ranges::range_value_t<Sequenceable>;
+
+  constexpr explicit ReferenceSequence(const Sequenceable& s) : s_(s) {}
+  constexpr ReferenceSequence(const ReferenceSequence&) = default;
+  constexpr ReferenceSequence& operator=(const ReferenceSequence&) = default;
+
+  constexpr auto begin() const { return s_->begin(); }
+  constexpr auto end() const { return s_->end(); }
+
+ private:
+  raw_ref<const Sequenceable> s_;
+};
+
+template <typename T>
+class SingletSequenceIterator {
+ public:
+  using iterator_category = std::forward_iterator_tag;
+  using difference_type = std::ptrdiff_t;
+  using value_type = T;
+
+  SingletSequenceIterator() = default;
+  constexpr explicit SingletSequenceIterator(const T* t, bool end = false)
+      : t_(t), end_(end) {}
+
+  constexpr const value_type& operator*() const { return *t_; }
+  constexpr const value_type* operator->() const { return t_; }
+
+  constexpr SingletSequenceIterator& operator++() {
+    t_ = nullptr;
+    end_ = true;
+    return *this;
+  }
+
+  constexpr SingletSequenceIterator operator++(int) {
+    SingletSequenceIterator temp = *this;
+    ++(*this);
+    return temp;
+  }
+
+  constexpr bool operator==(const SingletSequenceIterator& other) const {
+    return t_ == other.t_;
+  }
+
+ private:
+  raw_ptr<const T> t_;
+  bool end_;
+};
+
+template <typename T, bool Owned>
+class SingletSequence {
+ public:
+  using value_type = T;
+
+  constexpr explicit SingletSequence(T&& t)
+    requires(Owned)
+      : storage_(t) {}
+  constexpr explicit SingletSequence(T& t)
+    requires(!Owned)
+      : storage_(&t) {}
+
+  static constexpr SingletSequence EmptySinglet() { return SingletSequence(); }
+
+  constexpr SingletSequenceIterator<T> begin() const {
+    const T* ptr = GetDataPtr();
+    return SingletSequenceIterator<T>(ptr, ptr != nullptr);
+  }
+
+  constexpr SingletSequenceIterator<T> end() const {
+    return SingletSequenceIterator<T>(nullptr, true);
+  }
+
+ private:
+  std::conditional_t<Owned, const std::optional<T>, raw_ptr<const T>> storage_;
+
+  constexpr const T* GetDataPtr() const {
+    if constexpr (Owned) {
+      return storage_.has_value() ? &*storage_ : nullptr;
+    } else {
+      return storage_;
+    }
+  }
+
+  constexpr explicit SingletSequence() = default;
+};
+
+template <typename R, typename... Rs>
+struct ConcatSequenceIterator {
+ public:
+  using iterator_category = std::forward_iterator_tag;
+  using value_type = typename std::ranges::range_value_t<R>;
+  using difference_type = std::ptrdiff_t;
+
+  ConcatSequenceIterator() = default;
+
+  constexpr ConcatSequenceIterator(const std::tuple<R, Rs...>* view,
+                                   size_t index)
+      : sub_(view), index_(index) {
+    if (!IsEnd()) {
+      InitializeIter();
+      AdvanceIfEmpty();
+    }
+  }
+
+  constexpr const value_type& operator*() const {
+    return std::visit([](auto&& i) -> const value_type& { return *i; }, iter_);
+  }
+
+  constexpr const value_type* operator->() const {
+    return std::visit([](auto&& i) -> const value_type* { return &*i; }, iter_);
+  }
+
+  constexpr ConcatSequenceIterator& operator++() {
+    std::visit([](auto&& i) { ++i; }, iter_);
+    AdvanceIfEmpty();
+    return *this;
+  }
+
+  constexpr ConcatSequenceIterator operator++(int) {
+    ConcatSequenceIterator temp = *this;
+    ++(*this);
+    return temp;
+  }
+
+  constexpr bool operator==(const ConcatSequenceIterator& other) const {
+    if (IsEnd() && other.IsEnd()) {
+      return true;
+    }
+    if (index_ != other.index_) {
+      return false;
+    }
+    return iter_ == other.iter_;
+  }
+
+ private:
+  using VariantIter = std::variant<std::ranges::iterator_t<const R>,
+                                   std::ranges::iterator_t<const Rs>...>;
+
+  raw_ptr<const std::tuple<R, Rs...>> sub_ = nullptr;
+  size_t index_ = 0;
+  VariantIter iter_;
+
+  constexpr bool IsEnd() const { return index_ >= 1 + sizeof...(Rs); }
+
+  constexpr void InitializeIter() {
+    VisitAt<0>(index_, [this](auto I) {
+      iter_.template emplace<I>(std::get<I>(*sub_).begin());
+    });
+  }
+
+  constexpr bool CurrentIterIsRangeEnd() const {
+    return std::visit(
+        [this](auto&& i) {
+          return VisitAt<0>(iter_.index(), [&](auto I) {
+            auto end_iter = std::get<I>(*sub_).end();
+            if constexpr (std::is_same_v<std::decay_t<decltype(i)>,
+                                         decltype(end_iter)>) {
+              return i == end_iter;
+            } else {
+              return false;
+            }
+          });
+        },
+        iter_);
+  }
+
+  constexpr void AdvanceIfEmpty() {
+    while (!IsEnd() && CurrentIterIsRangeEnd()) {
+      index_++;
+      if (!IsEnd()) {
+        InitializeIter();
+      }
+    }
+  }
+
+  // Recursive helper to turn Runtime Index -> Compile Time Index
+  template <size_t I, typename Func>
+  static constexpr auto VisitAt(size_t runtime_index, Func&& f) {
+    if (runtime_index == I) {
+      return f(std::integral_constant<size_t, I>{});
+    }
+
+    if constexpr (I < sizeof...(Rs)) {
+      return VisitAt<I + 1>(runtime_index, std::forward<Func>(f));
+    } else {
+      return f(std::integral_constant<size_t, 0>{});
+    }
+  }
+};
+
+template <typename R, typename... Rs>
+struct ConcatSequence {
+ public:
+  using value_type = typename std::ranges::range_value_t<R>;
+
+  static_assert(
+      (std::same_as<value_type, typename std::ranges::range_value_t<Rs>> &&
+       ...),
+      "All concatenated sequences must have the same value_type");
+
+  ConcatSequence() = default;
+
+  constexpr explicit ConcatSequence(R r, Rs... rs)
+      : subsequences_{std::move(r), std::move(rs)...} {}
+
+  constexpr ConcatSequenceIterator<R, Rs...> begin() const {
+    return ConcatSequenceIterator<R, Rs...>{&subsequences_, 0};
+  }
+
+  constexpr ConcatSequenceIterator<R, Rs...> end() const {
+    return ConcatSequenceIterator<R, Rs...>{&subsequences_, 1 + sizeof...(Rs)};
+  }
+
+ private:
+  std::tuple<R, Rs...> subsequences_;
+};
+
+}  // namespace detail
+
+template <typename T>
+decltype(auto) Reference(T&& t) {
+  // If `t` is an lvalue reference type (a move only type for example), then
+  // this returns a non-owning view sequence over `t`.
+  // If `t` is an rvalue, this returns a decayed version of the iterable.
+  if constexpr (std::is_lvalue_reference_v<T>) {
+    return detail::ReferenceSequence<std::remove_reference_t<T>>(t);
+  } else {
+    return std::forward<T>(t);
+  }
+}
+
+template <typename T>
+auto Singlet(T&& t) {
+  return detail::SingletSequence<std::remove_reference_t<T>,
+                                 !std::is_lvalue_reference_v<T>>(
+      std::forward<T>(t));
+}
+
+template <typename T>
+auto EmptySinglet() {
+  return detail::SingletSequence<
+      std::remove_reference_t<T>,
+      !std::is_lvalue_reference_v<T>>::EmptySinglet();
+}
+
+template <typename... Rs>
+auto Concat(Rs&&... rs) {
+  return detail::ConcatSequence{Reference(std::forward<Rs>(rs))...};
+}
+
+}  // namespace media::sequence
+
+#endif  // MEDIA_BASE_SEQUENCE_H_
diff --git a/media/base/sequence_unittest.cc b/media/base/sequence_unittest.cc
new file mode 100644
index 0000000..d86ebd0
--- /dev/null
+++ b/media/base/sequence_unittest.cc
@@ -0,0 +1,304 @@
+// 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 "media/base/sequence.h"
+
+#include <list>
+#include <vector>
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace media {
+
+TEST(SequenceTest, ConcatTwoVectors) {
+  std::vector<int> v1 = {1, 2};
+  std::vector<int> v2 = {3, 4};
+
+  sequence::Sequence<int> auto v12 = sequence::Concat(v1, v2);
+
+  std::vector<int> actual = {v12.begin(), v12.end()};
+  std::vector<int> expected = {1, 2, 3, 4};
+
+  EXPECT_EQ(actual, expected);
+}
+
+TEST(SequenceTest, ConcatVecAndList) {
+  std::vector<int> v1 = {1, 2};
+  std::list<int> v2 = {3, 4};
+
+  sequence::Sequence<int> auto v12 = sequence::Concat(v1, v2);
+
+  std::vector<int> actual = {v12.begin(), v12.end()};
+  std::vector<int> expected = {1, 2, 3, 4};
+
+  EXPECT_EQ(actual, expected);
+}
+
+TEST(SequenceTest, ConcatVecAndSequence) {
+  std::vector<int> v1 = {1, 2};
+  std::vector<int> v2 = {3, 4};
+  std::vector<int> v3 = {5, 6};
+
+  sequence::Sequence<int> auto v12 = sequence::Concat(v1, v2);
+  sequence::Sequence<int> auto v123 = sequence::Concat(v12, v3);
+
+  std::vector<int> actual = {v123.begin(), v123.end()};
+  std::vector<int> expected = {1, 2, 3, 4, 5, 6};
+
+  EXPECT_EQ(actual, expected);
+}
+
+TEST(SequenceTest, ConcatHandlesEmptySubSequences) {
+  std::vector<int> empty1;
+  std::vector<int> val = {1};
+  std::vector<int> empty2;
+
+  sequence::Sequence<int> auto seq = sequence::Concat(empty1, val, empty2);
+
+  auto it = seq.begin();
+  EXPECT_EQ(*it, 1);
+  ++it;
+  EXPECT_EQ(it, seq.end());
+}
+
+TEST(SequenceTest, ConcatHandlesMoveOnlyTypes) {
+  std::vector<std::unique_ptr<int>> v1;
+  v1.push_back(std::make_unique<int>(1));
+  v1.push_back(std::make_unique<int>(2));
+
+  std::vector<std::unique_ptr<int>> v2;
+  v2.push_back(std::make_unique<int>(3));
+  v2.push_back(std::make_unique<int>(4));
+
+  std::vector<int> actual;
+  std::vector<int> expected = {1, 2, 3, 4};
+  for (const std::unique_ptr<int>& ptr : sequence::Concat(v1, v2)) {
+    actual.push_back(*ptr);
+  }
+
+  EXPECT_EQ(actual, expected);
+}
+
+TEST(SequenceTest, ConcatCanUseMovedTypes) {
+  std::vector<std::unique_ptr<int>> v1;
+  v1.push_back(std::make_unique<int>(1));
+  v1.push_back(std::make_unique<int>(2));
+
+  std::vector<std::unique_ptr<int>> v2;
+  v2.push_back(std::make_unique<int>(3));
+  v2.push_back(std::make_unique<int>(4));
+
+  std::vector<int> actual;
+  std::vector<int> expected = {1, 2, 3, 4};
+  for (const std::unique_ptr<int>& ptr : sequence::Concat(std::move(v1), v2)) {
+    actual.push_back(*ptr);
+  }
+
+  EXPECT_EQ(actual, expected);
+}
+
+TEST(SequenceTest, ConcatCanUseSinglets) {
+  std::unique_ptr<int> x = std::make_unique<int>(1);
+  std::unique_ptr<int> y = std::make_unique<int>(2);
+
+  std::vector<int> actual;
+  std::vector<int> expected = {1, 2};
+  for (const std::unique_ptr<int>& ptr :
+       sequence::Concat(sequence::Singlet(x), sequence::Singlet(y))) {
+    actual.push_back(*ptr);
+  }
+
+  EXPECT_EQ(actual, expected);
+}
+
+TEST(SequenceTest, CheckIteratorComparisons) {
+  std::vector<int> x = {1, 2, 3};
+  std::vector<int> y = {4, 5, 6};
+
+  auto seq = sequence::Concat(x, y);
+  auto it1 = seq.begin();
+  auto it2 = seq.begin();
+
+  ASSERT_EQ(it1, it2);
+  it1++;
+  ASSERT_NE(it1, it2);
+  it2++;
+  ASSERT_EQ(it1, it2);
+  it1++;
+  it1++;
+  ASSERT_NE(it1, it2);
+  it2++;
+  ASSERT_NE(it1, it2);
+  it2++;
+  ASSERT_EQ(it1, it2);
+  ASSERT_NE(it1, seq.end());
+  it1++;
+  it1++;
+  it1++;
+  it1++;
+  ASSERT_EQ(it1, seq.end());
+}
+
+namespace {
+
+// Helper class must be defined outside the TEST function
+struct CopyableNum {
+  static int copy_count;
+  static int move_count;
+  static int make_count;
+  int n_;
+
+  explicit CopyableNum(int n) : n_(n) { make_count++; }
+
+  CopyableNum(const CopyableNum& o) : n_(o.n_) { copy_count++; }
+
+  CopyableNum& operator=(const CopyableNum& o) {
+    n_ = o.n_;
+    copy_count++;
+    return *this;
+  }
+
+  CopyableNum(CopyableNum&& o) noexcept : n_(o.n_) {
+    o.n_ = 0;
+    move_count++;
+  }
+
+  CopyableNum& operator=(CopyableNum&& o) noexcept {
+    if (this != &o) {
+      n_ = o.n_;
+      o.n_ = 0;
+      move_count++;
+    }
+    return *this;
+  }
+
+  int Get() const { return n_; }
+};
+
+int CopyableNum::copy_count = 0;
+int CopyableNum::move_count = 0;
+int CopyableNum::make_count = 0;
+
+struct UncopyableObject {
+ public:
+  bool value;
+
+  explicit UncopyableObject(bool v) : value(v) {}
+
+  UncopyableObject(const UncopyableObject&) = delete;
+  UncopyableObject(UncopyableObject&&);
+  UncopyableObject& operator=(const UncopyableObject&) = delete;
+  UncopyableObject& operator=(UncopyableObject&&) = delete;
+};
+
+struct UncopyableHolder {
+  std::list<UncopyableObject> objs;
+
+  explicit UncopyableHolder() {
+    objs.emplace_back(true);
+    objs.emplace_back(true);
+  }
+
+  sequence::Sequence<UncopyableObject> auto iterate() const {
+    return sequence::Reference(objs);
+  }
+};
+
+}  // namespace
+
+TEST(SequenceTest, ListOfUncopyableObjects) {
+  std::list<UncopyableObject> objs;
+  objs.emplace_back(true);
+  objs.emplace_back(true);
+
+  for (const UncopyableObject& obj : sequence::Reference(objs)) {
+    EXPECT_TRUE(obj.value);
+  }
+
+  UncopyableHolder holder;
+  for (const UncopyableObject& obj : holder.iterate()) {
+    EXPECT_TRUE(obj.value);
+  }
+}
+
+TEST(SequenceTest, ConcatAvoidsCopiesUsingViews) {
+  std::vector<CopyableNum> v1;
+  v1.reserve(2);
+  v1.emplace_back(1);
+  v1.emplace_back(2);
+
+  ASSERT_EQ(CopyableNum::copy_count, 0);
+  ASSERT_EQ(CopyableNum::move_count, 0);
+  ASSERT_EQ(CopyableNum::make_count, 2);
+
+  std::vector<CopyableNum> v2;
+  v2.emplace_back(3);
+  v2.emplace_back(4);
+
+  // v2 gets resized after adding the second element, which triggered a move.
+  ASSERT_EQ(CopyableNum::copy_count, 0);
+  ASSERT_EQ(CopyableNum::move_count, 1);
+  ASSERT_EQ(CopyableNum::make_count, 4);
+
+  // Reset counter
+  CopyableNum::copy_count = 0;
+  CopyableNum::move_count = 0;
+  CopyableNum::make_count = 0;
+
+  std::vector<int> actual;
+  std::vector<int> expected = {1, 2, 3, 4};
+
+  for (const auto& impl : sequence::Concat(v1, v2)) {
+    actual.push_back(impl.Get());
+  }
+
+  // Because sequence::Concat took just rvalues, no moves or copies took place.
+  EXPECT_EQ(actual, expected);
+  EXPECT_EQ(CopyableNum::copy_count, 0);
+  EXPECT_EQ(CopyableNum::move_count, 0);
+  EXPECT_EQ(CopyableNum::make_count, 0);
+
+  actual.clear();
+
+  for (const auto& impl : sequence::Concat(std::move(v1), v2)) {
+    actual.push_back(impl.Get());
+  }
+
+  // Still no copies or moves!
+  EXPECT_EQ(actual, expected);
+  EXPECT_EQ(CopyableNum::copy_count, 0);
+  EXPECT_EQ(CopyableNum::move_count, 0);
+  EXPECT_EQ(CopyableNum::make_count, 0);
+}
+
+sequence::Sequence<int> auto GetAlternatingSequence(bool front) {
+  if (front) {
+    return sequence::Concat(sequence::Singlet(1), sequence::EmptySinglet<int>(),
+                            sequence::Singlet(2),
+                            sequence::EmptySinglet<int>());
+  } else {
+    return sequence::Concat(sequence::EmptySinglet<int>(), sequence::Singlet(1),
+                            sequence::EmptySinglet<int>(),
+                            sequence::Singlet(2));
+  }
+}
+
+TEST(SequenceTest, SingletsAndEmptyAlternate) {
+  sequence::Sequence<int> auto x = GetAlternatingSequence(true);
+  sequence::Sequence<int> auto y = GetAlternatingSequence(false);
+
+  auto x_it = x.begin();
+  auto y_it = y.begin();
+
+  while (x_it != x.end() && y_it != y.end()) {
+    EXPECT_EQ(*x_it, *y_it);
+    x_it++;
+    y_it++;
+  }
+
+  EXPECT_EQ(x_it, x.end());
+  EXPECT_EQ(y_it, y.end());
+}
+
+}  // namespace media
diff --git a/media/base/video_frame.cc b/media/base/video_frame.cc
index 95a15c71..4eba918 100644
--- a/media/base/video_frame.cc
+++ b/media/base/video_frame.cc
@@ -41,8 +41,6 @@
 #include "ui/gfx/geometry/point.h"
 
 #if BUILDFLAG(IS_CHROMEOS)
-// TODO(crbug.com/40263579): Remove.
-#include "gpu/ipc/common/legacy_gpu_memory_buffer_for_video.h"
 #include "ui/ozone/public/client_native_pixmap_factory_ozone.h"  // nogncheck
 #endif
 
@@ -362,82 +360,6 @@
   return frame;
 }
 
-#if BUILDFLAG(IS_CHROMEOS)
-scoped_refptr<VideoFrame> VideoFrame::CreateFrameForGpuMemoryBufferInternal(
-    const gfx::Rect& visible_rect,
-    const gfx::Size& natural_size,
-    std::unique_ptr<gpu::LegacyGpuMemoryBufferForVideo> gpu_memory_buffer,
-    base::TimeDelta timestamp) {
-  CHECK(gpu_memory_buffer);
-
-  auto si_format = gpu_memory_buffer->GetFormat();
-  auto format = SharedImageFormatToVideoPixelFormat(si_format);
-  if (!format) {
-    return nullptr;
-  }
-  constexpr StorageType storage = STORAGE_GPU_MEMORY_BUFFER;
-  const gfx::Size& coded_size = gpu_memory_buffer->GetSize();
-  if (!IsValidConfig(*format, storage, coded_size, visible_rect,
-                     natural_size)) {
-    DLOG(ERROR) << __func__ << " Invalid config"
-                << ConfigToString(*format, storage, coded_size, visible_rect,
-                                  natural_size);
-    return nullptr;
-  }
-
-  const size_t num_planes = si_format.NumberOfPlanes();
-  std::vector<ColorPlaneLayout> planes(num_planes);
-  for (size_t plane = 0; plane < num_planes; ++plane) {
-    planes[plane].stride = gpu_memory_buffer->stride(plane);
-    gfx::Size plane_size = PlaneSizeInSamples(*format, plane, coded_size);
-    planes[plane].size =
-        (plane_size.height() - 1) * planes[plane].stride +
-        plane_size.width() * VideoFrame::BytesPerElement(*format, plane);
-  }
-  uint64_t modifier = gfx::NativePixmapHandle::kNoModifier;
-  const auto gmb_handle = gpu_memory_buffer->CloneHandle();
-  if (gmb_handle.is_null() ||
-      gmb_handle.native_pixmap_handle().planes.empty()) {
-    DLOG(ERROR) << "Failed to clone the GpuMemoryBufferHandle";
-    return nullptr;
-  }
-  const gfx::NativePixmapHandle& native_pixmap_handle =
-      gmb_handle.native_pixmap_handle();
-  if (native_pixmap_handle.planes.size() != num_planes) {
-    DLOG(ERROR) << "Invalid number of planes="
-                << native_pixmap_handle.planes.size()
-                << ", expected num_planes=" << num_planes;
-    return nullptr;
-  }
-  for (size_t i = 0; i < num_planes; ++i) {
-    const auto& plane = native_pixmap_handle.planes[i];
-    planes[i].stride = plane.stride;
-    planes[i].offset = plane.offset;
-    planes[i].size = plane.size;
-  }
-  modifier = native_pixmap_handle.modifier;
-
-  const auto layout = VideoFrameLayout::CreateWithPlanes(
-      *format, coded_size, std::move(planes),
-      VideoFrameLayout::kBufferAddressAlignment, modifier);
-  if (!layout) {
-    DLOG(ERROR) << __func__ << " Invalid layout";
-    return nullptr;
-  }
-
-  auto frame = base::MakeRefCounted<VideoFrame>(base::PassKey<VideoFrame>(),
-                                                *layout, storage, visible_rect,
-                                                natural_size, timestamp);
-  if (!frame) {
-    DLOG(ERROR) << __func__ << " Couldn't create VideoFrame instance";
-    return nullptr;
-  }
-  frame->gpu_memory_buffer_ = std::move(gpu_memory_buffer);
-  frame->is_mappable_si_enabled_ = false;
-  return frame;
-}
-#endif
-
 // static
 scoped_refptr<VideoFrame> VideoFrame::WrapSharedImage(
     VideoPixelFormat format,
@@ -778,28 +700,6 @@
   return frame;
 }
 
-#if BUILDFLAG(IS_CHROMEOS)
-// static
-scoped_refptr<VideoFrame> VideoFrame::WrapExternalGpuMemoryBufferHandle(
-    const gfx::Rect& visible_rect,
-    const gfx::Size& natural_size,
-    gfx::ClientNativePixmapFactory* client_native_pixmap_factory,
-    gfx::GpuMemoryBufferHandle handle,
-    const gfx::Size& coded_size,
-    viz::SharedImageFormat format,
-    gfx::BufferUsage usage,
-    base::TimeDelta timestamp) {
-  CHECK(viz::HasEquivalentBufferFormat(format));
-  CHECK_EQ(handle.type, gfx::GpuMemoryBufferType::NATIVE_PIXMAP);
-  auto gpu_memory_buffer =
-      gpu::LegacyGpuMemoryBufferForVideo::CreateFromHandleForVideoFrame(
-          client_native_pixmap_factory, std::move(handle), coded_size, format,
-          usage);
-  return CreateFrameForGpuMemoryBufferInternal(
-      visible_rect, natural_size, std::move(gpu_memory_buffer), timestamp);
-}
-#endif
-
 #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
 // static
 scoped_refptr<VideoFrame> VideoFrame::WrapExternalDmabufs(
@@ -1288,11 +1188,6 @@
     CHECK(shared_image_);
     return !shared_image_->IsSharedMemoryForVideoFrame();
   }
-#if BUILDFLAG(IS_CHROMEOS)
-  else if (gpu_memory_buffer_) {
-    return gpu_memory_buffer_->GetType() != gfx::SHARED_MEMORY_BUFFER;
-  }
-#endif
   return false;
 }
 
@@ -1312,14 +1207,6 @@
                        base::Unretained(this), std::move(result_cb)));
     return;
   }
-#if BUILDFLAG(IS_CHROMEOS)
-  if (gpu_memory_buffer_) {
-    // LegacyGpuMemoryBufferForVideo supports only synchronous mapping.
-    MakeScopedMappingForGpuMemoryBuffer(std::move(result_cb),
-                                        gpu_memory_buffer_->Map());
-    return;
-  }
-#endif
   std::move(result_cb).Run(nullptr);
 }
 
@@ -1333,7 +1220,7 @@
     return shared_image_->AsyncMappingIsNonBlocking();
   }
 #if BUILDFLAG(IS_CHROMEOS)
-  // LegacyGpuMemoryBufferForVideo supports only synchronous mapping.
+  // TODO(crbug.com/404905709): Remove this code, which is now unreachable.
   return false;
 #else
   NOTREACHED();
@@ -1349,11 +1236,6 @@
     CHECK(HasSharedImage());
     return shared_image_->CloneGpuMemoryBufferHandle();
   }
-#if BUILDFLAG(IS_CHROMEOS)
-  if (gpu_memory_buffer_) {
-    return gpu_memory_buffer_->CloneHandle();
-  }
-#endif
   return gfx::GpuMemoryBufferHandle();
 }
 
@@ -1910,33 +1792,6 @@
   std::unique_ptr<gpu::ClientSharedImage::ScopedMapping> scoped_mapping_;
 };
 
-#if BUILDFLAG(IS_CHROMEOS)
-class ScopedMappingGMBImpl : public VideoFrame::ScopedMapping {
- public:
-  ScopedMappingGMBImpl(gpu::LegacyGpuMemoryBufferForVideo* gpu_memory_buffer)
-      : gpu_memory_buffer_(gpu_memory_buffer) {
-    CHECK(gpu_memory_buffer);
-  }
-
-  ~ScopedMappingGMBImpl() override { gpu_memory_buffer_->Unmap(); }
-
-  base::span<uint8_t> GetMemoryAsSpan(uint32_t plane_index) override {
-    return gpu_memory_buffer_->memory_span(plane_index);
-  }
-
-  size_t Stride(uint32_t plane_index) override {
-    return base::checked_cast<size_t>(gpu_memory_buffer_->stride(plane_index));
-  }
-
-  gfx::Size Size() override { return gpu_memory_buffer_->GetSize(); }
-
- private:
-  // RAW_PTR_EXCLUSION: Performance reasons (based on analysis of MotionMark).
-  RAW_PTR_EXCLUSION gpu::LegacyGpuMemoryBufferForVideo* gpu_memory_buffer_ =
-      nullptr;
-};
-#endif
-
 std::unique_ptr<VideoFrame::ScopedMapping> VideoFrame::MapGMBOrSharedImage()
     const {
   if (wrapped_frame_) {
@@ -1949,25 +1804,9 @@
       return base::WrapUnique(new ScopedMappingSIImpl(std::move(mapping)));
     }
   }
-#if BUILDFLAG(IS_CHROMEOS)
-  if (gpu_memory_buffer_ && gpu_memory_buffer_->Map()) {
-    return base::WrapUnique(new ScopedMappingGMBImpl(gpu_memory_buffer_.get()));
-  }
-#endif
   return nullptr;
 }
 
-#if BUILDFLAG(IS_CHROMEOS)
-void VideoFrame::MakeScopedMappingForGpuMemoryBuffer(
-    base::OnceCallback<void(std::unique_ptr<VideoFrame::ScopedMapping>)>
-        result_cb,
-    bool success) const {
-  std::move(result_cb).Run(success ? base::WrapUnique(new ScopedMappingGMBImpl(
-                                         gpu_memory_buffer_.get()))
-                                   : nullptr);
-}
-#endif
-
 void VideoFrame::WrapScopedSharedImageMapping(
     base::OnceCallback<void(std::unique_ptr<VideoFrame::ScopedMapping>)>
         result_cb,
diff --git a/media/base/video_frame.h b/media/base/video_frame.h
index d32d384..f92fe692 100644
--- a/media/base/video_frame.h
+++ b/media/base/video_frame.h
@@ -51,11 +51,6 @@
 
 namespace gfx {
 struct GpuMemoryBufferHandle;
-class ClientNativePixmapFactory;
-}
-
-namespace gpu {
-class LegacyGpuMemoryBufferForVideo;
 }
 
 namespace media {
@@ -334,20 +329,6 @@
       base::span<const uint8_t> uv_data,
       base::TimeDelta timestamp);
 
-#if BUILDFLAG(IS_CHROMEOS)
-  // Wraps |handle|. For use in contexts where the GPUMemoryBufferHandle has no
-  // SharedImage associated with it.
-  static scoped_refptr<VideoFrame> WrapExternalGpuMemoryBufferHandle(
-      const gfx::Rect& visible_rect,
-      const gfx::Size& natural_size,
-      gfx::ClientNativePixmapFactory* client_native_pixmap_factory,
-      gfx::GpuMemoryBufferHandle handle,
-      const gfx::Size& coded_size,
-      viz::SharedImageFormat format,
-      gfx::BufferUsage usage,
-      base::TimeDelta timestamp);
-#endif
-
 #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
   // Wraps provided dmabufs
   // (https://www.kernel.org/doc/html/latest/driver-api/dma-buf.html) with a
@@ -809,12 +790,6 @@
       base::TimeDelta timestamp);
 
 #if BUILDFLAG(IS_CHROMEOS)
-  static scoped_refptr<VideoFrame> CreateFrameForGpuMemoryBufferInternal(
-      const gfx::Rect& visible_rect,
-      const gfx::Size& natural_size,
-      std::unique_ptr<gpu::LegacyGpuMemoryBufferForVideo> gpu_memory_buffer,
-      base::TimeDelta timestamp);
-
   void MakeScopedMappingForGpuMemoryBuffer(
       base::OnceCallback<void(std::unique_ptr<VideoFrame::ScopedMapping>)>
           result_cb,
@@ -900,11 +875,6 @@
   base::ReadOnlySharedMemoryRegion owned_shm_region_;
   base::ReadOnlySharedMemoryMapping owned_shm_mapping_;
 
-#if BUILDFLAG(IS_CHROMEOS)
-  // GPU memory buffer, if this frame is STORAGE_GPU_MEMORY_BUFFER.
-  std::unique_ptr<gpu::LegacyGpuMemoryBufferForVideo> gpu_memory_buffer_;
-#endif
-
   // This field will be set by clients when using MappableSI instead of
   // GpuMemoryBuffers. Clients will set this flag while creating a VideoFrame.
   bool is_mappable_si_enabled_ = false;
diff --git a/media/cdm/library_cdm/clear_key_cdm/clear_key_cdm.cc b/media/cdm/library_cdm/clear_key_cdm/clear_key_cdm.cc
index 0b91110..69dedcc4 100644
--- a/media/cdm/library_cdm/clear_key_cdm/clear_key_cdm.cc
+++ b/media/cdm/library_cdm/clear_key_cdm/clear_key_cdm.cc
@@ -12,6 +12,7 @@
 #include <algorithm>
 #include <cstring>
 #include <memory>
+#include <optional>
 #include <sstream>
 #include <utility>
 
@@ -274,18 +275,17 @@
 
   int num_opened_files = 0;
   for (uint32_t i = 0; i < num_files; ++i) {
-    const int kBytesToRead = 10;
-    std::vector<char> buffer(kBytesToRead);
-
     base::File file(static_cast<base::PlatformFile>(host_files[i].file));
     if (!file.IsValid())
       continue;
 
     num_opened_files++;
 
-    int bytes_read = file.Read(0, buffer.data(), buffer.size());
-    if (bytes_read != kBytesToRead) {
-      LOG(ERROR) << "File bytes read: " << bytes_read;
+    constexpr int kBytesToRead = 10;
+    uint8_t buffer[kBytesToRead];
+    if (const std::optional<size_t> bytes_read = file.Read(0, buffer);
+        bytes_read != kBytesToRead) {
+      LOG(ERROR) << "File bytes read: " << bytes_read.value_or(-1);
       g_verify_host_files_result = false;
       return true;
     }
diff --git a/media/filters/hls_manifest_demuxer_engine.cc b/media/filters/hls_manifest_demuxer_engine.cc
index 5ed7ab8..453c459 100644
--- a/media/filters/hls_manifest_demuxer_engine.cc
+++ b/media/filters/hls_manifest_demuxer_engine.cc
@@ -423,8 +423,6 @@
     double playback_rate,
     ManifestDemuxer::DelayCallback cb) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
-  TRACE_EVENT_BEGIN("media", "HLS::OnTimeUpdate",
-                    perfetto::Track::FromPointer(this));
   cb = base::BindOnce(&HlsManifestDemuxerEngine::FinishTimeUpdate,
                       weak_factory_.GetWeakPtr(), std::move(cb));
   for (const auto& [role, _] : renditions_) {
@@ -439,7 +437,6 @@
     ManifestDemuxer::DelayCallback cb,
     base::TimeDelta delay_time) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
-  TRACE_EVENT_END("media", perfetto::Track::FromPointer(this));
   std::move(cb).Run(std::move(delay_time));
 }
 
@@ -492,8 +489,6 @@
     GURL uri,
     HlsDemuxerStatusCallback cb) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
-  TRACE_EVENT_BEGIN("media", "HLS::UpdateRenditionManifest",
-                    perfetto::Track::FromPointer(this), "uri", uri);
   GURL uri_copy = uri;
   ReadManifest(
       std::move(uri_copy),
@@ -538,7 +533,6 @@
                        std::move(error).AddHere()});
     return;
   }
-  TRACE_EVENT_END("media", perfetto::Track::FromPointer(this));
 
   renditions_[role]->UpdatePlaylist(std::move(maybe_playlist).value());
   std::move(cb).Run(OkStatus());
@@ -565,8 +559,6 @@
     std::optional<hls::RenditionGroup::RenditionTrack> extra,
     HlsDemuxerStatusCallback cb) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
-  TRACE_EVENT_BEGIN("media", "HLS::SelectRenditions",
-                    perfetto::Track::FromPointer(this), "reselect", true);
 
   OnRenditionsSelected(std::move(cb), variant, std::move(primary),
                        std::move(extra));
@@ -745,8 +737,6 @@
     scoped_refptr<hls::MultivariantPlaylist> playlist) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
   CHECK(!rendition_manager_);
-  TRACE_EVENT_BEGIN("media", "HLS::SelectRenditions",
-                    perfetto::Track::FromPointer(this));
   multivariant_root_ = std::move(playlist);
   rendition_manager_ = std::make_unique<hls::RenditionManager>(
       multivariant_root_,
@@ -760,8 +750,6 @@
     std::move(parse_complete_cb).Run(HlsDemuxerStatus::Codes::kNoRenditions);
     return;
   }
-
-  TRACE_EVENT_END("media", perfetto::Track::FromPointer(this));
   rendition_manager_->Reselect(
       base::BindOnce(&HlsManifestDemuxerEngine::OnRenditionsSelected,
                      weak_factory_.GetWeakPtr(), std::move(parse_complete_cb)));
@@ -803,8 +791,6 @@
   std::vector<std::string> no_codecs;
   selected_variant_codecs_ = variant->GetCodecs().value_or(no_codecs);
 
-  TRACE_EVENT_END("media", perfetto::Track::FromPointer(this));
-
   if (extra.has_value()) {
     on_complete = BindPlaylistLoader(extra.value(), kAudioOverride,
                                      std::move(on_complete));
@@ -821,8 +807,6 @@
     HlsDemuxerStatusCallback on_complete) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
   auto uri = parse_info.uri;
-  TRACE_EVENT_BEGIN("media", "HLS::LoadPlaylist",
-                    perfetto::Track::FromPointer(this), "uri", uri);
   ReadManifest(std::move(uri),
                base::BindOnce(&HlsManifestDemuxerEngine::ParsePlaylist,
                               weak_factory_.GetWeakPtr(),
@@ -842,14 +826,11 @@
   if (maybe_exists != renditions_.end()) {
     maybe_exists->second->UpdatePlaylistURI(parse_info.uri);
     maybe_exists->second->UpdatePlaylist(std::move(playlist));
-    TRACE_EVENT_END("media", perfetto::Track::FromPointer(this));
     std::move(parse_complete_cb).Run(OkStatus());
     return;
   }
 
   hls::MediaPlaylist* playlist_ptr = playlist.get();
-  TRACE_EVENT_BEGIN("media", "HLS::DetermineStreamContainerAndCodecs",
-                    perfetto::Track::FromPointer(this));
   DetermineStreamContainer(
       playlist_ptr,
       base::BindOnce(&HlsManifestDemuxerEngine::OnStreamContainerDetermined,
@@ -897,12 +878,6 @@
   is_seekable_ = seekable;
   stats_reporter_.SetIsLiveContent(!seekable);
   renditions_[parse_info.role] = std::move(rendition);
-  TRACE_EVENT_END(
-      "media",
-      /*HLS::DetermineStreamContainerAndCodecs */ perfetto::Track::FromPointer(
-          this));
-  TRACE_EVENT_END("media",
-                  /* HLS::LoadPlaylist */ perfetto::Track::FromPointer(this));
   std::move(parse_complete_cb).Run(OkStatus());
 }
 
@@ -938,9 +913,6 @@
   if (mime.has_value()) {
     std::move(container_cb).Run(mime.value());
   } else {
-    TRACE_EVENT_BEGIN("media", "HLS::PeekSegmentChunk",
-                      perfetto::Track::FromPointer(this), "uri",
-                      segments[0]->GetUri());
     bool read_chunked = true;
     if (auto enc_data = segments[0]->GetEncryptionData()) {
       switch (enc_data->GetMethod()) {
@@ -967,8 +939,6 @@
     HlsDemuxerStatusCb<RelaxedParserSupportedType> cb,
     HlsDataSourceProvider::ReadResult maybe_stream) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(media_sequence_checker_);
-  TRACE_EVENT_END(
-      "media", /* HLS::PeekSegmentChunk */ perfetto::Track::FromPointer(this));
 
   if (!maybe_stream.has_value()) {
     std::move(cb).Run(HlsDemuxerStatusTraits::FromReadStatus(
diff --git a/media/filters/hls_rendition_impl.cc b/media/filters/hls_rendition_impl.cc
index 46e525bb..cf5f533d 100644
--- a/media/filters/hls_rendition_impl.cc
+++ b/media/filters/hls_rendition_impl.cc
@@ -258,9 +258,6 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   CHECK(!is_stopped_for_shutdown_);
   last_download_time_ = base::TimeTicks::Now();
-  TRACE_EVENT_BEGIN("media", "HLS::FetchManifestUpdates",
-                    perfetto::Track::FromPointer(this), "uri",
-                    media_playlist_uri_);
   rendition_host_->UpdateRenditionManifestUri(
       role_, media_playlist_uri_,
       base::BindOnce(&HlsRenditionImpl::OnManifestUpdate,
@@ -270,7 +267,6 @@
 void HlsRenditionImpl::OnManifestUpdate(ManifestDemuxer::DelayCallback cb,
                                         base::TimeDelta delay,
                                         HlsDemuxerStatus success) {
-  TRACE_EVENT_END("media", perfetto::Track::FromPointer(this));
   auto update_duration = base::TimeTicks::Now() - last_download_time_;
   if (update_duration > delay) {
     std::move(cb).Run(base::Seconds(0));
@@ -467,10 +463,6 @@
   // again.
   bool include_init = requires_init_segment_ || needs_init;
 
-  TRACE_EVENT_BEGIN("media", "HLS::FetchSegment",
-                    perfetto::Track::FromPointer(this), "start", segment_start,
-                    "include init", include_init);
-
   bool is_fetching_new_key = false;
   if (auto enc = segment->GetEncryptionData()) {
     is_fetching_new_key = enc->NeedsKeyFetch();
@@ -493,7 +485,6 @@
     bool fetched_new_key,
     HlsDataSourceProvider::ReadResult result) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  TRACE_EVENT_END("media", perfetto::Track::FromPointer(this));
   if (is_stopped_for_shutdown_) {
     std::move(cb).Run();
     return;
diff --git a/media/formats/hls/rendition_group.h b/media/formats/hls/rendition_group.h
index 40def83..34b1b1d4 100644
--- a/media/formats/hls/rendition_group.h
+++ b/media/formats/hls/rendition_group.h
@@ -16,6 +16,7 @@
 #include "base/types/pass_key.h"
 #include "media/base/media_export.h"
 #include "media/base/media_track.h"
+#include "media/base/sequence.h"
 #include "media/formats/hls/parse_status.h"
 #include "media/formats/hls/tags.h"
 #include "media/formats/hls/types.h"
@@ -79,7 +80,9 @@
     return renditions_;
   }
 
-  const std::vector<MediaTrack>& GetTracks() const { return tracks_; }
+  sequence::Sequence<MediaTrack> auto GetTracks() const {
+    return sequence::Reference(tracks_);
+  }
 
   bool HasTracks() const { return !tracks_.empty(); }
 
diff --git a/media/formats/hls/rendition_manager.cc b/media/formats/hls/rendition_manager.cc
index cf2ecfde..9cd73947 100644
--- a/media/formats/hls/rendition_manager.cc
+++ b/media/formats/hls/rendition_manager.cc
@@ -280,15 +280,4 @@
   abr_algorithm_ = std::move(abr_algorithm);
 }
 
-std::vector<MediaTrack> RenditionManager::GetSelectableAudioRenditions() const {
-  if (active_variant_) {
-    return active_variant_->GetAudioRenditionGroup()->GetTracks();
-  }
-  return {};
-}
-
-std::vector<MediaTrack> RenditionManager::GetSelectableVideoRenditions() const {
-  return selectable_variant_tracks_;
-}
-
 }  // namespace media::hls
diff --git a/media/formats/hls/rendition_manager.h b/media/formats/hls/rendition_manager.h
index 5900ca0b..0316a90 100644
--- a/media/formats/hls/rendition_manager.h
+++ b/media/formats/hls/rendition_manager.h
@@ -21,6 +21,7 @@
 #include "media/base/demuxer.h"
 #include "media/base/limits.h"
 #include "media/base/media_export.h"
+#include "media/base/sequence.h"
 #include "media/formats/hls/abr_algorithm.h"
 #include "media/formats/hls/rendition_group.h"
 #include "media/formats/hls/types.h"
@@ -101,8 +102,17 @@
 
   bool HasSelectableVariants() const { return !selectable_variants_.empty(); }
 
-  std::vector<MediaTrack> GetSelectableVideoRenditions() const;
-  std::vector<MediaTrack> GetSelectableAudioRenditions() const;
+  sequence::Sequence<MediaTrack> auto GetSelectableVideoRenditions() const {
+    return sequence::Reference(selectable_variant_tracks_);
+  }
+
+  sequence::Sequence<MediaTrack> auto GetSelectableAudioRenditions() const {
+    static const std::vector<MediaTrack> kEmpty;
+    if (active_variant_) {
+      return active_variant_->GetAudioRenditionGroup()->GetTracks();
+    }
+    return sequence::Reference(kEmpty);
+  }
 
  private:
   const VariantStream* SelectBestVariant() const;
diff --git a/media/formats/hls/rendition_manager_unittest.cc b/media/formats/hls/rendition_manager_unittest.cc
index fe0e97c..02ff270f 100644
--- a/media/formats/hls/rendition_manager_unittest.cc
+++ b/media/formats/hls/rendition_manager_unittest.cc
@@ -708,11 +708,11 @@
 TEST_F(HlsRenditionManagerTest, VariantNames) {
   {
     // Differentiated by resolution
-    auto variants =
-        GetRenditionManager(
-            MakeVariantStr(1234, "1920x1080", std::nullopt), "playlist1.m3u8",
-            MakeVariantStr(1234, "1366x768", std::nullopt), "playlist2.m3u8")
-            .GetSelectableVideoRenditions();
+    auto rm = GetRenditionManager(
+        MakeVariantStr(1234, "1920x1080", std::nullopt), "playlist1.m3u8",
+        MakeVariantStr(1234, "1366x768", std::nullopt), "playlist2.m3u8");
+    auto sequence = rm.GetSelectableVideoRenditions();
+    std::vector<MediaTrack> variants(sequence.begin(), sequence.end());
     ASSERT_EQ(variants.size(), 2u);
     ASSERT_EQ(variants[0].label().value(), "1920x1080");
     ASSERT_EQ(variants[1].label().value(), "1366x768");
@@ -720,11 +720,12 @@
 
   {
     // No differentiation
-    auto variants =
-        GetRenditionManager(
-            MakeVariantStr(1234, "1920x1080", std::nullopt), "playlist1.m3u8",
-            MakeVariantStr(1234, "1920x1080", std::nullopt), "playlist2.m3u8")
-            .GetSelectableVideoRenditions();
+    auto rm = GetRenditionManager(
+        MakeVariantStr(1234, "1920x1080", std::nullopt), "playlist1.m3u8",
+        MakeVariantStr(1234, "1920x1080", std::nullopt), "playlist2.m3u8");
+    sequence::Sequence<MediaTrack> auto sequence =
+        rm.GetSelectableVideoRenditions();
+    std::vector<MediaTrack> variants(sequence.begin(), sequence.end());
     ASSERT_EQ(variants.size(), 2u);
     ASSERT_EQ(variants[0].label().value(), "Stream: 1");
     ASSERT_EQ(variants[1].label().value(), "Stream: 2");
@@ -732,11 +733,11 @@
 
   {
     // Same resolution, differentiated by framerate
-    auto variants =
-        GetRenditionManager(
-            MakeVariantStr(1234, "1920x1080", "24.00"), "playlist1.m3u8",
-            MakeVariantStr(1234, "1920x1080", "60.00"), "playlist2.m3u8")
-            .GetSelectableVideoRenditions();
+    auto rm = GetRenditionManager(
+        MakeVariantStr(1234, "1920x1080", "24.00"), "playlist1.m3u8",
+        MakeVariantStr(1234, "1920x1080", "60.00"), "playlist2.m3u8");
+    auto sequence = rm.GetSelectableVideoRenditions();
+    std::vector<MediaTrack> variants(sequence.begin(), sequence.end());
     ASSERT_EQ(variants.size(), 2u);
     ASSERT_EQ(variants[0].label().value(), "24fps");
     ASSERT_EQ(variants[1].label().value(), "60fps");
@@ -744,11 +745,11 @@
 
   {
     // No differentiation
-    auto variants =
-        GetRenditionManager(
-            MakeVariantStr(1234, "1920x1080", "60.00"), "playlist1.m3u8",
-            MakeVariantStr(1234, "1920x1080", "60.00"), "playlist2.m3u8")
-            .GetSelectableVideoRenditions();
+    auto rm = GetRenditionManager(
+        MakeVariantStr(1234, "1920x1080", "60.00"), "playlist1.m3u8",
+        MakeVariantStr(1234, "1920x1080", "60.00"), "playlist2.m3u8");
+    auto sequence = rm.GetSelectableVideoRenditions();
+    std::vector<MediaTrack> variants(sequence.begin(), sequence.end());
     ASSERT_EQ(variants.size(), 2u);
     ASSERT_EQ(variants[0].label().value(), "Stream: 1");
     ASSERT_EQ(variants[1].label().value(), "Stream: 2");
@@ -756,11 +757,11 @@
 
   {
     // Only bandwidth differentiation
-    auto variants =
-        GetRenditionManager(
-            MakeVariantStr(831270, "1920x1080", "60.00"), "playlist1.m3u8",
-            MakeVariantStr(1144430, "1920x1080", "60.00"), "playlist2.m3u8")
-            .GetSelectableVideoRenditions();
+    auto rm = GetRenditionManager(
+        MakeVariantStr(831270, "1920x1080", "60.00"), "playlist1.m3u8",
+        MakeVariantStr(1144430, "1920x1080", "60.00"), "playlist2.m3u8");
+    auto sequence = rm.GetSelectableVideoRenditions();
+    std::vector<MediaTrack> variants(sequence.begin(), sequence.end());
     ASSERT_EQ(variants.size(), 2u);
     ASSERT_EQ(variants[0].label().value(), "831 Kbps");
     ASSERT_EQ(variants[1].label().value(), "1.1 Mbps");
@@ -768,11 +769,11 @@
 
   {
     // Bandwidth differentiation greater than 0.1Mbps, but less than 1 Mbps.
-    auto variants =
-        GetRenditionManager(
-            MakeVariantStr(1144430, "1920x1080", "60.00"), "playlist1.m3u8",
-            MakeVariantStr(1344430, "1920x1080", "60.00"), "playlist2.m3u8")
-            .GetSelectableVideoRenditions();
+    auto rm = GetRenditionManager(
+        MakeVariantStr(1144430, "1920x1080", "60.00"), "playlist1.m3u8",
+        MakeVariantStr(1344430, "1920x1080", "60.00"), "playlist2.m3u8");
+    auto sequence = rm.GetSelectableVideoRenditions();
+    std::vector<MediaTrack> variants(sequence.begin(), sequence.end());
     ASSERT_EQ(variants.size(), 2u);
     ASSERT_EQ(variants[0].label().value(), "1.1 Mbps");
     ASSERT_EQ(variants[1].label().value(), "1.3 Mbps");
@@ -780,11 +781,11 @@
 
   {
     // Bandwidth differentiation resolution too small to differentiate names
-    auto variants =
-        GetRenditionManager(
-            MakeVariantStr(1144430, "1920x1080", "60.00"), "playlist1.m3u8",
-            MakeVariantStr(1144432, "1920x1080", "60.00"), "playlist2.m3u8")
-            .GetSelectableVideoRenditions();
+    auto rm = GetRenditionManager(
+        MakeVariantStr(1144430, "1920x1080", "60.00"), "playlist1.m3u8",
+        MakeVariantStr(1144432, "1920x1080", "60.00"), "playlist2.m3u8");
+    auto sequence = rm.GetSelectableVideoRenditions();
+    std::vector<MediaTrack> variants(sequence.begin(), sequence.end());
     ASSERT_EQ(variants.size(), 2u);
     ASSERT_EQ(variants[0].label().value(), "Stream: 1");
     ASSERT_EQ(variants[1].label().value(), "Stream: 2");
@@ -792,13 +793,13 @@
 
   {
     // Differentiated by cross prodoct of resolution and bandwidth
-    auto variants =
-        GetRenditionManager(
-            MakeVariantStr(831270, "1920x1080", "60.00"), "playlist1.m3u8",
-            MakeVariantStr(1144430, "1920x1080", "24.00"), "playlist2.m3u8",
-            MakeVariantStr(1234, "1366x768", "60.00"), "playlist3.m3u8",
-            MakeVariantStr(67989, "1366x768", "24.00"), "playlist4.m3u8")
-            .GetSelectableVideoRenditions();
+    auto rm = GetRenditionManager(
+        MakeVariantStr(831270, "1920x1080", "60.00"), "playlist1.m3u8",
+        MakeVariantStr(1144430, "1920x1080", "24.00"), "playlist2.m3u8",
+        MakeVariantStr(1234, "1366x768", "60.00"), "playlist3.m3u8",
+        MakeVariantStr(67989, "1366x768", "24.00"), "playlist4.m3u8");
+    auto sequence = rm.GetSelectableVideoRenditions();
+    std::vector<MediaTrack> variants(sequence.begin(), sequence.end());
     ASSERT_EQ(variants.size(), 4u);
     ASSERT_EQ(variants[0].label().value(), "1366x768 60fps");
     ASSERT_EQ(variants[1].label().value(), "1366x768 24fps");
diff --git a/media/gpu/chromeos/oop_video_decoder.h b/media/gpu/chromeos/oop_video_decoder.h
index aec0fb9..eb234a24 100644
--- a/media/gpu/chromeos/oop_video_decoder.h
+++ b/media/gpu/chromeos/oop_video_decoder.h
@@ -18,6 +18,7 @@
 #include "media/mojo/mojom/media_log.mojom.h"
 #include "media/mojo/mojom/video_decoder.mojom.h"
 #include "mojo/public/cpp/bindings/associated_receiver.h"
+#include "mojo/public/cpp/bindings/receiver.h"
 #include "mojo/public/cpp/bindings/remote.h"
 
 #if BUILDFLAG(IS_CHROMEOS)
diff --git a/media/gpu/v4l2/v4l2_video_decoder_delegate_av1.cc b/media/gpu/v4l2/v4l2_video_decoder_delegate_av1.cc
index 7ae87a31..08fc20b 100644
--- a/media/gpu/v4l2/v4l2_video_decoder_delegate_av1.cc
+++ b/media/gpu/v4l2/v4l2_video_decoder_delegate_av1.cc
@@ -253,16 +253,22 @@
         base::checked_cast<uint32_t>(ti.tile_row_start[i]);
   }
 
+  // Confirmed that |kMaxTileColumns| is enough size for
+  // |width_in_sbs_minus_1| and |kMaxTileRows| is enough size for
+  // |height_in_sbs_minus_1|
+  // https://b.corp.google.com/issues/187828854#comment19
+  static_assert(
+      std::size(decltype(v4l2_ti.width_in_sbs_minus_1){}) ==
+          libgav1::kMaxTileColumns,
+      "Size of |width_in_sbs_minus_1| array in |v4l2_av1_tile_info| struct "
+      "does not match libgav1 expectation");
+  static_assert(
+      std::size(decltype(v4l2_ti.height_in_sbs_minus_1){}) ==
+          libgav1::kMaxTileRows,
+      "Size of |height_in_sbs_minus_1| array in |v4l2_av1_tile_info| struct "
+      "does not match libgav1 expectation");
+
   if (!ti.uniform_spacing) {
-    // Confirmed that |kMaxTileColumns| is enough size for
-    // |width_in_sbs_minus_1| and |kMaxTileRows| is enough size for
-    // |height_in_sbs_minus_1|
-    // https://b.corp.google.com/issues/187828854#comment19
-    static_assert(
-        std::size(decltype(v4l2_ti.width_in_sbs_minus_1){}) ==
-            libgav1::kMaxTileColumns,
-        "Size of |width_in_sbs_minus_1| array in |v4l2_av1_tile_info| struct "
-        "does not match libgav1 expectation");
     for (size_t i = 0; i < libgav1::kMaxTileColumns; i++) {
       if (ti.tile_column_width_in_superblocks[i] >= 1) {
         v4l2_ti.width_in_sbs_minus_1[i] = base::checked_cast<uint32_t>(
@@ -270,17 +276,55 @@
       }
     }
 
-    static_assert(
-        std::size(decltype(v4l2_ti.height_in_sbs_minus_1){}) ==
-            libgav1::kMaxTileRows,
-        "Size of |height_in_sbs_minus_1| array in |v4l2_av1_tile_info| struct "
-        "does not match libgav1 expectation");
     for (size_t i = 0; i < libgav1::kMaxTileRows; i++) {
       if (ti.tile_row_height_in_superblocks[i] >= 1) {
         v4l2_ti.height_in_sbs_minus_1[i] = base::checked_cast<uint32_t>(
             ti.tile_row_height_in_superblocks[i] - 1);
       }
     }
+  } else {
+    // libgav1 doesn't provide tile_column_width_in_superblocks and
+    // tile_row_height_in_superblocks values when uniform_spacing is set,
+    // so we have to calculate width and height of superblocks via
+    // other column/row info.
+    const uint32_t cols4x4 =
+        base::checked_cast<uint32_t>(ti.tile_column_start[ti.tile_columns]);
+    const uint32_t sb_cols = base::checked_cast<uint32_t>(ti.sb_columns);
+    const uint32_t sb_cols_64 = (cols4x4 + 15) >> 4;
+    const uint32_t sb_cols_128 = (cols4x4 + 31) >> 5;
+    uint32_t sb_shift = 0;
+    if (sb_cols_64 == sb_cols) {
+      sb_shift = 4;
+    } else {
+      DCHECK_EQ(sb_cols_128, sb_cols);
+      sb_shift = 5;
+    }
+
+    const uint32_t sb_size_mi = 1u << sb_shift;
+
+    for (size_t i = 0; i < static_cast<uint32_t>(ti.tile_columns); ++i) {
+      const uint32_t mi_start =
+          base::checked_cast<uint32_t>(ti.tile_column_start[i]);
+      const uint32_t mi_end =
+          base::checked_cast<uint32_t>(ti.tile_column_start[i + 1]);
+      const uint32_t mi_width = mi_end - mi_start;
+
+      const uint32_t sb_w = (mi_width + sb_size_mi - 1) >> sb_shift;
+      DCHECK_GE(sb_w, 1u);
+      v4l2_ti.width_in_sbs_minus_1[i] = sb_w - 1;
+    }
+
+    for (size_t i = 0; i < static_cast<uint32_t>(ti.tile_rows); ++i) {
+      const uint32_t mi_start =
+          base::checked_cast<uint32_t>(ti.tile_row_start[i]);
+      const uint32_t mi_end =
+          base::checked_cast<uint32_t>(ti.tile_row_start[i + 1]);
+      const uint32_t mi_height = mi_end - mi_start;
+
+      const uint32_t sb_h = (mi_height + sb_size_mi - 1) >> sb_shift;
+      DCHECK_GE(sb_h, 1u);
+      v4l2_ti.height_in_sbs_minus_1[i] = sb_h - 1;
+    }
   }
 
   v4l2_ti.tile_size_bytes = ti.tile_size_bytes;
diff --git a/media/gpu/windows/d3d12_video_encode_av1_delegate.cc b/media/gpu/windows/d3d12_video_encode_av1_delegate.cc
index b2cb11a..09fa326 100644
--- a/media/gpu/windows/d3d12_video_encode_av1_delegate.cc
+++ b/media/gpu/windows/d3d12_video_encode_av1_delegate.cc
@@ -1042,7 +1042,6 @@
       used_as_ref
           ? D3D12_VIDEO_ENCODER_PICTURE_CONTROL_FLAG_USED_AS_REFERENCE_PICTURE
           : D3D12_VIDEO_ENCODER_PICTURE_CONTROL_FLAG_NONE;
-  auto reconstructed_buffer = dpb_.GetCurrentFrame();
   D3D12_VIDEO_ENCODE_REFERENCE_FRAMES reference_frames{};
   if (!IsKeyFrame()) {
     reference_frames = dpb_.ToD3D12VideoEncodeReferenceFrames();
@@ -1050,12 +1049,9 @@
   input_arguments_.PictureControlDesc.ReferenceFrames = reference_frames;
   input_arguments_.pInputFrame = input_frame;
   input_arguments_.InputFrameSubresource = input_frame_subresource;
-  D3D12_VIDEO_ENCODER_RECONSTRUCTED_PICTURE reconstructed_picture = {
-      .pReconstructedPicture =
-          used_as_ref ? reconstructed_buffer.resource_ : nullptr,
-      .ReconstructedPictureSubresource =
-          used_as_ref ? reconstructed_buffer.subresource_ : 0,
-  };
+  D3D12_VIDEO_ENCODER_RECONSTRUCTED_PICTURE reconstructed_picture =
+      used_as_ref ? dpb_.GetCurrentFrame()
+                  : D3D12_VIDEO_ENCODER_RECONSTRUCTED_PICTURE{};
 
   if (EncoderStatus result = video_encoder_wrapper_->Encode(
           input_arguments_, reconstructed_picture);
diff --git a/media/gpu/windows/d3d12_video_encode_delegate.cc b/media/gpu/windows/d3d12_video_encode_delegate.cc
index 68d32fb6..39d4101 100644
--- a/media/gpu/windows/d3d12_video_encode_delegate.cc
+++ b/media/gpu/windows/d3d12_video_encode_delegate.cc
@@ -616,7 +616,7 @@
 }
 
 template <size_t maxDpbSize>
-D3D12PictureBuffer
+D3D12_VIDEO_ENCODER_RECONSTRUCTED_PICTURE
 D3D12VideoEncodeDecodedPictureBuffers<maxDpbSize>::GetCurrentFrame() const {
   // Make sure we have initialized.
   CHECK_GT(resources_.size(), 0u);
diff --git a/media/gpu/windows/d3d12_video_encode_delegate.h b/media/gpu/windows/d3d12_video_encode_delegate.h
index fae38d1..06ebd4a 100644
--- a/media/gpu/windows/d3d12_video_encode_delegate.h
+++ b/media/gpu/windows/d3d12_video_encode_delegate.h
@@ -194,13 +194,6 @@
   Microsoft::WRL::ComPtr<ID3D12Resource> processed_input_frame_;
 };
 
-// Records an ID3D12Resource pointer and a subresource index for a specific
-// subresource.
-struct D3D12PictureBuffer {
-  raw_ptr<ID3D12Resource> resource_ = nullptr;
-  UINT subresource_ = 0;
-};
-
 // A class to manage the decoded picture buffers for D3D12 video encode and
 // returns the |D3D12_VIDEO_ENCODE_REFERENCE_FRAMES| that D3D12 video encode API
 // expects. When initialized, it creates the textures that may be needed during
@@ -227,7 +220,7 @@
                                   bool use_texture_array = false);
 
   // Get the unused buffer for current frame.
-  D3D12PictureBuffer GetCurrentFrame() const;
+  D3D12_VIDEO_ENCODER_RECONSTRUCTED_PICTURE GetCurrentFrame() const;
   // Insert the last picture buffer returned by |GetCurrentFrame()| into the
   // given index and move the old buffers with index no less than |position| to
   // the next index.
diff --git a/media/gpu/windows/d3d12_video_encode_h264_delegate.cc b/media/gpu/windows/d3d12_video_encode_h264_delegate.cc
index a3834f6..d741e0f 100644
--- a/media/gpu/windows/d3d12_video_encode_h264_delegate.cc
+++ b/media/gpu/windows/d3d12_video_encode_h264_delegate.cc
@@ -568,11 +568,7 @@
   if (update_buffer) {
     input_arguments_.PictureControlDesc.Flags =
         D3D12_VIDEO_ENCODER_PICTURE_CONTROL_FLAG_USED_AS_REFERENCE_PICTURE;
-    D3D12PictureBuffer reconstructed_picture =
-        reference_frame_manager_.GetCurrentFrame();
-    output_arguments.pReconstructedPicture = reconstructed_picture.resource_;
-    output_arguments.ReconstructedPictureSubresource =
-        reconstructed_picture.subresource_;
+    output_arguments = reference_frame_manager_.GetCurrentFrame();
   } else {
     input_arguments_.PictureControlDesc.Flags =
         D3D12_VIDEO_ENCODER_PICTURE_CONTROL_FLAG_NONE;
diff --git a/media/gpu/windows/d3d12_video_encode_h265_delegate.cc b/media/gpu/windows/d3d12_video_encode_h265_delegate.cc
index f2d3dde..890ae8ce 100644
--- a/media/gpu/windows/d3d12_video_encode_h265_delegate.cc
+++ b/media/gpu/windows/d3d12_video_encode_h265_delegate.cc
@@ -443,11 +443,7 @@
   if (update_buffer) {
     input_arguments_.PictureControlDesc.Flags =
         D3D12_VIDEO_ENCODER_PICTURE_CONTROL_FLAG_USED_AS_REFERENCE_PICTURE;
-    D3D12PictureBuffer reconstructed_picture =
-        reference_frame_manager_.GetCurrentFrame();
-    output_arguments.pReconstructedPicture = reconstructed_picture.resource_;
-    output_arguments.ReconstructedPictureSubresource =
-        reconstructed_picture.subresource_;
+    output_arguments = reference_frame_manager_.GetCurrentFrame();
   } else {
     input_arguments_.PictureControlDesc.Flags =
         D3D12_VIDEO_ENCODER_PICTURE_CONTROL_FLAG_NONE;
diff --git a/media/mojo/clients/mojo_gpu_video_accelerator_factories_unittest.cc b/media/mojo/clients/mojo_gpu_video_accelerator_factories_unittest.cc
index 379ccbb..2123594 100644
--- a/media/mojo/clients/mojo_gpu_video_accelerator_factories_unittest.cc
+++ b/media/mojo/clients/mojo_gpu_video_accelerator_factories_unittest.cc
@@ -440,7 +440,7 @@
         kGpuStreamPriorityDefault,
         gpu::mojom::ContextCreationAttribs::NewGles(
             gpu::mojom::GLESCreationAttribs::New()),
-        GURL());
+        /*enable_gpu_rasterization=*/false, GURL());
     ON_CALL(*mock_context_provider_, GetCommandBufferProxy())
         .WillByDefault(Return(gpu_command_buffer_proxy_.get()));
   }
diff --git a/media/mojo/mojom/BUILD.gn b/media/mojo/mojom/BUILD.gn
index ea57c3b..e3952e87 100644
--- a/media/mojo/mojom/BUILD.gn
+++ b/media/mojo/mojom/BUILD.gn
@@ -96,7 +96,7 @@
     "//services/network/public/mojom:cookies_mojom",
     "//services/network/public/mojom:mojom_storage_access_api",
     "//services/service_manager/public/mojom",
-    "//services/viz/public/mojom:shared_image_format",
+    "//services/viz/public/mojom/compositing:shared_image_format",
     "//ui/gfx/geometry/mojom",
     "//ui/gfx/mojom",
     "//ui/gfx/mojom:hdr_metadata",
diff --git a/media/mojo/mojom/media_types.mojom b/media/mojo/mojom/media_types.mojom
index f4d4472..282db1a6 100644
--- a/media/mojo/mojom/media_types.mojom
+++ b/media/mojo/mojom/media_types.mojom
@@ -483,7 +483,6 @@
 union VideoFrameData {
   EosVideoFrameData eos_data;
   SharedMemoryVideoFrameData shared_memory_data;
-  GpuMemoryBufferSharedImageVideoFrameData gpu_memory_buffer_shared_image_data;
   SharedImageVideoFrameData shared_image_data;
   OpaqueVideoFrameData opaque_data;
   [EnableIf=is_chromeos|is_linux]
@@ -505,12 +504,6 @@
   array<uint32> offsets;
 };
 
-struct GpuMemoryBufferSharedImageVideoFrameData {
-  gfx.mojom.GpuMemoryBufferHandle gpu_memory_buffer_handle;
-  gpu.mojom.ExportedSharedImage? shared_image;
-  gpu.mojom.SyncToken sync_token;
-};
-
 // This defines video frame data stored in texture shared images.
 struct SharedImageVideoFrameData {
   gpu.mojom.ExportedSharedImage shared_image;
diff --git a/media/mojo/mojom/video_frame_mojom_traits.cc b/media/mojo/mojom/video_frame_mojom_traits.cc
index 039a670b..84730cf 100644
--- a/media/mojo/mojom/video_frame_mojom_traits.cc
+++ b/media/mojo/mojom/video_frame_mojom_traits.cc
@@ -122,25 +122,6 @@
     // STORAGE_GPU_MEMORY_BUFFER may carry meaningful or dummy shared_image.
     std::optional<gpu::ExportedSharedImage> shared_image;
     gpu::SyncToken sync_token;
-#if BUILDFLAG(IS_CHROMEOS)
-    if (input->HasSharedImage()) {
-      shared_image = input->shared_image()->Export(
-          /*with_buffer_handle=*/is_mappable_si_enabled);
-      sync_token = input->acquire_sync_token();
-
-      if (is_mappable_si_enabled) {
-        return media::mojom::VideoFrameData::NewSharedImageData(
-            media::mojom::SharedImageVideoFrameData::New(
-                std::move(shared_image.value()), std::move(sync_token),
-                /*is_mappable_si_enabled=*/true));
-      }
-    }
-
-    return media::mojom::VideoFrameData::NewGpuMemoryBufferSharedImageData(
-        media::mojom::GpuMemoryBufferSharedImageVideoFrameData::New(
-            std::move(gpu_memory_buffer_handle), std::move(shared_image),
-            std::move(sync_token)));
-#else
     CHECK(input->HasSharedImage());
     CHECK(is_mappable_si_enabled);
     shared_image = input->shared_image()->Export(
@@ -157,7 +138,6 @@
             std::move(shared_image.value()), std::move(sync_token),
             /*is_mappable_si_enabled=*/true));
 #endif
-#endif
   }
 
   if (input->HasSharedImage()) {
@@ -359,46 +339,6 @@
     if (frame) {
       frame->BackWithOwnedSharedMemory(std::move(region), std::move(mapping));
     }
-  } else if (data.is_gpu_memory_buffer_shared_image_data()) {
-#if BUILDFLAG(IS_CHROMEOS)
-    media::mojom::GpuMemoryBufferSharedImageVideoFrameDataDataView
-        gpu_memory_buffer_data;
-    data.GetGpuMemoryBufferSharedImageDataDataView(&gpu_memory_buffer_data);
-
-    gfx::GpuMemoryBufferHandle gpu_memory_buffer_handle;
-    if (!gpu_memory_buffer_data.ReadGpuMemoryBufferHandle(
-            &gpu_memory_buffer_handle)) {
-      DLOG(ERROR) << "Failed to read GpuMemoryBufferHandle";
-      return false;
-    }
-
-    std::optional<viz::SharedImageFormat> si_format =
-        VideoPixelFormatToSharedImageFormat(format);
-    if (!si_format || !viz::HasEquivalentBufferFormat(*si_format)) {
-      return false;
-    }
-
-    if (gpu_memory_buffer_handle.type !=
-        gfx::GpuMemoryBufferType::NATIVE_PIXMAP) {
-      return false;
-    }
-
-    gfx::BufferUsage buffer_usage;
-    if (metadata.protected_video) {
-      buffer_usage = gfx::BufferUsage::PROTECTED_SCANOUT_VDA_WRITE;
-    } else {
-      buffer_usage = gfx::BufferUsage::VEA_READ_CAMERA_AND_CPU_READ_WRITE;
-    }
-
-    auto client_native_pixmap_factory =
-        ui::CreateClientNativePixmapFactoryOzone();
-    frame = media::VideoFrame::WrapExternalGpuMemoryBufferHandle(
-        visible_rect, natural_size, client_native_pixmap_factory.get(),
-        std::move(gpu_memory_buffer_handle), coded_size, *si_format,
-        buffer_usage, timestamp);
-#else
-    return false;
-#endif  // BUILDFLAG(IS_CHROMEOS)
   } else if (data.is_shared_image_data()) {
     media::mojom::SharedImageVideoFrameDataDataView shared_image_data;
     data.GetSharedImageDataDataView(&shared_image_data);
diff --git a/mojo/public/cpp/bindings/associated_receiver.h b/mojo/public/cpp/bindings/associated_receiver.h
index 76065029..9198822 100644
--- a/mojo/public/cpp/bindings/associated_receiver.h
+++ b/mojo/public/cpp/bindings/associated_receiver.h
@@ -18,11 +18,13 @@
 #include "base/memory/scoped_refptr.h"
 #include "base/memory/weak_ptr.h"
 #include "base/task/sequenced_task_runner.h"
+#include "mojo/public/cpp/bindings/connection_error_callback.h"
+#include "mojo/public/cpp/bindings/interface_endpoint_client.h"
 #include "mojo/public/cpp/bindings/lib/sync_method_traits.h"
+#include "mojo/public/cpp/bindings/message_metadata_helpers.h"
 #include "mojo/public/cpp/bindings/pending_associated_receiver.h"
 #include "mojo/public/cpp/bindings/pending_associated_remote.h"
 #include "mojo/public/cpp/bindings/raw_ptr_impl_ref_traits.h"
-#include "mojo/public/cpp/bindings/receiver.h"
 #include "mojo/public/cpp/bindings/runtime_features.h"
 
 namespace mojo {
diff --git a/mojo/public/cpp/bindings/tests/serialization_death_unittest.cc b/mojo/public/cpp/bindings/tests/serialization_death_unittest.cc
index cc9573b..79a7d24 100644
--- a/mojo/public/cpp/bindings/tests/serialization_death_unittest.cc
+++ b/mojo/public/cpp/bindings/tests/serialization_death_unittest.cc
@@ -16,6 +16,7 @@
 #include "mojo/public/cpp/bindings/lib/send_validation_serialization.h"
 #include "mojo/public/cpp/bindings/lib/send_validation_type.h"
 #include "mojo/public/cpp/bindings/lib/validation_errors.h"
+#include "mojo/public/cpp/bindings/receiver.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "mojo/public/cpp/bindings/tests/serialization_death.test-mojom.h"
 #include "mojo/public/cpp/system/message_pipe.h"
diff --git a/mojo/public/rust/BUILD.gn b/mojo/public/rust/BUILD.gn
index f7966d2..e51ebc9 100644
--- a/mojo/public/rust/BUILD.gn
+++ b/mojo/public/rust/BUILD.gn
@@ -30,8 +30,10 @@
 rust_static_library("mojo_rust_system_api") {
   crate_root = "system/lib.rs"
   sources = [
+    "system/data_pipe.rs",
     "system/lib.rs",
     "system/mojo_types.rs",
+    "system/trap.rs",
   ]
   allow_unsafe = true
   deps = [
diff --git a/mojo/public/rust/system/data_pipe.rs b/mojo/public/rust/system/data_pipe.rs
new file mode 100644
index 0000000..2bcba01
--- /dev/null
+++ b/mojo/public/rust/system/data_pipe.rs
@@ -0,0 +1,176 @@
+//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.
+
+use crate::mojo_types::{Handle, MojoResult, UntypedHandle};
+use std::ffi::c_void;
+use std::ptr;
+
+chromium::import! {
+  pub "//mojo/public/rust:mojo_ffi";
+}
+
+bitflags::bitflags! {
+    #[derive(Clone, Copy, Default)]
+    pub struct ReadFlags: u32 {
+        /// Read all the data from the pipe if possible, or none at all.
+        ///
+        /// The flags DISCARD, QUERY, and PEEK are mutually exclusive.
+        /// Attempting to use more than one of these in a call will result
+        /// in MojoResult::InvalidArgument.
+        ///
+        /// FOR_RELEASE: Would the API be nicer if we simply had `read`,
+        /// `discard`, `query`, and `peek` as distinction functions, and didn't
+        /// expose flags to the user at all? Or should that be a higher-level
+        /// API?
+        ///
+        /// FOR_RELEASE(https://crbug.com/458796903): It'd be nicer to access
+        /// MOJO_READ_DATA_FLAG_ALL_OR_NONE here, but the bindings we
+        /// have right now don't export that. We should change that.
+        const ALL_OR_NONE = 1 << 0;
+
+        /// Dequeue the message received rather than reading it.
+        const DISCARD = 1 << 1;
+
+        /// Get information about the queue on the pipe but do not perform the
+        /// read.
+        const QUERY = 1 << 2;
+
+        /// Read data off the pipe's queue but do not dequeue it.
+        const PEEK = 1 << 3;
+    }
+}
+
+bitflags::bitflags! {
+    #[derive(Clone, Copy, Default)]
+    pub struct WriteFlags: u32 {
+        /// Write all the data to the pipe if possible or none at all.
+        /// FOR_RELEASE(https://crbug.com/458796903): It'd be nicer to access
+        /// MOJO_READ_DATA_FLAG_ALL_OR_NONE here, but the bindings we
+        /// have right now don't export that. We should change that.
+        const ALL_OR_NONE = 1 << 0;
+    }
+}
+
+pub struct DataPipeConsumerHandle {
+    handle: UntypedHandle,
+}
+
+impl DataPipeConsumerHandle {
+    /// Perform a read operation. Returns the number of bytes read.
+    ///
+    /// FOR_RELEASE: Implement `read` and `read_exact` from the Read trait.
+    pub fn read_with_flags(&self, buf: &mut [u8], flags: ReadFlags) -> Result<usize, MojoResult> {
+        // Query the queue, but don't actually perform the read.
+        let mut options = mojo_ffi::MojoReadDataOptions::new(ReadFlags::QUERY.bits());
+        let mut num_bytes: u32 = 0;
+        // SAFETY: This call is safe because num_bytes is not null.
+        let r_prelim = unsafe {
+            mojo_ffi::MojoReadData(
+                self.handle.get_native_handle(),
+                options.as_ptr(),
+                ptr::null_mut() as *mut c_void,
+                &mut num_bytes as *mut u32,
+            )
+        };
+        if num_bytes == 0 {
+            return Ok(0);
+        }
+        if r_prelim != 0 {
+            return Err(MojoResult::from_code(r_prelim));
+        }
+
+        // Assuming no error, read the actual data.
+        options.flags = flags.bits();
+        // This call reads until the buffer is full OR there
+        // is no more data in the pipe, whichever comes first.
+        // SAFETY: This call is safe because num_bytes is not null.
+        let r = MojoResult::from_code(unsafe {
+            mojo_ffi::MojoReadData(
+                self.handle.get_native_handle(),
+                options.as_ptr(),
+                buf.as_mut_ptr() as *mut c_void,
+                &mut num_bytes as *mut u32,
+            )
+        });
+        if r != MojoResult::Okay {
+            Err(r)
+        } else {
+            Ok(num_bytes as usize)
+        }
+    }
+}
+
+pub struct DataPipeProducerHandle {
+    handle: UntypedHandle,
+}
+
+impl DataPipeProducerHandle {
+    /// Perform a write operation on the producer end of the data pipe. Returns
+    /// the number of elements actually written.
+    ///
+    /// # Implementation notes
+    ///
+    /// The underlying C API is thread agnostic, leaving the choice of how to
+    /// synchronize writes to the caller. We take `&mut self`
+    /// here to ensure thread safety.
+    ///
+    /// FOR_RELEASE: Implement `write` and `write_all` from the `Write` trait.
+    pub fn write_with_flags(
+        &mut self,
+        data: &[u8],
+        flags: WriteFlags,
+    ) -> Result<usize, MojoResult> {
+        let Ok(mut num_bytes) = u32::try_from(data.len()) else {
+            return Err(MojoResult::ResourceExhausted);
+        };
+        let options = mojo_ffi::MojoWriteDataOptions::new(flags.bits());
+        // SAFETY: This is safe because we have exclusive access to `handle` while
+        // writing, data.as_ptr() points to num_bytes of valid memory, and num_bytes is
+        // not null.
+        match MojoResult::from_code(unsafe {
+            mojo_ffi::MojoWriteData(
+                self.handle.get_native_handle(),
+                data.as_ptr() as *const c_void,
+                &mut num_bytes as *mut u32,
+                options.as_ptr(),
+            )
+        }) {
+            MojoResult::Okay => Ok(num_bytes as usize),
+            e => Err(e),
+        }
+    }
+}
+
+/// Creates a data pipe and returns the DataPipeConsumerHandle and
+/// DataPipeProducerHandle for that pipe. `capacity` is the minimum number of
+/// bytes that can be queued. A user can write at least this many bytes to
+/// the pipe before they are read. If `capacity` is zero it is chosen by the
+/// system but is at least one.
+///
+/// The pipe solely produces and consumes raw bytes. Interpreting those bytes
+/// into higher-level types is a job for the caller.
+pub fn create(
+    capacity: u32,
+) -> Result<(DataPipeConsumerHandle, DataPipeProducerHandle), MojoResult> {
+    let mut consumer_handle = UntypedHandle::invalid();
+    let mut producer_handle = UntypedHandle::invalid();
+    let options = mojo_ffi::MojoCreateDataPipeOptions::new(0, 1, capacity);
+
+    // SAFETY: This is safe because we are creating a pipe with two newly-created
+    // handles that have not already been assigned a value, so they are valid
+    // arguments for the function.
+    match MojoResult::from_code(unsafe {
+        mojo_ffi::MojoCreateDataPipe(
+            options.as_ptr(),
+            producer_handle.as_mut_ptr(),
+            consumer_handle.as_mut_ptr(),
+        )
+    }) {
+        MojoResult::Okay => Ok((
+            DataPipeConsumerHandle { handle: consumer_handle },
+            DataPipeProducerHandle { handle: producer_handle },
+        )),
+        e => Err(e),
+    }
+}
diff --git a/mojo/public/rust/system/ffi.rs b/mojo/public/rust/system/ffi.rs
index 68c8003..615ac31 100644
--- a/mojo/public/rust/system/ffi.rs
+++ b/mojo/public/rust/system/ffi.rs
@@ -45,6 +45,7 @@
 
 pub use raw_ffi::MojoAppendMessageData;
 pub use raw_ffi::MojoClose;
+pub use raw_ffi::MojoCreateDataPipe;
 pub use raw_ffi::MojoCreateMessage;
 pub use raw_ffi::MojoCreateMessagePipe;
 pub use raw_ffi::MojoDestroyMessage;
@@ -52,7 +53,14 @@
 pub use raw_ffi::MojoGetTimeTicksNow;
 pub use raw_ffi::MojoHandleSignalsState as SignalsState;
 pub use raw_ffi::MojoQueryHandleSignalsState;
+// SAFETY: The `num_bytes` argument to this function must not be null.
+pub use raw_ffi::MojoReadData;
 pub use raw_ffi::MojoReadMessage;
+// SAFETY: The `num_bytes` argument to this function must not be null.
+// Additionally the `data` argument must have at least `num_bytes` of
+// valid memory. Additionally, for thread safety, one must have exclusive
+// access to `data_pipe_producer_handle`.
+pub use raw_ffi::MojoWriteData;
 pub use raw_ffi::MojoWriteMessage;
 pub use types::MojoResultCode;
 
@@ -113,3 +121,14 @@
 declare_mojo_options!(MojoAppendMessageDataOptions, flags: types::MojoAppendMessageDataFlags);
 declare_mojo_options!(MojoCreateMessagePipeOptions, flags: types::MojoCreateMessagePipeFlags);
 declare_mojo_options!(MojoWriteMessageOptions, flags: types::MojoWriteMessageFlags);
+
+declare_mojo_options!(MojoReadDataOptions, flags: types::MojoReadDataFlags);
+
+declare_mojo_options!(MojoWriteDataOptions, flags: types::MojoWriteDataFlags);
+
+declare_mojo_options!(
+    MojoCreateDataPipeOptions,
+    flags: types::MojoCreateDataPipeFlags,
+    element_num_bytes: u32,
+    capacity_num_bytes: u32
+);
diff --git a/mojo/public/rust/system/lib.rs b/mojo/public/rust/system/lib.rs
index cfa2374..6565e72 100644
--- a/mojo/public/rust/system/lib.rs
+++ b/mojo/public/rust/system/lib.rs
@@ -2,4 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+pub mod data_pipe;
 pub mod mojo_types;
+pub mod trap;
diff --git a/mojo/public/rust/system/mojo_types.rs b/mojo/public/rust/system/mojo_types.rs
index afea46d..b7ccd93f 100644
--- a/mojo/public/rust/system/mojo_types.rs
+++ b/mojo/public/rust/system/mojo_types.rs
@@ -12,6 +12,11 @@
 // sure they describe distinct things: "how to use this function safely" vs
 // "why this instance is OK.")
 
+// FOR_RELEASE: Define `enum MojoErrorCode` (**without** `Okay` variant) and
+// use `Result<T, MojoErrorCode>` in all public APIs.  Maybe also provide some
+// internal convenience helpers and a public
+// `pub type MojoResult<T> = std::result::Result<T, MojoErrorCode>`.
+
 use std::ffi::c_void;
 use std::fmt;
 use std::ptr;
@@ -117,6 +122,78 @@
     }
 }
 
+bitflags::bitflags! {
+    #[derive(Clone, Copy, Default)]
+    #[repr(transparent)]
+    pub struct HandleSignals: types::MojoHandleSignals {
+        /// FOR_RELEASE(https://crbug.com/458796903): It'd be nicer to access
+        /// MOJO_HANDLE_SIGNAL_READABLE here, but the bindings we
+        /// have right now don't export that. We should change that.
+        const READABLE = 1 << 0;
+        const WRITABLE = 1 << 1;
+        const PEER_CLOSED = 1 << 2;
+        const NEW_DATA_READABLE = 1 << 3;
+        const PEER_REMOTE = 1 << 4;
+        const QUOTA_EXCEEDED = 1 << 5;
+    }
+}
+
+/// Represents the signals state of a handle: which signals are satisfied,
+/// and which are satisfiable.
+#[repr(transparent)]
+#[derive(Clone, Copy, Debug)]
+pub struct SignalsState(pub types::MojoHandleSignalsState);
+
+impl SignalsState {
+    /// Generates a new SignalsState
+    pub fn new(satisfied: HandleSignals, satisfiable: HandleSignals) -> SignalsState {
+        SignalsState(types::MojoHandleSignalsState {
+            satisfied_signals: satisfied.bits(),
+            satisfiable_signals: satisfiable.bits(),
+        })
+    }
+
+    /// Returns the bitfield of the satisfied signals
+    pub fn satisfied(&self) -> HandleSignals {
+        HandleSignals::from_bits_truncate(self.0.satisfied_signals)
+    }
+
+    /// Returns the bitfield of the satisfiable signals
+    pub fn satisfiable(&self) -> HandleSignals {
+        HandleSignals::from_bits_truncate(self.0.satisfiable_signals)
+    }
+
+    /// Return the wrapped Mojo FFI struct.
+    pub fn to_raw(self) -> types::MojoHandleSignalsState {
+        self.0
+    }
+
+    /// Get a pointer to the inner struct for FFI calls.
+    pub fn as_mut_ptr(&mut self) -> *mut types::MojoHandleSignalsState {
+        &mut self.0 as *mut _
+    }
+}
+
+impl std::default::Default for SignalsState {
+    fn default() -> Self {
+        SignalsState(types::MojoHandleSignalsState { satisfied_signals: 0, satisfiable_signals: 0 })
+    }
+}
+
+/// The result of `wait`ing on a handle. There are three possible outcomes:
+///     * A requested signal was satisfied
+///     * A requested signal became unsatisfiable due to a change on the handle
+///     * The handle was closed
+#[derive(Clone, Copy, Debug)]
+pub enum WaitResult {
+    /// The handle had a signal satisfied.
+    Satisfied(SignalsState),
+    /// A requested signal became unsatisfiable for the handle.
+    Unsatisfiable(SignalsState),
+    /// The handle was closed.
+    Closed,
+}
+
 /// THE MOJO HANDLE EXTENDED UNIVERSE
 ///
 /// `MojoHandle` always refers to the C type. It is an opaque handle for some
diff --git a/mojo/public/rust/system/trap.rs b/mojo/public/rust/system/trap.rs
new file mode 100644
index 0000000..6bfd0a4
--- /dev/null
+++ b/mojo/public/rust/system/trap.rs
@@ -0,0 +1,6 @@
+//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.
+
+// FOR RELEASE: Implementation for traps will go here (necessary for
+// coordinating synchronous writes/reads).
diff --git a/mojo/public/rust/test_system/core_api_unittests.rs b/mojo/public/rust/test_system/core_api_unittests.rs
index 96cab8e..c7556914 100644
--- a/mojo/public/rust/test_system/core_api_unittests.rs
+++ b/mojo/public/rust/test_system/core_api_unittests.rs
@@ -59,3 +59,24 @@
     // FOR_RELEASE: Implement all the above.
     assert_eq!(0, 0);
 }
+
+#[gtest(RustSystemAPITestSuite, DataPipeWriteAndSendTest)]
+fn test_data_pipe_write_and_send() {
+    test_util::init_mojo_if_needed();
+
+    let (consumer, mut producer) = system::data_pipe::create(5).unwrap();
+
+    let hello = b"hello";
+    let bytes_written =
+        producer.write_with_flags(hello, system::data_pipe::WriteFlags::empty()).unwrap();
+    expect_eq!(bytes_written, hello.len());
+
+    let mut read_buffer = [0u8; 5];
+    let bytes_read =
+        consumer.read_with_flags(&mut read_buffer, system::data_pipe::ReadFlags::empty()).unwrap();
+    expect_eq!(&read_buffer[..bytes_read], hello);
+
+    // TODO: implement and test two-phase read-write.
+
+    assert_eq!(0, 0);
+}
diff --git a/net/base/features.cc b/net/base/features.cc
index 68ad6d8c..4c0224f 100644
--- a/net/base/features.cc
+++ b/net/base/features.cc
@@ -524,6 +524,11 @@
                    &kDeviceBoundSessions,
                    "SchemaVersion",
                    2);
+BASE_FEATURE_PARAM(bool,
+                   kDeviceBoundSessionsIncludeAudFieldInJwt,
+                   &kDeviceBoundSessions,
+                   "IncludeAudFieldInJwt",
+                   false);
 
 BASE_FEATURE(kDeviceBoundSessionsFederatedRegistration,
              base::FEATURE_DISABLED_BY_DEFAULT);
diff --git a/net/base/features.h b/net/base/features.h
index e4f9be5..6cbb05b 100644
--- a/net/base/features.h
+++ b/net/base/features.h
@@ -632,6 +632,9 @@
     kDeviceBoundSessionsCheckSubdomainRegistration);
 // This feature controls the database schema version for stored sessions.
 NET_EXPORT BASE_DECLARE_FEATURE_PARAM(int, kDeviceBoundSessionsSchemaVersion);
+// This feature controls whether the "aud" field is included in the JWT.
+NET_EXPORT BASE_DECLARE_FEATURE_PARAM(bool,
+                                      kDeviceBoundSessionsIncludeAudFieldInJwt);
 
 // This feature controls whether DBSC allows federated sessions.
 NET_EXPORT BASE_DECLARE_FEATURE(kDeviceBoundSessionsFederatedRegistration);
diff --git a/net/device_bound_sessions/registration_fetcher.cc b/net/device_bound_sessions/registration_fetcher.cc
index 0567a53..b6cd268 100644
--- a/net/device_bound_sessions/registration_fetcher.cc
+++ b/net/device_bound_sessions/registration_fetcher.cc
@@ -93,12 +93,12 @@
 
   std::optional<std::string> header_and_payload;
   if (is_for_refresh) {
-    header_and_payload =
-        CreateKeyRefreshHeaderAndPayload(challenge, expected_algorithm.value());
+    header_and_payload = CreateKeyRefreshHeaderAndPayload(
+        challenge, expected_algorithm.value(), registration_url);
   } else {
     header_and_payload = CreateKeyRegistrationHeaderAndPayload(
         challenge, expected_algorithm.value(), expected_public_key.value(),
-        std::move(authorization));
+        std::move(authorization), registration_url);
   }
 
   if (!header_and_payload.has_value()) {
diff --git a/net/device_bound_sessions/session_binding_utils.cc b/net/device_bound_sessions/session_binding_utils.cc
index 8441c89e..869a645 100644
--- a/net/device_bound_sessions/session_binding_utils.cc
+++ b/net/device_bound_sessions/session_binding_utils.cc
@@ -19,6 +19,7 @@
 #include "crypto/keypair.h"
 #include "crypto/sha2.h"
 #include "crypto/signature_verifier.h"
+#include "net/base/features.h"
 #include "net/base/url_util.h"
 #include "net/device_bound_sessions/jwk_utils.h"
 #include "third_party/boringssl/src/include/openssl/bn.h"
@@ -80,7 +81,8 @@
     std::string_view challenge,
     crypto::SignatureVerifier::SignatureAlgorithm algorithm,
     std::optional<base::Value::Dict> jwk,
-    const std::optional<std::string>& authorization) {
+    const std::optional<std::string>& authorization,
+    const GURL& registration_url) {
   auto header = base::Value::Dict()
                     .Set("alg", SignatureAlgorithmToString(algorithm))
                     .Set("typ", "dbsc+jwt");
@@ -93,6 +95,10 @@
     payload.Set("authorization", authorization.value());
   }
 
+  if (features::kDeviceBoundSessionsIncludeAudFieldInJwt.Get()) {
+    payload.Set("aud", registration_url.spec());
+  }
+
   return CombineHeaderAndPayload(header, payload);
 }
 
@@ -102,7 +108,8 @@
     std::string_view challenge,
     crypto::SignatureVerifier::SignatureAlgorithm algorithm,
     base::span<const uint8_t> pubkey_spki,
-    std::optional<std::string> authorization) {
+    std::optional<std::string> authorization,
+    const GURL& registration_url) {
   base::Value::Dict jwk = ConvertPkeySpkiToJwk(algorithm, pubkey_spki);
   if (jwk.empty()) {
     DVLOG(1) << "Unexpected error when converting the SPKI to a JWK";
@@ -110,14 +117,16 @@
   }
 
   return CreateHeaderAndPayload(challenge, algorithm, std::move(jwk),
-                                std::move(authorization));
+                                std::move(authorization), registration_url);
 }
 
 std::optional<std::string> CreateKeyRefreshHeaderAndPayload(
     std::string_view challenge,
-    crypto::SignatureVerifier::SignatureAlgorithm algorithm) {
+    crypto::SignatureVerifier::SignatureAlgorithm algorithm,
+    const GURL& registration_url) {
   return CreateHeaderAndPayload(challenge, algorithm, /*jwk=*/std::nullopt,
-                                /*authorization=*/std::nullopt);
+                                /*authorization=*/std::nullopt,
+                                registration_url);
 }
 
 std::optional<std::string> AppendSignatureToHeaderAndPayload(
diff --git a/net/device_bound_sessions/session_binding_utils.h b/net/device_bound_sessions/session_binding_utils.h
index 14f1e95..981dadc 100644
--- a/net/device_bound_sessions/session_binding_utils.h
+++ b/net/device_bound_sessions/session_binding_utils.h
@@ -26,12 +26,14 @@
     std::string_view challenge,
     crypto::SignatureVerifier::SignatureAlgorithm algorithm,
     base::span<const uint8_t> pubkey_spki,
-    std::optional<std::string> authorization);
+    std::optional<std::string> authorization,
+    const GURL& registration_url);
 
 // Creates header and payload parts of a refresh JWT.
 std::optional<std::string> NET_EXPORT CreateKeyRefreshHeaderAndPayload(
     std::string_view challenge,
-    crypto::SignatureVerifier::SignatureAlgorithm algorithm);
+    crypto::SignatureVerifier::SignatureAlgorithm algorithm,
+    const GURL& registration_url);
 
 // Appends `signature` generated by `algorithm` to provided `header_and_payload`
 // to form a complete JWT.
diff --git a/net/device_bound_sessions/session_binding_utils_unittest.cc b/net/device_bound_sessions/session_binding_utils_unittest.cc
index e6f32cd..375f8bf 100644
--- a/net/device_bound_sessions/session_binding_utils_unittest.cc
+++ b/net/device_bound_sessions/session_binding_utils_unittest.cc
@@ -13,10 +13,12 @@
 #include "base/strings/strcat.h"
 #include "base/strings/string_split.h"
 #include "base/strings/stringprintf.h"
+#include "base/test/scoped_feature_list.h"
 #include "base/time/time.h"
 #include "base/value_iterators.h"
 #include "base/values.h"
 #include "crypto/signature_verifier.h"
+#include "net/base/features.h"
 #include "net/device_bound_sessions/test_support.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/gurl.h"
@@ -60,7 +62,8 @@
   std::optional<std::string> result = CreateKeyRegistrationHeaderAndPayload(
       "test_challenge",
       crypto::SignatureVerifier::SignatureAlgorithm::RSA_PKCS1_SHA256, spki,
-      /*authorization=*/"auth");
+      /*authorization=*/"auth",
+      GURL("https://accounts.example.test/RegisterKey"));
   ASSERT_TRUE(result.has_value());
 
   std::vector<std::string_view> header_and_payload = base::SplitStringPiece(
@@ -86,6 +89,45 @@
   EXPECT_EQ(actual_payload, expected_payload);
 }
 
+TEST(SessionBindingUtilsTest, CreateKeyRegistrationHeaderAndPayloadWithAud) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeatureWithParameters(
+      features::kDeviceBoundSessions, {{"IncludeAudFieldInJwt", "true"}});
+
+  auto [spki, jwk] = GetRS256SpkiAndJwkForTesting();
+
+  std::optional<std::string> result = CreateKeyRegistrationHeaderAndPayload(
+      "test_challenge",
+      crypto::SignatureVerifier::SignatureAlgorithm::RSA_PKCS1_SHA256, spki,
+      /*authorization=*/"auth",
+      GURL("https://accounts.example.test/RegisterKey"));
+  ASSERT_TRUE(result.has_value());
+
+  std::vector<std::string_view> header_and_payload = base::SplitStringPiece(
+      *result, ".", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
+  ASSERT_EQ(header_and_payload.size(), 2U);
+  base::Value actual_header =
+      Base64UrlEncodedJsonToValue(header_and_payload[0]);
+  base::Value actual_payload =
+      Base64UrlEncodedJsonToValue(header_and_payload[1]);
+
+  base::Value::Dict expected_header =
+      base::Value::Dict()
+          .Set("alg", "RS256")
+          .Set("typ", "dbsc+jwt")
+          .Set("jwk",
+               base::JSONReader::Read(jwk, base::JSON_PARSE_CHROMIUM_EXTENSIONS)
+                   .value());
+  base::Value::Dict expected_payload =
+      base::Value::Dict()
+          .Set("jti", "test_challenge")
+          .Set("authorization", "auth")
+          .Set("aud", "https://accounts.example.test/RegisterKey");
+
+  EXPECT_EQ(actual_header, expected_header);
+  EXPECT_EQ(actual_payload, expected_payload);
+}
+
 TEST(SessionBindingUtilsTest,
      CreateKeyRegistrationHeaderAndPayloadWithNullAuth) {
   auto [spki, jwk] = GetRS256SpkiAndJwkForTesting();
@@ -93,7 +135,8 @@
   std::optional<std::string> result = CreateKeyRegistrationHeaderAndPayload(
       "test_challenge",
       crypto::SignatureVerifier::SignatureAlgorithm::RSA_PKCS1_SHA256, spki,
-      /*authorization=*/std::nullopt);
+      /*authorization=*/std::nullopt,
+      GURL("https://accounts.example.test/RegisterKey"));
   ASSERT_TRUE(result.has_value());
 
   std::vector<std::string_view> header_and_payload = base::SplitStringPiece(
@@ -121,7 +164,8 @@
 TEST(SessionBindingUtilsTest, CreateKeyRefreshHeaderAndPayload) {
   std::optional<std::string> result = CreateKeyRefreshHeaderAndPayload(
       "test_challenge",
-      crypto::SignatureVerifier::SignatureAlgorithm::RSA_PKCS1_SHA256);
+      crypto::SignatureVerifier::SignatureAlgorithm::RSA_PKCS1_SHA256,
+      GURL("https://accounts.example.test/RegisterKey"));
   ASSERT_TRUE(result.has_value());
 
   std::vector<std::string_view> header_and_payload = base::SplitStringPiece(
@@ -141,6 +185,36 @@
   EXPECT_EQ(actual_payload, expected_payload);
 }
 
+TEST(SessionBindingUtilsTest, CreateKeyRefreshHeaderAndPayloadWithAud) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeatureWithParameters(
+      features::kDeviceBoundSessions, {{"IncludeAudFieldInJwt", "true"}});
+
+  std::optional<std::string> result = CreateKeyRefreshHeaderAndPayload(
+      "test_challenge",
+      crypto::SignatureVerifier::SignatureAlgorithm::RSA_PKCS1_SHA256,
+      GURL("https://accounts.example.test/RegisterKey"));
+  ASSERT_TRUE(result.has_value());
+
+  std::vector<std::string_view> header_and_payload = base::SplitStringPiece(
+      *result, ".", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
+  ASSERT_EQ(header_and_payload.size(), 2U);
+  base::Value actual_header =
+      Base64UrlEncodedJsonToValue(header_and_payload[0]);
+  base::Value actual_payload =
+      Base64UrlEncodedJsonToValue(header_and_payload[1]);
+
+  base::Value::Dict expected_header =
+      base::Value::Dict().Set("alg", "RS256").Set("typ", "dbsc+jwt");
+  base::Value::Dict expected_payload =
+      base::Value::Dict()
+          .Set("jti", "test_challenge")
+          .Set("aud", "https://accounts.example.test/RegisterKey");
+
+  EXPECT_EQ(actual_header, expected_header);
+  EXPECT_EQ(actual_payload, expected_payload);
+}
+
 TEST(SessionBindingUtilsTest, AppendSignatureToHeaderAndPayload) {
   std::optional<std::string> result = AppendSignatureToHeaderAndPayload(
       "abc.efg",
diff --git a/net/http/http_network_transaction.cc b/net/http/http_network_transaction.cc
index 9b8eddf..9b9953e 100644
--- a/net/http/http_network_transaction.cc
+++ b/net/http/http_network_transaction.cc
@@ -1311,34 +1311,12 @@
         target, AuthURL(target), request_->network_anonymization_key,
         session_->http_auth_cache(), session_->http_auth_handler_factory(),
         session_->host_resolver());
-  int rv = auth_controllers_[target]->MaybeGenerateAuthToken(
+  return auth_controllers_[target]->MaybeGenerateAuthToken(
       request_, io_callback_, net_log_);
-  // TODO(crbug.com/359404121): Remove this histogram after the investigation
-  // completes.
-  const bool blocked = rv == ERR_IO_PENDING;
-  if (blocked) {
-    blocked_generate_proxy_auth_token_start_time_ = base::TimeTicks::Now();
-  }
-  base::UmaHistogramBoolean(
-      base::StrCat({"Net.NetworkTransaction.GenerateProxyAuthTokenBlocked",
-                    IsGoogleHostWithAlpnH3(url_.host()) ? "GoogleHost." : ".",
-                    NegotiatedProtocolToHistogramSuffix(negotiated_protocol_)}),
-      blocked);
-  return rv;
 }
 
 int HttpNetworkTransaction::DoGenerateProxyAuthTokenComplete(int rv) {
   DCHECK_NE(ERR_IO_PENDING, rv);
-  // TODO(crbug.com/359404121): Remove this histogram after the investigation
-  // completes.
-  if (!blocked_generate_proxy_auth_token_start_time_.is_null()) {
-    base::UmaHistogramTimes(
-        base::StrCat(
-            {"Net.NetworkTransaction.GenerateProxyAuthTokenBlockTime",
-             IsGoogleHostWithAlpnH3(url_.host()) ? "GoogleHost." : ".",
-             NegotiatedProtocolToHistogramSuffix(negotiated_protocol_)}),
-        base::TimeTicks::Now() - blocked_generate_proxy_auth_token_start_time_);
-  }
   if (rv == OK)
     next_state_ = STATE_GENERATE_SERVER_AUTH_TOKEN;
   return rv;
@@ -1357,35 +1335,12 @@
   }
   if (!ShouldApplyServerAuth())
     return OK;
-  int rv = auth_controllers_[target]->MaybeGenerateAuthToken(
+  return auth_controllers_[target]->MaybeGenerateAuthToken(
       request_, io_callback_, net_log_);
-  // TODO(crbug.com/359404121): Remove this histogram after the investigation
-  // completes.
-  const bool blocked = rv == ERR_IO_PENDING;
-  if (blocked) {
-    blocked_generate_server_auth_token_start_time_ = base::TimeTicks::Now();
-  }
-  base::UmaHistogramBoolean(
-      base::StrCat({"Net.NetworkTransaction.GenerateServerAuthTokenBlocked",
-                    IsGoogleHostWithAlpnH3(url_.host()) ? "GoogleHost." : ".",
-                    NegotiatedProtocolToHistogramSuffix(negotiated_protocol_)}),
-      blocked);
-  return rv;
 }
 
 int HttpNetworkTransaction::DoGenerateServerAuthTokenComplete(int rv) {
   DCHECK_NE(ERR_IO_PENDING, rv);
-  // TODO(crbug.com/359404121): Remove this histogram after the investigation
-  // completes.
-  if (!blocked_generate_server_auth_token_start_time_.is_null()) {
-    base::UmaHistogramTimes(
-        base::StrCat(
-            {"Net.NetworkTransaction.GenerateServerAuthTokenBlockTime",
-             IsGoogleHostWithAlpnH3(url_.host()) ? "GoogleHost." : ".",
-             NegotiatedProtocolToHistogramSuffix(negotiated_protocol_)}),
-        base::TimeTicks::Now() -
-            blocked_generate_server_auth_token_start_time_);
-  }
   if (rv == OK)
     next_state_ = STATE_INIT_REQUEST_BODY;
   return rv;
diff --git a/net/http/http_network_transaction.h b/net/http/http_network_transaction.h
index e75a473..265fa8f3 100644
--- a/net/http/http_network_transaction.h
+++ b/net/http/http_network_transaction.h
@@ -531,8 +531,6 @@
   base::TimeTicks initialize_stream_end_time_;
 
   base::TimeTicks blocked_initialize_stream_start_time_;
-  base::TimeTicks blocked_generate_proxy_auth_token_start_time_;
-  base::TimeTicks blocked_generate_server_auth_token_start_time_;
 
   // Timing information for the connected callback.
   base::TimeTicks connected_callback_start_time_;
diff --git a/remoting/host/BUILD.gn b/remoting/host/BUILD.gn
index 39447176..438e3ac 100644
--- a/remoting/host/BUILD.gn
+++ b/remoting/host/BUILD.gn
@@ -109,6 +109,7 @@
     "audio_volume_filter.h",
     "basic_desktop_environment.h",
     "create_desktop_interaction_strategy_factory.h",
+    "delegating_desktop_display_info_monitor.h",
     "desktop_and_cursor_conditional_composer.h",
     "desktop_display_info_monitor.h",
     "desktop_environment.h",
@@ -376,7 +377,6 @@
     "daemon_process.cc",
     "daemon_process.h",
     "delegating_desktop_display_info_monitor.cc",
-    "delegating_desktop_display_info_monitor.h",
     "desktop_and_cursor_conditional_composer.cc",
     "desktop_process.cc",
     "desktop_process.h",
diff --git a/remoting/host/create_desktop_interaction_strategy_factory.cc b/remoting/host/create_desktop_interaction_strategy_factory.cc
index c331f893..d2433f8 100644
--- a/remoting/host/create_desktop_interaction_strategy_factory.cc
+++ b/remoting/host/create_desktop_interaction_strategy_factory.cc
@@ -14,6 +14,8 @@
 
 #if BUILDFLAG(IS_LINUX)
 #include "remoting/host/linux/gnome_interaction_strategy.h"
+#include "remoting/host/linux/gnome_remote_desktop_session.h"
+#include "remoting/host/linux/portal_interaction_strategy.h"
 #include "third_party/webrtc/modules/desktop_capture/desktop_capturer.h"
 #endif  // BUILDFLAG(IS_LINUX)
 
@@ -27,7 +29,11 @@
     scoped_refptr<base::SingleThreadTaskRunner> input_task_runner) {
 #if BUILDFLAG(IS_LINUX)
   if (webrtc::DesktopCapturer::IsRunningUnderWayland()) {
-    return std::make_unique<GnomeInteractionStrategyFactory>(ui_task_runner);
+    if (GnomeRemoteDesktopSession::IsRunningUnderGnome()) {
+      return std::make_unique<GnomeInteractionStrategyFactory>(ui_task_runner);
+    } else {
+      return std::make_unique<PortalInteractionStrategyFactory>();
+    }
   }
 #endif  // BUILDFLAG(IS_LINUX)
 
diff --git a/remoting/host/linux/BUILD.gn b/remoting/host/linux/BUILD.gn
index a45aed9..639c844 100644
--- a/remoting/host/linux/BUILD.gn
+++ b/remoting/host/linux/BUILD.gn
@@ -206,6 +206,10 @@
     "portal_desktop_resizer.h",
     "portal_display_info_loader.cc",
     "portal_display_info_loader.h",
+    "portal_interaction_strategy.cc",
+    "portal_interaction_strategy.h",
+    "portal_remote_desktop_session.cc",
+    "portal_remote_desktop_session.h",
     "portal_utils.cc",
     "portal_utils.h",
     "scoped_portal_request.cc",
diff --git a/remoting/host/linux/gnome_remote_desktop_session.cc b/remoting/host/linux/gnome_remote_desktop_session.cc
index e28a817..74228d6 100644
--- a/remoting/host/linux/gnome_remote_desktop_session.cc
+++ b/remoting/host/linux/gnome_remote_desktop_session.cc
@@ -6,6 +6,7 @@
 
 #include <memory>
 #include <string>
+#include <string_view>
 #include <utility>
 
 #include "base/functional/bind.h"
@@ -72,6 +73,14 @@
   }
 }
 
+// static
+bool GnomeRemoteDesktopSession::IsRunningUnderGnome() {
+  const char* xdg_current_desktop = getenv("XDG_CURRENT_DESKTOP");
+  return xdg_current_desktop &&
+         std::string_view{xdg_current_desktop} == "GNOME";
+}
+
+// static
 GnomeRemoteDesktopSession* GnomeRemoteDesktopSession::GetInstance() {
   static base::NoDestructor<GnomeRemoteDesktopSession> instance;
   return instance.get();
diff --git a/remoting/host/linux/gnome_remote_desktop_session.h b/remoting/host/linux/gnome_remote_desktop_session.h
index 373982a37..acbf45b 100644
--- a/remoting/host/linux/gnome_remote_desktop_session.h
+++ b/remoting/host/linux/gnome_remote_desktop_session.h
@@ -38,6 +38,8 @@
   using InitCallbackSignature = void(base::expected<void, std::string>);
   using InitCallback = base::OnceCallback<InitCallbackSignature>;
 
+  static bool IsRunningUnderGnome();
+
   // Returns the singleton instance of GnomeRemoteDesktopSession.
   static GnomeRemoteDesktopSession* GetInstance();
 
diff --git a/remoting/host/linux/linux_me2me_host.py b/remoting/host/linux/linux_me2me_host.py
index ba23a5b..e8f7bd7 100755
--- a/remoting/host/linux/linux_me2me_host.py
+++ b/remoting/host/linux/linux_me2me_host.py
@@ -171,9 +171,6 @@
 # This exit code is returned when a needed binary exists but cannot be executed.
 COMMAND_NOT_EXECUTABLE_EXIT_CODE = 126
 
-# Binary name for `gnome-session`.
-GNOME_SESSION = "gnome-session"
-
 # Globals needed by the atexit cleanup() handler.
 g_desktop = None
 g_host_hash = hashlib.md5(socket.gethostname().encode()).hexdigest()
@@ -1151,6 +1148,102 @@
     return False
 
 
+class WaylandSession(abc.ABC):
+  """Abstract base class for Wayland desktop environment specific logic."""
+
+  @classmethod
+  @abc.abstractmethod
+  def get_xdg_current_desktop(cls):
+    """Returns the value of the XDG_CURRENT_DESKTOP environment variable."""
+    pass
+
+  @abc.abstractmethod
+  def get_session_binary(self):
+    """Returns the name of the binary to start the Wayland session."""
+    pass
+
+  @abc.abstractmethod
+  def get_compositor_service(self):
+    """Returns the systemd service name for the Wayland compositor."""
+    pass
+
+  @abc.abstractmethod
+  def get_portal_services(self):
+    """Returns a list of XDG portal packages to restart."""
+    pass
+
+  @abc.abstractmethod
+  def pre_session_launch(self):
+    """Called before starting the Wayland session."""
+    pass
+
+  @abc.abstractmethod
+  def cleanup(self):
+    """Called during session cleanup."""
+    pass
+
+
+class GnomeWaylandSession(WaylandSession):
+  """GNOME Wayland desktop environment specific logic."""
+
+  @classmethod
+  def get_xdg_current_desktop(cls):
+    return "GNOME"
+
+  def get_session_binary(self):
+    return "gnome-session"
+
+  def get_compositor_service(self):
+    return "org.gnome.Shell@wayland"
+
+  def get_portal_services(self):
+    return ["xdg-desktop-portal-gnome", "xdg-desktop-portal-gtk"]
+
+  def pre_session_launch(self):
+    pass
+
+  def cleanup(self):
+    pass
+
+
+class KdeWaylandSession(WaylandSession):
+  """KDE Plasma Wayland desktop environment specific logic."""
+
+  @classmethod
+  def get_xdg_current_desktop(cls):
+    return "KDE"
+
+  def get_session_binary(self):
+    return "startplasma-wayland"
+
+  def get_compositor_service(self):
+    return "plasma-kwin_wayland.service"
+
+  def get_portal_services(self):
+    return ["plasma-xdg-desktop-portal-kde"]
+
+  def pre_session_launch(self):
+    self._terminate_kwin_wayland()
+
+  def cleanup(self):
+    self._terminate_kwin_wayland()
+
+  def _terminate_kwin_wayland(self):
+    # Killing startplasma-wayland does not kill kwin_wayland_wrapper and its
+    # subprocesses, so we need to manually terminate them.
+    for process in psutil.process_iter():
+      if process.name() in ['kwin_wayland', 'kwin_wayland_wrapper']:
+        try:
+          terminate_process(process.pid, process.name())
+        except psutil.NoSuchProcess:
+          pass
+
+WAYLAND_SESSIONS = {
+  GnomeWaylandSession.get_xdg_current_desktop(): GnomeWaylandSession,
+  KdeWaylandSession.get_xdg_current_desktop(): KdeWaylandSession,
+}
+
+
 class WaylandDesktop(Desktop):
   """Manage a single virtual wayland based desktop"""
 
@@ -1158,9 +1251,10 @@
   WL_SERVER_CHECK_TIMEOUT_SECONDS = 10
   WL_SERVER_REPLY_TIMEOUT_SECONDS = 1
 
-  def __init__(self, sizes, host_config):
+  def __init__(self, sizes, host_config, wayland_session):
     self.debug = False
     self._wayland_socket = None
+    self._wayland_session = wayland_session
     super(WaylandDesktop, self).__init__(sizes, host_config)
     self.inhibitors[self.server_inhibitor] \
         = HOST_OFFLINE_REASON_WAYLAND_SERVER_RETRIES_EXCEEDED
@@ -1171,6 +1265,8 @@
   def _init_child_env(self):
     super(WaylandDesktop, self)._init_child_env()
     self.child_env["XDG_SESSION_TYPE"] = "wayland"
+    self.child_env["XDG_CURRENT_DESKTOP"] = \
+      self._wayland_session.get_xdg_current_desktop()
 
     if self.debug:
       self.child_env["G_MESSAGES_DEBUG"] = "all"
@@ -1178,24 +1274,10 @@
       self.child_env["G_DEBUG"] = "fatal-criticals"
       self.child_env["WAYLAND_DEBUG"] = "1"
 
-  @staticmethod
-  def _is_gnome_session_present():
-    if not shutil.which(GNOME_SESSION):
-      logging.warning("Unable to find '%s' on the host" % GNOME_SESSION)
-      return False
-    return True
-
   def _launch_server(self, *args, **kwargs):
-    if not self._is_gnome_session_present():
-      logging.error("Only GNOME based wayland hosts are supported currently. "
-                    "If the host is a GNOME host, please ensure that "
-                    "'gnome-shell' is installed on it")
-      # Error won't be fixed without user intervention so we quit here without
-      # attempting to relaunch.
-      sys.exit(1)
     logging.info("Launching wayland server.")
     # Remove the display layout file, which will cause problems if it is applied
-    # right after the gnome session is launched. See: http://crbug.com/444052254
+    # right after the session is launched. See: http://crbug.com/444052254
     display_layout_file = os.path.join(
         CONFIG_DIR, "host#%s.display_layout.pb" % g_host_hash)
     try:
@@ -1204,14 +1286,20 @@
     except FileNotFoundError:
       pass
 
+    session_binary = self._wayland_session.get_session_binary()
+
+    if not shutil.which(session_binary):
+      logging.error("Unable to find '%s' on the host" % session_binary)
+      sys.exit(1)
+
     # Remove variables from the systemd environment that are known to cause
-    # problems in the GNOME Wayland session if present - see
+    # problems in the Wayland session if present - see
     # http://crbug.com/444255720.
     subprocess.call(["systemctl", "--user", "unset-environment",
                      "GDK_BACKEND", "SSH_CONNECTION"],
                     stdout=subprocess.DEVNULL)
-
-    self.server_proc = subprocess.Popen([GNOME_SESSION],
+    self._wayland_session.pre_session_launch()
+    self.server_proc = subprocess.Popen([session_binary],
                                         stdout=subprocess.PIPE,
                                         stderr=subprocess.STDOUT,
                                         env=self.child_env)
@@ -1269,24 +1357,25 @@
     happens within the allowed timeout, else false.
     """
     start_time = time.time()
+    compositor_service = self._wayland_session.get_compositor_service()
     while time.time() - start_time < self.WL_SERVER_CHECK_TIMEOUT_SECONDS:
       exit_code = subprocess.call(
-        # 'systemctl' is provided by the 'systemd' package. The GNOME Shell
-        # service is of type 'notify', which means that it tells systemd when
-        # it is ready to accept Wayland connections.
-        ["systemctl", "--user", "is-active", "org.gnome.Shell@wayland"],
+        # 'systemctl' is provided by the 'systemd' package. The service is of
+        # type 'notify', which means that it tells systemd when it is ready to
+        # accept Wayland connections.
+        ["systemctl", "--user", "is-active", compositor_service],
         stdout=subprocess.DEVNULL)
       if exit_code == 0:
-        logging.info("Wayland server became active in %s seconds: " %
-                 str(time.time() - start_time))
+        logging.info("Wayland compositor (%s) became active in %s seconds: " %
+          (compositor_service, str(time.time() - start_time)))
         # If the socket-name can't be fetched, treat it as a launch failure
         # requiring a restart, since it will not be possible for this script,
         # or a child process, to make a Wayland connection.
         return self._fetch_wayland_socket_from_systemd()
       time.sleep(self.WL_SERVER_CHECK_DELAY_SECONDS)
-    logging.error("Waited for wayland service to become active, but it "
+    logging.error("Waited for Wayland compositor (%s) to become active, but it "
                   "didn't happen in %s seconds" %
-                  (self.WL_SERVER_CHECK_TIMEOUT_SECONDS))
+                  (compositor_service, self.WL_SERVER_CHECK_TIMEOUT_SECONDS))
     return False
 
   def launch_desktop_session(self):
@@ -1312,10 +1401,9 @@
       sys.exit(1)
 
     try:
-      subprocess.check_output(["systemctl", "--user", "restart",
-                               "xdg-desktop-portal",
-                               "xdg-desktop-portal-gnome",
-                               "xdg-desktop-portal-gtk"],
+      portals = \
+        ["xdg-desktop-portal"] + self._wayland_session.get_portal_services()
+      subprocess.check_output(["systemctl", "--user", "restart"] + portals,
                                stderr=subprocess.STDOUT, env=self.child_env)
     except subprocess.CalledProcessError as err:
       logging.error("Unable to restart portal services on the host, "
@@ -1343,6 +1431,7 @@
       except psutil.Error:
         logging.error("Error terminating process")
       self.host_proc = None
+    self._wayland_session.cleanup()
 
     super(WaylandDesktop, self).cleanup()
 
@@ -2593,7 +2682,13 @@
       USE_WAYLAND_ENV_VAR in os.environ or
       '--enable-wayland' in extra_start_host_args)
   if is_wayland:
-    desktop = WaylandDesktop(sizes, host_config)
+      wayland_session_type = os.environ.get(USE_WAYLAND_ENV_VAR)
+      # Use GNOME as the default Wayland session.
+      wayland_session_class = \
+        WAYLAND_SESSIONS[wayland_session_type] \
+          if wayland_session_type in WAYLAND_SESSIONS \
+          else GnomeWaylandSession
+      desktop = WaylandDesktop(sizes, host_config, wayland_session_class())
   else:
     desktop = XDesktop(sizes, host_config)
 
diff --git a/remoting/host/linux/portal_interaction_strategy.cc b/remoting/host/linux/portal_interaction_strategy.cc
new file mode 100644
index 0000000..1f615a9
--- /dev/null
+++ b/remoting/host/linux/portal_interaction_strategy.cc
@@ -0,0 +1,152 @@
+// 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 "remoting/host/linux/portal_interaction_strategy.h"
+
+#include <memory>
+
+#include "base/functional/bind.h"
+#include "base/logging.h"
+#include "base/memory/ptr_util.h"
+#include "base/task/sequenced_task_runner.h"
+#include "remoting/base/logging.h"
+#include "remoting/host/action_executor.h"
+#include "remoting/host/audio_capturer.h"
+#include "remoting/host/delegating_desktop_display_info_monitor.h"
+#include "remoting/host/desktop_capturer_proxy.h"
+#include "remoting/host/linux/clipboard_portal.h"
+#include "remoting/host/linux/ei_input_injector.h"
+#include "remoting/host/linux/ei_keyboard_layout_monitor.h"
+#include "remoting/host/linux/pipewire_desktop_capturer.h"
+#include "remoting/host/linux/pipewire_local_input_monitor.h"
+#include "remoting/host/linux/pipewire_mouse_cursor_monitor.h"
+#include "remoting/host/linux/portal_curtain_mode.h"
+#include "remoting/host/linux/portal_desktop_resizer.h"
+#include "remoting/host/linux/portal_display_info_loader.h"
+#include "remoting/host/linux/portal_remote_desktop_session.h"
+#include "third_party/webrtc/modules/desktop_capture/desktop_capturer.h"
+
+namespace remoting {
+
+PortalInteractionStrategy::PortalInteractionStrategy() = default;
+PortalInteractionStrategy::~PortalInteractionStrategy() = default;
+
+std::unique_ptr<ActionExecutor>
+PortalInteractionStrategy::CreateActionExecutor() {
+  return nullptr;
+}
+
+std::unique_ptr<AudioCapturer>
+PortalInteractionStrategy::CreateAudioCapturer() {
+  return AudioCapturer::Create();
+}
+
+std::unique_ptr<InputInjector>
+PortalInteractionStrategy::CreateInputInjector() {
+  // The EI session is guaranteed to exist, because this InteractionStrategy
+  // (and DesktopEnvironment) are only returned to the caller (ClientSession)
+  // after the EI session is initialized.
+  DCHECK(remote_desktop_->ei_session());
+
+  auto result = std::make_unique<EiInputInjector>(
+      remote_desktop_->ei_session(),
+      remote_desktop_->capture_stream_manager()->GetWeakPtr(),
+      std::make_unique<ClipboardPortal>());
+  remote_desktop_->ei_session()->SetInputInjector(result->GetWeakPtr());
+  return result;
+}
+
+std::unique_ptr<DesktopResizer>
+PortalInteractionStrategy::CreateDesktopResizer() {
+  return std::make_unique<PortalDesktopResizer>(
+      *remote_desktop_->capture_stream_manager());
+}
+
+std::unique_ptr<DesktopCapturer> PortalInteractionStrategy::CreateVideoCapturer(
+    webrtc::ScreenId id) {
+  auto stream = remote_desktop_->capture_stream_manager()->GetStream(id);
+  if (!stream) {
+    LOG(ERROR) << "Cannot find stream for screen ID " << id;
+    return nullptr;
+  }
+  HOST_LOG << "Creating desktop capturer for stream ID " << id;
+  auto proxy = std::make_unique<DesktopCapturerProxy>(
+      base::SequencedTaskRunner::GetCurrentDefault());
+  proxy->set_supports_frame_callbacks(
+      PipewireDesktopCapturer::kSupportsFrameCallbacks);
+  proxy->set_capturer(std::make_unique<PipewireDesktopCapturer>(stream));
+  return proxy;
+}
+
+std::unique_ptr<protocol::MouseCursorMonitor>
+PortalInteractionStrategy::CreateMouseCursorMonitor() {
+  return std::make_unique<PipewireMouseCursorMonitor>(
+      remote_desktop_->mouse_cursor_capturer());
+}
+
+std::unique_ptr<KeyboardLayoutMonitor>
+PortalInteractionStrategy::CreateKeyboardLayoutMonitor(
+    base::RepeatingCallback<void(const protocol::KeyboardLayout&)> callback) {
+  return std::make_unique<EiKeyboardLayoutMonitor>(std::move(callback));
+}
+
+std::unique_ptr<ActiveDisplayMonitor>
+PortalInteractionStrategy::CreateActiveDisplayMonitor(
+    base::RepeatingCallback<void(webrtc::ScreenId)> callback) {
+  return nullptr;
+}
+std::unique_ptr<DesktopDisplayInfoMonitor>
+PortalInteractionStrategy::CreateDisplayInfoMonitor() {
+  return std::make_unique<DelegatingDesktopDisplayInfoMonitor>(
+      remote_desktop_->display_info_monitor());
+}
+
+std::unique_ptr<LocalInputMonitor>
+PortalInteractionStrategy::CreateLocalInputMonitor() {
+  return std::make_unique<PipewireLocalInputMonitor>(
+      *remote_desktop_->mouse_cursor_capturer());
+}
+
+std::unique_ptr<CurtainMode> PortalInteractionStrategy::CreateCurtainMode(
+    base::WeakPtr<ClientSessionControl> client_session_control) {
+  return std::make_unique<PortalCurtainMode>(client_session_control);
+}
+
+void PortalInteractionStrategy::Init(InitCallback callback) {
+  remote_desktop_->Init(base::BindOnce(&PortalInteractionStrategy::OnInitResult,
+                                       weak_ptr_factory_.GetWeakPtr(),
+                                       std::move(callback)));
+}
+
+void PortalInteractionStrategy::OnInitResult(
+    InitCallback callback,
+    base::expected<void, std::string> result) {
+  std::move(callback).Run(std::move(result));
+}
+
+PortalInteractionStrategyFactory::PortalInteractionStrategyFactory() = default;
+PortalInteractionStrategyFactory::~PortalInteractionStrategyFactory() = default;
+
+void PortalInteractionStrategyFactory::Create(
+    const DesktopEnvironmentOptions& options,
+    CreateCallback callback) {
+  auto session = base::WrapUnique(new PortalInteractionStrategy());
+  auto* raw = session.get();
+  raw->Init(base::BindOnce(
+      [](std::unique_ptr<PortalInteractionStrategy> session,
+         CreateCallback callback, base::expected<void, std::string> result) {
+        if (!result.has_value()) {
+          LOG(ERROR) << "Failed to initialize Portal Remote Desktop session: "
+                     << result.error();
+          std::move(callback).Run(nullptr);
+          return;
+        }
+
+        HOST_LOG << "Portal Remote Desktop interaction strategy initialized.";
+        std::move(callback).Run(std::move(session));
+      },
+      std::move(session), std::move(callback)));
+}
+
+}  // namespace remoting
diff --git a/remoting/host/linux/portal_interaction_strategy.h b/remoting/host/linux/portal_interaction_strategy.h
new file mode 100644
index 0000000..aefd8417
--- /dev/null
+++ b/remoting/host/linux/portal_interaction_strategy.h
@@ -0,0 +1,71 @@
+// 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 REMOTING_HOST_LINUX_PORTAL_INTERACTION_STRATEGY_H_
+#define REMOTING_HOST_LINUX_PORTAL_INTERACTION_STRATEGY_H_
+
+#include <memory>
+
+#include "base/memory/raw_ptr.h"
+#include "remoting/host/desktop_interaction_strategy.h"
+#include "remoting/host/linux/portal_remote_desktop_session.h"
+
+namespace remoting {
+
+class PortalInteractionStrategy : public DesktopInteractionStrategy {
+ public:
+  PortalInteractionStrategy();
+  ~PortalInteractionStrategy() override;
+
+  PortalInteractionStrategy(const PortalInteractionStrategy&) = delete;
+  PortalInteractionStrategy& operator=(const PortalInteractionStrategy&) =
+      delete;
+
+  // DesktopInteractionStrategy interface.
+  std::unique_ptr<ActionExecutor> CreateActionExecutor() override;
+  std::unique_ptr<AudioCapturer> CreateAudioCapturer() override;
+  std::unique_ptr<InputInjector> CreateInputInjector() override;
+  std::unique_ptr<DesktopResizer> CreateDesktopResizer() override;
+  std::unique_ptr<DesktopCapturer> CreateVideoCapturer(
+      webrtc::ScreenId id) override;
+  std::unique_ptr<protocol::MouseCursorMonitor> CreateMouseCursorMonitor()
+      override;
+  std::unique_ptr<KeyboardLayoutMonitor> CreateKeyboardLayoutMonitor(
+      base::RepeatingCallback<void(const protocol::KeyboardLayout&)> callback)
+      override;
+  std::unique_ptr<ActiveDisplayMonitor> CreateActiveDisplayMonitor(
+      base::RepeatingCallback<void(webrtc::ScreenId)> callback) override;
+  std::unique_ptr<DesktopDisplayInfoMonitor> CreateDisplayInfoMonitor()
+      override;
+  std::unique_ptr<LocalInputMonitor> CreateLocalInputMonitor() override;
+  std::unique_ptr<CurtainMode> CreateCurtainMode(
+      base::WeakPtr<ClientSessionControl> client_session_control) override;
+
+ private:
+  friend class PortalInteractionStrategyFactory;
+
+  using InitCallbackSignature = void(base::expected<void, std::string>);
+  using InitCallback = base::OnceCallback<InitCallbackSignature>;
+
+  void Init(InitCallback callback);
+  void OnInitResult(InitCallback callback,
+                    base::expected<void, std::string> result);
+
+  raw_ptr<PortalRemoteDesktopSession> remote_desktop_ =
+      PortalRemoteDesktopSession::GetInstance();
+  base::WeakPtrFactory<PortalInteractionStrategy> weak_ptr_factory_{this};
+};
+
+class PortalInteractionStrategyFactory
+    : public DesktopInteractionStrategyFactory {
+ public:
+  PortalInteractionStrategyFactory();
+  ~PortalInteractionStrategyFactory() override;
+  void Create(const DesktopEnvironmentOptions& options,
+              CreateCallback callback) override;
+};
+
+}  // namespace remoting
+
+#endif  // REMOTING_HOST_LINUX_PORTAL_INTERACTION_STRATEGY_H_
diff --git a/remoting/host/linux/portal_remote_desktop_session.cc b/remoting/host/linux/portal_remote_desktop_session.cc
new file mode 100644
index 0000000..e1b932c
--- /dev/null
+++ b/remoting/host/linux/portal_remote_desktop_session.cc
@@ -0,0 +1,270 @@
+// 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 "remoting/host/linux/portal_remote_desktop_session.h"
+
+#include <gio/gio.h>
+
+#include <memory>
+#include <string>
+#include <string_view>
+#include <utility>
+
+#include "base/check.h"
+#include "base/functional/bind.h"
+#include "base/functional/callback_helpers.h"
+#include "base/logging.h"
+#include "base/task/sequenced_task_runner.h"
+#include "base/time/time.h"
+#include "remoting/base/logging.h"
+#include "remoting/host/base/loggable.h"
+#include "remoting/host/delegating_desktop_display_info_monitor.h"
+#include "remoting/host/linux/dbus_interfaces/org_freedesktop_portal_RemoteDesktop.h"
+#include "remoting/host/linux/ei_sender_session.h"
+#include "remoting/host/linux/gdbus_connection_ref.h"
+#include "remoting/host/linux/gvariant_dict_builder.h"
+#include "remoting/host/linux/gvariant_ref.h"
+#include "remoting/host/linux/pipewire_mouse_cursor_capturer.h"
+#include "remoting/host/linux/portal_display_info_loader.h"
+#include "remoting/host/linux/portal_utils.h"
+#include "remoting/host/polling_desktop_display_info_monitor.h"
+#include "third_party/webrtc/modules/portal/scoped_glib.h"
+
+namespace remoting {
+
+// static
+PortalRemoteDesktopSession* PortalRemoteDesktopSession::GetInstance() {
+  static base::NoDestructor<PortalRemoteDesktopSession> instance;
+  return instance.get();
+}
+
+PortalRemoteDesktopSession::PortalRemoteDesktopSession() {
+  DETACH_FROM_SEQUENCE(sequence_checker_);
+}
+
+PortalRemoteDesktopSession::~PortalRemoteDesktopSession() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+}
+
+void PortalRemoteDesktopSession::Init(InitCallback callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (initialization_state_ == InitializationState::kInitialized) {
+    HOST_LOG << "Portal remote desktop session is already initialized. "
+             << "Posting a task to run the callback immediately.";
+    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
+        FROM_HERE, base::BindOnce(std::move(callback),
+                                  base::expected<void, std::string>()));
+    return;
+  }
+
+  init_callbacks_.AddUnsafe(std::move(callback));
+  if (initialization_state_ == InitializationState::kInitializing) {
+    return;
+  }
+
+  HOST_LOG << "Starting Portal remote desktop session";
+  initialization_state_ = InitializationState::kInitializing;
+  GDBusConnectionRef::CreateForSessionBus(CheckResultAndContinue(
+      &PortalRemoteDesktopSession::OnConnectionCreated,
+      /*request=*/nullptr, "Failed to connect to D-Bus session bus"));
+
+  // TODO: crbug.com/445973705 - Either the UI task runner, or use an event
+  // based implementation. We currently cannot pass the UI task runner, because
+  // it polls the PipeWire stream, which is bound to the network thread.
+  display_info_monitor_ = std::make_unique<PollingDesktopDisplayInfoMonitor>(
+      base::SequencedTaskRunner::GetCurrentDefault(),
+      std::make_unique<PortalDisplayInfoLoader>(capture_stream_manager_));
+  mouse_cursor_capturer_ = std::make_unique<PipewireMouseCursorCapturer>(
+      std::make_unique<DelegatingDesktopDisplayInfoMonitor>(
+          display_info_monitor_->GetWeakPtr()),
+      capture_stream_manager_.GetWeakPtr());
+  initialization_state_ = InitializationState::kInitializing;
+}
+
+template <typename SuccessType, typename String>
+GDBusConnectionRef::CallCallback<SuccessType>
+PortalRemoteDesktopSession::CheckResultAndContinue(
+    void (PortalRemoteDesktopSession::*success_method)(SuccessType),
+    std::unique_ptr<ScopedPortalRequest>* request,
+    String&& error_context) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  return base::BindOnce(
+      [](base::WeakPtr<PortalRemoteDesktopSession> that,
+         decltype(success_method) success_method,
+         std::unique_ptr<ScopedPortalRequest>* request,
+         std::string_view error_context,
+         base::expected<SuccessType, Loggable> result) {
+        if (!that) {
+          return;
+        }
+        if (request) {
+          request->reset();
+        }
+        if (result.has_value()) {
+          (that.get()->*success_method)(std::move(result).value());
+        } else {
+          that->OnInitError(error_context, std::move(result).error());
+        }
+      },
+      weak_ptr_factory_.GetWeakPtr(), success_method, request,
+      std::forward<String>(error_context));
+}
+
+template <typename String>
+GDBusConnectionRef::CallCallback<GVariantRef<"(o)">>
+PortalRemoteDesktopSession::ResetRequestOnFailure(
+    std::unique_ptr<ScopedPortalRequest>& request,
+    String&& error_context) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  return base::BindOnce(
+      [](base::WeakPtr<PortalRemoteDesktopSession> that,
+         std::unique_ptr<ScopedPortalRequest>& request,
+         const std::string& error_context,
+         base::expected<GVariantRef<"(o)">, Loggable> result) {
+        if (!that) {
+          return;
+        }
+        if (!result.has_value()) {
+          request.reset();
+          that->OnInitError(error_context, std::move(result).error());
+        }
+      },
+      weak_ptr_factory_.GetWeakPtr(), std::ref(request),
+      std::forward<String>(error_context));
+}
+
+void PortalRemoteDesktopSession::OnInitError(std::string_view error_message,
+                                             Loggable error_context) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  initialization_state_ = InitializationState::kNotInitialized;
+  portal_session_.reset();
+  create_session_request_.reset();
+  select_devices_request_.reset();
+  connection_ = {};
+  init_callbacks_.Notify(base::unexpected(
+      base::StrCat({error_message, ": ", error_context.ToString()})));
+  DCHECK(init_callbacks_.empty());
+}
+
+void PortalRemoteDesktopSession::OnConnectionCreated(
+    GDBusConnectionRef connection) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  connection_ = std::move(connection);
+
+  create_session_request_ = std::make_unique<ScopedPortalRequest>(
+      connection_,
+      CheckResultAndContinue(
+          &PortalRemoteDesktopSession::OnCreateSessionResponse,
+          &create_session_request_, "Failed to create remote-desktop session"));
+
+  portal_session_ = std::make_unique<ScopedPortalSession>(
+      connection_, base::BindOnce(&PortalRemoteDesktopSession::OnSessionClosed,
+                                  weak_ptr_factory_.GetWeakPtr()));
+
+  gvariant::GVariantRef<"a{sv}"> options =
+      GVariantDictBuilder()
+          .Add("handle_token", create_session_request_->token())
+          .Add("session_handle_token", portal_session_->token())
+          .Build();
+
+  connection_.Call<org_freedesktop_portal_RemoteDesktop::CreateSession>(
+      kPortalBusName, kPortalObjectPath, std::make_tuple(options),
+      ResetRequestOnFailure(create_session_request_,
+                            "RemoteDesktop.CreateSession failed"),
+      G_DBUS_CALL_FLAGS_NONE,
+      // The portal backend sometimes takes a long time to start up, so we set
+      // a large timeout.
+      /*timeout_msec=*/base::Minutes(2).InMilliseconds());
+}
+
+void PortalRemoteDesktopSession::OnCreateSessionResponse(
+    gvariant::GVariantRef<"a{sv}"> result) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  HOST_LOG << "RemoteDesktop.CreateSession succeeded.";
+
+  select_devices_request_ = std::make_unique<ScopedPortalRequest>(
+      connection_, CheckResultAndContinue(
+                       &PortalRemoteDesktopSession::OnSelectDevicesResponse,
+                       &select_devices_request_, "Failed to select devices"));
+
+  gvariant::GVariantRef<"a{sv}"> options =
+      GVariantDictBuilder()
+          .Add("handle_token", select_devices_request_->token())
+          .Add("types", /*KEYBOARD*/ 1u | /*MOUSE*/ 2u)
+          .Build();
+
+  connection_.Call<org_freedesktop_portal_RemoteDesktop::SelectDevices>(
+      kPortalBusName, kPortalObjectPath,
+      std::make_tuple(portal_session_->session_handle(), options),
+      ResetRequestOnFailure(select_devices_request_,
+                            "RemoteDesktop.SelectDevices failed"));
+}
+
+void PortalRemoteDesktopSession::OnSelectDevicesResponse(
+    gvariant::GVariantRef<"a{sv}"> result) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  capture_stream_manager_.Init(
+      connection_, portal_session_->session_handle(),
+      base::BindOnce(&PortalRemoteDesktopSession::OnCaptureStreamInitResult,
+                     weak_ptr_factory_.GetWeakPtr()));
+}
+
+void PortalRemoteDesktopSession::OnCaptureStreamInitResult(
+    base::expected<void, std::string> result) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (!result.has_value()) {
+    OnInitError("Failed to initialize capture stream manager",
+                Loggable(FROM_HERE, result.error()));
+    return;
+  }
+
+  HOST_LOG << "Capture stream initialized.";
+
+  connection_.Call<org_freedesktop_portal_RemoteDesktop::ConnectToEIS>(
+      kPortalBusName, kPortalObjectPath,
+      std::make_tuple(portal_session_->session_handle(),
+                      gvariant::EmptyArrayOf<"{sv}">()),
+      CheckResultAndContinue(&PortalRemoteDesktopSession::OnEisFd,
+                             /*request=*/nullptr,
+                             "RemoteDesktop.ConnectToEIS failed"));
+}
+
+void PortalRemoteDesktopSession::OnEisFd(
+    std::pair<std::tuple<GDBusFdList::Handle>, GDBusFdList> args) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  auto fd_list = std::move(args.second).MakeSparse();
+  auto [handle] = args.first;
+  auto eis_fd = fd_list.Extract(handle);
+  if (!eis_fd.is_valid()) {
+    OnInitError("Failed to get EIS FD",
+                Loggable(FROM_HERE, "Handle not present in FD list"));
+    return;
+  }
+
+  EiSenderSession::CreateWithFd(
+      std::move(eis_fd),
+      CheckResultAndContinue(&PortalRemoteDesktopSession::OnEiSession,
+                             /*request=*/nullptr,
+                             "Failed to create EI session"));
+}
+
+void PortalRemoteDesktopSession::OnEiSession(
+    std::unique_ptr<EiSenderSession> ei_session) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  ei_session_ = std::move(ei_session);
+  initialization_state_ = InitializationState::kInitialized;
+  init_callbacks_.Notify(base::ok());
+}
+
+void PortalRemoteDesktopSession::OnSessionClosed(
+    gvariant::GVariantRef<"a{sv}"> details) {
+  webrtc::Scoped<char> details_string(g_variant_print(details.raw(), FALSE));
+  OnInitError(
+      "Portal session closed unexpectedly.",
+      Loggable(FROM_HERE, details_string ? std::string{details_string.get()}
+                                         : std::string{}));
+}
+
+}  // namespace remoting
diff --git a/remoting/host/linux/portal_remote_desktop_session.h b/remoting/host/linux/portal_remote_desktop_session.h
new file mode 100644
index 0000000..87473db
--- /dev/null
+++ b/remoting/host/linux/portal_remote_desktop_session.h
@@ -0,0 +1,154 @@
+// 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 REMOTING_HOST_LINUX_PORTAL_REMOTE_DESKTOP_SESSION_H_
+#define REMOTING_HOST_LINUX_PORTAL_REMOTE_DESKTOP_SESSION_H_
+
+#include <memory>
+
+#include "base/callback_list.h"
+#include "base/memory/weak_ptr.h"
+#include "base/no_destructor.h"
+#include "base/sequence_checker.h"
+#include "base/thread_annotations.h"
+#include "base/types/expected.h"
+#include "remoting/host/base/loggable.h"
+#include "remoting/host/desktop_display_info_monitor.h"
+#include "remoting/host/linux/ei_sender_session.h"
+#include "remoting/host/linux/gdbus_connection_ref.h"
+#include "remoting/host/linux/pipewire_mouse_cursor_capturer.h"
+#include "remoting/host/linux/portal_capture_stream_manager.h"
+#include "remoting/host/linux/portal_desktop_resizer.h"
+#include "remoting/host/linux/scoped_portal_request.h"
+#include "remoting/host/linux/scoped_portal_session.h"
+#include "remoting/host/polling_desktop_display_info_monitor.h"
+
+namespace remoting {
+
+// A singleton for creating and managing a Portal-based remote desktop session.
+class PortalRemoteDesktopSession {
+ public:
+  using InitCallbackSignature = void(base::expected<void, std::string>);
+  using InitCallback = base::OnceCallback<InitCallbackSignature>;
+
+  // Returns the singleton instance.
+  static PortalRemoteDesktopSession* GetInstance();
+
+  PortalRemoteDesktopSession(const PortalRemoteDesktopSession&) = delete;
+  PortalRemoteDesktopSession& operator=(const PortalRemoteDesktopSession&) =
+      delete;
+
+  // Initializes the PortalRemoteDesktopSession, which includes creation of a
+  // virtual monitor and the corresponding screencast stream, then calls
+  // `callback`.
+  // This method can be called multiple times. If an initialization is already
+  // in progress, `callback` will be called after the initialization has
+  // completed. If the remote desktop session is already initialized, nothing
+  // happens, and `callback` will be called immediately by posting a task to the
+  // current sequence. If initialization has failed, calling this again will
+  // attempt to re-initialize.
+  void Init(InitCallback callback);
+
+  bool is_initialized() const {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    return initialization_state_ == InitializationState::kInitialized;
+  }
+
+  base::WeakPtr<PortalCaptureStreamManager> capture_stream_manager() {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    DCHECK(is_initialized());
+    return capture_stream_manager_.GetWeakPtr();
+  }
+
+  base::WeakPtr<PipewireMouseCursorCapturer> mouse_cursor_capturer() {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    DCHECK(is_initialized());
+    return mouse_cursor_capturer_->GetWeakPtr();
+  }
+
+  base::WeakPtr<PortalDesktopResizer> desktop_resizer() {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    DCHECK(is_initialized());
+    return desktop_resizer_.GetWeakPtr();
+  }
+
+  base::WeakPtr<EiSenderSession> ei_session() {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    DCHECK(is_initialized());
+    return ei_session_->GetWeakPtr();
+  }
+
+  base::WeakPtr<DesktopDisplayInfoMonitor> display_info_monitor() {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    DCHECK(is_initialized());
+    return display_info_monitor_->GetWeakPtr();
+  }
+
+ private:
+  friend class base::NoDestructor<PortalRemoteDesktopSession>;
+
+  enum class InitializationState {
+    kNotInitialized,
+    kInitializing,
+    kInitialized,
+  };
+
+  PortalRemoteDesktopSession();
+  ~PortalRemoteDesktopSession();
+
+  // Calls `success_method` if the returned callback is called with a result,
+  // otherwise calls OnInitError().
+  // Resets `request` iff it is not a nullptr (i.e. pointing to a unique_ptr).
+  template <typename SuccessType, typename String>
+  GDBusConnectionRef::CallCallback<SuccessType> CheckResultAndContinue(
+      void (PortalRemoteDesktopSession::*success_method)(SuccessType),
+      std::unique_ptr<ScopedPortalRequest>* request,
+      String&& error_context);
+
+  // No-op if the returned callback is called with a result, otherwise resets
+  // `request` and calls OnInitError().
+  template <typename String>
+  GDBusConnectionRef::CallCallback<GVariantRef<"(o)">> ResetRequestOnFailure(
+      std::unique_ptr<ScopedPortalRequest>& request,
+      String&& error_context);
+
+  void OnInitError(std::string_view what, Loggable why);
+  void OnConnectionCreated(GDBusConnectionRef connection);
+  void OnCreateSessionResponse(gvariant::GVariantRef<"a{sv}"> result);
+  void OnSelectDevicesResponse(gvariant::GVariantRef<"a{sv}"> result);
+  void OnCaptureStreamInitResult(base::expected<void, std::string> result);
+  void OnEisFd(std::pair<std::tuple<GDBusFdList::Handle>, GDBusFdList> args);
+  void OnEiSession(std::unique_ptr<EiSenderSession> ei_session);
+  void OnSessionClosed(gvariant::GVariantRef<"a{sv}"> details);
+
+  SEQUENCE_CHECKER(sequence_checker_);
+
+  InitializationState initialization_state_ GUARDED_BY_CONTEXT(
+      sequence_checker_) = InitializationState::kNotInitialized;
+  base::OnceCallbackList<InitCallbackSignature> init_callbacks_
+      GUARDED_BY_CONTEXT(sequence_checker_);
+  GDBusConnectionRef connection_ GUARDED_BY_CONTEXT(sequence_checker_);
+  std::unique_ptr<ScopedPortalSession> portal_session_
+      GUARDED_BY_CONTEXT(sequence_checker_);
+  std::unique_ptr<ScopedPortalRequest> create_session_request_
+      GUARDED_BY_CONTEXT(sequence_checker_);
+  std::unique_ptr<ScopedPortalRequest> select_devices_request_
+      GUARDED_BY_CONTEXT(sequence_checker_);
+
+  PortalCaptureStreamManager capture_stream_manager_
+      GUARDED_BY_CONTEXT(sequence_checker_);
+  PortalDesktopResizer desktop_resizer_ GUARDED_BY_CONTEXT(sequence_checker_){
+      capture_stream_manager_};
+  std::unique_ptr<PollingDesktopDisplayInfoMonitor> display_info_monitor_
+      GUARDED_BY_CONTEXT(sequence_checker_);
+  std::unique_ptr<PipewireMouseCursorCapturer> mouse_cursor_capturer_
+      GUARDED_BY_CONTEXT(sequence_checker_);
+  std::unique_ptr<EiSenderSession> ei_session_
+      GUARDED_BY_CONTEXT(sequence_checker_);
+
+  base::WeakPtrFactory<PortalRemoteDesktopSession> weak_ptr_factory_{this};
+};
+}  // namespace remoting
+
+#endif  // REMOTING_HOST_LINUX_PORTAL_REMOTE_DESKTOP_SESSION_H_
diff --git a/remoting/host/remoting_me2me_host.cc b/remoting/host/remoting_me2me_host.cc
index 6ce7caa..21294e83 100644
--- a/remoting/host/remoting_me2me_host.cc
+++ b/remoting/host/remoting_me2me_host.cc
@@ -145,6 +145,7 @@
 #include <gtk/gtk.h>
 
 #include "remoting/host/linux/gnome_remote_desktop_session.h"
+#include "remoting/host/linux/portal_remote_desktop_session.h"
 #include "ui/events/platform/x11/x11_event_source.h"
 #include "ui/gfx/x/connection.h"
 #include "ui/gfx/x/xlib_support.h"
@@ -1831,16 +1832,31 @@
 
 #if BUILDFLAG(IS_LINUX) && defined(REMOTING_USE_X11)
   if (webrtc::DesktopCapturer::IsRunningUnderWayland()) {
-    GnomeRemoteDesktopSession::GetInstance()->Init(
-        base::BindOnce([](base::expected<void, std::string> result) {
-          if (result.has_value()) {
-            LOG(INFO)
-                << "Gnome remote desktop session initialization succeeded.";
-          } else {
-            LOG(ERROR) << "Gnome remote desktop session initialization failed: "
-                       << result.error();
-          }
-        }));
+    if (GnomeRemoteDesktopSession::IsRunningUnderGnome()) {
+      GnomeRemoteDesktopSession::GetInstance()->Init(
+          base::BindOnce([](base::expected<void, std::string> result) {
+            if (result.has_value()) {
+              LOG(INFO)
+                  << "Gnome remote desktop session initialization succeeded.";
+            } else {
+              LOG(ERROR)
+                  << "Gnome remote desktop session initialization failed: "
+                  << result.error();
+            }
+          }));
+    } else {
+      PortalRemoteDesktopSession::GetInstance()->Init(
+          base::BindOnce([](base::expected<void, std::string> result) {
+            if (result.has_value()) {
+              LOG(INFO)
+                  << "Portal remote desktop session initialization succeeded.";
+            } else {
+              LOG(ERROR)
+                  << "Portal remote desktop session initialization failed: "
+                  << result.error();
+            }
+          }));
+    }
   }
 #endif
 
diff --git a/remoting/protocol/host_control_dispatcher.cc b/remoting/protocol/host_control_dispatcher.cc
index 78fbd6f7..9a14a91a 100644
--- a/remoting/protocol/host_control_dispatcher.cc
+++ b/remoting/protocol/host_control_dispatcher.cc
@@ -8,6 +8,7 @@
 #include "net/socket/stream_socket.h"
 #include "remoting/base/compound_buffer.h"
 #include "remoting/base/constants.h"
+#include "remoting/base/logging.h"
 #include "remoting/proto/control.pb.h"
 #include "remoting/proto/internal.pb.h"
 #include "remoting/protocol/clipboard_stub.h"
@@ -103,10 +104,21 @@
   message_pipe()->Send(&message, {});
 }
 
+void HostControlDispatcher::set_host_stub(HostStub* host_stub) {
+  host_stub_ = host_stub;
+  while (!pending_messages_.empty()) {
+    OnIncomingMessage(std::move(pending_messages_.front()));
+    pending_messages_.pop();
+  }
+}
+
 void HostControlDispatcher::OnIncomingMessage(
     std::unique_ptr<CompoundBuffer> buffer) {
-  DCHECK(clipboard_stub_);
-  DCHECK(host_stub_);
+  if (!host_stub_) {
+    HOST_LOG << "Received control message before host stub is set.";
+    pending_messages_.push(std::move(buffer));
+    return;
+  }
 
   std::unique_ptr<ControlMessage> message =
       ParseMessage<ControlMessage>(buffer.get());
@@ -116,7 +128,12 @@
 
   // TODO(sergeyu): Move message validation from the message handlers here.
   if (message->has_clipboard_event()) {
-    clipboard_stub_->InjectClipboardEvent(message->clipboard_event());
+    if (clipboard_stub_) {
+      clipboard_stub_->InjectClipboardEvent(message->clipboard_event());
+    } else {
+      LOG(WARNING)
+          << "Clipboard event ignored because no clipboard stub is set.";
+    }
   } else if (message->has_client_resolution()) {
     const ClientResolution& resolution = message->client_resolution();
     if ((resolution.has_width_pixels() && resolution.width_pixels() <= 0) ||
diff --git a/remoting/protocol/host_control_dispatcher.h b/remoting/protocol/host_control_dispatcher.h
index d4c8eaf..dc1b7aee 100644
--- a/remoting/protocol/host_control_dispatcher.h
+++ b/remoting/protocol/host_control_dispatcher.h
@@ -7,6 +7,7 @@
 
 #include <cstddef>
 
+#include "base/containers/queue.h"
 #include "base/logging.h"
 #include "base/memory/raw_ptr.h"
 #include "remoting/protocol/channel_dispatcher_base.h"
@@ -58,7 +59,9 @@
 
   // Sets the HostStub that will be called for each incoming control message.
   // |host_stub| must outlive this object.
-  void set_host_stub(HostStub* host_stub) { host_stub_ = host_stub; }
+  // If there are any messages that have been received prior to this call, they
+  // will be delivered to the host stub.
+  void set_host_stub(HostStub* host_stub);
 
   // Sets the maximum size of outgoing messages, which defaults to 64KiB. This
   // is used to ensure we don't try to send any clipboard messages that the
@@ -77,6 +80,9 @@
   // 64 KiB is the default message size expected to be supported in absence of a
   // higher value negotiated via SDP.
   std::size_t max_message_size_ = 64 * 1024;
+
+  // Messages that have been received before `set_host_stub` is called.
+  base::queue<std::unique_ptr<CompoundBuffer>> pending_messages_;
 };
 
 }  // namespace remoting::protocol
diff --git a/sandbox/policy/mac/common.sb b/sandbox/policy/mac/common.sb
index 9726d7a..fa8eb993 100644
--- a/sandbox/policy/mac/common.sb
+++ b/sandbox/policy/mac/common.sb
@@ -302,6 +302,7 @@
   (sysctl-name "hw.tbfrequency_compat")
   (sysctl-name "hw.vectorunit")
   (sysctl-name "kern.hostname")
+  (sysctl-name "kern.hv_vmm_present")
   (sysctl-name "kern.maxfilesperproc")
   (sysctl-name "kern.osproductversion")
   (sysctl-name "kern.osrelease")
diff --git a/sandbox/win/src/target_process.cc b/sandbox/win/src/target_process.cc
index 2d1d4a1..c4836ca 100644
--- a/sandbox/win/src/target_process.cc
+++ b/sandbox/win/src/target_process.cc
@@ -185,7 +185,9 @@
 
   bool inherit_handles = startup_info_helper->ShouldInheritHandles();
   PROCESS_INFORMATION temp_process_info = {};
-  if (!::CreateProcessAsUserW(lockdown_token_.get(), exe_path, cmd_line.get(),
+  // Allow Token handle to be closed once this function completes.
+  auto lockdown_token = lockdown_token_.release();
+  if (!::CreateProcessAsUserW(lockdown_token.get(), exe_path, cmd_line.get(),
                               nullptr,  // No security attribute.
                               nullptr,  // No thread attribute.
                               inherit_handles, flags,
@@ -196,17 +198,21 @@
     *win_error = ::GetLastError();
     return SBOX_ERROR_CREATE_PROCESS;
   }
+
   base::win::ScopedProcessInformation process_info(temp_process_info);
 
   // Change the token of the main thread of the new process for the
   // impersonation token with more rights. This allows the target to start;
   // otherwise it will crash too early for us to help.
   HANDLE temp_thread = process_info.thread_handle();
-  if (!::SetThreadToken(&temp_thread, initial_token_.get())) {
+  // Allow Token handle to be closed after this function completes.
+  auto initial_token = initial_token_.release();
+  if (!::SetThreadToken(&temp_thread, initial_token.get())) {
     *win_error = ::GetLastError();
     ::TerminateProcess(process_info.process_handle(), 0);
     return SBOX_ERROR_SET_THREAD_TOKEN;
   }
+
   if (!CheckImpersonationToken(process_info.thread_handle())) {
     *win_error = ERROR_BAD_IMPERSONATION_LEVEL;
     ::TerminateProcess(process_info.process_handle(), 0);
diff --git a/services/audio/output_device_mixer_manager_unittest.cc b/services/audio/output_device_mixer_manager_unittest.cc
index 92c1938..c880825 100644
--- a/services/audio/output_device_mixer_manager_unittest.cc
+++ b/services/audio/output_device_mixer_manager_unittest.cc
@@ -6,6 +6,7 @@
 
 #include <optional>
 
+#include "base/functional/callback_helpers.h"
 #include "base/task/single_thread_task_runner.h"
 #include "base/test/bind.h"
 #include "base/test/gmock_callback_support.h"
diff --git a/services/audio/public/cpp/output_device_unittest.cc b/services/audio/public/cpp/output_device_unittest.cc
index ce66116..6d817fa 100644
--- a/services/audio/public/cpp/output_device_unittest.cc
+++ b/services/audio/public/cpp/output_device_unittest.cc
@@ -8,6 +8,7 @@
 
 #include "base/compiler_specific.h"
 #include "base/functional/bind.h"
+#include "base/functional/callback_helpers.h"
 #include "base/memory/ptr_util.h"
 #include "base/run_loop.h"
 #include "base/test/task_environment.h"
diff --git a/services/data_decoder/public/cpp/safe_web_bundle_parser.cc b/services/data_decoder/public/cpp/safe_web_bundle_parser.cc
index 00c38381..12897b4 100644
--- a/services/data_decoder/public/cpp/safe_web_bundle_parser.cc
+++ b/services/data_decoder/public/cpp/safe_web_bundle_parser.cc
@@ -9,6 +9,7 @@
 
 #include "base/check.h"
 #include "base/functional/bind.h"
+#include "base/functional/callback_helpers.h"
 #include "base/task/task_traits.h"
 #include "base/task/thread_pool.h"
 #include "base/types/expected.h"
diff --git a/services/device/geolocation/location_provider_manager_unittest.cc b/services/device/geolocation/location_provider_manager_unittest.cc
index 82260028..b671d7e 100644
--- a/services/device/geolocation/location_provider_manager_unittest.cc
+++ b/services/device/geolocation/location_provider_manager_unittest.cc
@@ -11,6 +11,7 @@
 #include "base/compiler_specific.h"
 #include "base/containers/span.h"
 #include "base/functional/bind.h"
+#include "base/functional/callback_helpers.h"
 #include "base/memory/ptr_util.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
diff --git a/services/device/public/cpp/geolocation/geolocation_system_permission_manager_unittest.cc b/services/device/public/cpp/geolocation/geolocation_system_permission_manager_unittest.cc
index 8d30539..a64d4d0 100644
--- a/services/device/public/cpp/geolocation/geolocation_system_permission_manager_unittest.cc
+++ b/services/device/public/cpp/geolocation/geolocation_system_permission_manager_unittest.cc
@@ -7,6 +7,7 @@
 #include <memory>
 #include <vector>
 
+#include "base/functional/callback_helpers.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/task_environment.h"
 #include "services/device/public/cpp/device_features.h"
diff --git a/services/network/BUILD.gn b/services/network/BUILD.gn
index ab2a149..00db4dc6 100644
--- a/services/network/BUILD.gn
+++ b/services/network/BUILD.gn
@@ -341,18 +341,11 @@
     "//components/content_settings/core/common",
     "//components/cookie_config",
     "//components/domain_reliability",
-    "//components/ip_protection/common:ip_protection_core_host_remote",
-    "//components/ip_protection/common:ip_protection_core_impl",
-    "//components/ip_protection/common:ip_protection_core_impl_mojo",
-    "//components/ip_protection/common:ip_protection_proxy_delegate",
-    "//components/ip_protection/common:ip_protection_telemetry_uma",
-    "//components/ip_protection/common:masked_domain_list_manager",
     "//components/network_session_configurator/browser",
     "//components/network_session_configurator/common",
     "//components/os_crypt/async/common",
     "//components/os_crypt/sync",
     "//components/prefs",
-    "//components/privacy_sandbox/masked_domain_list:masked_domain_list_proto",
     "//components/unexportable_keys/mojom:mojo_service",
     "//components/url_matcher",
     "//components/url_pattern",
@@ -581,14 +574,10 @@
     ":network_service",
     ":test_support",
     "//base",
-    "//components/ip_protection/common:ip_protection_telemetry_uma",
-    "//components/ip_protection/common:masked_domain_list_manager",
     "//components/network_session_configurator/browser",
     "//components/os_crypt/async/browser:test_support",
     "//components/os_crypt/sync:test_support",
     "//components/prefs:test_support",
-    "//components/privacy_sandbox:features",
-    "//components/privacy_sandbox/masked_domain_list:masked_domain_list_proto",
     "//components/variations:test_support",
     "//components/web_package",
     "//components/webrtc:fake_ssl_socket",
diff --git a/services/network/DEPS b/services/network/DEPS
index 01048002..18a1a63 100644
--- a/services/network/DEPS
+++ b/services/network/DEPS
@@ -3,7 +3,6 @@
   "+components/content_settings/core/common",
   "+components/cookie_config",
   "+components/domain_reliability",
-  "+components/ip_protection",
   "+components/network_session_configurator",
   "+components/os_crypt/async",
   "+components/os_crypt/sync",
@@ -12,7 +11,6 @@
   # than to store user preferences.
   "+components/prefs",
   "+components/privacy_sandbox/privacy_sandbox_features.h",
-  "+components/privacy_sandbox/masked_domain_list",
   "+components/unexportable_keys",
   "+components/url_matcher",
   "+components/url_pattern",
diff --git a/services/network/brokered_udp_client_socket.cc b/services/network/brokered_udp_client_socket.cc
index 7345c5fd..b279a0b 100644
--- a/services/network/brokered_udp_client_socket.cc
+++ b/services/network/brokered_udp_client_socket.cc
@@ -5,6 +5,7 @@
 #include "services/network/brokered_udp_client_socket.h"
 
 #include "base/component_export.h"
+#include "base/functional/callback_helpers.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
 #include "base/sequence_checker.h"
diff --git a/services/network/cookie_settings_unittest.cc b/services/network/cookie_settings_unittest.cc
index 8c444f0..9276f83 100644
--- a/services/network/cookie_settings_unittest.cc
+++ b/services/network/cookie_settings_unittest.cc
@@ -16,7 +16,6 @@
 #include "components/content_settings/core/common/content_settings.h"
 #include "components/content_settings/core/common/content_settings_metadata.h"
 #include "components/content_settings/core/common/features.h"
-#include "components/privacy_sandbox/privacy_sandbox_features.h"
 #include "net/base/features.h"
 #include "net/base/network_delegate.h"
 #include "net/base/schemeful_site.h"
diff --git a/services/network/cors/cors_url_loader_factory_unittest.cc b/services/network/cors/cors_url_loader_factory_unittest.cc
index f1732fe..3087af0 100644
--- a/services/network/cors/cors_url_loader_factory_unittest.cc
+++ b/services/network/cors/cors_url_loader_factory_unittest.cc
@@ -9,7 +9,6 @@
 #include "base/memory/raw_ptr.h"
 #include "base/test/task_environment.h"
 #include "build/build_config.h"
-#include "components/privacy_sandbox/masked_domain_list/masked_domain_list.pb.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "mojo/public/cpp/test_support/fake_message_dispatch_context.h"
 #include "mojo/public/cpp/test_support/test_utils.h"
diff --git a/services/network/device_bound_session_manager.h b/services/network/device_bound_session_manager.h
index a324e49..c253db00 100644
--- a/services/network/device_bound_session_manager.h
+++ b/services/network/device_bound_session_manager.h
@@ -8,6 +8,7 @@
 #include <vector>
 
 #include "base/callback_list.h"
+#include "base/functional/callback_helpers.h"
 #include "mojo/public/cpp/bindings/receiver_set.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "services/network/public/mojom/device_bound_sessions.mojom.h"
diff --git a/services/network/devtools_durable_msg_collector_unittest.cc b/services/network/devtools_durable_msg_collector_unittest.cc
index b54f6f4..23faae1a 100644
--- a/services/network/devtools_durable_msg_collector_unittest.cc
+++ b/services/network/devtools_durable_msg_collector_unittest.cc
@@ -4,6 +4,7 @@
 
 #include "services/network/devtools_durable_msg_collector.h"
 
+#include "base/functional/callback_helpers.h"
 #include "base/run_loop.h"
 #include "base/test/bind.h"
 #include "base/test/task_environment.h"
diff --git a/services/network/enterprise/encryption/OWNERS b/services/network/enterprise/encryption/OWNERS
new file mode 100644
index 0000000..c40cb40
--- /dev/null
+++ b/services/network/enterprise/encryption/OWNERS
@@ -0,0 +1,5 @@
+# Primary
+haihan@google.com
+
+# For more trivial changes/+1s.
+cep-review@google.com
\ No newline at end of file
diff --git a/services/network/file_opener_for_upload.cc b/services/network/file_opener_for_upload.cc
index 30c97f5d..1423f36 100644
--- a/services/network/file_opener_for_upload.cc
+++ b/services/network/file_opener_for_upload.cc
@@ -4,6 +4,7 @@
 
 #include "services/network/file_opener_for_upload.h"
 
+#include "base/functional/callback_helpers.h"
 #include "base/task/thread_pool.h"
 #include "net/base/net_errors.h"
 #include "services/network/public/mojom/network_context_client.mojom.h"
diff --git a/services/network/first_party_sets/first_party_sets_access_delegate.cc b/services/network/first_party_sets/first_party_sets_access_delegate.cc
index 240c93e9..b780504e 100644
--- a/services/network/first_party_sets/first_party_sets_access_delegate.cc
+++ b/services/network/first_party_sets/first_party_sets_access_delegate.cc
@@ -7,6 +7,7 @@
 #include <utility>
 
 #include "base/feature_list.h"
+#include "base/functional/callback_helpers.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/time/time.h"
 #include "base/types/optional_ref.h"
diff --git a/services/network/network_context.cc b/services/network/network_context.cc
index 4ac92e5..48d5d5c0 100644
--- a/services/network/network_context.cc
+++ b/services/network/network_context.cc
@@ -47,9 +47,6 @@
 #include "build/chromecast_buildflags.h"
 #include "components/cookie_config/cookie_store_util.h"
 #include "components/domain_reliability/monitor.h"
-#include "components/ip_protection/common/ip_protection_core_host_remote.h"
-#include "components/ip_protection/common/ip_protection_core_impl_mojo.h"
-#include "components/ip_protection/common/ip_protection_proxy_delegate.h"
 #include "components/network_session_configurator/browser/network_session_configurator.h"
 #include "components/network_session_configurator/common/network_switches.h"
 #include "components/os_crypt/async/common/encryptor.h"
@@ -1226,14 +1223,6 @@
   block_trust_tokens_ = block;
 }
 
-void NetworkContext::SetTrackingProtectionContentSetting(
-    const ContentSettingsForOneType& settings) {
-  if (!ip_protection_core_) {
-    return;
-  }
-  ip_protection_core_->SetTrackingProtectionContentSetting(settings);
-}
-
 void NetworkContext::OnProxyLookupComplete(
     ProxyLookupRequest* proxy_lookup_request) {
   auto it = proxy_lookup_requests_.find(proxy_lookup_request);
@@ -2716,39 +2705,8 @@
   network_delegate_ = network_delegate.get();
   builder.set_network_delegate(std::move(network_delegate));
 
-  // Decide which ProxyDelegate to create. At most one of these will be the
-  // case for any given NetworkContext: either PrefetchProxy, handling its
-  // custom proxy configs, or IpProtection, using the proxy allowlist.
-  auto* mdl_manager = network_service_->masked_domain_list_manager();
-  bool requires_ipp_proxy_delegate =
-      (mdl_manager->IsEnabled() ||
-       !net::features::kIpPrivacyUnconditionalProxyDomainList.Get().empty()) &&
-      (params_->ip_protection_core_host ||
-       net::features::kIpPrivacyAlwaysCreateCore.Get());
-  if (requires_ipp_proxy_delegate) {
-    CHECK(!params_->initial_custom_proxy_config);
-    CHECK(!params_->custom_proxy_config_client_receiver);
-    scoped_refptr<ip_protection::IpProtectionCoreHostRemote> core_host_remote =
-        params_->ip_protection_core_host
-            ? base::MakeRefCounted<ip_protection::IpProtectionCoreHostRemote>(
-                  std::move(params_->ip_protection_core_host))
-            : nullptr;
-    auto ip_protection_core_impl =
-        std::make_unique<ip_protection::IpProtectionCoreImplMojo>(
-            std::move(params_->ip_protection_control), core_host_remote,
-            mdl_manager, params_->enable_ip_protection,
-            params_->ip_protection_incognito,
-            std::move(params_->initial_ip_protection_tokens));
-    builder.set_proxy_delegate(
-        std::make_unique<ip_protection::IpProtectionProxyDelegate>(
-            ip_protection_core_impl.get()));
-    // Set tracking protection content settings if there are any provided.
-    ip_protection_core_impl->SetTrackingProtectionContentSetting(
-        params_->tracking_protection_content_settings);
-
-    ip_protection_core_ = std::move(ip_protection_core_impl);
-  } else if (params_->initial_custom_proxy_config ||
-             params_->custom_proxy_config_client_receiver) {
+  if (params_->initial_custom_proxy_config ||
+      params_->custom_proxy_config_client_receiver) {
     builder.set_proxy_delegate(std::make_unique<NetworkServiceProxyDelegate>(
         std::move(params_->initial_custom_proxy_config),
         std::move(params_->custom_proxy_config_client_receiver),
diff --git a/services/network/network_context.h b/services/network/network_context.h
index 1de85535..855d72b 100644
--- a/services/network/network_context.h
+++ b/services/network/network_context.h
@@ -26,7 +26,6 @@
 #include "base/types/pass_key.h"
 #include "base/unguessable_token.h"
 #include "build/build_config.h"
-#include "components/ip_protection/common/ip_protection_core.h"
 #include "mojo/public/cpp/base/big_buffer.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
@@ -213,10 +212,6 @@
 
   CookieManager* cookie_manager() { return cookie_manager_.get(); }
 
-  ip_protection::IpProtectionCore* ip_protection_core() {
-    return ip_protection_core_.get();
-  }
-
   const base::flat_set<std::string>* cors_exempt_header_list() const {
     return &cors_exempt_header_list_;
   }
@@ -283,8 +278,6 @@
       const url::Origin& issuer,
       DeleteStoredTrustTokensCallback callback) override;
   void SetBlockTrustTokens(bool block) override;
-  void SetTrackingProtectionContentSetting(
-      const ContentSettingsForOneType& settings) override;
   void ClearNetworkingHistoryBetween(
       base::Time start_time,
       base::Time end_time,
@@ -871,11 +864,6 @@
 
   std::unique_ptr<ResourceScheduler> resource_scheduler_;
 
-  // The IpProtectionCore for this context, used to coordinate proxying
-  // protected requests. `url_request_context_owner_` indirectly holds
-  // a pointer to and must be defined after `ip_protection_core_`.
-  std::unique_ptr<ip_protection::IpProtectionCore> ip_protection_core_;
-
   // Used only when network::features::kCompressionDictionaryTransport is
   // enabled.
   // Note: `url_request_context_owner_` indirectly holds a pointer to
diff --git a/services/network/network_context_unittest.cc b/services/network/network_context_unittest.cc
index c6b3cf25..d15fa42 100644
--- a/services/network/network_context_unittest.cc
+++ b/services/network/network_context_unittest.cc
@@ -5165,51 +5165,6 @@
   EXPECT_FALSE(state->IsDohProbeRunning());
 }
 
-TEST_F(NetworkContextTest,
-       NetworkContextUpdatesIpProtectionCoreTrackingProtectionExceptions) {
-  const std::string url = "http://foo.com";
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitWithFeaturesAndParameters(
-      {{net::features::kEnableIpProtectionProxy,
-        {{net::features::kIpPrivacyAlwaysCreateCore.name, "true"}}},
-       {network::features::kMaskedDomainList, {}}},
-      {});
-
-  std::unique_ptr<NetworkContext> network_context =
-      CreateContextWithParams(CreateNetworkContextParamsForTesting());
-
-  content_settings::RuleMetaData metadata;
-  metadata.SetExpirationAndLifetime(base::Time(), base::TimeDelta());
-
-  // Verify with a TRACKING_PROTECTION exception.
-  {
-    network_context->SetTrackingProtectionContentSetting(
-        {ContentSettingPatternSource(ContentSettingsPattern::Wildcard(),
-                                     ContentSettingsPattern::FromString(url),
-                                     base::Value(CONTENT_SETTING_ALLOW),
-                                     content_settings::ProviderType::kNone,
-                                     /*incognito=*/true, metadata.Clone())});
-
-    EXPECT_TRUE(
-        network_context->ip_protection_core()->HasTrackingProtectionException(
-            GURL(url)));
-  }
-
-  // Verify without a TRACKING_PROTECTION exception.
-  {
-    network_context->SetTrackingProtectionContentSetting(
-        {ContentSettingPatternSource(ContentSettingsPattern::Wildcard(),
-                                     ContentSettingsPattern::FromString(url),
-                                     base::Value(CONTENT_SETTING_BLOCK),
-                                     content_settings::ProviderType::kNone,
-                                     /*incognito=*/true, std::move(metadata))});
-
-    EXPECT_FALSE(
-        network_context->ip_protection_core()->HasTrackingProtectionException(
-            GURL(url)));
-  }
-}
-
 TEST_F(NetworkContextTest, PrivacyModeDisabledByDefault) {
   const GURL kURL("http://foo.com");
   const GURL kOtherURL("http://other.com");
diff --git a/services/network/network_service.cc b/services/network/network_service.cc
index 4b05700..943e2e65 100644
--- a/services/network/network_service.cc
+++ b/services/network/network_service.cc
@@ -41,10 +41,7 @@
 #include "base/values.h"
 #include "build/build_config.h"
 #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/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"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/scoped_message_error_crash_key.h"
@@ -498,10 +495,6 @@
 
   tpcd_metadata_manager_ = std::make_unique<network::tpcd::metadata::Manager>();
 
-  masked_domain_list_manager_ =
-      std::make_unique<ip_protection::MaskedDomainListManager>(
-          params->ip_protection_proxy_bypass_policy);
-
 #if BUILDFLAG(IS_CT_SUPPORTED)
   constexpr size_t kMaxSCTAuditingCacheEntries = 1024;
   sct_auditing_cache_ =
@@ -989,16 +982,6 @@
   }
 }
 
-void NetworkService::UpdateMaskedDomainList(
-    base::File default_file,
-    uint64_t default_file_size,
-    base::File regular_browsing_file,
-    uint64_t regular_browsing_file_size) {
-  masked_domain_list_manager_->UpdateMaskedDomainList(
-      std::move(default_file), default_file_size,
-      std::move(regular_browsing_file), regular_browsing_file_size);
-}
-
 #if BUILDFLAG(IS_ANDROID)
 void NetworkService::DumpWithoutCrashing(base::Time dump_request_time) {
   static base::debug::CrashKeyString* time_key =
diff --git a/services/network/network_service.h b/services/network/network_service.h
index e1535f7d..d393374 100644
--- a/services/network/network_service.h
+++ b/services/network/network_service.h
@@ -25,8 +25,6 @@
 #include "base/time/time.h"
 #include "base/timer/timer.h"
 #include "build/build_config.h"
-#include "components/ip_protection/common/masked_domain_list_manager.h"
-#include "components/privacy_sandbox/masked_domain_list/masked_domain_list.pb.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/receiver.h"
@@ -223,12 +221,6 @@
   void UpdateKeyPinsList(mojom::PinListPtr pin_list,
                          base::Time update_time) override;
 
-  void UpdateMaskedDomainList(
-      base::File default_file,
-      uint64_t default_file_size,
-      base::File regular_browsing_file,
-      uint64_t regular_browsing_file_size) override;
-
 #if BUILDFLAG(IS_ANDROID)
   void DumpWithoutCrashing(base::Time dump_request_time) override;
 #endif  // BUILDFLAG(IS_ANDROID)
@@ -322,10 +314,6 @@
     return tpcd_metadata_manager_.get();
   }
 
-  ip_protection::MaskedDomainListManager* masked_domain_list_manager() const {
-    return masked_domain_list_manager_.get();
-  }
-
   void set_host_resolver_factory_for_testing(
       std::unique_ptr<net::HostResolver::Factory> host_resolver_factory) {
     host_resolver_factory_ = std::move(host_resolver_factory);
@@ -490,9 +478,6 @@
   // this with |owned_network_contexts_|.
   std::set<raw_ptr<NetworkContext, SetExperimental>> network_contexts_;
 
-  std::unique_ptr<ip_protection::MaskedDomainListManager>
-      masked_domain_list_manager_;
-
   // A per-process_id map of origins that are white-listed to allow
   // them to request raw headers for resources they request.
   std::map<int32_t, base::flat_set<url::Origin>>
diff --git a/services/network/network_service_proxy_delegate_unittest.cc b/services/network/network_service_proxy_delegate_unittest.cc
index 14dbd62a..6f533552 100644
--- a/services/network/network_service_proxy_delegate_unittest.cc
+++ b/services/network/network_service_proxy_delegate_unittest.cc
@@ -14,7 +14,6 @@
 #include "base/test/scoped_feature_list.h"
 #include "base/test/task_environment.h"
 #include "base/types/expected.h"
-#include "components/ip_protection/mojom/data_types.mojom.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "mojo/public/cpp/bindings/self_owned_receiver.h"
 #include "net/base/net_errors.h"
@@ -91,10 +90,6 @@
 
   void SetUp() override {
     context_ = net::CreateTestURLRequestContextBuilder()->Build();
-    scoped_feature_list_.InitWithFeatures(
-        {net::features::kEnableIpProtectionProxy,
-         network::features::kMaskedDomainList},
-        {});
   }
 
  protected:
@@ -126,13 +121,6 @@
     loop.Run();
   }
 
-  ip_protection::mojom::BlindSignedAuthTokenPtr MakeAuthToken(
-      std::string content) {
-    auto token = ip_protection::mojom::BlindSignedAuthToken::New();
-    token->token = std::move(content);
-    return token;
-  }
-
   void RunUntilIdle() { task_environment_.RunUntilIdle(); }
 
   TestCustomProxyConnectionObserver* TestObserver() const { return observer_; }
diff --git a/services/network/network_service_unittest.cc b/services/network/network_service_unittest.cc
index c2a2d25..72d2304 100644
--- a/services/network/network_service_unittest.cc
+++ b/services/network/network_service_unittest.cc
@@ -30,8 +30,6 @@
 #include "build/build_config.h"
 #include "components/os_crypt/async/browser/test_utils.h"
 #include "components/os_crypt/sync/os_crypt_mocker.h"
-#include "components/privacy_sandbox/masked_domain_list/masked_domain_list.pb.h"
-#include "mojo/public/cpp/base/proto_wrapper.h"
 #include "net/base/features.h"
 #include "net/base/ip_address.h"
 #include "net/base/ip_endpoint.h"
diff --git a/services/network/public/cpp/transferable_directory_unittest.cc b/services/network/public/cpp/transferable_directory_unittest.cc
index 855f871..d4d80497 100644
--- a/services/network/public/cpp/transferable_directory_unittest.cc
+++ b/services/network/public/cpp/transferable_directory_unittest.cc
@@ -7,6 +7,7 @@
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/files/scoped_temp_dir.h"
+#include "base/functional/callback_helpers.h"
 #include "mojo/public/cpp/test_support/test_utils.h"
 #include "services/network/public/cpp/network_service_buildflags.h"
 #include "services/network/public/mojom/transferable_directory.mojom.h"
diff --git a/services/network/public/mojom/BUILD.gn b/services/network/public/mojom/BUILD.gn
index 307cbfd..06d15f6 100644
--- a/services/network/public/mojom/BUILD.gn
+++ b/services/network/public/mojom/BUILD.gn
@@ -1789,7 +1789,6 @@
     ":url_loader_base",
     ":websocket_mojom",
     "//components/content_settings/core/common:mojo_bindings",
-    "//components/ip_protection/mojom",
     "//components/os_crypt/async/common:common_mojom",
     "//components/unexportable_keys/mojom",
     "//mojo/public/mojom/base",
diff --git a/services/network/public/mojom/network_context.mojom b/services/network/public/mojom/network_context.mojom
index 2829ec5..709aae55 100644
--- a/services/network/public/mojom/network_context.mojom
+++ b/services/network/public/mojom/network_context.mojom
@@ -5,8 +5,6 @@
 module network.mojom;
 
 import "components/content_settings/core/common/content_settings.mojom";
-import "components/ip_protection/mojom/core.mojom";
-import "components/ip_protection/mojom/data_types.mojom";
 import "components/unexportable_keys/mojom/unexportable_key_service.mojom";
 import "mojo/public/mojom/base/big_buffer.mojom";
 import "mojo/public/mojom/base/file_path.mojom";
@@ -436,32 +434,6 @@
   [EnableIf=is_win]
   SocketBrokerRemotes? socket_brokers;
 
-  // A service to fetch blind-signed auth tokens and proxy configuration info
-  // for IP protection. If this is not set, then either this NetworkContext
-  // doesn't have its traffic proxied by IP Protection or the feature has been
-  // disabled (and can't be re-enabled until the browser is restarted).
-  pending_remote<ip_protection.mojom.CoreHost>? ip_protection_core_host;
-
-  // A receiver for sending notifications from the browser process to the IP
-  // Protection business logic in the network service. This is set in the same
-  // cases as `ip_protection_core_host`.
-  pending_receiver<ip_protection.mojom.CoreControl>? ip_protection_control;
-
-  // Whether the IP Protection feature is enabled at the time the NetworkContext
-  // is created. This setting can change via the
-  // `SetIpProtectionEnabled` method of the `IpProtectionControl` interface.
-  bool enable_ip_protection = false;
-
-  // Signals to IP Protection if this is an incognito network context.
-  bool ip_protection_incognito = false;
-
-  // Contains unused IP Protection auth tokens recycled from a previous
-  // Incognito session, used to "pre-warm" the token cache for this
-  // NetworkContext.
-  map<ip_protection.mojom.ProxyLayer,
-      array<ip_protection.mojom.BlindSignedAuthToken>>
-          initial_ip_protection_tokens;
-
   // When PAC quick checking is enabled, DNS lookups for PAC script's host are
   // timed out aggressively. This prevents hanging all network request on DNS
   // lookups that are slow or are blockholed, at the cost of making it more
@@ -619,12 +591,6 @@
   // gets applied while constructing NetworkContext by updating the quic_params.
   int64? quic_idle_connection_timeout_seconds;
 
-  // Defines the current state of Tracking Protection content settings. This is
-  // used by IP Protection, and only set when IP Protection is enabled
-  // for the profile.
-  array<content_settings.mojom.ContentSettingPatternSource>
-      tracking_protection_content_settings;
-
   // If true, the HTTP cache will be encrypted on disk. For now, this only
   // switches the backend on Windows.
   bool enable_encrypted_http_cache = false;
@@ -1096,11 +1062,6 @@
   // Set Trust Token blocking to |block|.
   SetBlockTrustTokens(bool block);
 
-  // Sets the list of TRACKING_PROTECTION content setting exceptions in
-  // IpProtectionCore.
-  SetTrackingProtectionContentSetting(
-      array<content_settings.mojom.ContentSettingPatternSource> settings);
-
   // Clears network objects with implicit URL history information. Data related
   // to events that happened prior to |start_time| and after |end_time| may be
   // retained. Only applies to network objects without more specific methods
diff --git a/services/network/public/mojom/network_service.mojom b/services/network/public/mojom/network_service.mojom
index 433dd9df..378ab42 100644
--- a/services/network/public/mojom/network_service.mojom
+++ b/services/network/public/mojom/network_service.mojom
@@ -137,11 +137,6 @@
   // A SystemDnsResolver to provide out-of-process system DNS resolution, in
   // case the system DNS resolver cannot always run sandboxed.
   pending_remote<SystemDnsResolver>? system_dns_resolver;
-
-  // The policy used for bypassing requests that are eligible for IP Protection.
-  // Even if a domain is part of the masked domain list, the bypass policy can
-  // be used to bypass certain network requests from IP Protection.
-  IpProtectionProxyBypassPolicy ip_protection_proxy_bypass_policy;
 };
 
 // Configuration settings for SCT Auditing.
@@ -389,23 +384,6 @@
   // be restricted.
   SetExplicitlyAllowedPorts(array<uint16> ports);
 
-  // Updates the Masked Domain List data used to generate custom proxy configs
-  // for the Privacy Proxy, with files pointing to flatbuffer files. Any
-  // existing NetworkContexts that are using the custom proxy configs will have
-  // their configs updated. This is not compatible with MDL policies that
-  // require exclusion list.
-  //
-  // These files are opened read-only, but are also deleted. Per comments in
-  // the mojom traits for `ReadOnlyFile`, the underlying file must not be
-  // deleted, so we use `File` here.
-  //
-  // File sizes must be included here, as otherwise `MemoryMappedFile` cannot
-  // tell how much memory to map.
-  UpdateMaskedDomainList(mojo_base.mojom.File default_file,
-                         uint64 default_file_size,
-                         mojo_base.mojom.File regular_browsing_file,
-                         uint64 regular_browsing_file_size);
-
   // Parses response headers and returns a structured version to the caller.
   // This call originates from the browser process. Used for navigations when
   // the URLLoader didn't provide ParsedHeader in the first place. This happens
diff --git a/services/network/restricted_cookie_manager.cc b/services/network/restricted_cookie_manager.cc
index fc5a6eb4d..135a46c 100644
--- a/services/network/restricted_cookie_manager.cc
+++ b/services/network/restricted_cookie_manager.cc
@@ -14,6 +14,7 @@
 #include "base/compiler_specific.h"  // for [[fallthrough]];
 #include "base/feature_list.h"
 #include "base/functional/bind.h"
+#include "base/functional/callback_helpers.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/read_only_shared_memory_region.h"
 #include "base/memory/structured_shared_memory.h"
diff --git a/services/network/shared_dictionary/shared_dictionary_disk_cache.cc b/services/network/shared_dictionary/shared_dictionary_disk_cache.cc
index 1c26d85..5b5bbce 100644
--- a/services/network/shared_dictionary/shared_dictionary_disk_cache.cc
+++ b/services/network/shared_dictionary/shared_dictionary_disk_cache.cc
@@ -6,6 +6,7 @@
 
 #include <limits>
 
+#include "base/functional/callback_helpers.h"
 #include "base/trace_event/trace_event.h"
 #include "base/trace_event/trace_id_helper.h"
 
diff --git a/services/network/test/test_network_context.h b/services/network/test/test_network_context.h
index 317439a..9cb5bc9 100644
--- a/services/network/test/test_network_context.h
+++ b/services/network/test/test_network_context.h
@@ -85,8 +85,6 @@
       const url::Origin& issuer,
       DeleteStoredTrustTokensCallback callback) override {}
   void SetBlockTrustTokens(bool block) override {}
-  void SetTrackingProtectionContentSetting(
-      const ContentSettingsForOneType& settings) override {}
 #if BUILDFLAG(ENABLE_REPORTING)
   void AddReportingApiObserver(
       mojo::PendingRemote<network::mojom::ReportingApiObserver> observer)
diff --git a/services/network/test/test_network_context_with_host_resolver.cc b/services/network/test/test_network_context_with_host_resolver.cc
index 45459b0..152d085b 100644
--- a/services/network/test/test_network_context_with_host_resolver.cc
+++ b/services/network/test/test_network_context_with_host_resolver.cc
@@ -7,6 +7,8 @@
 #include <memory>
 #include <utility>
 
+#include "base/functional/callback_helpers.h"
+
 namespace network {
 
 TestNetworkContextWithHostResolver::TestNetworkContextWithHostResolver(
diff --git a/services/network/web_bundle/web_bundle_manager.cc b/services/network/web_bundle/web_bundle_manager.cc
index 515f77d..c04592fa 100644
--- a/services/network/web_bundle/web_bundle_manager.cc
+++ b/services/network/web_bundle/web_bundle_manager.cc
@@ -8,6 +8,7 @@
 
 #include "base/containers/contains.h"
 #include "base/functional/bind.h"
+#include "base/functional/callback_helpers.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/time/time.h"
 #include "components/web_package/web_bundle_utils.h"
diff --git a/services/network/web_bundle/web_bundle_manager_unittest.cc b/services/network/web_bundle/web_bundle_manager_unittest.cc
index 252ed239..ad7a4f7 100644
--- a/services/network/web_bundle/web_bundle_manager_unittest.cc
+++ b/services/network/web_bundle/web_bundle_manager_unittest.cc
@@ -4,6 +4,7 @@
 
 #include "services/network/web_bundle/web_bundle_manager.h"
 
+#include "base/functional/callback_helpers.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/task_environment.h"
 #include "base/unguessable_token.h"
diff --git a/services/on_device_model/android/backend_model_impl_android_unittest.cc b/services/on_device_model/android/backend_model_impl_android_unittest.cc
index c7138087..85b2d9e3 100644
--- a/services/on_device_model/android/backend_model_impl_android_unittest.cc
+++ b/services/on_device_model/android/backend_model_impl_android_unittest.cc
@@ -4,6 +4,7 @@
 
 #include "services/on_device_model/android/backend_model_impl_android.h"
 
+#include "base/functional/callback_helpers.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/task_environment.h"
 #include "components/optimization_guide/proto/model_execution.pb.h"
diff --git a/services/on_device_model/ml/BUILD.gn b/services/on_device_model/ml/BUILD.gn
index 38fcaa8..67ee35a 100644
--- a/services/on_device_model/ml/BUILD.gn
+++ b/services/on_device_model/ml/BUILD.gn
@@ -3,6 +3,7 @@
 # found in the LICENSE file.
 
 import("//build/config/features.gni")
+import("//components/optimization_guide/features.gni")
 import("//services/on_device_model/on_device_model.gni")
 
 source_set("api") {
@@ -64,6 +65,7 @@
     deps = [
       "//components/crash/core/common:crash_key",
       "//components/language_detection/core",
+      "//components/optimization_guide:machine_learning_tflite_buildflags",
       "//components/optimization_guide/core:features",
       "//components/translate/core/language_detection",
       "//services/on_device_model:backend_interfaces",
@@ -73,6 +75,7 @@
       "//third_party/dawn/src/dawn:proc",
       "//third_party/dawn/src/dawn/native",
     ]
+
     if (enable_constraints) {
       defines += [ "ENABLE_ON_DEVICE_CONSTRAINTS" ]
       deps += [ "//third_party/rust/llguidance/v1:lib" ]
@@ -80,7 +83,7 @@
     if (use_blink) {
       deps += [ "//gpu/config" ]
     }
-    if (!is_fuchsia) {
+    if (!is_fuchsia && build_with_tflite_lib) {
       deps += [ "//services/on_device_model/safety:bert_safety_model" ]
     }
     if (is_win || is_mac || is_linux) {
diff --git a/services/on_device_model/ml/DEPS b/services/on_device_model/ml/DEPS
index ae54ecf..f06f8ee 100644
--- a/services/on_device_model/ml/DEPS
+++ b/services/on_device_model/ml/DEPS
@@ -1,5 +1,6 @@
 include_rules = [
   "+components/language_detection/core",
+  "+components/optimization_guide/machine_learning_tflite_buildflags.h",
   "+components/translate/core/language_detection",
   "+third_party/rust/chromium_crates_io/vendor/llguidance-v1/llguidance.h",
   "+third_party/xnnpack",
diff --git a/services/on_device_model/ml/ts_model.cc b/services/on_device_model/ml/ts_model.cc
index 49998be2..5c13fe8 100644
--- a/services/on_device_model/ml/ts_model.cc
+++ b/services/on_device_model/ml/ts_model.cc
@@ -19,6 +19,7 @@
 #include "base/trace_event/trace_event.h"
 #include "build/build_config.h"
 #include "components/language_detection/core/language_detection_provider.h"
+#include "components/optimization_guide/machine_learning_tflite_buildflags.h"
 #include "components/translate/core/language_detection/language_detection_model.h"
 #include "services/on_device_model/ml/chrome_ml.h"
 #include "services/on_device_model/ml/chrome_ml_api.h"
@@ -26,7 +27,7 @@
 #include "services/on_device_model/public/mojom/on_device_model_service.mojom.h"
 #include "services/on_device_model/safety/safety_util.h"
 
-#if !BUILDFLAG(IS_FUCHSIA)
+#if !BUILDFLAG(IS_FUCHSIA) && BUILDFLAG(BUILD_WITH_TFLITE_LIB)
 #include "services/on_device_model/safety/bert_safety_model.h"
 #endif
 
@@ -98,6 +99,7 @@
 
 bool TsModel::InitLanguageDetection(mojom::LanguageModelAssetsPtr assets) {
   TRACE_EVENT("optimization_guide", "TsModel::InitLanguageDetection");
+#if BUILDFLAG(BUILD_WITH_TFLITE_LIB)
   auto tflite_model =
       std::make_unique<language_detection::LanguageDetectionModel>();
   tflite_model->UpdateWithFile(std::move(assets->model));
@@ -105,6 +107,9 @@
   language_detector_ = std::make_unique<translate::LanguageDetectionModel>(
       std::move(tflite_model));
   return language_detector_->IsAvailable();
+#else
+  return false;
+#endif
 }
 
 DISABLE_CFI_DLSYM
@@ -178,10 +183,14 @@
   if (!language_detector_) {
     return nullptr;
   }
+#if BUILDFLAG(BUILD_WITH_TFLITE_LIB)
   language_detection::Prediction prediction = on_device_model::PredictLanguage(
       language_detector_->tflite_model(), text);
   return mojom::LanguageDetectionResult::New(prediction.language,
                                              prediction.score);
+#else
+  return nullptr;
+#endif
 }
 
 TsHolder::TsHolder(raw_ref<const ChromeML> chrome_ml) : chrome_ml_(chrome_ml) {}
@@ -198,7 +207,7 @@
                      mojo::PendingReceiver<mojom::TextSafetyModel> model) {
   model_.Clear();
 
-#if !BUILDFLAG(IS_FUCHSIA)
+#if !BUILDFLAG(IS_FUCHSIA) && BUILDFLAG(BUILD_WITH_TFLITE_LIB)
   if (!params->safety_assets || params->safety_assets->which() ==
                                     mojom::SafetyModelAssets::Tag::kTsAssets) {
     auto impl = TsModel::Create(*chrome_ml_, std::move(params));
diff --git a/services/on_device_model/safety/BUILD.gn b/services/on_device_model/safety/BUILD.gn
index 434255b..2d443f0 100644
--- a/services/on_device_model/safety/BUILD.gn
+++ b/services/on_device_model/safety/BUILD.gn
@@ -4,7 +4,7 @@
 
 import("//components/optimization_guide/features.gni")
 
-if (!is_fuchsia) {
+if (!is_fuchsia && build_with_tflite_lib) {
   source_set("bert_safety_op_resolver") {
     sources = [
       "bert_safety_op_resolver.cc",
diff --git a/services/tracing/public/cpp/perfetto/perfetto_config.cc b/services/tracing/public/cpp/perfetto/perfetto_config.cc
index 11ed0257..48c5ed6 100644
--- a/services/tracing/public/cpp/perfetto/perfetto_config.cc
+++ b/services/tracing/public/cpp/perfetto/perfetto_config.cc
@@ -174,15 +174,19 @@
 void AdaptBuiltinDataSourcesConfig(
     perfetto::TraceConfig::BuiltinDataSource* config,
     bool privacy_filtering_enabled) {
-  // Chrome uses CLOCK_MONOTONIC as its trace clock on Posix. To avoid that
-  // trace processor converts Chrome's event timestamps into CLOCK_BOOTTIME
-  // during import, we set the trace clock here (the service will emit it into
-  // the trace's ClockSnapshots). See also crbug.com/1060400, where the
-  // conversion to BOOTTIME caused CrOS and chromecast system data source data
-  // to be misaligned.
-  config->set_primary_trace_clock(
-      static_cast<perfetto::protos::gen::BuiltinClock>(
-          base::tracing::kTraceClockId));
+  // Sets primary trace clock to CLOCK_MONOTONIC only if the config doesn't set
+  // it.
+  if (!config->has_primary_trace_clock()) {
+    // Chrome uses CLOCK_MONOTONIC as its trace clock on Posix. To avoid that
+    // trace processor converts Chrome's event timestamps into CLOCK_BOOTTIME
+    // during import, we set the trace clock here (the service will emit it into
+    // the trace's ClockSnapshots). See also crbug.com/1060400, where the
+    // conversion to BOOTTIME caused CrOS and chromecast system data source data
+    // to be misaligned.
+    config->set_primary_trace_clock(
+        static_cast<perfetto::protos::gen::BuiltinClock>(
+            base::tracing::kTraceClockId));
+  }
 
   // Chrome emits system / trace config metadata itself.
   config->set_disable_trace_config(privacy_filtering_enabled);
diff --git a/services/tracing/public/cpp/perfetto/perfetto_config_unittest.cc b/services/tracing/public/cpp/perfetto/perfetto_config_unittest.cc
index bf9f242..b8aa833 100644
--- a/services/tracing/public/cpp/perfetto/perfetto_config_unittest.cc
+++ b/services/tracing/public/cpp/perfetto/perfetto_config_unittest.cc
@@ -12,6 +12,7 @@
 #include "build/build_config.h"
 #include "build/chromecast_buildflags.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/perfetto/protos/perfetto/common/builtin_clock.gen.h"
 #include "third_party/perfetto/protos/perfetto/config/data_source_config.gen.h"
 
 namespace tracing {
@@ -297,4 +298,25 @@
   }
 }
 
+TEST_F(AdaptPerfettoConfigForChromeTest, BuiltinDataSource_ClockBoottime) {
+  auto perfetto_config = ParsePerfettoConfigFromText(R"pb(
+    data_sources: {
+      config: {
+        name: "track_event"
+        track_event_config: {
+          enabled_categories: [ "foo", "__metadata" ]
+          disabled_categories: [ "*" ]
+        }
+      }
+    }
+    data_sources: { config: { name: "org.chromium.trace_metadata2" } }
+    builtin_data_sources { primary_trace_clock: BUILTIN_CLOCK_BOOTTIME }
+  )pb");
+
+  EXPECT_TRUE(
+      AdaptPerfettoConfigForChrome(&perfetto_config, false, false, true));
+  EXPECT_EQ(::perfetto::protos::gen::BuiltinClock::BUILTIN_CLOCK_BOOTTIME,
+            perfetto_config.builtin_data_sources().primary_trace_clock());
+}
+
 }  // namespace tracing
diff --git a/services/viz/public/cpp/compositing/transferable_resource_mojom_traits.cc b/services/viz/public/cpp/compositing/transferable_resource_mojom_traits.cc
index 106c90c8..186fa14 100644
--- a/services/viz/public/cpp/compositing/transferable_resource_mojom_traits.cc
+++ b/services/viz/public/cpp/compositing/transferable_resource_mojom_traits.cc
@@ -16,6 +16,19 @@
 namespace mojo {
 
 // static
+bool StructTraits<viz::mojom::MetadataOverrideDataView,
+                  viz::TransferableResource::MetadataOverride>::
+    Read(viz::mojom::MetadataOverrideDataView data,
+         viz::TransferableResource::MetadataOverride* out) {
+  out->is_overlay_candidate = data.is_overlay_candidate();
+  if (!data.ReadColorSpace(&out->color_space) ||
+      !data.ReadOrigin(&out->origin) || !data.ReadAlphaType(&out->alpha_type)) {
+    return false;
+  }
+  return true;
+}
+
+// static
 viz::mojom::SynchronizationType
 EnumTraits<viz::mojom::SynchronizationType,
            viz::TransferableResource::SynchronizationType>::
diff --git a/services/viz/public/cpp/compositing/transferable_resource_mojom_traits.h b/services/viz/public/cpp/compositing/transferable_resource_mojom_traits.h
index 3a86c80..af4f5ac 100644
--- a/services/viz/public/cpp/compositing/transferable_resource_mojom_traits.h
+++ b/services/viz/public/cpp/compositing/transferable_resource_mojom_traits.h
@@ -24,6 +24,33 @@
 namespace mojo {
 
 template <>
+struct StructTraits<viz::mojom::MetadataOverrideDataView,
+                    viz::TransferableResource::MetadataOverride> {
+  static const std::optional<bool>& is_overlay_candidate(
+      const viz::TransferableResource::MetadataOverride& input) {
+    return input.is_overlay_candidate;
+  }
+
+  static const std::optional<gfx::ColorSpace>& color_space(
+      const viz::TransferableResource::MetadataOverride& input) {
+    return input.color_space;
+  }
+
+  static const std::optional<GrSurfaceOrigin>& origin(
+      const viz::TransferableResource::MetadataOverride& input) {
+    return input.origin;
+  }
+
+  static const std::optional<SkAlphaType>& alpha_type(
+      const viz::TransferableResource::MetadataOverride& input) {
+    return input.alpha_type;
+  }
+
+  static bool Read(viz::mojom::MetadataOverrideDataView data,
+                   viz::TransferableResource::MetadataOverride* out);
+};
+
+template <>
 struct EnumTraits<viz::mojom::SynchronizationType,
                   viz::TransferableResource::SynchronizationType> {
   static viz::mojom::SynchronizationType ToMojom(
diff --git a/services/viz/public/cpp/gpu/context_provider_command_buffer.cc b/services/viz/public/cpp/gpu/context_provider_command_buffer.cc
index 939e941..d8c7cbe4 100644
--- a/services/viz/public/cpp/gpu/context_provider_command_buffer.cc
+++ b/services/viz/public/cpp/gpu/context_provider_command_buffer.cc
@@ -63,6 +63,7 @@
     const gpu::SharedMemoryLimits& memory_limits,
     gpu::mojom::ContextCreationAttribsPtr attributes,
     command_buffer_metrics::ContextType type,
+    bool enable_gpu_rasterization,
     base::SharedMemoryMapper* buffer_mapper)
     : base::subtle::RefCountedThreadSafeBase(
           base::subtle::GetRefCountPreference<ContextProviderCommandBuffer>()),
@@ -75,6 +76,7 @@
       attributes_(std::move(attributes)),
       context_type_(type),
       channel_(std::move(channel)),
+      enable_gpu_rasterization_(enable_gpu_rasterization),
       buffer_mapper_(buffer_mapper) {
   DETACH_FROM_SEQUENCE(context_sequence_checker_);
   DCHECK(channel_);
@@ -163,7 +165,7 @@
       /*automatic_flushes=*/true,
       /*support_locking=*/false, gpu::SharedMemoryLimits::ForWebGPUContext(),
       gpu::mojom::ContextCreationAttribs::NewWebgpu(std::move(attributes)),
-      type, buffer_mapper);
+      type, /*enable_gpu_rasterization=*/false, buffer_mapper);
 }
 
 // static
@@ -180,7 +182,6 @@
     bool enable_gpu_rasterization,
     bool lose_context_when_out_of_memory) {
   auto attributes = gpu::mojom::RasterCreationAttribs::New();
-  attributes->enable_gpu_rasterization = enable_gpu_rasterization;
   attributes->lose_context_when_out_of_memory = lose_context_when_out_of_memory;
 
   return base::MakeRefCounted<ContextProviderCommandBuffer>(
@@ -188,7 +189,7 @@
       stream_id, stream_priority, active_url, automatic_flushes,
       support_locking, memory_limits,
       gpu::mojom::ContextCreationAttribs::NewRaster(std::move(attributes)),
-      type);
+      type, enable_gpu_rasterization);
 }
 
 ContextProviderCommandBuffer::~ContextProviderCommandBuffer() {
@@ -257,8 +258,8 @@
   command_buffer_ = std::make_unique<gpu::CommandBufferProxyImpl>(
       channel_, stream_id_, default_task_runner_, buffer_mapper_);
   bind_result_ = command_buffer_->Initialize(
-      stream_priority_, attributes_.Clone(), active_url_,
-      command_buffer_metrics::ContextTypeToString(context_type_));
+      stream_priority_, attributes_.Clone(), enable_gpu_rasterization_,
+      active_url_, command_buffer_metrics::ContextTypeToString(context_type_));
   if (bind_result_ != gpu::ContextResult::kSuccess) {
     DLOG(ERROR) << "GpuChannelHost failed to create command buffer.";
     command_buffer_metrics::UmaRecordContextInitFailed(context_type_);
diff --git a/services/viz/public/cpp/gpu/context_provider_command_buffer.h b/services/viz/public/cpp/gpu/context_provider_command_buffer.h
index c1a2a8b5..2233f4d9 100644
--- a/services/viz/public/cpp/gpu/context_provider_command_buffer.h
+++ b/services/viz/public/cpp/gpu/context_provider_command_buffer.h
@@ -115,6 +115,7 @@
       const gpu::SharedMemoryLimits& memory_limits,
       gpu::mojom::ContextCreationAttribsPtr attributes,
       command_buffer_metrics::ContextType type,
+      bool enable_gpu_rasterization = false,
       base::SharedMemoryMapper* buffer_mapper = nullptr);
 
   // Virtual for testing.
@@ -230,6 +231,8 @@
 
   base::ObserverList<ContextLostObserver>::Unchecked observers_;
 
+  bool enable_gpu_rasterization_ = false;
+
   // Shared memory mapper used by command buffer proxies created from this
   // provider when creating shared memory mappings.
   // TODO(crbug.com/40837434) remove this member again once users of the command
diff --git a/services/viz/public/mojom/BUILD.gn b/services/viz/public/mojom/BUILD.gn
index 92b5c25..4a055076 100644
--- a/services/viz/public/mojom/BUILD.gn
+++ b/services/viz/public/mojom/BUILD.gn
@@ -60,13 +60,13 @@
   }
 
   public_deps = [
-    ":shared_image_format",
     ":surfaces",
     "//cc/mojom:mojom_browser_controls",
     "//cc/mojom:mojom_layer_trees",
     "//gpu/ipc/common:interfaces",
     "//media/mojo/mojom:video_encode",
     "//mojo/public/mojom/base",
+    "//services/viz/public/mojom/compositing:shared_image_format",
     "//services/viz/public/mojom/compositing/internal:singleplanar_format",
     "//skia/public/mojom",
     "//third_party/blink/public/mojom/tokens",
@@ -709,29 +709,3 @@
     "//ui/gfx/geometry/mojom",
   ]
 }
-
-mojom("shared_image_format") {
-  generate_java = true
-  sources = [ "compositing/shared_image_format.mojom" ]
-  enabled_features = []
-  if (use_ozone) {
-    enabled_features += [ "use_ozone" ]
-  }
-  cpp_typemaps = [
-    {
-      types = [
-        {
-          mojom = "viz.mojom.SharedImageFormat"
-          cpp = "::viz::SharedImageFormat"
-        },
-      ]
-      traits_headers = [ "//services/viz/public/cpp/compositing/shared_image_format_mojom_traits.h" ]
-      traits_sources = [ "//services/viz/public/cpp/compositing/shared_image_format_mojom_traits.cc" ]
-      traits_public_deps =
-          [ "//components/viz/common/resources:shared_image_format" ]
-    },
-  ]
-
-  public_deps =
-      [ "//services/viz/public/mojom/compositing/internal:singleplanar_format" ]
-}
diff --git a/services/viz/public/mojom/compositing/BUILD.gn b/services/viz/public/mojom/compositing/BUILD.gn
new file mode 100644
index 0000000..a8073dc3
--- /dev/null
+++ b/services/viz/public/mojom/compositing/BUILD.gn
@@ -0,0 +1,31 @@
+# Copyright 2025 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+import("//build/config/ui.gni")
+import("//mojo/public/tools/bindings/mojom.gni")
+
+mojom("shared_image_format") {
+  generate_java = true
+  sources = [ "shared_image_format.mojom" ]
+  enabled_features = []
+  if (use_ozone) {
+    enabled_features += [ "use_ozone" ]
+  }
+  cpp_typemaps = [
+    {
+      types = [
+        {
+          mojom = "viz.mojom.SharedImageFormat"
+          cpp = "::viz::SharedImageFormat"
+        },
+      ]
+      traits_headers = [ "//services/viz/public/cpp/compositing/shared_image_format_mojom_traits.h" ]
+      traits_sources = [ "//services/viz/public/cpp/compositing/shared_image_format_mojom_traits.cc" ]
+      traits_public_deps =
+          [ "//components/viz/common/resources:shared_image_format" ]
+    },
+  ]
+
+  public_deps =
+      [ "//services/viz/public/mojom/compositing/internal:singleplanar_format" ]
+}
diff --git a/services/viz/public/mojom/compositing/internal/BUILD.gn b/services/viz/public/mojom/compositing/internal/BUILD.gn
index ed4a3c4..aef03521c9 100644
--- a/services/viz/public/mojom/compositing/internal/BUILD.gn
+++ b/services/viz/public/mojom/compositing/internal/BUILD.gn
@@ -14,6 +14,6 @@
   visibility = [
     "//components/viz/common/resources:shared_image_format",
     "//services/viz/public/mojom",
-    "//services/viz/public/mojom:shared_image_format",
+    "//services/viz/public/mojom/compositing:shared_image_format",
   ]
 }
diff --git a/services/viz/public/mojom/compositing/transferable_resource.mojom b/services/viz/public/mojom/compositing/transferable_resource.mojom
index b00b2a2..fae56c7ae 100644
--- a/services/viz/public/mojom/compositing/transferable_resource.mojom
+++ b/services/viz/public/mojom/compositing/transferable_resource.mojom
@@ -15,6 +15,15 @@
 import "skia/public/mojom/image_info.mojom";
 import "skia/public/mojom/surface_origin.mojom";
 
+// See viz::TransferableResource::MetadataOverride in
+// components/viz/common/resources/transferable_resource.h
+struct MetadataOverride {
+  bool? is_overlay_candidate;
+  gfx.mojom.ColorSpace? color_space;
+  skia.mojom.SurfaceOrigin? origin;
+  skia.mojom.AlphaType? alpha_type;
+};
+
 // See viz::TransferableResource::SynchronizationType in
 // components/viz/common/resources/transferable_resource.h
 enum SynchronizationType {
diff --git a/services/webnn/ort/context_impl_ort.cc b/services/webnn/ort/context_impl_ort.cc
index a751cbb..77bde2e 100644
--- a/services/webnn/ort/context_impl_ort.cc
+++ b/services/webnn/ort/context_impl_ort.cc
@@ -407,7 +407,7 @@
 
   return base::MakeRefCounted<TensorImplOrt>(
       std::move(receiver), AsWeakPtr(), std::move(tensor_info), size,
-      std::move(tensor), can_access_on_cpu);
+      std::move(tensor), can_access_on_cpu, device_allocator_);
 }
 
 base::expected<scoped_refptr<WebNNTensorImpl>, mojom::ErrorPtr>
diff --git a/services/webnn/ort/tensor_impl_ort.cc b/services/webnn/ort/tensor_impl_ort.cc
index 6df24b2..7d6744e 100644
--- a/services/webnn/ort/tensor_impl_ort.cc
+++ b/services/webnn/ort/tensor_impl_ort.cc
@@ -22,10 +22,12 @@
     mojom::TensorInfoPtr tensor_info,
     size_t size,
     ScopedOrtValue tensor,
-    bool can_access_on_cpu)
+    bool can_access_on_cpu,
+    scoped_refptr<DeviceAllocator> device_allocator)
     : WebNNTensorImpl(std::move(receiver),
                       std::move(context),
                       std::move(tensor_info)),
+      device_allocator_((std::move(device_allocator))),
       tensor_(std::move(tensor)),
       size_(size) {
   // Initialize the tensor with zeros, otherwise, reading uninitialized memory
diff --git a/services/webnn/ort/tensor_impl_ort.h b/services/webnn/ort/tensor_impl_ort.h
index a064ecf..20a8044 100644
--- a/services/webnn/ort/tensor_impl_ort.h
+++ b/services/webnn/ort/tensor_impl_ort.h
@@ -7,6 +7,7 @@
 
 #include "base/memory/scoped_refptr.h"
 #include "base/sequence_checker.h"
+#include "services/webnn/ort/device_allocator.h"
 #include "services/webnn/ort/scoped_ort_types.h"
 #include "services/webnn/public/mojom/webnn_tensor.mojom-forward.h"
 #include "services/webnn/webnn_context_impl.h"
@@ -22,7 +23,8 @@
                 mojom::TensorInfoPtr tensor_info,
                 size_t size,
                 ScopedOrtValue tensor,
-                bool can_access_on_cpu);
+                bool can_access_on_cpu,
+                scoped_refptr<DeviceAllocator> device_allocator);
 
   TensorImplOrt(const TensorImplOrt&) = delete;
   TensorImplOrt& operator=(const TensorImplOrt&) = delete;
@@ -40,6 +42,14 @@
 
   base::span<uint8_t> AsSpan() const;
 
+  // The device allocator used for device tensor creation. May be nullptr if
+  // device tensor is not supported.
+  // If the device allocator is present, the tensor is allocated by the device
+  // allocator, and its destruction depends on the allocator remaining valid.
+  // Therefore, the device allocator must be referenced by `TensorImplOrt`
+  // and declared before `tensor_` to ensure correct destruction order to avoid
+  // use-after-free errors.
+  scoped_refptr<DeviceAllocator> device_allocator_;
   const ScopedOrtValue tensor_ GUARDED_BY_CONTEXT(gpu_sequence_checker_);
   const size_t size_;
 };
diff --git a/services/webnn/webnn_context_impl.cc b/services/webnn/webnn_context_impl.cc
index 2fc618a2..8edf23e1 100644
--- a/services/webnn/webnn_context_impl.cc
+++ b/services/webnn/webnn_context_impl.cc
@@ -8,6 +8,7 @@
 #include <utility>
 
 #include "base/atomic_sequence_num.h"
+#include "base/functional/callback_helpers.h"
 #include "base/sequence_checker.h"
 #include "base/task/bind_post_task.h"
 #include "gpu/command_buffer/service/scheduler.h"
diff --git a/services/webnn/webnn_test_environment.h b/services/webnn/webnn_test_environment.h
index 079c2af23..2f3adadf 100644
--- a/services/webnn/webnn_test_environment.h
+++ b/services/webnn/webnn_test_environment.h
@@ -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/functional/callback_helpers.h"
 #include "gpu/command_buffer/service/scheduler.h"
 #include "services/webnn/webnn_context_provider_impl.h"
 
diff --git a/skia/BUILD.gn b/skia/BUILD.gn
index 23635270..4569053 100644
--- a/skia/BUILD.gn
+++ b/skia/BUILD.gn
@@ -466,11 +466,6 @@
   if (is_fuchsia) {
     public += skia_ports_fontmgr_fuchsia_public
     sources += skia_ports_fontmgr_fuchsia_sources
-
-    # This is already here if use_blink is set
-    if (!use_blink) {
-      sources += skia_ports_fontmgr_custom_sources
-    }
     deps += [
       "//third_party/fuchsia-sdk/sdk/fidl/fuchsia.fonts:fuchsia.fonts_hlcpp",
       "//third_party/fuchsia-sdk/sdk/fidl/fuchsia.io:fuchsia.io_hlcpp",
diff --git a/storage/browser/quota/quota_database.cc b/storage/browser/quota/quota_database.cc
index d1609af..426a95a 100644
--- a/storage/browser/quota/quota_database.cc
+++ b/storage/browser/quota/quota_database.cc
@@ -283,7 +283,6 @@
         "FROM buckets "
         "WHERE storage_key = ? AND name = ?";
   // clang-format on
-  last_operation_ = "GetBucket";
   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
   statement.BindString(0, storage_key.Serialize());
   statement.BindString(1, bucket_name);
@@ -307,7 +306,6 @@
         "WHERE id = ? "
         "RETURNING " BUCKET_INFO_FIELDS_SELECTOR;
   // clang-format on
-  last_operation_ = "UpdateBucketExpiration";
   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
   statement.BindTime(0, expiration);
   statement.BindInt64(1, bucket.value());
@@ -332,7 +330,6 @@
         "WHERE id = ? "
         "RETURNING " BUCKET_INFO_FIELDS_SELECTOR;
   // clang-format on
-  last_operation_ = "UpdateBucketPersistence";
   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
   statement.BindBool(0, persistent);
   statement.BindInt64(1, bucket.value());
@@ -354,7 +351,6 @@
         "FROM buckets "
         "WHERE id = ?";
   // clang-format on
-  last_operation_ = "GetBucketById";
   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
   statement.BindInt64(0, bucket_id.value());
 
@@ -373,7 +369,6 @@
       "SELECT " BUCKET_INFO_FIELDS_SELECTOR
         "FROM buckets ";
   // clang-format on
-  last_operation_ = "GetAllBuckets";
   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
 
   return BucketInfosFromSqlStatement(statement);
@@ -393,7 +388,6 @@
         "FROM buckets "
         "WHERE host = ?";
   // clang-format on
-  last_operation_ = "GetBucketsForHost";
   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
   statement.BindString(0, host);
 
@@ -414,7 +408,6 @@
         "FROM buckets "
         "WHERE storage_key = ?";
   // clang-format on
-  last_operation_ = "GetBucketsForStorageKey";
   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
   statement.BindString(0, storage_key.Serialize());
 
@@ -435,7 +428,6 @@
       "SELECT last_accessed FROM buckets "
         "WHERE storage_key = ? AND name = ?";
   // clang-format on
-  last_operation_ = "ReadStorageKeyLastAccessTime";
   sql::Statement statement_read(
       db_->GetCachedStatement(SQL_FROM_HERE, kSqlReadLastAccessed));
   statement_read.BindString(0, storage_key.Serialize());
@@ -462,7 +454,6 @@
         "SET use_count = use_count + 1, last_accessed = ? "
         "WHERE storage_key = ? AND name = ?";
   // clang-format on
-  last_operation_ = "SetStorageKeyLastAccessTime";
   sql::Statement statement_set(
       db_->GetCachedStatement(SQL_FROM_HERE, kSqlSetLastAccessed));
   statement_set.BindTime(0, last_accessed);
@@ -492,7 +483,6 @@
         "SET use_count = use_count + 1, last_accessed = ? "
         "WHERE id = ?";
   // clang-format on
-  last_operation_ = "SetBucketLastAccessTime";
   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
   statement.BindTime(0, last_accessed);
   statement.BindInt64(1, bucket_id.value());
@@ -516,7 +506,6 @@
 
   static constexpr char kSql[] =
       "UPDATE buckets SET last_modified = ? WHERE id = ?";
-  last_operation_ = "SetBucketLastModifiedTime";
   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
   statement.BindTime(0, last_modified);
   statement.BindInt64(1, bucket_id.value());
@@ -540,7 +529,6 @@
   for (const auto& storage_key : storage_keys) {
     static constexpr char kSql[] =
         "INSERT OR IGNORE INTO buckets" BUCKETS_FIELDS_INSERTER;
-    last_operation_ = "BootstrapDefaultBucket";
     sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
     BucketInitParams init_params =
         BucketInitParams::ForDefaultBucket(storage_key);
@@ -609,7 +597,6 @@
   static constexpr char kSql[] =
       "DELETE FROM buckets WHERE id = ? "
       "RETURNING " BUCKET_TABLE_ENTRY_FIELDS_SELECTOR;
-  last_operation_ = "DeleteBucket";
   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
   statement.BindInt64(0, bucket.id.value());
 
@@ -656,8 +643,6 @@
         "WHERE persistent = 0 "
         "ORDER BY last_accessed";
   // clang-format on
-  last_operation_ = "GetBucketsForEviction";
-
   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
 
   // The total space used by all buckets marked for eviction.
@@ -708,8 +693,6 @@
   }
 
   static constexpr char kSql[] = "SELECT DISTINCT storage_key FROM buckets";
-  last_operation_ = "GetStorageKeys";
-
   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
 
   std::set<StorageKey> storage_keys;
@@ -740,8 +723,6 @@
       "SELECT id, storage_key, name FROM buckets "
         "WHERE last_modified >= ? AND last_modified < ?";
   // clang-format on
-  last_operation_ = "GetBucketsModifiedBetween";
-
   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
   statement.BindTime(0, begin);
   statement.BindTime(1, end);
@@ -779,8 +760,6 @@
           "FROM buckets "
           "WHERE expiration > 0 AND expiration < ?";
     // clang-format on
-    last_operation_ = "GetExpired";
-
     sql::Statement statement(
         db_->GetCachedStatement(SQL_FROM_HERE, kSqlExpired));
     statement.BindTime(0, GetNow());
@@ -798,8 +777,6 @@
         "      (storage_key REGEXP '.*\\^(1|4).*' AND "
         "       last_accessed < ? AND last_modified < ?)";
   // clang-format on
-  last_operation_ = "GetExpiredAndOrphanAndStale";
-
   sql::Statement statement(
       db_->GetCachedStatement(SQL_FROM_HERE, kSqlExpiredAndStaleAndOrphan));
   base::Time expiration_cutoff = GetNow();
@@ -955,7 +932,6 @@
     timer_.Stop();
   }
 
-  last_operation_ = "Commit";
   DCHECK_EQ(1, db_->transaction_nesting());
   db_->CommitTransactionDeprecated();
   DCHECK_EQ(0, db_->transaction_nesting());
@@ -1036,15 +1012,6 @@
   sql::IsErrorCatastrophic(sqlite_error_code);
   sqlite_error_code_ = sqlite_error_code;
 
-  // Don't log UMA twice if the same operation manages to cause more than one
-  // error (this can happen in particular when opening a database).
-  if (last_operation_) {
-    sql::UmaHistogramSqliteResult(
-        std::string("Quota.DatabaseSpecificError.") + *last_operation_,
-        sqlite_error_code);
-    last_operation_.reset();
-  }
-
   if (db_error_callback_) {
     db_error_callback_.Run(sqlite_error_code);
   }
@@ -1079,7 +1046,6 @@
 
 bool QuotaDatabase::OpenDatabase() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  last_operation_ = "Open";
 
   // Open in memory database.
   if (db_file_path_.empty()) {
@@ -1175,7 +1141,6 @@
 bool QuotaDatabase::CreateTable(const TableSchema& table) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
-  last_operation_ = "CreateTable";
   std::string sql("CREATE TABLE ");
   sql += table.table_name;
   sql += table.columns;
@@ -1290,7 +1255,6 @@
           "FROM buckets "
           "WHERE storage_key = ?";
     // clang-format on
-    last_operation_ = "CountBuckets";
     sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
     statement.BindString(0, params.storage_key.Serialize());
 
@@ -1312,10 +1276,8 @@
       "INSERT INTO buckets " BUCKETS_FIELDS_INSERTER
         " RETURNING " BUCKET_INFO_FIELDS_SELECTOR;
   // clang-format on
-  last_operation_ = "CreateBucket";
-
-  const base::Time now = GetNow();
   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
+  const base::Time now = GetNow();
   BindBucketInitParamsToInsertStatement(params,
                                         /*use_count=*/0,
                                         /*last_accessed=*/now,
diff --git a/storage/browser/quota/quota_database.h b/storage/browser/quota/quota_database.h
index 7d91e82..3acec11 100644
--- a/storage/browser/quota/quota_database.h
+++ b/storage/browser/quota/quota_database.h
@@ -307,9 +307,6 @@
   static const IndexSchema kIndexes[];
   static const size_t kIndexCount;
 
-  // A descriptor of the last SQL statement that was executed, used for metrics.
-  std::optional<std::string> last_operation_;
-
   base::RepeatingCallback<void(int)> db_error_callback_;
 
   // We need to delay evicting stale buckets until after any session
diff --git a/storage/browser/quota/quota_database_unittest.cc b/storage/browser/quota/quota_database_unittest.cc
index 30eb3064..891135fa 100644
--- a/storage/browser/quota/quota_database_unittest.cc
+++ b/storage/browser/quota/quota_database_unittest.cc
@@ -854,8 +854,6 @@
     expecter.ExpectError(SQLITE_CORRUPT);
     auto db = CreateDatabase(/*is_incognito=*/false);
     ASSERT_TRUE(EnsureOpened(db.get()));
-    histograms.ExpectBucketCount("Quota.DatabaseSpecificError.Open",
-                                 sql::SqliteLoggedResultCode::kCorrupt, 1);
     EXPECT_TRUE(expecter.SawExpectedErrors());
 
     // Ensure no nested transactions after reentrant calls to EnsureOpened()
@@ -995,7 +993,6 @@
     EXPECT_EQ(sqlite_error_code,
               static_cast<int>(sql::SqliteResultCode::kCorrupt));
   }
-  histograms.ExpectTotalCount("Quota.DatabaseSpecificError.GetBucket", 1);
 }
 
 TEST_P(QuotaDatabaseTest, Expiration) {
diff --git a/testing/buildbot/filters/BUILD.gn b/testing/buildbot/filters/BUILD.gn
index 1bed82f..40b3e459 100644
--- a/testing/buildbot/filters/BUILD.gn
+++ b/testing/buildbot/filters/BUILD.gn
@@ -386,7 +386,6 @@
     "//testing/buildbot/filters/android.samsung_a13.gl_tests.filter",
     "//testing/buildbot/filters/android.samsung_a23.gl_tests.filter",
     "//testing/buildbot/filters/linux.intel.arc_b570.gl_tests_passthrough.filter",
-    "//testing/buildbot/filters/linux.swiftshader.tsan.gl_tests_passthrough.filter",
     "//testing/buildbot/filters/linux.uhd_630.gl_tests_passthrough.filter",
     "//testing/buildbot/filters/linux.uhd_770.gl_tests_passthrough.filter",
     "//testing/buildbot/filters/win.amd.7600.gl_tests_passthrough.filter",
diff --git a/testing/buildbot/filters/linux.swiftshader.tsan.gl_tests_passthrough.filter b/testing/buildbot/filters/linux.swiftshader.tsan.gl_tests_passthrough.filter
deleted file mode 100644
index f796aed..0000000
--- a/testing/buildbot/filters/linux.swiftshader.tsan.gl_tests_passthrough.filter
+++ /dev/null
@@ -1,33 +0,0 @@
-# crbug.com/462477487
--ExternalVkImageBackingFactoryWithFormatTest.Basic/*
--ExternalVkImageBackingFactoryWithFormatTest.ReadbackToMemory/*
--ExternalVkImageBackingFactoryWithFormatTest.Upload/*
-
-# crbug.com/329482486
--TranslatorVariants/EXTBlendFuncExtendedDrawTest.ESSL1FragColor/0
--TranslatorVariants/EXTBlendFuncExtendedDrawTest.ESSL1FragColor/1
--TranslatorVariants/EXTBlendFuncExtendedDrawTest.ESSL1FragData/0
--TranslatorVariants/EXTBlendFuncExtendedDrawTest.ESSL1FragData/1
--TranslatorVariants/EXTBlendFuncExtendedES3DrawTest.ES3Getters/0
--TranslatorVariants/EXTBlendFuncExtendedES3DrawTest.ES3Getters/1
--TranslatorVariants/EXTBlendFuncExtendedES3DrawTest.ESSL3BindArrayAsArray/0
--TranslatorVariants/EXTBlendFuncExtendedES3DrawTest.ESSL3BindArrayAsArray/1
--TranslatorVariants/EXTBlendFuncExtendedES3DrawTest.ESSL3BindArrayWithSimpleName/0
--TranslatorVariants/EXTBlendFuncExtendedES3DrawTest.ESSL3BindArrayWithSimpleName/1
--TranslatorVariants/EXTBlendFuncExtendedES3DrawTest.ESSL3BindSimpleVarAsArrayNoBind/0
--TranslatorVariants/EXTBlendFuncExtendedES3DrawTest.ESSL3BindSimpleVarAsArrayNoBind/1
--TranslatorVariants/EXTBlendFuncExtendedES3DrawTest.ESSL3Var/0
--TranslatorVariants/EXTBlendFuncExtendedES3DrawTest.ESSL3Var/1
-
-# crbug.com/462477375
--SharedImageGLBackingProduceDawnTest.Basic
--WebGPUMailboxBufferTest.*
--WebGPUMailboxTextureTest.*
--WebGPUTest.*
-
-# crbug.com/462493879
--GLClearFramebufferTestWithParam/GLClearFramebufferTest.ClearColorWithScissor/*
-
-# crbug.com/462477489
--ExternalVkImageBackingFactoryDawnTest.DawnWrite_SkiaVulkanRead
--ExternalVkImageBackingFactoryDawnTest.SkiaVulkanWrite_DawnRead
\ No newline at end of file
diff --git a/testing/buildbot/generate_buildbot_json.py b/testing/buildbot/generate_buildbot_json.py
index dad303e..7ad9436 100755
--- a/testing/buildbot/generate_buildbot_json.py
+++ b/testing/buildbot/generate_buildbot_json.py
@@ -34,7 +34,6 @@
 
 BROWSER_CONFIG_TO_TARGET_SUFFIX_MAP = {
     'android-chromium': '_android_chrome',
-    'android-chromium-monochrome': '_android_monochrome',
     'android-webview': '_android_webview',
 }
 
diff --git a/testing/buildbot/mixins.pyl b/testing/buildbot/mixins.pyl
index ed6e9511..9643879a 100644
--- a/testing/buildbot/mixins.pyl
+++ b/testing/buildbot/mixins.pyl
@@ -582,6 +582,16 @@
       },
     },
   },
+  'linux_nvidia_gtx_1660_obsolete': {
+    'fail_if_unused': False,
+    'swarming': {
+      'dimensions': {
+        'gpu': '10de:2184-440.100',
+        'os': 'Ubuntu-18.04',
+        'pool': 'chromium.tests.gpu',
+      },
+    },
+  },
   'linux_nvidia_gtx_1660_stable': {
     'fail_if_unused': False,
     'swarming': {
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index e6f537d..fe97ff63 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -2047,6 +2047,26 @@
             ]
         }
     ],
+    "AudioDecoderAudioFileReader": [
+        {
+            "platforms": [
+                "android",
+                "android_webview",
+                "chromeos",
+                "linux",
+                "mac",
+                "windows"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "AudioDecoderAudioFileReader"
+                    ]
+                }
+            ]
+        }
+    ],
     "AudioFocusEnforcementStudy": [
         {
             "platforms": [
@@ -2521,10 +2541,10 @@
                 {
                     "name": "Enabled",
                     "enable_features": [
-                        "SyncAutofillWalletCredentialData",
-                        "kAutofillEnableCvcStorageAndFilling",
-                        "kAutofillEnableCvcStorageAndFillingEnhancement",
-                        "kAutofillEnableCvcStorageAndFillingStandaloneFormEnhancement"
+                        "AutofillEnableCvcStorageAndFilling",
+                        "AutofillEnableCvcStorageAndFillingEnhancement",
+                        "AutofillEnableCvcStorageAndFillingStandaloneFormEnhancement",
+                        "SyncAutofillWalletCredentialData"
                     ]
                 }
             ]
@@ -2747,26 +2767,6 @@
             ]
         }
     ],
-    "AutofillEnableSyntheticSelectMetricsLogging": [
-        {
-            "platforms": [
-                "android",
-                "android_webview",
-                "chromeos",
-                "linux",
-                "mac",
-                "windows"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled",
-                    "enable_features": [
-                        "AutofillEnableSyntheticSelectMetricsLogging"
-                    ]
-                }
-            ]
-        }
-    ],
     "AutofillExtendZipCodeValidation": [
         {
             "platforms": [
@@ -4890,26 +4890,6 @@
             ]
         }
     ],
-    "CheckHTMLParserBudgetLessOften": [
-        {
-            "platforms": [
-                "android",
-                "android_webview",
-                "chromeos",
-                "linux",
-                "mac",
-                "windows"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled_Nonstable_20230320",
-                    "enable_features": [
-                        "CheckHTMLParserBudgetLessOften"
-                    ]
-                }
-            ]
-        }
-    ],
     "ChromeCompose": [
         {
             "platforms": [
@@ -6224,6 +6204,9 @@
             "experiments": [
                 {
                     "name": "Enabled",
+                    "params": {
+                        "upload-chunk-retries": "2"
+                    },
                     "enable_features": [
                         "LensOverlayUploadChunking"
                     ]
@@ -11605,6 +11588,12 @@
             ],
             "experiments": [
                 {
+                    "name": "Control_MultiArm",
+                    "disable_features": [
+                        "GlicEntrypointVariations"
+                    ]
+                },
+                {
                     "name": "Arm2_LabelOnly",
                     "params": {
                         "glic-entrypoint-variations-alt-icon": "false",
@@ -14621,24 +14610,6 @@
             ]
         }
     ],
-    "InfobarPrioritization": [
-        {
-            "platforms": [
-                "chromeos",
-                "linux",
-                "mac",
-                "windows"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled",
-                    "enable_features": [
-                        "InfobarPrioritization"
-                    ]
-                }
-            ]
-        }
-    ],
     "InfobarRefresh": [
         {
             "platforms": [
diff --git a/third_party/angle b/third_party/angle
index a6c4431..c4fe7ab 160000
--- a/third_party/angle
+++ b/third_party/angle
@@ -1 +1 @@
-Subproject commit a6c4431f99175f7dcc7effc7dcad3801aae2585c
+Subproject commit c4fe7abe14469bafce44bc4e9502e84c458d4801
diff --git a/third_party/blink/common/features.cc b/third_party/blink/common/features.cc
index a6f66cf1..dea2e8c3c 100644
--- a/third_party/blink/common/features.cc
+++ b/third_party/blink/common/features.cc
@@ -365,9 +365,6 @@
 // of the renderer eviction reasons for Back/Forward Cache.
 BASE_FEATURE(kCaptureJSExecutionLocation, base::FEATURE_ENABLED_BY_DEFAULT);
 
-BASE_FEATURE(kCheckHTMLParserBudgetLessOften,
-             base::FEATURE_DISABLED_BY_DEFAULT);
-
 BASE_FEATURE(kClearSiteDataPrefetchPrerenderCache,
              base::FEATURE_ENABLED_BY_DEFAULT);
 
diff --git a/third_party/blink/public/common/features.h b/third_party/blink/public/common/features.h
index f98ef27..3562708a 100644
--- a/third_party/blink/public/common/features.h
+++ b/third_party/blink/public/common/features.h
@@ -224,10 +224,6 @@
 
 BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kCaptureJSExecutionLocation);
 
-// If enabled, the HTMLDocumentParser will only check its budget after parsing a
-// commonly slow token or for one out of 10 fast tokens.
-BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kCheckHTMLParserBudgetLessOften);
-
 // If enabled, the Clear-Site-Data header will handle "prefetchCache" and
 // "prerenderCache" to clear the Prefetch and Prerender caches respectively.
 BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kClearSiteDataPrefetchPrerenderCache);
diff --git a/third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom b/third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom
index 827d2734..5d47e6b 100644
--- a/third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom
+++ b/third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom
@@ -5045,6 +5045,7 @@
   kExtendedTextMetrics = 5729,
   kUseCssSizingProperties = 5730,
   kSharedStorageAPIAll = 5731,
+  kGamepadRawInputChangeEventListener = 5732,
 
   // Add new features immediately above this line. Don't change assigned
   // numbers of any item, and don't reuse removed slots. Also don't add extra
diff --git a/third_party/blink/renderer/bindings/core/v8/generated_code_helper.cc b/third_party/blink/renderer/bindings/core/v8/generated_code_helper.cc
index cfa3f928..7d259bfa 100644
--- a/third_party/blink/renderer/bindings/core/v8/generated_code_helper.cc
+++ b/third_party/blink/renderer/bindings/core/v8/generated_code_helper.cc
@@ -74,6 +74,12 @@
          incumbent_execution_context->CanExecuteScripts(kAboutToExecuteScript);
 }
 
+String FormatInvalidEnumValueMessage(StringView value,
+                                     const char* enum_type_name) {
+  return StrCat({"The provided value '", value,
+                 "' is not a valid enum value of type ", enum_type_name, "."});
+}
+
 }  // namespace
 
 bool IsCallbackFunctionRunnable(
@@ -217,8 +223,7 @@
 
   if (!index.has_value()) [[unlikely]] {
     exception_state.ThrowTypeError(
-        StrCat({"The provided value '", str_value,
-                "' is not a valid enum value of type ", enum_type_name, "."}));
+        FormatInvalidEnumValueMessage(str_value, enum_type_name));
   }
   return index;
 }
@@ -237,18 +242,14 @@
 
 void ReportInvalidEnumSetToAttribute(v8::Isolate* isolate,
                                      const String& value,
-                                     const String& enum_type_name,
+                                     const char* enum_type_name,
                                      ExceptionState& exception_state) {
   ScriptState* script_state = ScriptState::ForCurrentRealm(isolate);
   ExecutionContext* execution_context = ExecutionContext::From(script_state);
-
-  String message =
-      StrCat({"The provided value '", value,
-              "' is not a valid enum value of type ", enum_type_name, "."});
-
   execution_context->AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
       mojom::blink::ConsoleMessageSource::kJavaScript,
-      mojom::blink::ConsoleMessageLevel::kWarning, message,
+      mojom::blink::ConsoleMessageLevel::kWarning,
+      FormatInvalidEnumValueMessage(value, enum_type_name),
       CaptureSourceLocation(execution_context)));
 }
 
diff --git a/third_party/blink/renderer/bindings/core/v8/generated_code_helper.h b/third_party/blink/renderer/bindings/core/v8/generated_code_helper.h
index 773bcfa..74c3e0b 100644
--- a/third_party/blink/renderer/bindings/core/v8/generated_code_helper.h
+++ b/third_party/blink/renderer/bindings/core/v8/generated_code_helper.h
@@ -150,7 +150,7 @@
 CORE_EXPORT void ReportInvalidEnumSetToAttribute(
     v8::Isolate* isolate,
     const String& value,
-    const String& enum_type_name,
+    const char* enum_type_name,
     ExceptionState& exception_state);
 
 CORE_EXPORT bool IsEsIterableObject(v8::Isolate* isolate,
diff --git a/third_party/blink/renderer/bindings/core/v8/module_record.cc b/third_party/blink/renderer/bindings/core/v8/module_record.cc
index 9939ad41..9f64880 100644
--- a/third_party/blink/renderer/bindings/core/v8/module_record.cc
+++ b/third_party/blink/renderer/bindings/core/v8/module_record.cc
@@ -159,8 +159,7 @@
 
   for (int i = 0; i < length; ++i) {
     v8::Local<v8::ModuleRequest> v8_module_request =
-        v8_module_requests->Get(script_state->GetContext(), i)
-            .As<v8::ModuleRequest>();
+        v8_module_requests->Get(i).As<v8::ModuleRequest>();
     v8::Local<v8::String> v8_specifier = v8_module_request->GetSpecifier();
     v8::ModuleImportPhase import_phase = v8_module_request->GetPhase();
     TextPosition position = TextPosition::MinimumPosition();
@@ -178,8 +177,7 @@
     }
     Vector<ImportAttribute> import_attributes =
         ModuleRecord::ToBlinkImportAttributes(
-            script_state->GetContext(), record,
-            v8_module_request->GetImportAttributes(),
+            record, v8_module_request->GetImportAttributes(),
             /*v8_import_attributes_has_positions=*/true);
 
     requests.emplace_back(
@@ -207,7 +205,7 @@
   ModuleRequest module_request(ToCoreStringWithNullCheck(isolate, specifier),
                                TextPosition::MinimumPosition(),
                                ModuleRecord::ToBlinkImportAttributes(
-                                   context, referrer, import_attributes,
+                                   referrer, import_attributes,
                                    /*v8_import_attributes_has_positions=*/true),
                                ModuleImportPhase::kEvaluation);
 
@@ -230,7 +228,7 @@
   ModuleRequest module_request(ToCoreStringWithNullCheck(isolate, specifier),
                                TextPosition::MinimumPosition(),
                                ModuleRecord::ToBlinkImportAttributes(
-                                   context, referrer, import_attributes,
+                                   referrer, import_attributes,
                                    /*v8_import_attributes_has_positions=*/true),
                                ModuleImportPhase::kSource);
 
@@ -242,7 +240,6 @@
 }
 
 Vector<ImportAttribute> ModuleRecord::ToBlinkImportAttributes(
-    v8::Local<v8::Context> context,
     v8::Local<v8::Module> record,
     v8::Local<v8::FixedArray> v8_import_attributes,
     bool v8_import_attributes_has_positions) {
@@ -260,15 +257,14 @@
   import_attributes.ReserveInitialCapacity(number_of_import_attributes);
   for (int i = 0; i < number_of_import_attributes; ++i) {
     v8::Local<v8::String> v8_attribute_key =
-        v8_import_attributes->Get(context, i * kV8AttributeEntrySize)
-            .As<v8::String>();
+        v8_import_attributes->Get(i * kV8AttributeEntrySize).As<v8::String>();
     v8::Local<v8::String> v8_attribute_value =
-        v8_import_attributes->Get(context, (i * kV8AttributeEntrySize) + 1)
+        v8_import_attributes->Get((i * kV8AttributeEntrySize) + 1)
             .As<v8::String>();
     TextPosition attribute_position = TextPosition::MinimumPosition();
     if (v8_import_attributes_has_positions) {
       int32_t v8_attribute_source_offset =
-          v8_import_attributes->Get(context, (i * kV8AttributeEntrySize) + 2)
+          v8_import_attributes->Get((i * kV8AttributeEntrySize) + 2)
               .As<v8::Int32>()
               ->Value();
       v8::Location v8_attribute_loc =
diff --git a/third_party/blink/renderer/bindings/core/v8/module_record.h b/third_party/blink/renderer/bindings/core/v8/module_record.h
index a85f6c7..51237be 100644
--- a/third_party/blink/renderer/bindings/core/v8/module_record.h
+++ b/third_party/blink/renderer/bindings/core/v8/module_record.h
@@ -86,7 +86,6 @@
   // expects [key1, value1, key2, value2, ...] encoding used in the
   // |HostImportModuleDynamically| callback.
   static Vector<ImportAttribute> ToBlinkImportAttributes(
-      v8::Local<v8::Context> context,
       v8::Local<v8::Module> record,
       v8::Local<v8::FixedArray> v8_import_attributes,
       bool v8_import_attributes_has_positions);
diff --git a/third_party/blink/renderer/bindings/core/v8/v8_initializer.cc b/third_party/blink/renderer/bindings/core/v8/v8_initializer.cc
index b55ebf73..d2affd3f 100644
--- a/third_party/blink/renderer/bindings/core/v8/v8_initializer.cc
+++ b/third_party/blink/renderer/bindings/core/v8/v8_initializer.cc
@@ -766,8 +766,8 @@
   ModuleRequest module_request(
       specifier, TextPosition::MinimumPosition(),
       ModuleRecord::ToBlinkImportAttributes(
-          script_state->GetContext(), v8::Local<v8::Module>(),
-          v8_import_attributes, /*v8_import_attributes_has_positions=*/false),
+          v8::Local<v8::Module>(), v8_import_attributes,
+          /*v8_import_attributes_has_positions=*/false),
       import_phase);
 
   auto* resolver = MakeGarbageCollected<ScriptPromiseResolver<IDLAny>>(
diff --git a/third_party/blink/renderer/bindings/generated_in_core.gni b/third_party/blink/renderer/bindings/generated_in_core.gni
index e6602c8..3fb7d75 100644
--- a/third_party/blink/renderer/bindings/generated_in_core.gni
+++ b/third_party/blink/renderer/bindings/generated_in_core.gni
@@ -858,6 +858,8 @@
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_css_mixin_rule.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_css_namespace_rule.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_css_namespace_rule.h",
+  "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_css_navigation_rule.cc",
+  "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_css_navigation_rule.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_css_nested_declarations.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_css_nested_declarations.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_css_numeric_array.cc",
@@ -882,8 +884,6 @@
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_css_rgb.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_css_rotate.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_css_rotate.h",
-  "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_css_route_rule.cc",
-  "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_css_route_rule.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_css_rule.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_css_rule.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_css_rule_list.cc",
diff --git a/third_party/blink/renderer/bindings/generated_in_modules.gni b/third_party/blink/renderer/bindings/generated_in_modules.gni
index b835154..9010e57 100644
--- a/third_party/blink/renderer/bindings/generated_in_modules.gni
+++ b/third_party/blink/renderer/bindings/generated_in_modules.gni
@@ -460,6 +460,8 @@
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_gamepad_effect_parameters.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_gamepad_event_init.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_gamepad_event_init.h",
+  "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_gamepad_raw_input_change_event_init.cc",
+  "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_gamepad_raw_input_change_event_init.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_get_notification_options.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_get_notification_options.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_google_play_billing_method_data.cc",
@@ -2318,6 +2320,8 @@
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_gamepad_event.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_gamepad_haptic_actuator.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_gamepad_haptic_actuator.h",
+  "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_gamepad_raw_input_change_event.cc",
+  "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_gamepad_raw_input_change_event.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_gamepad_touch.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_gamepad_touch.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_gpu.cc",
diff --git a/third_party/blink/renderer/bindings/idl_in_core.gni b/third_party/blink/renderer/bindings/idl_in_core.gni
index 7e80245..c502f4c 100644
--- a/third_party/blink/renderer/bindings/idl_in_core.gni
+++ b/third_party/blink/renderer/bindings/idl_in_core.gni
@@ -59,12 +59,12 @@
   "//third_party/blink/renderer/core/css/css_media_rule.idl",
   "//third_party/blink/renderer/core/css/css_mixin_rule.idl",
   "//third_party/blink/renderer/core/css/css_namespace_rule.idl",
+  "//third_party/blink/renderer/core/css/css_navigation_rule.idl",
   "//third_party/blink/renderer/core/css/css_nested_declarations.idl",
   "//third_party/blink/renderer/core/css/css_page_rule.idl",
   "//third_party/blink/renderer/core/css/css_position_try_descriptors.idl",
   "//third_party/blink/renderer/core/css/css_position_try_rule.idl",
   "//third_party/blink/renderer/core/css/css_property_rule.idl",
-  "//third_party/blink/renderer/core/css/css_route_rule.idl",
   "//third_party/blink/renderer/core/css/css_rule.idl",
   "//third_party/blink/renderer/core/css/css_rule_list.idl",
   "//third_party/blink/renderer/core/css/css_scope_rule.idl",
diff --git a/third_party/blink/renderer/bindings/idl_in_modules.gni b/third_party/blink/renderer/bindings/idl_in_modules.gni
index 03e7ef6..8af8798 100644
--- a/third_party/blink/renderer/bindings/idl_in_modules.gni
+++ b/third_party/blink/renderer/bindings/idl_in_modules.gni
@@ -339,6 +339,8 @@
   "//third_party/blink/renderer/modules/gamepad/gamepad_event.idl",
   "//third_party/blink/renderer/modules/gamepad/gamepad_event_init.idl",
   "//third_party/blink/renderer/modules/gamepad/gamepad_haptic_actuator.idl",
+  "//third_party/blink/renderer/modules/gamepad/gamepad_raw_input_change_event.idl",
+  "//third_party/blink/renderer/modules/gamepad/gamepad_raw_input_change_event_init.idl",
   "//third_party/blink/renderer/modules/gamepad/gamepad_touch.idl",
   "//third_party/blink/renderer/modules/gamepad/navigator_gamepad.idl",
   "//third_party/blink/renderer/modules/gamepad/window_gamepad.idl",
diff --git a/third_party/blink/renderer/core/css/build.gni b/third_party/blink/renderer/core/css/build.gni
index 25d7e163..2e1321c1 100644
--- a/third_party/blink/renderer/core/css/build.gni
+++ b/third_party/blink/renderer/core/css/build.gni
@@ -234,6 +234,8 @@
   "css_function_descriptors.h",
   "css_function_rule.cc",
   "css_function_rule.h",
+  "css_navigation_rule.cc",
+  "css_navigation_rule.h",
   "css_numeric_literal_value.cc",
   "css_numeric_literal_value.h",
   "css_origin_clean.h",
@@ -294,8 +296,6 @@
   "css_revert_rule_value.h",
   "css_revert_value.cc",
   "css_revert_value.h",
-  "css_route_rule.cc",
-  "css_route_rule.h",
   "css_rule.cc",
   "css_rule.h",
   "css_rule_list.h",
@@ -564,6 +564,8 @@
   "mixin_map.cc",
   "mixin_map.h",
   "native_paint_image_generator.h",
+  "navigation_query.cc",
+  "navigation_query.h",
   "offscreen_font_selector.cc",
   "offscreen_font_selector.h",
   "out_of_flow_data.cc",
@@ -625,8 +627,8 @@
   "parser/font_variant_alternates_parser.h",
   "parser/media_query_parser.cc",
   "parser/media_query_parser.h",
-  "parser/route_parser.cc",
-  "parser/route_parser.h",
+  "parser/navigation_parser.cc",
+  "parser/navigation_parser.h",
   "parser/sizes_attribute_parser.cc",
   "parser/sizes_attribute_parser.h",
   "parser/sizes_math_function_parser.cc",
@@ -724,8 +726,6 @@
   "resolver/viewport_style_resolver.cc",
   "resolver/viewport_style_resolver.h",
   "quiet_mutation_scope.h",
-  "route_query.cc",
-  "route_query.h",
   "rule_feature_set.cc",
   "rule_feature_set.h",
   "rule_set.cc",
diff --git a/third_party/blink/renderer/core/css/conditional_exp_node.h b/third_party/blink/renderer/core/css/conditional_exp_node.h
index 3bfd82f..fe30f77 100644
--- a/third_party/blink/renderer/core/css/conditional_exp_node.h
+++ b/third_party/blink/renderer/core/css/conditional_exp_node.h
@@ -18,7 +18,7 @@
 class ConditionalExpNodeUnknown;
 class MediaQueryFeatureExpNode;
 class MediaQuerySet;
-class RouteQueryExpNode;
+class NavigationExpNode;
 
 // Visitor and evaluation handler for leaf expression nodes, and contents of
 // such leaves, and functions. Will visit in tree order. Ancestor compound
@@ -28,7 +28,7 @@
 // overrides.
 class ConditionalExpNodeVisitor {
  public:
-  virtual KleeneValue EvaluateRouteQueryExpNode(const RouteQueryExpNode&) {
+  virtual KleeneValue EvaluateNavigationExpNode(const NavigationExpNode&) {
     return KleeneValue::kUnknown;
   }
   virtual KleeneValue EvaluateMediaQueryFeatureExpNode(
diff --git a/third_party/blink/renderer/core/css/css_grouping_rule.h b/third_party/blink/renderer/core/css/css_grouping_rule.h
index 1b7d783..f4468f89 100644
--- a/third_party/blink/renderer/core/css/css_grouping_rule.h
+++ b/third_party/blink/renderer/core/css/css_grouping_rule.h
@@ -136,7 +136,7 @@
       case CSSRule::kLayerBlockRule:
       case CSSRule::kMixinRule:
       case CSSRule::kPageRule:
-      case CSSRule::kRouteRule:
+      case CSSRule::kNavigationRule:
       case CSSRule::kScopeRule:
       case CSSRule::kStartingStyleRule:
         return true;
diff --git a/third_party/blink/renderer/core/css/css_navigation_rule.cc b/third_party/blink/renderer/core/css/css_navigation_rule.cc
new file mode 100644
index 0000000..c501f00
--- /dev/null
+++ b/third_party/blink/renderer/core/css/css_navigation_rule.cc
@@ -0,0 +1,39 @@
+// 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 "third_party/blink/renderer/core/css/css_navigation_rule.h"
+
+#include "third_party/blink/renderer/core/css/conditional_exp_node.h"
+#include "third_party/blink/renderer/core/css/navigation_query.h"
+#include "third_party/blink/renderer/core/css/style_rule.h"
+
+namespace blink {
+
+CSSNavigationRule::CSSNavigationRule(StyleRuleNavigation* navigation_rule,
+                                     CSSStyleSheet* parent)
+    : CSSConditionRule(navigation_rule, parent),
+      navigation_rule_(navigation_rule) {}
+
+CSSNavigationRule::~CSSNavigationRule() = default;
+
+String CSSNavigationRule::cssText() const {
+  StringBuilder result;
+  result.Append("@navigation ");
+  navigation_rule_->GetNavigationQuery().GetRootExp()->SerializeTo(result);
+  AppendCSSTextForItems(result);
+  return result.ToString();
+}
+
+void CSSNavigationRule::Reattach(StyleRuleBase* rule) {
+  DCHECK(rule);
+  navigation_rule_ = To<StyleRuleNavigation>(rule);
+  CSSConditionRule::Reattach(rule);
+}
+
+void CSSNavigationRule::Trace(Visitor* visitor) const {
+  visitor->Trace(navigation_rule_);
+  CSSConditionRule::Trace(visitor);
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/core/css/css_navigation_rule.h b/third_party/blink/renderer/core/css/css_navigation_rule.h
new file mode 100644
index 0000000..b96911e4
--- /dev/null
+++ b/third_party/blink/renderer/core/css/css_navigation_rule.h
@@ -0,0 +1,44 @@
+// 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 THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSS_NAVIGATION_RULE_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSS_NAVIGATION_RULE_H_
+
+#include "third_party/blink/renderer/core/css/css_condition_rule.h"
+#include "third_party/blink/renderer/platform/heap/member.h"
+#include "third_party/blink/renderer/platform/wtf/casting.h"
+
+namespace blink {
+
+class CSSStyleSheet;
+class StyleRuleNavigation;
+
+class CORE_EXPORT CSSNavigationRule final : public CSSConditionRule {
+  DEFINE_WRAPPERTYPEINFO();
+
+ public:
+  CSSNavigationRule(StyleRuleNavigation*, CSSStyleSheet*);
+  ~CSSNavigationRule() override;
+
+  String cssText() const override;
+  void Reattach(StyleRuleBase*) override;
+
+  void Trace(Visitor*) const override;
+
+ private:
+  CSSRule::Type GetType() const override { return kNavigationRule; }
+
+  Member<StyleRuleNavigation> navigation_rule_;
+};
+
+template <>
+struct DowncastTraits<CSSNavigationRule> {
+  static bool AllowFrom(const CSSRule& rule) {
+    return rule.GetType() == CSSRule::kNavigationRule;
+  }
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSS_NAVIGATION_RULE_H_
diff --git a/third_party/blink/renderer/core/css/css_route_rule.idl b/third_party/blink/renderer/core/css/css_navigation_rule.idl
similarity index 61%
rename from third_party/blink/renderer/core/css/css_route_rule.idl
rename to third_party/blink/renderer/core/css/css_navigation_rule.idl
index fc18fb11..d8ad37e1 100644
--- a/third_party/blink/renderer/core/css/css_route_rule.idl
+++ b/third_party/blink/renderer/core/css/css_navigation_rule.idl
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-// TODO(crbug.com/436805487): Link to spec, once it surfaces.
+// https://drafts.csswg.org/css-navigation-1/#at-ruledef-navigation
 
 [Exposed=Window, RuntimeEnabled=RouteMatching]
-interface CSSRouteRule : CSSConditionRule {};
+interface CSSNavigationRule : CSSConditionRule {};
diff --git a/third_party/blink/renderer/core/css/css_route_rule.cc b/third_party/blink/renderer/core/css/css_route_rule.cc
deleted file mode 100644
index d605cf7..0000000
--- a/third_party/blink/renderer/core/css/css_route_rule.cc
+++ /dev/null
@@ -1,37 +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 "third_party/blink/renderer/core/css/css_route_rule.h"
-
-#include "third_party/blink/renderer/core/css/conditional_exp_node.h"
-#include "third_party/blink/renderer/core/css/route_query.h"
-#include "third_party/blink/renderer/core/css/style_rule.h"
-
-namespace blink {
-
-CSSRouteRule::CSSRouteRule(StyleRuleRoute* route_rule, CSSStyleSheet* parent)
-    : CSSConditionRule(route_rule, parent), route_rule_(route_rule) {}
-
-CSSRouteRule::~CSSRouteRule() = default;
-
-String CSSRouteRule::cssText() const {
-  StringBuilder result;
-  result.Append("@route ");
-  route_rule_->GetRouteQuery().GetRootExp()->SerializeTo(result);
-  AppendCSSTextForItems(result);
-  return result.ToString();
-}
-
-void CSSRouteRule::Reattach(StyleRuleBase* rule) {
-  DCHECK(rule);
-  route_rule_ = To<StyleRuleRoute>(rule);
-  CSSConditionRule::Reattach(rule);
-}
-
-void CSSRouteRule::Trace(Visitor* visitor) const {
-  visitor->Trace(route_rule_);
-  CSSConditionRule::Trace(visitor);
-}
-
-}  // namespace blink
diff --git a/third_party/blink/renderer/core/css/css_route_rule.h b/third_party/blink/renderer/core/css/css_route_rule.h
deleted file mode 100644
index b1cd62b..0000000
--- a/third_party/blink/renderer/core/css/css_route_rule.h
+++ /dev/null
@@ -1,44 +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 THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSS_ROUTE_RULE_H_
-#define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSS_ROUTE_RULE_H_
-
-#include "third_party/blink/renderer/core/css/css_condition_rule.h"
-#include "third_party/blink/renderer/platform/heap/member.h"
-#include "third_party/blink/renderer/platform/wtf/casting.h"
-
-namespace blink {
-
-class CSSStyleSheet;
-class StyleRuleRoute;
-
-class CORE_EXPORT CSSRouteRule final : public CSSConditionRule {
-  DEFINE_WRAPPERTYPEINFO();
-
- public:
-  CSSRouteRule(StyleRuleRoute*, CSSStyleSheet*);
-  ~CSSRouteRule() override;
-
-  String cssText() const override;
-  void Reattach(StyleRuleBase*) override;
-
-  void Trace(Visitor*) const override;
-
- private:
-  CSSRule::Type GetType() const override { return kRouteRule; }
-
-  Member<StyleRuleRoute> route_rule_;
-};
-
-template <>
-struct DowncastTraits<CSSRouteRule> {
-  static bool AllowFrom(const CSSRule& rule) {
-    return rule.GetType() == CSSRule::kRouteRule;
-  }
-};
-
-}  // namespace blink
-
-#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CSS_ROUTE_RULE_H_
diff --git a/third_party/blink/renderer/core/css/css_rule.h b/third_party/blink/renderer/core/css/css_rule.h
index 1cfadcc..73d63c3 100644
--- a/third_party/blink/renderer/core/css/css_rule.h
+++ b/third_party/blink/renderer/core/css/css_rule.h
@@ -83,10 +83,10 @@
     kLayerBlockRule,
     kLayerStatementRule,
     kMixinRule,
+    kNavigationRule,
     kNestedDeclarationsRule,
     kPositionTryRule,
     kPropertyRule,
-    kRouteRule,
     kScopeRule,
     kStartingStyleRule,
     kViewTransitionRule,
diff --git a/third_party/blink/renderer/core/css/css_selector.cc b/third_party/blink/renderer/core/css/css_selector.cc
index abb7a29..acec8c85 100644
--- a/third_party/blink/renderer/core/css/css_selector.cc
+++ b/third_party/blink/renderer/core/css/css_selector.cc
@@ -35,10 +35,10 @@
 #include "style_rule.h"
 #include "third_party/blink/renderer/core/css/css_markup.h"
 #include "third_party/blink/renderer/core/css/css_selector_list.h"
+#include "third_party/blink/renderer/core/css/navigation_query.h"
 #include "third_party/blink/renderer/core/css/parser/css_parser_context.h"
 #include "third_party/blink/renderer/core/css/parser/css_selector_parser.h"
 #include "third_party/blink/renderer/core/css/parser/css_tokenizer.h"
-#include "third_party/blink/renderer/core/css/route_query.h"
 #include "third_party/blink/renderer/core/dom/document.h"
 #include "third_party/blink/renderer/core/dom/pseudo_element.h"
 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
@@ -463,6 +463,7 @@
     case kPseudoLastOfType:
     case kPseudoLeftPage:
     case kPseudoLink:
+    case kPseudoLinkTo:
     case kPseudoListBox:
     case kPseudoMenulistPopoverWithMenubarAnchor:
     case kPseudoMenulistPopoverWithMenulistAnchor:
@@ -496,7 +497,6 @@
     case kPseudoRequired:
     case kPseudoRightPage:
     case kPseudoRoot:
-    case kPseudoRouteMatch:
     case kPseudoScope:
     case kPseudoSelectorFragmentAnchor:
     case kPseudoSingleButton:
@@ -713,6 +713,7 @@
     {"host-context", CSSSelector::kPseudoHostContext},
     {"is", CSSSelector::kPseudoIs},
     {"lang", CSSSelector::kPseudoLang},
+    {"link-to", CSSSelector::kPseudoLinkTo},
     {"not", CSSSelector::kPseudoNot},
     {"nth-child", CSSSelector::kPseudoNthChild},
     {"nth-last-child", CSSSelector::kPseudoNthLastChild},
@@ -720,7 +721,6 @@
     {"nth-of-type", CSSSelector::kPseudoNthOfType},
     {"part", CSSSelector::kPseudoPart},
     {"picker", CSSSelector::kPseudoPicker},
-    {"route-match", CSSSelector::kPseudoRouteMatch},
     {"scroll-button", CSSSelector::kPseudoScrollButton},
     {"slotted", CSSSelector::kPseudoSlotted},
     {"state", CSSSelector::kPseudoState},
@@ -1040,6 +1040,7 @@
     case kPseudoLastChild:
     case kPseudoLastOfType:
     case kPseudoLink:
+    case kPseudoLinkTo:
     case kPseudoMenulistPopoverWithMenubarAnchor:
     case kPseudoMenulistPopoverWithMenulistAnchor:
     case kPseudoModal:
@@ -1069,7 +1070,6 @@
     case kPseudoRelativeAnchor:
     case kPseudoRequired:
     case kPseudoRoot:
-    case kPseudoRouteMatch:
     case kPseudoScope:
     case kPseudoSelectorFragmentAnchor:
     case kPseudoSingleButton:
@@ -1298,10 +1298,10 @@
         builder.Append(')');
         break;
       }
-      case kPseudoRouteMatch: {
-        DCHECK(GetRouteLocation());
+      case kPseudoLinkTo: {
+        DCHECK(GetNavigationLocation());
         builder.Append("(");
-        GetRouteLocation()->SerializeTo(builder);
+        GetNavigationLocation()->SerializeTo(builder);
         builder.Append(")");
         break;
       }
@@ -1510,9 +1510,9 @@
   data_.rare_data_->selector_list_ = selector_list;
 }
 
-void CSSSelector::SetRouteLocation(RouteLocation* location) {
+void CSSSelector::SetNavigationLocation(NavigationLocation* location) {
   CreateRareData();
-  data_.rare_data_->route_location_ = location;
+  data_.rare_data_->navigation_location_ = location;
 }
 
 void CSSSelector::SetContainsPseudoInsideHasPseudoClass() {
@@ -1813,6 +1813,7 @@
     case kPseudoInvalid:
     case kPseudoLang:
     case kPseudoLink:
+    case kPseudoLinkTo:
     case kPseudoMenulistPopoverWithMenubarAnchor:
     case kPseudoMenulistPopoverWithMenulistAnchor:
     case kPseudoModal:
@@ -1822,7 +1823,6 @@
     case kPseudoReadOnly:
     case kPseudoReadWrite:
     case kPseudoRequired:
-    case kPseudoRouteMatch:
     case kPseudoSelectorFragmentAnchor:
     case kPseudoState:
     case kPseudoTarget:
@@ -2082,7 +2082,7 @@
 
 void CSSSelector::RareData::Trace(Visitor* visitor) const {
   visitor->Trace(selector_list_);
-  visitor->Trace(route_location_);
+  visitor->Trace(navigation_location_);
 }
 
 const CSSSelector* CSSSelector::SelectorListOrParent() const {
diff --git a/third_party/blink/renderer/core/css/css_selector.h b/third_party/blink/renderer/core/css/css_selector.h
index 1befe366..d86d01f 100644
--- a/third_party/blink/renderer/core/css/css_selector.h
+++ b/third_party/blink/renderer/core/css/css_selector.h
@@ -46,7 +46,7 @@
 class CSSParserContext;
 class CSSSelectorList;
 class Document;
-class RouteLocation;
+class NavigationLocation;
 class StyleRule;
 
 // This class represents a simple selector for a StyleRule.
@@ -410,8 +410,9 @@
     kPseudoOverscrollAreaParent,
     kPseudoOverscrollClientArea,
 
-    // :route-match(<route-location>)
-    kPseudoRouteMatch,
+    // :link-to(<navigation-location>)
+    // TODO(crbug.com/436805487): Should be :link-to(<link-condition>)
+    kPseudoLinkTo,
   };
 
   enum class AttributeMatchType : int {
@@ -508,8 +509,11 @@
   const CSSSelectorList* SelectorList() const {
     return HasRareData() ? data_.rare_data_->selector_list_.Get() : nullptr;
   }
-  const RouteLocation* GetRouteLocation() const {
-    return HasRareData() ? data_.rare_data_->route_location_.Get() : nullptr;
+  const NavigationLocation* GetNavigationLocation() const {
+    if (!HasRareData()) {
+      return nullptr;
+    }
+    return data_.rare_data_->navigation_location_.Get();
   }
   // Similar to SelectorList(), but also works for kPseudoParent
   // (i.e., nested selectors); on &, will give the parent's selector list.
@@ -542,7 +546,7 @@
   void SetValue(const AtomicString&, bool match_lower_case);
   void SetArgument(const AtomicString&);
   void SetSelectorList(CSSSelectorList*);
-  void SetRouteLocation(RouteLocation*);
+  void SetNavigationLocation(NavigationLocation*);
   void SetIdentList(std::unique_ptr<Vector<AtomicString>>);
   void SetContainsPseudoInsideHasPseudoClass();
   void SetContainsComplexLogicalCombinationsInsideHasPseudoClass();
@@ -791,7 +795,7 @@
     AtomicString argument_;    // Used for :contains, :lang, :dir, etc.
     Member<CSSSelectorList>
         selector_list_;  // Used :is, :not, :-webkit-any, etc.
-    Member<RouteLocation> route_location_;  // Used for :route-match().
+    Member<NavigationLocation> navigation_location_;  // Used for :link-to().
     std::unique_ptr<Vector<AtomicString>>
         ident_list_;  // Used for ::part(), :active-view-transition-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 16bd1daf..f665bf4 100644
--- a/third_party/blink/renderer/core/css/css_value_keywords.json5
+++ b/third_party/blink/renderer/core/css/css_value_keywords.json5
@@ -1813,8 +1813,8 @@
     // Layered @import rule
     "layer",
 
-    // if(route())
-    "route",
+    // if(navigation())
+    "navigation",
 
     // supports in import rule: @import supports()
     "supports",
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 7aaae5642..30d73e9c 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
@@ -187,7 +187,7 @@
     case CSSSelector::kPseudoInterestSource:
     case CSSSelector::kPseudoInterestTarget:
     case CSSSelector::kPseudoHasSlotted:
-    case CSSSelector::kPseudoRouteMatch:
+    case CSSSelector::kPseudoLinkTo:
       return true;
     case CSSSelector::kPseudoUnknown:
     case CSSSelector::kPseudoLeftPage:
@@ -1698,7 +1698,7 @@
       case CSSSelector::kPseudoInterestSource:
       case CSSSelector::kPseudoInterestTarget:
       case CSSSelector::kPseudoHasSlotted:
-      case CSSSelector::kPseudoRouteMatch:
+      case CSSSelector::kPseudoLinkTo:
         return EnsurePseudoInvalidationSet(selector.GetPseudoType(), type,
                                            position, in_nth_child);
       case CSSSelector::kPseudoFirstOfType:
diff --git a/third_party/blink/renderer/core/css/route_query.cc b/third_party/blink/renderer/core/css/navigation_query.cc
similarity index 61%
rename from third_party/blink/renderer/core/css/route_query.cc
rename to third_party/blink/renderer/core/css/navigation_query.cc
index 7ee99c1..d95baa077 100644
--- a/third_party/blink/renderer/core/css/route_query.cc
+++ b/third_party/blink/renderer/core/css/navigation_query.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/css/route_query.h"
+#include "third_party/blink/renderer/core/css/navigation_query.h"
 
 #include "third_party/blink/renderer/core/dom/document.h"
 #include "third_party/blink/renderer/core/route_matching/route.h"
@@ -12,11 +12,11 @@
 
 namespace blink {
 
-void RouteLocation::Trace(Visitor* v) const {
+void NavigationLocation::Trace(Visitor* v) const {
   v->Trace(url_pattern_);
 }
 
-const Route* RouteLocation::FindOrCreateRoute(Document& document) const {
+const Route* NavigationLocation::FindOrCreateRoute(Document& document) const {
   if (url_pattern_) {
     // A URLPattern becomes an anonymous route. One route for each unique
     // URLPattern.
@@ -32,7 +32,7 @@
   return route_map->FindRoute(GetRouteName());
 }
 
-void RouteLocation::SerializeTo(StringBuilder& builder) const {
+void NavigationLocation::SerializeTo(StringBuilder& builder) const {
   DCHECK(!string_.IsNull());
   if (url_pattern_) {
     builder.Append("urlpattern(\"");
@@ -43,54 +43,54 @@
   }
 }
 
-bool RouteTest::Matches(Document& document) const {
-  const Route* route = route_location_->FindOrCreateRoute(document);
+bool NavigationTestExpression::Matches(Document& document) const {
+  const Route* route = navigation_location_->FindOrCreateRoute(document);
   return route && route->Matches(preposition_);
 }
 
-void RouteTest::SerializeTo(StringBuilder& builder) const {
+void NavigationTestExpression::SerializeTo(StringBuilder& builder) const {
   switch (preposition_) {
-    case RoutePreposition::kAt:
+    case NavigationPreposition::kAt:
       builder.Append("at: ");
       break;
-    case RoutePreposition::kFrom:
+    case NavigationPreposition::kFrom:
       builder.Append("from: ");
       break;
-    case RoutePreposition::kTo:
+    case NavigationPreposition::kTo:
       builder.Append("to: ");
       break;
   }
-  route_location_->SerializeTo(builder);
+  navigation_location_->SerializeTo(builder);
 }
 
-void RouteQueryExpNode::Trace(Visitor* v) const {
+void NavigationExpNode::Trace(Visitor* v) const {
   ConditionalExpNode::Trace(v);
-  v->Trace(route_test_);
+  v->Trace(navigation_test_);
 }
 
-KleeneValue RouteQueryExpNode::Evaluate(
+KleeneValue NavigationExpNode::Evaluate(
     ConditionalExpNodeVisitor& visitor) const {
-  return visitor.EvaluateRouteQueryExpNode(*this);
+  return visitor.EvaluateNavigationExpNode(*this);
 }
 
-void RouteQueryExpNode::SerializeTo(StringBuilder& builder) const {
-  route_test_->SerializeTo(builder);
+void NavigationExpNode::SerializeTo(StringBuilder& builder) const {
+  navigation_test_->SerializeTo(builder);
 }
 
-void RouteQuery::Trace(Visitor* v) const {
+void NavigationQuery::Trace(Visitor* v) const {
   v->Trace(root_exp_);
 }
 
-bool RouteQuery::Evaluate(Document* document) const {
+bool NavigationQuery::Evaluate(Document* document) const {
   class Handler : public ConditionalExpNodeVisitor {
     STACK_ALLOCATED();
 
    public:
     explicit Handler(Document& document) : document_(document) {}
 
-    KleeneValue EvaluateRouteQueryExpNode(
-        const RouteQueryExpNode& node) override {
-      const RouteTest& test = node.GetRouteTest();
+    KleeneValue EvaluateNavigationExpNode(
+        const NavigationExpNode& node) override {
+      const NavigationTestExpression& test = node.NavigationTest();
       return test.Matches(document_) ? KleeneValue::kTrue : KleeneValue::kFalse;
     }
 
diff --git a/third_party/blink/renderer/core/css/navigation_query.h b/third_party/blink/renderer/core/css/navigation_query.h
new file mode 100644
index 0000000..04568c3
--- /dev/null
+++ b/third_party/blink/renderer/core/css/navigation_query.h
@@ -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.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_CSS_NAVIGATION_QUERY_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_NAVIGATION_QUERY_H_
+
+#include "third_party/blink/renderer/core/css/conditional_exp_node.h"
+#include "third_party/blink/renderer/core/route_matching/navigation_preposition.h"
+#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
+#include "third_party/blink/renderer/platform/heap/member.h"
+#include "third_party/blink/renderer/platform/wtf/text/atomic_string.h"
+
+namespace blink {
+
+class Document;
+class Route;
+class URLPattern;
+
+// <navigation-location>
+//
+// https://drafts.csswg.org/css-navigation-1/#typedef-navigation-location
+class NavigationLocation : public GarbageCollected<NavigationLocation> {
+ public:
+  explicit NavigationLocation(const AtomicString& navigation_name)
+      : string_(navigation_name) {}
+  NavigationLocation(URLPattern* url_pattern,
+                     const AtomicString& original_url_pattern_string)
+      : url_pattern_(url_pattern), string_(original_url_pattern_string) {}
+
+  void Trace(Visitor*) const;
+
+  URLPattern* GetURLPattern() const { return url_pattern_; }
+
+  const AtomicString& OriginalURLPatternString() const {
+    if (url_pattern_) {
+      return string_;
+    }
+    return g_null_atom;
+  }
+
+  const AtomicString& GetRouteName() const {
+    if (url_pattern_) {
+      return g_null_atom;
+    }
+    return string_;
+  }
+
+  // Look for a `Route` entry in the route map. Additionally, if this
+  // <route-location> is a URLPattern, an entry will be inserted if it's
+  // missing.
+  const Route* FindOrCreateRoute(Document&) const;
+
+  void SerializeTo(StringBuilder&) const;
+
+ private:
+  Member<URLPattern> url_pattern_;
+
+  // Route name, or, if `url_pattern_` is set, the original URLPattern
+  // string. The reason for storing the original string is for
+  // serialization. The URLPattern API deliberately doesn't support
+  // serialization.
+  AtomicString string_;
+};
+
+// <navigation-test>
+//
+// https://drafts.csswg.org/css-navigation-1/#typedef-navigation-test
+class NavigationTestExpression
+    : public GarbageCollected<NavigationTestExpression> {
+ public:
+  NavigationTestExpression(NavigationLocation& location,
+                           NavigationPreposition preposition)
+      : navigation_location_(&location), preposition_(preposition) {}
+
+  void Trace(Visitor* v) const { v->Trace(navigation_location_); }
+
+  NavigationLocation& GetLocation() const { return *navigation_location_; }
+  NavigationPreposition GetPreposition() const { return preposition_; }
+
+  bool Matches(Document&) const;
+
+  void SerializeTo(StringBuilder&) const;
+
+ private:
+  Member<NavigationLocation> navigation_location_;
+  NavigationPreposition preposition_;
+};
+
+class NavigationExpNode : public ConditionalExpNode {
+ public:
+  explicit NavigationExpNode(NavigationTestExpression& test)
+      : navigation_test_(&test) {}
+
+  void Trace(Visitor*) const override;
+
+  const NavigationTestExpression& NavigationTest() const {
+    return *navigation_test_;
+  }
+
+  KleeneValue Evaluate(ConditionalExpNodeVisitor&) const override;
+  void SerializeTo(StringBuilder&) const override;
+
+ private:
+  Member<NavigationTestExpression> navigation_test_;
+};
+
+class NavigationQuery : public GarbageCollected<NavigationQuery> {
+ public:
+  explicit NavigationQuery(const ConditionalExpNode& root_exp)
+      : root_exp_(&root_exp) {}
+
+  void Trace(Visitor*) const;
+
+  const ConditionalExpNode* GetRootExp() const { return root_exp_; }
+  bool Evaluate(Document*) const;
+
+ private:
+  Member<const ConditionalExpNode> root_exp_;
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_CSS_NAVIGATION_QUERY_H_
diff --git a/third_party/blink/renderer/core/css/parser/at_rule_descriptor_parser.cc b/third_party/blink/renderer/core/css/parser/at_rule_descriptor_parser.cc
index 050b7df..20c4ba9 100644
--- a/third_party/blink/renderer/core/css/parser/at_rule_descriptor_parser.cc
+++ b/third_party/blink/renderer/core/css/parser/at_rule_descriptor_parser.cc
@@ -296,7 +296,7 @@
     case StyleRule::kMedia:
     case StyleRule::kPage:
     case StyleRule::kPageMargin:
-    case StyleRule::kRoute:
+    case StyleRule::kNavigation:
     case StyleRule::kKeyframes:
     case StyleRule::kKeyframe:
     case StyleRule::kFontFeatureValues:
diff --git a/third_party/blink/renderer/core/css/parser/css_at_rule_id.cc b/third_party/blink/renderer/core/css/parser/css_at_rule_id.cc
index e2ad454..3018fd3 100644
--- a/third_party/blink/renderer/core/css/parser/css_at_rule_id.cc
+++ b/third_party/blink/renderer/core/css/parser/css_at_rule_id.cc
@@ -72,8 +72,8 @@
     return CSSAtRuleID::kCSSAtRuleProperty;
   }
   if (RuntimeEnabledFeatures::RouteMatchingEnabled() &&
-      EqualIgnoringASCIICase(name, "route")) {
-    return CSSAtRuleID::kCSSAtRuleRoute;
+      EqualIgnoringASCIICase(name, "navigation")) {
+    return CSSAtRuleID::kCSSAtRuleNavigation;
   }
   if (EqualIgnoringASCIICase(name, "container")) {
     return CSSAtRuleID::kCSSAtRuleContainer;
@@ -195,8 +195,8 @@
       return "@position-try";
     case CSSAtRuleID::kCSSAtRuleProperty:
       return "@property";
-    case CSSAtRuleID::kCSSAtRuleRoute:
-      return "@route";
+    case CSSAtRuleID::kCSSAtRuleNavigation:
+      return "@navigation";
     case CSSAtRuleID::kCSSAtRuleContainer:
       return "@container";
     case CSSAtRuleID::kCSSAtRuleCounterStyle:
@@ -320,7 +320,7 @@
       return WebFeature::kCSSAtRulePageMargin;
     case CSSAtRuleID::kCSSAtRuleProperty:
       return WebFeature::kCSSAtRuleProperty;
-    case CSSAtRuleID::kCSSAtRuleRoute:
+    case CSSAtRuleID::kCSSAtRuleNavigation:
       return WebFeature::kCSSAtRuleRoute;
     case CSSAtRuleID::kCSSAtRuleContainer:
       return WebFeature::kCSSAtRuleContainer;
diff --git a/third_party/blink/renderer/core/css/parser/css_at_rule_id.h b/third_party/blink/renderer/core/css/parser/css_at_rule_id.h
index ac9892ce..07981b2 100644
--- a/third_party/blink/renderer/core/css/parser/css_at_rule_id.h
+++ b/third_party/blink/renderer/core/css/parser/css_at_rule_id.h
@@ -25,7 +25,7 @@
   kCSSAtRulePage,
   kCSSAtRulePositionTry,
   kCSSAtRuleProperty,
-  kCSSAtRuleRoute,
+  kCSSAtRuleNavigation,
   kCSSAtRuleContainer,
   kCSSAtRuleCounterStyle,
   kCSSAtRuleScope,
diff --git a/third_party/blink/renderer/core/css/parser/css_if_parser.cc b/third_party/blink/renderer/core/css/parser/css_if_parser.cc
index 87da6d6..e6260d1 100644
--- a/third_party/blink/renderer/core/css/parser/css_if_parser.cc
+++ b/third_party/blink/renderer/core/css/parser/css_if_parser.cc
@@ -11,7 +11,7 @@
 #include "third_party/blink/renderer/core/css/parser/css_parser_token_stream.h"
 #include "third_party/blink/renderer/core/css/parser/css_supports_parser.h"
 #include "third_party/blink/renderer/core/css/parser/media_query_parser.h"
-#include "third_party/blink/renderer/core/css/parser/route_parser.h"
+#include "third_party/blink/renderer/core/css/parser/navigation_parser.h"
 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
 
 namespace blink {
@@ -28,7 +28,7 @@
 //   supports( [ <supports-condition> | <ident> : <declaration-value> ] ) |
 //   media( <media-feature> | <media-condition> ) |
 //   style( <style-query> ) |
-//   route( <route-condition> )
+//   navigation( <navigation-condition> )
 const ConditionalExpNode* CSSIfParser::ConsumeFunction(
     CSSParserTokenStream& stream) {
   DCHECK_EQ(stream.Peek().GetType(), kFunctionToken);
@@ -83,20 +83,20 @@
       return ConditionalExpNode::Function(query, AtomicString("style"));
     }
   }
-  if (stream.Peek().FunctionId() == CSSValueID::kRoute &&
+  if (stream.Peek().FunctionId() == CSSValueID::kNavigation &&
       RuntimeEnabledFeatures::RouteMatchingEnabled()) {
     CSSParserTokenStream::RestoringBlockGuard guard(stream);
     stream.ConsumeWhitespace();
-    RouteParser route_parser(*parser_context_.GetDocument());
+    NavigationParser navigation_parser(*parser_context_.GetDocument());
     CSSParserTokenStream::State savepoint = stream.Save();
     // We're inside the function's parentheses. Don't require any additional
-    // ones. Look for <route-test>.
-    const ConditionalExpNode* node = route_parser.ConsumeLeaf(stream);
+    // ones. Look for <navigation-test>.
+    const ConditionalExpNode* node = navigation_parser.ConsumeLeaf(stream);
     if (!node) {
-      // If that fails, though, look for <route-condition>, to handle additional
-      // parentheses and expressions.
+      // If that fails, though, look for <navigation-condition>, to handle
+      // additional parentheses and expressions.
       stream.Restore(savepoint);
-      node = route_parser.ConsumeCondition(stream);
+      node = navigation_parser.ConsumeCondition(stream);
     }
     if (node) {
       guard.Release();
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 002fa63..7c8d227 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
@@ -1243,6 +1243,11 @@
     case CSSPropertyID::kGapRuleOverlap:
       return value_id == CSSValueID::kRowOverColumn ||
              value_id == CSSValueID::kColumnOverRow;
+    case CSSPropertyID::kGridLanesDirection:
+      return value_id == CSSValueID::kRow ||
+             value_id == CSSValueID::kRowReverse ||
+             value_id == CSSValueID::kColumn ||
+             value_id == CSSValueID::kColumnReverse;
     case CSSPropertyID::kGridLanesFill:
       return value_id == CSSValueID::kNormal ||
              value_id == CSSValueID::kReverse;
@@ -1261,11 +1266,6 @@
     case CSSPropertyID::kMaskType:
       return value_id == CSSValueID::kLuminance ||
              value_id == CSSValueID::kAlpha;
-    case CSSPropertyID::kGridLanesDirection:
-      return value_id == CSSValueID::kRow ||
-             value_id == CSSValueID::kRowReverse ||
-             value_id == CSSValueID::kColumn ||
-             value_id == CSSValueID::kColumnReverse;
     case CSSPropertyID::kMathShift:
       return value_id == CSSValueID::kNormal ||
              value_id == CSSValueID::kCompact;
@@ -1752,13 +1752,13 @@
     CSSPropertyID::kFieldSizing,
     CSSPropertyID::kForcedColorAdjust,
     CSSPropertyID::kGapRuleOverlap,
+    CSSPropertyID::kGridLanesDirection,
     CSSPropertyID::kGridLanesFill,
     CSSPropertyID::kHyphens,
     CSSPropertyID::kImageRendering,
     CSSPropertyID::kInterpolateSize,
     CSSPropertyID::kListStylePosition,
     CSSPropertyID::kMaskType,
-    CSSPropertyID::kGridLanesDirection,
     CSSPropertyID::kMathShift,
     CSSPropertyID::kMathStyle,
     CSSPropertyID::kObjectFit,
diff --git a/third_party/blink/renderer/core/css/parser/css_parser_impl.cc b/third_party/blink/renderer/core/css/parser/css_parser_impl.cc
index f429a57..b2ae9400 100644
--- a/third_party/blink/renderer/core/css/parser/css_parser_impl.cc
+++ b/third_party/blink/renderer/core/css/parser/css_parser_impl.cc
@@ -24,6 +24,7 @@
 #include "third_party/blink/renderer/core/css/css_style_sheet.h"
 #include "third_party/blink/renderer/core/css/css_syntax_string_parser.h"
 #include "third_party/blink/renderer/core/css/css_unparsed_declaration_value.h"
+#include "third_party/blink/renderer/core/css/navigation_query.h"
 #include "third_party/blink/renderer/core/css/parser/at_rule_descriptor_parser.h"
 #include "third_party/blink/renderer/core/css/parser/container_query_parser.h"
 #include "third_party/blink/renderer/core/css/parser/css_at_rule_id.h"
@@ -39,10 +40,9 @@
 #include "third_party/blink/renderer/core/css/parser/css_variable_parser.h"
 #include "third_party/blink/renderer/core/css/parser/find_length_of_declaration_list-inl.h"
 #include "third_party/blink/renderer/core/css/parser/media_query_parser.h"
-#include "third_party/blink/renderer/core/css/parser/route_parser.h"
+#include "third_party/blink/renderer/core/css/parser/navigation_parser.h"
 #include "third_party/blink/renderer/core/css/properties/css_parsing_utils.h"
 #include "third_party/blink/renderer/core/css/property_registry.h"
-#include "third_party/blink/renderer/core/css/route_query.h"
 #include "third_party/blink/renderer/core/css/style_rule.h"
 #include "third_party/blink/renderer/core/css/style_rule_counter_style.h"
 #include "third_party/blink/renderer/core/css/style_rule_font_feature_values.h"
@@ -900,8 +900,9 @@
       return ConsumePageRule(stream);
     case CSSAtRuleID::kCSSAtRuleProperty:
       return ConsumePropertyRule(stream);
-    case CSSAtRuleID::kCSSAtRuleRoute:
-      return ConsumeRouteRule(stream, nesting_type, parent_rule_for_nesting);
+    case CSSAtRuleID::kCSSAtRuleNavigation:
+      return ConsumeNavigationRule(stream, nesting_type,
+                                   parent_rule_for_nesting);
     case CSSAtRuleID::kCSSAtRuleScope:
       return ConsumeScopeRule(stream, nesting_type, parent_rule_for_nesting);
     case CSSAtRuleID::kCSSAtRuleCounterStyle:
@@ -1922,19 +1923,20 @@
   return rule;
 }
 
-StyleRuleRoute* CSSParserImpl::ConsumeRouteRule(
+StyleRuleNavigation* CSSParserImpl::ConsumeNavigationRule(
     CSSParserTokenStream& stream,
     CSSNestingType nesting_type,
     StyleRule* parent_rule_for_nesting) {
   // Parse the prelude.
   wtf_size_t header_start_offset = stream.LookAheadOffset();
-  RouteQuery* query = RouteParser::ParseQuery(stream, *context_->GetDocument());
+  NavigationQuery* query =
+      NavigationParser::ParseQuery(stream, *context_->GetDocument());
   if (!query) {
-    ConsumeErroneousAtRule(stream, CSSAtRuleID::kCSSAtRuleRoute);
+    ConsumeErroneousAtRule(stream, CSSAtRuleID::kCSSAtRuleNavigation);
     return nullptr;
   }
-  if (!ConsumeEndOfPreludeForAtRuleWithBlock(stream,
-                                             CSSAtRuleID::kCSSAtRuleRoute)) {
+  if (!ConsumeEndOfPreludeForAtRuleWithBlock(
+          stream, CSSAtRuleID::kCSSAtRuleNavigation)) {
     return nullptr;
   }
   wtf_size_t header_end_offset = stream.LookAheadOffset();
@@ -1942,7 +1944,7 @@
   // Parse the actual block.
   CSSParserTokenStream::BlockGuard body_guard(stream);
   if (observer_) {
-    observer_->StartRuleHeader(StyleRule::kRoute, header_start_offset);
+    observer_->StartRuleHeader(StyleRule::kNavigation, header_start_offset);
     observer_->EndRuleHeader(header_end_offset);
     observer_->StartRuleBody(stream.Offset());
   }
@@ -1955,7 +1957,7 @@
     observer_->EndRuleBody(stream.Offset());
   }
 
-  return MakeGarbageCollected<StyleRuleRoute>(query, std::move(rules));
+  return MakeGarbageCollected<StyleRuleNavigation>(query, std::move(rules));
 }
 
 StyleRuleCounterStyle* CSSParserImpl::ConsumeCounterStyleRule(
diff --git a/third_party/blink/renderer/core/css/parser/css_parser_impl.h b/third_party/blink/renderer/core/css/parser/css_parser_impl.h
index d02b036..aac6abf 100644
--- a/third_party/blink/renderer/core/css/parser/css_parser_impl.h
+++ b/third_party/blink/renderer/core/css/parser/css_parser_impl.h
@@ -46,10 +46,10 @@
 class StyleRuleKeyframes;
 class StyleRuleMedia;
 class StyleRuleNamespace;
+class StyleRuleNavigation;
 class StyleRulePage;
 class StyleRulePositionTry;
 class StyleRuleProperty;
-class StyleRuleRoute;
 class StyleRuleSupports;
 class StyleSheetContents;
 class Element;
@@ -82,7 +82,7 @@
           CSSAtRuleID::kCSSAtRulePage,
           CSSAtRuleID::kCSSAtRulePositionTry,
           CSSAtRuleID::kCSSAtRuleProperty,
-          CSSAtRuleID::kCSSAtRuleRoute,
+          CSSAtRuleID::kCSSAtRuleNavigation,
           CSSAtRuleID::kCSSAtRuleContainer,
           CSSAtRuleID::kCSSAtRuleCounterStyle,
           CSSAtRuleID::kCSSAtRuleScope,
@@ -146,7 +146,7 @@
       CSSAtRuleID::kCSSAtRuleMedia,
       CSSAtRuleID::kCSSAtRuleSupports,
       CSSAtRuleID::kCSSAtRuleContainer,
-      CSSAtRuleID::kCSSAtRuleRoute,
+      CSSAtRuleID::kCSSAtRuleNavigation,
   };
 
   // Rules that are valid when nested within a style rule.
@@ -321,9 +321,10 @@
                                            CSSParserTokenStream&);
   StyleRulePage* ConsumePageRule(CSSParserTokenStream&);
   StyleRuleProperty* ConsumePropertyRule(CSSParserTokenStream&);
-  StyleRuleRoute* ConsumeRouteRule(CSSParserTokenStream&,
-                                   CSSNestingType,
-                                   StyleRule* parent_rule_for_nesting);
+  StyleRuleNavigation* ConsumeNavigationRule(
+      CSSParserTokenStream&,
+      CSSNestingType,
+      StyleRule* parent_rule_for_nesting);
   StyleRuleCounterStyle* ConsumeCounterStyleRule(CSSParserTokenStream&);
   StyleRuleBase* ConsumeScopeRule(CSSParserTokenStream&,
                                   CSSNestingType,
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 b9750097b..4cc7eb6 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
@@ -19,7 +19,7 @@
 #include "third_party/blink/renderer/core/css/parser/css_parser_save_point.h"
 #include "third_party/blink/renderer/core/css/parser/css_parser_token.h"
 #include "third_party/blink/renderer/core/css/parser/css_parser_token_stream.h"
-#include "third_party/blink/renderer/core/css/parser/route_parser.h"
+#include "third_party/blink/renderer/core/css/parser/navigation_parser.h"
 #include "third_party/blink/renderer/core/css/properties/css_parsing_utils.h"
 #include "third_party/blink/renderer/core/css/style_sheet_contents.h"
 #include "third_party/blink/renderer/core/dom/document.h"
@@ -1909,13 +1909,14 @@
       output_.push_back(std::move(selector));
       return true;
     }
-    case CSSSelector::kPseudoRouteMatch:
+    case CSSSelector::kPseudoLinkTo:
       if (!RuntimeEnabledFeatures::RouteMatchingEnabled()) {
         return false;
       }
-      if (RouteLocation* route_location =
-              RouteParser::ParseLocation(stream, *context_->GetDocument())) {
-        selector.SetRouteLocation(route_location);
+      if (NavigationLocation* navigation_location =
+              NavigationParser::ParseLocation(stream,
+                                              *context_->GetDocument())) {
+        selector.SetNavigationLocation(navigation_location);
         output_.push_back(std::move(selector));
         return true;
       }
diff --git a/third_party/blink/renderer/core/css/parser/route_parser.cc b/third_party/blink/renderer/core/css/parser/navigation_parser.cc
similarity index 67%
rename from third_party/blink/renderer/core/css/parser/route_parser.cc
rename to third_party/blink/renderer/core/css/parser/navigation_parser.cc
index 2cac46b..45ed6521 100644
--- a/third_party/blink/renderer/core/css/parser/route_parser.cc
+++ b/third_party/blink/renderer/core/css/parser/navigation_parser.cc
@@ -2,12 +2,12 @@
 // 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/css/parser/route_parser.h"
+#include "third_party/blink/renderer/core/css/parser/navigation_parser.h"
 
 #include "third_party/blink/renderer/bindings/core/v8/v8_union_urlpatterninit_usvstring.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_url_pattern_init.h"
+#include "third_party/blink/renderer/core/css/navigation_query.h"
 #include "third_party/blink/renderer/core/css/parser/css_parser_token_stream.h"
-#include "third_party/blink/renderer/core/css/route_query.h"
 #include "third_party/blink/renderer/core/dom/document.h"
 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
 #include "third_party/blink/renderer/core/url_pattern/url_pattern.h"
@@ -61,16 +61,16 @@
       pattern_str);
 }
 
-// https://wicg.github.io/declarative-partial-updates/css-route-matching/#at-route
+// https://drafts.csswg.org/css-navigation-1/#typedef-navigation-test
 //
-// <route-test> = <route-location> | <route-keyword> : <route-location>
-// <route-keyword> = at | from | to
-// <route-location> = <route-name> | <urlpattern()>
-// <route-name> = <custom-ident>
-RouteTest* ParseRouteTest(CSSParserTokenStream& stream,
-                          const Document& document) {
+// <navigation-test> = <navigation-location> | <navigation-keyword> :
+// <navigation-location> <navigation-keyword> = at | from | to
+// <navigation-location> = <route-name> | <url-pattern()>
+// <route-name> = <dashed-ident>
+NavigationTestExpression* ParseNavigationTest(CSSParserTokenStream& stream,
+                                              const Document& document) {
   AtomicString route_name;
-  RoutePreposition preposition = RoutePreposition::kAt;
+  auto preposition = NavigationPreposition::kAt;
   URLPatternParseResult url_pattern_result;
 
   bool header_valid = [&]() {
@@ -79,11 +79,11 @@
           stream.ConsumeIncludingWhitespace().Value().ToString());
       if (stream.Peek().GetType() == kColonToken) {
         if (first_string == "at") {
-          preposition = RoutePreposition::kAt;
+          preposition = NavigationPreposition::kAt;
         } else if (first_string == "from") {
-          preposition = RoutePreposition::kFrom;
+          preposition = NavigationPreposition::kFrom;
         } else if (first_string == "to") {
-          preposition = RoutePreposition::kTo;
+          preposition = NavigationPreposition::kTo;
         } else {
           return false;
         }
@@ -114,61 +114,64 @@
     return nullptr;
   }
 
-  RouteLocation* route_location;
+  NavigationLocation* navigation_location;
   if (url_pattern_result.IsSuccess()) {
-    route_location = MakeGarbageCollected<RouteLocation>(
+    navigation_location = MakeGarbageCollected<NavigationLocation>(
         url_pattern_result.url_pattern, url_pattern_result.original_string);
   } else {
     DCHECK(!route_name.empty());
-    route_location = MakeGarbageCollected<RouteLocation>(route_name);
+    navigation_location = MakeGarbageCollected<NavigationLocation>(route_name);
   }
 
-  return MakeGarbageCollected<RouteTest>(*route_location, preposition);
+  return MakeGarbageCollected<NavigationTestExpression>(*navigation_location,
+                                                        preposition);
 }
 
 }  // anonymous namespace
 
-RouteQuery* RouteParser::ParseQuery(CSSParserTokenStream& stream,
-                                    const Document& document) {
-  RouteParser parser(document);
+NavigationQuery* NavigationParser::ParseQuery(CSSParserTokenStream& stream,
+                                              const Document& document) {
+  NavigationParser parser(document);
   const ConditionalExpNode* root = parser.ConsumeCondition(stream);
   if (!root) {
     return nullptr;
   }
-  return MakeGarbageCollected<RouteQuery>(*root);
+  return MakeGarbageCollected<NavigationQuery>(*root);
 }
 
-RouteLocation* RouteParser::ParseLocation(CSSParserTokenStream& stream,
-                                          const Document& document) {
+NavigationLocation* NavigationParser::ParseLocation(
+    CSSParserTokenStream& stream,
+    const Document& document) {
   if (stream.Peek().GetType() == kIdentToken) {
     AtomicString route_name(
         stream.ConsumeIncludingWhitespace().Value().ToString());
     if (stream.AtEnd()) {
-      return MakeGarbageCollected<RouteLocation>(route_name);
+      return MakeGarbageCollected<NavigationLocation>(route_name);
     }
   } else {
     URLPatternParseResult result = ParseURLPattern(stream, document);
     if (result.IsSuccess()) {
       stream.ConsumeWhitespace();
       if (stream.AtEnd()) {
-        return MakeGarbageCollected<RouteLocation>(result.url_pattern,
-                                                   result.original_string);
+        return MakeGarbageCollected<NavigationLocation>(result.url_pattern,
+                                                        result.original_string);
       }
     }
   }
   return nullptr;
 }
 
-const ConditionalExpNode* RouteParser::ConsumeLeaf(
+const ConditionalExpNode* NavigationParser::ConsumeLeaf(
     CSSParserTokenStream& stream) {
-  RouteTest* route_test = ParseRouteTest(stream, document_);
-  if (!route_test) {
+  NavigationTestExpression* navigation_test =
+      ParseNavigationTest(stream, document_);
+  if (!navigation_test) {
     return nullptr;
   }
-  return MakeGarbageCollected<RouteQueryExpNode>(*route_test);
+  return MakeGarbageCollected<NavigationExpNode>(*navigation_test);
 }
 
-const ConditionalExpNode* RouteParser::ConsumeFunction(
+const ConditionalExpNode* NavigationParser::ConsumeFunction(
     CSSParserTokenStream& stream) {
   return nullptr;
 }
diff --git a/third_party/blink/renderer/core/css/parser/navigation_parser.h b/third_party/blink/renderer/core/css/parser/navigation_parser.h
new file mode 100644
index 0000000..b4329fbe
--- /dev/null
+++ b/third_party/blink/renderer/core/css/parser/navigation_parser.h
@@ -0,0 +1,34 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_CSS_PARSER_NAVIGATION_PARSER_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_PARSER_NAVIGATION_PARSER_H_
+
+#include "third_party/blink/renderer/core/css/parser/conditional_parser.h"
+
+namespace blink {
+
+class CSSParserTokenStream;
+class Document;
+class NavigationQuery;
+class NavigationLocation;
+
+class NavigationParser : public ConditionalParser {
+ public:
+  static NavigationQuery* ParseQuery(CSSParserTokenStream&, const Document&);
+  static NavigationLocation* ParseLocation(CSSParserTokenStream&,
+                                           const Document&);
+
+  explicit NavigationParser(const Document& document) : document_(document) {}
+
+  const ConditionalExpNode* ConsumeLeaf(CSSParserTokenStream&) final;
+  const ConditionalExpNode* ConsumeFunction(CSSParserTokenStream&) final;
+
+ private:
+  const Document& document_;
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_CSS_PARSER_NAVIGATION_PARSER_H_
diff --git a/third_party/blink/renderer/core/css/parser/route_parser.h b/third_party/blink/renderer/core/css/parser/route_parser.h
deleted file mode 100644
index bb6fa24..0000000
--- a/third_party/blink/renderer/core/css/parser/route_parser.h
+++ /dev/null
@@ -1,33 +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 THIRD_PARTY_BLINK_RENDERER_CORE_CSS_PARSER_ROUTE_PARSER_H_
-#define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_PARSER_ROUTE_PARSER_H_
-
-#include "third_party/blink/renderer/core/css/parser/conditional_parser.h"
-
-namespace blink {
-
-class CSSParserTokenStream;
-class Document;
-class RouteQuery;
-class RouteLocation;
-
-class RouteParser : public ConditionalParser {
- public:
-  static RouteQuery* ParseQuery(CSSParserTokenStream&, const Document&);
-  static RouteLocation* ParseLocation(CSSParserTokenStream&, const Document&);
-
-  explicit RouteParser(const Document& document) : document_(document) {}
-
-  const ConditionalExpNode* ConsumeLeaf(CSSParserTokenStream&) final;
-  const ConditionalExpNode* ConsumeFunction(CSSParserTokenStream&) final;
-
- private:
-  const Document& document_;
-};
-
-}  // namespace blink
-
-#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_CSS_PARSER_ROUTE_PARSER_H_
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 79b292f..c2b2838 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
@@ -4834,7 +4834,7 @@
   CSSIdentifierValue* option = nullptr;
   if (intrinsic_length.HasAuto()) {
     option = CSSIdentifierValue::Create(CSSValueID::kAuto);
-  } else if (intrinsic_length.MatchesElement()) {
+  } else if (intrinsic_length.IsFromElement()) {
     DCHECK(RuntimeEnabledFeatures::ResponsiveIframesEnabled());
     option = CSSIdentifierValue::Create(CSSValueID::kFromElement);
   } else {
diff --git a/third_party/blink/renderer/core/css/resolver/style_cascade.cc b/third_party/blink/renderer/core/css/resolver/style_cascade.cc
index fbe02782..5967463 100644
--- a/third_party/blink/renderer/core/css/resolver/style_cascade.cc
+++ b/third_party/blink/renderer/core/css/resolver/style_cascade.cc
@@ -34,6 +34,7 @@
 #include "third_party/blink/renderer/core/css/kleene_value.h"
 #include "third_party/blink/renderer/core/css/media_list.h"
 #include "third_party/blink/renderer/core/css/media_query_exp.h"
+#include "third_party/blink/renderer/core/css/navigation_query.h"
 #include "third_party/blink/renderer/core/css/parser/css_if_parser.h"
 #include "third_party/blink/renderer/core/css/parser/css_parser_fast_paths.h"
 #include "third_party/blink/renderer/core/css/parser/css_parser_local_context.h"
@@ -56,7 +57,6 @@
 #include "third_party/blink/renderer/core/css/resolver/style_builder.h"
 #include "third_party/blink/renderer/core/css/resolver/style_builder_converter.h"
 #include "third_party/blink/renderer/core/css/resolver/style_resolver_state.h"
-#include "third_party/blink/renderer/core/css/route_query.h"
 #include "third_party/blink/renderer/core/css/style_engine.h"
 #include "third_party/blink/renderer/core/css/style_rule_function_declarations.h"
 #include "third_party/blink/renderer/core/css/try_value_flips.h"
@@ -2238,9 +2238,10 @@
         FlattenFunctionBody(*container_rule, function_tree_scope, result,
                             locals);
       }
-    } else if (auto* route_rule = DynamicTo<StyleRuleRoute>(child.Get())) {
+    } else if (auto* navigation_rule =
+                   DynamicTo<StyleRuleNavigation>(child.Get())) {
       // TODO(crbug.com/431374376): Implement
-      (void)route_rule;
+      (void)navigation_rule;
       NOTREACHED() << "Not yet implemented.";
     }
   }
@@ -2714,10 +2715,11 @@
         : evaluate_style_func_(evaluate_style_func),
           resolver_state_(resolver_state) {}
 
-    KleeneValue EvaluateRouteQueryExpNode(
-        const RouteQueryExpNode& node) override {
-      // Evaluate route() function
-      bool result = node.GetRouteTest().Matches(resolver_state_.GetDocument());
+    KleeneValue EvaluateNavigationExpNode(
+        const NavigationExpNode& node) override {
+      // Evaluate navigation() function
+      bool result =
+          node.NavigationTest().Matches(resolver_state_.GetDocument());
       return result ? KleeneValue::kTrue : KleeneValue::kFalse;
     }
 
diff --git a/third_party/blink/renderer/core/css/route_query.h b/third_party/blink/renderer/core/css/route_query.h
deleted file mode 100644
index 659a64d..0000000
--- a/third_party/blink/renderer/core/css/route_query.h
+++ /dev/null
@@ -1,120 +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 THIRD_PARTY_BLINK_RENDERER_CORE_CSS_ROUTE_QUERY_H_
-#define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_ROUTE_QUERY_H_
-
-#include "third_party/blink/renderer/core/css/conditional_exp_node.h"
-#include "third_party/blink/renderer/core/route_matching/route_preposition.h"
-#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
-#include "third_party/blink/renderer/platform/heap/member.h"
-#include "third_party/blink/renderer/platform/wtf/text/atomic_string.h"
-
-namespace blink {
-
-class Document;
-class Route;
-class URLPattern;
-
-// <route-location>
-//
-// https://wicg.github.io/declarative-partial-updates/css-route-matching/#at-route
-class RouteLocation : public GarbageCollected<RouteLocation> {
- public:
-  explicit RouteLocation(const AtomicString& route_name)
-      : string_(route_name) {}
-  RouteLocation(URLPattern* url_pattern,
-                const AtomicString& original_url_pattern_string)
-      : url_pattern_(url_pattern), string_(original_url_pattern_string) {}
-
-  void Trace(Visitor*) const;
-
-  URLPattern* GetURLPattern() const { return url_pattern_; }
-
-  const AtomicString& OriginalURLPatternString() const {
-    if (url_pattern_) {
-      return string_;
-    }
-    return g_null_atom;
-  }
-
-  const AtomicString& GetRouteName() const {
-    if (url_pattern_) {
-      return g_null_atom;
-    }
-    return string_;
-  }
-
-  // Look for a `Route` entry in the route map. Additionally, if this
-  // <route-location> is a URLPattern, an entry will be inserted if it's
-  // missing.
-  const Route* FindOrCreateRoute(Document&) const;
-
-  void SerializeTo(StringBuilder&) const;
-
- private:
-  Member<URLPattern> url_pattern_;
-
-  // Route name, or, if `url_pattern_` is set, the original URLPattern
-  // string. The reason for storing the original string is for
-  // serialization. The URLPattern API deliberately doesn't support
-  // serialization.
-  AtomicString string_;
-};
-
-// <route-test>
-//
-// https://wicg.github.io/declarative-partial-updates/css-route-matching/#at-route
-class RouteTest : public GarbageCollected<RouteTest> {
- public:
-  RouteTest(RouteLocation& location, RoutePreposition preposition)
-      : route_location_(&location), preposition_(preposition) {}
-
-  void Trace(Visitor* v) const { v->Trace(route_location_); }
-
-  RouteLocation& GetLocation() const { return *route_location_; }
-  RoutePreposition GetPreposition() const { return preposition_; }
-
-  bool Matches(Document&) const;
-
-  void SerializeTo(StringBuilder&) const;
-
- private:
-  Member<RouteLocation> route_location_;
-  RoutePreposition preposition_;
-};
-
-class RouteQueryExpNode : public ConditionalExpNode {
- public:
-  explicit RouteQueryExpNode(RouteTest& route_test)
-      : route_test_(&route_test) {}
-
-  void Trace(Visitor*) const override;
-
-  const RouteTest& GetRouteTest() const { return *route_test_; }
-
-  KleeneValue Evaluate(ConditionalExpNodeVisitor&) const override;
-  void SerializeTo(StringBuilder&) const override;
-
- private:
-  Member<RouteTest> route_test_;
-};
-
-class RouteQuery : public GarbageCollected<RouteQuery> {
- public:
-  explicit RouteQuery(const ConditionalExpNode& root_exp)
-      : root_exp_(&root_exp) {}
-
-  void Trace(Visitor*) const;
-
-  const ConditionalExpNode* GetRootExp() const { return root_exp_; }
-  bool Evaluate(Document*) const;
-
- private:
-  Member<const ConditionalExpNode> root_exp_;
-};
-
-}  // namespace blink
-
-#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_CSS_ROUTE_QUERY_H_
diff --git a/third_party/blink/renderer/core/css/rule_set.cc b/third_party/blink/renderer/core/css/rule_set.cc
index 9a9368d..08b0a75 100644
--- a/third_party/blink/renderer/core/css/rule_set.cc
+++ b/third_party/blink/renderer/core/css/rule_set.cc
@@ -42,8 +42,8 @@
 #include "third_party/blink/renderer/core/css/css_selector.h"
 #include "third_party/blink/renderer/core/css/css_selector_list.h"
 #include "third_party/blink/renderer/core/css/media_values.h"
+#include "third_party/blink/renderer/core/css/navigation_query.h"
 #include "third_party/blink/renderer/core/css/robin_hood_map-inl.h"
-#include "third_party/blink/renderer/core/css/route_query.h"
 #include "third_party/blink/renderer/core/css/seeker.h"
 #include "third_party/blink/renderer/core/css/selector_checker-inl.h"
 #include "third_party/blink/renderer/core/css/selector_checker.h"
@@ -900,9 +900,9 @@
   page_rules_.push_back(CascadeLayered<StyleRulePage>(rule, layer));
 }
 
-void RuleSet::AddRouteRule(StyleRuleRoute* rule) {
+void RuleSet::AddNavigationRule(StyleRuleNavigation* rule) {
   need_compaction_ = true;
-  route_rules_.push_back(rule);
+  navigation_rules_.push_back(rule);
 }
 
 void RuleSet::AddFontFaceRule(StyleRuleFontFace* rule,
@@ -978,11 +978,11 @@
                    style_scope);
     } else if (auto* page_rule = DynamicTo<StyleRulePage>(rule)) {
       AddPageRule(page_rule, cascade_layer);
-    } else if (auto* route_rule = DynamicTo<StyleRuleRoute>(rule)) {
-      const RouteQuery& query = route_rule->GetRouteQuery();
+    } else if (auto* navigation_rule = DynamicTo<StyleRuleNavigation>(rule)) {
+      const NavigationQuery& query = navigation_rule->GetNavigationQuery();
       if (query.Evaluate(medium.GetMediaValues().GetDocument())) {
-        AddChildRules(parent_rule, route_rule->ChildRules(), medium, mixins,
-                      add_rule_flags, container_query, cascade_layer,
+        AddChildRules(parent_rule, navigation_rule->ChildRules(), medium,
+                      mixins, add_rule_flags, container_query, cascade_layer,
                       style_scope, apply_mixins_stack);
       }
     } else if (auto* media_rule = DynamicTo<StyleRuleMedia>(rule)) {
@@ -1791,7 +1791,7 @@
   visitor->Trace(view_transition_rules_);
   visitor->Trace(keyframes_rules_);
   visitor->Trace(property_rules_);
-  visitor->Trace(route_rules_);
+  visitor->Trace(navigation_rules_);
   visitor->Trace(counter_style_rules_);
   visitor->Trace(position_try_rules_);
   visitor->Trace(function_rules_);
diff --git a/third_party/blink/renderer/core/css/rule_set.h b/third_party/blink/renderer/core/css/rule_set.h
index dd15abf..6a42a453 100644
--- a/third_party/blink/renderer/core/css/rule_set.h
+++ b/third_party/blink/renderer/core/css/rule_set.h
@@ -512,8 +512,8 @@
   const HeapVector<CascadeLayered<StyleRulePage>>& PageRules() const {
     return page_rules_;
   }
-  const HeapVector<Member<StyleRuleRoute>>& RouteRules() const {
-    return route_rules_;
+  const HeapVector<Member<StyleRuleNavigation>>& NavigationRules() const {
+    return navigation_rules_;
   }
   const HeapVector<CascadeLayered<StyleRuleFontFace>>& FontFaceRules() const {
     return font_face_rules_;
@@ -671,7 +671,7 @@
   void AddToBucket(const AtomicString& key, RuleMap&, const RuleData&);
   void AddToBucket(HeapVector<RuleData>&, const RuleData&);
   void AddPageRule(StyleRulePage*, const CascadeLayer*);
-  void AddRouteRule(StyleRuleRoute*);
+  void AddNavigationRule(StyleRuleNavigation*);
   void AddFontFaceRule(StyleRuleFontFace*, const CascadeLayer*);
   void AddKeyframesRule(StyleRuleKeyframes*, const CascadeLayer*);
   void AddPropertyRule(StyleRuleProperty*, const CascadeLayer*);
@@ -800,7 +800,7 @@
   HeapVector<RuleData> root_element_rules_;
   RuleFeatureSet features_;
   HeapVector<CascadeLayered<StyleRulePage>> page_rules_;
-  HeapVector<Member<StyleRuleRoute>> route_rules_;
+  HeapVector<Member<StyleRuleNavigation>> navigation_rules_;
   HeapVector<CascadeLayered<StyleRuleFontFace>> font_face_rules_;
   HeapVector<Member<StyleRuleFontPaletteValues>> font_palette_values_rules_;
   HeapVector<CascadeLayered<StyleRuleFontFeatureValues>>
diff --git a/third_party/blink/renderer/core/css/selector_checker.cc b/third_party/blink/renderer/core/css/selector_checker.cc
index df4903e..74b97553 100644
--- a/third_party/blink/renderer/core/css/selector_checker.cc
+++ b/third_party/blink/renderer/core/css/selector_checker.cc
@@ -34,9 +34,9 @@
 #include "third_party/blink/renderer/core/css/check_pseudo_has_argument_context.h"
 #include "third_party/blink/renderer/core/css/check_pseudo_has_cache_scope.h"
 #include "third_party/blink/renderer/core/css/css_selector_list.h"
+#include "third_party/blink/renderer/core/css/navigation_query.h"
 #include "third_party/blink/renderer/core/css/part_names.h"
 #include "third_party/blink/renderer/core/css/post_style_update_scope.h"
-#include "third_party/blink/renderer/core/css/route_query.h"
 #include "third_party/blink/renderer/core/css/style_engine.h"
 #include "third_party/blink/renderer/core/css/style_rule.h"
 #include "third_party/blink/renderer/core/css/style_scope_data.h"
@@ -586,7 +586,7 @@
     case CSSSelector::kPseudoResizer:
     case CSSSelector::kPseudoRightPage:
     case CSSSelector::kPseudoRoot:
-    case CSSSelector::kPseudoRouteMatch:
+    case CSSSelector::kPseudoLinkTo:
     case CSSSelector::kPseudoScrollbar:
     case CSSSelector::kPseudoScrollbarButton:
     case CSSSelector::kPseudoScrollbarCorner:
@@ -2056,18 +2056,18 @@
   return false;
 }
 
-bool SelectorChecker::CheckPseudoRouteMatch(
-    const SelectorCheckingContext& context,
-    MatchResult& result) const {
+bool SelectorChecker::CheckPseudoLinkTo(const SelectorCheckingContext& context,
+                                        MatchResult& result) const {
   DCHECK(context.selector);
-  DCHECK(context.selector->GetRouteLocation());
+  DCHECK(context.selector->GetNavigationLocation());
   Element& element = GetCandidateElement(context, result);
   const auto* anchor = DynamicTo<HTMLAnchorElement>(&element);
   if (!anchor) {
     return false;
   }
-  const Route* route = context.selector->GetRouteLocation()->FindOrCreateRoute(
-      element.GetDocument());
+  const Route* route =
+      context.selector->GetNavigationLocation()->FindOrCreateRoute(
+          element.GetDocument());
   return route && route->MatchesUrl(anchor->Href());
 }
 
@@ -2651,9 +2651,9 @@
     }
     case CSSSelector::kPseudoRoot:
       return element == element.GetDocument().documentElement();
-    case CSSSelector::kPseudoRouteMatch:
+    case CSSSelector::kPseudoLinkTo:
       DCHECK(RuntimeEnabledFeatures::RouteMatchingEnabled());
-      return CheckPseudoRouteMatch(context, result);
+      return CheckPseudoLinkTo(context, result);
     case CSSSelector::kPseudoLang: {
       auto* vtt_element = DynamicTo<VTTElement>(element);
       AtomicString value = vtt_element ? vtt_element->Language()
diff --git a/third_party/blink/renderer/core/css/selector_checker.h b/third_party/blink/renderer/core/css/selector_checker.h
index 23882fd4..82b302b 100644
--- a/third_party/blink/renderer/core/css/selector_checker.h
+++ b/third_party/blink/renderer/core/css/selector_checker.h
@@ -450,8 +450,7 @@
   bool CheckPseudoScope(const SelectorCheckingContext&, MatchResult&) const;
   bool CheckPseudoNot(const SelectorCheckingContext&, MatchResult&) const;
   bool CheckPseudoHas(const SelectorCheckingContext&, MatchResult&) const;
-  bool CheckPseudoRouteMatch(const SelectorCheckingContext&,
-                             MatchResult&) const;
+  bool CheckPseudoLinkTo(const SelectorCheckingContext&, MatchResult&) const;
   bool MatchesAnyInList(const SelectorCheckingContext& context,
                         const CSSSelector* selector_list,
                         MatchResult& result) const;
diff --git a/third_party/blink/renderer/core/css/style_engine.h b/third_party/blink/renderer/core/css/style_engine.h
index f156c47..5fcb78f 100644
--- a/third_party/blink/renderer/core/css/style_engine.h
+++ b/third_party/blink/renderer/core/css/style_engine.h
@@ -780,9 +780,9 @@
   void RevisitStyleSheetForInspector(StyleSheetContents* contents,
                                      const RuleFeatureSet* features) const;
 
-  // Call when @route rules may need to be re-evaluated, because the current URL
-  // has changed.
-  void RoutesMayHaveChanged() { SetNeedsActiveStyleUpdate(GetDocument()); }
+  // Call when @navigation rules may need to be re-evaluated, because the
+  // current URL has changed.
+  void NavigationsMayHaveChanged() { SetNeedsActiveStyleUpdate(GetDocument()); }
 
   // Returns a random base value for CSS random() function.
   // @param random_value_sharing <random-value-sharing> parameter of CSS
diff --git a/third_party/blink/renderer/core/css/style_rule.cc b/third_party/blink/renderer/core/css/style_rule.cc
index 696b691..f4ca892c 100644
--- a/third_party/blink/renderer/core/css/style_rule.cc
+++ b/third_party/blink/renderer/core/css/style_rule.cc
@@ -42,11 +42,11 @@
 #include "third_party/blink/renderer/core/css/css_media_rule.h"
 #include "third_party/blink/renderer/core/css/css_mixin_rule.h"
 #include "third_party/blink/renderer/core/css/css_namespace_rule.h"
+#include "third_party/blink/renderer/core/css/css_navigation_rule.h"
 #include "third_party/blink/renderer/core/css/css_nested_declarations_rule.h"
 #include "third_party/blink/renderer/core/css/css_page_rule.h"
 #include "third_party/blink/renderer/core/css/css_position_try_rule.h"
 #include "third_party/blink/renderer/core/css/css_property_rule.h"
-#include "third_party/blink/renderer/core/css/css_route_rule.h"
 #include "third_party/blink/renderer/core/css/css_scope_rule.h"
 #include "third_party/blink/renderer/core/css/css_starting_style_rule.h"
 #include "third_party/blink/renderer/core/css/css_style_rule.h"
@@ -55,6 +55,7 @@
 #include "third_party/blink/renderer/core/css/css_view_transition_rule.h"
 #include "third_party/blink/renderer/core/css/media_list.h"
 #include "third_party/blink/renderer/core/css/media_query_exp.h"
+#include "third_party/blink/renderer/core/css/navigation_query.h"
 #include "third_party/blink/renderer/core/css/parser/container_query_parser.h"
 #include "third_party/blink/renderer/core/css/parser/css_parser.h"
 #include "third_party/blink/renderer/core/css/parser/css_parser_context.h"
@@ -63,7 +64,6 @@
 #include "third_party/blink/renderer/core/css/parser/css_parser_token_stream.h"
 #include "third_party/blink/renderer/core/css/parser/css_supports_parser.h"
 #include "third_party/blink/renderer/core/css/parser/css_tokenizer.h"
-#include "third_party/blink/renderer/core/css/route_query.h"
 #include "third_party/blink/renderer/core/css/style_rule_counter_style.h"
 #include "third_party/blink/renderer/core/css/style_rule_font_feature_values.h"
 #include "third_party/blink/renderer/core/css/style_rule_font_palette_values.h"
@@ -121,8 +121,8 @@
     case kProperty:
       To<StyleRuleProperty>(this)->TraceAfterDispatch(visitor);
       return;
-    case kRoute:
-      To<StyleRuleRoute>(this)->TraceAfterDispatch(visitor);
+    case kNavigation:
+      To<StyleRuleNavigation>(this)->TraceAfterDispatch(visitor);
       return;
     case kFontFace:
       To<StyleRuleFontFace>(this)->TraceAfterDispatch(visitor);
@@ -220,8 +220,8 @@
     case kProperty:
       To<StyleRuleProperty>(this)->~StyleRuleProperty();
       return;
-    case kRoute:
-      To<StyleRuleRoute>(this)->~StyleRuleRoute();
+    case kNavigation:
+      To<StyleRuleNavigation>(this)->~StyleRuleNavigation();
       return;
     case kFontFace:
       To<StyleRuleFontFace>(this)->~StyleRuleFontFace();
@@ -325,9 +325,9 @@
       rule = MakeGarbageCollected<CSSMarginRule>(To<StyleRulePageMargin>(self),
                                                  parent_sheet);
       break;
-    case kRoute:
-      rule = MakeGarbageCollected<CSSRouteRule>(To<StyleRuleRoute>(self),
-                                                parent_sheet);
+    case kNavigation:
+      rule = MakeGarbageCollected<CSSNavigationRule>(
+          To<StyleRuleNavigation>(self), parent_sheet);
       break;
     case kProperty:
       rule = MakeGarbageCollected<CSSPropertyRule>(To<StyleRuleProperty>(self),
@@ -637,8 +637,8 @@
     case kMedia:
       return CloneGroupRule(To<StyleRuleMedia>(this), new_parent,
                             mixin_parameter_bindings);
-    case kRoute:
-      return CloneGroupRule(To<StyleRuleRoute>(this), new_parent,
+    case kNavigation:
+      return CloneGroupRule(To<StyleRuleNavigation>(this), new_parent,
                             mixin_parameter_bindings);
     case kSupports:
       return CloneGroupRule(To<StyleRuleSupports>(this), new_parent,
@@ -1034,17 +1034,20 @@
   StyleRuleCondition::TraceAfterDispatch(visitor);
 }
 
-StyleRuleRoute::StyleRuleRoute(RouteQuery* query,
-                               HeapVector<Member<StyleRuleBase>> child_rules)
-    : StyleRuleCondition(kRoute, std::move(child_rules)), route_query_(query) {}
+StyleRuleNavigation::StyleRuleNavigation(
+    NavigationQuery* query,
+    HeapVector<Member<StyleRuleBase>> child_rules)
+    : StyleRuleCondition(kNavigation, std::move(child_rules)),
+      navigation_query_(query) {}
 
-StyleRuleRoute::StyleRuleRoute(const StyleRuleRoute& other,
-                               HeapVector<Member<StyleRuleBase>> child_rules)
-    : StyleRuleCondition(kRoute, std::move(child_rules)),
-      route_query_(other.route_query_) {}
+StyleRuleNavigation::StyleRuleNavigation(
+    const StyleRuleNavigation& other,
+    HeapVector<Member<StyleRuleBase>> child_rules)
+    : StyleRuleCondition(kNavigation, std::move(child_rules)),
+      navigation_query_(other.navigation_query_) {}
 
-void StyleRuleRoute::TraceAfterDispatch(Visitor* v) const {
-  v->Trace(route_query_);
+void StyleRuleNavigation::TraceAfterDispatch(Visitor* v) const {
+  v->Trace(navigation_query_);
   StyleRuleCondition::TraceAfterDispatch(v);
 }
 
diff --git a/third_party/blink/renderer/core/css/style_rule.h b/third_party/blink/renderer/core/css/style_rule.h
index ec01a535..9e1fda1 100644
--- a/third_party/blink/renderer/core/css/style_rule.h
+++ b/third_party/blink/renderer/core/css/style_rule.h
@@ -38,7 +38,7 @@
 #include "third_party/blink/renderer/core/css/parser/css_lazy_property_parser.h"
 #include "third_party/blink/renderer/core/css/parser/css_nesting_type.h"
 #include "third_party/blink/renderer/core/css/style_scope.h"
-#include "third_party/blink/renderer/core/route_matching/route_preposition.h"
+#include "third_party/blink/renderer/core/route_matching/navigation_preposition.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 #include "third_party/blink/renderer/platform/wtf/casting.h"
 
@@ -48,7 +48,7 @@
 class CSSStyleSheet;
 class MixinParameterBindings;
 class ExecutionContext;
-class RouteQuery;
+class NavigationQuery;
 
 class CORE_EXPORT StyleRuleBase : public GarbageCollected<StyleRuleBase> {
  public:
@@ -64,7 +64,7 @@
     kPage,
     kPageMargin,
     kProperty,
-    kRoute,
+    kNavigation,
     kKeyframes,
     kKeyframe,
     kLayerBlock,
@@ -120,7 +120,7 @@
   bool IsPageRule() const { return GetType() == kPage; }
   bool IsPageRuleMargin() const { return GetType() == kPageMargin; }
   bool IsPropertyRule() const { return GetType() == kProperty; }
-  bool IsRouteRule() const { return GetType() == kRoute; }
+  bool IsNavigationRule() const { return GetType() == kNavigation; }
   bool IsStyleRule() const { return GetType() == kStyle; }
   bool IsScopeRule() const { return GetType() == kScope; }
   bool IsSupportsRule() const { return GetType() == kSupports; }
@@ -615,18 +615,22 @@
   Member<ContainerQuery> container_query_;
 };
 
-class StyleRuleRoute : public StyleRuleCondition {
+class StyleRuleNavigation : public StyleRuleCondition {
  public:
-  StyleRuleRoute(RouteQuery*, HeapVector<Member<StyleRuleBase>> child_rules);
-  StyleRuleRoute(const StyleRuleRoute&) = delete;
-  StyleRuleRoute(const StyleRuleRoute&, HeapVector<Member<StyleRuleBase>>);
+  StyleRuleNavigation(NavigationQuery*,
+                      HeapVector<Member<StyleRuleBase>> child_rules);
+  StyleRuleNavigation(const StyleRuleNavigation&) = delete;
+  StyleRuleNavigation(const StyleRuleNavigation&,
+                      HeapVector<Member<StyleRuleBase>>);
 
   void TraceAfterDispatch(Visitor*) const;
 
-  const RouteQuery& GetRouteQuery() const { return *route_query_; }
+  const NavigationQuery& GetNavigationQuery() const {
+    return *navigation_query_;
+  }
 
  private:
-  Member<RouteQuery> route_query_;
+  Member<NavigationQuery> navigation_query_;
 };
 
 class StyleRuleStartingStyle : public StyleRuleGroup {
@@ -852,9 +856,9 @@
 };
 
 template <>
-struct DowncastTraits<StyleRuleRoute> {
+struct DowncastTraits<StyleRuleNavigation> {
   static bool AllowFrom(const StyleRuleBase& rule) {
-    return rule.IsRouteRule();
+    return rule.IsNavigationRule();
   }
 };
 
diff --git a/third_party/blink/renderer/core/css/style_rule_test.cc b/third_party/blink/renderer/core/css/style_rule_test.cc
index 48e1dfd1..0911e2a 100644
--- a/third_party/blink/renderer/core/css/style_rule_test.cc
+++ b/third_party/blink/renderer/core/css/style_rule_test.cc
@@ -10,7 +10,7 @@
 #include "third_party/blink/renderer/core/css/css_style_rule.h"
 #include "third_party/blink/renderer/core/css/css_style_sheet.h"
 #include "third_party/blink/renderer/core/css/css_test_helpers.h"
-#include "third_party/blink/renderer/core/css/route_query.h"
+#include "third_party/blink/renderer/core/css/navigation_query.h"
 #include "third_party/blink/renderer/core/dom/document.h"
 #include "third_party/blink/renderer/core/testing/page_test_base.h"
 #include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
@@ -458,30 +458,33 @@
                 ->SelectorTextExpandingPseudoReferences(/*scope_id=*/0));
 }
 
-TEST_F(StyleRuleTest, RouteRuleDisabled) {
+TEST_F(StyleRuleTest, NavigationRuleDisabled) {
   ScopedRouteMatchingForTest enabled(false);
   // Test both old and new syntax.
   StyleRuleBase* rule =
-      css_test_helpers::ParseRule(GetDocument(), "@route sixtysix {}");
+      css_test_helpers::ParseRule(GetDocument(), "@navigation pun_ruined {}");
   EXPECT_FALSE(rule);
-  rule = css_test_helpers::ParseRule(GetDocument(), "@route (sixtysix) {}");
+  rule =
+      css_test_helpers::ParseRule(GetDocument(), "@navigation (pun_ruined) {}");
   EXPECT_FALSE(rule);
 }
 
-TEST_F(StyleRuleTest, RouteRule) {
+TEST_F(StyleRuleTest, NavigationRule) {
   ScopedRouteMatchingForTest enabled(true);
 
-  // Parse the specified CSS into a rule, and extract its RouteTest.
-  auto GetRouteTest = [this](const char* css) -> const RouteTest* {
-    using Callback = base::FunctionRef<void(const RouteTest&)>;
+  // Parse the specified CSS into a rule, and extract its
+  // NavigationTestExpression.
+  auto GetNavigationTest =
+      [this](const char* css) -> const NavigationTestExpression* {
+    using Callback = base::FunctionRef<void(const NavigationTestExpression&)>;
     class TestExtractor : public ConditionalExpNodeVisitor {
      public:
       explicit TestExtractor(Callback callback) : callback_(callback) {}
 
      private:
-      KleeneValue EvaluateRouteQueryExpNode(
-          const RouteQueryExpNode& node) override {
-        callback_(node.GetRouteTest());
+      KleeneValue EvaluateNavigationExpNode(
+          const NavigationExpNode& node) override {
+        callback_(node.NavigationTest());
         return KleeneValue::kFalse;
       }
 
@@ -489,49 +492,50 @@
     };
 
     StyleRuleBase* rule = css_test_helpers::ParseRule(GetDocument(), css);
-    auto* route_rule = DynamicTo<StyleRuleRoute>(rule);
-    if (!route_rule) {
+    auto* navigation_rule = DynamicTo<StyleRuleNavigation>(rule);
+    if (!navigation_rule) {
       return nullptr;
     }
     const ConditionalExpNode* root_exp =
-        route_rule->GetRouteQuery().GetRootExp();
+        navigation_rule->GetNavigationQuery().GetRootExp();
     if (!root_exp) {
       return nullptr;
     }
-    const RouteTest* route_test = nullptr;
-    auto set_test = [&route_test](const RouteTest& test) {
-      route_test = &test;
+    const NavigationTestExpression* navigation_test = nullptr;
+    auto set_test = [&navigation_test](const NavigationTestExpression& test) {
+      navigation_test = &test;
     };
     TestExtractor extractor(set_test);
     root_exp->Evaluate(extractor);
-    return route_test;
+    return navigation_test;
   };
 
-  const RouteTest* route_test = GetRouteTest("@route (sixtysix) {}");
-  ASSERT_TRUE(route_test);
-  EXPECT_EQ(route_test->GetLocation().GetRouteName(), "sixtysix");
-  EXPECT_EQ(route_test->GetPreposition(), RoutePreposition::kAt);
+  const NavigationTestExpression* navigation_test =
+      GetNavigationTest("@navigation (pun_ruined) {}");
+  ASSERT_TRUE(navigation_test);
+  EXPECT_EQ(navigation_test->GetLocation().GetRouteName(), "pun_ruined");
+  EXPECT_EQ(navigation_test->GetPreposition(), NavigationPreposition::kAt);
 
-  route_test = GetRouteTest("@route (from: sixtysix) {}");
-  ASSERT_TRUE(route_test);
-  EXPECT_EQ(route_test->GetLocation().GetRouteName(), "sixtysix");
-  EXPECT_EQ(route_test->GetPreposition(), RoutePreposition::kFrom);
+  navigation_test = GetNavigationTest("@navigation (from: pun_ruined) {}");
+  ASSERT_TRUE(navigation_test);
+  EXPECT_EQ(navigation_test->GetLocation().GetRouteName(), "pun_ruined");
+  EXPECT_EQ(navigation_test->GetPreposition(), NavigationPreposition::kFrom);
 
-  route_test = GetRouteTest("@route (to: sixtysix) {}");
-  ASSERT_TRUE(route_test);
-  EXPECT_EQ(route_test->GetLocation().GetRouteName(), "sixtysix");
-  EXPECT_EQ(route_test->GetPreposition(), RoutePreposition::kTo);
+  navigation_test = GetNavigationTest("@navigation (to: pun_ruined) {}");
+  ASSERT_TRUE(navigation_test);
+  EXPECT_EQ(navigation_test->GetLocation().GetRouteName(), "pun_ruined");
+  EXPECT_EQ(navigation_test->GetPreposition(), NavigationPreposition::kTo);
 
-  route_test = GetRouteTest("@route (at: sixtysix) {}");
-  ASSERT_TRUE(route_test);
-  EXPECT_EQ(route_test->GetLocation().GetRouteName(), "sixtysix");
-  EXPECT_EQ(route_test->GetPreposition(), RoutePreposition::kAt);
+  navigation_test = GetNavigationTest("@navigation (at: pun_ruined) {}");
+  ASSERT_TRUE(navigation_test);
+  EXPECT_EQ(navigation_test->GetLocation().GetRouteName(), "pun_ruined");
+  EXPECT_EQ(navigation_test->GetPreposition(), NavigationPreposition::kAt);
 
-  route_test = GetRouteTest("@route (below: sixtysix) {}");
-  EXPECT_FALSE(route_test);
+  navigation_test = GetNavigationTest("@navigation (below: pun_ruined) {}");
+  EXPECT_FALSE(navigation_test);
 
-  route_test = GetRouteTest("@route (at: ) {}");
-  EXPECT_FALSE(route_test);
+  navigation_test = GetNavigationTest("@navigation (at: ) {}");
+  EXPECT_FALSE(navigation_test);
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/style_sheet_contents.cc b/third_party/blink/renderer/core/css/style_sheet_contents.cc
index 01a2999..4f030be8 100644
--- a/third_party/blink/renderer/core/css/style_sheet_contents.cc
+++ b/third_party/blink/renderer/core/css/style_sheet_contents.cc
@@ -674,7 +674,7 @@
       case StyleRuleBase::kContainer:
       case StyleRuleBase::kMedia:
       case StyleRuleBase::kLayerBlock:
-      case StyleRuleBase::kRoute:
+      case StyleRuleBase::kNavigation:
       case StyleRuleBase::kScope:
       case StyleRuleBase::kStartingStyle:
         if (ChildRulesHaveFailedOrCanceledSubresources(
diff --git a/third_party/blink/renderer/core/events/event_type_names.json5 b/third_party/blink/renderer/core/events/event_type_names.json5
index 06c58a2..7f67d99 100644
--- a/third_party/blink/renderer/core/events/event_type_names.json5
+++ b/third_party/blink/renderer/core/events/event_type_names.json5
@@ -142,6 +142,7 @@
     "fullscreenerror",
     "gamepadconnected",
     "gamepaddisconnected",
+    "gamepadrawinputchanged",
     "gatheringstatechange",
     "gattserverdisconnected",
     "geofenceenter",
diff --git a/third_party/blink/renderer/core/fetch/form_data_bytes_consumer.cc b/third_party/blink/renderer/core/fetch/form_data_bytes_consumer.cc
index 668bce8..b23528d 100644
--- a/third_party/blink/renderer/core/fetch/form_data_bytes_consumer.cc
+++ b/third_party/blink/renderer/core/fetch/form_data_bytes_consumer.cc
@@ -179,10 +179,7 @@
           break;
       }
     }
-    // Here we handle m_formData->boundary() as a C-style string. See
-    // FormDataEncoder::generateUniqueBoundaryString.
-    blob_data->SetContentType(AtomicString("multipart/form-data; boundary=") +
-                              form_data_->Boundary().data());
+    blob_data->SetContentType(form_data_->FormatContentTypeWithBoundary());
     auto size = blob_data->length();
     blob_bytes_consumer_ = MakeGarbageCollected<BlobBytesConsumer>(
         execution_context, BlobDataHandle::Create(std::move(blob_data), size));
diff --git a/third_party/blink/renderer/core/fetch/global_fetch.cc b/third_party/blink/renderer/core/fetch/global_fetch.cc
index 7990a1c..409b3710 100644
--- a/third_party/blink/renderer/core/fetch/global_fetch.cc
+++ b/third_party/blink/renderer/core/fetch/global_fetch.cc
@@ -13,6 +13,7 @@
 #include "third_party/blink/renderer/core/fetch/fetch_manager.h"
 #include "third_party/blink/renderer/core/fetch/request.h"
 #include "third_party/blink/renderer/core/frame/local_dom_window.h"
+#include "third_party/blink/renderer/core/frame/window_or_worker_global_scope.h"
 #include "third_party/blink/renderer/core/probe/core_probes.h"
 #include "third_party/blink/renderer/core/workers/worker_global_scope.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
@@ -190,15 +191,9 @@
 }
 
 GlobalFetch::ScopedFetcher* GlobalFetch::ScopedFetcher::From(
-    LocalDOMWindow& window) {
-  return GlobalFetchImpl<LocalDOMWindow>::From(window,
-                                               window.GetExecutionContext());
-}
-
-GlobalFetch::ScopedFetcher* GlobalFetch::ScopedFetcher::From(
-    WorkerGlobalScope& worker) {
-  return GlobalFetchImpl<WorkerGlobalScope>::From(worker,
-                                                  worker.GetExecutionContext());
+    WindowOrWorkerGlobalScope& window_or_worker) {
+  return GlobalFetchImpl<WindowOrWorkerGlobalScope>::From(
+      window_or_worker, window_or_worker.GetExecutionContext());
 }
 
 GlobalFetch::ScopedFetcher* GlobalFetch::ScopedFetcher::From(
diff --git a/third_party/blink/renderer/core/fetch/global_fetch.h b/third_party/blink/renderer/core/fetch/global_fetch.h
index 7e0126a..2e11d58 100644
--- a/third_party/blink/renderer/core/fetch/global_fetch.h
+++ b/third_party/blink/renderer/core/fetch/global_fetch.h
@@ -20,6 +20,7 @@
 class DeferredRequestInit;
 class Response;
 class ScriptState;
+class WindowOrWorkerGlobalScope;
 class WorkerGlobalScope;
 class FetchLaterResult;
 
@@ -51,8 +52,7 @@
                                           uint64_t& quota_for_url_origin,
                                           uint64_t& total_quota) const;
 
-    static ScopedFetcher* From(LocalDOMWindow&);
-    static ScopedFetcher* From(WorkerGlobalScope&);
+    static ScopedFetcher* From(WindowOrWorkerGlobalScope&);
     static ScopedFetcher* From(NavigatorBase& navigator);
 
     void Trace(Visitor*) const override;
diff --git a/third_party/blink/renderer/core/fetch/request.cc b/third_party/blink/renderer/core/fetch/request.cc
index 5e975a17..c96fde1 100644
--- a/third_party/blink/renderer/core/fetch/request.cc
+++ b/third_party/blink/renderer/core/fetch/request.cc
@@ -279,10 +279,7 @@
         nullptr /* AbortSignal */, /*cached_metadata_handler=*/nullptr);
   } else if (FormData* form = V8FormData::ToWrappable(isolate, body)) {
     scoped_refptr<EncodedFormData> form_data = form->EncodeMultiPartFormData();
-    // Here we handle formData->boundary() as a C-style string. See
-    // FormDataEncoder::generateUniqueBoundaryString.
-    content_type = AtomicString("multipart/form-data; boundary=") +
-                   form_data->Boundary().data();
+    content_type = form_data->FormatContentTypeWithBoundary();
     body_byte_length = form_data->SizeInBytes();
     return_buffer = BodyStreamBuffer::Create(
         script_state,
@@ -401,16 +398,16 @@
     KURL parsed_url = KURL(base_url, input_string);
     // "If |parsedURL| is failure, throw a TypeError."
     if (!parsed_url.IsValid()) {
-      exception_state.ThrowTypeError("Failed to parse URL from " +
-                                     input_string);
+      exception_state.ThrowTypeError(
+          StrCat({"Failed to parse URL from ", input_string}));
       return nullptr;
     }
     //   "If |parsedURL| includes credentials, throw a TypeError."
     if (!parsed_url.User().empty() || !parsed_url.Pass().empty()) {
       exception_state.ThrowTypeError(
-          "Request cannot be constructed from a URL that includes "
-          "credentials: " +
-          input_string);
+          StrCat({"Request cannot be constructed from a URL that includes "
+                  "credentials: ",
+                  input_string}));
       return nullptr;
     }
     // "Set |request|'s url to |parsedURL| and replace |request|'s url list
@@ -473,8 +470,8 @@
       KURL parsed_referrer(base_url, init->referrer());
       if (!parsed_referrer.IsValid()) {
         // "If |parsedReferrer| is failure, throw a TypeError."
-        exception_state.ThrowTypeError("Referrer '" + init->referrer() +
-                                       "' is not a valid URL.");
+        exception_state.ThrowTypeError(
+            StrCat({"Referrer '", init->referrer(), "' is not a valid URL."}));
         return nullptr;
       }
       if ((parsed_referrer.ProtocolIsAbout() &&
@@ -757,22 +754,22 @@
   // "If |init|'s method member is present, let |method| be it and run these
   // substeps:"
   if (init->hasMethod()) {
+    const String& method = init->method();
     // "If |method| is not a method or method is a forbidden method, throw
     // a TypeError."
-    if (!IsValidHTTPToken(init->method())) {
-      exception_state.ThrowTypeError("'" + init->method() +
-                                     "' is not a valid HTTP method.");
+    if (!IsValidHTTPToken(method)) {
+      exception_state.ThrowTypeError(
+          StrCat({"'", method, "' is not a valid HTTP method."}));
       return nullptr;
     }
-    if (FetchUtils::IsForbiddenMethod(init->method())) {
-      exception_state.ThrowTypeError("'" + init->method() +
-                                     "' HTTP method is unsupported.");
+    if (FetchUtils::IsForbiddenMethod(method)) {
+      exception_state.ThrowTypeError(
+          StrCat({"'", method, "' HTTP method is unsupported."}));
       return nullptr;
     }
     // "Normalize |method|."
     // "Set |request|'s method to |method|."
-    request->SetMethod(
-        FetchUtils::NormalizeMethod(AtomicString(init->method())));
+    request->SetMethod(FetchUtils::NormalizeMethod(AtomicString(method)));
   }
 
   // "If |init|'s signal member is present, then set |signal| to it."
@@ -837,8 +834,9 @@
     // "If |r|'s request's method is not a CORS-safelisted method, throw a
     // TypeError."
     if (!cors::IsCorsSafelistedMethod(r->GetRequest()->Method())) {
-      exception_state.ThrowTypeError("'" + r->GetRequest()->Method() +
-                                     "' is unsupported in no-cors mode.");
+      exception_state.ThrowTypeError(
+          StrCat({"'", r->GetRequest()->Method(),
+                  "' is unsupported in no-cors mode."}));
       return nullptr;
     }
     // "Set |r|'s Headers object's guard to "request-no-cors"."
diff --git a/third_party/blink/renderer/core/fetch/response.cc b/third_party/blink/renderer/core/fetch/response.cc
index 4631e32e..aa3d08e 100644
--- a/third_party/blink/renderer/core/fetch/response.cc
+++ b/third_party/blink/renderer/core/fetch/response.cc
@@ -195,10 +195,7 @@
     }
   } else if (FormData* form = V8FormData::ToWrappable(isolate, body)) {
     scoped_refptr<EncodedFormData> form_data = form->EncodeMultiPartFormData();
-    // Here we handle formData->boundary() as a C-style string. See
-    // FormDataEncoder::generateUniqueBoundaryString.
-    content_type = AtomicString("multipart/form-data; boundary=") +
-                   form_data->Boundary().data();
+    content_type = form_data->FormatContentTypeWithBoundary();
     body_buffer = BodyStreamBuffer::Create(
         script_state,
         MakeGarbageCollected<FormDataBytesConsumer>(execution_context,
@@ -357,7 +354,7 @@
                              ExceptionState& exception_state) {
   KURL parsed_url = ExecutionContext::From(script_state)->CompleteURL(url);
   if (!parsed_url.IsValid()) {
-    exception_state.ThrowTypeError("Failed to parse URL from " + url);
+    exception_state.ThrowTypeError(StrCat({"Failed to parse URL from ", url}));
     return nullptr;
   }
 
diff --git a/third_party/blink/renderer/core/fragment_directive/css_selector_directive.cc b/third_party/blink/renderer/core/fragment_directive/css_selector_directive.cc
index 8ed18ca..9139016 100644
--- a/third_party/blink/renderer/core/fragment_directive/css_selector_directive.cc
+++ b/third_party/blink/renderer/core/fragment_directive/css_selector_directive.cc
@@ -69,8 +69,8 @@
     : Directive(Directive::kSelector), value_(value) {}
 
 String CssSelectorDirective::ToStringImpl() const {
-  return "selector(type=CssSelector,value=" +
-         EncodeWithURLEscapeSequences(value_) + ")";
+  return StrCat({"selector(type=CssSelector,value=",
+                 EncodeWithURLEscapeSequences(value_), ")"});
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/frame/ad_tracker.cc b/third_party/blink/renderer/core/frame/ad_tracker.cc
index a9894aa..f39cdb4 100644
--- a/third_party/blink/renderer/core/frame/ad_tracker.cc
+++ b/third_party/blink/renderer/core/frame/ad_tracker.cc
@@ -567,7 +567,7 @@
 
     v8::Local<v8::String> barrier_func_name =
         ad_barrier_frame->GetFunctionName();
-    v8::Local<v8::Value> api_func_name_value = api_function->GetInferredName();
+    v8::Local<v8::Value> api_func_name_value = api_function->GetDebugName();
 
     if (!barrier_func_name.IsEmpty() && !barrier_func_name->IsUndefined() &&
         api_func_name_value->IsString()) {
diff --git a/third_party/blink/renderer/core/frame/ad_tracker_test.cc b/third_party/blink/renderer/core/frame/ad_tracker_test.cc
index fc7dcf0..8c5c2f8 100644
--- a/third_party/blink/renderer/core/frame/ad_tracker_test.cc
+++ b/third_party/blink/renderer/core/frame/ad_tracker_test.cc
@@ -3152,6 +3152,44 @@
   EXPECT_FALSE(ad_tracker_->last_is_ad_script_in_stack_result());
 }
 
+// Tests that the heuristic correctly ignores the first call to a monkeypatched
+// API from a non-ad script. This prevents misattributing the call to the ad
+// script, which is likely acting only as a proxy. The only difference from
+// the test above is that the monkeypatch function has a name.
+TEST_F(AdTrackerSimTest,
+       IgnoreMonkeyPatchHeuristic_FirstNamedProxiedCall_IsNotAd) {
+  String ad_script_url = "https://example.com/script.js?ad=true";
+  String vanilla_script_url = "https://example.com/script.js";
+  SimSubresourceRequest ad_script(ad_script_url, "text/javascript");
+  SimSubresourceRequest vanilla_script(vanilla_script_url, "text/javascript");
+
+  main_resource_->Complete(R"HTML(
+    <body><script src="script.js?ad=true"></script>
+          <script src="script.js"></script></body>
+  )HTML");
+
+  // The ad script monkeypatches history.pushState.
+  ad_script.Complete(R"SCRIPT(
+    const originalPushState = window.history.pushState;
+    window.history.pushState = function Foo(...args) {
+      originalPushState.apply(window.history, args);
+    };
+  )SCRIPT");
+
+  // The vanilla script calls the now-monkeypatched API. The call stack will
+  // have the ad script's wrapper at the top.
+  vanilla_script.Complete(R"SCRIPT(
+    window.history.pushState({}, '', '/new-url');
+  )SCRIPT");
+
+  base::RunLoop().RunUntilIdle();
+
+  // The IsAdScriptInStack check is triggered by the pushState implementation.
+  // The heuristic identifies the monkeypatch and, for this first call, assumes
+  // the ad script is a proxy and returns false.
+  EXPECT_FALSE(ad_tracker_->last_is_ad_script_in_stack_result());
+}
+
 // Tests that monkeypatched API is invoked from non-ad script, and the second
 // proxy within the monkeypatch function is correctly flagged as an ad. The
 // heuristic is designed to only ignore the *first* call, assuming subsequent
diff --git a/third_party/blink/renderer/core/frame/csp/csp_directive_list.cc b/third_party/blink/renderer/core/frame/csp/csp_directive_list.cc
index 4d792f2..399b4e17 100644
--- a/third_party/blink/renderer/core/frame/csp/csp_directive_list.cc
+++ b/third_party/blink/renderer/core/frame/csp/csp_directive_list.cc
@@ -978,8 +978,8 @@
   // reasons (see https://www.w3.org/TR/CSP3/#strip-url-for-use-in-reports).
   KURL document_base(document_url.BaseAsString().ToString());
   String document_path = document_base.GetPath().ToString();
-  String script_path = script_url.GetPath().ToString() +
-                       script_url.QueryWithLeadingQuestionMark();
+  String script_path = StrCat({script_url.GetPath().ToString(),
+                               script_url.QueryWithLeadingQuestionMark()});
 
   Vector<String> document_path_tokens;
   Vector<String> script_path_tokens;
diff --git a/third_party/blink/renderer/core/frame/dom_window.cc b/third_party/blink/renderer/core/frame/dom_window.cc
index f8509c4..3ab6f46 100644
--- a/third_party/blink/renderer/core/frame/dom_window.cc
+++ b/third_party/blink/renderer/core/frame/dom_window.cc
@@ -218,9 +218,10 @@
   } else if (property_name == "indexed") {
     call = "window[i]";
   } else {
-    call = "window." + property_name;
+    call = StrCat({"window.", property_name});
   }
-  return "Cross-Origin-Opener-Policy policy would block the " + call + " call.";
+  return StrCat(
+      {"Cross-Origin-Opener-Policy policy would block the ", call, " call."});
 }
 
 }  // namespace
diff --git a/third_party/blink/renderer/core/frame/frame_serializer.cc b/third_party/blink/renderer/core/frame/frame_serializer.cc
index 0d20126..7daea4d 100644
--- a/third_party/blink/renderer/core/frame/frame_serializer.cc
+++ b/third_party/blink/renderer/core/frame/frame_serializer.cc
@@ -1216,7 +1216,7 @@
       case CSSRule::kNestedDeclarationsRule:
       case CSSRule::kMediaRule:
       case CSSRule::kMixinRule:
-      case CSSRule::kRouteRule:
+      case CSSRule::kNavigationRule:
       case CSSRule::kSupportsRule:
       case CSSRule::kContainerRule:
       case CSSRule::kLayerBlockRule:
diff --git a/third_party/blink/renderer/core/frame/local_dom_window.cc b/third_party/blink/renderer/core/frame/local_dom_window.cc
index 8d074a9..23ff30b 100644
--- a/third_party/blink/renderer/core/frame/local_dom_window.cc
+++ b/third_party/blink/renderer/core/frame/local_dom_window.cc
@@ -2547,7 +2547,6 @@
   visitor->Trace(crash_report_storage_);
   visitor->Trace(closewatcher_stack_);
   visitor->Trace(soft_navigation_heuristics_);
-  visitor->Trace(global_fetch_impl_);
   visitor->Trace(global_cache_storage_impl_);
   visitor->Trace(global_cookie_store_impl_);
   visitor->Trace(global_performance_impl_);
diff --git a/third_party/blink/renderer/core/frame/local_dom_window.h b/third_party/blink/renderer/core/frame/local_dom_window.h
index 16a6e11..2f209b50 100644
--- a/third_party/blink/renderer/core/frame/local_dom_window.h
+++ b/third_party/blink/renderer/core/frame/local_dom_window.h
@@ -143,8 +143,6 @@
 class WindowSharedStorageImpl;
 
 template <typename T>
-class GlobalFetchImpl;
-template <typename T>
 class GlobalCacheStorageImpl;
 template <typename T>
 class GlobalCookieStoreImpl;
@@ -615,15 +613,6 @@
 
   void requestResize(ExceptionState&);
 
-  ForwardDeclaredMember<GlobalFetchImpl<LocalDOMWindow>> GetGlobalFetchImpl()
-      const {
-    return global_fetch_impl_;
-  }
-  void SetGlobalFetchImpl(ForwardDeclaredMember<GlobalFetchImpl<LocalDOMWindow>>
-                              global_fetch_impl) {
-    global_fetch_impl_ = global_fetch_impl;
-  }
-
   ForwardDeclaredMember<GlobalCacheStorageImpl<LocalDOMWindow>>
   GetGlobalCacheStorageImpl() const {
     return global_cache_storage_impl_;
@@ -1128,7 +1117,6 @@
 
   Member<SoftNavigationHeuristics> soft_navigation_heuristics_;
 
-  ForwardDeclaredMember<GlobalFetchImpl<LocalDOMWindow>> global_fetch_impl_;
   ForwardDeclaredMember<GlobalCacheStorageImpl<LocalDOMWindow>>
       global_cache_storage_impl_;
   ForwardDeclaredMember<GlobalCookieStoreImpl<LocalDOMWindow>>
diff --git a/third_party/blink/renderer/core/frame/remote_frame.cc b/third_party/blink/renderer/core/frame/remote_frame.cc
index f6fe3f4f..9bd4c64 100644
--- a/third_party/blink/renderer/core/frame/remote_frame.cc
+++ b/third_party/blink/renderer/core/frame/remote_frame.cc
@@ -200,7 +200,7 @@
     window->AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
         mojom::blink::ConsoleMessageSource::kSecurity,
         mojom::blink::ConsoleMessageLevel::kError,
-        "Not allowed to load local resource: " + url.ElidedString()));
+        StrCat({"Not allowed to load local resource: ", url.ElidedString()})));
     return;
   }
 
diff --git a/third_party/blink/renderer/core/frame/web_frame_serializer_impl.cc b/third_party/blink/renderer/core/frame/web_frame_serializer_impl.cc
index aa76f1f..c036b7d 100644
--- a/third_party/blink/renderer/core/frame/web_frame_serializer_impl.cc
+++ b/third_party/blink/renderer/core/frame/web_frame_serializer_impl.cc
@@ -111,8 +111,7 @@
   // TODO(yosin) We should call |FrameSerializer::baseTagDeclarationOf()|.
   if (base_target.empty())
     return String("<base href=\".\">");
-  String base_string = "<base href=\".\" target=\"" + base_target + "\">";
-  return base_string;
+  return StrCat({"<base href=\".\" target=\"", base_target, "\">"});
 }
 
 }  // namespace
diff --git a/third_party/blink/renderer/core/frame/window_or_worker_global_scope.cc b/third_party/blink/renderer/core/frame/window_or_worker_global_scope.cc
index e00d094..e27d289f 100644
--- a/third_party/blink/renderer/core/frame/window_or_worker_global_scope.cc
+++ b/third_party/blink/renderer/core/frame/window_or_worker_global_scope.cc
@@ -65,6 +65,7 @@
 
 void WindowOrWorkerGlobalScope::Trace(Visitor* visitor) const {
   visitor->Trace(global_crypto_);
+  visitor->Trace(global_fetch_impl_);
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/frame/window_or_worker_global_scope.h b/third_party/blink/renderer/core/frame/window_or_worker_global_scope.h
index 031e1ed..2c29f77 100644
--- a/third_party/blink/renderer/core/frame/window_or_worker_global_scope.h
+++ b/third_party/blink/renderer/core/frame/window_or_worker_global_scope.h
@@ -44,6 +44,9 @@
 
 class GlobalCrypto;
 
+template <typename T>
+class GlobalFetchImpl;
+
 class CORE_EXPORT WindowOrWorkerGlobalScope : public GarbageCollectedMixin {
  public:
   bool crossOriginIsolated();
@@ -56,13 +59,24 @@
     global_crypto_ = global_crypto;
   }
 
+  ForwardDeclaredMember<GlobalFetchImpl<WindowOrWorkerGlobalScope>>
+  GetGlobalFetchImpl() const {
+    return global_fetch_impl_;
+  }
+  void SetGlobalFetchImpl(
+      ForwardDeclaredMember<GlobalFetchImpl<WindowOrWorkerGlobalScope>>
+          global_fetch_impl) {
+    global_fetch_impl_ = global_fetch_impl;
+  }
+
   void Trace(Visitor*) const override;
 
- protected:
   virtual ExecutionContext* GetExecutionContext() const = 0;
 
  private:
   ForwardDeclaredMember<GlobalCrypto> global_crypto_;
+  ForwardDeclaredMember<GlobalFetchImpl<WindowOrWorkerGlobalScope>>
+      global_fetch_impl_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/html/html_anchor_element.cc b/third_party/blink/renderer/core/html/html_anchor_element.cc
index 48e2c29..208e400 100644
--- a/third_party/blink/renderer/core/html/html_anchor_element.cc
+++ b/third_party/blink/renderer/core/html/html_anchor_element.cc
@@ -33,6 +33,7 @@
 #include "third_party/blink/public/common/features.h"
 #include "third_party/blink/public/common/navigation/impression.h"
 #include "third_party/blink/public/common/switches.h"
+#include "third_party/blink/public/mojom/devtools/inspector_issue.mojom-blink.h"
 #include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-blink.h"
 #include "third_party/blink/public/mojom/input/focus_type.mojom-blink.h"
 #include "third_party/blink/public/platform/platform.h"
diff --git a/third_party/blink/renderer/core/html/parser/html_document_parser.cc b/third_party/blink/renderer/core/html/parser/html_document_parser.cc
index c92eb3f..3d94b6f 100644
--- a/third_party/blink/renderer/core/html/parser/html_document_parser.cc
+++ b/third_party/blink/renderer/core/html/parser/html_document_parser.cc
@@ -117,14 +117,6 @@
   return kEnabled;
 }
 
-bool CheckParserBudgetLessOften() {
-  // Cache the feature value since checking for each parser regresses some micro
-  // benchmarks.
-  static const bool kEnabled =
-      base::FeatureList::IsEnabled(features::kCheckHTMLParserBudgetLessOften);
-  return kEnabled;
-}
-
 bool PrecompileInlineScriptsEnabled(
     FeatureResetMode reset_mode = FeatureResetMode::kUseCached) {
   // Cache the feature value since checking for each parser regresses some micro
@@ -736,7 +728,6 @@
   base::ElapsedTimer chunk_parsing_timer;
   base::TimeDelta elapsed_time;
   unsigned tokens_parsed = 0;
-  int characters_consumed_before_token = 0;
   base::TimeDelta time_executing_script;
   v8::Isolate* isolate = GetDocument()->GetAgent().isolate();
   while (true) {
@@ -783,23 +774,7 @@
       DCHECK_EQ(task_runner_state_->GetMode(), kAllowDeferredParsing);
       if (!RuntimeEnabledFeatures::
               HTMLParserYieldAndDelayOftenForTestingEnabled()) {
-        if (CheckParserBudgetLessOften()) {
-          int newly_consumed_characters =
-              input_.Current().NumberOfCharactersConsumed() -
-              characters_consumed_before_token;
-          characters_consumed_before_token =
-              input_.Current().NumberOfCharactersConsumed();
-          // On android calling chunk_parsing_timer.Elapsed seems fairly slow
-          // compared to the parsing time of small tokens. Only update the
-          // timer occasionally.
-          if (ShouldCheckTimeBudget(next_token_status,
-                                    atomic_html_token.GetHTMLTag(),
-                                    newly_consumed_characters, tokens_parsed)) {
-            elapsed_time = chunk_parsing_timer.Elapsed();
-          }
-        } else {
-          elapsed_time = chunk_parsing_timer.Elapsed();
-        }
+        elapsed_time = chunk_parsing_timer.Elapsed();
         should_yield = elapsed_time >= timed_budget;
       } else {
         should_yield = budget <= 0;
diff --git a/third_party/blink/renderer/core/inspector/inspector_audits_issue.cc b/third_party/blink/renderer/core/inspector/inspector_audits_issue.cc
index a1a56e6..bbb6271 100644
--- a/third_party/blink/renderer/core/inspector/inspector_audits_issue.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_audits_issue.cc
@@ -6,6 +6,7 @@
 
 #include "base/unguessable_token.h"
 #include "services/network/public/mojom/blocked_by_response_reason.mojom-blink.h"
+#include "third_party/blink/public/mojom/devtools/inspector_issue.mojom-blink.h"
 #include "third_party/blink/renderer/bindings/core/v8/capture_source_location.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_security_policy_violation_event_init.h"
 #include "third_party/blink/renderer/core/dom/document.h"
diff --git a/third_party/blink/renderer/core/inspector/inspector_audits_issue.h b/third_party/blink/renderer/core/inspector/inspector_audits_issue.h
index ae579d2..56e3eccc 100644
--- a/third_party/blink/renderer/core/inspector/inspector_audits_issue.h
+++ b/third_party/blink/renderer/core/inspector/inspector_audits_issue.h
@@ -9,17 +9,18 @@
 #include <optional>
 #include <string>
 
-#include "base/unguessable_token.h"
 #include "services/network/public/mojom/blocked_by_response_reason.mojom-forward.h"
-#include "third_party/blink/public/mojom/devtools/inspector_issue.mojom-blink.h"
-#include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-blink.h"
+#include "third_party/blink/public/mojom/devtools/inspector_issue.mojom-blink-forward.h"
+#include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-blink-forward.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/frame/csp/content_security_policy_violation_type.h"
 #include "third_party/blink/renderer/core/inspector/protocol/audits.h"
 #include "third_party/blink/renderer/platform/graphics/dom_node_id.h"
-#include "third_party/blink/renderer/platform/loader/fetch/fetch_initiator_info.h"
 #include "third_party/blink/renderer/platform/wtf/forward.h"
-#include "third_party/blink/renderer/platform/wtf/text/text_position.h"
+
+namespace base {
+class UnguessableToken;
+}  // namespace base
 
 namespace blink {
 
@@ -27,18 +28,16 @@
 class DocumentLoader;
 class Element;
 class ExecutionContext;
-class LocalFrame;
-class ResourceError;
+class KURL;
 class LocalDOMWindow;
 class LocalFrame;
+class ResourceError;
 class SecurityPolicyViolationEventInit;
 class SourceLocation;
 
-namespace protocol {
-namespace Audits {
+namespace protocol::Audits {
 class InspectorIssue;
-}
-}  // namespace protocol
+}  // namespace protocol::Audits
 
 enum class RendererCorsIssueCode {
   kDisallowedByMode,
diff --git a/third_party/blink/renderer/core/layout/inline/justification_utils.cc b/third_party/blink/renderer/core/layout/inline/justification_utils.cc
index 9bf4d451..428cc02 100644
--- a/third_party/blink/renderer/core/layout/inline/justification_utils.cc
+++ b/third_party/blink/renderer/core/layout/inline/justification_utils.cc
@@ -152,14 +152,14 @@
         // |spacing_before| is non-zero if this |item_result| is after
         // non-CJK character. See "text-combine-justify.html".
         DCHECK_EQ(kTextCombineItemMarker, line_text[line_text_offset]);
-        item_result.inline_size += spacing_after;
-        item_result.spacing_before = LayoutUnit(spacing_before);
+        item_result.inline_size += spacing_before + spacing_after;
+        item_result.spacing_before = spacing_before.To<LayoutUnit>();
       } else {
         DCHECK_EQ(uchar::kObjectReplacementCharacter,
                   line_text[line_text_offset]);
-        item_result.inline_size += spacing_after;
+        item_result.inline_size += spacing_before + spacing_after;
         // |spacing_before| is non-zero only before CJK characters.
-        DCHECK_EQ(spacing_before, 0.0f);
+        DCHECK_EQ(spacing_before, TextRunLayoutUnit());
       }
     } else if (item_result.IsRubyColumn()) {
       LineInfo& base_line = item_result.ruby_column->base_line;
@@ -185,7 +185,7 @@
         // ShapeResultSpacing doesn't ask for adding space to OBJECT
         // REPLACEMENT CHARACTER, and asks for adding space to the next item
         // instead.
-        DCHECK_EQ(spacing_before, 0.0f);
+        DCHECK_EQ(spacing_before, TextRunLayoutUnit());
         DCHECK_EQ(spacing_after, TextRunLayoutUnit());
       }
       if (i + 1 < results.size()) {
diff --git a/third_party/blink/renderer/core/layout/layout_iframe.cc b/third_party/blink/renderer/core/layout/layout_iframe.cc
index 7bea5c51..c8a882f 100644
--- a/third_party/blink/renderer/core/layout/layout_iframe.cc
+++ b/third_party/blink/renderer/core/layout/layout_iframe.cc
@@ -33,7 +33,7 @@
     : LayoutEmbeddedContent(element) {}
 
 bool LayoutIFrame::IsResponsivelySized() const {
-  return StyleRef().ContainIntrinsicBlockSize().MatchesElement();
+  return StyleRef().ContainIntrinsicBlockSize().IsFromElement();
 }
 
 void LayoutIFrame::UpdateAfterLayout() {
diff --git a/third_party/blink/renderer/core/loader/beacon_data.cc b/third_party/blink/renderer/core/loader/beacon_data.cc
index 5b89de89..b1fb08e 100644
--- a/third_party/blink/renderer/core/loader/beacon_data.cc
+++ b/third_party/blink/renderer/core/loader/beacon_data.cc
@@ -131,8 +131,7 @@
 BeaconFormData::BeaconFormData(FormData* data)
     : data_(data),
       entity_body_(data_->EncodeMultiPartFormData()),
-      content_type_(StrCat({"multipart/form-data; boundary=",
-                            entity_body_->Boundary().data()})) {}
+      content_type_(entity_body_->FormatContentTypeWithBoundary()) {}
 
 uint64_t BeaconFormData::size() const {
   return entity_body_->SizeInBytes();
diff --git a/third_party/blink/renderer/core/loader/build.gni b/third_party/blink/renderer/core/loader/build.gni
index db93c481..363c747 100644
--- a/third_party/blink/renderer/core/loader/build.gni
+++ b/third_party/blink/renderer/core/loader/build.gni
@@ -140,6 +140,7 @@
   "resource_load_observer_for_frame.h",
   "resource_load_observer_for_worker.cc",
   "resource_load_observer_for_worker.h",
+  "shared_dictionary_hint_type.h",
   "subresource_filter.cc",
   "subresource_filter.h",
   "speculation_rule_loader.h",
diff --git a/third_party/blink/renderer/core/loader/link_loader.cc b/third_party/blink/renderer/core/loader/link_loader.cc
index c135880..f08fd17c 100644
--- a/third_party/blink/renderer/core/loader/link_loader.cc
+++ b/third_party/blink/renderer/core/loader/link_loader.cc
@@ -31,6 +31,7 @@
 
 #include "third_party/blink/renderer/core/loader/link_loader.h"
 
+#include "base/metrics/histogram_functions.h"
 #include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-shared.h"
 #include "third_party/blink/renderer/core/dom/document.h"
 #include "third_party/blink/renderer/core/frame/local_dom_window.h"
@@ -43,6 +44,7 @@
 #include "third_party/blink/renderer/core/loader/preload_helper.h"
 #include "third_party/blink/renderer/core/loader/prerender_handle.h"
 #include "third_party/blink/renderer/core/loader/resource/css_style_sheet_resource.h"
+#include "third_party/blink/renderer/core/loader/shared_dictionary_hint_type.h"
 #include "third_party/blink/renderer/core/page/viewport_description.h"
 #include "third_party/blink/renderer/platform/heap/prefinalizer.h"
 #include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
@@ -137,6 +139,10 @@
     PreloadHelper::PrefetchIfNeeded(params, document, pending_preload_);
   PreloadHelper::ModulePreloadIfNeeded(
       params, document, nullptr /* viewport_description */, pending_preload_);
+  if (params.rel.IsCompressionDictionary()) {
+    base::UmaHistogramEnumeration("Blink.SharedDictionary.Hint.Discovery",
+                                  SharedDictionaryHintType::kHtmlLinkTag);
+  }
   PreloadHelper::FetchCompressionDictionaryIfNeeded(params, document,
                                                     pending_preload_);
 
diff --git a/third_party/blink/renderer/core/loader/link_loader_test.cc b/third_party/blink/renderer/core/loader/link_loader_test.cc
index 88a8597..80243868 100644
--- a/third_party/blink/renderer/core/loader/link_loader_test.cc
+++ b/third_party/blink/renderer/core/loader/link_loader_test.cc
@@ -7,6 +7,7 @@
 #include <memory>
 
 #include "base/task/single_thread_task_runner.h"
+#include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/test_mock_time_task_runner.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -26,6 +27,7 @@
 #include "third_party/blink/renderer/core/loader/modulescript/module_script_fetch_request.h"
 #include "third_party/blink/renderer/core/loader/pending_link_preload.h"
 #include "third_party/blink/renderer/core/loader/resource/link_dictionary_resource.h"
+#include "third_party/blink/renderer/core/loader/shared_dictionary_hint_type.h"
 #include "third_party/blink/renderer/core/testing/dummy_modulator.h"
 #include "third_party/blink/renderer/core/testing/dummy_page_holder.h"
 #include "third_party/blink/renderer/core/testing/scoped_mock_overlay_scrollbars.h"
@@ -751,6 +753,7 @@
 
 TEST_F(DictionaryLinkTest, LoadDictionaryFromLink) {
   static constexpr char href[] = "http://example.test/test.dict";
+  base::HistogramTester histogram_tester;
 
   // Test the cases with a single header
   auto dummy_page_holder =
@@ -777,6 +780,9 @@
   EXPECT_TRUE(resource);
   URLLoaderMockFactory::GetSingletonInstance()
       ->UnregisterAllURLsAndClearMemoryCache();
+  histogram_tester.ExpectUniqueSample("Blink.SharedDictionary.Hint.Discovery",
+                                      SharedDictionaryHintType::kHtmlLinkTag,
+                                      1);
 }
 
 }  // namespace
@@ -812,6 +818,9 @@
 };
 
 TEST_F(DictionaryLoadFromHeaderTest, LoadDictionaryFromHeader) {
+  base::HistogramTester histogram_tester;
+
+  // Test the cases with a single header
   KURL dict_url = KURL(NullURL(), dict_href_);
   ResourceResponse dict_response(dict_url);
   dict_response.SetHttpStatusCode(200);
@@ -830,6 +839,8 @@
   ASSERT_TRUE(dictionary_resource->IsLoaded());
   URLLoaderMockFactory::GetSingletonInstance()
       ->UnregisterAllURLsAndClearMemoryCache();
+  histogram_tester.ExpectUniqueSample("Blink.SharedDictionary.Hint.Discovery",
+                                      SharedDictionaryHintType::kHttpHeader, 1);
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/loader/preload_helper.cc b/third_party/blink/renderer/core/loader/preload_helper.cc
index 2b7efb1..cfc2dabe 100644
--- a/third_party/blink/renderer/core/loader/preload_helper.cc
+++ b/third_party/blink/renderer/core/loader/preload_helper.cc
@@ -47,6 +47,7 @@
 #include "third_party/blink/renderer/core/loader/resource/link_dictionary_resource.h"
 #include "third_party/blink/renderer/core/loader/resource/link_prefetch_resource.h"
 #include "third_party/blink/renderer/core/loader/resource/script_resource.h"
+#include "third_party/blink/renderer/core/loader/shared_dictionary_hint_type.h"
 #include "third_party/blink/renderer/core/page/page.h"
 #include "third_party/blink/renderer/core/page/viewport_description.h"
 #include "third_party/blink/renderer/core/scheduler/scripted_idle_task_controller.h"
@@ -949,6 +950,10 @@
                               pending_preload);
       }
       if (is_compression_dictionary_load_allowed) {
+        if (params.rel.IsCompressionDictionary()) {
+          base::UmaHistogramEnumeration("Blink.SharedDictionary.Hint.Discovery",
+                                        SharedDictionaryHintType::kHttpHeader);
+        }
         FetchCompressionDictionaryIfNeeded(params, *document, pending_preload);
       }
     }
diff --git a/third_party/blink/renderer/core/loader/shared_dictionary_hint_type.h b/third_party/blink/renderer/core/loader/shared_dictionary_hint_type.h
new file mode 100644
index 0000000..8097f33
--- /dev/null
+++ b/third_party/blink/renderer/core/loader/shared_dictionary_hint_type.h
@@ -0,0 +1,23 @@
+// 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 THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_SHARED_DICTIONARY_HINT_TYPE_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_SHARED_DICTIONARY_HINT_TYPE_H_
+
+namespace blink {
+
+// Used for UMA. Logged to "Blink.SharedDictionary.Hint.Discovery".
+// These values are persisted to logs. Entries should not be renumbered and
+// numeric values should never be reused.
+//
+// LINT.IfChange(SharedDictionaryHintType)
+enum class SharedDictionaryHintType {
+  kHtmlLinkTag = 0,
+  kHttpHeader = 1,
+  kMaxValue = kHttpHeader,
+};
+// LINT.ThenChange(//tools/metrics/histograms/metadata/blink/enums.xml:SharedDictionaryHintType)
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_SHARED_DICTIONARY_HINT_TYPE_H_
diff --git a/third_party/blink/renderer/core/paint/contoured_border_geometry.cc b/third_party/blink/renderer/core/paint/contoured_border_geometry.cc
index ff356b4..19cda70 100644
--- a/third_party/blink/renderer/core/paint/contoured_border_geometry.cc
+++ b/third_party/blink/renderer/core/paint/contoured_border_geometry.cc
@@ -12,6 +12,7 @@
 #include "third_party/blink/renderer/platform/geometry/physical_size.h"
 #include "third_party/blink/renderer/platform/transforms/affine_transform.h"
 #include "ui/gfx/geometry/line_f.h"
+#include "ui/gfx/geometry/point.h"
 #include "ui/gfx/geometry/point_f.h"
 #include "ui/gfx/geometry/quad_f.h"
 #include "ui/gfx/geometry/rect_conversions.h"
@@ -55,20 +56,15 @@
   const gfx::PointF perpendicular_line =
       half_corner + gfx::LineF(corner.Outer(), half_corner).Normal();
   const gfx::LineF tangent_line(half_corner, perpendicular_line);
-  auto intersection_point_1 =
-      tangent_line.IntersectionWith({corner.Start(), corner.Center()});
-  auto intersection_point_2 =
-      tangent_line.IntersectionWith({corner.End(), corner.Center()});
-  if (!intersection_point_1 || !intersection_point_2) {
-    // See crbug.com/451657549. This dump should help reproduce the crashing
-    // scenario.
-    DUMP_WILL_BE_CHECK(intersection_point_1 && intersection_point_2)
-        << "Invalid corner for hull detection " << corner.ToString();
-    return gfx::QuadF(corner.BoundingBox());
-  }
+  const gfx::PointF intersection_point_1 =
+      tangent_line.IntersectionWith({corner.Start(), corner.Center()})
+          .value_or(corner.Center());
+  const gfx::PointF intersection_point_2 =
+      tangent_line.IntersectionWith({corner.End(), corner.Center()})
+          .value_or(corner.Center());
 
-  return gfx::QuadF(corner.Start(), *intersection_point_1,
-                    *intersection_point_2, corner.End());
+  return gfx::QuadF(corner.Start(), intersection_point_1, intersection_point_2,
+                    corner.End());
 }
 
 gfx::QuadF ScaleQuadFromOrigin(const gfx::QuadF& quad,
diff --git a/third_party/blink/renderer/core/route_matching/build.gni b/third_party/blink/renderer/core/route_matching/build.gni
index 57bd375..50cb525 100644
--- a/third_party/blink/renderer/core/route_matching/build.gni
+++ b/third_party/blink/renderer/core/route_matching/build.gni
@@ -3,6 +3,7 @@
 # found in the LICENSE file.
 
 blink_core_sources_route_matching = [
+  "navigation_preposition.h",
   "route.cc",
   "route.h",
   "route_event.h",
@@ -10,7 +11,6 @@
   "route_map.h",
   "route_match_state.cc",
   "route_match_state.h",
-  "route_preposition.h",
 ]
 
 blink_core_tests_route_matching = [ "route_map_test.cc" ]
diff --git a/third_party/blink/renderer/core/route_matching/navigation_preposition.h b/third_party/blink/renderer/core/route_matching/navigation_preposition.h
new file mode 100644
index 0000000..139bbaa0
--- /dev/null
+++ b/third_party/blink/renderer/core/route_matching/navigation_preposition.h
@@ -0,0 +1,20 @@
+// 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 THIRD_PARTY_BLINK_RENDERER_CORE_ROUTE_MATCHING_NAVIGATION_PREPOSITION_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_ROUTE_MATCHING_NAVIGATION_PREPOSITION_H_
+
+#include <stdint.h>
+
+namespace blink {
+
+enum class NavigationPreposition : uint8_t {
+  kAt,
+  kFrom,
+  kTo,
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_ROUTE_MATCHING_NAVIGATION_PREPOSITION_H_
diff --git a/third_party/blink/renderer/core/route_matching/route.h b/third_party/blink/renderer/core/route_matching/route.h
index a799766..6441fe2 100644
--- a/third_party/blink/renderer/core/route_matching/route.h
+++ b/third_party/blink/renderer/core/route_matching/route.h
@@ -6,7 +6,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_ROUTE_MATCHING_ROUTE_H_
 
 #include "third_party/blink/renderer/core/dom/events/event_target.h"
-#include "third_party/blink/renderer/core/route_matching/route_preposition.h"
+#include "third_party/blink/renderer/core/route_matching/navigation_preposition.h"
 #include "third_party/blink/renderer/platform/heap/collection_support/heap_vector.h"
 #include "third_party/blink/renderer/platform/heap/member.h"
 
@@ -26,13 +26,13 @@
   URLPattern* pattern() const;
   bool matches() const { return matches_at_; }
 
-  bool Matches(RoutePreposition preposition) const {
+  bool Matches(NavigationPreposition preposition) const {
     switch (preposition) {
-      case RoutePreposition::kAt:
+      case NavigationPreposition::kAt:
         return matches_at_;
-      case RoutePreposition::kFrom:
+      case NavigationPreposition::kFrom:
         return matches_from_;
-      case RoutePreposition::kTo:
+      case NavigationPreposition::kTo:
         return matches_to_;
     }
   }
diff --git a/third_party/blink/renderer/core/route_matching/route_map.cc b/third_party/blink/renderer/core/route_matching/route_map.cc
index 1700c3c7..9e0c514 100644
--- a/third_party/blink/renderer/core/route_matching/route_map.cc
+++ b/third_party/blink/renderer/core/route_matching/route_map.cc
@@ -198,12 +198,12 @@
     changed = route.UpdateMatchStatus(previous_url_, next_url_) || changed;
   }
   if (changed) {
-    GetDocument().GetStyleEngine().RoutesMayHaveChanged();
+    GetDocument().GetStyleEngine().NavigationsMayHaveChanged();
   }
 }
 
 void RouteMap::GetActiveRoutes(
-    RoutePreposition preposition,
+    NavigationPreposition preposition,
     RouteMatchState::MatchCollection* collection) const {
   collection->clear();
   for (const auto& entry : routes_) {
diff --git a/third_party/blink/renderer/core/route_matching/route_map.h b/third_party/blink/renderer/core/route_matching/route_map.h
index e3a4b82..6023f3e 100644
--- a/third_party/blink/renderer/core/route_matching/route_map.h
+++ b/third_party/blink/renderer/core/route_matching/route_map.h
@@ -6,8 +6,8 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_ROUTE_MATCHING_ROUTE_MAP_H_
 
 #include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/core/route_matching/navigation_preposition.h"
 #include "third_party/blink/renderer/core/route_matching/route_match_state.h"
-#include "third_party/blink/renderer/core/route_matching/route_preposition.h"
 #include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
 #include "third_party/blink/renderer/platform/heap/collection_support/heap_hash_map.h"
 #include "third_party/blink/renderer/platform/heap/collection_support/heap_hash_set.h"
@@ -88,7 +88,7 @@
   // changed.
   void UpdateActiveRoutes();
 
-  void GetActiveRoutes(RoutePreposition,
+  void GetActiveRoutes(NavigationPreposition,
                        RouteMatchState::MatchCollection*) const;
 
   // Set the URLs that we're navigating between at the start of navigation. This
diff --git a/third_party/blink/renderer/core/route_matching/route_map_test.cc b/third_party/blink/renderer/core/route_matching/route_map_test.cc
index bb7b782f..de849eca 100644
--- a/third_party/blink/renderer/core/route_matching/route_map_test.cc
+++ b/third_party/blink/renderer/core/route_matching/route_map_test.cc
@@ -46,16 +46,16 @@
   const Route* route2 = route_map.FindRoute("route2");
   ASSERT_TRUE(route2);
 
-  EXPECT_TRUE(route1->Matches(RoutePreposition::kAt));
-  EXPECT_FALSE(route2->Matches(RoutePreposition::kAt));
+  EXPECT_TRUE(route1->Matches(NavigationPreposition::kAt));
+  EXPECT_FALSE(route2->Matches(NavigationPreposition::kAt));
 
   SetURL("https://example.com/bar");
-  EXPECT_FALSE(route1->Matches(RoutePreposition::kAt));
-  EXPECT_TRUE(route2->Matches(RoutePreposition::kAt));
+  EXPECT_FALSE(route1->Matches(NavigationPreposition::kAt));
+  EXPECT_TRUE(route2->Matches(NavigationPreposition::kAt));
 
   SetURL("https://example.com/baz");
-  EXPECT_FALSE(route1->Matches(RoutePreposition::kAt));
-  EXPECT_TRUE(route2->Matches(RoutePreposition::kAt));
+  EXPECT_FALSE(route1->Matches(NavigationPreposition::kAt));
+  EXPECT_TRUE(route2->Matches(NavigationPreposition::kAt));
 }
 
 TEST_F(RouteMapTest, GetActiveRoutes) {
@@ -80,11 +80,11 @@
   })");
 
   RouteMatchState::MatchCollection collection;
-  route_map.GetActiveRoutes(RoutePreposition::kAt, &collection);
+  route_map.GetActiveRoutes(NavigationPreposition::kAt, &collection);
   EXPECT_EQ(2u, collection.size());
 
   SetURL("https://example.com/bar");
-  route_map.GetActiveRoutes(RoutePreposition::kAt, &collection);
+  route_map.GetActiveRoutes(NavigationPreposition::kAt, &collection);
   EXPECT_EQ(1u, collection.size());
 }
 
diff --git a/third_party/blink/renderer/core/route_matching/route_match_state.cc b/third_party/blink/renderer/core/route_matching/route_match_state.cc
index 2118d75e..a56975b6 100644
--- a/third_party/blink/renderer/core/route_matching/route_match_state.cc
+++ b/third_party/blink/renderer/core/route_matching/route_match_state.cc
@@ -11,9 +11,9 @@
 
 RouteMatchState* RouteMatchState::Create(const RouteMap& map) {
   RouteMatchState* state = MakeGarbageCollected<RouteMatchState>();
-  map.GetActiveRoutes(RoutePreposition::kAt, &state->at_routes_);
-  map.GetActiveRoutes(RoutePreposition::kFrom, &state->from_routes_);
-  map.GetActiveRoutes(RoutePreposition::kTo, &state->to_routes_);
+  map.GetActiveRoutes(NavigationPreposition::kAt, &state->at_routes_);
+  map.GetActiveRoutes(NavigationPreposition::kFrom, &state->from_routes_);
+  map.GetActiveRoutes(NavigationPreposition::kTo, &state->to_routes_);
   return state;
 }
 
diff --git a/third_party/blink/renderer/core/route_matching/route_preposition.h b/third_party/blink/renderer/core/route_matching/route_preposition.h
deleted file mode 100644
index e3d3418d..0000000
--- a/third_party/blink/renderer/core/route_matching/route_preposition.h
+++ /dev/null
@@ -1,20 +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 THIRD_PARTY_BLINK_RENDERER_CORE_ROUTE_MATCHING_ROUTE_PREPOSITION_H_
-#define THIRD_PARTY_BLINK_RENDERER_CORE_ROUTE_MATCHING_ROUTE_PREPOSITION_H_
-
-#include <stdint.h>
-
-namespace blink {
-
-enum class RoutePreposition : uint8_t {
-  kAt,
-  kFrom,
-  kTo,
-};
-
-}  // namespace blink
-
-#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_ROUTE_MATCHING_ROUTE_PREPOSITION_H_
diff --git a/third_party/blink/renderer/core/style/style_intrinsic_length.h b/third_party/blink/renderer/core/style/style_intrinsic_length.h
index 068a3b0b..1625d9a 100644
--- a/third_party/blink/renderer/core/style/style_intrinsic_length.h
+++ b/third_party/blink/renderer/core/style/style_intrinsic_length.h
@@ -17,10 +17,10 @@
   // Style data for contain-intrinsic-size:
   //  none | <length> | auto && <length> | auto && none | from-element.
   StyleIntrinsicLength(bool has_auto,
-                       bool matches_element,
+                       bool is_from_element,
                        const std::optional<Length>& length)
       : has_auto_(has_auto),
-        matches_element_(matches_element),
+        is_from_element_(is_from_element),
         length_(length) {}
 
   StyleIntrinsicLength() = default;
@@ -28,24 +28,24 @@
   // This returns true if the value is "none" without auto. It's not named
   // "IsNone" to avoid confusion with "auto none" grammar.
   bool IsNoOp() const {
-    return !has_auto_ && !matches_element_ && !length_.has_value();
+    return !has_auto_ && !is_from_element_ && !length_.has_value();
   }
 
   bool HasAuto() const { return has_auto_; }
-  bool MatchesElement() const { return matches_element_; }
+  bool IsFromElement() const { return is_from_element_; }
 
   void SetHasAuto() { has_auto_ = true; }
 
   const std::optional<Length>& GetLength() const { return length_; }
 
   bool operator==(const StyleIntrinsicLength& o) const {
-    return has_auto_ == o.has_auto_ && matches_element_ == o.matches_element_ &&
+    return has_auto_ == o.has_auto_ && is_from_element_ == o.is_from_element_ &&
            length_ == o.length_;
   }
 
  private:
   bool has_auto_ = false;
-  bool matches_element_ = false;
+  bool is_from_element_ = false;
   std::optional<Length> length_;
 };
 
diff --git a/third_party/blink/renderer/core/svg/svg_animated_length.cc b/third_party/blink/renderer/core/svg/svg_animated_length.cc
index 13148eb..d3f0594 100644
--- a/third_party/blink/renderer/core/svg/svg_animated_length.cc
+++ b/third_party/blink/renderer/core/svg/svg_animated_length.cc
@@ -75,7 +75,7 @@
       return nullptr;
     }
   }
-  return &CurrentValue()->AsCSSPrimitiveValue();
+  return &CurrentValue()->AsCSSValue();
 }
 
 void SVGAnimatedLength::Trace(Visitor* visitor) const {
diff --git a/third_party/blink/renderer/core/svg/svg_length.cc b/third_party/blink/renderer/core/svg/svg_length.cc
index 95da2eb..bb6bc9e 100644
--- a/third_party/blink/renderer/core/svg/svg_length.cc
+++ b/third_party/blink/renderer/core/svg/svg_length.cc
@@ -24,8 +24,10 @@
 #include "third_party/blink/renderer/core/css/css_math_function_value.h"
 #include "third_party/blink/renderer/core/css/css_numeric_literal_value.h"
 #include "third_party/blink/renderer/core/css/css_primitive_value.h"
+#include "third_party/blink/renderer/core/css/css_unparsed_declaration_value.h"
 #include "third_party/blink/renderer/core/css/css_value.h"
 #include "third_party/blink/renderer/core/css/parser/css_parser.h"
+#include "third_party/blink/renderer/core/css/parser/css_variable_parser.h"
 #include "third_party/blink/renderer/core/execution_context/security_context.h"
 #include "third_party/blink/renderer/core/svg/animation/smil_animation_effect_parameters.h"
 #include "third_party/blink/renderer/core/svg/svg_length_context.h"
@@ -35,7 +37,6 @@
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
 
 namespace blink {
-
 namespace {
 
 #define CAST_UNIT(unit) \
@@ -75,6 +76,35 @@
       entry.value, static_cast<CSSPrimitiveValue::UnitType>(entry.unit));
 }
 
+bool IsSupportedCSSUnitType(CSSPrimitiveValue::UnitType type) {
+  return (CSSPrimitiveValue::IsLength(type) ||
+          type == CSSPrimitiveValue::UnitType::kNumber ||
+          type == CSSPrimitiveValue::UnitType::kPercentage) &&
+         type != CSSPrimitiveValue::UnitType::kQuirkyEms;
+}
+
+bool IsSupportedCalculationCategory(CalculationResultCategory category) {
+  switch (category) {
+    case kCalcLength:
+    case kCalcNumber:
+    case kCalcPercent:
+    case kCalcLengthFunction:
+      return true;
+    default:
+      return false;
+  }
+}
+
+bool AllowedCSSValueForSVGLength(const CSSValue& value) {
+  if (auto* numeric_value = DynamicTo<CSSNumericLiteralValue>(value)) {
+    return IsSupportedCSSUnitType(numeric_value->GetType());
+  }
+  if (auto* math_value = DynamicTo<CSSMathFunctionValue>(value)) {
+    return IsSupportedCalculationCategory(math_value->Category());
+  }
+  return value.IsUnparsedDeclaration();
+}
+
 }  // namespace
 
 SVGLength::SVGLength(SVGLengthMode mode)
@@ -86,9 +116,10 @@
 SVGLength::SVGLength(Initial initial, SVGLengthMode mode)
     : SVGLength(CreateInitialCSSValue(initial), mode) {}
 
-SVGLength::SVGLength(const CSSPrimitiveValue& value, SVGLengthMode mode)
+SVGLength::SVGLength(const CSSValue& value, SVGLengthMode mode)
     : value_(value), unit_mode_(static_cast<unsigned>(mode)) {
   DCHECK_EQ(UnitMode(), mode);
+  DCHECK(AllowedCSSValueForSVGLength(value));
 }
 
 void SVGLength::Trace(Visitor* visitor) const {
@@ -106,22 +137,39 @@
 
 Length SVGLength::ConvertToLength(
     const SVGLengthConversionData& conversion_data) const {
-  return value_->ConvertToLength(conversion_data);
+  const CSSValue* resolved_value =
+      conversion_data.MaybeResolveUnparsedValue(*value_);
+  if (!resolved_value) {
+    return Length::Fixed(0);
+  }
+  return To<CSSPrimitiveValue>(*resolved_value)
+      .ConvertToLength(conversion_data);
 }
 
 float SVGLength::Value(const SVGLengthConversionData& conversion_data,
                        float dimension) const {
-  return FloatValueForLength(value_->ConvertToLength(conversion_data),
-                             dimension);
+  const CSSValue* resolved_value =
+      conversion_data.MaybeResolveUnparsedValue(*value_);
+  if (!resolved_value) {
+    return 0;
+  }
+  return FloatValueForLength(
+      To<CSSPrimitiveValue>(*resolved_value).ConvertToLength(conversion_data),
+      dimension);
 }
 
 float SVGLength::Value(const SVGLengthContext& context) const {
-  if (const auto* math_function = DynamicTo<CSSMathFunctionValue>(*value_)) {
+  const CSSValue* resolved_value = context.MaybeResolveUnparsedValue(*value_);
+  if (!resolved_value) {
+    return 0;
+  }
+  if (const auto* math_function =
+          DynamicTo<CSSMathFunctionValue>(*resolved_value)) {
     return context.ResolveValue(*math_function, UnitMode());
   }
-  return context.ConvertValueToUserUnits(
-      To<CSSNumericLiteralValue>(*value_).DoubleValue(), UnitMode(),
-      NumericLiteralType());
+  const auto& numeric_literal = To<CSSNumericLiteralValue>(*resolved_value);
+  return context.ConvertValueToUserUnits(numeric_literal.DoubleValue(),
+                                         UnitMode(), numeric_literal.GetType());
 }
 
 void SVGLength::SetValueAsNumber(float value) {
@@ -139,27 +187,11 @@
     return true;
   // TODO(crbug.com/979895): This is the result of a refactoring, which might
   // have revealed an existing bug with relative units in math functions.
-  return !IsCalculated() &&
-         CSSPrimitiveValue::IsRelativeUnit(NumericLiteralType());
-}
-
-static bool IsSupportedCSSUnitType(CSSPrimitiveValue::UnitType type) {
-  return (CSSPrimitiveValue::IsLength(type) ||
-          type == CSSPrimitiveValue::UnitType::kNumber ||
-          type == CSSPrimitiveValue::UnitType::kPercentage) &&
-         type != CSSPrimitiveValue::UnitType::kQuirkyEms;
-}
-
-static bool IsSupportedCalculationCategory(CalculationResultCategory category) {
-  switch (category) {
-    case kCalcLength:
-    case kCalcNumber:
-    case kCalcPercent:
-    case kCalcLengthFunction:
-      return true;
-    default:
-      return false;
+  if (!IsNumericValue()) {
+    return false;
   }
+
+  return CSSPrimitiveValue::IsRelativeUnit(NumericLiteralType());
 }
 
 namespace {
@@ -190,8 +222,32 @@
   const CSSValue* parsed = CSSParser::ParseSingleValue(
       CSSPropertyID::kX, string, GetSVGAttributeParserContext());
   const auto* new_value = DynamicTo<CSSPrimitiveValue>(parsed);
-  if (!new_value)
-    return SVGParseStatus::kExpectedLength;
+  if (!new_value) {
+    if (RuntimeEnabledFeatures::SvgLengthResolveUnparsedValueEnabled()) {
+      CSSParserTokenStream stream(string);
+      stream.EnsureLookAhead();
+      bool important = false;
+      CSSVariableData* variable_data =
+          CSSVariableParser::ConsumeUnparsedDeclaration(
+              stream,
+              /*allow_important_annotation=*/true,
+              /*is_animation_tainted=*/false,
+              /*must_contain_variable_reference=*/true,
+              /*restricted_value=*/true, /*comma_ends_declaration=*/false,
+              important, *GetSVGAttributeParserContext());
+      if (!variable_data || important) {
+        return SVGParseStatus::kExpectedLength;
+      }
+
+      auto* unparsed_value = MakeGarbageCollected<CSSUnparsedDeclarationValue>(
+          variable_data, GetSVGAttributeParserContext());
+
+      value_ = unparsed_value;
+      return SVGParseStatus::kNoError;
+    } else {
+      return SVGParseStatus::kExpectedLength;
+    }
+  }
 
   if (const auto* math_value = DynamicTo<CSSMathFunctionValue>(new_value)) {
     if (!IsSupportedCalculationCategory(math_value->Category()))
@@ -207,7 +263,7 @@
 }
 
 String SVGLength::ValueAsString() const {
-  return value_->CustomCSSText();
+  return value_->CssText();
 }
 
 void SVGLength::NewValueSpecifiedUnits(CSSPrimitiveValue::UnitType type,
@@ -275,7 +331,7 @@
   const SVGLength* unit_determining_length =
       (percentage < 0.5) ? from_length : to_length;
   CSSPrimitiveValue::UnitType result_unit =
-      !unit_determining_length->IsCalculated()
+      unit_determining_length->IsNumericValue()
           ? unit_determining_length->NumericLiteralType()
           : CSSPrimitiveValue::UnitType::kUserUnits;
 
@@ -300,7 +356,11 @@
 }
 
 bool SVGLength::IsNegativeNumericLiteral() const {
-  std::optional<double> value = value_->GetValueIfKnown();
+  if (!value_->IsPrimitiveValue()) {
+    return false;
+  }
+  std::optional<double> value =
+      To<CSSPrimitiveValue>(*value_).GetValueIfKnown();
   return value && *value < 0.0;
 }
 
diff --git a/third_party/blink/renderer/core/svg/svg_length.h b/third_party/blink/renderer/core/svg/svg_length.h
index 36ed845..be1c5e5 100644
--- a/third_party/blink/renderer/core/svg/svg_length.h
+++ b/third_party/blink/renderer/core/svg/svg_length.h
@@ -59,7 +59,7 @@
 
   explicit SVGLength(SVGLengthMode = SVGLengthMode::kOther);
   SVGLength(Initial, SVGLengthMode);
-  SVGLength(const CSSPrimitiveValue&, SVGLengthMode);
+  SVGLength(const CSSValue&, SVGLengthMode);
 
   void SetInitial(unsigned);
 
@@ -89,7 +89,7 @@
   void SetValueAsNumber(float);
   void SetValueInSpecifiedUnits(float value);
 
-  const CSSPrimitiveValue& AsCSSPrimitiveValue() const { return *value_; }
+  const CSSValue& AsCSSValue() const { return *value_; }
 
   String ValueAsString() const override;
   SVGParsingError SetValueAsString(const String&);
@@ -107,13 +107,21 @@
     return value_->IsNumericLiteralValue() &&
            To<CSSNumericLiteralValue>(*value_).IsFontRelativeLength();
   }
-  bool IsCalculated() const { return value_->IsCalculated(); }
-  bool IsPercentage() const { return value_->IsPercentage(); }
+  bool IsCalculated() const {
+    return value_->IsPrimitiveValue() &&
+           To<CSSPrimitiveValue>(*value_).IsCalculated();
+  }
+  bool IsPercentage() const {
+    return value_->IsPrimitiveValue() &&
+           To<CSSPrimitiveValue>(*value_).IsPercentage();
+  }
   bool HasContainerRelativeUnits() const {
-    return value_->HasContainerRelativeUnits();
+    return value_->IsPrimitiveValue() &&
+           To<CSSPrimitiveValue>(*value_).HasContainerRelativeUnits();
   }
 
   bool IsNegativeNumericLiteral() const;
+  bool IsNumericValue() const { return value_->IsNumericLiteralValue(); }
 
   static bool NegativeValuesForbiddenForAnimatedLengthAttribute(
       const QualifiedName&);
@@ -134,7 +142,7 @@
   AnimatedPropertyType GetType() const override { return ClassType(); }
 
  private:
-  Member<const CSSPrimitiveValue> value_;
+  Member<const CSSValue> value_;
   unsigned unit_mode_ : 2;
 };
 
diff --git a/third_party/blink/renderer/core/svg/svg_length_context.cc b/third_party/blink/renderer/core/svg/svg_length_context.cc
index bb70a69..d88ab45 100644
--- a/third_party/blink/renderer/core/svg/svg_length_context.cc
+++ b/third_party/blink/renderer/core/svg/svg_length_context.cc
@@ -24,7 +24,11 @@
 
 #include "third_party/blink/renderer/core/css/css_math_function_value.h"
 #include "third_party/blink/renderer/core/css/css_resolution_units.h"
+#include "third_party/blink/renderer/core/css/css_unparsed_declaration_value.h"
+#include "third_party/blink/renderer/core/css/parser/css_parser.h"
+#include "third_party/blink/renderer/core/css/resolver/style_cascade.h"
 #include "third_party/blink/renderer/core/css/resolver/style_resolver.h"
+#include "third_party/blink/renderer/core/css/resolver/style_resolver_state.h"
 #include "third_party/blink/renderer/core/layout/layout_object.h"
 #include "third_party/blink/renderer/core/style/computed_style.h"
 #include "third_party/blink/renderer/core/svg/svg_element.h"
@@ -44,6 +48,45 @@
   return nullptr;
 }
 
+const CSSValue* MaybeResolveUnparsedValueInternal(const Element* context,
+                                                  const CSSValue& value) {
+  const auto* unparsed_value = DynamicTo<CSSUnparsedDeclarationValue>(value);
+  if (!unparsed_value) {
+    return &value;
+  }
+
+  if (!context) {
+    return nullptr;
+  }
+
+  DCHECK(!context->GetDocument().InStyleRecalc());
+  // TODO: The const_cast here is currently safe because
+  // `StyleCascade::ResolveSubstitutions` does not modify the element.
+  // However, if this API changes in the future, this code will need to be
+  // updated accordingly.
+  StyleResolverState state(context->GetDocument(),
+                           const_cast<Element&>(*context));
+  state.EnsureParentStyle();
+  const ComputedStyle* computed_style = context->GetComputedStyle();
+  if (!computed_style) {
+    return nullptr;
+  }
+  state.CreateNewClonedStyle(*computed_style);
+  const CSSUnparsedDeclarationValue* substituted =
+      StyleCascade::ResolveSubstitutions(state, *unparsed_value,
+                                         &context->GetDocument(),
+                                         /*env_bindings=*/nullptr);
+
+  if (substituted) {
+    return CSSParser::ParseSingleValue(CSSPropertyID::kX,
+                                       substituted->CssText(),
+                                       unparsed_value->ParserContext());
+  }
+
+  // Could not resolve the unparsed value.
+  return nullptr;
+}
+
 }  // namespace
 
 SVGLengthConversionData::SVGLengthConversionData(const Element& context,
@@ -212,4 +255,14 @@
   return ClampTo<float>(value / reference);
 }
 
+const CSSValue* SVGLengthContext::MaybeResolveUnparsedValue(
+    const CSSValue& value) const {
+  return MaybeResolveUnparsedValueInternal(context_, value);
+}
+
+const CSSValue* SVGLengthConversionData::MaybeResolveUnparsedValue(
+    const CSSValue& value) const {
+  return MaybeResolveUnparsedValueInternal(GetElement(), value);
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/svg/svg_length_context.h b/third_party/blink/renderer/core/svg/svg_length_context.h
index 94ed66d..0f957c2 100644
--- a/third_party/blink/renderer/core/svg/svg_length_context.h
+++ b/third_party/blink/renderer/core/svg/svg_length_context.h
@@ -28,6 +28,7 @@
 
 class CSSMathFunctionValue;
 class ComputedStyle;
+class CSSValue;
 class Element;
 class LayoutObject;
 class SVGElement;
@@ -48,6 +49,8 @@
                                   CSSPrimitiveValue::UnitType to_unit) const;
   float ResolveValue(const CSSMathFunctionValue&, SVGLengthMode) const;
 
+  const CSSValue* MaybeResolveUnparsedValue(const CSSValue& value) const;
+
   static const ComputedStyle* ComputedStyleForLengthResolving(
       const SVGElement&);
 
@@ -67,6 +70,8 @@
   SVGLengthConversionData(const Element& context, const ComputedStyle& style);
   explicit SVGLengthConversionData(const LayoutObject& object);
 
+  const CSSValue* MaybeResolveUnparsedValue(const CSSValue& value) const;
+
  private:
   CSSToLengthConversionData::Flags ignored_flags_ = 0;
 };
diff --git a/third_party/blink/renderer/core/svg/svg_length_tear_off.cc b/third_party/blink/renderer/core/svg/svg_length_tear_off.cc
index 63d7d062..89ee2a5c 100644
--- a/third_party/blink/renderer/core/svg/svg_length_tear_off.cc
+++ b/third_party/blink/renderer/core/svg/svg_length_tear_off.cc
@@ -95,8 +95,9 @@
 }
 
 bool HasExposedLengthUnit(const SVGLength& length) {
-  if (length.IsCalculated())
+  if (!length.IsNumericValue()) {
     return false;
+  }
 
   CSSPrimitiveValue::UnitType unit = length.NumericLiteralType();
   return IsValidLengthUnit(unit) ||
@@ -123,7 +124,7 @@
   if (!length.IsRelative()) {
     return true;
   }
-  const bool needs_layout = length.IsPercentage() || length.IsCalculated();
+  const bool needs_layout = length.IsPercentage() || !length.IsNumericValue();
   return EnsureResolvable(context_element, needs_layout);
 }
 
@@ -135,7 +136,7 @@
     return true;
   }
   const bool needs_layout =
-      length.IsPercentage() || length.IsCalculated() ||
+      length.IsPercentage() || !length.IsNumericValue() ||
       other_unit_type == CSSPrimitiveValue::UnitType::kPercentage;
   return EnsureResolvable(context_element, needs_layout);
 }
@@ -158,6 +159,11 @@
 }
 
 float SVGLengthTearOff::value(ExceptionState& exception_state) {
+  // Return 0 for unparsed values.
+  // See https://github.com/w3c/svgwg/issues/1038
+  if (Target()->AsCSSValue().IsUnparsedDeclaration()) {
+    return 0;
+  }
   SVGElement* context_element = ContextElement();
   if (!EnsureResolvable(*Target(), context_element)) {
     ThrowUnresolvableRelativeLength(exception_state);
@@ -172,7 +178,7 @@
     ThrowReadOnly(exception_state);
     return;
   }
-  if (Target()->IsCalculated() || Target()->HasContainerRelativeUnits()) {
+  if (!Target()->IsNumericValue() || Target()->HasContainerRelativeUnits()) {
     Target()->SetValueAsNumber(value);
   } else {
     SVGElement* context_element = ContextElement();
@@ -188,8 +194,9 @@
 }
 
 float SVGLengthTearOff::valueInSpecifiedUnits() {
-  if (Target()->IsCalculated())
+  if (!Target()->IsNumericValue()) {
     return 0;
+  }
   return Target()->ValueInSpecifiedUnits();
 }
 
@@ -200,10 +207,11 @@
     ThrowReadOnly(exception_state);
     return;
   }
-  if (Target()->IsCalculated())
+  if (!Target()->IsNumericValue()) {
     Target()->SetValueAsNumber(value);
-  else
+  } else {
     Target()->SetValueInSpecifiedUnits(value);
+  }
   CommitChange(SVGPropertyCommitReason::kUpdated);
 }
 
@@ -260,6 +268,12 @@
                 String::Number(unit_type), ")."}));
     return;
   }
+  // Cannot convert unparsed values.
+  // See https://github.com/w3c/svgwg/issues/1038
+  if (Target()->AsCSSValue().IsUnparsedDeclaration()) {
+    ThrowUnresolvableRelativeLength(exception_state);
+    return;
+  }
   SVGElement* context_element = ContextElement();
   if (!EnsureResolvable(*Target(), ToCSSUnitType(unit_type), context_element)) {
     ThrowUnresolvableRelativeLength(exception_state);
diff --git a/third_party/blink/renderer/core/workers/worker_global_scope.cc b/third_party/blink/renderer/core/workers/worker_global_scope.cc
index 5670eca..020f2f1 100644
--- a/third_party/blink/renderer/core/workers/worker_global_scope.cc
+++ b/third_party/blink/renderer/core/workers/worker_global_scope.cc
@@ -834,7 +834,6 @@
   visitor->Trace(trusted_types_);
   visitor->Trace(worker_script_);
   visitor->Trace(browser_interface_broker_proxy_);
-  visitor->Trace(global_fetch_impl_);
   visitor->Trace(global_cache_storage_impl_);
   visitor->Trace(global_cookie_store_impl_);
   visitor->Trace(global_performance_impl_);
diff --git a/third_party/blink/renderer/core/workers/worker_global_scope.h b/third_party/blink/renderer/core/workers/worker_global_scope.h
index 88e7af02..b295b34 100644
--- a/third_party/blink/renderer/core/workers/worker_global_scope.h
+++ b/third_party/blink/renderer/core/workers/worker_global_scope.h
@@ -81,8 +81,6 @@
 class FontFaceSetWorker;
 
 template <typename T>
-class GlobalFetchImpl;
-template <typename T>
 class GlobalCacheStorageImpl;
 template <typename T>
 class GlobalCookieStoreImpl;
@@ -285,16 +283,6 @@
     return top_level_frame_security_origin_.get();
   }
 
-  ForwardDeclaredMember<GlobalFetchImpl<WorkerGlobalScope>> GetGlobalFetchImpl()
-      const {
-    return global_fetch_impl_;
-  }
-  void SetGlobalFetchImpl(
-      ForwardDeclaredMember<GlobalFetchImpl<WorkerGlobalScope>>
-          global_fetch_impl) {
-    global_fetch_impl_ = global_fetch_impl;
-  }
-
   ForwardDeclaredMember<GlobalCacheStorageImpl<WorkerGlobalScope>>
   GetGlobalCacheStorageImpl() const {
     return global_cache_storage_impl_;
@@ -457,7 +445,6 @@
   // origin.
   scoped_refptr<const SecurityOrigin> top_level_frame_security_origin_;
 
-  ForwardDeclaredMember<GlobalFetchImpl<WorkerGlobalScope>> global_fetch_impl_;
   ForwardDeclaredMember<GlobalCacheStorageImpl<WorkerGlobalScope>>
       global_cache_storage_impl_;
   ForwardDeclaredMember<GlobalCookieStoreImpl<WorkerGlobalScope>>
diff --git a/third_party/blink/renderer/modules/accessibility/ax_block_flow_iterator.cc b/third_party/blink/renderer/modules/accessibility/ax_block_flow_iterator.cc
index adf61cb..d5d974d 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_block_flow_iterator.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_block_flow_iterator.cc
@@ -177,7 +177,11 @@
     switch (it->Type()) {
       case FragmentItem::kText:
       case FragmentItem::kGeneratedText:
-        if (it->GetLayoutObject()->IsText()) {
+        if (!it->GetLayoutObject()) [[unlikely]] {
+          // A generated text fragment item may not have a backing LayoutObject.
+          // For regular text, we expect them to always have one.
+          CHECK_EQ(it->Type(), FragmentItem::kGeneratedText);
+        } else if (it->GetLayoutObject()->IsText()) {
           // TODO(accessibility): Investigate when an item can be text, but its
           // LayoutObject is not marked as such.
           return std::make_pair(it->GetLayoutObject(), index);
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object.cc b/third_party/blink/renderer/modules/accessibility/ax_object.cc
index 4515611..6579f1b 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_object.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_object.cc
@@ -2458,12 +2458,14 @@
   }
 
   // Add aria-details for the element anchored to this object.
-  if (AXObject* positioned_obj = GetPositionedObjectForAnchor(node_data)) {
-    node_data->AddIntListAttribute(
-        ax::mojom::blink::IntListAttribute::kDetailsIds,
-        {static_cast<int32_t>(positioned_obj->AXObjectID())});
-    node_data->SetDetailsFrom(ax::mojom::blink::DetailsFrom::kCssAnchor);
-    return;
+  if (!RuntimeEnabledFeatures::NoAriaDetailsForAnchorPosEnabled()) {
+    if (AXObject* positioned_obj = GetPositionedObjectForAnchor(node_data)) {
+      node_data->AddIntListAttribute(
+          ax::mojom::blink::IntListAttribute::kDetailsIds,
+          {static_cast<int32_t>(positioned_obj->AXObjectID())});
+      node_data->SetDetailsFrom(ax::mojom::blink::DetailsFrom::kCssAnchor);
+      return;
+    }
   }
 
   // Add aria-details for a scroll marker pseudo-element.
@@ -2627,6 +2629,7 @@
 }
 
 AXObject* AXObject::GetPositionedObjectForAnchor(ui::AXNodeData* data) const {
+  CHECK(!RuntimeEnabledFeatures::NoAriaDetailsForAnchorPosEnabled());
   AXObject* positioned_obj = AXObjectCache().GetPositionedObjectForAnchor(this);
   if (!positioned_obj) {
     return nullptr;
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc b/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc
index 389c60c4..699cdcec 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc
@@ -1275,6 +1275,7 @@
 }
 
 AXObject* AXObjectCacheImpl::GetPositionedObjectForAnchor(const AXObject* obj) {
+  CHECK(!RuntimeEnabledFeatures::NoAriaDetailsForAnchorPosEnabled());
   return relation_cache_->GetPositionedObjectForAnchor(obj);
 }
 
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object_cache_test.cc b/third_party/blink/renderer/modules/accessibility/ax_object_cache_test.cc
index ec4a2ae..76270c6 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_object_cache_test.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_object_cache_test.cc
@@ -485,6 +485,10 @@
 };
 
 TEST_F(AccessibilityEnabledLaterTest, CSSAnchorPositioning) {
+  if (RuntimeEnabledFeatures::NoAriaDetailsForAnchorPosEnabled()) {
+    // This test can be removed when this flag is removed.
+    return;
+  }
   SetHtmlInnerHTML(R"HTML(
     <style>
       .anchor {
diff --git a/third_party/blink/renderer/modules/accessibility/ax_relation_cache.cc b/third_party/blink/renderer/modules/accessibility/ax_relation_cache.cc
index 26b10683..a8912447 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_relation_cache.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_relation_cache.cc
@@ -1362,6 +1362,7 @@
 
 AXObject* AXRelationCache::GetPositionedObjectForAnchor(
     const AXObject* anchor) {
+  CHECK(!RuntimeEnabledFeatures::NoAriaDetailsForAnchorPosEnabled());
   HashMap<AXID, AXID>::const_iterator iter =
       anchor_to_positioned_obj_mapping_.find(anchor->AXObjectID());
   if (iter == anchor_to_positioned_obj_mapping_.end()) {
diff --git a/third_party/blink/renderer/modules/file_system_access/file_system_access_regular_file_delegate.cc b/third_party/blink/renderer/modules/file_system_access/file_system_access_regular_file_delegate.cc
index 74197ed..a05dae06 100644
--- a/third_party/blink/renderer/modules/file_system_access/file_system_access_regular_file_delegate.cc
+++ b/third_party/blink/renderer/modules/file_system_access/file_system_access_regular_file_delegate.cc
@@ -53,11 +53,8 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   CHECK_GE(offset, 0);
 
-  int size = base::checked_cast<int>(data.size());
-  int result = UNSAFE_TODO(
-      backing_file_.Read(offset, reinterpret_cast<char*>(data.data()), size));
-  if (result >= 0) {
-    return result;
+  if (std::optional<size_t> bytes_read = backing_file_.Read(offset, data)) {
+    return bytes_read.value();
   }
   return base::unexpected(base::File::GetLastFileError());
 }
@@ -83,17 +80,20 @@
       return base::unexpected(base::File::FILE_ERROR_NO_SPACE);
   }
 
-  int result = UNSAFE_TODO(backing_file_.Write(
-      offset, reinterpret_cast<const char*>(data.data()), write_size));
-  // The file size may not have changed after the write operation. `CheckAdd()`
-  // is not needed here since `result` is guaranteed to be no more than
-  // `write_size`.
-  int64_t new_file_size = std::max(file_size_before, offset + result);
-  capacity_tracker_->OnFileContentsModified(new_file_size);
+  std::optional<size_t> bytes_written = backing_file_.Write(offset, data);
+  if (bytes_written.has_value()) {
+    // The file size may not have changed after the write operation.
+    // `CheckAdd()` is not needed here since `result` is guaranteed to be no
+    // more than `write_size`.
+    int64_t new_file_size = std::max(
+        file_size_before, offset + base::checked_cast<int64_t>(*bytes_written));
+    capacity_tracker_->OnFileContentsModified(new_file_size);
+  }
 
   // Only return an error if no bytes were written. Partial writes should return
   // the number of bytes written.
-  return result < 0 ? base::File::GetLastFileError() : result;
+  return bytes_written.has_value() ? *bytes_written
+                                   : base::File::GetLastFileError();
 }
 
 base::FileErrorOr<int64_t> FileSystemAccessRegularFileDelegate::GetLength() {
diff --git a/third_party/blink/renderer/modules/gamepad/BUILD.gn b/third_party/blink/renderer/modules/gamepad/BUILD.gn
index 1aea787..478d8284 100644
--- a/third_party/blink/renderer/modules/gamepad/BUILD.gn
+++ b/third_party/blink/renderer/modules/gamepad/BUILD.gn
@@ -20,6 +20,8 @@
     "gamepad_haptic_actuator.cc",
     "gamepad_haptic_actuator.h",
     "gamepad_listener.h",
+    "gamepad_raw_input_change_event.cc",
+    "gamepad_raw_input_change_event.h",
     "gamepad_shared_memory_reader.cc",
     "gamepad_shared_memory_reader.h",
     "gamepad_touch.cc",
diff --git a/third_party/blink/renderer/modules/gamepad/dom_window_gamepad.h b/third_party/blink/renderer/modules/gamepad/dom_window_gamepad.h
index 9fbb4dc..b255cbe0 100644
--- a/third_party/blink/renderer/modules/gamepad/dom_window_gamepad.h
+++ b/third_party/blink/renderer/modules/gamepad/dom_window_gamepad.h
@@ -15,6 +15,8 @@
   DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(gamepadconnected, kGamepadconnected)
   DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(gamepaddisconnected,
                                          kGamepaddisconnected)
+  DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(gamepadrawinputchanged,
+                                         kGamepadrawinputchanged)
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/gamepad/gamepad_comparisons.cc b/third_party/blink/renderer/modules/gamepad/gamepad_comparisons.cc
index 9f2511c..240c5a7 100644
--- a/third_party/blink/renderer/modules/gamepad/gamepad_comparisons.cc
+++ b/third_party/blink/renderer/modules/gamepad/gamepad_comparisons.cc
@@ -21,9 +21,15 @@
   return collection.AsSpan();
 }
 
-base::span<const GamepadTouchVector::ValueType> AsSpan(
-    const GamepadTouchVector& collection) {
-  return base::span(collection);
+template <size_t N>
+Vector<int> GetSetBitIndexes(const std::bitset<N>& bitset) {
+  Vector<int> set_bits;
+  for (size_t i = 0; i < bitset.size(); ++i) {
+    if (bitset.test(i)) {
+      set_bits.push_back(static_cast<int>(i));
+    }
+  }
+  return set_bits;
 }
 
 template <typename Collection,
@@ -96,6 +102,11 @@
   return gamepad_disconnected_.test(pad_index);
 }
 
+bool GamepadStateCompareResult::HasGamepadInputChanged(size_t pad_index) const {
+  DCHECK_LT(pad_index, device::Gamepads::kItemsLengthCap);
+  return gamepad_input_changed_.test(pad_index);
+}
+
 bool GamepadStateCompareResult::IsAxisChanged(size_t pad_index,
                                               size_t axis_index) const {
   DCHECK_LT(pad_index, device::Gamepads::kItemsLengthCap);
@@ -124,6 +135,13 @@
   return button_up_[pad_index].test(button_index);
 }
 
+bool GamepadStateCompareResult::IsTouchChanged(size_t pad_index,
+                                               size_t touch_index) const {
+  CHECK_LT(pad_index, device::Gamepads::kItemsLengthCap);
+  CHECK_LT(touch_index, device::Gamepad::kTouchEventsLengthCap);
+  return touch_changed_[pad_index].test(touch_index);
+}
+
 bool GamepadStateCompareResult::CompareGamepads(
     const HeapVector<Member<Gamepad>> old_gamepads,
     const HeapVector<Member<Gamepad>> new_gamepads,
@@ -151,12 +169,16 @@
         CompareAxes(old_gamepad, new_gamepad, i, compare_all_axes);
     bool any_button_updated =
         CompareButtons(old_gamepad, new_gamepad, i, compare_all_buttons);
-    bool any_touch_updated = CompareTouches(old_gamepad, new_gamepad);
+    bool any_touch_updated = CompareTouches(old_gamepad, new_gamepad, i);
 
     if (newly_connected)
       gamepad_connected_.set(i);
     if (newly_disconnected)
       gamepad_disconnected_.set(i);
+    if ((!newly_connected && !newly_disconnected) &&
+        (any_axis_updated || any_button_updated || any_touch_updated)) {
+      gamepad_input_changed_.set(i);
+    }
     if (newly_connected || newly_disconnected || any_axis_updated ||
         any_button_updated || any_touch_updated) {
       any_change = true;
@@ -251,7 +273,8 @@
 }
 
 bool GamepadStateCompareResult::CompareTouches(Gamepad* old_gamepad,
-                                               Gamepad* new_gamepad) {
+                                               Gamepad* new_gamepad,
+                                               size_t gamepad_index) {
   if (!new_gamepad) {
     return false;
   }
@@ -259,18 +282,61 @@
   const auto* new_touches = new_gamepad->touchEvents();
   const auto* old_touches = old_gamepad ? old_gamepad->touchEvents() : nullptr;
 
-  return Compare(old_touches, new_touches,
-                 [](const Member<GamepadTouch>& new_touch,
-                    const Member<GamepadTouch>& old_touch) {
-                   return new_touch->touchId() == old_touch->touchId() &&
-                          new_touch->surfaceId() == old_touch->surfaceId() &&
-                          new_touch->HasSurfaceDimensions() ==
-                              old_touch->HasSurfaceDimensions() &&
-                          !Compare(new_touch->surfaceDimensions().Get(),
-                                   old_touch->surfaceDimensions().Get()) &&
-                          !Compare(new_touch->position().Get(),
-                                   old_touch->position().Get());
-                 });
+  // No touches on either gamepad: nothing changed.
+  if (!new_touches && !old_touches) {
+    return false;
+  }
+
+  // One has touches and the other does not: considered different.
+  if (!new_touches != !old_touches) {
+    return true;
+  }
+
+  // Different number of touches: considered different.
+  if (new_touches->size() != old_touches->size()) {
+    return true;
+  }
+
+  // Compare individual touches.
+  for (wtf_size_t i = 0; i < new_touches->size(); ++i) {
+    const auto& new_touch = new_touches->at(i);
+    const auto& old_touch = old_touches->at(i);
+
+    if (new_touch->touchId() != old_touch->touchId() ||
+        new_touch->surfaceId() != old_touch->surfaceId() ||
+        Compare(new_touch->surfaceDimensions().Get(),
+                old_touch->surfaceDimensions().Get()) ||
+        Compare(new_touch->position().Get(), old_touch->position().Get())) {
+      touch_changed_[gamepad_index].set(i);
+      return touch_changed_[gamepad_index].any();
+    }
+  }
+
+  return false;
+}
+
+Vector<int> GamepadStateCompareResult::GetChangedAxes(size_t pad_index) const {
+  return GetSetBitIndexes(axis_changed_[pad_index]);
+}
+
+Vector<int> GamepadStateCompareResult::GetChangedButtons(
+    size_t pad_index) const {
+  return GetSetBitIndexes(button_changed_[pad_index]);
+}
+
+Vector<int> GamepadStateCompareResult::GetButtonsPressed(
+    size_t pad_index) const {
+  return GetSetBitIndexes(button_down_[pad_index]);
+}
+
+Vector<int> GamepadStateCompareResult::GetButtonsReleased(
+    size_t pad_index) const {
+  return GetSetBitIndexes(button_up_[pad_index]);
+}
+
+Vector<int> GamepadStateCompareResult::GetChangedTouches(
+    size_t pad_index) const {
+  return GetSetBitIndexes(touch_changed_[pad_index]);
 }
 
 GamepadStateCompareResult GamepadComparisons::Compare(
diff --git a/third_party/blink/renderer/modules/gamepad/gamepad_comparisons.h b/third_party/blink/renderer/modules/gamepad/gamepad_comparisons.h
index aa2bd2cf..1118954 100644
--- a/third_party/blink/renderer/modules/gamepad/gamepad_comparisons.h
+++ b/third_party/blink/renderer/modules/gamepad/gamepad_comparisons.h
@@ -32,10 +32,20 @@
   // True if the corresponding gamepad event should be dispatched.
   bool IsGamepadConnected(size_t pad_index) const;
   bool IsGamepadDisconnected(size_t pad_index) const;
+  bool HasGamepadInputChanged(size_t pad_index) const;
   bool IsAxisChanged(size_t pad_index, size_t axis_index) const;
   bool IsButtonChanged(size_t pad_index, size_t button_index) const;
   bool IsButtonDown(size_t pad_index, size_t button_index) const;
   bool IsButtonUp(size_t pad_index, size_t button_index) const;
+  bool IsTouchChanged(size_t pad_index, size_t touch_index) const;
+
+  // Returns indices of all axes/buttons/touches that changed for the given
+  // gamepad.
+  Vector<int> GetChangedAxes(size_t pad_index) const;
+  Vector<int> GetChangedButtons(size_t pad_index) const;
+  Vector<int> GetButtonsPressed(size_t pad_index) const;
+  Vector<int> GetButtonsReleased(size_t pad_index) const;
+  Vector<int> GetChangedTouches(size_t pad_index) const;
 
  private:
   bool CompareGamepads(const HeapVector<Member<Gamepad>> old_gamepads,
@@ -50,11 +60,14 @@
                       Gamepad* new_gamepad,
                       size_t gamepad_index,
                       bool compare_all);
-  bool CompareTouches(Gamepad* old_gamepad, Gamepad* new_gamepad);
+  bool CompareTouches(Gamepad* old_gamepad,
+                      Gamepad* new_gamepad,
+                      size_t gamepad_index);
 
   bool any_change_ = false;
   std::bitset<device::Gamepads::kItemsLengthCap> gamepad_connected_;
   std::bitset<device::Gamepads::kItemsLengthCap> gamepad_disconnected_;
+  std::bitset<device::Gamepads::kItemsLengthCap> gamepad_input_changed_;
   std::array<std::bitset<device::Gamepad::kAxesLengthCap>,
              device::Gamepads::kItemsLengthCap>
       axis_changed_;
@@ -67,6 +80,9 @@
   std::array<std::bitset<device::Gamepad::kButtonsLengthCap>,
              device::Gamepads::kItemsLengthCap>
       button_up_;
+  std::array<std::bitset<device::Gamepad::kTouchEventsLengthCap>,
+             device::Gamepads::kItemsLengthCap>
+      touch_changed_;
 };
 
 class MODULES_EXPORT GamepadComparisons {
diff --git a/third_party/blink/renderer/modules/gamepad/gamepad_comparisons_test.cc b/third_party/blink/renderer/modules/gamepad/gamepad_comparisons_test.cc
index a1507be9..65222e7f 100644
--- a/third_party/blink/renderer/modules/gamepad/gamepad_comparisons_test.cc
+++ b/third_party/blink/renderer/modules/gamepad/gamepad_comparisons_test.cc
@@ -566,4 +566,102 @@
   EXPECT_TRUE(compareResult.IsDifferent());
 }
 
+// Tests that a mismatch in the number of touches between two
+// gamepad states is detected as different.
+TEST_F(GamepadComparisonsTest, CompareTouchCountMismatch) {
+  auto list1 = CreateGamepadListWithTopLeftTouch();
+  auto list2 = CreateGamepadListWithTopLeftTouchesTouchId1();
+
+  auto compareResult = GamepadComparisons::Compare(
+      list1, list2, /*compare_all_axes=*/false, /*compare_all_buttons=*/false);
+  EXPECT_TRUE(compareResult.IsDifferent());
+}
+
+// Tests that a difference in surface dimensions for otherwise identical
+// touches is detected.
+TEST_F(GamepadComparisonsTest, CompareTouchSurfaceDimensionsMismatch) {
+  auto list1 = CreateGamepadListWithTopLeftTouchSurface1();
+  auto list2 = CreateGamepadListWithTopLeftTouchSurface2();
+
+  auto compareResult = GamepadComparisons::Compare(
+      list1, list2, /*compare_all_axes=*/false, /*compare_all_buttons=*/false);
+  EXPECT_TRUE(compareResult.IsDifferent());
+  EXPECT_FALSE(compareResult.GetChangedTouches(0).empty());
+  // Check which touch index changed.
+  for (int index : compareResult.GetChangedTouches(0)) {
+    EXPECT_EQ(list1[0]->touchEvents()->size(), list2[0]->touchEvents()->size());
+    // Surface dimensions should differ.
+    auto surface_dimensions1 =
+        (*list1[0]->touchEvents())[index].Get()->surfaceDimensions();
+    auto surface_dimensions2 =
+        (*list2[0]->touchEvents())[index].Get()->surfaceDimensions();
+    EXPECT_NE(surface_dimensions1->Item(0), surface_dimensions2->Item(0));
+    EXPECT_NE(surface_dimensions1->Item(1), surface_dimensions2->Item(1));
+  }
+}
+
+// Tests that a difference in touch IDs (even if other properties match)
+// is detected as different.
+TEST_F(GamepadComparisonsTest, CompareTouchIdOrderMatters) {
+  auto list1 = CreateGamepadListWithTopLeftTouchesTouchId1();
+  auto list2 = CreateGamepadListWithTopLeftTouchesTouchId3();
+  auto compareResult = GamepadComparisons::Compare(
+      list1, list2, /*compare_all_axes=*/false, /*compare_all_buttons=*/false);
+  EXPECT_TRUE(compareResult.IsDifferent());
+  EXPECT_FALSE(compareResult.GetChangedTouches(0).empty());
+  for (int index : compareResult.GetChangedTouches(0)) {
+    EXPECT_EQ(list1[0]->touchEvents()->size(), list2[0]->touchEvents()->size());
+    // Touch IDs should differ.
+    EXPECT_NE((*list1[0]->touchEvents())[index].Get()->touchId(),
+              (*list2[0]->touchEvents())[index].Get()->touchId());
+  }
+}
+
+// Tests that two gamepad states with identical touches
+// (compare a gamepad state to itself) are not detected as different.
+TEST_F(GamepadComparisonsTest, CompareIdenticalTouches) {
+  auto list1 = CreateGamepadListWithTopLeftTouchesTouchId1();
+  auto compareResult = GamepadComparisons::Compare(
+      list1, list1, /*compare_all_axes=*/false, /*compare_all_buttons=*/false);
+  EXPECT_FALSE(compareResult.IsDifferent());
+  EXPECT_TRUE(compareResult.GetChangedTouches(0).empty());
+}
+
+// Tests that when a gamepad previously had a touch event and now has no
+// touches, this change is detected as a difference. This simulates a user
+// lifting their finger off a touch-sensitive area of the gamepad.
+TEST_F(GamepadComparisonsTest, CompareTouchUpOnConnectedGamepad) {
+  auto list1 = CreateGamepadListWithTopLeftTouch();
+  auto list2 = CreateGamepadListWithNeutralGamepad();
+
+  auto compareResult = GamepadComparisons::Compare(
+      list1, list2, /*compare_all_axes=*/false, /*compare_all_buttons=*/false);
+
+  EXPECT_TRUE(compareResult.IsDifferent());
+  EXPECT_TRUE(compareResult.GetChangedTouches(0).empty());
+}
+
+// Tests that when a gamepad with an active touch becomes absent (e.g.,
+// disconnected), the change is detected as different.
+TEST_F(GamepadComparisonsTest, CompareTouchPresentToNone) {
+  auto list1 = CreateGamepadListWithTopLeftTouch();
+  auto list2 = CreateEmptyGamepadList();
+  auto compareResult = GamepadComparisons::Compare(
+      list1, list2, /*compare_all_axes=*/false, /*compare_all_buttons=*/false);
+  EXPECT_TRUE(compareResult.IsDifferent());
+}
+
+// Tests that when a touch appears on a previously absent gamepad, the change is
+// detected as different.
+TEST_F(GamepadComparisonsTest, CompareNoneToTouchPresent) {
+  auto list1 = CreateEmptyGamepadList();
+  auto list2 = CreateGamepadListWithTopLeftTouch();
+
+  auto compareResult = GamepadComparisons::Compare(
+      list1, list2, /*compare_all_axes=*/false, /*compare_all_buttons=*/false);
+
+  EXPECT_TRUE(compareResult.IsDifferent());
+  EXPECT_TRUE(compareResult.GetChangedTouches(0).empty());
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/gamepad/gamepad_dispatcher.cc b/third_party/blink/renderer/modules/gamepad/gamepad_dispatcher.cc
index 0e1939c..21d6d2f 100644
--- a/third_party/blink/renderer/modules/gamepad/gamepad_dispatcher.cc
+++ b/third_party/blink/renderer/modules/gamepad/gamepad_dispatcher.cc
@@ -73,9 +73,13 @@
   DispatchDidConnectOrDisconnectGamepad(index, gamepad, false);
 }
 
-void GamepadDispatcher::ButtonOrAxisDidChange(uint32_t index,
-                                              const device::Gamepad& gamepad) {
-  DCHECK_LT(index, device::Gamepads::kItemsLengthCap);
+void GamepadDispatcher::DidChangeGamepadRawInput(
+    uint32_t index,
+    const device::Gamepad& gamepad) {
+  CHECK_LT(index, device::Gamepads::kItemsLengthCap);
+  CHECK_EQ(true, gamepad.connected);
+  // TODO(https://crbug.com/438906421): Queue gamepad input changes in the
+  // renderer.
   NotifyControllers();
 }
 
diff --git a/third_party/blink/renderer/modules/gamepad/gamepad_dispatcher.h b/third_party/blink/renderer/modules/gamepad/gamepad_dispatcher.h
index 82678d6..b1ab5695 100644
--- a/third_party/blink/renderer/modules/gamepad/gamepad_dispatcher.h
+++ b/third_party/blink/renderer/modules/gamepad/gamepad_dispatcher.h
@@ -5,6 +5,7 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_GAMEPAD_GAMEPAD_DISPATCHER_H_
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_GAMEPAD_GAMEPAD_DISPATCHER_H_
 
+#include "base/memory/scoped_refptr.h"
 #include "device/gamepad/public/mojom/gamepad.mojom-blink.h"
 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
 #include "third_party/blink/renderer/core/frame/platform_event_dispatcher.h"
@@ -49,7 +50,8 @@
   // GamepadListener
   void DidConnectGamepad(uint32_t index, const device::Gamepad&) override;
   void DidDisconnectGamepad(uint32_t index, const device::Gamepad&) override;
-  void ButtonOrAxisDidChange(uint32_t index, const device::Gamepad&) override;
+  void DidChangeGamepadRawInput(uint32_t index,
+                                const device::Gamepad&) override;
 
   // PlatformEventDispatcher
   void StartListening(LocalDOMWindow*) override;
diff --git a/third_party/blink/renderer/modules/gamepad/gamepad_listener.h b/third_party/blink/renderer/modules/gamepad/gamepad_listener.h
index 5495f466..cbe47d2 100644
--- a/third_party/blink/renderer/modules/gamepad/gamepad_listener.h
+++ b/third_party/blink/renderer/modules/gamepad/gamepad_listener.h
@@ -28,11 +28,11 @@
   virtual void DidDisconnectGamepad(uint32_t index,
                                     const device::Gamepad& gamepad) = 0;
 
-  // Called when a button or axis is changed on a connected gamepad. |index| is
-  // the index of the gamepad in the gamepad array, and |gamepad| is a reference
-  // to the gamepad.
-  virtual void ButtonOrAxisDidChange(uint32_t index,
-                                     const device::Gamepad& gamepad) = 0;
+  // Called when any raw input data (buttons, axes, touches, etc.) changes
+  // on a connected gamepad. |index| is the gamepad's index in the array,
+  // and |gamepad| is a reference to the updated gamepad state.
+  virtual void DidChangeGamepadRawInput(uint32_t index,
+                                        const device::Gamepad& gamepad) = 0;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/gamepad/gamepad_raw_input_change_event.cc b/third_party/blink/renderer/modules/gamepad/gamepad_raw_input_change_event.cc
new file mode 100644
index 0000000..96b15217
--- /dev/null
+++ b/third_party/blink/renderer/modules/gamepad/gamepad_raw_input_change_event.cc
@@ -0,0 +1,52 @@
+// 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 "third_party/blink/renderer/modules/gamepad/gamepad_raw_input_change_event.h"
+
+#include "third_party/blink/renderer/bindings/modules/v8/v8_gamepad_raw_input_change_event_init.h"
+#include "third_party/blink/renderer/platform/bindings/exception_state.h"
+
+namespace blink {
+
+GamepadRawInputChangeEvent::GamepadRawInputChangeEvent(
+    const AtomicString& type,
+    Bubbles bubbles,
+    Cancelable cancelable,
+    Gamepad* gamepad,
+    const Vector<int>& axesChanged,
+    const Vector<int>& buttonsValueChanged,
+    const Vector<int>& buttonsPressed,
+    const Vector<int>& buttonsReleased,
+    const Vector<int>& touchesChanged)
+    : GamepadEvent(type, bubbles, cancelable, gamepad),
+      axes_changed_(axesChanged),
+      buttons_value_changed_(buttonsValueChanged),
+      buttons_pressed_(buttonsPressed),
+      buttons_released_(buttonsReleased),
+      touches_changed_(touchesChanged) {}
+
+GamepadRawInputChangeEvent::GamepadRawInputChangeEvent(
+    const AtomicString& type,
+    const GamepadRawInputChangeEventInit* initializer)
+    : GamepadEvent(type, initializer) {
+  if (initializer) {
+    axes_changed_ = initializer->axesChanged();
+    buttons_value_changed_ = initializer->buttonsValueChanged();
+    buttons_pressed_ = initializer->buttonsPressed();
+    buttons_released_ = initializer->buttonsReleased();
+    touches_changed_ = initializer->touchesChanged();
+  }
+}
+
+GamepadRawInputChangeEvent::~GamepadRawInputChangeEvent() = default;
+
+const AtomicString& GamepadRawInputChangeEvent::InterfaceName() const {
+  return event_interface_names::kGamepadRawInputChangeEvent;
+}
+
+void GamepadRawInputChangeEvent::Trace(Visitor* visitor) const {
+  GamepadEvent::Trace(visitor);
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/modules/gamepad/gamepad_raw_input_change_event.h b/third_party/blink/renderer/modules/gamepad/gamepad_raw_input_change_event.h
new file mode 100644
index 0000000..22e79f8
--- /dev/null
+++ b/third_party/blink/renderer/modules/gamepad/gamepad_raw_input_change_event.h
@@ -0,0 +1,84 @@
+// 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 THIRD_PARTY_BLINK_RENDERER_MODULES_GAMEPAD_GAMEPAD_RAW_INPUT_CHANGE_EVENT_H_
+#define THIRD_PARTY_BLINK_RENDERER_MODULES_GAMEPAD_GAMEPAD_RAW_INPUT_CHANGE_EVENT_H_
+
+#include "third_party/blink/renderer/modules/event_modules.h"
+#include "third_party/blink/renderer/modules/gamepad/gamepad.h"
+#include "third_party/blink/renderer/modules/gamepad/gamepad_event.h"
+
+namespace blink {
+
+class Gamepad;
+class GamepadRawInputChangeEventInit;
+
+class GamepadRawInputChangeEvent : public GamepadEvent {
+  DEFINE_WRAPPERTYPEINFO();
+
+ public:
+  // Create using explicit parameters (not init dictionary).
+  static GamepadRawInputChangeEvent* Create(
+      const AtomicString& type,
+      Bubbles bubbles,
+      Cancelable cancelable,
+      Gamepad* gamepad,
+      const Vector<int>& axesChanged,
+      const Vector<int>& buttonsValueChanged,
+      const Vector<int>& buttonsPressed,
+      const Vector<int>& buttonsReleased,
+      const Vector<int>& touchesChanged) {
+    return MakeGarbageCollected<GamepadRawInputChangeEvent>(
+        type, bubbles, cancelable, gamepad, axesChanged, buttonsValueChanged,
+        buttonsPressed, buttonsReleased, touchesChanged);
+  }
+
+  static GamepadRawInputChangeEvent* Create(
+      const AtomicString& type,
+      const GamepadRawInputChangeEventInit* initializer) {
+    return MakeGarbageCollected<GamepadRawInputChangeEvent>(type, initializer);
+  }
+
+  // Constructor with explicit parameters.
+  GamepadRawInputChangeEvent(const AtomicString& type,
+                             Bubbles bubbles,
+                             Cancelable cancelable,
+                             Gamepad* gamepad,
+                             const Vector<int>& axesChanged,
+                             const Vector<int>& buttonsValueChanged,
+                             const Vector<int>& buttonsPressed,
+                             const Vector<int>& buttonsReleased,
+                             const Vector<int>& touchesChanged);
+
+  GamepadRawInputChangeEvent(const AtomicString& type,
+                             const GamepadRawInputChangeEventInit* initializer);
+
+  ~GamepadRawInputChangeEvent() override;
+
+  // Getters for the attributes.
+  const Vector<int>& axesChanged() const { return axes_changed_; }
+  const Vector<int>& buttonsValueChanged() const {
+    return buttons_value_changed_;
+  }
+  const Vector<int>& buttonsPressed() const { return buttons_pressed_; }
+  const Vector<int>& buttonsReleased() const { return buttons_released_; }
+  const Vector<int>& touchesChanged() const { return touches_changed_; }
+
+  // Returns the interface name for this event, used for runtime type
+  // identification.
+  const AtomicString& InterfaceName() const override;
+
+  void Trace(Visitor*) const override;
+
+ private:
+  Vector<int> axes_changed_;
+  Vector<int> buttons_value_changed_;
+  Vector<int> buttons_pressed_;
+  Vector<int> buttons_released_;
+  Vector<int> touches_changed_;
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_GAMEPAD_GAMEPAD_RAW_INPUT_CHANGE_EVENT_H_
diff --git a/third_party/blink/renderer/modules/gamepad/gamepad_raw_input_change_event.idl b/third_party/blink/renderer/modules/gamepad/gamepad_raw_input_change_event.idl
new file mode 100644
index 0000000..dad3466
--- /dev/null
+++ b/third_party/blink/renderer/modules/gamepad/gamepad_raw_input_change_event.idl
@@ -0,0 +1,15 @@
+// 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.
+
+[
+    Exposed=Window,
+    RuntimeEnabled=GamepadRawInputChangeEvent
+] interface GamepadRawInputChangeEvent : GamepadEvent {
+    constructor(DOMString type, optional GamepadRawInputChangeEventInit eventInitDict = {});
+    readonly attribute FrozenArray<long> axesChanged;
+    readonly attribute FrozenArray<long> buttonsValueChanged;
+    readonly attribute FrozenArray<long> buttonsPressed;
+    readonly attribute FrozenArray<long> buttonsReleased;
+    readonly attribute FrozenArray<long> touchesChanged;
+};
\ No newline at end of file
diff --git a/third_party/blink/renderer/modules/gamepad/gamepad_raw_input_change_event_init.idl b/third_party/blink/renderer/modules/gamepad/gamepad_raw_input_change_event_init.idl
new file mode 100644
index 0000000..9e572069
--- /dev/null
+++ b/third_party/blink/renderer/modules/gamepad/gamepad_raw_input_change_event_init.idl
@@ -0,0 +1,11 @@
+// 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.
+
+dictionary GamepadRawInputChangeEventInit : GamepadEventInit {
+  FrozenArray<long> axesChanged;
+  FrozenArray<long> buttonsValueChanged;
+  FrozenArray<long> buttonsPressed;
+  FrozenArray<long> buttonsReleased;
+  FrozenArray<long> touchesChanged;
+};
\ No newline at end of file
diff --git a/third_party/blink/renderer/modules/gamepad/gamepad_shared_memory_reader.cc b/third_party/blink/renderer/modules/gamepad/gamepad_shared_memory_reader.cc
index 4e537c54..f0debed 100644
--- a/third_party/blink/renderer/modules/gamepad/gamepad_shared_memory_reader.cc
+++ b/third_party/blink/renderer/modules/gamepad/gamepad_shared_memory_reader.cc
@@ -153,4 +153,12 @@
     listener_->DidDisconnectGamepad(index, gamepad);
 }
 
+void GamepadSharedMemoryReader::GamepadRawInputChanged(
+    uint32_t index,
+    const device::Gamepad& gamepad) {
+  if (listener_) {
+    listener_->DidChangeGamepadRawInput(index, gamepad);
+  }
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/gamepad/gamepad_shared_memory_reader.h b/third_party/blink/renderer/modules/gamepad/gamepad_shared_memory_reader.h
index 1ffeeb4..152c730 100644
--- a/third_party/blink/renderer/modules/gamepad/gamepad_shared_memory_reader.h
+++ b/third_party/blink/renderer/modules/gamepad/gamepad_shared_memory_reader.h
@@ -55,6 +55,8 @@
                         const device::Gamepad& gamepad) override;
   void GamepadDisconnected(uint32_t index,
                            const device::Gamepad& gamepad) override;
+  void GamepadRawInputChanged(uint32_t index,
+                              const device::Gamepad& gamepad) override;
 
   base::ReadOnlySharedMemoryRegion renderer_shared_buffer_region_;
   base::ReadOnlySharedMemoryMapping renderer_shared_buffer_mapping_;
diff --git a/third_party/blink/renderer/modules/gamepad/navigator_gamepad.cc b/third_party/blink/renderer/modules/gamepad/navigator_gamepad.cc
index 8b64764c..f8329c5 100644
--- a/third_party/blink/renderer/modules/gamepad/navigator_gamepad.cc
+++ b/third_party/blink/renderer/modules/gamepad/navigator_gamepad.cc
@@ -40,6 +40,10 @@
 #include "third_party/blink/renderer/modules/gamepad/gamepad_comparisons.h"
 #include "third_party/blink/renderer/modules/gamepad/gamepad_dispatcher.h"
 #include "third_party/blink/renderer/modules/gamepad/gamepad_event.h"
+#include "third_party/blink/renderer/modules/gamepad/gamepad_raw_input_change_event.h"
+#include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
+#include "third_party/blink/renderer/platform/privacy_budget/identifiability_digest_helpers.h"
+#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
 #include "third_party/blink/renderer/platform/wtf/text/atomic_string.h"
 
 namespace blink {
@@ -51,11 +55,19 @@
          event_type == event_type_names::kGamepaddisconnected;
 }
 
-bool HasConnectionEventListeners(LocalDOMWindow* window) {
+bool IsGamepadRawInputChangedEvent(const AtomicString& event_type) {
+  return event_type == event_type_names::kGamepadrawinputchanged;
+}
+
+bool HasConnectionChangedEventListeners(LocalDOMWindow* window) {
   return window->HasEventListeners(event_type_names::kGamepadconnected) ||
          window->HasEventListeners(event_type_names::kGamepaddisconnected);
 }
 
+bool HasInputChangedEventListeners(LocalDOMWindow* window) {
+  return window->HasEventListeners(event_type_names::kGamepadrawinputchanged);
+}
+
 }  // namespace
 
 const char kFeaturePolicyBlocked[] =
@@ -275,6 +287,18 @@
                                            const AtomicString& event_type) {
   if (IsGamepadConnectionEvent(event_type)) {
     has_connection_event_listener_ = true;
+  }
+
+  if (RuntimeEnabledFeatures::GamepadRawInputChangeEventEnabled() &&
+      IsGamepadRawInputChangedEvent(event_type)) {
+    has_input_changed_event_listener_ = true;
+    if (DomWindow()) {
+      UseCounter::Count(DomWindow(),
+                        WebFeature::kGamepadRawInputChangeEventListener);
+    }
+  }
+
+  if (has_connection_event_listener_ || has_input_changed_event_listener_) {
     bool first_event_listener = !has_event_listener_;
     has_event_listener_ = true;
 
@@ -289,9 +313,16 @@
 void NavigatorGamepad::DidRemoveEventListener(LocalDOMWindow* window,
                                               const AtomicString& event_type) {
   if (IsGamepadConnectionEvent(event_type)) {
-    has_connection_event_listener_ = HasConnectionEventListeners(window);
-    if (!has_connection_event_listener_)
-      DidRemoveGamepadEventListeners();
+    has_connection_event_listener_ = HasConnectionChangedEventListeners(window);
+  }
+
+  if (RuntimeEnabledFeatures::GamepadRawInputChangeEventEnabled() &&
+      IsGamepadRawInputChangedEvent(event_type)) {
+    has_input_changed_event_listener_ = HasInputChangedEventListeners(window);
+  }
+
+  if (!has_connection_event_listener_ && !has_input_changed_event_listener_) {
+    DidRemoveGamepadEventListeners();
   }
 }
 
@@ -323,59 +354,80 @@
       // state changed. We must swap buffers before dispatching events to
       // ensure |gamepads_| holds the correct data when getGamepads is called
       // from inside a gamepad event listener.
-      auto compare_result =
-          GamepadComparisons::Compare(gamepads_, gamepads_back_, false, false);
+      bool should_compare_all_axes = false;
+      bool should_compare_all_buttons = false;
+
+      if (has_input_changed_event_listener_) {
+        // Only compare all axes/buttons if a raw input change event listener is
+        // present. This avoids unnecessary comparisons when no listener is
+        // attached.
+        should_compare_all_axes = true;
+        should_compare_all_buttons = true;
+      }
+
+      GamepadStateCompareResult compare_result = GamepadComparisons::Compare(
+          gamepads_, gamepads_back_, should_compare_all_axes,
+          should_compare_all_buttons);
+
       if (compare_result.IsDifferent()) {
         std::swap(gamepads_, gamepads_back_);
-        bool is_gamepads_back_exposed = is_gamepads_exposed_;
+        is_gamepads_back_exposed_ = is_gamepads_exposed_;
         is_gamepads_exposed_ = false;
 
-        // Dispatch gamepad events. Dispatching an event calls the event
-        // listeners synchronously.
-        //
-        // Note: In some instances the gamepad connection state may change while
-        // inside an event listener. This is most common when using test APIs
-        // that allow the gamepad state to be changed from javascript. The set
-        // of event listeners may also change if listeners are added or removed
-        // by another listener.
         for (uint32_t i = 0; i < device::Gamepads::kItemsLengthCap; ++i) {
-          bool is_connected = compare_result.IsGamepadConnected(i);
-          bool is_disconnected = compare_result.IsGamepadDisconnected(i);
-
-          // When a gamepad is disconnected and connected in the same update,
-          // dispatch the gamepaddisconnected event first.
-          if (has_connection_event_listener_ && is_disconnected) {
-            // Reset the vibration state associated with the disconnected
-            // gamepad to prevent it from being associated with a
-            // newly-connected gamepad at the same index.
-            vibration_actuators_[i] = nullptr;
-
-            Gamepad* pad = gamepads_back_[i];
-            DCHECK(pad);
-            pad->SetConnected(false);
-            is_gamepads_back_exposed = true;
-            DispatchGamepadEvent(event_type_names::kGamepaddisconnected, pad);
-          }
-          if (has_connection_event_listener_ && is_connected) {
-            Gamepad* pad = gamepads_[i];
-            DCHECK(pad);
-            is_gamepads_exposed_ = true;
-            DispatchGamepadEvent(event_type_names::kGamepadconnected, pad);
-          }
+          MaybeDispatchGamepadEvents(i, compare_result);
         }
 
         // Clear |gamepads_back_| if it was ever exposed to the page so it can
         // be garbage collected when no active references remain. If it was
         // never exposed, retain the buffer so it can be reused.
-        if (is_gamepads_back_exposed)
+        if (is_gamepads_back_exposed_) {
           gamepads_back_.clear();
+        }
       }
     }
   }
 }
 
-void NavigatorGamepad::DispatchGamepadEvent(const AtomicString& event_name,
-                                            Gamepad* gamepad) {
+void NavigatorGamepad::MaybeDispatchGamepadEvents(
+    uint32_t index,
+    const GamepadStateCompareResult& compare_result) {
+  bool is_connected = compare_result.IsGamepadConnected(index);
+  bool is_disconnected = compare_result.IsGamepadDisconnected(index);
+  bool has_input_changed = compare_result.HasGamepadInputChanged(index);
+
+  // When a gamepad is disconnected and connected in the same update,
+  // dispatch the gamepaddisconnected event first.
+  if (has_connection_event_listener_ && is_disconnected) {
+    // Reset the vibration state associated with the disconnected
+    // gamepad to prevent it from being associated with a
+    // newly-connected gamepad at the same index.
+    vibration_actuators_[index] = nullptr;
+
+    Gamepad* pad = gamepads_back_[index];
+    DCHECK(pad);
+    pad->SetConnected(false);
+    is_gamepads_back_exposed_ = true;
+    DispatchGamepadConnectionChangedEvent(
+        event_type_names::kGamepaddisconnected, pad);
+  }
+
+  if (has_connection_event_listener_ && is_connected) {
+    Gamepad* pad = gamepads_[index];
+    DCHECK(pad);
+    is_gamepads_exposed_ = true;
+    DispatchGamepadConnectionChangedEvent(event_type_names::kGamepadconnected,
+                                          pad);
+  }
+
+  if (has_input_changed_event_listener_ && has_input_changed) {
+    DispatchGamepadRawInputChangedEvent(index, compare_result);
+  }
+}
+
+void NavigatorGamepad::DispatchGamepadConnectionChangedEvent(
+    const AtomicString& event_name,
+    Gamepad* gamepad) {
   // Ensure that we're blocking re-entrancy.
   DCHECK(processing_events_);
   DCHECK(has_connection_event_listener_);
@@ -384,6 +436,22 @@
       event_name, Event::Bubbles::kNo, Event::Cancelable::kYes, gamepad));
 }
 
+void NavigatorGamepad::DispatchGamepadRawInputChangedEvent(
+    uint32_t index,
+    const GamepadStateCompareResult& compare_result) {
+  CHECK(processing_events_);
+  CHECK(has_input_changed_event_listener_);
+  CHECK(gamepads_[index]);
+  DomWindow()->DispatchEvent(*GamepadRawInputChangeEvent::Create(
+      event_type_names::kGamepadrawinputchanged, Event::Bubbles::kNo,
+      Event::Cancelable::kYes, gamepads_[index],
+      compare_result.GetChangedAxes(index),
+      compare_result.GetChangedButtons(index),
+      compare_result.GetButtonsPressed(index),
+      compare_result.GetButtonsReleased(index),
+      compare_result.GetChangedTouches(index)));
+}
+
 void NavigatorGamepad::PageVisibilityChanged() {
   // Inform the embedder whether it needs to provide gamepad data for us.
   bool visible = GetPage()->IsPageVisible();
diff --git a/third_party/blink/renderer/modules/gamepad/navigator_gamepad.h b/third_party/blink/renderer/modules/gamepad/navigator_gamepad.h
index 756acde..495f722 100644
--- a/third_party/blink/renderer/modules/gamepad/navigator_gamepad.h
+++ b/third_party/blink/renderer/modules/gamepad/navigator_gamepad.h
@@ -51,6 +51,7 @@
 
 class GamepadDispatcher;
 class GamepadHapticActuator;
+class GamepadStateCompareResult;
 class Navigator;
 
 class MODULES_EXPORT NavigatorGamepad final
@@ -76,7 +77,22 @@
   void DidRemoveGamepadEventListeners();
   bool StartUpdatingIfAttached();
   void SampleAndCompareGamepadState();
-  void DispatchGamepadEvent(const AtomicString&, Gamepad*);
+
+  // Dispatch gamepad events. Dispatching an event calls the event
+  // listeners synchronously.
+  //
+  // Note: In some instances the gamepad connection state may change while
+  // inside an event listener. This is most common when using test APIs
+  // that allow the gamepad state to be changed from javascript. The set
+  // of event listeners may also change if listeners are added or removed
+  // by another listener.
+  void MaybeDispatchGamepadEvents(
+      uint32_t index,
+      const GamepadStateCompareResult& compare_result);
+  void DispatchGamepadConnectionChangedEvent(const AtomicString&, Gamepad*);
+  void DispatchGamepadRawInputChangedEvent(
+      uint32_t index,
+      const GamepadStateCompareResult& compare_result);
 
   // PageVisibilityObserver
   void PageVisibilityChanged() override;
@@ -113,6 +129,10 @@
   // overwritten.
   HeapVector<Member<Gamepad>> gamepads_back_;
 
+  // True if the buffer referenced by |gamepads_back_| has been exposed to the
+  // page.
+  bool is_gamepads_back_exposed_ = false;
+
   HeapVector<Member<GamepadHapticActuator>> vibration_actuators_;
 
   // Together the following keep track of the nextTouchId per Gamepad
@@ -135,6 +155,10 @@
   // disconnection events.
   bool has_connection_event_listener_ = false;
 
+  // True if there is at least one listener for gamepad raw input changed
+  // events.
+  bool has_input_changed_event_listener_ = false;
+
   // True while processing gamepad events.
   bool processing_events_ = false;
 
diff --git a/third_party/blink/renderer/modules/gamepad/window_gamepad.idl b/third_party/blink/renderer/modules/gamepad/window_gamepad.idl
index 3b8190d8a..a0be30a 100644
--- a/third_party/blink/renderer/modules/gamepad/window_gamepad.idl
+++ b/third_party/blink/renderer/modules/gamepad/window_gamepad.idl
@@ -3,9 +3,9 @@
 // found in the LICENSE file.
 
 [
-    RuntimeEnabled=GamepadWindowEventHandlers,
     ImplementedAs=DOMWindowGamepad
 ] partial interface mixin WindowEventHandlers {
-    attribute EventHandler ongamepadconnected;
-    attribute EventHandler ongamepaddisconnected;
+    [RuntimeEnabled=GamepadWindowEventHandlers] attribute EventHandler ongamepadconnected;
+    [RuntimeEnabled=GamepadWindowEventHandlers] attribute EventHandler ongamepaddisconnected;
+    [RuntimeEnabled=GamepadRawInputChangeEvent] attribute EventHandler ongamepadrawinputchanged;
 };
diff --git a/third_party/blink/renderer/modules/indexeddb/idb_request_test.cc b/third_party/blink/renderer/modules/indexeddb/idb_request_test.cc
index 25cd9f5..45ba8ec7 100644
--- a/third_party/blink/renderer/modules/indexeddb/idb_request_test.cc
+++ b/third_party/blink/renderer/modules/indexeddb/idb_request_test.cc
@@ -39,6 +39,7 @@
 #include "mojo/public/cpp/bindings/associated_remote.h"
 #include "mojo/public/cpp/bindings/pending_associated_receiver.h"
 #include "mojo/public/cpp/bindings/pending_associated_remote.h"
+#include "mojo/public/cpp/bindings/receiver.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
diff --git a/third_party/blink/renderer/modules/webcodecs/DEPS b/third_party/blink/renderer/modules/webcodecs/DEPS
index 8e7eda4..2e1fbd6b 100644
--- a/third_party/blink/renderer/modules/webcodecs/DEPS
+++ b/third_party/blink/renderer/modules/webcodecs/DEPS
@@ -11,7 +11,6 @@
     "+gpu/command_buffer/client/shared_image_interface.h",
     "+gpu/command_buffer/client/raster_interface.h",
     "+gpu/config/gpu_feature_info.h",
-    "+gpu/config/gpu_finch_features.h",
     "+gpu/GLES2/gl2extchromium.h",
 
     "+media/audio",
diff --git a/third_party/blink/renderer/modules/webcodecs/video_encoder.cc b/third_party/blink/renderer/modules/webcodecs/video_encoder.cc
index 4dfe6938..dd2c700 100644
--- a/third_party/blink/renderer/modules/webcodecs/video_encoder.cc
+++ b/third_party/blink/renderer/modules/webcodecs/video_encoder.cc
@@ -23,7 +23,6 @@
 #include "components/viz/common/gpu/raster_context_provider.h"
 #include "gpu/GLES2/gl2extchromium.h"
 #include "gpu/command_buffer/client/raster_interface.h"
-#include "gpu/config/gpu_finch_features.h"
 #include "media/base/async_destroy_video_encoder.h"
 #include "media/base/limits.h"
 #include "media/base/media_log.h"
@@ -1098,9 +1097,6 @@
 
   bool mappable = frame->IsMappable() || frame->HasMappableGpuBuffer();
   bool can_handle_shared_image =
-#if BUILDFLAG(IS_WIN)
-      !base::FeatureList::IsEnabled(features::kSkiaGraphite) &&
-#endif  // BUILDFLAG(IS_WIN)
       encoder_info_.DoesSupportGpuSharedImages(frame->format()) &&
       frame->HasSharedImage();
 
diff --git a/third_party/blink/renderer/modules/webgpu/external_texture_helper.cc b/third_party/blink/renderer/modules/webgpu/external_texture_helper.cc
index 48ea71d..30e58c9 100644
--- a/third_party/blink/renderer/modules/webgpu/external_texture_helper.cc
+++ b/third_party/blink/renderer/modules/webgpu/external_texture_helper.cc
@@ -35,10 +35,6 @@
   DCHECK(frame);
   DCHECK(resource_provider);
 
-  // This method should only be called with context providers supporting OOP-R.
-  CHECK(!raster_context_provider ||
-        raster_context_provider->ContextCapabilities().gpu_rasterization);
-
   if (frame->HasSharedImage()) {
     if (!raster_context_provider) {
       DLOG(ERROR) << "Unable to process a texture backed VideoFrame w/o a "
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_texture.cc b/third_party/blink/renderer/modules/webgpu/gpu_texture.cc
index 0a4ba11..099b1140 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_texture.cc
+++ b/third_party/blink/renderer/modules/webgpu/gpu_texture.cc
@@ -116,10 +116,6 @@
 // Validate swizzle must be a four-character string that only includes "r", "g",
 // "b", "a", "0", or "1".
 bool ValidateSwizzle(const String& swizzle, ExceptionState& exception_state) {
-  if (!RuntimeEnabledFeatures::WebGPUTextureComponentSwizzleEnabled()) {
-    return true;
-  }
-
   if (swizzle.length() != 4) {
     exception_state.ThrowTypeError(String::Format(
         "Swizzle ('%s') must be exactly a four-character string.",
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_texture_view_descriptor.idl b/third_party/blink/renderer/modules/webgpu/gpu_texture_view_descriptor.idl
index 30c33d3..85288c3 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_texture_view_descriptor.idl
+++ b/third_party/blink/renderer/modules/webgpu/gpu_texture_view_descriptor.idl
@@ -7,13 +7,13 @@
 dictionary GPUTextureViewDescriptor : GPUObjectDescriptorBase {
     GPUTextureFormat format;
     GPUTextureViewDimension dimension;
+    GPUTextureUsageFlags usage = 0;
     GPUTextureAspect aspect = "all";
     GPUIntegerCoordinate baseMipLevel = 0;
     GPUIntegerCoordinate mipLevelCount;
     GPUIntegerCoordinate baseArrayLayer = 0;
     GPUIntegerCoordinate arrayLayerCount;
-    GPUTextureUsageFlags usage = 0;
-    [RuntimeEnabled=WebGPUTextureComponentSwizzle] DOMString swizzle = "rgba";
+    DOMString swizzle = "rgba";
 };
 
 enum GPUTextureViewDimension {
diff --git a/third_party/blink/renderer/platform/fonts/shaping/shape_result.cc b/third_party/blink/renderer/platform/fonts/shaping/shape_result.cc
index 40bc423..d196bab 100644
--- a/third_party/blink/renderer/platform/fonts/shaping/shape_result.cc
+++ b/third_party/blink/renderer/platform/fonts/shaping/shape_result.cc
@@ -983,12 +983,12 @@
 }
 }  // anonymous namespace
 
-float ShapeResult::ApplySpacingOrExpansion(ShapeResultSpacing& spacing,
-                                           bool is_expansion,
-                                           int text_start_offset) {
-  float offset = 0;
+TextRunLayoutUnit ShapeResult::ApplySpacingOrExpansion(
+    ShapeResultSpacing& spacing,
+    bool is_expansion,
+    int text_start_offset) {
   float total_advance = 0;
-  TextRunLayoutUnit space;
+  TextRunLayoutUnit spacing_after;
   for (auto& run : runs_) {
     if (!run)
       continue;
@@ -1008,49 +1008,49 @@
       ShapeResultSpacing::ComputeSpacingParameters parameters{
           .index = run_start_index + glyph_data.character_index,
           .original_advance = glyph_data.advance};
+      TextRunLayoutUnit spacing_before;
       if (is_expansion) {
         auto result = spacing.ComputeExpansion(parameters.index,
                                                IsCursiveScript(run->script_));
-        offset = result.first;
-        space = result.second;
+        spacing_before = result.first;
+        spacing_after = result.second;
       } else {
-        space = spacing.ComputeSpacing(parameters, offset,
-                                       IsCursiveScript(run->script_));
+        spacing_after =
+            spacing.ComputeSpacing(parameters, IsCursiveScript(run->script_));
       }
-      glyph_data.AddAdvance(space);
+      glyph_data.AddAdvance(spacing_before + spacing_after);
       total_advance_for_run += glyph_data.advance;
 
-      // |offset| is non-zero only when justifying CJK characters that follow
-      // non-CJK characters.
-      if (offset) [[unlikely]] {
+      // `spacing_before` is non-zero only when justifying CJK characters that
+      // follow non-CJK characters.
+      if (spacing_before) [[unlikely]] {
+        float offset = spacing_before.ToFloat();
         if (run->IsHorizontal()) {
           run->glyph_data_.AddOffsetWidthAt(i, offset);
         } else {
           run->glyph_data_.AddOffsetHeightAt(i, offset);
           has_vertical_offsets_ = true;
         }
-        offset = 0;
       }
     }
     run->width_ = total_advance_for_run;
     total_advance += run->width_;
   }
   width_ = total_advance;
-  return space;
+  return spacing_after;
 }
 
-float ShapeResult::ApplySpacing(ShapeResultSpacing& spacing,
-                                int text_start_offset) {
+void ShapeResult::ApplySpacing(ShapeResultSpacing& spacing,
+                               int text_start_offset) {
   // For simplicity, we apply spacing once only. If you want to do multiple
   // time, please get rid of below |DCHECK()|.
   DCHECK(!is_applied_spacing_) << this;
   is_applied_spacing_ = true;
-  return ApplySpacingOrExpansion(spacing, /* is_expansion */ false,
-                                 text_start_offset);
+  ApplySpacingOrExpansion(spacing, /* is_expansion */ false, text_start_offset);
 }
 
-float ShapeResult::ApplyExpansion(ShapeResultSpacing& spacing,
-                                  int text_start_offset) {
+TextRunLayoutUnit ShapeResult::ApplyExpansion(ShapeResultSpacing& spacing,
+                                              int text_start_offset) {
   // For simplicity, we apply spacing once only. If you want to do multiple
   // time, please get rid of below |DCHECK()|.
   DCHECK(!is_applied_spacing_) << this;
diff --git a/third_party/blink/renderer/platform/fonts/shaping/shape_result.h b/third_party/blink/renderer/platform/fonts/shaping/shape_result.h
index d26054e..3f3cdbff 100644
--- a/third_party/blink/renderer/platform/fonts/shaping/shape_result.h
+++ b/third_party/blink/renderer/platform/fonts/shaping/shape_result.h
@@ -262,9 +262,7 @@
   // |text_start_offset| adjusts the character index in the ShapeResult before
   // giving it to |ShapeResultSpacing|. It can be negative if
   // |StartIndex()| is larger than the text in |ShapeResultSpacing|.
-  //
-  // The function returns spacing amount on the right of the last glyph.
-  float ApplySpacing(ShapeResultSpacing&, int text_start_offset = 0);
+  void ApplySpacing(ShapeResultSpacing&, int text_start_offset = 0);
 
   // Apply expansion (justification) as configured to |ShapeResultSpacing|.
   // |text_start_offset| adjusts the character index in the ShapeResult before
@@ -272,7 +270,7 @@
   // |StartIndex()| is larger than the text in |ShapeResultSpacing|.
   //
   // The function returns spacing amount on the right of the last glyph.
-  float ApplyExpansion(ShapeResultSpacing&, int text_start_offset);
+  TextRunLayoutUnit ApplyExpansion(ShapeResultSpacing&, int text_start_offset);
   // Add `expansion` space before the first glyph.
   void ApplyLeadingExpansion(LayoutUnit expansion);
   // Add `expansion` space after the last glyph.
@@ -424,9 +422,9 @@
   void ComputePositionData() const;
   void RecalcCharacterPositions() const;
 
-  float ApplySpacingOrExpansion(ShapeResultSpacing&,
-                                bool is_expansion,
-                                int text_start_offset = 0);
+  TextRunLayoutUnit ApplySpacingOrExpansion(ShapeResultSpacing&,
+                                            bool is_expansion,
+                                            int text_start_offset = 0);
   template <bool is_horizontal_run>
   void ComputeGlyphPositions(ShapeResultRun*,
                              unsigned start_glyph,
diff --git a/third_party/blink/renderer/platform/fonts/shaping/shape_result_spacing.cc b/third_party/blink/renderer/platform/fonts/shaping/shape_result_spacing.cc
index b8dd9f0..11cce01 100644
--- a/third_party/blink/renderer/platform/fonts/shaping/shape_result_spacing.cc
+++ b/third_party/blink/renderer/platform/fonts/shaping/shape_result_spacing.cc
@@ -96,7 +96,6 @@
 
 TextRunLayoutUnit ShapeResultSpacing::ComputeSpacing(
     const ComputeSpacingParameters& parameters,
-    float& offset,
     bool is_cursive_script) {
   DCHECK(has_spacing_);
   unsigned index = parameters.index;
@@ -132,16 +131,15 @@
   return spacing;
 }
 
-std::pair<float, TextRunLayoutUnit> ShapeResultSpacing::ComputeExpansion(
-    unsigned index,
-    bool is_cursive_script) {
+std::pair<TextRunLayoutUnit, TextRunLayoutUnit>
+ShapeResultSpacing::ComputeExpansion(unsigned index, bool is_cursive_script) {
   if (!HasExpansion() || index >= text_.length()) {
-    return {0.0f, TextRunLayoutUnit()};
+    return {TextRunLayoutUnit(), TextRunLayoutUnit()};
   }
   DCHECK(!normalize_space_);
   DCHECK(!allow_tabs_);
 
-  float spacing_before = 0;
+  TextRunLayoutUnit spacing_before;
   TextRunLayoutUnit spacing_after;
 
   bool opportunity_before = false;
@@ -152,8 +150,6 @@
     opportunity_before = pair.first;
     opportunity_after = pair.second;
   } else {
-    VLOG(0) << __func__ << " size=" << text_.Span16().size()
-            << " index=" << index;
     auto pair = CheckJustificationOpportunity16(
         justification_method_, CodePointAt(text_.Span16(), index),
         is_after_expansion_);
@@ -163,17 +159,13 @@
 
   if (opportunity_before) {
     // Take the expansion opportunity before this ideograph.
-    TextRunLayoutUnit expand_before = NextExpansion();
-    if (expand_before) {
-      spacing_before += expand_before.ToFloat();
-      spacing_after += expand_before;
-    }
+    spacing_before = NextExpansion();
     if (!HasExpansion()) {
       return {spacing_before, spacing_after};
     }
   }
   if (opportunity_after) {
-    return {spacing_before, spacing_after + NextExpansion()};
+    spacing_after = NextExpansion();
   }
   return {spacing_before, spacing_after};
 }
diff --git a/third_party/blink/renderer/platform/fonts/shaping/shape_result_spacing.h b/third_party/blink/renderer/platform/fonts/shaping/shape_result_spacing.h
index 3eb133d..77156f5 100644
--- a/third_party/blink/renderer/platform/fonts/shaping/shape_result_spacing.h
+++ b/third_party/blink/renderer/platform/fonts/shaping/shape_result_spacing.h
@@ -60,29 +60,31 @@
                     bool allows_leading_expansion = false,
                     bool allows_trailing_expansion = false);
 
-  // Compute the sum of all spacings for the specified |index|.
-  // The |index| is for the string given in the constructor.
-  // For justification, this function must be called incrementally since it
-  // keeps states and counts consumed justification opportunities.
   struct ComputeSpacingParameters {
     unsigned index;
     float original_advance = 0.0;
   };
+  // Compute spacings for the specified `index`.
+  // This function returns space amount to be added after the glyph.
+  //
+  // The `index` is for the string given in the constructor.
   TextRunLayoutUnit ComputeSpacing(unsigned index,
-                                   float& offset,
                                    bool is_cursive_script = false) {
-    return ComputeSpacing(ComputeSpacingParameters{.index = index}, offset,
+    return ComputeSpacing(ComputeSpacingParameters{.index = index},
                           is_cursive_script);
   }
   TextRunLayoutUnit ComputeSpacing(const ComputeSpacingParameters& parameters,
-                                   float& offset,
                                    bool is_cursive_script);
 
-  // Returns a pair of
+  // Compute spacings to justify a glyph at the specified `index`.
+  // This function returns a pair of
   //  * Space amount to be added before the glyph, and
-  //  * Pixel amount to be added to the glyph's advance.
-  // Pixel amount to be added after the glyph is <the latter> - <the former>.
-  std::pair<float, TextRunLayoutUnit> ComputeExpansion(
+  //  * Space amount to be added after the glyph.
+  //
+  // The `index` is for the string given in the constructor.
+  // This function must be called incrementally since it keeps states and
+  // counts consumed justification opportunities.
+  std::pair<TextRunLayoutUnit, TextRunLayoutUnit> ComputeExpansion(
       unsigned index,
       bool is_cursive_script = false);
 
diff --git a/third_party/blink/renderer/platform/graphics/canvas_resource_provider.cc b/third_party/blink/renderer/platform/graphics/canvas_resource_provider.cc
index bf7f8ad2..90db0bb 100644
--- a/third_party/blink/renderer/platform/graphics/canvas_resource_provider.cc
+++ b/third_party/blink/renderer/platform/graphics/canvas_resource_provider.cc
@@ -335,13 +335,6 @@
           ContextProviderWrapper()->ContextProvider().RasterContextProvider())),
       is_accelerated_(is_accelerated),
       shared_image_usage_flags_(shared_image_usage_flags) {
-  if (is_accelerated_) {
-    CHECK(ContextProviderWrapper()
-              ->ContextProvider()
-              .GetCapabilities()
-              .gpu_rasterization);
-  }
-
   if (ContextProviderWrapper()) {
     // Graphite can handle a large buffer size.
     if (ContextProviderWrapper()
diff --git a/third_party/blink/renderer/platform/graphics/canvas_resource_provider.h b/third_party/blink/renderer/platform/graphics/canvas_resource_provider.h
index 4fb4ab4d..5c4c60e5 100644
--- a/third_party/blink/renderer/platform/graphics/canvas_resource_provider.h
+++ b/third_party/blink/renderer/platform/graphics/canvas_resource_provider.h
@@ -238,24 +238,13 @@
   // rate.
   virtual bool IsSingleBuffered() const = 0;
 
-  SkSurface* GetSkSurface() const;
   bool IsGpuContextLost() const;
 
-  // Returns true iff the resource provider is (a) using a GPU channel for
-  // software SharedImages and (b) that channel has been lost.
-  virtual bool IsSoftwareSharedImageGpuChannelLost() const;
-  static void NotifyGpuContextLostTask(base::WeakPtr<CanvasResourceProvider>);
-
   virtual bool WritePixels(const SkImageInfo& orig_info,
                            const void* pixels,
                            size_t row_bytes,
                            int x,
                            int y) = 0;
-  bool UnacceleratedWritePixels(const SkImageInfo& orig_info,
-                                const void* pixels,
-                                size_t row_bytes,
-                                int x,
-                                int y);
 
   CanvasResourceProvider(const CanvasResourceProvider&) = delete;
   CanvasResourceProvider& operator=(const CanvasResourceProvider&) = delete;
@@ -289,6 +278,18 @@
  protected:
   class CanvasImageProvider;
 
+  // Returns true iff the resource provider is (a) using a GPU channel for
+  // software SharedImages and (b) that channel has been lost.
+  virtual bool IsSoftwareSharedImageGpuChannelLost() const;
+  static void NotifyGpuContextLostTask(base::WeakPtr<CanvasResourceProvider>);
+
+  SkSurface* GetSkSurface() const;
+  bool UnacceleratedWritePixels(const SkImageInfo& orig_info,
+                                const void* pixels,
+                                size_t row_bytes,
+                                int x,
+                                int y);
+
   gpu::raster::RasterInterface* RasterInterface() const;
   base::WeakPtr<WebGraphicsContext3DProviderWrapper> ContextProviderWrapper()
       const {
diff --git a/third_party/blink/renderer/platform/loader/fetch/resource_response.h b/third_party/blink/renderer/platform/loader/fetch/resource_response.h
index 9ed1af5..a9eaae1 100644
--- a/third_party/blink/renderer/platform/loader/fetch/resource_response.h
+++ b/third_party/blink/renderer/platform/loader/fetch/resource_response.h
@@ -699,14 +699,6 @@
 
   bool emitted_extra_info_ = false;
 
-  // Flag indicating if the request used IP Protection proxies.
-  // This differs from the `is_for_ip_protection` used in the network proxy
-  // chain, but uses `is_for_ip_protection`, along with whether the response was
-  // cached, to determine if the request was actively sent through an IP
-  // Protection proxy. This value is currently only set if
-  // kIpPrivacyEnableIppInDevTools is enabled.
-  bool is_ip_protection_used_ = false;
-
   Vector<network::IntegrityMetadata> unencoded_digests_;
 };
 
diff --git a/third_party/blink/renderer/platform/network/encoded_form_data.cc b/third_party/blink/renderer/platform/network/encoded_form_data.cc
index b9e7492d..1991648 100644
--- a/third_party/blink/renderer/platform/network/encoded_form_data.cc
+++ b/third_party/blink/renderer/platform/network/encoded_form_data.cc
@@ -218,6 +218,12 @@
   return Latin1Encoding().Decode(base::as_byte_span(bytes));
 }
 
+String EncodedFormData::FormatContentTypeWithBoundary() const {
+  // Here we handle `boundary_` as a C-style string. See
+  // FormDataEncoder::GenerateUniqueBoundaryString.
+  return StrCat({"multipart/form-data; boundary=", boundary_.data()});
+}
+
 uint64_t EncodedFormData::SizeInBytes() const {
   unsigned size = 0;
   for (const FormDataElement& e : elements_) {
diff --git a/third_party/blink/renderer/platform/network/encoded_form_data.h b/third_party/blink/renderer/platform/network/encoded_form_data.h
index 4684308..2bb703a3 100644
--- a/third_party/blink/renderer/platform/network/encoded_form_data.h
+++ b/third_party/blink/renderer/platform/network/encoded_form_data.h
@@ -129,6 +129,9 @@
   Vector<FormDataElement>& MutableElements() { return elements_; }
 
   const Vector<char>& Boundary() const { return boundary_; }
+  // Returns a string concatenating "multipart/form-data; boundary=" and the
+  // boundary.
+  String FormatContentTypeWithBoundary() const;
   void SetBoundary(Vector<char> boundary) { boundary_ = boundary; }
 
   // Identifies a particular form submission instance.  A value of 0 is used
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index 18f7f51..1692e2c 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -2724,6 +2724,11 @@
       public: true,
     },
     {
+      name: "GamepadRawInputChangeEvent",
+      status: "experimental",
+      public: true,
+    },
+    {
       name: "GamepadWindowEventHandlers",
       status: "stable",
     },
@@ -3504,6 +3509,11 @@
       },
       base_feature: "none",
     },
+    // This is a killswitch flag, shipped in M144. It can be removed in M146.
+    {
+      name: "NoAriaDetailsForAnchorPos",
+      status: "stable",
+    },
     {
       // This turns off all font antialiasing for tests unless it is
       // explicitly required and enabled using the
@@ -4999,6 +5009,10 @@
       status: "test",
     },
     {
+      name: "SvgLengthResolveUnparsedValue",
+      status: "stable",
+    },
+    {
       name: "SvgNonScalingStrokePrecisionFix",
       status: "stable",
     },
@@ -5853,11 +5867,6 @@
       implied_by: ["WebGPUExperimentalFeatures"],
     },
     {
-      // https://chromestatus.com/feature/5110223547269120
-      name: "WebGPUTextureComponentSwizzle",
-      status: "stable",
-    },
-    {
       name: "WebHID",
       status: {"Android": "", "default": "stable"},
     },
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 762bc9e5..8012e32 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -66,14 +66,14 @@
 crbug.com/447125474 virtual/content-extraction/fast/dom/HTMLObjectElement/update-data.html [ Crash Timeout ]
 crbug.com/447125474 virtual/content-extraction/fast/dynamic/position-change-to-absolute-with-positioned-child-crash.html [ Crash Timeout ]
 crbug.com/447125474 virtual/content-extraction/fast/events/touch/touch-rect-assert-first-layer-special.html [ Crash Timeout ]
-crbug.com/447125474 virtual/content-extraction/fast/forms/datalist/input-appearance-range-with-transform.html [ Crash Timeout Failure Pass ]
+crbug.com/447125474 virtual/content-extraction/fast/forms/datalist/input-appearance-range-with-transform.html [ Crash Failure Pass Timeout ]
 crbug.com/447125474 virtual/content-extraction/fast/sub-pixel/sub-pixel-accumulates-to-layers.html [ Crash Timeout ]
 crbug.com/447125474 virtual/content-extraction/fast/sub-pixel/transformed-iframe-copy-on-scroll.html [ Crash Timeout ]
 crbug.com/447125474 virtual/content-extraction/paint/invalidation/compositing/subpixel-offset-scaled-will-change-transform.html [ Crash Timeout ]
 crbug.com/447125474 virtual/content-extraction/paint/invalidation/transform/subpixel-offset-scaled-transform.html [ Crash Timeout ]
 crbug.com/447125474 virtual/content-extraction/transforms/rotate.html [ Crash Timeout ]
 crbug.com/447125474 virtual/content-extraction/transforms/3d/hit-testing/hover-rotated-with-children-negative-z.html [ Crash Timeout ]
-crbug.com/447125474 virtual/content-extraction/transforms/3d/point-mapping/3d-point-mapping-deep.html [ Crash Timeout Failure ]
+crbug.com/447125474 virtual/content-extraction/transforms/3d/point-mapping/3d-point-mapping-deep.html [ Crash Failure Timeout ]
 
 # Reftest failure related to off-main-thread interaction (imperceptible AA differences)
 crbug.com/395909730 virtual/off-main-thread-css-paint/http/tests/csspaint/paint2d-zoom.html [ Failure ]  # Reftest image failure
@@ -1648,9 +1648,6 @@
 # This test is flaky when FixedElementsDontOverscroll is enabled.
 crbug.com/585766 fast/table/frame-and-rules.html [ Failure Pass Timeout ]
 
-# Hiding video::-webkit-media-controls breaks --force-overlay-fullscreen-video.
-crbug.com/1093447 virtual/android/fullscreen/rendering/backdrop-video.html [ Failure ]
-
 # gpuBenchmarking.pinchBy is busted on desktops for touchscreen pinch
 crbug.com/787615 [ Win ] virtual/threaded/synthetic_gestures/synthetic-pinch-zoom-gesture-touchscreen.html [ Failure Pass ]
 
@@ -9956,9 +9953,11 @@
 crbug.com/463661453 [ Mac ] virtual/threaded/wpt_internal/scheduler/integration_tests/rendering-starvation.html [ Failure Pass ]
 
 # Gardener 2025-11-27
-crbug.com/464191951 [ Mac ] http/tests/devtools/elements/inspect-pointer-events-none.js [ Timeout Pass ]
+crbug.com/464191951 [ Mac ] http/tests/devtools/elements/inspect-pointer-events-none.js [ Pass Timeout ]
 crbug.com/464220937 [ Mac ] http/tests/devtools/elements/inspect-pseudo-element.js [ Failure Pass ]
 
+crbug.com/407749531 http/tests/devtools/a11y-axe-core/application-panel/app-manifest-view-a11y-test.js [ Failure Pass ]
+
 # Gardener 2025-11-28
 crbug.com/464388023 [ Mac ] virtual/wasm-code-caching/http/tests/wasm/caching/code-cache.html [ Failure Pass ]
 crbug.com/464400408 wpt_internal/code-cache/code-cache-after-304.window.html [ Failure Pass ]
diff --git a/third_party/blink/web_tests/VirtualTestSuites b/third_party/blink/web_tests/VirtualTestSuites
index 11c69d1..4960acd 100644
--- a/third_party/blink/web_tests/VirtualTestSuites
+++ b/third_party/blink/web_tests/VirtualTestSuites
@@ -1217,7 +1217,7 @@
       "--test-third-party-cookie-phaseout",
       "--use-related-website-set={\"primary\":\"https://firstparty.test\",\"associatedSites\":[\"https://cookie.test\"]}"
     ],
-    "expires": "Dec 1, 2025",
+    "expires": "Jun 1, 2026",
     "owners": [
       "chrome-first-party-sets@chromium.org"
     ]
diff --git a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
index 8d731b2b..6a778e9 100644
--- a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
+++ b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
@@ -666,6 +666,13 @@
        {}
       ]
      ],
+     "anchor-loop-crash.html": [
+      "002e3a88bf6ff127f5d526adfddf84f19d68ca3d",
+      [
+       null,
+       {}
+      ]
+     ],
      "anchor-mathml-crash.html": [
       "595cc73d301ce04b854ee530a4a4bccf725d5e1b",
       [
@@ -3120,7 +3127,7 @@
      "grid-lanes": {
       "tentative": {
        "grid-lanes-fieldset-crash.html": [
-        "486ee4f6c71530137e0c53f7a174334bdd14b444",
+        "330be3e7025041927502ec881b1005179efe9cc3",
         [
          null,
          {}
@@ -3137,21 +3144,21 @@
          ],
          "intrinsic-auto-repeat": {
           "column-auto-repeat-auto-no-items-crash.html": [
-           "75b64ba940f985c5608d6b01487644fe7049d571",
+           "1de988007c15a4d5385fad865be861ea7892aee0",
            [
             null,
             {}
            ]
           ],
           "repeat-auto-fit-auto-crash.html": [
-           "b2136b34f34abd77a6ab48bc392d2810e98717dd",
+           "f3d7f304bdf8ca501a71dbd38ea3334f072e210b",
            [
             null,
             {}
            ]
           ],
           "row-auto-repeat-auto-no-items-crash.html": [
-           "cb31cc081d20433b716caf48e8f16d8145e081e8",
+           "feed979dce6e1e37846d2d27453d202e8beadabe",
            [
             null,
             {}
@@ -6491,6 +6498,13 @@
          null,
          {}
         ]
+       ],
+       "zindex-part.html": [
+        "694a6c9f6ca112054731f35977c2d01bd6e39aed",
+        [
+         null,
+         {}
+        ]
        ]
       }
      },
@@ -6630,6 +6644,13 @@
        {}
       ]
      ],
+     "text-combine-upright-sideways-crash.html": [
+      "ba85c1ea1dcd40c63e75ff5f8a7c252c7f8d40ef",
+      [
+       null,
+       {}
+      ]
+     ],
      "text-combine-webkit-crash.html": [
       "228f066f2360850ea881be2203753b7cef2a7685",
       [
@@ -6863,6 +6884,15 @@
       ]
      ]
     },
+    "mediaqueries": {
+     "duplicate-media-stylesheet-crash.html": [
+      "46b76f19e7c81d12da4ec4c3e1d3df59f9fdf755",
+      [
+       null,
+       {}
+      ]
+     ]
+    },
     "motion": {
      "offset-path-huge-angle-deg-001-crash.html": [
       "1e3ddfafe1f94196e8e4babf8dac560175d15481",
@@ -6968,6 +6998,13 @@
        ]
       ]
      },
+     "is-where-error-crash.html": [
+      "f2c0537d9cf5e73d8c9410205e780a490d1dfd02",
+      [
+       null,
+       {}
+      ]
+     ],
      "link-sharing-crash.html": [
       "3bcce92d49d67bd694028db2549b65eb5644fdca",
       [
@@ -7099,6 +7136,15 @@
        {}
       ]
      ],
+     "crashtests": {
+      "document-replaceChild-input-documentElement.html": [
+       "e435c6ceafbf4598b4a621931ee4f82b0fc2e466",
+       [
+        null,
+        {}
+       ]
+      ]
+     },
      "moveBefore": {
       "chrome-338071841-crash.html": [
        "26adfb1cbfae116e0a88a7ff2cbf24cdb9a5e7f7",
@@ -7808,6 +7854,13 @@
        {}
       ]
      ],
+     "join-first-li-containing-preformatted-linefeed-and-last-li.html": [
+      "3f2deed9bd48040e970587765008622cda8e81f4",
+      [
+       null,
+       {}
+      ]
+     ],
      "justifycenter-around-textarea.html": [
       "71d24f37b45303f938029bd13469b68f22fa38fd",
       [
@@ -77117,6 +77170,19 @@
        {}
       ]
      ],
+     "anchor-scroll-composited-scrolling-paint-001.html": [
+      "c1d209ad4b1b02425cf2b7ae85080c7f5e547687",
+      [
+       null,
+       [
+        [
+         "/css/css-anchor-position/anchor-scroll-composited-scrolling-paint-001-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
      "anchor-scroll-fixedpos-002.html": [
       "970fb45af0c8208aa509f554619326254f97ce18",
       [
@@ -100802,6 +100868,32 @@
        {}
       ]
      ],
+     "out-of-flow-in-multicolumn-118.html": [
+      "09e55ff6e208cee23366f02ab91bae68197d7409",
+      [
+       null,
+       [
+        [
+         "/css/reference/ref-filled-green-100px-square.xht",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
+     "out-of-flow-in-multicolumn-119.html": [
+      "6d372d51ff24151151fbed725c3d1a66447baa8a",
+      [
+       null,
+       [
+        [
+         "/css/reference/ref-filled-green-100px-square.xht",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
      "overflow-clip-000.html": [
       "72b10f5cdd3092a042f1f90bff04e9428d61608e",
       [
@@ -147050,7 +147142,7 @@
       "tentative": {
        "abspos": {
         "column-grid-lanes-alignment.html": [
-         "d1b8d638779e277c9fbf480680843730151150b7",
+         "58487896511a262a2505812c6de9fc23ff77a1f8",
          [
           null,
           [
@@ -147063,7 +147155,7 @@
          ]
         ],
         "column-grid-lanes-intrinsic-sizing-oof.html": [
-         "79d138a1cbbcba02d3d0e9fd5970c35a8e98f34b",
+         "62b39b817bc530330635e54fa3da8c6cd025f316",
          [
           null,
           [
@@ -147076,7 +147168,7 @@
          ]
         ],
         "column-grid-lanes-out-of-flow-001.html": [
-         "7e3d0b0d4ff837e6fac7770449593c6f3499ce16",
+         "d619fb23f0d099aebe5d595fc804f9c0f0634a69",
          [
           null,
           [
@@ -147089,7 +147181,7 @@
          ]
         ],
         "column-grid-lanes-out-of-flow-002.html": [
-         "af40c1918ab00c3fb022abdddde209ea42dc0204",
+         "4900ed7d11db6bc91629c31e40ed08dfb7c4b662",
          [
           null,
           [
@@ -147102,7 +147194,7 @@
          ]
         ],
         "column-grid-lanes-out-of-flow-003.html": [
-         "ed1d0778136085e7635b8e68f194b7fbcf153a38",
+         "376575c028a05a5c40bc05e43f20300e5d8da37e",
          [
           null,
           [
@@ -147115,7 +147207,7 @@
          ]
         ],
         "column-grid-lanes-positioned-item-dynamic-change.html": [
-         "ba01354c18ca7262b22769d8de6b8fd429a6950a",
+         "2b278261cb40899259055cf4bb3eeb77dbb33330",
          [
           null,
           [
@@ -147128,7 +147220,7 @@
          ]
         ],
         "row-grid-lanes-alignment.html": [
-         "eae8e14d86330fc4e0f20ece5b2761239759da05",
+         "a01209a58f0530bfc5d6d6b83deec6c323eff453",
          [
           null,
           [
@@ -147141,7 +147233,7 @@
          ]
         ],
         "row-grid-lanes-intrinsic-sizing-oof.html": [
-         "8e724bd9a849cf71e724673f98d39b518746d9be",
+         "330bbf8b14cf242c8b5be8f667c383ca93cf0766",
          [
           null,
           [
@@ -147154,7 +147246,7 @@
          ]
         ],
         "row-grid-lanes-out-of-flow-001.html": [
-         "f038e0843d3b0d00f7ba5fc83ccb258e972cf408",
+         "d9956828f035f8dc39d2cd74df6589cc7470f429",
          [
           null,
           [
@@ -147167,7 +147259,7 @@
          ]
         ],
         "row-grid-lanes-out-of-flow-002.html": [
-         "cbcd58065e9ad78f1496aa52151919bd811627b5",
+         "5e6ec792dd76e3a2926687197a2277cb24125299",
          [
           null,
           [
@@ -147180,7 +147272,7 @@
          ]
         ],
         "row-grid-lanes-out-of-flow-003.html": [
-         "bebeff178685d4d83296b8eecd2352337c07514c",
+         "dc71ee959697e0a8aa3356f36d84290825784dbf",
          [
           null,
           [
@@ -147193,7 +147285,7 @@
          ]
         ],
         "row-grid-lanes-positioned-item-dynamic-change.html": [
-         "cd01a6e803636427f8dc55d13eeb509cd458d893",
+         "9b84e90908f8428134be2a7b7fd8792ef7390e32",
          [
           null,
           [
@@ -147208,7 +147300,7 @@
        },
        "alignment": {
         "column-grid-lanes-alignment-positioned-items-001.html": [
-         "689dac5694c4b53f5dfaad88e01ee398e209e11a",
+         "e3498aa55b19465670db3c8c3d1f8fdb441d78e4",
          [
           null,
           [
@@ -147221,7 +147313,7 @@
          ]
         ],
         "column-grid-lanes-alignment-positioned-items-002.html": [
-         "9c2da506c4c89692df472a856a1130caadbaf64e",
+         "3074a6361b3b057645b84a6adf58bc3dccd11811",
          [
           null,
           [
@@ -147234,7 +147326,7 @@
          ]
         ],
         "column-grid-lanes-alignment-positioned-items-003.html": [
-         "7682081907a17694ebb6a4b1f2481eea4a21bff7",
+         "998f48bac3ffcc0830259c2f55e39b1fe4c972a2",
          [
           null,
           [
@@ -147247,7 +147339,7 @@
          ]
         ],
         "column-grid-lanes-alignment-positioned-items-004.html": [
-         "f7c18de0cc9baed0862c014d9553adec948b602b",
+         "3dd9ccc533deaf288584a12d186aaeda317873b2",
          [
           null,
           [
@@ -147260,7 +147352,7 @@
          ]
         ],
         "column-grid-lanes-justify-self-001.html": [
-         "4d92cb9912a5d94658c5c648e0aff9ce312670ad",
+         "a6de89befae289b748937fb4524e41d8b11d2642",
          [
           null,
           [
@@ -147273,7 +147365,7 @@
          ]
         ],
         "column-grid-lanes-justify-self-002.html": [
-         "a535e29df985be054c0f8645dc370d66afeb76e2",
+         "00f5355a71c804c53afd3b9f7f9e8487798bb9c1",
          [
           null,
           [
@@ -147286,7 +147378,7 @@
          ]
         ],
         "column-grid-lanes-justify-self-003.html": [
-         "564e96eb311eaa77372f56818d06f88d4bbfbfae",
+         "e21fbc69ee3215645351b91661a935449ca2c167",
          [
           null,
           [
@@ -147299,7 +147391,7 @@
          ]
         ],
         "column-justify-items-center-001.html": [
-         "f983622ed60fc64d2be852d56694a4acc489e636",
+         "ecd3aea27c5f1c269b2d8c45f09d5f3596fcdd82",
          [
           null,
           [
@@ -147312,7 +147404,7 @@
          ]
         ],
         "column-justify-items-end-justify-self-start-001.html": [
-         "7295e39b421f50708b485798ce9f97ea94801393",
+         "738860f33017f494afd275296d978de120f9deb7",
          [
           null,
           [
@@ -147338,7 +147430,7 @@
          ]
         ],
         "grid-lanes-align-content-001.html": [
-         "c1d67bc81325955ca92826686496f8f5da279904",
+         "847166dc8bf374dd6035c81c8aeda9019eb837aa",
          [
           null,
           [
@@ -147351,7 +147443,7 @@
          ]
         ],
         "grid-lanes-align-content-002.html": [
-         "91e651bfd0734daeb4cbf8ca636078d6fa015a45",
+         "7cafe129321e74db1b5e858fcdd179042140f1e5",
          [
           null,
           [
@@ -147364,7 +147456,7 @@
          ]
         ],
         "grid-lanes-align-content-003.html": [
-         "8ce72c9ae098cfe41c88ac829c6d2eafddb85991",
+         "c21dac8eeb577fbd6b0f2530476859cc5637d30c",
          [
           null,
           [
@@ -147377,7 +147469,7 @@
          ]
         ],
         "grid-lanes-align-content-004.html": [
-         "30b21cffef16e7de14951681eb7f87ae66904a55",
+         "9d3c7a45b0bd7f28e454e027782b5b870a17cd37",
          [
           null,
           [
@@ -147390,7 +147482,7 @@
          ]
         ],
         "grid-lanes-justify-content-001.html": [
-         "81a3b05d07a64a6626b9b1979d201b3707539e5d",
+         "4c05936b852e31b2cc1fae5aeee26fdddb37bb97",
          [
           null,
           [
@@ -147403,7 +147495,7 @@
          ]
         ],
         "grid-lanes-justify-content-002.html": [
-         "f38429bf872833cf352feb64c1831015edae2bd8",
+         "29e9df251496ca39c364495992d463026bc2cc65",
          [
           null,
           [
@@ -147416,7 +147508,7 @@
          ]
         ],
         "grid-lanes-justify-content-003.html": [
-         "cb1b1d21f3a101f669b5ced8088d136279a0e898",
+         "082db199ac98a041dfc3971daf1f21041aae69a3",
          [
           null,
           [
@@ -147429,7 +147521,7 @@
          ]
         ],
         "grid-lanes-justify-content-004.html": [
-         "3dca90a26fa947fe3497167050286206a2f03863",
+         "b210fdb486e19c284d814fbe4267408d740dfb9c",
          [
           null,
           [
@@ -147442,7 +147534,7 @@
          ]
         ],
         "row-align-items-center-001.html": [
-         "c8448ef337ea00ffd414ec8204f86610a8e7717e",
+         "3de32ab369d25532ce0877bd033c3d62f83d7dea",
          [
           null,
           [
@@ -147455,7 +147547,7 @@
          ]
         ],
         "row-align-items-end-align-self-start-001.html": [
-         "969147372b6380304a9784fb45359fe537abf10d",
+         "509c82510aec1ec2c08e7adcef4c259e35ae1594",
          [
           null,
           [
@@ -147468,7 +147560,7 @@
          ]
         ],
         "row-grid-lanes-align-self-001.html": [
-         "a28dc006167f6eb75bc3ed302cfc1ffe9f858b64",
+         "b69450b889111ba3286d76061f2dd1803d368c77",
          [
           null,
           [
@@ -147481,7 +147573,7 @@
          ]
         ],
         "row-grid-lanes-align-self-002.html": [
-         "b87cd97b461eabacf6c652a4f0d2c21545060d4c",
+         "f657f4c718c1ce1bd870320763313f8e2a330225",
          [
           null,
           [
@@ -147494,7 +147586,7 @@
          ]
         ],
         "row-grid-lanes-align-self-003.html": [
-         "00f88a3b03075a430281617bc44733b63e47f2b0",
+         "44752128f502ba129195026bd0b386a828d533e6",
          [
           null,
           [
@@ -147507,7 +147599,7 @@
          ]
         ],
         "row-grid-lanes-alignment-positioned-items-001.html": [
-         "61628b2cef516b562775816f1484388ec07e0e7c",
+         "0217879d91f0e100731591e120f274e5714e038f",
          [
           null,
           [
@@ -147520,7 +147612,7 @@
          ]
         ],
         "row-grid-lanes-alignment-positioned-items-002.html": [
-         "867ccced951d6e08341427153960b13351d5d2e0",
+         "b1c3d7d6e8332f0a958c26dad960ad913cefcd68",
          [
           null,
           [
@@ -147533,7 +147625,7 @@
          ]
         ],
         "row-grid-lanes-alignment-positioned-items-003.html": [
-         "af8be5d25af2490dda322b6736ebb9a3600367ba",
+         "92face8b07271ae779fdffb1f7305503e78b9f3f",
          [
           null,
           [
@@ -147546,7 +147638,7 @@
          ]
         ],
         "row-grid-lanes-alignment-positioned-items-004.html": [
-         "9f22c8136eeb5aeea3d754e6156a10bbacf27aa7",
+         "ee986af23ad7bf9a3d58fec61d88a03490654f5b",
          [
           null,
           [
@@ -147559,7 +147651,7 @@
          ]
         ],
         "row-overflow-alignment-001.html": [
-         "87fd71b82fa0ee9a090f773203f5de58e947e944",
+         "48cdc9408de4b1a5ec620d4b9896c2a11542875d",
          [
           null,
           [
@@ -147574,7 +147666,7 @@
        },
        "baseline": {
         "grid-lanes-grid-item-content-baseline-001.html": [
-         "9a248b74336021d4dd4920e4131a7b9ced0e1c33",
+         "c1917f5aff68ae42db789271b941b4f140a048d2",
          [
           null,
           [
@@ -147587,7 +147679,7 @@
          ]
         ],
         "grid-lanes-grid-item-self-baseline-001.html": [
-         "a17c4856fa914d8bee6d56fce1b8f60127462b53",
+         "ffae6833fd9e6f0a334ac3119e28dafd0754f7f8",
          [
           null,
           [
@@ -147600,7 +147692,7 @@
          ]
         ],
         "grid-lanes-grid-item-self-baseline-002a.html": [
-         "4883af84346ace95e6b7918bc27673f8b1041e8a",
+         "da0793f1573717507f25101ee439d9e93d58a431",
          [
           null,
           [
@@ -147613,7 +147705,7 @@
          ]
         ],
         "grid-lanes-grid-item-self-baseline-002b.html": [
-         "3fbfbaf76a95b777390894111b5ceafbaf2cb46c",
+         "926649d0c4eef3b6efd67d61ba0d5865ba8c8ee4",
          [
           null,
           [
@@ -147627,7 +147719,7 @@
         ]
        },
        "column-empty-grid-lanes-container-001.html": [
-        "6a32dc98b1f834b86c417bdc488aa95ba322ea7d",
+        "bc9f4174029b0c76786ca6883885c0e5b16cb3a9",
         [
          null,
          [
@@ -147640,7 +147732,7 @@
         ]
        ],
        "column-empty-grid-lanes-container-002.html": [
-        "4b7392e02ea131629e0fa49db53de78bdd18b0d5",
+        "f6039bff1452789d8188b69a14cc445a0f9cb707",
         [
          null,
          [
@@ -147654,7 +147746,7 @@
        ],
        "fragmentation": {
         "grid-lanes-fragmentation-001.html": [
-         "26b92c853e4b140d52cd83fec02b347ffa7e3931",
+         "df113deb76d8916402d4c56a73ed22ffbb770707",
          [
           null,
           [
@@ -147667,7 +147759,7 @@
          ]
         ],
         "grid-lanes-fragmentation-002.html": [
-         "80a561e7f8ad8815dfb659b1b5547122c4579b2e",
+         "32205c5468efb49a02571f7acb886354369776da",
          [
           null,
           [
@@ -147680,7 +147772,7 @@
          ]
         ],
         "grid-lanes-fragmentation-003.html": [
-         "3f687de827e028d03a8cf59682228302255efe0c",
+         "95067997bfbdd074546271b7055049eabc9ba036",
          [
           null,
           [
@@ -147695,7 +147787,7 @@
        },
        "gap": {
         "column-gaps-001.html": [
-         "0263dd28a15a666f1c316047ba824f8a84fdd6f0",
+         "e149a6170893776d0023255e2c6ec51728824ea0",
          [
           null,
           [
@@ -147708,7 +147800,7 @@
          ]
         ],
         "grid-lanes-gap-001.html": [
-         "931562312c4854c9fd939e4004f9853eb468bde0",
+         "d8296601e162d9c9cadbdca986e08aa147fbb504",
          [
           null,
           [
@@ -147721,7 +147813,7 @@
          ]
         ],
         "grid-lanes-gap-002.html": [
-         "4a429596b0566d7b0e9edd6a69d652d4bd4e90ee",
+         "79bc0a35f02ea18d16f1181ee0410b2c4172b731",
          [
           null,
           [
@@ -147734,7 +147826,7 @@
          ]
         ],
         "row-gaps-001.html": [
-         "4e674793810fd427c595321291151810d7db195f",
+         "1d241206e42e6736249d7821671a00fa322d7d93",
          [
           null,
           [
@@ -147748,7 +147840,7 @@
         ]
        },
        "grid-lanes-columns-item-containing-block-is-grid-content-width.html": [
-        "4c1b8a13d0b78e2249e869394b449e2850c6f548",
+        "a32ce6fd31449921fb280405dfa3dc1d12b6a6f5",
         [
          null,
          [
@@ -147761,7 +147853,7 @@
         ]
        ],
        "grid-lanes-not-inhibited-001.html": [
-        "9b9f6de870fcb8d668c6bf4b5d20015d238095c4",
+        "d00b274fd3aaa120e744ffb32336722b03c6736c",
         [
          null,
          [
@@ -147775,7 +147867,7 @@
        ],
        "grid-placement": {
         "column-explicit-placement-001.html": [
-         "98c69777e6e5aa650b41b2e2ff2d8a7d09f05e80",
+         "6de966b375cea7eb60c970950876c9222ff10191",
          [
           null,
           [
@@ -147788,7 +147880,7 @@
          ]
         ],
         "column-explicit-placement-002.html": [
-         "f86091d4eacbfb8442d473bbce7c98778d327ab6",
+         "34deb89d4d01f404b0b6b1c93075cb7677f9d3f9",
          [
           null,
           [
@@ -147801,7 +147893,7 @@
          ]
         ],
         "column-explicit-placement-003.html": [
-         "a3ec6b70a6f15f180304bd9ba41e2dcf607f7dd1",
+         "32918a5e736a8f2f94597390d99815363eec9990",
          [
           null,
           [
@@ -147814,7 +147906,7 @@
          ]
         ],
         "grid-lanes-grid-placement-named-lines-001.html": [
-         "d2170107d1617d78d8e65302ecd50516353ed5f2",
+         "9cb1d0bb99c86ce928505ceb90c749407fba581f",
          [
           null,
           [
@@ -147827,7 +147919,7 @@
          ]
         ],
         "grid-lanes-grid-placement-named-lines-002.html": [
-         "2c3925746f2b46157a3712ea7a3f0824d1e5b0eb",
+         "baeee7f3be1474096e4b2386723da080a390c6b3",
          [
           null,
           [
@@ -147840,7 +147932,7 @@
          ]
         ],
         "row-explicit-placement-001.html": [
-         "80dcc93dac6935a661a1d99d43b1b07109d89da4",
+         "bfa7920a153a252c834e99a51a9f1f45e7ca3916",
          [
           null,
           [
@@ -147853,7 +147945,7 @@
          ]
         ],
         "row-explicit-placement-002.html": [
-         "37dd34170b93011b30ab47738a5183e73e5b7a11",
+         "d521900b6244a926594d286f52b3d6e2d0fe5485",
          [
           null,
           [
@@ -147866,7 +147958,7 @@
          ]
         ],
         "row-explicit-placement-003.html": [
-         "efe9c01cb344f159e7e739e30e0a1874aa7df454",
+         "5077c48fa5e8fd1f4e3a107c13816577517f1ee2",
          [
           null,
           [
@@ -147879,7 +147971,7 @@
          ]
         ],
         "row-explicit-placement-004.html": [
-         "a251abdba497a5aa339543d82df68f8c689e3a25",
+         "7eac4348a2a9e8ecabb2b22aaa3365a1dab84f9e",
          [
           null,
           [
@@ -147892,7 +147984,7 @@
          ]
         ],
         "row-explicit-placement-005.html": [
-         "80c92ced8e9fc106a2c7dd9f2fdc5012415f34e1",
+         "08bfd0efd7972508005a2bd9dd43422509bf018d",
          [
           null,
           [
@@ -147905,7 +147997,7 @@
          ]
         ],
         "row-explicit-placement-006.html": [
-         "f8e9916881af31287a4d81410ede81a9e36d42d1",
+         "0742177aff4ad6785d05f19af4a42c2a4a13f45c",
          [
           null,
           [
@@ -147918,7 +148010,7 @@
          ]
         ],
         "row-explicit-placement-007.html": [
-         "a9bf4a32f4da9a79720ef5b20821fc1e6f56cdfb",
+         "78f6c3b3fcb36f7687d26dac93fea6fda124656a",
          [
           null,
           [
@@ -147931,7 +148023,7 @@
          ]
         ],
         "row-explicit-placement-008.html": [
-         "9f394bb0189dcc6c63215b6412cb3e2a211137d8",
+         "fd5d89ac100bdbb87cfd6e406dd1b861ca34736e",
          [
           null,
           [
@@ -147946,7 +148038,7 @@
        },
        "intrinsic-sizing": {
         "column-intrinsic-inline-container-size.html": [
-         "636ea2bda5105160bd2dfbb2ed25c5d0df2ee3f3",
+         "b209f5a6f0a4b6cd2739ca2afd2a61e89ad9107c",
          [
           null,
           [
@@ -147959,7 +148051,7 @@
          ]
         ],
         "grid-lanes-intrinsic-sizing-cols-001-auto.html": [
-         "f0577f20a79c50ae0548002c8ce42e6aacc07d4b",
+         "ec9a5b4dc766bfa1c4d45b6f929b870463c253bd",
          [
           null,
           [
@@ -147972,7 +148064,7 @@
          ]
         ],
         "grid-lanes-intrinsic-sizing-cols-001-fr.html": [
-         "c7baf73f7822bd40f1f944045e04444bed4b59d0",
+         "41786f5d48e412d86f72e235f8701c6ccc60415c",
          [
           null,
           [
@@ -147985,7 +148077,7 @@
          ]
         ],
         "grid-lanes-intrinsic-sizing-cols-001-mix1.html": [
-         "1a15436093123a9ac3dd387d175c4a07fd29b266",
+         "4e329c1a936745186042048fbbb62b2333d66557",
          [
           null,
           [
@@ -147998,7 +148090,7 @@
          ]
         ],
         "grid-lanes-intrinsic-sizing-cols-001-mix2.html": [
-         "f751b40ae1299bd487a0cad977ca5015494023d3",
+         "60a1534bff2c9908d4a4618f76cff256abba685b",
          [
           null,
           [
@@ -148011,7 +148103,7 @@
          ]
         ],
         "grid-lanes-intrinsic-sizing-cols-002-auto.html": [
-         "9aaafb08600b07070ce6a679007fa6e6a9bc61bf",
+         "c42f5407687aa006c2df03b5f516e5b3aa27f9d8",
          [
           null,
           [
@@ -148024,7 +148116,7 @@
          ]
         ],
         "grid-lanes-intrinsic-sizing-cols-002-fr.html": [
-         "25b6c45ed89edb761f9e982d336fcb69b355bb15",
+         "2ac4c3417ab61198f80e07d097cf53510cdc6da5",
          [
           null,
           [
@@ -148037,7 +148129,7 @@
          ]
         ],
         "grid-lanes-intrinsic-sizing-cols-002-mix1.html": [
-         "f9ba370633261473940489bfa617de993667a68d",
+         "f2ff9f051eb9626a00e3e9c5e4834fb9e65dfb98",
          [
           null,
           [
@@ -148050,7 +148142,7 @@
          ]
         ],
         "grid-lanes-intrinsic-sizing-cols-002-mix2.html": [
-         "9573517d2034c360c6630ffe8d353de52271ecef",
+         "5a8cf315945c94d9ab09005b1779e197def7c39a",
          [
           null,
           [
@@ -148063,7 +148155,7 @@
          ]
         ],
         "grid-lanes-intrinsic-sizing-cols-003-auto.html": [
-         "4655a28c85a07ceba950c3affc965edf8532ed91",
+         "54017308a1b177b19e7639c45218a526f17b7f55",
          [
           null,
           [
@@ -148076,7 +148168,7 @@
          ]
         ],
         "grid-lanes-intrinsic-sizing-cols-003-fr.html": [
-         "6bd73b23feffc174821e12d4dba2373dfc918d7b",
+         "23b66bcd74f07aa3df7a83623196692b6da4201b",
          [
           null,
           [
@@ -148089,7 +148181,7 @@
          ]
         ],
         "grid-lanes-intrinsic-sizing-cols-003-mix1.html": [
-         "623cb37704b3f67e839eab225a1bcb475f1dce26",
+         "775c80743c7271306f24c9f5715d555953780409",
          [
           null,
           [
@@ -148102,7 +148194,7 @@
          ]
         ],
         "grid-lanes-intrinsic-sizing-cols-003-mix2.html": [
-         "4a8f20ca351bd1c871ae9cbd90838047d55135bd",
+         "7237bf235443cb7e98029965892ee6bf2ac01844",
          [
           null,
           [
@@ -148115,7 +148207,7 @@
          ]
         ],
         "grid-lanes-intrinsic-sizing-cols-004-auto.html": [
-         "b913fa928365916f68853d748846ed24d0e4463c",
+         "9bb9e5be1a7b0295744d11ecb782da996da90e38",
          [
           null,
           [
@@ -148128,7 +148220,7 @@
          ]
         ],
         "grid-lanes-intrinsic-sizing-cols-004-fr.html": [
-         "5840c8b8a567387128878497dfd5e21e7321bd13",
+         "4f0f9dd99aac24869e09f769a51365525a49f132",
          [
           null,
           [
@@ -148141,7 +148233,7 @@
          ]
         ],
         "grid-lanes-intrinsic-sizing-cols-004-mix1.html": [
-         "f37a1718942b3638a3aea438e1ee122aa06b3ffc",
+         "02ab5a4ccb5cd58f264677034ead3be85c5f253e",
          [
           null,
           [
@@ -148154,7 +148246,7 @@
          ]
         ],
         "grid-lanes-intrinsic-sizing-cols-004-mix2.html": [
-         "3e62e74588743381ef11c3a3eab824243f5fdd68",
+         "e04dfcd7255f78d92459118d6d4eb38caebbdca7",
          [
           null,
           [
@@ -148167,7 +148259,7 @@
          ]
         ],
         "grid-lanes-intrinsic-sizing-cols-005.html": [
-         "987e0331990a385eccb935ee9fa824bbbc4989e0",
+         "b3ed8c0e9b7585c213bd5208e2eadef5a6684569",
          [
           null,
           [
@@ -148180,7 +148272,7 @@
          ]
         ],
         "grid-lanes-intrinsic-sizing-cols-006.html": [
-         "ede6d18fd06104b266e3b6fd2d5698991fd98d10",
+         "4f870d3a9748ab44a67f03b1a728c3da3aaac874",
          [
           null,
           [
@@ -148193,7 +148285,7 @@
          ]
         ],
         "grid-lanes-intrinsic-sizing-cols-007.html": [
-         "e2af4e321bd3e935462069ad025f6aeb5ca69f68",
+         "3f11dac7702a9fd5cabfa87a73451a9bdac23b6e",
          [
           null,
           [
@@ -148206,7 +148298,7 @@
          ]
         ],
         "grid-lanes-intrinsic-sizing-rows-001-auto.html": [
-         "12b222a7f975ee502f7e7d129ad30527421fe19f",
+         "29a289ea095621a9eaebbb9ed3245f5cedfc7f22",
          [
           null,
           [
@@ -148219,7 +148311,7 @@
          ]
         ],
         "grid-lanes-intrinsic-sizing-rows-001-fr.html": [
-         "3b90fa966883586c325a26c7f4d6d7e4af69c5ce",
+         "8cdb7d10c3becaba8a291b0fe9496a5c560944ae",
          [
           null,
           [
@@ -148232,7 +148324,7 @@
          ]
         ],
         "grid-lanes-intrinsic-sizing-rows-001-mix1.html": [
-         "4b16021f18bfc85d770d03b00d4de137867bdf38",
+         "65d9ba920d3c875baaaefee3a90ceb40594d1802",
          [
           null,
           [
@@ -148245,7 +148337,7 @@
          ]
         ],
         "grid-lanes-intrinsic-sizing-rows-001-mix2.html": [
-         "5503bc3a5cc752efc04a0474dff5e995406d4e00",
+         "2ffee33feca6dccb0655de3e691171ca1dd9604b",
          [
           null,
           [
@@ -148258,7 +148350,7 @@
          ]
         ],
         "grid-lanes-intrinsic-sizing-rows-002-auto.html": [
-         "c855577f8e2d8e6da091c4d5e65f9b96b6f877bf",
+         "c0c83a7c2a2a19c97f2adc7c4a08089462622f87",
          [
           null,
           [
@@ -148271,7 +148363,7 @@
          ]
         ],
         "grid-lanes-intrinsic-sizing-rows-002-fr.html": [
-         "1a03bc1b4e7676988314117d3a75328816c792cc",
+         "5a02304d8ebaf8490bd93f383ef2675294719342",
          [
           null,
           [
@@ -148284,7 +148376,7 @@
          ]
         ],
         "grid-lanes-intrinsic-sizing-rows-002-mix1.html": [
-         "30c9623a782247d356ecaf79b175f39078e13935",
+         "012c9a69e600e38fd48b8f7bb45cc8bed5cb4044",
          [
           null,
           [
@@ -148297,7 +148389,7 @@
          ]
         ],
         "grid-lanes-intrinsic-sizing-rows-002-mix2.html": [
-         "f637f4ebe528302a16c61d1a57fe6b5dd94639f4",
+         "10bef6818e72fc290c19ae9ba0d68725c5fa9c46",
          [
           null,
           [
@@ -148310,7 +148402,7 @@
          ]
         ],
         "grid-lanes-intrinsic-sizing-rows-003-auto.html": [
-         "105b7b3e099056c150aa79afadb11f82fc2b3347",
+         "656c1bfc8fec158afc2265d0039c94f687a8bb4a",
          [
           null,
           [
@@ -148323,7 +148415,7 @@
          ]
         ],
         "grid-lanes-intrinsic-sizing-rows-003-fr.html": [
-         "54466a235983f5ede59d344238a9574020161d70",
+         "5a9488ae62b40bbb840753835dafc026f85c14c7",
          [
           null,
           [
@@ -148336,7 +148428,7 @@
          ]
         ],
         "grid-lanes-intrinsic-sizing-rows-003-mix1.html": [
-         "ac4bb2fcbf4c015107c2b9ed3e6d0cd0ba0c92bb",
+         "d16b134f76301a29ec7c40b347a52b2cd5588d69",
          [
           null,
           [
@@ -148349,7 +148441,7 @@
          ]
         ],
         "grid-lanes-intrinsic-sizing-rows-003-mix2.html": [
-         "5a5f27901be93617a935959d9fc1ba35d41d5211",
+         "438b404856622883c2c6d730c387c8287e06b369",
          [
           null,
           [
@@ -148362,7 +148454,7 @@
          ]
         ],
         "grid-lanes-intrinsic-sizing-rows-004-auto.html": [
-         "88155ae696bc054d0570bfa5791d43810586a6bf",
+         "e8b1012803caa13196109b97a8dacdb0ccd7d8c5",
          [
           null,
           [
@@ -148375,7 +148467,7 @@
          ]
         ],
         "grid-lanes-intrinsic-sizing-rows-004-fr.html": [
-         "d44e661ae8b32d0c48b4301eaf8f515159bc663e",
+         "919342419ed59e91b161aaa489f04c91ad969ada",
          [
           null,
           [
@@ -148388,7 +148480,7 @@
          ]
         ],
         "grid-lanes-intrinsic-sizing-rows-004-mix1.html": [
-         "9014fd09a3159aef326d0fbb74fae8869a53130e",
+         "dfcf7a564a2a378080892b8aa69d2ffdd94050e3",
          [
           null,
           [
@@ -148401,7 +148493,7 @@
          ]
         ],
         "grid-lanes-intrinsic-sizing-rows-004-mix2.html": [
-         "f446a520dfd345f5601a88733f3a4ac7e7a58148",
+         "2dd257592445a10c1d082809fe593f4e089a7e51",
          [
           null,
           [
@@ -148414,7 +148506,7 @@
          ]
         ],
         "grid-lanes-intrinsic-sizing-rows-005.html": [
-         "c21bfbd2b5a291818970f70eb7b00b9cac8e11fb",
+         "e2d818eaa94def8937550753532eebbd3d52d5ba",
          [
           null,
           [
@@ -148427,7 +148519,7 @@
          ]
         ],
         "grid-lanes-intrinsic-sizing-rows-006.html": [
-         "8f66eb92466069bc1f7fcd39ecf9a3d4afe46cbb",
+         "80bb473e5216e702fbbdd7163b71dd420f74f13f",
          [
           null,
           [
@@ -148440,7 +148532,7 @@
          ]
         ],
         "grid-lanes-intrinsic-sizing-rows-007-ref.html": [
-         "74ead5816637d36de6670ac66bbe5e89c76f60aa",
+         "26e9747c4b382616cbf20ae12fe97f562e561eb6",
          [
           null,
           [
@@ -148453,7 +148545,7 @@
          ]
         ],
         "grid-lanes-intrinsic-sizing-rows-007.html": [
-         "25349ced0fe92e35f506691e60bfd57b84769651",
+         "e2cb5a68c817b4854ffed26b5fc538aa4de8b917",
          [
           null,
           [
@@ -148466,7 +148558,7 @@
          ]
         ],
         "row-defined-height.html": [
-         "e30ccc30f8db758fa73bfbcdba2fcbfc8fd8a944",
+         "587e22f429b5946b905b9815db78ae16377492ad",
          [
           null,
           [
@@ -148479,7 +148571,7 @@
          ]
         ],
         "row-intrinsic-inline-container-size.html": [
-         "ebcb9ff998f825085a9aa3b828e71e796c0ec00f",
+         "340ed1ab9e35030df34a8767185f6e58a55e79e2",
          [
           null,
           [
@@ -148494,7 +148586,7 @@
        },
        "item-placement": {
         "column-auto-placement-001.html": [
-         "b84745e6042a2fd351d940e74e29df74f6f6f19f",
+         "24a9e3e238d1f3f9b0d18ca041034b5544ee5d84",
          [
           null,
           [
@@ -148520,7 +148612,7 @@
          ]
         ],
         "column-reverse-001.html": [
-         "9b4686402176d47e9e390d521958a41bf125069b",
+         "b5ffd19540391116e643c72ab4fcf0fb14a214ed",
          [
           null,
           [
@@ -148533,7 +148625,7 @@
          ]
         ],
         "column-reverse-002.html": [
-         "41424d4cd21a127447c4a2c9eba6521a5702c798",
+         "ee49316aa446ab7ce9506662a4ba8023d8c2c11d",
          [
           null,
           [
@@ -148546,7 +148638,7 @@
          ]
         ],
         "column-reverse-003.html": [
-         "ed6a73460719d4d2cc7a0062b4cec614b3721578",
+         "26a76529c2c1b1f4e34e622fca258df7cc4613e2",
          [
           null,
           [
@@ -148558,9 +148650,87 @@
           {}
          ]
         ],
+        "column-reverse-dense-packing-001.html": [
+         "faf835c9bff4b0dc326016dfeb94fad71c486c2b",
+         [
+          null,
+          [
+           [
+            "/css/css-grid/grid-lanes/tentative/item-placement/column-reverse-dense-packing-001-ref.html",
+            "=="
+           ]
+          ],
+          {}
+         ]
+        ],
+        "column-reverse-dense-packing-multi-span-001.html": [
+         "67858c3d6248f40016760ad846b3b89fbbf891d6",
+         [
+          null,
+          [
+           [
+            "/css/css-grid/grid-lanes/tentative/item-placement/column-reverse-dense-packing-multi-span-001-ref.html",
+            "=="
+           ]
+          ],
+          {}
+         ]
+        ],
+        "column-reverse-dense-packing-multi-span-002.html": [
+         "c4788dd05df532bdc7420c66fc3e9e1d5e4e097e",
+         [
+          null,
+          [
+           [
+            "/css/css-grid/grid-lanes/tentative/item-placement/column-reverse-dense-packing-multi-span-002-ref.html",
+            "=="
+           ]
+          ],
+          {}
+         ]
+        ],
+        "column-reverse-dense-packing-multi-span-003.html": [
+         "15c7188d8fb944195d4af966f1f78da3dbb7c211",
+         [
+          null,
+          [
+           [
+            "/css/css-grid/grid-lanes/tentative/item-placement/column-reverse-dense-packing-multi-span-003-ref.html",
+            "=="
+           ]
+          ],
+          {}
+         ]
+        ],
+        "column-reverse-dense-packing-multi-span-004.html": [
+         "ee69a3c46b5cb443850e155fce1053f101d150ca",
+         [
+          null,
+          [
+           [
+            "/css/css-grid/grid-lanes/tentative/item-placement/column-reverse-dense-packing-multi-span-004-ref.html",
+            "=="
+           ]
+          ],
+          {}
+         ]
+        ],
+        "column-reverse-dense-packing-multi-span-005.html": [
+         "1e5c451bd2a20b712b0e274f6b8c43a48647d500",
+         [
+          null,
+          [
+           [
+            "/css/css-grid/grid-lanes/tentative/item-placement/column-reverse-dense-packing-multi-span-005-ref.html",
+            "=="
+           ]
+          ],
+          {}
+         ]
+        ],
         "dense-packing": {
          "column-dense-packing-001.html": [
-          "60289a465b98b4dbc9455f9d7e861f710535d5ee",
+          "f8d2d68ee2e8ffa43ce14f6f323547e74aee6eb0",
           [
            null,
            [
@@ -148625,7 +148795,7 @@
           ]
          ],
          "column-dense-packing-006.html": [
-          "2f7f58071ebe7a3a4a63ec3d51354a269d23fa6f",
+          "faf4ef69ac594a45b47c110480d306328c82d568",
           [
            null,
            [
@@ -148677,7 +148847,7 @@
           ]
          ],
          "column-dense-packing-multi-span-004.html": [
-          "556cfefbd7a9215ee73927c1850bd874b6630103",
+          "0e41c346685009fc94c6c71d34ef7997cb762302",
           [
            null,
            [
@@ -148703,7 +148873,7 @@
           ]
          ],
          "column-dense-packing-multi-span-007.html": [
-          "75fbcf27517470c0bef72023e828a96c1f6f3932",
+          "132911428333efffcbe53761a7ee12b38314472a",
           [
            null,
            [
@@ -148716,7 +148886,7 @@
           ]
          ],
          "column-dense-packing-multi-span-008.html": [
-          "f6a627fe9773a58085f70d9b36b045447eeafa15",
+          "77bd724a1aa30ccffaeed21d8deb7e30fa175898",
           [
            null,
            [
@@ -148742,7 +148912,7 @@
           ]
          ],
          "row-dense-packing-001.html": [
-          "fd563b2d87412fbdb909b0f8a31352eea76c2fff",
+          "565c4ba059366de60ea933db42930c440ece5e3a",
           [
            null,
            [
@@ -148755,7 +148925,7 @@
           ]
          ],
          "row-dense-packing-002.html": [
-          "9f5434856657e4d395b9948b7f1ee5e4e3796a35",
+          "54327df59f9d08b2b7b25754275ac4914df383d6",
           [
            null,
            [
@@ -148768,7 +148938,7 @@
           ]
          ],
          "row-dense-packing-003.html": [
-          "52f2bfbaf7dbb2fe062bedd589f7fd60afd6735e",
+          "9ca1d32d20551c1bcbab26eff9aa77157077fa18",
           [
            null,
            [
@@ -148781,7 +148951,7 @@
           ]
          ],
          "row-dense-packing-004.html": [
-          "df7fa2bed6ec2d1973d3e87cad78af58f4934382",
+          "9b758151666e0de85d5a60a98eff12042a59648f",
           [
            null,
            [
@@ -148794,7 +148964,7 @@
           ]
          ],
          "row-dense-packing-multi-span-001.html": [
-          "d59df294e62091b69b7f8f983a8bc82f066f4a2e",
+          "d1c2304f6cb0f512bb496a4c92a523e7ccdcff8e",
           [
            null,
            [
@@ -148807,7 +148977,7 @@
           ]
          ],
          "row-dense-packing-multi-span-002.html": [
-          "8d6584447c72c734db66abaf29ed024eaf7f1853",
+          "48c135db3328366e30e33358fb5a4bbec9522bd2",
           [
            null,
            [
@@ -148820,7 +148990,7 @@
           ]
          ],
          "row-dense-packing-multi-span-003.html": [
-          "6bf5f11236ffc1edbc590066a99382cd78eaede4",
+          "46b9fcb9c09353d43fbcd28fb85debf019385005",
           [
            null,
            [
@@ -148833,7 +149003,7 @@
           ]
          ],
          "row-dense-packing-multi-span-004.html": [
-          "2288a1a48eed06802e2cb401d08394dd54ad80b7",
+          "cb51320ea95e404310a62cbc8d8206c3a9a63042",
           [
            null,
            [
@@ -148846,7 +149016,7 @@
           ]
          ],
          "row-dense-packing-multi-span-005.html": [
-          "de25d6cc7c1ea5abbd8d7b9a3a2faec7308ea828",
+          "5b2d6daa87823155872bded3c71cdf398f2a392a",
           [
            null,
            [
@@ -148860,7 +149030,7 @@
          ]
         },
         "grid-lanes-item-placement-001.html": [
-         "2688341bffcca6556b195e0d2f7b0654992aa024",
+         "6917c2daddc6f41511fae8a56501d0373a6b1bd8",
          [
           null,
           [
@@ -148873,7 +149043,7 @@
          ]
         ],
         "grid-lanes-item-placement-002.html": [
-         "d2062c40a69e225dff1e2c0fc917c4867d19e0a3",
+         "906a6a912b21c4c8d1215c82a193076eed3067de",
          [
           null,
           [
@@ -148886,7 +149056,7 @@
          ]
         ],
         "grid-lanes-item-placement-003.html": [
-         "44216e272a51199a5844a9bb660dc305852c1194",
+         "7edb8ac5faf2082a02192257c90a8cfdedd33b6d",
          [
           null,
           [
@@ -148899,7 +149069,7 @@
          ]
         ],
         "grid-lanes-item-placement-004.html": [
-         "220033dfe0a9e98667483b3edf914615a72c8298",
+         "5b092f3e9fa066f7eccee67df6e50b12841cb324",
          [
           null,
           [
@@ -148912,7 +149082,7 @@
          ]
         ],
         "grid-lanes-item-placement-005.html": [
-         "d676a84adacc1f450efdf5d9158ac42f9b92f13f",
+         "fdc0e95d3b23ea63fc77906ab52bcecad5d453d5",
          [
           null,
           [
@@ -148925,7 +149095,7 @@
          ]
         ],
         "grid-lanes-item-placement-006.html": [
-         "eea91e8a8a05d017c37e61f9a8deb57118a2187b",
+         "2e0cf04060dbc33c0a821adebd458269546a13e2",
          [
           null,
           [
@@ -148938,7 +149108,7 @@
          ]
         ],
         "grid-lanes-item-placement-007.html": [
-         "6f322c8b97f9835544334ba123c8c41255128e67",
+         "61b29dad402a18380b870cc29ef31b85cb471e85",
          [
           null,
           [
@@ -148951,7 +149121,7 @@
          ]
         ],
         "grid-lanes-item-placement-008.html": [
-         "1b9ca00aa4ec23a78bbd95b5127cb4309662fadb",
+         "67f346b1023462e0aaa0648a14ff70607a91cb5f",
          [
           null,
           [
@@ -148964,7 +149134,7 @@
          ]
         ],
         "grid-lanes-rows-with-grid-width-changed.html": [
-         "474da4260b6dee0d95dd0bca32fc3c5301d423c7",
+         "47242bdd5c7d3642e216ec80ca184f70dff4b0fe",
          [
           null,
           [
@@ -148978,7 +149148,7 @@
         ],
         "item-tolerance": {
          "column-initial-item-tolerance.html": [
-          "f4e469a0e045ad7435c396036ace10b06a33e49c",
+          "825f627de8f79244ad368f99729dec8a10a50f16",
           [
            null,
            [
@@ -148991,7 +149161,7 @@
           ]
          ],
          "column-item-tolerance-infinite.html": [
-          "179f2c7b3ea934ee2336872bd47d8c3d810228e2",
+          "6851b77374f97e1d475eb0f68f93bbec3aff8a1f",
           [
            null,
            [
@@ -149004,7 +149174,7 @@
           ]
          ],
          "item-tolerance-column-001.html": [
-          "a2c9b92312605c4e3cd0a902fb15dd0a3b44ec0a",
+          "46e250fd4b5e1924e29ab5bc6db315e6bfd2aef3",
           [
            null,
            [
@@ -149017,7 +149187,7 @@
           ]
          ],
          "item-tolerance-column-002.html": [
-          "cdc2e776f5dbb8e1dbb7fb7a5aafb6e0b9eae295",
+          "ce2ca2dfe91260436726295632dcc335956c03d3",
           [
            null,
            [
@@ -149030,7 +149200,7 @@
           ]
          ],
          "item-tolerance-column-003.html": [
-          "bae2135fbb2f6af55495bc6ae25d01d7ae146876",
+          "a924b1d049ddd016d319f80a8fc329d1bfd896f3",
           [
            null,
            [
@@ -149043,7 +149213,7 @@
           ]
          ],
          "item-tolerance-column-004.html": [
-          "6cea7753c540de33e27be4fc426cdd1d0a61cb49",
+          "090b5463f7d49f4895569d09598300ecef96f975",
           [
            null,
            [
@@ -149056,7 +149226,7 @@
           ]
          ],
          "item-tolerance-row-001.html": [
-          "7ac8f14319a40cb6ed928c63d303ae4a3ea57604",
+          "93a78598ef90dea3cb4481d211f8caaa7b6b2a62",
           [
            null,
            [
@@ -149069,7 +149239,7 @@
           ]
          ],
          "item-tolerance-row-002.html": [
-          "d3242fa175a383ae13afa4d1cc8173ff21b819d9",
+          "98b4cc395049ad67aa6f5b66d8396927594fa702",
           [
            null,
            [
@@ -149082,7 +149252,7 @@
           ]
          ],
          "item-tolerance-row-003.html": [
-          "be43cd1f178331753ed5278e948c8e7d79107b6e",
+          "6131f98acffbcb9289f547d0760c628041e4ae36",
           [
            null,
            [
@@ -149095,7 +149265,7 @@
           ]
          ],
          "item-tolerance-row-004.html": [
-          "7cec8209e25f4c019510a2e9b49bfc6985541470",
+          "c427c799f045b3bea1151b6351f3c8dcf2dd4e79",
           [
            null,
            [
@@ -149108,7 +149278,7 @@
           ]
          ],
          "row-initial-item-tolerance.html": [
-          "0b04dc178b2e84a71ade72f835aaea5378d5a9d2",
+          "0e1dfd42b6b4d2d1c7e860a081167a43c0099b85",
           [
            null,
            [
@@ -149121,7 +149291,7 @@
           ]
          ],
          "row-item-tolerance-infinite.html": [
-          "8c1ffd4191ad6a35f8ab68a62c74a092bbae2587",
+          "543db8dbd9ae92a1a5bc74293f01ef3e4b620370",
           [
            null,
            [
@@ -149135,7 +149305,7 @@
          ]
         },
         "row-auto-placement-001.html": [
-         "31bac519e63d23f29bce8407d63a761c1742e1ae",
+         "093e91109ad38d927f0b27bded6c5fa80af2a693",
          [
           null,
           [
@@ -149148,7 +149318,7 @@
          ]
         ],
         "row-auto-placement-002.html": [
-         "9ccf6e87104ed99552c4dedda15f957fe8427990",
+         "6423bbf7bc39b4188865973631b60512a95335b5",
          [
           null,
           [
@@ -149161,7 +149331,7 @@
          ]
         ],
         "row-auto-placement-max-content.html": [
-         "66c4aa17e78678d5e11a251748d2207a7cd759f8",
+         "cfcbeb5c4f3bf45f459388fafb381a341d6c6a9e",
          [
           null,
           [
@@ -149174,7 +149344,7 @@
          ]
         ],
         "row-auto-placement-min-content.html": [
-         "55e10c5fce2ca54046e773b72881ce02d26bb1c3",
+         "7991de06f3e95178580588b623952db19a44500b",
          [
           null,
           [
@@ -149187,7 +149357,7 @@
          ]
         ],
         "row-negative-margin-001.html": [
-         "444318ec07ba17e675be8dba243bfa429f320faf",
+         "bbad2c0c7414b5a1a10e20e1a725e4f3c553a92d",
          [
           null,
           [
@@ -149200,7 +149370,7 @@
          ]
         ],
         "row-reverse-001.html": [
-         "af54573a9a22da3bf05d4941092425faf4da4d87",
+         "24576ccfc1f474edf74c70fc3b01a08ccd93a9fb",
          [
           null,
           [
@@ -149213,7 +149383,7 @@
          ]
         ],
         "row-reverse-002.html": [
-         "e26b659df35369da36ac69ad63b3aad12f859ec8",
+         "9bdbea63626cc9576473a24442ff136141c3572d",
          [
           null,
           [
@@ -149226,7 +149396,7 @@
          ]
         ],
         "row-reverse-003.html": [
-         "cf7f104dfe40e3acc66ef77ccf9bbfdb9c776063",
+         "0cec5ed57bc95dd185542a5e9ed60657137b3c2b",
          [
           null,
           [
@@ -149237,6 +149407,84 @@
           ],
           {}
          ]
+        ],
+        "row-reverse-dense-packing-001.html": [
+         "39e369d50fb88a74d9c3cc7b828a6cca46ef976b",
+         [
+          null,
+          [
+           [
+            "/css/css-grid/grid-lanes/tentative/item-placement/row-reverse-dense-packing-001-ref.html",
+            "=="
+           ]
+          ],
+          {}
+         ]
+        ],
+        "row-reverse-dense-packing-multi-span-001.html": [
+         "34f0fa325d3d2a74eaa1f189b14ea767097eb7c1",
+         [
+          null,
+          [
+           [
+            "/css/css-grid/grid-lanes/tentative/item-placement/row-reverse-dense-packing-multi-span-001-ref.html",
+            "=="
+           ]
+          ],
+          {}
+         ]
+        ],
+        "row-reverse-dense-packing-multi-span-002.html": [
+         "204ba9e5d3d711252537b3dd454cc9dc1a5514f5",
+         [
+          null,
+          [
+           [
+            "/css/css-grid/grid-lanes/tentative/item-placement/row-reverse-dense-packing-multi-span-002-ref.html",
+            "=="
+           ]
+          ],
+          {}
+         ]
+        ],
+        "row-reverse-dense-packing-multi-span-003.html": [
+         "f1491ccbef95d518b1ebecd67c853f0eb7fb1a82",
+         [
+          null,
+          [
+           [
+            "/css/css-grid/grid-lanes/tentative/item-placement/row-reverse-dense-packing-multi-span-003-ref.html",
+            "=="
+           ]
+          ],
+          {}
+         ]
+        ],
+        "row-reverse-dense-packing-multi-span-004.html": [
+         "e9c5ce5f4d25018aad1a2b5e2146b30b2e57daf8",
+         [
+          null,
+          [
+           [
+            "/css/css-grid/grid-lanes/tentative/item-placement/row-reverse-dense-packing-multi-span-004-ref.html",
+            "=="
+           ]
+          ],
+          {}
+         ]
+        ],
+        "row-reverse-dense-packing-multi-span-005.html": [
+         "8cd3f4b4a4fa801ad76416fdd7fc18b2177ce9fe",
+         [
+          null,
+          [
+           [
+            "/css/css-grid/grid-lanes/tentative/item-placement/row-reverse-dense-packing-multi-span-005-ref.html",
+            "=="
+           ]
+          ],
+          {}
+         ]
         ]
        },
        "items": {
@@ -149254,7 +149502,7 @@
          ]
         ],
         "column-flex-spanning-items.html": [
-         "07af352634570ef164ff5c30ca2df0b0490ce770",
+         "991fcd1f76d04f583e68bc2b3c5db246b7355da3",
          [
           null,
           [
@@ -149267,7 +149515,7 @@
          ]
         ],
         "column-intrinsic-maximums.html": [
-         "e539cc1a1d04cd4eccb20655af86744c0872d61f",
+         "76c0ff5d19207c939b0648c159d18e9d0b838e3c",
          [
           null,
           [
@@ -149280,7 +149528,7 @@
          ]
         ],
         "column-intrinsic-track-sizes-min-size.html": [
-         "2c3fecdbfff99d43ee2d21eddc12efb6f4aee2d8",
+         "3ba58fad1cf9f6208a00ea3758bd9271e3d3aa96",
          [
           null,
           [
@@ -149293,7 +149541,7 @@
          ]
         ],
         "column-item-minmax-img-001.html": [
-         "0fc9fe5fbf87d6893903cc49c83afeb52be59966",
+         "bc7fa606cec832de3432cc665a7e9bfd10602919",
          [
           null,
           [
@@ -149319,7 +149567,7 @@
          ]
         ],
         "column-item-percentage-sizes-001.html": [
-         "cfdf5eeff83ca419066d47edb896ef2590f68202",
+         "9de75c7e0fbf26a2cdc9da5f9342d9c5275a6a47",
          [
           null,
           [
@@ -149332,7 +149580,7 @@
          ]
         ],
         "column-item-percentage-sizes-002.html": [
-         "b953989cd4b5397bacb95bea3bb07d4dbe5d1f97",
+         "dd182acd308331aafa4c071d28f94934376fbd70",
          [
           null,
           [
@@ -149345,7 +149593,7 @@
          ]
         ],
         "column-item-percentage-sizes-003.html": [
-         "b48088f2053dd6c3883c0661836ce9487e0f1965",
+         "ba0e3285030851515bdb0a241ba1fa95378eb5ec",
          [
           null,
           [
@@ -149358,7 +149606,7 @@
          ]
         ],
         "column-minimum-size-grid-items-001.html": [
-         "6ac1765c379a86c026795389e14331a6dbccdae1",
+         "2044841564a18c2f1d883ba2493c4da9e03d1b32",
          [
           null,
           [
@@ -149371,7 +149619,7 @@
          ]
         ],
         "row-flex-and-intrinsic-sizes.html": [
-         "5bbd4c3e6d5ad00cb89769681f254143aed7e685",
+         "ff8811beba5ed8fb86bbe35cfcf9396bca879fff",
          [
           null,
           [
@@ -149384,7 +149632,7 @@
          ]
         ],
         "row-flex-spanning-items.html": [
-         "f84e91136d37dbfa2156f53e211676222df14c52",
+         "30c6f74a5c94b50682e270adf7283fa410ff70a4",
          [
           null,
           [
@@ -149397,7 +149645,7 @@
          ]
         ],
         "row-item-minmax-img-001.html": [
-         "243f341ecef6876de91473bd79ad5b4d84cb24ee",
+         "6f4fbd39cb7b4f77b9a0be474b1b6608a9b111aa",
          [
           null,
           [
@@ -149410,7 +149658,7 @@
          ]
         ],
         "row-item-minmax-img-002.html": [
-         "0dd368cd016b11ae9eec2088d1b6099aeca68e8d",
+         "27bf4dbbcaf38972cbe30e0fe32d96b357bb588a",
          [
           null,
           [
@@ -149423,7 +149671,7 @@
          ]
         ],
         "row-item-percentage-sizes-001.html": [
-         "c98c22a5db69066f8712158a301a40ccd5890ba6",
+         "1500735d3c27e683f5b4824ffe7826040734cbf1",
          [
           null,
           [
@@ -149436,7 +149684,7 @@
          ]
         ],
         "row-item-percentage-sizes-002.html": [
-         "29fb4fa0da0babc46c462497fd176a1a423ff53b",
+         "5ddf73f297fd1a203c80c26a86908ce11a0c1ce4",
          [
           null,
           [
@@ -149449,7 +149697,7 @@
          ]
         ],
         "row-item-percentage-sizes-003.html": [
-         "7b1916f7a5a343cfdda447142e316870b1720bea",
+         "ad744f0bf0d68ba0a647b163784a7cff5563f5c8",
          [
           null,
           [
@@ -149462,7 +149710,7 @@
          ]
         ],
         "row-minimum-size-grid-items-001.html": [
-         "97bbad1139aa12bd1361b2b410a95cc0a24fa25c",
+         "231f51840d05865bad732b8b2d6fbf54ff92f56b",
          [
           null,
           [
@@ -149477,7 +149725,7 @@
        },
        "order": {
         "column-order-property-auto-placement-001.html": [
-         "1563e896d5d34e68a71aa1da14f32ba547aaabcc",
+         "b74e06217c4288e5029ec35709fdfa55217ec2ab",
          [
           null,
           [
@@ -149490,7 +149738,7 @@
          ]
         ],
         "column-order-property-auto-placement-002.html": [
-         "7dc90ee90fc9d0dbb5786d80d9060780dddb4c2d",
+         "c191d22729ef1649f3b59bfcaee8807e885ec863",
          [
           null,
           [
@@ -149503,7 +149751,7 @@
          ]
         ],
         "column-order-property-auto-placement-003.html": [
-         "cfc06a4594ebc4d7c52ff88c030accd5cdde02d5",
+         "235d664c0a34b1bed68998f84610b78b1df6be9b",
          [
           null,
           [
@@ -149516,7 +149764,7 @@
          ]
         ],
         "column-order-property-auto-placement-004.html": [
-         "0f20558a2bcb31d9995b3d281ccc5c5b837327c8",
+         "16403bac0b7c553767177c3e849758a0ec28b89c",
          [
           null,
           [
@@ -149529,7 +149777,7 @@
          ]
         ],
         "column-order-property-auto-placement-005.html": [
-         "16f470419023207c98d88e45c27618e86410b6d2",
+         "1bcbf590a8e1ed883b82f991c68efe2a15a76399",
          [
           null,
           [
@@ -149542,7 +149790,7 @@
          ]
         ],
         "grid-lanes-order-001.html": [
-         "4996bedcb9c22e2b7fda327da52c740095525bc9",
+         "298a105a6ba6a6aa44ec3e3e8d0373a286de7e24",
          [
           null,
           [
@@ -149555,7 +149803,7 @@
          ]
         ],
         "row-order-property-auto-placement-001.html": [
-         "34704884a211b21020d65a4d9e18392fcfd339c1",
+         "e6a74309172973ee1695ecef2e97d2a0c29c85f4",
          [
           null,
           [
@@ -149568,7 +149816,7 @@
          ]
         ],
         "row-order-property-auto-placement-002.html": [
-         "ac9f8154ba266a2910f0752f38faf220ef117219",
+         "2627d05782a06dff4e2ed7c754780ca572346fa5",
          [
           null,
           [
@@ -149581,7 +149829,7 @@
          ]
         ],
         "row-order-property-auto-placement-003.html": [
-         "75272e644f9175e3131eca5b4107d58d0513c780",
+         "1d6d331d8cc4e3287210317d840898a14ae6202a",
          [
           null,
           [
@@ -149594,7 +149842,7 @@
          ]
         ],
         "row-order-property-auto-placement-004.html": [
-         "fd6fbff1dc76becae4740f0858849b4d36024e60",
+         "6762cd1c2fb169a9f072fb9cbcdafb4e740ba07d",
          [
           null,
           [
@@ -149607,7 +149855,7 @@
          ]
         ],
         "row-order-property-auto-placement-005.html": [
-         "79012a994308e84d5522619a6a04f0557a2d2d05",
+         "173e68d502457c1a7cfa7c7b66ad77afa9e5ec20",
          [
           null,
           [
@@ -149621,7 +149869,7 @@
         ]
        },
        "row-empty-grid-lanes-container-001.html": [
-        "3f74262ccbed0c274ac4e44acc603227ec03a10a",
+        "76c1c247cfbe53ffa5f7eec140c3fe3f5c3bc0d7",
         [
          null,
          [
@@ -149634,7 +149882,7 @@
         ]
        ],
        "row-empty-grid-lanes-container-002.html": [
-        "808567e62087bf4a24ccaf7e189fbb0ed072cbfa",
+        "377cf006a9cb1fbcec2f12fd2e75614f2d0c7452",
         [
          null,
          [
@@ -149647,7 +149895,7 @@
         ]
        ],
        "row-min-max-content-container.html": [
-        "88dea7c741a7cb29db6ecd6835457ffa1ce03f8b",
+        "e75d799774a0127e011cb9b45aead16934e06703",
         [
          null,
          [
@@ -149660,9 +149908,364 @@
         ]
        ],
        "subgrid": {
+        "column": {
+         "grid-lanes-subgrid-001a.html": [
+          "3774c87a353389e3c38623f5605565e0f8216b93",
+          [
+           null,
+           [
+            [
+             "/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-001a-ref.html",
+             "=="
+            ]
+           ],
+           {}
+          ]
+         ],
+         "grid-lanes-subgrid-001b.html": [
+          "732486c5b68dee909f88019a79a6369c1961a311",
+          [
+           null,
+           [
+            [
+             "/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-001b-ref.html",
+             "=="
+            ]
+           ],
+           {}
+          ]
+         ],
+         "grid-lanes-subgrid-001c.html": [
+          "d52db5e98053c72ac8182a1827b3bfaf19b412d0",
+          [
+           null,
+           [
+            [
+             "/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-001c-ref.html",
+             "=="
+            ]
+           ],
+           {}
+          ]
+         ],
+         "grid-lanes-subgrid-001d.html": [
+          "0bc94a2dff4049b4fd4ac2cfdd69b6c1534fa1b6",
+          [
+           null,
+           [
+            [
+             "/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-001d-ref.html",
+             "=="
+            ]
+           ],
+           {}
+          ]
+         ],
+         "grid-lanes-subgrid-001e.html": [
+          "718b36ae1283ce8ae601010eb37c8583a0f94dd3",
+          [
+           null,
+           [
+            [
+             "/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-001e-ref.html",
+             "=="
+            ]
+           ],
+           {}
+          ]
+         ],
+         "grid-lanes-subgrid-002a.html": [
+          "b5718e942a3bf6a2447f4468c43c582865988fca",
+          [
+           null,
+           [
+            [
+             "/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-002a-ref.html",
+             "=="
+            ]
+           ],
+           {}
+          ]
+         ],
+         "grid-lanes-subgrid-002b.html": [
+          "c56f38840e4d59edd2beb15250e65be69de66a01",
+          [
+           null,
+           [
+            [
+             "/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-002b-ref.html",
+             "=="
+            ]
+           ],
+           {}
+          ]
+         ],
+         "grid-lanes-subgrid-002c.html": [
+          "5f27962e8f8771a8335c934b6978e9a0971629f0",
+          [
+           null,
+           [
+            [
+             "/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-002c-ref.html",
+             "=="
+            ]
+           ],
+           {}
+          ]
+         ],
+         "grid-lanes-subgrid-002d.html": [
+          "739e0214d2ee14343da45e82da1b802269d91100",
+          [
+           null,
+           [
+            [
+             "/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-002d-ref.html",
+             "=="
+            ]
+           ],
+           {}
+          ]
+         ],
+         "grid-lanes-subgrid-002e.html": [
+          "c85057707951db68bf6ff8de5813946d49508274",
+          [
+           null,
+           [
+            [
+             "/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-002e-ref.html",
+             "=="
+            ]
+           ],
+           {}
+          ]
+         ],
+         "grid-lanes-subgrid-002f.html": [
+          "6f612f8f2fcb73753525c8622e83902c70072f5f",
+          [
+           null,
+           [
+            [
+             "/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-002f-ref.html",
+             "=="
+            ]
+           ],
+           {}
+          ]
+         ],
+         "grid-lanes-subgrid-002g.html": [
+          "08a9d539c36956e92b8529be481bcff4a3c6b343",
+          [
+           null,
+           [
+            [
+             "/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-002g-ref.html",
+             "=="
+            ]
+           ],
+           {}
+          ]
+         ],
+         "grid-lanes-subgrid-002h.html": [
+          "e91785c34245f156412398e121ceba40e10913e9",
+          [
+           null,
+           [
+            [
+             "/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-002h-ref.html",
+             "=="
+            ]
+           ],
+           {}
+          ]
+         ],
+         "grid-lanes-subgrid-002i.html": [
+          "6731311b3d5c56522834c080413241add4b94f29",
+          [
+           null,
+           [
+            [
+             "/css/css-grid/grid-lanes/tentative/subgrid/column/grid-lanes-subgrid-002i-ref.html",
+             "=="
+            ]
+           ],
+           {}
+          ]
+         ]
+        },
+        "row": {
+         "grid-lanes-subgrid-001a.html": [
+          "c966b42778df778e53dbeea61d9553163e6c6735",
+          [
+           null,
+           [
+            [
+             "/css/css-grid/grid-lanes/tentative/subgrid/row/grid-lanes-subgrid-001a-ref.html",
+             "=="
+            ]
+           ],
+           {}
+          ]
+         ],
+         "grid-lanes-subgrid-001b.html": [
+          "46a949598be8f3a03eec086f39b6502fe488c09a",
+          [
+           null,
+           [
+            [
+             "/css/css-grid/grid-lanes/tentative/subgrid/row/grid-lanes-subgrid-001b-ref.html",
+             "=="
+            ]
+           ],
+           {}
+          ]
+         ],
+         "grid-lanes-subgrid-001c.html": [
+          "772a6db577530b36e62b63899beb57093f64d035",
+          [
+           null,
+           [
+            [
+             "/css/css-grid/grid-lanes/tentative/subgrid/row/grid-lanes-subgrid-001c-ref.html",
+             "=="
+            ]
+           ],
+           {}
+          ]
+         ],
+         "grid-lanes-subgrid-001d.html": [
+          "359410384e5d5c811afa1ba97c00f5897452f97c",
+          [
+           null,
+           [
+            [
+             "/css/css-grid/grid-lanes/tentative/subgrid/row/grid-lanes-subgrid-001d-ref.html",
+             "=="
+            ]
+           ],
+           {}
+          ]
+         ],
+         "grid-lanes-subgrid-002a.html": [
+          "d4e84c8297f9d5392464bdc695407c87145e28b8",
+          [
+           null,
+           [
+            [
+             "/css/css-grid/grid-lanes/tentative/subgrid/row/grid-lanes-subgrid-002a-ref.html",
+             "=="
+            ]
+           ],
+           {}
+          ]
+         ],
+         "grid-lanes-subgrid-002b.html": [
+          "6236ab3fe51b80aabd255c3cd110c7f70a190b21",
+          [
+           null,
+           [
+            [
+             "/css/css-grid/grid-lanes/tentative/subgrid/row/grid-lanes-subgrid-002b-ref.html",
+             "=="
+            ]
+           ],
+           {}
+          ]
+         ],
+         "grid-lanes-subgrid-002c.html": [
+          "518633e732dd19aa27d76d11bf28351383651fa4",
+          [
+           null,
+           [
+            [
+             "/css/css-grid/grid-lanes/tentative/subgrid/row/grid-lanes-subgrid-002c-ref.html",
+             "=="
+            ]
+           ],
+           {}
+          ]
+         ],
+         "grid-lanes-subgrid-002d.html": [
+          "e1896e6aced5e0a39a4e78359293969005ec5c7a",
+          [
+           null,
+           [
+            [
+             "/css/css-grid/grid-lanes/tentative/subgrid/row/grid-lanes-subgrid-002d-ref.html",
+             "=="
+            ]
+           ],
+           {}
+          ]
+         ],
+         "grid-lanes-subgrid-002e.html": [
+          "ddcdea246dd3453de1656d634a04624767cdd939",
+          [
+           null,
+           [
+            [
+             "/css/css-grid/grid-lanes/tentative/subgrid/row/grid-lanes-subgrid-002e-ref.html",
+             "=="
+            ]
+           ],
+           {}
+          ]
+         ],
+         "grid-lanes-subgrid-002f.html": [
+          "0d0cc47ca224ad6acef7b20a40d5dc12045ab393",
+          [
+           null,
+           [
+            [
+             "/css/css-grid/grid-lanes/tentative/subgrid/row/grid-lanes-subgrid-002f-ref.html",
+             "=="
+            ]
+           ],
+           {}
+          ]
+         ],
+         "grid-lanes-subgrid-002g.html": [
+          "3b8f9aeb7973b94390c9c200389a59df4c1c87bb",
+          [
+           null,
+           [
+            [
+             "/css/css-grid/grid-lanes/tentative/subgrid/row/grid-lanes-subgrid-002g-ref.html",
+             "=="
+            ]
+           ],
+           {}
+          ]
+         ],
+         "grid-lanes-subgrid-002h.html": [
+          "038655a937a8b80503487c23005ccce0f5481ce1",
+          [
+           null,
+           [
+            [
+             "/css/css-grid/grid-lanes/tentative/subgrid/row/grid-lanes-subgrid-002h-ref.html",
+             "=="
+            ]
+           ],
+           {}
+          ]
+         ],
+         "grid-lanes-subgrid-002i.html": [
+          "8aa3f2ea1793cfe62595c719cf081e73d6c39f7a",
+          [
+           null,
+           [
+            [
+             "/css/css-grid/grid-lanes/tentative/subgrid/row/grid-lanes-subgrid-002i-ref.html",
+             "=="
+            ]
+           ],
+           {}
+          ]
+         ]
+        },
         "track-sizing": {
          "grid-lanes-subgrid-flex.html": [
-          "1ef819607a148526f6301f3d03472c316fa689eb",
+          "915c496966cc4bfa6d94acc3acd0092d4bfcc2c0",
           [
            null,
            [
@@ -149675,7 +150278,7 @@
           ]
          ],
          "grid-lanes-subgrid-intrinsic-sizing.html": [
-          "6f5a06f297d68851277f81f58e23ee156fa0a1fc",
+          "8a810612798269357882d5271f3d54841502451e",
           [
            null,
            [
@@ -149688,7 +150291,7 @@
           ]
          ],
          "grid-lanes-subgrid.html": [
-          "2faac717f2a72942a370ac790601c3d4776dad09",
+          "ba06f595a09c53a11f2f6926fddaa39ef57e9c79",
           [
            null,
            [
@@ -150473,7 +151076,7 @@
            ]
           ],
           "row-auto-repeat-auto-001.html": [
-           "f3fce65059d1a293fbba74f32813d403ac559181",
+           "4dcd96389b0eacf5290fa1191574fc7764989dc3",
            [
             null,
             [
@@ -150486,7 +151089,7 @@
            ]
           ],
           "row-auto-repeat-auto-002.html": [
-           "d843b6e1145854b74c9719f841c7ab27b5e296ba",
+           "f2e92b79904c812db11b88370075fb73827d2647",
            [
             null,
             [
@@ -150499,7 +151102,7 @@
            ]
           ],
           "row-auto-repeat-auto-003.html": [
-           "6c7a57adf05356c48854f13ca976b57aeb1ba910",
+           "1f7a6137a56ef606b09e5eb21fb1f00536aa5f9a",
            [
             null,
             [
@@ -150512,7 +151115,7 @@
            ]
           ],
           "row-auto-repeat-auto-004.html": [
-           "5b1ef9aa5d5a5f0b5439f77b03d038e0868e372c",
+           "377dc156ba76afaa11803d54e9d6909c390677e0",
            [
             null,
             [
@@ -150525,7 +151128,7 @@
            ]
           ],
           "row-auto-repeat-auto-005.html": [
-           "f7a90e1e91c636c3c2f2006353eb8e13a44a1af3",
+           "608f3bbf66757ca26dd38b92bc045e0313f14d90",
            [
             null,
             [
@@ -150538,7 +151141,7 @@
            ]
           ],
           "row-auto-repeat-auto-006.html": [
-           "c1fc07672c2bea71de31cfb4a719913fc1008451",
+           "649ec7ce6adaf136a3c586dabf32894ac5ca707d",
            [
             null,
             [
@@ -150551,7 +151154,7 @@
            ]
           ],
           "row-auto-repeat-auto-007.html": [
-           "673f460c6bdfcac1790b6499f79951e6b45f719e",
+           "d9f49e9efd0f06c718022047e79e98ef68d08aeb",
            [
             null,
             [
@@ -150564,7 +151167,7 @@
            ]
           ],
           "row-auto-repeat-auto-008.html": [
-           "8019bf9932d2ae78f9f3c9ee3b6da818e5da5632",
+           "85d8d6b8bc6cd36b68757a9737c9d532e1521192",
            [
             null,
             [
@@ -150577,7 +151180,7 @@
            ]
           ],
           "row-auto-repeat-auto-011.html": [
-           "571298b891ab85ed0aa1aac5fce0af4d849fcf82",
+           "332cd1f628bc734f875f70d240c0b96b53b6c552",
            [
             null,
             [
@@ -150590,7 +151193,7 @@
            ]
           ],
           "row-auto-repeat-auto-012.html": [
-           "baf27c54c3f3ba055449fd8d9f867106a0957fd8",
+           "c9974c4e635377e25410ac5574ac743f906339d4",
            [
             null,
             [
@@ -150603,7 +151206,7 @@
            ]
           ],
           "row-auto-repeat-auto-013.html": [
-           "b534d8962304dfcd8bca5a30426ba2c6cdea5cde",
+           "dec36c9ca8488412e67d4a1de763a96e1f1b8463",
            [
             null,
             [
@@ -150616,7 +151219,7 @@
            ]
           ],
           "row-auto-repeat-auto-014.html": [
-           "eb6bff120e6b35bda9de56092820756ba1b74e6d",
+           "6ddec3fbd58b2305cec8cc1d0bca7f34e48d38fb",
            [
             null,
             [
@@ -150629,7 +151232,7 @@
            ]
           ],
           "row-auto-repeat-auto-017.html": [
-           "abb02a51ba96ddcfb371ac03d2b92253cb9a9e76",
+           "ac06e877e01175a4fd03ea0c77976d497fef1884",
            [
             null,
             [
@@ -150642,7 +151245,7 @@
            ]
           ],
           "row-auto-repeat-auto-018.html": [
-           "73d3453c9cc5dcffc0ab72099af53891815b3a15",
+           "2f6d8efb33e6e00a6a4b9565a061aad4e29fa359",
            [
             null,
             [
@@ -150655,7 +151258,7 @@
            ]
           ],
           "row-auto-repeat-auto-019.html": [
-           "81eb47e080e43a9c58fa575e97e52959d31a9367",
+           "566eff97f8642540030abdd59ba53bd1dcb19e53",
            [
             null,
             [
@@ -150668,7 +151271,7 @@
            ]
           ],
           "row-auto-repeat-auto-022.html": [
-           "ca5ea4185c0504f28951a603d95daeeb53d4631a",
+           "3a403d149fad2593875c5ef8043199ef0f460919",
            [
             null,
             [
@@ -150681,7 +151284,7 @@
            ]
           ],
           "row-auto-repeat-auto-023.html": [
-           "635f8f13a1f1f2973b1e2fb031cb5e78df26a1df",
+           "688d3cc14364fab78407de4ed9c918e22c845572",
            [
             null,
             [
@@ -150694,7 +151297,7 @@
            ]
           ],
           "row-auto-repeat-auto-024.html": [
-           "017a8f5899adb414c075d29a4c3eb1e356b5e109",
+           "f19dfe0e0e9b4054034dc3f1146b90fba7856336",
            [
             null,
             [
@@ -150707,7 +151310,7 @@
            ]
           ],
           "row-auto-repeat-auto-025.html": [
-           "1362fd98bfb864b52e70cede57ae18158b53b3d7",
+           "ccf81378bb720af8dc316f0be47d683fabf17002",
            [
             null,
             [
@@ -150720,7 +151323,7 @@
            ]
           ],
           "row-auto-repeat-auto-026.html": [
-           "f3fdf43c789e0fefd1a005c6e18cd508302c7772",
+           "071aaae944e341f2571ed555ef6d4b8911b3f65a",
            [
             null,
             [
@@ -150733,7 +151336,7 @@
            ]
           ],
           "row-auto-repeat-auto-027.html": [
-           "3dadbe6d8a1c25be1b877e361031e4d3318ad70b",
+           "881d3c5e43cf79b77b21709f70452e3d1f757045",
            [
             null,
             [
@@ -150746,7 +151349,7 @@
            ]
           ],
           "row-auto-repeat-auto-028.html": [
-           "9dc814771fc5690d5b2daf8c899d5126ffa5f36c",
+           "4a7577bd3a08cfeb97e766afd426b68029f10402",
            [
             null,
             [
@@ -150759,7 +151362,7 @@
            ]
           ],
           "row-auto-repeat-fit-content-001.html": [
-           "35b7c7436fba9978a7cd9d417e6fee28385264e6",
+           "e528404d72b1327c06361759cc09fc3aae54a411",
            [
             null,
             [
@@ -150772,7 +151375,7 @@
            ]
           ],
           "row-auto-repeat-fit-content-002.html": [
-           "c762fea716ed4fb40d66bd3d8cdc3d64fc5d9562",
+           "285f8f8bc42259a47bc26e4d3bbf2aeb69eace44",
            [
             null,
             [
@@ -150785,7 +151388,7 @@
            ]
           ],
           "row-auto-repeat-fit-content-003.html": [
-           "adf360709bb022a41a12a79607edc54b3223ab03",
+           "dd21840a6f8ab9642015f7ed24001b1f9f52321f",
            [
             null,
             [
@@ -150798,7 +151401,7 @@
            ]
           ],
           "row-auto-repeat-fit-content-004.html": [
-           "961a43ff1ee3b88c3c1e72a89f2fb9bd8c145dce",
+           "66b30eb8f1152099d1e655e29385d214425b2b55",
            [
             null,
             [
@@ -150811,7 +151414,7 @@
            ]
           ],
           "row-auto-repeat-fit-content-005.html": [
-           "2b733ba5540aeab1bf4c556e476e5b0296fab16d",
+           "a83e2955e1870895bcd6adcb5705bc6983a6aa32",
            [
             null,
             [
@@ -150824,7 +151427,7 @@
            ]
           ],
           "row-auto-repeat-max-content-001.html": [
-           "225a72a366b705445614d704d4978c200e10e864",
+           "c988ad73263d2b39802ca664c55d98910ed43274",
            [
             null,
             [
@@ -150837,7 +151440,7 @@
            ]
           ],
           "row-auto-repeat-max-content-002.html": [
-           "2829fd3ad018c88f93bfdef91b1c8319bf581e28",
+           "25a247ef0729b7b7c458bb33640ab803c1c6462d",
            [
             null,
             [
@@ -150850,7 +151453,7 @@
            ]
           ],
           "row-auto-repeat-max-content-003.html": [
-           "f3338d0c4baf4713feee5fd4bd95e00550090bea",
+           "b02114cccfae879461dcf859659e0868c4b70217",
            [
             null,
             [
@@ -150863,7 +151466,7 @@
            ]
           ],
           "row-auto-repeat-max-content-004.html": [
-           "66e6ffcb08fcadda1c6723db0fa53faa3d2a7ccb",
+           "0506fb1e31241a89f6d03a334ba1b137cde96b82",
            [
             null,
             [
@@ -150876,7 +151479,7 @@
            ]
           ],
           "row-auto-repeat-max-content-005.html": [
-           "b6fc3556404a86d13d1000e665aa5b313321b6b9",
+           "8067460e738555df75b48861abc169991d22f103",
            [
             null,
             [
@@ -150889,7 +151492,7 @@
            ]
           ],
           "row-auto-repeat-min-content-001.html": [
-           "340c6787ec5b5596ed2ae90d05891fafe337f74d",
+           "699fed5c4afa1b653ea1ce8a23584ff6a017c368",
            [
             null,
             [
@@ -150902,7 +151505,7 @@
            ]
           ],
           "row-auto-repeat-min-content-002.html": [
-           "2ecea3fcb7e4ad126066f423420b61e21a97dc49",
+           "65c40c119088828b206ed64ea2ea674f25a2ca22",
            [
             null,
             [
@@ -150915,7 +151518,7 @@
            ]
           ],
           "row-auto-repeat-min-content-003.html": [
-           "88e017c15fd07c115c51a7d3114cab35401e3eef",
+           "658d72d76c65aad5d3ccf3e5a73396021ab568aa",
            [
             null,
             [
@@ -150928,7 +151531,7 @@
            ]
           ],
           "row-auto-repeat-min-content-004.html": [
-           "381a4bdaced711a6e9a5b8e58163da28fc8a0579",
+           "746561f721d37ecc2f445af79eae2ffb2608aa35",
            [
             null,
             [
@@ -150941,7 +151544,7 @@
            ]
           ],
           "row-auto-repeat-min-content-005.html": [
-           "aeee13a18c2494373a22ec7db234e00647576663",
+           "29f524e08a01014c5ea1e7d1631df030c507a0bd",
            [
             null,
             [
@@ -150954,7 +151557,7 @@
            ]
           ],
           "row-auto-repeat-mixed-intrinsic-001.html": [
-           "06baea4b16d6e4d75867dd311018a1be11805378",
+           "897962f4b17846bfbfe0a2f6d4e6924ef32c0399",
            [
             null,
             [
@@ -150967,7 +151570,7 @@
            ]
           ],
           "row-auto-repeat-mixed-intrinsic-002.html": [
-           "599b504f056e9f1b05cc4821ca7bc0d98d2e2e79",
+           "6474d55f85a293f3f18074a771714c14eab17cd1",
            [
             null,
             [
@@ -150981,7 +151584,7 @@
           ]
          },
          "row-auto-repeat-001.html": [
-          "b6463dbdbc61b73ed514b8d2f254096ca363d319",
+          "1bdbd9ace6433c0c1cd79c9bb8e527c17b96d4b3",
           [
            null,
            [
@@ -150994,7 +151597,7 @@
           ]
          ],
          "row-auto-repeat-002.html": [
-          "c6a4e3152d4e44b88d35c0c8eda920d232256f17",
+          "6660474121c0f004c3ecca97c33f3289a86a6678",
           [
            null,
            [
@@ -151007,7 +151610,7 @@
           ]
          ],
          "row-auto-repeat-003.html": [
-          "8c84d170782a1a2417a7b1349bf764705445431c",
+          "7df35faaf6aad97360f8bb3d6b088dde281ebb2b",
           [
            null,
            [
@@ -151020,7 +151623,7 @@
           ]
          ],
          "row-auto-repeat-004.html": [
-          "4fbf1f7c47a839230af90ea856d271cf664bd978",
+          "94dbf9d47b5dff48608dccb16b968fd54f81c418",
           [
            null,
            [
@@ -151033,7 +151636,7 @@
           ]
          ],
          "row-auto-repeat-005.html": [
-          "7744713e3b6ac52e49b7920d88a2d30b10fdf53e",
+          "20d28c7aeb509194cb853a46a1d95eaed648ab3f",
           [
            null,
            [
@@ -151046,7 +151649,7 @@
           ]
          ],
          "row-auto-repeat-006.html": [
-          "8cd9be9819a4fc222309f7d82059bfe92ccc78a2",
+          "91a73d244b046ea077080e4168538be1953e803a",
           [
            null,
            [
@@ -151059,7 +151662,7 @@
           ]
          ],
          "row-auto-repeat-012.html": [
-          "f7942816b4bfcf0423cf49ad9a759a47d85b69af",
+          "85e6f93e510af0e94ce25ba9b70cb8bf3755ee95",
           [
            null,
            [
@@ -151072,7 +151675,7 @@
           ]
          ],
          "row-auto-repeat-013.html": [
-          "d5eb8262088d89a05b37b9b7d059d6e7ee680f82",
+          "383df25516cb18ea26d5e07b0192cce42f7e400d",
           [
            null,
            [
@@ -151085,7 +151688,7 @@
           ]
          ],
          "row-auto-repeat-014.html": [
-          "2bbd080882a59ff42949b183a5378bf1bab6650c",
+          "70342493e95327bb87fe33d2d765dd05a1e58c54",
           [
            null,
            [
@@ -151098,7 +151701,7 @@
           ]
          ],
          "row-auto-repeat-015.html": [
-          "dd72b3aa73f19d3b6b55e8d998e2380007fc0b57",
+          "f3a68923039d0a5c6a3483addf81c9f95297ebcb",
           [
            null,
            [
@@ -151111,7 +151714,7 @@
           ]
          ],
          "row-auto-repeat-016.html": [
-          "d58a65a60a4408f97e17f786f4d980d38c16f055",
+          "139e406c9ee64fa82209e3c8409bf00cc445179e",
           [
            null,
            [
@@ -151124,7 +151727,7 @@
           ]
          ],
          "row-auto-repeat-019.html": [
-          "995bb560627e361ca9138e85c7adea47655397c0",
+          "4f884e9880d5d038409914a0f48ab29586a7c103",
           [
            null,
            [
@@ -151137,7 +151740,7 @@
           ]
          ],
          "row-auto-repeat-020.html": [
-          "5979304a83df8fee76094d4d16b523c9a5aac589",
+          "210dd8684e836d265b84df33caf13ababc98bcd8",
           [
            null,
            [
@@ -151150,7 +151753,7 @@
           ]
          ],
          "row-auto-repeat-021.html": [
-          "cca36eb20229e3f16b1c975715de792ab2278d5e",
+          "fc9ca7aa868182e3b605a80d5697188265161a0f",
           [
            null,
            [
@@ -151163,7 +151766,7 @@
           ]
          ],
          "row-auto-repeat-024.html": [
-          "92543e8ac1e28716dda4345079d42ac56672ed98",
+          "9fe68528fab3143976a06dfb383406c866993363",
           [
            null,
            [
@@ -151176,7 +151779,7 @@
           ]
          ],
          "row-auto-repeat-025.html": [
-          "cbfbe62f46ec00742c53295cf99f190e95719802",
+          "040dd7a5c3e3571abc825ea0640f70ffb2b26b74",
           [
            null,
            [
@@ -151190,7 +151793,7 @@
          ]
         },
         "column-track-sizing-001.html": [
-         "a29daf8135ade9fbb9577fa095f18722569b02f0",
+         "146e04f91cbb1a5c6ea4a1a6e3e2e565c8fbae33",
          [
           null,
           [
@@ -151203,7 +151806,7 @@
          ]
         ],
         "column-track-sizing-002.html": [
-         "8f2d5cdbaba95ad5e88a757f738cbdc717554b60",
+         "ef03e238c20b7a96d64435bb656cbbd8b30ca445",
          [
           null,
           [
@@ -151216,7 +151819,7 @@
          ]
         ],
         "column-track-sizing-003.html": [
-         "43941e9e8416451efba9fc040b7cb2ba19ea093e",
+         "608a2c95749193d2fc749567274442b659c2c678",
          [
           null,
           [
@@ -151229,7 +151832,7 @@
          ]
         ],
         "column-track-sizing-004.html": [
-         "47b6f0cc0a7a257288cce776ef0177aabcf32477",
+         "60e4605d0498588d359bed77c4a93ad84e02e85c",
          [
           null,
           [
@@ -151242,7 +151845,7 @@
          ]
         ],
         "column-track-sizing-005.html": [
-         "5c57e95c04690bbabd93c59ff5a96ca415c8f52e",
+         "5605c0bba5e630b8135241136061590a8adf508f",
          [
           null,
           [
@@ -151255,7 +151858,7 @@
          ]
         ],
         "grid-lanes-track-sizing-check-grid-height-on-resize.html": [
-         "d11fee90d42c7ea975c15ad3cbbf37e18c51fb0b",
+         "0e1408ee93a21228c7f778fe3b9ed94b4f95937b",
          [
           null,
           [
@@ -151320,7 +151923,7 @@
          ]
         ],
         "row-track-sizing-001.html": [
-         "d8eabd139bed354d5fbd6d5fe82cb4be146e355e",
+         "522a919b2e2cb968cfa42425dc65e5be87b28942",
          [
           null,
           [
@@ -151333,7 +151936,7 @@
          ]
         ],
         "row-track-sizing-002.html": [
-         "0452c446cb2f104be9cb8c7c4cc586d8cf8af49d",
+         "46d649f598e99ac94a0f0e4641975ec1b93b1eb0",
          [
           null,
           [
@@ -151346,7 +151949,7 @@
          ]
         ],
         "row-track-sizing-003.html": [
-         "a76e67fa0eb68d8d8c988fb70ff6808c7c135399",
+         "00a74774574bcbcd1ba7fda3cadfc4bde6b2bcfb",
          [
           null,
           [
@@ -151359,7 +151962,7 @@
          ]
         ],
         "row-track-sizing-004.html": [
-         "854f462327bf1bba9e64e6aec357b0798831bb07",
+         "71231271e908aff456e5aaf3839aad4a4980bb86",
          [
           null,
           [
@@ -151372,7 +151975,7 @@
          ]
         ],
         "row-track-sizing-005.html": [
-         "c8e1f8ee9c735495b4f383682f3750f3180c0495",
+         "29034a4ed4f70eb0bbbdf93b1251cef146b1170f",
          [
           null,
           [
@@ -152543,367 +153146,6 @@
        ]
       ]
      },
-     "masonry": {
-      "tentative": {
-       "subgrid": {
-        "column": {
-         "masonry-subgrid-001a.html": [
-          "5c583fe3863e4fb2928b88e4e9457bd085ef68c4",
-          [
-           null,
-           [
-            [
-             "/css/css-grid/masonry/tentative/subgrid/column/masonry-subgrid-001a-ref.html",
-             "=="
-            ]
-           ],
-           {}
-          ]
-         ],
-         "masonry-subgrid-001b.html": [
-          "8953c581c50050f700af2fc9ddcaa294bcff2adb",
-          [
-           null,
-           [
-            [
-             "/css/css-grid/masonry/tentative/subgrid/column/masonry-subgrid-001b-ref.html",
-             "=="
-            ]
-           ],
-           {}
-          ]
-         ],
-         "masonry-subgrid-001c.html": [
-          "5a9560bc8d78d6b00f0ffa45163e12ed281a7d61",
-          [
-           null,
-           [
-            [
-             "/css/css-grid/masonry/tentative/subgrid/column/masonry-subgrid-001c-ref.html",
-             "=="
-            ]
-           ],
-           {}
-          ]
-         ],
-         "masonry-subgrid-001d.html": [
-          "8813f3961f89bc2005ce23f2a64d37372128d47a",
-          [
-           null,
-           [
-            [
-             "/css/css-grid/masonry/tentative/subgrid/column/masonry-subgrid-001d-ref.html",
-             "=="
-            ]
-           ],
-           {}
-          ]
-         ],
-         "masonry-subgrid-001e.html": [
-          "b890b047e9f83dad566b8045e482d5e0247fb9e4",
-          [
-           null,
-           [
-            [
-             "/css/css-grid/masonry/tentative/subgrid/column/masonry-subgrid-001e-ref.html",
-             "=="
-            ]
-           ],
-           {}
-          ]
-         ],
-         "masonry-subgrid-002a.html": [
-          "3b90bd6c1313f5c543db4f73027235cbc6e5bdb5",
-          [
-           null,
-           [
-            [
-             "/css/css-grid/masonry/tentative/subgrid/column/masonry-subgrid-002a-ref.html",
-             "=="
-            ]
-           ],
-           {}
-          ]
-         ],
-         "masonry-subgrid-002b.html": [
-          "4321b7eadfe10b3fc867534193d94adf45f1e930",
-          [
-           null,
-           [
-            [
-             "/css/css-grid/masonry/tentative/subgrid/column/masonry-subgrid-002b-ref.html",
-             "=="
-            ]
-           ],
-           {}
-          ]
-         ],
-         "masonry-subgrid-002c.html": [
-          "5b2394cef9a865a4738f4c290e409704fbc0baf5",
-          [
-           null,
-           [
-            [
-             "/css/css-grid/masonry/tentative/subgrid/column/masonry-subgrid-002c-ref.html",
-             "=="
-            ]
-           ],
-           {}
-          ]
-         ],
-         "masonry-subgrid-002d.html": [
-          "2b5a4c96828f7dacdbda9994295810bb57844adf",
-          [
-           null,
-           [
-            [
-             "/css/css-grid/masonry/tentative/subgrid/column/masonry-subgrid-002d-ref.html",
-             "=="
-            ]
-           ],
-           {}
-          ]
-         ],
-         "masonry-subgrid-002e.html": [
-          "4a95d193ba51fad7118c9602a1018f1fd0c65649",
-          [
-           null,
-           [
-            [
-             "/css/css-grid/masonry/tentative/subgrid/column/masonry-subgrid-002e-ref.html",
-             "=="
-            ]
-           ],
-           {}
-          ]
-         ],
-         "masonry-subgrid-002f.html": [
-          "18ae09e06347b34bade7b9b84a12c3e927f545a0",
-          [
-           null,
-           [
-            [
-             "/css/css-grid/masonry/tentative/subgrid/column/masonry-subgrid-002f-ref.html",
-             "=="
-            ]
-           ],
-           {}
-          ]
-         ],
-         "masonry-subgrid-002g.html": [
-          "85d214619282c6dc222f91b7bd1c773d3c62b1e0",
-          [
-           null,
-           [
-            [
-             "/css/css-grid/masonry/tentative/subgrid/column/masonry-subgrid-002g-ref.html",
-             "=="
-            ]
-           ],
-           {}
-          ]
-         ],
-         "masonry-subgrid-002h.html": [
-          "2c3f1036a8f5aa673b2fae7c18fb843fe5f93d4a",
-          [
-           null,
-           [
-            [
-             "/css/css-grid/masonry/tentative/subgrid/column/masonry-subgrid-002h-ref.html",
-             "=="
-            ]
-           ],
-           {}
-          ]
-         ],
-         "masonry-subgrid-002i.html": [
-          "395aa6974dd768b1975b6264a25c4f134e2cb5b6",
-          [
-           null,
-           [
-            [
-             "/css/css-grid/masonry/tentative/subgrid/column/masonry-subgrid-002i-ref.html",
-             "=="
-            ]
-           ],
-           {}
-          ]
-         ]
-        },
-        "row": {
-         "masonry-subgrid-001a.html": [
-          "11fd405702bc9eb19d8aea212363020af80789fd",
-          [
-           null,
-           [
-            [
-             "/css/css-grid/masonry/tentative/subgrid/row/masonry-subgrid-001a-ref.html",
-             "=="
-            ]
-           ],
-           {}
-          ]
-         ],
-         "masonry-subgrid-001b.html": [
-          "72713430265382788ef04484fb321443e138aa00",
-          [
-           null,
-           [
-            [
-             "/css/css-grid/masonry/tentative/subgrid/row/masonry-subgrid-001b-ref.html",
-             "=="
-            ]
-           ],
-           {}
-          ]
-         ],
-         "masonry-subgrid-001c.html": [
-          "e4f361e773d7c84ff96488338e735c59e97cb518",
-          [
-           null,
-           [
-            [
-             "/css/css-grid/masonry/tentative/subgrid/row/masonry-subgrid-001c-ref.html",
-             "=="
-            ]
-           ],
-           {}
-          ]
-         ],
-         "masonry-subgrid-001d.html": [
-          "d8b65b5811a3002ee1fac6b46c4868beb5d543fa",
-          [
-           null,
-           [
-            [
-             "/css/css-grid/masonry/tentative/subgrid/row/masonry-subgrid-001d-ref.html",
-             "=="
-            ]
-           ],
-           {}
-          ]
-         ],
-         "masonry-subgrid-002a.html": [
-          "88ea99f08e8684f78c74c5f2d19e893da415f0d3",
-          [
-           null,
-           [
-            [
-             "/css/css-grid/masonry/tentative/subgrid/row/masonry-subgrid-002a-ref.html",
-             "=="
-            ]
-           ],
-           {}
-          ]
-         ],
-         "masonry-subgrid-002b.html": [
-          "453a780028da43cefed52a54299fc18e122d151c",
-          [
-           null,
-           [
-            [
-             "/css/css-grid/masonry/tentative/subgrid/row/masonry-subgrid-002b-ref.html",
-             "=="
-            ]
-           ],
-           {}
-          ]
-         ],
-         "masonry-subgrid-002c.html": [
-          "b20f50ae090c1263293f2aca20189a8a1f49783c",
-          [
-           null,
-           [
-            [
-             "/css/css-grid/masonry/tentative/subgrid/row/masonry-subgrid-002c-ref.html",
-             "=="
-            ]
-           ],
-           {}
-          ]
-         ],
-         "masonry-subgrid-002d.html": [
-          "3458310ab98f2d7d069495b8da4553c9b628094c",
-          [
-           null,
-           [
-            [
-             "/css/css-grid/masonry/tentative/subgrid/row/masonry-subgrid-002d-ref.html",
-             "=="
-            ]
-           ],
-           {}
-          ]
-         ],
-         "masonry-subgrid-002e.html": [
-          "1c755ab08d0f5f55070637f0086ae30d7c5fe939",
-          [
-           null,
-           [
-            [
-             "/css/css-grid/masonry/tentative/subgrid/row/masonry-subgrid-002e-ref.html",
-             "=="
-            ]
-           ],
-           {}
-          ]
-         ],
-         "masonry-subgrid-002f.html": [
-          "dae0b4a35af621e42b4182d38dcb320eba16bc98",
-          [
-           null,
-           [
-            [
-             "/css/css-grid/masonry/tentative/subgrid/row/masonry-subgrid-002f-ref.html",
-             "=="
-            ]
-           ],
-           {}
-          ]
-         ],
-         "masonry-subgrid-002g.html": [
-          "bcabd4dee8035bd6a9ec060cc8859fd90f0edda0",
-          [
-           null,
-           [
-            [
-             "/css/css-grid/masonry/tentative/subgrid/row/masonry-subgrid-002g-ref.html",
-             "=="
-            ]
-           ],
-           {}
-          ]
-         ],
-         "masonry-subgrid-002h.html": [
-          "4686a44208d035574365ed2eb8a0acfa30633214",
-          [
-           null,
-           [
-            [
-             "/css/css-grid/masonry/tentative/subgrid/row/masonry-subgrid-002h-ref.html",
-             "=="
-            ]
-           ],
-           {}
-          ]
-         ],
-         "masonry-subgrid-002i.html": [
-          "117c11e317bdbce1eed9979fa1fe41afc051b150",
-          [
-           null,
-           [
-            [
-             "/css/css-grid/masonry/tentative/subgrid/row/masonry-subgrid-002i-ref.html",
-             "=="
-            ]
-           ],
-           {}
-          ]
-         ]
-        }
-       }
-      }
-     },
      "min-size-auto-overflow-clip.html": [
       "3dfd2c18f51834d476f6501cddfa179fac07b4b1",
       [
@@ -169033,7 +169275,7 @@
     "css-masking": {
      "animations": {
       "clip-path-interpolation-shape-arc-direction-agnostic-radius.html": [
-       "aa91e1828a7841c4036f6c2d15639ebd2f1bf4e4",
+       "e153e9d153c790f12486137a533359d6822e6d48",
        [
         null,
         [
@@ -169049,7 +169291,7 @@
            [
             [
              0,
-             10
+             69
             ],
             [
              0,
@@ -172600,7 +172842,7 @@
        ]
       ],
       "clip-path-shape-011.html": [
-       "fc1927591e086bf341fcd9fa87a86429e1862053",
+       "ca9543a5a685fd9d485451ebc2b274499568f7e2",
        [
         null,
         [
@@ -172616,7 +172858,7 @@
            [
             [
              0,
-             64
+             67
             ],
             [
              0,
@@ -177159,7 +177401,7 @@
       ]
      ],
      "column-height-007.html": [
-      "4e7308dbd12025ea9d0b9b2b930595a974fb8ee7",
+      "89bf740055946309cfce1e655257fe840c68bf90",
       [
        null,
        [
@@ -177392,6 +177634,71 @@
        {}
       ]
      ],
+     "column-height-025.html": [
+      "3028ae1baec9d881020954f80bd5b471a6538862",
+      [
+       null,
+       [
+        [
+         "/css/reference/ref-filled-green-100px-square.xht",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
+     "column-height-026.html": [
+      "c8f129f8b51def3aba6e3a0d0c44ab0c4785b813",
+      [
+       null,
+       [
+        [
+         "/css/reference/ref-filled-green-100px-square.xht",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
+     "column-height-027.html": [
+      "4244a692a956795ba97c426bb1cc25f2c5260799",
+      [
+       null,
+       [
+        [
+         "/css/reference/ref-filled-green-100px-square.xht",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
+     "column-height-028.html": [
+      "7e75b3a5563d630c4c06e42f34c3832ab5e95a1b",
+      [
+       null,
+       [
+        [
+         "/css/reference/ref-filled-green-100px-square.xht",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
+     "column-height-029.html": [
+      "57649c79fc624c434e1d37cfd28102ea32cb6426",
+      [
+       null,
+       [
+        [
+         "/css/reference/ref-filled-green-100px-square.xht",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
      "column-pseudo-background-color.html": [
       "f321b1076eb58fcc00add55ab9f7dacd9daa2fba",
       [
@@ -210894,7 +211201,7 @@
        ]
       ],
       "responsive-iframe-cross-origin-not-embedded-sized.sub.tentative.html": [
-       "327fdd5f2dc99deafdfaafdfdb5ca6a7a6f92463",
+       "27e0ffdb56012719e4ed0b032220c5d0363b2d41",
        [
         null,
         [
@@ -210907,7 +211214,7 @@
        ]
       ],
       "responsive-iframe-cross-origin.sub.tentative.html": [
-       "1d735a0ef81ae81ca37cab5eafd19653056fb586",
+       "61f3835136584ae7aa4c7a373ed75ba86a6fc523",
        [
         null,
         [
@@ -210933,7 +211240,7 @@
        ]
       ],
       "responsive-iframe-not-embedded-sized.tentative.html": [
-       "0d1f91c5789afd8e9af69c2f19204dc0cae60bf3",
+       "786a8488e1937ba12f7b9ec11a71bfdca6ddb2a9",
        [
         null,
         [
@@ -210946,7 +211253,7 @@
        ]
       ],
       "responsive-iframe-request-resize.tentative.html": [
-       "043c7e5622e874ed2bd604a5bb10d205c05a7162",
+       "12444a130a0e010788f52004b0cde3203e7570cc",
        [
         null,
         [
@@ -210959,7 +211266,7 @@
        ]
       ],
       "responsive-iframe.tentative.html": [
-       "229cf01844968e7aa55c165b128435ea455a40d6",
+       "215119ef200b01840fa0f8cca90de9959ee0f5b0",
        [
         null,
         [
@@ -261468,6 +261775,19 @@
        {}
       ]
      ],
+     "outline-dynamic.html": [
+      "7c422a920a2d4ae66b49b5af496ce7d19a4f12b3",
+      [
+       null,
+       [
+        [
+         "/css/reference/ref-filled-green-100px-square-only.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
      "outline-negative-offset-composited-scroll.html": [
       "4abde0e80425dffaec3a99b1d8ec94aefdb6bc94",
       [
@@ -264943,7 +265263,7 @@
       ]
      ],
      "viewport-units-scrollbars-dynamic-001.html": [
-      "a8d374620046c976c78bc9e13fba190def8e284f",
+      "6d77656d363a6b5da05c675518cd175361f62151",
       [
        null,
        [
@@ -272339,6 +272659,19 @@
         {}
        ]
       ],
+      "clip-on-target-in-callback.html": [
+       "9116616adeb42ea80c067cd9633d081f939096e9",
+       [
+        null,
+        [
+         [
+          "/css/css-view-transitions/scoped/clip-on-target-in-callback-ref.html",
+          "=="
+         ]
+        ],
+        {}
+       ]
+      ],
       "clipper-non-containing-block.html": [
        "4d3f41b36713c20193dc7bbd28946ce536d08ea3",
        [
@@ -272475,6 +272808,19 @@
         {}
        ]
       ],
+      "paint-order.html": [
+       "243758b7eb2bc52e35b36cbd21a3fbcb621aeab3",
+       [
+        null,
+        [
+         [
+          "/css/css-view-transitions/scoped/paint-order-ref.html",
+          "=="
+         ]
+        ],
+        {}
+       ]
+      ],
       "pause-rendering.html": [
        "f920129ce06a925d01582be5acf6e845338dbb22",
        [
@@ -272517,6 +272863,19 @@
         }
        ]
       ],
+      "scrolled-target-position.html": [
+       "cdf1423850c561681cd0317a97fbd4a6eeee0650",
+       [
+        null,
+        [
+         [
+          "/css/css-view-transitions/scoped/scrolled-target-position-ref.html",
+          "=="
+         ]
+        ],
+        {}
+       ]
+      ],
       "shadow-dom.html": [
        "e14f823c25ebaef51267bfc4eb5f4652658bda65",
        [
@@ -272530,6 +272889,19 @@
         {}
        ]
       ],
+      "target-in-scrolled-container.html": [
+       "b0b19d93e084fc7ba2dc7392d8e75593a17b428a",
+       [
+        null,
+        [
+         [
+          "/css/css-view-transitions/scoped/target-in-scrolled-container-ref.html",
+          "=="
+         ]
+        ],
+        {}
+       ]
+      ],
       "to-scale-zero.html": [
        "531cb089373805320b3afd8ba4da9aff62f706ee",
        [
@@ -324668,6 +325040,19 @@
         {}
        ]
       ],
+      "non-mathml-namespace-001.html": [
+       "b0b2123e106f60fd006a14b3f806fa1d981491c0",
+       [
+        null,
+        [
+         [
+          "/mathml/relations/css-styling/non-mathml-namespace-001-ref.html",
+          "=="
+         ]
+        ],
+        {}
+       ]
+      ],
       "out-of-flow": {
        "absolutely-positioned-001.html": [
         "9a7851ba239574c3892cda5827cbf4f69d495891",
@@ -336895,6 +337280,10 @@
      "5546cf2b6f2a1b9d06054a515cc0bfc851a3c998",
      []
     ],
+    "WEB_FEATURES.yml": [
+     "6f47b86ed48fd52c46e5b4b7715e9e9f42fe3b41",
+     []
+    ],
     "crypto_key_cached_slots.https.any-expected.txt": [
      "b2194e1b792ca99ebd0d730aa67ae115420806dc",
      []
@@ -337912,6 +338301,10 @@
      }
     },
     "summarizer": {
+     "WEB_FEATURES.yml": [
+      "224df817b59440311f3e374b02b6145983863e84",
+      []
+     ],
      "resources": {
       "iframe-helper.html": [
        "2d417e04f624fc2bf10132d84621ef77cc6ac3e1",
@@ -338106,6 +338499,10 @@
      }
     },
     "display-override-member": {
+     "WEB_FEATURES.yml": [
+      "17d93173881780ed3465cc8a575683a596daf04d",
+      []
+     ],
      "display-override-member-media-feature-service-worker.js": [
       "02818dc6650f78cc388872ffb47cb0d3d3e143af",
       []
@@ -340014,7 +340411,7 @@
      }
     },
     "idlharness.https.window-expected.txt": [
-     "20a16fada97db02fe16d313033cd2b8490bb296a",
+     "35989af67c2afb6a32dc6385795f06f53e58956f",
      []
     ],
     "permissions": {
@@ -349605,6 +350002,10 @@
        "53463b38f4154029f9fadf7c337119f9828f57b4",
        []
       ],
+      "WEB_FEATURES.yml": [
+       "ab94d28cbbbbae51bb74438cd64e27712a6aafff",
+       []
+      ],
       "bidi-flag-emoji-02-ref.html": [
        "f1ea3145d3a0824c96727d337d1dcf2c89070832",
        []
@@ -351078,6 +351479,10 @@
       "7f4ec4b416f6f64e720aee28ec62ea42c25396a2",
       []
      ],
+     "anchor-scroll-composited-scrolling-paint-001-ref.html": [
+      "ca0096d430587015c9c1cc06a81a7795ecc81d58",
+      []
+     ],
      "anchor-scroll-overflow-hidden-ref.html": [
       "b674998a5db1f347af82bde1f5efb4261a3732af",
       []
@@ -355185,7 +355590,7 @@
       []
      ],
      "WEB_FEATURES.yml": [
-      "275611f531ea49474d58e8c28e0b953521f0a7ab",
+      "976f4021964bb41685d6be46f7a8f26fcdcd2eae",
       []
      ],
      "a98rgb-003-ref.html": [
@@ -358675,6 +359080,10 @@
      "META.yml": [
       "9d264a62281358545e6e842edb6a56105cb7dd5f",
       []
+     ],
+     "WEB_FEATURES.yml": [
+      "021ce547b3d165dcb810cfaba9422e1e1952184a",
+      []
      ]
     },
     "css-fill-stroke": {
@@ -370326,7 +370735,7 @@
          []
         ],
         "column-justify-items-end-justify-self-start-001-ref.html": [
-         "1b21df137df41cc860192b3baa59476c595540a7",
+         "da89dc0dbf7896f9e9dfb76260af09d4deaa3075",
          []
         ],
         "column-overflow-alignment-001-ref.html": [
@@ -370334,35 +370743,35 @@
          []
         ],
         "grid-lanes-align-content-001-ref.html": [
-         "17fa23f3254e0b189eadb09df62ade745e47d3e9",
+         "756d2c95d9d1a8e55438f2095e6e4e7d0dfa22e3",
          []
         ],
         "grid-lanes-align-content-002-ref.html": [
-         "2b063694542aa37565bf6b7e61b72b247d84a98d",
+         "13e717bec28c96321f6f06413fafd7e26be3dd46",
          []
         ],
         "grid-lanes-align-content-003-ref.html": [
-         "cf6eb9e9ca2994d9c53565c6a853b91e3fd8508b",
+         "e67a54d34e522cfd26a20fa4f1aa03935550cd47",
          []
         ],
         "grid-lanes-align-content-004-ref.html": [
-         "b034b3ee41f6c9afb77688ae90f5ed336dd08be0",
+         "615494d992274ee415ae272e7c58a7d82a7a955c",
          []
         ],
         "grid-lanes-justify-content-001-ref.html": [
-         "98b408fff008a7567cfcd1b87617d20a718f4da4",
+         "c497797b457b5665dfb44fae444e536c0c6d0e28",
          []
         ],
         "grid-lanes-justify-content-002-ref.html": [
-         "3a9562f06567b19b1fa9a1fcb32c9b0b0ca5a497",
+         "dc743b5bd0bb51d6dad8cbc86e0205ae868d75d0",
          []
         ],
         "grid-lanes-justify-content-003-ref.html": [
-         "e8de6baa2fc783ac9e91adf05ef96c37502b8f3d",
+         "b5583df666d7c6beacab191e0f615737ce925b7f",
          []
         ],
         "grid-lanes-justify-content-004-ref.html": [
-         "e0284f3b274fbf3fde947642c0e52ed4f79bfdb1",
+         "8ee7b33cc9601fae46da9e8e9e390fed4b17725f",
          []
         ],
         "row-align-items-center-001-ref.html": [
@@ -370370,7 +370779,7 @@
          []
         ],
         "row-align-items-end-align-self-start-001-ref.html": [
-         "9e50d5794752b4d79bde7eebe69870414056c5cb",
+         "972795cef8f5fe662d46d333efa64d5d604bc75b",
          []
         ],
         "row-grid-lanes-align-self-001-ref.html": [
@@ -370425,42 +370834,42 @@
         ]
        },
        "column-empty-grid-lanes-container-001-ref.html": [
-        "9f5112410821910aba59b90c919b7a6eefd256a1",
+        "2873cc5479538ed2bec0878a54a0bd1f843a875c",
         []
        ],
        "column-empty-grid-lanes-container-002-ref.html": [
-        "fd63c61d7bf597a846672d949434d05fc6ba2c65",
+        "14d5819a8f91cb806c7816ac180def66f7c60577",
         []
        ],
        "fragmentation": {
         "grid-lanes-fragmentation-001-ref.html": [
-         "c9318ae0bba5ba6419bab7d36fd37720af8fb3b1",
+         "b6c705f0391297afa60b18cc7347297c51987abf",
          []
         ],
         "grid-lanes-fragmentation-002-ref.html": [
-         "ea21350f43774f937d724bbc943c6733c3c241c1",
+         "f45e8285aa2705c0915880511f53d6303165d00a",
          []
         ],
         "grid-lanes-fragmentation-003-ref.html": [
-         "9d1036dfebde2b2c45841cdfdb0cc588f8775f1f",
+         "118e60db4944dbdd2df1948cfeb44a2e04c0c9c2",
          []
         ]
        },
        "gap": {
         "column-gaps-001-ref.html": [
-         "e7dd7a3c30ece62a2ef5dfade6a00f9f87443d5c",
+         "8c7613d2dc6c5cc791bf972d3e3e0100279d3087",
          []
         ],
         "grid-lanes-gap-001-ref.html": [
-         "7157460267ae8556006a3383fbde53618d6dbcd6",
+         "1de0cf1fab271f5b61cefff2c09dadfa0a492901",
          []
         ],
         "grid-lanes-gap-002-ref.html": [
-         "7c985442fad934ef038abb1e5188395dd4fed978",
+         "6f0ff6356b0f5669348123e287f06384d7a92340",
          []
         ],
         "row-gaps-001-ref.html": [
-         "1fb9459713d23c1e87ba7e807217e09c5af75cd6",
+         "82231c93d7a5d0dc6d226f28fb442716bac23ccd",
          []
         ]
        },
@@ -370470,217 +370879,217 @@
        ],
        "grid-placement": {
         "column-explicit-placement-001-ref.html": [
-         "85106196b1699587d30e9a9568e7944f361bf269",
+         "b3726c96269c43c1f433c5976ccaea4a301d24f0",
          []
         ],
         "column-explicit-placement-002-ref.html": [
-         "28bf1c96769ac3dd66283092a3a35150536315e6",
+         "a7541db025efd44e2a6f32838fee7ccf35ad5876",
          []
         ],
         "column-explicit-placement-003-ref.html": [
-         "f2855812f4ca8e6531d01f14457a84d69668596f",
+         "eec7e26b32e1f2205cfac209a0b4688b029c84bd",
          []
         ],
         "grid-lanes-grid-placement-named-lines-001-ref.html": [
-         "2aefa719dc4373781ff7e8c5975df68d464557ea",
+         "6c5a64e049972843d7ade25bfaddc7f5f30017b7",
          []
         ],
         "grid-lanes-grid-placement-named-lines-002-ref.html": [
-         "f24dd1c97bdfef56b7bd51f61c5fa8547679623b",
+         "8bd5749ff7ec8eaeeab6c1794180c5293ef854ac",
          []
         ],
         "row-explicit-placement-001-ref.html": [
-         "f854172633ab98abedb336c21a139c4793e0e31c",
+         "907ed6a07b54a73d06bf8ec32a51dd6c204a6ec5",
          []
         ],
         "row-explicit-placement-002-ref.html": [
-         "ba3b5e9558f5eb1658a3a23840ab6f47d1f28b1d",
+         "e464b9ab978834c271055408de6b4baef31bde13",
          []
         ],
         "row-explicit-placement-003-ref.html": [
-         "9657c592d6611a0567ec893bac3aba7c456c136f",
+         "8fdb943f0ad1fa6ef2e8a613bdd36b816743ab45",
          []
         ],
         "row-explicit-placement-004-ref.html": [
-         "899080b71a3c3e80700690417c4201e44c775407",
+         "900b5a36cc340733f906004f5dd6fe64091fb855",
          []
         ],
         "row-explicit-placement-005-ref.html": [
-         "c47748e65d6853b427d570d0c94c4be15013cdef",
+         "482f8c9ab8307da3c5f3b74e9ed746c3d9bf44b4",
          []
         ],
         "row-explicit-placement-006-ref.html": [
-         "ae9e8d04d6b962a6b21c3e37d0899af4053865cc",
+         "bc5509e4d40043570531a2be6576997b19df9216",
          []
         ],
         "row-explicit-placement-007-ref.html": [
-         "1c41ec327aded10c22a648cb0b8c6ffec7f77c2b",
+         "82e2a10a7069827ec876e93814d39e4b7acea417",
          []
         ],
         "row-explicit-placement-008-ref.html": [
-         "eb4ab2365c767213f917483aa7c6e59c1dd0d0a4",
+         "5f441457bb15b4413aecdad28e3ae32792a8092f",
          []
         ]
        },
        "intrinsic-sizing": {
         "column-intrinsic-inline-container-size-ref.html": [
-         "8b41ef60a0df08a824a58d402d6544bfbdedffb1",
+         "35c2f968bab40a3201ff4cb596fda90567cf39a3",
          []
         ],
         "grid-lanes-intrinsic-sizing-cols-001-auto-ref.html": [
-         "a738cffd12f44b5dcc2593ff0018004c38f6e9d4",
+         "764da6e0d803ecc3eccbedef49a3221b3e49569a",
          []
         ],
         "grid-lanes-intrinsic-sizing-cols-001-fr-ref.html": [
-         "d0af62d37cd4b3410d1636383864654b3e44e096",
+         "6815945b2cbaf43ea87183ce705fdeb0a99658ee",
          []
         ],
         "grid-lanes-intrinsic-sizing-cols-001-mix1-ref.html": [
-         "8f1d34a03e1053f445ba177a674688783424032f",
+         "4096380b387e09c1cd625b187369b95c9012ad5b",
          []
         ],
         "grid-lanes-intrinsic-sizing-cols-001-mix2-ref.html": [
-         "af3a7823e476f5cb4695c512c3098351963075f3",
+         "dbe2d9cf626dd5ed92a26d975665d5097bae0294",
          []
         ],
         "grid-lanes-intrinsic-sizing-cols-002-auto-ref.html": [
-         "d418d1f6d8a537aabb3e0f7f04bbbb20765159dd",
+         "e30a4959b95bc471a8249111e385653114541f08",
          []
         ],
         "grid-lanes-intrinsic-sizing-cols-002-fr-ref.html": [
-         "635804d382241c867450a0b07188dc7195cb4bec",
+         "bc88ea7cab3bb61137daf379f400c50ee3c710bd",
          []
         ],
         "grid-lanes-intrinsic-sizing-cols-002-mix1-ref.html": [
-         "db1aae2e815ee07de2492314bbdb84e61f35798f",
+         "98d1b4cda9208b958e195a95f46dcbb020c4f7e4",
          []
         ],
         "grid-lanes-intrinsic-sizing-cols-002-mix2-ref.html": [
-         "cd20b14863e7b28003e278c7cf0cde170b09d794",
+         "42b73eeac8c8d0aa2265c867f673dada56753913",
          []
         ],
         "grid-lanes-intrinsic-sizing-cols-003-auto-ref.html": [
-         "c592d9760c493565e179c1f47b743e23a9134887",
+         "6cb0258f3c85a5fe61542aa1ccd2e2cded60895a",
          []
         ],
         "grid-lanes-intrinsic-sizing-cols-003-fr-ref.html": [
-         "cc4110934dd45f3b9afe473d99c35231f1565c7b",
+         "7b70d1f68f49d4ac6671b214c2238e8b88d3ddb7",
          []
         ],
         "grid-lanes-intrinsic-sizing-cols-003-mix1-ref.html": [
-         "65984d7b628d3e3b69ddb98452cde00a31e37f97",
+         "9fe5f3a4137f09530626a4ceab389284f2185508",
          []
         ],
         "grid-lanes-intrinsic-sizing-cols-003-mix2-ref.html": [
-         "25f88a9b04d82c4165d8ed3ea59ddce87470094e",
+         "33ab52b9641435a56ff625705c316140e72df24d",
          []
         ],
         "grid-lanes-intrinsic-sizing-cols-004-auto-ref.html": [
-         "44c32aeaaa85a12aecdc9c6178b41e852e6d5206",
+         "e4ccb52527522b47cbb275345d3d8bde1b4ed385",
          []
         ],
         "grid-lanes-intrinsic-sizing-cols-004-fr-ref.html": [
-         "60d208a2bf9ddf29665893250f617375514992f5",
+         "582fbd69986fdd15d4453a0387a494a4a7f7b209",
          []
         ],
         "grid-lanes-intrinsic-sizing-cols-004-mix1-ref.html": [
-         "c5fc8ddd40e7894f0f947676732b0f5651accb84",
+         "5a8ea0d064dfa9eb2f3843cfaf4c60611d74bb98",
          []
         ],
         "grid-lanes-intrinsic-sizing-cols-004-mix2-ref.html": [
-         "b874f70283f74f4fd15c57278107ab7dabae7818",
+         "5eb9ff97b277f1141e0fa854cea56c1d7cb9ca27",
          []
         ],
         "grid-lanes-intrinsic-sizing-cols-005-ref.html": [
-         "7a86244a03313baad2ee086ab0dc657fba001c84",
+         "1b89aaa84da0693198f91055daf6eac4d383832b",
          []
         ],
         "grid-lanes-intrinsic-sizing-cols-006-ref.html": [
-         "1a41f071ab431c3c150aa9c06c28dd82b3f6cd71",
+         "5b9a50d041d5aad5df5af0ca005c1e4d18105b81",
          []
         ],
         "grid-lanes-intrinsic-sizing-cols-007-ref.html": [
-         "0c4da43ef50cd20a2c520267a52d0ecb37960f3d",
+         "c06c4f878d0b6764605ac3c6df4a4440c6846e1b",
          []
         ],
         "grid-lanes-intrinsic-sizing-rows-001-auto-ref.html": [
-         "4612a8562ffde7cb663ee65ad243b18bb0e2a324",
+         "b9e17f8af116a41876e3aa2764a72cccbd895f9c",
          []
         ],
         "grid-lanes-intrinsic-sizing-rows-001-fr-ref.html": [
-         "3b3aed96b01a2e79693efc956f643220c53b125a",
+         "3bd669484a2a9b67248f82eae2dbece8cc9f4fbe",
          []
         ],
         "grid-lanes-intrinsic-sizing-rows-001-mix1-ref.html": [
-         "6858811792da1318c086fbd299ff48719302cf74",
+         "40cbfbbfcebde9d68b14cc34147aeef3dc1028a7",
          []
         ],
         "grid-lanes-intrinsic-sizing-rows-001-mix2-ref.html": [
-         "d6007a4d8836bb463d9aaaa7660de67f2ff371e0",
+         "e8dbc25d3e944b060804bfc6fa24d8b2cd5578a0",
          []
         ],
         "grid-lanes-intrinsic-sizing-rows-002-auto-ref.html": [
-         "7bfa49eae1f4560ae758d6347ea43f76020655c7",
+         "2a0bedec20d3c87767309df41e1913e7cc345955",
          []
         ],
         "grid-lanes-intrinsic-sizing-rows-002-fr-ref.html": [
-         "2a1f1df8ec603833038bd1edc600cad3f19a552d",
+         "9b2c78145d9f041ac19f96fdfd21f3fb86c59656",
          []
         ],
         "grid-lanes-intrinsic-sizing-rows-002-mix1-ref.html": [
-         "2f5ebdc5f70d64df750ec93618e188488f8a8c3c",
+         "3a6c251f8d8194e98a4263f58e128c0ee1ffacdd",
          []
         ],
         "grid-lanes-intrinsic-sizing-rows-002-mix2-ref.html": [
-         "5c5b8ef4126f6c2124bd0eb1b74bf1e389968749",
+         "cf2adc413b26b0b0732b17bab2b6677229ffafb6",
          []
         ],
         "grid-lanes-intrinsic-sizing-rows-003-auto-ref.html": [
-         "c774c3dde7bc9c77ee79c2793b945439c59b6075",
+         "82e9d9713729136eda083454c55c1920b6f6fb40",
          []
         ],
         "grid-lanes-intrinsic-sizing-rows-003-fr-ref.html": [
-         "41a032712a21a28b2710630425391f7f63d15402",
+         "626da65151a268e9f332101c455c706dba723d3d",
          []
         ],
         "grid-lanes-intrinsic-sizing-rows-003-mix1-ref.html": [
-         "04d9971b057b66aea187760f12d40b7dccb9d97a",
+         "44899576b64ecd4b24ebadc900627b41975af237",
          []
         ],
         "grid-lanes-intrinsic-sizing-rows-003-mix2-ref.html": [
-         "a5c92c240f4a3b0d0c10489e262838c00fb09752",
+         "9d9587a0fde3ce8ab03e40bccdeffda8e9bd667d",
          []
         ],
         "grid-lanes-intrinsic-sizing-rows-004-auto-ref.html": [
-         "7d8d8888d90619388ac54b00ca09fe09f63cff1b",
+         "dcac00b48f154b75d0bea6af6d64046741cf4a73",
          []
         ],
         "grid-lanes-intrinsic-sizing-rows-004-fr-ref.html": [
-         "cccb436f503976752db5c521ce2eb3b4c8455e95",
+         "2af8ff6c0194c3d0037653bfb55b8c0328cbacc0",
          []
         ],
         "grid-lanes-intrinsic-sizing-rows-004-mix1-ref.html": [
-         "efb9969a6d40be8802151b0050ee8ad195baf68e",
+         "9d28c2771797caf37db9f525bb200d94ba325182",
          []
         ],
         "grid-lanes-intrinsic-sizing-rows-004-mix2-ref.html": [
-         "4d1470896c8e828f3aa52d6e6dc701f76fbd31a0",
+         "a0961427e9eae7716678708e921ebfdf9a1ddeab",
          []
         ],
         "grid-lanes-intrinsic-sizing-rows-005-ref.html": [
-         "c8928151042561455ff459477b765637d671c5d6",
+         "cc86972338bb765c82e9fc9a3e928620d271ecac",
          []
         ],
         "grid-lanes-intrinsic-sizing-rows-006-ref.html": [
-         "70af66fdcc21b853e27d526adfbd5d547b0eb01f",
+         "e7b288053eaaa6fe5c9df6a0f16d2161ef307b22",
          []
         ],
         "row-defined-height-ref.html": [
-         "6ce0350cb7b17e625f7cfc80436cab2b741866f8",
+         "852fadbf3ea1015a159b4821a8ccbd0697910d22",
          []
         ],
         "row-intrinsic-inline-container-size-ref.html": [
-         "29c8e8f8e8d63494a0eff64b02e1f27ba0ab5391",
+         "d857bc0cd2e1adacebbc3f4f3747d71774d42c96",
          []
         ],
         "support": {
@@ -370692,7 +371101,7 @@
        },
        "item-placement": {
         "column-auto-placement-001-ref.html": [
-         "b3e25fed55e765eb8a569b893a3fe3d018c249b1",
+         "3f59285812cb98ece65c29945d2d49d9e1bce072",
          []
         ],
         "column-negative-margin-001-ref.html": [
@@ -370700,20 +371109,44 @@
          []
         ],
         "column-reverse-001-ref.html": [
-         "9e52f14d23c9541ec5921142a747cebaf5e518bd",
+         "e62f5a991edbc2770cad1560228fecef9b53f1d4",
          []
         ],
         "column-reverse-002-ref.html": [
-         "02f19bf23d22c31ccadf3c84b6bd8b8b3c4214c8",
+         "c58c9b892f54e809eca881e2c3543c9a6575a05b",
          []
         ],
         "column-reverse-003-ref.html": [
-         "c7f2b5ccfd964c3c9734edde80326ea7fe9c2136",
+         "07797ba3a4d48496546fd3e4ea0c1f2951eab12b",
+         []
+        ],
+        "column-reverse-dense-packing-001-ref.html": [
+         "b800d814b7fbbab35273b21f01de48667c013a8b",
+         []
+        ],
+        "column-reverse-dense-packing-multi-span-001-ref.html": [
+         "eeae74cc2a1f5372054d0b47ce42cad2b040cd4e",
+         []
+        ],
+        "column-reverse-dense-packing-multi-span-002-ref.html": [
+         "8c77d04fbad9474c847279ddb07fd18b5674ddd6",
+         []
+        ],
+        "column-reverse-dense-packing-multi-span-003-ref.html": [
+         "4c017b2f21e05ca95cc5eae28b506fc5215e1357",
+         []
+        ],
+        "column-reverse-dense-packing-multi-span-004-ref.html": [
+         "ec9f13b9ef4679af25f29bc1ab81d4e660ee445d",
+         []
+        ],
+        "column-reverse-dense-packing-multi-span-005-ref.html": [
+         "63f03c31dd8a1c6dda7f9ef799329ce5a5b114a7",
          []
         ],
         "dense-packing": {
          "column-dense-packing-001-ref.html": [
-          "ab16ec39bb4ec7bee46611d59a27d959c16c863b",
+          "d05d3bbf94f27acb478ecc32d239668a7323f77b",
           []
          ],
          "column-dense-packing-002-ref.html": [
@@ -370729,11 +371162,11 @@
           []
          ],
          "column-dense-packing-005-ref.html": [
-          "6e64d0ed46346ac65d7ef47d4b04c8f081db1536",
+          "f819d8800c7f247cf98b5063fbef37ece55d42c2",
           []
          ],
          "column-dense-packing-006-ref.html": [
-          "8b666fa6323b0fb116a65ec440796f420b594cc9",
+          "01a6a416d54f5cbbf50a538a61cb4504fa6ce246",
           []
          ],
          "column-dense-packing-multi-span-001-ref.html": [
@@ -370749,7 +371182,7 @@
           []
          ],
          "column-dense-packing-multi-span-004-ref.html": [
-          "e2b1de4961188fbca5852c226c64769ff87adf2a",
+          "08d724c2860eed01c1992cb94abe0596835b5472",
           []
          ],
          "column-dense-packing-multi-span-006-ref.html": [
@@ -370757,11 +371190,11 @@
           []
          ],
          "column-dense-packing-multi-span-007-ref.html": [
-          "b47becd463cf9675935191e3e9c80b0898c2575d",
+          "99d6e21d92787dfa86851b407235cb084d1632ed",
           []
          ],
          "column-dense-packing-multi-span-008-ref.html": [
-          "822b18d5a0ee1baae840cdccb8d2f02849c161a0",
+          "10e082447c9fca0437b6ff54ef144d3e0b993abc",
           []
          ],
          "column-dense-packing-multi-span-009-ref.html": [
@@ -370769,7 +371202,7 @@
           []
          ],
          "row-dense-packing-001-ref.html": [
-          "e5bb67bebcf23ed8da0ae8fafd047157e3892b0d",
+          "eab73ed8e4857063f526e1b877a90e68f96d3c47",
           []
          ],
          "row-dense-packing-002-ref.html": [
@@ -370781,7 +371214,7 @@
           []
          ],
          "row-dense-packing-004-ref.html": [
-          "f3b2bbfed4211f016d820c9093e6486c23223270",
+          "bd08b28c3eba3a3f0bd669f1ef3ec1dbb9813faa",
           []
          ],
          "row-dense-packing-multi-span-001-ref.html": [
@@ -370797,7 +371230,7 @@
           []
          ],
          "row-dense-packing-multi-span-004-ref.html": [
-          "adf3fd39b59e9cf37135166bda24100106766fda",
+          "9c19bde5759737a4a9b66e77e096e26e3cb3962b",
           []
          ],
          "row-dense-packing-multi-span-005-ref.html": [
@@ -370806,105 +371239,105 @@
          ]
         },
         "grid-lanes-item-placement-001-ref.html": [
-         "aaa81d135a92f940c6efb5d8824ac6179cc5a303",
+         "97b503d66a97b06cbfc4ff54e99bdaa0c071d3ce",
          []
         ],
         "grid-lanes-item-placement-002-ref.html": [
-         "9723a6c4f5cbd724aa3269866afc7b0803524c08",
+         "dfbda45337f5b5c907ac0d6bc76c6bc1466e1601",
          []
         ],
         "grid-lanes-item-placement-003-ref.html": [
-         "5cbd8db9ef964400865c4c0d765becfcabd1ff28",
+         "42278eaef807fbf6b2a68d9dbf01fa654af0235f",
          []
         ],
         "grid-lanes-item-placement-004-ref.html": [
-         "9658ec4e81ba6f5de8fd91936a776f5441fceef9",
+         "3b05614c981bec0256964bc0295019b6d59db0af",
          []
         ],
         "grid-lanes-item-placement-005-ref.html": [
-         "4d2ac508ad8947d44234225728cec0f37470a23c",
+         "ffe92c32914b1b53432da2d8b27665fef5cc5180",
          []
         ],
         "grid-lanes-item-placement-006-ref.html": [
-         "948958051147825a37f80817066ad0bc08561b60",
+         "43ee24abeb4813d8c262eaf26768077f76df36d2",
          []
         ],
         "grid-lanes-item-placement-007-ref.html": [
-         "8e1e3b0bdeab62804c7c70441b0f3c36f8cfebdd",
+         "b11974231ef0b2d6f6a9d74bdb5c27c7ed1fb531",
          []
         ],
         "grid-lanes-item-placement-008-ref.html": [
-         "f3503bf071131f396111956dcc0ebddf5e9bd051",
+         "7b32b1c945ccdae7dc8a88b63eb52585d49e83f2",
          []
         ],
         "grid-lanes-rows-with-grid-width-changed-ref.html": [
-         "71c081f2cac1b1f358b484834ab0c5f8df68625c",
+         "2890609db1c6bfbdb73665a9de81c2a72a25e291",
          []
         ],
         "item-tolerance": {
          "column-initial-item-tolerance-ref.html": [
-          "16ae4eb0b9e21c01332ad011b4aaf01c59332385",
+          "b580c9508523d6d37f79cda20baa903b7a934013",
           []
          ],
          "column-item-tolerance-infinite-ref.html": [
-          "5dd29504641b576c7cc4f8a6a29a843f4764f1c3",
+          "757fd290e938624f11823f63cbd1f7baff27a9bc",
           []
          ],
          "item-tolerance-column-001-ref.html": [
-          "4123d8d8948234e4261808ab31c9b37f6665cbb9",
+          "642540560d413a2f6254162114a13bc90cf2cd4a",
           []
          ],
          "item-tolerance-column-002-ref.html": [
-          "cec15f36a9495176b11dc652b5679f51941cdcb5",
+          "ca63b77a3a8dcf8b362a7f7029d030d6d532d20b",
           []
          ],
          "item-tolerance-column-003-ref.html": [
-          "2d1f2f40f0ee491c1303c779392a08f354cdb277",
+          "4bdfc5baa02b7d4da0dc1a0286d5ec7f69bf5f92",
           []
          ],
          "item-tolerance-column-004-ref.html": [
-          "8c41341bf58e46c00a2c51022172eeebc009c047",
+          "8ad1f657266949c362badb7e9f356dad70727524",
           []
          ],
          "item-tolerance-row-001-ref.html": [
-          "261f083de6bc00e53ab53ee5ade781182db02d63",
+          "2506213823d643b64f84d89a2f699ec9c85d3bb1",
           []
          ],
          "item-tolerance-row-002-ref.html": [
-          "fa29c4f2975f97f2c283bfc5b2159a66d2873b07",
+          "a0196ff59372a80f5a29e38aeaebe09d924cf498",
           []
          ],
          "item-tolerance-row-003-ref.html": [
-          "3ba4b558748e7a0dab9a975aa976f56e213b6a91",
+          "e3e4a7d07305c995f4b33e1354d9bfaa6b444660",
           []
          ],
          "item-tolerance-row-004-ref.html": [
-          "1d0389fe32abc7cb4b47690fce1d9949eb37b918",
+          "d9f367db7e89e5c283121ba3496115ecf428c6eb",
           []
          ],
          "row-initial-item-tolerance-ref.html": [
-          "d5db97a431be67308b69da2b4ece818f82458019",
+          "77db60da924e6e1b5e7f60122c1dedc0613d04db",
           []
          ],
          "row-item-tolerance-infinite-ref.html": [
-          "9ebcc82b9bffe25fffce9ab68306ec9d93e9325d",
+          "b3de5457ca7434406334fe0509d0137d0fe45a94",
           []
          ]
         },
         "row-auto-placement-001-ref.html": [
-         "fa340f453a2f342d50f70d4a50a57cc9d20dc02e",
+         "45045b9fe0c0d51ea79f08422c4b3fc3a003c26e",
          []
         ],
         "row-auto-placement-002-ref.html": [
-         "00b3b19836c55eb0040fd9d675efb75a1ec23caa",
+         "7e05bdc5b036b21a22ba2d650fc7e089ab510549",
          []
         ],
         "row-auto-placement-max-content-ref.html": [
-         "1958f237a73204324922d9e684dd843df8b80b17",
+         "87f6c21806fc2eb50319b7f16f104f3d7f075150",
          []
         ],
         "row-auto-placement-min-content-ref.html": [
-         "503d45f530b327bc4bc935fab921354211ea0cc1",
+         "220baa8a2e14295cdce01d817c2dddabafcea72b",
          []
         ],
         "row-negative-margin-001-ref.html": [
@@ -370912,15 +371345,39 @@
          []
         ],
         "row-reverse-001-ref.html": [
-         "5c218f0c8c98d2a21734b4576fcd91d362d260f5",
+         "809aa4d551b247f5bd546cd8c176eef34420a70b",
          []
         ],
         "row-reverse-002-ref.html": [
-         "ec4fac4b23efe3acbdd52f94f40592cf0b9cf307",
+         "54af7b41465a690dc6c73556c195615cbece347f",
          []
         ],
         "row-reverse-003-ref.html": [
-         "a988d829893df1c0f59f3d2d1db24b9bda7af55f",
+         "177b3977f59eea4168cdb3e17d4041703c5a79ab",
+         []
+        ],
+        "row-reverse-dense-packing-001-ref.html": [
+         "e3988d2c6d99a97d1225d58c0ed8634671cf08f5",
+         []
+        ],
+        "row-reverse-dense-packing-multi-span-001-ref.html": [
+         "c625d382fed20914ad86eeefee3b2368b3707ba4",
+         []
+        ],
+        "row-reverse-dense-packing-multi-span-002-ref.html": [
+         "2ee7a50fb40367760006bc39ff4a05ddce23a82b",
+         []
+        ],
+        "row-reverse-dense-packing-multi-span-003-ref.html": [
+         "77861b0762a0cb777438916b639aeeb038062a4e",
+         []
+        ],
+        "row-reverse-dense-packing-multi-span-004-ref.html": [
+         "0d7e3b3b66ac671b08873710082c9f1687f67443",
+         []
+        ],
+        "row-reverse-dense-packing-multi-span-005-ref.html": [
+         "b40605d4a28aca2c63716ffbda8f7cbfc1a49690",
          []
         ]
        },
@@ -370978,47 +371435,47 @@
        },
        "order": {
         "column-order-property-auto-placement-001-ref.html": [
-         "c21deea2d171f62e53c72cda9df3bd31318e7854",
+         "c2d04d7533b7f2f0f2d19f2dacaef575826d7fab",
          []
         ],
         "column-order-property-auto-placement-002-ref.html": [
-         "788ad0edf2a612792354b2ef3f6ca9bb7b49b95f",
+         "6f50fb35191355c6b1c1d2eb63589a215c3dfa57",
          []
         ],
         "column-order-property-auto-placement-003-ref.html": [
-         "77eb8c1a0621cec9d63cfb38d1a41a401c900970",
+         "90f16b253da7f7d80ad53b3f4c81c3d461c521cd",
          []
         ],
         "column-order-property-auto-placement-004-ref.html": [
-         "da9617ec89b384f2ff859dcbf8c81c72cb583bde",
+         "f89047cbacfc28e806403e37e25c95b25ccff912",
          []
         ],
         "column-order-property-auto-placement-005-ref.html": [
-         "ea2d97e443667bd53ba71f58b4671e683205de7d",
+         "21bf83944ec5d7a5e818743684d4c0a81ac28db9",
          []
         ],
         "grid-lanes-order-001-ref.html": [
-         "94a8b18d62c47ff5133fef5a4f8cfb54a92a7d49",
+         "c40fbcde5be5420e8dd41ace9a6878c3c1631010",
          []
         ],
         "row-order-property-auto-placement-001-ref.html": [
-         "426cc82486f22c2505d128b68122c5ba4d8b0a32",
+         "de5fadf7b686f71895e53aa6d87b5b76ea0b34d9",
          []
         ],
         "row-order-property-auto-placement-002-ref.html": [
-         "150bc80085f28ca99d5b8964b8e568b897bdca54",
+         "33a4e20964378935734f17d483f961d2b3adba1c",
          []
         ],
         "row-order-property-auto-placement-003-ref.html": [
-         "822b694e8b2c2aa398adfba980f49ef9b40892da",
+         "4ea813a2eec499551a8c560e3cc8351b30c537ce",
          []
         ],
         "row-order-property-auto-placement-004-ref.html": [
-         "bc4d37209b2fb3a5ab7e1bf8d1651a179fca44c1",
+         "f0a600feb9b5946cfece070674f78846755a38a9",
          []
         ],
         "row-order-property-auto-placement-005-ref.html": [
-         "608740cc30947097774ec98dd0e0b690fccf9525",
+         "7a0ca2beced766cc911e8d4d2e202bab69907645",
          []
         ]
        },
@@ -371033,17 +371490,129 @@
         []
        ],
        "subgrid": {
+        "column": {
+         "grid-lanes-subgrid-001a-ref.html": [
+          "f869c1d44c1234bff843f8473809adb580e91f9c",
+          []
+         ],
+         "grid-lanes-subgrid-001b-ref.html": [
+          "fa902b1ac597ad2adae78a1f57bc3589cf50b3dd",
+          []
+         ],
+         "grid-lanes-subgrid-001c-ref.html": [
+          "9104ea2c338eff3b4b59db721e235cdf77bfdab7",
+          []
+         ],
+         "grid-lanes-subgrid-001d-ref.html": [
+          "5e99bfbc4dcaa78f15729c078479c83d39cbb133",
+          []
+         ],
+         "grid-lanes-subgrid-001e-ref.html": [
+          "12b49feb173ffc62fe6ff1ecf8f5f4bfb41b2cb1",
+          []
+         ],
+         "grid-lanes-subgrid-002a-ref.html": [
+          "a9f9a58f9118eb94e4cbd88d2decdfb722853471",
+          []
+         ],
+         "grid-lanes-subgrid-002b-ref.html": [
+          "58d10549ff602a9b36f57da3e5eaef32a00d6675",
+          []
+         ],
+         "grid-lanes-subgrid-002c-ref.html": [
+          "d3092019645ab0303319eb58cf64bba5e5a8a073",
+          []
+         ],
+         "grid-lanes-subgrid-002d-ref.html": [
+          "f66335d6f833e224915e39f6e062f56f5adb9654",
+          []
+         ],
+         "grid-lanes-subgrid-002e-ref.html": [
+          "e24e18a18b047cd3c75c24d07a62fcbc7fca986d",
+          []
+         ],
+         "grid-lanes-subgrid-002f-ref.html": [
+          "78bd6fea216a9b8df7b3aec2ce46aac0bf8059fe",
+          []
+         ],
+         "grid-lanes-subgrid-002g-ref.html": [
+          "250165af3785db32ff28c5749351b46bdee045f5",
+          []
+         ],
+         "grid-lanes-subgrid-002h-ref.html": [
+          "16e337c7457d9d47e27c8724f2ce2febb0ab9116",
+          []
+         ],
+         "grid-lanes-subgrid-002i-ref.html": [
+          "76bb93cd9ebf4cc1b55ac75cfdebdee84ffbfe6a",
+          []
+         ]
+        },
+        "row": {
+         "grid-lanes-subgrid-001a-ref.html": [
+          "9f46e1a3585f9e46df7b56a1252e3ebb0e716844",
+          []
+         ],
+         "grid-lanes-subgrid-001b-ref.html": [
+          "3e0b5cbd27115cdb11143930f4025da099aaab8d",
+          []
+         ],
+         "grid-lanes-subgrid-001c-ref.html": [
+          "6a8ee73a82e8bd20cb018158bc93f2d3f6d39c28",
+          []
+         ],
+         "grid-lanes-subgrid-001d-ref.html": [
+          "3a4e959b91ab3b54f3021eabd33b3144f8db8b5c",
+          []
+         ],
+         "grid-lanes-subgrid-002a-ref.html": [
+          "36de5635db37e04b70faab7bc1fc00f40115cb28",
+          []
+         ],
+         "grid-lanes-subgrid-002b-ref.html": [
+          "56609cbc61b7ed686a7491b81a74e75c398b999a",
+          []
+         ],
+         "grid-lanes-subgrid-002c-ref.html": [
+          "041e313bd3ef626df38ae5a6309867c1c30c91b3",
+          []
+         ],
+         "grid-lanes-subgrid-002d-ref.html": [
+          "02afb6c7ca3094e3d2a8344056edcdea1506e561",
+          []
+         ],
+         "grid-lanes-subgrid-002e-ref.html": [
+          "5f279cbe4a0edc9eabafcf80c8268ba943301725",
+          []
+         ],
+         "grid-lanes-subgrid-002f-ref.html": [
+          "744c44bd69c200750067c3037c614b045c3b13d8",
+          []
+         ],
+         "grid-lanes-subgrid-002g-ref.html": [
+          "cdbfd22a9fa398e4dff6b69ca1983e9f48d973dd",
+          []
+         ],
+         "grid-lanes-subgrid-002h-ref.html": [
+          "86df33d06678109227be223dce9d9362e51836b2",
+          []
+         ],
+         "grid-lanes-subgrid-002i-ref.html": [
+          "030b0e6a8576ffbd528323785d7d597db140311a",
+          []
+         ]
+        },
         "track-sizing": {
          "grid-lanes-subgrid-flex-ref.html": [
-          "5e4c758a0861d82f89ab76f7ca127b98ce65c506",
+          "fa061d2fc833cd9fc50bfdabe95c2ea177da43e2",
           []
          ],
          "grid-lanes-subgrid-intrinsic-sizing-ref.html": [
-          "c1b97841d43ef19fa26e637f227ab0d4dc5d472c",
+          "4989d640dc781a5920b3a3e30161203586d7411b",
           []
          ],
          "grid-lanes-subgrid-ref.html": [
-          "b1ae12c87980715e6abb361ad20d62724f008e22",
+          "ad717a4c6b0c865df5ac62b712e2ddfcb0a8fbbf",
           []
          ]
         }
@@ -371248,7 +371817,7 @@
            []
           ],
           "row-auto-repeat-auto-017-ref.html": [
-           "ee5e7e94a677f03b6dac68ce4581e986cbc4d8af",
+           "0094f91424877070c39af1e626e4635b8ab33a39",
            []
           ],
           "row-auto-repeat-auto-022-ref.html": [
@@ -371272,7 +371841,7 @@
            []
           ],
           "row-auto-repeat-fit-content-001-ref.html": [
-           "9e85f53df8f0178e467ea1d123affc6469e54482",
+           "0aac234246680838117f767e4426cc1c14f295f4",
            []
           ],
           "row-auto-repeat-fit-content-002-ref.html": [
@@ -371288,7 +371857,7 @@
            []
           ],
           "row-auto-repeat-max-content-001-ref.html": [
-           "e4b6f4731e03fb646699147d6d8a233729f1181b",
+           "43ac88cfdd5f4477e1f6aa1fdf53df4e48eccec6",
            []
           ],
           "row-auto-repeat-max-content-005-ref.html": [
@@ -371296,7 +371865,7 @@
            []
           ],
           "row-auto-repeat-min-content-001-ref.html": [
-           "cb5e7a493abb63562de818d4137842595d54526a",
+           "64bf4b0fea7ad27ff8aa62e99409683c967e291f",
            []
           ],
           "row-auto-repeat-min-content-005-ref.html": [
@@ -371308,7 +371877,7 @@
            []
           ],
           "row-auto-repeat-mixed-intrinsic-002-ref.html": [
-           "55044da1379057224688dbb59621f46738e583b4",
+           "06c74ac51db855eea37515ebe64d73c8de54c1e4",
            []
           ]
          },
@@ -371358,27 +371927,27 @@
          ]
         },
         "column-track-sizing-001-ref.html": [
-         "945b6fb2601d5c9136c26af2d4fdceee128a05fd",
+         "be8b0a00d1bb8727eb9f4b5fef6d932d98adb32a",
          []
         ],
         "column-track-sizing-002-ref.html": [
-         "5307cac531671e614d70f626350ce8e721f20aff",
+         "a21ce4bd1c20a804168da19e5bc19b2283c31d32",
          []
         ],
         "column-track-sizing-003-ref.html": [
-         "04ada49080011f3d49aa42bfad6f3d5076e96685",
+         "74ba27eefef117b9a0f4f8d537aab17f483d1c61",
          []
         ],
         "column-track-sizing-004-ref.html": [
-         "18cfec30692673679d3f61c5c177709747d72954",
+         "462f91dab067c44142187e77d8a5abc633930740",
          []
         ],
         "column-track-sizing-005-ref.html": [
-         "616640219cb9045de19f39e702e5d91a09a6f917",
+         "1e8bcd30885181f5bcc7d62ee2232dfd4a05deec",
          []
         ],
         "grid-lanes-track-sizing-check-grid-height-on-resize-ref.html": [
-         "53447e0dad88454672e0a70fbdbaaa6abc73055e",
+         "26e55ef3516c7aaac2f5076f9da96fda0408b5a7",
          []
         ],
         "grid-lanes-track-sizing-explicit-block-ref.html": [
@@ -371398,23 +371967,23 @@
          []
         ],
         "row-track-sizing-001-ref.html": [
-         "7217cd2dbc392523e7487f9533d05c6c6a25689f",
+         "4b5e6f25641861976f584ef9acdc4069c5525e9f",
          []
         ],
         "row-track-sizing-002-ref.html": [
-         "f4dfe5a9ca9e4c92239b5dd1b9107c7f1a7b468b",
+         "77575a920c32a8a9251c7651464bcf99b07e1fa4",
          []
         ],
         "row-track-sizing-003-ref.html": [
-         "ad11acafa51d8c82631f0505a9e224fb2c1b1f88",
+         "96e1a29550134750cd73bc1d79f0f8434018f1de",
          []
         ],
         "row-track-sizing-004-ref.html": [
-         "2e00794359d957e1028093467b4ecad374a36a77",
+         "ca6e97bd114dccf737ffc1355ccf07fa482339fb",
          []
         ],
         "row-track-sizing-005-ref.html": [
-         "e9f011040cef961727a2a2e2d7b80c4d48ea7962",
+         "19ab87be53997037180864e841de0661b16d588a",
          []
         ]
        }
@@ -371504,124 +372073,6 @@
        ]
       }
      },
-     "masonry": {
-      "tentative": {
-       "subgrid": {
-        "column": {
-         "masonry-subgrid-001a-ref.html": [
-          "f0d01465b6b8ba01a4fddcfdd1eb3a0b6b7d2b43",
-          []
-         ],
-         "masonry-subgrid-001b-ref.html": [
-          "ebd9591d96f2b86ad08935c5905a456006ab5519",
-          []
-         ],
-         "masonry-subgrid-001c-ref.html": [
-          "631709e26ba129db28821cf0d6857ef280e377e5",
-          []
-         ],
-         "masonry-subgrid-001d-ref.html": [
-          "39ef30832acd339e2bae9c7558e25fd1b483641b",
-          []
-         ],
-         "masonry-subgrid-001e-ref.html": [
-          "01be5760777dd93cd57df8ecd04c30a840c9c860",
-          []
-         ],
-         "masonry-subgrid-002a-ref.html": [
-          "7de723ec062e8f4b41d1327dffc23dd5c4bc08d4",
-          []
-         ],
-         "masonry-subgrid-002b-ref.html": [
-          "5036ce85cba5ff109cb528fe1fc60d2b9af2d76d",
-          []
-         ],
-         "masonry-subgrid-002c-ref.html": [
-          "fcab5588cff42ed8fa9f15fbae648fe11f882e2c",
-          []
-         ],
-         "masonry-subgrid-002d-ref.html": [
-          "0f378c47f393b289e6a238dee2dd596287a82a04",
-          []
-         ],
-         "masonry-subgrid-002e-ref.html": [
-          "c28e3954e4c6479680f3f53986afb7dcdc05220c",
-          []
-         ],
-         "masonry-subgrid-002f-ref.html": [
-          "e766813df8b009fd27c4e07879c29017c65dc81b",
-          []
-         ],
-         "masonry-subgrid-002g-ref.html": [
-          "7e2a69efcead0fc51639dbb54047d29f02235ea2",
-          []
-         ],
-         "masonry-subgrid-002h-ref.html": [
-          "485f8b877824f4beee6011e2a510a78ad4041e79",
-          []
-         ],
-         "masonry-subgrid-002i-ref.html": [
-          "5212964000dac190df060895c758aaae365ab4dc",
-          []
-         ]
-        },
-        "row": {
-         "masonry-subgrid-001a-ref.html": [
-          "4412b099d92dde74b40b37212e373b42c21d70f1",
-          []
-         ],
-         "masonry-subgrid-001b-ref.html": [
-          "369bb83336107395d5562f24644fd364666daf6c",
-          []
-         ],
-         "masonry-subgrid-001c-ref.html": [
-          "42fab41587193e3d95921e4d52bb44500bee98ca",
-          []
-         ],
-         "masonry-subgrid-001d-ref.html": [
-          "7a5617f56992949ea8a1f004fae5c1f2f9025473",
-          []
-         ],
-         "masonry-subgrid-002a-ref.html": [
-          "1c687f04e83ee136c10a01dbd426faa7bc25b814",
-          []
-         ],
-         "masonry-subgrid-002b-ref.html": [
-          "d711bcbedb4ecb4831ba3421d74871596c7195cb",
-          []
-         ],
-         "masonry-subgrid-002c-ref.html": [
-          "81f3e6b3ca8bf0b5ed5db6de056ddd26886dc01c",
-          []
-         ],
-         "masonry-subgrid-002d-ref.html": [
-          "0631432297c1aca0c39910a4ca762f2edbbb6f65",
-          []
-         ],
-         "masonry-subgrid-002e-ref.html": [
-          "20b39f30a4ea3cbcff021f474691154ac4706e38",
-          []
-         ],
-         "masonry-subgrid-002f-ref.html": [
-          "a33a2d975198c75cebea7f7b207c3efd8e79d170",
-          []
-         ],
-         "masonry-subgrid-002g-ref.html": [
-          "b2f67cab01be948824c17caccd5fb9f1c2fcf357",
-          []
-         ],
-         "masonry-subgrid-002h-ref.html": [
-          "09052dbfbf67a0f6980c3c11bc00af334b98baf1",
-          []
-         ],
-         "masonry-subgrid-002i-ref.html": [
-          "bcb8b95a9842ad1cadeee9bc6f4aa1512682949c",
-          []
-         ]
-        }
-       }
-      }
-     },
      "min-size-auto-overflow-clip-ref.html": [
       "64e6ff2d9994a74bea6fbf8be74de93239075006",
       []
@@ -373270,7 +373721,7 @@
      ],
      "parsing": {
       "WEB_FEATURES.yml": [
-       "d60ed3efd4ce9e1c4703439b0976b3e625e3bfab",
+       "fcbbc9f36c75dc2b2dfba2913f35fb7a025c707a",
        []
       ],
       "image-rendering-computed-expected.txt": [
@@ -374887,10 +375338,6 @@
        "a32d01c1d222eca1ba1fabb22d10a63cccd79096",
        []
       ],
-      "counter-reset-valid-expected.txt": [
-       "610350b8434b679cc1541e19fa191ce151bc5129",
-       []
-      ],
       "list-style-computed.sub-expected.txt": [
        "f3b7a8149af957f54f8e5352dc774bbd922b183c",
        []
@@ -375006,7 +375453,7 @@
      ],
      "animations": {
       "WEB_FEATURES.yml": [
-       "758ef35275eeacc96b6e584f672f86c4b170e0e1",
+       "12d684e80a708641d3874a53378a67719c5ad44f",
        []
       ],
       "clip-path-interpolation-shape-arc-direction-agnostic-radius-ref.html": [
@@ -375043,6 +375490,10 @@
       ]
      },
      "clip": {
+      "WEB_FEATURES.yml": [
+       "805859de22dc25c888ad8509425e56851ba2e78f",
+       []
+      ],
       "clip-filter-order-ref.html": [
        "fe9511a8dfce2b4f597597871abccd03727f136f",
        []
@@ -379811,7 +380262,7 @@
       []
      ],
      "WEB_FEATURES.yml": [
-      "9d9e69aa1e38efde0e1fb0affb8138537ed4004f",
+      "8e97d0dfa76288cbb9133063e6835aad88eff8e5",
       []
      ],
      "absolute-pos-box-inside-fixed-pos-box-with-changing-height-ref.html": [
@@ -379888,6 +380339,12 @@
        []
       ]
      },
+     "parsing": {
+      "WEB_FEATURES.yml": [
+       "09651aeaa90b013d6a3a07f561c40e74b259b3b8",
+       []
+      ]
+     },
      "position-absolute-iframe-print-ref.html": [
       "d89bb93cf9fb14caefee7a4bf27a15d4cc4f7615",
       []
@@ -381336,6 +381793,10 @@
       "a5823767d7a622ee1ffd2c57b6bf7dc9057580ad",
       []
      ],
+     "WEB_FEATURES.yml": [
+      "9f04b44dfcceaf593fc165dff58d3893798c320c",
+      []
+     ],
      "computedstyle": {
       "block-level-replaced-elements-affected-by-block-step-size-expected.txt": [
        "19375a7c7d59e39e8135b367e5b5bf7b0dac5e3c",
@@ -382342,6 +382803,10 @@
       "6cce42664a64778f548edb53ec86ae61531812f0",
       []
      ],
+     "WEB_FEATURES.yml": [
+      "e9fb6df46fa691b302664c9b92051c83cdb656f0",
+      []
+     ],
      "animation-part-ref.html": [
       "c0c8ff040c9d70abb569f058842c99902ff9e2bd",
       []
@@ -383722,6 +384187,10 @@
         "637519224c6f174d40db7e95399dd57c3954d380",
         []
        ],
+       "iframe-contents-slow.html": [
+        "d89fa2c61fec4bce08cd494209e7526c7b6bc981",
+        []
+       ],
        "iframe-contents-unsized.html": [
         "db1931a27d5a7ff84d30edf370688d0c33b139b9",
         []
@@ -383757,6 +384226,10 @@
       []
      ],
      "stretch": {
+      "WEB_FEATURES.yml": [
+       "886c3f278c31182880c5e06dd18daa7cd34e6fba",
+       []
+      ],
       "block-height-003-ref.html": [
        "76e2bc35cb78208bfa193d6b3d7a6d5ce501a73c",
        []
@@ -383886,6 +384359,10 @@
       "067e03cfbc7376daf0e1b5eba0dc1c35e21fe69f",
       []
      ],
+     "WEB_FEATURES.yml": [
+      "47e1c3f94c59058590df82629d18ed5fa1796dd5",
+      []
+     ],
      "reference": {
       "ref-green-on-green.xht": [
        "ce7a0494752ccab6d4746818f0b34f65fea6d170",
@@ -384527,6 +385004,10 @@
       []
      ],
      "animations": {
+      "WEB_FEATURES.yml": [
+       "c320f09cdf58c8de915e84b039588434da2f7839",
+       []
+      ],
       "hyphen-limit-chars-interpolation-expected.txt": [
        "c9b7a6a3abf65c6ce391dacdc3bb7add1d623340",
        []
@@ -386368,7 +386849,7 @@
      },
      "parsing": {
       "WEB_FEATURES.yml": [
-       "8335ec599f23a08977b5f0807d92992978167f6d",
+       "e8ff8f52e0204a1a241b6b0082f7e3cd44582c2b",
        []
       ],
       "hanging-punctuation-valid-expected.txt": [
@@ -386699,6 +387180,10 @@
       ]
      },
      "tab-size": {
+      "WEB_FEATURES.yml": [
+       "c1210929e40c6658edde2ec9d9343363f75d1ace",
+       []
+      ],
       "tab-min-rendered-width-1-ref.html": [
        "e154e0ca6265c9c1a30119448d69d241c5257f42",
        []
@@ -388739,6 +389224,10 @@
       }
      },
      "word-spacing": {
+      "WEB_FEATURES.yml": [
+       "6e28ca703ee2c66a186377cbe0c0d895edc64832",
+       []
+      ],
       "reference": {
        "ref-filled-green-100px-square.xht": [
         "05a13794482a94f6c10bd9d4c98704e63ef60331",
@@ -390040,6 +390529,10 @@
       "transform-valid-expected.txt": [
        "59c6ba4d66db35429b7e99f9ab774f36ee693838",
        []
+      ],
+      "webkit-perspective-invalid.tentative-expected.txt": [
+       "3184da1c2e00b5bacf83d92ec404754aaacdf40f",
+       []
       ]
      },
      "patternTransform": {
@@ -391277,7 +391770,7 @@
       []
      ],
      "WEB_FEATURES.yml": [
-      "472d6df1be73e9d36854f1cd813cc74c84fba23f",
+      "e50ab9915dea234b6e562219d4a03501e2d0e1cc",
       []
      ],
      "custom-property-and-allow-discrete-expected.txt": [
@@ -391652,6 +392145,10 @@
        ]
       },
       "properties": {
+       "WEB_FEATURES.yml": [
+        "fcba35b5f46fc65aaf7a32f9a435f02f72c4905a",
+        []
+       ],
        "accent-color-expected.txt": [
         "f147be866d76b87d9209252a009357a123c9d4eb",
         []
@@ -391999,7 +392496,7 @@
       []
      ],
      "WEB_FEATURES.yml": [
-      "d40280de98587dcbe078b62c2c732ec196b3b38e",
+      "48f2608f4b6de2914a2b0d0adb32666e25496f8d",
       []
      ],
      "accent-color-checkbox-checked-001-notref.html": [
@@ -392024,7 +392521,7 @@
      ],
      "animation": {
       "WEB_FEATURES.yml": [
-       "07cdf85b209f6e5a7e58837bc2df657eecb081b1",
+       "3a82d31a0ee9727d47669ee95306e38bba4f2b82",
        []
       ]
      },
@@ -392278,7 +392775,7 @@
      ],
      "parsing": {
       "WEB_FEATURES.yml": [
-       "3be770b3a51dcebaca35d4a92e1f0b4a2e8004b9",
+       "a01e785d90c0f58179f22be0dbc5408b0d38c8b4",
        []
       ],
       "canonical-order-outline-sub-properties-001-expected.txt": [
@@ -393525,7 +394022,7 @@
       []
      ],
      "WEB_FEATURES.yml": [
-      "d2964c6c3854071791e103a90cb89b3f922afa3f",
+      "92b34ec1d60a57a033eec016f54cbe705c98e74c",
       []
      ],
      "attr-all-types-expected.txt": [
@@ -393741,15 +394238,11 @@
       ]
      },
      "random-computed.tentative-expected.txt": [
-      "475af17a7b5755abef6e0b4380a848d9ad88b3af",
-      []
-     ],
-     "random-in-keyframe-expected.txt": [
-      "e0f4a99960fcc88db5d90b006ab6464d4d0e25ae",
+      "764120cd7db6e6bbe16ea9fcd086db47894f7742",
       []
      ],
      "random-serialize.tentative-expected.txt": [
-      "1e201901ffbe837f6e834338abe5c1c0044e95ea",
+      "6b35f47e54dac6a3355aade99e571a2adf676cec",
       []
      ],
      "reference": {
@@ -395211,6 +395704,10 @@
        "c75f9d2352976ce45b6b7905304432648a51a0d2",
        []
       ],
+      "clip-on-target-in-callback-ref.html": [
+       "ed03306b41f08a0f6011a66833c26af9fa88de34",
+       []
+      ],
       "clipper-non-containing-block-ref.html": [
        "7368ec81c1121f3bc7926522e7361f9c896a9f65",
        []
@@ -395239,6 +395736,10 @@
        "951c0928687dcd6edb32e8e3aa7307bf3d6b3923",
        []
       ],
+      "paint-order-ref.html": [
+       "6d049f7aa2e249be7f3ba346b3ddaa3d30cec2f0",
+       []
+      ],
       "pause-rendering-ref.html": [
        "e8435f0e1129f7088eae0a89e561a553cb1fff0b",
        []
@@ -395247,10 +395748,18 @@
        "c825b7b51d21a5ed97d472a2e4bb6fe8df62808d",
        []
       ],
+      "scrolled-target-position-ref.html": [
+       "dc1826553bdc51df3bd9d4aebd1bb5e30b622f69",
+       []
+      ],
       "shadow-dom-ref.html": [
        "2e9bc85dd85676a84a5f3205b1efef3e51ff7950",
        []
       ],
+      "target-in-scrolled-container-ref.html": [
+       "531c2d9b02c25aec4183bc14d6c75bca97bb1e92",
+       []
+      ],
       "transform-clip-ref.html": [
        "5429d9cb195adb915268cae37f1780d2b33382ae",
        []
@@ -395628,6 +396137,10 @@
       "8d265f24986c7af2b08a26741bbfa56f321bfbb3",
       []
      ],
+     "WEB_FEATURES.yml": [
+      "ee3fa15337f7bbbd75573c3ba5a3a0c4ecf82132",
+      []
+     ],
      "green-square-100-by-100-offset-ref.html": [
       "fee7030b6d047ff5ca9be9b6d0d69cd33e95af5c",
       []
@@ -395716,6 +396229,10 @@
       "53463b38f4154029f9fadf7c337119f9828f57b4",
       []
      ],
+     "WEB_FEATURES.yml": [
+      "74115971ea3dfb806aa2c10823d04cc661f3244a",
+      []
+     ],
      "abs-pos-border-offset-001-ref.html": [
       "18a54a41b8b9b9fae5c8c72359a2f2a1cabf8a48",
       []
@@ -398631,7 +399148,7 @@
       []
      ],
      "WEB_FEATURES.yml": [
-      "d5352fb4a11feeec4fc0390130ccbfe2fd51a632",
+      "8f19ffa7b5cc4d7d7f97df0c5cb8e2a0e1ddcd47",
       []
      ],
      "add-background-attachment-fixed-during-smooth-scroll-ref.html": [
@@ -398958,11 +399475,35 @@
        []
       ]
      },
+     "inheritance-expected.txt": [
+      "69d082e2dd9e5c131646d25c644c61d94a05ebec",
+      []
+     ],
      "reference": {
       "paint-order-001-ref.html": [
        "af56147f52291a8b4515c67f843505703875c594",
        []
       ]
+     },
+     "text-decoration-fill": {
+      "text-decoration-fill-computed.tentative-expected.txt": [
+       "fc2c7519c2f92e0dcc77d9263debf3d3fc7c19f9",
+       []
+      ],
+      "text-decoration-fill-valid-expected.txt": [
+       "46b17dcc39086cde98954dc54987a353fb8b9bba",
+       []
+      ]
+     },
+     "text-decoration-stroke": {
+      "text-decoration-stroke-computed.tentative-expected.txt": [
+       "7deb34b7a8a9ab000504ac9761aa85a347207e62",
+       []
+      ],
+      "text-decoration-stroke-valid-expected.txt": [
+       "aeff9bbced4378854d00eff4e8041920b1fac156",
+       []
+      ]
      }
     },
     "filter-effects": {
@@ -400433,7 +400974,7 @@
       []
      ],
      "WEB_FEATURES.yml": [
-      "05b5047e6b4be2277890594b31c567ecbb2f3aec",
+      "2093333a08064bce3a33f66ed235c015acdc2c33",
       []
      ],
      "attribute-selectors": {
@@ -400650,7 +401191,7 @@
      },
      "invalidation": {
       "WEB_FEATURES.yml": [
-       "f0fb9ee3f52189ededd687d089fa0721454cb7b5",
+       "773d02b0284e06ede531fac7128585c4f1176fc4",
        []
       ],
       "any-link-attribute-removal-ref.html": [
@@ -401026,7 +401567,7 @@
      },
      "parsing": {
       "WEB_FEATURES.yml": [
-       "175413584898e796bed8ef61dd3dabe0f2ad0bf4",
+       "1bd3e7789eb9220f96cc5420861c4be9c6dc410d",
        []
       ],
       "invalid-pseudos-expected.txt": [
@@ -401613,7 +402154,7 @@
       []
      ],
      "WEB_FEATURES.yml": [
-      "7dbe4c6283fc85496bf4a35027c8de92a1454ee2",
+      "627c7bbb9d0cb8827e0b839a6a65555c6dc3c266",
       []
      ],
      "adoption.window-expected.txt": [
@@ -401625,7 +402166,7 @@
       []
      ],
      "element-mutation-expected.txt": [
-      "63e9e41f0b9abc0f1ad36430bce255603f67c6a6",
+      "a9ca13b22e861d304a1f2a6206c47b69cf46d14b",
       []
      ],
      "global.window-expected.txt": [
@@ -401636,10 +402177,6 @@
       "a715c1e2339881b9cc061246cf1c0b71cdf4d371",
       []
      ],
-     "per-global-expected.txt": [
-      "b287f4ebf2aabcd00515c4db7d108c56e4835035",
-      []
-     ],
      "pseudo-class-defined.window-expected.txt": [
       "d22c78e62c03c02ebc9cd5fbb256cce53fc3aa93",
       []
@@ -401677,7 +402214,7 @@
       []
      ],
      "template.window-expected.txt": [
-      "d9af626b9a8f222c3dc2e33f038c4ae749b29479",
+      "7c7285c6a8010688d496f3cfbdbbabd1f8062cd5",
       []
      ]
     },
@@ -401729,6 +402266,10 @@
     }
    },
    "density-size-correction": {
+    "WEB_FEATURES.yml": [
+     "ac948e2206aff596a55f696ef30dc5be81a0ff87",
+     []
+    ],
     "density-corrected-image-svg-aspect-ratio-ref.html": [
      "fa13136876c88b8e3b32d68c2c0155862151c51b",
      []
@@ -401989,12 +402530,8 @@
      "9847d173eb6278152c83f719914f604375b4d151",
      []
     ],
-    "create.tentative.https-expected.txt": [
-     "ae711a2866cbe7a63ffde9987406983001dc742f",
-     []
-    ],
     "dc-types.ts": [
-     "1f3d620790c14557ba4eeb884b00520b6f1ea9c8",
+     "bd9fdb79ed9f560e1c299b533edd914bf0e8af81",
      []
     ],
     "digital-credentials-static-methods.tentative.https-expected.txt": [
@@ -402009,17 +402546,13 @@
      "5672154a42328b467a7cbf9e1ad94f3b2e7842d8",
      []
     ],
-    "get.tentative.https-expected.txt": [
-     "ee5d9675973dedab16a4977e617f42bab5b2e60c",
-     []
-    ],
     "non-fully-active.https-expected.txt": [
      "930530f510c39b4a42efd3cb221262dc3a9bfe95",
      []
     ],
     "support": {
      "helper.js": [
-      "5a9cda347888a2527ac059e332d6ad14331dc7ef",
+      "d79ed26b072e64e91f1579f86d720988f97bbc7e",
       []
      ],
      "iframe.html": [
@@ -402281,7 +402814,7 @@
       []
      ],
      "channels.md": [
-      "ccccd3c39aabc9e4cfb0377acf04f11c1481d292",
+      "1ab6e0067b5115481a6d743fae60804f4cee1023",
       []
      ],
      "crashtest.md": [
@@ -402572,6 +403105,10 @@
       "dd683f6f65f89f097ca70594e4a02c2027fdb66b",
       []
      ],
+     "WEB_FEATURES.yml": [
+      "d391160fbe336d5d9518155712032a90440235d0",
+      []
+     ],
      "non-cancelable-when-passive": {
       "WEB_FEATURES.yml": [
        "617921a6ab893f1f1fc0bdba927ab2b1fda0cb93",
@@ -403194,6 +403731,10 @@
       "ac713ce1798e3366a6aa8aaadf47348312592dcd",
       []
      ],
+     "WEB_FEATURES.yml": [
+      "4593bc6d4176686f580fca1958a4fba989e98498",
+      []
+     ],
      "document-element.window-expected.txt": [
       "614d0cdad88b4fd4696680857c51e34cfbe12617",
       []
@@ -403335,6 +403876,10 @@
      "918997b1646d73d71a79c302953ab9f0f8ed69cc",
      []
     ],
+    "WEB_FEATURES.yml": [
+     "50139ec29a5b8f51334907da97275c3b27f2e41d",
+     []
+    ],
     "fn-id-expected.txt": [
      "73400958f9f95edd164122bd2cf423bc794cdcc8",
      []
@@ -405655,7 +406200,7 @@
       []
      ],
      "content-metadata.js": [
-      "b33f1fd71848eebfc1dc75a67734f2a135367022",
+      "0ee7b65f01b35fce565b0ca6d3878c32a7c2bab1",
       []
      ],
      "video_512x288_h264-360k_clear_dashinit.mp4": [
@@ -405923,7 +406468,7 @@
       []
      ],
      "testmediasource.js": [
-      "47cfeb4512a4cf7adf17a84451dc88aa7b6794fe",
+      "1c4a36155832b9e79991ddf219f6c07421d4939e",
       []
      ],
      "utf8.js": [
@@ -406968,7 +407513,7 @@
      []
     ],
     "client-hints.https.html.headers": [
-     "56ce6114188741f4cc980575aa4467ae8d648023",
+     "a9246cffc9949ec43d9e90f7cd7447a04798bea6",
      []
     ],
     "default-enabled-features-allow-all.https.html.headers": [
@@ -407109,23 +407654,23 @@
       []
      ],
      "client-hints-iframe-inner.sub.https.html": [
-      "d02abd6957e33895be8e4b1458b24bc38159ce71",
+      "1fae4a6b0250f3b26d7163717c77775134e563d9",
       []
      ],
      "client-hints-iframe-inner.sub.https.html.headers": [
-      "f500a60ae889c308d0ba71c9d9244522c71b95ba",
+      "7bb3e199117bc9a0e354de75581677bc958fa93e",
       []
      ],
      "client-hints-inner.sub.https.html": [
-      "0271d0290d553f98c5dd35711a11237431195122",
+      "315d665b6e783d0a84976b60e02068580b5f0f7d",
       []
      ],
      "client-hints-inner.sub.https.html.headers": [
-      "ea4cf59d16a6816ca4c4dd9bef8f4ebe3e319fee",
+      "1242a1c4497dd797959aa09550622f9d3088aaed",
       []
      ],
      "client-hints-meta-iframe-inner.sub.https.html": [
-      "9afb5c6a8597c6dcc214984289b6cac8800181e6",
+      "a195b2827e4c297911bed31e813624d2cb7f8a08",
       []
      ],
      "client-hints-meta-iframe-inner.sub.https.html.headers": [
@@ -407133,7 +407678,7 @@
       []
      ],
      "client-hints-meta-inner.sub.https.html": [
-      "b84f16ffd06e65828bbfaff7e6ab644f43ed7c02",
+      "6f215d04a8c6b96750294abc91038f7bd7318a5e",
       []
      ],
      "client-hints-meta-inner.sub.https.html.headers": [
@@ -411444,7 +411989,7 @@
      []
     ],
     "WEB_FEATURES.yml": [
-     "58883c4b9dae998e70ec5d45ddbf3bd2f8d08f06",
+     "d5af597ccf89070861a34c007ee74cf18a322ebc",
      []
     ],
     "backplate": {
@@ -412023,6 +412568,10 @@
      "e401db692c6dbb25349aa94c91b859e379867fca",
      []
     ],
+    "WEB_FEATURES.yml": [
+     "22840b142ae29de50688708261d75e882e45df23",
+     []
+    ],
     "generic-sensor-feature-policy-test.sub.js": [
      "9bc46ae9364be41fdb4dc2cf47d8a231b04f6081",
      []
@@ -412503,7 +413052,7 @@
         []
        ],
        "browsing_context_name-0.html": [
-        "5cbab71a5eaac4fd789a470cca2538225589a497",
+        "ed50b9e82f7b1365c889b5e710747d684a55a886",
         []
        ],
        "browsing_context_name-1.html": [
@@ -412522,10 +413071,6 @@
         "5d2dfa6bb8fbc06593a860819bd0b26f160d4fa7",
         []
        ],
-       "browsing_context_name-expected.txt": [
-        "d75f282a8bd3a3ac4c9bfecae09c3da25d2c1abd",
-        []
-       ],
        "browsing_context_name_cross_origin-0.html": [
         "9e917227147346f0115330c3a6bc953f47349f06",
         []
@@ -412666,6 +413211,10 @@
         "c98711ae980534fafaef24d52c705fd8b9d9a023",
         []
        ],
+       "010.tentative-expected.txt": [
+        "26a52081d485523dc940a03a398e6c225e2030e3",
+        []
+       ],
        "012-expected.txt": [
         "b93175aff4b79cc099b35c11ad4b4d06da648902",
         []
@@ -412678,10 +413227,6 @@
         "966b93c51a958f0bcd1820004236feed5745ff45",
         []
        ],
-       "abort-document-load-expected.txt": [
-        "ae4c0c18275b099576e2dfe85a856672226dc054",
-        []
-       ],
        "about-srcdoc-navigation-blocked.window-expected.txt": [
         "db586637dd052131406d2b4023978e22d976e58f",
         []
@@ -413560,10 +414105,6 @@
         "94679571bec3ef3aa7b17b15e5a53df846a45c42",
         []
        ],
-       "assign_after_load-expected.txt": [
-        "074cbd984a2a5c2f25063d85861fb7fb7208720e",
-        []
-       ],
        "assign_before_load-1.html": [
         "2549867c8ff917ea874b1e3922c6238ce56dc105",
         []
@@ -413572,10 +414113,6 @@
         "94679571bec3ef3aa7b17b15e5a53df846a45c42",
         []
        ],
-       "assign_before_load-expected.txt": [
-        "cc5aa2b4e83c74e598f7aaa49b54799f3da3ea50",
-        []
-       ],
        "cross_origin_joined_frame.sub.html": [
         "a3ffdd005afadb784b1b64a12caa190a66271018",
         []
@@ -414213,23 +414750,15 @@
       },
       "garbage-collection-and-browsing-contexts": {
        "discard_iframe_history_1-1.html": [
-        "217608e46ef78a915c4d6192505187067199273c",
+        "e9ef94bba0ac1ecfff35089bd5f6f69219bc1568",
         []
        ],
        "discard_iframe_history_1-2.html": [
         "b43598f2cd8f47bcd23373075773ef245c95c21a",
         []
        ],
-       "discard_iframe_history_1-expected.txt": [
-        "99746ba8fd011e870e47eef77b262c1536183bf5",
-        []
-       ],
        "discard_iframe_history_2-1.html": [
-        "61e5891ebd63c7978678ad9a4c8e66224145351c",
-        []
-       ],
-       "discard_iframe_history_2-expected.txt": [
-        "2e0724973c9a8491e5a32533d716e552c5d77d20",
+        "1112d08ff25387b61c5739853856b0175ccbaaeb",
         []
        ],
        "discard_iframe_history_3-1.html": [
@@ -417532,7 +418061,7 @@
        []
       ],
       "README.md": [
-       "e6f3fd1c666c43f4c5c750d30696affea4b00c8b",
+       "96a7f376a3d2b3d11efc30e565ff75179b39e598",
        []
       ],
       "gentest.py": [
@@ -417589,7 +418118,7 @@
         []
        ],
        "testharness_element_grid.html": [
-        "33dc3632e498d55a17098b37085b68b29a6f464e",
+        "5bfdc75fa1e01dc16b746600697e0e834734983a",
         []
        ],
        "testharness_offscreen.html": [
@@ -417611,7 +418140,7 @@
       },
       "yaml": {
        "color_space.yaml": [
-        "118ca1e4d3880ca543d29a3203f3f664745621f5",
+        "69de6e0b7f015e4f4b016cdd63e952970e7141cf",
         []
        ],
        "color_type.yaml": [
@@ -417671,7 +418200,7 @@
         []
        ],
        "the-canvas-state.yaml": [
-        "c3f382ef92929aa86f26f5462a7bc6d0e8731854",
+        "ecd11b39323e95c8ecd5409b2bd6be29577f2266",
         []
        ],
        "the-canvas.yaml": [
@@ -418544,6 +419073,10 @@
         "94e9a4f1908e4f4eb3216981a91c3c8174902fcb",
         []
        ],
+       "WEB_FEATURES.yml": [
+        "6f4af901486cec94fad8ded1132c68b60c0fb1a9",
+        []
+       ],
        "cdata-iframe.xhtml": [
         "0c48cbef4fc323dd35ceae449f5494325fe4e1d3",
         []
@@ -419233,6 +419766,10 @@
        "aeda217e29454d000230ddbc3a3ee8bca773482f",
        []
       ],
+      "WEB_FEATURES.yml": [
+       "5fb6e57eb3d2369c0dfb276f801285bf3b8539b5",
+       []
+      ],
       "canvas": {
        "003-1.xhtml": [
         "22adda8720fc0476198fd39b6c31e2d8fae8d38f",
@@ -422162,6 +422699,10 @@
         "a70c6e73fcc9273f0bcf1836f8c830a38e820e0c",
         []
        ],
+       "WEB_FEATURES.yml": [
+        "5209f1c1d7fbba25820ddd98bdb43b8b2e1c6b61",
+        []
+       ],
        "references": {
         "spelling-markers-001-ref.html": [
          "68dcc54702cef01ee8490f6ba7c49e5ff1995848",
@@ -422289,6 +422830,10 @@
       }
      },
      "safe-passing-of-structured-data": {
+      "WEB_FEATURES.yml": [
+       "730bf7c30b5806b58c030053bf7a4d3783e1a41a",
+       []
+      ],
       "resources": {
        "echo-iframe.html": [
         "c4fd5824a1c617c21fe8b92483b388d586edf06e",
@@ -422316,6 +422861,10 @@
        ]
       },
       "shared-array-buffers": {
+       "WEB_FEATURES.yml": [
+        "76b77fd3f830c46c06942e1cf9a68dc76c162f7e",
+        []
+       ],
        "blob-data.https.html.headers": [
         "63b60e490f47f4db77d33d7a4ca2f5b9a4181de8",
         []
@@ -423081,11 +423630,11 @@
         []
        ],
        "lists-styles-expected.txt": [
-        "5ecf4e2b325476c989908fa7949d9e4108b5502d",
+        "37a26920918d42f97c1fd198920a9075cf667765",
         []
        ],
        "lists-styles-quirks-expected.txt": [
-        "5ecf4e2b325476c989908fa7949d9e4108b5502d",
+        "37a26920918d42f97c1fd198920a9075cf667765",
         []
        ],
        "ol-display-contents-ref.html": [
@@ -424438,6 +424987,10 @@
        }
       },
       "the-style-element": {
+       "WEB_FEATURES.yml": [
+        "089ad8443ff97773a1711b79b2503dfef3667f39",
+        []
+       ],
        "html_style_in_comment-ref.html": [
         "999383c76960e327d40c079d70e4ced7f0dcefc6",
         []
@@ -424992,6 +425545,10 @@
        ]
       },
       "the-area-element": {
+       "WEB_FEATURES.yml": [
+        "e3527769b61e21a904f0c97be24df22d86104910",
+        []
+       ],
        "resources": {
         "area-download-click.html": [
          "c0679f8233d9383c93a45707a08edc7611ec3f5b",
@@ -425062,6 +425619,10 @@
         "7b7b6aa0d0a9b196dc044cd077e4008d95cb963b",
         []
        ],
+       "WEB_FEATURES.yml": [
+        "4ce21f0aa5a03b32d316477761dbd622d2642aa6",
+        []
+       ],
        "embed-hidden-attribute-ref.html": [
         "4b3d0feceb6997d9f3230efa6ece7367455c28cc",
         []
@@ -425166,10 +425727,6 @@
         "a2e0b16646946288218e3f2b87444b4f1b636fe8",
         []
        ],
-       "iframe_javascript_url_xhr-expected.txt": [
-        "16162274bf3d53d9b8338b94503f997c992eace2",
-        []
-       ],
        "iframe_sandbox_popups_helper-1.html": [
         "6b120f15d0d7e6cd2fe6a2753ff4592b05056034",
         []
@@ -425493,7 +426050,7 @@
         []
        ],
        "WEB_FEATURES.yml": [
-        "28d0ead2dbc3f9db5e7c2714cc72434b4bc367a2",
+        "910320acfc85beed17d1a072daa24ccaaed46502",
         []
        ],
        "animated-image-update-ref.tentative.html": [
@@ -425679,6 +426236,10 @@
         []
        ],
        "sizes": {
+        "WEB_FEATURES.yml": [
+         "ad6fbd56038cb1c8c50845baf1b550159780cd4a",
+         []
+        ],
         "reference": {
          "sizes-auto-rendering-ref.html": [
           "221930fddb888e119420769da83e307a259fd4d6",
@@ -425701,6 +426262,10 @@
         }
        },
        "srcset": {
+        "WEB_FEATURES.yml": [
+         "ad6fbd56038cb1c8c50845baf1b550159780cd4a",
+         []
+        ],
         "common.js": [
          "d4d2c7534c7fadac56a59a09455180f57697a6d9",
          []
@@ -425819,6 +426384,10 @@
        "ce84e4ae4c9915f3c1c21d7d23f47a7086b83487",
        []
       ],
+      "WEB_FEATURES.yml": [
+       "7101f2a96ffc6ce780e6016184ae4b6f4bd0c036",
+       []
+      ],
       "attributes-common-to-form-controls": {
        "WEB_FEATURES.yml": [
         "457c9040053508f0a7d29ec4e1b94b53e3b1be04",
@@ -426041,7 +426610,7 @@
       },
       "the-input-element": {
        "WEB_FEATURES.yml": [
-        "4cb6a13e3ff0675445efac913325606a37af42b4",
+        "6d25995b9463819faf7224a62e9ea34f7a61d22a",
         []
        ],
        "auto-direction-ref.html": [
@@ -426533,7 +427102,7 @@
         []
        ],
        "WEB_FEATURES.yml": [
-        "14479d4bb3d44a72130eb7008e93b7b379bab48f",
+        "36350df2734df2624b6ed5a074812eecdf14b1dd",
         []
        ],
        "multiline-placeholder-ref.html": [
@@ -428854,6 +429423,10 @@
         "f5096b338a23062a1236f58ccde22517203fe20b",
         []
        ],
+       "WEB_FEATURES.yml": [
+        "eb578ea3d8ac79420f269317ebf4b5d53c957317",
+        []
+       ],
        "a-download-404.py": [
         "abb85139b39ec2cd41c19f4942e9f389cffc5d3a",
         []
@@ -428992,6 +429565,10 @@
        ]
       },
       "the-wbr-element": {
+       "WEB_FEATURES.yml": [
+        "d56b4a923e209bcfa4478291f94048fe934ea02a",
+        []
+       ],
        "wbr-element-ref.html": [
         "bedb6d62e8431db1da1d87077ee5234ff2937d11",
         []
@@ -429019,6 +429596,14 @@
         []
        ]
       }
+     },
+     "the-style-element": {
+      "tentative": {
+       "WEB_FEATURES.yml": [
+        "089ad8443ff97773a1711b79b2503dfef3667f39",
+        []
+       ]
+      }
      }
     },
     "syntax": {
@@ -431039,7 +431624,7 @@
      "system-state-and-capabilities": {
       "the-navigator-object": {
        "WEB_FEATURES.yml": [
-        "2c43701e4a892826c9218121df33532831dc3e9f",
+        "8d74a882c85880be5620b676bde8875a8ae51985",
         []
        ],
        "per-global.window-expected.txt": [
@@ -431631,6 +432216,10 @@
        "f488759aa4aefc146aa7c4c1e61c0557d5901e00",
        []
       ],
+      "empty-scopes.json": [
+       "259471ad04ddbb5f478632cf02b5742c1417e1bc",
+       []
+      ],
       "overlapping-entries.json": [
        "21354025451cf0c5bba5ff31f36f957189237513",
        []
@@ -432715,6 +433304,10 @@
      "035e1d233d6f5646768b78e878c9db43b84a0068",
      []
     ],
+    "WEB_FEATURES.yml": [
+     "61b6bd43738fafab634618a467776716cec01ab0",
+     []
+    ],
     "accelerometer.idl": [
      "a082224dc88e10e043d86d09cbf981616f6bb9a7",
      []
@@ -432784,7 +433377,7 @@
      []
     ],
     "clipboard-apis.idl": [
-     "594484ced62676cdf50fd8b213e8b7c020941c3d",
+     "ddbc0c0ed759c831274711e5cce25869dfcdd74c",
      []
     ],
     "compat.idl": [
@@ -432816,7 +433409,7 @@
      []
     ],
     "crash-reporting.idl": [
-     "60f15bbb44d4f4583194b0d84f916da7e0b26f97",
+     "b172e7351a7fca1855a356835da87b903f1b516f",
      []
     ],
     "credential-management.idl": [
@@ -432956,7 +433549,7 @@
      []
     ],
     "css-view-transitions.idl": [
-     "3260bb68d48f53a88d40100163013e875e4a9976",
+     "c8b1b94aaf7b25f244d96e5f4c64f3a40cae0b27",
      []
     ],
     "css-viewport.idl": [
@@ -433004,7 +433597,7 @@
      []
     ],
     "dom.idl": [
-     "2b43b98e8ef791c1b3e1a092f18d6f1cc5818874",
+     "1ddc084b949df64f36041f3a0c533468765dceb0",
      []
     ],
     "edit-context.idl": [
@@ -433128,7 +433721,7 @@
      []
     ],
     "html.idl": [
-     "072baa421dd52c4a3d41bbe5c7ac54cf14f6e351",
+     "d2e2879050c0d5ba9b29f3d8b275e20c11dea5c0",
      []
     ],
     "idle-detection.idl": [
@@ -433320,7 +433913,7 @@
      []
     ],
     "origin.tentative.idl": [
-     "17af9d3030649a48143e28c9d9a0ff4d1df97e79",
+     "4adc41f682bafb27c9d76123d9c8fa76e411c993",
      []
     ],
     "page-lifecycle.idl": [
@@ -433404,7 +433997,7 @@
      []
     ],
     "privacy-preserving-attribution.idl": [
-     "14dbe47f48aa3e1aab9ace913084f52dd92493c4",
+     "d335a54aa3f152abd56e5ec746807ce55d2086d9",
      []
     ],
     "private-aggregation-api.idl": [
@@ -433723,6 +434316,10 @@
      "194e2d806adb2131f7cdc56a70a46d7784cb7d75",
      []
     ],
+    "webextensions.idl": [
+     "bd9246deb76b56eff23c93cfab37a65c4801a728",
+     []
+    ],
     "webgl1.idl": [
      "c345b14272146a88d55146007c2883291a1b046a",
      []
@@ -433732,7 +434329,7 @@
      []
     ],
     "webgpu.idl": [
-     "e36637d203d17c8b84585c8ff09c34196fc1db2b",
+     "377ce9d50a2d7c8c7773ac9769555824c5a12eaa",
      []
     ],
     "webhid.idl": [
@@ -433752,7 +434349,7 @@
      []
     ],
     "webrtc-encoded-transform.idl": [
-     "ff38f5e2f0edfb8a2dfdec9694b22e74d9430e6a",
+     "998c3b527c25887a9522c8449a800ceefe61121b",
      []
     ],
     "webrtc-ice.idl": [
@@ -433828,7 +434425,7 @@
      []
     ],
     "webxr-plane-detection.idl": [
-     "037e9e251c743ae9cd573c512127a5f48663457e",
+     "3c48742f9c7491517f7b30108c954d74620652d7",
      []
     ],
     "webxr.idl": [
@@ -433836,7 +434433,7 @@
      []
     ],
     "webxrlayers.idl": [
-     "b99bef707fbd03e7a578215079c5df7714e67168",
+     "99b5989c821c726a0a34fec23158ae0152f26564",
      []
     ],
     "window-controls-overlay.idl": [
@@ -435411,6 +436008,10 @@
     },
     "relations": {
      "css-styling": {
+      "WEB_FEATURES.yml": [
+       "8b00e537736575b2d21a899bdd28f5ae5949c59f",
+       []
+      ],
       "blur-filter-ref.html": [
        "21fc165b7da477a619933185c2f45df3c3e9ab09",
        []
@@ -435589,6 +436190,10 @@
        "58dd5a5d8c39ff86376de7038a22727abe19c4ab",
        []
       ],
+      "non-mathml-namespace-001-ref.html": [
+       "974508c5fdb8d5c863b0d1934588ab35d8e3ceac",
+       []
+      ],
       "out-of-flow": {
        "absolutely-positioned-001-ref.html": [
         "447c5b722a540d07365475aab17214ad572cbc0e",
@@ -436671,6 +437276,10 @@
      "b64cd975108aceafff61dbfed27c598748513004",
      []
     ],
+    "WEB_FEATURES.yml": [
+     "77c09171745c97dafe175f23dd5eea05b95a000d",
+     []
+    ],
     "historical-expected.txt": [
      "2cb87266cb94b055e8a858cf236b72bf0a46b229",
      []
@@ -436903,6 +437512,10 @@
      "cc43ec1bbbb855066a4311812296c1cff7bf1d15",
      []
     ],
+    "WEB_FEATURES.yml": [
+     "a47daa01b167edf03c099d8259463d490a57a303",
+     []
+    ],
     "enumerateDevices-with-navigation.https-expected.txt": [
      "2a09ae75f113799ba12376aae1bd703779d916b9",
      []
@@ -437957,6 +438570,14 @@
      "ad2c11c6d5fca8492bc7f52bceaa15011d602037",
      []
     ],
+    "icon-fetch-sw.js": [
+     "31e4e1e883bab16c20a89bb1e39044de91e6abd5",
+     []
+    ],
+    "icon-fetch.tentative.https.window-expected.txt": [
+     "d907087fefc97a994a8914b5a519d0406b043a24",
+     []
+    ],
     "icon-url-encoding-euc-kr.tentative.https-expected.txt": [
      "1388f371159a39bc180f6427bdb95296d678e088",
      []
@@ -446815,6 +447436,10 @@
      "624b021336aff022d000d97586789e4b8f6c8ab1",
      []
     ],
+    "WEB_FEATURES.yml": [
+     "ac71d82621ebe1bc0b234f48da03904e5816adb0",
+     []
+    ],
     "resources": {
      "aztec-correction.jpg": [
       "55bc0d23d6a224363cc9a64ee99c15b52016dfc9",
@@ -446975,6 +447600,10 @@
     }
    },
    "shared-storage": {
+    "WEB_FEATURES.yml": [
+     "d532c98c565ddb5e8293e51dc3bbfbd32b28dc8e",
+     []
+    ],
     "resources": {
      "cors-redirect.py": [
       "a475ee4f96ebdba21095eae2ecf98e524d344d72",
@@ -448988,6 +449617,10 @@
      []
     ],
     "animations": {
+     "WEB_FEATURES.yml": [
+      "b7ca2adf4116fe1da7a73f6214341f9843a9b195",
+      []
+     ],
      "animate-marker-orient-from-auto-to-auto-start-reverse-expected.txt": [
       "3801cce430df8e09167206a218355263f656bc79",
       []
@@ -449389,6 +450022,10 @@
       ]
      },
      "scripted": {
+      "WEB_FEATURES.yml": [
+       "b5ba3cc1a25d72f56d53986bb4a6e06e72758b4f",
+       []
+      ],
       "href-animate-element-expected.txt": [
        "67f0c5ed9f5b5dedb54d172ae42549398a7b2a86",
        []
@@ -449887,6 +450524,10 @@
     },
     "render": {
      "order": {
+      "WEB_FEATURES.yml": [
+       "e34035e793d816d542d2a74df91ed75f3ba61b58",
+       []
+      ],
       "clip-path-filter-order-ref.svg": [
        "ea42d9eb02a5f23fadc062c41cfa513979764aef",
        []
@@ -450289,7 +450930,7 @@
     },
     "text": {
      "inheritance-expected.txt": [
-      "227d9840bb71dac30fb4cadae79d7c2a65f98611",
+      "04db53d2ebef979af9904f5e7f1178187d418ce2",
       []
      ],
      "parsing": {
@@ -450300,22 +450941,6 @@
       "shape-subtract-valid-expected.txt": [
        "fa65eda657d74e57e01d6b5090ae8734bc12881b",
        []
-      ],
-      "text-decoration-fill-computed.tentative-expected.txt": [
-       "c18ae4da0aad7c01de09ac657161ec9d5def1900",
-       []
-      ],
-      "text-decoration-fill-valid-expected.txt": [
-       "46b17dcc39086cde98954dc54987a353fb8b9bba",
-       []
-      ],
-      "text-decoration-stroke-computed.tentative-expected.txt": [
-       "073346a5196614c3e562f9b4dc4dd4b600ac56c3",
-       []
-      ],
-      "text-decoration-stroke-valid-expected.txt": [
-       "aeff9bbced4378854d00eff4e8041920b1fac156",
-       []
       ]
      },
      "reftests": {
@@ -455053,6 +455678,10 @@
      "5238ce954992f94507668167696b314cfddcf71d",
      []
     ],
+    "WEB_FEATURES.yml": [
+     "be77dc626405b4a14394bbe317e5801bff90ec0d",
+     []
+    ],
     "disabled-by-permissions-policy.https.sub.html.headers": [
      "83f5a343faf985e64f6a400cc0ecb2791fe39460",
      []
@@ -455588,7 +456217,7 @@
      []
     ],
     "WEB_FEATURES.yml": [
-     "8b273de80b54a1e320ec9ffdd29bdbaa92445430",
+     "02e64129df5b7f0b01540e13c0d4740b651c2337",
      []
     ],
     "helpers.js": [
@@ -457617,6 +458246,10 @@
      "8e6b680d91c9b81b85e3ce010c5fb9fe338f37bf",
      []
     ],
+    "WEB_FEATURES.yml": [
+     "1e167ea31744f6e8e2c1c79389667ff4a922843e",
+     []
+    ],
     "common.js": [
      "416c21ce9330d3f73576b6b5aa01dab06c5798ee",
      []
@@ -457824,7 +458457,7 @@
      []
     ],
     "WEB_FEATURES.yml": [
-     "0aacecad8927ad0841a5e507fc1cfa5c5cd99016",
+     "e3ff811df5d29e09ae3c69af702da66dfb1ddfa6",
      []
     ],
     "broadcastchannel": {
@@ -457864,7 +458497,15 @@
      }
     },
     "message-channels": {
+     "WEB_FEATURES.yml": [
+      "32648dca786c90997fe27bde6c925154ddf05ecc",
+      []
+     ],
      "close-event": {
+      "WEB_FEATURES.yml": [
+       "daebc7c8c4816a0367eea8cc05f7b5c74b3cdc11",
+       []
+      ],
       "resources": {
        "helper.js": [
         "48744ac1c5b530ef8d46c3d9a0378c698353a5bc",
@@ -457896,6 +458537,10 @@
      ]
     },
     "multi-globals": {
+     "WEB_FEATURES.yml": [
+      "68347b1e6cd3e368d0a850b39ce73f96841d9b57",
+      []
+     ],
      "support": {
       "current-document-domain.sub.html": [
        "1b15f72ca481e640f87f368daa2f8813cd0fb88c",
@@ -457943,6 +458588,18 @@
       []
      ]
     },
+    "with-options": {
+     "WEB_FEATURES.yml": [
+      "2a3e341f25fd6d0e17f55de0e9a3879b95dd21b8",
+      []
+     ]
+    },
+    "with-ports": {
+     "WEB_FEATURES.yml": [
+      "bea62498f6b0f5362bef95061dc77ec6201a5ae7",
+      []
+     ]
+    },
     "without-ports": {
      "019-1.html": [
       "513123ee6d56b01177c20bfb841c233118189515",
@@ -457955,6 +458612,10 @@
      "025-1.js": [
       "bd1d778d9b32651b05f1b7364459ed4096646528",
       []
+     ],
+     "WEB_FEATURES.yml": [
+      "187ee0267f65c130aacd28427caf6c7cd9deefe0",
+      []
      ]
     },
     "worker_postMessage_user_activation.js": [
@@ -458246,7 +458907,7 @@
      []
     ],
     "WEB_FEATURES.yml": [
-     "117b04f81fe45dc7935177063c7177ac00e06fc0",
+     "41831b330f2f10587d0d77951e7ccd42c1043601",
      []
     ],
     "back-forward-cache-with-closed-webrtc-connection-ccns.https.tentative.window-expected.txt": [
@@ -458288,6 +458949,10 @@
       "8adbf6aa173949718a90bd54cc95361e1a8801ba",
       []
      ],
+     "WEB_FEATURES.yml": [
+      "393ef853528192cd575e75ae8c38fe8f7350f880",
+      []
+     ],
      "munge-dont-expected.txt": [
       "6013bdd88010e749a9ed3c44f046b020456ee382",
       []
@@ -458298,6 +458963,10 @@
       "5e17fbf9c354edabaf48f6ee1d017f23d18e2171",
       []
      ],
+     "WEB_FEATURES.yml": [
+      "393ef853528192cd575e75ae8c38fe8f7350f880",
+      []
+     ],
      "av1-profile-asymmetry.https-expected.txt": [
       "5fb1c42c3ef06fcf500f3df24a4f014f2999b9fe",
       []
@@ -458342,6 +459011,10 @@
       "90fee69f044fc5adfcf49bac0fdb6dcd638556e1",
       []
      ],
+     "WEB_FEATURES.yml": [
+      "393ef853528192cd575e75ae8c38fe8f7350f880",
+      []
+     ],
      "negotiation-encodings.https-expected.txt": [
       "14f80479a4682297615055cee46d94fe6e110b02",
       []
@@ -459340,7 +460013,7 @@
     "api": {
      "VTTCue": {
       "WEB_FEATURES.yml": [
-       "bb2aa7cddd6d6e0c02c0e8c6bbe15e68f089aee6",
+       "e26a76914f4069a6b1bee40cb6b3dc41e3dccb73",
        []
       ],
       "categories.json": [
@@ -461638,7 +462311,7 @@
      []
     ],
     "WEB_FEATURES.yml": [
-     "d9c44073f7a6aba0d3e43bb22effe1ca817e0098",
+     "d2f14cf3c2c4e7e7677b25674c02a6b9db5bb0a7",
      []
     ],
     "Worker-creation-happens-in-parallel.https-expected.txt": [
@@ -461930,6 +462603,10 @@
     "interfaces": {
      "DedicatedWorkerGlobalScope": {
       "postMessage": {
+       "WEB_FEATURES.yml": [
+        "337a1fb5525d25f5c21954945b75c79ffb6a3674",
+        []
+       ],
        "event-ports-dedicated.js": [
         "c5cc9f8d9f580b43418045965b97bf4fd2ed371a",
         []
@@ -492592,7 +493269,7 @@
       ]
      ],
      "language-model-clone.tentative.https.window.js": [
-      "22cd5ff81092b867a118ff786724373d1531919d",
+      "ec5beadcb4b3d0d1cf98db5f6f2aa0119359ddd6",
       [
        "ai/language-model/language-model-clone.tentative.https.window.html",
        {
@@ -512598,7 +513275,7 @@
       ]
      ],
      "document-write-iframe.html": [
-      "d6ad88ddc93df40894ee27d9a141219d9f1725e8",
+      "73614e3aa2da597d17564ce290c1c0741eed4c92",
       [
        null,
        {}
@@ -521813,7 +522490,7 @@
       ]
      ],
      "try-tactic-position-area.html": [
-      "cbc24ab6f5391291b9535c24f204b55c85d37ab8",
+      "f47231b1504569cb6a6959d1121f5cf94716d107",
       [
        null,
        {}
@@ -522647,6 +523324,13 @@
         {}
        ]
       ],
+      "background-position-composition.html": [
+       "0d287f0ea6793107131d65c04ae8cd4cbca60588",
+       [
+        null,
+        {}
+       ]
+      ],
       "background-position-interpolation.html": [
        "2e7f2e24a1db1c6dd34033a753327e0ed3b08276",
        [
@@ -522675,6 +523359,13 @@
         {}
        ]
       ],
+      "background-size-composition.html": [
+       "f78eb48054a23747f1d99b239a0349d45fdcbcfe",
+       [
+        null,
+        {}
+       ]
+      ],
       "background-size-interpolation.html": [
        "f6a480c7bd2ccc4a6c46fa2eade5e7231fab4938",
        [
@@ -525048,6 +525739,13 @@
        {}
       ]
      ],
+     "layer-stylesheet-multi-adoption.html": [
+      "7fdeb7c133ee0ded1bcb3939f487b5ee45ae21f3",
+      [
+       null,
+       {}
+      ]
+     ],
      "layer-vs-inline-style.html": [
       "9ddfbc39075413fd421f4e8df3e65a2dd3b45f55",
       [
@@ -528386,7 +529084,7 @@
       ],
       "tentative": {
        "display-computed.html": [
-        "ee6f158eab548ca7d2f1b85e07d4c8cd8577f8da",
+        "ac48fb83379ab1028dc2456cc7c0758497b3da60",
         [
          null,
          {}
@@ -535602,7 +536300,7 @@
      "grid-lanes": {
       "tentative": {
        "grid-lanes-grid-template-columns-computed-withcontent.html": [
-        "6ee604b181f5704803c663cb80a4dc52b2dac0a6",
+        "8e24956fbb17c6d62d8f6eeae6bf7544370dee2e",
         [
          null,
          {}
@@ -535610,112 +536308,112 @@
        ],
        "items": {
         "column-automatic-minimum-for-auto.html": [
-         "d6ec858f8d0681c4064b08a89902bc1ee0cd2a0d",
+         "b2664c533534796e751064f28eef90a6c14480d1",
          [
           null,
           {}
          ]
         ],
         "column-fit-content-percentage.html": [
-         "be1202db8afc5027c15d87394cba23a9b211b21d",
+         "446303c1dd0db61f5e251c880cf9f39e31859a28",
          [
           null,
           {}
          ]
         ],
         "column-flex-track-intrinsic-sizes.html": [
-         "87063dd779f8fd517f02e55ab400686db68648b1",
+         "840f303ca7eaaaacd9a6478c7ff2ba871cc2aecc",
          [
           null,
           {}
          ]
         ],
         "column-intrinsic-track-sizes.html": [
-         "d2b7c2bf6a5b614b13865f4783fb0fcb30ce2a06",
+         "b1936d0fd3f6433c7b21280aca0d133aba6701cf",
          [
           null,
           {}
          ]
         ],
         "column-minimum-contribution-baseline-shim-vertical-lr.html": [
-         "73b42ee1370c453cb88e9c06e15d8243044bd919",
+         "9f52d79e5383ca8ef063d77ed1eacd35dc6c8e4b",
          [
           null,
           {}
          ]
         ],
         "column-minimum-contribution-baseline-shim-vertical-rl.html": [
-         "65550b63aa8d3c74e94396c327873f1a8bb70434",
+         "91411817ad3a775d53ffd080f92c0410e0b06b59",
          [
           null,
           {}
          ]
         ],
         "column-minimum-contribution-with-percentages.html": [
-         "d1e864b4b6999b950b734ddcb95e51934ff7d387",
+         "1acea5ce52ed016786ef12fd2832bc991bc60f0a",
          [
           null,
           {}
          ]
         ],
         "column-minimum-size-grid-items-002.html": [
-         "9f5943bb8dd0ef3e13c9afc48937dac37aa40643",
+         "322d0e1ab8a65bf0f4ea990348f5692c78c3ecb0",
          [
           null,
           {}
          ]
         ],
         "column-minimum-size-grid-items-003.html": [
-         "ab1bfcf97f0efd79dfa04d82f300d07bccfc3f73",
+         "86782917447577d0241f9717ffe77c6243dd9181",
          [
           null,
           {}
          ]
         ],
         "row-automatic-minimum-for-auto.html": [
-         "4e7a9e6a12d26f34f7600e27b2ec7da4e3313c08",
+         "49507400300c9a1d760091548e8b57bae55b231f",
          [
           null,
           {}
          ]
         ],
         "row-flex-track-intrinsic-sizes.html": [
-         "78ec15382df7d91427f77fcce4bccb2fa0ce98d4",
+         "9930f096795343584ad4275b88a4617a755e4ea1",
          [
           null,
           {}
          ]
         ],
         "row-intrinsic-track-sizes.html": [
-         "b8b452d4391d089654632438025849481a0da7dd",
+         "3a0d9fd284f55157b0685b20279b82c779b08d42",
          [
           null,
           {}
          ]
         ],
         "row-minimum-contribution-baseline-shim.html": [
-         "714d0114cac140f64b03be7e9b1c48bc42b45f97",
+         "7b6feb68fcfe4b967a0531fb83ad0b5770625071",
          [
           null,
           {}
          ]
         ],
         "row-minimum-contribution-with-percentages.html": [
-         "547e11abdbc7dca6c10761dc8fb6319df08680ed",
+         "8c13cc63ac9e87623743eff5c084e3418b439b2d",
          [
           null,
           {}
          ]
         ],
         "row-minimum-size-grid-items-002.html": [
-         "5719fa1832975cb4f1cb8d4958d584174b859509",
+         "60cb7d36c278f9287fd53d492172dc33b58f5f87",
          [
           null,
           {}
          ]
         ],
         "row-minimum-size-grid-items-003.html": [
-         "81f8aac70a3b00fdf53d4a5a1a2f6d85f1be1c21",
+         "704c91ad707ab1a425c7f5dd08d969da2056de52",
          [
           null,
           {}
@@ -535724,42 +536422,42 @@
        },
        "parsing": {
         "grid-lanes-direction-computed.html": [
-         "d94476540f59d254ba58c3c0513420a1e9afd608",
+         "e55e3630af239bdd0271fb30fb44d5d40fb8a6a7",
          [
           null,
           {}
          ]
         ],
         "grid-lanes-direction-invalid.html": [
-         "dddcb2f2471e0cd03280d3f40457e7e0baf5766a",
+         "e8e562190dc3bfaffd87b71e4da7760bb3e676e2",
          [
           null,
           {}
          ]
         ],
         "grid-lanes-direction-valid.html": [
-         "a901304bb1db780af6c89a8bd8d3aa132f59cc60",
+         "440ecc77912f2d77a70bba2ed7971f313049f49d",
          [
           null,
           {}
          ]
         ],
         "grid-lanes-fill-computed.html": [
-         "44ff30b8cd973ed305422d851ec9aad12abc022a",
+         "bfab050799bfb9432d242b367eaabc588d2df1e4",
          [
           null,
           {}
          ]
         ],
         "grid-lanes-fill-invalid.html": [
-         "10ccde781ff0c570eb1454b10f12b5167ac10e40",
+         "2ef34791c4c00dbbe5b9b3a613ee00776818811b",
          [
           null,
           {}
          ]
         ],
         "grid-lanes-fill-valid.html": [
-         "23498d40215072ad31bf64a8ebebe41416c97201",
+         "43d7f28e622860625caa30b613bbd7a74dc5d910",
          [
           null,
           {}
@@ -535780,7 +536478,7 @@
          ]
         ],
         "grid-lanes-flow-valid.html": [
-         "aa7b44ed971ae56f7413f4d381871d936440ae8a",
+         "5d6b4a841dd17ff74827d4892fc547223fb5ddbe",
          [
           null,
           {}
@@ -535801,35 +536499,35 @@
          ]
         ],
         "grid-lanes-shorthand-serialization.html": [
-         "ecedb3d847aa0eac80718bdbf9a6933e02a6bfa2",
+         "9357d7cd5571b02a850e9da5b7c31e4fb20a51a5",
          [
           null,
           {}
          ]
         ],
         "grid-lanes-shorthand-valid.html": [
-         "556b7c02e18a36618bb6c271b98d397648d0fd67",
+         "05a09f6700a30a745b685214339dc456d72eb2c7",
          [
           null,
           {}
          ]
         ],
         "item-tolerance-computed.html": [
-         "2d5a9125b6edcc5020a95f5729b466a876369110",
+         "53c58baa77e42be58956aefab146b4f333d51101",
          [
           null,
           {}
          ]
         ],
         "item-tolerance-invalid.html": [
-         "ab8eb59af3cad51dca0b8b1a2f9aa96fb821f1e1",
+         "d2e7d28edcdb3d3f9bd4e1918441212ce9833ffb",
          [
           null,
           {}
          ]
         ],
         "item-tolerance-valid.html": [
-         "820ef8a9436451a78c5a4aa22949ecab5a4a87d4",
+         "475a12618b389ddf9bb8592d7cba99e63bf852bd",
          [
           null,
           {}
@@ -535867,7 +536565,7 @@
           ]
          ],
          "column-auto-repeat-012.html": [
-          "28df256f49abc180e3b154ef32b506e4e3cf5eea",
+          "3f46ae741c9d4ae2ef00f691e930d5c5708f9416",
           [
            null,
            {}
@@ -535910,7 +536608,7 @@
            ]
           ],
           "column-auto-repeat-auto-010.html": [
-           "e071fac50bcca249638a67c6957d7e15a1042f2e",
+           "c9c421a8120014244bbd0ed6789939437c93326c",
            [
             null,
             {}
@@ -535952,42 +536650,42 @@
            ]
           ],
           "row-auto-repeat-auto-009.html": [
-           "b50da25d48cf7aaf81105644bb071aff09a69578",
+           "636bc1b46dd794729d58c974a4454e4961da0cdd",
            [
             null,
             {}
            ]
           ],
           "row-auto-repeat-auto-010.html": [
-           "66e49d6dd67aab9498d064c3378ce78ab7eb47e7",
+           "b46b82f710c18b6f093d30842a8a9bedd67519aa",
            [
             null,
             {}
            ]
           ],
           "row-auto-repeat-auto-015.html": [
-           "3460295baca61a55e4975972f5902ab580014d90",
+           "f178404244804ea7a2835516a0751c5005df4e2d",
            [
             null,
             {}
            ]
           ],
           "row-auto-repeat-auto-016.html": [
-           "d3c247c285fb3cbf4f38968af3904aa863ac4624",
+           "529cb9ea16d43a54adf5a261e6aeae968bc62409",
            [
             null,
             {}
            ]
           ],
           "row-auto-repeat-auto-020.html": [
-           "082002ea5b19010e6393d668a2622e7bca67e371",
+           "34d2a7619c3e32c7d749c00ed1f6fa523b559e44",
            [
             null,
             {}
            ]
           ],
           "row-auto-repeat-auto-021.html": [
-           "518727e12a079c5dfcadc66ac6e9bbb91fd01302",
+           "510532ffa20d25111cd6b774ff9fa220e868a8df",
            [
             null,
             {}
@@ -535995,63 +536693,63 @@
           ]
          },
          "row-auto-repeat-007.html": [
-          "6e13bd29cb20617831b9c005d2f80137f1f17ab8",
+          "0440a11907574d27b585a1b6f5aec327e3f46242",
           [
            null,
            {}
           ]
          ],
          "row-auto-repeat-008.html": [
-          "3b1673d72f13166bfb202ff5181b869061e00239",
+          "122022af591edd8ad1dfc2c56b7f44d53ec78d84",
           [
            null,
            {}
           ]
          ],
          "row-auto-repeat-009.html": [
-          "7bbcecb8405460839672ab34953c5855b2bf7c61",
+          "5e3c1668a6c0b3bc4cd72574b9a521098f32b79f",
           [
            null,
            {}
           ]
          ],
          "row-auto-repeat-010.html": [
-          "831269791bf175b6d1cfc95873474c88b23f93c8",
+          "9f96f127a96c3a7a2cea6e8bcf27136d4f4d9680",
           [
            null,
            {}
           ]
          ],
          "row-auto-repeat-011.html": [
-          "d9adabbe87b385a43a622badc4907340e25d3021",
+          "1c123fd5340939d3c82124d31cdf9d9cc2034f1f",
           [
            null,
            {}
           ]
          ],
          "row-auto-repeat-017.html": [
-          "78db77607da307d097bfbaa0d0d6c3740a159177",
+          "7c156d96a762d0a0c0780e20722fd257307dd400",
           [
            null,
            {}
           ]
          ],
          "row-auto-repeat-018.html": [
-          "631d6a5af271c298cffb809d3df5fc7444081331",
+          "620046939289783469bd4eca69cda0ff19999d37",
           [
            null,
            {}
           ]
          ],
          "row-auto-repeat-022.html": [
-          "622a4a8a94813e3d071eabd1434b967ccc054f50",
+          "96921ceaa3d4ea1c7284a0d4a999fcef29718eff",
           [
            null,
            {}
           ]
          ],
          "row-auto-repeat-023.html": [
-          "49d48268e3834d124912af4d9e59cf7ced405a3c",
+          "44832abcfbea95809306c99dc78a3afcfa14b03e",
           [
            null,
            {}
@@ -536059,7 +536757,7 @@
          ]
         },
         "dynamic-grid-track-direction.html": [
-         "6b2ca5f79b135b9237cc1f042e4e7da64bc5cee7",
+         "041ecd6da1fc3ffdd57b1e61ffbdd885774501bf",
          [
           null,
           {}
@@ -536683,28 +537381,28 @@
        ]
       ],
       "grid-template-columns-intrinsic-auto-repeat-computed-implicit-track.tentative.html": [
-       "b766fd336d5d8920619c7332bf4340c6ee2dcb55",
+       "b6edea55363581dad8ce6fd064fee4224cf6429a",
        [
         null,
         {}
        ]
       ],
       "grid-template-columns-intrinsic-auto-repeat-computed-nogrid.tentative.html": [
-       "4408ce428c8cf4e16866bca2df58266ad7d0ba57",
+       "d6e973d0bd08c0f0a8ac8bafeef02f867ddfad3f",
        [
         null,
         {}
        ]
       ],
       "grid-template-columns-intrinsic-auto-repeat-computed-withcontent.tentative.html": [
-       "1094d83206f8c86d75a7e77be79fe6b566a1a25f",
+       "ff4aa593e56b6af0ba9adae9175f208b79dcb1da",
        [
         null,
         {}
        ]
       ],
       "grid-template-columns-intrinsic-auto-repeat-computed.tentative.html": [
-       "d0c2b3c9503c6e8e2ca3d81c8ff6efa4c430fd3b",
+       "6136c765dded7fdffcce9c1ceae0e2b8b02aa99a",
        [
         null,
         {}
@@ -536788,28 +537486,28 @@
        ]
       ],
       "grid-template-rows-intrinsic-auto-repeat-computed-implicit-track.tentative.html": [
-       "0933988f6ad6e16df70bd63445b1726445e6d50e",
+       "562a6ff58be1d62d14013e2af80cfb79bca2dbfb",
        [
         null,
         {}
        ]
       ],
       "grid-template-rows-intrinsic-auto-repeat-computed-nogrid.tentative.html": [
-       "bfb88b7a9e42b534339493873a112c5c6feaf3a9",
+       "5b09fca0c36f1b78b13d84bef02e010c74e0a798",
        [
         null,
         {}
        ]
       ],
       "grid-template-rows-intrinsic-auto-repeat-computed-withcontent.tentative.html": [
-       "67b8e0d5a459b7999c72018a90f98a68e1b8b8d8",
+       "eb73c879177f6821f41bb672543acad01621e07d",
        [
         null,
         {}
        ]
       ],
       "grid-template-rows-intrinsic-auto-repeat-computed.tentative.html": [
-       "20216f5051a2d7de2c256e059dcfbdda333a70fb",
+       "649a74d67869cd743e0c543f0c073ee9a885c155",
        [
         null,
         {}
@@ -537883,7 +538581,7 @@
       ]
      ],
      "css-lists-no-interpolation.html": [
-      "a0e1005ef0bf3b1ff2690ee6a9fe2d3fd902e8ce",
+      "658f9feffa830a6fac0782f5f6227651b66b1f8b",
       [
        null,
        {}
@@ -538719,6 +539417,13 @@
     },
     "css-masking": {
      "animations": {
+      "clip-composition.html": [
+       "423ba84f75394856ae3af8a2eb7841be0fbaf8b9",
+       [
+        null,
+        {}
+       ]
+      ],
       "clip-interpolation.html": [
        "322ebd724e8524e85d8afe369b6bafaabbec8c79",
        [
@@ -539174,21 +539879,21 @@
       ]
      ],
      "contents-nested-declarations-fallback.html": [
-      "717d60e919a229591df40f7dee030cf0103c2b1d",
+      "78f9974fa6bbd2834a123f693765a5743636f30d",
       [
        null,
        {}
       ]
      ],
      "contents-nested-declarations.html": [
-      "d22466fd79f5c57c271f61a3ff47cd6ae183a830",
+      "1d12e3f631fe825d8aa28d6c13320a1d0a4357c1",
       [
        null,
        {}
       ]
      ],
      "contents-rule.html": [
-      "7e11cf14b9cc357b8b65f5ff7ececf604d644a6d",
+      "8bc608abaf93c601902e9b8abc598d07c81e217a",
       [
        null,
        {}
@@ -539370,7 +540075,7 @@
       ]
      ],
      "mixin-cssom.tentative.html": [
-      "01d2dcb4b27d3c35c2637c7f44b94202c6e10643",
+      "f60ab1a52fec029c61d8795c25995acd1e697d7b",
       [
        null,
        {}
@@ -540326,6 +541031,13 @@
        {}
       ]
      ],
+     "hit-test-border-radius-and-perspective.html": [
+      "c4ec39b81c0d0f7f3349995bfdf32d63ea534c34",
+      [
+       null,
+       {}
+      ]
+     ],
      "hit-test-stacking-context-parent-border-radius.html": [
       "7e0820f0b5730c053e0dfb9aa5fb9af3cc3aeac0",
       [
@@ -544179,7 +544891,7 @@
       ]
      ],
      "slotted-parsing.html": [
-      "69eabb5c833101f95dd592902d5ad309685dfa96",
+      "8a5851c598e9155e9824849b061f2ecc35db8494",
       [
        null,
        {}
@@ -547723,14 +548435,14 @@
         ]
        ],
        "from-element-computed.tentative.html": [
-        "bcaa0b6ac01dc05b81d4a54a0af5ee83f3012eea",
+        "3b702d6ae70cf4caa8771a6a594eebe52119eebe",
         [
          null,
          {}
         ]
        ],
        "from-element-valid.tentative.html": [
-        "fe99ccb23d9c279011e15f66cbbe20cd87e31568",
+        "13b0a279c76539bf5d60d7cefa9bd279d574a77b",
         [
          null,
          {}
@@ -548029,21 +548741,28 @@
      ],
      "responsive-iframe": {
       "responsive-iframe-dom-content-loaded.tentative.html": [
-       "590e98064fa06cf122c7142187619a5a7cb9ca07",
+       "93c49712b1805f5be63f3bc5c34dff67d046af67",
+       [
+        null,
+        {}
+       ]
+      ],
+      "responsive-iframe-fallback.tentative.html": [
+       "6c9592a52253804ca261ab29589f5ee9504458c1",
        [
         null,
         {}
        ]
       ],
       "responsive-iframe-icb.tentative.html": [
-       "510852653b626cf0e79d7cb58d6b28a335711152",
+       "e085f42ec31268d936a0ad6756753a51835b384d",
        [
         null,
         {}
        ]
       ],
       "responsive-iframe-logical.tentative.html": [
-       "575f72b9e4cd948198a3afc51366b7176cc89023",
+       "62b1596c10cabbd1a5545a073df8c8b2f8c4708f",
        [
         null,
         {}
@@ -552816,6 +553535,13 @@
         {}
        ]
       ],
+      "perspective-invalid.html": [
+       "12b38606143a1c49e2d36346b789812af56dcad0",
+       [
+        null,
+        {}
+       ]
+      ],
       "perspective-origin-computed.html": [
        "effeb2974e136831a646318e7fabf30cf27183a6",
        [
@@ -552908,7 +553634,7 @@
        ]
       ],
       "transform-invalid.html": [
-       "8985720622c692a93fda9d0277ca188f581f852a",
+       "3891f5e320c3327d5e82ec1659d35aeb1e8be419",
        [
         null,
         {}
@@ -552962,6 +553688,27 @@
         null,
         {}
        ]
+      ],
+      "webkit-perspective-invalid.tentative.html": [
+       "fae8ada8bdaf8807f19a29aebec3d0ec3b196437",
+       [
+        null,
+        {}
+       ]
+      ],
+      "webkit-perspective-valid.tentative.html": [
+       "453462f63260d9d0abd4d30482124cdf8dcb4baa",
+       [
+        null,
+        {}
+       ]
+      ],
+      "webkit-transform-valid.tentative.html": [
+       "9490f0f5ae837ad90b8b359316cbd0f31dd8a0f3",
+       [
+        null,
+        {}
+       ]
       ]
      },
      "preserve-3d-flat-grouping-properties-containing-block.html": [
@@ -558274,7 +559021,7 @@
       ]
      ],
      "random-computed.tentative.html": [
-      "08eb0992dc1b4c4dbadf18a0565bb6ae09fcfdb2",
+      "0e8777d279a5b11085cd5204e28fc056870eb2a0",
       [
        null,
        {
@@ -559783,6 +560530,13 @@
         {}
        ]
       ],
+      "hit-test-vt-overlay.html": [
+       "55957aed819ebdeaab7fddc9013f45985840a58e",
+       [
+        null,
+        {}
+       ]
+      ],
       "hit-testing.html": [
        "0befd56d237024e780a88ac2514b22b445b15d7c",
        [
@@ -559911,7 +560665,7 @@
       ]
      ],
      "view-transition-types-mutable.html": [
-      "86c77d615abf19471bc980f5cf6cf3fb7f9a97c4",
+      "d5f06446d72e373bc704415932c528da7dbaa607",
       [
        null,
        {}
@@ -562323,6 +563077,13 @@
        {}
       ]
      ],
+     "getBoundingClientRect-content-visibility-hidden.html": [
+      "b1ca1c37ceb0466fd2f539c5049d240a91a4f516",
+      [
+       null,
+       {}
+      ]
+     ],
      "getBoundingClientRect-empty-inline-002.html": [
       "05846986454f001c8e2afe49724ba58fe5e14a06",
       [
@@ -562918,6 +563679,13 @@
        {}
       ]
      ],
+     "scrollIntoView-nearest-visible-element.html": [
+      "1387c27f362c828a982ba50f361c8a679892b7ba",
+      [
+       null,
+       {}
+      ]
+     ],
      "scrollIntoView-scrollMargin.html": [
       "85395a203930e6a73bd01fe4f0f7eed1b5cfbe0b",
       [
@@ -563317,6 +564085,59 @@
        ]
       ]
      },
+     "inheritance.html": [
+      "5600025f4eeb57b754a388d2a9381e40247d7823",
+      [
+       null,
+       {}
+      ]
+     ],
+     "text-decoration-fill": {
+      "text-decoration-fill-computed.tentative.svg": [
+       "9a37a9ea1a83509ee7ef1a24c0e98a8366d13142",
+       [
+        null,
+        {}
+       ]
+      ],
+      "text-decoration-fill-invalid.svg": [
+       "cc4af12390c9d89dd109f12a567099dda7849753",
+       [
+        null,
+        {}
+       ]
+      ],
+      "text-decoration-fill-valid.svg": [
+       "d5d3f181c385c5a7e14fa6da01aa0eeafd4d5f42",
+       [
+        null,
+        {}
+       ]
+      ]
+     },
+     "text-decoration-stroke": {
+      "text-decoration-stroke-computed.tentative.svg": [
+       "da7e9509fdf314f6f6b3cb61f6aa2c6c4dcfbd52",
+       [
+        null,
+        {}
+       ]
+      ],
+      "text-decoration-stroke-invalid.svg": [
+       "aa450ccd25cd7d5f1d0f3ddec41c0709a3cab64d",
+       [
+        null,
+        {}
+       ]
+      ],
+      "text-decoration-stroke-valid.svg": [
+       "dd04e315e6ce2e4bdb44a114eb88eef04ebc4fe1",
+       [
+        null,
+        {}
+       ]
+      ]
+     },
      "webkit-text-stroke-computed.html": [
       "0dadfe402a58bf7c480910fe5a2ce6bf427f186f",
       [
@@ -567294,7 +568115,7 @@
       ]
      ],
      "element-mutation.html": [
-      "9ed6fa7d0b2b3f0e41df20240d6e0df0e4839e11",
+      "817965d260a8a3098aef4c83b02f7ee20cf18039",
       [
        null,
        {}
@@ -567314,8 +568135,8 @@
        {}
       ]
      ],
-     "per-global.html": [
-      "3570dcf811dbac33ecf60cd845f5b287f65ea550",
+     "per-document.html": [
+      "50e329ed22f5ca21e449c8b374a5a064456e08ac",
       [
        null,
        {}
@@ -567385,7 +568206,7 @@
       ]
      ],
      "template.window.js": [
-      "7d442a618f846ef30fde5c655e8127028e01beea",
+      "9b908badeaf8abf3aa734badaab068079c165171",
       [
        "custom-elements/registries/template.window.html",
        {}
@@ -567930,7 +568751,7 @@
    },
    "digital-credentials": {
     "allow-attribute-with-create.https.html": [
-     "4b1e96fec4cb62d336d3c1358a7c9781e6657f8d",
+     "c921f57923afa37f6fac22b845333b642da7c383",
      [
       null,
       {
@@ -567939,7 +568760,7 @@
      ]
     ],
     "allow-attribute-with-get.https.html": [
-     "58873ddff792b51692a6ff988b0e956b0c647605",
+     "59fd851755a9b7509d5a31bea9e74eb55c22be08",
      [
       null,
       {
@@ -567957,7 +568778,7 @@
      ]
     ],
     "create.tentative.https.html": [
-     "97dc04e6077548e218690aca834dce0025682ca9",
+     "043b59fa448d1b98a6e9966640853a682c5383db",
      [
       null,
       {
@@ -568007,7 +568828,7 @@
      ]
     ],
     "get.tentative.https.html": [
-     "1549bba673b2f4ea86d48ef670155212823bff42",
+     "e5d241d4f39e59ff1bb0ad30c636601362576f26",
      [
       null,
       {
@@ -568027,7 +568848,7 @@
      ]
     ],
     "non-fully-active.https.html": [
-     "61f40d1b0d3144b2fd470e4b0e297f7a17f02031",
+     "e1fdca39e59f9d7545fb4230ce5a45667beccf65",
      [
       null,
       {
@@ -568036,7 +568857,7 @@
      ]
     ],
     "user-activation.https.html": [
-     "1189c32252ae1a1c52fa2d75d11f4ec00b45d57a",
+     "476c9bfafd5dc83f52192dff5a7191783c724a49",
      [
       null,
       {
@@ -572358,7 +573179,7 @@
        ]
       ],
       "insertion-removing-steps-iframe.window.js": [
-       "60c2bec0c8aa213b1bfa1d75c99ca1745cb9871c",
+       "16a5021aad2b6a1b75ad87024171b9e1961b72ea",
        [
         "dom/nodes/insertion-removing-steps/insertion-removing-steps-iframe.window.html",
         {}
@@ -573651,6 +574472,13 @@
       {}
      ]
     ],
+    "DOMParser-parseFromString-stylesheets.html": [
+     "e30926af6764f4341b695c6244b39e2d66eaca21",
+     [
+      null,
+      {}
+     ]
+    ],
     "DOMParser-parseFromString-url-base-pushstate.html": [
      "41d7344a64de17a48d3cd5e26c309ef4eb1d260b",
      [
@@ -591698,6 +592526,20 @@
       {}
      ]
     ],
+    "clearkey-mp4-playback-temporary-multikey-missing-saio.https.html": [
+     "fc1e3c0f384f7b23b6268bda751c8282b885db6e",
+     [
+      null,
+      {}
+     ]
+    ],
+    "clearkey-mp4-playback-temporary-multikey-missing-senc.https.html": [
+     "519f30b29da336757cfc5e15f0b641ba712b556f",
+     [
+      null,
+      {}
+     ]
+    ],
     "clearkey-mp4-playback-temporary-multikey-sequential-readyState.https.html": [
      "ff5bb4763999688180ec703e9997255330f284ef",
      [
@@ -595635,7 +596477,7 @@
      ]
     ],
     "client-hints-meta.https.html": [
-     "3a4acf1b261d687304b197b52183786d2f009747",
+     "7a370f452c4aa2660b86e33253db2c2ed2ff79de",
      [
       null,
       {
@@ -595644,7 +596486,7 @@
      ]
     ],
     "client-hints.https.html": [
-     "fc7b8db08de58a2d0c5a024e8438b10dbbcde59f",
+     "10ba6225b34ab2dfe053687164d9058ad92e9f3e",
      [
       null,
       {
@@ -631251,8 +632093,8 @@
          {}
         ]
        ],
-       "010.html": [
-        "606ad82f48f340a74b75aa85cfc4f788baeb09d3",
+       "010.tentative.html": [
+        "f6c2189fd269331f1bf351b5e4b4011130284f41",
         [
          null,
          {}
@@ -631294,7 +632136,7 @@
         ]
        ],
        "abort-document-load.html": [
-        "4a4c3df4e75676ee2fe0e1daa435d5ac0e4cb416",
+        "be66f7b44cf8ca3bf13be2c7943710b9f835d8a2",
         [
          null,
          {}
@@ -631479,7 +632321,7 @@
          ]
         ],
         "iframe-src-204.html": [
-         "f7bade68fbd00c005b7f1f3e6a59a94113ca5933",
+         "a3bf429dac3d51675c3a83fa9765444de0c6e8e5",
          [
           null,
           {}
@@ -631507,7 +632349,7 @@
          ]
         ],
         "load-event-iframe-element.html": [
-         "0d19770cc1eb8cc97cf5ece953428f5bebb72fbd",
+         "b17528d1e1bf37ab57beea1be93a6f29dddd5665",
          [
           null,
           {}
@@ -634068,7 +634910,7 @@
         ]
        ],
        "joint-session-history-remove-iframe.html": [
-        "ee7aa368aff425dfffee66e97b8689631bd34be2",
+        "aa59e46e92e2f020abeee07f8be6eebcdcec8e53",
         [
          null,
          {
@@ -634612,14 +635454,14 @@
         ]
        ],
        "assign_after_load.html": [
-        "00dc931d4e7204ef302c7ab8d7340ec19feee61d",
+        "9ba0e5d7b9e8735adede50e7a1568936236556c8",
         [
          null,
          {}
         ]
        ],
        "assign_before_load.html": [
-        "62a2aa7c60df02707e8b423c28ddc657aed1f1ad",
+        "777651206207c93ab97adb8bb56a0ccc01b4245c",
         [
          null,
          {}
@@ -636441,7 +637283,7 @@
          ]
         ],
         "origin-comparison.any.js": [
-         "f03532b99b4fd7c9dae706e2b46e1faa7f316b10",
+         "06fe9befaf811cbd58ba3f52c5720793364f03c0",
          [
           "html/browsers/origin/tentative/api/origin-comparison.any.html",
           {
@@ -636585,7 +637427,7 @@
          ]
         ],
         "origin-from-location.window.js": [
-         "9c42d45cf7551a13d3b04d861f1735baa2cca10d",
+         "2469ff403ef1de798f3f5ada2d000534217ef508",
          [
           "html/browsers/origin/tentative/api/origin-from-location.window.html",
           {
@@ -636787,56 +637629,6 @@
            ]
           }
          ]
-        ],
-        "origin-fromURL.any.js": [
-         "ff55d00243e6f2099ae3fcfc48070094660af4bf",
-         [
-          "html/browsers/origin/tentative/api/origin-fromURL.any.html",
-          {
-           "script_metadata": [
-            [
-             "title",
-             "`Origin.fromURL()`"
-            ]
-           ]
-          }
-         ],
-         [
-          "html/browsers/origin/tentative/api/origin-fromURL.any.worker.html",
-          {
-           "script_metadata": [
-            [
-             "title",
-             "`Origin.fromURL()`"
-            ]
-           ]
-          }
-         ]
-        ],
-        "origin.any.js": [
-         "ec5b56d25726225ff9ce5d4aa7c2c1204e5c1ca5",
-         [
-          "html/browsers/origin/tentative/api/origin.any.html",
-          {
-           "script_metadata": [
-            [
-             "title",
-             "`Origin` Construction and Parsing"
-            ]
-           ]
-          }
-         ],
-         [
-          "html/browsers/origin/tentative/api/origin.any.worker.html",
-          {
-           "script_metadata": [
-            [
-             "title",
-             "`Origin` Construction and Parsing"
-            ]
-           ]
-          }
-         ]
         ]
        }
       }
@@ -636971,7 +637763,7 @@
        ]
       ],
       "sandbox-window-open-srcdoc.html": [
-       "6fbff6df82f57fce61f4d1affe5a965cb45f5e60",
+       "e882b890efec3814888c5aea37f00787b2a9de1d",
        [
         null,
         {}
@@ -637515,6 +638307,13 @@
          }
         ]
        ],
+       "open_fires_resize.tentative.html": [
+        "aec27ea9db6e9e3c2217dc083e816b803f7d9e8e",
+        [
+         null,
+         {}
+        ]
+       ],
        "open_initial_size.html": [
         "a3a82d7646891edc92008073c1bb66fab7d36fa6",
         [
@@ -637678,7 +638477,14 @@
        ]
       ],
       "window-reuse-in-nested-browsing-contexts.tentative.html": [
-       "03c7a68dfd3f113ad3ef3e1902bc76e188938232",
+       "3076e60f8457f45ec8ddbdf86be29be585492358",
+       [
+        null,
+        {}
+       ]
+      ],
+      "window-reuse.tentative.html": [
+       "a5e3b503bbf67e2ab38f1770e019afcfd87643f1",
        [
         null,
         {}
@@ -637947,7 +638753,7 @@
        ]
       ],
       "clear-window-name.https.html": [
-       "698de8a1cafd7911667f5a209ffa887a0ee5aea2",
+       "104b00eb5477014ad194b08142b3a655b866a550",
        [
         null,
         {
@@ -638099,7 +638905,7 @@
        ]
       ],
       "noreferrer-window-name.html": [
-       "5fe649d172ea493cfda41d38194d96be6f4dffb9",
+       "5b21372166c219164309cf45619102283dc2b832",
        [
         null,
         {
@@ -641829,14 +642635,14 @@
         ]
        ],
        "2d.layer.malformed-operations-with-promises.html": [
-        "d1b51718012bbe65a6b11d813cd2180fe9bcd48d",
+        "6dfaed6c5c8963184ae6a4b48d844d505bb30e73",
         [
          null,
          {}
         ]
        ],
        "2d.layer.malformed-operations.html": [
-        "7baf600bf8a90575e32bf7e571b2490abd2e18ab",
+        "a512cc4307030970658378a42198ef554b5053c5",
         [
          null,
          {}
@@ -645734,14 +646540,14 @@
         ]
        ],
        "2d.text.measure.getActualBoundingBox-full-text.tentative.html": [
-        "18095ac166df89f4f621f3d58d9ad49882367fa6",
+        "4ecc8d764f78a8893bbaba2f5aae53bb94627438",
         [
          null,
          {}
         ]
        ],
        "2d.text.measure.getActualBoundingBox.tentative.html": [
-        "ceb1185168d1a17a4d369047a5d7adc946fb8a04",
+        "cd3c74db692c970bdd4419e38268467c64a6b8f5",
         [
          null,
          {}
@@ -645755,14 +646561,14 @@
         ]
        ],
        "2d.text.measure.index-from-offset-edges.tentative.html": [
-        "842f50857cca530b130dd85f9e5a57204113875f",
+        "4a0c3c5c5b711dff3f91745ed51bb1f9ede26feb",
         [
          null,
          {}
         ]
        ],
        "2d.text.measure.index-from-offset.tentative.html": [
-        "9893fcdc4c293dbbdcac9795beb1e18b8f433bfc",
+        "bfc305ea1d3e43310b86c123f7a4034d36d5b795",
         [
          null,
          {}
@@ -645797,7 +646603,7 @@
         ]
        ],
        "2d.text.measure.selection-rects.tentative.html": [
-        "ee9ad76cdcbd0413e627cab5ab5d3270b5972148",
+        "9e5c6c0f72b5e08ed7f61fb3433e02d1f244f6f6",
         [
          null,
          {}
@@ -645904,56 +646710,56 @@
         ]
        ],
        "2d.state.saverestore.fillStyle.html": [
-        "f0aff2b1737a2af0594681347eaea2c1ed0f0a8e",
+        "1bd686ea4e07079c9caf43907d5c180b28f7acc7",
         [
          null,
          {}
         ]
        ],
        "2d.state.saverestore.font.html": [
-        "0db37aec90216505ea50158167a0ce25412e4fe6",
+        "787b8f2ce2bd0e3cdba9baeab58c11cd35b2aa0f",
         [
          null,
          {}
         ]
        ],
        "2d.state.saverestore.globalAlpha.html": [
-        "b5436748ec71211f8485fdf064173dcc911326d9",
+        "f743a0432faac3e66bc0d97b613a22ebd9bd3372",
         [
          null,
          {}
         ]
        ],
        "2d.state.saverestore.globalCompositeOperation.html": [
-        "16c337a10ba66d981ad5dbd894546e286ccd1851",
+        "73e50a8a64b2df5cd15599b9534e30fa48419d39",
         [
          null,
          {}
         ]
        ],
        "2d.state.saverestore.lineCap.html": [
-        "68afa38fea6a876083a2aafd9f949ee8e9942fb0",
+        "94a56c590a686e9592e66839f96c20f5d1046076",
         [
          null,
          {}
         ]
        ],
        "2d.state.saverestore.lineJoin.html": [
-        "bf2392d1027c247da9f1e0894be33dfa65027d9b",
+        "fececcf1d5d6ece864552f5e4b0e5da81bf590b3",
         [
          null,
          {}
         ]
        ],
        "2d.state.saverestore.lineWidth.html": [
-        "63d8fcda0f433077ef21762b6046a866481bca1d",
+        "f1949acc7b39495effa2bc828b2bc64789221efb",
         [
          null,
          {}
         ]
        ],
        "2d.state.saverestore.miterLimit.html": [
-        "aaff2f0ae739bb24699b8eefc574f843e4db80e2",
+        "e3e53bf453f2a3e6c21b2b895d49562867834c03",
         [
          null,
          {}
@@ -645967,28 +646773,28 @@
         ]
        ],
        "2d.state.saverestore.shadowBlur.html": [
-        "7944697e90fbba6a5ba19dd5cd6622578c778c54",
+        "32ebe729155803d3430a8859f783cb13f90ca2a0",
         [
          null,
          {}
         ]
        ],
        "2d.state.saverestore.shadowColor.html": [
-        "f48658ef0a20251e704c433c68dbaf5a48033e04",
+        "7b1e3cc89ca1e5bd4b61d46ed59b7d9a1ef96d69",
         [
          null,
          {}
         ]
        ],
        "2d.state.saverestore.shadowOffsetX.html": [
-        "a97d08c118e8e6003cb789fc6ba4f18de90a431c",
+        "266906e368f1b9ca997a2388fdfb161894025b94",
         [
          null,
          {}
         ]
        ],
        "2d.state.saverestore.shadowOffsetY.html": [
-        "2c78e266b961497b9eac503c5c60c9bc8e10115c",
+        "3abc969f15e483cd8923d2a86293facbdeb82cf3",
         [
          null,
          {}
@@ -646009,21 +646815,21 @@
         ]
        ],
        "2d.state.saverestore.strokeStyle.html": [
-        "a8e6390c710de6e30756ff4a7255baa8fab4c9df",
+        "ff6bdedc4e0401604ccb447a05d8ee0d5085ac6e",
         [
          null,
          {}
         ]
        ],
        "2d.state.saverestore.textAlign.html": [
-        "0752ab6d4adff8638df0541b1bb1970109eb4494",
+        "296d54f0be556e40fec726362eae4fd23aa5eb23",
         [
          null,
          {}
         ]
        ],
        "2d.state.saverestore.textBaseline.html": [
-        "6863086cd907184d866526ac111de9bfb8dd15e2",
+        "e49a8b5efe23983e15ae85bd80227854044181bf",
         [
          null,
          {}
@@ -646210,6 +647016,13 @@
        ]
       },
       "wide-gamut-canvas": {
+       "2d.color.space.display-p3-linear.html": [
+        "786e0cdba58f03d84207bc0fddbfc992794fe27a",
+        [
+         null,
+         {}
+        ]
+       ],
        "2d.color.space.p3.fillText.html": [
         "5e18ec2a4c9c021dbc9219927dd39a3c59c2d263",
         [
@@ -646279,6 +647092,13 @@
          null,
          {}
         ]
+       ],
+       "2d.color.space.srgb-linear.html": [
+        "a2d278fbeef28931a730064d12ff4dba7862b263",
+        [
+         null,
+         {}
+        ]
        ]
       }
      },
@@ -660130,98 +660950,98 @@
         ]
        ],
        "2d.state.saverestore.fillStyle.html": [
-        "3653ca119a0ce524f49ba813f0ed6e2744ad015e",
+        "7540678a6649c36bc5a3197a6ba4bc8b9ea799f3",
         [
          null,
          {}
         ]
        ],
        "2d.state.saverestore.fillStyle.worker.js": [
-        "46cf6d1daa06a2717f2e5d40d278413b8d9968ab",
+        "e7cbf3346ca3605a9c29461c299410c92e6e2af1",
         [
          "html/canvas/offscreen/the-canvas-state/2d.state.saverestore.fillStyle.worker.html",
          {}
         ]
        ],
        "2d.state.saverestore.globalAlpha.html": [
-        "25606a9b65f7ec97d26a44118ef763dfef2ec358",
+        "893799c56a4e8a4ebdb75d82dfca303fccad7165",
         [
          null,
          {}
         ]
        ],
        "2d.state.saverestore.globalAlpha.worker.js": [
-        "765f8b00a31d03f142518a684e8d5be99a200ca9",
+        "f1b6731ecc1923e6b9420c66f3a61de5fc9c310e",
         [
          "html/canvas/offscreen/the-canvas-state/2d.state.saverestore.globalAlpha.worker.html",
          {}
         ]
        ],
        "2d.state.saverestore.globalCompositeOperation.html": [
-        "c2c6c8fc575d2c741f5b1ce8dbe5bb41297f3327",
+        "045f70ecb98b178e15f680f900abb7544d0cf8b4",
         [
          null,
          {}
         ]
        ],
        "2d.state.saverestore.globalCompositeOperation.worker.js": [
-        "cd38d56c51f964754bd27f3e14e65020d4e2bb20",
+        "09d60a272d1726ad93e0d37045f8552a6321cbfe",
         [
          "html/canvas/offscreen/the-canvas-state/2d.state.saverestore.globalCompositeOperation.worker.html",
          {}
         ]
        ],
        "2d.state.saverestore.lineCap.html": [
-        "9c1552f4d42ff5c5bf65c500caea58aefcaf3b9a",
+        "bf25667eb2381b56b36add95a9b08aed6c0f915b",
         [
          null,
          {}
         ]
        ],
        "2d.state.saverestore.lineCap.worker.js": [
-        "9895b44b0030a3b5e61707fa60a0de4dbcbaae02",
+        "60815127af01e2c64742e0c07c43edcd6faffa8e",
         [
          "html/canvas/offscreen/the-canvas-state/2d.state.saverestore.lineCap.worker.html",
          {}
         ]
        ],
        "2d.state.saverestore.lineJoin.html": [
-        "3f17b67267db5b09e84faabca028aba96916be0d",
+        "a0aa6102e958caffe29ea988e16e03eb95318a1b",
         [
          null,
          {}
         ]
        ],
        "2d.state.saverestore.lineJoin.worker.js": [
-        "e8c85825f1743eebe20dd22b3497344932324613",
+        "ae6f1e47b446ae71ecadbdc562c51f9172492247",
         [
          "html/canvas/offscreen/the-canvas-state/2d.state.saverestore.lineJoin.worker.html",
          {}
         ]
        ],
        "2d.state.saverestore.lineWidth.html": [
-        "94f5752792c5edb081b3d5288a447532ff4e333f",
+        "f6a18a17e3015153439baa7864d293f0781dfb23",
         [
          null,
          {}
         ]
        ],
        "2d.state.saverestore.lineWidth.worker.js": [
-        "997748c37741dca38a767609f0019acf698a962b",
+        "5a7ed6ad22425c412dc74b9c04aa724dafa9c521",
         [
          "html/canvas/offscreen/the-canvas-state/2d.state.saverestore.lineWidth.worker.html",
          {}
         ]
        ],
        "2d.state.saverestore.miterLimit.html": [
-        "57b69bbad65d11842d77438724470016b5bb965f",
+        "3703e345f7713f63e06ac3b861c7e87be545ed66",
         [
          null,
          {}
         ]
        ],
        "2d.state.saverestore.miterLimit.worker.js": [
-        "eb5adffe06a3cac79daf3669d20820bbc25a30d9",
+        "39c9e423b57bd1671c210b3bc68821cfa6ac968a",
         [
          "html/canvas/offscreen/the-canvas-state/2d.state.saverestore.miterLimit.worker.html",
          {}
@@ -660242,56 +661062,56 @@
         ]
        ],
        "2d.state.saverestore.shadowBlur.html": [
-        "36710ef38cc4b082f7a64eee451a6c55110ddcc2",
+        "d753e4eb1d0657683b599ecfe6b990fe49caffd5",
         [
          null,
          {}
         ]
        ],
        "2d.state.saverestore.shadowBlur.worker.js": [
-        "00cd6f8317b41b869199546dfcc19c52fb9eba7e",
+        "d6eb35344727081678b14cd6ebc4b75be6f837c3",
         [
          "html/canvas/offscreen/the-canvas-state/2d.state.saverestore.shadowBlur.worker.html",
          {}
         ]
        ],
        "2d.state.saverestore.shadowColor.html": [
-        "ce156b67cd30799e90f1a095d18023c4b83f82c8",
+        "4c9e9454d44a24d8919350068a23227526af0ce6",
         [
          null,
          {}
         ]
        ],
        "2d.state.saverestore.shadowColor.worker.js": [
-        "13c033f3968cc2eead4a41fbaa3886f735041a42",
+        "b8d3460366cb06b482224fdeefc45151ec25842a",
         [
          "html/canvas/offscreen/the-canvas-state/2d.state.saverestore.shadowColor.worker.html",
          {}
         ]
        ],
        "2d.state.saverestore.shadowOffsetX.html": [
-        "641ddc9786ed689f42883d9fd9bcfa48ea76fe04",
+        "3b778c298b38c13110a89db770c0de6191fdcab3",
         [
          null,
          {}
         ]
        ],
        "2d.state.saverestore.shadowOffsetX.worker.js": [
-        "a6ad9226474aaa5d5c343e4c1fd3c66b3702d232",
+        "749a8f9ecaee207ba82ca195a090d97e7eedc9d2",
         [
          "html/canvas/offscreen/the-canvas-state/2d.state.saverestore.shadowOffsetX.worker.html",
          {}
         ]
        ],
        "2d.state.saverestore.shadowOffsetY.html": [
-        "57eb4efcc9031a46cee28aee576313316d5eb0fa",
+        "e0dc0c66e4fda2c547f8e56178889f4a8c6ee945",
         [
          null,
          {}
         ]
        ],
        "2d.state.saverestore.shadowOffsetY.worker.js": [
-        "323fcd14349c6f17c5080350882c2746ed4cd833",
+        "23cfe4767b81f65a578b429678fd184165ab8f38",
         [
          "html/canvas/offscreen/the-canvas-state/2d.state.saverestore.shadowOffsetY.worker.html",
          {}
@@ -660326,14 +661146,14 @@
         ]
        ],
        "2d.state.saverestore.strokeStyle.html": [
-        "770ad51fdbb8e753c6aca0f8bc6b08c0480723ac",
+        "d5d0b22fc17096893a4c8937f645dae0a3eab219",
         [
          null,
          {}
         ]
        ],
        "2d.state.saverestore.strokeStyle.worker.js": [
-        "878a6d68cb5606611b6ab6bab711c8572ce3bde5",
+        "57199178e2fd09134ecba03b888534c6684fa46d",
         [
          "html/canvas/offscreen/the-canvas-state/2d.state.saverestore.strokeStyle.worker.html",
          {}
@@ -660679,6 +661499,20 @@
        ]
       },
       "wide-gamut-canvas": {
+       "2d.color.space.display-p3-linear.html": [
+        "99f8233c66b0c50a929fc34c6d4572caf22b1c62",
+        [
+         null,
+         {}
+        ]
+       ],
+       "2d.color.space.display-p3-linear.worker.js": [
+        "f8e6e82fd25ead03e13d2b53f4e02f4c78c5fe5e",
+        [
+         "html/canvas/offscreen/wide-gamut-canvas/2d.color.space.display-p3-linear.worker.html",
+         {}
+        ]
+       ],
        "2d.color.space.p3.to.p3.html": [
         "b05056939bb81fc2c74422d9cf892b6d07e155f8",
         [
@@ -660706,6 +661540,20 @@
          "html/canvas/offscreen/wide-gamut-canvas/2d.color.space.p3.to.srgb.worker.html",
          {}
         ]
+       ],
+       "2d.color.space.srgb-linear.html": [
+        "b5406a6e0958e197cb4e1960b7418f0230914af0",
+        [
+         null,
+         {}
+        ]
+       ],
+       "2d.color.space.srgb-linear.worker.js": [
+        "b5fc709783ef220a9e7fab4dc01fbe23b5d3e109",
+        [
+         "html/canvas/offscreen/wide-gamut-canvas/2d.color.space.srgb-linear.worker.html",
+         {}
+        ]
        ]
       }
      }
@@ -669547,10 +670395,17 @@
      },
      "urls": {
       "base-url": {
-       "base-url-detached-document.https.window.js": [
-        "c51f425b7ac6505fec249c4ae90fb99bde7e54ea",
+       "base-url-detached-document-blank.https.window.js": [
+        "dadcd48a6e4f9f247f180e06b5069ffbf88fdd8d",
         [
-         "html/infrastructure/urls/base-url/base-url-detached-document.https.window.html",
+         "html/infrastructure/urls/base-url/base-url-detached-document-blank.https.window.html",
+         {}
+        ]
+       ],
+       "base-url-detached-document-srcdoc.https.window.js": [
+        "17bf413bd21246cc9a80710ee81cc2422506a288",
+        [
+         "html/infrastructure/urls/base-url/base-url-detached-document-srcdoc.https.window.html",
          {}
         ]
        ],
@@ -673480,6 +674335,13 @@
          {}
         ]
        ],
+       "event_order_durationchange_resize_loadedmetadata.html": [
+        "52fa104ce8ab5ebbf17407370c69988ab3c53eca",
+        [
+         null,
+         {}
+        ]
+       ],
        "event_order_loadedmetadata_loadeddata.html": [
         "71aeca50c137c610900c289d801c0dd28fc77bdc",
         [
@@ -676180,7 +677042,7 @@
         ]
        ],
        "iframe_javascript_url_xhr.html": [
-        "107862dd422db8c1cf968481cf194428e2ae44d3",
+        "fd07bef5d0423986fedc252b05cd836af28a73e7",
         [
          null,
          {}
@@ -681198,7 +682060,7 @@
         ]
        ],
        "wrapping-transformation.window.js": [
-        "c5c28a4854bb0690831f2be3a12de5cfad4c909f",
+        "70e0c3ce9fecbae6ca2f7a1c3b70c82ddbea9b4f",
         [
          "html/semantics/forms/the-textarea-element/wrapping-transformation.window.html",
          {}
@@ -682768,7 +683630,41 @@
         null,
         {}
        ]
-      ]
+      ],
+      "usermedia": {
+       "invalid-css-properties.tentative.html": [
+        "a850c2cc5a6f6922d6c17cdfc2a2227ce0ccb421",
+        [
+         null,
+         {}
+        ]
+       ],
+       "lang-attribute-update.html": [
+        "21521e3b04d0dda25363a8cb90e40760e232c9f5",
+        [
+         null,
+         {
+          "testdriver": true
+         }
+        ]
+       ],
+       "no-children-rendered.tentative.html": [
+        "694232922fca6c9d478566c0eeb6c934ceb4b135",
+        [
+         null,
+         {}
+        ]
+       ],
+       "no-focus.tentative.html": [
+        "5de16474ac78f5bbb19762334a2c3b6137674916",
+        [
+         null,
+         {
+          "testdriver": true
+         }
+        ]
+       ]
+      }
      },
      "popovers": {
       "button-type-popovertarget.html": [
@@ -694495,7 +695391,7 @@
     },
     "data-driven": {
      "resolving.html": [
-      "bcf3d1de7e028923c0b7f06770522eb2dec174d7",
+      "d5098feed14a9c5f4ab3470ad7a6bbd841488be3",
       [
        "import-maps/data-driven/resolving.html?data-url-prefix.json",
        {}
@@ -694505,6 +695401,10 @@
        {}
       ],
       [
+       "import-maps/data-driven/resolving.html?empty-scopes.json",
+       {}
+      ],
+      [
        "import-maps/data-driven/resolving.html?overlapping-entries.json",
        {}
       ],
@@ -694627,6 +695527,20 @@
        {}
       ]
      ],
+     "scope-ordering-reverse.html": [
+      "07a0bd63234f576d7d3960c113bcbff3052d4810",
+      [
+       null,
+       {}
+      ]
+     ],
+     "scope-ordering.html": [
+      "a1d3b09b1aafae893a2d7af1d9ea9a6987ef132c",
+      [
+       null,
+       {}
+      ]
+     ],
      "url-resolution-conflict.html": [
       "e8d06bdf52489712912195feac9a950561ae04e5",
       [
@@ -706396,7 +707310,7 @@
      ]
     ],
     "RTCRtpSendParameters-degradationPreference.html": [
-     "3573bb58760210611cd13f0d7d0fbd2aa6682bd7",
+     "1e93fdedfddaca4b5855913b0f82da978ec61508",
      [
       null,
       {}
@@ -710144,6 +711058,32 @@
       {}
      ]
     ],
+    "icon-fetch.tentative.https.window.js": [
+     "b04df9d44acfd3a7f613c76e81f1af903f0125bf",
+     [
+      "notifications/icon-fetch.tentative.https.window.html",
+      {
+       "script_metadata": [
+        [
+         "script",
+         "/resources/testdriver.js"
+        ],
+        [
+         "script",
+         "/resources/testdriver-vendor.js"
+        ],
+        [
+         "script",
+         "/service-workers/service-worker/resources/test-helpers.sub.js"
+        ],
+        [
+         "script",
+         "resources/helpers.js"
+        ]
+       ]
+      }
+     ]
+    ],
     "icon-url-encoding-euc-kr.tentative.https.html": [
      "93aeac0721d869fe07e7116626dd66e1ba393205",
      [
@@ -731058,7 +731998,7 @@
      ]
     ],
     "crashReport-test.html": [
-     "520c4ddf376b6d1164fc171f1aa0c259bc5627e2",
+     "08ee5fa813a2df03cb899cd2f80a7bd89f4050be",
      [
       null,
       {}
@@ -732719,7 +733659,7 @@
      ]
     ],
     "sanitizer-basic-filtering.tentative.html": [
-     "2e61a9ab9b40537a0d4edb0c952f03c9c3382b7b",
+     "8a1b413e9a0cba65c4a8ac5b92623dd80458f895",
      [
       null,
       {}
@@ -735111,7 +736051,7 @@
       ]
      ],
      "animation-trigger-repeat.tentative.html": [
-      "81bedc839e232259fac4a82f912f3077a64c7fa4",
+      "907595a7666e9e9910b9e4d39e7c424ca53a42e1",
       [
        null,
        {}
@@ -735167,6 +736107,13 @@
         {}
        ]
       ],
+      "timeline-trigger-exit-range-shorthand.tentative.html": [
+       "9b237bce484588eaec774f0653576c8b7e214e3e",
+       [
+        null,
+        {}
+       ]
+      ],
       "timeline-trigger-exit-range-start-computed.tentative.html": [
        "fd6878a2fd206b22250445d46ddfb7be7ba61577",
        [
@@ -735216,6 +736163,13 @@
         {}
        ]
       ],
+      "timeline-trigger-range-shorthand.tentative.html": [
+       "eb1da4363dfb1d14145b7e99d5cf1cd08ebd9e87",
+       [
+        null,
+        {}
+       ]
+      ],
       "timeline-trigger-range-start-computed.tentative.html": [
        "7727f0f0a0b2b9fd058f2bfcca2ca16e043b9671",
        [
@@ -735238,7 +736192,7 @@
        ]
       ],
       "timeline-trigger-shorthand.tentative.html": [
-       "026dfc1bfffd4a43e5d319eee7ff6e5033956ac0",
+       "5e516e7fb677e447b60e9bb41b8584f1f25b6761",
        [
         null,
         {}
@@ -747188,7 +748142,7 @@
       ]
      ],
      "response-code-non-successful.https.html": [
-      "68cd505b2ba56f987ec3918acd2d439fce8c1dab",
+      "3b6e01e9ea1aff2a191e0e9d60a31bb27d0e3113",
       [
        "speculation-rules/prerender/response-code-non-successful.https.html?code=204",
        {
@@ -764159,6 +765113,57 @@
      ]
     },
     "geometry": {
+     "animations": {
+      "cx-composition.html": [
+       "4c7ff99097e87d095ea1693dc0bf10feb8461349",
+       [
+        null,
+        {}
+       ]
+      ],
+      "cy-composition.html": [
+       "4dfd8bd8a79a9b2b2b2779e7dc9090d7efbc98b0",
+       [
+        null,
+        {}
+       ]
+      ],
+      "r-composition.html": [
+       "6d10d2c3b6b1b0d072881ed82ddc77c21c3d8533",
+       [
+        null,
+        {}
+       ]
+      ],
+      "rx-composition.html": [
+       "f3ccb64d1a291a03db4cc46a68f79e0859db0a8b",
+       [
+        null,
+        {}
+       ]
+      ],
+      "ry-composition.html": [
+       "600a75ab42cf9cbf63eeca2b565bd8f9b45f80b5",
+       [
+        null,
+        {}
+       ]
+      ],
+      "x-composition.html": [
+       "2e1b1991c8d0759e1867c5994756fb9c9310ea39",
+       [
+        null,
+        {}
+       ]
+      ],
+      "y-composition.html": [
+       "0d0e138f66a9bb9eef094e24cd73f761e7139ce6",
+       [
+        null,
+        {}
+       ]
+      ]
+     },
      "inheritance.svg": [
       "760328ac31818d86a9410fd7c412d803e30416a2",
       [
@@ -765669,7 +766674,7 @@
     },
     "text": {
      "inheritance.svg": [
-      "1f4609d7ac6c5384f68d109733be01c7ea915df4",
+      "bddc369854d283150ef268892e71e8e9b9721d36",
       [
        null,
        {}
@@ -765760,27 +766765,6 @@
         {}
        ]
       ],
-      "text-decoration-fill-computed.tentative.svg": [
-       "9a37a9ea1a83509ee7ef1a24c0e98a8366d13142",
-       [
-        null,
-        {}
-       ]
-      ],
-      "text-decoration-fill-invalid.svg": [
-       "19da0821730d5062e006e45eadb3cd3726318302",
-       [
-        null,
-        {}
-       ]
-      ],
-      "text-decoration-fill-valid.svg": [
-       "d2f9447a169d140bacd301d235707c02b30ba81d",
-       [
-        null,
-        {}
-       ]
-      ],
       "text-decoration-line-computed.svg": [
        "d7ea07662f7d3235ee19e6b88752056615e98f19",
        [
@@ -765788,27 +766772,6 @@
         {}
        ]
       ],
-      "text-decoration-stroke-computed.tentative.svg": [
-       "da7e9509fdf314f6f6b3cb61f6aa2c6c4dcfbd52",
-       [
-        null,
-        {}
-       ]
-      ],
-      "text-decoration-stroke-invalid.svg": [
-       "8d44d6f1c33898fca4a81924ba0d3e05cd3ed7d4",
-       [
-        null,
-        {}
-       ]
-      ],
-      "text-decoration-stroke-valid.svg": [
-       "df64cc9ec3d387f26238b66b92c9f356f2f23e09",
-       [
-        null,
-        {}
-       ]
-      ],
       "text-decoration-style-computed.svg": [
        "6f307ff0dca3ea1f2702249b7d148c83dfe4aafa",
        [
@@ -765833,7 +766796,7 @@
        ]
       ],
       "getextentofchar.html": [
-       "1a6bb32b4bf07f22e7e3b44815313882fd8a88c7",
+       "c37905268c3a091b3188531c8e0af3e580bf411a",
        [
         null,
         {}
@@ -768364,7 +769327,7 @@
      ]
     ],
     "useragentdata.https.any.js": [
-     "d94cd716b0fa3943ea98ec5a1a040b13124e6181",
+     "0a4f4a455486f7a46b2547ca2bb20d1a06649da4",
      [
       "ua-client-hints/useragentdata.https.any.html",
       {
@@ -788690,7 +789653,7 @@
        ]
       ],
       "audiocontext-playoutstats.html": [
-       "ef0468122ce3192ae87ec52af2317590b89f53a2",
+       "9096d185ad0f7cfd3b95a75bc6dbaf7029c31e21",
        [
         null,
         {}
@@ -789950,6 +790913,13 @@
       ]
      },
      "the-mediastreamaudiodestinationnode-interface": {
+      "closed-audiocontext-construction.html": [
+       "23bc9dc81b18bf0adb1b9d6f766029fb05d09b44",
+       [
+        null,
+        {}
+       ]
+      ],
       "ctor-mediastreamaudiodestination.html": [
        "7d67c1e6897c43090cd460f344ebed591f4ffe7a",
        [
@@ -803549,7 +804519,7 @@
       ]
      ],
      "cos.https.any.js": [
-      "57e7d9904eff21e1e68008e37bcdc916adc6e3b3",
+      "8f65703c2e11b5cf00461449ce40e7debf07e19c",
       [
        "webnn/conformance_tests/cos.https.any.html?cpu",
        {
@@ -803771,7 +804741,7 @@
       ]
      ],
      "dequantizeLinear.https.any.js": [
-      "d9cec91e2492e0f79b01cf650ea3e8266b2895fb",
+      "4c9dd7a21d42739dc82d14fd584dd61836ceea80",
       [
        "webnn/conformance_tests/dequantizeLinear.https.any.html?cpu",
        {
@@ -807416,7 +808386,7 @@
       ]
      ],
      "layer_normalization.https.any.js": [
-      "7ac8139a88beeb83f793dd39542cfb7391d9d96b",
+      "3588afd678bd8182f0864034b2b2b924932289fb",
       [
        "webnn/conformance_tests/layer_normalization.https.any.html?cpu",
        {
@@ -810839,7 +811809,7 @@
       ]
      ],
      "quantizeLinear.https.any.js": [
-      "3f883ef3e8b11da6c34815e570f2556010687886",
+      "229be4bab9d957eea9e7b252695069f9828f32ee",
       [
        "webnn/conformance_tests/quantizeLinear.https.any.html?cpu",
        {
@@ -813968,7 +814938,7 @@
       ]
      ],
      "sin.https.any.js": [
-      "e363d74535482d5f7ce7e3c00644e2ad53e08da2",
+      "28ba7c92827b5b2e3587e627ae58db8cc90e000c",
       [
        "webnn/conformance_tests/sin.https.any.html?cpu",
        {
@@ -815513,7 +816483,7 @@
       ]
      ],
      "tensor.https.any.js": [
-      "3da2e852445c488da8998e24bdedfdd6873380ce",
+      "66b79a392eb630cd7c98001e2bf4b0557e29fbe8",
       [
        "webnn/conformance_tests/tensor.https.any.html?cpu",
        {
@@ -834597,7 +835567,7 @@
      ]
     ],
     "RTCRtpTransceiver.https.html": [
-     "32717d9bc0b13a08fd7b83cd3227463a19c48882",
+     "f364753d423edc1ad481eb54ab8d203cbd1843e8",
      [
       null,
       {
@@ -834998,7 +835968,7 @@
       ]
      ],
      "h264-profile-levels.https.html": [
-      "cb0b581c30b73d35f63775a1388b14a43112d169",
+      "2ba92ffd315101fad42f271c6fd47cbbf2ee407a",
       [
        null,
        {
@@ -854345,14 +855315,7 @@
      ]
     ],
     "WorkerNavigator_userAgentData.https.html": [
-     "a46c530cefe3ef1ade047b0016e184060b8c0666",
-     [
-      null,
-      {}
-     ]
-    ],
-    "WorkerNavigator_userAgentData.https.tentative.html": [
-     "e4026e2ff06873c24c37cd59108b0a83dc69fd9a",
+     "8ca73df3e8cd107b69b2a26666f5ceef9a86926e",
      [
       null,
       {}
@@ -875995,7 +876958,7 @@
       "script": {
        "add_preload_script": {
         "add_preload_script.py": [
-         "5e3e2d62ac1d47a9ecb5a7b35f4f62c40030d0d9",
+         "39f8ee1b975d5a9073793cb6603d55d4f2d0847b",
          [
           null,
           {}
@@ -877129,7 +878092,7 @@
       },
       "find_element": {
        "find.py": [
-        "50de92554bcb29659d5a5b842ef12a485a0cde3a",
+        "0ede5a9659b9d00f23ec3898b8b82ab356acd70d",
         [
          null,
          {}
@@ -877147,7 +878110,7 @@
       },
       "find_element_from_element": {
        "find.py": [
-        "e0569f4e3c133a9b14aff559018b545991cff243",
+        "78d8d417adee7023a76b4965d12a2bc0bc00bb2e",
         [
          null,
          {}
@@ -877165,7 +878128,7 @@
       },
       "find_element_from_shadow_root": {
        "find.py": [
-        "cc0d912f4edd93a878e25ce688287d0f9f36dc69",
+        "c0adce66702ddd29b102f98bd4532990d40b9c99",
         [
          null,
          {}
diff --git a/third_party/blink/web_tests/external/wpt/WebCryptoAPI/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/WebCryptoAPI/WEB_FEATURES.yml
new file mode 100644
index 0000000..6f47b86e
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/WebCryptoAPI/WEB_FEATURES.yml
@@ -0,0 +1,3 @@
+features:
+- name: web-cryptography
+  files: '**'
diff --git a/third_party/blink/web_tests/external/wpt/ai/summarizer/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/ai/summarizer/WEB_FEATURES.yml
new file mode 100644
index 0000000..224df817
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/ai/summarizer/WEB_FEATURES.yml
@@ -0,0 +1,3 @@
+features:
+- name: summarizer
+  files: "**"
diff --git a/third_party/blink/web_tests/external/wpt/appmanifest/display-override-member/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/appmanifest/display-override-member/WEB_FEATURES.yml
new file mode 100644
index 0000000..17d9317
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/appmanifest/display-override-member/WEB_FEATURES.yml
@@ -0,0 +1,4 @@
+features:
+- name: window-controls-overlay
+  files:
+  - '*window-controls-overlay*'
diff --git a/third_party/blink/web_tests/external/wpt/clipboard-apis/idlharness.https.window-expected.txt b/third_party/blink/web_tests/external/wpt/clipboard-apis/idlharness.https.window-expected.txt
index 20a16fa..35989af6 100644
--- a/third_party/blink/web_tests/external/wpt/clipboard-apis/idlharness.https.window-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/clipboard-apis/idlharness.https.window-expected.txt
@@ -1,5 +1,7 @@
 This is a testharness.js-based test.
-Found 1 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 2 FAIL, 0 TIMEOUT, 0 NOTRUN.
+[FAIL] ClipboardChangeEvent interface object length
+  assert_equals: wrong value for ClipboardChangeEvent.length expected 1 but got 0
 [FAIL] ClipboardItem interface: attribute presentationStyle
   assert_true: The prototype object must have a property "presentationStyle" expected true got false
 Harness: the test ran to completion.
diff --git a/third_party/blink/web_tests/external/wpt/content-security-policy/inheritance/document-write-iframe.html b/third_party/blink/web_tests/external/wpt/content-security-policy/inheritance/document-write-iframe.html
index d6ad88dd..73614e3 100644
--- a/third_party/blink/web_tests/external/wpt/content-security-policy/inheritance/document-write-iframe.html
+++ b/third_party/blink/web_tests/external/wpt/content-security-policy/inheritance/document-write-iframe.html
@@ -35,7 +35,9 @@
 
     promise_test(async () => {
       let iframe = document.createElement('iframe');
+      let loaded = new Promise(resolve => iframe.onload = resolve);
       document.body.appendChild(iframe);
+      await loaded;
 
       let msg = message_from(iframe.contentWindow);
       let doc = iframe.contentWindow.document;
@@ -43,7 +45,7 @@
       doc.write("<html><body>" + documentBody(false) + "</body></html>");
       doc.close();
       assert_equals(await msg, "blocked");
-    }, "document.open() keeps inherited CSPs on empty iframe.");
+    }, "document.open() keeps inherited CSPs on initial about:blank.");
 
     promise_test(async () => {
       let iframe = document.createElement('iframe');
@@ -60,6 +62,22 @@
       assert_equals(await msg, "loaded");
     }, "document.open() does not change delivered CSPs.");
 
+    promise_test(async () => {
+      let iframe = document.createElement('iframe');
+      iframe.src = "/common/blank.html";
+      let loaded = false;
+      iframe.onload = () => loaded = true;
+      document.body.appendChild(iframe);
+      assert_false(loaded, "iframe document should be transient");
+
+      let msg = message_from(iframe.contentWindow);
+      let doc = iframe.contentWindow.document;
+      doc.open();
+      doc.write("<html><body>" + documentBody(false) + "</body></html>");
+      doc.close();
+      assert_equals(await msg, "blocked");
+    }, "document.open() keeps inherited CSPs on transient about:blank.");
+
   </script>
 </body>
 </html>
diff --git a/third_party/blink/web_tests/external/wpt/css/CSS2/text/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/css/CSS2/text/WEB_FEATURES.yml
new file mode 100644
index 0000000..ab94d28
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/CSS2/text/WEB_FEATURES.yml
@@ -0,0 +1,4 @@
+features:
+- name: word-spacing
+  files:
+  - word-spacing-*
diff --git a/third_party/blink/web_tests/external/wpt/css/css-anchor-position/anchor-loop-crash.html b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/anchor-loop-crash.html
new file mode 100644
index 0000000..002e3a8
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/anchor-loop-crash.html
@@ -0,0 +1,70 @@
+<!DOCTYPE html>
+<html reftest-async-scroll>
+<style>
+.container {
+  position: absolute;
+  left: 0;
+  top: 0;
+}
+.scroller {
+  position: absolute;
+  overflow: auto;
+  scrollbar-width: none;
+  width: 150px;
+  height: 100px;
+}
+.spacer {
+  width: 1px;
+  height: 500px;
+}
+.anchor1 {
+  width: 50px;
+  height: 50px;
+  background: blue;
+  anchor-name: --my-anchor1;
+  position: absolute;
+  position-anchor: --my-anchor2;
+  position-visibility: always;
+  left: anchor(right);
+  top: anchor(bottom);
+}
+.anchor2 {
+  width: 50px;
+  height: 50px;
+  background: pink;
+  anchor-name: --my-anchor2;
+  position: absolute;
+  position-anchor: --my-anchor3;
+  position-visibility: always;
+  left: anchor(right);
+  top: anchor(bottom);
+}
+.anchored {
+  position: absolute;
+  position-anchor: --my-anchor1;
+  position-visibility: always;
+  width: 50px;
+  height: 50px;
+  background: yellow;
+  left: anchor(right);
+  top: anchor(bottom);
+  anchor-name: --my-anchor3;
+}
+</style>
+<div class="container">
+  <div class="scroller"
+    reftest-displayport-x="0" reftest-displayport-y="0"
+    reftest-displayport-w="150" reftest-displayport-h="500">
+    <div class="scroller"
+    reftest-displayport-x="0" reftest-displayport-y="0"
+    reftest-displayport-w="150" reftest-displayport-h="500"
+    reftest-async-scroll-y="50">
+      <div class="anchor2"></div>
+      <div class="spacer"></div>
+    </div>
+    <div class="anchor1"></div>
+  	<div class="spacer"></div>
+  </div>
+  <div class="anchored"></div>
+</div>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-anchor-position/anchor-scroll-composited-scrolling-paint-001-ref.html b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/anchor-scroll-composited-scrolling-paint-001-ref.html
new file mode 100644
index 0000000..ca0096d4
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/anchor-scroll-composited-scrolling-paint-001-ref.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1999954">
+<style>
+.abs-cb {
+  width: 100px;
+  height: 100px;
+  position: relative;
+}
+
+.anchor {
+  anchor-name: --a;
+  width: 50px;
+  height: 50px;
+  background: blue;
+}
+
+.chain {
+  width: 25px;
+  height: 25px;
+  background: pink;
+  position: absolute;
+  left: 50px;
+  top: 50px;
+}
+
+.positioned {
+  width: 25px;
+  height: 25px;
+  background: yellow;
+  position: absolute;
+  left: 75px;
+  top: 75px;
+}
+</style>
+<div class=abs-cb>
+  <div class=anchor></div>
+  <div class=chain></div>
+  <div class=positioned></div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-anchor-position/anchor-scroll-composited-scrolling-paint-001.html b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/anchor-scroll-composited-scrolling-paint-001.html
new file mode 100644
index 0000000..c1d209a
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/anchor-scroll-composited-scrolling-paint-001.html
@@ -0,0 +1,71 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1999954">
+<link rel="match" href="anchor-scroll-composited-scrolling-paint-001-ref.html">
+<script src="/common/reftest-wait.js"></script>
+<style>
+.abs-cb {
+  width: 100px;
+  height: 100px;
+  position: relative;
+}
+
+.scroller {
+  overflow: scroll;
+  scrollbar-width: none;
+  width: 100%;
+  height: 100%;
+}
+
+.anchor {
+  anchor-name: --a;
+  width: 50px;
+  height: 50px;
+  background: blue;
+}
+
+.chain {
+  width: 25px;
+  height: 25px;
+  background: pink;
+  position: absolute;
+  position-anchor: --a;
+  anchor-name: --b;
+  left: anchor(right);
+  top: anchor(bottom);
+}
+
+.filler {
+  width: 1px;
+  height: 50px;
+}
+
+.positioned {
+  width: 25px;
+  height: 25px;
+  background: yellow;
+  position: absolute;
+  position-anchor: --b;
+  left: anchor(right);
+  top: anchor(bottom);
+  position-visibility: always;
+}
+</style>
+<div class=abs-cb>
+  <div class=scroller>
+    <div id=s class=scroller>
+      <div class=filler></div>
+      <div class=anchor></div>
+      <div class=filler></div>
+    </div>
+    <div class=chain></div>
+  </div>
+  <div class=positioned></div>
+</div>
+<script>
+window.addEventListener("TestRendered", () => {
+  s.scrollTop = 50;
+  takeScreenshot();
+});
+</script>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-anchor-position/try-tactic-position-area.html b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/try-tactic-position-area.html
index cbc24ab..f47231b1 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-anchor-position/try-tactic-position-area.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/try-tactic-position-area.html
@@ -18,7 +18,7 @@
     height: 50px;
     background-color: coral;
   }
-  #target {
+  #target, #ref {
     position: absolute;
     left: 450px; /* force fallback */
     width: 40px;
@@ -30,16 +30,11 @@
 </style>
 <div id=cb>
   <div id=target></div>
+  <div id=ref></div>
 </div>
 <script>
 
-function test_computed_value(try_tactic, property, value, expected, direction, writing_mode) {
-  if (direction==undefined) {
-    direction = 'ltr';
-  }
-  if (writing_mode==undefined) {
-    writing_mode = 'horizontal-tb';
-  }
+function test_position_area_tactic(try_tactic, property, value, expected, direction = 'ltr', writing_mode = 'horizontal-tb') {
   test((t) => {
     t.add_cleanup(() => {
       style.textContent = '';
@@ -55,197 +50,204 @@
       #target {
         position-try-fallbacks: --pf ${try_tactic};
       }
+      #ref {
+        inset: initial;
+        ${property}:${expected};
+      }
     `;
-    assert_equals(getComputedStyle(target).getPropertyValue(property), expected);
+    let targetRect = target.getBoundingClientRect();
+    let refRect = ref.getBoundingClientRect();
+    for (let prop of ["top", "right", "bottom", "left"]) {
+      assert_equals(targetRect[prop], refRect[prop], `Should render as ${property}: ${expected}: ${prop}`);
+    }
   }, `${try_tactic}, ${property}:${value}, ${direction} ${writing_mode}`);
 }
 
-
 // Physical:
 
-test_computed_value('flip-inline', 'position-area', 'left top', 'right top');
-test_computed_value('flip-inline', 'position-area', 'left bottom', 'right bottom');
-test_computed_value('flip-inline', 'position-area', 'right bottom', 'left bottom');
-test_computed_value('flip-inline', 'position-area', 'right top', 'left top');
+test_position_area_tactic('flip-inline', 'position-area', 'left top', 'right top');
+test_position_area_tactic('flip-inline', 'position-area', 'left bottom', 'right bottom');
+test_position_area_tactic('flip-inline', 'position-area', 'right bottom', 'left bottom');
+test_position_area_tactic('flip-inline', 'position-area', 'right top', 'left top');
 
-test_computed_value('flip-block', 'position-area', 'left top', 'left bottom');
-test_computed_value('flip-block', 'position-area', 'left bottom', 'left top');
-test_computed_value('flip-block', 'position-area', 'right bottom', 'right top');
-test_computed_value('flip-block', 'position-area', 'right top', 'right bottom');
+test_position_area_tactic('flip-block', 'position-area', 'left top', 'left bottom');
+test_position_area_tactic('flip-block', 'position-area', 'left bottom', 'left top');
+test_position_area_tactic('flip-block', 'position-area', 'right bottom', 'right top');
+test_position_area_tactic('flip-block', 'position-area', 'right top', 'right bottom');
 
-test_computed_value('flip-block flip-inline', 'position-area', 'left top', 'right bottom');
-test_computed_value('flip-block flip-inline', 'position-area', 'left bottom', 'right top');
-test_computed_value('flip-block flip-inline', 'position-area', 'right bottom', 'left top');
-test_computed_value('flip-block flip-inline', 'position-area', 'right top', 'left bottom');
+test_position_area_tactic('flip-block flip-inline', 'position-area', 'left top', 'right bottom');
+test_position_area_tactic('flip-block flip-inline', 'position-area', 'left bottom', 'right top');
+test_position_area_tactic('flip-block flip-inline', 'position-area', 'right bottom', 'left top');
+test_position_area_tactic('flip-block flip-inline', 'position-area', 'right top', 'left bottom');
 
-test_computed_value('flip-start', 'position-area', 'left top', 'left top');
-test_computed_value('flip-start', 'position-area', 'left bottom', 'right top');
-test_computed_value('flip-start', 'position-area', 'right bottom', 'right bottom');
-test_computed_value('flip-start', 'position-area', 'right top', 'left bottom');
+test_position_area_tactic('flip-start', 'position-area', 'left top', 'left top');
+test_position_area_tactic('flip-start', 'position-area', 'left bottom', 'right top');
+test_position_area_tactic('flip-start', 'position-area', 'right bottom', 'right bottom');
+test_position_area_tactic('flip-start', 'position-area', 'right top', 'left bottom');
 
-test_computed_value('flip-block flip-start', 'position-area', 'left top', 'right top');
-test_computed_value('flip-block flip-start', 'position-area', 'left bottom', 'left top');
-test_computed_value('flip-block flip-start', 'position-area', 'right bottom', 'left bottom');
-test_computed_value('flip-block flip-start', 'position-area', 'right top', 'right bottom');
+test_position_area_tactic('flip-block flip-start', 'position-area', 'left top', 'right top');
+test_position_area_tactic('flip-block flip-start', 'position-area', 'left bottom', 'left top');
+test_position_area_tactic('flip-block flip-start', 'position-area', 'right bottom', 'left bottom');
+test_position_area_tactic('flip-block flip-start', 'position-area', 'right top', 'right bottom');
 
-test_computed_value('flip-inline flip-start', 'position-area', 'left top', 'left bottom');
-test_computed_value('flip-inline flip-start', 'position-area', 'left bottom', 'right bottom');
-test_computed_value('flip-inline flip-start', 'position-area', 'right bottom', 'right top');
-test_computed_value('flip-inline flip-start', 'position-area', 'right top', 'left top');
+test_position_area_tactic('flip-inline flip-start', 'position-area', 'left top', 'left bottom');
+test_position_area_tactic('flip-inline flip-start', 'position-area', 'left bottom', 'right bottom');
+test_position_area_tactic('flip-inline flip-start', 'position-area', 'right bottom', 'right top');
+test_position_area_tactic('flip-inline flip-start', 'position-area', 'right top', 'left top');
 
-test_computed_value('flip-block flip-inline flip-start', 'position-area', 'left top', 'right bottom');
-test_computed_value('flip-block flip-inline flip-start', 'position-area', 'left bottom', 'left bottom');
-test_computed_value('flip-block flip-inline flip-start', 'position-area', 'right bottom', 'left top');
-test_computed_value('flip-block flip-inline flip-start', 'position-area', 'right top', 'right top');
+test_position_area_tactic('flip-block flip-inline flip-start', 'position-area', 'left top', 'right bottom');
+test_position_area_tactic('flip-block flip-inline flip-start', 'position-area', 'left bottom', 'left bottom');
+test_position_area_tactic('flip-block flip-inline flip-start', 'position-area', 'right bottom', 'left top');
+test_position_area_tactic('flip-block flip-inline flip-start', 'position-area', 'right top', 'right top');
 
 // Variations:
-test_computed_value('flip-block flip-inline', 'position-area', 'span-left span-top', 'span-right span-bottom');
+test_position_area_tactic('flip-block flip-inline', 'position-area', 'span-left span-top', 'span-right span-bottom');
 
 // XY:
 
-test_computed_value('flip-inline', 'position-area', 'x-start y-start', 'x-end y-start');
-test_computed_value('flip-inline', 'position-area', 'x-start y-end', 'x-end y-end');
-test_computed_value('flip-inline', 'position-area', 'x-end y-end', 'x-start y-end');
-test_computed_value('flip-inline', 'position-area', 'x-end y-start', 'x-start y-start');
+test_position_area_tactic('flip-inline', 'position-area', 'x-start y-start', 'x-end y-start');
+test_position_area_tactic('flip-inline', 'position-area', 'x-start y-end', 'x-end y-end');
+test_position_area_tactic('flip-inline', 'position-area', 'x-end y-end', 'x-start y-end');
+test_position_area_tactic('flip-inline', 'position-area', 'x-end y-start', 'x-start y-start');
 
-test_computed_value('flip-block', 'position-area', 'x-start y-start', 'x-start y-end');
-test_computed_value('flip-block', 'position-area', 'x-start y-end', 'x-start y-start');
-test_computed_value('flip-block', 'position-area', 'x-end y-end', 'x-end y-start');
-test_computed_value('flip-block', 'position-area', 'x-end y-start', 'x-end y-end');
+test_position_area_tactic('flip-block', 'position-area', 'x-start y-start', 'x-start y-end');
+test_position_area_tactic('flip-block', 'position-area', 'x-start y-end', 'x-start y-start');
+test_position_area_tactic('flip-block', 'position-area', 'x-end y-end', 'x-end y-start');
+test_position_area_tactic('flip-block', 'position-area', 'x-end y-start', 'x-end y-end');
 
-test_computed_value('flip-block flip-inline', 'position-area', 'x-start y-start', 'x-end y-end');
-test_computed_value('flip-block flip-inline', 'position-area', 'x-start y-end', 'x-end y-start');
-test_computed_value('flip-block flip-inline', 'position-area', 'x-end y-end', 'x-start y-start');
-test_computed_value('flip-block flip-inline', 'position-area', 'x-end y-start', 'x-start y-end');
+test_position_area_tactic('flip-block flip-inline', 'position-area', 'x-start y-start', 'x-end y-end');
+test_position_area_tactic('flip-block flip-inline', 'position-area', 'x-start y-end', 'x-end y-start');
+test_position_area_tactic('flip-block flip-inline', 'position-area', 'x-end y-end', 'x-start y-start');
+test_position_area_tactic('flip-block flip-inline', 'position-area', 'x-end y-start', 'x-start y-end');
 
-test_computed_value('flip-start', 'position-area', 'x-start y-start', 'x-start y-start');
-test_computed_value('flip-start', 'position-area', 'x-start y-end', 'x-end y-start');
-test_computed_value('flip-start', 'position-area', 'x-end y-end', 'x-end y-end');
-test_computed_value('flip-start', 'position-area', 'x-end y-start', 'x-start y-end');
+test_position_area_tactic('flip-start', 'position-area', 'x-start y-start', 'x-start y-start');
+test_position_area_tactic('flip-start', 'position-area', 'x-start y-end', 'x-end y-start');
+test_position_area_tactic('flip-start', 'position-area', 'x-end y-end', 'x-end y-end');
+test_position_area_tactic('flip-start', 'position-area', 'x-end y-start', 'x-start y-end');
 
-test_computed_value('flip-block flip-start', 'position-area', 'x-start y-start', 'x-end y-start');
-test_computed_value('flip-block flip-start', 'position-area', 'x-start y-end', 'x-start y-start');
-test_computed_value('flip-block flip-start', 'position-area', 'x-end y-end', 'x-start y-end');
-test_computed_value('flip-block flip-start', 'position-area', 'x-end y-start', 'x-end y-end');
+test_position_area_tactic('flip-block flip-start', 'position-area', 'x-start y-start', 'x-end y-start');
+test_position_area_tactic('flip-block flip-start', 'position-area', 'x-start y-end', 'x-start y-start');
+test_position_area_tactic('flip-block flip-start', 'position-area', 'x-end y-end', 'x-start y-end');
+test_position_area_tactic('flip-block flip-start', 'position-area', 'x-end y-start', 'x-end y-end');
 
-test_computed_value('flip-inline flip-start', 'position-area', 'x-start y-start', 'x-start y-end');
-test_computed_value('flip-inline flip-start', 'position-area', 'x-start y-end', 'x-end y-end');
-test_computed_value('flip-inline flip-start', 'position-area', 'x-end y-end', 'x-end y-start');
-test_computed_value('flip-inline flip-start', 'position-area', 'x-end y-start', 'x-start y-start');
+test_position_area_tactic('flip-inline flip-start', 'position-area', 'x-start y-start', 'x-start y-end');
+test_position_area_tactic('flip-inline flip-start', 'position-area', 'x-start y-end', 'x-end y-end');
+test_position_area_tactic('flip-inline flip-start', 'position-area', 'x-end y-end', 'x-end y-start');
+test_position_area_tactic('flip-inline flip-start', 'position-area', 'x-end y-start', 'x-start y-start');
 
-test_computed_value('flip-block flip-inline flip-start', 'position-area', 'x-start y-start', 'x-end y-end');
-test_computed_value('flip-block flip-inline flip-start', 'position-area', 'x-start y-end', 'x-start y-end');
-test_computed_value('flip-block flip-inline flip-start', 'position-area', 'x-end y-end', 'x-start y-start');
-test_computed_value('flip-block flip-inline flip-start', 'position-area', 'x-end y-start', 'x-end y-start');
+test_position_area_tactic('flip-block flip-inline flip-start', 'position-area', 'x-start y-start', 'x-end y-end');
+test_position_area_tactic('flip-block flip-inline flip-start', 'position-area', 'x-start y-end', 'x-start y-end');
+test_position_area_tactic('flip-block flip-inline flip-start', 'position-area', 'x-end y-end', 'x-start y-start');
+test_position_area_tactic('flip-block flip-inline flip-start', 'position-area', 'x-end y-start', 'x-end y-start');
 
 // Variations:
-test_computed_value('flip-block flip-inline', 'position-area', 'span-x-start span-y-start', 'span-x-end span-y-end');
-test_computed_value('flip-block flip-inline', 'position-area', 'self-x-start self-y-start', 'self-x-end self-y-end');
-test_computed_value('flip-block flip-inline', 'position-area', 'span-self-x-start span-self-y-start', 'span-self-x-end span-self-y-end');
+test_position_area_tactic('flip-block flip-inline', 'position-area', 'span-x-start span-y-start', 'span-x-end span-y-end');
+test_position_area_tactic('flip-block flip-inline', 'position-area', 'self-x-start self-y-start', 'self-x-end self-y-end');
+test_position_area_tactic('flip-block flip-inline', 'position-area', 'span-self-x-start span-self-y-start', 'span-self-x-end span-self-y-end');
 
 // Logical:
 
-test_computed_value('flip-inline', 'position-area', 'block-start inline-start', 'start end');
-test_computed_value('flip-inline', 'position-area', 'block-end inline-start', 'end');
-test_computed_value('flip-inline', 'position-area', 'block-end inline-end', 'end start');
-test_computed_value('flip-inline', 'position-area', 'block-start inline-end', 'start');
+test_position_area_tactic('flip-inline', 'position-area', 'block-start inline-start', 'start end');
+test_position_area_tactic('flip-inline', 'position-area', 'block-end inline-start', 'end');
+test_position_area_tactic('flip-inline', 'position-area', 'block-end inline-end', 'end start');
+test_position_area_tactic('flip-inline', 'position-area', 'block-start inline-end', 'start');
 
-test_computed_value('flip-block', 'position-area', 'block-start inline-start', 'end start');
-test_computed_value('flip-block', 'position-area', 'block-end inline-start', 'start');
-test_computed_value('flip-block', 'position-area', 'block-end inline-end', 'start end');
-test_computed_value('flip-block', 'position-area', 'block-start inline-end', 'end');
+test_position_area_tactic('flip-block', 'position-area', 'block-start inline-start', 'end start');
+test_position_area_tactic('flip-block', 'position-area', 'block-end inline-start', 'start');
+test_position_area_tactic('flip-block', 'position-area', 'block-end inline-end', 'start end');
+test_position_area_tactic('flip-block', 'position-area', 'block-start inline-end', 'end');
 
-test_computed_value('flip-block flip-inline', 'position-area', 'block-start inline-start', 'end');
-test_computed_value('flip-block flip-inline', 'position-area', 'block-end inline-start', 'start end');
-test_computed_value('flip-block flip-inline', 'position-area', 'block-end inline-end', 'start');
-test_computed_value('flip-block flip-inline', 'position-area', 'block-start inline-end', 'end start');
+test_position_area_tactic('flip-block flip-inline', 'position-area', 'block-start inline-start', 'end');
+test_position_area_tactic('flip-block flip-inline', 'position-area', 'block-end inline-start', 'start end');
+test_position_area_tactic('flip-block flip-inline', 'position-area', 'block-end inline-end', 'start');
+test_position_area_tactic('flip-block flip-inline', 'position-area', 'block-start inline-end', 'end start');
 
-test_computed_value('flip-start', 'position-area', 'block-start inline-start', 'start');
-test_computed_value('flip-start', 'position-area', 'block-end inline-start', 'start end');
-test_computed_value('flip-start', 'position-area', 'block-end inline-end', 'end');
-test_computed_value('flip-start', 'position-area', 'block-start inline-end', 'end start');
+test_position_area_tactic('flip-start', 'position-area', 'block-start inline-start', 'start');
+test_position_area_tactic('flip-start', 'position-area', 'block-end inline-start', 'start end');
+test_position_area_tactic('flip-start', 'position-area', 'block-end inline-end', 'end');
+test_position_area_tactic('flip-start', 'position-area', 'block-start inline-end', 'end start');
 
-test_computed_value('flip-block flip-start', 'position-area', 'block-start inline-start', 'start end');
-test_computed_value('flip-block flip-start', 'position-area', 'block-end inline-start', 'start');
-test_computed_value('flip-block flip-start', 'position-area', 'block-end inline-end', 'end start');
-test_computed_value('flip-block flip-start', 'position-area', 'block-start inline-end', 'end');
+test_position_area_tactic('flip-block flip-start', 'position-area', 'block-start inline-start', 'start end');
+test_position_area_tactic('flip-block flip-start', 'position-area', 'block-end inline-start', 'start');
+test_position_area_tactic('flip-block flip-start', 'position-area', 'block-end inline-end', 'end start');
+test_position_area_tactic('flip-block flip-start', 'position-area', 'block-start inline-end', 'end');
 
-test_computed_value('flip-inline flip-start', 'position-area', 'block-start inline-start', 'end start');
-test_computed_value('flip-inline flip-start', 'position-area', 'block-end inline-start', 'end');
-test_computed_value('flip-inline flip-start', 'position-area', 'block-end inline-end', 'start end');
-test_computed_value('flip-inline flip-start', 'position-area', 'block-start inline-end', 'start');
+test_position_area_tactic('flip-inline flip-start', 'position-area', 'block-start inline-start', 'end start');
+test_position_area_tactic('flip-inline flip-start', 'position-area', 'block-end inline-start', 'end');
+test_position_area_tactic('flip-inline flip-start', 'position-area', 'block-end inline-end', 'start end');
+test_position_area_tactic('flip-inline flip-start', 'position-area', 'block-start inline-end', 'start');
 
-test_computed_value('flip-block flip-inline flip-start', 'position-area', 'block-start inline-start', 'end');
-test_computed_value('flip-block flip-inline flip-start', 'position-area', 'block-end inline-start', 'end start');
-test_computed_value('flip-block flip-inline flip-start', 'position-area', 'block-end inline-end', 'start');
-test_computed_value('flip-block flip-inline flip-start', 'position-area', 'block-start inline-end', 'start end');
+test_position_area_tactic('flip-block flip-inline flip-start', 'position-area', 'block-start inline-start', 'end');
+test_position_area_tactic('flip-block flip-inline flip-start', 'position-area', 'block-end inline-start', 'end start');
+test_position_area_tactic('flip-block flip-inline flip-start', 'position-area', 'block-end inline-end', 'start');
+test_position_area_tactic('flip-block flip-inline flip-start', 'position-area', 'block-start inline-end', 'start end');
 
 // Variations:
-test_computed_value('flip-block flip-inline', 'position-area', 'span-block-start span-inline-start', 'span-end');
-test_computed_value('flip-block flip-inline', 'position-area', 'self-block-start self-inline-start', 'self-end');
-test_computed_value('flip-block flip-inline', 'position-area', 'span-self-block-start span-self-inline-start', 'span-self-end');
+test_position_area_tactic('flip-block flip-inline', 'position-area', 'span-block-start span-inline-start', 'span-end');
+test_position_area_tactic('flip-block flip-inline', 'position-area', 'self-block-start self-inline-start', 'self-end');
+test_position_area_tactic('flip-block flip-inline', 'position-area', 'span-self-block-start span-self-inline-start', 'span-self-end');
 
 // start/end
 
-test_computed_value('', 'position-area', 'start end', 'start end');
+test_position_area_tactic('', 'position-area', 'start end', 'start end');
 
-test_computed_value('flip-block', 'position-area', 'start end', 'end');
+test_position_area_tactic('flip-block', 'position-area', 'start end', 'end');
 
-test_computed_value('flip-inline', 'position-area', 'start end', 'start');
+test_position_area_tactic('flip-inline', 'position-area', 'start end', 'start');
 
-test_computed_value('flip-block flip-inline', 'position-area', 'start end', 'end start');
+test_position_area_tactic('flip-block flip-inline', 'position-area', 'start end', 'end start');
 
-test_computed_value('flip-start', 'position-area', 'start', 'start');
-test_computed_value('flip-start', 'position-area', 'end', 'end');
-test_computed_value('flip-start', 'position-area', 'start end', 'end start');
+test_position_area_tactic('flip-start', 'position-area', 'start', 'start');
+test_position_area_tactic('flip-start', 'position-area', 'end', 'end');
+test_position_area_tactic('flip-start', 'position-area', 'start end', 'end start');
 
-test_computed_value('flip-block flip-start', 'position-area', 'start end', 'end');
+test_position_area_tactic('flip-block flip-start', 'position-area', 'start end', 'end');
 
-test_computed_value('flip-inline flip-start', 'position-area', 'start end', 'start');
+test_position_area_tactic('flip-inline flip-start', 'position-area', 'start end', 'start');
 
-test_computed_value('flip-block flip-inline flip-start', 'position-area', 'start end', 'start end');
+test_position_area_tactic('flip-block flip-inline flip-start', 'position-area', 'start end', 'start end');
 
 // Variations:
 
-test_computed_value('flip-block flip-inline', 'position-area', 'span-start span-end', 'span-end span-start');
-test_computed_value('flip-block flip-inline', 'position-area', 'self-start self-end', 'self-end self-start');
-test_computed_value('flip-block flip-inline', 'position-area', 'span-self-start span-self-end', 'span-self-end span-self-start');
+test_position_area_tactic('flip-block flip-inline', 'position-area', 'span-start span-end', 'span-end span-start');
+test_position_area_tactic('flip-block flip-inline', 'position-area', 'self-start self-end', 'self-end self-start');
+test_position_area_tactic('flip-block flip-inline', 'position-area', 'span-self-start span-self-end', 'span-self-end span-self-start');
 
 // center
-test_computed_value('flip-block', 'position-area', 'left center', 'left center');
-test_computed_value('flip-block', 'position-area', 'center top', 'center bottom');
-test_computed_value('flip-block', 'position-area', 'center', 'center');
-test_computed_value('flip-block', 'position-area', 'start center', 'end center');
-test_computed_value('flip-block', 'position-area', 'center start', 'center start');
-test_computed_value('flip-inline', 'position-area', 'center start', 'center end');
-test_computed_value('flip-start', 'position-area', 'center start', 'start center');
+test_position_area_tactic('flip-block', 'position-area', 'left center', 'left center');
+test_position_area_tactic('flip-block', 'position-area', 'center top', 'center bottom');
+test_position_area_tactic('flip-block', 'position-area', 'center', 'center');
+test_position_area_tactic('flip-block', 'position-area', 'start center', 'end center');
+test_position_area_tactic('flip-block', 'position-area', 'center start', 'center start');
+test_position_area_tactic('flip-inline', 'position-area', 'center start', 'center end');
+test_position_area_tactic('flip-start', 'position-area', 'center start', 'start center');
 
 // span-all
-test_computed_value('flip-block', 'position-area', 'left span-all', 'left');
-test_computed_value('flip-block', 'position-area', 'span-all top', 'bottom');
-test_computed_value('flip-block', 'position-area', 'span-all', 'span-all');
-test_computed_value('flip-block', 'position-area', 'start span-all', 'block-end');
-test_computed_value('flip-block', 'position-area', 'span-all start', 'inline-start');
-test_computed_value('flip-inline', 'position-area', 'span-all start', 'inline-end');
-test_computed_value('flip-start', 'position-area', 'span-all start', 'block-start');
+test_position_area_tactic('flip-block', 'position-area', 'left span-all', 'left');
+test_position_area_tactic('flip-block', 'position-area', 'span-all top', 'bottom');
+test_position_area_tactic('flip-block', 'position-area', 'span-all', 'span-all');
+test_position_area_tactic('flip-block', 'position-area', 'start span-all', 'block-end');
+test_position_area_tactic('flip-block', 'position-area', 'span-all start', 'inline-start');
+test_position_area_tactic('flip-inline', 'position-area', 'span-all start', 'inline-end');
+test_position_area_tactic('flip-start', 'position-area', 'span-all start', 'block-start');
 
 // Span mix:
-test_computed_value('flip-block', 'position-area', 'left span-top', 'left span-bottom');
-test_computed_value('flip-inline', 'position-area', 'left span-top', 'right span-top');
-test_computed_value('flip-start', 'position-area', 'span-block-start inline-end', 'end span-start');
+test_position_area_tactic('flip-block', 'position-area', 'left span-top', 'left span-bottom');
+test_position_area_tactic('flip-inline', 'position-area', 'left span-top', 'right span-top');
+test_position_area_tactic('flip-start', 'position-area', 'span-block-start inline-end', 'end span-start');
 
 // Writing modes:
-test_computed_value('flip-block', 'position-area', 'left top', 'right top', 'ltr', 'vertical-rl');
+test_position_area_tactic('flip-block', 'position-area', 'left top', 'right top', 'ltr', 'vertical-rl');
 
-test_computed_value('', 'position-area', 'x-start y-start', 'x-start y-start', 'rtl');
-test_computed_value('flip-block', 'position-area', 'x-start y-start', 'x-start y-end', 'rtl');
-test_computed_value('flip-inline', 'position-area', 'x-start y-start', 'x-end y-start', 'rtl');
-test_computed_value('flip-block', 'position-area', 'x-end y-start', 'x-start y-start', 'ltr', 'vertical-rl');
-test_computed_value('flip-inline', 'position-area', 'x-end y-start', 'x-end y-end', 'ltr', 'vertical-rl');
+test_position_area_tactic('', 'position-area', 'x-start y-start', 'x-start y-start', 'rtl');
+test_position_area_tactic('flip-block', 'position-area', 'x-start y-start', 'x-start y-end', 'rtl');
+test_position_area_tactic('flip-inline', 'position-area', 'x-start y-start', 'x-end y-start', 'rtl');
+test_position_area_tactic('flip-block', 'position-area', 'x-end y-start', 'x-start y-start', 'ltr', 'vertical-rl');
+test_position_area_tactic('flip-inline', 'position-area', 'x-end y-start', 'x-end y-end', 'ltr', 'vertical-rl');
 
-test_computed_value('flip-inline', 'position-area', 'start end', 'start', 'rtl');
-test_computed_value('flip-inline', 'position-area', 'start end', 'start', 'ltr', 'vertical-rl');
-test_computed_value('flip-block', 'position-area', 'start end', 'end', 'rtl');
-test_computed_value('flip-block', 'position-area', 'start end', 'end', 'ltr', 'vertical-rl');
+test_position_area_tactic('flip-inline', 'position-area', 'start end', 'start', 'rtl');
+test_position_area_tactic('flip-inline', 'position-area', 'start end', 'start', 'ltr', 'vertical-rl');
+test_position_area_tactic('flip-block', 'position-area', 'start end', 'end', 'rtl');
+test_position_area_tactic('flip-block', 'position-area', 'start end', 'end', 'ltr', 'vertical-rl');
 
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-borders/corner-shape/corner-shape-zoom-overlap-extreme-values-crash.html b/third_party/blink/web_tests/external/wpt/css/css-borders/corner-shape/corner-shape-zoom-overlap-extreme-values-crash.html
new file mode 100644
index 0000000..8235315
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-borders/corner-shape/corner-shape-zoom-overlap-extreme-values-crash.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-borders-4/#corner-shape" />
+<style>
+  .target {
+    display: inline;
+    clip-path: border-box;
+    corner-shape: bevel square scoop;
+    zoom: 10%;
+    border-radius: 100%;
+    border: 5px solid purple;
+    bottom: -1e100px;
+    position: relative;
+    background: green;
+  }
+</style>
+<div class="target"></div>
+
+This test should not crash.
diff --git a/third_party/blink/web_tests/external/wpt/css/css-break/out-of-flow-in-multicolumn-118.html b/third_party/blink/web_tests/external/wpt/css/css-break/out-of-flow-in-multicolumn-118.html
new file mode 100644
index 0000000..09e55ff
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-break/out-of-flow-in-multicolumn-118.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<title>CSS Break Test: Fragmentation of abspos element in a vertical-rl multicol container</title>
+<link rel="author" title="Ting-Yu Lin" href="mailto:tlin@mozilla.com">
+<link rel="author" title="Mozilla" href="https://www.mozilla.org/">
+<link rel="help" href="https://drafts.csswg.org/css-position-3/#abspos-breaking">
+<link rel="match" href="../reference/ref-filled-green-100px-square.xht">
+
+<style>
+.multicol {
+  writing-mode: vertical-rl;
+  block-size: 100px;
+  inline-size: 100px;
+  column-count: 2;
+  column-fill: auto;
+  column-gap: 0;
+  background: red;
+}
+.container {
+  position: relative;
+  border-block: 20px solid green;
+  block-size: 160px;
+}
+
+.abspos {
+  position: absolute;
+  inline-size: 100%;
+  block-size: 160px;
+  background: green;
+}
+</style>
+
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div class="multicol">
+  <div class="container">
+    <div class="abspos"></div>
+  </div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-break/out-of-flow-in-multicolumn-119.html b/third_party/blink/web_tests/external/wpt/css/css-break/out-of-flow-in-multicolumn-119.html
new file mode 100644
index 0000000..6d372d51
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-break/out-of-flow-in-multicolumn-119.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<title>CSS Break Test: Fragmentation of abspos element in a vertical-rl multicol container</title>
+<link rel="author" title="Ting-Yu Lin" href="mailto:tlin@mozilla.com">
+<link rel="author" title="Mozilla" href="https://www.mozilla.org/">
+<link rel="help" href="https://drafts.csswg.org/css-position-3/#abspos-breaking">
+<link rel="match" href="../reference/ref-filled-green-100px-square.xht">
+
+<style>
+.multicol {
+  writing-mode: vertical-rl;
+  block-size: 100px;
+  inline-size: 100px;
+  column-count: 2;
+  column-fill: auto;
+  column-gap: 0;
+  background: red;
+}
+.container {
+  display: flex;
+  position: relative;
+  border-block: 20px solid green;
+  block-size: 160px;
+}
+
+.abspos {
+  position: absolute;
+  inline-size: 100%;
+  block-size: 160px;
+  background: green;
+}
+</style>
+
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div class="multicol">
+  <div class="container">
+    <div class="abspos"></div>
+  </div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-color/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/css/css-color/WEB_FEATURES.yml
index 275611f..976f4021 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-color/WEB_FEATURES.yml
+++ b/third_party/blink/web_tests/external/wpt/css/css-color/WEB_FEATURES.yml
@@ -29,3 +29,8 @@
 - name: relative-color
   files:
   - relative-*
+- name: rgb
+  files:
+  - rgb-*
+  - rgba-*
+  - hex-*
diff --git a/third_party/blink/web_tests/external/wpt/css/css-env/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/css/css-env/WEB_FEATURES.yml
new file mode 100644
index 0000000..021ce54
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-env/WEB_FEATURES.yml
@@ -0,0 +1,4 @@
+features:
+- name: safe-area-inset
+  files:
+  - "env-parsing*"
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/css/css-images/parsing/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/css/css-images/parsing/WEB_FEATURES.yml
index d60ed3e..fcbbc9f3 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-images/parsing/WEB_FEATURES.yml
+++ b/third_party/blink/web_tests/external/wpt/css/css-images/parsing/WEB_FEATURES.yml
@@ -5,3 +5,7 @@
 - name: gradient-interpolation
   files:
   - "gradient-interpolation-*"
+- name: smooth
+  files:
+  - image-rendering-computed.html
+  - image-rendering-valid.html
diff --git a/third_party/blink/web_tests/external/wpt/css/css-masking/animations/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/css/css-masking/animations/WEB_FEATURES.yml
index 758ef35..12d684e8 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-masking/animations/WEB_FEATURES.yml
+++ b/third_party/blink/web_tests/external/wpt/css/css-masking/animations/WEB_FEATURES.yml
@@ -6,3 +6,7 @@
   - mask-image-interpolation.html
   - mask-no-interpolation.html
   - mask-position-interpolation.html
+- name: clip
+  files:
+  - clip-*
+  - "!clip-path-*"
diff --git a/third_party/blink/web_tests/external/wpt/css/css-masking/animations/clip-path-interpolation-shape-arc-direction-agnostic-radius.html b/third_party/blink/web_tests/external/wpt/css/css-masking/animations/clip-path-interpolation-shape-arc-direction-agnostic-radius.html
index aa91e18..e153e9d1 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-masking/animations/clip-path-interpolation-shape-arc-direction-agnostic-radius.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-masking/animations/clip-path-interpolation-shape-arc-direction-agnostic-radius.html
@@ -4,7 +4,7 @@
   <title>CSS Masking: Test animating single-value arc radius of the shape() function</title>
   <link rel="help" href="https://drafts.csswg.org/css-shapes-2/#funcdef-shape">
   <link rel="match" href="clip-path-interpolation-shape-arc-direction-agnostic-radius-ref.html">
-  <meta name="fuzzy" content="maxDifference=0-10;totalPixels=0-360">
+  <meta name="fuzzy" content="maxDifference=0-69;totalPixels=0-360">
 </head>
 <style>
 @keyframes animate-shape {
diff --git a/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/clip-path-shape-011.html b/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/clip-path-shape-011.html
index fc192759..ca9543a 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/clip-path-shape-011.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/clip-path-shape-011.html
@@ -5,7 +5,7 @@
   <link rel="help" href="https://drafts.csswg.org/css-shapes-2/#funcdef-shape">
   <link rel="match" href="reference/clip-path-shape-arc-ref.html">
   <meta name="assert" content="When providing one radius value with percentage, the percentage should be relative to the diagonal.">
-<meta name="fuzzy" content="maxDifference=0-64;totalPixels=0-128">
+<meta name="fuzzy" content="maxDifference=0-67;totalPixels=0-128">
 </head>
 <style>
 #shape {
diff --git a/third_party/blink/web_tests/external/wpt/css/css-masking/clip/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/css/css-masking/clip/WEB_FEATURES.yml
new file mode 100644
index 0000000..805859d
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-masking/clip/WEB_FEATURES.yml
@@ -0,0 +1,3 @@
+features:
+- name: clip
+  files: "**"
diff --git a/third_party/blink/web_tests/external/wpt/css/css-position/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/css/css-position/WEB_FEATURES.yml
index 9d9e69a..8e97d0df 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-position/WEB_FEATURES.yml
+++ b/third_party/blink/web_tests/external/wpt/css/css-position/WEB_FEATURES.yml
@@ -8,3 +8,6 @@
   - backdrop-inherit-rendered.html
   - backdrop-tree-abiding-slotted.html
   - replaced-object-backdrop.html
+- name: z-index
+  files:
+  - '*z-index*'
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/css/css-position/parsing/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/css/css-position/parsing/WEB_FEATURES.yml
new file mode 100644
index 0000000..09651aea
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-position/parsing/WEB_FEATURES.yml
@@ -0,0 +1,4 @@
+features:
+- name: z-index
+  files:
+  - z-index-*
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/css/css-rhythm/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/css/css-rhythm/WEB_FEATURES.yml
new file mode 100644
index 0000000..9f04b44
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-rhythm/WEB_FEATURES.yml
@@ -0,0 +1,3 @@
+features:
+- name: rhythmic-sizing
+  files: "**"
diff --git a/third_party/blink/web_tests/external/wpt/css/css-scoping/slotted-parsing.html b/third_party/blink/web_tests/external/wpt/css/css-scoping/slotted-parsing.html
index 69eabb5..8a5851c5 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-scoping/slotted-parsing.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-scoping/slotted-parsing.html
@@ -16,6 +16,7 @@
   test_invalid_selector("::slotted(*)[attr]");
   test_invalid_selector("::slotted(*):host");
   test_invalid_selector("::slotted(*):host(div)");
+  test_invalid_selector("::slotted(*):has(span)");
   test_invalid_selector("::slotted(*):hover");
   test_invalid_selector("::slotted(*):read-only");
   test_invalid_selector("::slotted(*)::slotted(*)");
diff --git a/third_party/blink/web_tests/external/wpt/css/css-shadow-parts/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/css/css-shadow-parts/WEB_FEATURES.yml
new file mode 100644
index 0000000..e9fb6df4
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-shadow-parts/WEB_FEATURES.yml
@@ -0,0 +1,3 @@
+features:
+- name: shadow-parts
+  files: '**'
diff --git a/third_party/blink/web_tests/external/wpt/css/css-sizing/stretch/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/css/css-sizing/stretch/WEB_FEATURES.yml
new file mode 100644
index 0000000..886c3f2
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-sizing/stretch/WEB_FEATURES.yml
@@ -0,0 +1,3 @@
+features:
+- name: stretch
+  files: "**"
diff --git a/third_party/blink/web_tests/external/wpt/css/css-style-attr/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/css/css-style-attr/WEB_FEATURES.yml
new file mode 100644
index 0000000..47e1c3f
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-style-attr/WEB_FEATURES.yml
@@ -0,0 +1,3 @@
+features:
+- name: style-attr
+  files: "**"
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/animations/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/css/css-text/animations/WEB_FEATURES.yml
new file mode 100644
index 0000000..c320f09c
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/animations/WEB_FEATURES.yml
@@ -0,0 +1,4 @@
+features:
+- name: tab-size
+  files:
+  - tab-size-interpolation.html
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/parsing/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/css/css-text/parsing/WEB_FEATURES.yml
index 8335ec59..e8ff8f5 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-text/parsing/WEB_FEATURES.yml
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/parsing/WEB_FEATURES.yml
@@ -23,3 +23,9 @@
 - name: text-wrap-mode
   files:
   - text-wrap-mode-*
+- name: word-spacing
+  files:
+  - word-spacing-*
+- name: tab-size
+  files:
+  - tab-size-*
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/tab-size/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/css/css-text/tab-size/WEB_FEATURES.yml
new file mode 100644
index 0000000..c121092
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/tab-size/WEB_FEATURES.yml
@@ -0,0 +1,3 @@
+features:
+- name: tab-size
+  files: "**"
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/word-spacing/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/css/css-text/word-spacing/WEB_FEATURES.yml
new file mode 100644
index 0000000..6e28ca7
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/word-spacing/WEB_FEATURES.yml
@@ -0,0 +1,3 @@
+features:
+- name: word-spacing
+  files: "**"
diff --git a/third_party/blink/web_tests/external/wpt/css/css-transforms/parsing/perspective-invalid.html b/third_party/blink/web_tests/external/wpt/css/css-transforms/parsing/perspective-invalid.html
new file mode 100644
index 0000000..12b3860
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-transforms/parsing/perspective-invalid.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Transform Module Level 2: parsing perspective with invalid values</title>
+<link rel="help" href="https://drafts.csswg.org/css-transforms-2/#perspective-property">
+<meta name="assert" content="perspective supports only the grammar 'none | <length [0,∞]>'.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_invalid_value("perspective", "1000");
+test_invalid_value("perspective", "-1px");
+test_invalid_value("perspective", "80%");
+</script>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-transforms/parsing/transform-invalid.html b/third_party/blink/web_tests/external/wpt/css/css-transforms/parsing/transform-invalid.html
index 8985720..3891f5e 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-transforms/parsing/transform-invalid.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-transforms/parsing/transform-invalid.html
@@ -42,6 +42,8 @@
 test_invalid_value("transform", "skewY(0, 0)");
 
 test_invalid_value("transform", "scaleX(2), scaleY(3)");
+
+test_invalid_value("transform", "perspective(1000)");
 </script>
 </body>
 </html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-transforms/parsing/webkit-perspective-invalid.tentative-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-transforms/parsing/webkit-perspective-invalid.tentative-expected.txt
new file mode 100644
index 0000000..3184da1
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-transforms/parsing/webkit-perspective-invalid.tentative-expected.txt
@@ -0,0 +1,7 @@
+This is a testharness.js-based test.
+[FAIL] e.style['-webkit-perspective'] = "calc(1000)" should not set the property value
+  assert_equals: expected "" but got "1000px"
+[FAIL] e.style['-webkit-perspective'] = "calc(25)" should not set the property value
+  assert_equals: expected "" but got "25px"
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-transforms/parsing/webkit-perspective-invalid.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-transforms/parsing/webkit-perspective-invalid.tentative.html
new file mode 100644
index 0000000..fae8ada8
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-transforms/parsing/webkit-perspective-invalid.tentative.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Compatibility: parsing -webkit-perspective with invalid values</title>
+<link rel="help" href="https://compat.spec.whatwg.org/#propdef--webkit-perspective">
+<link rel="help" href="https://github.com/whatwg/compat/issues/100">
+<meta name="assert" content="-webkit-perspective also supports '<number [0,∞]>' in addition to 'none | <length [0,∞]>', but not 'calc([ <number [0,∞]> ])'">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_invalid_value("-webkit-perspective", "calc(1000)");
+test_invalid_value("-webkit-perspective", "calc(25)");
+</script>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-transforms/parsing/webkit-perspective-valid.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-transforms/parsing/webkit-perspective-valid.tentative.html
new file mode 100644
index 0000000..453462f
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-transforms/parsing/webkit-perspective-valid.tentative.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Compatibility: parsing -webkit-perspective with valid values</title>
+<link rel="help" href="https://compat.spec.whatwg.org/#propdef--webkit-perspective">
+<link rel="help" href="https://github.com/whatwg/compat/issues/100">
+<meta name="assert" content="-webkit-perspective also supports '<number [0,∞]>' in addition to 'none | <length [0,∞]>'">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_valid_value("-webkit-perspective", "initial");
+test_valid_value("-webkit-perspective", "inherit");
+test_valid_value("-webkit-perspective", "unset");
+test_valid_value("-webkit-perspective", "revert");
+test_valid_value("-webkit-perspective", "revert-layer");
+
+test_valid_value("-webkit-perspective", "none");
+test_valid_value("-webkit-perspective", "1000", "1000px");
+test_valid_value("-webkit-perspective", "25", "25px");
+test_valid_value("-webkit-perspective", "12px", "12px");
+test_valid_value("-webkit-perspective", "3.5em", "3.5em");
+</script>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-transforms/parsing/webkit-transform-valid.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-transforms/parsing/webkit-transform-valid.tentative.html
new file mode 100644
index 0000000..9490f0f5
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-transforms/parsing/webkit-transform-valid.tentative.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Compatibility: parsing -webkit-transform with valid values</title>
+<link rel="help" href="https://compat.spec.whatwg.org/#propdef--webkit-transform">
+<link rel="help" href="https://github.com/whatwg/compat/issues/100">
+<meta name="assert" content="-webkit-transform additionally supports the function call 'perspective([ <number [0,∞]> ])' in '<transform-list>'">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/parsing-testcommon.js"></script>
+</head>
+<body>
+<script>
+test_valid_value("-webkit-transform", "initial");
+test_valid_value("-webkit-transform", "inherit");
+test_valid_value("-webkit-transform", "unset");
+test_valid_value("-webkit-transform", "revert");
+test_valid_value("-webkit-transform", "revert-layer");
+
+test_valid_value("-webkit-transform", "perspective(1000)", "perspective(1000px)");
+test_valid_value("-webkit-transform", "perspective(25)", "perspective(25px)");
+test_valid_value("-webkit-transform", "perspective(12px)");
+test_valid_value("-webkit-transform", "perspective(3.5em)");
+</script>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-transitions/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/css/css-transitions/WEB_FEATURES.yml
index 472d6df1..e50ab991 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-transitions/WEB_FEATURES.yml
+++ b/third_party/blink/web_tests/external/wpt/css/css-transitions/WEB_FEATURES.yml
@@ -15,3 +15,6 @@
   - transition-timing-function-004-manual.html
   - transition-timing-function-005-manual.html
   - transition-timing-function-006-manual.html
+- name: clip
+  files:
+  - transition-property-017-manual.html
diff --git a/third_party/blink/web_tests/external/wpt/css/css-typed-om/the-stylepropertymap/properties/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/css/css-typed-om/the-stylepropertymap/properties/WEB_FEATURES.yml
new file mode 100644
index 0000000..fcba35b
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-typed-om/the-stylepropertymap/properties/WEB_FEATURES.yml
@@ -0,0 +1,4 @@
+features:
+- name: speak
+  files:
+  - speak.html
diff --git a/third_party/blink/web_tests/external/wpt/css/css-ui/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/css/css-ui/WEB_FEATURES.yml
index d40280d..48f2608 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-ui/WEB_FEATURES.yml
+++ b/third_party/blink/web_tests/external/wpt/css/css-ui/WEB_FEATURES.yml
@@ -9,6 +9,13 @@
 - name: outline
   files:
   - outline-*
+- name: resize
+  files:
+  - resize-*
 - name: user-select
   files:
   - user-select-*
+- name: caret-color
+  files:
+  - caret-color-*
+  - caret-eol-*
diff --git a/third_party/blink/web_tests/external/wpt/css/css-ui/animation/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/css/css-ui/animation/WEB_FEATURES.yml
index 07cdf85..3a82d31 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-ui/animation/WEB_FEATURES.yml
+++ b/third_party/blink/web_tests/external/wpt/css/css-ui/animation/WEB_FEATURES.yml
@@ -2,3 +2,6 @@
 - name: accent-color
   files:
   - accent-color-*
+- name: caret-color
+  files:
+  - caret-color-*
diff --git a/third_party/blink/web_tests/external/wpt/css/css-ui/outline-dynamic.html b/third_party/blink/web_tests/external/wpt/css/css-ui/outline-dynamic.html
new file mode 100644
index 0000000..7c422a9
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-ui/outline-dynamic.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<title>CSS Test: outline - dynamic changes</title>
+<link rel="author" title="Oriol Brufau" href="obrufau@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-ui-4/#outline" />
+<link rel="match" href="../reference/ref-filled-green-100px-square-only.html">
+<meta name="assert" content="Checks that outline can be changed dynamically.">
+<style>
+div {
+  position: absolute;
+}
+#red {
+  background: red;
+}
+#target {
+  margin: 50px;
+}
+#target.outline {
+  outline: solid 50px green;
+}
+</style>
+
+<p>Test passes if there is a filled green square.</p>
+
+<div id="red"></div>
+<div id="target"></div>
+
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/rendering-utils.js"></script>
+<script>
+waitForAtLeastOneFrame().then(() => {
+  document.getElementById("target").className = "outline";
+  takeScreenshot();
+});
+</script>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-ui/parsing/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/css/css-ui/parsing/WEB_FEATURES.yml
index 3be770b..a01e785d 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-ui/parsing/WEB_FEATURES.yml
+++ b/third_party/blink/web_tests/external/wpt/css/css-ui/parsing/WEB_FEATURES.yml
@@ -5,3 +5,6 @@
 - name: user-select
   files:
   - user-select-*
+- name: caret-color
+  files:
+  - caret-color-*
diff --git a/third_party/blink/web_tests/external/wpt/css/css-values/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/css/css-values/WEB_FEATURES.yml
index d2964c6c..92b34ec1 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-values/WEB_FEATURES.yml
+++ b/third_party/blink/web_tests/external/wpt/css/css-values/WEB_FEATURES.yml
@@ -36,6 +36,9 @@
 - name: rch
   files:
   - rch-*
+- name: ex
+  files:
+  - ex-*
 - name: rex
   files:
   - rex-*
diff --git a/third_party/blink/web_tests/external/wpt/css/css-view-transitions/view-transition-types-mutable.html b/third_party/blink/web_tests/external/wpt/css/css-view-transitions/view-transition-types-mutable.html
index 86c77d6..d5f0644 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-view-transitions/view-transition-types-mutable.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-view-transitions/view-transition-types-mutable.html
@@ -4,16 +4,18 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script>
-
-    test(() => {
+  promise_test(async () => {
     assert_implements(document.startViewTransition);
-    const { types } = document.startViewTransition();
-    assert_true(types instanceof ViewTransitionTypeSet);
+    let transition = document.startViewTransition();
+    assert_true(transition.types instanceof ViewTransitionTypeSet);
+    await transition.finished;
   }, "ViewTransition.types is a ViewTransitionTypeSet");
 
-  test(() => {
+  promise_test(async () => {
     assert_implements(document.startViewTransition);
-    const { types } = document.startViewTransition();
+    let transition = document.startViewTransition();
+    let types = transition.types;
+    assert_equals(types, transition.types, "Should return the same object");
     types.add("a");
     types.add("b");
     assert_array_equals([...types], ["a", "b"]);
@@ -27,6 +29,7 @@
     types.add("");
     types.add("123");
     assert_array_equals([...types], ["a", ".", "", "123"]);
+    await transition.finished;
   }, "ViewTransitionTypeSet behaves like an ordinary Set of strings");
 
   promise_test(async () => {
diff --git a/third_party/blink/web_tests/external/wpt/css/css-will-change/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/css/css-will-change/WEB_FEATURES.yml
new file mode 100644
index 0000000..ee3fa15
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-will-change/WEB_FEATURES.yml
@@ -0,0 +1,3 @@
+features:
+- name: will-change
+  files: '**'
diff --git a/third_party/blink/web_tests/external/wpt/css/css-writing-modes/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/css/css-writing-modes/WEB_FEATURES.yml
new file mode 100644
index 0000000..7411597
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-writing-modes/WEB_FEATURES.yml
@@ -0,0 +1,4 @@
+features:
+- name: clip
+  files:
+  - clip-rect-*
diff --git a/third_party/blink/web_tests/external/wpt/css/cssom-view/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/css/cssom-view/WEB_FEATURES.yml
index d5352fb..8f19ffa 100644
--- a/third_party/blink/web_tests/external/wpt/css/cssom-view/WEB_FEATURES.yml
+++ b/third_party/blink/web_tests/external/wpt/css/cssom-view/WEB_FEATURES.yml
@@ -2,6 +2,11 @@
 - name: check-visibility
   files:
   - checkVisibility.html
+- name: element-from-point
+  files:
+  - elementFromPoint*
+  - elementsFromPoint*
+  - negativeMargins.html
 - name: scroll-into-view
   files:
   - scrollIntoView-*
diff --git a/third_party/blink/web_tests/external/wpt/css/cssom-view/getBoundingClientRect-content-visibility-hidden.html b/third_party/blink/web_tests/external/wpt/css/cssom-view/getBoundingClientRect-content-visibility-hidden.html
new file mode 100644
index 0000000..b1ca1c3
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/cssom-view/getBoundingClientRect-content-visibility-hidden.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/cssom-view/#dom-element-getboundingclientrect">
+<link rel="help" href="https://drafts.csswg.org/cssom-view/#dom-range-getboundingclientrect">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1918733">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+</style>
+<body>
+  <div style="content-visibility: hidden; contain-intrinsic-size: 100px 100px;">
+      <span id="span">Hello</span>
+  </div>
+<script>
+  const rangeLeaf = document.createRange();
+  const rangeRoot = document.createRange();
+  rangeLeaf.selectNode(span.firstChild);
+  rangeRoot.selectNode(document.documentElement);
+  const rectLeaf = rangeLeaf.getBoundingClientRect();
+  const rectRoot = rangeRoot.getBoundingClientRect();
+  test(() => assert_not_equals(rectLeaf.width, 0, "leaf rect width should not be zero"));
+  test(() => assert_not_equals(rectLeaf.height, 0, "leaf rect height should not be zero"));
+  test(() => assert_not_equals(rectRoot.width, 0, "root rect width should not be zero"));
+  test(() => assert_not_equals(rectRoot.height, 0, "root rect height should not be zero"));
+</script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/css/cssom-view/scrollIntoView-nearest-visible-element.html b/third_party/blink/web_tests/external/wpt/css/cssom-view/scrollIntoView-nearest-visible-element.html
new file mode 100644
index 0000000..1387c27
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/cssom-view/scrollIntoView-nearest-visible-element.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>scrollIntoView nearest shouldn't scroll a completely visible element</title>
+  <link rel="author" title="Jo Steven Novaryo" href="mailto:steven.novaryo@gmail.com">
+  <link rel="help" href="https://drafts.csswg.org/cssom-view/#determine-the-scroll-into-view-position">
+  <script src="/resources/testharness.js"></script>
+  <script src="/resources/testharnessreport.js"></script>
+</head>
+
+<style>
+#container {
+  width: 300px;
+  height: 300px;
+  overflow: scroll;
+}
+
+#target {
+  width: 100px;
+  height: 100px;
+  position: relative;
+  top: 600px;
+  left: 800px;
+  background-color: aqua;
+}
+
+#overflowing {
+  width: 3000px;
+  height: 3000px;
+}
+</style>
+
+<div id="container">
+  <div id="target">
+  </div>
+  <div id="overflowing"></div>
+</div>
+
+<script>
+  test(function() {
+    container.scrollTop = 500;
+    container.scrollLeft = 700;
+    let initial_target_rect = target.getBoundingClientRect();
+    target.scrollIntoView({block: 'nearest', inline: 'nearest'});
+
+    let final_target_rect = target.getBoundingClientRect();
+    assert_equals(initial_target_rect.x, final_target_rect.x, 'Target x position remain unchanged');
+    assert_equals(initial_target_rect.y, final_target_rect.y, 'Target y position remain unchanged');
+  }, "scrollIntoView with `nearest` block and inline option shouldn't scroll a completely visible element");
+</script>
+</html>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/css/selectors/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/css/selectors/WEB_FEATURES.yml
index 05b5047..2093333a0 100644
--- a/third_party/blink/web_tests/external/wpt/css/selectors/WEB_FEATURES.yml
+++ b/third_party/blink/web_tests/external/wpt/css/selectors/WEB_FEATURES.yml
@@ -26,3 +26,6 @@
   files:
   - user-invalid.html
   - user-valid.html
+- name: caret-color
+  files:
+  - caret-color-visited-inheritance.html
diff --git a/third_party/blink/web_tests/external/wpt/css/selectors/invalidation/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/css/selectors/invalidation/WEB_FEATURES.yml
index f0fb9ee..773d02b 100644
--- a/third_party/blink/web_tests/external/wpt/css/selectors/invalidation/WEB_FEATURES.yml
+++ b/third_party/blink/web_tests/external/wpt/css/selectors/invalidation/WEB_FEATURES.yml
@@ -23,3 +23,8 @@
 - name: state
   files:
   - state-in-has.html
+- name: shadow-parts
+  files:
+  - part-dir.html
+  - part-lang.html
+  - part-pseudo.html
diff --git a/third_party/blink/web_tests/external/wpt/css/selectors/parsing/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/css/selectors/parsing/WEB_FEATURES.yml
index 1754135..1bd3e778 100644
--- a/third_party/blink/web_tests/external/wpt/css/selectors/parsing/WEB_FEATURES.yml
+++ b/third_party/blink/web_tests/external/wpt/css/selectors/parsing/WEB_FEATURES.yml
@@ -15,3 +15,6 @@
 - name: not
   files:
   - parse-not.html
+- name: shadow-parts
+  files:
+  - parse-part.html
diff --git a/third_party/blink/web_tests/external/wpt/custom-elements/registries/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/custom-elements/registries/WEB_FEATURES.yml
index 7dbe4c6..627c7bb 100644
--- a/third_party/blink/web_tests/external/wpt/custom-elements/registries/WEB_FEATURES.yml
+++ b/third_party/blink/web_tests/external/wpt/custom-elements/registries/WEB_FEATURES.yml
@@ -2,7 +2,7 @@
 - name: autonomous-custom-elements
   files:
   - define.html
-  - per-global.html
+  - per-document.html
   - upgrade.html
   - valid-custom-element-names.html
 - name: customized-built-in-elements
@@ -13,6 +13,6 @@
   - "*"
   - "!define-customized-builtins.html"
   - "!define.html"
-  - "!per-global.html"
+  - "!per-document.html"
   - "!upgrade.html"
   - "!valid-custom-element-names.html"
diff --git a/third_party/blink/web_tests/external/wpt/custom-elements/registries/element-mutation-expected.txt b/third_party/blink/web_tests/external/wpt/custom-elements/registries/element-mutation-expected.txt
index 63e9e41..a9ca13b2 100644
--- a/third_party/blink/web_tests/external/wpt/custom-elements/registries/element-mutation-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/custom-elements/registries/element-mutation-expected.txt
@@ -5,5 +5,7 @@
   Failed to construct 'CustomElementRegistry': Illegal constructor
 [FAIL] An element with scoped registry should not change its registry when moved into another shadow tree with different scoped registry.
   Failed to construct 'CustomElementRegistry': Illegal constructor
+[FAIL] Declarative shadow DOM with shadowrootcustomelementregistry attribute without registry initialized should remain null registry after adoption.
+  assert_equals: expected (object) object "[object CustomElementRegistry]" but got (undefined) undefined
 Harness: the test ran to completion.
 
diff --git a/third_party/blink/web_tests/external/wpt/custom-elements/registries/element-mutation.html b/third_party/blink/web_tests/external/wpt/custom-elements/registries/element-mutation.html
index 9ed6fa7d..817965d 100644
--- a/third_party/blink/web_tests/external/wpt/custom-elements/registries/element-mutation.html
+++ b/third_party/blink/web_tests/external/wpt/custom-elements/registries/element-mutation.html
@@ -20,6 +20,15 @@
     </div>
 </div>
 
+<template id="template">
+    <div id="template-host-1">
+        <template shadowrootmode="open" shadowrootclonable><x-foo></x-foo></template>
+    </div>
+    <div id="template-host-2">
+        <template shadowrootmode="open" shadowrootclonable shadowrootcustomelementregistry><x-foo></x-foo></template>
+    </div>
+</template>
+
 <script>
 
 test(() => {
@@ -66,5 +75,13 @@
     assert_equals(element.customElementRegistry, registry1);
 }, "An element with scoped registry should not change its registry when moved into another shadow tree with different scoped registry.")
 
+test(() => {
+    customElements.define('x-foo', class extends HTMLElement {});
+    const template = document.getElementById('template');
+    document.body.append(template.content.cloneNode(true));
+    assert_equals(document.querySelector('#template-host-1').shadowRoot.querySelector('x-foo').customElementRegistry, customElements);
+    assert_equals(document.querySelector('#template-host-2').shadowRoot.querySelector('x-foo').customElementRegistry, null);
+}, "Declarative shadow DOM with shadowrootcustomelementregistry attribute without registry initialized should remain null registry after adoption.")
+
 </script>
 </body>
diff --git a/third_party/blink/web_tests/external/wpt/custom-elements/registries/per-global.html b/third_party/blink/web_tests/external/wpt/custom-elements/registries/per-document.html
similarity index 78%
rename from third_party/blink/web_tests/external/wpt/custom-elements/registries/per-global.html
rename to third_party/blink/web_tests/external/wpt/custom-elements/registries/per-document.html
index 3570dcf8..50e329ed 100644
--- a/third_party/blink/web_tests/external/wpt/custom-elements/registries/per-global.html
+++ b/third_party/blink/web_tests/external/wpt/custom-elements/registries/per-document.html
@@ -1,6 +1,6 @@
 <!DOCTYPE html>
 <meta charset="utf-8">
-<title>Custom Elements: CustomElementRegistry is per global</title>
+<title>Custom Elements: window.customElements is per document</title>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <link rel="help" href="https://html.spec.whatwg.org/multipage/#custom-elements-api">
@@ -10,5 +10,5 @@
 <body>
 <script>
 "use strict";
-testIsPerWindow("customElements");
+testIsPerDocument("customElements");
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/custom-elements/registries/per-global-expected.txt b/third_party/blink/web_tests/external/wpt/custom-elements/registries/per-global-expected.txt
deleted file mode 100644
index b287f4e..0000000
--- a/third_party/blink/web_tests/external/wpt/custom-elements/registries/per-global-expected.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-This is a testharness.js-based test.
-[FAIL] Navigating from the initial about:blank must not replace window.customElements
-  assert_equals: expected object "[object CustomElementRegistry]" but got object "[object CustomElementRegistry]"
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/custom-elements/registries/template.window-expected.txt b/third_party/blink/web_tests/external/wpt/custom-elements/registries/template.window-expected.txt
index d9af626..7c7285c 100644
--- a/third_party/blink/web_tests/external/wpt/custom-elements/registries/template.window-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/custom-elements/registries/template.window-expected.txt
@@ -3,7 +3,17 @@
   assert_equals: expected (string) "" but got (undefined) undefined
 [FAIL] Serializing a ShadowRoot with a null registry
   assert_equals: expected "<div><template shadowrootmode=\\"open\\" shadowrootserializable=\\"\\" shadowrootcustomelementregistry=\\"\\"></template></div>" but got "<div><template shadowrootmode=\\"open\\" shadowrootserializable=\\"\\"></template></div>"
-[FAIL] Serializing a ShadowRoot with a registry that differs from its host
+[FAIL] Serializing a ShadowRoot with a global registry
+  assert_equals: expected (object) object "[object CustomElementRegistry]" but got (undefined) undefined
+[FAIL] Serializing a ShadowRoot with a registry that differs from its host document
+  Failed to construct 'CustomElementRegistry': Illegal constructor
+[FAIL] Serializing a ShadowRoot with a null registry with a null registry host document
+  assert_equals: expected (object) null but got (undefined) undefined
+[FAIL] Serializing a ShadowRoot with a registry with a null registry host document
+  assert_equals: expected (object) null but got (undefined) undefined
+[FAIL] Serializing a ShadowRoot with a null registry with a scoped registry host document
+  Failed to construct 'CustomElementRegistry': Illegal constructor
+[FAIL] A declarative shadow root gets its default registry from its node document
   Failed to construct 'CustomElementRegistry': Illegal constructor
 Harness: the test ran to completion.
 
diff --git a/third_party/blink/web_tests/external/wpt/custom-elements/registries/template.window.js b/third_party/blink/web_tests/external/wpt/custom-elements/registries/template.window.js
index 7d442a6..9b908bad 100644
--- a/third_party/blink/web_tests/external/wpt/custom-elements/registries/template.window.js
+++ b/third_party/blink/web_tests/external/wpt/custom-elements/registries/template.window.js
@@ -17,9 +17,53 @@
 
 test(() => {
   const div = document.createElement("div");
+  div.setHTMLUnsafe(`<div><template shadowrootmode=open shadowrootserializable></template></div>`);
+  assert_equals(div.firstChild.firstChild, null);
+  assert_equals(div.firstChild.shadowRoot.customElementRegistry, customElements);
+  assert_equals(div.getHTML({ serializableShadowRoots: true }), "<div><template shadowrootmode=\"open\" shadowrootserializable=\"\"></template></div>");
+}, "Serializing a ShadowRoot with a global registry");
+
+test(() => {
+  const div = document.createElement("div");
   div.setHTMLUnsafe(`<div><template shadowrootmode=open shadowrootcustomelementregistry shadowrootserializable></template></div>`);
   const registry = new CustomElementRegistry();
   registry.initialize(div.firstChild.shadowRoot);
   assert_equals(div.firstChild.shadowRoot.customElementRegistry, registry);
   assert_equals(div.getHTML({ serializableShadowRoots: true }), "<div><template shadowrootmode=\"open\" shadowrootserializable=\"\" shadowrootcustomelementregistry=\"\"></template></div>");
-}, "Serializing a ShadowRoot with a registry that differs from its host");
+}, "Serializing a ShadowRoot with a registry that differs from its host document");
+
+test(() => {
+  const div = document.implementation.createHTMLDocument().createElement("div");
+  assert_equals(div.customElementRegistry, null);
+  div.setHTMLUnsafe(`<div><template shadowrootmode=open shadowrootcustomelementregistry shadowrootserializable></template></div>`);
+  assert_equals(div.firstChild.shadowRoot.customElementRegistry, null);
+  assert_equals(div.getHTML({ serializableShadowRoots: true }), "<div><template shadowrootmode=\"open\" shadowrootserializable=\"\"></template></div>");
+}, "Serializing a ShadowRoot with a null registry with a null registry host document");
+
+test(() => {
+  const div = document.implementation.createHTMLDocument().createElement("div");
+  assert_equals(div.customElementRegistry, null);
+  div.setHTMLUnsafe(`<div><template shadowrootmode=open shadowrootcustomelementregistry shadowrootserializable></template></div>`);
+  const registry = new CustomElementRegistry();
+  registry.initialize(div.firstChild.shadowRoot);
+  assert_equals(div.firstChild.shadowRoot.customElementRegistry, registry);
+  assert_equals(div.getHTML({ serializableShadowRoots: true }), "<div><template shadowrootmode=\"open\" shadowrootserializable=\"\" shadowrootcustomelementregistry=\"\"></template></div>");
+}, "Serializing a ShadowRoot with a registry with a null registry host document");
+
+test(() => {
+  const registry = new CustomElementRegistry();
+  const hostDocument = document.implementation.createHTMLDocument();
+  registry.initialize(hostDocument);
+  assert_equals(hostDocument.customElementRegistry, registry);
+  const host = hostDocument.createElement('div');
+  const shadow = host.attachShadow({ mode: "closed", serializable: true, customElementRegistry: null });
+  assert_equals(host.getHTML({ serializableShadowRoots: true }), `<template shadowrootmode="closed" shadowrootserializable="" shadowrootcustomelementregistry=""></template>`);
+}, "Serializing a ShadowRoot with a null registry with a scoped registry host document");
+
+test(() => {
+  const registry = new CustomElementRegistry();
+  const element = document.createElement('a-b', { customElementRegistry: registry });
+  element.setHTMLUnsafe(`<a-b><template shadowrootmode="open"></template></a-b>`);
+  assert_equals(element.firstChild.customElementRegistry, registry);
+  assert_equals(element.firstChild.shadowRoot.customElementRegistry, customElements);
+}, "A declarative shadow root gets its default registry from its node document");
diff --git a/third_party/blink/web_tests/external/wpt/density-size-correction/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/density-size-correction/WEB_FEATURES.yml
new file mode 100644
index 0000000..ac948e22
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/density-size-correction/WEB_FEATURES.yml
@@ -0,0 +1,4 @@
+features:
+- name: srcset
+  files:
+  - "srcset*.html"
diff --git a/third_party/blink/web_tests/external/wpt/digital-credentials/allow-attribute-with-create.https.html b/third_party/blink/web_tests/external/wpt/digital-credentials/allow-attribute-with-create.https.html
index 4b1e96f..c921f57 100644
--- a/third_party/blink/web_tests/external/wpt/digital-credentials/allow-attribute-with-create.https.html
+++ b/third_party/blink/web_tests/external/wpt/digital-credentials/allow-attribute-with-create.https.html
@@ -107,12 +107,15 @@
                                 // Results in TypeError when allowed, NotAllowedError when disallowed
                                 requests: [],
                             },
-                            mediation: "required",
                         };
                         const { data } = await new Promise((resolve) => {
-                            window.addEventListener("message", resolve, {
-                                once: true,
-                            });
+                            const callback = (e) => {
+                                if (e.source === iframe.contentWindow) {
+                                    window.removeEventListener('message', callback);
+                                    resolve(e);
+                                }
+                            }
+                            window.addEventListener("message", callback);
                             iframe.contentWindow.postMessage(
                                 { action, options, needsActivation: true },
                                 "*"
diff --git a/third_party/blink/web_tests/external/wpt/digital-credentials/allow-attribute-with-get.https.html b/third_party/blink/web_tests/external/wpt/digital-credentials/allow-attribute-with-get.https.html
index 58873dd..59fd851 100644
--- a/third_party/blink/web_tests/external/wpt/digital-credentials/allow-attribute-with-get.https.html
+++ b/third_party/blink/web_tests/external/wpt/digital-credentials/allow-attribute-with-get.https.html
@@ -108,13 +108,16 @@
                                 // NotAllowedError when disallowed
                                 requests: [],
                             },
-                            mediation: "required",
                         };
                         await test_driver.bless("User activation");
                         const { data } = await new Promise((resolve) => {
-                            window.addEventListener("message", resolve, {
-                                once: true,
-                            });
+                            const callback = (e) => {
+                                if (e.source === iframe.contentWindow) {
+                                    window.removeEventListener('message', callback);
+                                    resolve(e);
+                                }
+                            }
+                            window.addEventListener("message", callback);
                             iframe.contentWindow.postMessage(
                                 { action, options, needsActivation: true },
                                 "*"
diff --git a/third_party/blink/web_tests/external/wpt/digital-credentials/create.tentative.https-expected.txt b/third_party/blink/web_tests/external/wpt/digital-credentials/create.tentative.https-expected.txt
deleted file mode 100644
index ae711a28..0000000
--- a/third_party/blink/web_tests/external/wpt/digital-credentials/create.tentative.https-expected.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-This is a testharness.js-based test.
-[FAIL] Mediation is required to create a DigitalCredential.
-  assert_unreached: Should have rejected: undefined Reached unreachable code
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/digital-credentials/create.tentative.https.html b/third_party/blink/web_tests/external/wpt/digital-credentials/create.tentative.https.html
index 97dc04e..043b59f 100644
--- a/third_party/blink/web_tests/external/wpt/digital-credentials/create.tentative.https.html
+++ b/third_party/blink/web_tests/external/wpt/digital-credentials/create.tentative.https.html
@@ -17,7 +17,6 @@
 
   const iframeSameOrigin = document.querySelector("iframe#same-origin");
   const iframeCrossOrigin = document.querySelector("iframe#cross-origin");
-  const mediations = ["silent", "optional", "conditional", "required"];
 
   promise_setup(async () => {
     const hostInfo = get_host_info();
@@ -209,6 +208,8 @@
   }, "navigator.credentials.create() promise is rejected if abort controller is aborted after call to create() in cross-origin iframe.");
 
   promise_test(async (t) => {
+    /** @type sequence<CredentialMediationRequirement> */
+    const mediations = ["silent", "optional", "conditional", "required"];
     const abortController = new AbortController();
     const { signal } = abortController;
     abortController.abort();
@@ -219,16 +220,7 @@
       });
       await promise_rejects_dom(t, "AbortError", requestPromise);
     }
-  }, "Adding mediations together with abort signal respects the abort signal.");
-
-  promise_test(async (t) => {
-    /** @type sequence<CredentialMediationRequirement> */
-    const disallowedMediations = [ "conditional", "optional", "silent"];
-    for (const mediation of disallowedMediations) {
-      const options = makeCreateOptions("default", mediation);
-      await promise_rejects_js(t, TypeError, navigator.credentials.create(options));
-    }
-  }, "Mediation is required to create a DigitalCredential.");
+  }, "Mediation is implicitly required and hence ignored. Request is aborted regardless.");
 
   promise_test(async (t) => {
     await promise_rejects_js(
diff --git a/third_party/blink/web_tests/external/wpt/digital-credentials/dc-types.ts b/third_party/blink/web_tests/external/wpt/digital-credentials/dc-types.ts
index 1f3d620..bd9fdb7 100644
--- a/third_party/blink/web_tests/external/wpt/digital-credentials/dc-types.ts
+++ b/third_party/blink/web_tests/external/wpt/digital-credentials/dc-types.ts
@@ -1,4 +1,4 @@
-export type GetProtocol = "default" | "openid4vp";
+export type GetProtocol = "default" | "openid4vp-v1-unsigned" | "openid4vp-v1-signed" | "openid4vp-v1-multisigned";
 export type CreateProtocol = "default" | "openid4vci";
 
 export type CredentialMediationRequirement =
diff --git a/third_party/blink/web_tests/external/wpt/digital-credentials/get.tentative.https-expected.txt b/third_party/blink/web_tests/external/wpt/digital-credentials/get.tentative.https-expected.txt
deleted file mode 100644
index ee5d967..0000000
--- a/third_party/blink/web_tests/external/wpt/digital-credentials/get.tentative.https-expected.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-This is a testharness.js-based test.
-[FAIL] Mediation is required to get a DigitalCredential.
-  assert_unreached: Should have rejected: undefined Reached unreachable code
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/digital-credentials/get.tentative.https.html b/third_party/blink/web_tests/external/wpt/digital-credentials/get.tentative.https.html
index 1549bba..e5d241d 100644
--- a/third_party/blink/web_tests/external/wpt/digital-credentials/get.tentative.https.html
+++ b/third_party/blink/web_tests/external/wpt/digital-credentials/get.tentative.https.html
@@ -36,7 +36,7 @@
           requests: [
             {
               data,
-              protocol: "openid4vp",
+              protocol: "openid4vp-v1-unsigned",
             },
           ],
         },
@@ -206,7 +206,7 @@
   promise_test(async (t) => {
     const abortController = new AbortController();
     const { signal } = abortController;
-    const options = makeGetOptions("openid4vp");
+    const options = makeGetOptions("openid4vp-v1-unsigned");
     options.signal = signal;
     await test_driver.bless("user activation");
     const promise = promise_rejects_dom(
@@ -225,7 +225,7 @@
       abort: "after",
       action: "get",
       needsActivation: true,
-      options: makeGetOptions("openid4vp"),
+      options: makeGetOptions("openid4vp-v1-signed"),
     });
     assert_equals(result.constructor, "DOMException");
     assert_equals(result.name, "AbortError");
@@ -233,16 +233,18 @@
 
   promise_test(async (t) => {
     /** @type sequence<CredentialMediationRequirement> */
-    const disallowedMediations = ["conditional", "optional", "silent"];
-    for (const mediation of disallowedMediations) {
-      const options = makeGetOptions("default", mediation);
-      await promise_rejects_js(
-        t,
-        TypeError,
-        navigator.credentials.get(options)
-      );
+    const mediations = ["silent", "optional", "conditional", "required"];
+    const abortController = new AbortController();
+    const { signal } = abortController;
+    abortController.abort();
+    for (const mediation of mediations) {
+      const requestPromise = navigator.credentials.get({
+        mediation,
+        signal,
+      });
+      await promise_rejects_dom(t, "AbortError", requestPromise);
     }
-  }, "Mediation is required to get a DigitalCredential.");
+  }, "Mediation is implicitly required and hence ignored. Request is aborted regardless.");
 
 promise_test(async t => {
   const throwingValues = [
@@ -252,7 +254,7 @@
   ];
 
   for (const badValue of throwingValues) {
-    const options = makeGetOptions("openid4vp");
+    const options = makeGetOptions("openid4vp-v1-multisigned");
     options.digital.requests[0].data = badValue;
 
     await promise_rejects_js(
diff --git a/third_party/blink/web_tests/external/wpt/digital-credentials/non-fully-active.https.html b/third_party/blink/web_tests/external/wpt/digital-credentials/non-fully-active.https.html
index 61f40d1..e1fdca3 100644
--- a/third_party/blink/web_tests/external/wpt/digital-credentials/non-fully-active.https.html
+++ b/third_party/blink/web_tests/external/wpt/digital-credentials/non-fully-active.https.html
@@ -71,7 +71,7 @@
     let iframe = await createIframe();
     const DOMExceptionCtor = iframe.contentWindow.DOMException;
     let stolenNavigator = iframe.contentWindow.navigator;
-    const request = makeGetOptions("openid4vp");
+    const request = makeGetOptions("openid4vp-v1-unsigned");
     await test_driver.bless("User activation", null, iframe.contentWindow);
     await iframe.focus();
     const p = promise_rejects_dom(
diff --git a/third_party/blink/web_tests/external/wpt/digital-credentials/support/helper.js b/third_party/blink/web_tests/external/wpt/digital-credentials/support/helper.js
index 5a9cda34..d79ed26 100644
--- a/third_party/blink/web_tests/external/wpt/digital-credentials/support/helper.js
+++ b/third_party/blink/web_tests/external/wpt/digital-credentials/support/helper.js
@@ -38,7 +38,9 @@
 
 const allMappings = {
   get: {
-    "openid4vp": () => makeOID4VPDict(),
+    "openid4vp-v1-unsigned": () => makeOID4VPDict("openid4vp-v1-unsigned"),
+    "openid4vp-v1-signed": () => makeOID4VPDict("openid4vp-v1-signed"),
+    "openid4vp-v1-multisigned": () => makeOID4VPDict("openid4vp-v1-multisigned"),
     "default": () => makeDigitalCredentialGetRequest(undefined, undefined),
   },
   create: {
@@ -96,7 +98,7 @@
 /**
  * Creates options for getting credentials.
  * @export
- * @param {string | string[]} [requestsToUse] - Request types ('default', 'openid4vp', or an array). Defaults to ['default'].
+ * @param {string | string[]} [requestsToUse] - Request types ('default', 'openid4vp-v1-unsigned', 'openid4vp-v1-signed', 'openid4vp-v1-multisigned', or an array). Defaults to ['default'].
  * @param {string} [mediation="required"] - Credential mediation requirement ("required", "optional", "silent").
  * @returns {{ digital: { requests: any[] }, mediation: string }}
  */
@@ -133,10 +135,11 @@
 /**
  * Representation of an OpenID4VP request.
  *
+ * @param {string} identifier
  * @returns {DigitalCredentialGetRequest}
  **/
-function makeOID4VPDict() {
-  return makeDigitalCredentialGetRequest("openid4vp", {
+function makeOID4VPDict(identifier = "openid4vp-v1-unsigned") {
+  return makeDigitalCredentialGetRequest(identifier, {
     // Canonical example of an OpenID4VP request coming soon.
   });
 }
@@ -201,8 +204,8 @@
  */
 export function loadIframe(iframe, url) {
   return new Promise((resolve, reject) => {
-    iframe.addEventListener("load", resolve, { once: true });
-    iframe.addEventListener("error", reject, { once: true });
+    iframe.addEventListener("load", () => resolve(), { once: true });
+    iframe.addEventListener("error", (event) => reject(event.error), { once: true });
     if (!iframe.isConnected) {
       document.body.appendChild(iframe);
     }
diff --git a/third_party/blink/web_tests/external/wpt/digital-credentials/user-activation.https.html b/third_party/blink/web_tests/external/wpt/digital-credentials/user-activation.https.html
index 1189c32..476c9bf 100644
--- a/third_party/blink/web_tests/external/wpt/digital-credentials/user-activation.https.html
+++ b/third_party/blink/web_tests/external/wpt/digital-credentials/user-activation.https.html
@@ -14,7 +14,7 @@
       navigator.userActivation.isActive,
       "User activation should not be active"
     );
-    const options = makeGetOptions("openid4vp");
+    const options = makeGetOptions("openid4vp-v1-unsigned");
     await promise_rejects_dom(
       t,
       "NotAllowedError",
@@ -25,7 +25,7 @@
   promise_test(async (t) => {
     await test_driver.bless();
     const abort = new AbortController();
-    const options = makeGetOptions("openid4vp");
+    const options = makeGetOptions("openid4vp-v1-unsigned");
     options.signal = abort.signal;
     assert_true(
       navigator.userActivation.isActive,
diff --git a/third_party/blink/web_tests/external/wpt/docs/writing-tests/channels.md b/third_party/blink/web_tests/external/wpt/docs/writing-tests/channels.md
index ccccd3c..1ab6e00 100644
--- a/third_party/blink/web_tests/external/wpt/docs/writing-tests/channels.md
+++ b/third_party/blink/web_tests/external/wpt/docs/writing-tests/channels.md
@@ -75,9 +75,9 @@
 ```html
 <!doctype html>
 <title>call example</title>
-<script src="/resources/testharness.js">
-<script src="/resources/testharnessreport.js">
-<script src="/resources/channel.js">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/channels.sub.js"></script>
 
 <script>
 promise_test(async t => {
@@ -94,7 +94,8 @@
 child.html
 
 ```html
-<script src="/resources/channel.js">
+<!doctype html>
+<script src="/resources/channels.sub.js"></script>
 
 <p id="nottest">FAIL</p>
 <p id="test">PASS</p>
diff --git a/third_party/blink/web_tests/external/wpt/dom/events/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/dom/events/WEB_FEATURES.yml
new file mode 100644
index 0000000..d391160
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/dom/events/WEB_FEATURES.yml
@@ -0,0 +1,4 @@
+features:
+- name: change-event
+  files:
+  - legacy-pre-activation-behavior.window.js
diff --git a/third_party/blink/web_tests/external/wpt/dom/nodes/crashtests/document-replaceChild-input-documentElement.html b/third_party/blink/web_tests/external/wpt/dom/nodes/crashtests/document-replaceChild-input-documentElement.html
new file mode 100644
index 0000000..e435c6c
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/dom/nodes/crashtests/document-replaceChild-input-documentElement.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<title>Node.replaceChild()</title>
+<link rel="author" title="Oriol Brufau" href="obrufau@igalia.com">
+<link rel="help" href="https://dom.spec.whatwg.org/#dom-node-replacechild">
+<link rel="help" href="https://github.com/servo/servo/issues/40920">
+<script>
+document.replaceChild(document.createElement("input"), document.documentElement);
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/dom/nodes/insertion-removing-steps/insertion-removing-steps-iframe.window.js b/third_party/blink/web_tests/external/wpt/dom/nodes/insertion-removing-steps/insertion-removing-steps-iframe.window.js
index 60c2bec..16a5021 100644
--- a/third_party/blink/web_tests/external/wpt/dom/nodes/insertion-removing-steps/insertion-removing-steps-iframe.window.js
+++ b/third_party/blink/web_tests/external/wpt/dom/nodes/insertion-removing-steps/insertion-removing-steps-iframe.window.js
@@ -29,7 +29,7 @@
   });
 
   let iframe1Loaded = false, iframe2Loaded = false;
-  iframe1.onload = e => {
+  iframe1.onload = t.step_func(e => {
     // iframe1 assertions:
     iframe1Loaded = true;
     assert_equals(window.frames.length, 1,
@@ -45,15 +45,15 @@
     assert_equals(iframe2.contentWindow, null,
         "... but iframe1 cannot observe iframe2's contentWindow because " +
         "iframe2's insertion steps have not been run yet");
-  };
+  });
 
-  iframe2.onload = e => {
+  iframe2.onload = t.step_func(e => {
     iframe2Loaded = true;
     assert_equals(window.frames.length, 2,
         "iframe2 load event can observe its own participation in the frame tree");
     assert_equals(iframe1.contentWindow, window.frames[0]);
     assert_equals(iframe2.contentWindow, window.frames[1]);
-  };
+  });
 
   // Synchronously consecutively adds both `iframe1` and `iframe2` to the DOM,
   // invoking their insertion steps (and thus firing each of their `load`
@@ -91,7 +91,7 @@
     // mutations (removals) are only observed atomically at the end. Specifically,
     // the observer's callback is not invoked synchronously for each removal.
     let observerCallbackInvoked = false;
-    const removalObserver = new MutationObserver(mutations => {
+    const removalObserver = new MutationObserver(t.step_func(mutations => {
       assert_false(observerCallbackInvoked,
           "MO callback is only invoked once, not multiple times, i.e., for " +
           "each removal");
@@ -103,7 +103,7 @@
       assert_equals(document.querySelector('iframe'), null,
           "No iframe elements are connected to the DOM when the MO callback is " +
           "run");
-    });
+    }));
 
     removalObserver.observe(div, {childList: true});
     t.add_cleanup(() => removalObserver.disconnect());
diff --git a/third_party/blink/web_tests/external/wpt/dom/xslt/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/dom/xslt/WEB_FEATURES.yml
new file mode 100644
index 0000000..4593bc6
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/dom/xslt/WEB_FEATURES.yml
@@ -0,0 +1,3 @@
+features:
+- name: xslt
+  files: '**'
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/domxpath/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/domxpath/WEB_FEATURES.yml
new file mode 100644
index 0000000..50139ec
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/domxpath/WEB_FEATURES.yml
@@ -0,0 +1,3 @@
+features:
+- name: xpath
+  files: '**'
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/editing/crashtests/join-first-li-containing-preformatted-linefeed-and-last-li.html b/third_party/blink/web_tests/external/wpt/editing/crashtests/join-first-li-containing-preformatted-linefeed-and-last-li.html
new file mode 100644
index 0000000..3f2deed
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/editing/crashtests/join-first-li-containing-preformatted-linefeed-and-last-li.html
@@ -0,0 +1,34 @@
+<!doctype html>
+<html>
+<head>
+<meta charset="utf-8">
+<style>
+*:last-child {
+  white-space: pre-line;
+}
+</style>
+<script>
+"use strict";
+
+addEventListener("load", () => {
+  document.designMode = "on";
+  getSelection().setBaseAndExtent(
+    document.querySelector("ol"),
+    0,
+    document.querySelector("ol"),
+    document.querySelector("ol").childNodes.length
+  );
+  document.execCommand("insertHTML", false, "");
+}, {once: true});
+</script>
+</head>
+<!-- Do not change the white-spaces between <li> elements nor the line break in the first <li> -->
+<body>
+<ol><li>
+</li><li>
+<blockquote style="display:inline-flex">
+  <title contenteditable></title>
+</blockquote>
+</li><li></li></ol>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/encrypted-media/clearkey-mp4-playback-temporary-multikey-missing-saio.https.html b/third_party/blink/web_tests/external/wpt/encrypted-media/clearkey-mp4-playback-temporary-multikey-missing-saio.https.html
new file mode 100644
index 0000000..fc1e3c0
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/encrypted-media/clearkey-mp4-playback-temporary-multikey-missing-saio.https.html
@@ -0,0 +1,57 @@
+<!doctype html>
+<html>
+  <head>
+    <meta charset=utf-8>
+    <title>Encrypted Media Extensions: Successful Playback, Temporary session with Clear Key, mp4, multiple keys for audio/video, with no saio boxes</title>
+    <link rel="help" href="https://w3c.github.io/encrypted-media/">
+
+    <!-- Web Platform Test Harness scripts -->
+    <script src=/resources/testharness.js></script>
+    <script src=/resources/testharnessreport.js></script>
+
+    <!-- Helper scripts for Encrypted Media Extensions tests  -->
+    <script src=/encrypted-media/util/utils.js></script>
+    <script src=/encrypted-media/util/testmediasource.js></script>
+    <script src=/encrypted-media/util/utf8.js></script>
+
+    <!-- Content metadata -->
+    <script src=/encrypted-media/content/content-metadata.js></script>
+
+    <!-- Message handler for Clear Key keysystem -->
+    <script src=/encrypted-media/util/clearkey-messagehandler.js></script>
+
+    <!-- The script for this specific test -->
+    <script src=/encrypted-media/scripts/playback-temporary.js></script>
+
+  </head>
+  <body>
+    <div id='log'></div>
+
+    <div id='video'>
+      <video id="videoelement" width="200px"></video>
+    </div>
+
+    <script>
+        var contentitem = content['mp4-av-multikey'],
+            handler = new MessageHandler( 'org.w3.clearkey', contentitem ),
+            config = {  video:              document.getElementById('videoelement'),
+                        keysystem:          'org.w3.clearkey',
+                        messagehandler:     handler.messagehandler,
+                        audioPath:          contentitem.audio.path,
+                        videoPath:          contentitem.video.path,
+                        audioType:          contentitem.audio.type,
+                        videoType:          contentitem.video.type,
+                        // Replace 'saio' text by ' aio', and 'saiz' by ' aiz'
+                        // to make those boxes unrecognized: 'senc' boxes shall
+                        // be used instead
+                        audioBlankBytes:    contentitem.audio.saioStarts.concat(contentitem.audio.saizStarts),
+                        videoBlankBytes:    contentitem.video.saioStarts.concat(contentitem.video.saizStarts),
+                        initDataType:       'keyids',
+                        initData:           getInitData(contentitem,'keyids'),
+                        testcase:           'multikey audio/video' };
+
+
+        runTest(config);
+    </script>
+  </body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/encrypted-media/clearkey-mp4-playback-temporary-multikey-missing-senc.https.html b/third_party/blink/web_tests/external/wpt/encrypted-media/clearkey-mp4-playback-temporary-multikey-missing-senc.https.html
new file mode 100644
index 0000000..519f30b
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/encrypted-media/clearkey-mp4-playback-temporary-multikey-missing-senc.https.html
@@ -0,0 +1,57 @@
+<!doctype html>
+<html>
+  <head>
+    <meta charset=utf-8>
+    <title>Encrypted Media Extensions: Successful Playback, Temporary session with Clear Key, mp4, multiple keys for audio/video, with no senc boxes</title>
+    <link rel="help" href="https://w3c.github.io/encrypted-media/">
+
+    <!-- Web Platform Test Harness scripts -->
+    <script src=/resources/testharness.js></script>
+    <script src=/resources/testharnessreport.js></script>
+
+    <!-- Helper scripts for Encrypted Media Extensions tests  -->
+    <script src=/encrypted-media/util/utils.js></script>
+    <script src=/encrypted-media/util/testmediasource.js></script>
+    <script src=/encrypted-media/util/utf8.js></script>
+
+    <!-- Content metadata -->
+    <script src=/encrypted-media/content/content-metadata.js></script>
+
+    <!-- Message handler for Clear Key keysystem -->
+    <script src=/encrypted-media/util/clearkey-messagehandler.js></script>
+
+    <!-- The script for this specific test -->
+    <script src=/encrypted-media/scripts/playback-temporary.js></script>
+
+  </head>
+  <body>
+    <div id='log'></div>
+
+    <div id='video'>
+      <video id="videoelement" width="200px"></video>
+    </div>
+
+    <script>
+        var contentitem = content['mp4-av-multikey'],
+            handler = new MessageHandler( 'org.w3.clearkey', contentitem ),
+            config = {  video:              document.getElementById('videoelement'),
+                        keysystem:          'org.w3.clearkey',
+                        messagehandler:     handler.messagehandler,
+                        audioPath:          contentitem.audio.path,
+                        videoPath:          contentitem.video.path,
+                        audioType:          contentitem.audio.type,
+                        videoType:          contentitem.video.type,
+                        // Replace 'senc' text by ' enc' to make those boxes
+                        // unrecognized: auxiliary data ('saio' boxes) shall be
+                        // used instead
+                        audioBlankBytes:    contentitem.audio.sencStarts,
+                        videoBlankBytes:    contentitem.video.sencStarts,
+                        initDataType:       'keyids',
+                        initData:           getInitData(contentitem,'keyids'),
+                        testcase:           'multikey audio/video' };
+
+
+        runTest(config);
+    </script>
+  </body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/encrypted-media/content/content-metadata.js b/third_party/blink/web_tests/external/wpt/encrypted-media/content/content-metadata.js
index b33f1fd71..0ee7b65f 100644
--- a/third_party/blink/web_tests/external/wpt/encrypted-media/content/content-metadata.js
+++ b/third_party/blink/web_tests/external/wpt/encrypted-media/content/content-metadata.js
@@ -52,9 +52,15 @@
                         associatedInitData: true,       // indicates that initData for one key causes other keys to be returned as well
                         audio:  {   type:   'audio/mp4;codecs="mp4a.40.2"',
                                     path:   '/encrypted-media/content/audio_aac-lc_128k_enc_dashinit.mp4',
-                                    sinfStart:   0x02aa },
+                                    sinfStart:  0x02aa,
+                                    saizStarts: [0x081b, 0x9065, 0x1150a],
+                                    saioStarts: [0x0802, 0x906e, 0x11523],
+                                    sencStarts: [0x9c3, 0x9226] },
                         video : {   type:   'video/mp4;codecs="avc1.4d401e"',
-                                    path:   '/encrypted-media/content/video_512x288_h264-360k_enc_dashinit.mp4' },
+                                    path:   '/encrypted-media/content/video_512x288_h264-360k_enc_dashinit.mp4',
+                                    saizStarts: [0x840, 0x18031, 0x2ebad],
+                                    saioStarts: [0x889, 0x1804a, 0x2ebc6],
+                                    sencStarts: [0x2ec62] },
                         keys :  [ { kid: [ 0xad, 0x13, 0xf9, 0xea, 0x2b, 0xe6, 0x98, 0xb8, 0x75, 0xf5, 0x04, 0xa8, 0xe3, 0xcc, 0xea, 0x64 ],
                                     key: [ 0xbe, 0x7d, 0xf8, 0xa3, 0x66, 0x7a, 0x6a, 0x8f, 0xd5, 0x64, 0xd0, 0xed, 0x81, 0x33, 0x9a, 0x95 ],
                                     initDataType: 'cenc',
diff --git a/third_party/blink/web_tests/external/wpt/encrypted-media/util/testmediasource.js b/third_party/blink/web_tests/external/wpt/encrypted-media/util/testmediasource.js
index 47cfeb4..1c4a3615 100644
--- a/third_party/blink/web_tests/external/wpt/encrypted-media/util/testmediasource.js
+++ b/third_party/blink/web_tests/external/wpt/encrypted-media/util/testmediasource.js
@@ -12,6 +12,18 @@
         Promise.all(fetches).then(function(resources) {
             config.audioMedia = resources[0];
             config.videoMedia = resources[1];
+            if (Object.hasOwn(config, 'audioBlankBytes')) {
+                audioMedia = new Uint8Array(config.audioMedia);
+                for (const pos of config.audioBlankBytes) {
+                    audioMedia[pos] = ' '.charCodeAt();
+                }
+            }
+            if (Object.hasOwn(config, 'videoBlankBytes')) {
+                videoMedia = new Uint8Array(config.videoMedia);
+                for (const pos of config.videoBlankBytes) {
+                    videoMedia[pos] = ' '.charCodeAt();
+                }
+            }
 
             // Create media source
             var source = new MediaSource();
diff --git a/third_party/blink/web_tests/external/wpt/forced-colors-mode/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/forced-colors-mode/WEB_FEATURES.yml
index 58883c4..d5af597c 100644
--- a/third_party/blink/web_tests/external/wpt/forced-colors-mode/WEB_FEATURES.yml
+++ b/third_party/blink/web_tests/external/wpt/forced-colors-mode/WEB_FEATURES.yml
@@ -2,3 +2,13 @@
 - name: forced-colors
   files:
   - "*"
+  - "!forced-colors-mode-42.html"
+  - "!forced-colors-mode-43.html"
+  - "!forced-colors-mode-44.html"
+  - "!forced-colors-mode-45.html"
+- name: caret-color
+  files:
+  - forced-colors-mode-42.html
+  - forced-colors-mode-43.html
+  - forced-colors-mode-44.html
+  - forced-colors-mode-45.html
diff --git a/third_party/blink/web_tests/external/wpt/generic-sensor/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/generic-sensor/WEB_FEATURES.yml
new file mode 100644
index 0000000..22840b14
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/generic-sensor/WEB_FEATURES.yml
@@ -0,0 +1,3 @@
+features:
+- name: virtual-sensor
+  files: '**'
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/browsing-the-web/back-forward-cache/pagehide-event-handler-microtasks.window.js b/third_party/blink/web_tests/external/wpt/html/browsers/browsing-the-web/back-forward-cache/pagehide-event-handler-microtasks.window.js
new file mode 100644
index 0000000..3ffbead9
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/browsing-the-web/back-forward-cache/pagehide-event-handler-microtasks.window.js
@@ -0,0 +1,28 @@
+// META: title=Assure microtasks posted by pagehide event handler are dispatched as page is BFCached
+// META: script=/common/dispatcher/dispatcher.js
+// META: script=/common/utils.js
+// META: script=./resources/test-helper.js
+
+'use strict';
+
+promise_test(async t => {
+  const uuid = token();
+  const url = remoteExecutorUrl(uuid, {protocol: 'http:'});
+  const win = window.open(url, '_blank', 'noopener');
+  const context = new RemoteContext(uuid);
+
+  // Navigate within the same eval, as (1) we can't navigate externally
+  // because of potential race against the eval, and (2) we can't navigate
+  // as a separate eval step as it would block on this one returning,
+  // which would only happen upon `pagehide`.
+  const {persisted} = await context.execute_script(
+      () => new Promise(resolve => {
+        window.addEventListener('pagehide', async (event) => {
+          await Promise.resolve();  // ... so the rest will run as a microtask.
+          resolve({persisted: event.persisted});
+        }, false);
+        location.href += '&navigated';
+      }));
+
+  assert_true(persisted);  // Validate we're BFCached.
+}, 'Assure microtasks posted by pagehide event handler are dispatched');
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/browsing-the-web/history-traversal/browsing_context_name-0.html b/third_party/blink/web_tests/external/wpt/html/browsers/browsing-the-web/history-traversal/browsing_context_name-0.html
index 5cbab71a..ed50b9e8 100644
--- a/third_party/blink/web_tests/external/wpt/html/browsers/browsing-the-web/history-traversal/browsing_context_name-0.html
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/browsing-the-web/history-traversal/browsing_context_name-0.html
@@ -8,18 +8,38 @@
 log = function(t) {l.textContent += ("\n" + t)}
 var navigated = false;
 var steps = [
-  () => f.src = "browsing_context_name-1.html",
+  () => {
+    opener.assert_equals(history.length, 1, "first history.length");
+    f.src = "browsing_context_name-1.html";
+  },
   () => {
     navigated = true;
     opener.assert_equals(f.contentWindow.name, "test", "Initial load");
+    opener.assert_equals(history.length, 1, "second history.length");
+    setTimeout(next, 0);
+  },
+  () => {
+    opener.assert_equals(f.contentWindow.name, "test", "Initial load");
+    opener.assert_equals(history.length, 1, "third history.length");
     f.src = "browsing_context_name-2.html"
   },
   () => {
     opener.assert_equals(f.contentWindow.name, "test1");
-    opener.assert_equals(history.length, 2);
-    history.back()
+    opener.assert_equals(history.length, 2, "fourth history.length");
+    setTimeout(next, 0);
   },
   () => {
+    opener.assert_equals(f.contentWindow.name, "test1");
+    opener.assert_equals(history.length, 2, "fifth history.length");
+    history.back();
+  },
+  () => {
+    opener.assert_equals(history.length, 2, "sixth history.length");
+    opener.assert_equals(f.contentWindow.name, "test1", "After navigation");
+    setTimeout(next, 0);
+  },
+  () => {
+    opener.assert_equals(history.length, 2, "seventh history.length");
     opener.assert_equals(f.contentWindow.name, "test1", "After navigation");
     t.done();
   }
@@ -29,7 +49,10 @@
 
 onload = () => {
   log("page load");
-  f.onload = () => {log("iframe onload"); next()};
+  f.onload = () => {
+    log("iframe onload");
+    next();
+  };
   setTimeout(next, 0);
 };
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/browsing-the-web/history-traversal/browsing_context_name-expected.txt b/third_party/blink/web_tests/external/wpt/html/browsers/browsing-the-web/history-traversal/browsing_context_name-expected.txt
deleted file mode 100644
index d75f282..0000000
--- a/third_party/blink/web_tests/external/wpt/html/browsers/browsing-the-web/history-traversal/browsing_context_name-expected.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-This is a testharness.js-based test.
-[FAIL] Retaining window.name on history traversal
-  assert_equals: expected 2 but got 1
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/browsing-the-web/navigating-across-documents/010.html b/third_party/blink/web_tests/external/wpt/html/browsers/browsing-the-web/navigating-across-documents/010.html
deleted file mode 100644
index 606ad82f..0000000
--- a/third_party/blink/web_tests/external/wpt/html/browsers/browsing-the-web/navigating-across-documents/010.html
+++ /dev/null
@@ -1,17 +0,0 @@
-<!doctype html>
-<title>Link with onclick form submit to javascript url with delayed document.write and href navigation </title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<div id="log"></div>
-<iframe id="test" name="test"></iframe>
-<form target="test" action="javascript:(function() {var x = new XMLHttpRequest(); x.open('GET', 'resources/blank.html?pipe=trickle(d2)', false); x.send(); document.write('<script>parent.postMessage(&quot;write&quot;, &quot;*&quot;)</script>'); return '<script>parent.postMessage(&quot;click&quot;, &quot;*&quot;)</script>'})()"></form>
-<a target="test" onclick="document.forms[0].submit()" href="resources/href.html">Test</a>
-<script>
-var t = async_test();
-onload = t.step_func(function() {document.getElementsByTagName("a")[0].click()});
-onmessage = t.step_func(
-  function(e) {
-    assert_equals(e.data, "href");
-    t.done();
-  });
-</script>
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/browsing-the-web/navigating-across-documents/010.tentative-expected.txt b/third_party/blink/web_tests/external/wpt/html/browsers/browsing-the-web/navigating-across-documents/010.tentative-expected.txt
new file mode 100644
index 0000000..26a5208
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/browsing-the-web/navigating-across-documents/010.tentative-expected.txt
@@ -0,0 +1,5 @@
+This is a testharness.js-based test.
+[FAIL] Link with onclick form submit to javascript url with delayed document.write and href navigation 
+  assert_equals: expected true but got false
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/browsing-the-web/navigating-across-documents/010.tentative.html b/third_party/blink/web_tests/external/wpt/html/browsers/browsing-the-web/navigating-across-documents/010.tentative.html
new file mode 100644
index 0000000..f6c2189
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/browsing-the-web/navigating-across-documents/010.tentative.html
@@ -0,0 +1,31 @@
+<!doctype html>
+<title>Link with onclick form submit to javascript url with delayed document.write and href navigation </title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+var flag = false;
+</script>
+<div id="log"></div>
+<iframe id="test" name="test"></iframe>
+<form target="test" action="javascript:(function() {parent.flag = true; var x = new XMLHttpRequest(); x.open('GET', 'resources/blank.html?pipe=trickle(d2)', false); x.send(); document.write('WRITE <script>parent.postMessage(&quot;write&quot;, &quot;*&quot;)</script>'); return 'RETURN <script>parent.postMessage(&quot;click&quot;, &quot;*&quot;)</script>'})()"></form>
+<a target="test" onclick="document.forms[0].submit()" href="resources/href.html">Test</a>
+<script>
+var t = async_test();
+onload = t.step_func(function() {document.getElementsByTagName("a")[0].click()});
+onmessage = t.step_func(
+  function(e) {
+    assert_equals(flag, true);
+    assert_equals(e.data, "write");
+    t.done();
+  });
+</script>
+<!--
+Tentative, because:
+ * Chrome doesn't appear to execute the javascript: URL at all.
+ * Safari seems to start the navigation to href.html to the extent
+   that the sync XHR goes away, but then document.write() takes
+   place so that postMessage succeeds and then the DOM for href.html
+   replaces the DOM for the document.write().
+ * In Firefox, the navigation to href.html makes the sync XHR go
+   away, but then document.write() wins over the href.html parse.
+-->
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/browsing-the-web/navigating-across-documents/abort-document-load-expected.txt b/third_party/blink/web_tests/external/wpt/html/browsers/browsing-the-web/navigating-across-documents/abort-document-load-expected.txt
deleted file mode 100644
index ae4c0c1..0000000
--- a/third_party/blink/web_tests/external/wpt/html/browsers/browsing-the-web/navigating-across-documents/abort-document-load-expected.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-This is a testharness.js-based test.
-[FAIL] Aborting a Document load
-  assert_array_equals: no load event was fired lengths differ, expected array ["loading", "DOMContentLoaded", "stop", "complete"] length 4, got ["loading", "stop"] length 2
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/browsing-the-web/navigating-across-documents/abort-document-load.html b/third_party/blink/web_tests/external/wpt/html/browsers/browsing-the-web/navigating-across-documents/abort-document-load.html
index 4a4c3df..be66f7b 100644
--- a/third_party/blink/web_tests/external/wpt/html/browsers/browsing-the-web/navigating-across-documents/abort-document-load.html
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/browsing-the-web/navigating-across-documents/abort-document-load.html
@@ -14,7 +14,7 @@
     const frame = document.querySelector('iframe');
     const child = frame.contentWindow;
     assert_equals(child.document.readyState, 'complete', 'readyState is complete');
-    assert_array_equals(events, ["loading", "DOMContentLoaded", "stop", "complete"], 'no load event was fired');
+    assert_array_equals(events, ["loading", "stop"], 'no load event was fired');
     events = [];
     frame.src = "abort-document-load-2.html";
 
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/iframe-src-204.html b/third_party/blink/web_tests/external/wpt/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/iframe-src-204.html
index f7bade68..a3bf429 100644
--- a/third_party/blink/web_tests/external/wpt/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/iframe-src-204.html
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/iframe-src-204.html
@@ -175,4 +175,24 @@
   assert_equals(history.length, startingHistoryLength + 1,
     "history.length increases after normal navigation from  non-initial empty document");
 }, "Navigating to a different document with form submission");
+
+promise_test(async t => {
+  // The ready state is asserted to be what it is currently Blink, Gecko, WebKit
+  const iframe = insertIframeWith204Src(t);
+  assert_equals(iframe.contentDocument.readyState, "complete");
+
+  let loaded = false;
+  iframe.onload = () => loaded = true;
+
+  iframe.src = "about:blank";
+  assert_false(loaded, "No sync load event");
+  assert_equals(iframe.contentDocument.readyState, "complete");
+
+  await Promise.resolve();
+  assert_false(loaded, "No load event after microtask checkpoint");
+
+  await waitForLoad(t, iframe, "about:blank");
+  assert_true(loaded, "Iframe loaded eventually");
+  assert_equals(iframe.contentDocument.readyState, "complete");
+}, "about:blank on top of 204 loads async");
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/load-event-iframe-element.html b/third_party/blink/web_tests/external/wpt/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/load-event-iframe-element.html
index 0d19770..b17528d1 100644
--- a/third_party/blink/web_tests/external/wpt/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/load-event-iframe-element.html
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/load-event-iframe-element.html
@@ -16,6 +16,7 @@
   });
   document.body.appendChild(iframe);
   assert_equals(loadCount, 1);
+  assert_equals(iframe.contentDocument.location.href, "about:blank");
 }, "load event fires synchronously on <iframe> element created with no src");
 
 promise_test(async t => {
@@ -27,6 +28,7 @@
   });
   document.body.appendChild(iframe);
   assert_equals(loadCount, 1);
+  assert_equals(iframe.contentDocument.location.href, "about:blank");
 }, "load event fires synchronously on <iframe> element created with src=''");
 
 promise_test(async t => {
@@ -38,6 +40,7 @@
   });
   document.body.appendChild(iframe);
   assert_equals(loadCount, 1);
+  assert_equals(iframe.contentDocument.location.href, "about:blank");
 }, "load event fires synchronously on <iframe> element created with src='about:blank'");
 
 promise_test(async t => {
@@ -49,6 +52,7 @@
   });
   document.body.appendChild(iframe);
   assert_equals(loadCount, 1);
+  assert_equals(iframe.contentDocument.location.href, "about:blank#foo");
 }, "load event fires synchronously on <iframe> element created with src='about:blank#foo'");
 
 promise_test(async t => {
@@ -60,5 +64,6 @@
   });
   document.body.appendChild(iframe);
   assert_equals(loadCount, 1);
+  assert_equals(iframe.contentDocument.location.href, "about:blank?foo");
 }, "load event fires synchronously on <iframe> element created with src='about:blank?foo'");
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/history/joint-session-history/joint-session-history-remove-iframe.html b/third_party/blink/web_tests/external/wpt/html/browsers/history/joint-session-history/joint-session-history-remove-iframe.html
index ee7aa36..aa59e46 100644
--- a/third_party/blink/web_tests/external/wpt/html/browsers/history/joint-session-history/joint-session-history-remove-iframe.html
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/history/joint-session-history/joint-session-history-remove-iframe.html
@@ -11,12 +11,11 @@
     t.step_timeout(() => {
         var child = document.getElementById("frame");
         var old_history_len = history.length;
-        child.onload = () => {
-            assert_equals(old_history_len + 1, history.length);
+        child.onload = t.step_func_done(() => {
+            assert_equals(old_history_len, history.length);
             document.body.removeChild(document.getElementById("frame"));
             assert_equals(old_history_len, history.length);
-            t.done();
-        }
+        })
         child.src = "joint-session-history-filler.html";
     }, 1000);
 });
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/history/the-location-interface/assign_after_load-expected.txt b/third_party/blink/web_tests/external/wpt/html/browsers/history/the-location-interface/assign_after_load-expected.txt
deleted file mode 100644
index 074cbd9..0000000
--- a/third_party/blink/web_tests/external/wpt/html/browsers/history/the-location-interface/assign_after_load-expected.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-This is a testharness.js-based test.
-[FAIL] Assignment to location after document is completely loaded
-  assert_equals: expected 3 but got 2
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/history/the-location-interface/assign_after_load.html b/third_party/blink/web_tests/external/wpt/html/browsers/history/the-location-interface/assign_after_load.html
index 00dc931..9ba0e5d7 100644
--- a/third_party/blink/web_tests/external/wpt/html/browsers/history/the-location-interface/assign_after_load.html
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/history/the-location-interface/assign_after_load.html
@@ -16,7 +16,7 @@
 });
 
 do_test = t.step_func(function() {
-    assert_equals(history.length, history_length + 2);
+    assert_equals(history.length, history_length + 1);
     t.done();
 });
 
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/history/the-location-interface/assign_before_load-expected.txt b/third_party/blink/web_tests/external/wpt/html/browsers/history/the-location-interface/assign_before_load-expected.txt
deleted file mode 100644
index cc5aa2b..0000000
--- a/third_party/blink/web_tests/external/wpt/html/browsers/history/the-location-interface/assign_before_load-expected.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-This is a testharness.js-based test.
-[FAIL] Assignment to location before document is completely loaded
-  assert_equals: expected 2 but got 1
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/history/the-location-interface/assign_before_load.html b/third_party/blink/web_tests/external/wpt/html/browsers/history/the-location-interface/assign_before_load.html
index 62a2aa7..7776512 100644
--- a/third_party/blink/web_tests/external/wpt/html/browsers/history/the-location-interface/assign_before_load.html
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/history/the-location-interface/assign_before_load.html
@@ -16,7 +16,7 @@
 });
 
 do_test = t.step_func(function() {
-    assert_equals(history.length, history_length + 1);
+    assert_equals(history.length, history_length);
     t.done();
 });
 
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/sandboxing/sandbox-window-open-srcdoc.html b/third_party/blink/web_tests/external/wpt/html/browsers/sandboxing/sandbox-window-open-srcdoc.html
index 6fbff6df..e882b890 100644
--- a/third_party/blink/web_tests/external/wpt/html/browsers/sandboxing/sandbox-window-open-srcdoc.html
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/sandboxing/sandbox-window-open-srcdoc.html
@@ -5,6 +5,15 @@
 <script src="/resources/testharnessreport.js"></script>
 <body>
 <script>
+function waitForEvent(name, target) {
+  return new Promise(resolve => {
+    function listener(event) {
+      resolve(event);
+      }
+    target.addEventListener(name, listener, { once: true });
+  });
+}
+
 // Check what happens when executing window.open("about:srcdoc") from a
 // sandboxed iframe. Srcdoc can't be loaded in the main frame. It should
 // result in an error page. The error page should be cross-origin with the
@@ -16,7 +25,7 @@
 // applies the sandbox flags of the opener to the internal error page document.
 //
 // This test is mainly a coverage test. It passes if it doesn't crash.
-async_test(test => {
+promise_test(async t => {
   let iframe = document.createElement("iframe");
   iframe.sandbox = "allow-scripts allow-popups allow-same-origin";
   iframe.srcdoc = `
@@ -40,13 +49,31 @@
     </scr`+`ipt>
   `;
 
-  let closed = false;
-  addEventListener("message", event => {
-    closed = (event.data === "done");
-    iframe.contentWindow.postMessage("ping","*");
-  });
-
+  let msg = waitForEvent("message", window);
   document.body.appendChild(iframe);
-  test.step_wait_func_done(()=>closed);
+  while ( (await msg).data !== "done" ) {
+    iframe.contentWindow.postMessage("ping","*");
+    msg = waitForEvent("message", window);
+  }
+  iframe.remove();
 }, "window.open('about:srcdoc') from sandboxed srcdoc doesn't crash.");
+
+promise_test(async t => {
+  let ifr = document.createElement("iframe");
+  ifr.sandbox = "allow-scripts allow-popups";
+  ifr.srcdoc = `<script>
+    const w = window.open();
+    try {
+      w.document;
+      parent.postMessage("fail", "*")
+    } catch (e) {
+      parent.postMessage(e.name, "*")
+    }
+  </scri`+`pt>`;
+
+  const msg = waitForEvent("message", window);
+  document.body.appendChild(ifr);
+  const data = (await msg).data;
+  assert_equals(data, "SecurityError", "");
+}, "popup is isolated from an isolated iframe");
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/discard_iframe_history_1-1.html b/third_party/blink/web_tests/external/wpt/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/discard_iframe_history_1-1.html
index 217608e4..e9ef94b 100644
--- a/third_party/blink/web_tests/external/wpt/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/discard_iframe_history_1-1.html
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/discard_iframe_history_1-1.html
@@ -8,9 +8,9 @@
      var history_length = history.length;
      var iframe = document.getElementsByTagName("iframe")[0];
      iframe.onload = t.step_func(function() {
-       opener.assert_equals(history.length, history_length + 1);
+       opener.assert_equals(history.length, history_length, "History length before iframe removal");
        iframe.parentNode.removeChild(iframe);
-       opener.assert_equals(history.length, history_length);
+       opener.assert_equals(history.length, history_length, "History length after iframe removal");
        t.done();
        window.close();
      });
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/discard_iframe_history_1-expected.txt b/third_party/blink/web_tests/external/wpt/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/discard_iframe_history_1-expected.txt
deleted file mode 100644
index 99746ba..0000000
--- a/third_party/blink/web_tests/external/wpt/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/discard_iframe_history_1-expected.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-This is a testharness.js-based test.
-[FAIL] Removing iframe from document removes it from history
-  assert_equals: expected 2 but got 1
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/discard_iframe_history_2-1.html b/third_party/blink/web_tests/external/wpt/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/discard_iframe_history_2-1.html
index 61e5891e..1112d08f 100644
--- a/third_party/blink/web_tests/external/wpt/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/discard_iframe_history_2-1.html
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/discard_iframe_history_2-1.html
@@ -9,7 +9,7 @@
      var iframe = document.getElementsByTagName("iframe")[0];
      iframe.onload = t.step_func(function() {
        setTimeout(t.step_func(function() {
-         opener.assert_equals(history.length, history_length + 1, "History length before iframe removal");
+         opener.assert_equals(history.length, history_length, "History length before iframe removal");
          document.body.innerHTML = "";
          opener.assert_equals(history.length, history_length, "History length after iframe removal");
          t.done();
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/discard_iframe_history_2-expected.txt b/third_party/blink/web_tests/external/wpt/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/discard_iframe_history_2-expected.txt
deleted file mode 100644
index 2e07249..0000000
--- a/third_party/blink/web_tests/external/wpt/html/browsers/the-window-object/garbage-collection-and-browsing-contexts/discard_iframe_history_2-expected.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-This is a testharness.js-based test.
-[FAIL] Removing iframe from document via innerHTML removes it from history
-  assert_equals: History length before iframe removal expected 2 but got 1
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/the-window-object/open-close/open_fires_resize.tentative.html b/third_party/blink/web_tests/external/wpt/html/browsers/the-window-object/open-close/open_fires_resize.tentative.html
new file mode 100644
index 0000000..aec27ea9
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/the-window-object/open-close/open_fires_resize.tentative.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Test that a resize event is fired on newly opened windows</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+
+promise_test(async t => {
+  popup = window.open("about:blank", "_blank", "width=500,height=500");
+  await new Promise(resolve => popup.onresize = resolve);
+  assert_true(true, "Got resize event");
+}, "Opening a popup with specified size fires a resize event");
+
+promise_test(async t => {
+  popup = window.open("about:blank", "_blank", "popup");
+  await new Promise(resolve => popup.onresize = resolve);
+  assert_true(true, "Got resize event");
+}, "Opening a popup fires a resize event");
+
+promise_test(async t => {
+  popup = window.open("about:blank");
+  await new Promise(resolve => popup.onresize = resolve);
+  assert_true(true, "Got resize event");
+}, "Opening a window fires a resize event");
+
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/the-window-object/window-reuse-in-nested-browsing-contexts.tentative.html b/third_party/blink/web_tests/external/wpt/html/browsers/the-window-object/window-reuse-in-nested-browsing-contexts.tentative.html
index 03c7a68..3076e60f 100644
--- a/third_party/blink/web_tests/external/wpt/html/browsers/the-window-object/window-reuse-in-nested-browsing-contexts.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/the-window-object/window-reuse-in-nested-browsing-contexts.tentative.html
@@ -14,6 +14,9 @@
   return {iframe, watcher};
 };
 
+// This test is outdated, see
+// https://github.com/whatwg/html/issues/3267#issuecomment-2788132131
+
 promise_test(async t => {
   const {iframe, watcher} = setupIframe(t, {});
 
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/the-window-object/window-reuse.tentative.html b/third_party/blink/web_tests/external/wpt/html/browsers/the-window-object/window-reuse.tentative.html
new file mode 100644
index 0000000..a5e3b50
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/the-window-object/window-reuse.tentative.html
@@ -0,0 +1,34 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Test window reuse on initial navigation away from about:blank</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+promise_test(async t => {
+  const w = window.open("/common/blank.html");
+  t.add_cleanup(() => w.close());
+
+  assert_equals(w.location.href, 'about:blank', 'window initially on a transient about:blank');
+  w.foo = 'bar';
+
+  await new Promise(res => w.onload = res);
+  assert_true(w.location.href.endsWith('/common/blank.html'), 'loaded initial navigation target');
+  assert_equals(w.foo, 'bar', 'did reuse window');
+}, 'Initially navigating window to non-about:blank page should reuse synchonously available window');
+
+promise_test(async t => {
+  const name = 'unique-name-11sep25';
+  const channel = new BroadcastChannel(name);
+  const w = window.open("about:blank", name);
+  t.add_cleanup(() => w.close());
+
+  assert_equals(w.location.href, 'about:blank', 'loaded about:blank');
+  w.foo = 'bar';
+
+  w.location.href = 'support/window-open-popup-target.html';
+  await new Promise(res => channel.onmessage = res);
+  assert_true(true, 'completed non-initial load onto initial about:blank');
+  assert_equals(w.foo, undefined, 'did reuse window');
+}, 'synchronously navigating window away from initial about:blank should not reuse window');
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/windows/clear-window-name.https.html b/third_party/blink/web_tests/external/wpt/html/browsers/windows/clear-window-name.https.html
index 698de8a1..104b00eb 100644
--- a/third_party/blink/web_tests/external/wpt/html/browsers/windows/clear-window-name.https.html
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/windows/clear-window-name.https.html
@@ -51,6 +51,14 @@
 promise_test(async t => {
   const id = token();
 
+  w = window.open("about:blank", id);
+  w.location = `resources/window-name.sub.html?cross|same|report=${id}|close`;
+  await pollResultAndCheck(t, id, id);
+}, "Window.name is not reset at the first navigation away from loaded initial about:blank");
+
+promise_test(async t => {
+  const id = token();
+
   window.open(`resources/window-name.sub.html?report=${id}|close`, id, "noopener");
   await pollResultAndCheck(t, id, id);
 }, "Window.name is not reset at the first navigation away from initial about:blank with noopener");
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/windows/noreferrer-window-name.html b/third_party/blink/web_tests/external/wpt/html/browsers/windows/noreferrer-window-name.html
index 5fe649d..5b21372 100644
--- a/third_party/blink/web_tests/external/wpt/html/browsers/windows/noreferrer-window-name.html
+++ b/third_party/blink/web_tests/external/wpt/html/browsers/windows/noreferrer-window-name.html
@@ -65,22 +65,33 @@
 
   async_test(function(t) {
     var win = window.open("", "sufficientlyrandomwindownameamiright3");
-    t.add_cleanup(function() {
-      win.close();
-    });
+    t.add_cleanup(() => win.close());
+
+    const channel = new BroadcastChannel('sufficientlyrandomchannelnameamiright3');
+    t.add_cleanup(() => channel.close());
+
+    const targetHtml = `
+      <script>
+        const channel = new BroadcastChannel('sufficientlyrandomchannelnameamiright3');
+        channel.postMessage({ name: window.name, hasOpener: window.opener === null });
+      </scr`+`ipt>
+    `;
 
     var hyperlink = document.body.appendChild(document.createElement("a"));
-    t.add_cleanup(function() {
-      hyperlink.remove();
-    });
+    t.add_cleanup(() => hyperlink.remove());
     hyperlink.rel = "noreferrer";
-    hyperlink.href = URL.createObjectURL(new Blob(["hello window"],
+    hyperlink.href = URL.createObjectURL(new Blob([targetHtml],
                                                   { type: "text/html"}));
     hyperlink.target = "sufficientlyrandomwindownameamiright3";
-    win.onload = t.step_func_done(function() {
-      assert_equals(win.document.documentElement.textContent,
-                    "hello window");
+
+    // win already loaded about:blank, the next load won't reuse the window. So we cannot
+    // add a load listener and rather need to use a channel.
+    channel.onmessage = t.step_func_done(function({ data }) {
+      assert_equals(data.name, 'sufficientlyrandomwindownameamiright3');
+      assert_equals(data.hasOpener, false);
+      assert_equals(win.location.href, hyperlink.href);
     });
+
     hyperlink.click();
   }, "Targeting a rel=noreferrer link at an existing named window should work");
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/element/the-canvas-state/2d.state.saverestore.fillStyle.html b/third_party/blink/web_tests/external/wpt/html/canvas/element/the-canvas-state/2d.state.saverestore.fillStyle.html
index 40710fd..1bd686e 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/element/the-canvas-state/2d.state.saverestore.fillStyle.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/element/the-canvas-state/2d.state.saverestore.fillStyle.html
@@ -28,7 +28,7 @@
   ctx.fillStyle = "#ff0000";
   old = ctx.fillStyle;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against "#ff0000".
+  // from rounding), so compare against `old` instead of against "#ff0000".
   ctx.save();
   _assertSame(ctx.fillStyle, old, "ctx.fillStyle", "old");
   ctx.restore();
@@ -51,7 +51,7 @@
   ctx.fillStyle = "#ff0000";
   old = ctx.fillStyle;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against "#ff0000".
+  // from rounding), so compare against `old` instead of against "#ff0000".
   ctx.save();
   _assertSame(ctx.fillStyle, old, "ctx.fillStyle", "old");
   ctx.restore();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/element/the-canvas-state/2d.state.saverestore.font.html b/third_party/blink/web_tests/external/wpt/html/canvas/element/the-canvas-state/2d.state.saverestore.font.html
index ba5f062..787b8f2 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/element/the-canvas-state/2d.state.saverestore.font.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/element/the-canvas-state/2d.state.saverestore.font.html
@@ -28,7 +28,7 @@
   ctx.font = "25px serif";
   old = ctx.font;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against "25px serif".
+  // from rounding), so compare against `old` instead of against "25px serif".
   ctx.save();
   _assertSame(ctx.font, old, "ctx.font", "old");
   ctx.restore();
@@ -51,7 +51,7 @@
   ctx.font = "25px serif";
   old = ctx.font;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against "25px serif".
+  // from rounding), so compare against `old` instead of against "25px serif".
   ctx.save();
   _assertSame(ctx.font, old, "ctx.font", "old");
   ctx.restore();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/element/the-canvas-state/2d.state.saverestore.globalAlpha.html b/third_party/blink/web_tests/external/wpt/html/canvas/element/the-canvas-state/2d.state.saverestore.globalAlpha.html
index 1bbf7997..f743a04 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/element/the-canvas-state/2d.state.saverestore.globalAlpha.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/element/the-canvas-state/2d.state.saverestore.globalAlpha.html
@@ -28,7 +28,7 @@
   ctx.globalAlpha = 0.5;
   old = ctx.globalAlpha;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against 0.5.
+  // from rounding), so compare against `old` instead of against 0.5.
   ctx.save();
   _assertSame(ctx.globalAlpha, old, "ctx.globalAlpha", "old");
   ctx.restore();
@@ -51,7 +51,7 @@
   ctx.globalAlpha = 0.5;
   old = ctx.globalAlpha;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against 0.5.
+  // from rounding), so compare against `old` instead of against 0.5.
   ctx.save();
   _assertSame(ctx.globalAlpha, old, "ctx.globalAlpha", "old");
   ctx.restore();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/element/the-canvas-state/2d.state.saverestore.globalCompositeOperation.html b/third_party/blink/web_tests/external/wpt/html/canvas/element/the-canvas-state/2d.state.saverestore.globalCompositeOperation.html
index e33029b..73e50a8 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/element/the-canvas-state/2d.state.saverestore.globalCompositeOperation.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/element/the-canvas-state/2d.state.saverestore.globalCompositeOperation.html
@@ -28,7 +28,7 @@
   ctx.globalCompositeOperation = "copy";
   old = ctx.globalCompositeOperation;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against "copy".
+  // from rounding), so compare against `old` instead of against "copy".
   ctx.save();
   _assertSame(ctx.globalCompositeOperation, old, "ctx.globalCompositeOperation", "old");
   ctx.restore();
@@ -51,7 +51,7 @@
   ctx.globalCompositeOperation = "copy";
   old = ctx.globalCompositeOperation;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against "copy".
+  // from rounding), so compare against `old` instead of against "copy".
   ctx.save();
   _assertSame(ctx.globalCompositeOperation, old, "ctx.globalCompositeOperation", "old");
   ctx.restore();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/element/the-canvas-state/2d.state.saverestore.lineCap.html b/third_party/blink/web_tests/external/wpt/html/canvas/element/the-canvas-state/2d.state.saverestore.lineCap.html
index 3611685..94a56c59 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/element/the-canvas-state/2d.state.saverestore.lineCap.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/element/the-canvas-state/2d.state.saverestore.lineCap.html
@@ -28,7 +28,7 @@
   ctx.lineCap = "round";
   old = ctx.lineCap;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against "round".
+  // from rounding), so compare against `old` instead of against "round".
   ctx.save();
   _assertSame(ctx.lineCap, old, "ctx.lineCap", "old");
   ctx.restore();
@@ -51,7 +51,7 @@
   ctx.lineCap = "round";
   old = ctx.lineCap;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against "round".
+  // from rounding), so compare against `old` instead of against "round".
   ctx.save();
   _assertSame(ctx.lineCap, old, "ctx.lineCap", "old");
   ctx.restore();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/element/the-canvas-state/2d.state.saverestore.lineJoin.html b/third_party/blink/web_tests/external/wpt/html/canvas/element/the-canvas-state/2d.state.saverestore.lineJoin.html
index 95292fb7..fececcf 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/element/the-canvas-state/2d.state.saverestore.lineJoin.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/element/the-canvas-state/2d.state.saverestore.lineJoin.html
@@ -28,7 +28,7 @@
   ctx.lineJoin = "round";
   old = ctx.lineJoin;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against "round".
+  // from rounding), so compare against `old` instead of against "round".
   ctx.save();
   _assertSame(ctx.lineJoin, old, "ctx.lineJoin", "old");
   ctx.restore();
@@ -51,7 +51,7 @@
   ctx.lineJoin = "round";
   old = ctx.lineJoin;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against "round".
+  // from rounding), so compare against `old` instead of against "round".
   ctx.save();
   _assertSame(ctx.lineJoin, old, "ctx.lineJoin", "old");
   ctx.restore();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/element/the-canvas-state/2d.state.saverestore.lineWidth.html b/third_party/blink/web_tests/external/wpt/html/canvas/element/the-canvas-state/2d.state.saverestore.lineWidth.html
index 2571fc6ba5..f1949ac 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/element/the-canvas-state/2d.state.saverestore.lineWidth.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/element/the-canvas-state/2d.state.saverestore.lineWidth.html
@@ -28,7 +28,7 @@
   ctx.lineWidth = 0.5;
   old = ctx.lineWidth;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against 0.5.
+  // from rounding), so compare against `old` instead of against 0.5.
   ctx.save();
   _assertSame(ctx.lineWidth, old, "ctx.lineWidth", "old");
   ctx.restore();
@@ -51,7 +51,7 @@
   ctx.lineWidth = 0.5;
   old = ctx.lineWidth;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against 0.5.
+  // from rounding), so compare against `old` instead of against 0.5.
   ctx.save();
   _assertSame(ctx.lineWidth, old, "ctx.lineWidth", "old");
   ctx.restore();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/element/the-canvas-state/2d.state.saverestore.miterLimit.html b/third_party/blink/web_tests/external/wpt/html/canvas/element/the-canvas-state/2d.state.saverestore.miterLimit.html
index fdcc2f0..e3e53bf4 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/element/the-canvas-state/2d.state.saverestore.miterLimit.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/element/the-canvas-state/2d.state.saverestore.miterLimit.html
@@ -28,7 +28,7 @@
   ctx.miterLimit = 0.5;
   old = ctx.miterLimit;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against 0.5.
+  // from rounding), so compare against `old` instead of against 0.5.
   ctx.save();
   _assertSame(ctx.miterLimit, old, "ctx.miterLimit", "old");
   ctx.restore();
@@ -51,7 +51,7 @@
   ctx.miterLimit = 0.5;
   old = ctx.miterLimit;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against 0.5.
+  // from rounding), so compare against `old` instead of against 0.5.
   ctx.save();
   _assertSame(ctx.miterLimit, old, "ctx.miterLimit", "old");
   ctx.restore();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/element/the-canvas-state/2d.state.saverestore.shadowBlur.html b/third_party/blink/web_tests/external/wpt/html/canvas/element/the-canvas-state/2d.state.saverestore.shadowBlur.html
index 28d885b..32ebe729 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/element/the-canvas-state/2d.state.saverestore.shadowBlur.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/element/the-canvas-state/2d.state.saverestore.shadowBlur.html
@@ -28,7 +28,7 @@
   ctx.shadowBlur = 5;
   old = ctx.shadowBlur;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against 5.
+  // from rounding), so compare against `old` instead of against 5.
   ctx.save();
   _assertSame(ctx.shadowBlur, old, "ctx.shadowBlur", "old");
   ctx.restore();
@@ -51,7 +51,7 @@
   ctx.shadowBlur = 5;
   old = ctx.shadowBlur;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against 5.
+  // from rounding), so compare against `old` instead of against 5.
   ctx.save();
   _assertSame(ctx.shadowBlur, old, "ctx.shadowBlur", "old");
   ctx.restore();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/element/the-canvas-state/2d.state.saverestore.shadowColor.html b/third_party/blink/web_tests/external/wpt/html/canvas/element/the-canvas-state/2d.state.saverestore.shadowColor.html
index 03cb498..7b1e3cc8 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/element/the-canvas-state/2d.state.saverestore.shadowColor.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/element/the-canvas-state/2d.state.saverestore.shadowColor.html
@@ -28,7 +28,7 @@
   ctx.shadowColor = "#ff0000";
   old = ctx.shadowColor;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against "#ff0000".
+  // from rounding), so compare against `old` instead of against "#ff0000".
   ctx.save();
   _assertSame(ctx.shadowColor, old, "ctx.shadowColor", "old");
   ctx.restore();
@@ -51,7 +51,7 @@
   ctx.shadowColor = "#ff0000";
   old = ctx.shadowColor;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against "#ff0000".
+  // from rounding), so compare against `old` instead of against "#ff0000".
   ctx.save();
   _assertSame(ctx.shadowColor, old, "ctx.shadowColor", "old");
   ctx.restore();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/element/the-canvas-state/2d.state.saverestore.shadowOffsetX.html b/third_party/blink/web_tests/external/wpt/html/canvas/element/the-canvas-state/2d.state.saverestore.shadowOffsetX.html
index f539043..266906e 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/element/the-canvas-state/2d.state.saverestore.shadowOffsetX.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/element/the-canvas-state/2d.state.saverestore.shadowOffsetX.html
@@ -28,7 +28,7 @@
   ctx.shadowOffsetX = 5;
   old = ctx.shadowOffsetX;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against 5.
+  // from rounding), so compare against `old` instead of against 5.
   ctx.save();
   _assertSame(ctx.shadowOffsetX, old, "ctx.shadowOffsetX", "old");
   ctx.restore();
@@ -51,7 +51,7 @@
   ctx.shadowOffsetX = 5;
   old = ctx.shadowOffsetX;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against 5.
+  // from rounding), so compare against `old` instead of against 5.
   ctx.save();
   _assertSame(ctx.shadowOffsetX, old, "ctx.shadowOffsetX", "old");
   ctx.restore();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/element/the-canvas-state/2d.state.saverestore.shadowOffsetY.html b/third_party/blink/web_tests/external/wpt/html/canvas/element/the-canvas-state/2d.state.saverestore.shadowOffsetY.html
index e1d8b05d..3abc969 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/element/the-canvas-state/2d.state.saverestore.shadowOffsetY.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/element/the-canvas-state/2d.state.saverestore.shadowOffsetY.html
@@ -28,7 +28,7 @@
   ctx.shadowOffsetY = 5;
   old = ctx.shadowOffsetY;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against 5.
+  // from rounding), so compare against `old` instead of against 5.
   ctx.save();
   _assertSame(ctx.shadowOffsetY, old, "ctx.shadowOffsetY", "old");
   ctx.restore();
@@ -51,7 +51,7 @@
   ctx.shadowOffsetY = 5;
   old = ctx.shadowOffsetY;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against 5.
+  // from rounding), so compare against `old` instead of against 5.
   ctx.save();
   _assertSame(ctx.shadowOffsetY, old, "ctx.shadowOffsetY", "old");
   ctx.restore();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/element/the-canvas-state/2d.state.saverestore.strokeStyle.html b/third_party/blink/web_tests/external/wpt/html/canvas/element/the-canvas-state/2d.state.saverestore.strokeStyle.html
index 8dbf1bb4..ff6bded 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/element/the-canvas-state/2d.state.saverestore.strokeStyle.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/element/the-canvas-state/2d.state.saverestore.strokeStyle.html
@@ -28,7 +28,7 @@
   ctx.strokeStyle = "#ff0000";
   old = ctx.strokeStyle;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against "#ff0000".
+  // from rounding), so compare against `old` instead of against "#ff0000".
   ctx.save();
   _assertSame(ctx.strokeStyle, old, "ctx.strokeStyle", "old");
   ctx.restore();
@@ -51,7 +51,7 @@
   ctx.strokeStyle = "#ff0000";
   old = ctx.strokeStyle;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against "#ff0000".
+  // from rounding), so compare against `old` instead of against "#ff0000".
   ctx.save();
   _assertSame(ctx.strokeStyle, old, "ctx.strokeStyle", "old");
   ctx.restore();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/element/the-canvas-state/2d.state.saverestore.textAlign.html b/third_party/blink/web_tests/external/wpt/html/canvas/element/the-canvas-state/2d.state.saverestore.textAlign.html
index 271864c..296d54f 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/element/the-canvas-state/2d.state.saverestore.textAlign.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/element/the-canvas-state/2d.state.saverestore.textAlign.html
@@ -28,7 +28,7 @@
   ctx.textAlign = "center";
   old = ctx.textAlign;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against "center".
+  // from rounding), so compare against `old` instead of against "center".
   ctx.save();
   _assertSame(ctx.textAlign, old, "ctx.textAlign", "old");
   ctx.restore();
@@ -51,7 +51,7 @@
   ctx.textAlign = "center";
   old = ctx.textAlign;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against "center".
+  // from rounding), so compare against `old` instead of against "center".
   ctx.save();
   _assertSame(ctx.textAlign, old, "ctx.textAlign", "old");
   ctx.restore();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/element/the-canvas-state/2d.state.saverestore.textBaseline.html b/third_party/blink/web_tests/external/wpt/html/canvas/element/the-canvas-state/2d.state.saverestore.textBaseline.html
index a6f19e7..e49a8b5 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/element/the-canvas-state/2d.state.saverestore.textBaseline.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/element/the-canvas-state/2d.state.saverestore.textBaseline.html
@@ -28,7 +28,7 @@
   ctx.textBaseline = "bottom";
   old = ctx.textBaseline;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against "bottom".
+  // from rounding), so compare against `old` instead of against "bottom".
   ctx.save();
   _assertSame(ctx.textBaseline, old, "ctx.textBaseline", "old");
   ctx.restore();
@@ -51,7 +51,7 @@
   ctx.textBaseline = "bottom";
   old = ctx.textBaseline;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against "bottom".
+  // from rounding), so compare against `old` instead of against "bottom".
   ctx.save();
   _assertSame(ctx.textBaseline, old, "ctx.textBaseline", "old");
   ctx.restore();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.fillStyle.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.fillStyle.html
index 18c54142..7540678 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.fillStyle.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.fillStyle.html
@@ -25,7 +25,7 @@
   ctx.fillStyle = "#ff0000";
   old = ctx.fillStyle;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against "#ff0000".
+  // from rounding), so compare against `old` instead of against "#ff0000".
   ctx.save();
   _assertSame(ctx.fillStyle, old, "ctx.fillStyle", "old");
   ctx.restore();
@@ -46,7 +46,7 @@
   ctx.fillStyle = "#ff0000";
   old = ctx.fillStyle;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against "#ff0000".
+  // from rounding), so compare against `old` instead of against "#ff0000".
   ctx.save();
   _assertSame(ctx.fillStyle, old, "ctx.fillStyle", "old");
   ctx.restore();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.fillStyle.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.fillStyle.worker.js
index 53b987f..e7cbf33 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.fillStyle.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.fillStyle.worker.js
@@ -21,7 +21,7 @@
   ctx.fillStyle = "#ff0000";
   old = ctx.fillStyle;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against "#ff0000".
+  // from rounding), so compare against `old` instead of against "#ff0000".
   ctx.save();
   _assertSame(ctx.fillStyle, old, "ctx.fillStyle", "old");
   ctx.restore();
@@ -42,7 +42,7 @@
   ctx.fillStyle = "#ff0000";
   old = ctx.fillStyle;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against "#ff0000".
+  // from rounding), so compare against `old` instead of against "#ff0000".
   ctx.save();
   _assertSame(ctx.fillStyle, old, "ctx.fillStyle", "old");
   ctx.restore();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.globalAlpha.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.globalAlpha.html
index 39b8b1d..893799c 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.globalAlpha.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.globalAlpha.html
@@ -25,7 +25,7 @@
   ctx.globalAlpha = 0.5;
   old = ctx.globalAlpha;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against 0.5.
+  // from rounding), so compare against `old` instead of against 0.5.
   ctx.save();
   _assertSame(ctx.globalAlpha, old, "ctx.globalAlpha", "old");
   ctx.restore();
@@ -46,7 +46,7 @@
   ctx.globalAlpha = 0.5;
   old = ctx.globalAlpha;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against 0.5.
+  // from rounding), so compare against `old` instead of against 0.5.
   ctx.save();
   _assertSame(ctx.globalAlpha, old, "ctx.globalAlpha", "old");
   ctx.restore();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.globalAlpha.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.globalAlpha.worker.js
index 7634ca52..f1b6731 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.globalAlpha.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.globalAlpha.worker.js
@@ -21,7 +21,7 @@
   ctx.globalAlpha = 0.5;
   old = ctx.globalAlpha;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against 0.5.
+  // from rounding), so compare against `old` instead of against 0.5.
   ctx.save();
   _assertSame(ctx.globalAlpha, old, "ctx.globalAlpha", "old");
   ctx.restore();
@@ -42,7 +42,7 @@
   ctx.globalAlpha = 0.5;
   old = ctx.globalAlpha;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against 0.5.
+  // from rounding), so compare against `old` instead of against 0.5.
   ctx.save();
   _assertSame(ctx.globalAlpha, old, "ctx.globalAlpha", "old");
   ctx.restore();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.globalCompositeOperation.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.globalCompositeOperation.html
index 03e1aa6..045f70e 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.globalCompositeOperation.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.globalCompositeOperation.html
@@ -25,7 +25,7 @@
   ctx.globalCompositeOperation = "copy";
   old = ctx.globalCompositeOperation;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against "copy".
+  // from rounding), so compare against `old` instead of against "copy".
   ctx.save();
   _assertSame(ctx.globalCompositeOperation, old, "ctx.globalCompositeOperation", "old");
   ctx.restore();
@@ -46,7 +46,7 @@
   ctx.globalCompositeOperation = "copy";
   old = ctx.globalCompositeOperation;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against "copy".
+  // from rounding), so compare against `old` instead of against "copy".
   ctx.save();
   _assertSame(ctx.globalCompositeOperation, old, "ctx.globalCompositeOperation", "old");
   ctx.restore();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.globalCompositeOperation.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.globalCompositeOperation.worker.js
index c4ab3ea..09d60a2 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.globalCompositeOperation.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.globalCompositeOperation.worker.js
@@ -21,7 +21,7 @@
   ctx.globalCompositeOperation = "copy";
   old = ctx.globalCompositeOperation;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against "copy".
+  // from rounding), so compare against `old` instead of against "copy".
   ctx.save();
   _assertSame(ctx.globalCompositeOperation, old, "ctx.globalCompositeOperation", "old");
   ctx.restore();
@@ -42,7 +42,7 @@
   ctx.globalCompositeOperation = "copy";
   old = ctx.globalCompositeOperation;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against "copy".
+  // from rounding), so compare against `old` instead of against "copy".
   ctx.save();
   _assertSame(ctx.globalCompositeOperation, old, "ctx.globalCompositeOperation", "old");
   ctx.restore();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.lineCap.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.lineCap.html
index 31e2798c..bf25667e 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.lineCap.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.lineCap.html
@@ -25,7 +25,7 @@
   ctx.lineCap = "round";
   old = ctx.lineCap;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against "round".
+  // from rounding), so compare against `old` instead of against "round".
   ctx.save();
   _assertSame(ctx.lineCap, old, "ctx.lineCap", "old");
   ctx.restore();
@@ -46,7 +46,7 @@
   ctx.lineCap = "round";
   old = ctx.lineCap;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against "round".
+  // from rounding), so compare against `old` instead of against "round".
   ctx.save();
   _assertSame(ctx.lineCap, old, "ctx.lineCap", "old");
   ctx.restore();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.lineCap.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.lineCap.worker.js
index cb78a34..6081512 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.lineCap.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.lineCap.worker.js
@@ -21,7 +21,7 @@
   ctx.lineCap = "round";
   old = ctx.lineCap;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against "round".
+  // from rounding), so compare against `old` instead of against "round".
   ctx.save();
   _assertSame(ctx.lineCap, old, "ctx.lineCap", "old");
   ctx.restore();
@@ -42,7 +42,7 @@
   ctx.lineCap = "round";
   old = ctx.lineCap;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against "round".
+  // from rounding), so compare against `old` instead of against "round".
   ctx.save();
   _assertSame(ctx.lineCap, old, "ctx.lineCap", "old");
   ctx.restore();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.lineJoin.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.lineJoin.html
index fa496f0e..a0aa610 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.lineJoin.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.lineJoin.html
@@ -25,7 +25,7 @@
   ctx.lineJoin = "round";
   old = ctx.lineJoin;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against "round".
+  // from rounding), so compare against `old` instead of against "round".
   ctx.save();
   _assertSame(ctx.lineJoin, old, "ctx.lineJoin", "old");
   ctx.restore();
@@ -46,7 +46,7 @@
   ctx.lineJoin = "round";
   old = ctx.lineJoin;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against "round".
+  // from rounding), so compare against `old` instead of against "round".
   ctx.save();
   _assertSame(ctx.lineJoin, old, "ctx.lineJoin", "old");
   ctx.restore();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.lineJoin.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.lineJoin.worker.js
index d1a5560..ae6f1e4 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.lineJoin.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.lineJoin.worker.js
@@ -21,7 +21,7 @@
   ctx.lineJoin = "round";
   old = ctx.lineJoin;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against "round".
+  // from rounding), so compare against `old` instead of against "round".
   ctx.save();
   _assertSame(ctx.lineJoin, old, "ctx.lineJoin", "old");
   ctx.restore();
@@ -42,7 +42,7 @@
   ctx.lineJoin = "round";
   old = ctx.lineJoin;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against "round".
+  // from rounding), so compare against `old` instead of against "round".
   ctx.save();
   _assertSame(ctx.lineJoin, old, "ctx.lineJoin", "old");
   ctx.restore();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.lineWidth.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.lineWidth.html
index f13c48e9..f6a18a1 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.lineWidth.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.lineWidth.html
@@ -25,7 +25,7 @@
   ctx.lineWidth = 0.5;
   old = ctx.lineWidth;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against 0.5.
+  // from rounding), so compare against `old` instead of against 0.5.
   ctx.save();
   _assertSame(ctx.lineWidth, old, "ctx.lineWidth", "old");
   ctx.restore();
@@ -46,7 +46,7 @@
   ctx.lineWidth = 0.5;
   old = ctx.lineWidth;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against 0.5.
+  // from rounding), so compare against `old` instead of against 0.5.
   ctx.save();
   _assertSame(ctx.lineWidth, old, "ctx.lineWidth", "old");
   ctx.restore();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.lineWidth.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.lineWidth.worker.js
index a717a3c..5a7ed6ad 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.lineWidth.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.lineWidth.worker.js
@@ -21,7 +21,7 @@
   ctx.lineWidth = 0.5;
   old = ctx.lineWidth;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against 0.5.
+  // from rounding), so compare against `old` instead of against 0.5.
   ctx.save();
   _assertSame(ctx.lineWidth, old, "ctx.lineWidth", "old");
   ctx.restore();
@@ -42,7 +42,7 @@
   ctx.lineWidth = 0.5;
   old = ctx.lineWidth;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against 0.5.
+  // from rounding), so compare against `old` instead of against 0.5.
   ctx.save();
   _assertSame(ctx.lineWidth, old, "ctx.lineWidth", "old");
   ctx.restore();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.miterLimit.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.miterLimit.html
index aea7021b..3703e345 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.miterLimit.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.miterLimit.html
@@ -25,7 +25,7 @@
   ctx.miterLimit = 0.5;
   old = ctx.miterLimit;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against 0.5.
+  // from rounding), so compare against `old` instead of against 0.5.
   ctx.save();
   _assertSame(ctx.miterLimit, old, "ctx.miterLimit", "old");
   ctx.restore();
@@ -46,7 +46,7 @@
   ctx.miterLimit = 0.5;
   old = ctx.miterLimit;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against 0.5.
+  // from rounding), so compare against `old` instead of against 0.5.
   ctx.save();
   _assertSame(ctx.miterLimit, old, "ctx.miterLimit", "old");
   ctx.restore();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.miterLimit.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.miterLimit.worker.js
index d60ff6cf..39c9e423b 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.miterLimit.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.miterLimit.worker.js
@@ -21,7 +21,7 @@
   ctx.miterLimit = 0.5;
   old = ctx.miterLimit;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against 0.5.
+  // from rounding), so compare against `old` instead of against 0.5.
   ctx.save();
   _assertSame(ctx.miterLimit, old, "ctx.miterLimit", "old");
   ctx.restore();
@@ -42,7 +42,7 @@
   ctx.miterLimit = 0.5;
   old = ctx.miterLimit;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against 0.5.
+  // from rounding), so compare against `old` instead of against 0.5.
   ctx.save();
   _assertSame(ctx.miterLimit, old, "ctx.miterLimit", "old");
   ctx.restore();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.shadowBlur.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.shadowBlur.html
index 1e0b0d8..d753e4eb 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.shadowBlur.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.shadowBlur.html
@@ -25,7 +25,7 @@
   ctx.shadowBlur = 5;
   old = ctx.shadowBlur;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against 5.
+  // from rounding), so compare against `old` instead of against 5.
   ctx.save();
   _assertSame(ctx.shadowBlur, old, "ctx.shadowBlur", "old");
   ctx.restore();
@@ -46,7 +46,7 @@
   ctx.shadowBlur = 5;
   old = ctx.shadowBlur;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against 5.
+  // from rounding), so compare against `old` instead of against 5.
   ctx.save();
   _assertSame(ctx.shadowBlur, old, "ctx.shadowBlur", "old");
   ctx.restore();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.shadowBlur.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.shadowBlur.worker.js
index cd72c68b..d6eb35344 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.shadowBlur.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.shadowBlur.worker.js
@@ -21,7 +21,7 @@
   ctx.shadowBlur = 5;
   old = ctx.shadowBlur;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against 5.
+  // from rounding), so compare against `old` instead of against 5.
   ctx.save();
   _assertSame(ctx.shadowBlur, old, "ctx.shadowBlur", "old");
   ctx.restore();
@@ -42,7 +42,7 @@
   ctx.shadowBlur = 5;
   old = ctx.shadowBlur;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against 5.
+  // from rounding), so compare against `old` instead of against 5.
   ctx.save();
   _assertSame(ctx.shadowBlur, old, "ctx.shadowBlur", "old");
   ctx.restore();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.shadowColor.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.shadowColor.html
index 40ba7b5..4c9e9454 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.shadowColor.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.shadowColor.html
@@ -25,7 +25,7 @@
   ctx.shadowColor = "#ff0000";
   old = ctx.shadowColor;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against "#ff0000".
+  // from rounding), so compare against `old` instead of against "#ff0000".
   ctx.save();
   _assertSame(ctx.shadowColor, old, "ctx.shadowColor", "old");
   ctx.restore();
@@ -46,7 +46,7 @@
   ctx.shadowColor = "#ff0000";
   old = ctx.shadowColor;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against "#ff0000".
+  // from rounding), so compare against `old` instead of against "#ff0000".
   ctx.save();
   _assertSame(ctx.shadowColor, old, "ctx.shadowColor", "old");
   ctx.restore();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.shadowColor.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.shadowColor.worker.js
index 529a2bc..b8d3460 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.shadowColor.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.shadowColor.worker.js
@@ -21,7 +21,7 @@
   ctx.shadowColor = "#ff0000";
   old = ctx.shadowColor;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against "#ff0000".
+  // from rounding), so compare against `old` instead of against "#ff0000".
   ctx.save();
   _assertSame(ctx.shadowColor, old, "ctx.shadowColor", "old");
   ctx.restore();
@@ -42,7 +42,7 @@
   ctx.shadowColor = "#ff0000";
   old = ctx.shadowColor;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against "#ff0000".
+  // from rounding), so compare against `old` instead of against "#ff0000".
   ctx.save();
   _assertSame(ctx.shadowColor, old, "ctx.shadowColor", "old");
   ctx.restore();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.shadowOffsetX.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.shadowOffsetX.html
index 71f1096..3b778c29 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.shadowOffsetX.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.shadowOffsetX.html
@@ -25,7 +25,7 @@
   ctx.shadowOffsetX = 5;
   old = ctx.shadowOffsetX;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against 5.
+  // from rounding), so compare against `old` instead of against 5.
   ctx.save();
   _assertSame(ctx.shadowOffsetX, old, "ctx.shadowOffsetX", "old");
   ctx.restore();
@@ -46,7 +46,7 @@
   ctx.shadowOffsetX = 5;
   old = ctx.shadowOffsetX;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against 5.
+  // from rounding), so compare against `old` instead of against 5.
   ctx.save();
   _assertSame(ctx.shadowOffsetX, old, "ctx.shadowOffsetX", "old");
   ctx.restore();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.shadowOffsetX.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.shadowOffsetX.worker.js
index f3a4031..749a8f9 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.shadowOffsetX.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.shadowOffsetX.worker.js
@@ -21,7 +21,7 @@
   ctx.shadowOffsetX = 5;
   old = ctx.shadowOffsetX;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against 5.
+  // from rounding), so compare against `old` instead of against 5.
   ctx.save();
   _assertSame(ctx.shadowOffsetX, old, "ctx.shadowOffsetX", "old");
   ctx.restore();
@@ -42,7 +42,7 @@
   ctx.shadowOffsetX = 5;
   old = ctx.shadowOffsetX;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against 5.
+  // from rounding), so compare against `old` instead of against 5.
   ctx.save();
   _assertSame(ctx.shadowOffsetX, old, "ctx.shadowOffsetX", "old");
   ctx.restore();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.shadowOffsetY.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.shadowOffsetY.html
index eaf6c53..e0dc0c6 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.shadowOffsetY.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.shadowOffsetY.html
@@ -25,7 +25,7 @@
   ctx.shadowOffsetY = 5;
   old = ctx.shadowOffsetY;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against 5.
+  // from rounding), so compare against `old` instead of against 5.
   ctx.save();
   _assertSame(ctx.shadowOffsetY, old, "ctx.shadowOffsetY", "old");
   ctx.restore();
@@ -46,7 +46,7 @@
   ctx.shadowOffsetY = 5;
   old = ctx.shadowOffsetY;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against 5.
+  // from rounding), so compare against `old` instead of against 5.
   ctx.save();
   _assertSame(ctx.shadowOffsetY, old, "ctx.shadowOffsetY", "old");
   ctx.restore();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.shadowOffsetY.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.shadowOffsetY.worker.js
index b00f76d..23cfe47 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.shadowOffsetY.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.shadowOffsetY.worker.js
@@ -21,7 +21,7 @@
   ctx.shadowOffsetY = 5;
   old = ctx.shadowOffsetY;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against 5.
+  // from rounding), so compare against `old` instead of against 5.
   ctx.save();
   _assertSame(ctx.shadowOffsetY, old, "ctx.shadowOffsetY", "old");
   ctx.restore();
@@ -42,7 +42,7 @@
   ctx.shadowOffsetY = 5;
   old = ctx.shadowOffsetY;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against 5.
+  // from rounding), so compare against `old` instead of against 5.
   ctx.save();
   _assertSame(ctx.shadowOffsetY, old, "ctx.shadowOffsetY", "old");
   ctx.restore();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.strokeStyle.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.strokeStyle.html
index 58a94aa..d5d0b22 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.strokeStyle.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.strokeStyle.html
@@ -25,7 +25,7 @@
   ctx.strokeStyle = "#ff0000";
   old = ctx.strokeStyle;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against "#ff0000".
+  // from rounding), so compare against `old` instead of against "#ff0000".
   ctx.save();
   _assertSame(ctx.strokeStyle, old, "ctx.strokeStyle", "old");
   ctx.restore();
@@ -46,7 +46,7 @@
   ctx.strokeStyle = "#ff0000";
   old = ctx.strokeStyle;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against "#ff0000".
+  // from rounding), so compare against `old` instead of against "#ff0000".
   ctx.save();
   _assertSame(ctx.strokeStyle, old, "ctx.strokeStyle", "old");
   ctx.restore();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.strokeStyle.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.strokeStyle.worker.js
index bb10ee9..5719917 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.strokeStyle.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/the-canvas-state/2d.state.saverestore.strokeStyle.worker.js
@@ -21,7 +21,7 @@
   ctx.strokeStyle = "#ff0000";
   old = ctx.strokeStyle;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against "#ff0000".
+  // from rounding), so compare against `old` instead of against "#ff0000".
   ctx.save();
   _assertSame(ctx.strokeStyle, old, "ctx.strokeStyle", "old");
   ctx.restore();
@@ -42,7 +42,7 @@
   ctx.strokeStyle = "#ff0000";
   old = ctx.strokeStyle;
   // We're not interested in failures caused by get(set(x)) != x (e.g.
-  // from rounding), so compare against 'old' instead of against "#ff0000".
+  // from rounding), so compare against `old` instead of against "#ff0000".
   ctx.save();
   _assertSame(ctx.strokeStyle, old, "ctx.strokeStyle", "old");
   ctx.restore();
diff --git a/third_party/blink/web_tests/external/wpt/html/dom/elements/global-attributes/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/html/dom/elements/global-attributes/WEB_FEATURES.yml
new file mode 100644
index 0000000..6f4af901
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/dom/elements/global-attributes/WEB_FEATURES.yml
@@ -0,0 +1,4 @@
+features:
+- name: style-attr
+  files:
+  - style-01.html
diff --git a/third_party/blink/web_tests/external/wpt/html/editing/dnd/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/html/editing/dnd/WEB_FEATURES.yml
new file mode 100644
index 0000000..5fb6e57e
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/editing/dnd/WEB_FEATURES.yml
@@ -0,0 +1,3 @@
+features:
+- name: draganddrop
+  files: '**'
diff --git a/third_party/blink/web_tests/external/wpt/html/editing/editing-0/spelling-and-grammar-checking/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/html/editing/editing-0/spelling-and-grammar-checking/WEB_FEATURES.yml
new file mode 100644
index 0000000..5209f1c1
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/editing/editing-0/spelling-and-grammar-checking/WEB_FEATURES.yml
@@ -0,0 +1,3 @@
+features:
+- name: spellcheck
+  files: "**"
diff --git a/third_party/blink/web_tests/external/wpt/html/infrastructure/safe-passing-of-structured-data/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/html/infrastructure/safe-passing-of-structured-data/WEB_FEATURES.yml
new file mode 100644
index 0000000..730bf7c
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/infrastructure/safe-passing-of-structured-data/WEB_FEATURES.yml
@@ -0,0 +1,12 @@
+features:
+- name: channel-messaging
+  files:
+  - messagechannel.any.js
+  - transfer-errors.window.js
+- name: postmessage
+  files:
+  - cross-origin-transfer-resizable-arraybuffer.html
+  - structured-cloning-error-extra.html
+  - structured-cloning-error-stack-optional.sub.window.js
+  - structuredclone_0.html
+  - window-postmessage.window.js
diff --git a/third_party/blink/web_tests/external/wpt/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/WEB_FEATURES.yml
new file mode 100644
index 0000000..76b77fd
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/WEB_FEATURES.yml
@@ -0,0 +1,18 @@
+features:
+- name: broadcast-channel
+  files:
+  - broadcastchannel-*
+- name: channel-messaging
+  files:
+  - window-messagechannel*
+  - window-*-messagechannel*
+- name: postmessage
+  files:
+  - blob-data.https.html
+  - identity-not-preserved.https.html
+  - no-coop-coep.https.any.js
+  - no-transferring.https.html
+  - window-serviceworker-failure.https.html
+  - window-sharedworker-failure.https.html
+  - window-simple-success.https.html
+  - window-simple-success.https.html.headers
diff --git a/third_party/blink/web_tests/external/wpt/html/infrastructure/urls/base-url/base-url-detached-document.https.window.js b/third_party/blink/web_tests/external/wpt/html/infrastructure/urls/base-url/base-url-detached-document-blank.https.window.js
similarity index 70%
rename from third_party/blink/web_tests/external/wpt/html/infrastructure/urls/base-url/base-url-detached-document.https.window.js
rename to third_party/blink/web_tests/external/wpt/html/infrastructure/urls/base-url/base-url-detached-document-blank.https.window.js
index c51f425b..dadcd48a 100644
--- a/third_party/blink/web_tests/external/wpt/html/infrastructure/urls/base-url/base-url-detached-document.https.window.js
+++ b/third_party/blink/web_tests/external/wpt/html/infrastructure/urls/base-url/base-url-detached-document-blank.https.window.js
@@ -1,13 +1,9 @@
-// Verify that an about:blank or about:srcdoc document remembers the baseURI
+// Verify that an about:blank document remembers the baseURI
 // it was created with even after it's detached.
-const runTest = (frame_type) => {
+const runTest = () => {
   async_test((t) => {
     const frame = document.createElement('iframe');
-
-    if (frame_type == "about:blank")
-      frame.src = "about:blank";
-    else
-      frame.srcdoc = "foo";
+    frame.src = "about:blank";
 
     frame.onload = () => {
       const frame_doc = frame.contentDocument;
@@ -25,10 +21,9 @@
     };
 
     document.body.appendChild(frame);
-  }, frame_type);
+  }, "about:blank");
 };
 
 onload = () => {
-  runTest("about:blank");
-  runTest("about:srcdoc");
+  runTest();
 };
diff --git a/third_party/blink/web_tests/external/wpt/html/infrastructure/urls/base-url/base-url-detached-document.https.window.js b/third_party/blink/web_tests/external/wpt/html/infrastructure/urls/base-url/base-url-detached-document-srcdoc.https.window.js
similarity index 70%
copy from third_party/blink/web_tests/external/wpt/html/infrastructure/urls/base-url/base-url-detached-document.https.window.js
copy to third_party/blink/web_tests/external/wpt/html/infrastructure/urls/base-url/base-url-detached-document-srcdoc.https.window.js
index c51f425b..17bf413 100644
--- a/third_party/blink/web_tests/external/wpt/html/infrastructure/urls/base-url/base-url-detached-document.https.window.js
+++ b/third_party/blink/web_tests/external/wpt/html/infrastructure/urls/base-url/base-url-detached-document-srcdoc.https.window.js
@@ -1,13 +1,9 @@
-// Verify that an about:blank or about:srcdoc document remembers the baseURI
+// Verify that an about:srcdoc document remembers the baseURI
 // it was created with even after it's detached.
-const runTest = (frame_type) => {
+const runTest = () => {
   async_test((t) => {
     const frame = document.createElement('iframe');
-
-    if (frame_type == "about:blank")
-      frame.src = "about:blank";
-    else
-      frame.srcdoc = "foo";
+    frame.srcdoc = "foo";
 
     frame.onload = () => {
       const frame_doc = frame.contentDocument;
@@ -25,10 +21,9 @@
     };
 
     document.body.appendChild(frame);
-  }, frame_type);
+  }, "about:srcdoc");
 };
 
 onload = () => {
-  runTest("about:blank");
-  runTest("about:srcdoc");
+  runTest();
 };
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/document-metadata/the-style-element/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/html/semantics/document-metadata/the-style-element/WEB_FEATURES.yml
new file mode 100644
index 0000000..089ad84
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/document-metadata/the-style-element/WEB_FEATURES.yml
@@ -0,0 +1,3 @@
+features:
+- name: style
+  files: "**"
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/media-elements/event_order_durationchange_resize_loadedmetadata.html b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/media-elements/event_order_durationchange_resize_loadedmetadata.html
new file mode 100644
index 0000000..52fa104
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/media-elements/event_order_durationchange_resize_loadedmetadata.html
@@ -0,0 +1,55 @@
+<!doctype html>
+<html>
+ <head>
+  <title>{audio,video} events - durationchange, resize (only video), then loadedmetadata</title>
+  <script src="/resources/testharness.js"></script>
+  <script src="/resources/testharnessreport.js"></script>
+  <script src="/common/media.js"></script>
+ </head>
+ <body>
+  <p><a href="https://html.spec.whatwg.org/multipage/#mediaevents">spec reference</a></p>
+  <audio id="a" autoplay controls>
+  </audio>
+  <video id="v" autoplay controls>
+  </video>
+  <div id="log"></div>
+  <script>
+test(function() {
+  var t = async_test("setting src attribute on autoplay audio should trigger durationchange then loadedmetadata event");
+  var a = document.getElementById("a");
+  var found_durationchange = false;
+  a.addEventListener("error", t.unreached_func());
+  a.addEventListener("durationchange", t.step_func(function() {
+    found_durationchange = true;
+  }));
+  a.addEventListener("loadedmetadata", t.step_func(function() {
+    assert_true(found_durationchange);
+    t.done();
+    a.pause();
+  }), false);
+  a.src = getAudioURI("/media/sound_5") + "?" + new Date() + Math.random();
+}, "audio events - durationchange, then loadedmetadata");
+
+test(function() {
+  var t = async_test("setting src attribute on autoplay video should trigger durationchange, resize then loadedmetadata event");
+  var v = document.getElementById("v");
+  var found_durationchange = false;
+  var found_resize = false;
+  v.addEventListener("error", t.unreached_func());
+  v.addEventListener("durationchange", t.step_func(function() {
+    found_durationchange = true;
+  }));
+  v.addEventListener("resize", t.step_func(function() {
+    found_resize = true;
+  }));
+  v.addEventListener("loadedmetadata", t.step_func(function() {
+    assert_true(found_durationchange);
+    assert_true(found_resize);
+    t.done();
+    v.pause();
+  }), false);
+  v.src = getVideoURI("/media/movie_5") + "?" + new Date() + Math.random();
+}, "video events - durationchange, resize then loadeddata");
+ </script>
+ </body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-area-element/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-area-element/WEB_FEATURES.yml
new file mode 100644
index 0000000..e352776
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-area-element/WEB_FEATURES.yml
@@ -0,0 +1,4 @@
+features:
+  - name: download
+    files:
+      - 'area-download-click.html'
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-embed-element/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-embed-element/WEB_FEATURES.yml
new file mode 100644
index 0000000..4ce21f0
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-embed-element/WEB_FEATURES.yml
@@ -0,0 +1,5 @@
+features:
+  - name: embed
+    files:
+      - '*'
+      - '!*-ref.*'
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-iframe-element/iframe_javascript_url_xhr-expected.txt b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-iframe-element/iframe_javascript_url_xhr-expected.txt
deleted file mode 100644
index 16162274..0000000
--- a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-iframe-element/iframe_javascript_url_xhr-expected.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-This is a testharness.js-based test.
-[FAIL] Image load should not trigger load event for non-initial javascript URL load
-  assert_true: iframeLoaded expected true got false
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-iframe-element/iframe_javascript_url_xhr.html b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-iframe-element/iframe_javascript_url_xhr.html
index 107862d..fd07bef5 100644
--- a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-iframe-element/iframe_javascript_url_xhr.html
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-iframe-element/iframe_javascript_url_xhr.html
@@ -24,20 +24,15 @@
 };
 
 promise_test(async t => {
-  iframeLoaded = false;
+  assert_true(iframeLoaded, "iframeLoaded", "Initial iframe load occured synchronously");
   javascriptUrlRan = false;
 
-  await new Promise(resolve => {
-    window.addEventListener("load", resolve, { once: true });
-  });
-
   let scriptExecutePromise = new Promise(resolve => {
     document.addEventListener("scriptExecuted", function() {
       resolve();
     }, { once: true });
   });
 
-  assert_true(iframeLoaded, "iframeLoaded");
   const iframe = document.querySelector('iframe');
 
   // Load image on iframe.
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-img-element/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-img-element/WEB_FEATURES.yml
index 28d0ead2..910320a 100644
--- a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-img-element/WEB_FEATURES.yml
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-img-element/WEB_FEATURES.yml
@@ -7,3 +7,8 @@
   - relevant-mutations-lazy.html
   - remove-element-and-scroll.html
   - scrolling-below-viewport-image-lazy-loading-in-iframe.html
+- name: srcset
+  files:
+  - responsive-image-select-print.html
+  - update-the-source-set.html
+  - null-image-source.html
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-img-element/sizes/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-img-element/sizes/WEB_FEATURES.yml
new file mode 100644
index 0000000..ad6fbd5
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-img-element/sizes/WEB_FEATURES.yml
@@ -0,0 +1,3 @@
+features:
+- name: srcset
+  files: "**"
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-img-element/srcset/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-img-element/srcset/WEB_FEATURES.yml
new file mode 100644
index 0000000..ad6fbd5
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-img-element/srcset/WEB_FEATURES.yml
@@ -0,0 +1,3 @@
+features:
+- name: srcset
+  files: "**"
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/forms/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/html/semantics/forms/WEB_FEATURES.yml
new file mode 100644
index 0000000..7101f2a9
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/forms/WEB_FEATURES.yml
@@ -0,0 +1,4 @@
+features:
+- name: change-event
+  files:
+  - input-change-event-properties.html
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-input-element/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-input-element/WEB_FEATURES.yml
index 4cb6a13e..6d25995 100644
--- a/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-input-element/WEB_FEATURES.yml
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-input-element/WEB_FEATURES.yml
@@ -18,3 +18,6 @@
   files:
   - type-change-file-to-text-crash.html
   - input-value-invalidstateerr.html
+- name: change-event
+  files:
+  - change-to-empty-value.html
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-textarea-element/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-textarea-element/WEB_FEATURES.yml
index 14479d4b..36350df 100644
--- a/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-textarea-element/WEB_FEATURES.yml
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-textarea-element/WEB_FEATURES.yml
@@ -4,3 +4,6 @@
   - textarea-setcustomvalidity.html
   - textarea-validity-clone.html
   - textarea-validity-valueMissing-inside-datalist.html
+- name: change-event
+  files:
+  - change-to-empty-value.html
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-textarea-element/wrapping-transformation.window.js b/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-textarea-element/wrapping-transformation.window.js
index c5c28a4..70e0c3ce 100644
--- a/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-textarea-element/wrapping-transformation.window.js
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-textarea-element/wrapping-transformation.window.js
@@ -56,3 +56,27 @@
     "The value must be wrapped.",
   );
 }, "Textarea wrapping transformation: Wrapping happens with LF newlines.");
+
+function assert_roundtrips(text) {
+  test((t) => {
+    const form = document.createElement("form");
+    const textarea = document.createElement("textarea");
+    textarea.name = "wrapTest";
+    textarea.cols = 10;
+    textarea.wrap = "hard";
+    form.appendChild(textarea);
+    document.body.appendChild(form);
+    t.add_cleanup(() => {
+      document.body.removeChild(form);
+    });
+    textarea.value = text;
+    const formDataValue = new FormData(form).get("wrapTest");
+    textarea.value = formDataValue;
+    const newFormDataValue = new FormData(form).get("wrapTest");
+    assert_equals(formDataValue, newFormDataValue, "Value should round-trip");
+  }, "Textarea wrapping transformation: wrapping round-trips: " + text);
+}
+
+assert_roundtrips("Some text that is too long for the specified character width.");
+assert_roundtrips("Some text that is too long for the\n\n\nspecified character width.");
+assert_roundtrips("exact  len");
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/text-level-semantics/the-a-element/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/html/semantics/text-level-semantics/the-a-element/WEB_FEATURES.yml
new file mode 100644
index 0000000..eb578ea
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/text-level-semantics/the-a-element/WEB_FEATURES.yml
@@ -0,0 +1,4 @@
+features:
+  - name: download
+    files:
+      - 'a-download-*'
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/text-level-semantics/the-wbr-element/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/html/semantics/text-level-semantics/the-wbr-element/WEB_FEATURES.yml
new file mode 100644
index 0000000..d56b4a9
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/text-level-semantics/the-wbr-element/WEB_FEATURES.yml
@@ -0,0 +1,3 @@
+features:
+- name: wbr
+  files: '**'
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/the-style-element/tentative/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/html/semantics/the-style-element/tentative/WEB_FEATURES.yml
new file mode 100644
index 0000000..089ad84
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/the-style-element/tentative/WEB_FEATURES.yml
@@ -0,0 +1,3 @@
+features:
+- name: style
+  files: "**"
diff --git a/third_party/blink/web_tests/external/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/WEB_FEATURES.yml
index 2c43701e..8d74a882 100644
--- a/third_party/blink/web_tests/external/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/WEB_FEATURES.yml
+++ b/third_party/blink/web_tests/external/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/WEB_FEATURES.yml
@@ -9,3 +9,6 @@
 - name: registerprotocolhandler
   files:
   - protocol-*
+- name: window-controls-overlay
+  files:
+  - navigator-window-controls-overlay.tentative.html
diff --git a/third_party/blink/web_tests/external/wpt/interfaces/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/interfaces/WEB_FEATURES.yml
new file mode 100644
index 0000000..61b6bd43
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/interfaces/WEB_FEATURES.yml
@@ -0,0 +1,4 @@
+features:
+- name: barcode
+  files:
+  - shape-detection-api.idl
diff --git a/third_party/blink/web_tests/external/wpt/interfaces/clipboard-apis.idl b/third_party/blink/web_tests/external/wpt/interfaces/clipboard-apis.idl
index 594484ce..ddbc0c0 100644
--- a/third_party/blink/web_tests/external/wpt/interfaces/clipboard-apis.idl
+++ b/third_party/blink/web_tests/external/wpt/interfaces/clipboard-apis.idl
@@ -13,6 +13,18 @@
   readonly attribute DataTransfer? clipboardData;
 };
 
+dictionary ClipboardChangeEventInit : EventInit {
+  sequence<DOMString> types = [];
+  bigint changeId = 0;
+};
+
+[Exposed=Window]
+interface ClipboardChangeEvent : Event {
+  constructor(DOMString type, optional ClipboardChangeEventInit eventInitDict = {});
+  readonly attribute FrozenArray<DOMString> types;
+  readonly attribute bigint changeId;
+};
+
 partial interface Navigator {
   [SecureContext, SameObject] readonly attribute Clipboard clipboard;
 };
diff --git a/third_party/blink/web_tests/external/wpt/interfaces/crash-reporting.idl b/third_party/blink/web_tests/external/wpt/interfaces/crash-reporting.idl
index 60f15bb..b172e73 100644
--- a/third_party/blink/web_tests/external/wpt/interfaces/crash-reporting.idl
+++ b/third_party/blink/web_tests/external/wpt/interfaces/crash-reporting.idl
@@ -7,5 +7,5 @@
   DOMString reason;
   DOMString stack;
   boolean is_top_level;
-  DocumentVisibilityState page_visibility;
+  DocumentVisibilityState visibility_state;
 };
diff --git a/third_party/blink/web_tests/external/wpt/interfaces/css-view-transitions.idl b/third_party/blink/web_tests/external/wpt/interfaces/css-view-transitions.idl
index 3260bb68..c8b1b94 100644
--- a/third_party/blink/web_tests/external/wpt/interfaces/css-view-transitions.idl
+++ b/third_party/blink/web_tests/external/wpt/interfaces/css-view-transitions.idl
@@ -36,7 +36,7 @@
   readonly attribute Promise<undefined> ready;
   readonly attribute Promise<undefined> finished;
   undefined skipTransition();
-  attribute ViewTransitionTypeSet types;
+  [SameObject] attribute ViewTransitionTypeSet types;
   readonly attribute Element transitionRoot;
   undefined waitUntil(Promise<any> promise);
 };
diff --git a/third_party/blink/web_tests/external/wpt/interfaces/dom.idl b/third_party/blink/web_tests/external/wpt/interfaces/dom.idl
index 2b43b98..1ddc084 100644
--- a/third_party/blink/web_tests/external/wpt/interfaces/dom.idl
+++ b/third_party/blink/web_tests/external/wpt/interfaces/dom.idl
@@ -313,7 +313,7 @@
 interface XMLDocument : Document {};
 
 dictionary ElementCreationOptions {
-  CustomElementRegistry customElementRegistry;
+  CustomElementRegistry? customElementRegistry;
   DOMString is;
 };
 
@@ -412,7 +412,7 @@
   SlotAssignmentMode slotAssignment = "named";
   boolean clonable = false;
   boolean serializable = false;
-  CustomElementRegistry? customElementRegistry = null;
+  CustomElementRegistry? customElementRegistry;
 };
 
 [Exposed=Window,
diff --git a/third_party/blink/web_tests/external/wpt/interfaces/html.idl b/third_party/blink/web_tests/external/wpt/interfaces/html.idl
index 072baa4..d2e2879 100644
--- a/third_party/blink/web_tests/external/wpt/interfaces/html.idl
+++ b/third_party/blink/web_tests/external/wpt/interfaces/html.idl
@@ -1613,7 +1613,7 @@
 OffscreenCanvasRenderingContext2D includes CanvasTextDrawingStyles;
 OffscreenCanvasRenderingContext2D includes CanvasPath;
 
-enum PredefinedColorSpace { "srgb", "display-p3" };
+enum PredefinedColorSpace { "srgb", "srgb-linear", "display-p3", "display-p3-linear" };
 
 [Exposed=Window]
 interface CustomElementRegistry {
diff --git a/third_party/blink/web_tests/external/wpt/interfaces/privacy-preserving-attribution.idl b/third_party/blink/web_tests/external/wpt/interfaces/privacy-preserving-attribution.idl
index 14dbe47f..d335a54 100644
--- a/third_party/blink/web_tests/external/wpt/interfaces/privacy-preserving-attribution.idl
+++ b/third_party/blink/web_tests/external/wpt/interfaces/privacy-preserving-attribution.idl
@@ -7,10 +7,10 @@
   [SecureContext, SameObject] readonly attribute Attribution attribution;
 };
 
-enum AttributionAggregationProtocol { "dap-15-histogram", "tee-00" };
+enum AttributionAggregationProtocol { "dap-15-histogram" };
 
 dictionary AttributionAggregationService {
-  required DOMString protocol;
+  required AttributionAggregationProtocol protocol;
 };
 
 [SecureContext, Exposed=Window]
diff --git a/third_party/blink/web_tests/external/wpt/interfaces/webextensions.idl b/third_party/blink/web_tests/external/wpt/interfaces/webextensions.idl
new file mode 100644
index 0000000..bd9246d
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/interfaces/webextensions.idl
@@ -0,0 +1,15 @@
+// GENERATED CONTENT - DO NOT EDIT
+// Content was automatically extracted by Reffy into webref
+// (https://github.com/w3c/webref)
+// Source: Web Extensions (https://w3c.github.io/webextensions/specification/)
+
+enum RunAt {
+    "document_start",
+    "document_end",
+    "document_idle"
+};
+
+enum ExecutionWorld {
+    "ISOLATED",
+    "MAIN"
+};
diff --git a/third_party/blink/web_tests/external/wpt/interfaces/webgpu.idl b/third_party/blink/web_tests/external/wpt/interfaces/webgpu.idl
index e36637d..377ce9d5 100644
--- a/third_party/blink/web_tests/external/wpt/interfaces/webgpu.idl
+++ b/third_party/blink/web_tests/external/wpt/interfaces/webgpu.idl
@@ -25,7 +25,11 @@
     readonly attribute unsigned long maxSampledTexturesPerShaderStage;
     readonly attribute unsigned long maxSamplersPerShaderStage;
     readonly attribute unsigned long maxStorageBuffersPerShaderStage;
+    readonly attribute unsigned long maxStorageBuffersInVertexStage;
+    readonly attribute unsigned long maxStorageBuffersInFragmentStage;
     readonly attribute unsigned long maxStorageTexturesPerShaderStage;
+    readonly attribute unsigned long maxStorageTexturesInVertexStage;
+    readonly attribute unsigned long maxStorageTexturesInFragmentStage;
     readonly attribute unsigned long maxUniformBuffersPerShaderStage;
     readonly attribute unsigned long long maxUniformBufferBindingSize;
     readonly attribute unsigned long long maxStorageBufferBindingSize;
@@ -229,6 +233,7 @@
     readonly attribute GPUTextureDimension dimension;
     readonly attribute GPUTextureFormat format;
     readonly attribute GPUFlagsConstant usage;
+    readonly attribute (GPUTextureViewDimension or undefined) textureBindingViewDimension;
 };
 GPUTexture includes GPUObjectBase;
 
@@ -241,6 +246,7 @@
     required GPUTextureFormat format;
     required GPUTextureUsageFlags usage;
     sequence<GPUTextureFormat> viewFormats = [];
+    GPUTextureViewDimension textureBindingViewDimension;
 };
 
 enum GPUTextureDimension {
diff --git a/third_party/blink/web_tests/external/wpt/interfaces/webrtc-encoded-transform.idl b/third_party/blink/web_tests/external/wpt/interfaces/webrtc-encoded-transform.idl
index ff38f5e2..998c3b5 100644
--- a/third_party/blink/web_tests/external/wpt/interfaces/webrtc-encoded-transform.idl
+++ b/third_party/blink/web_tests/external/wpt/interfaces/webrtc-encoded-transform.idl
@@ -87,14 +87,6 @@
     DOMString mimeType;
 };
 
-// New enum for video frame types. Will eventually re-use the equivalent defined
-// by WebCodecs.
-enum RTCEncodedVideoFrameType {
-    "empty",
-    "key",
-    "delta",
-};
-
 dictionary RTCEncodedVideoFrameMetadata : RTCEncodedFrameMetadata {
     unsigned long long frameId;
     sequence<unsigned long long> dependencies;
@@ -109,12 +101,11 @@
     RTCEncodedVideoFrameMetadata metadata;
 };
 
-// New interfaces to define encoded video and audio frames. Will eventually
-// re-use or extend the equivalent defined in WebCodecs.
+// New interfaces to define RTC specific encoded video and audio frames used by RTCRtpScriptTransform.
 [Exposed=(Window,DedicatedWorker), Serializable]
 interface RTCEncodedVideoFrame {
     constructor(RTCEncodedVideoFrame originalFrame, optional RTCEncodedVideoFrameOptions options = {});
-    readonly attribute RTCEncodedVideoFrameType type;
+    readonly attribute EncodedVideoChunkType type;
     attribute ArrayBuffer data;
     RTCEncodedVideoFrameMetadata getMetadata();
 };
diff --git a/third_party/blink/web_tests/external/wpt/interfaces/webxr-plane-detection.idl b/third_party/blink/web_tests/external/wpt/interfaces/webxr-plane-detection.idl
index 037e9e2..3c48742f 100644
--- a/third_party/blink/web_tests/external/wpt/interfaces/webxr-plane-detection.idl
+++ b/third_party/blink/web_tests/external/wpt/interfaces/webxr-plane-detection.idl
@@ -1,7 +1,7 @@
 // GENERATED CONTENT - DO NOT EDIT
 // Content was automatically extracted by Reffy into webref
 // (https://github.com/w3c/webref)
-// Source: WebXR Plane Detection Module (https://immersive-web.github.io/real-world-geometry/plane-detection.html)
+// Source: WebXR Plane Detection Module (https://immersive-web.github.io/plane-detection/)
 
 enum XRPlaneOrientation {
     "horizontal",
diff --git a/third_party/blink/web_tests/external/wpt/interfaces/webxrlayers.idl b/third_party/blink/web_tests/external/wpt/interfaces/webxrlayers.idl
index b99bef7..99b5989 100644
--- a/third_party/blink/web_tests/external/wpt/interfaces/webxrlayers.idl
+++ b/third_party/blink/web_tests/external/wpt/interfaces/webxrlayers.idl
@@ -118,6 +118,7 @@
 
 dictionary XRLayerInit {
   required XRSpace space;
+  XRTextureType textureType = "texture";
   GLenum colorFormat = 0x1908; // RGBA
   GLenum? depthFormat;
   unsigned long mipLevels = 1;
@@ -129,14 +130,12 @@
 };
 
 dictionary XRQuadLayerInit : XRLayerInit {
-  XRTextureType textureType = "texture";
   XRRigidTransform? transform;
   float width = 1.0;
   float height = 1.0;
 };
 
 dictionary XRCylinderLayerInit : XRLayerInit {
-  XRTextureType textureType = "texture";
   XRRigidTransform? transform;
   float radius = 2.0;
   float centralAngle = 0.78539;
@@ -144,7 +143,6 @@
 };
 
 dictionary XREquirectLayerInit : XRLayerInit {
-  XRTextureType textureType = "texture";
   XRRigidTransform? transform;
   float radius = 0;
   float centralHorizontalAngle = 6.28318;
diff --git a/third_party/blink/web_tests/external/wpt/mathml/relations/css-styling/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/mathml/relations/css-styling/WEB_FEATURES.yml
new file mode 100644
index 0000000..8b00e53
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/mathml/relations/css-styling/WEB_FEATURES.yml
@@ -0,0 +1,4 @@
+features:
+- name: clip
+  files:
+  - clip.html
diff --git a/third_party/blink/web_tests/external/wpt/mathml/relations/css-styling/non-mathml-namespace-001-ref.html b/third_party/blink/web_tests/external/wpt/mathml/relations/css-styling/non-mathml-namespace-001-ref.html
new file mode 100644
index 0000000..974508c5
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/mathml/relations/css-styling/non-mathml-namespace-001-ref.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>Non MathML namespace (reference)</title>
+  </head>
+  <body>
+    <div>
+      <span><span>Hello</span> <span>World!</span></span><span><span>Hello</span> <span>World!</span></span>
+    </div>
+  </body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/mathml/relations/css-styling/non-mathml-namespace-001.html b/third_party/blink/web_tests/external/wpt/mathml/relations/css-styling/non-mathml-namespace-001.html
new file mode 100644
index 0000000..b0b2123
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/mathml/relations/css-styling/non-mathml-namespace-001.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>Non MathML namespace</title>
+    <link rel="help" href="https://www.w3.org/TR/mathml-core/#user-agent-stylesheet">
+    <link rel="match" href="non-mathml-namespace-001-ref.html">
+    <meta name="assert" content="Verify that MathML stylesheets only apply to its namespace.">
+  </head>
+  <body>
+    <div id="container"></div>
+    <script>
+      ["semantics", "maction"].forEach((tagName) => {
+        const nonMathMLNS = "http://www.w3.org/1999/xhtml";
+        let el = document.createElement(tagName, nonMathMLNS);
+        el.innerHTML = "<span>Hello</span> <span>World!</span>";
+        container.appendChild(el);
+      });
+    </script>
+  </body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/mediacapture-fromelement/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/mediacapture-fromelement/WEB_FEATURES.yml
new file mode 100644
index 0000000..77c09171
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/mediacapture-fromelement/WEB_FEATURES.yml
@@ -0,0 +1,11 @@
+features:
+- name: capture-stream-audio-video
+  files:
+  - capture-muted.html
+  - capture.html
+  - creation.html
+  - cross-origin.html
+  - ended.html
+- name: capture-stream-canvas
+  files:
+  - HTMLCanvasElement-*
diff --git a/third_party/blink/web_tests/external/wpt/mediacapture-streams/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/mediacapture-streams/WEB_FEATURES.yml
new file mode 100644
index 0000000..a47daa0
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/mediacapture-streams/WEB_FEATURES.yml
@@ -0,0 +1,4 @@
+features:
+- name: element-capture
+  files:
+  - 'BrowserCaptureMediaStreamTrack-restrictTo.https.html'
diff --git a/third_party/blink/web_tests/external/wpt/notifications/icon-fetch-sw.js b/third_party/blink/web_tests/external/wpt/notifications/icon-fetch-sw.js
new file mode 100644
index 0000000..31e4e1e
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/notifications/icon-fetch-sw.js
@@ -0,0 +1,12 @@
+self.addEventListener('activate', (ev) => {
+  // claim() to control fetch immediately.
+  ev.waitUntil(self.clients.claim());
+});
+
+self.addEventListener('fetch', (ev) => {
+  ev.waitUntil((async () => {
+    const client = await self.clients.get(ev.clientId);
+    client.postMessage({ url: ev.request.url });
+  })());
+  ev.respondWith(fetch(ev.request));
+})
diff --git a/third_party/blink/web_tests/external/wpt/notifications/icon-fetch.tentative.https.window-expected.txt b/third_party/blink/web_tests/external/wpt/notifications/icon-fetch.tentative.https.window-expected.txt
new file mode 100644
index 0000000..d907087f
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/notifications/icon-fetch.tentative.https.window-expected.txt
@@ -0,0 +1,5 @@
+This is a testharness.js-based test.
+Harness Error. harness_status.status = 1 , harness_status.message = Error: Should have the permission granted to continue but found denied
+[NOTRUN] Icon fetch should cause a corresponding fetch event in the service worker
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/notifications/icon-fetch.tentative.https.window.js b/third_party/blink/web_tests/external/wpt/notifications/icon-fetch.tentative.https.window.js
new file mode 100644
index 0000000..b04df9d
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/notifications/icon-fetch.tentative.https.window.js
@@ -0,0 +1,30 @@
+// META: script=/resources/testdriver.js
+// META: script=/resources/testdriver-vendor.js
+// META: script=/service-workers/service-worker/resources/test-helpers.sub.js
+// META: script=resources/helpers.js
+
+let registration;
+
+promise_setup(async () => {
+  await trySettingPermission("granted");
+  registration = await prepareActiveServiceWorker("icon-fetch-sw.js");
+  await new Promise(r => navigator.serviceWorker.addEventListener("controllerchange", r, { once: true }));
+});
+
+promise_test(async t => {
+  const iconUrl = new URL("resources/icon.png", location.href).toString();
+
+  const { promise, resolve } = Promise.withResolvers();
+  navigator.serviceWorker.addEventListener("message", async ev => {
+    if (ev.data.url === iconUrl) {
+      resolve();
+    }
+  }, { signal: t.signal });
+
+  await registration.showNotification("new Notification", {
+    icon: iconUrl
+  });
+  t.add_cleanup(closeAllNotifications);
+
+  await promise;
+}, "Icon fetch should cause a corresponding fetch event in the service worker");
diff --git a/third_party/blink/web_tests/external/wpt/shape-detection/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/shape-detection/WEB_FEATURES.yml
new file mode 100644
index 0000000..ac71d826
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/shape-detection/WEB_FEATURES.yml
@@ -0,0 +1,25 @@
+features:
+- name: barcode
+  files:
+  - detected-boundingBox-read-only.https.html
+  - detected-postMessage.https.html
+  - detection-Blob.https.window.js
+  - detection-HTMLCanvasElement.https.html
+  - detection-HTMLImageElement-empty-src.https.html
+  - detection-HTMLImageElement-zero-dimension-image.https.html
+  - detection-HTMLImageElement.https.html
+  - detection-HTMLVideoElement.https.html
+  - detection-ImageBitmap-closed.https.window.js
+  - detection-ImageBitmap.https.html
+  - detection-ImageData-detached.https.html
+  - detection-ImageData.https.html
+  - detection-SVGImageElement.https.window.js
+  - detection-VideoFrame.https.window.js
+  - detection-getSupportedFormats.https.html
+  - detection-on-worker.https.worker.js
+  - detection-options.https.html
+  - detection-security-test.https.html
+  - detector-same-object.https.html
+  - idlharness.https.any.js
+  - shapedetection-cross-origin.sub.https.html
+  - single-barcode-detection.https.html
diff --git a/third_party/blink/web_tests/external/wpt/shared-storage/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/shared-storage/WEB_FEATURES.yml
new file mode 100644
index 0000000..d532c98
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/shared-storage/WEB_FEATURES.yml
@@ -0,0 +1,8 @@
+features:
+- name: shared-storage
+  files:
+  - "*"
+  - "!web-locks*"
+- name: shared-storage-locks
+  files:
+  - web-locks*
diff --git a/third_party/blink/web_tests/external/wpt/svg/animations/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/svg/animations/WEB_FEATURES.yml
new file mode 100644
index 0000000..b7ca2ad
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/svg/animations/WEB_FEATURES.yml
@@ -0,0 +1,4 @@
+features:
+- name: smil-svg-animations
+  files: "**"
+
diff --git a/third_party/blink/web_tests/external/wpt/svg/linking/scripted/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/svg/linking/scripted/WEB_FEATURES.yml
new file mode 100644
index 0000000..b5ba3cc
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/svg/linking/scripted/WEB_FEATURES.yml
@@ -0,0 +1,4 @@
+features:
+  - name: download
+    files:
+      - 'a-download-click.svg'
diff --git a/third_party/blink/web_tests/external/wpt/svg/render/order/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/svg/render/order/WEB_FEATURES.yml
new file mode 100644
index 0000000..e34035e
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/svg/render/order/WEB_FEATURES.yml
@@ -0,0 +1,4 @@
+features:
+- name: z-index
+  files:
+  - z-index.svg
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/svg/styling/css-var-dom-interface.html b/third_party/blink/web_tests/external/wpt/svg/styling/css-var-dom-interface.html
new file mode 100644
index 0000000..a10bee7
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/svg/styling/css-var-dom-interface.html
@@ -0,0 +1,131 @@
+<!DOCTYPE html>
+<title>SVGLength DOM interface with CSS variables</title>
+<link rel="author" title="Divyansh Mangal" href="mailto:dmangal@microsoft.com">
+<link rel="help" href="https://github.com/w3c/svgwg/issues/1038">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<style>
+  :root {
+    --length-100px: 100px;
+    --length-50: 50;
+    --length-75percent: 75%;
+    --invalid-length: not-a-length;
+  }
+</style>
+
+<svg id="svg" width="200" height="200">
+  <rect id="rect1-px" width="var(--length-100px)" height="50"/>
+  <rect id="rect2-px" width="var(--length-100px)" height="50"/>
+  <rect id="rect3-px" width="var(--length-100px)" height="50"/>
+  <rect id="rect-number" width="var(--length-50)" height="50"/>
+  <rect id="rect-percent" width="var(--length-75percent)" height="50"/>
+  <rect id="rect-invalid" width="var(--invalid-length)" height="50"/>
+  <rect id="rect-normal" width="100px" height="50"/>
+</svg>
+
+<script>
+test(() => {
+  const rect = document.getElementById('rect1-px');
+  const width = rect.width.baseVal;
+
+  assert_equals(width.value, 0);
+  assert_equals(width.valueInSpecifiedUnits, 0);
+  assert_equals(width.unitType, SVGLength.SVG_LENGTHTYPE_UNKNOWN);
+
+}, 'SVGLength DOM interface with CSS variable containing px unit');
+
+test(() => {
+  const rect = document.getElementById('rect-number');
+  const width = rect.width.baseVal;
+
+  assert_equals(width.value, 0);
+  assert_equals(width.valueInSpecifiedUnits, 0);
+  assert_equals(width.unitType, SVGLength.SVG_LENGTHTYPE_UNKNOWN);
+
+}, 'SVGLength DOM interface with CSS variable containing number');
+
+test(() => {
+  const rect = document.getElementById('rect-percent');
+  const width = rect.width.baseVal;
+
+  assert_equals(width.value, 0);
+  assert_equals(width.valueInSpecifiedUnits, 0);
+  assert_equals(width.unitType, SVGLength.SVG_LENGTHTYPE_UNKNOWN);
+
+}, 'SVGLength DOM interface with CSS variable containing percentage');
+
+test(() => {
+  const rect = document.getElementById('rect-invalid');
+  const width = rect.width.baseVal;
+
+  assert_equals(width.value, 0);
+  assert_equals(width.valueInSpecifiedUnits, 0);
+  assert_equals(width.unitType, SVGLength.SVG_LENGTHTYPE_UNKNOWN);
+
+}, 'SVGLength DOM interface with invalid CSS variable');
+
+test(() => {
+  const rect = document.getElementById('rect1-px');
+  const width = rect.width.baseVal;
+
+  assert_equals(width.valueAsString, 'var(--length-100px)');
+
+  // Setting value should work (converts to user units)
+  width.value = 120;
+  assert_equals(width.value, 120);
+  assert_equals(width.unitType, SVGLength.SVG_LENGTHTYPE_NUMBER);
+  assert_equals(width.valueAsString, '120');
+
+}, 'SVGLength DOM interface methods with CSS variables');
+
+test(() => {
+  const rect = document.getElementById('rect2-px');
+  const width = rect.width.baseVal;
+
+  assert_equals(width.valueAsString, 'var(--length-100px)');
+
+  assert_throws_dom("NotSupportedError", function() { width.convertToSpecifiedUnits(SVGLength.SVG_LENGTHTYPE_CM); });
+
+  assert_equals(width.valueAsString, 'var(--length-100px)');
+  assert_equals(width.unitType, SVGLength.SVG_LENGTHTYPE_UNKNOWN);
+  assert_equals(width.value, 0);
+
+}, 'SVGLength.convertToSpecifiedUnits with CSS variables');
+
+test(() => {
+  const rect = document.getElementById('rect3-px');
+  const width = rect.width.baseVal;
+
+  width.newValueSpecifiedUnits(SVGLength.SVG_LENGTHTYPE_CM, 2.54);
+  assert_equals(width.value, 96);
+  assert_equals(width.unitType, SVGLength.SVG_LENGTHTYPE_CM);
+  assert_equals(width.valueAsString, '2.54cm');
+
+}, 'SVGLength.newValueSpecifiedUnits with CSS variables');
+
+test(() => {
+  const detachedSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
+  const detachedRect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
+  detachedSvg.appendChild(detachedRect);
+
+  detachedRect.setAttribute('width', 'var(--length-100px)');
+
+  const width = detachedRect.width.baseVal;
+
+  assert_equals(width.value, 0);
+  assert_equals(width.unitType, SVGLength.SVG_LENGTHTYPE_UNKNOWN);
+
+}, 'SVGLength should handle detached elements gracefully for CSS variables');
+
+test(() => {
+  const svg = document.getElementById('svg');
+  const new_length = svg.createSVGLength();
+
+  new_length.valueAsString = 'var(--length-100px)';
+
+  assert_equals(new_length.value, 0);
+  assert_equals(new_length.unitType, SVGLength.SVG_LENGTHTYPE_UNKNOWN);
+
+}, 'SVGLength should handle standalone lengths gracefully for CSS variables');
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/svg/styling/css-var-on-length-attributes-01.svg b/third_party/blink/web_tests/external/wpt/svg/styling/css-var-on-length-attributes-01.svg
new file mode 100644
index 0000000..d9156a1
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/svg/styling/css-var-on-length-attributes-01.svg
@@ -0,0 +1,11 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+  <title>Usage of css var() on svg width and height attributes (units specified)</title>
+  <html:link rel="author" title="Divyansh Mangal" href="mailto:dmangal@microsoft.com"/>
+  <html:link rel="match" href="../embedded/reference/green-rect-100x100.svg"/>
+  <style>
+    rect {
+      --length: 100px;
+    }
+  </style>
+  <rect width="var(--length)" height="var(--length)" fill="green"/>
+</svg>
diff --git a/third_party/blink/web_tests/external/wpt/svg/styling/css-var-on-length-attributes-02.svg b/third_party/blink/web_tests/external/wpt/svg/styling/css-var-on-length-attributes-02.svg
new file mode 100644
index 0000000..a2969d6c
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/svg/styling/css-var-on-length-attributes-02.svg
@@ -0,0 +1,11 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+  <title>Usage of css var() on svg width and height attributes (units not specified)</title>
+  <html:link rel="author" title="Divyansh Mangal" href="mailto:dmangal@microsoft.com"/>
+  <html:link rel="match" href="../embedded/reference/green-rect-100x100.svg"/>
+  <style>
+    rect {
+      --length: 100;
+    }
+  </style>
+  <rect width="var(--length)" height="var(--length)" fill="green"/>
+</svg>
diff --git a/third_party/blink/web_tests/external/wpt/svg/styling/css-var-on-length-attributes-03.svg b/third_party/blink/web_tests/external/wpt/svg/styling/css-var-on-length-attributes-03.svg
new file mode 100644
index 0000000..a4c7c197
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/svg/styling/css-var-on-length-attributes-03.svg
@@ -0,0 +1,6 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+  <title>Usage of css var() with default fallback on svg width and height attributes (units specified)</title>
+  <html:link rel="author" title="Divyansh Mangal" href="mailto:dmangal@microsoft.com"/>
+  <html:link rel="match" href="../embedded/reference/green-rect-100x100.svg"/>
+  <rect width="var(--length,100px)" height="var(--length,100px)" fill="green"/>
+</svg>
diff --git a/third_party/blink/web_tests/external/wpt/svg/styling/css-var-on-length-attributes-04.svg b/third_party/blink/web_tests/external/wpt/svg/styling/css-var-on-length-attributes-04.svg
new file mode 100644
index 0000000..a00ac5ee
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/svg/styling/css-var-on-length-attributes-04.svg
@@ -0,0 +1,6 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+  <title>Usage of css var() with default fallback on svg width and height attributes (units not specified)</title>
+  <html:link rel="author" title="Divyansh Mangal" href="mailto:dmangal@microsoft.com"/>
+  <html:link rel="match" href="../embedded/reference/green-rect-100x100.svg"/>
+  <rect width="var(--length,100)" height="var(--length,100)" fill="green"/>
+</svg>
diff --git a/third_party/blink/web_tests/external/wpt/svg/styling/css-var-on-length-attributes-05.svg b/third_party/blink/web_tests/external/wpt/svg/styling/css-var-on-length-attributes-05.svg
new file mode 100644
index 0000000..5f5bce9
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/svg/styling/css-var-on-length-attributes-05.svg
@@ -0,0 +1,6 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+  <title>Usage of css var() with when no substitution is provided</title>
+  <html:link rel="author" title="Divyansh Mangal" href="mailto:dmangal@microsoft.com"/>
+  <html:link rel="match" href="about:blank"/>
+  <rect width="var(--length)" height="var(--length)" fill="red"/>
+</svg>
diff --git a/third_party/blink/web_tests/external/wpt/svg/styling/css-var-on-length-attributes-06.html b/third_party/blink/web_tests/external/wpt/svg/styling/css-var-on-length-attributes-06.html
new file mode 100644
index 0000000..b5d397d
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/svg/styling/css-var-on-length-attributes-06.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<title>Usage of css var() on circle radius attribute (units specified)</title>
+<link rel="author" title="Divyansh Mangal" href="mailto:dmangal@microsoft.com">
+<link rel="match" href="css-linked-parameters/circle-green-ref.html">
+<style>
+  circle {
+    --radii: 48px
+  }
+</style>
+<svg width="100px" height="100px">
+  <circle cx="50px" cy="50px" r="var(--radii)" fill="green" stroke-width="2"/>
+</svg>
diff --git a/third_party/blink/web_tests/external/wpt/svg/text/scripted/getextentofchar.html b/third_party/blink/web_tests/external/wpt/svg/text/scripted/getextentofchar.html
index 1a6bb32..c3790526 100644
--- a/third_party/blink/web_tests/external/wpt/svg/text/scripted/getextentofchar.html
+++ b/third_party/blink/web_tests/external/wpt/svg/text/scripted/getextentofchar.html
@@ -5,8 +5,9 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 
-<svg width="800" height="600">
+<svg width="800" height="200">
 <text><tspan id="tspan1" x="50 150 100" y="100">abc</tspan></text>
+<text dominant-baseline="hanging"><tspan id="tspan2" x="50" y="150">abc</tspan></text>
 </svg>
 
 <script>
@@ -17,4 +18,11 @@
   assert_equals(element.getExtentOfChar(1).x, 150);
   assert_equals(element.getExtentOfChar(2).x, 100);
 }, 'Multiple chunks in a tspan');
+
+test(() => {
+  const element = document.querySelector('#tspan2');
+  assert_equals(element.getNumberOfChars(), 3);
+  assert_equals(element.getExtentOfChar(0).x, 50);
+  assert_approx_equals(element.getExtentOfChar(0).y, 147.5, 1);
+}, 'With dominant-baseline');
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/web-share/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/web-share/WEB_FEATURES.yml
new file mode 100644
index 0000000..be77dc62
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/web-share/WEB_FEATURES.yml
@@ -0,0 +1,3 @@
+features:
+- name: share
+  files: "**"
diff --git a/third_party/blink/web_tests/external/wpt/webaudio/the-audio-api/the-mediastreamaudiodestinationnode-interface/closed-audiocontext-construction.html b/third_party/blink/web_tests/external/wpt/webaudio/the-audio-api/the-mediastreamaudiodestinationnode-interface/closed-audiocontext-construction.html
new file mode 100644
index 0000000..23bc9dc8
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/webaudio/the-audio-api/the-mediastreamaudiodestinationnode-interface/closed-audiocontext-construction.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<title>MediaStreamAudioDestinationNode after closing AudioContext</title>
+<meta charset="utf-8">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+promise_test(async t => {
+  const context = new AudioContext();
+  await context.close();
+  assert_equals(context.state, "closed", "The AudioContext should report a closed state");
+
+  let audioNode;
+  try {
+    audioNode = new MediaStreamAudioDestinationNode(context);
+  } catch (err) {
+    assert_unreached(`Constructing MediaStreamAudioDestinationNode should not throw when the context is closed. Threw: ${err}`);
+  }
+
+  assert_true(audioNode instanceof MediaStreamAudioDestinationNode, "The created node should be a MediaStreamAudioDestinationNode");
+}, "Constructing MediaStreamAudioDestinationNode on a closed AudioContext succeeds");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/webauthn/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/webauthn/WEB_FEATURES.yml
index 8b273de..02e6412 100644
--- a/third_party/blink/web_tests/external/wpt/webauthn/WEB_FEATURES.yml
+++ b/third_party/blink/web_tests/external/wpt/webauthn/WEB_FEATURES.yml
@@ -1,4 +1,14 @@
 features:
+- name: webauthn
+  files:
+  - '*'
+  - '!createcredential-getpublickey.https.html'
+  - '!signal-*'
 - name: webauthn-public-key-easy
   files:
   - createcredential-getpublickey.https.html
+- name: webauthn-signals
+  files:
+  - signal-all-accepted-credentials.https.html
+  - signal-current-user-details.https.html
+  - signal-unknown-credential.https.html
diff --git a/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/script/add_preload_script/add_preload_script.py b/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/script/add_preload_script/add_preload_script.py
index 5e3e2d6..39f8ee1 100644
--- a/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/script/add_preload_script/add_preload_script.py
+++ b/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/script/add_preload_script/add_preload_script.py
@@ -28,6 +28,18 @@
     )
     assert result == {"type": "string", "value": "bar"}
 
+    await bidi_session.browsing_context.reload(
+        context=new_context["context"], wait="complete"
+    )
+
+    # Check that preload script was applied after reload
+    result = await bidi_session.script.evaluate(
+        expression="window.foo",
+        target=ContextTarget(new_context["context"]),
+        await_promise=True,
+    )
+    assert result == {"type": "string", "value": "bar"}
+
     url = inline("<div>foo</div>")
     await bidi_session.browsing_context.navigate(
         context=new_context["context"],
diff --git a/third_party/blink/web_tests/external/wpt/webdriver/tests/classic/find_element/find.py b/third_party/blink/web_tests/external/wpt/webdriver/tests/classic/find_element/find.py
index 50de9255..0ede5a96 100644
--- a/third_party/blink/web_tests/external/wpt/webdriver/tests/classic/find_element/find.py
+++ b/third_party/blink/web_tests/external/wpt/webdriver/tests/classic/find_element/find.py
@@ -119,3 +119,29 @@
     session.url = inline("")
     response = find_element(session, using, value)
     assert_success(response)
+
+
+def test_implicit_wait(session, inline):
+    session.url = inline("""
+        <script>
+            setTimeout(() => {
+                document.body.innerHTML = '<div id="delayed"></div>';
+            }, 300);
+        </script>
+    """)
+    session.timeouts.implicit = 1
+
+    response = find_element(session, "css selector", "#delayed")
+    value = assert_success(response)
+
+    expected = session.execute_script("return document.getElementById('delayed')")
+    assert_same_element(session, value, expected)
+
+
+def test_implicit_wait_timeout(session, inline):
+    session.url = inline("")
+    session.timeouts.implicit = 0.5
+
+    # Element never created
+    response = find_element(session, "css selector", "#nonexistent")
+    assert_error(response, "no such element")
diff --git a/third_party/blink/web_tests/external/wpt/webdriver/tests/classic/find_element_from_element/find.py b/third_party/blink/web_tests/external/wpt/webdriver/tests/classic/find_element_from_element/find.py
index e0569f4..78d8d41 100644
--- a/third_party/blink/web_tests/external/wpt/webdriver/tests/classic/find_element_from_element/find.py
+++ b/third_party/blink/web_tests/external/wpt/webdriver/tests/classic/find_element_from_element/find.py
@@ -177,3 +177,29 @@
 
     response = find_element(session, from_element.id, "xpath", "..")
     assert_error(response, "invalid selector")
+
+def test_implicit_wait(session, inline):
+    session.url = inline("""
+        <div id="parent"></div>
+        <script>
+            setTimeout(() => {
+                document.getElementById('parent').innerHTML = '<div id="delayed"></div>';
+            }, 300);
+        </script>
+    """)
+    session.timeouts.implicit = 1
+
+    from_element = session.find.css("#parent", all=False)
+    response = find_element(session, from_element.id, "css selector", "#delayed")
+    value = assert_success(response)
+
+    expected = session.execute_script("return document.getElementById('delayed')")
+    assert_same_element(session, value, expected)
+
+def test_implicit_wait_timeout(session, inline):
+    session.url = inline("<div id='parent'></div>")
+    session.timeouts.implicit = 0.5
+
+    from_element = session.find.css("#parent", all=False)
+    response = find_element(session, from_element.id, "css selector", "#nonexistent")
+    assert_error(response, "no such element")
diff --git a/third_party/blink/web_tests/external/wpt/webdriver/tests/classic/find_element_from_shadow_root/find.py b/third_party/blink/web_tests/external/wpt/webdriver/tests/classic/find_element_from_shadow_root/find.py
index cc0d912..c0adce667 100644
--- a/third_party/blink/web_tests/external/wpt/webdriver/tests/classic/find_element_from_shadow_root/find.py
+++ b/third_party/blink/web_tests/external/wpt/webdriver/tests/classic/find_element_from_shadow_root/find.py
@@ -245,3 +245,34 @@
 
     element = WebElement.from_json(value, session)
     assert element.text == expected_text
+
+def test_implicit_wait_shadow_root(session, get_test_page):
+    session.url = get_test_page()
+    session.timeouts.implicit = 1
+
+    session.execute_script("""
+        setTimeout(() => {
+            const host = document.querySelector('custom-element');
+            const input = document.createElement('input');
+            input.id = 'delayed';
+            host.shadowRoot.appendChild(input);
+        }, 300);
+    """)
+
+    shadow_root = session.find.css("custom-element", all=False).shadow_root
+    response = find_element(session, shadow_root.id, "css selector", "#delayed")
+    value = assert_success(response)
+
+    expected = session.execute_script("""
+        return arguments[0].shadowRoot.getElementById('delayed')
+    """, args=(session.find.css("custom-element", all=False),))
+    assert_same_element(session, value, expected)
+
+
+def test_implicit_wait_timeout(session, get_test_page):
+    session.url = get_test_page()
+    session.timeouts.implicit = 0.5
+
+    shadow_root = session.find.css("custom-element", all=False).shadow_root
+    response = find_element(session, shadow_root.id, "css selector", "#nonexistent")
+    assert_error(response, "no such element")
diff --git a/third_party/blink/web_tests/external/wpt/webgl/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/webgl/WEB_FEATURES.yml
new file mode 100644
index 0000000..1e167ea3
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/webgl/WEB_FEATURES.yml
@@ -0,0 +1,3 @@
+features:
+- name: webgl
+  files: '**'
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/webmessaging/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/webmessaging/WEB_FEATURES.yml
index 0aaceca..e3ff811 100644
--- a/third_party/blink/web_tests/external/wpt/webmessaging/WEB_FEATURES.yml
+++ b/third_party/blink/web_tests/external/wpt/webmessaging/WEB_FEATURES.yml
@@ -3,3 +3,16 @@
   files:
   - messageerror.html
   - postMessage_CryptoKey_insecure.sub.html
+- name: channel-messaging
+  files:
+  - Channel_*
+  - MessagePort_*
+  - event.ports.sub.htm
+- name: postmessage
+  files:
+  - MessageEvent*
+  - Transferred_objects_unusable.sub.htm
+  - event.*
+  - postMessage_*
+  - "!postMessage_CryptoKey_insecure.sub.html"
+  - worker_postMessage_*
diff --git a/third_party/blink/web_tests/external/wpt/webmessaging/message-channels/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/webmessaging/message-channels/WEB_FEATURES.yml
new file mode 100644
index 0000000..32648dc
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/webmessaging/message-channels/WEB_FEATURES.yml
@@ -0,0 +1,13 @@
+features:
+- name: channel-messaging
+  files:
+  - basics.any.js
+  - close.any.js
+  - cross-document.html
+  - detached-iframe.window.js
+  - dictionary-transferrable.any.js
+  - implied-start.any.js
+  - no-start.any.js
+  - user-activation.tentative.any.js
+  - worker-post-after-close.any.js
+  - worker.any.js
diff --git a/third_party/blink/web_tests/external/wpt/webmessaging/message-channels/close-event/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/webmessaging/message-channels/close-event/WEB_FEATURES.yml
new file mode 100644
index 0000000..daebc7c8
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/webmessaging/message-channels/close-event/WEB_FEATURES.yml
@@ -0,0 +1,3 @@
+features:
+- name: channel-messaging
+  files: "**"
diff --git a/third_party/blink/web_tests/external/wpt/webmessaging/multi-globals/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/webmessaging/multi-globals/WEB_FEATURES.yml
new file mode 100644
index 0000000..68347b1e
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/webmessaging/multi-globals/WEB_FEATURES.yml
@@ -0,0 +1,7 @@
+features:
+- name: broadcast-channel
+  files:
+  - broadcastchannel-*
+- name: channel-messaging
+  files:
+  - messageport-*
diff --git a/third_party/blink/web_tests/external/wpt/webmessaging/with-options/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/webmessaging/with-options/WEB_FEATURES.yml
new file mode 100644
index 0000000..2a3e341f
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/webmessaging/with-options/WEB_FEATURES.yml
@@ -0,0 +1,8 @@
+features:
+- name: channel-messaging
+  files:
+  - message-channel-transferable.html
+- name: postmessage
+  files:
+  - '*'
+  - '!message-channel-transferable.html'
diff --git a/third_party/blink/web_tests/external/wpt/webmessaging/with-ports/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/webmessaging/with-ports/WEB_FEATURES.yml
new file mode 100644
index 0000000..bea6249
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/webmessaging/with-ports/WEB_FEATURES.yml
@@ -0,0 +1,8 @@
+features:
+- name: channel-messaging
+  files:
+  - 027.html
+- name: postmessage
+  files:
+  - '*'
+  - '!027.html'
diff --git a/third_party/blink/web_tests/external/wpt/webmessaging/without-ports/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/webmessaging/without-ports/WEB_FEATURES.yml
new file mode 100644
index 0000000..187ee026
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/webmessaging/without-ports/WEB_FEATURES.yml
@@ -0,0 +1,12 @@
+features:
+- name: channel-messaging
+  files:
+  - 023.html
+  - 024.html
+  - 025.html
+- name: postmessage
+  files:
+  - "*"
+  - "!023.html"
+  - "!024.html"
+  - "!025.html"
diff --git a/third_party/blink/web_tests/external/wpt/webnn/conformance_tests/cos.https.any.js b/third_party/blink/web_tests/external/wpt/webnn/conformance_tests/cos.https.any.js
index 57e7d99..8f65703 100644
--- a/third_party/blink/web_tests/external/wpt/webnn/conformance_tests/cos.https.any.js
+++ b/third_party/blink/web_tests/external/wpt/webnn/conformance_tests/cos.https.any.js
@@ -15,7 +15,9 @@
 
 
 const getCosPrecisionTolerance = (graphResources) => {
-  const toleranceValueDict = {float32: 1 / 1024, float16: 1 / 512};
+  // Use the float accuracy for WGSL for float16:
+  // https://gpuweb.github.io/gpuweb/wgsl/#concrete-float-accuracy
+  const toleranceValueDict = {float32: 2 ** -10, float16: 2 ** -7};
   const expectedDataType =
       getExpectedDataTypeOfSingleOutput(graphResources.expectedOutputs);
   return {metricType: 'ATOL', value: toleranceValueDict[expectedDataType]};
diff --git a/third_party/blink/web_tests/external/wpt/webnn/conformance_tests/layer_normalization.https.any.js b/third_party/blink/web_tests/external/wpt/webnn/conformance_tests/layer_normalization.https.any.js
index 7ac8139..3588afd 100644
--- a/third_party/blink/web_tests/external/wpt/webnn/conformance_tests/layer_normalization.https.any.js
+++ b/third_party/blink/web_tests/external/wpt/webnn/conformance_tests/layer_normalization.https.any.js
@@ -22,8 +22,11 @@
 //     MLOperand input, optional MLLayerNormalizationOptions options = {});
 
 
-const getLayerNormPrecisionTolerance = () => {
-  return {metricType: 'ULP', value: 14};
+const getLayerNormPrecisionTolerance = (graphResources) => {
+  const toleranceValueDict = {float32: 14, float16: 30};
+  const expectedDataType =
+      getExpectedDataTypeOfSingleOutput(graphResources.expectedOutputs);
+  return {metricType: 'ULP', value: toleranceValueDict[expectedDataType]};
 };
 
 const layerNormTests = [
diff --git a/third_party/blink/web_tests/external/wpt/webnn/conformance_tests/sin.https.any.js b/third_party/blink/web_tests/external/wpt/webnn/conformance_tests/sin.https.any.js
index e363d745..28ba7c9 100644
--- a/third_party/blink/web_tests/external/wpt/webnn/conformance_tests/sin.https.any.js
+++ b/third_party/blink/web_tests/external/wpt/webnn/conformance_tests/sin.https.any.js
@@ -15,7 +15,9 @@
 
 
 const getSinPrecisionTolerance = (graphResources) => {
-  const toleranceValueDict = {float32: 1 / 1024, float16: 1 / 512};
+  // Use the float accuracy for WGSL for float16:
+  // https://gpuweb.github.io/gpuweb/wgsl/#concrete-float-accuracy
+  const toleranceValueDict = {float32: 2 ** -10, float16: 2 ** -7};
   const expectedDataType =
       getExpectedDataTypeOfSingleOutput(graphResources.expectedOutputs);
   return {metricType: 'ATOL', value: toleranceValueDict[expectedDataType]};
diff --git a/third_party/blink/web_tests/external/wpt/webrtc/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/webrtc/WEB_FEATURES.yml
index 117b04f..41831b3 100644
--- a/third_party/blink/web_tests/external/wpt/webrtc/WEB_FEATURES.yml
+++ b/third_party/blink/web_tests/external/wpt/webrtc/WEB_FEATURES.yml
@@ -1,4 +1,8 @@
 features:
+- name: webrtc
+  files:
+  - '*'
+  - '!RTCSctpTransport-*'
 - name: webrtc-sctp
   files:
   - RTCSctpTransport-*
diff --git a/third_party/blink/web_tests/external/wpt/webrtc/legacy/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/webrtc/legacy/WEB_FEATURES.yml
new file mode 100644
index 0000000..393ef85
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/webrtc/legacy/WEB_FEATURES.yml
@@ -0,0 +1,3 @@
+features:
+- name: webrtc
+  files: '**'
diff --git a/third_party/blink/web_tests/external/wpt/webrtc/protocol/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/webrtc/protocol/WEB_FEATURES.yml
new file mode 100644
index 0000000..393ef85
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/webrtc/protocol/WEB_FEATURES.yml
@@ -0,0 +1,3 @@
+features:
+- name: webrtc
+  files: '**'
diff --git a/third_party/blink/web_tests/external/wpt/webrtc/protocol/h264-profile-levels.https.html b/third_party/blink/web_tests/external/wpt/webrtc/protocol/h264-profile-levels.https.html
index cb0b581..2ba92ff 100644
--- a/third_party/blink/web_tests/external/wpt/webrtc/protocol/h264-profile-levels.https.html
+++ b/third_party/blink/web_tests/external/wpt/webrtc/protocol/h264-profile-levels.https.html
@@ -10,7 +10,7 @@
 <script>
 
 function mungeLevel(sdp, level) {
-  level_hex = Math.round(level * 10).toString(16);
+  level_hex = Math.round(level * 10).toString(16).padStart(2, '0');
   return {
     type: sdp.type,
     sdp: sdp.sdp.replace(/(profile-level-id=....)(..)/g,
diff --git a/third_party/blink/web_tests/external/wpt/webrtc/simulcast/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/webrtc/simulcast/WEB_FEATURES.yml
new file mode 100644
index 0000000..393ef85
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/webrtc/simulcast/WEB_FEATURES.yml
@@ -0,0 +1,3 @@
+features:
+- name: webrtc
+  files: '**'
diff --git a/third_party/blink/web_tests/external/wpt/webvtt/api/VTTCue/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/webvtt/api/VTTCue/WEB_FEATURES.yml
index bb2aa7cdd..e26a769 100644
--- a/third_party/blink/web_tests/external/wpt/webvtt/api/VTTCue/WEB_FEATURES.yml
+++ b/third_party/blink/web_tests/external/wpt/webvtt/api/VTTCue/WEB_FEATURES.yml
@@ -1,14 +1,16 @@
 features:
 - name: webvtt
   files:
-  - align.html
   - constructor-exceptions.html
   - getCueAsHTML.html
+  - text.html
+- name: webvtt-cue-settings
+  files:
+  - align.html
   - line.html
   - position.html
   - size.html
   - snapToLines.html
-  - text.html
   - vertical.html
 - name: webvtt-cue-alignment
   files:
diff --git a/third_party/blink/web_tests/external/wpt/workers/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/workers/WEB_FEATURES.yml
index af1d15e..d2f14cf 100644
--- a/third_party/blink/web_tests/external/wpt/workers/WEB_FEATURES.yml
+++ b/third_party/blink/web_tests/external/wpt/workers/WEB_FEATURES.yml
@@ -12,3 +12,7 @@
   files:
   - WorkerNavigator_userAgentData.http.html
   - WorkerNavigator_userAgentData.https.html
+- name: postmessage
+  files:
+  - Worker-postMessage-happens-in-parallel.https.html
+  - postMessage_*
diff --git a/third_party/blink/web_tests/external/wpt/workers/interfaces/DedicatedWorkerGlobalScope/postMessage/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/workers/interfaces/DedicatedWorkerGlobalScope/postMessage/WEB_FEATURES.yml
new file mode 100644
index 0000000..337a1fb
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/workers/interfaces/DedicatedWorkerGlobalScope/postMessage/WEB_FEATURES.yml
@@ -0,0 +1,3 @@
+features:
+- name: postmessage
+  files: "**"
diff --git a/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-window-after-frame-navigated-expected.txt b/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-window-after-frame-navigated-expected.txt
index 9350934..99f20fb 100644
--- a/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-window-after-frame-navigated-expected.txt
+++ b/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-window-after-frame-navigated-expected.txt
@@ -164,6 +164,7 @@
 PASS oldChildWindow.onformdata is newChildWindow.onformdata
 PASS oldChildWindow.ongamepadconnected is newChildWindow.ongamepadconnected
 PASS oldChildWindow.ongamepaddisconnected is newChildWindow.ongamepaddisconnected
+PASS oldChildWindow.ongamepadrawinputchanged is newChildWindow.ongamepadrawinputchanged
 PASS oldChildWindow.ongotpointercapture is newChildWindow.ongotpointercapture
 PASS oldChildWindow.onhashchange is newChildWindow.onhashchange
 PASS oldChildWindow.oninput is newChildWindow.oninput
diff --git a/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-window-after-frame-removed-and-gced-expected.txt b/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-window-after-frame-removed-and-gced-expected.txt
index 464f4cc..6253908 100644
--- a/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-window-after-frame-removed-and-gced-expected.txt
+++ b/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-window-after-frame-removed-and-gced-expected.txt
@@ -103,6 +103,7 @@
 PASS childWindow.onformdata is null
 PASS childWindow.ongamepadconnected is null
 PASS childWindow.ongamepaddisconnected is null
+PASS childWindow.ongamepadrawinputchanged is null
 PASS childWindow.ongotpointercapture is null
 PASS childWindow.onhashchange is null
 PASS childWindow.oninput is null
diff --git a/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-window-after-frame-removed-expected.txt b/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-window-after-frame-removed-expected.txt
index 71d6fa06..8313909f 100644
--- a/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-window-after-frame-removed-expected.txt
+++ b/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-window-after-frame-removed-expected.txt
@@ -103,6 +103,7 @@
 PASS childWindow.onformdata is null
 PASS childWindow.ongamepadconnected is null
 PASS childWindow.ongamepaddisconnected is null
+PASS childWindow.ongamepadrawinputchanged is null
 PASS childWindow.ongotpointercapture is null
 PASS childWindow.onhashchange is null
 PASS childWindow.oninput is null
diff --git a/third_party/blink/web_tests/fullscreen/full-screen-iframe-allowed-video.html b/third_party/blink/web_tests/fullscreen/full-screen-iframe-allowed-video.html
index df8e8fe2..b8d5455 100644
--- a/third_party/blink/web_tests/fullscreen/full-screen-iframe-allowed-video.html
+++ b/third_party/blink/web_tests/fullscreen/full-screen-iframe-allowed-video.html
@@ -1,8 +1,4 @@
 <!DOCTYPE html>
-<script>
-    if (window.internals)
-        runPixelTests = internals.runtimeFlags.forceOverlayFullscreenVideoEnabled;
-</script>
 <script src="full-screen-test.js"></script>
 <script>
     var initialized = false;
diff --git a/third_party/blink/web_tests/gamepad/gamepad-raw-input-change-event.html b/third_party/blink/web_tests/gamepad/gamepad-raw-input-change-event.html
new file mode 100644
index 0000000..8a793755
--- /dev/null
+++ b/third_party/blink/web_tests/gamepad/gamepad-raw-input-change-event.html
@@ -0,0 +1,153 @@
+<!DOCTYPE html>
+<body>
+<script src="../resources/testharness.js"></script>
+<script src="../resources/testharnessreport.js"></script>
+<script src="resources/gamepad-helpers.js"></script>
+<script>
+
+promise_test(async (t) => {
+    disconnectGamepads();
+    testGamepadStateAllDisconnected();
+
+    // Connect a gamepad.
+    let connectPromise = onGamepadEventWithIndex('gamepadconnected', 0);
+
+    // Sets up mock gamepad with ID, buttons, axes.
+    connectGamepads(1);
+    await connectPromise;
+
+    // Listen for raw input events.
+    let eventFired = false;
+    let receivedEvent = null;
+
+    // `listener` is called whenever a `gamepadrawinputchanged` event is dispatched to window.
+    let listener = (e) => {
+        eventFired = true;
+        receivedEvent = e;
+    };
+    window.addEventListener('gamepadrawinputchanged', listener);
+
+    // Trigger the event from the test controller.
+    let rawInputChangedPromise = ongamepadrawinputchanged();
+    setButtonInput(0, 0, 0);
+    dispatchRawInputChanged(0);
+    await rawInputChangedPromise;
+    assert_true(eventFired, "Expected gamepadrawinputchanged event to fire");
+    assert_equals(receivedEvent.type, "gamepadrawinputchanged");
+    window.removeEventListener('gamepadrawinputchanged', listener);
+}, "Raw input change event fires when dispatchRawInputChanged is called.");
+
+promise_test(async (t) => {
+    disconnectGamepads();
+    testGamepadStateAllDisconnected();
+
+    // Connect one gamepad.
+    let connectPromise = onGamepadEventWithIndex('gamepadconnected', 0);
+    connectGamepads(1);
+    await connectPromise;
+
+    // Disconnect before dispatching raw input change.
+    gamepadController.disconnect(0);
+    let eventFired = false;
+    let listener = (e) => { eventFired = true; };
+    window.addEventListener('gamepadrawinputchanged', listener);
+
+    // Attempting to dispatch raw input on disconnected gamepad would crash.
+    // So we skip calling dispatchRawInputChanged entirely, just verify no events fire.
+    await t.step_timeout(() => {}, 10);
+    window.removeEventListener('gamepadrawinputchanged', listener);
+    assert_false(eventFired, "No event should fire after disconnect");
+}, "Disconnected gamepad does not fire event");
+
+promise_test(async (t) => {
+    disconnectGamepads();
+    testGamepadStateAllDisconnected();
+    let eventFired = false;
+    let listener = (e) => { eventFired = true; };
+    window.addEventListener('gamepadrawinputchanged', listener);
+
+    // Attempt to dispatch raw input change on invalid index.
+    setButtonInput(0, 0, 0);
+    dispatchRawInputChanged(99);
+    await t.step_timeout(() => {}, 50);
+    window.removeEventListener('gamepadrawinputchanged', listener);
+    assert_false(eventFired, "No event should fire for invalid index");
+}, "Invalid index does not fire event");
+
+promise_test(async (t) => {
+    disconnectGamepads();
+    testGamepadStateAllDisconnected();
+
+    let connectPromise = onGamepadEventWithIndex('gamepadconnected', 0);
+    connectGamepads(1);
+    await connectPromise;
+    const eventsReceived = [];
+    function listener(e) {
+        eventsReceived.push(e);
+    }
+    window.addEventListener('gamepadrawinputchanged', listener);
+
+    // First button change.
+    let rawInputPromise1 = ongamepadrawinputchanged();
+    setButtonInput(0, 0, 0);
+    dispatchRawInputChanged(0);
+    await rawInputPromise1;
+
+    // Second button change.
+    let rawInputPromise2 = ongamepadrawinputchanged();
+    setButtonInput(0, 0, 1);
+    dispatchRawInputChanged(0);
+    await rawInputPromise2;
+    window.removeEventListener('gamepadrawinputchanged', listener);
+    assert_equals(eventsReceived.length, 2, "Two raw input events should have fired");
+
+    // First event assertions.
+    assert_array_equals(eventsReceived[0].buttonsValueChanged, [0]);
+    assert_array_equals(eventsReceived[0].buttonsReleased, [0]);
+    assert_array_equals(eventsReceived[0].buttonsPressed, []);
+
+    // Second event assertions.
+    assert_array_equals(eventsReceived[1].buttonsValueChanged, [0]);
+    assert_array_equals(eventsReceived[1].buttonsPressed, [0]);
+    assert_array_equals(eventsReceived[1].buttonsReleased, []);
+}, "Two raw input changed events fire sequentially for a single connected gamepad (buttons only)");
+
+promise_test(async (t) => {
+    disconnectGamepads();
+    testGamepadStateAllDisconnected();
+
+    // Connect a gamepad first.
+    let connectPromise = onGamepadEventWithIndex('gamepadconnected', 0);
+
+    // sets up a mock gamepad with ID, buttons, axes.
+    connectGamepads(1);
+    await connectPromise;
+
+    // Listen for new raw input event.
+    let eventFired = false;
+    let receivedEvent = null;
+    let inputChangedListener = (e) => {
+        eventFired = true;
+        receivedEvent = e;
+    };
+    window.addEventListener('gamepadrawinputchanged', inputChangedListener);
+
+    // Trigger the event by changing an axis.
+    let rawInputChangedPromise = ongamepadrawinputchanged();
+    // Change axis 0 value to trigger event.
+    setAxisInput(0, 0, 1.0);
+    dispatchRawInputChanged(0);
+    await rawInputChangedPromise;
+    assert_true(eventFired, "Expected gamepadrawinputchanged event to fire for axis change");
+    assert_equals(receivedEvent.type, "gamepadrawinputchanged");
+
+    // Verify changed axes.
+    assert_array_equals(receivedEvent.axesChanged, [0], "Axis 0 should be reported as changed");
+    assert_equals(receivedEvent.gamepad.axes[0], 1.0, "Axis 0 value should be 1.0");
+
+    window.removeEventListener('gamepadrawinputchanged', inputChangedListener);
+}, "Raw input change event fires when axis value changes");
+
+</script>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/gamepad/resources/gamepad-helpers.js b/third_party/blink/web_tests/gamepad/resources/gamepad-helpers.js
index 4a2daf11..5bc79e94 100644
--- a/third_party/blink/web_tests/gamepad/resources/gamepad-helpers.js
+++ b/third_party/blink/web_tests/gamepad/resources/gamepad-helpers.js
@@ -22,14 +22,28 @@
         gamepadController.setId(i, "MockStick 3000");
         gamepadController.setButtonCount(i, 2);
         gamepadController.setAxisCount(i, 2);
+        gamepadController.setTouchCount(i, 0);
         gamepadController.setButtonData(i, 0, 1);
         gamepadController.setButtonData(i, 1, 0);
         gamepadController.setAxisData(i, 0, .5);
         gamepadController.setAxisData(i, 1, -1.0);
+        gamepadController.setTouchData(i, 0, 0, 0, 0);
         gamepadController.dispatchConnected(i);
     }
 }
 
+function dispatchRawInputChanged(index) {
+  gamepadController.dispatchRawInputChanged(index);
+}
+
+function setButtonInput(index, buttonIndex, value) {
+  gamepadController.setButtonData(index, buttonIndex, value);
+}
+
+function setAxisInput(index, axisIndex, value) {
+  gamepadController.setAxisData(index, axisIndex, value);
+}
+
 function testGamepadStateAllDisconnected() {
     // To pass this test, the getGamepads array should have only null elements.
     let pads = navigator.getGamepads();
@@ -84,3 +98,7 @@
 async function ongamepaddisconnected() {
   return onGamepadEvent('gamepaddisconnected');
 }
+
+async function ongamepadrawinputchanged() {
+  return onGamepadEvent('gamepadrawinputchanged');
+}
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/fetch/fetch-modify-existing-cookie-expected.txt b/third_party/blink/web_tests/http/tests/inspector-protocol/fetch/fetch-modify-existing-cookie-expected.txt
new file mode 100644
index 0000000..c460c337
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/fetch/fetch-modify-existing-cookie-expected.txt
@@ -0,0 +1,54 @@
+Tests that Fetch.continueRequest can override a cookie on main resource navigations and fetch requests, including redirects.
+
+--- Setting baseline cookie for localhost ---
+Intercepting navigation to set cookie for localhost...
+Cookie for localhost should now be set.
+
+--- Setting baseline cookie for 127.0.0.1 ---
+Intercepting navigation to set cookie for 127.0.0.1...
+Cookie for 127.0.0.1 should now be set.
+
+--- Running fetch() Test: Direct Fetch ---
+Intercepting final fetch to: http://127.0.0.1:8000/inspector-protocol/fetch/resources/cors-echo-headers.php?headers=HTTP_COOKIE
+Attempting to override Cookie header to: "modified_cookie_for_direct_fetch=from_devtools"
+fetch() for Direct Fetch complete.
+[Verification] Server response for Direct Fetch: HTTP_COOKIE: modified_cookie_for_direct_fetch=from_devtools
+
+--- Running fetch() Test: Same-Origin Redirect Fetch ---
+Intercepting initial fetch to redirect script: http://127.0.0.1:8000/inspector-protocol/fetch/resources/redirect.pl?http://127.0.0.1:8000/inspector-protocol/fetch/resources/cors-echo-headers.php?headers=HTTP_COOKIE
+Continuing initial fetch, awaiting redirect...
+Intercepting final fetch to: http://127.0.0.1:8000/inspector-protocol/fetch/resources/cors-echo-headers.php?headers=HTTP_COOKIE
+Attempting to override Cookie header to: "modified_cookie_for_redirect_fetch=from_devtools"
+fetch() for Same-Origin Redirect Fetch complete.
+[Verification] Server response for Same-Origin Redirect Fetch: HTTP_COOKIE: modified_cookie_for_redirect_fetch=from_devtools
+
+--- Running fetch() Test: Cross-Origin Redirect Fetch ---
+Intercepting initial fetch to redirect script: http://127.0.0.1:8000/inspector-protocol/fetch/resources/redirect.pl?http://localhost:8000/inspector-protocol/fetch/resources/cors-echo-headers.php?headers=HTTP_COOKIE
+Continuing initial fetch, awaiting redirect...
+Intercepting final fetch to: http://localhost:8000/inspector-protocol/fetch/resources/cors-echo-headers.php?headers=HTTP_COOKIE
+Attempting to override Cookie header to: "modified_cookie_for_cors_fetch=from_devtools"
+fetch() for Cross-Origin Redirect Fetch complete.
+[Verification] Server response for Cross-Origin Redirect Fetch: HTTP_COOKIE: modified_cookie_for_cors_fetch=from_devtools
+
+--- Running Navigation Test: Direct Navigation ---
+Intercepting final navigation to: http://127.0.0.1:8000/inspector-protocol/fetch/resources/cors-echo-headers.php?headers=HTTP_COOKIE
+Attempting to override Cookie header to: "modified_cookie_for_direct_nav=from_devtools"
+Navigation for Direct Navigation complete.
+[Verification] Server response for Direct Navigation: HTTP_COOKIE: modified_cookie_for_direct_nav=from_devtools
+
+--- Running Navigation Test: Same-Origin Redirect Navigation ---
+Intercepting initial navigation to redirect script: http://127.0.0.1:8000/inspector-protocol/fetch/resources/redirect.pl?http://127.0.0.1:8000/inspector-protocol/fetch/resources/cors-echo-headers.php?headers=HTTP_COOKIE
+Continuing initial navigation, awaiting redirect...
+Intercepting final navigation to: http://127.0.0.1:8000/inspector-protocol/fetch/resources/cors-echo-headers.php?headers=HTTP_COOKIE
+Attempting to override Cookie header to: "modified_cookie_for_redirect_nav=from_devtools"
+Navigation for Same-Origin Redirect Navigation complete.
+[Verification] Server response for Same-Origin Redirect Navigation: HTTP_COOKIE: modified_cookie_for_redirect_nav=from_devtools
+
+--- Running Navigation Test: Cross-Origin Redirect Navigation ---
+Intercepting initial navigation to redirect script: http://127.0.0.1:8000/inspector-protocol/fetch/resources/redirect.pl?http://localhost:8000/inspector-protocol/fetch/resources/cors-echo-headers.php?headers=HTTP_COOKIE
+Continuing initial navigation, awaiting redirect...
+Intercepting final navigation to: http://localhost:8000/inspector-protocol/fetch/resources/cors-echo-headers.php?headers=HTTP_COOKIE
+Attempting to override Cookie header to: "modified_cookie_for_cors_nav=from_devtools"
+Navigation for Cross-Origin Redirect Navigation complete.
+[Verification] Server response for Cross-Origin Redirect Navigation: HTTP_COOKIE: modified_cookie_for_cors_nav=from_devtools
+
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/fetch/fetch-modify-existing-cookie.js b/third_party/blink/web_tests/http/tests/inspector-protocol/fetch/fetch-modify-existing-cookie.js
new file mode 100644
index 0000000..6e9e3c1
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/fetch/fetch-modify-existing-cookie.js
@@ -0,0 +1,163 @@
+(async function (testRunner) {
+    const { session, dp } = await testRunner.startBlank(
+        'Tests that Fetch.continueRequest can override a cookie on main resource navigations and fetch requests, including redirects.');
+
+    const sameOriginCookieToSet = 'original_cookie=from_server; Path=/';
+    const sameOriginSetCookieUrl = `http://127.0.0.1:8000/inspector-protocol/network/resources/set-cookie.php?cookie=${encodeURIComponent(sameOriginCookieToSet)}`;
+    const sameOriginEchoUrl = 'http://127.0.0.1:8000/inspector-protocol/fetch/resources/cors-echo-headers.php?headers=HTTP_COOKIE';
+    const sameOriginRedirectUrl = `http://127.0.0.1:8000/inspector-protocol/fetch/resources/redirect.pl?${sameOriginEchoUrl}`;
+
+    const crossOriginCookieToSet = 'original_cross_origin_cookie=from_server; Path=/';
+    const crossOriginSetCookieUrl = `http://localhost:8000/inspector-protocol/network/resources/set-cookie.php?cookie=${encodeURIComponent(crossOriginCookieToSet)}`;
+    const crossOriginEchoUrl = `http://localhost:8000/inspector-protocol/fetch/resources/cors-echo-headers.php?headers=HTTP_COOKIE`;
+    const crossOriginRedirectUrl = `http://127.0.0.1:8000/inspector-protocol/fetch/resources/redirect.pl?${crossOriginEchoUrl}`;
+
+    await dp.Fetch.enable();
+    await dp.Network.enable();
+
+    async function setCookieForDomain(url) {
+        const domain = new URL(url).hostname;
+
+        const listener = event => {
+            const { requestId, resourceType, request } = event.params;
+            if (resourceType === 'Document' && request.url === url) {
+                testRunner.log(`Intercepting navigation to set cookie for ${domain}...`);
+            }
+            dp.Fetch.continueRequest({ requestId });
+        };
+
+        testRunner.log(`\n--- Setting baseline cookie for ${domain} ---`);
+        dp.Fetch.onRequestPaused(listener);
+        dp.Page.navigate({ url });
+        await dp.Network.onceLoadingFinished();
+        dp.Fetch.offRequestPaused(listener);
+        testRunner.log(`Cookie for ${domain} should now be set.`);
+    }
+
+    async function testNavigationCookieOverride({
+        testName,
+        urlToNavigate,
+        urlToIntercept,
+        isRedirect = false,
+        cookieToSet
+    }) {
+        testRunner.log(`\n--- Running Navigation Test: ${testName} ---`);
+
+        const listener = event => {
+            const { requestId, resourceType, request } = event.params;
+            if (resourceType === 'Document') {
+                if (request.url === urlToNavigate && isRedirect) {
+                    testRunner.log(`Intercepting initial navigation to redirect script: ${urlToNavigate}`);
+                    dp.Fetch.continueRequest({ requestId });
+                    testRunner.log('Continuing initial navigation, awaiting redirect...');
+                    return;
+                } else if (request.url === urlToIntercept) {
+                    testRunner.log(`Intercepting final navigation to: ${urlToIntercept}`);
+                    testRunner.log(`Attempting to override Cookie header to: "${cookieToSet}"`);
+                    dp.Fetch.continueRequest({
+                        requestId,
+                        headers: [{ name: 'Cookie', value: cookieToSet }]
+                    });
+                    return;
+                }
+            }
+            dp.Fetch.continueRequest({ requestId });
+        };
+
+        dp.Fetch.onRequestPaused(listener);
+        dp.Page.navigate({ url: urlToNavigate });
+        await dp.Network.onceLoadingFinished();
+        dp.Fetch.offRequestPaused(listener);
+
+        testRunner.log(`Navigation for ${testName} complete.`);
+        const result = await session.evaluate('document.body.innerText');
+        testRunner.log(`[Verification] Server response for ${testName}: ${result.trim()}`);
+    }
+
+    async function testFetchCookieOverride({
+        testName,
+        urlToFetch,
+        urlToIntercept,
+        isRedirect = false,
+        cookieToSet
+    }) {
+        testRunner.log(`\n--- Running fetch() Test: ${testName} ---`);
+
+        const listener = event => {
+            const { requestId, request } = event.params;
+            if (request.url === urlToFetch && isRedirect) {
+                testRunner.log(`Intercepting initial fetch to redirect script: ${urlToFetch}`);
+                dp.Fetch.continueRequest({ requestId: requestId });
+                testRunner.log('Continuing initial fetch, awaiting redirect...');
+                return;
+            } else if (request.url === urlToIntercept) {
+                testRunner.log(`Intercepting final fetch to: ${urlToIntercept}`);
+                testRunner.log(`Attempting to override Cookie header to: "${cookieToSet}"`);
+                dp.Fetch.continueRequest({
+                    requestId: requestId,
+                    headers: [{ name: 'Cookie', value: cookieToSet }]
+                });
+                return;
+            }
+            dp.Fetch.continueRequest({ requestId });
+        };
+
+        dp.Fetch.onRequestPaused(listener);
+        const fetchResult = await session.evaluateAsync(`fetch('${urlToFetch}').then(res => res.text())`);
+        dp.Fetch.offRequestPaused(listener);
+
+        testRunner.log(`fetch() for ${testName} complete.`);
+        testRunner.log(`[Verification] Server response for ${testName}: ${fetchResult.trim()}`);
+    }
+
+    await setCookieForDomain(crossOriginSetCookieUrl);
+    await setCookieForDomain(sameOriginSetCookieUrl);
+
+    await testFetchCookieOverride({
+        testName: 'Direct Fetch',
+        urlToFetch: sameOriginEchoUrl,
+        urlToIntercept: sameOriginEchoUrl,
+        cookieToSet: 'modified_cookie_for_direct_fetch=from_devtools'
+    });
+
+    await testFetchCookieOverride({
+        testName: 'Same-Origin Redirect Fetch',
+        urlToFetch: sameOriginRedirectUrl,
+        urlToIntercept: sameOriginEchoUrl,
+        isRedirect: true,
+        cookieToSet: 'modified_cookie_for_redirect_fetch=from_devtools'
+    });
+
+    await testFetchCookieOverride({
+        testName: 'Cross-Origin Redirect Fetch',
+        urlToFetch: crossOriginRedirectUrl,
+        urlToIntercept: crossOriginEchoUrl,
+        isRedirect: true,
+        cookieToSet: 'modified_cookie_for_cors_fetch=from_devtools'
+    });
+
+    await testNavigationCookieOverride({
+        testName: 'Direct Navigation',
+        urlToNavigate: sameOriginEchoUrl,
+        urlToIntercept: sameOriginEchoUrl,
+        cookieToSet: 'modified_cookie_for_direct_nav=from_devtools'
+    });
+
+    await testNavigationCookieOverride({
+        testName: 'Same-Origin Redirect Navigation',
+        urlToNavigate: sameOriginRedirectUrl,
+        urlToIntercept: sameOriginEchoUrl,
+        isRedirect: true,
+        cookieToSet: 'modified_cookie_for_redirect_nav=from_devtools'
+    });
+
+    await testNavigationCookieOverride({
+        testName: 'Cross-Origin Redirect Navigation',
+        urlToNavigate: crossOriginRedirectUrl,
+        urlToIntercept: crossOriginEchoUrl,
+        isRedirect: true,
+        cookieToSet: 'modified_cookie_for_cors_nav=from_devtools'
+    });
+
+    testRunner.completeTest();
+})
\ No newline at end of file
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/fetch/resources/cors-echo-headers.php b/third_party/blink/web_tests/http/tests/inspector-protocol/fetch/resources/cors-echo-headers.php
new file mode 100644
index 0000000..d1cf0e89
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/fetch/resources/cors-echo-headers.php
@@ -0,0 +1,7 @@
+<?php
+header("Access-Control-Allow-Origin: *");
+$headers = explode(":", $_GET['headers']);
+foreach ($headers as $header) {
+    echo $header . ": " . (isset($_SERVER[$header]) ? $_SERVER[$header] : "<not set>") . "\n";
+}
+?>
diff --git a/third_party/blink/web_tests/platform/fuchsia/webaudio/codec-tests/vorbis/vbr-128kbps-44khz-expected.wav b/third_party/blink/web_tests/platform/fuchsia/webaudio/codec-tests/vorbis/vbr-128kbps-44khz-expected.wav
deleted file mode 100644
index 420f66f..0000000
--- a/third_party/blink/web_tests/platform/fuchsia/webaudio/codec-tests/vorbis/vbr-128kbps-44khz-expected.wav
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/fuchsia/webaudio/codec-tests/vorbis/vbr-70kbps-44khz-expected.wav b/third_party/blink/web_tests/platform/fuchsia/webaudio/codec-tests/vorbis/vbr-70kbps-44khz-expected.wav
deleted file mode 100644
index dbd216b..0000000
--- a/third_party/blink/web_tests/platform/fuchsia/webaudio/codec-tests/vorbis/vbr-70kbps-44khz-expected.wav
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/fuchsia/webaudio/codec-tests/vorbis/vbr-96kbps-44khz-expected.wav b/third_party/blink/web_tests/platform/fuchsia/webaudio/codec-tests/vorbis/vbr-96kbps-44khz-expected.wav
deleted file mode 100644
index 0af475bf..0000000
--- a/third_party/blink/web_tests/platform/fuchsia/webaudio/codec-tests/vorbis/vbr-96kbps-44khz-expected.wav
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/webaudio/codec-tests/vorbis/vbr-128kbps-44khz-expected.wav b/third_party/blink/web_tests/platform/mac-mac12-arm64/webaudio/codec-tests/vorbis/vbr-128kbps-44khz-expected.wav
deleted file mode 100644
index e60131c..0000000
--- a/third_party/blink/web_tests/platform/mac-mac12-arm64/webaudio/codec-tests/vorbis/vbr-128kbps-44khz-expected.wav
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/webaudio/codec-tests/vorbis/vbr-70kbps-44khz-expected.wav b/third_party/blink/web_tests/platform/mac-mac12-arm64/webaudio/codec-tests/vorbis/vbr-70kbps-44khz-expected.wav
deleted file mode 100644
index bc487c5a..0000000
--- a/third_party/blink/web_tests/platform/mac-mac12-arm64/webaudio/codec-tests/vorbis/vbr-70kbps-44khz-expected.wav
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/webaudio/codec-tests/vorbis/vbr-96kbps-44khz-expected.wav b/third_party/blink/web_tests/platform/mac-mac12-arm64/webaudio/codec-tests/vorbis/vbr-96kbps-44khz-expected.wav
deleted file mode 100644
index 520998d2..0000000
--- a/third_party/blink/web_tests/platform/mac-mac12-arm64/webaudio/codec-tests/vorbis/vbr-96kbps-44khz-expected.wav
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac15-arm64/webaudio/codec-tests/wav/24bit-22khz-resample-expected.wav b/third_party/blink/web_tests/platform/mac-mac15-arm64/webaudio/codec-tests/wav/24bit-22khz-resample-expected.wav
deleted file mode 100644
index 8449898..0000000
--- a/third_party/blink/web_tests/platform/mac-mac15-arm64/webaudio/codec-tests/wav/24bit-22khz-resample-expected.wav
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/backface-visibility-interop/external/wpt/css/css-transforms/parsing/webkit-perspective-invalid.tentative-expected.txt b/third_party/blink/web_tests/platform/mac/virtual/backface-visibility-interop/external/wpt/css/css-transforms/parsing/webkit-perspective-invalid.tentative-expected.txt
new file mode 100644
index 0000000..3184da1
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac/virtual/backface-visibility-interop/external/wpt/css/css-transforms/parsing/webkit-perspective-invalid.tentative-expected.txt
@@ -0,0 +1,7 @@
+This is a testharness.js-based test.
+[FAIL] e.style['-webkit-perspective'] = "calc(1000)" should not set the property value
+  assert_equals: expected "" but got "1000px"
+[FAIL] e.style['-webkit-perspective'] = "calc(25)" should not set the property value
+  assert_equals: expected "" but got "25px"
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/platform/mac/virtual/scoped-custom-element-registry/external/wpt/custom-elements/registries/element-mutation-expected.txt b/third_party/blink/web_tests/platform/mac/virtual/scoped-custom-element-registry/external/wpt/custom-elements/registries/element-mutation-expected.txt
new file mode 100644
index 0000000..2d5c7a0
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac/virtual/scoped-custom-element-registry/external/wpt/custom-elements/registries/element-mutation-expected.txt
@@ -0,0 +1,11 @@
+This is a testharness.js-based test.
+[FAIL] An element with global registry should not change its registry when moved into a shadow tree with scoped registry.
+  assert_not_equals: got disallowed value object "[object CustomElementRegistry]"
+[FAIL] An element with scoped registry should not change its registry when moved out of the shadow tree.
+  assert_equals: expected object "[object CustomElementRegistry]" but got object "[object CustomElementRegistry]"
+[FAIL] An element with scoped registry should not change its registry when moved into another shadow tree with different scoped registry.
+  assert_equals: expected object "[object CustomElementRegistry]" but got object "[object CustomElementRegistry]"
+[FAIL] Declarative shadow DOM with shadowrootcustomelementregistry attribute without registry initialized should remain null registry after adoption.
+  assert_equals: expected object "[object CustomElementRegistry]" but got null
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/platform/mac/virtual/scoped-custom-element-registry/external/wpt/custom-elements/registries/template.window-expected.txt b/third_party/blink/web_tests/platform/mac/virtual/scoped-custom-element-registry/external/wpt/custom-elements/registries/template.window-expected.txt
new file mode 100644
index 0000000..558eeb4
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac/virtual/scoped-custom-element-registry/external/wpt/custom-elements/registries/template.window-expected.txt
@@ -0,0 +1,5 @@
+This is a testharness.js-based test.
+[FAIL] Serializing a ShadowRoot with a null registry with a scoped registry host document
+  assert_equals: expected "<template shadowrootmode=\\"closed\\" shadowrootserializable=\\"\\" shadowrootcustomelementregistry=\\"\\"></template>" but got "<template shadowrootmode=\\"closed\\" shadowrootserializable=\\"\\"></template>"
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/platform/win11-arm64/webaudio/codec-tests/wav/24bit-22khz-resample-expected.wav b/third_party/blink/web_tests/platform/win11-arm64/webaudio/codec-tests/wav/24bit-22khz-resample-expected.wav
deleted file mode 100644
index 8449898..0000000
--- a/third_party/blink/web_tests/platform/win11-arm64/webaudio/codec-tests/wav/24bit-22khz-resample-expected.wav
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/virtual/android/fullscreen/rendering/backdrop-video-expected.html b/third_party/blink/web_tests/virtual/android/fullscreen/rendering/backdrop-video-expected.html
deleted file mode 100644
index 99cbd08..0000000
--- a/third_party/blink/web_tests/virtual/android/fullscreen/rendering/backdrop-video-expected.html
+++ /dev/null
@@ -1,4 +0,0 @@
-<!DOCTYPE html>
-<!-- TODO(foolip): Setting the background on ::backdrop doesn't work on Android,
-     see crbug.com/627850 -->
-<body style="background: black"></body>
diff --git a/third_party/blink/web_tests/virtual/scoped-custom-element-registry/external/wpt/custom-elements/registries/element-mutation-expected.txt b/third_party/blink/web_tests/virtual/scoped-custom-element-registry/external/wpt/custom-elements/registries/element-mutation-expected.txt
index 547705de..2d5c7a0 100644
--- a/third_party/blink/web_tests/virtual/scoped-custom-element-registry/external/wpt/custom-elements/registries/element-mutation-expected.txt
+++ b/third_party/blink/web_tests/virtual/scoped-custom-element-registry/external/wpt/custom-elements/registries/element-mutation-expected.txt
@@ -5,5 +5,7 @@
   assert_equals: expected object "[object CustomElementRegistry]" but got object "[object CustomElementRegistry]"
 [FAIL] An element with scoped registry should not change its registry when moved into another shadow tree with different scoped registry.
   assert_equals: expected object "[object CustomElementRegistry]" but got object "[object CustomElementRegistry]"
+[FAIL] Declarative shadow DOM with shadowrootcustomelementregistry attribute without registry initialized should remain null registry after adoption.
+  assert_equals: expected object "[object CustomElementRegistry]" but got null
 Harness: the test ran to completion.
 
diff --git a/third_party/blink/web_tests/virtual/scoped-custom-element-registry/external/wpt/custom-elements/registries/template.window-expected.txt b/third_party/blink/web_tests/virtual/scoped-custom-element-registry/external/wpt/custom-elements/registries/template.window-expected.txt
index 5b37deb..558eeb4 100644
--- a/third_party/blink/web_tests/virtual/scoped-custom-element-registry/external/wpt/custom-elements/registries/template.window-expected.txt
+++ b/third_party/blink/web_tests/virtual/scoped-custom-element-registry/external/wpt/custom-elements/registries/template.window-expected.txt
@@ -1,4 +1,5 @@
 This is a testharness.js-based test.
-All subtests passed and are omitted for brevity.
-See https://chromium.googlesource.com/chromium/src/+/HEAD/docs/testing/writing_web_tests.md#Text-Test-Baselines for details.
+[FAIL] Serializing a ShadowRoot with a null registry with a scoped registry host document
+  assert_equals: expected "<template shadowrootmode=\\"closed\\" shadowrootserializable=\\"\\" shadowrootcustomelementregistry=\\"\\"></template>" but got "<template shadowrootmode=\\"closed\\" shadowrootserializable=\\"\\"></template>"
 Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/webaudio/AudioBufferSource/audiobuffersource-detune-modulation.html b/third_party/blink/web_tests/webaudio/AudioBufferSource/audiobuffersource-detune-modulation.html
index 4362f71..2ff8ff5 100644
--- a/third_party/blink/web_tests/webaudio/AudioBufferSource/audiobuffersource-detune-modulation.html
+++ b/third_party/blink/web_tests/webaudio/AudioBufferSource/audiobuffersource-detune-modulation.html
@@ -20,17 +20,10 @@
           new OfflineAudioContext(1, sampleRate * duration, sampleRate);
       let referenceBuffer;
 
-      promise_test(() => {
-        return new Promise((resolve, reject) => {
-          const loader = new BufferLoader(
-              context,
-              ['resources/audiobuffersource-detune-modulation-expected.wav'],
-              function(bufferList) {
-                referenceBuffer = bufferList[0];
-                resolve();
-              });
-          loader.load();
-        });
+      promise_test(async () => {
+        [referenceBuffer] = await loadBuffers(context, [
+          'resources/audiobuffersource-detune-modulation-expected.wav'
+        ]);
       }, 'load-reference');
 
       promise_test(async () => {
diff --git a/third_party/blink/web_tests/webaudio/AudioBufferSource/audiobuffersource-playbackrate-modulation.html b/third_party/blink/web_tests/webaudio/AudioBufferSource/audiobuffersource-playbackrate-modulation.html
index 40425826..a9d43ae 100644
--- a/third_party/blink/web_tests/webaudio/AudioBufferSource/audiobuffersource-playbackrate-modulation.html
+++ b/third_party/blink/web_tests/webaudio/AudioBufferSource/audiobuffersource-playbackrate-modulation.html
@@ -22,20 +22,11 @@
         const context =
             new OfflineAudioContext(1, sampleRate * duration, sampleRate);
 
-        // Load the reference file asynchronously.
-        const referenceBuffer = await new Promise((resolve) => {
-          const loader = new BufferLoader(
-              context,
-              [
-                'resources/' +
-                'audiobuffersource-playbackrate-modulation-expected.wav'
-              ],
-              (bufferList) => {
-                assert_true(true, 'Loaded reference file');
-                resolve(bufferList[0]);
-              });
-          loader.load();
-        });
+        const [referenceBuffer] = await loadBuffers(context, [
+          'resources/' +
+              'audiobuffersource-playbackrate-modulation-expected.wav'
+        ]);
+        assert_true(true, 'Loaded reference file');
 
         // With this setting, the playback rate will be changing continuously
         // and repeatedly within the range of [0, 200] around 100Hz, based on
diff --git a/third_party/blink/web_tests/webaudio/AudioParam/audioparam-k-rate.html b/third_party/blink/web_tests/webaudio/AudioParam/audioparam-k-rate.html
index e681a69..203138c 100644
--- a/third_party/blink/web_tests/webaudio/AudioParam/audioparam-k-rate.html
+++ b/third_party/blink/web_tests/webaudio/AudioParam/audioparam-k-rate.html
@@ -4,6 +4,7 @@
     <script src="../../resources/testharness.js"></script>
     <script src="../../resources/testharnessreport.js"></script>
     <script src="../resources/audit-util.js"></script>
+    <script src="../resources/buffer-loader.js"></script>
   </head>
   <body>
     <script>
diff --git a/third_party/blink/web_tests/webaudio/Panner/hrtf-database.html b/third_party/blink/web_tests/webaudio/Panner/hrtf-database.html
index 0013f05..59c1dbe2 100644
--- a/third_party/blink/web_tests/webaudio/Panner/hrtf-database.html
+++ b/third_party/blink/web_tests/webaudio/Panner/hrtf-database.html
@@ -22,26 +22,19 @@
 
       audit.define(
           {label: 'loadfiles', description: 'Load HRTF database files'},
-          function(task, should) {
-
+          async (task, should) => {
             // Any valid context with the right sample rate will do.
             context = new OfflineAudioContext(1, 1, sampleRate);
 
-            let bufferLoader = new BufferLoader(
-                context,
-                [
-                  '../resources/hrtf/Composite.wav',
-                  '../resources/hrtf/Composite.flac',
-                ],
-                function(bufferList) {
-                  should(bufferList.length, 'Number of buffers loaded')
-                      .beEqualTo(2);
-                  wavBuffer = bufferList[0];
-                  flacBuffer = bufferList[1];
-                  task.done();
-                });
+            const bufferList = await loadBuffers(context, [
+              '../resources/hrtf/Composite.wav',
+              '../resources/hrtf/Composite.flac',
+            ]);
 
-            bufferLoader.load();
+            should(bufferList.length, 'Number of buffers loaded').beEqualTo(2);
+            wavBuffer = bufferList[0];
+            flacBuffer = bufferList[1];
+            task.done();
           });
 
       audit.define(
diff --git a/third_party/blink/web_tests/webaudio/codec-tests/aac/m4a-short-duration-44khz.html b/third_party/blink/web_tests/webaudio/codec-tests/aac/m4a-short-duration-44khz.html
index fed6668b..b0e3b935 100644
--- a/third_party/blink/web_tests/webaudio/codec-tests/aac/m4a-short-duration-44khz.html
+++ b/third_party/blink/web_tests/webaudio/codec-tests/aac/m4a-short-duration-44khz.html
@@ -1,28 +1,26 @@
 <!DOCTYPE html>
 <html>
-  <head>
-    <title></title>
-    <script src="../../resources/audit-util.js"></script>
-    <script src="../../resources/buffer-loader.js"></script>
-    <script src="../../resources/audio-codec-test.js"></script>
-    <script src="../../resources/audio-file-utils.js"></script>
-  </head>
-  <body>
-    <script id="layout-test-code">
-      (function() {
 
-        // For certain AAC-encoded files, FFMPEG's estimated frame count might
-        // not be sufficient to capture the entire audio content that we want.
-        // This is especially noticeable for short files (< 10ms) resulting in
-        // silence throughout the decoded buffer. Thus we add the priming frames
-        // and the remainder frames to the estimation. (See: crbug.com/513178)
-        let targetUrl = '../../resources/media/440hz-10ms.m4a';
+<head>
+  <title>
+    Test AAC-LC Decoding
+  </title>
+  <script src="../../../resources/testharness.js"></script>
+  <script src="../../../resources/testharnessreport.js"></script>
+  <script src="../../resources/audit-util.js"></script>
+  <script src="../../resources/audio-file-utils.js"></script>
+  <script src="../../resources/buffer-loader.js"></script>
+  <script src="../resources/codec-tests.js"></script>
+</head>
 
-        window.onload = function() {
-          runDecodingTest(targetUrl);
-        };
+<body>
+  <script>
+    runCodecTest({
+      encodedFileName: '../../resources/media/440hz-10ms.m4a',
+      sampleRate: 44100,
+      description: 'Decoded short-duration AAC matches reference WAV',
+    });
+  </script>
+</body>
 
-      })();
-    </script>
-  </body>
 </html>
diff --git a/third_party/blink/web_tests/webaudio/codec-tests/aac/m4a-short-duration-44khz-expected.wav b/third_party/blink/web_tests/webaudio/codec-tests/aac/resources/m4a-short-duration-44khz-expected.wav
similarity index 100%
rename from third_party/blink/web_tests/webaudio/codec-tests/aac/m4a-short-duration-44khz-expected.wav
rename to third_party/blink/web_tests/webaudio/codec-tests/aac/resources/m4a-short-duration-44khz-expected.wav
Binary files differ
diff --git a/third_party/blink/web_tests/webaudio/codec-tests/aac/vbr-128kbps-44khz-expected.wav b/third_party/blink/web_tests/webaudio/codec-tests/aac/resources/vbr-128kbps-44khz-expected.wav
similarity index 100%
rename from third_party/blink/web_tests/webaudio/codec-tests/aac/vbr-128kbps-44khz-expected.wav
rename to third_party/blink/web_tests/webaudio/codec-tests/aac/resources/vbr-128kbps-44khz-expected.wav
Binary files differ
diff --git a/third_party/blink/web_tests/webaudio/codec-tests/aac/vbr-128kbps-44khz.html b/third_party/blink/web_tests/webaudio/codec-tests/aac/vbr-128kbps-44khz.html
index bfb8aa6..fdcae97 100644
--- a/third_party/blink/web_tests/webaudio/codec-tests/aac/vbr-128kbps-44khz.html
+++ b/third_party/blink/web_tests/webaudio/codec-tests/aac/vbr-128kbps-44khz.html
@@ -1,21 +1,26 @@
 <!DOCTYPE html>
 <html>
-  <head>
-    <title>
-      vbr-128kbps-44khz.html
-    </title>
-    <script src="../../resources/audio-codec-test.js"></script>
-    <script src="../../resources/audit-util.js"></script>
-    <script src="../../resources/buffer-loader.js"></script>
-    <script src="../../resources/audio-file-utils.js"></script>
-  </head>
-  <body>
-    <script id="layout-test-code">
-      let url = '../../resources/media/vbr-128kbps-44khz.m4a';
 
-      window.onload = function() {
-        runDecodingTest(url)
-      };
-    </script>
-  </body>
+<head>
+  <title>
+    Test AAC-LC VBR Decoding
+  </title>
+  <script src="../../../resources/testharness.js"></script>
+  <script src="../../../resources/testharnessreport.js"></script>
+  <script src="../../resources/audit-util.js"></script>
+  <script src="../../resources/audio-file-utils.js"></script>
+  <script src="../../resources/buffer-loader.js"></script>
+  <script src="../resources/codec-tests.js"></script>
+</head>
+
+<body>
+  <script>
+    runCodecTest({
+      encodedFileName: '../../resources/media/vbr-128kbps-44khz.m4a',
+      sampleRate: 44100,
+      description: 'Decoded AAC VBR file matches the reference WAV',
+    });
+  </script>
+</body>
+
 </html>
diff --git a/third_party/blink/web_tests/webaudio/codec-tests/flac/flac-decode.html b/third_party/blink/web_tests/webaudio/codec-tests/flac/flac-decode.html
index b06d99a..c7a3ac9d 100644
--- a/third_party/blink/web_tests/webaudio/codec-tests/flac/flac-decode.html
+++ b/third_party/blink/web_tests/webaudio/codec-tests/flac/flac-decode.html
@@ -1,21 +1,26 @@
 <!DOCTYPE html>
 <html>
-  <head>
-    <title>
-      Test FLAC Decoding
-    </title>
-    <script src="../../resources/audio-codec-test.js"></script>
-    <script src="../../resources/audit-util.js"></script>
-    <script src="../../resources/buffer-loader.js"></script>
-    <script src="../../resources/audio-file-utils.js"></script>
-  </head>
-  <body>
-    <script id="layout-test-code">
-      let url = 'flac-test.flac';
 
-      window.onload = function() {
-        runDecodingTest(url)
-      };
-    </script>
-  </body>
+<head>
+  <title>
+    Test FLAC Decoding
+  </title>
+  <script src="../../../resources/testharness.js"></script>
+  <script src="../../../resources/testharnessreport.js"></script>
+  <script src="../../resources/audit-util.js"></script>
+  <script src="../../resources/audio-file-utils.js"></script>
+  <script src="../../resources/buffer-loader.js"></script>
+  <script src="../resources/codec-tests.js"></script>
+</head>
+
+<body>
+  <script>
+    runCodecTest({
+      encodedFileName: 'flac-test.flac',
+      sampleRate: 44100,
+      description: 'Decoded FLAC matches reference WAV',
+    });
+  </script>
+</body>
+
 </html>
diff --git a/third_party/blink/web_tests/webaudio/codec-tests/flac/flac-decode-expected.wav b/third_party/blink/web_tests/webaudio/codec-tests/flac/resources/flac-decode-expected.wav
similarity index 100%
rename from third_party/blink/web_tests/webaudio/codec-tests/flac/flac-decode-expected.wav
rename to third_party/blink/web_tests/webaudio/codec-tests/flac/resources/flac-decode-expected.wav
Binary files differ
diff --git a/third_party/blink/web_tests/webaudio/codec-tests/mp3/128kbps-44khz.html b/third_party/blink/web_tests/webaudio/codec-tests/mp3/128kbps-44khz.html
index c7a859d5..fdd42e4 100644
--- a/third_party/blink/web_tests/webaudio/codec-tests/mp3/128kbps-44khz.html
+++ b/third_party/blink/web_tests/webaudio/codec-tests/mp3/128kbps-44khz.html
@@ -1,21 +1,26 @@
 <!DOCTYPE html>
 <html>
-  <head>
-    <title>
-      128kbps-44khz.html
-    </title>
-    <script src="../../resources/audio-codec-test.js"></script>
-    <script src="../../resources/audit-util.js"></script>
-    <script src="../../resources/buffer-loader.js"></script>
-    <script src="../../resources/audio-file-utils.js"></script>
-  </head>
-  <body>
-    <script id="layout-test-code">
-      let url = '../../resources/media/128kbps-44khz.mp3';
 
-      window.onload = function() {
-        runDecodingTest(url)
-      };
-    </script>
-  </body>
+<head>
+  <title>
+    Test MP3 Decoding
+  </title>
+  <script src="../../../resources/testharness.js"></script>
+  <script src="../../../resources/testharnessreport.js"></script>
+  <script src="../../resources/audit-util.js"></script>
+  <script src="../../resources/audio-file-utils.js"></script>
+  <script src="../../resources/buffer-loader.js"></script>
+  <script src="../resources/codec-tests.js"></script>
+</head>
+
+<body>
+  <script>
+    runCodecTest({
+      encodedFileName: '../../resources/media/128kbps-44khz.mp3',
+      sampleRate: 44100,
+      description: 'Decoded MP3 matches reference WAV',
+    });
+  </script>
+</body>
+
 </html>
diff --git a/third_party/blink/web_tests/webaudio/codec-tests/mp3/128kbps-44khz-expected.wav b/third_party/blink/web_tests/webaudio/codec-tests/mp3/resources/128kbps-44khz-expected.wav
similarity index 100%
rename from third_party/blink/web_tests/webaudio/codec-tests/mp3/128kbps-44khz-expected.wav
rename to third_party/blink/web_tests/webaudio/codec-tests/mp3/resources/128kbps-44khz-expected.wav
Binary files differ
diff --git a/third_party/blink/web_tests/webaudio/codec-tests/opus/opus-decode.html b/third_party/blink/web_tests/webaudio/codec-tests/opus/opus-decode.html
index f669a7d..a98f3d5b 100644
--- a/third_party/blink/web_tests/webaudio/codec-tests/opus/opus-decode.html
+++ b/third_party/blink/web_tests/webaudio/codec-tests/opus/opus-decode.html
@@ -5,90 +5,23 @@
   <title>
     Test OPUS Decoding
   </title>
-  <script src="../../resources/audio-codec-test.js"></script>
   <script src="../../../resources/testharness.js"></script>
   <script src="../../../resources/testharnessreport.js"></script>
   <script src="../../resources/audit-util.js"></script>
-  <script src="../../resources/buffer-loader.js"></script>
   <script src="../../resources/audio-file-utils.js"></script>
+  <script src="../../resources/buffer-loader.js"></script>
+  <script src="../resources/codec-tests.js"></script>
 </head>
 
 <body>
-  <script id="layout-test-code">
-    // Minimum acceptable signal-to-noise ratio (SNR) in decibels for each
-    // channel. The current value of 100dB is chosen as an absolute upper limit
-    // of what is audible by the human ear in professional audio applications.
-    // This value is permissive enough to allow for inconsequential changes in
-    // the actual waveform to occur due to things like the decoder implementation
-    // changing, but strict enough that it should not result in any meaningful
-    // changes to the output audio.
-    const kMinSnrDb = 100.0;
-
-    // Epsilon / absolute tolerance for sample-level comparison. While the most
-    // important part of the the test is the strict SNR analysis, sample-level
-    // comparison is a very useful tool for determining what types of problems
-    // may be causing issues with potential SNR failures. The current tolerance
-    // has been chosen experimentally to align with `kMinSnrDb`, meaning that
-    // you are quite unlikely to pass the SNR analysis but then get a
-    // sample-level failure.
-    const kAbsTolerance = 5e-5;
-
-    // Use 48 kHz to avoid resampling in decodeAudioData.
-    const kSampleRate = 48000;
-
-    function compareAudioBuffers(expectedBuffer, decodedBuffer) {
-      assert_equals(
-        decodedBuffer.numberOfChannels,
-        expectedBuffer.numberOfChannels,
-        'Channel count should match');
-      assert_equals(
-        decodedBuffer.length,
-        expectedBuffer.length,
-        'Frame count should match'
-      );
-
-      for (let channel = 0;
-        channel < expectedBuffer.numberOfChannels; ++channel) {
-        const reference = expectedBuffer.getChannelData(channel);
-        const actual = decodedBuffer.getChannelData(channel);
-
-        // Ensure the SNR meets the minimum requirement.
-        assert_greater_than_equal(
-          computeSnrInDecibels(actual, reference),
-          kMinSnrDb,
-          `SNR for channel ${channel} should meet the minimum requirement`
-        );
-
-        // Sample-wise comparison with absolute tolerance.
-        for (let i = 0; i < reference.length; ++i) {
-          assert_approx_equals(
-            actual[i], reference[i], kAbsTolerance,
-            `Channel ${channel}, sample ${i} should be approximately equal`);
-        }
-      }
-    }
-
-    promise_test(async t => {
-      const context = new AudioContext({
-        sampleRate: kSampleRate
-      });
-      assert_equals(context.sampleRate, kSampleRate, 'AudioContext sampleRate');
-
-      const [expectedBuffer, decodedBuffer] = await loadBuffers(
-        context,
-        [
-          'resources/opus-decode-expected.wav',
-          'opus-test.opus',
-        ]);
-
-      // Optionally save the decoded output for manual inspection.
-      const filename = 'opus-decode-actual.wav';
-      if (downloadAudioBuffer(decodedBuffer, filename, true)) {
-        assert_true(true, `Saved reference file: ${filename}`);
-      }
-
-      compareAudioBuffers(expectedBuffer, decodedBuffer);
-    }, 'Decoded Opus matches reference WAV within tolerance');
+  <script>
+    runCodecTest({
+      encodedFileName: 'opus-test.opus',
+      // Use 48 kHz to avoid resampling in decodeAudioData.
+      sampleRate: 48000,
+      description: 'Decoded Opus matches reference WAV within tolerance',
+    });
   </script>
 </body>
+
 </html>
diff --git a/third_party/blink/web_tests/webaudio/codec-tests/opus/resources/opus-decode-expected.wav b/third_party/blink/web_tests/webaudio/codec-tests/opus/resources/opus-decode-expected.wav
index aca788a..f7f00db 100644
--- a/third_party/blink/web_tests/webaudio/codec-tests/opus/resources/opus-decode-expected.wav
+++ b/third_party/blink/web_tests/webaudio/codec-tests/opus/resources/opus-decode-expected.wav
Binary files differ
diff --git a/third_party/blink/web_tests/webaudio/codec-tests/resources/codec-tests.js b/third_party/blink/web_tests/webaudio/codec-tests/resources/codec-tests.js
new file mode 100644
index 0000000..9c1c40c6
--- /dev/null
+++ b/third_party/blink/web_tests/webaudio/codec-tests/resources/codec-tests.js
@@ -0,0 +1,94 @@
+// Helper functions for running a codec test, which ensures that the WebAudio
+// implementation properly decodes specific containers by comparing the output
+// waveform to an expected baseline.
+
+// META: script=../../resources/buffer-loader.js
+
+// Does a thorough comparison of the actual buffer, `decoderBuffer`, against
+// an `expectedBuffer`. Both a file-level signal-to-noise ratio calculation
+// (compared against `minSnrDb`) and a per sample comparison (checked against
+// `absTolerance`) is performed.
+function compareAudioBuffers(
+    expectedBuffer, decodedBuffer, minSnrDb, absTolerance) {
+  assert_equals(
+      decodedBuffer.numberOfChannels, expectedBuffer.numberOfChannels,
+      'Channel count should match');
+  assert_equals(
+      decodedBuffer.length, expectedBuffer.length, 'Frame count should match');
+
+  for (let channel = 0; channel < expectedBuffer.numberOfChannels; ++channel) {
+    const reference = expectedBuffer.getChannelData(channel);
+    const actual = decodedBuffer.getChannelData(channel);
+
+    // Ensure the SNR meets the minimum requirement.
+    assert_greater_than_equal(
+        computeSnrInDecibels(actual, reference), minSnrDb,
+        `SNR for channel ${channel} should meet the minimum requirement`);
+
+    // Sample-wise comparison with absolute tolerance.
+    for (let i = 0; i < reference.length; ++i) {
+      assert_approx_equals(
+          actual[i], reference[i], absTolerance,
+          `Channel ${channel}, sample ${i} should be approximately equal`);
+    }
+  }
+}
+
+// Generates the expected and actual filenames based on the name of the current
+// test file.
+function getFileNames() {
+  const path = window.location.pathname;
+  const parts = path.split('/');
+  const testFile = parts[parts.length - 1];
+  const testName = testFile.substring(0, testFile.lastIndexOf('.'));
+
+  return {
+    expected: `resources/${testName}-expected.wav`,
+    actual: `${testName}-actual.wav`,
+  };
+}
+
+// Performs the actual codec test, using `options` where provided, and default
+// values when not provided.
+function runCodecTest(options) {
+  // Epsilon / absolute tolerance for sample-level comparison. While the
+  // most important part of the the test is the strict SNR analysis,
+  // sample-level comparison is a very useful tool for determining what
+  // types of problems may be causing issues with potential SNR failures.
+  // The current tolerance has been chosen experimentally to align with
+  // `kMinSnrDb`, meaning that you are quite unlikely to pass the SNR
+  // analysis but then get a sample-level failure.
+  const absTolerance = options.absTolerance ?? 5e-5;
+  // Minimum acceptable signal-to-noise ratio (SNR) in decibels for each
+  // channel. The current value of 100dB is chosen as an absolute upper
+  // limit of what is audible by the human ear in professional audio
+  // applications. This value is permissive enough to allow for
+  // inconsequential changes in the actual waveform to occur due to
+  // things like the decoder implementation changing, but strict enough
+  // that it should not result in any meaningful changes to the output
+  // audio.
+  const minSnrDb = options.minSnrDb ?? 100.0;
+
+  const fileNames = getFileNames();
+
+  promise_test(async t => {
+    const context = new AudioContext({sampleRate: options.sampleRate});
+    assert_equals(
+        context.sampleRate, options.sampleRate, 'AudioContext sampleRate');
+
+    let expectedBuffer, decodedBuffer;
+    try {
+      [expectedBuffer, decodedBuffer] = await loadBuffers(
+          context, [fileNames.expected, options.encodedFileName]);
+    } catch (e) {
+      assert_unreached('Failed to load audio files: ' + e);
+    }
+
+    // Optionally save the decoded output for manual inspection.
+    if (downloadAudioBuffer(decodedBuffer, fileNames.actual, true)) {
+      assert_true(true, `Saved reference file: ${fileNames.actual}`);
+    }
+
+    compareAudioBuffers(expectedBuffer, decodedBuffer, minSnrDb, absTolerance);
+  }, options.description);
+}
diff --git a/third_party/blink/web_tests/webaudio/codec-tests/vorbis/vbr-128kbps-44khz-expected.wav b/third_party/blink/web_tests/webaudio/codec-tests/vorbis/resources/vbr-128kbps-44khz-expected.wav
similarity index 100%
rename from third_party/blink/web_tests/webaudio/codec-tests/vorbis/vbr-128kbps-44khz-expected.wav
rename to third_party/blink/web_tests/webaudio/codec-tests/vorbis/resources/vbr-128kbps-44khz-expected.wav
Binary files differ
diff --git a/third_party/blink/web_tests/webaudio/codec-tests/vorbis/vbr-70kbps-44khz-expected.wav b/third_party/blink/web_tests/webaudio/codec-tests/vorbis/resources/vbr-70kbps-44khz-expected.wav
similarity index 100%
rename from third_party/blink/web_tests/webaudio/codec-tests/vorbis/vbr-70kbps-44khz-expected.wav
rename to third_party/blink/web_tests/webaudio/codec-tests/vorbis/resources/vbr-70kbps-44khz-expected.wav
Binary files differ
diff --git a/third_party/blink/web_tests/webaudio/codec-tests/vorbis/vbr-96kbps-44khz-expected.wav b/third_party/blink/web_tests/webaudio/codec-tests/vorbis/resources/vbr-96kbps-44khz-expected.wav
similarity index 100%
rename from third_party/blink/web_tests/webaudio/codec-tests/vorbis/vbr-96kbps-44khz-expected.wav
rename to third_party/blink/web_tests/webaudio/codec-tests/vorbis/resources/vbr-96kbps-44khz-expected.wav
Binary files differ
diff --git a/third_party/blink/web_tests/webaudio/codec-tests/vorbis/vbr-128kbps-44khz.html b/third_party/blink/web_tests/webaudio/codec-tests/vorbis/vbr-128kbps-44khz.html
index 527a0a6..be491b9 100644
--- a/third_party/blink/web_tests/webaudio/codec-tests/vorbis/vbr-128kbps-44khz.html
+++ b/third_party/blink/web_tests/webaudio/codec-tests/vorbis/vbr-128kbps-44khz.html
@@ -1,21 +1,28 @@
 <!DOCTYPE html>
 <html>
-  <head>
-    <title>
-      vbr-128kbps-44khz.html
-    </title>
-    <script src="../../resources/audio-codec-test.js"></script>
-    <script src="../../resources/audit-util.js"></script>
-    <script src="../../resources/buffer-loader.js"></script>
-    <script src="../../resources/audio-file-utils.js"></script>
-  </head>
-  <body>
-    <script id="layout-test-code">
-      let url = '../../resources/media/vbr-128kbps-44khz.ogg';
 
-      window.onload = function() {
-        runDecodingTest(url)
-      };
-    </script>
-  </body>
+<head>
+  <title>
+    Test Vorbis Decoding
+  </title>
+  <script src="../../../resources/testharness.js"></script>
+  <script src="../../../resources/testharnessreport.js"></script>
+  <script src="../../resources/audit-util.js"></script>
+  <script src="../../resources/audio-file-utils.js"></script>
+  <script src="../../resources/buffer-loader.js"></script>
+  <script src="../resources/codec-tests.js"></script>
+</head>
+
+<body>
+  <script>
+    runCodecTest({
+      encodedFileName: '../../resources/media/vbr-128kbps-44khz.ogg',
+      sampleRate: 44100,
+      // TODO(crbug.com/452432472): regenerate baseline file, bump value to 100.
+      minSnrDb: 80,
+      description: 'Decoded Vorbis (128kbps) matches reference WAV',
+    });
+  </script>
+</body>
+
 </html>
diff --git a/third_party/blink/web_tests/webaudio/codec-tests/vorbis/vbr-70kbps-44khz.html b/third_party/blink/web_tests/webaudio/codec-tests/vorbis/vbr-70kbps-44khz.html
index aec480e..11e026f 100644
--- a/third_party/blink/web_tests/webaudio/codec-tests/vorbis/vbr-70kbps-44khz.html
+++ b/third_party/blink/web_tests/webaudio/codec-tests/vorbis/vbr-70kbps-44khz.html
@@ -1,21 +1,28 @@
 <!DOCTYPE html>
 <html>
-  <head>
-    <title>
-      vbr-70kbps-44khz.html
-    </title>
-    <script src="../../resources/audio-codec-test.js"></script>
-    <script src="../../resources/audit-util.js"></script>
-    <script src="../../resources/buffer-loader.js"></script>
-    <script src="../../resources/audio-file-utils.js"></script>
-  </head>
-  <body>
-    <script id="layout-test-code">
-      let url = '../../resources/media/vbr-70kbps-44khz.ogg';
 
-      window.onload = function() {
-        runDecodingTest(url)
-      };
-    </script>
-  </body>
+<head>
+  <title>
+    Test Vorbis Decoding
+  </title>
+  <script src="../../../resources/testharness.js"></script>
+  <script src="../../../resources/testharnessreport.js"></script>
+  <script src="../../resources/audit-util.js"></script>
+  <script src="../../resources/audio-file-utils.js"></script>
+  <script src="../../resources/buffer-loader.js"></script>
+  <script src="../resources/codec-tests.js"></script>
+</head>
+
+<body>
+  <script>
+    runCodecTest({
+      encodedFileName: '../../resources/media/vbr-70kbps-44khz.ogg',
+      sampleRate: 44100,
+      // TODO(crbug.com/452432472): regenerate baseline file, bump value to 100.
+      minSnrDb: 89,
+      description: 'Decoded Vorbis (70kbps) matches reference WAV',
+    });
+  </script>
+</body>
+
 </html>
diff --git a/third_party/blink/web_tests/webaudio/codec-tests/vorbis/vbr-96kbps-44khz.html b/third_party/blink/web_tests/webaudio/codec-tests/vorbis/vbr-96kbps-44khz.html
index 467228a..57e7738d 100644
--- a/third_party/blink/web_tests/webaudio/codec-tests/vorbis/vbr-96kbps-44khz.html
+++ b/third_party/blink/web_tests/webaudio/codec-tests/vorbis/vbr-96kbps-44khz.html
@@ -1,21 +1,28 @@
 <!DOCTYPE html>
 <html>
-  <head>
-    <title>
-      vbr-96kbps-44khz.html
-    </title>
-    <script src="../../resources/audio-codec-test.js"></script>
-    <script src="../../resources/audit-util.js"></script>
-    <script src="../../resources/audio-file-utils.js"></script>
-    <script src="../../resources/buffer-loader.js"></script>
-  </head>
-  <body>
-    <script id="layout-test-code">
-      let url = '../../resources/media/vbr-96kbps-44khz.ogg';
 
-      window.onload = function() {
-        runDecodingTest(url)
-      };
-    </script>
-  </body>
+<head>
+  <title>
+    Test Vorbis Decoding
+  </title>
+  <script src="../../../resources/testharness.js"></script>
+  <script src="../../../resources/testharnessreport.js"></script>
+  <script src="../../resources/audit-util.js"></script>
+  <script src="../../resources/audio-file-utils.js"></script>
+  <script src="../../resources/buffer-loader.js"></script>
+  <script src="../resources/codec-tests.js"></script>
+</head>
+
+<body>
+  <script>
+    runCodecTest({
+      encodedFileName: '../../resources/media/vbr-96kbps-44khz.ogg',
+      // TODO(crbug.com/452432472): regenerate baseline file, bump value to 100.
+      minSnrDb: 72,
+      sampleRate: 44100,
+      description: 'Decoded Vorbis (96kbps) matches reference WAV',
+    });
+  </script>
+</body>
+
 </html>
diff --git a/third_party/blink/web_tests/webaudio/codec-tests/wav/24bit-22khz-resample.html b/third_party/blink/web_tests/webaudio/codec-tests/wav/24bit-22khz-resample.html
index a309080..56ae550 100644
--- a/third_party/blink/web_tests/webaudio/codec-tests/wav/24bit-22khz-resample.html
+++ b/third_party/blink/web_tests/webaudio/codec-tests/wav/24bit-22khz-resample.html
@@ -1,23 +1,30 @@
 <!DOCTYPE html>
 <html>
-  <head>
-    <title>
-      24bit-22khz-resample.html
-    </title>
-    <script src="../../resources/audio-codec-test.js"></script>
-    <script src="../../resources/audit-util.js"></script>
-    <script src="../../resources/buffer-loader.js"></script>
-    <script src="../../resources/audio-file-utils.js"></script>
-  </head>
-  <body>
-    <script id="layout-test-code">
+
+<head>
+  <title>
+    Test WAV Decoding (24bit, 22kHz with resample)
+  </title>
+  <script src="../../../resources/testharness.js"></script>
+  <script src="../../../resources/testharnessreport.js"></script>
+  <script src="../../resources/audit-util.js"></script>
+  <script src="../../resources/audio-file-utils.js"></script>
+  <script src="../../resources/buffer-loader.js"></script>
+  <script src="../resources/codec-tests.js"></script>
+</head>
+
+<body>
+  <script>
+    runCodecTest({
       // Test 24bit WAV decoder, 22.05 kHz sample rate.  This also tests the
       // resampler because the context is running at 44.1 kHz.
-      let url = '../../resources/media/24bit-22khz.wav';
+      encodedFileName: '../../resources/media/24bit-22khz.wav',
+      // TODO(crbug.com/452432472): regenerate baseline file, bump value to 100.
+      minSnrDb: 81,
+      sampleRate: 44100,
+      description: 'Decoded WAV (24bit, 22kHz, resampled) matches reference WAV',
+    });
+  </script>
+</body>
 
-      window.onload = function() {
-        runDecodingTest(url)
-      };
-    </script>
-  </body>
 </html>
diff --git a/third_party/blink/web_tests/webaudio/codec-tests/wav/24bit-44khz.html b/third_party/blink/web_tests/webaudio/codec-tests/wav/24bit-44khz.html
index 74c750a..661bc3c 100644
--- a/third_party/blink/web_tests/webaudio/codec-tests/wav/24bit-44khz.html
+++ b/third_party/blink/web_tests/webaudio/codec-tests/wav/24bit-44khz.html
@@ -1,21 +1,28 @@
 <!DOCTYPE html>
 <html>
-  <head>
-    <title>
-      24bit-44khz.html
-    </title>
-    <script src="../../resources/audio-codec-test.js"></script>
-    <script src="../../resources/audit-util.js"></script>
-    <script src="../../resources/buffer-loader.js"></script>
-    <script src="../../resources/audio-file-utils.js"></script>
-  </head>
-  <body>
-    <script id="layout-test-code">
-      let url = '../../resources/media/24bit-44khz.wav';
 
-      window.onload = function() {
-        runDecodingTest(url)
-      };
-    </script>
-  </body>
+<head>
+  <title>
+    Test WAV Decoding (24bit, 44kHz)
+  </title>
+  <script src="../../../resources/testharness.js"></script>
+  <script src="../../../resources/testharnessreport.js"></script>
+  <script src="../../resources/audit-util.js"></script>
+  <script src="../../resources/audio-file-utils.js"></script>
+  <script src="../../resources/buffer-loader.js"></script>
+  <script src="../resources/codec-tests.js"></script>
+</head>
+
+<body>
+  <script>
+    runCodecTest({
+      encodedFileName: '../../resources/media/24bit-44khz.wav',
+      // TODO(crbug.com/452432472): regenerate baseline file, bump value to 100.
+      minSnrDb: 93,
+      sampleRate: 44100,
+      description: 'Decoded WAV (24bit, 44kHz) matches reference WAV',
+    });
+  </script>
+</body>
+
 </html>
diff --git a/third_party/blink/web_tests/webaudio/codec-tests/wav/f32-44khz.html b/third_party/blink/web_tests/webaudio/codec-tests/wav/f32-44khz.html
index 7a31fcb..7c1950bf 100644
--- a/third_party/blink/web_tests/webaudio/codec-tests/wav/f32-44khz.html
+++ b/third_party/blink/web_tests/webaudio/codec-tests/wav/f32-44khz.html
@@ -1,21 +1,28 @@
 <!DOCTYPE html>
 <html>
-  <head>
-    <title>
-      f32-44khz.html
-    </title>
-    <script src="../../resources/audio-codec-test.js"></script>
-    <script src="../../resources/audit-util.js"></script>
-    <script src="../../resources/buffer-loader.js"></script>
-    <script src="../../resources/audio-file-utils.js"></script>
-  </head>
-  <body>
-    <script id="layout-test-code">
-      let url = '../../resources/media/f32-44khz.wav';
 
-      window.onload = function() {
-        runDecodingTest(url)
-      };
-    </script>
-  </body>
+<head>
+  <title>
+    Test WAV Decoding (32-bit float, 44kHz)
+  </title>
+  <script src="../../../resources/testharness.js"></script>
+  <script src="../../../resources/testharnessreport.js"></script>
+  <script src="../../resources/audit-util.js"></script>
+  <script src="../../resources/audio-file-utils.js"></script>
+  <script src="../../resources/buffer-loader.js"></script>
+  <script src="../resources/codec-tests.js"></script>
+</head>
+
+<body>
+  <script>
+    runCodecTest({
+      encodedFileName: '../../resources/media/f32-44khz.wav',
+      sampleRate: 44100,
+      // TODO(crbug.com/452432472): regenerate baseline file, bump value to 100.
+      minSnrDb: 93,
+      description: 'Decoded WAV (32-bit float, 44kHz) matches reference WAV',
+    });
+  </script>
+</body>
+
 </html>
diff --git a/third_party/blink/web_tests/webaudio/codec-tests/wav/24bit-22khz-resample-expected.wav b/third_party/blink/web_tests/webaudio/codec-tests/wav/resources/24bit-22khz-resample-expected.wav
similarity index 100%
rename from third_party/blink/web_tests/webaudio/codec-tests/wav/24bit-22khz-resample-expected.wav
rename to third_party/blink/web_tests/webaudio/codec-tests/wav/resources/24bit-22khz-resample-expected.wav
Binary files differ
diff --git a/third_party/blink/web_tests/webaudio/codec-tests/wav/24bit-44khz-expected.wav b/third_party/blink/web_tests/webaudio/codec-tests/wav/resources/24bit-44khz-expected.wav
similarity index 100%
rename from third_party/blink/web_tests/webaudio/codec-tests/wav/24bit-44khz-expected.wav
rename to third_party/blink/web_tests/webaudio/codec-tests/wav/resources/24bit-44khz-expected.wav
Binary files differ
diff --git a/third_party/blink/web_tests/webaudio/codec-tests/wav/f32-44khz-expected.wav b/third_party/blink/web_tests/webaudio/codec-tests/wav/resources/f32-44khz-expected.wav
similarity index 100%
rename from third_party/blink/web_tests/webaudio/codec-tests/wav/f32-44khz-expected.wav
rename to third_party/blink/web_tests/webaudio/codec-tests/wav/resources/f32-44khz-expected.wav
Binary files differ
diff --git a/third_party/blink/web_tests/webaudio/codec-tests/webm/webm-decode.html b/third_party/blink/web_tests/webaudio/codec-tests/webm/webm-decode.html
index deee9a2..da5b0626 100644
--- a/third_party/blink/web_tests/webaudio/codec-tests/webm/webm-decode.html
+++ b/third_party/blink/web_tests/webaudio/codec-tests/webm/webm-decode.html
@@ -1,72 +1,32 @@
 <!DOCTYPE html>
 <html>
-  <head>
-    <title>WebM decoding test </title>
-    <script src="../../../resources/testharness.js"></script>
-    <script src="../../../resources/testharnessreport.js"></script>
-    <script src="../../resources/audit-util.js"></script>
-    <script src="../../resources/audio-file-utils.js"></script>
-  </head>
-  <body>
-    <script>
-      // Absolute tolerance for sample-level comparison.
-      const kAbsTolerance = 4.0234e-5;
 
+<head>
+  <title>
+    WebM decoding test
+  </title>
+  <script src="../../../resources/testharness.js"></script>
+  <script src="../../../resources/testharnessreport.js"></script>
+  <script src="../../resources/audit-util.js"></script>
+  <script src="../../resources/audio-file-utils.js"></script>
+  <script src="../../resources/buffer-loader.js"></script>
+  <script src="../resources/codec-tests.js"></script>
+</head>
+
+<body>
+  <script>
+    runCodecTest({
+      encodedFileName: 'test-webm.webm',
+      // Absolute tolerance for sample-level comparison.
+      absTolerance: 4.0234e-5,
+      // Use 48 kHz to avoid resampling in decodeAudioData.
+      sampleRate: 48000,
       // Minimum acceptable signal-to-noise ratio (SNR) in decibels for each
       // channel.
-      const kMinSnrDb = 110.97;
+      minSnrDb: 110.97,
+      description: 'Decoded WebM matches reference WAV within tolerance',
+    });
+  </script>
+</body>
 
-      promise_test(async t => {
-        // Use 48 kHz to avoid resampling in decodeAudioData.
-        const context = new AudioContext({ sampleRate: 48000 });
-
-        const [expectedBuffer, decodedBuffer] = await loadBuffers(
-            context,
-            [
-              'resources/webm-decode-expected.wav',
-              'test-webm.webm',
-            ]);
-
-        // Optionally save the decoded output for manual inspection.
-        const filename = 'webm-decode-actual.wav';
-        if (downloadAudioBuffer(decodedBuffer, filename, true)) {
-          assert_true(true, `Saved reference file: ${filename}`);
-        }
-
-        assert_equals(context.sampleRate, 48000, 'AudioContext sampleRate');
-        assert_equals(
-            decodedBuffer.numberOfChannels,
-            expectedBuffer.numberOfChannels,
-            'Channel count');
-        assert_equals(
-          decodedBuffer.length,
-          expectedBuffer.length,
-          'Frame count'
-        );
-
-        for (let channel = 0;
-             channel < expectedBuffer.numberOfChannels;
-             ++channel) {
-          const reference = expectedBuffer.getChannelData(channel);
-          const actual = decodedBuffer.getChannelData(channel);
-
-          // Compute signal-to-noise ratio (SNR).
-          const snrDb = 10 * Math.log10(computeSNR(actual, reference));
-            // Ensure the SNR meets the minimum requirement.
-            assert_greater_than_equal(
-            snrDb,
-            kMinSnrDb,
-            `SNR for channel ${channel}`
-            );
-
-          // Sample-wise comparison with absolute tolerance.
-          for (let i = 0; i < reference.length; ++i) {
-            assert_approx_equals(
-              actual[i], reference[i], kAbsTolerance,
-              `Channel ${channel}, sample ${i}`);
-          }
-        }
-      }, 'Decoded WebM matches reference WAV within tolerance');
-    </script>
-  </body>
 </html>
diff --git a/third_party/blink/web_tests/webaudio/internals/audiobuffersource.html b/third_party/blink/web_tests/webaudio/internals/audiobuffersource.html
index e66e43c..967dc7cf 100644
--- a/third_party/blink/web_tests/webaudio/internals/audiobuffersource.html
+++ b/third_party/blink/web_tests/webaudio/internals/audiobuffersource.html
@@ -14,34 +14,26 @@
   </head>
   <body>
     <script id="layout-test-code">
-      window.onload = init;
+      window.onload = runTest;
 
-      let sampleRate = 44100.0;
-      let lengthInSeconds = 2;
-
-      let context = 0;
-      let bufferLoader = 0;
-
-      function init() {
-        if (!window.testRunner)
+      async function runTest() {
+        if (!window.testRunner) {
           return;
+        }
+
+        testRunner.waitUntilDone();
+
+        let sampleRate = 44100.0;
+        let lengthInSeconds = 2;
+        let context = 0;
 
         // Create offline audio context.
         context = new OfflineAudioContext(
             2, sampleRate * lengthInSeconds, sampleRate);
 
-        bufferLoader = new BufferLoader(
-            context,
-            [
-              '../resources/hyper-reality/br-jam-loop.wav',
-            ],
-            finishedLoading);
+        const bufferList = await loadBuffers(
+            context, ['../resources/hyper-reality/br-jam-loop.wav']);
 
-        bufferLoader.load();
-        testRunner.waitUntilDone();
-      }
-
-      function finishedLoading(bufferList) {
         let bufferSource = context.createBufferSource();
         bufferSource.buffer = bufferList[0];
 
diff --git a/third_party/blink/web_tests/webaudio/mixing.html b/third_party/blink/web_tests/webaudio/mixing.html
index 17297db5..e64e730 100644
--- a/third_party/blink/web_tests/webaudio/mixing.html
+++ b/third_party/blink/web_tests/webaudio/mixing.html
@@ -10,6 +10,7 @@
     <script src="../resources/testharness.js"></script>
     <script src="../resources/testharnessreport.js"></script>
     <script src="resources/audit-util.js"></script>
+    <script src="resources/buffer-loader.js"></script>
   </head>
   <body>
     <script id="layout-test-code">
diff --git a/third_party/blink/web_tests/webaudio/resources/audio-codec-test.js b/third_party/blink/web_tests/webaudio/resources/audio-codec-test.js
deleted file mode 100644
index b6cfa049..0000000
--- a/third_party/blink/web_tests/webaudio/resources/audio-codec-test.js
+++ /dev/null
@@ -1,31 +0,0 @@
-let defaultSampleRate = 44100.0;
-let lengthInSeconds = 1;
-
-let context = 0;
-let bufferLoader = 0;
-
-// Run test by loading the file specified by |url|.  An optional sample rate can
-// be given to select a context with a different sample rate.  The default value
-// is |defaultSampleRate|.
-function runDecodingTest(url, optionalSampleRate) {
-  if (!window.testRunner)
-    return;
-
-  let sampleRate = (typeof optionalSampleRate === 'undefined') ?
-      defaultSampleRate :
-      optionalSampleRate;
-
-  // Create offline audio context.
-  context =
-      new OfflineAudioContext(1, sampleRate * lengthInSeconds, sampleRate);
-
-  bufferLoader = new BufferLoader(context, [url], finishedLoading);
-
-  bufferLoader.load();
-  testRunner.waitUntilDone();
-}
-
-function finishedLoading(bufferList) {
-  testRunner.setAudioData(createAudioData(bufferList[0]));
-  testRunner.notifyDone();
-}
diff --git a/third_party/blink/web_tests/webaudio/resources/audit-util.js b/third_party/blink/web_tests/webaudio/resources/audit-util.js
index ffc1c950..d9496b8e 100644
--- a/third_party/blink/web_tests/webaudio/resources/audit-util.js
+++ b/third_party/blink/web_tests/webaudio/resources/audit-util.js
@@ -269,65 +269,7 @@
   return 10 * Math.log10(computeSNR(actual, expected));
 }
 
-// BufferLoader – utility for fetching & decoding multiple audio files.
-function BufferLoader(context, urlList, callback) {
-  this.context = context;
-  this.urlList = urlList;
-  this.onload = callback;
-  this.bufferList = new Array();
-  this.loadCount = 0;
-}
 
-// BufferLoader – utility for fetching & decoding multiple audio files.
-BufferLoader.prototype.loadBuffer = function(url, index) {
-  // Load buffer asynchronously
-  let request = new XMLHttpRequest();
-  request.open('GET', url, true);
-  request.responseType = 'arraybuffer';
-
-  let loader = this;
-
-  request.onload = function() {
-    loader.context.decodeAudioData(
-      request.response,
-      function(decodedAudio) {
-        try {
-          loader.bufferList[index] = decodedAudio;
-          if (++loader.loadCount === loader.urlList.length)
-            loader.onload(loader.bufferList);
-        } catch (e) {
-          console.log(e);
-          alert(
-            'BufferLoader: unable to load buffer ' + index +
-            ', url: ' + loader.urlList[index]);
-        }
-      },
-      function() {
-        alert('error decoding file data: ' + url);
-      }
-    );
-  };
-
-  request.onerror = function() {
-    alert('BufferLoader: XHR error');
-  };
-
-  request.send();
-};
-
-BufferLoader.prototype.load = function() {
-  for (let i = 0; i < this.urlList.length; ++i)
-    this.loadBuffer(this.urlList[i], i);
-};
-
-// Returns a promise that resolves with an array of AudioBuffers once all
-// resources have loaded.
-function loadBuffers(context, urls) {
-  return new Promise((resolve, reject) => {
-    const loader = new BufferLoader(context, urls, resolve, reject);
-    loader.load();
-  });
-}
 
 /**
  * Creates a test buffer with linear ramp PCM data: 0, 1, 2, ... length-1.
diff --git a/third_party/blink/web_tests/webaudio/resources/buffer-loader.js b/third_party/blink/web_tests/webaudio/resources/buffer-loader.js
index fc91aa64..b1f0a05 100644
--- a/third_party/blink/web_tests/webaudio/resources/buffer-loader.js
+++ b/third_party/blink/web_tests/webaudio/resources/buffer-loader.js
@@ -1,13 +1,19 @@
-function BufferLoader(context, urlList, callback) {
+// 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.
+
+// BufferLoader – utility for fetching & decoding multiple audio files.
+function BufferLoader(context, urlList, callback, reject) {
   this.context = context;
   this.urlList = urlList;
   this.onload = callback;
+  this.onerror = reject;
   this.bufferList = new Array();
   this.loadCount = 0;
 }
 
-BufferLoader.prototype.loadBuffer =
-    function(url, index) {
+// BufferLoader – utility for fetching & decoding multiple audio files.
+BufferLoader.prototype.loadBuffer = function(url, index) {
   // Load buffer asynchronously
   let request = new XMLHttpRequest();
   request.open('GET', url, true);
@@ -15,36 +21,42 @@
 
   let loader = this;
 
-  request.onload =
-      function() {
+  request.onload = function() {
     loader.context.decodeAudioData(
         request.response,
         function(decodedAudio) {
           try {
             loader.bufferList[index] = decodedAudio;
-            if (++loader.loadCount == loader.urlList.length)
+            if (++loader.loadCount === loader.urlList.length)
               loader.onload(loader.bufferList);
           } catch (e) {
-            console.log(e);
-            alert(
+            loader.onerror(
                 'BufferLoader: unable to load buffer ' + index +
-                ', url: ' + loader.urlList[index]);
+                ', url: ' + loader.urlList[index] + ' error: ' + e);
           }
         },
         function() {
-          alert('error decoding file data: ' + url);
+          loader.onerror('error decoding file data: ' + url);
         });
-  }
+  };
 
-      request.onerror =
-          function() {
-    alert('BufferLoader: XHR error');
-  }
+  request.onerror = function() {
+    loader.onerror('BufferLoader: XHR error');
+  };
 
-          request.send();
-}
+  request.send();
+};
 
-    BufferLoader.prototype.load = function() {
+BufferLoader.prototype.load = function() {
   for (let i = 0; i < this.urlList.length; ++i)
     this.loadBuffer(this.urlList[i], i);
+};
+
+// Returns a promise that resolves with an array of AudioBuffers once all
+// resources have loaded.
+function loadBuffers(context, urls) {
+  return new Promise((resolve, reject) => {
+    const loader = new BufferLoader(context, urls, resolve, reject);
+    loader.load();
+  });
 }
diff --git a/third_party/blink/web_tests/webexposed/element-instance-property-listing-expected.txt b/third_party/blink/web_tests/webexposed/element-instance-property-listing-expected.txt
index 8895b54..c590d0d 100644
--- a/third_party/blink/web_tests/webexposed/element-instance-property-listing-expected.txt
+++ b/third_party/blink/web_tests/webexposed/element-instance-property-listing-expected.txt
@@ -503,6 +503,7 @@
     property onbeforeunload
     property ongamepadconnected
     property ongamepaddisconnected
+    property ongamepadrawinputchanged
     property onhashchange
     property onlanguagechange
     property onmessage
@@ -673,6 +674,7 @@
     property onbeforeunload
     property ongamepadconnected
     property ongamepaddisconnected
+    property ongamepadrawinputchanged
     property onhashchange
     property onlanguagechange
     property onmessage
diff --git a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
index 66b3bfd..48a7181 100644
--- a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
+++ b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
@@ -791,6 +791,9 @@
     getter namespaceURI
     getter prefix
     method constructor
+interface CSSNavigationRule : CSSConditionRule
+    attribute @@toStringTag
+    method constructor
 interface CSSNestedDeclarations : CSSRule
     attribute @@toStringTag
     getter style
@@ -1022,9 +1025,6 @@
     setter x
     setter y
     setter z
-interface CSSRouteRule : CSSConditionRule
-    attribute @@toStringTag
-    method constructor
 interface CSSRule
     attribute @@toStringTag
     attribute CHARSET_RULE
@@ -3466,6 +3466,14 @@
     method constructor
     method playEffect
     method reset
+interface GamepadRawInputChangeEvent : GamepadEvent
+    attribute @@toStringTag
+    getter axesChanged
+    getter buttonsPressed
+    getter buttonsReleased
+    getter buttonsValueChanged
+    getter touchesChanged
+    method constructor
 interface GamepadTouch
     attribute @@toStringTag
     getter position
@@ -3692,6 +3700,7 @@
     getter onfocus
     getter ongamepadconnected
     getter ongamepaddisconnected
+    getter ongamepadrawinputchanged
     getter onhashchange
     getter onlanguagechange
     getter onload
@@ -3725,6 +3734,7 @@
     setter onfocus
     setter ongamepadconnected
     setter ongamepaddisconnected
+    setter ongamepadrawinputchanged
     setter onhashchange
     setter onlanguagechange
     setter onload
@@ -4259,6 +4269,7 @@
     getter onfocus
     getter ongamepadconnected
     getter ongamepaddisconnected
+    getter ongamepadrawinputchanged
     getter onhashchange
     getter onlanguagechange
     getter onload
@@ -4288,6 +4299,7 @@
     setter onfocus
     setter ongamepadconnected
     setter ongamepaddisconnected
+    setter ongamepadrawinputchanged
     setter onhashchange
     setter onlanguagechange
     setter onload
@@ -13447,6 +13459,7 @@
     getter onformdata
     getter ongamepadconnected
     getter ongamepaddisconnected
+    getter ongamepadrawinputchanged
     getter ongotpointercapture
     getter onhashchange
     getter oninput
@@ -13680,6 +13693,7 @@
     setter onformdata
     setter ongamepadconnected
     setter ongamepaddisconnected
+    setter ongamepadrawinputchanged
     setter ongotpointercapture
     setter onhashchange
     setter oninput
diff --git a/third_party/blink/web_tests/wpt_internal/route/parsing/invalid-route-rule-002.html b/third_party/blink/web_tests/wpt_internal/route/parsing/invalid-route-rule-002.html
index 4558bf8f..367418c 100644
--- a/third_party/blink/web_tests/wpt_internal/route/parsing/invalid-route-rule-002.html
+++ b/third_party/blink/web_tests/wpt_internal/route/parsing/invalid-route-rule-002.html
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
-<title>Invalid @route rule declarations, some parsed successfully, thanks to General Enclosed, but they should never match.</title>
+<title>Invalid @navigation rule declarations, some parsed successfully, thanks to General Enclosed, but they should never match.</title>
 <link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
-<link rel="help" href="https://wicg.github.io/declarative-partial-updates/css-route-matching/">
+<link rel="help" href="https://drafts.csswg.org/css-navigation-1/#at-navigation">
 <script type="routemap">
   {
     "routes": [
@@ -13,28 +13,28 @@
   }
 </script>
 <style>
-  @route incorrect { #canary { height: 1px; } }
-  @route (r) incorrect { #canary { height: 2px; } }
-  @route (r) (r) { #canary { height: 3px; } }
-  @route (r r) { #canary { height: 4px; } }
-  @route (r r) incorrect { #canary { height: 5px; } }
-  @route (at: r) incorrect { #canary { height: 6px; } }
-  @route (at: r r) { #canary { height: 7px; } }
-  @route (at: r r) incorrect { #canary { height: 8px; } }
-  @route (at: from:) { #canary { height: 9px; } }
-  @route (at: from:) incorrect { #canary { height: 10px; } }
-  @route (at: from: whatever) { #canary { height: 11px; } }
-  @route (at: from: whatever) incorrect { #canary { height: 12px; } }
-  @route incorrect (r) { #canary { height: 13px; } }
-  @route incorrect (at: r) incorrect { #canary { height: 14px; } }
-  @route (over:) incorrect { #canary { height: 15px; } }
-  @route (over: rainbow) incorrect { #canary { height: 16px; } }
-  @route (over:) { #canary { height: 17px; } }
-  @route (over: rainbow) { #canary { height: 18px; } }
-  @route incorrect (over: rainbow) incorrect { #canary { height: 19px; } }
+  @navigation incorrect { #canary { height: 1px; } }
+  @navigation (r) incorrect { #canary { height: 2px; } }
+  @navigation (r) (r) { #canary { height: 3px; } }
+  @navigation (r r) { #canary { height: 4px; } }
+  @navigation (r r) incorrect { #canary { height: 5px; } }
+  @navigation (at: r) incorrect { #canary { height: 6px; } }
+  @navigation (at: r r) { #canary { height: 7px; } }
+  @navigation (at: r r) incorrect { #canary { height: 8px; } }
+  @navigation (at: from:) { #canary { height: 9px; } }
+  @navigation (at: from:) incorrect { #canary { height: 10px; } }
+  @navigation (at: from: whatever) { #canary { height: 11px; } }
+  @navigation (at: from: whatever) incorrect { #canary { height: 12px; } }
+  @navigation incorrect (r) { #canary { height: 13px; } }
+  @navigation incorrect (at: r) incorrect { #canary { height: 14px; } }
+  @navigation (over:) incorrect { #canary { height: 15px; } }
+  @navigation (over: rainbow) incorrect { #canary { height: 16px; } }
+  @navigation (over:) { #canary { height: 17px; } }
+  @navigation (over: rainbow) { #canary { height: 18px; } }
+  @navigation incorrect (over: rainbow) incorrect { #canary { height: 19px; } }
 
   /* Sanity check. This one should match. */
-  @route (r) {
+  @navigation (r) {
     #sanityCheck { color: lime; }
   }
 </style>
@@ -45,7 +45,7 @@
 <script>
   test(()=> {
     assert_equals(canary.offsetHeight, 0);
-  }, "None of the @route rules should match");
+  }, "None of the @navigation rules should match");
   test(()=> {
     assert_equals(getComputedStyle(sanityCheck).color, "rgb(0, 255, 0)");
   }, "Sanity check");
diff --git a/third_party/blink/web_tests/wpt_internal/route/parsing/serialize-001.html b/third_party/blink/web_tests/wpt_internal/route/parsing/serialize-001.html
index e6161fa..a006548b6 100644
--- a/third_party/blink/web_tests/wpt_internal/route/parsing/serialize-001.html
+++ b/third_party/blink/web_tests/wpt_internal/route/parsing/serialize-001.html
@@ -1,30 +1,30 @@
 <!DOCTYPE html>
-<title>Serialization of @route</title>
+<title>Serialization of @navigation</title>
 <link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
-<link rel="help" href="https://wicg.github.io/declarative-partial-updates/css-route-matching/">
+<link rel="help" href="https://drafts.csswg.org/css-navigation-1/#at-navigation">
 <style class="test" id="sheet1">
-  @route (at: r66) { }
+  @navigation (at: r66) { }
 </style>
 <style class="test" id="sheet2">
-  @route (from: r66) or (to: blah) { }
+  @navigation (from: r66) or (to: blah) { }
 </style>
 <style class="test" id="sheet3">
-  @route ((from: r66) or (to: blah)) { }
+  @navigation ((from: r66) or (to: blah)) { }
 </style>
 <style class="test" id="sheet4">
-  @route (Attention! This is General Enclosed speaking) { }
+  @navigation (Attention! This is General Enclosed speaking) { }
 </style>
 <style class="test" id="sheet5">
-  @route (at: urlpattern("/r66/")) { }
+  @navigation (at: urlpattern("/r66/")) { }
 </style>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script>
-  let expectations = [ '@route (at: r66)',
-                       '@route (from: r66) or (to: blah)',
-                       '@route ((from: r66) or (to: blah))',
-                       '@route (Attention! This is General Enclosed speaking)',
-                       '@route (at: urlpattern("/r66/"))',
+  let expectations = [ '@navigation (at: r66)',
+                       '@navigation (from: r66) or (to: blah)',
+                       '@navigation ((from: r66) or (to: blah))',
+                       '@navigation (Attention! This is General Enclosed speaking)',
+                       '@navigation (at: urlpattern("/r66/"))',
                      ];
   document.querySelectorAll(".test").forEach((elm) => {
     test(()=> {
@@ -36,6 +36,6 @@
       const index = everything.indexOf("{");
       assert_not_equals(index, -1);
       assert_equals(everything.substring(0, index).trim(), expectation);
-    }, "Serialize route in " + elm.id);
+    }, "Serialize @navigation in " + elm.id);
   });
 </script>
diff --git a/third_party/blink/web_tests/wpt_internal/route/precommit-001.html b/third_party/blink/web_tests/wpt_internal/route/precommit-001.html
index 5374c831..78aa356 100644
--- a/third_party/blink/web_tests/wpt_internal/route/precommit-001.html
+++ b/third_party/blink/web_tests/wpt_internal/route/precommit-001.html
@@ -1,6 +1,6 @@
 <!DOCTYPE html>
-<title>@route(to) and @route(at) in onnavigate, precommitHandler, and handler()</title>
-<link rel="help" href="https://wicg.github.io/declarative-partial-updates/css-route-matching/#typedef-route-test">
+<title>@navigation(to) and @navigation(at) in onnavigate, precommitHandler, and handler()</title>
+<link rel="help" href="https://drafts.csswg.org/css-navigation-1/#at-navigation">
 <script type="routemap">
   {
     "routes":
@@ -10,12 +10,12 @@
   }
 </script>
 <style>
-  @route(to: a) {
+  @navigation(to: a) {
     body {
       background: #00ff00;
     }
   }
-  @route(at: a) {
+  @navigation(at: a) {
     body {
       color: #ff00ff;
     }
diff --git a/third_party/blink/web_tests/wpt_internal/route/route-condition-001.html b/third_party/blink/web_tests/wpt_internal/route/route-condition-001.html
index d731a855..66c120d8 100644
--- a/third_party/blink/web_tests/wpt_internal/route/route-condition-001.html
+++ b/third_party/blink/web_tests/wpt_internal/route/route-condition-001.html
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
-<title>@route rules with expressions</title>
+<title>@navigation rules with expressions</title>
 <link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
-<link rel="help" href="https://wicg.github.io/declarative-partial-updates/css-route-matching/#at-route">
+<link rel="help" href="https://drafts.csswg.org/css-navigation-1/#at-navigation">
 <!-- Assuming here that "route" is part of the test path. -->
 <script type="routemap">
   {
@@ -23,95 +23,95 @@
 </script>
 <style>
   /* These should match. */
-  @route (r1) or (r2) {
+  @navigation (r1) or (r2) {
     #match1 {
       height: 7px;
     }
   }
-  @route (r2) or (r1) {
+  @navigation (r2) or (r1) {
     #match2 {
       height: 7px;
     }
   }
-  @route (urlpattern("*route*")) and (r3) {
+  @navigation (urlpattern("*route*")) and (r3) {
     #match3 {
       height: 7px;
     }
   }
-  @route not (r2) {
+  @navigation not (r2) {
     #match4 {
       height: 7px;
     }
   }
-  @route not (to: r2) {
+  @navigation not (to: r2) {
     #match5 {
       height: 7px;
     }
   }
-  @route not ((to: r2) or (from: r2)) {
+  @navigation not ((to: r2) or (from: r2)) {
     #match6 {
       height: 7px;
     }
   }
-  @route (from: r1) or (at: r1) {
+  @navigation (from: r1) or (at: r1) {
     #match7 {
       height: 7px;
     }
   }
-  @route (at: r1) and (not (at: r2)) {
+  @navigation (at: r1) and (not (at: r2)) {
     #match8 {
       height: 7px;
     }
   }
-  @route ((r2) and (r1)) or (r3) {
+  @navigation ((r2) and (r1)) or (r3) {
     #match9 {
       height: 7px;
     }
   }
-  @route (not (r1)) or (r2) or (r3) {
+  @navigation (not (r1)) or (r2) or (r3) {
     #match10 {
       height: 7px;
     }
   }
-  @route (r1) and (not (r2)) and (r3) {
+  @navigation (r1) and (not (r2)) and (r3) {
     #match11 {
       height: 7px;
     }
   }
-  @route (r1) and (not (r2)) and (r3) and (not (from: r1)) and (not (to: r1)) and (not (from: r2)) and (not (to: r2)) and (not (from: r3)) and (not (to: r3)) {
+  @navigation (r1) and (not (r2)) and (r3) and (not (from: r1)) and (not (to: r1)) and (not (from: r2)) and (not (to: r2)) and (not (from: r3)) and (not (to: r3)) {
     #match12 {
       height: 7px;
     }
   }
 
-  @route ((r1) and (r2) and (r3) and (from: r1) and (to: r1) and (from: r2) and (to: r2) and (from: r3) and (to: r3)) or (at: urlpattern("*route*")) {
+  @navigation ((r1) and (r2) and (r3) and (from: r1) and (to: r1) and (from: r2) and (to: r2) and (from: r3) and (to: r3)) or (at: urlpattern("*route*")) {
     #match13 {
       height: 7px;
     }
   }
 
   /* These should not match. */
-  @route (r2) and (r1) {
+  @navigation (r2) and (r1) {
     #nomatch1 {
       height: 13px;
     }
   }
-  @route not (at: r1) {
+  @navigation not (at: r1) {
     #nomatch2 {
       height: 13px;
     }
   }
-  @route (from: r1) {
+  @navigation (from: r1) {
     #nomatch3 {
       height: 13px;
     }
   }
-  @route (to: r1) {
+  @navigation (to: r1) {
     #nomatch4 {
       height: 13px;
     }
   }
-  @route (r1) and (r2) and (r3) {
+  @navigation (r1) and (r2) and (r3) {
     #nomatch5 {
       height: 13px;
     }
diff --git a/third_party/blink/web_tests/wpt_internal/route/route-condition-002.html b/third_party/blink/web_tests/wpt_internal/route/route-condition-002.html
index cc03d2e..b7509681 100644
--- a/third_party/blink/web_tests/wpt_internal/route/route-condition-002.html
+++ b/third_party/blink/web_tests/wpt_internal/route/route-condition-002.html
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
-<title>if(route)</title>
+<title>if(navigation)</title>
 <link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
-<link rel="help" href="https://wicg.github.io/declarative-partial-updates/css-route-matching/#route-if-function">
+<link rel="help" href="https://drafts.csswg.org/css-navigation-1/#navigation-if-function">
 <!-- Assuming here that "route" is part of the test path. -->
 <script type="routemap">
   {
@@ -22,16 +22,16 @@
   }
 </script>
 <div class="tests">
-  <div id="elm1" style="background:if(route(r1):lime; else:red);"></div>
-  <div id="elm2" style="background:if(route(r2):red; else:lime);"></div>
-  <div id="elm3" style="background:if(not route(r3):red; else:lime);"></div>
-  <div id="elm4" style="background:if(route(at:r1):lime; else:red);"></div>
-  <div id="elm5" style="background:if(route(not (at:r2)):lime; else:red);"></div>
-  <div id="elm6" style="background:if(route((r2) or (r3)):lime; else:red);"></div>
-  <div id="elm7" style="background:if(route(urlpattern('*route*')):lime; else:red);"></div>
-  <div id="elm8" style="background:if(route(urlpattern('should-not-match')):red; else:lime);"></div>
-  <div id="elm9" style="background:if(route((urlpattern('should-not-match')) or (r3)):lime; else:red);"></div>
-  <div id="elm10" style="background:if(route((urlpattern('should-not-match')) or (urlpattern('*route*'))):lime; else:red);"></div>
+  <div id="elm1" style="background:if(navigation(r1):lime; else:red);"></div>
+  <div id="elm2" style="background:if(navigation(r2):red; else:lime);"></div>
+  <div id="elm3" style="background:if(not navigation(r3):red; else:lime);"></div>
+  <div id="elm4" style="background:if(navigation(at:r1):lime; else:red);"></div>
+  <div id="elm5" style="background:if(navigation(not (at:r2)):lime; else:red);"></div>
+  <div id="elm6" style="background:if(navigation((r2) or (r3)):lime; else:red);"></div>
+  <div id="elm7" style="background:if(navigation(urlpattern('*route*')):lime; else:red);"></div>
+  <div id="elm8" style="background:if(navigation(urlpattern('should-not-match')):red; else:lime);"></div>
+  <div id="elm9" style="background:if(navigation((urlpattern('should-not-match')) or (r3)):lime; else:red);"></div>
+  <div id="elm10" style="background:if(navigation((urlpattern('should-not-match')) or (urlpattern('*route*'))):lime; else:red);"></div>
 </div>
 
 <script src="/resources/testharness.js"></script>
diff --git a/third_party/blink/web_tests/wpt_internal/route/route-match-pseudo-001.html b/third_party/blink/web_tests/wpt_internal/route/route-match-pseudo-001.html
index e5caa67a..a47dcd0 100644
--- a/third_party/blink/web_tests/wpt_internal/route/route-match-pseudo-001.html
+++ b/third_party/blink/web_tests/wpt_internal/route/route-match-pseudo-001.html
@@ -1,11 +1,11 @@
 <!DOCTYPE html>
-<title>:route-match(urlpattern()) on link</title>
+<title>:link-to(urlpattern()) on link</title>
 <link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
 <link rel="help" href="https://issues.chromium.org/issues/436805487">
 <link rel="match" href="../css/reference/ref-filled-green-100px-square.xht">
 <p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
 <style>
-  :route-match(urlpattern("*route/other.html")) {
+  :link-to(urlpattern("*route/other.html")) {
     background: green;
   }
   a {
diff --git a/third_party/blink/web_tests/wpt_internal/route/route-match-pseudo-002.html b/third_party/blink/web_tests/wpt_internal/route/route-match-pseudo-002.html
index 149deb1..7573acf 100644
--- a/third_party/blink/web_tests/wpt_internal/route/route-match-pseudo-002.html
+++ b/third_party/blink/web_tests/wpt_internal/route/route-match-pseudo-002.html
@@ -1,11 +1,11 @@
 <!DOCTYPE html>
-<title>:route-match(name) on link</title>
+<title>:link-to(name) on link</title>
 <link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
 <link rel="help" href="https://issues.chromium.org/issues/436805487">
 <script type="routemap">
   {
     "routes": [
-      { "name": "ruth",
+      { "name": "r1",
         "pattern": { "protocol": "http*" }
       }
     ]
@@ -14,7 +14,7 @@
 <link rel="match" href="../css/reference/ref-filled-green-100px-square.xht">
 <p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
 <style>
-  :route-match(ruth) {
+  :link-to(r1) {
     background: green;
   }
   a {
diff --git a/third_party/blink/web_tests/wpt_internal/route/route-match-pseudo-003.html b/third_party/blink/web_tests/wpt_internal/route/route-match-pseudo-003.html
index 87944c11..af4580d3 100644
--- a/third_party/blink/web_tests/wpt_internal/route/route-match-pseudo-003.html
+++ b/third_party/blink/web_tests/wpt_internal/route/route-match-pseudo-003.html
@@ -1,11 +1,11 @@
 <!DOCTYPE html>
-<title>:route-match(urlpattern()) on link with changing href</title>
+<title>:link-to(urlpattern()) on link with changing href</title>
 <link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
 <link rel="help" href="https://issues.chromium.org/issues/436805487">
 <link rel="match" href="../css/reference/ref-filled-green-100px-square.xht">
 <p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
 <style>
-  :route-match(urlpattern("*route/other.html")) {
+  :link-to(urlpattern("*route/other.html")) {
     background: green;
   }
   a {
diff --git a/third_party/blink/web_tests/wpt_internal/route/route-match-pseudo-004.html b/third_party/blink/web_tests/wpt_internal/route/route-match-pseudo-004.html
index 8ba655c..f99f318 100644
--- a/third_party/blink/web_tests/wpt_internal/route/route-match-pseudo-004.html
+++ b/third_party/blink/web_tests/wpt_internal/route/route-match-pseudo-004.html
@@ -1,9 +1,9 @@
 <!DOCTYPE html>
-<title>:route-match(urlpattern()) serialization</title>
+<title>:link-to(urlpattern()) serialization</title>
 <link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
 <link rel="help" href="https://issues.chromium.org/issues/436805487">
 <style id="style">
-  :route-match(  urlpattern(  "*route/other.html"  )  )  {   }
+  :link-to(  urlpattern(  "*route/other.html"  )  )  {   }
 </style>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
@@ -11,6 +11,6 @@
   test(()=> {
     assert_equals(style.sheet.rules.length, 1);
     assert_equals(style.sheet.rules[0].cssText,
-                  ':route-match(urlpattern("*route/other.html")) { }');
+                  ':link-to(urlpattern("*route/other.html")) { }');
   }, "Serialize");
 </script>
diff --git a/third_party/blink/web_tests/wpt_internal/route/routemap-001.html b/third_party/blink/web_tests/wpt_internal/route/routemap-001.html
index 6a343ff5..eb43194 100644
--- a/third_party/blink/web_tests/wpt_internal/route/routemap-001.html
+++ b/third_party/blink/web_tests/wpt_internal/route/routemap-001.html
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <title>Simple routemap with one matching and one non-matching route</title>
 <link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
-<link rel="help" href="https://github.com/WICG/declarative-partial-updates?tab=readme-ov-file#part-2-route-matching">
+<link rel="help" href="https://drafts.csswg.org/css-navigation-1/#at-navigation">
 <!-- Assuming here that "route" is part of the test path. -->
 <script type="routemap">
   {
@@ -21,7 +21,7 @@
   #target {
     display: none;
   }
-  @route (r1) {
+  @navigation (r1) {
     #target {
       display: block;
       width: 100px;
@@ -29,7 +29,7 @@
       background: green;
     }
   }
-  @route (r2) {
+  @navigation (r2) {
     #target {
       border: 10px solid red;
     }
diff --git a/third_party/blink/web_tests/wpt_internal/route/routemap-002.html b/third_party/blink/web_tests/wpt_internal/route/routemap-002.html
index 88c5733..55e3231 100644
--- a/third_party/blink/web_tests/wpt_internal/route/routemap-002.html
+++ b/third_party/blink/web_tests/wpt_internal/route/routemap-002.html
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <title>Set new URL, then go back</title>
 <link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
-<link rel="help" href="https://github.com/WICG/declarative-partial-updates?tab=readme-ov-file#part-2-route-matching">
+<link rel="help" href="https://drafts.csswg.org/css-navigation-1/#at-navigation">
 <script type="routemap">
   {
     "routes": [
@@ -22,13 +22,13 @@
     height: 7px;
   }
 
-  @route (r1) {
+  @navigation (r1) {
     #target {
       width: 100px;
     }
   }
 
-  @route (r2) {
+  @navigation (r2) {
     #target {
       height: 100px;
     }
diff --git a/third_party/blink/web_tests/wpt_internal/route/routemap-003.html b/third_party/blink/web_tests/wpt_internal/route/routemap-003.html
index b27fa91b..5a44b82 100644
--- a/third_party/blink/web_tests/wpt_internal/route/routemap-003.html
+++ b/third_party/blink/web_tests/wpt_internal/route/routemap-003.html
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
-<title>Set new URL, add @route rule</title>
+<title>Set new URL, add @navigation rule</title>
 <link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
-<link rel="help" href="https://github.com/WICG/declarative-partial-updates?tab=readme-ov-file#part-2-route-matching">
+<link rel="help" href="https://drafts.csswg.org/css-navigation-1/#at-navigation">
 <script type="routemap">
   {
     "routes": [
@@ -27,21 +27,21 @@
 
   promise_test(async t => {
     history.pushState(0, null, "match-later");
-    sheetElm.sheet.insertRule("@route (r1) { #target { width: 100px; } }", 1);
+    sheetElm.sheet.insertRule("@navigation (r1) { #target { width: 100px; } }", 1);
     window.onpageshow = t.step_func_done(() => {
       assert_equals(getComputedStyle(target).width, "100px");
     });
-  }, "New URL, add route rule");
+  }, "New URL, add navigation rule");
 
   promise_test(async t => {
     sheetElm.sheet.deleteRule(1);
     document.body.offsetTop;
     assert_equals(getComputedStyle(target).width, "7px");
-  }, "Remove route rule");
+  }, "Remove navigation rule");
 
   promise_test(async t => {
-    sheetElm.sheet.insertRule("@route (r1) { #target { width: 200px; } }", 1);
+    sheetElm.sheet.insertRule("@navigation (r1) { #target { width: 200px; } }", 1);
     document.body.offsetTop;
     assert_equals(getComputedStyle(target).width, "200px");
-  }, "Add route rule again");
+  }, "Add navigation rule again");
 </script>
diff --git a/third_party/blink/web_tests/wpt_internal/route/routemap-004.html b/third_party/blink/web_tests/wpt_internal/route/routemap-004.html
index be169b0..8996917 100644
--- a/third_party/blink/web_tests/wpt_internal/route/routemap-004.html
+++ b/third_party/blink/web_tests/wpt_internal/route/routemap-004.html
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
-<title>Test @route(to:foo) and @route(from:foo)</title>
+<title>Test @navigation(to:foo) and @navigation(from:foo)</title>
 <link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
-<link rel="help" href="https://github.com/WICG/declarative-partial-updates?tab=readme-ov-file#part-2-route-matching">
+<link rel="help" href="https://drafts.csswg.org/css-navigation-1/#at-navigation">
 <!-- Assuming here that "route" is part of the test path but no other parts of the URL. -->
 <script type="routemap">
   {
@@ -31,36 +31,36 @@
     height: 7px;
   }
 
-  @route (at: r1) {
+  @navigation (at: r1) {
     #target { width: 100px; }
   }
 
-  @route (from: r1) {
+  @navigation (from: r1) {
     #target { padding-top: 20px; }
   }
 
-  @route (to: r2) {
+  @navigation (to: r2) {
     #target { padding-bottom: 30px; }
   }
 
-  @route (at: r2) {
+  @navigation (at: r2) {
     #target { height: 90px; }
   }
 
-  @route (from: r2) {
+  @navigation (from: r2) {
     #target { margin-left: 70px; }
   }
 
-  @route (to: r4) {
+  @navigation (to: r4) {
     #target { margin-right: 80px; }
   }
 
   /* These should never match in the test. */
-  @route (to: r1) {
+  @navigation (to: r1) {
     #target { padding-left: 666px; }
   }
 
-  @route (from: r4) {
+  @navigation (from: r4) {
     #target { padding-right: 666px; }
   }
 </style>
diff --git a/third_party/blink/web_tests/wpt_internal/route/routemap-005.html b/third_party/blink/web_tests/wpt_internal/route/routemap-005.html
index 756d29b..3b41c31 100644
--- a/third_party/blink/web_tests/wpt_internal/route/routemap-005.html
+++ b/third_party/blink/web_tests/wpt_internal/route/routemap-005.html
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
-<title>@route with urlpattern()</title>
+<title>@navigation with urlpattern()</title>
 <link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
-<link rel="help" href="https://wicg.github.io/declarative-partial-updates/css-route-matching/#urlpattern-function">
+<link rel="help" href="https://drafts.csswg.org/css-navigation-1/#at-navigation">
 <style>
   #target {
     width: 7px;
@@ -9,36 +9,36 @@
   }
 
   /* Assuming here that "route" is part of the test path but no other parts of the URL. */
-  @route (at: urlpattern("*route*")) {
+  @navigation (at: urlpattern("*route*")) {
     #target { width: 100px; }
   }
 
-  @route (from: urlpattern("*route*")) {
+  @navigation (from: urlpattern("*route*")) {
     #target { padding-top: 20px; }
   }
 
-  @route (to: urlpattern("match-later-r2")) {
+  @navigation (to: urlpattern("match-later-r2")) {
     #target { padding-bottom: 30px; }
   }
 
-  @route (at: urlpattern("match-later-r2")) {
+  @navigation (at: urlpattern("match-later-r2")) {
     #target { height: 90px; }
   }
 
-  @route (from: urlpattern("match-later-r2")) {
+  @navigation (from: urlpattern("match-later-r2")) {
     #target { margin-left: 70px; }
   }
 
-  @route (to: urlpattern("match-later-r4")) {
+  @navigation (to: urlpattern("match-later-r4")) {
     #target { margin-right: 80px; }
   }
 
   /* These should never match in the test. */
-  @route (to: urlpattern("*route*")) {
+  @navigation (to: urlpattern("*route*")) {
     #target { padding-left: 666px; }
   }
 
-  @route (from: urlpattern("match-later-r4")) {
+  @navigation (from: urlpattern("match-later-r4")) {
     #target { padding-right: 666px; }
   }
 </style>
diff --git a/third_party/boringssl/src b/third_party/boringssl/src
index b94d71f..4faf6b1 160000
--- a/third_party/boringssl/src
+++ b/third_party/boringssl/src
@@ -1 +1 @@
-Subproject commit b94d71f87ff943a617d77f3ff029f9a01a1ec6bc
+Subproject commit 4faf6b1e463c1f9e8cb596b1bfcd8b27454bea9a
diff --git a/third_party/catapult b/third_party/catapult
index 4646f4c..a202c86 160000
--- a/third_party/catapult
+++ b/third_party/catapult
@@ -1 +1 @@
-Subproject commit 4646f4c0fa1f6daa506d06153beae34eb7d87265
+Subproject commit a202c86635d505fa893d73bad1e220a66bb644e6
diff --git a/third_party/crossbench b/third_party/crossbench
index 77240be..11e5da1 160000
--- a/third_party/crossbench
+++ b/third_party/crossbench
@@ -1 +1 @@
-Subproject commit 77240be1ccd1dc99b73be332223a8856641a2073
+Subproject commit 11e5da1e630acf5dc2c8185eca8345645da4d2c8
diff --git a/third_party/dawn b/third_party/dawn
index 0223916..518caee 160000
--- a/third_party/dawn
+++ b/third_party/dawn
@@ -1 +1 @@
-Subproject commit 0223916e3a572e7c264d0b8da5f077556e660871
+Subproject commit 518caee86914cb4346f7f82a90c3ab99b7f84532
diff --git a/third_party/depot_tools b/third_party/depot_tools
index 2e88a3f..db99cc4 160000
--- a/third_party/depot_tools
+++ b/third_party/depot_tools
@@ -1 +1 @@
-Subproject commit 2e88a3f08bd8c4a0014eae82729beca935f7f188
+Subproject commit db99cc40f562179c59f12b10e8af2d8fe2770ad2
diff --git a/third_party/devtools-frontend/src b/third_party/devtools-frontend/src
index d962481a..7d26bdd 160000
--- a/third_party/devtools-frontend/src
+++ b/third_party/devtools-frontend/src
@@ -1 +1 @@
-Subproject commit d962481a57f0c1a6788204524f8ddc6729e5b6be
+Subproject commit 7d26bddc0d06cd6a294663fd23d0918e6ad33475
diff --git a/third_party/eigen3/README.chromium b/third_party/eigen3/README.chromium
index fdbf6a3..20e3107 100644
--- a/third_party/eigen3/README.chromium
+++ b/third_party/eigen3/README.chromium
@@ -1,9 +1,9 @@
 Name: Eigen
 Short Name: eigen3
 URL: https://gitlab.com/libeigen/eigen
-Version: dcbaf2d608f306450f1e74949eb87e9a22a7ef4b
-Date: 2025-11-17
-Revision: dcbaf2d608f306450f1e74949eb87e9a22a7ef4b
+Version: 49623d0c4e1af3c680845191948d10f6d3e92f8a
+Date: 2025-11-24
+Revision: 49623d0c4e1af3c680845191948d10f6d3e92f8a
 Update Mechanism: Manual
 License: MPL-2.0
 License File: LICENSE
diff --git a/third_party/eigen3/src b/third_party/eigen3/src
index dcbaf2d..49623d0 160000
--- a/third_party/eigen3/src
+++ b/third_party/eigen3/src
@@ -1 +1 @@
-Subproject commit dcbaf2d608f306450f1e74949eb87e9a22a7ef4b
+Subproject commit 49623d0c4e1af3c680845191948d10f6d3e92f8a
diff --git a/third_party/glslang/src b/third_party/glslang/src
index 7a47e25..b5782e52 160000
--- a/third_party/glslang/src
+++ b/third_party/glslang/src
@@ -1 +1 @@
-Subproject commit 7a47e2531cb334982b2a2dd8513dca0a3de4373d
+Subproject commit b5782e52ee2f7b3e40bb9c80d15b47016e008bc9
diff --git a/third_party/libunwind/src b/third_party/libunwind/src
index 7ff36f1..c65639b 160000
--- a/third_party/libunwind/src
+++ b/third_party/libunwind/src
@@ -1 +1 @@
-Subproject commit 7ff36f1e358ffb63e25f27a1b5e8e0c52e15c49c
+Subproject commit c65639bf792928e0d38aed822dc34d3e72066a6c
diff --git a/third_party/llvm-libc/src b/third_party/llvm-libc/src
index f950018..fe97633 160000
--- a/third_party/llvm-libc/src
+++ b/third_party/llvm-libc/src
@@ -1 +1 @@
-Subproject commit f950018bf8a84246c899b9ac360a271064577d42
+Subproject commit fe97633934c21742a74962d52c17b3bcba7ad824
diff --git a/third_party/metrics_proto/README.chromium b/third_party/metrics_proto/README.chromium
index 6142d65..d34c963 100644
--- a/third_party/metrics_proto/README.chromium
+++ b/third_party/metrics_proto/README.chromium
@@ -1,8 +1,8 @@
 Name: Metrics Protos
 Short Name: metrics_proto
 URL: This is the canonical public repository
-Version: 837116011
-Date: 2025-11-26
+Version: 838771809
+Date: 2025-12-01
 License: BSD-3-Clause
 License File: LICENSE
 Shipped: yes
diff --git a/third_party/metrics_proto/omnibox_event.proto b/third_party/metrics_proto/omnibox_event.proto
index aeb070a..699ec81f 100644
--- a/third_party/metrics_proto/omnibox_event.proto
+++ b/third_party/metrics_proto/omnibox_event.proto
@@ -238,6 +238,9 @@
     // The omnibox composebox on Web.
     OTHER_OMNIBOX_COMPOSEBOX = 35;
 
+    // Co-browsing composebox
+    CO_BROWSING_COMPOSEBOX = 36;
+
     // When adding new classifications, please update the `OmniboxPageContext`
     // variants listed in chromium's
     // tools/metrics/histograms/metadata/omnibox/histograms.xml and also
diff --git a/third_party/metrics_proto/private_metrics/private_metrics.proto b/third_party/metrics_proto/private_metrics/private_metrics.proto
index 6c0cfb2..cbff0e4 100644
--- a/third_party/metrics_proto/private_metrics/private_metrics.proto
+++ b/third_party/metrics_proto/private_metrics/private_metrics.proto
@@ -33,7 +33,7 @@
 // of user experience without compromising privacy. This proto will be stored
 // in Sawmill post upload to the UMA frontend, and will be subsequently
 // processed up by the k-anonymity pipeline in a trusted execution environment.
-// Next tag: 5
+// Next tag: 7
 message EncryptedPrivateMetricReport {
   // HPKE-encrypted and proto-serialized report, with associated data.
   // Associated data is the unencrypted non-sensitive header portion in
@@ -68,6 +68,16 @@
   }
 
   optional ReportType report_type = 4;
+
+  // The symmetric key used to encrypt the ciphertext, encrypted using HPKE.
+  // The key is encoded as a COSE_Key struct (RFC 9052); at least the
+  // following algorithms should be supported:
+  //   -65538: AEAD_AES_128_GCM_SIV (fixed nonce)
+  optional bytes encrypted_symmetric_key = 5;
+
+  // The ephemeral Diffie-Hellman key needed to derive the symmetric key used
+  // to encrypt `encrypted_secret_key`.
+  optional bytes encapsulated_public_key = 6;
 }
 
 // A report containing unencrypted private metrics data. The unencrypted report
diff --git a/third_party/omnibox_proto/README.chromium b/third_party/omnibox_proto/README.chromium
index a127032..73f67e8 100644
--- a/third_party/omnibox_proto/README.chromium
+++ b/third_party/omnibox_proto/README.chromium
@@ -1,8 +1,8 @@
 Name: Omnibox Protos
 Short Name: omnibox_proto
 URL: This is the canonical public repository
-Version: 834845016
-Date: 2025-11-20
+Version: 838772255
+Date: 2025-12-01
 License: BSD-3-Clause
 License File: LICENSE
 Shipped: yes
diff --git a/third_party/omnibox_proto/chrome_aim_entry_point.proto b/third_party/omnibox_proto/chrome_aim_entry_point.proto
index 0d21cb3..258768f6 100644
--- a/third_party/omnibox_proto/chrome_aim_entry_point.proto
+++ b/third_party/omnibox_proto/chrome_aim_entry_point.proto
@@ -46,4 +46,10 @@
 
   // AIM is entered from the composebox in Omnibox on Web.
   DESKTOP_CHROME_OTHER_OMNIBOX_COMPOSEBOX_ENTRY_POINT = 111;
+
+  // AIM is entered from the co-browsing composebox.
+  DESKTOP_CHROME_CO_BROWSING_COMPOSEBOX_ENTRY_POINT = 116;
+
+  // AIM is entered from the lens contextual searchbox.
+  DESKTOP_CHROME_LENS_CONTEXTUAL_SEARCHBOX_ENTRY_POINT = 117;
 }
diff --git a/third_party/openscreen/src b/third_party/openscreen/src
index 7348773..2f501fd 160000
--- a/third_party/openscreen/src
+++ b/third_party/openscreen/src
@@ -1 +1 @@
-Subproject commit 734877394201dcfcc786b3c8ea057b7607a56993
+Subproject commit 2f501fd6f616e647b8f3f4af2e8decfc91151a49
diff --git a/third_party/perfetto b/third_party/perfetto
index a55ca8a..f67fc0e 160000
--- a/third_party/perfetto
+++ b/third_party/perfetto
@@ -1 +1 @@
-Subproject commit a55ca8aac81bd5b9b52ed7e66584949b4a4a3d8d
+Subproject commit f67fc0ed0a168a0d80b35d07215c8fed2735d7d3
diff --git a/third_party/ruy/README.chromium b/third_party/ruy/README.chromium
index 0aa6d6f..9d26007 100644
--- a/third_party/ruy/README.chromium
+++ b/third_party/ruy/README.chromium
@@ -2,8 +2,8 @@
 Short Name: ruy
 URL: https://github.com/google/ruy
 Version: N/A
-Revision: 9940fbf1e0c0863907e77e0600b99bb3e2bc2b9f
-Date: 2025-07-22
+Revision: 1e6e50872655a73b5250f954d7b9da9a87292fd3
+Date: 2025-11-24
 Update Mechanism: Manual
 License: Apache-2.0
 License File: LICENSE
diff --git a/third_party/ruy/src b/third_party/ruy/src
index 9940fbf..1e6e508 160000
--- a/third_party/ruy/src
+++ b/third_party/ruy/src
@@ -1 +1 @@
-Subproject commit 9940fbf1e0c0863907e77e0600b99bb3e2bc2b9f
+Subproject commit 1e6e50872655a73b5250f954d7b9da9a87292fd3
diff --git a/third_party/search_engines_data/resources_internal b/third_party/search_engines_data/resources_internal
index d796f63..238cd4e 160000
--- a/third_party/search_engines_data/resources_internal
+++ b/third_party/search_engines_data/resources_internal
@@ -1 +1 @@
-Subproject commit d796f6348f95a4ff08a8efe02f11e55c2dd257ec
+Subproject commit 238cd4ef4ac82a9f819fa12da03ce22745eee391
diff --git a/third_party/skia b/third_party/skia
index a0de596..4371ed0 160000
--- a/third_party/skia
+++ b/third_party/skia
@@ -1 +1 @@
-Subproject commit a0de596ec9d554be6b39847634a2287dde4d7fc0
+Subproject commit 4371ed0ce49e08ac80bca3f997b72a11f4557a21
diff --git a/third_party/swiftshader b/third_party/swiftshader
index 498a6f7..04fbb7d 160000
--- a/third_party/swiftshader
+++ b/third_party/swiftshader
@@ -1 +1 @@
-Subproject commit 498a6f760dea6cdda0302406c49b8fbb09b9af92
+Subproject commit 04fbb7daf5a53689e067190e7ef1047c5d49e292
diff --git a/third_party/tflite/README.chromium b/third_party/tflite/README.chromium
index 3fe26c1..caa982e 100644
--- a/third_party/tflite/README.chromium
+++ b/third_party/tflite/README.chromium
@@ -2,8 +2,8 @@
 Short Name: tflite
 URL: https://github.com/tensorflow/tensorflow
 Version: N/A
-Revision: 581d1a6689062926c6459ad3c7807278220e24c9
-Date: 2025-11-17
+Revision: 247b0cf254fbbf3d326feed3820ec24503a353a5
+Date: 2025-11-24
 Update Mechanism: Manual
 License: Caffe, Apache-2.0
 License File: LICENSE
diff --git a/third_party/tflite/src b/third_party/tflite/src
index 581d1a6..247b0cf 160000
--- a/third_party/tflite/src
+++ b/third_party/tflite/src
@@ -1 +1 @@
-Subproject commit 581d1a6689062926c6459ad3c7807278220e24c9
+Subproject commit 247b0cf254fbbf3d326feed3820ec24503a353a5
diff --git a/third_party/vulkan-deps b/third_party/vulkan-deps
index bb0f420..d96e068 160000
--- a/third_party/vulkan-deps
+++ b/third_party/vulkan-deps
@@ -1 +1 @@
-Subproject commit bb0f420085e991834149a0d40e93136118d68dde
+Subproject commit d96e0682f2c04a2e4b1d847c125d561a1a74e763
diff --git a/third_party/vulkan-loader/src b/third_party/vulkan-loader/src
index 052ac24..e042a3a 160000
--- a/third_party/vulkan-loader/src
+++ b/third_party/vulkan-loader/src
@@ -1 +1 @@
-Subproject commit 052ac24611eced7b0ca62cc5cca2eeeb2051fa28
+Subproject commit e042a3a16bdf37e8c9d61b95b7a5933bccef0f45
diff --git a/third_party/vulkan-tools/src b/third_party/vulkan-tools/src
index 2a3347d..48b5d24 160000
--- a/third_party/vulkan-tools/src
+++ b/third_party/vulkan-tools/src
@@ -1 +1 @@
-Subproject commit 2a3347d5e74d359e3ecb8e229917f3335bfa2dfa
+Subproject commit 48b5d246b2d0b1a41ee7ea1b69525ae7bb38a2ae
diff --git a/third_party/vulkan-validation-layers/src b/third_party/vulkan-validation-layers/src
index bf463de..e1c92ec 160000
--- a/third_party/vulkan-validation-layers/src
+++ b/third_party/vulkan-validation-layers/src
@@ -1 +1 @@
-Subproject commit bf463deb91577c8ccf2031407a94cf36e66d6096
+Subproject commit e1c92ec6cd2be77ef1fc2a0a0dabd432a1bccca7
diff --git a/third_party/webgpu-cts/src b/third_party/webgpu-cts/src
index ef27c0b..e7cad01 160000
--- a/third_party/webgpu-cts/src
+++ b/third_party/webgpu-cts/src
@@ -1 +1 @@
-Subproject commit ef27c0b88b802ca64677e2c0ed60378951ac6c42
+Subproject commit e7cad0143f136c69b345024d0a60e0d859dd7503
diff --git a/third_party/webgpu-cts/ts_sources.txt b/third_party/webgpu-cts/ts_sources.txt
index e0e1b50..f18e29d 100644
--- a/third_party/webgpu-cts/ts_sources.txt
+++ b/third_party/webgpu-cts/ts_sources.txt
@@ -148,6 +148,8 @@
 src/unittests/test_group.spec.ts
 src/unittests/test_query.spec.ts
 src/unittests/texture_ok.spec.ts
+src/webgpu/shader/validation/uniformity/snippet.ts
+src/unittests/uniformity_snippet.spec.ts
 src/webgpu/error_test.ts
 src/webgpu/examples.spec.ts
 src/webgpu/inter_stage.ts
diff --git a/third_party/xnnpack/BUILD.gn b/third_party/xnnpack/BUILD.gn
index 6dcf579c..604bc759 100644
--- a/third_party/xnnpack/BUILD.gn
+++ b/third_party/xnnpack/BUILD.gn
@@ -1191,6 +1191,7 @@
       ":f32-vtanh_arm64",
       ":f32-vunary_arm64",
       ":operators_arm64",
+      ":pf16-f16-f16-igemm_arch=armv8.2-a+sve+sve2",
       ":pf16-gemm_arch=armv8.2-a+sve+sve2",
       ":pf32-gemm_arch=armv8.2-a+sve+sve2",
       ":pqs8-f32-qc8w-igemm_arch=armv8.2-a+sve+sve2",
@@ -1414,6 +1415,7 @@
       ":f32-vtanh_arm64_standalone",
       ":f32-vunary_arm64_standalone",
       ":operators_arm64_standalone",
+      ":pf16-f16-f16-igemm_arch=armv8.2-a+sve+sve2_standalone",
       ":pf16-gemm_arch=armv8.2-a+sve+sve2_standalone",
       ":pf32-gemm_arch=armv8.2-a+sve+sve2_standalone",
       ":pqs8-f32-qc8w-igemm_arch=armv8.2-a+sve+sve2_standalone",
@@ -1926,6 +1928,7 @@
       "src/src/sanitizers.c",
       "src/src/subgraph.c",
       "src/src/tensor.c",
+      "src/src/xnnpack/fingerprint_check.c",
     ]
 
     deps = xnnpack_deps + [
@@ -1973,6 +1976,7 @@
       "src/src/sanitizers.c",
       "src/src/subgraph.c",
       "src/src/tensor.c",
+      "src/src/xnnpack/fingerprint_check.c",
     ]
 
     deps = xnnpack_standalone_deps + [
@@ -16768,6 +16772,8 @@
         "src/src/operators/convolution-nhwc.c",
         "src/src/operators/deconvolution-nhwc.c",
         "src/src/operators/dynamic-fully-connected-nc.c",
+        "src/src/operators/fingerprint_cache.c",
+        "src/src/operators/fingerprint_id.c",
         "src/src/operators/fully-connected-nc.c",
         "src/src/operators/max-pooling-nhwc.c",
         "src/src/operators/pack-lh.c",
@@ -16814,6 +16820,8 @@
         "src/src/operators/convolution-nhwc.c",
         "src/src/operators/deconvolution-nhwc.c",
         "src/src/operators/dynamic-fully-connected-nc.c",
+        "src/src/operators/fingerprint_cache.c",
+        "src/src/operators/fingerprint_id.c",
         "src/src/operators/fully-connected-nc.c",
         "src/src/operators/max-pooling-nhwc.c",
         "src/src/operators/pack-lh.c",
@@ -40962,6 +40970,8 @@
         "src/src/operators/convolution-nhwc.c",
         "src/src/operators/deconvolution-nhwc.c",
         "src/src/operators/dynamic-fully-connected-nc.c",
+        "src/src/operators/fingerprint_cache.c",
+        "src/src/operators/fingerprint_id.c",
         "src/src/operators/fully-connected-nc.c",
         "src/src/operators/max-pooling-nhwc.c",
         "src/src/operators/pack-lh.c",
@@ -41008,6 +41018,8 @@
         "src/src/operators/convolution-nhwc.c",
         "src/src/operators/deconvolution-nhwc.c",
         "src/src/operators/dynamic-fully-connected-nc.c",
+        "src/src/operators/fingerprint_cache.c",
+        "src/src/operators/fingerprint_id.c",
         "src/src/operators/fully-connected-nc.c",
         "src/src/operators/max-pooling-nhwc.c",
         "src/src/operators/pack-lh.c",
@@ -41043,6 +41055,61 @@
   }
 
   if (build_with_chromium) {
+    source_set("pf16-f16-f16-igemm_arch=armv8.2-a+sve+sve2") {
+      cflags = [ "-march=armv8.2-a+sve+sve2" ]
+
+      sources = [
+        "src/include/xnnpack.h",
+        "src/src/pf16-f16-f16-igemm/pf16-f16-f16-igemm-32x32c2-minmax-neonsme2.c",
+      ]
+
+      configs -= [ "//build/config/compiler:chromium_code" ]
+      configs += [ "//build/config/compiler:no_chromium_code" ]
+      configs += [ "//build/config/sanitizers:cfi_icall_generalize_pointers" ]
+      configs += [ ":xnnpack_private_config" ]
+
+      deps = [
+        "//third_party/cpuinfo",
+        "//third_party/fp16",
+        "//third_party/fxdiv",
+        "//third_party/pthreadpool",
+      ]
+
+      public_configs = [ ":xnnpack_public_config" ]
+    }
+  }
+
+  # This is a target that cannot depend on //base.
+  if (build_with_internal_optimization_guide) {
+    source_set("pf16-f16-f16-igemm_arch=armv8.2-a+sve+sve2_standalone") {
+      cflags = [ "-march=armv8.2-a+sve+sve2" ]
+
+      sources = [
+        "src/include/xnnpack.h",
+        "src/src/pf16-f16-f16-igemm/pf16-f16-f16-igemm-32x32c2-minmax-neonsme2.c",
+      ]
+
+      configs -= [ "//build/config/compiler:chromium_code" ]
+      configs += [ "//build/config/compiler:no_chromium_code" ]
+      configs += [ "//build/config/sanitizers:cfi_icall_generalize_pointers" ]
+      configs += [ ":xnnpack_private_config" ]
+
+      deps = [
+        "//third_party/cpuinfo",
+        "//third_party/fp16",
+        "//third_party/fxdiv",
+        "//third_party/pthreadpool:pthreadpool_standalone",
+      ]
+
+      public_configs = [ ":xnnpack_public_config" ]
+
+      if (!(is_android && use_order_profiling)) {
+        assert_no_deps = [ "//base" ]
+      }
+    }
+  }
+
+  if (build_with_chromium) {
     source_set("pf16-gemm_arch=armv8.2-a+sve+sve2") {
       cflags = [ "-march=armv8.2-a+sve+sve2" ]
 
@@ -47121,6 +47188,9 @@
 
       sources = [
         "src/include/xnnpack.h",
+        "src/src/x16-pack-lh/x16-packlh-igemm-neonsme.c",
+        "src/src/x16-pack-lh/x16-packlh-igemm-neonsme2.c",
+        "src/src/x16-pack-lh/x16-packlh-neonsme.c",
         "src/src/x16-pack-lh/x16-packlh-neonsme2.c",
       ]
 
@@ -47147,6 +47217,9 @@
 
       sources = [
         "src/include/xnnpack.h",
+        "src/src/x16-pack-lh/x16-packlh-igemm-neonsme.c",
+        "src/src/x16-pack-lh/x16-packlh-igemm-neonsme2.c",
+        "src/src/x16-pack-lh/x16-packlh-neonsme.c",
         "src/src/x16-pack-lh/x16-packlh-neonsme2.c",
       ]
 
@@ -47407,6 +47480,7 @@
       sources = [
         "src/include/xnnpack.h",
         "src/src/x32-pack-lh/x32-packlh-neonsme.c",
+        "src/src/x32-pack-lh/x32-packlh-neonsme2.c",
       ]
 
       configs -= [ "//build/config/compiler:chromium_code" ]
@@ -47433,6 +47507,7 @@
       sources = [
         "src/include/xnnpack.h",
         "src/src/x32-pack-lh/x32-packlh-neonsme.c",
+        "src/src/x32-pack-lh/x32-packlh-neonsme2.c",
       ]
 
       configs -= [ "//build/config/compiler:chromium_code" ]
@@ -52673,6 +52748,8 @@
         "src/src/operators/convolution-nhwc.c",
         "src/src/operators/deconvolution-nhwc.c",
         "src/src/operators/dynamic-fully-connected-nc.c",
+        "src/src/operators/fingerprint_cache.c",
+        "src/src/operators/fingerprint_id.c",
         "src/src/operators/fully-connected-nc.c",
         "src/src/operators/max-pooling-nhwc.c",
         "src/src/operators/pack-lh.c",
@@ -52719,6 +52796,8 @@
         "src/src/operators/convolution-nhwc.c",
         "src/src/operators/deconvolution-nhwc.c",
         "src/src/operators/dynamic-fully-connected-nc.c",
+        "src/src/operators/fingerprint_cache.c",
+        "src/src/operators/fingerprint_id.c",
         "src/src/operators/fully-connected-nc.c",
         "src/src/operators/max-pooling-nhwc.c",
         "src/src/operators/pack-lh.c",
diff --git a/third_party/xnnpack/README.chromium b/third_party/xnnpack/README.chromium
index 13fb342..f8eb048 100644
--- a/third_party/xnnpack/README.chromium
+++ b/third_party/xnnpack/README.chromium
@@ -2,7 +2,7 @@
 Short Name: xnnpack
 URL: https://github.com/google/xnnpack
 Version: N/A
-Revision: b26a32f4afcef0a41c8b5f9981864ef7c838067d
+Revision: d3efd0a2fcd944931416811da6d24222c91ddd9d
 Update Mechanism: Manual
 License: BSD-3-Clause
 License File: src/LICENSE
diff --git a/third_party/xnnpack/build_identifier.c b/third_party/xnnpack/build_identifier.c
index f9baf9fd..ef99913 100644
--- a/third_party/xnnpack/build_identifier.c
+++ b/third_party/xnnpack/build_identifier.c
@@ -531,6 +531,7 @@
 // - external/xnnpack+/src/f32-vunary/gen/f32-vsqr-sse2.c
 // - external/xnnpack+/src/log.c
 // - external/xnnpack+/src/microparams-init.c
+// - external/xnnpack+/src/operators/fingerprint_id.c
 // - external/xnnpack+/src/qd8-f16-qb4w-gemm/gen/qd8-f16-qb4w-gemm-1x8c8-minmax-avx2.c
 // - external/xnnpack+/src/qd8-f16-qb4w-gemm/gen/qd8-f16-qb4w-gemm-3x8c8-minmax-avx2.c
 // - external/xnnpack+/src/qd8-f16-qc4w-gemm/gen/qd8-f16-qc4w-gemm-1x8c8-minmax-avx2-madd-prfm.c
@@ -1005,10 +1006,10 @@
 #include <string.h>
 
 static const uint8_t xnn_build_identifier[] = {
-  152,  83, 138, 239,  66,  82, 163,  86,
-   19, 153, 118, 161, 248,  25, 212,   7,
-   42, 232, 205, 140, 144,  63,  78, 111,
-  174, 198, 192, 226,  90,  48, 101, 161
+  165,  29,  77,  82, 254, 190,  37,  83,
+   68, 114, 203, 175, 162, 153,  76, 100,
+  137,  79,  38, 182, 221, 246, 183, 208,
+   11,  15,  90,   8, 251, 150, 194,  74
 };
 
 size_t xnn_experimental_get_build_identifier_size() {
diff --git a/third_party/xnnpack/src b/third_party/xnnpack/src
index b26a32f4..d3efd0a 160000
--- a/third_party/xnnpack/src
+++ b/third_party/xnnpack/src
@@ -1 +1 @@
-Subproject commit b26a32f4afcef0a41c8b5f9981864ef7c838067d
+Subproject commit d3efd0a2fcd944931416811da6d24222c91ddd9d
diff --git a/tools/metrics/actions/actions.xml b/tools/metrics/actions/actions.xml
index b9dbe4a..9dbafcf0 100644
--- a/tools/metrics/actions/actions.xml
+++ b/tools/metrics/actions/actions.xml
@@ -42890,6 +42890,15 @@
   </description>
 </action>
 
+<action name="Signin_Impression_FromCredentialExchangeImport">
+  <owner>rgod@google.com</owner>
+  <owner>bling-transactions@google.com</owner>
+  <description>
+    Recorded when showing sign in that originates from the Credential Exchange
+    import flow. iOS only.
+  </description>
+</action>
+
 <action name="Signin_Impression_FromDevicesPage" not_user_triggered="true">
   <owner>gogerald@chromium.org</owner>
   <description>
@@ -43721,6 +43730,15 @@
   </description>
 </action>
 
+<action name="Signin_Signin_FromCredentialExchangeImport">
+  <owner>rgod@google.com</owner>
+  <owner>bling-transactions@google.com</owner>
+  <description>
+    Recorded on sign in start from access point
+    signin_metrics::AccessPoint::kCredentialExchangeImport. iOS only.
+  </description>
+</action>
+
 <action name="Signin_Signin_FromDevicesPage">
   <owner>gogerald@chromium.org</owner>
   <description>
diff --git a/tools/metrics/histograms/metadata/actor/enums.xml b/tools/metrics/histograms/metadata/actor/enums.xml
index b053892..efd23e90 100644
--- a/tools/metrics/histograms/metadata/actor/enums.xml
+++ b/tools/metrics/histograms/metadata/actor/enums.xml
@@ -188,6 +188,17 @@
 
 <!-- LINT.ThenChange(//chrome/browser/actor/actor_task.h:StoppedReason) -->
 
+<!-- LINT.IfChange(TaskNudgeState) -->
+
+<enum name="TaskNudgeState">
+  <int value="0" label="kDefault"/>
+  <int value="1" label="kNeedsAttention"/>
+  <int value="2" label="kMultipleTasksNeedAttention"/>
+  <int value="3" label="kCompleteTasks"/>
+</enum>
+
+<!-- LINT.ThenChange(//chrome/browser/actor/ui/states/actor_task_nudge_state.h:ActorTaskNudgeState) -->
+
 <!-- LINT.IfChange(TimeOfUseResult) -->
 
 <enum name="TimeOfUseResult">
diff --git a/tools/metrics/histograms/metadata/actor/histograms.xml b/tools/metrics/histograms/metadata/actor/histograms.xml
index bc9552dd..570e784b 100644
--- a/tools/metrics/histograms/metadata/actor/histograms.xml
+++ b/tools/metrics/histograms/metadata/actor/histograms.xml
@@ -193,7 +193,7 @@
 </histogram>
 
 <histogram name="Actor.NavigationGating.AllowListSize" units="origins"
-    expires_after="2026-03-30">
+    expires_after="2026-05-31">
   <owner>dylancutler@google.com</owner>
   <owner>johannhof@google.com</owner>
   <summary>
@@ -205,7 +205,7 @@
 </histogram>
 
 <histogram name="Actor.NavigationGating.AppliedGate" enum="Boolean"
-    expires_after="2026-03-30">
+    expires_after="2026-05-31">
   <owner>dylancutler@google.com</owner>
   <owner>johannhof@google.com</owner>
   <summary>
@@ -229,7 +229,7 @@
 </histogram>
 
 <histogram name="Actor.NavigationGating.CrossOrigin" enum="Boolean"
-    expires_after="2026-03-30">
+    expires_after="2026-05-31">
   <owner>dylancutler@google.com</owner>
   <owner>johannhof@google.com</owner>
   <summary>
@@ -240,7 +240,7 @@
 </histogram>
 
 <histogram name="Actor.NavigationGating.CrossSite" enum="Boolean"
-    expires_after="2026-03-30">
+    expires_after="2026-05-31">
   <owner>dylancutler@google.com</owner>
   <owner>johannhof@google.com</owner>
   <summary>
@@ -261,7 +261,7 @@
 </histogram>
 
 <histogram name="Actor.NavigationGating.PermissionGranted" enum="Boolean"
-    expires_after="2026-03-30">
+    expires_after="2026-05-31">
   <owner>dylancutler@google.com</owner>
   <owner>johannhof@google.com</owner>
   <summary>
@@ -272,7 +272,7 @@
 </histogram>
 
 <histogram name="Actor.NavigationGating.TimeElapsedForGating" units="ms"
-    expires_after="2026-03-30">
+    expires_after="2026-05-31">
   <owner>dylancutler@google.com</owner>
   <owner>johannhof@google.com</owner>
   <summary>
@@ -670,6 +670,14 @@
   <summary>Recorded when an error happens in the ActorUiTabController.</summary>
 </histogram>
 
+<histogram name="Actor.Ui.TaskNudge.Shown" enum="TaskNudgeState"
+    expires_after="2026-08-11">
+  <owner>kenok@google.com</owner>
+  <owner>chrstne@google.com</owner>
+  <owner>koilos@google.com</owner>
+  <summary>Recorded when a task nudge is shown.</summary>
+</histogram>
+
 <histogram name="Actor.UiTabController.NumberOfPendingCallbacks"
     units="callbacks" expires_after="2026-08-11">
   <owner>chrstne@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/android/histograms.xml b/tools/metrics/histograms/metadata/android/histograms.xml
index 47f31883..6b7dc74 100644
--- a/tools/metrics/histograms/metadata/android/histograms.xml
+++ b/tools/metrics/histograms/metadata/android/histograms.xml
@@ -1068,7 +1068,7 @@
 </histogram>
 
 <histogram name="Android.BackPress.MinimizeAppAndCloseTab.CustomTab.{Task}"
-    enum="MinimizeAppAndCloseTabType" expires_after="2026-03-29">
+    enum="MinimizeAppAndCloseTabType" expires_after="2026-05-31">
   <owner>lazzzis@chromium.org</owner>
   <owner>src/chrome/browser/back_press/android/OWNERS</owner>
   <summary>
@@ -1143,7 +1143,7 @@
 
 <histogram
     name="Android.BottomControlsStacker.PercentageOfWindowUsedByBottomControlsAtMaxHeight"
-    units="%" expires_after="2026-03-29">
+    units="%" expires_after="2026-05-31">
   <owner>pnoland@chromium.org</owner>
   <owner>wenyufu@chromium.org</owner>
   <summary>
@@ -1156,7 +1156,7 @@
 
 <histogram
     name="Android.BottomControlsStacker.PercentageOfWindowUsedByBottomControlsAtMinHeight"
-    units="%" expires_after="2026-03-29">
+    units="%" expires_after="2026-05-31">
   <owner>pnoland@chromium.org</owner>
   <owner>wenyufu@chromium.org</owner>
   <summary>
@@ -1229,7 +1229,7 @@
 </histogram>
 
 <histogram name="Android.BrowserControls.RenderDrivenShowConstraint"
-    enum="Boolean" expires_after="2026-04-01">
+    enum="Boolean" expires_after="2026-05-31">
   <owner>wenyufu@chromium.org</owner>
   <owner>clank-app-team@google.com</owner>
   <summary>
@@ -1360,7 +1360,7 @@
 </histogram>
 
 <histogram name="Android.ChildProcessConectionEventCounts"
-    enum="AndroidChildProcessConnectionEvents" expires_after="2026-03-29">
+    enum="AndroidChildProcessConnectionEvents" expires_after="2026-05-31">
   <owner>boliu@chromium.org</owner>
   <owner>yfriedman@chromium.org</owner>
   <summary>
@@ -1373,7 +1373,7 @@
 </histogram>
 
 <histogram name="Android.ChildProcessConnection.OnServiceConnectedCounts"
-    units="count" expires_after="2026-03-29">
+    units="count" expires_after="2026-05-31">
   <owner>boliu@chromium.org</owner>
   <owner>kawasin@chromium.org</owner>
   <owner>yfriedman@chromium.org</owner>
@@ -1388,7 +1388,7 @@
 </histogram>
 
 <histogram name="Android.ChildProcessRanking.{RankType}.Count" units="count"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>kawasin@google.com</owner>
   <owner>clank-performance-team@google.com</owner>
   <summary>
@@ -1862,7 +1862,7 @@
 </histogram>
 
 <histogram name="Android.DragDrop.FromWebContent.DropInWebContent.Duration"
-    units="ms" expires_after="2026-03-29">
+    units="ms" expires_after="2026-05-31">
   <owner>wenyufu@chromium.org</owner>
   <owner>clank-large-form-factors@google.com</owner>
   <summary>
@@ -2103,7 +2103,7 @@
 </histogram>
 
 <histogram name="Android.EdgeToEdge.Debugging.ConfigurationSwitchOutcome"
-    enum="EdgeToEdgeConfigurationSwitchOutcome" expires_after="2026-03-29">
+    enum="EdgeToEdgeConfigurationSwitchOutcome" expires_after="2026-05-31">
   <owner>clhager@google.com</owner>
   <owner>wenyufu@chromium.org</owner>
   <owner>edge-to-edge@chromium.org</owner>
@@ -2129,7 +2129,7 @@
 </histogram>
 
 <histogram name="Android.EdgeToEdge.DrawToEdgeInUnsupportedConfiguration"
-    enum="Boolean" expires_after="2026-03-29">
+    enum="Boolean" expires_after="2026-05-31">
   <owner>clhager@google.com</owner>
   <owner>wenyufu@chromium.org</owner>
   <owner>edge-to-edge@chromium.org</owner>
@@ -2659,7 +2659,7 @@
 </histogram>
 
 <histogram name="Android.InputOnViz.Browser.TransferInputToVizResult2"
-    enum="TransferInputToVizResult" expires_after="2026-03-22">
+    enum="TransferInputToVizResult" expires_after="2026-05-31">
   <owner>kartarsingh@google.com</owner>
   <owner>amanvr@google.com</owner>
   <owner>woa-performance-team@google.com</owner>
@@ -2699,7 +2699,7 @@
 </histogram>
 
 <histogram name="Android.InputOnViz.Viz.DroppedNonTouchActions"
-    enum="MotionEventAction" expires_after="2026-03-29">
+    enum="MotionEventAction" expires_after="2026-05-31">
   <owner>kartarsingh@google.com</owner>
   <owner>woa-performance-team@google.com</owner>
   <summary>
@@ -2721,7 +2721,7 @@
 </histogram>
 
 <histogram name="Android.InputOnViz.Viz.PendingStateTransfers.{CurrentState}"
-    units="counts" expires_after="2026-03-29">
+    units="counts" expires_after="2026-05-31">
   <owner>kartarsingh@google.com</owner>
   <owner>woa-performance-team@google.com</owner>
   <summary>
@@ -3095,7 +3095,7 @@
 </histogram>
 
 <histogram name="Android.Messages.Enqueued.Hidden" enum="MessageIdentifier"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>lazzzis@chromium.org</owner>
   <owner>src/components/messages/OWNERS</owner>
   <summary>
@@ -3273,7 +3273,7 @@
 </histogram>
 
 <histogram name="Android.MultiInstance.MaxInstanceCount" units="instances"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>jinsukkim@chromium.org</owner>
   <owner>twellington@chromium.org</owner>
   <owner>clank-app-team@google.com</owner>
@@ -3712,7 +3712,7 @@
 </histogram>
 
 <histogram name="Android.Omnibox.InputToNavigationControllerStart" units="ms"
-    expires_after="2026-03-08">
+    expires_after="2026-05-31">
   <owner>spelchat@chromium.org</owner>
   <owner>chrome-brapp-loading@google.com</owner>
   <summary>
@@ -5742,6 +5742,26 @@
   </summary>
 </histogram>
 
+<histogram name="Android.TabItemPicker.SelectableTabs.Count" units="count"
+    expires_after="2026-05-01">
+  <owner>haileywang@google.com</owner>
+  <owner>ssid@chromium.org</owner>
+  <summary>
+    The number of selectable tabs available to the user in the tab item picker.
+    Recorded when the tab picker UI is shown.
+  </summary>
+</histogram>
+
+<histogram name="Android.TabItemPicker.SelectedTabs.Count" units="count"
+    expires_after="2026-05-01">
+  <owner>haileywang@google.com</owner>
+  <owner>ssid@chromium.org</owner>
+  <summary>
+    The number of tabs selected by the user in the tab item picker. Recorded
+    when the user confirms their selection.
+  </summary>
+</histogram>
+
 <histogram name="Android.TabletDeterminationMismatch" enum="Boolean"
     expires_after="2026-05-01">
   <owner>skavuluru@google.com</owner>
@@ -6091,7 +6111,7 @@
 </histogram>
 
 <histogram name="Android.ToolbarPosition.PositionAtStartup"
-    enum="ToolbarPosition" expires_after="2026-03-29">
+    enum="ToolbarPosition" expires_after="2026-05-31">
   <owner>pnoland@chromium.org</owner>
   <owner>gangwu@chromium.org</owner>
   <summary>
@@ -6107,7 +6127,7 @@
 </histogram>
 
 <histogram name="Android.ToolbarPosition.PositionPrefChanged"
-    enum="ToolbarPosition" expires_after="2026-03-29">
+    enum="ToolbarPosition" expires_after="2026-05-31">
   <owner>pnoland@chromium.org</owner>
   <owner>gangwu@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/apps/histograms.xml b/tools/metrics/histograms/metadata/apps/histograms.xml
index eda579a2..154c6a0 100644
--- a/tools/metrics/histograms/metadata/apps/histograms.xml
+++ b/tools/metrics/histograms/metadata/apps/histograms.xml
@@ -251,7 +251,7 @@
 </histogram>
 
 <histogram name="Apps.AppDiscovery.MallUsageTime" units="ms"
-    expires_after="2026-03-26">
+    expires_after="2026-05-31">
   <owner>joelhockey@chromium.org</owner>
   <owner>crosdev-commerce-eng@google.com</owner>
   <summary>
@@ -268,7 +268,7 @@
 </histogram>
 
 <histogram name="Apps.AppDiscovery.{InstallReason}.Install" enum="AppType"
-    expires_after="2026-03-26">
+    expires_after="2026-05-31">
   <owner>joelhockey@chromium.org</owner>
   <owner>crosdev-commerce-eng@google.com</owner>
   <summary>
@@ -2336,7 +2336,7 @@
 </histogram>
 
 <histogram name="Apps.CreateShortcuts.Mac.CopyShortcutResult"
-    enum="CopyShortcutResult" expires_after="2026-03-29">
+    enum="CopyShortcutResult" expires_after="2026-05-31">
   <owner>mek@chromium.org</owner>
   <owner>pwa-team@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/arc/histograms.xml b/tools/metrics/histograms/metadata/arc/histograms.xml
index d4254ed0..e588adbf 100644
--- a/tools/metrics/histograms/metadata/arc/histograms.xml
+++ b/tools/metrics/histograms/metadata/arc/histograms.xml
@@ -544,7 +544,7 @@
 </histogram>
 
 <histogram name="Arc.AppInstalledReason" enum="InstallationCounterReasonEnum"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>thanhdng@chromium.org</owner>
   <owner>tby@chromium.org</owner>
   <summary>
@@ -695,7 +695,7 @@
 </histogram>
 
 <histogram name="Arc.Attestation.CertificateSigning.Result"
-    enum="CertificateSigningResult" expires_after="2026-04-01">
+    enum="CertificateSigningResult" expires_after="2026-05-31">
   <owner>batoon@google.com</owner>
   <owner>arc-commercial@google.com</owner>
   <summary>
@@ -1330,7 +1330,7 @@
 </histogram>
 
 <histogram name="Arc.IdleManager.ScreenOffTime" units="ms"
-    expires_after="2026-03-01">
+    expires_after="2026-05-31">
   <owner>raging@google.com</owner>
   <owner>arcvm-eng@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/ash/histograms.xml b/tools/metrics/histograms/metadata/ash/histograms.xml
index f649d90..5af4964 100644
--- a/tools/metrics/histograms/metadata/ash/histograms.xml
+++ b/tools/metrics/histograms/metadata/ash/histograms.xml
@@ -1054,7 +1054,7 @@
 </histogram>
 
 <histogram name="Ash.Birch.Coral.Action" enum="CoralActionType"
-    expires_after="2026-03-22">
+    expires_after="2026-05-31">
   <owner>zxdan@chromium.org</owner>
   <owner>cros-coral@google.com</owner>
   <summary>
@@ -4561,7 +4561,7 @@
 
 <histogram
     name="Ash.Glanceables.TimeManagement.{GlanceableBubble}.{ExpandState}.AnimationSmoothness"
-    units="%" expires_after="2026-03-29">
+    units="%" expires_after="2026-05-31">
   <owner>wcwang@google.com</owner>
   <owner>chromeos-launcher@google.com</owner>
   <summary>
@@ -8084,7 +8084,7 @@
 </histogram>
 
 <histogram name="Ash.Shelf.DefaultApps.Pinned" enum="DefaultAppName"
-    expires_after="2026-01-04">
+    expires_after="2026-07-01">
   <owner>dmblack@google.com</owner>
   <owner>tbarzic@chromium.org</owner>
   <owner>chromeos-launcher@google.com</owner>
@@ -8747,7 +8747,7 @@
 </histogram>
 
 <histogram name="Ash.StatusArea.TrayItemView.Show" units="%"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>newcomer@chromium.org</owner>
   <owner>cros-status-area-eng@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/attribution_reporting/histograms.xml b/tools/metrics/histograms/metadata/attribution_reporting/histograms.xml
index ed0f9d0..f31fe82b 100644
--- a/tools/metrics/histograms/metadata/attribution_reporting/histograms.xml
+++ b/tools/metrics/histograms/metadata/attribution_reporting/histograms.xml
@@ -598,7 +598,7 @@
 </histogram>
 
 <histogram name="Conversions.NamedBudgetsPer{Type}Registration"
-    units="named_budgets" expires_after="2026-03-29">
+    units="named_budgets" expires_after="2026-05-31">
   <owner>tquintanilla@chromium.org</owner>
   <owner>measurement-api-dev+metrics@google.com</owner>
   <summary>
@@ -911,7 +911,7 @@
 </histogram>
 
 <histogram name="Conversions.Storage.ClearDataWithFilterDuration" units="ms"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>csharrison@chromium.org</owner>
   <owner>measurement-api-dev+metrics@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/autofill/enums.xml b/tools/metrics/histograms/metadata/autofill/enums.xml
index 9b93cf7..ce5b5d0 100644
--- a/tools/metrics/histograms/metadata/autofill/enums.xml
+++ b/tools/metrics/histograms/metadata/autofill/enums.xml
@@ -3257,8 +3257,8 @@
   <int value="301" label="RemoveEntityInstance: Failure"/>
   <int value="310" label="RemoveEntityInstancesModifiedBetween: Success"/>
   <int value="311" label="RemoveEntityInstancesModifiedBetween: Failure"/>
-  <int value="312" label="CleanupForCrbug411681430: Success"/>
-  <int value="313" label="CleanupForCrbug411681430: Failure"/>
+  <int value="312" label="ClearLocalCvcsUpToMay2025: Success"/>
+  <int value="313" label="ClearLocalCvcsUpToMay2025: Failure"/>
   <int value="314" label="CleanupForCrbug445879524: Success"/>
   <int value="315" label="CleanupForCrbug445879524: Failure"/>
 </enum>
diff --git a/tools/metrics/histograms/metadata/autofill/histograms.xml b/tools/metrics/histograms/metadata/autofill/histograms.xml
index 9ee3adf..0e3c269 100644
--- a/tools/metrics/histograms/metadata/autofill/histograms.xml
+++ b/tools/metrics/histograms/metadata/autofill/histograms.xml
@@ -3330,7 +3330,7 @@
 </histogram>
 
 <histogram name="Autofill.DriverRouter.TriggerFormExtractionExcept.Duration"
-    units="microseconds" expires_after="2026-03-29">
+    units="microseconds" expires_after="2026-05-31">
   <owner>vincb@google.com</owner>
   <owner>schwering@google.com</owner>
   <owner>bling-transactions@google.com</owner>
@@ -3810,7 +3810,7 @@
 </histogram>
 
 <histogram name="Autofill.Filling.DidAlternativeNameFieldRequireConversion"
-    enum="BooleanYesNo" expires_after="2026-03-29">
+    enum="BooleanYesNo" expires_after="2026-05-31">
   <owner>aayazhan@google.com</owner>
   <owner>sygiet@google.com</owner>
   <owner>chrome-autofill-alerts@google.com</owner>
@@ -5864,7 +5864,7 @@
 </histogram>
 
 <histogram name="Autofill.ProfileImport.InaccessibleFieldsRemoved.Total"
-    enum="Boolean" expires_after="2026-02-22">
+    enum="Boolean" expires_after="2026-05-31">
   <owner>fleimgruber@google.com</owner>
   <owner>chrome-autofill-alerts@google.com</owner>
   <summary>
@@ -6269,7 +6269,7 @@
 </histogram>
 
 <histogram name="Autofill.PromptStatus" enum="AutofillPromptStatus"
-    expires_after="2026-03-30">
+    expires_after="2026-05-31">
   <owner>vincb@google.com</owner>
   <owner>bling-transactions@google.com</owner>
   <owner>chrome-autofill-alerts@google.com</owner>
@@ -7635,7 +7635,7 @@
 
 <histogram name="Autofill.SubmittedAlternativeNameFieldValueCharacterSet"
     enum="AutofillAlternativeNameFieldValueCharacterSet"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>aayazhan@google.com</owner>
   <owner>sygiet@google.com</owner>
   <owner>chrome-autofill-alerts@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/blink/enums.xml b/tools/metrics/histograms/metadata/blink/enums.xml
index 8311429..1288281f 100644
--- a/tools/metrics/histograms/metadata/blink/enums.xml
+++ b/tools/metrics/histograms/metadata/blink/enums.xml
@@ -6365,6 +6365,7 @@
   <int value="5729" label="ExtendedTextMetrics"/>
   <int value="5730" label="UseCssSizingProperties"/>
   <int value="5731" label="SharedStorageAPIAll"/>
+  <int value="5732" label="GamepadRawInputChangeEventListener"/>
 </enum>
 
 <!-- LINT.ThenChange(//third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom:WebFeature) -->
@@ -8257,6 +8258,15 @@
   <int value="7" label="ForceDefer (Deprecated 2023)"/>
 </enum>
 
+<!-- LINT.IfChange(SharedDictionaryHintType)-->
+
+<enum name="SharedDictionaryHintType">
+  <int value="0" label="HTML Link Tag"/>
+  <int value="1" label="HTTP Header"/>
+</enum>
+
+<!-- LINT.ThenChange(//third_party/blink/renderer/core/loader/shared_dictionary_hint_type.h:SharedDictionaryHintType) -->
+
 <!-- LINT.IfChange(SharedStorageGetInFencedFrameOutcome) -->
 
 <enum name="SharedStorageGetInFencedFrameOutcome">
diff --git a/tools/metrics/histograms/metadata/blink/histograms.xml b/tools/metrics/histograms/metadata/blink/histograms.xml
index a6485f1..b20f7a2 100644
--- a/tools/metrics/histograms/metadata/blink/histograms.xml
+++ b/tools/metrics/histograms/metadata/blink/histograms.xml
@@ -941,7 +941,7 @@
 </histogram>
 
 <histogram name="Blink.CullRect.UpdateTime" units="microseconds"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>pdr@chromium.org</owner>
   <owner>wangxianzhu@chromium.org</owner>
   <owner>paint-dev@chromium.org</owner>
@@ -1664,7 +1664,7 @@
 </histogram>
 
 <histogram name="Blink.FedCm.DomainHint.NumMatchingAccounts"
-    enum="FedCmNumAccounts" expires_after="2026-03-29">
+    enum="FedCmNumAccounts" expires_after="2026-05-31">
   <owner>npm@chromium.org</owner>
   <owner>web-identity-eng@google.com</owner>
   <summary>
@@ -1895,7 +1895,7 @@
 </histogram>
 
 <histogram name="Blink.FedCm.LoginHint.NumMatchingAccounts"
-    enum="FedCmNumAccounts" expires_after="2026-03-29">
+    enum="FedCmNumAccounts" expires_after="2026-05-31">
   <owner>npm@chromium.org</owner>
   <owner>web-identity-eng@google.com</owner>
   <summary>
@@ -2017,7 +2017,7 @@
 </histogram>
 
 <histogram name="Blink.FedCm.SegmentationPlatform.UserAction"
-    enum="FedCmUserAction" expires_after="2026-03-29">
+    enum="FedCmUserAction" expires_after="2026-05-31">
   <owner>tanzachary@chromium.org</owner>
   <owner>web-identity-eng@google.com</owner>
   <summary>
@@ -4117,7 +4117,7 @@
 </histogram>
 
 <histogram name="Blink.LinkHeader.LoadLinksFromHeaderMode"
-    enum="LoadLinksFromHeaderMode" expires_after="2026-03-29">
+    enum="LoadLinksFromHeaderMode" expires_after="2026-05-31">
   <owner>tnak@chromium.org</owner>
   <owner>chrome-loading@google.com</owner>
   <summary>
@@ -5200,6 +5200,18 @@
   </summary>
 </histogram>
 
+<histogram name="Blink.SharedDictionary.Hint.Discovery"
+    enum="SharedDictionaryHintType" expires_after="2026-11-11">
+  <owner>wbjacksonjr@chromium.org</owner>
+  <owner>blink-network-stack@google.com</owner>
+  <summary>
+    Records how often a shared dictionary hint is discovered by the renderer,
+    either via an HTML &quot;link&quot; tag or an HTTP Link header.
+
+    The UMA is recorded before fetching the compression dictionary.
+  </summary>
+</histogram>
+
 <histogram name="Blink.Sms.BackendAvailability"
     enum="WebOTPBackendAvailability" expires_after="2026-04-12">
   <owner>yigu@chromium.org</owner>
@@ -5296,7 +5308,7 @@
 </histogram>
 
 <histogram name="Blink.Sms.Receive.TimeSmsReceive" units="ms"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>yigu@chromium.org</owner>
   <owner>goto@chromium.org</owner>
   <owner>web-identity-eng@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/browser/histograms.xml b/tools/metrics/histograms/metadata/browser/histograms.xml
index 0bd1ffe..74cfc2e 100644
--- a/tools/metrics/histograms/metadata/browser/histograms.xml
+++ b/tools/metrics/histograms/metadata/browser/histograms.xml
@@ -863,7 +863,7 @@
 
 <histogram
     name="Browser.Memory.Experimental.MemoryPressureOnProcessKill.{ProcessType}.{MetricName}"
-    units="MB" expires_after="2026-04-01">
+    units="MB" expires_after="2026-05-31">
   <owner>gjc@google.com</owner>
   <owner>chrome-loading@google.com</owner>
   <summary>
@@ -1204,7 +1204,7 @@
 </histogram>
 
 <histogram name="Browser.WindowCount.Guest" units="units"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>rhalavati@chromium.org</owner>
   <owner>chrome-privacy-core@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/chromeos/histograms.xml b/tools/metrics/histograms/metadata/chromeos/histograms.xml
index d8fc2da..9490269 100644
--- a/tools/metrics/histograms/metadata/chromeos/histograms.xml
+++ b/tools/metrics/histograms/metadata/chromeos/histograms.xml
@@ -1346,7 +1346,7 @@
 </histogram>
 
 <histogram name="ChromeOS.Debugd.Perf.{FunctionName}" units="ms"
-    expires_after="2026-04-01">
+    expires_after="2026-05-31">
   <owner>enlightened@google.com</owner>
   <owner>jorgelo@google.com</owner>
   <owner>chromeos-security-core@google.com</owner>
@@ -1746,6 +1746,66 @@
   </summary>
 </histogram>
 
+<histogram
+    name="ChromeOS.Geolocation.CacheEviction.{NonParameterizedEvictionStrategy}.{PredictionToken}"
+    units="meters" expires_after="2026-06-01">
+  <owner>zauri@google.com</owner>
+  <owner>chromeos-privacy-features-eng@google.com</owner>
+  <summary>
+    Records how {NonParameterizedEvictionStrategy} eviction strategy performs in
+    the wild. Namely, reports the distance traveled between the location
+    requests and emits it to either *PredictedYes or *PredictedNo, depending on
+    the eviction strategy assessment. Events are recorded every time ChromeOS
+    system service requests device location. This happens periodically every
+    6-to-24 hours (e.g. Automatic Time Zone has 6 hours refresh timeout, while
+    Night Light has 24h) and also on power events such as reboot and
+    wake-up-from-sleep. Emitted events are from [0m, ~20'000km (half the earth
+    circumference)] range, bucketed into [1m, 10m, 100m, 1km, 10km, 100km,
+    1000km] ranges.
+  </summary>
+  <token key="NonParameterizedEvictionStrategy">
+    <variant name="CommonCell"/>
+    <variant name="CommonWifi"/>
+    <variant name="CommonWifiAndCell"/>
+  </token>
+  <token key="PredictionToken">
+    <variant name="PredictedNo"/>
+    <variant name="PredictedYes"/>
+  </token>
+</histogram>
+
+<histogram
+    name="ChromeOS.Geolocation.CacheEviction.{ParameterizedEvictionStrategy}{SimilarityDegreeParameter}.{PredictionToken}"
+    units="meters" expires_after="2026-06-01">
+  <owner>zauri@google.com</owner>
+  <owner>chromeos-privacy-features-eng@google.com</owner>
+  <summary>
+    Records how {ParameterizedEvictionStrategy} eviction strategy with
+    {SimilarityDegreeParameter} performs in the wild. Namely, reports the
+    distance traveled between the location requests and emits it to either
+    *PredictedYes or *PredictedNo, depending on the eviction strategy
+    assessment. Events are recorded every time ChromeOS system service requests
+    device location. This happens periodically every 6-to-24 hours (e.g.
+    Automatic Time Zone has 6 hours refresh timeout, while Night Light has 24h)
+    and also on power events such as reboot and wake-up-from-sleep. Emitted
+    events are from [0m, ~20'000km (half the earth circumference)] range,
+    bucketed into [1m, 10m, 100m, 1km, 10km, 100km, 1'000km] ranges.
+  </summary>
+  <token key="ParameterizedEvictionStrategy">
+    <variant name="CellularTolerance"/>
+    <variant name="WifiTolerance"/>
+  </token>
+  <token key="SimilarityDegreeParameter">
+    <variant name="Loose" summary="50% similarity threshold"/>
+    <variant name="Moderate" summary="70% similarity threshold"/>
+    <variant name="Strict" summary="90 similarity threshold"/>
+  </token>
+  <token key="PredictionToken">
+    <variant name="PredictedNo"/>
+    <variant name="PredictedYes"/>
+  </token>
+</histogram>
+
 <histogram name="ChromeOS.HardwareVerifier.Report.IsCompliant" enum="Boolean"
     expires_after="2026-12-31">
   <owner>itspeter@chromium.org</owner>
@@ -3137,7 +3197,7 @@
 </histogram>
 
 <histogram name="ChromeOS.Regmon.PolicyHandlerTime" units="ms"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>chiav@chromium.org</owner>
   <owner>crmullins@chromium.org</owner>
   <summary>
@@ -3147,7 +3207,7 @@
 </histogram>
 
 <histogram name="ChromeOS.Regmon.PolicyViolation"
-    enum="NetworkAnnotationHashCodes" expires_after="2026-03-29">
+    enum="NetworkAnnotationHashCodes" expires_after="2026-05-31">
   <owner>chiav@chromium.org</owner>
   <owner>crmullins@chromium.org</owner>
   <summary>
@@ -3159,7 +3219,7 @@
 </histogram>
 
 <histogram name="ChromeOS.Regmon.ReportViolationTime" units="ms"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>chiav@chromium.org</owner>
   <owner>crmullins@chromium.org</owner>
   <summary>
@@ -3244,7 +3304,7 @@
 </histogram>
 
 <histogram name="ChromeOS.SAML.Provider" enum="ChromeOSSamlProvider"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>mslus@chromium.org</owner>
   <owner>cros-3pidp@google.com</owner>
   <summary>Records SAML provider when SAML login flow is used.</summary>
@@ -3381,7 +3441,7 @@
 </histogram>
 
 <histogram name="ChromeOS.Secagentd.Bootmode.Cros" enum="SecagentdBootmodeCros"
-    expires_after="2026-03-31">
+    expires_after="2026-05-31">
   <owner>rborzello@google.com</owner>
   <owner>cros-enterprise-security@google.com</owner>
   <summary>
@@ -3393,7 +3453,7 @@
 </histogram>
 
 <histogram name="ChromeOS.Secagentd.Bootmode.Uefi" enum="SecagentdBootmodeUefi"
-    expires_after="2026-03-31">
+    expires_after="2026-05-31">
   <owner>rborzello@google.com</owner>
   <owner>cros-enterprise-security@google.com</owner>
   <summary>
@@ -3405,7 +3465,7 @@
 </histogram>
 
 <histogram name="ChromeOS.Secagentd.Bpf.{Bpf}.AttachResult"
-    enum="SecagentdBpfAttachResult" expires_after="2026-03-31">
+    enum="SecagentdBpfAttachResult" expires_after="2026-05-31">
   <owner>rborzello@google.com</owner>
   <owner>cros-enterprise-security@google.com</owner>
   <summary>
@@ -3421,7 +3481,7 @@
 </histogram>
 
 <histogram name="ChromeOS.Secagentd.Cache" enum="SecagentdCache"
-    expires_after="2026-03-31">
+    expires_after="2026-05-31">
   <owner>rborzello@google.com</owner>
   <owner>cros-enterprise-security@google.com</owner>
   <summary>
@@ -3433,7 +3493,7 @@
 </histogram>
 
 <histogram name="ChromeOS.Secagentd.CacheFullness" units="%"
-    expires_after="2026-03-31">
+    expires_after="2026-05-31">
   <owner>rborzello@google.com</owner>
   <owner>cros-enterprise-security@google.com</owner>
   <summary>
@@ -3453,7 +3513,7 @@
 </histogram>
 
 <histogram name="ChromeOS.Secagentd.Policy" enum="SecagentdPolicy"
-    expires_after="2026-03-31">
+    expires_after="2026-05-31">
   <owner>rborzello@google.com</owner>
   <owner>cros-enterprise-security@google.com</owner>
   <summary>
@@ -3469,7 +3529,7 @@
 </histogram>
 
 <histogram name="ChromeOS.Secagentd.Process.{EventType}Event"
-    enum="SecagentdProcessEvent" expires_after="2026-03-31">
+    enum="SecagentdProcessEvent" expires_after="2026-05-31">
   <owner>rborzello@google.com</owner>
   <owner>cros-enterprise-security@google.com</owner>
   <summary>
@@ -3484,7 +3544,7 @@
 </histogram>
 
 <histogram name="ChromeOS.Secagentd.Redaction" units="position"
-    expires_after="2026-03-31">
+    expires_after="2026-05-31">
   <owner>rborzello@google.com</owner>
   <owner>cros-enterprise-security@google.com</owner>
   <summary>
@@ -3496,7 +3556,7 @@
 </histogram>
 
 <histogram name="ChromeOS.Secagentd.SendMessageResult"
-    enum="SecagentdSendMessageResult" expires_after="2026-03-31">
+    enum="SecagentdSendMessageResult" expires_after="2026-05-31">
   <owner>rborzello@google.com</owner>
   <owner>cros-enterprise-security@google.com</owner>
   <summary>
@@ -3529,7 +3589,7 @@
 </histogram>
 
 <histogram name="ChromeOS.Secagentd.Tpm" enum="SecagentdTpm"
-    expires_after="2026-03-31">
+    expires_after="2026-05-31">
   <owner>rborzello@google.com</owner>
   <owner>cros-enterprise-security@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/chromeos_settings/histograms.xml b/tools/metrics/histograms/metadata/chromeos_settings/histograms.xml
index df7f10db..06caaba 100644
--- a/tools/metrics/histograms/metadata/chromeos_settings/histograms.xml
+++ b/tools/metrics/histograms/metadata/chromeos_settings/histograms.xml
@@ -44,7 +44,7 @@
 </histogram>
 
 <histogram name="ChromeOS.Settings.Accessibility.CaretBlinkInterval" units="ms"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>katie@chromium.org</owner>
   <owner>chrome-a11y-core@google.com</owner>
   <summary>
@@ -135,7 +135,7 @@
 </histogram>
 
 <histogram name="ChromeOS.Settings.Accessibility.{SettingType}.Enabled"
-    enum="BooleanToggled" expires_after="2026-03-29">
+    enum="BooleanToggled" expires_after="2026-05-31">
   <owner>katie@chromium.org</owner>
   <owner>chromeos-a11y-eng@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/commerce/histograms.xml b/tools/metrics/histograms/metadata/commerce/histograms.xml
index 9160bc88..bff7bae6 100644
--- a/tools/metrics/histograms/metadata/commerce/histograms.xml
+++ b/tools/metrics/histograms/metadata/commerce/histograms.xml
@@ -538,7 +538,7 @@
 </histogram>
 
 <histogram name="Commerce.PriceDrop.NotificationChannelCreated" enum="Boolean"
-    expires_after="2026-01-18">
+    expires_after="2026-05-31">
   <owner>zhiyuancai@chromium.org</owner>
   <owner>chrome-shopping@google.com</owner>
   <summary>
@@ -562,7 +562,7 @@
 </histogram>
 
 <histogram name="Commerce.PriceDrops.{ManagementType}.NotificationCount"
-    units="notifications" expires_after="2026-02-01">
+    units="notifications" expires_after="2026-05-31">
   <owner>zhiyuancai@chromium.org</owner>
   <owner>ayman@chromium.org</owner>
   <owner>chrome-shopping@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/compositing/histograms.xml b/tools/metrics/histograms/metadata/compositing/histograms.xml
index a2eedf2..5d1e316 100644
--- a/tools/metrics/histograms/metadata/compositing/histograms.xml
+++ b/tools/metrics/histograms/metadata/compositing/histograms.xml
@@ -722,7 +722,7 @@
 </histogram>
 
 <histogram name="Compositing.Scheduler.DeadlineMode"
-    enum="BeginImplFrameDeadlineMode" expires_after="2026-03-29">
+    enum="BeginImplFrameDeadlineMode" expires_after="2026-05-31">
   <owner>lizeb@chromium.org</owner>
   <owner>jonross@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/content/histograms.xml b/tools/metrics/histograms/metadata/content/histograms.xml
index 96aa12a21..a97820c 100644
--- a/tools/metrics/histograms/metadata/content/histograms.xml
+++ b/tools/metrics/histograms/metadata/content/histograms.xml
@@ -91,7 +91,7 @@
 </histogram>
 
 <histogram name="Content.SharedWorker.Service.LastClientCheckTime" units="ms"
-    expires_after="2026-04-01">
+    expires_after="2026-05-31">
   <owner>annasato@chromium.org</owner>
   <owner>chrome-worker@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/contextual_tasks/enums.xml b/tools/metrics/histograms/metadata/contextual_tasks/enums.xml
index 8e9c5d7..188c1d6 100644
--- a/tools/metrics/histograms/metadata/contextual_tasks/enums.xml
+++ b/tools/metrics/histograms/metadata/contextual_tasks/enums.xml
@@ -31,6 +31,7 @@
   <int value="1" label="Embedder not available"/>
   <int value="2" label="Query embedding failed"/>
   <int value="3" label="Query embedding output malformed"/>
+  <int value="4" label="No eligible tabs"/>
 </enum>
 
 </enums>
diff --git a/tools/metrics/histograms/metadata/contextual_tasks/histograms.xml b/tools/metrics/histograms/metadata/contextual_tasks/histograms.xml
index 84971cc..ac9e3c9 100644
--- a/tools/metrics/histograms/metadata/contextual_tasks/histograms.xml
+++ b/tools/metrics/histograms/metadata/contextual_tasks/histograms.xml
@@ -109,19 +109,6 @@
   </summary>
 </histogram>
 
-<histogram name="ContextualTasks.Context.RelevantButInvalidTabsCount"
-    units="count" expires_after="2026-03-22">
-  <owner>sophiechang@chromium.org</owner>
-  <owner>abhayprakash@google.com</owner>
-  <summary>
-    Recorded every time a query is successfully made in the contextual tasks
-    framework, context was calculated successfully, and there was at least one
-    tab that would have otherwise been included but were discarded because they
-    were ineligible for server upload. Records the number of tabs that were
-    determined to be relevant to the query but were not valid for server upload.
-  </summary>
-</histogram>
-
 <histogram name="ContextualTasks.Context.RelevantTabsCount" units="count"
     expires_after="2026-03-22">
   <owner>sophiechang@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/cookie/histograms.xml b/tools/metrics/histograms/metadata/cookie/histograms.xml
index a10a1e7a..de369c8 100644
--- a/tools/metrics/histograms/metadata/cookie/histograms.xml
+++ b/tools/metrics/histograms/metadata/cookie/histograms.xml
@@ -60,7 +60,7 @@
 </histogram>
 
 <histogram name="Cookie.ClockSkew.{SkewDirection}.Subsampled" units="minutes"
-    expires_after="2026-03-31">
+    expires_after="2026-05-31">
   <owner>kyraseevers@chromium.org</owner>
   <owner>src/net/cookies/OWNERS</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/cras/histograms.xml b/tools/metrics/histograms/metadata/cras/histograms.xml
index 816aa5f..b0b42a5 100644
--- a/tools/metrics/histograms/metadata/cras/histograms.xml
+++ b/tools/metrics/histograms/metadata/cras/histograms.xml
@@ -830,7 +830,7 @@
 </histogram>
 
 <histogram name="Cras.RtcDevicePair" enum="CrasDevicePair"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>yuhsuan@chromium.org</owner>
   <owner>chromeos-audio@google.com</owner>
   <summary>
@@ -1081,7 +1081,7 @@
 </histogram>
 
 <histogram name="Cras.StreamOverrunCount" units="count"
-    expires_after="2026-03-25">
+    expires_after="2026-05-31">
 <!-- Name completed by histogram_suffixes
      name="Cras.ClientType" and
      name="Cras.StreamType" -->
diff --git a/tools/metrics/histograms/metadata/cros_ml/histograms.xml b/tools/metrics/histograms/metadata/cros_ml/histograms.xml
index 9fb0f10..ed935d5 100644
--- a/tools/metrics/histograms/metadata/cros_ml/histograms.xml
+++ b/tools/metrics/histograms/metadata/cros_ml/histograms.xml
@@ -87,7 +87,7 @@
 </histogram>
 
 <histogram name="MachineLearningService.GrammarChecker.Check.Event"
-    enum="Boolean" expires_after="2026-04-01">
+    enum="Boolean" expires_after="2026-05-31">
   <owner>jiwan@chromium.org</owner>
   <owner>amoylan@chromium.org</owner>
   <summary>The result of grammar check, which can be OK or ERROR.</summary>
diff --git a/tools/metrics/histograms/metadata/custom_tabs/histograms.xml b/tools/metrics/histograms/metadata/custom_tabs/histograms.xml
index 372e292..fbbf4ae 100644
--- a/tools/metrics/histograms/metadata/custom_tabs/histograms.xml
+++ b/tools/metrics/histograms/metadata/custom_tabs/histograms.xml
@@ -71,7 +71,7 @@
 </histogram>
 
 <histogram name="CustomTab.AdaptiveToolbarButton.FallbackUi"
-    enum="AdaptiveToolbarButtonVariant" expires_after="2026-03-31">
+    enum="AdaptiveToolbarButtonVariant" expires_after="2026-05-31">
   <owner>jinsukkiim@google.com</owner>
   <owner>clank-app-team@google.com</owner>
   <summary>
@@ -143,7 +143,7 @@
 </histogram>
 
 <histogram name="CustomTabs.AdaptiveToolbarButton.Shown"
-    enum="AdaptiveToolbarButtonVariant" expires_after="2026-03-29">
+    enum="AdaptiveToolbarButtonVariant" expires_after="2026-05-31">
   <owner>jinsukkiim@google.com</owner>
   <owner>clank-app-team@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/enterprise/histograms.xml b/tools/metrics/histograms/metadata/enterprise/histograms.xml
index a9919d0..f3cc0b8 100644
--- a/tools/metrics/histograms/metadata/enterprise/histograms.xml
+++ b/tools/metrics/histograms/metadata/enterprise/histograms.xml
@@ -1408,7 +1408,7 @@
 </histogram>
 
 <histogram name="Enterprise.Dlp.ActiveFileEventsCount" units="entries"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>accorsi@google.com</owner>
   <owner>chromeos-dlp@google.com</owner>
   <summary>
@@ -1744,7 +1744,7 @@
 </histogram>
 
 <histogram name="Enterprise.Dlp.ReportedBlockLevelRestriction"
-    enum="EnterpriseDlpPolicyRestriction" expires_after="2026-03-22">
+    enum="EnterpriseDlpPolicyRestriction" expires_after="2026-05-31">
   <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-03-22">
+    expires_after="2026-05-31">
   <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-03-22">
+    enum="EnterpriseDlpPolicyRestriction" expires_after="2026-05-31">
   <owner>poromov@chromium.org</owner>
   <owner>chromeos-dlp@google.com</owner>
   <summary>
@@ -2409,7 +2409,7 @@
 </histogram>
 
 <histogram name="Enterprise.FileAnalysisRequest.FileSize" units="KB"
-    expires_after="2026-03-22">
+    expires_after="2026-05-31">
   <owner>eliashomsi@google.com</owner>
   <owner>nancylanxiao@google.com</owner>
   <owner>cbe-cep-eng@google.com</owner>
@@ -2421,7 +2421,7 @@
 </histogram>
 
 <histogram name="Enterprise.FileAnalysisRequest.PrintedPageSize" units="KB"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>eliashomsi@google.com</owner>
   <owner>nancylanxiao@google.com</owner>
   <owner>cbe-cep-eng@google.com</owner>
@@ -3041,7 +3041,7 @@
 </histogram>
 
 <histogram name="Enterprise.ProfileAffiliation.IsAffiliated" enum="Boolean"
-    expires_after="2026-03-22">
+    expires_after="2026-05-31">
   <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-03-22">
+    enum="EnterpriseProfileUnaffiliatedReason" expires_after="2026-05-31">
   <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-03-22">
+    expires_after="2026-05-31">
   <owner>bfranz@chromium.org</owner>
   <owner>chromeos-kiosk-eng@google.com</owner>
   <summary>
@@ -3156,7 +3156,7 @@
 </histogram>
 
 <histogram name="Enterprise.ReportingEvent.{EventType}.UploadSize" units="B"
-    expires_after="2026-03-31">
+    expires_after="2026-05-31">
   <owner>domfc@chromium.org</owner>
   <owner>xanth@google.com</owner>
   <owner>nancylanxiao@google.com</owner>
@@ -3334,7 +3334,7 @@
 </histogram>
 
 <histogram name="Enterprise.SkyVault.Migration.WriteAccessError" enum="Boolean"
-    expires_after="2026-03-22">
+    expires_after="2026-05-31">
   <owner>aidazolic@google.com</owner>
   <owner>src/chrome/browser/ash/policy/skyvault/OWNERS</owner>
   <summary>
@@ -3966,7 +3966,7 @@
 
 <histogram
     name="Enterprise.{ContentAnalysisProtocol}Request.{ContentAnalysisRequestType}.Result"
-    enum="SafeBrowsingBinaryUploadResult" expires_after="2026-03-22">
+    enum="SafeBrowsingBinaryUploadResult" expires_after="2026-05-31">
   <owner>nancylanxiao@google.com</owner>
   <owner>domfc@chromium.org</owner>
   <owner>cbe-cep-eng@google.com</owner>
@@ -4004,7 +4004,7 @@
 </histogram>
 
 <histogram name="Enterprise.{UploadEvent}.FileCount" units="count"
-    expires_after="2026-03-28">
+    expires_after="2026-05-31">
   <owner>alshawwa@chromium.org</owner>
   <owner>nancylanxiao@google.com</owner>
   <owner>cbe-cep-eng@google.com</owner>
@@ -4169,7 +4169,7 @@
 </histogram>
 
 <histogram name="EnterpriseCheck.IsRunningOnManagedProfileDuration" units="ms"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>nafisabedin@google.com</owner>
   <improvement direction="LOWER_IS_BETTER"/>
   <summary>
diff --git a/tools/metrics/histograms/metadata/event/histograms.xml b/tools/metrics/histograms/metadata/event/histograms.xml
index fd91223..a08ee239 100644
--- a/tools/metrics/histograms/metadata/event/histograms.xml
+++ b/tools/metrics/histograms/metadata/event/histograms.xml
@@ -247,7 +247,7 @@
 
 <histogram
     name="Event.Jank.ScrollUpdate.{ScrollSpeed}.{VsyncStatus}.FrameAboveJankyThreshold2"
-    units="ratio * 100" expires_after="2026-03-29">
+    units="ratio * 100" expires_after="2026-05-31">
   <owner>jonross@chromium.org</owner>
   <owner>woa-performance@google.com</owner>
   <improvement direction="LOWER_IS_BETTER"/>
@@ -553,7 +553,7 @@
 </histogram>
 
 <histogram name="Event.ScrollJank.DelayedFramesPercentage.PerScroll" units="%"
-    expires_after="2026-05-24">
+    expires_after="2026-05-31">
   <owner>kartarsingh@google.com</owner>
   <owner>jonross@chromium.org</owner>
   <owner>woa-performance-team@google.com</owner>
@@ -688,7 +688,7 @@
 </histogram>
 
 <histogram name="Event.ScrollJank.FrameStageCalculationResult"
-    enum="ScrollJankFrameStageCalculationResult" expires_after="2026-03-10">
+    enum="ScrollJankFrameStageCalculationResult" expires_after="2026-05-31">
   <owner>petrcermak@chromium.org</owner>
   <owner>kartarsingh@google.com</owner>
   <owner>jonross@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/extensions/histograms.xml b/tools/metrics/histograms/metadata/extensions/histograms.xml
index 49bcb10..6d25644 100644
--- a/tools/metrics/histograms/metadata/extensions/histograms.xml
+++ b/tools/metrics/histograms/metadata/extensions/histograms.xml
@@ -2562,7 +2562,7 @@
 
 <histogram
     name="Extensions.Functions.DeveloperPrivate.NoSuchExtensionErrorThrown"
-    enum="ExtensionFunctions" expires_after="2026-04-01">
+    enum="ExtensionFunctions" expires_after="2026-05-31">
   <owner>kelvinjiang@chromium.org</owner>
   <owner>extensions-core@chromium.org</owner>
   <summary>
@@ -3676,7 +3676,7 @@
 </histogram>
 
 <histogram name="Extensions.Messaging.NativeMessagingCount" units="extensions"
-    expires_after="2026-03-30">
+    expires_after="2026-05-31">
   <owner>kelvinjiang@chromium.org</owner>
   <owner>extensions-core@chromium.org</owner>
   <summary>
@@ -4026,7 +4026,7 @@
 </histogram>
 
 <histogram name="Extensions.ServiceWorkerBackground.MultiWorkerVersionIdMatch"
-    enum="Boolean" expires_after="2026-03-29">
+    enum="Boolean" expires_after="2026-05-31">
   <owner>andreaorru@chromium.org</owner>
   <owner>jlulejian@chromium.org</owner>
   <owner>extensions-core@chromium.org</owner>
@@ -4978,7 +4978,7 @@
 </histogram>
 
 <histogram name="Extensions.ZeroStatePromo.IphActionChromeWebStoreLink"
-    enum="WebStoreLinkClicked" expires_after="2026-03-29">
+    enum="WebStoreLinkClicked" expires_after="2026-05-31">
   <owner>uwyiming@chromium.org</owner>
   <owner>cws-consumer-team@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/fingerprint/histograms.xml b/tools/metrics/histograms/metadata/fingerprint/histograms.xml
index f96e462..e67be85 100644
--- a/tools/metrics/histograms/metadata/fingerprint/histograms.xml
+++ b/tools/metrics/histograms/metadata/fingerprint/histograms.xml
@@ -36,7 +36,7 @@
 </histogram>
 
 <histogram name="Fingerprint.Auth.ScanResult" enum="FingerprintScanResult"
-    expires_after="2026-03-30">
+    expires_after="2026-05-31">
   <owner>emaamari@google.com</owner>
   <owner>tomhughes@chromium.org</owner>
   <owner>cros-lurs@google.com</owner>
@@ -49,7 +49,7 @@
 </histogram>
 
 <histogram name="Fingerprint.Enroll.NumCaptures" units="captures"
-    expires_after="2026-03-30">
+    expires_after="2026-05-31">
   <owner>fsammoura@google.com</owner>
   <owner>chromeos-fingerprint@google.com</owner>
   <summary>
@@ -61,7 +61,7 @@
 </histogram>
 
 <histogram name="Fingerprint.Enroll.ScanResult" enum="FingerprintScanResult"
-    expires_after="2026-03-30">
+    expires_after="2026-05-31">
   <owner>emaamari@google.com</owner>
   <owner>tomhughes@chromium.org</owner>
   <owner>cros-lurs@google.com</owner>
@@ -308,7 +308,7 @@
 </histogram>
 
 <histogram name="Fingerprint.Unlock.AuthSuccessful" enum="BooleanSuccess"
-    expires_after="2026-03-30">
+    expires_after="2026-05-31">
   <owner>emaamari@google.com</owner>
   <owner>cros-lurs@google.com</owner>
   <summary>
@@ -322,7 +322,7 @@
 </histogram>
 
 <histogram name="Fingerprint.Unlock.EnrolledFingerCount" units="count"
-    expires_after="2026-03-30">
+    expires_after="2026-05-31">
   <owner>tomhughes@chromium.org</owner>
   <owner>chromeos-fingerprint@google.com</owner>
   <summary>
@@ -396,7 +396,7 @@
 </histogram>
 
 <histogram name="Fingerprint.Unlock.RecentAttemptsCountBeforeSuccess"
-    units="attempts" expires_after="2026-03-30">
+    units="attempts" expires_after="2026-05-31">
   <owner>fsammoura@google.com</owner>
   <owner>chromeos-fingerprint@google.com</owner>
   <summary>
@@ -426,7 +426,7 @@
 </histogram>
 
 <histogram name="Fingerprint.Unlock.Result" enum="FingerprintUnlockResult"
-    expires_after="2026-03-30">
+    expires_after="2026-05-31">
   <owner>emaamari@google.com</owner>
   <owner>tomhughes@chromium.org</owner>
   <owner>cros-lurs@google.com</owner>
@@ -438,7 +438,7 @@
 </histogram>
 
 <histogram name="Fingerprint.Unlock.{Outcome}.Duration.{Interval}" units="ms"
-    expires_after="2026-03-30">
+    expires_after="2026-05-31">
   <owner>dawidn@google.com</owner>
   <owner>chromeos-fingerprint@google.com</owner>
   <summary>
@@ -458,7 +458,7 @@
 </histogram>
 
 <histogram name="Fingerprint.UnlockEnabled" enum="BooleanEnabled"
-    expires_after="2026-03-30">
+    expires_after="2026-05-31">
   <owner>tomhughes@chromium.org</owner>
   <owner>chromeos-fingerprint@google.com</owner>
   <summary>
@@ -477,7 +477,7 @@
 </histogram>
 
 <histogram name="Fingerprint.Updater.Reason" enum="FingerprintUpdaterReason"
-    expires_after="2026-03-30">
+    expires_after="2026-05-31">
   <owner>tomhughes@chromium.org</owner>
   <owner>fsammoura@google.com</owner>
   <owner>chromeos-fingerprint@google.com</owner>
@@ -487,7 +487,7 @@
 </histogram>
 
 <histogram name="Fingerprint.Updater.Status" enum="FingerprintUpdaterStatus"
-    expires_after="2026-03-30">
+    expires_after="2026-05-31">
   <owner>tomhughes@chromium.org</owner>
   <owner>fsammoura@google.com</owner>
   <owner>chromeos-fingerprint@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/glic/histograms.xml b/tools/metrics/histograms/metadata/glic/histograms.xml
index e61a8740..2cd3705 100644
--- a/tools/metrics/histograms/metadata/glic/histograms.xml
+++ b/tools/metrics/histograms/metadata/glic/histograms.xml
@@ -362,7 +362,7 @@
 </histogram>
 
 <histogram name="Glic.Focus.Settings.Shortcut.Customized" enum="BooleanEnabled"
-    expires_after="2026-03-22">
+    expires_after="2026-05-31">
   <owner>bryantchandler@chromium.org</owner>
   <owner>iwells@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/gpu/enums.xml b/tools/metrics/histograms/metadata/gpu/enums.xml
index 659d772..2df046e 100644
--- a/tools/metrics/histograms/metadata/gpu/enums.xml
+++ b/tools/metrics/histograms/metadata/gpu/enums.xml
@@ -1177,6 +1177,13 @@
 
 <!-- LINT.ThenChange(//gpu/config/gpu_info.h:IntelGpuSeriesType) -->
 
+<enum name="IntelNpuDeviceId">
+  <int value="25662" label="0x643e, Intel Lunarlake"/>
+  <int value="32029" label="0x7d1d, Intel Meteorlake"/>
+  <int value="44317" label="0xad1d, Intel Arrowlake"/>
+  <int value="45118" label="0xb03e, Intel Pantherlake"/>
+</enum>
+
 <enum name="MaximumGLESVersion">
   <summary>The maximum GLES version supported.</summary>
   <int value="0" label="GLES2.0"/>
diff --git a/tools/metrics/histograms/metadata/gpu/histograms.xml b/tools/metrics/histograms/metadata/gpu/histograms.xml
index 9524b4f8..f116b71b 100644
--- a/tools/metrics/histograms/metadata/gpu/histograms.xml
+++ b/tools/metrics/histograms/metadata/gpu/histograms.xml
@@ -1869,6 +1869,16 @@
   <token key="ExoSurfaceCodePaths" variants="ExoSurfaceCodePaths"/>
 </histogram>
 
+<histogram name="NPU.Intel.DeviceId" enum="IntelNpuDeviceId"
+    expires_after="2026-06-27">
+  <owner>reillyg@chromium.org</owner>
+  <owner>src/services/webnn/OWNERS</owner>
+  <summary>
+    Record Intel NPU device ID when GPU process launches and sends GPUInfo to
+    browser process.
+  </summary>
+</histogram>
+
 <histogram name="Skia.Graphite.PipelineCache.PipelineUsesInEpoch" units="count"
     expires_after="2026-01-01">
   <owner>robertphillips@google.com</owner>
@@ -2139,7 +2149,7 @@
 </histogram>
 
 <histogram name="Viz.FrameIntervalDecider.ActualIntervalDefault" units="ms"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>boliu@chromium.org</owner>
   <owner>chrome-gpu-metric-alerts@chromium.org</owner>
   <summary>
@@ -2156,7 +2166,7 @@
 </histogram>
 
 <histogram name="Viz.FrameIntervalDecider.ActualIntervalFor{DecidedHz}hz"
-    units="ms" expires_after="2026-03-29">
+    units="ms" expires_after="2026-05-31">
   <owner>boliu@chromium.org</owner>
   <owner>chrome-gpu-metric-alerts@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/history/histograms.xml b/tools/metrics/histograms/metadata/history/histograms.xml
index 1f1311bf9..093c6cf 100644
--- a/tools/metrics/histograms/metadata/history/histograms.xml
+++ b/tools/metrics/histograms/metadata/history/histograms.xml
@@ -2822,7 +2822,7 @@
 </histogram>
 
 <histogram name="History.MonthlyHostCount" units="hosts"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>mpearson@chromium.org</owner>
   <owner>sky@chromium.org</owner>
   <component>1456716</component>
@@ -2834,7 +2834,7 @@
 </histogram>
 
 <histogram name="History.MonthlyURLCount" units="urls"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>mpearson@chromium.org</owner>
   <owner>sky@chromium.org</owner>
   <component>1456716</component>
diff --git a/tools/metrics/histograms/metadata/installer/histograms.xml b/tools/metrics/histograms/metadata/installer/histograms.xml
index d8bcbc4..7550569 100644
--- a/tools/metrics/histograms/metadata/installer/histograms.xml
+++ b/tools/metrics/histograms/metadata/installer/histograms.xml
@@ -159,7 +159,7 @@
 </histogram>
 
 <histogram name="Installer.PowerwashDays" units="powerwashdays"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>qianwan@google.com</owner>
   <owner>chromeos-data@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/ios/histograms.xml b/tools/metrics/histograms/metadata/ios/histograms.xml
index 3f0c6c9f..4eea5c6b 100644
--- a/tools/metrics/histograms/metadata/ios/histograms.xml
+++ b/tools/metrics/histograms/metadata/ios/histograms.xml
@@ -370,7 +370,7 @@
 </histogram>
 
 <histogram name="IOS.BackgroundRefresh.InitStage"
-    enum="InitStageDuringBackgroundRefreshType" expires_after="2026-03-29">
+    enum="InitStageDuringBackgroundRefreshType" expires_after="2026-05-31">
   <owner>fedegermi@google.com</owner>
   <owner>marq@chromium.org</owner>
   <summary>
@@ -3901,7 +3901,7 @@
 </histogram>
 
 <histogram name="IOS.Notifications.SafetyCheck.Interaction"
-    enum="IOSSafetyCheckNotificationType" expires_after="2026-03-29">
+    enum="IOSSafetyCheckNotificationType" expires_after="2026-05-31">
   <owner>bwwilliams@google.com</owner>
   <owner>scottyoder@google.com</owner>
   <owner>bling-pandamonium@google.com</owner>
@@ -3958,7 +3958,7 @@
 
 <histogram name="IOS.Notifications.SendTab.Received"
     enum="PushNotificationSettingsAuthorizationStatus"
-    expires_after="2026-02-28">
+    expires_after="2026-05-31">
   <owner>hiramahmood@google.com</owner>
   <owner>bling-pandamonium@google.com</owner>
   <summary>
@@ -4816,7 +4816,7 @@
 </histogram>
 
 <histogram name="IOS.PersistTabContext.PurgeFileResult" enum="BooleanSuccess"
-    expires_after="2026-03-01">
+    expires_after="2026-05-31">
   <owner>cloutierc@google.com</owner>
   <owner>nicolasmacbeth@google.com</owner>
   <owner>bling-alchemy-eng@google.com</owner>
@@ -4828,7 +4828,7 @@
 </histogram>
 
 <histogram name="IOS.PersistTabContext.ReadResult"
-    enum="IOSPersistTabContextReadResult" expires_after="2026-03-01">
+    enum="IOSPersistTabContextReadResult" expires_after="2026-05-31">
   <owner>cloutierc@google.com</owner>
   <owner>nicolasmacbeth@google.com</owner>
   <owner>bling-alchemy-eng@google.com</owner>
@@ -5148,7 +5148,7 @@
 </histogram>
 
 <histogram name="IOS.PushNotification.EnabledPermisisons"
-    enum="PushNotificationPermissionPromptStatus" expires_after="2026-03-15">
+    enum="PushNotificationPermissionPromptStatus" expires_after="2026-05-31">
   <owner>danieltwhite@google.com</owner>
   <owner>scottyoder@google.com</owner>
   <summary>
@@ -5159,7 +5159,7 @@
 </histogram>
 
 <histogram name="IOS.PushNotification.EnabledPermisisons.{Status}"
-    enum="PushNotificationOptInAccessPoint" expires_after="2026-03-15">
+    enum="PushNotificationOptInAccessPoint" expires_after="2026-05-31">
   <owner>thegreenfrog@google.com</owner>
   <owner>bling-gsu-pod@google.com</owner>
   <summary>
@@ -5396,7 +5396,7 @@
 </histogram>
 
 <histogram name="IOS.ReaderMode.Distiller.Latency" units="ms"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>fernandex@google.com</owner>
   <owner>bling-squid-squad@google.com</owner>
   <summary>
@@ -5408,7 +5408,7 @@
 </histogram>
 
 <histogram name="IOS.ReaderMode.Distiller.Result"
-    enum="ReaderModeDistillerOutcome" expires_after="2026-03-29">
+    enum="ReaderModeDistillerOutcome" expires_after="2026-05-31">
   <owner>fernandex@google.com</owner>
   <owner>qpubert@google.com</owner>
   <owner>bling-squid-squad@google.com</owner>
@@ -5451,7 +5451,7 @@
 </histogram>
 
 <histogram name="IOS.ReaderMode.Heuristic.Latency" units="ms"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>fernandex@google.com</owner>
   <owner>bling-squid-squad@google.com</owner>
   <summary>
@@ -5648,7 +5648,7 @@
 </histogram>
 
 <histogram name="IOS.SafariImport.EntryPoint{EntryPoint}.Action"
-    enum="SafariDataImportEntryPointAction" expires_after="2026-03-29">
+    enum="SafariDataImportEntryPointAction" expires_after="2026-05-31">
   <owner>ginnyhuang@chromium.org</owner>
   <owner>bling-pandamonium@google.com</owner>
   <summary>
@@ -5664,7 +5664,7 @@
 </histogram>
 
 <histogram name="IOS.SafariImport.ExportEducationAction"
-    enum="SafariDataImportExportEducationAction" expires_after="2026-03-29">
+    enum="SafariDataImportExportEducationAction" expires_after="2026-05-31">
   <owner>ginnyhuang@chromium.org</owner>
   <owner>bling-pandamonium@google.com</owner>
   <summary>
@@ -6057,7 +6057,7 @@
 </histogram>
 
 <histogram name="IOS.SaveToDrive.UploadTask.NumberOfAttempts.{FinalState}"
-    units="count" expires_after="2026-02-12">
+    units="count" expires_after="2026-05-31">
   <owner>qpubert@google.com</owner>
   <owner>olivierrobin@chromium.org</owner>
   <owner>bling-team@google.com</owner>
@@ -7396,7 +7396,7 @@
 </histogram>
 
 <histogram name="IOS.{AppVersion}WarmStartCount" units="launches"
-    expires_after="2026-03-30">
+    expires_after="2026-05-31">
   <owner>michaeldo@chromium.org</owner>
   <owner>bling-team@google.com</owner>
   <summary>
@@ -7484,7 +7484,7 @@
 
 <histogram
     name="ManualFallback.VisibleSuggestions.ExpandIcon.OpenPaymentMethods"
-    units="Suggestions" expires_after="2026-03-29">
+    units="Suggestions" expires_after="2026-05-31">
   <owner>noemies@google.com</owner>
   <owner>sugoi@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/kiosk/histograms.xml b/tools/metrics/histograms/metadata/kiosk/histograms.xml
index 477eb27c..1c4bc126 100644
--- a/tools/metrics/histograms/metadata/kiosk/histograms.xml
+++ b/tools/metrics/histograms/metadata/kiosk/histograms.xml
@@ -53,40 +53,6 @@
   </summary>
 </histogram>
 
-<histogram name="Kiosk.ChromeApp.ExtensionUpdateDuration.{Result}" units="ms"
-    expires_after="2025-06-15">
-  <owner>yixie@chromium.org</owner>
-  <owner>chromeos-kiosk-eng@google.com</owner>
-  <summary>
-    ChromeOS only. Records the duration for checking updates of secondary apps
-    and extensions for a Kiosk Chrome app while launching.
-  </summary>
-  <token key="Result">
-    <variant name="HasUpdate" summary="found updates"/>
-    <variant name="NoUpdate" summary="no update found"/>
-  </token>
-</histogram>
-
-<histogram name="Kiosk.ChromeApp.ExtensionUpdateError"
-    enum="ExtensionInstallationFailureReason" expires_after="2025-06-15">
-  <owner>yixie@chromium.org</owner>
-  <owner>chromeos-kiosk-eng@google.com</owner>
-  <summary>
-    ChromeOS only. Records the reason for failed secondary apps and extensions
-    updates while a Kiosk Chrome app is launching.
-  </summary>
-</histogram>
-
-<histogram name="Kiosk.ChromeApp.ExternalUpdateSuccess" enum="BooleanYesNo"
-    expires_after="2025-06-15">
-  <owner>yixie@chromium.org</owner>
-  <owner>chromeos-kiosk-eng@google.com</owner>
-  <summary>
-    ChromeOS only. Records whether Kiosk Chrome app update via external USB
-    drive is successful.
-  </summary>
-</histogram>
-
 <histogram name="Kiosk.ChromeApp.PrimaryAppInSessionUpdate" units="times"
     expires_after="2025-06-15">
   <owner>yixie@chromium.org</owner>
@@ -97,16 +63,6 @@
   </summary>
 </histogram>
 
-<histogram name="Kiosk.ChromeApp.PrimaryAppInstallError"
-    enum="KioskPrimaryAppDownloadResult" expires_after="2025-06-15">
-  <owner>yixie@chromium.org</owner>
-  <owner>chromeos-kiosk-eng@google.com</owner>
-  <summary>
-    ChromeOS only. Records error for failed initial install of primary Chrome
-    app in Kiosk.
-  </summary>
-</histogram>
-
 <histogram name="Kiosk.ChromeApp.PrimaryAppUpdateResult"
     enum="KioskPrimaryAppDownloadResult" expires_after="2026-02-22">
   <owner>yixie@chromium.org</owner>
@@ -177,7 +133,7 @@
 </histogram>
 
 <histogram name="Kiosk.LaunchDuration.{KioskType}" units="ms"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>yixie@chromium.org</owner>
   <owner>chromeos-kiosk-eng@google.com</owner>
   <summary>Records the total duration it takes to launch a kiosk app.</summary>
diff --git a/tools/metrics/histograms/metadata/language/histograms.xml b/tools/metrics/histograms/metadata/language/histograms.xml
index a84368e..9a81ad8 100644
--- a/tools/metrics/histograms/metadata/language/histograms.xml
+++ b/tools/metrics/histograms/metadata/language/histograms.xml
@@ -197,7 +197,7 @@
 </histogram>
 
 <histogram name="LanguageSettings.AppLanguagePrompt.Action"
-    enum="LanguageSettingsAppLanguagePromptAction" expires_after="2026-03-24">
+    enum="LanguageSettingsAppLanguagePromptAction" expires_after="2026-05-31">
   <owner>perrier@chromium.org</owner>
   <owner>chrome-language@google.com</owner>
   <summary>
@@ -332,7 +332,7 @@
 </histogram>
 
 <histogram name="LanguageUsage.AcceptLanguage.Count2" units="count"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>victortan@chromium.org</owner>
   <owner>katabolism-finch@google.com</owner>
   <summary>
@@ -344,7 +344,7 @@
 </histogram>
 
 <histogram name="LanguageUsage.AcceptLanguage.FirstAcceptLanguage"
-    enum="LanguageName" expires_after="2026-03-29">
+    enum="LanguageName" expires_after="2026-05-31">
   <owner>victortan@chromium.org</owner>
   <owner>katabolism-finch@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/magic_stack/histograms.xml b/tools/metrics/histograms/metadata/magic_stack/histograms.xml
index d7cbbbf7..f94243e9 100644
--- a/tools/metrics/histograms/metadata/magic_stack/histograms.xml
+++ b/tools/metrics/histograms/metadata/magic_stack/histograms.xml
@@ -52,7 +52,7 @@
 </variants>
 
 <histogram name="MagicStack.Clank.NewTabPage.ContextMenu.OpenCustomizeSettings"
-    enum="ModuleType" expires_after="2026-03-29">
+    enum="ModuleType" expires_after="2026-05-31">
   <owner>hanxi@chromium.org</owner>
   <owner>xinyiji@chromium.org</owner>
   <summary>
@@ -63,7 +63,7 @@
 </histogram>
 
 <histogram name="MagicStack.Clank.NewTabPage.ContextMenu.RemoveModuleV2"
-    enum="ModuleType" expires_after="2026-03-29">
+    enum="ModuleType" expires_after="2026-05-31">
   <owner>hanxi@chromium.org</owner>
   <owner>xinyiji@chromium.org</owner>
   <summary>
@@ -73,7 +73,7 @@
 </histogram>
 
 <histogram name="MagicStack.Clank.NewTabPage.ContextMenu.ShownV2"
-    enum="ModuleType" expires_after="2026-03-29">
+    enum="ModuleType" expires_after="2026-05-31">
   <owner>hanxi@chromium.org</owner>
   <owner>xinyiji@chromium.org</owner>
   <summary>
@@ -83,7 +83,7 @@
 </histogram>
 
 <histogram name="MagicStack.Clank.NewTabPage.Module.Click" enum="ModuleType"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>hanxi@chromium.org</owner>
   <owner>xinyiji@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/media/histograms.xml b/tools/metrics/histograms/metadata/media/histograms.xml
index 5c383a8..f981024 100644
--- a/tools/metrics/histograms/metadata/media/histograms.xml
+++ b/tools/metrics/histograms/metadata/media/histograms.xml
@@ -571,7 +571,7 @@
 
 <histogram
     name="Media.Audio.Android.AAudioFramesPerBurst.{Direction}{AudioLatencyTag}"
-    units="frames" expires_after="2026-03-29">
+    units="frames" expires_after="2026-05-31">
   <owner>tguilbert@chromium.org</owner>
   <owner>media-dev-uma@chromium.org</owner>
   <summary>
@@ -585,7 +585,7 @@
 
 <histogram
     name="Media.Audio.Android.AAudioFramesPerBurstChanged.{Direction}{AudioLatencyTag}"
-    enum="Boolean" expires_after="2026-03-29">
+    enum="Boolean" expires_after="2026-05-31">
   <owner>tguilbert@chromium.org</owner>
   <owner>media-dev-uma@chromium.org</owner>
   <summary>
@@ -737,7 +737,7 @@
 </histogram>
 
 <histogram name="Media.Audio.Autoplay" enum="AutoplaySource"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>evliu@google.com</owner>
   <owner>dalecurtis@chromium.org</owner>
   <owner>media-dev-uma@chromium.org</owner>
@@ -829,7 +829,7 @@
 </histogram>
 
 <histogram name="Media.Audio.Capture.Mac.MicSystemPermission.Startup"
-    enum="SystemMediaCapturePermission" expires_after="2026-03-29">
+    enum="SystemMediaCapturePermission" expires_after="2026-05-31">
   <owner>toprice@chromium.org</owner>
   <owner>video-cmi-apis@google.com</owner>
   <owner>webrtc-audio-uma@google.com</owner>
@@ -1062,7 +1062,7 @@
 </histogram>
 
 <histogram name="Media.Audio.Capture.Win.InitError" enum="Hresult"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>olka@chromium.org</owner>
   <owner>tommi@chromium.org</owner>
   <owner>guidou@chromium.org</owner>
@@ -1485,7 +1485,7 @@
 </histogram>
 
 <histogram name="Media.Audio.OutputDeviceMixer.MixedPlaybackStatus"
-    enum="AudioOutputDeviceMixerMixedPlaybackStatus" expires_after="2026-03-09">
+    enum="AudioOutputDeviceMixerMixedPlaybackStatus" expires_after="2026-05-31">
   <owner>olka@chromium.org</owner>
   <owner>tguilbert@chromium.org</owner>
   <owner>webrtc-audio-uma@google.com</owner>
@@ -6284,7 +6284,7 @@
 </histogram>
 
 <histogram name="Media.Remoting.VideoNaturalWidth" units="pixels"
-    expires_after="2026-03-12">
+    expires_after="2026-05-31">
   <owner>jophba@chromium.org</owner>
   <owner>openscreen-eng@google.com</owner>
   <summary>
@@ -7074,7 +7074,7 @@
 </histogram>
 
 <histogram name="Media.Video.Autoplay" enum="AutoplaySource"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>evliu@google.com</owner>
   <owner>dalecurtis@chromium.org</owner>
   <owner>media-dev-uma@chromium.org</owner>
@@ -7099,7 +7099,7 @@
 </histogram>
 
 <histogram name="Media.Video.Autoplay.Muted.PlayMethod.OffscreenDuration"
-    units="ms" expires_after="2026-03-29">
+    units="ms" expires_after="2026-05-31">
   <owner>evliu@google.com</owner>
   <owner>dalecurtis@chromium.org</owner>
   <owner>media-dev-uma@chromium.org</owner>
@@ -7645,7 +7645,7 @@
 </histogram>
 
 <histogram name="Media.VideoCapture.Win.ErrorEvent" enum="Hresult"
-    expires_after="2026-03-30">
+    expires_after="2026-05-31">
   <owner>toprice@chromium.org</owner>
   <owner>hta@chromium.org</owner>
   <owner>video-cmi-apis@google.com</owner>
@@ -7741,7 +7741,7 @@
 </histogram>
 
 <histogram name="Media.VideoEncoder.D3D12VEA.EncodeStatus.{VideoCodec}"
-    enum="EncoderStatus" expires_after="2026-04-01">
+    enum="EncoderStatus" expires_after="2026-05-31">
   <owner>eugene@chromium.org</owner>
   <owner>media-dev-uma@chromium.org</owner>
   <summary>
@@ -7753,7 +7753,7 @@
 </histogram>
 
 <histogram name="Media.VideoEncoder.D3D12VEA.InitSuccessRate.{VideoCodec}"
-    enum="VEAInitSuccessRate" expires_after="2026-04-01">
+    enum="VEAInitSuccessRate" expires_after="2026-05-31">
   <owner>eugene@chromium.org</owner>
   <owner>media-dev-uma@chromium.org</owner>
   <summary>
@@ -7766,7 +7766,7 @@
 </histogram>
 
 <histogram name="Media.VideoEncoder.NDKVEA.EncodeStatus.{VideoCodec}"
-    enum="EncoderStatus" expires_after="2026-04-01">
+    enum="EncoderStatus" expires_after="2026-05-31">
   <owner>eugene@chromium.org</owner>
   <owner>media-dev-uma@chromium.org</owner>
   <summary>
@@ -7778,7 +7778,7 @@
 </histogram>
 
 <histogram name="Media.VideoEncoder.NDKVEA.InitStatus.{VideoCodec}"
-    enum="EncoderStatus" expires_after="2026-04-01">
+    enum="EncoderStatus" expires_after="2026-05-31">
   <owner>eugene@chromium.org</owner>
   <owner>media-dev-uma@chromium.org</owner>
   <summary>
@@ -7789,7 +7789,7 @@
 </histogram>
 
 <histogram name="Media.VideoEncoder.{Impl}.EncodingLatency.{VideoCodec}"
-    units="ms" expires_after="2026-04-01">
+    units="ms" expires_after="2026-05-31">
   <owner>eugene@chromium.org</owner>
   <owner>media-dev-uma@chromium.org</owner>
   <summary>
@@ -7818,7 +7818,7 @@
 </histogram>
 
 <histogram name="Media.VideoEncoder.{Usage}.{Impl}.Frames" units="frames"
-    expires_after="2026-02-22">
+    expires_after="2026-04-01">
   <owner>hiroh@chromium.org</owner>
   <owner>media-dev-uma@chromium.org</owner>
   <summary>
@@ -7830,7 +7830,7 @@
 </histogram>
 
 <histogram name="Media.VideoEncoder.{Usage}.{Impl}.Height" units="pixels"
-    expires_after="2025-12-15">
+    expires_after="2026-04-26">
   <owner>hiroh@chromium.org</owner>
   <owner>media-dev-uma@chromium.org</owner>
   <summary>
@@ -7867,7 +7867,7 @@
 </histogram>
 
 <histogram name="Media.VideoEncoder.{Usage}.{Impl}.SVC"
-    enum="SVCScalabilityMode" expires_after="2025-12-25">
+    enum="SVCScalabilityMode" expires_after="2026-04-26">
   <owner>hiroh@chromium.org</owner>
   <owner>media-dev-uma@chromium.org</owner>
   <summary>
@@ -8563,9 +8563,8 @@
 </histogram>
 
 <histogram name="MediaRouter.Cast.Channel.ConnectResult" enum="BooleanSuccess"
-    expires_after="2025-12-07">
+    expires_after="2026-06-01">
   <owner>muyaoxu@google.com</owner>
-  <owner>mfoltz@chromium.org</owner>
   <owner>openscreen-eng@google.com</owner>
   <summary>
     Whether opening Cast channel succeeds or not. Recorded when all opening
@@ -8574,9 +8573,8 @@
 </histogram>
 
 <histogram name="MediaRouter.Cast.Channel.Error"
-    enum="MediaRouterCastChannelError" expires_after="2025-12-21">
+    enum="MediaRouterCastChannelError" expires_after="2026-06-01">
   <owner>muyaoxu@google.com</owner>
-  <owner>mfoltz@chromium.org</owner>
   <owner>openscreen-eng@google.com</owner>
   <summary>
     Errors encountered on a Cast channel. Recorded when a Cast channel fails to
@@ -8586,9 +8584,8 @@
 </histogram>
 
 <histogram name="MediaRouter.Cast.Discovery.ConnectedDevicesCount"
-    units="devices" expires_after="2025-12-09">
+    units="devices" expires_after="2026-06-01">
   <owner>muyaoxu@google.com</owner>
-  <owner>mfoltz@chromium.org</owner>
   <owner>openscreen-eng@google.com</owner>
   <summary>
     The number of connected Cast devices. Recorded when browser finishes
@@ -8883,7 +8880,7 @@
 </histogram>
 
 <histogram name="MediaRouter.Provider.CreateRoute.Result{MediaRouteProvider}"
-    enum="MediaRouteProviderResult" expires_after="2025-12-07">
+    enum="MediaRouteProviderResult" expires_after="2026-06-01">
   <owner>muyaoxu@google.com</owner>
   <owner>mfoltz@chromium.org</owner>
   <owner>openscreen-eng@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/memory/histograms.xml b/tools/metrics/histograms/metadata/memory/histograms.xml
index a1622d9..46874ce 100644
--- a/tools/metrics/histograms/metadata/memory/histograms.xml
+++ b/tools/metrics/histograms/metadata/memory/histograms.xml
@@ -1362,7 +1362,7 @@
 
 <histogram
     name="Memory.Experimental.Browser2.Small{ProcessMemoryAllocatorSmall2}"
-    units="KB" expires_after="2026-03-29">
+    units="KB" expires_after="2026-05-31">
   <owner>erikchen@chromium.org</owner>
   <owner>ssid@chromium.org</owner>
   <owner>lizeb@chromium.org</owner>
@@ -1471,7 +1471,7 @@
 
 <histogram
     name="Memory.Experimental.Gpu2.Custom{ProcessMemoryAllocatorCustom2}"
-    units="bytes" expires_after="2026-03-29">
+    units="bytes" expires_after="2026-05-31">
   <owner>sashamcintosh@chromium.org</owner>
   <owner>chromeos-gfx@chromium.org</owner>
   <owner>erikchen@chromium.org</owner>
@@ -1726,7 +1726,7 @@
 
 <histogram
     name="Memory.Experimental.Renderer2.Small{ProcessMemoryAllocatorSmall2}"
-    units="KB" expires_after="2026-03-29">
+    units="KB" expires_after="2026-05-31">
   <owner>erikchen@chromium.org</owner>
   <owner>ssid@chromium.org</owner>
   <owner>lizeb@chromium.org</owner>
@@ -1741,7 +1741,7 @@
 
 <histogram
     name="Memory.Experimental.Renderer2.Tiny{ProcessMemoryAllocatorTiny2}"
-    units="bytes" expires_after="2026-01-25">
+    units="bytes" expires_after="2026-05-31">
   <owner>sashamcintosh@chromium.org</owner>
   <owner>chromeos-gfx@chromium.org</owner>
   <owner>erikchen@chromium.org</owner>
@@ -2623,7 +2623,7 @@
 </histogram>
 
 <histogram name="Memory.Renderer.MappingsCount" units="count"
-    expires_after="2026-03-01">
+    expires_after="2026-05-31">
   <owner>lizeb@chromium.org</owner>
   <owner>chrome-memory@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/navigation/histograms.xml b/tools/metrics/histograms/metadata/navigation/histograms.xml
index 5e84b4e..4831a61 100644
--- a/tools/metrics/histograms/metadata/navigation/histograms.xml
+++ b/tools/metrics/histograms/metadata/navigation/histograms.xml
@@ -2307,7 +2307,7 @@
 </histogram>
 
 <histogram name="Navigation.RenderFrameDelete" units="ms"
-    expires_after="2026-03-31">
+    expires_after="2026-05-31">
   <owner>rakina@chromium.org</owner>
   <owner>chrome-security-architecture@google.com</owner>
   <summary>
@@ -2317,7 +2317,7 @@
 </histogram>
 
 <histogram name="Navigation.RenderFrameHostConstructor" units="ms"
-    expires_after="2026-03-31">
+    expires_after="2026-05-31">
   <owner>rakina@chromium.org</owner>
   <owner>chrome-security-architecture@google.com</owner>
   <summary>
@@ -2327,7 +2327,7 @@
 </histogram>
 
 <histogram name="Navigation.RenderFrameHostDestructor" units="ms"
-    expires_after="2026-03-31">
+    expires_after="2026-05-31">
   <owner>rakina@chromium.org</owner>
   <owner>chrome-security-architecture@google.com</owner>
   <summary>
@@ -2347,7 +2347,7 @@
 </histogram>
 
 <histogram name="Navigation.RenderViewHostDestructor" units="ms"
-    expires_after="2026-03-31">
+    expires_after="2026-05-31">
   <owner>rakina@chromium.org</owner>
   <owner>chrome-security-architecture@google.com</owner>
   <summary>
@@ -2985,7 +2985,7 @@
 </histogram>
 
 <histogram name="Navigation.URLLoader.HasClientHintsControllerDelegate"
-    enum="Boolean" expires_after="2026-03-29">
+    enum="Boolean" expires_after="2026-05-31">
   <owner>yyanagisawa@chromium.org</owner>
   <owner>chrome-loading@google.com</owner>
   <summary>
@@ -3274,7 +3274,7 @@
 </histogram>
 
 <histogram name="Navigation.{Stage}ToCompositorCreation" units="ms"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>rakina@chromium.org</owner>
   <owner>bfcache-dev@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/net/histograms.xml b/tools/metrics/histograms/metadata/net/histograms.xml
index 6fb0b320..2df88ce 100644
--- a/tools/metrics/histograms/metadata/net/histograms.xml
+++ b/tools/metrics/histograms/metadata/net/histograms.xml
@@ -852,7 +852,7 @@
 </histogram>
 
 <histogram name="HttpCache.NoVarySearch.InitRenameError.{Directory}.{File}"
-    enum="PlatformFileError" expires_after="2026-03-22">
+    enum="PlatformFileError" expires_after="2026-05-31">
   <owner>ricea@chromium.org</owner>
   <owner>net-dev@chromium.org</owner>
   <summary>
@@ -2144,7 +2144,7 @@
 </histogram>
 
 <histogram name="Net.DeviceBoundSessions.RequestDeferralDecision2"
-    enum="DeviceBoundSessionUsage" expires_after="2026-03-29">
+    enum="DeviceBoundSessionUsage" expires_after="2026-05-31">
   <owner>thefrog@chromium.org</owner>
   <owner>chrome-counter-abuse-alerts@google.com</owner>
   <summary>
@@ -2242,7 +2242,7 @@
 </histogram>
 
 <histogram name="Net.DeviceBoundSessions.{RequestType}.Network.Result"
-    enum="CombinedHttpResponseAndNetErrorCode" expires_after="2026-03-29">
+    enum="CombinedHttpResponseAndNetErrorCode" expires_after="2026-05-31">
   <owner>thefrog@chromium.org</owner>
   <owner>chrome-counter-abuse-alerts@google.com</owner>
   <summary>
@@ -3620,7 +3620,7 @@
 </histogram>
 
 <histogram name="Net.HttpStreamPool.Existing{Protocol}SessionFoundTime"
-    units="ms" expires_after="2026-03-29">
+    units="ms" expires_after="2026-05-31">
   <owner>bashi@chromium.org</owner>
   <owner>blink-network-stack@google.com</owner>
   <summary>
@@ -4072,64 +4072,6 @@
 </histogram>
 
 <histogram
-    name="Net.NetworkTransaction.GenerateProxyAuthTokenBlocked{HostType}.{Protocol}"
-    enum="Boolean" expires_after="2026-01-11">
-  <owner>bashi@chromium.org</owner>
-  <owner>src/net/OWNERS</owner>
-  <summary>
-    Records whether generating a proxy auth token is blocked or not. Recorded
-    every time HttpAuthController::MaybeGenerateAuthToken() is called in
-    HttpNetworkTransaction::DoGenerateProxyAuthToken().
-  </summary>
-  <token key="HostType" variants="NetworkTransactionBlockedHostType"/>
-  <token key="Protocol" variants="NetworkTransactionBlockedProtocol"/>
-</histogram>
-
-<histogram
-    name="Net.NetworkTransaction.GenerateProxyAuthTokenBlockTime{HostType}.{Protocol}"
-    units="ms" expires_after="2026-03-17">
-  <owner>bashi@chromium.org</owner>
-  <owner>src/net/OWNERS</owner>
-  <summary>
-    The time taken for a proxy auth token generation to finish if the generation
-    was blocked. Recorded in
-    HttpNetworkTransaction::DoGenerateProxyAuthTokenComplete() when the
-    generation is complete.
-  </summary>
-  <token key="HostType" variants="NetworkTransactionBlockedHostType"/>
-  <token key="Protocol" variants="NetworkTransactionBlockedProtocol"/>
-</histogram>
-
-<histogram
-    name="Net.NetworkTransaction.GenerateServerAuthTokenBlocked{HostType}.{Protocol}"
-    enum="Boolean" expires_after="2026-01-11">
-  <owner>bashi@chromium.org</owner>
-  <owner>src/net/OWNERS</owner>
-  <summary>
-    Records whether generating a server auth token is blocked or not. Recorded
-    every time HttpAuthController::MaybeGenerateAuthToken() is called in
-    HttpNetworkTransaction::DoGenerateServerAuthToken().
-  </summary>
-  <token key="HostType" variants="NetworkTransactionBlockedHostType"/>
-  <token key="Protocol" variants="NetworkTransactionBlockedProtocol"/>
-</histogram>
-
-<histogram
-    name="Net.NetworkTransaction.GenerateServerAuthTokenBlockTime{HostType}.{Protocol}"
-    units="ms" expires_after="2026-03-17">
-  <owner>bashi@chromium.org</owner>
-  <owner>src/net/OWNERS</owner>
-  <summary>
-    The time taken for a server auth token generation to finish if the
-    generation was blocked. Recorded in
-    HttpNetworkTransaction::DoGenerateServerAuthTokenComplete() when the
-    generation is complete.
-  </summary>
-  <token key="HostType" variants="NetworkTransactionBlockedHostType"/>
-  <token key="Protocol" variants="NetworkTransactionBlockedProtocol"/>
-</histogram>
-
-<histogram
     name="Net.NetworkTransaction.InitializeStreamBlocked{HostType}.{Protocol}"
     enum="Boolean" expires_after="2026-05-17">
   <owner>bashi@chromium.org</owner>
@@ -4457,7 +4399,7 @@
 </histogram>
 
 <histogram name="Net.QuicConnection.ServerAllowsActiveMigrationForMultiPort"
-    enum="BooleanEnabled" expires_after="2026-03-29">
+    enum="BooleanEnabled" expires_after="2026-05-31">
   <owner>renjietang@chromium.org</owner>
   <owner>src/net/OWNERS</owner>
   <summary>
@@ -7242,7 +7184,7 @@
 
 <histogram
     name="Net.SessionCreate.GoogleSearch.Preconnect2.{ProtocolType}.IsSessionReused"
-    enum="Boolean" expires_after="2026-03-29">
+    enum="Boolean" expires_after="2026-05-31">
   <owner>suzukikeita@chromium.org</owner>
   <owner>blink-network-stack@google.com</owner>
   <summary>
@@ -7902,7 +7844,7 @@
 </histogram>
 
 <histogram name="Net.SqlDiskCache.Backend.{CheckpointType}.{ResultType}"
-    units="pages" expires_after="2026-03-29">
+    units="pages" expires_after="2026-05-31">
   <owner>horo@chromium.org</owner>
   <owner>src/net/OWNERS</owner>
   <summary>
@@ -7918,7 +7860,7 @@
 </histogram>
 
 <histogram name="Net.SqlDiskCache.Backend.{CheckpointType}.{TimeType}"
-    units="microseconds" expires_after="2026-03-29">
+    units="microseconds" expires_after="2026-05-31">
   <owner>horo@chromium.org</owner>
   <owner>src/net/OWNERS</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/network/histograms.xml b/tools/metrics/histograms/metadata/network/histograms.xml
index b64e246..cecbfd86 100644
--- a/tools/metrics/histograms/metadata/network/histograms.xml
+++ b/tools/metrics/histograms/metadata/network/histograms.xml
@@ -2834,7 +2834,7 @@
 </histogram>
 
 <histogram name="Network.Shill.Eap.Event" enum="NetworkEapConnectionEvent"
-    expires_after="2026-03-31">
+    expires_after="2026-05-31">
   <owner>olsa@google.com</owner>
   <owner>chromeos-commercial-networking@google.com</owner>
   <summary>
@@ -2843,7 +2843,7 @@
 </histogram>
 
 <histogram name="Network.Shill.Eap.EventCaCertExperiment1"
-    enum="NetworkEapConnectionEvent" expires_after="2026-03-31">
+    enum="NetworkEapConnectionEvent" expires_after="2026-05-31">
   <owner>olsa@google.com</owner>
   <owner>chromeos-commercial-networking@google.com</owner>
   <summary>
@@ -2854,7 +2854,7 @@
 </histogram>
 
 <histogram name="Network.Shill.Eap.EventCaCertExperiment2"
-    enum="NetworkEapConnectionEvent" expires_after="2026-03-31">
+    enum="NetworkEapConnectionEvent" expires_after="2026-05-31">
   <owner>olsa@google.com</owner>
   <owner>chromeos-commercial-networking@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/new_tab_page/histograms.xml b/tools/metrics/histograms/metadata/new_tab_page/histograms.xml
index 6d9a8fe..6a7ee7e 100644
--- a/tools/metrics/histograms/metadata/new_tab_page/histograms.xml
+++ b/tools/metrics/histograms/metadata/new_tab_page/histograms.xml
@@ -696,7 +696,7 @@
 </histogram>
 
 <histogram name="NewTabPage.Drive.FileClick" units="index"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>rtatum@google.com</owner>
   <owner>tiborg@chromium.org</owner>
   <owner>chrome-desktop-ntp@google.com</owner>
@@ -767,7 +767,7 @@
 </histogram>
 
 <histogram name="NewTabPage.Footer.Click" enum="FooterElement"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>temao@chromium.org</owner>
   <owner>tiborg@chromium.org</owner>
   <owner>chrome-desktop-ntp@google.com</owner>
@@ -783,7 +783,7 @@
 </histogram>
 
 <histogram name="NewTabPage.Footer.CustomizeChromeOpened"
-    enum="FooterCustomizeChromeEntryPoint" expires_after="2026-03-29">
+    enum="FooterCustomizeChromeEntryPoint" expires_after="2026-05-31">
   <owner>temao@chromium.org</owner>
   <owner>tiborg@chromium.org</owner>
   <owner>chrome-desktop-ntp@google.com</owner>
@@ -1776,7 +1776,7 @@
 </histogram>
 
 <histogram name="NewTabPage.MostVisited.NumberOfCustomTilesOnFirstNtp"
-    units="units" expires_after="2026-03-29">
+    units="units" expires_after="2026-05-31">
   <owner>fredmello@chromium.org</owner>
   <owner>huangs@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/notifications/histograms.xml b/tools/metrics/histograms/metadata/notifications/histograms.xml
index faaf5de4..308bb6c 100644
--- a/tools/metrics/histograms/metadata/notifications/histograms.xml
+++ b/tools/metrics/histograms/metadata/notifications/histograms.xml
@@ -129,7 +129,7 @@
 </histogram>
 
 <histogram name="Notifications.Android.ImageMemorySizeInKB" units="KB"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>peilinwang@google.com</owner>
   <owner>clank-performance-team@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/omnibox/histograms.xml b/tools/metrics/histograms/metadata/omnibox/histograms.xml
index ffbd5ce8..6afbd94 100644
--- a/tools/metrics/histograms/metadata/omnibox/histograms.xml
+++ b/tools/metrics/histograms/metadata/omnibox/histograms.xml
@@ -1939,7 +1939,7 @@
   </summary>
 </histogram>
 
-<histogram name="Omnibox.InputToAcceptNonAction" units="ms"
+<histogram name="Omnibox.InputToAcceptNonAction" units="microseconds"
     expires_after="2026-11-12">
   <owner>kouhei@google.com</owner>
   <owner>chrome-loading@google.com</owner>
@@ -1953,10 +1953,12 @@
     Omnibox.SuggestionUsed.Search.InputToNavigationStart2. See also
     Omnibox.InputToOpenSelection for the breakdown of input routing latency. See
     also Omnibox.InputToExecuteAction which covers the other branch.
+
+    This histogram only records metrics on machines with high-resolution clocks.
   </summary>
 </histogram>
 
-<histogram name="Omnibox.InputToExecuteAction" units="ms"
+<histogram name="Omnibox.InputToExecuteAction" units="microseconds"
     expires_after="2026-11-12">
   <owner>kouhei@google.com</owner>
   <owner>chrome-loading@google.com</owner>
@@ -1970,10 +1972,12 @@
     Omnibox.SuggestionUsed.Search.InputToNavigationStart2. See also
     Omnibox.InputToOpenSelection for the breakdown of input routing latency. See
     also Omnibox.InputToAcceptNonAction which covers the other branch.
+
+    This histogram only records metrics on machines with high-resolution clocks.
   </summary>
 </histogram>
 
-<histogram name="Omnibox.InputToOpenSelection" units="ms"
+<histogram name="Omnibox.InputToOpenSelection" units="microseconds"
     expires_after="2026-11-12">
   <owner>kouhei@google.com</owner>
   <owner>chrome-loading@google.com</owner>
@@ -1987,6 +1991,8 @@
     Omnibox.SuggestionUsed.Search.InputToNavigationStart2. See also
     Omnibox.InputToExecuteAction and Omnibox.InputToAcceptNonAction for other
     breakdowns of the metric.
+
+    This histogram only records metrics on machines with high-resolution clocks.
   </summary>
 </histogram>
 
@@ -2055,7 +2061,7 @@
 </histogram>
 
 <histogram name="Omnibox.KeywordCount{KeywordType}" units="count"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>mahmadi@chromium.org</owner>
   <owner>chrome-desktop-search@google.com</owner>
   <summary>
@@ -3086,7 +3092,7 @@
 </histogram>
 
 <histogram name="Omnibox.SearchPrefetch.DuplicateSearchTermsAge" units="ms"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>lingqi@chromium.org</owner>
   <owner>nhiroki@chromium.org</owner>
   <owner>chrome-prerendering@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/oobe/histograms.xml b/tools/metrics/histograms/metadata/oobe/histograms.xml
index 7e2d5c6..7ab7df8 100644
--- a/tools/metrics/histograms/metadata/oobe/histograms.xml
+++ b/tools/metrics/histograms/metadata/oobe/histograms.xml
@@ -513,7 +513,7 @@
 </histogram>
 
 <histogram name="OOBE.CategoriesSelectionScreen.SelectedUseCaseIDs"
-    units="useCaseID" expires_after="2026-04-01">
+    units="useCaseID" expires_after="2026-05-31">
   <owner>bohdanty@google.com</owner>
   <owner>cros-oobe@google.com</owner>
   <summary>
@@ -588,7 +588,7 @@
 </histogram>
 
 <histogram name="OOBE.ChromeVersionBeforeUpdate" units="custom"
-    expires_after="2026-04-01">
+    expires_after="2026-05-31">
   <owner>osamafathy@google.com</owner>
   <owner>cros-oobe@google.com</owner>
   <summary>
@@ -759,7 +759,7 @@
   </summary>
 </histogram>
 
-<histogram name="OOBE.GaiaLoginTime" units="ms" expires_after="2026-04-01">
+<histogram name="OOBE.GaiaLoginTime" units="ms" expires_after="2026-05-31">
   <owner>dkuzmin@google.com</owner>
   <owner>cros-oac@google.com</owner>
   <summary>
@@ -806,7 +806,7 @@
 </histogram>
 
 <histogram name="OOBE.GaiaScreen.SuccessLoginRequests" enum="GaiaLoginVariant"
-    expires_after="2026-04-01">
+    expires_after="2026-05-31">
   <owner>dkuzmin@google.com</owner>
   <owner>cros-oobe@google.com</owner>
   <summary>
@@ -957,7 +957,7 @@
 </histogram>
 
 <histogram name="OOBE.MetricsClientIdReset" enum="Boolean"
-    expires_after="2026-04-01">
+    expires_after="2026-05-31">
   <owner>osamafathy@google.com</owner>
   <owner>cros-oobe@google.com</owner>
   <summary>
@@ -1041,7 +1041,7 @@
   </summary>
 </histogram>
 
-<histogram name="OOBE.OobeFlowDuration" units="ms" expires_after="2026-04-01">
+<histogram name="OOBE.OobeFlowDuration" units="ms" expires_after="2026-05-31">
   <owner>osamafathy@google.com</owner>
   <owner>cros-oobe@google.com</owner>
   <summary>
@@ -1083,7 +1083,7 @@
 </histogram>
 
 <histogram name="OOBE.OobeStartToOnboardingStartTime" units="ms"
-    expires_after="2026-04-01">
+    expires_after="2026-05-31">
   <owner>osamafathy@google.com</owner>
   <owner>cros-oobe@google.com</owner>
   <summary>
@@ -1153,7 +1153,7 @@
 </histogram>
 
 <histogram name="OOBE.PersonalizedAppsScreen.SelectedAppIDs" units="appID"
-    expires_after="2026-04-01">
+    expires_after="2026-05-31">
   <owner>bohdanty@google.com</owner>
   <owner>cros-oobe@google.com</owner>
   <summary>
@@ -1224,7 +1224,7 @@
 </histogram>
 
 <histogram name="OOBE.RecommendApps.Fetcher.ResponseCode"
-    enum="HttpResponseCode" expires_after="2026-04-01">
+    enum="HttpResponseCode" expires_after="2026-05-31">
   <owner>dkuzmin@google.com</owner>
   <owner>cros-oac@google.com</owner>
   <owner>chromesky-eng@google.com</owner>
@@ -1320,7 +1320,7 @@
 
 <histogram
     name="OOBE.StepCompletionTimeByExitReason.{OOBEScreenName_ExitReason}"
-    units="ms" expires_after="2026-04-01">
+    units="ms" expires_after="2026-05-31">
   <owner>dkuzmin@google.com</owner>
   <owner>cros-oac@google.com</owner>
   <summary>Time spent on specific OOBE screen grouped by exit reason.</summary>
@@ -1328,7 +1328,7 @@
 </histogram>
 
 <histogram name="OOBE.StepShownStatus.Multidevice-setup-screen.Skipped"
-    enum="OobeMultideviceScreenSkippedReason" expires_after="2026-04-01">
+    enum="OobeMultideviceScreenSkippedReason" expires_after="2026-05-31">
   <owner>bhartmire@google.com</owner>
   <owner>chromeos-cross-device-eng@google.com</owner>
   <summary>
@@ -1354,7 +1354,7 @@
 </histogram>
 
 <histogram name="OOBE.StepShownStatus2.{CommonScreenName}.{OnboardingType}"
-    enum="BooleanShown" expires_after="2026-04-01">
+    enum="BooleanShown" expires_after="2026-05-31">
   <owner>osamafathy@google.com</owner>
   <owner>cros-oobe@google.com</owner>
   <summary>
@@ -1370,7 +1370,7 @@
 </histogram>
 
 <histogram name="OOBE.StepShownStatus2.{OOBEOnlyScreenName}"
-    enum="BooleanShown" expires_after="2026-04-01">
+    enum="BooleanShown" expires_after="2026-05-31">
   <owner>osamafathy@google.com</owner>
   <owner>cros-oobe@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/optimization/histograms.xml b/tools/metrics/histograms/metadata/optimization/histograms.xml
index 4d3aac1..44f49a3 100644
--- a/tools/metrics/histograms/metadata/optimization/histograms.xml
+++ b/tools/metrics/histograms/metadata/optimization/histograms.xml
@@ -1211,7 +1211,7 @@
 
 <histogram
     name="OptimizationGuide.ModelExecution.OnDeviceModelToBeInstalledReason.{ModelExecutionFeature}"
-    enum="OptimizationGuideOnDeviceModelStatus" expires_after="2026-03-29">
+    enum="OptimizationGuideOnDeviceModelStatus" expires_after="2026-05-31">
   <owner>holte@chromium.org</owner>
   <owner>andysjlim@chromium.org</owner>
   <summary>
@@ -1495,7 +1495,7 @@
 
 <histogram
     name="OptimizationGuide.ModelExecutor.ModelLoadingDuration2.{OptimizationTarget}"
-    units="ms" expires_after="2026-03-29">
+    units="ms" expires_after="2026-05-31">
   <owner>wittman@chromium.org</owner>
   <owner>rajendrant@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/others/histograms.xml b/tools/metrics/histograms/metadata/others/histograms.xml
index 99ba5db..d2b4c46 100644
--- a/tools/metrics/histograms/metadata/others/histograms.xml
+++ b/tools/metrics/histograms/metadata/others/histograms.xml
@@ -524,7 +524,7 @@
 
 <histogram
     name="Ads.InterestGroup.Auction.BidderWorkletIsolateTotalHeapSizeKilobytes"
-    units="KB" expires_after="2026-03-29">
+    units="KB" expires_after="2026-05-31">
   <owner>abigailkatcoff@google.com</owner>
   <owner>privacy-sandbox-dev@chromium.org</owner>
   <summary>
@@ -3137,7 +3137,7 @@
 </histogram>
 
 <histogram name="ComponentUpdater.Calls" enum="ComponentUpdaterCalls"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>sorin@chromium.org</owner>
   <owner>src/components/component_updater/OWNERS</owner>
   <summary>
@@ -3160,21 +3160,21 @@
 </histogram>
 
 <histogram name="ComponentUpdater.UpdateCompleteError"
-    enum="UpdateClientErrors" expires_after="2026-03-29">
+    enum="UpdateClientErrors" expires_after="2026-05-31">
   <owner>sorin@chromium.org</owner>
   <owner>src/components/component_updater/OWNERS</owner>
   <summary>The result of an install or an update check.</summary>
 </histogram>
 
 <histogram name="ComponentUpdater.UpdateCompleteResult" enum="BooleanError"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>sorin@chromium.org</owner>
   <owner>src/components/component_updater/OWNERS</owner>
   <summary>The result of an install or an update check.</summary>
 </histogram>
 
 <histogram name="ComponentUpdater.UpdateCompleteTime" units="ms"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>sorin@chromium.org</owner>
   <owner>src/components/component_updater/OWNERS</owner>
   <summary>
@@ -3424,7 +3424,7 @@
 </histogram>
 
 <histogram name="Document.BeforeUnloadDialog" enum="BeforeUnloadDialogResult"
-    expires_after="2026-03-01">
+    expires_after="2026-12-01">
   <owner>panicker@chromium.org</owner>
   <owner>chikamune@chromium.org</owner>
   <summary>
@@ -3436,7 +3436,7 @@
 </histogram>
 
 <histogram name="DocumentEventTiming.BeforeUnloadDialogDuration.ByNavigation"
-    units="ms" expires_after="2023-10-01">
+    units="ms" expires_after="2026-12-01">
   <owner>sullivan@chromium.org</owner>
   <owner>chikamune@chromium.org</owner>
   <summary>
@@ -3444,13 +3444,13 @@
     beforeunload event in JavaScript. Recorded immediately after closing dialog,
     upon user confirmation to navigate away from the page.
 
-    Warning: this histogram was expired from 2020-09-05 to M108; data may be
-    missing.
+    Warning: this histogram was expired from 2020-09-05 to M108, and from
+    2023-10-01 to M144; data may be missing.
   </summary>
 </histogram>
 
 <histogram name="DocumentScan.ScanFailed" enum="DocumentScanSaneBackend"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>bmgordon@chromium.org</owner>
   <owner>project-bolton@google.com</owner>
   <summary>
@@ -5103,7 +5103,7 @@
 </histogram>
 
 <histogram name="LoadingPredictor.IsSlowNetwork" enum="Boolean"
-    expires_after="2025-12-14">
+    expires_after="2026-12-01">
   <owner>chikamune@chromium.org</owner>
   <owner>chrome-loading@google.com</owner>
   <summary>
@@ -5167,7 +5167,7 @@
 </histogram>
 
 <histogram name="LoadingPredictor.SetLCPPNavigationHint.Time" units="ms"
-    expires_after="2026-05-03">
+    expires_after="2026-12-01">
   <owner>chikamune@chromium.org</owner>
   <owner>chrome-loading@google.com</owner>
   <improvement direction="LOWER_IS_BETTER"/>
@@ -5328,7 +5328,7 @@
 </histogram>
 
 <histogram name="Mojo.Channel.WriteToReadLatencyUs" units="microseconds"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>lizeb@chromium.org</owner>
   <owner>rockot@google.com</owner>
   <summary>
@@ -6879,7 +6879,7 @@
 </histogram>
 
 <histogram name="ReadingList.Read.Number" units="count"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>gambard@chromium.org</owner>
   <owner>bling-team@google.com</owner>
   <summary>Number of read entries in reading list.</summary>
@@ -8052,7 +8052,7 @@
 </histogram>
 
 <histogram name="SSORecallPromo.PromoAction" enum="SSOPromoUserAction"
-    expires_after="2026-04-01">
+    expires_after="2026-05-31">
   <owner>jlebel@chromium.org</owner>
   <owner>chrome-signin-team@google.com</owner>
   <summary>
@@ -8061,7 +8061,7 @@
 </histogram>
 
 <histogram name="SSORecallPromo.PromoSeenCount" units="units"
-    expires_after="2026-04-01">
+    expires_after="2026-05-31">
   <owner>jlebel@chromium.org</owner>
   <owner>chrome-signin-team@google.com</owner>
   <summary>
@@ -8604,7 +8604,7 @@
 </histogram>
 
 <histogram name="UpdateClient.Component.Updated" enum="Boolean"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>noahrose@google.com</owner>
   <owner>sorin@chromium.org</owner>
   <owner>waffles@chromium.org</owner>
@@ -8639,7 +8639,7 @@
 </histogram>
 
 <histogram name="UpdateClient.UnzipTime{AppID}" units="ms"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>noahrose@google.com</owner>
   <owner>sorin@chromium.org</owner>
   <owner>waffles@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/page/histograms.xml b/tools/metrics/histograms/metadata/page/histograms.xml
index 7d03f93..e8e5c70f 100644
--- a/tools/metrics/histograms/metadata/page/histograms.xml
+++ b/tools/metrics/histograms/metadata/page/histograms.xml
@@ -1302,7 +1302,7 @@
 
 <histogram
     name="PageLoad.Clients.GoogleSearch.AdvertisedAltSvcState{QuicAvailability}{IncognitoSuffix}"
-    enum="AdvertisedAltSvcState" expires_after="2026-03-29">
+    enum="AdvertisedAltSvcState" expires_after="2026-05-31">
   <owner>bashi@chromium.org</owner>
   <owner>chrome-loading@google.com</owner>
   <summary>
@@ -1523,7 +1523,7 @@
 </histogram>
 
 <histogram name="PageLoad.Clients.GoogleSearch.HttpNetworkSessionQuicEnabled"
-    enum="Boolean" expires_after="2026-03-29">
+    enum="Boolean" expires_after="2026-05-31">
   <owner>bashi@chromium.org</owner>
   <owner>chrome-loading@google.com</owner>
   <summary>
@@ -1893,7 +1893,7 @@
 
 <histogram
     name="PageLoad.Clients.GoogleSearch.Prerender.HostReused{IncognitoSuffix}"
-    enum="Boolean" expires_after="2026-03-29">
+    enum="Boolean" expires_after="2026-05-31">
   <owner>suzukikeita@chromium.org</owner>
   <owner>chrome-loading@google.com</owner>
   <summary>
@@ -1931,7 +1931,7 @@
 
 <histogram
     name="PageLoad.Clients.GoogleSearch.SessionSource{ConnectionProtocolType}"
-    enum="NetworkSessionSource" expires_after="2026-03-29">
+    enum="NetworkSessionSource" expires_after="2026-05-31">
   <owner>bashi@chromium.org</owner>
   <owner>chrome-loading@google.com</owner>
   <summary>
@@ -3685,7 +3685,7 @@
 </histogram>
 
 <histogram name="PageLoad.InteractiveTiming.INPOffset" units="offset"
-    expires_after="2026-03-30">
+    expires_after="2026-05-31">
   <owner>sullivan@chromium.org</owner>
   <owner>speed-metrics-dev@chromium.org</owner>
   <summary>
@@ -3902,7 +3902,7 @@
 
 <histogram
     name="PageLoad.Internal.Prerender2.DomContentLoadedToActivation3{PreloadingTriggerType}"
-    units="ms" expires_after="2026-03-22">
+    units="ms" expires_after="2026-05-31">
   <owner>lingqi@chromium.org</owner>
   <owner>chrome-prerendering@google.com</owner>
   <summary>
@@ -4004,7 +4004,7 @@
 </histogram>
 
 <histogram name="PageLoad.Internal.SuppressedEventsBeforeMissingFirstPaint"
-    enum="Boolean" expires_after="2025-12-14">
+    enum="Boolean" expires_after="2026-04-30">
   <owner>mustaq@chromium.org</owner>
   <owner>blink-interactions-team@google.com</owner>
   <summary>
@@ -4014,7 +4014,7 @@
 </histogram>
 
 <histogram name="PageLoad.Internal.SuppressedEventsCountBeforePaint3"
-    units="count" expires_after="2025-12-14">
+    units="count" expires_after="2026-04-30">
   <owner>mustaq@chromium.org</owner>
   <owner>blink-interactions-team@google.com</owner>
   <summary>
@@ -4034,7 +4034,7 @@
 </histogram>
 
 <histogram name="PageLoad.Internal.SuppressedEventsTimingBeforePaint3"
-    units="ms" expires_after="2025-12-14">
+    units="ms" expires_after="2026-04-30">
   <owner>mustaq@chromium.org</owner>
   <owner>blink-interactions-team@google.com</owner>
   <summary>
@@ -4060,7 +4060,7 @@
 </histogram>
 
 <histogram name="PageLoad.Internal.SuppressedInteractionsCountBeforePaint3"
-    units="count" expires_after="2025-12-14">
+    units="count" expires_after="2026-04-30">
   <owner>mustaq@chromium.org</owner>
   <owner>blink-interactions-team@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/password/histograms.xml b/tools/metrics/histograms/metadata/password/histograms.xml
index 1acd0f0..fc2c5a9 100644
--- a/tools/metrics/histograms/metadata/password/histograms.xml
+++ b/tools/metrics/histograms/metadata/password/histograms.xml
@@ -289,7 +289,7 @@
 </histogram>
 
 <histogram name="Passkeys.IOSMigration" enum="PasskeysMigrationStatus"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>sugoi@chromium.org</owner>
   <owner>tmartino@chromium.org</owner>
   <summary>
@@ -827,7 +827,7 @@
 </histogram>
 
 <histogram name="PasswordManager.AuthenticationAsyncOpFailureReson"
-    enum="Hresult" expires_after="2026-03-15">
+    enum="Hresult" expires_after="2026-05-31">
   <owner>sygiet@google.com</owner>
   <owner>vasilii@chromium.org</owner>
   <summary>
@@ -2203,7 +2203,7 @@
 <histogram
     name="PasswordManager.LoginDatabase.ShouldDeleteUndecryptablePasswords"
     enum="LoginDatabaseShouldDeleteUndecryptablePasswords"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>sygiet@google.com</owner>
   <owner>vasilii@chromium.org</owner>
   <summary>
@@ -2463,7 +2463,7 @@
 </histogram>
 
 <histogram name="PasswordManager.ParserDetectedOtpFieldWithRegex"
-    enum="Boolean" expires_after="2026-03-29">
+    enum="Boolean" expires_after="2026-05-31">
   <owner>kazinova@google.com</owner>
   <owner>vasilii@chromium.org</owner>
   <summary>
@@ -2990,7 +2990,7 @@
 </histogram>
 
 <histogram name="PasswordManager.PasswordFilling.ReauthPromo"
-    enum="PasswordFillingReauthPromoShown" expires_after="2026-03-28">
+    enum="PasswordFillingReauthPromoShown" expires_after="2026-05-31">
   <owner>droger@chromium.org</owner>
   <owner>mlbipin@google.com</owner>
   <summary>
@@ -4760,7 +4760,7 @@
 </histogram>
 
 <histogram name="PasswordProtection.RequestReferringAppSource"
-    enum="PasswordProtectionReferringAppSource" expires_after="2026-03-29">
+    enum="PasswordProtectionReferringAppSource" expires_after="2026-05-31">
   <owner>nwokedi@chromium.org</owner>
   <owner>chrome-counter-abuse-alerts@google.com</owner>
   <summary>
@@ -4860,7 +4860,7 @@
 </histogram>
 
 <histogram name="PasswordProtection.VisualFeaturesClearReason"
-    enum="CanExtractVisualFeaturesResult" expires_after="2026-03-02">
+    enum="CanExtractVisualFeaturesResult" expires_after="2026-05-31">
   <owner>andysjlim@chromium.org</owner>
   <owner>chrome-counter-abuse-alerts@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/performance_manager/histograms.xml b/tools/metrics/histograms/metadata/performance_manager/histograms.xml
index ea859239..93a2aef 100644
--- a/tools/metrics/histograms/metadata/performance_manager/histograms.xml
+++ b/tools/metrics/histograms/metadata/performance_manager/histograms.xml
@@ -121,7 +121,7 @@
 </histogram>
 
 <histogram name="CPU.Experimental.EstimatedFrequencyAsPercentOfMax.{CoreType}"
-    units="%" expires_after="2026-03-29">
+    units="%" expires_after="2026-05-31">
   <owner>anthonyvd@chromium.org</owner>
   <owner>catan-team@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/permissions/histograms.xml b/tools/metrics/histograms/metadata/permissions/histograms.xml
index 4404017..baa23125 100644
--- a/tools/metrics/histograms/metadata/permissions/histograms.xml
+++ b/tools/metrics/histograms/metadata/permissions/histograms.xml
@@ -1092,7 +1092,7 @@
 </histogram>
 
 <histogram name="Permissions.PredictionService.MSBB" enum="BooleanEnabled"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>elklm@chromium.org</owner>
   <owner>hempjudith@google.com</owner>
   <owner>src/components/permissions/PERMISSIONS_OWNERS</owner>
@@ -1131,7 +1131,7 @@
 </histogram>
 
 <histogram name="Permissions.PredictionService.TFLiteLibAvailable"
-    enum="BooleanAvailable" expires_after="2026-03-29">
+    enum="BooleanAvailable" expires_after="2026-05-31">
   <owner>elklm@chromium.org</owner>
   <owner>hempjudith@google.com</owner>
   <owner>src/components/permissions/PERMISSIONS_OWNERS</owner>
@@ -1971,7 +1971,7 @@
 </histogram>
 
 <histogram name="Permissions.QuietPrompt.Preignore.PageReloadInfoBar"
-    enum="BooleanShown" expires_after="2026-03-29">
+    enum="BooleanShown" expires_after="2026-05-31">
   <owner>elklm@chromium.org</owner>
   <owner>hempjudith@google.com</owner>
   <owner>src/components/permissions/PERMISSIONS_OWNERS</owner>
diff --git a/tools/metrics/histograms/metadata/platform/histograms.xml b/tools/metrics/histograms/metadata/platform/histograms.xml
index 8008bbe..81906fd 100644
--- a/tools/metrics/histograms/metadata/platform/histograms.xml
+++ b/tools/metrics/histograms/metadata/platform/histograms.xml
@@ -699,7 +699,7 @@
 </histogram>
 
 <histogram name="Platform.DeviceManagement.InstallAttributesStatus"
-    enum="InstallAttributesStatus" expires_after="2026-03-29">
+    enum="InstallAttributesStatus" expires_after="2026-05-31">
   <owner>sadmansakib@google.com</owner>
   <owner>cros-hwsec-userland-eng+uma@google.com</owner>
   <summary>
@@ -927,7 +927,7 @@
 </histogram>
 
 <histogram name="Platform.Firmware.Mismatch" enum="BooleanDetected"
-    expires_after="2026-03-26">
+    expires_after="2026-05-31">
   <owner>kimjae@chromium.org</owner>
   <owner>chromeos-core-services@google.com</owner>
   <summary>
@@ -953,7 +953,7 @@
 </histogram>
 
 <histogram name="Platform.FlexCpuIsaLevel" enum="FlexCpuIsaLevel"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>nicholasbishop@google.com</owner>
   <owner>chromeos-flex-eng@google.com</owner>
   <summary>
@@ -1488,7 +1488,7 @@
 </histogram>
 
 <histogram name="Platform.MantisService.ImageGenerationType"
-    enum="MantisServiceImageGenerationType" expires_after="2026-03-22">
+    enum="MantisServiceImageGenerationType" expires_after="2026-05-31">
   <owner>njrafi@google.com</owner>
   <owner>cros-odml-foundations-eng@google.com</owner>
   <summary>
@@ -2036,7 +2036,7 @@
 </histogram>
 
 <histogram name="Platform.Odml.CpuUsageMilliPercent" units="1/1000ths of %"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>hcyang@google.com</owner>
   <owner>cros-odml-foundations-eng@google.com</owner>
   <summary>
@@ -2046,7 +2046,7 @@
 </histogram>
 
 <histogram name="Platform.Odml.PeakTotalMallocMemoryKb" units="KB"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>hcyang@google.com</owner>
   <owner>cros-odml-foundations-eng@google.com</owner>
   <summary>
@@ -2056,7 +2056,7 @@
 </histogram>
 
 <histogram name="Platform.Odml.PeakTotalRssMemoryKb" units="KB"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>hcyang@google.com</owner>
   <owner>cros-odml-foundations-eng@google.com</owner>
   <summary>
@@ -2066,7 +2066,7 @@
 </histogram>
 
 <histogram name="Platform.Odml.PeakTotalSwapMemoryKb" units="KB"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>hcyang@google.com</owner>
   <owner>cros-odml-foundations-eng@google.com</owner>
   <summary>
@@ -2076,7 +2076,7 @@
 </histogram>
 
 <histogram name="Platform.Odml.TotalMallocMemoryKb" units="KB"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>hcyang@google.com</owner>
   <owner>cros-odml-foundations-eng@google.com</owner>
   <summary>
@@ -2085,7 +2085,7 @@
 </histogram>
 
 <histogram name="Platform.Odml.TotalRssMemoryKb" units="KB"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>hcyang@google.com</owner>
   <owner>cros-odml-foundations-eng@google.com</owner>
   <summary>
@@ -2095,7 +2095,7 @@
 </histogram>
 
 <histogram name="Platform.Odml.TotalSwapMemoryKb" units="KB"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>hcyang@google.com</owner>
   <owner>cros-odml-foundations-eng@google.com</owner>
   <summary>
@@ -3117,7 +3117,7 @@
 </histogram>
 
 <histogram name="Platform.{GSC}.FlashLog" enum="Cr50FlashLogs"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>apronin@chromium.org</owner>
   <owner>vbendeb@chromium.org</owner>
   <owner>cros-hwsec+uma@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/power/histograms.xml b/tools/metrics/histograms/metadata/power/histograms.xml
index 58b3d73..cd7d493 100644
--- a/tools/metrics/histograms/metadata/power/histograms.xml
+++ b/tools/metrics/histograms/metadata/power/histograms.xml
@@ -1345,7 +1345,7 @@
 
 <histogram
     name="Power.ForegroundBatteryDrainPerScenario.30Seconds{Exclusive}{LoadingScenario}{InputScenario}"
-    units="uAh" expires_after="2026-03-26">
+    units="uAh" expires_after="2026-05-31">
   <owner>kraskevich@google.com</owner>
   <owner>woa-performance@google.com</owner>
   <summary>
@@ -1875,7 +1875,7 @@
 </histogram>
 
 <histogram name="Power.SuspendResult" enum="SuspendResult"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>puthik@chromium.org</owner>
   <owner>chromeos-platform-power@google.com</owner>
   <summary>
@@ -1926,7 +1926,7 @@
 </histogram>
 
 <histogram name="Power.UserBrightnessAdjustmentsPerSessionOnBattery"
-    units="units" expires_after="2026-03-29">
+    units="units" expires_after="2026-05-31">
   <owner>alanlxl@chromium.org</owner>
   <owner>amoylan@chromium.org</owner>
   <owner>napper@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/preloading/histograms.xml b/tools/metrics/histograms/metadata/preloading/histograms.xml
index 4029c6f..9ef3f6a7 100644
--- a/tools/metrics/histograms/metadata/preloading/histograms.xml
+++ b/tools/metrics/histograms/metadata/preloading/histograms.xml
@@ -402,7 +402,7 @@
 </histogram>
 
 <histogram name="{PreloadServingMetricsPrefix}.PrefetchMatchMetrics.ExistsPaop"
-    units="bool" expires_after="2026-03-29">
+    units="bool" expires_after="2026-05-31">
   <owner>kenoss@chromium.org</owner>
   <owner>chrome-prerendering@google.com</owner>
   <summary>
@@ -417,7 +417,7 @@
 
 <histogram
     name="{PreloadServingMetricsPrefix}.PrefetchMatchMetrics.ExistsPaopThen.PotentialCandidateServingResultAndServableStateAndMatcherAction"
-    units="code" expires_after="2026-03-29">
+    units="code" expires_after="2026-05-31">
   <owner>kenoss@chromium.org</owner>
   <owner>chrome-prerendering@google.com</owner>
   <summary>
@@ -433,7 +433,7 @@
 
 <histogram
     name="{PreloadServingMetricsPrefix}.PrefetchMatchMetrics.ExistsPaopThen.PrefetchStatus"
-    enum="PrefetchStatus" expires_after="2026-03-29">
+    enum="PrefetchStatus" expires_after="2026-05-31">
   <owner>kenoss@chromium.org</owner>
   <owner>chrome-prerendering@google.com</owner>
   <summary>
@@ -448,7 +448,7 @@
 
 <histogram
     name="{PreloadServingMetricsPrefix}.PrefetchMatchMetrics.ExistsPaopThen.QueueIndexPlus1"
-    units="count" expires_after="2026-03-29">
+    units="count" expires_after="2026-05-31">
   <owner>kenoss@chromium.org</owner>
   <owner>chrome-prerendering@google.com</owner>
   <summary>
@@ -464,7 +464,7 @@
 
 <histogram
     name="{PreloadServingMetricsPrefix}.PrefetchMatchMetrics.ExistsPaopThen.QueueSize"
-    units="count" expires_after="2026-03-29">
+    units="count" expires_after="2026-05-31">
   <owner>kenoss@chromium.org</owner>
   <owner>chrome-prerendering@google.com</owner>
   <summary>
@@ -479,7 +479,7 @@
 
 <histogram
     name="{PreloadServingMetricsPrefix}.PrefetchMatchMetrics.ExistsPaopThen.ServableStateAndMatcherAction"
-    units="code" expires_after="2026-03-29">
+    units="code" expires_after="2026-05-31">
   <owner>kenoss@chromium.org</owner>
   <owner>chrome-prerendering@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/printing/histograms.xml b/tools/metrics/histograms/metadata/printing/histograms.xml
index bf610a30..1f1454e7 100644
--- a/tools/metrics/histograms/metadata/printing/histograms.xml
+++ b/tools/metrics/histograms/metadata/printing/histograms.xml
@@ -231,7 +231,7 @@
 </histogram>
 
 <histogram name="Printing.CUPS.NearbyNetworkDiscoveredPrintersCount"
-    units="printers" expires_after="2026-03-29">
+    units="printers" expires_after="2026-05-31">
   <owner>bmgordon@chromium.org</owner>
   <owner>project-bolton@google.com</owner>
   <summary>
@@ -299,7 +299,7 @@
 </histogram>
 
 <histogram name="Printing.CUPS.PrintersDiscovered" units="printers"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>bmgordon@chromium.org</owner>
   <owner>src/chromeos/printing/OWNERS</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/privacy/histograms.xml b/tools/metrics/histograms/metadata/privacy/histograms.xml
index 19d130d..129ba438 100644
--- a/tools/metrics/histograms/metadata/privacy/histograms.xml
+++ b/tools/metrics/histograms/metadata/privacy/histograms.xml
@@ -189,7 +189,7 @@
 </histogram>
 
 <histogram name="Privacy.3PCD.RollbackNotice.AutomaticallyDismissed"
-    enum="Boolean" expires_after="2026-03-31">
+    enum="Boolean" expires_after="2026-05-31">
   <owner>fmacintosh@google.com</owner>
   <owner>koilos@google.com</owner>
   <summary>
@@ -199,7 +199,7 @@
 </histogram>
 
 <histogram name="Privacy.3PCD.RollbackNotice.ShouldShow" enum="Boolean"
-    expires_after="2026-03-31">
+    expires_after="2026-05-31">
   <owner>fmacintosh@google.com</owner>
   <owner>koilos@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/profile/histograms.xml b/tools/metrics/histograms/metadata/profile/histograms.xml
index 8ac806d3..040f6e4 100644
--- a/tools/metrics/histograms/metadata/profile/histograms.xml
+++ b/tools/metrics/histograms/metadata/profile/histograms.xml
@@ -902,7 +902,7 @@
 </histogram>
 
 <histogram name="ProfilePicker.FirstRun.OrganizationAvailableTiming" units="ms"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>dgn@chromium.org</owner>
   <owner>chrome-signin-team@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/quota/histograms.xml b/tools/metrics/histograms/metadata/quota/histograms.xml
index a9ab05d..76b00623 100644
--- a/tools/metrics/histograms/metadata/quota/histograms.xml
+++ b/tools/metrics/histograms/metadata/quota/histograms.xml
@@ -77,44 +77,6 @@
   </token>
 </histogram>
 
-<histogram name="Quota.DatabaseSpecificError.{Statement}"
-    enum="SqliteLoggedResultCode" expires_after="2025-12-07">
-  <owner>estade@chromium.org</owner>
-  <owner>chrome-owp-storage@google.com</owner>
-  <summary>
-    Records error codes returned by SQLite for operations on the quota database.
-    These are logged separately for each database operation to help isolate
-    issues the quota db might be facing. Warning: this histogram was expired in
-    2025/06; data may be missing.
-  </summary>
-  <token key="Statement">
-    <variant name="BootstrapDefaultBucket"/>
-    <variant name="Commit"/>
-    <variant name="CountBuckets"/>
-    <variant name="CreateBucket"/>
-    <variant name="CreateTable"/>
-    <variant name="DeleteBucket"/>
-    <variant name="GetAllBuckets"/>
-    <variant name="GetBucket"/>
-    <variant name="GetBucketById"/>
-    <variant name="GetBucketsForEviction"/>
-    <variant name="GetBucketsForHost"/>
-    <variant name="GetBucketsForStorageKey"/>
-    <variant name="GetBucketsModifiedBetween"/>
-    <variant name="GetExpired"/>
-    <variant name="GetExpiredAndOrphanAndStale"/>
-    <variant name="GetOrphan"/>
-    <variant name="GetStale"/>
-    <variant name="GetStorageKeys"/>
-    <variant name="Open"/>
-    <variant name="SetBucketLastAccessTime"/>
-    <variant name="SetBucketLastModifiedTime"/>
-    <variant name="SetStorageKeyLastAccessTime"/>
-    <variant name="UpdateBucketExpiration"/>
-    <variant name="UpdateBucketPersistence"/>
-  </token>
-</histogram>
-
 <histogram name="Quota.DaysSinceLastAccessed" units="days"
     expires_after="2026-09-22">
   <owner>arichiv@chromium.org</owner>
@@ -282,8 +244,9 @@
 </histogram>
 
 <histogram name="Quota.QuotaDatabaseDisabled"
-    enum="QuotaDatabaseDisabledReason" expires_after="2025-12-07">
-  <owner>estade@chromium.org</owner>
+    enum="QuotaDatabaseDisabledReason" expires_after="2026-12-07">
+  <owner>leimy@chromium.org</owner>
+  <owner>evanstade@microsoft.com</owner>
   <owner>chrome-owp-storage@google.com</owner>
   <summary>
     Records at which step the QuotaDatabase failed to bootstrap. Warning: this
diff --git a/tools/metrics/histograms/metadata/regional_capabilities/histograms.xml b/tools/metrics/histograms/metadata/regional_capabilities/histograms.xml
index 83b7d7f..4333122a 100644
--- a/tools/metrics/histograms/metadata/regional_capabilities/histograms.xml
+++ b/tools/metrics/histograms/metadata/regional_capabilities/histograms.xml
@@ -106,7 +106,7 @@
 <!-- LINT.ThenChange(tools/metrics/histograms/metadata/puma/histograms.xml:PUMA.RegionalCapabilities.FunnelStage.Eligibility) -->
 
 <histogram name="RegionalCapabilities.FunnelStage.RegionalPresence"
-    enum="RegionalProgramAndLocationMatch" expires_after="2026-03-29">
+    enum="RegionalProgramAndLocationMatch" expires_after="2026-05-31">
   <owner>dgn@chromium.org</owner>
   <owner>chrome-regionalcapabilities@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/safe_browsing/histograms.xml b/tools/metrics/histograms/metadata/safe_browsing/histograms.xml
index 66a7808..c776c8f0 100644
--- a/tools/metrics/histograms/metadata/safe_browsing/histograms.xml
+++ b/tools/metrics/histograms/metadata/safe_browsing/histograms.xml
@@ -2129,7 +2129,7 @@
 </histogram>
 
 <histogram name="SafeBrowsing.IOS.TotalDelay2{UserCategory}" units="ms"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>danieltwhite@google.com</owner>
   <owner>joemerramos@chromium.org</owner>
   <owner>chrome-counter-abuse-alerts@google.com</owner>
@@ -2433,7 +2433,7 @@
 </histogram>
 
 <histogram name="SafeBrowsing.Pref.Daily.Extended" enum="BooleanEnabled"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>xinghuilu@chromium.org</owner>
   <owner>chrome-counter-abuse-alerts@google.com</owner>
   <summary>
@@ -2608,7 +2608,7 @@
 </histogram>
 
 <histogram name="SafeBrowsing.RT.AllowlistSizeTooSmall"
-    enum="BooleanUnavailable" expires_after="2026-04-01">
+    enum="BooleanUnavailable" expires_after="2026-05-31">
   <owner>xinghuilu@chromium.org</owner>
   <owner>chrome-counter-abuse-alerts@google.com</owner>
   <summary>
@@ -2624,7 +2624,7 @@
 </histogram>
 
 <histogram name="SafeBrowsing.RT.AllStoresAvailable" enum="BooleanAvailable"
-    expires_after="2026-04-01">
+    expires_after="2026-05-31">
   <owner>xinghuilu@chromium.org</owner>
   <owner>chrome-counter-abuse-alerts@google.com</owner>
   <summary>
@@ -2655,7 +2655,7 @@
 </histogram>
 
 <histogram name="SafeBrowsing.RT.CacheManager.CleanupReachedThreshold"
-    units="entries" expires_after="2026-03-29">
+    units="entries" expires_after="2026-05-31">
   <owner>thefrog@chromium.org</owner>
   <owner>chrome-counter-abuse-alerts@google.com</owner>
   <summary>
@@ -2688,7 +2688,7 @@
 </histogram>
 
 <histogram name="SafeBrowsing.RT.CanCheckDatabase" enum="BooleanEnabled"
-    expires_after="2026-04-01">
+    expires_after="2026-05-31">
   <owner>xinghuilu@chromium.org</owner>
   <owner>chrome-counter-abuse-alerts@google.com</owner>
   <summary>
@@ -3066,7 +3066,7 @@
 </histogram>
 
 <histogram name="SafeBrowsing.RT.ThreatInfoSize{UserCategory}" units="verdicts"
-    expires_after="2026-04-01">
+    expires_after="2026-05-31">
   <owner>xinghuilu@chromium.org</owner>
   <owner>chrome-counter-abuse-alerts@google.com</owner>
   <summary>
@@ -3455,7 +3455,7 @@
 </histogram>
 
 <histogram name="SafeBrowsing.Triggers.SuspiciousSite.Event"
-    enum="SuspiciousSiteTriggerEvent" expires_after="2026-03-29">
+    enum="SuspiciousSiteTriggerEvent" expires_after="2026-05-31">
   <owner>vakh@chromium.org</owner>
   <owner>chrome-counter-abuse-alerts@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/sb_client/histograms.xml b/tools/metrics/histograms/metadata/sb_client/histograms.xml
index b0ce43f..c95d604 100644
--- a/tools/metrics/histograms/metadata/sb_client/histograms.xml
+++ b/tools/metrics/histograms/metadata/sb_client/histograms.xml
@@ -303,7 +303,7 @@
 </histogram>
 
 <histogram name="SBClientDownload.TailoredWarningType"
-    enum="TailoredWarningType" expires_after="2026-03-29">
+    enum="TailoredWarningType" expires_after="2026-05-31">
   <owner>xinghuilu@chromium.org</owner>
   <owner>chrome-counter-abuse-alerts@google.com</owner>
   <summary>
@@ -876,7 +876,7 @@
 </histogram>
 
 <histogram name="SBClientPhishing.OnDeviceModelDownloadSuccess"
-    enum="BooleanSuccess" expires_after="2026-03-29">
+    enum="BooleanSuccess" expires_after="2026-05-31">
   <owner>andysjlim@chromium.org</owner>
   <owner>chrome-counter-abuse-alerts@google.com</owner>
   <summary>
@@ -922,7 +922,7 @@
 </histogram>
 
 <histogram name="SBClientPhishing.OnDeviceModelFetchTime" units="ms"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>andysjlim@chromium.org</owner>
   <owner>chrome-counter-abuse-alerts@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/service/histograms.xml b/tools/metrics/histograms/metadata/service/histograms.xml
index d3f73371..f8e6534 100644
--- a/tools/metrics/histograms/metadata/service/histograms.xml
+++ b/tools/metrics/histograms/metadata/service/histograms.xml
@@ -567,7 +567,7 @@
 
 <histogram
     name="ServiceWorker.FetchEvent.{Resource}.RaceNetworkRequest.Redirect"
-    enum="Boolean" expires_after="2026-03-29">
+    enum="Boolean" expires_after="2026-05-31">
   <owner>sisidovski@chromium.org</owner>
   <owner>chrome-worker@google.com</owner>
   <summary>
@@ -1234,7 +1234,7 @@
 
 <histogram
     name="ServiceWorker.MaybeStartWorker.RunningStatusByPurpose_{ServiceWorkerEventType}"
-    enum="EmbeddedWorkerStatus" expires_after="2026-03-29">
+    enum="EmbeddedWorkerStatus" expires_after="2026-05-31">
   <owner>yyanagisawa@chromium.org</owner>
   <owner>chrome-worker@google.com</owner>
   <summary>
@@ -1379,7 +1379,7 @@
 </histogram>
 
 <histogram name="ServiceWorker.Registration.Delete.Result.{DeleteInitiator}"
-    enum="ServiceWorkerStatusCode" expires_after="2026-03-29">
+    enum="ServiceWorkerStatusCode" expires_after="2026-05-31">
   <owner>yyanagisawa@chromium.org</owner>
   <owner>chrome-worker@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/session/histograms.xml b/tools/metrics/histograms/metadata/session/histograms.xml
index 224c37b..6dac7bb 100644
--- a/tools/metrics/histograms/metadata/session/histograms.xml
+++ b/tools/metrics/histograms/metadata/session/histograms.xml
@@ -65,7 +65,7 @@
 </histogram>
 
 <histogram name="Session.ID.RestoredDifference" units="Session IDs"
-    expires_after="2026-02-28">
+    expires_after="2026-05-31">
   <owner>gambard@chromium.org</owner>
   <owner>sdefresne@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/settings/histograms.xml b/tools/metrics/histograms/metadata/settings/histograms.xml
index 6318ec8..ceee2da 100644
--- a/tools/metrics/histograms/metadata/settings/histograms.xml
+++ b/tools/metrics/histograms/metadata/settings/histograms.xml
@@ -261,7 +261,7 @@
 </histogram>
 
 <histogram name="Settings.Cookies.TrackingProtectionRedirect"
-    enum="BooleanRedirected" expires_after="2026-03-29">
+    enum="BooleanRedirected" expires_after="2026-05-31">
   <owner>mjenn@google.com</owner>
   <owner>koilos@google.com</owner>
   <summary>
@@ -323,7 +323,7 @@
 </histogram>
 
 <histogram name="Settings.GivenShowHomeButton_HomePageIsNewTabPage2"
-    enum="Boolean" expires_after="2026-03-29">
+    enum="Boolean" expires_after="2026-05-31">
   <owner>mpearson@chromium.org</owner>
   <owner>twellington@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/signin/enums.xml b/tools/metrics/histograms/metadata/signin/enums.xml
index f0329f1..e172d4e1 100644
--- a/tools/metrics/histograms/metadata/signin/enums.xml
+++ b/tools/metrics/histograms/metadata/signin/enums.xml
@@ -984,6 +984,7 @@
   <int value="89" label="New Tab Page sign-in feature promo"/>
   <int value="90" label="Enterprise dialog after signin interception"/>
   <int value="91" label="Your saved info page in settings"/>
+  <int value="92" label="Credential Exchange import"/>
 </enum>
 
 <enum name="SigninAccountReconcilorState">
diff --git a/tools/metrics/histograms/metadata/signin/histograms.xml b/tools/metrics/histograms/metadata/signin/histograms.xml
index 98ced82d..705b435b 100644
--- a/tools/metrics/histograms/metadata/signin/histograms.xml
+++ b/tools/metrics/histograms/metadata/signin/histograms.xml
@@ -463,7 +463,7 @@
 </histogram>
 
 <histogram name="Signin.AccountManagementType"
-    enum="AccountManagedStatusFinderOutcome" expires_after="2026-03-29">
+    enum="AccountManagedStatusFinderOutcome" expires_after="2026-05-31">
   <owner>msarda@chromium.org</owner>
   <owner>treib@chromium.org</owner>
   <owner>chrome-signin-team@google.com</owner>
@@ -477,7 +477,7 @@
 </histogram>
 
 <histogram name="Signin.AccountManagementTypesSummary"
-    enum="AccountManagementTypesSummary" expires_after="2026-03-29">
+    enum="AccountManagementTypesSummary" expires_after="2026-05-31">
   <owner>msarda@chromium.org</owner>
   <owner>treib@chromium.org</owner>
   <owner>chrome-signin-team@google.com</owner>
@@ -510,7 +510,7 @@
 </histogram>
 
 <histogram name="Signin.AccountProfileStartupState2"
-    enum="AccountProfileStartupState" expires_after="2026-03-29">
+    enum="AccountProfileStartupState" expires_after="2026-05-31">
   <owner>jood@google.com</owner>
   <owner>treib@chromium.org</owner>
   <owner>chrome-signin-team@google.com</owner>
@@ -885,7 +885,7 @@
 </histogram>
 
 <histogram name="Signin.BoundSessionCredentials.CookieRotationOutageDuration"
-    units="ms" expires_after="2026-03-22">
+    units="ms" expires_after="2026-05-31">
   <owner>alexilin@chromium.org</owner>
   <owner>msalama@chromium.org</owner>
   <owner>chrome-signin-team@google.com</owner>
@@ -1566,7 +1566,7 @@
 </histogram>
 
 <histogram name="Signin.ExplicitSigninMigration.FromSync" enum="BooleanSuccess"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>droger@chromium.org</owner>
   <owner>rsult@google.com</owner>
   <summary>
@@ -2066,7 +2066,7 @@
 </histogram>
 
 <histogram name="Signin.IOSAccountsOnDeviceCount{AccountType}" units="accounts"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>msarda@chromium.org</owner>
   <owner>treib@chromium.org</owner>
   <owner>chrome-signin-mobile-team@google.com</owner>
@@ -2084,7 +2084,7 @@
 </histogram>
 
 <histogram name="Signin.IOSAccountsOnDeviceManagementTypesHadUnknownTypes"
-    enum="BooleanPresent" expires_after="2026-03-29">
+    enum="BooleanPresent" expires_after="2026-05-31">
   <owner>msarda@chromium.org</owner>
   <owner>treib@chromium.org</owner>
   <owner>chrome-signin-mobile-team@google.com</owner>
@@ -2095,7 +2095,7 @@
 </histogram>
 
 <histogram name="Signin.IOSAccountsOnDeviceManagementTypesSummary{DeviceType}"
-    enum="AccountManagementTypesSummary" expires_after="2026-03-29">
+    enum="AccountManagementTypesSummary" expires_after="2026-05-31">
   <owner>msarda@chromium.org</owner>
   <owner>treib@chromium.org</owner>
   <owner>chrome-signin-mobile-team@google.com</owner>
@@ -2141,7 +2141,7 @@
 </histogram>
 
 <histogram name="Signin.IOSChangeProfileReason" enum="IOSChangeProfileReason"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>jlebel@chromium.org</owner>
   <owner>treib@chromium.org</owner>
   <owner>chrome-signin-mobile-team@google.com</owner>
@@ -3083,7 +3083,7 @@
 <!-- LINT.IfChange(SigninFlowTimestamps) -->
 
 <histogram name="Signin.SignIn.Timestamps.{FlowVariant}.{Event}" units="ms"
-    expires_after="2026-04-01">
+    expires_after="2026-05-31">
   <owner>myuu@google.com</owner>
   <owner>chrome-signin-mobile-team@google.com</owner>
   <summary>
@@ -3387,7 +3387,7 @@
 </histogram>
 
 <histogram name="Signin.SignoutAndClearDataFromManagedAccount"
-    enum="BooleanSignoutFromManagedAccount" expires_after="2026-03-29">
+    enum="BooleanSignoutFromManagedAccount" expires_after="2026-05-31">
   <owner>esalma@google.com</owner>
   <owner>chrome-signin-team@google.com</owner>
   <summary>
@@ -3461,7 +3461,7 @@
 
 <histogram
     name="Signin.SSOIdentityListRequest.FetchIdentitiesWithCallback.Duration"
-    units="ms" expires_after="2026-04-01">
+    units="ms" expires_after="2026-05-31">
   <owner>jlebel@chromium.org</owner>
   <owner>chrome-signin-team@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/simple/histograms.xml b/tools/metrics/histograms/metadata/simple/histograms.xml
index 4c3ad81..107147fb 100644
--- a/tools/metrics/histograms/metadata/simple/histograms.xml
+++ b/tools/metrics/histograms/metadata/simple/histograms.xml
@@ -35,7 +35,7 @@
 
 <histogram
     name="SimpleGeolocation.Provider.GeolocationControllerRequestInterval"
-    units="hours" expires_after="2026-03-28">
+    units="hours" expires_after="2026-05-31">
   <owner>alvinji@chromium.org</owner>
   <owner>deviceapi-team@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/storage/histograms.xml b/tools/metrics/histograms/metadata/storage/histograms.xml
index 9b7912e..09b4435 100644
--- a/tools/metrics/histograms/metadata/storage/histograms.xml
+++ b/tools/metrics/histograms/metadata/storage/histograms.xml
@@ -166,7 +166,7 @@
 </histogram>
 
 <histogram name="API.StorageAccessHeader.ActivateStorageAccessLoadOutcome"
-    enum="ActivateStorageAccessLoadOutcome" expires_after="2026-01-04">
+    enum="ActivateStorageAccessLoadOutcome" expires_after="2027-01-04">
   <owner>sledoux@chromium.org</owner>
   <owner>cfredric@chromium.org</owner>
   <summary>
@@ -417,7 +417,7 @@
 </histogram>
 
 <histogram name="IndexedDB.Create{Mode}Transaction.NumTransactionsInConnection"
-    units="transactions" expires_after="2026-03-30">
+    units="transactions" expires_after="2026-05-31">
   <owner>fdoray@chromium.org</owner>
   <owner>catan-team@chromium.org</owner>
   <summary>
@@ -431,7 +431,7 @@
 </histogram>
 
 <histogram name="IndexedDB.Create{Mode}Transaction.NumTransactionsInDatabase"
-    units="transactions" expires_after="2026-03-30">
+    units="transactions" expires_after="2026-05-31">
   <owner>fdoray@chromium.org</owner>
   <owner>catan-team@chromium.org</owner>
   <summary>
@@ -639,7 +639,7 @@
 </histogram>
 
 <histogram name="IndexedDB.NumConnections.OnCreateAbove10k" units="connections"
-    expires_after="2026-03-30">
+    expires_after="2026-05-31">
   <owner>fdoray@chromium.org</owner>
   <owner>catan-team@chromium.org</owner>
   <summary>
@@ -651,7 +651,7 @@
 </histogram>
 
 <histogram name="IndexedDB.NumPendingConnections.OnCreateAbove10k"
-    units="connections" expires_after="2026-03-30">
+    units="connections" expires_after="2026-05-31">
   <owner>fdoray@chromium.org</owner>
   <owner>catan-team@chromium.org</owner>
   <summary>
@@ -665,7 +665,7 @@
 
 <histogram
     name="IndexedDB.NumTransactionsInIDBDatabaseOnTransactionCreated.10kTransactions"
-    units="transactions" expires_after="2026-03-30">
+    units="transactions" expires_after="2026-05-31">
   <owner>fdoray@chromium.org</owner>
   <owner>catan-team@chromium.org</owner>
   <summary>
@@ -758,7 +758,7 @@
 </histogram>
 
 <histogram name="IndexedDB.TransactionTimeout.{TransactionCount}CommitPending"
-    enum="Boolean" expires_after="2026-03-30">
+    enum="Boolean" expires_after="2026-05-31">
   <owner>fdoray@chromium.org</owner>
   <owner>catan-team@chromium.org</owner>
   <summary>
@@ -770,7 +770,7 @@
 </histogram>
 
 <histogram name="IndexedDB.TransactionTimeout.{TransactionCount}IsAborted"
-    enum="IDBTransactionMode" expires_after="2026-03-30">
+    enum="IDBTransactionMode" expires_after="2026-05-31">
   <owner>fdoray@chromium.org</owner>
   <owner>catan-team@chromium.org</owner>
   <summary>
@@ -782,7 +782,7 @@
 </histogram>
 
 <histogram name="IndexedDB.TransactionTimeout.{TransactionCount}IsConnected"
-    enum="Boolean" expires_after="2026-03-30">
+    enum="Boolean" expires_after="2026-05-31">
   <owner>fdoray@chromium.org</owner>
   <owner>catan-team@chromium.org</owner>
   <summary>
@@ -795,7 +795,7 @@
 </histogram>
 
 <histogram name="IndexedDB.TransactionTimeout.{TransactionCount}Mode"
-    enum="IDBTransactionMode" expires_after="2026-03-30">
+    enum="IDBTransactionMode" expires_after="2026-05-31">
   <owner>fdoray@chromium.org</owner>
   <owner>catan-team@chromium.org</owner>
   <summary>
@@ -808,7 +808,7 @@
 
 <histogram
     name="IndexedDB.TransactionTimeout.{TransactionCount}NumTransactionsInConnection"
-    units="transactions" expires_after="2026-03-30">
+    units="transactions" expires_after="2026-05-31">
   <owner>fdoray@chromium.org</owner>
   <owner>catan-team@chromium.org</owner>
   <summary>
@@ -822,7 +822,7 @@
 
 <histogram
     name="IndexedDB.TransactionTimeout.{TransactionCount}NumTransactionsInDB"
-    units="transactions" expires_after="2026-03-30">
+    units="transactions" expires_after="2026-05-31">
   <owner>fdoray@chromium.org</owner>
   <owner>catan-team@chromium.org</owner>
   <summary>
@@ -835,7 +835,7 @@
 </histogram>
 
 <histogram name="IndexedDB.TransactionTimeout.{TransactionCount}TaskRunQueued"
-    enum="Boolean" expires_after="2026-03-30">
+    enum="Boolean" expires_after="2026-05-31">
   <owner>fdoray@chromium.org</owner>
   <owner>catan-team@chromium.org</owner>
   <summary>
@@ -999,7 +999,7 @@
 </histogram>
 
 <histogram name="LocalStorage.MojoSizeInKB" units="KB"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>fergal@chromium.org</owner>
   <owner>chrome-owp-storage@google.com</owner>
   <summary>
@@ -1009,7 +1009,7 @@
 </histogram>
 
 <histogram name="LocalStorage.MojoTimeToPrime" units="ms"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>fergal@chromium.org</owner>
   <owner>chrome-owp-storage@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/subresource/histograms.xml b/tools/metrics/histograms/metadata/subresource/histograms.xml
index 330c68c..2b9f6fc8 100644
--- a/tools/metrics/histograms/metadata/subresource/histograms.xml
+++ b/tools/metrics/histograms/metadata/subresource/histograms.xml
@@ -230,7 +230,7 @@
 </histogram>
 
 <histogram name="SubresourceFilter.PageLoad.AdsInterventionTriggered"
-    enum="AdsViolations" expires_after="2026-03-29">
+    enum="AdsViolations" expires_after="2026-05-31">
   <owner>yaoxia@google.com</owner>
   <owner>chrome-ads-histograms@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/sync/enums.xml b/tools/metrics/histograms/metadata/sync/enums.xml
index 97ef6ed..c723a31 100644
--- a/tools/metrics/histograms/metadata/sync/enums.xml
+++ b/tools/metrics/histograms/metadata/sync/enums.xml
@@ -1480,6 +1480,18 @@
 
 <!-- LINT.ThenChange(/components/sync/service/sync_service_impl.h:SyncResetEngineReason) -->
 
+<!-- LINT.IfChange(SyncSetupIncompleteMigrationDecision) -->
+
+<enum name="SyncSetupIncompleteMigrationDecision">
+  <int value="0" label="Migrate"/>
+  <int value="1" label="Don't migrate, not signed in"/>
+  <int value="2" label="Don't migrate, not syncing"/>
+  <int value="3" label="Don't migrate, sync setup complete"/>
+  <int value="4" label="Don't migrate, migration feature flag disabled"/>
+</enum>
+
+<!-- LINT.ThenChange(/components/browser_sync/sync_to_signin_migration.cc:SyncSetupIncompleteMigrationDecision) -->
+
 <!-- LINT.IfChange(SyncSharingMessageCommitErrorCode) -->
 
 <enum name="SyncSharingMessageCommitErrorCode">
@@ -1526,7 +1538,7 @@
   <int value="9" label="Don't migrate, auth error"/>
 </enum>
 
-<!-- LINT.ThenChange(/components/browser_sync/sync_to_signin_migration.cc:SyncToSigninMigrationDecisionOverall) -->
+<!-- LINT.ThenChange(/components/browser_sync/sync_to_signin_migration.cc:SyncToSigninMigrationDecision) -->
 
 <!-- LINT.IfChange(SyncToSigninMigrationReadingListStep) -->
 
diff --git a/tools/metrics/histograms/metadata/sync/histograms.xml b/tools/metrics/histograms/metadata/sync/histograms.xml
index 63c095c7..6e3427e 100644
--- a/tools/metrics/histograms/metadata/sync/histograms.xml
+++ b/tools/metrics/histograms/metadata/sync/histograms.xml
@@ -627,7 +627,7 @@
 </histogram>
 
 <histogram name="Sync.ConfigureDataTypeManagerOption"
-    enum="SyncFeatureOrTransport" expires_after="2026-03-31">
+    enum="SyncFeatureOrTransport" expires_after="2026-05-31">
   <owner>mastiz@chromium.org</owner>
   <owner>treib@chromium.org</owner>
   <summary>
@@ -1202,7 +1202,7 @@
 </histogram>
 
 <histogram name="Sync.EntityTracker.InvalidEntitiesOnLoad{SyncDataType}"
-    units="entities" expires_after="2026-04-01">
+    units="entities" expires_after="2026-05-31">
   <owner>mastiz@chromium.org</owner>
   <owner>treib@chromium.org</owner>
   <summary>
@@ -1652,7 +1652,7 @@
 </histogram>
 
 <histogram name="Sync.PendingInvalidationStatus"
-    enum="PendingInvalidationStatus" expires_after="2026-03-29">
+    enum="PendingInvalidationStatus" expires_after="2026-05-31">
   <owner>shabdan@google.com</owner>
   <owner>rushans@google.com</owner>
   <summary>
@@ -2012,7 +2012,7 @@
 </histogram>
 
 <histogram name="Sync.SignoutWithUnsyncedData" enum="Boolean"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>ankushkush@google.com</owner>
   <owner>src/components/sync/OWNERS</owner>
   <summary>
@@ -2158,6 +2158,17 @@
   </summary>
 </histogram>
 
+<histogram name="Sync.SyncSetupIncompleteMigrationDecision"
+    enum="SyncSetupIncompleteMigrationDecision" expires_after="2026-11-28">
+  <owner>ankushkush@chromium.org</owner>
+  <owner>src/components/sync/OWNERS</owner>
+  <summary>
+    The decision whether to migrate the user from sync-setup-incomplete state to
+    signed-in history-off state. This is recorded for all users upon profile
+    load.
+  </summary>
+</histogram>
+
 <histogram name="Sync.SyncStoppedReported" enum="Boolean"
     expires_after="2026-05-24">
   <owner>rushans@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/tab/histograms.xml b/tools/metrics/histograms/metadata/tab/histograms.xml
index e9bdd994..c239c9c 100644
--- a/tools/metrics/histograms/metadata/tab/histograms.xml
+++ b/tools/metrics/histograms/metadata/tab/histograms.xml
@@ -332,7 +332,7 @@
   </summary>
 </histogram>
 
-<histogram name="Tab.AgeAtDeletion" units="ms" expires_after="2026-03-08">
+<histogram name="Tab.AgeAtDeletion" units="ms" expires_after="2026-05-31">
   <owner>ewannpv@chromium.org</owner>
   <owner>gambard@chromium.org</owner>
   <owner>bling-team@google.com</owner>
@@ -1215,7 +1215,7 @@
 </histogram>
 
 <histogram name="TabGroups.SavedTabGroupLifespan" units="minutes"
-    expires_after="2026-01-11">
+    expires_after="2026-04-05">
   <owner>dljames@chromium.org</owner>
   <owner>top-chrome-desktop-ui@google.com</owner>
   <summary>
@@ -1226,7 +1226,7 @@
 </histogram>
 
 <histogram name="TabGroups.SavedTabGroupOpenCount" units="groups"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>dpenning@chromium.org</owner>
   <owner>top-chrome-desktop-ui@google.com</owner>
   <summary>
@@ -1351,7 +1351,7 @@
 </histogram>
 
 <histogram name="TabGroups.Shared.LastTabClosed2" enum="Boolean"
-    expires_after="2026-01-11">
+    expires_after="2026-04-05">
   <owner>dljames@chromium.org</owner>
   <owner>top-chrome-desktop-ui@google.com</owner>
   <summary>
@@ -1375,7 +1375,7 @@
 </histogram>
 
 <histogram name="TabGroups.Shared.Manage.Desktop"
-    enum="SharedTabGroupManageTypeDesktop" expires_after="2026-01-11">
+    enum="SharedTabGroupManageTypeDesktop" expires_after="2026-04-05">
   <owner>dljames@chromium.org</owner>
   <owner>top-chrome-desktop-ui@google.com</owner>
   <summary>
@@ -1462,7 +1462,7 @@
 </histogram>
 
 <histogram name="TabGroups.Shortcuts" enum="TabGroupShortcut"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>dljames@chromium.org</owner>
   <owner>top-chrome-desktop-ui@google.com</owner>
   <summary>
@@ -1982,7 +1982,7 @@
 </histogram>
 
 <histogram name="TabGroups.UnsavedTabGroupCount" units="groups"
-    expires_after="2026-01-11">
+    expires_after="2026-04-05">
   <owner>dljames@chromium.org</owner>
   <owner>top-chrome-desktop-ui@google.com</owner>
   <summary>
@@ -1993,7 +1993,7 @@
 </histogram>
 
 <histogram name="TabGroups.UnsavedTabGroupTabCount" units="tabs"
-    expires_after="2026-01-11">
+    expires_after="2026-04-05">
   <owner>dljames@chromium.org</owner>
   <owner>top-chrome-desktop-ui@google.com</owner>
   <summary>
@@ -3041,7 +3041,7 @@
 </histogram>
 
 <histogram name="Tabs.Startup.TabCount.{TabType}" units="count"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>davidjm@chromium.org</owner>
   <owner>nyquist@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/ui/enums.xml b/tools/metrics/histograms/metadata/ui/enums.xml
index 711777be..2c92b256 100644
--- a/tools/metrics/histograms/metadata/ui/enums.xml
+++ b/tools/metrics/histograms/metadata/ui/enums.xml
@@ -441,6 +441,7 @@
   <int value="76" label="UMA_SIGN_IN"/>
   <int value="77" label="UMA_TAB_PICKER_LIMIT_REACHED"/>
   <int value="78" label="UMA_FUSEBOX_MAX_ATTACHMENTS"/>
+  <int value="79" label="UMA_FUSEBOX_UPLOAD_FAILED"/>
 </enum>
 
 <enum name="WebUIPreloadReason">
diff --git a/tools/metrics/histograms/metadata/ui/histograms.xml b/tools/metrics/histograms/metadata/ui/histograms.xml
index fd81065..44dadee6 100644
--- a/tools/metrics/histograms/metadata/ui/histograms.xml
+++ b/tools/metrics/histograms/metadata/ui/histograms.xml
@@ -275,7 +275,7 @@
 </histogram>
 
 <histogram name="DefaultBrowser.InfoBar.TimesShownBeforeAccept" units="count"
-    expires_after="2026-01-18">
+    expires_after="2026-05-31">
   <owner>agale@chromium.org</owner>
   <owner>robliao@chromium.org</owner>
   <summary>
@@ -885,7 +885,7 @@
 </histogram>
 
 <histogram name="WebUI.TopChrome.Preload.Reason" enum="WebUIPreloadReason"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>kerenzhu@chromium.org</owner>
   <owner>tluk@chromium.org</owner>
   <owner>robliao@chromium.org</owner>
@@ -895,7 +895,7 @@
 </histogram>
 
 <histogram name="WebUI.TopChrome.Preload.Result" enum="WebUIPreloadResult"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>kerenzhu@chromium.org</owner>
   <owner>dayeung@chromium.org</owner>
   <owner>elainechien@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/uma/histograms.xml b/tools/metrics/histograms/metadata/uma/histograms.xml
index a1702b7..5253cbc 100644
--- a/tools/metrics/histograms/metadata/uma/histograms.xml
+++ b/tools/metrics/histograms/metadata/uma/histograms.xml
@@ -1019,7 +1019,7 @@
 </histogram>
 
 <histogram name="UMA.TruncatedEvents.UserAction" units="events"
-    expires_after="2026-05-24">
+    expires_after="2026-05-31">
   <owner>rkaplow@chromium.org</owner>
   <owner>src/base/metrics/OWNERS</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/update_engine/histograms.xml b/tools/metrics/histograms/metadata/update_engine/histograms.xml
index 6314b33..34ffecfe 100644
--- a/tools/metrics/histograms/metadata/update_engine/histograms.xml
+++ b/tools/metrics/histograms/metadata/update_engine/histograms.xml
@@ -110,7 +110,7 @@
 </histogram>
 
 <histogram name="UpdateEngine.Attempt.Number" units="count"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>kimjae@chromium.org</owner>
   <owner>chromeos-core-services@google.com</owner>
   <summary>
@@ -194,7 +194,7 @@
 </histogram>
 
 <histogram name="UpdateEngine.Attempt.TimeSinceLastAttemptMinutes"
-    units="minutes" expires_after="2026-03-29">
+    units="minutes" expires_after="2026-05-31">
   <owner>kimjae@chromium.org</owner>
   <owner>chromeos-core-services@google.com</owner>
   <summary>
@@ -337,7 +337,7 @@
 </histogram>
 
 <histogram name="UpdateEngine.Check.TimeSinceLastCheckMinutes" units="minutes"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>kimjae@chromium.org</owner>
   <owner>chromeos-core-services@google.com</owner>
   <summary>
@@ -403,7 +403,7 @@
 </histogram>
 
 <histogram name="UpdateEngine.Daily.OSAgeDays" units="days"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>kimjae@chromium.org</owner>
   <owner>chromeos-core-services@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/user_education/histograms.xml b/tools/metrics/histograms/metadata/user_education/histograms.xml
index 6b32fa7..f17b5d4f 100644
--- a/tools/metrics/histograms/metadata/user_education/histograms.xml
+++ b/tools/metrics/histograms/metadata/user_education/histograms.xml
@@ -258,7 +258,7 @@
 </histogram>
 
 <histogram name="UserEducation.MessageNotShown.TimeInQueue" units="ms"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>dfried@chromium.org</owner>
   <owner>frizzle-team@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/v8/histograms.xml b/tools/metrics/histograms/metadata/v8/histograms.xml
index abfdbe4..4b0a36d 100644
--- a/tools/metrics/histograms/metadata/v8/histograms.xml
+++ b/tools/metrics/histograms/metadata/v8/histograms.xml
@@ -1685,7 +1685,7 @@
 </histogram>
 
 <histogram name="V8.SandboxReservationSizeGB" units="GB"
-    expires_after="2026-01-04">
+    expires_after="2026-07-04">
   <owner>mlippautz@chromium.org</owner>
   <owner>ishell@chromium.org</owner>
   <summary>
@@ -2259,7 +2259,7 @@
 </histogram>
 
 <histogram name="V8.WasmModuleCodeSizeMiB" units="MB"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>ecmziegler@chromium.org</owner>
   <owner>clemensb@chromium.org</owner>
   <owner>wasm-runtime@google.com</owner>
@@ -2282,7 +2282,7 @@
 </histogram>
 
 <histogram name="V8.WasmModuleMetadataSizeKiB" units="KB"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>ecmziegler@chromium.org</owner>
   <owner>clemensb@chromium.org</owner>
   <owner>wasm-runtime@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/web_core/histograms.xml b/tools/metrics/histograms/metadata/web_core/histograms.xml
index 7c69569..71d6f36e 100644
--- a/tools/metrics/histograms/metadata/web_core/histograms.xml
+++ b/tools/metrics/histograms/metadata/web_core/histograms.xml
@@ -63,7 +63,7 @@
 </variants>
 
 <histogram name="WebCore.DistillabilityUs" units="microseconds"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>wychen@chromium.org</owner>
   <owner>gilmanmh@google.com</owner>
   <summary>
@@ -280,22 +280,29 @@
 </histogram>
 
 <histogram name="WebCore.IndexedDB.LevelDBOpenErrors" enum="LevelDBErrorTypes"
-    expires_after="2025-02-02">
-  <owner>estade@chromium.org</owner>
+    expires_after="2026-05-01">
+  <owner>leimy@chromium.org</owner>
+  <owner>evanstade@microsoft.com</owner>
   <owner>chrome-owp-storage@google.com</owner>
   <summary>
     Error classes returned by LevelDB when it failed to open a database.
+
+    Warning: this histogram was expired from 2025-02-02 to 2025-12-01; data may
+    be missing.
   </summary>
 </histogram>
 
 <histogram name="WebCore.IndexedDB.LevelDBOpenErrors.BFE{LevelDBBFEMethods}"
-    enum="PlatformFileError" expires_after="2025-04-13">
-  <owner>estade@chromium.org</owner>
-  <owner>cmumford@chromium.org</owner>
+    enum="PlatformFileError" expires_after="2026-05-01">
+  <owner>leimy@chromium.org</owner>
+  <owner>evanstade@microsoft.com</owner>
   <owner>chrome-owp-storage@google.com</owner>
   <summary>
     Errors (base::File::Error) encountered by a single LevelDBEnv method when
     opening an IndexedDB instance. {LevelDBBFEMethods}
+
+    Warning: this histogram was expired from 2025-04-13 to 2025-12-01; data may
+    be missing.
   </summary>
   <token key="LevelDBBFEMethods" variants="LevelDBBFEMethods">
     <variant name=""/>
@@ -303,20 +310,28 @@
 </histogram>
 
 <histogram name="WebCore.IndexedDB.LevelDBOpenErrors.Corruption"
-    enum="LevelDBCorruptionTypes" expires_after="2025-06-22">
-  <owner>estade@chromium.org</owner>
+    enum="LevelDBCorruptionTypes" expires_after="2026-05-01">
+  <owner>leimy@chromium.org</owner>
+  <owner>evanstade@microsoft.com</owner>
   <owner>chrome-owp-storage@google.com</owner>
   <summary>
     Types of corruption that LevelDB encounters when opening a database.
+
+    Warning: this histogram was expired from 2025-06-22 to 2025-12-01; data may
+    be missing.
   </summary>
 </histogram>
 
 <histogram name="WebCore.IndexedDB.LevelDBOpenErrors.EnvMethod"
-    enum="LevelDBIOErrorMethods" expires_after="2025-03-23">
-  <owner>estade@chromium.org</owner>
+    enum="LevelDBIOErrorMethods" expires_after="2026-05-01">
+  <owner>leimy@chromium.org</owner>
+  <owner>evanstade@microsoft.com</owner>
   <owner>chrome-owp-storage@google.com</owner>
   <summary>
     LevelDBEnv methods that generated IO errors when opening a database.
+
+    Warning: this histogram was expired from 2025-03-23 to 2025-12-01; data may
+    be missing.
   </summary>
 </histogram>
 
@@ -474,7 +489,7 @@
 </histogram>
 
 <histogram name="WebCore.IndexedDB.RequestDuration2.{RequestType}.Foreground"
-    units="ms" expires_after="2026-03-29">
+    units="ms" expires_after="2026-05-31">
   <owner>estade@chromium.org</owner>
   <owner>chrome-owp-storage@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/web_rtc/histograms.xml b/tools/metrics/histograms/metadata/web_rtc/histograms.xml
index e2f02cc..c848f99 100644
--- a/tools/metrics/histograms/metadata/web_rtc/histograms.xml
+++ b/tools/metrics/histograms/metadata/web_rtc/histograms.xml
@@ -104,7 +104,7 @@
 </variants>
 
 <histogram name="WebRTC.Audio.Agc.InputClippingRate" units="%"
-    expires_after="2026-03-30">
+    expires_after="2026-05-31">
   <owner>silen@chromium.org</owner>
   <owner>peah@chromium.org</owner>
   <owner>webrtc-audio-uma@google.com</owner>
@@ -115,7 +115,7 @@
 </histogram>
 
 <histogram name="WebRTC.Audio.Agc2.DigitalGainApplied" units="dB"
-    expires_after="2026-03-30">
+    expires_after="2026-05-31">
   <owner>peah@chromium.org</owner>
   <owner>silen@chromium.org</owner>
   <owner>webrtc-audio-uma@google.com</owner>
@@ -141,7 +141,7 @@
 </histogram>
 
 <histogram name="WebRTC.Audio.Agc2.FixedDigitalGainCurveRegion.{Region}"
-    units="seconds" expires_after="2026-03-30">
+    units="seconds" expires_after="2026-05-31">
   <owner>peah@chromium.org</owner>
   <owner>silen@chromium.org</owner>
   <owner>webrtc-audio-uma@google.com</owner>
@@ -173,7 +173,7 @@
 </histogram>
 
 <histogram name="WebRTC.Audio.Apm.RecommendedInputVolume.OnChangeToMatchTarget"
-    units="volume" expires_after="2026-03-30">
+    units="volume" expires_after="2026-05-31">
   <owner>silen@chromium.org</owner>
   <owner>peah@chromium.org</owner>
   <owner>webrtc-audio-uma@google.com</owner>
@@ -185,7 +185,7 @@
 </histogram>
 
 <histogram name="WebRTC.Audio.Apm.{InputVolumeType}.OnChange" units="volume"
-    expires_after="2026-03-30">
+    expires_after="2026-05-31">
   <owner>silen@chromium.org</owner>
   <owner>peah@chromium.org</owner>
   <owner>webrtc-audio-uma@google.com</owner>
@@ -200,7 +200,7 @@
 </histogram>
 
 <histogram name="WebRTC.Audio.Apm.{InputVolumeType}.{Metric}Average"
-    units="volume" expires_after="2026-03-30">
+    units="volume" expires_after="2026-05-31">
   <owner>silen@chromium.org</owner>
   <owner>peah@chromium.org</owner>
   <owner>webrtc-audio-uma@google.com</owner>
@@ -221,7 +221,7 @@
 </histogram>
 
 <histogram name="WebRTC.Audio.Apm.{InputVolumeType}.{Metric}Rate"
-    units="changes/minute" expires_after="2026-03-30">
+    units="changes/minute" expires_after="2026-05-31">
   <owner>silen@chromium.org</owner>
   <owner>peah@chromium.org</owner>
   <owner>webrtc-audio-uma@google.com</owner>
@@ -1117,7 +1117,7 @@
 </histogram>
 
 <histogram name="WebRTC.DesktopCapture.Win.WgcCapturerResult"
-    enum="WebRtcWgcCapturerResult" expires_after="2026-03-29">
+    enum="WebRtcWgcCapturerResult" expires_after="2026-05-31">
   <owner>alcooper@chromium.org</owner>
   <owner>henrika@chromium.org</owner>
   <owner>edgecapabilitiesdev@microsoft.com</owner>
@@ -1128,7 +1128,7 @@
 </histogram>
 
 <histogram name="WebRTC.DesktopCapture.Win.WgcCaptureSessionGetFrameResult"
-    enum="WebRtcWgcCaptureSessionGetFrameResult" expires_after="2026-03-29">
+    enum="WebRtcWgcCaptureSessionGetFrameResult" expires_after="2026-05-31">
   <owner>alcooper@chromium.org</owner>
   <owner>henrika@chromium.org</owner>
   <owner>edgecapabilitiesdev@microsoft.com</owner>
@@ -1202,7 +1202,7 @@
 </histogram>
 
 <histogram name="WebRTC.DesktopCaptureCounters" enum="DesktopCaptureCounters"
-    expires_after="2026-03-29">
+    expires_after="2026-05-31">
   <owner>toprice@chromium.org</owner>
   <owner>webrtc-dev@chromium.org</owner>
   <summary>
@@ -1818,7 +1818,7 @@
 </histogram>
 
 <histogram name="WebRTC.Screenshare.DesktopCapturerFullscreenDetector"
-    enum="Boolean" expires_after="2026-03-29">
+    enum="Boolean" expires_after="2026-05-31">
   <owner>kron@chromium.org</owner>
   <owner>agpalak@chromium.org</owner>
   <owner>rtc-meet-in-chrome@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/webapps/histograms.xml b/tools/metrics/histograms/metadata/webapps/histograms.xml
index b00b20f6..403fd00c7 100644
--- a/tools/metrics/histograms/metadata/webapps/histograms.xml
+++ b/tools/metrics/histograms/metadata/webapps/histograms.xml
@@ -106,7 +106,7 @@
 </histogram>
 
 <histogram name="AppBanners.InstallableStatusCode" enum="InstallableStatusCode"
-    expires_after="2026-01-18">
+    expires_after="2026-05-31">
   <owner>dominickn@chromium.org</owner>
   <owner>pjmclachlan@google.com</owner>
   <summary>
@@ -1650,7 +1650,7 @@
 </histogram>
 
 <histogram name="WebApp.Isolated.ReadResponseHeadError"
-    enum="IsolatedWebAppReadResponseHeadError" expires_after="2026-03-08">
+    enum="IsolatedWebAppReadResponseHeadError" expires_after="2026-05-31">
   <owner>giovax@chromium.org</owner>
   <owner>pwa-commercial@google.com</owner>
   <owner>src/chrome/browser/web_applications/isolated_web_apps/OWNERS</owner>
@@ -1664,7 +1664,7 @@
 </histogram>
 
 <histogram name="WebApp.Isolated.ReadResponseHeadSuccess" enum="BooleanSuccess"
-    expires_after="2026-02-09">
+    expires_after="2026-05-31">
   <owner>giovax@chromium.org</owner>
   <owner>pwa-commercial@google.com</owner>
   <owner>src/chrome/browser/web_applications/isolated_web_apps/OWNERS</owner>
@@ -1724,7 +1724,7 @@
 </histogram>
 
 <histogram name="WebApp.Isolated.SignatureVerificationDuration" units="ms"
-    expires_after="2026-02-09">
+    expires_after="2026-05-31">
   <owner>giovax@chromium.org</owner>
   <owner>pwa-commercial@google.com</owner>
   <owner>src/chrome/browser/web_applications/isolated_web_apps/OWNERS</owner>
@@ -1764,7 +1764,7 @@
 </histogram>
 
 <histogram name="WebApp.Isolated.SwbnFileUsabilitySuccess" enum="BooleanValid"
-    expires_after="2026-02-09">
+    expires_after="2026-05-31">
   <owner>giovax@chromium.org</owner>
   <owner>pwa-commercial@google.com</owner>
   <owner>src/chrome/browser/web_applications/isolated_web_apps/OWNERS</owner>
@@ -1783,7 +1783,7 @@
 </histogram>
 
 <histogram name="WebApp.Isolated.UpdateError" enum="IsolatedWebAppUpdateError"
-    expires_after="2026-01-25">
+    expires_after="2026-05-31">
   <owner>giovax@chromium.org</owner>
   <owner>pwa-commercial@google.com</owner>
   <owner>src/chrome/browser/web_applications/isolated_web_apps/OWNERS</owner>
@@ -1798,7 +1798,7 @@
 </histogram>
 
 <histogram name="WebApp.Isolated.UpdateSuccess" enum="BooleanSuccess"
-    expires_after="2026-01-25">
+    expires_after="2026-05-31">
   <owner>giovax@chromium.org</owner>
   <owner>pwa-commercial@google.com</owner>
   <owner>src/chrome/browser/web_applications/isolated_web_apps/OWNERS</owner>
diff --git a/tools/metrics/histograms/metadata/windows/histograms.xml b/tools/metrics/histograms/metadata/windows/histograms.xml
index 4bb064c..9fb57c5 100644
--- a/tools/metrics/histograms/metadata/windows/histograms.xml
+++ b/tools/metrics/histograms/metadata/windows/histograms.xml
@@ -292,7 +292,7 @@
 </histogram>
 
 <histogram name="Windows.OpenWithLauncherResult" enum="OpenWithLauncherResult"
-    expires_after="2026-03-28">
+    expires_after="2026-05-31">
   <owner>jessemckenna@google.com</owner>
   <owner>davidbienvenu@chromium.org</owner>
   <summary>
diff --git a/tools/perf/contrib/vr_benchmarks/BUILD.gn b/tools/perf/contrib/vr_benchmarks/BUILD.gn
index 61bedc90..06b64e58 100644
--- a/tools/perf/contrib/vr_benchmarks/BUILD.gn
+++ b/tools/perf/contrib/vr_benchmarks/BUILD.gn
@@ -26,7 +26,7 @@
   data_deps = [ "//testing:run_perf_test" ]
 
   if (is_android) {
-    data_deps += [ "//chrome/android:monochrome_public_bundle" ]
+    data_deps += [ "//chrome/android:chrome_public_bundle" ]
   }
 
   if (is_win) {
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index 8f91ed8..96c14d4 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -5,8 +5,8 @@
             "full_remote_path": "perfetto-luci-artifacts/v52.0/linux-arm64/trace_processor_shell"
         },
         "win": {
-            "hash": "db7ab3047262bb7b815430f485b78c6fc6590c4e",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/5fba796da3fb46b78a44029c04ef10d111041426/trace_processor_shell.exe"
+            "hash": "1ec53a0007bae76d4740a9cc5f2ab095540110cd",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/fca800884a32a9d79d299fd493d753301c6aeaf6/trace_processor_shell.exe"
         },
         "linux_arm": {
             "hash": "46d798c1864490cbb2ee053d6eda436184470e69",
@@ -21,8 +21,8 @@
             "full_remote_path": "perfetto-luci-artifacts/ebf44e57a3b734c5281bdff53d9945805486004e/mac-arm64/trace_processor_shell"
         },
         "linux": {
-            "hash": "e131b79f9875ddccd085d3084d9f6dc125c657aa",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/988808bde39dae13486cf3936c729f86e1cc5e61/trace_processor_shell"
+            "hash": "c0ba75442d2cf6c53954b6d39145734521621c85",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/fca800884a32a9d79d299fd493d753301c6aeaf6/trace_processor_shell"
         }
     },
     "power_profile.sql": {
diff --git a/tools/typescript/definitions/autofill_private.d.ts b/tools/typescript/definitions/autofill_private.d.ts
index 6a470af..c8c2adb 100644
--- a/tools/typescript/definitions/autofill_private.d.ts
+++ b/tools/typescript/definitions/autofill_private.d.ts
@@ -331,7 +331,7 @@
           Promise<boolean>;
       export function getWalletablePassDetectionOptInStatus(): Promise<boolean>;
       export function setWalletablePassDetectionOptInStatus(optedIn: boolean):
-          Promise<void>;
+          Promise<boolean>;
       export const onPersonalDataChanged: ChromeEvent<
           (addresses: AddressEntry[], creditCards: CreditCardEntry[],
            ibans: IbanEntry[], payOverTimeIssuers: PayOverTimeIssuerEntry[],
diff --git a/ui/accessibility/ax_node_data.cc b/ui/accessibility/ax_node_data.cc
index 5e652c18..4a1ad0b 100644
--- a/ui/accessibility/ax_node_data.cc
+++ b/ui/accessibility/ax_node_data.cc
@@ -220,190 +220,6 @@
   }
 }
 
-AXVectorBoolStore::AXVectorBoolStore() = default;
-AXVectorBoolStore::~AXVectorBoolStore() = default;
-
-AXVectorBoolStore::AXVectorBoolStore(const AXVectorBoolStore& other)
-    : list_(other.list_) {}
-
-bool AXVectorBoolStore::Has(ax::mojom::BoolAttribute attribute) const {
-  return list_.Has(attribute);
-}
-
-bool AXVectorBoolStore::Get(ax::mojom::BoolAttribute attribute) const {
-  return list_.Get(attribute);
-}
-
-void AXVectorBoolStore::Set(ax::mojom::BoolAttribute attribute, bool value) {
-  list_.Set(attribute, value);
-}
-
-void AXVectorBoolStore::Remove(ax::mojom::BoolAttribute attribute) {
-  list_.Remove(attribute);
-}
-
-bool AXVectorBoolStore::IsBitset() const {
-  return false;
-}
-
-std::unique_ptr<AXBoolStore> AXVectorBoolStore::Clone() const {
-  return std::make_unique<AXVectorBoolStore>(*this);
-}
-
-size_t AXVectorBoolStore::Size() const {
-  return list_.size();
-}
-
-size_t AXVectorBoolStore::ObjectSize() const {
-  return sizeof(*this) + (list_.container().capacity() *
-                          sizeof(std::pair<ax::mojom::BoolAttribute, bool>));
-}
-
-void AXVectorBoolStore::ForEach(
-    base::FunctionRef<void(ax::mojom::BoolAttribute, bool)> callback) const {
-  for (const auto& [attr, value] : list_) {
-    callback(attr, value);
-  }
-}
-
-void AXVectorBoolStore::Merge(const AXBoolStore& other) {
-  other.ForEach([this](ax::mojom::BoolAttribute attr, bool value) {
-    this->Set(attr, value);
-  });
-}
-
-void AXVectorBoolStore::Clear() {
-  list_.clear();
-}
-
-const AXBitset<ax::mojom::BoolAttribute>& AXVectorBoolStore::GetBitsetStore()
-    const {
-  static const AXBitset<ax::mojom::BoolAttribute> bitset;
-  return bitset;
-}
-
-const AXBoolAttributes& AXVectorBoolStore::GetVectorStore() const {
-  return list_;
-}
-
-bool AXVectorBoolStore::IsEqual(const AXBoolStore& other) const {
-  if (other.IsBitset()) {
-    return false;
-  }
-  return IsEqual(static_cast<const AXVectorBoolStore&>(other));
-}
-
-bool AXVectorBoolStore::IsEqual(const AXVectorBoolStore& other) const {
-  return list_ == other.list_;
-}
-
-void AXVectorBoolStore::PopulateFromBitset(
-    const AXBitset<ax::mojom::BoolAttribute>& source_bitset) {
-  std::vector<std::pair<ax::mojom::BoolAttribute, bool>> pairs;
-  pairs.reserve(source_bitset.Size());
-  source_bitset.ForEach([&pairs](ax::mojom::BoolAttribute attr, bool value) {
-    pairs.emplace_back(attr, value);
-  });
-  list_.container().replace(std::move(pairs));
-}
-
-void AXVectorBoolStore::PopulateFromMap(
-    const base::flat_map<ax::mojom::BoolAttribute, bool>& source_map) {
-  list_.container() = source_map;
-}
-
-AXBitsetBoolStore::AXBitsetBoolStore() = default;
-AXBitsetBoolStore::~AXBitsetBoolStore() = default;
-
-AXBitsetBoolStore::AXBitsetBoolStore(const AXBitsetBoolStore& other)
-    : bitset_(other.bitset_) {}
-
-bool AXBitsetBoolStore::Has(ax::mojom::BoolAttribute attribute) const {
-  return bitset_.Get(attribute).has_value();
-}
-
-bool AXBitsetBoolStore::Get(ax::mojom::BoolAttribute attribute) const {
-  return bitset_.Get(attribute).value_or(false);
-}
-
-void AXBitsetBoolStore::Set(ax::mojom::BoolAttribute attribute, bool value) {
-  bitset_.Set(attribute, value);
-}
-
-void AXBitsetBoolStore::Remove(ax::mojom::BoolAttribute attribute) {
-  bitset_.Unset(attribute);
-}
-
-bool AXBitsetBoolStore::IsBitset() const {
-  return true;
-}
-
-std::unique_ptr<AXBoolStore> AXBitsetBoolStore::Clone() const {
-  return std::make_unique<AXBitsetBoolStore>(*this);
-}
-
-size_t AXBitsetBoolStore::Size() const {
-  return bitset_.Size();
-}
-
-size_t AXBitsetBoolStore::ObjectSize() const {
-  return sizeof(*this);
-}
-
-void AXBitsetBoolStore::Clear() {
-  bitset_ = AXBitset<ax::mojom::BoolAttribute>();
-}
-
-void AXBitsetBoolStore::ForEach(
-    base::FunctionRef<void(ax::mojom::BoolAttribute, bool)> callback) const {
-  bitset_.ForEach(callback);
-}
-
-void AXBitsetBoolStore::Merge(const AXBoolStore& other) {
-  if (other.IsBitset()) {
-    bitset_.Append(other.GetBitsetStore());
-  } else {
-    other.ForEach([this](ax::mojom::BoolAttribute attr, bool value) {
-      this->Set(attr, value);
-    });
-  }
-}
-
-bool AXBitsetBoolStore::IsEqual(const AXBoolStore& other) const {
-  if (!other.IsBitset()) {
-    return false;
-  }
-  return IsEqual(static_cast<const AXBitsetBoolStore&>(other));
-}
-
-bool AXBitsetBoolStore::IsEqual(const AXBitsetBoolStore& other) const {
-  return bitset_ == other.bitset_;
-}
-
-const AXBitset<ax::mojom::BoolAttribute>& AXBitsetBoolStore::GetBitsetStore()
-    const {
-  return bitset_;
-}
-
-const AXBoolAttributes& AXBitsetBoolStore::GetVectorStore() const {
-  static const AXBoolAttributes vector;
-  return vector;
-}
-
-void AXBitsetBoolStore::PopulateFromBitset(
-    const AXBitset<ax::mojom::BoolAttribute>& source_bitset) {
-  bitset_ = source_bitset;
-}
-
-void AXBitsetBoolStore::PopulateFromMap(
-    const base::flat_map<ax::mojom::BoolAttribute, bool>& source_map) {
-  AXBitset<ax::mojom::BoolAttribute> temp_bitset;
-  for (const auto& [attr, value] : source_map) {
-    temp_bitset.Set(attr, value);
-  }
-  bitset_ = temp_bitset;
-}
-
 AXNodeData::AXNodeData() : role(ax::mojom::Role::kUnknown) {}
 
 AXNodeData::~AXNodeData() = default;
diff --git a/ui/accessibility/ax_node_data.h b/ui/accessibility/ax_node_data.h
index f76cd34f..9b40e88 100644
--- a/ui/accessibility/ax_node_data.h
+++ b/ui/accessibility/ax_node_data.h
@@ -37,115 +37,6 @@
 // nodes in the same tree.
 AX_BASE_EXPORT bool IsNodeIdIntListAttribute(ax::mojom::IntListAttribute attr);
 
-class AX_BASE_EXPORT AXBoolStore {
- public:
-  virtual ~AXBoolStore() = default;
-
-  virtual bool Has(ax::mojom::BoolAttribute attr) const = 0;
-  virtual bool Get(ax::mojom::BoolAttribute attr) const = 0;
-  virtual void Set(ax::mojom::BoolAttribute attr, bool value) = 0;
-  virtual void Remove(ax::mojom::BoolAttribute attr) = 0;
-
-  // Returns the number of attributes currently set in the store.
-  virtual size_t Size() const = 0;
-
-  // Returns the total memory footprint of the store in bytes, including any
-  // heap allocations.
-  virtual size_t ObjectSize() const = 0;
-
-  virtual bool IsBitset() const = 0;
-  virtual bool IsEqual(const AXBoolStore& other) const = 0;
-  virtual std::unique_ptr<AXBoolStore> Clone() const = 0;
-  virtual void Clear() = 0;
-  virtual void Merge(const AXBoolStore& other) = 0;
-  virtual void ForEach(base::FunctionRef<void(ax::mojom::BoolAttribute, bool)>
-                           callback) const = 0;
-  virtual void PopulateFromBitset(
-      const AXBitset<ax::mojom::BoolAttribute>& source_bitset) = 0;
-  virtual void PopulateFromMap(
-      const base::flat_map<ax::mojom::BoolAttribute, bool>& source_map) = 0;
-
-  // The following methods break the abstraction of the AXBoolStore
-  // interface by exposing the underlying concrete implementation type.
-  //
-  // Their use should be limited to performance-critical code paths where
-  // type-aware algorithms are necessary (e.g., in diffing logic like
-  // CallIfAttributeValuesChanged).
-  //
-  // For all general-purpose interaction, prefer the standard virtual methods
-  // of this interface (Set/Get/Has/ForEach/etc.).
-  virtual const AXBitset<ax::mojom::BoolAttribute>& GetBitsetStore() const = 0;
-  virtual const AXBoolAttributes& GetVectorStore() const = 0;
-};
-
-class AX_BASE_EXPORT AXVectorBoolStore : public AXBoolStore {
- public:
-  AXVectorBoolStore();
-  ~AXVectorBoolStore() override;
-
-  AXVectorBoolStore(const AXVectorBoolStore& other);
-
-  // AXBoolStore implementation.
-  bool Has(ax::mojom::BoolAttribute attr) const override;
-  bool Get(ax::mojom::BoolAttribute attr) const override;
-  void Set(ax::mojom::BoolAttribute attr, bool value) override;
-  void Remove(ax::mojom::BoolAttribute attr) override;
-  bool IsBitset() const override;
-  bool IsEqual(const AXBoolStore& other) const override;
-  std::unique_ptr<AXBoolStore> Clone() const override;
-  void Clear() override;
-  size_t Size() const override;
-  size_t ObjectSize() const override;
-  void ForEach(base::FunctionRef<void(ax::mojom::BoolAttribute, bool)> callback)
-      const override;
-  void Merge(const AXBoolStore& other) override;
-  void PopulateFromBitset(
-      const AXBitset<ax::mojom::BoolAttribute>& source_bitset) override;
-  void PopulateFromMap(const base::flat_map<ax::mojom::BoolAttribute, bool>&
-                           source_map) override;
-  const AXBitset<ax::mojom::BoolAttribute>& GetBitsetStore() const override;
-  const AXBoolAttributes& GetVectorStore() const override;
-
-  bool IsEqual(const AXVectorBoolStore& other) const;
-
- private:
-  AXBoolAttributes list_;
-};
-
-class AX_BASE_EXPORT AXBitsetBoolStore : public AXBoolStore {
- public:
-  AXBitsetBoolStore();
-  ~AXBitsetBoolStore() override;
-
-  AXBitsetBoolStore(const AXBitsetBoolStore& other);
-
-  // AXBoolStore implementation.
-  bool Has(ax::mojom::BoolAttribute attr) const override;
-  bool Get(ax::mojom::BoolAttribute attr) const override;
-  void Set(ax::mojom::BoolAttribute attr, bool value) override;
-  void Remove(ax::mojom::BoolAttribute attr) override;
-  bool IsBitset() const override;
-  bool IsEqual(const AXBoolStore& other) const override;
-  std::unique_ptr<AXBoolStore> Clone() const override;
-  void Clear() override;
-  size_t Size() const override;
-  size_t ObjectSize() const override;
-  void Merge(const AXBoolStore& other) override;
-  void ForEach(base::FunctionRef<void(ax::mojom::BoolAttribute, bool)> callback)
-      const override;
-  void PopulateFromBitset(
-      const AXBitset<ax::mojom::BoolAttribute>& source_bitset) override;
-  void PopulateFromMap(const base::flat_map<ax::mojom::BoolAttribute, bool>&
-                           source_map) override;
-  const AXBitset<ax::mojom::BoolAttribute>& GetBitsetStore() const override;
-  const AXBoolAttributes& GetVectorStore() const override;
-
-  bool IsEqual(const AXBitsetBoolStore& other) const;
-
- private:
-  AXBitset<ax::mojom::BoolAttribute> bitset_;
-};
-
 // A compact representation of the accessibility information for a
 // single accessible object, in a form that can be serialized and sent from
 // one process to another.
diff --git a/ui/android/java/src/org/chromium/ui/widget/AnchoredPopupWindow.java b/ui/android/java/src/org/chromium/ui/widget/AnchoredPopupWindow.java
index 4a97478..daeaa5d6 100644
--- a/ui/android/java/src/org/chromium/ui/widget/AnchoredPopupWindow.java
+++ b/ui/android/java/src/org/chromium/ui/widget/AnchoredPopupWindow.java
@@ -1031,6 +1031,11 @@
         mAllowNonTouchableSize = allowNonTouchableSize;
     }
 
+    /** Changes the background at runtime. */
+    public void setBackgroundDrawable(Drawable background) {
+        mPopupWindow.setBackgroundDrawable(background);
+    }
+
     // RectProvider.Observer implementation.
     @Override
     public void onRectChanged() {
diff --git a/ui/android/javatests/src/org/chromium/ui/hierarchicalmenu/OWNERS b/ui/android/javatests/src/org/chromium/ui/hierarchicalmenu/OWNERS
new file mode 100644
index 0000000..3966cf9b
--- /dev/null
+++ b/ui/android/javatests/src/org/chromium/ui/hierarchicalmenu/OWNERS
@@ -0,0 +1 @@
+file://ui/android/java/src/org/chromium/ui/hierarchicalmenu/OWNERS
diff --git a/ui/android/junit/src/org/chromium/ui/hierarchicalmenu/OWNERS b/ui/android/junit/src/org/chromium/ui/hierarchicalmenu/OWNERS
new file mode 100644
index 0000000..3966cf9b
--- /dev/null
+++ b/ui/android/junit/src/org/chromium/ui/hierarchicalmenu/OWNERS
@@ -0,0 +1 @@
+file://ui/android/java/src/org/chromium/ui/hierarchicalmenu/OWNERS
diff --git a/ui/android/texture_compressor/decoder.rs b/ui/android/texture_compressor/decoder.rs
index e3fedc6..143b3c1 100644
--- a/ui/android/texture_compressor/decoder.rs
+++ b/ui/android/texture_compressor/decoder.rs
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+use crate::selectors::TABLES;
 #[derive(Debug, PartialEq, Eq)]
 pub struct BlockMetadata {
     pub base: [[i32; 3]; 2],
@@ -133,3 +134,82 @@
         flip: ((etc1_block >> 32) & 0b1) == 1,
     }
 }
+// Compute the final pixel color using the block's base color and per-pixel
+// bits. From the Khronos Data Format Specification v1.4.0
+// The modifier value comes from Table 142. and Table 143.
+// - `base`: should be the base color as an [R, G, B].
+// - `table_idx`: should be the index for the modifier table. (range 0-7).
+// - `negative`: If true, the modifier value is treated as negative.
+// - `large`: If true, we select the large value from the table.
+// The return value is a u32 packed in the 0xAABBGGRR layout.
+pub fn apply_modifier(base: [i32; 3], table_idx: u32, negative: bool, large: bool) -> u32 {
+    let base_delta = (TABLES[table_idx as usize][large as usize]) as i32;
+    let modifier_delta = if negative { -base_delta } else { base_delta };
+
+    let mut output_rgba: u32 = 0xFF000000; // Set alpha channel to 1.0
+
+    for i in 0..3 {
+        let channel_color = (base[i] + modifier_delta).clamp(0, 255) as u32;
+        // Pack the R,G,B values into a single 32-bit
+        let shift = i * 8;
+        output_rgba |= channel_color << shift;
+    }
+    return output_rgba;
+}
+
+pub fn decode_etc1_block(input_etc1: u64) -> [[u32; 4]; 4] {
+    let metadata = parse_block_metadata(input_etc1);
+
+    // The output layout is
+    // [[ a e i m ],
+    //  [ b f j n ],
+    //  [ c g k o ],
+    //  [ d h l p ]].
+    let mut output = [[0 as u32; 4]; 4];
+
+    for col in 0..4 {
+        for row in 0..4 {
+            // When flip bit = 0, the block is divided into two 2×4 subblocks.
+            // - Pixels a,b,c,d, e,f,g,h (columns 0, 1) use base color 1.
+            // - Pixels i,j,k,l, m,n,o,p (columns 2, 3) use base color 2.
+            //
+            // When flip bit = 1, the block is divided into two 4×2 subblocks.
+            // - Pixels a,e,i,m, b,f,j,n (rows 0, 1) use base color 1.
+            // - Pixels c,g,k,o, d,h,l,p (rows 2, 3) use base color 2.
+
+            let left_half = col <= 1;
+            let top_half = row <= 1;
+
+            let use_base_color_1 = if metadata.flip { top_half } else { left_half };
+
+            // The lower-32 bit contains indices for the color modifiers of all 16 pixels
+            // (a-p).
+            //
+            // Bits 16-31 (the upper 16 bit) decides whether the modifier value is negative
+            // or not. Bit 16 corresponds to pixel 'a', ..., Bit 31 to pixel 'p'
+            //
+            // Bits 0-15 (the lower 16 bit) decides whether the modifier value is large or
+            // not. Bit 0 corresponds to pixel 'a', ..., Bit 15 to pixel 'p'
+
+            // The modifier table provides pairs of [small, large] for each table index.
+            // The two bits extracted for a pixel select one of four possible deltas.
+            // For example, TABLES[2] is [9,29]
+            // - negative = 1, large = 1 : -large (ex. -29)
+            // - negative = 1, large = 0 : -small (ex. -9)
+            // - negative = 0, large = 0 : +small (ex. 9)
+            // - negative = 0, large = 1 : +large (ex. 29)
+
+            let shift = row + col * 4;
+            let large = (input_etc1 >> shift) & 0b1 == 1;
+            let negative = (input_etc1 >> shift + 16) & 0b1 == 1;
+            // TODO: base[0] actually means base color 1, and base[1] means base color 2.
+            //       Refactor this section for clarity.
+            output[row][col] = if use_base_color_1 {
+                apply_modifier(metadata.base[0], metadata.table_idx_1, negative, large)
+            } else {
+                apply_modifier(metadata.base[1], metadata.table_idx_2, negative, large)
+            };
+        }
+    }
+    return output;
+}
diff --git a/ui/android/texture_compressor/decoder_tests.rs b/ui/android/texture_compressor/decoder_tests.rs
index d00bd4a1..a98bfe3 100644
--- a/ui/android/texture_compressor/decoder_tests.rs
+++ b/ui/android/texture_compressor/decoder_tests.rs
@@ -4,6 +4,8 @@
 
 use rust_gtest_interop::expect_eq;
 use rust_gtest_interop::prelude::*;
+use ui_sandroid_ctexture_ucompressor::decoder::apply_modifier;
+use ui_sandroid_ctexture_ucompressor::decoder::decode_etc1_block;
 use ui_sandroid_ctexture_ucompressor::decoder::parse_block_metadata;
 use ui_sandroid_ctexture_ucompressor::decoder::read_delta_bits;
 use ui_sandroid_ctexture_ucompressor::decoder::scale_4bit_to_8bit;
@@ -71,12 +73,12 @@
     expect_eq!(expected, result);
 }
 
-#[gtest(TextureCompressorTest, SignExtend3bitNegative)]
+#[gtest(TextureCompressorTest, ReadDeltaBitsNegative)]
 fn test() {
     expect_eq!(-4, read_delta_bits(0b100));
 }
 
-#[gtest(TextureCompressorTest, SignExtend3bitNonNegative)]
+#[gtest(TextureCompressorTest, ReadDeltaBitsNonNegative)]
 fn test() {
     expect_eq!(0, read_delta_bits(0b000));
     expect_eq!(1, read_delta_bits(0b001));
@@ -91,3 +93,82 @@
 fn test() {
     expect_eq!(0b11100111, scale_5bit_to_8bit(0b11100));
 }
+
+#[gtest(TextureCompressorTest, ApplyModifier)]
+fn test() {
+    // In this tast case, we use the modifier table: [-8, -2, 2, 8](table codeword
+    // is 0b000), and base color: [R, G, B] = [16, 16, 16] Input format is [R,
+    // G, B], and Output format is 0xAABBGGRR.
+    let base = [16, 16, 16];
+    // If negative = true, large = true, then the modifier value is -8. Therefore,
+    // the expected components is [16-8, 16-8, 16-8]
+    expect_eq!(0x_FF_08_08_08, apply_modifier(base, 0b000, true, true));
+
+    // If negative = true, large = false, then the modifier value is -2. Therefore,
+    // the expected components is [16-2, 16-2, 16-2]
+    expect_eq!(0x_FF_0E_0E_0E, apply_modifier(base, 0b000, true, false));
+
+    // If negative = false, large = false, then the modifier value is 2. Therefore,
+    // the expected components is [16+2, 16+2, 16+2]
+    expect_eq!(0x_FF_12_12_12, apply_modifier(base, 0b000, false, false));
+
+    // If negative = false, large = true, then the modifier value is 8. So expected
+    // components is [16+8, 16+8, 16+8]
+    expect_eq!(0x_FF_18_18_18, apply_modifier(base, 0b000, false, true));
+}
+
+#[gtest(TextureCompressorTest, ApplyModifierClampToMax)]
+fn test() {
+    let base = [231, 8, 16];
+    // If negative = false, large = true, and the modifier table [-29, -9, 9, 29]
+    // is used, then the modifier value is +29. So expected components is
+    // [231+29, 8+29, 16+29], resulting in the color[255, 37, 45]
+    expect_eq!(0b_11111111_00101101_00100101_11111111, apply_modifier(base, 0b010, false, true));
+}
+
+#[gtest(TextureCompressorTest, ApplyModifierClampToMin)]
+fn test() {
+    let base = [231, 8, 16];
+    // If negative = true, large = true, and the modifier table [-29, -9, 9, 29] is
+    // used, then the modifier value is -29. So expected components is [231-29,
+    // 8-29, 16-29], resulting in the color[202, 0, 0]
+    expect_eq!(0b_11111111_00000000_00000000_11001010, apply_modifier(base, 0b010, true, true));
+}
+
+#[gtest(TextureCompressorTest, DecodeETC1BlockFlipFalse)]
+fn test() {
+    // If flip is false, the block is divided into two 2x4 subblocks side-by-side.
+    // basecolor1 fills the left one, and basecolor2 fills the right one.
+    // Input (upper 32 bits): 0b_RRRR_RRRR_GGGG_GGGG_BBBB_BBBB_TTT_TTT_D_F
+    // basecolor_1: FF0000FF
+    // basecolor_2: 0000FFFF
+    // offset: 2 (table codeword = 0, pixel index value = 00)
+    // Note: Output format is 0xAABBGGRR.
+    let input = 0b_1111_0000_0000_0000_0000_1111_000_000_0_0 << 32;
+
+    let expected = [
+        [0xff0202ff, 0xff0202ff, 0xffff0202, 0xffff0202],
+        [0xff0202ff, 0xff0202ff, 0xffff0202, 0xffff0202],
+        [0xff0202ff, 0xff0202ff, 0xffff0202, 0xffff0202],
+        [0xff0202ff, 0xff0202ff, 0xffff0202, 0xffff0202],
+    ];
+
+    expect_eq!(expected, decode_etc1_block(input));
+}
+
+#[gtest(TextureCompressorTest, DecodeETC1BlockFlipTrue)]
+fn test() {
+    // If flip is true, the block is divided into two 4x2 subblocks on top of each
+    // other. basecolor1 fills the top one, and basecolor2 fills the bottom one.
+    // `input` is same as above.
+    let input = 0b_1111_0000_0000_0000_0000_1111_000_000_0_1 << 32;
+
+    let expected = [
+        [0xff0202ff, 0xff0202ff, 0xff0202ff, 0xff0202ff],
+        [0xff0202ff, 0xff0202ff, 0xff0202ff, 0xff0202ff],
+        [0xffff0202, 0xffff0202, 0xffff0202, 0xffff0202],
+        [0xffff0202, 0xffff0202, 0xffff0202, 0xffff0202],
+    ];
+
+    expect_eq!(expected, decode_etc1_block(input));
+}
diff --git a/ui/android/texture_compressor/lib.rs b/ui/android/texture_compressor/lib.rs
index 53a3a44..60838341 100644
--- a/ui/android/texture_compressor/lib.rs
+++ b/ui/android/texture_compressor/lib.rs
@@ -15,7 +15,9 @@
 use std::simd::Simd;
 
 use bytemuck::cast_slice;
+use bytemuck::cast_slice_mut;
 
+use crate::decoder::decode_etc1_block;
 use crate::dither::dither;
 use crate::quant::{quantize_averages, QuantResult};
 use crate::selectors::search_table_and_selectors;
@@ -199,26 +201,48 @@
 ///   pixels out of bounds will be discarded. The number is truncated.
 /// - `src_row_width` should be the width of ETC1 image `dst_row_width` should
 ///   be the width of RGBA image
-///
-///
-/// This is a stub.
-/// TODO: b/393495436 - Implement ETC1 decoding logic.
 pub fn decompress_etc1(
-    _src: &[u8],
+    src: &[u8],
     dst: &mut [u32],
     dst_width: u32,
     dst_height: u32,
-    _src_row_width: u32,
+    src_row_width: u32,
     dst_row_width: u32,
 ) {
-    for y in 0..dst_height {
-        for x in 0..dst_width {
-            let r = (x % 256) as u32;
-            let b = (y % 256) as u32;
-            let pixel_value: u32 = 0xFF000000 // Alpha: 0xFF
-                    | ((r & 0xFF) << 16) // Red
-                    |  (b & 0xFF); // Blue
-            dst[(y * dst_row_width + x) as usize] = pixel_value;
+    // We access 'src' as array of u64s, but 'src' is not always aligned to 8-byte
+    // because of constrains at the callsite.(b/464139989) To solve the
+    // alignment issue, we copy the data from `src` into a temporary buffer that
+    // is guaranteed to be 8-byte aligned. To balance between copying overhead
+    // and memory overhead, we copy one row at a time.
+
+    let mut staging_row_u64 = vec![0u64; src_row_width as usize];
+    let bytes_per_row = src_row_width as usize * ETC1_BLOCK_BYTES;
+    for y in (0..dst_height).step_by(4) {
+        let src_y = (y / 4) as usize;
+        let copy_start_idx = src_y * bytes_per_row;
+        let copy_end_idx = (src_y + 1) * bytes_per_row;
+        let staging_row_bytes: &mut [u8] = cast_slice_mut(&mut staging_row_u64);
+        staging_row_bytes[..bytes_per_row].copy_from_slice(&src[copy_start_idx..copy_end_idx]);
+
+        for x in (0..dst_width).step_by(4) {
+            // The ETC1 specification ("Khronos Data Format Specification v1.1 rev 9")
+            // defines the 64-bit block data as big endian.
+            let src_x = (x / 4) as usize;
+            let output_rgba_block =
+                decode_etc1_block(u64::from_be(staging_row_u64[src_x as usize]));
+            for y_in_block in 0..4 {
+                for x_in_block in 0..4 {
+                    let dst_x = x + x_in_block;
+                    let dst_y = y + y_in_block;
+
+                    if dst_y < dst_height && dst_x < dst_width {
+                        let dst_idx = dst_y * dst_row_width + dst_x;
+
+                        dst[dst_idx as usize] =
+                            output_rgba_block[y_in_block as usize][x_in_block as usize];
+                    }
+                }
+            }
         }
     }
 }
diff --git a/ui/compositor/layer.cc b/ui/compositor/layer.cc
index f688256..bb316fd 100644
--- a/ui/compositor/layer.cc
+++ b/ui/compositor/layer.cc
@@ -11,7 +11,6 @@
 #include <sstream>
 #include <utility>
 
-#include "base/check_deref.h"
 #include "base/check_op.h"
 #include "base/command_line.h"
 #include "base/containers/contains.h"
@@ -247,9 +246,6 @@
   if (content_layer_)
     content_layer_->ClearClient();
   cc_layer_->RemoveFromParent();
-#if BUILDFLAG(IS_CHROMEOS)
-  cc_layer_->set_is_valid_to_destroy(true);
-#endif
   if (transfer_release_callback_)
     std::move(transfer_release_callback_).Run(gpu::SyncToken(), false);
 
@@ -299,12 +295,7 @@
   } else if (type_ == LAYER_SOLID_COLOR) {
     clone->SetColor(GetTargetColor());
   }
-#if BUILDFLAG(IS_CHROMEOS)
-  // Propagate the destruction check to a cloned layer.
-  if (!cc_layer_->is_valid_to_destroy()) {
-    clone->EnableLayerDestructionCheck();
-  }
-#endif
+
   clone->SetTransform(GetTargetTransform());
   clone->SetBounds(bounds_);
   if (subpixel_position_offset_->has_explicit_subpixel_offset())
@@ -550,7 +541,7 @@
 }
 
 void Layer::SetMasksToBounds(bool masks_to_bounds) {
-  CHECK_DEREF(cc_layer_.get()).SetMasksToBounds(masks_to_bounds);
+  cc_layer_->SetMasksToBounds(masks_to_bounds);
 }
 
 bool Layer::GetMasksToBounds() const {
@@ -978,10 +969,6 @@
   new_layer->SetIsFastRoundedCorner(cc_layer_->is_fast_rounded_corner());
   new_layer->SetMasksToBounds(cc_layer_->masks_to_bounds());
   new_layer->SetGradientMask(cc_layer_->gradient_mask());
-#if BUILDFLAG(IS_CHROMEOS)
-  new_layer->set_is_valid_to_destroy(cc_layer_->is_valid_to_destroy());
-  cc_layer_->set_is_valid_to_destroy(true);
-#endif
 
   cc_layer_ = new_layer.get();
   if (content_layer_) {
@@ -1111,13 +1098,6 @@
   return base::Contains(mirrors_, mirror, &LayerMirror::dest);
 }
 
-#if BUILDFLAG(IS_CHROMEOS)
-void Layer::EnableLayerDestructionCheck() {
-  // This should be set to true in the destructor.
-  cc_layer_->set_is_valid_to_destroy(false);
-}
-#endif
-
 void Layer::SetTransferableResource(const viz::TransferableResource& resource,
                                     viz::ReleaseCallback release_callback,
                                     gfx::Size texture_size_in_dip) {
@@ -1881,9 +1861,6 @@
     content_layer_ = cc::PictureLayer::Create(this);
     cc_layer_ = content_layer_.get();
   }
-#if BUILDFLAG(IS_CHROMEOS)
-  cc_layer_->set_is_valid_to_destroy(true);
-#endif
   cc_layer_->SetTransformOrigin(gfx::Point3F());
   cc_layer_->SetIsDrawable(type_ != LAYER_NOT_DRAWN);
   cc_layer_->SetHitTestable(IsHitTestableForCC());
diff --git a/ui/compositor/layer.h b/ui/compositor/layer.h
index 8ddc5bc7..621941df 100644
--- a/ui/compositor/layer.h
+++ b/ui/compositor/layer.h
@@ -620,10 +620,6 @@
     compositor_ = compositor;
   }
 
-#if BUILDFLAG(IS_CHROMEOS)
-  void EnableLayerDestructionCheck();
-#endif
-
  private:
   friend class LayerOwner;
   class LayerMirror;
diff --git a/ui/display/display_features.cc b/ui/display/display_features.cc
index ccb30d6..0872b32 100644
--- a/ui/display/display_features.cc
+++ b/ui/display/display_features.cc
@@ -37,15 +37,6 @@
 BASE_FEATURE(kCtmColorManagement, base::FEATURE_ENABLED_BY_DEFAULT);
 #endif
 
-// This features allows listing all display modes of external displays in the
-// display settings and setting any one of them exactly as requested, which can
-// be very useful for debugging and development purposes.
-BASE_FEATURE(kListAllDisplayModes, base::FEATURE_ENABLED_BY_DEFAULT);
-
-bool IsListAllDisplayModesEnabled() {
-  return base::FeatureList::IsEnabled(kListAllDisplayModes);
-}
-
 // TODO(gildekel): A temporary flag to control whether EDID-based (vs.
 // port-based) display IDs are generated per display. Remove once the migration
 // process it complete (b/193019614).
diff --git a/ui/display/display_features.h b/ui/display/display_features.h
index 85020aac..f5de422e 100644
--- a/ui/display/display_features.h
+++ b/ui/display/display_features.h
@@ -28,10 +28,6 @@
 BASE_DECLARE_FEATURE(kCtmColorManagement);
 #endif
 
-COMPONENT_EXPORT(DISPLAY_FEATURES) BASE_DECLARE_FEATURE(kListAllDisplayModes);
-
-COMPONENT_EXPORT(DISPLAY_FEATURES) bool IsListAllDisplayModesEnabled();
-
 COMPONENT_EXPORT(DISPLAY_FEATURES)
 BASE_DECLARE_FEATURE(kEnableEdidBasedDisplayIds);
 
diff --git a/ui/display/manager/display_change_observer.cc b/ui/display/manager/display_change_observer.cc
index 915fd4a..305e3f8 100644
--- a/ui/display/manager/display_change_observer.cc
+++ b/ui/display/manager/display_change_observer.cc
@@ -139,32 +139,6 @@
   return 1.0f;
 }
 
-// Returns a list of display modes for the given |output| that doesn't exclude
-// any mode. The returned list is sorted by size, then by refresh rate, then by
-// is_interlaced.
-ManagedDisplayInfo::ManagedDisplayModeList GetModeListWithAllRefreshRates(
-    const DisplaySnapshot& output) {
-  ManagedDisplayInfo::ManagedDisplayModeList display_mode_list;
-  for (const auto& mode_info : output.modes()) {
-    display_mode_list.emplace_back(
-        mode_info->size(), mode_info->refresh_rate(),
-        mode_info->is_interlaced(), output.native_mode() == mode_info.get(),
-        GetExternalDisplayScaleFactor(output.physical_size(),
-                                      mode_info->size()));
-  }
-
-  std::sort(
-      display_mode_list.begin(), display_mode_list.end(),
-      [](const ManagedDisplayMode& lhs, const ManagedDisplayMode& rhs) {
-        return std::forward_as_tuple(lhs.size().width(), lhs.size().height(),
-                                     lhs.refresh_rate(), lhs.is_interlaced()) <
-               std::forward_as_tuple(rhs.size().width(), rhs.size().height(),
-                                     rhs.refresh_rate(), rhs.is_interlaced());
-      });
-
-  return display_mode_list;
-}
-
 std::optional<gfx::RoundedCornersF> ParsePanelRadiiFromCommandLine() {
   if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
           switches::kDisplayProperties)) {
@@ -202,69 +176,26 @@
 ManagedDisplayInfo::ManagedDisplayModeList
 DisplayChangeObserver::GetExternalManagedDisplayModeList(
     const DisplaySnapshot& output) {
-  if (display::features::IsListAllDisplayModesEnabled())
-    return GetModeListWithAllRefreshRates(output);
-
-  struct SizeComparator {
-    constexpr bool operator()(const gfx::Size& lhs,
-                              const gfx::Size& rhs) const {
-      return std::forward_as_tuple(lhs.width(), lhs.height()) <
-             std::forward_as_tuple(rhs.width(), rhs.height());
-    }
-  };
-
-  using DisplayModeMap =
-      std::map<gfx::Size, ManagedDisplayMode, SizeComparator>;
-  DisplayModeMap display_mode_map;
-
-  ManagedDisplayMode native_mode;
+  // Returns a list of display modes for the given |output| that doesn't exclude
+  // any mode. The returned list is sorted by size, then by refresh rate, then
+  // by is_interlaced.
+  ManagedDisplayInfo::ManagedDisplayModeList display_mode_list;
   for (const auto& mode_info : output.modes()) {
-    const gfx::Size size = mode_info->size();
-
-    ManagedDisplayMode display_mode(
+    display_mode_list.emplace_back(
         mode_info->size(), mode_info->refresh_rate(),
         mode_info->is_interlaced(), output.native_mode() == mode_info.get(),
         GetExternalDisplayScaleFactor(output.physical_size(),
                                       mode_info->size()));
-    if (display_mode.native())
-      native_mode = display_mode;
-
-    // Add the display mode if it isn't already present and override interlaced
-    // display modes with non-interlaced ones. We prioritize having non
-    // interlaced mode over refresh rate. A mode having lower refresh rate
-    // but is not interlaced will be picked over a mode having high refresh
-    // rate but is interlaced.
-    auto display_mode_it = display_mode_map.find(size);
-    if (display_mode_it == display_mode_map.end()) {
-      display_mode_map.emplace(size, display_mode);
-    } else if (display_mode_it->second.is_interlaced() &&
-               !display_mode.is_interlaced()) {
-      display_mode_it->second = std::move(display_mode);
-    } else if (!display_mode.is_interlaced() &&
-               display_mode_it->second.refresh_rate() <
-                   display_mode.refresh_rate()) {
-      display_mode_it->second = std::move(display_mode);
-    }
   }
 
-  if (output.native_mode()) {
-    const gfx::Size size = native_mode.size();
-
-    auto it = display_mode_map.find(size);
-    CHECK(it != display_mode_map.end())
-        << "Native mode must be part of the mode list.";
-
-    // If the native mode was replaced (e.g. by a mode with similar size but
-    // higher refresh rate), we overwrite that mode with the native mode. The
-    // native mode will always be chosen as the best mode for this size (see
-    // DisplayConfigurator::FindDisplayModeMatchingSize()).
-    if (!it->second.native())
-      it->second = native_mode;
-  }
-
-  ManagedDisplayInfo::ManagedDisplayModeList display_mode_list;
-  for (const auto& display_mode_pair : display_mode_map)
-    display_mode_list.push_back(std::move(display_mode_pair.second));
+  std::sort(
+      display_mode_list.begin(), display_mode_list.end(),
+      [](const ManagedDisplayMode& lhs, const ManagedDisplayMode& rhs) {
+        return std::forward_as_tuple(lhs.size().width(), lhs.size().height(),
+                                     lhs.refresh_rate(), lhs.is_interlaced()) <
+               std::forward_as_tuple(rhs.size().width(), rhs.size().height(),
+                                     rhs.refresh_rate(), rhs.is_interlaced());
+      });
 
   return display_mode_list;
 }
diff --git a/ui/display/manager/display_change_observer.h b/ui/display/manager/display_change_observer.h
index d7ad440..78775920 100644
--- a/ui/display/manager/display_change_observer.h
+++ b/ui/display/manager/display_change_observer.h
@@ -79,7 +79,7 @@
       const gfx::RoundedCornersF& panel_radii = gfx::RoundedCornersF());
 
  private:
-  friend class DisplayChangeObserverTestBase;
+  friend class DisplayChangeObserverTest;
 
   void UpdateInternalDisplay(
       const DisplayConfigurator::DisplayStateList& display_states);
diff --git a/ui/display/manager/display_change_observer_unittest.cc b/ui/display/manager/display_change_observer_unittest.cc
index 7f20a7f9..e607a98 100644
--- a/ui/display/manager/display_change_observer_unittest.cc
+++ b/ui/display/manager/display_change_observer_unittest.cc
@@ -66,29 +66,7 @@
 
 }  // namespace
 
-class DisplayChangeObserverTestBase : public testing::Test {
- public:
-  DisplayChangeObserverTestBase() = default;
-
-  DisplayChangeObserverTestBase(const DisplayChangeObserverTestBase&) = delete;
-  DisplayChangeObserverTestBase& operator=(
-      const DisplayChangeObserverTestBase&) = delete;
-
-  ~DisplayChangeObserverTestBase() override = default;
-
-  // Pass through method to be called by individual test cases.
-  ManagedDisplayInfo CreateManagedDisplayInfo(DisplayChangeObserver* observer,
-                                              const DisplaySnapshot* snapshot,
-                                              const DisplayMode* mode_info) {
-    return observer->CreateManagedDisplayInfoInternal(snapshot, mode_info);
-  }
-
- protected:
-  base::test::ScopedFeatureList scoped_feature_list_;
-};
-
-class DisplayChangeObserverTest : public DisplayChangeObserverTestBase,
-                                  public testing::WithParamInterface<bool> {
+class DisplayChangeObserverTest : public testing::Test {
  public:
   DisplayChangeObserverTest() = default;
 
@@ -98,21 +76,15 @@
 
   ~DisplayChangeObserverTest() override = default;
 
-  // DisplayChangeObserverTestBase:
-  void SetUp() override {
-    if (GetParam()) {
-      scoped_feature_list_.InitAndEnableFeature(features::kListAllDisplayModes);
-    } else {
-      scoped_feature_list_.InitAndDisableFeature(
-          features::kListAllDisplayModes);
-    }
-
-    DisplayChangeObserverTestBase::SetUp();
+  // Pass through method to be called by individual test cases.
+  ManagedDisplayInfo CreateManagedDisplayInfo(DisplayChangeObserver* observer,
+                                              const DisplaySnapshot* snapshot,
+                                              const DisplayMode* mode_info) {
+    return observer->CreateManagedDisplayInfoInternal(snapshot, mode_info);
   }
 };
 
-class DisplayChangeObserverPanelRadiiTest
-    : public DisplayChangeObserverTestBase {
+class DisplayChangeObserverPanelRadiiTest : public DisplayChangeObserverTest {
  public:
   DisplayChangeObserverPanelRadiiTest() = default;
 
@@ -129,7 +101,7 @@
     default_display_mode_ = MakeDisplayMode(1920, 1080, true, 60);
 
     ui::DeviceDataManager::CreateInstance();
-    DisplayChangeObserverTestBase::SetUp();
+    DisplayChangeObserverTest::SetUp();
   }
 
   void InitializeDisplayChangeObserver() {
@@ -192,7 +164,7 @@
   EXPECT_TRUE(display_info.panel_corners_radii().IsEmpty());
 }
 
-TEST_P(DisplayChangeObserverTest, GetExternalManagedDisplayModeList) {
+TEST_F(DisplayChangeObserverTest, GetExternalManagedDisplayModeList) {
   std::unique_ptr<DisplaySnapshot> display_snapshot =
       FakeDisplaySnapshot::Builder()
           .SetId(123)
@@ -221,83 +193,55 @@
       DisplayChangeObserver::GetExternalManagedDisplayModeList(
           *display_snapshot);
 
-  const bool listing_all_modes = GetParam();
-  if (listing_all_modes) {
-    ASSERT_EQ(13u, display_modes.size());
-    EXPECT_EQ(gfx::Size(640, 480), display_modes[0].size());
-    EXPECT_TRUE(display_modes[0].is_interlaced());
-    EXPECT_EQ(display_modes[0].refresh_rate(), 60);
+  ASSERT_EQ(13u, display_modes.size());
+  EXPECT_EQ(gfx::Size(640, 480), display_modes[0].size());
+  EXPECT_TRUE(display_modes[0].is_interlaced());
+  EXPECT_EQ(display_modes[0].refresh_rate(), 60);
 
-    EXPECT_EQ(gfx::Size(1024, 600), display_modes[1].size());
-    EXPECT_FALSE(display_modes[1].is_interlaced());
-    EXPECT_EQ(display_modes[1].refresh_rate(), 60);
-    EXPECT_EQ(gfx::Size(1024, 600), display_modes[2].size());
-    EXPECT_TRUE(display_modes[2].is_interlaced());
-    EXPECT_EQ(display_modes[2].refresh_rate(), 60);
-    EXPECT_EQ(gfx::Size(1024, 600), display_modes[3].size());
-    EXPECT_FALSE(display_modes[3].is_interlaced());
-    EXPECT_EQ(display_modes[3].refresh_rate(), 70);
+  EXPECT_EQ(gfx::Size(1024, 600), display_modes[1].size());
+  EXPECT_FALSE(display_modes[1].is_interlaced());
+  EXPECT_EQ(display_modes[1].refresh_rate(), 60);
+  EXPECT_EQ(gfx::Size(1024, 600), display_modes[2].size());
+  EXPECT_TRUE(display_modes[2].is_interlaced());
+  EXPECT_EQ(display_modes[2].refresh_rate(), 60);
+  EXPECT_EQ(gfx::Size(1024, 600), display_modes[3].size());
+  EXPECT_FALSE(display_modes[3].is_interlaced());
+  EXPECT_EQ(display_modes[3].refresh_rate(), 70);
 
-    EXPECT_EQ(gfx::Size(1024, 768), display_modes[4].size());
-    EXPECT_TRUE(display_modes[4].is_interlaced());
-    EXPECT_EQ(display_modes[4].refresh_rate(), 60);
-    EXPECT_EQ(gfx::Size(1024, 768), display_modes[5].size());
-    EXPECT_TRUE(display_modes[5].is_interlaced());
-    EXPECT_EQ(display_modes[5].refresh_rate(), 70);
+  EXPECT_EQ(gfx::Size(1024, 768), display_modes[4].size());
+  EXPECT_TRUE(display_modes[4].is_interlaced());
+  EXPECT_EQ(display_modes[4].refresh_rate(), 60);
+  EXPECT_EQ(gfx::Size(1024, 768), display_modes[5].size());
+  EXPECT_TRUE(display_modes[5].is_interlaced());
+  EXPECT_EQ(display_modes[5].refresh_rate(), 70);
 
-    EXPECT_EQ(gfx::Size(1280, 720), display_modes[6].size());
-    EXPECT_FALSE(display_modes[6].is_interlaced());
-    EXPECT_EQ(display_modes[6].refresh_rate(), 60);
-    EXPECT_EQ(gfx::Size(1280, 720), display_modes[7].size());
-    EXPECT_TRUE(display_modes[7].is_interlaced());
-    EXPECT_EQ(display_modes[7].refresh_rate(), 60);
+  EXPECT_EQ(gfx::Size(1280, 720), display_modes[6].size());
+  EXPECT_FALSE(display_modes[6].is_interlaced());
+  EXPECT_EQ(display_modes[6].refresh_rate(), 60);
+  EXPECT_EQ(gfx::Size(1280, 720), display_modes[7].size());
+  EXPECT_TRUE(display_modes[7].is_interlaced());
+  EXPECT_EQ(display_modes[7].refresh_rate(), 60);
 
-    EXPECT_EQ(gfx::Size(1920, 1080), display_modes[8].size());
-    EXPECT_FALSE(display_modes[8].is_interlaced());
-    EXPECT_EQ(display_modes[8].refresh_rate(), 60);
-    EXPECT_EQ(gfx::Size(1920, 1080), display_modes[9].size());
-    EXPECT_FALSE(display_modes[9].is_interlaced());
-    EXPECT_EQ(display_modes[9].refresh_rate(), 70);
-    EXPECT_EQ(gfx::Size(1920, 1080), display_modes[10].size());
-    EXPECT_FALSE(display_modes[10].is_interlaced());
-    EXPECT_EQ(display_modes[10].refresh_rate(), 80);
+  EXPECT_EQ(gfx::Size(1920, 1080), display_modes[8].size());
+  EXPECT_FALSE(display_modes[8].is_interlaced());
+  EXPECT_EQ(display_modes[8].refresh_rate(), 60);
+  EXPECT_EQ(gfx::Size(1920, 1080), display_modes[9].size());
+  EXPECT_FALSE(display_modes[9].is_interlaced());
+  EXPECT_EQ(display_modes[9].refresh_rate(), 70);
+  EXPECT_EQ(gfx::Size(1920, 1080), display_modes[10].size());
+  EXPECT_FALSE(display_modes[10].is_interlaced());
+  EXPECT_EQ(display_modes[10].refresh_rate(), 80);
 
-    EXPECT_EQ(gfx::Size(1920, 1200), display_modes[11].size());
-    EXPECT_FALSE(display_modes[11].is_interlaced());
-    EXPECT_EQ(display_modes[11].refresh_rate(), 60);
+  EXPECT_EQ(gfx::Size(1920, 1200), display_modes[11].size());
+  EXPECT_FALSE(display_modes[11].is_interlaced());
+  EXPECT_EQ(display_modes[11].refresh_rate(), 60);
 
-    EXPECT_EQ(gfx::Size(1920, 1200), display_modes[12].size());
-    EXPECT_FALSE(display_modes[12].is_interlaced());
-    EXPECT_EQ(display_modes[12].refresh_rate(), 75);
-  } else {
-    ASSERT_EQ(6u, display_modes.size());
-    EXPECT_EQ(gfx::Size(640, 480), display_modes[0].size());
-    EXPECT_TRUE(display_modes[0].is_interlaced());
-    EXPECT_EQ(display_modes[0].refresh_rate(), 60);
-
-    EXPECT_EQ(gfx::Size(1024, 600), display_modes[1].size());
-    EXPECT_FALSE(display_modes[1].is_interlaced());
-    EXPECT_EQ(display_modes[1].refresh_rate(), 70);
-
-    EXPECT_EQ(gfx::Size(1024, 768), display_modes[2].size());
-    EXPECT_TRUE(display_modes[2].is_interlaced());
-    EXPECT_EQ(display_modes[2].refresh_rate(), 70);
-
-    EXPECT_EQ(gfx::Size(1280, 720), display_modes[3].size());
-    EXPECT_FALSE(display_modes[3].is_interlaced());
-    EXPECT_EQ(display_modes[3].refresh_rate(), 60);
-
-    EXPECT_EQ(gfx::Size(1920, 1080), display_modes[4].size());
-    EXPECT_FALSE(display_modes[4].is_interlaced());
-    EXPECT_EQ(display_modes[4].refresh_rate(), 80);
-
-    EXPECT_EQ(gfx::Size(1920, 1200), display_modes[5].size());
-    EXPECT_FALSE(display_modes[5].is_interlaced());
-    EXPECT_EQ(display_modes[5].refresh_rate(), 60);
-  }
+  EXPECT_EQ(gfx::Size(1920, 1200), display_modes[12].size());
+  EXPECT_FALSE(display_modes[12].is_interlaced());
+  EXPECT_EQ(display_modes[12].refresh_rate(), 75);
 }
 
-TEST_P(DisplayChangeObserverTest, GetEmptyExternalManagedDisplayModeList) {
+TEST_F(DisplayChangeObserverTest, GetEmptyExternalManagedDisplayModeList) {
   DisplaySnapshot::ColorInfo color_info;
   FakeDisplaySnapshot display_snapshot(
       /*display_id=*/123, /*port_display_id=*/123, /*edid_display_id=*/456,
@@ -374,7 +318,7 @@
   }
 }
 
-TEST_P(DisplayChangeObserverTest, FindDeviceScaleFactor) {
+TEST_F(DisplayChangeObserverTest, FindDeviceScaleFactor) {
   // Validation check
   EXPECT_EQ(1.25f,
             DisplayChangeObserver::FindDeviceScaleFactor(150, gfx::Size()));
@@ -413,7 +357,7 @@
                                   10000.0f, gfx::Size()));
 }
 
-TEST_P(DisplayChangeObserverTest, FindOledDeviceScaleFactor) {
+TEST_F(DisplayChangeObserverTest, FindOledDeviceScaleFactor) {
   // This test is the same as the `FindDeviceScaleFactor` test but with
   // kOledScaleFactorEnabled set to true.
   base::test::ScopedFeatureList feature_list_;
@@ -439,7 +383,7 @@
   }
 }
 
-TEST_P(DisplayChangeObserverTest,
+TEST_F(DisplayChangeObserverTest,
        FindExternalDisplayNativeModeWhenOverwritten) {
   std::unique_ptr<DisplaySnapshot> display_snapshot =
       FakeDisplaySnapshot::Builder()
@@ -452,30 +396,19 @@
       DisplayChangeObserver::GetExternalManagedDisplayModeList(
           *display_snapshot);
 
-  const bool listing_all_modes = GetParam();
+  ASSERT_EQ(2u, display_modes.size());
+  EXPECT_EQ(gfx::Size(1920, 1080), display_modes[0].size());
+  EXPECT_FALSE(display_modes[0].is_interlaced());
+  EXPECT_FALSE(display_modes[0].native());
+  EXPECT_EQ(display_modes[0].refresh_rate(), 60);
 
-  if (listing_all_modes) {
-    ASSERT_EQ(2u, display_modes.size());
-    EXPECT_EQ(gfx::Size(1920, 1080), display_modes[0].size());
-    EXPECT_FALSE(display_modes[0].is_interlaced());
-    EXPECT_FALSE(display_modes[0].native());
-    EXPECT_EQ(display_modes[0].refresh_rate(), 60);
-
-    EXPECT_EQ(gfx::Size(1920, 1080), display_modes[1].size());
-    EXPECT_TRUE(display_modes[1].is_interlaced());
-    EXPECT_TRUE(display_modes[1].native());
-    EXPECT_EQ(display_modes[1].refresh_rate(), 60);
-  } else {
-    // Only the native mode will be listed.
-    ASSERT_EQ(1u, display_modes.size());
-    EXPECT_EQ(gfx::Size(1920, 1080), display_modes[0].size());
-    EXPECT_TRUE(display_modes[0].is_interlaced());
-    EXPECT_TRUE(display_modes[0].native());
-    EXPECT_EQ(display_modes[0].refresh_rate(), 60);
-  }
+  EXPECT_EQ(gfx::Size(1920, 1080), display_modes[1].size());
+  EXPECT_TRUE(display_modes[1].is_interlaced());
+  EXPECT_TRUE(display_modes[1].native());
+  EXPECT_EQ(display_modes[1].refresh_rate(), 60);
 }
 
-TEST_P(DisplayChangeObserverTest, InvalidDisplayColorSpaces) {
+TEST_F(DisplayChangeObserverTest, InvalidDisplayColorSpaces) {
   const std::unique_ptr<DisplaySnapshot> display_snapshot =
       FakeDisplaySnapshot::Builder()
           .SetId(123)
@@ -506,7 +439,7 @@
   EXPECT_EQ(color_space, gfx::ColorSpace::CreateSRGB());
 }
 
-TEST_P(DisplayChangeObserverTest, SDRDisplayColorSpaces) {
+TEST_F(DisplayChangeObserverTest, SDRDisplayColorSpaces) {
   const std::unique_ptr<DisplaySnapshot> display_snapshot =
       FakeDisplaySnapshot::Builder()
           .SetId(123)
@@ -538,7 +471,7 @@
   EXPECT_EQ(color_space.GetTransferID(), gfx::ColorSpace::TransferID::SRGB);
 }
 
-TEST_P(DisplayChangeObserverTest, WCGDisplayColorSpaces) {
+TEST_F(DisplayChangeObserverTest, WCGDisplayColorSpaces) {
   const std::unique_ptr<DisplaySnapshot> display_snapshot =
       FakeDisplaySnapshot::Builder()
           .SetId(123)
@@ -570,7 +503,7 @@
   EXPECT_EQ(color_space.GetTransferID(), gfx::ColorSpace::TransferID::SRGB);
 }
 
-TEST_P(DisplayChangeObserverTest, HDRDisplayColorSpaces) {
+TEST_F(DisplayChangeObserverTest, HDRDisplayColorSpaces) {
   // TODO(crbug.com/40652358): Remove this flag and provision when HDR is fully
   // supported on ChromeOS.
   base::test::ScopedFeatureList scoped_feature_list;
@@ -624,7 +557,7 @@
             display_color_spaces.GetHDRMaxLuminanceRelative());
 }
 
-TEST_P(DisplayChangeObserverTest, VSyncRateMin) {
+TEST_F(DisplayChangeObserverTest, VSyncRateMin) {
   ui::DeviceDataManager::CreateInstance();
   DisplayManager manager(nullptr);
   DisplayChangeObserver observer(&manager);
@@ -664,7 +597,7 @@
   }
 }
 
-TEST_P(DisplayChangeObserverTest, DisplayModeNativeCalculation) {
+TEST_F(DisplayChangeObserverTest, DisplayModeNativeCalculation) {
   ui::DeviceDataManager::CreateInstance();
   DisplayManager manager(nullptr);
   DisplayChangeObserver observer(&manager);
@@ -724,7 +657,7 @@
   }
 }
 
-TEST_P(DisplayChangeObserverTest, OPSDisplayScaleFactor) {
+TEST_F(DisplayChangeObserverTest, OPSDisplayScaleFactor) {
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitAndEnableFeature(features::kOpsDisplayScaleFactor);
   // Since the only way to set the physical size of FakeDisplaySnapshot is to
@@ -767,10 +700,6 @@
   }
 }
 
-INSTANTIATE_TEST_SUITE_P(All,
-                         DisplayChangeObserverTest,
-                         ::testing::Values(false, true));
-
 using DisplayResolutionTest = testing::Test;
 
 auto CreateDisplay = [](const ManagedDisplayInfo& managed_display_info) {
diff --git a/ui/display/manager/display_configurator.cc b/ui/display/manager/display_configurator.cc
index 552a00b..c655cdb 100644
--- a/ui/display/manager/display_configurator.cc
+++ b/ui/display/manager/display_configurator.cc
@@ -368,17 +368,10 @@
     ManagedDisplayMode mode;
     const bool mode_found = state_controller->GetSelectedModeForDisplayId(
         display.display_id(), &mode);
-    if (display::features::IsListAllDisplayModesEnabled()) {
-      // When selecting any arbitrary display mode is enabled, we don't try to
-      // be smart about finding the best mode matching the user-selected display
-      // size, rather we find an exact match to the selected display mode.
-      selected_mode =
-          mode_found ? FindExactMatchingMode(display, mode) : nullptr;
-    } else {
-      selected_mode = mode_found
-                          ? FindDisplayModeMatchingSize(display, mode.size())
-                          : nullptr;
-    }
+    // When selecting any arbitrary display mode is enabled, we don't try to
+    // be smart about finding the best mode matching the user-selected display
+    // size, rather we find an exact match to the selected display mode.
+    selected_mode = mode_found ? FindExactMatchingMode(display, mode) : nullptr;
   }
 
   // Fall back to native mode.
diff --git a/ui/display/manager/display_manager.cc b/ui/display/manager/display_manager.cc
index 8d1c9dc..6fafb41 100644
--- a/ui/display/manager/display_manager.cc
+++ b/ui/display/manager/display_manager.cc
@@ -808,15 +808,13 @@
         display_property_changed = true;
       }
 
-      if (features::IsListAllDisplayModesEnabled()) {
-        if (info.refresh_rate() != display_mode.refresh_rate()) {
-          info.set_refresh_rate(display_mode.refresh_rate());
-          resolution_changed = true;
-        }
-        if (info.is_interlaced() != display_mode.is_interlaced()) {
-          info.set_is_interlaced(display_mode.is_interlaced());
-          resolution_changed = true;
-        }
+      if (info.refresh_rate() != display_mode.refresh_rate()) {
+        info.set_refresh_rate(display_mode.refresh_rate());
+        resolution_changed = true;
+      }
+      if (info.is_interlaced() != display_mode.is_interlaced()) {
+        info.set_is_interlaced(display_mode.is_interlaced());
+        resolution_changed = true;
       }
     }
     display_info_list.emplace_back(info);
diff --git a/ui/display/manager/display_manager.h b/ui/display/manager/display_manager.h
index 8d7e3ff..236ddec0 100644
--- a/ui/display/manager/display_manager.h
+++ b/ui/display/manager/display_manager.h
@@ -289,11 +289,11 @@
   // This is called by ScreenAsh when the primary display is requested, but
   // there is no valid display. It provides a display that
   // - has a non-empty screen rect
-  // - has a valid gfx::BufferFormat
+  // - has a valid format
   // This exists to enable buggy observers assume that the primary display
-  // will always have non-zero size and a valid gfx::BufferFormat. The right
-  // solution to this problem is to fix those observers.
-  // https://crbug.com/866714, https://crbug.com/1057501
+  // will always have non-zero size and a valid format. The right solution to
+  // this problem is to fix those observers. https://crbug.com/866714,
+  // https://crbug.com/1057501
   static const Display& GetFakePrimaryDisplay();
 
   // Returns the logical number of displays. This returns 1 when displays are
diff --git a/ui/display/manager/managed_display_info.cc b/ui/display/manager/managed_display_info.cc
index c9becff8..2ad13641 100644
--- a/ui/display/manager/managed_display_info.cc
+++ b/ui/display/manager/managed_display_info.cc
@@ -159,11 +159,7 @@
 }
 
 bool ManagedDisplayMode::IsEquivalent(const ManagedDisplayMode& other) const {
-  if (display::features::IsListAllDisplayModesEnabled())
-    return *this == other;
-
-  return size_ == other.size_ &&
-         IsWithinEpsilon(device_scale_factor_, other.device_scale_factor_);
+  return *this == other;
 }
 
 std::string ManagedDisplayMode::ToString() const {
diff --git a/ui/gfx/display_color_spaces.cc b/ui/gfx/display_color_spaces.cc
index 76863816..c30b35a 100644
--- a/ui/gfx/display_color_spaces.cc
+++ b/ui/gfx/display_color_spaces.cc
@@ -14,7 +14,6 @@
 
 #include "base/trace_event/traced_value.h"
 #include "build/build_config.h"
-#include "components/viz/common/resources/shared_image_format_utils.h"
 #include "skia/ext/skcolorspace_primaries.h"
 
 namespace gfx {
@@ -27,16 +26,16 @@
     ContentColorUsage::kHDR,
 };
 
-gfx::BufferFormat DefaultBufferFormat() {
-  // ChromeOS expects the default buffer format be BGRA_8888 in several places.
+viz::SharedImageFormat DefaultFormat() {
+  // ChromeOS expects the default format be BGRA_8888 in several places.
   // https://crbug.com/1057501, https://crbug.com/1073237
   // The default format on Mac is BGRA in screen_mac.cc, so we set it here
   // too so that it matches with --ensure-forced-color-profile.
   // https://crbug.com/1478708
 #if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_MAC)
-  return gfx::BufferFormat::BGRA_8888;
+  return viz::SinglePlaneFormat::kBGRA_8888;
 #else
-  return gfx::BufferFormat::RGBA_8888;
+  return viz::SinglePlaneFormat::kRGBA_8888;
 #endif
 }
 
@@ -57,7 +56,7 @@
   // TODO(crbug.com/40219387): Revert back to range-based for loops if possible
   for (size_t i = 0; i < kConfigCount; i++) {
     color_spaces_[i] = gfx::ColorSpace::CreateSRGB();
-    buffer_formats_[i] = DefaultBufferFormat();
+    formats_[i] = DefaultFormat();
   }
 }
 
@@ -79,9 +78,8 @@
 DisplayColorSpaces::DisplayColorSpaces(const ColorSpace& c,
                                        viz::SharedImageFormat f)
     : DisplayColorSpaces(c) {
-  auto buffer_format = viz::SinglePlaneSharedImageFormatToBufferFormat(f);
   for (size_t i = 0; i < kConfigCount; i++) {
-    buffer_formats_[i] = buffer_format;
+    formats_[i] = f;
   }
 }
 
@@ -91,10 +89,8 @@
   for (const auto& color_usage : kAllColorUsages) {
     size_t i_no_alpha = GetIndex(color_usage, false);
     size_t i_needs_alpha = GetIndex(color_usage, true);
-    buffer_formats_[i_no_alpha] =
-        viz::SinglePlaneSharedImageFormatToBufferFormat(format_no_alpha);
-    buffer_formats_[i_needs_alpha] =
-        viz::SinglePlaneSharedImageFormatToBufferFormat(format_with_alpha);
+    formats_[i_no_alpha] = format_no_alpha;
+    formats_[i_needs_alpha] = format_with_alpha;
   }
 }
 
@@ -105,7 +101,7 @@
     viz::SharedImageFormat format) {
   size_t i = GetIndex(color_usage, needs_alpha);
   color_spaces_[i] = color_space;
-  buffer_formats_[i] = viz::SinglePlaneSharedImageFormatToBufferFormat(format);
+  formats_[i] = format;
 }
 
 ColorSpace DisplayColorSpaces::GetOutputColorSpace(
@@ -117,8 +113,7 @@
 viz::SharedImageFormat DisplayColorSpaces::GetOutputFormat(
     ContentColorUsage color_usage,
     bool needs_alpha) const {
-  return viz::GetSharedImageFormat(
-      buffer_formats_[GetIndex(color_usage, needs_alpha)]);
+  return formats_[GetIndex(color_usage, needs_alpha)];
 }
 
 ColorSpace DisplayColorSpaces::GetRasterAndCompositeColorSpace(
@@ -203,15 +198,15 @@
 
   // We don't want to take up 6 lines (one for each config) if we don't need to.
   // To avoid this, build up half-open intervals [i, j) which have the same
-  // color space and buffer formats, and group them together. The above "special
+  // color space and formats, and group them together. The above "special
   // configs" give groups that have a common name.
   size_t i = 0;
   size_t j = 0;
   while (i != kConfigCount) {
     // Keep growing the interval [i, j) until entry j is different, or past the
     // end.
-    if (color_spaces_[i] == color_spaces_[j] &&
-        buffer_formats_[i] == buffer_formats_[j] && j != kConfigCount) {
+    if (color_spaces_[i] == color_spaces_[j] && formats_[i] == formats_[j] &&
+        j != kConfigCount) {
       j += 1;
       continue;
     }
@@ -236,7 +231,7 @@
 
     // Add an entry, and continue with the interval [j, j).
     out_names->push_back(name);
-    out_formats->push_back(viz::GetSharedImageFormat(buffer_formats_[i]));
+    out_formats->push_back(formats_[i]);
     out_color_spaces->push_back(color_spaces_[i]);
     i = j;
   };
@@ -267,8 +262,9 @@
   for (size_t i = 0; i < kConfigCount; ++i) {
     if (color_spaces_[i] != other.color_spaces_[i])
       return false;
-    if (buffer_formats_[i] != other.buffer_formats_[i])
+    if (formats_[i] != other.formats_[i]) {
       return false;
+    }
   }
   if (primaries_ != other.primaries_)
     return false;
diff --git a/ui/gfx/display_color_spaces.h b/ui/gfx/display_color_spaces.h
index 5155428..cbd6e0ab 100644
--- a/ui/gfx/display_color_spaces.h
+++ b/ui/gfx/display_color_spaces.h
@@ -56,9 +56,9 @@
   DisplayColorSpaces& operator=(const DisplayColorSpaces& display_color_space);
 
   // Initialize as |color_space| for all settings. If |color_space| is the
-  // default (invalid) color space, then initialize to sRGB. The BufferFormat
-  // will be set to a default value (BGRA_8888 or RGBA_8888) depending on
-  // build configuration.
+  // default (invalid) color space, then initialize to sRGB. The format will be
+  // set to a default value (BGRA_8888 or RGBA_8888) depending on build
+  // configuration.
   explicit DisplayColorSpaces(const ColorSpace& color_space);
 
   // Initialize as |color_space| and |format| (which must be single-plane) for
@@ -153,7 +153,7 @@
                                    gfx::DisplayColorSpaces>;
 
   gfx::ColorSpace color_spaces_[kConfigCount];
-  gfx::BufferFormat buffer_formats_[kConfigCount];
+  viz::SharedImageFormat formats_[kConfigCount];
   SkColorSpacePrimaries primaries_ = SkNamedPrimariesExt::kSRGB;
   float sdr_max_luminance_nits_ = ColorSpace::kDefaultSDRWhiteLevel;
   float hdr_max_luminance_relative_ = 1.f;
diff --git a/ui/gfx/ipc/color/BUILD.gn b/ui/gfx/ipc/color/BUILD.gn
index 38a0694..0e1d228a 100644
--- a/ui/gfx/ipc/color/BUILD.gn
+++ b/ui/gfx/ipc/color/BUILD.gn
@@ -20,4 +20,5 @@
     "//ui/gfx:color_space",
     "//ui/gfx/ipc/buffer_types",
   ]
+  deps = [ "//components/viz/common/resources:shared_image_format" ]
 }
diff --git a/ui/gfx/mojom/BUILD.gn b/ui/gfx/mojom/BUILD.gn
index 501a47f..0c66be6 100644
--- a/ui/gfx/mojom/BUILD.gn
+++ b/ui/gfx/mojom/BUILD.gn
@@ -43,6 +43,7 @@
     ":hdr_metadata",
     ":native_handle_types",
     "//mojo/public/mojom/base",
+    "//services/viz/public/mojom/compositing:shared_image_format",
     "//skia/public/mojom",
     "//ui/gfx/geometry/mojom",
   ]
@@ -596,6 +597,8 @@
     "//ui/gfx",
   ]
   deps = [
+    "//components/viz/common/resources:shared_image_format",
+    "//services/viz/public/mojom/compositing:shared_image_format",
     "//skia/public/mojom",
     "//ui/base:ozone_buildflags",
   ]
diff --git a/ui/gfx/mojom/DEPS b/ui/gfx/mojom/DEPS
index 4d68c95..b3b6ab2 100644
--- a/ui/gfx/mojom/DEPS
+++ b/ui/gfx/mojom/DEPS
@@ -6,7 +6,8 @@
   "delegated_ink_metadata_mojom_traits\.h" : [
     "+skia/public/mojom/skcolor_mojom_traits.h",
   ],
-  "display_color_spaces_mojom_traits.cc" : [
+  "display_color_spaces_mojom_traits.*" : [
+    "+services/viz/public/cpp/compositing/shared_image_format_mojom_traits.h",
     "+skia/public/mojom/skcolorspace_primaries_mojom_traits.h",
   ],
 }
diff --git a/ui/gfx/mojom/display_color_spaces.mojom b/ui/gfx/mojom/display_color_spaces.mojom
index 00be5ae5..563e569 100644
--- a/ui/gfx/mojom/display_color_spaces.mojom
+++ b/ui/gfx/mojom/display_color_spaces.mojom
@@ -4,8 +4,8 @@
 
 module gfx.mojom;
 
+import "services/viz/public/mojom/compositing/shared_image_format.mojom";
 import "skia/public/mojom/skcolorspace_primaries.mojom";
-import "ui/gfx/mojom/buffer_types.mojom";
 import "ui/gfx/mojom/color_space.mojom";
 import "ui/gfx/mojom/hdr_static_metadata.mojom";
 
@@ -22,7 +22,7 @@
   // cross-product of (SRGB, WCG, HDR) x (opaque, transparent). The order
   // corresponds to the order in gfx::DisplayColorSpaces.
   array<ColorSpace, 6> color_spaces;
-  array<BufferFormat, 6> buffer_formats;
+  array<viz.mojom.SharedImageFormat, 6> formats;
 
   // The primaries of the display.
   skia.mojom.SkColorSpacePrimaries primaries;
diff --git a/ui/gfx/mojom/display_color_spaces_mojom_traits.cc b/ui/gfx/mojom/display_color_spaces_mojom_traits.cc
index ff8e47c..cd24e07 100644
--- a/ui/gfx/mojom/display_color_spaces_mojom_traits.cc
+++ b/ui/gfx/mojom/display_color_spaces_mojom_traits.cc
@@ -5,6 +5,7 @@
 #include "ui/gfx/mojom/display_color_spaces_mojom_traits.h"
 
 #include "base/notreached.h"
+#include "components/viz/common/resources/shared_image_format.h"
 #include "skia/public/mojom/skcolorspace_primaries_mojom_traits.h"
 
 namespace mojo {
@@ -50,10 +51,10 @@
 }
 
 // static
-base::span<const gfx::BufferFormat>
-StructTraits<gfx::mojom::DisplayColorSpacesDataView, gfx::DisplayColorSpaces>::
-    buffer_formats(const gfx::DisplayColorSpaces& input) {
-  return input.buffer_formats_;
+base::span<const viz::SharedImageFormat> StructTraits<
+    gfx::mojom::DisplayColorSpacesDataView,
+    gfx::DisplayColorSpaces>::formats(const gfx::DisplayColorSpaces& input) {
+  return input.formats_;
 }
 
 // static
@@ -61,9 +62,10 @@
     gfx::mojom::DisplayColorSpacesDataView,
     gfx::DisplayColorSpaces>::Read(gfx::mojom::DisplayColorSpacesDataView input,
                                    gfx::DisplayColorSpaces* out) {
-  base::span<gfx::BufferFormat> buffer_formats(out->buffer_formats_);
-  if (!input.ReadBufferFormats(&buffer_formats))
+  base::span<viz::SharedImageFormat> formats(out->formats_);
+  if (!input.ReadFormats(&formats)) {
     return false;
+  }
 
   base::span<gfx::ColorSpace> color_spaces(out->color_spaces_);
   if (!input.ReadColorSpaces(&color_spaces))
diff --git a/ui/gfx/mojom/display_color_spaces_mojom_traits.h b/ui/gfx/mojom/display_color_spaces_mojom_traits.h
index 3b67a3ba..6aa21b4 100644
--- a/ui/gfx/mojom/display_color_spaces_mojom_traits.h
+++ b/ui/gfx/mojom/display_color_spaces_mojom_traits.h
@@ -7,6 +7,7 @@
 
 #include "base/component_export.h"
 #include "base/containers/span.h"
+#include "services/viz/public/cpp/compositing/shared_image_format_mojom_traits.h"
 #include "third_party/skia/include/core/SkColorSpace.h"
 #include "ui/gfx/display_color_spaces.h"
 #include "ui/gfx/mojom/buffer_types_mojom_traits.h"
@@ -30,7 +31,7 @@
                  gfx::DisplayColorSpaces> {
   static base::span<const gfx::ColorSpace> color_spaces(
       const gfx::DisplayColorSpaces& input);
-  static base::span<const gfx::BufferFormat> buffer_formats(
+  static base::span<const viz::SharedImageFormat> formats(
       const gfx::DisplayColorSpaces& input);
   static SkColorSpacePrimaries primaries(const gfx::DisplayColorSpaces& input) {
     return input.GetPrimaries();
diff --git a/ui/views/bubble/bubble_dialog_delegate_view.h b/ui/views/bubble/bubble_dialog_delegate_view.h
index bf15567..74c76950 100644
--- a/ui/views/bubble/bubble_dialog_delegate_view.h
+++ b/ui/views/bubble/bubble_dialog_delegate_view.h
@@ -57,7 +57,6 @@
 class CrostiniUpdateFilesystemView;
 class DiceWebSigninInterceptionBubbleView;
 class ExtensionInstallDialogView;
-class ExtensionInstallFrictionDialogView;
 class ExtensionPopup;
 class ExtensionsMenuView;
 class FlyingIndicator;
@@ -72,7 +71,6 @@
 class PageInfoBubbleViewBase;
 class PermissionPromptBaseView;
 class PluginVmInstallerView;
-class ProfileCustomizationBubbleView;
 class ProfileMenuViewBase;
 class RemoveSuggestionBubbleDialogDelegateView;
 class StoragePressureBubbleView;
@@ -80,7 +78,6 @@
 class TabHoverCardBubbleView;
 class TestBubbleView;
 class ToolbarActionHoverCardBubbleView;
-class ToolbarActionsBarBubbleViews;
 class ScreenshotSurfaceTestDialog;
 class WebBubbleView;
 class WebUIBubbleDialogView;
@@ -807,7 +804,6 @@
   friend class ::CrostiniUpdateFilesystemView;
   friend class ::DiceWebSigninInterceptionBubbleView;
   friend class ::ExtensionInstallDialogView;
-  friend class ::ExtensionInstallFrictionDialogView;
   friend class ::ExtensionPopup;
   friend class ::ExtensionsMenuView;
   friend class ::FlyingIndicator;
@@ -822,7 +818,6 @@
   friend class ::PageInfoBubbleViewBase;
   friend class ::PermissionPromptBaseView;
   friend class ::PluginVmInstallerView;
-  friend class ::ProfileCustomizationBubbleView;
   friend class ::ProfileMenuViewBase;
   friend class ::RemoveSuggestionBubbleDialogDelegateView;
   friend class ::StoragePressureBubbleView;
@@ -830,7 +825,6 @@
   friend class ::TabHoverCardBubbleView;
   friend class ::TestBubbleView;
   friend class ::ToolbarActionHoverCardBubbleView;
-  friend class ::ToolbarActionsBarBubbleViews;
   friend class ::ScreenshotSurfaceTestDialog;
   friend class ::WebBubbleView;
   friend class ::WebUIBubbleDialogView;
diff --git a/ui/webui/resources/cr_components/composebox/composebox.css b/ui/webui/resources/cr_components/composebox/composebox.css
index b0113381..692b2c35 100644
--- a/ui/webui/resources/cr_components/composebox/composebox.css
+++ b/ui/webui/resources/cr_components/composebox/composebox.css
@@ -255,7 +255,7 @@
 }
 
 #input::placeholder {
-  color: var(--color-new-tab-page-common-input-placeholder);
+  color: var(--cr-composebox-input-placeholder-color, var(--color-composebox-type-ahead));
 }
 
 #smartCompose {
diff --git a/ui/webui/resources/cr_components/searchbox/searchbox.css b/ui/webui/resources/cr_components/searchbox/searchbox.css
index b541616..674fdb4 100644
--- a/ui/webui/resources/cr_components/searchbox/searchbox.css
+++ b/ui/webui/resources/cr_components/searchbox/searchbox.css
@@ -250,7 +250,7 @@
  * Webkit mask images hide borders so container rules are created to
  * show focus borders on these icons. */
 .searchbox-icon-button-container {
-  border-radius: 4px;
+  border-radius: 50%;
   display: flex;
   height: 36px;
   position: absolute;
@@ -364,7 +364,7 @@
 
 :host([ntp-realbox-next-enabled]) input::placeholder {
   font-size: 18px;
-  color: var(--color-new-tab-page-common-input-placeholder);
+  color: var(--cr-composebox-input-placeholder-color, var(--color-composebox-type-ahead));
   font-weight: 400;
 }
 
diff --git a/url/url_canon_host.cc b/url/url_canon_host.cc
index 6c99601..4d017ca5 100644
--- a/url/url_canon_host.cc
+++ b/url/url_canon_host.cc
@@ -293,8 +293,7 @@
 
 // Canonicalizes a host that requires IDN conversion. Returns true on success
 template <CanonMode canon_mode>
-bool DoIDNHost(const char16_t* src, size_t src_len, CanonOutput* output) {
-  std::u16string_view host_view(src, src_len);
+bool DoIdnHost(std::u16string_view host_view, CanonOutput* output) {
   int original_output_len = output->length();  // So we can rewind below.
 
   // We need to escape URL before doing IDN conversion, since punicode strings
@@ -406,8 +405,7 @@
 
   // This will call DoSimpleHost which will do normal ASCII canonicalization
   // and also check for IP addresses in the outpt.
-  return DoIDNHost<canon_mode>(utf16.data(), utf16.length(), output) &&
-         are_all_escaped_valid;
+  return DoIdnHost<canon_mode>(utf16.view(), output) && are_all_escaped_valid;
 }
 
 // UTF-16 convert host to its ASCII version. The set up is already ready for
@@ -443,7 +441,7 @@
   // function will only get called if we either have escaped or non-ascii
   // input, so it's safe to just use ICU now. Even if the input is ASCII,
   // this function will do the right thing (just slower than we could).
-  return DoIDNHost<canon_mode>(host.data(), host.length(), output);
+  return DoIdnHost<canon_mode>(host, output);
 }
 
 template <typename CHAR, typename UCHAR, CanonMode canon_mode>