diff --git a/DEPS b/DEPS
index a472af1e..2eab38d 100644
--- a/DEPS
+++ b/DEPS
@@ -291,19 +291,19 @@
   # 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': 'a31d97d94499fb6f4825224003bb2c4e906dc4ff',
+  'src_internal_revision': '8b083b72d94d2705eb8a8a529226eec645ce4c96',
   # 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': 'abc8d1471f7b24158121769fac3e6121e47b32bc',
+  'skia_revision': '6276cc1b23fac891b89cd18ea452cad93a69fc8e',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'v8_revision': 'be3c06167b01bc7cd70edb4e44ec76ce8058390d',
+  'v8_revision': 'd154fab24bf5e8d933132b71bda7b1541998b9dd',
   # 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': '253ceef5eb2e27298a5fcbf36300c88925dd6b8f',
+  'angle_revision': 'e4bfa483a42277d4dbc626a1ef48131fcaf74a73',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -367,7 +367,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling catapult
   # and whatever else without interference from each other.
-  'catapult_revision': 'aec920c93d0ebfb84bad983ff5cf4841e2e9ca5b',
+  'catapult_revision': '5b409767f0b2a05044d7139c3c000773da013775',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling CrossBench
   # and whatever else without interference from each other.
@@ -387,7 +387,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling devtools-frontend
   # and whatever else without interference from each other.
-  'devtools_frontend_revision': 'e44baaee68cab3a5092a4900c8b6600a5476f19c',
+  'devtools_frontend_revision': '75529f1cd97224cf610bc19c4d6205f82190eb2c',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libprotobuf-mutator
   # and whatever else without interference from each other.
@@ -411,7 +411,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'dawn_revision': '8826cf6cfeb7ed83a5d3ab541f7eff117a254976',
+  'dawn_revision': '025b5011849104e079510121385aa099125ea65c',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -1149,7 +1149,7 @@
   },
 
   'src/chrome/release_scripts': {
-      'url': Var('chrome_git') + '/chrome/tools/release/scripts' + '@' + 'fb311181391e4fbbe19d8378fcb0cb2b68a2b195',
+      'url': Var('chrome_git') + '/chrome/tools/release/scripts' + '@' + '2b782b71a0c094c1ffd92a5faac19183fa908df5',
       'condition': 'checkout_chrome_release_scripts',
   },
 
@@ -1456,7 +1456,7 @@
   },
 
   'src/chrome/test/data/password/captured_sites/artifacts': {
-    'url': Var('chrome_git') + '/chrome/test/captured_sites/password.git' + '@' + '61b62cfe23abcdb7e86e3aa6b0690bd457d04604',
+    'url': Var('chrome_git') + '/chrome/test/captured_sites/password.git' + '@' + '749475b8448a6d9397fae332da7c9f989b386ac4',
     'condition': 'checkout_chromium_password_manager_test_dependencies',
   },
 
@@ -1492,7 +1492,7 @@
 
   'src/clank': {
     'url': Var('chrome_git') + '/clank/internal/apps.git' + '@' +
-    '7be9de743e86b1cbfae5ab74ca1a7824c17138e4',
+    '9c6ca681daaa22012dad5ebfaf9e87c7112ebac5',
     'condition': 'checkout_android and checkout_src_internal',
   },
 
@@ -1954,7 +1954,7 @@
   # Tools used when building Chrome for Chrome OS. This affects both the Simple
   # Chrome workflow, as well as the chromeos-chrome ebuild.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '655d8bb8027c64ec839a8a4408c02438957fdd78',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + 'acb1aa60a1f64474043bab4fc259b046e8935f42',
       'condition': 'checkout_chromeos',
   },
 
@@ -2532,7 +2532,7 @@
     Var('pdfium_git') + '/pdfium.git' + '@' +  Var('pdfium_revision'),
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + 'b3fc262d1f1294a2653e98b909cebc836eb88f97',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + '40b529923598b739b2892a536a7692eedbed5685',
 
   'src/base/tracing/test/data': {
     'bucket': 'perfetto',
@@ -2846,16 +2846,16 @@
       'dep_type': 'cipd',
   },
 
-  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@7f9757f8082d3a6d870e769934ed5962f6b5eea5',
+  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@b9840c73fad2fd81fe88dd40806b3ef0ddbf5c13',
   'src/third_party/glslang/src': '{chromium_git}/external/github.com/KhronosGroup/glslang@e57f993cff981c8c3ffd38967e030f04d13781a9',
   '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@0e710677989b4326ac974fd80c5308191ed80965',
-  'src/third_party/spirv-tools/src': '{chromium_git}/external/github.com/KhronosGroup/SPIRV-Tools@a48b473403b0990c62ff3175f1e63cbd8c906184',
+  'src/third_party/spirv-tools/src': '{chromium_git}/external/github.com/KhronosGroup/SPIRV-Tools@393d5c7df150532045c50affffea2df22e8231b0',
   'src/third_party/vulkan-headers/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-Headers@78c359741d855213e8685278eb81bb62599f8e56',
   'src/third_party/vulkan-loader/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-Loader@723d6b4aa35853315c6e021ec86388b3a2559fae',
-  'src/third_party/vulkan-tools/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-Tools@32ee3e6e333a4bc4064fe64cfdfdcf6e71a92743',
+  'src/third_party/vulkan-tools/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-Tools@da7c7db28d36f66dff88f00412dceb480ccc77ea',
   'src/third_party/vulkan-utility-libraries/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-Utility-Libraries@0d5b49b80f17bca25e7f9321ad4e671a56f70887',
-  'src/third_party/vulkan-validation-layers/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-ValidationLayers@52b492f11e74dcd44ad3e906125feae79d440c99',
+  'src/third_party/vulkan-validation-layers/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-ValidationLayers@2e6787d498d65bc20c195d667b8cd3c63e1a8aa9',
 
   'src/third_party/vulkan_memory_allocator':
     Var('chromium_git') + '/external/github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git' + '@' + '56300b29fbfcc693ee6609ddad3fdd5b7a449a21',
@@ -2894,13 +2894,13 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + 'c01b768bce4a143e152c1870b6ba99ea6267d2b0',
 
   'src/third_party/webgpu-cts/src':
-    Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '645c35eb32a795d7e3deccf9d1580a1136b939d8',
+    Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '2c09e17c32931273808700d7544ec28470e5d5fe',
 
   'src/third_party/webpagereplay':
     Var('chromium_git') + '/webpagereplay.git' + '@' + Var('webpagereplay_revision'),
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '25636d19d22291470dd7b10cec7d6f12082b678e',
+    Var('webrtc_git') + '/src.git' + '@' + '13e4923c12596c16018fc7bc5143e3f6e01a00c8',
 
   # Wuffs' canonical repository is at github.com/google/wuffs, but we use
   # Skia's mirror of Wuffs, the same as in upstream Skia's DEPS file.
@@ -3041,7 +3041,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/media_app/app',
-        'version': 'Q9jIFL_kv8iMtwEHCR8Lz0aYRtzp-nc3PaAhF_OcHAEC',
+        'version': '_kUJYkilPkDceHVSe8UO34GjN4tpVHbPArbDKXLXGJ4C',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
@@ -4433,7 +4433,7 @@
 
   'src/chrome/browser/glic/resources/internal': {
       'url': Var('chrome_git') + '/chrome/browser/glic/resources/internal.git' + '@' +
-        'b9ab4971feac2af59d5448e0dbbfe20c4965391f',
+        '5d196ac2e495e1714ae22c4eb6507db40367ee23',
       'condition': 'checkout_src_internal',
   },
 
@@ -4469,7 +4469,7 @@
 
   'src/chrome/browser/platform_experience/win': {
       'url': Var('chrome_git') + '/chrome/browser/platform_experience/win.git' + '@' +
-        '33c17cac9b138a9640468b481761408127d5d684',
+        '418eade860a2be8e4e174de3775d61162565ed2a',
       'condition': 'checkout_src_internal',
   },
 
@@ -4683,13 +4683,13 @@
 
   'src/google_apis/internal': {
       'url': Var('chrome_git') + '/chrome/google_apis/internal.git' + '@' +
-        'aa70199859693798799b7a72018f14dd510cd9f2',
+        '02538e2044536d99127760477f6d787d3c4f6ae2',
       'condition': 'checkout_src_internal',
   },
 
   'src/ios_internal':  {
       'url': Var('chrome_git') + '/chrome/ios_internal.git' + '@' +
-        '6b4d9a3f084c9b10ac4b7bacba54919bab6fe372',
+        '551c00da20474aeae5bd8e6356609f870e0acbc6',
       'condition': 'checkout_ios and checkout_src_internal',
   },
 
diff --git a/android_webview/browser/aw_contents_client_bridge.cc b/android_webview/browser/aw_contents_client_bridge.cc
index 7241222f0..81d682d0 100644
--- a/android_webview/browser/aw_contents_client_bridge.cc
+++ b/android_webview/browser/aw_contents_client_bridge.cc
@@ -5,6 +5,8 @@
 #include "android_webview/browser/aw_contents_client_bridge.h"
 
 #include <memory>
+#include <optional>
+#include <string>
 #include <string_view>
 #include <utility>
 
@@ -61,10 +63,10 @@
   static AwContentsClientBridge* GetContents(
       content::WebContents* web_contents) {
     if (!web_contents)
-      return NULL;
+      return nullptr;
     UserData* data = static_cast<UserData*>(
         web_contents->GetUserData(kAwContentsClientBridge));
-    return data ? data->contents_.get() : NULL;
+    return data ? data->contents_.get() : nullptr;
   }
 
   explicit UserData(AwContentsClientBridge* ptr) : contents_(ptr) {}
@@ -80,7 +82,7 @@
 
 AwContentsClientBridge::HttpErrorInfo::HttpErrorInfo() : status_code(0) {}
 
-AwContentsClientBridge::HttpErrorInfo::~HttpErrorInfo() {}
+AwContentsClientBridge::HttpErrorInfo::~HttpErrorInfo() = default;
 
 // static
 void AwContentsClientBridge::Associate(WebContents* web_contents,
@@ -135,14 +137,12 @@
       net::x509_util::CryptoBufferAsStringPiece(cert->cert_buffer());
   ScopedJavaLocalRef<jbyteArray> jcert =
       base::android::ToJavaByteArray(env, base::as_byte_span(der_string));
-  ScopedJavaLocalRef<jstring> jurl(
-      ConvertUTF8ToJavaString(env, request_url.spec()));
   // We need to add the callback before making the call to java side,
   // as it may do a synchronous callback prior to returning.
   int request_id = pending_cert_error_callbacks_.Add(
       std::make_unique<CertErrorCallback>(std::move(callback)));
   *cancel_request = !Java_AwContentsClientBridge_allowCertificateError(
-      env, obj, cert_error, jcert, jurl, request_id);
+      env, obj, cert_error, jcert, request_url.spec(), request_id);
   // if the request is cancelled, then cancel the stored callback
   if (*cancel_request) {
     pending_cert_error_callbacks_.Remove(request_id);
@@ -150,7 +150,6 @@
 }
 
 void AwContentsClientBridge::ProceedSslError(JNIEnv* env,
-                                             const JavaRef<jobject>& obj,
                                              jboolean proceed,
                                              jint id) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
@@ -181,12 +180,6 @@
   // Build the |key_types| JNI parameter, as a String[]
   std::vector<std::string> key_types = net::SignatureAlgorithmsToJavaKeyTypes(
       cert_request_info->signature_algorithms);
-  ScopedJavaLocalRef<jobjectArray> key_types_ref =
-      base::android::ToJavaArrayOfStrings(env, key_types);
-  if (!key_types_ref) {
-    LOG(ERROR) << "Could not create key types array (String[])";
-    return;
-  }
 
   // Build the |encoded_principals| JNI parameter, as a byte[][]
   ScopedJavaLocalRef<jobjectArray> principals_ref =
@@ -197,16 +190,11 @@
     return;
   }
 
-  // Build the |host_name| and |port| JNI parameters, as a String and
-  // a jint.
-  ScopedJavaLocalRef<jstring> host_name_ref =
-      base::android::ConvertUTF8ToJavaString(
-          env, cert_request_info->host_and_port.host());
-
   int request_id =
       pending_client_cert_request_delegates_.Add(std::move(delegate));
   Java_AwContentsClientBridge_selectClientCertificate(
-      env, obj, request_id, key_types_ref, principals_ref, host_name_ref,
+      env, obj, request_id, key_types, principals_ref,
+      cert_request_info->host_and_port.host(),
       cert_request_info->host_and_port.port());
 }
 
@@ -215,7 +203,6 @@
 // ssl_client_certificate_request.cc
 void AwContentsClientBridge::ProvideClientCertificateResponse(
     JNIEnv* env,
-    const JavaRef<jobject>& obj,
     int request_id,
     const JavaRef<jobjectArray>& encoded_chain_ref,
     const JavaRef<jobject>& private_key_ref) {
@@ -240,8 +227,9 @@
   }
 
   std::vector<std::string_view> encoded_chain;
-  for (size_t i = 0; i < encoded_chain_strings.size(); ++i)
-    encoded_chain.push_back(encoded_chain_strings[i]);
+  for (const auto& encoded_chain_string : encoded_chain_strings) {
+    encoded_chain.push_back(encoded_chain_string);
+  }
 
   // Create the X509Certificate object from the encoded chain.
   scoped_refptr<net::X509Certificate> client_cert(
@@ -281,33 +269,27 @@
   int callback_id = pending_js_dialog_callbacks_.Add(
       std::make_unique<content::JavaScriptDialogManager::DialogClosedCallback>(
           std::move(callback)));
-  ScopedJavaLocalRef<jstring> jurl(
-      ConvertUTF8ToJavaString(env, origin_url.spec()));
-  ScopedJavaLocalRef<jstring> jmessage(
-      ConvertUTF16ToJavaString(env, message_text));
-
   switch (dialog_type) {
     case content::JAVASCRIPT_DIALOG_TYPE_ALERT: {
       devtools_instrumentation::ScopedEmbedderCallbackTask embedder_callback(
           "onJsAlert");
-      Java_AwContentsClientBridge_handleJsAlert(env, obj, jurl, jmessage,
-                                                callback_id);
+      Java_AwContentsClientBridge_handleJsAlert(env, obj, origin_url.spec(),
+                                                message_text, callback_id);
       break;
     }
     case content::JAVASCRIPT_DIALOG_TYPE_CONFIRM: {
       devtools_instrumentation::ScopedEmbedderCallbackTask embedder_callback(
           "onJsConfirm");
-      Java_AwContentsClientBridge_handleJsConfirm(env, obj, jurl, jmessage,
-                                                  callback_id);
+      Java_AwContentsClientBridge_handleJsConfirm(env, obj, origin_url.spec(),
+                                                  message_text, callback_id);
       break;
     }
     case content::JAVASCRIPT_DIALOG_TYPE_PROMPT: {
-      ScopedJavaLocalRef<jstring> jdefault_value(
-          ConvertUTF16ToJavaString(env, default_prompt_text));
       devtools_instrumentation::ScopedEmbedderCallbackTask embedder_callback(
           "onJsPrompt");
-      Java_AwContentsClientBridge_handleJsPrompt(env, obj, jurl, jmessage,
-                                                 jdefault_value, callback_id);
+      Java_AwContentsClientBridge_handleJsPrompt(
+          env, obj, origin_url.spec(), message_text, default_prompt_text,
+          callback_id);
       break;
     }
     default:
@@ -333,15 +315,11 @@
   int callback_id = pending_js_dialog_callbacks_.Add(
       std::make_unique<content::JavaScriptDialogManager::DialogClosedCallback>(
           std::move(callback)));
-  ScopedJavaLocalRef<jstring> jurl(
-      ConvertUTF8ToJavaString(env, origin_url.spec()));
-  ScopedJavaLocalRef<jstring> jmessage(
-      ConvertUTF16ToJavaString(env, message_text));
 
   devtools_instrumentation::ScopedEmbedderCallbackTask embedder_callback(
       "onJsBeforeUnload");
-  Java_AwContentsClientBridge_handleJsBeforeUnload(env, obj, jurl, jmessage,
-                                                   callback_id);
+  Java_AwContentsClientBridge_handleJsBeforeUnload(env, obj, origin_url.spec(),
+                                                   message_text, callback_id);
 }
 
 bool AwContentsClientBridge::ShouldOverrideUrlLoading(
@@ -356,7 +334,6 @@
   ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
   if (!obj)
     return true;
-  ScopedJavaLocalRef<jstring> jurl = ConvertUTF16ToJavaString(env, url);
   devtools_instrumentation::ScopedEmbedderCallbackTask embedder_callback(
       "shouldOverrideUrlLoading");
 
@@ -365,14 +342,9 @@
   ConvertRequestHeadersToVectors(request_headers, &header_names,
                                  &header_values);
 
-  ScopedJavaLocalRef<jobjectArray> jheader_names =
-      ToJavaArrayOfStrings(env, header_names);
-  ScopedJavaLocalRef<jobjectArray> jheader_values =
-      ToJavaArrayOfStrings(env, header_values);
-
   *ignore_navigation = Java_AwContentsClientBridge_shouldOverrideUrlLoading(
-      env, obj, jurl, has_user_gesture, is_redirect, jheader_names,
-      jheader_values, is_outermost_main_frame);
+      env, obj, url, has_user_gesture, is_redirect, header_names, header_values,
+      is_outermost_main_frame);
   if (HasException(env)) {
     // Tell the chromium message loop to not perform any tasks after the current
     // one - we want to make sure we return to Java cleanly without first making
@@ -390,8 +362,7 @@
   ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
   if (!obj)
     return false;
-  ScopedJavaLocalRef<jstring> jurl = ConvertUTF16ToJavaString(env, url);
-  return Java_AwContentsClientBridge_sendBrowseIntent(env, obj, jurl);
+  return Java_AwContentsClientBridge_sendBrowseIntent(env, obj, url);
 }
 
 void AwContentsClientBridge::NewDownload(const GURL& url,
@@ -405,18 +376,9 @@
   if (!obj)
     return;
 
-  ScopedJavaLocalRef<jstring> jstring_url =
-      ConvertUTF8ToJavaString(env, url.spec());
-  ScopedJavaLocalRef<jstring> jstring_user_agent =
-      ConvertUTF8ToJavaString(env, user_agent);
-  ScopedJavaLocalRef<jstring> jstring_content_disposition =
-      ConvertUTF8ToJavaString(env, content_disposition);
-  ScopedJavaLocalRef<jstring> jstring_mime_type =
-      ConvertUTF8ToJavaString(env, mime_type);
-
-  Java_AwContentsClientBridge_newDownload(
-      env, obj, jstring_url, jstring_user_agent, jstring_content_disposition,
-      jstring_mime_type, content_length);
+  Java_AwContentsClientBridge_newDownload(env, obj, url.spec(), user_agent,
+                                          content_disposition, mime_type,
+                                          content_length);
 }
 
 void AwContentsClientBridge::NewLoginRequest(const std::string& realm,
@@ -427,16 +389,10 @@
   ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
   if (!obj)
     return;
-
-  ScopedJavaLocalRef<jstring> jrealm = ConvertUTF8ToJavaString(env, realm);
-  ScopedJavaLocalRef<jstring> jargs = ConvertUTF8ToJavaString(env, args);
-
-  ScopedJavaLocalRef<jstring> jaccount;
-  if (!account.empty())
-    jaccount = ConvertUTF8ToJavaString(env, account);
-
-  Java_AwContentsClientBridge_newLoginRequest(env, obj, jrealm, jaccount,
-                                              jargs);
+  // The API expects nullptr rather than empty string if account is missing.
+  const std::string* account_or_null = account.empty() ? nullptr : &account;
+  Java_AwContentsClientBridge_newLoginRequest(env, obj, realm, account_or_null,
+                                              args);
 }
 
 void AwContentsClientBridge::OnReceivedError(
@@ -451,18 +407,10 @@
   if (!obj)
     return;
 
-  ScopedJavaLocalRef<jstring> jstring_description =
-      ConvertUTF8ToJavaString(env, net::ErrorToString(error_code));
-
-  AwWebResourceRequest::AwJavaWebResourceRequest java_web_resource_request;
-  AwWebResourceRequest::ConvertToJava(env, request, &java_web_resource_request);
   Java_AwContentsClientBridge_onReceivedError(
-      env, obj, java_web_resource_request.jurl, request.is_outermost_main_frame,
-      request.has_user_gesture, *request.is_renderer_initiated,
-      java_web_resource_request.jmethod,
-      java_web_resource_request.jheader_names,
-      java_web_resource_request.jheader_values, error_code, jstring_description,
-      safebrowsing_hit, should_omit_notifications_for_safebrowsing_hit);
+      env, obj, request, request.is_renderer_initiated.value_or(false),
+      error_code, net::ErrorToString(error_code), safebrowsing_hit,
+      should_omit_notifications_for_safebrowsing_hit);
 }
 
 void AwContentsClientBridge::OnSafeBrowsingHit(
@@ -478,14 +426,8 @@
   if (!obj)
     return;
 
-  AwWebResourceRequest::AwJavaWebResourceRequest java_web_resource_request;
-  AwWebResourceRequest::ConvertToJava(env, request, &java_web_resource_request);
   Java_AwContentsClientBridge_onSafeBrowsingHit(
-      env, obj, java_web_resource_request.jurl, request.is_outermost_main_frame,
-      request.has_user_gesture, java_web_resource_request.jmethod,
-      java_web_resource_request.jheader_names,
-      java_web_resource_request.jheader_values, static_cast<int>(threat_type),
-      request_id);
+      env, obj, request, static_cast<int>(threat_type), request_id);
 }
 
 void AwContentsClientBridge::OnReceivedHttpError(
@@ -497,27 +439,11 @@
   if (!obj)
     return;
 
-  AwWebResourceRequest::AwJavaWebResourceRequest java_web_resource_request;
-  AwWebResourceRequest::ConvertToJava(env, request, &java_web_resource_request);
-
-  ScopedJavaLocalRef<jstring> jstring_mime_type =
-      ConvertUTF8ToJavaString(env, http_error_info->mime_type);
-  ScopedJavaLocalRef<jstring> jstring_encoding =
-      ConvertUTF8ToJavaString(env, http_error_info->encoding);
-  ScopedJavaLocalRef<jstring> jstring_reason =
-      ConvertUTF8ToJavaString(env, http_error_info->status_text);
-  ScopedJavaLocalRef<jobjectArray> jstringArray_response_header_names =
-      ToJavaArrayOfStrings(env, http_error_info->response_header_names);
-  ScopedJavaLocalRef<jobjectArray> jstringArray_response_header_values =
-      ToJavaArrayOfStrings(env, http_error_info->response_header_values);
-
   Java_AwContentsClientBridge_onReceivedHttpError(
-      env, obj, java_web_resource_request.jurl, request.is_outermost_main_frame,
-      request.has_user_gesture, java_web_resource_request.jmethod,
-      java_web_resource_request.jheader_names,
-      java_web_resource_request.jheader_values, jstring_mime_type,
-      jstring_encoding, http_error_info->status_code, jstring_reason,
-      jstringArray_response_header_names, jstringArray_response_header_values);
+      env, obj, request, http_error_info->mime_type, http_error_info->encoding,
+      http_error_info->status_code, http_error_info->status_text,
+      http_error_info->response_header_names,
+      http_error_info->response_header_values);
 }
 
 // static
@@ -542,10 +468,10 @@
   return http_error_info;
 }
 
-void AwContentsClientBridge::ConfirmJsResult(JNIEnv* env,
-                                             const JavaRef<jobject>&,
-                                             int id,
-                                             const JavaRef<jstring>& prompt) {
+void AwContentsClientBridge::ConfirmJsResult(
+    JNIEnv* env,
+    int id,
+    std::optional<std::u16string> prompt) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   content::JavaScriptDialogManager::DialogClosedCallback* callback =
       pending_js_dialog_callbacks_.Lookup(id);
@@ -553,16 +479,11 @@
     LOG(WARNING) << "Unexpected JS dialog confirm. " << id;
     return;
   }
-  std::u16string prompt_text;
-  if (prompt) {
-    prompt_text = ConvertJavaStringToUTF16(env, prompt);
-  }
-  std::move(*callback).Run(true, prompt_text);
+  std::move(*callback).Run(true, prompt.value_or(std::u16string()));
   pending_js_dialog_callbacks_.Remove(id);
 }
 
 void AwContentsClientBridge::TakeSafeBrowsingAction(JNIEnv*,
-                                                    const JavaRef<jobject>&,
                                                     int action,
                                                     bool reporting,
                                                     int request_id) {
@@ -578,9 +499,7 @@
   safe_browsing_callbacks_.Remove(request_id);
 }
 
-void AwContentsClientBridge::CancelJsResult(JNIEnv*,
-                                            const JavaRef<jobject>&,
-                                            int id) {
+void AwContentsClientBridge::CancelJsResult(JNIEnv*, int id) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   content::JavaScriptDialogManager::DialogClosedCallback* callback =
       pending_js_dialog_callbacks_.Lookup(id);
diff --git a/android_webview/browser/aw_contents_client_bridge.h b/android_webview/browser/aw_contents_client_bridge.h
index 65472f5..3eaddaed 100644
--- a/android_webview/browser/aw_contents_client_bridge.h
+++ b/android_webview/browser/aw_contents_client_bridge.h
@@ -137,23 +137,17 @@
 
   // Methods called from Java.
   void ProceedSslError(JNIEnv* env,
-                       const base::android::JavaRef<jobject>& obj,
                        jboolean proceed,
                        jint id);
   void ProvideClientCertificateResponse(
       JNIEnv* env,
-      const base::android::JavaRef<jobject>& object,
       jint request_id,
       const base::android::JavaRef<jobjectArray>& encoded_chain_ref,
       const base::android::JavaRef<jobject>& private_key_ref);
-  void ConfirmJsResult(JNIEnv*,
-                       const base::android::JavaRef<jobject>&,
-                       int id,
-                       const base::android::JavaRef<jstring>& prompt);
-  void CancelJsResult(JNIEnv*, const base::android::JavaRef<jobject>&, int id);
+  void ConfirmJsResult(JNIEnv*, int id, std::optional<std::u16string> prompt);
+  void CancelJsResult(JNIEnv*, int id);
 
   void TakeSafeBrowsingAction(JNIEnv*,
-                              const base::android::JavaRef<jobject>&,
                               int action,
                               bool reporting,
                               int request_id);
diff --git a/android_webview/browser/aw_contents_client_bridge_unittest.cc b/android_webview/browser/aw_contents_client_bridge_unittest.cc
index 3720fbf..7ef5551 100644
--- a/android_webview/browser/aw_contents_client_bridge_unittest.cc
+++ b/android_webview/browser/aw_contents_client_bridge_unittest.cc
@@ -7,7 +7,7 @@
 #include <memory>
 
 #include "base/android/jni_android.h"
-#include "base/android/jni_array.h"
+#include "base/android/jni_string.h"
 #include "base/android/scoped_java_ref.h"
 #include "base/functional/bind.h"
 #include "base/memory/ptr_util.h"
@@ -40,7 +40,7 @@
 // Tests the android_webview contents client bridge.
 class AwContentsClientBridgeTest : public Test {
  public:
-  AwContentsClientBridgeTest() {}
+  AwContentsClientBridgeTest() = default;
 
   // Callback method called when a cert is selected.
   void CertSelected(scoped_refptr<X509Certificate> cert,
@@ -125,11 +125,9 @@
       std::make_unique<TestClientCertificateDelegate>(this));
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(0, cert_selected_callbacks_);
-  ScopedJavaLocalRef<jobjectArray> key_types =
+  std::vector<std::string> key_types =
       Java_MockAwContentsClientBridge_getKeyTypes(env_, jbridge_);
-  std::vector<std::string> vec;
-  base::android::AppendJavaStringArrayToStringVector(env_, key_types, &vec);
-  EXPECT_EQ(expected_names, vec);
+  EXPECT_EQ(expected_names, key_types);
 }
 
 // Verify that ProvideClientCertificateResponse works properly when the client
@@ -142,8 +140,7 @@
       cert_request_info_.get(),
       base::WrapUnique(new TestClientCertificateDelegate(this)));
   bridge_->ProvideClientCertificateResponse(
-      env_, jbridge_,
-      Java_MockAwContentsClientBridge_getRequestId(env_, jbridge_),
+      env_, Java_MockAwContentsClientBridge_getRequestId(env_, jbridge_),
       Java_MockAwContentsClientBridge_createTestCertChain(env_, jbridge_),
       nullptr);
   base::RunLoop().RunUntilIdle();
@@ -162,8 +159,7 @@
       cert_request_info_.get(),
       base::WrapUnique(new TestClientCertificateDelegate(this)));
   int requestId = Java_MockAwContentsClientBridge_getRequestId(env_, jbridge_);
-  bridge_->ProvideClientCertificateResponse(env_, jbridge_, requestId, nullptr,
-                                            nullptr);
+  bridge_->ProvideClientCertificateResponse(env_, requestId, nullptr, nullptr);
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(nullptr, selected_cert_.get());
   EXPECT_EQ(nullptr, selected_key_.get());
diff --git a/android_webview/browser/network_service/aw_web_resource_request.cc b/android_webview/browser/network_service/aw_web_resource_request.cc
index 944ea635..e101a9d 100644
--- a/android_webview/browser/network_service/aw_web_resource_request.cc
+++ b/android_webview/browser/network_service/aw_web_resource_request.cc
@@ -16,11 +16,6 @@
 // Must come after all headers that specialize FromJniType() / ToJniType().
 #include "android_webview/browser_jni_headers/AwWebResourceRequest_jni.h"
 
-using base::android::ConvertJavaStringToUTF16;
-using base::android::ConvertUTF8ToJavaString;
-using base::android::ConvertUTF16ToJavaString;
-using base::android::ToJavaArrayOfStrings;
-
 namespace android_webview {
 
 AwWebResourceRequest::AwWebResourceRequest(
@@ -57,21 +52,6 @@
     AwWebResourceRequest&& other) = default;
 AwWebResourceRequest::~AwWebResourceRequest() = default;
 
-AwWebResourceRequest::AwJavaWebResourceRequest::AwJavaWebResourceRequest() =
-    default;
-AwWebResourceRequest::AwJavaWebResourceRequest::~AwJavaWebResourceRequest() =
-    default;
-
-// static
-void AwWebResourceRequest::ConvertToJava(JNIEnv* env,
-                                         const AwWebResourceRequest& request,
-                                         AwJavaWebResourceRequest* jRequest) {
-  jRequest->jurl = ConvertUTF8ToJavaString(env, request.url);
-  jRequest->jmethod = ConvertUTF8ToJavaString(env, request.method);
-  jRequest->jheader_names = ToJavaArrayOfStrings(env, request.header_names);
-  jRequest->jheader_values = ToJavaArrayOfStrings(env, request.header_values);
-}
-
 }  // namespace android_webview
 //
 namespace jni_zero {
diff --git a/android_webview/browser/network_service/aw_web_resource_request.h b/android_webview/browser/network_service/aw_web_resource_request.h
index 599fa02..b23813e 100644
--- a/android_webview/browser/network_service/aw_web_resource_request.h
+++ b/android_webview/browser/network_service/aw_web_resource_request.h
@@ -40,22 +40,6 @@
   AwWebResourceRequest& operator=(AwWebResourceRequest&& other);
   ~AwWebResourceRequest();
 
-  // The java equivalent
-  struct AwJavaWebResourceRequest {
-    AwJavaWebResourceRequest();
-    ~AwJavaWebResourceRequest();
-
-    base::android::ScopedJavaLocalRef<jstring> jurl;
-    base::android::ScopedJavaLocalRef<jstring> jmethod;
-    base::android::ScopedJavaLocalRef<jobjectArray> jheader_names;
-    base::android::ScopedJavaLocalRef<jobjectArray> jheader_values;
-  };
-
-  // Convenience method to convert AwWebResourceRequest to Java equivalent.
-  static void ConvertToJava(JNIEnv* env,
-                            const AwWebResourceRequest& request,
-                            AwJavaWebResourceRequest* jRequest);
-
   std::string url;
   std::string method;
   bool is_outermost_main_frame;
diff --git a/android_webview/java/src/org/chromium/android_webview/AwContentsClientBridge.java b/android_webview/java/src/org/chromium/android_webview/AwContentsClientBridge.java
index 7f7d35a3..ce72965 100644
--- a/android_webview/java/src/org/chromium/android_webview/AwContentsClientBridge.java
+++ b/android_webview/java/src/org/chromium/android_webview/AwContentsClientBridge.java
@@ -17,6 +17,7 @@
 import org.jni_zero.CalledByNative;
 import org.jni_zero.CalledByNativeUnchecked;
 import org.jni_zero.JNINamespace;
+import org.jni_zero.JniType;
 import org.jni_zero.NativeMethods;
 
 import org.chromium.android_webview.safe_browsing.AwSafeBrowsingConversionHelper;
@@ -26,6 +27,7 @@
 import org.chromium.base.TraceEvent;
 import org.chromium.base.task.PostTask;
 import org.chromium.base.task.TaskTraits;
+import org.chromium.build.annotations.Nullable;
 import org.chromium.components.embedder_support.util.WebResourceResponseInfo;
 import org.chromium.net.NetError;
 
@@ -147,11 +149,7 @@
             if (mNativeContentsClientBridge == 0) return;
             AwContentsClientBridgeJni.get()
                     .provideClientCertificateResponse(
-                            mNativeContentsClientBridge,
-                            AwContentsClientBridge.this,
-                            mId,
-                            certChain,
-                            privateKey);
+                            mNativeContentsClientBridge, mId, certChain, privateKey);
         }
     }
 
@@ -169,7 +167,10 @@
     // ssl_policy in native layers.
     @CalledByNative
     private boolean allowCertificateError(
-            int certError, byte[] derBytes, final String url, final int id) {
+            int certError,
+            byte[] derBytes,
+            final @JniType("std::string") String url,
+            final int id) {
         final SslCertificate cert = SslUtil.getCertificateFromDerBytes(derBytes);
         if (cert == null) {
             // if the certificate or the client is null, cancel the request
@@ -191,39 +192,28 @@
 
     private void proceedSslError(boolean proceed, int id) {
         if (mNativeContentsClientBridge == 0) return;
-        AwContentsClientBridgeJni.get()
-                .proceedSslError(
-                        mNativeContentsClientBridge, AwContentsClientBridge.this, proceed, id);
+        AwContentsClientBridgeJni.get().proceedSslError(mNativeContentsClientBridge, proceed, id);
     }
 
     // Intentionally not private for testing the native peer of this class.
     @CalledByNative
     protected void selectClientCertificate(
             final int id,
-            final String[] keyTypes,
+            final @JniType("std::vector<std::string>") String[] keyTypes,
             byte[][] encodedPrincipals,
-            final String host,
+            final @JniType("std::string") String host,
             final int port) {
         assert mNativeContentsClientBridge != 0;
         ClientCertLookupTable.Cert cert = mLookupTable.getCertData(host, port);
         if (mLookupTable.isDenied(host, port)) {
             AwContentsClientBridgeJni.get()
-                    .provideClientCertificateResponse(
-                            mNativeContentsClientBridge,
-                            AwContentsClientBridge.this,
-                            id,
-                            null,
-                            null);
+                    .provideClientCertificateResponse(mNativeContentsClientBridge, id, null, null);
             return;
         }
         if (cert != null) {
             AwContentsClientBridgeJni.get()
                     .provideClientCertificateResponse(
-                            mNativeContentsClientBridge,
-                            AwContentsClientBridge.this,
-                            id,
-                            cert.mCertChain,
-                            cert.mPrivateKey);
+                            mNativeContentsClientBridge, id, cert.mCertChain, cert.mPrivateKey);
             return;
         }
         // Build the list of principals from encoded versions.
@@ -237,11 +227,7 @@
                     Log.w(TAG, "Exception while decoding issuers list: " + e);
                     AwContentsClientBridgeJni.get()
                             .provideClientCertificateResponse(
-                                    mNativeContentsClientBridge,
-                                    AwContentsClientBridge.this,
-                                    id,
-                                    null,
-                                    null);
+                                    mNativeContentsClientBridge, id, null, null);
                     return;
                 }
             }
@@ -260,7 +246,10 @@
     }
 
     @CalledByNative
-    private void handleJsAlert(final String url, final String message, final int id) {
+    private void handleJsAlert(
+            final @JniType("std::string") String url,
+            final @JniType("std::u16string") String message,
+            final int id) {
         // Post the application callback back to the current thread to ensure the application
         // callback is executed without any native code on the stack. This so that any exception
         // thrown by the application callback won't have to be propagated through a native call
@@ -273,7 +262,10 @@
     }
 
     @CalledByNative
-    private void handleJsConfirm(final String url, final String message, final int id) {
+    private void handleJsConfirm(
+            final @JniType("std::string") String url,
+            final @JniType("std::u16string") String message,
+            final int id) {
         // Post the application callback back to the current thread to ensure the application
         // callback is executed without any native code on the stack. This so that any exception
         // thrown by the application callback won't have to be propagated through a native call
@@ -287,7 +279,10 @@
 
     @CalledByNative
     private void handleJsPrompt(
-            final String url, final String message, final String defaultValue, final int id) {
+            final @JniType("std::string") String url,
+            final @JniType("std::u16string") String message,
+            final @JniType("std::u16string") String defaultValue,
+            final int id) {
         // Post the application callback back to the current thread to ensure the application
         // callback is executed without any native code on the stack. This so that any exception
         // thrown by the application callback won't have to be propagated through a native call
@@ -300,7 +295,10 @@
     }
 
     @CalledByNative
-    private void handleJsBeforeUnload(final String url, final String message, final int id) {
+    private void handleJsBeforeUnload(
+            final @JniType("std::string") String url,
+            final @JniType("std::u16string") String message,
+            final int id) {
         // Post the application callback back to the current thread to ensure the application
         // callback is executed without any native code on the stack. This so that any exception
         // thrown by the application callback won't have to be propagated through a native call
@@ -314,10 +312,10 @@
 
     @CalledByNative
     private void newDownload(
-            String url,
-            String userAgent,
-            String contentDisposition,
-            String mimeType,
+            @JniType("std::string") String url,
+            @JniType("std::string") String userAgent,
+            @JniType("std::string") String contentDisposition,
+            @JniType("std::string") String mimeType,
             long contentLength) {
         try (TraceEvent event = TraceEvent.scoped("WebView.APICallback.ON_DOWNLOAD_START")) {
             mClient.getCallbackHelper()
@@ -331,7 +329,10 @@
     }
 
     @CalledByNative
-    private void newLoginRequest(String realm, String account, String args) {
+    private void newLoginRequest(
+            @JniType("std::string") String realm,
+            @JniType("const std::string*") @Nullable String account,
+            @JniType("std::string") String args) {
         try (TraceEvent event =
                 TraceEvent.scoped("WebView.APICallback.ON_RECEIVED_LOGIN_REQUEST")) {
             mClient.getCallbackHelper().postOnReceivedLoginRequest(realm, account, args);
@@ -344,27 +345,13 @@
 
     @CalledByNative
     private void onReceivedError(
-            // WebResourceRequest
-            String url,
-            boolean isOutermostMainFrame,
-            boolean hasUserGesture,
+            @JniType("android_webview::AwWebResourceRequest") AwWebResourceRequest request,
             boolean isRendererInitiated,
-            String method,
-            String[] requestHeaderNames,
-            String[] requestHeaderValues,
             // WebResourceError
             @NetError int errorCode,
-            String description,
+            @JniType("std::string") String description,
             boolean safebrowsingHit,
             boolean shouldOmitNotificationsForSafeBrowsingHit) {
-        AwWebResourceRequest request =
-                new AwWebResourceRequest(
-                        url,
-                        isOutermostMainFrame,
-                        hasUserGesture,
-                        method,
-                        requestHeaderNames,
-                        requestHeaderValues);
         AwContentsClient.AwWebResourceError error = new AwContentsClient.AwWebResourceError();
         error.errorCode = ErrorCodeConversionHelper.convertErrorCode(errorCode);
         error.description = description;
@@ -407,24 +394,9 @@
 
     @CalledByNative
     public void onSafeBrowsingHit(
-            // WebResourceRequest
-            String url,
-            boolean isOutermostMainFrame,
-            boolean hasUserGesture,
-            String method,
-            String[] requestHeaderNames,
-            String[] requestHeaderValues,
+            @JniType("android_webview::AwWebResourceRequest") AwWebResourceRequest request,
             int threatType,
             final int requestId) {
-        AwWebResourceRequest request =
-                new AwWebResourceRequest(
-                        url,
-                        isOutermostMainFrame,
-                        hasUserGesture,
-                        method,
-                        requestHeaderNames,
-                        requestHeaderValues);
-
         Callback<AwSafeBrowsingResponse> callback =
                 response ->
                         PostTask.runOrPostTask(
@@ -433,7 +405,6 @@
                                         AwContentsClientBridgeJni.get()
                                                 .takeSafeBrowsingAction(
                                                         mNativeContentsClientBridge,
-                                                        AwContentsClientBridge.this,
                                                         response.action(),
                                                         response.reporting(),
                                                         requestId));
@@ -444,30 +415,15 @@
 
     @CalledByNative
     private void onReceivedHttpError(
-            // WebResourceRequest
-            String url,
-            boolean isOutermostMainFrame,
-            boolean hasUserGesture,
-            String method,
-            String[] requestHeaderNames,
-            String[] requestHeaderValues,
+            @JniType("android_webview::AwWebResourceRequest") AwWebResourceRequest request,
             // WebResourceResponse
-            String mimeType,
-            String encoding,
+            @JniType("std::string") String mimeType,
+            @JniType("std::string") String encoding,
             int statusCode,
-            String reasonPhrase,
-            String[] responseHeaderNames,
-            String[] responseHeaderValues) {
-        AwWebResourceRequest request =
-                new AwWebResourceRequest(
-                        url,
-                        isOutermostMainFrame,
-                        hasUserGesture,
-                        method,
-                        requestHeaderNames,
-                        requestHeaderValues);
-        Map<String, String> responseHeaders =
-                new HashMap<String, String>(responseHeaderNames.length);
+            @JniType("std::string") String reasonPhrase,
+            @JniType("std::vector<std::string>") String[] responseHeaderNames,
+            @JniType("std::vector<std::string>") String[] responseHeaderValues) {
+        Map<String, String> responseHeaders = new HashMap<>(responseHeaderNames.length);
         // Note that we receive un-coalesced response header lines, thus we need to combine
         // values for the same header.
         for (int i = 0; i < responseHeaderNames.length; ++i) {
@@ -489,11 +445,11 @@
 
     @CalledByNativeUnchecked
     private boolean shouldOverrideUrlLoading(
-            String url,
+            @JniType("std::u16string") String url,
             boolean hasUserGesture,
             boolean isRedirect,
-            String[] requestHeaderNames,
-            String[] requestHeaderValues,
+            @JniType("std::vector<std::string>") String[] requestHeaderNames,
+            @JniType("std::vector<std::string>") String[] requestHeaderValues,
             boolean isOutermostMainFrame) {
         HashMap<String, String> requestHeaders = null;
         if (requestHeaderNames.length > 0) {
@@ -509,7 +465,7 @@
     }
 
     @CalledByNative
-    private boolean sendBrowseIntent(String url) {
+    private boolean sendBrowseIntent(@JniType("std::u16string") String url) {
         try {
             Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
             intent.addCategory(Intent.CATEGORY_BROWSABLE);
@@ -583,46 +539,32 @@
 
     void confirmJsResult(int id, String prompt) {
         if (mNativeContentsClientBridge == 0) return;
-        AwContentsClientBridgeJni.get()
-                .confirmJsResult(
-                        mNativeContentsClientBridge, AwContentsClientBridge.this, id, prompt);
+        AwContentsClientBridgeJni.get().confirmJsResult(mNativeContentsClientBridge, id, prompt);
     }
 
     void cancelJsResult(int id) {
         if (mNativeContentsClientBridge == 0) return;
-        AwContentsClientBridgeJni.get()
-                .cancelJsResult(mNativeContentsClientBridge, AwContentsClientBridge.this, id);
+        AwContentsClientBridgeJni.get().cancelJsResult(mNativeContentsClientBridge, id);
     }
 
     @NativeMethods
     interface Natives {
         void takeSafeBrowsingAction(
-                long nativeAwContentsClientBridge,
-                AwContentsClientBridge caller,
-                int action,
-                boolean reporting,
-                int requestId);
+                long nativeAwContentsClientBridge, int action, boolean reporting, int requestId);
 
-        void proceedSslError(
-                long nativeAwContentsClientBridge,
-                AwContentsClientBridge caller,
-                boolean proceed,
-                int id);
+        void proceedSslError(long nativeAwContentsClientBridge, boolean proceed, int id);
 
         void provideClientCertificateResponse(
                 long nativeAwContentsClientBridge,
-                AwContentsClientBridge caller,
                 int id,
                 byte[][] certChain,
                 PrivateKey androidKey);
 
         void confirmJsResult(
                 long nativeAwContentsClientBridge,
-                AwContentsClientBridge caller,
                 int id,
-                String prompt);
+                @JniType("std::optional<std::u16string>") @Nullable String prompt);
 
-        void cancelJsResult(
-                long nativeAwContentsClientBridge, AwContentsClientBridge caller, int id);
+        void cancelJsResult(long nativeAwContentsClientBridge, int id);
     }
 }
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 c45b391f..a3ae3a5 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
@@ -457,10 +457,6 @@
                 "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(
-                BlinkFeatures.THREADED_SCROLL_PREVENT_RENDERING_STARVATION,
-                "Enable rendering starvation-prevention during threaded scrolling."
-                        + " See https://crbug.com/40833407."),
-        Flag.baseFeature(
                 BlinkFeatures.VIEW_TRANSITION_ON_NAVIGATION,
                 "Enables the experimental View Transitions API for navigations."
                         + " See https://github.com/WICG/view-transitions/blob/main/explainer.md."),
diff --git a/android_webview/lib/DEPS b/android_webview/lib/DEPS
index 14b7bad..4cbb375 100644
--- a/android_webview/lib/DEPS
+++ b/android_webview/lib/DEPS
@@ -10,6 +10,7 @@
   "+components/viz/common",
   "+content/public",
   "+device/base/features.h",
+  "+services/tracing/public/cpp/perfetto",
   "+gin/public",
   "+gin/v8_initializer.h",
   "+mojo/core/embedder/embedder.h",
diff --git a/android_webview/lib/aw_main_delegate.cc b/android_webview/lib/aw_main_delegate.cc
index 999cb36..95aae9e 100644
--- a/android_webview/lib/aw_main_delegate.cc
+++ b/android_webview/lib/aw_main_delegate.cc
@@ -65,6 +65,7 @@
 #include "gpu/config/gpu_finch_features.h"
 #include "media/media_buildflags.h"
 #include "net/base/features.h"
+#include "services/tracing/public/cpp/perfetto/track_name_recorder.h"
 #include "third_party/blink/public/common/features.h"
 #include "third_party/blink/public/common/switches.h"
 #include "tools/v8_context_snapshot/buildflags.h"
@@ -219,7 +220,7 @@
       base::BindRepeating(&IsTraceEventArgsAllowlisted));
   base::trace_event::TraceLog::GetInstance()->SetMetadataFilterPredicate(
       base::BindRepeating(&IsTraceMetadataAllowlisted));
-  base::trace_event::TraceLog::GetInstance()->SetRecordHostAppPackageName(true);
+  tracing::TrackNameRecorder::GetInstance()->SetRecordHostAppPackageName(true);
 
   // The TLS slot used by the memlog allocator shim needs to be initialized
   // early to ensure that it gets assigned a low slot number. If it gets
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 030b86a..e3d804b2 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
@@ -2,7 +2,6 @@
 [INTERFACES]
 interface AI
     attribute @@toStringTag
-    getter languageDetector
     getter languageModel
     method constructor
 interface AICreateMonitor : EventTarget
@@ -10,11 +9,6 @@
     getter ondownloadprogress
     method constructor
     setter ondownloadprogress
-interface AILanguageDetectorFactory
-    attribute @@toStringTag
-    method availability
-    method constructor
-    method create
 interface AILanguageModel : EventTarget
     attribute @@toStringTag
     getter inputQuota
@@ -5564,6 +5558,8 @@
     setter pseudoElement
     setter target
 interface LanguageDetector
+    static method availability
+    static method create
     attribute @@toStringTag
     getter inputQuota
     method constructor
@@ -6373,10 +6369,8 @@
     getter signal
     getter sourceElement
     getter userInitiated
-    method commit
     method constructor
     method intercept
-    method redirect
     method scroll
 interface Navigation : EventTarget
     attribute @@toStringTag
@@ -6432,6 +6426,10 @@
     method constructor
     method getState
     setter ondispose
+interface NavigationPrecommitController
+    attribute @@toStringTag
+    method constructor
+    method redirect
 interface NavigationPreloadManager
     attribute @@toStringTag
     method constructor
diff --git a/android_webview/unittestjava/src/org/chromium/android_webview/unittest/MockAwContentsClientBridge.java b/android_webview/unittestjava/src/org/chromium/android_webview/unittest/MockAwContentsClientBridge.java
index 402c4aa..93da3ae 100644
--- a/android_webview/unittestjava/src/org/chromium/android_webview/unittest/MockAwContentsClientBridge.java
+++ b/android_webview/unittestjava/src/org/chromium/android_webview/unittest/MockAwContentsClientBridge.java
@@ -5,10 +5,12 @@
 package org.chromium.android_webview.unittest;
 
 import org.jni_zero.CalledByNative;
+import org.jni_zero.JniType;
 
 import org.chromium.android_webview.AwContentsClientBridge;
 import org.chromium.android_webview.ClientCertLookupTable;
 
+/** Used by android_webview/browser/aw_contents_client_bridge_unittest.cc */
 class MockAwContentsClientBridge extends AwContentsClientBridge {
 
     private int mId;
@@ -35,7 +37,7 @@
     }
 
     @CalledByNative
-    private String[] getKeyTypes() {
+    private @JniType("std::vector<std::string>") String[] getKeyTypes() {
         return mKeyTypes;
     }
 
diff --git a/ash/boca/on_task/on_task_pod_controller.h b/ash/boca/on_task/on_task_pod_controller.h
index 24f0b1f..2914099 100644
--- a/ash/boca/on_task/on_task_pod_controller.h
+++ b/ash/boca/on_task/on_task_pod_controller.h
@@ -34,6 +34,9 @@
   // Attempts to show or hide the tab strip.
   virtual void ToggleTabStripVisibility(bool show) = 0;
 
+  // Notifies pod widget when the app is paused or unpaused.
+  virtual void OnPauseModeChanged() = 0;
+
   // Notifies pod widget when there is an update in the page navigation context
   // (tab switch, URL navigation, etc.).
   virtual void OnPageNavigationContextChanged() = 0;
diff --git a/ash/boca/on_task/on_task_pod_view_unittest.cc b/ash/boca/on_task/on_task_pod_view_unittest.cc
index dd7e8b5..bbac0d3bb 100644
--- a/ash/boca/on_task/on_task_pod_view_unittest.cc
+++ b/ash/boca/on_task/on_task_pod_view_unittest.cc
@@ -32,6 +32,7 @@
               SetSnapLocation,
               (OnTaskPodSnapLocation snap_location),
               (override));
+  MOCK_METHOD(void, OnPauseModeChanged, (), (override));
   MOCK_METHOD(void, OnPageNavigationContextChanged, (), (override));
   MOCK_METHOD(bool, CanNavigateToPreviousPage, (), (override));
   MOCK_METHOD(bool, CanNavigateToNextPage, (), (override));
diff --git a/ash/constants/ash_features.cc b/ash/constants/ash_features.cc
index 905e30d..4edc381 100644
--- a/ash/constants/ash_features.cc
+++ b/ash/constants/ash_features.cc
@@ -1643,7 +1643,7 @@
 // S3 USM_RNNT model.
 BASE_FEATURE(kInternalServerSideSpeechRecognitionUSMModelFinch,
              "InternalServerSideSpeechRecognitionUSMModelFinch",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 
 // Enables sending `client-info` values to IPP printers on ChromeOS.
 BASE_FEATURE(kIppClientInfo, "IppClientInfo", base::FEATURE_ENABLED_BY_DEFAULT);
@@ -2479,7 +2479,7 @@
 // Controls whether to use USM for serverside speech recognition for projector.
 BASE_FEATURE(kProjectorUseUSMForS3,
              "ProjectorUseUSMForS3",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 
 // controls whether projector uses dynamic colors.
 BASE_FEATURE(kProjectorDynamicColors,
@@ -2625,6 +2625,11 @@
              "SeaPenTextInput",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
+// Enables sea pen text input translation feature.
+BASE_FEATURE(kSeaPenTextInputTranslation,
+             "SeaPenTextInputTranslation",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 // Enables sea pen feature for ChromeOS demo mode.
 BASE_FEATURE(kSeaPenDemoMode,
              "SeaPenDemoMode",
@@ -2633,7 +2638,7 @@
 // Enables sea pen prompt rewrite feature.
 BASE_FEATURE(kSeaPenQueryRewrite,
              "SeaPenQueryRewrite",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 
 // Enables sea pen feature with next templates.
 BASE_FEATURE(kSeaPenUseExptTemplate,
@@ -4437,6 +4442,11 @@
   return IsSeaPenEnabled() && base::FeatureList::IsEnabled(kSeaPenTextInput);
 }
 
+bool IsSeaPenTextInputTranslationEnabled() {
+  return IsSeaPenTextInputEnabled() &&
+         base::FeatureList::IsEnabled(kSeaPenTextInputTranslation);
+}
+
 bool IsSeaPenUseExptTemplateEnabled() {
   return IsSeaPenEnabled() &&
          base::FeatureList::IsEnabled(kSeaPenUseExptTemplate);
diff --git a/ash/constants/ash_features.h b/ash/constants/ash_features.h
index 140c26a..2c36844 100644
--- a/ash/constants/ash_features.h
+++ b/ash/constants/ash_features.h
@@ -860,6 +860,8 @@
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kSeaPenDemoMode);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kSeaPenQueryRewrite);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kSeaPenTextInput);
+COMPONENT_EXPORT(ASH_CONSTANTS)
+BASE_DECLARE_FEATURE(kSeaPenTextInputTranslation);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kSeaPenUseExptTemplate);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kSeparateNetworkIcons);
 COMPONENT_EXPORT(ASH_CONSTANTS)
@@ -1354,6 +1356,7 @@
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsSeaPenQueryRewriteEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsSeaPenEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsSeaPenTextInputEnabled();
+COMPONENT_EXPORT(ASH_CONSTANTS) bool IsSeaPenTextInputTranslationEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsSeaPenUseExptTemplateEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsSeparateNetworkIconsEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsSeparateWebAppShortcutBadgeIconEnabled();
diff --git a/ash/lobster/lobster_candidate_store_unittest.cc b/ash/lobster/lobster_candidate_store_unittest.cc
index 1b0863f..fd315ffc 100644
--- a/ash/lobster/lobster_candidate_store_unittest.cc
+++ b/ash/lobster/lobster_candidate_store_unittest.cc
@@ -32,32 +32,42 @@
 TEST_F(LobsterCandidateStoreTest, CanFindImageCandidateAfterCaching) {
   LobsterCandidateStore store;
 
-  store.Cache({.id = 123,
-               .image_bytes = "a1b2c3",
-               .seed = 10001,
-               .query = "a sunny day"});
-  store.Cache({.id = 456,
-               .image_bytes = "a4b5c6",
-               .seed = 10004,
-               .query = "a windy weekday"});
+  store.Cache(
+      LobsterImageCandidate(/*id=*/123,
+                            /*image_bytes=*/"a1b2c3",
+                            /*seed=*/10001,
+                            /*query=*/"a sunny day",
+                            /*rewritten_query=*/"rewritten: a sunny day"));
+  store.Cache(
+      LobsterImageCandidate(/*id=*/456,
+                            /*image_bytes=*/"a4b5c6",
+                            /*seed=*/10004,
+                            /*query=*/"a windy weekday",
+                            /*rewritten_query=*/"rewritten: a windy weekday"));
 
   EXPECT_EQ(store.FindCandidateById(123).value(),
-            LobsterImageCandidate(123, "a1b2c3", 10001, "a sunny day"));
+            LobsterImageCandidate(123, "a1b2c3", 10001, "a sunny day",
+                                  "rewritten: a sunny day"));
   EXPECT_EQ(store.FindCandidateById(456).value(),
-            LobsterImageCandidate(456, "a4b5c6", 10004, "a windy weekday"));
+            LobsterImageCandidate(456, "a4b5c6", 10004, "a windy weekday",
+                                  "rewritten: a windy weekday"));
 }
 
 TEST_F(LobsterCandidateStoreTest, ReturnsEmptyCandidateIfIdDoesNotMatch) {
   LobsterCandidateStore store;
 
-  store.Cache({.id = 123,
-               .image_bytes = "a1b2c3",
-               .seed = 10001,
-               .query = "a sunny day"});
-  store.Cache({.id = 456,
-               .image_bytes = "a4b5c6",
-               .seed = 10004,
-               .query = "a windy weekday"});
+  store.Cache(
+      LobsterImageCandidate(/*id=*/123,
+                            /*image_bytes=*/"a1b2c3",
+                            /*seed=*/10001,
+                            /*query=*/"a sunny day",
+                            /*rewritten_query=*/"rewritten: a sunny day"));
+  store.Cache(
+      LobsterImageCandidate(/*id=*/456,
+                            /*image_bytes=*/"a4b5c6",
+                            /*seed=*/10004,
+                            /*query=*/"a windy weekday",
+                            /*rewritten_query=*/"rewritten: a windy weekday"));
 
   EXPECT_FALSE(store.FindCandidateById(120).has_value());
   EXPECT_FALSE(store.FindCandidateById(505).has_value());
@@ -66,25 +76,33 @@
 TEST_F(LobsterCandidateStoreTest, CacheOverridesPreviouslyCachedCandidate) {
   LobsterCandidateStore store;
 
-  store.Cache({.id = 123,
-               .image_bytes = "a1b2c3",
-               .seed = 10001,
-               .query = "a sunny day"});
-  store.Cache({.id = 456,
-               .image_bytes = "a4b5c6",
-               .seed = 10004,
-               .query = "a windy weekday"});
+  store.Cache(
+      LobsterImageCandidate(/*id=*/123,
+                            /*image_bytes=*/"a1b2c3",
+                            /*seed=*/10001,
+                            /*query=*/"a sunny day",
+                            /*rewritten_query=*/"rewritten: a sunny day"));
+  store.Cache(
+      LobsterImageCandidate(/*id=*/456,
+                            /*image_bytes=*/"a4b5c6",
+                            /*seed=*/10004,
+                            /*query=*/"a windy weekday",
+                            /*rewritten_query=*/"rewritten: a windy weekday"));
 
   EXPECT_EQ(store.FindCandidateById(123).value(),
-            LobsterImageCandidate(123, "a1b2c3", 10001, "a sunny day"));
+            LobsterImageCandidate(123, "a1b2c3", 10001, "a sunny day",
+                                  "rewritten: a sunny day"));
 
-  store.Cache({.id = 123,
-               .image_bytes = "x8y9z0",
-               .seed = 10011,
-               .query = "a starry night"});
+  store.Cache(
+      LobsterImageCandidate(/*id=*/123,
+                            /*image_bytes=*/"x8y9z0",
+                            /*seed=*/10011,
+                            /*query=*/"a starry night",
+                            /*rewritten_query=*/"rewritten: a starry night"));
 
   EXPECT_EQ(store.FindCandidateById(123).value(),
-            LobsterImageCandidate(123, "x8y9z0", 10011, "a starry night"));
+            LobsterImageCandidate(123, "x8y9z0", 10011, "a starry night",
+                                  "rewritten: a starry night"));
 }
 
 }  // namespace
diff --git a/ash/lobster/lobster_session_impl.cc b/ash/lobster/lobster_session_impl.cc
index dab95b3..0834542 100644
--- a/ash/lobster/lobster_session_impl.cc
+++ b/ash/lobster/lobster_session_impl.cc
@@ -237,7 +237,9 @@
   }
 
   client_->InflateCandidate(
-      candidate->seed, candidate->query,
+      candidate->seed,
+      ash::features::IsLobsterUseRewrittenQuery() ? candidate->rewritten_query
+                                                  : candidate->user_query,
       base::BindOnce(
           [](LobsterClient* lobster_client,
              LobsterImageDownloadActuator* actuator,
@@ -252,7 +254,7 @@
 
             const LobsterImageCandidate& image_candidate = (*result)[0];
             actuator->WriteImageToPath(
-                download_dir, image_candidate.query, image_candidate.id,
+                download_dir, image_candidate.user_query, image_candidate.id,
                 image_candidate.image_bytes,
                 base::BindOnce(
                     [](StatusCallback status_callback,
@@ -304,7 +306,9 @@
   }
 
   client_->InflateCandidate(
-      candidate->seed, candidate->query,
+      candidate->seed,
+      ash::features::IsLobsterUseRewrittenQuery() ? candidate->rewritten_query
+                                                  : candidate->user_query,
       base::BindOnce(
           [](LobsterClient* lobster_client, StatusCallback status_callback,
              const LobsterResult& result) {
@@ -352,7 +356,9 @@
   }
 
   client_->InflateCandidate(
-      candidate->seed, candidate->query,
+      candidate->seed,
+      ash::features::IsLobsterUseRewrittenQuery() ? candidate->rewritten_query
+                                                  : candidate->user_query,
       base::BindOnce(
           [](LobsterClient* lobster_client,
              LobsterImageDownloadActuator* actuator,
@@ -367,7 +373,7 @@
 
             const LobsterImageCandidate& image_candidate = (*result)[0];
             actuator->WriteImageToPath(
-                download_dir, image_candidate.query, image_candidate.id,
+                download_dir, image_candidate.user_query, image_candidate.id,
                 image_candidate.image_bytes,
                 base::BindOnce(
                     [](LobsterClient* lobster_client,
@@ -409,7 +415,7 @@
   }
 
   std::move(callback).Run(LobsterFeedbackPreview(
-      {{"Query and image", candidate->query}}, candidate->image_bytes));
+      {{"Query and image", candidate->user_query}}, candidate->image_bytes));
 }
 
 bool LobsterSessionImpl::SubmitFeedback(int candidate_id,
@@ -422,7 +428,7 @@
   // Submit feedback along with the preview image.
   // TODO: b/362403784 - add the proper version.
   std::string feedback_description = BuildFeedbackDescription(
-      candidate->query, /*model_version=*/"dummy_version", description);
+      candidate->user_query, /*model_version=*/"dummy_version", description);
 
   return Shell::Get()->shell_delegate()->SendSpecializedFeatureFeedback(
       client_->GetAccountId(), feedback::kLobsterFeedbackProductId,
diff --git a/ash/lobster/lobster_session_impl_unittest.cc b/ash/lobster/lobster_session_impl_unittest.cc
index 05cdad9..8d44758a 100644
--- a/ash/lobster/lobster_session_impl_unittest.cc
+++ b/ash/lobster/lobster_session_impl_unittest.cc
@@ -41,14 +41,18 @@
 
 LobsterCandidateStore GetDummyLobsterCandidateStore() {
   LobsterCandidateStore store;
-  store.Cache({.id = 0,
-               .image_bytes = "a1b2c3",
-               .seed = 20,
-               .query = "a nice raspberry"});
-  store.Cache({.id = 1,
-               .image_bytes = "d4e5f6",
-               .seed = 21,
-               .query = "a nice raspberry"});
+  store.Cache(
+      LobsterImageCandidate(/*id=*/0,
+                            /*image_bytes=*/"a1b2c3",
+                            /*seed=*/20,
+                            /*user_query=*/"a nice raspberry",
+                            /*rewritten_query=*/"rewritten: a nice raspberry"));
+  store.Cache(
+      LobsterImageCandidate(/*id=*/1,
+                            /*image_bytes=*/"d4e5f6",
+                            /*seed=*/21,
+                            /*query=*/"a nice raspberry",
+                            /*rewritten_query=*/"rewritten: a nice raspberry"));
 
   return store;
 }
@@ -144,15 +148,21 @@
       .WillOnce(testing::Invoke([](std::string_view query, int num_candidates,
                                    RequestCandidatesCallback done_callback) {
         std::vector<LobsterImageCandidate> image_candidates = {
-            LobsterImageCandidate(/*id=*/0, /*image_bytes=*/"a1b2c3",
-                                  /*seed=*/20,
-                                  /*query=*/"a nice strawberry"),
-            LobsterImageCandidate(/*id=*/1, /*image_bytes=*/"d4e5f6",
-                                  /*seed=*/21,
-                                  /*query=*/"a nice strawberry"),
-            LobsterImageCandidate(/*id=*/2, /*image_bytes=*/"g7h8i9",
-                                  /*seed=*/22,
-                                  /*query=*/"a nice strawberry")};
+            LobsterImageCandidate(
+                /*id=*/0, /*image_bytes=*/"a1b2c3",
+                /*seed=*/20,
+                /*user_query=*/"a nice strawberry",
+                /*rewritten_query=*/"rewritten: a nice strawberry"),
+            LobsterImageCandidate(
+                /*id=*/1, /*image_bytes=*/"d4e5f6",
+                /*seed=*/21,
+                /*user_query=*/"a nice strawberry",
+                /*rewritten_query=*/"rewritten: a nice strawberry"),
+            LobsterImageCandidate(
+                /*id=*/2, /*image_bytes=*/"g7h8i9",
+                /*seed=*/22,
+                /*user_query=*/"a nice strawberry",
+                /*rewritten_query=*/"rewritten: a nice strawberry")};
         std::move(done_callback).Run(std::move(image_candidates));
       }));
 
@@ -165,18 +175,23 @@
   session.RequestCandidates(/*query=*/"a nice strawberry", /*num_candidates=*/3,
                             future.GetCallback());
 
-  EXPECT_THAT(
-      future.Get().value(),
-      testing::ElementsAre(
-          LobsterImageCandidate(/*expected_id=*/0,
-                                /*expected_image_bytes=*/"a1b2c3",
-                                /*seed=*/20, /*query=*/"a nice strawberry"),
-          LobsterImageCandidate(/*expected_id=*/1,
-                                /*expected_image_bytes=*/"d4e5f6",
-                                /*seed=*/21, /*query=*/"a nice strawberry"),
-          LobsterImageCandidate(/*expected_id=*/2,
-                                /*expected_image_bytes=*/"g7h8i9",
-                                /*seed=*/22, /*query=*/"a nice strawberry")));
+  EXPECT_THAT(future.Get().value(),
+              testing::ElementsAre(
+                  LobsterImageCandidate(
+                      /*expected_id=*/0,
+                      /*expected_image_bytes=*/"a1b2c3",
+                      /*seed=*/20, /*user_query=*/"a nice strawberry",
+                      /*rewritten_query=*/"rewritten: a nice strawberry"),
+                  LobsterImageCandidate(
+                      /*expected_id=*/1,
+                      /*expected_image_bytes=*/"d4e5f6",
+                      /*seed=*/21, /*user_query=*/"a nice strawberry",
+                      /*rewritten_query=*/"rewritten: a nice strawberry"),
+                  LobsterImageCandidate(
+                      /*expected_id=*/2,
+                      /*expected_image_bytes=*/"g7h8i9",
+                      /*seed=*/22, /*user_query=*/"a nice strawberry",
+                      /*rewritten_query=*/"rewritten: a nice strawberry")));
 }
 
 TEST_F(LobsterSessionImplTest, RequestCandidatesReturnsUnknownError) {
@@ -228,7 +243,8 @@
         std::vector<LobsterImageCandidate> inflated_candidates = {
             LobsterImageCandidate(/*id=*/1, /*image_bytes=*/"a1b2c3",
                                   /*seed=*/30,
-                                  /*query=*/"a nice strawberry")};
+                                  /*user_query=*/"a nice strawberry",
+                                  /*rewritten_query=*/"a nice strawberry")};
         std::move(done_callback).Run(std::move(inflated_candidates));
       });
 
@@ -455,7 +471,8 @@
         std::vector<LobsterImageCandidate> inflated_candidates = {
             LobsterImageCandidate(/*id=*/1, /*image_bytes=*/"a1b2c3",
                                   /*seed=*/31,
-                                  /*query=*/"a nice strawberry")};
+                                  /*user_query=*/"a nice strawberry",
+                                  /*rewritten_query=*/"a nice strawberry")};
         std::move(done_callback).Run(std::move(inflated_candidates));
       });
 
@@ -517,7 +534,8 @@
         std::vector<LobsterImageCandidate> inflated_candidates = {
             LobsterImageCandidate(/*id=*/1, /*image_bytes=*/"a1b2c3",
                                   /*seed=*/21,
-                                  /*query=*/"a nice strawberry")};
+                                  /*user_query=*/"a nice strawberry",
+                                  /*rewritten_query=*/"a nice strawberry")};
         std::move(done_callback).Run(std::move(inflated_candidates));
       });
 
@@ -583,7 +601,8 @@
         std::vector<LobsterImageCandidate> inflated_candidates = {
             LobsterImageCandidate(/*id=*/1, /*image_bytes=*/"a1b2c3",
                                   /*seed=*/21,
-                                  /*query=*/"a nice strawberry")};
+                                  /*user_query=*/"a nice strawberry",
+                                  /*rewritten_query=*/"a nice strawberry")};
         std::move(done_callback).Run(std::move(inflated_candidates));
       });
 
diff --git a/ash/public/cpp/BUILD.gn b/ash/public/cpp/BUILD.gn
index 8f41769..de9698da 100644
--- a/ash/public/cpp/BUILD.gn
+++ b/ash/public/cpp/BUILD.gn
@@ -205,6 +205,7 @@
     "lobster/lobster_enums.h",
     "lobster/lobster_feedback_preview.cc",
     "lobster/lobster_feedback_preview.h",
+    "lobster/lobster_image_candidate.cc",
     "lobster/lobster_image_candidate.h",
     "lobster/lobster_image_download_response.h",
     "lobster/lobster_metrics_state_enums.h",
diff --git a/ash/public/cpp/lobster/lobster_image_candidate.cc b/ash/public/cpp/lobster/lobster_image_candidate.cc
new file mode 100644
index 0000000..230cf17
--- /dev/null
+++ b/ash/public/cpp/lobster/lobster_image_candidate.cc
@@ -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.
+
+#include "ash/public/cpp/lobster/lobster_image_candidate.h"
+
+namespace ash {
+
+LobsterImageCandidate::LobsterImageCandidate(uint32_t id,
+                                             const std::string& image_bytes,
+                                             uint32_t seed,
+                                             const std::string& user_query,
+                                             const std::string& rewritten_query)
+    : id(id),
+      image_bytes(image_bytes),
+      seed(seed),
+      user_query(user_query),
+      rewritten_query(rewritten_query) {}
+
+LobsterImageCandidate::LobsterImageCandidate() = default;
+
+LobsterImageCandidate& LobsterImageCandidate::operator=(
+    const LobsterImageCandidate&) = default;
+
+LobsterImageCandidate::LobsterImageCandidate(const LobsterImageCandidate&) =
+    default;
+
+}  // namespace ash
diff --git a/ash/public/cpp/lobster/lobster_image_candidate.h b/ash/public/cpp/lobster/lobster_image_candidate.h
index 1550fab..566a93a 100644
--- a/ash/public/cpp/lobster/lobster_image_candidate.h
+++ b/ash/public/cpp/lobster/lobster_image_candidate.h
@@ -18,11 +18,26 @@
   uint32_t id;
   std::string image_bytes;
   uint32_t seed;
-  std::string query;
+  std::string user_query;
+  // In case Query Rewritten feature is enabled, the `rewritten_query` is
+  // returned by the server. Otherwise, it will be the same as the `user_query`.
+  std::string rewritten_query;
+
+  LobsterImageCandidate(uint32_t id,
+                        const std::string& image_bytes,
+                        uint32_t seed,
+                        const std::string& user_query,
+                        const std::string& rewritten_query);
+
+  LobsterImageCandidate();
+
+  LobsterImageCandidate(const LobsterImageCandidate&);
+  LobsterImageCandidate& operator=(const LobsterImageCandidate&);
 
   bool operator==(const LobsterImageCandidate& other) const {
     return id == other.id && image_bytes == other.image_bytes &&
-           seed == other.seed && query == other.query;
+           seed == other.seed && user_query == other.user_query &&
+           rewritten_query == other.rewritten_query;
   }
 };
 
diff --git a/ash/wm/desks/templates/saved_desk_unittest.cc b/ash/wm/desks/templates/saved_desk_unittest.cc
index 937fa21..5afa07a 100644
--- a/ash/wm/desks/templates/saved_desk_unittest.cc
+++ b/ash/wm/desks/templates/saved_desk_unittest.cc
@@ -1888,38 +1888,6 @@
   EXPECT_FALSE(GetSaveDeskButtonContainerForRoot(root));
 }
 
-// Tests that the library buttons and save desk buttons are hidden when
-// transitioning from clamshell to tablet mode.
-TEST_F(SavedDeskTest, ClamshellToTabletModeOld) {
-  base::test::ScopedFeatureList disable;
-  disable.InitAndDisableFeature(features::kForestFeature);
-
-  // Create a window and add a test entry. Otherwise the templates UI wouldn't
-  // show up.
-  auto test_window_1 = CreateAppWindow();
-  AddEntry(base::Uuid::GenerateRandomV4(), "template", base::Time::Now(),
-           DeskTemplateType::kTemplate);
-
-  // Test that on entering overview, the zero state desks templates button and
-  // the save template button are visible.
-  ToggleOverview();
-  aura::Window* root = Shell::GetPrimaryRootWindow();
-  auto* desks_bar_view = GetDesksBarViewForRoot(root);
-  ASSERT_TRUE(desks_bar_view);
-  auto* library_button = GetLibraryButtonForRoot(root);
-  ASSERT_TRUE(library_button);
-  EXPECT_TRUE(library_button->GetVisible());
-  EXPECT_EQ(DeskIconButton::State::kZero, library_button->state());
-  EXPECT_TRUE(OverviewGridTestApi(root).IsSaveDeskAsTemplateButtonVisible());
-
-  // Tests that after transitioning, we remain in overview mode and all the
-  // buttons are invisible.
-  EnterTabletMode();
-  ASSERT_TRUE(GetOverviewSession());
-  EXPECT_FALSE(library_button->GetVisible());
-  EXPECT_FALSE(OverviewGridTestApi(root).IsSaveDeskAsTemplateButtonVisible());
-}
-
 // Tests that the library button and save desk options in the desk context menu
 // are hidden when transitioning from clamshell to tablet mode.
 TEST_F(SavedDeskTest, ClamshellToTabletMode) {
diff --git a/ash/wm/overview/overview_grid_unittest.cc b/ash/wm/overview/overview_grid_unittest.cc
index 76d4767..1ca24838 100644
--- a/ash/wm/overview/overview_grid_unittest.cc
+++ b/ash/wm/overview/overview_grid_unittest.cc
@@ -36,366 +36,13 @@
 
 namespace ash {
 
-class OverviewGridTest : public AshTestBase {
+class OverviewGridTest : public OverviewTestBase {
  public:
-  OverviewGridTest() {
-    scoped_feature_list_.InitWithFeatures(
-        /*enabled_features=*/{},
-        /*disabled_features=*/{features::kForestFeature});
-  }
-
+  OverviewGridTest() = default;
   OverviewGridTest(const OverviewGridTest&) = delete;
   OverviewGridTest& operator=(const OverviewGridTest&) = delete;
-
   ~OverviewGridTest() override = default;
 
-  // testing::Test:
-  void TearDown() override {
-    // The `grid_` must be destroyed before `WindowTreeHostManager` (which is
-    // destroyed within `AshTestBase::TearDown()`), or there are dangling
-    // `raw_ptr` failures to the root window(s). This also reflects the
-    // real-world destruction order, since `OverviewController` (which owns the
-    // grid) is destroyed before `WindowTreeHostManager` when the `Shell` is
-    // shut down.
-    grid_.reset();
-    AshTestBase::TearDown();
-  }
-
-  void InitializeGrid(
-      const std::vector<raw_ptr<aura::Window, VectorExperimental>>& windows) {
-    aura::Window* root = Shell::GetPrimaryRootWindow();
-    grid_ = std::make_unique<OverviewGrid>(
-        root, windows, nullptr, window_occlusion_calculator_.AsWeakPtr());
-  }
-
-  void CheckAnimationStates(
-      const std::vector<raw_ptr<aura::Window, VectorExperimental>>& windows,
-      const std::vector<gfx::RectF>& target_bounds,
-      const std::vector<bool>& expected_start_animations,
-      const std::vector<bool>& expected_end_animations,
-      std::optional<size_t> selected_window_index = std::nullopt) {
-    ASSERT_EQ(windows.size(), target_bounds.size());
-    ASSERT_EQ(windows.size(), expected_start_animations.size());
-    ASSERT_EQ(windows.size(), expected_end_animations.size());
-
-    InitializeGrid(windows);
-    ASSERT_EQ(windows.size(), grid_->item_list().size());
-
-    // The default values are to animate.
-    for (const auto& item : grid_->item_list()) {
-      SCOPED_TRACE("Initial values");
-      EXPECT_TRUE(item->should_animate_when_entering());
-      EXPECT_TRUE(item->should_animate_when_exiting());
-    }
-
-    grid_->CalculateWindowListAnimationStates(
-        /*selected_item=*/nullptr, OverviewTransition::kEnter, target_bounds);
-    for (size_t i = 0; i < grid_->item_list().size(); ++i) {
-      SCOPED_TRACE("Enter animation, window " + base::NumberToString(i + 1));
-      EXPECT_EQ(expected_start_animations[i],
-                grid_->item_list()[i]->should_animate_when_entering());
-    }
-
-    for (size_t i = 0; i < grid_->item_list().size(); ++i) {
-      grid_->item_list()[i]->set_target_bounds_for_testing(target_bounds[i]);
-    }
-
-    auto* selected_item = selected_window_index
-                              ? grid_->item_list()[*selected_window_index].get()
-                              : nullptr;
-    grid_->CalculateWindowListAnimationStates(selected_item,
-                                              OverviewTransition::kExit, {});
-    for (size_t i = 0; i < grid_->item_list().size(); ++i) {
-      SCOPED_TRACE("Exit animation, window " + base::NumberToString(i + 1));
-      EXPECT_EQ(expected_end_animations[i],
-                grid_->item_list()[i]->should_animate_when_exiting());
-    }
-  }
-
-  SplitViewController* split_view_controller() {
-    return SplitViewController::Get(Shell::GetPrimaryRootWindow());
-  }
-
-  OverviewGrid* grid() { return grid_.get(); }
-
- private:
-  WindowOcclusionCalculator window_occlusion_calculator_;
-  std::unique_ptr<OverviewGrid> grid_;
-
-  base::test::ScopedFeatureList scoped_feature_list_;
-};
-
-// Tests that with only one window, we always animate.
-TEST_F(OverviewGridTest, AnimateWithSingleWindow) {
-  auto window = CreateTestWindow(gfx::Rect(100, 100));
-  CheckAnimationStates({window.get()}, {gfx::RectF(100.f, 100.f)}, {true},
-                       {true});
-}
-
-// Tests that if both the source and destination is hidden, there are no
-// animations on the second window.
-TEST_F(OverviewGridTest, SourceDestinationBothHidden) {
-  auto window1 = CreateTestWindow(gfx::Rect(400, 400));
-  auto window2 = CreateTestWindow(gfx::Rect(100, 100));
-  std::vector<gfx::RectF> target_bounds = {gfx::RectF(100.f, 100.f),
-                                           gfx::RectF(100.f, 100.f)};
-  CheckAnimationStates({window1.get(), window2.get()}, target_bounds,
-                       {true, false}, {true, false});
-}
-
-// Tests that are animations if the destination bounds are shown.
-TEST_F(OverviewGridTest, SourceHiddenDestinationShown) {
-  auto window1 = CreateTestWindow(gfx::Rect(400, 400));
-  auto window2 = CreateTestWindow(gfx::Rect(100, 100));
-  std::vector<gfx::RectF> target_bounds = {
-      gfx::RectF(100.f, 100.f), gfx::RectF(400.f, 400.f, 100.f, 100.f)};
-  CheckAnimationStates({window1.get(), window2.get()}, target_bounds,
-                       {true, true}, {true, true});
-}
-
-// Tests that are animations if the source bounds are shown.
-TEST_F(OverviewGridTest, SourceShownDestinationHidden) {
-  auto window1 = CreateTestWindow(gfx::Rect(100, 100));
-  auto window2 = CreateTestWindow(gfx::Rect(400, 400));
-  std::vector<gfx::RectF> target_bounds = {gfx::RectF(100.f, 100.f),
-                                           gfx::RectF(100.f, 100.f)};
-  CheckAnimationStates({window1.get(), window2.get()}, target_bounds,
-                       {true, true}, {true, true});
-}
-
-// Tests that a window that is in the union of two other windows, but is still
-// shown will be animated.
-TEST_F(OverviewGridTest, SourceShownButInTheUnionOfTwoOtherWindows) {
-  // Create three windows, the union of the first two windows will be
-  // gfx::Rect(0,0,200,200). Window 3 will be in that union, but should still
-  // animate since its not fully occluded.
-  auto window1 = CreateTestWindow(gfx::Rect(100, 100));
-  auto window2 = CreateTestWindow(gfx::Rect(50, 50, 150, 150));
-  auto window3 = CreateTestWindow(gfx::Rect(50, 200));
-  std::vector<gfx::RectF> target_bounds = {gfx::RectF(100.f, 100.f),
-                                           gfx::RectF(100.f, 100.f),
-                                           gfx::RectF(100.f, 100.f)};
-  CheckAnimationStates({window1.get(), window2.get(), window3.get()},
-                       target_bounds, {true, true, true}, {true, true, true});
-}
-
-// Tests that an always on top window will take precedence over a normal
-// window.
-TEST_F(OverviewGridTest, AlwaysOnTopWindow) {
-  // Create two windows, the second is always on top and covers the first
-  // window. So the first window will not animate.
-  auto window1 = CreateTestWindow(gfx::Rect(100, 100));
-  auto window2 = CreateTestWindow(gfx::Rect(400, 400));
-  window2->SetProperty(aura::client::kZOrderingKey,
-                       ui::ZOrderLevel::kFloatingWindow);
-  std::vector<gfx::RectF> target_bounds = {gfx::RectF(100.f, 100.f),
-                                           gfx::RectF(100.f, 100.f)};
-  CheckAnimationStates({window1.get(), window2.get()}, target_bounds,
-                       {false, true}, {false, true});
-}
-
-// Tests that windows that are minimized are animated as expected.
-TEST_F(OverviewGridTest, MinimizedWindows) {
-  // Create 3 windows with the second and third windows being minimized. Both
-  // the minimized window bounds are not occluded but only the third window is
-  // animated because the target bounds for the first window is blocked.
-  auto window1 = CreateTestWindow(gfx::Rect(100, 100));
-  auto window2 = CreateTestWindow(gfx::Rect(400, 400));
-  auto window3 = CreateTestWindow(gfx::Rect(400, 400));
-  WindowState::Get(window2.get())->Minimize();
-  WindowState::Get(window3.get())->Minimize();
-  std::vector<gfx::RectF> target_bounds = {gfx::RectF(100.f, 100.f),
-                                           gfx::RectF(100.f, 100.f),
-                                           gfx::RectF(200.f, 200.f)};
-  CheckAnimationStates({window1.get(), window2.get(), window3.get()},
-                       target_bounds, {true, false, true}, {true, false, true});
-}
-
-TEST_F(OverviewGridTest, SelectedWindow) {
-  // Create 3 windows with the third window being maximized. All windows are
-  // visible on entering, so they should all be animated. On exit we select the
-  // third window which is maximized, so the other two windows should not
-  // animate.
-  auto window1 = CreateTestWindow(gfx::Rect(100, 100));
-  auto window2 = CreateTestWindow(gfx::Rect(400, 400));
-  auto window3 = CreateTestWindow(gfx::Rect(400, 400));
-  WindowState::Get(window3.get())->Maximize();
-  std::vector<gfx::RectF> target_bounds = {gfx::RectF(100.f, 100.f),
-                                           gfx::RectF(100.f, 100.f),
-                                           gfx::RectF(100.f, 100.f)};
-  CheckAnimationStates({window1.get(), window2.get(), window3.get()},
-                       target_bounds, {true, true, true}, {false, false, true},
-                       std::make_optional(2u));
-}
-
-TEST_F(OverviewGridTest, WindowWithBackdrop) {
-  // Create one non resizable window and one normal window and verify that the
-  // backdrop shows over the non resizable window, and that normal window
-  // becomes maximized upon entering tablet mode.
-  auto window1 = CreateTestWindow(gfx::Rect(100, 100));
-  auto window2 = CreateTestWindow(gfx::Rect(400, 400));
-  window1->SetProperty(aura::client::kResizeBehaviorKey,
-                       aura::client::kResizeBehaviorNone);
-  wm::ActivateWindow(window1.get());
-
-  Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
-  BackdropController* backdrop_controller =
-      GetWorkspaceControllerForContext(window1.get())
-          ->layout_manager()
-          ->backdrop_controller();
-  EXPECT_EQ(window1.get(), backdrop_controller->GetTopmostWindowWithBackdrop());
-  EXPECT_TRUE(backdrop_controller->backdrop_window());
-  EXPECT_TRUE(WindowState::Get(window2.get())->IsMaximized());
-
-  // Tests that the second window despite being larger than the first window
-  // does not animate as it is hidden behind the backdrop. On exit, it still
-  // animates as the backdrop is not visible yet.
-  std::vector<gfx::RectF> target_bounds = {gfx::RectF(100.f, 100.f),
-                                           gfx::RectF(100.f, 100.f)};
-  CheckAnimationStates({window1.get(), window2.get()}, target_bounds,
-                       {true, false}, {true, true});
-}
-
-TEST_F(OverviewGridTest, DestinationPartiallyOffscreenWindow) {
-  UpdateDisplay("500x400");
-  auto window1 = CreateTestWindow(gfx::Rect(100, 100));
-  auto window2 = CreateTestWindow(gfx::Rect(100, 100));
-
-  // Position |window2|'s destination to be partially offscreen. Tests that it
-  // still animates because the onscreen portion is not occluded by |window1|.
-  std::vector<gfx::RectF> target_bounds = {
-      gfx::RectF(100.f, 100.f), gfx::RectF(350.f, 100.f, 100.f, 100.f)};
-  CheckAnimationStates({window1.get(), window2.get()}, target_bounds,
-                       {true, true}, {true, true});
-
-  // Maximize |window1|. |window2| should no longer animate since the parts of
-  // it that are onscreen are fully occluded.
-  WindowState::Get(window1.get())->Maximize();
-  CheckAnimationStates({window1.get(), window2.get()}, target_bounds,
-                       {true, false}, {true, false});
-}
-
-TEST_F(OverviewGridTest, SourcePartiallyOffscreenWindow) {
-  UpdateDisplay("500x400");
-  auto window1 = CreateTestWindow(gfx::Rect(100, 100));
-  // Create |window2| to be partially offscreen.
-  auto window2 = CreateTestWindow(gfx::Rect(450, 100, 100, 100));
-
-  // Tests that it still animates because the onscreen portion is not occluded
-  // by |window1|.
-  std::vector<gfx::RectF> target_bounds = {gfx::RectF(100.f, 100.f),
-                                           gfx::RectF(200.f, 200.f)};
-  CheckAnimationStates({window1.get(), window2.get()}, target_bounds,
-                       {true, true}, {true, true});
-
-  // Maximize |window1|. |window2| should no longer animate since the parts of
-  // it that are onscreen are fully occluded.
-  WindowState::Get(window1.get())->Maximize();
-  CheckAnimationStates({window1.get(), window2.get()}, target_bounds,
-                       {true, false}, {true, false});
-}
-
-// Tests that windows whose destination is fully offscreen never animate.
-TEST_F(OverviewGridTest, FullyOffscreenWindow) {
-  UpdateDisplay("500x400");
-  auto window1 = CreateTestWindow(gfx::Rect(100, 100));
-  auto window2 = CreateTestWindow(gfx::Rect(100, 100));
-
-  std::vector<gfx::RectF> target_bounds = {
-      gfx::RectF(100.f, 100.f), gfx::RectF(450.f, 450.f, 100.f, 100.f)};
-  CheckAnimationStates({window1.get(), window2.get()}, target_bounds,
-                       {true, false}, {true, false});
-
-  WindowState::Get(window1.get())->Maximize();
-  CheckAnimationStates({window1.get(), window2.get()}, target_bounds,
-                       {true, false}, {true, false});
-}
-
-// Tests that only one window animates when entering overview from splitview
-// double snapped.
-TEST_F(OverviewGridTest, SnappedWindow) {
-  auto window1 = CreateTestWindow(gfx::Rect(100, 100));
-  auto window2 = CreateTestWindow(gfx::Rect(100, 100));
-  auto window3 = CreateTestWindow(gfx::Rect(100, 100));
-  wm::ActivateWindow(window1.get());
-  wm::ActivateWindow(window2.get());
-
-  Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
-  split_view_controller()->SnapWindow(window1.get(), SnapPosition::kPrimary);
-
-  // Snap |window2| and check that |window3| is maximized.
-  split_view_controller()->SnapWindow(window2.get(), SnapPosition::kSecondary);
-  EXPECT_TRUE(WindowState::Get(window3.get())->IsMaximized());
-
-  // We cannot create a grid object like in the other tests because creating a
-  // grid calls |GetGridBoundsInScreen| with split view state both snapped which
-  // is an unnatural state.
-  EnterOverview();
-
-  // Tests that |window3| is not animated even though its bounds are larger than
-  // |window2| because it is fully occluded by |window1| + |window2| and the
-  // split view divider.
-  auto* item2 = GetOverviewItemForWindow(window2.get());
-  auto* item3 = GetOverviewItemForWindow(window3.get());
-  EXPECT_TRUE(item2->should_animate_when_entering());
-  EXPECT_FALSE(item3->should_animate_when_entering());
-}
-
-// TODO(b/350771229): Replace `OverviewGridTest` with `OverviewGridForestTest`
-// once `kForestFeature` is launched.
-TEST_F(OverviewGridTest, RecordsDelayedDeskBarPresentationMetric) {
-  ui::ScopedAnimationDurationScaleMode animation_scale(
-      ui::ScopedAnimationDurationScaleMode::FAST_DURATION);
-
-  // Since the windows are not maximized, the desk bar should open after
-  // the overview animation is complete, causing
-  // `kOverviewDelayedDeskBarPresentationHistogram` to be recorded.
-  std::unique_ptr<aura::Window> window1(CreateTestWindow());
-  std::unique_ptr<aura::Window> window2(CreateTestWindow());
-
-  ui::Compositor* const compositor = window1->GetHost()->compositor();
-  base::HistogramTester histogram_tester;
-  ToggleOverview();
-  ASSERT_TRUE(ui::WaitForNextFrameToBePresented(compositor));
-  histogram_tester.ExpectTotalCount(
-      kOverviewDelayedDeskBarPresentationHistogram, 0);
-  WaitForOverviewEnterAnimation();
-  ASSERT_TRUE(ui::WaitForNextFrameToBePresented(compositor));
-  histogram_tester.ExpectTotalCount(
-      kOverviewDelayedDeskBarPresentationHistogram, 1);
-}
-
-// TODO(b/350771229): Replace `OverviewGridTest` with `OverviewGridForestTest`
-// once `kForestFeature` is launched.
-TEST_F(OverviewGridTest, DoesNotRecordDelayedDeskBarPresentationMetric) {
-  ui::ScopedAnimationDurationScaleMode animation_scale(
-      ui::ScopedAnimationDurationScaleMode::FAST_DURATION);
-
-  // Since the windows are maximized, the desk bar should open immediately when
-  // we enter overview and `kOverviewDelayedDeskBarPresentationHistogram` should
-  // not be recorded.
-  std::unique_ptr<aura::Window> window1(CreateTestWindow());
-  std::unique_ptr<aura::Window> window2(CreateTestWindow());
-  WindowState::Get(window1.get())->Maximize();
-  WindowState::Get(window2.get())->Maximize();
-
-  ui::Compositor* const compositor = window1->GetHost()->compositor();
-  base::HistogramTester histogram_tester;
-  ToggleOverview();
-  ASSERT_TRUE(ui::WaitForNextFrameToBePresented(compositor));
-  WaitForOverviewEnterAnimation();
-  ASSERT_TRUE(ui::WaitForNextFrameToBePresented(compositor));
-  histogram_tester.ExpectTotalCount(
-      kOverviewDelayedDeskBarPresentationHistogram, 0);
-}
-
-class OverviewGridForestTest : public OverviewTestBase {
- public:
-  OverviewGridForestTest() = default;
-  OverviewGridForestTest(const OverviewGridForestTest&) = delete;
-  OverviewGridForestTest& operator=(const OverviewGridForestTest&) = delete;
-  ~OverviewGridForestTest() override = default;
-
   // Calculates `OverviewItemBase::should_animate_when_exiting_`. The reason we
   // do it like this is because this is normally called during shutdown, and
   // then the grid and items objects are destroyed. Note that this function
@@ -441,7 +88,7 @@
 };
 
 // Tests that with only one window, we always animate.
-TEST_F(OverviewGridForestTest, AnimateWithSingleWindow) {
+TEST_F(OverviewGridTest, AnimateWithSingleWindow) {
   auto window = CreateAppWindow(gfx::Rect(100, 100));
   ToggleOverview();
   VerifyAnimationStates({true}, {});
@@ -451,7 +98,7 @@
 }
 
 // Tests that are animations if the destination bounds are shown.
-TEST_F(OverviewGridForestTest, SourceHiddenDestinationShown) {
+TEST_F(OverviewGridTest, SourceHiddenDestinationShown) {
   auto window1 = CreateAppWindow(gfx::Rect(100, 100));
   auto window2 = CreateAppWindow(gfx::Rect(200, 200));
 
@@ -463,7 +110,7 @@
 }
 
 // Tests that are animations if the source bounds are shown.
-TEST_F(OverviewGridForestTest, SourceShownDestinationHidden) {
+TEST_F(OverviewGridTest, SourceShownDestinationHidden) {
   auto window1 = CreateAppWindow(gfx::Rect(100, 100));
   WindowState::Get(window1.get())->Maximize();
 
@@ -478,7 +125,7 @@
 
 // Tests that a window that is in the union of two other windows, but is still
 // shown will be animated.
-TEST_F(OverviewGridForestTest, SourceShownButInTheUnionOfTwoOtherWindows) {
+TEST_F(OverviewGridTest, SourceShownButInTheUnionOfTwoOtherWindows) {
   // Create three windows, the union of the first two windows will be
   // gfx::Rect(0, 0, 200, 200). Window 3 will be in that union, but should still
   // animate since its not fully occluded.
@@ -495,7 +142,7 @@
 
 // Tests that an always on top window will still be animated even if its source
 // and destination bounds are covered.
-TEST_F(OverviewGridForestTest, AlwaysOnTopWindow) {
+TEST_F(OverviewGridTest, AlwaysOnTopWindow) {
   UpdateDisplay("800x600");
 
   // Create two windows, even if `window1` is maximized, `window2` will still
@@ -514,7 +161,7 @@
 }
 
 // Tests that windows that are minimized are animated as expected.
-TEST_F(OverviewGridForestTest, MinimizedWindows) {
+TEST_F(OverviewGridTest, MinimizedWindows) {
   UpdateDisplay("800x600");
 
   auto window3 = CreateAppWindow(gfx::Rect(800, 600));
@@ -532,7 +179,7 @@
   VerifyAnimationStates({}, {true, false, false});
 }
 
-TEST_F(OverviewGridForestTest, SelectedWindow) {
+TEST_F(OverviewGridTest, SelectedWindow) {
   // Create 3 windows with the third window being maximized. All windows are
   // visible on entering, so they should all be animated. On exit we select the
   // third window which is maximized, so the other two windows should not
@@ -550,7 +197,7 @@
   VerifyAnimationStates({}, {false, false, true});
 }
 
-TEST_F(OverviewGridForestTest, WindowWithBackdrop) {
+TEST_F(OverviewGridTest, WindowWithBackdrop) {
   // Create one non resizable window and one normal window and verify that the
   // backdrop shows over the non resizable window, and that normal window
   // becomes maximized upon entering tablet mode.
@@ -579,7 +226,7 @@
   VerifyAnimationStates({}, {true, true});
 }
 
-TEST_F(OverviewGridForestTest, SourcePartiallyOffscreenWindow) {
+TEST_F(OverviewGridTest, SourcePartiallyOffscreenWindow) {
   UpdateDisplay("500x400");
 
   // Create `window2` to be partially offscreen.
@@ -604,7 +251,7 @@
 
 // Tests that windows whose destination is partially or fully offscreen never
 // animate.
-TEST_F(OverviewGridForestTest, PartialAndFullOffscreenWindow) {
+TEST_F(OverviewGridTest, PartialAndFullOffscreenWindow) {
   UpdateDisplay("800x600");
 
   Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
@@ -644,7 +291,7 @@
 
 // Tests that only one window animates when entering overview from splitview
 // double snapped.
-TEST_F(OverviewGridForestTest, SnappedWindow) {
+TEST_F(OverviewGridTest, SnappedWindow) {
   auto window3 = CreateAppWindow(gfx::Rect(100, 100));
   auto window2 = CreateAppWindow(gfx::Rect(100, 100));
   auto window1 = CreateAppWindow(gfx::Rect(100, 100));
@@ -665,4 +312,48 @@
   VerifyAnimationStates({true, false}, {});
 }
 
+TEST_F(OverviewGridTest, RecordsDelayedDeskBarPresentationMetric) {
+  ui::ScopedAnimationDurationScaleMode animation_scale(
+      ui::ScopedAnimationDurationScaleMode::FAST_DURATION);
+
+  // Since the windows are not maximized, the desk bar should open after
+  // the overview animation is complete, causing
+  // `kOverviewDelayedDeskBarPresentationHistogram` to be recorded.
+  std::unique_ptr<aura::Window> window1(CreateTestWindow());
+  std::unique_ptr<aura::Window> window2(CreateTestWindow());
+
+  ui::Compositor* const compositor = window1->GetHost()->compositor();
+  base::HistogramTester histogram_tester;
+  ToggleOverview();
+  ASSERT_TRUE(ui::WaitForNextFrameToBePresented(compositor));
+  histogram_tester.ExpectTotalCount(
+      kOverviewDelayedDeskBarPresentationHistogram, 0);
+  WaitForOverviewEnterAnimation();
+  ASSERT_TRUE(ui::WaitForNextFrameToBePresented(compositor));
+  histogram_tester.ExpectTotalCount(
+      kOverviewDelayedDeskBarPresentationHistogram, 1);
+}
+
+TEST_F(OverviewGridTest, DoesNotRecordDelayedDeskBarPresentationMetric) {
+  ui::ScopedAnimationDurationScaleMode animation_scale(
+      ui::ScopedAnimationDurationScaleMode::FAST_DURATION);
+
+  // Since the windows are maximized, the desk bar should open immediately when
+  // we enter overview and `kOverviewDelayedDeskBarPresentationHistogram` should
+  // not be recorded.
+  std::unique_ptr<aura::Window> window1(CreateTestWindow());
+  std::unique_ptr<aura::Window> window2(CreateTestWindow());
+  WindowState::Get(window1.get())->Maximize();
+  WindowState::Get(window2.get())->Maximize();
+
+  ui::Compositor* const compositor = window1->GetHost()->compositor();
+  base::HistogramTester histogram_tester;
+  ToggleOverview();
+  ASSERT_TRUE(ui::WaitForNextFrameToBePresented(compositor));
+  WaitForOverviewEnterAnimation();
+  ASSERT_TRUE(ui::WaitForNextFrameToBePresented(compositor));
+  histogram_tester.ExpectTotalCount(
+      kOverviewDelayedDeskBarPresentationHistogram, 0);
+}
+
 }  // namespace ash
diff --git a/ash/wm/snap_group/snap_group_unittest.cc b/ash/wm/snap_group/snap_group_unittest.cc
index 5bd59c3..88a16fad 100644
--- a/ash/wm/snap_group/snap_group_unittest.cc
+++ b/ash/wm/snap_group/snap_group_unittest.cc
@@ -6793,86 +6793,6 @@
 // Tests that accessing the saved desks library after creating a Snap Group does
 // not result in a crash, and the Snap Group is successfully restored upon
 // exiting overview mode. See regression at http://b/335301800.
-TEST_F(SnapGroupDesksTest, SaveDeskForSnapGroupWithAnotherSavedDeskOld) {
-  base::test::ScopedFeatureList disable;
-  disable.InitAndDisableFeature(features::kForestFeature);
-
-  OverviewController* overview_controller = OverviewController::Get();
-
-  // Explicitly disable `disable_app_id_check_for_saved_desks_` otherwise "Save
-  // desk for later" button will be disabled.
-  base::AutoReset<bool> disable_app_id_check =
-      overview_controller->SetDisableAppIdCheckForTests();
-
-  // Create `w0` and save `w0` in a saved desk by activing "Save desk for later"
-  // button in Overview.
-  aura::WindowTracker window_tracker;
-  aura::Window* w0 = CreateAppWindow(gfx::Rect(10, 10, 500, 300)).release();
-  window_tracker.Add(w0);
-
-  overview_controller->StartOverview(OverviewStartAction::kOverviewButton);
-  OverviewSession* overview_session = overview_controller->overview_session();
-  ASSERT_TRUE(overview_session);
-
-  auto* root_window = Shell::GetPrimaryRootWindow();
-  OverviewGrid* overview_grid = GetOverviewGridForRoot(root_window);
-  ASSERT_TRUE(overview_grid);
-  ASSERT_EQ(1u, overview_grid->item_list().size());
-
-  auto* save_for_later_button =
-      OverviewGridTestApi(overview_grid).GetSaveDeskForLaterButton();
-  ASSERT_TRUE(save_for_later_button);
-  base::RunLoop().RunUntilIdle();
-  LeftClickOn(save_for_later_button);
-
-  auto* desks_bar_view = overview_grid->desks_bar_view();
-  ASSERT_TRUE(desks_bar_view);
-
-  ASSERT_TRUE(WaitForLibraryButtonVisible());
-
-  overview_controller->EndOverview(OverviewEndAction::kOverviewButton);
-  // `w0` should have been destroyed automatically when the
-  // `save_for_later_button` was clicked.
-  ASSERT_FALSE(window_tracker.Contains(w0));
-
-  // Create a Snap Group and enter Overview again, click on the library button
-  // on the virtual desks bar and verify that there is no crash.
-  std::unique_ptr<aura::Window> w1(CreateAppWindow());
-  std::unique_ptr<aura::Window> w2(CreateAppWindow());
-  auto* event_generator = GetEventGenerator();
-  SnapTwoTestWindows(w1.get(), w2.get(), /*horizontal=*/true, event_generator);
-
-  overview_controller->StartOverview(OverviewStartAction::kOverviewButton);
-  ASSERT_TRUE(overview_session);
-
-  overview_grid = GetOverviewGridForRoot(root_window);
-  desks_bar_view = overview_grid->desks_bar_view();
-  ASSERT_TRUE(desks_bar_view);
-
-  auto* overview_group_item = GetOverviewItemForWindow(w1.get());
-  ASSERT_TRUE(overview_group_item);
-  ASSERT_FALSE(GetTopmostSnapGroupDivider()->divider_widget()->IsVisible());
-
-  const auto cached_group_item_bounds = overview_group_item->target_bounds();
-
-  ASSERT_TRUE(WaitForLibraryButtonVisible());
-  LeftClickOn(desks_bar_view->library_button());
-
-  // Click the point outside of `cached_group_item_bounds` will exit Overview
-  // and bring back the Snap Group.
-  const gfx::Point click_point = gfx::ToRoundedPoint(
-      cached_group_item_bounds.bottom_right() + gfx::Vector2d(20, 0));
-  event_generator->MoveMouseTo(click_point);
-
-  event_generator->ClickLeftButton();
-  EXPECT_FALSE(IsInOverviewSession());
-  UnionBoundsEqualToWorkAreaBounds(w1.get(), w2.get(),
-                                   GetTopmostSnapGroupDivider());
-}
-
-// Tests that accessing the saved desks library after creating a Snap Group does
-// not result in a crash, and the Snap Group is successfully restored upon
-// exiting overview mode. See regression at http://b/335301800.
 TEST_F(SnapGroupDesksTest, SaveDeskForSnapGroupWithAnotherSavedDesk) {
   saved_desk_test_helper()->WaitForDeskModels();
   base::test::ScopedFeatureList enable{features::kForestFeature};
diff --git a/ash/wm/workspace/workspace_event_handler.cc b/ash/wm/workspace/workspace_event_handler.cc
index 18e8579..aee92b8 100644
--- a/ash/wm/workspace/workspace_event_handler.cc
+++ b/ash/wm/workspace/workspace_event_handler.cc
@@ -9,11 +9,13 @@
 #include "ash/wm/overview/overview_controller.h"
 #include "ash/wm/overview/overview_session.h"
 #include "ash/wm/splitview/split_view_controller.h"
+#include "ash/wm/window_pin_util.h"
 #include "ash/wm/window_state.h"
 #include "ash/wm/window_util.h"
 #include "ash/wm/wm_event.h"
 #include "ash/wm/workspace/multi_window_resize_controller.h"
 #include "base/metrics/user_metrics.h"
+#include "chromeos/ui/base/window_pin_type.h"
 #include "ui/aura/window.h"
 #include "ui/aura/window_delegate.h"
 #include "ui/base/hit_test.h"
@@ -99,7 +101,12 @@
     return;
   }
 
-  aura::Window* target = static_cast<aura::Window*>(event->target());
+  aura::Window* const target = static_cast<aura::Window*>(event->target());
+  if (GetWindowPinType(target) == chromeos::WindowPinType::kTrustedPinned) {
+    // Do not attempt to resize or update locked fullscreen windows.
+    return;
+  }
+
   int previous_target_component = click_component_;
   click_component_ =
       window_util::GetNonClientComponent(target, event->location());
diff --git a/ash/wm/workspace/workspace_event_handler_unittest.cc b/ash/wm/workspace/workspace_event_handler_unittest.cc
index 2670b081..667f454b 100644
--- a/ash/wm/workspace/workspace_event_handler_unittest.cc
+++ b/ash/wm/workspace/workspace_event_handler_unittest.cc
@@ -7,6 +7,8 @@
 #include "ash/screen_util.h"
 #include "ash/shell.h"
 #include "ash/test/ash_test_base.h"
+#include "ash/wm/tablet_mode/tablet_mode_controller.h"
+#include "ash/wm/window_pin_util.h"
 #include "ash/wm/window_state.h"
 #include "ash/wm/window_util.h"
 #include "ash/wm/wm_event.h"
@@ -16,6 +18,7 @@
 #include "base/memory/raw_ptr.h"
 #include "base/run_loop.h"
 #include "base/task/single_thread_task_runner.h"
+#include "chromeos/ui/base/window_pin_type.h"
 #include "ui/aura/client/aura_constants.h"
 #include "ui/aura/test/test_window_delegate.h"
 #include "ui/aura/window.h"
@@ -621,6 +624,34 @@
   EXPECT_EQ(bounds.ToString(), window->bounds().ToString());
 }
 
+TEST_F(WorkspaceEventHandlerTest,
+       DoubleTapOnLockedFullscreenWindowDoesNotToggleMaximize) {
+  // Enable tablet mode controller to leverage tablet mode window states for
+  // testing purposes.
+  Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
+
+  aura::test::TestWindowDelegate delegate;
+  const gfx::Rect bounds(10, 20, 30, 40);
+  const std::unique_ptr<aura::Window> window(
+      CreateTestWindow(&delegate, bounds));
+  window->SetProperty(aura::client::kResizeBehaviorKey,
+                      aura::client::kResizeBehaviorCanMaximize);
+  delegate.set_window_component(HTCAPTION);
+
+  // Lock window.
+  window_util::PinWindow(window.get(), /*trusted=*/true);
+  WindowState* const window_state = WindowState::Get(window.get());
+  ASSERT_TRUE(window_state->IsTrustedPinned());
+
+  ui::test::EventGenerator generator(Shell::GetPrimaryRootWindow(),
+                                     window.get());
+
+  const gfx::Point tap_target = window->bounds().top_center();
+  generator.GestureTapAt(tap_target);
+  generator.GestureTapAt(tap_target);
+  EXPECT_TRUE(window_state->IsTrustedPinned());
+}
+
 // Verifies deleting the window while dragging doesn't crash.
 TEST_F(WorkspaceEventHandlerTest, DeleteWhenDragging) {
   // Create a large window in the background. This is necessary so that when we
diff --git a/base/BUILD.gn b/base/BUILD.gn
index 42ef31f..70444961 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -73,7 +73,12 @@
   # Set to true to enable mutex priority inheritance. See the comments in
   # LockImpl::PriorityInheritanceAvailable() in lock_impl_posix.cc for the
   # platform requirements to safely enable priority inheritance.
-  enable_mutex_priority_inheritance = false
+  #
+  # Enabled only on 64-bit Android. On 32 bit Android, PI mutexes have an extra
+  # level of indirection, so we don't bother enabling it. See:
+  # https://cs.android.com/android/platform/superproject/+/android14-qpr3-release:bionic/libc/bionic/pthread_mutex.cpp;drc=9108f258adad329a537e10461fbd526d5b9ad8bf;l=242
+  enable_mutex_priority_inheritance =
+      is_android && (current_cpu == "x64" || current_cpu == "arm64")
 
   # Control whether the ios stack sampling profiler is enabled. This flag is
   # only supported on iOS 64-bit architecture, but some project build //base
@@ -86,7 +91,8 @@
 # Chromecast builds have full control over the platform and ensure that
 # the kernel and glibc versions used have patched the vulnerabilities,
 # so it is safe to use mutex priority inheritance on Chromecast platform.
-assert(!enable_mutex_priority_inheritance || is_castos || is_cast_android,
+assert(!enable_mutex_priority_inheritance ||
+           (is_castos || is_cast_android || is_android),
        "Do not enable PI mutexes without consulting the security team")
 
 assert(!is_nacl || is_nacl_saigo,
diff --git a/base/features.h b/base/features.h
index a212f91a..df18062 100644
--- a/base/features.h
+++ b/base/features.h
@@ -39,6 +39,10 @@
 BASE_EXPORT BASE_DECLARE_FEATURE(kPostGetMyMemoryStateToBackground);
 #endif
 
+#if BUILDFLAG(ENABLE_MUTEX_PRIORITY_INHERITANCE)
+BASE_EXPORT BASE_DECLARE_FEATURE(kUsePriorityInheritanceMutex);
+#endif  // BUILDFLAG(ENABLE_MUTEX_PRIORITY_INHERITANCE)
+
 // Policy for emitting profiler metadata from `ThreadController`.
 enum class EmitThreadControllerProfilerMetadata {
   // Always emit metadata.
diff --git a/base/metrics/bucket_ranges.cc b/base/metrics/bucket_ranges.cc
index 31a55c4..edab2fe 100644
--- a/base/metrics/bucket_ranges.cc
+++ b/base/metrics/bucket_ranges.cc
@@ -4,6 +4,7 @@
 
 #include "base/metrics/bucket_ranges.h"
 
+#include <algorithm>
 #include <cmath>
 
 #include "base/containers/span.h"
@@ -11,9 +12,32 @@
 
 namespace base {
 
+namespace {
+
+constexpr bool is_sorted_and_unique(
+    base::span<const HistogramBase::Sample32> c) {
+  // Return true if we cannot find any adjacent pair {a, b} where a >= b.
+  return std::adjacent_find(c.begin(), c.end(),
+                            std::greater_equal<HistogramBase::Sample32>()) ==
+         c.end();
+}
+
+}  // namespace
+
 BucketRanges::BucketRanges(size_t num_ranges)
     : ranges_(num_ranges, 0), checksum_(0) {}
 
+BucketRanges::BucketRanges(base::span<const HistogramBase::Sample32> data)
+    : ranges_(data.begin(), data.end()), checksum_(0) {
+  // Because the range values must be in sorted order, it suffices to only
+  // validate that the first one is non-negative.
+  if (!ranges_.empty() && ranges_[0] >= 0 && is_sorted_and_unique(ranges_)) {
+    ResetChecksum();
+  } else {
+    ranges_.clear();
+  }
+}
+
 BucketRanges::~BucketRanges() = default;
 
 uint32_t BucketRanges::CalculateChecksum() const {
diff --git a/base/metrics/bucket_ranges.h b/base/metrics/bucket_ranges.h
index b22cd731..541a7d02 100644
--- a/base/metrics/bucket_ranges.h
+++ b/base/metrics/bucket_ranges.h
@@ -26,6 +26,7 @@
 
 #include "base/base_export.h"
 #include "base/check_op.h"
+#include "base/containers/span.h"
 #include "base/metrics/histogram_base.h"
 
 namespace base {
@@ -35,6 +36,7 @@
   typedef std::vector<HistogramBase::Sample32> Ranges;
 
   explicit BucketRanges(size_t num_ranges);
+  explicit BucketRanges(base::span<const HistogramBase::Sample32> data);
 
   BucketRanges(const BucketRanges&) = delete;
   BucketRanges& operator=(const BucketRanges&) = delete;
diff --git a/base/metrics/bucket_ranges_unittest.cc b/base/metrics/bucket_ranges_unittest.cc
index eb105c4e..099cc10a 100644
--- a/base/metrics/bucket_ranges_unittest.cc
+++ b/base/metrics/bucket_ranges_unittest.cc
@@ -74,5 +74,54 @@
   EXPECT_TRUE(ranges.HasValidChecksum());
 }
 
+TEST(BucketRangesTest, FromData) {
+  // Known good value #1
+  {
+    BucketRanges ranges({0, 1, 2});
+    EXPECT_EQ(ranges.size(), 3);
+    EXPECT_EQ(ranges.range(0), 0);
+    EXPECT_EQ(ranges.range(1), 1);
+    EXPECT_EQ(ranges.range(2), 2);
+    EXPECT_EQ(ranges.checksum(), 289217253u);
+  }
+  // Known good value #2
+  {
+    BucketRanges ranges({0, 1, 3});
+    EXPECT_EQ(ranges.size(), 3);
+    EXPECT_EQ(ranges.range(0), 0);
+    EXPECT_EQ(ranges.range(1), 1);
+    EXPECT_EQ(ranges.range(2), 3);
+    EXPECT_EQ(ranges.checksum(), 2843835776u);
+  }
+
+  // Must be non-empty.
+  {
+    BucketRanges ranges({});
+    EXPECT_EQ(ranges.size(), 0);
+    EXPECT_EQ(ranges.checksum(), 0);
+  }
+
+  // Must be non-negative.
+  {
+    BucketRanges ranges({-1, 1, 2});
+    EXPECT_EQ(ranges.size(), 0);
+    EXPECT_EQ(ranges.checksum(), 0);
+  }
+
+  // Must be in sorted order.
+  {
+    BucketRanges ranges({0, 2, 1});
+    EXPECT_EQ(ranges.size(), 0);
+    EXPECT_EQ(ranges.checksum(), 0);
+  }
+
+  // Must not contain duplicate values
+  {
+    BucketRanges ranges({0, 0, 1});
+    EXPECT_EQ(ranges.size(), 0);
+    EXPECT_EQ(ranges.checksum(), 0);
+  }
+}
+
 }  // namespace
 }  // namespace base
diff --git a/base/metrics/persistent_histogram_allocator.cc b/base/metrics/persistent_histogram_allocator.cc
index 26008a2..ce78ffd8 100644
--- a/base/metrics/persistent_histogram_allocator.cc
+++ b/base/metrics/persistent_histogram_allocator.cc
@@ -10,6 +10,7 @@
 #include <utility>
 
 #include "base/compiler_specific.h"
+#include "base/containers/span.h"
 #include "base/debug/crash_logging.h"
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
@@ -61,31 +62,6 @@
 // and dtors.
 subtle::AtomicWord g_histogram_allocator = 0;
 
-// Take an array of range boundaries and create a proper BucketRanges object
-// which is returned to the caller. A return of nullptr indicates that the
-// passed boundaries are invalid.
-std::unique_ptr<BucketRanges> CreateRangesFromData(
-    HistogramBase::Sample32* ranges_data,
-    uint32_t ranges_checksum,
-    size_t count) {
-  // To avoid racy destruction at shutdown, the following may be leaked.
-  std::unique_ptr<BucketRanges> ranges(new BucketRanges(count));
-  DCHECK_EQ(count, ranges->size());
-  for (size_t i = 0; i < count; ++i) {
-    if (i > 0 && UNSAFE_TODO(ranges_data[i] <= ranges_data[i - 1])) {
-      return nullptr;
-    }
-    ranges->set_range(i, UNSAFE_TODO(ranges_data[i]));
-  }
-
-  ranges->ResetChecksum();
-  if (ranges->checksum() != ranges_checksum) {
-    return nullptr;
-  }
-
-  return ranges;
-}
-
 // Calculate the number of bytes required to store all of a histogram's
 // "counts". This will return zero (0) if `bucket_count` is not valid.
 size_t CalculateRequiredCountsBytes(size_t bucket_count) {
@@ -598,33 +574,37 @@
   // changed at any moment by a malicious actor that shares access. The local
   // values are validated below and then used to create the histogram, knowing
   // they haven't changed between validation and use.
-  int32_t histogram_type = histogram_data_ptr->histogram_type;
-  int32_t histogram_flags = histogram_data_ptr->flags;
-  int32_t histogram_minimum = histogram_data_ptr->minimum;
-  int32_t histogram_maximum = histogram_data_ptr->maximum;
-  uint32_t histogram_bucket_count = histogram_data_ptr->bucket_count;
-  uint32_t histogram_ranges_ref = histogram_data_ptr->ranges_ref;
-  uint32_t histogram_ranges_checksum = histogram_data_ptr->ranges_checksum;
+  const int32_t histogram_type = histogram_data_ptr->histogram_type;
+  const int32_t histogram_flags = histogram_data_ptr->flags;
+  const int32_t histogram_minimum = histogram_data_ptr->minimum;
+  const int32_t histogram_maximum = histogram_data_ptr->maximum;
+  const uint32_t histogram_bucket_count = histogram_data_ptr->bucket_count;
+  const uint32_t histogram_ranges_ref = histogram_data_ptr->ranges_ref;
+  const uint32_t histogram_ranges_checksum =
+      histogram_data_ptr->ranges_checksum;
 
   size_t allocated_bytes = 0;
-  HistogramBase::Sample32* ranges_data =
+  const HistogramBase::Sample32* const ranges_data =
       memory_allocator_->GetAsArray<HistogramBase::Sample32>(
           histogram_ranges_ref, kTypeIdRangesArray,
           PersistentMemoryAllocator::kSizeAny, &allocated_bytes);
-
+  const size_t ranges_size = histogram_bucket_count + 1;
   const uint32_t max_buckets =
       std::numeric_limits<uint32_t>::max() / sizeof(HistogramBase::Sample32);
-  size_t required_bytes =
-      (histogram_bucket_count + 1) * sizeof(HistogramBase::Sample32);
+
+  const size_t required_bytes = ranges_size * sizeof(HistogramBase::Sample32);
   if (!ranges_data || histogram_bucket_count < 2 ||
       histogram_bucket_count >= max_buckets ||
       allocated_bytes < required_bytes) {
     return nullptr;
   }
 
-  std::unique_ptr<const BucketRanges> created_ranges = CreateRangesFromData(
-      ranges_data, histogram_ranges_checksum, histogram_bucket_count + 1);
-  if (!created_ranges || created_ranges->size() != histogram_bucket_count + 1 ||
+  auto created_ranges = std::make_unique<const BucketRanges>(
+      // SAFETY: We have validated above that `ranges_size` elements can be read
+      // from the allocation holding `ranges_data`.
+      UNSAFE_BUFFERS(base::span(ranges_data, ranges_size)));
+  if (!created_ranges || created_ranges->size() != ranges_size ||
+      created_ranges->checksum() != histogram_ranges_checksum ||
       created_ranges->range(1) != histogram_minimum ||
       created_ranges->range(histogram_bucket_count - 1) != histogram_maximum) {
     return nullptr;
diff --git a/base/synchronization/lock_impl.h b/base/synchronization/lock_impl.h
index d048938e..343b7ca0 100644
--- a/base/synchronization/lock_impl.h
+++ b/base/synchronization/lock_impl.h
@@ -13,6 +13,7 @@
 #include "base/memory/raw_ptr_exclusion.h"
 #include "base/memory/stack_allocated.h"
 #include "base/synchronization/lock_subtle.h"
+#include "base/synchronization/synchronization_buildflags.h"
 #include "base/thread_annotations.h"
 #include "build/build_config.h"
 
@@ -323,6 +324,15 @@
 };
 
 }  // namespace internal
+
+#if BUILDFLAG(ENABLE_MUTEX_PRIORITY_INHERITANCE)
+BASE_EXPORT bool ResetUsePriorityInheritanceMutexForTesting();
+
+// Check to see whether the current kernel supports priority inheritance
+// properly by adjusting process priorities to boost the futex owner.
+BASE_EXPORT bool KernelSupportsPriorityInheritanceFutex();
+#endif  // BUILDFLAG(ENABLE_MUTEX_PRIORITY_INHERITANCE)
+
 }  // namespace base
 
 #endif  // BASE_SYNCHRONIZATION_LOCK_IMPL_H_
diff --git a/base/synchronization/lock_impl_posix.cc b/base/synchronization/lock_impl_posix.cc
index 68eb742..a8a0551b 100644
--- a/base/synchronization/lock_impl_posix.cc
+++ b/base/synchronization/lock_impl_posix.cc
@@ -4,19 +4,84 @@
 
 #include "base/synchronization/lock_impl.h"
 
+#include <pthread.h>
+
+#include <atomic>
+#include <cstdint>
 #include <ostream>
 #include <string>
 
 #include "base/check_op.h"
+#include "base/feature_list.h"
+#include "base/features.h"
 #include "base/posix/safe_strerror.h"
 #include "base/synchronization/lock.h"
 #include "base/synchronization/synchronization_buildflags.h"
+#include "base/system/sys_info.h"
 #include "build/build_config.h"
 
-namespace base::internal {
+// On Android, `pthread_mutexattr_setprotocol()` is only defined in bionic
+// starting with API level 28. Make it a weak import, so that we can compile.
+extern "C" {
+int __attribute__((weak)) pthread_mutexattr_setprotocol(
+    pthread_mutexattr_t* _Nonnull __attr,
+    int __protocol);
+}
 
+namespace base {
+
+namespace features {
+#if BUILDFLAG(ENABLE_MUTEX_PRIORITY_INHERITANCE)
+BASE_FEATURE(kUsePriorityInheritanceMutex,
+             "UsePriorityInheritanceMutex",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+#endif  // BUILDFLAG(ENABLE_MUTEX_PRIORITY_INHERITANCE)
+}  // namespace features
+
+namespace internal {
 namespace {
 
+#if BUILDFLAG(ENABLE_MUTEX_PRIORITY_INHERITANCE)
+enum class UsePriorityInheritanceMutex : uint8_t {
+  kUnknown = 0,
+  kEnabled = 1,
+  kDisabled = 2,
+};
+
+static constinit std::atomic<UsePriorityInheritanceMutex>
+    s_pi_enablement_status{UsePriorityInheritanceMutex::kUnknown};
+
+bool IsMutexPriorityInheritanceEnabled() {
+  // The atomic operations in this method are idempotent and do not imply
+  // memory synchronization, so no need for anything else than relaxed ordering.
+  auto status = s_pi_enablement_status.load(std::memory_order_relaxed);
+  if (status != UsePriorityInheritanceMutex::kUnknown) {
+    return status == UsePriorityInheritanceMutex::kEnabled;
+  }
+
+  if (!base::FeatureList::GetInstance()) {
+    // The feature list is unavailable, so return false but don't cache the
+    // result.
+    return false;
+  }
+
+  // NOTE: The order of the checks matters here. The FeatureList is queried
+  // after checking for all other criteria so that the control and experiment
+  // groups don't get polluted with clients that cannot support priority
+  // inheriting mutexes
+  bool feature_enabled =
+      (pthread_mutexattr_setprotocol != nullptr) &&
+      KernelSupportsPriorityInheritanceFutex() &&
+      base::FeatureList::IsEnabled(features::kUsePriorityInheritanceMutex);
+
+  s_pi_enablement_status.store(feature_enabled
+                                   ? UsePriorityInheritanceMutex::kEnabled
+                                   : UsePriorityInheritanceMutex::kDisabled,
+                               std::memory_order_relaxed);
+  return feature_enabled;
+}
+#endif  // BUILDFLAG(ENABLE_MUTEX_PRIORITY_INHERITANCE)
+
 #if DCHECK_IS_ON()
 const char* AdditionalHintForSystemErrorCode(int error_code) {
   switch (error_code) {
@@ -58,7 +123,7 @@
 // Lock::PriorityInheritanceAvailable still must be checked as the code may
 // compile but the underlying platform still may not correctly support priority
 // inheritance locks.
-#if BUILDFLAG(IS_NACL) || BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_FUCHSIA)
+#if BUILDFLAG(IS_NACL) || BUILDFLAG(IS_FUCHSIA)
 #define PRIORITY_INHERITANCE_LOCKS_POSSIBLE() 0
 #else
 #define PRIORITY_INHERITANCE_LOCKS_POSSIBLE() 1
@@ -98,7 +163,7 @@
 // static
 bool LockImpl::PriorityInheritanceAvailable() {
 #if BUILDFLAG(ENABLE_MUTEX_PRIORITY_INHERITANCE)
-  return true;
+  return IsMutexPriorityInheritanceEnabled();
 #elif PRIORITY_INHERITANCE_LOCKS_POSSIBLE() && BUILDFLAG(IS_APPLE)
   return true;
 #else
@@ -121,4 +186,32 @@
 #endif
 }
 
-}  // namespace base::internal
+}  // namespace internal
+
+#if BUILDFLAG(ENABLE_MUTEX_PRIORITY_INHERITANCE)
+bool ResetUsePriorityInheritanceMutexForTesting() {
+  internal::s_pi_enablement_status.store(
+      internal::UsePriorityInheritanceMutex::kUnknown);
+  // Recompute immediately to cache the new value.
+  return internal::IsMutexPriorityInheritanceEnabled();
+}
+
+bool KernelSupportsPriorityInheritanceFutex() {
+  // https://android-review.googlesource.com/c/3481472 which fixes priority
+  // inheritance using rt-mutexes in the kernel landed in the 6.12.13 android
+  // kernel and was backported to the 6.1.75 and 6.6.29 kernels. This change
+  // hasn't been upstreamed yet.
+#if BUILDFLAG(IS_ANDROID)
+  auto kernel_version = SysInfo::KernelVersionNumber::Current();
+  return (kernel_version > SysInfo::KernelVersionNumber(6, 12, 13)) ||
+         ((kernel_version > SysInfo::KernelVersionNumber(6, 6, 29)) &&
+          (kernel_version < SysInfo::KernelVersionNumber(6, 6, INT32_MAX))) ||
+         ((kernel_version > SysInfo::KernelVersionNumber(6, 1, 75)) &&
+          (kernel_version < SysInfo::KernelVersionNumber(6, 1, INT32_MAX)));
+#else
+  return false;
+#endif
+}
+#endif  // BUILDFLAG(ENABLE_MUTEX_PRIORITY_INHERITANCE)
+
+}  // namespace base
diff --git a/base/synchronization/lock_unittest.cc b/base/synchronization/lock_unittest.cc
index 21d594f7..9d0309948 100644
--- a/base/synchronization/lock_unittest.cc
+++ b/base/synchronization/lock_unittest.cc
@@ -6,21 +6,36 @@
 
 #include <stdint.h>
 
+#include <algorithm>
+#include <atomic>
+#include <cmath>
+#include <cstdint>
 #include <memory>
 #include <utility>
 
 #include "base/check.h"
 #include "base/compiler_specific.h"
 #include "base/dcheck_is_on.h"
+#include "base/features.h"
+#include "base/functional/bind.h"
+#include "base/functional/callback.h"
+#include "base/functional/callback_helpers.h"
 #include "base/functional/function_ref.h"
 #include "base/location.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/raw_ref.h"
+#include "base/profiler/thread_delegate.h"
 #include "base/rand_util.h"
+#include "base/synchronization/lock_impl.h"
 #include "base/synchronization/lock_subtle.h"
+#include "base/system/sys_info.h"
+#include "base/test/bind.h"
 #include "base/test/gtest_util.h"
+#include "base/test/scoped_feature_list.h"
 #include "base/thread_annotations.h"
 #include "base/threading/platform_thread.h"
+#include "base/time/time.h"
+#include "base/timer/elapsed_timer.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -425,6 +440,209 @@
   EXPECT_TRUE(subtle::GetTrackedLocksHeldByCurrentThread().empty());
 }
 
+// Priority Inheritance Tests --------------------------------------------------
+
+#if BUILDFLAG(ENABLE_MUTEX_PRIORITY_INHERITANCE)
+namespace {
+class PriorityInheritanceTest {
+ public:
+  // The average value of MeasureRunTime() over |num_samples| iterations.
+  static TimeDelta MeasureAverageRunTime(int num_samples = 10) {
+    TimeDelta total_runtime;
+    for (int i = 0; i < num_samples; i++) {
+      total_runtime += MeasureRunTime();
+    }
+
+    return total_runtime / num_samples;
+  }
+
+  // Measure the time taken for a low-priority thread (kBackground) to perform
+  // CPU bound work when it holds a lock that is awaited by a high-priority
+  // thread (kRealtimeAudio).
+  static TimeDelta MeasureRunTime() {
+    Lock lock;
+    TimeDelta test_run_time;
+    std::atomic<bool> signal_cpu_bound_worker_threads_shutdown{false},
+        signal_thread_a_will_lock{false};
+
+    // Keep all the cores busy with a workload of CPU bound thread to reduce
+    // flakiness in the test by skewing the CPU time between the high-priority
+    // and low-priority measurement threads.
+    std::vector<TestThread> cpu_bound_worker_threads;
+    for (int i = 0; i < 15; i++) {
+      cpu_bound_worker_threads.emplace_back(
+          ThreadType::kDefault, base::BindLambdaForTesting([&]() {
+            while (!signal_cpu_bound_worker_threads_shutdown.load(
+                std::memory_order_relaxed)) {
+              BusyLoop(10);
+            }
+          }));
+    }
+
+    for (auto& worker_thread : cpu_bound_worker_threads) {
+      worker_thread.Create();
+    }
+
+    TestThread thread_a(
+        ThreadType::kRealtimeAudio, base::BindLambdaForTesting([&]() {
+          // Signal to thread B that the current thread will acquire the lock
+          // next, so that it can to start its CPU bound work.
+          signal_thread_a_will_lock.store(true, std::memory_order_relaxed);
+
+          // Wait on the lock to be released once the low-priority thread is
+          // done. In the case when priority inheritance mutexes are enabled,
+          // this should boost the priority of the low-priority thread to the
+          // priority of the highest priority waiter (i.e. the current thread).
+          AutoLock auto_lock(lock);
+          BusyLoop(10);
+        }));
+
+    TestThread thread_b(
+        ThreadType::kBackground, base::BindLambdaForTesting([&]() {
+          // Acquire the lock before creating the high-priority thread, so that
+          // the higher priority thread is blocked on the current thread while
+          // the current thread performs CPU-bound work.
+          AutoLock auto_lock(lock);
+          thread_a.Create();
+
+          // Before performing the CPU bound work, wait for the thread A to
+          // signal that it has started running and will acquire the lock next.
+          // While it is not a perfectly reliable signal (thread A may get
+          // descheduled immediately after signalling), given the relative
+          // priorities of the two threads it is good enough to reduce large
+          // variations due to latencies in thread bring up.
+          while (!signal_thread_a_will_lock.load(std::memory_order_relaxed)) {
+            usleep(10);
+          }
+
+          ElapsedTimer timer;
+          BusyLoop(1000000);
+          test_run_time = timer.Elapsed();
+        }));
+
+    // Create the low-priority thread which is responsible for creating the
+    // high-priority thread. Wait for both threads to finish before recording
+    // the elapsed time.
+    thread_b.Create();
+    thread_b.Join();
+    thread_a.Join();
+
+    signal_cpu_bound_worker_threads_shutdown.store(true,
+                                                   std::memory_order_relaxed);
+    for (auto& worker_thread : cpu_bound_worker_threads) {
+      worker_thread.Join();
+    }
+
+    return test_run_time;
+  }
+
+ private:
+  // CPU bound work for the threads to eat up CPU cycles.
+  static void BusyLoop(size_t n) {
+    __unused int sum = 0;
+    for (int i = 0; i < n; i++) {
+      if (base::ShouldRecordSubsampledMetric(0.5)) {
+        sum += 1;
+      }
+    }
+  }
+
+  class TestThread : public PlatformThread::Delegate {
+   public:
+    explicit TestThread(ThreadType thread_type, base::OnceClosure body)
+        : thread_type_(thread_type), body_(std::move(body)) {}
+
+    void Create() {
+      ASSERT_TRUE(
+          PlatformThread::CreateWithType(0, this, &handle_, thread_type_));
+    }
+
+    void ThreadMain() override { std::move(body_).Run(); }
+
+    void Join() { PlatformThread::Join(handle_); }
+
+   private:
+    ThreadType thread_type_;
+    PlatformThreadHandle handle_;
+    base::OnceClosure body_;
+  };
+};
+
+class ScopedConfigureUsePriorityInheritanceMutex {
+ public:
+  explicit ScopedConfigureUsePriorityInheritanceMutex(bool enabled) {
+    feature_list_.InitWithFeatureState(features::kUsePriorityInheritanceMutex,
+                                       enabled);
+    ResetUsePriorityInheritanceMutexForTesting();
+  }
+
+  ~ScopedConfigureUsePriorityInheritanceMutex() {
+    feature_list_.Reset();
+    ResetUsePriorityInheritanceMutexForTesting();
+  }
+
+ private:
+  test::ScopedFeatureList feature_list_;
+};
+
+}  // namespace
+
+// Tests that the time taken by a higher-priority thread to acquire a lock held
+// by a lower-priority thread is indeed reduced by priority inheritance.
+TEST(LockTest, PriorityIsInherited) {
+  TimeDelta avg_test_run_time_with_pi, avg_test_run_time_without_pi;
+
+  {
+    ScopedConfigureUsePriorityInheritanceMutex config_use_pi_mutex(true);
+
+    // Priority inheritance mutexes are not supported on Android kernels < 6.1
+    if (!Lock::HandlesMultipleThreadPriorities()) {
+      GTEST_SKIP() << "base::Lock does not handle multiple thread priorities "
+                   << "(Kernel version: "
+                   << base::SysInfo::KernelVersionNumber::Current() << ")";
+    }
+
+    avg_test_run_time_with_pi =
+        PriorityInheritanceTest::MeasureAverageRunTime();
+  }
+
+  {
+    ScopedConfigureUsePriorityInheritanceMutex config_use_pi_mutex(false);
+
+    avg_test_run_time_without_pi =
+        PriorityInheritanceTest::MeasureAverageRunTime();
+  }
+
+  // During the time in which the thread A is waiting on the lock to be released
+  // by the thread B, the thread B runs at kBackground priority in the non-PI
+  // case and at kRealtimeAudio priority in the PI case.
+  //
+  // Based on the Linux kernel's allocation of CPU shares documented in
+  // https://elixir.bootlin.com/linux/v6.12.5/source/kernel/sched/core.c#L9998,
+  // a thread running at kRealtimeAudio (nice value = -16) gets 36291 shares
+  // of the CPU, a thread at kDefault (nice value = 0) get 1024 shares and a
+  // thread at kBackground (nice value = 10) gets 110 shares of the CPU.
+  //
+  // Assuming no other threads except the ones created by this test are running,
+  // during the time in which thread A is waiting on the lock to be released by
+  // thread B, thread B gets 110/(15*1024 + 110) ≈ 0.7% of the CPU time in the
+  // non-PI case and 36291/(36291 + 15*1024) ≈ 70% of the CPU time in the PI
+  // case. This is approximately a 100x difference in CPU shares allocated to
+  // the thread B when it is doing CPU-bound work.
+  //
+  // The test is thus designed such that the measured run time is thread B's CPU
+  // bound work. While there are other factors at play that determine the
+  // measured run time such as the frequency at which the CPU is running, we can
+  // expect that there will be at least an order of magnitude of disparity in
+  // the test run times with and without PI.
+  //
+  // In order to reduce test flakiness while still eliminating the possibility
+  // of variance in measurements accounting for the test results, we
+  // conservatively expect a 3x improvement.
+  EXPECT_GT(avg_test_run_time_without_pi, 3 * avg_test_run_time_with_pi);
+}
+#endif  // BUILDFLAG(ENABLE_MUTEX_PRIORITY_INHERITANCE)
+
 #endif  // DCHECK_IS_ON()
 
 }  // namespace base
diff --git a/base/task/thread_pool/environment_config.cc b/base/task/thread_pool/environment_config.cc
index a2e75c42d..99db6e30 100644
--- a/base/task/thread_pool/environment_config.cc
+++ b/base/task/thread_pool/environment_config.cc
@@ -7,11 +7,41 @@
 #include "base/base_switches.h"
 #include "base/command_line.h"
 #include "base/synchronization/lock.h"
+#include "base/synchronization/lock_impl.h"
 #include "base/threading/platform_thread.h"
 #include "build/build_config.h"
 
 namespace base::internal {
 
+#if BUILDFLAG(IS_ANDROID)
+BASE_FEATURE(kForceBackgroundPriorityForWorkerThreads,
+             "ForceBackgroundPriorityForWorkerThreads",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+#if BUILDFLAG(ENABLE_MUTEX_PRIORITY_INHERITANCE)
+BASE_FEATURE(kUseBackgroundPriorityForWorkerThreads,
+             "UseBackgroundPriorityForWorkerThreads",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+#endif  // BUILDFLAG(ENABLE_MUTEX_PRIORITY_INHERITANCE)
+#endif  // BUILDFLAG(IS_ANDROID)
+
+#if BUILDFLAG(IS_ANDROID)
+const base::Feature& FeatureControllingBackgroundPriorityWorkerThreads() {
+  // There are two mutually exclusive feature flags that control whether the
+  // thread runs at background priority on Android:
+  // - |kUseBackgroundPriorityForWorkerThreads|: For newer kernels that support
+  //   priority inheritance mutexes, to be used in conjunction with
+  //   |kUsePriorityInheritanceMutex|.
+  // - |kForceBackgroundPriorityForWorkerThreads|: For older kernels that do not
+  //   support priority inheritance mutexes.
+#if BUILDFLAG(ENABLE_MUTEX_PRIORITY_INHERITANCE)
+  if (base::KernelSupportsPriorityInheritanceFutex()) {
+    return kUseBackgroundPriorityForWorkerThreads;
+  }
+#endif  // BUILDFLAG(ENABLE_MUTEX_PRIORITY_INHERITANCE)
+  return kForceBackgroundPriorityForWorkerThreads;
+}
+#endif  // BUILDFLAG(IS_ANDROID)
+
 namespace {
 
 bool CanUseBackgroundThreadTypeForWorkerThreadImpl() {
@@ -23,6 +53,10 @@
     return true;
   }
 
+#if BUILDFLAG(IS_ANDROID)
+  return base::FeatureList::IsEnabled(
+      FeatureControllingBackgroundPriorityWorkerThreads());
+#else   // BUILDFLAG(IS_ANDROID)
   // When Lock doesn't handle multiple thread priorities, run all
   // WorkerThread with a normal priority to avoid priority inversion when a
   // thread running with a normal priority tries to acquire a lock held by a
@@ -31,7 +65,6 @@
     return false;
   }
 
-#if !BUILDFLAG(IS_ANDROID)
   // When thread type can't be increased to kNormal, run all threads with a
   // kNormal thread type to avoid priority inversions on shutdown
   // (ThreadPoolImpl increases kBackground threads type to kNormal on shutdown
@@ -42,9 +75,9 @@
                                            ThreadType::kDefault)) {
     return false;
   }
-#endif  // BUILDFLAG(IS_ANDROID)
 
   return true;
+#endif  // BUILDFLAG(IS_ANDROID)
 }
 
 bool CanUseUtilityThreadTypeForWorkerThreadImpl() {
diff --git a/base/task/thread_pool/environment_config.h b/base/task/thread_pool/environment_config.h
index 181e117..747719e 100644
--- a/base/task/thread_pool/environment_config.h
+++ b/base/task/thread_pool/environment_config.h
@@ -8,6 +8,7 @@
 #include <stddef.h>
 
 #include "base/base_export.h"
+#include "base/feature_list.h"
 #include "base/task/task_traits.h"
 #include "base/threading/thread.h"
 
@@ -54,6 +55,11 @@
 // utility thread type.
 bool BASE_EXPORT CanUseUtilityThreadTypeForWorkerThread();
 
+#if BUILDFLAG(IS_ANDROID)
+const base::Feature& BASE_EXPORT
+FeatureControllingBackgroundPriorityWorkerThreads();
+#endif  // BUILDFLAG(IS_ANDROID)
+
 }  // namespace internal
 }  // namespace base
 
diff --git a/base/task/thread_pool/environment_config_unittest.cc b/base/task/thread_pool/environment_config_unittest.cc
index 7bddb670..7b1808d 100644
--- a/base/task/thread_pool/environment_config_unittest.cc
+++ b/base/task/thread_pool/environment_config_unittest.cc
@@ -4,6 +4,7 @@
 
 #include "base/task/thread_pool/environment_config.h"
 
+#include "base/feature_list.h"
 #include "build/build_config.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -13,7 +14,13 @@
 TEST(ThreadPoolEnvironmentConfig, CanUseBackgroundPriorityForWorker) {
 #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_APPLE)
   EXPECT_TRUE(CanUseBackgroundThreadTypeForWorkerThread());
-#elif BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_FUCHSIA) || \
+#elif BUILDFLAG(IS_ANDROID)
+  // On Android, use of background thread priority is controlled by one of two
+  // feature flags.
+  EXPECT_EQ(CanUseBackgroundThreadTypeForWorkerThread(),
+            base::FeatureList::IsEnabled(
+                FeatureControllingBackgroundPriorityWorkerThreads()));
+#elif BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_FUCHSIA) || \
     BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_NACL)
   EXPECT_FALSE(CanUseBackgroundThreadTypeForWorkerThread());
 #else
diff --git a/base/threading/sequence_bound.h b/base/threading/sequence_bound.h
index 3da6036..21e1580 100644
--- a/base/threading/sequence_bound.h
+++ b/base/threading/sequence_bound.h
@@ -581,7 +581,9 @@
     ~AsyncCallWithBoundArgsBuilderDefault() {
       // Must use Then() since the method's return type is not void.
       // Should be optimized out if the code is bug-free.
-      CHECK(!this->sequence_bound_);
+      CHECK(!this->sequence_bound_)
+          << "Then() not invoked for a method that returns a non-void type; "
+          << "make sure to invoke Then() or use base::IgnoreResult()";
     }
 
     template <template <typename> class CallbackType, typename ThenArg>
diff --git a/base/trace_event/trace_log.cc b/base/trace_event/trace_log.cc
index 4410440..d5dddba5 100644
--- a/base/trace_event/trace_log.cc
+++ b/base/trace_event/trace_log.cc
@@ -569,14 +569,6 @@
   return metadata_filter_predicate_;
 }
 
-void TraceLog::SetRecordHostAppPackageName(bool record_host_app_package_name) {
-  record_host_app_package_name_ = record_host_app_package_name;
-}
-
-bool TraceLog::ShouldRecordHostAppPackageName() const {
-  return record_host_app_package_name_;
-}
-
 TraceConfig TraceLog::GetCurrentTraceConfig() const {
   const auto chrome_config =
       GetCurrentTrackEventDataSourceConfig().chrome_config();
diff --git a/base/trace_event/trace_log.h b/base/trace_event/trace_log.h
index 65ef3c2..9d5264a 100644
--- a/base/trace_event/trace_log.h
+++ b/base/trace_event/trace_log.h
@@ -144,9 +144,6 @@
       const MetadataFilterPredicate& metadata_filter_predicate);
   MetadataFilterPredicate GetMetadataFilterPredicate() const;
 
-  void SetRecordHostAppPackageName(bool record_host_app_package_name);
-  bool ShouldRecordHostAppPackageName() const;
-
   // Flush all collected events to the given output callback. The callback will
   // be called one or more times either synchronously or asynchronously from
   // the current thread with IPC-bite-size chunks. The string format is
@@ -242,7 +239,6 @@
   // Set when asynchronous Flush is in progress.
   ArgumentFilterPredicate argument_filter_predicate_;
   MetadataFilterPredicate metadata_filter_predicate_;
-  bool record_host_app_package_name_{false};
 
   std::unique_ptr<perfetto::TracingSession> tracing_session_;
   perfetto::TraceConfig perfetto_config_;
diff --git a/cc/raster/categorized_worker_pool.cc b/cc/raster/categorized_worker_pool.cc
index 2a09470..37063794 100644
--- a/cc/raster/categorized_worker_pool.cc
+++ b/cc/raster/categorized_worker_pool.cc
@@ -265,6 +265,8 @@
     }
     TRACE_EVENT(
         "toplevel", "TaskGraphRunner::RunTask",
+        perfetto::TerminatingFlow::Global(
+            prioritized_task->task->trace_task_id()),
         [&](perfetto::EventContext ctx) {
           ctx.event<perfetto::protos::pbzero::ChromeTrackEvent>()
               ->set_chrome_raster_task()
diff --git a/cc/trees/draw_properties_unittest.cc b/cc/trees/draw_properties_unittest.cc
index cbc98fa..89e2eda 100644
--- a/cc/trees/draw_properties_unittest.cc
+++ b/cc/trees/draw_properties_unittest.cc
@@ -8070,19 +8070,16 @@
   UpdateMainDrawProperties();
 
   EffectNode* node = GetEffectNode(animated.get());
-  EXPECT_FALSE(node->is_currently_animating_opacity);
   EXPECT_TRUE(node->has_potential_opacity_animation);
 
   keyframe_model_ptr->set_time_offset(base::Milliseconds(0));
   host()->AnimateLayers(base::TimeTicks::Max());
   node = GetEffectNode(animated.get());
-  EXPECT_TRUE(node->is_currently_animating_opacity);
   EXPECT_TRUE(node->has_potential_opacity_animation);
 
   animation->AbortKeyframeModelsWithProperty(TargetProperty::OPACITY,
                                              false /*needs_completion*/);
   node = GetEffectNode(animated.get());
-  EXPECT_FALSE(node->is_currently_animating_opacity);
   EXPECT_FALSE(node->has_potential_opacity_animation);
 }
 
diff --git a/cc/trees/effect_node.h b/cc/trees/effect_node.h
index 3ea619f..df5ddf73 100644
--- a/cc/trees/effect_node.h
+++ b/cc/trees/effect_node.h
@@ -128,12 +128,6 @@
   // Whether this node has a potentially running (i.e., irrespective
   // of exact timeline) opacity animation.
   bool has_potential_opacity_animation : 1 = false;
-  // Whether this node has a currently running filter animation.
-  bool is_currently_animating_filter : 1 = false;
-  // Whether this node has a currently running backdrop-filter animation.
-  bool is_currently_animating_backdrop_filter : 1 = false;
-  // Whether this node has a currently running opacity animation.
-  bool is_currently_animating_opacity : 1 = false;
   // Whether this node has a child node with kDstIn blend mode.
   bool has_masking_child : 1 = false;
   // Whether this node's effect has been changed since the last
diff --git a/cc/trees/layer_tree_host_unittest.cc b/cc/trees/layer_tree_host_unittest.cc
index 07ab503..45b0bcd2 100644
--- a/cc/trees/layer_tree_host_unittest.cc
+++ b/cc/trees/layer_tree_host_unittest.cc
@@ -2113,31 +2113,23 @@
     switch (layer_tree_host()->SourceFrameNumber()) {
       case 1:
         node->opacity = 0.5f;
-        node->is_currently_animating_opacity = true;
         break;
       case 2:
-        node->is_currently_animating_opacity = true;
         break;
       case 3:
-        node->is_currently_animating_opacity = false;
         break;
       case 4:
         node->opacity = 0.25f;
-        node->is_currently_animating_opacity = true;
         break;
       case 5:
         node->filters = blur_filter_;
-        node->is_currently_animating_filter = true;
         break;
       case 6:
-        node->is_currently_animating_filter = true;
         break;
       case 7:
-        node->is_currently_animating_filter = false;
         break;
       case 8:
         node->filters = sepia_filter_;
-        node->is_currently_animating_filter = true;
         break;
     }
   }
diff --git a/cc/trees/layer_tree_impl.cc b/cc/trees/layer_tree_impl.cc
index 3fa2d7f5..03371f8 100644
--- a/cc/trees/layer_tree_impl.cc
+++ b/cc/trees/layer_tree_impl.cc
@@ -3037,6 +3037,9 @@
 
 std::vector<std::unique_ptr<ViewTransitionRequest>>
 LayerTreeImpl::TakeViewTransitionRequests() {
+  if (HasViewTransitionRequests()) {
+    set_needs_update_draw_properties();
+  }
   return std::move(view_transition_requests_);
 }
 
diff --git a/cc/trees/property_tree.cc b/cc/trees/property_tree.cc
index d7d8a70d..2cacba2 100644
--- a/cc/trees/property_tree.cc
+++ b/cc/trees/property_tree.cc
@@ -2276,9 +2276,6 @@
       case TargetProperty::OPACITY:
         if (EffectNode* effect_node =
                 effect_tree_mutable().FindNodeFromElementId(element_id)) {
-          if (mask.currently_running[property])
-            effect_node->is_currently_animating_opacity =
-                state.currently_running[property];
           if (mask.potentially_animating[property]) {
             effect_node->has_potential_opacity_animation =
                 state.potentially_animating[property];
@@ -2294,9 +2291,6 @@
       case TargetProperty::FILTER:
         if (EffectNode* effect_node =
                 effect_tree_mutable().FindNodeFromElementId(element_id)) {
-          if (mask.currently_running[property])
-            effect_node->is_currently_animating_filter =
-                state.currently_running[property];
           if (mask.potentially_animating[property])
             effect_node->has_potential_filter_animation =
                 state.potentially_animating[property];
@@ -2311,9 +2305,6 @@
       case TargetProperty::BACKDROP_FILTER:
         if (EffectNode* effect_node =
                 effect_tree_mutable().FindNodeFromElementId(element_id)) {
-          if (mask.currently_running[property])
-            effect_node->is_currently_animating_backdrop_filter =
-                state.currently_running[property];
           if (mask.potentially_animating[property])
             effect_node->has_potential_backdrop_filter_animation =
                 state.potentially_animating[property];
diff --git a/cc/trees/property_tree_builder.cc b/cc/trees/property_tree_builder.cc
index 5f08034..1a8b55e1 100644
--- a/cc/trees/property_tree_builder.cc
+++ b/cc/trees/property_tree_builder.cc
@@ -119,12 +119,6 @@
 };
 
 // Methods to query state from the AnimationHost ----------------------
-bool OpacityIsAnimating(const MutatorHost& host, Layer* layer) {
-  return host.IsAnimatingProperty(layer->element_id(),
-                                  layer->GetElementTypeForAnimation(),
-                                  TargetProperty::OPACITY);
-}
-
 bool HasPotentiallyRunningOpacityAnimation(const MutatorHost& host,
                                            Layer* layer) {
   return host.HasPotentiallyRunningAnimationForProperty(
@@ -137,12 +131,6 @@
          layer->OpacityCanAnimateOnImplThread();
 }
 
-bool FilterIsAnimating(const MutatorHost& host, Layer* layer) {
-  return host.IsAnimatingProperty(layer->element_id(),
-                                  layer->GetElementTypeForAnimation(),
-                                  TargetProperty::FILTER);
-}
-
 bool HasPotentiallyRunningFilterAnimation(const MutatorHost& host,
                                           Layer* layer) {
   return host.HasPotentiallyRunningAnimationForProperty(
@@ -538,10 +526,6 @@
   node->has_potential_opacity_animation = has_potential_opacity_animation;
   node->has_potential_filter_animation = has_potential_filter_animation;
   node->subtree_hidden = layer->hide_layer_and_subtree();
-  node->is_currently_animating_opacity =
-      OpacityIsAnimating(*mutator_host_, layer);
-  node->is_currently_animating_filter =
-      FilterIsAnimating(*mutator_host_, layer);
   node->effect_changed = layer->subtree_property_changed();
   node->subtree_has_copy_request = layer->subtree_has_copy_request();
   node->render_surface_reason = render_surface_reason;
diff --git a/chrome/android/features/keyboard_accessory/factory/java/src/org/chromium/chrome/browser/keyboard_accessory/ManualFillingComponentFactory.java b/chrome/android/features/keyboard_accessory/factory/java/src/org/chromium/chrome/browser/keyboard_accessory/ManualFillingComponentFactory.java
index 2918f3a..e253ba7 100644
--- a/chrome/android/features/keyboard_accessory/factory/java/src/org/chromium/chrome/browser/keyboard_accessory/ManualFillingComponentFactory.java
+++ b/chrome/android/features/keyboard_accessory/factory/java/src/org/chromium/chrome/browser/keyboard_accessory/ManualFillingComponentFactory.java
@@ -4,7 +4,10 @@
 
 package org.chromium.chrome.browser.keyboard_accessory;
 
+import org.chromium.build.annotations.NullMarked;
+
 /** Use {@link #createComponent()} to instantiate a {@link ManualFillingComponent}. */
+@NullMarked
 public class ManualFillingComponentFactory {
     private ManualFillingComponentFactory() {}
 
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupCreationDialogManager.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupCreationDialogManager.java
index 641ab45..5e7d4db7 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupCreationDialogManager.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupCreationDialogManager.java
@@ -26,6 +26,15 @@
 
 /** Manager of the observers that trigger a modal dialog on new tab group creation. */
 public class TabGroupCreationDialogManager {
+    /** Represents a factory for creating an instance of {@link TabGroupCreationDialogManager}. */
+    @FunctionalInterface
+    public interface TabGroupCreationDialogManagerFactory {
+        TabGroupCreationDialogManager create(
+                @NonNull Context context,
+                @NonNull ModalDialogManager modalDialogManager,
+                @Nullable Runnable onTabGroupCreation);
+    }
+
     private class TabGroupCreationDialogController implements ModalDialogProperties.Controller {
         private final int mRootId;
         private final TabGroupModelFilter mTabGroupModelFilter;
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupCreationUiFlow.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupCreationUiFlow.java
new file mode 100644
index 0000000..8d3e3dd
--- /dev/null
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupCreationUiFlow.java
@@ -0,0 +1,97 @@
+// 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.tasks.tab_management;
+
+import android.content.Context;
+
+import org.chromium.base.Token;
+import org.chromium.base.supplier.Supplier;
+import org.chromium.build.annotations.NullMarked;
+import org.chromium.build.annotations.Nullable;
+import org.chromium.chrome.browser.hub.PaneId;
+import org.chromium.chrome.browser.hub.PaneManager;
+import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tab.TabLaunchType;
+import org.chromium.chrome.browser.tabmodel.TabCreator;
+import org.chromium.chrome.browser.tabmodel.TabGroupModelFilter;
+import org.chromium.chrome.browser.tasks.tab_management.TabGroupCreationDialogManager.TabGroupCreationDialogManagerFactory;
+import org.chromium.components.embedder_support.util.UrlConstants;
+import org.chromium.content_public.browser.LoadUrlParams;
+import org.chromium.ui.modaldialog.ModalDialogManager;
+
+/** Handles the flow of creating a new tab group through the UI. */
+@NullMarked
+public class TabGroupCreationUiFlow {
+    private final Context mContext;
+    private final Supplier<ModalDialogManager> mModalDialogManagerSupplier;
+    private final Supplier<PaneManager> mPaneManagerSupplier;
+    private final Supplier<TabGroupModelFilter> mFilterSupplier;
+    private final TabGroupCreationDialogManagerFactory mFactory;
+
+    /**
+     * @param context The context for this UI flow.
+     * @param modalDialogManagerSupplier Supplies the {@link ModalDialogManager}.
+     * @param paneManagerSupplier Supplies the {@link PaneManager}.
+     * @param filterSupplier Supplies the filter used to create tab groups.
+     * @param factory Used to create an instance of {@link TabGroupCreationDialogManager}
+     */
+    public TabGroupCreationUiFlow(
+            Context context,
+            Supplier<ModalDialogManager> modalDialogManagerSupplier,
+            Supplier<PaneManager> paneManagerSupplier,
+            Supplier<TabGroupModelFilter> filterSupplier,
+            TabGroupCreationDialogManagerFactory factory) {
+        mContext = context;
+        mModalDialogManagerSupplier = modalDialogManagerSupplier;
+        mPaneManagerSupplier = paneManagerSupplier;
+        mFilterSupplier = filterSupplier;
+        mFactory = factory;
+    }
+
+    /**
+     * Creates a new tab group containing a newly created tab and starts a UI flow. This assumes
+     * that the Hub is currently visible.
+     *
+     * <p>The UI flow is as follows:
+     *
+     * <ul>
+     *   <li>A new tab is created in the background.
+     *   <li>A tab group is created containing the new tab.
+     *   <li>If successful, a {@link TabGroupCreationDialogManager} is shown to name the group.
+     *   <li>On completion of the dialog, the Hub is focused on the Tab Switcher pane.
+     *   <li>The Tab Switcher pane then opens the tab group UI for the newly created group.
+     * </ul>
+     */
+    public void newTabGroupFlow() {
+        TabGroupModelFilter filter = mFilterSupplier.get();
+        TabCreator tabCreator = filter.getTabModel().getTabCreator();
+        @Nullable Tab tab =
+                tabCreator.createNewTab(
+                        new LoadUrlParams(UrlConstants.NTP_URL),
+                        TabLaunchType.FROM_LONGPRESS_BACKGROUND,
+                        null);
+        if (tab != null) {
+            filter.createSingleTabGroup(tab);
+            @Nullable ModalDialogManager modalDialogManager = mModalDialogManagerSupplier.get();
+            @Nullable Token tabGroupId = tab.getTabGroupId();
+            if (tabGroupId != null && modalDialogManager != null) {
+                mFactory.create(mContext, modalDialogManager, () -> openTabGroupUi(tab))
+                        .showDialog(tabGroupId, filter);
+            }
+        }
+    }
+
+    private void openTabGroupUi(Tab tab) {
+        @Nullable PaneManager paneManager = mPaneManagerSupplier.get();
+        @Nullable Token groupId = tab.getTabGroupId();
+        if (paneManager != null && groupId != null && paneManager.focusPane(PaneId.TAB_SWITCHER)) {
+            @Nullable TabSwitcherPaneBase tabSwitcherPaneBase =
+                    (TabSwitcherPaneBase) paneManager.getPaneForId(PaneId.TAB_SWITCHER);
+            if (tabSwitcherPaneBase != null) {
+                tabSwitcherPaneBase.requestOpenTabGroupDialog(tab.getId());
+            }
+        }
+    }
+}
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupCreationUiFlowUnitTest.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupCreationUiFlowUnitTest.java
new file mode 100644
index 0000000..39667ae
--- /dev/null
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupCreationUiFlowUnitTest.java
@@ -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.
+
+package org.chromium.chrome.browser.tasks.tab_management;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Activity;
+
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
+
+import org.junit.Before;
+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.Token;
+import org.chromium.base.supplier.Supplier;
+import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.chrome.browser.hub.PaneId;
+import org.chromium.chrome.browser.hub.PaneManager;
+import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tabmodel.TabCreator;
+import org.chromium.chrome.browser.tabmodel.TabGroupModelFilter;
+import org.chromium.chrome.browser.tabmodel.TabModel;
+import org.chromium.ui.base.TestActivity;
+import org.chromium.ui.modaldialog.ModalDialogManager;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+/** Unit tests for {@link TabGroupCreationUiFlow}. */
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+public class TabGroupCreationUiFlowUnitTest {
+    @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+    @Rule
+    public ActivityScenarioRule<TestActivity> mActivityScenarioRule =
+            new ActivityScenarioRule<>(TestActivity.class);
+
+    @Mock private ModalDialogManager mModalDialogManager;
+    @Mock private TabGroupCreationDialogManager mTabGroupCreationDialogManager;
+    @Mock private PaneManager mPaneManager;
+    @Mock private TabGroupModelFilter mFilter;
+    @Mock private TabModel mTabModel;
+    @Mock private TabCreator mTabCreator;
+    @Mock private Tab mTab;
+    @Mock private TabSwitcherPaneBase mTabSwitcherPane;
+
+    private Supplier<ModalDialogManager> mModalDialogManagerSupplier;
+    private Supplier<PaneManager> mPaneManagerSupplier;
+    private Supplier<TabGroupModelFilter> mFilterSupplier;
+    private TabGroupCreationUiFlow mUiFlow;
+    private Token mToken;
+    private Activity mActivity;
+
+    @Before
+    public void setUp() {
+        mActivityScenarioRule.getScenario().onActivity(activity -> mActivity = activity);
+        mModalDialogManagerSupplier = () -> mModalDialogManager;
+        mPaneManagerSupplier = () -> mPaneManager;
+        mFilterSupplier = () -> mFilter;
+
+        when(mFilter.getTabModel()).thenReturn(mTabModel);
+        when(mTabModel.getTabCreator()).thenReturn(mTabCreator);
+        mToken = Token.createRandom();
+        when(mTab.getTabGroupId()).thenReturn(mToken);
+        when(mTab.getId()).thenReturn(1);
+
+        mUiFlow =
+                new TabGroupCreationUiFlow(
+                        mActivity,
+                        mModalDialogManagerSupplier,
+                        mPaneManagerSupplier,
+                        mFilterSupplier,
+                        (a, b, c) -> mTabGroupCreationDialogManager);
+
+        when(mTabCreator.createNewTab(any(), anyInt(), any())).thenReturn(mTab);
+    }
+
+    @Test
+    public void testNewTabGroupFlow() {
+        mUiFlow.newTabGroupFlow();
+        verify(mTabGroupCreationDialogManager).showDialog(mToken, mFilter);
+    }
+
+    @Test
+    public void testNewTabGroupFlow_tabCreationFails() {
+        when(mTabCreator.createNewTab(any(), anyInt(), any())).thenReturn(null);
+        mUiFlow.newTabGroupFlow();
+        verify(mTabGroupCreationDialogManager, never()).showDialog(mToken, mFilter);
+    }
+
+    @Test
+    public void testOpenTabGroupUi() {
+        when(mPaneManager.focusPane(PaneId.TAB_SWITCHER)).thenReturn(true);
+        when(mPaneManager.getPaneForId(PaneId.TAB_SWITCHER)).thenReturn(mTabSwitcherPane);
+
+        AtomicReference<Runnable> openTabGroupUiContainer = new AtomicReference<>(() -> {});
+        mUiFlow =
+                new TabGroupCreationUiFlow(
+                        mActivity,
+                        mModalDialogManagerSupplier,
+                        mPaneManagerSupplier,
+                        mFilterSupplier,
+                        (a, b, openTabGroupUi) -> {
+                            openTabGroupUiContainer.set(openTabGroupUi);
+                            return mTabGroupCreationDialogManager;
+                        });
+        mUiFlow.newTabGroupFlow();
+        openTabGroupUiContainer.get().run();
+        verify(mTabSwitcherPane).requestOpenTabGroupDialog(mTab.getId());
+    }
+
+    @Test
+    public void testOpenTabGroupUi_noTabSwitcherPane() {
+        when(mPaneManager.focusPane(PaneId.TAB_SWITCHER)).thenReturn(true);
+        when(mPaneManager.getPaneForId(PaneId.TAB_SWITCHER)).thenReturn(null);
+
+        AtomicReference<Runnable> openTabGroupUiContainer = new AtomicReference<>(() -> {});
+        mUiFlow =
+                new TabGroupCreationUiFlow(
+                        mActivity,
+                        mModalDialogManagerSupplier,
+                        mPaneManagerSupplier,
+                        mFilterSupplier,
+                        (a, b, openTabGroupUi) -> {
+                            openTabGroupUiContainer.set(openTabGroupUi);
+                            return mTabGroupCreationDialogManager;
+                        });
+        mUiFlow.newTabGroupFlow();
+        openTabGroupUiContainer.get().run();
+
+        verify(mPaneManager).focusPane(PaneId.TAB_SWITCHER);
+        verify(mPaneManager).getPaneForId(PaneId.TAB_SWITCHER);
+        verify(mTabSwitcherPane, never()).requestOpenTabGroupDialog(anyInt());
+    }
+}
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupsPane.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupsPane.java
index dd162ff..51d7203 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupsPane.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupsPane.java
@@ -18,6 +18,8 @@
 import org.chromium.base.supplier.ObservableSupplierImpl;
 import org.chromium.base.supplier.OneshotSupplier;
 import org.chromium.base.supplier.Supplier;
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
+import org.chromium.chrome.browser.hub.DelegateButtonData;
 import org.chromium.chrome.browser.hub.DisplayButtonData;
 import org.chromium.chrome.browser.hub.FadeHubLayoutAnimationFactory;
 import org.chromium.chrome.browser.hub.FullButtonData;
@@ -53,7 +55,7 @@
     private final FrameLayout mRootView;
     private final ObservableSupplierImpl<DisplayButtonData> mReferenceButtonSupplier =
             new ObservableSupplierImpl<>();
-    private final ObservableSupplier<FullButtonData> mEmptyActionButtonSupplier =
+    private final ObservableSupplierImpl<FullButtonData> mActionButtonSupplier =
             new ObservableSupplierImpl<>();
     private final ObservableSupplierImpl<Boolean> mHairlineVisibilitySupplier =
             new ObservableSupplierImpl<>();
@@ -90,6 +92,22 @@
         mTabGroupUiActionHandlerSupplier = tabGroupUiActionHandlerSupplier;
         mModalDialogManagerSupplier = modalDialogManagerSupplier;
         mEdgeToEdgeSupplier = edgeToEdgeSupplier;
+        if (ChromeFeatureList.sTabGroupEntryPointsAndroid.isEnabled()) {
+            TabGroupCreationUiFlow flow =
+                    new TabGroupCreationUiFlow(
+                            context,
+                            modalDialogManagerSupplier,
+                            paneManagerSupplier,
+                            mTabGroupModelFilterSupplier::get,
+                            TabGroupCreationDialogManager::new);
+            mActionButtonSupplier.set(
+                    new DelegateButtonData(
+                            new ResourceButtonData(
+                                    R.string.button_new_tab,
+                                    R.string.button_new_tab,
+                                    R.drawable.new_tab_icon),
+                            flow::newTabGroupFlow));
+        }
         mReferenceButtonSupplier.set(
                 new ResourceButtonData(
                         R.string.accessibility_tab_groups,
@@ -160,7 +178,7 @@
     @NonNull
     @Override
     public ObservableSupplier<FullButtonData> getActionButtonDataSupplier() {
-        return mEmptyActionButtonSupplier;
+        return mActionButtonSupplier;
     }
 
     @NonNull
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupsPaneUnitTest.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupsPaneUnitTest.java
index 1cc0b3d..c6b44c0f 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupsPaneUnitTest.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupsPaneUnitTest.java
@@ -7,8 +7,11 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.notNull;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.verify;
@@ -37,16 +40,20 @@
 import org.chromium.chrome.browser.collaboration.messaging.MessagingBackendServiceFactory;
 import org.chromium.chrome.browser.data_sharing.DataSharingServiceFactory;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
+import org.chromium.chrome.browser.hub.FullButtonData;
 import org.chromium.chrome.browser.hub.LoadHint;
 import org.chromium.chrome.browser.hub.PaneManager;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.profiles.ProfileProvider;
 import org.chromium.chrome.browser.signin.services.IdentityServicesProvider;
 import org.chromium.chrome.browser.sync.SyncServiceFactory;
+import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab_group_sync.TabGroupSyncFeatures;
 import org.chromium.chrome.browser.tab_group_sync.TabGroupSyncFeaturesJni;
 import org.chromium.chrome.browser.tab_group_sync.TabGroupSyncServiceFactory;
+import org.chromium.chrome.browser.tabmodel.TabCreator;
 import org.chromium.chrome.browser.tabmodel.TabGroupModelFilter;
+import org.chromium.chrome.browser.tabmodel.TabModel;
 import org.chromium.chrome.browser.ui.edge_to_edge.EdgeToEdgeController;
 import org.chromium.chrome.browser.ui.favicon.FaviconHelper;
 import org.chromium.chrome.browser.ui.favicon.FaviconHelperJni;
@@ -64,10 +71,13 @@
 /** Unit tests for {@link TabGroupsPane}. */
 @RunWith(BaseRobolectricTestRunner.class)
 @EnableFeatures({ChromeFeatureList.TAB_GROUP_SYNC_ANDROID, ChromeFeatureList.DATA_SHARING})
+@DisableFeatures(ChromeFeatureList.TAB_GROUP_ENTRY_POINTS_ANDROID)
 public class TabGroupsPaneUnitTest {
     @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
 
     @Mock private TabGroupModelFilter mTabGroupModelFilter;
+    @Mock private TabModel mTabModel;
+    @Mock private TabCreator mTabCreator;
     @Mock private DoubleConsumer mOnToolbarAlphaChange;
     @Mock private ProfileProvider mProfileProvider;
     @Mock private Profile mProfile;
@@ -84,6 +94,7 @@
     @Mock ModalDialogManager mModalDialogManager;
     @Mock TabGroupSyncFeatures.Natives mTabGroupSyncFeaturesJniMock;
     @Mock EdgeToEdgeController mEdgeToEdgeController;
+    @Mock Tab mTab;
 
     private final OneshotSupplierImpl<ProfileProvider> mProfileSupplier =
             new OneshotSupplierImpl<>();
@@ -112,6 +123,8 @@
         when(mTabGroupSyncService.getAllGroupIds()).thenReturn(new String[] {});
         TabGroupSyncFeaturesJni.setInstanceForTesting(mTabGroupSyncFeaturesJniMock);
         doReturn(true).when(mTabGroupSyncFeaturesJniMock).isTabGroupSyncEnabled(mProfile);
+        when(mTabGroupModelFilter.getTabModel()).thenReturn(mTabModel);
+        when(mTabModel.getTabCreator()).thenReturn(mTabCreator);
 
         mTabGroupsPane =
                 new TabGroupsPane(
@@ -208,4 +221,31 @@
         verify(controller2).registerAdjuster(notNull());
         verify(mEdgeToEdgeController).unregisterAdjuster(notNull());
     }
+
+    @Test
+    @DisableFeatures(ChromeFeatureList.TAB_GROUP_ENTRY_POINTS_ANDROID)
+    public void testNewTabGroupButtonDisabled() {
+        assertNull(mTabGroupsPane.getActionButtonDataSupplier().get());
+    }
+
+    @Test
+    @EnableFeatures(ChromeFeatureList.TAB_GROUP_ENTRY_POINTS_ANDROID)
+    public void testNewTabGroupButtonEnabled() {
+        assertNotNull(mTabGroupsPane.getActionButtonDataSupplier().get());
+    }
+
+    @Test
+    @EnableFeatures(ChromeFeatureList.TAB_GROUP_ENTRY_POINTS_ANDROID)
+    public void testNewTabGroupButton() {
+        when(mTabCreator.createNewTab(any(), anyInt(), any())).thenReturn(mTab);
+        FullButtonData actionButtonData = mTabGroupsPane.getActionButtonDataSupplier().get();
+        assertNotNull(actionButtonData);
+
+        Runnable onPressRunnable = actionButtonData.getOnPressRunnable();
+        assertNotNull(onPressRunnable);
+        onPressRunnable.run();
+
+        verify(mTabCreator).createNewTab(any(), anyInt(), any());
+        verify(mTabGroupModelFilter).createSingleTabGroup(mTab);
+    }
 }
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListEditorToolbar.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListEditorToolbar.java
index 87d7b8a..a3a9ad7d 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListEditorToolbar.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListEditorToolbar.java
@@ -6,6 +6,7 @@
 
 import android.content.Context;
 import android.content.res.ColorStateList;
+import android.graphics.Color;
 import android.util.AttributeSet;
 import android.view.Gravity;
 import android.view.ViewGroup;
@@ -106,7 +107,7 @@
     @Override
     protected void showSelectionView(List<Integer> selectedItems, boolean wasSelectionEnabled) {
         super.showSelectionView(selectedItems, wasSelectionEnabled);
-        if (mBackgroundColor != 0) {
+        if (mBackgroundColor != Color.TRANSPARENT) {
             setBackgroundColor(mBackgroundColor);
         }
     }
@@ -137,10 +138,12 @@
 
     /**
      * Update the toolbar background color.
+     *
      * @param backgroundColor The new color to use.
      */
     public void setToolbarBackgroundColor(@ColorInt int backgroundColor) {
         mBackgroundColor = backgroundColor;
+        setBackgroundColor(mBackgroundColor);
     }
 
     /**
diff --git a/chrome/android/features/tab_ui/tab_management_java_sources.gni b/chrome/android/features/tab_ui/tab_management_java_sources.gni
index 9c4bea60..4756f9b 100644
--- a/chrome/android/features/tab_ui/tab_management_java_sources.gni
+++ b/chrome/android/features/tab_ui/tab_management_java_sources.gni
@@ -96,6 +96,7 @@
   "//chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupColorPickerContainer.java",
   "//chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupColorViewProvider.java",
   "//chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupCreationDialogManager.java",
+  "//chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupCreationUiFlow.java",
   "//chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupFaviconCluster.java",
   "//chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupFaviconQuarter.java",
   "//chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupLabeller.java",
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
index a2a414c3..f1db312 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
@@ -1590,10 +1590,6 @@
         final LinkedHashMap<Integer, String> tabIdsToUrls = tabGroupMetadata.tabIdsToUrls;
         @TabOpenType int tabOpenType = IntentHandler.getTabOpenType(intent);
 
-        // This should not be set for group URL intents.
-        int tabIdToBringToFront = IntentHandler.getBringTabToFrontId(intent);
-        assert tabIdToBringToFront == Tab.INVALID_TAB_ID;
-
         ArrayList<Tab> tabs = new ArrayList();
         for (Map.Entry<Integer, String> entry : tabIdsToUrls.entrySet()) {
             int tabId = entry.getKey();
@@ -1605,34 +1601,20 @@
             // 1. Set intent tabId and url for each iteration.
             IntentHandler.setTabId(intent, tabId);
             intent.setData(Uri.parse(url));
+            assert !IntentHandler.isIntentForMhtmlFileOrContent(intent)
+                    : "Attempt to drop a tab group with MHTML tab";
 
             // 2. Create LoadUrlParams for the given URL.
             LoadUrlParams loadUrlParams =
                     IntentHandler.createLoadUrlParamsForIntent(url, intent, mIntentHandlingTimeMs);
 
-            // 3. Handle MHTML files or special content intents if necessary.
-            // TODO(crbug.com/403293319) Properly handle MHTML intents to ensure they are handled
-            // synchronously with other tabs.
-            if (IntentHandler.isIntentForMhtmlFileOrContent(intent)
-                    && tabOpenType == TabOpenType.OPEN_NEW_TAB
-                    && loadUrlParams.getReferrer() == null
-                    && loadUrlParams.getVerbatimHeaders() == null) {
-                getProfileProviderSupplier()
-                        .runSyncOrOnAvailable(
-                                (profileProvider) -> {
-                                    handleMhtmlFileOrContentIntent(
-                                            profileProvider.getOriginalProfile(), url, intent);
-                                });
-                continue;
-            }
-
-            // 4. Process the intent and open a new tab for the URL.
+            // 3. Process the intent and open a new tab for the URL.
             Tab tab =
                     processUrlViewIntent(
                             loadUrlParams,
                             tabOpenType,
                             IntentUtils.safeGetStringExtra(intent, Browser.EXTRA_APPLICATION_ID),
-                            tabIdToBringToFront,
+                            Tab.INVALID_TAB_ID,
                             intent);
             tabs.add(tab);
         }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/tabmodel/ArchivedTabModelOrchestrator.java b/chrome/android/java/src/org/chromium/chrome/browser/app/tabmodel/ArchivedTabModelOrchestrator.java
index f8c1271..d26e2a8 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/app/tabmodel/ArchivedTabModelOrchestrator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/app/tabmodel/ArchivedTabModelOrchestrator.java
@@ -108,14 +108,6 @@
                 public void onDeclutterPassCompleted() {
                     saveState();
                 }
-
-                @Override
-                public void onArchivePersistedTabDataCreated() {
-                    if (mTriggerAutodeleteAfterDataCreated) {
-                        mTabArchiver.doAutodeletePass();
-                        mTriggerAutodeleteAfterDataCreated = false;
-                    }
-                }
             };
 
     private final Profile mProfile;
@@ -146,7 +138,6 @@
     private CallbackController mCallbackController = new CallbackController();
     private ObservableSupplier<Integer> mUnderlyingTabCountSupplier;
     private @Nullable HistoricalTabModelObserver mHistoricalTabModelObserver;
-    private boolean mTriggerAutodeleteAfterDataCreated;
 
     /**
      * Returns the ArchivedTabModelOrchestrator that corresponds to the given profile. Must be
@@ -430,11 +421,15 @@
                             mTabArchiveSettings.setArchiveTimeDeltaHours(archiveTimeHours);
                         }
                         resumeSaveTabList(orchestrator);
+                    }
+
+                    @Override
+                    public void onArchivePersistedTabDataCreated() {
+                        mTabArchiver.doAutodeletePass();
                         mTabArchiver.removeObserver(this);
                     }
                 });
 
-        mTriggerAutodeleteAfterDataCreated = true;
         mTabArchiver.doArchivePass(orchestrator.getTabModelSelector());
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/StaticLayout.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/StaticLayout.java
index 9edb366..ac0f266c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/StaticLayout.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/StaticLayout.java
@@ -337,8 +337,7 @@
 
     private void requestFocus(Tab tab) {
         // TODO(crbug.com/40249125): Investigating guarded removal of this behavior (requesting
-        // focus on
-        // a tab) since it may no longer be relevant.
+        // focus on a tab) since it may no longer be relevant.
         // We will restrict avoidance of tab focus request only on tablet devices, since this is
         // known to cause regressions on phones - see crbug.com/1471887 for details.
         if (ChromeFeatureList.isEnabled(
@@ -347,6 +346,10 @@
             return;
         }
 
+        if (ChromeFeatureList.isEnabled(ChromeFeatureList.REMOVE_TAB_FOCUS_ON_SHOWING_AND_SELECT)) {
+            return;
+        }
+
         if (mIsActive && tab.getView() != null) tab.getView().requestFocus();
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/components/CompositorButton.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/components/CompositorButton.java
index 24f4d2c..baa8a9d 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/components/CompositorButton.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/components/CompositorButton.java
@@ -13,7 +13,7 @@
 
 import org.chromium.chrome.browser.compositor.overlays.strip.StripLayoutView;
 import org.chromium.chrome.browser.compositor.overlays.strip.TooltipManager;
-import org.chromium.ui.MotionEventUtils;
+import org.chromium.ui.util.MotionEventUtils;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelper.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelper.java
index d8181a0..a6929c0 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelper.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelper.java
@@ -118,11 +118,11 @@
 import org.chromium.components.tab_group_sync.TabGroupSyncService;
 import org.chromium.components.tab_group_sync.TriggerSource;
 import org.chromium.components.tab_groups.TabGroupColorId;
-import org.chromium.ui.MotionEventUtils;
 import org.chromium.ui.base.LocalizationUtils;
 import org.chromium.ui.base.WindowAndroid;
 import org.chromium.ui.modaldialog.ModalDialogManager;
 import org.chromium.ui.util.ColorUtils;
+import org.chromium.ui.util.MotionEventUtils;
 import org.chromium.ui.widget.RectProvider;
 
 import java.util.ArrayList;
@@ -2666,6 +2666,7 @@
             mTabCreator.launchNtp();
         }
         mIsStripScrollInProgress = false;
+        resetDelayedReorderState();
     }
 
     /** Handle view click * */
@@ -4516,9 +4517,25 @@
         }
     }
 
-    public void maybeMergeToGroupOnDrop(List<Integer> tabIds, int index) {
-        // TODO(crbug.com/405166521) Expand collapsed tabs when merged to group.
-        mReorderDelegate.handleDropForExternalView(mStripGroupTitles, tabIds, index);
+    /**
+     * Handles merging a group of tabs into an existing tab group on drop and expands them if the
+     * dropped group was collapsed.
+     *
+     * @param tabIds The list of tab IDs to merge into an existing group.
+     * @param index The index to insert the tabs.
+     * @param isCollapsed Whether the dropped group was collapsed before the drop.
+     */
+    public void maybeMergeToGroupOnDrop(List<Integer> tabIds, int index, boolean isCollapsed) {
+        boolean mergeToGroup =
+                mReorderDelegate.handleDropForExternalView(mStripGroupTitles, tabIds, index);
+
+        // Expand strip tabs if needed.
+        if (mergeToGroup && isCollapsed) {
+            // Selects the first tab in the collapsed group. For expanded groups, the correct tab
+            // should be selected during tab creation.
+            TabModelUtils.setIndex(mModel, index);
+            resizeTabStrip(/* animate= */ true, /* delay= */ false, /* deferAnimations= */ false);
+        }
     }
 
     public void stopReorderMode() {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutTab.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutTab.java
index 903532c..3e9c1ee 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutTab.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutTab.java
@@ -32,9 +32,9 @@
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tasks.tab_management.TabUiThemeUtil;
 import org.chromium.components.browser_ui.styles.SemanticColorUtils;
-import org.chromium.ui.MotionEventUtils;
 import org.chromium.ui.base.LocalizationUtils;
 import org.chromium.ui.util.ColorUtils;
+import org.chromium.ui.util.MotionEventUtils;
 
 import java.util.List;
 import java.util.Optional;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/reorder/ExternalViewDragDropReorderStrategy.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/reorder/ExternalViewDragDropReorderStrategy.java
index edf9bb6..9849f760 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/reorder/ExternalViewDragDropReorderStrategy.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/reorder/ExternalViewDragDropReorderStrategy.java
@@ -173,8 +173,8 @@
     }
 
     /** Merges dropped tabs to interacting view's tab group, if one exists. */
-    void handleDrop(StripLayoutGroupTitle[] groupTitles, List<Integer> tabIds, int dropIndex) {
-        if (mInteractingViewDuringStop == null) return;
+    boolean handleDrop(StripLayoutGroupTitle[] groupTitles, List<Integer> tabIds, int dropIndex) {
+        if (mInteractingViewDuringStop == null) return false;
 
         StripLayoutTab interactingView = (StripLayoutTab) mInteractingViewDuringStop;
         Tab interactingTab = mModel.getTabById(interactingView.getTabId());
@@ -183,10 +183,10 @@
         StripLayoutGroupTitle groupTitle =
                 StripLayoutUtils.findGroupTitle(groupTitles, interactingTab.getTabGroupId());
 
-        // 1. If hovered on tab is not part of group, no-op.
-        if (!mTabGroupModelFilter.isTabInTabGroup(interactingTab)) {
+        // 1. If hovered on tab is not part of group or is collapsed, no-op.
+        if (!mTabGroupModelFilter.isTabInTabGroup(interactingTab) || groupTitle.isCollapsed()) {
             mInteractingViewDuringStop = null;
-            return;
+            return false;
         }
 
         // 2. Merge all tabs in dragged tab group to hovered tab's group at drop index.
@@ -199,6 +199,7 @@
         // 3. Animate bottom indicator. Done after merging the dragged tab group to group,
         // so that the calculated bottom indicator width will be correct.
         runOnDropAnimation(groupTitle);
+        return true;
     }
 
     private void runOnDropAnimation(StripLayoutGroupTitle groupTitle) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/reorder/ReorderDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/reorder/ReorderDelegate.java
index 5d290ec..fa39a0e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/reorder/ReorderDelegate.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/reorder/ReorderDelegate.java
@@ -459,10 +459,10 @@
     }
 
     /** Update and animate views for external tabs to drop on strip. */
-    public void handleDropForExternalView(
+    public boolean handleDropForExternalView(
             StripLayoutGroupTitle[] groupTitles, List<Integer> tabIds, int dropIndex) {
         assert mInitialized && mExternalViewDragDropReorderStrategy != null;
-        mExternalViewDragDropReorderStrategy.handleDrop(groupTitles, tabIds, dropIndex);
+        return mExternalViewDragDropReorderStrategy.handleDrop(groupTitles, tabIds, dropIndex);
     }
 
     // ============================================================================================
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/reorder/TabDragSource.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/reorder/TabDragSource.java
index cb8813a..35e96f3 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/reorder/TabDragSource.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/reorder/TabDragSource.java
@@ -248,7 +248,6 @@
 
         // TODO(crbug.com/380327012): Block drag for last group when homepage enabled and is set to
         //  a custom url.
-
         // Allow drag to create new instance based on feature checks / current instance count.
         allowDragToCreateInstance =
                 allowDragToCreateInstance
@@ -519,7 +518,9 @@
             int tabIndex = helper.getTabIndexForTabDrop(dropEvent.getX() * mPxToDp);
             mMultiInstanceManager.moveTabToWindow(getActivity(), tabBeingDragged, tabIndex);
             helper.maybeMergeToGroupOnDrop(
-                    Collections.singletonList(tabBeingDragged.getId()), tabIndex);
+                    Collections.singletonList(tabBeingDragged.getId()),
+                    tabIndex,
+                    /* isCollapsed= */ false);
         }
         DragDropMetricUtils.recordTabDragDropType(
                 DragDropType.TAB_STRIP_TO_TAB_STRIP,
@@ -537,6 +538,12 @@
         if (tabGroupMetadata == null) {
             return false;
         }
+
+        if (disallowDragWithMhtmlTab(
+                mWindowAndroid.getContext().get(), tabGroupMetadata.mhtmlTabTitle)) {
+            return false;
+        }
+
         boolean tabGroupDraggedBelongToCurrentModel =
                 doesBelongToCurrentModel(tabGroupMetadata.isIncognito);
 
@@ -558,7 +565,8 @@
             if (!tabGroupMetadata.isGroupShared) {
                 maybeMergeToGroupOnDrop =
                         () -> {
-                            helper.maybeMergeToGroupOnDrop(tabIds, tabIndex);
+                            helper.maybeMergeToGroupOnDrop(
+                                    tabIds, tabIndex, tabGroupMetadata.tabGroupCollapsed);
                         };
             }
             mMultiInstanceManager.moveTabGroupToWindow(
@@ -711,6 +719,22 @@
         Toast.makeText(context, R.string.tab_dropped_different_model, Toast.LENGTH_LONG).show();
     }
 
+    /**
+     * Checks if the dragged group contains an MHTML tab (identified via a non-null tab title) and
+     * shows a toast message to inform the user that the tab cannot be moved.
+     *
+     * @param context The context to use for showing the toast.
+     * @param tabTitle The title of the tab that cannot be moved. If null, no toast is shown.
+     * @return {@code true} if the toast was shown, {@code false} otherwise.
+     */
+    // TODO(crbug.com/384979079): Record metrics.
+    private boolean disallowDragWithMhtmlTab(Context context, @Nullable String tabTitle) {
+        if (tabTitle == null) return false;
+        String text = context.getString(R.string.tab_cannot_be_moved, tabTitle);
+        Toast.makeText(context, text, Toast.LENGTH_LONG).show();
+        return true;
+    }
+
     private boolean doesBelongToCurrentModel(boolean draggedIncognito) {
         return mTabModelSelector.getCurrentModel().isIncognitoBranded() == draggedIncognito;
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/segmentation_platform/SignalAccumulator.java b/chrome/android/java/src/org/chromium/chrome/browser/segmentation_platform/SignalAccumulator.java
index 73b0f570..f7b67f7 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/segmentation_platform/SignalAccumulator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/segmentation_platform/SignalAccumulator.java
@@ -6,6 +6,7 @@
 
 import android.os.Handler;
 
+import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.chrome.browser.segmentation_platform.ContextualPageActionController.ActionProvider;
 import org.chromium.chrome.browser.tab.Tab;
 
@@ -16,6 +17,10 @@
  * all the feature backends have responded or a time out has happened.
  */
 public class SignalAccumulator {
+    /** Histogram name that records how long it takes to get a reader mode result. */
+    public static final String READER_MODE_SIGNAL_TIME_HISTOGRAM =
+            "DomDistiller.Time.TimeToProvideResultToAccumulator";
+
     private static final long ACTION_PROVIDER_TIMEOUT_MS = 100;
 
     // List of signals to query. Modify hasAllSignals() when adding signals to this list.
@@ -36,6 +41,7 @@
     // The callback to be invoked at the end of getting all the signals or time out. After it is
     // run, the accumulator becomes invalid.
     private Runnable mCompletionCallback;
+    private long mGetSignalsStartMs;
 
     private final List<ActionProvider> mActionProviders;
     private final Tab mTab;
@@ -59,6 +65,7 @@
      */
     public void getSignals(Runnable callback) {
         mCompletionCallback = callback;
+        mGetSignalsStartMs = System.currentTimeMillis();
         for (ActionProvider actionProvider : mActionProviders) {
             actionProvider.getAction(mTab, this);
         }
@@ -102,6 +109,8 @@
     /** Called to set whether the page can be viewed in reader mode. */
     public void setHasReaderMode(Boolean hasReaderMode) {
         mHasReaderMode = hasReaderMode;
+        RecordHistogram.recordLongTimesHistogram(
+                READER_MODE_SIGNAL_TIME_HISTOGRAM, System.currentTimeMillis() - mGetSignalsStartMs);
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/segmentation_platform/SignalAccumulatorTest.java b/chrome/android/java/src/org/chromium/chrome/browser/segmentation_platform/SignalAccumulatorTest.java
index 936c6dce..d7c59fa 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/segmentation_platform/SignalAccumulatorTest.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/segmentation_platform/SignalAccumulatorTest.java
@@ -22,6 +22,7 @@
 
 import org.chromium.base.test.BaseRobolectricTestRunner;
 import org.chromium.base.test.util.CallbackHelper;
+import org.chromium.base.test.util.HistogramWatcher;
 import org.chromium.chrome.browser.segmentation_platform.ContextualPageActionController.ActionProvider;
 import org.chromium.chrome.browser.tab.Tab;
 
@@ -85,4 +86,25 @@
         Assert.assertFalse(accumulator.hasPriceInsights());
         Assert.assertFalse(accumulator.hasDiscounts());
     }
+
+    @Test
+    public void testSetReaderModeRecordsTime() throws TimeoutException {
+        List<ActionProvider> actionProviders = new ArrayList<>();
+        ActionProvider actionProvider =
+                (tab, accumulator) -> {
+                    accumulator.setHasReaderMode(false);
+                };
+        actionProviders.add(actionProvider);
+        final CallbackHelper callbackHelper = new CallbackHelper();
+        SignalAccumulator accumulator = new SignalAccumulator(mHandler, mMockTab, actionProviders);
+
+        HistogramWatcher watcher =
+                HistogramWatcher.newBuilder()
+                        .expectAnyRecordTimes(
+                                SignalAccumulator.READER_MODE_SIGNAL_TIME_HISTOGRAM, 1)
+                        .build();
+        accumulator.getSignals(() -> callbackHelper.notifyCalled());
+        callbackHelper.waitForNext();
+        watcher.assertExpected();
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/site_settings/ChromeSiteSettingsDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/site_settings/ChromeSiteSettingsDelegate.java
index a82a0d3..6a70a5d 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/site_settings/ChromeSiteSettingsDelegate.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/site_settings/ChromeSiteSettingsDelegate.java
@@ -62,6 +62,8 @@
 import org.chromium.content_public.browser.ContentFeatureMap;
 import org.chromium.content_public.common.ContentFeatures;
 import org.chromium.content_public.common.ContentSwitches;
+import org.chromium.device.DeviceFeatureList;
+import org.chromium.device.DeviceFeatureMap;
 import org.chromium.url.GURL;
 
 import java.util.List;
@@ -163,6 +165,8 @@
             case SiteSettingsCategory.Type.REQUEST_DESKTOP_SITE:
                 // Desktop Android always requests desktop sites, so hide the category.
                 return !BuildConfig.IS_DESKTOP_ANDROID;
+            case SiteSettingsCategory.Type.SERIAL_PORT:
+                return DeviceFeatureMap.isEnabled(DeviceFeatureList.BLUETOOTH_RFCOMM_ANDROID);
             default:
                 return true;
         }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/tile_edit_dialog/CustomTileEditDelegates.java b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/tile_edit_dialog/CustomTileEditDelegates.java
index 0693ac7..a5cb30b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/tile_edit_dialog/CustomTileEditDelegates.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/tile_edit_dialog/CustomTileEditDelegates.java
@@ -14,7 +14,7 @@
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 
-/** Interfaces to decouple Coordinator / Mediator / View for the Most Visited Tile Edit Dialog. */
+/** Interfaces to decouple Coordinator / Mediator / View for the Custom Tile Edit Dialog. */
 @NullMarked
 class CustomTileEditDelegates {
     @IntDef({
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/tile_edit_dialog/CustomTileEditMediator.java b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/tile_edit_dialog/CustomTileEditMediator.java
index d954a964..50b25cd 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/tile_edit_dialog/CustomTileEditMediator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/tile_edit_dialog/CustomTileEditMediator.java
@@ -6,6 +6,7 @@
 
 import android.text.TextUtils;
 
+import org.chromium.build.annotations.Initializer;
 import org.chromium.build.annotations.NullMarked;
 import org.chromium.build.annotations.Nullable;
 import org.chromium.chrome.browser.suggestions.tile.Tile;
@@ -16,29 +17,37 @@
 import org.chromium.chrome.browser.suggestions.tile.tile_edit_dialog.CustomTileEditDelegates.ViewToMediator;
 import org.chromium.url.GURL;
 
-/** The Mediator of the Most Visited Tile Edit Dialog. */
+/** The Mediator of the Custom Tile Edit Dialog. */
 @NullMarked
 class CustomTileEditMediator implements ViewToMediator {
-    private final MediatorToBrowser mBrowserDelegate;
-    private final MediatorToView mViewDelegate;
     private final @Nullable Tile mOriginalTile;
 
+    private MediatorToView mViewDelegate;
+    private MediatorToBrowser mBrowserDelegate;
+
     /**
      * Constructs a new CustomTileEditMediator.
      *
-     * @param browserDelegate The interface to the Browser.
-     * @param viewDelegate The interface to the View.
      * @param originalTile the tile being edited, or null if adding a new tile.
      */
-    CustomTileEditMediator(
-            MediatorToBrowser browserDelegate,
-            MediatorToView viewDelegate,
-            @Nullable Tile originalTile) {
-        mBrowserDelegate = browserDelegate;
-        mViewDelegate = viewDelegate;
+    CustomTileEditMediator(@Nullable Tile originalTile) {
         mOriginalTile = originalTile;
     }
 
+    /**
+     * Assigns delegates for interacting with the Browser and the View.
+     *
+     * @param viewDelegate The interface to the View.
+     * @param browserDelegate The interface to the Browser.
+     */
+    @Initializer
+    void setDelegates(MediatorToView viewDelegate, MediatorToBrowser browserDelegate) {
+        assert mViewDelegate == null;
+        mViewDelegate = viewDelegate;
+        assert mBrowserDelegate == null;
+        mBrowserDelegate = browserDelegate;
+    }
+
     // ViewToMediator implementation.
     @Override
     public void onUrlTextChanged(String urlText) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/tile_edit_dialog/CustomTileEditView.java b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/tile_edit_dialog/CustomTileEditView.java
index 4294232..23bd3ed 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/tile_edit_dialog/CustomTileEditView.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/tile_edit_dialog/CustomTileEditView.java
@@ -25,7 +25,7 @@
 import org.chromium.ui.modelutil.PropertyModel;
 import org.chromium.ui.text.EmptyTextWatcher;
 
-/** The View of the Most Visited Tile Edit Dialog. */
+/** The View of the Custom Tile Edit Dialog. */
 @NullMarked
 class CustomTileEditView extends FrameLayout
         implements ModalDialogProperties.Controller, MediatorToView {
@@ -46,6 +46,7 @@
         mDialogModel = createDialogModel();
     }
 
+    // FrameLayout override.
     @Override
     @Initializer
     public void onFinishInflate() {
@@ -61,6 +62,17 @@
                 });
     }
 
+    /**
+     * Sets the delegate for handling user interactions.
+     *
+     * @param mediatorDelegate The delegate to set interaction events to.
+     */
+    @Initializer
+    void setMediatorDelegate(ViewToMediator mediatorDelegate) {
+        assert mMediatorDelegate == null;
+        mMediatorDelegate = mediatorDelegate;
+    }
+
     // ModalDialogProperties.Controller implementation.
     @Override
     public void onClick(PropertyModel modelDialogModel, int buttonType) {
@@ -115,17 +127,6 @@
         mUrlField.requestFocus();
     }
 
-    /**
-     * Sets the delegate for handling user interactions.
-     *
-     * @param mediatorDelegate The delegate to set interaction events to.
-     */
-    @Initializer
-    void setMediatorDelegate(ViewToMediator mediatorDelegate) {
-        assert mMediatorDelegate == null;
-        mMediatorDelegate = mediatorDelegate;
-    }
-
     PropertyModel getDialogModel() {
         return mDialogModel;
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabArchiverImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabArchiverImpl.java
index 6ee2862..150401e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabArchiverImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabArchiverImpl.java
@@ -87,7 +87,7 @@
     @Override
     public void doArchivePass(TabModelSelector selectorToArchive) {
         ThreadUtils.assertOnUiThread();
-        assert mTabArchiveSettings.getArchiveEnabled();
+        if (!mTabArchiveSettings.getArchiveEnabled()) return;
         long startTimeMs = mClock.currentTimeMillis();
 
         // Wait for the declutter pass to complete, then do follow-up tasks.
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/ChromeActionModeHandlerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/ChromeActionModeHandlerTest.java
index 5ecd53e..12825be 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/ChromeActionModeHandlerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/ChromeActionModeHandlerTest.java
@@ -5,7 +5,6 @@
 package org.chromium.chrome.browser;
 
 import androidx.test.filters.SmallTest;
-import androidx.test.platform.app.InstrumentationRegistry;
 
 import org.junit.Assert;
 import org.junit.Rule;
@@ -18,19 +17,20 @@
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab.TabLaunchType;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
-import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
-import org.chromium.components.embedder_support.util.UrlConstants;
+import org.chromium.chrome.test.transit.ChromeTransitTestRules;
+import org.chromium.chrome.test.transit.FreshCtaTransitTestRule;
+import org.chromium.chrome.test.transit.ntp.RegularNewTabPageStation;
 import org.chromium.content_public.browser.LoadUrlParams;
 import org.chromium.content_public.browser.test.util.WebContentsUtils;
 import org.chromium.content_public.common.ContentUrlConstants;
-import org.chromium.net.test.EmbeddedTestServer;
 
 /** Tests {@link ChromeActionModeHandler} operation. */
 @RunWith(ChromeJUnit4ClassRunner.class)
 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
 public class ChromeActionModeHandlerTest {
     @Rule
-    public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
+    public FreshCtaTransitTestRule mActivityTestRule =
+            ChromeTransitTestRules.freshChromeTabbedActivityRule();
 
     private void assertActionModeIsReady() {
         ThreadUtils.runOnUiThreadBlocking(
@@ -43,12 +43,9 @@
     @Test
     @SmallTest
     public void testActionModeSetForNewTab() {
-        EmbeddedTestServer testServer =
-                EmbeddedTestServer.createAndStartServer(
-                        InstrumentationRegistry.getInstrumentation().getContext());
+        RegularNewTabPageStation ntp = mActivityTestRule.startOnNtp();
+        ntp.loadAboutBlank();
 
-        mActivityTestRule.startMainActivityWithURL(UrlConstants.NTP_URL);
-        mActivityTestRule.loadUrl(ContentUrlConstants.ABOUT_BLANK_DISPLAY_URL);
         assertActionModeIsReady();
 
         LoadUrlParams urlParams = new LoadUrlParams(ContentUrlConstants.ABOUT_BLANK_DISPLAY_URL);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/ChromeTabbedActivityTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/ChromeTabbedActivityTest.java
index 04dbefc1..c6b49efa 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/ChromeTabbedActivityTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/ChromeTabbedActivityTest.java
@@ -60,7 +60,8 @@
 import org.chromium.chrome.browser.tabmodel.TabGroupModelFilter;
 import org.chromium.chrome.browser.tabmodel.TabModel;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
-import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
+import org.chromium.chrome.test.transit.ChromeTransitTestRules;
+import org.chromium.chrome.test.transit.FreshCtaTransitTestRule;
 import org.chromium.components.tab_group_sync.LocalTabGroupId;
 import org.chromium.components.tab_group_sync.SavedTabGroup;
 import org.chromium.components.tab_group_sync.SavedTabGroupTab;
@@ -93,8 +94,8 @@
     private static final String FILE_PATH = "/chrome/test/data/android/test.html";
 
     @Rule
-    public final ChromeTabbedActivityTestRule sActivityTestRule =
-            new ChromeTabbedActivityTestRule();
+    public FreshCtaTransitTestRule mActivityTestRule =
+            ChromeTransitTestRules.freshChromeTabbedActivityRule();
 
     @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
 
@@ -103,8 +104,8 @@
 
     @Before
     public void setUp() {
-        sActivityTestRule.startMainActivityOnBlankPage();
-        mActivity = sActivityTestRule.getActivity();
+        mActivityTestRule.startOnBlankPage();
+        mActivity = mActivityTestRule.getActivity();
         assertNotNull(mActivity);
     }
 
@@ -119,7 +120,7 @@
     public void testTabVisibility() {
         // Create two tabs - tab[0] in the foreground and tab[1] in the background.
         final Tab[] tabs = new Tab[2];
-        sActivityTestRule.getTestServer(); // Triggers the lazy initialization of the test server.
+        mActivityTestRule.getTestServer(); // Triggers the lazy initialization of the test server.
         ThreadUtils.runOnUiThreadBlocking(
                 () -> {
                     // Foreground tab.
@@ -127,14 +128,14 @@
                     tabs[0] =
                             tabCreator.createNewTab(
                                     new LoadUrlParams(
-                                            sActivityTestRule.getTestServer().getURL(FILE_PATH)),
+                                            mActivityTestRule.getTestServer().getURL(FILE_PATH)),
                                     TabLaunchType.FROM_CHROME_UI,
                                     null);
                     // Background tab.
                     tabs[1] =
                             tabCreator.createNewTab(
                                     new LoadUrlParams(
-                                            sActivityTestRule.getTestServer().getURL(FILE_PATH)),
+                                            mActivityTestRule.getTestServer().getURL(FILE_PATH)),
                                     TabLaunchType.FROM_LONGPRESS_BACKGROUND,
                                     null);
                 });
@@ -166,14 +167,14 @@
     @EnableFeatures(ChromeFeatureList.CHANGE_UNFOCUSED_PRIORITY)
     @MinAndroidSdkLevel(VERSION_CODES.S)
     public void testTabImportance() {
-        sActivityTestRule.getTestServer(); // Triggers the lazy initialization of the test server.
+        mActivityTestRule.getTestServer(); // Triggers the lazy initialization of the test server.
         final Tab tab =
                 ThreadUtils.runOnUiThreadBlocking(
                         () -> {
                             ChromeTabCreator tabCreator = mActivity.getCurrentTabCreator();
                             return tabCreator.createNewTab(
                                     new LoadUrlParams(
-                                            sActivityTestRule.getTestServer().getURL(FILE_PATH)),
+                                            mActivityTestRule.getTestServer().getURL(FILE_PATH)),
                                     TabLaunchType.FROM_CHROME_UI,
                                     null);
                         });
@@ -255,7 +256,7 @@
         Intent viewIntent =
                 new Intent(
                         Intent.ACTION_VIEW,
-                        Uri.parse(sActivityTestRule.getTestServer().getURL("/first")));
+                        Uri.parse(mActivityTestRule.getTestServer().getURL("/first")));
         viewIntent.putExtra(
                 Browser.EXTRA_APPLICATION_ID, mActivity.getApplicationContext().getPackageName());
         viewIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
@@ -264,8 +265,8 @@
         viewIntent.setClass(mActivity, ChromeLauncherActivity.class);
         ArrayList<String> extraUrls =
                 Lists.newArrayList(
-                        sActivityTestRule.getTestServer().getURL("/second"),
-                        sActivityTestRule.getTestServer().getURL("/third"));
+                        mActivityTestRule.getTestServer().getURL("/second"),
+                        mActivityTestRule.getTestServer().getURL("/third"));
         viewIntent.putExtra(IntentHandler.EXTRA_ADDITIONAL_URLS, extraUrls);
         IntentUtils.addTrustedIntentExtras(viewIntent);
 
@@ -533,13 +534,13 @@
     @DisabledTest(message = "crbug.com/1187320 This doesn't work with FeedV2 and crbug.com/1096295")
     public void testActivityCanBeGarbageCollectedAfterFinished() {
         WeakReference<ChromeTabbedActivity> activityRef =
-                new WeakReference<>(sActivityTestRule.getActivity());
+                new WeakReference<>(mActivityTestRule.getActivity());
 
         ChromeTabbedActivity activity =
-                ApplicationTestUtils.recreateActivity(sActivityTestRule.getActivity());
+                ApplicationTestUtils.recreateActivity(mActivityTestRule.getActivity());
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
 
-        sActivityTestRule.setActivity(activity);
+        mActivityTestRule.getActivityTestRule().setActivity(activity);
 
         CriteriaHelper.pollUiThread(
                 () -> GarbageCollectionTestUtils.canBeGarbageCollected(activityRef));
@@ -548,14 +549,14 @@
     @Test
     @MediumTest
     public void testBackShouldCloseTab() {
-        sActivityTestRule.getTestServer(); // Triggers the lazy initialization of the test server.
+        mActivityTestRule.getTestServer(); // Triggers the lazy initialization of the test server.
         Tab tab =
                 ThreadUtils.runOnUiThreadBlocking(
                         () -> {
                             ChromeTabCreator tabCreator = mActivity.getCurrentTabCreator();
                             return tabCreator.createNewTab(
                                     new LoadUrlParams(
-                                            sActivityTestRule.getTestServer().getURL(FILE_PATH)),
+                                            mActivityTestRule.getTestServer().getURL(FILE_PATH)),
                                     TabLaunchType.FROM_LINK,
                                     null);
                         });
@@ -570,7 +571,7 @@
     @Test
     @MediumTest
     public void testBackShouldCloseTab_Collaboration() {
-        sActivityTestRule.getTestServer(); // Triggers the lazy initialization of the test server.
+        mActivityTestRule.getTestServer(); // Triggers the lazy initialization of the test server.
         Tab tab =
                 ThreadUtils.runOnUiThreadBlocking(
                         () -> {
@@ -578,7 +579,7 @@
                             Tab newTab =
                                     tabCreator.createNewTab(
                                             new LoadUrlParams(
-                                                    sActivityTestRule
+                                                    mActivityTestRule
                                                             .getTestServer()
                                                             .getURL(FILE_PATH)),
                                             TabLaunchType.FROM_LINK,
@@ -624,6 +625,7 @@
                 TAB_IDS_TO_URLS,
                 /* tabGroupColor= */ 0,
                 TAB_GROUP_TITLE,
+                /* mhtmlTabTitle= */ null,
                 /* tabGroupCollapsed= */ false,
                 /* isGroupShared= */ false,
                 /* isIncognito= */ false);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/ContentViewFocusTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/ContentViewFocusTest.java
index 59d33760..7c41811 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/ContentViewFocusTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/ContentViewFocusTest.java
@@ -32,8 +32,9 @@
 import org.chromium.chrome.browser.layouts.LayoutTestUtils;
 import org.chromium.chrome.browser.layouts.LayoutType;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
-import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
 import org.chromium.chrome.test.R;
+import org.chromium.chrome.test.transit.ChromeTransitTestRules;
+import org.chromium.chrome.test.transit.FreshCtaTransitTestRule;
 import org.chromium.chrome.test.util.ChromeTabUtils;
 import org.chromium.components.browser_ui.widget.gesture.SwipeGestureListener.ScrollDirection;
 import org.chromium.components.browser_ui.widget.gesture.SwipeGestureListener.SwipeHandler;
@@ -52,7 +53,8 @@
 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
 public class ContentViewFocusTest {
     @Rule
-    public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
+    public FreshCtaTransitTestRule mActivityTestRule =
+            ChromeTransitTestRules.freshChromeTabbedActivityRule();
 
     private static final int WAIT_RESPONSE_MS = 2000;
 
@@ -113,14 +115,14 @@
     @Test
     @DisabledTest(message = "http://crbug.com/172473")
     public void testHideSelectionOnPhoneTabSwiping() throws Exception {
-        mActivityTestRule.startMainActivityOnBlankPage();
+        mActivityTestRule.startOnBlankPage();
         // Setup
         ChromeTabUtils.newTabsFromMenu(
                 InstrumentationRegistry.getInstrumentation(), mActivityTestRule.getActivity(), 2);
         String url =
                 UrlUtils.getIsolatedTestFileUrl(
                         "chrome/test/data/android/content_view_focus/content_view_focus_long_text.html");
-        mActivityTestRule.loadUrl(url);
+        mActivityTestRule.getActivityTestRule().loadUrl(url);
         View view = mActivityTestRule.getActivity().getActivityTab().getContentView();
 
         // Give the content view focus
@@ -179,7 +181,7 @@
     @Restriction(DeviceFormFactor.PHONE)
     @DisabledTest(message = "http://crbug.com/967128")
     public void testHideSelectionOnPhoneTabSwitcher() throws Exception {
-        mActivityTestRule.startMainActivityOnBlankPage();
+        mActivityTestRule.startOnBlankPage();
         // Setup
         View currentView = mActivityTestRule.getActivity().getActivityTab().getContentView();
         addFocusChangedListener(currentView);
@@ -213,7 +215,7 @@
     @Test
     @MediumTest
     public void testPauseTriggersBlur() throws Exception {
-        mActivityTestRule.startMainActivityOnBlankPage();
+        mActivityTestRule.startOnBlankPage();
         final WebContents webContents = mActivityTestRule.getWebContents();
         final CallbackHelper onTitleUpdatedHelper = new CallbackHelper();
         final WebContentsObserver observer =
@@ -231,7 +233,7 @@
         String url =
                 UrlUtils.getIsolatedTestFileUrl(
                         "chrome/test/data/android/content_view_focus/content_view_blur_focus.html");
-        mActivityTestRule.loadUrl(url);
+        mActivityTestRule.getActivityTestRule().loadUrl(url);
         ViewEventSink eventSink = WebContentsUtils.getViewEventSink(webContents);
         onTitleUpdatedHelper.waitForCallback(callCount);
         // The document can start out as focused or not focused at first, depending on whether a
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/ContentWebFeatureUsageUtilsIntegrationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/ContentWebFeatureUsageUtilsIntegrationTest.java
index edc5de77..f6ad986 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/ContentWebFeatureUsageUtilsIntegrationTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/ContentWebFeatureUsageUtilsIntegrationTest.java
@@ -18,9 +18,9 @@
 import org.chromium.blink.mojom.WebFeature;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
-import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
+import org.chromium.chrome.test.transit.ChromeTransitTestRules;
+import org.chromium.chrome.test.transit.FreshCtaTransitTestRule;
 import org.chromium.content_public.browser.ContentWebFeatureUsageUtils;
-import org.chromium.net.test.EmbeddedTestServer;
 
 /** Integration tests for {@link ContentWebFeatureUsageUtils}. */
 @RunWith(ChromeJUnit4ClassRunner.class)
@@ -28,13 +28,12 @@
 @Batch(Batch.PER_CLASS)
 public class ContentWebFeatureUsageUtilsIntegrationTest {
     @Rule
-    public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
+    public FreshCtaTransitTestRule mActivityTestRule =
+            ChromeTransitTestRules.freshChromeTabbedActivityRule();
 
     @Before
     public void setUp() {
-        EmbeddedTestServer testServer = mActivityTestRule.getTestServer();
-        mActivityTestRule.startMainActivityWithURL(
-                testServer.getURL("/chrome/test/data/android/simple.html"));
+        mActivityTestRule.startOnTestServerUrl("/chrome/test/data/android/simple.html");
     }
 
     @Test
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/FeaturesAnnotationsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/FeaturesAnnotationsTest.java
index 084c28c..e78eb64b 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/FeaturesAnnotationsTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/FeaturesAnnotationsTest.java
@@ -10,7 +10,7 @@
 
 import androidx.test.filters.SmallTest;
 
-import org.junit.ClassRule;
+import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -23,8 +23,9 @@
 import org.chromium.base.test.util.Features.EnableFeatures;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
-import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
-import org.chromium.chrome.test.batch.BlankCTATabInitialStateRule;
+import org.chromium.chrome.test.transit.ChromeTransitTestRules;
+import org.chromium.chrome.test.transit.ReusedCtaTransitTestRule;
+import org.chromium.chrome.test.transit.page.WebPageStation;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -35,13 +36,14 @@
 @CommandLineFlags.Add(ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE)
 @Batch(Batch.PER_CLASS)
 public class FeaturesAnnotationsTest {
-    @ClassRule
-    public static ChromeTabbedActivityTestRule sActivityTestRule =
-            new ChromeTabbedActivityTestRule();
-
     @Rule
-    public BlankCTATabInitialStateRule mInitialStateRule =
-            new BlankCTATabInitialStateRule(sActivityTestRule, false);
+    public ReusedCtaTransitTestRule<WebPageStation> mActivityTestRule =
+            ChromeTransitTestRules.blankPageStartReusedActivityRule();
+
+    @Before
+    public void setUp() {
+        mActivityTestRule.start();
+    }
 
     /**
      * Tests that {@link EnableFeatures} and {@link DisableFeatures} can alter the flags registered
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/FocusedEditableTextFieldZoomTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/FocusedEditableTextFieldZoomTest.java
index 0149d6a..75ae46a 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/FocusedEditableTextFieldZoomTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/FocusedEditableTextFieldZoomTest.java
@@ -8,7 +8,6 @@
 
 import android.view.KeyEvent;
 
-import androidx.test.core.app.ApplicationProvider;
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import org.hamcrest.Matchers;
@@ -25,11 +24,11 @@
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
-import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
+import org.chromium.chrome.test.transit.ChromeTransitTestRules;
+import org.chromium.chrome.test.transit.FreshCtaTransitTestRule;
 import org.chromium.content_public.browser.test.util.Coordinates;
 import org.chromium.content_public.browser.test.util.DOMUtils;
 import org.chromium.content_public.browser.test.util.KeyUtils;
-import org.chromium.net.test.EmbeddedTestServer;
 
 /** Tests for zooming into & out of a selected & deselected editable text field. */
 @RunWith(ChromeJUnit4ClassRunner.class)
@@ -38,23 +37,20 @@
 })
 public class FocusedEditableTextFieldZoomTest {
     @Rule
-    public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
+    public FreshCtaTransitTestRule mActivityTestRule =
+            ChromeTransitTestRules.freshChromeTabbedActivityRule();
 
     private static final int TEST_TIMEOUT = 5000;
     private static final String TEXTFIELD_DOM_ID = "textfield";
     private static final float FLOAT_DELTA = 0.01f;
     private static final float INITIAL_SCALE = 0.5f;
 
-    private EmbeddedTestServer mTestServer;
     private Coordinates mCoordinates;
 
     @Before
     public void setUp() {
-        mTestServer =
-                EmbeddedTestServer.createAndStartServer(
-                        ApplicationProvider.getApplicationContext());
-        mActivityTestRule.startMainActivityWithURL(
-                mTestServer.getURL("/chrome/test/data/android/focused_editable_zoom.html"));
+        mActivityTestRule.startOnTestServerUrl(
+                "/chrome/test/data/android/focused_editable_zoom.html");
         mCoordinates = Coordinates.createFor(mActivityTestRule.getWebContents());
         waitForInitialZoom();
     }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/HTTPSTabsOpenedFromExternalAppTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/HTTPSTabsOpenedFromExternalAppTest.java
index b68d9e9..887ec9ed 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/HTTPSTabsOpenedFromExternalAppTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/HTTPSTabsOpenedFromExternalAppTest.java
@@ -14,11 +14,13 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import org.chromium.base.test.util.Batch;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
-import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
+import org.chromium.chrome.test.transit.AutoResetCtaTransitTestRule;
+import org.chromium.chrome.test.transit.ChromeTransitTestRules;
 import org.chromium.net.test.EmbeddedTestServer;
 import org.chromium.net.test.ServerCertificate;
 import org.chromium.network.mojom.ReferrerPolicy;
@@ -26,15 +28,17 @@
 /** Test the behavior of tabs when opening an HTTPS URL from an external app. */
 @RunWith(ChromeJUnit4ClassRunner.class)
 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
+@Batch(Batch.PER_CLASS)
 public class HTTPSTabsOpenedFromExternalAppTest {
     @Rule
-    public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
+    public AutoResetCtaTransitTestRule mActivityTestRule =
+            ChromeTransitTestRules.autoResetCtaActivityRule();
 
     private EmbeddedTestServer mTestServer;
 
     @Before
     public void setUp() throws Exception {
-        mActivityTestRule.startMainActivityOnBlankPage();
+        mActivityTestRule.startOnBlankPage();
     }
 
     /**
@@ -50,6 +54,10 @@
                         ApplicationProvider.getApplicationContext(), ServerCertificate.CERT_OK);
         String url = mTestServer.getURL("/chrome/test/data/android/about.html");
         TabsOpenedFromExternalAppTest.loadUrlAndVerifyReferrerWithPolicy(
-                url, mActivityTestRule, ReferrerPolicy.DEFAULT, HTTP_REFERRER, HTTP_REFERRER);
+                url,
+                mActivityTestRule.getActivityTestRule(),
+                ReferrerPolicy.DEFAULT,
+                HTTP_REFERRER,
+                HTTP_REFERRER);
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/InputHintCheckerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/InputHintCheckerTest.java
index 28de1ee..7e45042 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/InputHintCheckerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/InputHintCheckerTest.java
@@ -14,7 +14,6 @@
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.ClassRule;
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -27,17 +26,12 @@
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.test.ChromeBrowserTestRule;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
-import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
 
 /** Integration tests for InputHintChecker. */
 @RunWith(ChromeJUnit4ClassRunner.class)
 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
 @DoNotBatch(reason = "Tests once-per-process initialization")
 public final class InputHintCheckerTest {
-    @Rule
-    public final ChromeTabbedActivityTestRule mActivityTestRule =
-            new ChromeTabbedActivityTestRule();
-
     @ClassRule
     public static final ChromeBrowserTestRule sBrowserTestRule = new ChromeBrowserTestRule();
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/InstalledAppTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/InstalledAppTest.java
index f3f14d5a..00dfac23 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/InstalledAppTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/InstalledAppTest.java
@@ -4,7 +4,6 @@
 
 package org.chromium.chrome.browser;
 
-import androidx.test.core.app.ApplicationProvider;
 import androidx.test.filters.MediumTest;
 
 import org.junit.After;
@@ -22,8 +21,9 @@
 import org.chromium.chrome.browser.tab.EmptyTabObserver;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
-import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
-import org.chromium.net.test.EmbeddedTestServer;
+import org.chromium.chrome.test.transit.ChromeTransitTestRules;
+import org.chromium.chrome.test.transit.FreshCtaTransitTestRule;
+import org.chromium.chrome.test.transit.page.WebPageStation;
 
 /** Test suite for navigator.getInstalledRelatedApps functionality. */
 @RunWith(ChromeJUnit4ClassRunner.class)
@@ -33,16 +33,16 @@
 })
 public class InstalledAppTest {
     @Rule
-    public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
+    public FreshCtaTransitTestRule mActivityTestRule =
+            ChromeTransitTestRules.freshChromeTabbedActivityRule();
 
     private static final String TEST_FILE = "/content/test/data/android/installedapp.html";
 
-    private EmbeddedTestServer mTestServer;
-
     private String mUrl;
 
     private Tab mTab;
     private InstalledAppUpdateWaiter mUpdateWaiter;
+    private WebPageStation mPage;
 
     /** Waits until the JavaScript code supplies a result. */
     private class InstalledAppUpdateWaiter extends EmptyTabObserver {
@@ -70,14 +70,8 @@
 
     @Before
     public void setUp() throws Exception {
-        mActivityTestRule.startMainActivityOnBlankPage();
-
-        mTestServer =
-                EmbeddedTestServer.createAndStartServer(
-                        ApplicationProvider.getApplicationContext());
-
-        mUrl = mTestServer.getURL(TEST_FILE);
-
+        mUrl = mActivityTestRule.getTestServer().getURL(TEST_FILE);
+        mPage = mActivityTestRule.startOnBlankPage();
         mTab = mActivityTestRule.getActivity().getActivityTab();
         mUpdateWaiter = new InstalledAppUpdateWaiter();
         ThreadUtils.runOnUiThreadBlocking(() -> mTab.addObserver(mUpdateWaiter));
@@ -100,7 +94,7 @@
     @MediumTest
     @Feature({"InstalledApp"})
     public void testGetInstalledRelatedApps() throws Exception {
-        mActivityTestRule.loadUrl(mUrl);
+        mPage = mPage.loadWebPageProgrammatically(mUrl);
         mActivityTestRule.runJavaScriptCodeInCurrentTab("doGetInstalledRelatedApps()");
         Assert.assertEquals("Success: 0 related apps", mUpdateWaiter.waitForUpdate());
     }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/JavaScriptEvalChromeTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/JavaScriptEvalChromeTest.java
index 68b226f..30c7851a 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/JavaScriptEvalChromeTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/JavaScriptEvalChromeTest.java
@@ -17,11 +17,11 @@
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.UrlUtils;
-import org.chromium.chrome.browser.customtabs.CustomTabActivityTestRule;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
-import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
+import org.chromium.chrome.test.transit.ChromeTransitTestRules;
+import org.chromium.chrome.test.transit.FreshCtaTransitTestRule;
 import org.chromium.chrome.test.util.ChromeTabUtils;
 import org.chromium.content_public.browser.test.util.JavaScriptUtils;
 
@@ -32,10 +32,8 @@
 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
 public class JavaScriptEvalChromeTest {
     @Rule
-    public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
-
-    @Rule
-    public CustomTabActivityTestRule mCustomTabActivityTestRule = new CustomTabActivityTestRule();
+    public FreshCtaTransitTestRule mActivityTestRule =
+            ChromeTransitTestRules.freshChromeTabbedActivityRule();
 
     private static final String JSTEST_URL =
             UrlUtils.encodeHtmlDataUri(
@@ -48,7 +46,7 @@
 
     @Before
     public void setUp() {
-        mActivityTestRule.startMainActivityWithURL(JSTEST_URL);
+        mActivityTestRule.startOnUrl(JSTEST_URL);
     }
 
     /**
@@ -64,7 +62,7 @@
         ChromeTabUtils.newTabFromMenu(
                 InstrumentationRegistry.getInstrumentation(), mActivityTestRule.getActivity());
         tab2 = mActivityTestRule.getActivity().getActivityTab();
-        mActivityTestRule.loadUrl(JSTEST_URL);
+        mActivityTestRule.getActivityTestRule().loadUrl(JSTEST_URL);
         ChromeTabUtils.switchTabInCurrentTabModel(mActivityTestRule.getActivity(), tab1.getId());
 
         Assert.assertFalse("Tab didn't open", tab1 == tab2);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/page_info/PageInfoDiscoverabilityTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/page_info/PageInfoDiscoverabilityTest.java
index 1ab5e722..e109bc2 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/page_info/PageInfoDiscoverabilityTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/page_info/PageInfoDiscoverabilityTest.java
@@ -192,7 +192,7 @@
             parameters.add(
                     new ParameterSet()
                             .name("Chooser.Serial")
-                            .value(ContentSettingsType.SERIAL_CHOOSER_DATA, false));
+                            .value(ContentSettingsType.SERIAL_CHOOSER_DATA, true));
 
             return parameters;
         }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/site_settings/SiteSettingsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/site_settings/SiteSettingsTest.java
index 25c27d9..63f50ee5 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/site_settings/SiteSettingsTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/site_settings/SiteSettingsTest.java
@@ -148,6 +148,7 @@
 import org.chromium.components.user_prefs.UserPrefs;
 import org.chromium.content_public.browser.BrowserContextHandle;
 import org.chromium.content_public.common.ContentSwitches;
+import org.chromium.device.DeviceFeatureList;
 import org.chromium.device.geolocation.LocationProviderOverrider;
 import org.chromium.device.geolocation.MockLocationProvider;
 import org.chromium.ui.base.DeviceFormFactor;
@@ -169,7 +170,10 @@
     ContentSwitches.HOST_RESOLVER_RULES + "=MAP * 127.0.0.1",
     "ignore-certificate-errors"
 })
-@EnableFeatures({ChromeFeatureList.PRIVACY_SANDBOX_RELATED_WEBSITE_SETS_UI})
+@EnableFeatures({
+    ChromeFeatureList.PRIVACY_SANDBOX_RELATED_WEBSITE_SETS_UI,
+    DeviceFeatureList.BLUETOOTH_RFCOMM_ANDROID
+})
 // TODO(crbug.com/370008370): Update individual tests after launch.
 @DisableFeatures({
     ChromeFeatureList.ALWAYS_BLOCK_3PCS_INCOGNITO,
@@ -1450,7 +1454,7 @@
     public void testOnlyExpectedPreferencesShown() {
         // If you add a category in the SiteSettings UI, please update this total AND add a test for
         // it below, named "testOnlyExpectedPreferences<Category>".
-        Assert.assertEquals(34, SiteSettingsCategory.Type.NUM_ENTRIES);
+        Assert.assertEquals(35, SiteSettingsCategory.Type.NUM_ENTRIES);
     }
 
     @Test
@@ -2066,6 +2070,14 @@
     @Test
     @SmallTest
     @Feature({"Preferences"})
+    public void testOnlyExpectedPreferencesSerialPort() {
+        testExpectedPreferences(
+                SiteSettingsCategory.Type.SERIAL_PORT, BINARY_TOGGLE, BINARY_TOGGLE);
+    }
+
+    @Test
+    @SmallTest
+    @Feature({"Preferences"})
     public void testOnlyExpectedPreferencesUseStorage() {
         checkPreferencesForCategory(SiteSettingsCategory.Type.USE_STORAGE, NULL_ARRAY);
     }
@@ -2300,6 +2312,30 @@
     @Test
     @SmallTest
     @Feature({"Preferences"})
+    public void testAllowSerialPort() {
+        new TwoStatePermissionTestCase(
+                        "SerialPort",
+                        SiteSettingsCategory.Type.SERIAL_PORT,
+                        ContentSettingsType.SERIAL_GUARD,
+                        true)
+                .run();
+    }
+
+    @Test
+    @SmallTest
+    @Feature({"Preferences"})
+    public void testBlockSerialPort() {
+        new TwoStatePermissionTestCase(
+                        "SerialPort",
+                        SiteSettingsCategory.Type.SERIAL_PORT,
+                        ContentSettingsType.SERIAL_GUARD,
+                        false)
+                .run();
+    }
+
+    @Test
+    @SmallTest
+    @Feature({"Preferences"})
     public void testAllowAutomaticDownloads() {
         new TwoStatePermissionTestCase(
                         "AutomaticDownloads",
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/site_settings/WebsitePermissionsFetcherTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/site_settings/WebsitePermissionsFetcherTest.java
index 11d000e..4fbb3b7 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/site_settings/WebsitePermissionsFetcherTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/site_settings/WebsitePermissionsFetcherTest.java
@@ -85,7 +85,8 @@
 @CommandLineFlags.Add({
     ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
     WebsitePermissionsFetcherTest.ENABLE_EXPERIMENTAL_WEB_PLATFORM_FEATURES,
-    WebsitePermissionsFetcherTest.ENABLE_WEB_BLUETOOTH_NEW_PERMISSIONS_BACKEND
+    WebsitePermissionsFetcherTest.ENABLE_WEB_BLUETOOTH_NEW_PERMISSIONS_BACKEND,
+    WebsitePermissionsFetcherTest.ENABLE_BLUETOOTH_RFCOMM_ANDROID
 })
 @Batch(Batch.PER_CLASS)
 public class WebsitePermissionsFetcherTest {
@@ -103,6 +104,12 @@
     public static final String ENABLE_WEB_BLUETOOTH_NEW_PERMISSIONS_BACKEND =
             "enable-features=WebBluetoothNewPermissionsBackend";
 
+    /**
+     * Command line flag to enable Bluetooth RFCOMM support for serial ports on Android in tests.
+     */
+    public static final String ENABLE_BLUETOOTH_RFCOMM_ANDROID =
+            "enable-features=BluetoothRfcommAndroid";
+
     private static final BrowserContextHandle UNUSED_BROWSER_CONTEXT_HANDLE = null;
 
     private static final String[] PERMISSION_URLS = {
@@ -869,6 +876,13 @@
                         "Wireless",
                         "Object",
                         false));
+        websitePreferenceBridge.addChosenObjectInfo(
+                new ChosenObjectInfo(
+                        ContentSettingsType.SERIAL_CHOOSER_DATA,
+                        ORIGIN,
+                        "Serial",
+                        "Object",
+                        false));
 
         fetcher.fetchAllPreferences(
                 (sites) -> {
@@ -988,13 +1002,16 @@
                     // Check chooser info types.
                     ArrayList<ChosenObjectInfo> chosenObjectInfos =
                             new ArrayList<>(site.getChosenObjectInfo());
-                    assertEquals(2, chosenObjectInfos.size());
+                    assertEquals(3, chosenObjectInfos.size());
                     assertEquals(
                             ContentSettingsType.BLUETOOTH_CHOOSER_DATA,
                             chosenObjectInfos.get(0).getContentSettingsType());
                     assertEquals(
                             ContentSettingsType.USB_CHOOSER_DATA,
                             chosenObjectInfos.get(1).getContentSettingsType());
+                    assertEquals(
+                            ContentSettingsType.SERIAL_CHOOSER_DATA,
+                            chosenObjectInfos.get(2).getContentSettingsType());
                 });
     }
 
@@ -1419,7 +1436,8 @@
                 new ArrayList<>(
                         Arrays.asList(
                                 SiteSettingsCategory.Type.USB,
-                                SiteSettingsCategory.Type.BLUETOOTH));
+                                SiteSettingsCategory.Type.BLUETOOTH,
+                                SiteSettingsCategory.Type.SERIAL_PORT));
 
         for (@SiteSettingsCategory.Type int type : chooserDataTypes) {
             WebsitePermissionsFetcher fetcher =
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/tab/TabArchiverTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/tab/TabArchiverTest.java
index 0e6e446..ae4281e4b6 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/tab/TabArchiverTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/tab/TabArchiverTest.java
@@ -177,7 +177,7 @@
 
     @Test
     @MediumTest
-    public void testArchiveThenUnarchiveTab() {
+    public void testArchiveThenUnarchiveTab() throws Exception {
         Tab tab =
                 sActivityTestRule.loadUrlInNewTab(
                         sActivityTestRule.getTestServer().getURL(TEST_PATH),
@@ -235,7 +235,7 @@
 
     @Test
     @MediumTest
-    public void testArchiveThenUnarchiveTab_NoTimestampUpdate() {
+    public void testArchiveThenUnarchiveTab_NoTimestampUpdate() throws Exception {
         Tab tab =
                 sActivityTestRule.loadUrlInNewTab(
                         sActivityTestRule.getTestServer().getURL(TEST_PATH),
@@ -293,7 +293,7 @@
     @Test
     @MediumTest
     @DisableFeatures(ChromeFeatureList.ANDROID_TAB_DECLUTTER_ARCHIVE_TAB_GROUPS)
-    public void testGroupedTabsAreNotArchived() {
+    public void testGroupedTabsAreNotArchived() throws Exception {
         sActivityTestRule.loadUrlInNewTab(
                 sActivityTestRule.getTestServer().getURL(TEST_PATH), /* incognito= */ false);
         sActivityTestRule.loadUrlInNewTab(
@@ -345,7 +345,7 @@
     @Test
     @MediumTest
     @EnableFeatures(ChromeFeatureList.ANDROID_TAB_DECLUTTER_ARCHIVE_TAB_GROUPS)
-    public void testGroupedTabsAreArchived() {
+    public void testGroupedTabsAreArchived() throws Exception {
         sActivityTestRule.loadUrlInNewTab(
                 sActivityTestRule.getTestServer().getURL(TEST_PATH), /* incognito= */ false);
         sActivityTestRule.loadUrlInNewTab(
@@ -398,7 +398,7 @@
     @MediumTest
     @DisableFeatures(ChromeFeatureList.ANDROID_TAB_DECLUTTER_ARCHIVE_TAB_GROUPS)
     @EnableFeatures(ChromeFeatureList.ANDROID_TAB_DECLUTTER_ARCHIVE_DUPLICATE_TABS)
-    public void testGroupedDuplicateTabsAreNotArchived() {
+    public void testGroupedDuplicateTabsAreNotArchived() throws Exception {
         sActivityTestRule.loadUrlInNewTab(
                 sActivityTestRule.getTestServer().getURL(TEST_PATH), /* incognito= */ false);
         sActivityTestRule.loadUrlInNewTab(
@@ -455,7 +455,7 @@
     @Test
     @MediumTest
     @EnableFeatures(ChromeFeatureList.ANDROID_TAB_DECLUTTER_ARCHIVE_DUPLICATE_TABS)
-    public void testDuplicateTabsAreArchived() {
+    public void testDuplicateTabsAreArchived() throws Exception {
         // Tab 2
         sActivityTestRule.loadUrlInNewTab(
                 sActivityTestRule.getTestServer().getURL(TEST_PATH), /* incognito= */ false);
@@ -507,7 +507,7 @@
     @Test
     @MediumTest
     @EnableFeatures(ChromeFeatureList.ANDROID_TAB_DECLUTTER_ARCHIVE_DUPLICATE_TABS)
-    public void testDuplicateTabsAreNotArchivedWithSwitchOff() {
+    public void testDuplicateTabsAreNotArchivedWithSwitchOff() throws Exception {
         // Tab 2
         sActivityTestRule.loadUrlInNewTab(
                 sActivityTestRule.getTestServer().getURL(TEST_PATH), /* incognito= */ false);
@@ -552,7 +552,7 @@
     @MediumTest
     @DisableFeatures(ChromeFeatureList.ANDROID_TAB_DECLUTTER_ARCHIVE_TAB_GROUPS)
     @EnableFeatures(ChromeFeatureList.ANDROID_TAB_DECLUTTER_ARCHIVE_DUPLICATE_TABS)
-    public void testDuplicateTabInGroupIsNotArchived_BaseDuplicateOutOfGroup() {
+    public void testDuplicateTabInGroupIsNotArchived_BaseDuplicateOutOfGroup() throws Exception {
         // Tab 2
         sActivityTestRule.loadUrlInNewTab(
                 sActivityTestRule.getTestServer().getURL(TEST_PATH), /* incognito= */ false);
@@ -610,7 +610,7 @@
 
     @Test
     @MediumTest
-    public void testTabModelSelectorInactiveTabsAreArchived() {
+    public void testTabModelSelectorInactiveTabsAreArchived() throws Exception {
         runOnUiThreadBlocking(
                 () -> {
                     // Set the tab to expire after 1 hour to simplify testing.
@@ -717,15 +717,16 @@
 
     @Test
     @MediumTest
-    public void testTabModelSelectorUninitialized() {
+    public void testTabModelSelectorUninitialized() throws Exception {
         doReturn(false).when(mSelector).isTabStateInitialized();
         runOnUiThreadBlocking(() -> mTabArchiver.doArchivePass(mSelector));
         verify(mSelector, times(0)).getModel(anyBoolean());
     }
 
-    @Test(expected = AssertionError.class)
+    @Test
     @MediumTest
-    public void testTabModelSelectorInactiveTabsAreArchived_AssertsWhenDisabled() throws Throwable {
+    public void testTabModelSelectorInactiveTabsAreArchived_NoActionTakenWhenDisabled()
+            throws Exception {
         runOnUiThreadBlocking(
                 () -> {
                     mTabArchiveSettings.setArchiveEnabled(false);
@@ -747,24 +748,21 @@
         assertEquals(2, mRegularTabModel.getCount());
         assertEquals(0, mArchivedTabModel.getCount());
 
-        try {
-            // Send an event, similar to how TabWindowManager would.
-            runOnUiThreadBlocking(
-                    () ->
-                            mTabArchiver.doArchivePass(
-                                    sActivityTestRule
-                                            .getActivity()
-                                            .getTabModelSelectorSupplier()
-                                            .get()));
-        } catch (RuntimeException re) {
-            // The UI thread hop will wrap with a RuntimeException, we want to verify the cause.
-            throw re.getCause();
-        }
+        // Send an event, similar to how TabWindowManager would.
+        runOnUiThreadBlocking(
+                () ->
+                        mTabArchiver.doArchivePass(
+                                sActivityTestRule
+                                        .getActivity()
+                                        .getTabModelSelectorSupplier()
+                                        .get()));
+        assertEquals(2, mRegularTabModel.getCount());
+        assertEquals(0, mArchivedTabModel.getCount());
     }
 
     @Test
     @MediumTest
-    public void testArchivedTabParentRootIdsReset() {
+    public void testArchivedTabParentRootIdsReset() throws Exception {
         Tab tab =
                 sActivityTestRule.loadUrlInNewTab(
                         sActivityTestRule.getTestServer().getURL(TEST_PATH),
@@ -800,7 +798,7 @@
 
     @Test
     @MediumTest
-    public void testTabIdPresentInBothModelsDeletesRegularTab() {
+    public void testTabIdPresentInBothModelsDeletesRegularTab() throws Exception {
         Tab tab =
                 sActivityTestRule.loadUrlInNewTab(
                         sActivityTestRule.getTestServer().getURL(TEST_PATH),
@@ -948,7 +946,7 @@
 
     @Test
     @MediumTest
-    public void testTabArchiverDestroyedWhileCreatingPtd() throws TimeoutException {
+    public void testTabArchverDestroyedWhileCreatingPTD() throws TimeoutException {
         // Setup the clock to differentiate between PTD created by tab archiver versus the
         // verification code.
         long tabArchiverTimestamp = 99L;
@@ -985,7 +983,7 @@
 
     @Test
     @MediumTest
-    public void testTabArchiverDestroyedWhileDestroyingPtd() throws TimeoutException {
+    public void testTabArchverDestroyedWhileDestroyingPTD() throws TimeoutException {
         // Setup the clock to differentiate between PTD created by tab archiver versus the
         // verification code.
         long tabArchiverTimestamp = 99L;
diff --git a/chrome/android/junit/BUILD.gn b/chrome/android/junit/BUILD.gn
index 925eae75..300bbc7 100644
--- a/chrome/android/junit/BUILD.gn
+++ b/chrome/android/junit/BUILD.gn
@@ -1101,6 +1101,7 @@
       "//chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogMenuCoordinatorUnitTest.java",
       "//chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridItemLongPressOrchestratorUnitTest.java",
       "//chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupColorViewProviderUnitTest.java",
+      "//chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupCreationUiFlowUnitTest.java",
       "//chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupFaviconClusterUnitTest.java",
       "//chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupFaviconQuarterUnitTest.java",
       "//chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupLabellerUnitTest.java",
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/compositor/layouts/StaticLayoutUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/compositor/layouts/StaticLayoutUnitTest.java
index 97b36d9f..f0baaa6 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/compositor/layouts/StaticLayoutUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/compositor/layouts/StaticLayoutUnitTest.java
@@ -66,7 +66,10 @@
 /** Unit tests for {@link StaticLayout}. */
 @RunWith(BaseRobolectricTestRunner.class)
 @Config(manifest = Config.NONE)
-@EnableFeatures(ChromeFeatureList.AVOID_SELECTED_TAB_FOCUS_ON_LAYOUT_DONE_SHOWING)
+@EnableFeatures({
+    ChromeFeatureList.AVOID_SELECTED_TAB_FOCUS_ON_LAYOUT_DONE_SHOWING,
+    ChromeFeatureList.REMOVE_TAB_FOCUS_ON_SHOWING_AND_SELECT
+})
 public class StaticLayoutUnitTest {
 
     private static final int TAB1_ID = 0;
@@ -354,7 +357,12 @@
         doReturn(true).when(mTabView).requestFocus();
 
         mStaticLayout.doneShowing();
-        verify(mTabView).requestFocus();
+
+        if (ChromeFeatureList.isEnabled(ChromeFeatureList.REMOVE_TAB_FOCUS_ON_SHOWING_AND_SELECT)) {
+            verify(mTabView, never()).requestFocus();
+        } else {
+            verify(mTabView).requestFocus();
+        }
     }
 
     @Test
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/compositor/overlays/strip/reorder/TabDragSourceTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/compositor/overlays/strip/reorder/TabDragSourceTest.java
index 62b4a34..95be4c0 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/compositor/overlays/strip/reorder/TabDragSourceTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/compositor/overlays/strip/reorder/TabDragSourceTest.java
@@ -31,6 +31,7 @@
 import android.content.res.Resources;
 import android.graphics.Point;
 import android.graphics.PointF;
+import android.net.Uri;
 import android.os.Build;
 import android.os.Build.VERSION_CODES;
 import android.text.format.DateUtils;
@@ -100,6 +101,7 @@
 import org.chromium.ui.dragdrop.DropDataAndroid;
 import org.chromium.ui.util.XrUtils;
 import org.chromium.ui.widget.ToastManager;
+import org.chromium.url.GURL;
 
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
@@ -705,14 +707,18 @@
         doTestDropInStripDestination(
                 /* isInDesktopWindow= */ false,
                 /* isGroupDrag= */ false,
-                /* isGroupShared= */ false);
+                /* isGroupShared= */ false,
+                /* mhtmlTabTitle= */ null);
     }
 
     /** Test for Tab Group Drag {@link #ONDRAG_TEST_CASES} - Scenario D.1 */
     @Test
     public void test_onDrag_dropInStrip_destination_tabGroup() {
         doTestDropInStripDestination(
-                /* isInDesktopWindow= */ true, /* isGroupDrag= */ true, /* isGroupShared= */ false);
+                /* isInDesktopWindow= */ true,
+                /* isGroupDrag= */ true,
+                /* isGroupShared= */ false,
+                /* mhtmlTabTitle= */ null);
     }
 
     /** Test for Shared Tab Group Drag {@link #ONDRAG_TEST_CASES} - Scenario D.1 */
@@ -720,7 +726,32 @@
     public void test_onDrag_dropInStrip_destination_sharedTabGroup() {
         setupTabGroup(/* isGroupShared= */ true);
         doTestDropInStripDestination(
-                /* isInDesktopWindow= */ true, /* isGroupDrag= */ true, /* isGroupShared= */ true);
+                /* isInDesktopWindow= */ true,
+                /* isGroupDrag= */ true,
+                /* isGroupShared= */ true,
+                /* mhtmlTabTitle= */ null);
+    }
+
+    /** Test for Tab Group Drag {@link #ONDRAG_TEST_CASES} - Scenario D.1 */
+    @Test
+    public void test_onDrag_dropInStrip_hasMhtmlTab_destination_tabGroup() {
+        String url = "file:///example.mhtml";
+        Uri uri = Uri.parse(url);
+        GURL gurl = new GURL(uri.toString());
+        String mhtmlTabTitle = "mhtmlTab";
+        doReturn(gurl).when(mGroupedTab1).getUrl();
+        doReturn(mhtmlTabTitle).when(mGroupedTab1).getTitle();
+        mTabGroupMetadata =
+                TabGroupMetadataExtractor.extractTabGroupMetadata(
+                        mTabGroupBeingDragged,
+                        /* sourceWindowIndex= */ -1,
+                        mGroupedTab1.getId(),
+                        /* isGroupShared= */ false);
+        doTestDropInStripDestination(
+                /* isInDesktopWindow= */ false,
+                /* isGroupDrag= */ true,
+                /* isGroupShared= */ false,
+                /* mhtmlTabTitle= */ mhtmlTabTitle);
     }
 
     /** Test for Desktop Window {@link #ONDRAG_TEST_CASES} - Scenario D.1 */
@@ -729,7 +760,8 @@
         doTestDropInStripDestination(
                 /* isInDesktopWindow= */ true,
                 /* isGroupDrag= */ false,
-                /* isGroupShared= */ false);
+                /* isGroupShared= */ false,
+                /* mhtmlTabTitle= */ null);
     }
 
     /** Test for Tab Drag {@link #ONDRAG_TEST_CASES} - Scenario D.2 */
@@ -1078,13 +1110,7 @@
                 .dragExit(mSourceInstance)
                 .end(false);
 
-        assertNotNull(ShadowToast.getLatestToast());
-        TextView textView = (TextView) ShadowToast.getLatestToast().getView();
-        String actualText = textView == null ? "" : textView.getText().toString();
-        assertEquals(
-                "Text for toast shown does not match.",
-                ContextUtils.getApplicationContext().getString(R.string.max_number_of_windows),
-                actualText);
+        verifyToast(ContextUtils.getApplicationContext().getString(R.string.max_number_of_windows));
         if (!isGroupDrag) {
             histogramExpectation.assertExpected();
         }
@@ -1125,7 +1151,10 @@
     }
 
     private void doTestDropInStripDestination(
-            boolean isInDesktopWindow, boolean isGroupDrag, boolean isGroupShared) {
+            boolean isInDesktopWindow,
+            boolean isGroupDrag,
+            boolean isGroupShared,
+            String mhtmlTabTitle) {
         HistogramWatcher.Builder builder =
                 HistogramWatcher.newBuilder()
                         .expectIntRecord(
@@ -1148,8 +1177,19 @@
 
         when(mDestStripLayoutHelper.getTabIndexForTabDrop(anyFloat())).thenReturn(TAB_INDEX);
 
-        // Verify view moved to window.
+        // Invoke drop.
         invokeDropInDestinationStrip(/* dragEndRes= */ true, isGroupDrag, isGroupShared);
+
+        // Verify - drop failed and toast is shown for group that has mhtml tab.
+        if (mhtmlTabTitle != null) {
+            verifyViewNotMovedToWindow(isGroupDrag);
+            verifyToast(
+                    ContextUtils.getApplicationContext()
+                            .getString(R.string.tab_cannot_be_moved, mhtmlTabTitle));
+            return;
+        }
+
+        // Verify view moved to window.
         verifyViewMovedToWindow(isGroupDrag, TAB_INDEX);
 
         // Verify reorder mode cleared.
@@ -1178,14 +1218,10 @@
         verifyViewMovedToWindow(isGroupDrag, /* index= */ 5);
 
         // Verify toast.
-        assertNotNull(ShadowToast.getLatestToast());
-        TextView textView = (TextView) ShadowToast.getLatestToast().getView();
-        String actualText = textView == null ? "" : textView.getText().toString();
-        assertEquals(
-                "Text for toast shown does not match.",
+        verifyToast(
                 ContextUtils.getApplicationContext()
-                        .getString(R.string.tab_dropped_different_model),
-                actualText);
+                        .getString(R.string.tab_dropped_different_model));
+        assertNotNull(ShadowToast.getLatestToast());
     }
 
     private void doTestDropInDestinationToolbarContainer(boolean isGroupDrag) {
@@ -1613,4 +1649,11 @@
             verify(mDestMultiInstanceManager, times(1)).moveTabToWindow(any(), any(), eq(index));
         }
     }
+
+    private void verifyToast(String expectedText) {
+        assertNotNull(ShadowToast.getLatestToast());
+        TextView textView = (TextView) ShadowToast.getLatestToast().getView();
+        String actualText = textView == null ? "" : textView.getText().toString();
+        assertEquals("Text for toast shown does not match.", expectedText, actualText);
+    }
 }
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/dragdrop/ChromeDragAndDropBrowserDelegateUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/dragdrop/ChromeDragAndDropBrowserDelegateUnitTest.java
index ee65a8d..abd1bfd8 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/dragdrop/ChromeDragAndDropBrowserDelegateUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/dragdrop/ChromeDragAndDropBrowserDelegateUnitTest.java
@@ -379,6 +379,7 @@
                         tabIdsToUrls,
                         /* tabGroupColor= */ 0,
                         tabGroupTitle,
+                        /* mhtmlTabTitle= */ null,
                         /* tabGroupCollapsed= */ false,
                         /* isGroupShared= */ false,
                         /* isIncognito= */ false);
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/dragdrop/DragAndDropLauncherActivityUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/dragdrop/DragAndDropLauncherActivityUnitTest.java
index a8ec97c..64ab3c4 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/dragdrop/DragAndDropLauncherActivityUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/dragdrop/DragAndDropLauncherActivityUnitTest.java
@@ -254,6 +254,7 @@
                         tabIdsToUrls,
                         /* tabGroupColor= */ 0,
                         tabGroupTitle,
+                        /* mhtmlTabTitle= */ null,
                         /* tabGroupCollapsed= */ false,
                         /* isGroupShared= */ false,
                         /* isIncognito= */ false);
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/multiwindow/MultiInstanceManagerApi31UnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/multiwindow/MultiInstanceManagerApi31UnitTest.java
index b0a3e71..329d4cf8 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/multiwindow/MultiInstanceManagerApi31UnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/multiwindow/MultiInstanceManagerApi31UnitTest.java
@@ -1491,6 +1491,7 @@
                         /* tabIdsToUrls= */ null,
                         /* tabGroupColor= */ 0,
                         /* tabGroupTitle= */ null,
+                        /* mhtmlTabTitle= */ null,
                         /* tabGroupCollapsed= */ false,
                         isGroupShared,
                         /* isIncognito= */ false);
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/suggestions/tile/tile_edit_dialog/CustomTileEditMediatorUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/suggestions/tile/tile_edit_dialog/CustomTileEditMediatorUnitTest.java
index 3465a990..854dc21 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/suggestions/tile/tile_edit_dialog/CustomTileEditMediatorUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/suggestions/tile/tile_edit_dialog/CustomTileEditMediatorUnitTest.java
@@ -21,6 +21,7 @@
 
 import org.chromium.base.test.BaseRobolectricTestRunner;
 import org.chromium.base.test.util.Features.EnableFeatures;
+import org.chromium.build.annotations.Nullable;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.suggestions.tile.Tile;
 import org.chromium.chrome.browser.suggestions.tile.tile_edit_dialog.CustomTileEditDelegates.DialogMode;
@@ -30,7 +31,6 @@
 import org.chromium.url.GURL;
 
 /** Unit tests for {@link CustomTileEditMediator}. */
-/** Tests for {@link MostVisitedTilesViewBinder}. */
 @RunWith(BaseRobolectricTestRunner.class)
 @Config(manifest = Config.NONE)
 @EnableFeatures({ChromeFeatureList.MOST_VISITED_TILES_CUSTOMIZATION})
@@ -41,12 +41,10 @@
     @Mock private MediatorToView mViewDelegate;
     @Mock private Tile mOriginalTile;
 
-    private CustomTileEditMediator mMediator;
-
     @Test
     public void testShowAddNewTile() {
-        mMediator = new CustomTileEditMediator(mBrowserDelegate, mViewDelegate, null);
-        mMediator.show();
+        CustomTileEditMediator mediator = createAndSetupMediator(/* originalTile= */ null);
+        mediator.show();
 
         verify(mViewDelegate).setDialogMode(DialogMode.ADD_SHORTCUT);
         verify(mViewDelegate).setName("");
@@ -58,8 +56,8 @@
     public void testShowEditExistingTile() {
         when(mOriginalTile.getTitle()).thenReturn("Test Name");
         when(mOriginalTile.getUrl()).thenReturn(new GURL("http://test.com"));
-        mMediator = new CustomTileEditMediator(mBrowserDelegate, mViewDelegate, mOriginalTile);
-        mMediator.show();
+        CustomTileEditMediator mediator = createAndSetupMediator(mOriginalTile);
+        mediator.show();
 
         verify(mViewDelegate).setDialogMode(DialogMode.EDIT_SHORTCUT);
         verify(mViewDelegate).setName("Test Name");
@@ -69,8 +67,8 @@
 
     @Test
     public void testOnUrlTextChangedValidUrl() {
-        mMediator = new CustomTileEditMediator(mBrowserDelegate, mViewDelegate, null);
-        mMediator.onUrlTextChanged("http://valid.com");
+        CustomTileEditMediator mediator = createAndSetupMediator(/* originalTile= */ null);
+        mediator.onUrlTextChanged("http://valid.com");
 
         verify(mViewDelegate, never()).setUrlErrorByCode(anyInt());
         verify(mViewDelegate).toggleSaveButton(true);
@@ -78,8 +76,8 @@
 
     @Test
     public void testOnUrlTextChangedInvalidUrl() {
-        mMediator = new CustomTileEditMediator(mBrowserDelegate, mViewDelegate, null);
-        mMediator.onUrlTextChanged("invalid url");
+        CustomTileEditMediator mediator = createAndSetupMediator(/* originalTile= */ null);
+        mediator.onUrlTextChanged("invalid url");
 
         verify(mViewDelegate).setUrlErrorByCode(UrlErrorCode.INVALID_URL);
         verify(mViewDelegate).toggleSaveButton(false);
@@ -88,8 +86,8 @@
     @Test
     public void testOnUrlTextChangedDuplicateUrl() {
         when(mBrowserDelegate.isUrlDuplicate(any())).thenReturn(true);
-        mMediator = new CustomTileEditMediator(mBrowserDelegate, mViewDelegate, null);
-        mMediator.onUrlTextChanged("http://duplicate.com");
+        CustomTileEditMediator mediator = createAndSetupMediator(/* originalTile= */ null);
+        mediator.onUrlTextChanged("http://duplicate.com");
 
         verify(mViewDelegate).setUrlErrorByCode(UrlErrorCode.DUPLICATE_URL);
         verify(mViewDelegate).toggleSaveButton(false);
@@ -98,8 +96,8 @@
     @Test
     public void testOnUrlTextChangedOriginalUrlUnchanged() {
         when(mOriginalTile.getUrl()).thenReturn(new GURL("http://original.com"));
-        mMediator = new CustomTileEditMediator(mBrowserDelegate, mViewDelegate, mOriginalTile);
-        mMediator.onUrlTextChanged("http://original.com");
+        CustomTileEditMediator mediator = createAndSetupMediator(mOriginalTile);
+        mediator.onUrlTextChanged("http://original.com");
 
         verify(mBrowserDelegate, never()).isUrlDuplicate(any());
         verify(mViewDelegate, never()).setUrlErrorByCode(anyInt());
@@ -109,8 +107,8 @@
     @Test
     public void testOnSaveValidSubmit() {
         when(mBrowserDelegate.submitChange(any(), any())).thenReturn(true);
-        mMediator = new CustomTileEditMediator(mBrowserDelegate, mViewDelegate, null);
-        mMediator.onSave("Test", "http://valid.com");
+        CustomTileEditMediator mediator = createAndSetupMediator(/* originalTile= */ null);
+        mediator.onSave("Test", "http://valid.com");
 
         verify(mBrowserDelegate).closeEditDialog(true);
         verify(mViewDelegate, never()).setUrlErrorByCode(anyInt());
@@ -118,8 +116,8 @@
 
     @Test
     public void testOnSaveInvalidUrl() {
-        mMediator = new CustomTileEditMediator(mBrowserDelegate, mViewDelegate, null);
-        mMediator.onSave("Test", "invalid url");
+        CustomTileEditMediator mediator = createAndSetupMediator(/* originalTile= */ null);
+        mediator.onSave("Test", "invalid url");
 
         verify(mBrowserDelegate, never()).closeEditDialog(anyBoolean());
         verify(mViewDelegate).setUrlErrorByCode(UrlErrorCode.INVALID_URL);
@@ -129,8 +127,8 @@
     @Test
     public void testOnSaveDuplicateUrl() {
         when(mBrowserDelegate.submitChange(any(), any())).thenReturn(false);
-        mMediator = new CustomTileEditMediator(mBrowserDelegate, mViewDelegate, null);
-        mMediator.onSave("Test", "http://duplicate.com");
+        CustomTileEditMediator mediator = createAndSetupMediator(/* originalTile= */ null);
+        mediator.onSave("Test", "http://duplicate.com");
 
         verify(mBrowserDelegate, never()).closeEditDialog(anyBoolean());
         verify(mViewDelegate).setUrlErrorByCode(UrlErrorCode.DUPLICATE_URL);
@@ -139,9 +137,20 @@
 
     @Test
     public void testOnCancel() {
-        mMediator = new CustomTileEditMediator(mBrowserDelegate, mViewDelegate, null);
-        mMediator.onCancel();
+        CustomTileEditMediator mediator = createAndSetupMediator(/* originalTile= */ null);
+        mediator.onCancel();
 
         verify(mBrowserDelegate).closeEditDialog(false);
     }
+
+    /**
+     * Helper to create the Mediator, assuming mocks have been set up.
+     *
+     * @param originalTile The tile to edit, or null to add a new tile.
+     */
+    private CustomTileEditMediator createAndSetupMediator(@Nullable Tile originalTile) {
+        CustomTileEditMediator mediator = new CustomTileEditMediator(originalTile);
+        mediator.setDelegates(mViewDelegate, mBrowserDelegate);
+        return mediator;
+    }
 }
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/suggestions/tile/tile_edit_dialog/CustomTileEditViewUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/suggestions/tile/tile_edit_dialog/CustomTileEditViewUnitTest.java
index 40cc29f7..35b4948 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/suggestions/tile/tile_edit_dialog/CustomTileEditViewUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/suggestions/tile/tile_edit_dialog/CustomTileEditViewUnitTest.java
@@ -26,6 +26,7 @@
 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.chrome.R;
@@ -37,6 +38,7 @@
 
 /** Unit tests for {@link CustomTileEditView}. */
 @RunWith(BaseRobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
 public class CustomTileEditViewUnitTest {
 
     @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/suggestions/tile/tile_edit_dialog/OWNERS b/chrome/android/junit/src/org/chromium/chrome/browser/suggestions/tile/tile_edit_dialog/OWNERS
new file mode 100644
index 0000000..3b8ad10
--- /dev/null
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/suggestions/tile/tile_edit_dialog/OWNERS
@@ -0,0 +1,2 @@
+ckitagawa@chromium.org
+huangs@chromium.org
diff --git a/chrome/app/chrome_command_ids.h b/chrome/app/chrome_command_ids.h
index 29a5048..9a4fd3a 100644
--- a/chrome/app/chrome_command_ids.h
+++ b/chrome/app/chrome_command_ids.h
@@ -281,6 +281,7 @@
 #define IDC_ADD_TO_COMPARISON_TABLE_MENU 40291
 #define IDC_CREATE_NEW_COMPARISON_TABLE_WITH_TAB 40292
 #define IDC_SHOW_HISTORY_SIDE_PANEL     40293
+#define IDC_OPEN_GLIC  40294
 
 // Spell-check
 // Insert any additional suggestions before _LAST; these have to be consecutive.
diff --git a/chrome/app/chromium_strings.grd b/chrome/app/chromium_strings.grd
index c0f11c0..5f3e89e 100644
--- a/chrome/app/chromium_strings.grd
+++ b/chrome/app/chromium_strings.grd
@@ -2476,6 +2476,20 @@
         <message name="IDS_PERFORMANCE_INTERVENTION_DIALOG_BODY_SINGULAR_V1" desc="Body text for a dialog suggesting users to deactivate certain tabs to improve performance.">
           This tab is using extra resources. To improve your performance, let Chromium make it inactive.
         </message>
+        <if expr="use_titlecase">
+          <message name="IDS_PERFORMANCE_INTERVENTION_DIALOG_TITLE_UPDATED_V3" desc="Title text for a dialog with information on tabs that could be causing performance issues.">
+            {NUM_TABS, plural,
+            =1 {Chromium Recommends Pausing the Tab Slowing Your Browser}
+            other {Chromium Recommends Pausing the Tabs Slowing Your Browser}}
+          </message>
+        </if>
+        <if expr="not use_titlecase">
+          <message name="IDS_PERFORMANCE_INTERVENTION_DIALOG_TITLE_UPDATED_V3" desc="Title text for a dialog with information on tabs that could be causing performance issues.">
+            {NUM_TABS, plural,
+            =1 {Chromium recommends pausing the tab slowing your browser}
+            other {Chromium recommends pausing the tabs slowing your browser}}
+          </message>
+        </if>
       </if>
 
       <!-- Delete Browsing Data -->
diff --git a/chrome/app/chromium_strings_grd/IDS_PERFORMANCE_INTERVENTION_DIALOG_TITLE_UPDATED_V3.png.sha1 b/chrome/app/chromium_strings_grd/IDS_PERFORMANCE_INTERVENTION_DIALOG_TITLE_UPDATED_V3.png.sha1
new file mode 100644
index 0000000..2820d10
--- /dev/null
+++ b/chrome/app/chromium_strings_grd/IDS_PERFORMANCE_INTERVENTION_DIALOG_TITLE_UPDATED_V3.png.sha1
@@ -0,0 +1 @@
+c48670b8b57b99df3664d125ee35792129c2693a
\ No newline at end of file
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index b9fc8d6b..311d4771 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -7577,6 +7577,9 @@
       <message name="IDS_GOOGLE_SEARCH_BOX_CONTEXTUAL_ERROR_TEXT" desc="The error text displayed in the overlay contextual searchbox ghost loader. It is meant to inform the user that they can still ask questions about the page even though suggestions were failed to be generated.">
         No suggested questions available
       </message>
+      <message name="IDS_GOOGLE_SEARCH_BOX_CONTEXTUAL_LOADING_HINT_PRIMARY_ALT" desc="The hint text displayed in the overlay contextual searchbox ghost loader. It is meant to inform the user that suggestions are still being generated.">
+        Generating suggestions…
+      </message>
       <message name="IDS_GOOGLE_SEARCH_BOX_CONTEXTUAL_NO_SUGGEST_TEXT" desc="The hint text displayed in the overlay contextual searchbox ghost loader. It is meant to inform the user that they can ask questions about the page.">
         Try asking things like “summarize this page” or your own question.
       </message>
@@ -18410,11 +18413,8 @@
     </if>
     <!-- Performance Intervention Strings-->
     <if expr="not is_android">
-        <message name="IDS_PERFORMANCE_INTERVENTION_DIALOG_TITLE_V1" desc="Title text for a dialog with information on tabs that could be causing performance issues.">
-        Performance issue alert
-        </message>
         <message name="IDS_PERFORMANCE_INTERVENTION_CLOSE_BUTTON_ACCNAME" desc="The accessibility text for the performance intervention close button that removes an item from the list of tabs to deactivate when clicked.">
-        Remove <ph name="SITE_NAME">$1<ex>Google</ex></ph> from list of tabs to make inactive
+          Remove <ph name="SITE_NAME">$1<ex>Google</ex></ph> from list of tabs to make inactive
         </message>
         <message name="IDS_PERFORMANCE_INTERVENTION_SINGLE_SUGGESTED_ROW_ACCNAME" desc="The accessibility text for a suggested tab in the performance intervention dialog when the user have removed all but one suggested tab.">
           You can make this tab inactive or refresh to see the full list again
@@ -18425,13 +18425,59 @@
           other {List box with {NUM_TABS} items}}
         </message>
       <if expr="use_titlecase">
+        <message name="IDS_PERFORMANCE_INTERVENTION_DIALOG_TITLE_V1" desc="Title text for a dialog with information on tabs that could be causing performance issues.">
+          Performance Issue Alert
+        </message>
+        <message name="IDS_PERFORMANCE_INTERVENTION_DIALOG_TITLE_UPDATED_V1" desc="Title text for a dialog with information on tabs that could be causing performance issues.">
+          {NUM_TABS, plural,
+          =1 {Fix the Tab Slowing Your Browser}
+          other {Fix the Tabs Slowing Your Browser}}
+        </message>
+        <message name="IDS_PERFORMANCE_INTERVENTION_DIALOG_TITLE_UPDATED_V2" desc="Title text for a dialog with information on tabs that could be causing performance issues.">
+          {NUM_TABS, plural,
+          =1 {Pause the Tab Slowing Your Browser}
+          other {Pause the Tabs Slowing Your Browser}}
+        </message>
         <message name="IDS_PERFORMANCE_INTERVENTION_DEACTIVATE_TABS_BUTTON_V1" desc="In Title Case: Label on a dialog button that deactivate the specified tabs in the dialog when clicked.">
-        Fix Now
+          Fix Now
+        </message>
+        <message name="IDS_PERFORMANCE_INTERVENTION_DEACTIVATE_TABS_BUTTON_UPDATED_V1" desc="In Title Case: Label on a dialog button that deactivate the specified tabs in the dialog when clicked.">
+          {NUM_TABS, plural,
+          =1 {Fix This Tab}
+          other {Fix These Tabs}}
+        </message>
+        <message name="IDS_PERFORMANCE_INTERVENTION_DEACTIVATE_TABS_BUTTON_UPDATED_V2" desc="In Title Case: Label on a dialog button that deactivate the specified tabs in the dialog when clicked.">
+          {NUM_TABS, plural,
+          =1 {Pause This Tab}
+          other {Pause These Tabs}}
         </message>
       </if>
       <if expr="not use_titlecase">
+        <message name="IDS_PERFORMANCE_INTERVENTION_DIALOG_TITLE_V1" desc="Title text for a dialog with information on tabs that could be causing performance issues.">
+          Performance issue alert
+        </message>
+        <message name="IDS_PERFORMANCE_INTERVENTION_DIALOG_TITLE_UPDATED_V1" desc="Title text for a dialog with information on tabs that could be causing performance issues.">
+          {NUM_TABS, plural,
+          =1 {Fix the tab slowing your browser}
+          other {Fix the tabs slowing your browser}}
+        </message>
+        <message name="IDS_PERFORMANCE_INTERVENTION_DIALOG_TITLE_UPDATED_V2" desc="Title text for a dialog with information on tabs that could be causing performance issues.">
+          {NUM_TABS, plural,
+          =1 {Pause the tab slowing your browser}
+          other {Pause the tabs slowing your browser}}
+        </message>
         <message name="IDS_PERFORMANCE_INTERVENTION_DEACTIVATE_TABS_BUTTON_V1" desc="Label on a dialog button that deactivate the specified tabs in the dialog when clicked.">
-        Fix now
+          Fix now
+        </message>
+        <message name="IDS_PERFORMANCE_INTERVENTION_DEACTIVATE_TABS_BUTTON_UPDATED_V1" desc="Label on a dialog button that deactivate the specified tabs in the dialog when clicked.">
+          {NUM_TABS, plural,
+          =1 {Fix this tab}
+          other {Fix these tabs}}
+        </message>
+        <message name="IDS_PERFORMANCE_INTERVENTION_DEACTIVATE_TABS_BUTTON_UPDATED_V2" desc="Label on a dialog button that deactivate the specified tabs in the dialog when clicked.">
+          {NUM_TABS, plural,
+          =1 {Pause this tab}
+          other {Pause these tabs}}
         </message>
       </if>
       <message name="IDS_PERFORMANCE_INTERVENTION_DISMISS_BUTTON" desc="Label on a dialog button that dismisses the dialog when clicked.">
diff --git a/chrome/app/generated_resources_grd/IDS_GOOGLE_SEARCH_BOX_CONTEXTUAL_LOADING_HINT_PRIMARY_ALT.png.sha1 b/chrome/app/generated_resources_grd/IDS_GOOGLE_SEARCH_BOX_CONTEXTUAL_LOADING_HINT_PRIMARY_ALT.png.sha1
new file mode 100644
index 0000000..13f1a56
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_GOOGLE_SEARCH_BOX_CONTEXTUAL_LOADING_HINT_PRIMARY_ALT.png.sha1
@@ -0,0 +1 @@
+c4fc29d46e12c56c7448be5f0ff379b25b74c999
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_PERFORMANCE_INTERVENTION_DEACTIVATE_TABS_BUTTON_UPDATED_V1.png.sha1 b/chrome/app/generated_resources_grd/IDS_PERFORMANCE_INTERVENTION_DEACTIVATE_TABS_BUTTON_UPDATED_V1.png.sha1
new file mode 100644
index 0000000..d3a1515
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_PERFORMANCE_INTERVENTION_DEACTIVATE_TABS_BUTTON_UPDATED_V1.png.sha1
@@ -0,0 +1 @@
+69938426200edced9e1bdadb639ad885f47ac889
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_PERFORMANCE_INTERVENTION_DEACTIVATE_TABS_BUTTON_UPDATED_V2.png.sha1 b/chrome/app/generated_resources_grd/IDS_PERFORMANCE_INTERVENTION_DEACTIVATE_TABS_BUTTON_UPDATED_V2.png.sha1
new file mode 100644
index 0000000..fd390204
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_PERFORMANCE_INTERVENTION_DEACTIVATE_TABS_BUTTON_UPDATED_V2.png.sha1
@@ -0,0 +1 @@
+b5d65cf67b46d999db5161ba393473b79299aeb5
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_PERFORMANCE_INTERVENTION_DIALOG_TITLE_UPDATED_V1.png.sha1 b/chrome/app/generated_resources_grd/IDS_PERFORMANCE_INTERVENTION_DIALOG_TITLE_UPDATED_V1.png.sha1
new file mode 100644
index 0000000..d3a1515
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_PERFORMANCE_INTERVENTION_DIALOG_TITLE_UPDATED_V1.png.sha1
@@ -0,0 +1 @@
+69938426200edced9e1bdadb639ad885f47ac889
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_PERFORMANCE_INTERVENTION_DIALOG_TITLE_UPDATED_V2.png.sha1 b/chrome/app/generated_resources_grd/IDS_PERFORMANCE_INTERVENTION_DIALOG_TITLE_UPDATED_V2.png.sha1
new file mode 100644
index 0000000..fd390204
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_PERFORMANCE_INTERVENTION_DIALOG_TITLE_UPDATED_V2.png.sha1
@@ -0,0 +1 @@
+b5d65cf67b46d999db5161ba393473b79299aeb5
\ No newline at end of file
diff --git a/chrome/app/google_chrome_strings.grd b/chrome/app/google_chrome_strings.grd
index cc1e733..0b7565c8 100644
--- a/chrome/app/google_chrome_strings.grd
+++ b/chrome/app/google_chrome_strings.grd
@@ -2492,6 +2492,20 @@
         <message name="IDS_PERFORMANCE_INTERVENTION_DIALOG_BODY_SINGULAR_V1" desc="Body text for a dialog suggesting users to deactivate certain tabs to improve performance.">
           This tab is using extra resources. To improve your performance, let Chrome make it inactive.
         </message>
+        <if expr="use_titlecase">
+          <message name="IDS_PERFORMANCE_INTERVENTION_DIALOG_TITLE_UPDATED_V3" desc="Title text for a dialog with information on tabs that could be causing performance issues.">
+            {NUM_TABS, plural,
+            =1 {Chrome Recommends Pausing the Tab Slowing Your Browser}
+            other {Chrome Recommends Pausing the Tabs Slowing Your Browser}}
+          </message>
+        </if>
+        <if expr="not use_titlecase">
+          <message name="IDS_PERFORMANCE_INTERVENTION_DIALOG_TITLE_UPDATED_V3" desc="Title text for a dialog with information on tabs that could be causing performance issues.">
+            {NUM_TABS, plural,
+            =1 {Chrome recommends pausing the tab slowing your browser}
+            other {Chrome recommends pausing the tabs slowing your browser}}
+          </message>
+        </if>
       </if>
 
       <!-- Delete Browsing Data -->
diff --git a/chrome/app/google_chrome_strings_grd/IDS_PERFORMANCE_INTERVENTION_DIALOG_TITLE_UPDATED_V3.png.sha1 b/chrome/app/google_chrome_strings_grd/IDS_PERFORMANCE_INTERVENTION_DIALOG_TITLE_UPDATED_V3.png.sha1
new file mode 100644
index 0000000..6ee706e9
--- /dev/null
+++ b/chrome/app/google_chrome_strings_grd/IDS_PERFORMANCE_INTERVENTION_DIALOG_TITLE_UPDATED_V3.png.sha1
@@ -0,0 +1 @@
+56889a44c614a070f2c4c63cf46342e8a294b6b9
\ No newline at end of file
diff --git a/chrome/app/profiles_strings.grdp b/chrome/app/profiles_strings.grdp
index 971c520..96a4342 100644
--- a/chrome/app/profiles_strings.grdp
+++ b/chrome/app/profiles_strings.grdp
@@ -1093,5 +1093,17 @@
       other {These extensions are saved in your Google Account, but any data they've saved only on this device may be deleted}
       }
     </message>
+
+    <!-- History Sync Opt-in Desktop -->
+    <!-- TODO(crbug.com/406751006): Finalize the strings and make them translateable. -->
+    <message translateable="false" name="IDS_HISTORY_SYNC_OPT_IN_TITLE" desc="">
+      Save time, type less
+    </message>
+    <message translateable="false" name="IDS_HISTORY_SYNC_OPT_IN_SUBTITLE" desc="">
+      To quickly get back to sites you've visited, sync your tabs and history
+    </message>
+    <message translateable="false" name="IDS_HISTORY_SYNC_OPT_IN_DESCRIPTION" desc="">
+      You can stop syncing anytime in settings. Google may personalize Search and other services based on your history.
+    </message>
   </if>
 </grit-part>
diff --git a/chrome/app/settings_chromium_strings.grdp b/chrome/app/settings_chromium_strings.grdp
index 69882f4..9305b60 100644
--- a/chrome/app/settings_chromium_strings.grdp
+++ b/chrome/app/settings_chromium_strings.grdp
@@ -85,10 +85,10 @@
   </message>
 
   <!-- Autofill with AI Page-->
-  <message name="IDS_SETTINGS_AUTOFILL_AI_DESCRIPTION" desc="The description of the autofill AI button on chrome://settings/ai.">
+  <message name="IDS_SETTINGS_AUTOFILL_AI_DESCRIPTION" desc="The description of the autofill AI button on chrome://settings/autofill.">
     Chromium understands forms better and can autofill them faster for you
   </message>
-  <message name="IDS_SETTINGS_AUTOFILL_AI_TOGGLE_SUB_LABEL" desc="The description of the setting. By 'understand forms better', we mean that when a user submits a form on a website, and if they're using Autofill with AI, this feature can offer to save the user's submitted info. When Chrome saves that info, we save the submitted values and consider the page context.">
+  <message name="IDS_SETTINGS_AUTOFILL_AI_TOGGLE_SUB_LABEL" desc="The description of the setting. By 'understand forms better', we mean that when a user submits a form on a website, and if they're using Autofill with AI, this feature can offer to save the user's submitted info. When Chromium saves that info, we save the submitted values and consider the page context.">
     Chromium understands forms better and can autofill them faster for you
   </message>
   <!-- TODO(crbug.com/395807244): Make string translateable. -->
diff --git a/chrome/app/settings_google_chrome_strings.grdp b/chrome/app/settings_google_chrome_strings.grdp
index 248a696..0d6b08e6 100644
--- a/chrome/app/settings_google_chrome_strings.grdp
+++ b/chrome/app/settings_google_chrome_strings.grdp
@@ -83,7 +83,7 @@
   </message>
 
   <!-- Autofill with AI Page-->
-  <message name="IDS_SETTINGS_AUTOFILL_AI_DESCRIPTION" desc="The description of the autofill AI button on chrome://settings/ai.">
+  <message name="IDS_SETTINGS_AUTOFILL_AI_DESCRIPTION" desc="The description of the autofill AI button on chrome://settings/autofill.">
     Chrome understands forms better and can autofill them faster for you
   </message>
   <message name="IDS_SETTINGS_AUTOFILL_AI_TOGGLE_SUB_LABEL" desc="The description of the setting. By 'understand forms better', we mean that when a user submits a form on a website, and if they're using Autofill with AI, this feature can offer to save the user's submitted info. When Chrome saves that info, we save the submitted values and consider the page context.">
diff --git a/chrome/app/settings_strings.grdp b/chrome/app/settings_strings.grdp
index 1c75e870e..18fa9de8 100644
--- a/chrome/app/settings_strings.grdp
+++ b/chrome/app/settings_strings.grdp
@@ -412,9 +412,8 @@
   <message name="IDS_SETTINGS_AUTOFILL_DETAIL" desc="Description of what toggling the 'Autofill' setting does. Immediately underneath IDS_SETTINGS_AUTOFILL">
     Enable Autofill to fill out forms in a single click
   </message>
-  <!-- TODO(crbug.com/395807244): Make string translateable. -->
-  <message name="IDS_SETTINGS_AUTOFILL_DROPDOWN_NO_OPTION_SELECTED" desc="An option inside a dropdown that spells: No option selected. The user can select this option." translateable="false">
-    No option selected
+  <message name="IDS_SETTINGS_AUTOFILL_DROPDOWN_NO_OPTION_SELECTED" desc="The default option in a dropdown menu that serves both to suggest to the user that they select something and also allows them to not select anything (by leaving the default value chosen). For example, a country dropdown includes the values 'Select, Afghanistan, Albania, Algeria etc.'. The user can choose to leave 'Select' selected — effectively not choosing a country at all.">
+    Select
   </message>
   <message name="IDS_SETTINGS_AUTOFILL_CARD_DESCRIPTION" desc="The (accessibility) credit card description from the network of the card and last 4 digits of the card number.">
     <ph name="NETWORK_NAME">$1<ex>Visa</ex></ph> ending in <ph name="LAST_FOUR_DIGITS">$2<ex>1234</ex></ph>
@@ -617,55 +616,48 @@
   <message name="IDS_SETTINGS_AUTOFILL_AI_PAGE_TITLE" desc="The title of the page in Settings that allows managing the Autofill AI settings.">
     Autofill with AI
   </message>
-  <message name="IDS_SETTINGS_AUTOFILL_AI_TO_CONSIDER_NEW_FEATURE"  desc="One of the items on the Autofill AI settings page that describes what the user should consider before enabling Autofill AI.">
+  <message name="IDS_SETTINGS_AUTOFILL_AI_TO_CONSIDER_NEW_FEATURE" desc="One of the items on the Autofill AI settings page that describes what the user should consider before enabling Autofill AI.">
     This feature uses AI, is in early development, and won’t always get it right
   </message>
-  <message name="IDS_SETTINGS_AUTOFILL_AI_TO_CONSIDER_DATA_USAGE"  desc="One of the items on the Autofill AI settings page that describes what the user should consider before enabling Autofill AI.">
+  <message name="IDS_SETTINGS_AUTOFILL_AI_TO_CONSIDER_DATA_USAGE" desc="One of the items on the Autofill AI settings page that describes what the user should consider before enabling Autofill AI.">
     When you fill a form, your info and the page it comes from are sent to Google to generate suggestions
   </message>
-  <message name="IDS_SETTINGS_AUTOFILL_AI_TO_CONSIDER_STORAGE"  desc="One of the items on the Autofill AI settings page that describes what the user should consider before enabling Autofill AI.">
+  <message name="IDS_SETTINGS_AUTOFILL_AI_TO_CONSIDER_STORAGE" desc="One of the items on the Autofill AI settings page that describes what the user should consider before enabling Autofill AI.">
     Your saved information is stored on your device
   </message>
-  <message name="IDS_SETTINGS_AUTOFILL_AI_TO_CONSIDER_IMPROVEMENT"  desc="One of the items on the Autofill AI settings page that describes what the user should consider before enabling Autofill AI.">
+  <message name="IDS_SETTINGS_AUTOFILL_AI_TO_CONSIDER_IMPROVEMENT" desc="One of the items on the Autofill AI settings page that describes what the user should consider before enabling Autofill AI.">
     Data may be seen by human reviewers to improve this feature
   </message>
-  <message name="IDS_SETTINGS_AUTOFILL_AI_ENTITY_INSTANCES_HEADER"  desc="Header of the entity instance list on the Autofill with AI settings page.">
+  <message name="IDS_SETTINGS_AUTOFILL_AI_ENTITY_INSTANCES_HEADER" desc="Header of the entity instance list on the Autofill with AI settings page.">
     Saved information
   </message>
-  <message name="IDS_SETTINGS_AUTOFILL_AI_ENTITY_INSTANCES_NONE"  desc="Placeholder for empty entity instance list on the Autofill with AI settings page.">
+  <message name="IDS_SETTINGS_AUTOFILL_AI_ENTITY_INSTANCES_NONE" desc="Placeholder for empty entity instance list on the Autofill with AI settings page.">
     Saved information will appear here
   </message>
-  <!-- TODO(crbug.com/395807244): Make string translateable. -->
-  <message name="IDS_SETTINGS_AUTOFILL_AI_MORE_ACTIONS_FOR_ENTITY_INSTANCE" desc="The (accessibility) title of the More Button, which brings up a menu of actions that can be taken on a stored entity instance. The entity instance is described by its label and sublabel. The More Button has the icon of 3 vertical dots." translateable = "false">
-    More actions for <ph name="ENTITY_INSTANCE_LABEL">$1<ex>Toyota</ex></ph>, <ph name="ENTITY_INSTANCE_SUBLABEL">$2<ex>Car</ex></ph>
+  <message name="IDS_SETTINGS_AUTOFILL_AI_MORE_ACTIONS_FOR_ENTITY_INSTANCE" desc="The accessibility label of the More options menu. Options in the menu include 'Edit' and 'Delete'. If including specific verbs in your language adds undo complexity, it's also acceptable to use something like: 'More options for 'label', 'sublabel''. Examples of 'label' and 'sublabel' include 'Toyota Corolla, Vehicle'.">
+    More options to edit or delete <ph name="ENTITY_INSTANCE_LABEL">$1<ex>Toyota</ex></ph>, <ph name="ENTITY_INSTANCE_SUBLABEL">$2<ex>Car</ex></ph>
   </message>
-  <!-- TODO(crbug.com/395807244): Make string translateable. -->
-  <message name="IDS_SETTINGS_AUTOFILL_AI_DELETE_ENTITY_INSTANCE_DIALOG_TITLE"  desc="The title of the delete entity instance confirmation dialog, on the Autofill AI settings page." translateable = "false">
+  <!-- TODO(crbug.com/393318914): Change this string in favor of entity specific strings. -->
+  <message name="IDS_SETTINGS_AUTOFILL_AI_DELETE_ENTITY_INSTANCE_DIALOG_TITLE" desc="A title of a dialog within settings that allows a user to delete an entry. The user opened this dialog by choosing 'Delete' from the more options menu.">
     Delete info?
   </message>
-  <!-- TODO(crbug.com/395807244): Make string translateable. -->
-  <message name="IDS_SETTINGS_AUTOFILL_AI_DELETE_ENTITY_INSTANCE_DIALOG_TEXT"  desc="The body text of the delete entity instance confirmation dialog, on the Autofill AI settings page." translateable = "false">
-    Autofill will have less info for filling out forms.
+  <message name="IDS_SETTINGS_AUTOFILL_AI_DELETE_ENTITY_INSTANCE_DIALOG_TEXT" desc="The body text of the delete entity instance confirmation dialog, on the Autofill AI settings page.">
+    Autofill won't have this info on this device to help fill out forms
   </message>
-  <!-- TODO(crbug.com/395807244): Make string translateable. -->
-  <message name="IDS_SETTINGS_AUTOFILL_AI_ACCESSIBILITY_LABEL_MONTH_DROPDOWN" desc="The accessibility label on the month dropdown of a date field, to let users know the dropdown corresponds to the month." translateable = "false">
-    <ph name="DROPDOWN_NAME">$1<ex>Expiration date</ex></ph>: month
+  <message name="IDS_SETTINGS_AUTOFILL_AI_ACCESSIBILITY_LABEL_MONTH_DROPDOWN" desc="The accessibility label on the month dropdown of a date field, to let users know the dropdown corresponds to the month.">
+    <ph name="DROPDOWN_NAME">$1<ex>Expiration date</ex></ph> month
   </message>
-  <!-- TODO(crbug.com/395807244): Make string translateable. -->
-  <message name="IDS_SETTINGS_AUTOFILL_AI_ACCESSIBILITY_LABEL_DAY_DROPDOWN" desc="The accessibility label on the day dropdown of a date field, to let users know the dropdown corresponds to the day." translateable = "false">
-    <ph name="DROPDOWN_NAME">$1<ex>Expiration date</ex></ph>: day
+  <message name="IDS_SETTINGS_AUTOFILL_AI_ACCESSIBILITY_LABEL_DAY_DROPDOWN" desc="The accessibility label on the day dropdown of a date field, to let users know the dropdown corresponds to the day.">
+    <ph name="DROPDOWN_NAME">$1<ex>Expiration date</ex></ph> day
   </message>
-  <!-- TODO(crbug.com/395807244): Make string translateable. -->
-  <message name="IDS_SETTINGS_AUTOFILL_AI_ACCESSIBILITY_LABEL_YEAR_DROPDOWN" desc="The accessibility label on the year dropdown of a date field, to let users know the dropdown corresponds to the year." translateable = "false">
-    <ph name="DROPDOWN_NAME">$1<ex>Expiration date</ex></ph>: year
+  <message name="IDS_SETTINGS_AUTOFILL_AI_ACCESSIBILITY_LABEL_YEAR_DROPDOWN" desc="The accessibility label on the year dropdown of a date field, to let users know the dropdown corresponds to the year.">
+    <ph name="DROPDOWN_NAME">$1<ex>Expiration date</ex></ph> year
   </message>
-  <!-- TODO(crbug.com/395807244): Make string translateable. -->
-  <message name="IDS_SETTINGS_AUTOFILL_AI_ADD_OR_EDIT_DIALOG_DATE_VALIDATION_ERROR"  desc="An error message if the user clicks Save with an invalid date." translateable = "false">
+  <message name="IDS_SETTINGS_AUTOFILL_AI_ADD_OR_EDIT_DIALOG_DATE_VALIDATION_ERROR" desc="An error message that appears beneath a date picker in the case that the user doesn't add a full date. For example, the user sees this error if they set the month but not the day or year, or choose an invalid date like February 30.">
     Check the date
   </message>
-  <!-- TODO(crbug.com/395807244): Make string translateable. -->
-  <message name="IDS_SETTINGS_AUTOFILL_AI_ADD_OR_EDIT_DIALOG_VALIDATION_ERROR"  desc="An error message if the user clicks Save without having filled at least one value in the form." translateable = "false">
-    Fill at least one field before saving.
+  <message name="IDS_SETTINGS_AUTOFILL_AI_ADD_OR_EDIT_DIALOG_VALIDATION_ERROR" desc="An error message if the user clicks Save without having filled at least one value in the form.">
+    Fill at least one field before saving
   </message>
 
   <if expr="is_win or is_macosx">
diff --git a/chrome/app/settings_strings_grdp/IDS_SETTINGS_AUTOFILL_AI_ACCESSIBILITY_LABEL_DAY_DROPDOWN.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_AUTOFILL_AI_ACCESSIBILITY_LABEL_DAY_DROPDOWN.png.sha1
new file mode 100644
index 0000000..a22ee34
--- /dev/null
+++ b/chrome/app/settings_strings_grdp/IDS_SETTINGS_AUTOFILL_AI_ACCESSIBILITY_LABEL_DAY_DROPDOWN.png.sha1
@@ -0,0 +1 @@
+36dc288e9234480ddc0492294382b27fd0ca1a82
\ No newline at end of file
diff --git a/chrome/app/settings_strings_grdp/IDS_SETTINGS_AUTOFILL_AI_ACCESSIBILITY_LABEL_MONTH_DROPDOWN.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_AUTOFILL_AI_ACCESSIBILITY_LABEL_MONTH_DROPDOWN.png.sha1
new file mode 100644
index 0000000..26622486
--- /dev/null
+++ b/chrome/app/settings_strings_grdp/IDS_SETTINGS_AUTOFILL_AI_ACCESSIBILITY_LABEL_MONTH_DROPDOWN.png.sha1
@@ -0,0 +1 @@
+23832983a486c7ab79e78d5c55989641cb0cf214
\ No newline at end of file
diff --git a/chrome/app/settings_strings_grdp/IDS_SETTINGS_AUTOFILL_AI_ACCESSIBILITY_LABEL_YEAR_DROPDOWN.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_AUTOFILL_AI_ACCESSIBILITY_LABEL_YEAR_DROPDOWN.png.sha1
new file mode 100644
index 0000000..6f98c56
--- /dev/null
+++ b/chrome/app/settings_strings_grdp/IDS_SETTINGS_AUTOFILL_AI_ACCESSIBILITY_LABEL_YEAR_DROPDOWN.png.sha1
@@ -0,0 +1 @@
+a645a69162666638bbb6d16b06520c391fa1aa46
\ No newline at end of file
diff --git a/chrome/app/settings_strings_grdp/IDS_SETTINGS_AUTOFILL_AI_ADD_OR_EDIT_DIALOG_DATE_VALIDATION_ERROR.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_AUTOFILL_AI_ADD_OR_EDIT_DIALOG_DATE_VALIDATION_ERROR.png.sha1
new file mode 100644
index 0000000..03798e1
--- /dev/null
+++ b/chrome/app/settings_strings_grdp/IDS_SETTINGS_AUTOFILL_AI_ADD_OR_EDIT_DIALOG_DATE_VALIDATION_ERROR.png.sha1
@@ -0,0 +1 @@
+5fcf8c63be10daa1256ffb7d3d9b762ac5bebe8d
\ No newline at end of file
diff --git a/chrome/app/settings_strings_grdp/IDS_SETTINGS_AUTOFILL_AI_ADD_OR_EDIT_DIALOG_VALIDATION_ERROR.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_AUTOFILL_AI_ADD_OR_EDIT_DIALOG_VALIDATION_ERROR.png.sha1
new file mode 100644
index 0000000..79979e9
--- /dev/null
+++ b/chrome/app/settings_strings_grdp/IDS_SETTINGS_AUTOFILL_AI_ADD_OR_EDIT_DIALOG_VALIDATION_ERROR.png.sha1
@@ -0,0 +1 @@
+0603480a328240593f3c31b129fafffc3ba80dda
\ No newline at end of file
diff --git a/chrome/app/settings_strings_grdp/IDS_SETTINGS_AUTOFILL_AI_DELETE_ENTITY_INSTANCE_DIALOG_TEXT.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_AUTOFILL_AI_DELETE_ENTITY_INSTANCE_DIALOG_TEXT.png.sha1
new file mode 100644
index 0000000..5025b62
--- /dev/null
+++ b/chrome/app/settings_strings_grdp/IDS_SETTINGS_AUTOFILL_AI_DELETE_ENTITY_INSTANCE_DIALOG_TEXT.png.sha1
@@ -0,0 +1 @@
+5964b4f3952648f1b230fdd103c64b30651f33ea
\ No newline at end of file
diff --git a/chrome/app/settings_strings_grdp/IDS_SETTINGS_AUTOFILL_AI_DELETE_ENTITY_INSTANCE_DIALOG_TITLE.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_AUTOFILL_AI_DELETE_ENTITY_INSTANCE_DIALOG_TITLE.png.sha1
new file mode 100644
index 0000000..03bbad1
--- /dev/null
+++ b/chrome/app/settings_strings_grdp/IDS_SETTINGS_AUTOFILL_AI_DELETE_ENTITY_INSTANCE_DIALOG_TITLE.png.sha1
@@ -0,0 +1 @@
+c14d8f5ddce7ed33180045ddcefedeed99632a66
\ No newline at end of file
diff --git a/chrome/app/settings_strings_grdp/IDS_SETTINGS_AUTOFILL_AI_MORE_ACTIONS_FOR_ENTITY_INSTANCE.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_AUTOFILL_AI_MORE_ACTIONS_FOR_ENTITY_INSTANCE.png.sha1
new file mode 100644
index 0000000..113f32e
--- /dev/null
+++ b/chrome/app/settings_strings_grdp/IDS_SETTINGS_AUTOFILL_AI_MORE_ACTIONS_FOR_ENTITY_INSTANCE.png.sha1
@@ -0,0 +1 @@
+b90fb28fcecf05f9036bbbd4659b464a89718b99
\ No newline at end of file
diff --git a/chrome/app/settings_strings_grdp/IDS_SETTINGS_AUTOFILL_DROPDOWN_NO_OPTION_SELECTED.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_AUTOFILL_DROPDOWN_NO_OPTION_SELECTED.png.sha1
new file mode 100644
index 0000000..ea7843be
--- /dev/null
+++ b/chrome/app/settings_strings_grdp/IDS_SETTINGS_AUTOFILL_DROPDOWN_NO_OPTION_SELECTED.png.sha1
@@ -0,0 +1 @@
+c8458d9ed6f50e4554bde3018d0c75048c76b898
\ No newline at end of file
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index aa57f11..fd02559 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -2318,6 +2318,7 @@
     "//components/safe_browsing/core/browser/db:database_manager",
     "//components/safe_browsing/core/browser/hashprefix_realtime:hash_realtime_service",
     "//components/safe_browsing/core/browser/password_protection:password_protection_metrics_util",
+    "//components/safe_browsing/core/browser/realtime:enterprise_url_lookup_service",
     "//components/safe_browsing/core/browser/realtime:policy_engine",
     "//components/safe_browsing/core/browser/realtime:url_lookup_service",
     "//components/safe_browsing/core/browser/sync",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index ffbdeada..a38a547 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -2836,6 +2836,13 @@
              kAndroidAppIntegrationMultiDataSource_UseSchemaV1AndSkipDeviceCheck),
          nullptr}};
 
+const FeatureEntry::FeatureParam kAndroidBottomToolbar_DefaultToBottom[] = {
+    {"default_to_top", "false"}};
+
+const FeatureEntry::FeatureVariation kAndroidBottomToolbarVariations[] = {
+    {"default to bottom", kAndroidBottomToolbar_DefaultToBottom,
+     std::size(kAndroidBottomToolbar_DefaultToBottom), nullptr}};
+
 const FeatureEntry::FeatureParam kAuxiliarySearchDonation_MaxDonation_20[] = {
     {chrome::android::kAuxiliarySearchMaxBookmarksCountParam.name, "20"},
     {chrome::android::kAuxiliarySearchMaxTabsCountParam.name, "20"}};
@@ -4073,32 +4080,6 @@
     {"with all task types", kDeferRendererTasksAfterInputAllTypesPolicyParam,
      std::size(kDeferRendererTasksAfterInputAllTypesPolicyParam), nullptr}};
 
-const FeatureEntry::FeatureParam
-    kThreadedScrollPreventRenderingStarvation_66ms[] = {{"threshold_ms", "66"}};
-const FeatureEntry::FeatureParam
-    kThreadedScrollPreventRenderingStarvation_100ms[] = {
-        {"threshold_ms", "100"}};
-const FeatureEntry::FeatureParam
-    kThreadedScrollPreventRenderingStarvation_200ms[] = {
-        {"threshold_ms", "200"}};
-const FeatureEntry::FeatureParam
-    kThreadedScrollPreventRenderingStarvation_333ms[] = {
-        {"threshold_ms", "333"}};
-const FeatureEntry::FeatureVariation
-    kThreadedScrollPreventRenderingStarvationVariations[] = {
-        {"with a 66ms threshold",
-         kThreadedScrollPreventRenderingStarvation_66ms,
-         std::size(kThreadedScrollPreventRenderingStarvation_66ms), nullptr},
-        {"with a 100ms threshold",
-         kThreadedScrollPreventRenderingStarvation_100ms,
-         std::size(kThreadedScrollPreventRenderingStarvation_100ms), nullptr},
-        {"with a 200ms threshold",
-         kThreadedScrollPreventRenderingStarvation_200ms,
-         std::size(kThreadedScrollPreventRenderingStarvation_200ms), nullptr},
-        {"with a 333ms threshold",
-         kThreadedScrollPreventRenderingStarvation_333ms,
-         std::size(kThreadedScrollPreventRenderingStarvation_333ms), nullptr}};
-
 // LINT.IfChange(AutofillUploadCardRequestTimeouts)
 const FeatureEntry::FeatureParam
     kAutofillUploadCardRequestTimeout_6Point5Seconds[] = {
@@ -5449,6 +5430,10 @@
      flag_descriptions::kEnableIsolatedWebAppUnmanagedInstallDescription,
      kOsCrOS, FEATURE_VALUE_TYPE(features::kIsolatedWebAppUnmanagedInstall)},
 #endif
+    {"enable-isolated-web-app-allowlist",
+     flag_descriptions::kEnableIsolatedWebAppAllowlistName,
+     flag_descriptions::kEnableIsolatedWebAppAllowlistDescription, kOsDesktop,
+     FEATURE_VALUE_TYPE(features::kIsolatedWebAppAllowlist)},
     {"enable-isolated-web-app-dev-mode",
      flag_descriptions::kEnableIsolatedWebAppDevModeName,
      flag_descriptions::kEnableIsolatedWebAppDevModeDescription, kOsDesktop,
@@ -6492,7 +6477,9 @@
 
     {"android-bottom-toolbar", flag_descriptions::kAndroidBottomToolbarName,
      flag_descriptions::kAndroidBottomToolbarDescription, kOsAndroid,
-     FEATURE_VALUE_TYPE(chrome::android::kAndroidBottomToolbar)},
+     FEATURE_WITH_PARAMS_VALUE_TYPE(chrome::android::kAndroidBottomToolbar,
+                                    kAndroidBottomToolbarVariations,
+                                    "AndroidBottomToolbar")},
 
     {"auxiliary-search-donation",
      flag_descriptions::kAuxiliarySearchDonationName,
@@ -11027,15 +11014,6 @@
          kDeferRendererTasksAfterInputVariations,
          "DeferRendererTasksAfterInput")},
 
-    {"threaded-scroll-prevent-rendering-starvation",
-     flag_descriptions::kThreadedScrollPreventRenderingStarvationName,
-     flag_descriptions::kThreadedScrollPreventRenderingStarvationDescription,
-     kOsAll,
-     FEATURE_WITH_PARAMS_VALUE_TYPE(
-         blink::features::kThreadedScrollPreventRenderingStarvation,
-         kThreadedScrollPreventRenderingStarvationVariations,
-         "ThreadedScrollPreventRenderingStarvation")},
-
     {"autofill-upload-card-request-timeout",
      flag_descriptions::kAutofillUploadCardRequestTimeoutName,
      flag_descriptions::kAutofillUploadCardRequestTimeoutDescription, kOsAll,
diff --git a/chrome/browser/accessibility/tree_fixing/internal/ax_tree_fixing_screen_ai_service.cc b/chrome/browser/accessibility/tree_fixing/internal/ax_tree_fixing_screen_ai_service.cc
index 132f112..227168a 100644
--- a/chrome/browser/accessibility/tree_fixing/internal/ax_tree_fixing_screen_ai_service.cc
+++ b/chrome/browser/accessibility/tree_fixing/internal/ax_tree_fixing_screen_ai_service.cc
@@ -7,7 +7,7 @@
 #include <utility>
 
 #include "base/check.h"
-#include "base/check_op.h"
+#include "base/metrics/histogram_functions.h"
 #include "base/notreached.h"
 #include "base/task/single_thread_task_runner.h"
 #include "chrome/browser/screen_ai/screen_ai_service_router.h"
@@ -21,6 +21,22 @@
 
 namespace tree_fixing {
 
+static constexpr char kAXTreeFixingClientRequestTypeHistogramName[] =
+    "Accessibility.AXTreeFixing.ScreenAI.MainNodeIdentification."
+    "ClientRequestType";
+
+// These values are persisted to logs. Entries should not be renumbered and
+// numeric values should never be reused.
+//
+// LINT.IfChange(AXTreeFixingClientScreenAIRequestType)
+enum class AXTreeFixingClientScreenAIRequestType {
+  kMainLandmarkAlreadyPresent = 0,
+  kServiceNotInitialized = 1,
+  kValid = 2,
+  kMaxValue = kValid,
+};
+// LINT.ThenChange(//tools/metrics/histograms/metadata/accessibility/enums.xml:AXTreeFixingClientScreenAIRequestType)
+
 AXTreeFixingScreenAIService::AXTreeFixingScreenAIService(
     MainNodeIdentificationDelegate& delegate,
     Profile* profile)
@@ -37,14 +53,22 @@
   // Client should not be sending requests for trees that have a kMain node.
   for (const ui::AXNodeData& node : ax_tree_update.nodes) {
     if (node.role == ax::mojom::Role::kMain) {
+      base::UmaHistogramEnumeration(
+          kAXTreeFixingClientRequestTypeHistogramName,
+          AXTreeFixingClientScreenAIRequestType::kMainLandmarkAlreadyPresent);
       NOTREACHED() << "A node with the main landmark is already present in the "
                       "accessibility tree.";
     }
   }
 
   // The ScreenAI service needs to be downloaded and loaded.
-  CHECK_EQ(initialization_state_, InitializationState::kInitialized)
-      << "Client sent request to identify main node before service was ready";
+  if (initialization_state_ != InitializationState::kInitialized) {
+    base::UmaHistogramEnumeration(
+        kAXTreeFixingClientRequestTypeHistogramName,
+        AXTreeFixingClientScreenAIRequestType::kServiceNotInitialized);
+    NOTREACHED() << "Client sent request to identify main node before service "
+                    "was ready.";
+  }
 
   // If the remote to ScreenAI has not yet been bound, do so now.
   if (!screen_ai_service_.is_bound() || !screen_ai_service_.is_connected()) {
@@ -60,11 +84,17 @@
   }
 
   // Identify the main node using ScreenAI.
+  base::UmaHistogramEnumeration(kAXTreeFixingClientRequestTypeHistogramName,
+                                AXTreeFixingClientScreenAIRequestType::kValid);
+  base::UmaHistogramBoolean(
+      "Accessibility.AXTreeFixing.ScreenAI.MainNodeIdentification.Request",
+      true);
   screen_ai_service_->IdentifyMainNode(
       ax_tree_update,
       base::BindOnce(&AXTreeFixingScreenAIService::
                          ProcessScreenAIMainNodeIdentificationResult,
-                     weak_ptr_factory_.GetWeakPtr(), request_id));
+                     weak_ptr_factory_.GetWeakPtr(), request_id,
+                     base::ElapsedTimer(), ax_tree_update));
 }
 
 void AXTreeFixingScreenAIService::Initialize() {
@@ -73,6 +103,8 @@
   initialization_state_ = InitializationState::kInitializing;
   ++initialization_attempt_count_;
   main_node_identification_delegate_->OnServiceStateChanged(false);
+  base::UmaHistogramBoolean(
+      "Accessibility.AXTreeFixing.ScreenAI.InitializationAttempt", true);
   screen_ai::ScreenAIServiceRouterFactory::GetForBrowserContext(profile_)
       ->GetServiceStateAsync(
           screen_ai::ScreenAIServiceRouter::Service::kMainContentExtraction,
@@ -84,6 +116,9 @@
 void AXTreeFixingScreenAIService::ServiceInitializationCallback(
     bool successful) {
   if (successful) {
+    base::UmaHistogramExactLinear(
+        "Accessibility.AXTreeFixing.ScreenAI.InitializedOnAttempt",
+        initialization_attempt_count_, kMaxInitializationAttempts);
     initialization_attempt_count_ = 0;
     initialization_state_ = InitializationState::kInitialized;
     main_node_identification_delegate_->OnServiceStateChanged(true);
@@ -92,9 +127,10 @@
     // the ScreenAI service, stop and send a signal to client that it failed.
     // Otherwise, re-attempt initialization.
     if (initialization_attempt_count_ >= kMaxInitializationAttempts) {
+      base::UmaHistogramBoolean(
+          "Accessibility.AXTreeFixing.ScreenAI.InitializedFailed", true);
       initialization_state_ = InitializationState::kInitializationFailed;
       main_node_identification_delegate_->OnServiceStateChanged(false);
-      // TODO(crbug.com/399383663): Record metric for repeated failures?
     } else {
       initialization_state_ = InitializationState::kUninitialized;
       // The ScreenAI service is suspended internally after each crash. We will
@@ -119,19 +155,47 @@
   initialization_state_ = InitializationState::kDisconnected;
   main_node_identification_delegate_->OnServiceStateChanged(false);
 
-  // TODO(crbug.com/399383663): Record metric for disconnects?
   if (!previously_attempted_reconnect_) {
+    base::UmaHistogramBoolean(
+        "Accessibility.AXTreeFixing.ScreenAI.Disconnect.First", true);
     initialization_attempt_count_ = 0;
     previously_attempted_reconnect_ = true;
     Initialize();
+  } else {
+    base::UmaHistogramBoolean(
+        "Accessibility.AXTreeFixing.ScreenAI.Disconnect.Multiple", true);
   }
 }
 
 void AXTreeFixingScreenAIService::ProcessScreenAIMainNodeIdentificationResult(
     int request_id,
+    base::ElapsedTimer timer,
+    const ui::AXTreeUpdate& ax_tree_update,
     const ui::AXTreeID& tree_id,
     int node_id) {
-  // TODO(crbug.com/399383663): Add metrics, internal logic, etc.
+  base::UmaHistogramTimes(
+      "Accessibility.AXTreeFixing.ScreenAI.MainNodeIdentification."
+      "RoundTripTime",
+      timer.Elapsed());
+  base::UmaHistogramBoolean(
+      "Accessibility.AXTreeFixing.ScreenAI.MainNodeIdentification.Response",
+      true);
+
+  bool found_main_node = node_id != ui::kInvalidAXNodeID;
+  base::UmaHistogramBoolean("Accessibility.AXTreeFixing.ScreenAI.FoundMainNode",
+                            found_main_node);
+
+  if (found_main_node) {
+    for (const ui::AXNodeData& node : ax_tree_update.nodes) {
+      if (node.id == node_id) {
+        base::UmaHistogramEnumeration(
+            "Accessibility.AXTreeFixing.ScreenAI.MainNodeInitialRole",
+            node.role);
+        break;
+      }
+    }
+  }
+
   main_node_identification_delegate_->OnMainNodeIdentified(tree_id, node_id,
                                                            request_id);
 }
diff --git a/chrome/browser/accessibility/tree_fixing/internal/ax_tree_fixing_screen_ai_service.h b/chrome/browser/accessibility/tree_fixing/internal/ax_tree_fixing_screen_ai_service.h
index 025c276..f9cf77a7 100644
--- a/chrome/browser/accessibility/tree_fixing/internal/ax_tree_fixing_screen_ai_service.h
+++ b/chrome/browser/accessibility/tree_fixing/internal/ax_tree_fixing_screen_ai_service.h
@@ -8,6 +8,7 @@
 #include "base/memory/raw_ptr.h"
 #include "base/memory/raw_ref.h"
 #include "base/memory/weak_ptr.h"
+#include "base/timer/elapsed_timer.h"
 #include "chrome/browser/profiles/profile.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "services/screen_ai/public/mojom/screen_ai_service.mojom.h"
@@ -90,9 +91,12 @@
   // Internal method that processes results from the ScreenAI service before
   // returning the results to the owner of this instance via the provided
   // delegate.
-  void ProcessScreenAIMainNodeIdentificationResult(int request_id,
-                                                   const ui::AXTreeID& tree_id,
-                                                   int node_id);
+  void ProcessScreenAIMainNodeIdentificationResult(
+      int request_id,
+      base::ElapsedTimer timer,
+      const ui::AXTreeUpdate& ax_tree_update,
+      const ui::AXTreeID& tree_id,
+      int node_id);
 
   // Delegate provided by client to receive main node identification results.
   // Use a raw_ref since we do not own the delegate or control its lifecycle.
diff --git a/chrome/browser/actor/BUILD.gn b/chrome/browser/actor/BUILD.gn
index ee8cd5c..9104ca25 100644
--- a/chrome/browser/actor/BUILD.gn
+++ b/chrome/browser/actor/BUILD.gn
@@ -51,7 +51,9 @@
     "actor_test_util.cc",
     "actor_test_util.h",
   ]
-  deps = [ ":actor" ]
+
+  public_deps =
+      [ "//components/optimization_guide/proto:optimization_guide_proto" ]
 }
 
 source_set("unit_tests") {
diff --git a/chrome/browser/actor/tools/page_tool.cc b/chrome/browser/actor/tools/page_tool.cc
index c2bb05e..17b5896e 100644
--- a/chrome/browser/actor/tools/page_tool.cc
+++ b/chrome/browser/actor/tools/page_tool.cc
@@ -11,6 +11,40 @@
 #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
 
 using content::RenderFrameHost;
+using optimization_guide::proto::ActionInformation;
+using optimization_guide::proto::ClickAction_ClickCount;
+using optimization_guide::proto::ClickAction_ClickType;
+
+namespace {
+// Set mojom for click action based on proto. Returns false if the proto does
+// not contain correct/sufficient information, true otherwise.
+bool SetClickToolArgs(actor::mojom::ClickActionPtr& click,
+                      ActionInformation action_info) {
+  click->target = actor::mojom::ToolTarget::New(
+      action_info.click().target().content_node_id());
+  switch (action_info.click().click_type()) {
+    case ClickAction_ClickType::ClickAction_ClickType_LEFT:
+      click->type = actor::mojom::ClickAction::Type::kLeft;
+      break;
+    case ClickAction_ClickType::ClickAction_ClickType_RIGHT:
+      click->type = actor::mojom::ClickAction::Type::kRight;
+      break;
+    default:
+      return false;
+  }
+  switch (action_info.click().click_count()) {
+    case ClickAction_ClickCount::ClickAction_ClickCount_SINGLE:
+      click->count = actor::mojom::ClickAction::Count::kSingle;
+      break;
+    case ClickAction_ClickCount::ClickAction_ClickCount_DOUBLE:
+      click->count = actor::mojom::ClickAction::Count::kDouble;
+      break;
+    default:
+      return false;
+  }
+  return true;
+}
+}  // namespace
 
 namespace actor {
 
@@ -29,7 +63,31 @@
 
 void PageTool::Invoke(InvokeCallback callback) {
   auto request = actor::mojom::ToolInvocation::New();
-  request->dom_node_id = invocation_.GetTargetDOMNodeId();
+  auto action_info = invocation_.GetActionInfo();
+
+  switch (action_info.action_info_case()) {
+    case ActionInformation::ActionInfoCase::kClick: {
+      auto click = mojom::ClickAction::New();
+      if (!SetClickToolArgs(click, action_info)) {
+        std::move(callback).Run(false);
+        return;
+      }
+      request->action = mojom::ToolAction::NewClick(std::move(click));
+      break;
+    }
+    case ActionInformation::ActionInfoCase::kType:
+    case ActionInformation::ActionInfoCase::kScroll:
+    case ActionInformation::ActionInfoCase::kMoveMouse:
+    case ActionInformation::ActionInfoCase::kDragAndRelease:
+    case ActionInformation::ActionInfoCase::kSelect: {
+      // Not implemented yet.
+      NOTIMPLEMENTED();
+      std::move(callback).Run(false);
+      return;
+    }
+    default:
+      NOTREACHED();
+  }
 
   chrome_render_frame_->InvokeTool(std::move(request), std::move(callback));
 }
diff --git a/chrome/browser/actor/tools/tool_invocation.cc b/chrome/browser/actor/tools/tool_invocation.cc
index 55e9e2d..f389ef9a 100644
--- a/chrome/browser/actor/tools/tool_invocation.cc
+++ b/chrome/browser/actor/tools/tool_invocation.cc
@@ -11,7 +11,6 @@
 
 using content::RenderFrameHost;
 using optimization_guide::proto::ActionInformation;
-using optimization_guide::proto::ActionTarget;
 using tabs::TabInterface;
 
 namespace actor {
@@ -44,36 +43,6 @@
   return &target_tab_.get();
 }
 
-int ToolInvocation::GetTargetDOMNodeId() const {
-  CHECK(IsTargetingPage());
-  return GetActionTarget().content_node_id();
-}
-
-const ActionTarget& ToolInvocation::GetActionTarget() const {
-  switch (action_information_.action_info_case()) {
-    case ActionInformation::ActionInfoCase::kClick:
-      return action_information_.click().target();
-    case ActionInformation::ActionInfoCase::kType:
-      return action_information_.type().target();
-    case ActionInformation::ActionInfoCase::kScroll:
-      return action_information_.scroll().target();
-    case ActionInformation::ActionInfoCase::kMoveMouse:
-      return action_information_.move_mouse().target();
-    case ActionInformation::ActionInfoCase::kDragAndRelease:
-      // TODO(crbug.com/398849001): if from and to can differ we'll need
-      // something something more sophisticated (this becomes tab-targeting).
-      return action_information_.drag_and_release().from_target();
-    case ActionInformation::ActionInfoCase::kSelect:
-      return action_information_.select().target();
-    case ActionInformation::ActionInfoCase::kNavigate:
-    case ActionInformation::ActionInfoCase::kBack:
-    case ActionInformation::ActionInfoCase::kForward:
-    case ActionInformation::ActionInfoCase::kWait:
-    case ActionInformation::ActionInfoCase::ACTION_INFO_NOT_SET:
-      NOTREACHED();
-  }
-}
-
 bool ToolInvocation::IsTargetingPage() const {
   return !IsTargetingTab();
 }
diff --git a/chrome/browser/actor/tools/tool_invocation.h b/chrome/browser/actor/tools/tool_invocation.h
index 3ea7c3b..d6e3fef 100644
--- a/chrome/browser/actor/tools/tool_invocation.h
+++ b/chrome/browser/actor/tools/tool_invocation.h
@@ -51,8 +51,6 @@
   const optimization_guide::proto::ActionInformation& GetActionInfo() const;
 
  private:
-  const optimization_guide::proto::ActionTarget& GetActionTarget() const;
-
   optimization_guide::proto::ActionInformation action_information_;
 
   // TODO(crbug.com/398849001): It'd be better if ActionInformation provided a
diff --git a/chrome/browser/actor/tools/tools_browsertest.cc b/chrome/browser/actor/tools/tools_browsertest.cc
index 9dbc965..1fff431 100644
--- a/chrome/browser/actor/tools/tools_browsertest.cc
+++ b/chrome/browser/actor/tools/tools_browsertest.cc
@@ -35,6 +35,8 @@
 
 namespace {
 
+constexpr int64_t kNonExistantContentNodeId = 12345;
+
 class ActorToolsTest : public InProcessBrowserTest {
  public:
   ActorToolsTest() {
@@ -75,13 +77,16 @@
   const GURL url = embedded_test_server()->GetURL("/simple.html");
   ASSERT_TRUE(content::NavigateToURL(web_contents(), url));
 
-  BrowserAction action = MakeClick(/*content_node_id=*/123);
+  // Use a random node id that doesn't exist.
+  BrowserAction action =
+      MakeClick(/*content_node_id=*/kNonExistantContentNodeId);
 
   TabInterface& tab = *active_tab();
 
-  TestFuture<bool> result_success;
-  actor_coordinator().Act(tab, action, result_success.GetCallback());
-  EXPECT_TRUE(result_success.Get());
+  TestFuture<bool> result_fail;
+  actor_coordinator().Act(tab, action, result_fail.GetCallback());
+  // The node id doesn't exist so the tool will return false.
+  EXPECT_FALSE(result_fail.Get());
 }
 
 // Basic test of the NavigateTool.
diff --git a/chrome/browser/apps/platform_apps/app_browsertest_util.cc b/chrome/browser/apps/platform_apps/app_browsertest_util.cc
index c4327f2..faad5de5 100644
--- a/chrome/browser/apps/platform_apps/app_browsertest_util.cc
+++ b/chrome/browser/apps/platform_apps/app_browsertest_util.cc
@@ -223,7 +223,8 @@
   function->set_extension(extension);
   utils::RunFunction(function.get(), base::StringPrintf("[%u]", window_id),
                      browser()->profile(), api_test_utils::FunctionMode::kNone);
-  return *function->response_type() == ExtensionFunction::SUCCEEDED;
+  return *function->response_type() ==
+         ExtensionFunction::ResponseType::kSucceeded;
 }
 
 size_t PlatformAppBrowserTest::GetAppWindowCount() {
diff --git a/chrome/browser/ash/app_restore/full_restore_service_unittest.cc b/chrome/browser/ash/app_restore/full_restore_service_unittest.cc
index 05f8398..49c27b5 100644
--- a/chrome/browser/ash/app_restore/full_restore_service_unittest.cc
+++ b/chrome/browser/ash/app_restore/full_restore_service_unittest.cc
@@ -365,662 +365,6 @@
   base::test::ScopedFeatureList scoped_feature_list_;
 };
 
-// If the system is crash, and there is no FullRestore file, don't show the
-// crash notification, and don't restore.
-TEST_F(FullRestoreServiceTest, Crash) {
-  ExitTypeService::GetInstanceForProfile(profile())
-      ->SetLastSessionExitTypeForTest(ExitType::kCrashed);
-  CreateFullRestoreServiceForTesting();
-
-  VerifyNotification(false /* has_crash_notification */,
-                     false /* has_restore_notification */);
-  EXPECT_EQ(RestoreOption::kAskEveryTime, GetRestoreOption());
-}
-
-// If the OS restore setting is 'Ask every time', and there is no FullRestore
-// file, after reboot, don't show the notification, and don't restore
-TEST_F(FullRestoreServiceTest, AskEveryTime) {
-  SetRestoreOption(RestoreOption::kAskEveryTime);
-  CreateFullRestoreServiceForTesting();
-
-  EXPECT_EQ(RestoreOption::kAskEveryTime, GetRestoreOption());
-  VerifyRestoreInitSettingHistogram(RestoreOption::kAskEveryTime, 1);
-
-  VerifyNotification(false /* has_crash_notification */,
-                     false /* has_restore_notification */);
-  EXPECT_EQ(RestoreOption::kAskEveryTime, GetRestoreOption());
-}
-
-class FullRestoreServiceTestHavingFullRestoreFile
-    : public FullRestoreServiceTest {
- public:
-  FullRestoreServiceTestHavingFullRestoreFile() {
-    base::CommandLine::ForCurrentProcess()->AppendSwitch(
-        ::switches::kNoFirstRun);
-    CreateRestoreData(profile());
-  }
-  FullRestoreServiceTestHavingFullRestoreFile(
-      const FullRestoreServiceTestHavingFullRestoreFile&) = delete;
-  FullRestoreServiceTestHavingFullRestoreFile& operator=(
-      const FullRestoreServiceTestHavingFullRestoreFile&) = delete;
-  ~FullRestoreServiceTestHavingFullRestoreFile() override {
-    ::full_restore::FullRestoreSaveHandler::GetInstance()->ClearForTesting();
-  }
-
-  bool allow_save() const {
-    return ::full_restore::FullRestoreSaveHandler::GetInstance()->allow_save_;
-  }
-
- protected:
-  void CreateRestoreData(Profile* profile) {
-    // Add app launch infos.
-    ::full_restore::SaveAppLaunchInfo(
-        profile->GetPath(), std::make_unique<::app_restore::AppLaunchInfo>(
-                                app_constants::kChromeAppId, kWindowId));
-
-    ::full_restore::FullRestoreSaveHandler* save_handler =
-        ::full_restore::FullRestoreSaveHandler::GetInstance();
-    base::OneShotTimer* timer = save_handler->GetTimerForTesting();
-    save_handler->AllowSave();
-
-    // Simulate timeout, and the launch info is saved.
-    timer->FireNow();
-
-    content::RunAllTasksUntilIdle();
-
-    ::full_restore::FullRestoreReadHandler::GetInstance()
-        ->profile_path_to_restore_data_.clear();
-  }
-};
-
-// If the system is crash, show the crash notification, and verify the restore
-// flag when click the restore button.
-TEST_F(FullRestoreServiceTestHavingFullRestoreFile, CrashAndRestore) {
-  ExitTypeService::GetInstanceForProfile(profile())
-      ->SetLastSessionExitTypeForTest(ExitType::kCrashed);
-  CreateFullRestoreServiceForTesting();
-
-  VerifyNotification(true /* has_crash_notification */,
-                     false /* has_restore_notification */);
-
-  SimulateClick(kRestoreForCrashNotificationId,
-                RestoreNotificationButtonIndex::kRestore);
-
-  EXPECT_TRUE(CanPerformRestore(account_id()));
-  EXPECT_TRUE(allow_save());
-
-  // Verify the set restore notification is not shown.
-  VerifyNotification(false /* has_crash_notification */,
-                     false /* has_restore_notification */);
-}
-
-// If the system is crash, show the crash notification, and verify the restore
-// flag when click the cancel button.
-TEST_F(FullRestoreServiceTestHavingFullRestoreFile, CrashAndCancel) {
-  ExitTypeService::GetInstanceForProfile(profile())
-      ->SetLastSessionExitTypeForTest(ExitType::kCrashed);
-  CreateFullRestoreServiceForTesting();
-
-  VerifyNotification(true /* has_crash_notification */,
-                     false /* has_restore_notification */);
-
-  SimulateClick(kRestoreForCrashNotificationId,
-                RestoreNotificationButtonIndex::kCancel);
-
-  EXPECT_TRUE(CanPerformRestore(account_id()));
-  EXPECT_TRUE(allow_save());
-}
-
-// If the system is crash, show the crash notification, close the notification,
-// and verify the restore flag when click the cancel button.
-TEST_F(FullRestoreServiceTestHavingFullRestoreFile, CrashAndCloseNotification) {
-  ExitTypeService::GetInstanceForProfile(profile())
-      ->SetLastSessionExitTypeForTest(ExitType::kCrashed);
-  CreateFullRestoreServiceForTesting();
-
-  VerifyNotification(true /* has_crash_notification */,
-                     false /* has_restore_notification */);
-
-  FullRestoreServiceFactory::GetForProfile(profile())->MaybeCloseNotification();
-  VerifyNotification(false /* has_crash_notification */,
-                     false /* has_restore_notification */);
-
-  EXPECT_TRUE(CanPerformRestore(account_id()));
-  EXPECT_TRUE(allow_save());
-}
-
-// For an existing user, if re-image, don't show notifications for the first
-// run.
-TEST_F(FullRestoreServiceTestHavingFullRestoreFile, ExistingUserReImage) {
-  // Set the restore pref setting to simulate sync for the first time.
-  SetRestoreOption(RestoreOption::kAskEveryTime);
-  first_run::ResetCachedSentinelDataForTesting();
-  base::CommandLine::ForCurrentProcess()->AppendSwitch(
-      ::switches::kForceFirstRun);
-
-  CreateFullRestoreServiceForTesting();
-
-  EXPECT_EQ(RestoreOption::kAskEveryTime, GetRestoreOption());
-  VerifyRestoreInitSettingHistogram(RestoreOption::kAskEveryTime, 1);
-
-  VerifyNotification(false, false);
-  EXPECT_TRUE(allow_save());
-
-  base::CommandLine::ForCurrentProcess()->RemoveSwitch(
-      ::switches::kForceFirstRun);
-  first_run::ResetCachedSentinelDataForTesting();
-}
-
-// For a brand new user, if sync off, set 'Ask Every Time' as the default value,
-// and don't show notifications, don't restore.
-TEST_F(FullRestoreServiceTest, NewUserSyncOff) {
-  fake_user_manager()->SetIsCurrentUserNew(true);
-  CreateFullRestoreServiceForTesting();
-
-  EXPECT_EQ(RestoreOption::kAskEveryTime, GetRestoreOption());
-  VerifyRestoreInitSettingHistogram(RestoreOption::kAskEveryTime, 0);
-
-  VerifyNotification(false, false);
-
-  EXPECT_TRUE(CanPerformRestore(account_id()));
-}
-
-// For a new Chrome OS user, if the Chrome restore setting is 'Continue where
-// you left off', after sync, set 'Always' as the default value, and don't show
-// notifications, don't restore.
-TEST_F(FullRestoreServiceTest, NewUserSyncChromeRestoreSetting) {
-  fake_user_manager()->SetIsCurrentUserNew(true);
-  CreateFullRestoreServiceForTesting();
-
-  EXPECT_EQ(RestoreOption::kAskEveryTime, GetRestoreOption());
-
-  VerifyNotification(false, false);
-
-  EXPECT_TRUE(CanPerformRestore(account_id()));
-
-  // Set the Chrome restore setting to simulate sync for the first time.
-  SyncPreferences(SessionStartupPref::kPrefValueLast, std::nullopt);
-  content::RunAllTasksUntilIdle();
-
-  EXPECT_EQ(RestoreOption::kAlways, GetRestoreOption());
-
-  VerifyNotification(false, false);
-
-  EXPECT_TRUE(CanPerformRestore(account_id()));
-
-  // Update the global values to simulate sync from other device.
-  ProcessSyncChanges(SessionStartupPref::kPrefValueNewTab,
-                     RestoreOption::kDoNotRestore);
-  content::RunAllTasksUntilIdle();
-
-  EXPECT_EQ(RestoreOption::kDoNotRestore, GetRestoreOption());
-  EXPECT_FALSE(CanPerformRestore(account_id()));
-}
-
-// For a new Chrome OS user, if the Chrome restore setting is 'New tab', after
-// sync, set 'Ask every time' as the default value, and don't show
-// notifications, don't restore.
-TEST_F(FullRestoreServiceTest, NewUserSyncChromeNotRestoreSetting) {
-  fake_user_manager()->SetIsCurrentUserNew(true);
-  CreateFullRestoreServiceForTesting();
-
-  EXPECT_EQ(RestoreOption::kAskEveryTime, GetRestoreOption());
-
-  VerifyNotification(false, false);
-
-  EXPECT_TRUE(CanPerformRestore(account_id()));
-
-  // Set the Chrome restore setting to simulate sync for the first time.
-  SyncPreferences(SessionStartupPref::kPrefValueNewTab, std::nullopt);
-  content::RunAllTasksUntilIdle();
-
-  EXPECT_EQ(RestoreOption::kAskEveryTime, GetRestoreOption());
-
-  VerifyNotification(false, false);
-
-  EXPECT_TRUE(CanPerformRestore(account_id()));
-
-  // Update the global values to simulate sync from other device.
-  ProcessSyncChanges(SessionStartupPref::kPrefValueLast,
-                     RestoreOption::kDoNotRestore);
-  content::RunAllTasksUntilIdle();
-
-  EXPECT_EQ(RestoreOption::kDoNotRestore, GetRestoreOption());
-  EXPECT_FALSE(CanPerformRestore(account_id()));
-}
-
-// For a new Chrome OS user, keep the ChromeOS restore setting from sync, and
-// don't show notifications, don't restore.
-TEST_F(FullRestoreServiceTest, ReImage) {
-  fake_user_manager()->SetIsCurrentUserNew(true);
-  CreateFullRestoreServiceForTesting();
-
-  EXPECT_EQ(RestoreOption::kAskEveryTime, GetRestoreOption());
-
-  VerifyNotification(false, false);
-
-  EXPECT_TRUE(CanPerformRestore(account_id()));
-
-  // Set the restore pref setting to simulate sync for the first time.
-  SyncPreferences(SessionStartupPref::kPrefValueLast,
-                  RestoreOption::kAskEveryTime);
-  content::RunAllTasksUntilIdle();
-
-  EXPECT_EQ(RestoreOption::kAskEveryTime, GetRestoreOption());
-
-  VerifyNotification(false, false);
-
-  EXPECT_TRUE(CanPerformRestore(account_id()));
-
-  // Update the global values to simulate sync from other device.
-  ProcessSyncChanges(SessionStartupPref::kPrefValueNewTab,
-                     RestoreOption::kAlways);
-  content::RunAllTasksUntilIdle();
-
-  EXPECT_EQ(RestoreOption::kAlways, GetRestoreOption());
-  EXPECT_TRUE(CanPerformRestore(account_id()));
-}
-
-// For the current ChromeOS user, when first time upgrading to the full restore
-// release, set the default value based on the current Chrome restore setting,
-// and don't show notifications, don't restore
-TEST_F(FullRestoreServiceTest, Upgrading) {
-  profile()->GetPrefs()->SetInteger(
-      ::prefs::kRestoreOnStartup,
-      static_cast<int>(SessionStartupPref::kPrefValueNewTab));
-  CreateFullRestoreServiceForTesting();
-
-  EXPECT_EQ(RestoreOption::kDoNotRestore, GetRestoreOption());
-  VerifyRestoreInitSettingHistogram(RestoreOption::kDoNotRestore, 0);
-
-  VerifyNotification(false, false);
-
-  EXPECT_FALSE(CanPerformRestore(account_id()));
-
-  // Simulate the Chrome restore setting is changed.
-  profile()->GetPrefs()->SetInteger(
-      ::prefs::kRestoreOnStartup,
-      static_cast<int>(SessionStartupPref::kPrefValueLast));
-
-  // The OS restore setting should not change.
-  EXPECT_EQ(RestoreOption::kDoNotRestore, GetRestoreOption());
-  EXPECT_FALSE(CanPerformRestore(account_id()));
-}
-
-// If the OS restore setting is 'Ask every time', after reboot, show the restore
-// notification, and verify the restore flag when click the restore button.
-TEST_F(FullRestoreServiceTestHavingFullRestoreFile, AskEveryTimeAndRestore) {
-  SetRestoreOption(RestoreOption::kAskEveryTime);
-  CreateFullRestoreServiceForTesting();
-
-  EXPECT_EQ(RestoreOption::kAskEveryTime, GetRestoreOption());
-  VerifyRestoreInitSettingHistogram(RestoreOption::kAskEveryTime, 1);
-
-  VerifyNotification(false /* has_crash_notification */,
-                     true /* has_restore_notification */);
-
-  SimulateClick(kRestoreNotificationId,
-                RestoreNotificationButtonIndex::kRestore);
-
-  EXPECT_EQ(RestoreOption::kAskEveryTime, GetRestoreOption());
-  EXPECT_TRUE(CanPerformRestore(account_id()));
-  EXPECT_TRUE(allow_save());
-
-  VerifyNotification(false, false);
-
-  FullRestoreServiceFactory::GetForProfile(profile())->MaybeCloseNotification();
-}
-
-// If the OS restore setting is 'Ask every time', after reboot, show the restore
-// notification, and verify the restore flag when click the Settings button.
-TEST_F(FullRestoreServiceTestHavingFullRestoreFile, AskEveryTimeAndSettings) {
-  SetRestoreOption(RestoreOption::kAskEveryTime);
-  CreateFullRestoreServiceForTesting();
-
-  EXPECT_EQ(RestoreOption::kAskEveryTime, GetRestoreOption());
-  VerifyRestoreInitSettingHistogram(RestoreOption::kAskEveryTime, 1);
-
-  VerifyNotification(false /* has_crash_notification */,
-                     true /* has_restore_notification */);
-
-  // For the restore notification, the cancel button is used to show the full
-  // restore settings.
-  SimulateClick(kRestoreNotificationId,
-                RestoreNotificationButtonIndex::kCancel);
-
-  EXPECT_EQ(RestoreOption::kAskEveryTime, GetRestoreOption());
-  EXPECT_TRUE(CanPerformRestore(account_id()));
-  EXPECT_TRUE(allow_save());
-
-  // Click the setting button, the restore notification should not be closed.
-  VerifyNotification(false /* has_crash_notification */,
-                     true /* has_restore_notification */);
-
-  FullRestoreServiceFactory::GetForProfile(profile())->MaybeCloseNotification();
-
-  VerifyNotification(false /* has_crash_notification */,
-                     false /* has_restore_notification */);
-}
-
-// If the OS restore setting is 'Ask every time', after reboot, show the restore
-// notification, and close the notification, then verify the restore flag.
-TEST_F(FullRestoreServiceTestHavingFullRestoreFile,
-       AskEveryTimeAndCloseNotification) {
-  SetRestoreOption(RestoreOption::kAskEveryTime);
-  CreateFullRestoreServiceForTesting();
-
-  EXPECT_EQ(RestoreOption::kAskEveryTime, GetRestoreOption());
-  VerifyRestoreInitSettingHistogram(RestoreOption::kAskEveryTime, 1);
-
-  VerifyNotification(false /* has_crash_notification */,
-                     true /* has_restore_notification */);
-
-  FullRestoreServiceFactory::GetForProfile(profile())->MaybeCloseNotification();
-
-  EXPECT_EQ(RestoreOption::kAskEveryTime, GetRestoreOption());
-  EXPECT_TRUE(CanPerformRestore(account_id()));
-  EXPECT_TRUE(allow_save());
-
-  VerifyNotification(false, false);
-}
-
-// If the OS restore setting is 'Ask every time' and the reboot occurred due to
-// DeviceScheduledReboot policy, after reboot show the restore notification with
-// post reboot notification title.
-TEST_F(FullRestoreServiceTestHavingFullRestoreFile,
-       AskEveryTimeWithPostRebootNotification) {
-  SetRestoreOption(RestoreOption::kAskEveryTime);
-  profile()->GetPrefs()->SetBoolean(prefs::kShowPostRebootNotification, true);
-  CreateFullRestoreServiceForTesting();
-
-  EXPECT_EQ(RestoreOption::kAskEveryTime, GetRestoreOption());
-  VerifyRestoreInitSettingHistogram(RestoreOption::kAskEveryTime, 1);
-
-  VerifyNotification(false /* has_crash_notification */,
-                     true /* has_restore_notification */,
-                     true /* is_reboot_notification*/);
-}
-
-// If the OS restore setting is 'Ask every time', after reboot, close the
-// notification before showing the notification. Verify the notification is not
-// created.
-TEST_F(FullRestoreServiceTestHavingFullRestoreFile, CloseNotificationEarly) {
-  SetRestoreOption(RestoreOption::kAskEveryTime);
-  FullRestoreServiceFactory::GetInstance()->SetTestingFactoryAndUse(
-      profile(), base::BindRepeating([](content::BrowserContext* context)
-                                         -> std::unique_ptr<KeyedService> {
-        return std::make_unique<FullRestoreService>(
-            Profile::FromBrowserContext(context));
-      }));
-
-  FullRestoreServiceFactory::GetForProfile(profile())->MaybeCloseNotification();
-
-  content::RunAllTasksUntilIdle();
-
-  EXPECT_EQ(RestoreOption::kAskEveryTime, GetRestoreOption());
-  VerifyRestoreInitSettingHistogram(RestoreOption::kAskEveryTime, 1);
-
-  VerifyNotification(false /* has_crash_notification */,
-                     false /* has_restore_notification */);
-
-  EXPECT_EQ(RestoreOption::kAskEveryTime, GetRestoreOption());
-  EXPECT_TRUE(CanPerformRestore(account_id()));
-  EXPECT_TRUE(allow_save());
-}
-
-// If the OS restore setting is 'Always', after reboot, don't show any
-// notfications, and verify the restore flag.
-TEST_F(FullRestoreServiceTest, Always) {
-  SetRestoreOption(RestoreOption::kAlways);
-  CreateFullRestoreServiceForTesting();
-
-  EXPECT_EQ(RestoreOption::kAlways, GetRestoreOption());
-  VerifyRestoreInitSettingHistogram(RestoreOption::kAlways, 1);
-
-  VerifyNotification(false, false);
-
-  EXPECT_TRUE(CanPerformRestore(account_id()));
-}
-
-// If the OS restore setting is 'Do not restore', after reboot, don't show any
-// notfications, and verify the restore flag.
-TEST_F(FullRestoreServiceTest, NotRestore) {
-  SetRestoreOption(RestoreOption::kDoNotRestore);
-  CreateFullRestoreServiceForTesting();
-
-  EXPECT_EQ(RestoreOption::kDoNotRestore, GetRestoreOption());
-  VerifyRestoreInitSettingHistogram(RestoreOption::kDoNotRestore, 1);
-
-  VerifyNotification(false, false);
-
-  EXPECT_FALSE(CanPerformRestore(account_id()));
-}
-
-// TODO(http://b/326982900): Migrate this test suite to test with forest
-// enabled.
-class FullRestoreServiceMultipleUsersTest
-    : public FullRestoreServiceTestHavingFullRestoreFile {
- protected:
-  FullRestoreServiceMultipleUsersTest() {
-    test_helper2_ = std::make_unique<FullRestoreTestHelper>(
-        "user2@gmail.com", GaiaId("111111"), fake_user_manager(),
-        profile_manager_.get(), testing_pref_service_.get());
-    CreateRestoreData(profile2());
-  }
-  FullRestoreServiceMultipleUsersTest(
-      const FullRestoreServiceMultipleUsersTest&) = delete;
-  FullRestoreServiceMultipleUsersTest& operator=(
-      const FullRestoreServiceMultipleUsersTest&) = delete;
-  ~FullRestoreServiceMultipleUsersTest() override = default;
-
-  TestingProfile* profile2() { return test_helper2_->profile(); }
-  const AccountId& account_id2() const { return test_helper2_->account_id(); }
-
-  void VerifyNotificationForProfile2(bool has_crash_notification,
-                                     bool has_restore_notification) {
-    test_helper2_->VerifyNotification(has_crash_notification,
-                                      has_restore_notification,
-                                      /*is_reboot_notification=*/false);
-  }
-
-  void SimulateClickForProfile2(const std::string& notification_id,
-                                RestoreNotificationButtonIndex action_index) {
-    test_helper2_->SimulateClick(notification_id, action_index);
-  }
-
-  void CreateFullRestoreService2ForTesting() {
-    test_helper2_->CreateFullRestoreServiceForTesting(nullptr);
-  }
-
-  void CreateFullRestoreService2ForTesting(
-      std::unique_ptr<MockFullRestoreServiceDelegate> mock_delegate) {
-    test_helper2_->CreateFullRestoreServiceForTesting(std::move(mock_delegate));
-    content::RunAllTasksUntilIdle();
-  }
-
-  RestoreOption GetRestoreOptionForProfile2() const {
-    return test_helper2_->GetRestoreOption();
-  }
-
-  void SetRestoreOptionForProfile2(RestoreOption restore_option) {
-    test_helper2_->SetRestoreOption(restore_option);
-  }
-
-  void TearDown() override {
-    fake_user_manager()->OnUserProfileWillBeDestroyed(account_id2());
-    test_helper2_.reset();
-
-    FullRestoreServiceTestHavingFullRestoreFile::TearDown();
-  }
-
- private:
-  std::unique_ptr<FullRestoreTestHelper> test_helper2_;
-  std::unique_ptr<NotificationDisplayServiceTester> display_service2_;
-};
-
-// Verify the full restore init process when 2 users login at the same time,
-// e.g. after the system restart or upgrading.
-TEST_F(FullRestoreServiceMultipleUsersTest, TwoUsersLoginAtTheSameTime) {
-  // Add `switches::kLoginUser` to the command line to simulate the system
-  // restart.
-  base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
-      switches::kLoginUser, account_id().GetUserEmail());
-  // Set the first user as the last session active user.
-  fake_user_manager()->set_last_session_active_account_id(account_id());
-
-  SetRestoreOption(RestoreOption::kAskEveryTime);
-  SetRestoreOptionForProfile2(RestoreOption::kAskEveryTime);
-  CreateFullRestoreServiceForTesting();
-  CreateFullRestoreService2ForTesting();
-  content::RunAllTasksUntilIdle();
-
-  EXPECT_EQ(RestoreOption::kAskEveryTime, GetRestoreOption());
-  VerifyRestoreInitSettingHistogram(RestoreOption::kAskEveryTime, 1);
-
-  VerifyNotification(false /* has_crash_notification */,
-                     true /* has_restore_notification */);
-
-  // The notification for the second user should not be displayed.
-  VerifyNotificationForProfile2(false /* has_crash_notification */,
-                                false /* has_restore_notification */);
-
-  SimulateClick(kRestoreNotificationId,
-                RestoreNotificationButtonIndex::kRestore);
-
-  EXPECT_EQ(RestoreOption::kAskEveryTime, GetRestoreOption());
-  EXPECT_TRUE(CanPerformRestore(account_id()));
-  EXPECT_TRUE(allow_save());
-
-  VerifyNotification(false /* has_crash_notification */,
-                     false /* has_restore_notification */);
-
-  // Simulate switch to the second user.
-  auto* full_restore_service2 =
-      FullRestoreServiceFactory::GetForProfile(profile2());
-  full_restore_service2->OnTransitionedToNewActiveUser(profile2());
-
-  EXPECT_EQ(RestoreOption::kAskEveryTime, GetRestoreOptionForProfile2());
-  VerifyRestoreInitSettingHistogram(RestoreOption::kAskEveryTime, 2);
-
-  // The notification for the second user should not be displayed.
-  VerifyNotificationForProfile2(false /* has_crash_notification */,
-                                true /* has_restore_notification */);
-
-  SimulateClickForProfile2(kRestoreNotificationId,
-                           RestoreNotificationButtonIndex::kRestore);
-
-  EXPECT_EQ(RestoreOption::kAskEveryTime, GetRestoreOptionForProfile2());
-  EXPECT_TRUE(CanPerformRestore(account_id()));
-  EXPECT_TRUE(allow_save());
-
-  VerifyNotificationForProfile2(false /* has_crash_notification */,
-                                false /* has_restore_notification */);
-}
-
-// Verify the full restore init process when 2 users login one by one.
-TEST_F(FullRestoreServiceMultipleUsersTest, TwoUsersLoginOneByOne) {
-  SetRestoreOption(RestoreOption::kAskEveryTime);
-  CreateFullRestoreServiceForTesting();
-  VerifyRestoreInitSettingHistogram(RestoreOption::kAskEveryTime, 1);
-  EXPECT_EQ(RestoreOption::kAskEveryTime, GetRestoreOption());
-
-  VerifyNotification(false /* has_crash_notification */,
-                     true /* has_restore_notification */);
-
-  SimulateClick(kRestoreNotificationId,
-                RestoreNotificationButtonIndex::kRestore);
-
-  EXPECT_EQ(RestoreOption::kAskEveryTime, GetRestoreOption());
-  EXPECT_TRUE(CanPerformRestore(account_id()));
-  EXPECT_TRUE(allow_save());
-
-  VerifyNotification(false /* has_crash_notification */,
-                     false /* has_restore_notification */);
-
-  // Simulate switch to the second user.
-  SetRestoreOptionForProfile2(RestoreOption::kAskEveryTime);
-  CreateFullRestoreService2ForTesting();
-
-  auto* full_restore_service2 =
-      FullRestoreServiceFactory::GetForProfile(profile2());
-  full_restore_service2->OnTransitionedToNewActiveUser(profile2());
-  VerifyRestoreInitSettingHistogram(RestoreOption::kAskEveryTime, 1);
-  content::RunAllTasksUntilIdle();
-
-  EXPECT_EQ(RestoreOption::kAskEveryTime, GetRestoreOptionForProfile2());
-  VerifyRestoreInitSettingHistogram(RestoreOption::kAskEveryTime, 2);
-
-  // The notification for the second user should be displayed.
-  VerifyNotificationForProfile2(false /* has_crash_notification */,
-                                true /* has_restore_notification */);
-
-  SimulateClickForProfile2(kRestoreNotificationId,
-                           RestoreNotificationButtonIndex::kRestore);
-
-  EXPECT_EQ(RestoreOption::kAskEveryTime, GetRestoreOptionForProfile2());
-  EXPECT_TRUE(CanPerformRestore(account_id()));
-  EXPECT_TRUE(allow_save());
-
-  VerifyNotificationForProfile2(false /* has_crash_notification */,
-                                false /* has_restore_notification */);
-}
-
-// Verify the full restore init process when the system restarts.
-TEST_F(FullRestoreServiceMultipleUsersTest, TwoUsersLoginWithActiveUserLogin) {
-  // Add `switches::kLoginUser` to the command line to simulate the system
-  // restart.
-  base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
-      switches::kLoginUser, account_id().GetUserEmail());
-  // Set the second user as the last session active user.
-  fake_user_manager()->set_last_session_active_account_id(account_id2());
-
-  SetRestoreOption(RestoreOption::kAskEveryTime);
-  SetRestoreOptionForProfile2(RestoreOption::kAskEveryTime);
-  CreateFullRestoreServiceForTesting();
-  CreateFullRestoreService2ForTesting();
-  content::RunAllTasksUntilIdle();
-
-  VerifyRestoreInitSettingHistogram(RestoreOption::kAskEveryTime, 0);
-  VerifyNotification(false /* has_crash_notification */,
-                     false /* has_restore_notification */);
-
-  // Simulate switch to the second user.
-  auto* full_restore_service2 =
-      FullRestoreServiceFactory::GetForProfile(profile2());
-  full_restore_service2->OnTransitionedToNewActiveUser(profile2());
-
-  // The notification for the second user should be displayed.
-  VerifyNotificationForProfile2(false /* has_crash_notification */,
-                                true /* has_restore_notification */);
-  VerifyRestoreInitSettingHistogram(RestoreOption::kAskEveryTime, 1);
-
-  SimulateClickForProfile2(kRestoreNotificationId,
-                           RestoreNotificationButtonIndex::kRestore);
-  EXPECT_TRUE(CanPerformRestore(account_id2()));
-
-  // Simulate switch to the first user.
-  auto* full_restore_service =
-      FullRestoreServiceFactory::GetForProfile(profile());
-  full_restore_service->OnTransitionedToNewActiveUser(profile());
-
-  // The notification for the first user should be displayed.
-  VerifyRestoreInitSettingHistogram(RestoreOption::kAskEveryTime, 2);
-  VerifyNotification(false /* has_crash_notification */,
-                     true /* has_restore_notification */);
-
-  SimulateClick(kRestoreNotificationId,
-                RestoreNotificationButtonIndex::kRestore);
-  EXPECT_TRUE(CanPerformRestore(account_id()));
-
-  // Simulate switch to the second user, and verify no more init process.
-  full_restore_service2->OnTransitionedToNewActiveUser(profile2());
-  VerifyRestoreInitSettingHistogram(RestoreOption::kAskEveryTime, 2);
-
-  // Simulate switch to the first user, and verify no more init process.
-  full_restore_service->OnTransitionedToNewActiveUser(profile());
-  VerifyRestoreInitSettingHistogram(RestoreOption::kAskEveryTime, 2);
-}
-
 class ForestFullRestoreServiceTest : public FullRestoreServiceTest {
  protected:
   ForestFullRestoreServiceTest() {
@@ -1207,6 +551,48 @@
   EXPECT_FALSE(CanPerformRestore(account_id()));
 }
 
+class FullRestoreServiceTestHavingFullRestoreFile
+    : public FullRestoreServiceTest {
+ public:
+  FullRestoreServiceTestHavingFullRestoreFile() {
+    base::CommandLine::ForCurrentProcess()->AppendSwitch(
+        ::switches::kNoFirstRun);
+    CreateRestoreData(profile());
+  }
+  FullRestoreServiceTestHavingFullRestoreFile(
+      const FullRestoreServiceTestHavingFullRestoreFile&) = delete;
+  FullRestoreServiceTestHavingFullRestoreFile& operator=(
+      const FullRestoreServiceTestHavingFullRestoreFile&) = delete;
+  ~FullRestoreServiceTestHavingFullRestoreFile() override {
+    ::full_restore::FullRestoreSaveHandler::GetInstance()->ClearForTesting();
+  }
+
+  bool allow_save() const {
+    return ::full_restore::FullRestoreSaveHandler::GetInstance()->allow_save_;
+  }
+
+ protected:
+  void CreateRestoreData(Profile* profile) {
+    // Add app launch infos.
+    ::full_restore::SaveAppLaunchInfo(
+        profile->GetPath(), std::make_unique<::app_restore::AppLaunchInfo>(
+                                app_constants::kChromeAppId, kWindowId));
+
+    ::full_restore::FullRestoreSaveHandler* save_handler =
+        ::full_restore::FullRestoreSaveHandler::GetInstance();
+    base::OneShotTimer* timer = save_handler->GetTimerForTesting();
+    save_handler->AllowSave();
+
+    // Simulate timeout, and the launch info is saved.
+    timer->FireNow();
+
+    content::RunAllTasksUntilIdle();
+
+    ::full_restore::FullRestoreReadHandler::GetInstance()
+        ->profile_path_to_restore_data_.clear();
+  }
+};
+
 class ForestFullRestoreServiceTestHavingFullRestoreFile
     : public FullRestoreServiceTestHavingFullRestoreFile {
  protected:
@@ -1307,6 +693,66 @@
   first_run::ResetCachedSentinelDataForTesting();
 }
 
+class FullRestoreServiceMultipleUsersTest
+    : public FullRestoreServiceTestHavingFullRestoreFile {
+ protected:
+  FullRestoreServiceMultipleUsersTest() {
+    test_helper2_ = std::make_unique<FullRestoreTestHelper>(
+        "user2@gmail.com", GaiaId("111111"), fake_user_manager(),
+        profile_manager_.get(), testing_pref_service_.get());
+    CreateRestoreData(profile2());
+  }
+  FullRestoreServiceMultipleUsersTest(
+      const FullRestoreServiceMultipleUsersTest&) = delete;
+  FullRestoreServiceMultipleUsersTest& operator=(
+      const FullRestoreServiceMultipleUsersTest&) = delete;
+  ~FullRestoreServiceMultipleUsersTest() override = default;
+
+  TestingProfile* profile2() { return test_helper2_->profile(); }
+  const AccountId& account_id2() const { return test_helper2_->account_id(); }
+
+  void VerifyNotificationForProfile2(bool has_crash_notification,
+                                     bool has_restore_notification) {
+    test_helper2_->VerifyNotification(has_crash_notification,
+                                      has_restore_notification,
+                                      /*is_reboot_notification=*/false);
+  }
+
+  void SimulateClickForProfile2(const std::string& notification_id,
+                                RestoreNotificationButtonIndex action_index) {
+    test_helper2_->SimulateClick(notification_id, action_index);
+  }
+
+  void CreateFullRestoreService2ForTesting() {
+    test_helper2_->CreateFullRestoreServiceForTesting(nullptr);
+  }
+
+  void CreateFullRestoreService2ForTesting(
+      std::unique_ptr<MockFullRestoreServiceDelegate> mock_delegate) {
+    test_helper2_->CreateFullRestoreServiceForTesting(std::move(mock_delegate));
+    content::RunAllTasksUntilIdle();
+  }
+
+  RestoreOption GetRestoreOptionForProfile2() const {
+    return test_helper2_->GetRestoreOption();
+  }
+
+  void SetRestoreOptionForProfile2(RestoreOption restore_option) {
+    test_helper2_->SetRestoreOption(restore_option);
+  }
+
+  void TearDown() override {
+    fake_user_manager()->OnUserProfileWillBeDestroyed(account_id2());
+    test_helper2_.reset();
+
+    FullRestoreServiceTestHavingFullRestoreFile::TearDown();
+  }
+
+ private:
+  std::unique_ptr<FullRestoreTestHelper> test_helper2_;
+  std::unique_ptr<NotificationDisplayServiceTester> display_service2_;
+};
+
 class ForestFullRestoreServiceMultipleUsersTest
     : public FullRestoreServiceMultipleUsersTest {
  protected:
diff --git a/chrome/browser/ash/boca/babelorca/babel_orca_speech_recognizer_impl.cc b/chrome/browser/ash/boca/babelorca/babel_orca_speech_recognizer_impl.cc
index ba403cc3..98472b4 100644
--- a/chrome/browser/ash/boca/babelorca/babel_orca_speech_recognizer_impl.cc
+++ b/chrome/browser/ash/boca/babelorca/babel_orca_speech_recognizer_impl.cc
@@ -9,6 +9,7 @@
 #include "base/functional/callback_helpers.h"
 #include "chrome/browser/ash/accessibility/live_caption/system_live_caption_service.h"
 #include "chromeos/ash/components/boca/babelorca/babel_orca_speech_recognizer.h"
+#include "chromeos/ash/components/boca/babelorca/soda_installer.h"
 #include "chromeos/ash/components/boca/babelorca/speech_recognition_event_handler.h"
 #include "components/live_caption/pref_names.h"
 #include "media/mojo/mojom/speech_recognition.mojom.h"
@@ -16,6 +17,15 @@
 
 namespace ash::babelorca {
 
+namespace {
+void UnwrapSodaInstallationStatus(
+    base::OnceCallback<void(bool)> availabililty_callback,
+    SodaInstaller::InstallationStatus status) {
+  std::move(availabililty_callback)
+      .Run(status == SodaInstaller::InstallationStatus::kReady);
+}
+}  // namespace
+
 BabelOrcaSpeechRecognizerImpl::BabelOrcaSpeechRecognizerImpl(
     Profile* profile,
     PrefService* global_prefs,
@@ -26,10 +36,6 @@
       soda_installer_(global_prefs, profile->GetPrefs(), application_locale),
       speech_recognition_event_handler_(application_locale),
       primary_profile_(profile) {
-  // TODO(384026579): Notify Producer of error, then retry or alert user.
-  // We pass DoNothing here as there is no error reporting mechanism and
-  // we don't want to start speech recognition until Start is called.
-  soda_installer_.GetAvailabilityOrInstall(base::DoNothing());
 }
 BabelOrcaSpeechRecognizerImpl::~BabelOrcaSpeechRecognizerImpl() = default;
 
@@ -47,9 +53,11 @@
 
 void BabelOrcaSpeechRecognizerImpl::Start() {
   // TODO(384026579): Notify Producer of error, then retry or alert user.
-  soda_installer_.GetAvailabilityOrInstall(base::BindOnce(
-      &SystemLiveCaptionService::SpeechRecognitionAvailabilityChanged,
-      service_ptr_factory_.GetWeakPtr()));
+  soda_installer_.InstallSoda(base::BindOnce(
+      &UnwrapSodaInstallationStatus,
+      base::BindOnce(
+          &SystemLiveCaptionService::SpeechRecognitionAvailabilityChanged,
+          service_ptr_factory_.GetWeakPtr())));
 }
 
 void BabelOrcaSpeechRecognizerImpl::Stop() {
diff --git a/chrome/browser/ash/boca/on_task/on_task_locked_session_window_tracker.cc b/chrome/browser/ash/boca/on_task/on_task_locked_session_window_tracker.cc
index 261dbacb..d6d5350 100644
--- a/chrome/browser/ash/boca/on_task/on_task_locked_session_window_tracker.cc
+++ b/chrome/browser/ash/boca/on_task/on_task_locked_session_window_tracker.cc
@@ -271,8 +271,7 @@
   notifications_manager_ = std::move(notifications_manager);
 }
 
-ash::OnTaskPodController*
-LockedSessionWindowTracker::GetOnTaskPodControllerForTesting() {
+ash::OnTaskPodController* LockedSessionWindowTracker::on_task_pod_controller() {
   if (!on_task_pod_controller_) {
     return nullptr;
   }
diff --git a/chrome/browser/ash/boca/on_task/on_task_locked_session_window_tracker.h b/chrome/browser/ash/boca/on_task/on_task_locked_session_window_tracker.h
index 7d8c459..4550ac5d6 100644
--- a/chrome/browser/ash/boca/on_task/on_task_locked_session_window_tracker.h
+++ b/chrome/browser/ash/boca/on_task/on_task_locked_session_window_tracker.h
@@ -95,7 +95,7 @@
       std::unique_ptr<ash::boca::OnTaskNotificationsManager>
           notification_manager);
 
-  ash::OnTaskPodController* GetOnTaskPodControllerForTesting();
+  ash::OnTaskPodController* on_task_pod_controller();
 
   OnTaskBlocklist* on_task_blocklist();
   Browser* browser();
diff --git a/chrome/browser/ash/boca/on_task/on_task_pod_controller_impl.cc b/chrome/browser/ash/boca/on_task/on_task_pod_controller_impl.cc
index f4532ebd..3769934 100644
--- a/chrome/browser/ash/boca/on_task/on_task_pod_controller_impl.cc
+++ b/chrome/browser/ash/boca/on_task/on_task_pod_controller_impl.cc
@@ -141,12 +141,31 @@
   if (is_window_pinned_ != is_window_pinned) {
     on_task_pod_view->OnLockedModeUpdate();
 
-    // Resize the widget to fit the contents view.
+    // Resize and reset bounds of the widget to fit the contents view.
     pod_widget_->SetSize(on_task_pod_view->GetPreferredSize());
+    pod_widget_->SetBounds(CalculateWidgetBounds());
   }
   is_window_pinned_ = is_window_pinned;
 }
 
+void OnTaskPodControllerImpl::OnPauseModeChanged() {
+  if (!pod_widget_) {
+    return;
+  }
+  views::View* const pod_widget_contents_view = pod_widget_->GetContentsView();
+  if (!pod_widget_contents_view) {
+    return;
+  }
+  OnTaskPodView* const on_task_pod_view =
+      static_cast<OnTaskPodView*>(pod_widget_contents_view);
+
+  on_task_pod_view->OnLockedModeUpdate();
+
+  // Resize and reset bounds of the widget to fit the contents view.
+  pod_widget_->SetSize(on_task_pod_view->GetPreferredSize());
+  pod_widget_->SetBounds(CalculateWidgetBounds());
+}
+
 void OnTaskPodControllerImpl::OnPageNavigationContextChanged() {
   if (!pod_widget_) {
     return;
@@ -175,7 +194,10 @@
 }
 
 bool OnTaskPodControllerImpl::CanToggleTabStripVisibility() {
-  return browser_ && platform_util::IsBrowserLockedFullscreen(browser_.get());
+  const auto* const browser_view =
+      BrowserView::GetBrowserViewForBrowser(browser_.get());
+  return browser_ && platform_util::IsBrowserLockedFullscreen(browser_.get()) &&
+         browser_view->immersive_mode_controller()->IsEnabled();
 }
 
 const gfx::Rect OnTaskPodControllerImpl::CalculateWidgetBounds() {
diff --git a/chrome/browser/ash/boca/on_task/on_task_pod_controller_impl.h b/chrome/browser/ash/boca/on_task/on_task_pod_controller_impl.h
index 7564bed7..6c8fe29e 100644
--- a/chrome/browser/ash/boca/on_task/on_task_pod_controller_impl.h
+++ b/chrome/browser/ash/boca/on_task/on_task_pod_controller_impl.h
@@ -38,6 +38,7 @@
   void ReloadCurrentPage() override;
   void ToggleTabStripVisibility(bool show) override;
   void SetSnapLocation(OnTaskPodSnapLocation snap_location) override;
+  void OnPauseModeChanged() override;
   void OnPageNavigationContextChanged() override;
   bool CanNavigateToPreviousPage() override;
   bool CanNavigateToNextPage() override;
diff --git a/chrome/browser/ash/boca/on_task/on_task_pod_controller_impl_browsertest.cc b/chrome/browser/ash/boca/on_task/on_task_pod_controller_impl_browsertest.cc
index eddb8356..555e735 100644
--- a/chrome/browser/ash/boca/on_task/on_task_pod_controller_impl_browsertest.cc
+++ b/chrome/browser/ash/boca/on_task/on_task_pod_controller_impl_browsertest.cc
@@ -70,7 +70,7 @@
 
   ash::OnTaskPodControllerImpl* on_task_pod_controller() {
     return static_cast<ash::OnTaskPodControllerImpl*>(
-        window_tracker()->GetOnTaskPodControllerForTesting());
+        window_tracker()->on_task_pod_controller());
   }
 
   boca::OnTaskSystemWebAppManagerImpl* system_web_app_manager() {
@@ -520,6 +520,61 @@
 }
 
 IN_PROC_BROWSER_TEST_F(OnTaskPodControllerImplBrowserTest,
+                       DisablePinTabStripFunctionalityWhenPaused) {
+  // Launch OnTask SWA.
+  base::test::TestFuture<bool> launch_future;
+  system_web_app_manager()->LaunchSystemWebAppAsync(
+      launch_future.GetCallback());
+  ASSERT_TRUE(launch_future.Get());
+  Browser* const boca_app_browser = FindBocaSystemWebAppBrowser();
+  ASSERT_THAT(boca_app_browser, NotNull());
+  ASSERT_TRUE(boca_app_browser->IsLockedForOnTask());
+
+  // Set up window tracker to track the app window. This is when the OnTask pod
+  // is set up.
+  const SessionID window_id = boca_app_browser->session_id();
+  ASSERT_TRUE(window_id.is_valid());
+  system_web_app_manager()->SetWindowTrackerForSystemWebAppWindow(
+      window_id, /*observers=*/{});
+  system_web_app_manager()->SetPinStateForSystemWebAppWindow(/*pinned=*/true,
+                                                             window_id);
+  system_web_app_manager()->SetPauseStateForSystemWebAppWindow(/*paused=*/true,
+                                                               window_id);
+  ASSERT_THAT(on_task_pod_controller(), NotNull());
+  EXPECT_FALSE(on_task_pod_controller()->CanToggleTabStripVisibility());
+}
+
+IN_PROC_BROWSER_TEST_F(OnTaskPodControllerImplBrowserTest,
+                       EnablePinTabStripFunctionalityWhenUnpaused) {
+  // Launch OnTask SWA.
+  base::test::TestFuture<bool> launch_future;
+  system_web_app_manager()->LaunchSystemWebAppAsync(
+      launch_future.GetCallback());
+  ASSERT_TRUE(launch_future.Get());
+  Browser* const boca_app_browser = FindBocaSystemWebAppBrowser();
+  ASSERT_THAT(boca_app_browser, NotNull());
+  ASSERT_TRUE(boca_app_browser->IsLockedForOnTask());
+
+  // Set up window tracker to track the app window. This is when the OnTask pod
+  // is set up.
+  const SessionID window_id = boca_app_browser->session_id();
+  ASSERT_TRUE(window_id.is_valid());
+  system_web_app_manager()->SetWindowTrackerForSystemWebAppWindow(
+      window_id, /*observers=*/{});
+  system_web_app_manager()->SetPinStateForSystemWebAppWindow(/*pinned=*/true,
+                                                             window_id);
+  system_web_app_manager()->SetPauseStateForSystemWebAppWindow(/*paused=*/true,
+                                                               window_id);
+  ASSERT_THAT(on_task_pod_controller(), NotNull());
+  EXPECT_FALSE(on_task_pod_controller()->CanToggleTabStripVisibility());
+
+  system_web_app_manager()->SetPauseStateForSystemWebAppWindow(/*paused=*/false,
+                                                               window_id);
+  ASSERT_THAT(on_task_pod_controller(), NotNull());
+  EXPECT_TRUE(on_task_pod_controller()->CanToggleTabStripVisibility());
+}
+
+IN_PROC_BROWSER_TEST_F(OnTaskPodControllerImplBrowserTest,
                        ShowAndHideTabStripWhenTogglePinTabStripButton) {
   // Launch OnTask SWA.
   base::test::TestFuture<bool> launch_future;
@@ -662,5 +717,70 @@
                            kPodHorizontalBorder));
 }
 
+IN_PROC_BROWSER_TEST_F(OnTaskPodControllerImplBrowserTest,
+                       RepositionPodWhenSnapLocationAndLocked) {
+  // Launch OnTask SWA.
+  base::test::TestFuture<bool> launch_future;
+  system_web_app_manager()->LaunchSystemWebAppAsync(
+      launch_future.GetCallback());
+  ASSERT_TRUE(launch_future.Get());
+  Browser* const boca_app_browser = FindBocaSystemWebAppBrowser();
+  ASSERT_THAT(boca_app_browser, NotNull());
+  ASSERT_TRUE(boca_app_browser->IsLockedForOnTask());
+
+  // Set up window tracker to track the app window. This is when the OnTask pod
+  // is set up.
+  const SessionID window_id = boca_app_browser->session_id();
+  ASSERT_TRUE(window_id.is_valid());
+  system_web_app_manager()->SetWindowTrackerForSystemWebAppWindow(
+      window_id, /*observers=*/{});
+  ASSERT_THAT(on_task_pod_controller(), NotNull());
+
+  // Verify initial pod position.
+  ASSERT_EQ(on_task_pod_controller()->GetSnapLocationForTesting(),
+            OnTaskPodSnapLocation::kTopLeft);
+  views::Widget* const on_task_pod_widget =
+      on_task_pod_controller()->GetPodWidgetForTesting();
+  const gfx::Rect boca_app_browser_bounds =
+      on_task_pod_widget->parent()->GetWindowBoundsInScreen();
+  const int boca_app_browser_frame_header_height =
+      boca::GetFrameHeaderHeight(on_task_pod_widget->parent());
+  EXPECT_EQ(on_task_pod_widget->GetWindowBoundsInScreen().origin(),
+            gfx::Point(boca_app_browser_bounds.x() + kPodVerticalBorder,
+                       boca_app_browser_bounds.y() +
+                           boca_app_browser_frame_header_height +
+                           kPodHorizontalBorder));
+
+  // Update pod snap location and verify its new position.
+  on_task_pod_controller()->SetSnapLocation(OnTaskPodSnapLocation::kTopRight);
+  ASSERT_EQ(on_task_pod_controller()->GetSnapLocationForTesting(),
+            OnTaskPodSnapLocation::kTopRight);
+  EXPECT_EQ(on_task_pod_widget->GetWindowBoundsInScreen().origin(),
+            gfx::Point(boca_app_browser_bounds.right() -
+                           on_task_pod_widget->GetContentsView()
+                               ->GetPreferredSize()
+                               .width() -
+                           kPodVerticalBorder,
+                       boca_app_browser_bounds.y() +
+                           boca_app_browser_frame_header_height +
+                           kPodHorizontalBorder));
+
+  // Pin the window and verify the new position of the pod.
+  system_web_app_manager()->SetPinStateForSystemWebAppWindow(/*pinned=*/true,
+                                                             window_id);
+  ASSERT_THAT(on_task_pod_controller(), NotNull());
+  const gfx::Rect new_boca_app_browser_bounds =
+      on_task_pod_widget->parent()->GetWindowBoundsInScreen();
+  EXPECT_EQ(on_task_pod_widget->GetWindowBoundsInScreen().origin(),
+            gfx::Point(new_boca_app_browser_bounds.right() -
+                           on_task_pod_widget->GetContentsView()
+                               ->GetPreferredSize()
+                               .width() -
+                           kPodVerticalBorder,
+                       new_boca_app_browser_bounds.y() +
+                           boca_app_browser_frame_header_height +
+                           kPodHorizontalBorder));
+}
+
 }  // namespace
 }  // namespace ash
diff --git a/chrome/browser/ash/boca/on_task/on_task_session_manager_browsertest.cc b/chrome/browser/ash/boca/on_task/on_task_session_manager_browsertest.cc
index d4eb0d8..641ee068 100644
--- a/chrome/browser/ash/boca/on_task/on_task_session_manager_browsertest.cc
+++ b/chrome/browser/ash/boca/on_task/on_task_session_manager_browsertest.cc
@@ -7,6 +7,7 @@
 #include "ash/constants/ash_features.h"
 #include "ash/webui/boca_ui/url_constants.h"
 #include "ash/webui/system_apps/public/system_web_app_type.h"
+#include "base/test/run_until.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/test_future.h"
 #include "chrome/browser/ash/boca/boca_manager.h"
@@ -17,6 +18,7 @@
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_commands.h"
 #include "chrome/browser/ui/browser_window.h"
+#include "chrome/browser/ui/views/frame/browser_view.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/ui_test_utils.h"
 #include "chromeos/ash/components/boca/on_task/util/mock_clock.h"
@@ -267,6 +269,66 @@
 }
 
 IN_PROC_BROWSER_TEST_F(OnTaskSessionManagerBrowserTest,
+                       ShouldPinBocaSWAWhenPauseAndUnpauseOnBundleUpdated) {
+  content::TestNavigationObserver navigation_observer((GURL(kTestUrl1)));
+  navigation_observer.StartWatchingNewWebContents();
+
+  // Start OnTask session and spawn one tab outside the homepage tab.
+  GetOnTaskSessionManager()->OnSessionStarted(kSessionId,
+                                              ::boca::UserIdentity());
+  ::boca::Bundle bundle;
+  bundle.add_content_configs()->set_url(kTestUrl1);
+  GetOnTaskSessionManager()->OnBundleUpdated(bundle);
+  navigation_observer.Wait();
+
+  Browser* const boca_app_browser = FindBocaSystemWebAppBrowser();
+  ASSERT_THAT(boca_app_browser, NotNull());
+  ASSERT_TRUE(boca_app_browser->IsLockedForOnTask());
+  auto* const tab_strip_model = boca_app_browser->tab_strip_model();
+  ASSERT_EQ(tab_strip_model->count(), 2);
+  tab_strip_model->ActivateTabAt(1);
+  EXPECT_EQ(tab_strip_model->GetActiveWebContents()->GetVisibleURL(),
+            GURL(kTestUrl1));
+
+  // Lock the boca app.
+  bundle.set_locked(true);
+  GetOnTaskSessionManager()->OnBundleUpdated(bundle);
+  WaitForLockedModeCountdown();
+  ASSERT_TRUE(platform_util::IsBrowserLockedFullscreen(boca_app_browser));
+  EXPECT_FALSE(chromeos::wm::CanFloatWindow(
+      boca_app_browser->window()->GetNativeWindow()));
+
+  // Pause the boca app.
+  bundle.set_lock_to_app_home(true);
+  GetOnTaskSessionManager()->OnBundleUpdated(bundle);
+  ASSERT_TRUE(platform_util::IsBrowserLockedFullscreen(boca_app_browser));
+  EXPECT_FALSE(chromeos::wm::CanFloatWindow(
+      boca_app_browser->window()->GetNativeWindow()));
+  auto* const browser_view =
+      BrowserView::GetBrowserViewForBrowser(boca_app_browser);
+  // Wait until immersive mode is disabled in pause mode.
+  ASSERT_TRUE(base::test::RunUntil([&]() {
+    return !browser_view->immersive_mode_controller()->IsEnabled();
+  }));
+  EXPECT_EQ(tab_strip_model->GetActiveWebContents()->GetVisibleURL(),
+            GURL(kChromeBocaAppUntrustedIndexURL));
+
+  // Unpause the boca app.
+  bundle.set_lock_to_app_home(false);
+  GetOnTaskSessionManager()->OnBundleUpdated(bundle);
+  ASSERT_TRUE(platform_util::IsBrowserLockedFullscreen(boca_app_browser));
+  EXPECT_FALSE(chromeos::wm::CanFloatWindow(
+      boca_app_browser->window()->GetNativeWindow()));
+  EXPECT_TRUE(browser_view->immersive_mode_controller()->IsEnabled());
+
+  // Unlock the Boca app to unblock test teardown that involves browser window
+  // close.
+  bundle.set_locked(false);
+  GetOnTaskSessionManager()->OnBundleUpdated(bundle);
+  EXPECT_FALSE(platform_util::IsBrowserLockedFullscreen(boca_app_browser));
+}
+
+IN_PROC_BROWSER_TEST_F(OnTaskSessionManagerBrowserTest,
                        ShouldApplyOpenNavRestrictionsToTabsOnBundleUpdated) {
   content::TestNavigationObserver navigation_observer((GURL(kTestUrl1)));
   navigation_observer.StartWatchingNewWebContents();
diff --git a/chrome/browser/ash/boca/on_task/on_task_system_web_app_manager_impl.cc b/chrome/browser/ash/boca/on_task/on_task_system_web_app_manager_impl.cc
index 373217a..a550c36 100644
--- a/chrome/browser/ash/boca/on_task/on_task_system_web_app_manager_impl.cc
+++ b/chrome/browser/ash/boca/on_task/on_task_system_web_app_manager_impl.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/ash/boca/on_task/on_task_system_web_app_manager_impl.h"
 
+#include "ash/boca/on_task/on_task_pod_controller.h"
 #include "ash/webui/boca_ui/url_constants.h"
 #include "ash/wm/window_pin_util.h"
 #include "base/functional/bind.h"
@@ -24,6 +25,7 @@
 #include "chrome/browser/ui/browser_window.h"
 #include "chrome/browser/ui/exclusive_access/exclusive_access_manager.h"
 #include "chrome/browser/ui/exclusive_access/fullscreen_controller.h"
+#include "chrome/browser/ui/views/frame/browser_view.h"
 #include "chromeos/ash/components/boca/on_task/activity/active_tab_tracker.h"
 #include "chromeos/ash/components/boca/on_task/on_task_blocklist.h"
 #include "chromeos/ui/base/window_properties.h"
@@ -180,6 +182,68 @@
   command_controller->UpdateCommandEnabled(IDC_DEV_TOOLS_TOGGLE, false);
 }
 
+void OnTaskSystemWebAppManagerImpl::SetPauseStateForSystemWebAppWindow(
+    bool paused,
+    SessionID window_id) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  Browser* const browser = GetBrowserWindowWithID(window_id);
+  if (!browser) {
+    return;
+  }
+
+  auto* const immersive_mode_controller =
+      BrowserView::GetBrowserViewForBrowser(browser)
+          ->immersive_mode_controller();
+  bool avoid_using_immersive_mode =
+      platform_util::IsBrowserLockedFullscreen(browser) && paused;
+  if (avoid_using_immersive_mode) {
+    // Hide tab strip in pause mode.
+    immersive_mode_controller->SetEnabled(false);
+  } else if (platform_util::IsBrowserLockedFullscreen(browser)) {
+    immersive_mode_controller->SetEnabled(true);
+  }
+
+  if (paused) {
+    // Focus on the boca homepage in pause mode.
+    browser->tab_strip_model()->ActivateTabAt(0);
+  }
+
+  EnableOrDisableCommandsForTabSwitch(window_id, !paused);
+
+  // Update Ontask pod to enable or disable toggle tab strip visibility.
+  LockedSessionWindowTracker* const window_tracker = GetWindowTracker();
+  if (!window_tracker) {
+    return;
+  }
+  auto* const pod_controller = window_tracker->on_task_pod_controller();
+  if (pod_controller) {
+    pod_controller->OnPauseModeChanged();
+  }
+}
+
+void OnTaskSystemWebAppManagerImpl::EnableOrDisableCommandsForTabSwitch(
+    SessionID window_id,
+    bool enabled) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  Browser* const browser = GetBrowserWindowWithID(window_id);
+  if (!browser) {
+    return;
+  }
+
+  chrome::BrowserCommandController* const command_controller =
+      browser->command_controller();
+  command_controller->UpdateCommandEnabled(IDC_SELECT_NEXT_TAB, enabled);
+  command_controller->UpdateCommandEnabled(IDC_SELECT_PREVIOUS_TAB, enabled);
+  command_controller->UpdateCommandEnabled(IDC_SELECT_TAB_0, enabled);
+  command_controller->UpdateCommandEnabled(IDC_SELECT_TAB_1, enabled);
+  command_controller->UpdateCommandEnabled(IDC_SELECT_TAB_2, enabled);
+  command_controller->UpdateCommandEnabled(IDC_SELECT_TAB_3, enabled);
+  command_controller->UpdateCommandEnabled(IDC_SELECT_TAB_4, enabled);
+  command_controller->UpdateCommandEnabled(IDC_SELECT_TAB_5, enabled);
+  command_controller->UpdateCommandEnabled(IDC_SELECT_TAB_6, enabled);
+  command_controller->UpdateCommandEnabled(IDC_SELECT_TAB_7, enabled);
+}
+
 // TODO(b/367417612): Add unit test for this function.
 void OnTaskSystemWebAppManagerImpl::SetWindowTrackerForSystemWebAppWindow(
     SessionID window_id,
diff --git a/chrome/browser/ash/boca/on_task/on_task_system_web_app_manager_impl.h b/chrome/browser/ash/boca/on_task/on_task_system_web_app_manager_impl.h
index b45f5104..174704a7 100644
--- a/chrome/browser/ash/boca/on_task/on_task_system_web_app_manager_impl.h
+++ b/chrome/browser/ash/boca/on_task/on_task_system_web_app_manager_impl.h
@@ -35,6 +35,8 @@
   SessionID GetActiveSystemWebAppWindowID() override;
   void SetPinStateForSystemWebAppWindow(bool pinned,
                                         SessionID window_id) override;
+  void SetPauseStateForSystemWebAppWindow(bool paused,
+                                          SessionID window_id) override;
   void SetWindowTrackerForSystemWebAppWindow(
       SessionID window_id,
       const std::vector<BocaWindowObserver*> observers) override;
@@ -59,6 +61,8 @@
 
   void DisableCommandsForDevTools(SessionID window_id);
 
+  void EnableOrDisableCommandsForTabSwitch(SessionID window_id, bool enabled);
+
   raw_ptr<Profile> profile_;
 
   raw_ptr<LockedSessionWindowTracker> window_tracker_for_testing_;
diff --git a/chrome/browser/ash/boca/on_task/on_task_system_web_app_manager_impl_browsertest.cc b/chrome/browser/ash/boca/on_task/on_task_system_web_app_manager_impl_browsertest.cc
index 33e0023..8067b5e 100644
--- a/chrome/browser/ash/boca/on_task/on_task_system_web_app_manager_impl_browsertest.cc
+++ b/chrome/browser/ash/boca/on_task/on_task_system_web_app_manager_impl_browsertest.cc
@@ -21,6 +21,7 @@
 #include "chrome/browser/ui/browser_window.h"
 #include "chrome/browser/ui/exclusive_access/exclusive_access_manager.h"
 #include "chrome/browser/ui/exclusive_access/fullscreen_controller.h"
+#include "chrome/browser/ui/views/frame/browser_view.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chromeos/ash/components/boca/on_task/on_task_session_manager.h"
 #include "chromeos/ash/components/boca/proto/roster.pb.h"
@@ -151,7 +152,6 @@
   // Pin the Boca app and verify result.
   system_web_app_manager.SetPinStateForSystemWebAppWindow(
       /*pinned=*/true, boca_app_browser->session_id());
-  content::RunAllTasksUntilIdle();
   EXPECT_TRUE(platform_util::IsBrowserLockedFullscreen(boca_app_browser));
   EXPECT_FALSE(chromeos::wm::CanFloatWindow(
       boca_app_browser->window()->GetNativeWindow()));
@@ -170,13 +170,11 @@
 
   system_web_app_manager.SetPinStateForSystemWebAppWindow(
       /*pinned=*/true, boca_app_browser->session_id());
-  content::RunAllTasksUntilIdle();
   ASSERT_TRUE(platform_util::IsBrowserLockedFullscreen(boca_app_browser));
 
   // Unpin the Boca app and verify result.
   system_web_app_manager.SetPinStateForSystemWebAppWindow(
       /*pinned=*/false, boca_app_browser->session_id());
-  content::RunAllTasksUntilIdle();
   EXPECT_FALSE(platform_util::IsBrowserLockedFullscreen(boca_app_browser));
   EXPECT_FALSE(chromeos::wm::CanFloatWindow(
       boca_app_browser->window()->GetNativeWindow()));
@@ -201,7 +199,6 @@
   // Attempt to unpin the Boca app and verify result.
   system_web_app_manager.SetPinStateForSystemWebAppWindow(
       /*pinned=*/false, boca_app_browser->session_id());
-  content::RunAllTasksUntilIdle();
   EXPECT_FALSE(fullscreen_controller->IsFullscreenForBrowser());
   EXPECT_FALSE(platform_util::IsBrowserLockedFullscreen(boca_app_browser));
   EXPECT_FALSE(chromeos::wm::CanFloatWindow(
@@ -209,6 +206,96 @@
 }
 
 IN_PROC_BROWSER_TEST_F(OnTaskSystemWebAppManagerImplBrowserTest,
+                       PinAndPauseSystemWebAppWindow) {
+  // Launch Boca app for testing purposes.
+  OnTaskSystemWebAppManagerImpl system_web_app_manager(profile());
+  base::test::TestFuture<bool> launch_future;
+  system_web_app_manager.LaunchSystemWebAppAsync(launch_future.GetCallback());
+  ASSERT_TRUE(launch_future.Get());
+  Browser* const boca_app_browser = FindBocaSystemWebAppBrowser();
+  ASSERT_THAT(boca_app_browser, NotNull());
+  EXPECT_EQ(boca_app_browser->tab_strip_model()->count(), 1);
+
+  // Create tab so we can verify we are on boca homepage when paused.
+  system_web_app_manager.CreateBackgroundTabWithUrl(
+      boca_app_browser->session_id(), GURL(kTestUrl),
+      LockedNavigationOptions::BLOCK_NAVIGATION);
+  ASSERT_EQ(boca_app_browser->tab_strip_model()->count(), 2);
+
+  // Pin and pause the Boca app and verify result.
+  system_web_app_manager.SetPinStateForSystemWebAppWindow(
+      /*pinned=*/true, boca_app_browser->session_id());
+  system_web_app_manager.SetPauseStateForSystemWebAppWindow(
+      /*paused=*/true, boca_app_browser->session_id());
+  ASSERT_TRUE(platform_util::IsBrowserLockedFullscreen(boca_app_browser));
+  EXPECT_FALSE(chromeos::wm::CanFloatWindow(
+      boca_app_browser->window()->GetNativeWindow()));
+  auto* const browser_view =
+      BrowserView::GetBrowserViewForBrowser(boca_app_browser);
+  EXPECT_FALSE(browser_view->immersive_mode_controller()->IsEnabled());
+  EXPECT_EQ(boca_app_browser->tab_strip_model()->active_index(), 0);
+
+  // Verify that tab switch commands are disabled.
+  chrome::BrowserCommandController* const command_controller =
+      boca_app_browser->command_controller();
+  EXPECT_FALSE(command_controller->IsCommandEnabled(IDC_SELECT_NEXT_TAB));
+  EXPECT_FALSE(command_controller->IsCommandEnabled(IDC_SELECT_PREVIOUS_TAB));
+  EXPECT_FALSE(command_controller->IsCommandEnabled(IDC_SELECT_TAB_0));
+  EXPECT_FALSE(command_controller->IsCommandEnabled(IDC_SELECT_TAB_1));
+  EXPECT_FALSE(command_controller->IsCommandEnabled(IDC_SELECT_TAB_2));
+  EXPECT_FALSE(command_controller->IsCommandEnabled(IDC_SELECT_TAB_3));
+  EXPECT_FALSE(command_controller->IsCommandEnabled(IDC_SELECT_TAB_4));
+  EXPECT_FALSE(command_controller->IsCommandEnabled(IDC_SELECT_TAB_5));
+  EXPECT_FALSE(command_controller->IsCommandEnabled(IDC_SELECT_TAB_6));
+  EXPECT_FALSE(command_controller->IsCommandEnabled(IDC_SELECT_TAB_7));
+}
+
+IN_PROC_BROWSER_TEST_F(OnTaskSystemWebAppManagerImplBrowserTest,
+                       UnpauseSystemWebAppWindow) {
+  // Launch Boca app for testing purposes.
+  OnTaskSystemWebAppManagerImpl system_web_app_manager(profile());
+  base::test::TestFuture<bool> launch_future;
+  system_web_app_manager.LaunchSystemWebAppAsync(launch_future.GetCallback());
+  ASSERT_TRUE(launch_future.Get());
+  Browser* const boca_app_browser = FindBocaSystemWebAppBrowser();
+  ASSERT_THAT(boca_app_browser, NotNull());
+
+  // Pin and pause the Boca app.
+  system_web_app_manager.SetPinStateForSystemWebAppWindow(
+      /*pinned=*/true, boca_app_browser->session_id());
+  system_web_app_manager.SetPauseStateForSystemWebAppWindow(
+      /*paused=*/true, boca_app_browser->session_id());
+  ASSERT_TRUE(platform_util::IsBrowserLockedFullscreen(boca_app_browser));
+  EXPECT_FALSE(chromeos::wm::CanFloatWindow(
+      boca_app_browser->window()->GetNativeWindow()));
+  auto* const browser_view =
+      BrowserView::GetBrowserViewForBrowser(boca_app_browser);
+  EXPECT_FALSE(browser_view->immersive_mode_controller()->IsEnabled());
+
+  // Unpause the Boca app and verify result.
+  system_web_app_manager.SetPauseStateForSystemWebAppWindow(
+      /*paused=*/false, boca_app_browser->session_id());
+  ASSERT_TRUE(platform_util::IsBrowserLockedFullscreen(boca_app_browser));
+  EXPECT_FALSE(chromeos::wm::CanFloatWindow(
+      boca_app_browser->window()->GetNativeWindow()));
+  EXPECT_TRUE(browser_view->immersive_mode_controller()->IsEnabled());
+
+  // Verify that tab switch commands are enabled.
+  chrome::BrowserCommandController* const command_controller =
+      boca_app_browser->command_controller();
+  EXPECT_TRUE(command_controller->IsCommandEnabled(IDC_SELECT_NEXT_TAB));
+  EXPECT_TRUE(command_controller->IsCommandEnabled(IDC_SELECT_PREVIOUS_TAB));
+  EXPECT_TRUE(command_controller->IsCommandEnabled(IDC_SELECT_TAB_0));
+  EXPECT_TRUE(command_controller->IsCommandEnabled(IDC_SELECT_TAB_1));
+  EXPECT_TRUE(command_controller->IsCommandEnabled(IDC_SELECT_TAB_2));
+  EXPECT_TRUE(command_controller->IsCommandEnabled(IDC_SELECT_TAB_3));
+  EXPECT_TRUE(command_controller->IsCommandEnabled(IDC_SELECT_TAB_4));
+  EXPECT_TRUE(command_controller->IsCommandEnabled(IDC_SELECT_TAB_5));
+  EXPECT_TRUE(command_controller->IsCommandEnabled(IDC_SELECT_TAB_6));
+  EXPECT_TRUE(command_controller->IsCommandEnabled(IDC_SELECT_TAB_7));
+}
+
+IN_PROC_BROWSER_TEST_F(OnTaskSystemWebAppManagerImplBrowserTest,
                        CreateBackgroundTabWithUrl) {
   // Launch Boca app for testing purposes.
   OnTaskSystemWebAppManagerImpl system_web_app_manager(profile());
@@ -393,13 +480,11 @@
 
   system_web_app_manager.SetPinStateForSystemWebAppWindow(
       /*pinned=*/true, boca_app_browser->session_id());
-  content::RunAllTasksUntilIdle();
   ASSERT_TRUE(platform_util::IsBrowserLockedFullscreen(boca_app_browser));
 
   // Unpin the Boca app and verify that dev tools commands are disabled.
   system_web_app_manager.SetPinStateForSystemWebAppWindow(
       /*pinned=*/false, boca_app_browser->session_id());
-  content::RunAllTasksUntilIdle();
   EXPECT_FALSE(platform_util::IsBrowserLockedFullscreen(boca_app_browser));
   chrome::BrowserCommandController* const command_controller =
       boca_app_browser->command_controller();
diff --git a/chrome/browser/ash/crostini/crostini_manager.cc b/chrome/browser/ash/crostini/crostini_manager.cc
index 9f1f609..a3974b6 100644
--- a/chrome/browser/ash/crostini/crostini_manager.cc
+++ b/chrome/browser/ash/crostini/crostini_manager.cc
@@ -1774,6 +1774,7 @@
   request.set_vm_name(vm_name);
   request.set_owner_id(owner_id_);
   request.set_username(username);
+  request.add_group_names("audio");
   request.add_group_names("cdrom");
   request.add_group_names("dialout");
   request.add_group_names("floppy");
diff --git a/chrome/browser/ash/extensions/external_cache_impl.cc b/chrome/browser/ash/extensions/external_cache_impl.cc
index cd144dd..bfd29c14 100644
--- a/chrome/browser/ash/extensions/external_cache_impl.cc
+++ b/chrome/browser/ash/extensions/external_cache_impl.cc
@@ -485,8 +485,7 @@
 
   // Jitter the frequency by +/- 20% like it's done in ExtensionUpdater.
   const double jitter_factor = base::RandDouble() * 0.4 + 0.8;
-  base::TimeDelta delay =
-      base::Seconds(extensions::kDefaultUpdateFrequencySeconds);
+  base::TimeDelta delay = extensions::kDefaultUpdateFrequency;
   delay *= jitter_factor;
   content::GetUIThreadTaskRunner({base::TaskPriority::BEST_EFFORT})
       ->PostDelayedTask(
diff --git a/chrome/browser/ash/extensions/file_manager/logged_extension_function.cc b/chrome/browser/ash/extensions/file_manager/logged_extension_function.cc
index ef41c90..4c6eda85 100644
--- a/chrome/browser/ash/extensions/file_manager/logged_extension_function.cc
+++ b/chrome/browser/ash/extensions/file_manager/logged_extension_function.cc
@@ -11,6 +11,7 @@
 #include "chrome/browser/ash/extensions/file_manager/private_api_util.h"
 #include "chrome/browser/profiles/profile.h"
 #include "components/drive/event_logger.h"
+#include "extensions/browser/extension_function.h"
 
 namespace extensions {
 
@@ -39,7 +40,7 @@
   std::string request_id_str = request_uuid().AsLowercaseString();
   if (logger && log_on_completion_) {
     DCHECK(response_type());
-    bool success = *response_type() == SUCCEEDED;
+    bool success = *response_type() == ResponseType::kSucceeded;
     logger->Log(logging::LOGGING_INFO,
                 "%s[%s] %s. (elapsed time: %" PRId64 "ms)", name(),
                 request_id_str.c_str(), success ? "succeeded" : "failed",
diff --git a/chrome/browser/ash/lobster/lobster_candidate_resizer_unittest.cc b/chrome/browser/ash/lobster/lobster_candidate_resizer_unittest.cc
index 5759c44e..1e373d9 100644
--- a/chrome/browser/ash/lobster/lobster_candidate_resizer_unittest.cc
+++ b/chrome/browser/ash/lobster/lobster_candidate_resizer_unittest.cc
@@ -79,7 +79,8 @@
                   CreateTestBitmap(kFullImageDimensionLength,
                                    kFullImageDimensionLength),
                   /*expected_generation_seed=*/kFakeBaseGenerationSeed,
-                  /*expected_query=*/"a nice strawberry")));
+                  /*expected_query=*/"a nice strawberry",
+                  /*expected_rewritten_query=*/"a nice strawberry")));
 }
 
 TEST_F(LobsterCandidateResizerTest,
diff --git a/chrome/browser/ash/lobster/lobster_image_provider_from_memory.cc b/chrome/browser/ash/lobster/lobster_image_provider_from_memory.cc
index 6e443b89..8fd9484d 100644
--- a/chrome/browser/ash/lobster/lobster_image_provider_from_memory.cc
+++ b/chrome/browser/ash/lobster/lobster_image_provider_from_memory.cc
@@ -6,6 +6,7 @@
 
 #include "ash/public/cpp/lobster/lobster_image_candidate.h"
 #include "base/no_destructor.h"
+#include "base/strings/strcat.h"
 #include "third_party/skia/include/core/SkBitmap.h"
 #include "third_party/skia/include/core/SkColor.h"
 #include "ui/gfx/codec/jpeg_codec.h"
@@ -34,19 +35,19 @@
 
 ash::LobsterResult CreateTestingLobsterResult(
     LobsterCandidateIdGenerator* id_generator,
-    const std::string& query,
+    const std::string& user_query,
+    const std::string& rewritten_query,
     int num_candidates,
     const gfx::Size& image_dimensions) {
   CHECK(id_generator);
 
   std::vector<ash::LobsterImageCandidate> image_candidates;
   for (int index = 0; index < num_candidates; ++index) {
-    image_candidates.push_back(ash::LobsterImageCandidate{
-        .id = id_generator->GenerateNextId(),
-        .image_bytes = std::string(GetTestJpgBytes(CreateTestBitmap(
+    image_candidates.push_back(ash::LobsterImageCandidate(
+        id_generator->GenerateNextId(),
+        std::string(GetTestJpgBytes(CreateTestBitmap(
             image_dimensions.width(), image_dimensions.height()))),
-        .seed = static_cast<uint32_t>(index),
-        .query = query});
+        static_cast<uint32_t>(index), user_query, rewritten_query));
   }
   return image_candidates;
 }
@@ -64,7 +65,9 @@
     int num_candidates,
     ash::RequestCandidatesCallback callback) {
   ash::LobsterResult results = CreateTestingLobsterResult(
-      id_generator_, query, num_candidates, kPreviewImageSize);
+      id_generator_, /*user_query=*/query,
+      /*rewritten_query=*/base::StrCat({"rewritten: ", query}), num_candidates,
+      kPreviewImageSize);
   delay_timer_.Start(FROM_HERE, kMultipleImagesFetchDelay,
                      base::BindOnce(
                          [](const ash::LobsterResult results,
@@ -78,9 +81,10 @@
     const std::string& query,
     uint32_t seed,
     ash::RequestCandidatesCallback callback) {
-  ash::LobsterResult results =
-      CreateTestingLobsterResult(id_generator_, query,
-                                 /*num_candidates=*/1, kFullImageSize);
+  ash::LobsterResult results = CreateTestingLobsterResult(
+      id_generator_, /*user_query=*/query,
+      /*rewritten_query=*/base::StrCat({"rewritten: ", query}),
+      /*num_candidates=*/1, kFullImageSize);
   delay_timer_.Start(FROM_HERE, kSingleImageFetchDelay,
                      base::BindOnce(
                          [](const ash::LobsterResult results,
diff --git a/chrome/browser/ash/lobster/lobster_image_provider_from_snapper.cc b/chrome/browser/ash/lobster/lobster_image_provider_from_snapper.cc
index 202e3d9..986acf0 100644
--- a/chrome/browser/ash/lobster/lobster_image_provider_from_snapper.cc
+++ b/chrome/browser/ash/lobster/lobster_image_provider_from_snapper.cc
@@ -127,7 +127,8 @@
 std::optional<ash::LobsterImageCandidate> ToLobsterImageCandidate(
     uint32_t id,
     uint32_t seed,
-    const std::string& query,
+    const std::string& user_query,
+    const std::string& rewritten_query,
     const SkBitmap& decoded_bitmap) {
   base::AssertLongCPUWorkAllowed();
 
@@ -141,13 +142,15 @@
       /*id=*/id, /*image_bytes=*/
       std::string(base::as_string_view(data.value())),
       /*seed=*/seed,
-      /*query=*/query.data());
+      /*user_query=*/user_query,
+      /*rewritten_query=*/rewritten_query);
 }
 
 void EncodeBitmap(
     uint32_t id,
     uint32_t seed,
-    const std::string& query,
+    const std::string& user_query,
+    const std::string& rewritten_query,
     base::OnceCallback<void(std::optional<ash::LobsterImageCandidate>)>
         callback,
     const SkBitmap& decoded_bitmap) {
@@ -158,7 +161,8 @@
   }
   base::ThreadPool::PostTaskAndReplyWithResult(
       FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_VISIBLE},
-      base::BindOnce(&ToLobsterImageCandidate, id, seed, query, decoded_bitmap),
+      base::BindOnce(&ToLobsterImageCandidate, id, seed, user_query,
+                     rewritten_query, decoded_bitmap),
       std::move(callback));
 }
 
@@ -166,7 +170,7 @@
     const manta::proto::OutputData& output_data,
     data_decoder::DataDecoder* data_decoder,
     uint32_t id,
-    const std::string& query,
+    const std::string& user_query,
     base::OnceCallback<void(std::optional<ash::LobsterImageCandidate>)>
         callback) {
   data_decoder::DecodeImage(
@@ -174,6 +178,7 @@
       data_decoder::mojom::ImageCodec::kDefault,
       /*shrink_to_fit=*/true, data_decoder::kDefaultMaxSizeInBytes, gfx::Size(),
       base::BindOnce(&EncodeBitmap, id, output_data.generation_seed(),
+                     user_query,
                      // if the `generative_prompt`is populated with the
                      // rewritten query if query rewritter is enabled, and is
                      // populated with the original query otherwise.
diff --git a/chrome/browser/ash/lobster/lobster_image_provider_from_snapper_unittest.cc b/chrome/browser/ash/lobster/lobster_image_provider_from_snapper_unittest.cc
index 26bee7c..e023997 100644
--- a/chrome/browser/ash/lobster/lobster_image_provider_from_snapper_unittest.cc
+++ b/chrome/browser/ash/lobster/lobster_image_provider_from_snapper_unittest.cc
@@ -74,22 +74,25 @@
       /*query=*/"a lovely cake",
       /*num_candidates=*/2, future.GetCallback());
 
-  EXPECT_THAT(future.Get().value(),
-              testing::ElementsAre(
-                  EqLobsterImageCandidate(
-                      /*expected_id=*/0,
-                      /*expected_bitmap=*/
-                      CreateTestBitmap(kPreviewImageDimensionSize,
-                                       kPreviewImageDimensionSize),
-                      /*expected_generation_seed=*/10,
-                      /*expected_query=*/"rewritten 1: a nice cake"),
-                  EqLobsterImageCandidate(
-                      /*expected_id=*/1,
-                      /*expected_bitmap=*/
-                      CreateTestBitmap(kPreviewImageDimensionSize,
-                                       kPreviewImageDimensionSize),
-                      /*expected_generation_seed=*/11,
-                      /*expected_query=*/"rewritten 2: a beautiful cake")));
+  EXPECT_THAT(
+      future.Get().value(),
+      testing::ElementsAre(
+          EqLobsterImageCandidate(
+              /*expected_id=*/0,
+              /*expected_bitmap=*/
+              CreateTestBitmap(kPreviewImageDimensionSize,
+                               kPreviewImageDimensionSize),
+              /*expected_generation_seed=*/10,
+              /*expected_user_query=*/"a lovely cake",
+              /*expected_rewritten_query=*/"rewritten 1: a nice cake"),
+          EqLobsterImageCandidate(
+              /*expected_id=*/1,
+              /*expected_bitmap=*/
+              CreateTestBitmap(kPreviewImageDimensionSize,
+                               kPreviewImageDimensionSize),
+              /*expected_generation_seed=*/11,
+              /*expected_user_query=*/"a lovely cake",
+              /*expected_rewritten_query=*/"rewritten 2: a beautiful cake")));
 }
 
 TEST_F(LobsterImageProviderFromSnapperTest,
@@ -134,7 +137,8 @@
           /*expected_bitmap=*/
           CreateTestBitmap(kFullImageDimensionSize, kFullImageDimensionSize),
           /*expected_generation_seed=*/kFakeBaseGenerationSeed,
-          /*expected_query=*/"rewritten 1: a nice cake")));
+          /*expected_rewritten_query=*/"a lovely cake",
+          /*expected_rewritten_query=*/"rewritten 1: a nice cake")));
 }
 
 TEST_F(
diff --git a/chrome/browser/ash/lobster/lobster_test_utils.cc b/chrome/browser/ash/lobster/lobster_test_utils.cc
index 076ae86..53f3a2d 100644
--- a/chrome/browser/ash/lobster/lobster_test_utils.cc
+++ b/chrome/browser/ash/lobster/lobster_test_utils.cc
@@ -97,12 +97,16 @@
     int expected_id,
     const SkBitmap& expected_bitmap,
     uint32_t expected_generation_seed,
-    std::string_view expected_query) {
+    std::string_view expected_user_query,
+    std::string_view expected_rewritten_query) {
   return testing::AllOf(
       testing::Field(&ash::LobsterImageCandidate::id, expected_id),
       testing::Field(&ash::LobsterImageCandidate::image_bytes,
                      AreJpgBytesClose(expected_bitmap)),
       testing::Field(&ash::LobsterImageCandidate::seed,
                      expected_generation_seed),
-      testing::Field(&ash::LobsterImageCandidate::query, expected_query));
+      testing::Field(&ash::LobsterImageCandidate::user_query,
+                     expected_user_query),
+      testing::Field(&ash::LobsterImageCandidate::rewritten_query,
+                     expected_rewritten_query));
 }
diff --git a/chrome/browser/ash/lobster/lobster_test_utils.h b/chrome/browser/ash/lobster/lobster_test_utils.h
index e688c87..c3bc099 100644
--- a/chrome/browser/ash/lobster/lobster_test_utils.h
+++ b/chrome/browser/ash/lobster/lobster_test_utils.h
@@ -30,6 +30,7 @@
     int expected_id,
     const SkBitmap& expected_bitmap,
     uint32_t expected_generation_seed,
-    std::string_view expected_query);
+    std::string_view expected_user_query,
+    std::string_view expected_rewritten_query);
 
 #endif  // CHROME_BROWSER_ASH_LOBSTER_LOBSTER_TEST_UTILS_H_
diff --git a/chrome/browser/ash/login/oobe_quick_start/connectivity/account_transfer_client_data.cc b/chrome/browser/ash/login/oobe_quick_start/connectivity/account_transfer_client_data.cc
index 9b54043..59e4e49 100644
--- a/chrome/browser/ash/login/oobe_quick_start/connectivity/account_transfer_client_data.cc
+++ b/chrome/browser/ash/login/oobe_quick_start/connectivity/account_transfer_client_data.cc
@@ -34,14 +34,9 @@
   return fido_client_data_json;
 }
 
-std::array<uint8_t, crypto::kSHA256Length>
+std::array<uint8_t, crypto::hash::kSha256Size>
 AccountTransferClientData::CreateHash() {
-  std::string json = CreateJson();
-  std::array<uint8_t, crypto::kSHA256Length> client_data_hash;
-  crypto::SHA256HashString(json, client_data_hash.data(),
-                           client_data_hash.size());
-
-  return client_data_hash;
+  return crypto::hash::Sha256(base::as_byte_span(CreateJson()));
 }
 
 }  // namespace ash::quick_start
diff --git a/chrome/browser/ash/login/oobe_quick_start/connectivity/account_transfer_client_data.h b/chrome/browser/ash/login/oobe_quick_start/connectivity/account_transfer_client_data.h
index 9c32a22..3143610 100644
--- a/chrome/browser/ash/login/oobe_quick_start/connectivity/account_transfer_client_data.h
+++ b/chrome/browser/ash/login/oobe_quick_start/connectivity/account_transfer_client_data.h
@@ -8,7 +8,7 @@
 #include <string>
 
 #include "chromeos/ash/components/quick_start/types.h"
-#include "crypto/sha2.h"
+#include "crypto/hash.h"
 #include "url/origin.h"
 
 namespace ash::quick_start {
@@ -30,7 +30,7 @@
   ~AccountTransferClientData();
 
   std::string CreateJson();
-  std::array<uint8_t, crypto::kSHA256Length> CreateHash();
+  std::array<uint8_t, crypto::hash::kSha256Size> CreateHash();
 
   Base64UrlString GetChallengeBase64URLString();
 
diff --git a/chrome/browser/ash/login/oobe_quick_start/connectivity/account_transfer_client_data_unittest.cc b/chrome/browser/ash/login/oobe_quick_start/connectivity/account_transfer_client_data_unittest.cc
index b20826c..7781ff42 100644
--- a/chrome/browser/ash/login/oobe_quick_start/connectivity/account_transfer_client_data_unittest.cc
+++ b/chrome/browser/ash/login/oobe_quick_start/connectivity/account_transfer_client_data_unittest.cc
@@ -49,15 +49,9 @@
 TEST_F(AccountTransferClientDataTest, CreateHash) {
   AccountTransferClientData data(challenge_b64url_);
 
-  std::string client_data_json = data.CreateJson();
-
-  std::string json = data.CreateJson();
-  std::array<uint8_t, crypto::kSHA256Length> expected_hash;
-  crypto::SHA256HashString(json, expected_hash.data(), expected_hash.size());
-
-  std::array<uint8_t, crypto::kSHA256Length> result = data.CreateHash();
-
-  EXPECT_EQ(expected_hash, result);
+  auto expected_hash =
+      crypto::hash::Sha256(base::as_byte_span(data.CreateJson()));
+  EXPECT_EQ(expected_hash, data.CreateHash());
 }
 
 }  // namespace ash::quick_start
diff --git a/chrome/browser/ash/policy/core/BUILD.gn b/chrome/browser/ash/policy/core/BUILD.gn
index 0dd03ca9..d9eac2b 100644
--- a/chrome/browser/ash/policy/core/BUILD.gn
+++ b/chrome/browser/ash/policy/core/BUILD.gn
@@ -211,6 +211,7 @@
     "device_cloud_policy_store_ash_unittest.cc",
     "device_local_account_external_cache_unittest.cc",
     "device_local_account_policy_service_unittest.cc",
+    "device_local_account_policy_store_unittest.cc",
     "device_local_account_unittest.cc",
     "device_policy_decoder_unittest.cc",
     "reporting_user_tracker_unittest.cc",
@@ -221,6 +222,7 @@
 
   deps = [
     ":core",
+    ":test_support",
     "//ash/constants",
     "//base",
     "//base/test:test_support",
diff --git a/chrome/browser/ash/policy/core/browser_policy_connector_ash.cc b/chrome/browser/ash/policy/core/browser_policy_connector_ash.cc
index 842b6a9b..988ecfcc 100644
--- a/chrome/browser/ash/policy/core/browser_policy_connector_ash.cc
+++ b/chrome/browser/ash/policy/core/browser_policy_connector_ash.cc
@@ -190,6 +190,12 @@
   return invalidation_service_provider_or_listener_per_project;
 }
 
+scoped_refptr<base::SequencedTaskRunner> CreateUserVisibleTaskRunner() {
+  return base::ThreadPool::CreateUpdateableSequencedTaskRunner(
+      {base::MayBlock(), base::TaskPriority::USER_VISIBLE,
+       base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN});
+}
+
 }  // namespace
 
 // static
@@ -279,7 +285,10 @@
           invalidation::UniquePointerVariantToPointer(
               invalidation_service_provider_or_listener_per_project_
                   [device_local_account_policy_project_number]),
-          CreateBackgroundTaskRunner(), CreateBackgroundTaskRunner(),
+          /*store_background_task_runner=*/CreateBackgroundTaskRunner(),
+          /*store_first_load_task_runner=*/CreateUserVisibleTaskRunner(),
+          /*extension_cache_task_runner=*/CreateBackgroundTaskRunner(),
+          /*external_data_service_backend_task_runner=*/
           CreateBackgroundTaskRunner(), url_loader_factory);
   device_local_account_policy_service_->Connect(device_management_service());
 
diff --git a/chrome/browser/ash/policy/core/device_local_account_policy_service.cc b/chrome/browser/ash/policy/core/device_local_account_policy_service.cc
index a38b7b94..e543bed 100644
--- a/chrome/browser/ash/policy/core/device_local_account_policy_service.cc
+++ b/chrome/browser/ash/policy/core/device_local_account_policy_service.cc
@@ -90,6 +90,7 @@
                  invalidation::InvalidationListener*>
         invalidation_service_provider_or_listener,
     scoped_refptr<base::SequencedTaskRunner> store_background_task_runner,
+    scoped_refptr<base::SequencedTaskRunner> store_first_load_task_runner,
     scoped_refptr<base::SequencedTaskRunner> extension_cache_task_runner,
     scoped_refptr<base::SequencedTaskRunner>
         external_data_service_backend_task_runner,
@@ -104,6 +105,7 @@
       waiting_for_cros_settings_(false),
       orphan_extension_cache_deletion_state_(NOT_STARTED),
       store_background_task_runner_(store_background_task_runner),
+      store_first_load_task_runner_(store_first_load_task_runner),
       extension_cache_task_runner_(extension_cache_task_runner),
       resource_cache_task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
           {base::MayBlock(), base::TaskPriority::BEST_EFFORT})),
@@ -298,7 +300,8 @@
     } else {
       auto store = std::make_unique<DeviceLocalAccountPolicyStore>(
           device_local_account.account_id, session_manager_client_,
-          device_settings_service_, store_background_task_runner_);
+          device_settings_service_, store_background_task_runner_,
+          store_first_load_task_runner_);
       scoped_refptr<DeviceLocalAccountExternalDataManager>
           external_data_manager =
               external_data_service_->GetExternalDataManager(
diff --git a/chrome/browser/ash/policy/core/device_local_account_policy_service.h b/chrome/browser/ash/policy/core/device_local_account_policy_service.h
index ee82377..ce3b1eb 100644
--- a/chrome/browser/ash/policy/core/device_local_account_policy_service.h
+++ b/chrome/browser/ash/policy/core/device_local_account_policy_service.h
@@ -73,6 +73,7 @@
                    invalidation::InvalidationListener*>
           invalidation_service_provider_or_listener,
       scoped_refptr<base::SequencedTaskRunner> store_background_task_runner,
+      scoped_refptr<base::SequencedTaskRunner> store_first_load_task_runner,
       scoped_refptr<base::SequencedTaskRunner> extension_cache_task_runner,
       scoped_refptr<base::SequencedTaskRunner>
           external_data_service_backend_task_runner,
@@ -178,6 +179,7 @@
   std::set<std::string> busy_extension_cache_directories_;
 
   const scoped_refptr<base::SequencedTaskRunner> store_background_task_runner_;
+  const scoped_refptr<base::SequencedTaskRunner> store_first_load_task_runner_;
   const scoped_refptr<base::SequencedTaskRunner> extension_cache_task_runner_;
   const scoped_refptr<base::SequencedTaskRunner> resource_cache_task_runner_;
 
diff --git a/chrome/browser/ash/policy/core/device_local_account_policy_service_unittest.cc b/chrome/browser/ash/policy/core/device_local_account_policy_service_unittest.cc
index 24d12ae..6337572 100644
--- a/chrome/browser/ash/policy/core/device_local_account_policy_service_unittest.cc
+++ b/chrome/browser/ash/policy/core/device_local_account_policy_service_unittest.cc
@@ -259,6 +259,7 @@
       &session_manager_client_, device_settings_service_.get(),
       ash::CrosSettings::Get(), GetInvalidationServiceProviderOrListener(),
       base::SingleThreadTaskRunner::GetCurrentDefault(),
+      base::SingleThreadTaskRunner::GetCurrentDefault(),
       extension_cache_task_runner_,
       base::SingleThreadTaskRunner::GetCurrentDefault(),
       /*url_loader_factory=*/nullptr);
diff --git a/chrome/browser/ash/policy/core/device_local_account_policy_store.cc b/chrome/browser/ash/policy/core/device_local_account_policy_store.cc
index 12a2ca31..470238b 100644
--- a/chrome/browser/ash/policy/core/device_local_account_policy_store.cc
+++ b/chrome/browser/ash/policy/core/device_local_account_policy_store.cc
@@ -32,9 +32,11 @@
     const std::string& account_id,
     ash::SessionManagerClient* session_manager_client,
     ash::DeviceSettingsService* device_settings_service,
-    scoped_refptr<base::SequencedTaskRunner> background_task_runner)
+    scoped_refptr<base::SequencedTaskRunner> background_task_runner,
+    scoped_refptr<base::SequencedTaskRunner> first_load_task_runner)
     : UserCloudPolicyStoreBase(background_task_runner,
                                PolicyScope::POLICY_SCOPE_USER),
+      first_load_task_runner_(first_load_task_runner),
       account_id_(account_id),
       session_manager_client_(session_manager_client),
       device_settings_service_(device_settings_service) {}
@@ -245,7 +247,7 @@
   }
 
   auto validator = std::make_unique<UserCloudPolicyValidator>(
-      std::move(policy_response), background_task_runner());
+      std::move(policy_response), GetValidationTaskRunner());
   validator->ValidateUsername(account_id_);
   validator->ValidatePolicyType(dm_protocol::kChromePublicAccountPolicyType);
   // The timestamp is verified when storing a new policy downloaded from the
@@ -284,4 +286,16 @@
   }
 }
 
+scoped_refptr<base::SequencedTaskRunner>
+DeviceLocalAccountPolicyStore::GetValidationTaskRunner() const {
+  // Before the store has policies, this returns the high priority
+  // `first_load_task_runner_`. Once the store gets policies, this returns the
+  // low priority `background_task_runner()`.
+  //
+  // This is necessary because MGS launch blocks on device local account policy
+  // load, so policy tasks should run in a high USER_VISIBLE priority runner.
+  // See crbug.com/263949579.
+  return has_policy() ? background_task_runner() : first_load_task_runner_;
+}
+
 }  // namespace policy
diff --git a/chrome/browser/ash/policy/core/device_local_account_policy_store.h b/chrome/browser/ash/policy/core/device_local_account_policy_store.h
index e3a4a27..9032fb1 100644
--- a/chrome/browser/ash/policy/core/device_local_account_policy_store.h
+++ b/chrome/browser/ash/policy/core/device_local_account_policy_store.h
@@ -35,7 +35,8 @@
       const std::string& account_id,
       ash::SessionManagerClient* client,
       ash::DeviceSettingsService* device_settings_service,
-      scoped_refptr<base::SequencedTaskRunner> background_task_runner);
+      scoped_refptr<base::SequencedTaskRunner> background_task_runner,
+      scoped_refptr<base::SequencedTaskRunner> first_load_task_runner);
 
   DeviceLocalAccountPolicyStore(const DeviceLocalAccountPolicyStore&) = delete;
   DeviceLocalAccountPolicyStore& operator=(
@@ -102,6 +103,11 @@
       bool validate_in_background,
       ash::DeviceSettingsService::OwnershipStatus ownership_status);
 
+  scoped_refptr<base::SequencedTaskRunner> GetValidationTaskRunner() const;
+
+  // Hish priority task runner to be used for the first policy load.
+  scoped_refptr<base::SequencedTaskRunner> first_load_task_runner_;
+
   const std::string account_id_;
   raw_ptr<ash::SessionManagerClient> session_manager_client_;
   raw_ptr<ash::DeviceSettingsService> device_settings_service_;
diff --git a/chrome/browser/ash/policy/core/device_local_account_policy_store_unittest.cc b/chrome/browser/ash/policy/core/device_local_account_policy_store_unittest.cc
new file mode 100644
index 0000000..35d70fc
--- /dev/null
+++ b/chrome/browser/ash/policy/core/device_local_account_policy_store_unittest.cc
@@ -0,0 +1,187 @@
+// 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/ash/policy/core/device_local_account_policy_store.h"
+
+#include <cstddef>
+#include <memory>
+
+#include "base/check_deref.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/test/task_environment.h"
+#include "base/test/test_simple_task_runner.h"
+#include "chrome/browser/ash/ownership/owner_settings_service_ash_factory.h"
+#include "chrome/browser/ash/policy/core/device_policy_builder.h"
+#include "chrome/browser/ash/settings/device_settings_service.h"
+#include "chrome_device_policy.pb.h"
+#include "chromeos/ash/components/dbus/session_manager/fake_session_manager_client.h"
+#include "chromeos/ash/components/dbus/session_manager/session_manager_client.h"
+#include "chromeos/ash/components/install_attributes/stub_install_attributes.h"
+#include "components/ownership/mock_owner_key_util.h"
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
+#include "components/policy/core/common/cloud/test/policy_builder.h"
+#include "components/policy/proto/device_management_backend.pb.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace em = enterprise_management;
+
+namespace policy {
+
+namespace {
+
+constexpr char kTestAccountId[] = "test_public_account_id";
+
+// Creates a valid `UserPolicyBuilder` configured for device local accounts.
+std::unique_ptr<policy::UserPolicyBuilder> CreateDeviceLocalAccountPolicy() {
+  auto device_local_account_policy =
+      std::make_unique<policy::UserPolicyBuilder>();
+  device_local_account_policy->policy_data().set_policy_type(
+      dm_protocol::kChromePublicAccountPolicyType);
+  device_local_account_policy->policy_data().set_settings_entity_id(
+      kTestAccountId);
+  device_local_account_policy->policy_data().set_username(kTestAccountId);
+  device_local_account_policy->Build();
+  return device_local_account_policy;
+}
+
+// Creates an invalid `UserPolicyBuilder`, such that policy validation for
+// device local accounts should fail.
+std::unique_ptr<policy::UserPolicyBuilder>
+CreateInvalidDeviceLocalAccountPolicy() {
+  auto policy = CreateDeviceLocalAccountPolicy();
+  policy->policy_data().set_policy_type(
+      dm_protocol::kChromeRemoteCommandPolicyType);
+  policy->Build();
+  return policy;
+}
+
+// Installs `device_local_account_policy` and `device_policy` into
+// `session_manager`.
+void InstallPolicies(base::test::TaskEnvironment& environment,
+                     ash::FakeSessionManagerClient& session_manager,
+                     DevicePolicyBuilder& device_policy,
+                     UserPolicyBuilder& device_local_account_policy) {
+  session_manager.set_device_local_account_policy(
+      kTestAccountId, device_local_account_policy.GetBlob());
+
+  em::DeviceLocalAccountInfoProto* account =
+      device_policy.payload().mutable_device_local_accounts()->add_account();
+  account->set_account_id(kTestAccountId);
+  account->set_type(
+      em::DeviceLocalAccountInfoProto::ACCOUNT_TYPE_PUBLIC_SESSION);
+
+  device_policy.Build();
+  session_manager.set_device_policy(device_policy.GetBlob());
+  ash::DeviceSettingsService::Get()->OwnerKeySet(true);
+  environment.RunUntilIdle();
+}
+
+// Runs `runner` until idle considering `environment` may post tasks back to it.
+void RunUntilIdle(base::test::TaskEnvironment& environment,
+                  scoped_refptr<base::TestSimpleTaskRunner> runner) {
+  while (runner->HasPendingTask()) {
+    runner->RunUntilIdle();
+    environment.RunUntilIdle();
+  }
+}
+
+DeviceLocalAccountPolicyStore CreatePolicyStore(
+    scoped_refptr<base::TestSimpleTaskRunner> background_runner,
+    scoped_refptr<base::TestSimpleTaskRunner> first_load_runner) {
+  return DeviceLocalAccountPolicyStore(
+      kTestAccountId, ash::SessionManagerClient::Get(),
+      ash::DeviceSettingsService::Get(), background_runner, first_load_runner);
+}
+
+}  // namespace
+
+class DeviceLocalAccountPolicyStoreTest : public testing::Test {
+ public:
+  DeviceLocalAccountPolicyStoreTest() {
+    owner_key_util_->SetPublicKeyFromPrivateKey(
+        *device_policy_.GetSigningKey());
+    owner_key_util_->ImportPrivateKeyAndSetPublicKey(
+        device_policy_.GetSigningKey());
+    ash::OwnerSettingsServiceAshFactory::GetInstance()
+        ->SetOwnerKeyUtilForTesting(owner_key_util_);
+    ash::DeviceSettingsService::Get()->SetSessionManager(
+        &fake_session_manager_client_, owner_key_util_);
+  }
+  DeviceLocalAccountPolicyStoreTest(const DeviceLocalAccountPolicyStoreTest&) =
+      delete;
+  DeviceLocalAccountPolicyStoreTest& operator=(
+      const DeviceLocalAccountPolicyStoreTest&) = delete;
+  ~DeviceLocalAccountPolicyStoreTest() override = default;
+
+  policy::DevicePolicyBuilder device_policy_;
+  ash::FakeSessionManagerClient fake_session_manager_client_;
+  base::test::TaskEnvironment environment_;
+
+ private:
+  ash::ScopedStubInstallAttributes install_attributes_;
+  ash::ScopedTestDeviceSettingsService scoped_test_device_settings_;
+  scoped_refptr<ownership::MockOwnerKeyUtil> owner_key_util_{
+      base::MakeRefCounted<ownership::MockOwnerKeyUtil>()};
+};
+
+TEST_F(DeviceLocalAccountPolicyStoreTest,
+       UsesFirstLoadRunnerWhenThereAreNoPolicies) {
+  auto background_runner = base::MakeRefCounted<base::TestSimpleTaskRunner>();
+  auto first_load_runner = base::MakeRefCounted<base::TestSimpleTaskRunner>();
+  auto policy_store = CreatePolicyStore(background_runner, first_load_runner);
+
+  auto bad_policy = CreateInvalidDeviceLocalAccountPolicy();
+
+  InstallPolicies(environment_, fake_session_manager_client_, device_policy_,
+                  *bad_policy);
+
+  ASSERT_FALSE(policy_store.has_policy());
+
+  // Call `Store()` multiple times with the bad policy, and verify
+  // `first_load_runner` is used every time.
+  for (size_t i = 0; i < 5; i++) {
+    policy_store.Store(bad_policy->policy());
+    environment_.RunUntilIdle();
+
+    ASSERT_FALSE(background_runner->HasPendingTask());
+    ASSERT_TRUE(first_load_runner->HasPendingTask());
+    RunUntilIdle(environment_, first_load_runner);
+
+    ASSERT_FALSE(policy_store.has_policy());
+  }
+}
+
+TEST_F(DeviceLocalAccountPolicyStoreTest,
+       UsesBackgroundRunnerWhenThereArePolicies) {
+  auto background_runner = base::MakeRefCounted<base::TestSimpleTaskRunner>();
+  auto first_load_runner = base::MakeRefCounted<base::TestSimpleTaskRunner>();
+  auto policy_store = CreatePolicyStore(background_runner, first_load_runner);
+
+  auto policy = CreateDeviceLocalAccountPolicy();
+
+  InstallPolicies(environment_, fake_session_manager_client_, device_policy_,
+                  *policy);
+
+  // First `Store()` uses `first_load_runner`.
+  policy_store.Store(policy->policy());
+  environment_.RunUntilIdle();
+
+  ASSERT_FALSE(background_runner->HasPendingTask());
+  ASSERT_TRUE(first_load_runner->HasPendingTask());
+  RunUntilIdle(environment_, first_load_runner);
+
+  // Now `policy_store` has policies, verify several following calls use
+  // `background_runner`.
+  ASSERT_TRUE(policy_store.has_policy());
+  for (size_t i = 0; i < 5; i++) {
+    policy_store.Store(policy->policy());
+    environment_.RunUntilIdle();
+
+    ASSERT_TRUE(background_runner->HasPendingTask());
+    ASSERT_FALSE(first_load_runner->HasPendingTask());
+    RunUntilIdle(environment_, background_runner);
+  }
+}
+
+}  // namespace policy
diff --git a/chrome/browser/ash/policy/external_data/cloud_external_data_policy_observer_unittest.cc b/chrome/browser/ash/policy/external_data/cloud_external_data_policy_observer_unittest.cc
index 20bcf31..1316168a 100644
--- a/chrome/browser/ash/policy/external_data/cloud_external_data_policy_observer_unittest.cc
+++ b/chrome/browser/ash/policy/external_data/cloud_external_data_policy_observer_unittest.cc
@@ -237,6 +237,7 @@
           base::SingleThreadTaskRunner::GetCurrentDefault(),
           base::SingleThreadTaskRunner::GetCurrentDefault(),
           base::SingleThreadTaskRunner::GetCurrentDefault(),
+          base::SingleThreadTaskRunner::GetCurrentDefault(),
           shared_url_loader_factory_);
 
   user_policy_provider_.SetDefaultReturns(
diff --git a/chrome/browser/ash/policy/test_support/embedded_policy_test_server_mixin.cc b/chrome/browser/ash/policy/test_support/embedded_policy_test_server_mixin.cc
index 04861203..b7fa3470 100644
--- a/chrome/browser/ash/policy/test_support/embedded_policy_test_server_mixin.cc
+++ b/chrome/browser/ash/policy/test_support/embedded_policy_test_server_mixin.cc
@@ -209,6 +209,13 @@
   policy_test_server_->policy_storage()->set_metrics_log_segment(segment);
 }
 
+void EmbeddedPolicyTestServerMixin::SetK12AgeClassificationMetricsLogSegment(
+    enterprise_management::PolicyData::K12AgeClassificationMetricsLogSegment
+        segment) {
+  policy_test_server_->policy_storage()
+      ->set_k12_age_classification_metrics_log_segment(segment);
+}
+
 void EmbeddedPolicyTestServerMixin::SetExpectedPsmParamsInDeviceRegisterRequest(
     const std::string& device_brand_code,
     const std::string& device_serial_number,
diff --git a/chrome/browser/ash/policy/test_support/embedded_policy_test_server_mixin.h b/chrome/browser/ash/policy/test_support/embedded_policy_test_server_mixin.h
index acd4a442..80481b9 100644
--- a/chrome/browser/ash/policy/test_support/embedded_policy_test_server_mixin.h
+++ b/chrome/browser/ash/policy/test_support/embedded_policy_test_server_mixin.h
@@ -128,6 +128,11 @@
   // metadata.
   void SetMetricsLogSegment(
       enterprise_management::PolicyData::MetricsLogSegment segment);
+  // Sets age classification metric segment for the K12 user, this information
+  // is provided via policy metadata.
+  void SetK12AgeClassificationMetricsLogSegment(
+      enterprise_management::PolicyData::K12AgeClassificationMetricsLogSegment
+          segment);
 
   // Configures server to expect these PSM (private set membership) execution
   // values (i.e. `psm_execution_result` and `psm_determination_timestamp`) as
diff --git a/chrome/browser/ash/wallpaper_handlers/sea_pen_fetcher_unittest.cc b/chrome/browser/ash/wallpaper_handlers/sea_pen_fetcher_unittest.cc
index f3344e51..ac69aec 100644
--- a/chrome/browser/ash/wallpaper_handlers/sea_pen_fetcher_unittest.cc
+++ b/chrome/browser/ash/wallpaper_handlers/sea_pen_fetcher_unittest.cc
@@ -1062,7 +1062,9 @@
           manta::features::kMantaService,
           ash::features::kSeaPenTextInput,
       },
-      {});
+      {
+          ash::features::kSeaPenQueryRewrite,
+      });
   auto user_query = MakeFreeformQuery();
   std::string generative_prompt = "prompt used to generate images";
   ash::personalization_app::mojom::SeaPenQueryPtr generative_prompt_query =
diff --git a/chrome/browser/ash/wallpaper_handlers/sea_pen_utils.cc b/chrome/browser/ash/wallpaper_handlers/sea_pen_utils.cc
index 60a41d2d..2349dbaf 100644
--- a/chrome/browser/ash/wallpaper_handlers/sea_pen_utils.cc
+++ b/chrome/browser/ash/wallpaper_handlers/sea_pen_utils.cc
@@ -114,6 +114,12 @@
     rewrite_input_data.set_tag("use_query_rewrite");
     rewrite_input_data.set_text("true");
   }
+  if (query->is_text_query() &&
+      ash::features::IsSeaPenTextInputTranslationEnabled()) {
+    manta::proto::InputData& translation_input_data = *request.add_input_data();
+    translation_input_data.set_tag("use_i18n");
+    translation_input_data.set_text("true");
+  }
   return request;
 }
 
diff --git a/chrome/browser/background/glic/glic_background_mode_manager_interactive_uitest.cc b/chrome/browser/background/glic/glic_background_mode_manager_interactive_uitest.cc
index 37709cf..c64db845 100644
--- a/chrome/browser/background/glic/glic_background_mode_manager_interactive_uitest.cc
+++ b/chrome/browser/background/glic/glic_background_mode_manager_interactive_uitest.cc
@@ -208,8 +208,11 @@
 }
 #endif
 
-// Test that hotkey is logged when pressed.
-IN_PROC_BROWSER_TEST_F(GlicBackgroundModeManagerUiTest, HotkeyPressed) {
+// TODO (crbug.com/406528268): Delete or fix tests that are disabled because
+// kGlicAlwaysDetached is now default true. Test that hotkey is logged when
+// pressed.
+IN_PROC_BROWSER_TEST_F(GlicBackgroundModeManagerUiTest,
+                       DISABLED_HotkeyPressed) {
   if (!IsHotkeySupported()) {
     GTEST_SKIP() << "Test does not apply to this platform.";
   }
diff --git a/chrome/browser/bookmarks/android/BUILD.gn b/chrome/browser/bookmarks/android/BUILD.gn
index 524b18c..11300fa 100644
--- a/chrome/browser/bookmarks/android/BUILD.gn
+++ b/chrome/browser/bookmarks/android/BUILD.gn
@@ -58,6 +58,8 @@
     "java/src/org/chromium/chrome/browser/bookmarks/BookmarkSearchBoxRow.java",
     "java/src/org/chromium/chrome/browser/bookmarks/BookmarkTextInputLayout.java",
     "java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbar.java",
+    "java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarCoordinator.java",
+    "java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarMediator.java",
     "java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarProperties.java",
     "java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarViewBinder.java",
     "java/src/org/chromium/chrome/browser/bookmarks/BookmarkUiObserver.java",
diff --git a/chrome/browser/bookmarks/android/unmodularized_bookmarks_java_files.gni b/chrome/browser/bookmarks/android/unmodularized_bookmarks_java_files.gni
index c68dde6..41daf61 100644
--- a/chrome/browser/bookmarks/android/unmodularized_bookmarks_java_files.gni
+++ b/chrome/browser/bookmarks/android/unmodularized_bookmarks_java_files.gni
@@ -10,8 +10,6 @@
   "//chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/AddToBookmarksToolbarButtonController.java",
   "//chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkManagerCoordinator.java",
   "//chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkManagerMediator.java",
-  "//chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarMediator.java",
-  "//chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarCoordinator.java",
   "//chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkPage.java",
   "//chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkPane.java",
   "//chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkPromoHeader.java",
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index d26951e9..5b8b1bf 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -741,7 +741,6 @@
 
 #if BUILDFLAG(SAFE_BROWSING_AVAILABLE)
 #include "chrome/browser/enterprise/connectors/connectors_service.h"
-#include "chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service.h"
 #include "chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service_factory.h"
 #include "chrome/browser/safe_browsing/chrome_password_protection_service.h"
 #include "chrome/browser/safe_browsing/chrome_ping_manager_factory.h"
@@ -750,6 +749,7 @@
 #include "chrome/browser/safe_browsing/safe_browsing_service.h"
 #include "chrome/browser/safe_browsing/url_lookup_service_factory.h"
 #include "components/safe_browsing/content/browser/safe_browsing_navigation_throttle.h"
+#include "components/safe_browsing/core/browser/realtime/chrome_enterprise_url_lookup_service.h"
 #endif
 
 #if BUILDFLAG(ENABLE_OFFLINE_PAGES)
@@ -3776,6 +3776,11 @@
 }
 
 std::string ChromeContentBrowserClient::GetGeolocationApiKey() {
+#if BUILDFLAG(IS_CHROMEOS)
+  if (ash::features::IsCrosSeparateGeoApiKeyEnabled()) {
+    return google_apis::GetCrosChromeGeoAPIKey();
+  }
+#endif
   return google_apis::GetAPIKey();
 }
 
diff --git a/chrome/browser/chrome_content_browser_client_unittest.cc b/chrome/browser/chrome_content_browser_client_unittest.cc
index 44341f3..5304141 100644
--- a/chrome/browser/chrome_content_browser_client_unittest.cc
+++ b/chrome/browser/chrome_content_browser_client_unittest.cc
@@ -147,6 +147,9 @@
 #include "chromeos/components/kiosk/kiosk_utils.h"
 #include "components/user_manager/scoped_user_manager.h"
 #include "content/public/test/scoped_web_ui_controller_factory_registration.h"
+#include "google_apis/api_key_cache.h"
+#include "google_apis/default_api_keys.h"
+#include "google_apis/google_api_keys.h"
 #endif  // BUILDFLAG(IS_CHROMEOS)
 
 #if BUILDFLAG(IS_WIN)
@@ -1378,6 +1381,50 @@
                             continuation_callback.GetCallback());
   EXPECT_FALSE(continuation_callback.Take().is_allowed());
 }
+
+namespace override_geo_api_keys {
+
+// We start every test by creating a clean environment for the
+// preprocessor defines used in define_baked_in_api_keys-inc.cc
+#undef GOOGLE_API_KEY
+#undef GOOGLE_API_KEY_CROS_SYSTEM_GEO
+#undef GOOGLE_API_KEY_CROS_CHROME_GEO
+
+// Set Geolocation-specific keys.
+#define GOOGLE_API_KEY "bogus_api_key"
+#define GOOGLE_API_KEY_CROS_SYSTEM_GEO "bogus_cros_system_geo_api_key"
+#define GOOGLE_API_KEY_CROS_CHROME_GEO "bogus_cros_chrome_geo_api_key"
+
+// This file must be included after the internal files defining official keys.
+#include "google_apis/default_api_keys-inc.cc"
+
+}  // namespace override_geo_api_keys
+
+// Test that when `kCrosSeparateGeoApiKey` feature is enabled,
+// Chrome-on-ChromeOS switches to using a separate (ChromeOS-specific) API Key
+// for the location requests.
+TEST_F(ChromeContentBrowserClientTest, UseCorrectGeoAPIKey) {
+  auto default_key_values =
+      override_geo_api_keys::GetDefaultApiKeysFromDefinedValues();
+  default_key_values.allow_unset_values = true;
+  google_apis::ApiKeyCache api_key_cache(default_key_values);
+  auto scoped_override =
+      google_apis::SetScopedApiKeyCacheForTesting(&api_key_cache);
+
+  // Check that by default Chrome-on-ChromeOS uses shared API key for
+  // geolocation requests.
+  ChromeContentBrowserClient client;
+  EXPECT_EQ(client.GetGeolocationApiKey(), google_apis::GetAPIKey());
+
+  // Check that when the `kCrosSeparateGeoApiKey` feature is enabled,
+  // Chrome-on-ChromeOS uses ChromeOS-specific API key for geolocation.
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndEnableFeature(
+      ash::features::kCrosSeparateGeoApiKey);
+  EXPECT_EQ(client.GetGeolocationApiKey(),
+            google_apis::GetCrosChromeGeoAPIKey());
+}
+
 #endif  // BUILDFLAG(IS_CHROMEOS)
 
 class ChromeContentBrowserClientSwitchTest
diff --git a/chrome/browser/chromeos/app_mode/startup_app_launcher_update_checker.cc b/chrome/browser/chromeos/app_mode/startup_app_launcher_update_checker.cc
index 550719d..cda51b4 100644
--- a/chrome/browser/chromeos/app_mode/startup_app_launcher_update_checker.cc
+++ b/chrome/browser/chromeos/app_mode/startup_app_launcher_update_checker.cc
@@ -9,10 +9,8 @@
 #include "base/functional/bind.h"
 #include "base/syslog_logging.h"
 #include "base/version.h"
-#include "chrome/browser/extensions/extension_service.h"
 #include "chrome/browser/extensions/updater/extension_updater.h"
 #include "chrome/browser/profiles/profile.h"
-#include "extensions/browser/extension_system.h"
 #include "extensions/browser/updater/extension_downloader.h"
 
 namespace chromeos {
@@ -29,11 +27,8 @@
     return false;
   }
 
-  extensions::ExtensionUpdater* updater =
-      extensions::ExtensionSystem::Get(profile_)
-          ->extension_service()
-          ->updater();
-  if (!updater) {
+  auto* updater = extensions::ExtensionUpdater::Get(profile_);
+  if (!updater->enabled()) {
     return false;
   }
 
diff --git a/chrome/browser/collaboration/android/java/src/org/chromium/chrome/browser/collaboration/CollaborationIntegrationTest.java b/chrome/browser/collaboration/android/java/src/org/chromium/chrome/browser/collaboration/CollaborationIntegrationTest.java
index 7588880..5ece586a 100644
--- a/chrome/browser/collaboration/android/java/src/org/chromium/chrome/browser/collaboration/CollaborationIntegrationTest.java
+++ b/chrome/browser/collaboration/android/java/src/org/chromium/chrome/browser/collaboration/CollaborationIntegrationTest.java
@@ -6,6 +6,7 @@
 
 import static androidx.test.espresso.Espresso.onView;
 import static androidx.test.espresso.action.ViewActions.click;
+import static androidx.test.espresso.action.ViewActions.scrollTo;
 import static androidx.test.espresso.assertion.ViewAssertions.matches;
 import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
 import static androidx.test.espresso.matcher.ViewMatchers.withId;
@@ -88,7 +89,7 @@
         // Verify that the fullscreen sign-in promo is shown and cancel.
         onViewWaiting(withText(R.string.collaboration_signin_description))
                 .check(matches(isDisplayed()));
-        onView(withText(R.string.collaboration_cancel)).perform(click());
+        onView(withText(R.string.collaboration_cancel)).perform(scrollTo(), click());
 
         // The new data sharing url was intercepted and the tab closed.
         Assert.assertEquals(1, mActivityTestRule.tabsCount(/* incognito= */ false));
@@ -103,7 +104,7 @@
         // Verify that the fullscreen sign-in promo is shown and cancel.
         onViewWaiting(withText(R.string.collaboration_signin_description))
                 .check(matches(isDisplayed()));
-        onView(withText(R.string.collaboration_cancel)).perform(click());
+        onView(withText(R.string.collaboration_cancel)).perform(scrollTo(), click());
 
         // The new data sharing url was intercepted and the tab closed.
         Assert.assertEquals(1, mActivityTestRule.tabsCount(/* incognito= */ false));
diff --git a/chrome/browser/enterprise/data_protection/data_protection_navigation_observer.cc b/chrome/browser/enterprise/data_protection/data_protection_navigation_observer.cc
index 5f37192..2b1c3c8 100644
--- a/chrome/browser/enterprise/data_protection/data_protection_navigation_observer.cc
+++ b/chrome/browser/enterprise/data_protection/data_protection_navigation_observer.cc
@@ -14,9 +14,9 @@
 #include "chrome/browser/enterprise/data_controls/chrome_rules_service.h"
 #include "chrome/browser/interstitials/enterprise_util.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service.h"
 #include "chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service_factory.h"
 #include "components/safe_browsing/buildflags.h"
+#include "components/safe_browsing/core/browser/realtime/chrome_enterprise_url_lookup_service.h"
 #include "components/safe_browsing/core/browser/realtime/policy_engine.h"
 #include "components/sessions/content/session_tab_helper.h"
 #include "content/public/browser/browser_thread.h"
diff --git a/chrome/browser/extensions/api/bookmarks_core/bookmarks_function.cc b/chrome/browser/extensions/api/bookmarks_core/bookmarks_function.cc
index 8b82df3..97dee495 100644
--- a/chrome/browser/extensions/api/bookmarks_core/bookmarks_function.cc
+++ b/chrome/browser/extensions/api/bookmarks_core/bookmarks_function.cc
@@ -108,7 +108,7 @@
 
 void BookmarksFunction::OnResponded() {
   DCHECK(response_type());
-  if (*response_type() == ExtensionFunction::SUCCEEDED) {
+  if (*response_type() == ResponseType::kSucceeded) {
     BookmarksApiWatcher::GetForBrowserContext(browser_context())
         ->NotifyApiInvoked(this);
   }
diff --git a/chrome/browser/extensions/api/developer_private/developer_private_functions_desktop.cc b/chrome/browser/extensions/api/developer_private/developer_private_functions_desktop.cc
index 305f025..db98632f 100644
--- a/chrome/browser/extensions/api/developer_private/developer_private_functions_desktop.cc
+++ b/chrome/browser/extensions/api/developer_private/developer_private_functions_desktop.cc
@@ -204,9 +204,9 @@
     default;
 
 ExtensionFunction::ResponseAction DeveloperPrivateAutoUpdateFunction::Run() {
-  ExtensionUpdater* updater =
-      ExtensionSystem::Get(browser_context())->extension_service()->updater();
-  if (updater) {
+  Profile* profile = Profile::FromBrowserContext(browser_context());
+  ExtensionUpdater* updater = ExtensionUpdater::Get(profile);
+  if (updater->enabled()) {
     ExtensionUpdater::CheckParams params;
     params.fetch_priority = DownloadFetchPriority::kForeground;
     params.install_immediately = true;
diff --git a/chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api_unittest.cc b/chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api_unittest.cc
index 4f3c7219..e46e07e3 100644
--- a/chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api_unittest.cc
+++ b/chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api_unittest.cc
@@ -141,7 +141,8 @@
     api_test_utils::RunFunction(
         function, std::move(args), browser_context,
         extensions::api_test_utils::FunctionMode::kNone);
-    EXPECT_EQ(ExtensionFunction::FAILED, *function->response_type());
+    EXPECT_EQ(ExtensionFunction::ResponseType::kFailed,
+              *function->response_type());
     return function->GetError();
   }
 
diff --git a/chrome/browser/extensions/api/gcm/extension_gcm_app_handler_unittest.cc b/chrome/browser/extensions/api/gcm/extension_gcm_app_handler_unittest.cc
index 6504cefb..81b25bda85 100644
--- a/chrome/browser/extensions/api/gcm/extension_gcm_app_handler_unittest.cc
+++ b/chrome/browser/extensions/api/gcm/extension_gcm_app_handler_unittest.cc
@@ -370,9 +370,8 @@
 
     ExtensionPrefs* prefs = ExtensionPrefs::Get(profile());
     ExtensionUpdater updater(profile());
-    updater.Init(prefs, prefs->pref_service(),
-                 /*frequency_seconds=*/600, /*cache=*/nullptr,
-                 ExtensionDownloader::Factory());
+    updater.InitAndEnable(prefs, prefs->pref_service(), base::Minutes(10),
+                          /*cache=*/nullptr, ExtensionDownloader::Factory());
     auto installer = updater.CreateUpdateInstaller(crx_info, true);
     installer->AddInstallerCallback(base::BindOnce(
         &ExtensionGCMAppHandlerTest::InstallerDone, base::Unretained(this)));
diff --git a/chrome/browser/extensions/api/identity/identity_apitest.cc b/chrome/browser/extensions/api/identity/identity_apitest.cc
index 4818f4f..a86179c8 100644
--- a/chrome/browser/extensions/api/identity/identity_apitest.cc
+++ b/chrome/browser/extensions/api/identity/identity_apitest.cc
@@ -187,7 +187,8 @@
   std::string WaitForError(ExtensionFunction* function) {
     RunMessageLoopUntilResponse();
     CHECK(function->response_type());
-    EXPECT_EQ(ExtensionFunction::FAILED, *function->response_type());
+    EXPECT_EQ(ExtensionFunction::ResponseType::kFailed,
+              *function->response_type());
     return function->GetError();
   }
 
diff --git a/chrome/browser/extensions/api/management/management_browsertest.cc b/chrome/browser/extensions/api/management/management_browsertest.cc
index 3af3d90..60b9cf7 100644
--- a/chrome/browser/extensions/api/management/management_browsertest.cc
+++ b/chrome/browser/extensions/api/management/management_browsertest.cc
@@ -54,6 +54,7 @@
 using extensions::Extension;
 using extensions::ExtensionRegistry;
 using extensions::ExtensionService;
+using extensions::ExtensionUpdater;
 using extensions::Manifest;
 using extensions::mojom::ManifestLocation;
 using policy::PolicyMap;
@@ -358,7 +359,7 @@
 
   // Install version 1 of the extension.
   ExtensionTestMessageListener listener1("v1 installed");
-  ExtensionService* service = extension_service();
+  ExtensionUpdater* updater = ExtensionUpdater::Get(profile());
   ExtensionRegistry* registry = extension_registry();
   const size_t size_before = registry->enabled_extensions().size();
   EXPECT_TRUE(registry->disabled_extensions().empty());
@@ -374,7 +375,7 @@
 
   {
     extensions::TestExtensionRegistryObserver install_observer(registry);
-    extensions::ExtensionUpdater::CheckParams params1;
+    ExtensionUpdater::CheckParams params1;
     bool install_finished = false;
     std::set<std::string> updates;
     params1.update_found_callback = base::BindLambdaForTesting(
@@ -383,7 +384,7 @@
         });
     params1.callback = base::BindLambdaForTesting(
         [&install_finished]() { install_finished = true; });
-    service->updater()->CheckNow(std::move(params1));
+    updater->CheckNow(std::move(params1));
     install_observer.WaitForExtensionWillBeInstalled();
     EXPECT_TRUE(listener2.WaitUntilSatisfied());
     ASSERT_EQ(size_before + 1, registry->enabled_extensions().size());
@@ -405,7 +406,7 @@
       temp_dir.GetPath(), "v3.crx", "manifest_v3.xml.template"));
 
   {
-    extensions::ExtensionUpdater::CheckParams params2;
+    ExtensionUpdater::CheckParams params2;
     base::RunLoop run_loop;
     std::set<std::string> updates;
     params2.update_found_callback = base::BindLambdaForTesting(
@@ -413,7 +414,7 @@
           updates.insert(id);
         });
     params2.callback = run_loop.QuitClosure();
-    service->updater()->CheckNow(std::move(params2));
+    updater->CheckNow(std::move(params2));
     run_loop.Run();
     ASSERT_TRUE(base::Contains(updates, "ogjcoiohnmldgjemafoockdghcjciccf"));
   }
@@ -458,7 +459,7 @@
 
   // Install version 1 of the extension.
   ExtensionTestMessageListener listener1("v1 installed");
-  ExtensionService* service = extension_service();
+  ExtensionUpdater* updater = ExtensionUpdater::Get(profile());
   ExtensionRegistry* registry = extension_registry();
   const size_t enabled_size_before = registry->enabled_extensions().size();
   const size_t disabled_size_before = registry->disabled_extensions().size();
@@ -477,14 +478,14 @@
   // is still disabled.
   bool install_finished = false;
   std::set<std::string> updates;
-  extensions::ExtensionUpdater::CheckParams params;
+  ExtensionUpdater::CheckParams params;
   params.update_found_callback = base::BindLambdaForTesting(
       [&updates](const std::string& id, const base::Version&) {
         updates.insert(id);
       });
   params.callback = base::BindLambdaForTesting(
       [&install_finished]() { install_finished = true; });
-  service->updater()->CheckNow(std::move(params));
+  updater->CheckNow(std::move(params));
   install_observer.WaitForExtensionWillBeInstalled();
   ASSERT_EQ(disabled_size_before + 1, registry->disabled_extensions().size());
   ASSERT_EQ(enabled_size_before, registry->enabled_extensions().size());
@@ -505,7 +506,7 @@
 }
 
 IN_PROC_BROWSER_TEST_F(ExtensionManagementTest, ExternalUrlUpdate) {
-  ExtensionService* service = extension_service();
+  ExtensionUpdater* updater = ExtensionUpdater::Get(profile());
   const char kExtensionId[] = "ogjcoiohnmldgjemafoockdghcjciccf";
 
   base::ScopedAllowBlockingForTesting allow_blocking;
@@ -540,7 +541,7 @@
 
   extensions::TestExtensionRegistryObserver install_observer(registry);
   // Run autoupdate and make sure version 2 of the extension was installed.
-  service->updater()->CheckNow(extensions::ExtensionUpdater::CheckParams());
+  updater->CheckNow(ExtensionUpdater::CheckParams());
   install_observer.WaitForExtensionWillBeInstalled();
   ASSERT_EQ(size_before + 1, registry->enabled_extensions().size());
   const Extension* extension =
diff --git a/chrome/browser/extensions/api/page_capture/page_capture_api_unittest.cc b/chrome/browser/extensions/api/page_capture/page_capture_api_unittest.cc
index 8cf4d99..efc861e 100644
--- a/chrome/browser/extensions/api/page_capture/page_capture_api_unittest.cc
+++ b/chrome/browser/extensions/api/page_capture/page_capture_api_unittest.cc
@@ -87,7 +87,8 @@
   ASSERT_TRUE(results);
   EXPECT_TRUE(results->empty()) << "Did not expect a result";
   CHECK(function->response_type());
-  EXPECT_EQ(ExtensionFunction::FAILED, *function->response_type());
+  EXPECT_EQ(ExtensionFunction::ResponseType::kFailed,
+            *function->response_type());
   EXPECT_EQ("Tab navigated before capture could complete.",
             function->GetError());
 
diff --git a/chrome/browser/extensions/api/runtime/chrome_runtime_api_delegate.cc b/chrome/browser/extensions/api/runtime/chrome_runtime_api_delegate.cc
index 244313b..0db26a1 100644
--- a/chrome/browser/extensions/api/runtime/chrome_runtime_api_delegate.cc
+++ b/chrome/browser/extensions/api/runtime/chrome_runtime_api_delegate.cc
@@ -94,7 +94,7 @@
 
       // initial_delay_ms (note that we set 'always_use_initial_delay' to false
       // below)
-      1000 * extensions::kDefaultUpdateFrequencySeconds,
+      extensions::kDefaultUpdateFrequency.InMilliseconds(),
 
       // multiply_factor
       1,
@@ -233,10 +233,9 @@
 bool ChromeRuntimeAPIDelegate::CheckForUpdates(
     const extensions::ExtensionId& extension_id,
     UpdateCheckCallback callback) {
-  ExtensionSystem* system = ExtensionSystem::Get(browser_context_);
-  extensions::ExtensionService* service = system->extension_service();
-  ExtensionUpdater* updater = service->updater();
-  if (!updater) {
+  Profile* profile = Profile::FromBrowserContext(browser_context_);
+  ExtensionUpdater* updater = ExtensionUpdater::Get(profile);
+  if (!updater->enabled()) {
     return false;
   }
 
diff --git a/chrome/browser/extensions/api/runtime/chrome_runtime_api_delegate_unittest.cc b/chrome/browser/extensions/api/runtime/chrome_runtime_api_delegate_unittest.cc
index 2afbe35..81376e4 100644
--- a/chrome/browser/extensions/api/runtime/chrome_runtime_api_delegate_unittest.cc
+++ b/chrome/browser/extensions/api/runtime/chrome_runtime_api_delegate_unittest.cc
@@ -214,7 +214,7 @@
     InitializeExtensionServiceWithUpdater();
     runtime_delegate_ =
         std::make_unique<ChromeRuntimeAPIDelegate>(browser_context());
-    service()->updater()->SetExtensionCacheForTesting(nullptr);
+    ExtensionUpdater::Get(profile())->SetExtensionCacheForTesting(nullptr);
     EventRouterFactory::GetInstance()->SetTestingFactory(
         browser_context(),
         base::BindRepeating(&TestEventRouterFactoryFunction));
diff --git a/chrome/browser/extensions/chrome_extension_function_unittest.cc b/chrome/browser/extensions/chrome_extension_function_unittest.cc
index 3edcc8e..dc0f99a 100644
--- a/chrome/browser/extensions/chrome_extension_function_unittest.cc
+++ b/chrome/browser/extensions/chrome_extension_function_unittest.cc
@@ -26,7 +26,7 @@
                      base::Value::List results,
                      const std::string& error,
                      mojom::ExtraResponseDataPtr) {
-  EXPECT_EQ(ExtensionFunction::ResponseType::SUCCEEDED, type);
+  EXPECT_EQ(ExtensionFunction::ResponseType::kSucceeded, type);
   *did_respond = true;
 }
 
@@ -35,7 +35,7 @@
                   base::Value::List results,
                   const std::string& error,
                   mojom::ExtraResponseDataPtr) {
-  EXPECT_EQ(ExtensionFunction::ResponseType::FAILED, type);
+  EXPECT_EQ(ExtensionFunction::ResponseType::kFailed, type);
   *did_respond = true;
 }
 
diff --git a/chrome/browser/extensions/extension_browsertest.cc b/chrome/browser/extensions/extension_browsertest.cc
index 6d7047f..575a926 100644
--- a/chrome/browser/extensions/extension_browsertest.cc
+++ b/chrome/browser/extensions/extension_browsertest.cc
@@ -203,9 +203,9 @@
           ? std::make_unique<ChromeExtensionTestNotificationObserver>(browser())
           : std::make_unique<ChromeExtensionTestNotificationObserver>(
                 profile());
-  if (extension_service()->updater()) {
-    extension_service()->updater()->SetExtensionCacheForTesting(
-        test_extension_cache_.get());
+  ExtensionUpdater* updater = ExtensionUpdater::Get(profile());
+  if (updater->enabled()) {
+    updater->SetExtensionCacheForTesting(test_extension_cache_.get());
   }
 
   content::URLDataSource::Add(profile(),
diff --git a/chrome/browser/extensions/extension_service.cc b/chrome/browser/extensions/extension_service.cc
index 633748be..6f2a720 100644
--- a/chrome/browser/extensions/extension_service.cc
+++ b/chrome/browser/extensions/extension_service.cc
@@ -208,6 +208,7 @@
       pending_extension_manager_(PendingExtensionManager::Get(profile)),
       external_provider_manager_(ExternalProviderManager::Get(profile)),
       ready_(ready),
+      updater_(ExtensionUpdater::Get(profile)),
       component_loader_(std::make_unique<ComponentLoader>(system_, profile_)),
       error_controller_(error_controller),
       external_install_manager_(ExternalInstallManager::Get(profile)),
@@ -252,11 +253,10 @@
 
   ExtensionManagementFactory::GetForBrowserContext(profile_)->AddObserver(this);
 
-  // Set up the ExtensionUpdater.
   if (autoupdate_enabled) {
-    updater_ = ExtensionUpdater::Get(profile);
-    updater_->Init(
-        extension_prefs, profile->GetPrefs(), kDefaultUpdateFrequencySeconds,
+    // Initialize and enable the ExtensionUpdater.
+    updater_->InitAndEnable(
+        extension_prefs, profile->GetPrefs(), kDefaultUpdateFrequency,
         ExtensionsBrowserClient::Get()->GetExtensionCache(),
         base::BindRepeating(ChromeExtensionDownloaderFactory::CreateForProfile,
                             profile));
@@ -766,7 +766,7 @@
     EnableExtension(id);
   }
 
-  if (updater_) {
+  if (updater_ && updater_->enabled()) {
     // Find all extensions disabled due to minimum version requirement from
     // policy (including the ones that got disabled just now), and check
     // for update.
@@ -805,7 +805,7 @@
 
 void ExtensionService::CheckForUpdatesSoon() {
   // This can legitimately happen in unit tests.
-  if (!updater_) {
+  if (!updater_ || !updater_->enabled()) {
     return;
   }
 
@@ -1243,7 +1243,7 @@
 }
 
 void ExtensionService::OnInstalledExtensionsLoaded() {
-  if (updater_) {
+  if (updater_ && updater_->enabled()) {
     updater_->Start();
   }
 
diff --git a/chrome/browser/extensions/extension_service.h b/chrome/browser/extensions/extension_service.h
index 7abca6d7..302cadc 100644
--- a/chrome/browser/extensions/extension_service.h
+++ b/chrome/browser/extensions/extension_service.h
@@ -366,11 +366,6 @@
 
   Profile* profile() { return profile_; }
 
-  // Note that this may return NULL if autoupdate is not turned on.
-  // TODO(crbug.com/404943906): Remove this function and have callers use
-  // ExtensionUpdater::Get() instead.
-  ExtensionUpdater* updater() { return updater_; }
-
   ComponentLoader* component_loader() { return component_loader_.get(); }
 
   SharedModuleService* shared_module_service() {
@@ -527,7 +522,7 @@
   // Signaled when all extensions are loaded.
   const raw_ptr<base::OneShotEvent> ready_;
 
-  // Our extension updater, if updates are turned on.
+  // Our extension updater. May be disabled if updates are turned off.
   raw_ptr<ExtensionUpdater> updater_ = nullptr;
 
   base::ScopedMultiSourceObservation<content::RenderProcessHost,
diff --git a/chrome/browser/extensions/extension_service_test_base.cc b/chrome/browser/extensions/extension_service_test_base.cc
index 0ec4405b..0e2d360 100644
--- a/chrome/browser/extensions/extension_service_test_base.cc
+++ b/chrome/browser/extensions/extension_service_test_base.cc
@@ -322,7 +322,9 @@
   ExtensionServiceInitParams params;
   params.autoupdate_enabled = true;
   InitializeExtensionService(std::move(params));
-  service_->updater()->Start();
+  auto* updater = ExtensionUpdater::Get(profile());
+  CHECK(updater->enabled());
+  updater->Start();
 }
 
 void ExtensionServiceTestBase::
diff --git a/chrome/browser/extensions/extension_service_test_with_install.cc b/chrome/browser/extensions/extension_service_test_with_install.cc
index ca1df6b..6cb0a0b 100644
--- a/chrome/browser/extensions/extension_service_test_with_install.cc
+++ b/chrome/browser/extensions/extension_service_test_with_install.cc
@@ -289,9 +289,8 @@
   // Create an ExtensionUpdater and use it to create a CrxInstaller.
   ExtensionPrefs* prefs = ExtensionPrefs::Get(profile());
   ExtensionUpdater updater(profile());
-  updater.Init(prefs, prefs->pref_service(),
-               /*frequency_seconds=*/600, /*cache=*/nullptr,
-               ExtensionDownloader::Factory());
+  updater.InitAndEnable(prefs, prefs->pref_service(), base::Minutes(10),
+                        /*cache=*/nullptr, ExtensionDownloader::Factory());
   auto installer = updater.CreateUpdateInstaller(crx_info, true);
 
   if (installer) {
diff --git a/chrome/browser/extensions/extension_service_unittest.cc b/chrome/browser/extensions/extension_service_unittest.cc
index 4a1250b..47c591bc 100644
--- a/chrome/browser/extensions/extension_service_unittest.cc
+++ b/chrome/browser/extensions/extension_service_unittest.cc
@@ -3234,9 +3234,8 @@
 
   // Simulate shutdown.
   ExtensionUpdater updater(profile());
-  updater.Init(prefs(), prefs()->pref_service(),
-               /*frequency_seconds=*/600, /*cache=*/nullptr,
-               ExtensionDownloader::Factory());
+  updater.InitAndEnable(prefs(), prefs()->pref_service(), base::Minutes(10),
+                        /*cache=*/nullptr, ExtensionDownloader::Factory());
   updater.set_browser_terminating_for_test(true);
 
   // Update should fail and extension should not be updated.
@@ -8544,10 +8543,10 @@
 
   ExtensionDownloaderTestHelper helper;
   NullExtensionCache extension_cache;
-  service()->updater()->SetExtensionDownloaderForTesting(
-      helper.CreateDownloader());
-  service()->updater()->SetExtensionCacheForTesting(&extension_cache);
-  service()->updater()->Start();
+  ExtensionUpdater* updater = ExtensionUpdater::Get(profile());
+  updater->SetExtensionDownloaderForTesting(helper.CreateDownloader());
+  updater->SetExtensionCacheForTesting(&extension_cache);
+  updater->Start();
 
   GURL update_url(extension_urls::kChromeWebstoreUpdateURL);
   external_provider_manager()->OnExternalExtensionUpdateUrlFound(
@@ -8575,7 +8574,7 @@
                                  "X-Goog-Update-Interactivity"));
 
   // Destroy updater's downloader as it uses |helper|.
-  service()->updater()->SetExtensionDownloaderForTesting(nullptr);
+  updater->SetExtensionDownloaderForTesting(nullptr);
 }
 
 INSTANTIATE_TEST_SUITE_P(
diff --git a/chrome/browser/extensions/extension_sync_service_unittest.cc b/chrome/browser/extensions/extension_sync_service_unittest.cc
index 82170cd..1f6f538 100644
--- a/chrome/browser/extensions/extension_sync_service_unittest.cc
+++ b/chrome/browser/extensions/extension_sync_service_unittest.cc
@@ -1261,6 +1261,7 @@
 
 TEST_F(ExtensionSyncServiceTest, ProcessSyncDataVersionCheck) {
   InitializeExtensionServiceWithUpdater();
+  auto* updater = extensions::ExtensionUpdater::Get(profile());
   extension_sync_service()->MergeDataAndStartSyncing(
       syncer::EXTENSIONS, syncer::SyncDataList(),
       std::make_unique<syncer::FakeSyncChangeProcessor>());
@@ -1285,7 +1286,7 @@
 
     // Should do nothing if extension version == sync version.
     extension_sync_service()->ProcessSyncChanges(FROM_HERE, list);
-    EXPECT_FALSE(service()->updater()->WillCheckSoon());
+    EXPECT_FALSE(updater->WillCheckSoon());
     // Make sure the version we'll send back to sync didn't change.
     syncer::SyncDataList data =
         extension_sync_service()->GetAllSyncDataForTesting(syncer::EXTENSIONS);
@@ -1304,7 +1305,7 @@
         MakeSyncChangeList(kGoodCrx, specifics, SyncChange::ACTION_UPDATE);
 
     extension_sync_service()->ProcessSyncChanges(FROM_HERE, list);
-    EXPECT_FALSE(service()->updater()->WillCheckSoon());
+    EXPECT_FALSE(updater->WillCheckSoon());
     // Make sure the version we'll send back to sync didn't change.
     syncer::SyncDataList data =
         extension_sync_service()->GetAllSyncDataForTesting(syncer::EXTENSIONS);
@@ -1324,7 +1325,7 @@
         MakeSyncChangeList(kGoodCrx, specifics, SyncChange::ACTION_UPDATE);
 
     extension_sync_service()->ProcessSyncChanges(FROM_HERE, list);
-    EXPECT_TRUE(service()->updater()->WillCheckSoon());
+    EXPECT_TRUE(updater->WillCheckSoon());
     // Make sure that we'll send the NEW version back to sync, even though we
     // haven't actually updated yet. This is to prevent the data in sync from
     // flip-flopping back and forth until all clients are up to date.
@@ -1343,6 +1344,7 @@
 
 TEST_F(ExtensionSyncServiceTest, ProcessSyncDataNotInstalled) {
   InitializeExtensionServiceWithUpdater();
+  auto* updater = extensions::ExtensionUpdater::Get(profile());
   extension_sync_service()->MergeDataAndStartSyncing(
       syncer::EXTENSIONS, syncer::SyncDataList(),
       std::make_unique<syncer::FakeSyncChangeProcessor>());
@@ -1361,7 +1363,7 @@
   EXPECT_TRUE(registrar()->IsExtensionEnabled(kGoodCrx));
   EXPECT_FALSE(extensions::util::IsIncognitoEnabled(kGoodCrx, profile()));
   extension_sync_service()->ProcessSyncChanges(FROM_HERE, list);
-  EXPECT_TRUE(service()->updater()->WillCheckSoon());
+  EXPECT_TRUE(updater->WillCheckSoon());
   EXPECT_FALSE(registrar()->IsExtensionEnabled(kGoodCrx));
   EXPECT_TRUE(extensions::util::IsIncognitoEnabled(kGoodCrx, profile()));
 
diff --git a/chrome/browser/extensions/external_provider_impl_unittest.cc b/chrome/browser/extensions/external_provider_impl_unittest.cc
index fd24173..d1685339 100644
--- a/chrome/browser/extensions/external_provider_impl_unittest.cc
+++ b/chrome/browser/extensions/external_provider_impl_unittest.cc
@@ -107,6 +107,10 @@
     return ExternalProviderManager::Get(profile());
   }
 
+  ExtensionUpdater* extension_updater() {
+    return ExtensionUpdater::Get(profile());
+  }
+
   void InitService() {
 #if BUILDFLAG(IS_CHROMEOS)
     user_manager::ScopedUserManager scoped_user_manager(
@@ -114,7 +118,7 @@
 #endif
     InitializeExtensionServiceWithUpdaterAndPrefs();
 
-    service()->updater()->SetExtensionCacheForTesting(
+    extension_updater()->SetExtensionCacheForTesting(
         test_extension_cache_.get());
 
     // Don't install pre-installed apps. Some of the pre-installed apps are
@@ -179,7 +183,7 @@
     params.prefs_content = "{}";
     params.autoupdate_enabled = true;
     InitializeExtensionService(std::move(params));
-    service_->updater()->Start();
+    extension_updater()->Start();
     content::RunAllTasksUntilIdle();
   }
 
@@ -200,7 +204,7 @@
 
   void TearDown() override {
     // Avoid dangling pointers.
-    service_->updater()->SetExtensionCacheForTesting(nullptr);
+    extension_updater()->SetExtensionCacheForTesting(nullptr);
     test_extension_cache_.reset();
     ExtensionServiceTestBase::TearDown();
   }
diff --git a/chrome/browser/extensions/external_provider_manager.cc b/chrome/browser/extensions/external_provider_manager.cc
index 38f3848..4af0d92f 100644
--- a/chrome/browser/extensions/external_provider_manager.cc
+++ b/chrome/browser/extensions/external_provider_manager.cc
@@ -130,21 +130,18 @@
 void ExternalProviderManager::OnAllExternalProvidersReady() {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
+  Profile* profile = Profile::FromBrowserContext(context_.get());
 #if BUILDFLAG(IS_CHROMEOS)
-  auto* install_limiter =
-      InstallLimiter::Get(Profile::FromBrowserContext(context_.get()));
+  auto* install_limiter = InstallLimiter::Get(profile);
   if (install_limiter) {
     install_limiter->OnAllExternalProvidersReady();
   }
 #endif  // BUILDFLAG(IS_CHROMEOS)
 
   // Install any pending extensions.
-  ExtensionService* service =
-      ExtensionSystem::Get(context_)->extension_service();
-  DCHECK(service);
-  ExtensionUpdater* updater = service->updater();
+  ExtensionUpdater* updater = ExtensionUpdater::Get(profile);
 
-  if (update_once_all_providers_are_ready_ && updater) {
+  if (update_once_all_providers_are_ready_ && updater->enabled()) {
     update_once_all_providers_are_ready_ = false;
     ExtensionUpdater::CheckParams params;
     params.callback = external_updates_finished_callback_
@@ -498,11 +495,9 @@
     CheckExternalUninstall(id);
   }
 
-  ExtensionService* service =
-      ExtensionSystem::Get(context_)->extension_service();
-  DCHECK(service);
-  ExtensionUpdater* updater = service->updater();
-  if (!update_url_extensions.empty() && updater) {
+  Profile* profile = Profile::FromBrowserContext(context_);
+  ExtensionUpdater* updater = ExtensionUpdater::Get(profile);
+  if (!update_url_extensions.empty() && updater->enabled()) {
     // Empty params will cause pending extensions to be updated.
     updater->CheckNow(ExtensionUpdater::CheckParams());
   }
diff --git a/chrome/browser/extensions/service_worker_apitest.cc b/chrome/browser/extensions/service_worker_apitest.cc
index b8fb852..4f4fb64f 100644
--- a/chrome/browser/extensions/service_worker_apitest.cc
+++ b/chrome/browser/extensions/service_worker_apitest.cc
@@ -1429,13 +1429,11 @@
     ExtensionTestMessageListener ready_listener("ready2");
     ExtensionTestMessageListener on_installed_listener("onInstalled");
     base::FilePath path = test_dir.Pack();
-    ExtensionService* const extension_service =
-        ExtensionSystem::Get(profile())->extension_service();
     CRXFileInfo crx_info(path, GetTestVerifierFormat());
     crx_info.extension_id = id;
 
-    auto installer =
-        extension_service->updater()->CreateUpdateInstaller(crx_info, true);
+    ExtensionUpdater* updater = ExtensionUpdater::Get(profile());
+    auto installer = updater->CreateUpdateInstaller(crx_info, true);
     EXPECT_TRUE(installer);
     installer->InstallCrxFile(crx_info);
 
diff --git a/chrome/browser/extensions/updater/extension_updater.cc b/chrome/browser/extensions/updater/extension_updater.cc
index ed1228f..bd6483e 100644
--- a/chrome/browser/extensions/updater/extension_updater.cc
+++ b/chrome/browser/extensions/updater/extension_updater.cc
@@ -81,9 +81,9 @@
 
 // For sanity checking on update frequency - enforced in release mode only.
 #if defined(NDEBUG)
-const int kMinUpdateFrequencySeconds = 30;
+constexpr base::TimeDelta kMinUpdateFrequency = base::Seconds(30);
 #endif
-const int kMaxUpdateFrequencySeconds = 60 * 60 * 24 * 7;  // 7 days
+constexpr base::TimeDelta kMaxUpdateFrequency = base::Days(7);
 
 bool g_skip_scheduled_checks_for_tests = false;
 
@@ -173,24 +173,24 @@
       pending_extension_manager_(PendingExtensionManager::Get(profile)),
       external_install_manager_(ExternalInstallManager::Get(profile)) {}
 
-void ExtensionUpdater::Init(
+void ExtensionUpdater::InitAndEnable(
     ExtensionPrefs* extension_prefs,
     PrefService* prefs,
-    int frequency_seconds,
+    base::TimeDelta frequency,
     ExtensionCache* cache,
     const ExtensionDownloader::Factory& downloader_factory) {
+  enabled_ = true;
   downloader_factory_ = downloader_factory;
-  frequency_ = base::Seconds(frequency_seconds);
+  frequency_ = frequency;
   extension_prefs_ = extension_prefs;
   prefs_ = prefs;
   extension_cache_ = cache;
-  DCHECK_LE(frequency_seconds, kMaxUpdateFrequencySeconds);
+  DCHECK_LE(frequency_, kMaxUpdateFrequency);
 #if defined(NDEBUG)
   // In Release mode we enforce that update checks don't happen too often.
-  frequency_seconds = std::max(frequency_seconds, kMinUpdateFrequencySeconds);
+  frequency_ = std::max(frequency_, kMinUpdateFrequency);
 #endif
-  frequency_seconds = std::min(frequency_seconds, kMaxUpdateFrequencySeconds);
-  frequency_ = base::Seconds(frequency_seconds);
+  frequency_ = std::min(frequency_, kMaxUpdateFrequency);
   on_app_terminating_subscription_ =
       browser_shutdown::AddAppTerminatingCallback(base::BindOnce(
           &ExtensionUpdater::OnAppTerminating, base::Unretained(this)));
@@ -214,6 +214,7 @@
 }
 
 void ExtensionUpdater::Start() {
+  CHECK(enabled_);
   DCHECK(!alive_);
   // If these are NULL, then that means we've been called after Stop()
   // has been called.
@@ -412,6 +413,7 @@
 }
 
 void ExtensionUpdater::CheckNow(CheckParams params) {
+  CHECK(enabled_);
   if (params.ids.empty()) {
     // Checking all extensions. Cancel pending DoCheckSoon() call if there's
     // one, as it would be redundant.
diff --git a/chrome/browser/extensions/updater/extension_updater.h b/chrome/browser/extensions/updater/extension_updater.h
index 3f445bf..6144e69e 100644
--- a/chrome/browser/extensions/updater/extension_updater.h
+++ b/chrome/browser/extensions/updater/extension_updater.h
@@ -131,12 +131,13 @@
   ExtensionUpdater& operator=(const ExtensionUpdater&) = delete;
   ~ExtensionUpdater() override;
 
-  // Initializes the updater. Does not start it. Use Start() for that.
-  void Init(ExtensionPrefs* extension_prefs,
-            PrefService* prefs,
-            int frequency_seconds,
-            ExtensionCache* cache,
-            const ExtensionDownloader::Factory& downloader_factory);
+  // Initializes and enables the updater. Does not start it. Use Start() for
+  // that.
+  void InitAndEnable(ExtensionPrefs* extension_prefs,
+                     PrefService* prefs,
+                     base::TimeDelta frequency,
+                     ExtensionCache* cache,
+                     const ExtensionDownloader::Factory& downloader_factory);
 
   // KeyedService:
   void Shutdown() override;
@@ -194,6 +195,8 @@
   void SetCrxInstallerResultCallbackForTesting(
       ExtensionSystem::InstallUpdateCallback callback);
 
+  bool enabled() const { return enabled_; }
+
   // Exists because some tests are not able to use the private constructor for
   // testing.
   void set_crx_installer_factory_for_test(CrxInstallerFactoryForTest* factory) {
@@ -345,6 +348,9 @@
   // Called when the browser is terminating.
   void OnAppTerminating();
 
+  // Whether the updater is enabled (i.e. it's legal to call Start()).
+  bool enabled_ = false;
+
   // Whether Start() has been called but not Stop().
   bool alive_ = false;
 
diff --git a/chrome/browser/extensions/updater/extension_updater_unittest.cc b/chrome/browser/extensions/updater/extension_updater_unittest.cc
index f58cdda4..0db18281 100644
--- a/chrome/browser/extensions/updater/extension_updater_unittest.cc
+++ b/chrome/browser/extensions/updater/extension_updater_unittest.cc
@@ -171,6 +171,8 @@
     "mmrBg0EEIsyLRmUmfyVEfvcIUOZxFqn4A9D2aaRSvNHy9qkasZMBDEql8Nt2iNZm/"
     "kGS7sizidDV6Bc/vyLNiH1gKOXBQ42JIxKjgtrmnhGV2giw2vJGwIDAQAB";
 
+constexpr base::TimeDelta kUpdateFrequency = base::Seconds(15);
+
 // Extracts the integer value of the |authuser| query parameter. Returns 0 if
 // the parameter is not set.
 int GetAuthUserQueryValue(const GURL& url) {
@@ -390,8 +392,6 @@
   }
 }
 
-static const int kUpdateFrequencySecs = 15;
-
 // Takes a string with KEY=VALUE parameters separated by '&' in |params| and
 // puts the key/value pairs into |result|. For keys with no value, the empty
 // string is used. So for "a=1&b=foo&c", result would map "a" to "1", "b" to
@@ -637,8 +637,8 @@
 
     // Set up and start the updater.
     ExtensionUpdater updater(profile());
-    updater.Init(extension_prefs(), pref_service(), 60 * 60 * 24, nullptr,
-                 factory.GetDownloaderFactory());
+    updater.InitAndEnable(extension_prefs(), pref_service(), base::Days(1),
+                          nullptr, factory.GetDownloaderFactory());
     updater.set_crx_installer_factory_for_test(&crx_installer_factory);
     updater.Start();
 
@@ -1432,8 +1432,8 @@
     TestDownloaderFactory factory(helper.url_loader_factory());
     TestCrxInstallerFactory crx_installer_factory;
     ExtensionUpdater updater(profile());
-    updater.Init(extension_prefs(), pref_service(), kUpdateFrequencySecs,
-                 nullptr, factory.GetDownloaderFactory());
+    updater.InitAndEnable(extension_prefs(), pref_service(), kUpdateFrequency,
+                          nullptr, factory.GetDownloaderFactory());
     updater.set_crx_installer_factory_for_test(&crx_installer_factory);
     MockExtensionDownloaderDelegate downloader_delegate;
     downloader_delegate.DelegateTo(&updater);
@@ -1470,8 +1470,8 @@
     TestDownloaderFactory factory(helper.url_loader_factory());
     TestCrxInstallerFactory crx_installer_factory;
     ExtensionUpdater updater(profile());
-    updater.Init(extension_prefs(), pref_service(), kUpdateFrequencySecs,
-                 nullptr, factory.GetDownloaderFactory());
+    updater.InitAndEnable(extension_prefs(), pref_service(), kUpdateFrequency,
+                          nullptr, factory.GetDownloaderFactory());
     updater.set_crx_installer_factory_for_test(&crx_installer_factory);
     MockExtensionDownloaderDelegate downloader_delegate;
     downloader_delegate.DelegateTo(&updater);
@@ -1631,8 +1631,8 @@
     crx_installer_factory.AddFakeCrxInstaller(kTestExtensionId, mock_installer);
 
     ExtensionUpdater updater(prefs_->profile());
-    updater.Init(extension_prefs(), pref_service(), kUpdateFrequencySecs,
-                 nullptr, factory.GetDownloaderFactory());
+    updater.InitAndEnable(extension_prefs(), pref_service(), kUpdateFrequency,
+                          nullptr, factory.GetDownloaderFactory());
     updater.set_crx_installer_factory_for_test(&crx_installer_factory);
     MockExtensionDownloaderDelegate& downloader_delegate = helper.delegate();
     downloader_delegate.DelegateTo(&updater);
@@ -1760,8 +1760,8 @@
         enable_oauth2 ? factory.GetAuthenticatedDownloaderFactory()
                       : factory.GetDownloaderFactory();
     ExtensionUpdater updater(profile());
-    updater.Init(extension_prefs(), pref_service(), kUpdateFrequencySecs,
-                 nullptr, downloader_factory);
+    updater.InitAndEnable(extension_prefs(), pref_service(), kUpdateFrequency,
+                          nullptr, downloader_factory);
     updater.set_crx_installer_factory_for_test(&crx_installer_factory);
 
     MockExtensionDownloaderDelegate downloader_delegate;
@@ -1974,8 +1974,8 @@
     TestDownloaderFactory factory(helper.url_loader_factory());
     TestCrxInstallerFactory crx_installer_factory;
     ExtensionUpdater updater(profile());
-    updater.Init(extension_prefs(), pref_service(), kUpdateFrequencySecs,
-                 nullptr, factory.GetDownloaderFactory());
+    updater.InitAndEnable(extension_prefs(), pref_service(), kUpdateFrequency,
+                          nullptr, factory.GetDownloaderFactory());
     updater.set_crx_installer_factory_for_test(&crx_installer_factory);
     updater.Start();
     updater.EnsureDownloaderCreated();
@@ -2170,8 +2170,8 @@
       prefs->SetActiveBit(id, true);
 
     ExtensionUpdater updater(profile());
-    updater.Init(extension_prefs(), pref_service(), kUpdateFrequencySecs,
-                 nullptr, factory.GetDownloaderFactory());
+    updater.InitAndEnable(extension_prefs(), pref_service(), kUpdateFrequency,
+                          nullptr, factory.GetDownloaderFactory());
     updater.set_crx_installer_factory_for_test(&crx_installer_factory);
     updater.Start();
     updater.CheckNow(ExtensionUpdater::CheckParams());
@@ -2273,8 +2273,8 @@
     SetExtensions(tmp, ExtensionList());
 
     ExtensionUpdater updater(profile());
-    updater.Init(extension_prefs(), pref_service(), kUpdateFrequencySecs,
-                 nullptr, factory.GetDownloaderFactory());
+    updater.InitAndEnable(extension_prefs(), pref_service(), kUpdateFrequency,
+                          nullptr, factory.GetDownloaderFactory());
     updater.set_crx_installer_factory_for_test(&crx_installer_factory);
     updater.Start();
     updater.EnsureDownloaderCreated();
@@ -2532,8 +2532,8 @@
   TestDownloaderFactory factory(helper.url_loader_factory());
   TestCrxInstallerFactory crx_installer_factory;
   ExtensionUpdater updater(profile());
-  updater.Init(extension_prefs(), pref_service(), kUpdateFrequencySecs, nullptr,
-               factory.GetDownloaderFactory());
+  updater.InitAndEnable(extension_prefs(), pref_service(), kUpdateFrequency,
+                        nullptr, factory.GetDownloaderFactory());
   updater.set_crx_installer_factory_for_test(&crx_installer_factory);
   MockExtensionDownloaderDelegate downloader_delegate;
   factory.OverrideDownloaderDelegate(&downloader_delegate);
@@ -2555,8 +2555,8 @@
   TestDownloaderFactory factory(helper.url_loader_factory());
   TestCrxInstallerFactory crx_installer_factory;
   ExtensionUpdater updater(profile());
-  updater.Init(extension_prefs(), pref_service(), kUpdateFrequencySecs, nullptr,
-               factory.GetDownloaderFactory());
+  updater.InitAndEnable(extension_prefs(), pref_service(), kUpdateFrequency,
+                        nullptr, factory.GetDownloaderFactory());
   updater.set_crx_installer_factory_for_test(&crx_installer_factory);
   NiceMock<MockUpdateService> update_service;
   OverrideUpdateService(&updater, &update_service);
@@ -2590,8 +2590,8 @@
   TestDownloaderFactory factory(helper.url_loader_factory());
   TestCrxInstallerFactory crx_installer_factory;
   ExtensionUpdater updater(profile());
-  updater.Init(extension_prefs(), pref_service(), kUpdateFrequencySecs, nullptr,
-               factory.GetDownloaderFactory());
+  updater.InitAndEnable(extension_prefs(), pref_service(), kUpdateFrequency,
+                        nullptr, factory.GetDownloaderFactory());
   updater.set_crx_installer_factory_for_test(&crx_installer_factory);
   NiceMock<MockUpdateService> update_service;
   OverrideUpdateService(&updater, &update_service);
@@ -2643,8 +2643,8 @@
   TestDownloaderFactory factory(helper.url_loader_factory());
   TestCrxInstallerFactory crx_installer_factory;
   ExtensionUpdater updater(profile());
-  updater.Init(extension_prefs(), pref_service(), kUpdateFrequencySecs, nullptr,
-               factory.GetDownloaderFactory());
+  updater.InitAndEnable(extension_prefs(), pref_service(), kUpdateFrequency,
+                        nullptr, factory.GetDownloaderFactory());
   updater.set_crx_installer_factory_for_test(&crx_installer_factory);
   NiceMock<MockUpdateService> update_service;
   OverrideUpdateService(&updater, &update_service);
@@ -2809,8 +2809,8 @@
   TestDownloaderFactory factory(helper.url_loader_factory());
   TestCrxInstallerFactory crx_installer_factory;
   ExtensionUpdater updater(profile());
-  updater.Init(extension_prefs(), pref_service(), kUpdateFrequencySecs, nullptr,
-               factory.GetDownloaderFactory());
+  updater.InitAndEnable(extension_prefs(), pref_service(), kUpdateFrequency,
+                        nullptr, factory.GetDownloaderFactory());
   updater.set_crx_installer_factory_for_test(&crx_installer_factory);
   EXPECT_FALSE(updater.WillCheckSoon());
   updater.Start();
@@ -2843,8 +2843,8 @@
   ASSERT_TRUE(registry->enabled_extensions().GetByID(id));
 
   ExtensionUpdater updater(profile());
-  updater.Init(extension_prefs(), pref_service(), kUpdateFrequencySecs, nullptr,
-               factory.GetDownloaderFactory());
+  updater.InitAndEnable(extension_prefs(), pref_service(), kUpdateFrequency,
+                        nullptr, factory.GetDownloaderFactory());
   updater.set_crx_installer_factory_for_test(&crx_installer_factory);
   ExtensionUpdater::CheckParams params;
   params.ids = {id};
@@ -2923,8 +2923,8 @@
         downloader_test_helper_.url_loader_factory());
     crx_installer_factory_ = std::make_unique<TestCrxInstallerFactory>();
     updater_ = std::make_unique<ExtensionUpdater>(profile());
-    updater_->Init(extension_prefs(), pref_service(), kUpdateFrequencySecs,
-                   nullptr, factory_->GetDownloaderFactory());
+    updater_->InitAndEnable(extension_prefs(), pref_service(), kUpdateFrequency,
+                            nullptr, factory_->GetDownloaderFactory());
     updater_->set_crx_installer_factory_for_test(crx_installer_factory_.get());
 
     store_extension_ =
diff --git a/chrome/browser/extensions/updater/update_service_browsertest.cc b/chrome/browser/extensions/updater/update_service_browsertest.cc
index defe58a..dae7d330 100644
--- a/chrome/browser/extensions/updater/update_service_browsertest.cc
+++ b/chrome/browser/extensions/updater/update_service_browsertest.cc
@@ -104,6 +104,10 @@
   base::Value::Dict GetFirstApp(const base::Value::Dict& root) {
     return GetApp(root, 0);
   }
+
+  ExtensionUpdater* extension_updater() {
+    return ExtensionUpdater::Get(profile());
+  }
 };
 
 IN_PROC_BROWSER_TEST_F(UpdateServiceTest, NoUpdate) {
@@ -124,7 +128,7 @@
 
   extensions::ExtensionUpdater::CheckParams params;
   params.ids = {kExtensionId};
-  extension_service()->updater()->CheckNow(std::move(params));
+  extension_updater()->CheckNow(std::move(params));
 
   // UpdateService should emit a not-updated event.
   EXPECT_EQ(update_client::ComponentState::kUpToDate,
@@ -165,7 +169,7 @@
 
   extensions::ExtensionUpdater::CheckParams params;
   params.ids = {kExtensionId};
-  extension_service()->updater()->CheckNow(std::move(params));
+  extension_updater()->CheckNow(std::move(params));
 
   // UpdateService should emit an error update event.
   EXPECT_EQ(update_client::ComponentState::kUpdateError,
@@ -209,18 +213,18 @@
       InstallExtension(crx_path2, 1, ManifestLocation::kExternalPolicyDownload);
   ASSERT_TRUE(extension1 && extension2);
 
-  extensions::ExtensionUpdater::CheckParams params;
-
   base::RunLoop run_loop1;
-  params.ids = {extension1->id(), extension2->id()};
-  params.callback = run_loop1.QuitClosure();
-  extension_service()->updater()->CheckNow(std::move(params));
+  extensions::ExtensionUpdater::CheckParams params1;
+  params1.ids = {extension1->id(), extension2->id()};
+  params1.callback = run_loop1.QuitClosure();
+  extension_updater()->CheckNow(std::move(params1));
   run_loop1.Run();
 
   base::RunLoop run_loop2;
-  params.ids = {extension1->id()};
-  params.callback = run_loop2.QuitClosure();
-  extension_service()->updater()->CheckNow(std::move(params));
+  extensions::ExtensionUpdater::CheckParams params2;
+  params2.ids = {extension1->id()};
+  params2.callback = run_loop2.QuitClosure();
+  extension_updater()->CheckNow(std::move(params2));
   run_loop2.Run();
 
   ASSERT_EQ(2, update_interceptor_->GetCount())
@@ -271,7 +275,7 @@
   extensions::ExtensionUpdater::CheckParams params;
   params.ids = {kExtensionId};
   params.callback = run_loop.QuitClosure();
-  extension_service()->updater()->CheckNow(std::move(params));
+  extension_updater()->CheckNow(std::move(params));
 
   ExpectProfileKeepAlive(true);
 
@@ -400,7 +404,7 @@
   extensions::ExtensionUpdater::CheckParams params;
   params.ids = {kExtensionId};
   params.callback = run_loop.QuitClosure();
-  extension_service()->updater()->CheckNow(std::move(params));
+  extension_updater()->CheckNow(std::move(params));
 
   // Uninstall the extension right before the message loop is executed to
   // emulate uninstalling an extension in the middle of an extension update.
diff --git a/chrome/browser/extensions/webstore_installer.cc b/chrome/browser/extensions/webstore_installer.cc
index fe45ce4..31dd8982 100644
--- a/chrome/browser/extensions/webstore_installer.cc
+++ b/chrome/browser/extensions/webstore_installer.cc
@@ -15,6 +15,7 @@
 #include <vector>
 
 #include "base/command_line.h"
+#include "base/feature_list.h"
 #include "base/files/file_util.h"
 #include "base/functional/bind.h"
 #include "base/metrics/field_trial.h"
@@ -58,6 +59,7 @@
 #include "extensions/browser/extension_system.h"
 #include "extensions/browser/install/crx_install_error.h"
 #include "extensions/common/extension.h"
+#include "extensions/common/extension_features.h"
 #include "extensions/common/extension_id.h"
 #include "extensions/common/extension_urls.h"
 #include "extensions/common/manifest_constants.h"
@@ -604,6 +606,15 @@
   params->set_callback(base::BindOnce(&WebstoreInstaller::OnDownloadStarted,
                                       this, extension_id));
   params->set_download_source(download::DownloadSource::EXTENSION_INSTALLER);
+
+  if (base::FeatureList::IsEnabled(
+          extensions_features::kWebstoreInstallerUserGestureKillSwitch)) {
+    // This is set to `true` so that the download stack can correctly apply
+    // download restriction policies and understand that a user gesture started
+    // the extension download.
+    params->set_has_user_gesture(true);
+  }
+
   download_manager->DownloadUrl(std::move(params));
 }
 
diff --git a/chrome/browser/extensions/webstore_installer_test.cc b/chrome/browser/extensions/webstore_installer_test.cc
index 8ac9b9dd3..383fdea 100644
--- a/chrome/browser/extensions/webstore_installer_test.cc
+++ b/chrome/browser/extensions/webstore_installer_test.cc
@@ -8,7 +8,10 @@
 
 #include "base/command_line.h"
 #include "base/strings/stringprintf.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/views/frame/browser_view.h"
 #include "chrome/common/chrome_switches.h"
+#include "components/policy/core/common/policy_pref_names.h"
 #include "content/public/test/browser_test_utils.h"
 #include "net/base/host_port_pair.h"
 #include "net/dns/mock_host_resolver.h"
@@ -64,6 +67,13 @@
   host_resolver()->AddRule(webstore_domain_, "127.0.0.1");
   host_resolver()->AddRule(verified_domain_, "127.0.0.1");
   host_resolver()->AddRule(unverified_domain_, "127.0.0.1");
+
+  // This policy set to 1 should not restrict webstore extension installs, even
+  // if .crx files technically fit the definition of a dangerous file type.
+  // Setting this value for every `WebstoreInstaller` allows for validation that
+  // this policy doesn't interfere with the normal extension install workflow.
+  browser()->profile()->GetPrefs()->SetInteger(
+      policy::policy_prefs::kDownloadRestrictions, 1);
 }
 
 GURL WebstoreInstallerTest::GenerateTestServerUrl(
diff --git a/chrome/browser/feedback/android/java/src/org/chromium/chrome/browser/feedback/HelpAndFeedbackLauncherFactory.java b/chrome/browser/feedback/android/java/src/org/chromium/chrome/browser/feedback/HelpAndFeedbackLauncherFactory.java
index 1e291cb..479b5e0 100644
--- a/chrome/browser/feedback/android/java/src/org/chromium/chrome/browser/feedback/HelpAndFeedbackLauncherFactory.java
+++ b/chrome/browser/feedback/android/java/src/org/chromium/chrome/browser/feedback/HelpAndFeedbackLauncherFactory.java
@@ -5,11 +5,14 @@
 package org.chromium.chrome.browser.feedback;
 
 import org.chromium.base.ResettersForTesting;
+import org.chromium.build.annotations.NullMarked;
+import org.chromium.build.annotations.Nullable;
 import org.chromium.chrome.browser.profiles.Profile;
 
 /** Factory for {@link HelpAndFeedbackLauncher}. Can be used from chrome/browser modules. */
+@NullMarked
 public class HelpAndFeedbackLauncherFactory {
-    private static HelpAndFeedbackLauncher sInstanceForTesting;
+    private static @Nullable HelpAndFeedbackLauncher sInstanceForTesting;
 
     /** Get a {@link HelpAndFeedbackLauncher} for the given {@link Profile}. */
     public static HelpAndFeedbackLauncher getForProfile(Profile profile) {
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index d1e6c07..afa89d8a 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -3374,6 +3374,11 @@
     "expiry_milestone": 145
   },
   {
+    "name": "enable-isolated-web-app-allowlist",
+    "owners": [ "rferens@google.com", "iwa-team@google.com" ],
+    "expiry_milestone": 140
+  },
+  {
     "name": "enable-isolated-web-app-dev-mode",
     "owners": [ "rmcelrath@chromium.org", "reillyg@chromium.org", "iwa-team@google.com" ],
     "expiry_milestone": 140
@@ -6365,6 +6370,14 @@
     "expiry_milestone": 140
   },
   {
+    "name": "ntp-background-customization",
+    "owners": [
+      "pabouchard@google.com",
+      "bling-mony-pod@google.com"
+    ],
+    "expiry_milestone": 150
+  },
+  {
     "name": "ntp-background-image-error-detection",
     "owners": [ "pauladedeji@chromium.org", "tiborg@chromium.org" ],
     "expiry_milestone": 140
@@ -7030,6 +7043,11 @@
     "expiry_milestone": 145
   },
   {
+    "name": "page-action-menu",
+    "owners": [ "adamta@google.com", "bling-alchemy-eng@google.com" ],
+    "expiry_milestone": 150
+  },
+  {
     "name": "page-content-annotations",
     "owners": [ "sophiechang@chromium.org", "mcrouse@chromium.org", "chrome-intelligence-core@google.com"],
     "expiry_milestone": 130
@@ -8899,11 +8917,6 @@
     "expiry_milestone": 135
   },
   {
-    "name": "threaded-scroll-prevent-rendering-starvation",
-    "owners": [ "shaseley@google.com" ],
-    "expiry_milestone": 135
-  },
-  {
     "name": "throttle-main-thread-to-60hz",
     "owners": [ "lizeb@google.com", "clank-performance-team@google.com" ],
     "expiry_milestone": 137
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 7d9983e..dcc9caf 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -1539,6 +1539,12 @@
     "into a managed guest session.";
 #endif  // BUILDFLAG(IS_CHROMEOS)
 
+const char kEnableIsolatedWebAppAllowlistName[] =
+    "Enable an allowlist for Isolated Web Apps";
+const char kEnableIsolatedWebAppAllowlistDescription[] =
+    "Enables an allowlist for Isolated Web Apps, restricting installation and "
+    "updates to only those apps that are allowlisted.";
+
 const char kEnableIsolatedWebAppDevModeName[] =
     "Enable Isolated Web App Developer Mode";
 const char kEnableIsolatedWebAppDevModeDescription[] =
@@ -3798,12 +3804,6 @@
     "Enables querying the third party autofill mode state from the Chrome app.";
 #endif
 
-const char kThreadedScrollPreventRenderingStarvationName[] =
-    "threaded-scroll-prevent-rendering-starvation";
-const char kThreadedScrollPreventRenderingStarvationDescription[] =
-    "Prevents main thread rendering starvation during threaded scrolling based "
-    "on a given threshold.";
-
 const char kThrottleMainTo60HzName[] = "throttle-main-thread-to-60hz";
 const char kThrottleMainTo60HzDescription[] =
     "Throttle main thread updates to 60fps, even when VSync rate is higher.";
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 05429d4..545beb5 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -935,6 +935,9 @@
 extern const char kEnableIsolatedWebAppManagedGuestSessionInstallDescription[];
 #endif  // BUILDFLAG(IS_CHROMEOS)
 
+extern const char kEnableIsolatedWebAppAllowlistName[];
+extern const char kEnableIsolatedWebAppAllowlistDescription[];
+
 extern const char kEnableIsolatedWebAppDevModeName[];
 extern const char kEnableIsolatedWebAppDevModeDescription[];
 
@@ -2204,9 +2207,6 @@
 extern const char kAutofillThirdPartyModeContentProviderDescription[];
 #endif
 
-extern const char kThreadedScrollPreventRenderingStarvationName[];
-extern const char kThreadedScrollPreventRenderingStarvationDescription[];
-
 extern const char kThrottleMainTo60HzName[];
 extern const char kThrottleMainTo60HzDescription[];
 
diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc
index bdba436..ed5b68f 100644
--- a/chrome/browser/flags/android/chrome_feature_list.cc
+++ b/chrome/browser/flags/android/chrome_feature_list.cc
@@ -323,6 +323,7 @@
     &kReengagementNotification,
     &kRelatedSearchesAllLanguage,
     &kRelatedSearchesSwitch,
+    &kRemoveTabFocusOnShowingAndSelect,
     &kReportParentalControlSitesChild,
     &kRightEdgeGoesForwardGestureNav,
     &kSearchInCCT,
@@ -978,7 +979,7 @@
 
 BASE_FEATURE(kPowerSavingModeBroadcastReceiverInBackground,
              "PowerSavingModeBroadcastReceiverInBackground",
-             base::FEATURE_ENABLED_BY_DEFAULT);
+             base::FEATURE_DISABLED_BY_DEFAULT);
 
 BASE_FEATURE(kPreconnectOnTabCreation,
              "PreconnectOnTabCreation",
@@ -1072,6 +1073,10 @@
              "RelatedSearchesSwitch",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
+BASE_FEATURE(kRemoveTabFocusOnShowingAndSelect,
+             "RemoveTabFocusOnShowingAndSelect",
+             base::FEATURE_ENABLED_BY_DEFAULT);
+
 BASE_FEATURE(kReportParentalControlSitesChild,
              "ReportParentalControlSitesChild",
              base::FEATURE_ENABLED_BY_DEFAULT);
diff --git a/chrome/browser/flags/android/chrome_feature_list.h b/chrome/browser/flags/android/chrome_feature_list.h
index 4a4f2b22..95c5b6d 100644
--- a/chrome/browser/flags/android/chrome_feature_list.h
+++ b/chrome/browser/flags/android/chrome_feature_list.h
@@ -176,6 +176,7 @@
 BASE_DECLARE_FEATURE(kRecordSuppressionMetrics);
 BASE_DECLARE_FEATURE(kRelatedSearchesAllLanguage);
 BASE_DECLARE_FEATURE(kRelatedSearchesSwitch);
+BASE_DECLARE_FEATURE(kRemoveTabFocusOnShowingAndSelect);
 BASE_DECLARE_FEATURE(kReportParentalControlSitesChild);
 BASE_DECLARE_FEATURE(kRightEdgeGoesForwardGestureNav);
 BASE_DECLARE_FEATURE(kSearchInCCT);
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 3fe5efe9..54d1b955 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
@@ -485,6 +485,8 @@
     public static final String REENGAGEMENT_NOTIFICATION = "ReengagementNotification";
     public static final String RELATED_SEARCHES_SWITCH = "RelatedSearchesSwitch";
     public static final String RELATED_SEARCHES_ALL_LANGUAGE = "RelatedSearchesAllLanguage";
+    public static final String REMOVE_TAB_FOCUS_ON_SHOWING_AND_SELECT =
+            "RemoveTabFocusOnShowingAndSelect";
     public static final String RENAME_JOURNEYS = "RenameJourneys";
     public static final String RIGHT_EDGE_GOES_FORWARD_GESTURE_NAV =
             "RightEdgeGoesForwardGestureNav";
@@ -777,7 +779,7 @@
     public static final CachedFlag sNotificationTrampoline =
             newCachedFlag(NOTIFICATION_TRAMPOLINE, false);
     public static final CachedFlag sPowerSavingModeBroadcastReceiverInBackground =
-            newCachedFlag(POWER_SAVING_MODE_BROADCAST_RECEIVER_IN_BACKGROUND, true);
+            newCachedFlag(POWER_SAVING_MODE_BROADCAST_RECEIVER_IN_BACKGROUND, false);
     public static final CachedFlag sPriceChangeModule = newCachedFlag(PRICE_CHANGE_MODULE, true);
     public static final CachedFlag sPriceInsights =
             newCachedFlag(
@@ -1100,6 +1102,9 @@
                             "multi_data_source_skip_device_check",
                             false);
 
+    public static final BooleanCachedFeatureParam sAndroidBottomToolbarDefaultToTop =
+            newBooleanCachedFeatureParam(ANDROID_BOTTOM_TOOLBAR, "default_to_top", true);
+
     public static final IntCachedFeatureParam sCctAuthTabEnableHttpsRedirectsVerificationTimeoutMs =
             newIntCachedFeatureParam(
                     CCT_AUTH_TAB_ENABLE_HTTPS_REDIRECTS, "verification_timeout_ms", 10_000);
@@ -1407,6 +1412,7 @@
                     sAndroidAppIntegrationMultiDataSourceSkipDeviceCheck,
                     sAndroidAppIntegrationWithFaviconUseLargeFavicon,
                     sAndroidAppIntegrationWithFaviconZeroStateFaviconNumber,
+                    sAndroidBottomToolbarDefaultToTop,
                     sCctAdaptiveButtonEnableOpenInBrowser,
                     sCctAdaptiveButtonEnableVoice,
                     sCctAuthTabEnableHttpsRedirectsVerificationTimeoutMs,
diff --git a/chrome/browser/glic/BUILD.gn b/chrome/browser/glic/BUILD.gn
index b6b3349..f41defe8 100644
--- a/chrome/browser/glic/BUILD.gn
+++ b/chrome/browser/glic/BUILD.gn
@@ -289,6 +289,7 @@
     ":test_support",
     "//chrome/browser",
     "//chrome/browser:global_features",
+    "//chrome/browser/actor:test_support",
     "//chrome/browser/background/glic",
     "//chrome/browser/ui",
     "//chrome/common",
diff --git a/chrome/browser/glic/browser_ui/glic_border_view.cc b/chrome/browser/glic/browser_ui/glic_border_view.cc
index 2f78c65..5ba7828 100644
--- a/chrome/browser/glic/browser_ui/glic_border_view.cc
+++ b/chrome/browser/glic/browser_ui/glic_border_view.cc
@@ -51,7 +51,7 @@
 constexpr static base::TimeDelta kEmphasisDuration = base::Milliseconds(1500);
 // Time since creation will roll over after this time to prevent growing
 // indefinitely.
-constexpr static base::TimeDelta kMaxTime = base::Days(1);
+constexpr static base::TimeDelta kMaxTime = base::Hours(1);
 
 float ClampAndInterpolate(gfx::Tween::Type type,
                           float t,
@@ -113,15 +113,20 @@
     content::WebContents* contents = focused_tab_data.focus();
     auto* previous_focus = glic_focused_contents_in_current_window_.get();
     if (contents && IsTabInCurrentWindow(contents)) {
-      glic_focused_contents_in_current_window_ =
-          const_cast<content::WebContents*>(contents)->GetWeakPtr();
+      glic_focused_contents_in_current_window_ = contents->GetWeakPtr();
     } else {
       glic_focused_contents_in_current_window_.reset();
     }
 
-    bool tab_switch =
-        previous_focus && glic_focused_contents_in_current_window_ &&
-        previous_focus != glic_focused_contents_in_current_window_.get();
+    auto* current_focus = glic_focused_contents_in_current_window_.get();
+    bool focus_changed = previous_focus != current_focus;
+    if (border_view_->tester_ && focus_changed) [[unlikely]] {
+      auto current_focus_url = current_focus ? current_focus->GetURL() : GURL();
+      border_view_->tester_->FocusedTabChanged(current_focus_url);
+    }
+
+    bool tab_switch = previous_focus &&
+                      glic_focused_contents_in_current_window_ && focus_changed;
     // OnFocusedTabChanged is dispatched after the previous focused WebContents
     // is destroyed, making it no different than the focus changing to a
     // different browser window. `border_view_->compositor_` helps
@@ -281,8 +286,7 @@
   const raw_ptr<BrowserWindowInterface> browser_;
 
   // Tracked states and their subscriptions.
-  base::WeakPtr<const content::WebContents>
-      glic_focused_contents_in_current_window_;
+  base::WeakPtr<content::WebContents> glic_focused_contents_in_current_window_;
   base::CallbackListSubscription focus_change_subscription_;
   bool context_access_indicator_enabled_ = false;
   base::CallbackListSubscription indicator_change_subscription_;
@@ -357,7 +361,8 @@
       {.name = SkString("u_emphasis"), .value = emphasis_},
       {.name = SkString("u_corner_radius"), .value = corner_radius},
       {.name = SkString("u_insets"),
-       .value = static_cast<float>(uniform_insets.left())}};
+       .value = static_cast<float>(uniform_insets.left())},
+      {.name = SkString("u_progress"), .value = progress_}};
   std::vector<cc::PaintShader::Float2Uniform> float2_uniforms = {
       // TODO(https://crbug.com/406026829): Ideally `u_resolution` should be a
       // vec4(x, y, w, h) and does not assume the origin is (0, 0). This way we
@@ -447,6 +452,7 @@
   emphasis_ = GetEmphasis(emphasis_since_first_frame);
   base::TimeDelta opacity_since_first_frame = timestamp - first_frame_time_;
   opacity_ = GetOpacity(timestamp);
+  progress_ = GetEffectProgress(timestamp);
 
   // TODO(liuwilliam): Ideally this should be done in paint-related methods.
   // Consider moving it to LayerDelegate::OnPaintLayer().
@@ -681,6 +687,19 @@
   return time_since_creation.InSecondsF();
 }
 
+float GlicBorderView::GetEffectProgress(base::TimeTicks timestamp) const {
+  if (skip_emphasis_animation_) {
+    return 0.0;
+  }
+  base::TimeDelta time_since_first_frame = timestamp - first_emphasis_frame_;
+  base::TimeDelta total_duration =
+      kEmphasisRampUpDuration + kEmphasisRampDownDuration + kEmphasisDuration;
+  return std::clamp(
+      static_cast<float>(time_since_first_frame.InMillisecondsF() /
+                         total_duration.InMillisecondsF()),
+      0.0f, 1.0f);
+}
+
 base::TimeTicks GlicBorderView::GetCreationTime() const {
   if (tester_ && !tester_->GetTestCreationTime().is_null()) [[unlikely]] {
     return tester_->GetTestCreationTime();
diff --git a/chrome/browser/glic/browser_ui/glic_border_view.h b/chrome/browser/glic/browser_ui/glic_border_view.h
index 2a2846c..ac9de945 100644
--- a/chrome/browser/glic/browser_ui/glic_border_view.h
+++ b/chrome/browser/glic/browser_ui/glic_border_view.h
@@ -57,6 +57,8 @@
   // once we set up the Skia Gold tests.
   float opacity_for_testing() const { return opacity_; }
   float emphasis_for_testing() const { return emphasis_; }
+  float progress_for_testing() const { return progress_; }
+  float GetEffectTimeForTesting() const;
 
   // Allows tests to alternate some animation APIs, for the deterministic
   // testing.
@@ -68,11 +70,10 @@
     virtual void AnimationStarted() = 0;
     virtual void EmphasisRestarted() = 0;
     virtual void RampDownStarted() = 0;
+    virtual void FocusedTabChanged(const GURL& actual_url) = 0;
   };
   void set_tester(Tester* tester) { tester_ = tester; }
 
-  float GetEffectTimeForTesting() const;
-
  private:
   void Show();
   void StopShowing();
@@ -89,9 +90,12 @@
   // Sets the necessary bits to start ramping down the opacity once it's called.
   void StartRampingDown();
 
-  // Returns the effect evolution time; wraps after a day.
+  // Returns the effect evolution time; wraps after an hour.
   float GetEffectTime() const;
 
+  // Returns a value from 0 to 1 indicating progress through the effect.
+  float GetEffectProgress(base::TimeTicks timestamp) const;
+
   // Returns the timestamp when the instance was created (but permits being
   // adjusted by the Tester).
   base::TimeTicks GetCreationTime() const;
@@ -111,6 +115,7 @@
 
   float opacity_ = 0.f;
   float emphasis_ = 0.f;
+  float progress_ = 0.f;
 
   const base::TimeTicks creation_time_;
   base::TimeTicks first_frame_time_;
diff --git a/chrome/browser/glic/browser_ui/glic_border_view_interactive_uitest.cc b/chrome/browser/glic/browser_ui/glic_border_view_interactive_uitest.cc
index 79ab658cd..c95591c 100644
--- a/chrome/browser/glic/browser_ui/glic_border_view_interactive_uitest.cc
+++ b/chrome/browser/glic/browser_ui/glic_border_view_interactive_uitest.cc
@@ -65,6 +65,12 @@
     ramp_down_started_ = true;
     wait_for_ramp_down_started_.Quit();
   }
+  void FocusedTabChanged(const GURL& actual_url) override {
+    actual_url_ = actual_url;
+    if (actual_url_ == expected_url_) {
+      wait_for_focused_tab_changed_.Quit();
+    }
+  }
 
   void WaitForAnimationStart() {
     if (animation_started_) {
@@ -87,6 +93,14 @@
     wait_for_ramp_down_started_.Run();
   }
 
+  void WaitForFocusedTabChange(const GURL& expected_url) {
+    expected_url_ = expected_url;
+    if (expected_url_ == actual_url_) {
+      return;
+    }
+    wait_for_focused_tab_changed_.Run();
+  }
+
   // Flush out the ramp down animation.
   void FinishRampDown() {
     // First call records the T0 for ramping down.
@@ -113,6 +127,10 @@
 
   bool ramp_down_started_ = false;
   base::RunLoop wait_for_ramp_down_started_;
+
+  GURL actual_url_;
+  GURL expected_url_;
+  base::RunLoop wait_for_focused_tab_changed_;
 };
 
 class GlicBorderViewUiTest : public test::InteractiveGlicTest {
@@ -233,6 +251,7 @@
   tester.AdvanceTimeAndTickAnimation(base::TimeDelta());
   EXPECT_NEAR(border->opacity_for_testing(), 0.f, kFloatComparisonTolerance);
   EXPECT_NEAR(border->emphasis_for_testing(), 0.f, kFloatComparisonTolerance);
+  EXPECT_NEAR(border->progress_for_testing(), 0.f, kFloatComparisonTolerance);
 
   // T=0.333s.
   tester.AdvanceTimeAndTickAnimation(base::Seconds(0.333));
@@ -240,6 +259,9 @@
   EXPECT_NEAR(border->opacity_for_testing(), 0.666, kFloatComparisonTolerance);
   // 0.333/0.5=0.666, 1-(1-0.666)**2~=0.888
   EXPECT_NEAR(border->emphasis_for_testing(), 0.888, kFloatComparisonTolerance);
+  // 0.333/3
+  EXPECT_NEAR(border->progress_for_testing(), 0.111f,
+              kFloatComparisonTolerance);
 
   // T=1.333s
   tester.AdvanceTimeAndTickAnimation(base::Seconds(1));
@@ -247,6 +269,9 @@
   EXPECT_NEAR(border->opacity_for_testing(), 1.f, kFloatComparisonTolerance);
   // clamped 1.333/0.5 -> 1.0, 1-(1-1.0.667)**2=1.0
   EXPECT_NEAR(border->emphasis_for_testing(), 1.f, kFloatComparisonTolerance);
+  // 1.333/3
+  EXPECT_NEAR(border->progress_for_testing(), 0.444f,
+              kFloatComparisonTolerance);
 
   // T=2.433s
   tester.AdvanceTimeAndTickAnimation(base::Seconds(1.1));
@@ -256,6 +281,8 @@
       border->emphasis_for_testing(),
       1.f - gfx::Tween::CalculateValue(gfx::Tween::Type::EASE_IN_OUT_2, 0.433),
       kFloatComparisonTolerance);
+  // 2.433/3
+  EXPECT_NEAR(border->progress_for_testing(), 0.811, kFloatComparisonTolerance);
 
   CloseGlicWindow();
   tester.WaitForRampDownStarted();
@@ -290,7 +317,7 @@
   EXPECT_FALSE(border->GetVisible());
 }
 
-// Ensures that the border animation is restarted when tab focus changes.
+// Ensures that the emphasis animation is restarted when tab focus changes.
 IN_PROC_BROWSER_TEST_F(GlicBorderViewUiTest, FocusedTabChange) {
   auto* border = browser()->window()->AsBrowserView()->glic_border();
   ASSERT_TRUE(border);
@@ -315,7 +342,7 @@
   tester.WaitForEmphasisRestarted();
 
   // Since the active tab has changed, only the emphasis animation should
-  // restart. This `OnAnimationStep()` resets the timeline of the emphasis
+  // restart. Ticking the animation resets the timeline of the emphasis
   // animation.
   tester.AdvanceTimeAndTickAnimation(base::TimeDelta());
   // Opacity isn't reset.
@@ -344,7 +371,65 @@
   EXPECT_FALSE(border->IsShowing());
 }
 
-IN_PROC_BROWSER_TEST_F(GlicBorderViewUiTest, FocusedWindowChange) {
+// Ensures that only the emphasis animation is restarted when the focused tab is
+// destroyed.
+IN_PROC_BROWSER_TEST_F(GlicBorderViewUiTest, FocusedTabDestroyed) {
+  auto* border = browser()->window()->AsBrowserView()->glic_border();
+  ASSERT_TRUE(border);
+  TesterImpl tester(border);
+
+  // Adding a new tab so the focus changes to the new tab.
+  auto new_tab_url = GURL(chrome::kChromeUINewTabURL);
+  chrome::AddTabAt(browser(), new_tab_url,
+                   /*index=*/-1, /*foreground=*/true);
+  ASSERT_EQ(2, browser()->tab_strip_model()->count());
+  ASSERT_EQ(browser()->tab_strip_model()->active_index(), 1);
+  tester.WaitForFocusedTabChange(new_tab_url);
+
+  StartBorderAnimation();
+  tester.WaitForAnimationStart();
+  EXPECT_TRUE(border->IsShowing());
+
+  // T=0s.
+  tester.AdvanceTimeAndTickAnimation(base::TimeDelta());
+
+  // T=1.333s.
+  tester.AdvanceTimeAndTickAnimation(base::Seconds(1.333));
+  EXPECT_NEAR(border->opacity_for_testing(), 1.f, kFloatComparisonTolerance);
+  EXPECT_NEAR(border->emphasis_for_testing(), 1.f, kFloatComparisonTolerance);
+
+  // Destroying the active tab.
+  chrome::CloseWebContents(browser(),
+                           browser()->tab_strip_model()->GetActiveWebContents(),
+                           /*add_to_history=*/false);
+  tester.WaitForEmphasisRestarted();
+  ASSERT_EQ(1, browser()->tab_strip_model()->count());
+  ASSERT_EQ(browser()->tab_strip_model()->active_index(), 0);
+
+  // Since the active tab is destroyed, only the emphasis animation should
+  // restart. Ticking the animation resets the timeline of the emphasis
+  // animation.
+  tester.AdvanceTimeAndTickAnimation(base::TimeDelta());
+  // Opacity isn't reset.
+  EXPECT_NEAR(border->opacity_for_testing(), 1.f, kFloatComparisonTolerance);
+  // Emphasis is reset.
+  EXPECT_NEAR(border->emphasis_for_testing(), 0.f, kFloatComparisonTolerance);
+
+  // T=1.444s. For emphasis, T=0.111s.
+  tester.AdvanceTimeAndTickAnimation(base::Seconds(0.111));
+  EXPECT_NEAR(border->opacity_for_testing(), 1.f, kFloatComparisonTolerance);
+  // 0.111/0.5=0.222, 1-(1-0.222)**2=0.394
+  EXPECT_NEAR(border->emphasis_for_testing(), 0.394, kFloatComparisonTolerance);
+
+  CloseGlicWindow();
+  tester.WaitForRampDownStarted();
+  tester.FinishRampDown();
+  EXPECT_FALSE(border->IsShowing());
+}
+
+// TODO (crbug.com/406528268): Delete or fix tests that are disabled because
+// kGlicAlwaysDetached is now default true.
+IN_PROC_BROWSER_TEST_F(GlicBorderViewUiTest, DISABLED_FocusedWindowChange) {
   auto* border = browser()->window()->AsBrowserView()->glic_border();
   ASSERT_TRUE(border);
   auto tester = std::make_unique<TesterImpl>(border);
@@ -572,17 +657,17 @@
   tester.WaitForAnimationStart();
   float seconds = border->GetEffectTimeForTesting();
 
-  tester.AdvanceTimeAndTickAnimation(base::Days(0.5));
-  float seconds_half_day = border->GetEffectTimeForTesting();
+  tester.AdvanceTimeAndTickAnimation(base::Hours(0.5));
+  float seconds_half_an_hour = border->GetEffectTimeForTesting();
 
   // Should not have wrapped.
-  EXPECT_LT(seconds, seconds_half_day);
+  EXPECT_LT(seconds, seconds_half_an_hour);
 
-  tester.AdvanceTimeAndTickAnimation(base::Days(0.5));
+  tester.AdvanceTimeAndTickAnimation(base::Hours(0.5));
 
-  // Now that more than a day has passed, we should have wrapped (and so the
-  // ms since creation should be lower than at the half-day mark).
-  EXPECT_GT(seconds_half_day, border->GetEffectTimeForTesting());
+  // Now that more than an hour has passed, we should have wrapped (and so the
+  // ms since creation should be lower than at the half-hour mark).
+  EXPECT_GT(seconds_half_an_hour, border->GetEffectTimeForTesting());
 }
 
 // Ensures that the effect time starts from where it was left off when
@@ -677,17 +762,20 @@
   // Opacity ramp up is 0.2; 0.123/0.2=0.615
   EXPECT_NEAR(border->opacity_for_testing(), 0.615, kFloatComparisonTolerance);
   EXPECT_NEAR(border->emphasis_for_testing(), 0.f, kFloatComparisonTolerance);
+  EXPECT_NEAR(border->progress_for_testing(), 0.f, kFloatComparisonTolerance);
 
   // T=0.146s.
   tester.AdvanceTimeAndTickAnimation(base::Seconds(0.023));
   // 0.146/0.2=0.73
   EXPECT_NEAR(border->opacity_for_testing(), 0.73, kFloatComparisonTolerance);
   EXPECT_NEAR(border->emphasis_for_testing(), 0.f, kFloatComparisonTolerance);
+  EXPECT_NEAR(border->progress_for_testing(), 0.f, kFloatComparisonTolerance);
 
   // T=1s.
   tester.AdvanceTimeAndTickAnimation(base::Seconds(0.854));
   EXPECT_NEAR(border->opacity_for_testing(), 1.f, kFloatComparisonTolerance);
   EXPECT_NEAR(border->emphasis_for_testing(), 0.f, kFloatComparisonTolerance);
+  EXPECT_NEAR(border->progress_for_testing(), 0.f, kFloatComparisonTolerance);
 
   CloseGlicWindow();
   tester.WaitForRampDownStarted();
@@ -697,23 +785,82 @@
   tester.AdvanceTimeAndTickAnimation(base::TimeDelta());
   EXPECT_NEAR(border->opacity_for_testing(), 1.f, kFloatComparisonTolerance);
   EXPECT_NEAR(border->emphasis_for_testing(), 0.f, kFloatComparisonTolerance);
+  EXPECT_NEAR(border->progress_for_testing(), 0.f, kFloatComparisonTolerance);
 
   // For opacity, T=0.123s.
   tester.AdvanceTimeAndTickAnimation(base::Seconds(0.123));
   // 1-(0.123/0.2)=0.385
   EXPECT_NEAR(border->opacity_for_testing(), 0.385, kFloatComparisonTolerance);
   EXPECT_NEAR(border->emphasis_for_testing(), 0.f, kFloatComparisonTolerance);
+  EXPECT_NEAR(border->progress_for_testing(), 0.f, kFloatComparisonTolerance);
 
   // T=1.134s. For opacity, T=0.134s.
   tester.AdvanceTimeAndTickAnimation(base::Seconds(0.011));
   // 1-(0.134/0.2)=0.33
   EXPECT_NEAR(border->opacity_for_testing(), 0.33, kFloatComparisonTolerance);
   EXPECT_NEAR(border->emphasis_for_testing(), 0.f, kFloatComparisonTolerance);
+  EXPECT_NEAR(border->progress_for_testing(), 0.f, kFloatComparisonTolerance);
 
   // T=2s. For opacity, T=1s.
   tester.AdvanceTimeAndTickAnimation(base::Seconds(0.866));
   EXPECT_NEAR(border->opacity_for_testing(), 0.f, kFloatComparisonTolerance);
   EXPECT_NEAR(border->emphasis_for_testing(), 0.f, kFloatComparisonTolerance);
+  EXPECT_NEAR(border->progress_for_testing(), 0.f, kFloatComparisonTolerance);
+  EXPECT_FALSE(border->IsShowing());
+}
+
+// Ensures that when PrefersReducedMotion is true and the focused tab is
+// destroyed, the border stays as is without replaying the opacity ramp
+// up animation.
+IN_PROC_BROWSER_TEST_F(GlicBorderViewPrefersReducedMotionUiTest,
+                       FocusedTabDestroyed) {
+  ASSERT_TRUE(gfx::Animation::PrefersReducedMotion());
+  auto* border = browser()->window()->AsBrowserView()->glic_border();
+  ASSERT_TRUE(border);
+  TesterImpl tester(border);
+
+  // Adding a new tab so the focus changes to the new tab.
+  auto new_tab_url = GURL(chrome::kChromeUINewTabURL);
+  chrome::AddTabAt(browser(), new_tab_url,
+                   /*index=*/-1, /*foreground=*/true);
+  ASSERT_EQ(2, browser()->tab_strip_model()->count());
+  ASSERT_EQ(browser()->tab_strip_model()->active_index(), 1);
+  tester.WaitForFocusedTabChange(new_tab_url);
+
+  StartBorderAnimation();
+  tester.WaitForAnimationStart();
+  EXPECT_TRUE(border->IsShowing());
+
+  // T=0s.
+  tester.AdvanceTimeAndTickAnimation(base::TimeDelta());
+
+  // T=1.333s.
+  tester.AdvanceTimeAndTickAnimation(base::Seconds(1.333));
+  EXPECT_NEAR(border->opacity_for_testing(), 1.f, kFloatComparisonTolerance);
+  EXPECT_NEAR(border->emphasis_for_testing(), 0.f, kFloatComparisonTolerance);
+
+  // Destroying the active tab.
+  chrome::CloseWebContents(browser(),
+                           browser()->tab_strip_model()->GetActiveWebContents(),
+                           /*add_to_history=*/false);
+  // Use the tester to wait for the UI change to populate.
+  tester.WaitForEmphasisRestarted();
+  ASSERT_EQ(1, browser()->tab_strip_model()->count());
+  ASSERT_EQ(browser()->tab_strip_model()->active_index(), 0);
+
+  // The opacity must remain unchanged and emphasis must remain 0.
+  tester.AdvanceTimeAndTickAnimation(base::TimeDelta());
+  EXPECT_NEAR(border->opacity_for_testing(), 1.f, kFloatComparisonTolerance);
+  EXPECT_NEAR(border->emphasis_for_testing(), 0.f, kFloatComparisonTolerance);
+
+  // T=1.444s.
+  tester.AdvanceTimeAndTickAnimation(base::Seconds(1.444));
+  EXPECT_NEAR(border->opacity_for_testing(), 1.f, kFloatComparisonTolerance);
+  EXPECT_NEAR(border->emphasis_for_testing(), 0.f, kFloatComparisonTolerance);
+
+  CloseGlicWindow();
+  tester.WaitForRampDownStarted();
+  tester.FinishRampDown();
   EXPECT_FALSE(border->IsShowing());
 }
 
diff --git a/chrome/browser/glic/browser_ui/glic_button_controller_unittest.cc b/chrome/browser/glic/browser_ui/glic_button_controller_unittest.cc
index 990de14..fcff96c 100644
--- a/chrome/browser/glic/browser_ui/glic_button_controller_unittest.cc
+++ b/chrome/browser/glic/browser_ui/glic_button_controller_unittest.cc
@@ -139,9 +139,11 @@
   EXPECT_FALSE(controller_delegate()->show_state());
 }
 
-// Test that when the glic window is detached, the button is shown regardless of
-// settings state.
-TEST_F(GlicButtonControllerTest, GlicDetachedOverridesSettings) {
+// TODO (crbug.com/406528268): Delete or fix tests that are disabled because
+// kGlicAlwaysDetached is now default true. detached default true. Test that
+// when the glic window is detached, the button is shown regardless of settings
+// state.
+TEST_F(GlicButtonControllerTest, DISABLED_GlicDetachedOverridesSettings) {
   PrefService* prefs = profile()->GetPrefs();
   prefs->SetInteger(
       ::prefs::kGeminiSettings,
@@ -158,9 +160,11 @@
   EXPECT_TRUE(controller_delegate()->show_state());
 }
 
-// Test the panel state of the glic window reflects the icon state
-// of the controller delegate.
-TEST_F(GlicButtonControllerTest, GlicWindowPanelState) {
+// TODO (crbug.com/406528268): Delete or fix tests that are disabled because
+// kGlicAlwaysDetached is now default true. detached default true. Test the
+// panel state of the glic window reflects the icon state of the controller
+// delegate.
+TEST_F(GlicButtonControllerTest, DISABLED_GlicWindowPanelState) {
   mojom::PanelState panel_state;
 
   panel_state.kind = mojom::PanelState_Kind::kHidden;
diff --git a/chrome/browser/glic/browser_ui/glic_tab_indicator_helper_interactive_uitest.cc b/chrome/browser/glic/browser_ui/glic_tab_indicator_helper_interactive_uitest.cc
index 206f86ca..e2a3b24 100644
--- a/chrome/browser/glic/browser_ui/glic_tab_indicator_helper_interactive_uitest.cc
+++ b/chrome/browser/glic/browser_ui/glic_tab_indicator_helper_interactive_uitest.cc
@@ -214,8 +214,11 @@
                   WaitForState(kTab2AlertState, IsNotAccessing()));
 }
 
-IN_PROC_BROWSER_TEST_F(GlicTabIndicatorHelperUiTest,
-                       DeactivatingWindowWithGlicAttachedShouldNotAlert) {
+// TODO (crbug.com/406528268): Delete or fix tests that are disabled because
+// kGlicAlwaysDetached is now default true.
+IN_PROC_BROWSER_TEST_F(
+    GlicTabIndicatorHelperUiTest,
+    DISABLED_DeactivatingWindowWithGlicAttachedShouldNotAlert) {
 #if BUILDFLAG(IS_LINUX)
   if (views::test::InteractionTestUtilSimulatorViews::IsWayland()) {
     GTEST_SKIP()
@@ -264,16 +267,11 @@
                   WaitForState(kTab1AlertState, IsAccessing()));
 }
 
-// TODO(crbug.com/404281597): Re-enable this test.
-#if BUILDFLAG(IS_LINUX)
-#define MAYBE_MinimizingWindowWithGlicAttachedShouldNotAlert \
-  DISABLED_MinimizingWindowWithGlicAttachedShouldNotAlert
-#else
-#define MAYBE_MinimizingWindowWithGlicAttachedShouldNotAlert \
-  MinimizingWindowWithGlicAttachedShouldNotAlert
-#endif  // BUILDFLAG(IS_LINUX)
-IN_PROC_BROWSER_TEST_F(GlicTabIndicatorHelperUiTest,
-                       MAYBE_MinimizingWindowWithGlicAttachedShouldNotAlert) {
+// TODO (crbug.com/406528268): Delete or fix tests that are disabled because
+// kGlicAlwaysDetached is now default true.
+IN_PROC_BROWSER_TEST_F(
+    GlicTabIndicatorHelperUiTest,
+    DISABLED_MinimizingWindowWithGlicAttachedShouldNotAlert) {
   Browser* const browser2 = CreateBrowser(browser()->profile());
   RunTestSequence(LoadStartingPage(), OpenGlicWindow(GlicWindowMode::kAttached),
                   ObserveState(kTab1AlertState, browser(), 0),
diff --git a/chrome/browser/glic/glic_policy_browsertest.cc b/chrome/browser/glic/glic_policy_browsertest.cc
index a89697a9..4f1201cb 100644
--- a/chrome/browser/glic/glic_policy_browsertest.cc
+++ b/chrome/browser/glic/glic_policy_browsertest.cc
@@ -496,12 +496,11 @@
 
   // Show the panel as if the glic button was clicked.
   {
-    base::test::TestFuture<void> wait_for_panel_attached;
+    base::test::TestFuture<void> wait_for_panel;
     PanelStateObserver panel_state_observer(
-        mojom::PanelState::Kind::kAttached,
-        wait_for_panel_attached.GetCallback());
+        mojom::PanelState::Kind::kDetached,
+        wait_for_panel.GetCallback());
     service->window_controller().AddStateObserver(&panel_state_observer);
-
     BrowserWindowInterface* bwi = browser()
                                       ->window()
                                       ->AsBrowserView()
@@ -511,7 +510,7 @@
     service->ToggleUI(bwi, /*prevent_close=*/false,
                       mojom::InvocationSource::kOsButton);
 
-    EXPECT_TRUE(wait_for_panel_attached.Wait());
+    EXPECT_TRUE(wait_for_panel.Wait());
     service->window_controller().RemoveStateObserver(&panel_state_observer);
   }
 
diff --git a/chrome/browser/glic/host/context/glic_screenshot_capturer.cc b/chrome/browser/glic/host/context/glic_screenshot_capturer.cc
index c00578b..388e151 100644
--- a/chrome/browser/glic/host/context/glic_screenshot_capturer.cc
+++ b/chrome/browser/glic/host/context/glic_screenshot_capturer.cc
@@ -8,6 +8,7 @@
 #include <utility>
 
 #include "base/logging.h"
+#include "base/task/single_thread_task_runner.h"
 #include "base/task/thread_pool.h"
 #include "base/time/time.h"
 #include "chrome/browser/glic/resources/grit/glic_browser_resources.h"
@@ -123,6 +124,20 @@
                     kUserCancelledScreenPickerDialog);
     return;
   }
+#if BUILDFLAG(IS_WIN)
+  // TODO(crbug.com/405177421): Remove this delay once we land the code to skip
+  // the screen picker entirely on Windows.
+  base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
+      FROM_HERE,
+      base::BindOnce(&GlicScreenshotCapturer::OnCaptureStarted,
+                     weak_ptr_factory_.GetWeakPtr(), id),
+      base::Milliseconds(500));
+#else
+  OnCaptureStarted(id);
+#endif
+}
+
+void GlicScreenshotCapturer::OnCaptureStarted(content::DesktopMediaID id) {
   desktop_capturer_ = content::desktop_capture::CreateScreenCapturer();
   desktop_capturer_->Start(this);
   if (!desktop_capturer_->SelectSource(id.id)) {
diff --git a/chrome/browser/glic/host/context/glic_screenshot_capturer.h b/chrome/browser/glic/host/context/glic_screenshot_capturer.h
index 23e0749..0a09edb9 100644
--- a/chrome/browser/glic/host/context/glic_screenshot_capturer.h
+++ b/chrome/browser/glic/host/context/glic_screenshot_capturer.h
@@ -45,6 +45,9 @@
   // Callback triggered when user selects a source to capture.
   void OnSourceSelected(const std::string& err, content::DesktopMediaID id);
 
+  // Called to start the actual screen capture.
+  void OnCaptureStarted(content::DesktopMediaID id);
+
   // webrtc::DesktopCapturer::Callback:
   void OnCaptureResult(webrtc::DesktopCapturer::Result result,
                        std::unique_ptr<webrtc::DesktopFrame> frame) override;
diff --git a/chrome/browser/glic/host/glic_actor_controller_interactive_uitest.cc b/chrome/browser/glic/host/glic_actor_controller_interactive_uitest.cc
index 104e376..ae58e2c 100644
--- a/chrome/browser/glic/host/glic_actor_controller_interactive_uitest.cc
+++ b/chrome/browser/glic/host/glic_actor_controller_interactive_uitest.cc
@@ -7,6 +7,7 @@
 
 #include "base/base64.h"
 #include "base/test/scoped_feature_list.h"
+#include "chrome/browser/actor/actor_test_util.h"
 #include "chrome/browser/glic/host/glic.mojom-shared.h"
 #include "chrome/browser/glic/test_support/interactive_glic_test.h"
 #include "chrome/browser/glic/test_support/interactive_test_util.h"
@@ -25,18 +26,11 @@
 using optimization_guide::proto::BrowserAction;
 using optimization_guide::proto::ClickAction;
 
-DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kActiveTabId);
+// TODO(https://crbug.com/402086021): Get the actual target details for the
+// button in the test page.
+constexpr int32_t kContentNodeId = 123;
 
-BrowserAction GetClickButtonAction() {
-  BrowserAction action;
-  ClickAction* click = action.add_action_information()->mutable_click();
-  // TODO(https://crbug.com/402086021): Get the actual target details for the
-  // button in the test page.
-  click->mutable_target()->set_content_node_id(123);
-  click->set_click_type(ClickAction::LEFT);
-  click->set_click_count(ClickAction::SINGLE);
-  return action;
-}
+DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kActiveTabId);
 
 class GlicActorControllerUiTest : public test::InteractiveGlicTest {
  public:
@@ -131,8 +125,11 @@
   base::test::ScopedFeatureList scoped_feature_list_;
 };
 
-IN_PROC_BROWSER_TEST_F(GlicActorControllerUiTest, ActionSucceeds) {
-  std::string encodedProto = EncodeActionProto(GetClickButtonAction());
+// TODO(https://crbug.com/402086021): Enable test after using real nodeId in
+// proto.
+IN_PROC_BROWSER_TEST_F(GlicActorControllerUiTest, DISABLED_ActionSucceeds) {
+  std::string encodedProto =
+      EncodeActionProto(actor::MakeClick(kContentNodeId));
   RunTestSequence(InstrumentTab(kActiveTabId),
                   NavigateWebContents(kActiveTabId,
                                       embedded_test_server()->GetURL(
@@ -161,7 +158,8 @@
 // calling to do the action.
 IN_PROC_BROWSER_TEST_F(GlicActorControllerUiTest,
                        DISABLED_ActionTargetNotFound) {
-  std::string encodedProto = EncodeActionProto(GetClickButtonAction());
+  std::string encodedProto =
+      EncodeActionProto(actor::MakeClick(kContentNodeId));
   RunTestSequence(
       InstrumentTab(kActiveTabId),
       NavigateWebContents(
diff --git a/chrome/browser/glic/host/glic_annotation_manager_interactive_uitest.cc b/chrome/browser/glic/host/glic_annotation_manager_interactive_uitest.cc
index 778e61c..1a81ff2 100644
--- a/chrome/browser/glic/host/glic_annotation_manager_interactive_uitest.cc
+++ b/chrome/browser/glic/host/glic_annotation_manager_interactive_uitest.cc
@@ -416,9 +416,11 @@
           glic::mojom::ScrollToErrorReason::kFocusedTabChangedOrNavigated));
 }
 
+// TODO (crbug.com/406528268): Delete or fix tests that are disabled because
+// kGlicAlwaysDetached is now default true.
 // Opens a new window while the GlicWindow is attached to the previous window.
 // This results in no tab being considered as focused by Glic.
-IN_PROC_BROWSER_TEST_F(GlicAnnotationManagerUiTest, NoFocusedTab) {
+IN_PROC_BROWSER_TEST_F(GlicAnnotationManagerUiTest, DISABLED_NoFocusedTab) {
   RunTestSequence(
       OpenGlicWindow(GlicWindowMode::kAttached),  //
       InsertFakeAnnotationService(),              //
diff --git a/chrome/browser/glic/host/glic_api_uitest.cc b/chrome/browser/glic/host/glic_api_uitest.cc
index 16cbe0fa..7492e39 100644
--- a/chrome/browser/glic/host/glic_api_uitest.cc
+++ b/chrome/browser/glic/host/glic_api_uitest.cc
@@ -139,7 +139,7 @@
         InProcessBrowserTest::embedded_test_server()->GetURL("/glic/test.html");
     RunTestSequence(InstrumentTab(kFirstTab),
                     NavigateWebContents(kFirstTab, page_url),
-                    OpenGlicWindow(GlicWindowMode::kAttached,
+                    OpenGlicWindow(GlicWindowMode::kDetached,
                                    GlicInstrumentMode::kHostAndContents));
   }
 };
@@ -202,20 +202,26 @@
   RunTestSequence(WaitForHide(kGlicViewElementId));
 }
 
-IN_PROC_BROWSER_TEST_F(GlicApiTest, testAttachPanel) {
+// TODO (crbug.com/406528268): Delete or fix tests that are disabled because
+// kGlicAlwaysDetached is now default true.
+IN_PROC_BROWSER_TEST_F(GlicApiTest, DISABLED_testAttachPanel) {
   RunTestSequence(OpenGlicWindow(GlicWindowMode::kDetached,
                                  GlicInstrumentMode::kHostAndContents));
   ExecuteJsTest();
   RunTestSequence(CheckControllerWidgetMode(GlicWindowMode::kAttached));
 }
 
-IN_PROC_BROWSER_TEST_F(GlicApiTest, testUnsubscribeFromObservable) {
+// TODO (crbug.com/406528268): Delete or fix tests that are disabled because
+// kGlicAlwaysDetached is now default true.
+IN_PROC_BROWSER_TEST_F(GlicApiTest, DISABLED_testUnsubscribeFromObservable) {
   RunTestSequence(OpenGlicWindow(GlicWindowMode::kDetached,
                                  GlicInstrumentMode::kHostAndContents));
   ExecuteJsTest();
 }
 
-IN_PROC_BROWSER_TEST_F(GlicApiTestWithOneTab, testDetachPanel) {
+// TODO (crbug.com/406528268): Delete or fix tests that are disabled because
+// kGlicAlwaysDetached is now default true.
+IN_PROC_BROWSER_TEST_F(GlicApiTestWithOneTab, DISABLED_testDetachPanel) {
   ExecuteJsTest();
   RunTestSequence(CheckControllerWidgetMode(GlicWindowMode::kDetached));
 }
@@ -243,7 +249,9 @@
   ContinueJsTest();
 }
 
-IN_PROC_BROWSER_TEST_F(GlicApiTest, testCanAttachPanel) {
+// TODO (crbug.com/406528268): Delete or fix tests that are disabled because
+// kGlicAlwaysDetached is now default true.
+IN_PROC_BROWSER_TEST_F(GlicApiTest, DISABLED_testCanAttachPanel) {
   RunTestSequence(OpenGlicWindow(GlicWindowMode::kDetached,
                                  GlicInstrumentMode::kHostAndContents));
   ExecuteJsTest();
diff --git a/chrome/browser/glic/host/glic_ui_interactive_uitest.cc b/chrome/browser/glic/host/glic_ui_interactive_uitest.cc
index f495bf7f9..00ca739 100644
--- a/chrome/browser/glic/host/glic_ui_interactive_uitest.cc
+++ b/chrome/browser/glic/host/glic_ui_interactive_uitest.cc
@@ -11,9 +11,13 @@
 #include "chrome/browser/glic/host/glic.mojom-shared.h"
 #include "chrome/browser/glic/host/glic_ui.h"
 #include "chrome/browser/glic/test_support/interactive_glic_test.h"
+#include "chrome/browser/glic/test_support/interactive_test_util.h"
 #include "chrome/browser/glic/widget/glic_window_controller.h"
+#include "chrome/browser/ui/browser_element_identifiers.h"
 #include "chrome/common/chrome_features.h"
 #include "content/public/test/browser_test.h"
+#include "ui/base/accelerators/accelerator.h"
+#include "ui/base/interaction/element_identifier.h"
 #include "ui/base/interaction/state_observer.h"
 
 namespace glic {
@@ -97,6 +101,9 @@
     return params;
   }
 };
+
+const ui::Accelerator escape_key(ui::VKEY_ESCAPE, ui::EF_NONE);
+
 }  // namespace
 
 // Base class that sets up network connection mode and timeouts based on
@@ -139,6 +146,15 @@
     return steps;
   }
 
+  auto CheckEscapeKeyDismisses(const DeepQuery& panel) {
+    return InAnyContext(
+        WaitForShow(test::kGlicHostElementId), CheckElementVisible(panel, true),
+        InSameContext(SendAccelerator(test::kGlicHostElementId, escape_key)
+                          .SetMustRemainVisible(false),
+                      WaitForHide(kGlicViewElementId)),
+        CheckControllerHasWidget(false));
+  }
+
   auto ChangeConnectionState(bool online) {
     return ExecuteJs(test::kGlicHostElementId,
                      base::StringPrintf(R"(
@@ -432,4 +448,40 @@
       CheckElementVisible(kLoadingPanel, true));
 }
 
+// Test that the escape key can be used to dismiss the floaty window in various
+// loading and error states.
+
+IN_PROC_BROWSER_TEST_F(GlicUiDisconnectedUiTest, EscapeKeyDismisses) {
+  RunTestSequence(
+      ObserveState(kGlicUiStateHistory, &window_controller()),
+      OpenGlicWindow(GlicWindowMode::kAttached, GlicInstrumentMode::kHostOnly),
+      WaitForState(kGlicUiStateHistory, IsCurrently(WebUiState::kOffline)),
+      CheckEscapeKeyDismisses(kOfflinePanel));
+}
+
+IN_PROC_BROWSER_TEST_F(GlicUiLoadingPanelWaitingTest, EscapeKeyDismisses) {
+  RunTestSequence(
+      ObserveState(kGlicUiStateHistory, &window_controller()),
+      OpenGlicWindow(GlicWindowMode::kAttached, GlicInstrumentMode::kHostOnly),
+      WaitForState(kGlicUiStateHistory,
+                   IsCurrently(WebUiState::kFinishLoading)),
+      CheckEscapeKeyDismisses(kLoadingPanel));
+}
+
+IN_PROC_BROWSER_TEST_F(GlicUiLoadingPanelHoldingTest, EscapeKeyDismisses) {
+  RunTestSequence(
+      ObserveState(kGlicUiStateHistory, &window_controller()),
+      OpenGlicWindow(GlicWindowMode::kAttached, GlicInstrumentMode::kHostOnly),
+      WaitForState(kGlicUiStateHistory, IsCurrently(WebUiState::kHoldLoading)),
+      CheckEscapeKeyDismisses(kLoadingPanel));
+}
+
+IN_PROC_BROWSER_TEST_F(GlicUiFullLoadingSequenceTest, EscapeKeyDismisses) {
+  RunTestSequence(
+      ObserveState(kGlicUiStateHistory, &window_controller()),
+      OpenGlicWindow(GlicWindowMode::kAttached, GlicInstrumentMode::kHostOnly),
+      WaitForState(kGlicUiStateHistory, IsCurrently(WebUiState::kError)),
+      CheckEscapeKeyDismisses(kErrorPanel));
+}
+
 }  // namespace glic
diff --git a/chrome/browser/glic/resources/border.sksl b/chrome/browser/glic/resources/border.sksl
index 4ed1b86e..5d94f0f 100644
--- a/chrome/browser/glic/resources/border.sksl
+++ b/chrome/browser/glic/resources/border.sksl
@@ -12,6 +12,7 @@
 uniform float u_time;
 uniform int u_dark;
 uniform float u_emphasis;
+uniform float u_progress;
 uniform float u_corner_radius;
 uniform float u_insets;
 uniform float2 u_resolution;
diff --git a/chrome/browser/glic/resources/browser_resources.grd b/chrome/browser/glic/resources/browser_resources.grd
index f43fc77..a55b257 100644
--- a/chrome/browser/glic/resources/browser_resources.grd
+++ b/chrome/browser/glic/resources/browser_resources.grd
@@ -259,13 +259,28 @@
       </message>
 
       <!-- Screenpicker Menu String -->
-      <message name="IDS_GLIC_SCREEN_PICKER_REQUESTER" desc="Text shown on screen picker dialog that indicates the requesting source is Glic." translateable="false">Glic</message>
-      <message name="IDS_GLIC_SCREEN_PICKER_HEADLINE" desc="Title of the Glic screen picker dialog used for specifying which screen to capture." translateable="false">Choose a screen to capture</message>
-      <message name="IDS_GLIC_SCREEN_PICKER_DESCRIPTION" desc="Text that shows on the Glic screen picker dialog to indicate what happens with the capture." translateable="false">Your screenshot can be used with your prompt or deleted</message>
-      <message name="IDS_GLIC_SCREEN_PICKER_CTA" desc="Text for call to action button on Glic screen picker dialog. " translateable="false">Take a screenshot</message>
+      <message name="IDS_GLIC_SCREEN_PICKER_REQUESTER" desc="Text shown on screen picker dialog that indicates the requesting source is Glic." translateable="false">
+        Glic
+      </message>
+      <message name="IDS_GLIC_SCREEN_PICKER_HEADLINE" desc="Title of the Glic screen picker dialog used for specifying which screen to capture." translateable="false">
+        Choose a screen to capture
+      </message>
+      <message name="IDS_GLIC_SCREEN_PICKER_DESCRIPTION" desc="Text that shows on the Glic screen picker dialog to indicate what happens with the capture." translateable="false">
+        Your screenshot can be used with your prompt or deleted
+      </message>
+      <message name="IDS_GLIC_SCREEN_PICKER_CTA" desc="Text for call to action button on Glic screen picker dialog. " translateable="false">
+        Take a screenshot
+      </message>
 
       <!-- Glic Window Title -->
-      <message name="IDS_GLIC_WINDOW_TITLE" desc="Accessible title for the Glic Window." translateable="false">Glic</message>
+      <message name="IDS_GLIC_WINDOW_TITLE" desc="Accessible title for the Glic Window." translateable="false">
+        Glic
+      </message>
+
+      <!-- Task Manager -->
+      <message name="IDS_TASK_MANAGER_GLIC_RENDERER_PREFIX" desc="Task Manager row for a render process used to host the Glic UI" translateable="false">
+        Glic Host Renderer
+      </message>
     </messages>
   </release>
 </grit>
diff --git a/chrome/browser/glic/resources/internal b/chrome/browser/glic/resources/internal
index b9ab497..5d196ac 160000
--- a/chrome/browser/glic/resources/internal
+++ b/chrome/browser/glic/resources/internal
@@ -1 +1 @@
-Subproject commit b9ab4971feac2af59d5448e0dbbfe20c4965391f
+Subproject commit 5d196ac2e495e1714ae22c4eb6507db40367ee23
diff --git a/chrome/browser/glic/widget/glic_window_controller_interactive_uitest.cc b/chrome/browser/glic/widget/glic_window_controller_interactive_uitest.cc
index 0d153bd..4afee8a 100644
--- a/chrome/browser/glic/widget/glic_window_controller_interactive_uitest.cc
+++ b/chrome/browser/glic/widget/glic_window_controller_interactive_uitest.cc
@@ -90,8 +90,11 @@
       std::make_unique<GlicController>();
 };
 
+// TODO (crbug.com/406528268): Delete or fix tests that are disabled because
+// kGlicAlwaysDetached is now default true.
 // TODO(394945970): Check top right corner position.
-IN_PROC_BROWSER_TEST_F(GlicWindowControllerUiTest, ShowAndCloseAttachedWidget) {
+IN_PROC_BROWSER_TEST_F(GlicWindowControllerUiTest,
+                       DISABLED_ShowAndCloseAttachedWidget) {
   RunTestSequence(OpenGlicWindow(GlicWindowMode::kAttached),
                   // Verify glic is open in attached mode.
                   CheckControllerHasWidget(true),
@@ -117,8 +120,11 @@
                   OpenGlicWindow(GlicWindowMode::kAttached));
 }
 
-IN_PROC_BROWSER_TEST_F(GlicWindowControllerUiTest,
-                       OpenAttachedThenOpenAttachedToSameBrowserCloses) {
+// TODO (crbug.com/406528268): Delete or fix tests that are disabled because
+// kGlicAlwaysDetached is now default true.
+IN_PROC_BROWSER_TEST_F(
+    GlicWindowControllerUiTest,
+    DISABLED_OpenAttachedThenOpenAttachedToSameBrowserCloses) {
   RunTestSequence(OpenGlicWindow(GlicWindowMode::kAttached),
                   CheckControllerHasWidget(true),
                   CheckControllerWidgetMode(GlicWindowMode::kAttached),
@@ -127,8 +133,11 @@
                   CheckControllerHasWidget(false));
 }
 
-IN_PROC_BROWSER_TEST_F(GlicWindowControllerUiTest,
-                       OpenAttachedThenOpenAttachedToDifferentBrowser) {
+// TODO (crbug.com/406528268): Delete or fix tests that are disabled because
+// kGlicAlwaysDetached is now default true.
+IN_PROC_BROWSER_TEST_F(
+    GlicWindowControllerUiTest,
+    DISABLED_OpenAttachedThenOpenAttachedToDifferentBrowser) {
   Browser* const new_browser = CreateBrowser(browser()->profile());
 
   RunTestSequence(OpenGlicWindow(GlicWindowMode::kAttached),
@@ -140,10 +149,12 @@
                   CheckIfAttachedToBrowser(new_browser));
 }
 
+// TODO (crbug.com/406528268): Delete or fix tests that are disabled because
+// kGlicAlwaysDetached is now default true.
 #if !BUILDFLAG(IS_LINUX)
 IN_PROC_BROWSER_TEST_F(
     GlicWindowControllerUiTest,
-    OpenAttachedThenOpenAttachedToDifferentBrowserWithHotkey) {
+    DISABLED_OpenAttachedThenOpenAttachedToDifferentBrowserWithHotkey) {
   Browser* const new_browser = CreateBrowser(browser()->profile());
 
   RunTestSequence(OpenGlicWindow(GlicWindowMode::kAttached),
@@ -186,8 +197,10 @@
       CheckControllerHasWidget(false));
 }
 
+// TODO (crbug.com/406528268): Delete or fix tests that are disabled because
+// kGlicAlwaysDetached is now default true.
 IN_PROC_BROWSER_TEST_F(GlicWindowControllerUiTest,
-                       HotkeyWhenAttachedToActiveBrowserCloses) {
+                       DISABLED_HotkeyWhenAttachedToActiveBrowserCloses) {
   RunTestSequence(
       OpenGlicWindow(GlicWindowMode::kAttached),
       // Glic should close.
@@ -209,8 +222,10 @@
       CheckControllerHasWidget(false));
 }
 
+// TODO (crbug.com/406528268): Delete or fix tests that are disabled because
+// kGlicAlwaysDetached is now default true.
 IN_PROC_BROWSER_TEST_F(GlicWindowControllerUiTest,
-                       HotkeyAttachesToActiveBrowser) {
+                       DISABLED_HotkeyAttachesToActiveBrowser) {
   RunTestSequence(
       // Glic should open attached to active browser.
       SetOnIncompatibleAction(OnIncompatibleAction::kSkipTest,
@@ -253,12 +268,13 @@
 }
 
 #if !BUILDFLAG(IS_LINUX)
-// Widget activation doesn't work on Linux; see
-// InteractionTestUtilSimulatorViews::ActivateWidget.
+// TODO (crbug.com/406528268): Delete or fix tests that are disabled because
+// kGlicAlwaysDetached is now default true. Widget activation doesn't work on
+// Linux; see InteractionTestUtilSimulatorViews::ActivateWidget.
 IN_PROC_BROWSER_TEST_F(GlicWindowControllerUiTest,
-                       CanFocusGlicWindowWithFocusDialogHotkey) {
+                       DISABLED_CanFocusGlicWindowWithFocusDialogHotkey) {
   RunTestSequence(
-      OpenGlicWindow(GlicWindowMode::kAttached),
+      OpenGlicWindow(GlicWindowMode::kDetached),
       ActivateSurface(kBrowserViewElementId),
       // Activating the browser actually focuses the omnibox.
       CheckViewProperty(kOmniboxElementId, &views::View::HasFocus, true),
@@ -321,7 +337,9 @@
 // that invoking the hotkey while open detached always closes glic regardless of
 // activation.
 
-IN_PROC_BROWSER_TEST_F(GlicWindowControllerUiTest, ApiDetach) {
+// TODO (crbug.com/406528268): Delete or fix tests that are disabled because
+// kGlicAlwaysDetached is now default true.
+IN_PROC_BROWSER_TEST_F(GlicWindowControllerUiTest, DISABLED_ApiDetach) {
   base::HistogramTester tester;
   RunTestSequence(
       // Open attached.
@@ -372,7 +390,7 @@
   RunTestSequence(SimulateOpenMenuItem(),
                   WaitForAndInstrumentGlic(kHostAndContents),
                   CheckControllerHasWidget(true),
-                  CheckControllerWidgetMode(GlicWindowMode::kAttached),
+                  CheckControllerWidgetMode(GlicWindowMode::kDetached),
                   CloseGlicWindow(), CheckControllerHasWidget(false));
 }
 
@@ -431,8 +449,10 @@
       WaitForState(test::internal::kGlicAppState, mojom::WebUiState::kError));
 }
 
+// TODO (crbug.com/406528268): Delete or fix tests that are disabled because
+// kGlicAlwaysDetached is now default true.
 IN_PROC_BROWSER_TEST_F(GlicWindowControllerUiTest,
-                       AttachedWidgetOpensAfterReauth) {
+                       DISABLED_AttachedWidgetOpensAfterReauth) {
   RunTestSequence(
       ForceInvalidateAccount(), PressButton(kGlicButtonElementId),
       ObserveState(test::internal::kLogInAndOpenState,
diff --git a/chrome/browser/language_detection/language_detection_model_service_browsertest.cc b/chrome/browser/language_detection/language_detection_model_service_browsertest.cc
index 503a2e5a..7a481bc 100644
--- a/chrome/browser/language_detection/language_detection_model_service_browsertest.cc
+++ b/chrome/browser/language_detection/language_detection_model_service_browsertest.cc
@@ -114,7 +114,7 @@
                      base::StringPrintf(R"(
         (async () => {
             try {
-            return await ai.languageDetector.availability();
+            return await LanguageDetector.availability();
             } catch (e) {
             return e.toString();
             }
diff --git a/chrome/browser/magic_stack/android/java/src/org/chromium/chrome/browser/magic_stack/HomeModulesConfigManager.java b/chrome/browser/magic_stack/android/java/src/org/chromium/chrome/browser/magic_stack/HomeModulesConfigManager.java
index d05d2d5c..acbaf7e 100644
--- a/chrome/browser/magic_stack/android/java/src/org/chromium/chrome/browser/magic_stack/HomeModulesConfigManager.java
+++ b/chrome/browser/magic_stack/android/java/src/org/chromium/chrome/browser/magic_stack/HomeModulesConfigManager.java
@@ -119,7 +119,7 @@
      * @param moduleType {@link ModuleType} needed to be notified to the listeners.
      * @param enabled True is the module type is enabled.
      */
-    void setPrefModuleTypeEnabled(@ModuleType int moduleType, boolean enabled) {
+    public void setPrefModuleTypeEnabled(@ModuleType int moduleType, boolean enabled) {
         mSharedPreferencesManager.writeBoolean(getSettingsPreferenceKey(moduleType), enabled);
         notifyModuleTypeUpdated(moduleType, enabled);
     }
diff --git a/chrome/browser/net/system_network_context_manager_browsertest.cc b/chrome/browser/net/system_network_context_manager_browsertest.cc
index 73f8eead..ed4df246 100644
--- a/chrome/browser/net/system_network_context_manager_browsertest.cc
+++ b/chrome/browser/net/system_network_context_manager_browsertest.cc
@@ -648,7 +648,7 @@
               /*warnings=*/{net::CookieInclusionStatus::WarningReason::
                                 WARN_PORT_MISMATCH}),
           net::CookieAccessSemantics::NONLEGACY,
-          net::CookieScopeSemantics::UNKNOWN, true)};
+          net::CookieScopeSemantics::NONLEGACY, true)};
   EXPECT_THAT(cookie_tracker.cookie_accesses(),
               testing::ElementsAre(
                   // a.test/title1.html
@@ -677,7 +677,7 @@
       net::CookieAccessResult(net::CookieEffectiveSameSite::NO_RESTRICTION,
                               expected_third_party_inclusion_status,
                               net::CookieAccessSemantics::NONLEGACY,
-                              net::CookieScopeSemantics::UNKNOWN, true)};
+                              net::CookieScopeSemantics::NONLEGACY, true)};
   EXPECT_THAT(cookie_tracker.cookie_accesses(),
               testing::ElementsAre(
                   // a.test iframe under b.test
diff --git a/chrome/browser/new_tab_page/modules/file_suggestion/drive_service.cc b/chrome/browser/new_tab_page/modules/file_suggestion/drive_service.cc
index ae327e8..844a85b 100644
--- a/chrome/browser/new_tab_page/modules/file_suggestion/drive_service.cc
+++ b/chrome/browser/new_tab_page/modules/file_suggestion/drive_service.cc
@@ -326,7 +326,10 @@
       base::BindOnce(&DriveService::OnTokenReceived,
                      weak_factory_.GetWeakPtr()),
       signin::PrimaryAccountAccessTokenFetcher::Mode::kImmediate,
-      signin::ConsentLevel::kSync);
+      base::FeatureList::IsEnabled(
+          ntp_features::kNtpDriveModuleNoSyncRequirement)
+          ? signin::ConsentLevel::kSignin
+          : signin::ConsentLevel::kSync);
 }
 
 void DriveService::DismissModule() {
diff --git a/chrome/browser/ntp_customization/BUILD.gn b/chrome/browser/ntp_customization/BUILD.gn
index 74279a8..95caaea0 100644
--- a/chrome/browser/ntp_customization/BUILD.gn
+++ b/chrome/browser/ntp_customization/BUILD.gn
@@ -7,19 +7,28 @@
 android_library("java") {
   sources = [
     "java/src/org/chromium/chrome/browser/ntp_customization/BottomSheetDelegate.java",
-    "java/src/org/chromium/chrome/browser/ntp_customization/NtpCardsContainerView.java",
+    "java/src/org/chromium/chrome/browser/ntp_customization/BottomSheetListContainerView.java",
+    "java/src/org/chromium/chrome/browser/ntp_customization/BottomSheetListContainerViewBinder.java",
+    "java/src/org/chromium/chrome/browser/ntp_customization/BottomSheetListItemView.java",
+    "java/src/org/chromium/chrome/browser/ntp_customization/BottomSheetViewBinder.java",
+    "java/src/org/chromium/chrome/browser/ntp_customization/ListContainerViewDelegate.java",
     "java/src/org/chromium/chrome/browser/ntp_customization/NtpCardsCoordinator.java",
+    "java/src/org/chromium/chrome/browser/ntp_customization/NtpCardsListContainerView.java",
     "java/src/org/chromium/chrome/browser/ntp_customization/NtpCardsListItemView.java",
+    "java/src/org/chromium/chrome/browser/ntp_customization/NtpCardsMediator.java",
     "java/src/org/chromium/chrome/browser/ntp_customization/NtpCustomizationBottomSheetContent.java",
     "java/src/org/chromium/chrome/browser/ntp_customization/NtpCustomizationCoordinator.java",
     "java/src/org/chromium/chrome/browser/ntp_customization/NtpCustomizationMediator.java",
-    "java/src/org/chromium/chrome/browser/ntp_customization/NtpCustomizationViewBinder.java",
+    "java/src/org/chromium/chrome/browser/ntp_customization/NtpCustomizationUtils.java",
     "java/src/org/chromium/chrome/browser/ntp_customization/NtpCustomizationViewProperties.java",
   ]
   deps = [
     ":java_resources",
+    "//base:log_java",
     "//base:supplier_java",
+    "//chrome/browser/feed/android:java",
     "//chrome/browser/magic_stack/android:java",
+    "//chrome/browser/profiles/android:java",
     "//components/browser_ui/bottomsheet/android:java",
     "//components/browser_ui/widget/android:java",
     "//third_party/android_deps:com_android_support_support_annotations_java",
@@ -40,6 +49,7 @@
     "java/res/drawable/ntp_customization_bottom_sheet_list_item_background_middle.xml",
     "java/res/drawable/ntp_customization_bottom_sheet_list_item_background_single.xml",
     "java/res/drawable/ntp_customization_bottom_sheet_list_item_background_top.xml",
+    "java/res/layout/bottom_sheet_list_item_view.xml",
     "java/res/layout/ntp_customization_bottom_sheet.xml",
     "java/res/layout/ntp_customization_main_bottom_sheet.xml",
     "java/res/layout/ntp_customization_ntp_cards_bottom_sheet.xml",
@@ -59,12 +69,18 @@
   resources_package = "org.chromium.chrome.browser.ntp_customization"
 
   sources = [
-    "junit/src/org/chromium/chrome/browser/ntp_customization/NtpCardsContainerViewUnitTest.java",
+    "junit/src/org/chromium/chrome/browser/ntp_customization/BottomSheetListContainerViewBinderUnitTest.java",
+    "junit/src/org/chromium/chrome/browser/ntp_customization/BottomSheetListContainerViewUnitTest.java",
+    "junit/src/org/chromium/chrome/browser/ntp_customization/BottomSheetListItemViewUnitTest.java",
+    "junit/src/org/chromium/chrome/browser/ntp_customization/BottomSheetViewBinderUnitTest.java",
     "junit/src/org/chromium/chrome/browser/ntp_customization/NtpCardsCoordinatorUnitTest.java",
+    "junit/src/org/chromium/chrome/browser/ntp_customization/NtpCardsListContainerViewUnitTest.java",
+    "junit/src/org/chromium/chrome/browser/ntp_customization/NtpCardsListItemViewUnitTest.java",
+    "junit/src/org/chromium/chrome/browser/ntp_customization/NtpCardsMediatorUnitTest.java",
     "junit/src/org/chromium/chrome/browser/ntp_customization/NtpCustomizationBottomSheetContentUnitTest.java",
     "junit/src/org/chromium/chrome/browser/ntp_customization/NtpCustomizationCoordinatorUnitTest.java",
     "junit/src/org/chromium/chrome/browser/ntp_customization/NtpCustomizationMediatorUnitTest.java",
-    "junit/src/org/chromium/chrome/browser/ntp_customization/NtpCustomizationViewBinderUnitTest.java",
+    "junit/src/org/chromium/chrome/browser/ntp_customization/NtpCustomizationUtilsUnitTest.java",
   ]
   deps = [
     ":java",
@@ -73,8 +89,11 @@
     "//base:base_java_test_support",
     "//base:base_junit_test_support",
     "//chrome/android:chrome_java",
+    "//chrome/browser/magic_stack/android:java",
     "//components/browser_ui/bottomsheet/android:java",
     "//components/browser_ui/theme/android:java_resources",
+    "//components/browser_ui/widget/android:java",
+    "//third_party/androidx:androidx_core_core_java",
     "//third_party/androidx:androidx_test_core_java",
     "//third_party/androidx:androidx_test_runner_java",
     "//third_party/junit:junit",
diff --git a/chrome/browser/ntp_customization/java/res/layout/bottom_sheet_list_item_view.xml b/chrome/browser/ntp_customization/java/res/layout/bottom_sheet_list_item_view.xml
new file mode 100644
index 0000000..12a8331
--- /dev/null
+++ b/chrome/browser/ntp_customization/java/res/layout/bottom_sheet_list_item_view.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright 2025 The Chromium Authors.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<org.chromium.chrome.browser.ntp_customization.BottomSheetListItemView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_marginTop="@dimen/ntp_customization_discover_feed_margin_top"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:focusable="true"
+    android:orientation="horizontal"
+    android:gravity="center_vertical">
+    <LinearLayout
+        android:id="@+id/content_container"
+        android:orientation="vertical"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:layout_marginStart="@dimen/ntp_customization_edit_icon_margin"
+        android:layout_marginVertical="@dimen/ntp_customization_edit_icon_margin">
+        <org.chromium.ui.widget.TextViewWithLeading
+            android:id="@+id/title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textAppearance="@style/TextAppearance.TextLarge.Primary" />
+        <org.chromium.ui.widget.TextViewWithLeading
+            android:id="@+id/subtitle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textAppearance="@style/TextAppearance.TextMedium.Secondary" />
+    </LinearLayout>
+    <ImageView
+        android:id="@+id/trailing_icon"
+        android:layout_width="@dimen/ntp_customization_bottom_sheet_list_item_icon_size"
+        android:layout_height="@dimen/ntp_customization_bottom_sheet_list_item_icon_size"
+        android:importantForAccessibility="no"
+        android:layout_marginEnd="@dimen/ntp_customization_edit_icon_margin"
+        android:layout_marginVertical="@dimen/ntp_customization_edit_icon_margin" />
+</org.chromium.chrome.browser.ntp_customization.BottomSheetListItemView>
\ No newline at end of file
diff --git a/chrome/browser/ntp_customization/java/res/layout/ntp_customization_bottom_sheet.xml b/chrome/browser/ntp_customization/java/res/layout/ntp_customization_bottom_sheet.xml
index 21324d15..2947d21 100644
--- a/chrome/browser/ntp_customization/java/res/layout/ntp_customization_bottom_sheet.xml
+++ b/chrome/browser/ntp_customization/java/res/layout/ntp_customization_bottom_sheet.xml
@@ -16,5 +16,8 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:measureAllChildren="false" >
+        <include
+            android:id="@+id/main_bottom_sheet"
+            layout="@layout/ntp_customization_main_bottom_sheet" />
     </ViewFlipper>
 </ScrollView>
\ No newline at end of file
diff --git a/chrome/browser/ntp_customization/java/res/layout/ntp_customization_main_bottom_sheet.xml b/chrome/browser/ntp_customization/java/res/layout/ntp_customization_main_bottom_sheet.xml
index 4fb9ce9..621566e 100644
--- a/chrome/browser/ntp_customization/java/res/layout/ntp_customization_main_bottom_sheet.xml
+++ b/chrome/browser/ntp_customization/java/res/layout/ntp_customization_main_bottom_sheet.xml
@@ -63,88 +63,14 @@
         app:layout_constraintEnd_toEndOf="@id/bottom_sheet_title"
         android:text="@string/ntp_customization_subtitle" />
 
-    <View
-        android:id="@+id/new_tab_page_cards_list_item_container"
-        android:layout_width="0dp"
-        android:layout_height="0dp"
-        android:background="@drawable/ntp_customization_bottom_sheet_list_item_background_top"
-        android:layout_marginTop="@dimen/ntp_customization_bottom_sheet_layout_padding_bottom"
-        android:layout_marginStart="@dimen/ntp_customization_bottom_sheet_layout_padding_bottom"
-        android:layout_marginEnd="@dimen/ntp_customization_bottom_sheet_layout_padding_bottom"
+    <org.chromium.chrome.browser.ntp_customization.BottomSheetListContainerView
+        android:id="@+id/ntp_customization_options_container"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        android:layout_marginTop="@dimen/ntp_customization_ntp_cards_margin"
+        android:layout_marginHorizontal="@dimen/ntp_customization_bottom_sheet_layout_padding_bottom"
         app:layout_constraintTop_toBottomOf="@id/bottom_sheet_description"
-        app:layout_constraintBottom_toBottomOf="@id/new_tab_page_cards_list_item_title"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"/>
-
-    <TextView
-        android:id="@+id/new_tab_page_cards_list_item_title"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:text="@string/home_modules_configuration"
-        android:textAppearance="@style/TextAppearance.TextLarge.Primary"
-        android:layout_marginTop="@dimen/ntp_customization_edit_icon_margin"
-        android:layout_marginStart="@dimen/ntp_customization_edit_icon_margin"
-        android:paddingBottom="@dimen/ntp_customization_edit_icon_margin"
-        app:layout_constraintTop_toTopOf="@id/new_tab_page_cards_list_item_container"
-        app:layout_constraintStart_toStartOf="@id/new_tab_page_cards_list_item_container"
-        app:layout_constraintEnd_toStartOf="@id/new_tab_page_cards_forward_arrow_icon" />
-
-    <ImageView
-        android:id="@+id/new_tab_page_cards_forward_arrow_icon"
-        android:layout_width="@dimen/ntp_customization_forward_arrow_icon_size"
-        android:layout_height="@dimen/ntp_customization_forward_arrow_icon_size"
-        android:importantForAccessibility="no"
-        app:srcCompat="@drawable/forward_arrow_icon"
-        android:layout_marginEnd="@dimen/ntp_customization_edit_icon_margin"
-        app:layout_constraintTop_toTopOf="@id/new_tab_page_cards_list_item_container"
-        app:layout_constraintBottom_toBottomOf="@id/new_tab_page_cards_list_item_container"
-        app:layout_constraintEnd_toEndOf="@id/new_tab_page_cards_list_item_container"
-        app:layout_constraintStart_toEndOf="@id/new_tab_page_cards_list_item_title" />
-
-    <View
-        android:id="@+id/discover_feed_list_item_container"
-        android:layout_width="0dp"
-        android:layout_height="0dp"
-        android:background="@drawable/ntp_customization_bottom_sheet_list_item_background_bottom"
-        android:layout_marginTop="@dimen/ntp_customization_discover_feed_margin_top"
-        app:layout_constraintTop_toBottomOf="@id/new_tab_page_cards_list_item_container"
-        app:layout_constraintBottom_toBottomOf="@id/discover_feed_status"
-        app:layout_constraintStart_toStartOf="@id/new_tab_page_cards_list_item_container"
-        app:layout_constraintEnd_toEndOf="@id/new_tab_page_cards_list_item_container" />
-
-    <TextView
-        android:id="@+id/discover_feed_list_item_title"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:text="@string/ntp_customization_feed_setting_title"
-        android:textAppearance="@style/TextAppearance.TextLarge.Primary"
-        android:layout_marginTop="@dimen/ntp_customization_edit_icon_margin"
-        android:layout_marginStart="@dimen/ntp_customization_edit_icon_margin"
-        app:layout_constraintTop_toTopOf="@id/discover_feed_list_item_container"
-        app:layout_constraintStart_toStartOf="@id/discover_feed_list_item_container"
-        app:layout_constraintEnd_toStartOf="@id/discover_feed_setting_forward" />
-
-    <!-- The status of the feed section is hardcoded for demonstration -->
-    <TextView
-        android:id="@+id/discover_feed_status"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:text="@string/ntp_customization_feed_section_on"
-        android:textAppearance="@style/TextAppearance.TextMedium.Secondary"
-        android:paddingBottom="@dimen/ntp_customization_edit_icon_margin"
-        app:layout_constraintTop_toBottomOf="@id/discover_feed_list_item_title"
-        app:layout_constraintStart_toStartOf="@id/discover_feed_list_item_title"
-        app:layout_constraintEnd_toStartOf="@id/discover_feed_setting_forward" />
-
-    <ImageView
-        android:id="@+id/discover_feed_setting_forward"
-        android:layout_width="@dimen/ntp_customization_forward_arrow_icon_size"
-        android:layout_height="@dimen/ntp_customization_forward_arrow_icon_size"
-        android:importantForAccessibility="no"
-        app:srcCompat="@drawable/forward_arrow_icon"
-        android:layout_marginEnd="@dimen/ntp_customization_edit_icon_margin"
-        app:layout_constraintTop_toTopOf="@id/discover_feed_list_item_container"
-        app:layout_constraintBottom_toBottomOf="@id/discover_feed_list_item_container"
-        app:layout_constraintStart_toEndOf="@id/discover_feed_list_item_title"
-        app:layout_constraintEnd_toEndOf="@id/discover_feed_list_item_container" />
+        app:layout_constraintStart_toStartOf="parent" >
+    </org.chromium.chrome.browser.ntp_customization.BottomSheetListContainerView>
 </androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/chrome/browser/ntp_customization/java/res/layout/ntp_customization_ntp_cards_bottom_sheet.xml b/chrome/browser/ntp_customization/java/res/layout/ntp_customization_ntp_cards_bottom_sheet.xml
index e41470b5..125ed4b 100644
--- a/chrome/browser/ntp_customization/java/res/layout/ntp_customization_ntp_cards_bottom_sheet.xml
+++ b/chrome/browser/ntp_customization/java/res/layout/ntp_customization_ntp_cards_bottom_sheet.xml
@@ -5,7 +5,6 @@
 found in the LICENSE file.
 -->
 
-
 <androidx.constraintlayout.widget.ConstraintLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
@@ -15,7 +14,7 @@
     android:paddingBottom="@dimen/ntp_customization_bottom_sheet_layout_padding_bottom">
 
     <ImageView
-        android:id="@+id/ntp_cards_back_button"
+        android:id="@+id/back_button"
         android:layout_height="@dimen/ntp_customization_back_button_clickable_size"
         android:layout_width="@dimen/ntp_customization_back_button_clickable_size"
         android:padding="@dimen/ntp_customization_back_button_padding"
@@ -37,11 +36,11 @@
         android:layout_marginStart="@dimen/ntp_customization_discover_feed_margin_top"
         android:layout_marginTop="@dimen/ntp_customization_back_button_margin"
         android:layout_marginEnd="@dimen/ntp_customization_bottom_sheet_layout_padding_bottom"
-        app:layout_constraintStart_toEndOf="@id/ntp_cards_back_button"
+        app:layout_constraintStart_toEndOf="@id/back_button"
         app:layout_constraintTop_toTopOf="parent"
         app:layout_constraintEnd_toEndOf="parent" />
 
-    <org.chromium.chrome.browser.ntp_customization.NtpCardsContainerView
+    <org.chromium.chrome.browser.ntp_customization.NtpCardsListContainerView
         android:id="@+id/ntp_cards_container"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
@@ -50,5 +49,5 @@
         android:layout_marginHorizontal="@dimen/ntp_customization_bottom_sheet_layout_padding_bottom"
         app:layout_constraintTop_toBottomOf="@id/bottom_sheet_title"
         app:layout_constraintStart_toStartOf="parent">
-    </org.chromium.chrome.browser.ntp_customization.NtpCardsContainerView>
+    </org.chromium.chrome.browser.ntp_customization.NtpCardsListContainerView>
 </androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/chrome/browser/ntp_customization/java/res/values/dimens.xml b/chrome/browser/ntp_customization/java/res/values/dimens.xml
index 7a956c2..6133bc06 100644
--- a/chrome/browser/ntp_customization/java/res/values/dimens.xml
+++ b/chrome/browser/ntp_customization/java/res/values/dimens.xml
@@ -17,6 +17,6 @@
   <dimen name="ntp_customization_bottom_sheet_layout_padding_bottom">24dp</dimen>
   <dimen name="ntp_customization_ntp_cards_margin">20dp</dimen>
   <dimen name="ntp_customization_back_button_margin">36dp</dimen>
-  <dimen name="ntp_customization_forward_arrow_icon_size">24dp</dimen>
+  <dimen name="ntp_customization_bottom_sheet_list_item_icon_size">24dp</dimen>
   <dimen name="ntp_customization_edit_icon_background_size">40dp</dimen>
 </resources>
\ No newline at end of file
diff --git a/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/BottomSheetDelegate.java b/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/BottomSheetDelegate.java
index 3907e2a92..0f1beab 100644
--- a/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/BottomSheetDelegate.java
+++ b/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/BottomSheetDelegate.java
@@ -7,18 +7,19 @@
 import android.view.View;
 
 /**
- * This delegate interface is responsible for adding and displaying bottom sheet layouts, as well as
- * handling system back press and back button clicks on the bottom sheet.
+ * This delegate interface is responsible for recording the position of the bottom sheet layout in
+ * the view flipper view, as well as handling back button clicks on the bottom sheet.
  */
 public interface BottomSheetDelegate {
     /**
-     * Records the position of the bottom sheet layout in the view flipper view.
+     * Records the position of the bottom sheet layout in the view flipper view. The position index
+     * starts at 0.
      *
      * @param type The type of the bottom sheet.
      */
     void registerBottomSheetLayout(
             @NtpCustomizationCoordinator.BottomSheetType int type, View view);
 
-    /** Handles system back press and back button clicks on the bottom sheet. */
+    /** Handles back button clicks in the bottom sheet. */
     void backPressOnCurrentBottomSheet();
 }
diff --git a/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/BottomSheetListContainerView.java b/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/BottomSheetListContainerView.java
new file mode 100644
index 0000000..39fd376
--- /dev/null
+++ b/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/BottomSheetListContainerView.java
@@ -0,0 +1,61 @@
+// 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.ntp_customization;
+
+import android.content.Context;
+import android.support.annotation.Nullable;
+import android.support.annotation.VisibleForTesting;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.LinearLayout;
+
+import java.util.List;
+
+/** The view holding {@link BottomSheetListItemView} in a bottom sheet. */
+public class BottomSheetListContainerView extends LinearLayout {
+    protected final Context mContext;
+
+    public BottomSheetListContainerView(Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+        mContext = context;
+    }
+
+    /**
+     * Adds list items views to this container view.
+     *
+     * @param delegate The delegate contains the content for each list item view.
+     */
+    public void renderAllListItems(ListContainerViewDelegate delegate) {
+        List<Integer> types = delegate.getListItems();
+        for (int i = 0; i < types.size(); i++) {
+            Integer type = types.get(i);
+            BottomSheetListItemView listItemView = (BottomSheetListItemView) createListItemView();
+
+            listItemView.setTitle(delegate.getListItemTitle(type, mContext));
+            listItemView.setSubtitle(delegate.getListItemSubtitle(type, mContext));
+            listItemView.setBackground(NtpCustomizationUtils.getBackground(types.size(), i));
+            listItemView.setTrailingIcon(delegate.getTrailingIcon(type));
+            listItemView.setOnClickListener(delegate.getListener(type));
+
+            addView(listItemView);
+        }
+    }
+
+    /** Returns a {@link BottomSheetListItemView}. */
+    @VisibleForTesting
+    View createListItemView() {
+        return LayoutInflater.from(mContext)
+                .inflate(R.layout.bottom_sheet_list_item_view, this, false);
+    }
+
+    /** Clears {@link View.OnClickListener} of each list item inside this container view. */
+    protected void destroy() {
+        for (int i = 0; i < getChildCount(); i++) {
+            BottomSheetListItemView child = (BottomSheetListItemView) getChildAt(i);
+            child.setOnClickListener(null);
+        }
+    }
+}
diff --git a/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/BottomSheetListContainerViewBinder.java b/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/BottomSheetListContainerViewBinder.java
new file mode 100644
index 0000000..0c0aa69
--- /dev/null
+++ b/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/BottomSheetListContainerViewBinder.java
@@ -0,0 +1,29 @@
+// 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.ntp_customization;
+
+import static org.chromium.chrome.browser.ntp_customization.NtpCustomizationViewProperties.LIST_CONTAINER_VIEW_DELEGATE;
+
+import android.view.View;
+
+import org.chromium.ui.modelutil.PropertyKey;
+import org.chromium.ui.modelutil.PropertyModel;
+
+/**
+ * Class responsible for binding a delegate to a {@link BottomSheetListContainerView}. The delegate
+ * provides list content and event handlers to the list container view.
+ */
+public class BottomSheetListContainerViewBinder {
+    public static void bind(PropertyModel model, View view, PropertyKey propertyKey) {
+        if (propertyKey == LIST_CONTAINER_VIEW_DELEGATE) {
+            ListContainerViewDelegate delegate = model.get(LIST_CONTAINER_VIEW_DELEGATE);
+            if (delegate == null) {
+                ((BottomSheetListContainerView) view).destroy();
+            } else {
+                ((BottomSheetListContainerView) view).renderAllListItems(delegate);
+            }
+        }
+    }
+}
diff --git a/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/BottomSheetListItemView.java b/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/BottomSheetListItemView.java
new file mode 100644
index 0000000..f6f6014
--- /dev/null
+++ b/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/BottomSheetListItemView.java
@@ -0,0 +1,70 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.ntp_customization;
+
+import android.content.Context;
+import android.support.annotation.DrawableRes;
+import android.support.annotation.Nullable;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+
+import androidx.appcompat.content.res.AppCompatResources;
+
+import org.chromium.ui.widget.TextViewWithLeading;
+
+/** The list item view within a {@link BottomSheetListContainerView} of a bottom sheet. */
+public class BottomSheetListItemView extends LinearLayout {
+    private TextViewWithLeading mTitleView;
+    private TextViewWithLeading mSubtitleView;
+    private ImageView mTrailingIcon;
+
+    public BottomSheetListItemView(Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mTitleView = findViewById(R.id.title);
+        mSubtitleView = findViewById(R.id.subtitle);
+        mTrailingIcon = findViewById(R.id.trailing_icon);
+    }
+
+    void setTitle(String text) {
+        mTitleView.setText(text);
+    }
+
+    /**
+     * Sets the content of the subtitle below the title in this list item view. When the given text
+     * is null, the visibility of the subtitle view is set to View.GONE.
+     *
+     * @param text The text to display under the title.
+     */
+    void setSubtitle(@Nullable String text) {
+        if (text == null) {
+            mSubtitleView.setVisibility(View.GONE);
+        }
+        mSubtitleView.setText(text);
+    }
+
+    /** Sets the background of this {@link BottomSheetListItemView}. */
+    void setBackground(@DrawableRes int resId) {
+        this.setBackground(AppCompatResources.getDrawable(getContext(), resId));
+    }
+
+    /**
+     * Sets the image resource of the trailing icon besides the title and the subtitle. When the
+     * given resId is null, the visibility of the icon will be set to View.GONE.
+     */
+    void setTrailingIcon(@Nullable @DrawableRes Integer resId) {
+        if (resId == null) {
+            mTrailingIcon.setVisibility(View.GONE);
+            return;
+        }
+        mTrailingIcon.setImageResource(resId);
+    }
+}
diff --git a/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/BottomSheetViewBinder.java b/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/BottomSheetViewBinder.java
new file mode 100644
index 0000000..f1f33d2
--- /dev/null
+++ b/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/BottomSheetViewBinder.java
@@ -0,0 +1,22 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.ntp_customization;
+
+import static org.chromium.chrome.browser.ntp_customization.NtpCustomizationViewProperties.BACK_PRESS_HANDLER;
+
+import android.view.View;
+
+import org.chromium.ui.modelutil.PropertyKey;
+import org.chromium.ui.modelutil.PropertyModel;
+
+/** Class responsible for binding a back press handler to the back button in a bottom sheet. */
+public class BottomSheetViewBinder {
+    public static void bind(PropertyModel model, View view, PropertyKey propertyKey) {
+        if (propertyKey == BACK_PRESS_HANDLER) {
+            View backButton = view.findViewById(R.id.back_button);
+            backButton.setOnClickListener(model.get(BACK_PRESS_HANDLER));
+        }
+    }
+}
diff --git a/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/ListContainerViewDelegate.java b/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/ListContainerViewDelegate.java
new file mode 100644
index 0000000..a710cdc
--- /dev/null
+++ b/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/ListContainerViewDelegate.java
@@ -0,0 +1,55 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.ntp_customization;
+
+import android.content.Context;
+import android.support.annotation.DrawableRes;
+import android.support.annotation.Nullable;
+import android.view.View;
+
+import java.util.List;
+
+/**
+ * This delegate provides list content and event handlers to {@link BottomSheetListContainerView}.
+ */
+public interface ListContainerViewDelegate {
+    /**
+     * Returns the types of items to be added to the container view. The size of the returned list
+     * should be the same as the number of the list items to be displayed.
+     */
+    List<Integer> getListItems();
+
+    /**
+     * Returns the title to be displayed in the the list item for the given type.
+     *
+     * @param type The type of the list item.
+     */
+    String getListItemTitle(int type, Context context);
+
+    /**
+     * Returns the subtitle to be displayed in the list item for the given type.
+     *
+     * @param type The type of the list item.
+     */
+    @Nullable
+    String getListItemSubtitle(int type, Context context);
+
+    /**
+     * Returns the listener associated with the given type of the list item.
+     *
+     * @param type The type of the list item.
+     */
+    @Nullable
+    View.OnClickListener getListener(int type);
+
+    /**
+     * Returns the resource id of the trailing icon in the list item for the given type.
+     *
+     * @param type The type of the list item.
+     */
+    @Nullable
+    @DrawableRes
+    Integer getTrailingIcon(int type);
+}
diff --git a/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/NtpCardsContainerView.java b/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/NtpCardsContainerView.java
deleted file mode 100644
index 9ed6ebf05..0000000
--- a/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/NtpCardsContainerView.java
+++ /dev/null
@@ -1,80 +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.chrome.browser.ntp_customization;
-
-import static org.chromium.chrome.browser.magic_stack.HomeModulesUtils.getTitleForModuleType;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.widget.LinearLayout;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
-
-import org.chromium.chrome.browser.magic_stack.HomeModulesConfigManager;
-import org.chromium.chrome.browser.magic_stack.ModuleDelegate;
-
-import java.util.List;
-
-/** The view containing {@link NtpCardsListItemView} in the "New tab page cards" bottom sheet. */
-public class NtpCardsContainerView extends LinearLayout {
-    public NtpCardsContainerView(Context context, @Nullable AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        HomeModulesConfigManager homeModulesConfigManager = HomeModulesConfigManager.getInstance();
-        List<Integer> ntpCardsItems = homeModulesConfigManager.getModuleListShownInSettings();
-
-        for (int i = 0; i < ntpCardsItems.size(); i++) {
-            @ModuleDelegate.ModuleType int itemType = ntpCardsItems.get(i);
-
-            NtpCardsListItemView listItemView =
-                    (NtpCardsListItemView)
-                            LayoutInflater.from(getContext())
-                                    .inflate(
-                                            R.layout.ntp_customization_ntp_cards_list_item,
-                                            this,
-                                            false);
-
-            listItemView.setTitle(getTitleForModuleType(itemType, getResources()));
-            setBackground(listItemView, ntpCardsItems.size(), i);
-            addView(listItemView);
-        }
-    }
-
-    private void setBackground(@NonNull NtpCardsListItemView view, int size, int index) {
-        view.setBackground(getBackground(size, index));
-    }
-
-    /**
-     * Every list in ntp customization bottom sheets should use this function to get the background
-     * of their list items.
-     *
-     * @param size The number of the list items to be displayed in the container view.
-     * @param index The index of the currently iterated list item.
-     * @return The background of the list item at the index.
-     */
-    @VisibleForTesting
-    int getBackground(int size, int index) {
-        if (size == 1) {
-            return R.drawable.ntp_customization_bottom_sheet_list_item_background_single;
-        }
-
-        if (index == 0) {
-            return R.drawable.ntp_customization_bottom_sheet_list_item_background_top;
-        }
-
-        if (index == size - 1) {
-            return R.drawable.ntp_customization_bottom_sheet_list_item_background_bottom;
-        }
-
-        return R.drawable.ntp_customization_bottom_sheet_list_item_background_middle;
-    }
-}
diff --git a/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/NtpCardsCoordinator.java b/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/NtpCardsCoordinator.java
index b6d4aa4..259afff 100644
--- a/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/NtpCardsCoordinator.java
+++ b/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/NtpCardsCoordinator.java
@@ -5,26 +5,53 @@
 package org.chromium.chrome.browser.ntp_customization;
 
 import static org.chromium.chrome.browser.ntp_customization.NtpCustomizationCoordinator.BottomSheetType.NTP_CARDS;
-import static org.chromium.chrome.browser.ntp_customization.NtpCustomizationViewProperties.NTP_CARDS_BACK_PRESS_HANDLER;
 
 import android.content.Context;
 import android.view.LayoutInflater;
 import android.view.View;
 
 import org.chromium.ui.modelutil.PropertyModel;
+import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
 
 /** Coordinator for the NTP cards bottom sheet. */
 public class NtpCardsCoordinator {
-    public NtpCardsCoordinator(
-            Context context, BottomSheetDelegate delegate, PropertyModel propertyModel) {
+    private NtpCardsMediator mMediator;
+
+    public NtpCardsCoordinator(Context context, BottomSheetDelegate delegate) {
         View view =
                 LayoutInflater.from(context)
                         .inflate(R.layout.ntp_customization_ntp_cards_bottom_sheet, null, false);
         delegate.registerBottomSheetLayout(NTP_CARDS, view);
 
-        // TODO(crbug.com/397439004): NtpCardsCoordinator creates it own property model instead of
-        // sharing it with other coordinators
-        propertyModel.set(
-                NTP_CARDS_BACK_PRESS_HANDLER, v -> delegate.backPressOnCurrentBottomSheet());
+        // The containerPropertyModel is responsible for managing a BottomSheetDelegate which
+        // provides list content and event handlers to the list container view.
+        PropertyModel containerPropertyModel =
+                new PropertyModel(NtpCustomizationViewProperties.LIST_CONTAINER_KEYS);
+        PropertyModelChangeProcessor.create(
+                containerPropertyModel,
+                view.findViewById(R.id.ntp_cards_container),
+                BottomSheetListContainerViewBinder::bind);
+
+        // The bottomSheetPropertyModel is responsible for managing the back press handler of the
+        // back button in the bottom sheet.
+        PropertyModel bottomSheetPropertyModel =
+                new PropertyModel(NtpCustomizationViewProperties.BOTTOM_SHEET_KEYS);
+        PropertyModelChangeProcessor.create(
+                bottomSheetPropertyModel, view, BottomSheetViewBinder::bind);
+
+        mMediator =
+                new NtpCardsMediator(containerPropertyModel, bottomSheetPropertyModel, delegate);
+    }
+
+    public void destroy() {
+        mMediator.destroy();
+    }
+
+    NtpCardsMediator getMediatorForTesting() {
+        return mMediator;
+    }
+
+    void setMediatorForTesting(NtpCardsMediator mediator) {
+        mMediator = mediator;
     }
 }
diff --git a/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/NtpCardsListContainerView.java b/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/NtpCardsListContainerView.java
new file mode 100644
index 0000000..b7690c2800
--- /dev/null
+++ b/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/NtpCardsListContainerView.java
@@ -0,0 +1,93 @@
+// 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.ntp_customization;
+
+import android.content.Context;
+import android.support.annotation.VisibleForTesting;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.CompoundButton;
+
+import androidx.annotation.Nullable;
+import androidx.appcompat.content.res.AppCompatResources;
+
+import org.chromium.chrome.browser.magic_stack.HomeModulesConfigManager;
+
+import java.util.List;
+
+/** The view holding {@link NtpCardsListItemView} in the "New tab page cards" bottom sheet. */
+public class NtpCardsListContainerView extends BottomSheetListContainerView {
+    public NtpCardsListContainerView(Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    /**
+     * Adds list items views to this container view.
+     *
+     * @param delegate The delegate contains the content for each list item view.
+     */
+    @Override
+    public void renderAllListItems(ListContainerViewDelegate delegate) {
+        HomeModulesConfigManager homeModulesConfigManager = HomeModulesConfigManager.getInstance();
+        List<Integer> types = delegate.getListItems();
+
+        for (int i = 0; i < types.size(); i++) {
+            Integer type = types.get(i);
+            NtpCardsListItemView listItemView = (NtpCardsListItemView) createListItemView();
+
+            listItemView.setTitle(delegate.getListItemTitle(type, mContext));
+            listItemView.setBackground(
+                    AppCompatResources.getDrawable(
+                            getContext(), NtpCustomizationUtils.getBackground(types.size(), i)));
+            setUpSwitch(homeModulesConfigManager, listItemView, type);
+
+            addView(listItemView);
+        }
+    }
+
+    /** Returns a {@link NtpCardsListItemView}. */
+    @Override
+    @VisibleForTesting
+    View createListItemView() {
+        return LayoutInflater.from(getContext())
+                .inflate(R.layout.ntp_customization_ntp_cards_list_item, this, false);
+    }
+
+    /**
+     * Sets up the current checked state of the switch in the list item view and sets up a {@link
+     * CompoundButton.OnCheckedChangeListener} to handle user selection to show or hide the magic
+     * stack module.
+     *
+     * @param homeModulesConfigManager The manager class handles showing and hiding each magic stack
+     *     module.
+     * @param listItemView The list item view that contains the switch.
+     * @param type The type of the magic stack module that this switch controls for showing and
+     *     hiding.
+     */
+    @VisibleForTesting
+    void setUpSwitch(
+            HomeModulesConfigManager homeModulesConfigManager,
+            NtpCardsListItemView listItemView,
+            int type) {
+        listItemView.setChecked(homeModulesConfigManager.getPrefModuleTypeEnabled(type));
+        listItemView.setOnCheckedChangeListener(
+                (button, newValue) -> {
+                    homeModulesConfigManager.setPrefModuleTypeEnabled(type, newValue);
+                });
+    }
+
+    /**
+     * Clear {@link CompoundButton.OnCheckedChangeListener} of every list item view inside this
+     * container view.
+     */
+    @Override
+    protected void destroy() {
+        for (int i = 0; i < getChildCount(); i++) {
+            NtpCardsListItemView child = (NtpCardsListItemView) getChildAt(i);
+            child.setOnCheckedChangeListener(null);
+        }
+    }
+}
diff --git a/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/NtpCardsListItemView.java b/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/NtpCardsListItemView.java
index 8fbb052..aa1adf5 100644
--- a/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/NtpCardsListItemView.java
+++ b/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/NtpCardsListItemView.java
@@ -5,18 +5,16 @@
 package org.chromium.chrome.browser.ntp_customization;
 
 import android.content.Context;
-import android.support.annotation.DrawableRes;
-import android.support.annotation.NonNull;
 import android.util.AttributeSet;
+import android.widget.CompoundButton;
 import android.widget.LinearLayout;
 
 import androidx.annotation.Nullable;
-import androidx.appcompat.content.res.AppCompatResources;
 
 import org.chromium.components.browser_ui.widget.MaterialSwitchWithText;
 
 /**
- * The view for a list item inside {@link NtpCardsContainerView} in the "New tab page cards" bottom
+ * The list item view within a {@link NtpCardsListContainerView} in the "New tab page cards" bottom
  * sheet.
  */
 public class NtpCardsListItemView extends LinearLayout {
@@ -33,21 +31,33 @@
     }
 
     /**
-     * Set the title for the Textview besides the material switch in this {@link
-     * NtpCardsListItemView}.
+     * Sets the content of the title besides the material switch.
      *
      * @param title The string besides the material switch.
      */
-    void setTitle(@NonNull String title) {
+    void setTitle(String title) {
         mMaterialSwitchWithText.setText(title);
     }
 
     /**
-     * Set the background of this {@link NtpCardsListItemView}.
+     * Sets the switch to On if is checked and Off otherwise.
      *
-     * @param resId The resource ID of the background drawable.
+     * @param checked The switch view should be set to On or Off.
      */
-    void setBackground(@DrawableRes int resId) {
-        this.setBackground(AppCompatResources.getDrawable(getContext(), resId));
+    void setChecked(boolean checked) {
+        mMaterialSwitchWithText.setChecked(checked);
+    }
+
+    /**
+     * Sets the OnCheckedChangeListener of the material switch.
+     *
+     * @see CompoundButton#setOnCheckedChangeListener(CompoundButton.OnCheckedChangeListener).
+     */
+    public void setOnCheckedChangeListener(CompoundButton.OnCheckedChangeListener listener) {
+        mMaterialSwitchWithText.setOnCheckedChangeListener(listener);
+    }
+
+    void setMaterialSwitchWithTextForTesting(MaterialSwitchWithText switchWithText) {
+        mMaterialSwitchWithText = switchWithText;
     }
 }
diff --git a/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/NtpCardsMediator.java b/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/NtpCardsMediator.java
new file mode 100644
index 0000000..3b814fe5
--- /dev/null
+++ b/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/NtpCardsMediator.java
@@ -0,0 +1,81 @@
+// 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.ntp_customization;
+
+import static org.chromium.chrome.browser.magic_stack.HomeModulesUtils.getTitleForModuleType;
+import static org.chromium.chrome.browser.ntp_customization.NtpCustomizationViewProperties.BACK_PRESS_HANDLER;
+import static org.chromium.chrome.browser.ntp_customization.NtpCustomizationViewProperties.LIST_CONTAINER_VIEW_DELEGATE;
+
+import android.content.Context;
+import android.support.annotation.VisibleForTesting;
+import android.view.View;
+
+import org.jni_zero.internal.Nullable;
+
+import org.chromium.chrome.browser.magic_stack.HomeModulesConfigManager;
+import org.chromium.ui.modelutil.PropertyModel;
+
+import java.util.List;
+
+/**
+ * A mediator class that manages navigation between bottom sheets and manages the container view on
+ * the NTP cards bottom sheet.
+ */
+public class NtpCardsMediator {
+
+    private final PropertyModel mContainerPropertyModel;
+    private final PropertyModel mBottomSheetPropertyModel;
+
+    public NtpCardsMediator(
+            PropertyModel containerPropertyModel,
+            PropertyModel bottomSheetPropertyModel,
+            BottomSheetDelegate delegate) {
+        mContainerPropertyModel = containerPropertyModel;
+        mBottomSheetPropertyModel = bottomSheetPropertyModel;
+
+        mContainerPropertyModel.set(LIST_CONTAINER_VIEW_DELEGATE, createListDelegate());
+        mBottomSheetPropertyModel.set(
+                BACK_PRESS_HANDLER, v -> delegate.backPressOnCurrentBottomSheet());
+    }
+
+    /** Returns {@link ListContainerViewDelegate} that defines the content of each list item. */
+    @VisibleForTesting
+    ListContainerViewDelegate createListDelegate() {
+        return new ListContainerViewDelegate() {
+            @Override
+            public List<Integer> getListItems() {
+                HomeModulesConfigManager homeModulesConfigManager =
+                        HomeModulesConfigManager.getInstance();
+                return homeModulesConfigManager.getModuleListShownInSettings();
+            }
+
+            @Override
+            public String getListItemTitle(int type, Context context) {
+                return getTitleForModuleType(type, context.getResources());
+            }
+
+            @Override
+            public String getListItemSubtitle(int type, Context context) {
+                return null;
+            }
+
+            @Override
+            public View.@Nullable OnClickListener getListener(int type) {
+                return null;
+            }
+
+            @Override
+            public Integer getTrailingIcon(int type) {
+                return null;
+            }
+        };
+    }
+
+    /** Clears the back press handler and click listeners of NTP cards bottom sheet. */
+    void destroy() {
+        mBottomSheetPropertyModel.set(BACK_PRESS_HANDLER, null);
+        mContainerPropertyModel.set(LIST_CONTAINER_VIEW_DELEGATE, null);
+    }
+}
diff --git a/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/NtpCustomizationCoordinator.java b/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/NtpCustomizationCoordinator.java
index 8e1109d..ae05efa 100644
--- a/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/NtpCustomizationCoordinator.java
+++ b/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/NtpCustomizationCoordinator.java
@@ -4,9 +4,12 @@
 
 package org.chromium.chrome.browser.ntp_customization;
 
+import static org.chromium.chrome.browser.ntp_customization.NtpCustomizationCoordinator.BottomSheetType.DISCOVER_FEED;
 import static org.chromium.chrome.browser.ntp_customization.NtpCustomizationCoordinator.BottomSheetType.MAIN;
-import static org.chromium.chrome.browser.ntp_customization.NtpCustomizationViewProperties.NTP_CARDS_BACK_PRESS_HANDLER;
-import static org.chromium.chrome.browser.ntp_customization.NtpCustomizationViewProperties.NTP_CARDS_OPTION_CLICK_LISTENER;
+import static org.chromium.chrome.browser.ntp_customization.NtpCustomizationCoordinator.BottomSheetType.NTP_CARDS;
+import static org.chromium.chrome.browser.ntp_customization.NtpCustomizationViewProperties.LAYOUT_TO_DISPLAY;
+import static org.chromium.chrome.browser.ntp_customization.NtpCustomizationViewProperties.LIST_CONTAINER_KEYS;
+import static org.chromium.chrome.browser.ntp_customization.NtpCustomizationViewProperties.VIEW_FLIPPER_KEYS;
 
 import android.content.Context;
 import android.support.annotation.IntDef;
@@ -16,115 +19,151 @@
 import android.widget.ViewFlipper;
 
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
+import org.chromium.ui.modelutil.PropertyKey;
 import org.chromium.ui.modelutil.PropertyModel;
 import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
-/** Coordinator of the ntp customization main bottom sheet. */
+/** Coordinator of the NTP customization main bottom sheet. */
 public class NtpCustomizationCoordinator {
-    private final BottomSheetController mBottomSheetController;
-    private final PropertyModel mPropertyModel;
-    private NtpCustomizationMediator mMediator;
-    private NtpCardsCoordinator mNtpCardsCoordinator;
-    private View mContentView;
-    private ViewFlipper mViewFlipperView;
-
     /**
      * mDelegate will be passed to every bottom sheet coordinator created by {@link
      * NtpCustomizationCoordinator}.
      */
-    private BottomSheetDelegate mDelegate;
+    private final BottomSheetDelegate mDelegate;
 
-    @IntDef({BottomSheetType.MAIN, BottomSheetType.NTP_CARDS})
+    private final Context mContext;
+    private NtpCustomizationMediator mMediator;
+    private NtpCardsCoordinator mNtpCardsCoordinator;
+    private ViewFlipper mViewFlipperView;
+
+    @IntDef({BottomSheetType.MAIN, BottomSheetType.NTP_CARDS, BottomSheetType.DISCOVER_FEED})
     @Retention(RetentionPolicy.SOURCE)
-    @interface BottomSheetType {
+    public @interface BottomSheetType {
         int MAIN = 0;
         int NTP_CARDS = 1;
+        int DISCOVER_FEED = 2;
     }
 
     public NtpCustomizationCoordinator(
             Context context, BottomSheetController bottomSheetController) {
-        this(
-                context,
-                bottomSheetController,
-                new PropertyModel(NtpCustomizationViewProperties.ALL_KEYS));
-    }
-
-    @VisibleForTesting
-    NtpCustomizationCoordinator(
-            Context context,
-            BottomSheetController bottomSheetController,
-            PropertyModel propertyModel) {
-        mBottomSheetController = bottomSheetController;
-        mPropertyModel = propertyModel;
-        mContentView =
-                LayoutInflater.from(context)
+        mContext = context;
+        View contentView =
+                LayoutInflater.from(mContext)
                         .inflate(R.layout.ntp_customization_bottom_sheet, /* root= */ null);
-        mViewFlipperView = mContentView.findViewById(R.id.ntp_customization_view_flipper);
-        PropertyModelChangeProcessor.create(
-                mPropertyModel, mViewFlipperView, NtpCustomizationViewBinder::bind);
+        mViewFlipperView = contentView.findViewById(R.id.ntp_customization_view_flipper);
 
         NtpCustomizationBottomSheetContent bottomSheetContent =
                 new NtpCustomizationBottomSheetContent(
-                        mContentView,
+                        contentView,
                         /* backPressRunnable= */ () -> mMediator.backPressOnCurrentBottomSheet(),
                         this::destroy);
 
+        // The containerPropertyModel is responsible for managing a BottomSheetDelegate which
+        // provides list content and event handlers to a list container view in the bottom sheet.
+        View mainBottomSheetView = mViewFlipperView.findViewById(R.id.main_bottom_sheet);
+        PropertyModel containerPropertyModel = new PropertyModel(LIST_CONTAINER_KEYS);
+        PropertyModelChangeProcessor.create(
+                containerPropertyModel,
+                mainBottomSheetView.findViewById(R.id.ntp_customization_options_container),
+                BottomSheetListContainerViewBinder::bind);
+
+        // The viewFlipperPropertyModel is responsible for controlling which bottom sheet layout to
+        // display.
+        PropertyModel viewFlipperPropertyModel = new PropertyModel(VIEW_FLIPPER_KEYS);
+        PropertyModelChangeProcessor.create(
+                viewFlipperPropertyModel,
+                mViewFlipperView,
+                NtpCustomizationCoordinator::bindViewFlipper);
+
         mMediator =
                 new NtpCustomizationMediator(
-                        mBottomSheetController, bottomSheetContent, mPropertyModel);
-
-        LayoutInflater.from(context)
-                .inflate(R.layout.ntp_customization_main_bottom_sheet, mViewFlipperView, true);
+                        bottomSheetController,
+                        bottomSheetContent,
+                        viewFlipperPropertyModel,
+                        containerPropertyModel);
         mMediator.registerBottomSheetLayout(MAIN);
 
-        mDelegate =
-                new BottomSheetDelegate() {
-                    @Override
-                    public void registerBottomSheetLayout(int type, View view) {
-                        mViewFlipperView.addView(view);
-                        mMediator.registerBottomSheetLayout(type);
-                    }
+        mDelegate = createBottomSheetDelegate();
 
-                    @Override
-                    public void backPressOnCurrentBottomSheet() {
-                        mMediator.backPressOnCurrentBottomSheet();
-                    }
-                };
-
-        mPropertyModel.set(
-                NTP_CARDS_OPTION_CLICK_LISTENER,
-                v -> {
-                    if (mNtpCardsCoordinator == null) {
-                        mNtpCardsCoordinator =
-                                new NtpCardsCoordinator(context, mDelegate, mPropertyModel);
-                    }
-                    mMediator.showBottomSheet(BottomSheetType.NTP_CARDS);
-                });
+        // The click listener for each list item in the main bottom sheet should be registered
+        // before calling renderListContent().
+        mMediator.registerClickListener(NTP_CARDS, getOptionClickListener(NTP_CARDS));
+        mMediator.renderListContent();
     }
 
-    /** Opens the new tab page customization main bottom sheet. */
+    /** Opens the NTP customization main bottom sheet. */
     public void showBottomSheet() {
         mMediator.showBottomSheet(MAIN);
     }
 
-    /** Removes all click listeners, all views inside the view flipper and destroy the mediator. */
-    public void destroy() {
-        mPropertyModel.set(NTP_CARDS_OPTION_CLICK_LISTENER, null);
-        if (mNtpCardsCoordinator != null) {
-            mPropertyModel.set(NTP_CARDS_BACK_PRESS_HANDLER, null);
+    /**
+     * Returns a click listener to handle user clicks on the options in the NTP customization main
+     * bottom sheet.
+     */
+    @VisibleForTesting
+    View.OnClickListener getOptionClickListener(@BottomSheetType int type) {
+        switch (type) {
+            case NTP_CARDS:
+                return v -> {
+                    if (mNtpCardsCoordinator == null) {
+                        mNtpCardsCoordinator = new NtpCardsCoordinator(mContext, mDelegate);
+                    }
+                    mMediator.showBottomSheet(BottomSheetType.NTP_CARDS);
+                };
+            case DISCOVER_FEED:
+                return null;
+            default:
+                assert false : "Bottom sheet type not supported!";
+                return null;
         }
+    }
+
+    /**
+     * Returns a {@link BottomSheetDelegate} which adds layouts to the view flipper and handles
+     * clicks on the back button in the bottom sheet.
+     */
+    BottomSheetDelegate createBottomSheetDelegate() {
+        return new BottomSheetDelegate() {
+            @Override
+            public void registerBottomSheetLayout(int type, View view) {
+                mViewFlipperView.addView(view);
+                mMediator.registerBottomSheetLayout(type);
+            }
+
+            @Override
+            public void backPressOnCurrentBottomSheet() {
+                mMediator.backPressOnCurrentBottomSheet();
+            }
+        };
+    }
+
+    /**
+     * Binds the property model to the view flipper view to control which bottom sheet layout to
+     * display.
+     *
+     * @param view The view flipper view.
+     */
+    static void bindViewFlipper(PropertyModel model, View view, PropertyKey propertyKey) {
+        if (propertyKey == LAYOUT_TO_DISPLAY) {
+            ((ViewFlipper) view).setDisplayedChild(model.get(LAYOUT_TO_DISPLAY));
+        }
+    }
+
+    /**
+     * Clears all views inside the view flipper as well as destroys the mediator and coordinators.
+     */
+    public void destroy() {
         mViewFlipperView.removeAllViews();
         mMediator.destroy();
+        if (mNtpCardsCoordinator != null) {
+            mNtpCardsCoordinator.destroy();
+        }
     }
 
-    void setMediatorForTesting(NtpCustomizationMediator mediator) {
-        mMediator = mediator;
-    }
-
-    BottomSheetDelegate getDelegateForTesting() {
+    BottomSheetDelegate getBottomSheetDelegateForTesting() {
         return mDelegate;
     }
 
@@ -132,11 +171,15 @@
         return mNtpCardsCoordinator;
     }
 
-    View getContentViewForTesting() {
-        return mContentView;
-    }
-
     void setViewFlipperForTesting(ViewFlipper viewFlipper) {
         mViewFlipperView = viewFlipper;
     }
+
+    void setNtpCardsCoordinatorForTesting(NtpCardsCoordinator coordinator) {
+        mNtpCardsCoordinator = coordinator;
+    }
+
+    void setMediatorForTesting(NtpCustomizationMediator mediator) {
+        mMediator = mediator;
+    }
 }
diff --git a/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/NtpCustomizationMediator.java b/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/NtpCustomizationMediator.java
index 369cc85f2..8f0f1e9 100644
--- a/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/NtpCustomizationMediator.java
+++ b/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/NtpCustomizationMediator.java
@@ -4,10 +4,15 @@
 
 package org.chromium.chrome.browser.ntp_customization;
 
+import static org.chromium.chrome.browser.ntp_customization.NtpCustomizationCoordinator.BottomSheetType.DISCOVER_FEED;
 import static org.chromium.chrome.browser.ntp_customization.NtpCustomizationCoordinator.BottomSheetType.MAIN;
+import static org.chromium.chrome.browser.ntp_customization.NtpCustomizationCoordinator.BottomSheetType.NTP_CARDS;
 import static org.chromium.chrome.browser.ntp_customization.NtpCustomizationViewProperties.LAYOUT_TO_DISPLAY;
+import static org.chromium.chrome.browser.ntp_customization.NtpCustomizationViewProperties.LIST_CONTAINER_VIEW_DELEGATE;
 
-import android.support.annotation.VisibleForTesting;
+import android.content.Context;
+import android.support.annotation.Nullable;
+import android.view.View;
 import android.widget.ViewFlipper;
 
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetContent;
@@ -16,34 +21,44 @@
 import org.chromium.components.browser_ui.bottomsheet.EmptyBottomSheetObserver;
 import org.chromium.ui.modelutil.PropertyModel;
 
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 /**
- * A mediator class that manages the view flipper and {@link BottomSheetContent} of ntp
+ * A mediator class that manages the view flipper and {@link BottomSheetContent} of NTP
  * customization bottom sheets.
  */
 public class NtpCustomizationMediator {
     /**
-     * A map of <{@link NtpCustomizationCoordinator.BottomSheetType}, view's index in the {@link
-     * ViewFlipper}>.
+     * A map of <{@link NtpCustomizationCoordinator.BottomSheetType}, view's position index in the
+     * {@link ViewFlipper}>.
      */
     private final Map<Integer, Integer> mViewFlipperMap;
 
+    private final Map<Integer, View.OnClickListener> mTypeToListenersMap;
     private final BottomSheetController mBottomSheetController;
     private final NtpCustomizationBottomSheetContent mBottomSheetContent;
     private final BottomSheetObserver mBottomSheetObserver;
-    private final PropertyModel mPropertyModel;
+    private final PropertyModel mViewFlipperPropertyModel;
+    private final List<Integer> mListContent;
+    private final PropertyModel mContainerPropertyModel;
     private Integer mCurrentBottomSheet;
 
     public NtpCustomizationMediator(
             BottomSheetController bottomSheetController,
             NtpCustomizationBottomSheetContent bottomSheetContent,
-            PropertyModel propertyModel) {
+            PropertyModel viewFlipperPropertyModel,
+            PropertyModel containerPropertyModel) {
         mBottomSheetController = bottomSheetController;
         mBottomSheetContent = bottomSheetContent;
-        mPropertyModel = propertyModel;
+        mViewFlipperPropertyModel = viewFlipperPropertyModel;
+        mContainerPropertyModel = containerPropertyModel;
         mViewFlipperMap = new HashMap<>();
+        mTypeToListenersMap = new HashMap<>();
+        mListContent = buildListContent();
+
         mBottomSheetObserver =
                 new EmptyBottomSheetObserver() {
                     @Override
@@ -61,7 +76,8 @@
     }
 
     /**
-     * Records the position of the bottom sheet layout in the view flipper view.
+     * Records the position of the bottom sheet layout in the view flipper view. The position index
+     * starts at 0.
      *
      * @param type The type of the bottom sheet.
      */
@@ -76,11 +92,12 @@
         assert mViewFlipperMap.get(type) != null;
 
         int viewIndex = mViewFlipperMap.get(type);
-        mPropertyModel.set(LAYOUT_TO_DISPLAY, viewIndex);
+        mViewFlipperPropertyModel.set(LAYOUT_TO_DISPLAY, viewIndex);
         boolean shouldRequestShowContent = mCurrentBottomSheet == null;
         mCurrentBottomSheet = type;
 
-        // requestShowContent() is called only when showBottomSheet() is called at the first time.
+        // requestShowContent() is called only once when showBottomSheet() is invoked for the first
+        // time.
         if (shouldRequestShowContent) {
             mBottomSheetController.requestShowContent(mBottomSheetContent, /* animate= */ true);
         }
@@ -98,9 +115,81 @@
         }
     }
 
-    /** Clears the map */
+    /**
+     * Returns {@link ListContainerViewDelegate} that defines the content of each list item in the
+     * main bottom sheet.
+     */
+    ListContainerViewDelegate createListDelegate() {
+        return new ListContainerViewDelegate() {
+            @Override
+            public List<Integer> getListItems() {
+                return mListContent;
+            }
+
+            @Override
+            public String getListItemTitle(int type, Context context) {
+                switch (type) {
+                    case NTP_CARDS:
+                        return context.getString(R.string.home_modules_configuration);
+                    case DISCOVER_FEED:
+                        return context.getString(R.string.ntp_customization_feed_setting_title);
+                    default:
+                        assert false : "Bottom sheet type not supported!";
+                        return null;
+                }
+            }
+
+            @Override
+            @Nullable
+            public String getListItemSubtitle(int type, Context context) {
+                if (type == DISCOVER_FEED) {
+                    // TODO(crbug.com/397439004): Add logics to display "off".
+                    return context.getString(R.string.ntp_customization_feed_section_on);
+                }
+                return null;
+            }
+
+            @Override
+            public View.OnClickListener getListener(int type) {
+                return mTypeToListenersMap.get(type);
+            }
+
+            @Override
+            @Nullable
+            public Integer getTrailingIcon(int type) {
+                return R.drawable.forward_arrow_icon;
+            }
+        };
+    }
+
+    /** Renders the options list in the main bottom sheet. */
+    void renderListContent() {
+        mContainerPropertyModel.set(LIST_CONTAINER_VIEW_DELEGATE, createListDelegate());
+    }
+
+    /**
+     * Sets click listener on the list item view of the given type.
+     *
+     * @param type The type of the list item to which the click listener will be added.
+     * @param listener The click listener to set on the list item of the given type.
+     */
+    void registerClickListener(int type, View.OnClickListener listener) {
+        mTypeToListenersMap.put(type, listener);
+    }
+
+    /** Returns the content of the list displayed in the main bottom sheet. */
+    List<Integer> buildListContent() {
+        List<Integer> content = new ArrayList<>();
+        content.add(NTP_CARDS);
+        // TODO(crbug.com/397439004): Add logics to hide the "feeds" list item.
+        content.add(DISCOVER_FEED);
+        return content;
+    }
+
+    /** Clears maps */
     void destroy() {
         mViewFlipperMap.clear();
+        mTypeToListenersMap.clear();
     }
 
     Map<Integer, Integer> getViewFlipperMapForTesting() {
@@ -117,8 +206,11 @@
         mCurrentBottomSheet = bottomSheetType;
     }
 
-    @VisibleForTesting
-    BottomSheetObserver getBottomSheetObserver() {
+    BottomSheetObserver getBottomSheetObserverForTesting() {
         return mBottomSheetObserver;
     }
+
+    Map<Integer, View.OnClickListener> getTypeToListenersForTesting() {
+        return mTypeToListenersMap;
+    }
 }
diff --git a/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/NtpCustomizationUtils.java b/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/NtpCustomizationUtils.java
new file mode 100644
index 0000000..a2da04ed
--- /dev/null
+++ b/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/NtpCustomizationUtils.java
@@ -0,0 +1,33 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.ntp_customization;
+
+/** Utility class of the NTP customization. */
+public class NtpCustomizationUtils {
+
+    /**
+     * Every list in NTP customization bottom sheets should use this function to get the background
+     * for its list items.
+     *
+     * @param size The number of the list items to be displayed in a container view.
+     * @param index The index of the currently iterated list item.
+     * @return The background of the list item view at the given index.
+     */
+    public static int getBackground(int size, int index) {
+        if (size == 1) {
+            return R.drawable.ntp_customization_bottom_sheet_list_item_background_single;
+        }
+
+        if (index == 0) {
+            return R.drawable.ntp_customization_bottom_sheet_list_item_background_top;
+        }
+
+        if (index == size - 1) {
+            return R.drawable.ntp_customization_bottom_sheet_list_item_background_bottom;
+        }
+
+        return R.drawable.ntp_customization_bottom_sheet_list_item_background_middle;
+    }
+}
diff --git a/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/NtpCustomizationViewBinder.java b/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/NtpCustomizationViewBinder.java
deleted file mode 100644
index 7ac7d1c..0000000
--- a/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/NtpCustomizationViewBinder.java
+++ /dev/null
@@ -1,31 +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.chrome.browser.ntp_customization;
-
-import static org.chromium.chrome.browser.ntp_customization.NtpCustomizationViewProperties.LAYOUT_TO_DISPLAY;
-import static org.chromium.chrome.browser.ntp_customization.NtpCustomizationViewProperties.NTP_CARDS_BACK_PRESS_HANDLER;
-import static org.chromium.chrome.browser.ntp_customization.NtpCustomizationViewProperties.NTP_CARDS_OPTION_CLICK_LISTENER;
-
-import android.view.View;
-import android.widget.ViewFlipper;
-
-import org.chromium.ui.modelutil.PropertyKey;
-import org.chromium.ui.modelutil.PropertyModel;
-
-public class NtpCustomizationViewBinder {
-    public static void bind(PropertyModel model, View view, PropertyKey propertyKey) {
-        if (propertyKey == NTP_CARDS_OPTION_CLICK_LISTENER) {
-            View ntpCards = view.findViewById(R.id.new_tab_page_cards_list_item_container);
-            ntpCards.setOnClickListener(model.get(NTP_CARDS_OPTION_CLICK_LISTENER));
-        } else if (propertyKey == NTP_CARDS_BACK_PRESS_HANDLER) {
-            View backButton = view.findViewById(R.id.ntp_cards_back_button);
-            backButton.setOnClickListener(model.get(NTP_CARDS_BACK_PRESS_HANDLER));
-        } else if (propertyKey == LAYOUT_TO_DISPLAY) {
-            ((ViewFlipper) view).setDisplayedChild(model.get(LAYOUT_TO_DISPLAY));
-        } else {
-            assert false : "Unsupported property key";
-        }
-    }
-}
diff --git a/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/NtpCustomizationViewProperties.java b/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/NtpCustomizationViewProperties.java
index da068741..dd0ef00 100644
--- a/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/NtpCustomizationViewProperties.java
+++ b/chrome/browser/ntp_customization/java/src/org/chromium/chrome/browser/ntp_customization/NtpCustomizationViewProperties.java
@@ -9,16 +9,30 @@
 import org.chromium.ui.modelutil.PropertyKey;
 import org.chromium.ui.modelutil.PropertyModel;
 
-class NtpCustomizationViewProperties {
+/** The properties associated with rendering NTP customization bottom sheets. */
+public class NtpCustomizationViewProperties {
+    /** The click listener to handle back button clicks in the bottom sheet. */
     public static final PropertyModel.WritableObjectPropertyKey<View.OnClickListener>
-            NTP_CARDS_OPTION_CLICK_LISTENER = new PropertyModel.WritableObjectPropertyKey<>();
-    public static final PropertyModel.WritableObjectPropertyKey<View.OnClickListener>
-            NTP_CARDS_BACK_PRESS_HANDLER = new PropertyModel.WritableObjectPropertyKey<>();
+            BACK_PRESS_HANDLER = new PropertyModel.WritableObjectPropertyKey<>();
+
+    /** The position of the bottom sheet layout in the view flipper. */
     public static final PropertyModel.WritableIntPropertyKey LAYOUT_TO_DISPLAY =
             new PropertyModel.WritableIntPropertyKey();
 
-    public static final PropertyKey[] ALL_KEYS =
-            new PropertyKey[] {
-                NTP_CARDS_OPTION_CLICK_LISTENER, NTP_CARDS_BACK_PRESS_HANDLER, LAYOUT_TO_DISPLAY,
-            };
+    /**
+     * The delegate provides list content and event handlers to a {@link
+     * BottomSheetListContainerView}.
+     */
+    public static final PropertyModel.WritableObjectPropertyKey<ListContainerViewDelegate>
+            LIST_CONTAINER_VIEW_DELEGATE = new PropertyModel.WritableObjectPropertyKey<>();
+
+    /** The keys to bind a view flipper view. */
+    public static final PropertyKey[] VIEW_FLIPPER_KEYS = new PropertyKey[] {LAYOUT_TO_DISPLAY};
+
+    /** The keys to bind a {@link BottomSheetListContainerView}. */
+    public static final PropertyKey[] LIST_CONTAINER_KEYS =
+            new PropertyKey[] {LIST_CONTAINER_VIEW_DELEGATE};
+
+    /** The keys to bind a NTP customization bottom sheet with a back button inside. */
+    public static final PropertyKey[] BOTTOM_SHEET_KEYS = new PropertyKey[] {BACK_PRESS_HANDLER};
 }
diff --git a/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/BottomSheetListContainerViewBinderUnitTest.java b/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/BottomSheetListContainerViewBinderUnitTest.java
new file mode 100644
index 0000000..c2331d61
--- /dev/null
+++ b/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/BottomSheetListContainerViewBinderUnitTest.java
@@ -0,0 +1,54 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.ntp_customization;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import static org.chromium.chrome.browser.ntp_customization.NtpCustomizationViewProperties.LIST_CONTAINER_VIEW_DELEGATE;
+
+import androidx.test.filters.SmallTest;
+
+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.test.BaseRobolectricTestRunner;
+import org.chromium.ui.modelutil.PropertyModel;
+import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
+
+/** Unit tests for {@link BottomSheetListContainerViewBinder}. */
+@RunWith(BaseRobolectricTestRunner.class)
+public class BottomSheetListContainerViewBinderUnitTest {
+    @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+    private PropertyModel mPropertyModel;
+
+    @Before
+    public void setUp() {
+        mPropertyModel = new PropertyModel(NtpCustomizationViewProperties.LIST_CONTAINER_KEYS);
+    }
+
+    @Test
+    @SmallTest
+    public void testBind() {
+        BottomSheetListContainerView containerView = mock(BottomSheetListContainerView.class);
+        PropertyModelChangeProcessor.create(
+                mPropertyModel, containerView, BottomSheetListContainerViewBinder::bind);
+
+        // Verifies if the delegate is not null, it should be bound to the containerView.
+        ListContainerViewDelegate delegate = mock(ListContainerViewDelegate.class);
+        mPropertyModel.set(LIST_CONTAINER_VIEW_DELEGATE, delegate);
+        verify(containerView).renderAllListItems(eq(delegate));
+
+        // Verifies the delegate is null, the containerView should be destroyed.
+        mPropertyModel.set(LIST_CONTAINER_VIEW_DELEGATE, null);
+        verify(containerView).destroy();
+    }
+}
diff --git a/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/BottomSheetListContainerViewUnitTest.java b/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/BottomSheetListContainerViewUnitTest.java
new file mode 100644
index 0000000..746dc3c
--- /dev/null
+++ b/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/BottomSheetListContainerViewUnitTest.java
@@ -0,0 +1,101 @@
+// 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.ntp_customization;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import static org.chromium.chrome.browser.ntp_customization.NtpCustomizationCoordinator.BottomSheetType.DISCOVER_FEED;
+import static org.chromium.chrome.browser.ntp_customization.NtpCustomizationCoordinator.BottomSheetType.NTP_CARDS;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+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.chromium.base.test.BaseRobolectricTestRunner;
+
+import java.util.List;
+
+/** Unit tests for {@link BottomSheetListContainerView}. */
+@RunWith(BaseRobolectricTestRunner.class)
+public class BottomSheetListContainerViewUnitTest {
+    @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+    @Mock private ListContainerViewDelegate mDelegate;
+    @Mock private BottomSheetListItemView mListItemView;
+    private BottomSheetListContainerView mContainerView;
+
+    private List<Integer> mListContent;
+
+    @Before
+    public void setUp() {
+        Context context = ApplicationProvider.getApplicationContext();
+        View view =
+                LayoutInflater.from(context)
+                        .inflate(R.layout.ntp_customization_main_bottom_sheet, null, false);
+        mContainerView = spy(view.findViewById(R.id.ntp_customization_options_container));
+
+        // Spies on the containerView to avoid the inflating the MaterialSwitchWithText which
+        // requires complicated setting of themes.
+        doReturn(mListItemView).when(mContainerView).createListItemView();
+
+        mListContent = List.of(NTP_CARDS, DISCOVER_FEED);
+        when(mDelegate.getListItems()).thenReturn(mListContent);
+    }
+
+    @Test
+    @SmallTest
+    public void testDelegateInRenderAllListItems() {
+        mContainerView.renderAllListItems(mDelegate);
+
+        // Verifies that getListItems(), getTitle(), getSubtitle(), getTrailingIcon(), getListener()
+        // are called on delegate.
+        verify(mDelegate).getListItems();
+        for (int type : mListContent) {
+            verify(mDelegate).getListItemTitle(eq(type), any(Context.class));
+            verify(mDelegate).getListItemSubtitle(eq(type), any(Context.class));
+            verify(mDelegate).getTrailingIcon(eq(type));
+            verify(mDelegate).getListener(eq(type));
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void testRenderAllListItems() {
+        View.OnClickListener listener = view -> {};
+        for (int type : mListContent) {
+            when(mDelegate.getListener(type)).thenReturn(listener);
+        }
+
+        mContainerView.renderAllListItems(mDelegate);
+
+        // Verifies that titles, subtitles, backgrounds, trailing icons are set.
+        int itemListSize = mListContent.size();
+        verify(mListItemView, times(itemListSize)).setTitle(any());
+        verify(mListItemView, times(itemListSize)).setSubtitle(any());
+        verify(mListItemView, times(itemListSize)).setBackground(anyInt());
+        verify(mListItemView, times(itemListSize)).setTrailingIcon(anyInt());
+
+        // Verifies that if the listener is not null, it is set on the listItemView.
+        verify(mListItemView, times(itemListSize)).setOnClickListener(eq(listener));
+    }
+}
diff --git a/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/BottomSheetListItemViewUnitTest.java b/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/BottomSheetListItemViewUnitTest.java
new file mode 100644
index 0000000..e1787dc
--- /dev/null
+++ b/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/BottomSheetListItemViewUnitTest.java
@@ -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.
+
+package org.chromium.chrome.browser.ntp_customization;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ImageView;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.SmallTest;
+
+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.test.BaseRobolectricTestRunner;
+import org.chromium.ui.widget.TextViewWithLeading;
+
+/** Unit tests for {@link BottomSheetListItemView}. */
+@RunWith(BaseRobolectricTestRunner.class)
+public class BottomSheetListItemViewUnitTest {
+    @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+    private BottomSheetListItemView mListView;
+
+    @Before
+    public void setUp() {
+        Context context = ApplicationProvider.getApplicationContext();
+        mListView =
+                (BottomSheetListItemView)
+                        LayoutInflater.from(context)
+                                .inflate(R.layout.bottom_sheet_list_item_view, null, false);
+    }
+
+    @Test
+    @SmallTest
+    public void testSetTitle() {
+        String text = "New tab page cards";
+        mListView.setTitle(text);
+        TextViewWithLeading textView = mListView.findViewById(R.id.title);
+        assertEquals(text, textView.getText());
+    }
+
+    @Test
+    @SmallTest
+    public void testSetSubtitle() {
+        // Verifies when the subtitle is null, the visibility of the subtitle view is set to GONE.
+        TextViewWithLeading subtitleView = mListView.findViewById(R.id.subtitle);
+        mListView.setSubtitle(null);
+        assertEquals(View.GONE, subtitleView.getVisibility());
+
+        // Verifies when the subtitle is not null, the text is properly set in the subtitleView.
+        String text = "Off";
+        mListView.setSubtitle(text);
+        assertEquals(text, subtitleView.getText());
+    }
+
+    @Test
+    @SmallTest
+    public void testSetNullTrailingIcon() {
+        // Verifies when the resId given is null, the visibility of the icon is set to View.GONE.
+        ImageView iconView = mListView.findViewById(R.id.trailing_icon);
+        mListView.setTrailingIcon(null);
+        assertEquals(View.GONE, iconView.getVisibility());
+    }
+
+    @Test
+    @SmallTest
+    public void testSetNonNullTrailingIcon() {
+        // Verifies when the resId given is not null, the visibility of the icon is not View.GONE.
+        ImageView iconView = mListView.findViewById(R.id.trailing_icon);
+        mListView.setTrailingIcon(
+                R.drawable.ntp_customization_bottom_sheet_list_item_background_single);
+        assertEquals(View.VISIBLE, iconView.getVisibility());
+    }
+}
diff --git a/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/BottomSheetViewBinderUnitTest.java b/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/BottomSheetViewBinderUnitTest.java
new file mode 100644
index 0000000..70f1d703
--- /dev/null
+++ b/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/BottomSheetViewBinderUnitTest.java
@@ -0,0 +1,76 @@
+// 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.ntp_customization;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import static org.chromium.chrome.browser.ntp_customization.NtpCustomizationViewProperties.BACK_PRESS_HANDLER;
+
+import android.app.Activity;
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ViewFlipper;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.SmallTest;
+
+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.robolectric.Robolectric;
+
+import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.ui.modelutil.PropertyModel;
+import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
+
+/** Unit tests for {@link BottomSheetViewBinder} */
+@RunWith(BaseRobolectricTestRunner.class)
+public class BottomSheetViewBinderUnitTest {
+    @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+    private Context mContext;
+    private View mContentView;
+    private PropertyModel mPropertyModel;
+
+    @Before
+    public void setUp() {
+        mContext = ApplicationProvider.getApplicationContext();
+        Activity activity = Robolectric.buildActivity(Activity.class).setup().get();
+        activity.setTheme(R.style.Theme_BrowserUI_DayNight);
+        mContentView =
+                LayoutInflater.from(mContext)
+                        .inflate(R.layout.ntp_customization_bottom_sheet, /* root= */ null);
+        activity.setContentView(mContentView);
+        mPropertyModel = new PropertyModel(NtpCustomizationViewProperties.BOTTOM_SHEET_KEYS);
+    }
+
+    @Test
+    @SmallTest
+    public void testBindOnNtpCardsBottomSheet() {
+        // Adds the ntp cards bottom sheet to the view of the activity.
+        ViewFlipper viewFlipperView =
+                mContentView.findViewById(R.id.ntp_customization_view_flipper);
+        View ntpCardsBottomSheet =
+                LayoutInflater.from(mContext)
+                        .inflate(
+                                R.layout.ntp_customization_ntp_cards_bottom_sheet,
+                                viewFlipperView,
+                                true);
+
+        // Verifies the back press handler is added on the ntp cards bottom sheet.
+        PropertyModelChangeProcessor.create(
+                mPropertyModel, ntpCardsBottomSheet, BottomSheetViewBinder::bind);
+        View.OnClickListener backPressHandler = mock(View.OnClickListener.class);
+        mPropertyModel.set(BACK_PRESS_HANDLER, backPressHandler);
+        View backButton = mContentView.findViewById(R.id.back_button);
+        backButton.performClick();
+        verify(backPressHandler).onClick(backButton);
+    }
+}
diff --git a/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/NtpCardsCoordinatorUnitTest.java b/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/NtpCardsCoordinatorUnitTest.java
index 97ca284..4cedb7a 100644
--- a/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/NtpCardsCoordinatorUnitTest.java
+++ b/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/NtpCardsCoordinatorUnitTest.java
@@ -4,15 +4,11 @@
 
 package org.chromium.chrome.browser.ntp_customization;
 
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 
-import static org.chromium.chrome.browser.ntp_customization.NtpCustomizationCoordinator.BottomSheetType.NTP_CARDS;
-import static org.chromium.chrome.browser.ntp_customization.NtpCustomizationViewProperties.NTP_CARDS_BACK_PRESS_HANDLER;
-
 import android.content.Context;
-import android.view.View;
 
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.filters.SmallTest;
@@ -21,36 +17,37 @@
 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.chromium.base.test.BaseRobolectricTestRunner;
-import org.chromium.ui.modelutil.PropertyModel;
 
 /** Unit tests for {@link NtpCardsCoordinator} */
 @RunWith(BaseRobolectricTestRunner.class)
 public class NtpCardsCoordinatorUnitTest {
     @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
 
-    @Mock BottomSheetDelegate mDelegate;
-    @Mock PropertyModel mPropertyModel;
-
-    private NtpCardsCoordinator mNtpCardsCoordinator;
+    private NtpCardsCoordinator mCoordinator;
 
     @Before
     public void setUp() {
         Context context = ApplicationProvider.getApplicationContext();
-        mNtpCardsCoordinator = new NtpCardsCoordinator(context, mDelegate, mPropertyModel);
+        mCoordinator = new NtpCardsCoordinator(context, mock(BottomSheetDelegate.class));
     }
 
     @Test
     @SmallTest
     public void testConstructor() {
-        // Verifies the view is added to view flipper and the back press listener is set in the
-        // property model
-        verify(mDelegate).registerBottomSheetLayout(eq(NTP_CARDS), any(View.class));
-        verify(mPropertyModel)
-                .set(eq(NTP_CARDS_BACK_PRESS_HANDLER), any(View.OnClickListener.class));
+        assertNotNull(mCoordinator.getMediatorForTesting());
+    }
+
+    @Test
+    @SmallTest
+    public void testDestroy() {
+        NtpCardsMediator mediator = mock(NtpCardsMediator.class);
+        mCoordinator.setMediatorForTesting(mediator);
+
+        mCoordinator.destroy();
+        verify(mediator).destroy();
     }
 }
diff --git a/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/NtpCardsListContainerViewUnitTest.java b/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/NtpCardsListContainerViewUnitTest.java
new file mode 100644
index 0000000..696577c
--- /dev/null
+++ b/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/NtpCardsListContainerViewUnitTest.java
@@ -0,0 +1,130 @@
+// 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.ntp_customization;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import static org.chromium.chrome.browser.magic_stack.ModuleDelegate.ModuleType.PRICE_CHANGE;
+import static org.chromium.chrome.browser.magic_stack.ModuleDelegate.ModuleType.SAFETY_HUB;
+import static org.chromium.chrome.browser.magic_stack.ModuleDelegate.ModuleType.SINGLE_TAB;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.CompoundButton;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.chrome.browser.magic_stack.HomeModulesConfigManager;
+
+import java.util.List;
+
+/** Unit tests for {@link NtpCardsListContainerView}. */
+@RunWith(BaseRobolectricTestRunner.class)
+public class NtpCardsListContainerViewUnitTest {
+    @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+    @Mock ListContainerViewDelegate mDelegate;
+    @Mock NtpCardsListItemView mListItemView;
+
+    @Captor
+    private ArgumentCaptor<CompoundButton.OnCheckedChangeListener> mOnCheckedChangeListenerCaptor;
+
+    private NtpCardsListContainerView mContainerView;
+    private List<Integer> mListContent;
+
+    @Before
+    public void setUp() {
+        Context context = ApplicationProvider.getApplicationContext();
+        View view =
+                LayoutInflater.from(context)
+                        .inflate(
+                                org.chromium.chrome.browser.ntp_customization.R.layout
+                                        .ntp_customization_ntp_cards_bottom_sheet,
+                                null,
+                                false);
+        mContainerView = spy(view.findViewById(R.id.ntp_cards_container));
+        mListContent = List.of(SINGLE_TAB, SAFETY_HUB, PRICE_CHANGE);
+        when(mDelegate.getListItems()).thenReturn(mListContent);
+        doReturn(mListItemView).when(mContainerView).createListItemView();
+    }
+
+    @Test
+    @SmallTest
+    public void testDelegateInRenderAllListItems() {
+        mContainerView.renderAllListItems(mDelegate);
+
+        // Verifies that only mDelegate.getListItems() and mDelegate.getListItemTitle() are called
+        // when creating list items.
+        verify(mDelegate).getListItems();
+        for (int type : mListContent) {
+            verify(mDelegate).getListItemTitle(eq(type), any(Context.class));
+        }
+        verify(mDelegate, never()).getListener(anyInt());
+        verify(mDelegate, never()).getTrailingIcon(anyInt());
+        verify(mDelegate, never()).getListItemSubtitle(anyInt(), any(Context.class));
+    }
+
+    @Test
+    @SmallTest
+    public void testRenderAllListItems() {
+        mContainerView.renderAllListItems(mDelegate);
+
+        // Verifies the title, background, and switch are set.
+        int itemListSize = mListContent.size();
+        verify(mListItemView, times(itemListSize)).setTitle(any());
+        verify(mListItemView, times(itemListSize)).setBackground(any());
+        verify(mContainerView, times(itemListSize))
+                .setUpSwitch(
+                        any(HomeModulesConfigManager.class),
+                        any(NtpCardsListItemView.class),
+                        anyInt());
+    }
+
+    @Test
+    @SmallTest
+    public void testSetUpSwitch() {
+        HomeModulesConfigManager manager = mock(HomeModulesConfigManager.class);
+        NtpCardsListItemView listItemView = mock(NtpCardsListItemView.class);
+
+        // Verifies that getPrefModuleTypeEnabled() is called.
+        mContainerView.setUpSwitch(manager, listItemView, 10);
+        verify(manager).getPrefModuleTypeEnabled(10);
+
+        // Verifies setPrefModuleTypeEnabled() is called inside the OnCheckedChangeListener to
+        // update the checked state of the switch.
+        verify(listItemView).setOnCheckedChangeListener(mOnCheckedChangeListenerCaptor.capture());
+        mOnCheckedChangeListenerCaptor
+                .getValue()
+                .onCheckedChanged(mock(CompoundButton.class), true);
+        verify(manager).setPrefModuleTypeEnabled(10, true);
+
+        mOnCheckedChangeListenerCaptor
+                .getValue()
+                .onCheckedChanged(mock(CompoundButton.class), false);
+        verify(manager).setPrefModuleTypeEnabled(10, false);
+    }
+}
diff --git a/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/NtpCardsListItemViewUnitTest.java b/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/NtpCardsListItemViewUnitTest.java
new file mode 100644
index 0000000..e2952b54
--- /dev/null
+++ b/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/NtpCardsListItemViewUnitTest.java
@@ -0,0 +1,70 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.ntp_customization;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.widget.CompoundButton;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+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.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.components.browser_ui.widget.MaterialSwitchWithText;
+
+/** Unit tests for {@link NtpCardsListItemView}. */
+@RunWith(BaseRobolectricTestRunner.class)
+public class NtpCardsListItemViewUnitTest {
+    @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+    @Mock private MaterialSwitchWithText mMaterialSwitchWithText;
+
+    private NtpCardsListItemView mListView;
+
+    @Before
+    public void setUp() {
+        Context context = ApplicationProvider.getApplicationContext();
+        mListView = new NtpCardsListItemView(context, null);
+        mListView.setMaterialSwitchWithTextForTesting(mMaterialSwitchWithText);
+    }
+
+    @Test
+    @SmallTest
+    public void testSetTitle() {
+        String text = "New tab page cards";
+        mListView.setTitle(text);
+        verify(mMaterialSwitchWithText).setText(text);
+    }
+
+    @Test
+    @SmallTest
+    public void testSetChecked() {
+        mListView.setChecked(false);
+        verify(mMaterialSwitchWithText).setChecked(eq(false));
+
+        mListView.setChecked(true);
+        verify(mMaterialSwitchWithText).setChecked(eq(true));
+    }
+
+    @Test
+    @SmallTest
+    public void testSetOnCheckedChangeListener() {
+        CompoundButton.OnCheckedChangeListener listener =
+                mock(CompoundButton.OnCheckedChangeListener.class);
+        mListView.setOnCheckedChangeListener(listener);
+        verify(mMaterialSwitchWithText).setOnCheckedChangeListener(eq(listener));
+    }
+}
diff --git a/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/NtpCardsMediatorUnitTest.java b/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/NtpCardsMediatorUnitTest.java
new file mode 100644
index 0000000..00651d3
--- /dev/null
+++ b/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/NtpCardsMediatorUnitTest.java
@@ -0,0 +1,116 @@
+// 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.ntp_customization;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+import static org.chromium.chrome.browser.magic_stack.ModuleDelegate.ModuleType.AUXILIARY_SEARCH;
+import static org.chromium.chrome.browser.magic_stack.ModuleDelegate.ModuleType.DEFAULT_BROWSER_PROMO;
+import static org.chromium.chrome.browser.magic_stack.ModuleDelegate.ModuleType.PRICE_CHANGE;
+import static org.chromium.chrome.browser.magic_stack.ModuleDelegate.ModuleType.QUICK_DELETE_PROMO;
+import static org.chromium.chrome.browser.magic_stack.ModuleDelegate.ModuleType.SAFETY_HUB;
+import static org.chromium.chrome.browser.magic_stack.ModuleDelegate.ModuleType.SINGLE_TAB;
+import static org.chromium.chrome.browser.magic_stack.ModuleDelegate.ModuleType.TAB_GROUP_PROMO;
+import static org.chromium.chrome.browser.magic_stack.ModuleDelegate.ModuleType.TAB_GROUP_SYNC_PROMO;
+import static org.chromium.chrome.browser.magic_stack.ModuleDelegate.ModuleType.TAB_RESUMPTION;
+import static org.chromium.chrome.browser.ntp_customization.NtpCustomizationViewProperties.BACK_PRESS_HANDLER;
+import static org.chromium.chrome.browser.ntp_customization.NtpCustomizationViewProperties.LIST_CONTAINER_VIEW_DELEGATE;
+
+import android.content.Context;
+import android.view.View;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.chrome.browser.magic_stack.HomeModulesConfigManager;
+import org.chromium.chrome.browser.magic_stack.HomeModulesUtils;
+import org.chromium.ui.modelutil.PropertyModel;
+
+import java.util.List;
+
+/** Unit tests for {@link NtpCardsMediator} */
+@RunWith(BaseRobolectricTestRunner.class)
+public class NtpCardsMediatorUnitTest {
+    @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+    @Mock private PropertyModel mContainerPropertyModel;
+    @Mock private PropertyModel mBottomSheetPropertyModel;
+    @Mock private BottomSheetDelegate mDelegate;
+    @Captor private ArgumentCaptor<View.OnClickListener> mBackPressHandlerCaptor;
+
+    private NtpCardsMediator mNtpCardsMediator;
+    private Context mContext;
+
+    @Before
+    public void setUp() {
+        mContext = ApplicationProvider.getApplicationContext();
+        mNtpCardsMediator =
+                new NtpCardsMediator(mContainerPropertyModel, mBottomSheetPropertyModel, mDelegate);
+    }
+
+    @Test
+    @SmallTest
+    public void testConstructor() {
+        verify(mContainerPropertyModel)
+                .set(eq(LIST_CONTAINER_VIEW_DELEGATE), any(ListContainerViewDelegate.class));
+        verify(mBottomSheetPropertyModel)
+                .set(eq(BACK_PRESS_HANDLER), mBackPressHandlerCaptor.capture());
+        mBackPressHandlerCaptor.getValue().onClick(new View(mContext));
+        verify(mDelegate).backPressOnCurrentBottomSheet();
+    }
+
+    @Test
+    @SmallTest
+    public void testListContainerViewDelegate() {
+        ListContainerViewDelegate delegate = mNtpCardsMediator.createListDelegate();
+        HomeModulesConfigManager homeModulesConfigManager = HomeModulesConfigManager.getInstance();
+
+        // Verifies that the content of the delegate.getListItems() comes from
+        // homeModulesConfigManager.
+        List<Integer> content = delegate.getListItems();
+        assertEquals(content, homeModulesConfigManager.getModuleListShownInSettings());
+
+        // Verifies that the titles of list items come from HomeModulesUtils.
+        List<Integer> types =
+                List.of(
+                        SINGLE_TAB,
+                        TAB_RESUMPTION,
+                        PRICE_CHANGE,
+                        SAFETY_HUB,
+                        AUXILIARY_SEARCH,
+                        DEFAULT_BROWSER_PROMO,
+                        TAB_GROUP_PROMO,
+                        TAB_GROUP_SYNC_PROMO,
+                        QUICK_DELETE_PROMO);
+        for (int type : types) {
+            assertEquals(
+                    HomeModulesUtils.getTitleForModuleType(type, mContext.getResources()),
+                    delegate.getListItemTitle(type, mContext));
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void testDestroy() {
+        mNtpCardsMediator.destroy();
+
+        verify(mBottomSheetPropertyModel).set(eq(BACK_PRESS_HANDLER), eq(null));
+        verify(mContainerPropertyModel).set(eq(LIST_CONTAINER_VIEW_DELEGATE), eq(null));
+    }
+}
diff --git a/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/NtpCustomizationCoordinatorUnitTest.java b/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/NtpCustomizationCoordinatorUnitTest.java
index fe0a7b5..9c568a7 100644
--- a/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/NtpCustomizationCoordinatorUnitTest.java
+++ b/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/NtpCustomizationCoordinatorUnitTest.java
@@ -5,13 +5,12 @@
 package org.chromium.chrome.browser.ntp_customization;
 
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 
 import static org.chromium.chrome.browser.ntp_customization.NtpCustomizationCoordinator.BottomSheetType.MAIN;
-import static org.chromium.chrome.browser.ntp_customization.NtpCustomizationViewProperties.NTP_CARDS_BACK_PRESS_HANDLER;
-import static org.chromium.chrome.browser.ntp_customization.NtpCustomizationViewProperties.NTP_CARDS_OPTION_CLICK_LISTENER;
+import static org.chromium.chrome.browser.ntp_customization.NtpCustomizationCoordinator.BottomSheetType.NTP_CARDS;
 
 import android.content.Context;
 import android.view.View;
@@ -30,7 +29,6 @@
 
 import org.chromium.base.test.BaseRobolectricTestRunner;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
-import org.chromium.ui.modelutil.PropertyModel;
 
 /** Unit tests for {@link NtpCustomizationCoordinator} */
 @RunWith(BaseRobolectricTestRunner.class)
@@ -39,7 +37,6 @@
 
     @Mock private BottomSheetController mBottomSheetController;
     @Mock private NtpCustomizationMediator mMediator;
-    @Mock private PropertyModel mPropertyModel;
     @Mock private View mView;
     @Mock private ViewFlipper mViewFlipper;
 
@@ -50,27 +47,9 @@
     public void setUp() {
         mContext = ApplicationProvider.getApplicationContext();
         mNtpCustomizationCoordinator =
-                new NtpCustomizationCoordinator(mContext, mBottomSheetController, mPropertyModel);
-        mNtpCustomizationCoordinator.setMediatorForTesting(mMediator);
+                new NtpCustomizationCoordinator(mContext, mBottomSheetController);
         mNtpCustomizationCoordinator.setViewFlipperForTesting(mViewFlipper);
-    }
-
-    @Test
-    @SmallTest
-    public void testConstructor() {
-        // Verifies that the mNtpCardsCoordinator is initialized.
-        NtpCustomizationCoordinator coordinator =
-                new NtpCustomizationCoordinator(
-                        mContext,
-                        mBottomSheetController,
-                        new PropertyModel(NtpCustomizationViewProperties.ALL_KEYS));
-
-        View ntpCards =
-                coordinator
-                        .getContentViewForTesting()
-                        .findViewById(R.id.new_tab_page_cards_list_item_container);
-        ntpCards.performClick();
-        assertNotNull(coordinator.getNtpCardsCoordinatorForTesting());
+        mNtpCustomizationCoordinator.setMediatorForTesting(mMediator);
     }
 
     @Test
@@ -82,8 +61,9 @@
 
     @Test
     @SmallTest
-    public void testImplementBottomSheetDelegate() {
-        BottomSheetDelegate delegate = mNtpCustomizationCoordinator.getDelegateForTesting();
+    public void testBottomSheetDelegateImplementation() {
+        BottomSheetDelegate delegate =
+                mNtpCustomizationCoordinator.getBottomSheetDelegateForTesting();
 
         // Verifies each implementation calls the corresponding method of the mediator.
         delegate.registerBottomSheetLayout(11, mView);
@@ -96,12 +76,25 @@
 
     @Test
     @SmallTest
+    public void testGetOptionClickListener() {
+        View.OnClickListener listener =
+                mNtpCustomizationCoordinator.getOptionClickListener(NTP_CARDS);
+
+        listener.onClick(new View(mContext));
+        assertNotNull(mNtpCustomizationCoordinator.getNtpCardsCoordinatorForTesting());
+        verify(mMediator).showBottomSheet(eq(NTP_CARDS));
+    }
+
+    @Test
+    @SmallTest
     public void testDestroy() {
+        NtpCardsCoordinator ntpCardsCoordinator = mock(NtpCardsCoordinator.class);
+        mNtpCustomizationCoordinator.setNtpCardsCoordinatorForTesting(ntpCardsCoordinator);
+
         mNtpCustomizationCoordinator.destroy();
 
-        assertNull(mPropertyModel.get(NTP_CARDS_OPTION_CLICK_LISTENER));
-        assertNull(mPropertyModel.get(NTP_CARDS_BACK_PRESS_HANDLER));
         verify(mViewFlipper).removeAllViews();
         verify(mMediator).destroy();
+        verify(ntpCardsCoordinator).destroy();
     }
 }
diff --git a/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/NtpCustomizationMediatorUnitTest.java b/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/NtpCustomizationMediatorUnitTest.java
index b4925ef..e64b9040 100644
--- a/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/NtpCustomizationMediatorUnitTest.java
+++ b/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/NtpCustomizationMediatorUnitTest.java
@@ -12,11 +12,20 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 
+import static org.chromium.chrome.browser.ntp_customization.NtpCustomizationCoordinator.BottomSheetType.DISCOVER_FEED;
+import static org.chromium.chrome.browser.ntp_customization.NtpCustomizationCoordinator.BottomSheetType.MAIN;
+import static org.chromium.chrome.browser.ntp_customization.NtpCustomizationCoordinator.BottomSheetType.NTP_CARDS;
 import static org.chromium.chrome.browser.ntp_customization.NtpCustomizationViewProperties.LAYOUT_TO_DISPLAY;
+import static org.chromium.chrome.browser.ntp_customization.NtpCustomizationViewProperties.LIST_CONTAINER_VIEW_DELEGATE;
 
+import android.content.Context;
+import android.view.View;
+
+import androidx.test.core.app.ApplicationProvider;
 import androidx.test.filters.SmallTest;
 
 import org.junit.Before;
@@ -35,6 +44,7 @@
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetObserver;
 import org.chromium.ui.modelutil.PropertyModel;
 
+import java.util.List;
 import java.util.Map;
 
 /** Unit tests for {@link NtpCustomizationMediator} */
@@ -44,17 +54,25 @@
 
     @Mock private BottomSheetController mBottomSheetController;
     @Mock private NtpCustomizationBottomSheetContent mBottomSheetContent;
-    @Mock private PropertyModel mPropertyModel;
+    @Mock private PropertyModel mViewFlipperPropertyModel;
+    @Mock private PropertyModel mContainerPropertyModel;
 
     private NtpCustomizationMediator mMediator;
     private Map<Integer, Integer> mViewFlipperMap;
+    private ListContainerViewDelegate mListDelegate;
+    private Context mContext;
 
     @Before
     public void setUp() {
+        mContext = ApplicationProvider.getApplicationContext();
         mMediator =
                 new NtpCustomizationMediator(
-                        mBottomSheetController, mBottomSheetContent, mPropertyModel);
+                        mBottomSheetController,
+                        mBottomSheetContent,
+                        mViewFlipperPropertyModel,
+                        mContainerPropertyModel);
         mViewFlipperMap = mMediator.getViewFlipperMapForTesting();
+        mListDelegate = mMediator.createListDelegate();
     }
 
     @Test
@@ -128,7 +146,7 @@
 
         mMediator.showBottomSheet(bottomSheetType);
 
-        verify(mPropertyModel).set(eq(LAYOUT_TO_DISPLAY), eq(viewFlipperIndex));
+        verify(mViewFlipperPropertyModel).set(eq(LAYOUT_TO_DISPLAY), eq(viewFlipperIndex));
         assertEquals(bottomSheetType, (int) mMediator.getCurrentBottomSheetForTesting());
     }
 
@@ -151,7 +169,7 @@
 
         verify(mBottomSheetController, never())
                 .hideContent(any(BottomSheetContent.class), anyBoolean());
-        verify(mPropertyModel, never()).set(eq(LAYOUT_TO_DISPLAY), anyInt());
+        verify(mViewFlipperPropertyModel, never()).set(eq(LAYOUT_TO_DISPLAY), anyInt());
     }
 
     @Test
@@ -166,7 +184,7 @@
         assertNull(mMediator.getCurrentBottomSheetForTesting());
 
         // Verifies that showBottomSheet() is not called.
-        verify(mPropertyModel, never()).set(eq(LAYOUT_TO_DISPLAY), anyInt());
+        verify(mViewFlipperPropertyModel, never()).set(eq(LAYOUT_TO_DISPLAY), anyInt());
     }
 
     @Test
@@ -182,25 +200,34 @@
         verify(mBottomSheetController, never())
                 .hideContent(any(BottomSheetContent.class), anyBoolean());
         assertEquals(BottomSheetType.MAIN, (int) mMediator.getCurrentBottomSheetForTesting());
-        verify(mPropertyModel).set(eq(LAYOUT_TO_DISPLAY), eq(10));
+        verify(mViewFlipperPropertyModel).set(eq(LAYOUT_TO_DISPLAY), eq(10));
     }
 
     @Test
     @SmallTest
     public void testDestroy() {
+        // Verifies mViewFlipperMap is cleared.
         mViewFlipperMap.put(BottomSheetType.NTP_CARDS, 9);
         mViewFlipperMap.put(BottomSheetType.MAIN, 10);
 
         assertEquals(2, mViewFlipperMap.size());
         mMediator.destroy();
         assertEquals(0, mViewFlipperMap.size());
+
+        // Verifies mTypeToListenerMap is cleared.
+        Map<Integer, View.OnClickListener> typeToListenerMap =
+                mMediator.getTypeToListenersForTesting();
+        typeToListenerMap.put(BottomSheetType.NTP_CARDS, view -> {});
+        assertEquals(1, typeToListenerMap.size());
+        mMediator.destroy();
+        assertEquals(0, typeToListenerMap.size());
     }
 
     @Test
     @SmallTest
     public void testBottomSheetObserver() {
         // Verifies the supplier is set to true when the sheet opens.
-        BottomSheetObserver observer = mMediator.getBottomSheetObserver();
+        BottomSheetObserver observer = mMediator.getBottomSheetObserverForTesting();
         observer.onSheetOpened(0);
         verify(mBottomSheetContent).onSheetOpened();
 
@@ -215,4 +242,44 @@
         verify(mBottomSheetContent).onSheetClosed();
         verify(mBottomSheetController).removeObserver(eq(observer));
     }
+
+    @Test
+    @SmallTest
+    public void testListContainerViewDelegate() {
+        // Verifies that the content of the delegate.getListItems() is consist of MAIN and FEEDS
+        // while MAIN comes before FEEDS.
+        List<Integer> content = mListDelegate.getListItems();
+        assertEquals(NTP_CARDS, (int) content.get(0));
+        assertEquals(DISCOVER_FEED, (int) content.get(1));
+
+        // Verifies the subtitle of the "feeds" list item is "On" and is null for other list item.
+        assertEquals("On", mListDelegate.getListItemSubtitle(DISCOVER_FEED, mContext));
+        assertNull(mListDelegate.getListItemSubtitle(MAIN, mContext));
+
+        // Verifies the listener returned from the delegate is in mTypeToListeners map.
+        Map<Integer, View.OnClickListener> typeToListenerMap =
+                mMediator.getTypeToListenersForTesting();
+        View.OnClickListener ntpListener = mock(View.OnClickListener.class);
+        View.OnClickListener feedsListener = mock(View.OnClickListener.class);
+        typeToListenerMap.put(NTP_CARDS, ntpListener);
+        typeToListenerMap.put(DISCOVER_FEED, feedsListener);
+        assertEquals(ntpListener, mListDelegate.getListener(NTP_CARDS));
+        assertEquals(feedsListener, mListDelegate.getListener(DISCOVER_FEED));
+    }
+
+    @Test
+    @SmallTest
+    public void testRegisterClickListener() {
+        View.OnClickListener listener = mock(View.OnClickListener.class);
+        mMediator.registerClickListener(10, listener);
+        assertEquals(listener, mMediator.getTypeToListenersForTesting().get(10));
+    }
+
+    @Test
+    @SmallTest
+    public void testRenderContent() {
+        mMediator.renderListContent();
+        verify(mContainerPropertyModel)
+                .set(eq(LIST_CONTAINER_VIEW_DELEGATE), any(ListContainerViewDelegate.class));
+    }
 }
diff --git a/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/NtpCardsContainerViewUnitTest.java b/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/NtpCustomizationUtilsUnitTest.java
similarity index 68%
rename from chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/NtpCardsContainerViewUnitTest.java
rename to chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/NtpCustomizationUtilsUnitTest.java
index e99b803..16a20c3 100644
--- a/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/NtpCardsContainerViewUnitTest.java
+++ b/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/NtpCustomizationUtilsUnitTest.java
@@ -6,10 +6,10 @@
 
 import static org.junit.Assert.assertEquals;
 
-import androidx.test.core.app.ApplicationProvider;
+import static org.chromium.chrome.browser.ntp_customization.NtpCustomizationUtils.getBackground;
+
 import androidx.test.filters.SmallTest;
 
-import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -18,46 +18,38 @@
 
 import org.chromium.base.test.BaseRobolectricTestRunner;
 
-/** Unit tests for {@link NtpCardsContainerView} */
+/** Unit tests for {@link NtpCustomizationUtils} */
 @RunWith(BaseRobolectricTestRunner.class)
-public class NtpCardsContainerViewUnitTest {
+public class NtpCustomizationUtilsUnitTest {
     @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
 
-    private NtpCardsContainerView mContainerView;
-
-    @Before
-    public void setup() {
-        mContainerView =
-                new NtpCardsContainerView(ApplicationProvider.getApplicationContext(), null);
-    }
-
     @Test
     @SmallTest
     public void testGetBackgroundSizeOne() {
-        int resId = mContainerView.getBackground(1, 0);
+        int resId = getBackground(1, 0);
         assertEquals(R.drawable.ntp_customization_bottom_sheet_list_item_background_single, resId);
     }
 
     @Test
     @SmallTest
     public void testGetBackgroundSizeTwo() {
-        int resId = mContainerView.getBackground(2, 0);
+        int resId = getBackground(2, 0);
         assertEquals(R.drawable.ntp_customization_bottom_sheet_list_item_background_top, resId);
 
-        resId = mContainerView.getBackground(2, 1);
+        resId = getBackground(2, 1);
         assertEquals(R.drawable.ntp_customization_bottom_sheet_list_item_background_bottom, resId);
     }
 
     @Test
     @SmallTest
     public void testGetBackgroundSizeThree() {
-        int resId = mContainerView.getBackground(3, 0);
+        int resId = getBackground(3, 0);
         assertEquals(R.drawable.ntp_customization_bottom_sheet_list_item_background_top, resId);
 
-        resId = mContainerView.getBackground(3, 1);
+        resId = getBackground(3, 1);
         assertEquals(R.drawable.ntp_customization_bottom_sheet_list_item_background_middle, resId);
 
-        resId = mContainerView.getBackground(3, 2);
+        resId = getBackground(3, 2);
         assertEquals(R.drawable.ntp_customization_bottom_sheet_list_item_background_bottom, resId);
     }
 
@@ -65,16 +57,16 @@
     @SmallTest
     public void testGetBackgroundLargeSize() {
         int listSize = 10;
-        int resId = mContainerView.getBackground(listSize, 0);
+        int resId = getBackground(listSize, 0);
         assertEquals(R.drawable.ntp_customization_bottom_sheet_list_item_background_top, resId);
 
         for (int index = 1; index < listSize - 1; index++) {
-            resId = mContainerView.getBackground(listSize, index);
+            resId = getBackground(listSize, index);
             assertEquals(
                     R.drawable.ntp_customization_bottom_sheet_list_item_background_middle, resId);
         }
 
-        resId = mContainerView.getBackground(listSize, listSize - 1);
+        resId = getBackground(listSize, listSize - 1);
         assertEquals(R.drawable.ntp_customization_bottom_sheet_list_item_background_bottom, resId);
     }
 }
diff --git a/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/NtpCustomizationViewBinderUnitTest.java b/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/NtpCustomizationViewBinderUnitTest.java
deleted file mode 100644
index 734d9ca4..0000000
--- a/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/NtpCustomizationViewBinderUnitTest.java
+++ /dev/null
@@ -1,104 +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.chrome.browser.ntp_customization;
-
-import static org.junit.Assert.assertNotNull;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-
-import static org.chromium.chrome.browser.ntp_customization.NtpCustomizationViewProperties.LAYOUT_TO_DISPLAY;
-import static org.chromium.chrome.browser.ntp_customization.NtpCustomizationViewProperties.NTP_CARDS_BACK_PRESS_HANDLER;
-import static org.chromium.chrome.browser.ntp_customization.NtpCustomizationViewProperties.NTP_CARDS_OPTION_CLICK_LISTENER;
-
-import android.app.Activity;
-import android.content.Context;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.ViewFlipper;
-
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.filters.SmallTest;
-
-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.robolectric.Robolectric;
-
-import org.chromium.base.test.BaseRobolectricTestRunner;
-import org.chromium.ui.modelutil.PropertyModel;
-import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
-
-/** Unit tests for {@link NtpCustomizationCoordinator} */
-@RunWith(BaseRobolectricTestRunner.class)
-public class NtpCustomizationViewBinderUnitTest {
-    @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
-
-    private Context mContext;
-    private View mContentView;
-    private ViewFlipper mViewFlipperView;
-    private PropertyModel mPropertyModel;
-
-    @Before
-    public void setUp() {
-        mContext = ApplicationProvider.getApplicationContext();
-        Activity activity = Robolectric.buildActivity(Activity.class).setup().get();
-        activity.setTheme(R.style.Theme_BrowserUI_DayNight);
-        mContentView =
-                LayoutInflater.from(mContext)
-                        .inflate(R.layout.ntp_customization_bottom_sheet, /* root= */ null);
-        activity.setContentView(mContentView);
-
-        mPropertyModel = new PropertyModel(NtpCustomizationViewProperties.ALL_KEYS);
-        mViewFlipperView = mContentView.findViewById(R.id.ntp_customization_view_flipper);
-        PropertyModelChangeProcessor.create(
-                mPropertyModel, mViewFlipperView, NtpCustomizationViewBinder::bind);
-    }
-
-    @Test
-    @SmallTest
-    public void testSetNtpCardsOptionClickListener() {
-        // Adds the main bottom sheet to mViewFlipperView.
-        LayoutInflater.from(mContext)
-                .inflate(R.layout.ntp_customization_main_bottom_sheet, mViewFlipperView, true);
-        View.OnClickListener ntpCardsClickListener = mock(View.OnClickListener.class);
-
-        mPropertyModel.set(NTP_CARDS_OPTION_CLICK_LISTENER, ntpCardsClickListener);
-        View ntpCards = mContentView.findViewById(R.id.new_tab_page_cards_list_item_container);
-
-        assertNotNull(ntpCards);
-        ntpCards.performClick();
-        verify(ntpCardsClickListener).onClick(ntpCards);
-    }
-
-    @Test
-    @SmallTest
-    public void testSetNtpCardsBackPressHandler() {
-        // Adds the ntp_cards bottom sheet to mViewFlipperView.
-        LayoutInflater.from(mContext)
-                .inflate(R.layout.ntp_customization_ntp_cards_bottom_sheet, mViewFlipperView, true);
-        View.OnClickListener backPressHandler = mock(View.OnClickListener.class);
-
-        mPropertyModel.set(NTP_CARDS_BACK_PRESS_HANDLER, backPressHandler);
-        View backButton = mContentView.findViewById(R.id.ntp_cards_back_button);
-
-        assertNotNull(backButton);
-        backButton.performClick();
-        verify(backPressHandler).onClick(backButton);
-    }
-
-    @Test
-    @SmallTest
-    public void testSetLayoutToDisplay() {
-        ViewFlipper viewFlipperMock = mock(ViewFlipper.class);
-        PropertyModelChangeProcessor.create(
-                mPropertyModel, viewFlipperMock, NtpCustomizationViewBinder::bind);
-        mPropertyModel.set(LAYOUT_TO_DISPLAY, 10);
-
-        verify(viewFlipperMock).setDisplayedChild(10);
-    }
-}
diff --git a/chrome/browser/optimization_guide/model_execution/model_execution_browsertest.cc b/chrome/browser/optimization_guide/model_execution/model_execution_browsertest.cc
index 32af1cb..f170ab1 100644
--- a/chrome/browser/optimization_guide/model_execution/model_execution_browsertest.cc
+++ b/chrome/browser/optimization_guide/model_execution/model_execution_browsertest.cc
@@ -33,6 +33,7 @@
 #include "components/optimization_guide/core/model_execution/on_device_model_service_controller.h"
 #include "components/optimization_guide/core/model_execution/optimization_guide_model_execution_error.h"
 #include "components/optimization_guide/core/model_execution/test/fake_model_assets.h"
+#include "components/optimization_guide/core/model_execution/test/feature_config_builder.h"
 #include "components/optimization_guide/core/model_quality/model_execution_logging_wrappers.h"
 #include "components/optimization_guide/core/model_quality/model_quality_log_entry.h"
 #include "components/optimization_guide/core/optimization_guide_constants.h"
@@ -687,79 +688,89 @@
           {{"compatible_on_device_performance_classes", "*"}}}},
         {});
   }
-  void SetUpBaseModel() {
+
+  void SetUpGlobalAssets() {
     model_execution::prefs::RecordFeatureUsage(
         g_browser_process->local_state(), ModelBasedCapabilityKey::kCompose);
     base_model_asset_.SetReadyIn(
         *OnDeviceModelComponentStateManager::GetInstanceForTesting());
   }
 
-  void SetUpComposeModelExecutionConfig() {
-    proto::OnDeviceModelExecutionFeatureConfig feature_config;
-    feature_config.set_can_skip_text_safety(true);
-    auto sampling_params_proto =
-        std::make_unique<optimization_guide::proto::SamplingParams>();
-    sampling_params_proto->set_top_k(kTestDefaultTopK);
-    sampling_params_proto->set_temperature(kTestDefaultTemperature);
-    feature_config.set_allocated_sampling_params(
-        sampling_params_proto.release());
-    auto metadata = OnDeviceModelAdaptationMetadata::New(
-        nullptr, 123,
-        base::MakeRefCounted<OnDeviceModelFeatureAdapter>(
-            std::move(feature_config)));
-    ChromeOnDeviceModelServiceController::GetSingleInstanceMayBeNull()
-        ->MaybeUpdateModelAdaptation(ModelBasedCapabilityKey::kCompose,
-                                     std::move(metadata));
-    base::test::RunUntil([&]() {
-      return ChromeOnDeviceModelServiceController::GetSingleInstanceMayBeNull()
-          ->model_metadata_.get();
-    });
+  // Set up assets which are registered per-profile.
+  void SetUpProfileAssets() {
+    compose_asset_.SendTo(
+        *ChromeOnDeviceModelServiceController::GetSingleInstanceMayBeNull());
   }
 
  private:
   optimization_guide::FakeBaseModelAsset base_model_asset_;
+  FakeAdaptationAsset compose_asset_{{
+      .config =
+          []() {
+            proto::OnDeviceModelExecutionFeatureConfig config;
+            config.set_feature(proto::MODEL_EXECUTION_FEATURE_COMPOSE);
+            config.set_can_skip_text_safety(true);
+            auto* params = config.mutable_sampling_params();
+            params->set_top_k(kTestDefaultTopK);
+            params->set_temperature(kTestDefaultTemperature);
+            return config;
+          }(),
+  }};
 };
 
 IN_PROC_BROWSER_TEST_F(OnDeviceModelExecutionEnabledBrowserTest,
                        GetOnDeviceModelEligibilityInRegularProfile) {
-  SetUpBaseModel();
-  SetUpComposeModelExecutionConfig();
+  SetUpGlobalAssets();
+  SetUpProfileAssets();
 
-  EXPECT_EQ(GetOnDeviceModelEligibility(ModelBasedCapabilityKey::kCompose),
-            OnDeviceModelEligibilityReason::kSuccess);
+  ASSERT_TRUE(base::test::RunUntil([&]() {
+    return GetOnDeviceModelEligibility(ModelBasedCapabilityKey::kCompose,
+                                       nullptr) ==
+           OnDeviceModelEligibilityReason::kSuccess;
+  })) << "Timeout waiting for model to be marked eligible.";
 }
 
 IN_PROC_BROWSER_TEST_F(OnDeviceModelExecutionEnabledBrowserTest,
                        GetOnDeviceModelEligibilityInIncognito) {
-  SetUpBaseModel();
+  SetUpGlobalAssets();
 
   Browser* otr_browser = CreateIncognitoBrowser();
-  SetUpComposeModelExecutionConfig();
+  SetUpProfileAssets();
 
-  EXPECT_EQ(GetOnDeviceModelEligibility(ModelBasedCapabilityKey::kCompose,
-                                        otr_browser->profile()),
-            OnDeviceModelEligibilityReason::kSuccess);
+  ASSERT_TRUE(base::test::RunUntil([&]() {
+    return GetOnDeviceModelEligibility(ModelBasedCapabilityKey::kCompose,
+                                       otr_browser->profile()) ==
+           OnDeviceModelEligibilityReason::kSuccess;
+  })) << "Timeout waiting for model to be marked eligible.";
 }
 
 #if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_CHROMEOS)
 // Guest profile only available in some platforms.
 IN_PROC_BROWSER_TEST_F(OnDeviceModelExecutionEnabledBrowserTest,
                        GetOnDeviceModelEligibilityInGuestProfile) {
-  SetUpBaseModel();
+  SetUpGlobalAssets();
 
   Browser* guest_browser = CreateGuestBrowser();
-  SetUpComposeModelExecutionConfig();
+  SetUpProfileAssets();
 
-  EXPECT_EQ(GetOnDeviceModelEligibility(ModelBasedCapabilityKey::kCompose,
-                                        guest_browser->profile()),
-            OnDeviceModelEligibilityReason::kSuccess);
+  ASSERT_TRUE(base::test::RunUntil([&]() {
+    return GetOnDeviceModelEligibility(ModelBasedCapabilityKey::kCompose,
+                                       guest_browser->profile()) ==
+           OnDeviceModelEligibilityReason::kSuccess;
+  })) << "Timeout waiting for model to be marked eligible.";
 }
 #endif  // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_CHROMEOS)
 
 IN_PROC_BROWSER_TEST_F(OnDeviceModelExecutionEnabledBrowserTest,
                        GetSamplingParamsConfig) {
-  SetUpBaseModel();
-  SetUpComposeModelExecutionConfig();
+  SetUpGlobalAssets();
+  SetUpProfileAssets();
+
+  ASSERT_TRUE(base::test::RunUntil([&]() {
+    return GetOnDeviceModelEligibility(ModelBasedCapabilityKey::kCompose,
+                                       nullptr) ==
+           OnDeviceModelEligibilityReason::kSuccess;
+  })) << "Timeout waiting for model to be marked eligible.";
 
   auto sampling_config =
       GetOptimizationGuideKeyedService()->GetSamplingParamsConfig(
diff --git a/chrome/browser/pdf/pdf_extension_util.cc b/chrome/browser/pdf/pdf_extension_util.cc
index b2a5f012..1254b579 100644
--- a/chrome/browser/pdf/pdf_extension_util.cc
+++ b/chrome/browser/pdf/pdf_extension_util.cc
@@ -261,6 +261,9 @@
     annotations_enabled = enable_annotations;
   }
   dict->Set("pdfInk2Enabled", use_ink2);
+  bool text_annotations_enabled =
+      use_ink2 && chrome_pdf::features::kPdfInk2TextAnnotations.Get();
+  dict->Set("pdfTextAnnotationsEnabled", text_annotations_enabled);
 #endif  // BUILDFLAG(ENABLE_PDF_INK2)
   dict->Set("printingEnabled", printing_enabled);
   dict->Set("pdfAnnotationsEnabled", annotations_enabled);
diff --git a/chrome/browser/performance_manager/frame_node_impl_browsertest.cc b/chrome/browser/performance_manager/frame_node_impl_browsertest.cc
index 55cf02d6e..2abef85f 100644
--- a/chrome/browser/performance_manager/frame_node_impl_browsertest.cc
+++ b/chrome/browser/performance_manager/frame_node_impl_browsertest.cc
@@ -135,7 +135,7 @@
   base::RunLoop run_loop;
   PassToPMGraph(std::make_unique<ViewportIntersectionStateChangedObserver>(
       std::move(frame_node_matcher),
-      performance_manager::features::kRenderedOutOfViewIsNotVisible.Get(),
+      !performance_manager::features::kRenderedOutOfViewIsNotVisible.Get(),
       run_loop.QuitClosure()));
   //
   // Navigate.
diff --git a/chrome/browser/permissions/chrome_permissions_client.cc b/chrome/browser/permissions/chrome_permissions_client.cc
index 3e44af19..d3587ab 100644
--- a/chrome/browser/permissions/chrome_permissions_client.cc
+++ b/chrome/browser/permissions/chrome_permissions_client.cc
@@ -36,6 +36,8 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profiles_state.h"
 #include "chrome/browser/search_engines/ui_thread_search_terms_data.h"
+#include "chrome/browser/serial/serial_chooser_context.h"
+#include "chrome/browser/serial/serial_chooser_context_factory.h"
 #include "chrome/browser/subresource_filter/subresource_filter_profile_context_factory.h"
 #include "chrome/browser/ui/browser_finder.h"
 #include "chrome/browser/ui/hats/hats_service.h"
@@ -215,6 +217,9 @@
     case ContentSettingsType::BLUETOOTH_CHOOSER_DATA:
       return BluetoothChooserContextFactory::GetForProfile(
           Profile::FromBrowserContext(browser_context));
+    case ContentSettingsType::SERIAL_CHOOSER_DATA:
+      return SerialChooserContextFactory::GetForProfile(
+          Profile::FromBrowserContext(browser_context));
     default:
       NOTREACHED();
   }
diff --git a/chrome/browser/platform_experience/win b/chrome/browser/platform_experience/win
index 33c17ca..418eade 160000
--- a/chrome/browser/platform_experience/win
+++ b/chrome/browser/platform_experience/win
@@ -1 +1 @@
-Subproject commit 33c17cac9b138a9640468b481761408127d5d684
+Subproject commit 418eade860a2be8e4e174de3775d61162565ed2a
diff --git a/chrome/browser/policy/extension_policy_browsertest.cc b/chrome/browser/policy/extension_policy_browsertest.cc
index 46094f3..a32287e 100644
--- a/chrome/browser/policy/extension_policy_browsertest.cc
+++ b/chrome/browser/policy/extension_policy_browsertest.cc
@@ -263,12 +263,19 @@
 
   void SetUpOnMainThread() override {
     ExtensionPolicyTestBase::SetUpOnMainThread();
-    if (extension_service()->updater()) {
-      extension_service()->updater()->SetExtensionCacheForTesting(
+    if (extension_updater()->enabled()) {
+      extension_updater()->SetExtensionCacheForTesting(
           test_extension_cache_.get());
     }
   }
 
+  void TearDownOnMainThread() override {
+    if (extension_updater()->enabled()) {
+      extension_updater()->SetExtensionCacheForTesting(nullptr);
+    }
+    ExtensionPolicyTestBase::TearDownOnMainThread();
+  }
+
   void SetUpCommandLine(base::CommandLine* command_line) override {
     ExtensionPolicyTestBase::SetUpCommandLine(command_line);
     // Some bots are flaky due to slower loading interacting with
@@ -294,6 +301,10 @@
     return extensions::ExtensionRegistry::Get(browser()->profile());
   }
 
+  extensions::ExtensionUpdater* extension_updater() {
+    return extensions::ExtensionUpdater::Get(browser()->profile());
+  }
+
   web_app::WebAppProvider* web_app_provider() {
     return web_app::WebAppProvider::GetForTest(browser()->profile());
   }
@@ -835,7 +846,7 @@
   PolicyMap policies;
 
   TestFuture<std::optional<CrxInstallError>> installer_done_future;
-  extension_service()->updater()->SetCrxInstallerResultCallbackForTesting(
+  extension_updater()->SetCrxInstallerResultCallbackForTesting(
       installer_done_future
           .GetCallback<const std::optional<CrxInstallError>&>());
 
@@ -876,7 +887,7 @@
   // installation fails due to version mismatch.
   extensions::ExtensionCache* cache =
       extensions::ExtensionsBrowserClient::Get()->GetExtensionCache();
-  extension_service()->updater()->SetExtensionCacheForTesting(cache);
+  extension_updater()->SetExtensionCacheForTesting(cache);
 
   base::FilePath extension_path(ui_test_utils::GetTestFilePath(
       base::FilePath(kTestExtensionsDir), base::FilePath(kGoodCrxName)));
@@ -916,7 +927,7 @@
   PolicyMap policies;
 
   TestFuture<std::optional<CrxInstallError>> installer_done_future;
-  extension_service()->updater()->SetCrxInstallerResultCallbackForTesting(
+  extension_updater()->SetCrxInstallerResultCallbackForTesting(
       installer_done_future
           .GetCallback<const std::optional<CrxInstallError>&>());
 
@@ -956,7 +967,6 @@
   // Mark as enterprise managed.
   policy::ScopedDomainEnterpriseManagement scoped_domain;
   ExtensionRequestInterceptor interceptor;
-  extensions::ExtensionService* service = extension_service();
   extensions::ExtensionRegistry* registry = extension_registry();
   ASSERT_FALSE(registry->GetExtensionById(
       kGoodCrxId, extensions::ExtensionRegistry::EVERYTHING));
@@ -1041,7 +1051,7 @@
       extensions::mojom::ViewType::kExtensionBackgroundPage);
 
   // Updating the force-installed extension.
-  extensions::ExtensionUpdater* updater = service->updater();
+  extensions::ExtensionUpdater* updater = extension_updater();
   extensions::ExtensionUpdater::CheckParams params;
   params.install_immediately = true;
   extensions::TestExtensionRegistryObserver update_observer(
@@ -1106,8 +1116,7 @@
 
   // Update the extension and verify the version according to "prodversionmin"
   // in the update manifest.
-  extensions::ExtensionService* service = extension_service();
-  extensions::ExtensionUpdater* updater = service->updater();
+  extensions::ExtensionUpdater* updater = extension_updater();
   extensions::ExtensionUpdater::CheckParams params;
   params.install_immediately = true;
   extensions::TestExtensionRegistryObserver update_observer(
@@ -1192,8 +1201,8 @@
       return nullptr;
     }
 
-    extensions::ExtensionService* service = extension_service();
-    extensions::ExtensionUpdater* updater = service->updater();
+    extensions::ExtensionUpdater* updater =
+        extensions::ExtensionUpdater::Get(browser()->profile());
     extensions::ExtensionUpdater::CheckParams params;
     params.install_immediately = true;
 
@@ -1721,7 +1730,7 @@
   GURL url =
       embedded_test_server()->GetURL("/extensions/good_v1_update_manifest.xml");
 
-  extension_service()->updater()->SetBackoffPolicyForTesting(
+  extension_updater()->SetBackoffPolicyForTesting(
       kDefaultBackOffPolicyForTesting);
 
   base::FilePath extension_path(ui_test_utils::GetTestFilePath(
@@ -1780,7 +1789,7 @@
           return true;
         }));
   }
-  extension_service()->updater()->SetBackoffPolicyForTesting(
+  extension_updater()->SetBackoffPolicyForTesting(
       kDefaultBackOffPolicyForTesting);
 
   base::FilePath extension_path(ui_test_utils::GetTestFilePath(
@@ -2019,7 +2028,6 @@
         return true;
       }));
 
-  extensions::ExtensionService* service = extension_service();
   extensions::ExtensionRegistry* registry = extension_registry();
   extensions::ExtensionPrefs* extension_prefs =
       extensions::ExtensionPrefs::Get(browser()->profile());
@@ -2059,7 +2067,7 @@
   EXPECT_TRUE(update_extension_count.IsOne());
   {
     extensions::TestExtensionRegistryObserver update_observer(registry);
-    service->updater()->CheckSoon();
+    extension_updater()->CheckSoon();
     update_observer.WaitForExtensionWillBeInstalled();
   }
   EXPECT_EQ(2, update_extension_count.SubtleRefCountForDebug());
@@ -2469,11 +2477,9 @@
 
     profile2_ = CreateProfile(&profile2_policy_);
 
-    auto* service1 = CreateExtensionService(profile1_);
-    auto* service2 = CreateExtensionService(profile2_);
-    service1->updater()->SetExtensionCacheForTesting(
+    extensions::ExtensionUpdater::Get(profile1_)->SetExtensionCacheForTesting(
         test_extension_cache1_.get());
-    service2->updater()->SetExtensionCacheForTesting(
+    extensions::ExtensionUpdater::Get(profile1_)->SetExtensionCacheForTesting(
         test_extension_cache2_.get());
     registrar1_ = extensions::ExtensionRegistrar::Get(profile1_);
     registrar2_ = extensions::ExtensionRegistrar::Get(profile2_);
@@ -2541,13 +2547,6 @@
     return &profiles::testing::CreateProfileSync(profile_manager, path_profile);
   }
 
-  extensions::ExtensionService* CreateExtensionService(
-      content::BrowserContext* context) {
-    extensions::ExtensionSystem* system =
-        extensions::ExtensionSystem::Get(context);
-    return system->extension_service();
-  }
-
   extensions::ExtensionRegistry* CreateExtensionRegistry(
       content::BrowserContext* context) {
     return extensions::ExtensionRegistry::Get(context);
diff --git a/chrome/browser/privacy_sandbox/privacy_sandbox_attestations/privacy_sandbox_attestations_component_installer_browsertest.cc b/chrome/browser/privacy_sandbox/privacy_sandbox_attestations/privacy_sandbox_attestations_component_installer_browsertest.cc
index 8b808c6..f24e5da4a 100644
--- a/chrome/browser/privacy_sandbox/privacy_sandbox_attestations/privacy_sandbox_attestations_component_installer_browsertest.cc
+++ b/chrome/browser/privacy_sandbox/privacy_sandbox_attestations/privacy_sandbox_attestations_component_installer_browsertest.cc
@@ -24,9 +24,14 @@
 #include "components/privacy_sandbox/privacy_sandbox_attestations/privacy_sandbox_attestations_histograms.h"
 #include "components/privacy_sandbox/privacy_sandbox_attestations/proto/privacy_sandbox_attestations.pb.h"
 #include "components/privacy_sandbox/privacy_sandbox_features.h"
+#include "components/privacy_sandbox/privacy_sandbox_test_util.h"
 #include "content/public/test/browser_test.h"
 
 namespace privacy_sandbox {
+
+using ::privacy_sandbox_test_util::PrivacySandboxSettingsTestPeer;
+using Status = PrivacySandboxSettingsTestPeer::Status;
+
 class PrivacySandboxAttestationsBrowserTestBase
     : public MixinBasedInProcessBrowserTest {
  public:
@@ -106,10 +111,11 @@
                   .IsValid());
   EXPECT_EQ(PrivacySandboxAttestations::GetInstance()->GetVersionForTesting(),
             version);
-  EXPECT_TRUE(PrivacySandboxSettingsImpl::IsAllowed(
-      PrivacySandboxAttestations::GetInstance()->IsSiteAttested(
-          net::SchemefulSite(GURL(site)),
-          PrivacySandboxAttestationsGatedAPI::kTopics)));
+  EXPECT_TRUE(
+      privacy_sandbox_test_util::PrivacySandboxSettingsTestPeer::IsAllowed(
+          PrivacySandboxAttestations::GetInstance()->IsSiteAttested(
+              net::SchemefulSite(GURL(site)),
+              PrivacySandboxAttestationsGatedAPI::kTopics)));
 
   histogram_tester().ExpectTotalCount(kAttestationsFileSource, 1);
   histogram_tester().ExpectBucketCount(kAttestationsFileSource,
@@ -122,16 +128,16 @@
 IN_PROC_BROWSER_TEST_F(PrivacySandboxAttestationsBrowserTest,
                        DifferentHistogramAfterAttestationsFileCheck) {
   std::string site = "https://example.com";
-  EXPECT_FALSE(PrivacySandboxSettingsImpl::IsAllowed(
-      PrivacySandboxAttestations::GetInstance()->IsSiteAttested(
-          net::SchemefulSite(GURL(site)),
-          PrivacySandboxAttestationsGatedAPI::kTopics)));
+  EXPECT_FALSE(
+      privacy_sandbox_test_util::PrivacySandboxSettingsTestPeer::IsAllowed(
+          PrivacySandboxAttestations::GetInstance()->IsSiteAttested(
+              net::SchemefulSite(GURL(site)),
+              PrivacySandboxAttestationsGatedAPI::kTopics)));
 
   // The attestation component has not yet checked the attestations file.
   histogram_tester().ExpectTotalCount(kAttestationStatusUMA, 1);
   histogram_tester().ExpectBucketCount(
-      kAttestationStatusUMA,
-      PrivacySandboxSettingsImpl::Status::kAttestationsFileNotYetChecked, 1);
+      kAttestationStatusUMA, Status::kAttestationsFileNotYetChecked, 1);
 
   base::RunLoop run_loop;
   PrivacySandboxAttestations::GetInstance()
@@ -146,17 +152,17 @@
   run_loop.Run();
 
   // Check attestation again.
-  EXPECT_FALSE(PrivacySandboxSettingsImpl::IsAllowed(
-      PrivacySandboxAttestations::GetInstance()->IsSiteAttested(
-          net::SchemefulSite(GURL(site)),
-          PrivacySandboxAttestationsGatedAPI::kTopics)));
+  EXPECT_FALSE(
+      privacy_sandbox_test_util::PrivacySandboxSettingsTestPeer::IsAllowed(
+          PrivacySandboxAttestations::GetInstance()->IsSiteAttested(
+              net::SchemefulSite(GURL(site)),
+              PrivacySandboxAttestationsGatedAPI::kTopics)));
 
   // It should record in a different histogram bucket because the file check has
   // completed but no file was found.
   histogram_tester().ExpectTotalCount(kAttestationStatusUMA, 2);
-  histogram_tester().ExpectBucketCount(
-      kAttestationStatusUMA,
-      PrivacySandboxSettingsImpl::Status::kAttestationsFileNotPresent, 1);
+  histogram_tester().ExpectBucketCount(kAttestationStatusUMA,
+                                       Status::kAttestationsFileNotPresent, 1);
 }
 
 // This test verifies there is a copy of pre-installed attestation list in the
@@ -261,10 +267,11 @@
 
   // Make an attestation check to verify the data point is recorded to the
   // correct histogram bucket.
-  ASSERT_TRUE(PrivacySandboxSettingsImpl::IsAllowed(
-      PrivacySandboxAttestations::GetInstance()->IsSiteAttested(
-          net::SchemefulSite(GURL("https://example.com")),
-          PrivacySandboxAttestationsGatedAPI::kTopics)));
+  ASSERT_TRUE(
+      privacy_sandbox_test_util::PrivacySandboxSettingsTestPeer::IsAllowed(
+          PrivacySandboxAttestations::GetInstance()->IsSiteAttested(
+              net::SchemefulSite(GURL("https://example.com")),
+              PrivacySandboxAttestationsGatedAPI::kTopics)));
   histogram_tester().ExpectTotalCount(kAttestationsFileSource, 1);
   histogram_tester().ExpectBucketCount(kAttestationsFileSource,
                                        FileSource::kPreInstalled, 1);
diff --git a/chrome/browser/profiles/profile_keyed_service_browsertest.cc b/chrome/browser/profiles/profile_keyed_service_browsertest.cc
index 4101f669..b4e2cc7 100644
--- a/chrome/browser/profiles/profile_keyed_service_browsertest.cc
+++ b/chrome/browser/profiles/profile_keyed_service_browsertest.cc
@@ -635,6 +635,7 @@
     "ExtensionSyncService",
     "ExtensionSystem",
     "ExtensionSystemShared",
+    "ExtensionUpdater",
     "ExtensionURLLoaderFactory::BrowserContextShutdownNotifierFactory",
     "ExtensionWebUIOverrideRegistrar",
     "ExternalInstallManager",
diff --git a/chrome/browser/resources/glic/glic_app_controller.ts b/chrome/browser/resources/glic/glic_app_controller.ts
index e5d1c8b8..ccf6f30 100644
--- a/chrome/browser/resources/glic/glic_app_controller.ts
+++ b/chrome/browser/resources/glic/glic_app_controller.ts
@@ -100,6 +100,16 @@
       this.setState(WebUiState.kOffline);
     }
 
+    document.addEventListener('keydown', ev => {
+      if (this.state !== WebUiState.kReady) {
+        if (ev.code === 'Escape') {
+          ev.stopPropagation();
+          ev.preventDefault();
+          this.browserProxy.handler.closePanel();
+        }
+      }
+    });
+
     if (kEnableDebug) {
       window.addEventListener('load', () => {
         this.installDebugButton();
diff --git a/chrome/browser/resources/pdf/elements/icons.html b/chrome/browser/resources/pdf/elements/icons.html
index f1654ca..a4510c7 100644
--- a/chrome/browser/resources/pdf/elements/icons.html
+++ b/chrome/browser/resources/pdf/elements/icons.html
@@ -67,6 +67,7 @@
     <g id="pen-size-3" viewBox="0 -960 960 960"><path d="M218-218q-17-17-17-42t17-42l440-440q17-18 42-17.5t42 17.5q17 17 17.5 42T742-658L302-218q-17 17-42 17.5T218-218Z"></path></g>
     <g id="pen-size-4" viewBox="0 -960 960 960"><path d="M229-229q-29-29-29-71t29-71l360-360q29-29 71-29t71 29q29 29 29 71t-29 71L371-229q-29 29-71 29t-71-29Z"></path></g>
     <g id="pen-size-5" viewBox="0 -960 960 960"><path d="M235-235q-35-35-35-85t35-85l320-320q35-35 85-35t85 35q35 35 35 85t-35 85L405-235q-35 35-85 35t-85-35Z"></path></g>
+    <g id="text-annotate" viewBox="0 -960 960 960"><path d="M280-160v-520H80v-120h520v120H400v520H280Zm360 0v-320H520v-120h360v120H760v320H640Z"></path></g>
 </if>
   </defs>
   </svg>
diff --git a/chrome/browser/resources/pdf/elements/viewer_bottom_toolbar.ts b/chrome/browser/resources/pdf/elements/viewer_bottom_toolbar.ts
index 8035ade..62fe2ff 100644
--- a/chrome/browser/resources/pdf/elements/viewer_bottom_toolbar.ts
+++ b/chrome/browser/resources/pdf/elements/viewer_bottom_toolbar.ts
@@ -12,7 +12,6 @@
 import type {PropertyValues} from 'chrome://resources/lit/v3_0/lit.rollup.js';
 
 import {AnnotationBrushType} from '../constants.js';
-import {record, UserAction} from '../metrics.js';
 import {blendHighlighterColorValue, colorToHex} from '../pdf_viewer_utils.js';
 
 import {InkAnnotationBrushMixin} from './ink_annotation_brush_mixin.js';
@@ -51,11 +50,6 @@
 
   strings?: {[key: string]: string};
 
-  constructor() {
-    super();
-    record(UserAction.OPEN_INK2_BOTTOM_TOOLBAR);
-  }
-
   override updated(changedProperties: PropertyValues<this>) {
     super.updated(changedProperties);
     if (changedProperties.has('currentColor') && this.currentColor) {
diff --git a/chrome/browser/resources/pdf/elements/viewer_side_panel.css b/chrome/browser/resources/pdf/elements/viewer_side_panel.css
index afaa0102..0be2ac9 100644
--- a/chrome/browser/resources/pdf/elements/viewer_side_panel.css
+++ b/chrome/browser/resources/pdf/elements/viewer_side_panel.css
@@ -13,7 +13,6 @@
   --cr-icon-button-margin-end: 0;
   --cr-icon-button-margin-start: 0;
   background-color: var(--viewer-side-background-color);
-  padding-top: 8px;
   width: 288px;
 }
 
diff --git a/chrome/browser/resources/pdf/elements/viewer_side_panel.ts b/chrome/browser/resources/pdf/elements/viewer_side_panel.ts
index cbaddcc..333e50f 100644
--- a/chrome/browser/resources/pdf/elements/viewer_side_panel.ts
+++ b/chrome/browser/resources/pdf/elements/viewer_side_panel.ts
@@ -9,7 +9,6 @@
 import {CrLitElement} from 'chrome://resources/lit/v3_0/lit.rollup.js';
 
 import {AnnotationBrushType} from '../constants.js';
-import {record, UserAction} from '../metrics.js';
 
 import {InkAnnotationBrushMixin} from './ink_annotation_brush_mixin.js';
 import {getCss} from './viewer_side_panel.css.js';
@@ -30,11 +29,6 @@
     return getHtml.bind(this)();
   }
 
-  constructor() {
-    super();
-    record(UserAction.OPEN_INK2_SIDE_PANEL);
-  }
-
   protected shouldShowBrushOptions_(): boolean {
     return this.currentType !== AnnotationBrushType.ERASER;
   }
diff --git a/chrome/browser/resources/pdf/elements/viewer_toolbar.css b/chrome/browser/resources/pdf/elements/viewer_toolbar.css
index 54a956a..5039b31 100644
--- a/chrome/browser/resources/pdf/elements/viewer_toolbar.css
+++ b/chrome/browser/resources/pdf/elements/viewer_toolbar.css
@@ -182,11 +182,11 @@
 }
 
 <if expr="enable_ink or enable_pdf_ink2">
-#annotate {
+.annotate-button {
   margin-inline-end: 4px;
 }
 
-:host([annotation-mode="draw"]) #annotate {
+.annotate-button.active {
   background-color: var(--active-button-bg);
   border-radius: 50%;
 }
@@ -199,11 +199,11 @@
   margin-inline-start: 6px;
 }
 
-#annotate-controls #annotate {
+#annotate-controls .annotate-button {
   margin-inline: 0;
 }
 
-:host([annotation-mode="draw"]) #annotate-controls #annotate {
+#annotate-controls .annotate-button.active {
   --cr-icon-button-fill-color: var(--viewer-icon-ink-selected-fill-color);
 }
 
diff --git a/chrome/browser/resources/pdf/elements/viewer_toolbar.html b/chrome/browser/resources/pdf/elements/viewer_toolbar.html
index 325f3ca..b7256e3 100644
--- a/chrome/browser/resources/pdf/elements/viewer_toolbar.html
+++ b/chrome/browser/resources/pdf/elements/viewer_toolbar.html
@@ -1,3 +1,7 @@
+<!-- #html_wrapper_imports_start
+import {AnnotationMode} from '../constants.js';
+#html_wrapper_imports_end -->
+
 <div id="toolbar">
   <div id="start">
     <cr-icon-button id="sidenavToggle" iron-icon="${this.menuIcon_()}"
@@ -53,7 +57,17 @@
     ${this.showInk2Buttons_() ? html`
       <span id="annotate-controls">
         <span class="vertical-separator"></span>
+        ${this.pdfTextAnnotationsEnabled_ ? html`
+          <!-- TODO(crbug.com/402547554): Add aria-label and i18n string -->
+          <cr-icon-button id="text-annotate" iron-icon="pdf:text-annotate"
+              class="annotate-button ${this.getActive_(AnnotationMode.TEXT)}"
+              @click="${this.onTextAnnotationClick_}"
+              ?disabled="${!this.annotationAvailable}"
+              title="Text annotation">
+          </cr-icon-button>
+        ` : ''}
         <cr-icon-button id="annotate" iron-icon="pdf:annotate"
+            class="annotate-button ${this.getActive_(AnnotationMode.DRAW)}"
             @click="${this.onAnnotationClick_}"
             aria-label="$i18n{ink2Draw}"
             ?disabled="${!this.annotationAvailable}"
@@ -76,6 +90,7 @@
   <if expr="enable_ink">
     ${this.showInkAnnotationButton_() ? html`<cr-icon-button id="annotate"
           iron-icon="cr:create"
+          class="annotate-button ${this.getActive_(AnnotationMode.DRAW)}"
           @click="${this.onAnnotationClick_}"
           aria-label="$i18n{tooltipAnnotate}"
           ?disabled="${!this.annotationAvailable}"
diff --git a/chrome/browser/resources/pdf/elements/viewer_toolbar.ts b/chrome/browser/resources/pdf/elements/viewer_toolbar.ts
index 00effb4..75fe51165 100644
--- a/chrome/browser/resources/pdf/elements/viewer_toolbar.ts
+++ b/chrome/browser/resources/pdf/elements/viewer_toolbar.ts
@@ -70,27 +70,11 @@
 
   static override get properties() {
     return {
-      // <if expr="enable_ink or enable_pdf_ink2">
-      annotationAvailable: {type: Boolean},
-      annotationMode: {
-        type: String,
-        reflect: true,
-      },
-      // </if>
-
-      // <if expr="enable_pdf_ink2">
-      canRedoAnnotation_: {type: Boolean},
-      canUndoAnnotation_: {type: Boolean},
-      // </if>
-
       docTitle: {type: String},
       docLength: {type: Number},
       embeddedViewer: {type: Boolean},
       hasEdits: {type: Boolean},
       hasEnteredAnnotationMode: {type: Boolean},
-      // <if expr="enable_pdf_ink2">
-      hasInk2Edits: {type: Boolean},
-      // </if>
       formFieldFocus: {type: String},
       loadProgress: {type: Number},
 
@@ -101,9 +85,6 @@
 
       pageNo: {type: Number},
       pdfCr23Enabled: {type: Boolean},
-      // <if expr="enable_pdf_ink2">
-      pdfInk2Enabled: {type: Boolean},
-      // </if>
 
       rotated: {type: Boolean},
       strings: {type: Object},
@@ -122,9 +103,25 @@
       printingEnabled_: {type: Boolean},
       viewportZoomPercent_: {type: Number},
 
+      // <if expr="enable_ink or enable_pdf_ink2">
+      annotationAvailable: {type: Boolean},
+      annotationMode: {
+        type: String,
+        reflect: true,
+      },
+      // </if>
+
       // <if expr="enable_ink">
       showAnnotationsModeDialog_: {type: Boolean},
       // </if> enable_ink
+
+      // <if expr="enable_pdf_ink2">
+      hasInk2Edits: {type: Boolean},
+      pdfInk2Enabled: {type: Boolean},
+      canRedoAnnotation_: {type: Boolean},
+      canUndoAnnotation_: {type: Boolean},
+      pdfTextAnnotationsEnabled_: {type: Boolean},
+      // </if>
     };
   }
 
@@ -133,9 +130,6 @@
   embeddedViewer: boolean = false;
   hasEdits: boolean = false;
   hasEnteredAnnotationMode: boolean = false;
-  // <if expr="enable_pdf_ink2">
-  hasInk2Edits: boolean = false;
-  // </if>
   formFieldFocus: FormFieldFocusType = FormFieldFocusType.NONE;
   loadProgress: number = 0;
   pageNo: number = 0;
@@ -155,18 +149,25 @@
   private viewportZoomPercent_: number = 0;
 
   // <if expr="enable_ink or enable_pdf_ink2">
+  // Reactive properties common to ink and ink2
   annotationAvailable: boolean = false;
   annotationMode: AnnotationMode = AnnotationMode.NONE;
   // </if>
 
   // <if expr="enable_ink">
+  // Ink reactive properties
   protected showAnnotationsModeDialog_: boolean = false;
   // </if>
 
   // <if expr="enable_pdf_ink2">
+  // Ink2 reactive properties
+  hasInk2Edits: boolean = false;
   pdfInk2Enabled: boolean = false;
   protected canRedoAnnotation_: boolean = false;
   protected canUndoAnnotation_: boolean = false;
+  protected pdfTextAnnotationsEnabled_: boolean = false;
+
+  // Ink2 class members
   private currentStroke: number = 0;
   private mostRecentStroke: number = 0;
   private pluginController_: PluginController = PluginController.getInstance();
@@ -217,6 +218,10 @@
     this.printingEnabled_ = loadTimeData.getBoolean('printingEnabled');
     this.pdfAnnotationsEnabled_ =
         loadTimeData.getBoolean('pdfAnnotationsEnabled');
+    // <if expr="enable_pdf_ink2">
+    this.pdfTextAnnotationsEnabled_ =
+        loadTimeData.getBoolean('pdfTextAnnotationsEnabled');
+    // </if>
   }
 
   protected onSidenavToggleClick_() {
@@ -450,6 +455,11 @@
   // </if>
 
   // <if expr="enable_ink or enable_pdf_ink2">
+  // Gets a CSS class of "active" if `mode` is the active annotation mode.
+  protected getActive_(mode: AnnotationMode): string {
+    return mode === this.annotationMode ? 'active' : '';
+  }
+
   protected onAnnotationClick_() {
     const newAnnotationMode = this.annotationMode === AnnotationMode.DRAW ?
         AnnotationMode.NONE :
@@ -490,6 +500,12 @@
   // </if> enable_ink or enable_pdf_ink2
 
   // <if expr="enable_pdf_ink2">
+  protected onTextAnnotationClick_() {
+    this.setAnnotationMode(
+        this.annotationMode === AnnotationMode.TEXT ? AnnotationMode.NONE :
+                                                      AnnotationMode.TEXT);
+  }
+
   /**
    * Handles whether the undo and redo buttons should be enabled or disabled
    * when a new ink stroke is added to the page.
diff --git a/chrome/browser/resources/pdf/metrics.ts b/chrome/browser/resources/pdf/metrics.ts
index 3445ec4..e75c49a 100644
--- a/chrome/browser/resources/pdf/metrics.ts
+++ b/chrome/browser/resources/pdf/metrics.ts
@@ -265,7 +265,15 @@
   SAVE_SEARCHIFIED_FIRST = 99,
   SAVE_SEARCHIFIED = 100,
 
-  NUMBER_OF_ACTIONS = 101,
+  // Recorded when the user enters Ink2 text annotation mode.
+  ENTER_INK2_TEXT_ANNOTATION_MODE_FIRST = 101,
+  ENTER_INK2_TEXT_ANNOTATION_MODE = 102,
+
+  // Recorded when the user exits Ink2 text annotation mode.
+  EXIT_INK2_TEXT_ANNOTATION_MODE_FIRST = 103,
+  EXIT_INK2_TEXT_ANNOTATION_MODE = 104,
+
+  NUMBER_OF_ACTIONS = 105,
 }
 
 function createFirstMap(): Map<UserAction, UserAction> {
diff --git a/chrome/browser/resources/pdf/pdf_viewer.css b/chrome/browser/resources/pdf/pdf_viewer.css
index b8e4a49f..60e6934 100644
--- a/chrome/browser/resources/pdf/pdf_viewer.css
+++ b/chrome/browser/resources/pdf/pdf_viewer.css
@@ -26,9 +26,10 @@
 }
 
 <if expr="enable_pdf_ink2">
-  viewer-side-panel {
-    border-inline-start: 1px solid var(--viewer-border-color);
-  }
+#side-panel {
+  border-inline-start: 1px solid var(--viewer-border-color);
+  padding-top: 8px;
+}
 </if>
 
 viewer-toolbar {
diff --git a/chrome/browser/resources/pdf/pdf_viewer.html b/chrome/browser/resources/pdf/pdf_viewer.html
index ec2d09b..fd5f93a 100644
--- a/chrome/browser/resources/pdf/pdf_viewer.html
+++ b/chrome/browser/resources/pdf/pdf_viewer.html
@@ -1,3 +1,7 @@
+<!-- #html_wrapper_imports_start
+import {AnnotationMode} from './constants.js';
+#html_wrapper_imports_end -->
+
 <viewer-toolbar id="toolbar" .annotationMode="${this.annotationMode_}"
     .docTitle="${this.title_}" .docLength="${this.docLength_}"
     .embeddedViewer="${this.embedded_}" .pageNo="${this.pageNo_}"
@@ -67,15 +71,30 @@
     </div>
     <if expr="enable_pdf_ink2">
       ${this.shouldShowInkBottomToolbar_() ? html`
-        <div id="bottom">
-          <viewer-bottom-toolbar .strings="${this.strings}">
+        <cr-page-selector id="bottom" attr-for-selected="toolbar-name"
+            selected="${this.annotationMode_}">
+          <viewer-bottom-toolbar .strings="${this.strings}"
+              toolbar-name="${AnnotationMode.DRAW}">
           </viewer-bottom-toolbar>
-        </div>` : ''}
+          <div toolbar-name="${AnnotationMode.TEXT}"
+              style="background-color: lightgray; position: fixed; bottom: 0;">
+            Text Annotation coming soon
+          </div>
+        </cr-page-selector>` : ''}
     </if>
   </div>
   <if expr="enable_pdf_ink2">
     ${this.shouldShowInkSidePanel_() ? html`
-      <viewer-side-panel></viewer-side-panel>` : ''}
+      <cr-page-selector attr-for-selected="page-name" id="side-panel"
+          selected="${this.annotationMode_}">
+        <viewer-side-panel page-name="${AnnotationMode.DRAW}">
+        </viewer-side-panel>
+        <div page-name="${AnnotationMode.TEXT}"
+            style="color: lightgray; width: 288px;">
+          Text Annotation coming soon
+        </div>
+      </cr-page-selector>
+    ` : ''}
   </if>
   <cr-toast id="searchifyProgress">
     <div class="spinner"></div>
diff --git a/chrome/browser/resources/pdf/pdf_viewer.ts b/chrome/browser/resources/pdf/pdf_viewer.ts
index 673088b..1dfd38b4 100644
--- a/chrome/browser/resources/pdf/pdf_viewer.ts
+++ b/chrome/browser/resources/pdf/pdf_viewer.ts
@@ -2,6 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// clang-format off
+// <if expr="enable_pdf_ink2">
+import 'chrome://resources/cr_elements/cr_page_selector/cr_page_selector.js';
+// </if>
 import 'chrome://resources/cr_elements/cr_toast/cr_toast.js';
 import './elements/viewer_error_dialog.js';
 // <if expr="enable_ink">
@@ -70,6 +74,7 @@
 import {PdfViewerPrivateProxyImpl} from './pdf_viewer_private_proxy.js';
 import type {DocumentDimensionsMessageData} from './pdf_viewer_utils.js';
 import {hasCtrlModifier, hasCtrlModifierOnly, shouldIgnoreKeyEvents} from './pdf_viewer_utils.js';
+// clang-format on
 
 /**
  * Keep in sync with the values for enum PDFPostMessageDataType in
@@ -321,9 +326,16 @@
     // <if expr="enable_pdf_ink2">
     const mediaQuery = window.matchMedia('(min-width: 960px)');
     this.useSidePanelForInk_ = mediaQuery.matches;
-    this.tracker.add(
-        mediaQuery, 'change',
-        () => this.useSidePanelForInk_ = mediaQuery.matches);
+    this.tracker.add(mediaQuery, 'change', () => {
+      this.useSidePanelForInk_ = mediaQuery.matches;
+      // If we are in DRAW or TEXT annotation mode, record opening the
+      // UI that's opened by making the window narrower/wider.
+      if (this.annotationMode_ !== AnnotationMode.NONE) {
+        record(
+            this.useSidePanelForInk_ ? UserAction.OPEN_INK2_SIDE_PANEL :
+                                       UserAction.OPEN_INK2_BOTTOM_TOOLBAR);
+      }
+    });
     // </if> enable_pdf_ink2
   }
 
@@ -517,39 +529,71 @@
   }
   // </if>
 
+  // <if expr="enable_pdf_ink2">
+  private recordEnterExitAnnotationModeMetrics_(
+      newAnnotationMode: AnnotationMode) {
+    // Record exit metrics if annotation mode is being changed from one of
+    // the ink annotation modes.
+    switch (this.annotationMode_) {
+      case AnnotationMode.DRAW:
+        record(UserAction.EXIT_INK2_ANNOTATION_MODE);
+        break;
+      case AnnotationMode.TEXT:
+        record(UserAction.EXIT_INK2_TEXT_ANNOTATION_MODE);
+        break;
+      case AnnotationMode.NONE:
+        break;
+      default:
+        assertNotReached();
+    }
+    // Record enter metrics if annotation mode is being changed to one of
+    // the ink annotation modes.
+    switch (newAnnotationMode) {
+      case AnnotationMode.DRAW:
+        record(UserAction.ENTER_INK2_ANNOTATION_MODE);
+        break;
+      case AnnotationMode.TEXT:
+        record(UserAction.ENTER_INK2_TEXT_ANNOTATION_MODE);
+        break;
+      case AnnotationMode.NONE:
+        break;
+      default:
+        assertNotReached();
+    }
+  }
+  // </if>
+
   // <if expr="enable_ink or enable_pdf_ink2">
-  /** Handles the annotation mode being toggled on or off. */
+  // Handles the annotation mode being updated from the toolbar buttons.
   protected async onAnnotationModeUpdated_(e: CustomEvent<AnnotationMode>) {
-    const annotationMode = e.detail;
+    const newAnnotationMode = e.detail;
+    if (newAnnotationMode === this.annotationMode_) {
+      return;
+    }
+
     // <if expr="enable_pdf_ink2">
     if (this.pdfInk2Enabled_) {
+      if (this.annotationMode_ === AnnotationMode.NONE) {
+        record(
+            this.useSidePanelForInk_ ? UserAction.OPEN_INK2_SIDE_PANEL :
+                                       UserAction.OPEN_INK2_BOTTOM_TOOLBAR);
+      }
       if (this.restoreAnnotationMode_ === AnnotationMode.NONE) {
-        let action: UserAction;
-        switch (annotationMode) {
-          case AnnotationMode.DRAW:
-            action = UserAction.ENTER_INK2_ANNOTATION_MODE;
-            break;
-          case AnnotationMode.NONE:
-            action = UserAction.EXIT_INK2_ANNOTATION_MODE;
-            break;
-          default:
-            assertNotReached();
-        }
-        record(action);
+        this.recordEnterExitAnnotationModeMetrics_(newAnnotationMode);
       }
       this.pluginController_.setAnnotationMode(
-          annotationMode !== AnnotationMode.NONE);
-      if (annotationMode === AnnotationMode.DRAW &&
+          newAnnotationMode !== AnnotationMode.NONE);
+      if (newAnnotationMode === AnnotationMode.DRAW &&
           !Ink2Manager.getInstance().isInitializationStarted()) {
         await Ink2Manager.getInstance().initializeBrush();
       }
-      this.annotationMode_ = annotationMode;
+      this.annotationMode_ = newAnnotationMode;
       return;
     }
     // </if> enable_pdf_ink2
 
     // <if expr="enable_ink">
-    if (annotationMode === AnnotationMode.DRAW) {
+    if (newAnnotationMode === AnnotationMode.DRAW) {
       // Enter annotation mode.
       assert(this.pluginController_.isActive);
       assert(!this.inkController_.isActive);
diff --git a/chrome/browser/resources/settings/autofill_page/autofill_page.html b/chrome/browser/resources/settings/autofill_page/autofill_page.html
index 702d498..494df25 100644
--- a/chrome/browser/resources/settings/autofill_page/autofill_page.html
+++ b/chrome/browser/resources/settings/autofill_page/autofill_page.html
@@ -28,6 +28,7 @@
           <cr-link-row id="autofillAiManagerButton"
               start-icon="settings20:text-analysis"
               label="$i18n{autofillAiPageTitle}"
+              sub-label="$i18n{autofillAiDescription}"
               on-click="onAutofillAiClick_"></cr-link-row>
         </template>
       </div>
diff --git a/chrome/browser/resources/settings/images/iban.svg b/chrome/browser/resources/settings/images/iban.svg
index 6b9aed5..9e646bb2 100644
--- a/chrome/browser/resources/settings/images/iban.svg
+++ b/chrome/browser/resources/settings/images/iban.svg
@@ -1,10 +1,17 @@
-<svg width="40" height="24" viewBox="0 0 40 24" fill="none" xmlns="http://www.w3.org/2000/svg">
-<rect width="40" height="24" fill="#D3E3FD"/>
-<path d="M20.6089 16.4568H11.0542V18.6516L20.789 18.6934L20.6089 16.4568Z" fill="#041E49"/>
-<path d="M15.3126 9.04987H12.9839V15.3046H15.3126V9.04987Z" fill="#041E49"/>
-<path d="M25.132 9.04987H22.7637V12.1731C23.4423 11.5883 24.2473 11.1513 25.132 10.9037V9.04956V9.04987Z" fill="#041E49"/>
-<path d="M18.9927 2L19.0201 2.01444L19.0475 2H18.9927Z" fill="#041E49"/>
-<path d="M27.0414 6.2246L19.0207 2.01447L11 6.2246V7.95282H27.0414V6.2246ZM14.8666 6.11496L18.9933 4.11247H19.0482L22.9233 6.11496H14.8666Z" fill="#041E49"/>
-<path d="M20.2459 9.04987H17.9038V15.3046H20.2459V9.04987Z" fill="#041E49"/>
-<path d="M27.0421 12.0089C24.3079 12.0089 22.0835 14.25 22.0835 17.0046C22.0835 19.7593 24.3079 22.0004 27.0421 22.0004C29.7762 22.0004 32.0006 19.7593 32.0006 17.0046C32.0006 14.25 29.7762 12.0089 27.0421 12.0089ZM26.7958 17.6106C26.437 17.2491 26.0056 17.0685 25.5017 17.0685H23.6111C23.6108 17.047 23.6077 17.0261 23.6077 17.0046C23.6077 15.0967 25.1484 13.5445 27.0421 13.5445C27.2637 13.5445 27.4795 13.5675 27.6896 13.6081C27.6112 13.6738 27.5384 13.746 27.4771 13.8323C27.3817 13.9668 27.3338 14.1225 27.3338 14.2994H26.8756C26.6238 14.2994 26.408 14.3897 26.2287 14.5706C26.0492 14.7512 25.9596 14.9687 25.9596 15.2224V16.1453H27.7917V16.6069C27.7917 16.8606 27.8813 17.078 28.0609 17.2586C28.2404 17.4392 28.4559 17.5298 28.7077 17.5298H29.6238C29.8527 17.5298 30.0512 17.4567 30.2191 17.3105C30.3359 17.2089 30.4191 17.0876 30.4734 16.9487C30.4734 16.9675 30.4761 16.9856 30.4761 17.0043C30.4761 18.9122 28.9354 20.4644 27.0418 20.4644C26.6635 20.4644 26.3001 20.3999 25.9596 20.2857V19.3757H27.3338V18.9141C27.3338 18.4064 27.1546 17.9721 26.7958 17.6106Z" fill="#041E49"/>
+<svg width="48" height="30" viewBox="0 0 48 30" fill="none" xmlns="http://www.w3.org/2000/svg">
+<rect width="48" height="30" rx="4" fill="#D3E3FD"/>
+<g clip-path="url(#clip0_1575_2429)">
+<path d="M24.4391 20.339H13.0645V22.9681L24.6536 23.0182L24.4391 20.339Z" fill="#041E49"/>
+<path d="M18.1338 11.4661H15.3616V18.9587H18.1338V11.4661Z" fill="#041E49"/>
+<path d="M29.8241 11.4661H27.0046V15.2074C27.8125 14.5069 28.7709 13.9833 29.8241 13.6868V11.4657V11.4661Z" fill="#041E49"/>
+<path d="M22.5149 3.02087L22.5476 3.03762L22.5802 3.02087H22.5149Z" fill="#041E49"/>
+<path d="M32.0969 8.0816L22.5485 3.03821L13 8.0816V10.1519H32.0969V8.0816ZM17.6031 7.95026L22.5158 5.55144H22.5811L27.1944 7.95026H17.6031Z" fill="#041E49"/>
+<path d="M24.007 11.4661H21.2188V18.9587H24.007V11.4661Z" fill="#041E49"/>
+<path d="M32.0976 15.0106C28.8427 15.0106 26.1946 17.6953 26.1946 20.9951C26.1946 24.295 28.8427 26.9797 32.0976 26.9797C35.3526 26.9797 38.0007 24.295 38.0007 20.9951C38.0007 17.6953 35.3526 15.0106 32.0976 15.0106ZM31.8044 21.721C31.3773 21.288 30.8638 21.0717 30.2639 21.0717H28.0131C28.0127 21.0459 28.0091 21.0209 28.0091 20.9951C28.0091 18.7096 29.8433 16.8502 32.0976 16.8502C32.3615 16.8502 32.6184 16.8778 32.8685 16.9264C32.7752 17.0051 32.6885 17.0916 32.6155 17.1949C32.5019 17.3561 32.4449 17.5426 32.4449 17.7545H31.8995C31.5997 17.7545 31.3428 17.8627 31.1294 18.0794C30.9156 18.2957 30.809 18.5562 30.809 18.8601V19.9657H32.99V20.5187C32.99 20.8226 33.0967 21.0831 33.3105 21.2994C33.5242 21.5157 33.7808 21.6243 34.0806 21.6243H35.1711C35.4437 21.6243 35.6799 21.5367 35.8799 21.3616C36.0189 21.2398 36.1179 21.0945 36.1825 20.9282C36.1825 20.9506 36.1858 20.9723 36.1858 20.9948C36.1858 23.2803 34.3517 25.1397 32.0973 25.1397C31.6469 25.1397 31.2143 25.0625 30.809 24.9256V23.8355H32.4449V23.2825C32.4449 22.6743 32.2316 22.1541 31.8044 21.721Z" fill="#041E49"/>
+</g>
+<defs>
+<clipPath id="clip0_1575_2429">
+<rect width="25" height="23.9583" fill="white" transform="translate(13 3.02087)"/>
+</clipPath>
+</defs>
 </svg>
diff --git a/chrome/browser/resources/side_panel/customize_chrome/app.html.ts b/chrome/browser/resources/side_panel/customize_chrome/app.html.ts
index 807eb95..b3019bb 100644
--- a/chrome/browser/resources/side_panel/customize_chrome/app.html.ts
+++ b/chrome/browser/resources/side_panel/customize_chrome/app.html.ts
@@ -87,18 +87,18 @@
       </div>
     `: ''}
   </div>
-  ${(this.isSourceTabFirstPartyNtp_) ? html`
+  ${(this.showEditTheme_) ? html`
   <customize-chrome-categories @back-click="${this.onBackClick_}"
       @collection-select="${this.onCollectionSelect_}" page-name="categories"
       id="categoriesPage" @local-image-upload="${this.onLocalImageUpload_}"
       @wallpaper-search-select="${this.onWallpaperSearchSelect_}">
   </customize-chrome-categories>`: ''}
-  ${(this.isSourceTabFirstPartyNtp_) ? html`
+  ${(this.showEditTheme_) ? html`
   <customize-chrome-themes @back-click="${this.onBackClick_}"
       page-name="themes" id="themesPage"
       .selectedCollection="${this.selectedCollection_}">
   </customize-chrome-themes>`: ''}
-  ${(this.wallpaperSearchEnabled_ && this.isSourceTabFirstPartyNtp_) ? html`
+  ${(this.wallpaperSearchEnabled_ && this.showEditTheme_) ? html`
     <customize-chrome-wallpaper-search @back-click="${this.onBackClick_}"
         page-name="wallpaper-search" id="wallpaperSearchPage">
     </customize-chrome-wallpaper-search>
diff --git a/chrome/browser/resources/side_panel/customize_chrome/app.ts b/chrome/browser/resources/side_panel/customize_chrome/app.ts
index cd7e9a602..4e3a9e4 100644
--- a/chrome/browser/resources/side_panel/customize_chrome/app.ts
+++ b/chrome/browser/resources/side_panel/customize_chrome/app.ts
@@ -83,6 +83,7 @@
       footerEnabled_: {type: Boolean},
       wallpaperSearchEnabled_: {type: Boolean},
       isSourceTabFirstPartyNtp_: {type: Boolean},
+      showEditTheme_: {type: Boolean},
     };
   }
 
@@ -105,8 +106,10 @@
   protected wallpaperSearchEnabled_: boolean =
       loadTimeData.getBoolean('wallpaperSearchEnabled');
   protected isSourceTabFirstPartyNtp_: boolean = true;
+  protected showEditTheme_: boolean = true;
   private scrollToSectionListenerId_: number|null = null;
   private attachedTabStateUpdatedId_: number|null = null;
+  private setThemeEditableId_: number|null = null;
   private pageHandler_: CustomizeChromePageHandlerInterface =
       CustomizeChromeApiProxy.getInstance().handler;
 
@@ -155,6 +158,12 @@
                 });
     this.pageHandler_.updateAttachedTabState();
 
+    this.setThemeEditableId_ = CustomizeChromeApiProxy.getInstance()
+                                   .callbackRouter.setThemeEditable.addListener(
+                                       (isThemeEditable: boolean) => {
+                                         this.showEditTheme_ = isThemeEditable;
+                                       });
+
     // We wait for load because `scrollIntoView` above requires the page to be
     // laid out.
     window.addEventListener('load', () => {
@@ -191,6 +200,10 @@
     assert(this.attachedTabStateUpdatedId_);
     CustomizeChromeApiProxy.getInstance().callbackRouter.removeListener(
         this.attachedTabStateUpdatedId_);
+
+    assert(this.setThemeEditableId_);
+    CustomizeChromeApiProxy.getInstance().callbackRouter.removeListener(
+        this.setThemeEditableId_);
   }
 
   protected async onBackClick_() {
diff --git a/chrome/browser/resources/side_panel/customize_chrome/appearance.html.ts b/chrome/browser/resources/side_panel/customize_chrome/appearance.html.ts
index a4fcd3e66..e1a81e42 100644
--- a/chrome/browser/resources/side_panel/customize_chrome/appearance.html.ts
+++ b/chrome/browser/resources/side_panel/customize_chrome/appearance.html.ts
@@ -6,6 +6,9 @@
 
 import type {AppearanceElement} from './appearance.js';
 
+// TODO(crbug.com/399172460) For extension NTPs with 3P theme, show edit theme
+// buttons after hover buttons (thirdPartyThemeLinkButton and
+// thirdPartyManageLinkButton).
 export function getHtml(this: AppearanceElement) {
   // clang-format off
   return html`<!--_html_template_start_-->
@@ -32,7 +35,7 @@
     label="$i18n{yourSearchedImage}"
     label-description="$i18n{currentTheme}">
 </customize-chrome-hover-button>
-<div id="editButtonsContainer" ?hidden="${!this.isSourceTabFirstPartyNtp_}">
+<div id="editButtonsContainer" ?hidden="${!this.showEditTheme_}">
   <cr-button id="editThemeButton" @click="${this.onEditThemeClicked_}"
       class="floating-button">
     <div id="editThemeIcon" class="cr-icon edit-theme-icon" slot="prefix-icon"
diff --git a/chrome/browser/resources/side_panel/customize_chrome/appearance.ts b/chrome/browser/resources/side_panel/customize_chrome/appearance.ts
index 72c8770..1e5b235 100644
--- a/chrome/browser/resources/side_panel/customize_chrome/appearance.ts
+++ b/chrome/browser/resources/side_panel/customize_chrome/appearance.ts
@@ -77,6 +77,7 @@
       showUploadedImageButton_: {type: Boolean},
       showSearchedImageButton_: {type: Boolean},
       showManagedDialog_: {type: Boolean},
+      showEditTheme_: {type: Boolean},
       isSourceTabFirstPartyNtp_: {type: Boolean},
 
       wallpaperSearchButtonEnabled_: {
@@ -105,7 +106,9 @@
   private wallpaperSearchEnabled_: boolean =
       loadTimeData.getBoolean('wallpaperSearchEnabled');
   protected isSourceTabFirstPartyNtp_: boolean = true;
+  protected showEditTheme_: boolean = true;
   protected ntpManagedByName_: string = '';
+  private setThemeEditableId_: number|null = null;
   private setThemeListenerId_: number|null = null;
   private attachedTabStateUpdatedId_: number|null = null;
   private ntpManagedByNameUpdatedId_: number|null = null;
@@ -136,6 +139,12 @@
                 });
     this.pageHandler_.updateAttachedTabState();
 
+    this.setThemeEditableId_ = CustomizeChromeApiProxy.getInstance()
+                                   .callbackRouter.setThemeEditable.addListener(
+                                       (isThemeEditable: boolean) => {
+                                         this.showEditTheme_ = isThemeEditable;
+                                       });
+
     this.ntpManagedByNameUpdatedId_ =
         CustomizeChromeApiProxy.getInstance()
             .callbackRouter.ntpManagedByNameUpdated.addListener(
@@ -151,12 +160,13 @@
     this.callbackRouter_.removeListener(this.setThemeListenerId_);
 
     assert(this.attachedTabStateUpdatedId_);
-    CustomizeChromeApiProxy.getInstance().callbackRouter.removeListener(
-        this.attachedTabStateUpdatedId_);
+    this.callbackRouter_.removeListener(this.attachedTabStateUpdatedId_);
 
     assert(this.ntpManagedByNameUpdatedId_);
-    CustomizeChromeApiProxy.getInstance().callbackRouter.removeListener(
-        this.ntpManagedByNameUpdatedId_);
+    this.callbackRouter_.removeListener(this.ntpManagedByNameUpdatedId_);
+
+    assert(this.setThemeEditableId_);
+    this.callbackRouter_.removeListener(this.setThemeEditableId_);
   }
 
   override willUpdate(changedProperties: PropertyValues<this>) {
@@ -247,6 +257,8 @@
     return !!this.theme_ && !this.theme_.thirdPartyThemeInfo &&
         (!(this.theme_.backgroundImage &&
            this.theme_.backgroundImage.isUploadedImage)) &&
+        // TODO(crbug.com/404247286) Enable snapshots for extension NTP with 1P
+        // theme.
         this.isSourceTabFirstPartyNtp_;
   }
 
diff --git a/chrome/browser/resources/side_panel/customize_chrome/wallpaper_search/wallpaper_search.html.ts b/chrome/browser/resources/side_panel/customize_chrome/wallpaper_search/wallpaper_search.html.ts
index 66bee9c..3850734 100644
--- a/chrome/browser/resources/side_panel/customize_chrome/wallpaper_search/wallpaper_search.html.ts
+++ b/chrome/browser/resources/side_panel/customize_chrome/wallpaper_search/wallpaper_search.html.ts
@@ -111,7 +111,7 @@
         </svg>
       </cr-loading-gradient>
     </div>
-    <cr-grid id="resultGrid" columns="3" disable-arrow-navigation
+    <cr-grid id="resultGrid" columns="3"
         ?hidden="${!this.results_}" role="radiogroup">
       ${this.results_.map((item, index) => html`
         <div class="tile result" tabindex="0" role="radio"
@@ -182,7 +182,7 @@
               ${this.getInspirationGroupTitle_(item.descriptors)}
             </div>
           </h3>
-          <cr-grid columns="3" disable-arrow-navigation role="radiogroup">
+          <cr-grid columns="3" role="radiogroup">
             ${item.inspirations.map((item, index) => html`
               <div class="tile result"
                   data-group-index="${groupIndex}" data-index="${index}"
@@ -210,7 +210,7 @@
     <h2 slot="heading">$i18n{wallpaperSearchHistoryHeader}</h2>
   </sp-heading>
   <div class="content">
-    <cr-grid columns="3" disable-arrow-navigation role="radiogroup">
+    <cr-grid columns="3" role="radiogroup">
       ${this.history_.map((item, index) => html`
         <div class="tile result" tabindex="0" role="radio"
             aria-label="${this.getHistoryResultAriaLabel_(index, item)}"
diff --git a/chrome/browser/resources/side_panel/read_anything/app.ts b/chrome/browser/resources/side_panel/read_anything/app.ts
index f174b74f..5935744 100644
--- a/chrome/browser/resources/side_panel/read_anything/app.ts
+++ b/chrome/browser/resources/side_panel/read_anything/app.ts
@@ -389,9 +389,10 @@
     setTimeout(() => chrome.readingMode.shouldShowUi(), 0);
 
     this.showLoading();
-    VoiceNotificationManager.getInstance().addListener(this.$.languageToast);
 
     if (this.isReadAloudEnabled_) {
+      VoiceNotificationManager.getInstance().addListener(this.$.languageToast);
+
       // Clear state. We don't do this in disconnectedCallback because that's
       // not always reliabled called.
       this.speech_.cancel();
@@ -713,6 +714,16 @@
     // Each time we rebuild the subtree, we should clear the node id of the
     // first text node.
     this.firstTextNodeSetForReadAloud = null;
+
+    // This shouldn't happen. If it does, there is likely a bug, so log it so
+    // we can monitor it.
+    if (this.speechPlayingState.isAudioCurrentlyPlaying) {
+      console.error(
+          'updateContent called while audio is currently playing. ',
+          'There may be a bug.');
+      this.logger_.logSpeechStopSource(
+          chrome.readingMode.unexpectedUpdateContentStopSource);
+    }
     this.speech_.cancel();
     this.clearReadAloudState();
     const container = this.$.container;
@@ -2022,6 +2033,8 @@
         // from opening another instance of reading mode), so we should
         // ensure speech state, including the play / pause button, is
         // updated.
+        this.logger_.logSpeechStopSource(
+            chrome.readingMode.engineInterruptStopSource);
         this.stopSpeech(PauseActionSource.ENGINE_INTERRUPT);
       }
       return;
@@ -2048,6 +2061,7 @@
       // speech rate, update the speech rate to the WebSpeech default of 1.
       chrome.readingMode.onSpeechRateChange(1);
       this.resetSpeechPostSettingChange_();
+      return;
     }
 
     // No appropriate voice is available for the language designated in
@@ -2077,6 +2091,7 @@
     // button state, and highlighting in order to give visual feedback that
     // something went wrong.
     // TODO: crbug.com/40927698 - Consider showing an error message.
+    this.logger_.logSpeechStopSource(chrome.readingMode.engineErrorStopSource);
     this.stopSpeech(PauseActionSource.DEFAULT);
   }
 
@@ -2330,6 +2345,8 @@
   }
 
   private onSpeechFinished() {
+    this.logger_.logSpeechStopSource(
+        chrome.readingMode.contentFinishedStopSource);
     this.clearReadAloudState();
 
     // Show links when speech finishes playing.
@@ -2838,6 +2855,10 @@
   protected onKeyDown_(e: KeyboardEvent) {
     if (e.key === 'k') {
       e.stopPropagation();
+      if (this.speechPlayingState.isSpeechActive) {
+        this.logger_.logSpeechStopSource(
+            chrome.readingMode.keyboardShortcutStopSource);
+      }
       this.onPlayPauseClick_();
     }
   }
diff --git a/chrome/browser/resources/side_panel/read_anything/metrics_browser_proxy.ts b/chrome/browser/resources/side_panel/read_anything/metrics_browser_proxy.ts
index ce66fd73..e22efe3 100644
--- a/chrome/browser/resources/side_panel/read_anything/metrics_browser_proxy.ts
+++ b/chrome/browser/resources/side_panel/read_anything/metrics_browser_proxy.ts
@@ -147,6 +147,7 @@
   recordSpeechError(error: ReadAnythingSpeechError): void;
   recordSpeechPlaybackLength(time: number): void;
   recordSpeechSettingsChange(settingsChange: ReadAloudSettingsChange): void;
+  recordSpeechStopSource(source: number): void;
   recordTextSettingsChange(settingsChange: ReadAnythingSettingsChange): void;
   recordTime(umaName: string, time: number): void;
   recordVoiceSpeed(index: number): void;
@@ -158,6 +159,10 @@
     chrome.readingMode.incrementMetricCount(umaName);
   }
 
+  recordSpeechStopSource(source: number) {
+    chrome.readingMode.logSpeechStop(source);
+  }
+
   recordSpeechError(error: ReadAnythingSpeechError) {
     chrome.metricsPrivate.recordEnumerationValue(
         UmaName.SPEECH_ERROR, error, ReadAnythingSpeechError.COUNT);
diff --git a/chrome/browser/resources/side_panel/read_anything/read_anything.d.ts b/chrome/browser/resources/side_panel/read_anything/read_anything.d.ts
index 744e8689..2f604c2 100644
--- a/chrome/browser/resources/side_panel/read_anything/read_anything.d.ts
+++ b/chrome/browser/resources/side_panel/read_anything/read_anything.d.ts
@@ -62,6 +62,14 @@
     let sentenceHighlighting: number;
     let noHighlighting: number;
 
+    // Enum values for speech stop sources.
+    let pauseButtonStopSource: number;
+    let keyboardShortcutStopSource: number;
+    let engineInterruptStopSource: number;
+    let engineErrorStopSource: number;
+    let contentFinishedStopSource: number;
+    let unexpectedUpdateContentStopSource: number;
+
     // Whether the Read Aloud feature flag is enabled.
     let isReadAloudEnabled: boolean;
 
@@ -373,8 +381,8 @@
     // Log UmaHistogramCount
     function incrementMetricCount(metricName: string): void;
 
-    // Log speech errors.
-    function logSpeechError(errorCode: string): void;
+    // Log when speech stops and why.
+    function logSpeechStop(source: number): void;
 
     // Returns a list of node ids and ranges (start and length) associated with
     // the index within the given text segment. The intended use is for
diff --git a/chrome/browser/resources/side_panel/read_anything/read_anything_logger.ts b/chrome/browser/resources/side_panel/read_anything/read_anything_logger.ts
index 94030da..77a094f 100644
--- a/chrome/browser/resources/side_panel/read_anything/read_anything_logger.ts
+++ b/chrome/browser/resources/side_panel/read_anything/read_anything_logger.ts
@@ -22,6 +22,10 @@
 export class ReadAnythingLogger {
   private metrics: MetricsBrowserProxy = MetricsBrowserProxyImpl.getInstance();
 
+  logSpeechStopSource(source: number) {
+    this.metrics.recordSpeechStopSource(source);
+  }
+
   logSpeechError(errorCode: string) {
     let error: ReadAnythingSpeechError;
     switch (errorCode) {
diff --git a/chrome/browser/resources/side_panel/read_anything/read_anything_toolbar.ts b/chrome/browser/resources/side_panel/read_anything/read_anything_toolbar.ts
index 914e610..5e5ca0c 100644
--- a/chrome/browser/resources/side_panel/read_anything/read_anything_toolbar.ts
+++ b/chrome/browser/resources/side_panel/read_anything/read_anything_toolbar.ts
@@ -721,6 +721,10 @@
   protected onPlayPauseClick_() {
     this.logger_.logSpeechControlClick(
         this.isSpeechActive ? SpeechControls.PAUSE : SpeechControls.PLAY);
+    if (this.isSpeechActive) {
+      this.logger_.logSpeechStopSource(
+          chrome.readingMode.pauseButtonStopSource);
+    }
     this.fire(ToolbarEvent.PLAY_PAUSE);
   }
 
diff --git a/chrome/browser/safe_browsing/BUILD.gn b/chrome/browser/safe_browsing/BUILD.gn
index 5ace46e6..50c6735f 100644
--- a/chrome/browser/safe_browsing/BUILD.gn
+++ b/chrome/browser/safe_browsing/BUILD.gn
@@ -109,8 +109,6 @@
       "chrome_client_side_detection_host_delegate.h",
       "chrome_client_side_detection_service_delegate.cc",
       "chrome_client_side_detection_service_delegate.h",
-      "chrome_enterprise_url_lookup_service.cc",
-      "chrome_enterprise_url_lookup_service.h",
       "chrome_enterprise_url_lookup_service_factory.cc",
       "chrome_enterprise_url_lookup_service_factory.h",
       "chrome_password_protection_service.cc",
@@ -221,7 +219,6 @@
       "//chrome/common/safe_browsing:proto",
       "//components/autofill/core/browser",
       "//components/content_settings/core/browser",
-      "//components/enterprise/common/proto:connectors_proto",
       "//components/enterprise/connectors/core",
       "//components/no_state_prefetch/browser",
       "//components/omnibox/browser",
@@ -247,9 +244,9 @@
       "//components/safe_browsing/core/browser/db:allowlist_checker_client",
       "//components/safe_browsing/core/browser/password_protection",
       "//components/safe_browsing/core/browser/password_protection:password_reuse_detection_manager_client",
+      "//components/safe_browsing/core/browser/realtime:enterprise_url_lookup_service",
       "//components/safe_browsing/core/browser/realtime:policy_engine",
       "//components/safe_browsing/core/browser/realtime:url_lookup_service",
-      "//components/safe_browsing/core/browser/realtime:url_lookup_service_base",
       "//components/safe_browsing/core/browser/sync",
       "//components/safe_browsing/core/browser/tailored_security_service",
       "//components/safe_browsing/core/common",
diff --git a/chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service_factory.cc b/chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service_factory.cc
index 90f07fb..377baa81 100644
--- a/chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service_factory.cc
+++ b/chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service_factory.cc
@@ -13,7 +13,6 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/safe_browsing/advanced_protection_status_manager.h"
 #include "chrome/browser/safe_browsing/advanced_protection_status_manager_factory.h"
-#include "chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service.h"
 #include "chrome/browser/safe_browsing/chrome_user_population_helper.h"
 #include "chrome/browser/safe_browsing/safe_browsing_navigation_observer_manager_factory.h"
 #include "chrome/browser/safe_browsing/safe_browsing_service.h"
@@ -21,6 +20,7 @@
 #include "chrome/browser/signin/identity_manager_factory.h"
 #include "components/safe_browsing/content/browser/safe_browsing_navigation_observer_manager.h"
 #include "components/safe_browsing/content/browser/web_ui/safe_browsing_ui.h"
+#include "components/safe_browsing/core/browser/realtime/chrome_enterprise_url_lookup_service.h"
 #include "components/safe_browsing/core/browser/sync/safe_browsing_primary_account_token_fetcher.h"
 #include "components/safe_browsing/core/browser/sync/sync_utils.h"
 #include "components/safe_browsing/core/browser/verdict_cache_manager.h"
diff --git a/chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service_unittest.cc b/chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service_unittest.cc
index f8af688..9b96321 100644
--- a/chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service_unittest.cc
+++ b/chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service_unittest.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 "chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service.h"
+#include "components/safe_browsing/core/browser/realtime/chrome_enterprise_url_lookup_service.h"
 
 #include "base/functional/bind.h"
 #include "base/test/bind.h"
diff --git a/chrome/browser/settings/android/java/src/org/chromium/chrome/browser/settings/SettingsNavigationFactory.java b/chrome/browser/settings/android/java/src/org/chromium/chrome/browser/settings/SettingsNavigationFactory.java
index 68e206f7..0514346 100644
--- a/chrome/browser/settings/android/java/src/org/chromium/chrome/browser/settings/SettingsNavigationFactory.java
+++ b/chrome/browser/settings/android/java/src/org/chromium/chrome/browser/settings/SettingsNavigationFactory.java
@@ -5,12 +5,15 @@
 package org.chromium.chrome.browser.settings;
 
 import org.chromium.base.ResettersForTesting;
+import org.chromium.build.annotations.NullMarked;
+import org.chromium.build.annotations.Nullable;
 import org.chromium.components.browser_ui.settings.SettingsNavigation;
 
 /** Factory for {@link SettingsNavigation}. Can be used from chrome/browser modules. */
+@NullMarked
 public class SettingsNavigationFactory {
     private static SettingsNavigation sInstance = new SettingsNavigationImpl();
-    private static SettingsNavigation sInstanceForTesting;
+    private static @Nullable SettingsNavigation sInstanceForTesting;
 
     /** Create a {@link SettingsNavigation}. */
     public static SettingsNavigation createSettingsNavigation() {
diff --git a/chrome/browser/signin/bound_session_credentials/bound_session_refresh_cookie_fetcher_impl_unittest.cc b/chrome/browser/signin/bound_session_credentials/bound_session_refresh_cookie_fetcher_impl_unittest.cc
index 87564d29..15e0302b 100644
--- a/chrome/browser/signin/bound_session_credentials/bound_session_refresh_cookie_fetcher_impl_unittest.cc
+++ b/chrome/browser/signin/bound_session_credentials/bound_session_refresh_cookie_fetcher_impl_unittest.cc
@@ -202,8 +202,8 @@
       network::mojom::CookieAccessDetails::Type access_type) {
     std::vector<network::mojom::CookieAccessDetailsPtr> cookie_access_details;
     cookie_access_details.emplace_back(network::mojom::CookieAccessDetails::New(
-        access_type, kGaiaUrl, url::Origin(), net::SiteForCookies(),
-        CreateReportedCookies(cookies_), std::nullopt,
+        access_type, kGaiaUrl, url::Origin(), url::Origin(),
+        net::SiteForCookies(), CreateReportedCookies(cookies_), std::nullopt,
         /*is_ad_tagged=*/false, net::CookieSettingOverrides()));
     fetcher_->OnCookiesAccessed(std::move(cookie_access_details));
   }
diff --git a/chrome/browser/storage_access_api/api_browsertest.cc b/chrome/browser/storage_access_api/api_browsertest.cc
index 6c37ec3..cf34319 100644
--- a/chrome/browser/storage_access_api/api_browsertest.cc
+++ b/chrome/browser/storage_access_api/api_browsertest.cc
@@ -869,6 +869,7 @@
 IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
                        ThirdPartyCookiesIFrameRequestsAccess_CrossSiteIframe) {
   SetBlockThirdPartyCookies(true);
+  base::HistogramTester histogram_tester;
 
   NavigateToPageWithFrame(kHostA);
   NavigateFrameTo(EchoCookiesURL(kHostB));
@@ -881,6 +882,12 @@
 
   EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));
   EXPECT_EQ(ReadCookies(GetFrame(), kHostB), CookieBundle("cross-site=b.test"));
+
+  histogram_tester.ExpectBucketCount(
+      "Blink.UseCounter.Features",
+      blink::mojom::WebFeature::
+          kCrossOriginSameSiteCookieAccessViaStorageAccessAPI,
+      0);
 }
 
 IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
@@ -989,6 +996,7 @@
 IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
                        ThirdPartyCookiesIFrameRequestsAccess_CrossOriginFetch) {
   SetBlockThirdPartyCookies(true);
+  base::HistogramTester histogram_tester;
 
   NavigateToPageWithFrame(kHostA);
   NavigateFrameTo(EchoCookiesURL(kHostBSubdomain));
@@ -1003,6 +1011,12 @@
 
   EXPECT_EQ(CookiesFromFetch(GetFrame(), kHostBSubdomain2),
             "cross-site=b.test");
+
+  histogram_tester.ExpectBucketCount(
+      "Blink.UseCounter.Features",
+      blink::mojom::WebFeature::
+          kCrossOriginSameSiteCookieAccessViaStorageAccessAPI,
+      1);
 }
 
 // Validate that in a A(B(B)) frame tree, the middle B iframe can obtain access,
diff --git a/chrome/browser/sync/test/integration/apps_helper.cc b/chrome/browser/sync/test/integration/apps_helper.cc
index fc0d602..a18e4818 100644
--- a/chrome/browser/sync/test/integration/apps_helper.cc
+++ b/chrome/browser/sync/test/integration/apps_helper.cc
@@ -14,7 +14,6 @@
 #include "base/test/bind.h"
 #include "chrome/browser/apps/app_service/app_service_proxy.h"
 #include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
-#include "chrome/browser/extensions/extension_service.h"
 #include "chrome/browser/extensions/updater/extension_updater.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/sync/test/integration/sync_app_helper.h"
@@ -33,7 +32,6 @@
 #include "components/webapps/common/web_app_id.h"
 #include "extensions/browser/extension_prefs.h"
 #include "extensions/browser/extension_registry.h"
-#include "extensions/browser/extension_system.h"
 #include "extensions/common/manifest.h"
 
 using sync_datatype_helper::test;
@@ -262,13 +260,11 @@
     InstallSyncedApps(profile);
 
     // Fake the installation of synced apps from the web store.
-    CHECK(extensions::ExtensionSystem::Get(profile)
-              ->extension_service()
-              ->updater());
-    extensions::ExtensionSystem::Get(profile)
-        ->extension_service()
-        ->updater()
-        ->SetUpdatingStartedCallbackForTesting(base::BindLambdaForTesting(
+    auto* updater = extensions::ExtensionUpdater::Get(profile);
+    CHECK(updater);
+    CHECK(updater->enabled());
+    updater->SetUpdatingStartedCallbackForTesting(
+        base::BindLambdaForTesting(
             [self = weak_ptr_factory_.GetWeakPtr(), profile]() {
               base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
                   FROM_HERE,
diff --git a/chrome/browser/sync/test/integration/extensions_helper.cc b/chrome/browser/sync/test/integration/extensions_helper.cc
index 3a1d6b97..3eb0bcd0 100644
--- a/chrome/browser/sync/test/integration/extensions_helper.cc
+++ b/chrome/browser/sync/test/integration/extensions_helper.cc
@@ -8,13 +8,11 @@
 #include "base/logging.h"
 #include "base/task/single_thread_task_runner.h"
 #include "base/test/bind.h"
-#include "chrome/browser/extensions/extension_service.h"
 #include "chrome/browser/extensions/updater/extension_updater.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/sync/test/integration/sync_datatype_helper.h"
 #include "chrome/browser/sync/test/integration/sync_extension_helper.h"
 #include "extensions/browser/extension_registry.h"
-#include "extensions/browser/extension_system.h"
 #include "extensions/common/manifest.h"
 
 using sync_datatype_helper::test;
@@ -137,14 +135,11 @@
     SyncExtensionHelper::GetInstance()->InstallExtensionsPendingForSync(
         profile);
 
-    CHECK(extensions::ExtensionSystem::Get(profile)
-              ->extension_service()
-              ->updater());
-
-    extensions::ExtensionSystem::Get(profile)
-        ->extension_service()
-        ->updater()
-        ->SetUpdatingStartedCallbackForTesting(base::BindLambdaForTesting(
+    auto* updater = extensions::ExtensionUpdater::Get(profile);
+    CHECK(updater);
+    CHECK(updater->enabled());
+    updater->SetUpdatingStartedCallbackForTesting(
+        base::BindLambdaForTesting(
             [self = weak_ptr_factory_.GetWeakPtr(), profile]() {
               base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
                   FROM_HERE,
diff --git a/chrome/browser/sync/test/integration/themes_helper.cc b/chrome/browser/sync/test/integration/themes_helper.cc
index 2b01181..bccc867 100644
--- a/chrome/browser/sync/test/integration/themes_helper.cc
+++ b/chrome/browser/sync/test/integration/themes_helper.cc
@@ -7,7 +7,6 @@
 #include "base/check.h"
 #include "base/functional/bind.h"
 #include "base/strings/string_number_conversions.h"
-#include "chrome/browser/extensions/extension_service.h"
 #include "chrome/browser/extensions/updater/extension_updater.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/sync/test/integration/sync_extension_helper.h"
@@ -15,7 +14,6 @@
 #include "chrome/browser/themes/theme_service.h"
 #include "chrome/browser/themes/theme_service_factory.h"
 #include "components/crx_file/id_util.h"
-#include "extensions/browser/extension_system.h"
 #include "extensions/common/manifest.h"
 
 namespace {
@@ -100,15 +98,12 @@
 ThemePendingInstallChecker::ThemePendingInstallChecker(Profile* profile,
                                                        const std::string& theme)
     : profile_(profile), theme_(theme) {
-  CHECK(extensions::ExtensionSystem::Get(profile)
-            ->extension_service()
-            ->updater());
-  extensions::ExtensionSystem::Get(profile)
-      ->extension_service()
-      ->updater()
-      ->SetUpdatingStartedCallbackForTesting(
-          base::BindRepeating(&ThemePendingInstallChecker::CheckExitCondition,
-                              weak_ptr_factory_.GetWeakPtr()));
+  auto* updater = extensions::ExtensionUpdater::Get(profile_);
+  CHECK(updater);
+  CHECK(updater->enabled());
+  updater->SetUpdatingStartedCallbackForTesting(
+      base::BindRepeating(&ThemePendingInstallChecker::CheckExitCondition,
+                          weak_ptr_factory_.GetWeakPtr()));
 }
 
 ThemePendingInstallChecker::~ThemePendingInstallChecker() = default;
diff --git a/chrome/browser/tabmodel/BUILD.gn b/chrome/browser/tabmodel/BUILD.gn
index d4d655c..4581466 100644
--- a/chrome/browser/tabmodel/BUILD.gn
+++ b/chrome/browser/tabmodel/BUILD.gn
@@ -79,6 +79,7 @@
     "//chrome/browser/ui/android/multiwindow:java",
     "//components/browser_ui/widget/android:java",
     "//components/embedder_support/android:util_java",
+    "//components/external_intents/android:java",
     "//components/saved_tab_groups/public:java",
     "//components/tab_groups:tab_groups_java",
     "//content/public/android:content_java",
diff --git a/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabGroupMetadata.java b/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabGroupMetadata.java
index b7f0e64..e8c1574 100644
--- a/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabGroupMetadata.java
+++ b/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabGroupMetadata.java
@@ -32,6 +32,7 @@
     private static final String KEY_TAB_GROUP_COLLAPSED = "tabGroupCollapsed";
     private static final String KEY_IS_GROUP_SHARED = "isGroupShared";
     private static final String KEY_IS_INCOGNITO = "isIncognito";
+    private static final String KEY_MHTML_TAB_TITLE = "mhtmlTabTitle";
 
     public final int rootId;
     public final int selectedTabId;
@@ -40,6 +41,7 @@
     public final LinkedHashMap<Integer, String> tabIdsToUrls;
     public final @ColorInt int tabGroupColor;
     @Nullable public final String tabGroupTitle;
+    @Nullable public final String mhtmlTabTitle;
     public final boolean tabGroupCollapsed;
     public final boolean isGroupShared;
     public final boolean isIncognito;
@@ -54,6 +56,7 @@
      * @param tabIdsToUrls The LinkedHashMap containing key-value pairs of tab IDs and URLs.
      * @param tabGroupColor The color of the tab group.
      * @param tabGroupTitle The title of the tab group.
+     * @param mhtmlTabTitle The title of the first MHTML tab in the group if there is any.
      * @param tabGroupCollapsed Whether the tab group is currently collapsed.
      * @param isGroupShared Whether the tab group is shared with other collaborators.
      * @param isIncognito Whether the tab group is in incognito mode.
@@ -66,6 +69,7 @@
             LinkedHashMap<Integer, String> tabIdsToUrls,
             @ColorInt int tabGroupColor,
             @Nullable String tabGroupTitle,
+            @Nullable String mhtmlTabTitle,
             boolean tabGroupCollapsed,
             boolean isGroupShared,
             boolean isIncognito) {
@@ -76,6 +80,7 @@
         this.tabIdsToUrls = tabIdsToUrls;
         this.tabGroupColor = tabGroupColor;
         this.tabGroupTitle = tabGroupTitle;
+        this.mhtmlTabTitle = mhtmlTabTitle;
         this.tabGroupCollapsed = tabGroupCollapsed;
         this.isGroupShared = isGroupShared;
         this.isIncognito = isIncognito;
@@ -95,6 +100,7 @@
         bundle.putSerializable(KEY_TAB_IDS_TO_URLS, tabIdsToUrls);
         bundle.putInt(KEY_TAB_GROUP_COLOR, tabGroupColor);
         bundle.putString(KEY_TAB_GROUP_TITLE, tabGroupTitle);
+        bundle.putString(KEY_MHTML_TAB_TITLE, mhtmlTabTitle);
         bundle.putBoolean(KEY_TAB_GROUP_COLLAPSED, tabGroupCollapsed);
         bundle.putBoolean(KEY_IS_GROUP_SHARED, isGroupShared);
         bundle.putBoolean(KEY_IS_INCOGNITO, isIncognito);
@@ -135,6 +141,7 @@
                         tabIdsToUrls,
                         bundle.getInt(KEY_TAB_GROUP_COLOR),
                         bundle.getString(KEY_TAB_GROUP_TITLE),
+                        bundle.getString(KEY_MHTML_TAB_TITLE),
                         bundle.getBoolean(KEY_TAB_GROUP_COLLAPSED),
                         bundle.getBoolean(KEY_IS_GROUP_SHARED),
                         bundle.getBoolean(KEY_IS_INCOGNITO));
@@ -155,7 +162,8 @@
                 && isIncognito == that.isIncognito
                 && Objects.equals(tabGroupId, that.tabGroupId)
                 && Objects.equals(tabIdsToUrls, that.tabIdsToUrls)
-                && Objects.equals(tabGroupTitle, that.tabGroupTitle);
+                && Objects.equals(tabGroupTitle, that.tabGroupTitle)
+                && Objects.equals(mhtmlTabTitle, that.mhtmlTabTitle);
     }
 
     @Override
@@ -168,6 +176,7 @@
                 this.tabIdsToUrls,
                 this.tabGroupColor,
                 this.tabGroupTitle,
+                this.mhtmlTabTitle,
                 this.tabGroupCollapsed,
                 this.isGroupShared,
                 this.isIncognito);
@@ -190,6 +199,9 @@
                 + ", tabGroupTitle='"
                 + tabGroupTitle
                 + '\''
+                + ", mhtmlTabTitle='"
+                + mhtmlTabTitle
+                + '\''
                 + ", isCollapsed="
                 + tabGroupCollapsed
                 + ", isGroupShared="
diff --git a/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabGroupMetadataExtractor.java b/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabGroupMetadataExtractor.java
index 6958b29..c202f97 100644
--- a/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabGroupMetadataExtractor.java
+++ b/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabGroupMetadataExtractor.java
@@ -4,11 +4,16 @@
 
 package org.chromium.chrome.browser.tabmodel;
 
+import android.text.TextUtils;
+
 import androidx.annotation.Nullable;
 
+import org.chromium.base.FileUtils;
 import org.chromium.base.Token;
 import org.chromium.build.annotations.NullMarked;
 import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.components.embedder_support.util.UrlConstants;
+import org.chromium.components.external_intents.ExternalNavigationHandler;
 
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -38,6 +43,7 @@
         // tab group, otherwise default select the first tab in the group after re-parenting to
         // destination window.
         LinkedHashMap<Integer, String> tabIdsToUrls = new LinkedHashMap();
+        @Nullable String mhtmlTabTitle = null;
         boolean selectedTabIsInGroup = false;
         // Tabs are stored in reverse to ensure the correct opening order. Because tabs are inserted
         // one-by-one at the same start index in the target window, storing them in their original
@@ -45,7 +51,11 @@
         for (int i = groupedTabs.size() - 1; i >= 0; i--) {
             Tab tab = groupedTabs.get(i);
             if (tab.getId() == selectedTabId) selectedTabIsInGroup = true;
-            tabIdsToUrls.put(tab.getId(), tab.getUrl().getSpec());
+            String url = tab.getUrl().getSpec();
+            tabIdsToUrls.put(tab.getId(), url);
+            if (isMhtmlUrl(url)) {
+                mhtmlTabTitle = tab.getTitle();
+            }
         }
         if (!selectedTabIsInGroup) selectedTabId = groupedTabs.get(0).getId();
 
@@ -60,6 +70,9 @@
         @Nullable String tabGroupTitle = TabGroupTitleUtils.getTabGroupTitle(rootId);
         boolean tabGroupCollapsed = TabGroupCollapsedUtils.getTabGroupCollapsed(rootId);
 
+        // If the tab group is collapsed, do not select any tab within the group.
+        if (tabGroupCollapsed) selectedTabId = Tab.INVALID_TAB_ID;
+
         // 4. Create and populate TabGroupMetadata with data gathered above.
         TabGroupMetadata tabGroupMetadata =
                 new TabGroupMetadata(
@@ -70,9 +83,18 @@
                         tabIdsToUrls,
                         tabGroupColor,
                         tabGroupTitle,
+                        mhtmlTabTitle,
                         tabGroupCollapsed,
                         isGroupShared,
                         firstTab.isIncognitoBranded());
         return tabGroupMetadata;
     }
+
+    private static boolean isMhtmlUrl(String url) {
+        String scheme = ExternalNavigationHandler.getSanitizedUrlScheme(url);
+        boolean isFileUriScheme = TextUtils.equals(scheme, UrlConstants.FILE_SCHEME);
+        String extension = FileUtils.getExtension(url);
+        boolean isMhtmlExtension = extension.equals("mhtml") || extension.equals("mht");
+        return isFileUriScheme && isMhtmlExtension;
+    }
 }
diff --git a/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabGroupMetadataUnitTest.java b/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabGroupMetadataUnitTest.java
index e2023d1..550e556 100644
--- a/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabGroupMetadataUnitTest.java
+++ b/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabGroupMetadataUnitTest.java
@@ -41,6 +41,7 @@
     private static final int SOURCE_WINDOW_INDEX = 5;
     private static final @ColorInt int TAB_GROUP_COLOR = 0;
     private static final String TAB_GROUP_TITLE = "Title";
+    private static final String MHTML_TAB_TITLE = "mhtml tab";
     private static final boolean TAB_GROUP_COLLAPSED = true;
     private static final boolean IS_GROUP_SHARED = false;
     private static final boolean IS_INCOGNITO = false;
@@ -59,6 +60,7 @@
                         TAB_IDS_TO_URLS,
                         TAB_GROUP_COLOR,
                         TAB_GROUP_TITLE,
+                        MHTML_TAB_TITLE,
                         TAB_GROUP_COLLAPSED,
                         IS_GROUP_SHARED,
                         IS_INCOGNITO);
diff --git a/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabGroupUtilsUnitTest.java b/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabGroupUtilsUnitTest.java
index 55f16ca..a44782a 100644
--- a/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabGroupUtilsUnitTest.java
+++ b/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabGroupUtilsUnitTest.java
@@ -146,6 +146,7 @@
                         TAB_IDS_TO_URLS,
                         /* tabGroupColor= */ 0,
                         TAB_GROUP_TITLE,
+                        /* mhtmlTabTitle= */ null,
                         /* tabGroupCollapsed= */ true,
                         /* isGroupShared= */ false,
                         /* isIncognito= */ false);
diff --git a/chrome/browser/task_manager/BUILD.gn b/chrome/browser/task_manager/BUILD.gn
index 6537f36a..b81bbf4 100644
--- a/chrome/browser/task_manager/BUILD.gn
+++ b/chrome/browser/task_manager/BUILD.gn
@@ -2,6 +2,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//chrome/common/features.gni")
 import("//components/nacl/features.gni")
 
 if (is_android) {
@@ -59,6 +60,7 @@
     "//chrome/browser/profiles:profile",
     "//chrome/browser/profiles:profile_util",
     "//chrome/browser/task_manager/common:common",
+    "//chrome/common:buildflags",
     "//components/favicon/core",
     "//components/nacl/common:buildflags",
     "//components/sessions:session_id",
@@ -249,6 +251,9 @@
       "providers/web_contents/guest_task_mparch.cc",
     ]
   }
+  if (enable_glic) {
+    deps += [ "//chrome/browser/glic" ]
+  }
 }
 
 if (is_android) {
diff --git a/chrome/browser/task_manager/providers/child_process_task.cc b/chrome/browser/task_manager/providers/child_process_task.cc
index 5357618..461fbcc 100644
--- a/chrome/browser/task_manager/providers/child_process_task.cc
+++ b/chrome/browser/task_manager/providers/child_process_task.cc
@@ -17,6 +17,7 @@
 #include "chrome/browser/process_resource_usage.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/task_manager/task_manager_observer.h"
+#include "chrome/common/buildflags.h"
 #include "chrome/common/chrome_switches.h"
 #include "chrome/grit/generated_resources.h"
 #include "chrome/grit/theme_resources.h"
@@ -27,17 +28,20 @@
 #include "content/public/browser/child_process_data.h"
 #include "content/public/browser/child_process_host.h"
 #include "content/public/common/process_type.h"
-
-#if !BUILDFLAG(IS_ANDROID)
-#include "extensions/browser/extension_registry.h"  // nogncheck
-#include "extensions/common/extension_set.h"        // nogncheck
-#endif                                              // !BUILDFLAG(IS_ANDROID)
-
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "services/service_manager/public/cpp/interface_provider.h"
 #include "ui/base/l10n/l10n_util.h"
 
+#if !BUILDFLAG(IS_ANDROID)
+#include "extensions/browser/extension_registry.h"  // nogncheck
+#include "extensions/common/extension_set.h"        // nogncheck
+#endif
+
+#if BUILDFLAG(ENABLE_GLIC)
+#include "chrome/browser/glic/resources/grit/glic_browser_resources.h"
+#endif
+
 namespace task_manager {
 
 namespace {
@@ -112,6 +116,11 @@
         case ChildProcessTask::ProcessSubtype::kSpareRenderProcess:
           return l10n_util::GetStringUTF16(
               IDS_TASK_MANAGER_SPARE_RENDERER_PREFIX);
+#if BUILDFLAG(ENABLE_GLIC)
+        case ChildProcessTask::ProcessSubtype::kGlicRenderProcess:
+          return l10n_util::GetStringUTF16(
+              IDS_TASK_MANAGER_GLIC_RENDERER_PREFIX);
+#endif
         case ChildProcessTask::ProcessSubtype::kUnknownRenderProcess:
           return l10n_util::GetStringUTF16(
               IDS_TASK_MANAGER_UNKNOWN_RENDERER_PREFIX);
@@ -229,6 +238,9 @@
   switch (process_subtype_) {
     case ChildProcessTask::ProcessSubtype::kSpareRenderProcess:
       return Task::SubType::kSpareRenderer;
+#if BUILDFLAG(ENABLE_GLIC)
+    case ChildProcessTask::ProcessSubtype::kGlicRenderProcess:
+#endif
     case ChildProcessTask::ProcessSubtype::kUnknownRenderProcess:
       return Task::SubType::kUnknownRenderer;
     default:
diff --git a/chrome/browser/task_manager/providers/child_process_task.h b/chrome/browser/task_manager/providers/child_process_task.h
index c0c18f4..579b8c5 100644
--- a/chrome/browser/task_manager/providers/child_process_task.h
+++ b/chrome/browser/task_manager/providers/child_process_task.h
@@ -10,6 +10,7 @@
 #include <memory>
 
 #include "chrome/browser/task_manager/providers/task.h"
+#include "chrome/common/buildflags.h"
 
 class ProcessResourceUsage;
 
@@ -31,6 +32,10 @@
     // The "spare" render process, a render process used so that there is always
     // a render process ready to go.
     kSpareRenderProcess,
+#if BUILDFLAG(ENABLE_GLIC)
+    // A render process used for chrome://glic.
+    kGlicRenderProcess,
+#endif
     // A render process that is unknown and for which no provider is available.
     // Should not be used; all processes should be shown in the Task Manager.
     // See https://crbug.com/739782 .
diff --git a/chrome/browser/task_manager/providers/render_process_host_task_provider.cc b/chrome/browser/task_manager/providers/render_process_host_task_provider.cc
index 27d531d..99dad0a 100644
--- a/chrome/browser/task_manager/providers/render_process_host_task_provider.cc
+++ b/chrome/browser/task_manager/providers/render_process_host_task_provider.cc
@@ -6,6 +6,7 @@
 
 #include "base/process/process.h"
 #include "chrome/browser/task_manager/providers/child_process_task.h"
+#include "chrome/common/buildflags.h"
 #include "content/public/browser/browser_child_process_host_iterator.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/child_process_data.h"
@@ -13,16 +14,51 @@
 #include "content/public/common/process_type.h"
 #include "extensions/buildflags/buildflags.h"
 
-using content::RenderProcessHost;
-using content::BrowserThread;
-using content::ChildProcessData;
-
 #if BUILDFLAG(ENABLE_EXTENSIONS)
 #include "extensions/browser/process_map.h"  // nogncheck
 #endif
 
+#if BUILDFLAG(ENABLE_GLIC)
+#include "chrome/browser/glic/glic_enabling.h"
+#include "chrome/browser/glic/glic_keyed_service_factory.h"
+#include "chrome/browser/glic/widget/glic_window_controller.h"
+#endif
+
+using content::BrowserThread;
+using content::ChildProcessData;
+using content::RenderProcessHost;
+
 namespace task_manager {
 
+namespace {
+
+#if BUILDFLAG(ENABLE_GLIC)
+bool IsHostForGlic(content::RenderProcessHost* host) {
+  // This needs to match the GlicKeyedServiceFactory initialization logic in
+  // EnsureBrowserContextKeyedServiceFactoriesBuilt().
+  if (!glic::GlicEnabling::IsEnabledByFlags()) {
+    return false;
+  }
+
+  auto* glic_service = glic::GlicKeyedServiceFactory::GetGlicKeyedService(
+      host->GetBrowserContext());
+  if (!glic_service) {
+    return false;
+  }
+
+  auto& window_controller = glic_service->window_controller();
+  auto* wc = window_controller.GetWebContents();
+  if (wc && wc->GetPrimaryMainFrame()->GetProcess() == host) {
+    return true;
+  }
+
+  auto* fre_wc = window_controller.GetFreWebContents();
+  return fre_wc && fre_wc->GetPrimaryMainFrame()->GetProcess() == host;
+}
+#endif  // BUILDFLAG(ENABLE_GLIC)
+
+}  // namespace
+
 RenderProcessHostTaskProvider::RenderProcessHostTaskProvider() = default;
 
 RenderProcessHostTaskProvider::~RenderProcessHostTaskProvider() = default;
@@ -74,10 +110,15 @@
   ChildProcessData data(content::PROCESS_TYPE_RENDERER, host->GetID());
   data.SetProcess(host->GetProcess().Duplicate());
 
+  auto subtype = ChildProcessTask::ProcessSubtype::kUnknownRenderProcess;
+#if BUILDFLAG(ENABLE_GLIC)
+  if (IsHostForGlic(host)) {
+    subtype = ChildProcessTask::ProcessSubtype::kGlicRenderProcess;
+  }
+#endif
   std::unique_ptr<ChildProcessTask>& task =
       tasks_by_rph_id_[render_process_host_id];
-  task = std::make_unique<ChildProcessTask>(
-      data, ChildProcessTask::ProcessSubtype::kUnknownRenderProcess);
+  task = std::make_unique<ChildProcessTask>(data, subtype);
   NotifyObserverTaskAdded(task.get());
 }
 
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index a35e9a8..fe76d8c 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -24,6 +24,7 @@
 import("//printing/buildflags/buildflags.gni")
 import("//rlz/buildflags/buildflags.gni")
 import("//services/screen_ai/buildflags/features.gni")
+import("//services/video_effects/args.gni")
 import("//third_party/protobuf/proto_library.gni")
 import("//tools/metrics/generate_allowlist_from_histograms_file.gni")
 import("//ui/base/ui_features.gni")
@@ -3045,8 +3046,6 @@
       "startup/web_app_info_recorder_utils.h",
       "views/media_preview/active_devices_media_coordinator.cc",
       "views/media_preview/active_devices_media_coordinator.h",
-      "views/media_preview/camera_preview/blur_switch_view_controller.cc",
-      "views/media_preview/camera_preview/blur_switch_view_controller.h",
       "views/media_preview/camera_preview/camera_coordinator.cc",
       "views/media_preview/camera_preview/camera_coordinator.h",
       "views/media_preview/camera_preview/camera_mediator.cc",
@@ -3111,6 +3110,13 @@
       "webui/on_device_translation_internals/on_device_translation_internals_ui.h",
     ]
 
+    if (enable_video_effects) {
+      sources += [
+        "views/media_preview/camera_preview/blur_switch_view_controller.cc",
+        "views/media_preview/camera_preview/blur_switch_view_controller.h",
+      ]
+    }
+
     deps += [
       "//chrome/app:generated_resources",
       "//chrome/browser/on_device_translation",
@@ -3119,6 +3125,7 @@
       "//components/capture_mode",
       "//services/audio/public/mojom",
       "//services/video_capture/public/mojom",
+      "//services/video_effects/buildflags",
     ]
   }
 
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/OmniboxSuggestionsDropdown.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/OmniboxSuggestionsDropdown.java
index 22fe4884..098c28f 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/OmniboxSuggestionsDropdown.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/OmniboxSuggestionsDropdown.java
@@ -41,9 +41,9 @@
 import org.chromium.components.browser_ui.widget.RoundedCornerOutlineProvider;
 import org.chromium.components.omnibox.OmniboxFeatures;
 import org.chromium.ui.KeyboardVisibilityDelegate;
-import org.chromium.ui.MotionEventUtils;
 import org.chromium.ui.base.DeviceFormFactor;
 import org.chromium.ui.base.ViewUtils;
+import org.chromium.ui.util.MotionEventUtils;
 
 import java.util.Optional;
 
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chrome/browser/ui/android/strings/android_chrome_strings.grd
index 418007c1..81e2df9 100644
--- a/chrome/browser/ui/android/strings/android_chrome_strings.grd
+++ b/chrome/browser/ui/android/strings/android_chrome_strings.grd
@@ -5177,6 +5177,9 @@
       <message name="IDS_TAB_DROPPED_DIFFERENT_MODEL" desc="Toast that appears when a tab is dropped into tab strip with a different profile (regular v.s. incognito).">
         You can find all open tabs in the tab switcher
       </message>
+      <message name="IDS_TAB_CANNOT_BE_MOVED" desc="Toast that appears when attempting to move a tab group containing an MHTML tab into another window.">
+        Tab <ph name="TAB_TITLE">%1$s<ex>Google</ex></ph> can't be moved
+      </message>
 
       <message name="IDS_CLOSE_TAB" desc="Context menu option that allows the user to close the selected tab.">
         Close tab
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_TAB_CANNOT_BE_MOVED.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_TAB_CANNOT_BE_MOVED.png.sha1
new file mode 100644
index 0000000..9115773
--- /dev/null
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_TAB_CANNOT_BE_MOVED.png.sha1
@@ -0,0 +1 @@
+9cc916808eec71e0a596f44c11c30847c230d43b
\ No newline at end of file
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarLongPressMenuHandler.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarLongPressMenuHandler.java
index 22798763..c8eb81d 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarLongPressMenuHandler.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarLongPressMenuHandler.java
@@ -29,6 +29,7 @@
 import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
 import org.chromium.chrome.browser.preferences.ChromeSharedPreferences;
 import org.chromium.chrome.browser.profiles.Profile;
+import org.chromium.chrome.browser.toolbar.settings.AddressBarPreference;
 import org.chromium.components.browser_ui.widget.BrowserUiListMenuUtils;
 import org.chromium.components.feature_engagement.EventConstants;
 import org.chromium.components.feature_engagement.Tracker;
@@ -159,9 +160,7 @@
     }
 
     private void displayMenu(View view) {
-        boolean onTop =
-                mSharedPreferencesManager.readBoolean(
-                        ChromePreferenceKeys.TOOLBAR_TOP_ANCHORED, true);
+        boolean onTop = AddressBarPreference.isToolbarConfiguredToShowOnTop();
 
         BasicListMenu listMenu =
                 BrowserUiListMenuUtils.getBasicListMenu(
@@ -235,9 +234,7 @@
     }
 
     private void handleMoveAddressBarTo() {
-        boolean onTop =
-                mSharedPreferencesManager.readBoolean(
-                        ChromePreferenceKeys.TOOLBAR_TOP_ANCHORED, true);
+        boolean onTop = AddressBarPreference.isToolbarConfiguredToShowOnTop();
         mSharedPreferencesManager.writeBoolean(ChromePreferenceKeys.TOOLBAR_TOP_ANCHORED, !onTop);
     }
 
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarPositionController.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarPositionController.java
index 2c660b8..8e0dd59 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarPositionController.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarPositionController.java
@@ -17,7 +17,6 @@
 import androidx.coordinatorlayout.widget.CoordinatorLayout;
 import androidx.coordinatorlayout.widget.CoordinatorLayout.LayoutParams;
 
-import org.chromium.base.ContextUtils;
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.supplier.ObservableSupplier;
 import org.chromium.base.supplier.ObservableSupplierImpl;
@@ -31,6 +30,7 @@
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
 import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.toolbar.settings.AddressBarPreference;
 import org.chromium.components.embedder_support.util.UrlUtilities;
 import org.chromium.ui.KeyboardVisibilityDelegate;
 import org.chromium.ui.base.DeviceFormFactor;
@@ -253,9 +253,7 @@
     /** Returns true if toolbar is user-configured to show on top. */
     private static boolean isToolbarConfiguredToShowOnTop() {
         if (sToolbarShouldShowOnTop == null) {
-            sToolbarShouldShowOnTop =
-                    ContextUtils.getAppSharedPreferences()
-                            .getBoolean(ChromePreferenceKeys.TOOLBAR_TOP_ANCHORED, true);
+            sToolbarShouldShowOnTop = AddressBarPreference.isToolbarConfiguredToShowOnTop();
         }
         return sToolbarShouldShowOnTop;
     }
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarPositionControllerTest.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarPositionControllerTest.java
index e361785..3b46837 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarPositionControllerTest.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarPositionControllerTest.java
@@ -385,6 +385,16 @@
 
     @Test
     @Config(qualifiers = "sw400dp")
+    @EnableFeatures(ChromeFeatureList.ANDROID_BOTTOM_TOOLBAR + ":default_to_top/false")
+    public void testDefaultBottom() {
+        assertControlsAtBottom();
+
+        setUserToolbarAnchorPreference(/* showToolbarOnTop= */ true);
+        assertControlsAtTop();
+    }
+
+    @Test
+    @Config(qualifiers = "sw400dp")
     @EnableFeatures(ChromeFeatureList.ANDROID_BOTTOM_TOOLBAR)
     public void testUpdatePositionChangesWithPref() {
         assertControlsAtTop();
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/settings/AddressBarHeaderPreference.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/settings/AddressBarHeaderPreference.java
index f282fcd..b81ce081 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/settings/AddressBarHeaderPreference.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/settings/AddressBarHeaderPreference.java
@@ -17,22 +17,18 @@
 import androidx.preference.PreferenceViewHolder;
 
 import org.chromium.base.ContextUtils;
-import org.chromium.base.shared_preferences.SharedPreferencesManager;
 import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
-import org.chromium.chrome.browser.preferences.ChromeSharedPreferences;
 import org.chromium.chrome.browser.toolbar.R;
 
 /** The header shows on the top of {@link AddressBarPreference}. */
 public class AddressBarHeaderPreference extends Preference
         implements OnSharedPreferenceChangeListener {
-    private @NonNull SharedPreferencesManager mSharedPreferencesManager;
     private @NonNull ImageView mToolbarPositionImage;
 
     public AddressBarHeaderPreference(Context context, AttributeSet attrs) {
         super(context, attrs);
         // Inflating from XML.
         setLayoutResource(R.layout.address_bar_header_preference);
-        mSharedPreferencesManager = ChromeSharedPreferences.getInstance();
     }
 
     @Override
@@ -66,9 +62,7 @@
     }
 
     private void updateImageVisibility() {
-        boolean showOnTop =
-                mSharedPreferencesManager.readBoolean(
-                        ChromePreferenceKeys.TOOLBAR_TOP_ANCHORED, true);
+        boolean showOnTop = AddressBarPreference.isToolbarConfiguredToShowOnTop();
 
         mToolbarPositionImage.setSelected(showOnTop);
         int stringRes =
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/settings/AddressBarPreference.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/settings/AddressBarPreference.java
index e790cfdf..d391b04 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/settings/AddressBarPreference.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/settings/AddressBarPreference.java
@@ -14,6 +14,7 @@
 
 import org.chromium.build.annotations.Initializer;
 import org.chromium.build.annotations.NullMarked;
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
 import org.chromium.chrome.browser.preferences.ChromeSharedPreferences;
 import org.chromium.chrome.browser.toolbar.R;
@@ -33,6 +34,18 @@
         setLayoutResource(R.layout.address_bar_preference);
     }
 
+    /**
+     * Returns whether the toolbar is user-configured to show on top. If no value has been set
+     * explicitly by the user a default param is used. The value of the default param is
+     * configurable for experimental purposes but defaults to top.
+     */
+    public static boolean isToolbarConfiguredToShowOnTop() {
+        return ChromeSharedPreferences.getInstance()
+                .readBoolean(
+                        ChromePreferenceKeys.TOOLBAR_TOP_ANCHORED,
+                        ChromeFeatureList.sAndroidBottomToolbarDefaultToTop.getValue());
+    }
+
     @Override
     @Initializer
     public void onBindViewHolder(PreferenceViewHolder holder) {
@@ -56,9 +69,7 @@
     }
 
     private void initializeRadioButtonSelection() {
-        boolean showOnTop =
-                ChromeSharedPreferences.getInstance()
-                        .readBoolean(ChromePreferenceKeys.TOOLBAR_TOP_ANCHORED, true);
+        boolean showOnTop = isToolbarConfiguredToShowOnTop();
         mTopButton.setChecked(showOnTop);
         mBottomButton.setChecked(!showOnTop);
     }
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarLayout.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarLayout.java
index 2210030..3a0bd69 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarLayout.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarLayout.java
@@ -62,8 +62,8 @@
 import org.chromium.chrome.browser.util.BrowserUiUtils;
 import org.chromium.chrome.browser.util.BrowserUiUtils.ModuleTypeOnStartAndNtp;
 import org.chromium.components.feature_engagement.Tracker;
-import org.chromium.ui.MotionEventUtils;
 import org.chromium.ui.base.ViewUtils;
+import org.chromium.ui.util.MotionEventUtils;
 import org.chromium.ui.util.TokenHolder;
 import org.chromium.url.GURL;
 
diff --git a/chrome/browser/ui/android/webid/account_selection_view_android.cc b/chrome/browser/ui/android/webid/account_selection_view_android.cc
index 13be56975..862ba39 100644
--- a/chrome/browser/ui/android/webid/account_selection_view_android.cc
+++ b/chrome/browser/ui/android/webid/account_selection_view_android.cc
@@ -238,7 +238,7 @@
 }
 
 bool AccountSelectionViewAndroid::Show(
-    const std::string& rp_for_display,
+    const content::RelyingPartyData& rp_data,
     const std::vector<IdentityProviderDataPtr>& idp_list,
     const std::vector<IdentityRequestAccountPtr>& accounts,
     Account::SignInMode sign_in_mode,
@@ -271,10 +271,15 @@
   ScopedJavaLocalRef<jobjectArray> identity_providers_list =
       ConvertToJavaIdentityProvidersList(env, identity_providers_map);
 
+  ScopedJavaLocalRef<jobject> java_rp_icon = nullptr;
+  if (!rp_data.rp_icon.IsEmpty()) {
+    java_rp_icon = gfx::ConvertToJavaBitmap(*rp_data.rp_icon.ToSkBitmap());
+  }
+
   return Java_AccountSelectionBridge_showAccounts(
-      env, java_object_internal_, rp_for_display, accounts_obj,
+      env, java_object_internal_, rp_data.rp_for_display, accounts_obj,
       identity_providers_list, sign_in_mode == Account::SignInMode::kAuto,
-      new_accounts_obj);
+      new_accounts_obj, java_rp_icon);
 }
 
 bool AccountSelectionViewAndroid::ShowFailureDialog(
diff --git a/chrome/browser/ui/android/webid/account_selection_view_android.h b/chrome/browser/ui/android/webid/account_selection_view_android.h
index ec9529c..3cb5bd2 100644
--- a/chrome/browser/ui/android/webid/account_selection_view_android.h
+++ b/chrome/browser/ui/android/webid/account_selection_view_android.h
@@ -27,7 +27,7 @@
 
   // AccountSelectionView:
   bool Show(
-      const std::string& rp_for_display,
+      const content::RelyingPartyData& rp_data,
       const std::vector<IdentityProviderDataPtr>& idp_list,
       const std::vector<IdentityRequestAccountPtr>& accounts,
       Account::SignInMode sign_in_mode,
diff --git a/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionBridge.java b/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionBridge.java
index c5ebb38..4894a27f 100644
--- a/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionBridge.java
+++ b/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionBridge.java
@@ -5,6 +5,7 @@
 package org.chromium.chrome.browser.ui.android.webid;
 
 import android.content.res.Resources;
+import android.graphics.Bitmap;
 
 import androidx.annotation.Nullable;
 
@@ -106,6 +107,7 @@
      * @param idpDataList is the list of IDP datas.
      * @param isAutoReauthn represents whether this is an auto re-authn flow.
      * @param newAccounts represents the newly logged in accounts.
+     * @param favicon represents the favicon to be used when multi IDP UI is to be shown.
      * @return whether the invocation is successful. If false is returned, the caller must assume
      *     that onDismiss was called and must return early.
      */
@@ -115,14 +117,16 @@
             Account[] accounts,
             IdentityProviderData[] idpDataList,
             boolean isAutoReauthn,
-            Account[] newAccounts) {
+            Account[] newAccounts,
+            Bitmap favicon) {
         assert accounts != null && accounts.length > 0;
         return mAccountSelectionComponent.showAccounts(
                 rpForDisplay,
                 Arrays.asList(accounts),
                 Arrays.asList(idpDataList),
                 isAutoReauthn,
-                Arrays.asList(newAccounts));
+                Arrays.asList(newAccounts),
+                favicon);
     }
 
     /**
diff --git a/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionButtonModeControllerTest.java b/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionButtonModeControllerTest.java
index 2a2c72e..858258d6 100644
--- a/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionButtonModeControllerTest.java
+++ b/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionButtonModeControllerTest.java
@@ -58,7 +58,8 @@
                     Arrays.asList(mNewUserAccount),
                     Arrays.asList(mIdpData),
                     /* isAutoReauthn= */ false,
-                    /* newAccounts= */ Collections.EMPTY_LIST);
+                    /* newAccounts= */ Collections.EMPTY_LIST,
+                    /* favicon= */ null);
             mMediator.showVerifySheet(mAnaAccount);
 
             // There is no account shown in the verify sheet on active mode.
@@ -82,7 +83,8 @@
                     Arrays.asList(mAnaAccount),
                     Arrays.asList(mIdpData),
                     /* isAutoReauthn= */ true,
-                    /* newAccounts= */ Collections.EMPTY_LIST);
+                    /* newAccounts= */ Collections.EMPTY_LIST,
+                    /* favicon= */ null);
 
             // There is no account shown on the loading dialog in active mode.
             assertEquals(0, mSheetAccountItems.size());
@@ -110,7 +112,8 @@
                 Arrays.asList(mAnaAccount, mBobAccount),
                 Arrays.asList(mIdpData),
                 /* isAutoReauthn= */ false,
-                /* newAccounts= */ Collections.EMPTY_LIST);
+                /* newAccounts= */ Collections.EMPTY_LIST,
+                /* favicon= */ null);
         assertEquals(HeaderType.SIGN_IN, mModel.get(ItemProperties.HEADER).get(TYPE));
 
         // For accounts dialog, we expect header + two accounts.
@@ -126,7 +129,8 @@
                 Arrays.asList(mNewUserAccount),
                 Arrays.asList(mIdpData),
                 /* isAutoReauthn= */ false,
-                /* newAccounts= */ Collections.EMPTY_LIST);
+                /* newAccounts= */ Collections.EMPTY_LIST,
+                /* favicon= */ null);
         mMediator.showRequestPermissionModalSheet(mNewUserAccount);
 
         // For request permission dialog, we expect header + account chip + disclosure text +
@@ -149,7 +153,8 @@
                 Arrays.asList(mAnaAccount),
                 Arrays.asList(mIdpData),
                 /* isAutoReauthn= */ false,
-                /* newAccounts= */ Collections.EMPTY_LIST);
+                /* newAccounts= */ Collections.EMPTY_LIST,
+                /* favicon= */ null);
 
         assertNotNull(mModel.get(ItemProperties.HEADER).get(RP_BRAND_ICON));
     }
@@ -161,7 +166,8 @@
                 Arrays.asList(mAnaAccountWithoutBrandIcons),
                 Arrays.asList(mIdpDataWithoutIcons),
                 /* isAutoReauthn= */ false,
-                /* newAccounts= */ Collections.EMPTY_LIST);
+                /* newAccounts= */ Collections.EMPTY_LIST,
+                /* favicon= */ null);
 
         PropertyModel headerModel = mModel.get(ItemProperties.HEADER);
         // Unlike passive mode, brand icons should not be available because we do not show any
@@ -178,7 +184,8 @@
                 Arrays.asList(),
                 Arrays.asList(mIdpData),
                 /* isAutoReauthn= */ false,
-                mNewAccountsSingleNewAccount);
+                mNewAccountsSingleNewAccount,
+                /* favicon= */ null);
 
         // Request permission modal dialog is NOT skipped for a single newly signed-in new account.
         // Since
@@ -197,7 +204,8 @@
                 Arrays.asList(),
                 Arrays.asList(mIdpData),
                 /* isAutoReauthn= */ false,
-                mNewAccountsSingleNewAccount);
+                mNewAccountsSingleNewAccount,
+                /* favicon= */ null);
 
         // Account chooser dialog is shown for a single newly signed-in new account where request
         // permission is false. Since this is a new account and request permission is false, we need
@@ -215,7 +223,8 @@
                 Arrays.asList(),
                 Arrays.asList(mIdpData),
                 /* isAutoReauthn= */ false,
-                mNewAccountsSingleReturningAccount);
+                mNewAccountsSingleReturningAccount,
+                /* favicon= */ null);
 
         // Account chooser dialog is shown for a single newly signed-in returning account. Although
         // this is a returning account, we cannot skip directly to signing in because we have to
@@ -234,7 +243,8 @@
                 Arrays.asList(),
                 Arrays.asList(mIdpData),
                 /* isAutoReauthn= */ false,
-                mNewAccountsSingleReturningAccount);
+                mNewAccountsSingleReturningAccount,
+                /* favicon= */ null);
         mMediator.showErrorDialog(
                 mTestEtldPlusOne,
                 mTestEtldPlusOne2,
@@ -271,7 +281,8 @@
                 Arrays.asList(),
                 Arrays.asList(mIdpData),
                 /* isAutoReauthn= */ false,
-                mNewAccountsSingleReturningAccount);
+                mNewAccountsSingleReturningAccount,
+                /* favicon= */ null);
         mMediator.showErrorDialog(
                 mTestEtldPlusOne, mTestEtldPlusOne2, mIdpMetadata, RpContext.SIGN_IN, mTokenError);
 
diff --git a/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionButtonModeIntegrationTest.java b/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionButtonModeIntegrationTest.java
index 84851d4..8de427d 100644
--- a/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionButtonModeIntegrationTest.java
+++ b/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionButtonModeIntegrationTest.java
@@ -86,7 +86,8 @@
                             Arrays.asList(mNewBobWithAddAccount),
                             Arrays.asList(mIdpDataWithAddAccount),
                             /* isAutoReauthn= */ false,
-                            /* newAccounts= */ Collections.EMPTY_LIST);
+                            /* newAccounts= */ Collections.EMPTY_LIST,
+                            /* favicon= */ null);
                     mAccountSelection.getMediator().setComponentShowTime(-1000);
                 });
         pollUiThread(() -> getBottomSheetState() == BottomSheetController.SheetState.HALF);
@@ -107,7 +108,8 @@
                                                 mNewBobWithAddAccount, mReturningAnaWithAddAccount),
                                         Arrays.asList(mIdpDataWithAddAccount),
                                         /* isAutoReauthn= */ false,
-                                        mNewAccountsReturningAna);
+                                        mNewAccountsReturningAna,
+                                        /* favicon= */ null);
                                 mAccountSelection.getMediator().setComponentShowTime(-1000);
                                 return null;
                             }
@@ -139,7 +141,8 @@
                             Arrays.asList(mReturningAnaWithAddAccount),
                             Arrays.asList(mIdpDataWithAddAccount),
                             /* isAutoReauthn= */ false,
-                            /* newAccounts= */ Collections.EMPTY_LIST);
+                            /* newAccounts= */ Collections.EMPTY_LIST,
+                            /* favicon= */ null);
                     mAccountSelection.getMediator().setComponentShowTime(-1000);
                 });
         pollUiThread(() -> getBottomSheetState() == BottomSheetController.SheetState.HALF);
@@ -160,7 +163,8 @@
                                                 mNewBobWithAddAccount, mReturningAnaWithAddAccount),
                                         Arrays.asList(mIdpDataWithAddAccount),
                                         /* isAutoReauthn= */ false,
-                                        mNewAccountsNewBob);
+                                        mNewAccountsNewBob,
+                                        /* favicon= */ null);
                                 mAccountSelection.getMediator().setComponentShowTime(-1000);
                                 return null;
                             }
@@ -221,7 +225,8 @@
                             Arrays.asList(mReturningAnaWithAddAccount),
                             Arrays.asList(mIdpDataWithAddAccount),
                             /* isAutoReauthn= */ false,
-                            /* newAccounts= */ Collections.EMPTY_LIST);
+                            /* newAccounts= */ Collections.EMPTY_LIST,
+                            /* favicon= */ null);
                     mAccountSelection.getMediator().setComponentShowTime(-1000);
                 });
         pollUiThread(() -> getBottomSheetState() == BottomSheetController.SheetState.HALF);
@@ -241,7 +246,8 @@
                                         Arrays.asList(account, mReturningAnaWithAddAccount),
                                         Arrays.asList(mIdpDataWithAddAccount),
                                         /* isAutoReauthn= */ false,
-                                        Arrays.asList(account));
+                                        Arrays.asList(account),
+                                        /* favicon= */ null);
                                 mAccountSelection.getMediator().setComponentShowTime(-1000);
                                 return null;
                             }
@@ -281,7 +287,8 @@
                             Arrays.asList(mNewBobWithAddAccount),
                             Arrays.asList(mIdpDataWithAddAccount),
                             /* isAutoReauthn= */ false,
-                            /* newAccounts= */ Collections.EMPTY_LIST);
+                            /* newAccounts= */ Collections.EMPTY_LIST,
+                            /* favicon= */ null);
                     mAccountSelection.getMediator().setComponentShowTime(-1000);
                 });
         pollUiThread(() -> getBottomSheetState() == BottomSheetController.SheetState.HALF);
@@ -325,7 +332,8 @@
                             Arrays.asList(mReturningAnaWithAddAccount),
                             Arrays.asList(mIdpDataWithAddAccount),
                             /* isAutoReauthn= */ false,
-                            /* newAccounts= */ Collections.EMPTY_LIST);
+                            /* newAccounts= */ Collections.EMPTY_LIST,
+                            /* favicon= */ null);
                     mAccountSelection.getMediator().setComponentShowTime(-1000);
                 });
         pollUiThread(() -> getBottomSheetState() == BottomSheetController.SheetState.HALF);
@@ -353,7 +361,8 @@
                             Arrays.asList(mNewBobWithAddAccount),
                             Arrays.asList(mIdpDataWithAddAccount),
                             /* isAutoReauthn= */ false,
-                            /* newAccounts= */ Collections.EMPTY_LIST);
+                            /* newAccounts= */ Collections.EMPTY_LIST,
+                            /* favicon= */ null);
                     mAccountSelection.getMediator().setComponentShowTime(-1000);
                 });
         pollUiThread(() -> getBottomSheetState() == BottomSheetController.SheetState.HALF);
@@ -383,7 +392,8 @@
                             Arrays.asList(mNewBobWithAddAccount, mReturningAnaWithAddAccount),
                             Arrays.asList(mIdpDataWithAddAccount),
                             /* isAutoReauthn= */ false,
-                            /* newAccounts= */ Collections.EMPTY_LIST);
+                            /* newAccounts= */ Collections.EMPTY_LIST,
+                            /* favicon= */ null);
                     mAccountSelection.getMediator().setComponentShowTime(-1000);
                 });
         pollUiThread(() -> getBottomSheetState() == BottomSheetController.SheetState.HALF);
@@ -453,7 +463,8 @@
                             Arrays.asList(mNewBobWithAddAccount),
                             Arrays.asList(mIdpDataWithAddAccount),
                             /* isAutoReauthn= */ false,
-                            /* newAccounts= */ Collections.EMPTY_LIST);
+                            /* newAccounts= */ Collections.EMPTY_LIST,
+                            /* favicon= */ null);
                     mAccountSelection.getMediator().setComponentShowTime(-1000);
                 });
         pollUiThread(() -> getBottomSheetState() == BottomSheetController.SheetState.HALF);
@@ -486,7 +497,8 @@
                             Arrays.asList(mReturningAnaWithAddAccount),
                             Arrays.asList(mIdpDataWithAddAccount),
                             /* isAutoReauthn= */ false,
-                            /* newAccounts= */ Collections.EMPTY_LIST);
+                            /* newAccounts= */ Collections.EMPTY_LIST,
+                            /* favicon= */ null);
                     mAccountSelection.getMediator().setComponentShowTime(-1000);
                 });
         pollUiThread(() -> getBottomSheetState() == BottomSheetController.SheetState.HALF);
@@ -514,7 +526,8 @@
                             Arrays.asList(mNewBobWithAddAccount),
                             Arrays.asList(mIdpDataWithAddAccount),
                             /* isAutoReauthn= */ false,
-                            /* newAccounts= */ Collections.EMPTY_LIST);
+                            /* newAccounts= */ Collections.EMPTY_LIST,
+                            /* favicon= */ null);
                     mAccountSelection.getMediator().setComponentShowTime(-1000);
                 });
         pollUiThread(() -> getBottomSheetState() == BottomSheetController.SheetState.HALF);
@@ -541,7 +554,8 @@
                             Arrays.asList(mNewBobWithAddAccount),
                             Arrays.asList(mIdpDataWithAddAccount),
                             /* isAutoReauthn= */ false,
-                            /* newAccounts= */ Collections.EMPTY_LIST);
+                            /* newAccounts= */ Collections.EMPTY_LIST,
+                            /* favicon= */ null);
                     mAccountSelection.getMediator().setComponentShowTime(-1000);
                 });
         pollUiThread(() -> getBottomSheetState() == BottomSheetController.SheetState.HALF);
@@ -572,7 +586,8 @@
                             Arrays.asList(mNewBobWithAddAccount),
                             Arrays.asList(mIdpDataWithAddAccount),
                             /* isAutoReauthn= */ false,
-                            /* newAccounts= */ Collections.EMPTY_LIST);
+                            /* newAccounts= */ Collections.EMPTY_LIST,
+                            /* favicon= */ null);
                     mAccountSelection.getMediator().setComponentShowTime(-1000);
                 });
         pollUiThread(() -> getBottomSheetState() == BottomSheetController.SheetState.HALF);
@@ -608,7 +623,8 @@
                             Arrays.asList(mReturningAnaWithAddAccount),
                             Arrays.asList(mIdpDataWithAddAccount),
                             /* isAutoReauthn= */ false,
-                            /* newAccounts= */ Collections.EMPTY_LIST);
+                            /* newAccounts= */ Collections.EMPTY_LIST,
+                            /* favicon= */ null);
                     mAccountSelection.getMediator().setComponentShowTime(-1000);
                 });
         pollUiThread(() -> getBottomSheetState() == BottomSheetController.SheetState.HALF);
@@ -649,7 +665,8 @@
                             Arrays.asList(mNewBobWithAddAccount),
                             Arrays.asList(mIdpDataWithAddAccount),
                             /* isAutoReauthn= */ false,
-                            /* newAccounts= */ Collections.EMPTY_LIST);
+                            /* newAccounts= */ Collections.EMPTY_LIST,
+                            /* favicon= */ null);
                     mAccountSelection.getMediator().setComponentShowTime(-1000);
                 });
         pollUiThread(() -> getBottomSheetState() == BottomSheetController.SheetState.HALF);
@@ -697,7 +714,8 @@
                             Arrays.asList(mReturningAnaWithAddAccount),
                             Arrays.asList(mIdpDataWithAddAccount),
                             /* isAutoReauthn= */ false,
-                            /* newAccounts= */ Collections.EMPTY_LIST);
+                            /* newAccounts= */ Collections.EMPTY_LIST,
+                            /* favicon= */ null);
                     mAccountSelection.getMediator().setComponentShowTime(-1000);
                 });
         pollUiThread(() -> getBottomSheetState() == BottomSheetController.SheetState.HALF);
@@ -722,7 +740,8 @@
                             Arrays.asList(mNewBobWithAddAccount),
                             Arrays.asList(mIdpDataWithAddAccount),
                             /* isAutoReauthn= */ false,
-                            /* newAccounts= */ Collections.EMPTY_LIST);
+                            /* newAccounts= */ Collections.EMPTY_LIST,
+                            /* favicon= */ null);
                     mAccountSelection.getMediator().setComponentShowTime(-1000);
                 });
         pollUiThread(() -> getBottomSheetState() == BottomSheetController.SheetState.HALF);
@@ -754,7 +773,8 @@
                             Arrays.asList(mReturningAna, mNewBob),
                             Arrays.asList(mIdpData),
                             /* isAutoReauthn= */ false,
-                            /* newAccounts= */ Collections.EMPTY_LIST);
+                            /* newAccounts= */ Collections.EMPTY_LIST,
+                            /* favicon= */ null);
                 });
         pollUiThread(() -> getBottomSheetState() == BottomSheetController.SheetState.HALF);
 
@@ -782,7 +802,8 @@
                             Arrays.asList(mReturningAnaWithAddAccount),
                             Arrays.asList(mIdpDataWithAddAccount),
                             /* isAutoReauthn= */ false,
-                            /* newAccounts= */ Collections.EMPTY_LIST);
+                            /* newAccounts= */ Collections.EMPTY_LIST,
+                            /* favicon= */ null);
                     mAccountSelection.getMediator().setComponentShowTime(-1000);
                 });
         pollUiThread(() -> getBottomSheetState() == BottomSheetController.SheetState.HALF);
@@ -807,7 +828,8 @@
                             Arrays.asList(mReturningAnaWithAddAccount),
                             Arrays.asList(mIdpDataWithAddAccount),
                             /* isAutoReauthn= */ false,
-                            /* newAccounts= */ Collections.EMPTY_LIST);
+                            /* newAccounts= */ Collections.EMPTY_LIST,
+                            /* favicon= */ null);
                     mAccountSelection.getMediator().setComponentShowTime(-1000);
                 });
         pollUiThread(() -> getBottomSheetState() == BottomSheetController.SheetState.HALF);
@@ -837,7 +859,8 @@
                             Arrays.asList(mReturningAnaWithAddAccount),
                             Arrays.asList(mIdpDataWithAddAccount),
                             /* isAutoReauthn= */ false,
-                            /* newAccounts= */ Collections.EMPTY_LIST);
+                            /* newAccounts= */ Collections.EMPTY_LIST,
+                            /* favicon= */ null);
                     mAccountSelection.getMediator().setComponentShowTime(-1000);
                 });
         pollUiThread(() -> getBottomSheetState() == BottomSheetController.SheetState.HALF);
@@ -867,7 +890,8 @@
                             Arrays.asList(mNewBobWithAddAccount),
                             Arrays.asList(mIdpDataWithAddAccount),
                             /* isAutoReauthn= */ false,
-                            /* newAccounts= */ Collections.EMPTY_LIST);
+                            /* newAccounts= */ Collections.EMPTY_LIST,
+                            /* favicon= */ null);
                     mAccountSelection.getMediator().setComponentShowTime(-1000);
                 });
         pollUiThread(() -> getBottomSheetState() == BottomSheetController.SheetState.HALF);
@@ -916,7 +940,8 @@
                             Arrays.asList(mReturningAnaWithAddAccount),
                             Arrays.asList(mIdpDataWithAddAccount),
                             /* isAutoReauthn= */ false,
-                            /* newAccounts= */ Collections.EMPTY_LIST);
+                            /* newAccounts= */ Collections.EMPTY_LIST,
+                            /* favicon= */ null);
                 });
         pollUiThread(() -> getBottomSheetState() == BottomSheetController.SheetState.HALF);
 
@@ -941,7 +966,8 @@
                             Arrays.asList(mReturningAnaWithAddAccount),
                             Arrays.asList(mIdpDataWithAddAccount),
                             /* isAutoReauthn= */ false,
-                            /* newAccounts= */ Collections.EMPTY_LIST);
+                            /* newAccounts= */ Collections.EMPTY_LIST,
+                            /* favicon= */ null);
                 });
         pollUiThread(() -> getBottomSheetState() == BottomSheetController.SheetState.HALF);
 
@@ -1052,7 +1078,8 @@
                             Arrays.asList(mNewBobWithAddAccount),
                             Arrays.asList(mIdpDataWithAddAccount),
                             /* isAutoReauthn= */ false,
-                            /* newAccounts= */ Collections.EMPTY_LIST);
+                            /* newAccounts= */ Collections.EMPTY_LIST,
+                            /* favicon= */ null);
                     mAccountSelection.getMediator().setComponentShowTime(-1000);
                 });
         pollUiThread(() -> getBottomSheetState() == BottomSheetController.SheetState.HALF);
@@ -1082,7 +1109,8 @@
                             Arrays.asList(mNewBobWithAddAccount),
                             Arrays.asList(mIdpDataWithAddAccount),
                             /* isAutoReauthn= */ false,
-                            /* newAccounts= */ Collections.EMPTY_LIST);
+                            /* newAccounts= */ Collections.EMPTY_LIST,
+                            /* favicon= */ null);
                     mAccountSelection.getMediator().setComponentShowTime(-1000);
                 });
         pollUiThread(() -> getBottomSheetState() == BottomSheetController.SheetState.HALF);
@@ -1112,7 +1140,8 @@
                             Arrays.asList(mNewBobWithAddAccount),
                             Arrays.asList(mIdpDataWithAddAccount),
                             /* isAutoReauthn= */ false,
-                            /* newAccounts= */ Collections.EMPTY_LIST);
+                            /* newAccounts= */ Collections.EMPTY_LIST,
+                            /* favicon= */ null);
                     mAccountSelection.getMediator().setComponentShowTime(-1000);
                 });
         pollUiThread(() -> getBottomSheetState() == BottomSheetController.SheetState.HALF);
@@ -1138,7 +1167,8 @@
                             Arrays.asList(mNewBobWithAddAccount),
                             Arrays.asList(mIdpDataWithAddAccount),
                             /* isAutoReauthn= */ false,
-                            /* newAccounts= */ Collections.EMPTY_LIST);
+                            /* newAccounts= */ Collections.EMPTY_LIST,
+                            /* favicon= */ null);
                     mAccountSelection.getMediator().setComponentShowTime(-1000);
                 });
         pollUiThread(() -> getBottomSheetState() == BottomSheetController.SheetState.HALF);
@@ -1170,7 +1200,8 @@
                             Arrays.asList(mNewBobWithAddAccount),
                             Arrays.asList(mIdpDataWithAddAccount),
                             /* isAutoReauthn= */ false,
-                            /* newAccounts= */ Collections.EMPTY_LIST);
+                            /* newAccounts= */ Collections.EMPTY_LIST,
+                            /* favicon= */ null);
                     mAccountSelection.getMediator().setComponentShowTime(-1000);
                 });
         pollUiThread(() -> getBottomSheetState() == BottomSheetController.SheetState.HALF);
diff --git a/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionControllerTest.java b/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionControllerTest.java
index 4c67e4d5..6664a4d 100644
--- a/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionControllerTest.java
+++ b/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionControllerTest.java
@@ -106,7 +106,8 @@
                 Arrays.asList(mAnaAccount),
                 Arrays.asList(mIdpData),
                 /* isAutoReauthn= */ false,
-                /* newAccounts= */ Collections.EMPTY_LIST);
+                /* newAccounts= */ Collections.EMPTY_LIST,
+                /* favicon= */ null);
 
         PropertyModel headerModel = mModel.get(ItemProperties.HEADER);
         assertEquals(HeaderType.SIGN_IN, headerModel.get(TYPE));
@@ -131,7 +132,8 @@
                 Arrays.asList(mAnaAccount, mBobAccount),
                 Arrays.asList(mIdpData),
                 /* isAutoReauthn= */ false,
-                /* newAccounts= */ Collections.EMPTY_LIST);
+                /* newAccounts= */ Collections.EMPTY_LIST,
+                /* favicon= */ null);
 
         PropertyModel headerModel = mModel.get(ItemProperties.HEADER);
         assertEquals(HeaderType.SIGN_IN, headerModel.get(TYPE));
@@ -165,7 +167,8 @@
                 Arrays.asList(mAnaAccountWithoutBrandIcons),
                 Arrays.asList(mIdpDataWithoutIcons),
                 /* isAutoReauthn= */ false,
-                /* newAccounts= */ Collections.EMPTY_LIST);
+                /* newAccounts= */ Collections.EMPTY_LIST,
+                /* favicon= */ null);
 
         PropertyModel headerModel = mModel.get(ItemProperties.HEADER);
         assertNull(headerModel.get(IDP_BRAND_ICON));
@@ -178,7 +181,8 @@
                 Arrays.asList(mNewUserAccount),
                 Arrays.asList(mIdpData),
                 /* isAutoReauthn= */ false,
-                /* newAccounts= */ Collections.EMPTY_LIST);
+                /* newAccounts= */ Collections.EMPTY_LIST,
+                /* favicon= */ null);
 
         PropertyModel headerModel = mModel.get(ItemProperties.HEADER);
         assertEquals(HeaderType.SIGN_IN, headerModel.get(TYPE));
@@ -191,7 +195,8 @@
                 Arrays.asList(mAnaAccount, mBobAccount),
                 Arrays.asList(mIdpData),
                 /* isAutoReauthn= */ false,
-                /* newAccounts= */ Collections.EMPTY_LIST);
+                /* newAccounts= */ Collections.EMPTY_LIST,
+                /* favicon= */ null);
         assertEquals(3, countAllItems()); // Header + two Accounts
         assertEquals("Incorrect item sheet count", 2, mSheetAccountItems.size());
     }
@@ -203,7 +208,8 @@
                 Collections.singletonList(mAnaAccount),
                 Arrays.asList(mIdpData),
                 /* isAutoReauthn= */ false,
-                /* newAccounts= */ Collections.EMPTY_LIST);
+                /* newAccounts= */ Collections.EMPTY_LIST,
+                /* favicon= */ null);
         assertEquals(3, countAllItems()); // Header + Account + Continue Button
         assertEquals(1, mSheetAccountItems.size());
         assertEquals(
@@ -215,7 +221,8 @@
                 Collections.singletonList(mBobAccount),
                 Arrays.asList(mIdpData),
                 /* isAutoReauthn= */ false,
-                /* newAccounts= */ Collections.EMPTY_LIST);
+                /* newAccounts= */ Collections.EMPTY_LIST,
+                /* favicon= */ null);
         assertEquals(3, countAllItems()); // Header + Account + Continue Button
         assertEquals(1, mSheetAccountItems.size());
         assertEquals(
@@ -230,7 +237,8 @@
                 Arrays.asList(mAnaAccount, mCarlAccount, mBobAccount),
                 Arrays.asList(mIdpData),
                 /* isAutoReauthn= */ false,
-                /* newAccounts= */ Collections.EMPTY_LIST);
+                /* newAccounts= */ Collections.EMPTY_LIST,
+                /* favicon= */ null);
         verify(mMockBottomSheetController, times(1)).requestShowContent(any(), eq(true));
 
         assertFalse(mMediator.wasDismissed());
@@ -244,7 +252,8 @@
                 Arrays.asList(mAnaAccount),
                 Arrays.asList(mIdpData),
                 /* isAutoReauthn= */ false,
-                /* newAccounts= */ Collections.EMPTY_LIST);
+                /* newAccounts= */ Collections.EMPTY_LIST,
+                /* favicon= */ null);
         // Do not let test inputs be ignored.
         mMediator.setComponentShowTime(-1000);
         assertFalse(mMediator.wasDismissed());
@@ -271,7 +280,8 @@
                 Arrays.asList(mAnaAccount, mCarlAccount),
                 Arrays.asList(mIdpData),
                 /* isAutoReauthn= */ false,
-                /* newAccounts= */ Collections.EMPTY_LIST);
+                /* newAccounts= */ Collections.EMPTY_LIST,
+                /* favicon= */ null);
         // Do not let test inputs be ignored.
         mMediator.setComponentShowTime(-1000);
         assertFalse(mMediator.wasDismissed());
@@ -296,7 +306,8 @@
                 Arrays.asList(mAnaAccount),
                 Arrays.asList(mIdpData),
                 /* isAutoReauthn= */ false,
-                /* newAccounts= */ Collections.EMPTY_LIST);
+                /* newAccounts= */ Collections.EMPTY_LIST,
+                /* favicon= */ null);
         pressBack();
         verify(mMockDelegate).onDismissed(IdentityRequestDialogDismissReason.OTHER);
         assertTrue(mMediator.wasDismissed());
@@ -310,7 +321,8 @@
                 Arrays.asList(mAnaAccount, mBobAccount),
                 Arrays.asList(mIdpData),
                 /* isAutoReauthn= */ false,
-                /* newAccounts= */ Collections.EMPTY_LIST);
+                /* newAccounts= */ Collections.EMPTY_LIST,
+                /* favicon= */ null);
         pressBack();
         verify(mMockDelegate).onDismissed(IdentityRequestDialogDismissReason.OTHER);
         assertTrue(mMediator.wasDismissed());
@@ -324,7 +336,8 @@
                 Arrays.asList(mAnaAccount, mBobAccount),
                 Arrays.asList(mIdpData),
                 /* isAutoReauthn= */ false,
-                /* newAccounts= */ Collections.EMPTY_LIST);
+                /* newAccounts= */ Collections.EMPTY_LIST,
+                /* favicon= */ null);
         mMediator.onAccountSelected(new ButtonData(mAnaAccount, /* idpMetadata= */ null));
         verify(mMockDelegate).onAccountSelected(mAnaAccount);
         assertFalse(mMediator.wasDismissed());
@@ -340,7 +353,8 @@
                 Arrays.asList(mAnaAccount, mNewUserAccount),
                 Arrays.asList(mIdpData),
                 /* isAutoReauthn= */ false,
-                /* newAccounts= */ Collections.EMPTY_LIST);
+                /* newAccounts= */ Collections.EMPTY_LIST,
+                /* favicon= */ null);
         mMediator.onAccountSelected(new ButtonData(mNewUserAccount, /* idpMetadata= */ null));
 
         assertFalse(mMediator.wasDismissed());
@@ -357,7 +371,8 @@
                 Arrays.asList(mAnaAccount, mNewUserAccount),
                 Arrays.asList(mIdpData),
                 /* isAutoReauthn= */ false,
-                /* newAccounts= */ Collections.EMPTY_LIST);
+                /* newAccounts= */ Collections.EMPTY_LIST,
+                /* favicon= */ null);
         mMediator.onAccountSelected(new ButtonData(mNewUserAccount, /* idpMetadata= */ null));
 
         pressBack();
@@ -379,7 +394,8 @@
                 Arrays.asList(mAnaAccount, mNewUserAccount),
                 Arrays.asList(mIdpData),
                 /* isAutoReauthn= */ false,
-                /* newAccounts= */ Collections.EMPTY_LIST);
+                /* newAccounts= */ Collections.EMPTY_LIST,
+                /* favicon= */ null);
         assertEquals(2, mSheetAccountItems.size());
         mMediator.onAccountSelected(new ButtonData(mAnaAccount, /* idpMetadata= */ null));
 
@@ -395,7 +411,8 @@
                 Arrays.asList(mAnaAccount),
                 Arrays.asList(mIdpData),
                 /* isAutoReauthn= */ true,
-                /* newAccounts= */ Collections.EMPTY_LIST);
+                /* newAccounts= */ Collections.EMPTY_LIST,
+                /* favicon= */ null);
         // Auto reauthenticates if no action is taken.
         ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
         verify(mMockDelegate).onAccountSelected(mAnaAccount);
@@ -412,7 +429,8 @@
                 Arrays.asList(mAnaAccount),
                 Arrays.asList(mIdpData),
                 /* isAutoReauthn= */ true,
-                /* newAccounts= */ Collections.EMPTY_LIST);
+                /* newAccounts= */ Collections.EMPTY_LIST,
+                /* favicon= */ null);
         // Auto reauthenticates even if dismissed.
         pressBack();
         verify(mMockDelegate).onDismissed(IdentityRequestDialogDismissReason.OTHER);
@@ -433,7 +451,8 @@
                 Arrays.asList(mNewUserAccount),
                 Arrays.asList(mIdpData),
                 /* isAutoReauthn= */ false,
-                /* newAccounts= */ Collections.EMPTY_LIST);
+                /* newAccounts= */ Collections.EMPTY_LIST,
+                /* favicon= */ null);
         // For new user we expect header + account + consent text + continue btn
         assertEquals(4, countAllItems());
         assertEquals("Incorrect item sheet count", 1, mSheetAccountItems.size());
@@ -465,7 +484,8 @@
                 Arrays.asList(mNewUserAccount),
                 Arrays.asList(mIdpData),
                 /* isAutoReauthn= */ false,
-                /* newAccounts= */ Collections.EMPTY_LIST);
+                /* newAccounts= */ Collections.EMPTY_LIST,
+                /* favicon= */ null);
         // Because disclosureFields are empty, we expect header + account + continue btn
         assertEquals(3, countAllItems());
         assertEquals("Incorrect item sheet count", 1, mSheetAccountItems.size());
@@ -481,7 +501,8 @@
                 Arrays.asList(mNewUserAccount, mBobAccount),
                 Arrays.asList(mIdpData),
                 /* isAutoReauthn= */ false,
-                /* newAccounts= */ Collections.EMPTY_LIST);
+                /* newAccounts= */ Collections.EMPTY_LIST,
+                /* favicon= */ null);
         mMediator.onAccountSelected(new ButtonData(mNewUserAccount, /* idpMetadata= */ null));
         verify(mMockDelegate).onAccountSelected(mNewUserAccount);
         assertFalse(mMediator.wasDismissed());
@@ -532,7 +553,8 @@
                 Arrays.asList(mAnaAccount),
                 Arrays.asList(mIdpData),
                 /* isAutoReauthn= */ false,
-                /* newAccounts= */ Collections.EMPTY_LIST);
+                /* newAccounts= */ Collections.EMPTY_LIST,
+                /* favicon= */ null);
         KeyboardVisibilityListener listener = mMediator.getKeyboardEventListener();
         listener.keyboardVisibilityChanged(true);
         verify(mMockBottomSheetController).hideContent(mBottomSheetContent, true);
@@ -550,7 +572,8 @@
                 Arrays.asList(mAnaAccount),
                 Arrays.asList(mIdpData),
                 /* isAutoReauthn= */ false,
-                /* newAccounts= */ Collections.EMPTY_LIST);
+                /* newAccounts= */ Collections.EMPTY_LIST,
+                /* favicon= */ null);
         mMediator.getTabObserver().onInteractabilityChanged(mTab, false);
         verify(mMockBottomSheetController).hideContent(mBottomSheetContent, false);
         mMediator.getTabObserver().onInteractabilityChanged(mTab, true);
@@ -566,7 +589,8 @@
                 Arrays.asList(mAnaAccount),
                 Arrays.asList(mIdpData),
                 /* isAutoReauthn= */ false,
-                /* newAccounts= */ Collections.EMPTY_LIST);
+                /* newAccounts= */ Collections.EMPTY_LIST,
+                /* favicon= */ null);
         // We pass null as |mMediatior| does not really care about where we navigate to.
         mMediator.getTabObserver().onDidStartNavigationInPrimaryMainFrame(mTab, null);
         assertTrue(mMediator.wasDismissed());
@@ -581,7 +605,8 @@
                 Arrays.asList(mAnaAccount),
                 Arrays.asList(mIdpData),
                 /* isAutoReauthn= */ false,
-                /* newAccounts= */ Collections.EMPTY_LIST);
+                /* newAccounts= */ Collections.EMPTY_LIST,
+                /* favicon= */ null);
         KeyboardVisibilityListener listener = mMediator.getKeyboardEventListener();
         listener.keyboardVisibilityChanged(true);
         verify(mMockBottomSheetController).hideContent(mBottomSheetContent, true);
@@ -604,7 +629,8 @@
                 Arrays.asList(mAnaAccount),
                 Arrays.asList(mIdpData),
                 /* isAutoReauthn= */ false,
-                /* newAccounts= */ Collections.EMPTY_LIST);
+                /* newAccounts= */ Collections.EMPTY_LIST,
+                /* favicon= */ null);
         verify(mMockBottomSheetController, never()).requestShowContent(any(), anyBoolean());
         mMediator.getTabObserver().onInteractabilityChanged(mTab, true);
         verify(mMockBottomSheetController, times(1)).requestShowContent(mBottomSheetContent, true);
@@ -618,7 +644,8 @@
                 Arrays.asList(mNewUserAccount),
                 Arrays.asList(mIdpData),
                 /* isAutoReauthn= */ false,
-                /* newAccounts= */ Collections.EMPTY_LIST);
+                /* newAccounts= */ Collections.EMPTY_LIST,
+                /* favicon= */ null);
 
         assertNotNull(mModel.get(ItemProperties.HEADER).get(SET_FOCUS_VIEW_CALLBACK));
         assertNotNull(
@@ -638,7 +665,8 @@
                 mNewAccountsMultipleAccounts,
                 Arrays.asList(mIdpData),
                 /* isAutoReauthn= */ false,
-                mNewAccountsMultipleAccounts);
+                mNewAccountsMultipleAccounts,
+                /* favicon= */ null);
 
         // Account chooser is shown for multiple newly signed-in accounts.
         assertEquals(HeaderType.SIGN_IN, mModel.get(ItemProperties.HEADER).get(TYPE));
@@ -653,7 +681,8 @@
                         mFilteredOutAccountWithUseDifferentAccount),
                 Arrays.asList(mIdpDataWithUseDifferentAccount),
                 /* isAutoReauthn= */ false,
-                /* newAccounts= */ Collections.EMPTY_LIST);
+                /* newAccounts= */ Collections.EMPTY_LIST,
+                /* favicon= */ null);
 
         // Account chooser is shown.
         assertEquals(HeaderType.SIGN_IN, mModel.get(ItemProperties.HEADER).get(TYPE));
@@ -722,7 +751,8 @@
                 Arrays.asList(mFilteredOutAccountWithUseDifferentAccount),
                 Arrays.asList(mIdpDataWithUseDifferentAccount),
                 /* isAutoReauthn= */ false,
-                Arrays.asList(mFilteredOutAccountWithUseDifferentAccount));
+                Arrays.asList(mFilteredOutAccountWithUseDifferentAccount),
+                /* favicon= */ null);
         // Account chooser is shown.
         assertEquals(HeaderType.SIGN_IN, mModel.get(ItemProperties.HEADER).get(TYPE));
         int expectedCount = mRpMode == RpMode.PASSIVE ? 3 : 2;
@@ -779,7 +809,8 @@
                 Arrays.asList(mNewUserAccount, mAnaAccountWithUseDifferentAccount),
                 Arrays.asList(mIdpData, mIdpDataWithUseDifferentAccount),
                 /* isAutoReauthn= */ false,
-                /* newAccounts= */ Collections.EMPTY_LIST);
+                /* newAccounts= */ Collections.EMPTY_LIST,
+                /* favicon= */ null);
 
         PropertyModel headerModel = mModel.get(ItemProperties.HEADER);
         assertEquals(HeaderType.SIGN_IN, headerModel.get(TYPE));
diff --git a/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionCoordinator.java b/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionCoordinator.java
index a044d6f4..92c52fb 100644
--- a/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionCoordinator.java
+++ b/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionCoordinator.java
@@ -10,6 +10,7 @@
 import android.app.Activity;
 import android.content.Context;
 import android.content.Intent;
+import android.graphics.Bitmap;
 import android.net.Uri;
 import android.provider.Browser;
 import android.view.LayoutInflater;
@@ -197,9 +198,10 @@
             List<Account> accounts,
             List<IdentityProviderData> idpDataList,
             boolean isAutoReauthn,
-            List<Account> newAccounts) {
+            List<Account> newAccounts,
+            Bitmap favicon) {
         return mMediator.showAccounts(
-                rpEtldPlusOne, accounts, idpDataList, isAutoReauthn, newAccounts);
+                rpEtldPlusOne, accounts, idpDataList, isAutoReauthn, newAccounts, favicon);
     }
 
     @Override
diff --git a/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionIntegrationTest.java b/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionIntegrationTest.java
index 4359c06..a02336b 100644
--- a/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionIntegrationTest.java
+++ b/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionIntegrationTest.java
@@ -105,7 +105,8 @@
                             Arrays.asList(mReturningAna, mNewBob),
                             Arrays.asList(mIdpData),
                             /* isAutoReauthn= */ false,
-                            /* newAccounts= */ Collections.EMPTY_LIST);
+                            /* newAccounts= */ Collections.EMPTY_LIST,
+                            /* favicon= */ null);
                 });
         pollUiThread(() -> getBottomSheetState() == mExpectedSheetState);
 
@@ -125,7 +126,8 @@
                             Arrays.asList(mReturningAna, mNewBob),
                             Arrays.asList(mIdpData),
                             /* isAutoReauthn= */ false,
-                            /* newAccounts= */ Collections.EMPTY_LIST);
+                            /* newAccounts= */ Collections.EMPTY_LIST,
+                            /* favicon= */ null);
                 });
         pollUiThread(() -> getBottomSheetState() == mExpectedSheetState);
         BottomSheetTestSupport sheetSupport = new BottomSheetTestSupport(mBottomSheetController);
@@ -145,7 +147,8 @@
                             Arrays.asList(mNewBob),
                             Arrays.asList(mIdpData),
                             /* isAutoReauthn= */ false,
-                            /* newAccounts= */ Collections.EMPTY_LIST);
+                            /* newAccounts= */ Collections.EMPTY_LIST,
+                            /* favicon= */ null);
                 });
         pollUiThread(() -> getBottomSheetState() == mExpectedSheetState);
 
@@ -218,7 +221,8 @@
                             Arrays.asList(mReturningAna, mNewBob),
                             Arrays.asList(mIdpData),
                             /* isAutoReauthn= */ false,
-                            /* newAccounts= */ Collections.EMPTY_LIST);
+                            /* newAccounts= */ Collections.EMPTY_LIST,
+                            /* favicon= */ null);
                 });
         waitForEvent(mMockBridge).onDismissed(IdentityRequestDialogDismissReason.OTHER);
         verify(mMockBridge, never()).onAccountSelected(any());
@@ -364,7 +368,8 @@
                             Arrays.asList(mNewBobWithAddAccount, mReturningAnaWithAddAccount),
                             Arrays.asList(mIdpDataWithAddAccount),
                             /* isAutoReauthn= */ false,
-                            /* newAccounts= */ Collections.EMPTY_LIST);
+                            /* newAccounts= */ Collections.EMPTY_LIST,
+                            /* favicon= */ null);
                     mAccountSelection.getMediator().setComponentShowTime(-1000);
                 });
         pollUiThread(() -> getBottomSheetState() == mExpectedSheetState);
diff --git a/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionMediator.java b/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionMediator.java
index 9482f4e..108650c 100644
--- a/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionMediator.java
+++ b/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionMediator.java
@@ -714,7 +714,9 @@
             List<Account> accounts,
             List<IdentityProviderData> idpDataList,
             boolean isAutoReauthn,
-            List<Account> newAccounts) {
+            List<Account> newAccounts,
+            Bitmap favicon) {
+        // TOOD(crbug.com/392142580): use the favicon where needed.
         mRpForDisplay = rpForDisplay;
         mAccounts = accounts;
         mIdpDataListForShowAccounts = idpDataList;
diff --git a/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionWidgetModeControllerTest.java b/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionWidgetModeControllerTest.java
index 274f8e8..43ffe13 100644
--- a/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionWidgetModeControllerTest.java
+++ b/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionWidgetModeControllerTest.java
@@ -58,7 +58,8 @@
                     Arrays.asList(mNewUserAccount),
                     Arrays.asList(mIdpData),
                     /* isAutoReauthn= */ false,
-                    /* newAccounts= */ Collections.EMPTY_LIST);
+                    /* newAccounts= */ Collections.EMPTY_LIST,
+                    /* favicon= */ null);
             mMediator.showVerifySheet(mAnaAccount);
 
             assertEquals(1, mSheetAccountItems.size());
@@ -80,7 +81,8 @@
                     Arrays.asList(mAnaAccount),
                     Arrays.asList(mIdpData),
                     /* isAutoReauthn= */ true,
-                    /* newAccounts= */ Collections.EMPTY_LIST);
+                    /* newAccounts= */ Collections.EMPTY_LIST,
+                    /* favicon= */ null);
 
             assertEquals(1, mSheetAccountItems.size());
             assertEquals(
@@ -97,7 +99,8 @@
                 Arrays.asList(mAnaAccountWithoutBrandIcons),
                 Arrays.asList(mIdpDataWithoutIcons),
                 /* isAutoReauthn= */ false,
-                /* newAccounts= */ Collections.EMPTY_LIST);
+                /* newAccounts= */ Collections.EMPTY_LIST,
+                /* favicon= */ null);
 
         assertNull(mModel.get(ItemProperties.HEADER).get(RP_BRAND_ICON));
         PropertyModel headerModel = mModel.get(ItemProperties.HEADER);
diff --git a/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionWidgetModeIntegrationTest.java b/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionWidgetModeIntegrationTest.java
index d689828..f72bf1c7 100644
--- a/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionWidgetModeIntegrationTest.java
+++ b/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionWidgetModeIntegrationTest.java
@@ -63,7 +63,8 @@
                             Arrays.asList(mNewBobWithAddAccount),
                             Arrays.asList(mIdpDataWithAddAccount),
                             /* isAutoReauthn= */ false,
-                            /* newAccounts= */ Collections.EMPTY_LIST);
+                            /* newAccounts= */ Collections.EMPTY_LIST,
+                            /* favicon= */ null);
                     mAccountSelection.getMediator().setComponentShowTime(-1000);
                 });
         pollUiThread(() -> getBottomSheetState() == BottomSheetController.SheetState.FULL);
diff --git a/chrome/browser/ui/android/webid/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionComponent.java b/chrome/browser/ui/android/webid/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionComponent.java
index 45dc1047..35c08bd 100644
--- a/chrome/browser/ui/android/webid/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionComponent.java
+++ b/chrome/browser/ui/android/webid/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionComponent.java
@@ -4,6 +4,8 @@
 
 package org.chromium.chrome.browser.ui.android.webid;
 
+import android.graphics.Bitmap;
+
 import org.chromium.blink.mojom.RpContext;
 import org.chromium.chrome.browser.ui.android.webid.data.Account;
 import org.chromium.chrome.browser.ui.android.webid.data.IdentityCredentialTokenError;
@@ -68,6 +70,7 @@
      * @param idpDataList The list with information about the identity providers.
      * @param isAutoReauthn A {@link boolean} that represents whether this is an auto re-authn flow.
      * @param newAccounts The newly logged in accounts.
+     * @param favicon represents the favicon to be used when multi IDP UI is to be shown.
      * @return whether the invocation is successful. If false is returned, the caller must assume
      *     that onDismiss was called and must return early.
      */
@@ -76,7 +79,8 @@
             List<Account> accounts,
             List<IdentityProviderData> idpDataList,
             boolean isAutoReauthn,
-            List<Account> newAccounts);
+            List<Account> newAccounts,
+            Bitmap favicon);
 
     /**
      * Displays a dialog telling the user that they can sign in to an IDP for the purpose of
diff --git a/chrome/browser/ui/ash/projector/projector_client_impl_unittest.cc b/chrome/browser/ui/ash/projector/projector_client_impl_unittest.cc
index 5f2ef6e..333d1c9 100644
--- a/chrome/browser/ui/ash/projector/projector_client_impl_unittest.cc
+++ b/chrome/browser/ui/ash/projector/projector_client_impl_unittest.cc
@@ -413,18 +413,25 @@
 
 #endif  // BUILDFLAG(GOOGLE_CHROME_BRANDING)
 
+// TODO: dorianbrandon - Remove finch flag from disabled list. The finch
+// flag currently sets the experiment to true for all languages. This isn't a
+// problem since all ChromeOS languages are covered but it affects the
+// structure of the language disabled test.
 INSTANTIATE_TEST_SUITE_P(
     ProjectorClientTestScenarios,
     ProjectorClientImplUnitTest,
     ::testing::Values(
-        ProjectorClientTestScenario({features::kOnDeviceSpeechRecognition}, {}),
+        ProjectorClientTestScenario(
+            {features::kOnDeviceSpeechRecognition},
+            {features::kInternalServerSideSpeechRecognitionUSMModelFinch}),
         ProjectorClientTestScenario(
             {features::kOnDeviceSpeechRecognition,
              features::kForceEnableServerSideSpeechRecognition},
-            {}),
+            {features::kInternalServerSideSpeechRecognitionUSMModelFinch}),
         ProjectorClientTestScenario(
             {features::kInternalServerSideSpeechRecognition,
              features::kOnDeviceSpeechRecognition},
-            {features::kForceEnableServerSideSpeechRecognition})));
+            {features::kForceEnableServerSideSpeechRecognition,
+             features::kInternalServerSideSpeechRecognitionUSMModelFinch})));
 
 }  // namespace ash
diff --git a/chrome/browser/ui/ash/projector/projector_soda_installation_controller_unittest.cc b/chrome/browser/ui/ash/projector/projector_soda_installation_controller_unittest.cc
index 4dd71d7b..9133e84 100644
--- a/chrome/browser/ui/ash/projector/projector_soda_installation_controller_unittest.cc
+++ b/chrome/browser/ui/ash/projector/projector_soda_installation_controller_unittest.cc
@@ -73,10 +73,12 @@
 class ProjectorSodaInstallationControllerTest : public ChromeAshTestBase {
  public:
   ProjectorSodaInstallationControllerTest() {
+    // TODO: dorianbrandon - Remove finch flag from disabled list.
     scoped_feature_list_.InitWithFeatures(
         {features::kOnDeviceSpeechRecognition},
         {features::kInternalServerSideSpeechRecognition,
-         features::kForceEnableServerSideSpeechRecognition});
+         features::kForceEnableServerSideSpeechRecognition,
+         features::kInternalServerSideSpeechRecognitionUSMModelFinch});
   }
   ProjectorSodaInstallationControllerTest(
       const ProjectorSodaInstallationControllerTest&) = delete;
diff --git a/chrome/browser/ui/browser_actions.cc b/chrome/browser/ui/browser_actions.cc
index 7ae1dac..18577b7 100644
--- a/chrome/browser/ui/browser_actions.cc
+++ b/chrome/browser/ui/browser_actions.cc
@@ -367,7 +367,7 @@
                              },
                              base::Unretained(browser)),
                          kActionTabSearch, IDS_TAB_SEARCH_MENU,
-                         IDS_TAB_SEARCH_MENU, kSearchMenuIcon)
+                         IDS_TAB_SEARCH_MENU, vector_icons::kExpandMoreIcon)
             .Build());
   }
 
diff --git a/chrome/browser/ui/browser_command_controller.cc b/chrome/browser/ui/browser_command_controller.cc
index 31c89d3..3bce394 100644
--- a/chrome/browser/ui/browser_command_controller.cc
+++ b/chrome/browser/ui/browser_command_controller.cc
@@ -142,6 +142,8 @@
 
 #if BUILDFLAG(ENABLE_GLIC)
 #include "chrome/browser/glic/glic_enabling.h"
+#include "chrome/browser/glic/glic_enums.h"
+#include "chrome/browser/glic/glic_keyed_service_factory.h"
 #include "chrome/browser/glic/glic_pref_names.h"
 #endif
 
@@ -1166,6 +1168,17 @@
           !profile_prefs->GetBoolean(glic::prefs::kGlicPinnedToTabstrip));
       break;
     }
+    case IDC_OPEN_GLIC: {
+      auto* service =
+          glic::GlicKeyedServiceFactory::GetGlicKeyedService(profile());
+      if (service) {
+        glic::GlicKeyedServiceFactory::GetGlicKeyedService(profile())->ToggleUI(
+            browser_,
+            /*prevent_close=*/true,
+            glic::mojom::InvocationSource::kThreeDotsMenu);
+      }
+      break;
+    }
 #endif
     default:
       LOG(WARNING) << "Received Unimplemented Command: " << id;
@@ -1503,6 +1516,8 @@
   // Glic commands.
   command_updater_.UpdateCommandEnabled(
       IDC_GLIC_TOGGLE_PIN, glic::GlicEnabling::IsProfileEligible(profile()));
+  command_updater_.UpdateCommandEnabled(
+      IDC_OPEN_GLIC, glic::GlicEnabling::IsProfileEligible(profile()));
 #endif
 
   // Initialize other commands whose state changes based on various conditions.
diff --git a/chrome/browser/ui/browser_command_controller_browsertest.cc b/chrome/browser/ui/browser_command_controller_browsertest.cc
index 72a728f..4236618 100644
--- a/chrome/browser/ui/browser_command_controller_browsertest.cc
+++ b/chrome/browser/ui/browser_command_controller_browsertest.cc
@@ -64,7 +64,10 @@
 #endif  // BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_WIN)
 
 #if BUILDFLAG(ENABLE_GLIC)
+#include "chrome/browser/glic/glic_keyed_service_factory.h"
 #include "chrome/browser/glic/glic_pref_names.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/pref_names.h"
 #include "components/prefs/pref_service.h"
 #endif
 
@@ -717,6 +720,26 @@
   EXPECT_FALSE(
       chrome::IsCommandEnabled(guest_browser.get(), IDC_GLIC_TOGGLE_PIN));
 }
+
+IN_PROC_BROWSER_TEST_F(BrowserCommandControllerBrowserTestGlic,
+                       ThreeDotMenuItemEnabledInRegularProfile) {
+  ASSERT_TRUE(browser()->profile()->IsRegularProfile());
+  EXPECT_TRUE(chrome::IsCommandEnabled(browser(), IDC_OPEN_GLIC));
+}
+
+IN_PROC_BROWSER_TEST_F(BrowserCommandControllerBrowserTestGlic,
+                       ExecuteGlicThreeDotMenuItem) {
+  // Bypass glic eligibility check.
+  base::CommandLine::ForCurrentProcess()->AppendSwitch(::switches::kGlicDev);
+  // Bypass fre.
+  PrefService* profile_prefs = browser()->profile()->GetPrefs();
+  profile_prefs->SetBoolean(glic::prefs::kGlicCompletedFre, true);
+
+  EXPECT_TRUE(chrome::ExecuteCommand(browser(), IDC_OPEN_GLIC));
+  ASSERT_TRUE(
+      glic::GlicKeyedServiceFactory::GetGlicKeyedService(browser()->profile())
+          ->IsWindowShowing());
+}
 #endif
 
 }  // namespace chrome
diff --git a/chrome/browser/ui/hats/survey_config.cc b/chrome/browser/ui/hats/survey_config.cc
index 044d43e..88c09c3b 100644
--- a/chrome/browser/ui/hats/survey_config.cc
+++ b/chrome/browser/ui/hats/survey_config.cc
@@ -490,8 +490,12 @@
       kHatsSurveyTriggerPerformanceControlsPPM,
       /*presupplied_trigger_id=*/std::nullopt,
       std::vector<std::string>{"Memory Saver Mode Enabled",
-                               "Battery Saver Mode Enabled"},
-      std::vector<std::string>{},
+                               "Battery Saver Mode Enabled",
+                               "Selected for Uniform Sample"},
+      std::vector<std::string>{
+          "Channel",
+          // Note memory is reported as a range, eg. "Windows, 4 to 8 GB".
+          "Performance Characteristics (OS and Total Memory)"},
       // TODO(crbug.com/404915122): Enable UMA logging.
       /*log_responses_to_uma=*/false,
       /*log_responses_to_ukm=*/false);
diff --git a/chrome/browser/ui/lens/lens_overlay_untrusted_ui.cc b/chrome/browser/ui/lens/lens_overlay_untrusted_ui.cc
index 416b484..47f72b3a 100644
--- a/chrome/browser/ui/lens/lens_overlay_untrusted_ui.cc
+++ b/chrome/browser/ui/lens/lens_overlay_untrusted_ui.cc
@@ -106,10 +106,14 @@
       IDS_LENS_OVERLAY_TARGET_LANGUAGE_ACCESSIBILITY_LABEL);
   html_source->AddLocalizedString(
       "searchboxGhostLoaderHintTextPrimaryDefault",
-      IDS_GOOGLE_SEARCH_BOX_CONTEXTUAL_LOADING_HINT_PRIMARY);
+      lens::features::ShouldUseAltLoadingHintWeb()
+          ? IDS_GOOGLE_SEARCH_BOX_CONTEXTUAL_LOADING_HINT_PRIMARY_ALT
+          : IDS_GOOGLE_SEARCH_BOX_CONTEXTUAL_LOADING_HINT_PRIMARY);
   html_source->AddLocalizedString(
       "searchboxGhostLoaderHintTextPrimaryPdf",
-      IDS_GOOGLE_SEARCH_BOX_CONTEXTUAL_LOADING_HINT_PRIMARY_PDF);
+      lens::features::ShouldUseAltLoadingHintPdf()
+          ? IDS_GOOGLE_SEARCH_BOX_CONTEXTUAL_LOADING_HINT_PRIMARY_ALT
+          : IDS_GOOGLE_SEARCH_BOX_CONTEXTUAL_LOADING_HINT_PRIMARY_PDF);
   html_source->AddLocalizedString(
       "searchboxGhostLoaderHintTextSecondary",
       IDS_GOOGLE_SEARCH_BOX_CONTEXTUAL_LOADING_HINT_SECONDARY);
diff --git a/chrome/browser/ui/lens/lens_side_panel_untrusted_ui.cc b/chrome/browser/ui/lens/lens_side_panel_untrusted_ui.cc
index 2bd361b..1f85a94 100644
--- a/chrome/browser/ui/lens/lens_side_panel_untrusted_ui.cc
+++ b/chrome/browser/ui/lens/lens_side_panel_untrusted_ui.cc
@@ -51,10 +51,14 @@
       IDS_SIDE_PANEL_COMPANION_ERROR_PAGE_SECOND_LINE);
   html_source->AddLocalizedString(
       "searchboxGhostLoaderHintTextPrimaryDefault",
-      IDS_GOOGLE_SEARCH_BOX_CONTEXTUAL_LOADING_HINT_PRIMARY);
+      lens::features::ShouldUseAltLoadingHintWeb()
+          ? IDS_GOOGLE_SEARCH_BOX_CONTEXTUAL_LOADING_HINT_PRIMARY_ALT
+          : IDS_GOOGLE_SEARCH_BOX_CONTEXTUAL_LOADING_HINT_PRIMARY);
   html_source->AddLocalizedString(
       "searchboxGhostLoaderHintTextPrimaryPdf",
-      IDS_GOOGLE_SEARCH_BOX_CONTEXTUAL_LOADING_HINT_PRIMARY_PDF);
+      lens::features::ShouldUseAltLoadingHintPdf()
+          ? IDS_GOOGLE_SEARCH_BOX_CONTEXTUAL_LOADING_HINT_PRIMARY_ALT
+          : IDS_GOOGLE_SEARCH_BOX_CONTEXTUAL_LOADING_HINT_PRIMARY_PDF);
   html_source->AddLocalizedString(
       "searchboxGhostLoaderHintTextSecondary",
       IDS_GOOGLE_SEARCH_BOX_CONTEXTUAL_LOADING_HINT_SECONDARY);
diff --git a/chrome/browser/ui/performance_controls/performance_controls_hats_service.cc b/chrome/browser/ui/performance_controls/performance_controls_hats_service.cc
index 03bcaa7..6537b8a 100644
--- a/chrome/browser/ui/performance_controls/performance_controls_hats_service.cc
+++ b/chrome/browser/ui/performance_controls/performance_controls_hats_service.cc
@@ -12,6 +12,7 @@
 #include "base/functional/callback_helpers.h"
 #include "base/rand_util.h"
 #include "base/strings/string_number_conversions.h"
+#include "base/system/sys_info.h"
 #include "build/build_config.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/performance_manager/public/user_tuning/battery_saver_mode_manager.h"
@@ -19,11 +20,21 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/hats/hats_service.h"
 #include "chrome/browser/ui/hats/hats_service_factory.h"
+#include "chrome/common/channel_info.h"
 #include "components/performance_manager/public/features.h"
 
 using performance_manager::features::kPerformanceControlsPPMSurvey;
 using performance_manager::features::kPerformanceControlsPPMSurveyMaxDelay;
 using performance_manager::features::kPerformanceControlsPPMSurveyMinDelay;
+using performance_manager::features::
+    kPerformanceControlsPPMSurveySegmentMaxMemoryGB1;
+using performance_manager::features::
+    kPerformanceControlsPPMSurveySegmentMaxMemoryGB2;
+using performance_manager::features::kPerformanceControlsPPMSurveySegmentName1;
+using performance_manager::features::kPerformanceControlsPPMSurveySegmentName2;
+using performance_manager::features::kPerformanceControlsPPMSurveySegmentName3;
+using performance_manager::features::
+    kPerformanceControlsPPMSurveyUniformSampleValue;
 
 PerformanceControlsHatsService::PerformanceControlsHatsService(Profile* profile)
     : profile_(profile),
@@ -80,13 +91,19 @@
 
   auto launch_survey_if_enabled =
       [hats_service, battery_saver_mode, memory_saver_mode](
-          const base::Feature& feature, const std::string& trigger) {
+          const base::Feature& feature, const std::string& trigger,
+          const SurveyBitsData& extra_data = {},
+          const SurveyStringData& string_data = {}) {
         if (base::FeatureList::IsEnabled(feature)) {
-          hats_service->LaunchSurvey(
-              trigger, base::DoNothing(), base::DoNothing(),
-              {{"Memory Saver Mode Enabled", memory_saver_mode},
-               {"Battery Saver Mode Enabled", battery_saver_mode}},
-              {});
+          SurveyBitsData bits_data = {
+              {kMemorySaverPSDName, memory_saver_mode},
+              {kBatterySaverPSDName, battery_saver_mode}};
+          for (const auto& [key, value] : extra_data) {
+            auto [_, inserted] = bits_data.try_emplace(key, value);
+            CHECK(inserted);
+          }
+          hats_service->LaunchSurvey(trigger, base::DoNothing(),
+                                     base::DoNothing(), bits_data, string_data);
         }
       };
 
@@ -96,9 +113,16 @@
       kHatsSurveyTriggerPerformanceControlsPerformance);
 
   // Survey to correlate UMA metrics with Poor Performance Moments.
-  if (MayLaunchPPMSurvey()) {
-    launch_survey_if_enabled(kPerformanceControlsPPMSurvey,
-                             kHatsSurveyTriggerPerformanceControlsPPM);
+  if (auto ppm_segment_name = GetPPMSurveySegmentName();
+      !ppm_segment_name.empty() && MayLaunchPPMSurvey()) {
+    const std::string channel =
+        chrome::GetChannelName(chrome::WithExtendedStable(false));
+    launch_survey_if_enabled(
+        kPerformanceControlsPPMSurvey, kHatsSurveyTriggerPerformanceControlsPPM,
+        {{kUniformSamplePSDName,
+          kPerformanceControlsPPMSurveyUniformSampleValue.Get()}},
+        {{kPerformanceSegmentPSDName, ppm_segment_name},
+         {kChannelPSDName, channel.empty() ? "stable" : channel}});
   }
 
 #if BUILDFLAG(IS_CHROMEOS)
@@ -168,3 +192,19 @@
   }
   return true;
 }
+
+std::string PerformanceControlsHatsService::GetPPMSurveySegmentName() {
+  uint64_t system_ram = memory_mb_for_testing_.value_or(
+      base::SysInfo::AmountOfPhysicalMemoryMB());
+  size_t max_memory1 = kPerformanceControlsPPMSurveySegmentMaxMemoryGB1.Get();
+  size_t max_memory2 = kPerformanceControlsPPMSurveySegmentMaxMemoryGB2.Get();
+  if (max_memory1 == 0 || system_ram <= max_memory1 * 1024) {
+    // Segment 1 has no upper bound, or the system RAM is in its bounds.
+    return kPerformanceControlsPPMSurveySegmentName1.Get();
+  }
+  if (max_memory2 == 0 || system_ram <= max_memory2 * 1024) {
+    // Segment 2 has no upper bound, or the system RAM is in its bounds.
+    return kPerformanceControlsPPMSurveySegmentName2.Get();
+  }
+  return kPerformanceControlsPPMSurveySegmentName3.Get();
+}
diff --git a/chrome/browser/ui/performance_controls/performance_controls_hats_service.h b/chrome/browser/ui/performance_controls/performance_controls_hats_service.h
index 2f649634..c9e8af2f 100644
--- a/chrome/browser/ui/performance_controls/performance_controls_hats_service.h
+++ b/chrome/browser/ui/performance_controls/performance_controls_hats_service.h
@@ -5,6 +5,9 @@
 #ifndef CHROME_BROWSER_UI_PERFORMANCE_CONTROLS_PERFORMANCE_CONTROLS_HATS_SERVICE_H_
 #define CHROME_BROWSER_UI_PERFORMANCE_CONTROLS_PERFORMANCE_CONTROLS_HATS_SERVICE_H_
 
+#include <optional>
+#include <string>
+
 #include "base/time/time.h"
 #include "chrome/browser/performance_manager/public/user_tuning/battery_saver_mode_manager.h"
 #include "chrome/browser/performance_manager/public/user_tuning/user_performance_tuning_manager.h"
@@ -18,6 +21,16 @@
       public performance_manager::user_tuning::BatterySaverModeManager::
           Observer {
  public:
+  // Names of Product Specific Data bit entries.
+  static constexpr char kBatterySaverPSDName[] = "Battery Saver Mode Enabled";
+  static constexpr char kMemorySaverPSDName[] = "Memory Saver Mode Enabled";
+  static constexpr char kUniformSamplePSDName[] = "Selected for Uniform Sample";
+
+  // Names of Product Specific Data string entries.
+  static constexpr char kChannelPSDName[] = "Channel";
+  static constexpr char kPerformanceSegmentPSDName[] =
+      "Performance Characteristics (OS and Total Memory)";
+
   explicit PerformanceControlsHatsService(Profile* profile);
   ~PerformanceControlsHatsService() override;
 
@@ -46,15 +59,28 @@
     delay_before_ppm_survey_ = delay;
   }
 
+  // Overrides the amount of physical memory reported for testing.
+  void SetAmountOfPhysicalMemoryMBForTesting(uint64_t memory_mb) {
+    memory_mb_for_testing_ = memory_mb;
+  }
+
  private:
   // Returns true if the PPM survey can be shown at this time.
   bool MayLaunchPPMSurvey() const;
 
+  // Returns the name of the segment this client falls into for the PPM survey,
+  // or an empty string if it doesn't fall into any defined survey segment.
+  std::string GetPPMSurveySegmentName();
+
   raw_ptr<Profile> profile_;
 
   // Delay before showing the PPM UMA survey. Randomly generated when the
   // service is created.
   base::TimeDelta delay_before_ppm_survey_;
+
+  // A value to use instead of calling base::SysInfo::AmountOfPhysicalMemoryMB()
+  // in tests.
+  std::optional<uint64_t> memory_mb_for_testing_;
 };
 
 #endif  // CHROME_BROWSER_UI_PERFORMANCE_CONTROLS_PERFORMANCE_CONTROLS_HATS_SERVICE_H_
diff --git a/chrome/browser/ui/performance_controls/performance_controls_hats_service_unittest.cc b/chrome/browser/ui/performance_controls/performance_controls_hats_service_unittest.cc
index 5e0de0e..343ba0dd 100644
--- a/chrome/browser/ui/performance_controls/performance_controls_hats_service_unittest.cc
+++ b/chrome/browser/ui/performance_controls/performance_controls_hats_service_unittest.cc
@@ -7,6 +7,7 @@
 #include <memory>
 
 #include "base/memory/raw_ptr.h"
+#include "base/metrics/field_trial_params.h"
 #include "base/test/power_monitor_test_utils.h"
 #include "base/test/scoped_feature_list.h"
 #include "chrome/browser/browser_process.h"
@@ -27,9 +28,30 @@
 #include "ash/constants/ash_features.h"
 #endif
 
+namespace {
+
 using performance_manager::features::kPerformanceControlsPPMSurveyMaxDelay;
 using performance_manager::features::kPerformanceControlsPPMSurveyMinDelay;
 using ::testing::_;
+using ::testing::Pair;
+using ::testing::UnorderedElementsAre;
+
+const char* kBatterySaverPSDName =
+    PerformanceControlsHatsService::kBatterySaverPSDName;
+const char* kChannelPSDName = PerformanceControlsHatsService::kChannelPSDName;
+const char* kMemorySaverPSDName =
+    PerformanceControlsHatsService::kMemorySaverPSDName;
+const char* kPerformanceSegmentPSDName =
+    PerformanceControlsHatsService::kPerformanceSegmentPSDName;
+const char* kUniformSamplePSDName =
+    PerformanceControlsHatsService::kUniformSamplePSDName;
+
+// GMock matcher for any expected "channel" string
+auto MatchesAnyChannel() {
+  return ::testing::AnyOf("canary", "dev", "beta", "stable");
+}
+
+}  // namespace
 
 class PerformanceControlsHatsServiceTest : public testing::Test {
  public:
@@ -181,9 +203,8 @@
   const bool battery_saver_mode = true;
 #endif
 
-  SurveyBitsData expected_bits = {
-      {"Memory Saver Mode Enabled", false},
-      {"Battery Saver Mode Enabled", battery_saver_mode}};
+  SurveyBitsData expected_bits = {{kMemorySaverPSDName, false},
+                                  {kBatterySaverPSDName, battery_saver_mode}};
   SurveyStringData expected_strings = {};
   EXPECT_CALL(*mock_hats_service(),
               LaunchSurvey(kHatsSurveyTriggerPerformanceControlsPerformance, _,
@@ -238,9 +259,12 @@
 
   const std::vector<base::test::FeatureRefAndParams> GetFeatures() override {
     return {
-        {performance_manager::features::kPerformanceControlsPPMSurvey, {}},
+        {performance_manager::features::kPerformanceControlsPPMSurvey,
+         GetFieldTrialParams()},
     };
   }
+
+  virtual base::FieldTrialParams GetFieldTrialParams() const { return {}; }
 };
 
 TEST_F(PerformanceControlsHatsServicePPMTest, NoPPMSurveyBeforeDelay) {
@@ -254,7 +278,13 @@
 TEST_F(PerformanceControlsHatsServicePPMTest, LaunchesPPMSurveyAfterDelay) {
   EXPECT_CALL(
       *mock_hats_service(),
-      LaunchSurvey(kHatsSurveyTriggerPerformanceControlsPPM, _, _, _, _));
+      LaunchSurvey(
+          kHatsSurveyTriggerPerformanceControlsPPM, _, _,
+          UnorderedElementsAre(Pair(kMemorySaverPSDName, _),
+                               Pair(kBatterySaverPSDName, _),
+                               Pair(kUniformSamplePSDName, true)),
+          UnorderedElementsAre(Pair(kPerformanceSegmentPSDName, _),
+                               Pair(kChannelPSDName, MatchesAnyChannel()))));
   task_env().FastForwardBy(
       performance_controls_hats_service()->delay_before_ppm_survey());
   performance_controls_hats_service()->OpenedNewTabPage();
@@ -284,6 +314,190 @@
   performance_controls_hats_service()->OpenedNewTabPage();
 }
 
+class PerformanceControlsHatsServicePPM2SegmentTest
+    : public PerformanceControlsHatsServicePPMTest {
+ protected:
+  base::FieldTrialParams GetFieldTrialParams() const override {
+    return {
+        // <= 8 GB
+        {"ppm_survey_segment_name1", "Low Memory"},
+        {"ppm_survey_segment_max_memory_gb1", "8"},
+        // > 8 GB
+        {"ppm_survey_segment_name2", "High Memory"},
+    };
+  }
+};
+
+TEST_F(PerformanceControlsHatsServicePPM2SegmentTest, LowMemorySegment) {
+  EXPECT_CALL(
+      *mock_hats_service(),
+      LaunchSurvey(
+          kHatsSurveyTriggerPerformanceControlsPPM, _, _,
+          UnorderedElementsAre(Pair(kMemorySaverPSDName, _),
+                               Pair(kBatterySaverPSDName, _),
+                               Pair(kUniformSamplePSDName, true)),
+          UnorderedElementsAre(Pair(kPerformanceSegmentPSDName, "Low Memory"),
+                               Pair(kChannelPSDName, MatchesAnyChannel()))));
+  performance_controls_hats_service()->SetAmountOfPhysicalMemoryMBForTesting(
+      8192);
+  task_env().FastForwardBy(
+      performance_controls_hats_service()->delay_before_ppm_survey());
+  performance_controls_hats_service()->OpenedNewTabPage();
+}
+
+TEST_F(PerformanceControlsHatsServicePPM2SegmentTest, HighMemorySegment) {
+  EXPECT_CALL(
+      *mock_hats_service(),
+      LaunchSurvey(
+          kHatsSurveyTriggerPerformanceControlsPPM, _, _,
+          UnorderedElementsAre(Pair(kMemorySaverPSDName, _),
+                               Pair(kBatterySaverPSDName, _),
+                               Pair(kUniformSamplePSDName, true)),
+          UnorderedElementsAre(Pair(kPerformanceSegmentPSDName, "High Memory"),
+                               Pair(kChannelPSDName, MatchesAnyChannel()))));
+  performance_controls_hats_service()->SetAmountOfPhysicalMemoryMBForTesting(
+      12288);
+  task_env().FastForwardBy(
+      performance_controls_hats_service()->delay_before_ppm_survey());
+  performance_controls_hats_service()->OpenedNewTabPage();
+}
+
+class PerformanceControlsHatsServicePPM3SegmentTest
+    : public PerformanceControlsHatsServicePPMTest {
+ protected:
+  base::FieldTrialParams GetFieldTrialParams() const override {
+    return {
+        // <= 4 GB
+        {"ppm_survey_segment_name1", "Low Memory"},
+        {"ppm_survey_segment_max_memory_gb1", "4"},
+        // 4-8 GB
+        {"ppm_survey_segment_name2", "Medium Memory"},
+        {"ppm_survey_segment_max_memory_gb2", "8"},
+        // > 8 GB
+        {"ppm_survey_segment_name3", "High Memory"},
+    };
+  }
+};
+
+TEST_F(PerformanceControlsHatsServicePPM3SegmentTest, LowMemorySegment) {
+  EXPECT_CALL(
+      *mock_hats_service(),
+      LaunchSurvey(
+          kHatsSurveyTriggerPerformanceControlsPPM, _, _,
+          UnorderedElementsAre(Pair(kMemorySaverPSDName, _),
+                               Pair(kBatterySaverPSDName, _),
+                               Pair(kUniformSamplePSDName, true)),
+          UnorderedElementsAre(Pair(kPerformanceSegmentPSDName, "Low Memory"),
+                               Pair(kChannelPSDName, MatchesAnyChannel()))));
+  performance_controls_hats_service()->SetAmountOfPhysicalMemoryMBForTesting(
+      4096);
+  task_env().FastForwardBy(
+      performance_controls_hats_service()->delay_before_ppm_survey());
+  performance_controls_hats_service()->OpenedNewTabPage();
+}
+
+TEST_F(PerformanceControlsHatsServicePPM3SegmentTest, MediumMemorySegment) {
+  EXPECT_CALL(
+      *mock_hats_service(),
+      LaunchSurvey(kHatsSurveyTriggerPerformanceControlsPPM, _, _,
+                   UnorderedElementsAre(Pair(kMemorySaverPSDName, _),
+                                        Pair(kBatterySaverPSDName, _),
+                                        Pair(kUniformSamplePSDName, true)),
+                   UnorderedElementsAre(
+                       Pair(kPerformanceSegmentPSDName, "Medium Memory"),
+                       Pair(kChannelPSDName, MatchesAnyChannel()))));
+  performance_controls_hats_service()->SetAmountOfPhysicalMemoryMBForTesting(
+      8192);
+  task_env().FastForwardBy(
+      performance_controls_hats_service()->delay_before_ppm_survey());
+  performance_controls_hats_service()->OpenedNewTabPage();
+}
+
+TEST_F(PerformanceControlsHatsServicePPM3SegmentTest, HighMemorySegment) {
+  EXPECT_CALL(
+      *mock_hats_service(),
+      LaunchSurvey(
+          kHatsSurveyTriggerPerformanceControlsPPM, _, _,
+          UnorderedElementsAre(Pair(kMemorySaverPSDName, _),
+                               Pair(kBatterySaverPSDName, _),
+                               Pair(kUniformSamplePSDName, true)),
+          UnorderedElementsAre(Pair(kPerformanceSegmentPSDName, "High Memory"),
+                               Pair(kChannelPSDName, MatchesAnyChannel()))));
+  performance_controls_hats_service()->SetAmountOfPhysicalMemoryMBForTesting(
+      16384);
+  task_env().FastForwardBy(
+      performance_controls_hats_service()->delay_before_ppm_survey());
+  performance_controls_hats_service()->OpenedNewTabPage();
+}
+
+class PerformanceControlsHatsServicePPMFinishedSegmentTest
+    : public PerformanceControlsHatsServicePPMTest {
+ protected:
+  base::FieldTrialParams GetFieldTrialParams() const override {
+    return {
+        // uniform_sample should be disabled before a segment is finished, since
+        // the weight of each segment no longer reflects the general population.
+        {"ppm_survey_uniform_sample", "false"},
+        // <= 4 GB
+        {"ppm_survey_segment_name1", "Low Memory"},
+        {"ppm_survey_segment_max_memory_gb1", "4"},
+        // 4-8 GB has enough responses and shouldn't be shown.
+        {"ppm_survey_segment_name2", ""},
+        {"ppm_survey_segment_max_memory_gb2", "8"},
+        // > 8 GB
+        {"ppm_survey_segment_name3", "High Memory"},
+    };
+  }
+};
+
+TEST_F(PerformanceControlsHatsServicePPMFinishedSegmentTest, LowMemorySegment) {
+  EXPECT_CALL(
+      *mock_hats_service(),
+      LaunchSurvey(
+          kHatsSurveyTriggerPerformanceControlsPPM, _, _,
+          UnorderedElementsAre(Pair(kMemorySaverPSDName, _),
+                               Pair(kBatterySaverPSDName, _),
+                               Pair(kUniformSamplePSDName, false)),
+          UnorderedElementsAre(Pair(kPerformanceSegmentPSDName, "Low Memory"),
+                               Pair(kChannelPSDName, MatchesAnyChannel()))));
+  performance_controls_hats_service()->SetAmountOfPhysicalMemoryMBForTesting(
+      4096);
+  task_env().FastForwardBy(
+      performance_controls_hats_service()->delay_before_ppm_survey());
+  performance_controls_hats_service()->OpenedNewTabPage();
+}
+
+TEST_F(PerformanceControlsHatsServicePPMFinishedSegmentTest,
+       MediumMemorySegmentDone) {
+  EXPECT_CALL(
+      *mock_hats_service(),
+      LaunchSurvey(kHatsSurveyTriggerPerformanceControlsPPM, _, _, _, _))
+      .Times(0);
+  performance_controls_hats_service()->SetAmountOfPhysicalMemoryMBForTesting(
+      8192);
+  task_env().FastForwardBy(
+      performance_controls_hats_service()->delay_before_ppm_survey());
+  performance_controls_hats_service()->OpenedNewTabPage();
+}
+
+TEST_F(PerformanceControlsHatsServicePPMFinishedSegmentTest,
+       HighMemorySegment) {
+  EXPECT_CALL(
+      *mock_hats_service(),
+      LaunchSurvey(
+          kHatsSurveyTriggerPerformanceControlsPPM, _, _,
+          UnorderedElementsAre(Pair(kMemorySaverPSDName, _),
+                               Pair(kBatterySaverPSDName, _),
+                               Pair(kUniformSamplePSDName, false)),
+          UnorderedElementsAre(Pair(kPerformanceSegmentPSDName, "High Memory"),
+                               Pair(kChannelPSDName, MatchesAnyChannel()))));
+  performance_controls_hats_service()->SetAmountOfPhysicalMemoryMBForTesting(
+      16384);
+  task_env().FastForwardBy(
+      performance_controls_hats_service()->delay_before_ppm_survey());
+  performance_controls_hats_service()->OpenedNewTabPage();
+}
+
 class PerformanceControlsHatsServiceDestructorTest : public testing::Test {
  public:
   PerformanceControlsHatsServiceDestructorTest() = default;
diff --git a/chrome/browser/ui/tabs/tab_strip_model.cc b/chrome/browser/ui/tabs/tab_strip_model.cc
index 4085836..ec4b9e4 100644
--- a/chrome/browser/ui/tabs/tab_strip_model.cc
+++ b/chrome/browser/ui/tabs/tab_strip_model.cc
@@ -544,6 +544,12 @@
 
   OnChange(change, selection);
 
+  if (empty()) {
+    for (auto& observer : observers_) {
+      observer.TabStripEmpty();
+    }
+  }
+
   std::optional<int> active_index_in_group =
       active_tab_removed
           ? group_collection->GetIndexOfTabRecursive(active_tab_model)
diff --git a/chrome/browser/ui/toolbar/app_menu_model.cc b/chrome/browser/ui/toolbar/app_menu_model.cc
index 6031924..3dc212eb 100644
--- a/chrome/browser/ui/toolbar/app_menu_model.cc
+++ b/chrome/browser/ui/toolbar/app_menu_model.cc
@@ -142,6 +142,12 @@
 #include "ui/gfx/text_elider.h"
 #include "ui/menus/simple_menu_model.h"
 
+#if BUILDFLAG(ENABLE_GLIC)
+#include "chrome/browser/glic/browser_ui/glic_vector_icon_manager.h"
+#include "chrome/browser/glic/glic_enabling.h"
+#include "chrome/browser/glic/resources/grit/glic_browser_resources.h"
+#endif
+
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING) || BUILDFLAG(IS_CHROMEOS)
 #include "base/feature_list.h"
 #endif
@@ -894,7 +900,8 @@
     if (features::HasTabSearchToolbarButton()) {
       // TODO(crbug.com/404319632): Update with proper icon
       AddItemWithStringIdAndVectorIcon(this, IDC_TAB_SEARCH,
-                                       IDS_TAB_SEARCH_MENU, kSearchMenuIcon);
+                                       IDS_TAB_SEARCH_MENU,
+                                       vector_icons::kExpandMoreIcon);
     }
 
     if (base::FeatureList::IsEnabled(features::kTabOrganizationAppMenuItem) &&
@@ -1235,6 +1242,15 @@
       }
       LogMenuAction(MENU_ACTION_PRINT);
       break;
+#if BUILDFLAG(ENABLE_GLIC)
+    case IDC_OPEN_GLIC:
+      if (!uma_action_recorded_) {
+        base::UmaHistogramMediumTimes("WrenchMenu.TimeToAction.OpenGlic",
+                                      delta);
+      }
+      LogMenuAction(MENU_ACTION_OPEN_GLIC);
+      break;
+#endif
 
     case IDC_SHOW_TRANSLATE:
       if (!uma_action_recorded_) {
@@ -1909,6 +1925,15 @@
 
   AddItemWithStringIdAndVectorIcon(this, IDC_PRINT, IDS_PRINT, kPrintMenuIcon);
 
+#if BUILDFLAG(ENABLE_GLIC)
+  if (glic::GlicEnabling::IsProfileEligible(browser_->profile())) {
+    AddItemWithStringIdAndVectorIcon(this, IDC_OPEN_GLIC,
+                                     IDS_GLIC_THREE_DOT_MENU_ITEM,
+                                     glic::GlicVectorIconManager::GetVectorIcon(
+                                         IDR_GLIC_BUTTON_VECTOR_ICON));
+  }
+#endif
+
   if (browser()
           ->GetFeatures()
           .lens_overlay_entry_point_controller()
diff --git a/chrome/browser/ui/toolbar/app_menu_model.h b/chrome/browser/ui/toolbar/app_menu_model.h
index 559ff92..e65c121f 100644
--- a/chrome/browser/ui/toolbar/app_menu_model.h
+++ b/chrome/browser/ui/toolbar/app_menu_model.h
@@ -113,6 +113,7 @@
   MENU_ACTION_SAFETY_HUB_MANAGE_EXTENSIONS = 91,
   MENU_ACTION_SHOW_CUSTOMIZE_CHROME_SIDE_PANEL = 92,
   MENU_ACTION_DECLUTTER_TABS = 93,
+  MENU_ACTION_OPEN_GLIC = 94,
   LIMIT_MENU_ACTION
 };
 // LINT.ThenChange(/tools/metrics/histograms/metadata/ui/enums.xml:WrenchMenuAction)
diff --git a/chrome/browser/ui/toolbar/app_menu_model_unittest.cc b/chrome/browser/ui/toolbar/app_menu_model_unittest.cc
index 930d5d3..ca80b37 100644
--- a/chrome/browser/ui/toolbar/app_menu_model_unittest.cc
+++ b/chrome/browser/ui/toolbar/app_menu_model_unittest.cc
@@ -374,6 +374,19 @@
   EXPECT_EQ(1, model.log_metrics_count_);
 }
 
+#if BUILDFLAG(ENABLE_GLIC)
+TEST_F(AppMenuModelTest, GlicItem) {
+  feature_list_.Reset();
+  feature_list_.InitWithFeatures(
+      {features::kGlic, features::kTabstripComboButton}, {});
+
+  TestLogMetricsAppMenuModel model(this, browser());
+  model.Init();
+  model.ExecuteCommand(IDC_OPEN_GLIC, 0);
+  EXPECT_EQ(1, model.log_metrics_count_);
+}
+#endif
+
 TEST_F(AppMenuModelTest, ModelHasIcons) {
   // Skip the items that are either not supposed to have an icon, or are not
   // ready to be tested. Remove items once they're ready for testing.
diff --git a/chrome/browser/ui/views/frame/browser_root_view.cc b/chrome/browser/ui/views/frame/browser_root_view.cc
index 42b9210..d38c38b 100644
--- a/chrome/browser/ui/views/frame/browser_root_view.cc
+++ b/chrome/browser/ui/views/frame/browser_root_view.cc
@@ -434,14 +434,25 @@
     if (active_tab_index.has_value()) {
       Tab* active_tab = tabstrip()->tab_at(active_tab_index.value());
       if (active_tab && active_tab->GetVisible()) {
-        gfx::RectF bounds(active_tab->GetMirroredBounds());
-        // The root of the views tree that hosts tabstrip is BrowserRootView.
-        // Except in Mac Immersive Fullscreen where the tabstrip is hosted in
-        // `overlay_widget` or `tab_overlay_widget`, each have their own root
-        // view.
-        ConvertRectToTarget(tabstrip(), tabstrip()->GetWidget()->GetRootView(),
-                            &bounds);
-        canvas->ClipRect(bounds, SkClipOp::kDifference);
+        auto clip_rect_for_tab = [canvas, this](Tab* tab) {
+          gfx::RectF bounds(tab->GetMirroredBounds());
+          // The root of the views tree that hosts tabstrip is BrowserRootView.
+          // Except in Mac Immersive Fullscreen where the tabstrip is hosted in
+          // `overlay_widget` or `tab_overlay_widget`, each have their own root
+          // view.
+          ConvertRectToTarget(tabstrip(),
+                              tabstrip()->GetWidget()->GetRootView(), &bounds);
+          canvas->ClipRect(bounds, SkClipOp::kDifference);
+        };
+
+        if (active_tab->split()) {
+          for (Tab* split_tab :
+               active_tab->controller()->GetTabsInSplit(active_tab)) {
+            clip_rect_for_tab(split_tab);
+          }
+        } else {
+          clip_rect_for_tab(active_tab);
+        }
       }
     }
     canvas->UndoDeviceScaleFactor();
diff --git a/chrome/browser/ui/views/frame/browser_view.cc b/chrome/browser/ui/views/frame/browser_view.cc
index c0c40c99..ac0264a 100644
--- a/chrome/browser/ui/views/frame/browser_view.cc
+++ b/chrome/browser/ui/views/frame/browser_view.cc
@@ -1480,13 +1480,18 @@
   // TODO(crbug.com/392951786): Use the Collections API for accessing the tabs
   // in a split view, rather than searching by index.
   int active_index = browser_->tab_strip_model()->active_index();
+  std::optional<split_tabs::SplitTabId> active_split_id =
+      browser_->tab_strip_model()->GetTabAtIndex(active_index)->GetSplit();
   const std::vector<int> potential_split_indices = {active_index - 1,
                                                     active_index + 1};
   for (int index : potential_split_indices) {
     if (index < 0 || index >= browser_->tab_strip_model()->GetTabCount()) {
       continue;
     }
-    if (browser_->tab_strip_model()->GetTabAtIndex(index)->IsSplit()) {
+    auto* potential_split_tab =
+        browser_->tab_strip_model()->GetTabAtIndex(index);
+    if (potential_split_tab->IsSplit() &&
+        potential_split_tab->GetSplit() == active_split_id) {
       return index;
     }
   }
diff --git a/chrome/browser/ui/views/frame/browser_view.h b/chrome/browser/ui/views/frame/browser_view.h
index 9e5646d6..7a2f7b5 100644
--- a/chrome/browser/ui/views/frame/browser_view.h
+++ b/chrome/browser/ui/views/frame/browser_view.h
@@ -471,9 +471,8 @@
   std::optional<bool> GetWebApiWindowResizable() const;
 
   // Return the tab strip index of the single tab (if any) that is inactive but
-  // part of a split view. Assumes there is max one split view in the tab
-  // strip, it contains exactly two tabs, and one of those tabs is currently
-  // active.
+  // part of a split view. Assumes the split view contains exactly two tabs, and
+  // one of those tabs is currently active.
   int GetInactiveSplitTabIndex();
 
   // Display the current active split view as a series of multiple side-by-side
diff --git a/chrome/browser/ui/views/frame/browser_view_browsertest.cc b/chrome/browser/ui/views/frame/browser_view_browsertest.cc
index a5cc8d53..54f4300 100644
--- a/chrome/browser/ui/views/frame/browser_view_browsertest.cc
+++ b/chrome/browser/ui/views/frame/browser_view_browsertest.cc
@@ -26,6 +26,7 @@
 #include "chrome/browser/ui/tab_modal_confirm_dialog.h"
 #include "chrome/browser/ui/tab_ui_helper.h"
 #include "chrome/browser/ui/tabs/public/tab_features.h"
+#include "chrome/browser/ui/tabs/split_tab_collection.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/browser/ui/tabs/tab_strip_user_gesture_details.h"
 #include "chrome/browser/ui/test/test_browser_ui.h"
@@ -489,6 +490,51 @@
 }
 #endif  // !BUILDFLAG(IS_MAC)
 
+class SideBySideBrowserViewTest : public InProcessBrowserTest {
+ public:
+  SideBySideBrowserViewTest() {
+    scoped_feature_list_.InitAndEnableFeature(features::kSideBySide);
+  }
+
+  SideBySideBrowserViewTest(const SideBySideBrowserViewTest&) = delete;
+  SideBySideBrowserViewTest& operator=(const SideBySideBrowserViewTest&) =
+      delete;
+
+ protected:
+  BrowserView* browser_view() {
+    return BrowserView::GetBrowserViewForBrowser(browser());
+  }
+
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+// Tests that GetInactiveSplitTabIndex returns correctly with two adjacent
+// splits.
+IN_PROC_BROWSER_TEST_F(SideBySideBrowserViewTest,
+                       SplitViewInactiveIndexReturnsCorrectly) {
+  // Add enough tabs to create two split views.
+  chrome::AddTabAt(browser(), GURL(), -1, true);
+  chrome::AddTabAt(browser(), GURL(), -1, true);
+  chrome::AddTabAt(browser(), GURL(), -1, true);
+  // Add tabs to splits.
+  browser()->tab_strip_model()->ActivateTabAt(0);
+  browser()->tab_strip_model()->AddToNewSplit(
+      {1}, tabs::SplitTabLayout::kHorizontal);
+  browser()->tab_strip_model()->ActivateTabAt(2);
+  browser()->tab_strip_model()->AddToNewSplit(
+      {3}, tabs::SplitTabLayout::kHorizontal);
+  // Verify GetInactiveSplitTabIndex() correctly returns the inactive tab if
+  // each index is activated.
+  browser()->tab_strip_model()->ActivateTabAt(0);
+  EXPECT_EQ(browser_view()->GetInactiveSplitTabIndex(), 1);
+  browser()->tab_strip_model()->ActivateTabAt(1);
+  EXPECT_EQ(browser_view()->GetInactiveSplitTabIndex(), 0);
+  browser()->tab_strip_model()->ActivateTabAt(2);
+  EXPECT_EQ(browser_view()->GetInactiveSplitTabIndex(), 3);
+  browser()->tab_strip_model()->ActivateTabAt(3);
+  EXPECT_EQ(browser_view()->GetInactiveSplitTabIndex(), 2);
+}
+
 namespace {
 
 class FakeRealTimeUrlLookupService
diff --git a/chrome/browser/ui/views/media_preview/camera_preview/camera_coordinator.cc b/chrome/browser/ui/views/media_preview/camera_preview/camera_coordinator.cc
index ec6e30e8..b94ff9b 100644
--- a/chrome/browser/ui/views/media_preview/camera_preview/camera_coordinator.cc
+++ b/chrome/browser/ui/views/media_preview/camera_preview/camera_coordinator.cc
@@ -9,7 +9,6 @@
 
 #include "base/functional/bind.h"
 #include "chrome/browser/media/prefs/capture_device_ranking.h"
-#include "chrome/browser/media_effects/media_effects_manager_binder.h"
 #include "chrome/browser/ui/views/media_preview/camera_preview/camera_mediator.h"
 #include "chrome/browser/ui/views/media_preview/media_view.h"
 #include "components/user_prefs/user_prefs.h"
@@ -18,6 +17,10 @@
 #include "mojo/public/cpp/bindings/remote.h"
 #include "services/video_capture/public/mojom/video_source.mojom.h"
 
+#if BUILDFLAG(ENABLE_VIDEO_EFFECTS)
+#include "chrome/browser/media_effects/media_effects_manager_binder.h"
+#endif
+
 CameraCoordinator::CameraCoordinator(
     views::View& parent_view,
     bool needs_borders,
@@ -51,9 +54,11 @@
                           base::Unretained(this)),
       metrics_context_);
 
+#if BUILDFLAG(ENABLE_VIDEO_EFFECTS)
   if (base::FeatureList::IsEnabled(media::kCameraMicEffects)) {
     blur_switch_view_controller_.emplace(*camera_view, browser_context_);
   }
+#endif
 
   video_stream_coordinator_.emplace(
       camera_view_controller_->GetLiveFeedContainer(), metrics_context_);
@@ -91,9 +96,11 @@
   if (eligible_device_infos_.empty()) {
     active_device_id_.clear();
     video_stream_coordinator_->Stop();
+#if BUILDFLAG(ENABLE_VIDEO_EFFECTS)
     if (blur_switch_view_controller_) {
       blur_switch_view_controller_->ResetConnections();
     }
+#endif
   }
   camera_view_controller_->UpdateVideoSourceInfos(eligible_device_infos_);
 }
@@ -114,6 +121,7 @@
   camera_mediator_.BindVideoSource(active_device_id_,
                                    video_source.BindNewPipeAndPassReceiver());
 
+#if BUILDFLAG(ENABLE_VIDEO_EFFECTS)
   if (base::FeatureList::IsEnabled(media::kCameraMicEffects) &&
       browser_context_) {
     if (blur_switch_view_controller_) {
@@ -139,6 +147,7 @@
     video_source->RegisterReadonlyVideoEffectsManager(
         std::move(readonly_video_effects_manager));
   }
+#endif
 
   video_stream_coordinator_->ConnectToDevice(device_info,
                                              std::move(video_source));
@@ -171,5 +180,7 @@
 
 void CameraCoordinator::ResetViewController() {
   camera_view_controller_.reset();
+#if BUILDFLAG(ENABLE_VIDEO_EFFECTS)
   blur_switch_view_controller_.reset();
+#endif
 }
diff --git a/chrome/browser/ui/views/media_preview/camera_preview/camera_coordinator.h b/chrome/browser/ui/views/media_preview/camera_preview/camera_coordinator.h
index a5d26af..a7cb4a3 100644
--- a/chrome/browser/ui/views/media_preview/camera_preview/camera_coordinator.h
+++ b/chrome/browser/ui/views/media_preview/camera_preview/camera_coordinator.h
@@ -9,16 +9,20 @@
 #include <string>
 #include <vector>
 
-#include "chrome/browser/ui/views/media_preview/camera_preview/blur_switch_view_controller.h"
 #include "chrome/browser/ui/views/media_preview/camera_preview/camera_mediator.h"
 #include "chrome/browser/ui/views/media_preview/camera_preview/camera_view_controller.h"
 #include "chrome/browser/ui/views/media_preview/camera_preview/video_stream_coordinator.h"
 #include "chrome/browser/ui/views/media_preview/media_preview_metrics.h"
 #include "components/prefs/pref_service.h"
 #include "content/public/browser/browser_context.h"
+#include "services/video_effects/public/cpp/buildflags.h"
 #include "ui/base/models/simple_combobox_model.h"
 #include "ui/views/view_tracker.h"
 
+#if BUILDFLAG(ENABLE_VIDEO_EFFECTS)
+#include "chrome/browser/ui/views/media_preview/camera_preview/blur_switch_view_controller.h"
+#endif
+
 // Acts as a middle man between the ViewController and the Mediator.
 // Maintains the lifetime of its views.
 class CameraCoordinator {
@@ -66,7 +70,9 @@
   const bool allow_device_selection_;
   const media_preview_metrics::Context metrics_context_;
   std::optional<CameraViewController> camera_view_controller_;
+#if BUILDFLAG(ENABLE_VIDEO_EFFECTS)
   std::optional<BlurSwitchViewController> blur_switch_view_controller_;
+#endif
   std::optional<VideoStreamCoordinator> video_stream_coordinator_;
 };
 
diff --git a/chrome/browser/ui/views/performance_controls/performance_intervention_bubble.cc b/chrome/browser/ui/views/performance_controls/performance_intervention_bubble.cc
index f0e6047c..4aa6948 100644
--- a/chrome/browser/ui/views/performance_controls/performance_intervention_bubble.cc
+++ b/chrome/browser/ui/views/performance_controls/performance_intervention_bubble.cc
@@ -117,11 +117,45 @@
 }
 
 DialogStrings PerformanceInterventionBubble::GetStrings(int count) {
-  return {
-      l10n_util::GetStringUTF16(IDS_PERFORMANCE_INTERVENTION_DIALOG_TITLE_V1),
-      l10n_util::GetStringUTF16(
-          count > 1 ? IDS_PERFORMANCE_INTERVENTION_DIALOG_BODY_V1
-                    : IDS_PERFORMANCE_INTERVENTION_DIALOG_BODY_SINGULAR_V1),
-      l10n_util::GetStringUTF16(
-          IDS_PERFORMANCE_INTERVENTION_DEACTIVATE_TABS_BUTTON_V1)};
+  const std::u16string body_text = l10n_util::GetStringUTF16(
+      count > 1 ? IDS_PERFORMANCE_INTERVENTION_DIALOG_BODY_V1
+                : IDS_PERFORMANCE_INTERVENTION_DIALOG_BODY_SINGULAR_V1);
+
+  if (base::FeatureList::IsEnabled(
+          performance_manager::features::
+              kPerformanceInterventionNotificationStringImprovements)) {
+    const int string_version =
+        performance_manager::features::kNotificationStringVersion.Get();
+    if (string_version == 1) {
+      return {
+          l10n_util::GetPluralStringFUTF16(
+              IDS_PERFORMANCE_INTERVENTION_DIALOG_TITLE_UPDATED_V1, count),
+          body_text,
+          l10n_util::GetPluralStringFUTF16(
+              IDS_PERFORMANCE_INTERVENTION_DEACTIVATE_TABS_BUTTON_UPDATED_V1,
+              count)};
+    } else if (string_version == 2) {
+      return {
+          l10n_util::GetPluralStringFUTF16(
+              IDS_PERFORMANCE_INTERVENTION_DIALOG_TITLE_UPDATED_V2, count),
+          body_text,
+          l10n_util::GetPluralStringFUTF16(
+              IDS_PERFORMANCE_INTERVENTION_DEACTIVATE_TABS_BUTTON_UPDATED_V2,
+              count)};
+    } else {
+      return {
+          l10n_util::GetPluralStringFUTF16(
+              IDS_PERFORMANCE_INTERVENTION_DIALOG_TITLE_UPDATED_V3, count),
+          body_text,
+          l10n_util::GetPluralStringFUTF16(
+              IDS_PERFORMANCE_INTERVENTION_DEACTIVATE_TABS_BUTTON_UPDATED_V2,
+              count)};
+    }
+  } else {
+    return {
+        l10n_util::GetStringUTF16(IDS_PERFORMANCE_INTERVENTION_DIALOG_TITLE_V1),
+        body_text,
+        l10n_util::GetStringUTF16(
+            IDS_PERFORMANCE_INTERVENTION_DEACTIVATE_TABS_BUTTON_V1)};
+  }
 }
diff --git a/chrome/browser/ui/views/side_panel/customize_chrome/side_panel_controller_views.cc b/chrome/browser/ui/views/side_panel/customize_chrome/side_panel_controller_views.cc
index 135461c..3d35370 100644
--- a/chrome/browser/ui/views/side_panel/customize_chrome/side_panel_controller_views.cc
+++ b/chrome/browser/ui/views/side_panel/customize_chrome/side_panel_controller_views.cc
@@ -7,6 +7,7 @@
 #include "base/functional/bind.h"
 #include "base/functional/callback.h"
 #include "chrome/app/vector_icons/vector_icons.h"
+#include "chrome/browser/extensions/settings_api_helpers.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/search/background/ntp_custom_background_service_factory.h"
 #include "chrome/browser/ui/browser_element_identifiers.h"
@@ -20,6 +21,7 @@
 #include "chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_ui.h"
 #include "chrome/common/webui_url_constants.h"
 #include "chrome/grit/generated_resources.h"
+#include "components/search/ntp_features.h"
 #include "components/vector_icons/vector_icons.h"
 #include "content/public/browser/navigation_entry.h"
 #include "content/public/browser/navigation_handle.h"
@@ -93,6 +95,29 @@
   return true;
 }
 
+bool SidePanelControllerViews::ShouldEnableEditTheme(const GURL& url) const {
+  return NewTabPageUI::IsNewTabPageOrigin(url) ||
+         (base::FeatureList::IsEnabled(ntp_features::kNtpFooter) &&
+          IsExtensionNtp(url));
+}
+
+bool SidePanelControllerViews::IsExtensionNtp(const GURL& url) const {
+  if (!url.SchemeIs(extensions::kExtensionScheme)) {
+    return false;
+  }
+
+  Profile* const profile =
+      Profile::FromBrowserContext(tab_->GetContents()->GetBrowserContext());
+  const extensions::Extension* extension_managing_ntp =
+      extensions::GetExtensionOverridingNewTabPage(profile);
+
+  if (!extension_managing_ntp) {
+    return false;
+  }
+
+  return extension_managing_ntp->id() == url.host();
+}
+
 void SidePanelControllerViews::DidFinishNavigation(
     content::NavigationHandle* navigation_handle) {
   // Check the actual navigation entry of the page, this is more proper than the
@@ -104,11 +129,13 @@
     entry = tab_->GetContents()->GetController().GetVisibleEntry();
   }
 
-  if (CanShowOnURL(entry->GetURL())) {
+  const GURL& url = entry->GetURL();
+  if (CanShowOnURL(url)) {
     CreateAndRegisterEntry();
     if (customize_chrome_ui_) {
       customize_chrome_ui_->AttachedTabStateUpdated(
-          NewTabPageUI::IsNewTabPageOrigin(entry->GetURL()));
+          NewTabPageUI::IsNewTabPageOrigin(url));
+      customize_chrome_ui_->UpdateThemeEditable(ShouldEnableEditTheme(url));
     }
   } else {
     DeregisterEntry();
@@ -212,8 +239,10 @@
   if (!entry) {
     entry = tab_->GetContents()->GetController().GetVisibleEntry();
   }
+  const GURL& url = entry->GetURL();
   customize_chrome_ui_->AttachedTabStateUpdated(
-      NewTabPageUI::IsNewTabPageOrigin(entry->GetURL()));
+      NewTabPageUI::IsNewTabPageOrigin(url));
+  customize_chrome_ui_->UpdateThemeEditable(ShouldEnableEditTheme(url));
 
   return customize_chrome_web_view;
 }
diff --git a/chrome/browser/ui/views/side_panel/customize_chrome/side_panel_controller_views.h b/chrome/browser/ui/views/side_panel/customize_chrome/side_panel_controller_views.h
index 97e883d..7ec206b1 100644
--- a/chrome/browser/ui/views/side_panel/customize_chrome/side_panel_controller_views.h
+++ b/chrome/browser/ui/views/side_panel/customize_chrome/side_panel_controller_views.h
@@ -63,6 +63,12 @@
   // Currently this limits to the New Tab Page only.
   bool CanShowOnURL(const GURL& url) const;
 
+  // Returns true for 1P NTP or extension NTP, otherwise returns false.
+  bool ShouldEnableEditTheme(const GURL& url) const;
+
+  // Helper function to check if the URL belongs to an extension NTP.
+  bool IsExtensionNtp(const GURL& url) const;
+
   // Generates the view for the SidePanel contents. This is the WebUI for the
   // SidePanel. Used by the SidepanelRegistry to create the view.
   std::unique_ptr<views::View> CreateCustomizeChromeWebView(
diff --git a/chrome/browser/ui/views/side_panel/read_anything/read_anything_side_panel_controller.cc b/chrome/browser/ui/views/side_panel/read_anything/read_anything_side_panel_controller.cc
index 9a2cd855..6dfd65f 100644
--- a/chrome/browser/ui/views/side_panel/read_anything/read_anything_side_panel_controller.cc
+++ b/chrome/browser/ui/views/side_panel/read_anything/read_anything_side_panel_controller.cc
@@ -185,6 +185,8 @@
     return;
   }
 
+  observers_.Notify(
+      &ReadAnythingSidePanelController::Observer::OnTabWillDetach);
   auto* coordinator =
       tab_->GetBrowserWindowInterface()->GetFeatures().side_panel_coordinator();
   // TODO(https://crbug.com/360163254): BrowserWithTestWindowTest currently does
diff --git a/chrome/browser/ui/views/side_panel/read_anything/read_anything_side_panel_controller.h b/chrome/browser/ui/views/side_panel/read_anything/read_anything_side_panel_controller.h
index 6b0c717f..42b14cc 100644
--- a/chrome/browser/ui/views/side_panel/read_anything/read_anything_side_panel_controller.h
+++ b/chrome/browser/ui/views/side_panel/read_anything/read_anything_side_panel_controller.h
@@ -63,6 +63,7 @@
    public:
     virtual void Activate(bool active) {}
     virtual void OnSidePanelControllerDestroyed() = 0;
+    virtual void OnTabWillDetach() = 0;
   };
   ReadAnythingSidePanelController(tabs::TabInterface* tab,
                                   SidePanelRegistry* side_panel_registry);
diff --git a/chrome/browser/ui/views/side_panel/read_anything/read_anything_side_panel_controller_interactive_uitest.cc b/chrome/browser/ui/views/side_panel/read_anything/read_anything_side_panel_controller_interactive_uitest.cc
index 5cce7902..cd6ac70 100644
--- a/chrome/browser/ui/views/side_panel/read_anything/read_anything_side_panel_controller_interactive_uitest.cc
+++ b/chrome/browser/ui/views/side_panel/read_anything/read_anything_side_panel_controller_interactive_uitest.cc
@@ -25,6 +25,7 @@
  public:
   MOCK_METHOD(void, Activate, (bool active), (override));
   MOCK_METHOD(void, OnSidePanelControllerDestroyed, (), (override));
+  MOCK_METHOD(void, OnTabWillDetach, (), (override));
 };
 
 class ReadAnythingSidePanelControllerTest : public InProcessBrowserTest {
@@ -91,6 +92,14 @@
   side_panel_controller()->OnEntryHidden(entry);
 }
 
+IN_PROC_BROWSER_TEST_F(ReadAnythingSidePanelControllerTest,
+                       TabWillDetach_NotfiyObservers) {
+  AddObserver(&side_panel_controller_observer_);
+
+  EXPECT_CALL(side_panel_controller_observer_, OnTabWillDetach()).Times(1);
+  browser()->GetActiveTabInterface()->Close();
+}
+
 class ReadAnythingCUJTest : public InteractiveFeaturePromoTest {
  public:
   template <typename... Args>
diff --git a/chrome/browser/ui/views/task_manager_view.cc b/chrome/browser/ui/views/task_manager_view.cc
index 394a298d..f3dbcf1 100644
--- a/chrome/browser/ui/views/task_manager_view.cc
+++ b/chrome/browser/ui/views/task_manager_view.cc
@@ -347,7 +347,6 @@
 }
 
 void TaskManagerView::SearchBarOnInputChanged(std::u16string_view query) {
-  // TODO(zhzhliu): Clean up tab disabled related code.
   search_terms_ = query;
   PerformFilter(
       kTabDefinitions[tabs_->GetSelectedTabIndex()].associated_category);
diff --git a/chrome/browser/ui/views/webid/account_selection_bubble_view.cc b/chrome/browser/ui/views/webid/account_selection_bubble_view.cc
index 79ed6f59..c5bf11a 100644
--- a/chrome/browser/ui/views/webid/account_selection_bubble_view.cc
+++ b/chrome/browser/ui/views/webid/account_selection_bubble_view.cc
@@ -204,8 +204,7 @@
   SetLayoutManager(std::make_unique<views::BoxLayout>(
       views::BoxLayout::Orientation::kVertical, gfx::Insets(),
       kTopBottomPadding));
-  header_view_ =
-      AddChildView(CreateHeaderView(/*has_idp_icon=*/idp_title.has_value()));
+  header_view_ = AddChildView(CreateHeaderView());
 }
 
 AccountSelectionBubbleView::~AccountSelectionBubbleView() = default;
@@ -213,20 +212,19 @@
 void AccountSelectionBubbleView::ShowMultiAccountPicker(
     const std::vector<IdentityRequestAccountPtr>& accounts,
     const std::vector<IdentityProviderDataPtr>& idp_list,
+    const gfx::Image& rp_icon,
     bool show_back_button) {
-  // If there are multiple IDPs, then the content::IdentityProviderMetadata
-  // passed will be unused since there will be no `header_icon_view_`.
-  // Therefore, it is fine to pass the first one into UpdateHeader().
-  DCHECK(idp_list.size() == 1u || !header_icon_view_);
-  std::u16string title =
-      GetTitle(rp_for_display_,
-               idp_list.size() > 1u
-                   ? std::nullopt
+  bool is_multi_idp = idp_list.size() > 1u;
+  std::u16string title = GetTitle(
+      rp_for_display_,
+      is_multi_idp ? std::nullopt
                    : std::make_optional<std::u16string>(
                          base::UTF8ToUTF16(idp_list[0]->idp_for_display)),
-               rp_context_);
-  UpdateHeader(idp_list[0]->idp_metadata.brand_decoded_icon, title,
-               show_back_button);
+      rp_context_);
+  UpdateHeader(
+      is_multi_idp ? rp_icon : idp_list[0]->idp_metadata.brand_decoded_icon,
+      title, show_back_button,
+      /*should_circle_crop_header_icon=*/!is_multi_idp);
 
   RemoveNonHeaderChildViews();
   AddSeparatorAndMultipleAccountChooser(accounts, idp_list);
@@ -239,7 +237,8 @@
     const std::u16string& title) {
   UpdateHeader(account->identity_provider->idp_metadata.brand_decoded_icon,
                title,
-               /*show_back_button=*/false);
+               /*show_back_button=*/false,
+               /*should_circle_crop_header_icon=*/true);
 
   RemoveNonHeaderChildViews();
   views::ProgressBar* const progress_bar =
@@ -269,7 +268,8 @@
                base::UTF8ToUTF16(account->identity_provider->idp_for_display),
                rp_context_);
   UpdateHeader(account->identity_provider->idp_metadata.brand_decoded_icon,
-               title, show_back_button);
+               title, show_back_button,
+               /*should_circle_crop_header_icon=*/true);
 
   RemoveNonHeaderChildViews();
   AddChildView(std::make_unique<views::Separator>());
@@ -283,7 +283,8 @@
     const content::IdentityProviderMetadata& idp_metadata) {
   UpdateHeader(idp_metadata.brand_decoded_icon,
                GetTitle(rp_for_display_, idp_for_display, rp_context_),
-               /*show_back_button=*/false);
+               /*show_back_button=*/false,
+               /*should_circle_crop_header_icon=*/true);
 
   RemoveNonHeaderChildViews();
   AddChildView(std::make_unique<views::Separator>());
@@ -328,7 +329,8 @@
   std::u16string title =
       GetTitle(rp_for_display_, idp_for_display, rp_context_);
   UpdateHeader(idp_metadata.brand_decoded_icon, title,
-               /*show_back_button=*/false);
+               /*show_back_button=*/false,
+               /*should_circle_crop_header_icon=*/true);
 
   RemoveNonHeaderChildViews();
   AddChildView(std::make_unique<views::Separator>());
@@ -459,8 +461,7 @@
   return bubble_bounds;
 }
 
-std::unique_ptr<views::View> AccountSelectionBubbleView::CreateHeaderView(
-    bool has_idp_icon) {
+std::unique_ptr<views::View> AccountSelectionBubbleView::CreateHeaderView() {
   auto header = std::make_unique<views::View>();
   // Do not use a top margin as it has already been set in the bubble.
   header->SetLayoutManager(std::make_unique<views::FlexLayout>())
@@ -468,14 +469,10 @@
           0, kLeftRightPadding, kVerticalSpacing, kLeftRightPadding));
 
   // Add the space for the icon.
-  if (has_idp_icon) {
-    auto image_view = std::make_unique<BrandIconImageView>(
-        kBubbleIdpIconSize, /*should_circle_crop=*/true);
-    image_view->SetImageSize(gfx::Size(kBubbleIdpIconSize, kBubbleIdpIconSize));
-    image_view->SetProperty(views::kMarginsKey,
-                            gfx::Insets().set_right(kLeftRightPadding));
-    header_icon_view_ = header->AddChildView(std::move(image_view));
-  }
+  auto image_view = std::make_unique<BrandIconImageView>(kBubbleIdpIconSize);
+  image_view->SetProperty(views::kMarginsKey,
+                          gfx::Insets().set_right(kLeftRightPadding));
+  header_icon_view_ = header->AddChildView(std::move(image_view));
 
   back_button_ =
       header->AddChildView(views::CreateVectorImageButtonWithNativeTheme(
@@ -487,15 +484,13 @@
   back_button_->SetVisible(false);
 
   int back_button_right_margin = kLeftRightPadding;
-  if (header_icon_view_) {
-    // Set the right margin of the back button so that the back button and
-    // the IDP brand icon have the same width. This ensures that the header
-    // title does not shift when the user navigates to the consent screen.
-    back_button_right_margin =
-        std::max(0, back_button_right_margin +
-                        header_icon_view_->GetPreferredSize().width() -
-                        back_button_->GetPreferredSize().width());
-  }
+  // Set the right margin of the back button so that the back button and
+  // the IDP brand icon have the same width. This ensures that the header
+  // title does not shift when the user navigates to the consent screen.
+  back_button_right_margin =
+      std::max(0, back_button_right_margin +
+                      header_icon_view_->GetPreferredSize().width() -
+                      back_button_->GetPreferredSize().width());
   back_button_->SetProperty(views::kMarginsKey,
                             gfx::Insets().set_right(back_button_right_margin));
 
@@ -681,11 +676,10 @@
 std::unique_ptr<views::View> AccountSelectionBubbleView::CreateMultiIdpLoginRow(
     const std::u16string& idp_for_display,
     const IdentityProviderDataPtr& idp_data) {
-  auto image_view = std::make_unique<BrandIconImageView>(
-      kMultiIdpIconSize, /*should_circle_crop=*/true);
-  image_view->SetImageSize(gfx::Size(kMultiIdpIconSize, kMultiIdpIconSize));
+  auto image_view = std::make_unique<BrandIconImageView>(kMultiIdpIconSize);
   image_view->SetVisible(!idp_data->idp_metadata.brand_decoded_icon.IsEmpty());
-  image_view->CropAndSetImage(idp_data->idp_metadata.brand_decoded_icon);
+  image_view->SetBrandIconImage(idp_data->idp_metadata.brand_decoded_icon,
+                                /*should_circle_crop=*/true);
 
   auto button = std::make_unique<HoverButton>(
       base::BindRepeating(&FedCmAccountSelectionView::OnLoginToIdP,
@@ -724,21 +718,22 @@
   return button;
 }
 
-void AccountSelectionBubbleView::UpdateHeader(const gfx::Image& idp_image,
-                                              const std::u16string& title,
-                                              bool show_back_button) {
+void AccountSelectionBubbleView::UpdateHeader(
+    const gfx::Image& idp_image,
+    const std::u16string& title,
+    bool show_back_button,
+    bool should_circle_crop_header_icon) {
   back_button_->SetVisible(show_back_button);
-  if (header_icon_view_) {
-    // The back button takes the place of the brand icon, if it is shown. By
-    // default, we show placeholder brand icon prior to brand icon being fetched
-    // so that header text wrapping does not change when brand icon is fetched.
-    // Therefore, we need to hide the brand icon if the image is empty.
-    if (show_back_button || idp_image.IsEmpty()) {
-      header_icon_view_->SetVisible(false);
-    } else {
-      header_icon_view_->CropAndSetImage(idp_image);
-      header_icon_view_->SetVisible(true);
-    }
+  // The back button takes the place of the brand icon, if it is shown. By
+  // default, we show placeholder brand icon prior to brand icon being fetched
+  // so that header text wrapping does not change when brand icon is fetched.
+  // Therefore, we need to hide the brand icon if the image is empty.
+  if (show_back_button || idp_image.IsEmpty()) {
+    header_icon_view_->SetVisible(false);
+  } else {
+    header_icon_view_->SetBrandIconImage(idp_image,
+                                         should_circle_crop_header_icon);
+    header_icon_view_->SetVisible(true);
   }
   if (title.compare(title_) != 0) {
     title_ = title;
diff --git a/chrome/browser/ui/views/webid/account_selection_bubble_view.h b/chrome/browser/ui/views/webid/account_selection_bubble_view.h
index d0ae543..ab343b8 100644
--- a/chrome/browser/ui/views/webid/account_selection_bubble_view.h
+++ b/chrome/browser/ui/views/webid/account_selection_bubble_view.h
@@ -45,6 +45,7 @@
   void ShowMultiAccountPicker(
       const std::vector<IdentityRequestAccountPtr>& accounts,
       const std::vector<IdentityProviderDataPtr>& idp_list,
+      const gfx::Image& rp_icon,
       bool show_back_button) override;
   void ShowVerifyingSheet(const IdentityRequestAccountPtr& account,
                           const std::u16string& title) override;
@@ -72,9 +73,8 @@
   FRIEND_TEST_ALL_PREFIXES(AccountSelectionBubbleViewTest,
                            WebContentsLargeEnoughToFitDialog);
 
-  // Returns a View containing the logo of the identity provider. Creates the
-  // `header_icon_view_` if `has_idp_icon` is true.
-  std::unique_ptr<views::View> CreateHeaderView(bool has_idp_icon);
+  // Returns a View containing the logo of the identity provider.
+  std::unique_ptr<views::View> CreateHeaderView();
 
   // Returns a View for single account chooser. It contains the account
   // information, disclosure text and a button for the user to confirm the
@@ -113,10 +113,14 @@
 
   // Updates the header title, the header icon visibility and the header back
   // button visibiltiy. `idp_image` is not empty when we need to set a header
-  // image based on the IDP.
+  // image based on the IDP. `should_circle_crop_header_icon` determines whether
+  // the icon passed should be cropped or not. Some icons like the RP icon are
+  // not meant to be cropped, and some icons like the badged account icon are
+  // cropped on the backend, so they should not be cropped here.
   void UpdateHeader(const gfx::Image& idp_image,
                     const std::u16string& title,
-                    bool show_back_button);
+                    bool show_back_button,
+                    bool should_circle_crop_header_icon);
 
   // Removes all children except for `header_view_`.
   void RemoveNonHeaderChildViews();
diff --git a/chrome/browser/ui/views/webid/account_selection_bubble_view_unittest.cc b/chrome/browser/ui/views/webid/account_selection_bubble_view_unittest.cc
index 5559f66..a8199e1 100644
--- a/chrome/browser/ui/views/webid/account_selection_bubble_view_unittest.cc
+++ b/chrome/browser/ui/views/webid/account_selection_bubble_view_unittest.cc
@@ -150,9 +150,10 @@
     if (idp_list.empty()) {
       idp_list = {idp_data_};
     }
-    account_selection_view_->Show(kTopFrameEtldPlusOne, idp_list, accounts_,
-                                  Account::SignInMode::kExplicit,
-                                  blink::mojom::RpMode::kPassive, new_accounts);
+    account_selection_view_->Show(
+        content::RelyingPartyData(kTopFrameEtldPlusOne), idp_list, accounts_,
+        Account::SignInMode::kExplicit, blink::mojom::RpMode::kPassive,
+        new_accounts);
     dialog_ = static_cast<AccountSelectionBubbleView*>(
         account_selection_view_->account_selection_view());
   }
@@ -176,6 +177,7 @@
 
     CreateAccountSelectionBubble();
     dialog_->ShowMultiAccountPicker(account_list, {idp_data_},
+                                    /*rp_icon=*/gfx::Image(),
                                     /*show_back_button=*/false);
   }
 
@@ -190,7 +192,7 @@
 
   void PerformHeaderChecks(views::View* header,
                            const std::u16string& expected_title,
-                           bool expect_idp_brand_icon_in_header) {
+                           bool expected_icon_visibility) {
     // Perform some basic dialog checks.
     EXPECT_FALSE(dialog()->ShouldShowCloseButton());
     EXPECT_FALSE(dialog()->ShouldShowWindowTitle());
@@ -202,10 +204,8 @@
     // title, close button.
     std::vector<std::string> expected_class_names = {"ImageButton", "Label",
                                                      "ImageButton"};
-    if (expect_idp_brand_icon_in_header) {
-      expected_class_names.insert(expected_class_names.begin(),
-                                  "BrandIconImageView");
-    }
+    expected_class_names.insert(expected_class_names.begin(),
+                                "BrandIconImageView");
     EXPECT_THAT(GetChildClassNames(header),
                 testing::ElementsAreArray(expected_class_names));
 
@@ -215,12 +215,10 @@
     ASSERT_TRUE(title_view);
     EXPECT_EQ(title_view->GetText(), expected_title);
 
-    if (expect_idp_brand_icon_in_header) {
-      views::ImageView* idp_brand_icon = static_cast<views::ImageView*>(
-          GetViewWithClassName(header, "BrandIconImageView"));
-      ASSERT_TRUE(idp_brand_icon);
-      EXPECT_TRUE(idp_brand_icon->GetVisible());
-    }
+    views::ImageView* idp_brand_icon = static_cast<views::ImageView*>(
+        GetViewWithClassName(header, "BrandIconImageView"));
+    ASSERT_TRUE(idp_brand_icon);
+    EXPECT_EQ(idp_brand_icon->GetVisible(), expected_icon_visibility);
   }
 
   void PerformMultiAccountChecks(views::View* container,
@@ -279,14 +277,13 @@
   }
 
   void TestSingleAccount(const std::u16string expected_title,
-                         bool expect_idp_brand_icon_in_header) {
+                         bool expected_icon_visibility) {
     CreateAndShowSingleAccountPicker();
 
     std::vector<raw_ptr<views::View, VectorExperimental>> children =
         dialog()->children();
     ASSERT_EQ(children.size(), 3u);
-    PerformHeaderChecks(children[0], expected_title,
-                        expect_idp_brand_icon_in_header);
+    PerformHeaderChecks(children[0], expected_title, expected_icon_visibility);
     EXPECT_TRUE(IsViewClass<views::Separator>(children[1]));
 
     views::View* single_account_chooser = children[2];
@@ -309,7 +306,7 @@
   }
 
   void TestMultipleAccounts(const std::u16string& expected_title,
-                            bool expect_idp_brand_icon_in_header) {
+                            bool expected_icon_visibility) {
     const std::vector<std::string> kAccountSuffixes = {"0", "1", "2"};
     CreateAndShowMultiAccountPicker(kAccountSuffixes);
 
@@ -317,8 +314,7 @@
         dialog()->children();
     // The separator is in the multiple accounts container.
     ASSERT_EQ(children.size(), 2u);
-    PerformHeaderChecks(children[0], expected_title,
-                        expect_idp_brand_icon_in_header);
+    PerformHeaderChecks(children[0], expected_title, expected_icon_visibility);
 
     PerformMultiAccountChecks(children[1], /*expected_account_rows=*/3,
                               /*expected_login_rows=*/0);
@@ -333,7 +329,7 @@
   }
 
   void TestFailureDialog(const std::u16string expected_title,
-                         bool expect_idp_brand_icon_in_header) {
+                         bool expected_icon_visibility) {
     CreateAccountSelectionBubble();
     dialog_->ShowFailureDialog(kIdpETLDPlusOne, idp_data_->idp_metadata);
 
@@ -341,8 +337,7 @@
         dialog()->children();
     ASSERT_EQ(children.size(), 3u);
 
-    PerformHeaderChecks(children[0], expected_title,
-                        expect_idp_brand_icon_in_header);
+    PerformHeaderChecks(children[0], expected_title, expected_icon_visibility);
     EXPECT_TRUE(IsViewClass<views::Separator>(children[1]));
 
     const views::View* failure_dialog = children[2];
@@ -368,7 +363,7 @@
   void TestErrorDialog(const std::u16string expected_title,
                        const std::u16string expected_summary,
                        const std::u16string expected_description,
-                       bool expect_idp_brand_icon_in_header,
+                       bool expected_icon_visibility,
                        const std::string& error_code,
                        const GURL& error_url) {
     CreateAccountSelectionBubble();
@@ -380,8 +375,7 @@
         dialog()->children();
     ASSERT_EQ(children.size(), 4u);
 
-    PerformHeaderChecks(children[0], expected_title,
-                        expect_idp_brand_icon_in_header);
+    PerformHeaderChecks(children[0], expected_title, expected_icon_visibility);
     EXPECT_TRUE(IsViewClass<views::Separator>(children[1]));
 
     const views::View* error_dialog = children[2];
@@ -515,7 +509,7 @@
 
 TEST_F(AccountSelectionBubbleViewTest, SingleAccount) {
   TestSingleAccount(kTitleSignIn,
-                    /*expect_idp_brand_icon_in_header=*/true);
+                    /*expected_icon_visibility=*/true);
 }
 
 TEST_F(AccountSelectionBubbleViewTest, SingleAccountNoTermsOfService) {
@@ -526,7 +520,7 @@
       dialog()->children();
   ASSERT_EQ(children.size(), 3u);
   PerformHeaderChecks(children[0], kTitleSignIn,
-                      /*expect_idp_brand_icon_in_header=*/true);
+                      /*expected_icon_visibility=*/true);
   EXPECT_TRUE(IsViewClass<views::Separator>(children[1]));
 
   views::View* single_account_chooser = children[2];
@@ -556,7 +550,7 @@
       dialog()->children();
   ASSERT_EQ(children.size(), 3u);
   PerformHeaderChecks(children[0], kTitleSignIn,
-                      /*expect_idp_brand_icon_in_header=*/true);
+                      /*expected_icon_visibility=*/true);
   EXPECT_TRUE(IsViewClass<views::Separator>(children[1]));
 
   views::View* single_account_chooser = children[2];
@@ -581,7 +575,7 @@
 
 TEST_F(AccountSelectionBubbleViewTest, MultipleAccounts) {
   TestMultipleAccounts(kTitleSignIn,
-                       /*expect_idp_brand_icon_in_header=*/true);
+                       /*expected_icon_visibility=*/true);
 }
 
 TEST_F(AccountSelectionBubbleViewTest, UseDifferentAccountNotSupported) {
@@ -593,7 +587,7 @@
       dialog()->children();
   ASSERT_EQ(children.size(), 2u);
   PerformHeaderChecks(children[0], kTitleSignIn,
-                      /*expect_idp_brand_icon_in_header=*/true);
+                      /*expected_icon_visibility=*/true);
 
   PerformMultiAccountChecks(children[1], /*expected_account_rows=*/2,
                             /*expected_login_rows=*/0);
@@ -606,7 +600,7 @@
       dialog()->children();
   ASSERT_EQ(children.size(), 3u);
   PerformHeaderChecks(children[0], kTitleSignIn,
-                      /*expect_idp_brand_icon_in_header=*/true);
+                      /*expected_icon_visibility=*/true);
   EXPECT_TRUE(IsViewClass<views::Separator>(children[1]));
 
   views::View* single_account_chooser = children[2];
@@ -633,7 +627,7 @@
       dialog()->children();
   ASSERT_EQ(children.size(), 3u);
   PerformHeaderChecks(children[0], kTitleSignIn,
-                      /*expect_idp_brand_icon_in_header=*/true);
+                      /*expected_icon_visibility=*/true);
   EXPECT_TRUE(IsViewClass<views::Separator>(children[1]));
 
   views::View* single_account_chooser = children[2];
@@ -734,7 +728,7 @@
       dialog()->children();
   ASSERT_EQ(children.size(), 3u);
   PerformHeaderChecks(children[0], kTitleSigningIn,
-                      /*expect_idp_brand_icon_in_header=*/true);
+                      /*expected_icon_visibility=*/true);
   EXPECT_TRUE(IsViewClass<views::ProgressBar>(children[1]));
 
   views::View* row_container = dialog()->children()[2];
@@ -754,7 +748,7 @@
       dialog()->children();
   ASSERT_EQ(children.size(), 3u);
   PerformHeaderChecks(children[0], kTitleSigningInWithAutoReauthn,
-                      /*expect_idp_brand_icon_in_header=*/true);
+                      /*expected_icon_visibility=*/true);
   EXPECT_TRUE(IsViewClass<views::ProgressBar>(children[1]));
 
   views::View* row_container = dialog()->children()[2];
@@ -763,7 +757,7 @@
 }
 
 TEST_F(AccountSelectionBubbleViewTest, Failure) {
-  TestFailureDialog(kTitleSignIn, /*expect_idp_brand_icon_in_header=*/true);
+  TestFailureDialog(kTitleSignIn, /*expected_icon_visibility=*/true);
 }
 
 class MultipleIdpAccountSelectionBubbleViewTest
@@ -787,7 +781,7 @@
 // AccountSelectionBubbleViewTest's SingleAccount test.
 TEST_F(MultipleIdpAccountSelectionBubbleViewTest, SingleAccount) {
   TestSingleAccount(kTitleSignIn,
-                    /*expect_idp_brand_icon_in_header=*/true);
+                    /*expected_icon_visibility=*/true);
 }
 
 // Tests that when there is multiple accounts but only one IDP, the UI is
@@ -795,7 +789,7 @@
 // AccountSelectionBubbleViewTest's MultipleAccounts test).
 TEST_F(MultipleIdpAccountSelectionBubbleViewTest, MultipleAccountsSingleIdp) {
   TestMultipleAccounts(kTitleSignIn,
-                       /*expect_idp_brand_icon_in_header=*/true);
+                       /*expected_icon_visibility=*/true);
 }
 
 // Tests that the logo is visible with features::kFedCmMultipleIdentityProviders
@@ -826,7 +820,7 @@
       dialog()->children();
   ASSERT_EQ(children.size(), 2u);
   PerformHeaderChecks(children[0], kTitleSignInWithoutIdp,
-                      /*expect_idp_brand_icon_in_header=*/false);
+                      /*expected_icon_visibility=*/false);
 
   views::View* accounts_container = children[1];
   PerformMultiAccountChecks(accounts_container, /*expected_account_rows=*/4,
@@ -866,7 +860,7 @@
       dialog()->children();
   ASSERT_EQ(children.size(), 2u);
   PerformHeaderChecks(children[0], kTitleSignInWithoutIdp,
-                      /*expect_idp_brand_icon_in_header=*/false);
+                      /*expected_icon_visibility=*/false);
 
   PerformMultiAccountChecks(children[1], /*expected_account_rows=*/2,
                             /*expected_login_rows=*/1);
@@ -912,7 +906,7 @@
       dialog()->children();
   ASSERT_EQ(children.size(), 2u);
   PerformHeaderChecks(children[0], kTitleSignInWithoutIdp,
-                      /*expect_idp_brand_icon_in_header=*/false);
+                      /*expected_icon_visibility=*/false);
 
   PerformMultiAccountChecks(children[1], /*expected_account_rows=*/3,
                             /*expected_login_rows=*/0);
@@ -965,7 +959,7 @@
       dialog()->children();
   ASSERT_EQ(children.size(), 2u);
   PerformHeaderChecks(children[0], kTitleSignInWithoutIdp,
-                      /*expect_idp_brand_icon_in_header=*/false);
+                      /*expected_icon_visibility=*/false);
 
   views::View* wrapper = children[1];
 
@@ -1008,7 +1002,7 @@
       dialog()->children();
   ASSERT_EQ(children.size(), 2u);
   PerformHeaderChecks(children[0], kTitleSignInWithoutIdp,
-                      /*expect_idp_brand_icon_in_header=*/false);
+                      /*expected_icon_visibility=*/false);
 
   PerformMultiAccountChecks(children[1], /*expected_account_rows=*/0,
                             /*expected_login_rows=*/2);
@@ -1049,7 +1043,7 @@
       dialog()->children();
   ASSERT_EQ(children.size(), 2u);
   PerformHeaderChecks(children[0], kTitleSignInWithoutIdp,
-                      /*expect_idp_brand_icon_in_header=*/false);
+                      /*expected_icon_visibility=*/false);
 
   PerformMultiAccountChecks(children[1], /*expected_account_rows=*/4,
                             /*expected_login_rows=*/0);
@@ -1106,7 +1100,7 @@
   ASSERT_EQ(children.size(), 2u);
   // The multiple account chooser container includes the separator.
   PerformHeaderChecks(children[0], kTitleSignInWithoutIdp,
-                      /*expect_idp_brand_icon_in_header=*/false);
+                      /*expected_icon_visibility=*/false);
 
   PerformMultiAccountChecks(children[1], /*expected_account_rows=*/6,
                             /*expected_login_rows=*/0);
@@ -1124,7 +1118,7 @@
 TEST_F(AccountSelectionBubbleViewTest, GenericError) {
   TestErrorDialog(kTitleSignIn, u"Can't continue with idp-example.com",
                   u"Something went wrong",
-                  /*expect_idp_brand_icon_in_header=*/true,
+                  /*expected_icon_visibility=*/true,
                   /*error_code=*/"",
                   /*error_url=*/GURL());
 }
@@ -1132,7 +1126,7 @@
 TEST_F(AccountSelectionBubbleViewTest, GenericErrorWithErrorUrl) {
   TestErrorDialog(kTitleSignIn, u"Can't continue with idp-example.com",
                   u"Something went wrong",
-                  /*expect_idp_brand_icon_in_header=*/true,
+                  /*expected_icon_visibility=*/true,
                   /*error_code=*/"",
                   GURL(u"https://idp-example.com/more-details"));
 }
@@ -1143,7 +1137,7 @@
                   u"rp-example.com can't continue using idp-example.com",
                   u"This option is unavailable right now. You can try other "
                   u"ways to continue on rp-example.com.",
-                  /*expect_idp_brand_icon_in_header=*/true,
+                  /*expected_icon_visibility=*/true,
                   /*error_code=*/"invalid_request",
                   /*error_url=*/GURL());
 
@@ -1152,7 +1146,7 @@
       kTitleSignIn, u"rp-example.com can't continue using idp-example.com",
       u"This option is unavailable right now. Choose \"More "
       u"details\" below to get more information from idp-example.com.",
-      /*expect_idp_brand_icon_in_header=*/true,
+      /*expected_icon_visibility=*/true,
       /*error_code=*/"invalid_request",
       GURL(u"https://idp-example.com/more-details"));
 
@@ -1161,7 +1155,7 @@
                   u"rp-example.com can't continue using idp-example.com",
                   u"This option is unavailable right now. You can try other "
                   u"ways to continue on rp-example.com.",
-                  /*expect_idp_brand_icon_in_header=*/true,
+                  /*expected_icon_visibility=*/true,
                   /*error_code=*/"unauthorized_client",
                   /*error_url=*/GURL());
 
@@ -1170,7 +1164,7 @@
       kTitleSignIn, u"rp-example.com can't continue using idp-example.com",
       u"This option is unavailable right now. Choose \"More "
       u"details\" below to get more information from idp-example.com.",
-      /*expect_idp_brand_icon_in_header=*/true,
+      /*expected_icon_visibility=*/true,
       /*error_code=*/"unauthorized_client",
       GURL(u"https://idp-example.com/more-details"));
 
@@ -1178,7 +1172,7 @@
   TestErrorDialog(kTitleSignIn, u"Check that you chose the right account",
                   u"Check if the selected account is supported. You can try "
                   u"other ways to continue on rp-example.com.",
-                  /*expect_idp_brand_icon_in_header=*/true,
+                  /*expected_icon_visibility=*/true,
                   /*error_code=*/"access_denied",
                   /*error_url=*/GURL());
 
@@ -1187,7 +1181,7 @@
       kTitleSignIn, u"Check that you chose the right account",
       u"Check if the selected account is supported. Choose \"More "
       u"details\" below to get more information from idp-example.com.",
-      /*expect_idp_brand_icon_in_header=*/true,
+      /*expected_icon_visibility=*/true,
       /*error_code=*/"access_denied",
       GURL(u"https://idp-example.com/more-details"));
 
@@ -1196,7 +1190,7 @@
                   u"idp-example.com isn't available right now. If this issue "
                   u"keeps happening, you can try other ways to continue on "
                   u"rp-example.com.",
-                  /*expect_idp_brand_icon_in_header=*/true,
+                  /*expected_icon_visibility=*/true,
                   /*error_code=*/"temporarily_unavailable",
                   /*error_url=*/GURL());
 
@@ -1205,7 +1199,7 @@
                   u"idp-example.com isn't available right now. If this issue "
                   u"keeps happening, choose \"More details\" below to get more "
                   u"information from idp-example.com.",
-                  /*expect_idp_brand_icon_in_header=*/true,
+                  /*expected_icon_visibility=*/true,
                   /*error_code=*/"temporarily_unavailable",
                   GURL(u"https://idp-example.com/more-details"));
 
@@ -1213,7 +1207,7 @@
   TestErrorDialog(kTitleSignIn, u"Check your internet connection",
                   u"If you're online but this issue keeps happening, you can "
                   u"try other ways to continue on rp-example.com.",
-                  /*expect_idp_brand_icon_in_header=*/true,
+                  /*expected_icon_visibility=*/true,
                   /*error_code=*/"server_error",
                   /*error_url=*/GURL());
 
@@ -1221,21 +1215,21 @@
   TestErrorDialog(kTitleSignIn, u"Check your internet connection",
                   u"If you're online but this issue keeps happening, you can "
                   u"try other ways to continue on rp-example.com.",
-                  /*expect_idp_brand_icon_in_header=*/true,
+                  /*expected_icon_visibility=*/true,
                   /*error_code=*/"server_error",
                   GURL(u"https://idp-example.com/more-details"));
 
   // Error not in our predefined list without error URL
   TestErrorDialog(kTitleSignIn, u"Can't continue with idp-example.com",
                   u"Something went wrong",
-                  /*expect_idp_brand_icon_in_header=*/true,
+                  /*expected_icon_visibility=*/true,
                   /*error_code=*/"error_we_dont_support",
                   /*error_url=*/GURL());
 
   // Error not in our predefined list with error URL
   TestErrorDialog(kTitleSignIn, u"Can't continue with idp-example.com",
                   u"Something went wrong",
-                  /*expect_idp_brand_icon_in_header=*/true,
+                  /*expected_icon_visibility=*/true,
                   /*error_code=*/"error_we_dont_support",
                   GURL(u"https://idp-example.com/more-details"));
 }
@@ -1262,6 +1256,7 @@
   // The backend will invoke ShowMultiAccountPicker with a single account since
   // there are filtered out accounts.
   dialog_->ShowMultiAccountPicker({account}, {idp_data_},
+                                  /*rp_icon=*/gfx::Image(),
                                   /*show_back_button=*/false);
 
   std::vector<raw_ptr<views::View, VectorExperimental>> children =
@@ -1269,7 +1264,7 @@
   // The separator is in the multiple accounts container.
   ASSERT_EQ(children.size(), 2u);
   PerformHeaderChecks(children[0], kTitleSignIn,
-                      /*expect_idp_brand_icon_in_header=*/true);
+                      /*expected_icon_visibility=*/true);
 
   PerformMultiAccountChecks(children[1], /*expected_account_rows=*/1,
                             /*expected_login_rows=*/1);
@@ -1296,6 +1291,7 @@
   }
   CreateAccountSelectionBubble();
   dialog_->ShowMultiAccountPicker(accounts_list, {idp_data_},
+                                  /*rp_icon=*/gfx::Image(),
                                   /*show_back_button=*/false);
 
   std::vector<raw_ptr<views::View, VectorExperimental>> children =
@@ -1303,7 +1299,7 @@
   // The separator is in the multiple accounts container.
   ASSERT_EQ(children.size(), 2u);
   PerformHeaderChecks(children[0], kTitleSignIn,
-                      /*expect_idp_brand_icon_in_header=*/true);
+                      /*expected_icon_visibility=*/true);
 
   PerformMultiAccountChecks(children[1], /*expected_account_rows=*/3,
                             /*expected_login_rows=*/1);
@@ -1335,6 +1331,7 @@
 
   CreateAccountSelectionBubble();
   dialog_->ShowMultiAccountPicker(accounts_list, {idp_data_},
+                                  /*rp_icon=*/gfx::Image(),
                                   /*show_back_button=*/false);
 
   std::vector<raw_ptr<views::View, VectorExperimental>> children =
@@ -1342,7 +1339,7 @@
   // The separator is in the multiple accounts container.
   ASSERT_EQ(children.size(), 2u);
   PerformHeaderChecks(children[0], kTitleSignIn,
-                      /*expect_idp_brand_icon_in_header=*/true);
+                      /*expected_icon_visibility=*/true);
 
   PerformMultiAccountChecks(children[1], /*expected_account_rows=*/2,
                             /*expected_login_rows=*/1);
diff --git a/chrome/browser/ui/views/webid/account_selection_modal_view.cc b/chrome/browser/ui/views/webid/account_selection_modal_view.cc
index 2126f93..962f28f 100644
--- a/chrome/browser/ui/views/webid/account_selection_modal_view.cc
+++ b/chrome/browser/ui/views/webid/account_selection_modal_view.cc
@@ -379,6 +379,7 @@
 void AccountSelectionModalView::ShowMultiAccountPicker(
     const std::vector<IdentityRequestAccountPtr>& accounts,
     const std::vector<IdentityProviderDataPtr>& idp_list,
+    const gfx::Image& rp_icon,
     bool show_back_button) {
   DCHECK(!show_back_button);
   CHECK_EQ(idp_list.size(), 1u);
@@ -394,7 +395,10 @@
       accounts[0]->identity_provider->idp_metadata;
   // If `brand_decoded_icon` is empty, a globe icon is shown instead.
   if (!idp_metadata.brand_decoded_icon.IsEmpty()) {
-    idp_brand_icon_->CropAndSetImage(idp_metadata.brand_decoded_icon);
+    if (idp_brand_icon_->SetBrandIconImage(idp_metadata.brand_decoded_icon,
+                                           /*should_circle_crop=*/true)) {
+      OnIdpBrandIconSet();
+    }
   } else {
     idp_brand_icon_->SetImage(ui::ImageModel::FromVectorIcon(
         kWebidGlobeIcon, ui::kColorIconSecondary, kModalIdpIconSize));
@@ -576,23 +580,17 @@
   AddChildView(std::move(button_container));
 }
 
-void AccountSelectionModalView::OnIdpBrandIconFetched() {
-  if (!idp_brand_icon_) {
-    return;
-  }
+void AccountSelectionModalView::OnIdpBrandIconSet() {
   header_icon_spinner_->Stop();
-  header_icon_spinner_->SetVisible(/*visible=*/false);
-  idp_brand_icon_->SetVisible(/*visible=*/true);
+  header_icon_spinner_->SetVisible(false);
+  idp_brand_icon_->SetVisible(true);
 }
 
-void AccountSelectionModalView::OnCombinedIconsFetched() {
-  if (!combined_icons_) {
-    return;
-  }
+void AccountSelectionModalView::OnCombinedIconsSet() {
   header_icon_spinner_->Stop();
-  header_icon_spinner_->SetVisible(/*visible=*/false);
-  idp_brand_icon_->SetVisible(/*visible=*/false);
-  combined_icons_->SetVisible(/*visible=*/true);
+  header_icon_spinner_->SetVisible(false);
+  idp_brand_icon_->SetVisible(false);
+  combined_icons_->SetVisible(true);
 }
 
 void AccountSelectionModalView::ShowRequestPermissionDialog(
@@ -608,12 +606,20 @@
   if (!idp_brand_icon.IsEmpty() && !rp_brand_icon.IsEmpty()) {
     combined_icons_ =
         header_icon_view_->AddChildView(CreateCombinedIconsView());
-    combined_icons_idp_brand_icon_->CropAndSetImage(idp_brand_icon);
-    combined_icons_rp_brand_icon_->CropAndSetImage(rp_brand_icon);
+    bool idp_icon_set = combined_icons_idp_brand_icon_->SetBrandIconImage(
+        idp_brand_icon, /*should_circle_crop=*/true);
+    bool rp_icon_set = combined_icons_rp_brand_icon_->SetBrandIconImage(
+        rp_brand_icon, /*should_circle_crop=*/true);
+    if (idp_icon_set && rp_icon_set) {
+      OnCombinedIconsSet();
+    }
   } else {
     // If `idp_brand_icon` is empty, a globe icon is shown instead.
     if (!idp_brand_icon.IsEmpty()) {
-      idp_brand_icon_->CropAndSetImage(idp_brand_icon);
+      if (idp_brand_icon_->SetBrandIconImage(idp_brand_icon,
+                                             /*should_circle_crop=*/true)) {
+        OnIdpBrandIconSet();
+      }
     } else {
       idp_brand_icon_->SetImage(ui::ImageModel::FromVectorIcon(
           kWebidGlobeIcon, ui::kColorIconSecondary, kModalIdpIconSize));
@@ -734,18 +740,9 @@
 
 std::unique_ptr<views::BoxLayoutView>
 AccountSelectionModalView::CreateIdpIconView() {
-  constexpr int kNumIconsInIdpIconView = 1;
-  base::RepeatingClosure on_image_set = BarrierClosure(
-      kNumIconsInIdpIconView,
-      base::BindOnce(&AccountSelectionModalView::OnIdpBrandIconFetched,
-                     weak_ptr_factory_.GetWeakPtr()));
-
   // Create IDP brand icon image view.
-  std::unique_ptr<BrandIconImageView> idp_brand_icon_image_view =
-      std::make_unique<BrandIconImageView>(
-          kModalIdpIconSize, /*should_circle_crop=*/true, on_image_set);
-  idp_brand_icon_image_view->SetImageSize(
-      gfx::Size(kModalIdpIconSize, kModalIdpIconSize));
+  auto idp_brand_icon_image_view =
+      std::make_unique<BrandIconImageView>(kModalIdpIconSize);
   idp_brand_icon_image_view->SetVisible(/*visible=*/false);
 
   // Put IDP icon into a BoxLayout container so that it can be stacked on top of
@@ -762,19 +759,10 @@
 
 std::unique_ptr<views::BoxLayoutView>
 AccountSelectionModalView::CreateCombinedIconsView() {
-  constexpr int kNumIconsInCombinedIconsView = 2;
-  base::RepeatingClosure on_image_set = BarrierClosure(
-      kNumIconsInCombinedIconsView,
-      base::BindOnce(&AccountSelectionModalView::OnCombinedIconsFetched,
-                     weak_ptr_factory_.GetWeakPtr()));
-
   // Create IDP brand icon image view.
-  std::unique_ptr<BrandIconImageView> idp_brand_icon_image_view =
-      std::make_unique<BrandIconImageView>(
-          kModalCombinedIconSize, /*should_circle_crop=*/true, on_image_set);
+  auto idp_brand_icon_image_view =
+      std::make_unique<BrandIconImageView>(kModalCombinedIconSize);
   combined_icons_idp_brand_icon_ = idp_brand_icon_image_view.get();
-  idp_brand_icon_image_view->SetImageSize(
-      gfx::Size(kModalCombinedIconSize, kModalCombinedIconSize));
   idp_brand_icon_image_view->SetVisible(/*visible=*/true);
 
   // Create arrow icon image view.
@@ -784,12 +772,9 @@
       kWebidArrowIcon, ui::kColorIconSecondary, kModalCombinedIconSize));
 
   // Create RP brand icon image view.
-  std::unique_ptr<BrandIconImageView> rp_brand_icon_image_view =
-      std::make_unique<BrandIconImageView>(
-          kModalCombinedIconSize, /*should_circle_crop=*/true, on_image_set);
+  auto rp_brand_icon_image_view =
+      std::make_unique<BrandIconImageView>(kModalCombinedIconSize);
   combined_icons_rp_brand_icon_ = rp_brand_icon_image_view.get();
-  rp_brand_icon_image_view->SetImageSize(
-      gfx::Size(kModalCombinedIconSize, kModalCombinedIconSize));
   rp_brand_icon_image_view->SetVisible(/*visible=*/true);
 
   // Put IDP icon, arrow icon and RP icon into a BoxLayout container, in that
diff --git a/chrome/browser/ui/views/webid/account_selection_modal_view.h b/chrome/browser/ui/views/webid/account_selection_modal_view.h
index 35cd5f0b..3dc36401 100644
--- a/chrome/browser/ui/views/webid/account_selection_modal_view.h
+++ b/chrome/browser/ui/views/webid/account_selection_modal_view.h
@@ -50,6 +50,7 @@
   void ShowMultiAccountPicker(
       const std::vector<IdentityRequestAccountPtr>& accounts,
       const std::vector<IdentityProviderDataPtr>& idp_list,
+      const gfx::Image& rp_icon,
       bool show_back_button) override;
 
   void ShowVerifyingSheet(const IdentityRequestAccountPtr& account,
@@ -130,13 +131,13 @@
   // that order, horizontally.
   std::unique_ptr<views::BoxLayoutView> CreateCombinedIconsView();
 
-  // Hides `header_icon_spinner_` and shows `idp_brand_icon_` upon successful
-  // IDP icon fetch.
-  void OnIdpBrandIconFetched();
+  // Hides `header_icon_spinner_` and shows `idp_brand_icon_` upon successfully
+  // setting the IDP icon.
+  void OnIdpBrandIconSet();
 
   // Hides `header_icon_spinner_`, `idp_brand_icon_` and shows `combined_icons_`
-  // upon successful IDP and RP icon fetches.
-  void OnCombinedIconsFetched();
+  // upon successfully setting the IDP and RP icons.
+  void OnCombinedIconsSet();
 
   // Removes all child views and dangling pointers and adjust header with
   // progress bar and body label if needed.
diff --git a/chrome/browser/ui/views/webid/account_selection_modal_view_browsertest.cc b/chrome/browser/ui/views/webid/account_selection_modal_view_browsertest.cc
index f3afe56..34ae5fa 100644
--- a/chrome/browser/ui/views/webid/account_selection_modal_view_browsertest.cc
+++ b/chrome/browser/ui/views/webid/account_selection_modal_view_browsertest.cc
@@ -115,6 +115,7 @@
 
     CreateAccountSelectionModal();
     dialog_->ShowMultiAccountPicker(account_list_, {idp_data_},
+                                    /*rp_icon=*/gfx::Image(),
                                     /*show_back_button=*/false);
     account_selection_view_->UpdateDialogPosition();
   }
@@ -594,6 +595,7 @@
     }
     CreateAccountSelectionModal();
     dialog()->ShowMultiAccountPicker(account_list_, {idp_data()},
+                                     /*rp_icon=*/gfx::Image(),
                                      /*show_back_button=*/false);
     account_selection_view_->UpdateDialogPosition();
 
@@ -630,6 +632,7 @@
     account_list_[1]->is_filtered_out = true;
     CreateAccountSelectionModal();
     dialog()->ShowMultiAccountPicker(account_list_, {idp_data()},
+                                     /*rp_icon=*/gfx::Image(),
                                      /*show_back_button=*/false);
     account_selection_view_->UpdateDialogPosition();
 
diff --git a/chrome/browser/ui/views/webid/account_selection_view_base.cc b/chrome/browser/ui/views/webid/account_selection_view_base.cc
index 2127e65..7524c15 100644
--- a/chrome/browser/ui/views/webid/account_selection_view_base.cc
+++ b/chrome/browser/ui/views/webid/account_selection_view_base.cc
@@ -191,33 +191,31 @@
       kArrowIconSize));
 }
 
-BrandIconImageView::BrandIconImageView(int image_size,
-                                       bool should_circle_crop,
-                                       base::RepeatingClosure on_image_set)
-    : image_size_(image_size),
-      should_circle_crop_(should_circle_crop),
-      on_image_set_(std::move(on_image_set)) {}
+BrandIconImageView::BrandIconImageView(int image_size)
+    : image_size_(image_size) {
+  SetImageSize(gfx::Size(image_size_, image_size_));
+}
 
 BrandIconImageView::~BrandIconImageView() = default;
 
-void BrandIconImageView::CropAndSetImage(const gfx::Image& image) {
-  if (image.Width() != image.Height() ||
+bool BrandIconImageView::SetBrandIconImage(const gfx::Image& image,
+                                           bool should_circle_crop) {
+  if (image.Width() != image.Height()) {
+    return false;
+  }
+  if (should_circle_crop &&
       image.Width() < (image_size_ / kMaskableWebIconSafeZoneRatio)) {
-    return;
+    return false;
   }
   const gfx::ImageSkia& original_image = image.AsImageSkia();
   gfx::ImageSkia cropped_idp_image =
-      should_circle_crop_
+      should_circle_crop
           ? CreateCircleCroppedImage(original_image, image_size_)
           : gfx::ImageSkiaOperations::CreateResizedImage(
                 original_image, skia::ImageOperations::RESIZE_BEST,
                 gfx::Size(image_size_, image_size_));
   SetImage(ui::ImageModel::FromImageSkia(cropped_idp_image));
-
-  if (!on_image_set_) {
-    return;
-  }
-  std::move(on_image_set_).Run();
+  return true;
 }
 
 BEGIN_METADATA(BrandIconImageView)
@@ -406,12 +404,15 @@
   auto row = std::make_unique<views::View>();
   row->SetProperty(views::kElementIdentifierKey,
                    kFedCmAccountChooserDialogAccountElementId);
-  row->SetLayoutManager(std::make_unique<views::BoxLayout>(
-      views::BoxLayout::Orientation::kHorizontal,
-      gfx::Insets::VH(
-          /*vertical=*/kVerticalSpacing + additional_vertical_padding,
-          /*horizontal=*/is_modal_dialog ? kModalHorizontalSpacing : 0),
-      kLeftRightPadding));
+  std::unique_ptr<views::BoxLayout> box_layout(
+      std::make_unique<views::BoxLayout>(
+          views::BoxLayout::Orientation::kHorizontal,
+          gfx::Insets::VH(
+              /*vertical=*/kVerticalSpacing + additional_vertical_padding,
+              /*horizontal=*/is_modal_dialog ? kModalHorizontalSpacing : 0),
+          kLeftRightPadding));
+  box_layout->set_cross_axis_alignment(views::LayoutAlignment::kCenter);
+  row->SetLayoutManager(std::move(box_layout));
   row->AddChildView(std::move(account_image_view));
   views::View* const text_column =
       row->AddChildView(std::make_unique<views::View>());
@@ -426,12 +427,14 @@
   account_name->SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_LEFT);
 
   // Add account identifier.
-  views::Label* const account_identifier =
-      text_column->AddChildView(std::make_unique<views::Label>(
-          base::UTF8ToUTF16(account->display_identifier),
-          views::style::CONTEXT_DIALOG_BODY_TEXT, account_identifier_style));
-  account_identifier->SetHorizontalAlignment(
-      gfx::HorizontalAlignment::ALIGN_LEFT);
+  if (!account->display_identifier.empty()) {
+    views::Label* const account_identifier =
+        text_column->AddChildView(std::make_unique<views::Label>(
+            base::UTF8ToUTF16(account->display_identifier),
+            views::style::CONTEXT_DIALOG_BODY_TEXT, account_identifier_style));
+    account_identifier->SetHorizontalAlignment(
+        gfx::HorizontalAlignment::ALIGN_LEFT);
+  }
 
   return row;
 }
diff --git a/chrome/browser/ui/views/webid/account_selection_view_base.h b/chrome/browser/ui/views/webid/account_selection_view_base.h
index c8a55d8..8467ef2 100644
--- a/chrome/browser/ui/views/webid/account_selection_view_base.h
+++ b/chrome/browser/ui/views/webid/account_selection_view_base.h
@@ -80,22 +80,18 @@
   METADATA_HEADER(BrandIconImageView, views::ImageView)
 
  public:
-  BrandIconImageView(
-      int image_size,
-      bool should_circle_crop,
-      base::RepeatingClosure on_image_set = base::DoNothing());
+  explicit BrandIconImageView(int image_size);
   BrandIconImageView(const BrandIconImageView&) = delete;
   BrandIconImageView& operator=(const BrandIconImageView&) = delete;
   ~BrandIconImageView() override;
 
-  void CropAndSetImage(const gfx::Image& image);
+  // This method will crop the given `image` if `should_circle_crop` and will
+  // attempt to set it into the BrandIconImageView. Returns whether the image
+  // was successfully set or not.
+  bool SetBrandIconImage(const gfx::Image& image, bool should_circle_crop);
 
  private:
   int image_size_;
-  bool should_circle_crop_;
-  base::RepeatingClosure on_image_set_;
-
-  base::WeakPtrFactory<BrandIconImageView> weak_ptr_factory_{this};
 };
 
 class AccountHoverButton : public HoverButton {
@@ -160,12 +156,12 @@
   virtual ~AccountSelectionViewBase();
 
   // Updates the FedCM dialog to show the "account picker" sheet.
-  // `is_choose_an_account` is true if the dialog must change its title to
-  // 'Choose an account'. This is currently only used on widget mode, when
-  // clicking on the 'Choose an account' button.
+  // `rp_icon` is the RP icon to be displayed on the header of the dialog when
+  // there are multiple IdPs to select from.
   virtual void ShowMultiAccountPicker(
       const std::vector<IdentityRequestAccountPtr>& accounts,
       const std::vector<IdentityProviderDataPtr>& idp_list,
+      const gfx::Image& rp_icon,
       bool show_back_button) = 0;
 
   // Updates the FedCM dialog to show the "verifying" sheet.
diff --git a/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop.cc b/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop.cc
index b0ce0f8..3c8080d 100644
--- a/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop.cc
+++ b/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop.cc
@@ -109,7 +109,7 @@
 }
 
 bool FedCmAccountSelectionView::Show(
-    const std::string& rp_for_display,
+    const content::RelyingPartyData& rp_data,
     const std::vector<IdentityProviderDataPtr>& idp_list,
     const std::vector<IdentityRequestAccountPtr>& accounts,
     Account::SignInMode sign_in_mode,
@@ -129,7 +129,7 @@
     // WeakPtrs to methods with return values.
     show_accounts_dialog_callback_ =
         base::BindOnce(base::IgnoreResult(&FedCmAccountSelectionView::Show),
-                       weak_ptr_factory_.GetWeakPtr(), rp_for_display, idp_list,
+                       weak_ptr_factory_.GetWeakPtr(), rp_data, idp_list,
                        accounts, sign_in_mode, rp_mode, new_accounts);
     // This is considered successful since we are intentionally delaying showing
     // the UI.
@@ -149,6 +149,7 @@
   idp_list_ = idp_list;
   accounts_ = accounts;
   new_accounts_ = new_accounts;
+  rp_icon_ = rp_data.rp_icon;
 
   size_t accounts_or_mismatches_size = accounts.size();
   bool supports_add_account = false;
@@ -181,7 +182,6 @@
           ? std::make_optional<std::u16string>(
                 base::UTF8ToUTF16(idp_list_[0]->idp_for_display))
           : std::nullopt;
-  rp_for_display_ = base::UTF8ToUTF16(rp_for_display);
 
   // If a modal dialog was created previously but there is no modal support for
   // this type of dialog, reset account_selection_view_ to create a bubble
@@ -194,8 +194,8 @@
 
   bool create_view = !account_selection_view_;
   if (create_view) {
-    CreateViewAndWidget(rp_for_display_, idp_title, rp_context, rp_mode,
-                        has_modal_support);
+    CreateViewAndWidget(base::UTF8ToUTF16(rp_data.rp_for_display), idp_title,
+                        rp_context, rp_mode, has_modal_support);
   }
 
   if (sign_in_mode == Account::SignInMode::kAuto) {
@@ -265,7 +265,7 @@
         // with most recently signed in accounts at the top to reduce the
         // exposure of extra UI surfaces and to work around the account picker
         // not having a back button.
-        ShowMultiAccountPicker(accounts_, idp_list_,
+        ShowMultiAccountPicker(accounts_, idp_list_, rp_icon_,
                                /*show_back_button=*/false);
       }
     } else {
@@ -277,7 +277,7 @@
                 supports_add_account);
       } else {
         ShowMultiAccountPicker(
-            new_accounts_, {new_accounts_[0]->identity_provider},
+            new_accounts_, {new_accounts_[0]->identity_provider}, rp_icon_,
             /*show_back_button=*/accounts_or_mismatches_size >
                 new_accounts_.size());
         // Override the state to NEWLY_LOGGED_IN_ACCOUNT_PICKER so the back
@@ -290,7 +290,8 @@
         (supports_add_account || has_filtered_out_accounts)) {
       // The logic to support add account is in ShowMultiAccountPicker for the
       // bubble dialog.
-      ShowMultiAccountPicker(accounts_, idp_list_, /*show_back_button=*/false);
+      ShowMultiAccountPicker(accounts_, idp_list_, rp_icon_,
+                             /*show_back_button=*/false);
     } else {
       state_ = State::SINGLE_ACCOUNT_PICKER;
       account_selection_view_->ShowSingleAccountConfirmDialog(
@@ -298,7 +299,7 @@
           /*show_back_button=*/false);
     }
   } else {
-    ShowMultiAccountPicker(accounts_, idp_list_,
+    ShowMultiAccountPicker(accounts_, idp_list_, rp_icon_,
                            /*show_back_button=*/false);
   }
   UpdateDialogVisibilityAndPosition();
@@ -361,10 +362,10 @@
   }
 
   bool create_view = !account_selection_view_;
-  rp_for_display_ = base::UTF8ToUTF16(rp_for_display);
   if (create_view) {
-    CreateViewAndWidget(rp_for_display_, base::UTF8ToUTF16(idp_etld_plus_one),
-                        rp_context, rp_mode, has_modal_support);
+    CreateViewAndWidget(base::UTF8ToUTF16(rp_for_display),
+                        base::UTF8ToUTF16(idp_etld_plus_one), rp_context,
+                        rp_mode, has_modal_support);
   }
 
   account_selection_view_->ShowFailureDialog(
@@ -401,8 +402,9 @@
 
   bool create_view = !account_selection_view_;
   if (create_view) {
-    CreateViewAndWidget(rp_for_display_, base::UTF8ToUTF16(idp_etld_plus_one),
-                        rp_context, rp_mode, has_modal_support);
+    CreateViewAndWidget(base::UTF8ToUTF16(rp_for_display),
+                        base::UTF8ToUTF16(idp_etld_plus_one), rp_context,
+                        rp_mode, has_modal_support);
   }
 
   account_selection_view_->ShowErrorDialog(base::UTF8ToUTF16(idp_etld_plus_one),
@@ -604,7 +606,7 @@
     UpdateDialogPosition();
     return;
   }
-  ShowMultiAccountPicker(accounts_, idp_list_,
+  ShowMultiAccountPicker(accounts_, idp_list_, rp_icon_,
                          /*show_back_button=*/false);
   UpdateDialogPosition();
 }
@@ -1035,9 +1037,10 @@
 void FedCmAccountSelectionView::ShowMultiAccountPicker(
     const std::vector<IdentityRequestAccountPtr>& accounts,
     const std::vector<IdentityProviderDataPtr>& idp_list,
+    const gfx::Image& rp_icon,
     bool show_back_button) {
   state_ = State::MULTI_ACCOUNT_PICKER;
-  account_selection_view_->ShowMultiAccountPicker(accounts, idp_list,
+  account_selection_view_->ShowMultiAccountPicker(accounts, idp_list, rp_icon,
                                                   show_back_button);
 }
 
diff --git a/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop.h b/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop.h
index 5ed62c4..76008fd 100644
--- a/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop.h
+++ b/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop.h
@@ -85,7 +85,7 @@
 
   // AccountSelectionView:
   bool Show(
-      const std::string& rp_for_display,
+      const content::RelyingPartyData& rp_data,
       const std::vector<IdentityProviderDataPtr>& idp_list,
       const std::vector<IdentityRequestAccountPtr>& accounts,
       Account::SignInMode sign_in_mode,
@@ -450,6 +450,7 @@
   void ShowMultiAccountPicker(
       const std::vector<IdentityRequestAccountPtr>& accounts,
       const std::vector<IdentityProviderDataPtr>& idp_list,
+      const gfx::Image& rp_icon,
       bool show_back_button);
 
   // PictureInPictureOcclusionObserver:
@@ -503,7 +504,8 @@
   // are multiple accounts, but it is size 0 when there are no new accounts.
   std::vector<IdentityRequestAccountPtr> new_accounts_;
 
-  std::u16string rp_for_display_;
+  // The RP icon to be displayed in the UI when needed.
+  gfx::Image rp_icon_;
 
   State state_{State::MULTI_ACCOUNT_PICKER};
 
diff --git a/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop_browsertest.cc b/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop_browsertest.cc
index 359d372c..0812467 100644
--- a/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop_browsertest.cc
+++ b/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop_browsertest.cc
@@ -55,7 +55,7 @@
         /*labels=*/std::vector<std::string>())};
     accounts_[0]->identity_provider = idps_[0];
     account_selection_view()->Show(
-        "rp-example.com", idps_, accounts_, mode,
+        content::RelyingPartyData("rp-example.com"), idps_, accounts_, mode,
         blink::mojom::RpMode::kPassive,
         /*new_accounts=*/std::vector<IdentityRequestAccountPtr>());
   }
@@ -326,7 +326,7 @@
         /*labels=*/std::vector<std::string>())};
     accounts_[0]->identity_provider = idps_[0];
     account_selection_view_->Show(
-        "rp-example.com", idps_, accounts_, mode,
+        content::RelyingPartyData("rp-example.com"), idps_, accounts_, mode,
         blink::mojom::RpMode::kPassive,
         /*new_accounts=*/std::vector<IdentityRequestAccountPtr>());
   }
diff --git a/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop_unittest.cc b/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop_unittest.cc
index 384dcd57..6dca814 100644
--- a/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop_unittest.cc
+++ b/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop_unittest.cc
@@ -76,6 +76,7 @@
   void ShowMultiAccountPicker(
       const std::vector<IdentityRequestAccountPtr>& accounts,
       const std::vector<IdentityProviderDataPtr>& idp_list,
+      const gfx::Image& rp_icon,
       bool show_back_button) override {
     show_back_button_ = show_back_button;
     sheet_type_ = SheetType::kAccountPicker;
@@ -363,8 +364,8 @@
             blink::mojom::RpMode rp_mode,
             const std::vector<IdentityRequestAccountPtr>& new_accounts =
                 std::vector<IdentityRequestAccountPtr>()) {
-    controller.Show(kTopFrameEtldPlusOne, {idp_data_}, accounts, sign_in_mode,
-                    rp_mode, new_accounts);
+    controller.Show(content::RelyingPartyData(kTopFrameEtldPlusOne),
+                    {idp_data_}, accounts, sign_in_mode, rp_mode, new_accounts);
   }
 
   std::unique_ptr<TestFedCmAccountSelectionView> CreateAndShowMismatchDialog(
@@ -422,8 +423,8 @@
       blink::mojom::RpMode rp_mode) {
     auto controller = std::make_unique<TestFedCmAccountSelectionView>(
         delegate_.get(), tab_interface_.get(), this);
-    controller->Show(kTopFrameEtldPlusOne, idp_list, accounts, sign_in_mode,
-                     rp_mode,
+    controller->Show(content::RelyingPartyData(kTopFrameEtldPlusOne), idp_list,
+                     accounts, sign_in_mode, rp_mode,
                      /*new_accounts=*/std::vector<IdentityRequestAccountPtr>());
     return controller;
   }
diff --git a/chrome/browser/ui/views/webid/fedcm_interactive_uitest.cc b/chrome/browser/ui/views/webid/fedcm_interactive_uitest.cc
index 65eef50..181716c9 100644
--- a/chrome/browser/ui/views/webid/fedcm_interactive_uitest.cc
+++ b/chrome/browser/ui/views/webid/fedcm_interactive_uitest.cc
@@ -51,8 +51,9 @@
           /*labels=*/std::vector<std::string>())};
       accounts_[0]->identity_provider = idps_[0];
       account_selection_view_->Show(
-          "rp-example.com", idps_, accounts_, Account::SignInMode::kExplicit,
-          mode, /*new_accounts=*/std::vector<IdentityRequestAccountPtr>());
+          content::RelyingPartyData("rp-example.com"), idps_, accounts_,
+          Account::SignInMode::kExplicit, mode,
+          /*new_accounts=*/std::vector<IdentityRequestAccountPtr>());
     });
   }
 
diff --git a/chrome/browser/ui/webid/account_selection_view.h b/chrome/browser/ui/webid/account_selection_view.h
index 87076e2..894852be 100644
--- a/chrome/browser/ui/webid/account_selection_view.h
+++ b/chrome/browser/ui/webid/account_selection_view.h
@@ -80,16 +80,16 @@
   virtual ~AccountSelectionView() = default;
 
   // Instructs the view to show the provided accounts to the user.
-  // `rp_for_display` is the relying party's URL. All IDP-specific information,
-  // is stored in `idp_list`. `sign_in_mode`
+  // `rp_data` is the relying party's data, such as the display name and icon.
+  // All IDP-specific information, is stored in `idp_list`. `sign_in_mode`
   // represents whether this is an auto re-authn flow. If it is the auto
-  // re-authn flow, `idp_list` will only include the single
-  // returning account and its IDP. `new_accounts` is a vector where each member
-  // is a newly logged in account that ought to be prioritized in the UI.
-  // Returns true if it was possible to show UI. If this method could not show
-  // UI and called Dismiss, returns false.
+  // re-authn flow, `idp_list` will only include the single returning account
+  // and its IDP. `new_accounts` is a vector where each member is a newly logged
+  // in account that ought to be prioritized in the UI. Returns true if it was
+  // possible to show UI. If this method could not show UI and called Dismiss,
+  // returns false.
   virtual bool Show(
-      const std::string& rp_for_display,
+      const content::RelyingPartyData& rp_data,
       const std::vector<IdentityProviderDataPtr>& idp_list,
       const std::vector<IdentityRequestAccountPtr>& accounts,
       Account::SignInMode sign_in_mode,
diff --git a/chrome/browser/ui/webid/identity_dialog_controller.cc b/chrome/browser/ui/webid/identity_dialog_controller.cc
index dcc9260..271de0a 100644
--- a/chrome/browser/ui/webid/identity_dialog_controller.cc
+++ b/chrome/browser/ui/webid/identity_dialog_controller.cc
@@ -9,6 +9,8 @@
 #include "build/build_config.h"
 #include "chrome/browser/segmentation_platform/segmentation_platform_service_factory.h"
 #include "chrome/browser/ui/browser_finder.h"
+#include "components/favicon/content/content_favicon_driver.h"
+#include "components/favicon/core/favicon_driver.h"
 #include "components/segmentation_platform/public/features.h"
 #include "components/segmentation_platform/public/segmentation_platform_service.h"
 
@@ -41,7 +43,7 @@
 }
 
 bool IdentityDialogController::ShowAccountsDialog(
-    const std::string& rp_for_display,
+    content::RelyingPartyData rp_data,
     const std::vector<IdentityProviderDataPtr>& identity_provider_data,
     const std::vector<IdentityRequestAccountPtr>& accounts,
     content::IdentityRequestAccount::SignInMode sign_in_mode,
@@ -59,7 +61,12 @@
   if (!TrySetAccountView()) {
     return false;
   }
-  return account_view_->Show(rp_for_display, identity_provider_data, accounts,
+  favicon::FaviconDriver* favicon_driver =
+      favicon::ContentFaviconDriver::FromWebContents(rp_web_contents_);
+  if (favicon_driver) {
+    rp_data.rp_icon = favicon_driver->GetFavicon();
+  }
+  return account_view_->Show(rp_data, identity_provider_data, accounts,
                              sign_in_mode, rp_mode, new_accounts);
 }
 
diff --git a/chrome/browser/ui/webid/identity_dialog_controller.h b/chrome/browser/ui/webid/identity_dialog_controller.h
index 08b416e..873f18e3 100644
--- a/chrome/browser/ui/webid/identity_dialog_controller.h
+++ b/chrome/browser/ui/webid/identity_dialog_controller.h
@@ -44,7 +44,7 @@
 
   // content::IdentityRequestDialogController
   bool ShowAccountsDialog(
-      const std::string& rp_for_display,
+      content::RelyingPartyData rp_data,
       const std::vector<IdentityProviderDataPtr>& identity_provider_data,
       const std::vector<IdentityRequestAccountPtr>& accounts,
       content::IdentityRequestAccount::SignInMode sign_in_mode,
diff --git a/chrome/browser/ui/webid/identity_dialog_controller_unittest.cc b/chrome/browser/ui/webid/identity_dialog_controller_unittest.cc
index 798fed8..f6099a29 100644
--- a/chrome/browser/ui/webid/identity_dialog_controller_unittest.cc
+++ b/chrome/browser/ui/webid/identity_dialog_controller_unittest.cc
@@ -45,7 +45,7 @@
   MOCK_METHOD(
       bool,
       Show,
-      (const std::string& rp_for_display,
+      (const content::RelyingPartyData& rp_data,
        const std::vector<IdentityProviderDataPtr>& identity_provider_data,
        const std::vector<IdentityRequestAccountPtr>& accounts,
        Account::SignInMode sign_in_mode,
@@ -246,7 +246,7 @@
 
   // Show button mode accounts dialog.
   controller.ShowAccountsDialog(
-      kTopFrameEtldPlusOne, {idp_data}, accounts,
+      content::RelyingPartyData(kTopFrameEtldPlusOne), {idp_data}, accounts,
       content::IdentityRequestAccount::SignInMode::kExplicit,
       blink::mojom::RpMode::kActive,
       /*new_accounts=*/std::vector<IdentityRequestAccountPtr>(),
@@ -277,7 +277,7 @@
 
   // Show widget mode accounts dialog.
   controller.ShowAccountsDialog(
-      kTopFrameEtldPlusOne, {idp_data}, accounts,
+      content::RelyingPartyData(kTopFrameEtldPlusOne), {idp_data}, accounts,
       content::IdentityRequestAccount::SignInMode::kExplicit,
       blink::mojom::RpMode::kPassive,
       /*new_accounts=*/std::vector<IdentityRequestAccountPtr>(),
@@ -300,7 +300,7 @@
 
   // Show button mode accounts dialog.
   EXPECT_FALSE(controller.ShowAccountsDialog(
-      kTopFrameEtldPlusOne, {idp_data}, accounts,
+      content::RelyingPartyData(kTopFrameEtldPlusOne), {idp_data}, accounts,
       content::IdentityRequestAccount::SignInMode::kExplicit,
       blink::mojom::RpMode::kActive,
       /*new_accounts=*/std::vector<IdentityRequestAccountPtr>(),
diff --git a/chrome/browser/ui/webui/ash/lobster/lobster_page_handler_unittest.cc b/chrome/browser/ui/webui/ash/lobster/lobster_page_handler_unittest.cc
index bc27867..6c7f8d7 100644
--- a/chrome/browser/ui/webui/ash/lobster/lobster_page_handler_unittest.cc
+++ b/chrome/browser/ui/webui/ash/lobster/lobster_page_handler_unittest.cc
@@ -115,10 +115,13 @@
   std::vector<LobsterImageCandidate> image_candidates = {
       LobsterImageCandidate(/*id=*/0, /*image_bytes=*/kRawBytes1.data(),
                             /*seed=*/20,
-                            /*query=*/"a nice strawberry"),
-      LobsterImageCandidate(/*id=*/1, /*image_bytes=*/kRawBytes2.data(),
-                            /*seed=*/21,
-                            /*query=*/"a nice strawberry")};
+                            /*user_query=*/"a nice strawberry",
+                            /*rewritten_query=*/"rewritten: a nice strawberry"),
+      LobsterImageCandidate(
+          /*id=*/1, /*image_bytes=*/kRawBytes2.data(),
+          /*seed=*/21,
+          /*user_query=*/"a nice strawberry",
+          /*rewritten_query=*/"rewritten: a nice strawberry")};
   FakeLobsterSession session(std::move(image_candidates),
                              /*commit_or_download_status=*/true,
                              /*feedback_submission_status=*/true);
diff --git a/chrome/browser/ui/webui/on_device_internals/on_device_internals_page_handler.cc b/chrome/browser/ui/webui/on_device_internals/on_device_internals_page_handler.cc
index 5e70d57..69284464 100644
--- a/chrome/browser/ui/webui/on_device_internals/on_device_internals_page_handler.cc
+++ b/chrome/browser/ui/webui/on_device_internals/on_device_internals_page_handler.cc
@@ -130,6 +130,10 @@
     on_device_model::ModelAssets assets) {
   auto params = on_device_model::mojom::LoadModelParams::New();
   params->assets = assets;
+  params->backend_type =
+      optimization_guide::features::ForceCpuBackendForOnDeviceModel()
+          ? ml::ModelBackendType::kCpuBackend
+          : ml::ModelBackendType::kGpuBackend;
   params->max_tokens = 4096;
   params->performance_hint = performance_hint;
   GetService().LoadModel(
diff --git a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome.mojom b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome.mojom
index 24531ac5..915eef3b 100644
--- a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome.mojom
+++ b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome.mojom
@@ -142,6 +142,9 @@
   // Triggers a call to |CustomizeChromePage.SetTheme()|.
   UpdateTheme();
 
+  // Triggers a call to |CustomizeChromePage.SetThemeEditable()|.
+  UpdateThemeEditable(bool is_theme_editable);
+
   // Sets Chrome's theme according to the default color.
   SetDefaultColor();
 
@@ -209,6 +212,8 @@
   SetMostVisitedSettings(bool custom_links_enabled, bool visible);
   // Sets the current theme.
   SetTheme(Theme theme);
+  // Sets whether edit theme should be enabled.
+  SetThemeEditable(bool is_theme_editable);
   // Scrolls side panel to |section|. Possibly a response to a call to
   // |CustomizeChromePageHandler.UpdateScrollToSection()|.
   ScrollToSection(CustomizeChromeSection section);
diff --git a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_interactive_uitest.cc b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_interactive_uitest.cc
new file mode 100644
index 0000000..13bc5ed
--- /dev/null
+++ b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_interactive_uitest.cc
@@ -0,0 +1,110 @@
+// 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/test/scoped_feature_list.h"
+#include "chrome/app/chrome_command_ids.h"
+#include "chrome/browser/extensions/chrome_test_extension_loader.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser_commands.h"
+#include "chrome/browser/ui/browser_element_identifiers.h"
+#include "chrome/browser/ui/webui/test_support/webui_interactive_test_mixin.h"
+#include "chrome/test/interaction/interactive_browser_test.h"
+#include "components/search/ntp_features.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/test_navigation_observer.h"
+#include "extensions/test/test_extension_dir.h"
+
+namespace {
+DEFINE_LOCAL_CUSTOM_ELEMENT_EVENT_TYPE(kElementExists);
+
+class CustomizeChromeInteractiveTest
+    : public WebUiInteractiveTestMixin<InteractiveBrowserTest> {
+ public:
+  CustomizeChromeInteractiveTest() {
+    scoped_feature_list_.InitAndEnableFeature(ntp_features::kNtpFooter);
+  }
+
+  InteractiveTestApi::MultiStep WaitForElementExists(
+      const ui::ElementIdentifier& contents_id,
+      const DeepQuery& element) {
+    StateChange element_exists;
+    element_exists.type =
+        WebContentsInteractionTestUtil::StateChange::Type::kExists;
+    element_exists.event = kElementExists;
+    element_exists.where = element;
+    return WaitForStateChange(contents_id, element_exists);
+  }
+
+  InteractiveTestApi::MultiStep OpenCustomizeChromeSidePanel(
+      const ui::ElementIdentifier& contents_id) {
+    return Steps(Do(base::BindLambdaForTesting([=, this]() {
+                   chrome::ExecuteCommand(browser(),
+                                          IDC_SHOW_CUSTOMIZE_CHROME_SIDE_PANEL);
+                 })),
+                 WaitForShow(kCustomizeChromeSidePanelWebViewElementId),
+                 InstrumentNonTabWebView(
+                     contents_id, kCustomizeChromeSidePanelWebViewElementId));
+  }
+
+  // Installs an extension and overrides ntp.
+  void InstallExtension(Profile* profile) {
+    extensions::TestExtensionDir extension_dir;
+    extension_dir.WriteFile(FILE_PATH_LITERAL("ext.html"),
+                            "<body>Extension-overridden NTP</body>");
+
+    const char extension_manifest[] = R"(
+        {
+            "chrome_url_overrides": {
+                "newtab": "ext.html"
+            },
+            "name": "Extension-overridden NTP",
+            "manifest_version": 3,
+            "version": "0.1"
+          })";
+
+    extension_dir.WriteManifest(extension_manifest);
+
+    extensions::ChromeTestExtensionLoader extension_loader(profile);
+    extension_loader.set_ignore_manifest_warnings(true);
+    // TODO(temao) Not blocking the test, but note that LoadExtension()
+    // occasionally returns null.
+    extension_loader.LoadExtension(extension_dir.Pack()).get();
+  }
+
+  void OpenExtensionNewTabPage() {
+    chrome::NewTab(browser());
+    content::WebContents* web_contents =
+        browser()->tab_strip_model()->GetActiveWebContents();
+
+    // Wait until chrome://newtab navigation finished.
+    content::TestNavigationObserver nav_observer(web_contents);
+    nav_observer.Wait();
+  }
+
+ protected:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+}  // namespace
+
+IN_PROC_BROWSER_TEST_F(CustomizeChromeInteractiveTest,
+                       EditThemeEnabledForExtensionNtp) {
+  DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kLocalCustomizeChromeElementId);
+
+  const DeepQuery kEditThemeButton = {"customize-chrome-app",
+                                      "#appearanceElement", "#editThemeButton"};
+
+  // 1. Load extension that overrides NTP.
+  InstallExtension(browser()->profile());
+  RunTestSequence(
+      // 2. Open extension new tab page.
+      Do(base::BindLambdaForTesting(
+          [&, this]() { OpenExtensionNewTabPage(); })),
+      // 3. Open customize chrome side panel.
+      OpenCustomizeChromeSidePanel(kLocalCustomizeChromeElementId),
+      // 4. Check edit theme is enabled in customize chrome side panel.
+      Steps(WaitForElementExists(kLocalCustomizeChromeElementId,
+                                 kEditThemeButton),
+            WaitForElementToRender(kLocalCustomizeChromeElementId,
+                                   kEditThemeButton)));
+}
diff --git a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler.cc b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler.cc
index 7d68888..53f1d9c 100644
--- a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler.cc
+++ b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler.cc
@@ -366,6 +366,10 @@
   page_->SetTheme(std::move(theme));
 }
 
+void CustomizeChromePageHandler::UpdateThemeEditable(bool is_theme_editable) {
+  page_->SetThemeEditable(is_theme_editable);
+}
+
 void CustomizeChromePageHandler::OpenChromeWebStore() {
   open_url_callback_.Run(
       GURL("https://chrome.google.com/webstore?category=theme"));
diff --git a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler.h b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler.h
index cdd44d2..f9a196f 100644
--- a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler.h
+++ b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler.h
@@ -117,6 +117,7 @@
       ChooseLocalCustomBackgroundCallback callback) override;
   void RemoveBackgroundImage() override;
   void UpdateTheme() override;
+  void UpdateThemeEditable(bool is_theme_editable) override;
   void OpenChromeWebStore() override;
   void OpenThirdPartyThemePage(const std::string& theme_id) override;
   void OpenChromeWebStoreCategoryPage(
diff --git a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler_unittest.cc b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler_unittest.cc
index ad3b7e2..aa8fbfd 100644
--- a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler_unittest.cc
+++ b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler_unittest.cc
@@ -162,6 +162,7 @@
               SetMostVisitedSettings,
               (bool custom_links_enabled, bool visible));
   MOCK_METHOD(void, SetTheme, (side_panel::mojom::ThemePtr));
+  MOCK_METHOD(void, SetThemeEditable, (bool));
   MOCK_METHOD(void,
               ScrollToSection,
               (side_panel::mojom::CustomizeChromeSection));
diff --git a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_ui.cc b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_ui.cc
index b35b12c..1c9e0c1 100644
--- a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_ui.cc
+++ b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_ui.cc
@@ -315,6 +315,14 @@
   }
 }
 
+void CustomizeChromeUI::UpdateThemeEditable(bool is_theme_editable) {
+  if (customize_chrome_page_handler_) {
+    customize_chrome_page_handler_->UpdateThemeEditable(is_theme_editable);
+  } else {
+    is_theme_editable_ = is_theme_editable;
+  }
+}
+
 base::WeakPtr<CustomizeChromeUI> CustomizeChromeUI::GetWeakPtr() {
   return weak_ptr_factory_.GetWeakPtr();
 }
@@ -406,6 +414,11 @@
         is_source_tab_first_party_ntp_.value());
     is_source_tab_first_party_ntp_.reset();
   }
+  if (is_theme_editable_.has_value()) {
+    customize_chrome_page_handler_->UpdateThemeEditable(
+        is_theme_editable_.value());
+    is_theme_editable_.reset();
+  }
 }
 
 void CustomizeChromeUI::CreateHelpBubbleHandler(
diff --git a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_ui.h b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_ui.h
index a99a8bf9..8445464 100644
--- a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_ui.h
+++ b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_ui.h
@@ -83,7 +83,10 @@
   void ScrollToSection(CustomizeChromeSection section);
 
   // Passthrough that calls the CustomizeChromePage's AttachedTabStateUpdated.
-  void AttachedTabStateUpdated(bool is_source_tab_first_party_ntp);
+  void AttachedTabStateUpdated(bool is_attached_tab_first_party_ntp);
+
+  // Passthrough that calls to CustomizeChromePage's UpdateThemeEditable.
+  void UpdateThemeEditable(bool is_theme_editable);
 
   // Gets a weak pointer to this object.
   base::WeakPtr<CustomizeChromeUI> GetWeakPtr();
@@ -185,6 +188,7 @@
   // the front-end is ready to receive the request.
   std::optional<CustomizeChromeSection> section_;
   std::optional<bool> is_source_tab_first_party_ntp_;
+  std::optional<bool> is_theme_editable_;
 
   std::unique_ptr<user_education::HelpBubbleHandler> help_bubble_handler_;
   mojo::Receiver<help_bubble::mojom::HelpBubbleHandlerFactory>
diff --git a/chrome/browser/ui/webui/side_panel/read_anything/read_anything_untrusted_page_handler.cc b/chrome/browser/ui/webui/side_panel/read_anything/read_anything_untrusted_page_handler.cc
index 84cbe316..a5ee4f6 100644
--- a/chrome/browser/ui/webui/side_panel/read_anything/read_anything_untrusted_page_handler.cc
+++ b/chrome/browser/ui/webui/side_panel/read_anything/read_anything_untrusted_page_handler.cc
@@ -788,12 +788,25 @@
 
 void ReadAnythingUntrustedPageHandler::Activate(bool active) {
   active_ = active;
+  if (features::IsReadAnythingReadAloudEnabled() && !active &&
+      side_panel_controller_->tab()->IsActivated() && !tab_will_detach_) {
+    page_->OnReadingModeHidden();
+  }
 }
 
 void ReadAnythingUntrustedPageHandler::OnSidePanelControllerDestroyed() {
   side_panel_controller_ = nullptr;
 }
 
+void ReadAnythingUntrustedPageHandler::OnTabWillDetach() {
+  if (!features::IsReadAnythingReadAloudEnabled()) {
+    return;
+  }
+
+  tab_will_detach_ = true;
+  page_->OnTabWillDetach();
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 // screen_ai::ScreenAIInstallState::Observer:
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/chrome/browser/ui/webui/side_panel/read_anything/read_anything_untrusted_page_handler.h b/chrome/browser/ui/webui/side_panel/read_anything/read_anything_untrusted_page_handler.h
index c0e48d6..c32ec08 100644
--- a/chrome/browser/ui/webui/side_panel/read_anything/read_anything_untrusted_page_handler.h
+++ b/chrome/browser/ui/webui/side_panel/read_anything/read_anything_untrusted_page_handler.h
@@ -144,6 +144,9 @@
       const translate::LanguageDetectionDetails& details) override;
   void OnTranslateDriverDestroyed(translate::TranslateDriver* driver) override;
 
+  // ReadAnythingSidePanelController::Observer:
+  void OnTabWillDetach() override;
+
   // ash::SessionObserver
 #if BUILDFLAG(IS_CHROMEOS)
   void OnLockStateChanged(bool locked) override;
@@ -198,6 +201,7 @@
 
   // ReadAnythingSidePanelController::Observer:
   void Activate(bool active) override;
+  void OnSidePanelControllerDestroyed() override;
 
   void SetDefaultLanguageCode(const std::string& code);
 
@@ -205,9 +209,6 @@
   // be determined.
   void SetLanguageCode(const std::string& code);
 
-  // ReadAnythingSidePanelController::Observer:
-  void OnSidePanelControllerDestroyed() override;
-
   void SetUpPdfObserver();
 
   void OnGetVoicePackInfo(read_anything::mojom::VoicePackInfoPtr info);
@@ -242,6 +243,8 @@
   // Whether the Read Anything feature is currently active. The feature is
   // active when it is currently shown in the Side Panel.
   bool active_ = true;
+  // Whether the tab is going to detach soon.
+  bool tab_will_detach_ = false;
 
   // The current language being used in the app.
   std::string current_language_code_ = "en-US";
diff --git a/chrome/browser/ui/webui/side_panel/read_anything/read_anything_untrusted_page_handler_unittest.cc b/chrome/browser/ui/webui/side_panel/read_anything/read_anything_untrusted_page_handler_unittest.cc
index 7ef48d3..1a99f351 100644
--- a/chrome/browser/ui/webui/side_panel/read_anything/read_anything_untrusted_page_handler_unittest.cc
+++ b/chrome/browser/ui/webui/side_panel/read_anything/read_anything_untrusted_page_handler_unittest.cc
@@ -13,6 +13,7 @@
 #include "chrome/browser/translate/chrome_translate_client.h"
 #include "chrome/browser/ui/tabs/public/tab_features.h"
 #include "chrome/browser/ui/views/side_panel/read_anything/read_anything_side_panel_controller.h"
+#include "chrome/browser/ui/views/side_panel/side_panel_registry.h"
 #include "chrome/browser/ui/webui/side_panel/read_anything/read_anything_prefs.h"
 #include "chrome/common/read_anything/read_anything.mojom-forward.h"
 #include "chrome/common/read_anything/read_anything.mojom.h"
@@ -86,6 +87,8 @@
   MOCK_METHOD(void, SetLanguageCode, (const std::string&));
   MOCK_METHOD(void, SetDefaultLanguageCode, (const std::string&));
   MOCK_METHOD(void, ScreenAIServiceReady, ());
+  MOCK_METHOD(void, OnReadingModeHidden, ());
+  MOCK_METHOD(void, OnTabWillDetach, ());
   MOCK_METHOD(void,
               OnGetVoicePackInfo,
               (read_anything::mojom::VoicePackInfoPtr voice_pack_info));
@@ -202,6 +205,13 @@
     BrowserWithTestWindowTest::TearDown();
   }
 
+  ReadAnythingSidePanelController* side_panel_controller() {
+    return browser()
+        ->GetActiveTabInterface()
+        ->GetTabFeatures()
+        ->read_anything_side_panel_controller();
+  }
+
   ChromeTranslateClient* GetChromeTranslateClient() {
     return ChromeTranslateClient::FromWebContents(
         browser()
@@ -260,6 +270,22 @@
 
   void OnSpeechRateChange(double rate) { handler_->OnSpeechRateChange(rate); }
 
+  void OnTabWillDetach() { handler_->OnTabWillDetach(); }
+
+  void Activate(bool active) {
+    SidePanelEntry* entry = browser()
+                                ->GetActiveTabInterface()
+                                ->GetTabFeatures()
+                                ->side_panel_registry()
+                                ->GetEntryForKey(SidePanelEntry::Key(
+                                    SidePanelEntry::Id::kReadAnything));
+    if (active) {
+      side_panel_controller()->OnEntryShown(entry);
+    } else {
+      side_panel_controller()->OnEntryHidden(entry);
+    }
+  }
+
   void OnImageDataRequested(const ui::AXTreeID& target_tree_id,
                             ui::AXNodeID target_node_id) {
     handler_->OnImageDataRequested(target_tree_id, target_node_id);
@@ -944,4 +970,31 @@
 }
 #endif  // !BUILDFLAG(IS_CHROMEOS)
 
+TEST_F(ReadAnythingUntrustedPageHandlerTest, OnTabWillDetach) {
+  handler_ = std::make_unique<TestReadAnythingUntrustedPageHandler>(
+      page_.BindAndGetRemote(), test_web_ui_.get());
+
+  OnTabWillDetach();
+  EXPECT_CALL(page_, OnTabWillDetach).Times(1);
+  EXPECT_CALL(page_, OnReadingModeHidden).Times(0);
+}
+
+TEST_F(ReadAnythingUntrustedPageHandlerTest,
+       Activate_OnDeactivateTab_NotifiesPage) {
+  handler_ = std::make_unique<TestReadAnythingUntrustedPageHandler>(
+      page_.BindAndGetRemote(), test_web_ui_.get());
+
+  Activate(false);
+  EXPECT_CALL(page_, OnReadingModeHidden).Times(1);
+}
+
+TEST_F(ReadAnythingUntrustedPageHandlerTest,
+       Activate_OnActivateTab_DoesNotNotifyPage) {
+  handler_ = std::make_unique<TestReadAnythingUntrustedPageHandler>(
+      page_.BindAndGetRemote(), test_web_ui_.get());
+
+  Activate(true);
+  EXPECT_CALL(page_, OnReadingModeHidden).Times(0);
+}
+
 }  // namespace
diff --git a/chrome/browser/web_applications/preinstalled_web_app_migration_browsertest.cc b/chrome/browser/web_applications/preinstalled_web_app_migration_browsertest.cc
index 028765a..de874da 100644
--- a/chrome/browser/web_applications/preinstalled_web_app_migration_browsertest.cc
+++ b/chrome/browser/web_applications/preinstalled_web_app_migration_browsertest.cc
@@ -181,7 +181,7 @@
   void SetUpExtensionTestExternalProvider() {
     external_provider_manager()->ClearProvidersForTesting();
 
-    extension_service().updater()->SetExtensionCacheForTesting(
+    extensions::ExtensionUpdater::Get(profile())->SetExtensionCacheForTesting(
         test_extension_cache_.get());
 
     std::string external_extension_config = base::ReplaceStringPlaceholders(
diff --git a/chrome/browser/webauthn/enclave_manager.cc b/chrome/browser/webauthn/enclave_manager.cc
index 2c38a89..7255717 100644
--- a/chrome/browser/webauthn/enclave_manager.cc
+++ b/chrome/browser/webauthn/enclave_manager.cc
@@ -141,10 +141,9 @@
   std::string set_pin;      // the PIN to set on an existing account.
   std::string updated_pin;  // a new PIN, to replace the current PIN.
   std::string rapt;         // ReAuthentication Proof Token.
-  bool update_wrapped_pin;  // copy `wrapped_pin` and `pin_public_key` to the
-                            // state.
+  bool update_wrapped_pin;  // copy `wrapped_pin` to the state.
   std::unique_ptr<EnclaveLocalState::WrappedPIN> wrapped_pin;
-  std::optional<std::string> pin_public_key;
+  std::optional<std::string> pin_public_key;  // the current PIN PK in the SDS.
 #if BUILDFLAG(IS_MAC)
   std::unique_ptr<device::enclave::ICloudRecoveryKey> icloud_recovery_key;
 #endif                      // BUILDFLAG(IS_MAC)
@@ -352,9 +351,6 @@
     return __LINE__;
   }
 
-  if (user.has_wrapped_pin() != user.has_pin_public_key()) {
-    return __LINE__;
-  }
   if (user.has_wrapped_pin()) {
     return CheckPINInvariants(user.wrapped_pin());
   }
@@ -1568,7 +1564,6 @@
 
     if (action_->update_wrapped_pin) {
       *user_->mutable_wrapped_pin() = std::move(*action_->wrapped_pin);
-      user_->set_pin_public_key(std::move(*action_->pin_public_key));
       manager_->WriteState(&local_state_);
     }
 
@@ -1891,7 +1886,6 @@
 
     if (action_->wrapped_pin) {
       *user_->mutable_wrapped_pin() = std::move(*action_->wrapped_pin);
-      user_->set_pin_public_key(std::move(*action_->pin_public_key));
       action_->wrapped_pin.reset();
     }
 
@@ -1976,9 +1970,9 @@
       // security domain requires the current PIN public key to be set when
       // joining a PIN, which Chrome will do later during processing.
       if (result->gpm_pin_metadata->public_key) {
-        FIDO_LOG(EVENT) << "Updating GPM PIN public key";
-        user_->set_pin_public_key(
-            std::move(*result->gpm_pin_metadata->public_key));
+        FIDO_LOG(EVENT) << "GPM PIN public key updated";
+        action_->pin_public_key =
+            std::move(*result->gpm_pin_metadata->public_key);
       }
       if (result->gpm_pin_metadata->usable_pin_metadata) {
         const auto& metadata = *result->gpm_pin_metadata->usable_pin_metadata;
@@ -2243,14 +2237,11 @@
     std::string wrapped_pin_proto_serialized =
         wrapped_pin_proto_->SerializeAsString();
     *user_->mutable_wrapped_pin() = std::move(*wrapped_pin_proto_);
-    const std::string previous_pin_public_key =
-        action_->pin_public_key.value_or(user_->pin_public_key());
     // If changing the PIN, there must be a previous PIN member public key.
     // If enrolling with a PIN, it's possible Chrome is replacing an existing
     // PIN that cannot be used, in which case we also need to set the previous
     // PIN member public key.
-    CHECK(!updating_pin_member || !previous_pin_public_key.empty());
-    user_->set_pin_public_key(vault_public_key);
+    CHECK(!updating_pin_member || action_->pin_public_key);
 
     state_ = (updating_pin_member || is_set_pin_)
                  ? State::kJoiningUpdatedPINToDomain
@@ -2272,7 +2263,7 @@
         *primary_account_info_, std::move(*member_keys_source),
         *secure_box_pub_key,
         trusted_vault::GpmPinMetadata(
-            previous_pin_public_key,
+            action_->pin_public_key,
             trusted_vault::UsableRecoveryPinMetadata(
                 std::move(wrapped_pin_proto_serialized),
                 /*expiry=*/base::Time())),
@@ -2951,7 +2942,7 @@
         !CheckPINInvariants(*wrapped_pin).has_value()) {
       if (metadata.public_key.has_value() &&
           (!user_->has_wrapped_pin() ||
-           user_->wrapped_pin().generation() != wrapped_pin->generation())) {
+           user_->wrapped_pin().wrapped_pin() != wrapped_pin->wrapped_pin())) {
         std::unique_ptr<PendingAction> action =
             std::make_unique<PendingAction>();
         action->callback = std::move(callback);
diff --git a/chrome/browser/webauthn/enclave_manager_unittest.cc b/chrome/browser/webauthn/enclave_manager_unittest.cc
index f0d8d7c..54412b07 100644
--- a/chrome/browser/webauthn/enclave_manager_unittest.cc
+++ b/chrome/browser/webauthn/enclave_manager_unittest.cc
@@ -1272,6 +1272,7 @@
 
 TEST_F(EnclaveManagerTest, PINChanged) {
   ASSERT_TRUE(Register());
+  constexpr std::string_view kNewWrappedPin = "dummy wrapped pin >= 29 chars";
 
   BoolFuture setup_future;
   manager_.SetupWithPIN("123456", setup_future.GetCallback());
@@ -1281,16 +1282,16 @@
   const webauthn_pb::EnclaveLocalState::User& user =
       manager_.local_state_for_testing().users().begin()->second;
   webauthn_pb::EnclaveLocalState::WrappedPIN wrapped_pin = user.wrapped_pin();
-  wrapped_pin.set_generation(wrapped_pin.generation() + 1);
+  wrapped_pin.set_wrapped_pin(kNewWrappedPin);
 
   trusted_vault::DownloadAuthenticationFactorsRegistrationStateResult state;
   state.state = trusted_vault::
       DownloadAuthenticationFactorsRegistrationStateResult::State::kRecoverable;
   state.key_version = kSecretVersion;
   state.gpm_pin_metadata = trusted_vault::GpmPinMetadata(
-      user.pin_public_key(), trusted_vault::UsableRecoveryPinMetadata(
-                                 wrapped_pin.SerializeAsString(),
-                                 /*expiry=*/base::Time::FromTimeT(1)));
+      "new public key", trusted_vault::UsableRecoveryPinMetadata(
+                            wrapped_pin.SerializeAsString(),
+                            /*expiry=*/base::Time::FromTimeT(1)));
 
   BoolFuture update_future;
   EXPECT_TRUE(
@@ -1299,7 +1300,7 @@
   EXPECT_TRUE(manager_.is_ready());
   const webauthn_pb::EnclaveLocalState::User& updated_user =
       manager_.local_state_for_testing().users().begin()->second;
-  EXPECT_EQ(updated_user.wrapped_pin().generation(), wrapped_pin.generation());
+  EXPECT_EQ(updated_user.wrapped_pin().wrapped_pin(), kNewWrappedPin);
 }
 
 TEST_F(EnclaveManagerTest, SigningFails) {
diff --git a/chrome/browser/webauthn/proto/enclave_local_state.proto b/chrome/browser/webauthn/proto/enclave_local_state.proto
index 1e90519..5964632b 100644
--- a/chrome/browser/webauthn/proto/enclave_local_state.proto
+++ b/chrome/browser/webauthn/proto/enclave_local_state.proto
@@ -59,6 +59,9 @@
 
   // User contains state for a specific GAIA ID.
   message User {
+    reserved "pin_public_key";
+    reserved 12;
+
     // The not-user-verification-interlocked device key:
     //
     // These three members are either all empty or all non-empty.
@@ -102,9 +105,6 @@
     // The wrapped Google Password Manager PIN.
     optional WrappedPIN wrapped_pin = 11;
 
-    // The public key of the GPM PIN member of the security domain.
-    optional bytes pin_public_key = 12;
-
     // Flag set when the device is in a newly-registered state but the UV key
     // has not yet been created, and should be created at the time of the first
     // UV request.
diff --git a/chrome/build/android-arm64.pgo.txt b/chrome/build/android-arm64.pgo.txt
index a2e384f..7878d651 100644
--- a/chrome/build/android-arm64.pgo.txt
+++ b/chrome/build/android-arm64.pgo.txt
@@ -1 +1 @@
-chrome-android64-main-1743082740-737c3a48072a59f07278802f6a3d6fb01b21ca0f-db553ee56abed8da92bfad27804855ea240e994a.profdata
+chrome-android64-main-1743104037-9691cb74d2dc80bf5cc6b1eae66cf184472ded88-8c0fc503c3fdd3538f6f6eac306b503e5ecc3fc1.profdata
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt
index 2b1b06f..cd0efd4 100644
--- a/chrome/build/linux.pgo.txt
+++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@
-chrome-linux-main-1743076491-65dcb83731f6f416758f71562e07c29c3c45ddfa-3133e24bb2d86b290a1f14326a5b961266a1a451.profdata
+chrome-linux-main-1743098392-f2a562d2d44509ebb699f0fc8856d8a8b057b42e-0037ffbe9f73bf38e6839fe513e7f804a1e8a88c.profdata
diff --git a/chrome/build/mac-arm.pgo.txt b/chrome/build/mac-arm.pgo.txt
index f9b4f18..6401667 100644
--- a/chrome/build/mac-arm.pgo.txt
+++ b/chrome/build/mac-arm.pgo.txt
@@ -1 +1 @@
-chrome-mac-arm-main-1743083806-616ef1adb265ab16f72a1a19fbd76a190f33440c-2ef4ed75dbf745be2fdbd3c7bae0efa0010052a4.profdata
+chrome-mac-arm-main-1743105572-0578b1b7c1f1465599413e619439ffba4b48be74-c5df7ab3f5837bca1001fae1482ab0aab9141f68.profdata
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt
index 54a0373..8c3dac4 100644
--- a/chrome/build/mac.pgo.txt
+++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@
-chrome-mac-main-1743076491-ba72f8a2116b61045ccd9624f1e4495f56e14061-3133e24bb2d86b290a1f14326a5b961266a1a451.profdata
+chrome-mac-main-1743098392-7ef396167eab995bc448afdaaadf945d6480cdf9-0037ffbe9f73bf38e6839fe513e7f804a1e8a88c.profdata
diff --git a/chrome/build/win-arm64.pgo.txt b/chrome/build/win-arm64.pgo.txt
index 0bffbda..d6c717b 100644
--- a/chrome/build/win-arm64.pgo.txt
+++ b/chrome/build/win-arm64.pgo.txt
@@ -1 +1 @@
-chrome-win-arm64-main-1743076491-1f290a0dc3ec9174d7c83766bd9822e981ee7dba-3133e24bb2d86b290a1f14326a5b961266a1a451.profdata
+chrome-win-arm64-main-1743098392-b3bc1b77fcd4c602a5c1ac0ee59b96222dabaa9a-0037ffbe9f73bf38e6839fe513e7f804a1e8a88c.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index cea4ac8..869b8fb 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1743065830-dbfaab5b8cd9128d542cf5586c72287ec847acfb-f8cd18789b75db47166078ac6c699b17d69ccdcd.profdata
+chrome-win32-main-1743087539-5ec8258c70e6894e61eba9d819bab07db2e1b820-619a20666fb3bb503c09257d92be393856c585ee.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index ac98f7b3..b258a3e5 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1743065830-dffaf8c5865af35a4a3a3fa805f0f30c077c8b01-f8cd18789b75db47166078ac6c699b17d69ccdcd.profdata
+chrome-win64-main-1743087539-99a20a09f113f3d8c11834507ae7f8ce5ceb81c5-619a20666fb3bb503c09257d92be393856c585ee.profdata
diff --git a/chrome/common/actor.mojom b/chrome/common/actor.mojom
index 1dcd0d6a..bc78d98 100644
--- a/chrome/common/actor.mojom
+++ b/chrome/common/actor.mojom
@@ -4,11 +4,39 @@
 
 module actor.mojom;
 
-// All information required to invoke a tool in the renderer.
-struct ToolInvocation {
+// This interface is meant to largely mirror the
+// BrowserAction::ActionInformation proto.
+
+// Tool-specific target.
+struct ToolTarget {
   // DOMNodeId for the node this invocation should be applied to.
   int32 dom_node_id;
-
-  // TODO(crbug.com/6377727): Add details union which includes parameters
-  // for what type of tool to invoke and any details.
 };
+
+// Information specific to a click action.
+struct ClickAction {
+    // Corresponds to ClickAction.ClickType
+  enum Type {
+    kLeft = 1,
+    kRight = 2,
+  };
+
+  // Corresponds to ClickAction.ClickCount
+  enum Count {
+    kSingle = 1,
+    kDouble = 2,
+  };
+  Type type;
+  Count count;
+  ToolTarget target;
+};
+
+// Union of tool-specific action.
+union ToolAction {
+  ClickAction click;
+};
+
+// All information required to invoke a tool in the renderer.
+struct ToolInvocation {
+  ToolAction action;
+};
\ No newline at end of file
diff --git a/chrome/common/chrome_features.cc b/chrome/common/chrome_features.cc
index 1f86a80..1e59593 100644
--- a/chrome/common/chrome_features.cc
+++ b/chrome/common/chrome_features.cc
@@ -312,7 +312,7 @@
 BASE_FEATURE(kGlicActor, "GlicActor", base::FEATURE_DISABLED_BY_DEFAULT);
 
 // Controls whether the Glic feature is always detached.
-BASE_FEATURE(kGlicDetached, "GlicDetached", base::FEATURE_DISABLED_BY_DEFAULT);
+BASE_FEATURE(kGlicDetached, "GlicDetached", base::FEATURE_ENABLED_BY_DEFAULT);
 
 // Whether to sync @google.com account cookies. This is only for development and
 // testing.
@@ -818,6 +818,11 @@
              base::FEATURE_DISABLED_BY_DEFAULT);
 #endif
 
+// Prevents the installation of non-allowlisted Isolated Web Apps.
+BASE_FEATURE(kIsolatedWebAppAllowlist,
+             "IsolatedWebAppAllowlist",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 // Enables Isolated Web App Developer Mode, which allows developers to
 // install untrusted Isolated Web Apps.
 BASE_FEATURE(kIsolatedWebAppDevMode,
diff --git a/chrome/common/chrome_features.h b/chrome/common/chrome_features.h
index 3296f7f..ec0a3c14 100644
--- a/chrome/common/chrome_features.h
+++ b/chrome/common/chrome_features.h
@@ -467,6 +467,9 @@
 BASE_DECLARE_FEATURE(kIncompatibleApplicationsWarning);
 #endif  // BUILDFLAG(IS_ANDROID)
 
+COMPONENT_EXPORT(CHROME_FEATURES)
+BASE_DECLARE_FEATURE(kIsolatedWebAppAllowlist);
+
 // LINT.IfChange
 COMPONENT_EXPORT(CHROME_FEATURES) BASE_DECLARE_FEATURE(kIsolatedWebAppDevMode);
 COMPONENT_EXPORT(CHROME_FEATURES)
diff --git a/chrome/common/read_anything/read_anything.mojom b/chrome/common/read_anything/read_anything.mojom
index 8ba89e3..e93e58d 100644
--- a/chrome/common/read_anything/read_anything.mojom
+++ b/chrome/common/read_anything/read_anything.mojom
@@ -287,6 +287,15 @@
   // Notifies the WebUI that the ScreenAI service is ready on this device.
   ScreenAIServiceReady();
 
+  // Notifies the WebUI that the current reading mode panel is hidden. This
+  // might be due to a different side panel opening, the side panel closing
+  // while reading mode is open, or a new tab opening. If reading mode is closed
+  // because of the tab closing, OnTabWillDetach is called instead.
+  OnReadingModeHidden();
+
+  // Notifies the WebUI that current tab will detach so it can clean up.
+  OnTabWillDetach();
+
   // Notifies the WebUI that an image has been downloaded for a given node.
   OnImageDataDownloaded(ax.mojom.AXTreeID tree_id,
     int32 node_id,
diff --git a/chrome/release_scripts b/chrome/release_scripts
index fb31118..2b782b7 160000
--- a/chrome/release_scripts
+++ b/chrome/release_scripts
@@ -1 +1 @@
-Subproject commit fb311181391e4fbbe19d8378fcb0cb2b68a2b195
+Subproject commit 2b782b71a0c094c1ffd92a5faac19183fa908df5
diff --git a/chrome/renderer/accessibility/read_anything/read_aloud_app_model.cc b/chrome/renderer/accessibility/read_anything/read_aloud_app_model.cc
index 7f2e443c..6aeee8e 100644
--- a/chrome/renderer/accessibility/read_anything/read_aloud_app_model.cc
+++ b/chrome/renderer/accessibility/read_anything/read_aloud_app_model.cc
@@ -11,6 +11,7 @@
 #include <vector>
 
 #include "base/containers/span.h"
+#include "base/metrics/histogram_functions.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/task/task_traits.h"
 #include "base/task/thread_pool.h"
@@ -756,3 +757,9 @@
         metric_to_count_map_[metric_name]);
   }
 }
+
+void ReadAloudAppModel::LogSpeechStop(ReadAloudStopSource source) {
+  if (features::IsReadAnythingReadAloudEnabled()) {
+    base::UmaHistogramEnumeration(kSpeechStopSourceHistogramName, source);
+  }
+}
diff --git a/chrome/renderer/accessibility/read_anything/read_aloud_app_model.h b/chrome/renderer/accessibility/read_anything/read_aloud_app_model.h
index 0550d7f23..e161f78 100644
--- a/chrome/renderer/accessibility/read_anything/read_aloud_app_model.h
+++ b/chrome/renderer/accessibility/read_anything/read_aloud_app_model.h
@@ -16,6 +16,32 @@
 // ReadAnythingAppController for the Read Anything WebUI app.
 class ReadAloudAppModel {
  public:
+  // Enum for logging when speech is stopped and why.
+  // These values are persisted to logs. Entries should not be renumbered and
+  // numeric values should never be reused.
+  //
+  // LINT.IfChange(ReadAloudStopSource)
+  enum class ReadAloudStopSource {
+    kButton = 0,
+    kKeyboardShortcut = 1,
+    kCloseReadingMode = 2,
+    kCloseTabOrWindow = 3,
+    kReloadPage = 4,
+    kChangePage = 5,
+    kEngineInterrupt = 6,
+    kEngineError = 7,
+    kFinishContent = 8,
+    kLockChromeosDevice = 9,
+    kUnexpectedUpdateContent = 10,
+
+    kMinValue = kButton,
+    kMaxValue = kUnexpectedUpdateContent,
+  };
+  // LINT.ThenChange(/tools/metrics/histograms/metadata/accessibility/enums.xml:ReadAnythingSpeechStopSource)
+
+  static constexpr char kSpeechStopSourceHistogramName[] =
+      "Accessibility.ReadAnything.SpeechStopSource";
+
   ReadAloudAppModel();
   ~ReadAloudAppModel();
   ReadAloudAppModel(const ReadAloudAppModel& other) = delete;
@@ -143,6 +169,8 @@
   // SingleSampleMetric. These are then logged once on destruction.
   void IncrementMetric(const std::string& metric_name);
 
+  void LogSpeechStop(ReadAloudStopSource source);
+
  private:
   // Returns true if the node was previously spoken or we expect to speak it
   // to be spoken once the current run of #GetCurrentText which called
diff --git a/chrome/renderer/accessibility/read_anything/read_aloud_app_model_browsertest.cc b/chrome/renderer/accessibility/read_anything/read_aloud_app_model_browsertest.cc
index 055d64b..f41f17a9 100644
--- a/chrome/renderer/accessibility/read_anything/read_aloud_app_model_browsertest.cc
+++ b/chrome/renderer/accessibility/read_anything/read_aloud_app_model_browsertest.cc
@@ -5,7 +5,10 @@
 #include "chrome/renderer/accessibility/read_anything/read_aloud_app_model.h"
 
 #include "base/memory/raw_ptr.h"
+#include "base/test/metrics/histogram_tester.h"
+#include "base/test/scoped_feature_list.h"
 #include "chrome/test/base/chrome_render_view_test.h"
+#include "ui/accessibility/accessibility_features.h"
 
 class ReadAnythingReadAloudAppModelTest : public ChromeRenderViewTest {
  public:
@@ -61,12 +64,43 @@
     model_->set_default_language_code(lang);
   }
 
+  void LogSpeechStop(ReadAloudAppModel::ReadAloudStopSource source) {
+    model_->LogSpeechStop(source);
+  }
+
+  void EnableReadAloud() {
+    scoped_feature_list_.InitAndEnableFeature(features::kReadAnythingReadAloud);
+  }
+
  private:
   // ReadAloudAppModel constructor and destructor are private so it's
   // not accessible by std::make_unique.
   raw_ptr<ReadAloudAppModel> model_ = nullptr;
+  base::test::ScopedFeatureList scoped_feature_list_;
 };
 
+// Read Aloud is currently only enabled by default on ChromeOS.
+#if !BUILDFLAG(IS_CHROMEOS)
+TEST_F(ReadAnythingReadAloudAppModelTest, LogSpeechStop_WithoutReadAloud) {
+  auto source = ReadAloudAppModel::ReadAloudStopSource::kCloseReadingMode;
+  base::HistogramTester histogram_tester;
+
+  LogSpeechStop(source);
+  EXPECT_EQ(0, histogram_tester.GetTotalSum(
+                   ReadAloudAppModel::kSpeechStopSourceHistogramName));
+}
+#endif
+
+TEST_F(ReadAnythingReadAloudAppModelTest, LogSpeechStop_WithReadAloud) {
+  EnableReadAloud();
+  auto source = ReadAloudAppModel::ReadAloudStopSource::kCloseReadingMode;
+  base::HistogramTester histogram_tester;
+
+  LogSpeechStop(source);
+  histogram_tester.ExpectUniqueSample(
+      ReadAloudAppModel::kSpeechStopSourceHistogramName, source, 1);
+}
+
 TEST_F(ReadAnythingReadAloudAppModelTest, SpeechPlaying) {
   EXPECT_FALSE(SpeechPlaying());
 
diff --git a/chrome/renderer/accessibility/read_anything/read_anything_app_controller.cc b/chrome/renderer/accessibility/read_anything/read_anything_app_controller.cc
index d238b2e5..b0d45f7 100644
--- a/chrome/renderer/accessibility/read_anything/read_anything_app_controller.cc
+++ b/chrome/renderer/accessibility/read_anything/read_anything_app_controller.cc
@@ -595,6 +595,13 @@
   model_.SetActiveTreeId(tree_id);
   model_.SetUkmSourceId(ukm_source_id);
   model_.set_is_pdf(is_pdf);
+
+  if (IsReadAloudEnabled() && read_aloud_model_.speech_playing()) {
+    model_.SetUrlInformationCallback(
+        base::BindOnce(&ReadAnythingAppController::OnUrlInformationSet,
+                       weak_ptr_factory_.GetWeakPtr()));
+  }
+
   // Delete all pending updates on the formerly active AXTree.
   // TODO(crbug.com/40802192): If distillation is in progress, cancel the
   // distillation request.
@@ -897,6 +904,19 @@
       .SetProperty("sentenceHighlighting",
                    &ReadAnythingAppController::SentenceHighlighting)
       .SetProperty("noHighlighting", &ReadAnythingAppController::NoHighlighting)
+      .SetProperty("pauseButtonStopSource",
+                   &ReadAnythingAppController::PauseButtonStopSource)
+      .SetProperty("keyboardShortcutStopSource",
+                   &ReadAnythingAppController::KeyboardShortcutStopSource)
+      .SetProperty("engineInterruptStopSource",
+                   &ReadAnythingAppController::EngineInterruptStopSource)
+      .SetProperty("engineErrorStopSource",
+                   &ReadAnythingAppController::EngineErrorStopSource)
+      .SetProperty("contentFinishedStopSource",
+                   &ReadAnythingAppController::ContentFinishedStopSource)
+      .SetProperty(
+          "unexpectedUpdateContentStopSource",
+          &ReadAnythingAppController::UnexpectedUpdateContentStopSource)
       .SetProperty("speechRate", &ReadAnythingAppController::SpeechRate)
       .SetProperty("isGoogleDocs", &ReadAnythingAppController::IsGoogleDocs)
       .SetProperty("isReadAloudEnabled",
@@ -991,6 +1011,7 @@
                  &ReadAnythingAppController::GetDisplayNameForLocale)
       .SetMethod("incrementMetricCount",
                  &ReadAnythingAppController::IncrementMetricCount)
+      .SetMethod("logSpeechStop", &ReadAnythingAppController::LogSpeechStop)
       .SetMethod("sendGetVoicePackInfoRequest",
                  &ReadAnythingAppController::SendGetVoicePackInfoRequest)
       .SetMethod("sendInstallVoicePackRequest",
@@ -1158,6 +1179,35 @@
   return static_cast<int>(read_anything::mojom::HighlightGranularity::kOff);
 }
 
+int ReadAnythingAppController::PauseButtonStopSource() const {
+  return base::to_underlying(ReadAloudAppModel::ReadAloudStopSource::kButton);
+}
+
+int ReadAnythingAppController::KeyboardShortcutStopSource() const {
+  return base::to_underlying(
+      ReadAloudAppModel::ReadAloudStopSource::kKeyboardShortcut);
+}
+
+int ReadAnythingAppController::EngineInterruptStopSource() const {
+  return base::to_underlying(
+      ReadAloudAppModel::ReadAloudStopSource::kEngineInterrupt);
+}
+
+int ReadAnythingAppController::EngineErrorStopSource() const {
+  return base::to_underlying(
+      ReadAloudAppModel::ReadAloudStopSource::kEngineError);
+}
+
+int ReadAnythingAppController::ContentFinishedStopSource() const {
+  return base::to_underlying(
+      ReadAloudAppModel::ReadAloudStopSource::kFinishContent);
+}
+
+int ReadAnythingAppController::UnexpectedUpdateContentStopSource() const {
+  return base::to_underlying(
+      ReadAloudAppModel::ReadAloudStopSource::kUnexpectedUpdateContent);
+}
+
 std::vector<ui::AXNodeID> ReadAnythingAppController::GetChildren(
     ui::AXNodeID ax_node_id) const {
   std::vector<ui::AXNodeID> child_ids;
@@ -1734,6 +1784,10 @@
 
 #if BUILDFLAG(IS_CHROMEOS)
 void ReadAnythingAppController::OnDeviceLocked() {
+  if (IsReadAloudEnabled() && read_aloud_model_.speech_playing()) {
+    read_aloud_model_.LogSpeechStop(
+        ReadAloudAppModel::ReadAloudStopSource::kLockChromeosDevice);
+  }
   // Signal to the WebUI that the device has been locked. We'll only receive
   // this callback on ChromeOS.
   ExecuteJavaScript("chrome.readingMode.onLockScreen();");
@@ -1744,6 +1798,22 @@
 }
 #endif
 
+void ReadAnythingAppController::OnReadingModeHidden() {
+  model_.set_will_hide(true);
+  if (read_aloud_model_.speech_playing()) {
+    read_aloud_model_.LogSpeechStop(
+        ReadAloudAppModel::ReadAloudStopSource::kCloseReadingMode);
+  }
+}
+
+void ReadAnythingAppController::OnTabWillDetach() {
+  model_.set_will_hide(true);
+  if (read_aloud_model_.speech_playing()) {
+    read_aloud_model_.LogSpeechStop(
+        ReadAloudAppModel::ReadAloudStopSource::kCloseTabOrWindow);
+  }
+}
+
 void ReadAnythingAppController::SetDefaultLanguageCode(
     const std::string& code) {
   std::string default_lang = std::string(language::ExtractBaseLanguage(code));
@@ -1851,6 +1921,29 @@
   read_aloud_model_.IncrementMetric(metric);
 }
 
+void ReadAnythingAppController::LogSpeechStop(int source) {
+  if (!IsReadAloudEnabled()) {
+    return;
+  }
+
+  // Don't log speech stopping if the reading mode panel is going to hide. That
+  // case is logged separately.
+  if (model_.will_hide()) {
+    return;
+  }
+
+  if (const auto maybe_enum =
+          ToEnum<ReadAloudAppModel::ReadAloudStopSource>(source)) {
+    read_aloud_model_.LogSpeechStop(maybe_enum.value());
+  }
+}
+
+void ReadAnythingAppController::OnUrlInformationSet() {
+  read_aloud_model_.LogSpeechStop(
+      model_.IsReload() ? ReadAloudAppModel::ReadAloudStopSource::kReloadPage
+                        : ReadAloudAppModel::ReadAloudStopSource::kChangePage);
+}
+
 void ReadAnythingAppController::OnScrolledToBottom() {
   if (IsGoogleDocs()) {
     // Scroll to the last display node shown on the Reading Mode side panel
diff --git a/chrome/renderer/accessibility/read_anything/read_anything_app_controller.h b/chrome/renderer/accessibility/read_anything/read_anything_app_controller.h
index 14eb48a..5bdf1d20f 100644
--- a/chrome/renderer/accessibility/read_anything/read_anything_app_controller.h
+++ b/chrome/renderer/accessibility/read_anything/read_anything_app_controller.h
@@ -134,6 +134,8 @@
   void ScreenAIServiceReady() override;
   void OnGetVoicePackInfo(
       read_anything::mojom::VoicePackInfoPtr voice_pack_info) override;
+  void OnReadingModeHidden() override;
+  void OnTabWillDetach() override;
 #if BUILDFLAG(IS_CHROMEOS)
   void OnDeviceLocked() override;
 #else
@@ -186,6 +188,12 @@
   int PhraseHighlighting() const;
   int SentenceHighlighting() const;
   int NoHighlighting() const;
+  int PauseButtonStopSource() const;
+  int KeyboardShortcutStopSource() const;
+  int EngineInterruptStopSource() const;
+  int EngineErrorStopSource() const;
+  int ContentFinishedStopSource() const;
+  int UnexpectedUpdateContentStopSource() const;
   std::string GetStoredVoice() const;
   std::vector<std::string> GetLanguagesEnabledInPref() const;
   std::vector<ui::AXNodeID> GetChildren(ui::AXNodeID ax_node_id) const;
@@ -376,7 +384,10 @@
 
   // Helpers for logging UmaHistograms based on times recorded in WebUI.
   void IncrementMetricCount(const std::string& metric);
-  void LogSpeechEventCounts();
+
+  void LogSpeechStop(int source);
+
+  void OnUrlInformationSet();
 
   // Stores a screenshot of the page and triggers distillation to record protos.
   // This function is not used in production and is behind the disabled
diff --git a/chrome/renderer/accessibility/read_anything/read_anything_app_controller_browsertest.cc b/chrome/renderer/accessibility/read_anything/read_anything_app_controller_browsertest.cc
index fe62db3..187d63e 100644
--- a/chrome/renderer/accessibility/read_anything/read_anything_app_controller_browsertest.cc
+++ b/chrome/renderer/accessibility/read_anything/read_anything_app_controller_browsertest.cc
@@ -15,16 +15,19 @@
 #include "base/files/scoped_temp_dir.h"
 #include "base/memory/raw_ptr.h"
 #include "base/path_service.h"
+#include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/types/cxx23_to_underlying.h"
 #include "build/build_config.h"
 #include "chrome/common/read_anything/read_anything_util.h"
 #include "chrome/renderer/accessibility/ax_tree_distiller.h"
 #include "chrome/renderer/accessibility/phrase_segmentation/dependency_parser_model.h"
+#include "chrome/renderer/accessibility/read_anything/read_aloud_app_model.h"
 #include "chrome/renderer/accessibility/read_anything/read_anything_test_utils.h"
 #include "chrome/test/base/chrome_render_view_test.h"
 #include "content/public/renderer/render_frame.h"
 #include "read_anything_test_utils.h"
+#include "services/metrics/public/cpp/ukm_source_id.h"
 #include "services/strings/grit/services_strings.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "ui/accessibility/accessibility_features.h"
@@ -316,6 +319,10 @@
     controller().InitAXPositionWithNode(nodes[0].id);
   }
 
+  void EnableReadAloud() {
+    scoped_feature_list_.InitAndEnableFeature(features::kReadAnythingReadAloud);
+  }
+
   ui::AXTreeID tree_id_;
   raw_ptr<MockAXTreeDistiller, DanglingUntriaged> distiller_ = nullptr;
   testing::StrictMock<MockReadAnythingUntrustedPageHandler> page_handler_;
@@ -334,11 +341,141 @@
 #else
   EXPECT_FALSE(controller().IsReadAloudEnabled());
 
-  scoped_feature_list_.InitAndEnableFeature(features::kReadAnythingReadAloud);
+  EnableReadAloud();
   EXPECT_TRUE(controller().IsReadAloudEnabled());
 #endif  // IS_CHROMEOS
 }
 
+#if BUILDFLAG(IS_CHROMEOS)
+TEST_F(ReadAnythingAppControllerTest, OnDeviceLocked_OnlyLogsIfSpeechPlaying) {
+  read_aloud_model().set_speech_playing(false);
+  base::HistogramTester histogram_tester;
+
+  controller().OnDeviceLocked();
+  EXPECT_EQ(0, histogram_tester.GetTotalSum(
+                   ReadAloudAppModel::kSpeechStopSourceHistogramName));
+
+  EnableReadAloud();
+  controller().OnDeviceLocked();
+  EXPECT_EQ(0, histogram_tester.GetTotalSum(
+                   ReadAloudAppModel::kSpeechStopSourceHistogramName));
+
+  read_aloud_model().set_speech_playing(true);
+  controller().OnDeviceLocked();
+  histogram_tester.ExpectUniqueSample(
+      ReadAloudAppModel::kSpeechStopSourceHistogramName,
+      ReadAloudAppModel::ReadAloudStopSource::kLockChromeosDevice, 1);
+}
+#endif
+
+TEST_F(ReadAnythingAppControllerTest,
+       OnReadingModeHidden_OnlyLogsIfSpeechPlaying) {
+  read_aloud_model().set_speech_playing(false);
+  base::HistogramTester histogram_tester;
+
+  controller().OnReadingModeHidden();
+  EXPECT_EQ(0, histogram_tester.GetTotalSum(
+                   ReadAloudAppModel::kSpeechStopSourceHistogramName));
+
+  EnableReadAloud();
+  controller().OnReadingModeHidden();
+  EXPECT_EQ(0, histogram_tester.GetTotalSum(
+                   ReadAloudAppModel::kSpeechStopSourceHistogramName));
+
+  read_aloud_model().set_speech_playing(true);
+  controller().OnReadingModeHidden();
+  histogram_tester.ExpectUniqueSample(
+      ReadAloudAppModel::kSpeechStopSourceHistogramName,
+      ReadAloudAppModel::ReadAloudStopSource::kCloseReadingMode, 1);
+}
+
+TEST_F(ReadAnythingAppControllerTest, OnTabWillDetach_OnlyLogsIfSpeechPlaying) {
+  read_aloud_model().set_speech_playing(false);
+  base::HistogramTester histogram_tester;
+
+  controller().OnTabWillDetach();
+  EXPECT_EQ(0, histogram_tester.GetTotalSum(
+                   ReadAloudAppModel::kSpeechStopSourceHistogramName));
+
+  EnableReadAloud();
+  controller().OnTabWillDetach();
+  EXPECT_EQ(0, histogram_tester.GetTotalSum(
+                   ReadAloudAppModel::kSpeechStopSourceHistogramName));
+
+  read_aloud_model().set_speech_playing(true);
+  controller().OnTabWillDetach();
+  histogram_tester.ExpectUniqueSample(
+      ReadAloudAppModel::kSpeechStopSourceHistogramName,
+      ReadAloudAppModel::ReadAloudStopSource::kCloseTabOrWindow, 1);
+}
+
+TEST_F(ReadAnythingAppControllerTest, OnUrlInformationSet_LogsReload) {
+  EnableReadAloud();
+  read_aloud_model().set_speech_playing(true);
+  ui::AXTreeUpdate update1;
+  ui::AXTreeID id_1 = ui::AXTreeID::CreateNewAXTreeID();
+  test::SetUpdateTreeID(&update1, id_1);
+  ui::AXNodeData root1 = test::LinkNode(/* id= */ 1, "https://www.google.com");
+  update1.root_id = root1.id;
+  update1.nodes = {std::move(root1)};
+
+  ui::AXTreeUpdate update2;
+  ui::AXTreeID id_2 = ui::AXTreeID::CreateNewAXTreeID();
+  test::SetUpdateTreeID(&update2, id_2);
+  ui::AXNodeData root2 = test::LinkNode(/* id= */ 5, "https://www.google.com");
+  update2.root_id = root2.id;
+  update2.nodes = {std::move(root2)};
+  base::HistogramTester histogram_tester;
+
+  AccessibilityEventReceived({std::move(update1)});
+  controller().OnActiveAXTreeIDChanged(id_1, ukm::kInvalidSourceId, false);
+  EXPECT_TRUE(
+      model().tree_infos_for_testing().at(id_1)->is_url_information_set);
+  histogram_tester.ExpectBucketCount(
+      ReadAloudAppModel::kSpeechStopSourceHistogramName,
+      ReadAloudAppModel::ReadAloudStopSource::kChangePage, 1);
+
+  AccessibilityEventReceived({std::move(update2)});
+  controller().OnActiveAXTreeIDChanged(id_2, ukm::kInvalidSourceId, false);
+  EXPECT_TRUE(
+      model().tree_infos_for_testing().at(id_2)->is_url_information_set);
+  histogram_tester.ExpectBucketCount(
+      ReadAloudAppModel::kSpeechStopSourceHistogramName,
+      ReadAloudAppModel::ReadAloudStopSource::kReloadPage, 1);
+}
+
+TEST_F(ReadAnythingAppControllerTest, OnUrlInformationSet_LogsNewPage) {
+  EnableReadAloud();
+  read_aloud_model().set_speech_playing(true);
+  ui::AXTreeUpdate update1;
+  ui::AXTreeID id_1 = ui::AXTreeID::CreateNewAXTreeID();
+  test::SetUpdateTreeID(&update1, id_1);
+  ui::AXNodeData root1 = test::LinkNode(/* id= */ 1, "https://www.google.com");
+  update1.root_id = root1.id;
+  update1.nodes = {std::move(root1)};
+
+  ui::AXTreeUpdate update2;
+  ui::AXTreeID id_2 = ui::AXTreeID::CreateNewAXTreeID();
+  test::SetUpdateTreeID(&update2, id_2);
+  ui::AXNodeData root2 = test::LinkNode(/* id= */ 5, "https://waymo.com");
+  update2.root_id = root2.id;
+  update2.nodes = {std::move(root2)};
+  base::HistogramTester histogram_tester;
+
+  AccessibilityEventReceived({std::move(update1)});
+  controller().OnActiveAXTreeIDChanged(id_1, ukm::kInvalidSourceId, false);
+  EXPECT_TRUE(
+      model().tree_infos_for_testing().at(id_1)->is_url_information_set);
+
+  AccessibilityEventReceived({std::move(update2)});
+  controller().OnActiveAXTreeIDChanged(id_2, ukm::kInvalidSourceId, false);
+  EXPECT_TRUE(
+      model().tree_infos_for_testing().at(id_2)->is_url_information_set);
+  histogram_tester.ExpectBucketCount(
+      ReadAloudAppModel::kSpeechStopSourceHistogramName,
+      ReadAloudAppModel::ReadAloudStopSource::kChangePage, 2);
+}
+
 TEST_F(ReadAnythingAppControllerTest, OnLetterSpacingChange_ValidChange) {
   static constexpr auto kLetterSpacing =
       read_anything::mojom::LetterSpacing::kWide;
diff --git a/chrome/renderer/accessibility/read_anything/read_anything_app_model.cc b/chrome/renderer/accessibility/read_anything/read_anything_app_model.cc
index 4b55a56e..afbf2873 100644
--- a/chrome/renderer/accessibility/read_anything/read_anything_app_model.cc
+++ b/chrome/renderer/accessibility/read_anything/read_anything_app_model.cc
@@ -66,37 +66,6 @@
   return parent == node ? nullptr : parent;
 }
 
-void SetTreeInfoUrlInformation(ReadAnythingAppModel::AXTreeInfo& tree_info) {
-  // If the url information has already been set for this tree, do nothing.
-  if (tree_info.is_url_information_set) {
-    return;
-  }
-
-  // If the tree manager is not the root manager, do nothing.
-  CHECK(tree_info.manager);
-  if (!tree_info.manager->IsRoot()) {
-    return;
-  }
-
-  // If the tree doesn't have a root, or the root doesn't have a url set, do
-  // nothing.
-  const ui::AXNode* const root = tree_info.manager->GetRoot();
-  if (!root || !root->HasStringAttribute(ax::mojom::StringAttribute::kUrl)) {
-    return;
-  }
-
-  // A Google Docs URL is in the form of "https://docs.google.com/document*" or
-  // "https://docs.sandbox.google.com/document*".
-  const GURL url(root->GetStringAttribute(ax::mojom::StringAttribute::kUrl));
-  tree_info.is_docs = url.SchemeIsHTTPOrHTTPS() &&
-                      (url.DomainIs("docs.google.com") ||
-                       url.DomainIs("docs.sandbox.google.com")) &&
-                      url.path().starts_with("/document") &&
-                      !url.ExtractFileName().empty();
-
-  tree_info.is_url_information_set = true;
-}
-
 // These values are persisted to logs. Entries should not be renumbered and
 // numeric values should never be reused.
 // LINT.IfChange(ReadAnythingHeuristics)
@@ -428,6 +397,59 @@
   return base::Contains(tree_infos_, tree_id);
 }
 
+void ReadAnythingAppModel::SetUrlInformationCallback(
+    base::OnceCallback<void()> callback) {
+  // If the given tree already has its url information set, run the callback
+  // immediately.
+  if (tree_infos_.contains(active_tree_id_) &&
+      tree_infos_.at(active_tree_id_)->is_url_information_set) {
+    std::move(callback).Run();
+    return;
+  }
+
+  set_url_information_callback_ = std::move(callback);
+}
+
+void ReadAnythingAppModel::SetTreeInfoUrlInformation(
+    ReadAnythingAppModel::AXTreeInfo& tree_info) {
+  // If the url information has already been set for this tree, do nothing.
+  if (tree_info.is_url_information_set) {
+    return;
+  }
+
+  // If the tree manager is not the root manager, do nothing.
+  CHECK(tree_info.manager);
+  if (!tree_info.manager->IsRoot()) {
+    return;
+  }
+
+  // If the tree doesn't have a root, or the root doesn't have a url set, do
+  // nothing.
+  const ui::AXNode* const root = tree_info.manager->GetRoot();
+  if (!root || !root->HasStringAttribute(ax::mojom::StringAttribute::kUrl)) {
+    return;
+  }
+
+  // A Google Docs URL is in the form of "https://docs.google.com/document*" or
+  // "https://docs.sandbox.google.com/document*".
+  const GURL url(root->GetStringAttribute(ax::mojom::StringAttribute::kUrl));
+  tree_info.is_reload =
+      !previous_tree_url_.empty() && (previous_tree_url_ == url.GetContent());
+
+  tree_info.is_docs = url.SchemeIsHTTPOrHTTPS() &&
+                      (url.DomainIs("docs.google.com") ||
+                       url.DomainIs("docs.sandbox.google.com")) &&
+                      url.path().starts_with("/document") &&
+                      !url.ExtractFileName().empty();
+
+  tree_info.is_url_information_set = true;
+  previous_tree_url_ = url.GetContent();
+
+  if (!set_url_information_callback_.is_null()) {
+    std::move(set_url_information_callback_).Run();
+  }
+}
+
 bool ReadAnythingAppModel::IsDocs() const {
   // Sometimes during an initial page load, this may be called before the
   // tree has been initialized. If this happens, IsDocs should return false
@@ -439,6 +461,14 @@
   return tree_infos_.at(active_tree_id_)->is_docs;
 }
 
+bool ReadAnythingAppModel::IsReload() const {
+  if (!tree_infos_.contains(active_tree_id_)) {
+    return false;
+  }
+
+  return tree_infos_.at(active_tree_id_)->is_reload;
+}
+
 void ReadAnythingAppModel::AddPendingUpdates(const ui::AXTreeID& tree_id,
                                              Updates& updates) {
   Updates& update = pending_updates_[tree_id];
@@ -815,7 +845,14 @@
       case ax::mojom::Event::kTooltipClosed:
       case ax::mojom::Event::kTooltipOpened:
       case ax::mojom::Event::kTreeChanged:
+        if (!features::IsReadAnythingReadAloudEnabled()) {
+          break;
+        }
+        [[fallthrough]];
       case ax::mojom::Event::kValueChanged:
+        if (!features::IsReadAnythingReadAloudEnabled()) {
+          reset_draw_timer_ = true;
+        }
         break;
       case ax::mojom::Event::kAriaAttributeChangedDeprecated:
       case ax::mojom::Event::kMenuListValueChangedDeprecated:
@@ -879,8 +916,11 @@
       // After the user finishes typing something we wait for a timer and redraw
       // to capture the input.
       case ui::AXEventGenerator::Event::EDITABLE_TEXT_CHANGED:
-        reset_draw_timer_ = true;
-        break;
+        if (features::IsReadAnythingReadAloudEnabled()) {
+          reset_draw_timer_ = true;
+          break;
+        }
+        [[fallthrough]];
       // Audit these events e.g. to trigger distillation.
       case ui::AXEventGenerator::Event::NONE:
       case ui::AXEventGenerator::Event::ACCESS_KEY_CHANGED:
diff --git a/chrome/renderer/accessibility/read_anything/read_anything_app_model.h b/chrome/renderer/accessibility/read_anything/read_anything_app_model.h
index 2e83defe..8140e1c 100644
--- a/chrome/renderer/accessibility/read_anything/read_anything_app_model.h
+++ b/chrome/renderer/accessibility/read_anything/read_anything_app_model.h
@@ -20,6 +20,7 @@
 #include "services/metrics/public/cpp/ukm_source_id.h"
 #include "ui/accessibility/ax_event_generator.h"
 #include "ui/accessibility/ax_node_id_forward.h"
+#include "ui/accessibility/ax_tree_id.h"
 #include "ui/accessibility/ax_tree_manager.h"
 #include "ui/accessibility/ax_tree_update_forward.h"
 
@@ -79,6 +80,10 @@
     // AXTrees have this set.
     bool is_docs = false;
 
+    // Whether the latest tree is a reload of the previous tree. If false, the
+    // latest tree is a new page.
+    bool is_reload = false;
+
     // TODO(41496290): Include any information that is associated with a
     // particular AXTree, namely is_pdf. Right now, this is set every time the
     // active ax tree id changes; instead, it should be set once when a new tree
@@ -201,6 +206,9 @@
     requires_tree_lang_ = requires_tree_lang;
   }
 
+  bool will_hide() const { return will_hide_; }
+  void set_will_hide(bool will_hide) { will_hide_ = will_hide; }
+
   const std::vector<ui::AXNodeID>& content_node_ids() const {
     return content_node_ids_;
   }
@@ -225,8 +233,10 @@
 
   int GetNumSelections() const;
   void SetNumSelections(int num_selections);
-
+  void SetTreeInfoUrlInformation(AXTreeInfo& tree_info);
+  void SetUrlInformationCallback(base::OnceCallback<void()> callback);
   bool IsDocs() const;
+  bool IsReload() const;
 
   ui::AXNode* GetAXNode(const ui::AXNodeID& ax_node_id) const;
 
@@ -356,6 +366,10 @@
   // child).
   ui::AXTreeID active_tree_id_ = ui::AXTreeIDUnknown();
 
+  // For determining whether the latest tree is a reload or new page.
+  std::string previous_tree_url_;
+  base::OnceCallback<void()> set_url_information_callback_;
+
   // PDFs are handled differently than regular webpages. That is because they
   // are stored in a different web contents and the actual PDF text is inside an
   // iframe. In order to get tree information from the PDF web contents, we need
@@ -445,6 +459,8 @@
   // that here.
   bool requires_tree_lang_ = false;
 
+  bool will_hide_ = false;
+
   // List of observers of model state changes.
   base::ObserverList<ModelObserver, /*check_empty=*/true> observers_;
 
diff --git a/chrome/renderer/accessibility/read_anything/read_anything_app_model_browsertest.cc b/chrome/renderer/accessibility/read_anything/read_anything_app_model_browsertest.cc
index 0d1f336..fb4ad8e5 100644
--- a/chrome/renderer/accessibility/read_anything/read_anything_app_model_browsertest.cc
+++ b/chrome/renderer/accessibility/read_anything/read_anything_app_model_browsertest.cc
@@ -11,12 +11,15 @@
 #include <vector>
 
 #include "base/containers/span.h"
+#include "base/functional/bind.h"
 #include "base/memory/raw_ptr.h"
+#include "base/test/scoped_feature_list.h"
 #include "base/threading/platform_thread.h"
 #include "chrome/test/base/chrome_render_view_test.h"
 #include "read_anything_test_utils.h"
 #include "services/strings/grit/services_strings.h"
 #include "testing/gmock/include/gmock/gmock.h"
+#include "ui/accessibility/accessibility_features.h"
 #include "ui/accessibility/ax_enums.mojom-shared.h"
 #include "ui/accessibility/ax_event.h"
 #include "ui/accessibility/ax_node_id_forward.h"
@@ -46,6 +49,10 @@
   EXPECT_FALSE(model().IsDocs());
 }
 
+TEST_F(ReadAnythingAppModelNoInitTest, IsReload_FalseBeforeTreeInitialization) {
+  EXPECT_FALSE(model().IsReload());
+}
+
 class ReadAnythingAppModelTest : public ChromeRenderViewTest {
  public:
   ReadAnythingAppModelTest() = default;
@@ -53,6 +60,11 @@
   ReadAnythingAppModelTest& operator=(const ReadAnythingAppModelTest&) = delete;
   ~ReadAnythingAppModelTest() override = default;
 
+  const std::string DOCS_URL =
+      "https://docs.google.com/document/d/"
+      "1t6x1PQaQWjE8wb9iyYmFaoK1XAEgsl8G1Hx3rzfpoKA/"
+      "edit?ouid=103677288878638916900&usp=docs_home&ths=true";
+
   void SetUp() override {
     ChromeRenderViewTest::SetUp();
 
@@ -87,6 +99,10 @@
         speech_playing);
   }
 
+  void EnableReadAloud() {
+    scoped_feature_list_.InitAndEnableFeature(features::kReadAnythingReadAloud);
+  }
+
   std::set<ui::AXNodeID> GetNotIgnoredIds(base::span<const ui::AXNodeID> ids) {
     std::set<ui::AXNodeID> set;
     for (auto id : ids) {
@@ -100,6 +116,10 @@
     model().ComputeDisplayNodeIdsForDistilledTree();
   }
 
+  void SetUrlInformationCallback() { ranSetUrlInformationCallback_ = true; }
+
+  bool RanSetUrlInformationCallback() { return ranSetUrlInformationCallback_; }
+
   std::vector<int> SendSimpleUpdateAndGetChildIds() {
     // Set the name of each node to be its id.
     ui::AXTreeUpdate initial_update;
@@ -120,6 +140,8 @@
 
  private:
   ReadAnythingAppModel model_;
+  bool ranSetUrlInformationCallback_ = false;
+  base::test::ScopedFeatureList scoped_feature_list_;
 };
 
 TEST_F(ReadAnythingAppModelTest, FontName) {
@@ -152,6 +174,129 @@
   EXPECT_EQ(color, model().color_theme());
 }
 
+TEST_F(ReadAnythingAppModelTest, SetTreeInfoUrlInformation_RunsCallback) {
+  ui::AXTreeUpdate update;
+  ui::AXTreeID tree_id = ui::AXTreeID::CreateNewAXTreeID();
+  test::SetUpdateTreeID(&update, tree_id);
+  ui::AXNodeData root = test::LinkNode(/* id= */ 1, DOCS_URL);
+  update.root_id = root.id;
+  update.nodes = {std::move(root)};
+  model().SetUrlInformationCallback(
+      base::BindOnce(&ReadAnythingAppModelTest::SetUrlInformationCallback,
+                     base::Unretained(this)));
+  EXPECT_FALSE(RanSetUrlInformationCallback());
+
+  AccessibilityEventReceived({std::move(update)});
+  model().SetActiveTreeId(tree_id);
+
+  EXPECT_TRUE(RanSetUrlInformationCallback());
+}
+
+TEST_F(ReadAnythingAppModelTest, SetTreeInfoUrlInformation_IsDocs) {
+  ui::AXTreeUpdate update;
+  ui::AXTreeID tree_id = ui::AXTreeID::CreateNewAXTreeID();
+  test::SetUpdateTreeID(&update, tree_id);
+  ui::AXNodeData root = test::LinkNode(/* id= */ 1, DOCS_URL);
+  update.root_id = root.id;
+  update.nodes = {std::move(root)};
+
+  AccessibilityEventReceived({std::move(update)});
+  model().SetActiveTreeId(tree_id);
+
+  EXPECT_TRUE(
+      model().tree_infos_for_testing().at(tree_id)->is_url_information_set);
+  EXPECT_TRUE(model().IsDocs());
+}
+
+TEST_F(ReadAnythingAppModelTest, SetTreeInfoUrlInformation_IsNotDocs) {
+  ui::AXTreeUpdate update;
+  ui::AXTreeID tree_id = ui::AXTreeID::CreateNewAXTreeID();
+  test::SetUpdateTreeID(&update, tree_id);
+  ui::AXNodeData root = test::LinkNode(/* id= */ 1, "https://www.google.com");
+  update.root_id = root.id;
+  update.nodes = {std::move(root)};
+
+  AccessibilityEventReceived({std::move(update)});
+  model().SetActiveTreeId(tree_id);
+
+  EXPECT_TRUE(
+      model().tree_infos_for_testing().at(tree_id)->is_url_information_set);
+  EXPECT_FALSE(model().IsDocs());
+}
+
+TEST_F(ReadAnythingAppModelTest,
+       SetTreeInfoUrlInformation_FirstTreeIsNotReload) {
+  ui::AXTreeUpdate update;
+  ui::AXTreeID tree_id = ui::AXTreeID::CreateNewAXTreeID();
+  test::SetUpdateTreeID(&update, tree_id);
+  ui::AXNodeData root = test::LinkNode(/* id= */ 1, "https://www.google.com");
+  update.root_id = root.id;
+  update.nodes = {std::move(root)};
+
+  AccessibilityEventReceived({std::move(update)});
+  model().SetActiveTreeId(tree_id);
+
+  EXPECT_TRUE(
+      model().tree_infos_for_testing().at(tree_id)->is_url_information_set);
+  EXPECT_FALSE(model().IsReload());
+}
+
+TEST_F(ReadAnythingAppModelTest, SetTreeInfoUrlInformation_IsReload) {
+  ui::AXTreeUpdate update1;
+  ui::AXTreeID id_1 = ui::AXTreeID::CreateNewAXTreeID();
+  test::SetUpdateTreeID(&update1, id_1);
+  ui::AXNodeData root1 = test::LinkNode(/* id= */ 1, "https://www.google.com");
+  update1.root_id = root1.id;
+  update1.nodes = {std::move(root1)};
+
+  ui::AXTreeUpdate update2;
+  ui::AXTreeID id_2 = ui::AXTreeID::CreateNewAXTreeID();
+  test::SetUpdateTreeID(&update2, id_2);
+  ui::AXNodeData root2 = test::LinkNode(/* id= */ 5, "https://www.google.com");
+  update2.root_id = root2.id;
+  update2.nodes = {std::move(root2)};
+
+  AccessibilityEventReceived({std::move(update1)});
+  model().SetActiveTreeId(id_1);
+  EXPECT_TRUE(
+      model().tree_infos_for_testing().at(id_1)->is_url_information_set);
+  EXPECT_FALSE(model().IsReload());
+
+  AccessibilityEventReceived({std::move(update2)});
+  model().SetActiveTreeId(id_2);
+  EXPECT_TRUE(
+      model().tree_infos_for_testing().at(id_2)->is_url_information_set);
+  EXPECT_TRUE(model().IsReload());
+}
+
+TEST_F(ReadAnythingAppModelTest, SetTreeInfoUrlInformation_IsNotReload) {
+  ui::AXTreeUpdate update1;
+  ui::AXTreeID id_1 = ui::AXTreeID::CreateNewAXTreeID();
+  test::SetUpdateTreeID(&update1, id_1);
+  ui::AXNodeData root1 = test::LinkNode(/* id= */ 1, "https://www.google.com");
+  update1.root_id = root1.id;
+  update1.nodes = {std::move(root1)};
+
+  ui::AXTreeUpdate update2;
+  ui::AXTreeID id_2 = ui::AXTreeID::CreateNewAXTreeID();
+  test::SetUpdateTreeID(&update2, id_2);
+  ui::AXNodeData root2 = test::LinkNode(/* id= */ 5, "https://www.youtube.com");
+  update2.root_id = root2.id;
+  update2.nodes = {std::move(root2)};
+
+  AccessibilityEventReceived({std::move(update1)});
+  model().SetActiveTreeId(id_1);
+  EXPECT_TRUE(
+      model().tree_infos_for_testing().at(id_1)->is_url_information_set);
+  EXPECT_FALSE(model().IsReload());
+
+  AccessibilityEventReceived({std::move(update2)});
+  model().SetActiveTreeId(id_2);
+  EXPECT_TRUE(
+      model().tree_infos_for_testing().at(id_2)->is_url_information_set);
+  EXPECT_FALSE(model().IsReload());
+}
+
 TEST_F(ReadAnythingAppModelTest, InsertIdIfNotIgnored) {
   ui::AXTreeUpdate update;
   test::SetUpdateTreeID(&update, tree_id_);
@@ -1704,7 +1849,31 @@
   EXPECT_TRUE(model().selection_node_ids().empty());
 }
 
+// The read aloud flag is already enabled on ChromeOS.
+#if !BUILDFLAG(IS_CHROMEOS)
 TEST_F(ReadAnythingAppModelTest, ContentEditableValueChanged_ResetsDrawTimer) {
+  ui::AXTreeUpdate update;
+  test::SetUpdateTreeID(&update, tree_id_);
+  ui::AXNodeData node1;
+  static constexpr int kId = 1;
+  node1.id = kId;
+  update.nodes = {std::move(node1)};
+  ReadAnythingAppModel::Updates updates = {std::move(update)};
+
+  ui::AXEvent event;
+  event.id = kId;
+  event.event_type = ax::mojom::Event::kValueChanged;
+  std::vector<ui::AXEvent> events = {std::move(event)};
+  // This update changes the structure of the tree. When the controller receives
+  // it in AccessibilityEventReceived, it will re-distill the tree.
+  model().AccessibilityEventReceived(tree_id_, updates, events, false);
+  EXPECT_TRUE(model().reset_draw_timer());
+}
+#endif  // !BUILDFLAG(IS_CHROMEOS)
+
+TEST_F(ReadAnythingAppModelTest,
+       ContentEditableValueChanged_ReadAloudEnabled_ResetsDrawTimer) {
+  EnableReadAloud();
   // Create a tree with a text field.
   ui::AXNodeData root;
   root.id = 1;
diff --git a/chrome/renderer/actor/BUILD.gn b/chrome/renderer/actor/BUILD.gn
index bdf99d6f..a710f5f 100644
--- a/chrome/renderer/actor/BUILD.gn
+++ b/chrome/renderer/actor/BUILD.gn
@@ -8,8 +8,13 @@
 
 source_set("actor") {
   sources = [
+    "click_tool.cc",
+    "click_tool.h",
+    "tool_base.h",
     "tool_executor.cc",
     "tool_executor.h",
+    "tool_utils.cc",
+    "tool_utils.h",
   ]
 
   public_deps = [
@@ -19,3 +24,16 @@
 
   deps = [ "//chrome/common" ]
 }
+
+source_set("browser_tests") {
+  testonly = true
+  sources = [ "tool_utils_browsertest.cc" ]
+  deps = [
+    ":actor",
+    "//base/test:test_support",
+    "//chrome/test:test_support",
+    "//chrome/test:test_support_ui",
+    "//content/test:test_support",
+    "//testing/gtest",
+  ]
+}
diff --git a/chrome/renderer/actor/click_tool.cc b/chrome/renderer/actor/click_tool.cc
new file mode 100644
index 0000000..3d8bd255
--- /dev/null
+++ b/chrome/renderer/actor/click_tool.cc
@@ -0,0 +1,138 @@
+// 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/renderer/actor/click_tool.h"
+
+#include <cstdint>
+#include <optional>
+
+#include "base/logging.h"
+#include "base/time/time.h"
+#include "chrome/renderer/actor/tool_utils.h"
+#include "content/public/renderer/render_frame.h"
+#include "third_party/blink/public/common/input/web_coalesced_input_event.h"
+#include "third_party/blink/public/common/input/web_input_event.h"
+#include "third_party/blink/public/common/input/web_mouse_event.h"
+#include "third_party/blink/public/platform/web_input_event_result.h"
+#include "third_party/blink/public/web/web_element.h"
+#include "third_party/blink/public/web/web_frame_widget.h"
+#include "third_party/blink/public/web/web_local_frame.h"
+#include "third_party/blink/public/web/web_node.h"
+#include "ui/events/base_event_utils.h"
+#include "ui/gfx/geometry/point_f.h"
+#include "ui/latency/latency_info.h"
+
+namespace {
+constexpr base::TimeDelta kClickDelay = base::Milliseconds(50);
+}
+
+namespace actor {
+
+ClickTool::ClickTool(mojom::ClickActionPtr action,
+                     base::raw_ref<content::RenderFrame> frame)
+    : frame_(frame), action_(std::move(action)) {}
+
+ClickTool::~ClickTool() = default;
+
+blink::WebMouseEvent ClickTool::CreateClickMouseEvent(
+    const blink::WebNode& node,
+    mojom::ClickAction::Type type,
+    mojom::ClickAction::Count count,
+    blink::WebInputEvent::Type event_type,
+    const gfx::PointF& click_point) {
+  blink::WebMouseEvent mouse_event(
+      event_type, blink::WebInputEvent::kNoModifiers, ui::EventTimeForNow());
+
+  switch (type) {
+    case mojom::ClickAction::Type::kLeft: {
+      mouse_event.button = blink::WebMouseEvent::Button::kLeft;
+      break;
+    }
+    case mojom::ClickAction::Type::kRight: {
+      mouse_event.button = blink::WebMouseEvent::Button::kRight;
+      break;
+    }
+  }
+
+  switch (count) {
+    case mojom::ClickAction::Count::kSingle: {
+      mouse_event.click_count = 1;
+      break;
+    }
+    case mojom::ClickAction::Count::kDouble: {
+      mouse_event.click_count = 2;
+      break;
+    }
+  }
+
+  mouse_event.SetPositionInWidget(click_point);
+
+  // TODO(crbug.com/402082828): Find a way to set screen position.
+  //   const gfx::Rect offset =
+  //     render_frame_host_->GetRenderWidgetHost()->GetView()->GetViewBounds();
+  //   mouse_event_.SetPositionInScreen(point.x() + offset.x(),
+  //                                    point.y() + offset.y());
+  return mouse_event;
+}
+
+void ClickTool::Execute(ToolFinishedCallback callback) {
+  if (!frame_->GetWebFrame()->FrameWidget()) {
+    DLOG(ERROR) << "RenderWidget is invalid.";
+    std::move(callback).Run(false);
+    return;
+  }
+
+  mojom::ToolTargetPtr& target = action_->target;
+
+  // Currently only support DOMNodeId as target.
+  int32_t dom_node_id = target->dom_node_id;
+  CHECK(dom_node_id);
+
+  blink::WebNode node = GetNodeFromId(frame_.get(), dom_node_id);
+  if (node.IsNull()) {
+    DLOG(ERROR) << "Cannot find dom node with id " << dom_node_id;
+    std::move(callback).Run(false);
+    return;
+  }
+
+  std::optional<gfx::PointF> click_point = InteractionPointFromWebNode(node);
+  if (!click_point.has_value()) {
+    std::move(callback).Run(false);
+    return;
+  }
+
+  // Create and send MouseDown event
+  blink::WebMouseEvent mouse_down = CreateClickMouseEvent(
+      node, action_->type, action_->count,
+      blink::WebInputEvent::Type::kMouseDown, click_point.value());
+  blink::WebMouseEvent mouse_up = mouse_down;
+  blink::WebInputEventResult result =
+      frame_->GetWebFrame()->FrameWidget()->HandleInputEvent(
+          blink::WebCoalescedInputEvent(mouse_down, ui::LatencyInfo()));
+
+  if (result == blink::WebInputEventResult::kNotHandled ||
+      result == blink::WebInputEventResult::kHandledSuppressed) {
+    std::move(callback).Run(false);
+    return;
+  }
+
+  mouse_up.SetType(blink::WebInputEvent::Type::kMouseUp);
+  mouse_up.SetTimeStamp(mouse_down.TimeStamp() + kClickDelay);
+
+  // TODO(crbug.com/402082828): Delay the mouse up to simulate natural click
+  // after ToolExecutor lifetime update.
+
+  result = frame_->GetWebFrame()->FrameWidget()->HandleInputEvent(
+      blink::WebCoalescedInputEvent(std::move(mouse_up), ui::LatencyInfo()));
+
+  if (result == blink::WebInputEventResult::kNotHandled ||
+      result == blink::WebInputEventResult::kHandledSuppressed) {
+    std::move(callback).Run(false);
+    return;
+  }
+  std::move(callback).Run(true);
+  return;
+}
+
+}  // namespace actor
diff --git a/chrome/renderer/actor/click_tool.h b/chrome/renderer/actor/click_tool.h
new file mode 100644
index 0000000..cd17424d
--- /dev/null
+++ b/chrome/renderer/actor/click_tool.h
@@ -0,0 +1,63 @@
+// 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_RENDERER_ACTOR_CLICK_TOOL_H_
+#define CHROME_RENDERER_ACTOR_CLICK_TOOL_H_
+
+#include <cstdint>
+
+#include "base/memory/raw_ref.h"
+#include "base/memory/weak_ptr.h"
+#include "chrome/common/actor.mojom.h"
+#include "chrome/renderer/actor/tool_base.h"
+#include "third_party/blink/public/common/input/web_input_event.h"
+
+namespace blink {
+class WebNode;
+class WebMouseEvent;
+}  // namespace blink
+
+namespace content {
+class RenderFrame;
+}  // namespace content
+
+namespace gfx {
+class PointF;
+}  // namespace gfx
+
+namespace actor {
+
+// A tool that can be invoked to perform a click on a target.
+class ClickTool : public ToolBase {
+ public:
+  ClickTool(mojom::ClickActionPtr action,
+            base::raw_ref<content::RenderFrame> frame);
+  ~ClickTool() override;
+
+  // Performs a click on the specified node. Invoke callback with true if
+  // success and false otherwise.
+  void Execute(ToolFinishedCallback callback) override;
+
+ private:
+  blink::WebMouseEvent CreateClickMouseEvent(
+      const blink::WebNode& node,
+      const mojom::ClickAction::Type type,
+      const mojom::ClickAction::Count count,
+      blink::WebInputEvent::Type event_type,
+      const gfx::PointF& click_point);
+
+  void SendMouseUp(blink::WebMouseEvent mouse_event,
+                   ToolFinishedCallback callback);
+
+  // Raw ref since this is owned by ToolExecutor whose lifetime is tied to
+  // RenderFrame.
+  base::raw_ref<content::RenderFrame> frame_;
+  mojom::ClickActionPtr action_;
+
+  base::WeakPtrFactory<ClickTool> weak_factory_{this};
+};
+
+}  // namespace actor
+
+#endif  // CHROME_RENDERER_ACTOR_CLICK_TOOL_H_
diff --git a/chrome/renderer/actor/tool_base.h b/chrome/renderer/actor/tool_base.h
new file mode 100644
index 0000000..004fff0f
--- /dev/null
+++ b/chrome/renderer/actor/tool_base.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_RENDERER_ACTOR_TOOL_BASE_H_
+#define CHROME_RENDERER_ACTOR_TOOL_BASE_H_
+
+#include <cstdint>
+
+#include "base/functional/callback_forward.h"
+
+namespace actor {
+class ToolBase {
+ public:
+  using ToolFinishedCallback = base::OnceCallback<void(bool)>;
+  virtual void Execute(ToolFinishedCallback done_cb) = 0;
+  virtual ~ToolBase() = default;
+};
+}  // namespace actor
+
+#endif  // CHROME_RENDERER_ACTOR_TOOL_BASE_H_
diff --git a/chrome/renderer/actor/tool_executor.cc b/chrome/renderer/actor/tool_executor.cc
index df5d3ac..728fa6d 100644
--- a/chrome/renderer/actor/tool_executor.cc
+++ b/chrome/renderer/actor/tool_executor.cc
@@ -4,13 +4,17 @@
 
 #include "chrome/renderer/actor/tool_executor.h"
 
+#include <cstdint>
+#include <memory>
+
 #include "base/functional/callback.h"
+#include "base/memory/ptr_util.h"
+#include "base/notreached.h"
 #include "chrome/common/actor.mojom.h"
+#include "chrome/renderer/actor/click_tool.h"
 #include "content/public/renderer/render_frame.h"
-#include "mojo/public/cpp/bindings/pending_receiver.h"
 
 using content::RenderFrame;
-using mojo::PendingReceiver;
 
 namespace actor {
 
@@ -25,8 +29,28 @@
 
 void ToolExecutor::InvokeTool(mojom::ToolInvocationPtr request,
                               ToolExecutorCallback callback) {
-  // TODO(crbug.com/402731599): Implement tools.
-  std::move(callback).Run(true);
+  CHECK(!tool_);
+  switch (request->action->which()) {
+    case actor::mojom::ToolAction::Tag::kClick: {
+      // Check the mojom we received is in good shape.
+      CHECK(request->action->get_click());
+      tool_ = std::make_unique<ClickTool>(
+          std::move(request->action->get_click()), frame_);
+      break;
+    }
+  }
+  // It's safe to use base::Unretained as tool_ is owned by this object and
+  // tool_ has its own weak factory to manage the callback.
+  tool_->Execute(base::BindOnce(&ToolExecutor::ToolFinished,
+                                base::Unretained(this), std::move(callback)));
+}
+
+void ToolExecutor::ToolFinished(ToolExecutorCallback callback,
+                                bool tool_status) {
+  CHECK(tool_);
+  // Release current tool so we can accept a new tool invocation.
+  tool_.reset();
+  std::move(callback).Run(tool_status);
 }
 
 }  // namespace actor
diff --git a/chrome/renderer/actor/tool_executor.h b/chrome/renderer/actor/tool_executor.h
index 194b417e..02d3bd34 100644
--- a/chrome/renderer/actor/tool_executor.h
+++ b/chrome/renderer/actor/tool_executor.h
@@ -9,11 +9,12 @@
 #include "base/memory/raw_ref.h"
 #include "base/memory/stack_allocated.h"
 #include "chrome/common/chrome_render_frame.mojom.h"
+#include "chrome/renderer/actor/tool_base.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 
 namespace content {
 class RenderFrame;
-}
+}  // namespace content
 
 namespace actor {
 
@@ -36,9 +37,12 @@
                   ToolExecutorCallback callback);
 
  private:
-  // Raw ref since the executor is currently only stack allocated by the render
-  // frame so it must be outlived.
+  void ToolFinished(ToolExecutorCallback callback, bool tool_status);
+
+  // Raw ref since the executor is currently only stack allocated by the
+  // render frame so it must be outlived.
   base::raw_ref<content::RenderFrame> frame_;
+  std::unique_ptr<ToolBase> tool_;
 };
 
 }  // namespace actor
diff --git a/chrome/renderer/actor/tool_utils.cc b/chrome/renderer/actor/tool_utils.cc
new file mode 100644
index 0000000..91533605
--- /dev/null
+++ b/chrome/renderer/actor/tool_utils.cc
@@ -0,0 +1,42 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/actor/tool_utils.h"
+
+#include "content/public/renderer/render_frame.h"
+#include "third_party/blink/public/web/web_element.h"
+#include "third_party/blink/public/web/web_local_frame.h"
+#include "third_party/blink/public/web/web_node.h"
+#include "ui/gfx/geometry/point_f.h"
+#include "ui/gfx/geometry/rect.h"
+
+namespace actor {
+std::optional<gfx::PointF> InteractionPointFromWebNode(
+    const blink::WebNode& node) {
+  // Find and validate the bounding box.
+  blink::WebElement web_element = node.To<blink::WebElement>();
+  gfx::Rect rect = web_element.BoundsInWidget();
+  // Validate element is visible.
+  if (rect.width() == 0 || rect.height() == 0) {
+    return std::nullopt;
+  }
+  return {{rect.x() + rect.width() / 2.0f, rect.y() + rect.height() / 2.0f}};
+}
+
+blink::WebNode GetNodeFromId(const content::RenderFrame& frame,
+                             int32_t node_id) {
+  const blink::WebLocalFrame* web_frame = frame.GetWebFrame();
+  if (!web_frame) {
+    return blink::WebNode();
+  }
+
+  blink::WebNode node = blink::WebNode::FromDomNodeId(node_id);
+  // Make sure the node we're getting belongs to the document inside this frame.
+  if (node.IsNull() || node.GetDocument() != web_frame->GetDocument()) {
+    return blink::WebNode();
+  }
+  return node;
+}
+
+}  // namespace actor
diff --git a/chrome/renderer/actor/tool_utils.h b/chrome/renderer/actor/tool_utils.h
new file mode 100644
index 0000000..65c4cafc
--- /dev/null
+++ b/chrome/renderer/actor/tool_utils.h
@@ -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.
+
+#ifndef CHROME_RENDERER_ACTOR_TOOL_UTILS_H_
+#define CHROME_RENDERER_ACTOR_TOOL_UTILS_H_
+
+#include <cstdint>
+#include <optional>
+
+namespace blink {
+class WebNode;
+}  // namespace blink
+
+namespace content {
+class RenderFrame;
+}  // namespace content
+
+namespace gfx {
+class PointF;
+}  // namespace gfx
+
+namespace actor {
+
+blink::WebNode GetNodeFromId(const content::RenderFrame& frame,
+                             int32_t node_id);
+std::optional<gfx::PointF> InteractionPointFromWebNode(
+    const blink::WebNode& node);
+}  // namespace actor
+
+#endif  // CHROME_RENDERER_ACTOR_TOOL_UTILS_H_
diff --git a/chrome/renderer/actor/tool_utils_browsertest.cc b/chrome/renderer/actor/tool_utils_browsertest.cc
new file mode 100644
index 0000000..74a9b52
--- /dev/null
+++ b/chrome/renderer/actor/tool_utils_browsertest.cc
@@ -0,0 +1,139 @@
+// 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/renderer/actor/tool_utils.h"
+
+#include <memory>
+
+#include "chrome/renderer/actor/click_tool.h"
+#include "chrome/test/base/chrome_render_view_test.h"
+#include "content/public/renderer/render_frame.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/web/web_document.h"
+#include "third_party/blink/public/web/web_element.h"
+#include "third_party/blink/public/web/web_local_frame.h"
+#include "third_party/blink/public/web/web_node.h"
+#include "third_party/blink/public/web/web_view.h"
+#include "url/gurl.h"
+
+namespace actor {
+
+class ToolUtilsTest : public ChromeRenderViewTest {
+ public:
+  ToolUtilsTest() = default;
+
+ protected:
+  void SetUp() override {
+    ChromeRenderViewTest::SetUp();
+
+    // Create a basic HTML structure.
+    LoadHTML(
+        "<div id='div1'>42</div>"
+        "<div id='div2'>Goodbye</div>"
+        "<iframe id='iframe1' srcdoc='<div id=div3>Nested</div>'></iframe>");
+  }
+
+  blink::WebNode GetNodeByHtmlId(const std::string& html_id) {
+    blink::WebElement element =
+        GetMainRenderFrame()->GetWebFrame()->GetDocument().GetElementById(
+            blink::WebString::FromUTF8(html_id));
+    if (element.IsNull()) {
+      return blink::WebNode();
+    }
+    return blink::WebNode(element);
+  }
+
+  blink::WebNode GetIframeNodeByHtmlId(const std::string& iframe_id_str,
+                                       const std::string& html_id_str) {
+    const blink::WebElement iframe_element =
+        GetMainRenderFrame()->GetWebFrame()->GetDocument().GetElementById(
+            blink::WebString::FromUTF8(iframe_id_str));
+
+    const blink::WebElement child_element =
+        blink::WebFrame::FromFrameOwnerElement(iframe_element)
+            ->ToWebLocalFrame()
+            ->GetDocument()
+            .GetElementById(blink::WebString::FromUTF8(html_id_str));
+    if (child_element.IsNull()) {
+      return blink::WebNode();
+    }
+    return blink::WebNode(child_element);
+  }
+};
+
+// Tests with valid frame and node ID.
+TEST_F(ToolUtilsTest, GetNodeFromId_Valid) {
+  blink::WebNode div1 = GetNodeByHtmlId("div1");
+  ASSERT_FALSE(div1.IsNull());
+  int32_t div1_node_id = div1.GetDomNodeId();
+
+  blink::WebNode node = GetNodeFromId(*GetMainRenderFrame(), div1_node_id);
+  ASSERT_FALSE(node.IsNull());
+  EXPECT_EQ(div1_node_id, node.GetDomNodeId());
+  EXPECT_EQ(div1, node);
+
+  blink::WebNode div2 = GetNodeByHtmlId("div2");
+  ASSERT_FALSE(div2.IsNull());
+  int32_t div2_node_id = div2.GetDomNodeId();
+
+  blink::WebNode node2 = GetNodeFromId(*GetMainRenderFrame(), div2_node_id);
+  ASSERT_FALSE(node2.IsNull());
+  EXPECT_EQ(div2_node_id, node2.GetDomNodeId());
+  EXPECT_EQ(div2, node2);
+}
+
+// Tests with invalid node ID (node does not exist).
+TEST_F(ToolUtilsTest, GetNodeFromId_InvalidNodeId) {
+  blink::WebNode node = GetNodeFromId(*GetMainRenderFrame(), 123456);
+  EXPECT_TRUE(node.IsNull());
+}
+
+// Tests a node in a nested frame (iframe).
+TEST_F(ToolUtilsTest, GetNodeFromId_NestedFrame) {
+  // Verify that dom node inside iframe is not available in the frame.
+  blink::WebNode div3_in_main = GetNodeByHtmlId("div3");
+  ASSERT_TRUE(div3_in_main.IsNull());
+
+  // Get node within the iframe
+  blink::WebNode div3_node = GetIframeNodeByHtmlId("iframe1", "div3");
+  ASSERT_FALSE(div3_node.IsNull());
+  ASSERT_TRUE(div3_node.IsElementNode());
+  ASSERT_EQ("div3",
+            div3_node.To<blink::WebElement>().GetAttribute("id").Utf8());
+  int32_t div3_node_id = div3_node.GetDomNodeId();
+  ASSERT_NE(0, div3_node_id);
+
+  // Get the child RenderFrame* context
+  blink::WebNode iframe_node = GetNodeByHtmlId("iframe1");
+  ASSERT_FALSE(iframe_node.IsNull());
+  blink::WebFrame* child_web_frame = blink::WebFrame::FromFrameOwnerElement(
+      iframe_node.To<blink::WebElement>());
+  ASSERT_NE(nullptr, child_web_frame);
+  content::RenderFrame* child_frame =
+      content::RenderFrame::FromWebFrame(child_web_frame->ToWebLocalFrame());
+  ASSERT_NE(nullptr, child_frame);
+
+  // Find node div3 within the iframe.
+  blink::WebNode node_found_in_child =
+      GetNodeFromId(*child_frame, div3_node_id);
+  ASSERT_FALSE(node_found_in_child.IsNull());
+  EXPECT_EQ(div3_node_id, node_found_in_child.GetDomNodeId());
+  EXPECT_EQ(div3_node, node_found_in_child);  // Verify it's the exact same node
+
+  // Try to find iframe node (div3) using the main frame context -> fails
+  blink::WebNode node_not_found_in_main =
+      GetNodeFromId(*GetMainRenderFrame(), div3_node_id);
+  EXPECT_TRUE(node_not_found_in_main.IsNull());
+
+  // Try to find main frame node (div1) using the child frame context -> fails
+  blink::WebNode div1 = GetNodeByHtmlId("div1");  // Searches main frame only
+  ASSERT_FALSE(div1.IsNull());
+  int32_t div1_node_id = div1.GetDomNodeId();
+  ASSERT_NE(0, div1_node_id);
+  blink::WebNode node_not_found_in_child =
+      GetNodeFromId(*child_frame, div1_node_id);
+  EXPECT_TRUE(node_not_found_in_child.IsNull());
+}
+
+}  // namespace actor
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 2a407df..48488b2 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -3548,6 +3548,7 @@
         "//chrome/browser/glic:glic",
         "//chrome/browser/glic:impl",
         "//chrome/browser/glic:test_support",
+        "//chrome/renderer/actor:browser_tests",
       ]
     }
 
@@ -9409,6 +9410,8 @@
         "../browser/safe_browsing/chrome_enterprise_url_lookup_service_factory_unittest.cc",
         "../browser/safe_browsing/chrome_enterprise_url_lookup_service_unittest.cc",
       ]
+
+      deps += [ "//components/safe_browsing/core/browser/realtime:enterprise_url_lookup_service" ]
     }
   }
 
@@ -10793,6 +10796,7 @@
       "../browser/ui/webui/downloads/downloads_page_interactive_uitest.cc",
       "../browser/ui/webui/settings/performance_settings_interactive_uitest.cc",
       "../browser/ui/webui/settings/settings_interactive_uitest.cc",
+      "../browser/ui/webui/side_panel/customize_chrome/customize_chrome_interactive_uitest.cc",
       "../browser/ui/webui/side_panel/customize_chrome/wallpaper_search/wallpaper_search_interactive_uitest.cc",
       "../browser/webapps/web_app_offline_browsertest.cc",
       "../browser/webauthn/chrome_webauthn_autofill_interactive_uitest.cc",
diff --git a/chrome/test/android/BUILD.gn b/chrome/test/android/BUILD.gn
index 3267f8e..e4dbb6b 100644
--- a/chrome/test/android/BUILD.gn
+++ b/chrome/test/android/BUILD.gn
@@ -200,7 +200,6 @@
     "javatests/src/org/chromium/chrome/test/transit/AppMenuFacility.java",
     "javatests/src/org/chromium/chrome/test/transit/AutoResetCtaTransitTestRule.java",
     "javatests/src/org/chromium/chrome/test/transit/BaseCtaTransitTestRule.java",
-    "javatests/src/org/chromium/chrome/test/transit/BlankCTATabInitialStatePublicTransitRule.java",
     "javatests/src/org/chromium/chrome/test/transit/ChromeTabbedActivityEntryPoints.java",
     "javatests/src/org/chromium/chrome/test/transit/ChromeTransitTestRules.java",
     "javatests/src/org/chromium/chrome/test/transit/CtaAppMenuFacility.java",
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/test/transit/BlankCTATabInitialStatePublicTransitRule.java b/chrome/test/android/javatests/src/org/chromium/chrome/test/transit/BlankCTATabInitialStatePublicTransitRule.java
deleted file mode 100644
index 35cd91c..0000000
--- a/chrome/test/android/javatests/src/org/chromium/chrome/test/transit/BlankCTATabInitialStatePublicTransitRule.java
+++ /dev/null
@@ -1,83 +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.
-
-package org.chromium.chrome.test.transit;
-
-import org.junit.rules.RuleChain;
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-
-import org.chromium.base.test.transit.BatchedPublicTransitRule;
-import org.chromium.base.test.transit.EntryPointSentinelStation;
-import org.chromium.base.test.transit.Station;
-import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
-import org.chromium.chrome.test.batch.BlankCTATabInitialStateRule;
-import org.chromium.chrome.test.transit.ntp.RegularNewTabPageStation;
-import org.chromium.chrome.test.transit.page.PageStation;
-import org.chromium.chrome.test.transit.page.WebPageStation;
-import org.chromium.components.embedder_support.util.UrlConstants;
-
-/**
- * Wraps BlankCTATabInitialStateRule to be used in Public Transit batched tests.
- *
- * <p>TODO(crbug.com/404294940): Remove this after migrating all usages.
- *
- * @deprecated Use {@link ChromeTransitTestRules#autoResetCtaActivityRule()} instead.
- */
-@Deprecated
-public class BlankCTATabInitialStatePublicTransitRule implements TestRule {
-
-    private final ChromeTabbedActivityTestRule mActivityTestRule;
-
-    public final BatchedPublicTransitRule<PageStation> mBatchedRule;
-
-    public final BlankCTATabInitialStateRule mInitialStateRule;
-    private final RuleChain mChain;
-
-    @Deprecated
-    public BlankCTATabInitialStatePublicTransitRule(ChromeTabbedActivityTestRule activityTestRule) {
-        mActivityTestRule = activityTestRule;
-        mBatchedRule =
-                new BatchedPublicTransitRule<>(PageStation.class, /* expectResetByTest= */ false);
-        mInitialStateRule = new BlankCTATabInitialStateRule(mActivityTestRule, true);
-        mChain = RuleChain.outerRule(mBatchedRule).around(mInitialStateRule);
-    }
-
-    @Override
-    public Statement apply(Statement statement, Description description) {
-        return mChain.apply(statement, description);
-    }
-
-    /**
-     * Start the batched test in a blank page.
-     *
-     * <p>From the second test onwards, state was reset by {@link BlankCTATabInitialStateRule}.
-     */
-    public WebPageStation startOnBlankPage() {
-        // Null in the first test, non-null from the second test onwards.
-        Station<?> homeStation = mBatchedRule.getHomeStation();
-        if (homeStation == null) {
-            EntryPointSentinelStation entryPoint = new EntryPointSentinelStation();
-            entryPoint.setAsEntryPoint();
-            homeStation = entryPoint;
-        }
-
-        WebPageStation entryPageStation = WebPageStation.newBuilder().withEntryPoint().build();
-
-        // Wait for the Conditions to be met to return an active PageStation.
-        return homeStation.travelToSync(entryPageStation, /* trigger= */ null);
-    }
-
-    /**
-     * Start the batched test in an NTP.
-     *
-     * <p>From the second test onwards, state was reset by {@link BlankCTATabInitialStateRule}.
-     */
-    public RegularNewTabPageStation startOnNtp() {
-        WebPageStation blankPage = startOnBlankPage();
-        return blankPage.loadPageProgrammatically(
-                UrlConstants.NTP_URL, RegularNewTabPageStation.newBuilder());
-    }
-}
diff --git a/chrome/test/data/password/captured_sites/artifacts b/chrome/test/data/password/captured_sites/artifacts
index 61b62cf..749475b 160000
--- a/chrome/test/data/password/captured_sites/artifacts
+++ b/chrome/test/data/password/captured_sites/artifacts
@@ -1 +1 @@
-Subproject commit 61b62cfe23abcdb7e86e3aa6b0690bd457d04604
+Subproject commit 749475b8448a6d9397fae332da7c9f989b386ac4
diff --git a/chrome/test/data/pdf/ink2_bottom_toolbar_test.ts b/chrome/test/data/pdf/ink2_bottom_toolbar_test.ts
index 96a92c2..4827568 100644
--- a/chrome/test/data/pdf/ink2_bottom_toolbar_test.ts
+++ b/chrome/test/data/pdf/ink2_bottom_toolbar_test.ts
@@ -4,12 +4,14 @@
 
 import {AnnotationBrushType, AnnotationMode, UserAction} from 'chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai/pdf_viewer_wrapper.js';
 import type {InkColorSelectorElement, InkSizeSelectorElement, ViewerBottomToolbarDropdownElement, ViewerBottomToolbarElement} from 'chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai/pdf_viewer_wrapper.js';
-import {microtasksFinished} from 'chrome://webui-test/test_util.js';
+import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
+import {isVisible, microtasksFinished} from 'chrome://webui-test/test_util.js';
 
 import {assertAnnotationBrush, assertSelectedSize, getBrushSelector, getColorButtons, getRequiredElement, getSizeButtons, setGetAnnotationBrushReply, setupMockMetricsPrivate, setupTestMockPluginForInk} from './test_util.js';
 
 const viewer = document.body.querySelector('pdf-viewer')!;
 const mockPlugin = setupTestMockPluginForInk();
+const mockMetricsPrivate = setupMockMetricsPrivate();
 
 function getBottomToolbar(): ViewerBottomToolbarElement {
   return getRequiredElement(viewer, 'viewer-bottom-toolbar');
@@ -53,10 +55,68 @@
 }
 
 chrome.test.runTests([
+  // Test that toggling annotation mode opens the bottom toolbar when text
+  // annotation is enabled.
+  async function testOpenBottomToolbarTextEnabled() {
+    mockMetricsPrivate.reset();
+
+    // Enable text annotations.
+    loadTimeData.overrideValues({'pdfTextAnnotationsEnabled': true});
+    viewer.$.toolbar.strings = Object.assign({}, viewer.$.toolbar.strings);
+    await microtasksFinished();
+
+    // No toolbars initially.
+    const drawToolbarQuery = 'viewer-bottom-toolbar';
+    const textToolbarQuery = `div[toolbar-name="${AnnotationMode.TEXT}"]`;
+    chrome.test.assertEq(AnnotationMode.NONE, viewer.$.toolbar.annotationMode);
+    chrome.test.assertFalse(
+        !!viewer.shadowRoot.querySelector(drawToolbarQuery));
+    chrome.test.assertFalse(
+        !!viewer.shadowRoot.querySelector(textToolbarQuery));
+
+    // Text annotation mode shows the text toolbar.
+    viewer.$.toolbar.setAnnotationMode(AnnotationMode.TEXT);
+    await microtasksFinished();
+    chrome.test.assertEq(AnnotationMode.TEXT, viewer.$.toolbar.annotationMode);
+    const drawToolbar = viewer.shadowRoot.querySelector(drawToolbarQuery);
+    const textToolbar = viewer.shadowRoot.querySelector(textToolbarQuery);
+    chrome.test.assertTrue(isVisible(textToolbar));
+    chrome.test.assertFalse(isVisible(drawToolbar));
+    mockMetricsPrivate.assertCount(UserAction.OPEN_INK2_SIDE_PANEL, 0);
+    mockMetricsPrivate.assertCount(UserAction.OPEN_INK2_BOTTOM_TOOLBAR, 1);
+
+    // Draw annotation mode shows the drawing toolbar.
+    viewer.$.toolbar.setAnnotationMode(AnnotationMode.DRAW);
+    await microtasksFinished();
+    chrome.test.assertEq(AnnotationMode.DRAW, viewer.$.toolbar.annotationMode);
+    chrome.test.assertFalse(isVisible(textToolbar));
+    chrome.test.assertTrue(isVisible(drawToolbar));
+    mockMetricsPrivate.assertCount(UserAction.OPEN_INK2_SIDE_PANEL, 0);
+    // Still 1, because we're still using the bottom toolbar, just in a
+    // different mode.
+    mockMetricsPrivate.assertCount(UserAction.OPEN_INK2_BOTTOM_TOOLBAR, 1);
+
+    // No annotation mode removes both toolbars from the DOM.
+    viewer.$.toolbar.setAnnotationMode(AnnotationMode.NONE);
+    await microtasksFinished();
+    chrome.test.assertEq(AnnotationMode.NONE, viewer.$.toolbar.annotationMode);
+    chrome.test.assertFalse(
+        !!viewer.shadowRoot.querySelector(drawToolbarQuery));
+    chrome.test.assertFalse(
+        !!viewer.shadowRoot.querySelector(textToolbarQuery));
+    chrome.test.succeed();
+  },
+
   // Test that toggling annotation mode opens the bottom toolbar. Must be run
-  // first, as other tests expect to already be in annotation mode.
+  // before remaining tests, as other tests expect to already be in annotation
+  // mode.
   async function testOpenBottomToolbar() {
-    const mockMetricsPrivate = setupMockMetricsPrivate();
+    mockMetricsPrivate.reset();
+
+    // Disable text annotations.
+    loadTimeData.overrideValues({'pdfTextAnnotationsEnabled': false});
+    viewer.$.toolbar.strings = Object.assign({}, viewer.$.toolbar.strings);
+    await microtasksFinished();
 
     viewer.$.toolbar.setAnnotationMode(AnnotationMode.DRAW);
     await microtasksFinished();
diff --git a/chrome/test/data/pdf/ink2_test.ts b/chrome/test/data/pdf/ink2_test.ts
index 4bc3acd..81a24fec 100644
--- a/chrome/test/data/pdf/ink2_test.ts
+++ b/chrome/test/data/pdf/ink2_test.ts
@@ -27,28 +27,122 @@
   },
 
   // Test that annotation mode can be set.
+  // TODO (crbug.com/402547554): Remove this test once text annotations
+  // launches, since it will be fully covered by the test below.
   async function testSetAnnotationMode() {
     mockMetricsPrivate.reset();
 
+    // Disable text annotations.
+    loadTimeData.overrideValues({'pdfTextAnnotationsEnabled': false});
+    viewerToolbar.strings = Object.assign({}, viewerToolbar.strings);
+    await microtasksFinished();
     chrome.test.assertEq(AnnotationMode.NONE, viewerToolbar.annotationMode);
 
-    viewer.$.toolbar.setAnnotationMode(AnnotationMode.DRAW);
+    viewerToolbar.setAnnotationMode(AnnotationMode.DRAW);
     await microtasksFinished();
     chrome.test.assertEq(AnnotationMode.DRAW, viewerToolbar.annotationMode);
     mockMetricsPrivate.assertCount(UserAction.ENTER_INK2_ANNOTATION_MODE, 1);
 
-    viewer.$.toolbar.setAnnotationMode(AnnotationMode.NONE);
+    viewerToolbar.setAnnotationMode(AnnotationMode.NONE);
     await microtasksFinished();
     chrome.test.assertEq(AnnotationMode.NONE, viewerToolbar.annotationMode);
     mockMetricsPrivate.assertCount(UserAction.EXIT_INK2_ANNOTATION_MODE, 1);
     chrome.test.succeed();
   },
 
+  // Test that annotation modes can be set when text annotations are enabled.
+  async function testSetAnnotationModeTextEnabled() {
+    mockMetricsPrivate.reset();
+
+    // Enable text annotations.
+    loadTimeData.overrideValues({'pdfTextAnnotationsEnabled': true});
+    viewerToolbar.strings = Object.assign({}, viewerToolbar.strings);
+    await microtasksFinished();
+
+    function assertMetrics(expected: {
+      ink2: {enter: number, exit: number},
+      text: {enter: number, exit: number},
+      sidepanel: number,
+    }) {
+      mockMetricsPrivate.assertCount(
+          UserAction.ENTER_INK2_ANNOTATION_MODE, expected.ink2.enter);
+      mockMetricsPrivate.assertCount(
+          UserAction.ENTER_INK2_TEXT_ANNOTATION_MODE, expected.text.enter);
+      mockMetricsPrivate.assertCount(
+          UserAction.EXIT_INK2_ANNOTATION_MODE, expected.ink2.exit);
+      mockMetricsPrivate.assertCount(
+          UserAction.EXIT_INK2_TEXT_ANNOTATION_MODE, expected.text.exit);
+
+      // Also confirm that the side panel metrics get recorded.
+      mockMetricsPrivate.assertCount(
+          UserAction.OPEN_INK2_SIDE_PANEL, expected.sidepanel);
+      mockMetricsPrivate.assertCount(UserAction.OPEN_INK2_BOTTOM_TOOLBAR, 0);
+    }
+
+    // Initial state, no metrics recorded.
+    assertMetrics(
+        {ink2: {enter: 0, exit: 0}, text: {enter: 0, exit: 0}, sidepanel: 0});
+    chrome.test.assertEq(AnnotationMode.NONE, viewerToolbar.annotationMode);
+
+    // NONE -> TEXT
+    viewerToolbar.setAnnotationMode(AnnotationMode.TEXT);
+    await microtasksFinished();
+    chrome.test.assertEq(AnnotationMode.TEXT, viewerToolbar.annotationMode);
+    assertMetrics(
+        {ink2: {enter: 0, exit: 0}, text: {enter: 1, exit: 0}, sidepanel: 1});
+
+    // TEXT -> NONE
+    viewerToolbar.setAnnotationMode(AnnotationMode.NONE);
+    await microtasksFinished();
+    chrome.test.assertEq(AnnotationMode.NONE, viewerToolbar.annotationMode);
+    assertMetrics(
+        {ink2: {enter: 0, exit: 0}, text: {enter: 1, exit: 1}, sidepanel: 1});
+
+    // NONE -> DRAW
+    viewerToolbar.setAnnotationMode(AnnotationMode.DRAW);
+    await microtasksFinished();
+    chrome.test.assertEq(AnnotationMode.DRAW, viewerToolbar.annotationMode);
+    assertMetrics(
+        {ink2: {enter: 1, exit: 0}, text: {enter: 1, exit: 1}, sidepanel: 2});
+
+    // DRAW -> TEXT
+    viewerToolbar.setAnnotationMode(AnnotationMode.TEXT);
+    await microtasksFinished();
+    chrome.test.assertEq(AnnotationMode.TEXT, viewerToolbar.annotationMode);
+    // Back and forth between draw and text doesn't increment sidepanel entry
+    // metric.
+    assertMetrics(
+        {ink2: {enter: 1, exit: 1}, text: {enter: 2, exit: 1}, sidepanel: 2});
+
+    // TEXT -> DRAW
+    viewerToolbar.setAnnotationMode(AnnotationMode.DRAW);
+    await microtasksFinished();
+    chrome.test.assertEq(AnnotationMode.DRAW, viewerToolbar.annotationMode);
+    assertMetrics(
+        {ink2: {enter: 2, exit: 1}, text: {enter: 2, exit: 2}, sidepanel: 2});
+
+    // DRAW -> NONE
+    viewerToolbar.setAnnotationMode(AnnotationMode.NONE);
+    await microtasksFinished();
+    chrome.test.assertEq(AnnotationMode.NONE, viewerToolbar.annotationMode);
+    assertMetrics(
+        {ink2: {enter: 2, exit: 2}, text: {enter: 2, exit: 2}, sidepanel: 2});
+
+    chrome.test.succeed();
+  },
+
   // Test that the side panel is shown when annotation mode is enabled and
   // hidden when annotation mode is disabled.
+  // TODO (crbug.com/402547554): Remove this test once text annotations
+  // launches, since it will be fully covered by the test below.
   async function testSidePanelVisible() {
     mockMetricsPrivate.reset();
 
+    // Disable text annotations.
+    loadTimeData.overrideValues({'pdfTextAnnotationsEnabled': false});
+    viewerToolbar.strings = Object.assign({}, viewerToolbar.strings);
+    await microtasksFinished();
+
     chrome.test.assertEq(AnnotationMode.NONE, viewerToolbar.annotationMode);
 
     viewerToolbar.setAnnotationMode(AnnotationMode.DRAW);
@@ -71,4 +165,47 @@
 
     chrome.test.succeed();
   },
+
+  // Test that the correct side panel is shown for the annotation mode when
+  // text annotations are enabled.
+  async function testSidePanelVisibleTextEnabled() {
+    mockMetricsPrivate.reset();
+
+    // Enable text annotations.
+    loadTimeData.overrideValues({'pdfTextAnnotationsEnabled': true});
+    viewerToolbar.strings = Object.assign({}, viewerToolbar.strings);
+    await microtasksFinished();
+
+    const drawPanelQuery = 'viewer-side-panel';
+    const textPanelQuery = `div[page-name="${AnnotationMode.TEXT}"]`;
+
+    // Panels are not in the DOM in NONE annotation mode.
+    chrome.test.assertEq(AnnotationMode.NONE, viewerToolbar.annotationMode);
+    chrome.test.assertFalse(!!viewer.shadowRoot.querySelector(textPanelQuery));
+    chrome.test.assertFalse(!!viewer.shadowRoot.querySelector(drawPanelQuery));
+
+    // In text mode, only the text panel is visible.
+    viewerToolbar.setAnnotationMode(AnnotationMode.TEXT);
+    await microtasksFinished();
+    const drawPanel = viewer.shadowRoot.querySelector(drawPanelQuery);
+    const textPanel = viewer.shadowRoot.querySelector(textPanelQuery);
+    chrome.test.assertTrue(!!drawPanel);
+    chrome.test.assertTrue(!!textPanel);
+    chrome.test.assertFalse(isVisible(drawPanel));
+    chrome.test.assertTrue(isVisible(textPanel));
+
+    // In draw mode, only the draw panel is visible.
+    viewerToolbar.setAnnotationMode(AnnotationMode.DRAW);
+    await microtasksFinished();
+    chrome.test.assertTrue(isVisible(drawPanel));
+    chrome.test.assertFalse(isVisible(textPanel));
+
+    // Both removed from the DOM again when annotation mode is disabled.
+    viewerToolbar.setAnnotationMode(AnnotationMode.NONE);
+    await microtasksFinished();
+    chrome.test.assertFalse(!!viewer.shadowRoot.querySelector(textPanelQuery));
+    chrome.test.assertFalse(!!viewer.shadowRoot.querySelector(drawPanelQuery));
+
+    chrome.test.succeed();
+  },
 ]);
diff --git a/chrome/test/data/pdf/ink2_viewer_toolbar_test.ts b/chrome/test/data/pdf/ink2_viewer_toolbar_test.ts
index 4a634c7..7bdaa5a 100644
--- a/chrome/test/data/pdf/ink2_viewer_toolbar_test.ts
+++ b/chrome/test/data/pdf/ink2_viewer_toolbar_test.ts
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 import {AnnotationMode, PluginController, UserAction} from 'chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai/pdf_viewer_wrapper.js';
+import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
 import {isMac} from 'chrome://resources/js/platform.js';
 import {keyDownOn} from 'chrome://webui-test/keyboard_mock_interactions.js';
 import {eventToPromise, microtasksFinished} from 'chrome://webui-test/test_util.js';
@@ -35,6 +36,47 @@
     chrome.test.assertEq(AnnotationMode.NONE, viewerToolbar.annotationMode);
     chrome.test.succeed();
   },
+  // Test that clicking the text annotation button toggles text annotation mode.
+  async function testTextAnnotationButton() {
+    // No button if feature param is not enabled.
+    loadTimeData.overrideValues({'pdfTextAnnotationsEnabled': false});
+    viewerToolbar.strings = Object.assign({}, viewerToolbar.strings);
+    await microtasksFinished();
+    chrome.test.assertFalse(
+        !!viewerToolbar.shadowRoot.querySelector('#text-annotate'));
+
+    // Set the feature param in loadTimeData and trigger Lit binding.
+    loadTimeData.overrideValues({'pdfTextAnnotationsEnabled': true});
+    viewerToolbar.strings = Object.assign({}, viewerToolbar.strings);
+    await microtasksFinished();
+
+    // Button should now exist. Clicking the text annotation button enables
+    // text annotation mode.
+    chrome.test.assertEq(AnnotationMode.NONE, viewerToolbar.annotationMode);
+    const textButton = getRequiredElement(viewerToolbar, '#text-annotate');
+    textButton.click();
+    await microtasksFinished();
+    chrome.test.assertEq(AnnotationMode.TEXT, viewerToolbar.annotationMode);
+
+    // Clicking the draw annotation button while text is enabled switches to
+    // draw mode.
+    const annotateButton = getRequiredElement(viewerToolbar, '#annotate');
+    annotateButton.click();
+    await microtasksFinished();
+    chrome.test.assertEq(AnnotationMode.DRAW, viewerToolbar.annotationMode);
+
+    // Clicking text annotation button while drawing is enabled switches to
+    // text mode.
+    textButton.click();
+    await microtasksFinished();
+    chrome.test.assertEq(AnnotationMode.TEXT, viewerToolbar.annotationMode);
+
+    // Clicking the text button while in text mode exits annotation mode.
+    textButton.click();
+    await microtasksFinished();
+    chrome.test.assertEq(AnnotationMode.NONE, viewerToolbar.annotationMode);
+    chrome.test.succeed();
+  },
   // <if expr="enable_ink">
   // Test that the original Ink annotation bar is not used.
   async function testInkAnnotationBarNotVisible() {
diff --git a/chrome/test/data/webui/glic/api_test.ts b/chrome/test/data/webui/glic/api_test.ts
index 1457b22..03efcd8 100644
--- a/chrome/test/data/webui/glic/api_test.ts
+++ b/chrome/test/data/webui/glic/api_test.ts
@@ -203,6 +203,8 @@
   async testPanelActive() {
     assertTrue(!!this.host.panelActive);
     const activeSequence = observeSequence(this.host.panelActive());
+    assertTrue(!!this.host.closePanel);
+    await this.host.closePanel();
     assertTrue(await activeSequence.next());
     await this.advanceToNextStep();
     assertFalse(await activeSequence.next());
diff --git a/chrome/test/data/webui/settings/autofill_ai_add_or_edit_dialog_test.ts b/chrome/test/data/webui/settings/autofill_ai_add_or_edit_dialog_test.ts
index 491ab1d..1e8495b 100644
--- a/chrome/test/data/webui/settings/autofill_ai_add_or_edit_dialog_test.ts
+++ b/chrome/test/data/webui/settings/autofill_ai_add_or_edit_dialog_test.ts
@@ -407,7 +407,7 @@
         assertTrue(!!countrySelect);
         if (params.add) {
           assertEquals('', countrySelect.value);
-          assertTrue(countrySelect.textContent!.includes('No option selected'));
+          assertTrue(countrySelect.textContent!.includes('Select'));
         } else {
           assertEquals(oldCountryCode, countrySelect.value);
           assertTrue(countrySelect.textContent!.includes('Germany'));
@@ -502,9 +502,9 @@
           assertEquals('', monthSelect.value);
           assertEquals('', daySelect.value);
           assertEquals('', yearSelect.value);
-          assertTrue(monthSelect.textContent!.includes('No option selected'));
-          assertTrue(daySelect.textContent!.includes('No option selected'));
-          assertTrue(yearSelect.textContent!.includes('No option selected'));
+          assertTrue(monthSelect.textContent!.includes('Select'));
+          assertTrue(daySelect.textContent!.includes('Select'));
+          assertTrue(yearSelect.textContent!.includes('Select'));
         } else {
           assertEquals(oldDate.month, monthSelect.value);
           assertEquals(oldDate.day, daySelect.value);
diff --git a/chrome/test/data/webui/side_panel/customize_chrome/app_test.ts b/chrome/test/data/webui/side_panel/customize_chrome/app_test.ts
index d493f1e..6d06f6f 100644
--- a/chrome/test/data/webui/side_panel/customize_chrome/app_test.ts
+++ b/chrome/test/data/webui/side_panel/customize_chrome/app_test.ts
@@ -302,6 +302,7 @@
           assertEquals(customizeChromeApp, document.activeElement);
 
           callbackRouter.attachedTabStateUpdated(false);
+          callbackRouter.setThemeEditable(false);
           await microtasksFinished();
 
           assertTrue(
diff --git a/chrome/test/data/webui/side_panel/read_anything/app_receives_toolbar_changes_test.ts b/chrome/test/data/webui/side_panel/read_anything/app_receives_toolbar_changes_test.ts
index bfbf877..27dc940 100644
--- a/chrome/test/data/webui/side_panel/read_anything/app_receives_toolbar_changes_test.ts
+++ b/chrome/test/data/webui/side_panel/read_anything/app_receives_toolbar_changes_test.ts
@@ -10,14 +10,16 @@
 import {assertArrayEquals, assertEquals, assertFalse, assertNotEquals, assertTrue} from 'chrome-untrusted://webui-test/chai_assert.js';
 import {hasStyle, microtasksFinished} from 'chrome-untrusted://webui-test/test_util.js';
 
-import {createApp, emitEvent, setupBasicSpeech} from './common.js';
+import {createApp, emitEvent, mockMetrics, setupBasicSpeech} from './common.js';
 import {FakeReadingMode} from './fake_reading_mode.js';
 import {TestColorUpdaterBrowserProxy} from './test_color_updater_browser_proxy.js';
+import type {TestMetricsBrowserProxy} from './test_metrics_browser_proxy.js';
 import {TestSpeechBrowserProxy} from './test_speech_browser_proxy.js';
 
 suite('AppReceivesToolbarChanges', () => {
   let app: AppElement;
   let speech: TestSpeechBrowserProxy;
+  let metrics: TestMetricsBrowserProxy;
 
   function containerLetterSpacing(): number {
     return +window.getComputedStyle(app.$.container)
@@ -80,6 +82,7 @@
     SpeechBrowserProxyImpl.setInstance(speech);
     const readingMode = new FakeReadingMode();
     chrome.readingMode = readingMode as unknown as typeof chrome.readingMode;
+    metrics = mockMetrics();
     app = await createApp();
   });
 
@@ -318,6 +321,7 @@
 
         assertTrue(app.speechPlayingState.isSpeechActive);
         assertTrue(propagatedActiveState);
+        assertEquals(0, metrics.getCallCount('recordSpeechStopSource'));
       });
 
       test('second press pauses', async () => {
@@ -327,6 +331,9 @@
 
         assertFalse(app.speechPlayingState.isSpeechActive);
         assertFalse(propagatedActiveState);
+        assertEquals(
+            chrome.readingMode.keyboardShortcutStopSource,
+            await metrics.whenCalled('recordSpeechStopSource'));
       });
     });
   });
diff --git a/chrome/test/data/webui/side_panel/read_anything/fake_reading_mode.ts b/chrome/test/data/webui/side_panel/read_anything/fake_reading_mode.ts
index 9c235b5..843cf61 100644
--- a/chrome/test/data/webui/side_panel/read_anything/fake_reading_mode.ts
+++ b/chrome/test/data/webui/side_panel/read_anything/fake_reading_mode.ts
@@ -48,6 +48,14 @@
   sentenceHighlighting: number = 3;
   noHighlighting: number = 4;
 
+  // Enum values for speech stop sources.
+  pauseButtonStopSource: number = 30;
+  keyboardShortcutStopSource: number = 31;
+  engineInterruptStopSource: number = 32;
+  engineErrorStopSource: number = 33;
+  contentFinishedStopSource: number = 34;
+  unexpectedUpdateContentStopSource: number = 35;
+
   // Whether the WebUI toolbar feature flag is enabled.
   isWebUIToolbarVisible: boolean = true;
 
@@ -226,6 +234,9 @@
   // Called when a tracked count-based metric is incremented.
   incrementMetricCount(_metric: string) {}
 
+  // Log when speech stops and why.
+  logSpeechStop(_source: number) {}
+
   // Called when the highlight granularity is changed via the webui toolbar.
   turnedHighlightOn() {
     this.highlightGranularity = this.autoHighlighting;
diff --git a/chrome/test/data/webui/side_panel/read_anything/play_pause_test.ts b/chrome/test/data/webui/side_panel/read_anything/play_pause_test.ts
index 9987326..0bc4126 100644
--- a/chrome/test/data/webui/side_panel/read_anything/play_pause_test.ts
+++ b/chrome/test/data/webui/side_panel/read_anything/play_pause_test.ts
@@ -82,6 +82,19 @@
         await metrics.whenCalled('incrementMetricCount'));
   });
 
+  test('on click logs speech stop pause source', async () => {
+    toolbar.isReadAloudPlayable = true;
+    await microtasksFinished();
+    toolbar.isSpeechActive = false;
+    playPauseButton.click();
+
+    toolbar.isSpeechActive = true;
+    playPauseButton.click();
+    assertEquals(
+        chrome.readingMode.pauseButtonStopSource,
+        await metrics.whenCalled('recordSpeechStopSource'));
+  });
+
   test('when playing', async () => {
     toolbar.isSpeechActive = true;
     await microtasksFinished();
diff --git a/chrome/test/data/webui/side_panel/read_anything/read_anything_logger_test.ts b/chrome/test/data/webui/side_panel/read_anything/read_anything_logger_test.ts
index 0ff359f..f773b17 100644
--- a/chrome/test/data/webui/side_panel/read_anything/read_anything_logger_test.ts
+++ b/chrome/test/data/webui/side_panel/read_anything/read_anything_logger_test.ts
@@ -113,6 +113,12 @@
     assertEquals(0, metrics.getCallCount('recordTextSettingsChange'));
   });
 
+  test('speech stop source', async () => {
+    const source = 123;
+    logger.logSpeechStopSource(source);
+    assertEquals(source, await metrics.whenCalled('recordSpeechStopSource'));
+  });
+
   test('logVoiceSpeed', () => {
     logger.logVoiceSpeed(1);
     logger.logVoiceSpeed(2);
diff --git a/chrome/test/data/webui/side_panel/read_anything/speech_test.ts b/chrome/test/data/webui/side_panel/read_anything/speech_test.ts
index 921519a..9b225fda 100644
--- a/chrome/test/data/webui/side_panel/read_anything/speech_test.ts
+++ b/chrome/test/data/webui/side_panel/read_anything/speech_test.ts
@@ -10,11 +10,14 @@
 import {microtasksFinished} from 'chrome-untrusted://webui-test/test_util.js';
 
 import {createAndSetVoices, createSpeechErrorEvent, createSpeechSynthesisVoice, emitEvent, mockMetrics, setSimpleAxTreeWithText, setupBasicSpeech} from './common.js';
+import type {TestMetricsBrowserProxy} from './test_metrics_browser_proxy.js';
 import {TestSpeechBrowserProxy} from './test_speech_browser_proxy.js';
 
 suite('Speech', () => {
   let app: AppElement;
   let speech: TestSpeechBrowserProxy;
+  let metrics: TestMetricsBrowserProxy;
+
   const paragraph1: string[] = [
     'Something has changed within me, something is not the same.',
     'I\'m through with playing by the rules of someone else\'s game.',
@@ -82,7 +85,7 @@
     chrome.readingMode.restoreSettingsFromPrefs = () => {};
     chrome.readingMode.languageChanged = () => {};
     chrome.readingMode.onTtsEngineInstalled = () => {};
-    mockMetrics();
+    metrics = mockMetrics();
 
     app = document.createElement('read-anything-app');
     document.body.appendChild(app);
@@ -167,6 +170,18 @@
     });
   });
 
+  test('on finished, logs speech stop source', async () => {
+    app.playSpeech();
+    for (let i = 0; i < paragraph1.length + paragraph2.length; i++) {
+      const spoken = speech.getArgs('speak')[i];
+      assertTrue(!!spoken);
+      spoken.onend();
+    }
+    assertEquals(
+        chrome.readingMode.contentFinishedStopSource,
+        await metrics.whenCalled('recordSpeechStopSource'));
+  });
+
   suite('with text selected', () => {
     let mockTimer: MockTimer;
 
@@ -412,7 +427,7 @@
         assertTrue(app.speechPlayingState.isSpeechBeingRepositioned);
       });
 
-  test('interrupt error stops speech', () => {
+  test('interrupt error stops speech', async () => {
     app.speechPlayingState.isSpeechTreeInitialized = true;
     app.speechPlayingState.isAudioCurrentlyPlaying = true;
     app.playSpeech();
@@ -424,9 +439,11 @@
     assertFalse(app.speechPlayingState.isAudioCurrentlyPlaying);
     assertFalse(app.speechPlayingState.isSpeechActive);
     assertFalse(app.speechPlayingState.isSpeechBeingRepositioned);
+    assertEquals(
+        chrome.readingMode.engineInterruptStopSource,
+        await metrics.whenCalled('recordSpeechStopSource'));
   });
 
-
   suite('very long text', () => {
     const longSentences =
         'A kingdom of isolation, and it looks like I am the queen and the ' +
@@ -479,6 +496,7 @@
 
       assertEquals(0, speech.getCallCount('pause'));
       assertEquals(1, speech.getCallCount('cancel'));
+      assertEquals(0, metrics.getCallCount('recordSpeechStopSource'));
       const spoken1 = speech.getArgs('speak')[0];
       assertEquals(
           longSentences.substring(0, accessibleTextLength), getSpokenText());
@@ -557,7 +575,7 @@
       assertTrue(app.$.toolbar.isReadAloudPlayable);
     });
 
-    test('selects default voice on language-unavailable', () => {
+    test('selects default voice on language-unavailable', async () => {
       const pageLanguage = 'es';
       assertFalse(pageLanguage === chrome.readingMode.defaultLanguageForSpeech);
       assertFalse(
@@ -578,6 +596,9 @@
       assertEquals(
           chrome.readingMode.defaultLanguageForSpeech,
           app.speechSynthesisLanguage);
+      assertEquals(
+          chrome.readingMode.engineErrorStopSource,
+          await metrics.whenCalled('recordSpeechStopSource'));
     });
 
     suite('voice change to unavailable voice', () => {
@@ -589,7 +610,7 @@
         utterance = speech.getArgs('speak')[0];
       });
 
-      test('cancels and selects default voice', () => {
+      test('cancels and selects default voice', async () => {
         emitEvent(app, ToolbarEvent.VOICE, {
           detail: {
             selectedVoice:
@@ -606,6 +627,9 @@
         assertEquals(0, speech.getCallCount('pause'));
         assertEquals(0, speech.getCallCount('speak'));
         assertEquals(speech.getVoices()[0], app.getSpeechSynthesisVoice());
+        assertEquals(
+            chrome.readingMode.engineErrorStopSource,
+            await metrics.whenCalled('recordSpeechStopSource'));
       });
 
       test('still in getVoices(), cancels and selects another voice', () => {
@@ -652,7 +676,6 @@
           });
     });
 
-
     test('invalid argument cancels and uses default rate', () => {
       app.playSpeech();
       assertEquals(1, speech.getCallCount('speak'));
@@ -672,10 +695,11 @@
       assertTrue(!!utterance.onerror);
       utterance.onerror(createSpeechErrorEvent(utterance, 'invalid-argument'));
 
-      assertEquals(3, speech.getCallCount('cancel'));
+      assertEquals(2, speech.getCallCount('cancel'));
       assertEquals(0, speech.getCallCount('pause'));
       assertEquals(1, speech.getCallCount('speak'));
       assertEquals(1, speechRate);
+      assertEquals(0, metrics.getCallCount('recordSpeechStopSource'));
     });
 
     suite('and voice preview is played', () => {
diff --git a/chrome/test/data/webui/side_panel/read_anything/test_metrics_browser_proxy.ts b/chrome/test/data/webui/side_panel/read_anything/test_metrics_browser_proxy.ts
index d252802..ed282e2 100644
--- a/chrome/test/data/webui/side_panel/read_anything/test_metrics_browser_proxy.ts
+++ b/chrome/test/data/webui/side_panel/read_anything/test_metrics_browser_proxy.ts
@@ -22,6 +22,7 @@
       'recordSpeechError',
       'recordSpeechPlaybackLength',
       'recordSpeechSettingsChange',
+      'recordSpeechStopSource',
       'recordTextSettingsChange',
       'recordTime',
       'recordVoiceSpeed',
@@ -69,6 +70,10 @@
     this.methodCalled('recordSpeechSettingsChange', settingsChange);
   }
 
+  recordSpeechStopSource(source: number) {
+    this.methodCalled('recordSpeechStopSource', source);
+  }
+
   recordVoiceSpeed(index: number) {
     this.methodCalled('recordVoiceSpeed', index);
   }
diff --git a/chrome/test/data/webui/side_panel/read_anything/update_content_test.ts b/chrome/test/data/webui/side_panel/read_anything/update_content_test.ts
index 0e453884..31f3599 100644
--- a/chrome/test/data/webui/side_panel/read_anything/update_content_test.ts
+++ b/chrome/test/data/webui/side_panel/read_anything/update_content_test.ts
@@ -8,15 +8,17 @@
 import {assertEquals, assertFalse, assertTrue} from 'chrome-untrusted://webui-test/chai_assert.js';
 import {microtasksFinished} from 'chrome-untrusted://webui-test/test_util.js';
 
-import {setupBasicSpeech} from './common.js';
+import {mockMetrics, setupBasicSpeech} from './common.js';
 import {FakeReadingMode} from './fake_reading_mode.js';
 import {FakeTreeBuilder} from './fake_tree_builder.js';
 import {TestColorUpdaterBrowserProxy} from './test_color_updater_browser_proxy.js';
+import type {TestMetricsBrowserProxy} from './test_metrics_browser_proxy.js';
 import {TestSpeechBrowserProxy} from './test_speech_browser_proxy.js';
 
 suite('UpdateContent', () => {
   let app: AppElement;
   let readingMode: FakeReadingMode;
+  let metrics: TestMetricsBrowserProxy;
 
   const textNodeIds = [3, 5, 7, 9];
   const texts = [
@@ -34,6 +36,7 @@
     chrome.readingMode = readingMode as unknown as typeof chrome.readingMode;
     const speech = new TestSpeechBrowserProxy();
     SpeechBrowserProxyImpl.setInstance(speech);
+    metrics = mockMetrics();
 
     // Don't use await createApp() when using a FakeTree, as it seems to cause
     // flakiness.
@@ -70,6 +73,16 @@
     assertFalse(app.$.toolbar.isReadAloudPlayable);
   });
 
+  test('logs speech stop if called while audio playing', async () => {
+    app.speechPlayingState.isAudioCurrentlyPlaying = true;
+    app.updateContent();
+    await microtasksFinished();
+
+    assertEquals(
+        chrome.readingMode.unexpectedUpdateContentStopSource,
+        await metrics.whenCalled('recordSpeechStopSource'));
+  });
+
   test('hides loading page', async () => {
     app.updateContent();
     await microtasksFinished();
diff --git a/chromeos/ash/components/boca/babelorca/soda_installer.cc b/chromeos/ash/components/boca/babelorca/soda_installer.cc
index d7081ad1..f5fb3d4 100644
--- a/chromeos/ash/components/boca/babelorca/soda_installer.cc
+++ b/chromeos/ash/components/boca/babelorca/soda_installer.cc
@@ -4,6 +4,8 @@
 
 #include "chromeos/ash/components/boca/babelorca/soda_installer.h"
 
+#include <algorithm>
+
 #include "base/functional/callback.h"
 #include "base/logging.h"
 #include "components/soda/constants.h"
@@ -20,22 +22,42 @@
 SodaInstaller::~SodaInstaller() {
   speech::SodaInstaller::GetInstance()->RemoveObserver(this);
 }
-void SodaInstaller::GetAvailabilityOrInstall(AvailabilityCallback callback) {
-  speech::SodaInstaller* soda_installer = speech::SodaInstaller::GetInstance();
-  speech::LanguageCode language_code =
-      speech::GetLanguageCode(std::string(language_));
 
-  if (soda_installer->IsSodaInstalled(language_code)) {
-    std::move(callback).Run(true);
+SodaInstaller::InstallationStatus SodaInstaller::GetStaus() {
+  if (!ValidLanguage()) {
+    status_ = InstallationStatus::kLanguageUnavailable;
+    return status_;
+  }
+
+  if (IsAlreadyInstalled()) {
+    status_ = InstallationStatus::kReady;
+    return status_;
+  }
+
+  return status_;
+}
+
+void SodaInstaller::InstallSoda(AvailabilityCallback callback) {
+  if (!ValidLanguage()) {
+    status_ = InstallationStatus::kLanguageUnavailable;
+    std::move(callback).Run(InstallationStatus::kLanguageUnavailable);
+    return;
+  }
+
+  if (IsAlreadyInstalled()) {
+    status_ = InstallationStatus::kReady;
+    std::move(callback).Run(InstallationStatus::kReady);
     return;
   }
 
   callbacks_.push(std::move(callback));
 
-  if (installing_) {
+  if (status_ == InstallationStatus::kInstalling) {
     return;
   }
-  installing_ = true;
+  status_ = InstallationStatus::kInstalling;
+
+  speech::SodaInstaller* soda_installer = speech::SodaInstaller::GetInstance();
 
   soda_installer->AddObserver(this);
 
@@ -59,10 +81,10 @@
     return;
   }
 
-  FlushCallbacks(true);
+  status_ = InstallationStatus::kReady;
+  FlushCallbacks(InstallationStatus::kReady);
 
   speech::SodaInstaller::GetInstance()->RemoveObserver(this);
-  installing_ = false;
 }
 
 void SodaInstaller::OnSodaInstallError(
@@ -75,9 +97,9 @@
   // If the language code is kNone then the binary failed to install.
   if (language_code == speech::GetLanguageCode(language_) ||
       language_code == speech::LanguageCode::kNone) {
-    FlushCallbacks(false);
+    status_ = InstallationStatus::kInstallationFailure;
+    FlushCallbacks(InstallationStatus::kInstallationFailure);
     speech::SodaInstaller::GetInstance()->RemoveObserver(this);
-    installing_ = false;
   }
 }
 
@@ -86,11 +108,24 @@
 void SodaInstaller::OnSodaProgress(speech::LanguageCode language_code,
                                    int progress) {}
 
-void SodaInstaller::FlushCallbacks(bool result) {
+void SodaInstaller::FlushCallbacks(InstallationStatus status) {
   while (!callbacks_.empty()) {
-    std::move(callbacks_.front()).Run(result);
+    std::move(callbacks_.front()).Run(status);
     callbacks_.pop();
   }
 }
 
+bool SodaInstaller::ValidLanguage() {
+  std::vector<std::string> languages =
+      speech::SodaInstaller::GetInstance()->GetAvailableLanguages();
+
+  return std::find(languages.begin(), languages.end(), language_) !=
+         languages.end();
+}
+
+bool SodaInstaller::IsAlreadyInstalled() {
+  return speech::SodaInstaller::GetInstance()->IsSodaInstalled(
+      speech::GetLanguageCode(language_));
+}
+
 }  // namespace ash::babelorca
diff --git a/chromeos/ash/components/boca/babelorca/soda_installer.h b/chromeos/ash/components/boca/babelorca/soda_installer.h
index e3d2c5a..f29c67e 100644
--- a/chromeos/ash/components/boca/babelorca/soda_installer.h
+++ b/chromeos/ash/components/boca/babelorca/soda_installer.h
@@ -16,7 +16,16 @@
 
 class SodaInstaller : public speech::SodaInstaller::Observer {
  public:
-  using AvailabilityCallback = base::OnceCallback<void(bool success)>;
+  enum InstallationStatus {
+    kUninstalled,
+    kReady,
+    kInstalling,
+    kInstallationFailure,
+    kLanguageUnavailable,
+  };
+
+  using AvailabilityCallback =
+      base::OnceCallback<void(InstallationStatus status)>;
 
   SodaInstaller(PrefService* global_prefs,
                 PrefService* profile_prefs,
@@ -25,11 +34,8 @@
   SodaInstaller(const SodaInstaller&) = delete;
   SodaInstaller operator=(const SodaInstaller&) = delete;
 
-  // For a caller that wants to check the availability of SODA they will
-  // pass a callback.  If not installed this class will attempt to install
-  // and pass the result of the installation back in this callback.
-  // Otherwise this callback will invoke immediately with the availability.
-  void GetAvailabilityOrInstall(AvailabilityCallback callback);
+  InstallationStatus GetStaus();
+  void InstallSoda(AvailabilityCallback callback);
 
   // speech::SodaInstaller::Observer
   void OnSodaInstalled(speech::LanguageCode language_code) override;
@@ -39,10 +45,13 @@
                       int progress) override;
 
  private:
-  void FlushCallbacks(bool result);
+  void FlushCallbacks(InstallationStatus result);
 
+  bool ValidLanguage();
+  bool IsAlreadyInstalled();
+
+  InstallationStatus status_ = InstallationStatus::kUninstalled;
   std::queue<AvailabilityCallback> callbacks_;
-  bool installing_ = false;
   const std::string language_;
   raw_ptr<PrefService> global_prefs_;
   raw_ptr<PrefService> profile_prefs_;
diff --git a/chromeos/ash/components/boca/babelorca/soda_installer_unittest.cc b/chromeos/ash/components/boca/babelorca/soda_installer_unittest.cc
index ad645ec..5623bbd 100644
--- a/chromeos/ash/components/boca/babelorca/soda_installer_unittest.cc
+++ b/chromeos/ash/components/boca/babelorca/soda_installer_unittest.cc
@@ -4,6 +4,8 @@
 
 #include "chromeos/ash/components/boca/babelorca/soda_installer.h"
 
+#include <optional>
+
 #include "ash/constants/ash_features.h"
 #include "ash/constants/ash_pref_names.h"
 #include "base/files/file_path.h"
@@ -24,6 +26,7 @@
 namespace {
 constexpr char kAlternativeLanguage[] = "de-DE";
 constexpr char kDefaultLanguage[] = "en-US";
+constexpr char kBadLanguage[] = "unkown language";
 constexpr char kTeacherSetting[] = "teacher";
 }  // namespace
 
@@ -87,8 +90,12 @@
         ash::prefs::kProjectorCreationFlowEnabled, false);
     profile_prefs_.registry()->RegisterStringPref(
         ash::prefs::kClassManagementToolsAvailabilitySetting, kTeacherSetting);
+
+    EXPECT_CALL(soda_installer_, GetAvailableLanguages)
+        .WillRepeatedly(testing::Return(available_languages_));
   }
 
+  std::vector<std::string> available_languages_ = {{kDefaultLanguage}};
   MockSodaInstaller soda_installer_;
   TestingPrefServiceSimple profile_prefs_;
   TestingPrefServiceSimple global_prefs_;
@@ -96,11 +103,17 @@
   std::unique_ptr<SodaInstaller> installer_under_test_;
 };
 
-TEST_F(BabelOrcaSodaInstallerTest, CallsBackImmediatelyIfPackInstalled) {
-  bool speech_recognition_available = false;
+TEST_F(BabelOrcaSodaInstallerTest, UninstalledDefaultStatus) {
+  EXPECT_EQ(installer_under_test_->GetStaus(),
+            SodaInstaller::InstallationStatus::kUninstalled);
+}
 
-  // This first call fakes the binary installation, which is necessary for the
-  // installer to report the installed language correctly.
+TEST_F(BabelOrcaSodaInstallerTest, CallsBackImmediatelyIfPackInstalled) {
+  std::optional<SodaInstaller::InstallationStatus> speech_recognition_available;
+  // we expect two calls to InstallLanguage, once when during Init the
+  // installer installs the language associated with live capiton.
+  // then when the event handler calls InstallLanguage for the
+  // language associated with BabelOrca.
   speech::SodaInstaller::GetInstance()->NotifySodaInstalledForTesting();
   speech::SodaInstaller::GetInstance()->NotifySodaInstalledForTesting(
       speech::GetLanguageCode(kDefaultLanguage));
@@ -108,51 +121,88 @@
   EXPECT_CALL(soda_installer_, InstallSoda).Times(0);
   EXPECT_CALL(soda_installer_, InstallLanguage).Times(0);
 
-  installer_under_test_->GetAvailabilityOrInstall(base::BindLambdaForTesting(
-      [&speech_recognition_available](bool available) {
+  installer_under_test_->InstallSoda(base::BindLambdaForTesting(
+      [&speech_recognition_available](
+          SodaInstaller::InstallationStatus available) {
         speech_recognition_available = available;
       }));
+  EXPECT_EQ(installer_under_test_->GetStaus(),
+            SodaInstaller::InstallationStatus::kReady);
 
-  ASSERT_TRUE(speech_recognition_available);
+  ASSERT_TRUE(speech_recognition_available.has_value());
+  ASSERT_EQ(speech_recognition_available.value(),
+            SodaInstaller::InstallationStatus::kReady);
 }
 
 TEST_F(BabelOrcaSodaInstallerTest, InstallsSodaIfNeeded) {
-  bool speech_recognition_available = false;
-
+  std::optional<SodaInstaller::InstallationStatus> speech_recognition_available;
   EXPECT_CALL(soda_installer_, InstallSoda).Times(1);
-  // we expect two calls to InstallLanguage, once when during Init the
-  // installer installs the language associated with live capiton.
-  // then when the event handler calls InstallLanguage for the
-  // language associated with BabelOrca.
   EXPECT_CALL(soda_installer_, InstallLanguage).Times(2);
 
-  installer_under_test_->GetAvailabilityOrInstall(base::BindLambdaForTesting(
-      [&speech_recognition_available](bool available) {
+  installer_under_test_->InstallSoda(base::BindLambdaForTesting(
+      [&speech_recognition_available](
+          SodaInstaller::InstallationStatus available) {
         speech_recognition_available = available;
       }));
+  EXPECT_EQ(installer_under_test_->GetStaus(),
+            SodaInstaller::InstallationStatus::kInstalling);
   speech::SodaInstaller::GetInstance()->NotifySodaInstalledForTesting();
   speech::SodaInstaller::GetInstance()->NotifySodaInstalledForTesting(
       speech::GetLanguageCode(kDefaultLanguage));
+  EXPECT_EQ(installer_under_test_->GetStaus(),
+            SodaInstaller::InstallationStatus::kReady);
 
-  ASSERT_TRUE(speech_recognition_available);
+  ASSERT_TRUE(speech_recognition_available.has_value());
+  ASSERT_EQ(speech_recognition_available.value(),
+            SodaInstaller::InstallationStatus::kReady);
 }
 
 TEST_F(BabelOrcaSodaInstallerTest, ReportsSodaInstallFailure) {
-  // This should be set to false by the end of the test.
-  bool speech_recognition_available = true;
+  std::optional<SodaInstaller::InstallationStatus> speech_recognition_available;
 
   EXPECT_CALL(soda_installer_, InstallSoda).Times(1);
   EXPECT_CALL(soda_installer_, InstallLanguage).Times(2);
 
-  installer_under_test_->GetAvailabilityOrInstall(base::BindLambdaForTesting(
-      [&speech_recognition_available](bool available) {
+  installer_under_test_->InstallSoda(base::BindLambdaForTesting(
+      [&speech_recognition_available](
+          SodaInstaller::InstallationStatus available) {
         speech_recognition_available = available;
       }));
+  EXPECT_EQ(installer_under_test_->GetStaus(),
+            SodaInstaller::InstallationStatus::kInstalling);
+  speech::SodaInstaller::GetInstance()->NotifySodaInstalledForTesting();
+  speech::SodaInstaller::GetInstance()->NotifySodaErrorForTesting(
+      speech::LanguageCode::kNone);
+  EXPECT_EQ(installer_under_test_->GetStaus(),
+            SodaInstaller::InstallationStatus::kInstallationFailure);
+
+  ASSERT_TRUE(speech_recognition_available.has_value());
+  ASSERT_EQ(speech_recognition_available.value(),
+            SodaInstaller::InstallationStatus::kInstallationFailure);
+}
+
+TEST_F(BabelOrcaSodaInstallerTest, ReportsSodaLanguageInstallFailure) {
+  std::optional<SodaInstaller::InstallationStatus> speech_recognition_available;
+
+  EXPECT_CALL(soda_installer_, InstallSoda).Times(1);
+  EXPECT_CALL(soda_installer_, InstallLanguage).Times(2);
+
+  installer_under_test_->InstallSoda(base::BindLambdaForTesting(
+      [&speech_recognition_available](
+          SodaInstaller::InstallationStatus available) {
+        speech_recognition_available = available;
+      }));
+  EXPECT_EQ(installer_under_test_->GetStaus(),
+            SodaInstaller::InstallationStatus::kInstalling);
   speech::SodaInstaller::GetInstance()->NotifySodaInstalledForTesting();
   speech::SodaInstaller::GetInstance()->NotifySodaErrorForTesting(
       speech::GetLanguageCode(kDefaultLanguage));
+  EXPECT_EQ(installer_under_test_->GetStaus(),
+            SodaInstaller::InstallationStatus::kInstallationFailure);
 
-  ASSERT_FALSE(speech_recognition_available);
+  ASSERT_TRUE(speech_recognition_available.has_value());
+  ASSERT_EQ(speech_recognition_available.value(),
+            SodaInstaller::InstallationStatus::kInstallationFailure);
 }
 
 TEST_F(BabelOrcaSodaInstallerTest, IgnoresOtherLanguageFailures) {
@@ -164,13 +214,18 @@
   EXPECT_CALL(soda_installer_, InstallSoda).Times(1);
   EXPECT_CALL(soda_installer_, InstallLanguage).Times(2);
 
-  installer_under_test_->GetAvailabilityOrInstall(base::BindLambdaForTesting(
-      [&availability_callback_invoked](bool available) {
+  installer_under_test_->InstallSoda(base::BindLambdaForTesting(
+      [&availability_callback_invoked](
+          SodaInstaller::InstallationStatus available) {
         availability_callback_invoked = true;
       }));
+  EXPECT_EQ(installer_under_test_->GetStaus(),
+            SodaInstaller::InstallationStatus::kInstalling);
   speech::SodaInstaller::GetInstance()->NotifySodaInstalledForTesting();
   speech::SodaInstaller::GetInstance()->NotifySodaErrorForTesting(
       speech::GetLanguageCode(kAlternativeLanguage));
+  EXPECT_EQ(installer_under_test_->GetStaus(),
+            SodaInstaller::InstallationStatus::kInstalling);
 
   // The callback should not have been called.
   ASSERT_FALSE(availability_callback_invoked);
@@ -178,17 +233,21 @@
 
 TEST_F(BabelOrcaSodaInstallerTest, IgnoresOtherLanguageInstalls) {
   bool availability_callback_invoked = false;
-
   EXPECT_CALL(soda_installer_, InstallSoda).Times(1);
   EXPECT_CALL(soda_installer_, InstallLanguage).Times(2);
 
-  installer_under_test_->GetAvailabilityOrInstall(base::BindLambdaForTesting(
-      [&availability_callback_invoked](bool available) {
+  installer_under_test_->InstallSoda(base::BindLambdaForTesting(
+      [&availability_callback_invoked](
+          SodaInstaller::InstallationStatus available) {
         availability_callback_invoked = true;
       }));
+  EXPECT_EQ(installer_under_test_->GetStaus(),
+            SodaInstaller::InstallationStatus::kInstalling);
   speech::SodaInstaller::GetInstance()->NotifySodaInstalledForTesting();
   speech::SodaInstaller::GetInstance()->NotifySodaInstalledForTesting(
       speech::GetLanguageCode(kAlternativeLanguage));
+  EXPECT_EQ(installer_under_test_->GetStaus(),
+            SodaInstaller::InstallationStatus::kInstalling);
 
   // The callback should not have been called.
   ASSERT_FALSE(availability_callback_invoked);
@@ -210,49 +269,93 @@
 }
 
 TEST_F(BabelOrcaSodaInstallerTest, HandlesMultipleInstallationObservers) {
-  bool speech_recognition_available_one = false;
-  bool speech_recognition_available_two = false;
-
+  std::optional<SodaInstaller::InstallationStatus>
+      speech_recognition_available_one;
+  std::optional<SodaInstaller::InstallationStatus>
+      speech_recognition_available_two;
   EXPECT_CALL(soda_installer_, InstallSoda).Times(1);
   EXPECT_CALL(soda_installer_, InstallLanguage).Times(2);
 
-  installer_under_test_->GetAvailabilityOrInstall(base::BindLambdaForTesting(
-      [&speech_recognition_available_one](bool available) {
+  installer_under_test_->InstallSoda(base::BindLambdaForTesting(
+      [&speech_recognition_available_one](
+          SodaInstaller::InstallationStatus available) {
         speech_recognition_available_one = available;
       }));
-  installer_under_test_->GetAvailabilityOrInstall(base::BindLambdaForTesting(
-      [&speech_recognition_available_two](bool available) {
+  EXPECT_EQ(installer_under_test_->GetStaus(),
+            SodaInstaller::InstallationStatus::kInstalling);
+  installer_under_test_->InstallSoda(base::BindLambdaForTesting(
+      [&speech_recognition_available_two](
+          SodaInstaller::InstallationStatus available) {
         speech_recognition_available_two = available;
       }));
   speech::SodaInstaller::GetInstance()->NotifySodaInstalledForTesting();
   speech::SodaInstaller::GetInstance()->NotifySodaInstalledForTesting(
       speech::GetLanguageCode(kDefaultLanguage));
+  EXPECT_EQ(installer_under_test_->GetStaus(),
+            SodaInstaller::InstallationStatus::kReady);
 
-  ASSERT_TRUE(speech_recognition_available_one);
-  ASSERT_TRUE(speech_recognition_available_two);
+  ASSERT_TRUE(speech_recognition_available_one.has_value());
+  ASSERT_EQ(speech_recognition_available_one.value(),
+            SodaInstaller::InstallationStatus::kReady);
+  ASSERT_TRUE(speech_recognition_available_one.has_value());
+  ASSERT_EQ(speech_recognition_available_two.value(),
+            SodaInstaller::InstallationStatus::kReady);
 }
 
 TEST_F(BabelOrcaSodaInstallerTest,
        HandlesMultipleInstallationObserversOnFailure) {
-  bool speech_recognition_available_one = true;
-  bool speech_recognition_available_two = true;
+  std::optional<SodaInstaller::InstallationStatus>
+      speech_recognition_available_one;
+  std::optional<SodaInstaller::InstallationStatus>
+      speech_recognition_available_two;
 
   EXPECT_CALL(soda_installer_, InstallSoda).Times(1);
   EXPECT_CALL(soda_installer_, InstallLanguage).Times(2);
 
-  installer_under_test_->GetAvailabilityOrInstall(base::BindLambdaForTesting(
-      [&speech_recognition_available_one](bool available) {
+  installer_under_test_->InstallSoda(base::BindLambdaForTesting(
+      [&speech_recognition_available_one](
+          SodaInstaller::InstallationStatus available) {
         speech_recognition_available_one = available;
       }));
-  installer_under_test_->GetAvailabilityOrInstall(base::BindLambdaForTesting(
-      [&speech_recognition_available_two](bool available) {
+  EXPECT_EQ(installer_under_test_->GetStaus(),
+            SodaInstaller::InstallationStatus::kInstalling);
+  installer_under_test_->InstallSoda(base::BindLambdaForTesting(
+      [&speech_recognition_available_two](
+          SodaInstaller::InstallationStatus available) {
         speech_recognition_available_two = available;
       }));
   speech::SodaInstaller::GetInstance()->NotifySodaInstalledForTesting();
   speech::SodaInstaller::GetInstance()->NotifySodaErrorForTesting(
       speech::GetLanguageCode(kDefaultLanguage));
+  EXPECT_EQ(installer_under_test_->GetStaus(),
+            SodaInstaller::InstallationStatus::kInstallationFailure);
 
-  ASSERT_FALSE(speech_recognition_available_one);
-  ASSERT_FALSE(speech_recognition_available_two);
+  ASSERT_TRUE(speech_recognition_available_one.has_value());
+  ASSERT_EQ(speech_recognition_available_one.value(),
+            SodaInstaller::InstallationStatus::kInstallationFailure);
+  ASSERT_TRUE(speech_recognition_available_two.has_value());
+  ASSERT_EQ(speech_recognition_available_two.value(),
+            SodaInstaller::InstallationStatus::kInstallationFailure);
+}
+
+TEST_F(BabelOrcaSodaInstallerTest, GetStatusCatchesBadLanguage) {
+  installer_under_test_ = std::make_unique<SodaInstaller>(
+      &global_prefs_, &profile_prefs_, kBadLanguage);
+  EXPECT_EQ(installer_under_test_->GetStaus(),
+            SodaInstaller::InstallationStatus::kLanguageUnavailable);
+}
+
+TEST_F(BabelOrcaSodaInstallerTest, InstallLanguageCatchesBadLanguage) {
+  std::optional<SodaInstaller::InstallationStatus> speech_recognition_available;
+  installer_under_test_ = std::make_unique<SodaInstaller>(
+      &global_prefs_, &profile_prefs_, kBadLanguage);
+  installer_under_test_->InstallSoda(base::BindLambdaForTesting(
+      [&speech_recognition_available](
+          SodaInstaller::InstallationStatus available) {
+        speech_recognition_available = available;
+      }));
+  ASSERT_TRUE(speech_recognition_available.has_value());
+  EXPECT_EQ(speech_recognition_available,
+            SodaInstaller::InstallationStatus::kLanguageUnavailable);
 }
 }  // namespace ash::babelorca
diff --git a/chromeos/ash/components/boca/babelorca/tachyon_streaming_client.cc b/chromeos/ash/components/boca/babelorca/tachyon_streaming_client.cc
index cfa166e1..aa65e582 100644
--- a/chromeos/ash/components/boca/babelorca/tachyon_streaming_client.cc
+++ b/chromeos/ash/components/boca/babelorca/tachyon_streaming_client.cc
@@ -14,6 +14,7 @@
 #include "base/location.h"
 #include "base/logging.h"
 #include "base/memory/scoped_refptr.h"
+#include "base/metrics/histogram_functions.h"
 #include "base/sequence_checker.h"
 #include "base/strings/stringprintf.h"
 #include "base/time/time.h"
@@ -32,7 +33,10 @@
 
 namespace ash::babelorca {
 namespace {
+
 constexpr base::TimeDelta kReceiveTimeout = base::Minutes(1);
+constexpr char kStreamEndReasonUma[] = "Ash.Boca.Babelorca.StreamEndReason";
+
 }  // namespace
 
 TachyonStreamingClient::TachyonStreamingClient(
@@ -45,6 +49,10 @@
 
 TachyonStreamingClient::~TachyonStreamingClient() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (url_loader_) {
+    base::UmaHistogramEnumeration(kStreamEndReasonUma,
+                                  StreamEndReason::kEndedByClient);
+  }
 }
 
 void TachyonStreamingClient::StartRequest(
@@ -101,10 +109,14 @@
   timeout_timer_.Stop();
   MaybeRecordUma(url_loader_.get(), request_data_.get());
   if (success) {
+    base::UmaHistogramEnumeration(kStreamEndReasonUma,
+                                  StreamEndReason::kConnectionClosedSuccess);
     std::move(request_data_->response_cb)
         .Run(TachyonResponse(TachyonResponse::Status::kOk));
     return;
   }
+  base::UmaHistogramEnumeration(kStreamEndReasonUma,
+                                StreamEndReason::kConnectionClosedError);
   HandleResponse(std::move(url_loader_), std::move(request_data_),
                  std::move(auth_failure_cb_), /*response_body=*/nullptr);
 }
@@ -137,6 +149,8 @@
   //  and stream_status is not present.
   if (parsing_state == mojom::ParsingState::kError || stream_status.is_null()) {
     LOG(ERROR) << "Stream closed with parsing state: " << parsing_state;
+    base::UmaHistogramEnumeration(kStreamEndReasonUma,
+                                  StreamEndReason::kParseError);
     std::move(request_data_->response_cb)
         .Run(TachyonResponse(TachyonResponse::Status::kInternalError));
     return;
@@ -145,6 +159,10 @@
   VLOG_IF(1, !response.ok())
       << "Stream closed with error. Status: " << stream_status->code
       << ", message: " << stream_status->message;
+  base::UmaHistogramEnumeration(kStreamEndReasonUma,
+                                response.ok()
+                                    ? StreamEndReason::kConnectionClosedSuccess
+                                    : StreamEndReason::kConnectionClosedError);
   if (response.status() == TachyonResponse::Status::kAuthError) {
     std::move(auth_failure_cb_).Run(std::move(request_data_));
     return;
@@ -156,6 +174,8 @@
 void TachyonStreamingClient::OnParsingServiceDisconnected() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   LOG(ERROR) << "Parsing service disconnected";
+  base::UmaHistogramEnumeration(kStreamEndReasonUma,
+                                StreamEndReason::kParsingServiceDisconnected);
   url_loader_.reset();
   parsing_service_.reset();
   timeout_timer_.Stop();
@@ -166,6 +186,7 @@
 void TachyonStreamingClient::OnTimeout() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   VLOG(1) << "Streaming request timeout";
+  base::UmaHistogramEnumeration(kStreamEndReasonUma, StreamEndReason::kTimeout);
   url_loader_.reset();
   parsing_service_.reset();
   std::move(request_data_->response_cb)
diff --git a/chromeos/ash/components/boca/babelorca/tachyon_streaming_client.h b/chromeos/ash/components/boca/babelorca/tachyon_streaming_client.h
index 12b7fbb..9d3939ea 100644
--- a/chromeos/ash/components/boca/babelorca/tachyon_streaming_client.h
+++ b/chromeos/ash/components/boca/babelorca/tachyon_streaming_client.h
@@ -34,6 +34,20 @@
 class TachyonStreamingClient : public TachyonClient,
                                public network::SimpleURLLoaderStreamConsumer {
  public:
+  // These values are persisted to logs. Entries should not be renumbered and
+  // numeric values should never be reused. Visible for testing.
+  //
+  // LINT.IfChange(StreamEndReason)
+  enum class StreamEndReason {
+    kEndedByClient = 0,
+    kConnectionClosedSuccess = 1,
+    kConnectionClosedError = 2,
+    kParseError = 3,
+    kParsingServiceDisconnected = 4,
+    kTimeout = 5,
+    kMaxValue = kTimeout,
+  };
+  // LINT.ThenChange(//tools/metrics/histograms/metadata/ash/enums.xml:BabelOrcaStreamEndReason)
   using ParsingServiceBinder =
       base::RepeatingCallback<mojo::Remote<mojom::TachyonParsingService>()>;
   using OnMessageCallback =
diff --git a/chromeos/ash/components/boca/babelorca/tachyon_streaming_client_unittest.cc b/chromeos/ash/components/boca/babelorca/tachyon_streaming_client_unittest.cc
index 6377638..398fffca 100644
--- a/chromeos/ash/components/boca/babelorca/tachyon_streaming_client_unittest.cc
+++ b/chromeos/ash/components/boca/babelorca/tachyon_streaming_client_unittest.cc
@@ -40,8 +40,9 @@
 constexpr char kOAuthToken[] = "oauth-token";
 constexpr char kUrl[] = "https://test.com";
 constexpr char kUmaName[] = "TestStreaming";
-constexpr char kUmaPath[] =
+constexpr char kResponseUma[] =
     "Ash.Boca.Babelorca.TestStreaming.HttpResponseCodeOrNetError";
+constexpr char kStreamEndReasonUma[] = "Ash.Boca.Babelorca.StreamEndReason";
 
 class FakeTachonParsingService : public mojom::TachyonParsingService {
  public:
@@ -155,7 +156,13 @@
   EXPECT_TRUE(on_message_future_.IsEmpty());
   EXPECT_FALSE(auth_failure_future_.IsReady());
   EXPECT_EQ(
-      uma_recorder_.GetBucketCount(kUmaPath, net::HttpStatusCode::HTTP_OK), 1);
+      uma_recorder_.GetBucketCount(kResponseUma, net::HttpStatusCode::HTTP_OK),
+      1);
+  EXPECT_EQ(
+      uma_recorder_.GetBucketCount(
+          kStreamEndReasonUma,
+          TachyonStreamingClient::StreamEndReason::kConnectionClosedSuccess),
+      1);
 }
 
 TEST_F(TachyonStreamingClientTest, HttpErrorNoDataStreamed) {
@@ -173,8 +180,13 @@
   EXPECT_TRUE(on_message_future_.IsEmpty());
   EXPECT_FALSE(auth_failure_future_.IsReady());
   EXPECT_EQ(uma_recorder_.GetBucketCount(
-                kUmaPath, net::HttpStatusCode::HTTP_PRECONDITION_FAILED),
+                kResponseUma, net::HttpStatusCode::HTTP_PRECONDITION_FAILED),
             1);
+  EXPECT_EQ(
+      uma_recorder_.GetBucketCount(
+          kStreamEndReasonUma,
+          TachyonStreamingClient::StreamEndReason::kConnectionClosedError),
+      1);
 }
 
 TEST_F(TachyonStreamingClientTest, AuthErrorNoDataStreamed) {
@@ -191,8 +203,13 @@
   EXPECT_FALSE(auth_request_data->response_cb.is_null());
   EXPECT_TRUE(on_message_future_.IsEmpty());
   EXPECT_EQ(uma_recorder_.GetBucketCount(
-                kUmaPath, net::HttpStatusCode::HTTP_UNAUTHORIZED),
+                kResponseUma, net::HttpStatusCode::HTTP_UNAUTHORIZED),
             1);
+  EXPECT_EQ(
+      uma_recorder_.GetBucketCount(
+          kStreamEndReasonUma,
+          TachyonStreamingClient::StreamEndReason::kConnectionClosedError),
+      1);
 }
 
 TEST_F(TachyonStreamingClientTest, TimeoutAfterStartRequest) {
@@ -203,6 +220,10 @@
 
   TachyonResponse result = result_future_.Take();
   EXPECT_EQ(result.status(), TachyonResponse::Status::kTimeout);
+  EXPECT_EQ(uma_recorder_.GetBucketCount(
+                kStreamEndReasonUma,
+                TachyonStreamingClient::StreamEndReason::kTimeout),
+            1);
 }
 
 TEST_F(TachyonStreamingClientTest, TimeoutAfterDataReceived) {
@@ -304,6 +325,11 @@
   EXPECT_FALSE(on_message_future_.IsEmpty());
   on_message_future_.Take();
   EXPECT_TRUE(on_message_future_.IsEmpty());
+  EXPECT_EQ(
+      uma_recorder_.GetBucketCount(
+          kStreamEndReasonUma,
+          TachyonStreamingClient::StreamEndReason::kConnectionClosedSuccess),
+      1);
 }
 
 TEST_F(TachyonStreamingClientTest, DataStreamedParsingError) {
@@ -329,6 +355,10 @@
   EXPECT_FALSE(on_message_future_.IsEmpty());
   on_message_future_.Take();
   EXPECT_TRUE(on_message_future_.IsEmpty());
+  EXPECT_EQ(uma_recorder_.GetBucketCount(
+                kStreamEndReasonUma,
+                TachyonStreamingClient::StreamEndReason::kParseError),
+            1);
 }
 
 TEST_F(TachyonStreamingClientTest, DataStreamedAuthErrorClosed) {
@@ -345,6 +375,11 @@
   EXPECT_FALSE(result_future_.IsReady());
   EXPECT_FALSE(resume_future_.IsReady());
   EXPECT_TRUE(on_message_future_.IsEmpty());
+  EXPECT_EQ(
+      uma_recorder_.GetBucketCount(
+          kStreamEndReasonUma,
+          TachyonStreamingClient::StreamEndReason::kConnectionClosedError),
+      1);
 }
 
 TEST_F(TachyonStreamingClientTest, ParsingServiceDisconnected) {
@@ -362,6 +397,11 @@
   task_environment_.FastForwardBy(base::Minutes(1));
 
   EXPECT_EQ(result.status(), TachyonResponse::Status::kInternalError);
+  EXPECT_EQ(
+      uma_recorder_.GetBucketCount(
+          kStreamEndReasonUma,
+          TachyonStreamingClient::StreamEndReason::kParsingServiceDisconnected),
+      1);
 }
 
 TEST_F(TachyonStreamingClientTest, UseSingleParsingServicePerConnection) {
diff --git a/chromeos/ash/components/boca/on_task/on_task_session_manager.cc b/chromeos/ash/components/boca/on_task/on_task_session_manager.cc
index 39c38a57..16461ad 100644
--- a/chromeos/ash/components/boca/on_task/on_task_session_manager.cc
+++ b/chromeos/ash/components/boca/on_task/on_task_session_manager.cc
@@ -44,6 +44,9 @@
 // Delay in seconds before we attempt to pin or unpin the active SWA window.
 constexpr base::TimeDelta kSetPinnedStateDelay = base::Seconds(3);
 
+// Delay in seconds before we attempt to pause or unpause the active SWA window.
+constexpr base::TimeDelta kSetPausedStateDelay = base::Seconds(3);
+
 }  // namespace
 
 OnTaskSessionManager::OnTaskSessionManager(
@@ -189,6 +192,7 @@
   }
 
   LockOrUnlockWindow(bundle.locked());
+  PauseOrUnpauseApp(bundle.lock_to_app_home());
 
   // Show relevant notifications if content was added or deleted.
   if (has_new_content) {
@@ -276,6 +280,7 @@
 
 void OnTaskSessionManager::LockOrUnlockWindow(bool lock_window) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  lock_in_progress_ = lock_window;
   bool locked_mode_state_changed = (should_lock_window_ != lock_window);
   should_lock_window_ = lock_window;
   notifications_manager_->ConfigureForLockedMode(should_lock_window_);
@@ -329,6 +334,7 @@
 void OnTaskSessionManager::EnterLockedMode() {
   // If the Boca SWA is closed during the countdown, we launch it again so we
   // can pin the SWA window.
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (const SessionID window_id =
           system_web_app_manager_->GetActiveSystemWebAppWindowID();
       !window_id.is_valid()) {
@@ -340,6 +346,24 @@
                           weak_ptr_factory_.GetWeakPtr()));
 }
 
+void OnTaskSessionManager::PauseOrUnpauseApp(bool pause_app) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (lock_in_progress_) {
+    base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
+        FROM_HERE,
+        base::BindOnce(&OnTaskSessionManager::PauseOrUnpauseApp,
+                       weak_ptr_factory_.GetWeakPtr(), pause_app),
+        kSetPausedStateDelay);
+    return;
+  }
+  if (const SessionID window_id =
+          system_web_app_manager_->GetActiveSystemWebAppWindowID();
+      window_id.is_valid()) {
+    system_web_app_manager_->SetPauseStateForSystemWebAppWindow(pause_app,
+                                                                window_id);
+  }
+}
+
 void OnTaskSessionManager::OnTabAdded(const SessionID active_tab_id,
                                       const SessionID tab_id,
                                       const GURL url) {
@@ -538,6 +562,8 @@
 }
 
 void OnTaskSessionManager::OnSetPinStateOnBocaSWAWindow() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  lock_in_progress_ = false;
   // TODO (b/370871395): Move `SetWindowTrackerForSystemWebAppWindow` to
   // `OnTaskSystemWebAppManager` eliminating the need for this callback.
   if (const SessionID window_id =
diff --git a/chromeos/ash/components/boca/on_task/on_task_session_manager.h b/chromeos/ash/components/boca/on_task/on_task_session_manager.h
index cccc88b..a07fc86 100644
--- a/chromeos/ash/components/boca/on_task/on_task_session_manager.h
+++ b/chromeos/ash/components/boca/on_task/on_task_session_manager.h
@@ -113,6 +113,9 @@
   // otherwise.
   void LockOrUnlockWindow(bool lock_window);
 
+  // Internal helper used to pause or unpause the boca app.
+  void PauseOrUnpauseApp(bool pause_app);
+
   // Show enter locked mode notification and lock the Boca SWA window.
   void EnterLockedMode();
 
@@ -141,6 +144,7 @@
       GUARDED_BY_CONTEXT(sequence_checker_) = std::nullopt;
   GURL active_tab_url_ GUARDED_BY_CONTEXT(sequence_checker_);
   bool should_lock_window_ GUARDED_BY_CONTEXT(sequence_checker_) = false;
+  bool lock_in_progress_ GUARDED_BY_CONTEXT(sequence_checker_) = false;
 
   // Maps the url that providers send to the tab ids spawned from the url. This
   // map allows to remove all the related tabs to the url.
diff --git a/chromeos/ash/components/boca/on_task/on_task_session_manager_unittest.cc b/chromeos/ash/components/boca/on_task/on_task_session_manager_unittest.cc
index 07a55c8..dbf5a5db 100644
--- a/chromeos/ash/components/boca/on_task/on_task_session_manager_unittest.cc
+++ b/chromeos/ash/components/boca/on_task/on_task_session_manager_unittest.cc
@@ -66,6 +66,10 @@
               (bool pinned, SessionID window_id),
               (override));
   MOCK_METHOD(void,
+              SetPauseStateForSystemWebAppWindow,
+              (bool paused, SessionID window_id),
+              (override));
+  MOCK_METHOD(void,
               SetWindowTrackerForSystemWebAppWindow,
               (SessionID window_id,
                const std::vector<boca::BocaWindowObserver*> observers),
diff --git a/chromeos/ash/components/boca/on_task/on_task_system_web_app_manager.h b/chromeos/ash/components/boca/on_task/on_task_system_web_app_manager.h
index c60505a..9d4e613 100644
--- a/chromeos/ash/components/boca/on_task/on_task_system_web_app_manager.h
+++ b/chromeos/ash/components/boca/on_task/on_task_system_web_app_manager.h
@@ -39,6 +39,10 @@
   virtual void SetPinStateForSystemWebAppWindow(bool pinned,
                                                 SessionID window_id) = 0;
 
+  // Pause/unpause the specified Boca SWA window based on the specified value.
+  virtual void SetPauseStateForSystemWebAppWindow(bool paused,
+                                                  SessionID window_id) = 0;
+
   // Set the window tracker to track the browser browser window with specified
   // id.
   virtual void SetWindowTrackerForSystemWebAppWindow(
diff --git a/chromeos/ash/components/geolocation/BUILD.gn b/chromeos/ash/components/geolocation/BUILD.gn
index 0329928..2a8ade7 100644
--- a/chromeos/ash/components/geolocation/BUILD.gn
+++ b/chromeos/ash/components/geolocation/BUILD.gn
@@ -36,6 +36,7 @@
     "//base/test:test_support",
     "//chromeos/ash/components/dbus/shill",
     "//chromeos/ash/components/network:test_support",
+    "//google_apis",
     "//net",
     "//services/network:test_support",
     "//services/network/public/cpp",
diff --git a/chromeos/ash/components/geolocation/simple_geolocation_request.cc b/chromeos/ash/components/geolocation/simple_geolocation_request.cc
index 59ab275..9691480 100644
--- a/chromeos/ash/components/geolocation/simple_geolocation_request.cc
+++ b/chromeos/ash/components/geolocation/simple_geolocation_request.cc
@@ -11,6 +11,7 @@
 #include <string>
 #include <utility>
 
+#include "ash/constants/ash_features.h"
 #include "base/functional/bind.h"
 #include "base/json/json_reader.h"
 #include "base/json/json_writer.h"
@@ -153,7 +154,12 @@
   if (url != SimpleGeolocationProvider::DefaultGeolocationProviderURL())
     return url;
 
-  std::string api_key = google_apis::GetAPIKey();
+  std::string api_key;
+  if (features::IsCrosSeparateGeoApiKeyEnabled()) {
+    api_key = google_apis::GetCrosSystemGeoAPIKey();
+  } else {
+    api_key = google_apis::GetAPIKey();
+  }
   if (api_key.empty())
     return url;
 
@@ -478,6 +484,10 @@
   return FormatRequestBody();
 }
 
+GURL SimpleGeolocationRequest::GetServiceURLForTesting() const {
+  return request_url_;
+}
+
 void SimpleGeolocationRequest::Retry(bool server_error) {
   base::TimeDelta delay(server_error ? retry_sleep_on_server_error_
                                      : retry_sleep_on_bad_response_);
diff --git a/chromeos/ash/components/geolocation/simple_geolocation_request.h b/chromeos/ash/components/geolocation/simple_geolocation_request.h
index d36406c..4c1d539 100644
--- a/chromeos/ash/components/geolocation/simple_geolocation_request.h
+++ b/chromeos/ash/components/geolocation/simple_geolocation_request.h
@@ -85,6 +85,7 @@
   static void SetTestMonitor(SimpleGeolocationRequestTestMonitor* monitor);
 
   std::string FormatRequestBodyForTesting() const;
+  GURL GetServiceURLForTesting() const;
 
  private:
   void OnSimpleURLLoaderComplete(std::unique_ptr<std::string> response_body);
diff --git a/chromeos/ash/components/geolocation/simple_geolocation_unittest.cc b/chromeos/ash/components/geolocation/simple_geolocation_unittest.cc
index e511954..3902aae 100644
--- a/chromeos/ash/components/geolocation/simple_geolocation_unittest.cc
+++ b/chromeos/ash/components/geolocation/simple_geolocation_unittest.cc
@@ -6,6 +6,7 @@
 
 #include <memory>
 
+#include "ash/constants/ash_features.h"
 #include "ash/constants/geolocation_access_level.h"
 #include "base/containers/contains.h"
 #include "base/functional/bind.h"
@@ -14,6 +15,7 @@
 #include "base/run_loop.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/stringprintf.h"
+#include "base/test/scoped_feature_list.h"
 #include "base/test/task_environment.h"
 #include "base/time/time.h"
 #include "chromeos/ash/components/dbus/shill/shill_manager_client.h"
@@ -21,6 +23,9 @@
 #include "chromeos/ash/components/geolocation/simple_geolocation_request_test_monitor.h"
 #include "chromeos/ash/components/network/geolocation_handler.h"
 #include "chromeos/ash/components/network/network_handler_test_helper.h"
+#include "google_apis/api_key_cache.h"
+#include "google_apis/default_api_keys.h"
+#include "google_apis/google_api_keys.h"
 #include "net/http/http_response_headers.h"
 #include "net/http/http_status_code.h"
 #include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
@@ -117,7 +122,12 @@
   }
 
   void Intercept(const network::ResourceRequest& request) {
-    EXPECT_EQ(url_, request.url);
+    // Drop the query component potentially appended by the
+    // `SimpleGeolocationRequest` class.
+    GURL::Replacements replacements;
+    replacements.ClearQuery();
+    EXPECT_EQ(url_.ReplaceComponents(replacements),
+              request.url.ReplaceComponents(replacements));
 
     SimpleGeolocationProvider* provider =
         SimpleGeolocationProvider::GetInstance();
@@ -188,14 +198,17 @@
   void OnRequestCreated(SimpleGeolocationRequest* request) override {}
   void OnStart(SimpleGeolocationRequest* request) override {
     last_request_body_ = request->FormatRequestBodyForTesting();
+    last_request_url_ = request->GetServiceURLForTesting();
     ++requests_count_;
   }
 
   const std::string& last_request_body() const { return last_request_body_; }
+  GURL last_request_url() const { return last_request_url_; }
   unsigned int requests_count() const { return requests_count_; }
 
  private:
   std::string last_request_body_;
+  GURL last_request_url_;
   unsigned int requests_count_ = 0;
 };
 
@@ -399,6 +412,93 @@
   }
 }
 
+namespace override_geo_api_keys {
+
+// We start every test by creating a clean environment for the
+// preprocessor defines used in define_baked_in_api_keys-inc.cc
+#undef GOOGLE_API_KEY
+#undef GOOGLE_API_KEY_CROS_SYSTEM_GEO
+#undef GOOGLE_API_KEY_CROS_CHROME_GEO
+
+// Set Geolocation-specific keys.
+#define GOOGLE_API_KEY "bogus_api_key"
+#define GOOGLE_API_KEY_CROS_SYSTEM_GEO "bogus_cros_system_geo_api_key"
+#define GOOGLE_API_KEY_CROS_CHROME_GEO "bogus_cros_chrome_geo_api_key"
+
+// This file must be included after the internal files defining official keys.
+#include "google_apis/default_api_keys-inc.cc"
+
+}  // namespace override_geo_api_keys
+
+class SimpleGeolocationAPIKeyTest : public SimpleGeolocationTest,
+                                    public testing::WithParamInterface<bool> {
+ public:
+  SimpleGeolocationAPIKeyTest() : is_separate_api_keys_enabled_(GetParam()) {
+    if (is_separate_api_keys_enabled_) {
+      feature_list_.InitAndEnableFeature(features::kCrosSeparateGeoApiKey);
+    } else {
+      feature_list_.InitAndDisableFeature(features::kCrosSeparateGeoApiKey);
+    }
+  }
+
+  void SetUp() override {
+    SimpleGeolocationTest::SetUp();
+
+    // Set URL to the production value to let the `SimpleGeolocationRequest`
+    // extend it with the API keys.
+    SimpleGeolocationProvider::GetInstance()
+        ->SetGeolocationProviderUrlForTesting(
+            SimpleGeolocationProvider::DefaultGeolocationProviderURL()
+                .spec()
+                .c_str());
+    url_factory_.Configure(
+        SimpleGeolocationProvider::DefaultGeolocationProviderURL(),
+        net::HTTP_OK, kSimpleResponseBody, 0);
+  }
+
+  const bool is_separate_api_keys_enabled_;
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+TEST_P(SimpleGeolocationAPIKeyTest, TestCorrectAPIKeysAreUsed) {
+  GeolocationReceiver receiver;
+  WirelessTestMonitor requests_monitor;
+  SimpleGeolocationRequest::SetTestMonitor(&requests_monitor);
+
+  // Override the `ApiKeyCache` with the bogus values from above.
+  auto default_key_values =
+      override_geo_api_keys::GetDefaultApiKeysFromDefinedValues();
+  default_key_values.allow_unset_values = true;
+  google_apis::ApiKeyCache api_key_cache(default_key_values);
+  auto scoped_key_cache_override =
+      google_apis::SetScopedApiKeyCacheForTesting(&api_key_cache);
+
+  // Request geolocation and wait for the response.
+  EnableGeolocationUsage();
+  SimpleGeolocationProvider::GetInstance()->RequestGeolocation(
+      base::Seconds(1), false, false,
+      base::BindOnce(&GeolocationReceiver::OnRequestDone,
+                     base::Unretained(&receiver)),
+      SimpleGeolocationProvider::ClientId::kForTesting);
+  receiver.WaitUntilRequestDone();
+
+  // Check that the appropriate API key was used depending on the
+  // `CrosSeparateGeoApiKey` feature status.
+  const GURL request_url = requests_monitor.last_request_url();
+  ASSERT_TRUE(request_url.has_query());
+  EXPECT_EQ(is_separate_api_keys_enabled_,
+            request_url.query().find(GOOGLE_API_KEY_CROS_SYSTEM_GEO) !=
+                std::string::npos);
+  EXPECT_EQ(is_separate_api_keys_enabled_,
+            request_url.query().find(GOOGLE_API_KEY) == std::string::npos);
+  EXPECT_EQ(1U, url_factory_.attempts());
+}
+
+// GetParam() - `ash::features::kCrosSeparateGeoApiKey` feature state.
+INSTANTIATE_TEST_SUITE_P(All, SimpleGeolocationAPIKeyTest, testing::Bool());
+
 // Test sending of WiFi Access points and Cell Towers.
 // (This is mostly derived from GeolocationHandlerTest.)
 class SimpleGeolocationWirelessTest : public SimpleGeolocationTestBase,
diff --git a/clank b/clank
index 7be9de7..9c6ca68 160000
--- a/clank
+++ b/clank
@@ -1 +1 @@
-Subproject commit 7be9de743e86b1cbfae5ab74ca1a7824c17138e4
+Subproject commit 9c6ca681daaa22012dad5ebfaf9e87c7112ebac5
diff --git a/components/autofill/core/browser/filling/autofill_ai/field_filling_entity_util.cc b/components/autofill/core/browser/filling/autofill_ai/field_filling_entity_util.cc
index 2cac901..1652ddc 100644
--- a/components/autofill/core/browser/filling/autofill_ai/field_filling_entity_util.cc
+++ b/components/autofill/core/browser/filling/autofill_ai/field_filling_entity_util.cc
@@ -28,7 +28,6 @@
 struct Sequence {
   size_t offset = 0;
   size_t length = 0;
-  bool reverse = false;
 };
 
 // Returns a longest sequence of non-negative consecutive integers in `nums`.
@@ -117,7 +116,7 @@
     }
     // The user-invisible values must start at "0" or "1".
     if (28 <= value_seq.length && value_seq.length <= 31 &&
-        text_nums[text_seq.offset] <= 1) {
+        value_nums[value_seq.offset] <= 1) {
       return value_seq.offset + day - 1;
     }
     return -1;
@@ -162,7 +161,7 @@
       return text_seq.offset + month - 1;
     }
     // The user-invisible values must be "1" to "12" or "0" to "11".
-    if (value_seq.length == 12 && text_nums[text_seq.offset] <= 1) {
+    if (value_seq.length == 12 && value_nums[value_seq.offset] <= 1) {
       return value_seq.offset + month - 1;
     }
     // If there are no numbers, perhaps the months are spelled out.
diff --git a/components/autofill/core/browser/payments/credit_card_access_manager.cc b/components/autofill/core/browser/payments/credit_card_access_manager.cc
index 24fac95..da892f5 100644
--- a/components/autofill/core/browser/payments/credit_card_access_manager.cc
+++ b/components/autofill/core/browser/payments/credit_card_access_manager.cc
@@ -392,9 +392,15 @@
 }
 
 bool CreditCardAccessManager::IsMaskedServerCardRiskBasedAuthAvailable() const {
+  bool isCardInfoRetrievalEnrolled =
+      base::FeatureList::IsEnabled(
+          features::kAutofillEnableCardInfoRuntimeRetrieval) &&
+      (card_->card_info_retrieval_enrollment_state() ==
+       CreditCard::CardInfoRetrievalEnrollmentState::kRetrievalEnrolled);
   return !card_->IsExpired(AutofillClock::Now()) &&
-         base::FeatureList::IsEnabled(
-             features::kAutofillEnableFpanRiskBasedAuthentication);
+         (base::FeatureList::IsEnabled(
+              features::kAutofillEnableFpanRiskBasedAuthentication) ||
+          isCardInfoRetrievalEnrolled);
 }
 
 void CreditCardAccessManager::FIDOAuthOptChange(bool opt_in) {
diff --git a/components/autofill/core/browser/payments/credit_card_access_manager_risk_based_unittest.cc b/components/autofill/core/browser/payments/credit_card_access_manager_risk_based_unittest.cc
index 36d9da5d..014968c8 100644
--- a/components/autofill/core/browser/payments/credit_card_access_manager_risk_based_unittest.cc
+++ b/components/autofill/core/browser/payments/credit_card_access_manager_risk_based_unittest.cc
@@ -37,12 +37,17 @@
 class CreditCardAccessManagerRiskBasedMaskedServerCardUnmaskingTest
     : public CreditCardAccessManagerTestBase {
  public:
-  CreditCardAccessManagerRiskBasedMaskedServerCardUnmaskingTest() = default;
+  CreditCardAccessManagerRiskBasedMaskedServerCardUnmaskingTest() {
+    feature_list_.InitWithFeatures(
+        /*enabled_features=*/{features::kAutofillEnableCardInfoRuntimeRetrieval,
+                              features::
+                                  kAutofillEnableFpanRiskBasedAuthentication},
+        /*disabled_features=*/{});
+  }
   ~CreditCardAccessManagerRiskBasedMaskedServerCardUnmaskingTest() override =
       default;
 
-  base::test::ScopedFeatureList feature_list_{
-      features::kAutofillEnableFpanRiskBasedAuthentication};
+  base::test::ScopedFeatureList feature_list_;
 
   void MockRiskBasedAuthSucceedsWithoutPanReturned(
       const CreditCard* card,
@@ -486,7 +491,6 @@
                    ->risk_based_authentication_invoked());
 }
 
-#if !BUILDFLAG(IS_IOS)
 // Test the green path flow when the masked server card enrolled in card info
 // retrieval is successfully returned from the server during a risk-based
 // retrieval.
@@ -579,6 +583,7 @@
       1);
 }
 
+#if !BUILDFLAG(IS_IOS)
 // Test the yellow path flow when the masked server card enrolled in card info
 // retrieval is retrieved from the server with Sms Otp authentication.
 TEST_F(CreditCardAccessManagerRiskBasedMaskedServerCardUnmaskingTest,
diff --git a/components/autofill/core/browser/payments/credit_card_access_manager_unittest.cc b/components/autofill/core/browser/payments/credit_card_access_manager_unittest.cc
index 5ee3444c1..a51fd94 100644
--- a/components/autofill/core/browser/payments/credit_card_access_manager_unittest.cc
+++ b/components/autofill/core/browser/payments/credit_card_access_manager_unittest.cc
@@ -1925,6 +1925,32 @@
                 kCardInfoRetrievalEnrolledUnmaskProgressDialog);
 }
 
+// Ensures the `kCardInfoRetrievalEnrolledUnmaskProgressDialog` is not set if a
+// card is not enrolled for retrieval.
+TEST_F(CreditCardAccessManagerTest,
+       CardInfoRetrievalEnrolledCardUnmaskingDisabled) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitWithFeatures(
+      /*enabled_features=*/{features::kAutofillEnableCardInfoRuntimeRetrieval},
+      /*disabled_features=*/{
+          features::kAutofillEnableFpanRiskBasedAuthentication});
+
+  base::HistogramTester histogram_tester;
+  CreditCard server_card = test::GetMaskedServerCard();
+  server_card.set_guid(kTestGUID);
+  server_card.set_card_info_retrieval_enrollment_state(
+      CreditCard::CardInfoRetrievalEnrollmentState::kRetrievalUnspecified);
+  personal_data().test_payments_data_manager().AddServerCreditCard(server_card);
+  const CreditCard* card =
+      personal_data().payments_data_manager().GetCreditCardByGUID(kTestGUID);
+
+  FetchCreditCard(card);
+
+  // Ensures CreditCardRiskBasedAuthenticator::Authenticate is not invoked.
+  ASSERT_FALSE(autofill_client_.GetPaymentsAutofillClient()
+                   ->risk_based_authentication_invoked());
+}
+
 // Ensures the virtual card risk-based unmasking response is handled correctly
 // and authentication is delegated to the correct authenticator when multiple
 // challenge options are returned.
diff --git a/components/autofill/core/browser/webdata/valuables/loyalty_card_sync_bridge.cc b/components/autofill/core/browser/webdata/valuables/loyalty_card_sync_bridge.cc
index 32060d6c..b8d306f 100644
--- a/components/autofill/core/browser/webdata/valuables/loyalty_card_sync_bridge.cc
+++ b/components/autofill/core/browser/webdata/valuables/loyalty_card_sync_bridge.cc
@@ -17,7 +17,7 @@
 #include "components/sync/model/client_tag_based_data_type_processor.h"
 #include "components/sync/model/in_memory_metadata_change_list.h"
 #include "components/sync/model/sync_metadata_store_change_list.h"
-#include "components/sync/protocol/autofill_loyalty_card_specifics.pb.h"
+#include "components/sync/protocol/autofill_valuable_specifics.pb.h"
 #include "components/sync/protocol/entity_data.h"
 #include "components/webdata/common/web_database.h"
 
@@ -97,11 +97,11 @@
   for (const std::unique_ptr<syncer::EntityChange>& change : entity_data) {
     switch (change->type()) {
       case syncer::EntityChange::ACTION_ADD: {
-        DCHECK(change->data().specifics.has_autofill_loyalty_card());
+        DCHECK(change->data().specifics.has_autofill_valuable());
         // Deserialize the LoyaltyCardSpecifics and add them in the DB.
         std::optional<LoyaltyCard> remote =
             CreateAutofillLoyaltyCardFromSpecifics(
-                change->data().specifics.autofill_loyalty_card());
+                change->data().specifics.autofill_valuable());
         // Since the specifics are guaranteed to be valid by
         // `IsEntityDataValid()`, the conversion will succeed.
         DCHECK(remote);
@@ -166,9 +166,9 @@
 
 bool LoyaltyCardSyncBridge::IsEntityDataValid(
     const syncer::EntityData& entity_data) const {
-  DCHECK(entity_data.specifics.has_autofill_loyalty_card());
+  DCHECK(entity_data.specifics.has_autofill_valuable());
   return AreAutofillLoyaltyCardSpecificsValid(
-      entity_data.specifics.autofill_loyalty_card());
+      entity_data.specifics.autofill_valuable());
 }
 
 std::string LoyaltyCardSyncBridge::GetClientTag(
@@ -179,7 +179,7 @@
 std::string LoyaltyCardSyncBridge::GetStorageKey(
     const syncer::EntityData& entity_data) {
   DCHECK(IsEntityDataValid(entity_data));
-  return entity_data.specifics.autofill_loyalty_card().id();
+  return entity_data.specifics.autofill_valuable().id();
 }
 
 void LoyaltyCardSyncBridge::ApplyDisableSyncChanges(
@@ -209,20 +209,19 @@
 sync_pb::EntitySpecifics
 LoyaltyCardSyncBridge::TrimAllSupportedFieldsFromRemoteSpecifics(
     const sync_pb::EntitySpecifics& entity_specifics) const {
-  sync_pb::AutofillLoyaltyCardSpecifics
-      trimmed_autofill_loyalty_card_specifics =
-          TrimLoyaltyCardSpecificsDataForCaching(
-              entity_specifics.autofill_loyalty_card());
+  sync_pb::AutofillValuableSpecifics trimmed_autofill_valuable_specifics =
+      TrimAutofillValuableSpecificsDataForCaching(
+          entity_specifics.autofill_valuable());
 
-  // If all fields are cleared from the loyalty card specifics, return a fresh
+  // If all fields are cleared from the valuable specifics, return a fresh
   // EntitySpecifics to avoid caching a few residual bytes.
-  if (trimmed_autofill_loyalty_card_specifics.ByteSizeLong() == 0u) {
+  if (trimmed_autofill_valuable_specifics.ByteSizeLong() == 0u) {
     return sync_pb::EntitySpecifics();
   }
 
   sync_pb::EntitySpecifics trimmed_entity_specifics;
-  *trimmed_entity_specifics.mutable_autofill_loyalty_card() =
-      std::move(trimmed_autofill_loyalty_card_specifics);
+  *trimmed_entity_specifics.mutable_autofill_valuable() =
+      std::move(trimmed_autofill_valuable_specifics);
 
   return trimmed_entity_specifics;
 }
diff --git a/components/autofill/core/browser/webdata/valuables/loyalty_card_sync_bridge_unittest.cc b/components/autofill/core/browser/webdata/valuables/loyalty_card_sync_bridge_unittest.cc
index 7a203b24..5e66cdf 100644
--- a/components/autofill/core/browser/webdata/valuables/loyalty_card_sync_bridge_unittest.cc
+++ b/components/autofill/core/browser/webdata/valuables/loyalty_card_sync_bridge_unittest.cc
@@ -21,7 +21,7 @@
 #include "components/sync/base/data_type.h"
 #include "components/sync/base/features.h"
 #include "components/sync/model/data_batch.h"
-#include "components/sync/protocol/autofill_loyalty_card_specifics.pb.h"
+#include "components/sync/protocol/autofill_valuable_specifics.pb.h"
 #include "components/sync/test/mock_data_type_local_change_processor.h"
 #include "components/webdata/common/web_database.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -51,7 +51,7 @@
   while (batch->HasNext()) {
     const syncer::KeyAndData& data_pair = batch->Next();
     loyalty_cards.push_back(*CreateAutofillLoyaltyCardFromSpecifics(
-        data_pair.second->specifics.autofill_loyalty_card()));
+        data_pair.second->specifics.autofill_valuable()));
   }
   return loyalty_cards;
 }
@@ -139,7 +139,7 @@
       CreateEntityDataFromLoyaltyCard(TestLoyaltyCard(kId1));
   EXPECT_TRUE(bridge().IsEntityDataValid(*entity));
   // Invalid case.
-  entity->specifics.mutable_autofill_loyalty_card()->set_id(kInvalidId);
+  entity->specifics.mutable_autofill_valuable()->set_id(kInvalidId);
   EXPECT_FALSE(bridge().IsEntityDataValid(*entity));
 }
 
@@ -261,17 +261,19 @@
   EXPECT_TRUE(GetAllDataFromTable().empty());
 }
 
-// Tests that trimming `AutofillLoyaltyCardSpecifics` with only supported values
+// Tests that trimming `AutofillValuableSpecifics` with only supported values
 // set results in a zero-length specifics.
 TEST_F(LoyaltyCardSyncBridgeTest,
        TrimAllSupportedFieldsFromRemoteSpecificsPreservesOnlySupportedFields) {
   sync_pb::EntitySpecifics specifics;
-  sync_pb::AutofillLoyaltyCardSpecifics* loyalty_card_specifics =
-      specifics.mutable_autofill_loyalty_card();
-  loyalty_card_specifics->mutable_program_name()->assign("program_name");
-  loyalty_card_specifics->mutable_program_logo()->assign("program_logo");
-  loyalty_card_specifics->mutable_merchant_name()->assign("merchant_name");
-  loyalty_card_specifics->mutable_loyalty_card_suffix()->assign("card_suffix");
+  sync_pb::AutofillValuableSpecifics* autofill_valuables_specifics =
+      specifics.mutable_autofill_valuable();
+  sync_pb::AutofillValuableSpecifics::LoyaltyCard* loyalty_card =
+      autofill_valuables_specifics->mutable_loyalty_card();
+  loyalty_card->mutable_program_name()->assign("program_name");
+  loyalty_card->mutable_program_logo()->assign("program_logo");
+  loyalty_card->mutable_merchant_name()->assign("merchant_name");
+  loyalty_card->mutable_loyalty_card_suffix()->assign("card_suffix");
 
   EXPECT_EQ(bridge()
                 .TrimAllSupportedFieldsFromRemoteSpecifics(specifics)
@@ -279,22 +281,25 @@
             0u);
 }
 
-// Tests that trimming `AutofillLoyaltyCardSpecifics` with unsupported fields
+// Tests that trimming `AutofillValuableSpecifics` with unsupported fields
 // will only preserve the unknown fields.
 TEST_F(LoyaltyCardSyncBridgeTest,
        TrimRemoteSpecificsReturnsEmptyProtoWhenAllFieldsAreSupported) {
   sync_pb::EntitySpecifics specifics_with_only_unknown_fields;
-  *specifics_with_only_unknown_fields.mutable_autofill_loyalty_card()
+  *specifics_with_only_unknown_fields.mutable_autofill_valuable()
        ->mutable_unknown_fields() = "unsupported_fields";
 
   sync_pb::EntitySpecifics specifics_with_known_and_unknown_fields =
       specifics_with_only_unknown_fields;
-  sync_pb::AutofillLoyaltyCardSpecifics* loyalty_card_specifics =
-      specifics_with_known_and_unknown_fields.mutable_autofill_loyalty_card();
-  loyalty_card_specifics->mutable_program_name()->assign("program_name");
-  loyalty_card_specifics->mutable_program_logo()->assign("program_logo");
-  loyalty_card_specifics->mutable_merchant_name()->assign("merchant_name");
-  loyalty_card_specifics->mutable_loyalty_card_suffix()->assign("card_suffix");
+  sync_pb::AutofillValuableSpecifics* autofill_valuables_specifics =
+      specifics_with_known_and_unknown_fields.mutable_autofill_valuable();
+  sync_pb::AutofillValuableSpecifics::LoyaltyCard* loyalty_card =
+      autofill_valuables_specifics->mutable_loyalty_card();
+
+  loyalty_card->mutable_program_name()->assign("program_name");
+  loyalty_card->mutable_program_logo()->assign("program_logo");
+  loyalty_card->mutable_merchant_name()->assign("merchant_name");
+  loyalty_card->mutable_loyalty_card_suffix()->assign("card_suffix");
 
   EXPECT_EQ(bridge()
                 .TrimAllSupportedFieldsFromRemoteSpecifics(
diff --git a/components/autofill/core/browser/webdata/valuables/loyalty_card_sync_util.cc b/components/autofill/core/browser/webdata/valuables/loyalty_card_sync_util.cc
index 8accd37..1aec0279 100644
--- a/components/autofill/core/browser/webdata/valuables/loyalty_card_sync_util.cc
+++ b/components/autofill/core/browser/webdata/valuables/loyalty_card_sync_util.cc
@@ -5,64 +5,69 @@
 #include "components/autofill/core/browser/webdata/valuables/loyalty_card_sync_util.h"
 
 #include "base/uuid.h"
-#include "components/sync/protocol/autofill_loyalty_card_specifics.pb.h"
+#include "components/sync/protocol/autofill_valuable_specifics.pb.h"
 #include "url/gurl.h"
 
 namespace autofill {
 
-using sync_pb::AutofillLoyaltyCardSpecifics;
+using sync_pb::AutofillValuableSpecifics;
 
-AutofillLoyaltyCardSpecifics CreateSpecificsFromLoyaltyCard(
+AutofillValuableSpecifics CreateSpecificsFromLoyaltyCard(
     const LoyaltyCard& card) {
-  AutofillLoyaltyCardSpecifics specifics =
-      sync_pb::AutofillLoyaltyCardSpecifics();
+  AutofillValuableSpecifics specifics = sync_pb::AutofillValuableSpecifics();
   specifics.set_id(card.id().value());
-  specifics.set_merchant_name(card.merchant_name());
-  specifics.set_program_name(card.program_name());
-  specifics.set_program_logo(card.program_logo().possibly_invalid_spec());
-  specifics.set_loyalty_card_suffix(card.loyalty_card_suffix());
+  sync_pb::AutofillValuableSpecifics::LoyaltyCard* loyalty_card =
+      specifics.mutable_loyalty_card();
+  loyalty_card->set_merchant_name(card.merchant_name());
+  loyalty_card->set_program_name(card.program_name());
+  loyalty_card->set_program_logo(card.program_logo().possibly_invalid_spec());
+  loyalty_card->set_loyalty_card_suffix(card.loyalty_card_suffix());
   return specifics;
 }
 
-bool AreAutofillLoyaltyCardSpecificsValid(
-    const AutofillLoyaltyCardSpecifics& specifics) {
-  return !specifics.id().empty() && GURL(specifics.program_logo()).is_valid();
-}
-
 std::optional<LoyaltyCard> CreateAutofillLoyaltyCardFromSpecifics(
-    const AutofillLoyaltyCardSpecifics& specifics) {
+    const AutofillValuableSpecifics& specifics) {
   if (!AreAutofillLoyaltyCardSpecificsValid(specifics)) {
     return std::nullopt;
   }
-  return LoyaltyCard(ValuableId(specifics.id()), specifics.merchant_name(),
-                     specifics.program_name(), GURL(specifics.program_logo()),
-                     specifics.loyalty_card_suffix());
+  return LoyaltyCard(ValuableId(specifics.id()),
+                     specifics.loyalty_card().merchant_name(),
+                     specifics.loyalty_card().program_name(),
+                     GURL(specifics.loyalty_card().program_logo()),
+                     specifics.loyalty_card().loyalty_card_suffix());
 }
 
 std::unique_ptr<syncer::EntityData> CreateEntityDataFromLoyaltyCard(
     const LoyaltyCard& loyalty_card) {
-  AutofillLoyaltyCardSpecifics card_specifics =
+  AutofillValuableSpecifics card_specifics =
       CreateSpecificsFromLoyaltyCard(loyalty_card);
   std::unique_ptr<syncer::EntityData> entity_data =
       std::make_unique<syncer::EntityData>();
 
   entity_data->name = card_specifics.id();
-  AutofillLoyaltyCardSpecifics* specifics =
-      entity_data->specifics.mutable_autofill_loyalty_card();
+  AutofillValuableSpecifics* specifics =
+      entity_data->specifics.mutable_autofill_valuable();
   specifics->CopyFrom(card_specifics);
 
   return entity_data;
 }
 
-AutofillLoyaltyCardSpecifics TrimLoyaltyCardSpecificsDataForCaching(
-    const AutofillLoyaltyCardSpecifics& specifics) {
-  AutofillLoyaltyCardSpecifics trimmed_specifics =
-      AutofillLoyaltyCardSpecifics(specifics);
+bool AreAutofillLoyaltyCardSpecificsValid(
+    const AutofillValuableSpecifics& specifics) {
+  return !specifics.id().empty() && specifics.has_loyalty_card() &&
+         GURL(specifics.loyalty_card().program_logo()).is_valid();
+}
+
+AutofillValuableSpecifics TrimAutofillValuableSpecificsDataForCaching(
+    const AutofillValuableSpecifics& specifics) {
+  AutofillValuableSpecifics trimmed_specifics =
+      AutofillValuableSpecifics(specifics);
   trimmed_specifics.clear_id();
-  trimmed_specifics.clear_merchant_name();
-  trimmed_specifics.clear_program_name();
-  trimmed_specifics.clear_program_logo();
-  trimmed_specifics.clear_loyalty_card_suffix();
+  trimmed_specifics.mutable_loyalty_card()->clear_merchant_name();
+  trimmed_specifics.mutable_loyalty_card()->clear_program_name();
+  trimmed_specifics.mutable_loyalty_card()->clear_program_logo();
+  trimmed_specifics.mutable_loyalty_card()->clear_loyalty_card_suffix();
+  trimmed_specifics.clear_valuable_data();
   return trimmed_specifics;
 }
 }  // namespace autofill
diff --git a/components/autofill/core/browser/webdata/valuables/loyalty_card_sync_util.h b/components/autofill/core/browser/webdata/valuables/loyalty_card_sync_util.h
index e3217abf..7e1fda6 100644
--- a/components/autofill/core/browser/webdata/valuables/loyalty_card_sync_util.h
+++ b/components/autofill/core/browser/webdata/valuables/loyalty_card_sync_util.h
@@ -6,34 +6,34 @@
 #define COMPONENTS_AUTOFILL_CORE_BROWSER_WEBDATA_VALUABLES_LOYALTY_CARD_SYNC_UTIL_H_
 
 #include "components/autofill/core/browser/data_model/valuables/loyalty_card.h"
-#include "components/sync/protocol/autofill_loyalty_card_specifics.pb.h"
+#include "components/sync/protocol/autofill_valuable_specifics.pb.h"
 #include "components/sync/protocol/entity_data.h"
 
 namespace autofill {
 // For a given `LoyaltyCard`, returns the corresponding
-// `sync_pb::AutofillLoyaltyCardSpecifics`.
-sync_pb::AutofillLoyaltyCardSpecifics CreateSpecificsFromLoyaltyCard(
+// `sync_pb::AutofillValuableSpecifics`.
+sync_pb::AutofillValuableSpecifics CreateSpecificsFromLoyaltyCard(
     const LoyaltyCard& card);
 
-// Converts the given `loyaltyCard` into a `syncer::EntityData`.
+// Converts the given `loyalty_card` into a `syncer::EntityData`.
 std::unique_ptr<syncer::EntityData> CreateEntityDataFromLoyaltyCard(
     const LoyaltyCard& loyalty_card);
 
-// Converts the given loyalty card `specifics` into an equivalent
-// `LoyaltyCard` or returns `nullopt` if specifics are invalid.
+// Converts the given valuable `specifics` into an equivalent LoyaltyCard
+// instance or returns `nullopt` if specifics are invalid.
 std::optional<LoyaltyCard> CreateAutofillLoyaltyCardFromSpecifics(
-    const sync_pb::AutofillLoyaltyCardSpecifics& specifics);
+    const sync_pb::AutofillValuableSpecifics& specifics);
 
-// Tests if the loyalty card `specifics` are valid and can be converted into an
-// `LoyaltyCard` using `CreateAutofillLoyaltyCardFromSpecifics()`.
+// Tests if the valuable `specifics` are valid and can be converted into an
+// Autofill class instance using `CreateAutofillLoyaltyCardFromSpecifics()`.
 bool AreAutofillLoyaltyCardSpecificsValid(
-    const sync_pb::AutofillLoyaltyCardSpecifics& specifics);
+    const sync_pb::AutofillValuableSpecifics& specifics);
 
 // Clears all supported fields from `specifics`. Supported
 // fields are all fields in the protobuf definition that have already been
 // included in the client version.
-sync_pb::AutofillLoyaltyCardSpecifics TrimLoyaltyCardSpecificsDataForCaching(
-    const sync_pb::AutofillLoyaltyCardSpecifics& specifics);
+sync_pb::AutofillValuableSpecifics TrimAutofillValuableSpecificsDataForCaching(
+    const sync_pb::AutofillValuableSpecifics& specifics);
 }  // namespace autofill
 
 #endif  // COMPONENTS_AUTOFILL_CORE_BROWSER_WEBDATA_VALUABLES_LOYALTY_CARD_SYNC_UTIL_H_
diff --git a/components/autofill/core/browser/webdata/valuables/loyalty_card_sync_util_unittest.cc b/components/autofill/core/browser/webdata/valuables/loyalty_card_sync_util_unittest.cc
index e113e0e..ff87733 100644
--- a/components/autofill/core/browser/webdata/valuables/loyalty_card_sync_util_unittest.cc
+++ b/components/autofill/core/browser/webdata/valuables/loyalty_card_sync_util_unittest.cc
@@ -4,7 +4,7 @@
 
 #include "components/autofill/core/browser/webdata/valuables/loyalty_card_sync_util.h"
 
-#include "components/sync/protocol/autofill_loyalty_card_specifics.pb.h"
+#include "components/sync/protocol/autofill_valuable_specifics.pb.h"
 #include "components/sync/protocol/entity_data.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -24,16 +24,19 @@
                      "suffix");
 }
 
-sync_pb::AutofillLoyaltyCardSpecifics TestSpecifics(
+sync_pb::AutofillValuableSpecifics TestLoyaltyCardSpecifics(
     std::string_view id = kId1,
     std::string_view program_logo = kValidProgramLogo) {
-  sync_pb::AutofillLoyaltyCardSpecifics specifics =
-      sync_pb::AutofillLoyaltyCardSpecifics();
+  sync_pb::AutofillValuableSpecifics specifics =
+      sync_pb::AutofillValuableSpecifics();
   specifics.set_id(std::string(id));
-  specifics.set_merchant_name("merchant_name");
-  specifics.set_program_name("program_name");
-  specifics.set_program_logo(std::string(program_logo));
-  specifics.set_loyalty_card_suffix("suffix");
+
+  sync_pb::AutofillValuableSpecifics::LoyaltyCard* loyalty_card =
+      specifics.mutable_loyalty_card();
+  loyalty_card->set_merchant_name("merchant_name");
+  loyalty_card->set_program_name("program_name");
+  loyalty_card->set_program_logo(std::string(program_logo));
+  loyalty_card->set_loyalty_card_suffix("suffix");
   return specifics;
 }
 
@@ -41,16 +44,17 @@
 
 class LoyaltyCardSyncUtilTest : public testing::Test {};
 
-TEST_F(LoyaltyCardSyncUtilTest, CreateSpecificsFromLoyaltyCard) {
+TEST_F(LoyaltyCardSyncUtilTest, CreateValuableSpecificsFromLoyaltyCard) {
   LoyaltyCard card = TestLoyaltyCard();
-  sync_pb::AutofillLoyaltyCardSpecifics specifics =
+  sync_pb::AutofillValuableSpecifics specifics =
       CreateSpecificsFromLoyaltyCard(card);
 
   EXPECT_EQ(card.id().value(), specifics.id());
-  EXPECT_EQ(card.merchant_name(), specifics.merchant_name());
-  EXPECT_EQ(card.program_name(), specifics.program_name());
-  EXPECT_EQ(card.program_logo(), specifics.program_logo());
-  EXPECT_EQ(card.loyalty_card_suffix(), specifics.loyalty_card_suffix());
+  EXPECT_EQ(card.merchant_name(), specifics.loyalty_card().merchant_name());
+  EXPECT_EQ(card.program_name(), specifics.loyalty_card().program_name());
+  EXPECT_EQ(card.program_logo(), specifics.loyalty_card().program_logo());
+  EXPECT_EQ(card.loyalty_card_suffix(),
+            specifics.loyalty_card().loyalty_card_suffix());
 }
 
 TEST_F(LoyaltyCardSyncUtilTest, CreateEntityDataFromLoyaltyCard) {
@@ -58,33 +62,38 @@
   std::unique_ptr<syncer::EntityData> entity_data =
       CreateEntityDataFromLoyaltyCard(card);
 
-  sync_pb::AutofillLoyaltyCardSpecifics specifics =
-      entity_data->specifics.autofill_loyalty_card();
+  sync_pb::AutofillValuableSpecifics specifics =
+      entity_data->specifics.autofill_valuable();
 
-  EXPECT_TRUE(entity_data->specifics.has_autofill_loyalty_card());
+  EXPECT_TRUE(entity_data->specifics.has_autofill_valuable());
   EXPECT_EQ(card.id().value(), specifics.id());
-  EXPECT_EQ(card.merchant_name(), specifics.merchant_name());
-  EXPECT_EQ(card.program_name(), specifics.program_name());
-  EXPECT_EQ(card.program_logo(), specifics.program_logo());
-  EXPECT_EQ(card.loyalty_card_suffix(), specifics.loyalty_card_suffix());
+  EXPECT_EQ(card.merchant_name(), specifics.loyalty_card().merchant_name());
+  EXPECT_EQ(card.program_name(), specifics.loyalty_card().program_name());
+  EXPECT_EQ(card.program_logo(), specifics.loyalty_card().program_logo());
+  EXPECT_EQ(card.loyalty_card_suffix(),
+            specifics.loyalty_card().loyalty_card_suffix());
 }
 
 TEST_F(LoyaltyCardSyncUtilTest, CreateAutofillLoyaltyCardFromSpecifics) {
-  EXPECT_EQ(CreateAutofillLoyaltyCardFromSpecifics(TestSpecifics(kInvalidId)),
+  EXPECT_EQ(CreateAutofillLoyaltyCardFromSpecifics(
+                TestLoyaltyCardSpecifics(kInvalidId)),
             std::nullopt);
-  EXPECT_EQ(TestLoyaltyCard(),
-            CreateAutofillLoyaltyCardFromSpecifics(TestSpecifics(kId1)));
+  EXPECT_EQ(TestLoyaltyCard(), CreateAutofillLoyaltyCardFromSpecifics(
+                                   TestLoyaltyCardSpecifics(kId1)));
 }
 
 TEST_F(LoyaltyCardSyncUtilTest, AreAutofillLoyaltyCardSpecificsValid) {
-  EXPECT_FALSE(AreAutofillLoyaltyCardSpecificsValid(TestSpecifics(kInvalidId)));
   EXPECT_FALSE(AreAutofillLoyaltyCardSpecificsValid(
-      TestSpecifics(kId1, kInvalidProgramLogo)));
-  EXPECT_TRUE(AreAutofillLoyaltyCardSpecificsValid(TestSpecifics(kId1)));
+      TestLoyaltyCardSpecifics(kInvalidId)));
+  EXPECT_FALSE(AreAutofillLoyaltyCardSpecificsValid(
+      TestLoyaltyCardSpecifics(kId1, kInvalidProgramLogo)));
+  EXPECT_TRUE(
+      AreAutofillLoyaltyCardSpecificsValid(TestLoyaltyCardSpecifics(kId1)));
 }
 
-TEST_F(LoyaltyCardSyncUtilTest, TrimLoyaltyCardSpecificsDataForCaching) {
-  EXPECT_EQ(TrimLoyaltyCardSpecificsDataForCaching(TestSpecifics(kId1))
+TEST_F(LoyaltyCardSyncUtilTest, TrimAutofillValuableSpecificsDataForCaching) {
+  EXPECT_EQ(TrimAutofillValuableSpecificsDataForCaching(
+                TestLoyaltyCardSpecifics(kId1))
                 .ByteSizeLong(),
             0u);
 }
diff --git a/components/browser_sync/common_controller_builder.cc b/components/browser_sync/common_controller_builder.cc
index 6e06ee2..25e5cbe 100644
--- a/components/browser_sync/common_controller_builder.cc
+++ b/components/browser_sync/common_controller_builder.cc
@@ -920,8 +920,6 @@
             std::make_unique<syncer::ForwardingDataTypeControllerDelegate>(
                 delegate),
             sync_service, identity_manager_.value()));
-
-    // In CLs #5, #6, ..., implement the bridge and keep adding unit tests.
   }
 
 #if !BUILDFLAG(IS_ANDROID)
diff --git a/components/browser_ui/site_settings/android/java/res/xml/site_settings_preferences.xml b/components/browser_ui/site_settings/android/java/res/xml/site_settings_preferences.xml
index 12e4c4a..6accdb5 100644
--- a/components/browser_ui/site_settings/android/java/res/xml/site_settings_preferences.xml
+++ b/components/browser_ui/site_settings/android/java/res/xml/site_settings_preferences.xml
@@ -52,6 +52,10 @@
     <org.chromium.components.browser_ui.settings.ChromeBasePreference
         android:fragment="org.chromium.components.browser_ui.site_settings.SingleCategorySettings"
         android:key="usb" />
+    <!-- Serial port -->
+    <org.chromium.components.browser_ui.settings.ChromeBasePreference
+        android:fragment="org.chromium.components.browser_ui.site_settings.SingleCategorySettings"
+        android:key="serial_port" />
     <!-- File editing -->
     <org.chromium.components.browser_ui.settings.ChromeBasePreference
         android:fragment="org.chromium.components.browser_ui.site_settings.SingleCategorySettings"
diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/ContentSettingsResources.java b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/ContentSettingsResources.java
index 778d095..5bb3350 100644
--- a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/ContentSettingsResources.java
+++ b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/ContentSettingsResources.java
@@ -548,6 +548,32 @@
                         .setDisabledDescriptionText(
                                 R.string.website_settings_motion_sensors_block_description);
 
+            case ContentSettingsType.SERIAL_CHOOSER_DATA:
+                return new ResourceItem(
+                        R.drawable.gm_filled_developer_board_24,
+                        0,
+                        ContentSettingValues.ASK,
+                        ContentSettingValues.BLOCK,
+                        0,
+                        0,
+                        0,
+                        0,
+                        0,
+                        0);
+
+            case ContentSettingsType.SERIAL_GUARD:
+                return new ResourceItem(
+                        R.drawable.gm_filled_developer_board_24,
+                        R.string.website_settings_serial_port,
+                        ContentSettingValues.ASK,
+                        ContentSettingValues.BLOCK,
+                        R.string.website_settings_category_serial_port_ask,
+                        R.string.website_settings_category_serial_port_blocked,
+                        R.string.website_settings_category_serial_port_a11y,
+                        R.drawable.gm_filled_developer_board_off_24,
+                        R.string.website_settings_serial_port_ask,
+                        R.string.website_settings_serial_port_block);
+
             case ContentSettingsType.SOUND:
                 return new ResourceItem(
                                 R.drawable.ic_volume_up_grey600_24dp,
diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SingleCategorySettings.java b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SingleCategorySettings.java
index a328059..253feef 100644
--- a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SingleCategorySettings.java
+++ b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SingleCategorySettings.java
@@ -1224,6 +1224,8 @@
                 return R.string.website_settings_automatic_downloads_page_description;
             } else if (mCategory.getType() == SiteSettingsCategory.Type.FILE_EDITING) {
                 return R.string.website_settings_file_editing_page_description;
+            } else if (mCategory.getType() == SiteSettingsCategory.Type.SERIAL_PORT) {
+                return R.string.website_settings_serial_port_page_description;
             }
         }
         return -1;
diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SiteSettingsCategory.java b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SiteSettingsCategory.java
index f2b819d7..2f74ecf 100644
--- a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SiteSettingsCategory.java
+++ b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SiteSettingsCategory.java
@@ -77,6 +77,7 @@
         Type.TRACKING_PROTECTION,
         Type.FILE_EDITING,
         Type.JAVASCRIPT_OPTIMIZER,
+        Type.SERIAL_PORT,
         Type.NUM_ENTRIES
     })
     @Retention(RetentionPolicy.SOURCE)
@@ -117,9 +118,10 @@
         int HAND_TRACKING = 31;
         int FILE_EDITING = 32;
         int JAVASCRIPT_OPTIMIZER = 33;
+        int SERIAL_PORT = 34;
 
         /** Number of handled categories used for calculating array sizes. */
-        int NUM_ENTRIES = 34;
+        int NUM_ENTRIES = 35;
     }
 
     private final BrowserContextHandle mBrowserContextHandle;
@@ -252,6 +254,8 @@
                 return ContentSettingsType.PROTECTED_MEDIA_IDENTIFIER;
             case Type.SENSORS:
                 return ContentSettingsType.SENSORS;
+            case Type.SERIAL_PORT:
+                return ContentSettingsType.SERIAL_GUARD;
             case Type.STORAGE_ACCESS:
                 return ContentSettingsType.STORAGE_ACCESS;
             case Type.SOUND:
@@ -280,6 +284,8 @@
                 return ContentSettingsType.USB_CHOOSER_DATA;
             case ContentSettingsType.BLUETOOTH_GUARD:
                 return ContentSettingsType.BLUETOOTH_CHOOSER_DATA;
+            case ContentSettingsType.SERIAL_GUARD:
+                return ContentSettingsType.SERIAL_CHOOSER_DATA;
             default:
                 return -1; // Conversion unavailable.
         }
@@ -339,6 +345,8 @@
                 return "protected_content";
             case Type.SENSORS:
                 return "sensors";
+            case Type.SERIAL_PORT:
+                return "serial_port";
             case Type.STORAGE_ACCESS:
                 return "storage_access";
             case Type.SOUND:
diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SiteSettingsUtil.java b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SiteSettingsUtil.java
index 5d0b3d9..0734cd8a 100644
--- a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SiteSettingsUtil.java
+++ b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SiteSettingsUtil.java
@@ -57,6 +57,8 @@
         ContentSettingsType.USB_CHOOSER_DATA,
         // Bluetooth is only shown when WEB_BLUETOOTH_NEW_PERMISSIONS_BACKEND is enabled.
         ContentSettingsType.BLUETOOTH_CHOOSER_DATA,
+        // Serial port is only shown when BLUETOOTH_RFCOMM_ANDROID is enabled.
+        ContentSettingsType.SERIAL_CHOOSER_DATA,
     };
 
     static final int[] EMBEDDED_PERMISSIONS = {
diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/WebsitePermissionsFetcher.java b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/WebsitePermissionsFetcher.java
index 34a7011..7f6cf4d 100644
--- a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/WebsitePermissionsFetcher.java
+++ b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/WebsitePermissionsFetcher.java
@@ -23,6 +23,8 @@
 import org.chromium.content_public.browser.ContentFeatureMap;
 import org.chromium.content_public.browser.HostZoomMap;
 import org.chromium.content_public.common.ContentSwitches;
+import org.chromium.device.DeviceFeatureList;
+import org.chromium.device.DeviceFeatureMap;
 import org.chromium.url.Origin;
 
 import java.util.ArrayList;
@@ -109,6 +111,7 @@
             case ContentSettingsType.STORAGE_ACCESS:
                 return WebsitePermissionsType.EMBEDDED_PERMISSION;
             case ContentSettingsType.BLUETOOTH_GUARD:
+            case ContentSettingsType.SERIAL_GUARD:
             case ContentSettingsType.USB_GUARD:
                 return WebsitePermissionsType.CHOSEN_OBJECT_INFO;
             default:
@@ -335,6 +338,17 @@
                 return;
             }
 
+            // The serial guard permission controls access to the Web Serial API, which enables
+            // sites to request access to connect specific serial ports. Users are presented with a
+            // chooser prompt in which they must select the serial port they would like to allow the
+            // site to connect to. Therefore, this permission also displays a list of permitted
+            // serial ports that each site can connect to.
+            // Remove this check after the flag is removed.
+            if (contentSettingsType == ContentSettingsType.SERIAL_GUARD
+                    && !DeviceFeatureMap.isEnabled(DeviceFeatureList.BLUETOOTH_RFCOMM_ANDROID)) {
+                return;
+            }
+
             switch (websitePermissionsType) {
                 case CONTENT_SETTING_EXCEPTION:
                     queue.add(new ExceptionInfoFetcher(contentSettingsType));
diff --git a/components/browser_ui/site_settings/android/website_preference_bridge.cc b/components/browser_ui/site_settings/android/website_preference_bridge.cc
index 53056857..d846452 100644
--- a/components/browser_ui/site_settings/android/website_preference_bridge.cc
+++ b/components/browser_ui/site_settings/android/website_preference_bridge.cc
@@ -887,6 +887,7 @@
       case ContentSettingsType::MEDIASTREAM_MIC:
       case ContentSettingsType::NFC:
       case ContentSettingsType::NOTIFICATIONS:
+      case ContentSettingsType::SERIAL_GUARD:
       case ContentSettingsType::STORAGE_ACCESS:
       case ContentSettingsType::USB_GUARD:
       case ContentSettingsType::VR:
diff --git a/components/browser_ui/strings/android/site_settings.grdp b/components/browser_ui/strings/android/site_settings.grdp
index 52ea5f8a..d021e64 100644
--- a/components/browser_ui/strings/android/site_settings.grdp
+++ b/components/browser_ui/strings/android/site_settings.grdp
@@ -1160,4 +1160,27 @@
       =1 {<ph name="SITE_COUNT">%1$s<ex>1</ex></ph> site}
       other {<ph name="SITE_COUNT">%1$s<ex>9</ex></ph> sites}}
   </message>
+
+  <!-- Serial port -->
+  <message name="IDS_WEBSITE_SETTINGS_CATEGORY_SERIAL_PORT_ASK" desc="Summary text explaining that the serial port permission is set to ask the user for permission to access individual devices. To be shown in the list of permission categories.">
+    Ask before allowing sites to connect to a device (recommended)
+  </message>
+  <message name="IDS_WEBSITE_SETTINGS_CATEGORY_SERIAL_PORT_BLOCKED" desc="Summary text explaining that the serial port permission is set to block all requests for access to devices. To be shown in the list of permission categories.">
+    Block sites from connecting to devices
+  </message>
+  <message name="IDS_WEBSITE_SETTINGS_CATEGORY_SERIAL_PORT_A11Y" desc="The screen reader announcement describing a toggle with two states, controlling sites can request permission to use serial ports. It is important to keep this string as two full sentences terminated by a period.">
+    When on, sites can ask to use serial ports. When off, sites can’t use serial ports.
+  </message>
+  <message name="IDS_WEBSITE_SETTINGS_SERIAL_PORT" desc="Title for serial port settings, which control which of the user's serial ports can be accessed from websites.">
+    Serial port
+  </message>
+  <message name="IDS_WEBSITE_SETTINGS_SERIAL_PORT_PAGE_DESCRIPTION" desc="Description of the serial ports content settings">
+    Sites usually connect to serial ports for data transfer features, like setting up your network
+  </message>
+  <message name="IDS_WEBSITE_SETTINGS_SERIAL_PORT_ASK" desc="Primary text corresponding to the ask button in the serial port permission radio button group.">
+    Sites can ask to connect to serial ports
+  </message>
+  <message name="IDS_WEBSITE_SETTINGS_SERIAL_PORT_BLOCK" desc="Primary text corresponding to the block button in the serial port permission radio button group.">
+    Don't allow sites to connect to serial ports
+  </message>
 </grit-part>
diff --git a/components/browser_ui/strings/android/site_settings_grdp/IDS_WEBSITE_SETTINGS_CATEGORY_SERIAL_PORT_A11Y.png.sha1 b/components/browser_ui/strings/android/site_settings_grdp/IDS_WEBSITE_SETTINGS_CATEGORY_SERIAL_PORT_A11Y.png.sha1
new file mode 100644
index 0000000..2f79160
--- /dev/null
+++ b/components/browser_ui/strings/android/site_settings_grdp/IDS_WEBSITE_SETTINGS_CATEGORY_SERIAL_PORT_A11Y.png.sha1
@@ -0,0 +1 @@
+b5562bb59d094add4a9166a82ae4761ba51d00c2
\ No newline at end of file
diff --git a/components/browser_ui/strings/android/site_settings_grdp/IDS_WEBSITE_SETTINGS_CATEGORY_SERIAL_PORT_ASK.png.sha1 b/components/browser_ui/strings/android/site_settings_grdp/IDS_WEBSITE_SETTINGS_CATEGORY_SERIAL_PORT_ASK.png.sha1
new file mode 100644
index 0000000..3dd2f9298
--- /dev/null
+++ b/components/browser_ui/strings/android/site_settings_grdp/IDS_WEBSITE_SETTINGS_CATEGORY_SERIAL_PORT_ASK.png.sha1
@@ -0,0 +1 @@
+7801aa7fcff82bf90d069cc485be7a9b375212b0
\ No newline at end of file
diff --git a/components/browser_ui/strings/android/site_settings_grdp/IDS_WEBSITE_SETTINGS_CATEGORY_SERIAL_PORT_BLOCKED.png.sha1 b/components/browser_ui/strings/android/site_settings_grdp/IDS_WEBSITE_SETTINGS_CATEGORY_SERIAL_PORT_BLOCKED.png.sha1
new file mode 100644
index 0000000..9bdab3b
--- /dev/null
+++ b/components/browser_ui/strings/android/site_settings_grdp/IDS_WEBSITE_SETTINGS_CATEGORY_SERIAL_PORT_BLOCKED.png.sha1
@@ -0,0 +1 @@
+83d1f9cd995bba6f718e72fbfc76231b32f583d7
\ No newline at end of file
diff --git a/components/browser_ui/strings/android/site_settings_grdp/IDS_WEBSITE_SETTINGS_SERIAL_PORT.png.sha1 b/components/browser_ui/strings/android/site_settings_grdp/IDS_WEBSITE_SETTINGS_SERIAL_PORT.png.sha1
new file mode 100644
index 0000000..3dd2f9298
--- /dev/null
+++ b/components/browser_ui/strings/android/site_settings_grdp/IDS_WEBSITE_SETTINGS_SERIAL_PORT.png.sha1
@@ -0,0 +1 @@
+7801aa7fcff82bf90d069cc485be7a9b375212b0
\ No newline at end of file
diff --git a/components/browser_ui/strings/android/site_settings_grdp/IDS_WEBSITE_SETTINGS_SERIAL_PORT_ASK.png.sha1 b/components/browser_ui/strings/android/site_settings_grdp/IDS_WEBSITE_SETTINGS_SERIAL_PORT_ASK.png.sha1
new file mode 100644
index 0000000..946fac5
--- /dev/null
+++ b/components/browser_ui/strings/android/site_settings_grdp/IDS_WEBSITE_SETTINGS_SERIAL_PORT_ASK.png.sha1
@@ -0,0 +1 @@
+a6bbbb925ec448703f16467baa25063a859c64e2
\ No newline at end of file
diff --git a/components/browser_ui/strings/android/site_settings_grdp/IDS_WEBSITE_SETTINGS_SERIAL_PORT_BLOCK.png.sha1 b/components/browser_ui/strings/android/site_settings_grdp/IDS_WEBSITE_SETTINGS_SERIAL_PORT_BLOCK.png.sha1
new file mode 100644
index 0000000..946fac5
--- /dev/null
+++ b/components/browser_ui/strings/android/site_settings_grdp/IDS_WEBSITE_SETTINGS_SERIAL_PORT_BLOCK.png.sha1
@@ -0,0 +1 @@
+a6bbbb925ec448703f16467baa25063a859c64e2
\ No newline at end of file
diff --git a/components/browser_ui/strings/android/site_settings_grdp/IDS_WEBSITE_SETTINGS_SERIAL_PORT_PAGE_DESCRIPTION.png.sha1 b/components/browser_ui/strings/android/site_settings_grdp/IDS_WEBSITE_SETTINGS_SERIAL_PORT_PAGE_DESCRIPTION.png.sha1
new file mode 100644
index 0000000..946fac5
--- /dev/null
+++ b/components/browser_ui/strings/android/site_settings_grdp/IDS_WEBSITE_SETTINGS_SERIAL_PORT_PAGE_DESCRIPTION.png.sha1
@@ -0,0 +1 @@
+a6bbbb925ec448703f16467baa25063a859c64e2
\ No newline at end of file
diff --git a/components/collaboration/internal/collaboration_controller.cc b/components/collaboration/internal/collaboration_controller.cc
index fc21840..7376225 100644
--- a/components/collaboration/internal/collaboration_controller.cc
+++ b/components/collaboration/internal/collaboration_controller.cc
@@ -14,6 +14,7 @@
 #include "components/collaboration/internal/metrics.h"
 #include "components/collaboration/public/collaboration_flow_type.h"
 #include "components/collaboration/public/collaboration_service.h"
+#include "components/collaboration/public/collaboration_utils.h"
 #include "components/data_sharing/public/data_sharing_service.h"
 #include "components/data_sharing/public/logger.h"
 #include "components/data_sharing/public/logger_common.mojom.h"
@@ -526,7 +527,6 @@
           controller->TransitionTo(StateId::kWaitingForSyncAndDataSharingGroup);
           return;
         }
-
         // If user is not part of the group, do a readgroup to ensure version
         // match.
         // TODO(haileywang): Do the version check in the preview data and do the
@@ -564,11 +564,6 @@
     }
   }
 
-  void OnProcessingFinishedWithSuccess() override {
-    CHECK_EQ(controller->flow().type, FlowType::kJoin);
-    controller->TransitionTo(StateId::kAddingUserToGroup);
-  }
-
  private:
   // Called to process the outcome of data sharing read event.
   void ProcessGroupDataOrFailureOutcome(
@@ -578,11 +573,24 @@
     if (!group_outcome.has_value()) {
       RecordJoinEvent(GetLogger(),
                       CollaborationServiceJoinEvent::kReadNewGroupFailed);
+      HandleError();
+      return;
     }
 
     RecordJoinEvent(GetLogger(),
                     CollaborationServiceJoinEvent::kReadNewGroupSuccess);
-    OnProcessingFinishedWithSuccess();
+
+    if (GetCurrentUserRoleForGroup(controller->identity_manager(),
+                                   group_outcome.value()) !=
+        data_sharing::MemberRole::kUnknown) {
+      RecordJoinEvent(
+          GetLogger(),
+          CollaborationServiceJoinEvent::kReadNewGroupUserIsAlreadyMember);
+      controller->TransitionTo(StateId::kWaitingForSyncAndDataSharingGroup);
+      return;
+    }
+
+    controller->TransitionTo(StateId::kAddingUserToGroup);
   }
 
   base::WeakPtrFactory<CheckingFlowRequirementsState> local_weak_ptr_factory_{
@@ -680,8 +688,21 @@
             CollaborationServiceJoinEvent::
                 kTimeoutWaitingForSyncAndDataSharingGroup),
         kTimeoutWaitingForDataSharingGroup);
-    tab_group_sync_observer_.Observe(controller->tab_group_sync_service());
-    data_sharing_observer_.Observe(controller->data_sharing_service());
+    const data_sharing::GroupId group_id =
+        controller->flow().join_token().group_id;
+
+    if (IsTabGroupInSync(group_id) && IsPeopleGroupInDataSharing(group_id)) {
+      OnProcessingFinishedWithSuccess();
+      return;
+    }
+
+    if (!IsTabGroupInSync(group_id)) {
+      tab_group_sync_observer_.Observe(controller->tab_group_sync_service());
+    }
+
+    if (!IsPeopleGroupInDataSharing(group_id)) {
+      data_sharing_observer_.Observe(controller->data_sharing_service());
+    }
   }
 
   // ControllerState implementation.
@@ -989,6 +1010,7 @@
     data_sharing::DataSharingService* data_sharing_service,
     tab_groups::TabGroupSyncService* tab_group_sync_service,
     syncer::SyncService* sync_service,
+    signin::IdentityManager* identity_manager,
     std::unique_ptr<CollaborationControllerDelegate> delegate,
     FinishCallback finish_and_delete)
     : flow_(flow),
@@ -996,6 +1018,7 @@
       data_sharing_service_(data_sharing_service),
       tab_group_sync_service_(tab_group_sync_service),
       sync_service_(sync_service),
+      identity_manager_(identity_manager),
       delegate_(std::move(delegate)),
       finish_and_delete_(std::move(finish_and_delete)) {
   current_state_ = std::make_unique<PendingState>(
diff --git a/components/collaboration/internal/collaboration_controller.h b/components/collaboration/internal/collaboration_controller.h
index 673c256e..9453e60 100644
--- a/components/collaboration/internal/collaboration_controller.h
+++ b/components/collaboration/internal/collaboration_controller.h
@@ -15,6 +15,7 @@
 #include "components/data_sharing/public/data_sharing_service.h"
 #include "components/data_sharing/public/group_data.h"
 #include "components/saved_tab_groups/public/types.h"
+#include "components/signin/public/identity_manager/identity_manager.h"
 
 namespace syncer {
 class SyncService;
@@ -133,6 +134,7 @@
       data_sharing::DataSharingService* data_sharing_service,
       tab_groups::TabGroupSyncService* tab_group_sync_service,
       syncer::SyncService* sync_service,
+      signin::IdentityManager* identity_manager,
       std::unique_ptr<CollaborationControllerDelegate> delegate,
       FinishCallback finish_and_delete);
   ~CollaborationController();
@@ -150,6 +152,9 @@
     return tab_group_sync_service_.get();
   }
   syncer::SyncService* sync_service() { return sync_service_.get(); }
+  signin::IdentityManager* identity_manager() {
+    return identity_manager_.get();
+  }
   CollaborationService* collaboration_service() {
     return collaboration_service_.get();
   }
@@ -315,6 +320,7 @@
   const raw_ptr<data_sharing::DataSharingService> data_sharing_service_;
   const raw_ptr<tab_groups::TabGroupSyncService> tab_group_sync_service_;
   const raw_ptr<syncer::SyncService> sync_service_;
+  const raw_ptr<signin::IdentityManager> identity_manager_;
   std::unique_ptr<CollaborationControllerDelegate> delegate_;
   FinishCallback finish_and_delete_;
   base::WeakPtrFactory<CollaborationController> weak_ptr_factory_{this};
diff --git a/components/collaboration/internal/collaboration_controller_unittest.cc b/components/collaboration/internal/collaboration_controller_unittest.cc
index 2087b13..7f9ccb9 100644
--- a/components/collaboration/internal/collaboration_controller_unittest.cc
+++ b/components/collaboration/internal/collaboration_controller_unittest.cc
@@ -22,6 +22,7 @@
 #include "components/saved_tab_groups/public/tab_group_sync_service.h"
 #include "components/saved_tab_groups/test_support/mock_tab_group_sync_service.h"
 #include "components/saved_tab_groups/test_support/saved_tab_group_test_utils.h"
+#include "components/signin/public/identity_manager/identity_test_environment.h"
 #include "components/sync/test/mock_sync_service.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -30,8 +31,9 @@
 
 namespace {
 
-const char kGroupId[] = "/?-group_id";
+const data_sharing::GroupId kGroupId("/?-group_id");
 const char kAccessToken[] = "/?-access_token";
+constexpr char kUserEmail[] = "test@email.com";
 
 using StateId = CollaborationController::StateId;
 using Outcome = CollaborationControllerDelegate::Outcome;
@@ -75,7 +77,8 @@
         .WillOnce(MoveArg<1>(&prepare_ui_callback_));
     controller_ = std::make_unique<CollaborationController>(
         flow, collaboration_service_.get(), data_sharing_service_.get(),
-        tab_group_sync_service_.get(), sync_service_.get(), std::move(delegate),
+        tab_group_sync_service_.get(), sync_service_.get(),
+        identity_test_env_.identity_manager(), std::move(delegate),
         base::BindOnce(&CollaborationControllerTest::FinishFlow,
                        weak_ptr_factory_.GetWeakPtr(),
                        std::move(run_on_flow_exit)));
@@ -84,8 +87,7 @@
   void InitializeJoinController(OnceClosure run_on_flow_exit) {
     InitializeController(
         std::move(run_on_flow_exit),
-        Flow(FlowType::kJoin,
-             GroupToken(data_sharing::GroupId(kGroupId), kAccessToken)));
+        Flow(FlowType::kJoin, GroupToken(kGroupId, kAccessToken)));
   }
 
   void FinishFlow(OnceClosure run_on_flow_exit) {
@@ -96,6 +98,7 @@
  protected:
   base::test::SingleThreadTaskEnvironment task_environment_{
       base::test::TaskEnvironment::TimeSource::MOCK_TIME};
+  signin::IdentityTestEnvironment identity_test_env_;
   base::OnceCallback<void(Outcome)> prepare_ui_callback_;
   MockCollaborationControllerDelegate* delegate_;
   std::unique_ptr<MockCollaborationService> collaboration_service_;
@@ -156,12 +159,11 @@
 
   // Simulate that the user is not already in the tab group.
   EXPECT_CALL(*tab_group_sync_service_, RemoveObserver(sync_observer));
-  data_sharing::GroupId group_id(kGroupId);
-  const GroupToken& token = GroupToken(group_id, kAccessToken);
+  const GroupToken& token = GroupToken(kGroupId, kAccessToken);
   base::OnceCallback<void(
       const data_sharing::DataSharingService::GroupDataOrFailureOutcome&)>
       group_data_callback;
-  EXPECT_CALL(*collaboration_service_, GetCurrentUserRoleForGroup(group_id))
+  EXPECT_CALL(*collaboration_service_, GetCurrentUserRoleForGroup(kGroupId))
       .WillRepeatedly(Return(data_sharing::MemberRole::kUnknown));
   EXPECT_CALL(*data_sharing_service_, ReadNewGroup(token, IsNotNullCallback()))
       .WillOnce(MoveArg<1>(&group_data_callback));
@@ -174,7 +176,7 @@
   // The user should be shown invitation screen for joining a collaboration
   // group.
   GroupData group_data =
-      GroupData(group_id, /*display_name=*/"",
+      GroupData(kGroupId, /*display_name=*/"",
                 /*members=*/{}, /*former_members=*/{}, kAccessToken);
   base::OnceCallback<void(Outcome)> join_ui_callback;
   base::OnceCallback<void(const data_sharing::DataSharingService::
@@ -197,8 +199,7 @@
   // added in sync.
   SavedTabGroup tab_group(std::u16string(u"title"),
                           tab_groups::TabGroupColorId::kGrey, {});
-  tab_group.SetCollaborationId(
-      tab_groups::CollaborationId(std::string(kGroupId)));
+  tab_group.SetCollaborationId(tab_groups::CollaborationId(kGroupId.value()));
   std::vector<SavedTabGroup> all_tab_groups;
   EXPECT_CALL(*tab_group_sync_service_, GetAllGroups())
       .WillRepeatedly(Return(all_tab_groups));
@@ -215,7 +216,7 @@
             StateId::kWaitingForSyncAndDataSharingGroup);
 
   // Added tab group in sync but not in data sharing should not transition.
-  EXPECT_CALL(*collaboration_service_, GetCurrentUserRoleForGroup(group_id))
+  EXPECT_CALL(*collaboration_service_, GetCurrentUserRoleForGroup(kGroupId))
       .WillOnce(Return(data_sharing::MemberRole::kUnknown));
   sync_observer->OnTabGroupAdded(tab_group, tab_groups::TriggerSource::REMOTE);
   EXPECT_EQ(controller_->GetStateForTesting(),
@@ -223,10 +224,9 @@
 
   // Simulate added in both tab group and data_sharing group.
   base::OnceCallback<void(Outcome)> promote_ui_callback;
-  EXPECT_CALL(*collaboration_service_, GetCurrentUserRoleForGroup(group_id))
+  EXPECT_CALL(*collaboration_service_, GetCurrentUserRoleForGroup(kGroupId))
       .WillOnce(Return(data_sharing::MemberRole::kMember));
-  EXPECT_CALL(*delegate_, PromoteTabGroup(data_sharing::GroupId(kGroupId),
-                                          IsNotNullCallback()))
+  EXPECT_CALL(*delegate_, PromoteTabGroup(kGroupId, IsNotNullCallback()))
       .WillOnce(MoveArg<1>(&promote_ui_callback));
   EXPECT_CALL(*tab_group_sync_service_, RemoveObserver(sync_observer));
   EXPECT_CALL(*data_sharing_service_, RemoveObserver(data_sharing_observer));
@@ -436,8 +436,7 @@
       callback;
   EXPECT_CALL(
       *data_sharing_service_,
-      ReadNewGroup(GroupToken(data_sharing::GroupId(kGroupId), kAccessToken),
-                   IsNotNullCallback()))
+      ReadNewGroup(GroupToken(kGroupId, kAccessToken), IsNotNullCallback()))
       .WillOnce(MoveArg<1>(&callback));
 
   controller_->SetStateForTesting(StateId::kCheckingFlowRequirements);
@@ -446,8 +445,36 @@
       base::unexpected(data_sharing::DataSharingService::
                            PeopleGroupActionFailure::kPersistentFailure));
 
+  EXPECT_EQ(controller_->GetStateForTesting(), StateId::kError);
+}
+
+TEST_F(CollaborationControllerTest, ReadNewGroupAlreadyExist) {
+  // Start Join flow.
+  InitializeJoinController(base::DoNothing());
+
+  base::OnceCallback<void(
+      const data_sharing::DataSharingService::GroupDataOrFailureOutcome&)>
+      callback;
+  EXPECT_CALL(
+      *data_sharing_service_,
+      ReadNewGroup(GroupToken(kGroupId, kAccessToken), IsNotNullCallback()))
+      .WillOnce(MoveArg<1>(&callback));
+
+  controller_->SetStateForTesting(StateId::kCheckingFlowRequirements);
+
+  CoreAccountInfo account = identity_test_env_.SetPrimaryAccount(
+      kUserEmail, signin::ConsentLevel::kSignin);
+  data_sharing::GroupMember self;
+  self.gaia_id = account.gaia;
+  self.role = data_sharing::MemberRole::kOwner;
+  GroupData group_data =
+      GroupData(kGroupId, /*display_name=*/"",
+                /*members=*/{self}, /*former_members=*/{}, kAccessToken);
+  std::move(callback).Run(group_data);
+
   // Fix this to expect error when SDK implementation is done.
-  EXPECT_EQ(controller_->GetStateForTesting(), StateId::kAddingUserToGroup);
+  EXPECT_EQ(controller_->GetStateForTesting(),
+            StateId::kWaitingForSyncAndDataSharingGroup);
 }
 
 TEST_F(CollaborationControllerTest, PreviewDataUrlInvalidFailure) {
@@ -458,9 +485,8 @@
                               SharedDataPreviewOrFailureOutcome&)>
       preview_callback;
   EXPECT_CALL(*data_sharing_service_,
-              GetSharedEntitiesPreview(
-                  GroupToken(data_sharing::GroupId(kGroupId), kAccessToken),
-                  IsNotNullCallback()))
+              GetSharedEntitiesPreview(GroupToken(kGroupId, kAccessToken),
+                                       IsNotNullCallback()))
       .WillOnce(MoveArg<1>(&preview_callback));
   controller_->SetStateForTesting(StateId::kAddingUserToGroup);
   base::OnceCallback<void(Outcome)> error_ui_callback;
@@ -556,9 +582,8 @@
                               SharedDataPreviewOrFailureOutcome&)>
       preview_callback;
   EXPECT_CALL(*data_sharing_service_,
-              GetSharedEntitiesPreview(
-                  GroupToken(data_sharing::GroupId(kGroupId), kAccessToken),
-                  IsNotNullCallback()))
+              GetSharedEntitiesPreview(GroupToken(kGroupId, kAccessToken),
+                                       IsNotNullCallback()))
       .WillOnce(MoveArg<1>(&preview_callback));
   EXPECT_CALL(*delegate_, ShowJoinDialog(_, _, IsNotNullCallback()))
       .WillOnce(MoveArg<2>(&join_ui_callback));
@@ -686,17 +711,17 @@
       .WillOnce(Return(tab_group));
   tab_groups::TabGroupSyncService::TabGroupSharingCallback
       tab_group_sharing_callback;
-  EXPECT_CALL(*tab_group_sync_service_,
-              MakeTabGroupShared(local_id, kGroupId, IsNotNullCallback()))
+  EXPECT_CALL(
+      *tab_group_sync_service_,
+      MakeTabGroupShared(local_id, kGroupId.value(), IsNotNullCallback()))
       .WillOnce(MoveArg<2>(&tab_group_sharing_callback));
   base::OnceCallback<void(
       const data_sharing::DataSharingService::GroupDataOrFailureOutcome&)>
       group_data_callback;
-  data_sharing::GroupId group_id(kGroupId);
   EXPECT_CALL(*data_sharing_service_,
-              ReadGroupDeprecated(group_id, IsNotNullCallback()))
+              ReadGroupDeprecated(kGroupId, IsNotNullCallback()))
       .WillOnce(MoveArg<1>(&group_data_callback));
-  data_sharing::GroupToken token(group_id, kAccessToken);
+  data_sharing::GroupToken token(kGroupId, kAccessToken);
   std::move(share_dialog_callback).Run(Outcome::kSuccess, token);
   EXPECT_EQ(controller_->GetStateForTesting(), StateId::kMakingTabGroupShared);
 
@@ -705,9 +730,9 @@
   EXPECT_CALL(*data_sharing_service_, GetDataSharingUrl(_))
       .WillOnce(Return(std::make_unique<GURL>(url)));
   EXPECT_CALL(*delegate_,
-              OnUrlReadyToShare(group_id, GURL(url), IsNotNullCallback()));
+              OnUrlReadyToShare(kGroupId, GURL(url), IsNotNullCallback()));
   GroupData group_data =
-      GroupData(group_id, /*display_name=*/"",
+      GroupData(kGroupId, /*display_name=*/"",
                 /*members=*/{}, /*former_members=*/{}, kAccessToken);
   std::move(group_data_callback).Run(group_data);
   std::move(tab_group_sharing_callback)
@@ -742,8 +767,7 @@
                           tab_groups::TabGroupColorId::kGrey, {});
   tab_group.SetLocalGroupId(local_id);
   // Simulate that the tab group exists locally and is a shared tab group.
-  tab_group.SetCollaborationId(
-      tab_groups::CollaborationId(std::string(kGroupId)));
+  tab_group.SetCollaborationId(tab_groups::CollaborationId(kGroupId.value()));
   EXPECT_CALL(*tab_group_sync_service_, GetGroup(either_id))
       .WillRepeatedly(Return(tab_group));
 
diff --git a/components/collaboration/internal/collaboration_service_impl.cc b/components/collaboration/internal/collaboration_service_impl.cc
index 25b26693..99e2a71 100644
--- a/components/collaboration/internal/collaboration_service_impl.cc
+++ b/components/collaboration/internal/collaboration_service_impl.cc
@@ -9,6 +9,7 @@
 #include "components/collaboration/internal/collaboration_controller.h"
 #include "components/collaboration/internal/metrics.h"
 #include "components/collaboration/public/collaboration_flow_type.h"
+#include "components/collaboration/public/collaboration_utils.h"
 #include "components/collaboration/public/service_status.h"
 #include "components/data_sharing/public/data_sharing_service.h"
 #include "components/data_sharing/public/features.h"
@@ -150,22 +151,8 @@
     return MemberRole::kUnknown;
   }
 
-  CoreAccountInfo account =
-      identity_manager_->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin);
-
-  if (account.IsEmpty()) {
-    // No current logged in user.
-    return MemberRole::kUnknown;
-  }
-
-  for (const GroupMember& member : group_data.value().members) {
-    if (member.gaia_id == account.gaia) {
-      return member.role;
-    }
-  }
-
-  // Current user is not found in group.
-  return MemberRole::kUnknown;
+  return ::collaboration::GetCurrentUserRoleForGroup(identity_manager_.get(),
+                                                     group_data.value());
 }
 
 std::optional<data_sharing::GroupData> CollaborationServiceImpl::GetGroupData(
@@ -376,12 +363,13 @@
     std::unique_ptr<CollaborationControllerDelegate> delegate,
     const GroupToken& token) {
   join_controllers_.insert(
-      {token, std::make_unique<CollaborationController>(
-                  Flow(FlowType::kJoin, token), this,
-                  data_sharing_service_.get(), tab_group_sync_service_.get(),
-                  sync_service_.get(), std::move(delegate),
-                  base::BindOnce(&CollaborationServiceImpl::FinishJoinFlow,
-                                 weak_ptr_factory_.GetWeakPtr(), token))});
+      {token,
+       std::make_unique<CollaborationController>(
+           Flow(FlowType::kJoin, token), this, data_sharing_service_.get(),
+           tab_group_sync_service_.get(), sync_service_.get(),
+           identity_manager_.get(), std::move(delegate),
+           base::BindOnce(&CollaborationServiceImpl::FinishJoinFlow,
+                          weak_ptr_factory_.GetWeakPtr(), token))});
 }
 
 void CollaborationServiceImpl::StartShareOrManageFlowInternal(
@@ -392,7 +380,7 @@
        std::make_unique<CollaborationController>(
            Flow(FlowType::kShareOrManage, group_id), this,
            data_sharing_service_.get(), tab_group_sync_service_.get(),
-           sync_service_.get(), std::move(delegate),
+           sync_service_.get(), identity_manager_.get(), std::move(delegate),
            base::BindOnce(&CollaborationServiceImpl::FinishShareFlow,
                           weak_ptr_factory_.GetWeakPtr(), group_id))});
 }
diff --git a/components/collaboration/internal/metrics.cc b/components/collaboration/internal/metrics.cc
index 47be63d7..13d9ccd 100644
--- a/components/collaboration/internal/metrics.cc
+++ b/components/collaboration/internal/metrics.cc
@@ -83,6 +83,8 @@
       return "ManagedAccountSignin";
     case CollaborationServiceJoinEvent::kAccountInfoNotReadyOnSignin:
       return "AccountInfoNotReadyOnSignin";
+    case CollaborationServiceJoinEvent::kReadNewGroupUserIsAlreadyMember:
+      return "ReadNewGroupUserIsAlreadyMember";
   }
 }
 
diff --git a/components/collaboration/internal/metrics.h b/components/collaboration/internal/metrics.h
index 1c59e248..b8dec75 100644
--- a/components/collaboration/internal/metrics.h
+++ b/components/collaboration/internal/metrics.h
@@ -52,7 +52,8 @@
   kDevicePolicyDisableSignin = 30,
   kManagedAccountSignin = 31,
   kAccountInfoNotReadyOnSignin = 32,
-  kMaxValue = kAccountInfoNotReadyOnSignin,
+  kReadNewGroupUserIsAlreadyMember = 33,
+  kMaxValue = kReadNewGroupUserIsAlreadyMember,
 };
 // LINT.ThenChange(//tools/metrics/histograms/metadata/collaboration_service/enums.xml:CollaborationServiceJoinEvent)
 
diff --git a/components/collaboration/public/BUILD.gn b/components/collaboration/public/BUILD.gn
index 20cd91b..82b4d70 100644
--- a/components/collaboration/public/BUILD.gn
+++ b/components/collaboration/public/BUILD.gn
@@ -22,6 +22,8 @@
     "collaboration_flow_entry_point.h",
     "collaboration_flow_type.h",
     "collaboration_service.h",
+    "collaboration_utils.cc",
+    "collaboration_utils.h",
     "service_status.cc",
     "service_status.h",
   ]
diff --git a/components/collaboration/public/collaboration_utils.cc b/components/collaboration/public/collaboration_utils.cc
new file mode 100644
index 0000000..d7b52b5
--- /dev/null
+++ b/components/collaboration/public/collaboration_utils.cc
@@ -0,0 +1,30 @@
+// 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/collaboration/public/collaboration_utils.h"
+
+namespace collaboration {
+
+data_sharing::MemberRole GetCurrentUserRoleForGroup(
+    signin::IdentityManager* identity_manager,
+    const data_sharing::GroupData& group_data) {
+  CoreAccountInfo account =
+      identity_manager->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin);
+
+  if (account.IsEmpty()) {
+    // No current logged in user.
+    return data_sharing::MemberRole::kUnknown;
+  }
+
+  for (const data_sharing::GroupMember& member : group_data.members) {
+    if (member.gaia_id == account.gaia) {
+      return member.role;
+    }
+  }
+
+  // Current user is not found in group.
+  return data_sharing::MemberRole::kUnknown;
+}
+
+}  // namespace collaboration
diff --git a/components/collaboration/public/collaboration_utils.h b/components/collaboration/public/collaboration_utils.h
new file mode 100644
index 0000000..604cc95a
--- /dev/null
+++ b/components/collaboration/public/collaboration_utils.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 COMPONENTS_COLLABORATION_PUBLIC_COLLABORATION_UTILS_H_
+#define COMPONENTS_COLLABORATION_PUBLIC_COLLABORATION_UTILS_H_
+
+#include "components/data_sharing/public/group_data.h"
+#include "components/signin/public/identity_manager/identity_manager.h"
+
+namespace collaboration {
+
+// Get the group member information of the current user given the GroupData.
+data_sharing::MemberRole GetCurrentUserRoleForGroup(
+    signin::IdentityManager* identity_manager,
+    const data_sharing::GroupData& group_data);
+
+}  // namespace collaboration
+
+#endif  // COMPONENTS_COLLABORATION_PUBLIC_COLLABORATION_UTILS_H_
diff --git a/components/commerce/core/shopping_service.cc b/components/commerce/core/shopping_service.cc
index ef3551782..177bf41 100644
--- a/components/commerce/core/shopping_service.cc
+++ b/components/commerce/core/shopping_service.cc
@@ -1654,7 +1654,8 @@
         continue;
       }
 
-      if (discount.has_type()) {
+      if (discount.has_type() &&
+          discount.type() != commerce::Discount::TYPE_UNSPECIFIED) {
         info.type = DiscountType(discount.type());
       } else {
         continue;
diff --git a/components/commerce/core/shopping_service_unittest.cc b/components/commerce/core/shopping_service_unittest.cc
index fc48e72d..6e0b8ff 100644
--- a/components/commerce/core/shopping_service_unittest.cc
+++ b/components/commerce/core/shopping_service_unittest.cc
@@ -2248,6 +2248,43 @@
   run_loop.Run();
 }
 
+TEST_P(ShoppingServiceTest, TestDiscountInfoResponse_InfoWithUnspecifiedType) {
+  test_features_.InitWithFeatures({kEnableDiscountInfoApi}, {});
+
+  std::vector<DiscountInfo> infos;
+
+  // Invalid info without discount code.
+  DiscountInfo invalid_info;
+  invalid_info.cluster_type = DiscountClusterType::kOfferLevel;
+  invalid_info.type = DiscountType::kUnspecified;
+  invalid_info.language_code = kDiscountLanguageCode;
+  invalid_info.description_detail = kDiscountDetail;
+  invalid_info.terms_and_conditions = kDiscountTerms;
+  invalid_info.value_in_text = kDiscountValueText;
+  invalid_info.discount_code = std::nullopt;
+  invalid_info.id = kDiscountId1;
+  invalid_info.is_merchant_wide = true;
+  invalid_info.expiry_time_sec = kDiscountExpiryTime;
+  invalid_info.offer_id = kDiscountOfferId;
+  infos.push_back(invalid_info);
+
+  opt_guide_->SetResponse(GURL(kDiscountsUrl2),
+                          OptimizationType::SHOPPING_DISCOUNTS,
+                          OptimizationGuideDecision::kTrue,
+                          opt_guide_->BuildDiscountsResponse(infos));
+
+  base::RunLoop run_loop;
+  shopping_service_->GetDiscountInfoForUrl(
+      GURL(kDiscountsUrl1), base::BindOnce(
+                                [](base::RunLoop* run_loop, const GURL& key,
+                                   const std::vector<DiscountInfo> discounts) {
+                                  ASSERT_EQ(0, (int)discounts.size());
+                                  run_loop->Quit();
+                                },
+                                &run_loop));
+  run_loop.Run();
+}
+
 TEST_P(ShoppingServiceTest, TestProductSpecificationsCache) {
   test_features_.InitWithFeatures({kProductSpecifications}, {});
 
diff --git a/components/cronet/gn2bp/copy.bara.sky b/components/cronet/gn2bp/copy.bara.sky
index 8a36dea..9e15b9c 100644
--- a/components/cronet/gn2bp/copy.bara.sky
+++ b/components/cronet/gn2bp/copy.bara.sky
@@ -72,7 +72,6 @@
     "third_party/ashmem/**",
     # Note: Only used for tests.
     "third_party/apache-portable-runtime/**",
-    "third_party/boringssl/**",
     "third_party/brotli/**",
     # Note: Only used for tests.
     "third_party/ced/**",
@@ -105,6 +104,13 @@
     "url/third_party/mozilla/**",
     "third_party/simdutf/**",
   ]) + glob([
+    "third_party/boringssl/**",
+  ]) - glob([
+    # This is not used, and contains a large number of files that slows down
+    # imports and checkouts.
+    "third_party/boringssl/src/fuzz/**",
+    "third_party/boringssl/src/pki/testdata/**",
+  ]) + glob([
     # Note: Only used for tests.
     "third_party/quic_trace/**"
   ]) - glob([
diff --git a/components/lens/lens_features.cc b/components/lens/lens_features.cc
index 7c3335c..9f513f6efdc 100644
--- a/components/lens/lens_features.cc
+++ b/components/lens/lens_features.cc
@@ -386,6 +386,11 @@
 constexpr base::FeatureParam<bool> kUpdateViewportEachQuery{
     &kLensOverlayContextualSearchbox, "update-viewport-each-query", false};
 
+constexpr base::FeatureParam<bool> kUseAltLoadingHintWeb{
+    &kLensOverlayContextualSearchbox, "use-alt-loading-hint-web", false};
+constexpr base::FeatureParam<bool> kUseAltLoadingHintPdf{
+    &kLensOverlayContextualSearchbox, "use-alt-loading-hint-pdf", false};
+
 constexpr base::FeatureParam<std::string> kTranslateEndpointUrl{
     &kLensOverlayTranslateLanguages, "translate-endpoint-url",
     "https://translate-pa.googleapis.com/v1/supportedLanguages"};
@@ -946,4 +951,12 @@
   return base::FeatureList::IsEnabled(kLensOverlayMGTInSidePanel);
 }
 
+bool ShouldUseAltLoadingHintWeb() {
+  return kUseAltLoadingHintWeb.Get();
+}
+
+bool ShouldUseAltLoadingHintPdf() {
+  return kUseAltLoadingHintPdf.Get();
+}
+
 }  // namespace lens::features
diff --git a/components/lens/lens_features.h b/components/lens/lens_features.h
index 7b49a55c..77d8e1f 100644
--- a/components/lens/lens_features.h
+++ b/components/lens/lens_features.h
@@ -745,6 +745,14 @@
 COMPONENT_EXPORT(LENS_FEATURES)
 extern bool ShouldShowMGTInSidePanel();
 
+// Whether to use the alt loading hint when overlay is opened on web pages.
+COMPONENT_EXPORT(LENS_FEATURES)
+extern bool ShouldUseAltLoadingHintWeb();
+
+// Whether to use the alt loading hint when overlay is opened on pdfs.
+COMPONENT_EXPORT(LENS_FEATURES)
+extern bool ShouldUseAltLoadingHintPdf();
+
 }  // namespace lens::features
 
 #endif  // COMPONENTS_LENS_LENS_FEATURES_H_
diff --git a/components/manta/walrus_provider.cc b/components/manta/walrus_provider.cc
index 1e43e3d4..d09ab35 100644
--- a/components/manta/walrus_provider.cc
+++ b/components/manta/walrus_provider.cc
@@ -23,6 +23,43 @@
 constexpr base::TimeDelta kTimeout = base::Seconds(30);
 // The maximum number of pixels after resizing an image.
 constexpr int32_t kMaxPixelsAfterResizing = 512 * 512;
+constexpr auto kTrafficAnnotation =
+    net::DefineNetworkTrafficAnnotation("chromeos_walrus_provider", R"(
+      semantics {
+        sender: "ChromeOS Walrus"
+        description:
+          "Requests the trust and safety verdict of images and text prompt "
+          "from the Mantis service."
+        trigger:
+          "User editing an image in the Gallery app with 'Edit with AI'"
+        internal {
+          contacts {
+            email: "cros-mantis@google.com"
+          }
+        }
+        user_data {
+          type: USER_CONTENT
+        }
+        data:
+          "The image user selected to edit in the gallery and the text prompt "
+          "typed in a editable text field. The generated images from the model "
+          "are also sent for the trust and safety verdict."
+        destination: GOOGLE_OWNED_SERVICE
+        last_reviewed: "2025-03-26"
+      }
+      policy {
+        cookies_allowed: NO
+        setting:
+            "User/Admin can enable or disable this feature via the Google "
+            "Admin Console by updating the GenAI Photo Editing settings. "
+            "The feature is enabled by default."
+        chrome_policy {
+            GenAIPhotoEditingSettings {
+              GenAIPhotoEditingSettings: 0
+            }
+        }
+      }
+    )");
 
 void OnServerResponseOrErrorReceived(
     MantaGenericCallback callback,
@@ -175,12 +212,9 @@
     return;
   }
 
-  // TODO(b:370476808): MISSING_TRAFFIC_ANNOTATION should be resolved before
-  // launch.
   RequestInternal(
       GURL{GetProviderEndpoint(features::IsWalrusUseProdServerEnabled())},
-      kOauthConsumerName, MISSING_TRAFFIC_ANNOTATION, request,
-      MantaMetricType::kWalrus,
+      kOauthConsumerName, kTrafficAnnotation, request, MantaMetricType::kWalrus,
       base::BindOnce(&OnServerResponseOrErrorReceived,
                      std::move(done_callback)),
       kTimeout);
diff --git a/components/optimization_guide/core/feature_registry/feature_registration.cc b/components/optimization_guide/core/feature_registry/feature_registration.cc
index 4c6cc8a..d181207 100644
--- a/components/optimization_guide/core/feature_registry/feature_registration.cc
+++ b/components/optimization_guide/core/feature_registry/feature_registration.cc
@@ -76,6 +76,10 @@
              "FormsAnnotationsMqlsLogging",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
+BASE_FEATURE(kFormsClassificationsMqlsLogging,
+             "FormsClassificationsMqlsLogging",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 BASE_FEATURE(kPasswordChangeSubmissionMqlsLogging,
              "PasswordChangeSubmissionMqlsLogging",
              base::FEATURE_DISABLED_BY_DEFAULT);
@@ -84,6 +88,14 @@
 
 namespace {
 
+// Helper function that creates a `UserFeedbackCallback` for unspecified
+// feedback.
+UserFeedbackCallback FeedbackUnspecified() {
+  return base::BindRepeating([](proto::LogAiDataRequest&) {
+    return proto::UserFeedback::USER_FEEDBACK_UNSPECIFIED;
+  });
+}
+
 void RegisterCompose() {
   const char* kComposeName = "Compose";
   EnterprisePolicyPref enterprise_policy =
@@ -175,16 +187,10 @@
       logging_callback_query);
   MqlsFeatureRegistry::GetInstance().Register(std::move(mqls_metadata_query));
 
-  UserFeedbackCallback logging_callback_answer =
-      base::BindRepeating([](proto::LogAiDataRequest& request_proto) {
-        // There is no user feedback on history answer. It's recorded on history
-        // query.
-        return proto::UserFeedback::USER_FEEDBACK_UNSPECIFIED;
-      });
   auto mqls_metadata_answer = std::make_unique<MqlsFeatureMetadata>(
       "HistoryAnswer", proto::LogAiDataRequest::FeatureCase::kHistoryAnswer,
       enterprise_policy, &features::kHistorySearchMqlsLogging,
-      logging_callback_answer);
+      FeedbackUnspecified());
   MqlsFeatureRegistry::GetInstance().Register(std::move(mqls_metadata_answer));
 
   auto ui_metadata = std::make_unique<SettingsUiMetadata>(
@@ -205,16 +211,11 @@
       std::move(enterprise_policy));
   SettingsUiRegistry::GetInstance().Register(std::move(ui_metadata));
 
-  UserFeedbackCallback logging_callback_answer =
-      base::BindRepeating([](proto::LogAiDataRequest& request_proto) {
-        // There is no user feedback on password change submission yet.
-        return proto::UserFeedback::USER_FEEDBACK_UNSPECIFIED;
-      });
   auto mqls_metadata = std::make_unique<MqlsFeatureMetadata>(
       kPasswordChangeSubmissionName,
       proto::LogAiDataRequest::FeatureCase::kPasswordChangeSubmission,
       enterprise_policy, &features::kPasswordChangeSubmissionMqlsLogging,
-      logging_callback_answer);
+      FeedbackUnspecified());
   MqlsFeatureRegistry::GetInstance().Register(std::move(mqls_metadata));
 }
 
@@ -262,6 +263,13 @@
       enterprise_policy, &features::kFormsAnnotationsMqlsLogging,
       fa_logging_callback);
   MqlsFeatureRegistry::GetInstance().Register(std::move(fa_mqls_metadata));
+
+  MqlsFeatureRegistry::GetInstance().Register(
+      std::make_unique<MqlsFeatureMetadata>(
+          "FormsClassifications",
+          proto::LogAiDataRequest::FeatureCase::kFormsClassifications,
+          enterprise_policy, &features::kFormsClassificationsMqlsLogging,
+          FeedbackUnspecified()));
 }
 
 }  // anonymous namespace
diff --git a/components/optimization_guide/core/feature_registry/feature_registration.h b/components/optimization_guide/core/feature_registry/feature_registration.h
index 670d08b..5f743b0 100644
--- a/components/optimization_guide/core/feature_registry/feature_registration.h
+++ b/components/optimization_guide/core/feature_registry/feature_registration.h
@@ -44,6 +44,8 @@
 COMPONENT_EXPORT(OPTIMIZATION_GUIDE_FEATURES)
 BASE_DECLARE_FEATURE(kFormsAnnotationsMqlsLogging);
 COMPONENT_EXPORT(OPTIMIZATION_GUIDE_FEATURES)
+BASE_DECLARE_FEATURE(kFormsClassificationsMqlsLogging);
+COMPONENT_EXPORT(OPTIMIZATION_GUIDE_FEATURES)
 BASE_DECLARE_FEATURE(kPasswordChangeSubmissionMqlsLogging);
 }  // namespace features
 
diff --git a/components/optimization_guide/core/model_execution/on_device_model_component.cc b/components/optimization_guide/core/model_execution/on_device_model_component.cc
index 845d16498..d3f65f9 100644
--- a/components/optimization_guide/core/model_execution/on_device_model_component.cc
+++ b/components/optimization_guide/core/model_execution/on_device_model_component.cc
@@ -52,8 +52,9 @@
 
 bool IsDeviceCapable(const PrefService& local_state) {
   return IsPerformanceClassCompatible(
-      features::kPerformanceClassListForOnDeviceModel.Get(),
-      PerformanceClassFromPref(local_state));
+             features::kPerformanceClassListForOnDeviceModel.Get(),
+             PerformanceClassFromPref(local_state)) ||
+         features::ForceCpuBackendForOnDeviceModel();
 }
 
 void LogInstallCriteria(std::string_view event_name,
diff --git a/components/optimization_guide/core/model_execution/on_device_model_feature_adapter.cc b/components/optimization_guide/core/model_execution/on_device_model_feature_adapter.cc
index 338b317..0142d07 100644
--- a/components/optimization_guide/core/model_execution/on_device_model_feature_adapter.cc
+++ b/components/optimization_guide/core/model_execution/on_device_model_feature_adapter.cc
@@ -26,11 +26,11 @@
 namespace optimization_guide {
 
 OnDeviceModelFeatureAdapter::OnDeviceModelFeatureAdapter(
-    proto::OnDeviceModelExecutionFeatureConfig&& config)
-    : config_(config),
-      redactor_(Redactor::FromProto(config.output_config().redact_rules())),
+    proto::OnDeviceModelExecutionFeatureConfig config)
+    : config_(std::move(config)),
+      redactor_(Redactor::FromProto(config_.output_config().redact_rules())),
       response_streaming_mode_(
-          config.output_config().response_streaming_mode()),
+          config_.output_config().response_streaming_mode()),
       parser_(
           ResponseParserRegistry::Get().CreateParser(config_.output_config())) {
   // Set limits values in `token_limits_`.
diff --git a/components/optimization_guide/core/model_execution/on_device_model_feature_adapter.h b/components/optimization_guide/core/model_execution/on_device_model_feature_adapter.h
index 54fd337..c1f2fef 100644
--- a/components/optimization_guide/core/model_execution/on_device_model_feature_adapter.h
+++ b/components/optimization_guide/core/model_execution/on_device_model_feature_adapter.h
@@ -36,7 +36,7 @@
  public:
   // Constructs an adapter from a configuration proto.
   explicit OnDeviceModelFeatureAdapter(
-      proto::OnDeviceModelExecutionFeatureConfig&& config);
+      proto::OnDeviceModelExecutionFeatureConfig config);
 
   // Constructs the model input from `request`.
   std::optional<SubstitutionResult> ConstructInputString(
diff --git a/components/optimization_guide/core/model_execution/on_device_model_service_controller.cc b/components/optimization_guide/core/model_execution/on_device_model_service_controller.cc
index b3a06a2..f616749 100644
--- a/components/optimization_guide/core/model_execution/on_device_model_service_controller.cc
+++ b/components/optimization_guide/core/model_execution/on_device_model_service_controller.cc
@@ -280,6 +280,9 @@
     return;
   }
   auto params = on_device_model::mojom::LoadModelParams::New();
+  params->backend_type = features::ForceCpuBackendForOnDeviceModel()
+                             ? ml::ModelBackendType::kCpuBackend
+                             : ml::ModelBackendType::kGpuBackend;
   params->assets = std::move(assets);
   // TODO(crbug.com/302402959): Choose max_tokens based on device.
   params->max_tokens = features::GetOnDeviceModelMaxTokens();
diff --git a/components/optimization_guide/core/model_execution/on_device_model_service_controller.h b/components/optimization_guide/core/model_execution/on_device_model_service_controller.h
index 2765334..c162e3ab 100644
--- a/components/optimization_guide/core/model_execution/on_device_model_service_controller.h
+++ b/components/optimization_guide/core/model_execution/on_device_model_service_controller.h
@@ -169,7 +169,6 @@
   };
   friend class OnDeviceModelAdaptationController;
   friend class OnDeviceModelClient;
-  friend class OnDeviceModelExecutionEnabledBrowserTest;
   friend class base::RefCounted<OnDeviceModelServiceController>;
 
   // Ensures the service is running and provides a remote for the model.
diff --git a/components/optimization_guide/core/model_execution/test/fake_model_assets.cc b/components/optimization_guide/core/model_execution/test/fake_model_assets.cc
index 58bf16c..b3bcc90 100644
--- a/components/optimization_guide/core/model_execution/test/fake_model_assets.cc
+++ b/components/optimization_guide/core/model_execution/test/fake_model_assets.cc
@@ -72,6 +72,11 @@
 }
 FakeAdaptationAsset::~FakeAdaptationAsset() = default;
 
+void FakeAdaptationAsset::SendTo(
+    OnDeviceModelServiceController& controller) const {
+  controller.MaybeUpdateModelAdaptation(feature(), metadata());
+}
+
 FakeLanguageModelAsset::FakeLanguageModelAsset() {
   CHECK(temp_dir_.CreateUniqueTempDir());
   auto model_path = temp_dir_.GetPath().Append(kWeightsFile);
diff --git a/components/optimization_guide/core/model_execution/test/fake_model_assets.h b/components/optimization_guide/core/model_execution/test/fake_model_assets.h
index ccf9adfd..42d1a02 100644
--- a/components/optimization_guide/core/model_execution/test/fake_model_assets.h
+++ b/components/optimization_guide/core/model_execution/test/fake_model_assets.h
@@ -13,6 +13,7 @@
 #include "base/files/scoped_temp_dir.h"
 #include "components/optimization_guide/core/model_execution/feature_keys.h"
 #include "components/optimization_guide/core/model_execution/on_device_model_adaptation_loader.h"
+#include "components/optimization_guide/core/model_execution/on_device_model_service_controller.h"
 #include "components/optimization_guide/core/model_info.h"
 #include "components/optimization_guide/proto/on_device_model_execution_config.pb.h"
 #include "components/optimization_guide/proto/text_safety_model_metadata.pb.h"
@@ -64,12 +65,14 @@
   explicit FakeAdaptationAsset(Content&& content);
   ~FakeAdaptationAsset();
 
-  int64_t version() { return 12345; }
-  ModelBasedCapabilityKey feature() { return feature_; }
-  std::unique_ptr<OnDeviceModelAdaptationMetadata> metadata() {
+  int64_t version() const { return 12345; }
+  ModelBasedCapabilityKey feature() const { return feature_; }
+  std::unique_ptr<OnDeviceModelAdaptationMetadata> metadata() const {
     return std::make_unique<OnDeviceModelAdaptationMetadata>(*metadata_);
   }
 
+  void SendTo(OnDeviceModelServiceController& controller) const;
+
  private:
   base::ScopedTempDir temp_dir_;
   ModelBasedCapabilityKey feature_;
diff --git a/components/optimization_guide/core/optimization_guide_features.cc b/components/optimization_guide/core/optimization_guide_features.cc
index aa3dff6..ab4dc76f 100644
--- a/components/optimization_guide/core/optimization_guide_features.cc
+++ b/components/optimization_guide/core/optimization_guide_features.cc
@@ -847,6 +847,13 @@
   return ranks;
 }
 
+bool ForceCpuBackendForOnDeviceModel() {
+  static const base::FeatureParam<bool> kForceCpuBackend{
+      &kOptimizationGuideOnDeviceModel, "on_device_model_force_cpu_backend",
+      false};
+  return kForceCpuBackend.Get();
+}
+
 bool IsOnDeviceModelValidationEnabled() {
   return base::FeatureList::IsEnabled(kOnDeviceModelValidation);
 }
diff --git a/components/optimization_guide/core/optimization_guide_features.h b/components/optimization_guide/core/optimization_guide_features.h
index 9d5cc3d..875b3fa 100644
--- a/components/optimization_guide/core/optimization_guide_features.h
+++ b/components/optimization_guide/core/optimization_guide_features.h
@@ -510,6 +510,10 @@
 COMPONENT_EXPORT(OPTIMIZATION_GUIDE_FEATURES)
 std::vector<uint32_t> GetOnDeviceModelAllowedAdaptationRanks();
 
+// Whether the on-device model should be limited to running only on the CPU.
+COMPONENT_EXPORT(OPTIMIZATION_GUIDE_FEATURES)
+bool ForceCpuBackendForOnDeviceModel();
+
 // Whether the on-device model will be validated when updated using a set of
 // prompts with expected output.
 COMPONENT_EXPORT(OPTIMIZATION_GUIDE_FEATURES)
diff --git a/components/optimization_guide/proto/features/common_quality_data.proto b/components/optimization_guide/proto/features/common_quality_data.proto
index cbb87bfe..43ae20e9 100644
--- a/components/optimization_guide/proto/features/common_quality_data.proto
+++ b/components/optimization_guide/proto/features/common_quality_data.proto
@@ -196,7 +196,7 @@
 
 // The page content represented as a tree of semantic chunks and (in the
 // future) annotated with additional information about the page.
-// Next ID: 5
+// Next ID: 6
 message AnnotatedPageContent {
   AnnotatedPageContentVersion version = 1;
 
@@ -211,6 +211,9 @@
   // for the entire page. Includes interaction information for the main frame
   // and embedded iframes.
   PageInteractionInfo page_interaction_info = 4;
+
+  // The rect of the viewport for the tab. i.e., screenshot dimensions.
+  BoundingRect viewport_geometry = 5;
 }
 
 // The ContentNode tree is a sparse representation of the DOM tree for a web
diff --git a/components/optimization_guide/proto/features/forms_classifications.proto b/components/optimization_guide/proto/features/forms_classifications.proto
index 4f9f80e..195d5c8 100644
--- a/components/optimization_guide/proto/features/forms_classifications.proto
+++ b/components/optimization_guide/proto/features/forms_classifications.proto
@@ -32,17 +32,63 @@
   ModelExecutionInfo model_execution_info = 4;
 }
 
-enum AutofillAiFieldEventType {
-  AUTOFILL_AI_FIELD_EVENT_TYPE_SUGGESTION_SHOWN = 0;
-  AUTOFILL_AI_FIELD_EVENT_TYPE_SUGGESTION_FILLED = 1;
-  AUTOFILL_AI_FIELD_EVENT_TYPE_EDITED_AUTOFILLED_FIELD = 2;
+// Next ID: 6
+message AutofillAiTypeRequest {
+  // The context of the page that the form is on.
+  PageContext page_context = 1;
+
+  // The form to predict values for. It is expected that the values for each
+  // field are empty.
+  FormData form_data = 2;
+
+  // A screenshot of the form.
+  // This is optional and may be used by the model to improve predictions.
+  FormMedia media = 3;
+
+  // The annotated page content of the page that the form is on.
+  AnnotatedPageContent annotated_page_content = 5;
+
+  reserved 4;
 }
 
-enum FormatStringSource {
-  FORMAT_STRING_SOURCE_UNSET = 0;
-  FORMAT_STRING_SOURCE_HEURISTICS = 1;
-  FORMAT_STRING_SOURCE_ML_MODEL = 2;
-  FORMAT_STRING_SOURCE_SERVER = 3;
+message FormMedia {
+  // Screenshots in base64-encoded PNG format
+  repeated bytes screenshots = 1;
+}
+
+message AutofillAiTypeResponse {
+  // A list of field-level type predictions for the form.
+  repeated FieldTypeResponse field_responses = 1;
+}
+
+// Next ID: 6
+message FieldTypeResponse {
+  // Do not use - use field_index instead.
+  string field_global_id = 1 [deprecated = true];
+
+  // The (zero-based) index of the field among those passed in the request that
+  // this prediction refers to.
+  int32 field_index = 5;
+
+  // The predicted type for the field.
+  int32 field_type = 2;
+
+  // Optional formatting metadata for downstream use.
+  // Example formats: "dd/mm/yyyy" for dates, "NAME_FIRST_LAST" for names.
+  string formatting_meta = 3;
+
+  // The confidence score for the prediction, ranging from 0.0 to 1.0.
+  // Higher values indicate greater confidence in the predicted type.
+  float confidence = 4;
+}
+
+message FormTypeQuality {
+  // User-provided feedback on the predictions.
+  UserFeedback user_feedback = 1;
+
+  AutofillAiFieldEvent field_event = 2;
+
+  AutofillAiKeyMetrics key_metrics = 3;
 }
 
 // See `AutofillAiUkmLogger::FieldEvent`.
@@ -151,61 +197,20 @@
   string domain = 7;
 }
 
-// Next ID: 6
-message AutofillAiTypeRequest {
-  // The context of the page that the form is on.
-  PageContext page_context = 1;
+enum AutofillAiFieldEventType {
+  AUTOFILL_AI_FIELD_EVENT_TYPE_SUGGESTION_SHOWN = 0;
 
-  // The form to predict values for. It is expected that the values for each
-  // field are empty.
-  FormData form_data = 2;
+  AUTOFILL_AI_FIELD_EVENT_TYPE_SUGGESTION_FILLED = 1;
 
-  // A screenshot of the form.
-  // This is optional and may be used by the model to improve predictions.
-  FormMedia media = 3;
-
-  // The annotated page content of the page that the form is on.
-  AnnotatedPageContent annotated_page_content = 5;
-
-  reserved 4;
+  AUTOFILL_AI_FIELD_EVENT_TYPE_EDITED_AUTOFILLED_FIELD = 2;
 }
 
-message FormMedia {
-  // Screenshots in base64-encoded PNG format
-  repeated bytes screenshots = 1;
-}
+enum FormatStringSource {
+  FORMAT_STRING_SOURCE_UNSET = 0;
 
-message AutofillAiTypeResponse {
-  // A list of field-level type predictions for the form.
-  repeated FieldTypeResponse field_responses = 1;
-}
+  FORMAT_STRING_SOURCE_HEURISTICS = 1;
 
-// Next ID: 6
-message FieldTypeResponse {
-  // Do not use - use field_index instead.
-  string field_global_id = 1 [deprecated = true];
+  FORMAT_STRING_SOURCE_ML_MODEL = 2;
 
-  // The (zero-based) index of the field among those passed in the request that
-  // this prediction refers to.
-  int32 field_index = 5;
-
-  // The predicted type for the field.
-  int32 field_type = 2;
-
-  // Optional formatting metadata for downstream use.
-  // Example formats: "dd/mm/yyyy" for dates, "NAME_FIRST_LAST" for names.
-  string formatting_meta = 3;
-
-  // The confidence score for the prediction, ranging from 0.0 to 1.0.
-  // Higher values indicate greater confidence in the predicted type.
-  float confidence = 4;
-}
-
-message FormTypeQuality {
-  // User-provided feedback on the predictions.
-  UserFeedback user_feedback = 1;
-
-  AutofillAiFieldEvent field_event = 2;
-
-  AutofillAiKeyMetrics key_metrics = 3;
+  FORMAT_STRING_SOURCE_SERVER = 3;
 }
diff --git a/components/page_load_metrics/browser/observers/use_counter/ukm_features.cc b/components/page_load_metrics/browser/observers/use_counter/ukm_features.cc
index b0ca0f3..46c62b8 100644
--- a/components/page_load_metrics/browser/observers/use_counter/ukm_features.cc
+++ b/components/page_load_metrics/browser/observers/use_counter/ukm_features.cc
@@ -467,6 +467,7 @@
           WebFeature::kLanguageModel_Create,
           WebFeature::kLanguageModel_Prompt,
           WebFeature::kLanguageModel_PromptStreaming,
+          WebFeature::kCrossOriginSameSiteCookieAccessViaStorageAccessAPI,
           // NOTE: before adding new use counters here, verify in UMA that their
           // emissions are very rare, e.g. <1% of page loads.
       }));
diff --git a/components/performance_manager/features.cc b/components/performance_manager/features.cc
index 1400597..f593286 100644
--- a/components/performance_manager/features.cc
+++ b/components/performance_manager/features.cc
@@ -70,6 +70,69 @@
                    "ppm_survey_max_delay",
                    base::Minutes(60));
 
+BASE_FEATURE_PARAM(bool,
+                   kPerformanceControlsPPMSurveyUniformSampleValue,
+                   &kPerformanceControlsPPMSurvey,
+                   "ppm_survey_uniform_sample",
+                   true);
+
+// Depending on platform, clients will be split into 1-3 segments based on the
+// amount of physical RAM they have. "ppm_survey_segment_name1" through
+// "ppm_survey_segment_name3" give the names of the segments, which will be
+// included in the PPM survey string data.
+//
+// "ppm_survey_segment_max_memory_gb1" and "ppm_survey_segment_max_memory_gb2"
+// define the upper bounds of segments 1 and 2. The lower bound of segment 1 is
+// always 0 GB; if "ppm_survey_segment_max_memory_gb1" is 0, it has no upper
+// bound so it's the only defined segment ("ppm_survey_segment_name2", etc, are
+// ignored). Otherwise "ppm_survey_segment_max_memory_gb1" is the upper bound
+// (inclusive) of segment 1 and the lower bound (exclusive) of segment 2.
+//
+// Likewise, if "ppm_survey_segment_max_memory_gb2" is 0, segment 2 has no upper
+// bound so this platform has only 2 defined segments. Otherwise
+// "ppm_survey_segment_max_memory_gb2" is the upper bound (inclusive) of segment
+// 2 and the lower bound (exclusive) of segment 3. Segment 3 is the last segment
+// that can be defined so it never has an upper bound.
+//
+// Comparing the client's physical RAM to the boundaries of each defined segment
+// determines which one the client falls into. The default parameters give the
+// trivial case with only 1 segment containing all users.
+//
+// If the name parameter of the client's segment is an empty string, that
+// segment has already received enough survey responses so clients in that
+// segment should not see the survey.
+BASE_FEATURE_PARAM(std::string,
+                   kPerformanceControlsPPMSurveySegmentName1,
+                   &kPerformanceControlsPPMSurvey,
+                   "ppm_survey_segment_name1",
+                   // All clients fall into this segment when
+                   // "ppm_survey_segment_max_memory_gb1" is 0.
+                   "Default");
+BASE_FEATURE_PARAM(std::string,
+                   kPerformanceControlsPPMSurveySegmentName2,
+                   &kPerformanceControlsPPMSurvey,
+                   "ppm_survey_segment_name2",
+                   // Default is "Invalid" since this should never be used when
+                   // "ppm_survey_segment_max_memory_gb1" is 0.
+                   "Invalid1");
+BASE_FEATURE_PARAM(std::string,
+                   kPerformanceControlsPPMSurveySegmentName3,
+                   &kPerformanceControlsPPMSurvey,
+                   "ppm_survey_segment_name3",
+                   // Default is "Invalid" since this should never be used when
+                   // "ppm_survey_segment_max_memory_gb1" is 0.
+                   "Invalid2");
+BASE_FEATURE_PARAM(size_t,
+                   kPerformanceControlsPPMSurveySegmentMaxMemoryGB1,
+                   &kPerformanceControlsPPMSurvey,
+                   "ppm_survey_segment_max_memory_gb1",
+                   0);
+BASE_FEATURE_PARAM(size_t,
+                   kPerformanceControlsPPMSurveySegmentMaxMemoryGB2,
+                   &kPerformanceControlsPPMSurvey,
+                   "ppm_survey_segment_max_memory_gb2",
+                   0);
+
 BASE_FEATURE(kPerformanceInterventionDemoMode,
              "PerformanceInterventionDemoMode",
              base::FEATURE_DISABLED_BY_DEFAULT);
@@ -108,6 +171,16 @@
                    "no_acceptance_back_off",
                    base::Days(30));
 
+BASE_FEATURE(kPerformanceInterventionNotificationStringImprovements,
+             "PerformanceInterventionNotificationStringImprovements",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
+BASE_FEATURE_PARAM(int,
+                   kNotificationStringVersion,
+                   &kPerformanceInterventionNotificationStringImprovements,
+                   "string_version",
+                   1);
+
 #if BUILDFLAG(IS_CHROMEOS)
 BASE_FEATURE(kUnthrottledTabProcessReporting,
              "UnthrottledTabProcessReporting",
diff --git a/components/performance_manager/graph/frame_node_impl.cc b/components/performance_manager/graph/frame_node_impl.cc
index 226ac11c..196707a 100644
--- a/components/performance_manager/graph/frame_node_impl.cc
+++ b/components/performance_manager/graph/frame_node_impl.cc
@@ -492,7 +492,12 @@
       case blink::mojom::FrameVisibility::kNotRendered:
         return false;
       case blink::mojom::FrameVisibility::kRenderedOutOfViewport:
-        return features::kRenderedOutOfViewIsNotVisible.Get();
+        if (!features::kRenderedOutOfViewIsNotVisible.Get()) {
+          // Old, seemingly incorrect behavior. Treat an out of view frame as
+          // intersecting with the viewport.
+          return true;
+        }
+        return false;
       case blink::mojom::FrameVisibility::kRenderedInViewport:
         // Since we don't know if this frame is intersecting with a large area
         // of the viewport, it'll be inherited from the parent.
diff --git a/components/performance_manager/public/features.h b/components/performance_manager/public/features.h
index 049e7dde..25e63b1 100644
--- a/components/performance_manager/public/features.h
+++ b/components/performance_manager/public/features.h
@@ -8,6 +8,8 @@
 #ifndef COMPONENTS_PERFORMANCE_MANAGER_PUBLIC_FEATURES_H_
 #define COMPONENTS_PERFORMANCE_MANAGER_PUBLIC_FEATURES_H_
 
+#include <string>
+
 #include "base/feature_list.h"
 #include "base/metrics/field_trial_params.h"
 #include "base/time/time.h"
@@ -70,6 +72,26 @@
 BASE_DECLARE_FEATURE_PARAM(base::TimeDelta,
                            kPerformanceControlsPPMSurveyMaxDelay);
 
+// Controls whether survey responses will be tagged as "Selected For Uniform
+// Sample". The subset of responses with this tag approximate the general
+// population, no matter how many responses are received in individual segments.
+BASE_DECLARE_FEATURE_PARAM(bool,
+                           kPerformanceControlsPPMSurveyUniformSampleValue);
+
+// Defines the names and boundaries of up to 3 segments for the PPM survey.
+// There's no kPerformanceControlsPPMSurveySegmentMaxMemoryGB3 because there's
+// never a 4th segment, so segment 3 has no maximum.
+BASE_DECLARE_FEATURE_PARAM(std::string,
+                           kPerformanceControlsPPMSurveySegmentName1);
+BASE_DECLARE_FEATURE_PARAM(std::string,
+                           kPerformanceControlsPPMSurveySegmentName2);
+BASE_DECLARE_FEATURE_PARAM(std::string,
+                           kPerformanceControlsPPMSurveySegmentName3);
+BASE_DECLARE_FEATURE_PARAM(size_t,
+                           kPerformanceControlsPPMSurveySegmentMaxMemoryGB1);
+BASE_DECLARE_FEATURE_PARAM(size_t,
+                           kPerformanceControlsPPMSurveySegmentMaxMemoryGB2);
+
 // This enables performance intervention to run in demo mode. While in demo
 // mode, performance intervention will ignore rate throttling and CPU thresholds
 // to make it easier to trigger performance intervention for testing purposes.
@@ -94,6 +116,14 @@
 // The amount of time a user needs to wait before being shown performance
 // intervention with a 0% acceptance rate
 BASE_DECLARE_FEATURE_PARAM(base::TimeDelta, kNoAcceptanceBackOff);
+
+// This enables performance intervention to use the updated notification
+// strings.
+BASE_DECLARE_FEATURE(kPerformanceInterventionNotificationStringImprovements);
+
+// The version string that is used on the performance detection dialog.
+BASE_DECLARE_FEATURE_PARAM(int, kNotificationStringVersion);
+
 #endif
 
 BASE_DECLARE_FEATURE(kPMProcessPriorityPolicy);
diff --git a/components/permissions/android/BUILD.gn b/components/permissions/android/BUILD.gn
index 45faf4c..d8c6ab20 100644
--- a/components/permissions/android/BUILD.gn
+++ b/components/permissions/android/BUILD.gn
@@ -66,6 +66,8 @@
     "res/drawable/gm_filled_bluetooth_searching_24.xml",
     "res/drawable/gm_filled_cardboard_24.xml",
     "res/drawable/gm_filled_content_paste_24.xml",
+    "res/drawable/gm_filled_developer_board_24.xml",
+    "res/drawable/gm_filled_developer_board_off_24.xml",
     "res/drawable/gm_filled_devices_24.xml",
     "res/drawable/gm_filled_hand_gesture_24.xml",
     "res/drawable/gm_filled_location_on_24.xml",
diff --git a/components/permissions/android/res/drawable/gm_filled_developer_board_24.xml b/components/permissions/android/res/drawable/gm_filled_developer_board_24.xml
new file mode 100644
index 0000000..6c55e11
--- /dev/null
+++ b/components/permissions/android/res/drawable/gm_filled_developer_board_24.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright 2025 The Chromium Authors
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="960"
+    android:viewportHeight="960"
+    android:tint="?attr/colorControlNormal">
+  <path
+    android:fillColor="@android:color/white"
+    android:pathData="M160,840Q127,840 103.5,816.5Q80,793 80,760L80,200Q80,167 103.5,143.5Q127,120 160,120L720,120Q753,120 776.5,143.5Q800,167 800,200L800,280L880,280L880,360L800,360L800,440L880,440L880,520L800,520L800,600L880,600L880,680L800,680L800,760Q800,793 776.5,816.5Q753,840 720,840L160,840ZM160,760L720,760Q720,760 720,760Q720,760 720,760L720,200Q720,200 720,200Q720,200 720,200L160,200Q160,200 160,200Q160,200 160,200L160,760Q160,760 160,760Q160,760 160,760ZM240,680L440,680L440,520L240,520L240,680ZM480,400L640,400L640,280L480,280L480,400ZM240,480L440,480L440,280L240,280L240,480ZM480,680L640,680L640,440L480,440L480,680ZM160,200L160,200Q160,200 160,200Q160,200 160,200L160,760Q160,760 160,760Q160,760 160,760L160,760Q160,760 160,760Q160,760 160,760L160,200Q160,200 160,200Q160,200 160,200Z"/>
+</vector>
\ No newline at end of file
diff --git a/components/permissions/android/res/drawable/gm_filled_developer_board_off_24.xml b/components/permissions/android/res/drawable/gm_filled_developer_board_off_24.xml
new file mode 100644
index 0000000..3c3748a95
--- /dev/null
+++ b/components/permissions/android/res/drawable/gm_filled_developer_board_off_24.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright 2025 The Chromium Authors
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="960"
+    android:viewportHeight="960"
+    android:tint="?attr/colorControlNormal">
+  <path
+    android:fillColor="@android:color/white"
+    android:pathData="M819,932L27,140L84,83L876,875L819,932ZM233,120L720,120Q753,120 776.5,143.5Q800,167 800,200L800,280L880,280L880,360L800,360L800,440L880,440L880,520L800,520L800,600L880,600L880,680L793,680L720,607L720,200Q720,200 720,200Q720,200 720,200L313,200L233,120ZM440,327L393,280L440,280L440,327ZM513,400L480,367L480,280L640,280L640,400L513,400ZM640,526L554,440L640,440L640,526ZM407,520L407,520Q407,520 407,520Q407,520 407,520L407,520Q407,520 407,520Q407,520 407,520L407,520Q407,520 407,520Q407,520 407,520ZM517,402L517,402L517,402Q517,402 517,402Q517,402 517,402ZM240,680L240,520L440,520L440,680L240,680ZM128,127L201,200L160,200Q160,200 160,200Q160,200 160,200L160,760Q160,760 160,760Q160,760 160,760L720,760Q720,760 720,760Q720,760 720,760L720,719L800,799L800,799Q786,819 765,829.5Q744,840 720,840L160,840Q127,840 103.5,816.5Q80,793 80,760L80,200Q80,175 93.5,155.5Q107,136 128,127ZM480,479L640,639L640,680L480,680L480,479ZM281,280L440,439L440,480L240,480L240,280L281,280Z"/>
+</vector>
\ No newline at end of file
diff --git a/components/policy/proto/device_management_backend.proto b/components/policy/proto/device_management_backend.proto
index 1b422ff..9fd5741 100644
--- a/components/policy/proto/device_management_backend.proto
+++ b/components/policy/proto/device_management_backend.proto
@@ -916,8 +916,8 @@
   // (http://shortn/_POBIUl3EIK) should be updated.
   enum K12AgeClassificationMetricsLogSegment {
     AGE_UNSPECIFIED = 0;
-    AGE_UNDER_18 = 1;
-    AGE_EQUAL_OR_OVER_18 = 2;
+    AGE_UNDER18 = 1;
+    AGE_EQUAL_OR_OVER18 = 2;
   }
 
   // Indicates the segment the user's age classification metrics should be
diff --git a/components/policy/resources/templates/policy_definitions/Miscellaneous/ParcelTrackingEnabled.yaml b/components/policy/resources/templates/policy_definitions/Miscellaneous/ParcelTrackingEnabled.yaml
index 015c6f4d..9f63d55 100644
--- a/components/policy/resources/templates/policy_definitions/Miscellaneous/ParcelTrackingEnabled.yaml
+++ b/components/policy/resources/templates/policy_definitions/Miscellaneous/ParcelTrackingEnabled.yaml
@@ -2,11 +2,13 @@
 - hiramahmood@google.com
 - file://ios/chrome/browser/parcel_tracking/OWNERS
 caption: Allows users to track their packages on Chrome.
+deprecated: true
 desc: |-
   When the policy is not set or set to Enabled, users will be able to track their packages on <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph> through the New Tab Page.
   When the policy is set to Disabled, users will not be able to track their packages on <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph> through the New Tab Page.
+  Deprecated: The Parcel Tracking feature is disabled since M132, and this policy has no effect since then.
 supported_on:
-- ios:120-
+- ios:120-131
 features:
   dynamic_refresh: false
   per_profile: false
diff --git a/components/policy/test/data/pref_mapping/ParcelTrackingEnabled.json b/components/policy/test/data/pref_mapping/ParcelTrackingEnabled.json
deleted file mode 100644
index 5dca3e65..0000000
--- a/components/policy/test/data/pref_mapping/ParcelTrackingEnabled.json
+++ /dev/null
@@ -1,5 +0,0 @@
-[
-  {
-    "reason_for_missing_test": "Maps into an iOS-specific pref"
-  }
-]
diff --git a/components/policy/test_support/policy_storage.h b/components/policy/test_support/policy_storage.h
index a54725b..643df6e 100644
--- a/components/policy/test_support/policy_storage.h
+++ b/components/policy/test_support/policy_storage.h
@@ -147,6 +147,17 @@
     metrics_log_segment_ = segment;
   }
 
+  const std::optional<
+      enterprise_management::PolicyData::K12AgeClassificationMetricsLogSegment>
+  k12_age_classification_metrics_log_segment() const {
+    return k12_age_classification_metrics_log_segment_;
+  }
+  void set_k12_age_classification_metrics_log_segment(
+      enterprise_management::PolicyData::K12AgeClassificationMetricsLogSegment
+          segment) {
+    k12_age_classification_metrics_log_segment_ = segment;
+  }
+
   base::Time timestamp() const { return timestamp_; }
   void set_timestamp(const base::Time& timestamp) { timestamp_ = timestamp; }
 
@@ -239,6 +250,9 @@
       market_segment_;
   std::optional<enterprise_management::PolicyData::MetricsLogSegment>
       metrics_log_segment_;
+  std::optional<
+      enterprise_management::PolicyData::K12AgeClassificationMetricsLogSegment>
+      k12_age_classification_metrics_log_segment_;
 
   base::Time timestamp_;
 
diff --git a/components/policy/test_support/request_handler_for_policy.cc b/components/policy/test_support/request_handler_for_policy.cc
index 0533757..33dad72 100644
--- a/components/policy/test_support/request_handler_for_policy.cc
+++ b/components/policy/test_support/request_handler_for_policy.cc
@@ -229,6 +229,12 @@
       policy_data.set_metrics_log_segment(
           policy_storage()->metrics_log_segment().value());
     }
+    if (policy_storage()->k12_age_classification_metrics_log_segment()) {
+      policy_data.set_k12_age_classification_metrics_log_segment(
+          policy_storage()
+              ->k12_age_classification_metrics_log_segment()
+              .value());
+    }
   } else if (policy_type == dm_protocol::kChromeDevicePolicyType) {
     std::vector<std::string> device_affiliation_ids =
         policy_storage()->device_affiliation_ids();
diff --git a/components/privacy_sandbox/privacy_sandbox_attestations/BUILD.gn b/components/privacy_sandbox/privacy_sandbox_attestations/BUILD.gn
index af2f6b97..789fba31 100644
--- a/components/privacy_sandbox/privacy_sandbox_attestations/BUILD.gn
+++ b/components/privacy_sandbox/privacy_sandbox_attestations/BUILD.gn
@@ -45,6 +45,7 @@
       ":test_support",
       "//base/test:test_support",
       "//components/privacy_sandbox:features",
+      "//components/privacy_sandbox:test_support",
       "//components/privacy_sandbox/privacy_sandbox_attestations/proto:proto",
       "//content/test:test_support",
       "//net",
diff --git a/components/privacy_sandbox/privacy_sandbox_attestations/privacy_sandbox_attestations_unittest.cc b/components/privacy_sandbox/privacy_sandbox_attestations/privacy_sandbox_attestations_unittest.cc
index 0c83280b..a358de29 100644
--- a/components/privacy_sandbox/privacy_sandbox_attestations/privacy_sandbox_attestations_unittest.cc
+++ b/components/privacy_sandbox/privacy_sandbox_attestations/privacy_sandbox_attestations_unittest.cc
@@ -19,6 +19,7 @@
 #include "components/privacy_sandbox/privacy_sandbox_attestations/proto/privacy_sandbox_attestations.pb.h"
 #include "components/privacy_sandbox/privacy_sandbox_attestations/scoped_privacy_sandbox_attestations.h"
 #include "components/privacy_sandbox/privacy_sandbox_features.h"
+#include "components/privacy_sandbox/privacy_sandbox_test_util.h"
 #include "components/startup_metric_utils/browser/startup_metric_utils.h"
 #include "content/public/test/browser_task_environment.h"
 #include "net/base/schemeful_site.h"
@@ -27,14 +28,15 @@
 
 namespace privacy_sandbox {
 
+using ::privacy_sandbox_test_util::PrivacySandboxSettingsTestPeer;
+using Status = PrivacySandboxSettingsTestPeer::Status;
+
 class PrivacySandboxAttestationsTestBase : public testing::Test {
  public:
   PrivacySandboxAttestationsTestBase()
       : scoped_attestations_(PrivacySandboxAttestations::CreateForTesting()) {}
 
  protected:
-  using Status = PrivacySandboxSettingsImpl::Status;
-
   const base::HistogramTester& histogram_tester() const {
     return histogram_tester_;
   }
diff --git a/components/privacy_sandbox/privacy_sandbox_settings_impl.h b/components/privacy_sandbox/privacy_sandbox_settings_impl.h
index fa49e95..ff2c081 100644
--- a/components/privacy_sandbox/privacy_sandbox_settings_impl.h
+++ b/components/privacy_sandbox/privacy_sandbox_settings_impl.h
@@ -131,21 +131,7 @@
   bool AreRelatedWebsiteSetsEnabled() const override;
 
  private:
-  // TODO(crbug.com/366168654): Browser tests should not reach into the private
-  // method or states of this class. Consider exposing the required functions
-  // via a test helper class or test only functions.
   friend class PrivacySandboxAttestations;
-  friend class PrivacySandboxAttestationsTestBase;
-  FRIEND_TEST_ALL_PREFIXES(
-      PrivacySandboxAttestationsBrowserTest,
-      CallComponentReadyWhenRegistrationFindsExistingComponent);
-  FRIEND_TEST_ALL_PREFIXES(PrivacySandboxAttestationsBrowserTest,
-                           SentinelFilePreventsSubsequentParsings);
-  FRIEND_TEST_ALL_PREFIXES(PrivacySandboxAttestationsBrowserTest,
-                           DifferentHistogramAfterAttestationsFileCheck);
-  FRIEND_TEST_ALL_PREFIXES(
-      PrivacySandboxAttestationPreInstallInteractionWithDownloadTest,
-      BothPreinstalledAndDownloadedAttestationsAvailable);
   // NOTE: Do not add any new friend classes for testing; tests that need
   // access to private functions / variables should go through this peer class.
   friend class privacy_sandbox_test_util::PrivacySandboxSettingsTestPeer;
diff --git a/components/reporting/proto/synced/record_constants.proto b/components/reporting/proto/synced/record_constants.proto
index 0f0f8b67..de5a1904 100644
--- a/components/reporting/proto/synced/record_constants.proto
+++ b/components/reporting/proto/synced/record_constants.proto
@@ -131,6 +131,13 @@
   // active/idle states during the session.
   USER_SESSION_ACTIVITY = 34;
 
+  // |AL_STATUS_REPORT| is for collecting status report from the AL
+  // devices.
+  AL_STATUS_REPORT = 35;
+
+  // |AL_USAGE_LOGS| is for usage logs events from the AL devices.
+  AL_USAGE_LOGS = 36;
+
   reserved 8;
 }
 
diff --git a/components/safe_browsing/core/browser/realtime/BUILD.gn b/components/safe_browsing/core/browser/realtime/BUILD.gn
index 8967d47..7110c78a 100644
--- a/components/safe_browsing/core/browser/realtime/BUILD.gn
+++ b/components/safe_browsing/core/browser/realtime/BUILD.gn
@@ -56,6 +56,36 @@
   ]
 }
 
+static_library("enterprise_url_lookup_service") {
+  sources = [
+    "chrome_enterprise_url_lookup_service.cc",
+    "chrome_enterprise_url_lookup_service.h",
+  ]
+
+  deps = [
+    ":policy_engine",
+    ":url_lookup_service_base",
+    "//base",
+    "//components/enterprise/common/proto:connectors_proto",
+    "//components/enterprise/connectors/core",
+    "//components/policy/core/common",
+    "//components/prefs",
+    "//components/safe_browsing/core/browser:referrer_chain_provider",
+    "//components/safe_browsing/core/browser:referring_app_info",
+    "//components/safe_browsing/core/browser:token_fetcher",
+    "//components/safe_browsing/core/browser:verdict_cache_manager",
+    "//components/safe_browsing/core/browser/sync",
+    "//components/safe_browsing/core/common",
+    "//components/safe_browsing/core/common:features",
+    "//components/safe_browsing/core/common/proto:csd_proto",
+    "//components/safe_browsing/core/common/proto:realtimeapi_proto",
+    "//components/signin/public/identity_manager",
+    "//net/traffic_annotation",
+    "//services/network/public/cpp",
+    "//url",
+  ]
+}
+
 static_library("url_lookup_service_base") {
   sources = [
     "url_lookup_service_base.cc",
diff --git a/components/safe_browsing/core/browser/realtime/DEPS b/components/safe_browsing/core/browser/realtime/DEPS
new file mode 100644
index 0000000..42d312c
--- /dev/null
+++ b/components/safe_browsing/core/browser/realtime/DEPS
@@ -0,0 +1,5 @@
+include_rules = [
+  "+components/signin/public/identity_manager",
+  "+components/policy/core/common/cloud",
+  "+components/policy/core/common/management"
+]
\ No newline at end of file
diff --git a/chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service.cc b/components/safe_browsing/core/browser/realtime/chrome_enterprise_url_lookup_service.cc
similarity index 98%
rename from chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service.cc
rename to components/safe_browsing/core/browser/realtime/chrome_enterprise_url_lookup_service.cc
index 1f7b42d..4a598aa 100644
--- a/chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service.cc
+++ b/components/safe_browsing/core/browser/realtime/chrome_enterprise_url_lookup_service.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 "chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service.h"
+#include "components/safe_browsing/core/browser/realtime/chrome_enterprise_url_lookup_service.h"
 
 #include "base/command_line.h"
 #include "base/functional/callback.h"
diff --git a/chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service.h b/components/safe_browsing/core/browser/realtime/chrome_enterprise_url_lookup_service.h
similarity index 92%
rename from chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service.h
rename to components/safe_browsing/core/browser/realtime/chrome_enterprise_url_lookup_service.h
index abbea70..7640c0b9 100644
--- a/chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service.h
+++ b/components/safe_browsing/core/browser/realtime/chrome_enterprise_url_lookup_service.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 CHROME_BROWSER_SAFE_BROWSING_CHROME_ENTERPRISE_URL_LOOKUP_SERVICE_H_
-#define CHROME_BROWSER_SAFE_BROWSING_CHROME_ENTERPRISE_URL_LOOKUP_SERVICE_H_
+#ifndef COMPONENTS_SAFE_BROWSING_CORE_BROWSER_REALTIME_CHROME_ENTERPRISE_URL_LOOKUP_SERVICE_H_
+#define COMPONENTS_SAFE_BROWSING_CORE_BROWSER_REALTIME_CHROME_ENTERPRISE_URL_LOOKUP_SERVICE_H_
 
 #include <memory>
 #include <string>
@@ -43,6 +43,10 @@
 
 class ReferrerChainProvider;
 
+// TODO(crbug.com/406211981): Migrate unit tests to components and remove the
+// comment below.
+// Note: Unit tests for this class are in chrome/browser/safe_browsing.
+
 // This class implements the real time lookup feature for a given user/profile.
 // It is separated from the base class for logic that is related to enterprise
 // users.(See: go/chrome-protego-enterprise-dd)
@@ -159,4 +163,4 @@
 
 }  // namespace safe_browsing
 
-#endif  // CHROME_BROWSER_SAFE_BROWSING_CHROME_ENTERPRISE_URL_LOOKUP_SERVICE_H_
+#endif  // COMPONENTS_SAFE_BROWSING_CORE_BROWSER_REALTIME_CHROME_ENTERPRISE_URL_LOOKUP_SERVICE_H_
diff --git a/components/saved_tab_groups/internal/BUILD.gn b/components/saved_tab_groups/internal/BUILD.gn
index 07ad08a..481a9f57 100644
--- a/components/saved_tab_groups/internal/BUILD.gn
+++ b/components/saved_tab_groups/internal/BUILD.gn
@@ -160,6 +160,7 @@
     "saved_tab_group_model_unittest.cc",
     "saved_tab_group_proto_conversion_unittest.cc",
     "saved_tab_group_sync_bridge_unittest.cc",
+    "shared_tab_group_account_data_sync_bridge_unittest.cc",
     "shared_tab_group_data_sync_bridge_unittest.cc",
     "startup_helper_unittest.cc",
     "tab_group_sync_bridge_mediator_unittest.cc",
diff --git a/components/saved_tab_groups/internal/shared_tab_group_account_data_sync_bridge.cc b/components/saved_tab_groups/internal/shared_tab_group_account_data_sync_bridge.cc
index a9b1e4460f..a9c91a5 100644
--- a/components/saved_tab_groups/internal/shared_tab_group_account_data_sync_bridge.cc
+++ b/components/saved_tab_groups/internal/shared_tab_group_account_data_sync_bridge.cc
@@ -6,13 +6,48 @@
 
 #include <optional>
 
-#include "base/notimplemented.h"
+#include "base/functional/callback_helpers.h"
+#include "base/uuid.h"
 #include "components/sync/model/entity_change.h"
+#include "components/sync/model/in_memory_metadata_change_list.h"
+#include "components/sync/model/metadata_batch.h"
 #include "components/sync/model/metadata_change_list.h"
 #include "components/sync/model/model_error.h"
+#include "components/sync/model/mutable_data_batch.h"
 #include "components/sync/protocol/entity_data.h"
+#include "components/sync/protocol/entity_specifics.pb.h"
+#include "components/sync/protocol/shared_tab_group_account_data_specifics.pb.h"
 
 namespace tab_groups {
+namespace {
+
+std::unique_ptr<syncer::EntityData> SpecificsToEntityData(
+    const sync_pb::SharedTabGroupAccountDataSpecifics& specifics) {
+  auto entity_data = std::make_unique<syncer::EntityData>();
+  *entity_data->specifics.mutable_shared_tab_group_account_data() = specifics;
+  entity_data->name = specifics.guid();
+  return entity_data;
+}
+
+std::string GetClientTagFromSpecifics(
+    const sync_pb::SharedTabGroupAccountDataSpecifics& specifics) {
+  return specifics.guid() + "|" + specifics.collaboration_id();
+}
+
+sync_pb::SharedTabGroupAccountDataSpecifics TrimSpecifics(
+    const sync_pb::SharedTabGroupAccountDataSpecifics& account_specifics) {
+  sync_pb::SharedTabGroupAccountDataSpecifics trimmed_account_specifics =
+      sync_pb::SharedTabGroupAccountDataSpecifics(account_specifics);
+  trimmed_account_specifics.clear_guid();
+  trimmed_account_specifics.clear_collaboration_id();
+  trimmed_account_specifics.mutable_shared_tab_details()
+      ->clear_shared_tab_group_guid();
+  trimmed_account_specifics.mutable_shared_tab_details()
+      ->clear_last_seen_timestamp_windows_epoch();
+  return trimmed_account_specifics;
+}
+
+}  // namespace
 
 SharedTabGroupAccountDataSyncBridge::SharedTabGroupAccountDataSyncBridge(
     std::unique_ptr<SyncDataTypeConfiguration> configuration)
@@ -30,8 +65,7 @@
 std::unique_ptr<syncer::MetadataChangeList>
 SharedTabGroupAccountDataSyncBridge::CreateMetadataChangeList() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  NOTIMPLEMENTED();
-  return nullptr;
+  return std::make_unique<syncer::InMemoryMetadataChangeList>();
 }
 
 std::optional<syncer::ModelError>
@@ -39,16 +73,58 @@
     std::unique_ptr<syncer::MetadataChangeList> metadata_change_list,
     syncer::EntityChangeList entity_change_list) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  NOTIMPLEMENTED();
-  return std::nullopt;
+  CHECK(specifics_.empty());
+
+  // Since this data type is grouped with shared tab group data, there
+  // will never be any shared tab groups in the model, therefore no data
+  // to merge, when this data type is enabled.
+
+  return ApplyIncrementalSyncChanges(std::move(metadata_change_list),
+                                     std::move(entity_change_list));
 }
 
 std::optional<syncer::ModelError>
 SharedTabGroupAccountDataSyncBridge::ApplyIncrementalSyncChanges(
     std::unique_ptr<syncer::MetadataChangeList> metadata_change_list,
-    syncer::EntityChangeList entity_changes) {
+    syncer::EntityChangeList entity_change_list) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  NOTIMPLEMENTED();
+
+  std::unique_ptr<syncer::DataTypeStore::WriteBatch> batch =
+      store_->CreateWriteBatch();
+
+  for (const std::unique_ptr<syncer::EntityChange>& change :
+       entity_change_list) {
+    switch (change->type()) {
+      case syncer::EntityChange::ACTION_ADD:
+      case syncer::EntityChange::ACTION_UPDATE: {
+        const sync_pb::EntitySpecifics& entity_specifics =
+            change->data().specifics;
+        // Guaranteed by ClientTagBasedDataTypeProcessor, based on
+        // IsEntityDataValid().
+        CHECK(entity_specifics.has_shared_tab_group_account_data());
+        const sync_pb::SharedTabGroupAccountDataSpecifics& specifics =
+            entity_specifics.shared_tab_group_account_data();
+
+        batch->WriteData(change->storage_key(), specifics.SerializeAsString());
+        specifics_[change->storage_key()] = specifics;
+        break;
+      }
+      case syncer::EntityChange::ACTION_DELETE:
+        specifics_.erase(change->storage_key());
+        batch->DeleteData(change->storage_key());
+        break;
+    }
+  }
+
+  // TODO(crbug.com/397767033): Notify observers
+
+  batch->TakeMetadataChangesFrom(std::move(metadata_change_list));
+  store_->CommitWriteBatch(
+      std::move(batch),
+      base::BindOnce(
+          &SharedTabGroupAccountDataSyncBridge::OnDataTypeStoreCommit,
+          weak_ptr_factory_.GetWeakPtr()));
+
   return std::nullopt;
 }
 
@@ -56,63 +132,168 @@
 SharedTabGroupAccountDataSyncBridge::GetDataForCommit(
     StorageKeyList storage_keys) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  NOTIMPLEMENTED();
-  return nullptr;
+
+  auto batch = std::make_unique<syncer::MutableDataBatch>();
+  for (const std::string& storage_key : storage_keys) {
+    if (specifics_.contains(storage_key)) {
+      batch->Put(storage_key, SpecificsToEntityData(specifics_[storage_key]));
+    }
+  }
+  return batch;
 }
 
 std::unique_ptr<syncer::DataBatch>
 SharedTabGroupAccountDataSyncBridge::GetAllDataForDebugging() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  NOTIMPLEMENTED();
-  return nullptr;
+
+  auto batch = std::make_unique<syncer::MutableDataBatch>();
+  for (const auto& [storage_key, specifics] : specifics_) {
+    batch->Put(storage_key, SpecificsToEntityData(specifics));
+  }
+  return batch;
 }
 
 std::string SharedTabGroupAccountDataSyncBridge::GetClientTag(
     const syncer::EntityData& entity_data) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  NOTIMPLEMENTED();
-  return "";
+  return GetClientTagFromSpecifics(
+      entity_data.specifics.shared_tab_group_account_data());
 }
 
 std::string SharedTabGroupAccountDataSyncBridge::GetStorageKey(
     const syncer::EntityData& entity_data) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  NOTIMPLEMENTED();
-  return "";
+  return GetClientTag(entity_data);
+}
+
+bool SharedTabGroupAccountDataSyncBridge::SupportsGetClientTag() const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  return true;
+}
+
+bool SharedTabGroupAccountDataSyncBridge::SupportsGetStorageKey() const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  return true;
 }
 
 void SharedTabGroupAccountDataSyncBridge::ApplyDisableSyncChanges(
     std::unique_ptr<syncer::MetadataChangeList> delete_metadata_change_list) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  NOTIMPLEMENTED();
+
+  // TODO(crbug.com/397767033): Get guids before clearing to notify
+  // observer.
+
+  specifics_.clear();
+  store_->DeleteAllDataAndMetadata(base::DoNothing());
+  weak_ptr_factory_.InvalidateWeakPtrs();
+
+  // TODO(crbug.com/397767033): Notify observers
 }
 
 bool SharedTabGroupAccountDataSyncBridge::IsEntityDataValid(
     const syncer::EntityData& entity_data) const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  NOTIMPLEMENTED();
-  return false;
+
+  const sync_pb::SharedTabGroupAccountDataSpecifics& specifics =
+      entity_data.specifics.shared_tab_group_account_data();
+  if (!base::Uuid::ParseCaseInsensitive(specifics.guid()).is_valid() ||
+      specifics.collaboration_id().empty()) {
+    return false;
+  }
+
+  if (!specifics.has_shared_tab_details()) {
+    // Non-tab account specifics should be handled here.
+    return false;
+  }
+
+  const sync_pb::SharedTabDetails& tab_details = specifics.shared_tab_details();
+  if (!base::Uuid::ParseCaseInsensitive(tab_details.shared_tab_group_guid())
+           .is_valid() ||
+      !tab_details.has_last_seen_timestamp_windows_epoch()) {
+    return false;
+  }
+
+  return true;
 }
 
 sync_pb::EntitySpecifics
 SharedTabGroupAccountDataSyncBridge::TrimAllSupportedFieldsFromRemoteSpecifics(
     const sync_pb::EntitySpecifics& entity_specifics) const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  NOTIMPLEMENTED();
-  return sync_pb::EntitySpecifics();
+
+  sync_pb::SharedTabGroupAccountDataSpecifics trimmed_specifics =
+      TrimSpecifics(entity_specifics.shared_tab_group_account_data());
+
+  if (trimmed_specifics.ByteSizeLong() == 0u) {
+    return sync_pb::EntitySpecifics();
+  }
+
+  sync_pb::EntitySpecifics trimmed_entity_specifics;
+  *trimmed_entity_specifics.mutable_shared_tab_group_account_data() =
+      trimmed_specifics;
+  return trimmed_entity_specifics;
+}
+
+bool SharedTabGroupAccountDataSyncBridge::IsInitialized() const {
+  return is_initialized_;
 }
 
 void SharedTabGroupAccountDataSyncBridge::OnStoreCreated(
     const std::optional<syncer::ModelError>& error,
     std::unique_ptr<syncer::DataTypeStore> store) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
   if (error) {
     change_processor()->ReportError(*error);
     return;
   }
 
   store_ = std::move(store);
+  store_->ReadAllDataAndMetadata(base::BindOnce(
+      &SharedTabGroupAccountDataSyncBridge::OnReadAllDataAndMetadata,
+      weak_ptr_factory_.GetWeakPtr()));
+}
 
-  // TODO(crbug.com/397767033): Read all data and metadata
+void SharedTabGroupAccountDataSyncBridge::OnReadAllDataAndMetadata(
+    const std::optional<syncer::ModelError>& error,
+    std::unique_ptr<syncer::DataTypeStore::RecordList> entries,
+    std::unique_ptr<syncer::MetadataBatch> metadata_batch) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  if (error) {
+    change_processor()->ReportError(*error);
+    return;
+  }
+
+  specifics_.reserve(entries->size());
+
+  for (const syncer::DataTypeStore::Record& r : *entries) {
+    sync_pb::SharedTabGroupAccountDataSpecifics specifics;
+    if (!specifics.ParseFromString(r.value)) {
+      // Ignore invalid entries.
+      continue;
+    }
+    if (specifics.guid() != r.id) {
+      // GUID is used as a storage key, so it should always match.
+      continue;
+    }
+    specifics_[GetClientTagFromSpecifics(specifics)] = std::move(specifics);
+  }
+
+  is_initialized_ = true;
+
+  // TODO(crbug.com/397767033): Notify observers
+
+  change_processor()->ModelReadyToSync(std::move(metadata_batch));
+}
+
+void SharedTabGroupAccountDataSyncBridge::OnDataTypeStoreCommit(
+    const std::optional<syncer::ModelError>& error) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  if (error) {
+    change_processor()->ReportError(*error);
+  }
 }
 
 }  // namespace tab_groups
diff --git a/components/saved_tab_groups/internal/shared_tab_group_account_data_sync_bridge.h b/components/saved_tab_groups/internal/shared_tab_group_account_data_sync_bridge.h
index a92e30d6..ec715018 100644
--- a/components/saved_tab_groups/internal/shared_tab_group_account_data_sync_bridge.h
+++ b/components/saved_tab_groups/internal/shared_tab_group_account_data_sync_bridge.h
@@ -19,7 +19,7 @@
 
 namespace tab_groups {
 
-// Sync bridge implementation for SHARED_TAB_GROUP_DATA data type.
+// Sync bridge implementation for SHARED_TAB_GROUP_ACCOUNT_DATA data type.
 class SharedTabGroupAccountDataSyncBridge : public syncer::DataTypeSyncBridge {
  public:
   explicit SharedTabGroupAccountDataSyncBridge(
@@ -45,22 +45,44 @@
   std::unique_ptr<syncer::DataBatch> GetAllDataForDebugging() override;
   std::string GetClientTag(const syncer::EntityData& entity_data) override;
   std::string GetStorageKey(const syncer::EntityData& entity_data) override;
+  bool SupportsGetClientTag() const override;
+  bool SupportsGetStorageKey() const override;
   void ApplyDisableSyncChanges(std::unique_ptr<syncer::MetadataChangeList>
                                    delete_metadata_change_list) override;
   bool IsEntityDataValid(const syncer::EntityData& entity_data) const override;
   sync_pb::EntitySpecifics TrimAllSupportedFieldsFromRemoteSpecifics(
       const sync_pb::EntitySpecifics& entity_specifics) const override;
 
+  // Returns whether the sync bridge has initialized by reading data
+  // from the on-disk store.
+  bool IsInitialized() const;
+
  private:
   // Loads the data already stored in the DataTypeStore.
   void OnStoreCreated(const std::optional<syncer::ModelError>& error,
                       std::unique_ptr<syncer::DataTypeStore> store);
 
+  // Calls ModelReadyToSync if there are no errors to report and propagators the
+  // stored entries to `on_load_callback`.
+  void OnReadAllDataAndMetadata(
+      const std::optional<syncer::ModelError>& error,
+      std::unique_ptr<syncer::DataTypeStore::RecordList> entries,
+      std::unique_ptr<syncer::MetadataBatch> metadata_batch);
+
+  void OnDataTypeStoreCommit(const std::optional<syncer::ModelError>& error);
+
   SEQUENCE_CHECKER(sequence_checker_);
 
   // In charge of actually persisting changes to disk, or loading previous data.
   std::unique_ptr<syncer::DataTypeStore> store_;
 
+  // Set to true once data is loaded from disk into the in-memory cache.
+  bool is_initialized_ = false;
+
+  // In-memory data cache of specifics, keyed by its storage key.
+  std::unordered_map<std::string, sync_pb::SharedTabGroupAccountDataSpecifics>
+      specifics_;
+
   // Allows safe temporary use of the SharedTabGroupAccountDataSyncBridge
   // object if it exists at the time of use.
   base::WeakPtrFactory<SharedTabGroupAccountDataSyncBridge> weak_ptr_factory_{
diff --git a/components/saved_tab_groups/internal/shared_tab_group_account_data_sync_bridge_unittest.cc b/components/saved_tab_groups/internal/shared_tab_group_account_data_sync_bridge_unittest.cc
new file mode 100644
index 0000000..ba0a66c
--- /dev/null
+++ b/components/saved_tab_groups/internal/shared_tab_group_account_data_sync_bridge_unittest.cc
@@ -0,0 +1,129 @@
+// 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/saved_tab_groups/internal/shared_tab_group_account_data_sync_bridge.h"
+
+#include "base/run_loop.h"
+#include "base/test/scoped_feature_list.h"
+#include "base/test/task_environment.h"
+#include "base/time/time.h"
+#include "base/uuid.h"
+#include "components/sync/base/features.h"
+#include "components/sync/protocol/entity_data.h"
+#include "components/sync/protocol/shared_tab_group_account_data_specifics.pb.h"
+#include "components/sync/test/data_type_store_test_util.h"
+#include "components/sync/test/mock_data_type_local_change_processor.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace tab_groups {
+
+namespace {
+
+using testing::Invoke;
+
+sync_pb::SharedTabGroupAccountDataSpecifics MakeTabGroupAccountSpecifics(
+    base::Time last_seen_timestamp) {
+  sync_pb::SharedTabGroupAccountDataSpecifics specifics;
+  specifics.set_guid(base::Uuid::GenerateRandomV4().AsLowercaseString());
+  specifics.set_collaboration_id(
+      base::Uuid::GenerateRandomV4().AsLowercaseString());
+  sync_pb::SharedTabDetails* tab_group_details =
+      specifics.mutable_shared_tab_details();
+  tab_group_details->set_shared_tab_group_guid(
+      base::Uuid::GenerateRandomV4().AsLowercaseString());
+  tab_group_details->set_last_seen_timestamp_windows_epoch(
+      last_seen_timestamp.ToDeltaSinceWindowsEpoch().InMicroseconds());
+  return specifics;
+}
+
+syncer::EntityData CreateEntityData(
+    const sync_pb::SharedTabGroupAccountDataSpecifics& specifics,
+    base::Time creation_time = base::Time::Now()) {
+  syncer::EntityData entity_data;
+  *entity_data.specifics.mutable_shared_tab_group_account_data() = specifics;
+  entity_data.name = specifics.guid();
+  entity_data.creation_time = creation_time;
+  return entity_data;
+}
+
+class SharedTabGroupAccountDataSyncBridgeTest : public testing::Test {
+ public:
+  SharedTabGroupAccountDataSyncBridgeTest()
+      : store_(syncer::DataTypeStoreTestUtil::CreateInMemoryStoreForTest()) {
+    feature_list_.InitAndEnableFeature(syncer::kSyncSharedTabGroupAccountData);
+  }
+
+  // Creates the bridges and initializes the model. Returns true when succeeds.
+  void InitializeBridgeAndModel() {
+    ON_CALL(processor_, IsTrackingMetadata())
+        .WillByDefault(testing::Return(true));
+
+    base::RunLoop run_loop;
+    base::RepeatingClosure quit_closure = run_loop.QuitClosure();
+    EXPECT_CALL(processor_, ModelReadyToSync).WillOnce(Invoke([&]() {
+      quit_closure.Run();
+    }));
+
+    bridge_ = std::make_unique<SharedTabGroupAccountDataSyncBridge>(
+        std::make_unique<SyncDataTypeConfiguration>(
+            processor_.CreateForwardingProcessor(),
+            syncer::DataTypeStoreTestUtil::FactoryForForwardingStore(
+                store_.get())));
+
+    run_loop.Run();
+
+    ASSERT_TRUE(bridge().IsInitialized());
+  }
+
+  SharedTabGroupAccountDataSyncBridge& bridge() { return *bridge_; }
+  syncer::DataTypeStore& store() { return *store_; }
+  testing::NiceMock<syncer::MockDataTypeLocalChangeProcessor>&
+  mock_processor() {
+    return processor_;
+  }
+  const testing::NiceMock<syncer::MockDataTypeLocalChangeProcessor>&
+  mock_processor() const {
+    return processor_;
+  }
+
+ protected:
+  // In memory data type store needs to be able to post tasks.
+  base::test::TaskEnvironment task_environment_;
+
+  testing::NiceMock<syncer::MockDataTypeLocalChangeProcessor> processor_;
+  std::unique_ptr<syncer::DataTypeStore> store_;
+  std::unique_ptr<SharedTabGroupAccountDataSyncBridge> bridge_;
+  base::test::ScopedFeatureList feature_list_;
+};
+
+TEST_F(SharedTabGroupAccountDataSyncBridgeTest, ShouldReturnClientTag) {
+  InitializeBridgeAndModel();
+
+  EXPECT_TRUE(bridge().SupportsGetClientTag());
+  sync_pb::SharedTabGroupAccountDataSpecifics shared_tab_details =
+      MakeTabGroupAccountSpecifics(base::Time::Now());
+  EXPECT_EQ(
+      bridge().GetClientTag(CreateEntityData(shared_tab_details)),
+      shared_tab_details.guid() + "|" + shared_tab_details.collaboration_id());
+}
+
+TEST_F(SharedTabGroupAccountDataSyncBridgeTest,
+       ShouldAddDataAtIncrementalUpdate) {
+  InitializeBridgeAndModel();
+
+  // TODO(crbug.com/397767033): Add test for applying incremental
+  // updates from sync.
+}
+
+TEST_F(SharedTabGroupAccountDataSyncBridgeTest,
+       ShouldReloadDataOnBrowserRestart) {
+  InitializeBridgeAndModel();
+
+  // TODO(crbug.com/397767033): Add test for loading store data after
+  // restart.
+}
+
+}  // namespace
+}  // namespace tab_groups
diff --git a/components/saved_tab_groups/internal/tab_group_sync_service_unittest.cc b/components/saved_tab_groups/internal/tab_group_sync_service_unittest.cc
index bfd4aeb3..b67c3ea2 100644
--- a/components/saved_tab_groups/internal/tab_group_sync_service_unittest.cc
+++ b/components/saved_tab_groups/internal/tab_group_sync_service_unittest.cc
@@ -220,11 +220,7 @@
             shared_processor_.CreateForwardingProcessor(),
             syncer::DataTypeStoreTestUtil::FactoryForForwardingStore(
                 shared_store_.get())),
-        std::make_unique<SyncDataTypeConfiguration>(
-            shared_account_processor_.CreateForwardingProcessor(),
-            syncer::DataTypeStoreTestUtil::FactoryForForwardingStore(
-                shared_account_store_.get())),
-        &pref_service_, std::move(metrics_logger), decider_.get(),
+        nullptr, &pref_service_, std::move(metrics_logger), decider_.get(),
         identity_test_environment_.identity_manager(),
         std::move(collaboration_finder), /*logger=*/nullptr);
     ON_CALL(saved_processor_, IsTrackingMetadata())
diff --git a/components/search_engines/search_engine_choice/search_engine_choice_service.cc b/components/search_engines/search_engine_choice/search_engine_choice_service.cc
index 6d9aaa317..0ba33386 100644
--- a/components/search_engines/search_engine_choice/search_engine_choice_service.cc
+++ b/components/search_engines/search_engine_choice/search_engine_choice_service.cc
@@ -119,17 +119,6 @@
                   version_info::GetVersionNumber());
 }
 
-std::optional<base::Time> GetChoiceScreenCompletionTimestamp(
-    PrefService& prefs) {
-  if (!prefs.HasPrefPath(
-          prefs::kDefaultSearchProviderChoiceScreenCompletionTimestamp)) {
-    return std::nullopt;
-  }
-
-  return base::Time::FromDeltaSinceWindowsEpoch(base::Seconds(prefs.GetInt64(
-      prefs::kDefaultSearchProviderChoiceScreenCompletionTimestamp)));
-}
-
 // Returns true if the version is valid and can be compared to the current
 // Chrome version.
 bool IsValidVersionFormat(const base::Version& version) {
diff --git a/components/search_engines/search_engine_choice/search_engine_choice_utils.cc b/components/search_engines/search_engine_choice/search_engine_choice_utils.cc
index 0b274b36..b031f4f7 100644
--- a/components/search_engines/search_engine_choice/search_engine_choice_utils.cc
+++ b/components/search_engines/search_engine_choice/search_engine_choice_utils.cc
@@ -224,6 +224,17 @@
 #endif
 }
 
+std::optional<base::Time> GetChoiceScreenCompletionTimestamp(
+    PrefService& prefs) {
+  if (!prefs.HasPrefPath(
+          prefs::kDefaultSearchProviderChoiceScreenCompletionTimestamp)) {
+    return std::nullopt;
+  }
+
+  return base::Time::FromDeltaSinceWindowsEpoch(base::Seconds(prefs.GetInt64(
+      prefs::kDefaultSearchProviderChoiceScreenCompletionTimestamp)));
+}
+
 #if !BUILDFLAG(IS_ANDROID)
 std::u16string GetMarketingSnippetString(
     const TemplateURLData& template_url_data) {
@@ -246,6 +257,6 @@
   return iterator == kSearchEngineResourceIdMap.cend() ? -1 : iterator->second;
 }
 
-#endif
+#endif  // !BUILDFLAG(IS_ANDROID)
 
 }  // namespace search_engines
diff --git a/components/search_engines/search_engine_choice/search_engine_choice_utils.h b/components/search_engines/search_engine_choice/search_engine_choice_utils.h
index ff6330f..bf1952893e 100644
--- a/components/search_engines/search_engine_choice/search_engine_choice_utils.h
+++ b/components/search_engines/search_engine_choice/search_engine_choice_utils.h
@@ -20,6 +20,10 @@
 class SearchTermsData;
 struct TemplateURLData;
 
+namespace base {
+class Time;
+}  // namespace base
+
 namespace search_engines {
 
 inline constexpr char
@@ -146,8 +150,9 @@
   kInvalidChoiceVersion = 2,
   kReprompt = 3,
   kCommandLineFlag = 4,
+  kDeviceRestored = 5,
 
-  kMaxValue = kCommandLineFlag,
+  kMaxValue = kDeviceRestored,
 };
 
 // Exposed for testing.
@@ -280,6 +285,11 @@
 void WipeSearchEngineChoicePrefs(PrefService& profile_prefs,
                                  WipeSearchEngineChoiceReason reason);
 
+// Returns the timestamp of search engine choice screen. No value if no choice
+// has been made.
+std::optional<base::Time> GetChoiceScreenCompletionTimestamp(
+    PrefService& prefs);
+
 #if !BUILDFLAG(IS_ANDROID)
 // Returns the engine marketing snippet string resource id or -1 if the snippet
 // was not found.
@@ -299,7 +309,7 @@
 // `generated_search_engine_resource_ids.cc`.
 int GetIconResourceId(const std::u16string& engine_keyword);
 
-#endif
+#endif  // !BUILDFLAG(IS_ANDROID)
 
 }  // namespace search_engines
 
diff --git a/components/signin/public/identity_manager/access_token_restriction.cc b/components/signin/public/identity_manager/access_token_restriction.cc
index fbe623c..f22141ab 100644
--- a/components/signin/public/identity_manager/access_token_restriction.cc
+++ b/components/signin/public/identity_manager/access_token_restriction.cc
@@ -120,8 +120,9 @@
       // identity system.
       GaiaConstants::kOAuth1LoginScope,
 
-      // Required by the Google Calendar NTP module and ChromeOS.
+      // Required by the Desktop NTP and ChromeOS.
       GaiaConstants::kCalendarReadOnlyOAuth2Scope,
+      GaiaConstants::kDriveReadOnlyOAuth2Scope,
 
       // Used by DevTools GenAI features
       GaiaConstants::kAidaOAuth2Scope,
@@ -133,7 +134,6 @@
       GaiaConstants::kCastBackdropOAuth2Scope,
       GaiaConstants::kClearCutOAuth2Scope,
       GaiaConstants::kDriveOAuth2Scope,
-      GaiaConstants::kDriveReadOnlyOAuth2Scope,
       GaiaConstants::kExperimentsAndConfigsOAuth2Scope,
       GaiaConstants::kGCMGroupServerOAuth2Scope,
       GaiaConstants::kNearbyDevicesOAuth2Scope,
diff --git a/components/signin/public/identity_manager/access_token_restriction_unittest.cc b/components/signin/public/identity_manager/access_token_restriction_unittest.cc
index 8642efcf..7401be6 100644
--- a/components/signin/public/identity_manager/access_token_restriction_unittest.cc
+++ b/components/signin/public/identity_manager/access_token_restriction_unittest.cc
@@ -46,13 +46,13 @@
  {GaiaConstants::kDiscoveryEngineCompleteQueryOAuth2Scope, OAuth2ScopeRestriction::kSignedIn},
  {GaiaConstants::kOAuth1LoginScope, OAuth2ScopeRestriction::kSignedIn},
  {GaiaConstants::kCalendarReadOnlyOAuth2Scope, OAuth2ScopeRestriction::kSignedIn},
+ {GaiaConstants::kDriveReadOnlyOAuth2Scope, OAuth2ScopeRestriction::kSignedIn},
 #if BUILDFLAG(IS_CHROMEOS)
  {GaiaConstants::kAssistantOAuth2Scope, OAuth2ScopeRestriction::kSignedIn},
  {GaiaConstants::kAuditRecordingOAuth2Scope, OAuth2ScopeRestriction::kSignedIn},
  {GaiaConstants::kCastBackdropOAuth2Scope, OAuth2ScopeRestriction::kSignedIn},
  {GaiaConstants::kClearCutOAuth2Scope, OAuth2ScopeRestriction::kSignedIn},
  {GaiaConstants::kDriveOAuth2Scope, OAuth2ScopeRestriction::kSignedIn},
- {GaiaConstants::kDriveReadOnlyOAuth2Scope, OAuth2ScopeRestriction::kSignedIn},
  {GaiaConstants::kExperimentsAndConfigsOAuth2Scope, OAuth2ScopeRestriction::kSignedIn},
  {GaiaConstants::kGCMGroupServerOAuth2Scope, OAuth2ScopeRestriction::kSignedIn},
  {GaiaConstants::kNearbyDevicesOAuth2Scope, OAuth2ScopeRestriction::kSignedIn},
diff --git a/components/sync/base/data_type.cc b/components/sync/base/data_type.cc
index dd3c3245..f526b40 100644
--- a/components/sync/base/data_type.cc
+++ b/components/sync/base/data_type.cc
@@ -106,7 +106,7 @@
         {sync_pb::EntitySpecifics::kCookieFieldNumber, COOKIES},
         {sync_pb::EntitySpecifics::kPlusAddressSettingFieldNumber,
          PLUS_ADDRESS_SETTING},
-        {sync_pb::EntitySpecifics::kAutofillLoyaltyCardFieldNumber,
+        {sync_pb::EntitySpecifics::kAutofillValuableFieldNumber,
          AUTOFILL_LOYALTY_CARD},
         {sync_pb::EntitySpecifics::kSharedTabGroupAccountDataFieldNumber,
          SHARED_TAB_GROUP_ACCOUNT_DATA},
@@ -278,7 +278,7 @@
       specifics->mutable_plus_address_setting();
       break;
     case AUTOFILL_LOYALTY_CARD:
-      specifics->mutable_autofill_loyalty_card();
+      specifics->mutable_autofill_valuable();
       break;
     case SHARED_TAB_GROUP_ACCOUNT_DATA:
       specifics->mutable_shared_tab_group_account_data();
@@ -404,7 +404,7 @@
     case PLUS_ADDRESS_SETTING:
       return sync_pb::EntitySpecifics::kPlusAddressSettingFieldNumber;
     case AUTOFILL_LOYALTY_CARD:
-      return sync_pb::EntitySpecifics::kAutofillLoyaltyCardFieldNumber;
+      return sync_pb::EntitySpecifics::kAutofillValuableFieldNumber;
     case SHARED_TAB_GROUP_ACCOUNT_DATA:
       return sync_pb::EntitySpecifics::kSharedTabGroupAccountDataFieldNumber;
     case NIGORI:
@@ -584,7 +584,7 @@
   if (specifics.has_plus_address_setting()) {
     return PLUS_ADDRESS_SETTING;
   }
-  if (specifics.has_autofill_loyalty_card()) {
+  if (specifics.has_autofill_valuable()) {
     return AUTOFILL_LOYALTY_CARD;
   }
   if (specifics.has_shared_tab_group_account_data()) {
diff --git a/components/sync/base/data_type.h b/components/sync/base/data_type.h
index 8b6be0b..a3440c1 100644
--- a/components/sync/base/data_type.h
+++ b/components/sync/base/data_type.h
@@ -168,8 +168,9 @@
   // standard syncable prefs.
   PLUS_ADDRESS_SETTING,
 
-  // Loyalty cards stored in the Google Wallet.
+  // Valuables stored in the Google Wallet.
   // Read-only on the client.
+  // TODO(crbug.com/393119606): Rename to AUTOFILL_VALUABLE
   AUTOFILL_LOYALTY_CARD,
 
   // Account-local metadata for shared tab groups.
diff --git a/components/sync/protocol/autofill_loyalty_card_specifics.proto b/components/sync/protocol/autofill_loyalty_card_specifics.proto
deleted file mode 100644
index 79d0c85..0000000
--- a/components/sync/protocol/autofill_loyalty_card_specifics.proto
+++ /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.
-
-// If you change or add any fields in this file, update proto_visitors.h and
-// potentially proto_enum_conversions.{h, cc}.
-
-syntax = "proto2";
-
-option java_multiple_files = true;
-option java_package = "org.chromium.components.sync.protocol";
-
-option optimize_for = LITE_RUNTIME;
-
-package sync_pb;
-
-// Loyalty cards coming from Google Wallet.
-
-// Next tag: 6
-message AutofillLoyaltyCardSpecifics {
-  // The card id.
-  optional string id = 1;
-
-  // The merchant name e.g. "Deutsche Bahn".
-  optional string merchant_name = 2;
-
-  // The program name e.g. "BahnBonus".
-  optional string program_name = 3;
-
-  // The logo URL.
-  optional string program_logo = 4;
-
-  // The unmasked suffix of the loyalty card text code.
-  // Full text code of the loyalty card is not shared via sync and requested
-  // separately on demand.
-  optional string loyalty_card_suffix = 5;
-}
diff --git a/components/sync/protocol/autofill_valuable_specifics.proto b/components/sync/protocol/autofill_valuable_specifics.proto
new file mode 100644
index 0000000..b187406
--- /dev/null
+++ b/components/sync/protocol/autofill_valuable_specifics.proto
@@ -0,0 +1,42 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// If you change or add any fields in this file, update proto_visitors.h and
+// potentially proto_enum_conversions.{h, cc}.
+
+syntax = "proto2";
+
+option java_multiple_files = true;
+option java_package = "org.chromium.components.sync.protocol";
+
+option optimize_for = LITE_RUNTIME;
+
+package sync_pb;
+
+// Valuables coming from Google Wallet.
+message AutofillValuableSpecifics {
+  // The valuable id.
+  optional string id = 1;
+
+  message LoyaltyCard {
+    // The merchant name e.g. "Deutsche Bahn".
+    optional string merchant_name = 1;
+
+    // The program name e.g. "BahnBonus".
+    optional string program_name = 2;
+
+    // The logo URL.
+    optional string program_logo = 3;
+
+    // The unmasked suffix of the loyalty card text code.
+    // Full text code of the loyalty card is not shared via sync and requested
+    // separately on demand.
+    optional string loyalty_card_suffix = 4;
+  }
+
+  oneof valuable_data {
+    LoyaltyCard loyalty_card = 2;
+    // add other valuable types here.
+  }
+}
diff --git a/components/sync/protocol/entity_specifics.proto b/components/sync/protocol/entity_specifics.proto
index a113f46..78f7925 100644
--- a/components/sync/protocol/entity_specifics.proto
+++ b/components/sync/protocol/entity_specifics.proto
@@ -18,6 +18,7 @@
 import "components/sync/protocol/arc_package_specifics.proto";
 import "components/sync/protocol/autofill_specifics.proto";
 import "components/sync/protocol/autofill_offer_specifics.proto";
+import "components/sync/protocol/autofill_valuable_specifics.proto";
 import "components/sync/protocol/autofill_wallet_credential_specifics.proto";
 import "components/sync/protocol/autofill_wallet_usage_specifics.proto";
 import "components/sync/protocol/bookmark_specifics.proto";
@@ -62,7 +63,6 @@
 import "components/sync/protocol/webauthn_credential_specifics.proto";
 import "components/sync/protocol/wifi_configuration_specifics.proto";
 import "components/sync/protocol/workspace_desk_specifics.proto";
-import "components/sync/protocol/autofill_loyalty_card_specifics.proto";
 import "components/sync/protocol/shared_tab_group_account_data_specifics.proto";
 
 message EntitySpecifics {
@@ -157,7 +157,7 @@
     PlusAddressSpecifics plus_address = 1267844;
     CookieSpecifics cookie = 1281100;
     PlusAddressSettingSpecifics plus_address_setting = 1303742;
-    AutofillLoyaltyCardSpecifics autofill_loyalty_card = 1419865;
+    AutofillValuableSpecifics autofill_valuable = 1419865;
     SharedTabGroupAccountDataSpecifics shared_tab_group_account_data = 1429255;
     // When adding a new type, follow the docs below and keep this comment as
     // the last entry.
diff --git a/components/sync/protocol/proto_value_conversions_unittest.cc b/components/sync/protocol/proto_value_conversions_unittest.cc
index 41a88511..be57513 100644
--- a/components/sync/protocol/proto_value_conversions_unittest.cc
+++ b/components/sync/protocol/proto_value_conversions_unittest.cc
@@ -126,7 +126,7 @@
 DEFINE_SPECIFICS_TO_VALUE_TEST(webauthn_credential)
 DEFINE_SPECIFICS_TO_VALUE_TEST(wifi_configuration)
 DEFINE_SPECIFICS_TO_VALUE_TEST(workspace_desk)
-DEFINE_SPECIFICS_TO_VALUE_TEST(autofill_loyalty_card)
+DEFINE_SPECIFICS_TO_VALUE_TEST(autofill_valuable)
 DEFINE_SPECIFICS_TO_VALUE_TEST(shared_tab_group_account_data)
 
 TEST(ProtoValueConversionsTest, AutofillWalletSpecificsToValue) {
diff --git a/components/sync/protocol/proto_visitors.h b/components/sync/protocol/proto_visitors.h
index c456d65..80493850 100644
--- a/components/sync/protocol/proto_visitors.h
+++ b/components/sync/protocol/proto_visitors.h
@@ -12,6 +12,7 @@
 #include "components/sync/protocol/arc_package_specifics.pb.h"
 #include "components/sync/protocol/autofill_offer_specifics.pb.h"
 #include "components/sync/protocol/autofill_specifics.pb.h"
+#include "components/sync/protocol/autofill_valuable_specifics.pb.h"
 #include "components/sync/protocol/autofill_wallet_credential_specifics.pb.h"
 #include "components/sync/protocol/autofill_wallet_usage_specifics.pb.h"
 #include "components/sync/protocol/bookmark_specifics.pb.h"
@@ -731,9 +732,9 @@
   VISIT(app_setting);
   VISIT(arc_package);
   VISIT(autofill);
-  VISIT(autofill_loyalty_card);
   VISIT(autofill_offer);
   VISIT(autofill_profile);
+  VISIT(autofill_valuable);
   VISIT(autofill_wallet);
   VISIT(autofill_wallet_credential);
   VISIT(autofill_wallet_usage);
@@ -1995,8 +1996,13 @@
   VISIT(is_collapsed);
 }
 
-VISIT_PROTO_FIELDS(const sync_pb::AutofillLoyaltyCardSpecifics& proto) {
+VISIT_PROTO_FIELDS(const sync_pb::AutofillValuableSpecifics& proto) {
   VISIT(id);
+  VISIT(loyalty_card);
+}
+
+VISIT_PROTO_FIELDS(
+    const sync_pb::AutofillValuableSpecifics::LoyaltyCard& proto) {
   VISIT(merchant_name);
   VISIT(program_name);
   VISIT(program_logo);
diff --git a/components/sync/protocol/protocol_sources.gni b/components/sync/protocol/protocol_sources.gni
index f6ca83046..4a601b45 100644
--- a/components/sync/protocol/protocol_sources.gni
+++ b/components/sync/protocol/protocol_sources.gni
@@ -7,9 +7,9 @@
   "app_setting_specifics.proto",
   "app_specifics.proto",
   "arc_package_specifics.proto",
-  "autofill_loyalty_card_specifics.proto",
   "autofill_offer_specifics.proto",
   "autofill_specifics.proto",
+  "autofill_valuable_specifics.proto",
   "autofill_wallet_credential_specifics.proto",
   "autofill_wallet_usage_specifics.proto",
   "bookmark_model_metadata.proto",
diff --git a/components/viz/common/viz_utils.h b/components/viz/common/viz_utils.h
index 56e9dac..08d9bcd 100644
--- a/components/viz/common/viz_utils.h
+++ b/components/viz/common/viz_utils.h
@@ -5,13 +5,14 @@
 #ifndef COMPONENTS_VIZ_COMMON_VIZ_UTILS_H_
 #define COMPONENTS_VIZ_COMMON_VIZ_UTILS_H_
 
+#include <string>
+
 #include "base/timer/elapsed_timer.h"
+#include "build/build_config.h"
 #include "cc/paint/filter_operations.h"
 #include "components/viz/common/quads/draw_quad.h"
 #include "components/viz/common/viz_common_export.h"
 
-#include "build/build_config.h"
-
 namespace gfx {
 class Rect;
 class RRectF;
@@ -86,4 +87,15 @@
 
 }  // namespace viz
 
+#define VIZ_HIT_PATH(path_name)                                         \
+  do {                                                                  \
+    static bool init = false;                                           \
+    if (!init) {                                                        \
+      std::string name =                                                \
+          "Compositing.Display.VizCodePath." + std::string(#path_name); \
+      UMA_HISTOGRAM_BOOLEAN(name, true);                                \
+      init = true;                                                      \
+    }                                                                   \
+  } while (0)
+
 #endif  // COMPONENTS_VIZ_COMMON_VIZ_UTILS_H_
diff --git a/components/viz/service/display/display.cc b/components/viz/service/display/display.cc
index e9a17b58..5383e497 100644
--- a/components/viz/service/display/display.cc
+++ b/components/viz/service/display/display.cc
@@ -18,6 +18,7 @@
 
 #include "base/check.h"
 #include "base/containers/contains.h"
+#include "base/containers/flat_set.h"
 #include "base/debug/dump_without_crashing.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/observer_list.h"
@@ -815,6 +816,7 @@
 
 bool Display::DrawAndSwap(const DrawAndSwapParams& params) {
   TRACE_EVENT0("viz", "Display::DrawAndSwap");
+  VIZ_HIT_PATH("DrawAndSwap");
   if (debug_settings_->show_aggregated_damage !=
       aggregator_->HasFrameAnnotator()) {
     if (debug_settings_->show_aggregated_damage) {
@@ -911,6 +913,7 @@
     // aggregated again so that the trail exists for a single frame.
     target_damage_bounding_rect.Union(
         renderer_->GetDelegatedInkTrailDamageRect());
+    VIZ_HIT_PATH("Aggregate");
     frame = aggregator_->Aggregate(
         current_surface_id_, params.expected_display_time,
         current_display_transform, target_damage_bounding_rect,
diff --git a/components/winhttp/scoped_hinternet.cc b/components/winhttp/scoped_hinternet.cc
index 5f7b2e61..0d8b8dc 100644
--- a/components/winhttp/scoped_hinternet.cc
+++ b/components/winhttp/scoped_hinternet.cc
@@ -11,6 +11,8 @@
 #include <string_view>
 #include <utility>
 
+#include "base/logging.h"
+
 namespace winhttp {
 
 ScopedHInternet CreateSessionHandle(std::wstring_view user_agent,
@@ -30,16 +32,30 @@
     DWORD protocols = WINHTTP_FLAG_SECURE_PROTOCOL_TLS1 |
                       WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1 |
                       WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2;
-    ::WinHttpSetOption(session_handle.get(), WINHTTP_OPTION_SECURE_PROTOCOLS,
-                       &protocols, sizeof(protocols));
+    BOOL result = ::WinHttpSetOption(session_handle.get(),
+                                     WINHTTP_OPTION_SECURE_PROTOCOLS,
+                                     &protocols, sizeof(protocols));
+    VLOG_IF(1, !result)
+        << "Failed to configure WINHTTP_OPTION_SECURE_PROTOCOLS";
   }
 
   // WINHTTP_OPTION_DECOMPRESSION is supported on Windows 8.1 and higher.
   if (session_handle.is_valid() && ::IsWindows8Point1OrGreater()) {
     DWORD decompression_flag = WINHTTP_DECOMPRESSION_FLAG_ALL;
-    ::WinHttpSetOption(session_handle.get(), WINHTTP_OPTION_DECOMPRESSION,
-                       &decompression_flag, sizeof(decompression_flag));
+    BOOL result =
+        ::WinHttpSetOption(session_handle.get(), WINHTTP_OPTION_DECOMPRESSION,
+                           &decompression_flag, sizeof(decompression_flag));
+    VLOG_IF(1, !result)
+        << "Failed to configure WINHTTP_OPTION_SECURE_PROTOCOLS";
   }
+
+  DWORD protocol_flag = WINHTTP_PROTOCOL_FLAG_HTTP2;
+  BOOL result = ::WinHttpSetOption(session_handle.get(),
+                                   WINHTTP_OPTION_ENABLE_HTTP_PROTOCOL,
+                                   &protocol_flag, sizeof(protocol_flag));
+  VLOG_IF(1, !result)
+      << "Failed to configure WINHTTP_OPTION_ENABLE_HTTP_PROTOCOL";
+
   return session_handle;
 }
 
diff --git a/content/browser/accessibility/browser_accessibility_state_impl.cc b/content/browser/accessibility/browser_accessibility_state_impl.cc
index b636da8..6ee02976 100644
--- a/content/browser/accessibility/browser_accessibility_state_impl.cc
+++ b/content/browser/accessibility/browser_accessibility_state_impl.cc
@@ -274,10 +274,6 @@
   SetProcessMode(ui::AXMode());
 }
 
-bool BrowserAccessibilityStateImpl::IsAccessibleBrowser() {
-  return GetAccessibilityMode() == ui::kAXModeComplete;
-}
-
 void BrowserAccessibilityStateImpl::SetPerformanceFilteringAllowed(
     bool allowed) {
   performance_filtering_allowed_ = allowed;
diff --git a/content/browser/accessibility/browser_accessibility_state_impl.h b/content/browser/accessibility/browser_accessibility_state_impl.h
index ff13027..4a9a77c8 100644
--- a/content/browser/accessibility/browser_accessibility_state_impl.h
+++ b/content/browser/accessibility/browser_accessibility_state_impl.h
@@ -78,7 +78,6 @@
   // Any currently running assistive tech that should prevent accessibility from
   // being auto-disabled.
   ui::AssistiveTech ActiveAssistiveTech() const override;
-  bool IsAccessibleBrowser() override;
   void SetPerformanceFilteringAllowed(bool enabled) override;
   bool IsPerformanceFilteringAllowed() override;
   base::CallbackListSubscription RegisterFocusChangedCallback(
diff --git a/content/browser/accessibility/browser_accessibility_state_impl_unittest.cc b/content/browser/accessibility/browser_accessibility_state_impl_unittest.cc
index f6a06dd..0626cbf 100644
--- a/content/browser/accessibility/browser_accessibility_state_impl_unittest.cc
+++ b/content/browser/accessibility/browser_accessibility_state_impl_unittest.cc
@@ -59,7 +59,6 @@
   base::HistogramTester histograms;
 
   // Initially, accessibility should be disabled.
-  EXPECT_FALSE(state_->IsAccessibleBrowser());
   EXPECT_EQ(ui::AXPlatform::GetInstance().GetMode(), ui::AXMode());
 
   // Enable accessibility based on usage of accessibility APIs.
@@ -67,7 +66,6 @@
   // Indicate that an actual screen reader is not running (a screen reader
   // will prevent auto-disable from taking place).
   state_->SetScreenReaderAppActive(false);
-  EXPECT_TRUE(state_->IsAccessibleBrowser());
   EXPECT_EQ(ui::AXPlatform::GetInstance().GetMode(), ui::kAXModeComplete);
 
   // Send user input, wait 31 seconds, then send another user input event.
@@ -78,7 +76,6 @@
   state_->OnUserInputEvent();
 
   // Accessibility should now be disabled.
-  EXPECT_FALSE(state_->IsAccessibleBrowser());
   EXPECT_EQ(ui::AXPlatform::GetInstance().GetMode(), ui::AXMode());
 
   // A histogram should record that accessibility was disabled with
@@ -94,7 +91,6 @@
 TEST_F(BrowserAccessibilityStateImplTest,
        AccessibilityApiUsagePreventsAutoDisableAccessibility) {
   // Initially, accessibility should be disabled.
-  EXPECT_FALSE(state_->IsAccessibleBrowser());
   EXPECT_EQ(ui::AXPlatform::GetInstance().GetMode(), ui::AXMode());
 
   // Enable accessibility based on usage of accessibility APIs.
@@ -102,7 +98,6 @@
   // Indicate that an actual screen reader is not running (a screen reader
   // will prevent auto-disable from taking place).
   state_->SetScreenReaderAppActive(false);
-  EXPECT_TRUE(state_->IsAccessibleBrowser());
   EXPECT_EQ(ui::AXPlatform::GetInstance().GetMode(), ui::kAXModeComplete);
 
   // Send user input, wait 31 seconds, then send another user input event -
@@ -114,7 +109,6 @@
   state_->OnUserInputEvent();
 
   // Accessibility should still be enabled.
-  EXPECT_TRUE(state_->IsAccessibleBrowser());
   EXPECT_EQ(ui::AXPlatform::GetInstance().GetMode(), ui::kAXModeComplete);
 
   // Same test, but simulate accessibility API usage after the first
@@ -126,21 +120,18 @@
   state_->OnUserInputEvent();
 
   // Accessibility should still be enabled.
-  EXPECT_TRUE(state_->IsAccessibleBrowser());
   EXPECT_EQ(ui::AXPlatform::GetInstance().GetMode(), ui::kAXModeComplete);
 
   // Advance another 31 seconds and simulate another user input event;
   // now accessibility should be disabled.
   task_environment_.FastForwardBy(base::Seconds(31));
   state_->OnUserInputEvent();
-  EXPECT_FALSE(state_->IsAccessibleBrowser());
   EXPECT_EQ(ui::AXPlatform::GetInstance().GetMode(), ui::AXMode());
 }
 
 TEST_F(BrowserAccessibilityStateImplTest,
        AddAccessibilityModeFlagsPreventsAutoDisableAccessibility) {
   // Initially, accessibility should be disabled.
-  EXPECT_FALSE(state_->IsAccessibleBrowser());
   EXPECT_EQ(ui::AXPlatform::GetInstance().GetMode(), ui::AXMode());
 
   // Enable accessibility.
@@ -148,7 +139,6 @@
   // Indicate that an actual screen reader is not running (a screen reader
   // will prevent auto-disable from taking place).
   state_->SetScreenReaderAppActive(false);
-  EXPECT_TRUE(state_->IsAccessibleBrowser());
   EXPECT_EQ(ui::AXPlatform::GetInstance().GetMode(), ui::kAXModeComplete);
 
   // Send user input, wait 31 seconds, then send another user input event -
@@ -160,7 +150,6 @@
   state_->OnUserInputEvent();
 
   // Accessibility should still be enabled.
-  EXPECT_TRUE(state_->IsAccessibleBrowser());
   EXPECT_EQ(ui::AXPlatform::GetInstance().GetMode(), ui::kAXModeComplete);
 }
 
@@ -189,12 +178,10 @@
   ASSERT_NE(nullptr, ax_root);
 
   // Initially, accessibility should be disabled.
-  EXPECT_FALSE(state_->IsAccessibleBrowser());
   EXPECT_EQ(ui::AXPlatform::GetInstance().GetMode(), ui::AXMode());
 
   // Enable accessibility.
   state_->EnableProcessAccessibility();
-  EXPECT_TRUE(state_->IsAccessibleBrowser());
   EXPECT_EQ(ui::AXPlatform::GetInstance().GetMode(), ui::kAXModeComplete);
 
   // Send user input, wait 31 seconds, then send another user input event after
@@ -206,7 +193,6 @@
   state_->OnUserInputEvent();
 
   // Accessibility should still be enabled due to GetRole() being called.
-  EXPECT_TRUE(state_->IsAccessibleBrowser());
   EXPECT_EQ(ui::AXPlatform::GetInstance().GetMode(), ui::kAXModeComplete);
 }
 
@@ -222,7 +208,7 @@
 TEST_F(BrowserAccessibilityStateImplTest,
        EnablingAccessibilityTwiceSendsASingleNotification) {
   // Initially accessibility should be disabled.
-  EXPECT_FALSE(state_->IsAccessibleBrowser());
+  EXPECT_EQ(ui::AXPlatform::GetInstance().GetMode(), ui::AXMode());
 
   auto& ax_platform = ui::AXPlatform::GetInstance();
   ::testing::StrictMock<MockAXModeObserver> mock_observer;
diff --git a/content/browser/accessibility/dump_accessibility_browsertest_base.cc b/content/browser/accessibility/dump_accessibility_browsertest_base.cc
index 1f3b3329..cac38ff 100644
--- a/content/browser/accessibility/dump_accessibility_browsertest_base.cc
+++ b/content/browser/accessibility/dump_accessibility_browsertest_base.cc
@@ -425,7 +425,7 @@
 }
 
 void DumpAccessibilityTestBase::RunTestForPlatform(
-    ui::AXMode mode,
+    ui::AXMode ax_mode_for_test,
     const base::FilePath file_path,
     const char* file_dir,
     const base::FilePath::StringType& expectations_qualifier) {
@@ -438,6 +438,27 @@
   ui::AccessibilityState::ForceRespectDisplayedPasswordTextForTesting();
 #endif
 
+  // If there are unwanted AXMode flags already set, skip the test.
+  // TODO(crbug.com/371230119): This condition is mostly needed because the
+  // Android Automotive bot is enabling accessibility with kAXModeComplete,
+  // which causes form controls tests to fail, but it could also help prevent
+  // future failures where bots turn on the wrong flags for a test.
+  ui::AXMode initial_ax_mode =
+      BrowserAccessibilityState::GetInstance()->GetAccessibilityMode();
+  // Perform a bitwise AND between initial_ax_mode and the bitwise NOT of
+  // ax_mode_for_test. If the result is non-zero, it means there are flags set
+  // in initial_ax_mode that are NOT set in ax_mode_for_test.
+  ui::AXMode unwanted_mode_flags = ~ax_mode_for_test;
+  if ((initial_ax_mode & unwanted_mode_flags).is_mode_off() == false) {
+    // There were extra AXMode flags present, so the test cannot continue.
+    GTEST_SKIP() << "The initial AXMode contained more flags than the test is "
+                    "designed for."
+                 << "\n* Test requires: " << ax_mode_for_test
+                 << "\n* Initial AXMode: " << initial_ax_mode
+                 << "\n* Extra, unwanted flags: "
+                 << (initial_ax_mode & unwanted_mode_flags);
+  }
+
   // Normally some accessibility events that would be fired are suppressed or
   // delayed, depending on what has focus or the type of event. For testing,
   // we want all events to fire immediately to make tests predictable and not
@@ -478,17 +499,12 @@
       "/" + std::string(file_dir) + "/" + file_path.BaseName().MaybeAsASCII()));
   WebContentsImpl* web_contents = GetWebContents();
 
-  // Start with no AXMode, so that in case the test was run with
-  // --force-renderer-accessibility, we can still set the correct mode for the
-  // test, e.g. form controls mode.
-  BrowserAccessibilityState::GetInstance()->DisableProcessAccessibility();
-
   if (enable_accessibility_after_navigating_ &&
       web_contents->GetAccessibilityMode().is_mode_off()) {
     // Load the url, then enable accessibility.
     EXPECT_TRUE(NavigateToURL(shell(), url));
     AccessibilityNotificationWaiter accessibility_waiter(
-        web_contents, mode, ax::mojom::Event::kNone);
+        web_contents, ax_mode_for_test, ax::mojom::Event::kNone);
     static_cast<BrowserAccessibilityStateImpl*>(
         BrowserAccessibilityState::GetInstance())
         ->SetAXModeChangeAllowed(false);
@@ -497,7 +513,7 @@
     // Enable accessibility, then load the test html and wait for the
     // "load complete" AX event.
     AccessibilityNotificationWaiter accessibility_waiter(
-        web_contents, mode, ax::mojom::Event::kLoadComplete);
+        web_contents, ax_mode_for_test, ax::mojom::Event::kLoadComplete);
     static_cast<BrowserAccessibilityStateImpl*>(
         BrowserAccessibilityState::GetInstance())
         ->SetAXModeChangeAllowed(false);
@@ -507,10 +523,10 @@
     ASSERT_TRUE(accessibility_waiter.WaitForNotification());
   }
 
-  WaitForAllFramesLoaded(mode);
+  WaitForAllFramesLoaded(ax_mode_for_test);
 
   // Call the subclass to dump the output.
-  std::vector<std::string> actual_lines = Dump(mode);
+  std::vector<std::string> actual_lines = Dump(ax_mode_for_test);
 
   // Execute and wait for specified string
   for (const auto& function_name : scenario_.execute) {
@@ -526,7 +542,7 @@
       if (base::Contains(tree_dump, str)) {
         wait_for_string = false;
         // Append an additional dump if the specified string was found.
-        std::vector<std::string> additional_dump = Dump(mode);
+        std::vector<std::string> additional_dump = Dump(ax_mode_for_test);
         actual_lines.emplace_back("=== Start Continuation ===");
         actual_lines.insert(actual_lines.end(), additional_dump.begin(),
                             additional_dump.end());
diff --git a/content/browser/accessibility/dump_accessibility_tree_browsertest.cc b/content/browser/accessibility/dump_accessibility_tree_browsertest.cc
index 64a2bff..b26a18a 100644
--- a/content/browser/accessibility/dump_accessibility_tree_browsertest.cc
+++ b/content/browser/accessibility/dump_accessibility_tree_browsertest.cc
@@ -3583,20 +3583,6 @@
   RunFormControlsTest(FILE_PATH_LITERAL("role-group.html"));
 }
 
-IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest,
-                       AccessibilityRoleGroupFormControlsWithInitialFullA11y) {
-  // First turn on full a11y, including screen reader mode.
-  WebContentsImpl* web_contents = GetWebContents();
-  static_cast<WebContentsImpl*>(web_contents)
-      ->AddAccessibilityModeForTesting(ui::kAXModeComplete);
-  BrowserAccessibilityState::GetInstance()->AddAccessibilityModeFlags(
-      ui::kAXModeComplete);
-
-  // Ensure that a form controls test can still set form controls mode even
-  // if incompatible modes were set previously.
-  RunFormControlsTest(FILE_PATH_LITERAL("role-group.html"));
-}
-
 IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest, AccessibilityRuby) {
   RunHtmlTest(FILE_PATH_LITERAL("ruby.html"));
 }
diff --git a/content/browser/accessibility/web_contents_accessibility_android.cc b/content/browser/accessibility/web_contents_accessibility_android.cc
index 762602da..5b4ed0a 100644
--- a/content/browser/accessibility/web_contents_accessibility_android.cc
+++ b/content/browser/accessibility/web_contents_accessibility_android.cc
@@ -33,6 +33,7 @@
 #include "content/browser/android/render_widget_host_connector.h"
 #include "content/browser/renderer_host/render_widget_host_view_android.h"
 #include "content/browser/web_contents/web_contents_impl.h"
+#include "content/public/browser/scoped_accessibility_mode.h"
 #include "content/public/common/content_features.h"
 #include "net/base/data_url.h"
 #include "ui/accessibility/accessibility_features.h"
@@ -584,9 +585,7 @@
   ResetContentChangedEventsCounter();
 
   // Turn off accessibility on the renderer side by resetting the AXMode.
-  BrowserAccessibilityStateImpl* accessibility_state =
-      BrowserAccessibilityStateImpl::GetInstance();
-  accessibility_state->DisableProcessAccessibility();
+  scoped_accessibility_mode_.reset();
 }
 
 void WebContentsAccessibilityAndroid::ReEnableRendererAccessibility(
@@ -617,6 +616,41 @@
   }
 }
 
+void WebContentsAccessibilityAndroid::SetBrowserAXMode(
+    JNIEnv* env,
+    jboolean needs_full_engine,
+    jboolean is_form_controls_candidate,
+    jboolean is_screen_reader_running) {
+  BrowserAccessibilityStateImpl* accessibility_state =
+      BrowserAccessibilityStateImpl::GetInstance();
+  auto* accessibility_state_android =
+      static_cast<BrowserAccessibilityStateImplAndroid*>(accessibility_state);
+  accessibility_state_android->SetScreenReaderAppActive(
+      is_screen_reader_running);
+
+  ui::AXMode target_mode;
+  // Set the AXMode based on currently running services, sent from Java-side
+  // code and will fit into one of the below categories:
+  // 1. Screen reader -- |ui::kAXModeComplete|.
+  // 2. Performance filtering disallowed -- |ui::kAXModeComplete|.
+  // 2. Only password manager running -- |ui::kAXModeFormControls|
+  // 3. Some accessibility services running that need more information than a
+  //       password manager, but not as much as a screenreader -
+  //       |ui::kAXModeBasic|
+  if (needs_full_engine) {
+    target_mode = ui::kAXModeComplete;
+  } else if (!accessibility_state->IsPerformanceFilteringAllowed()) {
+    target_mode = ui::kAXModeComplete;
+  } else if (is_form_controls_candidate) {
+    target_mode = ui::kAXModeFormControls;
+  } else {
+    target_mode = ui::kAXModeBasic;
+  }
+
+  scoped_accessibility_mode_ =
+      accessibility_state->CreateScopedModeForProcess(target_mode);
+}
+
 jboolean WebContentsAccessibilityAndroid::IsRootManagerConnected(JNIEnv* env) {
   return !!GetRootBrowserAccessibilityManager();
 }
@@ -2208,75 +2242,4 @@
       env, obj, jassist_data_builder, web_contents));
 }
 
-void JNI_WebContentsAccessibilityImpl_SetBrowserAXMode(
-    JNIEnv* env,
-    const JavaParamRef<jobject>& obj,
-    jboolean is_screen_reader_enabled,
-    jboolean form_controls_mode,
-    jboolean is_known_screen_reader_running,
-    jboolean is_any_accessibility_tool_present) {
-  BrowserAccessibilityStateImpl* accessibility_state =
-      BrowserAccessibilityStateImpl::GetInstance();
-
-  // Always update state if a known screen reader is running.
-  auto* accessibility_state_android =
-      static_cast<BrowserAccessibilityStateImplAndroid*>(accessibility_state);
-  accessibility_state_android->SetScreenReaderAppActive(
-      is_known_screen_reader_running);
-
-  // The AXMode flags will be set according to requirements of the current
-  // system based on running services. This can be disabled with an enterprise
-  // policy, in which case accessibility becomes an all-or-none approach.
-  if (!accessibility_state->IsPerformanceFilteringAllowed()) {
-    // When the browser is not yet accessible, then set the AXMode to
-    // |ui::kAXModeComplete| for all web contents.
-    if (!accessibility_state->IsAccessibleBrowser()) {
-      accessibility_state->EnableProcessAccessibility();
-    }
-    return;
-  }
-
-  // Set the AXMode based on currently running services, sent from Java-side
-  // code and will fit into one of the below categories:
-  //
-  //    1. Screenreader running - |ui::kAXModeComplete|
-  //    2. Only password manager running - |ui::kAXModeFormControls|
-  //    3. Some accessibility services running that need more information than a
-  //       password manager, but not as much as a screenreader -
-  //       |ui::kAXModeBasic|
-  //
-  if (is_screen_reader_enabled) {
-    // Remove form controls filter mode to preserve screen reader mode.
-    ui::AXMode flags_to_remove(ui::AXMode::kNone,
-                               ui::AXMode::kFormsAndLabelsOnly);
-    accessibility_state->RemoveAccessibilityModeFlags(flags_to_remove);
-
-    accessibility_state->AddAccessibilityModeFlags(ui::kAXModeComplete);
-  } else if (form_controls_mode) {
-    // TODO (aldietz): Add a SetAccessibilityModeFlags method to
-    // BrowserAccessibilityState to add and remove flags atomically in one
-    // operation.
-    // Remove the mode flags present in kAXModeComplete but not in
-    // kAXModeFormControls, thereby reverting the mode to kAXModeFormControls
-    // while not touching any other flags.
-    ui::AXMode flags_to_remove(ui::kAXModeComplete.flags() &
-                               ~ui::kAXModeFormControls.flags());
-    accessibility_state->RemoveAccessibilityModeFlags(flags_to_remove);
-
-    // Add form controls filter mode.
-    accessibility_state->AddAccessibilityModeFlags(ui::kAXModeFormControls);
-  } else {
-    // Remove the mode flags present in kAXModeComplete and
-    // kFormsAndLabelsOnly but not in kAXModeBasic, thereby reverting
-    // the mode to kAXModeBasic while not touching any other flags.
-    ui::AXMode flags_to_remove(
-        ui::kAXModeComplete.flags() & ~ui::kAXModeBasic.flags(),
-        ui::AXMode::kFormsAndLabelsOnly);
-    accessibility_state->RemoveAccessibilityModeFlags(flags_to_remove);
-
-    // Add basic mode
-    accessibility_state->AddAccessibilityModeFlags(ui::kAXModeBasic);
-  }
-}
-
 }  // namespace content
diff --git a/content/browser/accessibility/web_contents_accessibility_android.h b/content/browser/accessibility/web_contents_accessibility_android.h
index 9d78ab1..c6d23e14 100644
--- a/content/browser/accessibility/web_contents_accessibility_android.h
+++ b/content/browser/accessibility/web_contents_accessibility_android.h
@@ -15,6 +15,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "content/browser/accessibility/web_contents_accessibility.h"
 #include "content/common/content_export.h"
+#include "content/public/browser/scoped_accessibility_mode.h"
 #include "ui/accessibility/platform/ax_node_id_delegate.h"
 #include "ui/gfx/geometry/size.h"
 
@@ -42,6 +43,7 @@
 class BrowserAccessibilityManagerAndroid;
 class WebContents;
 class WebContentsImpl;
+class ScopedAccessibilityMode;
 
 // Bridges BrowserAccessibilityManagerAndroid and Java WebContentsAccessibility.
 // A RenderWidgetHostConnector runs behind to manage the connection. Referenced
@@ -132,6 +134,13 @@
       JNIEnv* env,
       const base::android::JavaParamRef<jobject>& jweb_contents);
 
+  // This method turns on the renderer-side accessibility engine for this
+  // web contents.
+  void SetBrowserAXMode(JNIEnv* env,
+                        jboolean needs_full_engine,
+                        jboolean form_controls_mode,
+                        jboolean is_screen_reader_running);
+
   base::android::ScopedJavaLocalRef<jstring> GetSupportedHtmlElementTypes(
       JNIEnv* env);
 
@@ -457,6 +466,8 @@
   // this class is constructed with a ui::AXTreeUpdate.
   std::unique_ptr<BrowserAccessibilityManagerAndroid> snapshot_root_manager_;
 
+  std::unique_ptr<ScopedAccessibilityMode> scoped_accessibility_mode_;
+
   base::WeakPtrFactory<WebContentsAccessibilityAndroid> weak_ptr_factory_{this};
 };
 
diff --git a/content/browser/btm/btm_navigation_flow_detector.cc b/content/browser/btm/btm_navigation_flow_detector.cc
index 5acc9c7..cc3a16c 100644
--- a/content/browser/btm/btm_navigation_flow_detector.cc
+++ b/content/browser/btm/btm_navigation_flow_detector.cc
@@ -7,6 +7,8 @@
 #include "base/check.h"
 #include "base/rand_util.h"
 #include "content/browser/btm/btm_utils.h"
+#include "content/public/browser/cookie_access_details.h"
+#include "content/public/browser/navigation_handle.h"
 #include "content/public/browser/web_contents.h"
 #include "services/metrics/public/cpp/metrics_utils.h"
 #include "services/metrics/public/cpp/ukm_builders.h"
@@ -55,40 +57,38 @@
 // Looks for a redirect to the current page that qualifies as a server-redirect
 // exit from a suspected tracker flow (i.e., a single-hop server-side redirect)
 // and returns it, if one exists. Returns nullptr otherwise.
-const BtmServerRedirectInfo* GetEntrypointExitServerRedirect(
-    const BtmNavigationInfo& navigation) {
-  return navigation.server_redirects.size() == 1
-             ? &navigation.server_redirects.front()
-             : nullptr;
+const BtmRedirectInfo* GetEntrypointExitServerRedirect(
+    const BtmRedirectContext& redirect_context) {
+  base::span<const BtmRedirectInfoPtr> server_redirects =
+      redirect_context.GetServerRedirectsSinceLastPrimaryPageChange();
+  return server_redirects.size() == 1 ? server_redirects.front().get()
+                                      : nullptr;
 }
 
-// Matches true client redirects (HTML `<meta>` tag redirects, JavaScript
-// `window.location.replace` redirects), as well as navigations initiated by
-// page content without a user gesture.
-bool WasClientRedirectLike(const BtmNavigationInfo& navigation) {
-  return navigation.was_renderer_initiated && !navigation.was_user_initiated;
+const BtmRedirectInfo* GetFirstServerRedirect(
+    const BtmRedirectContext& redirect_context) {
+  base::span<const BtmRedirectInfoPtr> server_redirects =
+      redirect_context.GetServerRedirectsSinceLastPrimaryPageChange();
+  return server_redirects.empty() ? nullptr : server_redirects.front().get();
 }
 
 QuantityBucket GetCrossSiteRedirectQuantity(
     const std::string& initial_site,
-    const BtmNavigationInfo& navigation) {
-  std::string referring_site = initial_site;
+    base::span<const BtmRedirectInfoPtr> server_redirects,
+    const std::string& final_site) {
+  const std::string* referring_site = &initial_site;
   size_t num_cross_site_redirects = 0;
 
-  for (const auto& server_redirect : navigation.server_redirects) {
-    std::string redirector_site = GetSiteForBtm(server_redirect.url);
-    if (redirector_site != referring_site) {
+  for (const auto& server_redirect : server_redirects) {
+    if (server_redirect->site != *referring_site) {
       num_cross_site_redirects += 1;
       if (num_cross_site_redirects >= 2) {
         return QuantityBucket::kMultiple;
       }
-      referring_site.swap(redirector_site);
+      referring_site = &server_redirect->site;
     }
   }
-
-  const std::string destination_site =
-      GetSiteForBtm(navigation.destination.url);
-  if (destination_site != referring_site) {
+  if (final_site != *referring_site) {
     num_cross_site_redirects += 1;
   }
 
@@ -116,45 +116,68 @@
       .Record(ukm::UkmRecorder::Get());
 }
 
-void MaybeEmitDirectNavigationUkm(const BtmNavigationInfo& navigation) {
-  if (!IsPageTransitionDirectNavigation(navigation.page_transition)) {
+void MaybeEmitDirectNavigationUkm(NavigationHandle* navigation_handle,
+                                  const BtmRedirectContext& redirect_context) {
+  if (!IsPageTransitionDirectNavigation(
+          navigation_handle->GetPageTransition())) {
     return;
   }
 
-  ukm::SourceId source_id = navigation.server_redirects.empty()
-                                ? navigation.destination.source_id
-                                : navigation.server_redirects.front().source_id;
+  const BtmRedirectInfo* first_server_redirect =
+      GetFirstServerRedirect(redirect_context);
+  ukm::SourceId source_id =
+      first_server_redirect ? first_server_redirect->redirecting_url.source_id
+                            : navigation_handle->GetNextPageUkmSourceId();
 
   ukm::builders::DIPS_TrustIndicator_DirectNavigationV2(source_id)
       .SetNavigationSource(static_cast<int64_t>(
-          ToDirectNavigationSource(navigation.page_transition)))
+          ToDirectNavigationSource(navigation_handle->GetPageTransition())))
       .Record(ukm::UkmRecorder::Get());
 }
 }  // namespace
 
 namespace btm {
 
-EntrypointInfo::EntrypointInfo(
-    const BtmServerRedirectInfo& server_redirect_info,
-    bool was_referral_client_redirect_like)
-    : site(GetSiteForBtm(server_redirect_info.url)),
-      source_id(server_redirect_info.source_id),
-      had_triggering_storage_access(server_redirect_info.did_write_cookies),
-      was_referral_client_redirect(was_referral_client_redirect_like) {}
+PageVisitInfo::PageVisitInfo() {
+  site = "";
+  source_id = ukm::kInvalidSourceId;
+  did_page_access_cookies = false;
+  did_page_access_storage = false;
+  did_page_receive_user_activation = false;
+  did_page_have_successful_waa = false;
+  was_navigation_to_page_user_initiated = std::nullopt;
+  was_navigation_to_page_renderer_initiated = std::nullopt;
+}
 
-EntrypointInfo::EntrypointInfo(const BtmNavigationInfo& referral)
-    : site(GetSiteForBtm(referral.destination.url)),
-      source_id(referral.destination.source_id),
-      had_triggering_storage_access(false),
-      was_referral_client_redirect(WasClientRedirectLike(referral)) {}
+PageVisitInfo::PageVisitInfo(PageVisitInfo&& other) = default;
 
-EntrypointInfo::EntrypointInfo(const BtmNavigationInfo& referral,
-                               const BtmPageVisitInfo& entrypoint_visit)
-    : site(GetSiteForBtm(entrypoint_visit.url)),
-      source_id(entrypoint_visit.source_id),
+PageVisitInfo& PageVisitInfo::operator=(PageVisitInfo&& other) = default;
+
+bool PageVisitInfo::WasNavigationToPageClientRedirect() const {
+  return was_navigation_to_page_renderer_initiated.has_value() &&
+         *was_navigation_to_page_renderer_initiated &&
+         was_navigation_to_page_user_initiated.has_value() &&
+         !*was_navigation_to_page_user_initiated;
+}
+
+EntrypointInfo::EntrypointInfo(const BtmRedirectInfo& server_redirect_info,
+                               const btm::PageVisitInfo& exit_page_info)
+    : site(server_redirect_info.site),
+      source_id(server_redirect_info.redirecting_url.source_id),
       had_triggering_storage_access(
-          entrypoint_visit.had_qualifying_storage_access),
-      was_referral_client_redirect(WasClientRedirectLike(referral)) {}
+          server_redirect_info.access_type == BtmDataAccessType::kWrite ||
+          server_redirect_info.access_type == BtmDataAccessType::kReadWrite),
+      was_referral_client_redirect(
+          exit_page_info.WasNavigationToPageClientRedirect()) {}
+
+EntrypointInfo::EntrypointInfo(const btm::PageVisitInfo& client_redirector_info)
+    : site(client_redirector_info.site),
+      source_id(client_redirector_info.source_id),
+      had_triggering_storage_access(
+          client_redirector_info.did_page_access_storage ||
+          client_redirector_info.did_page_access_cookies),
+      was_referral_client_redirect(
+          client_redirector_info.WasNavigationToPageClientRedirect()) {}
 
 InFlowSuccessorInteractionState::InFlowSuccessorInteractionState(
     btm::EntrypointInfo flow_entrypoint)
@@ -188,60 +211,69 @@
 }  // namespace btm
 
 BtmNavigationFlowDetector::BtmNavigationFlowDetector(WebContents* web_contents)
-    : WebContentsUserData<BtmNavigationFlowDetector>(*web_contents),
-      page_visit_observer_(
-          web_contents,
-          base::BindRepeating(
-              &BtmNavigationFlowDetector::OnPageVisitReported,
-              // `base::Unretained()` is safe here because this class outlives
-              // `page_visit_observer_`, so `page_visit_observer_` won't run the
-              // callback after this class is destroyed.
-              base::Unretained(this))) {}
+    : WebContentsObserver(web_contents),
+      WebContentsUserData<BtmNavigationFlowDetector>(*web_contents),
+      current_page_visit_info_(btm::PageVisitInfo()) {
+  redirect_chain_observation_.Observe(
+      RedirectChainDetector::FromWebContents(web_contents));
+}
 
 BtmNavigationFlowDetector::~BtmNavigationFlowDetector() = default;
 
-void BtmNavigationFlowDetector::OnPageVisitReported(
-    const BtmPageVisitInfo& page_visit,
-    const BtmNavigationInfo& navigation) {
-  CHECK(!previous_page_to_current_page_.has_value() ||
-        previous_page_to_current_page_->destination.url == page_visit.url);
-
-  // Slide our sliding window by one report (page visit + navigation).
-  two_pages_ago_ = std::move(previous_page_);
-  two_pages_ago_to_previous_page_ = std::move(previous_page_to_current_page_);
-  previous_page_ = page_visit;
-  previous_page_to_current_page_ = navigation;
-
-  // Update IFSI tracking state based on the visit. To have all the information
-  // we need, we have to do this after the visit is reported but before we
-  // modify the IFSI flow state.
-  //
-  // Make sure in-visit storage accesses are propagated to IFSI tracking state
-  // entrypoints.
-  bool was_visit_for_successor_flow_entrypoint =
-      flow_status_ == btm::FlowStatus::kOngoing &&
-      successor_interaction_tracking_state_.has_value() &&
-      !successor_interaction_tracking_state_->IsAtSuccessor();
-  if (was_visit_for_successor_flow_entrypoint &&
-      previous_page_->had_qualifying_storage_access) {
-    successor_interaction_tracking_state_
-        ->RecordTriggeringStorageAccessByEntrypoint();
-  }
-  // Record any in-flow successor interactions.
-  if (previous_page_->received_user_activation &&
-      successor_interaction_tracking_state_.has_value() &&
-      successor_interaction_tracking_state_->IsAtSuccessor()) {
-    successor_interaction_tracking_state_
-        ->RecordSuccessorInteractionAtCurrentFlowIndex();
+void BtmNavigationFlowDetector::OnNavigationCommitted(
+    NavigationHandle* navigation_handle) {
+  bool primary_page_changed = navigation_handle->IsInPrimaryMainFrame() &&
+                              !navigation_handle->IsSameDocument() &&
+                              navigation_handle->HasCommitted();
+  if (!primary_page_changed) {
+    return;
   }
 
-  // Update in-flow successor interaction tracking state based on the flow
-  // status after this report, and maybe emit InFlowSuccessorInteraction UKM.
+  RenderFrameHost* render_frame_host =
+      navigation_handle->GetWebContents()->GetPrimaryMainFrame();
+
+  GURL current_page_url = render_frame_host->GetLastCommittedURL();
+  if (current_page_url == url::kAboutBlankURL) {
+    return;
+  }
+
+  bool is_first_page_load_in_tab = current_page_visit_info_->site.empty();
+
+  two_pages_ago_visit_info_ = std::move(previous_page_visit_info_);
+  previous_page_visit_info_ = std::move(current_page_visit_info_);
+  current_page_visit_info_.emplace();
+
+  current_page_visit_info_->url = current_page_url;
+  current_page_visit_info_->site = GetSiteForBtm(current_page_url);
+  current_page_visit_info_->source_id = render_frame_host->GetPageUkmSourceId();
+  current_page_visit_info_->was_navigation_to_page_renderer_initiated =
+      navigation_handle->IsRendererInitiated();
+  current_page_visit_info_->was_navigation_to_page_user_initiated =
+      !navigation_handle->IsRendererInitiated() ||
+      navigation_handle->HasUserGesture();
+  if (navigation_cookie_access_url_ == current_page_url) {
+    current_page_visit_info_->did_page_access_cookies = true;
+  }
+  navigation_cookie_access_url_ = std::nullopt;
+
+  base::Time now = clock_->Now();
+  if (!is_first_page_load_in_tab) {
+    int64_t raw_visit_duration_ms =
+        (now - last_page_change_time_).InMilliseconds();
+    bucketized_previous_page_visit_duration_ =
+        ukm::GetExponentialBucketMinForUserTiming(raw_visit_duration_ms);
+  }
+  last_page_change_time_ = now;
+
+  const BtmRedirectContext& redirect_context = GetRedirectContext();
+
   bool did_start_new_flow = MaybeInitializeSuccessorInteractionTrackingState();
+
   flow_status_ = FlowStatusAfterNavigation(did_start_new_flow);
   if (flow_status_ == btm::FlowStatus::kOngoing && !did_start_new_flow) {
     successor_interaction_tracking_state_->IncrementFlowIndex(
-        previous_page_to_current_page_->server_redirects.size() + 1);
+        redirect_context.GetServerRedirectsSinceLastPrimaryPageChange().size() +
+        1);
   }
   if (flow_status_ == btm::FlowStatus::kEnded) {
     MaybeEmitInFlowSuccessorInteraction();
@@ -250,15 +282,15 @@
     successor_interaction_tracking_state_.reset();
   }
 
-  MaybeEmitDirectNavigationUkm(previous_page_to_current_page_.value());
+  MaybeEmitDirectNavigationUkm(navigation_handle, redirect_context);
   MaybeEmitNavFlowNodeUkmForPreviousPage();
 
   int32_t flow_id = static_cast<int32_t>(base::RandUint64());
-  const BtmServerRedirectInfo* server_redirect_entrypoint_exit =
-      GetEntrypointExitServerRedirect(previous_page_to_current_page_.value());
+  const BtmRedirectInfo* server_redirect_entrypoint_exit =
+      GetEntrypointExitServerRedirect(redirect_context);
   if (server_redirect_entrypoint_exit != nullptr) {
     MaybeEmitSuspectedTrackerFlowUkmForServerRedirectExit(
-        *server_redirect_entrypoint_exit, flow_id);
+        server_redirect_entrypoint_exit, flow_id);
   } else {
     MaybeEmitSuspectedTrackerFlowUkmForClientRedirectExit(flow_id);
     MaybeEmitInFlowInteraction(flow_id);
@@ -270,71 +302,76 @@
     return;
   }
 
-  ukm::builders::DIPS_NavigationFlowNode(previous_page_->source_id)
-      .SetWerePreviousAndNextSiteSame(GetSiteForBtm(two_pages_ago_->url) ==
-                                      GetSiteForCurrentPage())
-      .SetDidHaveUserActivation(previous_page_->received_user_activation)
+  ukm::builders::DIPS_NavigationFlowNode(previous_page_visit_info_->source_id)
+      .SetWerePreviousAndNextSiteSame(two_pages_ago_visit_info_->site ==
+                                      current_page_visit_info_->site)
+      .SetDidHaveUserActivation(
+          previous_page_visit_info_->did_page_receive_user_activation)
       .SetDidHaveSuccessfulWAA(
-          previous_page_->had_successful_web_authn_assertion)
+          previous_page_visit_info_->did_page_have_successful_waa)
       .SetWereEntryAndExitRendererInitiated(
-          two_pages_ago_to_previous_page_->was_renderer_initiated &&
-          previous_page_to_current_page_->was_renderer_initiated)
+          *previous_page_visit_info_
+               ->was_navigation_to_page_renderer_initiated &&
+          *current_page_visit_info_->was_navigation_to_page_renderer_initiated)
       .SetWasEntryUserInitiated(
-          two_pages_ago_to_previous_page_->was_user_initiated)
+          *previous_page_visit_info_->was_navigation_to_page_user_initiated)
       .SetWasExitUserInitiated(
-          previous_page_to_current_page_->was_user_initiated)
-      .SetVisitDurationMilliseconds(ukm::GetExponentialBucketMinForUserTiming(
-          previous_page_->visit_duration.InMilliseconds()))
+          *current_page_visit_info_->was_navigation_to_page_user_initiated)
+      .SetVisitDurationMilliseconds(bucketized_previous_page_visit_duration_)
       .Record(ukm::UkmRecorder::Get());
 }
 
 bool BtmNavigationFlowDetector::CanEmitNavFlowNodeUkmForPreviousPage() const {
-  bool page_is_in_series_of_three =
-      two_pages_ago_.has_value() && !two_pages_ago_->url.is_empty();
+  bool page_is_in_series_of_three = two_pages_ago_visit_info_.has_value() &&
+                                    !two_pages_ago_visit_info_->site.empty() &&
+                                    previous_page_visit_info_.has_value() &&
+                                    !previous_page_visit_info_->site.empty() &&
+                                    current_page_visit_info_.has_value() &&
+                                    !current_page_visit_info_->site.empty();
   if (!page_is_in_series_of_three) {
     return false;
   }
 
-  CHECK(previous_page_.has_value() &&
-        previous_page_to_current_page_.has_value());
-
   bool page_has_valid_source_id =
-      previous_page_->source_id != ukm::kInvalidSourceId;
+      previous_page_visit_info_->source_id != ukm::kInvalidSourceId;
+  bool site_had_triggering_storage_access =
+      previous_page_visit_info_->did_page_access_cookies ||
+      previous_page_visit_info_->did_page_access_storage;
   bool is_site_different_from_prior_page =
-      GetSiteForBtm(previous_page_->url) != GetSiteForBtm(two_pages_ago_->url);
+      previous_page_visit_info_->site != two_pages_ago_visit_info_->site;
   bool is_site_different_from_next_page =
-      GetSiteForBtm(previous_page_->url) != GetSiteForCurrentPage();
+      previous_page_visit_info_->site != current_page_visit_info_->site;
 
-  return page_has_valid_source_id &&
-         previous_page_->had_qualifying_storage_access &&
+  return page_has_valid_source_id && site_had_triggering_storage_access &&
          is_site_different_from_prior_page && is_site_different_from_next_page;
 }
 
 void BtmNavigationFlowDetector::
     MaybeEmitSuspectedTrackerFlowUkmForServerRedirectExit(
-        const BtmServerRedirectInfo& exit_info,
+        const BtmRedirectInfo* exit_info,
         int32_t flow_id) {
   if (!CanEmitSuspectedTrackerFlowUkmForServerRedirectExit(exit_info)) {
     return;
   }
 
-  EmitSuspectedTrackerFlowUkm(previous_page_->source_id, exit_info.source_id,
-                              flow_id, BtmRedirectType::kServer);
+  EmitSuspectedTrackerFlowUkm(previous_page_visit_info_->source_id,
+                              exit_info->redirecting_url.source_id, flow_id,
+                              BtmRedirectType::kServer);
 }
 
 bool BtmNavigationFlowDetector::
     CanEmitSuspectedTrackerFlowUkmForServerRedirectExit(
-        const BtmServerRedirectInfo& exit_info) const {
-  if (!previous_page_.has_value() ||
-      !previous_page_to_current_page_.has_value()) {
+        const BtmRedirectInfo* exit_info) const {
+  if (!previous_page_visit_info_.has_value() || exit_info == nullptr ||
+      !current_page_visit_info_.has_value()) {
     return false;
   }
 
   btm::EntrypointInfo entrypoint_info_for_server_redirect_exit(
-      exit_info, WasClientRedirectLike(*previous_page_to_current_page_));
+      *exit_info, *current_page_visit_info_);
   return CanEmitSuspectedTrackerFlowUkm(
-      *previous_page_, entrypoint_info_for_server_redirect_exit,
-      GetSiteForCurrentPage());
+      *previous_page_visit_info_, entrypoint_info_for_server_redirect_exit,
+      *current_page_visit_info_);
 }
 
 void BtmNavigationFlowDetector::
@@ -343,44 +380,45 @@
     return;
   }
 
-  EmitSuspectedTrackerFlowUkm(two_pages_ago_->source_id,
-                              previous_page_->source_id, flow_id,
+  EmitSuspectedTrackerFlowUkm(two_pages_ago_visit_info_->source_id,
+                              previous_page_visit_info_->source_id, flow_id,
                               BtmRedirectType::kClient);
 }
 
 bool BtmNavigationFlowDetector::
     CanEmitSuspectedTrackerFlowUkmForClientRedirectExit() const {
-  bool page_is_in_series_of_three =
-      two_pages_ago_.has_value() && !two_pages_ago_->url.is_empty();
+  bool page_is_in_series_of_three = two_pages_ago_visit_info_.has_value() &&
+                                    previous_page_visit_info_.has_value() &&
+                                    current_page_visit_info_.has_value();
   if (!page_is_in_series_of_three) {
     return false;
   }
 
-  CHECK(previous_page_.has_value() &&
-        previous_page_to_current_page_.has_value());
-
-  if (!WasClientRedirectLike(*previous_page_to_current_page_)) {
+  std::optional<bool> is_exit_client_redirect =
+      current_page_visit_info_->WasNavigationToPageClientRedirect();
+  if (!is_exit_client_redirect.has_value() ||
+      !is_exit_client_redirect.value()) {
     return false;
   }
 
-  btm::EntrypointInfo entrypoint_info(*two_pages_ago_to_previous_page_,
-                                      *previous_page_);
-  return CanEmitSuspectedTrackerFlowUkm(two_pages_ago_.value(), entrypoint_info,
-                                        GetSiteForCurrentPage());
+  btm::EntrypointInfo entrypoint_info(previous_page_visit_info_.value());
+  return CanEmitSuspectedTrackerFlowUkm(two_pages_ago_visit_info_.value(),
+                                        entrypoint_info,
+                                        current_page_visit_info_.value());
 }
 
 bool BtmNavigationFlowDetector::CanEmitSuspectedTrackerFlowUkm(
-    const BtmPageVisitInfo& referrer_page_info,
+    const btm::PageVisitInfo& referrer_page_info,
     const btm::EntrypointInfo& entrypoint_info,
-    const std::string& exit_site) const {
+    const btm::PageVisitInfo& exit_page_info) const {
   bool referrer_has_valid_source_id =
       referrer_page_info.source_id != ukm::kInvalidSourceId;
   bool entrypoint_has_valid_source_id =
       entrypoint_info.source_id != ukm::kInvalidSourceId;
   bool is_entrypoint_site_different_from_referrer =
-      entrypoint_info.site != GetSiteForBtm(referrer_page_info.url);
+      entrypoint_info.site != referrer_page_info.site;
   bool is_entrypoint_site_different_from_exit_page =
-      entrypoint_info.site != exit_site;
+      entrypoint_info.site != exit_page_info.site;
 
   return referrer_has_valid_source_id && entrypoint_has_valid_source_id &&
          is_entrypoint_site_different_from_referrer &&
@@ -391,13 +429,12 @@
 
 void BtmNavigationFlowDetector::MaybeEmitInFlowInteraction(int32_t flow_id) {
   if (!CanEmitSuspectedTrackerFlowUkmForClientRedirectExit() ||
-      !two_pages_ago_to_previous_page_->server_redirects.empty() ||
-      !previous_page_->received_user_activation) {
+      !previous_page_visit_info_->did_page_receive_user_activation) {
     return;
   }
 
   ukm::builders::DIPS_TrustIndicator_InFlowInteractionV2(
-      previous_page_->source_id)
+      previous_page_visit_info_->source_id)
       .SetFlowId(flow_id)
       .Record(ukm::UkmRecorder::Get());
 }
@@ -423,30 +460,32 @@
 
 btm::FlowStatus BtmNavigationFlowDetector::FlowStatusAfterNavigation(
     bool did_most_recent_navigation_start_new_flow) const {
-  if (!WasClientRedirectLike(*previous_page_to_current_page_)) {
+  if (!current_page_visit_info_->WasNavigationToPageClientRedirect()) {
     return btm::FlowStatus::kInvalidated;
   }
   if (!successor_interaction_tracking_state_.has_value()) {
     return btm::FlowStatus::kInvalidated;
   }
 
+  const base::span<const BtmRedirectInfoPtr> server_redirects =
+      GetRedirectContext().GetServerRedirectsSinceLastPrimaryPageChange();
+
   if (did_most_recent_navigation_start_new_flow) {
-    bool is_still_on_entrypoint =
-        previous_page_to_current_page_->server_redirects.empty();
+    bool is_still_on_entrypoint = server_redirects.empty();
     if (is_still_on_entrypoint) {
       return btm::FlowStatus::kOngoing;
     }
 
-    bool are_entrypoint_and_current_page_same_site =
-        successor_interaction_tracking_state_->flow_entrypoint().site ==
-        GetSiteForCurrentPage();
-    return are_entrypoint_and_current_page_same_site ? btm::FlowStatus::kOngoing
-                                                     : btm::FlowStatus::kEnded;
+    return successor_interaction_tracking_state_->flow_entrypoint().site ==
+                   current_page_visit_info_->site
+               ? btm::FlowStatus::kOngoing
+               : btm::FlowStatus::kEnded;
   }
 
   QuantityBucket cross_site_redirect_quantity_bucket =
-      GetCrossSiteRedirectQuantity(GetSiteForBtm(previous_page_->url),
-                                   *previous_page_to_current_page_);
+      GetCrossSiteRedirectQuantity(previous_page_visit_info_->site,
+                                   server_redirects,
+                                   current_page_visit_info_->site);
   switch (cross_site_redirect_quantity_bucket) {
     case QuantityBucket::kZero:
       return btm::FlowStatus::kOngoing;
@@ -462,52 +501,165 @@
   if (flow_status_ == btm::FlowStatus::kOngoing) {
     return false;
   }
-  if (!previous_page_ || !previous_page_to_current_page_) {
+  if (!previous_page_visit_info_ || !current_page_visit_info_) {
     return false;
   }
-  if (!WasClientRedirectLike(*previous_page_to_current_page_)) {
+  if (!current_page_visit_info_->WasNavigationToPageClientRedirect()) {
     return false;
   }
 
   // Look for an entrypoint, which must either be the current page or the first
   // server redirect since the prior page.
 
-  const std::vector<BtmServerRedirectInfo>& server_redirects =
-      previous_page_to_current_page_->server_redirects;
+  base::span<const BtmRedirectInfoPtr> server_redirects =
+      GetRedirectContext().GetServerRedirectsSinceLastPrimaryPageChange();
   bool can_entrypoint_be_current_page = server_redirects.empty();
-  const std::string site_for_previous_page = GetSiteForBtm(previous_page_->url);
 
   if (can_entrypoint_be_current_page) {
-    if (site_for_previous_page != GetSiteForCurrentPage()) {
+    if (current_page_visit_info_->site != previous_page_visit_info_->site) {
       successor_interaction_tracking_state_.emplace(
-          btm::EntrypointInfo(*previous_page_to_current_page_));
+          btm::EntrypointInfo(*current_page_visit_info_));
       return true;
     }
     return false;
   }
 
-  const BtmServerRedirectInfo& possible_entrypoint = server_redirects.front();
-  if (GetSiteForBtm(possible_entrypoint.url) == site_for_previous_page) {
+  const BtmRedirectInfo* possible_entrypoint = server_redirects.front().get();
+  if (possible_entrypoint->site == previous_page_visit_info_->site) {
     return false;
   }
   bool had_cross_site_redirect_after_entrypoint =
-      GetCrossSiteRedirectQuantity(site_for_previous_page,
-                                   *previous_page_to_current_page_) ==
-      QuantityBucket::kMultiple;
+      GetCrossSiteRedirectQuantity(
+          previous_page_visit_info_->site, server_redirects,
+          current_page_visit_info_->site) == QuantityBucket::kMultiple;
   if (had_cross_site_redirect_after_entrypoint) {
     return false;
   }
 
-  successor_interaction_tracking_state_.emplace(btm::EntrypointInfo(
-      possible_entrypoint,
-      WasClientRedirectLike(*previous_page_to_current_page_)));
+  successor_interaction_tracking_state_.emplace(
+      btm::EntrypointInfo(*possible_entrypoint, *current_page_visit_info_));
   successor_interaction_tracking_state_->IncrementFlowIndex(
       server_redirects.size());
   return true;
 }
 
-const std::string BtmNavigationFlowDetector::GetSiteForCurrentPage() const {
-  return GetSiteForBtm(previous_page_to_current_page_->destination.url);
+const BtmRedirectContext& BtmNavigationFlowDetector::GetRedirectContext()
+    const {
+  return redirect_chain_observation_.GetSource()->CommittedRedirectContext();
+}
+
+void BtmNavigationFlowDetector::OnCookiesAccessed(
+    RenderFrameHost* render_frame_host,
+    const CookieAccessDetails& details) {
+  // Ignore notifications for prerenders, fenced frames, etc., and for blocked
+  // access attempts.
+  if (!btm::IsOrWasInPrimaryPage(*render_frame_host) ||
+      details.blocked_by_policy) {
+    return;
+  }
+  // Attribute accesses by iframes to the first-party page they're embedded in.
+  const GURL& first_party_url = GetFirstPartyURL(*render_frame_host);
+  // BTM is only turned on when non-CHIPS 3PCs are blocked, so
+  // mirror that behavior by ignoring non-CHIPS 3PC accesses.
+  if (!HasCHIPS(details.cookie_access_result_list) &&
+      !IsSameSiteForBtm(first_party_url, details.url)) {
+    return;
+  }
+
+  current_page_visit_info_->did_page_access_cookies = true;
+  if (flow_status_ == btm::FlowStatus::kOngoing &&
+      !successor_interaction_tracking_state_->IsAtSuccessor()) {
+    successor_interaction_tracking_state_
+        ->RecordTriggeringStorageAccessByEntrypoint();
+  }
+}
+
+void BtmNavigationFlowDetector::OnCookiesAccessed(
+    NavigationHandle* navigation_handle,
+    const CookieAccessDetails& details) {
+  // Ignore notifications for prerenders, fenced frames, etc., and for blocked
+  // access attempts.
+  if (!IsInPrimaryPage(*navigation_handle) || details.blocked_by_policy) {
+    return;
+  }
+
+  // Treat cookie accesses from iframe navigations as content-initiated.
+  if (IsInPrimaryPageIFrame(*navigation_handle)) {
+    const GURL& first_party_url = GetFirstPartyURL(*navigation_handle);
+
+    // BTM is only turned on when non-CHIPS 3PCs are blocked, so
+    // mirror that behavior by ignoring non-CHIPS 3PC accesses.
+    if (!HasCHIPS(details.cookie_access_result_list) &&
+        !IsSameSiteForBtm(first_party_url, details.url)) {
+      return;
+    }
+
+    current_page_visit_info_->did_page_access_cookies = true;
+    if (flow_status_ == btm::FlowStatus::kOngoing &&
+        !successor_interaction_tracking_state_->IsAtSuccessor()) {
+      successor_interaction_tracking_state_
+          ->RecordTriggeringStorageAccessByEntrypoint();
+    }
+    return;
+  }
+
+  // For accesses in main frame navigations, only count writes, as the browser
+  // sends cookies automatically and so sites have no control over whether they
+  // read cookies or not.
+  if (details.type != CookieOperation::kChange) {
+    return;
+  }
+
+  if (details.url == current_page_visit_info_->url) {
+    current_page_visit_info_->did_page_access_cookies = true;
+    if (flow_status_ == btm::FlowStatus::kOngoing &&
+        !successor_interaction_tracking_state_->IsAtSuccessor()) {
+      successor_interaction_tracking_state_
+          ->RecordTriggeringStorageAccessByEntrypoint();
+    }
+  } else {
+    // This notification might be for an in-progress navigation, so we should
+    // remember this notification until the next navigation finishes.
+    navigation_cookie_access_url_ = details.url;
+  }
+}
+
+void BtmNavigationFlowDetector::NotifyStorageAccessed(
+    RenderFrameHost* render_frame_host,
+    blink::mojom::StorageTypeAccessed storage_type,
+    bool blocked) {
+  if (!render_frame_host->IsInPrimaryMainFrame() || blocked) {
+    return;
+  }
+  current_page_visit_info_->did_page_access_storage = true;
+  if (flow_status_ == btm::FlowStatus::kOngoing &&
+      !successor_interaction_tracking_state_->IsAtSuccessor()) {
+    successor_interaction_tracking_state_
+        ->RecordTriggeringStorageAccessByEntrypoint();
+  }
+}
+
+void BtmNavigationFlowDetector::FrameReceivedUserActivation(
+    RenderFrameHost* render_frame_host) {
+  current_page_visit_info_->did_page_receive_user_activation = true;
+
+  if (successor_interaction_tracking_state_.has_value() &&
+      successor_interaction_tracking_state_->IsAtSuccessor()) {
+    successor_interaction_tracking_state_
+        ->RecordSuccessorInteractionAtCurrentFlowIndex();
+  }
+}
+
+void BtmNavigationFlowDetector::WebAuthnAssertionRequestSucceeded(
+    RenderFrameHost* render_frame_host) {
+  if (!render_frame_host->IsInPrimaryMainFrame()) {
+    return;
+  }
+  current_page_visit_info_->did_page_have_successful_waa = true;
+}
+
+void BtmNavigationFlowDetector::WebContentsDestroyed() {
+  redirect_chain_observation_.Reset();
 }
 
 WEB_CONTENTS_USER_DATA_KEY_IMPL(BtmNavigationFlowDetector);
diff --git a/content/browser/btm/btm_navigation_flow_detector.h b/content/browser/btm/btm_navigation_flow_detector.h
index 6387b88..1091843 100644
--- a/content/browser/btm/btm_navigation_flow_detector.h
+++ b/content/browser/btm/btm_navigation_flow_detector.h
@@ -8,16 +8,27 @@
 #include <optional>
 #include <string>
 
+#include "base/memory/raw_ptr.h"
+#include "base/memory/raw_ref.h"
 #include "base/memory/weak_ptr.h"
+#include "base/scoped_observation.h"
 #include "base/time/clock.h"
-#include "content/browser/btm/btm_page_visit_observer.h"
+#include "base/time/default_clock.h"
+#include "base/time/time.h"
+#include "content/browser/btm/btm_bounce_detector.h"
 #include "content/common/content_export.h"
+#include "content/public/browser/web_contents_observer.h"
 #include "content/public/browser/web_contents_user_data.h"
 #include "services/metrics/public/cpp/ukm_source_id.h"
+#include "third_party/blink/public/mojom/frame/frame.mojom-forward.h"
 #include "url/gurl.h"
 
 namespace content {
 
+struct CookieAccessDetails;
+class NavigationHandle;
+class RenderFrameHost;
+
 namespace btm {
 
 // Should match DIPSDirectNavigationSource in tools/metrics/histograms/enums.xml
@@ -27,17 +38,31 @@
   kBookmark = 2,
 };
 
+struct PageVisitInfo {
+  PageVisitInfo();
+  PageVisitInfo(PageVisitInfo&& other);
+
+  PageVisitInfo& operator=(PageVisitInfo&& other);
+
+  GURL url;
+  std::string site;
+  ukm::SourceId source_id;
+  bool did_page_access_cookies;
+  bool did_page_access_storage;
+  bool did_page_receive_user_activation;
+  bool did_page_have_successful_waa;
+  std::optional<bool> was_navigation_to_page_renderer_initiated;
+  std::optional<bool> was_navigation_to_page_user_initiated;
+
+  bool WasNavigationToPageClientRedirect() const;
+};
+
 struct EntrypointInfo {
   // Used when the entrypoint has a server redirect exit.
-  explicit EntrypointInfo(const BtmServerRedirectInfo& server_redirect_info,
-                          bool was_referral_client_redirect_like);
-  // Used when the entrypoint has a client redirect(-like) exit, when the page
-  // visit has already been reported.
-  explicit EntrypointInfo(const BtmNavigationInfo& referral,
-                          const BtmPageVisitInfo& entrypoint_visit);
-  // Used when the entrypoint has a client redirect(-like) exit, when the
-  // EntrypointInfo needs to be created before the page visit is reported.
-  explicit EntrypointInfo(const BtmNavigationInfo& referral);
+  explicit EntrypointInfo(const BtmRedirectInfo& server_redirect_info,
+                          const btm::PageVisitInfo& exit_page_info);
+  // Used when the entrypoint has a client redirect exit.
+  explicit EntrypointInfo(const btm::PageVisitInfo& client_redirector_info);
 
   const std::string site;
   ukm::SourceId source_id;
@@ -88,12 +113,15 @@
 // Currently only reports UKM to inform how we might identify possible
 // navigational tracking by sites that also perform user-interest activity.
 class CONTENT_EXPORT BtmNavigationFlowDetector
-    : public WebContentsUserData<BtmNavigationFlowDetector> {
+    : public RedirectChainDetector::Observer,
+      public WebContentsObserver,
+      public WebContentsUserData<BtmNavigationFlowDetector> {
  public:
   ~BtmNavigationFlowDetector() override;
 
   void SetClockForTesting(base::Clock* clock) {
-    page_visit_observer_.SetClockForTesting(clock);
+    CHECK(clock);
+    clock_ = *clock;
   }
 
  protected:
@@ -106,10 +134,10 @@
   // Records events for flows we suspect include a tracker and have a server
   // redirect.
   void MaybeEmitSuspectedTrackerFlowUkmForServerRedirectExit(
-      const BtmServerRedirectInfo& exit_info,
+      const BtmRedirectInfo* exit_info,
       int32_t flow_id);
   bool CanEmitSuspectedTrackerFlowUkmForServerRedirectExit(
-      const BtmServerRedirectInfo& exit_info) const;
+      const BtmRedirectInfo* exit_info) const;
 
   // Records events for flows we suspect include a tracker and have a client
   // redirect.
@@ -117,9 +145,9 @@
   bool CanEmitSuspectedTrackerFlowUkmForClientRedirectExit() const;
 
   bool CanEmitSuspectedTrackerFlowUkm(
-      const BtmPageVisitInfo& referrer_page_info,
+      const btm::PageVisitInfo& referrer_page_info,
       const btm::EntrypointInfo& entrypoint_info,
-      const std::string& exit_site) const;
+      const btm::PageVisitInfo& exit_page_info) const;
 
   // Records an event for flows where there was a user interaction in between,
   // i.e. for flow A->B->C, there was a user interaction on B. This could be
@@ -138,42 +166,48 @@
   // So WebContentsUserData::CreateForWebContents can call the constructor.
   friend class WebContentsUserData<BtmNavigationFlowDetector>;
 
-  // Callback to be called by `BtmPageVisitObserver`.
-  void OnPageVisitReported(const BtmPageVisitInfo& page_visit,
-                           const BtmNavigationInfo& navigation);
-
   btm::FlowStatus FlowStatusAfterNavigation(
       bool did_most_recent_navigation_start_new_flow) const;
   // Returns whether the entrypoint was set or not.
   bool MaybeInitializeSuccessorInteractionTrackingState();
+  void ResetSuccessorInteractionTrackingState();
 
-  // Must be called only when `previous_page_to_current_page_` is populated.
-  const std::string GetSiteForCurrentPage() const;
+  const BtmRedirectContext& GetRedirectContext() const;
+
+  // start WebContentsObserver overrides
+  // For client-initiated cookie accesses, and late-reported cookie accesses in
+  // navigations.
+  void OnCookiesAccessed(RenderFrameHost* render_frame_host,
+                         const CookieAccessDetails& details) override;
+  // For cookie accesses in navigations.
+  void OnCookiesAccessed(NavigationHandle* navigation_handle,
+                         const CookieAccessDetails& details) override;
+  void NotifyStorageAccessed(RenderFrameHost* render_frame_host,
+                             blink::mojom::StorageTypeAccessed storage_type,
+                             bool blocked) override;
+  void FrameReceivedUserActivation(RenderFrameHost* render_frame_host) override;
+  void WebAuthnAssertionRequestSucceeded(
+      RenderFrameHost* render_frame_host) override;
+  void WebContentsDestroyed() override;
+  // end WebContentsObserver overrides
+
+  // start RedirectChainDetector::Observer overrides
+  void OnNavigationCommitted(NavigationHandle* navigation_handle) override;
+  // end RedirectChainDetector::Observer overrides
 
   // Navigation Flow:
   // A navigation flow consists of three navigations in a tab (A->B->C).
-  // The infos below are updated when the primary page changes.
+  // The infos below correspond to A, B, and C, respectively and are updated
+  // when a new primary main frame navigation commits.
   //
   // Note that server redirects don't commit, so if there's a server redirect
-  // from B->C, B is not committed and not reported as a page visit, but instead
-  // in the `server_redirects` field of the corresponding `BtmNavigationInfo`.
-  // In this case, `previous_page_` corresponds to A,
-  // `previous_page_to_current_page_->server_redirects` will contain B, and
-  // `previous_page_to_current_page_->destination` will have some limited
-  // information about C.
-
-  // In a series of three committed pages A->B->C, contains information about
-  // the visit on A.
-  std::optional<BtmPageVisitInfo> two_pages_ago_;
-  // In a series of three committed pages A->B->C, contains information about
-  // the navigation A->B.
-  std::optional<BtmNavigationInfo> two_pages_ago_to_previous_page_;
-  // In a series of three committed pages A->B->C, contains information about
-  // the visit on B.
-  std::optional<BtmPageVisitInfo> previous_page_;
-  // In a series of three committed pages A->B->C, contains information about
-  // the navigation B->C.
-  std::optional<BtmNavigationInfo> previous_page_to_current_page_;
+  // from B->C, the navigation to B is not committed and we need to retrieve B's
+  // information by other means i.e. using BtmRedirectContext. In this case,
+  // `previous_page_visit_info_` corresponds to A and `current_page_visit_info_`
+  // corresponds to C.
+  std::optional<btm::PageVisitInfo> two_pages_ago_visit_info_;
+  std::optional<btm::PageVisitInfo> previous_page_visit_info_;
+  std::optional<btm::PageVisitInfo> current_page_visit_info_;
 
   // The status of a flow for the purposes of InFlowSuccessorInteraction, after
   // the most recent primary page change.
@@ -184,7 +218,20 @@
   std::optional<btm::InFlowSuccessorInteractionState>
       successor_interaction_tracking_state_;
 
-  BtmPageVisitObserver page_visit_observer_;
+  // Tracks a navigational cookie access notification that is received before
+  // the navigation finishes.
+  std::optional<GURL> navigation_cookie_access_url_;
+
+  base::Time last_page_change_time_;
+  long bucketized_previous_page_visit_duration_;
+
+  base::ScopedObservation<RedirectChainDetector,
+                          RedirectChainDetector::Observer>
+      redirect_chain_observation_{this};
+
+  raw_ref<base::Clock> clock_{*base::DefaultClock::GetInstance()};
+
+  base::WeakPtrFactory<BtmNavigationFlowDetector> weak_factory_{this};
 
   WEB_CONTENTS_USER_DATA_KEY_DECL();
 };
diff --git a/content/browser/btm/btm_navigation_flow_detector_browsertest.cc b/content/browser/btm/btm_navigation_flow_detector_browsertest.cc
index ce8089d..480b7b7 100644
--- a/content/browser/btm/btm_navigation_flow_detector_browsertest.cc
+++ b/content/browser/btm/btm_navigation_flow_detector_browsertest.cc
@@ -519,8 +519,16 @@
   // Implied assert: no DirectNavigation UKM entry for link_target_url.
 }
 
+// TODO - crbug.com/388718419: Flaky on release builds
+#if defined(NDEBUG)
+#define MAYBE_SuspectedTrackerFlowEmittedForServerRedirectExit \
+  DISABLED_SuspectedTrackerFlowEmittedForServerRedirectExit
+#else
+#define MAYBE_SuspectedTrackerFlowEmittedForServerRedirectExit \
+  SuspectedTrackerFlowEmittedForServerRedirectExit
+#endif
 IN_PROC_BROWSER_TEST_F(BtmNavigationFlowDetectorTest,
-                       SuspectedTrackerFlowEmittedForServerRedirectExit) {
+                       MAYBE_SuspectedTrackerFlowEmittedForServerRedirectExit) {
   // Visit A.
   WebContents* web_contents = GetActiveWebContents();
   GURL referrer_url =
@@ -563,9 +571,17 @@
   ExpectNoUkmEventsOfType(kInFlowInteractionUkmEventName);
 }
 
+// TODO - crbug.com/388718419: Flaky on release builds
+#if defined(NDEBUG)
+#define MAYBE_SuspectedTrackerFlowEmittedForServerRedirectExitConsecutiveEvents \
+  DISABLED_SuspectedTrackerFlowEmittedForServerRedirectExitConsecutiveEvents
+#else
+#define MAYBE_SuspectedTrackerFlowEmittedForServerRedirectExitConsecutiveEvents \
+  SuspectedTrackerFlowEmittedForServerRedirectExitConsecutiveEvents
+#endif
 IN_PROC_BROWSER_TEST_F(
     BtmNavigationFlowDetectorTest,
-    SuspectedTrackerFlowEmittedForServerRedirectExitConsecutiveEvents) {
+    MAYBE_SuspectedTrackerFlowEmittedForServerRedirectExitConsecutiveEvents) {
   // Visit A.
   WebContents* web_contents = GetActiveWebContents();
   GURL referrer_url =
@@ -1168,13 +1184,6 @@
       embedded_https_test_server_.GetURL(kSiteB, "/title1.html");
   ASSERT_TRUE(NavigateToURLFromRendererWithoutUserGesture(
       web_contents, first_entrypoint_url));
-  // Access cookies during the visit on the entrypoint.
-  RenderFrameHost* frame = web_contents->GetPrimaryMainFrame();
-  FrameCookieAccessObserver observer(web_contents, frame,
-                                     CookieOperation::kChange);
-  ASSERT_TRUE(ExecJs(frame, "document.cookie = 'name=value;';",
-                     EXECUTE_SCRIPT_NO_USER_GESTURE));
-  observer.Wait();
   // Client-redirect to another page on B, the successor for the first flow, and
   // interact with the page.
   GURL first_successor_url =
@@ -1225,23 +1234,21 @@
   auto ukm_entries =
       ukm_recorder().GetEntriesByName(kInFlowSuccessorInteractionUkmEventName);
   ASSERT_EQ(ukm_entries.size(), 2u);
-
   auto first_ukm_entry = ukm_entries.at(0);
   ukm_recorder().ExpectEntrySourceHasUrl(first_ukm_entry, first_entrypoint_url);
   ukm_recorder().ExpectEntryMetric(first_ukm_entry, "SuccessorRedirectIndex",
                                    1);
-  // In-visit storage access should be reported.
   ukm_recorder().ExpectEntryMetric(first_ukm_entry,
-                                   "DidEntrypointAccessStorage", true);
-
+                                   "DidEntrypointAccessStorage", false);
   auto second_ukm_entry = ukm_entries.at(1);
   ukm_recorder().ExpectEntrySourceHasUrl(second_ukm_entry,
                                          second_entrypoint_url);
   ukm_recorder().ExpectEntryMetric(second_ukm_entry, "SuccessorRedirectIndex",
                                    3);
-  // Navigational storage access should be reported.
-  ukm_recorder().ExpectEntryMetric(second_ukm_entry,
-                                   "DidEntrypointAccessStorage", true);
+  // TODO - crbug.com/388718419: Uncomment this assertion — currently flaky on
+  // release builds.
+  // ukm_recorder().ExpectEntryMetric(second_ukm_entry,
+  //                                  "DidEntrypointAccessStorage", true);
 }
 
 IN_PROC_BROWSER_TEST_F(BtmNavigationFlowDetectorTest,
@@ -1428,8 +1435,16 @@
   ExpectNoNavigationFlowNodeUkmEvents();
 }
 
-IN_PROC_BROWSER_TEST_F(BtmNavigationFlowDetectorTest,
-                       NavigationFlowNodeNotEmittedWhenCookiesReadViaHeaders) {
+#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC)
+#define MAYBE_NavigationFlowNodeNotEmittedWhenCookiesReadViaHeaders \
+  DISABLED_NavigationFlowNodeNotEmittedWhenCookiesReadViaHeaders
+#else
+#define MAYBE_NavigationFlowNodeNotEmittedWhenCookiesReadViaHeaders \
+  NavigationFlowNodeNotEmittedWhenCookiesReadViaHeaders
+#endif
+IN_PROC_BROWSER_TEST_F(
+    BtmNavigationFlowDetectorTest,
+    MAYBE_NavigationFlowNodeNotEmittedWhenCookiesReadViaHeaders) {
   // Pre-write a cookie for site B so it can be passed in request headers later.
   WebContents* web_contents = GetActiveWebContents();
   ASSERT_TRUE(
diff --git a/content/browser/btm/btm_page_visit_observer.cc b/content/browser/btm/btm_page_visit_observer.cc
index b91f7a6..f90e17f 100644
--- a/content/browser/btm/btm_page_visit_observer.cc
+++ b/content/browser/btm/btm_page_visit_observer.cc
@@ -70,10 +70,8 @@
     filter_.AddAccess(url, op);
   }
 
-  // Idempotent for multiple calls with the same value of
-  // `redirect_chain_index`.
   void RecordServerRedirectAtChainIndex(size_t redirect_chain_index) {
-    server_redirect_chain_indices_.insert(redirect_chain_index);
+    server_redirect_chain_indices_.push_back(redirect_chain_index);
   }
 
   // Returns the navigation info paired with the cookie access of the final
@@ -96,13 +94,12 @@
     // recorded an access type for.
     urls.push_back(navigation_handle.GetURL());
     CHECK(filter_.Filter(urls, accesses));
-    int i = 0;
-    for (const size_t redirect_chain_index : server_redirect_chain_indices_) {
+    for (size_t i = 0; i < server_redirect_chain_indices_.size(); ++i) {
       navigation.server_redirects.emplace_back(
           urls[i],
-          btm::GetRedirectSourceId(&navigation_handle, redirect_chain_index),
+          btm::GetRedirectSourceId(&navigation_handle,
+                                   server_redirect_chain_indices_[i]),
           IsWrite(accesses[i]));
-      i += 1;
     }
 
     BtmDataAccessType committed_url_access_type = accesses.back();
@@ -114,11 +111,7 @@
 
  private:
   CookieAccessFilter filter_;
-  // This is a set instead of a vector because there can be multiple callers
-  // recording server redirects per instance of `NavigationState`, and we
-  // therefore need repeated recordings of the same server redirect to be
-  // idempotent.
-  std::set<size_t> server_redirect_chain_indices_;
+  std::vector<size_t> server_redirect_chain_indices_;
 };
 
 NAVIGATION_HANDLE_USER_DATA_KEY_IMPL(NavigationState);
diff --git a/content/browser/btm/btm_page_visit_observer.h b/content/browser/btm/btm_page_visit_observer.h
index 538e89a8..8a85103 100644
--- a/content/browser/btm/btm_page_visit_observer.h
+++ b/content/browser/btm/btm_page_visit_observer.h
@@ -87,8 +87,6 @@
   void WebAuthnAssertionRequestSucceeded(
       RenderFrameHost* render_frame_host) override;
 
-  void SetClockForTesting(base::Clock* clock) { clock_ = CHECK_DEREF(clock); }
-
  private:
   // Execute the visit callback with a tuple from the pending queue.
   void ReportVisit();
diff --git a/content/browser/installedapp/installed_app_provider_impl.cc b/content/browser/installedapp/installed_app_provider_impl.cc
index a306943..4c0e69d 100644
--- a/content/browser/installedapp/installed_app_provider_impl.cc
+++ b/content/browser/installedapp/installed_app_provider_impl.cc
@@ -75,11 +75,14 @@
   if (add_saved_related_applications) {
     WebContents* web_contents =
         WebContents::FromRenderFrameHost(&render_frame_host());
-    std::vector<blink::mojom::RelatedApplicationPtr> saved_related_apps =
-        web_contents->GetDelegate()->GetSavedRelatedApplications(web_contents);
-    related_apps.insert(related_apps.end(),
-                        std::make_move_iterator(saved_related_apps.begin()),
-                        std::make_move_iterator(saved_related_apps.end()));
+    WebContentsDelegate* delegate = web_contents->GetDelegate();
+    if (delegate) {
+      std::vector<blink::mojom::RelatedApplicationPtr> saved_related_apps =
+          delegate->GetSavedRelatedApplications(web_contents);
+      related_apps.insert(related_apps.end(),
+                          std::make_move_iterator(saved_related_apps.begin()),
+                          std::make_move_iterator(saved_related_apps.end()));
+    }
   }
 
   if (related_apps.size() > kMaxNumberOfQueriedApps) {
diff --git a/content/browser/interest_group/ad_auction_service_impl.cc b/content/browser/interest_group/ad_auction_service_impl.cc
index 8f45a99..7d79b770 100644
--- a/content/browser/interest_group/ad_auction_service_impl.cc
+++ b/content/browser/interest_group/ad_auction_service_impl.cc
@@ -1169,10 +1169,14 @@
     return;
   }
 
+  std::vector<url::Origin> seller_origins;
+  for (const auto& [seller, _] : state.sellers) {
+    seller_origins.push_back(seller);
+  }
   GetInterestGroupManager().GetInterestGroupAdAuctionData(
       GetTopWindowOrigin(),
       /* generation_id=*/base::Uuid::GenerateRandomV4(), state.timestamp,
-      std::move(state.config),
+      std::move(state.config), std::move(seller_origins),
       base::BindOnce(&AdAuctionServiceImpl::OnGotAuctionData,
                      weak_ptr_factory_.GetWeakPtr(), state.request_id));
 
@@ -1250,7 +1254,7 @@
   BiddingAndAuctionDataConstructionState& state = ba_data_callbacks_.front();
   DCHECK(state.data);
 
-  if (state.data->request.empty()) {
+  if (state.data->requests[seller].empty()) {
     AddEmptyGetInterestGroupAdAuctionDataRequest(seller, "");
     return;
   }
@@ -1266,7 +1270,8 @@
 
   auto maybe_request =
       quiche::ObliviousHttpRequest::CreateClientObliviousRequest(
-          std::string(state.data->request.begin(), state.data->request.end()),
+          std::string(state.data->requests[seller].begin(),
+                      state.data->requests[seller].end()),
           ba_key.key, maybe_key_config.value(),
           kBiddingAndAuctionEncryptionRequestMediaType);
   if (!maybe_request.ok()) {
diff --git a/content/browser/interest_group/ad_auction_service_impl_unittest.cc b/content/browser/interest_group/ad_auction_service_impl_unittest.cc
index 3203b7d..5cd36eb 100644
--- a/content/browser/interest_group/ad_auction_service_impl_unittest.cc
+++ b/content/browser/interest_group/ad_auction_service_impl_unittest.cc
@@ -24,7 +24,7 @@
 #include "base/features.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback.h"
-#include "base/json/json_string_value_serializer.h"
+#include "base/json/json_writer.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/raw_ref.h"
 #include "base/memory/scoped_refptr.h"
@@ -166,10 +166,7 @@
   base::Value::Dict outer;
   outer.Set("keys", std::move(keys));
 
-  std::string json_output;
-  JSONStringValueSerializer serializer(&json_output);
-  serializer.Serialize(outer);
-  return json_output;
+  return base::WriteJson(outer).value_or(std::string());
 }
 
 // Returns a basic bidder script that sends reports to
@@ -11291,8 +11288,10 @@
       base::Uuid::ParseCaseInsensitive("00000000-0000-0000-0000-000000000000"),
       /*timestamp=*/base::Time::FromMillisecondsSinceUnixEpoch(10),
       /*config=*/blink::mojom::AuctionDataConfig::New(),
+      /*sellers=*/{test_origin},
       /*callback=*/base::BindLambdaForTesting([&](BiddingAndAuctionData data) {
-        msg = std::move(data.request);
+        EXPECT_EQ(1u, data.requests.size());
+        msg = std::move(data.requests[test_origin]);
         group_names = std::move(data.group_names);
         run_loop.Quit();
       }));
@@ -11311,6 +11310,85 @@
                                test_origin, testing::ElementsAre("cars"))));
 }
 
+// Test that interest_group_manager serialize the blob correctly, for multiple
+// sellers. Similar to SerializesAuctionBlob.
+TEST_F(AdAuctionServiceImplTest, SerializesAuctionBlobMultiSellers) {
+  url::Origin test_origin = url::Origin::Create(GURL(kOriginStringA));
+  url::Origin test_origin_b = url::Origin::Create(GURL(kOriginStringB));
+  manager_->JoinInterestGroup(
+      blink::TestInterestGroupBuilder(test_origin, "cars")
+          .SetTrustedBiddingSignalsKeys({{"key1", "key2"}})
+          .SetUserBiddingSignals("{}")
+          .SetAds(
+              {{{GURL("https://c.test/ad.html"), /*metadata=*/"do not send",
+                 /*size_group=*/std::nullopt,
+                 /*buyer_reporting_id=*/std::nullopt,
+                 /*buyer_and_seller_reporting_id=*/std::nullopt,
+                 /*selectable_buyer_and_seller_reporting_ids=*/std::nullopt,
+                 "1234"},
+                {GURL("https://c.test/ad2.html"), /*metadata=*/std::nullopt},
+                {GURL("https://c.test/ad3.html"), /*metadata=*/std::nullopt,
+                 /*size_group=*/std::nullopt,
+                 /*buyer_reporting_id=*/std::nullopt,
+                 /*buyer_and_seller_reporting_id=*/std::nullopt,
+                 /*selectable_buyer_and_seller_reporting_ids=*/std::nullopt,
+                 "456"}}})
+          .SetAdComponents(
+              {{{GURL("https://c.test/ad4.html"), /*metadata=*/std::nullopt,
+                 /*size_group=*/std::nullopt,
+                 /*buyer_reporting_id=*/std::nullopt,
+                 /*buyer_and_seller_reporting_id=*/std::nullopt,
+                 /*selectable_buyer_and_seller_reporting_ids=*/std::nullopt,
+                 "789"}}})
+          .Build(),
+      GURL("https://a.test/example.html"));
+  task_environment()->FastForwardBy(base::Seconds(1));
+  manager_->RecordInterestGroupWin(
+      {test_origin, "cars"},
+      R"({"renderURL": "https://c.test/ad.html", "adRenderId": "1234"})");
+  task_environment()->FastForwardBy(base::Seconds(1));
+  manager_->RecordInterestGroupWin(
+      {test_origin, "cars"}, R"({"renderURL": "https://c.test/ad2.html"})");
+  task_environment()->FastForwardBy(base::Seconds(1));
+  manager_->RecordInterestGroupWin({test_origin, "cars"},
+                                   R"({"renderURL": "corrupt JSON)");
+  task_environment()->FastForwardBy(base::Seconds(1));
+
+  std::vector<uint8_t> msg;
+  std::vector<uint8_t> msg_b;
+  base::flat_map<url::Origin, std::vector<std::string>> group_names;
+  base::RunLoop run_loop;
+  manager_->GetInterestGroupAdAuctionData(
+      /*top_level_origin=*/test_origin,
+      /*generation_id=*/
+      base::Uuid::ParseCaseInsensitive("00000000-0000-0000-0000-000000000000"),
+      /*timestamp=*/base::Time::FromMillisecondsSinceUnixEpoch(10),
+      /*config=*/blink::mojom::AuctionDataConfig::New(),
+      /*sellers=*/{test_origin, test_origin_b},
+      /*callback=*/base::BindLambdaForTesting([&](BiddingAndAuctionData data) {
+        EXPECT_EQ(2u, data.requests.size());
+        msg = std::move(data.requests[test_origin]);
+        msg_b = std::move(data.requests[test_origin_b]);
+        group_names = std::move(data.group_names);
+        run_loop.Quit();
+      }));
+  run_loop.Run();
+  std::string expected =
+      "AgAAAS6mZ3ZlcnNpb24AaXB1Ymxpc2hlcmZhLnRlc3RsZ2VuZXJhdGlvbklkeCQwMDAwMDAw"
+      "MC0wMDAwLTAwMDAtMDAwMC0wMDAwMDAwMDAwMDBuaW50ZXJlc3RHcm91cHOhbmh0dHBzOi8v"
+      "YS50ZXN0WJUfiwgAAAAAAAAAVYy7DoJAEADPTwLx1WpprCxsvdvdwKLskV3QEGPBfYvxO43E"
+      "xmaamcz4Bo+WMMvnBRSLJYpvCMGr1RCbNgpJZyOs1hsJGu9GeuRS/"
+      "NVeVWDcxV46V7VKtxOLpeTOyU0vriPLpGesBCQwHMxpYESW8vfY02AJLzRkX+TaG+"
+      "n2rwiP5wdgB1I6ogAAAHJyZXF1ZXN0VGltZXN0YW1wTXMKdGVuYWJsZURlYnVnUmVwb3J0aW"
+      "5n9QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
+  EXPECT_THAT(base::Base64Encode(msg), testing::StartsWith(expected));
+  EXPECT_EQ(5u * 1024u - kEncryptionOverhead, msg.size());
+  EXPECT_THAT(base::Base64Encode(msg_b), testing::StartsWith(expected));
+  EXPECT_EQ(5u * 1024u - kEncryptionOverhead, msg_b.size());
+  EXPECT_THAT(group_names, testing::ElementsAre(testing::Pair(
+                               test_origin, testing::ElementsAre("cars"))));
+}
+
 TEST_F(AdAuctionServiceImplTest, SerializesAuctionBlobWithNoGroups) {
   url::Origin test_origin = url::Origin::Create(GURL(kOriginStringA));
   std::vector<uint8_t> msg;
@@ -11322,8 +11400,10 @@
       base::Uuid::ParseCaseInsensitive("00000000-0000-0000-0000-000000000000"),
       /*timestamp=*/base::Time::FromMillisecondsSinceUnixEpoch(0),
       /*config=*/blink::mojom::AuctionDataConfig::New(),
+      /*sellers=*/{test_origin},
       /*callback=*/base::BindLambdaForTesting([&](BiddingAndAuctionData data) {
-        msg = std::move(data.request);
+        EXPECT_TRUE(data.requests.empty());
+        msg = std::move(data.requests[test_origin]);
         group_names = std::move(data.group_names);
         run_loop.Quit();
       }));
@@ -11332,6 +11412,35 @@
   EXPECT_TRUE(group_names.empty());
 }
 
+// Similar to SerializesAuctionBlobWithNoGroups, but with multi sellers.
+TEST_F(AdAuctionServiceImplTest,
+       SerializesAuctionBlobWithNoGroupsMultiSellers) {
+  url::Origin test_origin = url::Origin::Create(GURL(kOriginStringA));
+  url::Origin test_origin_b = url::Origin::Create(GURL(kOriginStringB));
+  std::vector<uint8_t> msg;
+  std::vector<uint8_t> msg_b;
+  base::flat_map<url::Origin, std::vector<std::string>> group_names;
+  base::RunLoop run_loop;
+  manager_->GetInterestGroupAdAuctionData(
+      /*top_level_origin=*/test_origin,
+      /*generation_id=*/
+      base::Uuid::ParseCaseInsensitive("00000000-0000-0000-0000-000000000000"),
+      /*timestamp=*/base::Time::FromMillisecondsSinceUnixEpoch(0),
+      /*config=*/blink::mojom::AuctionDataConfig::New(),
+      /*sellers=*/{test_origin, test_origin_b},
+      /*callback=*/base::BindLambdaForTesting([&](BiddingAndAuctionData data) {
+        EXPECT_TRUE(data.requests.empty());
+        msg = std::move(data.requests[test_origin]);
+        msg_b = std::move(data.requests[test_origin_b]);
+        group_names = std::move(data.group_names);
+        run_loop.Quit();
+      }));
+  run_loop.Run();
+  EXPECT_EQ("", base::Base64Encode(msg));
+  EXPECT_EQ("", base::Base64Encode(msg_b));
+  EXPECT_TRUE(group_names.empty());
+}
+
 TEST_F(AdAuctionServiceImplTest, SerializesAuctionBlobWithEmptyGroup) {
   url::Origin test_origin = url::Origin::Create(GURL(kOriginStringA));
   manager_->JoinInterestGroup(
@@ -11348,8 +11457,10 @@
       base::Uuid::ParseCaseInsensitive("00000000-0000-0000-0000-000000000000"),
       /*timestamp=*/base::Time::FromMillisecondsSinceUnixEpoch(0),
       /*config=*/blink::mojom::AuctionDataConfig::New(),
+      /*sellers=*/{test_origin},
       /*callback=*/base::BindLambdaForTesting([&](BiddingAndAuctionData data) {
-        msg = std::move(data.request);
+        EXPECT_TRUE(data.requests.empty());
+        msg = std::move(data.requests[test_origin]);
         group_names = std::move(data.group_names);
         run_loop.Quit();
       }));
@@ -11463,8 +11574,10 @@
       base::Uuid::ParseCaseInsensitive("00000000-0000-0000-0000-000000000000"),
       /*timestamp=*/base::Time::FromMillisecondsSinceUnixEpoch(0),
       /*config=*/blink::mojom::AuctionDataConfig::New(),
+      /*sellers=*/{test_origin_a},
       /*callback=*/base::BindLambdaForTesting([&](BiddingAndAuctionData data) {
-        msg = std::move(data.request);
+        EXPECT_EQ(1u, data.requests.size());
+        msg = std::move(data.requests[test_origin_a]);
         group_names = std::move(data.group_names);
         run_loop.Quit();
       }));
@@ -11522,8 +11635,10 @@
       base::Uuid::ParseCaseInsensitive("00000000-0000-0000-0000-000000000000"),
       /*timestamp=*/base::Time::FromMillisecondsSinceUnixEpoch(0),
       /*config=*/blink::mojom::AuctionDataConfig::New(),
+      /*sellers=*/{test_origin},
       /*callback=*/base::BindLambdaForTesting([&](BiddingAndAuctionData data) {
-        msg = std::move(data.request);
+        EXPECT_EQ(1u, data.requests.size());
+        msg = std::move(data.requests[test_origin]);
         group_names = std::move(data.group_names);
         run_loop.Quit();
       }));
@@ -11542,6 +11657,101 @@
                                test_origin, testing::ElementsAre("cars"))));
 }
 
+TEST_F(AdAuctionServiceImplTest,
+       SerializesAuctionBlobSendDebugReportCooldownsToBandA) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeaturesAndParameters(
+      {{features::kFledgeSendDebugReportCooldownsToBandA, {}},
+       {blink::features::kFledgeSampleDebugReports,
+        {{"fledge_enable_filtering_debug_report_starting_from", "100ms"}}}},
+      {});
+
+  url::Origin test_origin_a = url::Origin::Create(GURL(kOriginStringA));
+  url::Origin test_origin_b = url::Origin::Create(GURL(kOriginStringB));
+  manager_->JoinInterestGroup(
+      blink::TestInterestGroupBuilder(test_origin_a, "cars")
+          .SetAds({{{GURL("https://c.test/ad.html"), /*metadata=*/"do not send",
+                     /*size_group=*/std::nullopt,
+                     /*buyer_reporting_id=*/std::nullopt,
+                     /*buyer_and_seller_reporting_id=*/std::nullopt,
+                     /*selectable_buyer_and_seller_reporting_ids=*/std::nullopt,
+                     "1234"}}})
+          .Build(),
+      GURL("https://a.test/example.html"));
+  task_environment()->FastForwardBy(base::Seconds(1));
+
+  manager_->JoinInterestGroup(
+      blink::TestInterestGroupBuilder(test_origin_b, "bikes")
+          .SetAds({{{GURL("https://c.test/ad.html"), /*metadata=*/"do not send",
+                     /*size_group=*/std::nullopt,
+                     /*buyer_reporting_id=*/std::nullopt,
+                     /*buyer_and_seller_reporting_id=*/std::nullopt,
+                     /*selectable_buyer_and_seller_reporting_ids=*/std::nullopt,
+                     "1234"}}})
+          .Build(),
+      GURL("https://c.test/example.html"));
+  task_environment()->FastForwardBy(base::Seconds(1));
+
+  manager_->RecordDebugReportCooldown(test_origin_a, base::Time::Now(),
+                                      DebugReportCooldownType::kShortCooldown);
+  task_environment()->FastForwardBy(base::Seconds(1));
+
+  std::vector<uint8_t> msg_a;
+  std::vector<uint8_t> msg_b;
+  base::flat_map<url::Origin, std::vector<std::string>> group_names;
+  base::RunLoop run_loop;
+  manager_->GetInterestGroupAdAuctionData(
+      /*top_level_origin=*/test_origin_a,
+      /*generation_id=*/
+      base::Uuid::ParseCaseInsensitive("00000000-0000-0000-0000-000000000000"),
+      /*timestamp=*/base::Time::FromMillisecondsSinceUnixEpoch(0),
+      /*config=*/blink::mojom::AuctionDataConfig::New(),
+      /*sellers=*/{test_origin_a, test_origin_b},
+      /*callback=*/base::BindLambdaForTesting([&](BiddingAndAuctionData data) {
+        EXPECT_EQ(2u, data.requests.size());
+        msg_a = std::move(data.requests[test_origin_a]);
+        msg_b = std::move(data.requests[test_origin_b]);
+        group_names = std::move(data.group_names);
+        run_loop.Quit();
+      }));
+  run_loop.Run();
+  std::string expected_a =
+      "AgAAAainZ3ZlcnNpb24AaXB1Ymxpc2hlcmZhLnRlc3RsZ2VuZXJhdGlvbklkeCQwMDAwMDAw"
+      "MC0wMDAwLTAwMDAtMDAwMC0wMDAwMDAwMDAwMDBuaW50ZXJlc3RHcm91cHOibmh0dHBzOi8v"
+      "YS50ZXN0WHMfiwgAAAAAAAAAHcgxDoJgDAZQPBLIDZhMIA4Ozj9tA0X8SlqQuMlduKI7CW98"
+      "206JY+O8uJaM9Bam5IHWbQ3xh3ZIY+x9q1zZgjnrJ5fPUxE/"
+      "HUxx5kVdSEDfJrK4oTIb2VbcvTZ62TL/"
+      "DxMSQbVlAAAAbmh0dHBzOi8vYi50ZXN0WHYfiwgAAAAAAAAAHchNDoJADAZQPJLoDViZQFy4"
+      "cD0zbaAyfjUtP3HH3IUjegAT3vKVPQXyQuf6ciWEN3OUkR3RdHW2h/QI2fchCjU6Y6qGj/"
+      "HyFPgmLxUceRLjxEjfziu/"
+      "oVHNpCvu1moadZ5+"
+      "f28Ar5pmAAAAcnJlcXVlc3RUaW1lc3RhbXBNcwBzSW5Db29sZG93bk9yTG9ja291dPV0ZW5h"
+      "YmxlRGVidWdSZXBvcnRpbmf1AAAAAAAAAAAAAAAAAAA";
+  EXPECT_THAT(base::Base64Encode(msg_a), testing::StartsWith(expected_a));
+  EXPECT_EQ(5u * 1024u - kEncryptionOverhead, msg_a.size());
+
+  std::string expected_b =
+      "AgAAAainZ3ZlcnNpb24AaXB1Ymxpc2hlcmZhLnRlc3RsZ2VuZXJhdGlvbklkeCQwMDAwMDAw"
+      "MC0wMDAwLTAwMDAtMDAwMC0wMDAwMDAwMDAwMDBuaW50ZXJlc3RHcm91cHOibmh0dHBzOi8v"
+      "YS50ZXN0WHMfiwgAAAAAAAAAHcgxDoJgDAZQPBLIDZhMIA4Ozj9tA0X8SlqQuMlduKI7CW98"
+      "206JY+O8uJaM9Bam5IHWbQ3xh3ZIY+x9q1zZgjnrJ5fPUxE/"
+      "HUxx5kVdSEDfJrK4oTIb2VbcvTZ62TL/"
+      "DxMSQbVlAAAAbmh0dHBzOi8vYi50ZXN0WHYfiwgAAAAAAAAAHchNDoJADAZQPJLoDViZQFy4"
+      "cD0zbaAyfjUtP3HH3IUjegAT3vKVPQXyQuf6ciWEN3OUkR3RdHW2h/QI2fchCjU6Y6qGj/"
+      "HyFPgmLxUceRLjxEjfziu/"
+      "oVHNpCvu1moadZ5+"
+      "f28Ar5pmAAAAcnJlcXVlc3RUaW1lc3RhbXBNcwBzSW5Db29sZG93bk9yTG9ja291dPR0ZW5h"
+      "YmxlRGVidWdSZXBvcnRpbmf1AAAAAAAAAAAAAAAAAAA";
+  EXPECT_NE(expected_a, expected_b);
+  EXPECT_THAT(base::Base64Encode(msg_b), testing::StartsWith(expected_b));
+  EXPECT_EQ(5u * 1024u - kEncryptionOverhead, msg_b.size());
+
+  EXPECT_THAT(group_names,
+              testing::ElementsAre(
+                  testing::Pair(test_origin_a, testing::ElementsAre("cars")),
+                  testing::Pair(test_origin_b, testing::ElementsAre("bikes"))));
+}
+
 TEST_F(AdAuctionServiceImplTest, SerializesAuctionBlobDebugReportSampling) {
   base::test::ScopedFeatureList feature_list;
   feature_list.InitAndEnableFeature(
@@ -11572,8 +11782,10 @@
       base::Uuid::ParseCaseInsensitive("00000000-0000-0000-0000-000000000000"),
       /*timestamp=*/base::Time::FromMillisecondsSinceUnixEpoch(0),
       /*config=*/blink::mojom::AuctionDataConfig::New(),
+      /*sellers=*/{test_origin},
       /*callback=*/base::BindLambdaForTesting([&](BiddingAndAuctionData data) {
-        msg = std::move(data.request);
+        EXPECT_EQ(1u, data.requests.size());
+        msg = std::move(data.requests[test_origin]);
         group_names = std::move(data.group_names);
         run_loop.Quit();
       }));
@@ -11627,8 +11839,10 @@
       base::Uuid::ParseCaseInsensitive("00000000-0000-0000-0000-000000000000"),
       /*timestamp=*/base::Time::FromMillisecondsSinceUnixEpoch(0),
       /*config=*/blink::mojom::AuctionDataConfig::New(),
+      /*sellers=*/{test_origin},
       /*callback=*/base::BindLambdaForTesting([&](BiddingAndAuctionData data) {
-        msg = std::move(data.request);
+        EXPECT_EQ(1u, data.requests.size());
+        msg = std::move(data.requests[test_origin]);
         group_names = std::move(data.group_names);
         run_loop.Quit();
       }));
@@ -11680,8 +11894,10 @@
       base::Uuid::ParseCaseInsensitive("00000000-0000-0000-0000-000000000000"),
       /*timestamp=*/base::Time::FromMillisecondsSinceUnixEpoch(0),
       /*config=*/blink::mojom::AuctionDataConfig::New(),
+      /*sellers=*/{test_origin},
       /*callback=*/base::BindLambdaForTesting([&](BiddingAndAuctionData data) {
-        msg = std::move(data.request);
+        EXPECT_EQ(1u, data.requests.size());
+        msg = std::move(data.requests[test_origin]);
         group_names = std::move(data.group_names);
         run_loop.Quit();
       }));
@@ -11737,8 +11953,10 @@
       base::Uuid::ParseCaseInsensitive("00000000-0000-0000-0000-000000000000"),
       /*timestamp=*/base::Time::FromMillisecondsSinceUnixEpoch(0),
       /*config=*/blink::mojom::AuctionDataConfig::New(),
+      /*sellers=*/{test_origin},
       /*callback=*/base::BindLambdaForTesting([&](BiddingAndAuctionData data) {
-        msg = std::move(data.request);
+        EXPECT_EQ(1u, data.requests.size());
+        msg = std::move(data.requests[test_origin]);
         group_names = std::move(data.group_names);
         run_loop.Quit();
       }));
@@ -11793,8 +12011,10 @@
       base::Uuid::ParseCaseInsensitive("00000000-0000-0000-0000-000000000000"),
       /*timestamp=*/base::Time::FromMillisecondsSinceUnixEpoch(0),
       /*config=*/blink::mojom::AuctionDataConfig::New(),
+      /*sellers=*/{test_origin},
       /*callback=*/base::BindLambdaForTesting([&](BiddingAndAuctionData data) {
-        msg = std::move(data.request);
+        EXPECT_EQ(1u, data.requests.size());
+        msg = std::move(data.requests[test_origin]);
         group_names = std::move(data.group_names);
         run_loop.Quit();
       }));
@@ -11929,9 +12149,11 @@
             "00000000-0000-0000-0000-000000000000"),
         /*timestamp=*/base::Time::FromMillisecondsSinceUnixEpoch(0),
         /*config=*/config->Clone(),
+        /*sellers=*/{test_origin_a},
         /*callback=*/
         base::BindLambdaForTesting([&](BiddingAndAuctionData data) {
-          msg = std::move(data.request);
+          EXPECT_EQ(1u, data.requests.size());
+          msg = std::move(data.requests[test_origin_a]);
           group_names = std::move(data.group_names);
           run_loop.Quit();
         }));
@@ -11978,9 +12200,11 @@
             "00000000-0000-0000-0000-000000000000"),
         /*timestamp=*/base::Time::FromMillisecondsSinceUnixEpoch(0),
         /*config=*/config->Clone(),
+        /*sellers=*/{test_origin_a},
         /*callback=*/
         base::BindLambdaForTesting([&](BiddingAndAuctionData data) {
-          msg = std::move(data.request);
+          EXPECT_EQ(1u, data.requests.size());
+          msg = std::move(data.requests[test_origin_a]);
           group_names = std::move(data.group_names);
           run_loop.Quit();
         }));
@@ -12025,9 +12249,11 @@
             "00000000-0000-0000-0000-000000000000"),
         /*timestamp=*/base::Time::FromMillisecondsSinceUnixEpoch(0),
         /*config=*/config->Clone(),
+        /*sellers=*/{test_origin_a},
         /*callback=*/
         base::BindLambdaForTesting([&](BiddingAndAuctionData data) {
-          msg = std::move(data.request);
+          EXPECT_EQ(1u, data.requests.size());
+          msg = std::move(data.requests[test_origin_a]);
           group_names = std::move(data.group_names);
           run_loop.Quit();
         }));
@@ -12071,9 +12297,11 @@
             "00000000-0000-0000-0000-000000000000"),
         /*timestamp=*/base::Time::FromMillisecondsSinceUnixEpoch(0),
         /*config=*/config->Clone(),
+        /*sellers=*/{test_origin_a},
         /*callback=*/
         base::BindLambdaForTesting([&](BiddingAndAuctionData data) {
-          msg = std::move(data.request);
+          EXPECT_EQ(1u, data.requests.size());
+          msg = std::move(data.requests[test_origin_a]);
           group_names = std::move(data.group_names);
           run_loop.Quit();
         }));
@@ -12120,9 +12348,11 @@
             "00000000-0000-0000-0000-000000000000"),
         /*timestamp=*/base::Time::FromMillisecondsSinceUnixEpoch(0),
         /*config=*/config->Clone(),
+        /*sellers=*/{test_origin_a},
         /*callback=*/
         base::BindLambdaForTesting([&](BiddingAndAuctionData data) {
-          msg = std::move(data.request);
+          EXPECT_EQ(1u, data.requests.size());
+          msg = std::move(data.requests[test_origin_a]);
           group_names = std::move(data.group_names);
           run_loop.Quit();
         }));
@@ -12163,9 +12393,11 @@
             "00000000-0000-0000-0000-000000000000"),
         /*timestamp=*/base::Time::FromMillisecondsSinceUnixEpoch(0),
         /*config=*/config->Clone(),
+        /*sellers=*/{test_origin_a},
         /*callback=*/
         base::BindLambdaForTesting([&](BiddingAndAuctionData data) {
-          msg = std::move(data.request);
+          EXPECT_EQ(1u, data.requests.size());
+          msg = std::move(data.requests[test_origin_a]);
           group_names = std::move(data.group_names);
           run_loop.Quit();
         }));
@@ -12205,9 +12437,11 @@
             "00000000-0000-0000-0000-000000000000"),
         /*timestamp=*/base::Time::FromMillisecondsSinceUnixEpoch(0),
         /*config=*/config->Clone(),
+        /*sellers=*/{test_origin_a},
         /*callback=*/
         base::BindLambdaForTesting([&](BiddingAndAuctionData data) {
-          msg = std::move(data.request);
+          EXPECT_EQ(1u, data.requests.size());
+          msg = std::move(data.requests[test_origin_a]);
           group_names = std::move(data.group_names);
           run_loop.Quit();
         }));
@@ -12250,9 +12484,11 @@
             "00000000-0000-0000-0000-000000000000"),
         /*timestamp=*/base::Time::FromMillisecondsSinceUnixEpoch(0),
         /*config=*/config->Clone(),
+        /*sellers=*/{test_origin_a},
         /*callback=*/
         base::BindLambdaForTesting([&](BiddingAndAuctionData data) {
-          msg = std::move(data.request);
+          EXPECT_TRUE(data.requests.empty());
+          msg = std::move(data.requests[test_origin_a]);
           group_names = std::move(data.group_names);
           run_loop.Quit();
         }));
@@ -12493,6 +12729,186 @@
 }
 
 TEST_F(AdAuctionServiceImplBAndATest,
+       EncryptsPayload_DebugReportNotInLockoutOrCooldown) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeaturesAndParameters(
+      {{features::kFledgeSendDebugReportCooldownsToBandA, {}},
+       {blink::features::kFledgeSampleDebugReports,
+        {{"fledge_enable_filtering_debug_report_starting_from", "100ms"}}}},
+      {});
+
+  ProvideKeys();
+  NavigateAndCommit(kUrlA);
+  url::Origin test_origin = url::Origin::Create(GURL(kOriginStringA));
+  manager_->JoinInterestGroup(
+      blink::TestInterestGroupBuilder(test_origin, "cars")
+          .SetAds(
+              {{{GURL("https://c.test/ad1.html"), /*metadata=*/std::nullopt}}})
+          .Build(),
+      GURL("https://a.test/example.html"));
+  task_environment()->FastForwardBy(base::Seconds(1));
+
+  std::optional<AdAuctionDataAndId> result =
+      GetAdAuctionDataAndFlushForFrame(test_origin);
+
+  ASSERT_TRUE(result.has_value());
+  ASSERT_FALSE(result->request.empty());
+
+  auto key_config =
+      quiche::ObliviousHttpHeaderKeyConfig::Create(
+          kTestPrivacySandboxCoordinatorId, EVP_HPKE_DHKEM_X25519_HKDF_SHA256,
+          EVP_HPKE_HKDF_SHA256, EVP_HPKE_AES_256_GCM)
+          .value();
+  auto ohttp_gateway =
+      quiche::ObliviousHttpGateway::Create(
+          GetTestPrivacySandboxCoordinatorPrivateKey(), key_config)
+          .value();
+  EXPECT_EQ(0x00, result->request[0]);
+  auto request = ohttp_gateway.DecryptObliviousHttpRequest(
+      result->request.substr(1), kBiddingAndAuctionEncryptionRequestMediaType);
+  ASSERT_TRUE(request.ok()) << request.status();
+  auto plaintext_data = request->GetPlaintextData();
+
+  EXPECT_EQ(0x02, plaintext_data[0]);
+  size_t request_size = 0;
+  for (size_t idx = 0; idx < sizeof(uint32_t); idx++) {
+    request_size =
+        (request_size << 8) | static_cast<uint8_t>(plaintext_data[idx + 1]);
+  }
+
+  std::string got_str = cbor::DiagnosticWriter::Write(
+      cbor::Reader::Read(
+          base::as_byte_span(plaintext_data.substr(5, request_size)))
+          .value());
+  EXPECT_THAT(
+      got_str,
+      testing::EndsWith(
+          R"(, "InCooldownOrLockout": false, "enableDebugReporting": true})"));
+}
+
+// Similar to _DebugReportNotInLockoutOrCooldown, but the browser is in lockout.
+TEST_F(AdAuctionServiceImplBAndATest, EncryptsPayload_DebugReportInLockout) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeaturesAndParameters(
+      {{features::kFledgeSendDebugReportCooldownsToBandA, {}},
+       {blink::features::kFledgeSampleDebugReports,
+        {{"fledge_enable_filtering_debug_report_starting_from", "100ms"}}}},
+      {});
+
+  ProvideKeys();
+  NavigateAndCommit(kUrlA);
+  url::Origin test_origin = url::Origin::Create(GURL(kOriginStringA));
+  manager_->JoinInterestGroup(
+      blink::TestInterestGroupBuilder(test_origin, "cars")
+          .SetAds(
+              {{{GURL("https://c.test/ad1.html"), /*metadata=*/std::nullopt}}})
+          .Build(),
+      GURL("https://a.test/example.html"));
+  task_environment()->FastForwardBy(base::Seconds(1));
+  manager_->RecordDebugReportLockout(
+      base::Time::Now(), blink::features::kFledgeDebugReportLockout.Get());
+  task_environment()->FastForwardBy(base::Seconds(1));
+
+  std::optional<AdAuctionDataAndId> result =
+      GetAdAuctionDataAndFlushForFrame(test_origin);
+
+  ASSERT_TRUE(result.has_value());
+  ASSERT_FALSE(result->request.empty());
+
+  auto key_config =
+      quiche::ObliviousHttpHeaderKeyConfig::Create(
+          kTestPrivacySandboxCoordinatorId, EVP_HPKE_DHKEM_X25519_HKDF_SHA256,
+          EVP_HPKE_HKDF_SHA256, EVP_HPKE_AES_256_GCM)
+          .value();
+  auto ohttp_gateway =
+      quiche::ObliviousHttpGateway::Create(
+          GetTestPrivacySandboxCoordinatorPrivateKey(), key_config)
+          .value();
+  EXPECT_EQ(0x00, result->request[0]);
+  auto request = ohttp_gateway.DecryptObliviousHttpRequest(
+      result->request.substr(1), kBiddingAndAuctionEncryptionRequestMediaType);
+  ASSERT_TRUE(request.ok()) << request.status();
+  auto plaintext_data = request->GetPlaintextData();
+
+  EXPECT_EQ(0x02, plaintext_data[0]);
+  size_t request_size = 0;
+  for (size_t idx = 0; idx < sizeof(uint32_t); idx++) {
+    request_size =
+        (request_size << 8) | static_cast<uint8_t>(plaintext_data[idx + 1]);
+  }
+
+  std::string got_str = cbor::DiagnosticWriter::Write(
+      cbor::Reader::Read(
+          base::as_byte_span(plaintext_data.substr(5, request_size)))
+          .value());
+  EXPECT_THAT(
+      got_str,
+      testing::EndsWith(
+          R"(, "InCooldownOrLockout": true, "enableDebugReporting": false})"));
+}
+
+// Similar to DebugReportNotInLockoutOrCooldown, but the seller is in cooldown.
+TEST_F(AdAuctionServiceImplBAndATest, EncryptsPayload_DebugReportInCooldown) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeaturesAndParameters(
+      {{features::kFledgeSendDebugReportCooldownsToBandA, {}},
+       {blink::features::kFledgeSampleDebugReports,
+        {{"fledge_enable_filtering_debug_report_starting_from", "100ms"}}}},
+      {});
+
+  ProvideKeys();
+  NavigateAndCommit(kUrlA);
+  url::Origin test_origin = url::Origin::Create(GURL(kOriginStringA));
+  manager_->JoinInterestGroup(
+      blink::TestInterestGroupBuilder(test_origin, "cars")
+          .SetAds(
+              {{{GURL("https://c.test/ad1.html"), /*metadata=*/std::nullopt}}})
+          .Build(),
+      GURL("https://a.test/example.html"));
+  task_environment()->FastForwardBy(base::Seconds(1));
+  manager_->RecordDebugReportCooldown(test_origin, base::Time::Now(),
+                                      DebugReportCooldownType::kShortCooldown);
+  task_environment()->FastForwardBy(base::Seconds(1));
+
+  std::optional<AdAuctionDataAndId> result =
+      GetAdAuctionDataAndFlushForFrame(test_origin);
+
+  ASSERT_TRUE(result.has_value());
+  ASSERT_FALSE(result->request.empty());
+
+  auto key_config =
+      quiche::ObliviousHttpHeaderKeyConfig::Create(
+          kTestPrivacySandboxCoordinatorId, EVP_HPKE_DHKEM_X25519_HKDF_SHA256,
+          EVP_HPKE_HKDF_SHA256, EVP_HPKE_AES_256_GCM)
+          .value();
+  auto ohttp_gateway =
+      quiche::ObliviousHttpGateway::Create(
+          GetTestPrivacySandboxCoordinatorPrivateKey(), key_config)
+          .value();
+  EXPECT_EQ(0x00, result->request[0]);
+  auto request = ohttp_gateway.DecryptObliviousHttpRequest(
+      result->request.substr(1), kBiddingAndAuctionEncryptionRequestMediaType);
+  ASSERT_TRUE(request.ok()) << request.status();
+  auto plaintext_data = request->GetPlaintextData();
+
+  EXPECT_EQ(0x02, plaintext_data[0]);
+  size_t request_size = 0;
+  for (size_t idx = 0; idx < sizeof(uint32_t); idx++) {
+    request_size =
+        (request_size << 8) | static_cast<uint8_t>(plaintext_data[idx + 1]);
+  }
+
+  std::string got_str = cbor::DiagnosticWriter::Write(
+      cbor::Reader::Read(
+          base::as_byte_span(plaintext_data.substr(5, request_size)))
+          .value());
+  EXPECT_THAT(
+      got_str,
+      testing::EndsWith(
+          R"(, "InCooldownOrLockout": true, "enableDebugReporting": true})"));
+}
+
+TEST_F(AdAuctionServiceImplBAndATest,
        EncryptsPayloadWithDebugReportSamplingFlag) {
   base::test::ScopedFeatureList feature_list;
   feature_list.InitAndEnableFeature(
diff --git a/content/browser/interest_group/bidding_and_auction_serializer.cc b/content/browser/interest_group/bidding_and_auction_serializer.cc
index ddaa64cb..5558bf4 100644
--- a/content/browser/interest_group/bidding_and_auction_serializer.cc
+++ b/content/browser/interest_group/bidding_and_auction_serializer.cc
@@ -11,6 +11,7 @@
 
 #include <algorithm>
 #include <array>
+#include <map>
 #include <optional>
 #include <set>
 #include <string>
@@ -28,6 +29,7 @@
 #include "components/cbor/diagnostic_writer.h"
 #include "components/cbor/values.h"
 #include "components/cbor/writer.h"
+#include "content/browser/interest_group/for_debugging_only_report_util.h"
 #include "content/browser/interest_group/interest_group_auction.h"
 #include "content/browser/interest_group/interest_group_caching_storage.h"
 #include "content/browser/interest_group/interest_group_features.h"
@@ -199,6 +201,10 @@
   return 1 + LengthOfLength(2 * map.size()) + elements_size;
 }
 
+size_t GetFramingSize() {
+  return kFramingHeaderSize + kOhttpHeaderSize + 1;
+}
+
 ValueAndSize SerializeAds(const std::vector<blink::InterestGroup::Ad>& ads,
                           bool include_full_ads) {
   cbor::Value::ArrayValue result;
@@ -246,7 +252,8 @@
 // We can't add fields to this format without coordinating with the B&A team.
 ValueAndSizeAndPrevWinsSize SerializeInterestGroup(
     base::Time start_time,
-    const SingleStorageInterestGroup& group) {
+    const SingleStorageInterestGroup& group,
+    bool in_cooldown_or_lockout) {
   cbor::Value::MapValue group_obj;
   base::CheckedNumeric<size_t> group_elements_size = 0;
 
@@ -401,6 +408,14 @@
   group_obj[cbor::Value("browserSignals")] =
       cbor::Value(std::move(browser_signals));
 
+  if (base::FeatureList::IsEnabled(
+          features::kFledgeSendDebugReportCooldownsToBandA)) {
+    group_elements_size +=
+        TaggedStringLength(constexpr_strlen("InCooldownOrLockout")) + 1;
+    group_obj[cbor::Value("InCooldownOrLockout")] =
+        cbor::Value(in_cooldown_or_lockout);
+  }
+
   base::CheckedNumeric<size_t> total_size =
       TaggedMapLength(group_obj, group_elements_size);
   return {cbor::Value(std::move(group_obj)), total_size, prev_wins_array_size};
@@ -409,6 +424,7 @@
 CompressedInterestGroups CompressInterestGroups(
     const url::Origin& owner,
     const std::vector<SingleStorageInterestGroup>& groups,
+    bool in_cooldown_or_lockout,
     base::Time start_time,
     std::optional<uint32_t> target_uncompressed_size) {
   CompressedInterestGroups result{{}, {}, 0, 0};
@@ -416,7 +432,7 @@
   base::CheckedNumeric<size_t> groups_elements_size = 0;
   for (const SingleStorageInterestGroup& group : groups) {
     ValueAndSizeAndPrevWinsSize serialized_group =
-        SerializeInterestGroup(start_time, group);
+        SerializeInterestGroup(start_time, group, in_cooldown_or_lockout);
     if (serialized_group.prev_wins_array_size.IsValid()) {
       result.prev_wins_array_sizes.push_back(static_cast<size_t>(
           serialized_group.prev_wins_array_size.ValueOrDie()));
@@ -470,6 +486,8 @@
         std::pair<url::Origin, std::vector<SingleStorageInterestGroup>>>&
         bidders_and_groups,
     const blink::mojom::AuctionDataConfig& config,
+    bool debug_report_in_lockout,
+    const std::map<url::Origin, DebugReportCooldown>& debug_report_cooldown_map,
     size_t total_size_before_groups,
     base::Time start_time) {
   BiddingAndAuctionSerializer::TargetSizeEstimator estimator(
@@ -483,8 +501,13 @@
   all_bidders_full_compressed_groups.reserve(bidders_and_groups.size());
   for (size_t idx = 0; idx < bidders_and_groups.size(); ++idx) {
     const auto& bidder_groups = bidders_and_groups[idx];
+    bool in_cooldown_or_lockout =
+        debug_report_in_lockout ||
+        IsInDebugReportCooldown(bidder_groups.first, debug_report_cooldown_map,
+                                start_time);
     all_bidders_full_compressed_groups.emplace_back(CompressInterestGroups(
-        bidder_groups.first, bidder_groups.second, start_time, std::nullopt));
+        bidder_groups.first, bidder_groups.second, in_cooldown_or_lockout,
+        start_time, std::nullopt));
     estimator.UpdatePerBuyerMaxSize(
         bidder_groups.first,
         all_bidders_full_compressed_groups[idx].data.size());
@@ -506,6 +529,11 @@
       continue;
     }
 
+    bool in_cooldown_or_lockout =
+        debug_report_in_lockout ||
+        IsInDebugReportCooldown(bidder_groups.first, debug_report_cooldown_map,
+                                start_time);
+
     CompressedInterestGroups compressed_groups =
         std::move(all_bidders_full_compressed_groups[idx]);
 
@@ -554,8 +582,8 @@
             (current_uncompressed_target_size * 15) / 16;
 
         compressed_groups = CompressInterestGroups(
-            bidder_groups.first, bidder_groups.second, start_time,
-            current_uncompressed_target_size);
+            bidder_groups.first, bidder_groups.second, in_cooldown_or_lockout,
+            start_time, current_uncompressed_target_size);
       }
 
       // Only record iteration count if we were trying to fit within a
@@ -931,12 +959,12 @@
   accumulated_groups_.emplace_back(std::move(owner), std::move(groups_to_add));
 }
 
-BiddingAndAuctionData BiddingAndAuctionSerializer::Build() {
+std::optional<BiddingAndAuctionData> BiddingAndAuctionSerializer::Build() {
   DCHECK(config_);
   // If we are serializing all groups then we can return an empty list.
   // Otherwise we still need to return a fixed size request (all padding).
   if (config_->per_buyer_configs.empty() && accumulated_groups_.empty()) {
-    return {};
+    return std::nullopt;
   }
 
   BiddingAndAuctionData data;
@@ -966,6 +994,20 @@
       TaggedStringLength(constexpr_strlen("enableDebugReporting")) + 1;
 
   if (base::FeatureList::IsEnabled(
+          features::kFledgeSendDebugReportCooldownsToBandA)) {
+    // Note: here this field's value is set false as a placeholder only, but it
+    // will be overwritten in
+    // `InterestGroupManagerImpl::OnAdAuctionDataLoadComplete` to its real
+    // value, with lockout and cooldowns considered.
+    message_obj[cbor::Value("InCooldownOrLockout")] = cbor::Value(false);
+
+    // Boolean values (true and false) have the same size (1 byte), so changing
+    // the field's value won't change the field's size.
+    message_elements_size +=
+        TaggedStringLength(constexpr_strlen("InCooldownOrLockout")) + 1;
+  }
+
+  if (base::FeatureList::IsEnabled(
           blink::features::kFledgeEnableSampleDebugReportOnCookieSetting)) {
     bool for_debugging_only_sampling = ShouldSampleDebugReport();
     message_obj[cbor::Value("enableSampledDebugReporting")] =
@@ -1012,7 +1054,7 @@
   message_elements_size +=
       TaggedStringLength(constexpr_strlen("interestGroups"));
 
-  const size_t framing_size = kFramingHeaderSize + kOhttpHeaderSize + 1;
+  const size_t framing_size = GetFramingSize();
   const base::CheckedNumeric<size_t> total_size_before_groups =
       TaggedMapLength(message_obj,
                       message_elements_size + 1 +
@@ -1021,13 +1063,13 @@
 
   if (!total_size_before_groups.IsValid()) {
     DLOG(ERROR) << "total_size_before_groups is invalid";
-    return {};
+    return std::nullopt;
   }
 
   // If we don't fit in the desired size, don't send anything.
   if (total_size_before_groups.ValueOrDie() >
       config_->request_size.value_or(kBinSizes.back())) {
-    return {};
+    return std::nullopt;
   }
 
   blink::mojom::AuctionDataConfigPtr config = config_->Clone();
@@ -1037,14 +1079,15 @@
   }
 
   SerializedBiddersMap groups = SerializeBidderGroupsWithConfig(
-      accumulated_groups_, *config, total_size_before_groups.ValueOrDie(),
+      accumulated_groups_, *config, debug_report_in_lockout_,
+      debug_report_cooldown_map_, total_size_before_groups.ValueOrDie(),
       timestamp_);
 
   // If we have no groups and the buyers weren't specified, don't send anything.
   // We still need to provide a non-empty request if the buyers are specified in
   // order to avoid leaking interest groups state.
   if (config->per_buyer_configs.empty() && groups.bidders.empty()) {
-    return {};
+    return std::nullopt;
   }
 
   message_elements_size +=
@@ -1052,18 +1095,46 @@
   message_obj[cbor::Value("interestGroups")] =
       cbor::Value(std::move(groups.bidders));
 
+  message_total_size_ = TaggedMapLength(message_obj, message_elements_size);
+  message_obj_ = std::move(message_obj);
+
   base::UmaHistogramCounts1000(
       "Ads.InterestGroup.ServerAuction.Request.NumGroups", groups.num_groups);
 
-  base::CheckedNumeric<size_t> total_size =
-      TaggedMapLength(message_obj, message_elements_size);
-  cbor::Value message(std::move(message_obj));
-  std::optional<std::vector<uint8_t>> maybe_msg = cbor::Writer::Write(message);
+  data.group_names = std::move(groups.group_names);
+  data.group_pagg_coordinators = std::move(groups.group_pagg_coordinators);
+  return data;
+}
+
+std::optional<std::vector<uint8_t>>
+BiddingAndAuctionSerializer::BuildRequestFromMessage(const url::Origin& seller,
+                                                     base::Time now) {
+  if (message_obj_.empty()) {
+    NOTREACHED(base::NotFatalUntil::M138);
+  }
+  if (base::FeatureList::IsEnabled(
+          features::kFledgeSendDebugReportCooldownsToBandA)) {
+    bool debug_report_in_cooldown_or_lockout =
+        debug_report_in_lockout_ ||
+        IsInDebugReportCooldown(seller, debug_report_cooldown_map_, now);
+    // Set InCooldownOrLockout field's real value. It does not change
+    // `data`'s message_total_size.
+    message_obj_[cbor::Value("InCooldownOrLockout")] =
+        cbor::Value(debug_report_in_cooldown_or_lockout);
+  }
+  std::optional<std::vector<uint8_t>> maybe_msg =
+      cbor::Writer::Write(cbor::Value(message_obj_));
   DCHECK(maybe_msg);
-  DCHECK_EQ(static_cast<size_t>(total_size.ValueOrDie()), maybe_msg->size());
+  DCHECK_EQ(static_cast<size_t>(message_total_size_.ValueOrDie()),
+            maybe_msg->size());
   base::UmaHistogramCounts100000(
     "Ads.InterestGroup.ServerAuction.Request.UnpaddedSize", maybe_msg->size());
-
+  blink::mojom::AuctionDataConfigPtr config = config_->Clone();
+  if (!config->request_size) {
+    // If size isn't specified, then we need to fit in the biggest bin.
+    config->request_size = kBinSizes.back();
+  }
+  const size_t framing_size = GetFramingSize();
   base::CheckedNumeric<uint32_t> desired_size;
   if (config->per_buyer_configs.empty()) {
     // If we didn't set a list of buyers then use the requested size as the
@@ -1090,7 +1161,7 @@
       desired_size - framing_size + kFramingHeaderSize;
   if (!padded_size.IsValid()) {
     DLOG(ERROR) << "padded_size is invalid";
-    return {};
+    return std::nullopt;
   }
   CHECK_GE(static_cast<size_t>(padded_size.ValueOrDie()),
            maybe_msg->size() + kFramingHeaderSize);
@@ -1106,11 +1177,7 @@
   request[4] = (request_size >> 0) & 0xff;
 
   memcpy(&request[kFramingHeaderSize], maybe_msg->data(), maybe_msg->size());
-
-  data.request = std::move(request);
-  data.group_names = std::move(groups.group_names);
-  data.group_pagg_coordinators = std::move(groups.group_pagg_coordinators);
-  return data;
+  return request;
 }
 
 }  // namespace content
diff --git a/content/browser/interest_group/bidding_and_auction_serializer.h b/content/browser/interest_group/bidding_and_auction_serializer.h
index 768922ef..b8a8468 100644
--- a/content/browser/interest_group/bidding_and_auction_serializer.h
+++ b/content/browser/interest_group/bidding_and_auction_serializer.h
@@ -5,6 +5,7 @@
 #ifndef CONTENT_BROWSER_INTEREST_GROUP_BIDDING_AND_AUCTION_SERIALIZER_H_
 #define CONTENT_BROWSER_INTEREST_GROUP_BIDDING_AND_AUCTION_SERIALIZER_H_
 
+#include <map>
 #include <string>
 #include <vector>
 
@@ -12,6 +13,7 @@
 #include "base/numerics/checked_math.h"
 #include "base/time/time.h"
 #include "base/uuid.h"
+#include "components/cbor/values.h"
 #include "content/browser/interest_group/interest_group_caching_storage.h"
 #include "content/browser/interest_group/storage_interest_group.h"
 #include "content/common/content_export.h"
@@ -28,7 +30,7 @@
 
   BiddingAndAuctionData& operator=(BiddingAndAuctionData&& other);
 
-  std::vector<uint8_t> request;
+  base::flat_map<url::Origin, std::vector<uint8_t>> requests;
   base::flat_map<url::Origin, std::vector<std::string>> group_names;
   base::flat_map<blink::InterestGroupKey, url::Origin> group_pagg_coordinators;
 };
@@ -109,18 +111,28 @@
   void SetDebugReportInLockout(bool debug_report_in_lockout) {
     debug_report_in_lockout_ = debug_report_in_lockout;
   }
+  void SetDebugReportCooldownsMap(
+      std::map<url::Origin, DebugReportCooldown> cooldowns_map) {
+    debug_report_cooldown_map_ = std::move(cooldowns_map);
+  }
   void AddGroups(const url::Origin& owner,
                  scoped_refptr<StorageInterestGroups> groups);
-  BiddingAndAuctionData Build();
+  std::optional<BiddingAndAuctionData> Build();
+  std::optional<std::vector<uint8_t>> BuildRequestFromMessage(
+      const url::Origin& seller,
+      base::Time now);
 
  private:
   base::Uuid generation_id_;
   std::string publisher_;
   base::Time timestamp_;
   blink::mojom::AuctionDataConfigPtr config_;
-  bool debug_report_in_lockout_;
+  bool debug_report_in_lockout_ = false;
+  std::map<url::Origin, DebugReportCooldown> debug_report_cooldown_map_;
   std::vector<std::pair<url::Origin, std::vector<SingleStorageInterestGroup>>>
       accumulated_groups_;
+  cbor::Value::MapValue message_obj_;
+  base::CheckedNumeric<size_t> message_total_size_ = 0;
 };
 
 }  // namespace content
diff --git a/content/browser/interest_group/bidding_and_auction_serializer_unittest.cc b/content/browser/interest_group/bidding_and_auction_serializer_unittest.cc
index 08bf3e7..93d12d3c 100644
--- a/content/browser/interest_group/bidding_and_auction_serializer_unittest.cc
+++ b/content/browser/interest_group/bidding_and_auction_serializer_unittest.cc
@@ -110,8 +110,12 @@
 
   AddGroupsToSerializer(serializer);
 
-  BiddingAndAuctionData data = serializer.Build();
-  EXPECT_EQ(data.request.size(), 5 * 1024 - kEncryptionOverhead);
+  std::optional<BiddingAndAuctionData> data = serializer.Build();
+  ASSERT_TRUE(data.has_value());
+  std::optional<std::vector<uint8_t>> request =
+      serializer.BuildRequestFromMessage(kOriginA, base::Time::Now());
+  ASSERT_TRUE(request.has_value());
+  EXPECT_EQ(request->size(), 5 * 1024 - kEncryptionOverhead);
   histogram_tester.ExpectTotalCount(
       "Ads.InterestGroup.ServerAuction.Request.NumIterations", 4);
   histogram_tester.ExpectUniqueSample(
@@ -136,8 +140,12 @@
 
   AddGroupsToSerializer(serializer);
 
-  BiddingAndAuctionData data = serializer.Build();
-  EXPECT_EQ(data.request.size(), kRequestSize - kEncryptionOverhead);
+  std::optional<BiddingAndAuctionData> data = serializer.Build();
+  ASSERT_TRUE(data.has_value());
+  std::optional<std::vector<uint8_t>> request =
+      serializer.BuildRequestFromMessage(kOriginA, base::Time::Now());
+  ASSERT_TRUE(request.has_value());
+  EXPECT_EQ(request->size(), kRequestSize - kEncryptionOverhead);
   histogram_tester.ExpectUniqueSample(
       "Ads.InterestGroup.ServerAuction.Request.NumIterations", 0, 4);
   histogram_tester.ExpectUniqueSample(
@@ -160,8 +168,12 @@
 
   AddGroupsToSerializer(serializer);
 
-  BiddingAndAuctionData data = serializer.Build();
-  EXPECT_EQ(data.request.size(), kRequestSize - kEncryptionOverhead);
+  std::optional<BiddingAndAuctionData> data = serializer.Build();
+  ASSERT_TRUE(data.has_value());
+  std::optional<std::vector<uint8_t>> request =
+      serializer.BuildRequestFromMessage(kOriginA, base::Time::Now());
+  ASSERT_TRUE(request.has_value());
+  EXPECT_EQ(request->size(), kRequestSize - kEncryptionOverhead);
 }
 
 TEST_F(BiddingAndAuctionSerializerTest, SerializeWithTooSmallRequestSize) {
@@ -182,8 +194,9 @@
 
   AddGroupsToSerializer(serializer);
 
-  BiddingAndAuctionData data = serializer.Build();
-  EXPECT_EQ(data.request.size(), 0u);
+  std::optional<BiddingAndAuctionData> data = serializer.Build();
+  ASSERT_FALSE(data.has_value());
+
   histogram_tester.ExpectUniqueSample(
       "Ads.InterestGroup.ServerAuction.Request.NumIterations", 2, 1);
   histogram_tester.ExpectTotalCount(
@@ -217,8 +230,13 @@
 
   AddGroupsToSerializer(serializer);
 
-  BiddingAndAuctionData data = serializer.Build();
-  EXPECT_EQ(data.request.size(), kRequestSize - kEncryptionOverhead);
+  std::optional<BiddingAndAuctionData> data = serializer.Build();
+  ASSERT_TRUE(data.has_value());
+  std::optional<std::vector<uint8_t>> request =
+      serializer.BuildRequestFromMessage(kOriginA, base::Time::Now());
+  ASSERT_TRUE(request.has_value());
+  EXPECT_EQ(request->size(), kRequestSize - kEncryptionOverhead);
+
   histogram_tester.ExpectUniqueSample(
       "Ads.InterestGroup.ServerAuction.Request.NumIterations", 0, 4);
   histogram_tester.ExpectUniqueSample(
@@ -251,8 +269,12 @@
 
   AddGroupsToSerializer(serializer);
 
-  BiddingAndAuctionData data = serializer.Build();
-  EXPECT_EQ(data.request.size(), kRequestSize - kEncryptionOverhead);
+  std::optional<BiddingAndAuctionData> data = serializer.Build();
+  ASSERT_TRUE(data.has_value());
+  std::optional<std::vector<uint8_t>> request =
+      serializer.BuildRequestFromMessage(kOriginA, base::Time::Now());
+  ASSERT_TRUE(request.has_value());
+  EXPECT_EQ(request->size(), kRequestSize - kEncryptionOverhead);
 }
 
 TEST_F(BiddingAndAuctionSerializerTest, SerializeWithPerOwnerSizeExpands) {
@@ -280,8 +302,12 @@
 
   AddGroupsToSerializer(serializer);
 
-  BiddingAndAuctionData data = serializer.Build();
-  EXPECT_EQ(data.request.size(), kRequestSize - kEncryptionOverhead);
+  std::optional<BiddingAndAuctionData> data = serializer.Build();
+  ASSERT_TRUE(data.has_value());
+  std::optional<std::vector<uint8_t>> request =
+      serializer.BuildRequestFromMessage(kOriginA, base::Time::Now());
+  ASSERT_TRUE(request.has_value());
+  EXPECT_EQ(request->size(), kRequestSize - kEncryptionOverhead);
 }
 
 TEST_F(BiddingAndAuctionSerializerTest, SerializeWithPerOwnerSizeShrinks) {
@@ -311,8 +337,13 @@
 
   AddGroupsToSerializer(serializer);
 
-  BiddingAndAuctionData data = serializer.Build();
-  EXPECT_EQ(data.request.size(), kRequestSize - kEncryptionOverhead);
+  std::optional<BiddingAndAuctionData> data = serializer.Build();
+  ASSERT_TRUE(data.has_value());
+  std::optional<std::vector<uint8_t>> request =
+      serializer.BuildRequestFromMessage(kOriginA, base::Time::Now());
+  ASSERT_TRUE(request.has_value());
+  EXPECT_EQ(request->size(), kRequestSize - kEncryptionOverhead);
+
   histogram_tester.ExpectBucketCount(
       "Ads.InterestGroup.ServerAuction.Request.NumIterations", 0, 2);
   histogram_tester.ExpectTotalCount(
@@ -348,8 +379,13 @@
 
   AddGroupsToSerializer(serializer);
 
-  BiddingAndAuctionData data = serializer.Build();
-  EXPECT_EQ(data.request.size(), kRequestSize - kEncryptionOverhead);
+  std::optional<BiddingAndAuctionData> data = serializer.Build();
+  ASSERT_TRUE(data.has_value());
+  std::optional<std::vector<uint8_t>> request =
+      serializer.BuildRequestFromMessage(kOriginA, base::Time::Now());
+  ASSERT_TRUE(request.has_value());
+  EXPECT_EQ(request->size(), kRequestSize - kEncryptionOverhead);
+
   histogram_tester.ExpectBucketCount(
       "Ads.InterestGroup.ServerAuction.Request.NumIterations", 3, 3);
   histogram_tester.ExpectBucketCount(
@@ -385,8 +421,12 @@
   serializer.SetConfig(std::move(config));
   serializer.SetDebugReportInLockout(false);
 
-  BiddingAndAuctionData data = serializer.Build();
-  EXPECT_EQ(data.request.size(), kRequestSize - kEncryptionOverhead);
+  std::optional<BiddingAndAuctionData> data = serializer.Build();
+  ASSERT_TRUE(data.has_value());
+  std::optional<std::vector<uint8_t>> request =
+      serializer.BuildRequestFromMessage(kOriginA, base::Time::Now());
+  ASSERT_TRUE(request.has_value());
+  EXPECT_EQ(request->size(), kRequestSize - kEncryptionOverhead);
 }
 
 // Test that the encrypted request still has the full size even when the
@@ -415,8 +455,12 @@
   serializer.SetConfig(std::move(config));
   serializer.SetDebugReportInLockout(false);
 
-  BiddingAndAuctionData data = serializer.Build();
-  EXPECT_EQ(data.request.size(), kRequestSize - kEncryptionOverhead);
+  std::optional<BiddingAndAuctionData> data = serializer.Build();
+  ASSERT_TRUE(data.has_value());
+  std::optional<std::vector<uint8_t>> request =
+      serializer.BuildRequestFromMessage(kOriginA, base::Time::Now());
+  ASSERT_TRUE(request.has_value());
+  EXPECT_EQ(request->size(), kRequestSize - kEncryptionOverhead);
 }
 
 class TargetSizeEstimatorTest : public testing::Test {
diff --git a/content/browser/interest_group/for_debugging_only_report_util.cc b/content/browser/interest_group/for_debugging_only_report_util.cc
index 91e1061..37a2acd 100644
--- a/content/browser/interest_group/for_debugging_only_report_util.cc
+++ b/content/browser/interest_group/for_debugging_only_report_util.cc
@@ -4,11 +4,13 @@
 
 #include "content/browser/interest_group/for_debugging_only_report_util.h"
 
+#include <map>
 #include <optional>
 
 #include "base/time/time.h"
 #include "content/browser/interest_group/interest_group_features.h"
 #include "third_party/blink/public/common/features.h"
+#include "url/origin.h"
 
 namespace content {
 
@@ -43,6 +45,17 @@
       delta.CeilToMultiple(base::Hours(1)));
 }
 
+std::optional<base::TimeDelta> ConvertDebugReportCooldownTypeToDuration(
+    DebugReportCooldownType type) {
+  switch (type) {
+    case DebugReportCooldownType::kShortCooldown:
+      return blink::features::kFledgeDebugReportShortCooldown.Get();
+    case DebugReportCooldownType::kRestrictedCooldown:
+      return blink::features::kFledgeDebugReportRestrictedCooldown.Get();
+  }
+  return std::nullopt;
+}
+
 bool IsInDebugReportLockout(const std::optional<DebugReportLockout>& lockout,
                             const base::Time now) {
   if (!lockout.has_value()) {
@@ -58,4 +71,28 @@
   return !is_lockout_before_filtering_starting && is_in_lockout;
 }
 
+bool IsInDebugReportCooldown(
+    const url::Origin& origin,
+    const std::map<url::Origin, DebugReportCooldown>& cooldowns_map,
+    const base::Time now) {
+  const auto cooldown_it = cooldowns_map.find(origin);
+  if (cooldown_it != cooldowns_map.end()) {
+    std::optional<base::TimeDelta> duration =
+        ConvertDebugReportCooldownTypeToDuration(cooldown_it->second.type);
+    if (duration.has_value()) {
+      bool is_cooldown_before_filtering_starting =
+          cooldown_it->second.starting_time <
+          CeilToNearestNextHour(
+              blink::features::kFledgeEnableFilteringDebugReportStartingFrom
+                  .Get());
+      bool is_in_cooldown =
+          cooldown_it->second.starting_time + *duration >= now;
+      if (!is_cooldown_before_filtering_starting && is_in_cooldown) {
+        return true;
+      }
+    }
+  }
+  return false;
+}
+
 }  // namespace content
diff --git a/content/browser/interest_group/for_debugging_only_report_util.h b/content/browser/interest_group/for_debugging_only_report_util.h
index 908b8be..65dde59 100644
--- a/content/browser/interest_group/for_debugging_only_report_util.h
+++ b/content/browser/interest_group/for_debugging_only_report_util.h
@@ -5,10 +5,12 @@
 #ifndef CONTENT_BROWSER_INTEREST_GROUP_FOR_DEBUGGING_ONLY_REPORT_UTIL_H_
 #define CONTENT_BROWSER_INTEREST_GROUP_FOR_DEBUGGING_ONLY_REPORT_UTIL_H_
 
+#include <map>
 #include <optional>
 
 #include "base/time/time.h"
 #include "content/common/content_export.h"
+#include "url/origin.h"
 
 namespace content {
 
@@ -19,6 +21,20 @@
   bool operator==(const DebugReportLockout& other) const = default;
 };
 
+enum class DebugReportCooldownType {
+  kShortCooldown = 0,
+  kRestrictedCooldown = 1,
+
+  kMaxValue = kRestrictedCooldown,
+};
+
+struct CONTENT_EXPORT DebugReportCooldown {
+  base::Time starting_time;
+  DebugReportCooldownType type;
+
+  bool operator==(const DebugReportCooldown& other) const = default;
+};
+
 // Should forDebuggingOnly reports be sampled or not.
 CONTENT_EXPORT bool ShouldSampleDebugReport();
 
@@ -29,11 +45,22 @@
 // Ceil `detla` to its nearest next hour.
 CONTENT_EXPORT base::Time CeilToNearestNextHour(base::TimeDelta delta);
 
+// Converts forDebuggingOnly API's cooldown type to its actual cooldown
+// duration.
+CONTENT_EXPORT std::optional<base::TimeDelta>
+ConvertDebugReportCooldownTypeToDuration(DebugReportCooldownType type);
+
 // Returns true if the client is under forDebuggingOnly API's lockout period.
 CONTENT_EXPORT bool IsInDebugReportLockout(
     const std::optional<DebugReportLockout>& lockout,
     const base::Time now);
 
+// Returns true if the `origin` is under forDebuggingOnly API's cooldown period.
+CONTENT_EXPORT bool IsInDebugReportCooldown(
+    const url::Origin& origin,
+    const std::map<url::Origin, DebugReportCooldown>& cooldowns_map,
+    const base::Time now);
+
 }  // namespace content
 
 #endif  // CONTENT_BROWSER_INTEREST_GROUP_FOR_DEBUGGING_ONLY_REPORT_UTIL_H_
diff --git a/content/browser/interest_group/for_debugging_only_report_util_unittest.cc b/content/browser/interest_group/for_debugging_only_report_util_unittest.cc
index 88228c547..8e42d0d 100644
--- a/content/browser/interest_group/for_debugging_only_report_util_unittest.cc
+++ b/content/browser/interest_group/for_debugging_only_report_util_unittest.cc
@@ -15,6 +15,8 @@
 #include "testing/gmock/include/gmock/gmock-matchers.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/common/features.h"
+#include "url/gurl.h"
+#include "url/origin.h"
 
 namespace content {
 
@@ -37,6 +39,31 @@
       now));
 }
 
+TEST_F(ForDebuggingOnlyReportUtilTest, IsInDebugReportCooldown) {
+  base::Time now = base::Time::Now();
+  url::Origin origin_a = url::Origin::Create(GURL("https://example-a.com"));
+  url::Origin origin_b = url::Origin::Create(GURL("https://example-b.com"));
+  url::Origin origin_c = url::Origin::Create(GURL("https://example-c.com"));
+
+  std::map<url::Origin, DebugReportCooldown> cooldowns_map;
+  cooldowns_map.emplace(
+      origin_a,
+      DebugReportCooldown(now, DebugReportCooldownType::kShortCooldown));
+  cooldowns_map.emplace(
+      origin_b,
+      DebugReportCooldown(
+          now - blink::features::kFledgeDebugReportShortCooldown.Get() -
+              base::Days(1),
+          DebugReportCooldownType::kShortCooldown));
+
+  // origin_a is in cooldown.
+  EXPECT_TRUE(IsInDebugReportCooldown(origin_a, cooldowns_map, now));
+  // origin_b's cooldown expired.
+  EXPECT_FALSE(IsInDebugReportCooldown(origin_b, cooldowns_map, now));
+  // No cooldown entry for origin_c.
+  EXPECT_FALSE(IsInDebugReportCooldown(origin_c, cooldowns_map, now));
+}
+
 TEST_F(ForDebuggingOnlyReportUtilTest, EnableFilteringInFutureTime) {
   base::test::ScopedFeatureList feature_list;
   feature_list.InitWithFeaturesAndParameters(
@@ -51,6 +78,13 @@
   base::Time now = base::Time::Now();
   EXPECT_FALSE(IsInDebugReportLockout(
       DebugReportLockout(now, /*duration=*/base::Days(90)), now));
+
+  url::Origin origin_a = url::Origin::Create(GURL("https://example-a.com"));
+  std::map<url::Origin, DebugReportCooldown> cooldowns_map;
+  cooldowns_map.emplace(
+      origin_a,
+      DebugReportCooldown(now, DebugReportCooldownType::kShortCooldown));
+  EXPECT_FALSE(IsInDebugReportCooldown(origin_a, cooldowns_map, now));
 }
 
 TEST_F(ForDebuggingOnlyReportUtilTest, ShouldSampleDebugReport) {
diff --git a/content/browser/interest_group/header_direct_from_seller_signals.cc b/content/browser/interest_group/header_direct_from_seller_signals.cc
index 0d6800a..e63c704 100644
--- a/content/browser/interest_group/header_direct_from_seller_signals.cc
+++ b/content/browser/interest_group/header_direct_from_seller_signals.cc
@@ -15,7 +15,7 @@
 #include "base/containers/flat_map.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback.h"
-#include "base/json/json_string_value_serializer.h"
+#include "base/json/json_writer.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/strings/stringprintf.h"
@@ -210,14 +210,12 @@
     const base::Value* maybe_seller_signals = maybe_dict->Find("sellerSignals");
     std::optional<std::string> seller_signals;
     if (maybe_seller_signals) {
-      seller_signals.emplace();
-      JSONStringValueSerializer serializer(&seller_signals.value());
-      if (!serializer.Serialize(*maybe_seller_signals)) {
+      seller_signals = base::WriteJson(*maybe_seller_signals);
+      if (!seller_signals.has_value()) {
         errors.push_back(base::StringPrintf(
             "directFromSellerSignalsHeaderAdSlot: failed to re-serialize "
             "sellerSignals: Ad-Auction-Signals=%s",
             unprocessed_response.response_json.c_str()));
-        seller_signals.reset();
       }
     }
 
@@ -225,14 +223,12 @@
         maybe_dict->Find("auctionSignals");
     std::optional<std::string> auction_signals;
     if (maybe_auction_signals) {
-      auction_signals.emplace();
-      JSONStringValueSerializer serializer(&auction_signals.value());
-      if (!serializer.Serialize(*maybe_auction_signals)) {
+      auction_signals = base::WriteJson(*maybe_auction_signals);
+      if (!auction_signals.has_value()) {
         errors.push_back(base::StringPrintf(
             "directFromSellerSignalsHeaderAdSlot: failed to re-serialize "
             "auctionSignals: Ad-Auction-Signals=%s",
             unprocessed_response.response_json.c_str()));
-        auction_signals.reset();
       }
     }
 
@@ -254,11 +250,10 @@
               item.first.c_str(), unprocessed_response.response_json.c_str()));
           continue;
         }
-        std::string origin_signals;
-        JSONStringValueSerializer serializer(&origin_signals);
-        if (serializer.Serialize(item.second)) {
+        if (std::optional<std::string> origin_signals =
+                base::WriteJson(item.second)) {
           per_buyer_signals_vec.emplace_back(std::move(origin),
-                                             std::move(origin_signals));
+                                             *std::move(origin_signals));
         } else {
           errors.push_back(base::StringPrintf(
               "directFromSellerSignalsHeaderAdSlot: failed to re-serialize "
diff --git a/content/browser/interest_group/interest_group_auction.cc b/content/browser/interest_group/interest_group_auction.cc
index db012a6..0f3611d 100644
--- a/content/browser/interest_group/interest_group_auction.cc
+++ b/content/browser/interest_group/interest_group_auction.cc
@@ -27,7 +27,7 @@
 #include "base/feature_list.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback.h"
-#include "base/json/json_string_value_serializer.h"
+#include "base/json/json_writer.h"
 #include "base/location.h"
 #include "base/memory/ptr_util.h"
 #include "base/memory/raw_ptr.h"
@@ -630,32 +630,12 @@
     return false;
   }
 
-  if (IsInDebugReportLockout(debug_report_lockout_and_cooldowns->lockout,
-                             now)) {
-    return true;
-  }
-
-  const auto cooldown_it =
-      debug_report_lockout_and_cooldowns->debug_report_cooldown_map.find(
-          origin);
-  if (cooldown_it !=
-      debug_report_lockout_and_cooldowns->debug_report_cooldown_map.end()) {
-    std::optional<base::TimeDelta> duration =
-        ConvertDebugReportCooldownTypeToDuration(cooldown_it->second.type);
-    if (duration.has_value()) {
-      bool is_cooldown_before_filtering_starting =
-          cooldown_it->second.starting_time <
-          CeilToNearestNextHour(
-              blink::features::kFledgeEnableFilteringDebugReportStartingFrom
-                  .Get());
-      bool is_in_cooldown =
-          cooldown_it->second.starting_time + *duration >= now;
-      if (!is_cooldown_before_filtering_starting && is_in_cooldown) {
-        return true;
-      }
-    }
-  }
-  return false;
+  return IsInDebugReportLockout(debug_report_lockout_and_cooldowns->lockout,
+                                now) ||
+         IsInDebugReportCooldown(
+             origin,
+             debug_report_lockout_and_cooldowns->debug_report_cooldown_map,
+             now);
 }
 
 void UpdateDebugReportCooldown(
@@ -3588,8 +3568,8 @@
   if (winner->bid->bid_ad->ad_render_id) {
     ad_metadata.Set("adRenderId", winner->bid->bid_ad->ad_render_id.value());
   }
-  JSONStringValueSerializer serializer(&winning_bid_info.ad_metadata);
-  serializer.Serialize(base::Value(std::move(ad_metadata)));
+  winning_bid_info.ad_metadata =
+      base::WriteJson(ad_metadata).value_or(std::string());
 
   InterestGroupAuctionReporter::SellerWinningBidInfo
       top_level_seller_winning_bid_info;
diff --git a/content/browser/interest_group/interest_group_browsertest.cc b/content/browser/interest_group/interest_group_browsertest.cc
index 690f071..7dd4f24 100644
--- a/content/browser/interest_group/interest_group_browsertest.cc
+++ b/content/browser/interest_group/interest_group_browsertest.cc
@@ -29,7 +29,6 @@
 #include "base/functional/callback.h"
 #include "base/functional/callback_forward.h"
 #include "base/json/json_reader.h"
-#include "base/json/json_string_value_serializer.h"
 #include "base/json/json_writer.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/scoped_refptr.h"
@@ -21639,9 +21638,8 @@
               base::Value::Dict outer;
               outer.Set("keys", std::move(keys));
 
-              std::string json_output;
-              JSONStringValueSerializer serializer(&json_output);
-              serializer.Serialize(outer);
+              std::string json_output =
+                  base::WriteJson(outer).value_or(std::string());
               URLLoaderInterceptor::WriteResponse(headers, json_output,
                                                   params->client.get());
               return true;
diff --git a/content/browser/interest_group/interest_group_caching_storage.cc b/content/browser/interest_group/interest_group_caching_storage.cc
index 00a0905..169f152 100644
--- a/content/browser/interest_group/interest_group_caching_storage.cc
+++ b/content/browser/interest_group/interest_group_caching_storage.cc
@@ -461,13 +461,6 @@
       .Then(std::move(callback));
 }
 
-void InterestGroupCachingStorage::GetDebugReportLockout(
-    base::OnceCallback<void(std::optional<DebugReportLockout>)> callback) {
-  return interest_group_storage_
-      .AsyncCall(&InterestGroupStorage::GetDebugReportLockout)
-      .Then(std::move(callback));
-}
-
 void InterestGroupCachingStorage::GetDebugReportLockoutAndCooldowns(
     base::flat_set<url::Origin> origins,
     base::OnceCallback<void(std::optional<DebugReportLockoutAndCooldowns>)>
@@ -478,6 +471,14 @@
       .Then(std::move(callback));
 }
 
+void InterestGroupCachingStorage::GetDebugReportLockoutAndAllCooldowns(
+    base::OnceCallback<void(std::optional<DebugReportLockoutAndCooldowns>)>
+        callback) {
+  return interest_group_storage_
+      .AsyncCall(&InterestGroupStorage::GetDebugReportLockoutAndAllCooldowns)
+      .Then(std::move(callback));
+}
+
 void InterestGroupCachingStorage::GetAllInterestGroupJoiningOrigins(
     base::OnceCallback<void(std::vector<url::Origin>)> callback) {
   interest_group_storage_
diff --git a/content/browser/interest_group/interest_group_caching_storage.h b/content/browser/interest_group/interest_group_caching_storage.h
index 56b24efd..d38316a 100644
--- a/content/browser/interest_group/interest_group_caching_storage.h
+++ b/content/browser/interest_group/interest_group_caching_storage.h
@@ -267,16 +267,18 @@
       base::OnceCallback<void(std::vector<InterestGroupUpdateParameter>)>
           callback);
 
-  // Gets lockout for sending forDebuggingOnly reports.
-  void GetDebugReportLockout(
-      base::OnceCallback<void(std::optional<DebugReportLockout>)> callback);
-
-  // Gets lockout and cooldown for sending forDebuggingOnly reports.
+  // Gets lockout and cooldowns of `origins` for sending forDebuggingOnly
+  // reports.
   void GetDebugReportLockoutAndCooldowns(
       base::flat_set<url::Origin> origins,
       base::OnceCallback<void(std::optional<DebugReportLockoutAndCooldowns>)>
           callback);
 
+  // Gets lockout and all cooldowns for sending forDebuggingOnly reports.
+  void GetDebugReportLockoutAndAllCooldowns(
+      base::OnceCallback<void(std::optional<DebugReportLockoutAndCooldowns>)>
+          callback);
+
   // Gets a list of all interest group joining origins. Each joining origin
   // will only appear once.
   void GetAllInterestGroupJoiningOrigins(
diff --git a/content/browser/interest_group/interest_group_features.cc b/content/browser/interest_group/interest_group_features.cc
index bd56ca9..f0f9d4c0 100644
--- a/content/browser/interest_group/interest_group_features.cc
+++ b/content/browser/interest_group/interest_group_features.cc
@@ -105,6 +105,13 @@
              "FledgeQueryKAnonymity",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
+// Send forDebuggingOnly's per ad tech cooldown statuses to B&A server side in
+// the request. Only lockout status is sent to B&A server side when this flag is
+// disabled.
+BASE_FEATURE(kFledgeSendDebugReportCooldownsToBandA,
+             "FledgeSendDebugReportCooldownsToBandA",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 // Enables starting worklet processes at auction start time in anticipation
 // of needing them for future worklets.
 BASE_FEATURE(kFledgeStartAnticipatoryProcesses,
diff --git a/content/browser/interest_group/interest_group_features.h b/content/browser/interest_group/interest_group_features.h
index 51cd6fb..04d1575 100644
--- a/content/browser/interest_group/interest_group_features.h
+++ b/content/browser/interest_group/interest_group_features.h
@@ -40,6 +40,8 @@
 
 CONTENT_EXPORT BASE_DECLARE_FEATURE(kFledgeQueryKAnonymity);
 
+CONTENT_EXPORT BASE_DECLARE_FEATURE(kFledgeSendDebugReportCooldownsToBandA);
+
 CONTENT_EXPORT BASE_DECLARE_FEATURE(kFledgeStartAnticipatoryProcesses);
 CONTENT_EXPORT BASE_DECLARE_FEATURE_PARAM(
     base::TimeDelta,
diff --git a/content/browser/interest_group/interest_group_manager_impl.cc b/content/browser/interest_group/interest_group_manager_impl.cc
index 4a6c8c8..17874f26 100644
--- a/content/browser/interest_group/interest_group_manager_impl.cc
+++ b/content/browser/interest_group/interest_group_manager_impl.cc
@@ -808,11 +808,13 @@
     base::Uuid generation_id,
     base::Time timestamp,
     blink::mojom::AuctionDataConfigPtr config,
+    std::vector<url::Origin> sellers,
     base::OnceCallback<void(BiddingAndAuctionData)> callback) {
   AdAuctionDataLoaderState state;
   state.serializer.SetPublisher(top_level_origin.host());
   state.serializer.SetGenerationId(std::move(generation_id));
   state.serializer.SetTimestamp(timestamp);
+  state.sellers = std::move(sellers);
   state.callback = std::move(callback);
   if (config->per_buyer_configs.size() == 0) {
     state.serializer.SetConfig(std::move(config));
@@ -895,7 +897,7 @@
     AdAuctionDataLoaderState state) {
   if (blink::features::kFledgeEnableFilteringDebugReportStartingFrom.Get() !=
       base::Milliseconds(0)) {
-    caching_storage_.GetDebugReportLockout(
+    caching_storage_.GetDebugReportLockoutAndAllCooldowns(
         base::BindOnce(&InterestGroupManagerImpl::OnAdAuctionDataLoadComplete,
                        weak_factory_.GetWeakPtr(), std::move(state)));
   } else {
@@ -905,14 +907,39 @@
 
 void InterestGroupManagerImpl::OnAdAuctionDataLoadComplete(
     AdAuctionDataLoaderState state,
-    std::optional<DebugReportLockout> lockout) {
-  state.serializer.SetDebugReportInLockout(
-      IsInDebugReportLockout(lockout, base::Time::Now()));
-  BiddingAndAuctionData data = state.serializer.Build();
+    std::optional<DebugReportLockoutAndCooldowns> lockoutAndCooldowns) {
+  base::Time now = base::Time::Now();
+  bool in_debug_report_lockout = false;
+  if (lockoutAndCooldowns.has_value()) {
+    in_debug_report_lockout =
+        IsInDebugReportLockout(lockoutAndCooldowns->lockout, now);
+    state.serializer.SetDebugReportInLockout(in_debug_report_lockout);
+    state.serializer.SetDebugReportCooldownsMap(
+        lockoutAndCooldowns->debug_report_cooldown_map);
+  }
+
+  std::optional<BiddingAndAuctionData> data = state.serializer.Build();
+  if (data.has_value()) {
+    for (const auto& seller : state.sellers) {
+      std::optional<std::vector<uint8_t>> request =
+          state.serializer.BuildRequestFromMessage(seller, now);
+      if (request.has_value()) {
+        data->requests[seller] = std::move(*request);
+      } else {
+        data = std::nullopt;
+        break;
+      }
+    }
+  }
+
   base::UmaHistogramTimes(
       "Ads.InterestGroup.ServerAuction.AdAuctionDataLoadTime",
       base::TimeTicks::Now() - state.start_time);
-  std::move(state.callback).Run(std::move(data));
+  if (data.has_value()) {
+    std::move(state.callback).Run(*std::move(data));
+  } else {
+    std::move(state.callback).Run({});
+  }
 }
 
 void InterestGroupManagerImpl::GetTrustedServerKey(
@@ -1022,6 +1049,12 @@
                                                      std::move(callback));
 }
 
+void InterestGroupManagerImpl::GetDebugReportLockoutAndAllCooldowns(
+    base::OnceCallback<void(std::optional<DebugReportLockoutAndCooldowns>)>
+        callback) {
+  caching_storage_.GetDebugReportLockoutAndAllCooldowns(std::move(callback));
+}
+
 void InterestGroupManagerImpl::UpdateInterestGroup(
     const blink::InterestGroupKey& group_key,
     InterestGroupUpdate update,
diff --git a/content/browser/interest_group/interest_group_manager_impl.h b/content/browser/interest_group/interest_group_manager_impl.h
index 94553578..0e5f441 100644
--- a/content/browser/interest_group/interest_group_manager_impl.h
+++ b/content/browser/interest_group/interest_group_manager_impl.h
@@ -488,12 +488,18 @@
                         const base::Time update_time,
                         bool initial_update);
 
-  // Gets lockout and cooldown for sending forDebuggingOnly reports.
+  // Gets lockout and cooldowns of `origins` for sending forDebuggingOnly
+  // reports.
   void GetDebugReportLockoutAndCooldowns(
       base::flat_set<url::Origin> origins,
       base::OnceCallback<void(std::optional<DebugReportLockoutAndCooldowns>)>
           callback);
 
+  // Gets lockout and all cooldowns for sending forDebuggingOnly reports.
+  void GetDebugReportLockoutAndAllCooldowns(
+      base::OnceCallback<void(std::optional<DebugReportLockoutAndCooldowns>)>
+          callback);
+
   // Gets the last time that the key was reported to the k-anonymity server.
   void GetLastKAnonymityReported(
       const std::string& hashed_key,
@@ -506,6 +512,7 @@
       base::Uuid generation_id,
       base::Time timestamp,
       blink::mojom::AuctionDataConfigPtr config,
+      std::vector<url::Origin> sellers,
       base::OnceCallback<void(BiddingAndAuctionData)> callback);
 
   // Get the public key to use for the auction data. The `callback` may be
@@ -592,6 +599,7 @@
     ~AdAuctionDataLoaderState();
     AdAuctionDataLoaderState(AdAuctionDataLoaderState&& state);
     BiddingAndAuctionSerializer serializer;
+    std::vector<url::Origin> sellers;
     base::OnceCallback<void(BiddingAndAuctionData)> callback;
     base::TimeTicks start_time;
   };
@@ -711,10 +719,11 @@
   // `OnAdAuctionDataLoadComplete()` directly.
   void OnInterestGroupAdAuctionDataLoadComplete(AdAuctionDataLoaderState state);
 
-  // Constructs the AuctionAdata when the load is complete and calls the
+  // Constructs the AdAuctionData when the load is complete and calls the
   // provided callback.
-  void OnAdAuctionDataLoadComplete(AdAuctionDataLoaderState state,
-                                   std::optional<DebugReportLockout> lockout);
+  void OnAdAuctionDataLoadComplete(
+      AdAuctionDataLoaderState state,
+      std::optional<DebugReportLockoutAndCooldowns> lockout);
 
   // Helper to that returns bound NotifyInterestGroupAccessed() callbacks to
   // allow notifications to be sent after a database update.
diff --git a/content/browser/interest_group/interest_group_storage.cc b/content/browser/interest_group/interest_group_storage.cc
index 9b1fc4e..d2ee57b 100644
--- a/content/browser/interest_group/interest_group_storage.cc
+++ b/content/browser/interest_group/interest_group_storage.cc
@@ -5125,6 +5125,34 @@
   return std::nullopt;
 }
 
+void DoGetAllDebugReportCooldowns(
+    sql::Database& db,
+    std::optional<base::Time> ignore_before,
+    DebugReportLockoutAndCooldowns& debug_report_lockout_and_cooldowns) {
+  sql::Statement cooldowns(
+      db.GetCachedStatement(SQL_FROM_HERE,
+                            "SELECT origin, starting_time, type "
+                            "FROM cooldown_debugging_only_report "
+                            "WHERE starting_time > ?"));
+  if (!cooldowns.is_valid()) {
+    DLOG(ERROR) << "GetAllDebugReportCooldowns SQL statement did not compile: "
+                << db.GetErrorMessage();
+    return;
+  }
+  cooldowns.BindTime(0, ignore_before.value_or(base::Time::Min()));
+
+  while (cooldowns.Step()) {
+    url::Origin origin = DeserializeOrigin(cooldowns.ColumnStringView(0));
+    debug_report_lockout_and_cooldowns
+        .debug_report_cooldown_map[std::move(origin)] = DebugReportCooldown(
+        cooldowns.ColumnTime(1),
+        static_cast<DebugReportCooldownType>(cooldowns.ColumnInt(2)));
+  }
+
+  // TODO(qingxinwu): When reading the table fails, treat it as there is an
+  // unexpired cooldown.
+}
+
 std::optional<DebugReportCooldown> DoGetDebugReportCooldownForOrigin(
     sql::Database& db,
     const url::Origin& origin,
@@ -6360,15 +6388,6 @@
   return std::move(left_interest_groups.value());
 }
 
-std::optional<DebugReportLockout>
-InterestGroupStorage::GetDebugReportLockout() {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  if (!EnsureDBInitialized()) {
-    return std::nullopt;
-  }
-  return DoGetDebugReportLockout(*db_, GetSampleDebugReportStartingFrom());
-}
-
 std::optional<DebugReportLockoutAndCooldowns>
 InterestGroupStorage::GetDebugReportLockoutAndCooldowns(
     const base::flat_set<url::Origin>& origins) {
@@ -6388,6 +6407,24 @@
   return debug_report_lockout_and_cooldowns;
 }
 
+std::optional<DebugReportLockoutAndCooldowns>
+InterestGroupStorage::GetDebugReportLockoutAndAllCooldowns() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (!EnsureDBInitialized()) {
+    return std::nullopt;
+  }
+  DebugReportLockoutAndCooldowns debug_report_lockout_and_cooldowns;
+
+  // Ignore lockout and cooldowns whose start time is before
+  // kFledgeEnableFilteringDebugReportStartingFrom.
+  std::optional<base::Time> ignore_before = GetSampleDebugReportStartingFrom();
+  debug_report_lockout_and_cooldowns.lockout =
+      DoGetDebugReportLockout(*db_, ignore_before);
+  DoGetAllDebugReportCooldowns(*db_, ignore_before,
+                               debug_report_lockout_and_cooldowns);
+  return debug_report_lockout_and_cooldowns;
+}
+
 std::optional<InterestGroupKanonUpdateParameter>
 InterestGroupStorage::UpdateInterestGroup(
     const blink::InterestGroupKey& group_key,
diff --git a/content/browser/interest_group/interest_group_storage.h b/content/browser/interest_group/interest_group_storage.h
index 68eb443..22c4939d 100644
--- a/content/browser/interest_group/interest_group_storage.h
+++ b/content/browser/interest_group/interest_group_storage.h
@@ -88,13 +88,15 @@
       const std::set<std::string>& interest_groups_to_keep,
       const url::Origin& main_frame_origin);
 
-  // Gets lockout for sending forDebuggingOnly reports.
-  std::optional<DebugReportLockout> GetDebugReportLockout();
-
-  // Gets lockout and cooldowns for sending forDebuggingOnly reports.
+  // Gets lockout and cooldowns of `origins` for sending forDebuggingOnly
+  // reports.
   std::optional<DebugReportLockoutAndCooldowns>
   GetDebugReportLockoutAndCooldowns(const base::flat_set<url::Origin>& origins);
 
+  // Gets lockout and all cooldowns for sending forDebuggingOnly reports.
+  std::optional<DebugReportLockoutAndCooldowns>
+  GetDebugReportLockoutAndAllCooldowns();
+
   // Updates the interest group `name` of `owner` with the populated fields of
   // `update`.
   //
diff --git a/content/browser/interest_group/interest_group_storage_unittest.cc b/content/browser/interest_group/interest_group_storage_unittest.cc
index 73a6fc5..cb08026 100644
--- a/content/browser/interest_group/interest_group_storage_unittest.cc
+++ b/content/browser/interest_group/interest_group_storage_unittest.cc
@@ -1456,9 +1456,6 @@
   EXPECT_FALSE(cooldowns->lockout.has_value());
   EXPECT_TRUE(cooldowns->debug_report_cooldown_map.empty());
 
-  std::optional<DebugReportLockout> lockout = storage->GetDebugReportLockout();
-  ASSERT_FALSE(lockout.has_value());
-
   base::Time time = base::Time::Now();
   base::Time expected_time = base::Time::FromDeltaSinceWindowsEpoch(
       time.ToDeltaSinceWindowsEpoch().CeilToMultiple(base::Hours(1)));
@@ -1471,11 +1468,6 @@
   EXPECT_EQ(blink::features::kFledgeDebugReportLockout.Get(),
             cooldowns->lockout->duration);
   EXPECT_TRUE(cooldowns->debug_report_cooldown_map.empty());
-  lockout = storage->GetDebugReportLockout();
-  ASSERT_TRUE(lockout.has_value());
-  EXPECT_EQ(expected_time, lockout->starting_time);
-  EXPECT_EQ(blink::features::kFledgeDebugReportLockout.Get(),
-            lockout->duration);
 
   storage->RecordDebugReportCooldown(test_origin, time,
                                      DebugReportCooldownType::kShortCooldown);
@@ -1541,22 +1533,28 @@
                                  .Build(),
                              kOrigin.GetURL());
 
-  std::optional<DebugReportLockout> lockout = storage->GetDebugReportLockout();
-  ASSERT_FALSE(lockout.has_value());
+  std::optional<DebugReportLockoutAndCooldowns> lockout_and_cooldowns =
+      storage->GetDebugReportLockoutAndAllCooldowns();
+  ASSERT_TRUE(lockout_and_cooldowns.has_value());
+  ASSERT_FALSE(lockout_and_cooldowns->lockout.has_value());
 
   storage->SetDebugReportLockoutUntilIGExpires();
-  lockout = storage->GetDebugReportLockout();
-  ASSERT_TRUE(lockout.has_value());
+  lockout_and_cooldowns = storage->GetDebugReportLockoutAndAllCooldowns();
+  ASSERT_TRUE(lockout_and_cooldowns.has_value());
+  ASSERT_TRUE(lockout_and_cooldowns->lockout.has_value());
   base::Time expected_starting_time = base::Time::FromDeltaSinceWindowsEpoch(
       start.ToDeltaSinceWindowsEpoch().CeilToMultiple(base::Hours(1)));
-  EXPECT_EQ(expected_starting_time, lockout->starting_time);
-  EXPECT_EQ(even_later - expected_starting_time, lockout->duration);
+  EXPECT_EQ(expected_starting_time,
+            lockout_and_cooldowns->lockout->starting_time);
+  EXPECT_EQ(even_later - expected_starting_time,
+            lockout_and_cooldowns->lockout->duration);
 
   // All IGs joined before has already expired.
   task_environment().FastForwardBy(base::Days(3));
   storage->SetDebugReportLockoutUntilIGExpires();
-  lockout = storage->GetDebugReportLockout();
-  ASSERT_FALSE(lockout.has_value());
+  lockout_and_cooldowns = storage->GetDebugReportLockoutAndAllCooldowns();
+  ASSERT_TRUE(lockout_and_cooldowns.has_value());
+  ASSERT_FALSE(lockout_and_cooldowns->lockout.has_value());
 }
 
 TEST_F(InterestGroupStorageTest, DeleteExpiredDebugReportCooldown) {
@@ -4352,10 +4350,13 @@
       base::Time::Now().ToDeltaSinceWindowsEpoch().CeilToMultiple(
           base::Hours(1)));
   storage->RecordDebugReportLockout(now_nearest_next_hour, base::Days(90));
-  std::optional<DebugReportLockout> lockout = storage->GetDebugReportLockout();
-  ASSERT_TRUE(lockout.has_value());
-  EXPECT_EQ(now_nearest_next_hour, lockout->starting_time);
-  EXPECT_EQ(base::Days(90), lockout->duration);
+  std::optional<DebugReportLockoutAndCooldowns> lockout_and_cooldowns =
+      storage->GetDebugReportLockoutAndAllCooldowns();
+  ASSERT_TRUE(lockout_and_cooldowns.has_value());
+  ASSERT_TRUE(lockout_and_cooldowns->lockout.has_value());
+  EXPECT_EQ(now_nearest_next_hour,
+            lockout_and_cooldowns->lockout->starting_time);
+  EXPECT_EQ(base::Days(90), lockout_and_cooldowns->lockout->duration);
 }
 
 TEST_F(InterestGroupStorageTest, MultiVersionUpgradeTest) {
diff --git a/content/browser/interest_group/interest_group_update_manager.cc b/content/browser/interest_group/interest_group_update_manager.cc
index 1622691..aa93392 100644
--- a/content/browser/interest_group/interest_group_update_manager.cc
+++ b/content/browser/interest_group/interest_group_update_manager.cc
@@ -21,7 +21,7 @@
 #include "base/containers/span.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback.h"
-#include "base/json/json_string_value_serializer.h"
+#include "base/json/json_writer.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/rand_util.h"
@@ -324,12 +324,12 @@
   if (!maybe_user_bidding_signals) {
     return true;
   }
-  std::string user_bidding_signals;
-  JSONStringValueSerializer serializer(&user_bidding_signals);
-  if (!serializer.Serialize(*maybe_user_bidding_signals)) {
+  std::optional<std::string> user_bidding_signals =
+      base::WriteJson(*maybe_user_bidding_signals);
+  if (!user_bidding_signals.has_value()) {
     return false;
   }
-  interest_group_update.user_bidding_signals = std::move(user_bidding_signals);
+  interest_group_update.user_bidding_signals = *std::move(user_bidding_signals);
   return true;
 }
 
@@ -544,14 +544,13 @@
     }
     const base::Value* maybe_metadata = ads_dict->Find("metadata");
     if (maybe_metadata) {
-      std::string metadata;
-      JSONStringValueSerializer serializer(&metadata);
-      if (!serializer.Serialize(*maybe_metadata)) {
+      std::optional<std::string> metadata = base::WriteJson(*maybe_metadata);
+      if (!metadata.has_value()) {
         // Binary blobs shouldn't be present, but it's possible we exceeded the
         // max JSON depth.
         return std::nullopt;
       }
-      ad.metadata = std::move(metadata);
+      ad.metadata = *std::move(metadata);
     }
     const std::string* maybe_ad_render_id = ads_dict->FindString("adRenderId");
     if (maybe_ad_render_id) {
diff --git a/content/browser/interest_group/storage_interest_group.cc b/content/browser/interest_group/storage_interest_group.cc
index 16be159..c488547 100644
--- a/content/browser/interest_group/storage_interest_group.cc
+++ b/content/browser/interest_group/storage_interest_group.cc
@@ -32,15 +32,4 @@
     DebugReportLockoutAndCooldowns&&) = default;
 DebugReportLockoutAndCooldowns::~DebugReportLockoutAndCooldowns() = default;
 
-std::optional<base::TimeDelta> ConvertDebugReportCooldownTypeToDuration(
-    DebugReportCooldownType type) {
-  switch (type) {
-    case DebugReportCooldownType::kShortCooldown:
-      return blink::features::kFledgeDebugReportShortCooldown.Get();
-    case DebugReportCooldownType::kRestrictedCooldown:
-      return blink::features::kFledgeDebugReportRestrictedCooldown.Get();
-  }
-  return std::nullopt;
-}
-
 }  // namespace content
diff --git a/content/browser/interest_group/storage_interest_group.h b/content/browser/interest_group/storage_interest_group.h
index ecdaeed..fac6eaf1 100644
--- a/content/browser/interest_group/storage_interest_group.h
+++ b/content/browser/interest_group/storage_interest_group.h
@@ -5,6 +5,7 @@
 #ifndef CONTENT_BROWSER_INTEREST_GROUP_STORAGE_INTEREST_GROUP_H_
 #define CONTENT_BROWSER_INTEREST_GROUP_STORAGE_INTEREST_GROUP_H_
 
+#include <map>
 #include <optional>
 #include <vector>
 
@@ -47,20 +48,6 @@
   base::Time last_k_anon_updated;
 };
 
-enum class DebugReportCooldownType {
-  kShortCooldown = 0,
-  kRestrictedCooldown = 1,
-
-  kMaxValue = kRestrictedCooldown,
-};
-
-struct CONTENT_EXPORT DebugReportCooldown {
-  base::Time starting_time;
-  DebugReportCooldownType type;
-
-  bool operator==(const DebugReportCooldown& other) const = default;
-};
-
 struct CONTENT_EXPORT DebugReportLockoutAndCooldowns {
   DebugReportLockoutAndCooldowns();
   DebugReportLockoutAndCooldowns(
@@ -76,14 +63,9 @@
   std::optional<DebugReportLockout> lockout;
   // The key is an ad tech origin, and value is its cooldown of sending
   // forDebuggingOnly reports.
-  std::map<url::Origin, DebugReportCooldown> debug_report_cooldown_map = {};
+  std::map<url::Origin, DebugReportCooldown> debug_report_cooldown_map;
 };
 
-// Converts forDebuggingOnly API's cooldown type to its actual cooldown
-// duration.
-CONTENT_EXPORT std::optional<base::TimeDelta>
-ConvertDebugReportCooldownTypeToDuration(DebugReportCooldownType type);
-
 }  // namespace content
 
 #endif  // CONTENT_BROWSER_INTEREST_GROUP_STORAGE_INTEREST_GROUP_H_
diff --git a/content/browser/renderer_host/cookie_utils.cc b/content/browser/renderer_host/cookie_utils.cc
index b389dfb..13e0fce7 100644
--- a/content/browser/renderer_host/cookie_utils.cc
+++ b/content/browser/renderer_host/cookie_utils.cc
@@ -22,9 +22,12 @@
 #include "content/public/browser/legacy_tech_cookie_issue_details.h"
 #include "content/public/common/content_client.h"
 #include "content/public/common/content_features.h"
+#include "net/cookies/cookie_constants.h"
 #include "net/cookies/cookie_inclusion_status.h"
+#include "net/cookies/cookie_setting_override.h"
 #include "services/metrics/public/cpp/metrics_utils.h"
 #include "services/metrics/public/cpp/ukm_builders.h"
+#include "third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom-shared.h"
 #include "url/gurl.h"
 
 namespace content {
@@ -274,6 +277,21 @@
              net::CookieInclusionStatus::ExemptionReason::k3PCDHeuristics;
 }
 
+bool IsCrossOriginSameSiteNetworkAccessWithStorageAccessEligible(
+    const network::mojom::CookieAccessDetailsPtr& cookie_details) {
+  if (!cookie_details->frame_origin ||
+      !cookie_details->cookie_setting_overrides.Has(
+          net::CookieSettingOverride::kStorageAccessGrantEligible)) {
+    // `frame_origin` is unset for script accesses, and network accesses whose
+    // IsolationInfo's `frame_origin` was nullptr.
+    return false;
+  }
+  const url::Origin origin = url::Origin::Create(cookie_details->url);
+  return !origin.IsSameOriginWith(cookie_details->frame_origin.value()) &&
+         net::SchemefulSite::IsSameSite(origin,
+                                        cookie_details->frame_origin.value());
+}
+
 }  // namespace
 
 void SplitCookiesIntoAllowedAndBlocked(
@@ -375,6 +393,11 @@
 
   int cookies_exempted_by_top_level_storage_access = 0;
 
+  const bool cross_origin_same_site_with_storage_access_eligible =
+      IsCrossOriginSameSiteNetworkAccessWithStorageAccessEligible(
+          cookie_details);
+  bool cross_origin_same_site_cookie_via_storage_access_api = false;
+
   for (const network::mojom::CookieOrLineWithAccessResultPtr& cookie :
        cookie_details->cookie_list) {
     const net::CookieInclusionStatus& status = cookie->access_result.status;
@@ -499,6 +522,12 @@
       cookie_has_not_been_refreshed_in_351_to_400_days |=
           days_since_refresh > 350 && days_since_refresh <= 400;
     }
+
+    cross_origin_same_site_cookie_via_storage_access_api |=
+        cross_origin_same_site_with_storage_access_eligible &&
+        cookie->access_result.status.IsInclude() &&
+        cookie->access_result.status.exemption_reason() ==
+            net::CookieInclusionStatus::ExemptionReason::kStorageAccess;
   }
 
   if (samesite_treated_as_lax_cookies) {
@@ -560,6 +589,12 @@
         rfh->GetPageUkmSourceId(),
         cookies_exempted_by_top_level_storage_access);
   }
+
+  if (cross_origin_same_site_cookie_via_storage_access_api) {
+    GetContentClient()->browser()->LogWebFeatureForCurrentPage(
+        rfh, blink::mojom::WebFeature::
+                 kCrossOriginSameSiteCookieAccessViaStorageAccessAPI);
+  }
 }
 
 }  // namespace content
diff --git a/content/browser/renderer_host/render_frame_host_impl_browsertest.cc b/content/browser/renderer_host/render_frame_host_impl_browsertest.cc
index 9b0f224..60f28af 100644
--- a/content/browser/renderer_host/render_frame_host_impl_browsertest.cc
+++ b/content/browser/renderer_host/render_frame_host_impl_browsertest.cc
@@ -3629,8 +3629,9 @@
   EXPECT_TRUE(ExecJs(root_frame_host(),
                      "navigation.addEventListener('navigate',"
                      "  e => { e.intercept({"
-                     "    commit: 'after-transition',"
-                     "    handler: () => new Promise(r => setTimeout(r, 100))"
+                     "    precommitHandler: async() => {"
+                     "      await new Promise(r => setTimeout(r, 100));"
+                     "    }"
                      "  }); "
                      "  setTimeout(() => navigation.navigate('#allowed'), 0);"
                      "}, { once: true });"));
@@ -3662,8 +3663,7 @@
   EXPECT_TRUE(ExecJs(root_frame_host(),
                      "navigation.addEventListener('navigate',"
                      "  e => { e.intercept({"
-                     "    commit: 'after-transition',"
-                     "    handler: () => Promise.reject()"
+                     "    precommitHandler: () => Promise.reject()"
                      "  }); "
                      "});"));
   GURL blocked_url(
diff --git a/content/browser/renderer_host/view_transition_browsertest.cc b/content/browser/renderer_host/view_transition_browsertest.cc
index dfa2937..f458415 100644
--- a/content/browser/renderer_host/view_transition_browsertest.cc
+++ b/content/browser/renderer_host/view_transition_browsertest.cc
@@ -457,14 +457,14 @@
 
   // Sanity to see that we've captured something.
   ASSERT_NE(before_bitmap.getColor(5, 5), 0u);
-  // This starts a view transition with a "hanging" promise that never resolves.
-  // When the view-transition callback is called, we resolve the external
-  // promise that signals us that it's time to capture.
+  // This starts a view transition with a callback that signals that we're ok
+  // to capture, but otherwise never finishes running the callback.
   ASSERT_EQ(EvalJs(web_contents, JsReplace(R"(
-              new Promise(ready_to_capture => {
-                document.startViewTransition(() => new Promise(() => {
-                    ready_to_capture('ok');
-                }));
+              new Promise(dom_callback_started => {
+                document.startViewTransition(async () => {
+                  dom_callback_started('ok');
+                  await new Promise(() => {});
+                });
               }))")),
             "ok");
   WaitForSurfaceAnimationManager(
diff --git a/content/browser/service_worker/service_worker_client.h b/content/browser/service_worker/service_worker_client.h
index 9f686512..27a9fd4 100644
--- a/content/browser/service_worker/service_worker_client.h
+++ b/content/browser/service_worker/service_worker_client.h
@@ -75,7 +75,7 @@
   ServiceWorkerClient(const ServiceWorkerClient& other) = delete;
   ServiceWorkerClient& operator=(const ServiceWorkerClient& other) = delete;
 
-  virtual ~ServiceWorkerClient();
+  ~ServiceWorkerClient();
 
   ServiceWorkerContainerHostForClient* container_host() {
     return container_host_.get();
diff --git a/content/browser/tracing/tracing_end_to_end_browsertest.cc b/content/browser/tracing/tracing_end_to_end_browsertest.cc
index c22b1aab..67496a5 100644
--- a/content/browser/tracing/tracing_end_to_end_browsertest.cc
+++ b/content/browser/tracing/tracing_end_to_end_browsertest.cc
@@ -21,6 +21,7 @@
 #include "content/public/test/content_browser_test_utils.h"
 #include "services/resource_coordinator/public/cpp/memory_instrumentation/tracing_observer_proto.h"
 #include "services/tracing/public/cpp/perfetto/metadata_data_source.h"
+#include "services/tracing/public/cpp/perfetto/track_name_recorder.h"
 #include "services/tracing/public/cpp/stack_sampling/tracing_sampler_profiler.h"
 #include "services/tracing/public/mojom/perfetto_service.mojom.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -476,7 +477,7 @@
 #if BUILDFLAG(IS_ANDROID)
 IN_PROC_BROWSER_TEST_F(TracingEndToEndBrowserTest,
                        PackageNameRecordedTraceLogSet) {
-  base::trace_event::TraceLog::GetInstance()->SetRecordHostAppPackageName(true);
+  tracing::TrackNameRecorder::GetInstance()->SetRecordHostAppPackageName(true);
   base::test::TestTraceProcessor ttp;
   ttp.StartTrace(base::test::DefaultTraceConfig("foo", false),
                  perfetto::kCustomBackend);
@@ -508,8 +509,7 @@
 
 IN_PROC_BROWSER_TEST_F(TracingEndToEndBrowserTest,
                        PackageNameNotRecordedTraceLogNotSet) {
-  base::trace_event::TraceLog::GetInstance()->SetRecordHostAppPackageName(
-      false);
+  tracing::TrackNameRecorder::GetInstance()->SetRecordHostAppPackageName(false);
   base::test::TestTraceProcessor ttp;
   ttp.StartTrace(base::test::DefaultTraceConfig("foo", false),
                  perfetto::kCustomBackend);
diff --git a/content/browser/web_contents/web_contents_observer_browsertest.cc b/content/browser/web_contents/web_contents_observer_browsertest.cc
index 77ff411..04c3261 100644
--- a/content/browser/web_contents/web_contents_observer_browsertest.cc
+++ b/content/browser/web_contents/web_contents_observer_browsertest.cc
@@ -471,7 +471,7 @@
                       net::CookieEffectiveSameSite::LAX_MODE_ALLOW_UNSAFE,
                       net::CookieInclusionStatus(),
                       net::CookieAccessSemantics::NONLEGACY,
-                      net::CookieScopeSemantics::UNKNOWN, false)}));
+                      net::CookieScopeSemantics::NONLEGACY, false)}));
   cookie_tracker.cookie_accesses().clear();
 }
 
@@ -529,7 +529,7 @@
                            net::CookieEffectiveSameSite::LAX_MODE_ALLOW_UNSAFE,
                            net::CookieInclusionStatus(),
                            net::CookieAccessSemantics::NONLEGACY,
-                           net::CookieScopeSemantics::UNKNOWN, false)}));
+                           net::CookieScopeSemantics::NONLEGACY, false)}));
   cookie_tracker.cookie_accesses().clear();
 
   // 2) Navigate to another url on the same site and expect a notification about
@@ -551,7 +551,7 @@
                       net::CookieEffectiveSameSite::LAX_MODE_ALLOW_UNSAFE,
                       net::CookieInclusionStatus(),
                       net::CookieAccessSemantics::NONLEGACY,
-                      net::CookieScopeSemantics::UNKNOWN, false)}));
+                      net::CookieScopeSemantics::NONLEGACY, false)}));
   cookie_tracker.cookie_accesses().clear();
 }
 
@@ -619,7 +619,7 @@
                            net::CookieEffectiveSameSite::LAX_MODE_ALLOW_UNSAFE,
                            net::CookieInclusionStatus(),
                            net::CookieAccessSemantics::NONLEGACY,
-                           net::CookieScopeSemantics::UNKNOWN, false)},
+                           net::CookieScopeSemantics::NONLEGACY, false)},
           CookieAccess{CookieAccessDetails::Type::kRead,
                        ContextType::kNavigation,
                        {},
@@ -632,7 +632,7 @@
                            net::CookieEffectiveSameSite::LAX_MODE_ALLOW_UNSAFE,
                            net::CookieInclusionStatus(),
                            net::CookieAccessSemantics::NONLEGACY,
-                           net::CookieScopeSemantics::UNKNOWN, false)}));
+                           net::CookieScopeSemantics::NONLEGACY, false)}));
   cookie_tracker.cookie_accesses().clear();
 }
 
@@ -697,7 +697,7 @@
                            net::CookieEffectiveSameSite::LAX_MODE_ALLOW_UNSAFE,
                            net::CookieInclusionStatus(),
                            net::CookieAccessSemantics::NONLEGACY,
-                           net::CookieScopeSemantics::UNKNOWN, false)},
+                           net::CookieScopeSemantics::NONLEGACY, false)},
           CookieAccess{CookieAccessDetails::Type::kRead, ContextType::kFrame,
                        cookie_tracker.frame_id(frame_id_index), -1, url2_image,
                        first_party_url, "foo", "bar",
@@ -705,7 +705,7 @@
                            net::CookieEffectiveSameSite::LAX_MODE_ALLOW_UNSAFE,
                            net::CookieInclusionStatus(),
                            net::CookieAccessSemantics::NONLEGACY,
-                           net::CookieScopeSemantics::UNKNOWN, false)}));
+                           net::CookieScopeSemantics::NONLEGACY, false)}));
   cookie_tracker.cookie_accesses().clear();
 }
 
@@ -744,7 +744,7 @@
               net::CookieEffectiveSameSite::LAX_MODE_ALLOW_UNSAFE,
               net::CookieInclusionStatus(),
               net::CookieAccessSemantics::NONLEGACY,
-              net::CookieScopeSemantics::UNKNOWN, false)}));
+              net::CookieScopeSemantics::NONLEGACY, false)}));
   cookie_tracker.cookie_accesses().clear();
 }
 
@@ -782,21 +782,21 @@
                                  "foo=bar;SameSite=None;Secure"));
   EXPECT_TRUE(NavigateToURL(web_contents(), url_a_check_cookie));
   cookie_tracker.WaitForCookies(1);
-  EXPECT_THAT(
-      cookie_tracker.cookie_accesses(),
-      testing::ElementsAre(CookieAccess{
-          CookieAccessDetails::Type::kRead,
-          ContextType::kNavigation,
-          {},
-          cookie_tracker.navigation_id(0),
-          url_a_check_cookie,
-          url_a,
-          "foo",
-          "bar",
-          net::CookieAccessResult(net::CookieEffectiveSameSite::NO_RESTRICTION,
-                                  net::CookieInclusionStatus(),
-                                  net::CookieAccessSemantics::NONLEGACY,
-                                  net::CookieScopeSemantics::UNKNOWN, true)}));
+  EXPECT_THAT(cookie_tracker.cookie_accesses(),
+              testing::ElementsAre(CookieAccess{
+                  CookieAccessDetails::Type::kRead,
+                  ContextType::kNavigation,
+                  {},
+                  cookie_tracker.navigation_id(0),
+                  url_a_check_cookie,
+                  url_a,
+                  "foo",
+                  "bar",
+                  net::CookieAccessResult(
+                      net::CookieEffectiveSameSite::NO_RESTRICTION,
+                      net::CookieInclusionStatus(),
+                      net::CookieAccessSemantics::NONLEGACY,
+                      net::CookieScopeSemantics::NONLEGACY, true)}));
   cookie_tracker.cookie_accesses().clear();
 
   // 2) Navigate to |url_b_cross_site|. This page should load b.test(a.test)
@@ -819,7 +819,7 @@
                                   net::CookieInclusionStatus::ExclusionReason::
                                       EXCLUDE_THIRD_PARTY_PHASEOUT}),
                           net::CookieAccessSemantics::NONLEGACY,
-                          net::CookieScopeSemantics::UNKNOWN, true)),
+                          net::CookieScopeSemantics::NONLEGACY, true)),
                   MatchesCookieAccess(
                       CookieAccessDetails::Type::kRead, testing::_, testing::_,
                       testing::_, testing::_, testing::_, "foo", "bar",
@@ -830,7 +830,7 @@
                                   net::CookieInclusionStatus::ExclusionReason::
                                       EXCLUDE_THIRD_PARTY_PHASEOUT}),
                           net::CookieAccessSemantics::NONLEGACY,
-                          net::CookieScopeSemantics::UNKNOWN, true))));
+                          net::CookieScopeSemantics::NONLEGACY, true))));
   cookie_tracker.cookie_accesses().clear();
 }
 namespace {
diff --git a/content/browser/webid/fake_identity_request_dialog_controller.cc b/content/browser/webid/fake_identity_request_dialog_controller.cc
index c6e51881f..73189685 100644
--- a/content/browser/webid/fake_identity_request_dialog_controller.cc
+++ b/content/browser/webid/fake_identity_request_dialog_controller.cc
@@ -23,7 +23,7 @@
     default;
 
 bool FakeIdentityRequestDialogController::ShowAccountsDialog(
-    const std::string& rp_for_display,
+    content::RelyingPartyData rp_data,
     const std::vector<IdentityProviderDataPtr>& idp_list,
     const std::vector<IdentityRequestAccountPtr>& accounts,
     content::IdentityRequestAccount::SignInMode sign_in_mode,
diff --git a/content/browser/webid/fake_identity_request_dialog_controller.h b/content/browser/webid/fake_identity_request_dialog_controller.h
index e8660903..2294a98 100644
--- a/content/browser/webid/fake_identity_request_dialog_controller.h
+++ b/content/browser/webid/fake_identity_request_dialog_controller.h
@@ -37,7 +37,7 @@
   ~FakeIdentityRequestDialogController() override;
 
   bool ShowAccountsDialog(
-      const std::string& rp_for_display,
+      content::RelyingPartyData rp_data,
       const std::vector<IdentityProviderDataPtr>& idp_list,
       const std::vector<IdentityRequestAccountPtr>& accounts,
       IdentityRequestAccount::SignInMode sign_in_mode,
diff --git a/content/browser/webid/fedcm_metrics.cc b/content/browser/webid/fedcm_metrics.cc
index 9e9d252..c964d5f 100644
--- a/content/browser/webid/fedcm_metrics.cc
+++ b/content/browser/webid/fedcm_metrics.cc
@@ -785,6 +785,16 @@
   RecordUkm(fedcm_builder);
 }
 
+void FedCmMetrics::RecordIdentityProvidersCount(int count) {
+  CHECK_GT(count, 0);
+  base::UmaHistogramCounts100("Blink.FedCm.IdentityProvidersCount", count);
+  ukm::builders::Blink_FedCm fedcm_builder(page_source_id_);
+  fedcm_builder.SetIdentityProvidersCount(
+      ukm::GetExponentialBucketMin(count, /*bucket_spacing=*/1.3));
+  fedcm_builder.SetFedCmSessionID(session_id_);
+  fedcm_builder.Record(ukm::UkmRecorder::Get());
+}
+
 ukm::SourceId FedCmMetrics::GetOrCreateProviderSourceId(const GURL& provider) {
   auto it = provider_source_ids_.find(provider);
   if (it != provider_source_ids_.end()) {
@@ -859,9 +869,4 @@
                                  /*exclusive_max=*/10, /*buckets=*/10);
 }
 
-void RecordIdentityProvidersCount(int count) {
-  CHECK_GT(count, 0);
-  base::UmaHistogramCounts100("Blink.FedCm.IdentityProvidersCount", count);
-}
-
 }  // namespace content
diff --git a/content/browser/webid/fedcm_metrics.h b/content/browser/webid/fedcm_metrics.h
index 6b850dc..de562b4 100644
--- a/content/browser/webid/fedcm_metrics.h
+++ b/content/browser/webid/fedcm_metrics.h
@@ -507,6 +507,9 @@
   // Records the page scroll Y-axis position upon account selection.
   void RecordAccountSelectionScrollPosition(const gfx::Point& scroll_position);
 
+  // Records the count of identity providers in the request
+  void RecordIdentityProvidersCount(int count);
+
   int session_id() { return session_id_; }
 
  private:
@@ -564,9 +567,6 @@
 // filter. If no account left, nothing will be recorded.
 void RecordReadyToShowAccountsSize(int size);
 
-// Records the count of identity providers in the request
-void RecordIdentityProvidersCount(int count);
-
 }  // namespace content
 
 #endif  // CONTENT_BROWSER_WEBID_FEDCM_METRICS_H_
diff --git a/content/browser/webid/federated_auth_request_impl.cc b/content/browser/webid/federated_auth_request_impl.cc
index a928dff5..b9058416 100644
--- a/content/browser/webid/federated_auth_request_impl.cc
+++ b/content/browser/webid/federated_auth_request_impl.cc
@@ -1072,7 +1072,7 @@
   }
 
   if (IsFedCmMultipleIdentityProvidersEnabled()) {
-    RecordIdentityProvidersCount(idp_order_.size());
+    fedcm_metrics_->RecordIdentityProvidersCount(idp_order_.size());
   }
 
   CHECK(!unique_idps.empty());
@@ -1702,6 +1702,7 @@
     }
   }
   idp_accounts_.clear();
+
   std::stable_sort(
       accounts_.begin(), accounts_.end(),
       [&](const auto& account1, const auto& account2) {
@@ -1905,7 +1906,8 @@
   // so invocations after this method should assume that the members may have
   // been cleaned up.
   if (!request_dialog_controller_->ShowAccountsDialog(
-          GetTopFrameOriginForDisplay(GetEmbeddingOrigin()),
+          std::move(content::RelyingPartyData(
+              GetTopFrameOriginForDisplay(GetEmbeddingOrigin()))),
           idp_data_for_display_, accounts_,
           identity_selection_type_ == kExplicit ? SignInMode::kExplicit
                                                 : SignInMode::kAuto,
diff --git a/content/browser/webid/federated_auth_request_impl.h b/content/browser/webid/federated_auth_request_impl.h
index ec6ff2c..64fc0a36 100644
--- a/content/browser/webid/federated_auth_request_impl.h
+++ b/content/browser/webid/federated_auth_request_impl.h
@@ -351,6 +351,10 @@
       std::unique_ptr<IdentityProviderInfo> idp_info,
       const std::vector<IdentityRequestAccountPtr>& accounts,
       IdpNetworkRequestManager::ClientMetadata&& client_metadata);
+  // Fetches the given `url` and stores the result in `downloaded_images_`. Runs
+  // the `callback` exactly once regardless of whether the GURL is valid or the
+  // fetch result.
+  void FetchImage(const GURL& url, base::OnceClosure callback);
   void OnImageReceived(base::OnceClosure callback,
                        GURL url,
                        const gfx::Image& image);
@@ -358,10 +362,7 @@
       std::unique_ptr<IdentityProviderInfo> idp_info,
       std::vector<IdentityRequestAccountPtr>&& accounts,
       const IdpNetworkRequestManager::ClientMetadata& client_metadata);
-  // Fetches the given `url` and stores the result in `downloaded_images_`. Runs
-  // the `callback` exactly once regardless of whether the GURL is valid or the
-  // fetch result.
-  void FetchImage(const GURL& url, base::OnceClosure callback);
+
   void OnAccountSelected(const GURL& idp_config_url,
                          const std::string& account_id,
                          bool is_sign_in);
diff --git a/content/browser/webid/federated_auth_request_impl_multiple_frames_unittest.cc b/content/browser/webid/federated_auth_request_impl_multiple_frames_unittest.cc
index 2893d53..30d548d 100644
--- a/content/browser/webid/federated_auth_request_impl_multiple_frames_unittest.cc
+++ b/content/browser/webid/federated_auth_request_impl_multiple_frames_unittest.cc
@@ -158,7 +158,7 @@
   TestDialogController& operator=(TestDialogController&) = delete;
 
   bool ShowAccountsDialog(
-      const std::string& rp_for_display,
+      content::RelyingPartyData rp_data,
       const std::vector<IdentityProviderDataPtr>& idp_list,
       const std::vector<IdentityRequestAccountPtr>& accounts,
       IdentityRequestAccount::SignInMode sign_in_mode,
@@ -170,7 +170,7 @@
       IdentityRequestDialogController::AccountsDisplayedCallback
           accounts_displayed_callback) override {
     state_->did_show_accounts_dialog = true;
-    state_->rp_for_display = rp_for_display;
+    state_->rp_for_display = rp_data.rp_for_display;
     if (accounts_dialog_action_ == AccountsDialogAction::kSelectAccount) {
       std::move(on_selected)
           .Run(GURL(kProviderUrlFull), kAccountId, /*is_sign_in=*/true);
diff --git a/content/browser/webid/federated_auth_request_impl_unittest.cc b/content/browser/webid/federated_auth_request_impl_unittest.cc
index fb0739e4..3beccc5 100644
--- a/content/browser/webid/federated_auth_request_impl_unittest.cc
+++ b/content/browser/webid/federated_auth_request_impl_unittest.cc
@@ -618,7 +618,7 @@
   }
 
   bool ShowAccountsDialog(
-      const std::string& rp_for_display,
+      content::RelyingPartyData rp_data,
       const std::vector<IdentityProviderDataPtr>& idp_list,
       const std::vector<IdentityRequestAccountPtr>& accounts,
       IdentityRequestAccount::SignInMode sign_in_mode,
@@ -3554,7 +3554,7 @@
       DisableApiWhenDialogShownDialogController&) = delete;
 
   bool ShowAccountsDialog(
-      const std::string& rp_for_display,
+      content::RelyingPartyData rp_data,
       const std::vector<IdentityProviderDataPtr>& idp_list,
       const std::vector<IdentityRequestAccountPtr>& accounts,
       SignInMode sign_in_mode,
@@ -3571,8 +3571,8 @@
 
     // Call parent class method in order to store callback parameters.
     return TestDialogController::ShowAccountsDialog(
-        rp_for_display, idp_list, accounts, sign_in_mode, rp_mode, new_accounts,
-        std::move(on_selected), std::move(on_add_account),
+        std::move(rp_data), idp_list, accounts, sign_in_mode, rp_mode,
+        new_accounts, std::move(on_selected), std::move(on_add_account),
         std::move(dismiss_callback), std::move(accounts_displayed_callback));
   }
 
@@ -4449,6 +4449,8 @@
 
   histogram_tester_.ExpectUniqueSample("Blink.FedCm.IdentityProvidersCount", 2,
                                        1);
+  ExpectUKMCount("IdentityProvidersCount", FedCmEntry::kEntryName, 1);
+  ExpectUkmValueInEntry("IdentityProvidersCount", FedCmEntry::kEntryName, 2);
 }
 
 // Test successful multi IDP FedCM request.
@@ -4485,6 +4487,8 @@
   ExpectUkmValue("NumIdpsMismatch", 0);
   histogram_tester_.ExpectUniqueSample("Blink.FedCm.IdentityProvidersCount", 2,
                                        1);
+  ExpectUKMCount("IdentityProvidersCount", FedCmEntry::kEntryName, 1);
+  ExpectUkmValueInEntry("IdentityProvidersCount", FedCmEntry::kEntryName, 2);
 }
 
 // Test fetching information for the 1st IdP failing, and succeeding for the
@@ -4516,6 +4520,8 @@
   histogram_tester_.ExpectTotalCount("Blink.FedCm.AccountsRequestSent", 1);
   histogram_tester_.ExpectUniqueSample("Blink.FedCm.IdentityProvidersCount", 2,
                                        1);
+  ExpectUKMCount("IdentityProvidersCount", FedCmEntry::kEntryName, 1);
+  ExpectUkmValueInEntry("IdentityProvidersCount", FedCmEntry::kEntryName, 2);
 }
 
 // Test fetching information for the 1st IdP succeeding, and failing for the
@@ -4547,6 +4553,8 @@
   histogram_tester_.ExpectTotalCount("Blink.FedCm.AccountsRequestSent", 1);
   histogram_tester_.ExpectUniqueSample("Blink.FedCm.IdentityProvidersCount", 2,
                                        1);
+  ExpectUKMCount("IdentityProvidersCount", FedCmEntry::kEntryName, 1);
+  ExpectUkmValueInEntry("IdentityProvidersCount", FedCmEntry::kEntryName, 2);
 }
 
 // Test fetching information for all of the IdPs failing.
@@ -4577,6 +4585,8 @@
   histogram_tester_.ExpectTotalCount("Blink.FedCm.AccountsRequestSent", 0);
   histogram_tester_.ExpectUniqueSample("Blink.FedCm.IdentityProvidersCount", 2,
                                        1);
+  ExpectUKMCount("IdentityProvidersCount", FedCmEntry::kEntryName, 1);
+  ExpectUkmValueInEntry("IdentityProvidersCount", FedCmEntry::kEntryName, 2);
 }
 
 // Test multi IDP FedCM request with duplicate IDPs should throw an error.
@@ -4600,6 +4610,7 @@
   EXPECT_FALSE(did_show_accounts_dialog());
 
   histogram_tester_.ExpectTotalCount("Blink.FedCm.IdentityProvidersCount", 0);
+  ExpectUKMCount("IdentityProvidersCount", FedCmEntry::kEntryName, 0);
 }
 
 // Test that API can succeed with multiple IdPs, if one IdP is signed out but
@@ -7718,7 +7729,7 @@
       TestDialogControllerWithImmediateDismiss&) = delete;
 
   bool ShowAccountsDialog(
-      const std::string& rp_for_display,
+      content::RelyingPartyData rp_data,
       const std::vector<IdentityProviderDataPtr>& idp_list,
       const std::vector<IdentityRequestAccountPtr>& accounts,
       IdentityRequestAccount::SignInMode sign_in_mode,
diff --git a/content/browser/webid/test/mock_identity_request_dialog_controller.h b/content/browser/webid/test/mock_identity_request_dialog_controller.h
index a6e93da..59f909f 100644
--- a/content/browser/webid/test/mock_identity_request_dialog_controller.h
+++ b/content/browser/webid/test/mock_identity_request_dialog_controller.h
@@ -29,7 +29,7 @@
 
   MOCK_METHOD(bool,
               ShowAccountsDialog,
-              (const std::string&,
+              (content::RelyingPartyData,
                const std::vector<IdentityProviderDataPtr>&,
                const std::vector<IdentityRequestAccountPtr>&,
                IdentityRequestAccount::SignInMode,
diff --git a/content/public/android/java/src/org/chromium/content/browser/ContentUiEventHandler.java b/content/public/android/java/src/org/chromium/content/browser/ContentUiEventHandler.java
index 955daad9..9f7fe27 100644
--- a/content/public/android/java/src/org/chromium/content/browser/ContentUiEventHandler.java
+++ b/content/public/android/java/src/org/chromium/content/browser/ContentUiEventHandler.java
@@ -25,12 +25,11 @@
 import org.chromium.content_public.browser.ViewEventSink.InternalAccessDelegate;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.content_public.browser.WebContents.UserDataFactory;
-import org.chromium.ui.MotionEventUtils;
 import org.chromium.ui.base.EventForwarder;
+import org.chromium.ui.util.MotionEventUtils;
 
 /**
- * Called from native to handle UI events that need access to various Java layer
- * content components.
+ * Called from native to handle UI events that need access to various Java layer content components.
  */
 @JNINamespace("content")
 @NullMarked
diff --git a/content/public/android/java/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityImpl.java b/content/public/android/java/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityImpl.java
index 3d068ab..7802359 100644
--- a/content/public/android/java/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityImpl.java
+++ b/content/public/android/java/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityImpl.java
@@ -804,11 +804,10 @@
             // Update the browser-level AXMode based on running applications.
             WebContentsAccessibilityImplJni.get()
                     .setBrowserAXMode(
-                            WebContentsAccessibilityImpl.this,
+                            mNativeObj,
                             AccessibilityState.isScreenReaderEnabled(),
                             AccessibilityState.isOnlyPasswordManagersEnabled(),
-                            AccessibilityState.isScreenReaderRunning(),
-                            /* isAccessibilityEnabled= */ true);
+                            AccessibilityState.isScreenReaderRunning());
 
             // Update the state of enabling/disabling the image descriptions feature. To enable the
             // feature, this instance must be a candidate and a screen reader must be enabled.
@@ -2300,11 +2299,10 @@
         void connectInstanceToRootManager(long nativeWebContentsAccessibilityAndroid);
 
         void setBrowserAXMode(
-                WebContentsAccessibilityImpl caller,
+                long nativeWebContentsAccessibilityAndroid,
                 boolean screenReaderMode,
                 boolean formControlsMode,
-                boolean isScreenReaderRunning,
-                boolean isAccessibilityEnabled);
+                boolean isScreenReaderRunning);
 
         void disableRendererAccessibility(long nativeWebContentsAccessibilityAndroid);
 
diff --git a/content/public/android/junit/src/org/chromium/content/browser/ContentUiEventHandlerTest.java b/content/public/android/junit/src/org/chromium/content/browser/ContentUiEventHandlerTest.java
index 43fbf77..832d8dbb 100644
--- a/content/public/android/junit/src/org/chromium/content/browser/ContentUiEventHandlerTest.java
+++ b/content/public/android/junit/src/org/chromium/content/browser/ContentUiEventHandlerTest.java
@@ -26,8 +26,8 @@
 import org.chromium.content.browser.webcontents.WebContentsImpl;
 import org.chromium.content.browser.webcontents.WebContentsImplJni;
 import org.chromium.content_public.browser.NavigationController;
-import org.chromium.ui.MotionEventUtils;
 import org.chromium.ui.base.EventForwarder;
+import org.chromium.ui.util.MotionEventUtils;
 
 /** Unit tests for {@link ContentUiEventHandler} */
 @RunWith(BaseRobolectricTestRunner.class)
diff --git a/content/public/browser/browser_accessibility_state.h b/content/public/browser/browser_accessibility_state.h
index 3d58d2d..c808068 100644
--- a/content/public/browser/browser_accessibility_state.h
+++ b/content/public/browser/browser_accessibility_state.h
@@ -104,9 +104,6 @@
   // to this state.
   virtual ui::AssistiveTech ActiveAssistiveTech() const = 0;
 
-  // Returns true if the browser should be customized for accessibility.
-  virtual bool IsAccessibleBrowser() = 0;
-
   // Update BrowserAccessibilityState with the current status of performance
   // filtering.
   virtual void SetPerformanceFilteringAllowed(bool allowed) = 0;
diff --git a/content/public/browser/identity_request_dialog_controller.cc b/content/public/browser/identity_request_dialog_controller.cc
index 8f6694c1..d10efd7 100644
--- a/content/public/browser/identity_request_dialog_controller.cc
+++ b/content/public/browser/identity_request_dialog_controller.cc
@@ -43,6 +43,11 @@
 
 IdentityProviderData::~IdentityProviderData() = default;
 
+RelyingPartyData::RelyingPartyData(const std::string& rp_for_display)
+    : rp_for_display(rp_for_display) {}
+RelyingPartyData::RelyingPartyData(const RelyingPartyData& other) = default;
+RelyingPartyData::~RelyingPartyData() = default;
+
 int IdentityRequestDialogController::GetBrandIconIdealSize(
     blink::mojom::RpMode rp_mode) {
   return 0;
@@ -58,7 +63,7 @@
 }
 
 bool IdentityRequestDialogController::ShowAccountsDialog(
-    const std::string& rp_for_display,
+    content::RelyingPartyData rp_data,
     const std::vector<scoped_refptr<content::IdentityProviderData>>& idp_list,
     const std::vector<scoped_refptr<content::IdentityRequestAccount>>& accounts,
     content::IdentityRequestAccount::SignInMode sign_in_mode,
diff --git a/content/public/browser/identity_request_dialog_controller.h b/content/public/browser/identity_request_dialog_controller.h
index 5223a8c..b7d2ca8 100644
--- a/content/public/browser/identity_request_dialog_controller.h
+++ b/content/public/browser/identity_request_dialog_controller.h
@@ -33,6 +33,9 @@
   kUsername
 };
 
+// The client metadata that will be used to display a FedCM dialog. This data is
+// extracted from the client metadata endpoint from the FedCM API, where
+// 'client' is essentially the relying party which invoked the API.
 struct CONTENT_EXPORT ClientMetadata {
   ClientMetadata(const GURL& terms_of_service_url,
                  const GURL& privacy_policy_url,
@@ -48,11 +51,18 @@
   gfx::Image brand_decoded_icon;
 };
 
+// The information about an error that will be used to display a FedCM dialog.
+// This data is extracted from the error object returned by the identity
+// provider when the user attempts to login via the FedCM API and an error
+// occurs.
 struct CONTENT_EXPORT IdentityCredentialTokenError {
   std::string code;
   GURL url;
 };
 
+// The metadata about the identity provider that will be used to display a FedCM
+// dialog. This data is extracted from the config file which is fetched when the
+// FedCM API is invoked.
 struct CONTENT_EXPORT IdentityProviderMetadata {
   IdentityProviderMetadata();
   IdentityProviderMetadata(const IdentityProviderMetadata& other);
@@ -81,6 +91,10 @@
   gfx::Image brand_decoded_icon;
 };
 
+// This class contains all of the data specific to an identity provider that is
+// going to be used to display a FedCM dialog. This data is gathered from
+// endpoints fetched when the FedCM API is invoked as well as from the
+// parameters provided by the relying party when the API is invoked.
 class CONTENT_EXPORT IdentityProviderData
     : public base::RefCounted<IdentityProviderData> {
  public:
@@ -109,6 +123,19 @@
   ~IdentityProviderData();
 };
 
+// The relying party data that will be used to display a FedCM dialog. This data
+// is extracted from the website which invoked the API, not from the FedCM
+// endpoints themselves.
+struct CONTENT_EXPORT RelyingPartyData {
+ public:
+  explicit RelyingPartyData(const std::string& rp_for_display);
+  RelyingPartyData(const RelyingPartyData& other);
+  ~RelyingPartyData();
+
+  std::string rp_for_display;
+  gfx::Image rp_icon;
+};
+
 // IdentityRequestDialogController is an interface, overridden and implemented
 // by embedders, that controls the UI surfaces that are displayed to
 // intermediate the exchange of federated accounts between identity providers
@@ -181,16 +208,15 @@
   // `new_accounts` are the accounts that were just logged in, which should
   // be prioritized in the UI. Returns true if the method successfully showed
   // UI. When false, the caller should assume that the API invocation was
-  // terminated and the cleanup methods invoked.
+  // terminated and the cleanup methods invoked. `rp_data` may be modified by
+  // this method, such as by setting the RP icon.
   virtual bool ShowAccountsDialog(
-      const std::string& rp_for_display,
-      const std::vector<scoped_refptr<content::IdentityProviderData>>& idp_list,
-      const std::vector<scoped_refptr<content::IdentityRequestAccount>>&
-          accounts,
-      content::IdentityRequestAccount::SignInMode sign_in_mode,
+      RelyingPartyData rp_data,
+      const std::vector<scoped_refptr<IdentityProviderData>>& idp_list,
+      const std::vector<scoped_refptr<IdentityRequestAccount>>& accounts,
+      IdentityRequestAccount::SignInMode sign_in_mode,
       blink::mojom::RpMode rp_mode,
-      const std::vector<scoped_refptr<content::IdentityRequestAccount>>&
-          new_accounts,
+      const std::vector<scoped_refptr<IdentityRequestAccount>>& new_accounts,
       AccountSelectionCallback on_selected,
       LoginToIdPCallback on_add_account,
       DismissCallback dismiss_callback,
diff --git a/content/public/common/content_switch_dependent_feature_overrides.cc b/content/public/common/content_switch_dependent_feature_overrides.cc
index 35db8043..1ed6ca09 100644
--- a/content/public/common/content_switch_dependent_feature_overrides.cc
+++ b/content/public/common/content_switch_dependent_feature_overrides.cc
@@ -46,10 +46,6 @@
        std::cref(features::kOriginIsolationHeader),
        base::FeatureList::OVERRIDE_ENABLE_FEATURE},
       {switches::kEnableExperimentalWebPlatformFeatures,
-       std::cref(
-           blink::features::kDocumentPolicyIncludeJSCallStacksInCrashReports),
-       base::FeatureList::OVERRIDE_ENABLE_FEATURE},
-      {switches::kEnableExperimentalWebPlatformFeatures,
        std::cref(features::kEnableCanvas2DLayers),
        base::FeatureList::OVERRIDE_ENABLE_FEATURE},
       {switches::kEnableExperimentalWebPlatformFeatures,
diff --git a/content/renderer/accessibility/annotations/ax_image_annotator.h b/content/renderer/accessibility/annotations/ax_image_annotator.h
index c461af9..c5003e5 100644
--- a/content/renderer/accessibility/annotations/ax_image_annotator.h
+++ b/content/renderer/accessibility/annotations/ax_image_annotator.h
@@ -66,7 +66,7 @@
   class ImageInfo final {
    public:
     ImageInfo(const blink::WebAXObject& image);
-    virtual ~ImageInfo();
+    ~ImageInfo();
 
     mojo::PendingRemote<image_annotation::mojom::ImageProcessor>
     GetImageProcessor();
diff --git a/content/services/auction_worklet/public/mojom/auction_shared_storage_host.mojom b/content/services/auction_worklet/public/mojom/auction_shared_storage_host.mojom
index 9d5f61b..9b65451 100644
--- a/content/services/auction_worklet/public/mojom/auction_shared_storage_host.mojom
+++ b/content/services/auction_worklet/public/mojom/auction_shared_storage_host.mojom
@@ -28,7 +28,7 @@
   // acquired on the resource with name `with_lock`. `with_lock` shouldn't start
   // with '-'.
   SharedStorageBatchUpdate(
-    array<network.mojom.SharedStorageModifierMethodWithOptions> methods_with_options,
+    network.mojom.SharedStorageBatchUpdateMethodsArgument methods_with_options,
     network.mojom.LockName? with_lock,
     AuctionWorkletFunction source_auction_worklet_function);
 };
diff --git a/content/test/gpu/gpu_tests/webgpu_cts_integration_test_base.py b/content/test/gpu/gpu_tests/webgpu_cts_integration_test_base.py
index ac32d25..3d055f6 100644
--- a/content/test/gpu/gpu_tests/webgpu_cts_integration_test_base.py
+++ b/content/test/gpu/gpu_tests/webgpu_cts_integration_test_base.py
@@ -108,6 +108,7 @@
   _use_fxc = False
   _os_name: str | None = None
   _worker_type: WorkerType | None = None
+  _force_unroll_const_eval_loops = False
 
   _build_dir: str | None = None
 
@@ -185,6 +186,11 @@
               'all tests outside of workers. If a worker type is specified, '
               'then a subset of tests will be run in the specified worker '
               'type.'))
+    parser.add_argument(
+        '--force-unroll-const-eval-loops',
+        action='store_true',
+        default=False,
+        help='Force use of the unrollConstEvalLoops setting in JavaScript.')
 
   @classmethod
   def StartBrowser(cls) -> None:
@@ -275,6 +281,14 @@
     cls._use_webgpu_power_preference = options.use_webgpu_power_preference
     cls._use_fxc = options.use_fxc
     cls._worker_type = WorkerType(options.use_worker)
+    cls._force_unroll_const_eval_loops = options.force_unroll_const_eval_loops
+    # TODO(crbug.com/406301896): Remove this automatic application once the
+    # driver-level issue causing flakiness on Win/Intel/DXC is fixed.
+    if (not cls._force_unroll_const_eval_loops and not cls._use_fxc
+        and host_information.IsWindows() and host_information.IsIntelGpu()):
+      logging.warning(
+          'Forcing unrolling of const eval loops for crbug.com/406301896')
+      cls._force_unroll_const_eval_loops = True
 
   @classmethod
   def _ModifyBrowserEnvironment(cls) -> None:
@@ -602,6 +616,10 @@
     response = cls.websocket_server.Receive(MESSAGE_TIMEOUT_CONNECTION_ACK)
     assert json.loads(response)['type'] == MESSAGE_TYPE_CONNECTION_ACK
 
+    if self._force_unroll_const_eval_loops:
+      self.tab.action_runner.ExecuteJavaScript(
+          'window.globalTestConfig.unrollConstEvalLoops = true')
+
     cls.page_loaded = True
     return True
 
diff --git a/extensions/browser/api/networking_private/networking_private_chromeos_unittest.cc b/extensions/browser/api/networking_private/networking_private_chromeos_unittest.cc
index 557a717..5719d12 100644
--- a/extensions/browser/api/networking_private/networking_private_chromeos_unittest.cc
+++ b/extensions/browser/api/networking_private/networking_private_chromeos_unittest.cc
@@ -394,7 +394,8 @@
   RunFunction(
       set_properties.get(),
       base::StringPrintf(R"(["%s", {"Priority": 0}])", kSharedWifiGuid));
-  EXPECT_EQ(ExtensionFunction::SUCCEEDED, *set_properties->response_type());
+  EXPECT_EQ(ExtensionFunction::ResponseType::kSucceeded,
+            *set_properties->response_type());
 
   const ash::NetworkState* network =
       ash::NetworkHandler::Get()
@@ -411,7 +412,8 @@
   RunFunction(
       set_properties.get(),
       base::StringPrintf(R"(["%s", {"Priority": 0}])", kPrivateWifiGuid));
-  EXPECT_EQ(ExtensionFunction::SUCCEEDED, *set_properties->response_type());
+  EXPECT_EQ(ExtensionFunction::ResponseType::kSucceeded,
+            *set_properties->response_type());
 
   const ash::NetworkState* network =
       ash::NetworkHandler::Get()
@@ -520,7 +522,8 @@
   RunFunction(
       set_properties.get(),
       base::StringPrintf(R"(["%s", %s])", kPrivateWifiGuid, kCombinedSettings));
-  EXPECT_EQ(ExtensionFunction::SUCCEEDED, *set_properties->response_type());
+  EXPECT_EQ(ExtensionFunction::ResponseType::kSucceeded,
+            *set_properties->response_type());
 
   EXPECT_TRUE(GetUserSettingStringData(kPrivateWifiGuid, "ProxySettings.Type"));
   EXPECT_TRUE(
@@ -602,7 +605,8 @@
 
   RunFunction(set_properties.get(),
               base::StringPrintf(R"(["%s", {"Priority": 2}])", guid.c_str()));
-  EXPECT_EQ(ExtensionFunction::SUCCEEDED, *set_properties->response_type());
+  EXPECT_EQ(ExtensionFunction::ResponseType::kSucceeded,
+            *set_properties->response_type());
 
   EXPECT_EQ(2, GetNetworkPriority(network));
 }
diff --git a/extensions/browser/api_test_utils.cc b/extensions/browser/api_test_utils.cc
index d8aac9cf..da1d301ad 100644
--- a/extensions/browser/api_test_utils.cc
+++ b/extensions/browser/api_test_utils.cc
@@ -45,8 +45,9 @@
                                     base::Value::List results,
                                     const std::string& error,
                                     mojom::ExtraResponseDataPtr) {
-  ASSERT_NE(ExtensionFunction::BAD_MESSAGE, response);
-  response_ = std::make_unique<bool>(response == ExtensionFunction::SUCCEEDED);
+  ASSERT_NE(ExtensionFunction::ResponseType::kBadMessage, response);
+  response_ = std::make_unique<bool>(
+      response == ExtensionFunction::ResponseType::kSucceeded);
   run_loop_.Quit();
 }
 
@@ -165,7 +166,8 @@
   CHECK(results);
   EXPECT_TRUE(results->empty()) << "Did not expect a result";
   CHECK(function->response_type());
-  EXPECT_EQ(ExtensionFunction::FAILED, *function->response_type());
+  EXPECT_EQ(ExtensionFunction::ResponseType::kFailed,
+            *function->response_type());
   return function->GetError();
 }
 
@@ -179,14 +181,14 @@
   CHECK(function->response_type());
 
   switch (*function->response_type()) {
-    case ExtensionFunction::BAD_MESSAGE:
+    case ExtensionFunction::ResponseType::kBadMessage:
       // This case ASSERTs in `SendResponseHelper::OnResponse`.
       NOTREACHED();
 
-    case ExtensionFunction::FAILED:
+    case ExtensionFunction::ResponseType::kFailed:
       return base::unexpected(function->GetError());
 
-    case ExtensionFunction::SUCCEEDED:
+    case ExtensionFunction::ResponseType::kSucceeded:
       const base::Value::List* results = function->GetResultListForTest();
       CHECK(results);
       return results->Clone();
diff --git a/extensions/browser/extension_function.cc b/extensions/browser/extension_function.cc
index 5a516aa..1e59fa4 100644
--- a/extensions/browser/extension_function.cc
+++ b/extensions/browser/extension_function.cc
@@ -422,8 +422,8 @@
   if (!response_callback_.is_null()) {
     constexpr char kShouldCallMojoCallback[] = "Ignored did_respond()";
     std::move(response_callback_)
-        .Run(ResponseType::FAILED, base::Value::List(), kShouldCallMojoCallback,
-             nullptr);
+        .Run(ResponseType::kFailed, base::Value::List(),
+             kShouldCallMojoCallback, nullptr);
   }
 #endif  // DCHECK_IS_ON()
 }
@@ -703,9 +703,10 @@
   DCHECK(!response_callback_.is_null());
   DCHECK(!did_respond()) << name_;
 
-  ResponseType response = success ? SUCCEEDED : FAILED;
+  ResponseType response =
+      success ? ResponseType::kSucceeded : ResponseType::kFailed;
   if (bad_message_) {
-    response = BAD_MESSAGE;
+    response = ResponseType::kBadMessage;
     LOG(ERROR) << "Bad extension message " << name_;
   }
   response_type_ = std::make_unique<ResponseType>(response);
diff --git a/extensions/browser/extension_function.h b/extensions/browser/extension_function.h
index 3d7eafe9e..2f672ba1 100644
--- a/extensions/browser/extension_function.h
+++ b/extensions/browser/extension_function.h
@@ -136,13 +136,13 @@
                               ExtensionFunction,
                               content::BrowserThread::DeleteOnUIThread> {
  public:
-  enum ResponseType {
+  enum class ResponseType {
     // The function has succeeded.
-    SUCCEEDED,
+    kSucceeded,
     // The function has failed.
-    FAILED,
+    kFailed,
     // The input message is malformed.
-    BAD_MESSAGE
+    kBadMessage,
   };
 
   using ResponseCallback = base::OnceCallback<void(
diff --git a/extensions/browser/extension_function_dispatcher.cc b/extensions/browser/extension_function_dispatcher.cc
index 914d5d3..eb98168a 100644
--- a/extensions/browser/extension_function_dispatcher.cc
+++ b/extensions/browser/extension_function_dispatcher.cc
@@ -191,7 +191,7 @@
     debug::ScopedScriptInjectionTrackerFailureCrashKeys tracker_keys(
         frame, params->extension_id);
     bad_message::ReceivedBadMessage(&process, *bad_message_code);
-    std::move(callback).Run(ExtensionFunction::FAILED, base::Value::List(),
+    std::move(callback).Run(/*kFailed=*/true, base::Value::List(),
                             ToString(*bad_message_code), nullptr);
     return;
   }
@@ -204,9 +204,9 @@
              ExtensionFunction::ResponseType type, base::Value::List results,
              const std::string& error,
              mojom::ExtraResponseDataPtr response_data) {
-            std::move(callback).Run(type == ExtensionFunction::SUCCEEDED,
-                                    std::move(results), error,
-                                    std::move(response_data));
+            std::move(callback).Run(
+                type == ExtensionFunction::ResponseType::kSucceeded,
+                std::move(results), error, std::move(response_data));
           },
           std::move(callback)));
 }
@@ -225,8 +225,8 @@
   content::RenderProcessHost* rph =
       content::RenderProcessHost::FromID(render_process_id);
   if (!rph) {
-    std::move(callback).Run(ExtensionFunction::FAILED, base::Value::List(),
-                            "No RPH", nullptr);
+    std::move(callback).Run(/*kFailed=*/true, base::Value::List(), "No RPH",
+                            nullptr);
     return;
   }
 
@@ -238,7 +238,7 @@
   if (auto bad_message_code = ValidateRequest(*params, nullptr, *rph)) {
     // Kill the renderer if it's an invalid request.
     bad_message::ReceivedBadMessage(render_process_id, *bad_message_code);
-    std::move(callback).Run(ExtensionFunction::FAILED, base::Value::List(),
+    std::move(callback).Run(/*kFailed=*/true, base::Value::List(),
                             ToString(*bad_message_code), nullptr);
     return;
   }
@@ -248,8 +248,8 @@
                      params->worker_thread_id};
   // Ignore if the worker has already stopped.
   if (!ProcessManager::Get(browser_context_)->HasServiceWorker(worker_id)) {
-    std::move(callback).Run(ExtensionFunction::FAILED, base::Value::List(),
-                            "No SW", nullptr);
+    std::move(callback).Run(/*kFailed=*/true, base::Value::List(), "No SW",
+                            nullptr);
     return;
   }
 
@@ -260,9 +260,9 @@
              ExtensionFunction::ResponseType type, base::Value::List results,
              const std::string& error,
              mojom::ExtraResponseDataPtr response_data) {
-            std::move(callback).Run(type == ExtensionFunction::SUCCEEDED,
-                                    std::move(results), error,
-                                    std::move(response_data));
+            std::move(callback).Run(
+                type == ExtensionFunction::ResponseType::kSucceeded,
+                std::move(results), error, std::move(response_data));
           },
           std::move(callback)));
 }
@@ -276,7 +276,8 @@
   if (!process_map) {
     constexpr char kProcessNotFound[] =
         "The process for the extension is not found.";
-    ResponseCallbackOnError(std::move(callback), ExtensionFunction::FAILED,
+    ResponseCallbackOnError(std::move(callback),
+                            ExtensionFunction::ResponseType::kFailed,
                             kProcessNotFound);
     return;
   }
@@ -311,7 +312,8 @@
     // (privileged extension contexts in web processes).
     static constexpr char kInvalidContextType[] =
         "Invalid context type provided.";
-    ResponseCallbackOnError(std::move(callback), ExtensionFunction::FAILED,
+    ResponseCallbackOnError(std::move(callback),
+                            ExtensionFunction::ResponseType::kFailed,
                             kInvalidContextType);
     return;
   }
@@ -324,7 +326,8 @@
         !render_frame_host_url->SchemeIs(content::kChromeUIUntrustedScheme)) {
       constexpr char kInvalidWebUiUntrustedContext[] =
           "Context indicated it was untrusted webui, but is invalid.";
-      ResponseCallbackOnError(std::move(callback), ExtensionFunction::FAILED,
+      ResponseCallbackOnError(std::move(callback),
+                              ExtensionFunction::ResponseType::kFailed,
                               kInvalidWebUiUntrustedContext);
       return;
     }
@@ -543,7 +546,8 @@
       ExtensionFunctionRegistry::GetInstance().NewFunction(params.name);
   if (!function) {
     LOG(ERROR) << "Unknown Extension API - " << params.name;
-    ResponseCallbackOnError(std::move(callback), ExtensionFunction::FAILED,
+    ResponseCallbackOnError(std::move(callback),
+                            ExtensionFunction::ResponseType::kFailed,
                             kCreationFailed);
     return nullptr;
   }
diff --git a/extensions/browser/service_worker/service_worker_host.cc b/extensions/browser/service_worker/service_worker_host.cc
index 12775f97..795f26b 100644
--- a/extensions/browser/service_worker/service_worker_host.cc
+++ b/extensions/browser/service_worker/service_worker_host.cc
@@ -254,7 +254,7 @@
                                       RequestWorkerCallback callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   if (!GetBrowserContext()) {
-    std::move(callback).Run(ExtensionFunction::FAILED, base::Value::List(),
+    std::move(callback).Run(/*kFailed=*/true, base::Value::List(),
                             "No browser context", nullptr);
     return;
   }
diff --git a/extensions/common/constants.h b/extensions/common/constants.h
index b92f26f..2d3c0d7 100644
--- a/extensions/common/constants.h
+++ b/extensions/common/constants.h
@@ -11,6 +11,7 @@
 
 #include "base/containers/span.h"
 #include "base/files/file_path.h"
+#include "base/time/time.h"
 #include "build/chromeos_buildflags.h"
 #include "extensions/common/extensions_export.h"
 
@@ -100,8 +101,8 @@
 inline constexpr base::FilePath::CharType kExtensionKeyFileExtension[] =
     FILE_PATH_LITERAL(".pem");
 
-// Default frequency for auto updates, if turned on (5 hours).
-inline constexpr int kDefaultUpdateFrequencySeconds = 60 * 60 * 5;
+// Default frequency for auto updates, if turned on.
+inline constexpr base::TimeDelta kDefaultUpdateFrequency = base::Hours(5);
 
 // The name of the directory inside the profile where per-app local settings
 // are stored.
diff --git a/extensions/common/extension_features.cc b/extensions/common/extension_features.cc
index b214cc3..f63eb21 100644
--- a/extensions/common/extension_features.cc
+++ b/extensions/common/extension_features.cc
@@ -156,6 +156,12 @@
              "TelemetryExtensionPendingApprovalApi",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
+// TODO(crbug.com/399447642): Clean up this feature after confirming the fix is
+// sufficient.
+BASE_FEATURE(kWebstoreInstallerUserGestureKillSwitch,
+             "WebstoreInstallerUserGestureKillSwitch",
+             base::FEATURE_ENABLED_BY_DEFAULT);
+
 #if BUILDFLAG(IS_WIN)
 // TODO(https://crbug.com/400119351): Remove this feature flag in M138.
 BASE_FEATURE(kWinRejectDotSpaceSuffixFilePaths,
diff --git a/extensions/common/extension_features.h b/extensions/common/extension_features.h
index 1e9df50..6fede66a 100644
--- a/extensions/common/extension_features.h
+++ b/extensions/common/extension_features.h
@@ -191,6 +191,10 @@
 // https://chromium.googlesource.com/chromium/src/+/master/docs/telemetry_extension/README.md.
 BASE_DECLARE_FEATURE(kTelemetryExtensionPendingApprovalApi);
 
+// Used to control whether downloads initiated by `WebstoreInstaller` are marked
+// as having a corresponding user gesture or not.
+BASE_DECLARE_FEATURE(kWebstoreInstallerUserGestureKillSwitch);
+
 #if BUILDFLAG(IS_WIN)
 // TODO(https://crbug.com/400119351): Remove this feature flag in M138.
 BASE_DECLARE_FEATURE(kWinRejectDotSpaceSuffixFilePaths);
diff --git a/extensions/common/features/feature_flags.cc b/extensions/common/features/feature_flags.cc
index 3c05590e..4ee8769 100644
--- a/extensions/common/features/feature_flags.cc
+++ b/extensions/common/features/feature_flags.cc
@@ -33,6 +33,7 @@
     &extensions_features::kTelemetryExtensionPendingApprovalApi,
     &extensions_features::
         kApiEnterpriseReportingPrivateOnDataMaskingRulesTriggered,
+    &extensions_features::kWebstoreInstallerUserGestureKillSwitch,
 };
 
 constinit base::span<const base::Feature*> g_feature_flags_test_override;
diff --git a/google_apis/api_key_cache.cc b/google_apis/api_key_cache.cc
index 55c60282..9f5a5a1 100644
--- a/google_apis/api_key_cache.cc
+++ b/google_apis/api_key_cache.cc
@@ -180,6 +180,21 @@
       environment.get(), command_line, gaia_config,
       default_api_keys.allow_override_via_environment,
       default_api_keys.allow_unset_values);
+
+  api_key_cros_system_geo_ = CalculateKeyValue(
+      default_api_keys.google_api_key_cros_system_geo_,
+      STRINGIZE_NO_EXPANSION(GOOGLE_API_KEY_CROS_SYSTEM_GEO), nullptr,
+      std::string(), environment.get(), command_line, gaia_config,
+      default_api_keys.allow_override_via_environment,
+      default_api_keys.allow_unset_values);
+
+  api_key_cros_chrome_geo_ = CalculateKeyValue(
+      default_api_keys.google_api_key_cros_chrome_geo_,
+      STRINGIZE_NO_EXPANSION(GOOGLE_API_KEY_CROS_CHROME_GEO), nullptr,
+      std::string(), environment.get(), command_line, gaia_config,
+      default_api_keys.allow_override_via_environment,
+      default_api_keys.allow_unset_values);
+
 #endif
 
   metrics_key_ = CalculateKeyValue(
diff --git a/google_apis/api_key_cache.h b/google_apis/api_key_cache.h
index a3a64745e..cb7a864 100644
--- a/google_apis/api_key_cache.h
+++ b/google_apis/api_key_cache.h
@@ -38,6 +38,12 @@
   const std::string& api_key_read_aloud() const { return api_key_read_aloud_; }
   const std::string& api_key_fresnel() const { return api_key_fresnel_; }
   const std::string& api_key_boca() const { return api_key_boca_; }
+  const std::string& api_key_cros_system_geo() const {
+    return api_key_cros_system_geo_;
+  }
+  const std::string& api_key_cros_chrome_geo() const {
+    return api_key_cros_chrome_geo_;
+  }
 #endif
 
   const std::string& metrics_key() const { return metrics_key_; }
@@ -67,6 +73,8 @@
   std::string api_key_read_aloud_;
   std::string api_key_fresnel_;
   std::string api_key_boca_;
+  std::string api_key_cros_system_geo_;
+  std::string api_key_cros_chrome_geo_;
 #endif
 
   std::string metrics_key_;
diff --git a/google_apis/default_api_keys-inc.cc b/google_apis/default_api_keys-inc.cc
index c4630ac..6b8b027 100644
--- a/google_apis/default_api_keys-inc.cc
+++ b/google_apis/default_api_keys-inc.cc
@@ -92,6 +92,19 @@
 #if !defined(GOOGLE_API_KEY_BOCA)
 #define GOOGLE_API_KEY_BOCA google_apis::DefaultApiKeys::kUnsetApiToken
 #endif
+
+// API key for ChromeOS system services to resolve location.
+#if !defined(GOOGLE_API_KEY_CROS_SYSTEM_GEO)
+#define GOOGLE_API_KEY_CROS_SYSTEM_GEO \
+  google_apis::DefaultApiKeys::kUnsetApiToken
+#endif
+
+// API key for ChromeOS Chrome to resolve location.
+#if !defined(GOOGLE_API_KEY_CROS_CHROME_GEO)
+#define GOOGLE_API_KEY_CROS_CHROME_GEO \
+  google_apis::DefaultApiKeys::kUnsetApiToken
+#endif
+
 #endif  // BUILDFLAG(IS_CHROMEOS)
 
 // These are used as shortcuts for developers and users providing
@@ -127,6 +140,8 @@
       .google_api_key_read_aloud = GOOGLE_API_KEY_READ_ALOUD,
       .google_api_key_fresnel = GOOGLE_API_KEY_FRESNEL,
       .google_api_key_boca = GOOGLE_API_KEY_BOCA,
+      .google_api_key_cros_system_geo_ = GOOGLE_API_KEY_CROS_SYSTEM_GEO,
+      .google_api_key_cros_chrome_geo_ = GOOGLE_API_KEY_CROS_CHROME_GEO,
 #endif
       .google_client_id_main = GOOGLE_CLIENT_ID_MAIN,
       .google_client_secret_main = GOOGLE_CLIENT_SECRET_MAIN,
diff --git a/google_apis/default_api_keys.h b/google_apis/default_api_keys.h
index 5010913d..5f9e8cc 100644
--- a/google_apis/default_api_keys.h
+++ b/google_apis/default_api_keys.h
@@ -34,6 +34,8 @@
   const char* google_api_key_read_aloud;
   const char* google_api_key_fresnel;
   const char* google_api_key_boca;
+  const char* google_api_key_cros_system_geo_;
+  const char* google_api_key_cros_chrome_geo_;
 #endif
 
   const char* google_client_id_main;
diff --git a/google_apis/google_api_keys.cc b/google_apis/google_api_keys.cc
index 84cc1ca..6e98c9ae 100644
--- a/google_apis/google_api_keys.cc
+++ b/google_apis/google_api_keys.cc
@@ -114,6 +114,13 @@
 const std::string& GetBocaAPIKey() {
   return GetApiKeyCacheInstance().api_key_boca();
 }
+
+const std::string& GetCrosSystemGeoAPIKey() {
+  return GetApiKeyCacheInstance().api_key_cros_system_geo();
+}
+const std::string& GetCrosChromeGeoAPIKey() {
+  return GetApiKeyCacheInstance().api_key_cros_chrome_geo();
+}
 #endif
 
 const std::string& GetMetricsKey() {
diff --git a/google_apis/google_api_keys.h b/google_apis/google_api_keys.h
index ada1e52..0f10fd0 100644
--- a/google_apis/google_api_keys.h
+++ b/google_apis/google_api_keys.h
@@ -113,6 +113,14 @@
 
 // Retrieves the Boca API Key.
 COMPONENT_EXPORT(GOOGLE_APIS) const std::string& GetBocaAPIKey();
+
+// Retrieves the ChromeOS-specific Geolocation API Key used by the System
+// Services.
+COMPONENT_EXPORT(GOOGLE_APIS) const std::string& GetCrosSystemGeoAPIKey();
+// Retrieves the ChromeOS-specific Geolocation API Key used by the Chrome
+// browser.
+COMPONENT_EXPORT(GOOGLE_APIS) const std::string& GetCrosChromeGeoAPIKey();
+
 #endif
 
 // Retrieves the key used to sign metrics (UMA/UKM) uploads.
diff --git a/google_apis/internal b/google_apis/internal
index aa70199..02538e2 160000
--- a/google_apis/internal
+++ b/google_apis/internal
@@ -1 +1 @@
-Subproject commit aa70199859693798799b7a72018f14dd510cd9f2
+Subproject commit 02538e2044536d99127760477f6d787d3c4f6ae2
diff --git a/gpu/command_buffer/service/shared_image/wrapped_sk_image_backing_factory.cc b/gpu/command_buffer/service/shared_image/wrapped_sk_image_backing_factory.cc
index 159c56c..8922ae6e 100644
--- a/gpu/command_buffer/service/shared_image/wrapped_sk_image_backing_factory.cc
+++ b/gpu/command_buffer/service/shared_image/wrapped_sk_image_backing_factory.cc
@@ -44,6 +44,7 @@
   constexpr SharedImageUsageSet kGraphiteDawnFallbackUsage =
       SHARED_IMAGE_USAGE_GLES2_READ | SHARED_IMAGE_USAGE_GLES2_WRITE |
       SHARED_IMAGE_USAGE_GLES2_FOR_RASTER_ONLY |
+      SHARED_IMAGE_USAGE_HIGH_PERFORMANCE_GPU |
       // NOTE: In this case, it is also possible to support raster-over-GLES2.
       SHARED_IMAGE_USAGE_RASTER_OVER_GLES2_ONLY |
       SHARED_IMAGE_USAGE_WEBGPU_READ | SHARED_IMAGE_USAGE_WEBGPU_WRITE |
diff --git a/infra/config/generated/builders/ci/linux-chromeos-rel/targets/chromium.chromiumos.json b/infra/config/generated/builders/ci/linux-chromeos-rel/targets/chromium.chromiumos.json
index 090eb248..b21974c 100644
--- a/infra/config/generated/builders/ci/linux-chromeos-rel/targets/chromium.chromiumos.json
+++ b/infra/config/generated/builders/ci/linux-chromeos-rel/targets/chromium.chromiumos.json
@@ -1114,34 +1114,6 @@
         "test_id_prefix": "ninja://third_party/perfetto:perfetto_unittests/"
       },
       {
-        "args": [
-          "--browser-ui-tests-verify-pixels",
-          "--enable-pixel-output-in-tests",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/linux-chromeos.browser_tests.pixel_tests.filter",
-          "--git-revision=${got_revision}"
-        ],
-        "experiment_percentage": 100,
-        "isolate_profile_data": true,
-        "merge": {
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "pixel_experimental_browser_tests",
-        "precommit_args": [
-          "--gerrit-issue=${patch_issue}",
-          "--gerrit-patchset=${patch_set}",
-          "--buildbucket-id=${buildbucket_build_id}"
-        ],
-        "swarming": {
-          "dimensions": {
-            "cpu": "x86-64",
-            "os": "Ubuntu-22.04"
-          },
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "browser_tests",
-        "test_id_prefix": "ninja://chrome/test:browser_tests/"
-      },
-      {
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
diff --git a/infra/config/generated/builders/try/linux-chromeos-rel/targets/chromium.chromiumos.json b/infra/config/generated/builders/try/linux-chromeos-rel/targets/chromium.chromiumos.json
index 090eb248..b21974c 100644
--- a/infra/config/generated/builders/try/linux-chromeos-rel/targets/chromium.chromiumos.json
+++ b/infra/config/generated/builders/try/linux-chromeos-rel/targets/chromium.chromiumos.json
@@ -1114,34 +1114,6 @@
         "test_id_prefix": "ninja://third_party/perfetto:perfetto_unittests/"
       },
       {
-        "args": [
-          "--browser-ui-tests-verify-pixels",
-          "--enable-pixel-output-in-tests",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/linux-chromeos.browser_tests.pixel_tests.filter",
-          "--git-revision=${got_revision}"
-        ],
-        "experiment_percentage": 100,
-        "isolate_profile_data": true,
-        "merge": {
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "pixel_experimental_browser_tests",
-        "precommit_args": [
-          "--gerrit-issue=${patch_issue}",
-          "--gerrit-patchset=${patch_set}",
-          "--buildbucket-id=${buildbucket_build_id}"
-        ],
-        "swarming": {
-          "dimensions": {
-            "cpu": "x86-64",
-            "os": "Ubuntu-22.04"
-          },
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "browser_tests",
-        "test_id_prefix": "ninja://chrome/test:browser_tests/"
-      },
-      {
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
diff --git a/infra/config/generated/luci/cr-buildbucket.cfg b/infra/config/generated/luci/cr-buildbucket.cfg
index 0426b923..ab9f879 100644
--- a/infra/config/generated/luci/cr-buildbucket.cfg
+++ b/infra/config/generated/luci/cr-buildbucket.cfg
@@ -39778,21 +39778,11 @@
         '      "tools/android/avd/proto_creation/android_30_google_apis_x86.textpb",'
         '      "tools/android/avd/proto_creation/android_31_google_apis_x64.textpb",'
         '      "tools/android/avd/proto_creation/android_32_google_apis_x64_foldable.textpb",'
-        '      "tools/android/avd/proto_creation/android_32_google_apis_x64_foldable_landscape.textpb",'
         '      "tools/android/avd/proto_creation/android_33_google_apis_x64.textpb",'
         '      "tools/android/avd/proto_creation/android_34_google_apis_x64.textpb",'
         '      "tools/android/avd/proto_creation/android_35_google_apis_x64.textpb",'
+        '      "tools/android/avd/proto_creation/android_36_google_apis_x64.textpb",'
         '      "tools/android/avd/proto_creation/android_35_google_apis_tablet_x64.textpb",'
-        '      "tools/android/avd/proto_creation/android_30_google_atd_x86.textpb",'
-        '      "tools/android/avd/proto_creation/android_30_google_atd_x64.textpb",'
-        '      "tools/android/avd/proto_creation/android_31_google_atd_x64.textpb",'
-        '      "tools/android/avd/proto_creation/android_32_google_atd_x64_foldable.textpb",'
-        '      "tools/android/avd/proto_creation/android_33_google_atd_x64.textpb",'
-        '      "tools/android/avd/proto_creation/generic_android19.textpb",'
-        '      "tools/android/avd/proto_creation/generic_android22.textpb",'
-        '      "tools/android/avd/proto_creation/generic_android23.textpb",'
-        '      "tools/android/avd/proto_creation/generic_android24.textpb",'
-        '      "tools/android/avd/proto_creation/generic_android25.textpb",'
         '      "tools/android/avd/proto_creation/generic_android26.textpb",'
         '      "tools/android/avd/proto_creation/generic_android27.textpb"'
         '    ],'
diff --git a/infra/config/lib/builder_config.star b/infra/config/lib/builder_config.star
index 96f34a0..5c8cda9 100644
--- a/infra/config/lib/builder_config.star
+++ b/infra/config/lib/builder_config.star
@@ -84,92 +84,6 @@
     MAC = "mac",
 )
 
-# TODO: crbug.com/404841037 - Once config_vars are removed recipe-side, the
-# builders should be updated to explicitly set these arguments and these
-# defaults should be removed
-_chromium_config_defaults_by_config_name = {
-    "arm_v6_builder_rel": {
-        "target_arch": _target_arch.INTEL,
-        "target_bits": 32,
-        "build_config": _build_config.RELEASE,
-    },
-    "arm64_builder": {
-        "target_arch": _target_arch.ARM,
-        "target_bits": 64,
-        "build_config": _build_config.DEBUG,
-    },
-    "arm64_builder_mb": {
-        "target_arch": _target_arch.ARM,
-        "target_bits": 64,
-        "build_config": _build_config.DEBUG,
-    },
-    "arm64_builder_rel": {
-        "target_arch": _target_arch.ARM,
-        "target_bits": 64,
-        "build_config": _build_config.RELEASE,
-    },
-    "arm64_builder_rel_mb": {
-        "target_arch": _target_arch.ARM,
-        "target_bits": 64,
-        "build_config": _build_config.RELEASE,
-    },
-    "base_config": {
-        "target_arch": _target_arch.ARM,
-        "target_bits": 32,
-        "build_config": _build_config.DEBUG,
-    },
-    "cast_builder": {
-        "target_arch": _target_arch.ARM,
-        "target_bits": 32,
-        "build_config": _build_config.DEBUG,
-    },
-    "clang_builder": {
-        "target_arch": _target_arch.ARM,
-        "target_bits": 32,
-        "build_config": _build_config.DEBUG,
-    },
-    "clang_builder_mb": {
-        "target_arch": _target_arch.ARM,
-        "target_bits": 32,
-        "build_config": _build_config.DEBUG,
-    },
-    "main_builder": {
-        "target_arch": _target_arch.ARM,
-        "target_bits": 32,
-        "build_config": _build_config.DEBUG,
-    },
-    "main_builder_mb": {
-        "target_arch": _target_arch.ARM,
-        "target_bits": 32,
-        "build_config": _build_config.DEBUG,
-    },
-    "main_builder_rel_mb": {
-        "target_arch": _target_arch.ARM,
-        "target_bits": 32,
-        "build_config": _build_config.RELEASE,
-    },
-    "x64_builder": {
-        "target_arch": _target_arch.INTEL,
-        "target_bits": 64,
-        "build_config": _build_config.DEBUG,
-    },
-    "x64_builder_mb": {
-        "target_arch": _target_arch.INTEL,
-        "target_bits": 64,
-        "build_config": _build_config.DEBUG,
-    },
-    "x86_builder": {
-        "target_arch": _target_arch.INTEL,
-        "target_bits": 32,
-        "build_config": _build_config.DEBUG,
-    },
-    "x86_builder_mb": {
-        "target_arch": _target_arch.INTEL,
-        "target_bits": 32,
-        "build_config": _build_config.DEBUG,
-    },
-}
-
 def _chromium_config(
         *,
         config,
@@ -221,14 +135,12 @@
         fail("CrOS boards can only be specified for target platform '{}'"
             .format(_target_platform.CHROMEOS))
 
-    defaults = _chromium_config_defaults_by_config_name.get(config, {})
-
     return struct(
         config = config,
         apply_configs = args.listify(apply_configs),
-        build_config = build_config or defaults.get("build_config", None),
-        target_arch = target_arch or defaults.get("target_arch", None),
-        target_bits = target_bits or defaults.get("target_bits", None),
+        build_config = build_config,
+        target_arch = target_arch,
+        target_bits = target_bits,
         target_platform = target_platform,
         host_platform = host_platform,
         target_cros_boards = args.listify(target_cros_boards),
diff --git a/infra/config/subprojects/build/build.star b/infra/config/subprojects/build/build.star
index f46c45b..822b7820 100644
--- a/infra/config/subprojects/build/build.star
+++ b/infra/config/subprojects/build/build.star
@@ -147,6 +147,7 @@
                 "mb",
             ],
             build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.ARM,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -182,6 +183,7 @@
                 "mb",
             ],
             build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.ARM,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -590,6 +592,7 @@
                 "mb",
             ],
             build_config = builder_config.build_config.DEBUG,
+            target_arch = builder_config.target_arch.ARM,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
diff --git a/infra/config/subprojects/chromium/ci/chromium.android.desktop.fyi.star b/infra/config/subprojects/chromium/ci/chromium.android.desktop.fyi.star
index 2647ac9..be8bed92 100644
--- a/infra/config/subprojects/chromium/ci/chromium.android.desktop.fyi.star
+++ b/infra/config/subprojects/chromium/ci/chromium.android.desktop.fyi.star
@@ -50,6 +50,7 @@
         chromium_config = builder_config.chromium_config(
             config = "x64_builder_mb",
             build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.INTEL,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
diff --git a/infra/config/subprojects/chromium/ci/chromium.android.fyi.star b/infra/config/subprojects/chromium/ci/chromium.android.fyi.star
index b9aa5bee..af21f36d 100644
--- a/infra/config/subprojects/chromium/ci/chromium.android.fyi.star
+++ b/infra/config/subprojects/chromium/ci/chromium.android.fyi.star
@@ -50,6 +50,7 @@
             config = "x86_builder",
             apply_configs = ["mb"],
             build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.INTEL,
             target_bits = 32,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -110,6 +111,7 @@
         chromium_config = builder_config.chromium_config(
             config = "x64_builder_mb",
             build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.INTEL,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -162,6 +164,7 @@
         chromium_config = builder_config.chromium_config(
             config = "x64_builder_mb",
             build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.INTEL,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -216,6 +219,7 @@
             config = "x86_builder",
             apply_configs = ["mb"],
             build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.INTEL,
             target_bits = 32,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -286,6 +290,7 @@
             config = "x86_builder",
             apply_configs = ["mb"],
             build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.INTEL,
             target_bits = 32,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -469,6 +474,7 @@
         chromium_config = builder_config.chromium_config(
             config = "x86_builder_mb",
             build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.INTEL,
             target_bits = 32,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -547,6 +553,7 @@
         chromium_config = builder_config.chromium_config(
             config = "x86_builder_mb",
             build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.INTEL,
             target_bits = 32,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -628,6 +635,7 @@
                 "mb",
             ],
             build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.INTEL,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -677,6 +685,7 @@
                 "download_xr_test_apks",
             ],
             build_config = builder_config.build_config.DEBUG,
+            target_arch = builder_config.target_arch.INTEL,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -802,6 +811,7 @@
         chromium_config = builder_config.chromium_config(
             config = "x64_builder_mb",
             build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.INTEL,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -952,6 +962,7 @@
                 "mb",
             ],
             build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.INTEL,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -1126,6 +1137,7 @@
         chromium_config = builder_config.chromium_config(
             config = "main_builder_mb",
             build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.ARM,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -1179,6 +1191,7 @@
         chromium_config = builder_config.chromium_config(
             config = "x64_builder_mb",
             build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.INTEL,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -1243,6 +1256,7 @@
         chromium_config = builder_config.chromium_config(
             config = "x64_builder_mb",
             build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.INTEL,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -1351,6 +1365,7 @@
             config = "main_builder",
             apply_configs = ["mb"],
             build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.ARM,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -1398,6 +1413,7 @@
         chromium_config = builder_config.chromium_config(
             config = "x64_builder_mb",
             build_config = builder_config.build_config.DEBUG,
+            target_arch = builder_config.target_arch.INTEL,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -1441,6 +1457,7 @@
         chromium_config = builder_config.chromium_config(
             config = "x64_builder_mb",
             build_config = builder_config.build_config.DEBUG,
+            target_arch = builder_config.target_arch.INTEL,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -1487,6 +1504,7 @@
         chromium_config = builder_config.chromium_config(
             config = "x64_builder_mb",
             build_config = builder_config.build_config.DEBUG,
+            target_arch = builder_config.target_arch.INTEL,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -1531,6 +1549,7 @@
                 "mb",
             ],
             build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.INTEL,
             target_bits = 32,
             target_platform = builder_config.target_platform.ANDROID,
         ),
diff --git a/infra/config/subprojects/chromium/ci/chromium.android.star b/infra/config/subprojects/chromium/ci/chromium.android.star
index 17d5851..ada48fc 100644
--- a/infra/config/subprojects/chromium/ci/chromium.android.star
+++ b/infra/config/subprojects/chromium/ci/chromium.android.star
@@ -73,6 +73,7 @@
                 "download_xr_test_apks",
             ],
             build_config = builder_config.build_config.DEBUG,
+            target_arch = builder_config.target_arch.ARM,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -125,6 +126,7 @@
                 "download_xr_test_apks",
             ],
             build_config = builder_config.build_config.DEBUG,
+            target_arch = builder_config.target_arch.ARM,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -167,6 +169,7 @@
                 "download_xr_test_apks",
             ],
             build_config = builder_config.build_config.DEBUG,
+            target_arch = builder_config.target_arch.ARM,
             target_bits = 32,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -222,6 +225,7 @@
                 "download_xr_test_apks",
             ],
             build_config = builder_config.build_config.DEBUG,
+            target_arch = builder_config.target_arch.ARM,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -281,6 +285,7 @@
                 "download_xr_test_apks",
             ],
             build_config = builder_config.build_config.DEBUG,
+            target_arch = builder_config.target_arch.ARM,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -364,6 +369,7 @@
         chromium_config = builder_config.chromium_config(
             config = "x64_builder_mb",
             build_config = builder_config.build_config.DEBUG,
+            target_arch = builder_config.target_arch.INTEL,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -411,6 +417,7 @@
         chromium_config = builder_config.chromium_config(
             config = "x86_builder_mb",
             build_config = builder_config.build_config.DEBUG,
+            target_arch = builder_config.target_arch.INTEL,
             target_bits = 32,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -456,6 +463,7 @@
         chromium_config = builder_config.chromium_config(
             config = "main_builder_mb",
             build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.ARM,
             target_bits = 32,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -501,6 +509,7 @@
         chromium_config = builder_config.chromium_config(
             config = "main_builder_mb",
             build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.ARM,
             target_bits = 32,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -548,6 +557,7 @@
                 "mb",
             ],
             build_config = builder_config.build_config.DEBUG,
+            target_arch = builder_config.target_arch.ARM,
             target_bits = 32,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -603,6 +613,7 @@
                 "mb",
             ],
             build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.ARM,
             target_bits = 32,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -658,6 +669,7 @@
                 "mb",
             ],
             build_config = builder_config.build_config.DEBUG,
+            target_arch = builder_config.target_arch.ARM,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -713,6 +725,7 @@
                 "mb",
             ],
             build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.ARM,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -818,6 +831,7 @@
                 "download_xr_test_apks",
             ],
             build_config = builder_config.build_config.DEBUG,
+            target_arch = builder_config.target_arch.ARM,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -872,6 +886,7 @@
                 "mb",
             ],
             build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.ARM,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -938,6 +953,7 @@
                 "download_xr_test_apks",
             ],
             build_config = builder_config.build_config.DEBUG,
+            target_arch = builder_config.target_arch.INTEL,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -1058,6 +1074,7 @@
                 "download_xr_test_apks",
             ],
             build_config = builder_config.build_config.DEBUG,
+            target_arch = builder_config.target_arch.INTEL,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -1136,6 +1153,7 @@
                 "mb",
             ],
             build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.ARM,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -1224,6 +1242,7 @@
         chromium_config = builder_config.chromium_config(
             config = "x86_builder_mb",
             build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.INTEL,
             target_bits = 32,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -1326,6 +1345,7 @@
                 "mb",
             ],
             build_config = builder_config.build_config.DEBUG,
+            target_arch = builder_config.target_arch.ARM,
             target_bits = 32,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -1377,6 +1397,7 @@
                 "mb",
             ],
             build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.ARM,
             target_bits = 32,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -1429,6 +1450,7 @@
                 "mb",
             ],
             build_config = builder_config.build_config.DEBUG,
+            target_arch = builder_config.target_arch.ARM,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -1482,6 +1504,7 @@
                 "mb",
             ],
             build_config = builder_config.build_config.DEBUG,
+            target_arch = builder_config.target_arch.ARM,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -1527,6 +1550,7 @@
                 "mb",
             ],
             build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.ARM,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -1577,6 +1601,7 @@
                 "mb",
             ],
             build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.ARM,
             target_bits = 32,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -1633,6 +1658,7 @@
                 "mb",
             ],
             build_config = builder_config.build_config.DEBUG,
+            target_arch = builder_config.target_arch.ARM,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -1678,6 +1704,7 @@
                 "mb",
             ],
             build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.ARM,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -1727,6 +1754,7 @@
                 "mb",
             ],
             build_config = builder_config.build_config.DEBUG,
+            target_arch = builder_config.target_arch.ARM,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -1772,6 +1800,7 @@
                 "mb",
             ],
             build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.ARM,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -1823,6 +1852,7 @@
                 "mb",
             ],
             build_config = builder_config.build_config.DEBUG,
+            target_arch = builder_config.target_arch.INTEL,
             target_bits = 32,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -1870,6 +1900,7 @@
                 "mb",
             ],
             build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.INTEL,
             target_bits = 32,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -1919,6 +1950,7 @@
                 "mb",
             ],
             build_config = builder_config.build_config.DEBUG,
+            target_arch = builder_config.target_arch.ARM,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -1964,6 +1996,7 @@
                 "mb",
             ],
             build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.ARM,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -2014,6 +2047,7 @@
                 "mb",
             ],
             build_config = builder_config.build_config.DEBUG,
+            target_arch = builder_config.target_arch.INTEL,
             target_bits = 32,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -2074,6 +2108,7 @@
                 "mb",
             ],
             build_config = builder_config.build_config.DEBUG,
+            target_arch = builder_config.target_arch.INTEL,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -2135,6 +2170,7 @@
                 "mb",
             ],
             build_config = builder_config.build_config.DEBUG,
+            target_arch = builder_config.target_arch.INTEL,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -2182,6 +2218,7 @@
                 "mb",
             ],
             build_config = builder_config.build_config.DEBUG,
+            target_arch = builder_config.target_arch.INTEL,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -2230,6 +2267,7 @@
                 "mb",
             ],
             build_config = builder_config.build_config.DEBUG,
+            target_arch = builder_config.target_arch.INTEL,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -2279,6 +2317,7 @@
                 "mb",
             ],
             build_config = builder_config.build_config.DEBUG,
+            target_arch = builder_config.target_arch.INTEL,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -2327,6 +2366,7 @@
                 "mb",
             ],
             build_config = builder_config.build_config.DEBUG,
+            target_arch = builder_config.target_arch.INTEL,
             target_bits = 32,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -2374,6 +2414,7 @@
                 "mb",
             ],
             build_config = builder_config.build_config.DEBUG,
+            target_arch = builder_config.target_arch.INTEL,
             target_bits = 32,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -2421,6 +2462,7 @@
                 "mb",
             ],
             build_config = builder_config.build_config.DEBUG,
+            target_arch = builder_config.target_arch.INTEL,
             target_bits = 32,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -2475,6 +2517,7 @@
                 "mb",
             ],
             build_config = builder_config.build_config.DEBUG,
+            target_arch = builder_config.target_arch.INTEL,
             target_bits = 32,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -2531,6 +2574,7 @@
                 "mb",
             ],
             build_config = builder_config.build_config.DEBUG,
+            target_arch = builder_config.target_arch.INTEL,
             target_bits = 32,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -2585,6 +2629,7 @@
                 "mb",
             ],
             build_config = builder_config.build_config.DEBUG,
+            target_arch = builder_config.target_arch.INTEL,
             target_bits = 32,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -2628,6 +2673,7 @@
                 "mb",
             ],
             build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.INTEL,
             target_bits = 32,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -2679,6 +2725,7 @@
                 "mb",
             ],
             build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.INTEL,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -2732,6 +2779,7 @@
         chromium_config = builder_config.chromium_config(
             config = "x86_builder_mb",
             build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.INTEL,
             target_bits = 32,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -2946,6 +2994,7 @@
                 "download_xr_test_apks",
             ],
             build_config = builder_config.build_config.DEBUG,
+            target_arch = builder_config.target_arch.ARM,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -2986,6 +3035,7 @@
                 "mb",
             ],
             build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.ARM,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -3202,6 +3252,7 @@
                 "mb",
             ],
             build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.INTEL,
             target_bits = 32,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -3395,6 +3446,7 @@
         chromium_config = builder_config.chromium_config(
             config = "x86_builder_mb",
             build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.INTEL,
             target_bits = 32,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -3559,6 +3611,7 @@
                 "mb",
             ],
             build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.INTEL,
             target_bits = 32,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -3692,6 +3745,7 @@
                 "mb",
             ],
             build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.INTEL,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -3872,6 +3926,7 @@
                 "mb",
             ],
             build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.INTEL,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -4014,6 +4069,7 @@
                 "mb",
             ],
             build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.ARM,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -4106,6 +4162,7 @@
                 "mb",
             ],
             build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.INTEL,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -4280,6 +4337,7 @@
         chromium_config = builder_config.chromium_config(
             config = "main_builder_mb",
             build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.ARM,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -4409,6 +4467,7 @@
                 "mb",
             ],
             build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.INTEL,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -4567,6 +4626,7 @@
                 "mb",
             ],
             build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.INTEL,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -4638,6 +4698,7 @@
                 "mb",
             ],
             build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.INTEL,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -4761,6 +4822,7 @@
         chromium_config = builder_config.chromium_config(
             config = "x64_builder_mb",
             build_config = builder_config.build_config.DEBUG,
+            target_arch = builder_config.target_arch.INTEL,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -4872,6 +4934,7 @@
         chromium_config = builder_config.chromium_config(
             config = "x64_builder_mb",
             build_config = builder_config.build_config.DEBUG,
+            target_arch = builder_config.target_arch.INTEL,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -4931,6 +4994,7 @@
                 "mb",
             ],
             build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.INTEL,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -4991,6 +5055,7 @@
             config = "main_builder",
             apply_configs = ["mb"],
             build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.ARM,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
diff --git a/infra/config/subprojects/chromium/ci/chromium.angle.star b/infra/config/subprojects/chromium/ci/chromium.angle.star
index 07fb2fe..4b4e17d 100644
--- a/infra/config/subprojects/chromium/ci/chromium.angle.star
+++ b/infra/config/subprojects/chromium/ci/chromium.angle.star
@@ -76,6 +76,7 @@
         chromium_config = builder_config.chromium_config(
             config = "main_builder_mb",
             build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.ARM,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -120,6 +121,7 @@
         chromium_config = builder_config.chromium_config(
             config = "main_builder_mb",
             build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.ARM,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
diff --git a/infra/config/subprojects/chromium/ci/chromium.coverage.star b/infra/config/subprojects/chromium/ci/chromium.coverage.star
index 3fe0544..96b9094 100644
--- a/infra/config/subprojects/chromium/ci/chromium.coverage.star
+++ b/infra/config/subprojects/chromium/ci/chromium.coverage.star
@@ -102,6 +102,7 @@
                 "mb",
             ],
             build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.ARM,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -167,6 +168,7 @@
                 "mb",
             ],
             build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.ARM,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -231,6 +233,7 @@
         chromium_config = builder_config.chromium_config(
             config = "x86_builder_mb",
             build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.INTEL,
             target_bits = 32,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -433,6 +436,7 @@
                 "mb",
             ],
             build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.ARM,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -570,6 +574,7 @@
                 "mb",
             ],
             build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.ARM,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -652,6 +657,7 @@
                 "mb",
             ],
             build_config = builder_config.build_config.DEBUG,
+            target_arch = builder_config.target_arch.INTEL,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -718,6 +724,7 @@
                 "mb",
             ],
             build_config = builder_config.build_config.DEBUG,
+            target_arch = builder_config.target_arch.INTEL,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
diff --git a/infra/config/subprojects/chromium/ci/chromium.dawn.star b/infra/config/subprojects/chromium/ci/chromium.dawn.star
index 0d970ae..f725428 100644
--- a/infra/config/subprojects/chromium/ci/chromium.dawn.star
+++ b/infra/config/subprojects/chromium/ci/chromium.dawn.star
@@ -206,6 +206,9 @@
         ),
         chromium_config = builder_config.chromium_config(
             config = "main_builder_rel_mb",
+            build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.ARM,
+            target_bits = 32,
             target_platform = builder_config.target_platform.ANDROID,
         ),
         android_config = builder_config.android_config(
@@ -243,6 +246,9 @@
         ),
         chromium_config = builder_config.chromium_config(
             config = "arm64_builder_rel_mb",
+            build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.ARM,
+            target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
         android_config = builder_config.android_config(
@@ -281,6 +287,9 @@
         ),
         chromium_config = builder_config.chromium_config(
             config = "main_builder_rel_mb",
+            build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.ARM,
+            target_bits = 32,
             target_platform = builder_config.target_platform.ANDROID,
         ),
         android_config = builder_config.android_config(
@@ -322,6 +331,9 @@
         ),
         chromium_config = builder_config.chromium_config(
             config = "main_builder_rel_mb",
+            build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.ARM,
+            target_bits = 32,
             target_platform = builder_config.target_platform.ANDROID,
         ),
         android_config = builder_config.android_config(
@@ -403,6 +415,9 @@
         ),
         chromium_config = builder_config.chromium_config(
             config = "arm64_builder_rel_mb",
+            build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.ARM,
+            target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
         android_config = builder_config.android_config(
@@ -652,6 +667,9 @@
         ),
         chromium_config = builder_config.chromium_config(
             config = "main_builder_rel_mb",
+            build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.ARM,
+            target_bits = 32,
             target_platform = builder_config.target_platform.ANDROID,
         ),
         android_config = builder_config.android_config(
@@ -690,6 +708,9 @@
         ),
         chromium_config = builder_config.chromium_config(
             config = "arm64_builder_rel_mb",
+            build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.ARM,
+            target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
         android_config = builder_config.android_config(
@@ -729,6 +750,9 @@
         ),
         chromium_config = builder_config.chromium_config(
             config = "main_builder_rel_mb",
+            build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.ARM,
+            target_bits = 32,
             target_platform = builder_config.target_platform.ANDROID,
         ),
         android_config = builder_config.android_config(
@@ -771,6 +795,9 @@
         ),
         chromium_config = builder_config.chromium_config(
             config = "main_builder_rel_mb",
+            build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.ARM,
+            target_bits = 32,
             target_platform = builder_config.target_platform.ANDROID,
         ),
         android_config = builder_config.android_config(
@@ -853,6 +880,9 @@
         ),
         chromium_config = builder_config.chromium_config(
             config = "arm64_builder_rel_mb",
+            build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.ARM,
+            target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
         android_config = builder_config.android_config(
@@ -932,6 +962,9 @@
         ),
         chromium_config = builder_config.chromium_config(
             config = "arm64_builder_rel_mb",
+            build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.ARM,
+            target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
         android_config = builder_config.android_config(
@@ -1021,6 +1054,9 @@
         ),
         chromium_config = builder_config.chromium_config(
             config = "arm64_builder_rel_mb",
+            build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.ARM,
+            target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
         android_config = builder_config.android_config(
diff --git a/infra/config/subprojects/chromium/ci/chromium.fyi.star b/infra/config/subprojects/chromium/ci/chromium.fyi.star
index 10fe170..5ae4eca 100644
--- a/infra/config/subprojects/chromium/ci/chromium.fyi.star
+++ b/infra/config/subprojects/chromium/ci/chromium.fyi.star
@@ -171,6 +171,7 @@
         chromium_config = builder_config.chromium_config(
             config = "arm64_builder_mb",
             build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.ARM,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -710,6 +711,7 @@
                 "mb",
             ],
             build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.INTEL,
             target_bits = 32,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -820,6 +822,7 @@
             config = "x64_builder",
             apply_configs = ["mb"],
             build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.INTEL,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
diff --git a/infra/config/subprojects/chromium/ci/chromium.gpu.fyi.star b/infra/config/subprojects/chromium/ci/chromium.gpu.fyi.star
index da6689a..1765ccca 100644
--- a/infra/config/subprojects/chromium/ci/chromium.gpu.fyi.star
+++ b/infra/config/subprojects/chromium/ci/chromium.gpu.fyi.star
@@ -96,6 +96,9 @@
         ),
         chromium_config = builder_config.chromium_config(
             config = "main_builder_rel_mb",
+            build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.ARM,
+            target_bits = 32,
             target_platform = builder_config.target_platform.ANDROID,
         ),
         android_config = builder_config.android_config(
@@ -141,6 +144,9 @@
             apply_configs = [
                 "download_xr_test_apks",
             ],
+            build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.ARM,
+            target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
         android_config = builder_config.android_config(
@@ -203,6 +209,9 @@
         ),
         chromium_config = builder_config.chromium_config(
             config = "main_builder_rel_mb",
+            build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.ARM,
+            target_bits = 32,
             target_platform = builder_config.target_platform.ANDROID,
         ),
         android_config = builder_config.android_config(
@@ -260,6 +269,9 @@
         ),
         chromium_config = builder_config.chromium_config(
             config = "main_builder_rel_mb",
+            build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.ARM,
+            target_bits = 32,
             target_platform = builder_config.target_platform.ANDROID,
         ),
         android_config = builder_config.android_config(
@@ -368,6 +380,9 @@
             apply_configs = [
                 "download_xr_test_apks",
             ],
+            build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.ARM,
+            target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
         android_config = builder_config.android_config(
@@ -429,6 +444,9 @@
             apply_configs = [
                 "download_xr_test_apks",
             ],
+            build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.ARM,
+            target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
         android_config = builder_config.android_config(
@@ -493,6 +511,9 @@
         ),
         chromium_config = builder_config.chromium_config(
             config = "main_builder_rel_mb",
+            build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.ARM,
+            target_bits = 32,
             target_platform = builder_config.target_platform.ANDROID,
         ),
         android_config = builder_config.android_config(
@@ -543,6 +564,9 @@
         ),
         chromium_config = builder_config.chromium_config(
             config = "main_builder_rel_mb",
+            build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.ARM,
+            target_bits = 32,
             target_platform = builder_config.target_platform.ANDROID,
         ),
         android_config = builder_config.android_config(
@@ -593,6 +617,9 @@
         ),
         chromium_config = builder_config.chromium_config(
             config = "arm64_builder_rel_mb",
+            build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.ARM,
+            target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
         android_config = builder_config.android_config(
@@ -817,6 +844,9 @@
         ),
         chromium_config = builder_config.chromium_config(
             config = "main_builder_rel_mb",
+            build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.ARM,
+            target_bits = 32,
             target_platform = builder_config.target_platform.ANDROID,
         ),
         android_config = builder_config.android_config(
@@ -858,6 +888,9 @@
             apply_configs = [
                 "download_xr_test_apks",
             ],
+            build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.ARM,
+            target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
         android_config = builder_config.android_config(
diff --git a/infra/config/subprojects/chromium/ci/chromium.gpu.star b/infra/config/subprojects/chromium/ci/chromium.gpu.star
index fa03b5f..7ab62f68 100644
--- a/infra/config/subprojects/chromium/ci/chromium.gpu.star
+++ b/infra/config/subprojects/chromium/ci/chromium.gpu.star
@@ -76,6 +76,7 @@
                 "mb",
             ],
             build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.ARM,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -141,6 +142,7 @@
                 "mb",
             ],
             build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.ARM,
             target_bits = 64,
             target_platform = builder_config.target_platform.ANDROID,
         ),
diff --git a/infra/config/subprojects/chromium/ci/chromium.infra.star b/infra/config/subprojects/chromium/ci/chromium.infra.star
index ebb5b600..5d1adb0e 100644
--- a/infra/config/subprojects/chromium/ci/chromium.infra.star
+++ b/infra/config/subprojects/chromium/ci/chromium.infra.star
@@ -180,27 +180,15 @@
                 "tools/android/avd/proto_creation/android_30_google_apis_x86.textpb",
                 "tools/android/avd/proto_creation/android_31_google_apis_x64.textpb",
                 "tools/android/avd/proto_creation/android_32_google_apis_x64_foldable.textpb",
-                "tools/android/avd/proto_creation/android_32_google_apis_x64_foldable_landscape.textpb",
                 "tools/android/avd/proto_creation/android_33_google_apis_x64.textpb",
                 "tools/android/avd/proto_creation/android_34_google_apis_x64.textpb",
                 "tools/android/avd/proto_creation/android_35_google_apis_x64.textpb",
+                "tools/android/avd/proto_creation/android_36_google_apis_x64.textpb",
 
                 # google_apis_tablet system images
                 "tools/android/avd/proto_creation/android_35_google_apis_tablet_x64.textpb",
 
-                # google_atd system images
-                "tools/android/avd/proto_creation/android_30_google_atd_x86.textpb",
-                "tools/android/avd/proto_creation/android_30_google_atd_x64.textpb",
-                "tools/android/avd/proto_creation/android_31_google_atd_x64.textpb",
-                "tools/android/avd/proto_creation/android_32_google_atd_x64_foldable.textpb",
-                "tools/android/avd/proto_creation/android_33_google_atd_x64.textpb",
-
                 # TODO(hypan): Using more specific names for the configs below.
-                "tools/android/avd/proto_creation/generic_android19.textpb",
-                "tools/android/avd/proto_creation/generic_android22.textpb",
-                "tools/android/avd/proto_creation/generic_android23.textpb",
-                "tools/android/avd/proto_creation/generic_android24.textpb",
-                "tools/android/avd/proto_creation/generic_android25.textpb",
                 "tools/android/avd/proto_creation/generic_android26.textpb",
                 "tools/android/avd/proto_creation/generic_android27.textpb",
             ],
diff --git a/infra/config/subprojects/chromium/ci/chromium.star b/infra/config/subprojects/chromium/ci/chromium.star
index 99f85f7e..f6e146a 100644
--- a/infra/config/subprojects/chromium/ci/chromium.star
+++ b/infra/config/subprojects/chromium/ci/chromium.star
@@ -72,6 +72,7 @@
             ],
             build_config = builder_config.build_config.RELEASE,
             target_arch = builder_config.target_arch.ARM,
+            target_bits = 32,
             target_platform = builder_config.target_platform.ANDROID,
         ),
         android_config = builder_config.android_config(
@@ -310,7 +311,9 @@
             apply_configs = [
                 "mb",
             ],
+            build_config = builder_config.build_config.DEBUG,
             target_arch = builder_config.target_arch.ARM,
+            target_bits = 32,
             target_platform = builder_config.target_platform.ANDROID,
         ),
         android_config = builder_config.android_config(
diff --git a/infra/config/subprojects/chromium/try/tryserver.chromium.android.star b/infra/config/subprojects/chromium/try/tryserver.chromium.android.star
index b81c45c..2d0e40ed 100644
--- a/infra/config/subprojects/chromium/try/tryserver.chromium.android.star
+++ b/infra/config/subprojects/chromium/try/tryserver.chromium.android.star
@@ -1350,6 +1350,7 @@
                 "mb",
             ],
             build_config = builder_config.build_config.RELEASE,
+            target_arch = builder_config.target_arch.ARM,
             target_bits = 32,
             target_platform = builder_config.target_platform.ANDROID,
         ),
@@ -1607,6 +1608,9 @@
         ),
         chromium_config = builder_config.chromium_config(
             config = "main_builder",
+            build_config = builder_config.build_config.DEBUG,
+            target_arch = builder_config.target_arch.ARM,
+            target_bits = 32,
             target_platform = builder_config.target_platform.ANDROID,
         ),
         android_config = builder_config.android_config(
diff --git a/infra/config/targets/bundles.star b/infra/config/targets/bundles.star
index 84b9ca8e..fa33389 100644
--- a/infra/config/targets/bundles.star
+++ b/infra/config/targets/bundles.star
@@ -5774,7 +5774,6 @@
         "linux_chromeos_specific_gtests",
         "linux_flavor_specific_chromium_gtests",
         "non_android_chromium_gtests",
-        "pixel_experimental_browser_tests_gtests",
     ],
 )
 
@@ -6256,18 +6255,6 @@
     },
 )
 
-targets.bundle(
-    name = "pixel_experimental_browser_tests_gtests",
-    targets = [
-        "pixel_experimental_browser_tests",
-    ],
-    per_test_modifications = {
-        "pixel_experimental_browser_tests": targets.mixin(
-            experiment_percentage = 100,
-        ),
-    },
-)
-
 # TODO(dpranke): These are run on the p/chromium waterfall; they should
 # probably be run on other builders, and we should get rid of the p/chromium
 # waterfall.
diff --git a/infra/config/targets/tests.star b/infra/config/targets/tests.star
index 617f8b4..0ff29fd0 100644
--- a/infra/config/targets/tests.star
+++ b/infra/config/targets/tests.star
@@ -1971,19 +1971,6 @@
 )
 
 targets.tests.gtest_test(
-    name = "pixel_experimental_browser_tests",
-    mixins = [
-        "skia_gold_test",
-    ],
-    args = [
-        "--browser-ui-tests-verify-pixels",
-        "--enable-pixel-output-in-tests",
-        "--test-launcher-filter-file=../../testing/buildbot/filters/linux-chromeos.browser_tests.pixel_tests.filter",
-    ],
-    binary = "browser_tests",
-)
-
-targets.tests.gtest_test(
     name = "pixel_interactive_ui_tests",
     mixins = [
         "skia_gold_test",
diff --git a/internal b/internal
index a31d97d..8b083b7 160000
--- a/internal
+++ b/internal
@@ -1 +1 @@
-Subproject commit a31d97d94499fb6f4825224003bb2c4e906dc4ff
+Subproject commit 8b083b72d94d2705eb8a8a529226eec645ce4c96
diff --git a/ios/chrome/app/change_profile_animator.mm b/ios/chrome/app/change_profile_animator.mm
index f45a94c..b8ca9f4 100644
--- a/ios/chrome/app/change_profile_animator.mm
+++ b/ios/chrome/app/change_profile_animator.mm
@@ -15,25 +15,6 @@
 #import "ios/chrome/browser/shared/coordinator/scene/scene_state.h"
 #import "ios/chrome/browser/shared/coordinator/scene/scene_state_observer.h"
 
-namespace {
-
-// Invokes `continuation` if `weak_scene_state` is not nil and the UI is
-// enabled (to avoid crashing if the `SceneState` is disconnected while
-// the callback was pending).
-void InvokeChangeProfileContinuation(ChangeProfileContinuation continuation,
-                                     __weak SceneState* weak_scene_state) {
-  if (SceneState* strong_scene_state = weak_scene_state) {
-    if (strong_scene_state.UIEnabled) {
-      std::move(continuation).Run(strong_scene_state, base::DoNothing());
-    }
-  }
-}
-
-// Duration for the fade-in and fade-out of the change profile animation.
-constexpr base::TimeDelta kAnimationDuration = base::Milliseconds(250);
-
-}  // namespace
-
 @interface ChangeProfileAnimation : NSObject
 
 - (instancetype)init NS_UNAVAILABLE;
@@ -51,6 +32,34 @@
 
 @end
 
+namespace {
+
+// Duration for the fade-in and fade-out of the change profile animation.
+constexpr base::TimeDelta kAnimationDuration = base::Milliseconds(250);
+
+// Returns a callback that starts the unblur animation on `animator` with
+// a `duration`, or does nothing if `animator` is nil.
+void UnblurWithDuration(ChangeProfileAnimation* animator,
+                        base::TimeDelta duration) {
+  [animator unblurWithDuration:kAnimationDuration];
+}
+
+// Invokes `continuation` if `weak_scene_state` is not nil and the UI is
+// enabled (to avoid crashing if the `SceneState` is disconnected while
+// the callback was pending). Then stops the animation using `animator`
+// once the continuation completes.
+void InvokeChangeProfileContinuation(ChangeProfileContinuation continuation,
+                                     __weak SceneState* weak_scene_state,
+                                     base::OnceClosure closure) {
+  if (SceneState* strong_scene_state = weak_scene_state) {
+    if (strong_scene_state.UIEnabled) {
+      std::move(continuation).Run(strong_scene_state, std::move(closure));
+    }
+  }
+}
+
+}  // namespace
+
 @implementation ChangeProfileAnimation {
   // The window on which the animations should be played.
   __weak MDCOverlayWindow* _window;
@@ -231,12 +240,6 @@
 
 #pragma mark Private methods
 
-// Stops the animation (if it has been started). No-op if the animation
-// has not been started or already stopped.
-- (void)stopAnimation {
-  [_animation unblurWithDuration:kAnimationDuration];
-}
-
 // Called when the initialisation progressed (i.e. the state of any of the
 // observed object changed).
 - (void)initialisationProgressed {
@@ -249,14 +252,18 @@
     return;
   }
 
-  [self stopAnimation];
-
   // Ensure that the completion is always invoked asynchronously, even if
-  // the profile was already in the expected stage. This does not strongly
-  // captures SceneState since _sceneDelete is a weak pointer.
+  // the profile was already in the expected stage and that the animation
+  // to unblur the view starts when the continuation is complete.
+  //
+  // The callback does not strongly retain the SceneState since the ivar
+  // is declared as __weak SceneState* and base::BindOnce(...) correctly
+  // use a weak pointer for its storage.
   base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE, base::BindOnce(&InvokeChangeProfileContinuation,
-                                std::move(_continuation), _sceneState));
+                                std::move(_continuation), _sceneState,
+                                base::BindOnce(&UnblurWithDuration, _animation,
+                                               kAnimationDuration)));
 
   // Stop observing the ProfileState and the SceneState.
   [profileState removeObserver:self];
diff --git a/ios/chrome/app/strings/ios_chromium_strings.grd b/ios/chrome/app/strings/ios_chromium_strings.grd
index 4e9c1c0..5cddc7b 100644
--- a/ios/chrome/app/strings/ios_chromium_strings.grd
+++ b/ios/chrome/app/strings/ios_chromium_strings.grd
@@ -231,6 +231,12 @@
       <message name="IDS_IOS_BEST_FEATURES_NEVER_FORGET_PASSWORDS_SECOND_STEP" desc="The second step to set up save and autofill passwords">
         Chromium will offer to save your password
       </message>
+      <message name="IDS_IOS_BEST_FEATURES_PASSWORDS_IN_OTHER_APPS_STEP_4_SELECT" desc="The fourth step to enable Passwords in other apps. OS must be below iOS 18.">
+        Select Chromium
+      </message>
+      <message name="IDS_IOS_BEST_FEATURES_PASSWORDS_IN_OTHER_APPS_STEP_4_TOGGLE" desc="The fourth step to enable Passwords in other apps. OS must be iOS 18 or later.">
+        Toggle Chromium
+      </message>
       <message name="IDS_IOS_BEST_FEATURES_PASSWORDS_IN_OTHER_APPS_SUBTITLE" desc="Subtitle for the Passwords in other apps Chrome iOS Best Features Promo">
         To easily get your saved passwords in your other apps, choose Chromium for Autofill
       </message>
diff --git a/ios/chrome/app/strings/ios_chromium_strings_grd/IDS_IOS_BEST_FEATURES_PASSWORDS_IN_OTHER_APPS_STEP_4_SELECT.png.sha1 b/ios/chrome/app/strings/ios_chromium_strings_grd/IDS_IOS_BEST_FEATURES_PASSWORDS_IN_OTHER_APPS_STEP_4_SELECT.png.sha1
new file mode 100644
index 0000000..acb40183
--- /dev/null
+++ b/ios/chrome/app/strings/ios_chromium_strings_grd/IDS_IOS_BEST_FEATURES_PASSWORDS_IN_OTHER_APPS_STEP_4_SELECT.png.sha1
@@ -0,0 +1 @@
+e235e33b8f6049be813d506e46316a181c06950c
\ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_chromium_strings_grd/IDS_IOS_BEST_FEATURES_PASSWORDS_IN_OTHER_APPS_STEP_4_TOGGLE.png.sha1 b/ios/chrome/app/strings/ios_chromium_strings_grd/IDS_IOS_BEST_FEATURES_PASSWORDS_IN_OTHER_APPS_STEP_4_TOGGLE.png.sha1
new file mode 100644
index 0000000..741ed5e
--- /dev/null
+++ b/ios/chrome/app/strings/ios_chromium_strings_grd/IDS_IOS_BEST_FEATURES_PASSWORDS_IN_OTHER_APPS_STEP_4_TOGGLE.png.sha1
@@ -0,0 +1 @@
+7de89dfa07216680b58d73ce796a5fc7eff385cb
\ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_google_chrome_strings.grd b/ios/chrome/app/strings/ios_google_chrome_strings.grd
index a10c807c..81eac69 100644
--- a/ios/chrome/app/strings/ios_google_chrome_strings.grd
+++ b/ios/chrome/app/strings/ios_google_chrome_strings.grd
@@ -231,6 +231,12 @@
       <message name="IDS_IOS_BEST_FEATURES_NEVER_FORGET_PASSWORDS_SECOND_STEP" desc="The second step to set up save and autofill passwords">
         Chrome will offer to save your password
       </message>
+      <message name="IDS_IOS_BEST_FEATURES_PASSWORDS_IN_OTHER_APPS_STEP_4_SELECT" desc="The fourth step to enable Passwords in other apps. OS must be below iOS 18.">
+        Select Chrome
+      </message>
+      <message name="IDS_IOS_BEST_FEATURES_PASSWORDS_IN_OTHER_APPS_STEP_4_TOGGLE" desc="The fourth step to enable Passwords in other apps. OS must be iOS 18 or later.">
+        Toggle Chrome
+      </message>
       <message name="IDS_IOS_BEST_FEATURES_PASSWORDS_IN_OTHER_APPS_SUBTITLE" desc="Subtitle for the Passwords in other apps Chrome iOS Best Features Promo">
         To easily get your saved passwords in your other apps, choose Chrome for Autofill
       </message>
diff --git a/ios/chrome/app/strings/ios_google_chrome_strings_grd/IDS_IOS_BEST_FEATURES_PASSWORDS_IN_OTHER_APPS_STEP_4_SELECT.png.sha1 b/ios/chrome/app/strings/ios_google_chrome_strings_grd/IDS_IOS_BEST_FEATURES_PASSWORDS_IN_OTHER_APPS_STEP_4_SELECT.png.sha1
new file mode 100644
index 0000000..acb40183
--- /dev/null
+++ b/ios/chrome/app/strings/ios_google_chrome_strings_grd/IDS_IOS_BEST_FEATURES_PASSWORDS_IN_OTHER_APPS_STEP_4_SELECT.png.sha1
@@ -0,0 +1 @@
+e235e33b8f6049be813d506e46316a181c06950c
\ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_google_chrome_strings_grd/IDS_IOS_BEST_FEATURES_PASSWORDS_IN_OTHER_APPS_STEP_4_TOGGLE.png.sha1 b/ios/chrome/app/strings/ios_google_chrome_strings_grd/IDS_IOS_BEST_FEATURES_PASSWORDS_IN_OTHER_APPS_STEP_4_TOGGLE.png.sha1
new file mode 100644
index 0000000..741ed5e
--- /dev/null
+++ b/ios/chrome/app/strings/ios_google_chrome_strings_grd/IDS_IOS_BEST_FEATURES_PASSWORDS_IN_OTHER_APPS_STEP_4_TOGGLE.png.sha1
@@ -0,0 +1 @@
+7de89dfa07216680b58d73ce796a5fc7eff385cb
\ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_strings.grd b/ios/chrome/app/strings/ios_strings.grd
index 5c6ecf07c..6eaf5ac 100644
--- a/ios/chrome/app/strings/ios_strings.grd
+++ b/ios/chrome/app/strings/ios_strings.grd
@@ -605,6 +605,21 @@
       <message name="IDS_IOS_BEST_FEATURES_PASSWORDS_IN_OTHER_APPS_CAPTION" desc="Caption for the Passwords in other apps button on the Chrome iOS Best Features Promo">
         Get saved passwords in other apps
       </message>
+      <message name="IDS_IOS_BEST_FEATURES_PASSWORDS_IN_OTHER_APPS_STEP_1" desc="The first step to enable Passwords in other apps">
+        Open Settings
+      </message>
+      <message name="IDS_IOS_BEST_FEATURES_PASSWORDS_IN_OTHER_APPS_STEP_2_GENERAL" desc="The second step to enable Passwords in other apps. OS must be iOS 18 or later.">
+        Open General
+      </message>
+      <message name="IDS_IOS_BEST_FEATURES_PASSWORDS_IN_OTHER_APPS_STEP_2_PASSWORDS" desc="The second step to enable Passwords in other apps. OS must be below iOS 18.">
+        Open Passwords
+      </message>
+      <message name="IDS_IOS_BEST_FEATURES_PASSWORDS_IN_OTHER_APPS_STEP_3_AUTOFILL_SETTINGS" desc="The third step to enable Passwords in other apps. OS must be iOS 18 or later.">
+        Open Autofill &amp; Password settings
+      </message>
+      <message name="IDS_IOS_BEST_FEATURES_PASSWORDS_IN_OTHER_APPS_STEP_3_PASSWORD_OPTIONS" desc="The third step to enable Passwords in other apps. OS must be below iOS 18.">
+        Open Password Options
+      </message>
       <message name="IDS_IOS_BEST_FEATURES_PASSWORDS_IN_OTHER_APPS_TITLE" desc="Title for the Passwords in other apps Chrome iOS Best Features Promo">
         Passwords in other apps
       </message>
@@ -7265,4 +7280,4 @@
       </message>
       </messages>
   </release>
-</grit>
\ No newline at end of file
+</grit>
diff --git a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_BEST_FEATURES_PASSWORDS_IN_OTHER_APPS_STEP_1.png.sha1 b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_BEST_FEATURES_PASSWORDS_IN_OTHER_APPS_STEP_1.png.sha1
new file mode 100644
index 0000000..acb40183
--- /dev/null
+++ b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_BEST_FEATURES_PASSWORDS_IN_OTHER_APPS_STEP_1.png.sha1
@@ -0,0 +1 @@
+e235e33b8f6049be813d506e46316a181c06950c
\ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_BEST_FEATURES_PASSWORDS_IN_OTHER_APPS_STEP_2_GENERAL.png.sha1 b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_BEST_FEATURES_PASSWORDS_IN_OTHER_APPS_STEP_2_GENERAL.png.sha1
new file mode 100644
index 0000000..741ed5e
--- /dev/null
+++ b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_BEST_FEATURES_PASSWORDS_IN_OTHER_APPS_STEP_2_GENERAL.png.sha1
@@ -0,0 +1 @@
+7de89dfa07216680b58d73ce796a5fc7eff385cb
\ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_BEST_FEATURES_PASSWORDS_IN_OTHER_APPS_STEP_2_PASSWORDS.png.sha1 b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_BEST_FEATURES_PASSWORDS_IN_OTHER_APPS_STEP_2_PASSWORDS.png.sha1
new file mode 100644
index 0000000..acb40183
--- /dev/null
+++ b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_BEST_FEATURES_PASSWORDS_IN_OTHER_APPS_STEP_2_PASSWORDS.png.sha1
@@ -0,0 +1 @@
+e235e33b8f6049be813d506e46316a181c06950c
\ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_BEST_FEATURES_PASSWORDS_IN_OTHER_APPS_STEP_3_AUTOFILL_SETTINGS.png.sha1 b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_BEST_FEATURES_PASSWORDS_IN_OTHER_APPS_STEP_3_AUTOFILL_SETTINGS.png.sha1
new file mode 100644
index 0000000..741ed5e
--- /dev/null
+++ b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_BEST_FEATURES_PASSWORDS_IN_OTHER_APPS_STEP_3_AUTOFILL_SETTINGS.png.sha1
@@ -0,0 +1 @@
+7de89dfa07216680b58d73ce796a5fc7eff385cb
\ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_BEST_FEATURES_PASSWORDS_IN_OTHER_APPS_STEP_3_PASSWORD_OPTIONS.png.sha1 b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_BEST_FEATURES_PASSWORDS_IN_OTHER_APPS_STEP_3_PASSWORD_OPTIONS.png.sha1
new file mode 100644
index 0000000..acb40183
--- /dev/null
+++ b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_BEST_FEATURES_PASSWORDS_IN_OTHER_APPS_STEP_3_PASSWORD_OPTIONS.png.sha1
@@ -0,0 +1 @@
+e235e33b8f6049be813d506e46316a181c06950c
\ No newline at end of file
diff --git a/ios/chrome/browser/authentication/ui_bundled/BUILD.gn b/ios/chrome/browser/authentication/ui_bundled/BUILD.gn
index 5589f767..307ed31 100644
--- a/ios/chrome/browser/authentication/ui_bundled/BUILD.gn
+++ b/ios/chrome/browser/authentication/ui_bundled/BUILD.gn
@@ -194,6 +194,7 @@
     ":signin_presenter",
     ":ui_bundled",
     "//components/bookmarks/browser",
+    "//components/policy/core/browser",
     "//components/prefs",
     "//components/signin/public/identity_manager",
     "//components/signin/public/identity_manager:test_support",
@@ -243,6 +244,7 @@
     ":signin_presenter",
     "//base",
     "//base/test:test_support",
+    "//components/policy/core/browser",
     "//components/signin/public/base",
     "//components/sync/base",
     "//ios/chrome/app/strings",
diff --git a/ios/chrome/browser/authentication/ui_bundled/authentication_flow/authentication_flow.h b/ios/chrome/browser/authentication/ui_bundled/authentication_flow/authentication_flow.h
index b6a18a8a..bb96706 100644
--- a/ios/chrome/browser/authentication/ui_bundled/authentication_flow/authentication_flow.h
+++ b/ios/chrome/browser/authentication/ui_bundled/authentication_flow/authentication_flow.h
@@ -7,6 +7,7 @@
 
 #import <UIKit/UIKit.h>
 
+#import "components/policy/core/browser/signin/profile_separation_policies.h"
 #import "components/signin/public/base/signin_metrics.h"
 #import "ios/chrome/browser/authentication/ui_bundled/authentication_flow/authentication_flow_performer_delegate.h"
 #import "ios/chrome/browser/authentication/ui_bundled/signin/signin_constants.h"
@@ -60,6 +61,13 @@
 // Does noting if the sign-in flow is already done
 - (void)interrupt;
 
+// Forces the ProfileSeparationDataMigrationSettings value for the next request
+// made to fetch ProfileSeparationPolicies. This function is only for testing
+// purposes.
++ (void)forcePolicyResponseForNextRequestForTesting:
+    (policy::ProfileSeparationDataMigrationSettings)
+        profileSeparationDataMigrationSettings;
+
 // Identity to sign-in.
 @property(nonatomic, strong, readonly) id<SystemIdentity> identity;
 
diff --git a/ios/chrome/browser/authentication/ui_bundled/authentication_flow/authentication_flow.mm b/ios/chrome/browser/authentication/ui_bundled/authentication_flow/authentication_flow.mm
index 03afac2..59c70662 100644
--- a/ios/chrome/browser/authentication/ui_bundled/authentication_flow/authentication_flow.mm
+++ b/ios/chrome/browser/authentication/ui_bundled/authentication_flow/authentication_flow.mm
@@ -57,6 +57,14 @@
 
 namespace {
 
+// Returns a reference to the global used by tests to force the
+// next policy fetch to terminate with this policy value if set.
+std::optional<policy::ProfileSeparationDataMigrationSettings>&
+GetForcedPolicyResponseForNextFetchRequestForTesting() {
+  static std::optional<policy::ProfileSeparationDataMigrationSettings> instance;
+  return instance;
+}
+
 // The states of the sign-in flow state machine.
 enum class AuthenticationState {
   kBegin,
@@ -580,6 +588,21 @@
     [self continueFlow];
     return;
   }
+
+  auto& optionalForcedPolicy =
+      GetForcedPolicyResponseForNextFetchRequestForTesting();
+  if (optionalForcedPolicy.has_value()) {
+    auto policy = optionalForcedPolicy.value();
+    optionalForcedPolicy = std::nullopt;
+
+    __weak __typeof(self) weakSelf = self;
+    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
+        FROM_HERE, base::BindOnce(^{
+          [weakSelf didFetchProfileSeparationPolicies:policy];
+        }));
+    return;
+  }
+
   ProfileIOS* profile = [self originalProfile];
   [_performer fetchProfileSeparationPolicies:profile
                                  forIdentity:_identityToSignIn];
@@ -892,4 +915,13 @@
   return [self originalProfile]->GetPrefs();
 }
 
++ (void)forcePolicyResponseForNextRequestForTesting:
+    (policy::ProfileSeparationDataMigrationSettings)
+        profileSeparationDataMigrationSettings {
+  auto& optionalForcedPolicy =
+      GetForcedPolicyResponseForNextFetchRequestForTesting();
+  CHECK(!optionalForcedPolicy.has_value());
+  optionalForcedPolicy = profileSeparationDataMigrationSettings;
+}
+
 @end
diff --git a/ios/chrome/browser/authentication/ui_bundled/separate_profiles_egtest.mm b/ios/chrome/browser/authentication/ui_bundled/separate_profiles_egtest.mm
index 153c97f..9c0b0de2 100644
--- a/ios/chrome/browser/authentication/ui_bundled/separate_profiles_egtest.mm
+++ b/ios/chrome/browser/authentication/ui_bundled/separate_profiles_egtest.mm
@@ -216,7 +216,7 @@
           chrome_test_util::ManagedProfileCreationNavigationBarBackButton()]
       performAction:grey_tap()];
 
-  // Wait for the browsing data management to disappear screen.
+  // Wait for the browsing data management screen to disappear.
   [ChromeEarlGrey waitForSufficientlyVisibleElementWithMatcher:
                       ManagedProfileCreationScreenMatcher()];
 
@@ -313,7 +313,7 @@
           chrome_test_util::ManagedProfileCreationNavigationBarBackButton()]
       performAction:grey_tap()];
 
-  // Wait for the browsing data management to disappear screen.
+  // Wait for the browsing data management screen to disappear.
   [ChromeEarlGrey waitForSufficientlyVisibleElementWithMatcher:
                       ManagedProfileCreationScreenMatcher()];
 
@@ -385,6 +385,10 @@
           userIntegerPref:prefs::kProfileSeparationDataMigrationSettings],
       @"Profile separation data migration settings not properly set.");
 
+  // Enabled on account level, but the strictest value should apply.
+  [SigninEarlGrey setPolicyResponseForNextProfileSeparationPolicyRequest:
+                      policy::USER_OPT_IN];
+
   // Switch to the managed account, and sign in with the managed account.
   TapIdentityDisc();
   [[EarlGrey selectElementWithMatcher:ContinueButtonWithIdentityMatcher(
@@ -432,8 +436,58 @@
 }
 
 // Tests that signing in from a signed out state with a managed account
+// shows the enterprise onboarding. And The user cannot merge existing
+// browsing data because it is disabled by policy.
+- (void)
+    testSigninWithManagedAccountFromUnsignedStateWithDataMigrationDisabledOnAccount {
+  // Separate profiles are only available in iOS 17+.
+  if (!@available(iOS 17, *)) {
+    return;
+  }
+  // Setup: There's 1 managed account. No account is signed in.
+  FakeSystemIdentity* const managedIdentity =
+      [FakeSystemIdentity fakeManagedIdentity];
+  [SigninEarlGrey addFakeIdentity:managedIdentity];
+
+  // Disabled browsing data migration
+  [SigninEarlGrey setPolicyResponseForNextProfileSeparationPolicyRequest:
+                      policy::ALWAYS_SEPARATE];
+
+  // Set device policy that enables migration, the most strict value should
+  // apply.
+  [ChromeEarlGrey
+      setIntegerValue:policy::USER_OPT_OUT
+          forUserPref:prefs::kProfileSeparationDataMigrationSettings];
+
+  // Switch to the managed account, and sign in with the managed account.
+  TapIdentityDisc();
+  [[EarlGrey selectElementWithMatcher:ContinueButtonWithIdentityMatcher(
+                                          managedIdentity)]
+      performAction:grey_tap()];
+
+  // Wait for enterprise onboarding screen.
+  [ChromeEarlGrey waitForSufficientlyVisibleElementWithMatcher:
+                      ManagedProfileCreationScreenMatcher()];
+
+  // The data migration disabled message should be shown
+  [[EarlGrey selectElementWithMatcher:
+                 ManagedProfileCreationDataMigrationDisabledSubtitleMatcher()]
+      assertWithMatcher:grey_sufficientlyVisible()];
+
+  // We are still signed out before accepting enterprise management.
+  [SigninEarlGrey verifySignedOut];
+
+  // Confirm the enterprise onboarding screen.
+  [[EarlGrey selectElementWithMatcher:chrome_test_util::
+                                          PromoScreenPrimaryButtonMatcher()]
+      performAction:grey_tap()];
+
+  [SigninEarlGrey verifySignedInWithFakeIdentity:managedIdentity];
+}
+
+// Tests that signing in from a signed out state with a managed account
 // shows the enterprise onboarding only the first time and merging browsing data
-// // is suggested by policy.
+// is suggested by policy.
 - (void)testSigninWithManagedAccountFromUnsignedStateWithDataMergingSuggested {
   // Separate profiles are only available in iOS 17+.
   if (!@available(iOS 17, *)) {
@@ -487,7 +541,7 @@
           chrome_test_util::ManagedProfileCreationNavigationBarBackButton()]
       performAction:grey_tap()];
 
-  // Wait for the browsing data management to disappear screen.
+  // Wait for the browsing data management screen to disappear.
   [ChromeEarlGrey waitForSufficientlyVisibleElementWithMatcher:
                       ManagedProfileCreationScreenMatcher()];
 
@@ -528,6 +582,74 @@
   [SigninEarlGrey verifySignedInWithFakeIdentity:managedIdentity];
 }
 
+// Tests that signing in from a signed out state with a managed account
+// shows the enterprise onboarding and when merging browsing data is suggested
+// by policy on the account, it does not actually suggest merging the data since
+// that value is not supported at account level.
+- (void)
+    testSigninWithManagedAccountFromUnsignedStateWithDataMergingSuggestedOnAccount {
+  // Separate profiles are only available in iOS 17+.
+  if (!@available(iOS 17, *)) {
+    return;
+  }
+
+  // Setup: There's 1 managed account. No account is signed in.
+  FakeSystemIdentity* const managedIdentity =
+      [FakeSystemIdentity fakeManagedIdentity];
+  [SigninEarlGrey addFakeIdentity:managedIdentity];
+
+  // Set the policy to suggest data migration. Note that this is not actually
+  // supported on the account level, so the user should still see
+  // "keep separate" by default.
+  [SigninEarlGrey setPolicyResponseForNextProfileSeparationPolicyRequest:
+                      policy::USER_OPT_OUT];
+
+  // Switch to the managed account, and sign in with the managed account.
+  TapIdentityDisc();
+  [[EarlGrey selectElementWithMatcher:ContinueButtonWithIdentityMatcher(
+                                          managedIdentity)]
+      performAction:grey_tap()];
+
+  // Wait for enterprise onboarding screen.
+  [ChromeEarlGrey waitForSufficientlyVisibleElementWithMatcher:
+                      ManagedProfileCreationScreenMatcher()];
+
+  // Open the browsing data management screen.
+  [[EarlGrey selectElementWithMatcher:
+                 ManagedProfileCreationBrowsingDataButtonMatcher()]
+      assertWithMatcher:grey_sufficientlyVisible()];
+  [[EarlGrey selectElementWithMatcher:
+                 ManagedProfileCreationBrowsingDataButtonMatcher()]
+      performAction:grey_tap()];
+
+  [ChromeEarlGrey waitForSufficientlyVisibleElementWithMatcher:
+                      BrowsingDataManagementScreenMatcher()];
+
+  // Browsing data not merged by default when the policy comes from the account
+  // and not the device.
+  [[EarlGrey selectElementWithMatcher:SeparateBrowsingDataCellMatcher()]
+      assertWithMatcher:grey_selected()];
+
+  [[EarlGrey
+      selectElementWithMatcher:
+          chrome_test_util::ManagedProfileCreationNavigationBarBackButton()]
+      performAction:grey_tap()];
+
+  // Wait for the browsing data management screen to disappear.
+  [ChromeEarlGrey waitForSufficientlyVisibleElementWithMatcher:
+                      ManagedProfileCreationScreenMatcher()];
+
+  // We are still signed out before accepting enterprise management.
+  [SigninEarlGrey verifySignedOut];
+
+  // Confirm the enterprise onboarding screen.
+  [[EarlGrey selectElementWithMatcher:chrome_test_util::
+                                          PromoScreenPrimaryButtonMatcher()]
+      performAction:grey_tap()];
+
+  [SigninEarlGrey verifySignedInWithFakeIdentity:managedIdentity];
+}
+
 // Tests switching to a managed account (and thus managed profile) and back via
 // the account menu.
 - (void)testSwitchFromPersonalToManagedAndBack {
diff --git a/ios/chrome/browser/authentication/ui_bundled/signin_earl_grey.h b/ios/chrome/browser/authentication/ui_bundled/signin_earl_grey.h
index acd47769..ce3c4ef7 100644
--- a/ios/chrome/browser/authentication/ui_bundled/signin_earl_grey.h
+++ b/ios/chrome/browser/authentication/ui_bundled/signin_earl_grey.h
@@ -7,6 +7,7 @@
 
 #import <Foundation/Foundation.h>
 
+#import "components/policy/core/browser/signin/profile_separation_policies.h"
 #import "components/sync/base/user_selectable_type.h"
 #import "ios/testing/earl_grey/base_eg_test_helper_impl.h"
 
@@ -141,6 +142,12 @@
 // number of time indicated in the property for `accessPoint`.
 - (void)assertExpectedSigninHistograms:(ExpectedSigninHistograms*)expecteds;
 
+// Stores a policy that will be returned for the next fetch profile separation
+// policy request.
+- (void)setPolicyResponseForNextProfileSeparationPolicyRequest:
+    (policy::ProfileSeparationDataMigrationSettings)
+        profileSeparationDataMigrationSettings;
+
 @end
 
 #endif  // IOS_CHROME_BROWSER_AUTHENTICATION_UI_BUNDLED_SIGNIN_EARL_GREY_H_
diff --git a/ios/chrome/browser/authentication/ui_bundled/signin_earl_grey.mm b/ios/chrome/browser/authentication/ui_bundled/signin_earl_grey.mm
index d63530c..3fdaa9e 100644
--- a/ios/chrome/browser/authentication/ui_bundled/signin_earl_grey.mm
+++ b/ios/chrome/browser/authentication/ui_bundled/signin_earl_grey.mm
@@ -5,6 +5,7 @@
 #import "ios/chrome/browser/authentication/ui_bundled/signin_earl_grey.h"
 
 #import "base/test/ios/wait_util.h"
+#import "components/policy/core/browser/signin/profile_separation_policies.h"
 #import "components/signin/public/base/consent_level.h"
 #import "components/signin/public/base/signin_metrics.h"
 #import "ios/chrome/browser/authentication/ui_bundled/expected_signin_histograms.h"
@@ -234,4 +235,12 @@
   }
 }
 
+- (void)setPolicyResponseForNextProfileSeparationPolicyRequest:
+    (policy::ProfileSeparationDataMigrationSettings)
+        profileSeparationDataMigrationSettings {
+  [SigninEarlGreyAppInterface
+      setPolicyResponseForNextProfileSeparationPolicyRequest:
+          profileSeparationDataMigrationSettings];
+}
+
 @end
diff --git a/ios/chrome/browser/authentication/ui_bundled/signin_earl_grey_app_interface.h b/ios/chrome/browser/authentication/ui_bundled/signin_earl_grey_app_interface.h
index c9ea76a..1d814ab4 100644
--- a/ios/chrome/browser/authentication/ui_bundled/signin_earl_grey_app_interface.h
+++ b/ios/chrome/browser/authentication/ui_bundled/signin_earl_grey_app_interface.h
@@ -7,6 +7,7 @@
 
 #import <UIKit/UIKit.h>
 
+#import "components/policy/core/browser/signin/profile_separation_policies.h"
 #import "ios/chrome/browser/signin/model/capabilities_dict.h"
 #import "url/gurl.h"
 
@@ -107,6 +108,12 @@
 // Returns if the data type is enabled for the sync service.
 + (BOOL)isSelectedTypeEnabled:(syncer::UserSelectableType)type;
 
+// Stores a policy that will be returned for the next fetch profile separation
+// policy request.
++ (void)setPolicyResponseForNextProfileSeparationPolicyRequest:
+    (policy::ProfileSeparationDataMigrationSettings)
+        profileSeparationDataMigrationSettings;
+
 @end
 
 #endif  // IOS_CHROME_BROWSER_AUTHENTICATION_UI_BUNDLED_SIGNIN_EARL_GREY_APP_INTERFACE_H_
diff --git a/ios/chrome/browser/authentication/ui_bundled/signin_earl_grey_app_interface.mm b/ios/chrome/browser/authentication/ui_bundled/signin_earl_grey_app_interface.mm
index a1c766de..ddd2b34 100644
--- a/ios/chrome/browser/authentication/ui_bundled/signin_earl_grey_app_interface.mm
+++ b/ios/chrome/browser/authentication/ui_bundled/signin_earl_grey_app_interface.mm
@@ -12,6 +12,7 @@
 #import "base/notreached.h"
 #import "base/strings/sys_string_conversions.h"
 #import "components/bookmarks/browser/titled_url_match.h"
+#import "components/policy/core/browser/signin/profile_separation_policies.h"
 #import "components/prefs/pref_service.h"
 #import "components/signin/public/base/signin_pref_names.h"
 #import "components/signin/public/identity_manager/account_capabilities_test_mutator.h"
@@ -218,4 +219,11 @@
   return settings->GetSelectedTypes().Has(type) ? YES : NO;
 }
 
++ (void)setPolicyResponseForNextProfileSeparationPolicyRequest:
+    (policy::ProfileSeparationDataMigrationSettings)
+        profileSeparationDataMigrationSettings {
+  chrome_test_util::SetPolicyResponseForNextProfileSeparationPolicyRequest(
+      profileSeparationDataMigrationSettings);
+}
+
 @end
diff --git a/ios/chrome/browser/context_menu/ui_bundled/BUILD.gn b/ios/chrome/browser/context_menu/ui_bundled/BUILD.gn
index 79637fc..66ff9517 100644
--- a/ios/chrome/browser/context_menu/ui_bundled/BUILD.gn
+++ b/ios/chrome/browser/context_menu/ui_bundled/BUILD.gn
@@ -135,6 +135,7 @@
     ":constants",
     "//base",
     "//base/test:test_support",
+    "//components/data_sharing/public:features",
     "//components/strings",
     "//components/url_formatter",
     "//ios/chrome/app/strings",
diff --git a/ios/chrome/browser/context_menu/ui_bundled/DEPS b/ios/chrome/browser/context_menu/ui_bundled/DEPS
index ed34577..ce04317 100644
--- a/ios/chrome/browser/context_menu/ui_bundled/DEPS
+++ b/ios/chrome/browser/context_menu/ui_bundled/DEPS
@@ -1,4 +1,5 @@
 include_rules = [
+  "+components/data_sharing/public/features.h",
   "+ios/chrome/browser/favicon/model",
   "+ios/chrome/browser/net/model/crurl.h",
   "+ios/chrome/browser/photos/model",
diff --git a/ios/chrome/browser/context_menu/ui_bundled/context_menu_egtest.mm b/ios/chrome/browser/context_menu/ui_bundled/context_menu_egtest.mm
index ec07c6c..3de8b1b 100644
--- a/ios/chrome/browser/context_menu/ui_bundled/context_menu_egtest.mm
+++ b/ios/chrome/browser/context_menu/ui_bundled/context_menu_egtest.mm
@@ -10,6 +10,7 @@
 #import "base/ios/ios_util.h"
 #import "base/strings/sys_string_conversions.h"
 #import "base/test/ios/wait_util.h"
+#import "components/data_sharing/public/features.h"
 #import "components/strings/grit/components_strings.h"
 #import "components/url_formatter/url_formatter.h"
 #import "ios/chrome/browser/context_menu/ui_bundled/constants.h"
@@ -301,6 +302,8 @@
   AppLaunchConfiguration config;
   config.features_enabled.push_back(kTabGroupsIPad);
   config.features_enabled.push_back(kShareInWebContextMenuIOS);
+  config.features_enabled.push_back(
+      data_sharing::features::kDataSharingFeature);
   config.features_disabled.push_back(web::features::kSmoothScrollingDefault);
   return config;
 }
diff --git a/ios/chrome/browser/first_run/ui_bundled/best_features/coordinator/best_features_screen_coordinator.mm b/ios/chrome/browser/first_run/ui_bundled/best_features/coordinator/best_features_screen_coordinator.mm
index a8ca715..8348c26 100644
--- a/ios/chrome/browser/first_run/ui_bundled/best_features/coordinator/best_features_screen_coordinator.mm
+++ b/ios/chrome/browser/first_run/ui_bundled/best_features/coordinator/best_features_screen_coordinator.mm
@@ -83,7 +83,7 @@
   // Retrieve the user's segmentation status before presenting the view if the
   // "shopping" arm is enabled. Otherwise, present the view.
   if (variation == first_run::BestFeaturesScreenVariationType::
-                       kShoppingUsersWithFallbackBeforeDBPromo) {
+                       kShoppingUsersWithFallbackAfterDBPromo) {
     // Present a transparent view to block UI interaction until screen presents.
     // TODO(crbug.com/396480750): This is a temporary solution. If the feature
     // becomes a full launch candidate, consider more polished solutions, like a
diff --git a/ios/chrome/browser/first_run/ui_bundled/best_features/coordinator/best_features_screen_mediator.mm b/ios/chrome/browser/first_run/ui_bundled/best_features/coordinator/best_features_screen_mediator.mm
index bb23641..78d1fa4 100644
--- a/ios/chrome/browser/first_run/ui_bundled/best_features/coordinator/best_features_screen_mediator.mm
+++ b/ios/chrome/browser/first_run/ui_bundled/best_features/coordinator/best_features_screen_mediator.mm
@@ -94,7 +94,7 @@
       itemTypes = {kLensSearch, kEnhancedSafeBrowsing,
                    kSaveAndAutofillPasswords};
       break;
-    case kShoppingUsersWithFallbackBeforeDBPromo:
+    case kShoppingUsersWithFallbackAfterDBPromo:
       if (_shoppingUser && _shoppingService->IsShoppingListEligible()) {
         itemTypes = {kTabGroups, kLockedIncognitoTabs,
                      kPriceTrackingAndInsights};
diff --git a/ios/chrome/browser/first_run/ui_bundled/best_features/ui/best_features_item.mm b/ios/chrome/browser/first_run/ui_bundled/best_features/ui/best_features_item.mm
index 2a52cb78..16f662a 100644
--- a/ios/chrome/browser/first_run/ui_bundled/best_features/ui/best_features_item.mm
+++ b/ios/chrome/browser/first_run/ui_bundled/best_features/ui/best_features_item.mm
@@ -207,11 +207,30 @@
         ]];
         break;
       case BestFeaturesItemType::kAutofillPasswordsInOtherApps:
-        // TODO(crbug.com/405405829): Update the instructions when the new
-        // strings are uploaded.
-        [instructions addObjectsFromArray:@[
-          @"",
-        ]];
+        // Add the correct strings depending on the device OS.
+        if (@available(iOS 18, *)) {
+          [instructions addObjectsFromArray:@[
+            l10n_util::GetNSString(
+                IDS_IOS_BEST_FEATURES_PASSWORDS_IN_OTHER_APPS_STEP_1),
+            l10n_util::GetNSString(
+                IDS_IOS_BEST_FEATURES_PASSWORDS_IN_OTHER_APPS_STEP_2_GENERAL),
+            l10n_util::GetNSString(
+                IDS_IOS_BEST_FEATURES_PASSWORDS_IN_OTHER_APPS_STEP_3_AUTOFILL_SETTINGS),
+            l10n_util::GetNSString(
+                IDS_IOS_BEST_FEATURES_PASSWORDS_IN_OTHER_APPS_STEP_4_TOGGLE),
+          ]];
+        } else {
+          [instructions addObjectsFromArray:@[
+            l10n_util::GetNSString(
+                IDS_IOS_BEST_FEATURES_PASSWORDS_IN_OTHER_APPS_STEP_1),
+            l10n_util::GetNSString(
+                IDS_IOS_BEST_FEATURES_PASSWORDS_IN_OTHER_APPS_STEP_2_PASSWORDS),
+            l10n_util::GetNSString(
+                IDS_IOS_BEST_FEATURES_PASSWORDS_IN_OTHER_APPS_STEP_3_PASSWORD_OPTIONS),
+            l10n_util::GetNSString(
+                IDS_IOS_BEST_FEATURES_PASSWORDS_IN_OTHER_APPS_STEP_4_SELECT),
+          ]];
+        }
         break;
       case BestFeaturesItemType::kSharePasswordsWithFamily:
         [instructions addObjectsFromArray:@[
diff --git a/ios/chrome/browser/first_run/ui_bundled/best_features/ui/best_features_view_controller.mm b/ios/chrome/browser/first_run/ui_bundled/best_features/ui/best_features_view_controller.mm
index 43d1f00..09c34843 100644
--- a/ios/chrome/browser/first_run/ui_bundled/best_features/ui/best_features_view_controller.mm
+++ b/ios/chrome/browser/first_run/ui_bundled/best_features/ui/best_features_view_controller.mm
@@ -126,11 +126,11 @@
   switch (first_run::GetBestFeaturesScreenVariationType()) {
     case kGeneralScreenAfterDBPromo:
     case kGeneralScreenWithPasswordItemAfterDBPromo:
+    case kShoppingUsersWithFallbackAfterDBPromo:
     case kSignedInUsersOnlyAfterDBPromo:
       return l10n_util::GetNSString(
           IDS_IOS_BEST_FEATURES_START_BROWSING_BUTTON);
     case kGeneralScreenBeforeDBPromo:
-    case kShoppingUsersWithFallbackBeforeDBPromo:
       return l10n_util::GetNSString(IDS_IOS_BEST_FEATURES_CONTINUE_BUTTON);
     case kAddressBarPromoInsteadOfBestFeaturesScreen:
     case kDisabled:
diff --git a/ios/chrome/browser/first_run/ui_bundled/best_features/ui/feature_highlight_screenshot_view_controller.mm b/ios/chrome/browser/first_run/ui_bundled/best_features/ui/feature_highlight_screenshot_view_controller.mm
index 9196040..b412ead 100644
--- a/ios/chrome/browser/first_run/ui_bundled/best_features/ui/feature_highlight_screenshot_view_controller.mm
+++ b/ios/chrome/browser/first_run/ui_bundled/best_features/ui/feature_highlight_screenshot_view_controller.mm
@@ -232,10 +232,10 @@
   switch (first_run::GetBestFeaturesScreenVariationType()) {
     case kGeneralScreenAfterDBPromo:
     case kGeneralScreenWithPasswordItemAfterDBPromo:
+    case kShoppingUsersWithFallbackAfterDBPromo:
     case kSignedInUsersOnlyAfterDBPromo:
       return YES;
     case kGeneralScreenBeforeDBPromo:
-    case kShoppingUsersWithFallbackBeforeDBPromo:
       return NO;
     case kAddressBarPromoInsteadOfBestFeaturesScreen:
     case kDisabled:
diff --git a/ios/chrome/browser/first_run/ui_bundled/features.h b/ios/chrome/browser/first_run/ui_bundled/features.h
index 94555e0..8f49b277 100644
--- a/ios/chrome/browser/first_run/ui_bundled/features.h
+++ b/ios/chrome/browser/first_run/ui_bundled/features.h
@@ -40,8 +40,8 @@
   kGeneralScreenWithPasswordItemAfterDBPromo,
   // For "Shopping" classified users, show the Shopping-specific screen. For all
   // other users, show screen in kGeneralScreenWithPasswordItem arm. Appears
-  // before Default Browser promo.
-  kShoppingUsersWithFallbackBeforeDBPromo,
+  // after Default Browser promo.
+  kShoppingUsersWithFallbackAfterDBPromo,
   // For signed-in users, show the "signed-in" specific screen. For all other
   // users, don't show screen. Appears after Default Browser promo.
   kSignedInUsersOnlyAfterDBPromo,
diff --git a/ios/chrome/browser/first_run/ui_bundled/first_run_screen_provider.mm b/ios/chrome/browser/first_run/ui_bundled/first_run_screen_provider.mm
index 09ae37c..d9250c8 100644
--- a/ios/chrome/browser/first_run/ui_bundled/first_run_screen_provider.mm
+++ b/ios/chrome/browser/first_run/ui_bundled/first_run_screen_provider.mm
@@ -31,12 +31,12 @@
   switch (bestFeaturesType) {
     case kGeneralScreenAfterDBPromo:
     case kGeneralScreenWithPasswordItemAfterDBPromo:
+    case kShoppingUsersWithFallbackAfterDBPromo:
     case kSignedInUsersOnlyAfterDBPromo:
       [screens addObject:@(kDefaultBrowserPromo)];
       [screens addObject:@(kBestFeatures)];
       break;
     case kGeneralScreenBeforeDBPromo:
-    case kShoppingUsersWithFallbackBeforeDBPromo:
       [screens addObject:@(kBestFeatures)];
       [screens addObject:@(kDefaultBrowserPromo)];
       break;
diff --git a/ios/chrome/browser/flags/about_flags.mm b/ios/chrome/browser/flags/about_flags.mm
index bc08acd..388ce0b 100644
--- a/ios/chrome/browser/flags/about_flags.mm
+++ b/ios/chrome/browser/flags/about_flags.mm
@@ -1461,6 +1461,10 @@
     {"sign-in-button-no-avatar", flag_descriptions::kSignInButtonNoAvatarName,
      flag_descriptions::kSignInButtonNoAvatarDescription, flags_ui::kOsIos,
      FEATURE_VALUE_TYPE(kSignInButtonNoAvatar)},
+    {"ntp-background-customization",
+     flag_descriptions::kNTPBackgroundCustomizationName,
+     flag_descriptions::kNTPBackgroundCustomizationDescription,
+     flags_ui::kOsIos, FEATURE_VALUE_TYPE(kNTPBackgroundCustomization)},
     {"fullscreen-promos-manager-skip-internal-limits",
      flag_descriptions::kFullscreenPromosManagerSkipInternalLimitsName,
      flag_descriptions::kFullscreenPromosManagerSkipInternalLimitsDescription,
@@ -2597,6 +2601,9 @@
      flag_descriptions::kLensOverlayNavigationHistoryName,
      flag_descriptions::kLensOverlayNavigationHistoryDescription,
      flags_ui::kOsIos, FEATURE_VALUE_TYPE(kLensOverlayNavigationHistory)},
+    {"page-action-menu", flag_descriptions::kPageActionMenuName,
+     flag_descriptions::kPageActionMenuDescription, flags_ui::kOsIos,
+     FEATURE_VALUE_TYPE(kPageActionMenu)},
 };
 
 bool SkipConditionalFeatureEntry(const flags_ui::FeatureEntry& entry) {
diff --git a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
index fb0e820..8ca71b6 100644
--- a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
+++ b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
@@ -1037,6 +1037,11 @@
     "Enables the client that handles incoming push notifications on behalf of "
     "the optimization guide.";
 
+const char kPageActionMenuName[] = "Page Action Menu";
+const char kPageActionMenuDescription[] =
+    "When enabled, the entry point for the Page Action Menu becomes available "
+    "for actions relating to the web page.";
+
 const char kPageContentAnnotationsName[] = "Page content annotations";
 const char kPageContentAnnotationsDescription[] =
     "Enables page content to be annotated on-device.";
@@ -1259,6 +1264,11 @@
 const char kSignInButtonNoAvatarDescription[] =
     "When enabled, the sign-in button is shown without an avatar on the NTP.";
 
+const char kNTPBackgroundCustomizationName[] =
+    "Enable background customization menu on the NTP";
+const char kNTPBackgroundCustomizationDescription[] =
+    "When enabled, the background customization menu is available on the NTP.";
+
 const char kSpotlightNeverRetainIndexName[] = "Don't retain spotlight index";
 const char kSpotlightNeverRetainIndexDescription[] =
     "Tentative spotlight memory improvement by not storing a strong pointer to "
diff --git a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
index a63de73..961ac29 100644
--- a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
+++ b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
@@ -612,6 +612,9 @@
 extern const char kOneTapForMapsName[];
 extern const char kOneTapForMapsDescription[];
 
+extern const char kPageActionMenuName[];
+extern const char kPageActionMenuDescription[];
+
 extern const char kPageContentAnnotationsName[];
 extern const char kPageContentAnnotationsDescription[];
 
@@ -744,6 +747,9 @@
 extern const char kSignInButtonNoAvatarName[];
 extern const char kSignInButtonNoAvatarDescription[];
 
+extern const char kNTPBackgroundCustomizationName[];
+extern const char kNTPBackgroundCustomizationDescription[];
+
 extern const char kSpotlightNeverRetainIndexName[];
 extern const char kSpotlightNeverRetainIndexDescription[];
 
diff --git a/ios/chrome/browser/home_customization/ui/BUILD.gn b/ios/chrome/browser/home_customization/ui/BUILD.gn
index f2221d1..da4d748a 100644
--- a/ios/chrome/browser/home_customization/ui/BUILD.gn
+++ b/ios/chrome/browser/home_customization/ui/BUILD.gn
@@ -4,6 +4,8 @@
 
 source_set("ui") {
   sources = [
+    "home_customization_background_view_cell.h",
+    "home_customization_background_view_cell.mm",
     "home_customization_collection_configurator.h",
     "home_customization_collection_configurator.mm",
     "home_customization_discover_consumer.h",
@@ -27,6 +29,7 @@
   deps = [
     "//ios/chrome/app/strings:ios_strings",
     "//ios/chrome/browser/home_customization/utils",
+    "//ios/chrome/browser/shared/public/features",
     "//ios/chrome/browser/shared/ui/symbols",
     "//ios/chrome/browser/shared/ui/util",
     "//ios/chrome/common/ui/colors",
diff --git a/ios/chrome/browser/home_customization/ui/home_customization_background_view_cell.h b/ios/chrome/browser/home_customization/ui/home_customization_background_view_cell.h
new file mode 100644
index 0000000..ec8a177
--- /dev/null
+++ b/ios/chrome/browser/home_customization/ui/home_customization_background_view_cell.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 IOS_CHROME_BROWSER_HOME_CUSTOMIZATION_UI_HOME_CUSTOMIZATION_BACKGROUND_VIEW_CELL_H_
+#define IOS_CHROME_BROWSER_HOME_CUSTOMIZATION_UI_HOME_CUSTOMIZATION_BACKGROUND_VIEW_CELL_H_
+
+#import <UIKit/UIKit.h>
+
+@protocol HomeCustomizationMutator;
+
+// A view cell in the customization menu that presents background options.
+@interface HomeCustomizationBackgroundViewCell : UICollectionViewCell
+
+// Mutator for communicating with the HomeCustomizationMediator.
+@property(nonatomic, weak) id<HomeCustomizationMutator> mutator;
+
+@end
+
+#endif  // IOS_CHROME_BROWSER_HOME_CUSTOMIZATION_UI_HOME_CUSTOMIZATION_BACKGROUND_VIEW_CELL_H_
diff --git a/ios/chrome/browser/home_customization/ui/home_customization_background_view_cell.mm b/ios/chrome/browser/home_customization/ui/home_customization_background_view_cell.mm
new file mode 100644
index 0000000..fa7160a
--- /dev/null
+++ b/ios/chrome/browser/home_customization/ui/home_customization_background_view_cell.mm
@@ -0,0 +1,9 @@
+// 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/home_customization/ui/home_customization_background_view_cell.h"
+
+@implementation HomeCustomizationBackgroundViewCell
+
+@end
diff --git a/ios/chrome/browser/home_customization/ui/home_customization_main_view_controller.mm b/ios/chrome/browser/home_customization/ui/home_customization_main_view_controller.mm
index 615342c1..41b2d6a 100644
--- a/ios/chrome/browser/home_customization/ui/home_customization_main_view_controller.mm
+++ b/ios/chrome/browser/home_customization/ui/home_customization_main_view_controller.mm
@@ -6,11 +6,13 @@
 
 #import <map>
 
+#import "ios/chrome/browser/home_customization/ui/home_customization_background_view_cell.h"
 #import "ios/chrome/browser/home_customization/ui/home_customization_collection_configurator.h"
 #import "ios/chrome/browser/home_customization/ui/home_customization_mutator.h"
 #import "ios/chrome/browser/home_customization/ui/home_customization_toggle_cell.h"
 #import "ios/chrome/browser/home_customization/ui/home_customization_view_controller_protocol.h"
 #import "ios/chrome/browser/home_customization/utils/home_customization_constants.h"
+#import "ios/chrome/browser/shared/public/features/features.h"
 #import "ios/chrome/grit/ios_strings.h"
 #import "ui/base/l10n/l10n_util.h"
 
@@ -30,6 +32,9 @@
 
   // Registration for the HomeCustomizationToggleCells.
   UICollectionViewCellRegistration* _toggleCellRegistration;
+
+  // Registration for the background customization cell.
+  UICollectionViewCellRegistration* _backgroundCustomizationRegistration;
 }
 
 // Synthesized from HomeCustomizationViewControllerProtocol.
@@ -74,6 +79,16 @@
              [cell configureCellWithType:toggleType enabled:enabled];
              cell.mutator = weakSelf.mutator;
            }];
+
+  if (IsNTPBackgroundCustomizationEnabled()) {
+    _backgroundCustomizationRegistration = [UICollectionViewCellRegistration
+        registrationWithCellClass:[HomeCustomizationBackgroundViewCell class]
+             configurationHandler:^(HomeCustomizationBackgroundViewCell* cell,
+                                    NSIndexPath* indexPath,
+                                    NSNumber* itemIdentifier) {
+               cell.mutator = weakSelf.mutator;
+             }];
+  }
 }
 
 // Creates a data snapshot representing the content of the collection view.
@@ -82,6 +97,17 @@
   NSDiffableDataSourceSnapshot<CustomizationSection*, NSNumber*>* snapshot =
       [[NSDiffableDataSourceSnapshot alloc] init];
 
+  if (IsNTPBackgroundCustomizationEnabled()) {
+    // Create background customization section and add items to it.
+    [snapshot
+        appendSectionsWithIdentifiers:@[ kCustomizationSectionBackground ]];
+
+    NSInteger smallestExistingIdentifier =
+        static_cast<NSInteger>(self.toggleMap.begin()->first);
+    [snapshot appendItemsWithIdentifiers:@[ @(smallestExistingIdentifier - 1) ]
+               intoSectionWithIdentifier:kCustomizationSectionBackground];
+  }
+
   // Create toggles section and add items to it.
   [snapshot
       appendSectionsWithIdentifiers:@[ kCustomizationSectionMainToggles ]];
@@ -101,9 +127,15 @@
 - (NSCollectionLayoutSection*)
       sectionForIndex:(NSInteger)sectionIndex
     layoutEnvironment:(id<NSCollectionLayoutEnvironment>)layoutEnvironment {
-  if (sectionIndex ==
+  NSInteger mainTogglesIdentifier = [self.diffableDataSource.snapshot
+      indexOfSectionIdentifier:kCustomizationSectionMainToggles];
+
+  NSInteger backgroundCustomizationIdentifier =
       [self.diffableDataSource.snapshot
-          indexOfSectionIdentifier:kCustomizationSectionMainToggles]) {
+          indexOfSectionIdentifier:kCustomizationSectionBackground];
+
+  if (sectionIndex == mainTogglesIdentifier ||
+      sectionIndex == backgroundCustomizationIdentifier) {
     return [_collectionConfigurator
         verticalListSectionForLayoutEnvironment:layoutEnvironment];
   }
@@ -112,13 +144,20 @@
 
 - (UICollectionViewCell*)configuredCellForIndexPath:(NSIndexPath*)indexPath
                                      itemIdentifier:(NSNumber*)itemIdentifier {
-  if (kCustomizationSectionMainToggles ==
-      [_diffableDataSource.snapshot
-          sectionIdentifierForSectionContainingItemIdentifier:itemIdentifier]) {
+  CustomizationSection* section = [_diffableDataSource.snapshot
+      sectionIdentifierForSectionContainingItemIdentifier:itemIdentifier];
+
+  if (kCustomizationSectionMainToggles == section) {
     return [_collectionView
         dequeueConfiguredReusableCellWithRegistration:_toggleCellRegistration
                                          forIndexPath:indexPath
                                                  item:itemIdentifier];
+  } else if (kCustomizationSectionBackground == section) {
+    return [_collectionView
+        dequeueConfiguredReusableCellWithRegistration:
+            _backgroundCustomizationRegistration
+                                         forIndexPath:indexPath
+                                                 item:itemIdentifier];
   }
   return nil;
 }
diff --git a/ios/chrome/browser/home_customization/utils/home_customization_constants.h b/ios/chrome/browser/home_customization/utils/home_customization_constants.h
index 5f2a6e4..f523cd8 100644
--- a/ios/chrome/browser/home_customization/utils/home_customization_constants.h
+++ b/ios/chrome/browser/home_customization/utils/home_customization_constants.h
@@ -10,6 +10,9 @@
 // Represents the section identifiers of the customization menu as an NSString.
 typedef NSString CustomizationSection;
 
+// The section identifier for the main menu's background customizations.
+extern CustomizationSection* const kCustomizationSectionBackground;
+
 // The section identifier for the main menu's visibility toggles.
 extern CustomizationSection* const kCustomizationSectionMainToggles;
 
diff --git a/ios/chrome/browser/home_customization/utils/home_customization_constants.mm b/ios/chrome/browser/home_customization/utils/home_customization_constants.mm
index d2fda0f..7a8bf45 100644
--- a/ios/chrome/browser/home_customization/utils/home_customization_constants.mm
+++ b/ios/chrome/browser/home_customization/utils/home_customization_constants.mm
@@ -4,6 +4,9 @@
 
 #import "ios/chrome/browser/home_customization/utils/home_customization_constants.h"
 
+CustomizationSection* const kCustomizationSectionBackground =
+    @"kCustomizationSectionBackground";
+
 CustomizationSection* const kCustomizationSectionMainToggles =
     @"kCustomizationSectionMainToggles";
 
diff --git a/ios/chrome/browser/intelligence/features/features.h b/ios/chrome/browser/intelligence/features/features.h
index f80f0ca4..0615283 100644
--- a/ios/chrome/browser/intelligence/features/features.h
+++ b/ios/chrome/browser/intelligence/features/features.h
@@ -13,4 +13,10 @@
 // Returns true if enhanced calendar is enabled.
 bool IsEnhancedCalendarEnabled();
 
+// Feature flag controlling the page action menu.
+BASE_DECLARE_FEATURE(kPageActionMenu);
+
+// Returns true if the page action menu is enabled.
+bool IsPageActionMenuEnabled();
+
 #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 890927d..ac8398c 100644
--- a/ios/chrome/browser/intelligence/features/features.mm
+++ b/ios/chrome/browser/intelligence/features/features.mm
@@ -11,3 +11,11 @@
 bool IsEnhancedCalendarEnabled() {
   return base::FeatureList::IsEnabled(kEnhancedCalendar);
 }
+
+BASE_FEATURE(kPageActionMenu,
+             "PageActionMenu",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
+bool IsPageActionMenuEnabled() {
+  return base::FeatureList::IsEnabled(kPageActionMenu);
+}
diff --git a/ios/chrome/browser/policy/model/configuration_policy_handler_list_factory.mm b/ios/chrome/browser/policy/model/configuration_policy_handler_list_factory.mm
index 21b8776..9207758 100644
--- a/ios/chrome/browser/policy/model/configuration_policy_handler_list_factory.mm
+++ b/ios/chrome/browser/policy/model/configuration_policy_handler_list_factory.mm
@@ -146,9 +146,6 @@
   { policy::key::kContextMenuPhotoSharingSettings,
     prefs::kIosSaveToPhotosContextMenuPolicySettings,
     base::Value::Type::INTEGER },
-  { policy::key::kParcelTrackingEnabled,
-    prefs::kIosParcelTrackingPolicyEnabled,
-    base::Value::Type::BOOLEAN },
   { policy::key::kInsecureFormsWarningsEnabled,
     prefs::kInsecureFormWarningsEnabled,
     base::Value::Type::BOOLEAN },
diff --git a/ios/chrome/browser/policy/model/policy_watcher_browser_agent_unittest.mm b/ios/chrome/browser/policy/model/policy_watcher_browser_agent_unittest.mm
index a3eacdfa..89ff2730 100644
--- a/ios/chrome/browser/policy/model/policy_watcher_browser_agent_unittest.mm
+++ b/ios/chrome/browser/policy/model/policy_watcher_browser_agent_unittest.mm
@@ -380,7 +380,7 @@
 
   EXPECT_OCMOCK_VERIFY(mockHandler);
 
-  // TODO(crbug.com/40066949): Reset the expectation for the SignInUIDismissed
+  // TODO(crbug.com/364574533): Reset the expectation for the SignInUIDismissed
   // call.
 }
 
diff --git a/ios/chrome/browser/recent_tabs/ui_bundled/sessions_sync_user_state.h b/ios/chrome/browser/recent_tabs/ui_bundled/sessions_sync_user_state.h
index e9590139..ea44cc4 100644
--- a/ios/chrome/browser/recent_tabs/ui_bundled/sessions_sync_user_state.h
+++ b/ios/chrome/browser/recent_tabs/ui_bundled/sessions_sync_user_state.h
@@ -5,14 +5,19 @@
 #ifndef IOS_CHROME_BROWSER_RECENT_TABS_UI_BUNDLED_SESSIONS_SYNC_USER_STATE_H_
 #define IOS_CHROME_BROWSER_RECENT_TABS_UI_BUNDLED_SESSIONS_SYNC_USER_STATE_H_
 
-// States listing the user's signed-in and sync status.
-// TODO(crbug.com/40276546): Update this enum to reflect the more modern product
-// state machine.
+// States listing the user's signed-in and tab sync (syncer::SESSIONS) status.
 enum class SessionsSyncUserState {
+  // The user signed-out and thus tab sync is off.
   USER_SIGNED_OUT,
+  // The user is signed-in but tab sync is not working, either because the
+  // user is not opted into it, or because there's an error that needs to be
+  // resolved (e.g. passphrase error).
   USER_SIGNED_IN_SYNC_OFF,
+  // Tab sync is on but tabs from other devices are still being downloaded.
   USER_SIGNED_IN_SYNC_IN_PROGRESS,
+  // Tab sync is on but there are no tabs from other devices.
   USER_SIGNED_IN_SYNC_ON_NO_SESSIONS,
+  // Tab sync is on and there are tabs from other devices.
   USER_SIGNED_IN_SYNC_ON_WITH_SESSIONS,
 };
 
diff --git a/ios/chrome/browser/saved_tab_groups/model/BUILD.gn b/ios/chrome/browser/saved_tab_groups/model/BUILD.gn
index 3e07f14..0018e4d 100644
--- a/ios/chrome/browser/saved_tab_groups/model/BUILD.gn
+++ b/ios/chrome/browser/saved_tab_groups/model/BUILD.gn
@@ -35,6 +35,7 @@
     "//ios/chrome/browser/sessions/model:restoration_observer",
     "//ios/chrome/browser/sessions/model:session_restoration_service",
     "//ios/chrome/browser/sessions/model:session_restoration_service_factory",
+    "//ios/chrome/browser/share_kit/model",
     "//ios/chrome/browser/shared/coordinator/scene:scene_state_header",
     "//ios/chrome/browser/shared/model/browser",
     "//ios/chrome/browser/shared/model/profile",
@@ -87,8 +88,11 @@
     "//components/saved_tab_groups/test_support",
     "//components/tab_groups",
     "//ios/chrome/app/application_delegate:app_state",
+    "//ios/chrome/browser/data_sharing/model",
     "//ios/chrome/browser/sessions/model:session_restoration_service_factory",
     "//ios/chrome/browser/sessions/model:test_support",
+    "//ios/chrome/browser/share_kit/model:factory",
+    "//ios/chrome/browser/share_kit/model:test_support",
     "//ios/chrome/browser/shared/coordinator/scene:scene_state_header",
     "//ios/chrome/browser/shared/coordinator/scene/test",
     "//ios/chrome/browser/shared/model/browser",
diff --git a/ios/chrome/browser/saved_tab_groups/model/DEPS b/ios/chrome/browser/saved_tab_groups/model/DEPS
index a70b1f2..90b0c44 100644
--- a/ios/chrome/browser/saved_tab_groups/model/DEPS
+++ b/ios/chrome/browser/saved_tab_groups/model/DEPS
@@ -9,6 +9,7 @@
   "+ios/chrome/browser/metrics/model/ios_chrome_metrics_service_accessor.h",
   "+ios/chrome/browser/optimization_guide/model",
   "+ios/chrome/browser/sessions/model",
+  "+ios/chrome/browser/share_kit/model",
   "+ios/chrome/browser/signin/model/identity_manager_factory.h",
   "+ios/chrome/browser/sync/model/data_type_store_service_factory.h",
   "+ios/chrome/browser/sync/model/device_info_sync_service_factory.h",
diff --git a/ios/chrome/browser/saved_tab_groups/model/ios_tab_group_sync_util.h b/ios/chrome/browser/saved_tab_groups/model/ios_tab_group_sync_util.h
index bcfe173..19093a9 100644
--- a/ios/chrome/browser/saved_tab_groups/model/ios_tab_group_sync_util.h
+++ b/ios/chrome/browser/saved_tab_groups/model/ios_tab_group_sync_util.h
@@ -26,6 +26,7 @@
 
 class Browser;
 class BrowserList;
+class ShareKitService;
 
 namespace tab_groups {
 namespace utils {
@@ -87,7 +88,8 @@
 
 // Whether the given `tab_group` is shared or not.
 bool IsTabGroupShared(const TabGroup* tab_group,
-                      TabGroupSyncService* sync_service);
+                      TabGroupSyncService* sync_service,
+                      ShareKitService* share_kit_service);
 
 // Returns the `MemberRole` for the given `tab_group`. If
 // `tab_group_sync_service` or `collaboration_service` is nil, returns
diff --git a/ios/chrome/browser/saved_tab_groups/model/ios_tab_group_sync_util.mm b/ios/chrome/browser/saved_tab_groups/model/ios_tab_group_sync_util.mm
index 60f8625..2b6aca6 100644
--- a/ios/chrome/browser/saved_tab_groups/model/ios_tab_group_sync_util.mm
+++ b/ios/chrome/browser/saved_tab_groups/model/ios_tab_group_sync_util.mm
@@ -14,6 +14,7 @@
 #import "components/saved_tab_groups/public/types.h"
 #import "components/saved_tab_groups/public/utils.h"
 #import "ios/chrome/browser/saved_tab_groups/model/tab_group_sync_service_factory.h"
+#import "ios/chrome/browser/share_kit/model/share_kit_service.h"
 #import "ios/chrome/browser/shared/model/browser/browser.h"
 #import "ios/chrome/browser/shared/model/browser/browser_list.h"
 #import "ios/chrome/browser/shared/model/browser/browser_list_factory.h"
@@ -292,15 +293,17 @@
 }
 
 bool IsTabGroupShared(const TabGroup* tab_group,
-                      TabGroupSyncService* sync_service) {
-  BOOL shared = false;
-  if (sync_service && tab_group) {
-    std::optional<tab_groups::SavedTabGroup> saved_group =
-        sync_service->GetGroup(tab_group->tab_group_id());
-    shared =
-        saved_group.has_value() && saved_group->collaboration_id().has_value();
+                      TabGroupSyncService* sync_service,
+                      ShareKitService* share_kit_service) {
+  BOOL is_shared_tab_group_supported =
+      share_kit_service && share_kit_service->IsSupported();
+  if (!is_shared_tab_group_supported || !sync_service || !tab_group) {
+    return false;
   }
-  return shared;
+
+  std::optional<tab_groups::SavedTabGroup> saved_group =
+      sync_service->GetGroup(tab_group->tab_group_id());
+  return saved_group.has_value() && saved_group->collaboration_id().has_value();
 }
 
 data_sharing::MemberRole GetUserRoleForGroup(
diff --git a/ios/chrome/browser/saved_tab_groups/model/ios_tab_group_sync_util_unittest.mm b/ios/chrome/browser/saved_tab_groups/model/ios_tab_group_sync_util_unittest.mm
index 4b3eb29..427451f 100644
--- a/ios/chrome/browser/saved_tab_groups/model/ios_tab_group_sync_util_unittest.mm
+++ b/ios/chrome/browser/saved_tab_groups/model/ios_tab_group_sync_util_unittest.mm
@@ -6,10 +6,14 @@
 
 #import "base/test/scoped_feature_list.h"
 #import "components/collaboration/test_support/mock_collaboration_service.h"
+#import "components/data_sharing/public/features.h"
 #import "components/saved_tab_groups/test_support/mock_tab_group_sync_service.h"
 #import "components/tab_groups/tab_group_id.h"
+#import "ios/chrome/browser/data_sharing/model/data_sharing_service_factory.h"
 #import "ios/chrome/browser/saved_tab_groups/model/tab_group_local_update_observer.h"
 #import "ios/chrome/browser/saved_tab_groups/model/tab_group_sync_service_factory.h"
+#import "ios/chrome/browser/share_kit/model/share_kit_service_factory.h"
+#import "ios/chrome/browser/share_kit/model/test_share_kit_service.h"
 #import "ios/chrome/browser/shared/model/browser/browser_list.h"
 #import "ios/chrome/browser/shared/model/browser/browser_list_factory.h"
 #import "ios/chrome/browser/shared/model/browser/test/test_browser.h"
@@ -43,6 +47,17 @@
   return std::make_unique<::testing::NiceMock<MockTabGroupSyncService>>();
 }
 
+// Creates a test ShareKitService.
+std::unique_ptr<KeyedService> BuildTestShareKitService(
+    web::BrowserState* context) {
+  ProfileIOS* profile = static_cast<ProfileIOS*>(context);
+  data_sharing::DataSharingService* data_sharing_service =
+      data_sharing::DataSharingServiceFactory::GetForProfile(profile);
+
+  return std::make_unique<TestShareKitService>(data_sharing_service, nullptr,
+                                               nullptr);
+}
+
 // Returns the tab ID for the web state at `index` in `browser`.
 web::WebStateID GetTabIDForWebStateAt(int index, Browser* browser) {
   web::WebState* web_state = browser->GetWebStateList()->GetWebStateAt(index);
@@ -58,10 +73,14 @@
     test_profile_builder.AddTestingFactory(
         TabGroupSyncServiceFactory::GetInstance(),
         base::BindRepeating(&CreateMockSyncService));
+    test_profile_builder.AddTestingFactory(
+        ShareKitServiceFactory::GetInstance(),
+        base::BindRepeating(&BuildTestShareKitService));
     profile_ = std::move(test_profile_builder).Build();
 
     mock_service_ = static_cast<MockTabGroupSyncService*>(
         TabGroupSyncServiceFactory::GetForProfile(profile_.get()));
+    share_kit_service_ = ShareKitServiceFactory::GetForProfile(profile_.get());
 
     browser_ = std::make_unique<TestBrowser>(profile_.get());
     other_browser_ = std::make_unique<TestBrowser>(profile_.get());
@@ -78,7 +97,10 @@
   }
 
   void SetUp() override {
-    feature_list_.InitWithFeatures({kTabGroupsIPad, kTabGroupSync}, {});
+    feature_list_.InitWithFeatures(
+        {kTabGroupsIPad, kTabGroupSync,
+         data_sharing::features::kDataSharingFeature},
+        {});
     AppendNewWebState(browser_.get());
     AppendNewWebState(browser_.get());
     AppendNewWebState(browser_.get());
@@ -103,6 +125,7 @@
   raw_ptr<BrowserList> browser_list_;
   raw_ptr<MockTabGroupSyncService> mock_service_;
   std::unique_ptr<TabGroupLocalUpdateObserver> local_observer_;
+  raw_ptr<ShareKitService> share_kit_service_;
 };
 
 // Tests that a tab group with one tab is moved from one regular browser to
@@ -558,8 +581,12 @@
   EXPECT_CALL(*mock_service_, GetGroup(tab_group_id))
       .WillOnce(testing::Return(saved_group));
 
-  EXPECT_TRUE(IsTabGroupShared(local_group, mock_service_));
-  EXPECT_FALSE(IsTabGroupShared(local_group, nullptr));
+  EXPECT_NE(nullptr, share_kit_service_.get());
+  EXPECT_TRUE(
+      IsTabGroupShared(local_group, mock_service_, share_kit_service_.get()));
+  EXPECT_FALSE(
+      IsTabGroupShared(local_group, nullptr, share_kit_service_.get()));
+  EXPECT_FALSE(IsTabGroupShared(local_group, mock_service_, nullptr));
 }
 
 // Tests the `IsTabGroupShared` method with a non shared group.
@@ -579,8 +606,11 @@
   EXPECT_CALL(*mock_service_, GetGroup(tab_group_id))
       .WillOnce(testing::Return(saved_group));
 
-  EXPECT_FALSE(IsTabGroupShared(local_group, mock_service_));
-  EXPECT_FALSE(IsTabGroupShared(local_group, nullptr));
+  EXPECT_NE(nullptr, share_kit_service_.get());
+  EXPECT_FALSE(
+      IsTabGroupShared(local_group, mock_service_, share_kit_service_.get()));
+  EXPECT_FALSE(
+      IsTabGroupShared(local_group, nullptr, share_kit_service_.get()));
 }
 
 // Tests the `GetTabGroupCollabID` method with a shared group.
diff --git a/ios/chrome/browser/settings/ui_bundled/DEPS b/ios/chrome/browser/settings/ui_bundled/DEPS
index 7327cb3..914f1e5 100644
--- a/ios/chrome/browser/settings/ui_bundled/DEPS
+++ b/ios/chrome/browser/settings/ui_bundled/DEPS
@@ -1,4 +1,5 @@
 include_rules = [
+  "+components/data_sharing/public/features.h",
   "+ios/chrome/browser/account_picker/ui_bundled/account_picker_selection/account_picker_selection_screen_identity_item_configurator.h",
   "+ios/chrome/browser/affiliations/model/ios_chrome_affiliation_service_factory.h",
   "+ios/chrome/browser/autofill/model/personal_data_manager_factory.h",
diff --git a/ios/chrome/browser/settings/ui_bundled/clear_browsing_data/BUILD.gn b/ios/chrome/browser/settings/ui_bundled/clear_browsing_data/BUILD.gn
index 6893978..b8e9829 100644
--- a/ios/chrome/browser/settings/ui_bundled/clear_browsing_data/BUILD.gn
+++ b/ios/chrome/browser/settings/ui_bundled/clear_browsing_data/BUILD.gn
@@ -199,6 +199,7 @@
     "//base/test:test_support",
     "//components/browsing_data/core",
     "//components/browsing_data/core:cookie_or_cache_deletion_choice",
+    "//components/data_sharing/public:features",
     "//components/signin/internal/identity_manager:capabilities",
     "//components/signin/public/base",
     "//components/strings",
diff --git a/ios/chrome/browser/settings/ui_bundled/clear_browsing_data/quick_delete_egtest.mm b/ios/chrome/browser/settings/ui_bundled/clear_browsing_data/quick_delete_egtest.mm
index 9c830b6..98c25e7 100644
--- a/ios/chrome/browser/settings/ui_bundled/clear_browsing_data/quick_delete_egtest.mm
+++ b/ios/chrome/browser/settings/ui_bundled/clear_browsing_data/quick_delete_egtest.mm
@@ -10,6 +10,7 @@
 #import "components/browsing_data/core/browsing_data_utils.h"
 #import "components/browsing_data/core/cookie_or_cache_deletion_choice.h"
 #import "components/browsing_data/core/pref_names.h"
+#import "components/data_sharing/public/features.h"
 #import "components/signin/internal/identity_manager/account_capabilities_constants.h"
 #import "components/strings/grit/components_strings.h"
 #import "components/sync/base/command_line_switches.h"
@@ -283,6 +284,8 @@
   config.additional_args.push_back(std::string("--") +
                                    syncer::kSyncShortNudgeDelayForTest);
   config.features_enabled.push_back(kTabGroupsIPad);
+  config.features_enabled.push_back(
+      data_sharing::features::kDataSharingFeature);
   return config;
 }
 
diff --git a/ios/chrome/browser/shared/public/features/features.h b/ios/chrome/browser/shared/public/features/features.h
index 8e12231..314d378 100644
--- a/ios/chrome/browser/shared/public/features/features.h
+++ b/ios/chrome/browser/shared/public/features/features.h
@@ -1097,4 +1097,10 @@
 // Returns whether the sign-in button without avatar is enabled.
 bool IsSignInButtonNoAvatarEnabled();
 
+// Feature flag to enable background customization on the NTP.
+BASE_DECLARE_FEATURE(kNTPBackgroundCustomization);
+
+// Checks if background customization is enabled on the NTP.
+bool IsNTPBackgroundCustomizationEnabled();
+
 #endif  // IOS_CHROME_BROWSER_SHARED_PUBLIC_FEATURES_FEATURES_H_
diff --git a/ios/chrome/browser/shared/public/features/features.mm b/ios/chrome/browser/shared/public/features/features.mm
index 7aa56102..3b37c48a 100644
--- a/ios/chrome/browser/shared/public/features/features.mm
+++ b/ios/chrome/browser/shared/public/features/features.mm
@@ -1342,3 +1342,11 @@
 bool IsSignInButtonNoAvatarEnabled() {
   return base::FeatureList::IsEnabled(kSignInButtonNoAvatar);
 }
+
+BASE_FEATURE(kNTPBackgroundCustomization,
+             "NTPBackgroundCustomization",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
+bool IsNTPBackgroundCustomizationEnabled() {
+  return base::FeatureList::IsEnabled(kNTPBackgroundCustomization);
+}
diff --git a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_context_menu/tab_context_menu_helper.mm b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_context_menu/tab_context_menu_helper.mm
index 810c262..72976f4a 100644
--- a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_context_menu/tab_context_menu_helper.mm
+++ b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_context_menu/tab_context_menu_helper.mm
@@ -337,7 +337,8 @@
   BOOL isSharedTabGroupSupported =
       shareKitService && shareKitService->IsSupported();
 
-  if (tab_groups::utils::IsTabGroupShared(group, tabGroupSyncService)) {
+  if (tab_groups::utils::IsTabGroupShared(group, tabGroupSyncService,
+                                          shareKitService)) {
     collaboration::CollaborationService* collaborationService =
         collaboration::CollaborationServiceFactory::GetForProfile(_profile);
     data_sharing::MemberRole userRole = tab_groups::utils::GetUserRoleForGroup(
diff --git a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/BUILD.gn b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/BUILD.gn
index 7f1e166..b3d52cd 100644
--- a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/BUILD.gn
+++ b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/BUILD.gn
@@ -287,11 +287,13 @@
     "//components/tab_groups",
     "//ios/chrome/browser/collaboration/model",
     "//ios/chrome/browser/collaboration/model/messaging",
+    "//ios/chrome/browser/data_sharing/model",
     "//ios/chrome/browser/drag_and_drop/model",
     "//ios/chrome/browser/main/model",
     "//ios/chrome/browser/policy/model:policy_util",
     "//ios/chrome/browser/saved_tab_groups/model",
     "//ios/chrome/browser/share_kit/model:constants",
+    "//ios/chrome/browser/share_kit/model:factory",
     "//ios/chrome/browser/share_kit/model:test_support",
     "//ios/chrome/browser/shared/model/browser",
     "//ios/chrome/browser/shared/model/browser/test:test_support",
diff --git a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/tab_group_coordinator.mm b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/tab_group_coordinator.mm
index fbd331e..9840cc87 100644
--- a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/tab_group_coordinator.mm
+++ b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/tab_group_coordinator.mm
@@ -503,8 +503,11 @@
 - (void)startUserEducationIfNeeded {
   tab_groups::TabGroupSyncService* syncService =
       tab_groups::TabGroupSyncServiceFactory::GetForProfile(self.profile);
+  ShareKitService* shareKitService =
+      ShareKitServiceFactory::GetForProfile(self.profile);
 
-  if (!tab_groups::utils::IsTabGroupShared(_tabGroup, syncService)) {
+  if (!tab_groups::utils::IsTabGroupShared(_tabGroup, syncService,
+                                           shareKitService)) {
     return;
   }
   NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
diff --git a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/tab_group_coordinator_unittest.mm b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/tab_group_coordinator_unittest.mm
index d02f621..cd0e03cd 100644
--- a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/tab_group_coordinator_unittest.mm
+++ b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/tab_group_coordinator_unittest.mm
@@ -9,7 +9,10 @@
 #import "components/data_sharing/public/features.h"
 #import "components/saved_tab_groups/test_support/fake_tab_group_sync_service.h"
 #import "components/tab_groups/tab_group_id.h"
+#import "ios/chrome/browser/data_sharing/model/data_sharing_service_factory.h"
 #import "ios/chrome/browser/saved_tab_groups/model/tab_group_sync_service_factory.h"
+#import "ios/chrome/browser/share_kit/model/share_kit_service_factory.h"
+#import "ios/chrome/browser/share_kit/model/test_share_kit_service.h"
 #import "ios/chrome/browser/shared/model/browser/test/test_browser.h"
 #import "ios/chrome/browser/shared/model/profile/test/test_profile_ios.h"
 #import "ios/chrome/browser/shared/model/web_state_list/tab_group.h"
@@ -51,10 +54,21 @@
   return std::make_unique<tab_groups::FakeTabGroupSyncService>();
 }
 
+// Creates a test ShareKitService.
+std::unique_ptr<KeyedService> BuildTestShareKitService(
+    web::BrowserState* context) {
+  ProfileIOS* profile = static_cast<ProfileIOS*>(context);
+  data_sharing::DataSharingService* data_sharing_service =
+      data_sharing::DataSharingServiceFactory::GetForProfile(profile);
+
+  return std::make_unique<TestShareKitService>(data_sharing_service, nullptr,
+                                               nullptr);
+}
+
 class TabGroupCoordinatorTest : public PlatformTest {
  protected:
   TabGroupCoordinatorTest() {
-    feature_list_.InitWithFeatures({kTabGroupsIPad}, {});
+    feature_list_.InitWithFeatures({kTabGroupsIPad, kTabGroupSync}, {});
   }
 
   void SetUp() override {
@@ -69,6 +83,8 @@
     builder.AddTestingFactory(
         tab_groups::TabGroupSyncServiceFactory::GetInstance(),
         base::BindRepeating(&CreateFakeTabGroupSyncService));
+    builder.AddTestingFactory(ShareKitServiceFactory::GetInstance(),
+                              base::BindRepeating(&BuildTestShareKitService));
     profile_ = std::move(builder).Build();
 
     tab_group_sync_service_ = static_cast<tab_groups::FakeTabGroupSyncService*>(
diff --git a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/tab_group_mediator.mm b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/tab_group_mediator.mm
index 922f41c..c1cfa8be 100644
--- a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/tab_group_mediator.mm
+++ b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/tab_group_mediator.mm
@@ -850,15 +850,8 @@
 // Returns YES if the group is shared.
 - (BOOL)isShared {
   CHECK(_tabGroup);
-  BOOL isSharedTabGroupSupported =
-      _shareKitService && _shareKitService->IsSupported();
-
-  if (!isSharedTabGroupSupported || !_tabGroupSyncService) {
-    return NO;
-  }
-
-  return tab_groups::utils::IsTabGroupShared(_tabGroup.get(),
-                                             _tabGroupSyncService);
+  return tab_groups::utils::IsTabGroupShared(
+      _tabGroup.get(), _tabGroupSyncService, _shareKitService);
 }
 
 // Updates the consumer after a data sharing service update for the current tab
@@ -891,8 +884,7 @@
 // Updates the sharing state for the current `_tabGroup`.
 - (void)updateTabGroupSharingState {
   CHECK(_tabGroup);
-  BOOL shared = tab_groups::utils::IsTabGroupShared(_tabGroup.get(),
-                                                    _tabGroupSyncService);
+  BOOL shared = [self isShared];
   if (!shared) {
     [_groupConsumer setSharingState:SharingState::kNotShared];
     return;
diff --git a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/tab_group_sync_egtest.mm b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/tab_group_sync_egtest.mm
index f2591eb..efa4ad09 100644
--- a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/tab_group_sync_egtest.mm
+++ b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/tab_group_sync_egtest.mm
@@ -4,6 +4,7 @@
 
 #import <Foundation/Foundation.h>
 
+#import "components/data_sharing/public/features.h"
 #import "ios/chrome/browser/authentication/ui_bundled/signin_earl_grey_ui_test_util.h"
 #import "ios/chrome/browser/shared/public/features/features.h"
 #import "ios/chrome/browser/signin/model/fake_system_identity.h"
@@ -132,6 +133,8 @@
   // Add the flag to use FakeTabGroupSyncService.
   config.additional_args.push_back(
       "--" + std::string(test_switches::kEnableFakeTabGroupSyncService));
+  config.features_enabled.push_back(
+      data_sharing::features::kDataSharingFeature);
   return config;
 }
 
diff --git a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/tab_group_sync_signin_egtest.mm b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/tab_group_sync_signin_egtest.mm
index 2b724a82d..b609abc 100644
--- a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/tab_group_sync_signin_egtest.mm
+++ b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/tab_group_sync_signin_egtest.mm
@@ -7,6 +7,7 @@
 #import "base/test/ios/wait_util.h"
 #import "base/threading/platform_thread.h"
 #import "base/time/time.h"
+#import "components/data_sharing/public/features.h"
 #import "components/sync/base/command_line_switches.h"
 #import "components/sync/base/data_type.h"
 #import "ios/chrome/browser/authentication/ui_bundled/signin_earl_grey.h"
@@ -92,6 +93,8 @@
   config.features_enabled.push_back(kTabGroupSync);
   config.additional_args.push_back(std::string("--") +
                                    syncer::kSyncShortNudgeDelayForTest);
+  config.features_enabled.push_back(
+      data_sharing::features::kDataSharingFeature);
   return config;
 }
 
diff --git a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/tab_groups_egtest.mm b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/tab_groups_egtest.mm
index 0ef1d29..a77a71f 100644
--- a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/tab_groups_egtest.mm
+++ b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/tab_groups_egtest.mm
@@ -4,6 +4,7 @@
 
 #import "base/strings/sys_string_conversions.h"
 #import "base/test/ios/wait_util.h"
+#import "components/data_sharing/public/features.h"
 #import "ios/chrome/browser/authentication/ui_bundled/signin_earl_grey.h"
 #import "ios/chrome/browser/incognito_reauth/ui_bundled/incognito_reauth_util.h"
 #import "ios/chrome/browser/shared/model/prefs/pref_names.h"
@@ -272,6 +273,8 @@
   } else {
     config.features_enabled.push_back(kTabGroupSync);
   }
+  config.features_enabled.push_back(
+      data_sharing::features::kDataSharingFeature);
   return config;
 }
 
diff --git a/ios/chrome/browser/tab_switcher/ui_bundled/tab_strip/coordinator/BUILD.gn b/ios/chrome/browser/tab_switcher/ui_bundled/tab_strip/coordinator/BUILD.gn
index b7e346b..dd49539d 100644
--- a/ios/chrome/browser/tab_switcher/ui_bundled/tab_strip/coordinator/BUILD.gn
+++ b/ios/chrome/browser/tab_switcher/ui_bundled/tab_strip/coordinator/BUILD.gn
@@ -119,6 +119,8 @@
     "//ios/chrome/browser/favicon/model",
     "//ios/chrome/browser/history/model",
     "//ios/chrome/browser/saved_tab_groups/model",
+    "//ios/chrome/browser/share_kit/model",
+    "//ios/chrome/browser/share_kit/model:factory",
     "//ios/chrome/browser/shared/model/browser",
     "//ios/chrome/browser/shared/model/browser/test:test_support",
     "//ios/chrome/browser/shared/model/profile/test",
diff --git a/ios/chrome/browser/tab_switcher/ui_bundled/tab_strip/coordinator/tab_strip_coordinator.mm b/ios/chrome/browser/tab_switcher/ui_bundled/tab_strip/coordinator/tab_strip_coordinator.mm
index 8ebd7f1..96a83280 100644
--- a/ios/chrome/browser/tab_switcher/ui_bundled/tab_strip/coordinator/tab_strip_coordinator.mm
+++ b/ios/chrome/browser/tab_switcher/ui_bundled/tab_strip/coordinator/tab_strip_coordinator.mm
@@ -112,13 +112,16 @@
           profile);
   collaboration::CollaborationService* collaborationService =
       collaboration::CollaborationServiceFactory::GetForProfile(profile);
+  ShareKitService* shareKitService =
+      ShareKitServiceFactory::GetForProfile(profile);
 
   self.mediator =
       [[TabStripMediator alloc] initWithConsumer:self.tabStripViewController
                              tabGroupSyncService:tabGroupSyncService
                                      browserList:browserList
                                 messagingService:messagingService
-                            collaborationService:collaborationService];
+                            collaborationService:collaborationService
+                                 shareKitService:shareKitService];
   self.mediator.webStateList = self.browser->GetWebStateList();
   self.mediator.profile = profile;
   self.mediator.browser = self.browser;
diff --git a/ios/chrome/browser/tab_switcher/ui_bundled/tab_strip/coordinator/tab_strip_mediator.h b/ios/chrome/browser/tab_switcher/ui_bundled/tab_strip/coordinator/tab_strip_mediator.h
index f61bba4c3..c88565e1 100644
--- a/ios/chrome/browser/tab_switcher/ui_bundled/tab_strip/coordinator/tab_strip_mediator.h
+++ b/ios/chrome/browser/tab_switcher/ui_bundled/tab_strip/coordinator/tab_strip_mediator.h
@@ -13,6 +13,7 @@
 class Browser;
 class BrowserList;
 class ProfileIOS;
+class ShareKitService;
 enum class TabGroupActionType;
 @protocol TabStripCommands;
 @protocol TabStripConsumer;
@@ -70,6 +71,7 @@
             (collaboration::messaging::MessagingBackendService*)messagingService
     collaborationService:
         (collaboration::CollaborationService*)collaborationService
+         shareKitService:(ShareKitService*)shareKitService
     NS_DESIGNATED_INITIALIZER;
 - (instancetype)init NS_UNAVAILABLE;
 
diff --git a/ios/chrome/browser/tab_switcher/ui_bundled/tab_strip/coordinator/tab_strip_mediator.mm b/ios/chrome/browser/tab_switcher/ui_bundled/tab_strip/coordinator/tab_strip_mediator.mm
index 4f53242f..3bec685a 100644
--- a/ios/chrome/browser/tab_switcher/ui_bundled/tab_strip/coordinator/tab_strip_mediator.mm
+++ b/ios/chrome/browser/tab_switcher/ui_bundled/tab_strip/coordinator/tab_strip_mediator.mm
@@ -25,6 +25,7 @@
 #import "ios/chrome/browser/ntp/model/new_tab_page_util.h"
 #import "ios/chrome/browser/policy/model/policy_util.h"
 #import "ios/chrome/browser/saved_tab_groups/model/ios_tab_group_sync_util.h"
+#import "ios/chrome/browser/share_kit/model/share_kit_service.h"
 #import "ios/chrome/browser/shared/model/browser/browser.h"
 #import "ios/chrome/browser/shared/model/browser/browser_list.h"
 #import "ios/chrome/browser/shared/model/profile/profile_ios.h"
@@ -273,7 +274,8 @@
 
   // Used to get info about saved groups and to mutate them.
   raw_ptr<tab_groups::TabGroupSyncService> _tabGroupSyncService;
-
+  // The share kit service.
+  raw_ptr<ShareKitService> _shareKitService;
   // A service to get activity messages for a shared tab group.
   raw_ptr<collaboration::messaging::MessagingBackendService> _messagingService;
   // The bridge between the C++ MessagingBackendService observer and this
@@ -298,7 +300,8 @@
         messagingService:
             (collaboration::messaging::MessagingBackendService*)messagingService
     collaborationService:
-        (collaboration::CollaborationService*)collaborationService {
+        (collaboration::CollaborationService*)collaborationService
+         shareKitService:(ShareKitService*)shareKitService {
   if ((self = [super init])) {
     CHECK(browserList);
     _browserList = browserList;
@@ -306,6 +309,7 @@
     _tabGroupSyncService = tabGroupSyncService;
     _consumer = consumer;
     _messagingService = messagingService;
+    _shareKitService = shareKitService;
     if (_messagingService) {
       _messagingBackendServiceBridge =
           std::make_unique<MessagingBackendServiceBridge>(self);
@@ -869,8 +873,8 @@
   BOOL displayAlert = NO;
   const TabGroup* group = self.webStateList->GetGroupOfWebStateAt(index);
   if (group) {
-    BOOL isSharedGroup =
-        tab_groups::utils::IsTabGroupShared(group, _tabGroupSyncService);
+    BOOL isSharedGroup = tab_groups::utils::IsTabGroupShared(
+        group, _tabGroupSyncService, _shareKitService);
     displayAlert = group->range().count() == 1 && isSharedGroup;
   }
 
diff --git a/ios/chrome/browser/tab_switcher/ui_bundled/tab_strip/coordinator/tab_strip_mediator_unittest.mm b/ios/chrome/browser/tab_switcher/ui_bundled/tab_strip/coordinator/tab_strip_mediator_unittest.mm
index cd5a002..082f768 100644
--- a/ios/chrome/browser/tab_switcher/ui_bundled/tab_strip/coordinator/tab_strip_mediator_unittest.mm
+++ b/ios/chrome/browser/tab_switcher/ui_bundled/tab_strip/coordinator/tab_strip_mediator_unittest.mm
@@ -23,6 +23,8 @@
 #import "ios/chrome/browser/drag_and_drop/model/drag_item_util.h"
 #import "ios/chrome/browser/favicon/model/favicon_service_factory.h"
 #import "ios/chrome/browser/history/model/history_service_factory.h"
+#import "ios/chrome/browser/share_kit/model/share_kit_service.h"
+#import "ios/chrome/browser/share_kit/model/share_kit_service_factory.h"
 #import "ios/chrome/browser/shared/model/browser/browser_list.h"
 #import "ios/chrome/browser/shared/model/browser/browser_list_factory.h"
 #import "ios/chrome/browser/shared/model/browser/test/test_browser.h"
@@ -146,6 +148,8 @@
     tab_strip_handler_ = [[FakeTabStripHandler alloc] init];
 
     consumer_ = [[FakeTabStripConsumer alloc] init];
+
+    share_kit_service_ = ShareKitServiceFactory::GetForProfile(profile_.get());
   }
 
   ~TabStripMediatorTest() override { [mediator_ disconnect]; }
@@ -161,7 +165,8 @@
                                tabGroupSyncService:tab_group_sync_service_.get()
                                        browserList:browser_list
                                   messagingService:&messaging_backend_
-                              collaborationService:nil];
+                              collaborationService:nil
+                                   shareKitService:share_kit_service_];
 
     mediator_.profile = profile_.get();
     mediator_.webStateList = web_state_list_;
@@ -212,6 +217,7 @@
   raw_ptr<UrlLoadingBrowserAgent> loader_;
   FakeURLLoadingDelegate* url_loading_delegate_;
   collaboration::messaging::MockMessagingBackendService messaging_backend_;
+  raw_ptr<ShareKitService> share_kit_service_;
 };
 
 // Tests that the mediator correctly populates the consumer at startup and after
diff --git a/ios/chrome/browser/tab_switcher/ui_bundled/tab_strip/ui/context_menu/tab_strip_context_menu_helper.mm b/ios/chrome/browser/tab_switcher/ui_bundled/tab_strip/ui/context_menu/tab_strip_context_menu_helper.mm
index 79b2bca..bc5584a59 100644
--- a/ios/chrome/browser/tab_switcher/ui_bundled/tab_strip/ui/context_menu/tab_strip_context_menu_helper.mm
+++ b/ios/chrome/browser/tab_switcher/ui_bundled/tab_strip/ui/context_menu/tab_strip_context_menu_helper.mm
@@ -203,8 +203,8 @@
       shareKitService && shareKitService->IsSupported();
 
   SharingState sharingState = SharingState::kNotShared;
-  if (tab_groups::utils::IsTabGroupShared(tabGroupItem.tabGroup,
-                                          tabGroupSyncService)) {
+  if (tab_groups::utils::IsTabGroupShared(
+          tabGroupItem.tabGroup, tabGroupSyncService, shareKitService)) {
     collaboration::CollaborationService* collaborationService =
         collaboration::CollaborationServiceFactory::GetForProfile(_profile);
     data_sharing::MemberRole userRole = tab_groups::utils::GetUserRoleForGroup(
diff --git a/ios/chrome/browser/toolbar/ui_bundled/tab_groups/coordinator/tab_group_indicator_mediator.mm b/ios/chrome/browser/toolbar/ui_bundled/tab_groups/coordinator/tab_group_indicator_mediator.mm
index 8a1fdbc..2a5d09c 100644
--- a/ios/chrome/browser/toolbar/ui_bundled/tab_groups/coordinator/tab_group_indicator_mediator.mm
+++ b/ios/chrome/browser/toolbar/ui_bundled/tab_groups/coordinator/tab_group_indicator_mediator.mm
@@ -432,8 +432,8 @@
 // shared tab group visible.
 - (void)presentForegroundIPHIfNeeded {
   const TabGroup* tabGroup = [self currentTabGroup];
-  if (!tabGroup ||
-      !tab_groups::utils::IsTabGroupShared(tabGroup, _tabGroupSyncService)) {
+  if (!tabGroup || !tab_groups::utils::IsTabGroupShared(
+                       tabGroup, _tabGroupSyncService, _shareKitService)) {
     return;
   }
   if (_tracker->WouldTriggerHelpUI(
@@ -498,8 +498,8 @@
 
 // Updates the sharing state for the given `tabGroup`.
 - (void)updateTabGroupSharingState:(const TabGroup*)tabGroup {
-  BOOL shared =
-      tab_groups::utils::IsTabGroupShared(tabGroup, _tabGroupSyncService);
+  BOOL shared = tab_groups::utils::IsTabGroupShared(
+      tabGroup, _tabGroupSyncService, _shareKitService);
   if (!shared) {
     [_consumer setSharingState:SharingState::kNotShared];
     return;
diff --git a/ios/chrome/browser/variations/model/BUILD.gn b/ios/chrome/browser/variations/model/BUILD.gn
index 2c8c8359..b1474d0 100644
--- a/ios/chrome/browser/variations/model/BUILD.gn
+++ b/ios/chrome/browser/variations/model/BUILD.gn
@@ -130,6 +130,7 @@
   sources = [
     "variations_safe_mode_egtest.mm",
     "variations_safe_mode_end_to_end_egtest.mm",
+    "variations_safe_mode_seedfile_egtest.mm",
   ]
   deps = [
     ":eg_test_support+eg2",
diff --git a/ios/chrome/browser/variations/model/variations_app_interface.mm b/ios/chrome/browser/variations/model/variations_app_interface.mm
index 0dbd752..0e8cc63 100644
--- a/ios/chrome/browser/variations/model/variations_app_interface.mm
+++ b/ios/chrome/browser/variations/model/variations_app_interface.mm
@@ -63,10 +63,12 @@
 }
 
 + (BOOL)hasSafeSeed {
-  PrefService* prefService = GetApplicationContext()->GetLocalState();
-  const std::string& safe_seed =
-      prefService->GetString(variations::prefs::kVariationsSafeCompressedSeed);
-  return !safe_seed.empty();
+  return !GetApplicationContext()
+              ->GetVariationsService()
+              ->GetSeedStoreForTesting()
+              ->GetSafeSeedReaderWriterForTesting()
+              ->GetSeedData()
+              .data.empty();
 }
 
 + (void)setTestSafeSeedAndSignature {
diff --git a/ios/chrome/browser/variations/model/variations_safe_mode_egtest.mm b/ios/chrome/browser/variations/model/variations_safe_mode_egtest.mm
index 250b4966..7be0794 100644
--- a/ios/chrome/browser/variations/model/variations_safe_mode_egtest.mm
+++ b/ios/chrome/browser/variations/model/variations_safe_mode_egtest.mm
@@ -9,6 +9,20 @@
 #import "ios/testing/earl_grey/app_launch_manager.h"
 #import "ios/testing/earl_grey/earl_grey_test.h"
 
+// The tests in this file should roughly correspond to the tests in
+// chrome/browser/metrics/variations/variations_safe_mode_browsertest.cc when
+// not being in the treatment group of the Seed File experiment. "Roughly"
+// because there is a significant difference between the tests.
+//
+// The browser tests use a HistogramTester to check if Chrome is running in safe
+// mode while the EG tests use the existence of a field trial associated with
+// the test safe seed's sole study. HistogramTesters are not an option in EG
+// tests because ensureAppLaunchedWithConfiguration() both shuts down and
+// relaunches Chrome. It is not possible to initialize a HistogramTester until
+// after the startup code under test has executed. Initializing a
+// HistogramTester before shutting down Chrome isn't helpful because the
+// tester is not persisted across sessions.
+
 @interface VariationsSafeModeTestCase : ChromeTestCase
 @end
 
@@ -23,8 +37,6 @@
 - (AppLaunchConfiguration)appConfigurationForTestCase {
   AppLaunchConfiguration config;
   config.relaunch_policy = ForceRelaunchByCleanShutdown;
-  // TODO(crbug.com/371171846): Make the test not read directly from Local
-  // State and add --force-fieldtrials for the other groups.
   config.additional_args = {"--disable-field-trial-config",
                             "--disable-variations-seed-fetch",
                             "--force-fieldtrials=SeedFileTrial/Control_V7"};
@@ -39,8 +51,6 @@
 - (AppLaunchConfiguration)appConfigurationForCrashing {
   AppLaunchConfiguration config;
   config.relaunch_policy = ForceRelaunchByKilling;
-  // TODO(crbug.com/371171846): Make the test not read directly from Local
-  // State and add --force-fieldtrials for the other groups.
   config.additional_args = {"--disable-field-trial-config",
                             "--disable-variations-seed-fetch",
                             "--force-fieldtrials=SeedFileTrial/Control_V7"};
@@ -109,19 +119,6 @@
 
 #pragma mark - Tests
 
-// The tests in this file should roughly correspond to the tests in
-// chrome/browser/metrics/variations/variations_safe_mode_browsertest.cc.
-// "Roughly" because there is a significant difference between the tests.
-//
-// The browser tests use a HistogramTester to check if Chrome is running in safe
-// mode while the EG tests use the existence of a field trial associated with
-// the test safe seed's sole study. HistogramTesters are not an option in EG
-// tests because ensureAppLaunchedWithConfiguration() both shuts down and
-// relaunches Chrome. It is not possible to initialize a HistogramTester until
-// after the startup code under test has executed. Initializing a
-// HistogramTester before shutting down Chrome isn't helpful because the
-// tester is not persisted across sessions.
-
 // Tests that three crashes trigger variations safe mode.
 //
 // Corresponds to VariationsSafeModeBrowserTest.ThreeCrashesTriggerSafeMode in
@@ -140,7 +137,7 @@
   [self checkCrashStreakValue:0];
   [self checkFailedFetchStreakValue:0];
   GREYAssertTrue([VariationsAppInterface hasSafeSeed],
-                 @"The variations safe seed pref should be set.");
+                 @"The variations safe seed should exist.");
   GREYAssertFalse([VariationsAppInterface fieldTrialExistsForTestSeed],
                   @"There should be no field trials from kTestSeedData.");
 
@@ -162,7 +159,7 @@
   [[AppLaunchManager sharedManager] ensureAppLaunchedWithConfiguration:config];
   [self checkCrashStreakValue:3];
   GREYAssertTrue([VariationsAppInterface hasSafeSeed],
-                 @"The variations safe seed pref should be set.");
+                 @"The variations safe seed should exist.");
   // Verify that Chrome fell back to variations safe mode by checking that there
   // is a field trial for the test safe seed's study.
   GREYAssertTrue([VariationsAppInterface fieldTrialExistsForTestSeed],
@@ -192,7 +189,7 @@
   [self checkCrashStreakValue:0];
   [self checkFailedFetchStreakValue:25];
   GREYAssertTrue([VariationsAppInterface hasSafeSeed],
-                 @"The variations safe seed pref should be set.");
+                 @"The variations safe seed should exist.");
   // Verify that Chrome fell back to variations safe mode by checking that there
   // is a field trial for the test safe seed's study.
   GREYAssertTrue([VariationsAppInterface fieldTrialExistsForTestSeed],
@@ -226,7 +223,7 @@
   [self checkCrashStreakValue:2];
   [self checkFailedFetchStreakValue:24];
   GREYAssertTrue([VariationsAppInterface hasSafeSeed],
-                 @"The variations safe seed pref should be set.");
+                 @"The variations safe seed should exist.");
   // Verify that Chrome did not fall back to variations safe mode by checking
   // that there isn't a field trial for the test safe seed's study.
   GREYAssertFalse([VariationsAppInterface fieldTrialExistsForTestSeed],
diff --git a/ios/chrome/browser/variations/model/variations_safe_mode_seedfile_egtest.mm b/ios/chrome/browser/variations/model/variations_safe_mode_seedfile_egtest.mm
new file mode 100644
index 0000000..416145c2
--- /dev/null
+++ b/ios/chrome/browser/variations/model/variations_safe_mode_seedfile_egtest.mm
@@ -0,0 +1,248 @@
+// 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/branding_buildflags.h"
+#import "ios/chrome/browser/variations/model/variations_app_interface.h"
+#import "ios/chrome/test/earl_grey/chrome_earl_grey.h"
+#import "ios/chrome/test/earl_grey/chrome_test_case.h"
+#import "ios/testing/earl_grey/app_launch_manager.h"
+#import "ios/testing/earl_grey/earl_grey_test.h"
+
+// The tests in this file should roughly correspond to the tests in
+// chrome/browser/metrics/variations/variations_safe_mode_browsertest.cc when
+// the client is in the treatment group of the Seed File experiment. "Roughly"
+// because there is a significant difference between the tests.
+//
+// The browser tests use a HistogramTester to check if Chrome is running in safe
+// mode while the EG tests use the existence of a field trial associated with
+// the test safe seed's sole study. HistogramTesters are not an option in EG
+// tests because ensureAppLaunchedWithConfiguration() both shuts down and
+// relaunches Chrome. It is not possible to initialize a HistogramTester until
+// after the startup code under test has executed. Initializing a
+// HistogramTester before shutting down Chrome isn't helpful because the
+// tester is not persisted across sessions.
+
+@interface VariationsSafeModeSeedFileTestCase : ChromeTestCase
+@end
+
+@implementation VariationsSafeModeSeedFileTestCase
+
+#pragma mark - Helpers
+
+// Returns an AppLaunchConfiguration that shuts down Chrome cleanly and
+// relaunches it without using the field trial testing config. Shutting down
+// cleanly flushes local state. Disabling the testing config means that the only
+// field trials after the relaunch, if any, are client-configured field trials
+// and those that are forced via command-line flags.
+- (AppLaunchConfiguration)appConfigurationForTestCase {
+  AppLaunchConfiguration config;
+  config.relaunch_policy = ForceRelaunchByCleanShutdown;
+  // Force the channel to stable so that the client participates in the
+  // SeedFileTrial study. Using any channel other than UNKNOWN will participate
+  // in the study.
+  config.additional_args = {"--disable-field-trial-config",
+                            "--disable-variations-seed-fetch",
+                            "--force-fieldtrials=SeedFileTrial/SeedFiles_V7",
+                            "--fake-variations-channel=stable"};
+  return config;
+}
+
+// Returns an AppLaunchConfiguration that crashes and relaunches Chrome. The
+// config also disables the use of the field trial testing config so that
+// VariationsFieldTrialCreator::CreateTrialsFromSeed() executes and determines
+// whether to use variations safe mode. See the comment above
+// appConfigForPersistingPrefs for more info on disabling the testing config.
+- (AppLaunchConfiguration)appConfigurationForCrashing {
+  AppLaunchConfiguration config;
+  config.relaunch_policy = ForceRelaunchByKilling;
+  // Force the channel to stable so that the client participates in the
+  // SeedFileTrial study. Using any channel other than UNKNOWN will participate
+  // in the study.
+  config.additional_args = {"--disable-field-trial-config",
+                            "--disable-variations-seed-fetch",
+                            "--force-fieldtrials=SeedFileTrial/SeedFiles_V7",
+                            "--fake-variations-channel=stable"};
+  return config;
+}
+
+// Returns an AppLaunchConfiguration that shuts down Chrome cleanly (if it is
+// already running) and relaunches it with no additional flags or settings.
+- (AppLaunchConfiguration)appConfigurationForCleanRestart {
+  AppLaunchConfiguration config;
+  config.relaunch_policy = ForceRelaunchByCleanShutdown;
+  // Force the channel to stable so that the client participates in the
+  // SeedFileTrial study. Using any channel other than UNKNOWN will participate
+  // in the study.
+  config.additional_args = {"--disable-variations-seed-fetch",
+                            "--force-fieldtrials=SeedFileTrial/SeedFiles_V7",
+                            "--fake-variations-channel=stable"};
+  return config;
+}
+
+// Checks that the variations crash streak is `value`.
+- (void)checkCrashStreakValue:(int)value {
+  int actualStreak = [VariationsAppInterface crashStreak];
+  GREYAssertEqual(actualStreak, value,
+                  @"Expected a crash streak of %d, but got %d", value,
+                  actualStreak);
+}
+
+// Checks that the variations failed fetch streak is `value`.
+- (void)checkFailedFetchStreakValue:(int)value {
+  int actualStreak = [VariationsAppInterface failedFetchStreak];
+  GREYAssertEqual(actualStreak, value,
+                  @"Expected a failed fetch streak of %d, but got %d", value,
+                  actualStreak);
+}
+
+// Restarts the app and ensures there's no variations/crash state active.
+- (void)resetAppState:(AppLaunchConfiguration)config {
+  // Clear local state variations prefs since local state is persisted between
+  // EG tests and restart Chrome. This is to avoid flakiness caused by tests
+  // that may have run previously and to avoid introducing flakiness in tests
+  // that might run after.
+  //
+  // See crbug.com/1069086.
+  [VariationsAppInterface clearVariationsPrefs];
+  [[AppLaunchManager sharedManager] ensureAppLaunchedWithConfiguration:config];
+
+  // Validate app state:
+  //   * App is running
+  //   * No safe seed value in local state
+  //   * No evidence of safe seed settings in local state.
+  //   * No active crash streak
+  XCTAssertTrue([[AppLaunchManager sharedManager] appIsLaunched],
+                @"App should be launched.");
+  GREYAssertFalse([VariationsAppInterface hasSafeSeed], @"No safe seed.");
+  GREYAssertFalse([VariationsAppInterface fieldTrialExistsForTestSeed],
+                  @"No field trial from test seed.");
+  [self checkCrashStreakValue:0];
+}
+
+#pragma mark - Lifecycle
+
+- (void)setUp {
+  [super setUp];
+  [self resetAppState:[self appConfigurationForTestCase]];
+}
+
+- (void)tearDownHelper {
+  [self resetAppState:[self appConfigurationForCleanRestart]];
+  [super tearDownHelper];
+}
+
+#pragma mark - Tests
+
+// Tests that three crashes trigger variations safe mode.
+//
+// Corresponds to VariationsSafeModeBrowserTest.ThreeCrashesTriggerSafeMode in
+// variations_safe_mode_browsertest.cc.
+- (void)testThreeCrashesTriggerSafeMode {
+  [VariationsAppInterface setTestSafeSeedAndSignature];
+
+  // Persist the local state pref changes made above and in setUp().
+  [[AppLaunchManager sharedManager]
+      ensureAppLaunchedWithConfiguration:[self appConfigurationForTestCase]];
+
+  // Verify that (i) the crash and failed fetch streaks were reset, (ii) the
+  // safe seed was persisted, and (iii) there is no field trial associated with
+  // the test safe seed's sole study. There should be a field trial associated
+  // with the study only after variations safe mode is triggered.
+  [self checkCrashStreakValue:0];
+  [self checkFailedFetchStreakValue:0];
+  GREYAssertTrue([VariationsAppInterface hasSafeSeed],
+                 @"The variations safe seed should exist.");
+  GREYAssertFalse([VariationsAppInterface fieldTrialExistsForTestSeed],
+                  @"There should be no field trials from kTestSeedData.");
+
+  // Crash the app three times since a crash streak of three or more triggers
+  // variations safe mode. Also, verify the crash streak and the field trial
+  // after crashes.
+  AppLaunchConfiguration config = [self appConfigurationForCrashing];
+  // First crash.
+  [[AppLaunchManager sharedManager] ensureAppLaunchedWithConfiguration:config];
+  [self checkCrashStreakValue:1];
+  GREYAssertFalse([VariationsAppInterface fieldTrialExistsForTestSeed],
+                  @"There should be no field trials from kTestSeedData.");
+  // Second crash.
+  [[AppLaunchManager sharedManager] ensureAppLaunchedWithConfiguration:config];
+  [self checkCrashStreakValue:2];
+  GREYAssertFalse([VariationsAppInterface fieldTrialExistsForTestSeed],
+                  @"There should be no field trials from kTestSeedData.");
+  // Third crash.
+  [[AppLaunchManager sharedManager] ensureAppLaunchedWithConfiguration:config];
+  [self checkCrashStreakValue:3];
+  GREYAssertTrue([VariationsAppInterface hasSafeSeed],
+                 @"The variations safe seed should exist.");
+  // Verify that Chrome fell back to variations safe mode by checking that there
+  // is a field trial for the test safe seed's study.
+  GREYAssertTrue([VariationsAppInterface fieldTrialExistsForTestSeed],
+                 @"There should be field trials from kTestSeedData.");
+}
+
+// Tests that variations seed fetch failures trigger variations safe mode.
+//
+// Corresponds to VariationsSafeModeBrowserTest.FetchFailuresTriggerSafeMode in
+// variations_safe_mode_browsertest.cc.
+- (void)testFetchFailuresTriggerSafeMode {
+  [VariationsAppInterface setTestSafeSeedAndSignature];
+  // The fetch failure streak threshold for triggering safe mode is 25.
+  [VariationsAppInterface setFetchFailureValue:25];
+
+  // Verify that there is no field trial associated with the test safe seed's
+  // sole study.
+  GREYAssertFalse([VariationsAppInterface fieldTrialExistsForTestSeed],
+                  @"There should be no field trials from kTestSeedData.");
+
+  // Persist the local state pref changes made above and in setUp().
+  [[AppLaunchManager sharedManager]
+      ensureAppLaunchedWithConfiguration:[self appConfigurationForTestCase]];
+
+  // Verify that (i) the crash streak was reset, (ii) the failed fetch streak
+  // and the safe seed were persisted, and (iii) safe mode was triggered.
+  [self checkCrashStreakValue:0];
+  [self checkFailedFetchStreakValue:25];
+  GREYAssertTrue([VariationsAppInterface hasSafeSeed],
+                 @"The variations safe seed should exist.");
+  // Verify that Chrome fell back to variations safe mode by checking that there
+  // is a field trial for the test safe seed's study.
+  GREYAssertTrue([VariationsAppInterface fieldTrialExistsForTestSeed],
+                 @"There should be field trials from kTestSeedData.");
+}
+
+// Tests that variations safe mode is not triggered.
+//
+// Corresponds to VariationsSafeModeBrowserTest.DoNotTriggerSafeMode in
+// variations_safe_mode_browsertest.cc.
+- (void)testDoNotTriggerSafeMode {
+  [VariationsAppInterface setTestSafeSeedAndSignature];
+  // Neither a crash streak of 2 nor a fetch failure streak of 24 will trigger
+  // variations safe mode in the next session.
+  int crashes = 2;
+  int fetchFailures = 24;
+  [VariationsAppInterface setCrashValue:crashes];
+  [VariationsAppInterface setFetchFailureValue:fetchFailures];
+
+  // Verify that there is no field trial associated with the test safe seed's
+  // sole study.
+  GREYAssertFalse([VariationsAppInterface fieldTrialExistsForTestSeed],
+                  @"There should be no field trials from kTestSeedData.");
+
+  // Persist the local state pref changes made above and in setUp().
+  [[AppLaunchManager sharedManager]
+      ensureAppLaunchedWithConfiguration:[self appConfigurationForTestCase]];
+
+  // Verify that (i) the crash and failed fetch streaks are as expected, (ii)
+  // the safe seed was stored, and (iii) safe mode was not triggered.
+  [self checkCrashStreakValue:2];
+  [self checkFailedFetchStreakValue:24];
+  GREYAssertTrue([VariationsAppInterface hasSafeSeed],
+                 @"The variations safe seed should exist.");
+  // Verify that Chrome did not fall back to variations safe mode by checking
+  // that there isn't a field trial for the test safe seed's study.
+  GREYAssertFalse([VariationsAppInterface fieldTrialExistsForTestSeed],
+                  @"There should be no field trials from kTestSeedData.");
+}
+
+@end
diff --git a/ios/chrome/common/credential_provider/constants.h b/ios/chrome/common/credential_provider/constants.h
index 1fbf52a..2c38755 100644
--- a/ios/chrome/common/credential_provider/constants.h
+++ b/ios/chrome/common/credential_provider/constants.h
@@ -18,6 +18,10 @@
 // Key for the app group user defaults containing the current user ID.
 NSString* AppGroupUserDefaultsCredentialProviderUserID();
 
+// Key for the app group user defaults containing whether multiple profiles are
+// currently in use.
+NSString* AppGroupUserDefaultsCredentialProviderMultiProfileSetting();
+
 // Key for the app group user defaults containing the current user email.
 NSString* AppGroupUserDefaultsCredentialProviderUserEmail();
 
diff --git a/ios/chrome/common/credential_provider/constants.mm b/ios/chrome/common/credential_provider/constants.mm
index 94d194777..113cbca 100644
--- a/ios/chrome/common/credential_provider/constants.mm
+++ b/ios/chrome/common/credential_provider/constants.mm
@@ -31,6 +31,11 @@
 NSString* const kUserDefaultsCredentialProviderUserID =
     @"kUserDefaultsCredentialProviderUserID";
 
+// Used to generate the key for the app group user defaults containing whether
+// multiple profiles are currently in use.
+NSString* const kUserDefaultsCredentialProviderMultiProfile =
+    @"kUserDefaultsCredentialProviderMultiProfile";
+
 // Used to generate the key for the app group user defaults containing the
 // current user id.
 NSString* const kUserDefaultsCredentialProviderUserEmail =
@@ -126,6 +131,11 @@
       stringByAppendingString:kUserDefaultsCredentialProviderUserID];
 }
 
+NSString* AppGroupUserDefaultsCredentialProviderMultiProfileSetting() {
+  return [AppGroupPrefix()
+      stringByAppendingString:kUserDefaultsCredentialProviderMultiProfile];
+}
+
 NSString* AppGroupUserDefaultsCredentialProviderUserEmail() {
   return [AppGroupPrefix()
       stringByAppendingString:kUserDefaultsCredentialProviderUserEmail];
diff --git a/ios/chrome/credential_provider_extension/credential_provider_view_controller.mm b/ios/chrome/credential_provider_extension/credential_provider_view_controller.mm
index e170f47..da8d9797 100644
--- a/ios/chrome/credential_provider_extension/credential_provider_view_controller.mm
+++ b/ios/chrome/credential_provider_extension/credential_provider_view_controller.mm
@@ -241,7 +241,7 @@
 
   __weak __typeof__(self) weakSelf = self;
   [self validateUserWithCompletion:^(BOOL userIsValid) {
-    // `reauthenticationModule` can't attempt reauth when no passscode is set.
+    // `reauthenticationModule` can't attempt reauth when no passcode is set.
     // This means a credential shouldn't be retrieved just yet.
     if (!weakSelf.reauthenticationModule.canAttemptReauth || !userIsValid) {
       [weakSelf exitWithErrorCode:ASExtensionErrorCodeUserInteractionRequired];
@@ -315,13 +315,13 @@
       IsPasskeysM2Enabled()) {
     __weak __typeof__(self) weakSelf = self;
     auto completion = ^(NSArray<NSData*>* securityDomainSecrets) {
-      [weakSelf completeSecurityDomainSecretFetchForExtensionConfigutation];
+      [weakSelf completeSecurityDomainSecretFetchForExtensionConfiguration];
     };
 
     // Trigger a security domain secret fetch to know whether the user needs to
     // bootstrap (create/enter their GPM pin) to use passkeys on their device.
     // If bootstrapping is needed, then the fetching flow will take care of
-    // presenting the relevent UI. The `completion` will then take care of
+    // presenting the relevant UI. The `completion` will then take care of
     // dismissing the bootstrapping UI if it was presented. If it wasn't
     // presented, it means that the user was already bootstrapped. In this case,
     // `completion` will present the ConsentViewController.
@@ -565,6 +565,12 @@
       AppGroupUserDefaultsCredentialProviderUserEmail(), /*default_value=*/@"");
 }
 
+// Returns whether the user is currently using multiple profile in Chrome.
+- (BOOL)isUsingMultiProfile {
+  return [app_group::GetGroupUserDefaults()
+      boolForKey:AppGroupUserDefaultsCredentialProviderMultiProfileSetting()];
+}
+
 #pragma mark - PasskeyKeychainProviderBridgeDelegate
 
 - (void)performUserVerificationIfNeeded:(ProceduralBlock)completion {
@@ -699,7 +705,7 @@
   }
 
   if (passkeyRequestDetails.userVerificationRequired ||
-      !IsAutomaticPasskeyUpgradeEnabled()) {
+      !IsAutomaticPasskeyUpgradeEnabled() || [self isUsingMultiProfile]) {
     return PasskeyCreationEligibility::kCanCreateWithUserInteraction;
   }
 
@@ -1118,7 +1124,7 @@
 
   ProceduralBlock action;
   // With the `kReauthenticate` purpose, the user will be asked to enter their
-  // Google Passowrd Manager PIN, so no need to also do a device
+  // Google Password Manager PIN, so no need to also do a device
   // reauthentication before showing the UI.
   if (purpose != PasskeyWelcomeScreenPurpose::kReauthenticate &&
       _userVerificationRequired) {
@@ -1180,7 +1186,7 @@
 // as a credential provider in iOS Settings. Dismisses the
 // `passkeyNavigationController` if presented for passkey bootstrapping purposes
 // during the fetching process. Otherwise, presents the ConsentViewController.
-- (void)completeSecurityDomainSecretFetchForExtensionConfigutation {
+- (void)completeSecurityDomainSecretFetchForExtensionConfiguration {
   // If the `passkeyNavigationController` has a `visibleViewController`, it
   // means that the bootstrapping UI has been presented to the user through the
   // security domain secret fetch (see
diff --git a/ios/chrome/credential_provider_extension/passkey_keychain_provider_bridge.h b/ios/chrome/credential_provider_extension/passkey_keychain_provider_bridge.h
index 9ab34205..7b6b718 100644
--- a/ios/chrome/credential_provider_extension/passkey_keychain_provider_bridge.h
+++ b/ios/chrome/credential_provider_extension/passkey_keychain_provider_bridge.h
@@ -22,7 +22,7 @@
 - (void)showFixDegradedRecoverabilityWelcomeScreen:
     (ProceduralBlock)fixDegradedRecoverabilityBlock;
 
-// Presents the passkey reauthentication weclome screen.
+// Presents the passkey reauthentication welcome screen.
 - (void)showReauthenticationWelcomeScreen:(ProceduralBlock)reauthenticateBlock;
 
 @end
diff --git a/ios/chrome/credential_provider_extension/passkey_keychain_provider_bridge.mm b/ios/chrome/credential_provider_extension/passkey_keychain_provider_bridge.mm
index 56f67cd..878e79e 100644
--- a/ios/chrome/credential_provider_extension/passkey_keychain_provider_bridge.mm
+++ b/ios/chrome/credential_provider_extension/passkey_keychain_provider_bridge.mm
@@ -180,7 +180,7 @@
 }
 
 // Attempts to fetch the keys for the account associated with the provided gaia
-// ID if no error occured at the previous stage. `canReauthenticate` indicates
+// ID if no error occurred at the previous stage. `canReauthenticate` indicates
 // whether the user can be asked to reauthenticate by entering their GPM PIN.
 // This argument should only be set to `NO` if the user has already been asked
 // to reauthenticate.
diff --git a/ios/chrome/credential_provider_extension/ui/credential_details_view_controller.mm b/ios/chrome/credential_provider_extension/ui/credential_details_view_controller.mm
index adf64df..697fd66 100644
--- a/ios/chrome/credential_provider_extension/ui/credential_details_view_controller.mm
+++ b/ios/chrome/credential_provider_extension/ui/credential_details_view_controller.mm
@@ -179,44 +179,48 @@
 
   switch ([self rowIdentifier:indexPath.row]) {
     case RowIdentifier::RowIdentifierURL:
-      [self showTootip:NSLocalizedString(
-                           @"IDS_IOS_CREDENTIAL_PROVIDER_DETAILS_COPY", @"Copy")
-            atBottomOf:cell
-                action:@selector(copyURL)];
+      [self
+          showTooltip:NSLocalizedString(
+                          @"IDS_IOS_CREDENTIAL_PROVIDER_DETAILS_COPY", @"Copy")
+           atBottomOf:cell
+               action:@selector(copyURL)];
       break;
     case RowIdentifier::RowIdentifierUsername:
-      [self showTootip:NSLocalizedString(
-                           @"IDS_IOS_CREDENTIAL_PROVIDER_DETAILS_COPY", @"Copy")
-            atBottomOf:cell
-                action:@selector(copyUsername)];
+      [self
+          showTooltip:NSLocalizedString(
+                          @"IDS_IOS_CREDENTIAL_PROVIDER_DETAILS_COPY", @"Copy")
+           atBottomOf:cell
+               action:@selector(copyUsername)];
       break;
     case RowIdentifier::RowIdentifierPassword:
       if (self.clearPassword) {
-        [self
-            showTootip:NSLocalizedString(
-                           @"IDS_IOS_CREDENTIAL_PROVIDER_DETAILS_COPY", @"Copy")
-            atBottomOf:cell
-                action:@selector(copyPassword)];
+        [self showTooltip:NSLocalizedString(
+                              @"IDS_IOS_CREDENTIAL_PROVIDER_DETAILS_COPY",
+                              @"Copy")
+               atBottomOf:cell
+                   action:@selector(copyPassword)];
       } else {
-        [self
-            showTootip:NSLocalizedString(
-                           @"IDS_IOS_CREDENTIAL_PROVIDER_DETAILS_SHOW_PASSWORD",
-                           @"Show Password")
-            atBottomOf:cell
-                action:@selector(showPassword)];
+        [self showTooltip:
+                  NSLocalizedString(
+                      @"IDS_IOS_CREDENTIAL_PROVIDER_DETAILS_SHOW_PASSWORD",
+                      @"Show Password")
+               atBottomOf:cell
+                   action:@selector(showPassword)];
       }
       break;
     case RowIdentifier::RowIdentifierUserDisplayName:
-      [self showTootip:NSLocalizedString(
-                           @"IDS_IOS_CREDENTIAL_PROVIDER_DETAILS_COPY", @"Copy")
-            atBottomOf:cell
-                action:@selector(copyUserDisplayName)];
+      [self
+          showTooltip:NSLocalizedString(
+                          @"IDS_IOS_CREDENTIAL_PROVIDER_DETAILS_COPY", @"Copy")
+           atBottomOf:cell
+               action:@selector(copyUserDisplayName)];
       break;
     case RowIdentifier::RowIdentifierCreationDate:
-      [self showTootip:NSLocalizedString(
-                           @"IDS_IOS_CREDENTIAL_PROVIDER_DETAILS_COPY", @"Copy")
-            atBottomOf:cell
-                action:@selector(copyCreationDate)];
+      [self
+          showTooltip:NSLocalizedString(
+                          @"IDS_IOS_CREDENTIAL_PROVIDER_DETAILS_COPY", @"Copy")
+           atBottomOf:cell
+               action:@selector(copyCreationDate)];
       break;
     default:
       break;
@@ -381,7 +385,7 @@
   return button;
 }
 
-// Called when show/hine password icon is tapped.
+// Called when show/hide password icon is tapped.
 - (void)passwordIconButtonTapped:(id)sender event:(id)event {
   // Only password reveal / hide is an accessory, so no need to check
   // indexPath.
@@ -416,9 +420,9 @@
                         withRowAnimation:UITableViewRowAnimationAutomatic];
 }
 
-- (void)showTootip:(NSString*)message
-        atBottomOf:(UITableViewCell*)cell
-            action:(SEL)action {
+- (void)showTooltip:(NSString*)message
+         atBottomOf:(UITableViewCell*)cell
+             action:(SEL)action {
   TooltipView* tooltip = [[TooltipView alloc] initWithKeyWindow:self.view
                                                          target:self
                                                          action:action];
diff --git a/ios/chrome/credential_provider_extension/ui/credential_details_view_controller_unittest.mm b/ios/chrome/credential_provider_extension/ui/credential_details_view_controller_unittest.mm
index e90f33a..7c593edd 100644
--- a/ios/chrome/credential_provider_extension/ui/credential_details_view_controller_unittest.mm
+++ b/ios/chrome/credential_provider_extension/ui/credential_details_view_controller_unittest.mm
@@ -90,7 +90,7 @@
   id<Credential> credential = TestPasskeyCredential();
   [controller() presentCredential:credential];
 
-  // Check that the table view has the exepected number of sections and rows.
+  // Check that the table view has the expected number of sections and rows.
   UITableView* table_view = controller().tableView;
   EXPECT_EQ([table_view numberOfSections], 1);
   EXPECT_EQ([table_view numberOfRowsInSection:0], 4);
diff --git a/ios/chrome/credential_provider_extension/ui/credential_list_mediator.mm b/ios/chrome/credential_provider_extension/ui/credential_list_mediator.mm
index 6768994..aee01bb 100644
--- a/ios/chrome/credential_provider_extension/ui/credential_list_mediator.mm
+++ b/ios/chrome/credential_provider_extension/ui/credential_list_mediator.mm
@@ -129,7 +129,7 @@
 
 #pragma mark - Private
 
-// Fetches and presents credentials that are relavent to the service the user is
+// Fetches and presents credentials that are relevant to the service the user is
 // trying to log into.
 - (void)fetchAndPresentRelevantCredentials {
   self.allCredentials = [self fetchAllCredentials];
diff --git a/ios/chrome/credential_provider_extension/ui/feature_flags.h b/ios/chrome/credential_provider_extension/ui/feature_flags.h
index 4927d44..4689717 100644
--- a/ios/chrome/credential_provider_extension/ui/feature_flags.h
+++ b/ios/chrome/credential_provider_extension/ui/feature_flags.h
@@ -29,7 +29,7 @@
 // the policy isn't set in user defaults. The policy itself returns `true` for
 // unmanaged users, or for users whose enterprise has not configured this
 // policy. IMPORTANT: If `IsPasswordCreationUserEnabled()` is `NO`, that
-// supercedes this policy.
+// supersedes this policy.
 std::optional<bool> GetPasskeyCreationPolicy();
 
 // Whether the passkeys M2 feature is currently enabled.
diff --git a/ios/chrome/credential_provider_extension/ui/new_password_footer_view.h b/ios/chrome/credential_provider_extension/ui/new_password_footer_view.h
index dcd10d7..89e1a20 100644
--- a/ios/chrome/credential_provider_extension/ui/new_password_footer_view.h
+++ b/ios/chrome/credential_provider_extension/ui/new_password_footer_view.h
@@ -9,7 +9,7 @@
 
 @interface NewPasswordFooterView : UITableViewHeaderFooterView
 
-// ReuseID for ths class.
+// ReuseID for this class.
 @property(class, readonly) NSString* reuseID;
 
 @end
diff --git a/ios/chrome/credential_provider_extension/ui/new_password_view_controller.mm b/ios/chrome/credential_provider_extension/ui/new_password_view_controller.mm
index e4ce281d..08c17ffb 100644
--- a/ios/chrome/credential_provider_extension/ui/new_password_view_controller.mm
+++ b/ios/chrome/credential_provider_extension/ui/new_password_view_controller.mm
@@ -411,7 +411,7 @@
   passwordCell.textField.text = password;
   self.passwordText = password;
   // Move voiceover focus to the save button so the user knows that something
-  // has happend and the save button is now enabled.
+  // has happened and the save button is now enabled.
   UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification,
                                   self.navigationItem.rightBarButtonItem);
   [self updateSaveButtonState];
diff --git a/ios/chrome/credential_provider_extension/ui/passkey_error_alert_view_controller_unittest.mm b/ios/chrome/credential_provider_extension/ui/passkey_error_alert_view_controller_unittest.mm
index 2adebd93..85790e7 100644
--- a/ios/chrome/credential_provider_extension/ui/passkey_error_alert_view_controller_unittest.mm
+++ b/ios/chrome/credential_provider_extension/ui/passkey_error_alert_view_controller_unittest.mm
@@ -39,7 +39,8 @@
 
 // Tests that the view's content with the `kEnterpriseDisabledSavingCredentials`
 // error type is as expected.
-TEST_F(PasskeyErrorAlertViewControllerTest, TestContentWithEntepriseErrorType) {
+TEST_F(PasskeyErrorAlertViewControllerTest,
+       TestContentWithEnterpriseErrorType) {
   PasskeyErrorAlertViewController* controller =
       CreateController(ErrorType::kEnterpriseDisabledSavingCredentials);
   [controller loadView];
diff --git a/ios/chrome/credential_provider_extension/ui/passkey_welcome_screen_view_controller.mm b/ios/chrome/credential_provider_extension/ui/passkey_welcome_screen_view_controller.mm
index c3e13ae..a4962f6 100644
--- a/ios/chrome/credential_provider_extension/ui/passkey_welcome_screen_view_controller.mm
+++ b/ios/chrome/credential_provider_extension/ui/passkey_welcome_screen_view_controller.mm
@@ -206,10 +206,10 @@
         @"First step of the passkey enrollment instructions"),
     NSLocalizedString(
         @"IDS_IOS_CREDENTIAL_PROVIDER_PASSKEY_ENROLLMENT_INSTRUCTIONS_STEP_2",
-        @"First step of the passkey enrollment instructions"),
+        @"Second step of the passkey enrollment instructions"),
     NSLocalizedString(
         @"IDS_IOS_CREDENTIAL_PROVIDER_PASSKEY_ENROLLMENT_INSTRUCTIONS_STEP_3",
-        @"First step of the passkey enrollment instructions"),
+        @"Third step of the passkey enrollment instructions"),
   ];
 
   InstructionView* instructionView =
@@ -234,7 +234,7 @@
   CHECK(_userEmail);
   NSString* stringWithPlaceholder = NSLocalizedString(
       @"IDS_IOS_CREDENTIAL_PROVIDER_PASSKEY_ENROLLMENT_FOOTER_MESSAGE",
-      @"Footer messsage shown at the bottom of the screen-specific view.");
+      @"Footer message shown at the bottom of the screen-specific view.");
   footerMessage.text =
       [stringWithPlaceholder stringByReplacingOccurrencesOfString:@"$1"
                                                        withString:_userEmail];
diff --git a/ios/chrome/credential_provider_extension/ui/ui_util.h b/ios/chrome/credential_provider_extension/ui/ui_util.h
index 6b7a01a..2fffd45 100644
--- a/ios/chrome/credential_provider_extension/ui/ui_util.h
+++ b/ios/chrome/credential_provider_extension/ui/ui_util.h
@@ -13,11 +13,11 @@
 
 // The user friendly host for a service identifier.
 NSString* HostForServiceIdentifier(
-    ASCredentialServiceIdentifier* serviceIdentfier);
+    ASCredentialServiceIdentifier* serviceIdentifier);
 
 // Prompt for the top of the navigation controller telling what the current site
 // is.
 NSString* PromptForServiceIdentifiers(
-    NSArray<ASCredentialServiceIdentifier*>* serviceIdentfiers);
+    NSArray<ASCredentialServiceIdentifier*>* serviceIdentifiers);
 
 #endif  // IOS_CHROME_CREDENTIAL_PROVIDER_EXTENSION_UI_UI_UTIL_H_
diff --git a/ios/chrome/test/app/BUILD.gn b/ios/chrome/test/app/BUILD.gn
index 88e6102..19f8c82 100644
--- a/ios/chrome/test/app/BUILD.gn
+++ b/ios/chrome/test/app/BUILD.gn
@@ -45,6 +45,7 @@
     "//components/history/core/browser",
     "//components/metrics",
     "//components/metrics/demographics:test_support",
+    "//components/policy/core/browser",
     "//components/prefs",
     "//components/previous_session_info",
     "//components/saved_tab_groups/internal:tab_group_sync_bridge",
diff --git a/ios/chrome/test/app/DEPS b/ios/chrome/test/app/DEPS
index c81d7b5..4ce5ced 100644
--- a/ios/chrome/test/app/DEPS
+++ b/ios/chrome/test/app/DEPS
@@ -6,6 +6,9 @@
     "+components/previous_session_info/previous_session_info.h",
     "+components/previous_session_info/previous_session_info_private.h",
   ],
+"signin_test_util\.*": [
+    "+components/policy/core/browser/signin/profile_separation_policies.h",
+  ],
 "sync_test_util\.mm": [
     "+components/data_sharing/public",
     "+components/saved_tab_groups",
diff --git a/ios/chrome/test/app/signin_test_util.h b/ios/chrome/test/app/signin_test_util.h
index 4cee496..b1fcb0e0 100644
--- a/ios/chrome/test/app/signin_test_util.h
+++ b/ios/chrome/test/app/signin_test_util.h
@@ -6,6 +6,7 @@
 #define IOS_CHROME_TEST_APP_SIGNIN_TEST_UTIL_H_
 
 #import "base/ios/block_types.h"
+#import "components/policy/core/browser/signin/profile_separation_policies.h"
 
 @protocol SystemIdentity;
 
@@ -42,6 +43,12 @@
 // clear per-account passphrases.
 void ResetSyncAccountSettingsPrefs();
 
+// Stores a policy that will be returned for the next fetch profile separation
+// policy request.
+void SetPolicyResponseForNextProfileSeparationPolicyRequest(
+    policy::ProfileSeparationDataMigrationSettings
+        profileSeparationDataMigrationSettings);
+
 }  // namespace chrome_test_util
 
 #endif  // IOS_CHROME_TEST_APP_SIGNIN_TEST_UTIL_H_
diff --git a/ios/chrome/test/app/signin_test_util.mm b/ios/chrome/test/app/signin_test_util.mm
index 853aa85..bdb0f1f 100644
--- a/ios/chrome/test/app/signin_test_util.mm
+++ b/ios/chrome/test/app/signin_test_util.mm
@@ -7,6 +7,7 @@
 #import "base/check.h"
 #import "base/notreached.h"
 #import "base/test/ios/wait_util.h"
+#import "components/policy/core/browser/signin/profile_separation_policies.h"
 #import "components/prefs/pref_service.h"
 #import "components/signin/public/base/signin_metrics.h"
 #import "components/signin/public/base/signin_pref_names.h"
@@ -186,4 +187,11 @@
       ->KeepAccountSettingsPrefsOnlyForUsers({});
 }
 
+void SetPolicyResponseForNextProfileSeparationPolicyRequest(
+    policy::ProfileSeparationDataMigrationSettings
+        profileSeparationDataMigrationSettings) {
+  [AuthenticationFlow forcePolicyResponseForNextRequestForTesting:
+                          profileSeparationDataMigrationSettings];
+}
+
 }  // namespace chrome_test_util
diff --git a/ios/chrome/test/data/policy/policy_test_bundle_data.filelist b/ios/chrome/test/data/policy/policy_test_bundle_data.filelist
index 24da73f5..0171807 100644
--- a/ios/chrome/test/data/policy/policy_test_bundle_data.filelist
+++ b/ios/chrome/test/data/policy/policy_test_bundle_data.filelist
@@ -55,7 +55,6 @@
 //ios/chrome/test/data/policy/pref_mapping/NewTabPageLocation.json
 //ios/chrome/test/data/policy/pref_mapping/OnSecurityEventEnterpriseConnector.json
 //ios/chrome/test/data/policy/pref_mapping/OptimizationGuideFetchingEnabled.json
-//ios/chrome/test/data/policy/pref_mapping/ParcelTrackingEnabled.json
 //ios/chrome/test/data/policy/pref_mapping/PasswordManagerEnabled.json
 //ios/chrome/test/data/policy/pref_mapping/PasswordManagerPasskeysEnabled.json
 //ios/chrome/test/data/policy/pref_mapping/PasswordSharingEnabled.json
diff --git a/ios/chrome/test/data/policy/pref_mapping/ParcelTrackingEnabled.json b/ios/chrome/test/data/policy/pref_mapping/ParcelTrackingEnabled.json
deleted file mode 100644
index 95503d46..0000000
--- a/ios/chrome/test/data/policy/pref_mapping/ParcelTrackingEnabled.json
+++ /dev/null
@@ -1,40 +0,0 @@
-[
-  {
-    "os": [
-      "ios"
-    ],
-    "policy_pref_mapping_tests": [
-      {
-        "note": "Default value (no policies set).",
-        "prefs": {
-          "ios.parcel_tracking.policy_enabled": {
-            "default_value": true,
-            "location": "local_state"
-          }
-        }
-      },
-      {
-        "policies": {
-          "ParcelTrackingEnabled": false
-        },
-        "prefs": {
-          "ios.parcel_tracking.policy_enabled": {
-            "location": "local_state",
-            "value": false
-          }
-        }
-      },
-      {
-        "policies": {
-          "ParcelTrackingEnabled": true
-        },
-        "prefs": {
-          "ios.parcel_tracking.policy_enabled": {
-            "location": "local_state",
-            "value": true
-          }
-        }
-      }
-    ]
-  }
-]
diff --git a/ios_internal b/ios_internal
index 6b4d9a3..551c00d 160000
--- a/ios_internal
+++ b/ios_internal
@@ -1 +1 @@
-Subproject commit 6b4d9a3f084c9b10ac4b7bacba54919bab6fe372
+Subproject commit 551c00da20474aeae5bd8e6356609f870e0acbc6
diff --git a/media/audio/win/core_audio_util_win.cc b/media/audio/win/core_audio_util_win.cc
index 1a35c7f..2086469 100644
--- a/media/audio/win/core_audio_util_win.cc
+++ b/media/audio/win/core_audio_util_win.cc
@@ -5,9 +5,9 @@
 #include "media/audio/win/core_audio_util_win.h"
 
 #include <objbase.h>
-
 #include <comdef.h>
-#include <devicetopology.h>
+#include <initguid.h>  // It should be before `devpkey.h`
+#include <devpkey.h>
 #include <functiondiscoverykeys_devpkey.h>
 #include <stddef.h>
 #include <stdint.h>
@@ -22,12 +22,14 @@
 #include "base/metrics/histogram_functions.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/stringprintf.h"
+#include "base/strings/sys_string_conversions.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/trace_event/trace_event.h"
 #include "base/win/scoped_co_mem.h"
 #include "base/win/scoped_handle.h"
 #include "base/win/scoped_propvariant.h"
 #include "base/win/scoped_variant.h"
+#include "base/win/win_util.h"
 #include "base/win/windows_version.h"
 #include "media/audio/audio_device_description.h"
 #include "media/base/audio_timestamp_helper.h"
@@ -772,49 +774,23 @@
   return hr;
 }
 
-std::string CoreAudioUtil::GetAudioControllerID(IMMDevice* device,
-    IMMDeviceEnumerator* enumerator) {
-  // Fetching the controller device id could be as simple as fetching the value
-  // of the "{B3F8FA53-0004-438E-9003-51A46E139BFC},2" property in the property
-  // store of the |device|, but that key isn't defined in any header and
-  // according to MS should not be relied upon.
-  // So, instead, we go deeper, look at the device topology and fetch the
-  // PKEY_Device_InstanceId of the associated physical audio device.
-  ComPtr<IDeviceTopology> topology;
-  ComPtr<IConnector> connector;
-  ScopedCoMem<WCHAR> filter_id;
-  if (FAILED(device->Activate(__uuidof(IDeviceTopology), CLSCTX_ALL, NULL,
-                              &topology)) ||
-      // For our purposes checking the first connected device should be enough
-      // and if there are cases where there are more than one device connected
-      // we're not sure how to handle that anyway. So we pass 0.
-      FAILED(topology->GetConnector(0, &connector)) ||
-      FAILED(connector->GetDeviceIdConnectedTo(&filter_id))) {
-    DLOG(ERROR) << "Failed to get the device identifier of the audio device";
-    return std::string();
-  }
-
-  // Now look at the properties of the connected device node and fetch the
-  // instance id (PKEY_Device_InstanceId) of the device node that uniquely
-  // identifies the controller.
-  ComPtr<IMMDevice> device_node;
+std::string CoreAudioUtil::GetAudioControllerID(IMMDevice* device) {
+  // Windows uses device container to represent the same physical device
+  // regardless of the number of devices nodes. The container id is a GUID
+  // and can be retrieved by using the `DEVPKEY_Device_ContainerId`.
+  // https://learn.microsoft.com/en-us/windows-hardware/drivers/install/devpkey-device-containerid.
   ComPtr<IPropertyStore> properties;
   base::win::ScopedPropVariant instance_id;
-  if (FAILED(enumerator->GetDevice(filter_id, &device_node)) ||
-      FAILED(device_node->OpenPropertyStore(STGM_READ, &properties)) ||
-      FAILED(properties->GetValue(PKEY_Device_InstanceId,
+  if (FAILED(device->OpenPropertyStore(STGM_READ, &properties)) ||
+      FAILED(properties->GetValue((REFPROPERTYKEY)DEVPKEY_Device_ContainerId,
                                   instance_id.Receive())) ||
-      instance_id.get().vt != VT_LPWSTR) {
+      instance_id.get().vt != VT_CLSID) {
     DLOG(ERROR) << "Failed to get instance id of the audio device node";
     return std::string();
   }
 
-  std::string controller_id;
-  base::WideToUTF8(instance_id.get().pwszVal,
-                   UNSAFE_TODO(wcslen(instance_id.get().pwszVal)),
-                   &controller_id);
-
-  return controller_id;
+  return base::SysWideToUTF8(
+      base::win::WStringFromGUID(*instance_id.get().puuid));
 }
 
 std::string CoreAudioUtil::GetMatchingOutputDeviceID(
@@ -838,14 +814,13 @@
     return std::string();
 
   // See if we can get id of the associated controller.
-  ComPtr<IMMDeviceEnumerator> enumerator(CreateDeviceEnumerator());
-  std::string controller_id(
-      GetAudioControllerID(input_device.Get(), enumerator.Get()));
+  std::string controller_id(GetAudioControllerID(input_device.Get()));
   if (controller_id.empty())
     return std::string();
 
   // Now enumerate the available (and active) output devices and see if any of
   // them is associated with the same controller.
+  ComPtr<IMMDeviceEnumerator> enumerator(CreateDeviceEnumerator());
   ComPtr<IMMDeviceCollection> collection;
   enumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &collection);
   if (!collection.Get())
@@ -856,8 +831,7 @@
   ComPtr<IMMDevice> output_device;
   for (UINT i = 0; i < count; ++i) {
     collection->Item(i, &output_device);
-    std::string output_controller_id(
-        GetAudioControllerID(output_device.Get(), enumerator.Get()));
+    std::string output_controller_id(GetAudioControllerID(output_device.Get()));
     if (output_controller_id == controller_id)
       break;
     output_device = nullptr;
diff --git a/media/audio/win/core_audio_util_win.h b/media/audio/win/core_audio_util_win.h
index aedd532e..fa64a688 100644
--- a/media/audio/win/core_audio_util_win.h
+++ b/media/audio/win/core_audio_util_win.h
@@ -120,13 +120,7 @@
   // |device| is connected to.  This ID will be the same for all devices from
   // the same controller so it is useful for doing things like determining
   // whether a set of output and input devices belong to the same controller.
-  // The device enumerator is required as well as the device itself since
-  // looking at the device topology is required and we need to open up
-  // associated devices to determine the controller id.
-  // If the ID could not be determined for some reason, an empty string is
-  // returned.
-  static std::string GetAudioControllerID(IMMDevice* device,
-      IMMDeviceEnumerator* enumerator);
+  static std::string GetAudioControllerID(IMMDevice* device);
 
   // Accepts an id of an input device and finds a matching output device id.
   // If the associated hardware does not have an audio output device (e.g.
diff --git a/media/audio/win/core_audio_util_win_unittest.cc b/media/audio/win/core_audio_util_win_unittest.cc
index 44e8bfd..74b3c3b71 100644
--- a/media/audio/win/core_audio_util_win_unittest.cc
+++ b/media/audio/win/core_audio_util_win_unittest.cc
@@ -245,7 +245,7 @@
       ComPtr<IMMDevice> device;
       collection->Item(j, &device);
       std::string controller_id(
-          CoreAudioUtil::GetAudioControllerID(device.Get(), enumerator.Get()));
+          CoreAudioUtil::GetAudioControllerID(device.Get()));
       EXPECT_FALSE(controller_id.empty());
     }
   }
diff --git a/media/audio/win/device_enumeration_win.cc b/media/audio/win/device_enumeration_win.cc
index e2f35a5a..e1f36efe 100644
--- a/media/audio/win/device_enumeration_win.cc
+++ b/media/audio/win/device_enumeration_win.cc
@@ -92,8 +92,8 @@
       }
 
       // Append suffix to USB and Bluetooth devices.
-      std::string controller_id = CoreAudioUtil::GetAudioControllerID(
-          audio_device.Get(), enumerator.Get());
+      std::string controller_id =
+          CoreAudioUtil::GetAudioControllerID(audio_device.Get());
       std::string suffix = GetDeviceSuffixWin(controller_id);
       if (!suffix.empty())
         device.device_name += suffix;
diff --git a/media/capture/video/video_capture_device_client.cc b/media/capture/video/video_capture_device_client.cc
index f22d049..8252ec7 100644
--- a/media/capture/video/video_capture_device_client.cc
+++ b/media/capture/video/video_capture_device_client.cc
@@ -192,6 +192,10 @@
     case media::PIXEL_FORMAT_ARGB:
       // Windows platforms e.g. send the data vertically flipped sometimes.
       return {libyuv::FOURCC_ARGB, flip_y};
+    case media::PIXEL_FORMAT_ABGR:
+      return {libyuv::FOURCC_ABGR};
+    case media::PIXEL_FORMAT_BGRA:
+      return {libyuv::FOURCC_BGRA};
     case media::PIXEL_FORMAT_MJPEG:
       return {libyuv::FOURCC_MJPG};
     default:
diff --git a/media/cdm/aes_decryptor.cc b/media/cdm/aes_decryptor.cc
index 5a7b285..a928e89 100644
--- a/media/cdm/aes_decryptor.cc
+++ b/media/cdm/aes_decryptor.cc
@@ -152,7 +152,7 @@
 
 void AesDecryptor::SessionIdDecryptionKeyMap::Erase(
     KeyList::iterator position) {
-  DCHECK(!position->second.empty());
+  CHECK(!position->second.empty(), base::NotFatalUntil::M140);
   key_list_.erase(position);
 }
 
@@ -182,9 +182,9 @@
       session_closed_cb_(session_closed_cb),
       session_keys_change_cb_(session_keys_change_cb) {
   DVLOG(1) << __func__;
-  DCHECK(session_message_cb_);
-  DCHECK(session_closed_cb_);
-  DCHECK(session_keys_change_cb_);
+  CHECK(session_message_cb_, base::NotFatalUntil::M140);
+  CHECK(session_closed_cb_, base::NotFatalUntil::M140);
+  CHECK(session_keys_change_cb_, base::NotFatalUntil::M140);
 }
 
 AesDecryptor::~AesDecryptor() {
@@ -213,7 +213,8 @@
     std::unique_ptr<NewSessionCdmPromise> promise) {
   std::string session_id = GenerateSessionId();
   bool session_added = CreateSession(session_id, session_type);
-  DCHECK(session_added) << "Failed to add new session " << session_id;
+  CHECK(session_added, base::NotFatalUntil::M140)
+      << "Failed to add new session " << session_id;
 
   std::vector<uint8_t> message;
   std::vector<std::vector<uint8_t>> keys;
diff --git a/media/cdm/cbcs_decryptor.cc b/media/cdm/cbcs_decryptor.cc
index d1605dcd..ab07120 100644
--- a/media/cdm/cbcs_decryptor.cc
+++ b/media/cdm/cbcs_decryptor.cc
@@ -128,13 +128,14 @@
 scoped_refptr<DecoderBuffer> DecryptCbcsBuffer(const DecoderBuffer& input,
                                                base::span<const uint8_t> key) {
   const size_t sample_size = input.size();
-  DCHECK(sample_size) << "No data to decrypt.";
+  CHECK(sample_size, base::NotFatalUntil::M140) << "No data to decrypt.";
 
   const DecryptConfig* decrypt_config = input.decrypt_config();
-  DCHECK(decrypt_config) << "No need to call Decrypt() on unencrypted buffer.";
+  CHECK(decrypt_config, base::NotFatalUntil::M140)
+      << "No need to call Decrypt() on unencrypted buffer.";
   DCHECK_EQ(EncryptionScheme::kCbcs, decrypt_config->encryption_scheme());
 
-  DCHECK(decrypt_config->HasPattern());
+  CHECK(decrypt_config->HasPattern(), base::NotFatalUntil::M140);
   const EncryptionPattern pattern =
       decrypt_config->encryption_pattern().value();
 
diff --git a/media/cdm/cdm_adapter.cc b/media/cdm/cdm_adapter.cc
index 49f14a39..4f1f9f3b 100644
--- a/media/cdm/cdm_adapter.cc
+++ b/media/cdm/cdm_adapter.cc
@@ -24,6 +24,7 @@
 #include "base/memory/scoped_refptr.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
+#include "base/not_fatal_until.h"
 #include "base/numerics/clamped_math.h"
 #include "base/numerics/safe_conversions.h"
 #include "base/strings/string_number_conversions.h"
@@ -136,7 +137,8 @@
                                     cdm::Host_12::kVersion),
       "Mismatch between GetCdmHost() and IsSupportedCdmHostVersion()");
 
-  DCHECK(IsSupportedCdmHostVersion(host_interface_version));
+  CHECK(IsSupportedCdmHostVersion(host_interface_version),
+        base::NotFatalUntil::M140);
 
   CdmAdapter* cdm_adapter = static_cast<CdmAdapter*>(user_data);
   DVLOG(1) << "Create CDM Host with version " << host_interface_version;
@@ -214,11 +216,11 @@
     const SessionExpirationUpdateCB& session_expiration_update_cb,
     CdmCreatedCB cdm_created_cb,
     const bool is_debugger_attached) {
-  DCHECK(!cdm_config.key_system.empty());
-  DCHECK(session_message_cb);
-  DCHECK(session_closed_cb);
-  DCHECK(session_keys_change_cb);
-  DCHECK(session_expiration_update_cb);
+  CHECK(!cdm_config.key_system.empty(), base::NotFatalUntil::M140);
+  CHECK(session_message_cb, base::NotFatalUntil::M140);
+  CHECK(session_closed_cb, base::NotFatalUntil::M140);
+  CHECK(session_keys_change_cb, base::NotFatalUntil::M140);
+  CHECK(session_expiration_update_cb, base::NotFatalUntil::M140);
 
   auto cdm = base::MakeRefCounted<CdmAdapter>(
       base::PassKey<CdmAdapter>(), cdm_config, create_cdm_func,
@@ -254,13 +256,13 @@
       pool_(base::MakeRefCounted<AudioBufferMemoryPool>()) {
   DVLOG(1) << __func__;
 
-  DCHECK(!cdm_config.key_system.empty());
-  DCHECK(create_cdm_func_);
-  DCHECK(helper_);
-  DCHECK(session_message_cb_);
-  DCHECK(session_closed_cb_);
-  DCHECK(session_keys_change_cb_);
-  DCHECK(session_expiration_update_cb_);
+  CHECK(!cdm_config.key_system.empty(), base::NotFatalUntil::M140);
+  CHECK(create_cdm_func_, base::NotFatalUntil::M140);
+  CHECK(helper_, base::NotFatalUntil::M140);
+  CHECK(session_message_cb_, base::NotFatalUntil::M140);
+  CHECK(session_closed_cb_, base::NotFatalUntil::M140);
+  CHECK(session_keys_change_cb_, base::NotFatalUntil::M140);
+  CHECK(session_expiration_update_cb_, base::NotFatalUntil::M140);
 
   if (is_debugger_attached) {
     SCOPED_CRASH_KEY_BOOL("CDMUtilityProcess", "Debugger_attached",
@@ -294,7 +296,7 @@
 }
 
 CdmWrapper* CdmAdapter::CreateCdmInstance(const std::string& key_system) {
-  DCHECK(task_runner_->BelongsToCurrentThread());
+  CHECK(task_runner_->BelongsToCurrentThread(), base::NotFatalUntil::M140);
   TRACE_EVENT0("media", "CdmAdapter::CreateCdmInstance");
 
   CdmWrapper* cdm = CdmWrapper::Create(create_cdm_func_, key_system.data(),
@@ -304,8 +306,8 @@
 
   if (cdm) {
     // The interface version is relatively small. So using normal histogram
-    // instead of a sparse histogram is okay. The following DCHECK asserts this.
-    DCHECK(cdm->GetInterfaceVersion() <= 30);
+    // instead of a sparse histogram is okay. The following CHECK asserts this.
+    CHECK(cdm->GetInterfaceVersion() <= 30, base::NotFatalUntil::M140);
     UMA_HISTOGRAM_ENUMERATION("Media.EME.CdmInterfaceVersion",
                               cdm->GetInterfaceVersion(), 30);
   }
@@ -347,7 +349,7 @@
     const std::vector<uint8_t>& certificate,
     std::unique_ptr<SimpleCdmPromise> promise) {
   DVLOG(2) << __func__;
-  DCHECK(task_runner_->BelongsToCurrentThread());
+  CHECK(task_runner_->BelongsToCurrentThread(), base::NotFatalUntil::M140);
   TRACE_EVENT0("media", "CdmAdapter::SetServerCertificate");
 
   if (certificate.size() < limits::kMinCertificateLength ||
@@ -366,7 +368,7 @@
 void CdmAdapter::GetStatusForPolicy(
     HdcpVersion min_hdcp_version,
     std::unique_ptr<KeyStatusCdmPromise> promise) {
-  DCHECK(task_runner_->BelongsToCurrentThread());
+  CHECK(task_runner_->BelongsToCurrentThread(), base::NotFatalUntil::M140);
   TRACE_EVENT0("media", "CdmAdapter::GetStatusForPolicy");
 
   uint32_t promise_id =
@@ -386,7 +388,7 @@
     EmeInitDataType init_data_type,
     const std::vector<uint8_t>& init_data,
     std::unique_ptr<NewSessionCdmPromise> promise) {
-  DCHECK(task_runner_->BelongsToCurrentThread());
+  CHECK(task_runner_->BelongsToCurrentThread(), base::NotFatalUntil::M140);
   TRACE_EVENT0("media", "CdmAdapter::CreateSessionAndGenerateRequest");
 
   uint32_t promise_id =
@@ -401,7 +403,7 @@
 void CdmAdapter::LoadSession(CdmSessionType session_type,
                              const std::string& session_id,
                              std::unique_ptr<NewSessionCdmPromise> promise) {
-  DCHECK(task_runner_->BelongsToCurrentThread());
+  CHECK(task_runner_->BelongsToCurrentThread(), base::NotFatalUntil::M140);
   TRACE_EVENT1("media", "CdmAdapter::LoadSession", "session_id", session_id);
 
   uint32_t promise_id =
@@ -416,9 +418,9 @@
 void CdmAdapter::UpdateSession(const std::string& session_id,
                                const std::vector<uint8_t>& response,
                                std::unique_ptr<SimpleCdmPromise> promise) {
-  DCHECK(task_runner_->BelongsToCurrentThread());
-  DCHECK(!session_id.empty());
-  DCHECK(!response.empty());
+  CHECK(task_runner_->BelongsToCurrentThread(), base::NotFatalUntil::M140);
+  CHECK(!session_id.empty(), base::NotFatalUntil::M140);
+  CHECK(!response.empty(), base::NotFatalUntil::M140);
   TRACE_EVENT1("media", "CdmAdapter::UpdateSession", "session_id", session_id);
 
   cdm_metrics_data_.number_of_update_calls++;
@@ -434,8 +436,8 @@
 
 void CdmAdapter::CloseSession(const std::string& session_id,
                               std::unique_ptr<SimpleCdmPromise> promise) {
-  DCHECK(task_runner_->BelongsToCurrentThread());
-  DCHECK(!session_id.empty());
+  CHECK(task_runner_->BelongsToCurrentThread(), base::NotFatalUntil::M140);
+  CHECK(!session_id.empty(), base::NotFatalUntil::M140);
   TRACE_EVENT1("media", "CdmAdapter::CloseSession", "session_id", session_id);
 
   uint32_t promise_id =
@@ -448,8 +450,8 @@
 
 void CdmAdapter::RemoveSession(const std::string& session_id,
                                std::unique_ptr<SimpleCdmPromise> promise) {
-  DCHECK(task_runner_->BelongsToCurrentThread());
-  DCHECK(!session_id.empty());
+  CHECK(task_runner_->BelongsToCurrentThread(), base::NotFatalUntil::M140);
+  CHECK(!session_id.empty(), base::NotFatalUntil::M140);
   TRACE_EVENT1("media", "CdmAdapter::RemoveSession", "session_id", session_id);
 
   uint32_t promise_id =
@@ -461,7 +463,7 @@
 }
 
 CdmContext* CdmAdapter::GetCdmContext() {
-  DCHECK(task_runner_->BelongsToCurrentThread());
+  CHECK(task_runner_->BelongsToCurrentThread(), base::NotFatalUntil::M140);
   return this;
 }
 
@@ -471,12 +473,12 @@
 }
 
 Decryptor* CdmAdapter::GetDecryptor() {
-  DCHECK(task_runner_->BelongsToCurrentThread());
+  CHECK(task_runner_->BelongsToCurrentThread(), base::NotFatalUntil::M140);
   return this;
 }
 
 std::optional<base::UnguessableToken> CdmAdapter::GetCdmId() const {
-  DCHECK(task_runner_->BelongsToCurrentThread());
+  CHECK(task_runner_->BelongsToCurrentThread(), base::NotFatalUntil::M140);
   return std::nullopt;
 }
 
@@ -485,7 +487,7 @@
                          DecryptCB decrypt_cb) {
   DVLOG(3) << __func__ << ": "
            << encrypted->AsHumanReadableString(/*verbose=*/true);
-  DCHECK(task_runner_->BelongsToCurrentThread());
+  CHECK(task_runner_->BelongsToCurrentThread(), base::NotFatalUntil::M140);
 
   cdm::InputBuffer_2 input_buffer = {};
   std::vector<cdm::SubsampleEntry> subsamples;
@@ -516,14 +518,14 @@
 
 void CdmAdapter::CancelDecrypt(StreamType stream_type) {
   // As the Decrypt methods are synchronous, nothing can be done here.
-  DCHECK(task_runner_->BelongsToCurrentThread());
+  CHECK(task_runner_->BelongsToCurrentThread(), base::NotFatalUntil::M140);
 }
 
 void CdmAdapter::InitializeAudioDecoder(const AudioDecoderConfig& config,
                                         DecoderInitCB init_cb) {
   DVLOG(2) << __func__ << ": " << config.AsHumanReadableString();
-  DCHECK(task_runner_->BelongsToCurrentThread());
-  DCHECK(!audio_init_cb_);
+  CHECK(task_runner_->BelongsToCurrentThread(), base::NotFatalUntil::M140);
+  CHECK(!audio_init_cb_, base::NotFatalUntil::M140);
   TRACE_EVENT0("media", "CdmAdapter::InitializeAudioDecode");
 
   auto cdm_config = ToCdmAudioDecoderConfig(config);
@@ -536,7 +538,7 @@
 
   cdm::Status status = cdm_->InitializeAudioDecoder(cdm_config);
   if (status != cdm::kSuccess && status != cdm::kDeferredInitialization) {
-    DCHECK(status == cdm::kInitializationError);
+    CHECK(status == cdm::kInitializationError, base::NotFatalUntil::M140);
     DVLOG(1) << __func__ << ": status = " << status;
     std::move(init_cb).Run(false);
     return;
@@ -557,8 +559,8 @@
 void CdmAdapter::InitializeVideoDecoder(const VideoDecoderConfig& config,
                                         DecoderInitCB init_cb) {
   DVLOG(2) << __func__ << ": " << config.AsHumanReadableString();
-  DCHECK(task_runner_->BelongsToCurrentThread());
-  DCHECK(!video_init_cb_);
+  CHECK(task_runner_->BelongsToCurrentThread(), base::NotFatalUntil::M140);
+  CHECK(!video_init_cb_, base::NotFatalUntil::M140);
   TRACE_EVENT0("media", "CdmAdapter::InitializeVideoDecoder");
 
   // Alpha decoding is not supported by the CDM.
@@ -582,7 +584,7 @@
 
   cdm::Status status = cdm_->InitializeVideoDecoder(cdm_config);
   if (status != cdm::kSuccess && status != cdm::kDeferredInitialization) {
-    DCHECK(status == cdm::kInitializationError);
+    CHECK(status == cdm::kInitializationError, base::NotFatalUntil::M140);
     DVLOG(1) << __func__ << ": status = " << status;
     std::move(init_cb).Run(false);
     return;
@@ -605,7 +607,7 @@
                                        AudioDecodeCB audio_decode_cb) {
   DVLOG(3) << __func__ << ": "
            << encrypted->AsHumanReadableString(/*verbose=*/true);
-  DCHECK(task_runner_->BelongsToCurrentThread());
+  CHECK(task_runner_->BelongsToCurrentThread(), base::NotFatalUntil::M140);
 
   cdm::InputBuffer_2 input_buffer = {};
   std::vector<cdm::SubsampleEntry> subsamples;
@@ -628,7 +630,7 @@
   }
 
   Decryptor::AudioFrames audio_frame_list;
-  DCHECK(audio_frames->FrameBuffer());
+  CHECK(audio_frames->FrameBuffer(), base::NotFatalUntil::M140);
   if (!AudioFramesDataToAudioFrames(std::move(audio_frames),
                                     &audio_frame_list)) {
     DVLOG(1) << __func__ << " unable to convert Audio Frames";
@@ -643,7 +645,7 @@
                                        VideoDecodeCB video_decode_cb) {
   DVLOG(3) << __func__ << ": "
            << encrypted->AsHumanReadableString(/*verbose=*/true);
-  DCHECK(task_runner_->BelongsToCurrentThread());
+  CHECK(task_runner_->BelongsToCurrentThread(), base::NotFatalUntil::M140);
 
   cdm::InputBuffer_2 input_buffer = {};
   std::vector<cdm::SubsampleEntry> subsamples;
@@ -686,7 +688,7 @@
 
 void CdmAdapter::ResetDecoder(StreamType stream_type) {
   DVLOG(2) << __func__ << ": stream_type = " << stream_type;
-  DCHECK(task_runner_->BelongsToCurrentThread());
+  CHECK(task_runner_->BelongsToCurrentThread(), base::NotFatalUntil::M140);
   TRACE_EVENT1("media", "CdmAdapter::ResetDecoder", "stream_type", stream_type);
 
   cdm_->ResetDecoder(ToCdmStreamType(stream_type));
@@ -694,7 +696,7 @@
 
 void CdmAdapter::DeinitializeDecoder(StreamType stream_type) {
   DVLOG(2) << __func__ << ": stream_type = " << stream_type;
-  DCHECK(task_runner_->BelongsToCurrentThread());
+  CHECK(task_runner_->BelongsToCurrentThread(), base::NotFatalUntil::M140);
   TRACE_EVENT1("media", "CdmAdapter::DeinitializeDecoder", "stream_type",
                stream_type);
 
@@ -714,14 +716,14 @@
 
 cdm::Buffer* CdmAdapter::Allocate(uint32_t capacity) {
   DVLOG(3) << __func__ << ": capacity = " << capacity;
-  DCHECK(task_runner_->BelongsToCurrentThread());
+  CHECK(task_runner_->BelongsToCurrentThread(), base::NotFatalUntil::M140);
   TRACE_EVENT1("media", "CdmAdapter::Allocate", "capacity", capacity);
 
   return helper_->CreateCdmBuffer(capacity);
 }
 
 void CdmAdapter::SetTimer(int64_t delay_ms, void* context) {
-  DCHECK(task_runner_->BelongsToCurrentThread());
+  CHECK(task_runner_->BelongsToCurrentThread(), base::NotFatalUntil::M140);
 
   auto delay = base::Milliseconds(delay_ms);
   DVLOG(3) << __func__ << ": delay = " << delay << ", context = " << context;
@@ -737,20 +739,20 @@
 
 void CdmAdapter::TimerExpired(void* context) {
   DVLOG(3) << __func__ << ": context = " << context;
-  DCHECK(task_runner_->BelongsToCurrentThread());
+  CHECK(task_runner_->BelongsToCurrentThread(), base::NotFatalUntil::M140);
   TRACE_EVENT1("media", "CdmAdapter::TimerExpired", "context", context);
 
   cdm_->TimerExpired(context);
 }
 
 cdm::Time CdmAdapter::GetCurrentWallTime() {
-  DCHECK(task_runner_->BelongsToCurrentThread());
+  CHECK(task_runner_->BelongsToCurrentThread(), base::NotFatalUntil::M140);
   return base::Time::Now().InSecondsFSinceUnixEpoch();
 }
 
 void CdmAdapter::OnInitialized(bool success) {
   DVLOG(3) << __func__ << ": success = " << success;
-  DCHECK(task_runner_->BelongsToCurrentThread());
+  CHECK(task_runner_->BelongsToCurrentThread(), base::NotFatalUntil::M140);
   DCHECK_NE(init_promise_id_, CdmPromiseAdapter::kInvalidPromiseId);
 
   if (!success) {
@@ -768,13 +770,13 @@
                                            cdm::KeyStatus key_status) {
   DVLOG(2) << __func__ << ": promise_id = " << promise_id
            << ", key_status = " << key_status;
-  DCHECK(task_runner_->BelongsToCurrentThread());
+  CHECK(task_runner_->BelongsToCurrentThread(), base::NotFatalUntil::M140);
   cdm_promise_adapter_.ResolvePromise(promise_id, ToMediaKeyStatus(key_status));
 }
 
 void CdmAdapter::OnResolvePromise(uint32_t promise_id) {
   DVLOG(2) << __func__ << ": promise_id = " << promise_id;
-  DCHECK(task_runner_->BelongsToCurrentThread());
+  CHECK(task_runner_->BelongsToCurrentThread(), base::NotFatalUntil::M140);
   cdm_promise_adapter_.ResolvePromise(promise_id);
 }
 
@@ -782,7 +784,7 @@
                                             const char* session_id,
                                             uint32_t session_id_size) {
   DVLOG(2) << __func__ << ": promise_id = " << promise_id;
-  DCHECK(task_runner_->BelongsToCurrentThread());
+  CHECK(task_runner_->BelongsToCurrentThread(), base::NotFatalUntil::M140);
   cdm_promise_adapter_.ResolvePromise(promise_id,
                                       std::string(session_id, session_id_size));
 }
@@ -809,7 +811,7 @@
                                 kSizeKBBuckets);
   }
 
-  DCHECK(task_runner_->BelongsToCurrentThread());
+  CHECK(task_runner_->BelongsToCurrentThread(), base::NotFatalUntil::M140);
   cdm_promise_adapter_.RejectPromise(promise_id,
                                      ToMediaCdmPromiseException(exception),
                                      system_code, error_message_str);
@@ -822,7 +824,7 @@
                                   uint32_t message_size) {
   std::string session_id_str(session_id, session_id_size);
   DVLOG(2) << __func__ << ": session_id = " << session_id_str;
-  DCHECK(task_runner_->BelongsToCurrentThread());
+  CHECK(task_runner_->BelongsToCurrentThread(), base::NotFatalUntil::M140);
 
   cdm_metrics_data_.number_of_on_message_events++;
 
@@ -842,7 +844,7 @@
                                      uint32_t keys_info_count) {
   std::string session_id_str(session_id, session_id_size);
   DVLOG(2) << __func__ << ": session_id = " << session_id_str;
-  DCHECK(task_runner_->BelongsToCurrentThread());
+  CHECK(task_runner_->BelongsToCurrentThread(), base::NotFatalUntil::M140);
 
   TRACE_EVENT2("media", "CdmAdapter::OnSessionKeysChange", "session_id",
                session_id_str, "has_additional_usable_key",
@@ -870,7 +872,7 @@
   std::string session_id_str(session_id, session_id_size);
   DVLOG(2) << __func__ << ": session_id = " << session_id_str
            << ", new_expiry_time = " << new_expiry_time;
-  DCHECK(task_runner_->BelongsToCurrentThread());
+  CHECK(task_runner_->BelongsToCurrentThread(), base::NotFatalUntil::M140);
 
   base::Time expiration =
       base::Time::FromSecondsSinceUnixEpoch(new_expiry_time);
@@ -881,7 +883,7 @@
 
 void CdmAdapter::OnSessionClosed(const char* session_id,
                                  uint32_t session_id_size) {
-  DCHECK(task_runner_->BelongsToCurrentThread());
+  CHECK(task_runner_->BelongsToCurrentThread(), base::NotFatalUntil::M140);
 
   std::string session_id_str(session_id, session_id_size);
   TRACE_EVENT1("media", "CdmAdapter::OnSessionClosed", "session_id",
@@ -894,7 +896,7 @@
                                        uint32_t service_id_size,
                                        const char* challenge,
                                        uint32_t challenge_size) {
-  DCHECK(task_runner_->BelongsToCurrentThread());
+  CHECK(task_runner_->BelongsToCurrentThread(), base::NotFatalUntil::M140);
 
   if (!cdm_config_.allow_distinctive_identifier) {
     task_runner_->PostTask(
@@ -940,7 +942,7 @@
 
 void CdmAdapter::EnableOutputProtection(uint32_t desired_protection_mask) {
   DVLOG(1) << __func__;
-  DCHECK(task_runner_->BelongsToCurrentThread());
+  CHECK(task_runner_->BelongsToCurrentThread(), base::NotFatalUntil::M140);
   TRACE_EVENT1("media", "CdmAdapter::EnableOutputProtection",
                "desired_protection_mask", GetHexMask(desired_protection_mask));
 
@@ -959,7 +961,7 @@
 }
 
 void CdmAdapter::QueryOutputProtectionStatus() {
-  DCHECK(task_runner_->BelongsToCurrentThread());
+  CHECK(task_runner_->BelongsToCurrentThread(), base::NotFatalUntil::M140);
   TRACE_EVENT0("media", "CdmAdapter::QueryOutputProtectionStatus");
 
   ReportOutputProtectionQuery();
@@ -1004,7 +1006,7 @@
 
 void CdmAdapter::ReportOutputProtectionQueryResult(uint32_t link_mask,
                                                    uint32_t protection_mask) {
-  DCHECK(uma_for_output_protection_query_reported_);
+  CHECK(uma_for_output_protection_query_reported_, base::NotFatalUntil::M140);
 
   if (uma_for_output_protection_positive_result_reported_)
     return;
@@ -1043,7 +1045,7 @@
                                               cdm::Status decoder_status) {
   DVLOG(1) << __func__ << ": stream_type = " << stream_type
            << ", decoder_status = " << decoder_status;
-  DCHECK(task_runner_->BelongsToCurrentThread());
+  CHECK(task_runner_->BelongsToCurrentThread(), base::NotFatalUntil::M140);
 
   switch (stream_type) {
     case cdm::kStreamTypeAudio:
@@ -1059,7 +1061,7 @@
 
 cdm::FileIO* CdmAdapter::CreateFileIO(cdm::FileIOClient* client) {
   DVLOG(3) << __func__;
-  DCHECK(task_runner_->BelongsToCurrentThread());
+  CHECK(task_runner_->BelongsToCurrentThread(), base::NotFatalUntil::M140);
 
   if (!cdm_config_.allow_persistent_state) {
     DVLOG(1) << __func__ << ": Persistent state not allowed.";
@@ -1071,7 +1073,7 @@
 
 void CdmAdapter::RequestStorageId(uint32_t version) {
   DVLOG(2) << __func__ << ": version = " << version;
-  DCHECK(task_runner_->BelongsToCurrentThread());
+  CHECK(task_runner_->BelongsToCurrentThread(), base::NotFatalUntil::M140);
 
   if (!cdm_config_.allow_persistent_state ||
       !(version == kCurrentStorageIdVersion ||
@@ -1110,7 +1112,7 @@
 void CdmAdapter::OnStorageIdObtained(uint32_t version,
                                      const std::vector<uint8_t>& storage_id) {
   DVLOG(2) << __func__ << ": version = " << version;
-  DCHECK(task_runner_->BelongsToCurrentThread());
+  CHECK(task_runner_->BelongsToCurrentThread(), base::NotFatalUntil::M140);
   TRACE_EVENT1("media", "CdmAdapter::OnStorageIdObtained", "version", version);
 
   cdm_->OnStorageId(version, storage_id.data(), storage_id.size());
diff --git a/media/cdm/cdm_adapter_factory.cc b/media/cdm/cdm_adapter_factory.cc
index 0a7c852..0be9430 100644
--- a/media/cdm/cdm_adapter_factory.cc
+++ b/media/cdm/cdm_adapter_factory.cc
@@ -5,6 +5,7 @@
 #include "media/cdm/cdm_adapter_factory.h"
 
 #include "base/functional/bind.h"
+#include "base/not_fatal_until.h"
 #include "base/task/single_thread_task_runner.h"
 #include "media/base/cdm_factory.h"
 #include "media/cdm/cdm_adapter.h"
@@ -15,7 +16,7 @@
 
 CdmAdapterFactory::CdmAdapterFactory(HelperCreationCB helper_creation_cb)
     : helper_creation_cb_(std::move(helper_creation_cb)) {
-  DCHECK(helper_creation_cb_);
+  CHECK(helper_creation_cb_, base::NotFatalUntil::M140);
 }
 
 CdmAdapterFactory::~CdmAdapterFactory() = default;
diff --git a/media/cdm/cdm_context_ref_impl.cc b/media/cdm/cdm_context_ref_impl.cc
index 91fc43cc..9fb7e85 100644
--- a/media/cdm/cdm_context_ref_impl.cc
+++ b/media/cdm/cdm_context_ref_impl.cc
@@ -7,13 +7,14 @@
 #include <ostream>
 
 #include "base/check_op.h"
+#include "base/not_fatal_until.h"
 #include "media/base/content_decryption_module.h"
 
 namespace media {
 
 CdmContextRefImpl::CdmContextRefImpl(scoped_refptr<ContentDecryptionModule> cdm)
     : cdm_(std::move(cdm)) {
-  DCHECK(cdm_);
+  CHECK(cdm_, base::NotFatalUntil::M140);
 }
 
 CdmContextRefImpl::~CdmContextRefImpl() {
diff --git a/media/cdm/cdm_helpers.cc b/media/cdm/cdm_helpers.cc
index 47eeb7e..e044b3a51 100644
--- a/media/cdm/cdm_helpers.cc
+++ b/media/cdm/cdm_helpers.cc
@@ -10,6 +10,7 @@
 #include "media/cdm/cdm_helpers.h"
 
 #include "base/check.h"
+#include "base/not_fatal_until.h"
 #include "ui/gfx/color_space.h"
 
 namespace media {
@@ -99,22 +100,22 @@
 }
 
 void VideoFrameImpl::SetPlaneOffset(cdm::VideoPlane plane, uint32_t offset) {
-  DCHECK(plane < cdm::kMaxPlanes);
+  CHECK(plane < cdm::kMaxPlanes, base::NotFatalUntil::M140);
   plane_offsets_[plane] = offset;
 }
 
 uint32_t VideoFrameImpl::PlaneOffset(cdm::VideoPlane plane) {
-  DCHECK(plane < cdm::kMaxPlanes);
+  CHECK(plane < cdm::kMaxPlanes, base::NotFatalUntil::M140);
   return plane_offsets_[plane];
 }
 
 void VideoFrameImpl::SetStride(cdm::VideoPlane plane, uint32_t stride) {
-  DCHECK(plane < cdm::kMaxPlanes);
+  CHECK(plane < cdm::kMaxPlanes, base::NotFatalUntil::M140);
   strides_[plane] = stride;
 }
 
 uint32_t VideoFrameImpl::Stride(cdm::VideoPlane plane) {
-  DCHECK(plane < cdm::kMaxPlanes);
+  CHECK(plane < cdm::kMaxPlanes, base::NotFatalUntil::M140);
   return strides_[plane];
 }
 
diff --git a/media/cdm/cdm_host_file.cc b/media/cdm/cdm_host_file.cc
index 1eb2ad3..3cc6f61 100644
--- a/media/cdm/cdm_host_file.cc
+++ b/media/cdm/cdm_host_file.cc
@@ -12,6 +12,7 @@
 #include "base/files/file_util.h"
 #include "base/logging.h"
 #include "base/memory/ptr_util.h"
+#include "base/not_fatal_until.h"
 #include "media/cdm/api/content_decryption_module_ext.h"
 
 namespace media {
@@ -57,7 +58,7 @@
     : file_path_(file_path),
       file_(std::move(file)),
       sig_file_(std::move(sig_file)) {
-  DCHECK(!file_path_.empty());
+  CHECK(!file_path_.empty(), base::NotFatalUntil::M140);
 }
 
 }  // namespace media
diff --git a/media/cdm/cdm_host_files.cc b/media/cdm/cdm_host_files.cc
index 86dc248..b2ac51e9 100644
--- a/media/cdm/cdm_host_files.cc
+++ b/media/cdm/cdm_host_files.cc
@@ -14,6 +14,7 @@
 #include "base/logging.h"
 #include "base/memory/ptr_util.h"
 #include "base/native_library.h"
+#include "base/not_fatal_until.h"
 #include "base/path_service.h"
 #include "base/scoped_native_library.h"
 #include "build/build_config.h"
@@ -54,7 +55,7 @@
 CdmHostFiles::Status CdmHostFiles::InitVerification(
     base::NativeLibrary cdm_library) {
   DVLOG(1) << __func__;
-  DCHECK(cdm_library);
+  CHECK(cdm_library, base::NotFatalUntil::M140);
 
   // Get function pointer exported by the CDM.
   // See media/cdm/api/content_decryption_module_ext.h.
@@ -109,7 +110,7 @@
 
 void CdmHostFiles::OpenCommonFiles(
     const std::vector<CdmHostFilePath>& cdm_host_file_paths) {
-  DCHECK(common_files_.empty());
+  CHECK(common_files_.empty(), base::NotFatalUntil::M140);
 
   for (const auto& value : cdm_host_file_paths) {
     common_files_.push_back(
@@ -118,14 +119,14 @@
 }
 
 void CdmHostFiles::OpenCdmFile(const base::FilePath& cdm_path) {
-  DCHECK(!cdm_path.empty());
+  CHECK(!cdm_path.empty(), base::NotFatalUntil::M140);
   cdm_specific_files_.push_back(
       CdmHostFile::Create(cdm_path, GetSigFilePath(cdm_path)));
 }
 
 void CdmHostFiles::TakePlatformFiles(
     std::vector<cdm::HostFile>* cdm_host_files) {
-  DCHECK(cdm_host_files->empty());
+  CHECK(cdm_host_files->empty(), base::NotFatalUntil::M140);
 
   // Populate an array of cdm::HostFile.
   for (const auto& file : common_files_)
diff --git a/media/cdm/cdm_module.cc b/media/cdm/cdm_module.cc
index fcf95d3..7d32009e 100644
--- a/media/cdm/cdm_module.cc
+++ b/media/cdm/cdm_module.cc
@@ -8,6 +8,7 @@
 #include "base/logging.h"
 #include "base/memory/ptr_util.h"
 #include "base/metrics/histogram_macros.h"
+#include "base/not_fatal_until.h"
 #include "base/notreached.h"
 #include "base/time/time.h"
 #include "base/trace_event/trace_event.h"
@@ -45,7 +46,7 @@
     base::NativeLibrary cdm_library,
     const base::FilePath& cdm_path,
     const std::vector<CdmHostFilePath>& cdm_host_file_paths) {
-  DCHECK(cdm_library);
+  CHECK(cdm_library, base::NotFatalUntil::M140);
 
   CdmHostFiles cdm_host_files;
   cdm_host_files.Initialize(cdm_path, cdm_host_file_paths);
@@ -182,8 +183,8 @@
 }
 
 void CdmModule::InitializeCdmModule() {
-  DCHECK(initialized_);
-  DCHECK(initialize_cdm_module_func_);
+  CHECK(initialized_, base::NotFatalUntil::M140);
+  CHECK(initialize_cdm_module_func_, base::NotFatalUntil::M140);
   TRACE_EVENT0("media", "CdmModule::InitializeCdmModule");
   initialize_cdm_module_func_();
 }
diff --git a/media/cdm/cdm_type_conversion.cc b/media/cdm/cdm_type_conversion.cc
index 034addf4..04ed33596 100644
--- a/media/cdm/cdm_type_conversion.cc
+++ b/media/cdm/cdm_type_conversion.cc
@@ -7,6 +7,7 @@
 #include <stdint.h>
 
 #include "base/logging.h"
+#include "base/not_fatal_until.h"
 #include "base/numerics/safe_conversions.h"
 #include "ui/gfx/color_space.h"
 #include "ui/gfx/geometry/size.h"
@@ -555,7 +556,7 @@
                       std::vector<cdm::SubsampleEntry>* subsamples,
                       cdm::InputBuffer_2* input_buffer) {
   // End of stream buffers are represented as empty resources.
-  DCHECK(!input_buffer->data);
+  CHECK(!input_buffer->data, base::NotFatalUntil::M140);
   if (encrypted_buffer.end_of_stream())
     return;
 
@@ -577,7 +578,7 @@
       reinterpret_cast<const uint8_t*>(decrypt_config->iv().data());
   input_buffer->iv_size = decrypt_config->iv().size();
 
-  DCHECK(subsamples->empty());
+  CHECK(subsamples->empty(), base::NotFatalUntil::M140);
   size_t num_subsamples = decrypt_config->subsamples().size();
   if (num_subsamples > 0) {
     subsamples->reserve(num_subsamples);
diff --git a/media/cdm/cdm_wrapper.h b/media/cdm/cdm_wrapper.h
index 72a39f3b..e194d7c 100644
--- a/media/cdm/cdm_wrapper.h
+++ b/media/cdm/cdm_wrapper.h
@@ -10,6 +10,7 @@
 #include "base/check.h"
 #include "base/feature_list.h"
 #include "base/memory/raw_ptr.h"
+#include "base/not_fatal_until.h"
 #include "media/base/media_switches.h"
 #include "media/cdm/api/content_decryption_module.h"
 #include "media/cdm/cdm_helpers.h"
@@ -297,7 +298,9 @@
     void operator()(CdmInterface* cdm) const { cdm->Destroy(); }
   };
 
-  explicit CdmWrapperImpl(CdmInterface* cdm) : cdm_(cdm) { DCHECK(cdm_); }
+  explicit CdmWrapperImpl(CdmInterface* cdm) : cdm_(cdm) {
+    CHECK(cdm_, base::NotFatalUntil::M140);
+  }
 
   std::unique_ptr<CdmInterface, CdmDeleter> cdm_;
 };
diff --git a/media/cdm/cenc_decryptor.cc b/media/cdm/cenc_decryptor.cc
index a16d95cb8..e3f4538 100644
--- a/media/cdm/cenc_decryptor.cc
+++ b/media/cdm/cenc_decryptor.cc
@@ -14,6 +14,7 @@
 #include "base/containers/heap_array.h"
 #include "base/containers/span.h"
 #include "base/logging.h"
+#include "base/not_fatal_until.h"
 #include "crypto/aes_ctr.h"
 #include "media/base/decoder_buffer.h"
 #include "media/base/decrypt_config.h"
@@ -71,10 +72,11 @@
 scoped_refptr<DecoderBuffer> DecryptCencBuffer(const DecoderBuffer& input,
                                                base::span<const uint8_t> key) {
   base::span<const uint8_t> sample = input;
-  DCHECK(!sample.empty()) << "No data to decrypt.";
+  CHECK(!sample.empty(), base::NotFatalUntil::M140) << "No data to decrypt.";
 
   const DecryptConfig* decrypt_config = input.decrypt_config();
-  DCHECK(decrypt_config) << "No need to call Decrypt() on unencrypted buffer.";
+  CHECK(decrypt_config, base::NotFatalUntil::M140)
+      << "No need to call Decrypt() on unencrypted buffer.";
   DCHECK_EQ(EncryptionScheme::kCenc, decrypt_config->encryption_scheme());
 
   if (key.size() != kRequiredKeyBytes) {
diff --git a/media/cdm/cenc_utils.cc b/media/cdm/cenc_utils.cc
index ae44862f..bf9ed73 100644
--- a/media/cdm/cenc_utils.cc
+++ b/media/cdm/cenc_utils.cc
@@ -6,6 +6,7 @@
 
 #include <memory>
 
+#include "base/not_fatal_until.h"
 #include "media/base/media_util.h"
 #include "media/formats/mp4/box_definitions.h"
 #include "media/formats/mp4/box_reader.h"
@@ -30,7 +31,7 @@
 static bool ReadAllPsshBoxes(
     base::span<const uint8_t> input,
     std::vector<mp4::FullProtectionSystemSpecificHeader>* pssh_boxes) {
-  DCHECK(!input.empty());
+  CHECK(!input.empty(), base::NotFatalUntil::M140);
 
   // TODO(wolenetz): Questionable MediaLog usage, http://crbug.com/712310
   NullMediaLog media_log;
diff --git a/media/cdm/fuchsia/fuchsia_cdm.cc b/media/cdm/fuchsia/fuchsia_cdm.cc
index 4cd2888..9b81a95 100644
--- a/media/cdm/fuchsia/fuchsia_cdm.cc
+++ b/media/cdm/fuchsia/fuchsia_cdm.cc
@@ -11,6 +11,7 @@
 #include "base/fuchsia/mem_buffer_util.h"
 #include "base/logging.h"
 #include "base/memory/raw_ptr.h"
+#include "base/not_fatal_until.h"
 #include "media/base/callback_registry.h"
 #include "media/base/cdm_factory.h"
 #include "media/base/cdm_promise.h"
@@ -162,7 +163,7 @@
   void GenerateLicenseRequest(EmeInitDataType init_data_type,
                               const std::vector<uint8_t>& init_data,
                               ResultCB generate_license_request_cb) {
-    DCHECK(!result_cb_);
+    CHECK(!result_cb_, base::NotFatalUntil::M140);
     result_cb_ = std::move(generate_license_request_cb);
     session_->GenerateLicenseRequest(
         CreateLicenseInitData(init_data_type, init_data),
@@ -171,7 +172,7 @@
   }
 
   void GenerateLicenseRelease(ResultCB generate_license_release_cb) {
-    DCHECK(!result_cb_);
+    CHECK(!result_cb_, base::NotFatalUntil::M140);
     result_cb_ = std::move(generate_license_release_cb);
     pending_release_ = true;
     session_->GenerateLicenseRelease(
@@ -181,7 +182,7 @@
 
   void ProcessLicenseResponse(const std::vector<uint8_t>& response,
                               ResultCB process_license_response_cb) {
-    DCHECK(!result_cb_);
+    CHECK(!result_cb_, base::NotFatalUntil::M140);
     result_cb_ = std::move(process_license_response_cb);
     session_->ProcessLicenseResponse(
         CreateLicenseServerMessage(response),
@@ -204,12 +205,12 @@
 
  private:
   void OnSessionReady() {
-    DCHECK(session_ready_cb_);
+    CHECK(session_ready_cb_, base::NotFatalUntil::M140);
     std::move(session_ready_cb_).Run(true);
   }
 
   void OnLicenseMessageGenerated(fuchsia::media::drm::LicenseMessage message) {
-    DCHECK(!session_id_.empty());
+    CHECK(!session_id_.empty(), base::NotFatalUntil::M140);
     std::optional<std::string> session_msg =
         base::StringFromMemBuffer(message.message);
 
@@ -259,7 +260,7 @@
 
   template <typename T>
   void ProcessResult(const T& result) {
-    DCHECK(result_cb_);
+    CHECK(result_cb_, base::NotFatalUntil::M140);
     std::move(result_cb_)
         .Run(result.is_err()
                  ? std::make_optional(ToCdmPromiseException(result.err()))
@@ -296,7 +297,7 @@
       ready_cb_(std::move(ready_cb)),
       session_callbacks_(std::move(callbacks)),
       decryptor_(this) {
-  DCHECK(cdm_);
+  CHECK(cdm_, base::NotFatalUntil::M140);
   cdm_.events().OnProvisioned =
       fit::bind_member(this, &FuchsiaCdm::OnProvisioned);
   cdm_.set_error_handler([this](zx_status_t status) {
@@ -422,7 +423,7 @@
   }
 
   session->set_session_id(session_id);
-  DCHECK(!session_map_.contains(session_id))
+  CHECK(!session_map_.contains(session_id), base::NotFatalUntil::M140)
       << "Duplicated session id " << session_id;
   session_map_[session_id] = std::move(session);
 }
@@ -431,7 +432,7 @@
     CdmSession* session,
     uint32_t promise_id,
     std::optional<CdmPromise::Exception> exception) {
-  DCHECK(session);
+  CHECK(session, base::NotFatalUntil::M140);
   std::string session_id = session->session_id();
 
   if (exception.has_value()) {
@@ -441,7 +442,7 @@
     return;
   }
 
-  DCHECK(!session_id.empty());
+  CHECK(!session_id.empty(), base::NotFatalUntil::M140);
   promises_.ResolvePromise(promise_id, session_id);
 }
 
@@ -449,7 +450,7 @@
                              const std::string& session_id,
                              std::unique_ptr<NewSessionCdmPromise> promise) {
   DCHECK_NE(session_type, CdmSessionType::kTemporary);
-  DCHECK(!session_id.empty());
+  CHECK(!session_id.empty(), base::NotFatalUntil::M140);
   REJECT_PROMISE_AND_RETURN_IF_BAD_CDM(promise, cdm_);
 
   if (session_map_.contains(session_id)) {
@@ -482,7 +483,7 @@
   }
 
   std::string session_id = session->session_id();
-  DCHECK(!session_map_.contains(session_id))
+  CHECK(!session_map_.contains(session_id), base::NotFatalUntil::M140)
       << "Duplicated session id " << session_id;
 
   session_map_.emplace(session_id, std::move(session));
@@ -503,12 +504,12 @@
   REJECT_PROMISE_AND_RETURN_IF_BAD_CDM(promise, cdm_);
 
   // Caller should NOT pass in an empty response.
-  DCHECK(!response.empty());
+  CHECK(!response.empty(), base::NotFatalUntil::M140);
 
   uint32_t promise_id = promises_.SavePromise(std::move(promise));
 
   CdmSession* session = it->second.get();
-  DCHECK(session);
+  CHECK(session, base::NotFatalUntil::M140);
 
   session->ProcessLicenseResponse(
       response, base::BindOnce(&FuchsiaCdm::OnProcessLicenseServerMessageStatus,
@@ -534,7 +535,7 @@
 
   // Close the session if the session is waiting for license release ack.
   CdmSession* session = it->second.get();
-  DCHECK(session);
+  CHECK(session, base::NotFatalUntil::M140);
 
   if (!session->pending_release()) {
     return;
@@ -566,7 +567,7 @@
   uint32_t promise_id = promises_.SavePromise(std::move(promise));
 
   CdmSession* session = it->second.get();
-  DCHECK(session);
+  CHECK(session, base::NotFatalUntil::M140);
 
   // For a temporary session, the API will remove the keys and close the
   // session. For a persistent license session, the API will invalidate the keys
@@ -587,7 +588,7 @@
     return;
   }
 
-  DCHECK(!session_id.empty());
+  CHECK(!session_id.empty(), base::NotFatalUntil::M140);
   promises_.ResolvePromise(promise_id);
 }
 
diff --git a/media/cdm/fuchsia/fuchsia_cdm_factory.cc b/media/cdm/fuchsia/fuchsia_cdm_factory.cc
index 6b4314f..4e0bca8c 100644
--- a/media/cdm/fuchsia/fuchsia_cdm_factory.cc
+++ b/media/cdm/fuchsia/fuchsia_cdm_factory.cc
@@ -19,8 +19,8 @@
     std::unique_ptr<FuchsiaCdmProvider> cdm_provider,
     KeySystems* key_systems)
     : cdm_provider_(std::move(cdm_provider)), key_systems_(key_systems) {
-  DCHECK(cdm_provider_);
-  DCHECK(key_systems_);
+  CHECK(cdm_provider_, base::NotFatalUntil::M140);
+  CHECK(key_systems_, base::NotFatalUntil::M140);
 }
 
 FuchsiaCdmFactory::~FuchsiaCdmFactory() = default;
diff --git a/media/cdm/fuchsia/fuchsia_decryptor.cc b/media/cdm/fuchsia/fuchsia_decryptor.cc
index ef48344..103816c5 100644
--- a/media/cdm/fuchsia/fuchsia_decryptor.cc
+++ b/media/cdm/fuchsia/fuchsia_decryptor.cc
@@ -7,6 +7,7 @@
 #include "base/check.h"
 #include "base/fuchsia/fuchsia_logging.h"
 #include "base/location.h"
+#include "base/not_fatal_until.h"
 #include "base/notreached.h"
 #include "media/base/decoder_buffer.h"
 #include "media/base/video_frame.h"
@@ -17,7 +18,7 @@
 
 FuchsiaDecryptor::FuchsiaDecryptor(FuchsiaCdmContext* cdm_context)
     : cdm_context_(cdm_context) {
-  DCHECK(cdm_context_);
+  CHECK(cdm_context_, base::NotFatalUntil::M140);
 }
 
 FuchsiaDecryptor::~FuchsiaDecryptor() {}
diff --git a/media/cdm/fuchsia/fuchsia_stream_decryptor.cc b/media/cdm/fuchsia/fuchsia_stream_decryptor.cc
index c800a7f64..371446b 100644
--- a/media/cdm/fuchsia/fuchsia_stream_decryptor.cc
+++ b/media/cdm/fuchsia/fuchsia_stream_decryptor.cc
@@ -10,6 +10,7 @@
 #include "base/fuchsia/fuchsia_logging.h"
 #include "base/functional/bind.h"
 #include "base/logging.h"
+#include "base/not_fatal_until.h"
 #include "base/task/bind_post_task.h"
 #include "media/base/decoder_buffer.h"
 #include "media/base/decrypt_config.h"
@@ -65,7 +66,7 @@
 
 fuchsia::media::FormatDetails GetEncryptedFormatDetails(
     const DecryptConfig* config) {
-  DCHECK(config);
+  CHECK(config, base::NotFatalUntil::M140);
 
   fuchsia::media::EncryptedFormat encrypted_format;
   encrypted_format.set_scheme(GetEncryptionScheme(config->encryption_scheme()))
@@ -75,7 +76,7 @@
           std::vector<uint8_t>(config->iv().begin(), config->iv().end()))
       .set_subsamples(GetSubsamples(config->subsamples()));
   if (config->encryption_scheme() == EncryptionScheme::kCbcs) {
-    DCHECK(config->encryption_pattern().has_value());
+    CHECK(config->encryption_pattern().has_value(), base::NotFatalUntil::M140);
     encrypted_format.set_pattern(
         GetEncryptionPattern(config->encryption_pattern().value()));
   }
@@ -188,7 +189,7 @@
 
 void FuchsiaStreamDecryptor::OnStreamProcessorNoKey() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  DCHECK(!waiting_for_key_);
+  CHECK(!waiting_for_key_, base::NotFatalUntil::M140);
 
   // Reset stream position, but keep all pending buffers. They will be
   // resubmitted later, when we have a new key.
@@ -276,7 +277,7 @@
     return;
   }
 
-  DCHECK(!retry_on_no_key_event_);
+  CHECK(!retry_on_no_key_event_, base::NotFatalUntil::M140);
   waiting_for_key_ = false;
   input_writer_queue_.Unpause();
 }
diff --git a/media/cdm/simple_cdm_allocator.cc b/media/cdm/simple_cdm_allocator.cc
index 333fc9c0..4eb16ea 100644
--- a/media/cdm/simple_cdm_allocator.cc
+++ b/media/cdm/simple_cdm_allocator.cc
@@ -12,6 +12,7 @@
 #include <memory>
 
 #include "base/functional/bind.h"
+#include "base/not_fatal_until.h"
 #include "media/base/video_frame.h"
 #include "media/cdm/cdm_helpers.h"
 #include "media/cdm/simple_cdm_buffer.h"
@@ -34,7 +35,7 @@
   // VideoFrameImpl implementation.
   scoped_refptr<media::VideoFrame> TransformToVideoFrame(
       gfx::Size natural_size) override {
-    DCHECK(FrameBuffer());
+    CHECK(FrameBuffer(), base::NotFatalUntil::M140);
 
     cdm::Buffer* buffer = FrameBuffer();
     gfx::Size frame_size(Size().width, Size().height);
diff --git a/media/cdm/simple_cdm_buffer.cc b/media/cdm/simple_cdm_buffer.cc
index 09b6173..67d3936e 100644
--- a/media/cdm/simple_cdm_buffer.cc
+++ b/media/cdm/simple_cdm_buffer.cc
@@ -7,13 +7,14 @@
 #include <limits>
 
 #include "base/check_op.h"
+#include "base/not_fatal_until.h"
 #include "base/numerics/safe_conversions.h"
 
 namespace media {
 
 // static
 SimpleCdmBuffer* SimpleCdmBuffer::Create(size_t capacity) {
-  DCHECK(capacity);
+  CHECK(capacity, base::NotFatalUntil::M140);
 
   // cdm::Buffer interface limits capacity to uint32.
   DCHECK_LE(capacity, std::numeric_limits<uint32_t>::max());
@@ -38,7 +39,7 @@
 }
 
 void SimpleCdmBuffer::SetSize(uint32_t size) {
-  DCHECK(size <= Capacity());
+  CHECK(size <= Capacity(), base::NotFatalUntil::M140);
   size_ = size > Capacity() ? 0 : size;
 }
 
diff --git a/media/cdm/win/media_foundation_cdm.cc b/media/cdm/win/media_foundation_cdm.cc
index 3b9abd0..b338174 100644
--- a/media/cdm/win/media_foundation_cdm.cc
+++ b/media/cdm/win/media_foundation_cdm.cc
@@ -313,13 +313,13 @@
       session_keys_change_cb_(session_keys_change_cb),
       session_expiration_update_cb_(session_expiration_update_cb) {
   DVLOG_FUNC(1);
-  DCHECK(!uma_prefix_.empty());
-  DCHECK(create_mf_cdm_cb_);
-  DCHECK(is_type_supported_cb_);
-  DCHECK(session_message_cb_);
-  DCHECK(session_closed_cb_);
-  DCHECK(session_keys_change_cb_);
-  DCHECK(session_expiration_update_cb_);
+  CHECK(!uma_prefix_.empty(), base::NotFatalUntil::M140);
+  CHECK(create_mf_cdm_cb_, base::NotFatalUntil::M140);
+  CHECK(is_type_supported_cb_, base::NotFatalUntil::M140);
+  CHECK(session_message_cb_, base::NotFatalUntil::M140);
+  CHECK(session_closed_cb_, base::NotFatalUntil::M140);
+  CHECK(session_keys_change_cb_, base::NotFatalUntil::M140);
+  CHECK(session_expiration_update_cb_, base::NotFatalUntil::M140);
 }
 
 MediaFoundationCdm::~MediaFoundationCdm() {
@@ -331,7 +331,7 @@
   ComPtr<IMFContentDecryptionModule> mf_cdm;
   create_mf_cdm_cb_.Run(hr, mf_cdm);
   if (!mf_cdm) {
-    DCHECK(FAILED(hr));
+    CHECK(FAILED(hr), base::NotFatalUntil::M140);
 
     if (hr == DRM_E_TEE_INVALID_HWDRM_STATE) {
       OnCdmEvent(CdmEvent::kHardwareContextReset, hr);
@@ -601,7 +601,7 @@
   auto itr = pending_sessions_.find(session_token);
   CHECK(itr != pending_sessions_.end());
   auto session = std::move(itr->second);
-  DCHECK(session);
+  CHECK(session, base::NotFatalUntil::M140);
   pending_sessions_.erase(itr);
 
   if (session_id.empty() || sessions_.count(session_id)) {
@@ -693,7 +693,7 @@
   // Recreates IMFContentDecryptionModule so we can create new sessions.
   if (FAILED(Initialize())) {
     DLOG(ERROR) << __func__ << ": Re-initialization failed";
-    DCHECK(!mf_cdm_);
+    CHECK(!mf_cdm_, base::NotFatalUntil::M140);
   }
 }
 
diff --git a/media/cdm/win/media_foundation_cdm_factory.cc b/media/cdm/win/media_foundation_cdm_factory.cc
index 0b4abdc..eead4cd 100644
--- a/media/cdm/win/media_foundation_cdm_factory.cc
+++ b/media/cdm/win/media_foundation_cdm_factory.cc
@@ -11,6 +11,7 @@
 #include "base/functional/bind.h"
 #include "base/functional/callback_helpers.h"
 #include "base/metrics/histogram_functions.h"
+#include "base/not_fatal_until.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/task/bind_post_task.h"
 #include "base/task/thread_pool.h"
@@ -52,7 +53,8 @@
 void MediaFoundationCdmFactory::SetCreateCdmFactoryCallbackForTesting(
     const std::string& key_system,
     CreateCdmFactoryCB create_cdm_factory_cb) {
-  DCHECK(!create_cdm_factory_cbs_for_testing_.count(key_system));
+  CHECK(!create_cdm_factory_cbs_for_testing_.count(key_system),
+        base::NotFatalUntil::M140);
   create_cdm_factory_cbs_for_testing_[key_system] =
       std::move(create_cdm_factory_cb);
 }
@@ -69,8 +71,8 @@
   // IMFContentDecryptionModule CDMs typically require persistent storage and
   // distinctive identifier and this should be guaranteed by key system support
   // code. Update this if there are new CDMs that doesn't require these.
-  DCHECK(cdm_config.allow_persistent_state);
-  DCHECK(cdm_config.allow_distinctive_identifier);
+  CHECK(cdm_config.allow_persistent_state, base::NotFatalUntil::M140);
+  CHECK(cdm_config.allow_distinctive_identifier, base::NotFatalUntil::M140);
 
   // Don't cache `cdm_origin_id` in this class since user can clear it any time.
   helper_->GetMediaFoundationCdmData(
@@ -152,7 +154,7 @@
   auto itr = create_cdm_factory_cbs_for_testing_.find(key_system);
   if (itr != create_cdm_factory_cbs_for_testing_.end()) {
     auto& create_cdm_factory_cb = itr->second;
-    DCHECK(create_cdm_factory_cb);
+    CHECK(create_cdm_factory_cb, base::NotFatalUntil::M140);
     RETURN_IF_FAILED(create_cdm_factory_cb.Run(cdm_factory));
     return S_OK;
   }
diff --git a/media/cdm/win/media_foundation_cdm_module.cc b/media/cdm/win/media_foundation_cdm_module.cc
index 52f49dee..6d7c7b1 100644
--- a/media/cdm/win/media_foundation_cdm_module.cc
+++ b/media/cdm/win/media_foundation_cdm_module.cc
@@ -9,6 +9,7 @@
 #include "base/files/file_util.h"
 #include "base/logging.h"
 #include "base/metrics/histogram_functions.h"
+#include "base/not_fatal_until.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/win/scoped_hstring.h"
 #include "media/base/win/hresults.h"
@@ -106,7 +107,7 @@
 }
 
 HRESULT MediaFoundationCdmModule::ActivateCdmFactory() {
-  DCHECK(initialized_);
+  CHECK(initialized_, base::NotFatalUntil::M140);
 
   if (activated_) {
     DLOG(ERROR) << "CDM failed to activate previously";
@@ -117,7 +118,7 @@
 
   // For OS or store CDM, the `cdm_path_` is empty. Just use default creation.
   if (cdm_path_.empty()) {
-    DCHECK(!library_.is_valid());
+    CHECK(!library_.is_valid(), base::NotFatalUntil::M140);
     ComPtr<IMFMediaEngineClassFactory4> class_factory;
     RETURN_IF_FAILED(CoCreateInstance(CLSID_MFMediaEngineClassFactory, nullptr,
                                       CLSCTX_INPROC_SERVER,
diff --git a/media/cdm/win/media_foundation_cdm_session.cc b/media/cdm/win/media_foundation_cdm_session.cc
index c4f0ad2..316a2945 100644
--- a/media/cdm/win/media_foundation_cdm_session.cc
+++ b/media/cdm/win/media_foundation_cdm_session.cc
@@ -13,6 +13,7 @@
 
 #include "base/logging.h"
 #include "base/metrics/histogram_functions.h"
+#include "base/not_fatal_until.h"
 #include "base/numerics/safe_conversions.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/task/bind_post_task.h"
@@ -201,7 +202,7 @@
     const std::vector<uint8_t>& init_data,
     SessionIdCB session_id_cb) {
   DVLOG_FUNC(1);
-  DCHECK(session_id_.empty() && !session_id_cb_);
+  CHECK((session_id_.empty() && !session_id_cb_), base::NotFatalUntil::M140);
 
   session_id_cb_ = std::move(session_id_cb);
 
@@ -263,7 +264,7 @@
   if (session_id_.empty() && !SetSessionId())
     return;
 
-  DCHECK(!session_id_.empty());
+  CHECK(!session_id_.empty(), base::NotFatalUntil::M140);
   session_message_cb_.Run(session_id_, message_type, message);
 }
 
@@ -297,20 +298,22 @@
 }
 
 bool MediaFoundationCdmSession::SetSessionId() {
-  DCHECK(session_id_.empty() && session_id_cb_);
+  CHECK((session_id_.empty() && session_id_cb_), base::NotFatalUntil::M140);
 
   base::win::ScopedCoMem<wchar_t> session_id;
   HRESULT hr = mf_cdm_session_->GetSessionId(&session_id);
   if (FAILED(hr) || !session_id) {
     bool success = std::move(session_id_cb_).Run("");
-    DCHECK(!success) << "Empty session ID should not be accepted";
+    CHECK(!success, base::NotFatalUntil::M140)
+        << "Empty session ID should not be accepted";
     return false;
   }
 
   auto session_id_str = base::WideToUTF8(session_id.get());
   if (session_id_str.empty()) {
     bool success = std::move(session_id_cb_).Run("");
-    DCHECK(!success) << "Empty session ID should not be accepted";
+    CHECK(!success, base::NotFatalUntil::M140)
+        << "Empty session ID should not be accepted";
     return false;
   }
 
@@ -326,7 +329,7 @@
 }
 
 HRESULT MediaFoundationCdmSession::UpdateExpirationIfNeeded() {
-  DCHECK(!session_id_.empty());
+  CHECK(!session_id_.empty(), base::NotFatalUntil::M140);
 
   // Media Foundation CDM follows the EME spec where Time generally represents
   // an instant in time with millisecond accuracy.
diff --git a/media/cdm/win/media_foundation_cdm_util.cc b/media/cdm/win/media_foundation_cdm_util.cc
index c4867744..e4ae69f 100644
--- a/media/cdm/win/media_foundation_cdm_util.cc
+++ b/media/cdm/win/media_foundation_cdm_util.cc
@@ -12,6 +12,7 @@
 
 #include "base/compiler_specific.h"
 #include "base/files/file_util.h"
+#include "base/not_fatal_until.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/win/propvarutil.h"
 #include "base/win/scoped_propvariant.h"
@@ -102,7 +103,7 @@
                                                  video_capabilities.get()));
 
   // Add persistent state.
-  DCHECK(cdm_config.allow_persistent_state);
+  CHECK(cdm_config.allow_persistent_state, base::NotFatalUntil::M140);
   base::win::ScopedPropVariant persisted_state;
   RETURN_IF_FAILED(InitPropVariantFromUInt32(MF_MEDIAKEYS_REQUIREMENT_REQUIRED,
                                              persisted_state.Receive()));
@@ -110,7 +111,7 @@
                                                  persisted_state.get()));
 
   // Add distinctive identifier.
-  DCHECK(cdm_config.allow_distinctive_identifier);
+  CHECK(cdm_config.allow_distinctive_identifier, base::NotFatalUntil::M140);
   base::win::ScopedPropVariant distinctive_identifier;
   RETURN_IF_FAILED(InitPropVariantFromUInt32(MF_MEDIAKEYS_REQUIREMENT_REQUIRED,
                                              distinctive_identifier.Receive()));
@@ -126,7 +127,7 @@
     const std::optional<std::vector<uint8_t>>& client_token,
     const base::FilePath& store_path,
     ComPtr<IPropertyStore>& properties) {
-  DCHECK(!origin_id.is_empty());
+  CHECK(!origin_id.is_empty(), base::NotFatalUntil::M140);
 
   ComPtr<IPropertyStore> temp_properties;
   RETURN_IF_FAILED(PSCreateMemoryPropertyStore(IID_PPV_ARGS(&temp_properties)));
@@ -174,7 +175,7 @@
            << ", cdm_origin_id=" << cdm_origin_id.ToString()
            << ", cdm_store_path_root=" << cdm_store_path_root;
 
-  DCHECK(!cdm_origin_id.is_empty());
+  CHECK(!cdm_origin_id.is_empty(), base::NotFatalUntil::M140);
 
   const auto key_system = cdm_config.key_system;
   auto key_system_str = base::UTF8ToWide(key_system);
diff --git a/media/gpu/chromeos/decoder_buffer_transcryptor.cc b/media/gpu/chromeos/decoder_buffer_transcryptor.cc
index f94a844..1da97ba 100644
--- a/media/gpu/chromeos/decoder_buffer_transcryptor.cc
+++ b/media/gpu/chromeos/decoder_buffer_transcryptor.cc
@@ -173,7 +173,9 @@
       buffer->set_timestamp(superframe->timestamp());
       buffer->set_duration(superframe->duration());
       buffer->set_is_key_frame(superframe->is_key_frame());
-      buffer->set_side_data(superframe->side_data()->Clone());
+      if (superframe->side_data()) {
+        buffer->set_side_data(superframe->side_data()->Clone());
+      }
       if (frames.back().decrypt_config) {
         buffer->set_decrypt_config(std::move(frames.back().decrypt_config));
       }
diff --git a/net/cookies/cookie_access_result.h b/net/cookies/cookie_access_result.h
index ad92430..2b2de9b6 100644
--- a/net/cookies/cookie_access_result.h
+++ b/net/cookies/cookie_access_result.h
@@ -34,13 +34,8 @@
 
   ~CookieAccessResult();
 
-  bool operator==(const CookieAccessResult& other) const {
-    return status == other.status &&
-           effective_same_site == other.effective_same_site &&
-           access_semantics == other.access_semantics &&
-           is_allowed_to_access_secure_cookies ==
-               other.is_allowed_to_access_secure_cookies;
-  }
+  friend bool operator==(const CookieAccessResult&,
+                         const CookieAccessResult&) = default;
 
   CookieInclusionStatus status;
   CookieEffectiveSameSite effective_same_site =
@@ -59,6 +54,7 @@
   PrintTo(car.status, os);
   *os << " }, effective_same_site=" << static_cast<int>(car.effective_same_site)
       << ", access_semantics=" << static_cast<int>(car.access_semantics)
+      << ", scope_semantics=" << static_cast<int>(car.scope_semantics)
       << ", is_allowed_to_access_secure_cookies="
       << car.is_allowed_to_access_secure_cookies << " }";
 }
diff --git a/net/cookies/cookie_monster.cc b/net/cookies/cookie_monster.cc
index b1cfd1a8..274e997 100644
--- a/net/cookies/cookie_monster.cc
+++ b/net/cookies/cookie_monster.cc
@@ -364,10 +364,10 @@
 
 size_t CountCookiesForPossibleDeletion(
     CookiePriority priority,
-    const CookieMonster::CookieItVector* cookies,
+    const CookieMonster::CookieItVector& cookies,
     bool protect_secure_cookies) {
   size_t cookies_count = 0U;
-  for (const auto& cookie : *cookies) {
+  for (const auto& cookie : cookies) {
     if (cookie->second->Priority() == priority) {
       if (!protect_secure_cookies || cookie->second->SecureAttribute()) {
         cookies_count++;
@@ -390,11 +390,11 @@
 size_t CountCookiesAndGenerateListsForPossibleDeletion(
     CookiePriority priority,
     DeletionCookieLists& could_be_deleted,
-    const CookieMonster::CookieItList* cookies,
+    const CookieMonster::CookieItList& cookies,
     bool generate_for_secure) {
   size_t total_cookies_at_priority = 0;
 
-  for (auto list_it = cookies->begin(); list_it != cookies->end(); list_it++) {
+  for (auto list_it = cookies.begin(); list_it != cookies.end(); list_it++) {
     const auto cookiemap_it = *list_it;
     const auto& cookie = cookiemap_it->second;
 
@@ -803,8 +803,7 @@
 
     included_cookies.reserve(cookie_ptrs.size());
     FilterCookiesWithOptions(url, options, cookie_partition_key_collection,
-                             &cookie_ptrs, &included_cookies,
-                             &excluded_cookies);
+                             cookie_ptrs, included_cookies, excluded_cookies);
   }
 
   MaybeRunCookieCallback(std::move(callback), std::move(included_cookies),
@@ -1327,9 +1326,9 @@
     const GURL& url,
     const CookieOptions& options,
     const CookiePartitionKeyCollection& cookie_partition_key_collection,
-    std::vector<CanonicalCookie*>* cookie_ptrs,
-    CookieAccessResultList* included_cookies,
-    CookieAccessResultList* excluded_cookies) {
+    std::vector<CanonicalCookie*>& cookie_ptrs,
+    CookieAccessResultList& included_cookies,
+    CookieAccessResultList& excluded_cookies) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
 
   // Probe to save statistics relatively frequently.  We do it here rather
@@ -1344,13 +1343,13 @@
 
   std::vector<std::pair<CanonicalCookie*, CookieAccessResult>>
       cookies_and_access_results;
-  cookies_and_access_results.reserve(cookie_ptrs->size());
+  cookies_and_access_results.reserve(cookie_ptrs.size());
   std::set<std::string> origin_cookie_names;
 
   const bool include_unpartitioned_cookies =
       IncludeUnpartitionedCookies(cookie_partition_key_collection);
 
-  for (CanonicalCookie* cookie_ptr : *cookie_ptrs) {
+  for (CanonicalCookie* cookie_ptr : cookie_ptrs) {
     // Filter out cookies that should not be included for a request to the
     // given |url|. HTTP only cookies are filtered depending on the passed
     // cookie |options|.
@@ -1391,6 +1390,7 @@
 
   for (auto& cookie_result : cookies_and_access_results) {
     CanonicalCookie* cookie_ptr = cookie_result.first;
+    DCHECK(cookie_ptr);
     CookieAccessResult& access_result = cookie_result.second;
 
     // We want to collect these metrics for cookies that would be included
@@ -1451,16 +1451,16 @@
 
     if (!access_result.status.IsInclude()) {
       if (options.return_excluded_cookies()) {
-        excluded_cookies->push_back({*cookie_ptr, access_result});
+        excluded_cookies.push_back({*cookie_ptr, access_result});
       }
       continue;
     }
 
     if (options.update_access_time()) {
-      InternalUpdateCookieAccessTime(cookie_ptr, current_time);
+      InternalUpdateCookieAccessTime(*cookie_ptr, current_time);
     }
 
-    included_cookies->push_back({*cookie_ptr, access_result});
+    included_cookies.push_back({*cookie_ptr, access_result});
   }
 }
 
@@ -1470,13 +1470,13 @@
     bool allowed_to_set_secure_cookie,
     bool skip_httponly,
     bool already_expired,
-    base::Time* creation_date_to_inherit,
-    CookieInclusionStatus* status,
+    base::Time& creation_date_to_inherit,
+    CookieInclusionStatus& status,
     std::optional<PartitionedCookieMap::iterator> cookie_partition_it) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
-  DCHECK(!status->HasExclusionReason(
+  DCHECK(!status.HasExclusionReason(
       CookieInclusionStatus::ExclusionReason::EXCLUDE_OVERWRITE_SECURE));
-  DCHECK(!status->HasExclusionReason(
+  DCHECK(!status.HasExclusionReason(
       CookieInclusionStatus::ExclusionReason::EXCLUDE_OVERWRITE_HTTP_ONLY));
 
   CookieMap* cookie_map = &cookies_;
@@ -1520,7 +1520,7 @@
                               skipped_secure_cookie, &cookie_being_set,
                               capture_mode);
                         });
-      status->AddExclusionReason(
+      status.AddExclusionReason(
           CookieInclusionStatus::ExclusionReason::EXCLUDE_OVERWRITE_SECURE);
     }
     // If cookie's domain is in legacy mode, check to make sure we are not
@@ -1546,8 +1546,8 @@
               return NetLogCookieMonsterCookieRejectedHttponly(
                   cur_existing_cookie, &cookie_being_set, capture_mode);
             });
-        status->AddExclusionReason(CookieInclusionStatus::ExclusionReason::
-                                       EXCLUDE_OVERWRITE_HTTP_ONLY);
+        status.AddExclusionReason(CookieInclusionStatus::ExclusionReason::
+                                      EXCLUDE_OVERWRITE_HTTP_ONLY);
       } else {
         deletion_candidate_it = cur_it;
       }
@@ -1557,8 +1557,8 @@
   if (deletion_candidate_it != cookie_map->end()) {
     CanonicalCookie* deletion_candidate = deletion_candidate_it->second.get();
     if (deletion_candidate->Value() == cookie_being_set.Value())
-      *creation_date_to_inherit = deletion_candidate->CreationDate();
-    if (status->IsInclude()) {
+      creation_date_to_inherit = deletion_candidate->CreationDate();
+    if (status.IsInclude()) {
       if (cookie_being_set.IsPartitioned()) {
         InternalDeletePartitionedCookie(
             cookie_partition_it.value(), deletion_candidate_it,
@@ -1570,7 +1570,7 @@
                              already_expired ? DELETE_COOKIE_EXPIRED_OVERWRITE
                                              : DELETE_COOKIE_OVERWRITE);
       }
-    } else if (status->HasExclusionReason(
+    } else if (status.HasExclusionReason(
                    CookieInclusionStatus::ExclusionReason::
                        EXCLUDE_OVERWRITE_SECURE)) {
       // Log that we preserved a cookie that would have been deleted due to
@@ -1596,6 +1596,7 @@
     const CookieAccessResult& access_result,
     bool dispatch_change) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  DCHECK(cc);
   CanonicalCookie* cc_ptr = cc.get();
 
   net_log_.AddEvent(NetLogEventType::COOKIE_STORE_COOKIE_ADDED,
@@ -1603,8 +1604,9 @@
                       return NetLogCookieMonsterCookieAdded(
                           cc.get(), sync_to_store, capture_mode);
                     });
-  if (ShouldUpdatePersistentStore(cc_ptr) && sync_to_store)
+  if (ShouldUpdatePersistentStore(*cc_ptr) && sync_to_store) {
     store_->AddCookie(*cc_ptr);
+  }
 
   auto inserted = cookies_.insert(CookieMap::value_type(key, std::move(cc)));
 
@@ -1636,8 +1638,8 @@
   return inserted;
 }
 
-bool CookieMonster::ShouldUpdatePersistentStore(CanonicalCookie* cc) {
-  return (cc->IsPersistent() || persist_session_cookies_) && store_.get();
+bool CookieMonster::ShouldUpdatePersistentStore(CanonicalCookie& cc) {
+  return (cc.IsPersistent() || persist_session_cookies_) && store_.get();
 }
 
 CookieMonster::PartitionedCookieMapIterators
@@ -1647,6 +1649,7 @@
     bool sync_to_store,
     const CookieAccessResult& access_result,
     bool dispatch_change) {
+  DCHECK(cc);
   DCHECK(cc->IsPartitioned());
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   CanonicalCookie* cc_ptr = cc.get();
@@ -1656,8 +1659,9 @@
                       return NetLogCookieMonsterCookieAdded(
                           cc.get(), sync_to_store, capture_mode);
                     });
-  if (ShouldUpdatePersistentStore(cc_ptr) && sync_to_store)
+  if (ShouldUpdatePersistentStore(*cc_ptr) && sync_to_store) {
     store_->AddCookie(*cc_ptr);
+  }
 
   CookiePartitionKey partition_key(cc->PartitionKey().value());
 
@@ -1764,8 +1768,8 @@
   if (should_try_to_delete_duplicates) {
     MaybeDeleteEquivalentCookieAndUpdateStatus(
         key, *cc, access_result.is_allowed_to_access_secure_cookies,
-        options.exclude_httponly(), already_expired, &creation_date_to_inherit,
-        &access_result.status, cookie_partition_it);
+        options.exclude_httponly(), already_expired, creation_date_to_inherit,
+        access_result.status, cookie_partition_it);
   }
 
   if (access_result.status.HasExclusionReason(
@@ -1930,7 +1934,7 @@
   MaybeRunCookieCallback(std::move(callback), access_result);
 }
 
-void CookieMonster::InternalUpdateCookieAccessTime(CanonicalCookie* cc,
+void CookieMonster::InternalUpdateCookieAccessTime(CanonicalCookie& cc,
                                                    const Time& current) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
 
@@ -1938,12 +1942,13 @@
   // don't bother updating its access time again.  This reduces the number of
   // updates we do during pageload, which in turn reduces the chance our storage
   // backend will hit its batch thresholds and be forced to update.
-  if ((current - cc->LastAccessDate()) < last_access_threshold_)
+  if ((current - cc.LastAccessDate()) < last_access_threshold_) {
     return;
+  }
 
-  cc->SetLastAccessDate(current);
+  cc.SetLastAccessDate(current);
   if (ShouldUpdatePersistentStore(cc))
-    store_->UpdateCookieAccessTime(*cc);
+    store_->UpdateCookieAccessTime(cc);
 }
 
 // InternalDeleteCookies must not invalidate iterators other than the one being
@@ -1960,6 +1965,7 @@
                 "kChangeCauseMapping size should match DeletionCause size");
 
   CanonicalCookie* cc = it->second.get();
+  DCHECK(cc);
   DVLOG(net::cookie_util::kVlogSetCookies)
       << "InternalDeleteCookie()"
       << ", cause:" << deletion_cause << ", cc: " << cc->DebugString();
@@ -1973,8 +1979,9 @@
                       });
   }
 
-  if (ShouldUpdatePersistentStore(cc) && sync_to_store)
+  if (ShouldUpdatePersistentStore(*cc) && sync_to_store) {
     store_->DeleteCookie(*cc);
+  }
 
   change_dispatcher_.DispatchChange(
       CookieChangeInfo(
@@ -2015,6 +2022,7 @@
                 "kChangeCauseMapping size should match DeletionCause size");
 
   CanonicalCookie* cc = cookie_it->second.get();
+  DCHECK(cc);
   DCHECK(cc->IsPartitioned());
   DVLOG(net::cookie_util::kVlogSetCookies)
       << "InternalDeletePartitionedCookie()"
@@ -2029,8 +2037,9 @@
                       });
   }
 
-  if (ShouldUpdatePersistentStore(cc) && sync_to_store)
+  if (ShouldUpdatePersistentStore(*cc) && sync_to_store) {
     store_->DeleteCookie(*cc);
+  }
 
   change_dispatcher_.DispatchChange(
       CookieChangeInfo(
@@ -2165,11 +2174,11 @@
         if (purge_goal > 0) {
           if (obc_behavior_enabled) {
             just_deleted = PurgeLeastRecentMatchesForOBC(
-                &cookie_it_list, purge_round.priority, quota, purge_goal,
+                cookie_it_list, purge_round.priority, quota, purge_goal,
                 !purge_round.protect_secure_cookies);
           } else {
             just_deleted = PurgeLeastRecentMatches(
-                cookie_its, purge_round.priority, quota, purge_goal,
+                *cookie_its, purge_round.priority, quota, purge_goal,
                 purge_round.protect_secure_cookies);
           }
           DCHECK_LE(just_deleted, purge_goal);
@@ -2209,7 +2218,7 @@
       base::Time earliest_non_secure_access_time;
       size_t just_deleted = GarbageCollectLeastRecentlyAccessed(
           current, safe_date, non_secure_purge_goal,
-          std::move(non_secure_cookie_its), &earliest_non_secure_access_time);
+          std::move(non_secure_cookie_its), earliest_non_secure_access_time);
       num_deleted += just_deleted;
 
       if (secure_cookie_its.size() == 0) {
@@ -2224,7 +2233,7 @@
         base::Time earliest_secure_access_time;
         num_deleted += GarbageCollectLeastRecentlyAccessed(
             current, safe_date, secure_purge_goal, std::move(secure_cookie_its),
-            &earliest_secure_access_time);
+            earliest_secure_access_time);
 
         if (!earliest_non_secure_access_time.is_null() &&
             earliest_non_secure_access_time < earliest_secure_access_time) {
@@ -2370,7 +2379,7 @@
   return num_deleted;
 }
 
-size_t CookieMonster::PurgeLeastRecentMatches(CookieItVector* cookies,
+size_t CookieMonster::PurgeLeastRecentMatches(CookieItVector& cookies,
                                               CookiePriority priority,
                                               size_t to_protect,
                                               size_t purge_goal,
@@ -2403,16 +2412,16 @@
 
   size_t removed = 0u;
   size_t current = 0u;
-  while ((removed < purge_goal && current < cookies->size()) &&
+  while ((removed < purge_goal && current < cookies.size()) &&
          cookies_count_possibly_to_be_deleted > 0) {
-    const CanonicalCookie* current_cookie = cookies->at(current)->second.get();
+    const CanonicalCookie* current_cookie = cookies.at(current)->second.get();
     // Only delete the current cookie if the priority is equal to
     // the current level.
     if (IsCookieEligibleForEviction(priority, protect_secure_cookies,
                                     current_cookie)) {
-      InternalDeleteCookie(cookies->at(current), true,
+      InternalDeleteCookie(cookies.at(current), true,
                            DELETE_COOKIE_EVICTED_DOMAIN);
-      cookies->erase(cookies->begin() + current);
+      cookies.erase(cookies.begin() + current);
       removed++;
       cookies_count_possibly_to_be_deleted--;
     } else {
@@ -2423,7 +2432,7 @@
 }
 
 size_t CookieMonster::PurgeLeastRecentMatchesForOBC(
-    CookieItList* cookies,
+    CookieItList& cookies,
     CookiePriority priority,
     size_t to_protect,
     size_t purge_goal,
@@ -2472,7 +2481,7 @@
     InternalDeleteCookie(cookie_map_it, /*sync_to_store=*/true,
                          DELETE_COOKIE_EVICTED_DOMAIN);
     // Delete from `cookies`.
-    cookies->erase(cookies_list_it);
+    cookies.erase(cookies_list_it);
     // Delete from `could_be_deleted`.
     domain_list_it = could_be_deleted.domain_cookies.erase(domain_list_it);
 
@@ -2490,7 +2499,7 @@
     InternalDeleteCookie(cookie_map_it, /*sync_to_store=*/true,
                          DELETE_COOKIE_EVICTED_DOMAIN);
     // Delete from `cookies`.
-    cookies->erase(cookies_list_it);
+    cookies.erase(cookies_list_it);
     // Delete from `could_be_deleted`.
     host_list_it = could_be_deleted.host_cookies.erase(host_list_it);
 
@@ -2581,7 +2590,7 @@
     const base::Time& safe_date,
     size_t purge_goal,
     CookieItVector cookie_its,
-    base::Time* earliest_time) {
+    base::Time& earliest_time) {
   DCHECK_LE(purge_goal, cookie_its.size());
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
 
@@ -2599,7 +2608,7 @@
       GarbageCollectDeleteRange(current, DELETE_COOKIE_EVICTED_GLOBAL,
                                 cookie_its.begin(), global_purge_it);
   if (global_purge_it != cookie_its.end())
-    *earliest_time = (*global_purge_it)->second->LastAccessDate();
+    earliest_time = (*global_purge_it)->second->LastAccessDate();
   return num_deleted;
 }
 
diff --git a/net/cookies/cookie_monster.h b/net/cookies/cookie_monster.h
index 0915b5a..66fc28e 100644
--- a/net/cookies/cookie_monster.h
+++ b/net/cookies/cookie_monster.h
@@ -541,9 +541,9 @@
       const GURL& url,
       const CookieOptions& options,
       const CookiePartitionKeyCollection& cookie_partition_key_collection,
-      std::vector<CanonicalCookie*>* cookie_ptrs,
-      CookieAccessResultList* included_cookies,
-      CookieAccessResultList* excluded_cookies);
+      std::vector<CanonicalCookie*>& cookie_ptrs,
+      CookieAccessResultList& included_cookies,
+      CookieAccessResultList& excluded_cookies);
 
   // Possibly delete an existing cookie equivalent to |cookie_being_set| (same
   // path, domain, and name).
@@ -580,8 +580,8 @@
       bool allowed_to_set_secure_cookie,
       bool skip_httponly,
       bool already_expired,
-      base::Time* creation_date_to_inherit,
-      CookieInclusionStatus* status,
+      base::Time& creation_date_to_inherit,
+      CookieInclusionStatus& status,
       std::optional<PartitionedCookieMap::iterator> cookie_partition_it);
 
   // Inserts `cc` into cookies_. Returns an iterator that points to the inserted
@@ -597,7 +597,7 @@
 
   // Returns true if the cookie should be (or is already) synced to the store.
   // Used for cookies during insertion and deletion into the in-memory store.
-  bool ShouldUpdatePersistentStore(CanonicalCookie* cc);
+  bool ShouldUpdatePersistentStore(CanonicalCookie& cc);
 
   // Inserts `cc` into partitioned_cookies_. Should only be used when
   // cc->IsPartitioned() is true.
@@ -613,7 +613,7 @@
   // restoring saved cookies; some statistics are not gathered in this case.
   void SetAllCookies(CookieList list, SetCookiesCallback callback);
 
-  void InternalUpdateCookieAccessTime(CanonicalCookie* cc,
+  void InternalUpdateCookieAccessTime(CanonicalCookie& cc,
                                       const base::Time& current_time);
 
   // |deletion_cause| argument is used for collecting statistics and choosing
@@ -663,14 +663,14 @@
   // |cookies| must be sorted from least-recent to most-recent.
   //
   // Returns the number of cookies deleted.
-  size_t PurgeLeastRecentMatches(CookieItVector* cookies,
+  size_t PurgeLeastRecentMatches(CookieItVector& cookies,
                                  CookiePriority priority,
                                  size_t to_protect,
                                  size_t purge_goal,
                                  bool protect_secure_cookies);
   // Same as above except that for a given {priority, secureness} tuple domain
   // cookies will be deleted before host cookies.
-  size_t PurgeLeastRecentMatchesForOBC(CookieItList* cookies,
+  size_t PurgeLeastRecentMatchesForOBC(CookieItList& cookies,
                                        CookiePriority priority,
                                        size_t to_protect,
                                        size_t purge_goal,
@@ -718,7 +718,7 @@
                                              const base::Time& safe_date,
                                              size_t purge_goal,
                                              CookieItVector cookie_its,
-                                             base::Time* earliest_time);
+                                             base::Time& earliest_time);
 
   bool HasCookieableScheme(const GURL& url);
 
diff --git a/net/cookies/cookie_monster_unittest.cc b/net/cookies/cookie_monster_unittest.cc
index b4eaa387..64ffadef 100644
--- a/net/cookies/cookie_monster_unittest.cc
+++ b/net/cookies/cookie_monster_unittest.cc
@@ -7724,8 +7724,8 @@
   cookie_ptrs = {origin_cookie1.get(), origin_cookie2.get(),
                  domain_cookie1.get()};
   cm->FilterCookiesWithOptions(https_www_foo_.url(), options,
-                               CookiePartitionKeyCollection(), &cookie_ptrs,
-                               &included, &excluded);
+                               CookiePartitionKeyCollection(), cookie_ptrs,
+                               included, excluded);
   EXPECT_TRUE(CookieListsMatch(included, {*origin_cookie1, *origin_cookie2}));
   EXPECT_TRUE(CookieListsMatch(excluded, {*domain_cookie1}));
   reset();
@@ -7734,8 +7734,8 @@
   cookie_ptrs = {domain_cookie1.get(), origin_cookie2.get(),
                  origin_cookie1.get()};
   cm->FilterCookiesWithOptions(https_www_foo_.url(), options,
-                               CookiePartitionKeyCollection(), &cookie_ptrs,
-                               &included, &excluded);
+                               CookiePartitionKeyCollection(), cookie_ptrs,
+                               included, excluded);
   EXPECT_TRUE(CookieListsMatch(included, {*origin_cookie2, *origin_cookie1}));
   EXPECT_TRUE(CookieListsMatch(excluded, {*domain_cookie1}));
   reset();
@@ -7748,8 +7748,8 @@
   cookie_ptrs = {domain_cookie1.get(), origin_cookie2.get(),
                  origin_cookie1.get(), domain_cookie2.get()};
   cm->FilterCookiesWithOptions(https_www_foo_.url(), options,
-                               CookiePartitionKeyCollection(), &cookie_ptrs,
-                               &included, &excluded);
+                               CookiePartitionKeyCollection(), cookie_ptrs,
+                               included, excluded);
   EXPECT_TRUE(CookieListsMatch(included, {*origin_cookie2, *origin_cookie1}));
   EXPECT_TRUE(CookieListsMatch(excluded, {*domain_cookie1, *domain_cookie2}));
   reset();
@@ -7763,8 +7763,8 @@
                  origin_cookie1.get(), domain_cookie2.get(),
                  domain_cookie3.get()};
   cm->FilterCookiesWithOptions(https_www_foo_.url(), options,
-                               CookiePartitionKeyCollection(), &cookie_ptrs,
-                               &included, &excluded);
+                               CookiePartitionKeyCollection(), cookie_ptrs,
+                               included, excluded);
   EXPECT_TRUE(CookieListsMatch(
       included, {*origin_cookie2, *origin_cookie1, *domain_cookie3}));
   EXPECT_TRUE(CookieListsMatch(excluded, {*domain_cookie1, *domain_cookie2}));
@@ -7779,8 +7779,8 @@
   cookie_ptrs = {domain_cookie1.get(), origin_cookie2.get(),
                  origin_cookie1.get(), sub_domain_cookie1.get()};
   cm->FilterCookiesWithOptions(https_www_foo_.url(), options,
-                               CookiePartitionKeyCollection(), &cookie_ptrs,
-                               &included, &excluded);
+                               CookiePartitionKeyCollection(), cookie_ptrs,
+                               included, excluded);
   EXPECT_TRUE(CookieListsMatch(included, {*origin_cookie2, *origin_cookie1}));
   EXPECT_TRUE(
       CookieListsMatch(excluded, {*domain_cookie1, *sub_domain_cookie1}));
@@ -7789,8 +7789,8 @@
   // Domain cookies may shadow each other.
   cookie_ptrs = {domain_cookie1.get(), sub_domain_cookie1.get()};
   cm->FilterCookiesWithOptions(https_www_foo_.url(), options,
-                               CookiePartitionKeyCollection(), &cookie_ptrs,
-                               &included, &excluded);
+                               CookiePartitionKeyCollection(), cookie_ptrs,
+                               included, excluded);
   EXPECT_TRUE(
       CookieListsMatch(included, {*domain_cookie1, *sub_domain_cookie1}));
   EXPECT_TRUE(CookieListsMatch(excluded, {}));
@@ -7804,8 +7804,8 @@
   // origin cookie wouldn't be included on this request.
   cookie_ptrs = {path_origin_cookie1.get(), domain_cookie1.get()};
   cm->FilterCookiesWithOptions(https_www_foo_.url(), options,
-                               CookiePartitionKeyCollection(), &cookie_ptrs,
-                               &included, &excluded);
+                               CookiePartitionKeyCollection(), cookie_ptrs,
+                               included, excluded);
   EXPECT_TRUE(CookieListsMatch(included, {}));
   EXPECT_TRUE(
       CookieListsMatch(excluded, {*path_origin_cookie1, *domain_cookie1}));
@@ -7820,8 +7820,8 @@
   // cookies.
   cookie_ptrs = {insecure_origin_cookie1.get(), domain_cookie1.get()};
   cm->FilterCookiesWithOptions(https_www_foo_.url(), options,
-                               CookiePartitionKeyCollection(), &cookie_ptrs,
-                               &included, &excluded);
+                               CookiePartitionKeyCollection(), cookie_ptrs,
+                               included, excluded);
   EXPECT_TRUE(CookieListsMatch(included, {*domain_cookie1}));
   EXPECT_TRUE(CookieListsMatch(excluded, {*insecure_origin_cookie1}));
   EXPECT_TRUE(
@@ -7838,8 +7838,8 @@
   // exclude because of shadowing.
   cookie_ptrs = {origin_cookie1.get(), insecure_domain_cookie1.get()};
   cm->FilterCookiesWithOptions(https_www_foo_.url(), options,
-                               CookiePartitionKeyCollection(), &cookie_ptrs,
-                               &included, &excluded);
+                               CookiePartitionKeyCollection(), cookie_ptrs,
+                               included, excluded);
   EXPECT_TRUE(CookieListsMatch(included, {*origin_cookie1}));
   EXPECT_TRUE(CookieListsMatch(excluded, {*insecure_domain_cookie1}));
   EXPECT_TRUE(
@@ -7851,8 +7851,8 @@
   // domain cookie shouldn't get shadowing exclusion.
   cookie_ptrs = {insecure_origin_cookie1.get(), insecure_domain_cookie1.get()};
   cm->FilterCookiesWithOptions(https_www_foo_.url(), options,
-                               CookiePartitionKeyCollection(), &cookie_ptrs,
-                               &included, &excluded);
+                               CookiePartitionKeyCollection(), cookie_ptrs,
+                               included, excluded);
   EXPECT_TRUE(CookieListsMatch(included, {}));
   EXPECT_TRUE(CookieListsMatch(
       excluded, {*insecure_origin_cookie1, *insecure_domain_cookie1}));
@@ -7886,8 +7886,8 @@
   cookie_ptrs = {trust_origin_cookie1.get(), secure_trust_domain_cookie1.get(),
                  secure_trust_domain_cookie2.get()};
   cm->FilterCookiesWithOptions(http_www_trustworthy.url(), options,
-                               CookiePartitionKeyCollection(), &cookie_ptrs,
-                               &included, &excluded);
+                               CookiePartitionKeyCollection(), cookie_ptrs,
+                               included, excluded);
   EXPECT_TRUE(CookieListsMatch(
       included, {*trust_origin_cookie1, *secure_trust_domain_cookie2}));
   EXPECT_TRUE(CookieListsMatch(excluded, {*secure_trust_domain_cookie1}));
@@ -7910,8 +7910,8 @@
   cookie_ptrs = {secure_trust_origin_cookie1.get(), trust_domain_cookie1.get(),
                  trust_domain_cookie2.get()};
   cm->FilterCookiesWithOptions(http_www_trustworthy.url(), options,
-                               CookiePartitionKeyCollection(), &cookie_ptrs,
-                               &included, &excluded);
+                               CookiePartitionKeyCollection(), cookie_ptrs,
+                               included, excluded);
   EXPECT_TRUE(CookieListsMatch(
       included, {*secure_trust_origin_cookie1, *trust_domain_cookie2}));
   EXPECT_TRUE(CookieListsMatch(excluded, {*trust_domain_cookie1}));
@@ -7926,8 +7926,8 @@
   // cookies.
   cookie_ptrs = {port_origin_cookie1.get(), domain_cookie1.get()};
   cm->FilterCookiesWithOptions(https_www_foo_.url(), options,
-                               CookiePartitionKeyCollection(), &cookie_ptrs,
-                               &included, &excluded);
+                               CookiePartitionKeyCollection(), cookie_ptrs,
+                               included, excluded);
   EXPECT_TRUE(
       CookieListsMatch(included, {*port_origin_cookie1, *domain_cookie1}));
   EXPECT_TRUE(included[0].access_result.status.HasWarningReason(
@@ -7942,8 +7942,8 @@
   // binding warning don't affect domain cookies.
   cookie_ptrs = {port_insecure_origin_cookie1.get(), domain_cookie1.get()};
   cm->FilterCookiesWithOptions(https_www_foo_.url(), options,
-                               CookiePartitionKeyCollection(), &cookie_ptrs,
-                               &included, &excluded);
+                               CookiePartitionKeyCollection(), cookie_ptrs,
+                               included, excluded);
   EXPECT_TRUE(CookieListsMatch(included, {*domain_cookie1}));
   EXPECT_TRUE(
       excluded[0].access_result.status.HasExactlyWarningReasonsForTesting(
@@ -7964,8 +7964,8 @@
   // cookies.
   cookie_ptrs = {port_origin_cookie1.get(), domain_cookie1.get()};
   cm->FilterCookiesWithOptions(https_www_foo_.url(), options,
-                               CookiePartitionKeyCollection(), &cookie_ptrs,
-                               &included, &excluded);
+                               CookiePartitionKeyCollection(), cookie_ptrs,
+                               included, excluded);
   EXPECT_TRUE(CookieListsMatch(included, {*domain_cookie1}));
   EXPECT_TRUE(CookieListsMatch(excluded, {*port_origin_cookie1}));
   EXPECT_TRUE(
@@ -7977,8 +7977,8 @@
   // affect domain cookies.
   cookie_ptrs = {port_insecure_origin_cookie1.get(), domain_cookie1.get()};
   cm->FilterCookiesWithOptions(https_www_foo_.url(), options,
-                               CookiePartitionKeyCollection(), &cookie_ptrs,
-                               &included, &excluded);
+                               CookiePartitionKeyCollection(), cookie_ptrs,
+                               included, excluded);
   EXPECT_TRUE(CookieListsMatch(included, {*domain_cookie1}));
   EXPECT_TRUE(CookieListsMatch(excluded, {*port_insecure_origin_cookie1}));
   EXPECT_TRUE(
@@ -8081,8 +8081,8 @@
   cookie_ptrs = {origin_cookie1.get(), origin_cookie2.get(),
                  domain_cookie1.get()};
   cm->FilterCookiesWithOptions(https_www_foo_.url(), options,
-                               CookiePartitionKeyCollection(), &cookie_ptrs,
-                               &included, &excluded);
+                               CookiePartitionKeyCollection(), cookie_ptrs,
+                               included, excluded);
   EXPECT_TRUE(CookieListsMatch(included, cookie_ptrs));
   EXPECT_TRUE(DomainCookiesHaveWarnings(included, {*domain_cookie1}));
   reset();
@@ -8091,8 +8091,8 @@
   cookie_ptrs = {domain_cookie1.get(), origin_cookie2.get(),
                  origin_cookie1.get()};
   cm->FilterCookiesWithOptions(https_www_foo_.url(), options,
-                               CookiePartitionKeyCollection(), &cookie_ptrs,
-                               &included, &excluded);
+                               CookiePartitionKeyCollection(), cookie_ptrs,
+                               included, excluded);
   EXPECT_TRUE(CookieListsMatch(included, cookie_ptrs));
   EXPECT_TRUE(DomainCookiesHaveWarnings(included, {*domain_cookie1}));
   reset();
@@ -8105,8 +8105,8 @@
   cookie_ptrs = {domain_cookie1.get(), origin_cookie2.get(),
                  origin_cookie1.get(), domain_cookie2.get()};
   cm->FilterCookiesWithOptions(https_www_foo_.url(), options,
-                               CookiePartitionKeyCollection(), &cookie_ptrs,
-                               &included, &excluded);
+                               CookiePartitionKeyCollection(), cookie_ptrs,
+                               included, excluded);
   EXPECT_TRUE(CookieListsMatch(included, cookie_ptrs));
   EXPECT_TRUE(
       DomainCookiesHaveWarnings(included, {*domain_cookie1, *domain_cookie2}));
@@ -8121,8 +8121,8 @@
                  origin_cookie1.get(), domain_cookie2.get(),
                  domain_cookie3.get()};
   cm->FilterCookiesWithOptions(https_www_foo_.url(), options,
-                               CookiePartitionKeyCollection(), &cookie_ptrs,
-                               &included, &excluded);
+                               CookiePartitionKeyCollection(), cookie_ptrs,
+                               included, excluded);
   EXPECT_TRUE(CookieListsMatch(included, cookie_ptrs));
   EXPECT_TRUE(
       DomainCookiesHaveWarnings(included, {*domain_cookie1, *domain_cookie2}));
@@ -8137,8 +8137,8 @@
   cookie_ptrs = {domain_cookie1.get(), origin_cookie2.get(),
                  origin_cookie1.get(), sub_domain_cookie1.get()};
   cm->FilterCookiesWithOptions(https_www_foo_.url(), options,
-                               CookiePartitionKeyCollection(), &cookie_ptrs,
-                               &included, &excluded);
+                               CookiePartitionKeyCollection(), cookie_ptrs,
+                               included, excluded);
   EXPECT_TRUE(CookieListsMatch(included, cookie_ptrs));
   EXPECT_TRUE(DomainCookiesHaveWarnings(
       included, {*domain_cookie1, *sub_domain_cookie1}));
@@ -8147,8 +8147,8 @@
   // Domain cookies may shadow each other.
   cookie_ptrs = {domain_cookie1.get(), sub_domain_cookie1.get()};
   cm->FilterCookiesWithOptions(https_www_foo_.url(), options,
-                               CookiePartitionKeyCollection(), &cookie_ptrs,
-                               &included, &excluded);
+                               CookiePartitionKeyCollection(), cookie_ptrs,
+                               included, excluded);
   EXPECT_TRUE(CookieListsMatch(included, cookie_ptrs));
   EXPECT_TRUE(DomainCookiesHaveWarnings(included, {}));
   reset();
@@ -8161,8 +8161,8 @@
   // origin cookie wouldn't be included on this request.
   cookie_ptrs = {path_origin_cookie1.get(), domain_cookie1.get()};
   cm->FilterCookiesWithOptions(https_www_foo_.url(), options,
-                               CookiePartitionKeyCollection(), &cookie_ptrs,
-                               &included, &excluded);
+                               CookiePartitionKeyCollection(), cookie_ptrs,
+                               included, excluded);
   EXPECT_TRUE(CookieListsMatch(included, {domain_cookie1.get()}));
   EXPECT_TRUE(DomainCookiesHaveWarnings(included, {*domain_cookie1}));
   reset();
@@ -8176,8 +8176,8 @@
   // cookies.
   cookie_ptrs = {insecure_origin_cookie1.get(), domain_cookie1.get()};
   cm->FilterCookiesWithOptions(https_www_foo_.url(), options,
-                               CookiePartitionKeyCollection(), &cookie_ptrs,
-                               &included, &excluded);
+                               CookiePartitionKeyCollection(), cookie_ptrs,
+                               included, excluded);
   EXPECT_TRUE(CookieListsMatch(included, cookie_ptrs));
   EXPECT_TRUE(DomainCookiesHaveWarnings(included, {}));
   EXPECT_TRUE(included[0].access_result.status.HasWarningReason(
@@ -8193,8 +8193,8 @@
   // shadow warning.
   cookie_ptrs = {origin_cookie1.get(), insecure_domain_cookie1.get()};
   cm->FilterCookiesWithOptions(https_www_foo_.url(), options,
-                               CookiePartitionKeyCollection(), &cookie_ptrs,
-                               &included, &excluded);
+                               CookiePartitionKeyCollection(), cookie_ptrs,
+                               included, excluded);
   EXPECT_TRUE(CookieListsMatch(included, cookie_ptrs));
   EXPECT_TRUE(DomainCookiesHaveWarnings(included, {}));
   EXPECT_TRUE(
@@ -8206,8 +8206,8 @@
   // domain cookie shouldn't get shadowing warning.
   cookie_ptrs = {insecure_origin_cookie1.get(), insecure_domain_cookie1.get()};
   cm->FilterCookiesWithOptions(https_www_foo_.url(), options,
-                               CookiePartitionKeyCollection(), &cookie_ptrs,
-                               &included, &excluded);
+                               CookiePartitionKeyCollection(), cookie_ptrs,
+                               included, excluded);
   EXPECT_TRUE(CookieListsMatch(included, cookie_ptrs));
   EXPECT_TRUE(DomainCookiesHaveWarnings(included, {}));
   EXPECT_TRUE(included[0].access_result.status.HasWarningReason(
@@ -8242,8 +8242,8 @@
   cookie_ptrs = {trust_origin_cookie1.get(), secure_trust_domain_cookie1.get(),
                  secure_trust_domain_cookie2.get()};
   cm->FilterCookiesWithOptions(http_www_trustworthy.url(), options,
-                               CookiePartitionKeyCollection(), &cookie_ptrs,
-                               &included, &excluded);
+                               CookiePartitionKeyCollection(), cookie_ptrs,
+                               included, excluded);
   EXPECT_TRUE(CookieListsMatch(included, cookie_ptrs));
   EXPECT_TRUE(
       DomainCookiesHaveWarnings(included, {*secure_trust_domain_cookie1}));
@@ -8266,8 +8266,8 @@
   cookie_ptrs = {secure_trust_origin_cookie1.get(), trust_domain_cookie1.get(),
                  trust_domain_cookie2.get()};
   cm->FilterCookiesWithOptions(http_www_trustworthy.url(), options,
-                               CookiePartitionKeyCollection(), &cookie_ptrs,
-                               &included, &excluded);
+                               CookiePartitionKeyCollection(), cookie_ptrs,
+                               included, excluded);
   EXPECT_TRUE(CookieListsMatch(included, cookie_ptrs));
   EXPECT_TRUE(DomainCookiesHaveWarnings(included, {*trust_domain_cookie1}));
   reset();
@@ -8281,8 +8281,8 @@
   // cookies.
   cookie_ptrs = {port_origin_cookie1.get(), domain_cookie1.get()};
   cm->FilterCookiesWithOptions(https_www_foo_.url(), options,
-                               CookiePartitionKeyCollection(), &cookie_ptrs,
-                               &included, &excluded);
+                               CookiePartitionKeyCollection(), cookie_ptrs,
+                               included, excluded);
   EXPECT_TRUE(CookieListsMatch(included, cookie_ptrs));
   EXPECT_TRUE(DomainCookiesHaveWarnings(included, {}));
   EXPECT_TRUE(included[0].access_result.status.HasWarningReason(
@@ -8297,8 +8297,8 @@
   // affect domain cookies.
   cookie_ptrs = {port_insecure_origin_cookie1.get(), domain_cookie1.get()};
   cm->FilterCookiesWithOptions(https_www_foo_.url(), options,
-                               CookiePartitionKeyCollection(), &cookie_ptrs,
-                               &included, &excluded);
+                               CookiePartitionKeyCollection(), cookie_ptrs,
+                               included, excluded);
   EXPECT_TRUE(CookieListsMatch(included, cookie_ptrs));
   EXPECT_TRUE(DomainCookiesHaveWarnings(included, {}));
   EXPECT_TRUE(
@@ -8317,8 +8317,8 @@
   // cookies.
   cookie_ptrs = {port_origin_cookie1.get(), domain_cookie1.get()};
   cm->FilterCookiesWithOptions(https_www_foo_.url(), options,
-                               CookiePartitionKeyCollection(), &cookie_ptrs,
-                               &included, &excluded);
+                               CookiePartitionKeyCollection(), cookie_ptrs,
+                               included, excluded);
   EXPECT_TRUE(CookieListsMatch(included, {domain_cookie1.get()}));
   EXPECT_TRUE(DomainCookiesHaveWarnings(included, {}));
   EXPECT_TRUE(CookieListsMatch(excluded, {port_origin_cookie1.get()}));
@@ -8331,8 +8331,8 @@
   // binding warning don't affect domain cookies.
   cookie_ptrs = {port_insecure_origin_cookie1.get(), domain_cookie1.get()};
   cm->FilterCookiesWithOptions(https_www_foo_.url(), options,
-                               CookiePartitionKeyCollection(), &cookie_ptrs,
-                               &included, &excluded);
+                               CookiePartitionKeyCollection(), cookie_ptrs,
+                               included, excluded);
   EXPECT_TRUE(CookieListsMatch(included, {domain_cookie1.get()}));
   EXPECT_TRUE(DomainCookiesHaveWarnings(included, {}));
   EXPECT_TRUE(CookieListsMatch(excluded, {port_insecure_origin_cookie1.get()}));
diff --git a/net/http/http_auth_gssapi_posix.cc b/net/http/http_auth_gssapi_posix.cc
index 0fb944b1..d1033b8 100644
--- a/net/http/http_auth_gssapi_posix.cc
+++ b/net/http/http_auth_gssapi_posix.cc
@@ -118,7 +118,8 @@
 bool OidEquals(const gss_OID left, const gss_OID right) {
   if (left->length != right->length)
     return false;
-  return 0 == memcmp(left->elements, right->elements, right->length);
+  return 0 ==
+         UNSAFE_TODO(memcmp(left->elements, right->elements, right->length));
 }
 
 base::Value::Dict GetGssStatusCodeValue(GSSAPILibrary* gssapi_lib,
diff --git a/pdf/pdf_features.cc b/pdf/pdf_features.cc
index e216295..9fbfcfe0 100644
--- a/pdf/pdf_features.cc
+++ b/pdf/pdf_features.cc
@@ -75,6 +75,10 @@
 #if BUILDFLAG(ENABLE_PDF_INK2)
 BASE_FEATURE(kPdfInk2, "PdfInk2", base::FEATURE_DISABLED_BY_DEFAULT);
 
+// Enables text annotations.
+const base::FeatureParam<bool> kPdfInk2TextAnnotations{
+    &kPdfInk2, "text-annotations", false};
+
 // Enables text highlighting with the Ink highlighter brush.
 const base::FeatureParam<bool> kPdfInk2TextHighlighting{
     &kPdfInk2, "text-highlighting", false};
diff --git a/pdf/pdf_features.h b/pdf/pdf_features.h
index 40ec9d3..0a585a2c 100644
--- a/pdf/pdf_features.h
+++ b/pdf/pdf_features.h
@@ -33,6 +33,7 @@
 
 #if BUILDFLAG(ENABLE_PDF_INK2)
 BASE_DECLARE_FEATURE(kPdfInk2);
+extern const base::FeatureParam<bool> kPdfInk2TextAnnotations;
 extern const base::FeatureParam<bool> kPdfInk2TextHighlighting;
 #endif
 
diff --git a/pdf/pdf_ink_brush.cc b/pdf/pdf_ink_brush.cc
index 6f83ad21..6f310168 100644
--- a/pdf/pdf_ink_brush.cc
+++ b/pdf/pdf_ink_brush.cc
@@ -55,8 +55,6 @@
 }
 
 ink::Brush CreateInkBrush(PdfInkBrush::Type type, SkColor color, float size) {
-  CHECK(PdfInkBrush::IsToolSizeInRange(size));
-
   // TODO(crbug.com/353942923): Use real values here.
   ink::BrushTip tip;
   tip.corner_rounding = GetCornerRounding(type);
diff --git a/pdf/pdf_ink_brush.h b/pdf/pdf_ink_brush.h
index 8d96aea..5610ae1 100644
--- a/pdf/pdf_ink_brush.h
+++ b/pdf/pdf_ink_brush.h
@@ -46,7 +46,7 @@
 
   static std::string TypeToString(Type brush_type);
 
-  // Returns whether `size` is in range or not.
+  // Returns whether `size` is a valid drawing brush size or not.
   static bool IsToolSizeInRange(float size);
 
   const ink::Brush& ink_brush() const { return ink_brush_; }
diff --git a/pdf/pdf_ink_module.cc b/pdf/pdf_ink_module.cc
index 486505e..68cb698f 100644
--- a/pdf/pdf_ink_module.cc
+++ b/pdf/pdf_ink_module.cc
@@ -1073,6 +1073,7 @@
 
   const DrawingStrokeState& state = drawing_stroke_state();
   const ink::Brush& brush = GetDrawingBrush().ink_brush();
+  CHECK(PdfInkBrush::IsToolSizeInRange(brush.GetSize()));
   std::vector<ink::InProgressStroke> stroke_segments;
   stroke_segments.reserve(state.inputs.size());
   for (size_t segment_number = 0; const auto& segment : state.inputs) {
diff --git a/sandbox/linux/seccomp-bpf-helpers/baseline_policy_unittest.cc b/sandbox/linux/seccomp-bpf-helpers/baseline_policy_unittest.cc
index f290c843..ad4f21de 100644
--- a/sandbox/linux/seccomp-bpf-helpers/baseline_policy_unittest.cc
+++ b/sandbox/linux/seccomp-bpf-helpers/baseline_policy_unittest.cc
@@ -2,13 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "base/feature_list.h"
+#include "base/synchronization/lock_impl.h"
 #ifdef UNSAFE_BUFFERS_BUILD
 // TODO(crbug.com/351564777): Remove this and convert code to safer constructs.
 #pragma allow_unsafe_buffers
 #endif
 
-#include "sandbox/linux/seccomp-bpf-helpers/baseline_policy.h"
-
 #include <errno.h>
 #include <fcntl.h>
 #include <netinet/in.h>
@@ -32,10 +32,13 @@
 #include <tuple>
 
 #include "base/clang_profiling_buildflags.h"
+#include "base/features.h"
 #include "base/files/scoped_file.h"
 #include "base/posix/eintr_wrapper.h"
+#include "base/test/scoped_feature_list.h"
 #include "base/threading/thread.h"
 #include "build/build_config.h"
+#include "sandbox/linux/seccomp-bpf-helpers/baseline_policy.h"
 #include "sandbox/linux/seccomp-bpf-helpers/sigsys_handlers.h"
 #include "sandbox/linux/seccomp-bpf/bpf_tests.h"
 #include "sandbox/linux/seccomp-bpf/sandbox_bpf.h"
@@ -365,31 +368,67 @@
   }
 }
 #else
-BPF_DEATH_TEST_C(BaselinePolicy,
-                 FutexWithRequeuePriorityInheritence,
-                 DEATH_SEGV_MESSAGE(GetFutexErrorMessageContentForTests()),
-                 BaselinePolicy) {
-  syscall(__NR_futex, nullptr, FUTEX_CMP_REQUEUE_PI, 0, nullptr, nullptr, 0);
-  _exit(1);
-}
 
-BPF_DEATH_TEST_C(BaselinePolicy,
-                 FutexWithRequeuePriorityInheritencePrivate,
-                 DEATH_SEGV_MESSAGE(GetFutexErrorMessageContentForTests()),
-                 BaselinePolicy) {
-  syscall(__NR_futex, nullptr, FUTEX_CMP_REQUEUE_PI_PRIVATE, 0, nullptr,
-          nullptr, 0);
-  _exit(1);
-}
+#define TEST_BASELINE_PI_FUTEX(op)                            \
+  _TEST_BASELINE_PI_FUTEX(BaselinePolicy, Futex_##op) {       \
+    syscall(__NR_futex, nullptr, op, 0, nullptr, nullptr, 0); \
+    _exit(1);                                                 \
+  }
 
-BPF_DEATH_TEST_C(BaselinePolicy,
-                 FutexWithUnlockPIPrivate,
-                 DEATH_SEGV_MESSAGE(GetFutexErrorMessageContentForTests()),
-                 BaselinePolicy) {
-  syscall(__NR_futex, nullptr, FUTEX_UNLOCK_PI_PRIVATE, 0, nullptr, nullptr, 0);
-  _exit(1);
+#if BUILDFLAG(ENABLE_MUTEX_PRIORITY_INHERITANCE)
+// PI futexes are only allowed by the sandbox on kernels >= 6.1 and if the
+// kUsePriorityInheritanceMutex is enabled. In order to test this,
+// |_TEST_BASELINE_PI_FUTEX| generates a test which has two parts:
+//  - The first part of the test enables the feature and performs the futex
+//    syscall in a child process with the provided futex operation. Then it
+//    asserts that the syscall succeed only if the kernel version is at
+//    least 6.1.
+//  - The second part of the test disables the feature and performs the futex
+//    syscall in a child process with the provided futex operation. Then it
+//    asserts that the syscall always crashes the process.
+#define _TEST_BASELINE_PI_FUTEX(test_case_name, test_name)          \
+  void BPF_TEST_PI_FUTEX_##test_name();                             \
+  TEST(test_case_name, DISABLE_ON_TSAN(test_name)) {                \
+    __TEST_BASELINE_PI_FUTEX(BPF_TEST_PI_FUTEX_##test_name, true);  \
+    __TEST_BASELINE_PI_FUTEX(BPF_TEST_PI_FUTEX_##test_name, false); \
+  }                                                                 \
+  void BPF_TEST_PI_FUTEX_##test_name()
+
+#define __TEST_BASELINE_PI_FUTEX(test_name, use_pi_mutex)                 \
+  {                                                                       \
+    base::test::ScopedFeatureList feature_;                               \
+    feature_.InitWithFeatureState(                                        \
+        base::features::kUsePriorityInheritanceMutex, use_pi_mutex);      \
+    sandbox::SandboxBPFTestRunner bpf_test_runner(                        \
+        new BPFTesterSimpleDelegate<BaselinePolicy>(test_name));          \
+    sandbox::UnitTests::RunTestInProcess(                                 \
+        &bpf_test_runner, PIFutexDeath,                                   \
+        static_cast<const void*>(GetFutexErrorMessageContentForTests())); \
+  }
+
+void PIFutexDeath(int status, const std::string& msg, const void* aux) {
+  if (base::KernelSupportsPriorityInheritanceFutex() &&
+      base::FeatureList::IsEnabled(
+          base::features::kUsePriorityInheritanceMutex)) {
+    sandbox::UnitTests::DeathSuccess(status, msg, nullptr);
+  } else {
+    sandbox::UnitTests::DeathSEGVMessage(status, msg, aux);
+  }
 }
-#endif  // defined(LIBC_GLIBC) && !BUILDFLAG(IS_CHROMEOS)
+#else
+#define _TEST_BASELINE_PI_FUTEX(test_case_name, test_name)                    \
+  BPF_DEATH_TEST_C(BaselinePolicy, test_name,                                 \
+                   DEATH_SEGV_MESSAGE(GetFutexErrorMessageContentForTests()), \
+                   BaselinePolicy)
+#endif
+
+TEST_BASELINE_PI_FUTEX(FUTEX_LOCK_PI)
+TEST_BASELINE_PI_FUTEX(FUTEX_LOCK_PI2)
+TEST_BASELINE_PI_FUTEX(FUTEX_TRYLOCK_PI)
+TEST_BASELINE_PI_FUTEX(FUTEX_WAIT_REQUEUE_PI)
+TEST_BASELINE_PI_FUTEX(FUTEX_CMP_REQUEUE_PI)
+TEST_BASELINE_PI_FUTEX(FUTEX_UNLOCK_PI_PRIVATE)
+#endif
 
 BPF_TEST_C(BaselinePolicy, PrctlDumpable, BaselinePolicy) {
   const int is_dumpable = prctl(PR_GET_DUMPABLE, 0, 0, 0, 0);
diff --git a/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions.cc b/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions.cc
index 0ee343b..7e4da17 100644
--- a/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions.cc
+++ b/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions.cc
@@ -21,6 +21,8 @@
 #include <time.h>
 #include <unistd.h>
 
+#include "base/feature_list.h"
+#include "base/features.h"
 #include "base/notreached.h"
 #include "base/synchronization/synchronization_buildflags.h"
 #include "build/build_config.h"
@@ -339,17 +341,26 @@
 
 ResultExpr RestrictFutex() {
   const uint64_t kAllowedFutexFlags = FUTEX_PRIVATE_FLAG | FUTEX_CLOCK_REALTIME;
+  ResultExpr error = IsBuggyGlibcSemPost() ? Error(EINVAL) : CrashSIGSYSFutex();
   const Arg<int> op(1);
   return Switch(op & ~kAllowedFutexFlags)
       .Cases({FUTEX_WAIT, FUTEX_WAKE, FUTEX_REQUEUE, FUTEX_CMP_REQUEUE,
-#if BUILDFLAG(ENABLE_MUTEX_PRIORITY_INHERITANCE)
-              // Enable priority-inheritance operations.
-              FUTEX_LOCK_PI, FUTEX_UNLOCK_PI, FUTEX_TRYLOCK_PI,
-              FUTEX_WAIT_REQUEUE_PI, FUTEX_CMP_REQUEUE_PI,
-#endif  // BUILDFLAG(ENABLE_MUTEX_PRIORITY_INHERITANCE)
               FUTEX_WAKE_OP, FUTEX_WAIT_BITSET, FUTEX_WAKE_BITSET},
              Allow())
-      .Default(IsBuggyGlibcSemPost() ? Error(EINVAL) : CrashSIGSYSFutex());
+#if BUILDFLAG(ENABLE_MUTEX_PRIORITY_INHERITANCE)
+      // Priority-inheritance futex operations are enabled only on Android
+      // kernels 6.1+. Bionic uses the PI variants of the futex operations
+      // (FUTEX_LOCK_PI2, FUTEX_UNLOCK_PI) to implement priority inheriting
+      // mutexes.
+      .Cases({FUTEX_LOCK_PI, FUTEX_UNLOCK_PI, FUTEX_TRYLOCK_PI,
+              FUTEX_WAIT_REQUEUE_PI, FUTEX_CMP_REQUEUE_PI, FUTEX_LOCK_PI2},
+             (base::KernelSupportsPriorityInheritanceFutex() &&
+              base::FeatureList::IsEnabled(
+                  base::features::kUsePriorityInheritanceMutex))
+                 ? Allow()
+                 : error)
+#endif  // BUILDFLAG(ENABLE_MUTEX_PRIORITY_INHERITANCE)
+      .Default(error);
 }
 
 ResultExpr RestrictGetSetpriority(pid_t target_pid) {
diff --git a/sandbox/linux/system_headers/linux_futex.h b/sandbox/linux/system_headers/linux_futex.h
index 6fcd9af..4eff74d 100644
--- a/sandbox/linux/system_headers/linux_futex.h
+++ b/sandbox/linux/system_headers/linux_futex.h
@@ -59,6 +59,10 @@
 #define FUTEX_CMP_REQUEUE_PI 12
 #endif
 
+#if !defined(FUTEX_LOCK_PI2)
+#define FUTEX_LOCK_PI2 13
+#endif
+
 #if !defined(FUTEX_PRIVATE_FLAG)
 #define FUTEX_PRIVATE_FLAG 128
 #endif
diff --git a/services/device/public/cpp/BUILD.gn b/services/device/public/cpp/BUILD.gn
index e7fded2..7ef62ca7 100644
--- a/services/device/public/cpp/BUILD.gn
+++ b/services/device/public/cpp/BUILD.gn
@@ -23,6 +23,7 @@
   if (is_android) {
     sources += [ "device_feature_map.cc" ]
     deps += [
+      "//device/base",
       "//device/fido",
       "//services/device/public/java:device_feature_list_jni",
     ]
diff --git a/services/device/public/cpp/device_feature_map.cc b/services/device/public/cpp/device_feature_map.cc
index ecf99fc3..52a6443 100644
--- a/services/device/public/cpp/device_feature_map.cc
+++ b/services/device/public/cpp/device_feature_map.cc
@@ -7,6 +7,7 @@
 #include "base/android/feature_map.h"
 #include "base/feature_list.h"
 #include "base/no_destructor.h"
+#include "device/base/features.h"
 #include "device/fido/features.h"
 #include "services/device/public/cpp/device_features.h"
 
@@ -26,6 +27,7 @@
     &device::kWebAuthnRemoteDesktopAllowedOriginsPolicy,
     &kGenericSensorExtraClasses,
     &kBatteryStatusManagerBroadcastReceiverInBackground,
+    &device::features::kBluetoothRfcommAndroid,
 };
 
 // static
diff --git a/services/device/public/cpp/device_features.cc b/services/device/public/cpp/device_features.cc
index 34610be..c21d36a 100644
--- a/services/device/public/cpp/device_features.cc
+++ b/services/device/public/cpp/device_features.cc
@@ -85,7 +85,7 @@
 // receiver to the background thread.
 BASE_FEATURE(kBatteryStatusManagerBroadcastReceiverInBackground,
              "BatteryStatusManagerBroadcastReceiverInBackground",
-             base::FEATURE_ENABLED_BY_DEFAULT);
+             base::FEATURE_DISABLED_BY_DEFAULT);
 #endif  // BUILDFLAG(IS_ANDROID)
 
 #if !BUILDFLAG(IS_ANDROID)
diff --git a/services/device/public/java/src/org/chromium/device/DeviceFeatureList.java b/services/device/public/java/src/org/chromium/device/DeviceFeatureList.java
index 2a8d56fb..ad9ca6d 100644
--- a/services/device/public/java/src/org/chromium/device/DeviceFeatureList.java
+++ b/services/device/public/java/src/org/chromium/device/DeviceFeatureList.java
@@ -24,4 +24,5 @@
             "BatteryStatusManagerBroadcastReceiverInBackground";
     public static final String WEBAUTHN_REMOTE_DESKTOP_ALLOWED_ORIGINS =
             "WebAuthenticationRemoteDesktopAllowedOriginsPolicy";
+    public static final String BLUETOOTH_RFCOMM_ANDROID = "BluetoothRfcommAndroid";
 }
diff --git a/services/network/public/cpp/shared_storage_mojom_traits.cc b/services/network/public/cpp/shared_storage_mojom_traits.cc
index ab032fe..02c1eea 100644
--- a/services/network/public/cpp/shared_storage_mojom_traits.cc
+++ b/services/network/public/cpp/shared_storage_mojom_traits.cc
@@ -43,4 +43,20 @@
   return !network::IsReservedLockName(*out_value);
 }
 
+// static
+bool StructTraits<
+    network::mojom::SharedStorageBatchUpdateMethodsArgumentDataView,
+    std::vector<network::mojom::SharedStorageModifierMethodWithOptionsPtr>>::
+    Read(network::mojom::SharedStorageBatchUpdateMethodsArgumentDataView data,
+         std::vector<network::mojom::SharedStorageModifierMethodWithOptionsPtr>*
+             out_value) {
+  if (!data.ReadData(out_value)) {
+    return false;
+  }
+
+  // TODO(crbug.com/404568020): Add validation when we switch to disallow the
+  // 'withLock' option for methods within batchUpdate().
+  return true;
+}
+
 }  // namespace mojo
diff --git a/services/network/public/cpp/shared_storage_mojom_traits.h b/services/network/public/cpp/shared_storage_mojom_traits.h
index 958fa5e..552c67c 100644
--- a/services/network/public/cpp/shared_storage_mojom_traits.h
+++ b/services/network/public/cpp/shared_storage_mojom_traits.h
@@ -45,6 +45,23 @@
   static const std::string& data(const std::string& input) { return input; }
 };
 
+template <>
+struct COMPONENT_EXPORT(NETWORK_CPP_SHARED_STORAGE) StructTraits<
+    network::mojom::SharedStorageBatchUpdateMethodsArgumentDataView,
+    std::vector<network::mojom::SharedStorageModifierMethodWithOptionsPtr>> {
+  static bool Read(
+      network::mojom::SharedStorageBatchUpdateMethodsArgumentDataView data,
+      std::vector<network::mojom::SharedStorageModifierMethodWithOptionsPtr>*
+          out_value);
+
+  static const std::vector<
+      network::mojom::SharedStorageModifierMethodWithOptionsPtr>&
+  data(const std::vector<
+       network::mojom::SharedStorageModifierMethodWithOptionsPtr>& input) {
+    return input;
+  }
+};
+
 }  // namespace mojo
 
 #endif  // SERVICES_NETWORK_PUBLIC_CPP_SHARED_STORAGE_MOJOM_TRAITS_H_
diff --git a/services/network/public/cpp/shared_storage_mojom_traits_unittest.cc b/services/network/public/cpp/shared_storage_mojom_traits_unittest.cc
index df9eb52..1ac2e186 100644
--- a/services/network/public/cpp/shared_storage_mojom_traits_unittest.cc
+++ b/services/network/public/cpp/shared_storage_mojom_traits_unittest.cc
@@ -77,5 +77,30 @@
   }
 }
 
+TEST(SharedStorageMojomTraitsTest,
+     SerializeAndDeserializeBatchUpdateMethodsArgument) {
+  auto method1 = mojom::SharedStorageModifierMethodWithOptions::New(
+      mojom::SharedStorageModifierMethod::NewSetMethod(
+          mojom::SharedStorageSetMethod::New(/*key=*/u"a", /*key=*/u"b",
+                                             /*ignore_if_present=*/true)),
+      /*with_lock=*/std::nullopt);
+
+  auto method2 = mojom::SharedStorageModifierMethodWithOptions::New(
+      mojom::SharedStorageModifierMethod::NewAppendMethod(
+          mojom::SharedStorageAppendMethod::New(/*key=*/u"c", /*key=*/u"d")),
+      /*with_lock=*/std::nullopt);
+
+  std::vector<mojom::SharedStorageModifierMethodWithOptionsPtr>
+      original_methods;
+  original_methods.push_back(std::move(method1));
+  original_methods.push_back(std::move(method2));
+
+  std::vector<mojom::SharedStorageModifierMethodWithOptionsPtr> copied_methods;
+  EXPECT_TRUE(mojo::test::SerializeAndDeserialize<
+              mojom::SharedStorageBatchUpdateMethodsArgument>(original_methods,
+                                                              copied_methods));
+  EXPECT_EQ(original_methods, copied_methods);
+}
+
 }  // namespace
 }  // namespace network
diff --git a/services/network/public/mojom/BUILD.gn b/services/network/public/mojom/BUILD.gn
index 57bb88f..e7db5ef 100644
--- a/services/network/public/mojom/BUILD.gn
+++ b/services/network/public/mojom/BUILD.gn
@@ -297,6 +297,11 @@
           mojom = "network.mojom.LockName"
           cpp = "::std::string"
         },
+        {
+          mojom = "network.mojom.SharedStorageBatchUpdateMethodsArgument"
+          cpp = "::std::vector<::network::mojom::SharedStorageModifierMethodWithOptionsPtr>"
+          move_only = true
+        },
       ]
       traits_headers =
           [ "//services/network/public/cpp/shared_storage_mojom_traits.h" ]
@@ -318,6 +323,11 @@
           mojom = "network.mojom.LockName"
           cpp = "::WTF::String"
         },
+        {
+          mojom = "network.mojom.SharedStorageBatchUpdateMethodsArgument"
+          cpp = "::WTF::Vector<::network::mojom::blink::SharedStorageModifierMethodWithOptionsPtr>"
+          move_only = true
+        },
       ]
       traits_headers = [ "//third_party/blink/renderer/modules/shared_storage/shared_storage_blink_mojom_traits.h" ]
       traits_sources = [ "//third_party/blink/renderer/modules/shared_storage/shared_storage_blink_mojom_traits.cc" ]
diff --git a/services/network/public/mojom/cookie_access_observer.mojom b/services/network/public/mojom/cookie_access_observer.mojom
index 36a98f2..f124a24 100644
--- a/services/network/public/mojom/cookie_access_observer.mojom
+++ b/services/network/public/mojom/cookie_access_observer.mojom
@@ -24,6 +24,12 @@
   // worker script looking them up.
   url.mojom.Url url;
 
+  // If non-null, then the cookies are accessed by a network transfer and
+  // |frame_origin| is the origin of the frame executing the fetch.  May be null
+  // for network transfers whose IsolationInfo's `frame_origin()` is
+  // std::nullopt.
+  url.mojom.Origin? frame_origin;
+
   // The top frame origin for the given request, we use top_frame_origin from
   // net::IsolationInfo.
   url.mojom.Origin top_frame_origin;
diff --git a/services/network/public/mojom/shared_storage.mojom b/services/network/public/mojom/shared_storage.mojom
index 9088caa..5adce3e8 100644
--- a/services/network/public/mojom/shared_storage.mojom
+++ b/services/network/public/mojom/shared_storage.mojom
@@ -65,3 +65,10 @@
   // resource with name `with_lock`. `with_lock` shouldn't start with '-'.
   LockName? with_lock;
 };
+
+// Represents the batchUpdate()'s `methods` argument in the Shared Storage
+// API. It will be validated during serialization.
+// https://wicg.github.io/shared-storage/#batch-update
+struct SharedStorageBatchUpdateMethodsArgument {
+  array<SharedStorageModifierMethodWithOptions> data;
+};
diff --git a/services/network/public/mojom/url_loader_network_service_observer.mojom b/services/network/public/mojom/url_loader_network_service_observer.mojom
index 05e7044..6f64c1c 100644
--- a/services/network/public/mojom/url_loader_network_service_observer.mojom
+++ b/services/network/public/mojom/url_loader_network_service_observer.mojom
@@ -203,7 +203,7 @@
   // triggered by response headers and Shared Storage calls from the same
   // renderer process where this request originated.
   OnSharedStorageHeaderReceived(url.mojom.Origin request_origin,
-      array<SharedStorageModifierMethodWithOptions> methods_with_options,
+      network.mojom.SharedStorageBatchUpdateMethodsArgument methods_with_options,
       network.mojom.LockName? with_lock) => ();
 
   // Notifies the browser of the results of successful parsing of
diff --git a/services/network/restricted_cookie_manager.cc b/services/network/restricted_cookie_manager.cc
index f29b11f..29d3589 100644
--- a/services/network/restricted_cookie_manager.cc
+++ b/services/network/restricted_cookie_manager.cc
@@ -663,7 +663,8 @@
 
   if (cookie_observer_ && !on_cookies_accessed_result.empty()) {
     OnCookiesAccessed(mojom::CookieAccessDetails::New(
-        mojom::CookieAccessDetails::Type::kRead, url, isolated_top_frame_origin,
+        mojom::CookieAccessDetails::Type::kRead, url,
+        /*frame_origin=*/std::nullopt, isolated_top_frame_origin,
         site_for_cookies, std::move(on_cookies_accessed_result), std::nullopt,
         is_ad_tagged, cookie_setting_overrides));
   }
@@ -770,9 +771,9 @@
               net::CookieAccessResult(status)));
       OnCookiesAccessed(mojom::CookieAccessDetails::New(
           mojom::CookieAccessDetails::Type::kChange, url,
-          isolated_top_frame_origin, site_for_cookies,
-          std::move(result_with_access_result), std::nullopt, is_ad_tagged,
-          cookie_setting_overrides));
+          /*frame_origin=*/std::nullopt, isolated_top_frame_origin,
+          site_for_cookies, std::move(result_with_access_result), std::nullopt,
+          is_ad_tagged, cookie_setting_overrides));
     }
     std::move(callback).Run(false);
     return;
@@ -893,8 +894,9 @@
           mojom::CookieOrLine::NewCookie(cookie), access_result));
       OnCookiesAccessed(mojom::CookieAccessDetails::New(
           mojom::CookieAccessDetails::Type::kChange, url,
-          isolated_top_frame_origin, site_for_cookies, std::move(notify),
-          std::nullopt, is_ad_tagged, cookie_setting_overrides));
+          /*frame_origin=*/std::nullopt, isolated_top_frame_origin,
+          site_for_cookies, std::move(notify), std::nullopt, is_ad_tagged,
+          cookie_setting_overrides));
     }
   }
   std::move(user_callback).Run(access_result.status.IsInclude());
@@ -979,6 +981,7 @@
               net::CookieAccessResult(status)));
       OnCookiesAccessed(mojom::CookieAccessDetails::New(
           mojom::CookieAccessDetails::Type::kChange, url,
+          /*frame_origin=*/std::nullopt,
           isolation_info_.top_frame_origin().value_or(url::Origin()),
           site_for_cookies, std::move(result_with_access_result), std::nullopt,
           is_ad_tagged,
diff --git a/services/network/url_loader.cc b/services/network/url_loader.cc
index ede0531..5450162 100644
--- a/services/network/url_loader.cc
+++ b/services/network/url_loader.cc
@@ -3121,6 +3121,7 @@
     if (!reported_cookies.empty()) {
       cookie_access_details_.emplace_back(mojom::CookieAccessDetails::New(
           mojom::CookieAccessDetails::Type::kRead, url_request_->url(),
+          url_request_->isolation_info().frame_origin(),
           url_request_->isolation_info().top_frame_origin().value_or(
               url::Origin()),
           url_request_->site_for_cookies(), std::move(reported_cookies),
@@ -3456,6 +3457,7 @@
   if (!reported_cookies.empty()) {
     cookie_access_details_.emplace_back(mojom::CookieAccessDetails::New(
         mojom::CookieAccessDetails::Type::kChange, url_request_->url(),
+        url_request_->isolation_info().frame_origin(),
         url_request_->isolation_info().top_frame_origin().value_or(
             url::Origin()),
         url_request_->site_for_cookies(), std::move(reported_cookies),
diff --git a/services/on_device_model/ml/chrome_ml_types.h b/services/on_device_model/ml/chrome_ml_types.h
index 8ce19b4d..2653f81 100644
--- a/services/on_device_model/ml/chrome_ml_types.h
+++ b/services/on_device_model/ml/chrome_ml_types.h
@@ -44,6 +44,8 @@
   // The APU accelerator backend. Only available on devices with APU, and need
   // special APU model files.
   kApuBackend,
+  // The CPU backend.
+  kCpuBackend,
 };
 
 }  // namespace ml
diff --git a/services/on_device_model/ml/chrome_ml_types_traits.cc b/services/on_device_model/ml/chrome_ml_types_traits.cc
index ca0f7bc9..c3b8e86 100644
--- a/services/on_device_model/ml/chrome_ml_types_traits.cc
+++ b/services/on_device_model/ml/chrome_ml_types_traits.cc
@@ -120,6 +120,8 @@
       return on_device_model::mojom::ModelBackendType::kGpu;
     case ml::ModelBackendType::kApuBackend:
       return on_device_model::mojom::ModelBackendType::kApu;
+    case ml::ModelBackendType::kCpuBackend:
+      return on_device_model::mojom::ModelBackendType::kCpu;
   }
   NOTREACHED();
 }
@@ -136,6 +138,9 @@
     case on_device_model::mojom::ModelBackendType::kApu:
       *output = ml::ModelBackendType::kApuBackend;
       return true;
+    case on_device_model::mojom::ModelBackendType::kCpu:
+      *output = ml::ModelBackendType::kCpuBackend;
+      return true;
   }
   return false;
 }
diff --git a/services/on_device_model/ml/on_device_model_executor.cc b/services/on_device_model/ml/on_device_model_executor.cc
index d57f3ca6..4de2b0c 100644
--- a/services/on_device_model/ml/on_device_model_executor.cc
+++ b/services/on_device_model/ml/on_device_model_executor.cc
@@ -447,11 +447,15 @@
   ChromeMLModelData data;
   std::string weights_path_str = assets.weights_path.AsUTF8Unsafe();
   std::string sp_model_path_str = assets.sp_model_path.AsUTF8Unsafe();
-  if (params->backend_type == ml::ModelBackendType::kGpuBackend) {
-    data.weights_file = assets.weights.TakePlatformFile();
-  } else {
-    data.model_path = weights_path_str.data();
-    data.sentencepiece_model_path = sp_model_path_str.data();
+  switch (params->backend_type) {
+    case ModelBackendType::kGpuBackend:
+    case ModelBackendType::kCpuBackend:
+      data.weights_file = assets.weights.TakePlatformFile();
+      break;
+    case ModelBackendType::kApuBackend:
+      data.model_path = weights_path_str.data();
+      data.sentencepiece_model_path = sp_model_path_str.data();
+      break;
   }
   ChromeMLModelDescriptor descriptor = {
       .backend_type = params->backend_type,
diff --git a/services/on_device_model/on_device_model_service.cc b/services/on_device_model/on_device_model_service.cc
index d9f1ad0..546d3cd 100644
--- a/services/on_device_model/on_device_model_service.cc
+++ b/services/on_device_model/on_device_model_service.cc
@@ -354,7 +354,8 @@
         "Unable to load chrome_ml library.");
     return nullptr;
   }
-  if (ml::IsGpuBlocked(chrome_ml->api())) {
+  if (!optimization_guide::features::ForceCpuBackendForOnDeviceModel() &&
+      ml::IsGpuBlocked(chrome_ml->api())) {
     receiver.ResetWithReason(
         static_cast<uint32_t>(ServiceDisconnectReason::kGpuBlocked),
         "The device's GPU is not supported.");
diff --git a/services/on_device_model/public/mojom/on_device_model_service.mojom b/services/on_device_model/public/mojom/on_device_model_service.mojom
index 01010de..75961c59 100644
--- a/services/on_device_model/public/mojom/on_device_model_service.mojom
+++ b/services/on_device_model/public/mojom/on_device_model_service.mojom
@@ -34,6 +34,8 @@
   // The APU accelerator backend. Only available on devices with APU, and need
   // special APU model files.
   kApu,
+  // The CPU backend.
+  kCpu,
 };
 
 // Options for specifying the performance characteristics of the model to load.
diff --git a/services/tracing/public/cpp/perfetto/track_name_recorder.cc b/services/tracing/public/cpp/perfetto/track_name_recorder.cc
index 0126c294..c70b21a9 100644
--- a/services/tracing/public/cpp/perfetto/track_name_recorder.cc
+++ b/services/tracing/public/cpp/perfetto/track_name_recorder.cc
@@ -109,8 +109,7 @@
 #if BUILDFLAG(IS_ANDROID)
   // Host app package name is only recorded if the corresponding TraceLog
   // setting is set to true.
-  if (base::trace_event::TraceLog::GetInstance()
-          ->ShouldRecordHostAppPackageName()) {
+  if (record_host_app_package_name_) {
     // Host app package name is used to group information from different
     // processes that "belong" to the same WebView app.
     if (process_type == ChromeProcessDescriptor::PROCESS_RENDERER ||
@@ -200,4 +199,9 @@
   SetProcessTrackDescriptor(process_name, process_type);
 }
 
+void TrackNameRecorder::SetRecordHostAppPackageName(
+    bool record_host_app_package_name) {
+  record_host_app_package_name_ = record_host_app_package_name;
+}
+
 }  // namespace tracing
diff --git a/services/tracing/public/cpp/perfetto/track_name_recorder.h b/services/tracing/public/cpp/perfetto/track_name_recorder.h
index e89f004..e73851ebc 100644
--- a/services/tracing/public/cpp/perfetto/track_name_recorder.h
+++ b/services/tracing/public/cpp/perfetto/track_name_recorder.h
@@ -46,6 +46,8 @@
   void UpdateProcessLabel(int label_id, const std::string& current_label);
   void RemoveProcessLabel(int label_id);
 
+  void SetRecordHostAppPackageName(bool record_host_app_package_name);
+
  private:
   friend class base::NoDestructor<TrackNameRecorder>;
   using ChromeProcessDescriptor =
@@ -66,6 +68,7 @@
   }
 
   int64_t process_start_timestamp_;
+  bool record_host_app_package_name_{false};
 
   // This lock protects `process_labels_` member accesses from arbitrary
   // threads.
diff --git a/skia/ext/skia_histogram.cc b/skia/ext/skia_histogram.cc
index 20580ea..a8022fc 100644
--- a/skia/ext/skia_histogram.cc
+++ b/skia/ext/skia_histogram.cc
@@ -56,20 +56,35 @@
 }
 
 // Wrapper around HISTOGRAM_POINTER_USE - mimics
+// UMA_HISTOGRAM_CUSTOM_COUNTS but allows for an external
+// atomic_histogram_pointer.
+void HistogramCustomCounts(std::atomic_uintptr_t* atomic_histogram_pointer,
+                           const char* name,
+                           int sample,
+                           int min_count,
+                           int max_count,
+                           size_t bucket_count) {
+  HISTOGRAM_POINTER_USE(atomic_histogram_pointer, name, Add(sample),
+                        base::Histogram::FactoryGet(
+                            name, min_count, max_count, bucket_count,
+                            base::HistogramBase::kUmaTargetedHistogramFlag));
+}
+
+// Wrapper around HISTOGRAM_POINTER_USE - mimics
 // UMA_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES but allows for an external
 // atomic_histogram_pointer.
 void HistogramCustomMicrosecondsTimes(
     std::atomic_uintptr_t* atomic_histogram_pointer,
     const char* name,
-    int64_t sampleUSec,
-    unsigned minUSec,
-    unsigned maxUSec,
-    size_t bucketCount) {
+    int64_t sample_usec,
+    unsigned min_usec,
+    unsigned max_usec,
+    size_t bucket_count) {
   HISTOGRAM_POINTER_USE(
-      atomic_histogram_pointer, name, Add(sampleUSec),
+      atomic_histogram_pointer, name, Add(sample_usec),
       base::Histogram::FactoryMicrosecondsTimeGet(
-          name, base::Microseconds(minUSec), base::Microseconds(maxUSec),
-          bucketCount, base::HistogramBase::kUmaTargetedHistogramFlag));
+          name, base::Microseconds(min_usec), base::Microseconds(max_usec),
+          bucket_count, base::HistogramBase::kUmaTargetedHistogramFlag));
 }
 
 }  // namespace skia
diff --git a/skia/ext/skia_histogram.h b/skia/ext/skia_histogram.h
index 4640982e..9e78cc2 100644
--- a/skia/ext/skia_histogram.h
+++ b/skia/ext/skia_histogram.h
@@ -36,11 +36,16 @@
 #define SK_HISTOGRAM_MEMORY_KB(name, sample) \
   SK_HISTOGRAM_POINTER_HELPER(skia::HistogramMemoryKB, "Skia." name, sample)
 
-#define SK_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES(name, sampleUSec, minUSec, \
-                                               maxUSec, bucketCount)      \
-  SK_HISTOGRAM_POINTER_HELPER(skia::HistogramCustomMicrosecondsTimes,     \
-                              "Skia." name, sampleUSec, minUSec, maxUSec, \
-                              bucketCount);
+#define SK_HISTOGRAM_CUSTOM_COUNTS(name, sample, min_count, max_count,   \
+                                   bucket_count)                         \
+  SK_HISTOGRAM_POINTER_HELPER(skia::HistogramCustomCounts, "Skia." name, \
+                              sample, min_count, max_count, bucket_count);
+
+#define SK_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES(name, sample_usec, min_usec,  \
+                                               max_usec, bucket_count)       \
+  SK_HISTOGRAM_POINTER_HELPER(skia::HistogramCustomMicrosecondsTimes,        \
+                              "Skia." name, sample_usec, min_usec, max_usec, \
+                              bucket_count);
 
 namespace skia {
 
@@ -58,13 +63,19 @@
 void HistogramMemoryKB(std::atomic_uintptr_t* atomic_histogram_pointer,
                        const char* name,
                        int sample);
+void HistogramCustomCounts(std::atomic_uintptr_t* atomic_histogram_pointer,
+                           const char* name,
+                           int sample,
+                           int min_count,
+                           int max_count,
+                           size_t bucket_count);
 void HistogramCustomMicrosecondsTimes(
     std::atomic_uintptr_t* atomic_histogram_pointer,
     const char* name,
-    int64_t sampleUSec,
-    unsigned minUSec,
-    unsigned maxUSec,
-    size_t bucketCount);
+    int64_t sample_usec,
+    unsigned min_usec,
+    unsigned max_usec,
+    size_t bucket_count);
 
 }  // namespace skia
 
diff --git a/testing/buildbot/filters/BUILD.gn b/testing/buildbot/filters/BUILD.gn
index dc0b40e..8258e48 100644
--- a/testing/buildbot/filters/BUILD.gn
+++ b/testing/buildbot/filters/BUILD.gn
@@ -88,7 +88,6 @@
     "//testing/buildbot/filters/chromeos.msan.browser_tests.oobe_positive.filter",
     "//testing/buildbot/filters/code_coverage.browser_tests.filter",
     "//testing/buildbot/filters/fuchsia.browser_tests.filter",
-    "//testing/buildbot/filters/linux-chromeos.browser_tests.pixel_tests.filter",
     "//testing/buildbot/filters/linux.linux-rel-cft.browser_tests.filter",
     "//testing/buildbot/filters/mac.mac-rel-cft.browser_tests.filter",
     "//testing/buildbot/filters/mac.mac-rel.browser_tests.filter",
diff --git a/testing/buildbot/filters/linux-chromeos.browser_tests.pixel_tests.filter b/testing/buildbot/filters/linux-chromeos.browser_tests.pixel_tests.filter
deleted file mode 100644
index 34442cc..0000000
--- a/testing/buildbot/filters/linux-chromeos.browser_tests.pixel_tests.filter
+++ /dev/null
@@ -1,13 +0,0 @@
-# This is the filter for browser_tests that support pixel tests on ChromeOS.
-# In order for pixel tests to work, you *must* include your test here.
-
-All/CurtainModeChromeOsPixelTest.*
-
-# TODO(b/305779741) re-enable this test.
-# *PersonalizationAppIntegrationPixelBrowserTest.*
-
-*MahiMenuPixelBrowserTest*
-
-*QuickAnswersPixelTest*
-
-*CandidateWindowViewPixelBrowserTest*
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index b22b41c..a8f8165 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -7609,7 +7609,7 @@
             ],
             "experiments": [
                 {
-                    "name": "Enabled",
+                    "name": "Enabled_135",
                     "params": {
                         "allowlist": "a.com,b.com,c.com,d.com,e.com,f.com,web-platform.test,www1.web-platform.test,127.0.0.1,example.test,www.google.com",
                         "rollout_percent": "0"
@@ -14019,6 +14019,25 @@
             ]
         }
     ],
+    "LongAnimationFrameSourceCharPosition": [
+        {
+            "platforms": [
+                "android",
+                "chromeos",
+                "linux",
+                "mac",
+                "windows"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "LongAnimationFrameSourceCharPosition"
+                    ]
+                }
+            ]
+        }
+    ],
     "LowPriorityAsyncScriptExecution": [
         {
             "platforms": [
@@ -17129,6 +17148,57 @@
             ]
         }
     ],
+    "PerformanceInterventionAlgorithm": [
+        {
+            "platforms": [
+                "chromeos",
+                "linux",
+                "mac",
+                "windows"
+            ],
+            "experiments": [
+                {
+                    "name": "EnabledLessAggressive_20250326",
+                    "params": {
+                        "minimum_time_reshow": "1h",
+                        "no_acceptance_back_off": "7d",
+                        "scale_max_times_per_day": "3",
+                        "scale_max_times_per_week": "21",
+                        "window_size": "10"
+                    },
+                    "enable_features": [
+                        "PerformanceInterventionNotificationImprovements"
+                    ]
+                },
+                {
+                    "name": "EnabledMediumAggressive_20250326",
+                    "params": {
+                        "minimum_time_reshow": "1h",
+                        "no_acceptance_back_off": "7d",
+                        "scale_max_times_per_day": "10",
+                        "scale_max_times_per_week": "70",
+                        "window_size": "10"
+                    },
+                    "enable_features": [
+                        "PerformanceInterventionNotificationImprovements"
+                    ]
+                },
+                {
+                    "name": "EnabledMoreAggressive_20250326",
+                    "params": {
+                        "minimum_time_reshow": "1h",
+                        "no_acceptance_back_off": "7d",
+                        "scale_max_times_per_day": "24",
+                        "scale_max_times_per_week": "168",
+                        "window_size": "10"
+                    },
+                    "enable_features": [
+                        "PerformanceInterventionNotificationImprovements"
+                    ]
+                }
+            ]
+        }
+    ],
     "PermissionElementPromptPositioning": [
         {
             "platforms": [
@@ -21657,22 +21727,6 @@
             ]
         }
     ],
-    "ScreencastServerBasedUSMRNNTLocales": [
-        {
-            "platforms": [
-                "chromeos"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled",
-                    "enable_features": [
-                        "InternalServerSideSpeechRecognitionUSMModelFinch",
-                        "ProjectorUseUSMForS3"
-                    ]
-                }
-            ]
-        }
-    ],
     "ScrimForBrowserWindowModal": [
         {
             "platforms": [
diff --git a/third_party/angle b/third_party/angle
index 253ceef5..e4bfa48 160000
--- a/third_party/angle
+++ b/third_party/angle
@@ -1 +1 @@
-Subproject commit 253ceef5eb2e27298a5fcbf36300c88925dd6b8f
+Subproject commit e4bfa483a42277d4dbc626a1ef48131fcaf74a73
diff --git a/third_party/blink/common/features.cc b/third_party/blink/common/features.cc
index 21a7af8..5533944 100644
--- a/third_party/blink/common/features.cc
+++ b/third_party/blink/common/features.cc
@@ -2614,10 +2614,6 @@
              "kEmulateLoadStartedForInspectorOncePerResource",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
-BASE_FEATURE(kThreadedScrollPreventRenderingStarvation,
-             "ThreadedScrollPreventRenderingStarvation",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
 // If enabled, the usage of unload handlers causes a blocklisted reason for
 // BFCache. The purpose is to capture their source location.
 BASE_FEATURE(kUnloadBlocklisted,
diff --git a/third_party/blink/common/fingerprinting_protection/canvas_noise_token.cc b/third_party/blink/common/fingerprinting_protection/canvas_noise_token.cc
index 5362318..bfd90b6 100644
--- a/third_party/blink/common/fingerprinting_protection/canvas_noise_token.cc
+++ b/third_party/blink/common/fingerprinting_protection/canvas_noise_token.cc
@@ -4,20 +4,40 @@
 
 #include "third_party/blink/public/common/fingerprinting_protection/canvas_noise_token.h"
 
+#include "base/lazy_instance.h"
+#include "base/synchronization/lock.h"
+#include "base/thread_annotations.h"
+
 namespace blink {
 
 namespace {
-uint64_t noise_token_ = 0;
+// Protect |noise_token|. |noise_token| is written by the main renderer thread
+// and is read by the main or worker thread(s).
+static base::LazyInstance<base::Lock>::Leaky g_lock = LAZY_INSTANCE_INITIALIZER;
+uint64_t noise_token GUARDED_BY(g_lock.Get()){0};
+
+// For debugging, bool that indicates the token has been initialized.
+#if DCHECK_IS_ON()
+bool initialized = false;
+#endif
 }  // namespace
 
 // static
 void CanvasNoiseToken::Set(uint64_t token) {
-  noise_token_ = token;
+  base::AutoLock lock(g_lock.Get());
+  noise_token = token;
+#if DCHECK_IS_ON()
+  initialized = true;
+#endif
 }
 
 // static
 uint64_t CanvasNoiseToken::Get() {
-  return noise_token_;
+  base::AutoLock lock(g_lock.Get());
+#if DCHECK_IS_ON()
+  DCHECK(initialized);
+#endif
+  return noise_token;
 }
 
 }  // namespace blink
diff --git a/third_party/blink/public/common/features.h b/third_party/blink/public/common/features.h
index 375e399..6ef45488 100644
--- a/third_party/blink/public/common/features.h
+++ b/third_party/blink/public/common/features.h
@@ -129,14 +129,6 @@
     std::string,
     kBackgroundTracingPerformanceMark_AllowList);
 
-// Finch flag for preventing rendering starvation during threaded scrolling.
-// With this feature enabled, the compositor task queue priority remains low
-// during compositor gestures, e.g. scrolling, but main thread compositor tasks
-// are prioritized if a frame has not been produced recently (a configurable
-// duration), until the next BeginMainFrame.
-BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(
-    kThreadedScrollPreventRenderingStarvation);
-
 // Block all MIDI access with the MIDI_SYSEX permission
 BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kBlockMidiByDefault);
 
diff --git a/third_party/blink/public/mojom/shared_storage/shared_storage.mojom b/third_party/blink/public/mojom/shared_storage/shared_storage.mojom
index 154bc003..714abaf5 100644
--- a/third_party/blink/public/mojom/shared_storage/shared_storage.mojom
+++ b/third_party/blink/public/mojom/shared_storage/shared_storage.mojom
@@ -151,7 +151,7 @@
   // with '-'. Return an empty `error_message` on success, and return an
   // non-empty `error_message` on failure.
   SharedStorageBatchUpdate(
-    array<network.mojom.SharedStorageModifierMethodWithOptions> methods_with_options,
+    network.mojom.SharedStorageBatchUpdateMethodsArgument methods_with_options,
     network.mojom.LockName? with_lock)
       => (string error_message);
 };
diff --git a/third_party/blink/public/mojom/shared_storage/shared_storage_worklet_service.mojom b/third_party/blink/public/mojom/shared_storage/shared_storage_worklet_service.mojom
index 03d8627..c4d68657 100644
--- a/third_party/blink/public/mojom/shared_storage/shared_storage_worklet_service.mojom
+++ b/third_party/blink/public/mojom/shared_storage/shared_storage_worklet_service.mojom
@@ -74,7 +74,7 @@
   // with '-'. Return an empty `error_message` on success, and return an
   // non-empty `error_message` on failure.
   SharedStorageBatchUpdate(
-    array<network.mojom.SharedStorageModifierMethodWithOptions> methods_with_options,
+    network.mojom.SharedStorageBatchUpdateMethodsArgument methods_with_options,
     network.mojom.LockName? with_lock)
       => (string error_message);
 
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 cf47a7ac..c58e7000 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
@@ -4842,6 +4842,7 @@
   kSummarizer_OutputLanguage = 5456,
   kSummarizer_SharedContext = 5457,
   kSummarizer_Type = 5458,
+  kCrossOriginSameSiteCookieAccessViaStorageAccessAPI = 5459,
 
   // 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/generated_in_core.gni b/third_party/blink/renderer/bindings/generated_in_core.gni
index 17f0ea2..2e939292 100644
--- a/third_party/blink/renderer/bindings/generated_in_core.gni
+++ b/third_party/blink/renderer/bindings/generated_in_core.gni
@@ -58,6 +58,8 @@
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_mutation_callback.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_navigation_intercept_handler.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_navigation_intercept_handler.h",
+  "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_navigation_intercept_precommit_handler.cc",
+  "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_navigation_intercept_precommit_handler.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_no_argument_constructor.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_no_argument_constructor.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_observable_inspector_abort_handler.cc",
@@ -561,8 +563,6 @@
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_mojo_interface_interceptor_scope.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_mojo_scope.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_mojo_scope.h",
-  "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_navigation_commit_behavior.cc",
-  "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_navigation_commit_behavior.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_navigation_entropy.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_navigation_entropy.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_navigation_focus_reset.cc",
@@ -1252,6 +1252,8 @@
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_navigation_destination.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_navigation_history_entry.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_navigation_history_entry.h",
+  "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_navigation_precommit_controller.cc",
+  "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_navigation_precommit_controller.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_navigation_transition.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_navigation_transition.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_navigator.cc",
diff --git a/third_party/blink/renderer/bindings/generated_in_modules.gni b/third_party/blink/renderer/bindings/generated_in_modules.gni
index 59bd2af7..b023253 100644
--- a/third_party/blink/renderer/bindings/generated_in_modules.gni
+++ b/third_party/blink/renderer/bindings/generated_in_modules.gni
@@ -1974,8 +1974,6 @@
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_ai_language_model_params.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_ai_create_monitor.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_ai_create_monitor.h",
-  "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_ai_language_detector_factory.cc",
-  "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_ai_language_detector_factory.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_ai_rewriter.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_ai_rewriter.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_ai_summarizer.cc",
diff --git a/third_party/blink/renderer/bindings/idl_in_core.gni b/third_party/blink/renderer/bindings/idl_in_core.gni
index cd3d3a6..42e1ddef 100644
--- a/third_party/blink/renderer/bindings/idl_in_core.gni
+++ b/third_party/blink/renderer/bindings/idl_in_core.gni
@@ -539,6 +539,7 @@
   "//third_party/blink/renderer/core/navigation_api/navigation_intercept_options.idl",
   "//third_party/blink/renderer/core/navigation_api/navigation_navigate_options.idl",
   "//third_party/blink/renderer/core/navigation_api/navigation_options.idl",
+  "//third_party/blink/renderer/core/navigation_api/navigation_precommit_controller.idl",
   "//third_party/blink/renderer/core/navigation_api/navigation_reload_options.idl",
   "//third_party/blink/renderer/core/navigation_api/navigation_result.idl",
   "//third_party/blink/renderer/core/navigation_api/navigation_transition.idl",
diff --git a/third_party/blink/renderer/bindings/idl_in_modules.gni b/third_party/blink/renderer/bindings/idl_in_modules.gni
index 43ce8f95..0dbdc3fb 100644
--- a/third_party/blink/renderer/bindings/idl_in_modules.gni
+++ b/third_party/blink/renderer/bindings/idl_in_modules.gni
@@ -27,7 +27,6 @@
   "//third_party/blink/renderer/modules/ai/ai_rewriter.idl",
   "//third_party/blink/renderer/modules/ai/ai_summarizer.idl",
   "//third_party/blink/renderer/modules/ai/ai_writer.idl",
-  "//third_party/blink/renderer/modules/ai/on_device_translation/ai_language_detector_factory.idl",
   "//third_party/blink/renderer/modules/ai/on_device_translation/language_detector.idl",
   "//third_party/blink/renderer/modules/ai/on_device_translation/translator.idl",
   "//third_party/blink/renderer/modules/ai/window_or_worker_global_scope_ai.idl",
diff --git a/third_party/blink/renderer/core/animation/compositor_animations.cc b/third_party/blink/renderer/core/animation/compositor_animations.cc
index fc357cb..ff6f103 100644
--- a/third_party/blink/renderer/core/animation/compositor_animations.cc
+++ b/third_party/blink/renderer/core/animation/compositor_animations.cc
@@ -266,10 +266,6 @@
                       WebFeature::kStaticPropertyInAnimation);
   }
 
-  // Presently limited to a single Native Paint Worklet property per
-  // animation.
-  NativePaintImageGenerator* generator = nullptr;
-
   // Limit to one native property and one CSS custom property per animation.
   for (const auto& property : properties) {
     if (!property.IsCSSProperty()) {
@@ -299,53 +295,54 @@
       }
     }
 
-    switch (property.GetCSSProperty().PropertyID()) {
-      case CSSPropertyID::kBackgroundColor:
-      case CSSPropertyID::kBoxShadow:
-      case CSSPropertyID::kClipPath:
-        if (!layout_object) {
-          // Not having a layout object is a reason for not compositing marked
-          // in CompositorAnimations::CheckCanStartElementOnCompositor.
-          break;
-        }
-        if (generator) {
-          // Presently limited to a single Native Paint Worklet property per
-          // animation.
-          DefaultToUnsupportedProperty(unsupported_properties_for_tracing,
-                                       property, &reasons);
-          break;
-        }
-        if (property.GetCSSProperty().PropertyID() ==
-                CSSPropertyID::kBackgroundColor &&
-            RuntimeEnabledFeatures::CompositeBGColorAnimationEnabled()) {
-          generator = target_element.GetDocument()
-                          .GetFrame()
-                          ->GetBackgroundColorPaintImageGenerator();
-        } else if (property.GetCSSProperty().PropertyID() ==
-                       CSSPropertyID::kBoxShadow &&
-                   RuntimeEnabledFeatures ::
-                       CompositeBoxShadowAnimationEnabled()) {
-          generator = target_element.GetDocument()
-                          .GetFrame()
-                          ->GetBoxShadowPaintImageGenerator();
-        } else if (property.GetCSSProperty().PropertyID() ==
-                       CSSPropertyID::kClipPath &&
-                   RuntimeEnabledFeatures::
-                       CompositeClipPathAnimationEnabled()) {
-          generator = target_element.GetDocument()
-                          .GetFrame()
-                          ->GetClipPathPaintImageGenerator();
-        }
+    // Presently native paint worklets only work with monotonic timelines.
+    NativePaintImageGenerator* generator = nullptr;
+    Animation::NativePaintWorkletReasons npw_reasons =
+        Animation::NativePaintWorkletProperties::kNoPaintWorklet;
+    if (animation_to_add) {
+      AnimationTimeline* timeline = animation_to_add->TimelineInternal();
+      if (timeline && timeline->IsMonotonicallyIncreasing()) {
+        npw_reasons = animation_to_add->GetNativePaintWorkletReasons();
+      }
+    }
 
-        if (!generator ||
-            !generator->GetAnimationIfCompositable(&target_element)) {
-          DefaultToUnsupportedProperty(unsupported_properties_for_tracing,
-                                       property, &reasons);
-        }
-        break;
+    if (layout_object) {
+      // Not having a layout object is a reason for not compositing marked
+      // in CompositorAnimations::CheckCanStartElementOnCompositor.
+      switch (property.GetCSSProperty().PropertyID()) {
+        case CSSPropertyID::kBackgroundColor:
+          if (npw_reasons == Animation::NativePaintWorkletProperties::
+                                 kBackgroundColorPaintWorklet) {
+            DCHECK(RuntimeEnabledFeatures::CompositeBGColorAnimationEnabled());
+            generator = target_element.GetDocument()
+                            .GetFrame()
+                            ->GetBackgroundColorPaintImageGenerator();
+          }
+          if (!generator ||
+              !generator->GetAnimationIfCompositable(&target_element)) {
+            DefaultToUnsupportedProperty(unsupported_properties_for_tracing,
+                                         property, &reasons);
+          }
+          break;
 
-      default:
-        break;
+        case CSSPropertyID::kClipPath:
+          if (npw_reasons ==
+              Animation::NativePaintWorkletProperties::kClipPathPaintWorklet) {
+            DCHECK(RuntimeEnabledFeatures::CompositeClipPathAnimationEnabled());
+            generator = target_element.GetDocument()
+                            .GetFrame()
+                            ->GetClipPathPaintImageGenerator();
+          }
+          if (!generator ||
+              !generator->GetAnimationIfCompositable(&target_element)) {
+            DefaultToUnsupportedProperty(unsupported_properties_for_tracing,
+                                         property, &reasons);
+          }
+          break;
+
+        default:
+          break;
+      }
     }
 
     const PropertySpecificKeyframeVector& keyframes =
@@ -381,7 +378,6 @@
           // like regular filters do, so they can still be composited.
           break;
         case CSSPropertyID::kBackgroundColor:
-        case CSSPropertyID::kBoxShadow:
         case CSSPropertyID::kClipPath:
           // Handled above. No additional checks required on a per-keyframe
           // basis.
diff --git a/third_party/blink/renderer/core/animation/compositor_animations_test.cc b/third_party/blink/renderer/core/animation/compositor_animations_test.cc
index 0364b56..d5b9d75 100644
--- a/third_party/blink/renderer/core/animation/compositor_animations_test.cc
+++ b/third_party/blink/renderer/core/animation/compositor_animations_test.cc
@@ -2995,9 +2995,7 @@
   EXPECT_TRUE(IsUseCounted(WebFeature::kStaticPropertyInAnimation));
 }
 
-// TODO(crbug.com/403708813): Adding support for native paint worklets with non-
-// monotonic timelines, introduced a regression. Feature currently disabled.
-TEST_P(AnimationCompositorAnimationsTest, DISABLED_ClipPathWithViewTimeline) {
+TEST_P(AnimationCompositorAnimationsTest, ClipPathWithViewTimeline) {
   std::unique_ptr<ScopedCompositeClipPathAnimationForTest>
       scoped_composite_clip_path_animation =
           std::make_unique<ScopedCompositeClipPathAnimationForTest>(true);
@@ -3041,20 +3039,20 @@
   Element* element = GetElementById("target");
   EXPECT_TRUE(element->GetElementAnimations());
   EXPECT_EQ(element->GetElementAnimations()->Animations().size(), 1u);
-  // A scroll-driven animation that is outside the active phase is forced onto
-  // the main thread. No need to tick even on the compositor until scrolled.
+  // TODO(crbug.com/403708813): Native paint worklets do not presently support
+  // non-monotonic timelines. Once fixed, we may get fresh compositing decisions
+  // when entering and leaving the active phase. For now, the compositing
+  // decision remains kNotComposited.
   EXPECT_EQ(element->GetElementAnimations()->CompositedClipPathStatus(),
             ElementAnimations::CompositedPaintStatus::kNotComposited);
   Animation* animation =
       element->GetElementAnimations()->Animations().begin()->key;
   EXPECT_FALSE(animation->HasActiveAnimationsOnCompositor());
-  EXPECT_EQ(CompositorAnimations::kInvalidAnimationOrEffect,
+  EXPECT_EQ(CompositorAnimations::kUnsupportedCSSProperty,
             animation->CheckCanStartAnimationOnCompositor(
                 GetDocument().View()->GetPaintArtifactCompositor()) &
-                CompositorAnimations::kInvalidAnimationOrEffect);
+                CompositorAnimations::kUnsupportedCSSProperty);
 
-  // Shrinking the top spacer places the animated element onscreen and the
-  // animation enters the active phase. A new compositing decision is made.
   GetElementById("adjustable-spacer")
       ->classList()
       .add({"thin"}, ASSERT_NO_EXCEPTION);
@@ -3068,11 +3066,9 @@
   UpdateAllLifecyclePhasesForTest();
 
   EXPECT_EQ(element->GetElementAnimations()->CompositedClipPathStatus(),
-            ElementAnimations::CompositedPaintStatus::kComposited);
-  EXPECT_TRUE(animation->HasActiveAnimationsOnCompositor());
+            ElementAnimations::CompositedPaintStatus::kNotComposited);
+  EXPECT_FALSE(animation->HasActiveAnimationsOnCompositor());
 
-  // Expanding the top spacer places the animated element off-screen and the
-  // animation enters the before phase. A new compositing decision is made.
   GetElementById("adjustable-spacer")
       ->classList()
       .remove({"thin"}, ASSERT_NO_EXCEPTION);
diff --git a/third_party/blink/renderer/core/animation/css/css_animations.cc b/third_party/blink/renderer/core/animation/css/css_animations.cc
index 5786317..117cd7c 100644
--- a/third_party/blink/renderer/core/animation/css/css_animations.cc
+++ b/third_party/blink/renderer/core/animation/css/css_animations.cc
@@ -107,6 +107,7 @@
 class CSSAnimationProxy : public AnimationProxy {
  public:
   CSSAnimationProxy(AnimationTimeline* timeline,
+                    AnimationTrigger* trigger,
                     CSSAnimation* animation,
                     bool is_paused,
                     const std::optional<TimelineOffset>& range_start,
@@ -130,8 +131,11 @@
   }
 
  private:
+  static bool IdleTriggerAllowsVisualEffect(AnimationTrigger* trigger,
+                                            const Timing& timing);
   std::optional<AnimationTimeDelta> CalculateInheritedTime(
       AnimationTimeline* timeline,
+      AnimationTrigger* trigger,
       CSSAnimation* animation,
       const std::optional<TimelineOffset>& range_start,
       const std::optional<TimelineOffset>& range_end,
@@ -147,6 +151,7 @@
 
 CSSAnimationProxy::CSSAnimationProxy(
     AnimationTimeline* timeline,
+    AnimationTrigger* trigger,
     CSSAnimation* animation,
     bool is_paused,
     const std::optional<TimelineOffset>& range_start,
@@ -172,8 +177,9 @@
       timeline ? timeline->CalculateIntrinsicIterationDuration(
                      adjusted_range_start, adjusted_range_end, timing)
                : AnimationTimeDelta();
-  inherited_time_ = CalculateInheritedTime(
-      timeline, animation, adjusted_range_start, adjusted_range_end, timing);
+  inherited_time_ =
+      CalculateInheritedTime(timeline, trigger, animation, adjusted_range_start,
+                             adjusted_range_end, timing);
 
   timeline_duration_ = timeline ? timeline->GetDuration() : std::nullopt;
   if (timeline && timeline->IsProgressBased() && timeline->CurrentTime()) {
@@ -187,6 +193,7 @@
 
 std::optional<AnimationTimeDelta> CSSAnimationProxy::CalculateInheritedTime(
     AnimationTimeline* timeline,
+    AnimationTrigger* trigger,
     CSSAnimation* animation,
     const std::optional<TimelineOffset>& range_start,
     const std::optional<TimelineOffset>& range_end,
@@ -215,6 +222,18 @@
     previous_timeline = animation->TimelineInternal();
   }
 
+  if (trigger) {
+    // If a trigger is present, we might need to prevent its animation's
+    // InertEffect from having visual effects. Ensure this by making
+    // sure the animation's InertEffect's local time is unresolved.
+    if (!animation ||
+        animation->GetTriggerState() == AnimationTriggerState::kIdle) {
+      if (!IdleTriggerAllowsVisualEffect(trigger, timing)) {
+        return std::nullopt;
+      }
+    }
+  }
+
   bool range_changed =
       !animation || ((range_start != animation->GetRangeStartInternal() ||
                       range_end != animation->GetRangeEndInternal()) &&
@@ -303,6 +322,34 @@
   return inherited_time;
 }
 
+// static
+bool CSSAnimationProxy::IdleTriggerAllowsVisualEffect(AnimationTrigger* trigger,
+                                                      const Timing& timing) {
+  AnimationTimeline* timeline = trigger->GetTimelineInternal();
+  if (!timeline || !timeline->IsProgressBased()) {
+    return true;
+  }
+
+  // If an animation will be acted on by a trigger, depending on its
+  // fill-mode, we might need to disable its visual effect before its trigger
+  // acts on it.
+  switch (timing.fill_mode) {
+    case Timing::FillMode::BOTH:
+      return true;
+    case Timing::FillMode::BACKWARDS:
+      return timing.direction == Timing::PlaybackDirection::NORMAL ||
+             timing.direction == Timing::PlaybackDirection::ALTERNATE_NORMAL;
+    case Timing::FillMode::FORWARDS:
+      return timing.direction == Timing::PlaybackDirection::REVERSE ||
+             timing.direction == Timing::PlaybackDirection::ALTERNATE_REVERSE;
+    case Timing::FillMode::NONE:
+    case Timing::FillMode::AUTO:
+      return false;
+  }
+
+  NOTREACHED();
+}
+
 class CSSTransitionProxy : public AnimationProxy {
  public:
   explicit CSSTransitionProxy(std::optional<AnimationTimeDelta> current_time)
@@ -1885,7 +1932,7 @@
             trigger != existing_trigger) {
           DCHECK(!is_animation_style_change);
 
-          CSSAnimationProxy animation_proxy(timeline, animation,
+          CSSAnimationProxy animation_proxy(timeline, trigger, animation,
                                             !will_be_playing, range_start,
                                             range_end, timing);
           update.UpdateAnimation(
@@ -1911,9 +1958,9 @@
                 ? ComputeTrigger(&animating_element, animation_data, i, update,
                                  /* existing_trigger */ nullptr)
                 : nullptr;
-        CSSAnimationProxy animation_proxy(timeline, /* animation */ nullptr,
-                                          is_paused, range_start, range_end,
-                                          timing);
+        CSSAnimationProxy animation_proxy(timeline, trigger,
+                                          /* animation */ nullptr, is_paused,
+                                          range_start, range_end, timing);
         update.StartAnimation(
             name, name_index, i,
             *MakeGarbageCollected<InertEffect>(
diff --git a/third_party/blink/renderer/core/canvas_interventions/canvas_interventions_helper.cc b/third_party/blink/renderer/core/canvas_interventions/canvas_interventions_helper.cc
index f97071d1..5213329 100644
--- a/third_party/blink/renderer/core/canvas_interventions/canvas_interventions_helper.cc
+++ b/third_party/blink/renderer/core/canvas_interventions/canvas_interventions_helper.cc
@@ -8,6 +8,7 @@
 
 #include "base/containers/span.h"
 #include "base/memory/scoped_refptr.h"
+#include "third_party/blink/public/common/fingerprinting_protection/canvas_noise_token.h"
 #include "third_party/blink/public/mojom/devtools/console_message.mojom-shared.h"
 #include "third_party/blink/renderer/core/canvas_interventions/noise_hash.h"
 #include "third_party/blink/renderer/core/canvas_interventions/noise_helper.h"
@@ -16,10 +17,8 @@
 #include "third_party/blink/renderer/platform/graphics/canvas_resource_host.h"
 #include "third_party/blink/renderer/platform/graphics/static_bitmap_image.h"
 #include "third_party/blink/renderer/platform/graphics/unaccelerated_static_bitmap_image.h"
-#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 #include "third_party/blink/renderer/platform/mojo/mojo_binding_context.h"
 #include "third_party/blink/renderer/platform/runtime_feature_state/runtime_feature_state_override_context.h"
-#include "third_party/blink/renderer/platform/supplementable.h"
 #include "third_party/skia/include/core/SkBitmap.h"
 #include "third_party/skia/include/core/SkImageInfo.h"
 #include "ui/gfx/skia_span_util.h"
@@ -28,10 +27,6 @@
 
 namespace {
 
-// TODO(crbug.com/392627601): This value is a placeholder. Use the token that is
-// piped down from the browser.
-constexpr uint64_t kTokenForHash = 0x1234567890123456;
-
 // Returns true when all criteria to apply noising are met. Currently this
 // entails that
 //   1) an operation was made on the canvas that triggers an
@@ -42,7 +37,6 @@
 bool ShouldApplyNoise(CanvasRenderingContext* rendering_context,
                       RasterMode raster_mode,
                       ExecutionContext* execution_context) {
-  // TODO(https://crbug.com/392627601): Ensure session seed is initialized.
   if (!rendering_context) {
     return false;
   }
@@ -63,26 +57,6 @@
 }  // namespace
 
 // static
-const char CanvasInterventionsHelper::kSupplementName[] =
-    "CanvasInterventionsHelper";
-
-CanvasInterventionsHelper::CanvasInterventionsHelper(ExecutionContext& context)
-    : Supplement<ExecutionContext>(context), execution_context_(context) {}
-
-// static
-// TODO(https://crbug.com/392627601): Pipe session seeds.
-CanvasInterventionsHelper* CanvasInterventionsHelper::Create(
-    ExecutionContext* context) {
-  CanvasInterventionsHelper* helper =
-      Supplement<ExecutionContext>::From<CanvasInterventionsHelper>(context);
-  if (!helper) {
-    helper = MakeGarbageCollected<CanvasInterventionsHelper>(*context);
-    Supplement<ExecutionContext>::ProvideTo(*context, helper);
-  }
-  return helper;
-}
-
-// static
 bool CanvasInterventionsHelper::MaybeNoiseSnapshot(
     CanvasRenderingContext* rendering_context,
     ExecutionContext* execution_context,
@@ -118,13 +92,11 @@
   base::span<uint8_t> modify_pixels =
       gfx::SkPixmapToWritableSpan(pixmap_to_noise);
 
-  // TODO(crbug.com/392627601): Use the token that is piped down from the
-  // browser.
-  auto token_hash =
-      NoiseHash(kTokenForHash, execution_context->GetSecurityOrigin()
-                                   ->GetOriginOrPrecursorOriginIfOpaque()
-                                   ->RegistrableDomain()
-                                   .Utf8());
+  auto token_hash = NoiseHash(CanvasNoiseToken::Get(),
+                              execution_context->GetSecurityOrigin()
+                                  ->GetOriginOrPrecursorOriginIfOpaque()
+                                  ->RegistrableDomain()
+                                  .Utf8());
   NoisePixels(token_hash, modify_pixels, pixmap_to_noise.width(),
               pixmap_to_noise.height());
 
diff --git a/third_party/blink/renderer/core/canvas_interventions/canvas_interventions_helper.h b/third_party/blink/renderer/core/canvas_interventions/canvas_interventions_helper.h
index fee4002..4e88529 100644
--- a/third_party/blink/renderer/core/canvas_interventions/canvas_interventions_helper.h
+++ b/third_party/blink/renderer/core/canvas_interventions/canvas_interventions_helper.h
@@ -10,30 +10,16 @@
 #include "third_party/blink/renderer/core/html/canvas/canvas_rendering_context.h"
 #include "third_party/blink/renderer/platform/graphics/canvas_resource_host.h"
 #include "third_party/blink/renderer/platform/graphics/static_bitmap_image.h"
-#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
-#include "third_party/blink/renderer/platform/heap/visitor.h"
-#include "third_party/blink/renderer/platform/supplementable.h"
 
 namespace blink {
 
-class ExecutionContext;
-
-class CORE_EXPORT CanvasInterventionsHelper
-    : public GarbageCollected<CanvasInterventionsHelper>,
-      public Supplement<ExecutionContext> {
+class CORE_EXPORT CanvasInterventionsHelper {
  public:
   enum class CanvasInterventionType {
     kNone,
     kNoise,
   };
 
-  static const char kSupplementName[];
-
-  static CanvasInterventionsHelper* Create(ExecutionContext* execution_context);
-
-  explicit CanvasInterventionsHelper(ExecutionContext& execution_context);
-  virtual ~CanvasInterventionsHelper() = default;
-
   // If allowed, performs noising on a copy of the snapshot StaticBitmapImage
   // and returns the noised snapshot, otherwise it will return the original
   // inputted snapshot.
@@ -41,18 +27,6 @@
                                  ExecutionContext* execution_context,
                                  scoped_refptr<StaticBitmapImage>& snapshot,
                                  RasterMode raster_mode);
-
-  void Trace(Visitor* visitor) const override {
-    visitor->Trace(execution_context_);
-    Supplement<ExecutionContext>::Trace(visitor);
-  }
-
- private:
-  ExecutionContext* GetExecutionContext() const {
-    return execution_context_.Get();
-  }
-
-  Member<ExecutionContext> execution_context_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/dom/document_parser_timing.h b/third_party/blink/renderer/core/dom/document_parser_timing.h
index 77b9c491..cee3d3d4 100644
--- a/third_party/blink/renderer/core/dom/document_parser_timing.h
+++ b/third_party/blink/renderer/core/dom/document_parser_timing.h
@@ -23,7 +23,7 @@
   explicit DocumentParserTiming(Document&);
   DocumentParserTiming(const DocumentParserTiming&) = delete;
   DocumentParserTiming& operator=(const DocumentParserTiming&) = delete;
-  virtual ~DocumentParserTiming() = default;
+  ~DocumentParserTiming() = default;
 
   static DocumentParserTiming& From(Document&);
 
diff --git a/third_party/blink/renderer/core/dom/weak_identifier_map_test.cc b/third_party/blink/renderer/core/dom/weak_identifier_map_test.cc
index 6f2cb687..3d2eec31 100644
--- a/third_party/blink/renderer/core/dom/weak_identifier_map_test.cc
+++ b/third_party/blink/renderer/core/dom/weak_identifier_map_test.cc
@@ -15,7 +15,7 @@
  public:
   class TestClass final : public GarbageCollected<TestClass> {
    public:
-    virtual void Trace(Visitor*) const {}
+    void Trace(Visitor*) const {}
   };
 
   using TestMap = WeakIdentifierMap<TestClass>;
diff --git a/third_party/blink/renderer/core/editing/suggestion/text_suggestion_controller.h b/third_party/blink/renderer/core/editing/suggestion/text_suggestion_controller.h
index 21d013b..c6e1314 100644
--- a/third_party/blink/renderer/core/editing/suggestion/text_suggestion_controller.h
+++ b/third_party/blink/renderer/core/editing/suggestion/text_suggestion_controller.h
@@ -45,7 +45,7 @@
   void OnSuggestionMenuClosed();
   void SuggestionMenuTimeoutCallback(size_t max_number_of_suggestions);
 
-  virtual void Trace(Visitor*) const;
+  void Trace(Visitor*) const;
 
  private:
   friend class TextSuggestionControllerTest;
diff --git a/third_party/blink/renderer/core/exported/web_form_element_observer_impl.h b/third_party/blink/renderer/core/exported/web_form_element_observer_impl.h
index 65abbd2..86dca3f 100644
--- a/third_party/blink/renderer/core/exported/web_form_element_observer_impl.h
+++ b/third_party/blink/renderer/core/exported/web_form_element_observer_impl.h
@@ -30,7 +30,7 @@
   // WebFormElementObserver implementation.
   void Disconnect() override;
 
-  virtual void Trace(Visitor*) const;
+  void Trace(Visitor*) const;
 
  private:
   class ObserverCallback;
diff --git a/third_party/blink/renderer/core/exported/web_settings_impl.h b/third_party/blink/renderer/core/exported/web_settings_impl.h
index eabcddf..5e8d2bfb 100644
--- a/third_party/blink/renderer/core/exported/web_settings_impl.h
+++ b/third_party/blink/renderer/core/exported/web_settings_impl.h
@@ -45,7 +45,7 @@
 class CORE_EXPORT WebSettingsImpl final : public WebSettings {
  public:
   WebSettingsImpl(Settings*, DevToolsEmulator*);
-  virtual ~WebSettingsImpl() = default;
+  ~WebSettingsImpl() = default;
 
   void SetFromStrings(const WebString& name, const WebString& value) override;
 
diff --git a/third_party/blink/renderer/core/frame/animation_frame_timing_monitor.h b/third_party/blink/renderer/core/frame/animation_frame_timing_monitor.h
index 6587f0e..fa95b726 100644
--- a/third_party/blink/renderer/core/frame/animation_frame_timing_monitor.h
+++ b/third_party/blink/renderer/core/frame/animation_frame_timing_monitor.h
@@ -47,7 +47,7 @@
 
   ~AnimationFrameTimingMonitor() override = default;
 
-  virtual void Trace(Visitor*) const;
+  void Trace(Visitor*) const;
 
   void Shutdown();
 
diff --git a/third_party/blink/renderer/core/frame/event_handler_registry.h b/third_party/blink/renderer/core/frame/event_handler_registry.h
index 6547882..752623f 100644
--- a/third_party/blink/renderer/core/frame/event_handler_registry.h
+++ b/third_party/blink/renderer/core/frame/event_handler_registry.h
@@ -34,7 +34,7 @@
     : public GarbageCollected<EventHandlerRegistry> {
  public:
   explicit EventHandlerRegistry(LocalFrame&);
-  virtual ~EventHandlerRegistry();
+  ~EventHandlerRegistry();
 
   // Supported event handler classes. Note that each one may correspond to
   // multiple event types.
diff --git a/third_party/blink/renderer/core/frame/performance_monitor.h b/third_party/blink/renderer/core/frame/performance_monitor.h
index 8860697e..d06b144 100644
--- a/third_party/blink/renderer/core/frame/performance_monitor.h
+++ b/third_party/blink/renderer/core/frame/performance_monitor.h
@@ -112,7 +112,7 @@
   PerformanceMonitor& operator=(const PerformanceMonitor&) = delete;
   ~PerformanceMonitor() override;
 
-  virtual void Trace(Visitor*) const;
+  void Trace(Visitor*) const;
 
  private:
   friend class PerformanceMonitorTest;
diff --git a/third_party/blink/renderer/core/frame/web_local_frame_impl.h b/third_party/blink/renderer/core/frame/web_local_frame_impl.h
index 9ae322eb..f52966d 100644
--- a/third_party/blink/renderer/core/frame/web_local_frame_impl.h
+++ b/third_party/blink/renderer/core/frame/web_local_frame_impl.h
@@ -570,7 +570,7 @@
       const ContextMenuData& data,
       const std::optional<gfx::Point>& host_context_menu_location);
 
-  virtual void Trace(Visitor*) const;
+  void Trace(Visitor*) const;
 
   // Functions to add and remove observers for this object.
   void AddObserver(WebLocalFrameObserver* observer);
diff --git a/third_party/blink/renderer/core/input/keyboard_event_manager.cc b/third_party/blink/renderer/core/input/keyboard_event_manager.cc
index c54bc2f..29e4f82e 100644
--- a/third_party/blink/renderer/core/input/keyboard_event_manager.cc
+++ b/third_party/blink/renderer/core/input/keyboard_event_manager.cc
@@ -546,18 +546,10 @@
 }
 
 void KeyboardEventManager::DefaultTabEventHandler(KeyboardEvent* event) {
-  // TODO (liviutinta) remove TRACE after fixing crbug.com/1063548
-  TRACE_EVENT0("input", "KeyboardEventManager::DefaultTabEventHandler");
   DCHECK_EQ(event->type(), event_type_names::kKeydown);
   // We should only advance focus on tabs if no special modifier keys are held
   // down.
   if (event->ctrlKey() || event->metaKey()) {
-    // TODO (liviutinta) remove TRACE after fixing crbug.com/1063548
-    TRACE_EVENT_INSTANT1(
-        "input", "KeyboardEventManager::DefaultTabEventHandler",
-        TRACE_EVENT_SCOPE_THREAD, "reason_tab_does_not_advance_focus",
-        (event->ctrlKey() ? (event->metaKey() ? "Ctrl+MetaKey+Tab" : "Ctrl+Tab")
-                          : "MetaKey+Tab"));
     return;
   }
 
@@ -565,30 +557,15 @@
   // Option-Tab is a shortcut based on a system-wide preference on Mac but
   // should be ignored on all other platforms.
   if (event->altKey()) {
-    // TODO (liviutinta) remove TRACE after fixing crbug.com/1063548
-    TRACE_EVENT_INSTANT1("input",
-                         "KeyboardEventManager::DefaultTabEventHandler",
-                         TRACE_EVENT_SCOPE_THREAD,
-                         "reason_tab_does_not_advance_focus", "Alt+Tab");
     return;
   }
 #endif
 
   Page* page = frame_->GetPage();
   if (!page) {
-    // TODO (liviutinta) remove TRACE after fixing crbug.com/1063548
-    TRACE_EVENT_INSTANT1("input",
-                         "KeyboardEventManager::DefaultTabEventHandler",
-                         TRACE_EVENT_SCOPE_THREAD,
-                         "reason_tab_does_not_advance_focus", "Page is null");
     return;
   }
   if (!page->TabKeyCyclesThroughElements()) {
-    // TODO (liviutinta) remove TRACE after fixing crbug.com/1063548
-    TRACE_EVENT_INSTANT1(
-        "input", "KeyboardEventManager::DefaultTabEventHandler",
-        TRACE_EVENT_SCOPE_THREAD, "reason_tab_does_not_advance_focus",
-        "TabKeyCyclesThroughElements is false");
     return;
   }
 
@@ -598,11 +575,6 @@
 
   // Tabs can be used in design mode editing.
   if (frame_->GetDocument()->InDesignMode()) {
-    // TODO (liviutinta) remove TRACE after fixing crbug.com/1063548
-    TRACE_EVENT_INSTANT1(
-        "input", "KeyboardEventManager::DefaultTabEventHandler",
-        TRACE_EVENT_SCOPE_THREAD, "reason_tab_does_not_advance_focus",
-        "DesignMode is true");
     return;
   }
 
@@ -612,13 +584,6 @@
                                                   ->GetInputDeviceCapabilities()
                                                   ->FiresTouchEvents(false))) {
     event->SetDefaultHandled();
-  } else {
-    // TODO (liviutinta) remove TRACE after fixing crbug.com/1063548
-    TRACE_EVENT_INSTANT1(
-        "input", "KeyboardEventManager::DefaultTabEventHandler",
-        TRACE_EVENT_SCOPE_THREAD, "reason_tab_does_not_advance_focus",
-        "AdvanceFocus returned false");
-    return;
   }
 }
 
diff --git a/third_party/blink/renderer/core/layout/gap_fragment_data.h b/third_party/blink/renderer/core/layout/gap_fragment_data.h
index 8b082693..75b7b3a 100644
--- a/third_party/blink/renderer/core/layout/gap_fragment_data.h
+++ b/third_party/blink/renderer/core/layout/gap_fragment_data.h
@@ -7,7 +7,8 @@
 
 #include "third_party/blink/renderer/core/style/grid_enums.h"
 #include "third_party/blink/renderer/platform/geometry/layout_unit.h"
-#include "third_party/blink/renderer/platform/heap/collection_support/heap_vector.h"
+#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
+#include "third_party/blink/renderer/platform/wtf/vector.h"
 
 namespace blink {
 
@@ -43,45 +44,10 @@
 
 using GapIntersectionList = Vector<GapIntersection>;
 
-// TODO(samomekarajr): Take this out when done with the new implementation.
-// GapBoundary represents the start and end offsets of a single gap.
-struct GapBoundary {
-  DISALLOW_NEW();
-
- public:
-  GapBoundary(wtf_size_t index, LayoutUnit start_offset, LayoutUnit end_offset)
-      : index(index), start_offset(start_offset), end_offset(end_offset) {}
-
-  void Trace(Visitor* visitor) const { visitor->Trace(intersection_points); }
-
-  wtf_size_t index;
-  LayoutUnit start_offset;
-  LayoutUnit end_offset;
-  HeapVector<LayoutUnit> intersection_points;
-};
-
-using GapBoundaries = HeapVector<GapBoundary>;
-
 // Gap locations are used for painting gap decorations.
 struct GapGeometry : public GarbageCollected<GapGeometry> {
  public:
-  GapBoundaries columns;
-  GapBoundaries rows;
-
-  void AddGapBoundary(GridTrackSizingDirection track_direction,
-                      GapBoundary gap) {
-    (track_direction == kForColumns) ? columns.push_back(gap)
-                                     : rows.push_back(gap);
-  }
-
-  GapBoundaries& GetGapBoundaries(GridTrackSizingDirection track_direction) {
-    return track_direction == kForColumns ? columns : rows;
-  }
-
-  void Trace(Visitor* visitor) const {
-    visitor->Trace(rows);
-    visitor->Trace(columns);
-  }
+  void Trace(Visitor* visitor) const {}
 
   void SetGapIntersections(GridTrackSizingDirection track_direction,
                            Vector<GapIntersectionList>&& intersection_list) {
@@ -123,7 +89,4 @@
 };
 
 }  // namespace blink
-
-WTF_ALLOW_CLEAR_UNUSED_SLOTS_WITH_MEM_FUNCTIONS(blink::GapBoundary)
-
 #endif  // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_GAP_FRAGMENT_DATA_H_
diff --git a/third_party/blink/renderer/core/lcp_critical_path_predictor/lcp_critical_path_predictor.h b/third_party/blink/renderer/core/lcp_critical_path_predictor/lcp_critical_path_predictor.h
index f1eef9e..734d1347 100644
--- a/third_party/blink/renderer/core/lcp_critical_path_predictor/lcp_critical_path_predictor.h
+++ b/third_party/blink/renderer/core/lcp_critical_path_predictor/lcp_critical_path_predictor.h
@@ -31,7 +31,7 @@
     : public GarbageCollected<LCPCriticalPathPredictor> {
  public:
   explicit LCPCriticalPathPredictor(LocalFrame& frame);
-  virtual ~LCPCriticalPathPredictor();
+  ~LCPCriticalPathPredictor();
 
   LCPCriticalPathPredictor(const LCPCriticalPathPredictor&) = delete;
   LCPCriticalPathPredictor& operator=(const LCPCriticalPathPredictor&) = delete;
diff --git a/third_party/blink/renderer/core/loader/prerender_handle.h b/third_party/blink/renderer/core/loader/prerender_handle.h
index d6e7ab36..e53c832 100644
--- a/third_party/blink/renderer/core/loader/prerender_handle.h
+++ b/third_party/blink/renderer/core/loader/prerender_handle.h
@@ -78,7 +78,7 @@
 
   const KURL& Url() const;
 
-  virtual void Trace(Visitor*) const;
+  void Trace(Visitor*) const;
 
  private:
   const KURL url_;
diff --git a/third_party/blink/renderer/core/loader/subresource_filter.h b/third_party/blink/renderer/core/loader/subresource_filter.h
index 81c3bb62..e028208 100644
--- a/third_party/blink/renderer/core/loader/subresource_filter.h
+++ b/third_party/blink/renderer/core/loader/subresource_filter.h
@@ -43,7 +43,7 @@
   bool IsAdResource(const KURL& resource_url,
                     network::mojom::RequestDestination);
 
-  virtual void Trace(Visitor*) const;
+  void Trace(Visitor*) const;
 
  private:
   void ReportLoad(const KURL& resource_url,
diff --git a/third_party/blink/renderer/core/navigation_api/navigate_event.cc b/third_party/blink/renderer/core/navigation_api/navigate_event.cc
index 93d5df1..522fbb2 100644
--- a/third_party/blink/renderer/core/navigation_api/navigate_event.cc
+++ b/third_party/blink/renderer/core/navigation_api/navigate_event.cc
@@ -11,6 +11,7 @@
 #include "third_party/blink/renderer/bindings/core/v8/v8_navigate_event_init.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_navigation_intercept_handler.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_navigation_intercept_options.h"
+#include "third_party/blink/renderer/bindings/core/v8/v8_navigation_intercept_precommit_handler.h"
 #include "third_party/blink/renderer/core/accessibility/ax_object_cache.h"
 #include "third_party/blink/renderer/core/dom/abort_controller.h"
 #include "third_party/blink/renderer/core/dom/abort_signal.h"
@@ -29,26 +30,34 @@
 #include "third_party/blink/renderer/core/loader/document_loader.h"
 #include "third_party/blink/renderer/core/loader/progress_tracker.h"
 #include "third_party/blink/renderer/core/navigation_api/navigation_destination.h"
+#include "third_party/blink/renderer/core/navigation_api/navigation_precommit_controller.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 #include "third_party/blink/renderer/platform/scheduler/public/post_cancellable_task.h"
 
 namespace blink {
 
+enum class HandlerPhase { kPrecommit, kPostcommit };
+
 class NavigateEvent::FulfillReaction final
     : public ThenCallable<IDLUndefined, FulfillReaction> {
  public:
-  explicit FulfillReaction(NavigateEvent* navigate_event)
-      : navigate_event_(navigate_event) {}
+  FulfillReaction(NavigateEvent* navigate_event, HandlerPhase type)
+      : navigate_event_(navigate_event), type_(type) {}
   void Trace(Visitor* visitor) const final {
     ThenCallable<IDLUndefined, FulfillReaction>::Trace(visitor);
     visitor->Trace(navigate_event_);
   }
-  void React(ScriptState*) {
-    navigate_event_->ReactDone(ScriptValue(), /*did_fulfill=*/true);
+  void React(ScriptState* script_state) {
+    if (type_ == HandlerPhase::kPrecommit) {
+      navigate_event_->CommitNow(script_state);
+    } else {
+      navigate_event_->ReactDone(ScriptValue(), /*did_fulfill=*/true);
+    }
   }
 
  private:
   Member<NavigateEvent> navigate_event_;
+  HandlerPhase type_;
 };
 
 class NavigateEvent::RejectReaction final
@@ -138,15 +147,17 @@
     return;
   }
 
-  if (RuntimeEnabledFeatures::NavigateEventCommitBehaviorEnabled() &&
-      !cancelable() && options->hasCommit() &&
-      options->commit().AsEnum() ==
-          V8NavigationCommitBehavior::Enum::kAfterTransition) {
-    exception_state.ThrowDOMException(
-        DOMExceptionCode::kInvalidStateError,
-        "intercept() may only be called with a commit option of "
-        "\"after-transition\" when the navigate event is cancelable.");
-    return;
+  if (options->hasPrecommitHandler()) {
+    CHECK(RuntimeEnabledFeatures::NavigateEventCommitBehaviorEnabled());
+    if (!cancelable()) {
+      exception_state.ThrowDOMException(
+          DOMExceptionCode::kInvalidStateError,
+          "intercept() may only be called with a precommitHandler when the"
+          "navigate event is cancelable.");
+      return;
+    }
+    navigation_action_precommit_handlers_list_.push_back(
+        options->precommitHandler());
   }
 
   if (!HasNavigationActions()) {
@@ -183,81 +194,24 @@
     scroll_behavior_ = options->scroll();
   }
 
-  if (RuntimeEnabledFeatures::NavigateEventCommitBehaviorEnabled()) {
-    if (options->hasCommit()) {
-      if (commit_behavior_ &&
-          commit_behavior_->AsEnum() != options->commit().AsEnum()) {
-        GetExecutionContext()->AddConsoleMessage(
-            MakeGarbageCollected<ConsoleMessage>(
-                mojom::blink::ConsoleMessageSource::kJavaScript,
-                mojom::blink::ConsoleMessageLevel::kWarning,
-                "The \"" + options->commit().AsString() + "\" value for " +
-                    "intercept()'s commit option "
-                    "will override the previously-passed value of \"" +
-                    commit_behavior_->AsString() + "\"."));
-      }
-      commit_behavior_ = options->commit();
-    }
-  }
-
   CHECK(intercept_state_ == InterceptState::kNone ||
         intercept_state_ == InterceptState::kIntercepted);
   intercept_state_ = InterceptState::kIntercepted;
-  if (options->hasHandler())
+  if (options->hasHandler()) {
     navigation_action_handlers_list_.push_back(options->handler());
+  }
 }
 
-bool NavigateEvent::PerformSharedCommitChecks(const String& function_name,
-                                              ExceptionState& exception_state) {
-  if (!PerformSharedChecks(function_name, exception_state)) {
-    return false;
-  }
-
-  if (intercept_state_ == InterceptState::kNone) {
-    exception_state.ThrowDOMException(
-        DOMExceptionCode::kInvalidStateError,
-        "intercept() must be called before " + function_name + "().");
-    return false;
-  }
-  if (ShouldCommitImmediately()) {
-    exception_state.ThrowDOMException(
-        DOMExceptionCode::kInvalidStateError,
-        function_name +
-            "() may only be used if a navigate event was "
-            "intercepted with { commit: 'after-transition' } specified.");
-    return false;
-  }
-  if (IsBeingDispatched()) {
-    exception_state.ThrowDOMException(
-        DOMExceptionCode::kInvalidStateError,
-        function_name + "() may not be called during event dispatch");
-    return false;
-  }
-  if (intercept_state_ == InterceptState::kFinished) {
-    exception_state.ThrowDOMException(
-        DOMExceptionCode::kInvalidStateError,
-        function_name + "() may not be called after transition completes.");
-    return false;
-  }
-  if (intercept_state_ == InterceptState::kCommitted ||
-      intercept_state_ == InterceptState::kScrolled) {
-    exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
-                                      "navigation has already committed.");
-    return false;
-  }
-  return true;
-}
-
-void NavigateEvent::commit(ExceptionState& exception_state) {
-  if (!PerformSharedCommitChecks("commit", exception_state)) {
+void NavigateEvent::Redirect(const String& url_string,
+                             ExceptionState& exception_state) {
+  CHECK_NE(intercept_state_, InterceptState::kNone);
+  if (!PerformSharedChecks("redirect", exception_state)) {
     return;
   }
-  CommitNow();
-}
 
-void NavigateEvent::redirect(const String& url_string,
-                             ExceptionState& exception_state) {
-  if (!PerformSharedCommitChecks("redirect", exception_state)) {
+  if (intercept_state_ > InterceptState::kIntercepted) {
+    exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
+                                      "navigation has already committed.");
     return;
   }
 
@@ -294,23 +248,39 @@
                     WrapWeakPersistent(this)),
       kDelayLoadStart);
 
-  if (ShouldCommitImmediately()) {
-    CommitNow();
+  if (navigation_action_precommit_handlers_list_.empty()) {
+    CommitNow(script_state);
     return;
   }
 
   DomWindow()->GetFrame()->Loader().Progress().ProgressStarted();
-  FinalizeNavigationActionPromisesList();
+
+  HeapVector<Member<V8NavigationInterceptPrecommitHandler>> handlers_list;
+  handlers_list.swap(navigation_action_precommit_handlers_list_);
+
+  auto* controller = MakeGarbageCollected<NavigationPrecommitController>(this);
+
+  HeapVector<MemberScriptPromise<IDLUndefined>> precommit_promises_list;
+  for (auto& function : handlers_list) {
+    ScriptPromise<IDLUndefined> result;
+    if (function->Invoke(this, controller).To(&result)) {
+      precommit_promises_list.push_back(result);
+    }
+  }
+
+  PromiseAll<IDLUndefined>::Create(script_state, precommit_promises_list)
+      .Then(
+          script_state,
+          MakeGarbageCollected<FulfillReaction>(this, HandlerPhase::kPrecommit),
+          MakeGarbageCollected<RejectReaction>(this));
 }
 
-bool NavigateEvent::ShouldCommitImmediately() {
-  return !commit_behavior_ || commit_behavior_->AsEnum() ==
-                                  V8NavigationCommitBehavior::Enum::kImmediate;
-}
-
-void NavigateEvent::CommitNow() {
+void NavigateEvent::CommitNow(ScriptState* script_state) {
   CHECK_EQ(intercept_state_, InterceptState::kIntercepted);
   CHECK(!dispatch_params_->destination_item || !dispatch_params_->state_object);
+  if (signal_->aborted()) {
+    return;
+  }
 
   intercept_state_ = InterceptState::kCommitted;
 
@@ -341,6 +311,8 @@
       dispatch_params_->is_browser_initiated,
       dispatch_params_->is_synchronously_committed_same_document,
       dispatch_params_->soft_navigation_heuristics_task_id);
+
+  React(script_state);
 }
 
 void NavigateEvent::React(ScriptState* script_state) {
@@ -361,8 +333,10 @@
 
   auto promise = PromiseAll<IDLUndefined>::Create(
       script_state, navigation_action_promises_list_);
-  promise.Then(script_state, MakeGarbageCollected<FulfillReaction>(this),
-               MakeGarbageCollected<RejectReaction>(this));
+  promise.Then(
+      script_state,
+      MakeGarbageCollected<FulfillReaction>(this, HandlerPhase::kPostcommit),
+      MakeGarbageCollected<RejectReaction>(this));
 
   if (HasNavigationActions() && DomWindow()) {
     if (AXObjectCache* cache =
@@ -386,11 +360,8 @@
   window->navigation()->ongoing_navigate_event_ = nullptr;
 
   if (intercept_state_ == InterceptState::kIntercepted) {
-    if (did_fulfill) {
-      CommitNow();
-    } else {
-      DomWindow()->GetFrame()->Client()->DidFailAsyncSameDocumentCommit();
-    }
+    CHECK(!did_fulfill);
+    window->GetFrame()->Client()->DidFailAsyncSameDocumentCommit();
   }
 
   if (intercept_state_ >= InterceptState::kCommitted) {
@@ -581,6 +552,7 @@
   visitor->Trace(form_data_);
   visitor->Trace(info_);
   visitor->Trace(source_element_);
+  visitor->Trace(navigation_action_precommit_handlers_list_);
   visitor->Trace(navigation_action_promises_list_);
   visitor->Trace(navigation_action_handlers_list_);
 }
diff --git a/third_party/blink/renderer/core/navigation_api/navigate_event.h b/third_party/blink/renderer/core/navigation_api/navigate_event.h
index d48de45..67e490f 100644
--- a/third_party/blink/renderer/core/navigation_api/navigate_event.h
+++ b/third_party/blink/renderer/core/navigation_api/navigate_event.h
@@ -11,7 +11,6 @@
 #include "third_party/blink/public/web/web_frame_load_type.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
 #include "third_party/blink/renderer/bindings/core/v8/serialization/serialized_script_value.h"
-#include "third_party/blink/renderer/bindings/core/v8/v8_navigation_commit_behavior.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_navigation_focus_reset.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_navigation_scroll_behavior.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_navigation_type.h"
@@ -35,6 +34,7 @@
 class ExceptionState;
 class FormData;
 class V8NavigationInterceptHandler;
+class V8NavigationInterceptPrecommitHandler;
 
 class NavigateEvent final : public Event,
                             public ExecutionContextClient,
@@ -72,13 +72,13 @@
   bool hasUAVisualTransition() const { return has_ua_visual_transition_; }
   Element* sourceElement() const { return source_element_.Get(); }
   void intercept(NavigationInterceptOptions*, ExceptionState&);
-  void commit(ExceptionState&);
-  void redirect(const String& url, ExceptionState&);
 
   // If intercept() was called, this is called after dispatch to either commit
   // the navigation or set the appropritate state for a deferred commit.
   void MaybeCommitImmediately(ScriptState*);
 
+  void Redirect(const String& url, ExceptionState&);
+
   void React(ScriptState* script_state);
 
   void scroll(ExceptionState&);
@@ -98,10 +98,8 @@
 
  private:
   bool PerformSharedChecks(const String& function_name, ExceptionState&);
-  bool PerformSharedCommitChecks(const String& function_name, ExceptionState&);
 
-  bool ShouldCommitImmediately();
-  void CommitNow();
+  void CommitNow(ScriptState*);
 
   void PotentiallyResetTheFocus();
   void PotentiallyProcessScrollBehavior();
@@ -127,7 +125,6 @@
   Member<Element> source_element_;
   std::optional<V8NavigationFocusReset> focus_reset_behavior_ = std::nullopt;
   std::optional<V8NavigationScrollBehavior> scroll_behavior_ = std::nullopt;
-  std::optional<V8NavigationCommitBehavior> commit_behavior_ = std::nullopt;
 
   Member<NavigateEventDispatchParams> dispatch_params_;
 
@@ -140,10 +137,14 @@
   };
   InterceptState intercept_state_ = InterceptState::kNone;
 
+  HeapVector<Member<V8NavigationInterceptPrecommitHandler>>
+      navigation_action_precommit_handlers_list_;
+
   HeapVector<MemberScriptPromise<IDLUndefined>>
       navigation_action_promises_list_;
   HeapVector<Member<V8NavigationInterceptHandler>>
       navigation_action_handlers_list_;
+
   bool did_change_focus_during_intercept_ = false;
 
   // Used to delay the start of the loading UI when the navigation is
diff --git a/third_party/blink/renderer/core/navigation_api/navigate_event.idl b/third_party/blink/renderer/core/navigation_api/navigate_event.idl
index 4f454b2..45487bee 100644
--- a/third_party/blink/renderer/core/navigation_api/navigate_event.idl
+++ b/third_party/blink/renderer/core/navigation_api/navigate_event.idl
@@ -22,7 +22,5 @@
   [RuntimeEnabled=NavigateEventSourceElement] readonly attribute Element? sourceElement;
 
   [RaisesException] void intercept(optional NavigationInterceptOptions options = {});
-  [RaisesException, RuntimeEnabled=NavigateEventCommitBehavior] void commit();
-  [RaisesException, RuntimeEnabled=NavigateEventCommitBehavior] void redirect(USVString url);
   [RaisesException] void scroll();
 };
diff --git a/third_party/blink/renderer/core/navigation_api/navigation_api.cc b/third_party/blink/renderer/core/navigation_api/navigation_api.cc
index bcd3840..46dbcb1 100644
--- a/third_party/blink/renderer/core/navigation_api/navigation_api.cc
+++ b/third_party/blink/renderer/core/navigation_api/navigation_api.cc
@@ -870,10 +870,7 @@
     transition_ = MakeGarbageCollected<NavigationTransition>(
         window_, navigation_type, currentEntry());
     navigate_event->MaybeCommitImmediately(script_state);
-  }
-
-  if (navigate_event->HasNavigationActions() ||
-      params->event_type != NavigateEventType::kCrossDocument) {
+  } else if (params->event_type != NavigateEventType::kCrossDocument) {
     navigate_event->React(script_state);
   }
 
diff --git a/third_party/blink/renderer/core/navigation_api/navigation_intercept_options.idl b/third_party/blink/renderer/core/navigation_api/navigation_intercept_options.idl
index 4e91e93..f508669b 100644
--- a/third_party/blink/renderer/core/navigation_api/navigation_intercept_options.idl
+++ b/third_party/blink/renderer/core/navigation_api/navigation_intercept_options.idl
@@ -4,12 +4,13 @@
 
 // https://wicg.github.io/navigation-api/
 callback NavigationInterceptHandler = Promise<undefined>();
+callback NavigationInterceptPrecommitHandler = Promise<undefined>(NavigationPrecommitController controller);
 
 dictionary NavigationInterceptOptions {
   NavigationInterceptHandler handler;
+  [RuntimeEnabled=NavigateEventCommitBehavior] NavigationInterceptPrecommitHandler precommitHandler;
   NavigationFocusReset focusReset;
   NavigationScrollBehavior scroll;
-  NavigationCommitBehavior commit;
 };
 
 enum NavigationFocusReset {
@@ -21,8 +22,3 @@
   "after-transition",
   "manual"
 };
-
-enum NavigationCommitBehavior {
-  "after-transition",
-  "immediate"
-};
diff --git a/third_party/blink/renderer/core/navigation_api/navigation_precommit_controller.h b/third_party/blink/renderer/core/navigation_api/navigation_precommit_controller.h
new file mode 100644
index 0000000..13021fdf
--- /dev/null
+++ b/third_party/blink/renderer/core/navigation_api/navigation_precommit_controller.h
@@ -0,0 +1,38 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_NAVIGATION_API_NAVIGATION_PRECOMMIT_CONTROLLER_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_NAVIGATION_API_NAVIGATION_PRECOMMIT_CONTROLLER_H_
+
+#include <optional>
+
+#include "third_party/blink/renderer/core/navigation_api/navigate_event.h"
+#include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
+#include "third_party/blink/renderer/platform/heap/member.h"
+
+namespace blink {
+
+class NavigationPrecommitController final : public ScriptWrappable {
+  DEFINE_WRAPPERTYPEINFO();
+
+ public:
+  explicit NavigationPrecommitController(NavigateEvent* event)
+      : navigate_event_(event) {}
+
+  void redirect(const String& url, ExceptionState& exception_state) {
+    navigate_event_->Redirect(url, exception_state);
+  }
+
+  void Trace(Visitor* visitor) const final {
+    ScriptWrappable::Trace(visitor);
+    visitor->Trace(navigate_event_);
+  }
+
+ private:
+  Member<NavigateEvent> navigate_event_;
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_NAVIGATION_API_NAVIGATION_PRECOMMIT_CONTROLLER_H_
diff --git a/third_party/blink/renderer/core/navigation_api/navigation_precommit_controller.idl b/third_party/blink/renderer/core/navigation_api/navigation_precommit_controller.idl
new file mode 100644
index 0000000..0cc1b5ff
--- /dev/null
+++ b/third_party/blink/renderer/core/navigation_api/navigation_precommit_controller.idl
@@ -0,0 +1,12 @@
+// 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.
+
+// https://wicg.github.io/navigation-api/
+[
+  Exposed=Window,
+  RuntimeEnabled=NavigateEventCommitBehavior
+]
+interface NavigationPrecommitController {
+  [RaisesException] undefined redirect(USVString url);
+};
diff --git a/third_party/blink/renderer/core/page/focus_controller.cc b/third_party/blink/renderer/core/page/focus_controller.cc
index 1ff7f7e6..36a8bdc 100644
--- a/third_party/blink/renderer/core/page/focus_controller.cc
+++ b/third_party/blink/renderer/core/page/focus_controller.cc
@@ -1581,8 +1581,6 @@
     mojom::blink::FocusType type,
     bool initial_focus,
     InputDeviceCapabilities* source_capabilities) {
-  // TODO (liviutinta) remove TRACE after fixing crbug.com/1063548
-  TRACE_EVENT0("input", "FocusController::AdvanceFocus");
   switch (type) {
     case mojom::blink::FocusType::kForward:
     case mojom::blink::FocusType::kBackward: {
@@ -1637,8 +1635,6 @@
     mojom::blink::FocusType type,
     bool initial_focus,
     InputDeviceCapabilities* source_capabilities) {
-  // TODO (liviutinta) remove TRACE after fixing crbug.com/1063548
-  TRACE_EVENT0("input", "FocusController::AdvanceFocusInDocumentOrder");
   DCHECK(frame);
   Document* document = frame->GetDocument();
   OwnerMap owner_map;
@@ -1707,11 +1703,6 @@
                                                                   owner_map);
 
     if (!element) {
-      // TODO (liviutinta) remove TRACE after fixing crbug.com/1063548
-      TRACE_EVENT_INSTANT1(
-          "input", "FocusController::AdvanceFocusInDocumentOrder",
-          TRACE_EVENT_SCOPE_THREAD, "reason_for_no_focus_element",
-          "no_recursive_focusable_element");
       return false;
     }
   }
diff --git a/third_party/blink/renderer/core/paint/clip_path_clipper.cc b/third_party/blink/renderer/core/paint/clip_path_clipper.cc
index 6862c444..fb5c3bff 100644
--- a/third_party/blink/renderer/core/paint/clip_path_clipper.cc
+++ b/third_party/blink/renderer/core/paint/clip_path_clipper.cc
@@ -23,7 +23,6 @@
 #include "third_party/blink/renderer/core/style/geometry_box_clip_path_operation.h"
 #include "third_party/blink/renderer/core/style/reference_clip_path_operation.h"
 #include "third_party/blink/renderer/core/style/shape_clip_path_operation.h"
-#include "third_party/blink/renderer/platform/geometry/contoured_rect.h"
 #include "third_party/blink/renderer/platform/graphics/graphics_context.h"
 #include "third_party/blink/renderer/platform/graphics/image.h"
 #include "third_party/blink/renderer/platform/graphics/paint/drawing_display_item.h"
@@ -126,27 +125,6 @@
   }
 }
 
-ContouredRect RoundedReferenceBox(GeometryBox geometry_box,
-                                  const LayoutObject& object) {
-  if (object.IsSVGChild()) {
-    return ContouredRect(
-        FloatRoundedRect(ClipPathClipper::LocalReferenceBox(object)));
-  }
-
-  const auto& box = To<LayoutBoxModelObject>(object);
-  PhysicalRect border_box_rect = BorderBoxRect(box);
-  ContouredRect contoured_border_box_rect =
-      ContouredBorderGeometry::ContouredBorder(box.StyleRef(), border_box_rect);
-  if (geometry_box == GeometryBox::kMarginBox) {
-    contoured_border_box_rect.OutsetForMarginOrShadow(
-        gfx::OutsetsF(ReferenceBoxBorderBoxOutsets(geometry_box, box)));
-  } else {
-    contoured_border_box_rect.Outset(
-        gfx::OutsetsF(ReferenceBoxBorderBoxOutsets(geometry_box, box)));
-  }
-  return contoured_border_box_rect;
-}
-
 // Should the paint offset be applied to clip-path geometry for
 // `clip_path_owner`?
 bool UsesPaintOffset(const LayoutObject& clip_path_owner) {
@@ -273,6 +251,27 @@
 
 }  // namespace
 
+ContouredRect ClipPathClipper::RoundedReferenceBox(GeometryBox geometry_box,
+                                                   const LayoutObject& object) {
+  if (object.IsSVGChild()) {
+    return ContouredRect(
+        FloatRoundedRect(ClipPathClipper::LocalReferenceBox(object)));
+  }
+
+  const auto& box = To<LayoutBoxModelObject>(object);
+  PhysicalRect border_box_rect = BorderBoxRect(box);
+  ContouredRect contoured_border_box_rect =
+      ContouredBorderGeometry::ContouredBorder(box.StyleRef(), border_box_rect);
+  if (geometry_box == GeometryBox::kMarginBox) {
+    contoured_border_box_rect.OutsetForMarginOrShadow(
+        gfx::OutsetsF(ReferenceBoxBorderBoxOutsets(geometry_box, box)));
+  } else {
+    contoured_border_box_rect.Outset(
+        gfx::OutsetsF(ReferenceBoxBorderBoxOutsets(geometry_box, box)));
+  }
+  return contoured_border_box_rect;
+}
+
 Animation* ClipPathClipper::GetClipPathAnimation(
     const LayoutObject& layout_object) {
   ClipPathPaintImageGenerator* generator =
@@ -375,7 +374,9 @@
   // fragmented. We also shouldn't composite in the case of will-change:
   // contents.
   if (is_in_block_fragmentation ||
-      layout_object.StyleRef().SubtreeWillChangeContents()) {
+      layout_object.StyleRef().SubtreeWillChangeContents() ||
+      (layout_object.StyleRef().HasClipPath() &&
+       IsA<ReferenceClipPathOperation>(layout_object.StyleRef().ClipPath()))) {
     SetCompositeClipPathStatus(layout_object.GetNode(),
                                CompositedPaintStatus::kNotComposited);
   }
@@ -546,7 +547,7 @@
   return mask_to_content;
 }
 
-static std::optional<Path> PathBasedClipInternal(
+std::optional<Path> ClipPathClipper::PathBasedClipInternal(
     const LayoutObject& clip_path_owner,
     const gfx::RectF& reference_box,
     const LayoutObject& reference_box_object) {
diff --git a/third_party/blink/renderer/core/paint/clip_path_clipper.h b/third_party/blink/renderer/core/paint/clip_path_clipper.h
index e41b4c4..39c65b02 100644
--- a/third_party/blink/renderer/core/paint/clip_path_clipper.h
+++ b/third_party/blink/renderer/core/paint/clip_path_clipper.h
@@ -9,6 +9,7 @@
 
 #include "third_party/blink/renderer/core/animation/element_animations.h"
 #include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/platform/geometry/contoured_rect.h"
 #include "third_party/blink/renderer/platform/geometry/path.h"
 #include "ui/gfx/geometry/rect_f.h"
 
@@ -53,6 +54,9 @@
   // animation. Returns nullptr if the animation is not compositable.
   static Animation* GetClipPathAnimation(const LayoutObject& layout_object);
 
+  static ContouredRect RoundedReferenceBox(GeometryBox geometry_box,
+                                           const LayoutObject& object);
+
   // Checks the composited paint status for a given Layout Object and checks
   // whether it contains a composited clip path animation. Assumes
   // ResolveClipPathStatus has been called, will fail otherwise.
@@ -97,6 +101,12 @@
   // Like the above, but derives the reference box from the LayoutObject using
   // `LocalReferenceBox()`.
   static bool HitTest(const LayoutObject&, const HitTestLocation& location);
+
+ private:
+  static std::optional<Path> PathBasedClipInternal(
+      const LayoutObject& clip_path_owner,
+      const gfx::RectF& reference_box,
+      const LayoutObject& reference_box_object);
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/paint/timing/text_element_timing.cc b/third_party/blink/renderer/core/paint/timing/text_element_timing.cc
index cab8c9d..346de0a 100644
--- a/third_party/blink/renderer/core/paint/timing/text_element_timing.cc
+++ b/third_party/blink/renderer/core/paint/timing/text_element_timing.cc
@@ -44,23 +44,35 @@
     const LocalFrameView* frame_view) {
   Node* node = object.GetNode();
   DCHECK(node);
-  if (!NeededForElementTiming(*node))
+  if (!NeededForTiming(*node)) {
     return gfx::RectF();
+  }
 
   return ElementTimingUtils::ComputeIntersectionRect(
       &frame_view->GetFrame(), aggregated_visual_rect, property_tree_state);
 }
 
-bool TextElementTiming::CanReportElements() const {
+bool TextElementTiming::CanReportToElementTiming() const {
   DCHECK(performance_);
   return performance_->HasObserverFor(PerformanceEntry::kElement) ||
          !performance_->IsElementTimingBufferFull();
 }
+bool TextElementTiming::CanReportToContainerTiming() {
+  DCHECK(performance_);
+  if (!RuntimeEnabledFeatures::ContainerTimingEnabled()) {
+    return false;
+  }
+  EnsureContainerTiming();
+  return container_timing_->CanReportToContainerTiming();
+}
+bool TextElementTiming::CanReportElements() {
+  return CanReportToElementTiming() || CanReportToContainerTiming();
+}
 
 void TextElementTiming::OnTextObjectPainted(
     const TextRecord& record,
     const DOMPaintTimingInfo& paint_timing_info) {
-  DCHECK(record.is_needed_for_element_timing_);
+  DCHECK(record.is_needed_for_timing_);
   Node* node = record.node_;
 
   // Text aggregators need to be Elements. This will not be the case if the
@@ -75,21 +87,36 @@
   }
 
   auto* element = To<Element>(node);
-  const AtomicString& id = element->GetIdAttribute();
-  if (!element->FastHasAttribute(html_names::kElementtimingAttr))
-    return;
 
-  DEFINE_STATIC_LOCAL(const AtomicString, kTextPaint, ("text-paint"));
-  performance_->AddElementTiming(
-      kTextPaint, g_empty_string, record.element_timing_rect_,
-      paint_timing_info, base::TimeTicks(),
-      element->FastGetAttribute(html_names::kElementtimingAttr), gfx::Size(),
-      id, element);
+  if (CanReportToElementTiming() &&
+      element->FastHasAttribute(html_names::kElementtimingAttr)) {
+    DEFINE_STATIC_LOCAL(const AtomicString, kTextPaint, ("text-paint"));
+    const AtomicString& id = element->GetIdAttribute();
+    performance_->AddElementTiming(
+        kTextPaint, g_empty_string, record.element_timing_rect_,
+        paint_timing_info, base::TimeTicks(),
+        element->FastGetAttribute(html_names::kElementtimingAttr), gfx::Size(),
+        id, element);
+  }
+  if (CanReportToContainerTiming()) {
+    container_timing_->OnElementPainted(paint_timing_info, element,
+                                        record.element_timing_rect_);
+  }
 }
 
 void TextElementTiming::Trace(Visitor* visitor) const {
   Supplement<LocalDOMWindow>::Trace(visitor);
   visitor->Trace(performance_);
+  visitor->Trace(container_timing_);
+}
+
+void TextElementTiming::EnsureContainerTiming() {
+  if (container_timing_) {
+    return;
+  }
+  LocalDOMWindow* window = GetSupplementable();
+  DCHECK(window);
+  container_timing_ = ContainerTiming::From(*window);
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/paint/timing/text_element_timing.h b/third_party/blink/renderer/core/paint/timing/text_element_timing.h
index 5ac5743a..c84f62c 100644
--- a/third_party/blink/renderer/core/paint/timing/text_element_timing.h
+++ b/third_party/blink/renderer/core/paint/timing/text_element_timing.h
@@ -9,6 +9,7 @@
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/dom/element.h"
 #include "third_party/blink/renderer/core/frame/local_dom_window.h"
+#include "third_party/blink/renderer/core/paint/timing/container_timing.h"
 #include "third_party/blink/renderer/core/timing/window_performance.h"
 #include "third_party/blink/renderer/platform/supplementable.h"
 #include "third_party/blink/renderer/platform/wtf/deque.h"
@@ -39,10 +40,11 @@
 
   static TextElementTiming& From(LocalDOMWindow&);
 
-  static inline bool NeededForElementTiming(Node& node) {
+  static inline bool NeededForTiming(Node& node) {
     auto* element = DynamicTo<Element>(node);
     return !node.IsInShadowTree() && element &&
-           element->FastHasAttribute(html_names::kElementtimingAttr);
+           (element->FastHasAttribute(html_names::kElementtimingAttr) ||
+            element->SelfOrAncestorHasContainerTiming());
   }
 
   static gfx::RectF ComputeIntersectionRect(
@@ -51,7 +53,9 @@
       const PropertyTreeStateOrAlias&,
       const LocalFrameView*);
 
-  bool CanReportElements() const;
+  bool CanReportToElementTiming() const;
+  bool CanReportToContainerTiming();
+  bool CanReportElements();
 
   // Called when the swap promise queued by TextPaintTimingDetector has been
   // resolved. Dispatches PerformanceElementTiming entries to WindowPerformance.
@@ -59,7 +63,11 @@
 
   void Trace(Visitor* visitor) const override;
 
+ private:
+  void EnsureContainerTiming();
+
   Member<WindowPerformance> performance_;
+  Member<ContainerTiming> container_timing_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/paint/timing/text_paint_timing_detector.cc b/third_party/blink/renderer/core/paint/timing/text_paint_timing_detector.cc
index 7ecb566..94b44790 100644
--- a/third_party/blink/renderer/core/paint/timing/text_paint_timing_detector.cc
+++ b/third_party/blink/renderer/core/paint/timing/text_paint_timing_detector.cc
@@ -119,7 +119,7 @@
   // shadow element or has no elementtiming attribute, then we should not record
   // its text.
   if (!IsRecordingLargestTextPaint() &&
-      !TextElementTiming::NeededForElementTiming(*node)) {
+      !TextElementTiming::NeededForTiming(*node)) {
     return false;
   }
 
@@ -253,7 +253,7 @@
     // queued for paint, we'll set the appropriate |frame_index_|.
     largest_ignored_text_ = MakeGarbageCollected<TextRecord>(
         *object.GetNode(), size, gfx::RectF(), frame_visual_rect,
-        root_visual_rect, 0u, false /* is_needed_for_element_timing */);
+        root_visual_rect, 0u, false /* is_needed_for_timing */);
   }
 }
 
@@ -276,8 +276,8 @@
     }
   }
 
-  bool can_report_element_timing =
-      text_element_timing_ && text_element_timing_->CanReportElements();
+  bool can_report_timing =
+      text_element_timing_ ? text_element_timing_->CanReportElements() : false;
   HeapVector<Member<const LayoutObject>> keys_to_be_removed;
   for (const auto& [key, record] : texts_queued_for_paint_time_) {
     if (!record->paint_time.is_null() || record->frame_index_ > frame_index) {
@@ -285,7 +285,7 @@
     }
     record->paint_time = timestamp;
     record->paint_timing_info = paint_timing_info;
-    if (can_report_element_timing && record->is_needed_for_element_timing_) {
+    if (can_report_timing && record->is_needed_for_timing_) {
       text_element_timing_->OnTextObjectPainted(*record, paint_timing_info);
     }
 
@@ -308,26 +308,25 @@
   Node* node = object.GetNode();
   DCHECK(node);
 
-  bool is_needed_for_element_timing =
-      TextElementTiming::NeededForElementTiming(*node);
+  bool is_needed_for_timing = TextElementTiming::NeededForTiming(*node);
   // If the node is not required by LCP and not required by ElementTiming, we
   // can bail out early.
   if ((visual_size == 0u || !IsRecordingLargestTextPaint()) &&
-      !is_needed_for_element_timing) {
+      !is_needed_for_timing) {
     return;
   }
   TextRecord* record;
   if (visual_size == 0u) {
     record = MakeGarbageCollected<TextRecord>(
         *node, 0, gfx::RectF(), gfx::Rect(), gfx::RectF(), frame_index_,
-        is_needed_for_element_timing);
+        is_needed_for_timing);
   } else {
     record = MakeGarbageCollected<TextRecord>(
         *object.GetNode(), visual_size,
         TextElementTiming::ComputeIntersectionRect(
             object, frame_visual_rect, property_tree_state, frame_view_),
         frame_visual_rect, root_visual_rect, frame_index_,
-        is_needed_for_element_timing);
+        is_needed_for_timing);
   }
   QueueToMeasurePaintTime(object, record);
 }
diff --git a/third_party/blink/renderer/core/paint/timing/text_paint_timing_detector.h b/third_party/blink/renderer/core/paint/timing/text_paint_timing_detector.h
index 413656a..f799966 100644
--- a/third_party/blink/renderer/core/paint/timing/text_paint_timing_detector.h
+++ b/third_party/blink/renderer/core/paint/timing/text_paint_timing_detector.h
@@ -32,12 +32,12 @@
              const gfx::Rect& frame_visual_rect,
              const gfx::RectF& root_visual_rect,
              uint32_t frame_index,
-             bool is_needed_for_element_timing)
+             bool is_needed_for_timing)
       : node_(&node),
         recorded_size(new_recorded_size),
         frame_index_(frame_index),
         element_timing_rect_(element_timing_rect),
-        is_needed_for_element_timing_(is_needed_for_element_timing) {
+        is_needed_for_timing_(is_needed_for_timing) {
     if (PaintTimingVisualizer::IsTracingEnabled()) {
       lcp_rect_info_ = std::make_unique<LCPRectInfo>(
           frame_visual_rect, gfx::ToRoundedRect(root_visual_rect));
@@ -56,7 +56,7 @@
   // The time of the first paint after fully loaded.
   base::TimeTicks paint_time = base::TimeTicks();
   DOMPaintTimingInfo paint_timing_info;
-  bool is_needed_for_element_timing_ = false;
+  bool is_needed_for_timing_ = false;
 };
 
 class CORE_EXPORT LargestTextPaintManager final
diff --git a/third_party/blink/renderer/core/scheduler/dom_task.h b/third_party/blink/renderer/core/scheduler/dom_task.h
index 0bbf3c30..5340120 100644
--- a/third_party/blink/renderer/core/scheduler/dom_task.h
+++ b/third_party/blink/renderer/core/scheduler/dom_task.h
@@ -36,7 +36,7 @@
           base::TimeDelta delay,
           uint64_t task_id_for_tracing);
 
-  virtual void Trace(Visitor*) const;
+  void Trace(Visitor*) const;
 
   void OnPendingPromiseSettled();
 
diff --git a/third_party/blink/renderer/core/scheduler/dom_task_continuation.h b/third_party/blink/renderer/core/scheduler/dom_task_continuation.h
index d2f2ce7..fdaf9c53 100644
--- a/third_party/blink/renderer/core/scheduler/dom_task_continuation.h
+++ b/third_party/blink/renderer/core/scheduler/dom_task_continuation.h
@@ -26,7 +26,7 @@
                       DOMScheduler::DOMTaskQueue*,
                       uint64_t task_id_for_tracing);
 
-  virtual void Trace(Visitor*) const;
+  void Trace(Visitor*) const;
 
  private:
   // Entry point for running this continuation.
diff --git a/third_party/blink/renderer/core/style/shape_value.h b/third_party/blink/renderer/core/style/shape_value.h
index 1301ede9..9449322d 100644
--- a/third_party/blink/renderer/core/style/shape_value.h
+++ b/third_party/blink/renderer/core/style/shape_value.h
@@ -73,7 +73,7 @@
 
   bool operator==(const ShapeValue& other) const;
 
-  virtual void Trace(Visitor* visitor) const {
+  void Trace(Visitor* visitor) const {
     visitor->Trace(shape_);
     visitor->Trace(image_);
   }
diff --git a/third_party/blink/renderer/core/style/style_path.cc b/third_party/blink/renderer/core/style/style_path.cc
index 9783dc1..668c681 100644
--- a/third_party/blink/renderer/core/style/style_path.cc
+++ b/third_party/blink/renderer/core/style/style_path.cc
@@ -23,8 +23,7 @@
 
 const Path& StylePath::GetPath() const {
   if (!path_) {
-    path_ = BuildPathFromByteStream(byte_stream_);
-    path_->SetWindRule(wind_rule_);
+    path_ = BuildPathFromByteStream(byte_stream_, wind_rule_);
   }
   return *path_;
 }
diff --git a/third_party/blink/renderer/core/svg/svg_circle_element.cc b/third_party/blink/renderer/core/svg/svg_circle_element.cc
index 06e71fa..17eb44de 100644
--- a/third_party/blink/renderer/core/svg/svg_circle_element.cc
+++ b/third_party/blink/renderer/core/svg/svg_circle_element.cc
@@ -24,6 +24,7 @@
 #include "third_party/blink/renderer/core/svg/svg_animated_length.h"
 #include "third_party/blink/renderer/core/svg/svg_length.h"
 #include "third_party/blink/renderer/core/svg/svg_length_functions.h"
+#include "third_party/blink/renderer/platform/geometry/path_builder.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 
 namespace blink {
@@ -57,7 +58,11 @@
 }
 
 Path SVGCircleElement::AsPath() const {
-  Path path;
+  return AsMutablePath().Finalize();
+}
+
+PathBuilder SVGCircleElement::AsMutablePath() const {
+  PathBuilder builder;
 
   const SVGViewportResolver viewport_resolver(*this);
   const ComputedStyle& style = ComputedStyleRef();
@@ -67,9 +72,9 @@
   if (r > 0) {
     gfx::PointF center =
         PointForLengthPair(style.Cx(), style.Cy(), viewport_resolver, style);
-    path = Path::MakeEllipse(center, r, r);
+    builder.AddEllipse(center, r, r);
   }
-  return path;
+  return builder;
 }
 
 void SVGCircleElement::SvgAttributeChanged(
diff --git a/third_party/blink/renderer/core/svg/svg_circle_element.h b/third_party/blink/renderer/core/svg/svg_circle_element.h
index 5f0963e..c0f01cfd 100644
--- a/third_party/blink/renderer/core/svg/svg_circle_element.h
+++ b/third_party/blink/renderer/core/svg/svg_circle_element.h
@@ -35,6 +35,7 @@
   explicit SVGCircleElement(Document&);
 
   Path AsPath() const override;
+  PathBuilder AsMutablePath() const override;
 
   SVGAnimatedLength* cx() const { return cx_.Get(); }
   SVGAnimatedLength* cy() const { return cy_.Get(); }
diff --git a/third_party/blink/renderer/core/svg/svg_ellipse_element.cc b/third_party/blink/renderer/core/svg/svg_ellipse_element.cc
index 760ca20..414610f4 100644
--- a/third_party/blink/renderer/core/svg/svg_ellipse_element.cc
+++ b/third_party/blink/renderer/core/svg/svg_ellipse_element.cc
@@ -24,6 +24,7 @@
 #include "third_party/blink/renderer/core/svg/svg_animated_length.h"
 #include "third_party/blink/renderer/core/svg/svg_length.h"
 #include "third_party/blink/renderer/core/svg/svg_length_functions.h"
+#include "third_party/blink/renderer/platform/geometry/path_builder.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 
 namespace blink {
@@ -64,7 +65,11 @@
 }
 
 Path SVGEllipseElement::AsPath() const {
-  Path path;
+  return AsMutablePath().Finalize();
+}
+
+PathBuilder SVGEllipseElement::AsMutablePath() const {
+  PathBuilder builder;
 
   const SVGViewportResolver viewport_resolver(*this);
   const ComputedStyle& style = ComputedStyleRef();
@@ -76,13 +81,13 @@
   else if (style.Ry().IsAuto())
     radii.set_y(radii.x());
   if (radii.x() < 0 || radii.y() < 0 || (!radii.x() && !radii.y()))
-    return path;
+    return builder;
 
   gfx::PointF center =
       PointForLengthPair(style.Cx(), style.Cy(), viewport_resolver, style);
-  path = Path::MakeEllipse(center, radii.x(), radii.y());
+  builder.AddEllipse(center, radii.x(), radii.y());
 
-  return path;
+  return builder;
 }
 
 void SVGEllipseElement::SvgAttributeChanged(
diff --git a/third_party/blink/renderer/core/svg/svg_ellipse_element.h b/third_party/blink/renderer/core/svg/svg_ellipse_element.h
index 7d042e9..38a0651 100644
--- a/third_party/blink/renderer/core/svg/svg_ellipse_element.h
+++ b/third_party/blink/renderer/core/svg/svg_ellipse_element.h
@@ -35,6 +35,7 @@
   explicit SVGEllipseElement(Document&);
 
   Path AsPath() const override;
+  PathBuilder AsMutablePath() const override;
 
   SVGAnimatedLength* cx() const { return cx_.Get(); }
   SVGAnimatedLength* cy() const { return cy_.Get(); }
diff --git a/third_party/blink/renderer/core/svg/svg_geometry_element.cc b/third_party/blink/renderer/core/svg/svg_geometry_element.cc
index 69c39dc..20411d3 100644
--- a/third_party/blink/renderer/core/svg/svg_geometry_element.cc
+++ b/third_party/blink/renderer/core/svg/svg_geometry_element.cc
@@ -38,6 +38,7 @@
 #include "third_party/blink/renderer/core/svg/svg_animated_number.h"
 #include "third_party/blink/renderer/core/svg/svg_point_tear_off.h"
 #include "third_party/blink/renderer/core/svg_names.h"
+#include "third_party/blink/renderer/platform/geometry/path_builder.h"
 #include "third_party/blink/renderer/platform/geometry/stroke_data.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 
@@ -121,7 +122,7 @@
 
   AffineTransform root_transform;
 
-  Path path = AsPath();
+  PathBuilder path = AsMutablePath();
   gfx::PointF local_point(ClampTo<float>(point->x()),
                           ClampTo<float>(point->y()));
   if (layout_shape.HasNonScalingStroke()) {
@@ -144,17 +145,19 @@
       PathLengthScaleFactor());
 
   // Path::StrokeContains will reject points with a non-finite component.
-  return path.StrokeContains(local_point, stroke_data, root_transform);
+  return path.Finalize().StrokeContains(local_point, stroke_data,
+                                        root_transform);
 }
 
 Path SVGGeometryElement::ToClipPath() const {
-  Path path = AsPath();
+  PathBuilder path = AsMutablePath();
   path.Transform(CalculateTransform(SVGElement::kIncludeMotionTransform));
 
   DCHECK(GetLayoutObject());
   DCHECK(GetLayoutObject()->Style());
   path.SetWindRule(GetLayoutObject()->StyleRef().ClipRule());
-  return path;
+
+  return path.Finalize();
 }
 
 float SVGGeometryElement::getTotalLength(ExceptionState& exception_state) {
@@ -183,7 +186,7 @@
     return nullptr;
   }
 
-  const Path& path = AsPath();
+  const Path path = AsPath();
 
   if (path.IsEmpty()) {
     exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
diff --git a/third_party/blink/renderer/core/svg/svg_geometry_element.h b/third_party/blink/renderer/core/svg/svg_geometry_element.h
index 4f9f029..7d71a15 100644
--- a/third_party/blink/renderer/core/svg/svg_geometry_element.h
+++ b/third_party/blink/renderer/core/svg/svg_geometry_element.h
@@ -37,6 +37,7 @@
 
 class DOMPointInit;
 class Path;
+class PathBuilder;
 class SVGAnimatedNumber;
 class SVGPointTearOff;
 
@@ -45,6 +46,8 @@
 
  public:
   virtual Path AsPath() const = 0;
+  virtual PathBuilder AsMutablePath() const = 0;
+
   bool isPointInFill(const DOMPointInit*) const;
   bool isPointInStroke(const DOMPointInit*) const;
 
diff --git a/third_party/blink/renderer/core/svg/svg_line_element.cc b/third_party/blink/renderer/core/svg/svg_line_element.cc
index 14a196b..e6363328 100644
--- a/third_party/blink/renderer/core/svg/svg_line_element.cc
+++ b/third_party/blink/renderer/core/svg/svg_line_element.cc
@@ -24,6 +24,7 @@
 #include "third_party/blink/renderer/core/svg/svg_length.h"
 #include "third_party/blink/renderer/core/svg/svg_length_context.h"
 #include "third_party/blink/renderer/platform/geometry/path.h"
+#include "third_party/blink/renderer/platform/geometry/path_builder.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 
 namespace blink {
@@ -60,17 +61,21 @@
 }
 
 Path SVGLineElement::AsPath() const {
-  Path path;
+  return AsMutablePath().Finalize();
+}
+
+PathBuilder SVGLineElement::AsMutablePath() const {
+  PathBuilder builder;
 
   SVGLengthContext length_context(this);
   DCHECK(GetComputedStyle());
 
-  path.MoveTo(gfx::PointF(x1()->CurrentValue()->Value(length_context),
-                          y1()->CurrentValue()->Value(length_context)));
-  path.AddLineTo(gfx::PointF(x2()->CurrentValue()->Value(length_context),
+  builder.MoveTo(gfx::PointF(x1()->CurrentValue()->Value(length_context),
+                             y1()->CurrentValue()->Value(length_context)));
+  builder.LineTo(gfx::PointF(x2()->CurrentValue()->Value(length_context),
                              y2()->CurrentValue()->Value(length_context)));
 
-  return path;
+  return builder;
 }
 
 void SVGLineElement::SvgAttributeChanged(
diff --git a/third_party/blink/renderer/core/svg/svg_line_element.h b/third_party/blink/renderer/core/svg/svg_line_element.h
index c1b6d02..061414d 100644
--- a/third_party/blink/renderer/core/svg/svg_line_element.h
+++ b/third_party/blink/renderer/core/svg/svg_line_element.h
@@ -35,6 +35,7 @@
   explicit SVGLineElement(Document&);
 
   Path AsPath() const override;
+  PathBuilder AsMutablePath() const override;
 
   SVGAnimatedLength* x1() const { return x1_.Get(); }
   SVGAnimatedLength* y1() const { return y1_.Get(); }
diff --git a/third_party/blink/renderer/core/svg/svg_path_element.cc b/third_party/blink/renderer/core/svg/svg_path_element.cc
index 8d22f006..d0e7ba0 100644
--- a/third_party/blink/renderer/core/svg/svg_path_element.cc
+++ b/third_party/blink/renderer/core/svg/svg_path_element.cc
@@ -27,6 +27,7 @@
 #include "third_party/blink/renderer/core/svg/svg_path_query.h"
 #include "third_party/blink/renderer/core/svg/svg_path_utilities.h"
 #include "third_party/blink/renderer/core/svg/svg_point_tear_off.h"
+#include "third_party/blink/renderer/platform/geometry/path_builder.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 
 namespace blink {
@@ -67,6 +68,10 @@
   return GetStylePath()->GetPath();
 }
 
+PathBuilder SVGPathElement::AsMutablePath() const {
+  return PathBuilder(AsPath());
+}
+
 float SVGPathElement::getTotalLength(ExceptionState& exception_state) {
   GetDocument().UpdateStyleAndLayoutForNode(this,
                                             DocumentUpdateReason::kJavaScript);
diff --git a/third_party/blink/renderer/core/svg/svg_path_element.h b/third_party/blink/renderer/core/svg/svg_path_element.h
index d30a2a4..97500d2 100644
--- a/third_party/blink/renderer/core/svg/svg_path_element.h
+++ b/third_party/blink/renderer/core/svg/svg_path_element.h
@@ -37,6 +37,7 @@
   explicit SVGPathElement(Document&);
 
   Path AsPath() const override;
+  PathBuilder AsMutablePath() const override;
   Path AttributePath() const;
 
   float getTotalLength(ExceptionState&) override;
diff --git a/third_party/blink/renderer/core/svg/svg_path_utilities.cc b/third_party/blink/renderer/core/svg/svg_path_utilities.cc
index d37478a..d75214e 100644
--- a/third_party/blink/renderer/core/svg/svg_path_utilities.cc
+++ b/third_party/blink/renderer/core/svg/svg_path_utilities.cc
@@ -26,6 +26,7 @@
 #include "third_party/blink/renderer/core/svg/svg_path_string_builder.h"
 #include "third_party/blink/renderer/core/svg/svg_path_string_source.h"
 #include "third_party/blink/renderer/platform/geometry/path.h"
+#include "third_party/blink/renderer/platform/geometry/path_types.h"
 
 namespace blink {
 
@@ -40,11 +41,12 @@
   return builder.Finalize();
 }
 
-Path BuildPathFromByteStream(const SVGPathByteStream& stream) {
+Path BuildPathFromByteStream(const SVGPathByteStream& stream,
+                             WindRule wind_rule) {
   if (stream.IsEmpty())
     return Path();
 
-  SVGPathBuilder builder;
+  SVGPathBuilder builder(wind_rule);
   SVGPathByteStreamSource source(stream);
   svg_path_parser::ParsePath(source, builder);
 
diff --git a/third_party/blink/renderer/core/svg/svg_path_utilities.h b/third_party/blink/renderer/core/svg/svg_path_utilities.h
index 6472ffa..d75b8264 100644
--- a/third_party/blink/renderer/core/svg/svg_path_utilities.h
+++ b/third_party/blink/renderer/core/svg/svg_path_utilities.h
@@ -22,6 +22,7 @@
 
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/svg/svg_parsing_error.h"
+#include "third_party/blink/renderer/platform/geometry/path_types.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
 
 namespace blink {
@@ -32,7 +33,7 @@
 
 // StringView/SVGPathByteStream -> Path
 Path CORE_EXPORT BuildPathFromString(const StringView&);
-Path BuildPathFromByteStream(const SVGPathByteStream&);
+Path BuildPathFromByteStream(const SVGPathByteStream&, WindRule);
 
 SVGParsingError CORE_EXPORT
 BuildByteStreamFromString(const StringView&, SVGPathByteStreamBuilder&);
diff --git a/third_party/blink/renderer/core/svg/svg_poly_element.cc b/third_party/blink/renderer/core/svg/svg_poly_element.cc
index 77c946f..297fb4b 100644
--- a/third_party/blink/renderer/core/svg/svg_poly_element.cc
+++ b/third_party/blink/renderer/core/svg/svg_poly_element.cc
@@ -22,6 +22,7 @@
 
 #include "third_party/blink/renderer/core/svg/svg_animated_point_list.h"
 #include "third_party/blink/renderer/platform/geometry/path.h"
+#include "third_party/blink/renderer/platform/geometry/path_builder.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 
 namespace blink {
@@ -47,24 +48,24 @@
   SVGGeometryElement::Trace(visitor);
 }
 
-Path SVGPolyElement::AsPathFromPoints() const {
-  Path path;
+PathBuilder SVGPolyElement::AsPathFromPoints() const {
+  PathBuilder builder;
   DCHECK(GetComputedStyle());
 
   const SVGPointList* points_value = Points()->CurrentValue();
   if (points_value->IsEmpty())
-    return path;
+    return builder;
 
   auto it = points_value->begin();
   auto it_end = points_value->end();
   DCHECK(it != it_end);
-  path.MoveTo((*it)->Value());
+  builder.MoveTo((*it)->Value());
   ++it;
 
   for (; it != it_end; ++it)
-    path.AddLineTo((*it)->Value());
+    builder.LineTo((*it)->Value());
 
-  return path;
+  return builder;
 }
 
 void SVGPolyElement::SvgAttributeChanged(
diff --git a/third_party/blink/renderer/core/svg/svg_poly_element.h b/third_party/blink/renderer/core/svg/svg_poly_element.h
index 7c60a71f..637e6ba 100644
--- a/third_party/blink/renderer/core/svg/svg_poly_element.h
+++ b/third_party/blink/renderer/core/svg/svg_poly_element.h
@@ -27,6 +27,7 @@
 
 namespace blink {
 
+class PathBuilder;
 class SVGAnimatedPointList;
 class SVGPointListTearOff;
 
@@ -42,7 +43,7 @@
  protected:
   SVGPolyElement(const QualifiedName&, Document&);
 
-  Path AsPathFromPoints() const;
+  PathBuilder AsPathFromPoints() const;
 
  private:
   void SvgAttributeChanged(const SvgAttributeChangedParams&) final;
diff --git a/third_party/blink/renderer/core/svg/svg_polygon_element.cc b/third_party/blink/renderer/core/svg/svg_polygon_element.cc
index 9bf2654..a2ca69d7 100644
--- a/third_party/blink/renderer/core/svg/svg_polygon_element.cc
+++ b/third_party/blink/renderer/core/svg/svg_polygon_element.cc
@@ -21,6 +21,7 @@
 #include "third_party/blink/renderer/core/svg/svg_polygon_element.h"
 
 #include "third_party/blink/renderer/platform/geometry/path.h"
+#include "third_party/blink/renderer/platform/geometry/path_builder.h"
 
 namespace blink {
 
@@ -28,9 +29,11 @@
     : SVGPolyElement(svg_names::kPolygonTag, document) {}
 
 Path SVGPolygonElement::AsPath() const {
-  Path path = AsPathFromPoints();
-  path.CloseSubpath();
-  return path;
+  return AsPathFromPoints().Close().Finalize();
+}
+
+PathBuilder SVGPolygonElement::AsMutablePath() const {
+  return AsPathFromPoints().Close();
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/svg/svg_polygon_element.h b/third_party/blink/renderer/core/svg/svg_polygon_element.h
index b4d1b56d..295f9ff 100644
--- a/third_party/blink/renderer/core/svg/svg_polygon_element.h
+++ b/third_party/blink/renderer/core/svg/svg_polygon_element.h
@@ -32,6 +32,7 @@
   explicit SVGPolygonElement(Document&);
 
   Path AsPath() const override;
+  PathBuilder AsMutablePath() const override;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/svg/svg_polyline_element.cc b/third_party/blink/renderer/core/svg/svg_polyline_element.cc
index 1c77aab8..1629534 100644
--- a/third_party/blink/renderer/core/svg/svg_polyline_element.cc
+++ b/third_party/blink/renderer/core/svg/svg_polyline_element.cc
@@ -21,6 +21,7 @@
 #include "third_party/blink/renderer/core/svg/svg_polyline_element.h"
 
 #include "third_party/blink/renderer/platform/geometry/path.h"
+#include "third_party/blink/renderer/platform/geometry/path_builder.h"
 
 namespace blink {
 
@@ -28,6 +29,10 @@
     : SVGPolyElement(svg_names::kPolylineTag, document) {}
 
 Path SVGPolylineElement::AsPath() const {
+  return AsPathFromPoints().Finalize();
+}
+
+PathBuilder SVGPolylineElement::AsMutablePath() const {
   return AsPathFromPoints();
 }
 
diff --git a/third_party/blink/renderer/core/svg/svg_polyline_element.h b/third_party/blink/renderer/core/svg/svg_polyline_element.h
index 7c0fc55..2a993d74 100644
--- a/third_party/blink/renderer/core/svg/svg_polyline_element.h
+++ b/third_party/blink/renderer/core/svg/svg_polyline_element.h
@@ -32,6 +32,7 @@
   explicit SVGPolylineElement(Document&);
 
   Path AsPath() const override;
+  PathBuilder AsMutablePath() const override;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/svg/svg_rect_element.cc b/third_party/blink/renderer/core/svg/svg_rect_element.cc
index fbd926ee..34295b9 100644
--- a/third_party/blink/renderer/core/svg/svg_rect_element.cc
+++ b/third_party/blink/renderer/core/svg/svg_rect_element.cc
@@ -24,6 +24,7 @@
 #include "third_party/blink/renderer/core/svg/svg_animated_length.h"
 #include "third_party/blink/renderer/core/svg/svg_length.h"
 #include "third_party/blink/renderer/core/svg/svg_length_functions.h"
+#include "third_party/blink/renderer/platform/geometry/path_builder.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 
 namespace blink {
@@ -78,7 +79,11 @@
 }
 
 Path SVGRectElement::AsPath() const {
-  Path path;
+  return AsMutablePath().Finalize();
+}
+
+PathBuilder SVGRectElement::AsMutablePath() const {
+  PathBuilder builder;
 
   const SVGViewportResolver viewport_resolver(*this);
   const ComputedStyle& style = ComputedStyleRef();
@@ -86,7 +91,7 @@
   gfx::Vector2dF size = VectorForLengthPair(style.Width(), style.Height(),
                                             viewport_resolver, style);
   if (size.x() < 0 || size.y() < 0 || size.IsZero())
-    return path;
+    return builder;
 
   gfx::PointF origin =
       PointForLengthPair(style.X(), style.Y(), viewport_resolver, style);
@@ -108,11 +113,11 @@
     // than half of the width of the rectangle then its set to half of the
     // width; radii.y() is handled similarly.
     radii.SetToMin(gfx::ScaleVector2d(size, 0.5));
-    path = Path::MakeRoundedRect(FloatRoundedRect(rect, radii.x(), radii.y()));
+    builder.AddRoundedRect(FloatRoundedRect(rect, radii.x(), radii.y()));
   } else {
-    path = Path::MakeRect(rect);
+    builder.AddRect(rect);
   }
-  return path;
+  return builder;
 }
 
 void SVGRectElement::SvgAttributeChanged(
diff --git a/third_party/blink/renderer/core/svg/svg_rect_element.h b/third_party/blink/renderer/core/svg/svg_rect_element.h
index f7893f3..9c517f6 100644
--- a/third_party/blink/renderer/core/svg/svg_rect_element.h
+++ b/third_party/blink/renderer/core/svg/svg_rect_element.h
@@ -35,6 +35,7 @@
   explicit SVGRectElement(Document&);
 
   Path AsPath() const override;
+  PathBuilder AsMutablePath() const override;
 
   SVGAnimatedLength* x() const { return x_.Get(); }
   SVGAnimatedLength* y() const { return y_.Get(); }
diff --git a/third_party/blink/renderer/core/view_transition/view_transition.cc b/third_party/blink/renderer/core/view_transition/view_transition.cc
index af28b46..5c7658e 100644
--- a/third_party/blink/renderer/core/view_transition/view_transition.cc
+++ b/third_party/blink/renderer/core/view_transition/view_transition.cc
@@ -150,12 +150,6 @@
           *this,
           update_dom_callback)) {
   InitTypes(types.value_or(Vector<String>()));
-  if (auto* originating_element = document_->documentElement()) {
-    originating_element->ActiveViewTransitionStateChanged();
-    if (types_ && !types_->IsEmpty()) {
-      originating_element->ActiveViewTransitionTypeStateChanged();
-    }
-  }
   if (previously_active && previously_active->PendingDomCallback()) {
     previously_active->blocking_ = this;
     blocked_on_ = previously_active;
@@ -671,6 +665,16 @@
 
 void ViewTransition::InitTypes(const Vector<String>& types) {
   types_ = MakeGarbageCollected<ViewTransitionTypeSet>(this, types);
+  // Although ViewTransitionTypeSet can invalidate its own style, there are
+  // checks that it is in a current view transition. Because InitTypes is
+  // called during ctor, the supplement does not yet know that this will become
+  // a current view transition, so we need to invalidate explicitly.
+  if (auto* originating_element = document_->documentElement()) {
+    originating_element->ActiveViewTransitionStateChanged();
+    if (!types_->IsEmpty()) {
+      originating_element->ActiveViewTransitionTypeStateChanged();
+    }
+  }
 }
 
 void ViewTransition::Trace(Visitor* visitor) const {
diff --git a/third_party/blink/renderer/core/workers/shared_worker_client_holder.h b/third_party/blink/renderer/core/workers/shared_worker_client_holder.h
index 27fc2b4f..f8aa10f 100644
--- a/third_party/blink/renderer/core/workers/shared_worker_client_holder.h
+++ b/third_party/blink/renderer/core/workers/shared_worker_client_holder.h
@@ -70,7 +70,7 @@
   explicit SharedWorkerClientHolder(LocalDOMWindow&);
   SharedWorkerClientHolder(const SharedWorkerClientHolder&) = delete;
   SharedWorkerClientHolder& operator=(const SharedWorkerClientHolder&) = delete;
-  virtual ~SharedWorkerClientHolder() = default;
+  ~SharedWorkerClientHolder() = default;
 
   // Establishes a connection with SharedWorkerHost in the browser process.
   // `connector_override` is used to force creation of the shared worker on
diff --git a/third_party/blink/renderer/core/workers/worklet_pending_tasks.h b/third_party/blink/renderer/core/workers/worklet_pending_tasks.h
index e138290..7127e89 100644
--- a/third_party/blink/renderer/core/workers/worklet_pending_tasks.h
+++ b/third_party/blink/renderer/core/workers/worklet_pending_tasks.h
@@ -37,7 +37,7 @@
   // Decrements |counter_| and resolves the promise if the counter becomes 0.
   void DecrementCounter();
 
-  virtual void Trace(Visitor*) const;
+  void Trace(Visitor*) const;
 
  private:
   // The number of pending tasks. -1 indicates these tasks are aborted and
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 c019ece8..ebb7e15 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
@@ -125,16 +125,12 @@
   wtf_size_t length = end_offset - start_offset;
 
   const String& full_text = fragment_items.Text(item.UsesFirstLineStyle());
-  const FragmentProperties& fragment_properties =
-      fragment_properties_[item_index];
 
   // If the text elided a trailing whitespace, we may need to reintroduce it.
   // Trailing whitespace is elided since not rendered; however, there may still
   // be required for a11y.
-  if (fragment_properties.line_index) {
-    const AXBlockFlowData::Line& line =
-        lines_[fragment_properties.line_index.value()];
-    if (IncludeTrailingWhitespace(full_text, end_offset, item, line)) {
+  if (const auto line = GetLine(item_index)) {
+    if (IncludeTrailingWhitespace(full_text, end_offset, item, line.value())) {
       length++;
     }
   }
@@ -152,6 +148,16 @@
   return StringView(full_text, start_offset, length).ToString();
 }
 
+std::optional<AXBlockFlowData::Line> AXBlockFlowData::GetLine(
+    wtf_size_t index) const {
+  for (const Line& line : lines_) {
+    if (index >= line.start_index && index < line.start_index + line.length) {
+      return line;
+    }
+  }
+  return std::nullopt;
+}
+
 AXBlockFlowData::Neighbor AXBlockFlowData::ComputeNeighborOnLine(
     FragmentIndex index,
     bool forward) const {
@@ -199,16 +205,10 @@
   return FailureReason::kAtLineBoundary;
 }
 
-const AXBlockFlowData::FragmentProperties& AXBlockFlowData::GetProperties(
-    wtf_size_t index) const {
-  return fragment_properties_[index];
-}
-
 void AXBlockFlowData::Trace(Visitor* visitor) const {
   visitor->Trace(block_flow_container_);
   visitor->Trace(layout_fragment_map_);
   visitor->Trace(lines_);
-  visitor->Trace(fragment_properties_);
 }
 
 AXBlockFlowData::AXBlockFlowData(LayoutBlockFlow* layout_block_flow)
@@ -236,7 +236,6 @@
       if (fragment->Items()) {
         wtf_size_t next_starting_index =
             starting_fragment_index + fragment->Items()->Size();
-        fragment_properties_.resize(next_starting_index);
         ProcessBoxFragment(fragment, starting_fragment_index);
         starting_fragment_index = next_starting_index;
       }
@@ -253,7 +252,6 @@
   }
 
   wtf_size_t fragment_index = starting_fragment_index;
-  std::optional<wtf_size_t> previous_on_line;
 
   // Keep track of the current outermost line index, as ruby content may have
   // multiple nested lines within the flattened list. When checking if a
@@ -284,54 +282,25 @@
                      fragment_index)
             : false;
     if (it->Type() == FragmentItem::kLine) {
-      wtf_size_t length = it->DescendantsCount();
-      const InlineBreakToken* break_token = it->GetInlineBreakToken();
-      bool forced_break = break_token && break_token->IsForcedBreak();
-      std::optional<wtf_size_t> break_index;
-      if (break_token) {
-        break_index = break_token->StartTextOffset();
-      }
-      lines_.push_back<Line>({.start_index = fragment_index,
-                              .length = length,
-                              .forced_break = forced_break,
-                              .break_index = break_index});
       if (!on_current_line) {
         // We are moving to a new line that is not nested in the previous line.
+        wtf_size_t length = it->DescendantsCount();
+        const InlineBreakToken* break_token = it->GetInlineBreakToken();
+        bool forced_break = break_token && break_token->IsForcedBreak();
+        std::optional<wtf_size_t> break_index;
+        if (break_token) {
+          break_index = break_token->StartTextOffset();
+        }
+        lines_.push_back<Line>({.start_index = fragment_index,
+                                .length = length,
+                                .forced_break = forced_break,
+                                .break_index = break_index});
         current_outermost_line_index = lines_.size() - 1;
-        on_current_line = true;
       }
-
-      // There are no previous items on this new line, since it just started.
-      // Note that here it may be a outermost line or a nested one, it does not
-      // matter. A new line has just started and we don't want to make
-      // connections between the two.
-      previous_on_line = std::nullopt;
-    }
-
-    FragmentProperties& properties = fragment_properties_[fragment_index];
-    if (on_current_line) {
-      properties.line_index = lines_.size() - 1;
-    }
-
-    if (it->Type() == FragmentItem::kText ||
-        it->Type() == FragmentItem::kGeneratedText) {
-      if (previous_on_line) {
-        CHECK(properties.line_index ==
-              fragment_properties_[previous_on_line.value()].line_index);
-        properties.previous_on_line = previous_on_line;
-        fragment_properties_[previous_on_line.value()].next_on_line =
-            fragment_index;
-      }
-      previous_on_line = fragment_index;
     }
 
     // TODO(crbug.com/399204651): Implement navigating into separate PhysicalBox
     // fragments.
-    if (it->IsContainer() && it->BoxFragment()) {
-      /// A physical box exists for this item, meaning that the next and
-      /// previous for this particular item needs to be cleared.
-      previous_on_line = std::nullopt;
-    }
   }
 }
 
diff --git a/third_party/blink/renderer/modules/accessibility/ax_block_flow_iterator.h b/third_party/blink/renderer/modules/accessibility/ax_block_flow_iterator.h
index 85ee02c..295cbf1 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_block_flow_iterator.h
+++ b/third_party/blink/renderer/modules/accessibility/ax_block_flow_iterator.h
@@ -53,19 +53,7 @@
   // AXInlineTextBox. The LineItem is used for determining if the next or
   // previous fragment is on the same line, and to determine if a trailing
   // space is required in the text content for an AXInlineTextBox.
-  struct FragmentProperties {
-    // Index of the corresponding line in the vector of lines.
-    std::optional<wtf_size_t> line_index;
-    // Index of the previous or next fragment that is on the same line that
-    // may be part of another LayoutObject. Typically, this index directly
-    // corresponds to the index within a physical box fragments vector of
-    // fragment items; however, there is an additional index flattening process
-    // in the case where a layout block flow contains multiple physical box
-    // fragments. See FindFirstFragment for details.
-    std::optional<wtf_size_t> previous_on_line;
-    std::optional<wtf_size_t> next_on_line;
-  };
-
+  //
   // Each LineItem in the layout fragmentation has a corresponding Line instant
   // containing pertinent details for the construction of AXInlineTextBox
   // objects.
@@ -142,7 +130,8 @@
 
   WTF::String GetText(wtf_size_t index) const;
 
-  const FragmentProperties& GetProperties(wtf_size_t index) const;
+  std::optional<Line> GetLine(wtf_size_t index) const;
+
   Neighbor ComputeNeighborOnLine(FragmentIndex index, bool forward) const;
 
  private:
@@ -158,7 +147,6 @@
   HeapHashMap<Member<const LayoutObject>, wtf_size_t> layout_fragment_map_;
 
   HeapVector<Line> lines_;
-  HeapVector<FragmentProperties> fragment_properties_;
 
   wtf_size_t total_fragment_count_ = 0;
 };
diff --git a/third_party/blink/renderer/modules/ai/BUILD.gn b/third_party/blink/renderer/modules/ai/BUILD.gn
index c7e8b99..0f86399 100644
--- a/third_party/blink/renderer/modules/ai/BUILD.gn
+++ b/third_party/blink/renderer/modules/ai/BUILD.gn
@@ -39,8 +39,6 @@
     "exception_helpers.h",
     "model_execution_responder.cc",
     "model_execution_responder.h",
-    "on_device_translation/ai_language_detector_factory.cc",
-    "on_device_translation/ai_language_detector_factory.h",
     "on_device_translation/create_translator_client.cc",
     "on_device_translation/create_translator_client.h",
     "on_device_translation/language_detector.cc",
diff --git a/third_party/blink/renderer/modules/ai/ai.cc b/third_party/blink/renderer/modules/ai/ai.cc
index 17b84ea..3d5d6e8f 100644
--- a/third_party/blink/renderer/modules/ai/ai.cc
+++ b/third_party/blink/renderer/modules/ai/ai.cc
@@ -24,7 +24,6 @@
   ExecutionContextClient::Trace(visitor);
   visitor->Trace(ai_remote_);
   visitor->Trace(ai_language_model_factory_);
-  visitor->Trace(ai_language_detector_factory_);
 }
 
 HeapMojoRemote<mojom::blink::AIManager>& AI::GetAIRemote() {
@@ -49,12 +48,5 @@
   return ai_language_model_factory_.Get();
 }
 
-AILanguageDetectorFactory* AI::languageDetector() {
-  if (!ai_language_detector_factory_) {
-    ai_language_detector_factory_ =
-        MakeGarbageCollected<AILanguageDetectorFactory>(GetExecutionContext());
-  }
-  return ai_language_detector_factory_.Get();
-}
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/ai/ai.h b/third_party/blink/renderer/modules/ai/ai.h
index 720b9d3..f72848b2 100644
--- a/third_party/blink/renderer/modules/ai/ai.h
+++ b/third_party/blink/renderer/modules/ai/ai.h
@@ -10,7 +10,6 @@
 #include "third_party/blink/public/mojom/ai/ai_manager.mojom-blink.h"
 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
 #include "third_party/blink/renderer/core/execution_context/execution_context_lifecycle_observer.h"
-#include "third_party/blink/renderer/modules/ai/on_device_translation/ai_language_detector_factory.h"
 #include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
 #include "third_party/blink/renderer/platform/mojo/heap_mojo_remote.h"
 
@@ -30,7 +29,6 @@
 
   // model_manager.idl implementation.
   AILanguageModelFactory* languageModel();
-  AILanguageDetectorFactory* languageDetector();
 
   HeapMojoRemote<mojom::blink::AIManager>& GetAIRemote();
 
@@ -40,7 +38,6 @@
   scoped_refptr<base::SequencedTaskRunner> task_runner_;
   HeapMojoRemote<mojom::blink::AIManager> ai_remote_;
   Member<AILanguageModelFactory> ai_language_model_factory_;
-  Member<AILanguageDetectorFactory> ai_language_detector_factory_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/ai/ai.idl b/third_party/blink/renderer/modules/ai/ai.idl
index 33c8bdd..d792507 100644
--- a/third_party/blink/renderer/modules/ai/ai.idl
+++ b/third_party/blink/renderer/modules/ai/ai.idl
@@ -15,9 +15,4 @@
     RuntimeEnabled=AIPromptAPI
   ]
   readonly attribute AILanguageModelFactory languageModel;
-
-  [
-    RuntimeEnabled=LanguageDetectionAPI
-  ]
-  readonly attribute AILanguageDetectorFactory languageDetector;
 };
diff --git a/third_party/blink/renderer/modules/ai/ai_interface_proxy.cc b/third_party/blink/renderer/modules/ai/ai_interface_proxy.cc
index cd090b6a..876e43e 100644
--- a/third_party/blink/renderer/modules/ai/ai_interface_proxy.cc
+++ b/third_party/blink/renderer/modules/ai/ai_interface_proxy.cc
@@ -4,35 +4,26 @@
 
 #include "third_party/blink/renderer/modules/ai/ai_interface_proxy.h"
 
-#include "third_party/blink/renderer/core/execution_context/execution_context.h"
-
 namespace blink {
 
 const char AIInterfaceProxy::kSupplementName[] = "AIInterfaceProxy";
 
+// TODO(crbug.com/406770758): Consider refactoring to have this class own the
+// execution context as a member.
 AIInterfaceProxy::AIInterfaceProxy(ExecutionContext* execution_context)
     : Supplement<ExecutionContext>(*execution_context),
       task_runner_(
-          execution_context->GetTaskRunner(TaskType::kInternalDefault)) {}
+          execution_context->GetTaskRunner(TaskType::kInternalDefault)),
+      language_detection_model_(
+          MakeGarbageCollected<LanguageDetectionModel>()) {}
 
 AIInterfaceProxy::~AIInterfaceProxy() = default;
 
-// static
-AIInterfaceProxy* AIInterfaceProxy::From(ExecutionContext* execution_context) {
-  AIInterfaceProxy* translation_manager_proxy =
-      Supplement<ExecutionContext>::From<AIInterfaceProxy>(*execution_context);
-  if (!translation_manager_proxy) {
-    translation_manager_proxy =
-        MakeGarbageCollected<AIInterfaceProxy>(execution_context);
-    ProvideTo(*execution_context, translation_manager_proxy);
-  }
-  return translation_manager_proxy;
-}
-
 void AIInterfaceProxy::Trace(Visitor* visitor) const {
   Supplement<ExecutionContext>::Trace(visitor);
   visitor->Trace(translation_manager_remote_);
   visitor->Trace(language_detection_driver_);
+  visitor->Trace(language_detection_model_);
   visitor->Trace(ai_manager_remote_);
 }
 
@@ -51,12 +42,20 @@
 }
 
 // static
-HeapMojoRemote<
-    language_detection::mojom::blink::ContentLanguageDetectionDriver>&
-AIInterfaceProxy::GetLanguageDetectionDriverRemote(
-    ExecutionContext* execution_context) {
-  return From(execution_context)
-      ->GetLanguageDetectionDriverRemoteImpl(execution_context);
+void AIInterfaceProxy::GetLanguageDetectionModelStatus(
+    ExecutionContext* execution_context,
+    GetLanguageDetectionModelStatusCallback callback) {
+  From(execution_context)
+      ->GetLanguageDetectionDriverRemote(execution_context)
+      ->GetLanguageDetectionModelStatus(std::move(callback));
+}
+
+// static
+void AIInterfaceProxy::GetLanguageDetectionModel(
+    ExecutionContext* execution_context,
+    GetLanguageDetectionModelCallback callback) {
+  From(execution_context)
+      ->GetLanguageDetectionModelImpl(execution_context, std::move(callback));
 }
 
 // static
@@ -65,6 +64,18 @@
   return From(execution_context)->GetAIManagerRemoteImpl(execution_context);
 }
 
+// static
+AIInterfaceProxy* AIInterfaceProxy::From(ExecutionContext* execution_context) {
+  AIInterfaceProxy* translation_manager_proxy =
+      Supplement<ExecutionContext>::From<AIInterfaceProxy>(*execution_context);
+  if (!translation_manager_proxy) {
+    translation_manager_proxy =
+        MakeGarbageCollected<AIInterfaceProxy>(execution_context);
+    ProvideTo(*execution_context, translation_manager_proxy);
+  }
+  return translation_manager_proxy;
+}
+
 HeapMojoRemote<mojom::blink::TranslationManager>&
 AIInterfaceProxy::GetTranslationManagerRemoteImpl(
     ExecutionContext* execution_context) {
@@ -77,7 +88,7 @@
 
 HeapMojoRemote<
     language_detection::mojom::blink::ContentLanguageDetectionDriver>&
-AIInterfaceProxy::GetLanguageDetectionDriverRemoteImpl(
+AIInterfaceProxy::GetLanguageDetectionDriverRemote(
     ExecutionContext* execution_context) {
   if (!language_detection_driver_.is_bound()) {
     execution_context->GetBrowserInterfaceBroker().GetInterface(
@@ -86,6 +97,20 @@
   return language_detection_driver_;
 }
 
+void AIInterfaceProxy::GetLanguageDetectionModelImpl(
+    ExecutionContext* execution_context,
+    GetLanguageDetectionModelCallback callback) {
+  GetLanguageDetectionDriverRemote(execution_context)
+      ->GetLanguageDetectionModel(WTF::BindOnce(
+          [](LanguageDetectionModel* language_detection_model,
+             GetLanguageDetectionModelCallback callback, base::File model) {
+            language_detection_model->LoadModelFile(std::move(model),
+                                                    std::move(callback));
+          },
+          WrapPersistent(language_detection_model_.Get()),
+          std::move(callback)));
+}
+
 HeapMojoRemote<mojom::blink::AIManager>&
 AIInterfaceProxy::GetAIManagerRemoteImpl(ExecutionContext* execution_context) {
   if (!ai_manager_remote_.is_bound()) {
diff --git a/third_party/blink/renderer/modules/ai/ai_interface_proxy.h b/third_party/blink/renderer/modules/ai/ai_interface_proxy.h
index 64e9d6e..7e72828 100644
--- a/third_party/blink/renderer/modules/ai/ai_interface_proxy.h
+++ b/third_party/blink/renderer/modules/ai/ai_interface_proxy.h
@@ -11,6 +11,7 @@
 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
 #include "third_party/blink/renderer/core/frame/local_dom_window.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
+#include "third_party/blink/renderer/platform/language_detection/language_detection_model.h"
 
 namespace blink {
 
@@ -20,6 +21,12 @@
  public:
   static const char kSupplementName[];
 
+  using GetLanguageDetectionModelStatusCallback = base::OnceCallback<void(
+      language_detection::mojom::blink::LanguageDetectionModelStatus)>;
+
+  using GetLanguageDetectionModelCallback = base::OnceCallback<void(
+      base::expected<LanguageDetectionModel*, DetectLanguageError>)>;
+
   explicit AIInterfaceProxy(ExecutionContext* execution_context);
   ~AIInterfaceProxy();
 
@@ -35,9 +42,13 @@
   static HeapMojoRemote<mojom::blink::TranslationManager>&
   GetTranslationManagerRemote(ExecutionContext* execution_context);
 
-  static HeapMojoRemote<
-      language_detection::mojom::blink::ContentLanguageDetectionDriver>&
-  GetLanguageDetectionDriverRemote(ExecutionContext* execution_context);
+  static void GetLanguageDetectionModelStatus(
+      ExecutionContext* execution_context,
+      GetLanguageDetectionModelStatusCallback callback);
+
+  static void GetLanguageDetectionModel(
+      ExecutionContext* execution_context,
+      GetLanguageDetectionModelCallback callback);
 
   static HeapMojoRemote<mojom::blink::AIManager>& GetAIManagerRemote(
       ExecutionContext* execution_context);
@@ -50,7 +61,11 @@
 
   HeapMojoRemote<
       language_detection::mojom::blink::ContentLanguageDetectionDriver>&
-  GetLanguageDetectionDriverRemoteImpl(ExecutionContext* execution_context);
+  GetLanguageDetectionDriverRemote(ExecutionContext* execution_context);
+
+  void GetLanguageDetectionModelImpl(
+      ExecutionContext* execution_context,
+      GetLanguageDetectionModelCallback callback);
 
   HeapMojoRemote<mojom::blink::AIManager>& GetAIManagerRemoteImpl(
       ExecutionContext* execution_context);
@@ -64,6 +79,10 @@
       language_detection::mojom::blink::ContentLanguageDetectionDriver>
       language_detection_driver_{nullptr};
 
+  // TODO(crbug.com/406770758): Consider updating ownership of
+  // `language_detection_model_` to the `LanguageDetectorCreate` class.
+  Member<LanguageDetectionModel> language_detection_model_;
+
   HeapMojoRemote<mojom::blink::AIManager> ai_manager_remote_{nullptr};
 };
 
diff --git a/third_party/blink/renderer/modules/ai/on_device_translation/ai_language_detector_factory.cc b/third_party/blink/renderer/modules/ai/on_device_translation/ai_language_detector_factory.cc
deleted file mode 100644
index abb8569..0000000
--- a/third_party/blink/renderer/modules/ai/on_device_translation/ai_language_detector_factory.cc
+++ /dev/null
@@ -1,226 +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 "third_party/blink/renderer/modules/ai/on_device_translation/ai_language_detector_factory.h"
-
-#include "third_party/blink/public/platform/browser_interface_broker_proxy.h"
-#include "third_party/blink/renderer/bindings/modules/v8/v8_ai_create_monitor_callback.h"
-#include "third_party/blink/renderer/modules/ai/ai.h"
-#include "third_party/blink/renderer/modules/ai/ai_availability.h"
-#include "third_party/blink/renderer/modules/ai/ai_context_observer.h"
-#include "third_party/blink/renderer/modules/ai/ai_create_monitor.h"
-#include "third_party/blink/renderer/modules/ai/ai_interface_proxy.h"
-#include "third_party/blink/renderer/modules/ai/ai_utils.h"
-#include "third_party/blink/renderer/modules/ai/exception_helpers.h"
-#include "third_party/blink/renderer/modules/ai/on_device_translation/language_detector.h"
-#include "third_party/blink/renderer/platform/language_detection/language_detection_model.h"
-
-namespace blink {
-
-namespace {
-
-void RejectModelNotAvailable(
-    ScriptPromiseResolver<LanguageDetector>* resolver) {
-  resolver->Reject("Model not available");
-}
-
-template <typename T>
-class RejectOnDestructionHelper {
- public:
-  explicit RejectOnDestructionHelper(ScriptPromiseResolver<T>* resolver)
-      : resolver_(resolver) {
-    CHECK(resolver);
-  }
-
-  RejectOnDestructionHelper(const RejectOnDestructionHelper&) = delete;
-  RejectOnDestructionHelper& operator=(const RejectOnDestructionHelper&) =
-      delete;
-
-  RejectOnDestructionHelper(RejectOnDestructionHelper&& other) = default;
-  RejectOnDestructionHelper& operator=(RejectOnDestructionHelper&& other) =
-      default;
-
-  void Reset() { resolver_ = nullptr; }
-
-  ~RejectOnDestructionHelper() {
-    if (resolver_) {
-      resolver_->Reject();
-    }
-  }
-
- private:
-  Persistent<ScriptPromiseResolver<T>> resolver_;
-};
-
-template <typename T>
-base::OnceClosure RejectOnDestruction(ScriptPromiseResolver<T>* resolver) {
-  return WTF::BindOnce(
-      [](RejectOnDestructionHelper<T> resolver_holder) {
-        resolver_holder.Reset();
-      },
-      RejectOnDestructionHelper(resolver));
-}
-
-class LanguageDetectorCreateTask
-    : public GarbageCollected<LanguageDetectorCreateTask>,
-      public ExecutionContextClient,
-      public AIContextObserver<LanguageDetector> {
- public:
-  LanguageDetectorCreateTask(ScriptState* script_state,
-                             ScriptPromiseResolver<LanguageDetector>* resolver,
-                             LanguageDetectionModel* model,
-                             const LanguageDetectorCreateOptions* options)
-      : ExecutionContextClient(ExecutionContext::From(script_state)),
-        AIContextObserver(script_state,
-                          this,
-                          resolver,
-                          options->getSignalOr(nullptr)),
-        task_runner_(AIInterfaceProxy::GetTaskRunner(GetExecutionContext())),
-        resolver_(resolver),
-        language_detection_model_(model) {
-    if (options->hasMonitor()) {
-      monitor_ = MakeGarbageCollected<AICreateMonitor>(GetExecutionContext(),
-                                                       task_runner_);
-      std::ignore = options->monitor()->Invoke(nullptr, monitor_);
-    }
-  }
-
-  void CreateDetector(base::File model_file) {
-    if (!GetExecutionContext() || !resolver_) {
-      return;
-    }
-    if (!model_file.IsValid()) {
-      RejectModelNotAvailable(resolver_);
-      return;
-    }
-    language_detection_model_->LoadModelFile(
-        std::move(model_file),
-        WTF::BindOnce(&LanguageDetectorCreateTask::OnModelLoaded,
-                      WrapPersistent(this)));
-  }
-
-  void Trace(Visitor* visitor) const override {
-    ExecutionContextClient::Trace(visitor);
-    AIContextObserver::Trace(visitor);
-    visitor->Trace(resolver_);
-    visitor->Trace(monitor_);
-    visitor->Trace(language_detection_model_);
-  }
-
- private:
-  void OnModelLoaded(base::expected<LanguageDetectionModel*,
-                                    DetectLanguageError> maybe_model) {
-    if (!resolver_) {
-      return;
-    }
-    if (maybe_model.has_value()) {
-      LanguageDetectionModel* model = maybe_model.value();
-      // TODO (crbug.com/383022111): Pass the real download progress rather than
-      // mocking one.
-      if (monitor_) {
-        monitor_->OnDownloadProgressUpdate(0, kNormalizedDownloadProgressMax);
-        monitor_->OnDownloadProgressUpdate(kNormalizedDownloadProgressMax,
-                                           kNormalizedDownloadProgressMax);
-      }
-      resolver_->Resolve(
-          MakeGarbageCollected<LanguageDetector>(model, task_runner_));
-    } else {
-      switch (maybe_model.error()) {
-        case DetectLanguageError::kUnavailable:
-          RejectModelNotAvailable(resolver_);
-      }
-    }
-  }
-
-  void ResetReceiver() override { resolver_ = nullptr; }
-
-  scoped_refptr<base::SequencedTaskRunner> task_runner_;
-
-  Member<AICreateMonitor> monitor_;
-  Member<ScriptPromiseResolver<LanguageDetector>> resolver_;
-  Member<LanguageDetectionModel> language_detection_model_;
-};
-
-}  // namespace
-
-AILanguageDetectorFactory::AILanguageDetectorFactory(ExecutionContext* context)
-    : ExecutionContextClient(context),
-      language_detection_model_(
-          MakeGarbageCollected<LanguageDetectionModel>()) {}
-
-void AILanguageDetectorFactory::Trace(Visitor* visitor) const {
-  ScriptWrappable::Trace(visitor);
-  ExecutionContextClient::Trace(visitor);
-  visitor->Trace(language_detection_model_);
-}
-
-ScriptPromise<V8AIAvailability> AILanguageDetectorFactory::availability(
-    ScriptState* script_state,
-    ExceptionState& exception_state) {
-  if (!script_state->ContextIsValid()) {
-    exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
-                                      "The execution context is not valid.");
-    return EmptyPromise();
-  }
-
-  ScriptPromiseResolver<V8AIAvailability>* resolver =
-      MakeGarbageCollected<ScriptPromiseResolver<V8AIAvailability>>(
-          script_state);
-  ScriptPromise<V8AIAvailability> promise = resolver->Promise();
-  ExecutionContext* execution_context = GetExecutionContext();
-
-  AIInterfaceProxy::GetLanguageDetectionDriverRemote(execution_context)
-      ->GetLanguageDetectionModelStatus(
-          WTF::BindOnce(&AILanguageDetectorFactory::OnGotStatus,
-                        WrapPersistent(this), WrapPersistent(resolver))
-              .Then(RejectOnDestruction(resolver)));
-
-  return promise;
-}
-
-void AILanguageDetectorFactory::OnGotStatus(
-    ScriptPromiseResolver<V8AIAvailability>* resolver,
-    language_detection::mojom::blink::LanguageDetectionModelStatus result) {
-  if (!GetExecutionContext()) {
-    return;
-  }
-  AIAvailability availability =
-      HandleLanguageDetectionModelCheckResult(GetExecutionContext(), result);
-  resolver->Resolve(AIAvailabilityToV8(availability));
-}
-
-ScriptPromise<LanguageDetector> AILanguageDetectorFactory::create(
-    ScriptState* script_state,
-    LanguageDetectorCreateOptions* options,
-    ExceptionState& exception_state) {
-  // TODO(crbug.com/349927087): Take `options` into account.
-  if (!script_state->ContextIsValid() || !GetExecutionContext()) {
-    exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
-                                      "The execution context is not valid.");
-    return EmptyPromise();
-  }
-
-  CHECK(options);
-  AbortSignal* signal = options->getSignalOr(nullptr);
-  if (HandleAbortSignal(signal, script_state, exception_state)) {
-    return EmptyPromise();
-  }
-
-  auto* resolver =
-      MakeGarbageCollected<ScriptPromiseResolver<LanguageDetector>>(
-          script_state);
-  LanguageDetectorCreateTask* create_task =
-      MakeGarbageCollected<LanguageDetectorCreateTask>(
-          script_state, resolver, language_detection_model_, options);
-
-  AIInterfaceProxy::GetLanguageDetectionDriverRemote(GetExecutionContext())
-      ->GetLanguageDetectionModel(
-          WTF::BindOnce(&LanguageDetectorCreateTask::CreateDetector,
-                        WrapPersistent(create_task))
-              .Then(RejectOnDestruction(resolver)));
-
-  return resolver->Promise();
-}
-
-}  // namespace blink
diff --git a/third_party/blink/renderer/modules/ai/on_device_translation/ai_language_detector_factory.h b/third_party/blink/renderer/modules/ai/on_device_translation/ai_language_detector_factory.h
deleted file mode 100644
index 6458895..0000000
--- a/third_party/blink/renderer/modules/ai/on_device_translation/ai_language_detector_factory.h
+++ /dev/null
@@ -1,47 +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 THIRD_PARTY_BLINK_RENDERER_MODULES_AI_ON_DEVICE_TRANSLATION_AI_LANGUAGE_DETECTOR_FACTORY_H_
-#define THIRD_PARTY_BLINK_RENDERER_MODULES_AI_ON_DEVICE_TRANSLATION_AI_LANGUAGE_DETECTOR_FACTORY_H_
-
-#include "third_party/blink/renderer/bindings/modules/v8/v8_language_detector_create_options.h"
-#include "third_party/blink/renderer/core/execution_context/execution_context_lifecycle_observer.h"
-#include "third_party/blink/renderer/modules/ai/ai_availability.h"
-#include "third_party/blink/renderer/modules/ai/on_device_translation/language_detector.h"
-#include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
-#include "third_party/blink/renderer/platform/heap/collection_support/heap_hash_set.h"
-#include "third_party/blink/renderer/platform/mojo/heap_mojo_remote.h"
-
-namespace blink {
-
-// `ExecutionContextClient` gives us access to the browser interface broker.
-class AILanguageDetectorFactory final : public ScriptWrappable,
-                                        public ExecutionContextClient {
-  DEFINE_WRAPPERTYPEINFO();
-
- public:
-  explicit AILanguageDetectorFactory(ExecutionContext* context);
-
-  void Trace(Visitor* visitor) const override;
-
-  // Checks the availability of the Language Detection model.
-  ScriptPromise<V8AIAvailability> availability(ScriptState* script_state,
-                                               ExceptionState& exception_state);
-
-  // Creates an `LanguageDetector`, with a model ready to use.
-  ScriptPromise<LanguageDetector> create(ScriptState* script_state,
-                                         LanguageDetectorCreateOptions* options,
-                                         ExceptionState& exception_state);
-
- private:
-  void OnGotStatus(
-      ScriptPromiseResolver<V8AIAvailability>* resolver,
-      language_detection::mojom::blink::LanguageDetectionModelStatus result);
-
-  Member<LanguageDetectionModel> language_detection_model_;
-};
-
-}  // namespace blink
-
-#endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_AI_ON_DEVICE_TRANSLATION_AI_LANGUAGE_DETECTOR_FACTORY_H_
diff --git a/third_party/blink/renderer/modules/ai/on_device_translation/ai_language_detector_factory.idl b/third_party/blink/renderer/modules/ai/on_device_translation/ai_language_detector_factory.idl
deleted file mode 100644
index 2fc5b6c..0000000
--- a/third_party/blink/renderer/modules/ai/on_device_translation/ai_language_detector_factory.idl
+++ /dev/null
@@ -1,24 +0,0 @@
-[
-    RuntimeEnabled=LanguageDetectionAPI,
-    Exposed=(Window, Worker),
-    SecureContext
-]
-interface AILanguageDetectorFactory {
-    [
-    MeasureAs=LanguageDetector_Availability,
-    CallWith=ScriptState,
-    RaisesException
-  ]
-  Promise<AIAvailability> availability();
-  [
-    MeasureAs=LanguageDetector_Create,
-    CallWith=ScriptState,
-    RaisesException
-  ]
-  Promise<LanguageDetector> create(optional LanguageDetectorCreateOptions options = {});
-};
-
-dictionary LanguageDetectorCreateOptions {
-  AbortSignal signal;
-  AICreateMonitorCallback monitor;
-};
diff --git a/third_party/blink/renderer/modules/ai/on_device_translation/language_detector.cc b/third_party/blink/renderer/modules/ai/on_device_translation/language_detector.cc
index 5f22c3a..5837f02 100644
--- a/third_party/blink/renderer/modules/ai/on_device_translation/language_detector.cc
+++ b/third_party/blink/renderer/modules/ai/on_device_translation/language_detector.cc
@@ -4,12 +4,194 @@
 
 #include "third_party/blink/renderer/modules/ai/on_device_translation/language_detector.h"
 
+#include "third_party/blink/renderer/bindings/modules/v8/v8_ai_create_monitor_callback.h"
 #include "third_party/blink/renderer/core/frame/local_dom_window.h"
+#include "third_party/blink/renderer/modules/ai/ai_availability.h"
+#include "third_party/blink/renderer/modules/ai/ai_context_observer.h"
+#include "third_party/blink/renderer/modules/ai/ai_create_monitor.h"
+#include "third_party/blink/renderer/modules/ai/ai_interface_proxy.h"
+#include "third_party/blink/renderer/modules/ai/ai_utils.h"
 #include "third_party/blink/renderer/modules/ai/exception_helpers.h"
 #include "third_party/blink/renderer/platform/wtf/casting.h"
 
 namespace blink {
 
+namespace {
+
+template <typename T>
+class RejectOnDestructionHelper {
+ public:
+  explicit RejectOnDestructionHelper(ScriptPromiseResolver<T>* resolver)
+      : resolver_(resolver) {
+    CHECK(resolver);
+  }
+
+  RejectOnDestructionHelper(const RejectOnDestructionHelper&) = delete;
+  RejectOnDestructionHelper& operator=(const RejectOnDestructionHelper&) =
+      delete;
+
+  RejectOnDestructionHelper(RejectOnDestructionHelper&& other) = default;
+  RejectOnDestructionHelper& operator=(RejectOnDestructionHelper&& other) =
+      default;
+
+  void Reset() { resolver_ = nullptr; }
+
+  ~RejectOnDestructionHelper() {
+    if (resolver_) {
+      resolver_->Reject();
+    }
+  }
+
+ private:
+  Persistent<ScriptPromiseResolver<T>> resolver_;
+};
+
+template <typename T>
+base::OnceClosure RejectOnDestruction(ScriptPromiseResolver<T>* resolver) {
+  return WTF::BindOnce(
+      [](RejectOnDestructionHelper<T> resolver_holder) {
+        resolver_holder.Reset();
+      },
+      RejectOnDestructionHelper(resolver));
+}
+
+class LanguageDetectorCreateTask
+    : public GarbageCollected<LanguageDetectorCreateTask>,
+      public ExecutionContextClient,
+      public AIContextObserver<LanguageDetector> {
+ public:
+  LanguageDetectorCreateTask(ScriptState* script_state,
+                             ScriptPromiseResolver<LanguageDetector>* resolver,
+                             const LanguageDetectorCreateOptions* options)
+      : ExecutionContextClient(ExecutionContext::From(script_state)),
+        AIContextObserver(script_state,
+                          this,
+                          resolver,
+                          options->getSignalOr(nullptr)),
+        task_runner_(AIInterfaceProxy::GetTaskRunner(GetExecutionContext())),
+        resolver_(resolver) {
+    if (options->hasMonitor()) {
+      monitor_ = MakeGarbageCollected<AICreateMonitor>(GetExecutionContext(),
+                                                       task_runner_);
+      std::ignore = options->monitor()->Invoke(nullptr, monitor_);
+    }
+  }
+
+  void Trace(Visitor* visitor) const override {
+    ExecutionContextClient::Trace(visitor);
+    AIContextObserver::Trace(visitor);
+    visitor->Trace(resolver_);
+    visitor->Trace(monitor_);
+  }
+
+  void OnModelLoaded(base::expected<LanguageDetectionModel*,
+                                    DetectLanguageError> maybe_model) {
+    if (!resolver_) {
+      return;
+    }
+    if (!maybe_model.has_value()) {
+      switch (maybe_model.error()) {
+        case DetectLanguageError::kUnavailable:
+          resolver_->Reject("Model not available");
+          break;
+      }
+      Cleanup();
+      return;
+    }
+    // TODO (crbug.com/383022111): Pass the real download progress rather
+    // than mocking one.
+    if (monitor_) {
+      monitor_->OnDownloadProgressUpdate(0, kNormalizedDownloadProgressMax);
+      monitor_->OnDownloadProgressUpdate(kNormalizedDownloadProgressMax,
+                                         kNormalizedDownloadProgressMax);
+    }
+    resolver_->Resolve(MakeGarbageCollected<LanguageDetector>(
+        maybe_model.value(), task_runner_));
+    Cleanup();
+  }
+
+ private:
+  void ResetReceiver() override { resolver_ = nullptr; }
+
+  scoped_refptr<base::SequencedTaskRunner> task_runner_;
+
+  Member<AICreateMonitor> monitor_;
+  Member<ScriptPromiseResolver<LanguageDetector>> resolver_;
+};
+
+void OnGotStatus(
+    ExecutionContext* execution_context,
+    ScriptPromiseResolver<V8AIAvailability>* resolver,
+    language_detection::mojom::blink::LanguageDetectionModelStatus result) {
+  if (!execution_context) {
+    return;
+  }
+  AIAvailability availability =
+      HandleLanguageDetectionModelCheckResult(execution_context, result);
+  resolver->Resolve(AIAvailabilityToV8(availability));
+}
+
+}  // namespace
+
+// static
+ScriptPromise<V8AIAvailability> LanguageDetector::availability(
+    ScriptState* script_state,
+    ExceptionState& exception_state) {
+  if (!script_state->ContextIsValid()) {
+    exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
+                                      "The execution context is not valid.");
+    return EmptyPromise();
+  }
+
+  ScriptPromiseResolver<V8AIAvailability>* resolver =
+      MakeGarbageCollected<ScriptPromiseResolver<V8AIAvailability>>(
+          script_state);
+  ScriptPromise<V8AIAvailability> promise = resolver->Promise();
+  ExecutionContext* execution_context = ExecutionContext::From(script_state);
+
+  AIInterfaceProxy::GetLanguageDetectionModelStatus(
+      execution_context,
+      WTF::BindOnce(&OnGotStatus, WrapWeakPersistent(execution_context),
+                    WrapPersistent(resolver))
+          .Then(RejectOnDestruction(resolver)));
+
+  return promise;
+}
+
+// static
+ScriptPromise<LanguageDetector> LanguageDetector::create(
+    ScriptState* script_state,
+    LanguageDetectorCreateOptions* options,
+    ExceptionState& exception_state) {
+  // TODO(crbug.com/349927087): Take `options` into account.
+  if (!script_state->ContextIsValid()) {
+    exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
+                                      "The execution context is not valid.");
+    return EmptyPromise();
+  }
+
+  CHECK(options);
+  AbortSignal* signal = options->getSignalOr(nullptr);
+  if (HandleAbortSignal(signal, script_state, exception_state)) {
+    return EmptyPromise();
+  }
+
+  auto* resolver =
+      MakeGarbageCollected<ScriptPromiseResolver<LanguageDetector>>(
+          script_state);
+  LanguageDetectorCreateTask* create_task =
+      MakeGarbageCollected<LanguageDetectorCreateTask>(script_state, resolver,
+                                                       options);
+
+  AIInterfaceProxy::GetLanguageDetectionModel(
+      ExecutionContext::From(script_state),
+      WTF::BindOnce(&LanguageDetectorCreateTask::OnModelLoaded,
+                    WrapPersistent(create_task))
+          .Then(RejectOnDestruction(resolver)));
+
+  return resolver->Promise();
+}
+
 LanguageDetector::LanguageDetector(
     LanguageDetectionModel* language_detection_model,
     scoped_refptr<base::SequencedTaskRunner>& task_runner)
diff --git a/third_party/blink/renderer/modules/ai/on_device_translation/language_detector.h b/third_party/blink/renderer/modules/ai/on_device_translation/language_detector.h
index 5fd5f41..097030f 100644
--- a/third_party/blink/renderer/modules/ai/on_device_translation/language_detector.h
+++ b/third_party/blink/renderer/modules/ai/on_device_translation/language_detector.h
@@ -8,7 +8,9 @@
 #include "base/task/sequenced_task_runner.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
+#include "third_party/blink/renderer/bindings/modules/v8/v8_ai_availability.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_language_detection_result.h"
+#include "third_party/blink/renderer/bindings/modules/v8/v8_language_detector_create_options.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_language_detector_detect_options.h"
 #include "third_party/blink/renderer/modules/ai/on_device_translation/resolver_with_abort_signal.h"
 #include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
@@ -20,6 +22,15 @@
   DEFINE_WRAPPERTYPEINFO();
 
  public:
+  static ScriptPromise<V8AIAvailability> availability(
+      ScriptState* script_state,
+      ExceptionState& exception_state);
+
+  static ScriptPromise<LanguageDetector> create(
+      ScriptState* script_state,
+      LanguageDetectorCreateOptions* options,
+      ExceptionState& exception_state);
+
   explicit LanguageDetector(
       LanguageDetectionModel* language_detection_model,
       scoped_refptr<base::SequencedTaskRunner>& task_runner);
diff --git a/third_party/blink/renderer/modules/ai/on_device_translation/language_detector.idl b/third_party/blink/renderer/modules/ai/on_device_translation/language_detector.idl
index 905cc8da..6204c9bd 100644
--- a/third_party/blink/renderer/modules/ai/on_device_translation/language_detector.idl
+++ b/third_party/blink/renderer/modules/ai/on_device_translation/language_detector.idl
@@ -10,6 +10,21 @@
 ]
 interface LanguageDetector {
   [
+    MeasureAs=LanguageDetector_Create,
+    CallWith=ScriptState,
+    RaisesException
+  ]
+  static Promise<LanguageDetector> create(
+    optional LanguageDetectorCreateOptions options = {}
+  );
+  [
+    MeasureAs=LanguageDetector_Availability,
+    CallWith=ScriptState,
+    RaisesException
+  ]
+  static Promise<AIAvailability> availability();
+
+  [
     MeasureAs=LanguageDetector_Detect,
     CallWith=ScriptState,
     RaisesException
@@ -36,6 +51,11 @@
   readonly attribute unrestricted double inputQuota;
 };
 
+dictionary LanguageDetectorCreateOptions {
+  AbortSignal signal;
+  AICreateMonitorCallback monitor;
+};
+
 dictionary LanguageDetectorDetectOptions {
   AbortSignal signal;
 };
diff --git a/third_party/blink/renderer/modules/canvas/canvas_noise_test.cc b/third_party/blink/renderer/modules/canvas/canvas_noise_test.cc
index a61d11f..ecd056e 100644
--- a/third_party/blink/renderer/modules/canvas/canvas_noise_test.cc
+++ b/third_party/blink/renderer/modules/canvas/canvas_noise_test.cc
@@ -4,8 +4,11 @@
 
 #include <cstddef>
 #include <cstdint>
+#include <initializer_list>
+#include <memory>
 
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/common/fingerprinting_protection/canvas_noise_token.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_union_float16array_float32array_uint8clampedarray.h"
 #include "third_party/blink/renderer/core/canvas_interventions/canvas_interventions_helper.h"
@@ -48,6 +51,7 @@
                                                    attributes));
     PutRandomPixels(context, canvas_element_->width(),
                     canvas_element_->height());
+    CanvasNoiseToken::Set(0x1234567890123456);
   }
 
   void TearDown() override {
@@ -82,6 +86,18 @@
         ->SetCanvasInterventionsForceEnabled();
   }
 
+  base::span<uint8_t> GetNoisedPixelsWithData(ImageData* original_data,
+                                              ExecutionContext* ec) {
+    NonThrowableExceptionState exception_state;
+    Context2D()->putImageData(original_data, 0, 0, exception_state);
+    scoped_refptr<StaticBitmapImage> snapshot =
+        Context2D()->GetImage(FlushReason::kTesting);
+    EXPECT_TRUE(CanvasInterventionsHelper::MaybeNoiseSnapshot(
+        Context2D(), ec, snapshot, RasterMode::kGPU));
+    return GetPixels(Context2D(), CanvasElement().width(),
+                     CanvasElement().height());
+  }
+
   static base::span<uint8_t> GetPixels(BaseRenderingContext2D* context,
                                        size_t width,
                                        size_t height) {
@@ -105,7 +121,7 @@
       if (diff > 0) {
         ++num_changed_pixel_values;
       }
-      if (diff > 3) {
+      if (diff > 6) {
         ++too_large_diffs;
       }
     }
@@ -230,6 +246,33 @@
   EXPECT_EQ(snapshot_copy, snapshot);
 }
 
+TEST_F(CanvasNoiseTest, MaybeNoiseSnapshotDifferentNoiseTokenNoiseDiffers) {
+  NonThrowableExceptionState exception_state;
+
+  auto* window = GetFrame().DomWindow();
+  window->GetRuntimeFeatureStateOverrideContext()
+      ->SetCanvasInterventionsForceEnabled();
+  DrawSomethingWithTrigger();
+
+  // Save a copy of the image data to reset.
+  ImageData* copy_image_data = Context2D()->getImageData(
+      0, 0, CanvasElement().width(), CanvasElement().height(), exception_state);
+  base::span<uint8_t> original_noised_pixels =
+      GetNoisedPixelsWithData(copy_image_data, window);
+
+  // Sanity check to ensure GetNoisedPixelsWithData performs the same noising
+  // pattern without changing the noise token.
+  EXPECT_EQ(original_noised_pixels,
+            GetNoisedPixelsWithData(copy_image_data, window));
+
+  // Now change the noise token.
+  CanvasNoiseToken::Set(0xdeadbeef);
+  base::span<uint8_t> updated_noised_pixels =
+      GetNoisedPixelsWithData(copy_image_data, window);
+
+  EXPECT_NE(original_noised_pixels, updated_noised_pixels);
+}
+
 TEST_F(CanvasNoiseTest, NoTriggerOnFillRect) {
   V8TestingScope scope;
   SetFillStyleString(Context2D(), GetScriptState(), "red");
diff --git a/third_party/blink/renderer/modules/csspaint/nativepaint/clip_path_paint_definition.cc b/third_party/blink/renderer/modules/csspaint/nativepaint/clip_path_paint_definition.cc
index 94b7f6f..836cc77b 100644
--- a/third_party/blink/renderer/modules/csspaint/nativepaint/clip_path_paint_definition.cc
+++ b/third_party/blink/renderer/modules/csspaint/nativepaint/clip_path_paint_definition.cc
@@ -27,7 +27,9 @@
 #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/paint/clip_path_clipper.h"
 #include "third_party/blink/renderer/core/style/computed_style_constants.h"
+#include "third_party/blink/renderer/core/style/geometry_box_clip_path_operation.h"
 #include "third_party/blink/renderer/core/style/shape_clip_path_operation.h"
 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
 #include "ui/gfx/geometry/size_f.h"
@@ -489,11 +491,26 @@
       // isn't backwards or both, we will need to ensure no items are clipped
       // during the delay. Use an 'infinite' clip rect to do this.
       if (element->GetLayoutObject()->StyleRef().HasClipPath()) {
-        ClipPathOperation* static_shape =
+        ClipPathOperation* static_op =
             element->GetLayoutObject()->StyleRef().ClipPath();
-        DCHECK_EQ(static_shape->GetType(), ClipPathOperation::kShape);
-        Path path = To<ShapeClipPathOperation>(static_shape)
-                        ->GetPath(reference_size, zoom);
+        Path path;
+        switch (static_op->GetType()) {
+          case ClipPathOperation::kShape:
+            path = To<ShapeClipPathOperation>(static_op)->GetPath(
+                reference_size, zoom);
+            break;
+          case ClipPathOperation::kGeometryBox:
+            path = ClipPathClipper::RoundedReferenceBox(
+                       To<GeometryBoxClipPathOperation>(static_op)
+                           ->GetGeometryBox(),
+                       *element->GetLayoutObject())
+                       .GetPath();
+            break;
+          case ClipPathOperation::kReference:
+            // Reference clip paths are implemented with mask images, and are
+            // not reducible to single SkPaths.
+            NOTREACHED();
+        }
         static_path = path.GetSkPath();
       } else {
         static_path = InfiniteClipPath();
diff --git a/third_party/blink/renderer/modules/media_capabilities/media_capabilities.cc b/third_party/blink/renderer/modules/media_capabilities/media_capabilities.cc
index c65e2f7..52161b7e 100644
--- a/third_party/blink/renderer/modules/media_capabilities/media_capabilities.cc
+++ b/third_party/blink/renderer/modules/media_capabilities/media_capabilities.cc
@@ -1091,7 +1091,7 @@
               : std::nullopt;
 
       std::optional<webrtc::SdpVideoFormat> sdp_video_format;
-      std::optional<String> scalability_mode;
+      String scalability_mode;
       media::VideoCodecProfile codec_profile =
           media::VIDEO_CODEC_PROFILE_UNKNOWN;
       int video_pixels = 0;
@@ -1099,10 +1099,9 @@
       if (config->hasVideo()) {
         sdp_video_format =
             std::make_optional(ToSdpVideoFormat(config->video()));
-        scalability_mode =
-            config->video()->hasScalabilityMode()
-                ? std::make_optional(config->video()->scalabilityMode())
-                : std::nullopt;
+        if (config->video()->hasScalabilityMode()) {
+          scalability_mode = config->video()->scalabilityMode();
+        }
 
         // Additional information needed for lookup in WebrtcVideoPerfHistory.
         codec_profile =
diff --git a/third_party/blink/renderer/modules/peerconnection/fake_rtc_rtp_transceiver_impl.cc b/third_party/blink/renderer/modules/peerconnection/fake_rtc_rtp_transceiver_impl.cc
index 8a6982e1..47f987f 100644
--- a/third_party/blink/renderer/modules/peerconnection/fake_rtc_rtp_transceiver_impl.cc
+++ b/third_party/blink/renderer/modules/peerconnection/fake_rtc_rtp_transceiver_impl.cc
@@ -31,7 +31,7 @@
 }
 
 FakeRTCRtpSenderImpl::FakeRTCRtpSenderImpl(
-    std::optional<String> track_id,
+    String track_id,
     Vector<String> stream_ids,
     scoped_refptr<base::SingleThreadTaskRunner> task_runner)
     : track_id_(std::move(track_id)),
@@ -71,8 +71,9 @@
 }
 
 MediaStreamComponent* FakeRTCRtpSenderImpl::Track() const {
-  return track_id_ ? CreateMediaStreamComponent(*track_id_, task_runner_)
-                   : nullptr;
+  return !track_id_.IsNull()
+             ? CreateMediaStreamComponent(track_id_, task_runner_)
+             : nullptr;
 }
 
 Vector<String> FakeRTCRtpSenderImpl::StreamIds() const {
diff --git a/third_party/blink/renderer/modules/peerconnection/fake_rtc_rtp_transceiver_impl.h b/third_party/blink/renderer/modules/peerconnection/fake_rtc_rtp_transceiver_impl.h
index 6483da6a9..628b51b44 100644
--- a/third_party/blink/renderer/modules/peerconnection/fake_rtc_rtp_transceiver_impl.h
+++ b/third_party/blink/renderer/modules/peerconnection/fake_rtc_rtp_transceiver_impl.h
@@ -29,7 +29,7 @@
 
 class FakeRTCRtpSenderImpl : public blink::RTCRtpSenderPlatform {
  public:
-  FakeRTCRtpSenderImpl(std::optional<String> track_id,
+  FakeRTCRtpSenderImpl(String track_id,
                        Vector<String> stream_ids,
                        scoped_refptr<base::SingleThreadTaskRunner> task_runner);
   FakeRTCRtpSenderImpl(const FakeRTCRtpSenderImpl&);
@@ -53,7 +53,7 @@
   void SetStreams(const Vector<String>& stream_ids) override;
 
  private:
-  std::optional<String> track_id_;
+  String track_id_;
   Vector<String> stream_ids_;
   scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
 };
diff --git a/third_party/blink/renderer/modules/peerconnection/peer_connection_tracker.cc b/third_party/blink/renderer/modules/peerconnection/peer_connection_tracker.cc
index f668d00..14d3d22e 100644
--- a/third_party/blink/renderer/modules/peerconnection/peer_connection_tracker.cc
+++ b/third_party/blink/renderer/modules/peerconnection/peer_connection_tracker.cc
@@ -841,15 +841,16 @@
   int id = GetLocalIDForHandler(pc_handler);
   if (id == -1)
     return;
-  std::optional<String> relay_protocol = candidate->RelayProtocol();
-  std::optional<String> url = candidate->Url();
+  String relay_protocol = candidate->RelayProtocol();
+  String url = candidate->Url();
   String value =
       "sdpMid: " + String(candidate->SdpMid()) + ", " + "sdpMLineIndex: " +
       (candidate->SdpMLineIndex() ? String::Number(*candidate->SdpMLineIndex())
                                   : "null") +
       ", candidate: " + String(candidate->Candidate()) +
-      (url ? ", url: " + *url : String()) +
-      (relay_protocol ? ", relayProtocol: " + *relay_protocol : String());
+      (!url.IsNull() ? ", url: " + url : String()) +
+      (!relay_protocol.IsNull() ? ", relayProtocol: " + relay_protocol
+                                : String());
 
   // OnIceCandidate always succeeds as it's a callback from the browser.
   DCHECK(source != kSourceLocal || succeeded);
diff --git a/third_party/blink/renderer/modules/peerconnection/peer_connection_tracker_test.cc b/third_party/blink/renderer/modules/peerconnection/peer_connection_tracker_test.cc
index bfe4e7d..04a901b 100644
--- a/third_party/blink/renderer/modules/peerconnection/peer_connection_tracker_test.cc
+++ b/third_party/blink/renderer/modules/peerconnection/peer_connection_tracker_test.cc
@@ -303,7 +303,7 @@
   blink::FakeRTCRtpTransceiverImpl transceiver(
       String(),
       blink::FakeRTCRtpSenderImpl(
-          std::nullopt, {},
+          String(), {},
           blink::scheduler::GetSingleThreadTaskRunnerForTesting()),
       blink::FakeRTCRtpReceiverImpl(
           "receiverTrackId", {},
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_ice_candidate.cc b/third_party/blink/renderer/modules/peerconnection/rtc_ice_candidate.cc
index 2a6164e6..8763a49 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_ice_candidate.cc
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_ice_candidate.cc
@@ -73,7 +73,7 @@
       MakeGarbageCollected<RTCIceCandidatePlatform>(
           candidate_init->candidate(), sdp_mid, std::move(sdp_m_line_index),
           candidate_init->usernameFragment(),
-          /*url can not be reconstruncted*/ std::nullopt));
+          /*url can not be reconstruncted*/ String()));
 }
 
 RTCIceCandidate* RTCIceCandidate::Create(
@@ -134,11 +134,11 @@
 }
 
 std::optional<V8RTCIceTcpCandidateType> RTCIceCandidate::tcpType() const {
-  std::optional<String> tcp_type = platform_candidate_->TcpType();
-  if (!tcp_type.has_value()) {
+  String tcp_type = platform_candidate_->TcpType();
+  if (tcp_type.IsNull()) {
     return std::nullopt;
   }
-  return V8RTCIceTcpCandidateType::Create(tcp_type.value());
+  return V8RTCIceTcpCandidateType::Create(tcp_type);
 }
 
 String RTCIceCandidate::relatedAddress() const {
@@ -154,20 +154,16 @@
 }
 
 String RTCIceCandidate::url() const {
-  const std::optional<String> url = platform_candidate_->Url();
-  if (!url) {
-    return g_null_atom;
-  }
-  return *url;
+  return platform_candidate_->Url();
 }
 
 std::optional<V8RTCIceServerTransportProtocol> RTCIceCandidate::relayProtocol()
     const {
-  std::optional<String> relay_protocol = platform_candidate_->RelayProtocol();
-  if (!relay_protocol.has_value()) {
+  String relay_protocol = platform_candidate_->RelayProtocol();
+  if (relay_protocol.IsNull()) {
     return std::nullopt;
   }
-  return V8RTCIceServerTransportProtocol::Create(relay_protocol.value());
+  return V8RTCIceServerTransportProtocol::Create(relay_protocol);
 }
 
 ScriptObject RTCIceCandidate::toJSONForBinding(ScriptState* script_state) {
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_ice_transport.cc b/third_party/blink/renderer/modules/peerconnection/rtc_ice_transport.cc
index 8886a59..f8980197e 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_ice_transport.cc
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_ice_transport.cc
@@ -42,15 +42,10 @@
 namespace {
 
 RTCIceCandidate* ConvertToRtcIceCandidate(const cricket::Candidate& candidate) {
-  std::string url = candidate.url();
-  std::optional<String> optional_url;
-  if (!url.empty()) {
-    optional_url = String(url);
-  }
   // The "" mid and sdpMLineIndex 0 are wrong, see https://crbug.com/1385446
   return RTCIceCandidate::Create(MakeGarbageCollected<RTCIceCandidatePlatform>(
       String::FromUTF8(webrtc::SdpSerializeCandidate(candidate)), "", 0,
-      String(candidate.username()), optional_url));
+      String(candidate.username()), String(candidate.url())));
 }
 
 class DtlsIceTransportAdapterCrossThreadFactory
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection.cc b/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection.cc
index 727cb70..a8c88d0 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection.cc
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection.cc
@@ -242,7 +242,7 @@
   return MakeGarbageCollected<RTCIceCandidatePlatform>(
       candidate->candidate(), candidate->sdpMid(), sdp_m_line_index,
       candidate->usernameFragment(),
-      /*url can not be reconstruncted*/ std::nullopt);
+      /*url can not be reconstruncted*/ String());
 }
 
 webrtc::PeerConnectionInterface::IceTransportsType IceTransportPolicyFromEnum(
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection_handler.cc b/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection_handler.cc
index 55df420..2249344 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection_handler.cc
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection_handler.cc
@@ -1946,13 +1946,9 @@
     return;
   }
   TRACE_EVENT0("webrtc", "RTCPeerConnectionHandler::OnIceCandidateImpl");
-  std::optional<String> url_or_null;
-  if (!url.empty()) {
-    url_or_null = url;
-  }
   // This line can cause garbage collection.
   auto* platform_candidate = MakeGarbageCollected<RTCIceCandidatePlatform>(
-      sdp, sdp_mid, sdp_mline_index, usernameFragment, url_or_null);
+      sdp, sdp_mid, sdp_mline_index, usernameFragment, url);
   if (peer_connection_tracker_) {
     peer_connection_tracker_->TrackAddIceCandidate(
         this, platform_candidate, PeerConnectionTracker::kSourceLocal, true);
diff --git a/third_party/blink/renderer/modules/shared_storage/shared_storage_blink_mojom_traits.cc b/third_party/blink/renderer/modules/shared_storage/shared_storage_blink_mojom_traits.cc
index 6ca5ed1..e690131 100644
--- a/third_party/blink/renderer/modules/shared_storage/shared_storage_blink_mojom_traits.cc
+++ b/third_party/blink/renderer/modules/shared_storage/shared_storage_blink_mojom_traits.cc
@@ -38,4 +38,21 @@
   NOTREACHED();
 }
 
+// static
+bool StructTraits<
+    network::mojom::SharedStorageBatchUpdateMethodsArgumentDataView,
+    WTF::Vector<
+        network::mojom::blink::SharedStorageModifierMethodWithOptionsPtr>>::
+    Read(network::mojom::SharedStorageBatchUpdateMethodsArgumentDataView data,
+         WTF::Vector<
+             network::mojom::blink::SharedStorageModifierMethodWithOptionsPtr>*
+             out_value) {
+  // There is no need to convert
+  // `SharedStorageBatchUpdateMethodsArgumentDataView` back to
+  // `WTF::Vector<network::mojom::blink::SharedStorageModifierMethodWithOptionsPtr>`.
+  // If we do need to implement deserialization later, we need to validate its
+  // content.
+  NOTREACHED();
+}
+
 }  // namespace mojo
diff --git a/third_party/blink/renderer/modules/shared_storage/shared_storage_blink_mojom_traits.h b/third_party/blink/renderer/modules/shared_storage/shared_storage_blink_mojom_traits.h
index 019384b..90e20f3 100644
--- a/third_party/blink/renderer/modules/shared_storage/shared_storage_blink_mojom_traits.h
+++ b/third_party/blink/renderer/modules/shared_storage/shared_storage_blink_mojom_traits.h
@@ -41,6 +41,26 @@
   static const WTF::String& data(const WTF::String& input) { return input; }
 };
 
+template <>
+struct PLATFORM_EXPORT StructTraits<
+    network::mojom::SharedStorageBatchUpdateMethodsArgumentDataView,
+    WTF::Vector<
+        network::mojom::blink::SharedStorageModifierMethodWithOptionsPtr>> {
+  static bool Read(
+      network::mojom::SharedStorageBatchUpdateMethodsArgumentDataView data,
+      WTF::Vector<
+          network::mojom::blink::SharedStorageModifierMethodWithOptionsPtr>*
+          out_value);
+
+  static const WTF::Vector<
+      network::mojom::blink::SharedStorageModifierMethodWithOptionsPtr>&
+  data(const WTF::Vector<
+       network::mojom::blink::SharedStorageModifierMethodWithOptionsPtr>&
+           input) {
+    return input;
+  }
+};
+
 }  // namespace mojo
 
 #endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_SHARED_STORAGE_SHARED_STORAGE_BLINK_MOJOM_TRAITS_H_
diff --git a/third_party/blink/renderer/platform/fonts/shaping/glyph_data.h b/third_party/blink/renderer/platform/fonts/shaping/glyph_data.h
index 8dda89d..cb5286b6 100644
--- a/third_party/blink/renderer/platform/fonts/shaping/glyph_data.h
+++ b/third_party/blink/renderer/platform/fonts/shaping/glyph_data.h
@@ -142,6 +142,8 @@
 // A array of glyph offsets. If all offsets are zero, we don't allocate
 // storage for reducing memory usage.
 class GlyphOffsetArray final {
+  DISALLOW_NEW();
+
  public:
   explicit GlyphOffsetArray(unsigned size) : size_(size) {}
 
diff --git a/third_party/blink/renderer/platform/fonts/shaping/shape_result_inline_headers.h b/third_party/blink/renderer/platform/fonts/shaping/shape_result_inline_headers.h
index 7bff6ed9..21a34f5 100644
--- a/third_party/blink/renderer/platform/fonts/shaping/shape_result_inline_headers.h
+++ b/third_party/blink/renderer/platform/fonts/shaping/shape_result_inline_headers.h
@@ -242,6 +242,8 @@
 
   // Collection of |HarfBuzzRunGlyphData| with optional glyph offset
   class GlyphDataCollection final {
+    DISALLOW_NEW();
+
    public:
     explicit GlyphDataCollection(unsigned num_glyphs)
         : data_(new HarfBuzzRunGlyphData[num_glyphs]), offsets_(num_glyphs) {}
diff --git a/third_party/blink/renderer/platform/geometry/path.cc b/third_party/blink/renderer/platform/geometry/path.cc
index 518d398..2ded30e 100644
--- a/third_party/blink/renderer/platform/geometry/path.cc
+++ b/third_party/blink/renderer/platform/geometry/path.cc
@@ -334,10 +334,6 @@
   return std::nullopt;
 }
 
-void Path::SetWindRule(const WindRule rule) {
-  path_.setFillType(WebCoreWindRuleToSkFillType(rule));
-}
-
 void Path::MoveTo(const gfx::PointF& point) {
   path_.moveTo(gfx::PointFToSkPoint(point));
 }
diff --git a/third_party/blink/renderer/platform/geometry/path.h b/third_party/blink/renderer/platform/geometry/path.h
index f7e77d2..3c4178a2 100644
--- a/third_party/blink/renderer/platform/geometry/path.h
+++ b/third_party/blink/renderer/platform/geometry/path.h
@@ -156,8 +156,6 @@
 
   // TODO(crbug.com/378688986): convert clients to PathBuilder and remove all
   // editing (non-const) methods.
-  void SetWindRule(const WindRule);
-
   void MoveTo(const gfx::PointF&);
   void AddLineTo(const gfx::PointF&);
   void AddQuadCurveTo(const gfx::PointF& control_point,
diff --git a/third_party/blink/renderer/platform/geometry/path_builder.cc b/third_party/blink/renderer/platform/geometry/path_builder.cc
index 749ff3d..346cfc0 100644
--- a/third_party/blink/renderer/platform/geometry/path_builder.cc
+++ b/third_party/blink/renderer/platform/geometry/path_builder.cc
@@ -352,4 +352,11 @@
   return *this;
 }
 
+PathBuilder& PathBuilder::Transform(const AffineTransform& xform) {
+  builder_.transform(xform.ToSkMatrix());
+
+  current_path_.reset();
+  return *this;
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/geometry/path_builder.h b/third_party/blink/renderer/platform/geometry/path_builder.h
index a2b4f1c..71025bf1 100644
--- a/third_party/blink/renderer/platform/geometry/path_builder.h
+++ b/third_party/blink/renderer/platform/geometry/path_builder.h
@@ -107,6 +107,7 @@
 
   PathBuilder& SetWindRule(WindRule);
   PathBuilder& Translate(const gfx::Vector2dF& offset);
+  PathBuilder& Transform(const AffineTransform&);
 
  private:
   // TODO(crbug.com/378688986): switch to SkPathBuilder when ready.
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 3723a7d..18e61fc 100644
--- a/third_party/blink/renderer/platform/graphics/canvas_resource_provider.cc
+++ b/third_party/blink/renderer/platform/graphics/canvas_resource_provider.cc
@@ -1213,9 +1213,10 @@
     }
   }
 
+  // TODO(crbug.com/404887530) : Remove or Rename IsGMBAllowed() since
+  // CanvasResourceProvider no longer uses GMBs.
   const bool is_gpu_memory_buffer_image_allowed =
-      is_gpu_compositing_enabled && IsGMBAllowed(size, format, capabilities) &&
-      SharedGpuContext::GetGpuMemoryBufferManager();
+      is_gpu_compositing_enabled && IsGMBAllowed(size, format, capabilities);
 
   if (raster_mode == RasterMode::kCPU && !is_gpu_memory_buffer_image_allowed)
     return nullptr;
@@ -1310,10 +1311,11 @@
       context_provider_wrapper->ContextProvider()
           .SharedImageInterface()
           ->GetCapabilities();
-  // Either swap_chain or gpu memory buffer should be enabled for this be used
+  // Either swap_chain or gpu memory buffer should be enabled for this be used.
+  // TODO(crbug.com/404887530) : Remove or Rename IsGMBAllowed() since
+  // CanvasResourceProvider no longer uses GMBs.
   if (!shared_image_capabilities.shared_image_swap_chain &&
-      (!IsGMBAllowed(size, format, capabilities) ||
-       !Platform::Current()->GetGpuMemoryBufferManager())) {
+      !IsGMBAllowed(size, format, capabilities)) {
     return nullptr;
   }
 
diff --git a/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.cc b/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.cc
index 9828719..e8e0f51 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.cc
+++ b/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.cc
@@ -127,11 +127,6 @@
   if (!cc_effect)
     return false;
 
-  // We directly update opacity only when it's not animating in compositor. If
-  // the compositor has not cleared is_currently_animating_opacity, we should
-  // clear it now to let the compositor respect the new value.
-  cc_effect->is_currently_animating_opacity = false;
-
   cc_effect->opacity = effect.Opacity();
   cc_effect->effect_changed = true;
   property_trees->effect_tree_mutable().set_needs_update(true);
diff --git a/third_party/blink/renderer/platform/graphics/gpu/shared_gpu_context.cc b/third_party/blink/renderer/platform/graphics/gpu/shared_gpu_context.cc
index af98266..07e94ab 100644
--- a/third_party/blink/renderer/platform/graphics/gpu/shared_gpu_context.cc
+++ b/third_party/blink/renderer/platform/graphics/gpu/shared_gpu_context.cc
@@ -79,26 +79,10 @@
   return this_ptr->shared_image_interface_provider_.get();
 }
 
-gpu::GpuMemoryBufferManager* SharedGpuContext::GetGpuMemoryBufferManager() {
-  SharedGpuContext* this_ptr = GetInstanceForCurrentThread();
-  if (!this_ptr->gpu_memory_buffer_manager_) {
-    this_ptr->CreateContextProviderIfNeeded(/*only_if_gpu_compositing =*/true);
-  }
-  return this_ptr->gpu_memory_buffer_manager_;
-}
-
-void SharedGpuContext::SetGpuMemoryBufferManagerForTesting(
-    gpu::GpuMemoryBufferManager* mgr) {
-  SharedGpuContext* this_ptr = GetInstanceForCurrentThread();
-  DCHECK(!this_ptr->gpu_memory_buffer_manager_ || !mgr);
-  this_ptr->gpu_memory_buffer_manager_ = mgr;
-}
-
 static void CreateContextProviderOnMainThread(
     bool only_if_gpu_compositing,
     bool* gpu_compositing_disabled,
     std::unique_ptr<WebGraphicsContext3DProviderWrapper>* wrapper,
-    gpu::GpuMemoryBufferManager** gpu_memory_buffer_manager,
     base::WaitableEvent* waitable_event) {
   DCHECK(IsMainThread());
 
@@ -125,10 +109,6 @@
         std::move(context_provider));
   }
 
-  // A reference to the GpuMemoryBufferManager can only be obtained on the main
-  // thread, but it is safe to use on other threads.
-  *gpu_memory_buffer_manager = Platform::Current()->GetGpuMemoryBufferManager();
-
   waitable_event->Signal();
 }
 
@@ -172,8 +152,6 @@
           std::make_unique<WebGraphicsContext3DProviderWrapper>(
               std::move(context_provider));
     }
-    gpu_memory_buffer_manager_ =
-        Platform::Current()->GetGpuMemoryBufferManager();
   } else {
     // This synchronous round-trip to the main thread is the reason why
     // SharedGpuContext encasulates the context provider: so we only have to do
@@ -187,7 +165,6 @@
             &CreateContextProviderOnMainThread, only_if_gpu_compositing,
             CrossThreadUnretained(&is_gpu_compositing_disabled_),
             CrossThreadUnretained(&context_provider_wrapper_),
-            CrossThreadUnretained(&gpu_memory_buffer_manager_),
             CrossThreadUnretained(&waitable_event)));
     waitable_event.Wait();
     if (context_provider_wrapper_ &&
@@ -262,7 +239,6 @@
   this_ptr->shared_image_interface_provider_.reset();
   this_ptr->context_provider_wrapper_.reset();
   this_ptr->context_provider_factory_.Reset();
-  this_ptr->gpu_memory_buffer_manager_ = nullptr;
 }
 
 bool SharedGpuContext::IsValidWithoutRestoring() {
diff --git a/third_party/blink/renderer/platform/graphics/gpu/shared_gpu_context.h b/third_party/blink/renderer/platform/graphics/gpu/shared_gpu_context.h
index cd16844..9b6731e 100644
--- a/third_party/blink/renderer/platform/graphics/gpu/shared_gpu_context.h
+++ b/third_party/blink/renderer/platform/graphics/gpu/shared_gpu_context.h
@@ -17,12 +17,6 @@
 #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 #include "third_party/blink/renderer/platform/wtf/thread_specific.h"
 
-namespace gpu {
-
-class GpuMemoryBufferManager;
-
-}  // namespace gpu
-
 namespace blink {
 
 class WebGraphicsContext3DProvider;
@@ -73,10 +67,6 @@
   // to not interfere with the next test and when terminating web workers.
   static void Reset();
 
-  static gpu::GpuMemoryBufferManager* GetGpuMemoryBufferManager();
-  static void SetGpuMemoryBufferManagerForTesting(
-      gpu::GpuMemoryBufferManager* mgr);
-
  private:
   friend class WTF::ThreadSpecific<SharedGpuContext>;
 
@@ -96,11 +86,6 @@
 
   std::unique_ptr<WebGraphicsSharedImageInterfaceProvider>
       shared_image_interface_provider_;
-
-  // RAW_PTR_EXCLUSION: Performance (MotionMark). Please see crbug.com/346693834
-  // for more details.
-  RAW_PTR_EXCLUSION gpu::GpuMemoryBufferManager* gpu_memory_buffer_manager_ =
-      nullptr;
 };
 
 }  // blink
diff --git a/third_party/blink/renderer/platform/graphics/test/gpu_memory_buffer_test_platform.h b/third_party/blink/renderer/platform/graphics/test/gpu_memory_buffer_test_platform.h
index 104e73f..e2a15db 100644
--- a/third_party/blink/renderer/platform/graphics/test/gpu_memory_buffer_test_platform.h
+++ b/third_party/blink/renderer/platform/graphics/test/gpu_memory_buffer_test_platform.h
@@ -12,13 +12,9 @@
 namespace blink {
 class GpuMemoryBufferTestPlatform : public blink::TestingPlatformSupport {
  public:
-  GpuMemoryBufferTestPlatform() {
-    SharedGpuContext::SetGpuMemoryBufferManagerForTesting(
-        &test_gpu_memory_buffer_manager_);
-  }
-  ~GpuMemoryBufferTestPlatform() override {
-    SharedGpuContext::SetGpuMemoryBufferManagerForTesting(nullptr);
-  }
+  GpuMemoryBufferTestPlatform() {}
+
+  ~GpuMemoryBufferTestPlatform() override {}
 
   bool IsGpuCompositingDisabled() const override {
     return is_gpu_compositing_disabled_;
@@ -36,6 +32,7 @@
   gpu::TestGpuMemoryBufferManager test_gpu_memory_buffer_manager_;
   bool is_gpu_compositing_disabled_ = false;
 };
+
 }  // namespace blink
 
 #endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_TEST_GPU_MEMORY_BUFFER_TEST_PLATFORM_H_
diff --git a/third_party/blink/renderer/platform/peerconnection/rtc_ice_candidate_platform.cc b/third_party/blink/renderer/platform/peerconnection/rtc_ice_candidate_platform.cc
index 0cdc5560..05935c28 100644
--- a/third_party/blink/renderer/platform/peerconnection/rtc_ice_candidate_platform.cc
+++ b/third_party/blink/renderer/platform/peerconnection/rtc_ice_candidate_platform.cc
@@ -46,7 +46,7 @@
     String sdp_mid,
     std::optional<uint16_t> sdp_m_line_index,
     String username_fragment,
-    std::optional<String> url)
+    String url)
     : candidate_(std::move(candidate)),
       sdp_mid_(std::move(sdp_mid)),
       sdp_m_line_index_(std::move(sdp_m_line_index)),
@@ -94,7 +94,7 @@
     related_port_ = c.related_address().port();
   }
   // url_ is set only when the candidate was gathered locally.
-  if (type_ == "relay" && priority_ && url_) {
+  if (type_ == "relay" && priority_ && !url_.IsNull()) {
     relay_protocol_ = PriorityToRelayProtocol(*priority_);
   }
 
diff --git a/third_party/blink/renderer/platform/peerconnection/rtc_ice_candidate_platform.h b/third_party/blink/renderer/platform/peerconnection/rtc_ice_candidate_platform.h
index 29e4fda8..de34beac 100644
--- a/third_party/blink/renderer/platform/peerconnection/rtc_ice_candidate_platform.h
+++ b/third_party/blink/renderer/platform/peerconnection/rtc_ice_candidate_platform.h
@@ -50,12 +50,12 @@
                           std::optional<uint16_t> sdp_m_line_index);
 
   // Creates a new RTCIceCandidatePlatform using |candidate|, |sdp_mid|,
-  // |sdp_m_line_index|, |username_fragment| and optional |url|.
+  // |sdp_m_line_index|, |username_fragment| and |url|.
   RTCIceCandidatePlatform(String candidate,
                           String sdp_mid,
                           std::optional<uint16_t> sdp_m_line_index,
                           String username_fragment,
-                          std::optional<String> url);
+                          String url);
   RTCIceCandidatePlatform(const RTCIceCandidatePlatform&) = delete;
   RTCIceCandidatePlatform& operator=(const RTCIceCandidatePlatform&) = delete;
   ~RTCIceCandidatePlatform() = default;
@@ -72,12 +72,12 @@
   const String Protocol() const { return protocol_; }
   const std::optional<uint16_t>& Port() const { return port_; }
   const String& Type() const { return type_; }
-  const std::optional<String>& TcpType() const { return tcp_type_; }
+  const String& TcpType() const { return tcp_type_; }
   const String& RelatedAddress() const { return related_address_; }
   const std::optional<uint16_t>& RelatedPort() const { return related_port_; }
   const String& UsernameFragment() const { return username_fragment_; }
-  const std::optional<String>& RelayProtocol() const { return relay_protocol_; }
-  const std::optional<String>& Url() const { return url_; }
+  const String& RelayProtocol() const { return relay_protocol_; }
+  const String& Url() const { return url_; }
 
   void Trace(Visitor*) const {}
 
@@ -94,12 +94,12 @@
   String protocol_;
   std::optional<uint16_t> port_;
   String type_;
-  std::optional<String> tcp_type_;
+  String tcp_type_;
   String related_address_;
   std::optional<uint16_t> related_port_;
   String username_fragment_;
-  std::optional<String> url_;
-  std::optional<String> relay_protocol_;
+  String url_;
+  String relay_protocol_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/peerconnection/rtc_ice_candidate_platform_test.cc b/third_party/blink/renderer/platform/peerconnection/rtc_ice_candidate_platform_test.cc
index 50ba53f..fe1ea9e 100644
--- a/third_party/blink/renderer/platform/peerconnection/rtc_ice_candidate_platform_test.cc
+++ b/third_party/blink/renderer/platform/peerconnection/rtc_ice_candidate_platform_test.cc
@@ -38,7 +38,7 @@
   RTCIceCandidatePlatform* candidate =
       MakeGarbageCollected<RTCIceCandidatePlatform>(
           kSrflxCandidateStr, kMid, kSdpMLineIndex, kUsernameFragment, kUrl);
-  EXPECT_EQ(candidate->RelayProtocol(), std::nullopt);
+  EXPECT_EQ(candidate->RelayProtocol(), String());
 }
 
 TEST(RTCIceCandidatePlatformTest, LocalRelayCandidateRelayProtocolSet) {
@@ -58,8 +58,8 @@
 TEST(RTCIceCandidatePlatformTest, RemoteRelayCandidateRelayProtocolUnset) {
   RTCIceCandidatePlatform* candidate =
       MakeGarbageCollected<RTCIceCandidatePlatform>(
-          kUdpRelayCandidateStr, kMid, 1, kUsernameFragment, std::nullopt);
-  EXPECT_EQ(candidate->RelayProtocol(), std::nullopt);
+          kUdpRelayCandidateStr, kMid, 1, kUsernameFragment, String());
+  EXPECT_EQ(candidate->RelayProtocol(), String());
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/peerconnection/webrtc_encoding_info_handler.cc b/third_party/blink/renderer/platform/peerconnection/webrtc_encoding_info_handler.cc
index 1119516..c5cbdc7 100644
--- a/third_party/blink/renderer/platform/peerconnection/webrtc_encoding_info_handler.cc
+++ b/third_party/blink/renderer/platform/peerconnection/webrtc_encoding_info_handler.cc
@@ -55,7 +55,7 @@
 void WebrtcEncodingInfoHandler::EncodingInfo(
     const std::optional<webrtc::SdpAudioFormat> sdp_audio_format,
     const std::optional<webrtc::SdpVideoFormat> sdp_video_format,
-    const std::optional<String> video_scalability_mode,
+    const String video_scalability_mode,
     OnMediaCapabilitiesEncodingInfoCallback callback) const {
   DCHECK(sdp_audio_format || sdp_video_format);
 
@@ -77,8 +77,8 @@
   // not specified).
   if (sdp_video_format && supported) {
     std::optional<std::string> scalability_mode =
-        video_scalability_mode
-            ? std::make_optional(video_scalability_mode->Utf8())
+        !video_scalability_mode.IsNull()
+            ? std::make_optional(video_scalability_mode.Utf8())
             : std::nullopt;
     webrtc::VideoEncoderFactory::CodecSupport support =
         video_encoder_factory_->QueryCodecSupport(*sdp_video_format,
diff --git a/third_party/blink/renderer/platform/peerconnection/webrtc_encoding_info_handler.h b/third_party/blink/renderer/platform/peerconnection/webrtc_encoding_info_handler.h
index 76740bd..4808b644 100644
--- a/third_party/blink/renderer/platform/peerconnection/webrtc_encoding_info_handler.h
+++ b/third_party/blink/renderer/platform/peerconnection/webrtc_encoding_info_handler.h
@@ -44,7 +44,7 @@
   void EncodingInfo(
       const std::optional<webrtc::SdpAudioFormat> sdp_audio_format,
       const std::optional<webrtc::SdpVideoFormat> sdp_video_format,
-      const std::optional<String> video_scalability_mode,
+      const String video_scalability_mode,
       OnMediaCapabilitiesEncodingInfoCallback callback) const;
 
  private:
diff --git a/third_party/blink/renderer/platform/peerconnection/webrtc_encoding_info_handler_test.cc b/third_party/blink/renderer/platform/peerconnection/webrtc_encoding_info_handler_test.cc
index 1c06c62e..decf45f 100644
--- a/third_party/blink/renderer/platform/peerconnection/webrtc_encoding_info_handler_test.cc
+++ b/third_party/blink/renderer/platform/peerconnection/webrtc_encoding_info_handler_test.cc
@@ -82,15 +82,15 @@
   void VerifyEncodingInfo(
       const std::optional<webrtc::SdpAudioFormat> sdp_audio_format,
       const std::optional<webrtc::SdpVideoFormat> sdp_video_format,
-      const std::optional<String> video_scalability_mode,
+      const String video_scalability_mode,
       const CodecSupport support) {
     auto video_encoder_factory = std::make_unique<MockVideoEncoderFactory>();
     rtc::scoped_refptr<webrtc::AudioEncoderFactory> audio_encoder_factory =
         blink::CreateWebrtcAudioEncoderFactory();
     if (sdp_video_format) {
       const std::optional<std::string> expected_scalability_mode =
-          video_scalability_mode
-              ? std::make_optional(video_scalability_mode->Utf8())
+          !video_scalability_mode.IsNull()
+              ? std::make_optional(video_scalability_mode.Utf8())
               : std::nullopt;
 
       ON_CALL(*video_encoder_factory, QueryCodecSupport)
@@ -126,14 +126,14 @@
 TEST_F(WebrtcEncodingInfoHandlerTests, BasicAudio) {
   VerifyEncodingInfo(
       kAudioFormatOpus, /*sdp_video_format=*/std::nullopt,
-      /*video_scalability_mode=*/std::nullopt,
+      /*video_scalability_mode=*/String(),
       CodecSupport{/*is_supported=*/true, /*is_power_efficient=*/true});
 }
 
 TEST_F(WebrtcEncodingInfoHandlerTests, UnsupportedAudio) {
   VerifyEncodingInfo(
       kAudioFormatFoo, /*sdp_video_format=*/std::nullopt,
-      /*video_scalability_mode=*/std::nullopt,
+      /*video_scalability_mode=*/String(),
       CodecSupport{/*is_supported=*/false, /*is_power_efficient=*/false});
 }
 
@@ -144,21 +144,21 @@
 TEST_F(WebrtcEncodingInfoHandlerTests, BasicVideo) {
   VerifyEncodingInfo(
       /*sdp_audio_format=*/std::nullopt, kVideoFormatVp9,
-      /*video_scalability_mode=*/std::nullopt,
+      /*video_scalability_mode=*/String(),
       CodecSupport{/*is_supported=*/true, /*is_power_efficient=*/false});
 }
 
 TEST_F(WebrtcEncodingInfoHandlerTests, BasicVideoPowerEfficient) {
   VerifyEncodingInfo(
       /*sdp_audio_format=*/std::nullopt, kVideoFormatVp9,
-      /*video_scalability_mode=*/std::nullopt,
+      /*video_scalability_mode=*/String(),
       CodecSupport{/*is_supported=*/true, /*is_power_efficient=*/true});
 }
 
 TEST_F(WebrtcEncodingInfoHandlerTests, UnsupportedVideo) {
   VerifyEncodingInfo(
       /*sdp_audio_format=*/std::nullopt, kVideoFormatFoo,
-      /*video_scalability_mode=*/std::nullopt,
+      /*video_scalability_mode=*/String(),
       CodecSupport{/*is_supported=*/true, /*is_power_efficient=*/false});
 }
 
@@ -171,14 +171,14 @@
 TEST_F(WebrtcEncodingInfoHandlerTests, SupportedAudioUnsupportedVideo) {
   VerifyEncodingInfo(
       kAudioFormatOpus, kVideoFormatFoo,
-      /*video_scalability_mode=*/std::nullopt,
+      /*video_scalability_mode=*/String(),
       CodecSupport{/*is_supported=*/false, /*is_power_efficient=*/false});
 }
 
 TEST_F(WebrtcEncodingInfoHandlerTests, SupportedVideoUnsupportedAudio) {
   VerifyEncodingInfo(
       kAudioFormatFoo, kVideoFormatVp9,
-      /*video_scalability_mode=*/std::nullopt,
+      /*video_scalability_mode=*/String(),
       CodecSupport{/*is_supported=*/false, /*is_power_efficient=*/false});
 }
 
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index 4ee5be9b..e7aec76 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -1742,10 +1742,7 @@
     // IncludeJSCallStacksInCrashReports. https://chromestatus.com/feature/4731248572628992
     {
       name: "DocumentPolicyIncludeJSCallStacksInCrashReports",
-      origin_trial_feature_name: "DocumentPolicyIncludeJSCallStacksInCrashReports",
-      status: "experimental",
-      base_feature_status: "enabled",
-      copied_from_base_feature_if: "overridden",
+      status: "stable",
     },
     {
       name: "DocumentPolicyNegotiation",
diff --git a/third_party/blink/renderer/platform/scheduler/common/features.cc b/third_party/blink/renderer/platform/scheduler/common/features.cc
index c3557a8..1179cb7 100644
--- a/third_party/blink/renderer/platform/scheduler/common/features.cc
+++ b/third_party/blink/renderer/platform/scheduler/common/features.cc
@@ -104,19 +104,6 @@
       GetLoadingPhaseBufferTimeAfterFirstMeaningfulPaintMillis());
 }
 
-base::TimeDelta GetThreadedScrollRenderingStarvationThreshold() {
-  static const base::FeatureParam<int>
-      kThreadedScrollRenderingStarvationThreshold{
-          &features::kThreadedScrollPreventRenderingStarvation, "threshold_ms",
-          100};
-  if (base::FeatureList::IsEnabled(
-          features::kThreadedScrollPreventRenderingStarvation)) {
-    return base::Milliseconds(
-        kThreadedScrollRenderingStarvationThreshold.Get());
-  }
-  return base::TimeDelta::Max();
-}
-
 BASE_FEATURE(kThrottleTimedOutIdleTasks,
              "ThrottleTimedOutIdleTasks",
              base::FEATURE_ENABLED_BY_DEFAULT);
diff --git a/third_party/blink/renderer/platform/scheduler/common/features.h b/third_party/blink/renderer/platform/scheduler/common/features.h
index 7d358a35..518d373 100644
--- a/third_party/blink/renderer/platform/scheduler/common/features.h
+++ b/third_party/blink/renderer/platform/scheduler/common/features.h
@@ -71,12 +71,6 @@
 PLATFORM_EXPORT base::TimeDelta
 GetLoadingPhaseBufferTimeAfterFirstMeaningfulPaint();
 
-// Returns the threshold to consider rendering starved during threaded
-// scrolling. If `kThreadedScrollPreventRenderingStarvation` is enabled, this
-// returns value of the associated "threshold_ms" FeatureParam; otherwise this
-// returns TimeDelta::Max().
-PLATFORM_EXPORT base::TimeDelta GetThreadedScrollRenderingStarvationThreshold();
-
 // Kill switch for throttling timed-out requestIdleCallback tasks.
 PLATFORM_EXPORT BASE_DECLARE_FEATURE(kThrottleTimedOutIdleTasks);
 
diff --git a/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.cc b/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.cc
index a0456ae..c793a28 100644
--- a/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.cc
+++ b/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.cc
@@ -452,8 +452,6 @@
 MainThreadSchedulerImpl::SchedulingSettings::SchedulingSettings()
     : mbi_override_task_runner_handle(
           base::FeatureList::IsEnabled(kMbiOverrideTaskRunnerHandle)),
-      compositor_gesture_rendering_starvation_threshold(
-          GetThreadedScrollRenderingStarvationThreshold()),
       discrete_input_task_deferral_policy(
           base::FeatureList::IsEnabled(features::kDeferRendererTasksAfterInput)
               ? std::optional<features::TaskDeferralPolicy>(
@@ -2483,30 +2481,14 @@
     return TaskPriority::kHighestPriority;
   }
   // Otherwise, this must be a combination of UseCase::kCompositorGesture and
-  // rendering starvation since all other states set the priority to highest.
+  // rendering starvation since all other use cases set the priority to highest.
   CHECK(current_use_case() == UseCase::kCompositorGesture &&
         (main_thread_only().main_frame_prioritization_state ==
              RenderingPrioritizationState::kRenderingStarved ||
          main_thread_only().main_frame_prioritization_state ==
              RenderingPrioritizationState::kRenderingStarvedByRenderBlocking));
-
-  // The default behavior for compositor gestures like compositor-driven
-  // scrolling is to deprioritize compositor TQ tasks (low priority) and not
-  // apply delay-based anti-starvation. This can lead to degraded user
-  // experience due to increased checkerboarding or scrolling blank content.
-  // When `features::kThreadedScrollPreventRenderingStarvation` is enabled, we
-  // use a configurable value to control the delay-based anti-starvation to
-  // mitigate these issues.
-  //
-  // Note: for other use cases, the computed priority is higher, so they are
-  // not prone to rendering starvation in the same way.
-  if (!base::FeatureList::IsEnabled(
-          features::kThreadedScrollPreventRenderingStarvation)) {
-    return *use_case_priority;
-  } else {
-    CHECK_LE(*targeted_main_frame_priority, *use_case_priority);
-    return *targeted_main_frame_priority;
-  }
+  CHECK_LE(*targeted_main_frame_priority, *use_case_priority);
+  return *targeted_main_frame_priority;
 }
 
 void MainThreadSchedulerImpl::UpdateCompositorTaskQueuePriority() {
@@ -2585,20 +2567,6 @@
         task_timing.wall_duration();
   }
 
-  // With `features::kThreadedScrollPreventRenderingStarvation` enabled, no
-  // rendering anti-starvation policy should kick in until the configurable
-  // threshold is reached when in `UseCase::kCompositorGesture`.
-  base::TimeDelta render_blocking_starvation_threshold =
-      base::FeatureList::IsEnabled(
-          features::kThreadedScrollPreventRenderingStarvation) &&
-              current_use_case() == UseCase::kCompositorGesture &&
-              kRenderBlockingStarvationThreshold <
-                  scheduling_settings_
-                      .compositor_gesture_rendering_starvation_threshold
-          ? scheduling_settings_
-                .compositor_gesture_rendering_starvation_threshold
-          : kRenderBlockingStarvationThreshold;
-
   // A main frame task resets the rendering prioritization state. Otherwise if
   // the scheduler is waiting for a frame because of discrete input, the state
   // will only change once a main frame happens. Otherwise, compute the state in
@@ -2621,17 +2589,12 @@
           RenderingPrioritizationState::kWaitingForInputResponse;
     } else if (main_thread_only()
                    .rendering_blocking_duration_since_last_frame >=
-               render_blocking_starvation_threshold) {
+               kRenderBlockingStarvationThreshold) {
       main_thread_only().main_frame_prioritization_state =
           RenderingPrioritizationState::kRenderingStarvedByRenderBlocking;
     } else {
-      base::TimeDelta threshold =
-          current_use_case() == UseCase::kCompositorGesture
-              ? scheduling_settings_
-                    .compositor_gesture_rendering_starvation_threshold
-              : kDefaultRenderingStarvationThreshold;
       if (task_timing.end_time() - main_thread_only().last_frame_time >=
-          threshold) {
+          kDefaultRenderingStarvationThreshold) {
         main_thread_only().main_frame_prioritization_state =
             RenderingPrioritizationState::kRenderingStarved;
       }
@@ -2645,18 +2608,10 @@
     case UseCase::kCompositorGesture:
       if (main_thread_only().blocking_input_expected_soon)
         return TaskPriority::kHighestPriority;
-      // What we really want to do is priorize loading tasks, but that doesn't
-      // seem to be safe. Instead we do that by proxy by deprioritizing
-      // compositor tasks. This should be safe since we've already gone to the
-      // pain of fixing ordering issues with them.
-      //
-      // During periods of main-thread contention, e.g. scrolling while loading
-      // new content, rendering can be indefinitely starved, leading user
-      // experience issues like scrolling blank/stale content and
-      // checkerboarding. We adjust the compositor TQ priority and enable
-      // delay-based rendering anti-starvation when the
-      // `kThreadedScrollPreventRenderingStarvation` experiment is enabled to
-      // mitigate these issues.
+      // Deprioritizing compositor tasks can improve smoothness, but this can
+      // also can increase checkerboarding. This tradeoff is balanced by
+      // increasing priority if rendering is being starved, which is controlled
+      // with `kDefaultRenderingStarvationThreshold`.
       return TaskPriority::kLowPriority;
 
     case UseCase::kSynchronizedGesture:
diff --git a/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.h b/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.h
index ca3cb2c..02226a2 100644
--- a/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.h
+++ b/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.h
@@ -97,11 +97,9 @@
  public:
   // Duration after which rendering is considered starved, in which case the
   // compositor task queues will have an increased priority until the next
-  // BeginMainFrame. This excludes `UseCase::kCompositorGesture` (see
-  // `kThreadedScrollPreventRenderingStarvation). Note that the elevated
-  // priority is lower than that of render-blocking tasks, and a separate higher
-  // threshold is used to prevent starvation by those tasks (see
-  // `kRenderBlockingStarvationThreshold`).
+  // BeginMainFrame. Note that the elevated priority is lower than that of
+  // render-blocking tasks, and a separate higher threshold is used to prevent
+  // starvation by those tasks (see `kRenderBlockingStarvationThreshold`).
   static constexpr base::TimeDelta kDefaultRenderingStarvationThreshold =
       base::Milliseconds(100);
 
@@ -149,10 +147,6 @@
     // per-ASG task runner instead of the per-thread task runner.
     bool mbi_override_task_runner_handle;
 
-    // If ThreadedScrollPreventRenderingStarvation is enabled, this controls the
-    // rendering anti-starvation threshold during UseCase::kCompositorGesture.
-    base::TimeDelta compositor_gesture_rendering_starvation_threshold;
-
     // The policy to use for discrete input-based task deferral. If
     // `features::kDeferRendererTasksAfterInput` is enabled, this is set to the
     // policy set in the associated feature param, otherwise this is
diff --git a/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl_unittest.cc b/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl_unittest.cc
index 416d1041..087e5abd 100644
--- a/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl_unittest.cc
+++ b/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl_unittest.cc
@@ -3611,20 +3611,14 @@
 }
 
 class ThreadedScrollPreventRenderingStarvationTest
-    : public MainThreadSchedulerImplTest,
-      public ::testing::WithParamInterface<int> {
+    : public MainThreadSchedulerImplTest {
  public:
-  ThreadedScrollPreventRenderingStarvationTest() {
-    feature_list_.Reset();
-    feature_list_.InitWithFeaturesAndParameters(
-        {{features::kThreadedScrollPreventRenderingStarvation,
-          base::FieldTrialParams(
-              {{"threshold_ms", base::NumberToString(GetParam())}})}},
-        {});
+  static constexpr base::TimeDelta StarvationThreshold() {
+    return MainThreadSchedulerImpl::kDefaultRenderingStarvationThreshold;
   }
 };
 
-TEST_P(ThreadedScrollPreventRenderingStarvationTest, CompositorPriority) {
+TEST_F(ThreadedScrollPreventRenderingStarvationTest, CompositorPriority) {
   SimulateEnteringCompositorGestureUseCase();
 
   // Compositor task queues should initially have low priority.
@@ -3635,7 +3629,7 @@
   EXPECT_EQ(UseCase::kCompositorGesture, CurrentUseCase());
 
   // The priority should remain low up to the timeout.
-  AdvanceTimeWithTask(base::Milliseconds(GetParam() - 1));
+  AdvanceTimeWithTask(StarvationThreshold() - base::Milliseconds(1));
   // The policy has a max duration, so simulate a longer scroll (multiple
   // updates) with another scroll start.
   SimulateEnteringCompositorGestureUseCase();
@@ -3667,7 +3661,7 @@
   EXPECT_EQ(UseCase::kCompositorGesture, CurrentUseCase());
 }
 
-TEST_P(ThreadedScrollPreventRenderingStarvationTest,
+TEST_F(ThreadedScrollPreventRenderingStarvationTest,
        CompositorPriorityWithRenderBlockingTaskStarvation) {
   // The starved-by-render-blocking-tasks bit isn't cleared when we change use
   // cases, so start out in scrolling use case.
@@ -3684,30 +3678,9 @@
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(UseCase::kCompositorGesture, CurrentUseCase());
 
-  if (base::Milliseconds(GetParam()) >
-      MainThreadSchedulerImpl::kRenderBlockingStarvationThreshold) {
-    // No anti-starvation should kick in.
-    EXPECT_THAT(run_order, testing::ElementsAre("R1", "D1", "D2", "C1", "C2"));
-
-    // Advance far enough to trigger the render-blocking anti-starvation.
-    run_order.clear();
-    SimulateRenderBlockingTask(
-        base::Milliseconds(GetParam()) -
-        MainThreadSchedulerImpl::kRenderBlockingStarvationThreshold);
-    SimulateEnteringCompositorGestureUseCase();
-
-    PostTestTasks(&run_order, "D1 C1 D2 R1 C2");
-    base::RunLoop().RunUntilIdle();
-    EXPECT_EQ(UseCase::kCompositorGesture, CurrentUseCase());
-  }
-
   EXPECT_THAT(run_order, testing::ElementsAre("C1", "R1", "C2", "D1", "D2"));
 }
 
-INSTANTIATE_TEST_SUITE_P(,
-                         ThreadedScrollPreventRenderingStarvationTest,
-                         testing::Values(100, 250, 500, 600));
-
 TEST_F(MainThreadSchedulerImplTest, RenderBlockingTaskPriority) {
   Vector<String> run_order;
   PostTestTasks(&run_order, "D1 CM1 R1 R2 R3");
diff --git a/third_party/blink/renderer/platform/webrtc/webrtc_video_frame_adapter.cc b/third_party/blink/renderer/platform/webrtc/webrtc_video_frame_adapter.cc
index f75bdcb..3b36b07 100644
--- a/third_party/blink/renderer/platform/webrtc/webrtc_video_frame_adapter.cc
+++ b/third_party/blink/renderer/platform/webrtc/webrtc_video_frame_adapter.cc
@@ -225,13 +225,12 @@
 
     if (dst_frame) {
       CHECK(dst_frame->HasSharedImage());
-      const bool copy_succeeded =
+      std::optional<gpu::SyncToken> blit_done_sync_token =
           media::CopyRGBATextureToVideoFrame(
               raster_context_provider.get(), source_frame->coded_size(),
               source_frame->shared_image(), source_frame->acquire_sync_token(),
-              dst_frame.get())
-              .has_value();
-      if (copy_succeeded) {
+              dst_frame.get());
+      if (blit_done_sync_token) {
         // CopyRGBATextureToVideoFrame() operates on mailboxes and not frames,
         // so we must manually copy over properties relevant to the encoder.
         // TODO(https://crbug.com/1272852): Consider bailing out of this path if
@@ -256,13 +255,11 @@
         // copy from the shared image GPU texture to the GMB.
         CHECK(dst_frame->HasMappableGpuBuffer());
         CHECK(!dst_frame->HasNativeGpuMemoryBuffer());
-        gpu::SyncToken blit_done_sync_token;
-        ri->GenUnverifiedSyncTokenCHROMIUM(blit_done_sync_token.GetData());
 
         auto* sii = raster_context_provider->SharedImageInterface();
 
         const auto& mailbox = dst_frame->shared_image()->mailbox();
-        sii->CopyToGpuMemoryBuffer(blit_done_sync_token, mailbox);
+        sii->CopyToGpuMemoryBuffer(*blit_done_sync_token, mailbox);
 
         // Synchronize RasterInterface with SharedImageInterface.
         auto copy_to_gmb_done_sync_token = sii->GenUnverifiedSyncToken();
diff --git a/third_party/blink/tools/commit_stats/affiliations.json5 b/third_party/blink/tools/commit_stats/affiliations.json5
index d777f7f..307f263 100644
--- a/third_party/blink/tools/commit_stats/affiliations.json5
+++ b/third_party/blink/tools/commit_stats/affiliations.json5
@@ -18,6 +18,16 @@
  * Affilitions must be sorted from most recent to least recent.
  */
 {
+  "benjamin.beaudry@chromium.org": {
+    affiliations: [
+      { start: "2019-04-02", domain: "microsoft.com" },
+    ]
+  },
+  "brandonm@chromium.org": {
+    affiliations: [
+      { start: "2020-06-07", domain: "microsoft.com" },
+    ]
+  },
   "cavalcantii@chromium.org": {
     affiliations: [
       { start: "2022-05-03", domain: "intel.com" },
@@ -30,17 +40,32 @@
       { start: "2019-05-01", domain: "microsoft.com" },
     ]
   },
+  "donna@chromium.org": {
+    affiliations: [
+      { start: "2021-11-29", domain: "microsoft.com" },
+    ]
+  },
   "estade@chromium.org": {
     affiliations: [
       { start: "2025-03-10", domain: "microsoft.com" },
     ]
   },
+  "fabiorocha@chromium.org": {
+    affiliations: [
+      { start: "2021-03-05", domain: "microsoft.com" },
+    ]
+  },
   "joone@chromium.org": {
     affiliations: [
       { start: "2024-04-29", domain: "microsoft.com" },
       { start: "2022-06-01", domain: "joone.net" },
     ]
   },
+  "kbabbitt@chromium.org": {
+    affiliations: [
+      { start: "2019-04-02", domain: "microsoft.com" },
+    ]
+  },
   "kzar@chromium.org": {
     affiliations: [
       { start: "2021-11-29", domain: "kzar.co.uk" },
@@ -51,11 +76,26 @@
       { start: "2010-03-09", domain: "gmail.com" },
     ]
   },
+  "minggang@chromium.org": {
+    affiliations: [
+      { start: "2021-10-31", domain: "microsoft.com" },
+    ]
+  },
   "pfeldman@chromium.org": {
     affiliations: [
       { start: "2019-05-01", domain: "microsoft.com" },
     ]
   },
+  "rafael.cintron@chromium.org": {
+    affiliations: [
+      { start: "2019-04-02", domain: "microsoft.com" },
+    ]
+  },
+  "samfort@chromium.org": {
+    affiliations: [
+      { start: "2020-01-14", domain: "microsoft.com" },
+    ]
+  },
   "seokho@chromium.org": {
     affiliations: [
       { start: "2020-07-25", domain: "seokho.dev" },
@@ -67,6 +107,11 @@
       { start: "2014-01-10", domain: "samsung.com" },
     ]
   },
+  "ting.shao@chromium.org": {
+    affiliations: [
+      { start: "2022-03-09", domain: "microsoft.com" },
+    ]
+  },
   "yoavweiss@chromium.org": {
     affiliations: [
       { start: "2024-01-07", domain: "shopify.com"},
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 b1a1cf7..d90e51b 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
@@ -5605,6 +5605,13 @@
       ]
      ],
      "crashtests": {
+      "chrome-405422528-crash.html": [
+       "fcf7407b5f754aa236ddd053f67fb3a15fc6e1d9",
+       [
+        null,
+        {}
+       ]
+      ],
       "viewport-unit-inline-style-crash.html": [
        "4c38e18781bfdbcddce02c1cc5cd3336a7529e85",
        [
@@ -137373,6 +137380,19 @@
        ]
       ]
      },
+     "grid-flex-spanning-items-001.html": [
+      "f9358b65db96d2e7c184ccabd79c5a95765de90c",
+      [
+       null,
+       [
+        [
+         "/css/css-grid/grid-flex-spanning-items-001-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
      "grid-in-table-cell-with-img.html": [
       "492a5866b71cf503ea41c9cb4d862e96c4ee4d18",
       [
@@ -157687,6 +157707,19 @@
          {}
         ]
        ],
+       "clip-path-animation-geometry-box-delay.html": [
+        "a6040d445a90c35f811ba31a70622b1b88351af1",
+        [
+         null,
+         [
+          [
+           "/css/css-masking/clip-path/animations/clip-path-animation-non-shape-delay-ref.html",
+           "=="
+          ]
+         ],
+         {}
+        ]
+       ],
        "clip-path-animation-incompatible-shapes1.html": [
         "88c6862aa9ede58f0d2d0b384f2454ed6130c86f",
         [
@@ -157923,6 +157956,19 @@
          {}
         ]
        ],
+       "clip-path-animation-reference-delay.html": [
+        "4afecbbf9870560718be1ec454d40261326928b5",
+        [
+         null,
+         [
+          [
+           "/css/css-masking/clip-path/animations/clip-path-animation-non-shape-delay-ref.html",
+           "=="
+          ]
+         ],
+         {}
+        ]
+       ],
        "clip-path-animation-revert-layer.html": [
         "cc23c96e2ee76f75cb30280320da3ee9e4cbdb1b",
         [
@@ -173628,6 +173674,19 @@
        {}
       ]
      ],
+     "root-scroll-marker.html": [
+      "48f3a81338a8f0c0445ab1365349d047493e92fc",
+      [
+       null,
+       [
+        [
+         "/css/css-overflow/root-scroll-marker-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
      "rounded-overflow-clip-visible.html": [
       "65e7f055a3907da79701f1f8d68f6527d7c69181",
       [
@@ -252411,6 +252470,19 @@
          ],
          {}
         ]
+       ],
+       "at-rule-types-shared-elements.html": [
+        "50dc5ba1f43b95927f1c2a7559d68ce2bf2b10d7",
+        [
+         null,
+         [
+          [
+           "/css/css-view-transitions/navigation/with-types/at-rule-types-shared-elements-ref.html",
+           "=="
+          ]
+         ],
+         {}
+        ]
        ]
       }
      },
@@ -292427,7 +292499,7 @@
        ]
       ],
       "hidden-until-found-001.html": [
-       "3c87b985dcc885208017f38229fdf853ed748a67",
+       "0fa46f523cd8161879093a78af3a84632e58c91f",
        [
         null,
         [
@@ -292440,7 +292512,7 @@
        ]
       ],
       "hidden-until-found-004.html": [
-       "3ca6feb7a3dd41ab5067b952372962a6446cfda8",
+       "2c0c643c307479b40e3ff4a18a8e97b05117c9fc",
        [
         null,
         [
@@ -292453,7 +292525,7 @@
        ]
       ],
       "hidden-until-found-005.html": [
-       "fabf636ec94850c7eea86b4e4bafafb1f795fd66",
+       "2c4df955aee922c7f53db4b8d432cbe751a7a14a",
        [
         null,
         [
@@ -292466,7 +292538,7 @@
        ]
       ],
       "hidden-until-found-007.html": [
-       "7b11a92cc93940d99769804951ad3fb6c3929094",
+       "64c39c4153a58f2049f50c265dae7346e6b313ee",
        [
         null,
         [
@@ -305830,6 +305902,34 @@
     ]
    },
    "scroll-animations": {
+    "animation-trigger": {
+     "animation-trigger-fill-mode-both.tentative.html": [
+      "237bbb996702d35e3992ce51afb42298398443c2",
+      [
+       null,
+       [
+        [
+         "/scroll-animations/animation-trigger/animation-trigger-fill-mode-both-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
+     "animation-trigger-fill-mode-none.tentative.html": [
+      "81dae8a2f724845c35c50d1413af6796e62797a3",
+      [
+       null,
+       [
+        [
+         "/scroll-animations/animation-trigger/animation-trigger-fill-mode-none-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ]
+    },
     "css": {
      "animation-fill-outside-range-test.html": [
       "c63a5f07662dd04b9b97209b27ec575866680216",
@@ -347012,6 +347112,10 @@
        ]
       }
      },
+     "grid-flex-spanning-items-001-ref.html": [
+      "ec65437e8f385b016dff2f28ac3f82d3500ff91f",
+      []
+     ],
      "grid-fragmentation-between-rows-001-print-ref.tentative.html": [
       "78464712c5cb698fd26a7a12b6c522d4bda946d6",
       []
@@ -349140,7 +349244,7 @@
      ],
      "parsing": {
       "WEB_FEATURES.yml": [
-       "023eb5f6e051bdb858b88e1d70bdbb1f07efe816",
+       "d60ed3efd4ce9e1c4703439b0976b3e625e3bfab",
        []
       ],
       "gradient-interpolation-method-computed-expected.txt": [
@@ -350960,6 +351064,10 @@
         "d351f80a26fa93866afaed00f872a7800078d6c7",
         []
        ],
+       "clip-path-animation-non-shape-delay-ref.html": [
+        "1bd7107cbbd2083b779bb0e82a8341d4576afbbd",
+        []
+       ],
        "clip-path-animation-none-ref.html": [
         "f47e9f31b07c993aac8e065a699d5db2bc3cd9a0",
         []
@@ -353710,6 +353818,10 @@
        []
       ]
      },
+     "root-scroll-marker-ref.html": [
+      "24d3fab4ef1e109a62006799a55b344fd0178759",
+      []
+     ],
      "rounded-overflow-clip-visible-ref.html": [
       "3a25b794d8d4136c2a7c8c94e5d9eb80daceedc7",
       []
@@ -355857,7 +355969,7 @@
       []
      ],
      "WEB_FEATURES.yml": [
-      "ac89c2b73139a5c5c475fdfa06794088cd406236",
+      "c197d348edf390027106ab541eedba8d83a8ed56",
       []
      ],
      "active-selection-051-ref.html": [
@@ -369630,6 +369742,10 @@
         "bf5a73c73ee014ecf5777f77b9282d8f947f58af",
         []
        ],
+       "at-rule-types-shared-elements.html": [
+        "c9cff7f473410e36bad847c37eb3dbdd1369f9ae",
+        []
+       ],
        "auto-name-from-id.html": [
         "71ea52a273e3fca98440cdefc50c77c722663a21",
         []
@@ -369718,7 +369834,13 @@
       "transition-to-prerender-ref.html": [
        "7df899fdca151dd273063323733c0c20fedb3bb3",
        []
-      ]
+      ],
+      "with-types": {
+       "at-rule-types-shared-elements-ref.html": [
+        "2fc574c0f793c57645d9c2e058b1f525b512d401",
+        []
+       ]
+      }
      },
      "nested": {
       "nested-opacity-ref.html": [
@@ -374429,7 +374551,7 @@
       []
      ],
      "WEB_FEATURES.yml": [
-      "cc952ac20098092f75ff503d4bdded9c9ee2ed99",
+      "b090111be0ad9c6be864fa0710948f9dc7a6fb7b",
       []
      ],
      "dynamic-range-expected.txt": [
@@ -396492,11 +396614,11 @@
        []
       ],
       "hidden-until-found-005-ref.html": [
-       "0ca77849a4696c741333c59ca6ab3d5e17c055ae",
+       "e128e1e71395b138e96ac078b8f4616ebf8c6531",
        []
       ],
       "hidden-until-found-006-ref.html": [
-       "e14ba82c278e2c1fc4ecb864871bd6a961c28622",
+       "d8b9761694c396cdab6dfd2b382989c1c3604ab0",
        []
       ],
       "resources": {
@@ -396513,7 +396635,7 @@
         []
        ],
        "container-ref.html": [
-        "e2df59014b06c6f44a506b6e4fd7d56537ae5fa4",
+        "53986abdd1fce970445f7310ab4dfafa59b068d9",
         []
        ],
        "hidden-until-found-text-fragment.html": [
@@ -396521,7 +396643,7 @@
         []
        ],
        "spacer-and-container-ref.html": [
-        "816a6c9a9d800688217a581e7ec0c2164c1f9ec7",
+        "474da70708383ed52e99549b136adfd4a829d0c6",
         []
        ]
       }
@@ -416652,6 +416774,14 @@
      []
     ],
     "animation-trigger": {
+     "animation-trigger-fill-mode-both-ref.html": [
+      "62b74fb778f1acb5e481ad8135b9b34a06a69d24",
+      []
+     ],
+     "animation-trigger-fill-mode-none-ref.html": [
+      "229a31dec909a69aad6e7d86d4d009948db2df74",
+      []
+     ],
      "support": {
       "support.js": [
        "f5aa18e82f19f7fa56b2853e2c46b21342b7d970",
@@ -422024,6 +422154,10 @@
      []
     ],
     "readable-byte-streams": {
+     "WEB_FEATURES.yml": [
+      "a35508fc0dc65d5fcbf785289c5bd05674de7b32",
+      []
+     ],
      "patched-global.any-expected.txt": [
       "2e9003101aab1f0e532cc10f52d40f1885b1679f",
       []
@@ -422188,6 +422322,10 @@
      ]
     },
     "transferable": {
+     "WEB_FEATURES.yml": [
+      "4ecacf5edbc7daca0cc2f94e71c3b8daf8ee52d4",
+      []
+     ],
      "resources": {
       "create-wasm-module.js": [
        "37064af95c55c00e27d5f73b8fc3b0603ac63077",
@@ -424036,7 +424174,7 @@
      []
     ],
     "modify-attributes-in-callback-expected.txt": [
-     "874eb73e808f505d96dfa8c0339664203dc34964",
+     "9a1eb057f50d3a7150b6d949d55a49625c8da8ee",
      []
     ],
     "no-require-trusted-types-for-report-only.html.headers": [
@@ -424057,10 +424195,6 @@
       []
      ]
     },
-    "set-attributes-mutations-in-callback.tentative-expected.txt": [
-     "a91b1a080f69962b888486d055255327776098ef",
-     []
-    ],
     "set-attributes-require-trusted-types-default-policy-expected.txt": [
      "05039edea5d5b9c9a15d0449df3493984be7d263",
      []
@@ -424078,7 +424212,11 @@
      []
     ],
     "should-trusted-type-policy-creation-be-blocked-by-csp-002-expected.txt": [
-     "0d1fa5fa7bd1f597228c51704f98fd1819737642",
+     "894439787e788546b8e446810fee8491a9635da8",
+     []
+    ],
+    "should-trusted-type-policy-creation-be-blocked-by-csp-005.html.headers": [
+     "7e28a4dbc8e80bb47e340df23428a51fabd09aaa",
      []
     ],
     "support": {
@@ -424222,12 +424360,72 @@
       "c17d57d19ae4ef9d3e584d10fc39065823cb2f73",
       []
      ],
-     "should-trusted-type-policy-creation-be-blocked-by-csp-004-worker.js": [
-      "c5c512d98a2748bfbe12880b7003a6845806c6a2",
+     "should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-allow-duplicates-tt-policy-name.js": [
+      "59d9711f1225a9ebbe91afe8f0cc12888cf183bc",
       []
      ],
-     "should-trusted-type-policy-creation-be-blocked-by-csp-004-worker.js.headers": [
-      "c27bd116d8d25f28a411097e6d53b1dc30f3bdc5",
+     "should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-allow-duplicates-tt-policy-name.js.headers": [
+      "9fa44d4200b670da44b6b98699aeef3a5f5392de",
+      []
+     ],
+     "should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-location.js": [
+      "fc4773f923809e96bf44b8af4e34c92ee7bd0ceb",
+      []
+     ],
+     "should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-location.js.headers": [
+      "7e28a4dbc8e80bb47e340df23428a51fabd09aaa",
+      []
+     ],
+     "should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-multiple-violations.js": [
+      "74d4429c3949863c209492d093d26e103f165153",
+      []
+     ],
+     "should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-multiple-violations.js.headers": [
+      "5edaafc1ff6769a5e1c6a5d7d20e1d79aa44ded9",
+      []
+     ],
+     "should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-none.js": [
+      "8d0ba3da646a5c8e3cfc5031094b240d865125e4",
+      []
+     ],
+     "should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-none.js.headers": [
+      "7e28a4dbc8e80bb47e340df23428a51fabd09aaa",
+      []
+     ],
+     "should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-tt-policy-name-allow-duplicates.js": [
+      "ed3524005eafdc3d8f9fdfcbc4f0b91008eeabba",
+      []
+     ],
+     "should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-tt-policy-name-allow-duplicates.js.headers": [
+      "673b327086267519e887ea7753da73901d757d13",
+      []
+     ],
+     "should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-tt-policy-name-none.js": [
+      "d34bd04eaeb4c9256e67391fe9ac22b6899f6caf",
+      []
+     ],
+     "should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-tt-policy-name-none.js.headers": [
+      "55cf94e10e624d47acfd0283e8fa35529a1e9185",
+      []
+     ],
+     "should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-tt-policy-name.js": [
+      "985ee43e833b7e7ac00072e3eb92f51534b8cec6",
+      []
+     ],
+     "should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-tt-policy-name.js.headers": [
+      "96b79d71c5d506851a6f708b7b67b98e1a9a2813",
+      []
+     ],
+     "should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-wildcard.js": [
+      "004530def32d301d56235c0b45b420d10656feb2",
+      []
+     ],
+     "should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-wildcard.js.headers": [
+      "b93b85d4a352ddfb355a4e708e3b4f50111326f9",
+      []
+     ],
+     "should-trusted-type-policy-creation-be-blocked-by-csp-location.js": [
+      "11390a6318497968e40fd9393fa6a6bf9266a163",
       []
      ],
      "trusted-types-reporting-check-report-create-policy-worker.js": [
@@ -430485,6 +430683,10 @@
      "06fa607635a68aa0e866cbb83b47dc0b0ddf6f86",
      []
     ],
+    "WEB_FEATURES.yml": [
+     "0df5a2c40ef70132caa3ee324837f3d5fd4496f9",
+     []
+    ],
     "helper.js": [
      "d4cec39ffce0f58b76da653c78dc384f22ffa43d",
      []
@@ -487831,6 +488033,13 @@
        {}
       ]
      ],
+     "anchor-position-flip-sibling-index.html": [
+      "5fa17dc057a62d7b07c83c8203c30cab8bb668ff",
+      [
+       null,
+       {}
+      ]
+     ],
      "anchor-position-grid-001.html": [
       "92fb4d275b8988641ed0736969e918703e4d649d",
       [
@@ -487943,6 +488152,13 @@
        {}
       ]
      ],
+     "anchor-position-sibling-index.html": [
+      "ade6b4a0e01469119d451082ff31f4743e2cb245",
+      [
+       null,
+       {}
+      ]
+     ],
      "anchor-position-top-layer-007.html": [
       "944911f70085b9412e2865a90d52889cc6d37cc0",
       [
@@ -494706,7 +494922,7 @@
        ]
       ],
       "content-visibility-auto-text-fragment.html": [
-       "d5184d72a4ebc9192c1d15a4334fe603048e91b8",
+       "923c5af04f438a690a1048b6729fe51abc7a2932",
        [
         null,
         {
@@ -506388,6 +506604,15 @@
        }
       ]
      ],
+     "root-scroll-marker-activation-and-scroll-tracking.html": [
+      "524e5399022a106133491edd9b926550439c7961",
+      [
+       null,
+       {
+        "testdriver": true
+       }
+      ]
+     ],
      "scroll-button-activation-content-hit-test.html": [
       "26702bb65f8f3ee190acfb1b313ca663c7374f7d",
       [
@@ -518720,7 +518945,7 @@
        ]
       ],
       "transition-timing-function-computed.html": [
-       "cb110549d0b072311d6f00e23bb5452170b0f43c",
+       "3bf1e818e7769c8f4f048f53fc1f4a2220d78441",
        [
         null,
         {}
@@ -518734,7 +518959,7 @@
        ]
       ],
       "transition-timing-function-valid.html": [
-       "658ef76bbe84d47864570be49726d36e75b1b555",
+       "5c7214e4041dcec617dc83ebca2c99c6b9bcd302",
        [
         null,
         {}
@@ -631347,14 +631572,14 @@
      },
      "the-hidden-attribute": {
       "beforematch-element-fragment-navigation.html": [
-       "812a55f3187f60bda3ae702142de47168b1d8abd",
+       "9be5f9baa6d0453a50e4e287e007685ffa9a42bf",
        [
         null,
         {}
        ]
       ],
       "beforematch-scroll-to-text-fragment.html": [
-       "dddab4c6ba8621fb27e63835311930f2b53f41df",
+       "618493addeac829e672c4da5844ae03521704562",
        [
         null,
         {
@@ -631377,7 +631602,7 @@
        ]
       ],
       "hidden-until-found-002.html": [
-       "a454e3a0091d1f5913b7656114bc1e7650c14949",
+       "e947274bedb89ad4cb162082f5e42c10e5365dad",
        [
         null,
         {
@@ -631393,7 +631618,7 @@
        ]
       ],
       "hidden-until-found-text-fragment.html": [
-       "05220f7ce11b5c4d59d20904d2c6303c2107f25c",
+       "aa8a989509f5f68c7bf589d4ee2e399f1a7a0f6e",
        [
         null,
         {
@@ -634646,7 +634871,7 @@
      },
      "the-details-element": {
       "auto-expand-details-text-fragment.html": [
-       "321d82c02d0e1b04d2dd3cafb1d47a6776f36db7",
+       "7cc3cc22964a07a3448464bcc9ffaae8086397c8",
        [
         null,
         {
@@ -726518,7 +726743,7 @@
      ]
     ],
     "should-trusted-type-policy-creation-be-blocked-by-csp-001.html": [
-     "6e51bd5ef198a7419539e63b835b4f07becfcea3",
+     "1816a34746fe0f101594f027b68f49646cc4cf2c",
      [
       null,
       {
@@ -726527,7 +726752,7 @@
      ]
     ],
     "should-trusted-type-policy-creation-be-blocked-by-csp-002.html": [
-     "ef1a4bde3782b5dfb5a3e1471f6b9e38217d6f1f",
+     "fc1906a7c99fdb82e9c2e6a7870471cf6ad3f6f0",
      [
       null,
       {
@@ -726536,7 +726761,7 @@
      ]
     ],
     "should-trusted-type-policy-creation-be-blocked-by-csp-003.html": [
-     "55f1479e3db0e88684a171cfee5a948e5cae8fe9",
+     "052fbb8cbfb94023cadb003e858647de0d2b139e",
      [
       null,
       {
@@ -726545,7 +726770,16 @@
      ]
     ],
     "should-trusted-type-policy-creation-be-blocked-by-csp-004-worker.html": [
-     "7e928d2d7192e694d632ce5907c430a673653bf8",
+     "5726db94bf374996de8049af62036b69150b7608",
+     [
+      null,
+      {
+       "timeout": "long"
+      }
+     ]
+    ],
+    "should-trusted-type-policy-creation-be-blocked-by-csp-005.html": [
+     "8d10bcb30132755e9301913b0eebeee48c754f01",
      [
       null,
       {
diff --git a/third_party/blink/web_tests/external/wpt/ai/language_detection/availability-detached-crash.tentative.https.html b/third_party/blink/web_tests/external/wpt/ai/language_detection/availability-detached-crash.tentative.https.html
index 8dd685d..5f76d59e 100644
--- a/third_party/blink/web_tests/external/wpt/ai/language_detection/availability-detached-crash.tentative.https.html
+++ b/third_party/blink/web_tests/external/wpt/ai/language_detection/availability-detached-crash.tentative.https.html
@@ -5,7 +5,7 @@
 <body>
 <iframe src="about:blank"></iframe>
 <script>
-const factory = frames[0].ai.languageDetector;
+const factory = frames[0].LanguageDetector;
 const detector = factory.create();
 document.getElementsByTagName("iframe")[0].remove();
 detector.availability();
diff --git a/third_party/blink/web_tests/external/wpt/ai/language_detection/detector.https.tentative.any.js b/third_party/blink/web_tests/external/wpt/ai/language_detection/detector.https.tentative.any.js
index f28b3eb..d54d011 100644
--- a/third_party/blink/web_tests/external/wpt/ai/language_detection/detector.https.tentative.any.js
+++ b/third_party/blink/web_tests/external/wpt/ai/language_detection/detector.https.tentative.any.js
@@ -6,13 +6,13 @@
 
 promise_test(async t => {
   // Language detection is available after call to `create()`.
-  await ai.languageDetector.create();
-  const availability = await ai.languageDetector.availability();
+  await LanguageDetector.create();
+  const availability = await LanguageDetector.availability();
   assert_equals(availability, 'available');
 }, 'Simple LanguageDetector.availability() call');
 
 promise_test(async t => {
-  const detector = await ai.languageDetector.create();
+  const detector = await LanguageDetector.create();
   const results = await detector.detect('this string is in English');
   // "en" should be highest confidence.
   assert_equals(results[0].detectedLanguage, 'en');
@@ -26,22 +26,22 @@
   const controller = new AbortController();
   controller.abort();
 
-  const createPromise = ai.languageDetector.create({signal: controller.signal});
+  const createPromise = LanguageDetector.create({signal: controller.signal});
 
   await promise_rejects_dom(t, 'AbortError', createPromise);
-}, 'AILanguageDetectorFactory.create() call with an aborted signal.');
+}, 'LanguageDetector.create() call with an aborted signal.');
 
 promise_test(async t => {
   await testAbortPromise(t, signal => {
-    return ai.languageDetector.create({signal});
+    return LanguageDetector.create({signal});
   });
-}, 'Aborting AILanguageDetectorFactory.create().');
+}, 'Aborting LanguageDetector.create().');
 
 promise_test(async t => {
   const controller = new AbortController();
   controller.abort();
 
-  const detector = await ai.languageDetector.create();
+  const detector = await LanguageDetector.create();
   const detectPromise =
       detector.detect('this string is in English', {signal: controller.signal});
 
@@ -49,14 +49,14 @@
 }, 'LanguageDetector.detect() call with an aborted signal.');
 
 promise_test(async t => {
-  const detector = await ai.languageDetector.create();
+  const detector = await LanguageDetector.create();
   await testAbortPromise(t, signal => {
     return detector.detect('this string is in English', {signal});
   });
 }, 'Aborting LanguageDetector.detect().');
 
 promise_test(async t => {
-  const detector = await ai.languageDetector.create();
+  const detector = await LanguageDetector.create();
 
   const text = 'this string is in English';
   const inputUsage = await detector.measureInputUsage(text);
@@ -77,7 +77,7 @@
   const controller = new AbortController();
   controller.abort();
 
-  const detector = await ai.languageDetector.create();
+  const detector = await LanguageDetector.create();
   const measureInputUsagePromise =
       detector.measureInputUsage('hello', {signal: controller.signal});
 
@@ -85,7 +85,7 @@
 }, 'Translator.measureInputUsage() call with an aborted signal.');
 
 promise_test(async t => {
-  const detector = await ai.languageDetector.create();
+  const detector = await LanguageDetector.create();
   await testAbortPromise(t, signal => {
     return detector.measureInputUsage('hello', {signal});
   });
diff --git a/third_party/blink/web_tests/external/wpt/container-timing/tentative/containertiming-text-itself.html b/third_party/blink/web_tests/external/wpt/container-timing/tentative/containertiming-text-itself.html
new file mode 100644
index 0000000..bead4d17
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/container-timing/tentative/containertiming-text-itself.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>Container Timing: observe a paragraph with containertiming attribute</title>
+<body>
+<style>
+body {
+  margin: 0;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/container-timing/resources/container-timing-helpers.js"></script>
+<script src="/element-timing/resources/element-timing-helpers.js"></script>
+<script>
+  let beforeRender;
+  async_test(function (t) {
+    assert_implements(window.PerformanceContainerTiming, "PerformanceContainerTiming is not implemented");
+    const observer = new PerformanceObserver(
+      t.step_func_done(function(entryList) {
+        assert_equals(entryList.getEntries().length, 1);
+        const entry = entryList.getEntries()[0];
+        checkContainerEntry(entry, 'p_ct', 'my_id', beforeRender);
+      })
+    );
+    observer.observe({entryTypes: ['container']});
+    // Add the text during onload to be sure that the observer is registered
+    // in time.
+    window.onload = () => {
+      const p = document.createElement('p');
+      p.setAttribute('containertiming', 'p_ct');
+      p.id = 'my_id';
+      p.innerText = "This is a text";
+      document.body.appendChild(p);
+      beforeRender = performance.now();
+    };
+  }, 'Element with containertiming attribute is observable and reports the text paint.');
+</script>
+
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/container-timing/tentative/containertiming-with-child-text.html b/third_party/blink/web_tests/external/wpt/container-timing/tentative/containertiming-with-child-text.html
new file mode 100644
index 0000000..1efc189c
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/container-timing/tentative/containertiming-with-child-text.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>Container Timing: observe with a node with containertiming and a text child</title>
+<body>
+<style>
+body {
+  margin: 0;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/container-timing/resources/container-timing-helpers.js"></script>
+<script src="/element-timing/resources/element-timing-helpers.js"></script>
+<script>
+  let beforeRender;
+  async_test(function (t) {
+    assert_implements(window.PerformanceContainerTiming, "PerformanceContainerTiming is not implemented");
+    const observer = new PerformanceObserver(
+      t.step_func_done(function(entryList) {
+        assert_equals(entryList.getEntries().length, 1);
+        const entry = entryList.getEntries()[0];
+        checkContainerEntry(entry, 'div_ct', 'p_id', beforeRender)
+      })
+    );
+    observer.observe({entryTypes: ['container']});
+    // Add the text during onload to be sure that the observer is registered
+    // in time.
+    window.onload = () => {
+      // Add a div that is the container timing root
+      const div = document.createElement('div');
+      div.setAttribute('containertiming', 'div_ct');
+      document.body.appendChild(div);
+
+      const p = document.createElement('p');
+      p.id = 'p_id';
+      p.innerText = "This is a text";
+      div.appendChild(p);
+      beforeRender = performance.now();
+    };
+  }, 'Paint of the text child of container timing is reported.');
+</script>
+
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-contain/content-visibility/content-visibility-auto-text-fragment.html b/third_party/blink/web_tests/external/wpt/css/css-contain/content-visibility/content-visibility-auto-text-fragment.html
index d5184d7..923c5af 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-contain/content-visibility/content-visibility-auto-text-fragment.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-contain/content-visibility/content-visibility-auto-text-fragment.html
@@ -3,7 +3,7 @@
 <meta charset="utf8">
 <title>Content Visibility: navigating to a text fragment.</title>
 <link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
-<link rel="help" href="https://github.com/WICG/display-locking">
+<link rel="help" href="https://www.w3.org/TR/css-contain-2/#content-visibility">
 <meta name="timeout" content="long">
 <meta name="assert" content="content-visibility: auto subtrees are 'searchable' by text fragment links">
 
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-flex-spanning-items-001-ref.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-flex-spanning-items-001-ref.html
new file mode 100644
index 0000000..ec65437e
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-flex-spanning-items-001-ref.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Grid Layout Reference Case</title>
+<link rel="author" title="Emily McDonough" href="mailto:emcdonough@mozilla.com"/>
+<style>
+#holder {
+    height: 50px;
+    width: 30px;
+    border: 10px solid fuchsia;
+}
+#item {
+    width: 300px;
+    height: 50px;
+    background: aqua;
+}
+</style>
+<div id="holder">
+  <div id="item"></div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-flex-spanning-items-001.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-flex-spanning-items-001.html
new file mode 100644
index 0000000..f9358b6
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-flex-spanning-items-001.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Grid Layout Test: Intrinsic sizing of grid with spanning flex items in a min-content container</title>
+<link rel="match" href="grid-flex-spanning-items-001-ref.html"/>
+<link rel="help" href="https://drafts.csswg.org/css-grid-2/#intrinsic-sizes"/>
+<link rel="author" title="Emily McDonough" href="mailto:emcdonough@mozilla.com"/>
+<style>
+#mygrid {
+    display: grid;
+    grid-template-columns: 1fr 30px;
+    border: 10px solid fuchsia;
+    width: min-content;
+}
+#item {
+    grid-column: 1 / span 2;
+}
+#filler {
+    width: 300px;
+    height: 50px;
+    background: aqua;
+}
+</style>
+<div id="mygrid">
+  <div id="item">
+    <div id="filler"></div>
+  </div>
+</div>
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 023eb5f..d60ed3e 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
@@ -2,7 +2,6 @@
 - name: image-orientation
   files:
   - image-orientation-*
-features:
 - name: gradient-interpolation
   files:
   - "gradient-interpolation-*"
diff --git a/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/animations/clip-path-animation-geometry-box-delay.html b/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/animations/clip-path-animation-geometry-box-delay.html
new file mode 100644
index 0000000..a6040d44
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/animations/clip-path-animation-geometry-box-delay.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<link rel="help" href="https://drafts.csswg.org/css-shapes-1/#basic-shape-interpolation">
+<link rel="match" href="clip-path-animation-non-shape-delay-ref.html">
+<style>
+  .container {
+    width: 100px;
+    height: 100px;
+    background-color: green;
+    animation: clippath 1s 10s;
+    animation-fill-mode: none;
+    clip-path: border-box;
+    position: absolute;
+    left: 10px;
+    top: 10px;
+  }
+
+  .child {
+    width: 10px;
+    height: 10px;
+    background-color: blue;
+    left: 150px;
+    position: absolute;
+  }
+
+  @keyframes clippath {
+    0% {
+      clip-path: circle(50% at 50% 50%);
+    }
+
+    100% {
+      clip-path: circle(35% at 35% 35%);
+    }
+  }
+</style>
+<script src="/common/reftest-wait.js"></script>
+<script src="../../../../web-animations/resources/timing-utils.js"></script>
+
+<body>
+  This test passes if the blue child is occluded by the clip-path during the animation delay.
+  <div class="container">
+    <div class="child"></div>
+  </div>
+
+  <script>
+    document.getAnimations()[0].ready.then(() => requestAnimationFrame(takeScreenshot));
+  </script>
+</body>
+
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/animations/clip-path-animation-non-shape-delay-ref.html b/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/animations/clip-path-animation-non-shape-delay-ref.html
new file mode 100644
index 0000000..1bd7107cb
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/animations/clip-path-animation-non-shape-delay-ref.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<style>
+.container {
+  width: 100px;
+  height: 100px;
+  background-color: green;
+  position: absolute;
+  left: 10px;
+  top: 10px;
+}
+</style>
+<body>
+  This test passes if the blue child is occluded by the clip-path during the animation delay.
+  <div class="container">
+  </div>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/animations/clip-path-animation-reference-delay.html b/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/animations/clip-path-animation-reference-delay.html
new file mode 100644
index 0000000..4afecbbf
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/animations/clip-path-animation-reference-delay.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<link rel="help" href="https://drafts.csswg.org/css-shapes-1/#basic-shape-interpolation">
+<link rel="match" href="clip-path-animation-non-shape-delay-ref.html">
+<style>
+  .container {
+    width: 100px;
+    height: 100px;
+    background-color: green;
+    animation: clippath 1s 10s;
+    animation-fill-mode: none;
+    clip-path: url(#path);
+    position: absolute;
+    left: 10px;
+    top: 10px;
+  }
+
+  .child {
+    width: 10px;
+    height: 10px;
+    background-color: blue;
+    left: 150px;
+    position: absolute;
+  }
+
+  @keyframes clippath {
+    0% {
+      clip-path: circle(50% at 50% 50%);
+    }
+
+    100% {
+      clip-path: circle(35% at 35% 35%);
+    }
+  }
+</style>
+<script src="/common/reftest-wait.js"></script>
+<script src="../../../../web-animations/resources/timing-utils.js"></script>
+
+<body>
+  This test passes if the blue child is occluded by the clip-path during the animation delay.
+  <svg style="position:absolute; left: 0">
+    <defs>
+      <clipPath id="path">
+        <rect x="0" y="0" width="100" height="100"/>
+        <!-- Adding an empty text node to force mask-based clipping. -->
+        <text></text>
+      </clipPath>
+    </defs>
+  </svg>
+  <div class="container">
+    <div class="child"></div>
+  </div>
+
+  <script>
+    document.getAnimations()[0].ready.then(() => requestAnimationFrame(takeScreenshot));
+  </script>
+</body>
+
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-pseudo/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/css/css-pseudo/WEB_FEATURES.yml
index ac89c2b..c197d34 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-pseudo/WEB_FEATURES.yml
+++ b/third_party/blink/web_tests/external/wpt/css/css-pseudo/WEB_FEATURES.yml
@@ -2,16 +2,13 @@
 - name: target-text
   files:
   - target-text-*
-features:
 - name: spelling-grammar-error
   files:
   - spelling-error-*
   - grammar-spelling-errors-*
-features:
 - name: marker
   files:
   - marker-*
-features:
 - name: file-selector-button
   files:
   - file-selector-button-*
diff --git a/third_party/blink/web_tests/external/wpt/css/css-view-transitions/navigation/resources/at-rule-types-shared-elements.html b/third_party/blink/web_tests/external/wpt/css/css-view-transitions/navigation/resources/at-rule-types-shared-elements.html
new file mode 100644
index 0000000..c9cff7f
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-view-transitions/navigation/resources/at-rule-types-shared-elements.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<!DOCTYPE html>
+<html class="reftest-wait">
+<title>View Transitions: @view-transition opt in with types and elements</title>
+<link rel="help" href="https://drafts.csswg.org/css-view-transitions-2/">
+<script src="/common/reftest-wait.js"></script>
+
+<link rel=expect href="#target">
+
+<style>
+@view-transition {
+  navigation:auto;
+  types:mode;
+}
+
+:root { view-transition-name: none }
+html:active-view-transition-type(mode) {
+  .target {
+    view-transition-name: target;
+  }
+}
+
+.target {
+  width: 100px;
+  height: 100px;
+  position: relative;
+  background: green;
+  left: 200px;
+}
+
+::view-transition-group(target) {
+  animation-play-state: paused;
+}
+
+::view-transition-old(target) {
+  animation: unset;
+  opacity: 1;
+}
+::view-transition-new(target) {
+  animation: unset;
+  opacity: 0;
+}
+</style>
+</head>
+
+<div id=target class=target></div>
+
+<script>
+  onload = takeScreenshot;
+</script>
+
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-view-transitions/navigation/with-types/at-rule-types-shared-elements-ref.html b/third_party/blink/web_tests/external/wpt/css/css-view-transitions/navigation/with-types/at-rule-types-shared-elements-ref.html
new file mode 100644
index 0000000..2fc574c0
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-view-transitions/navigation/with-types/at-rule-types-shared-elements-ref.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<title>View Transitions: @view-transition opt in with types and elements (ref)</title>
+<link rel="help" href="https://drafts.csswg.org/css-view-transitions-2/">
+
+<style>
+.target {
+  width: 100px;
+  height: 100px;
+  position: relative;
+  background: blue;
+}
+</style>
+
+<div class=target></div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-view-transitions/navigation/with-types/at-rule-types-shared-elements.html b/third_party/blink/web_tests/external/wpt/css/css-view-transitions/navigation/with-types/at-rule-types-shared-elements.html
new file mode 100644
index 0000000..50dc5ba
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-view-transitions/navigation/with-types/at-rule-types-shared-elements.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<title>View Transitions: @view-transition opt in with types and elements</title>
+<link rel="help" href="https://drafts.csswg.org/css-view-transitions-2/">
+<link rel="match" href="at-rule-types-shared-elements-ref.html">
+<script src="/common/reftest-wait.js"></script>
+
+<style>
+@view-transition {
+  navigation:auto;
+  types:mode;
+}
+
+:root { view-transition-name: none }
+html:active-view-transition-type(mode) {
+  .target {
+    view-transition-name: target;
+  }
+}
+
+.target {
+  width: 100px;
+  height: 100px;
+  position: relative;
+  background: blue;
+}
+</style>
+
+<div class=target></div>
+<script>
+  function runTest() {
+    const url = "../resources/at-rule-types-shared-elements.html";
+    window.location.replace(new URL(url, window.location));
+  }
+  onload = () => requestAnimationFrame(() => requestAnimationFrame(runTest));
+</script>
+
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-view-transitions/pseudo-with-classes-mismatch-ident.html b/third_party/blink/web_tests/external/wpt/css/css-view-transitions/pseudo-with-classes-mismatch-ident.html
index 107f388..493127b8 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-view-transitions/pseudo-with-classes-mismatch-ident.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-view-transitions/pseudo-with-classes-mismatch-ident.html
@@ -20,7 +20,10 @@
   view-transition-class: cls;
 }
 
-::view-transition-group(*) {
+::view-transition-group(*),
+::view-transition-image-pair(*),
+::view-transition-old(*),
+::view-transition-new(*) {
   animation-play-state: paused;
 }
 
diff --git a/third_party/blink/web_tests/external/wpt/css/css-view-transitions/pseudo-with-classes-mismatch-partial.html b/third_party/blink/web_tests/external/wpt/css/css-view-transitions/pseudo-with-classes-mismatch-partial.html
index ddb9b253..d4a6e0a 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-view-transitions/pseudo-with-classes-mismatch-partial.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-view-transitions/pseudo-with-classes-mismatch-partial.html
@@ -20,7 +20,10 @@
   view-transition-class: cls;
 }
 
-::view-transition-group(*) {
+::view-transition-group(*),
+::view-transition-image-pair(*),
+::view-transition-old(*),
+::view-transition-new(*) {
   animation-play-state: paused;
 }
 
diff --git a/third_party/blink/web_tests/external/wpt/css/css-view-transitions/pseudo-with-classes-mismatch-wildcard.html b/third_party/blink/web_tests/external/wpt/css/css-view-transitions/pseudo-with-classes-mismatch-wildcard.html
index 1e27dc2..bdf5116 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-view-transitions/pseudo-with-classes-mismatch-wildcard.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-view-transitions/pseudo-with-classes-mismatch-wildcard.html
@@ -20,7 +20,10 @@
   view-transition-class: cls;
 }
 
-::view-transition-group(*) {
+::view-transition-group(*),
+::view-transition-image-pair(*),
+::view-transition-old(*),
+::view-transition-new(*) {
   animation-play-state: paused;
 }
 
diff --git a/third_party/blink/web_tests/external/wpt/css/css-view-transitions/pseudo-with-classes-new-with-class-old-without.html b/third_party/blink/web_tests/external/wpt/css/css-view-transitions/pseudo-with-classes-new-with-class-old-without.html
index 225ed1c..5adb88f 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-view-transitions/pseudo-with-classes-new-with-class-old-without.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-view-transitions/pseudo-with-classes-new-with-class-old-without.html
@@ -23,7 +23,10 @@
   view-transition-class: cls;
 }
 
-::view-transition-group(*) {
+::view-transition-group(*),
+::view-transition-image-pair(*),
+::view-transition-old(*),
+::view-transition-new(*) {
   animation-play-state: paused;
 }
 
diff --git a/third_party/blink/web_tests/external/wpt/css/css-view-transitions/pseudo-with-classes-old-with-class-new-without.html b/third_party/blink/web_tests/external/wpt/css/css-view-transitions/pseudo-with-classes-old-with-class-new-without.html
index f9ef100..17d03f8 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-view-transitions/pseudo-with-classes-old-with-class-new-without.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-view-transitions/pseudo-with-classes-old-with-class-new-without.html
@@ -23,7 +23,10 @@
   view-transition-class: cls;
 }
 
-::view-transition-group(*) {
+::view-transition-group(*),
+::view-transition-image-pair(*),
+::view-transition-old(*),
+::view-transition-new(*) {
   animation-play-state: paused;
 }
 
diff --git a/third_party/blink/web_tests/external/wpt/css/mediaqueries/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/css/mediaqueries/WEB_FEATURES.yml
index cc952ac..b090111b 100644
--- a/third_party/blink/web_tests/external/wpt/css/mediaqueries/WEB_FEATURES.yml
+++ b/third_party/blink/web_tests/external/wpt/css/mediaqueries/WEB_FEATURES.yml
@@ -3,27 +3,21 @@
   files:
   - prefers-color-scheme.html
   - prefers-color-scheme-*
-features:
 - name: forced-colors
   files:
   - forced-colors.html
-features:
 - name: prefers-contrast
   files:
   - prefers-contrast.html
-features:
 - name: prefers-reduced-transparency
   files:
   - prefers-reduced-transparency.html
-features:
 - name: overflow
   files:
   - overflow-media-features.html
-features:
 - name: dynamic-range
   files:
   - dynamic-range.html
-features:
 - name: display-mode
   files:
   - display-mode.html
diff --git a/third_party/blink/web_tests/external/wpt/device-bound-session-credentials/fetch-no-credentials.https.html b/third_party/blink/web_tests/external/wpt/device-bound-session-credentials/fetch-no-credentials.https.html
new file mode 100644
index 0000000..4cac685
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/device-bound-session-credentials/fetch-no-credentials.https.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>DBSC does not refresh cross-site fetch without credentials</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="helper.js" type="module"></script>
+
+<script type="module">
+  import { documentHasCookie, expireCookie, waitForCookie, addCookieAndSessionCleanup, setupShardedServerState, configureServer } from "./helper.js";
+
+  promise_test(async t => {
+    const testId = await setupShardedServerState();
+    const expectedCookieAndValue = "auth_cookie=abcdef0123";
+    const expectedCookieAndAttributes = `${expectedCookieAndValue};Domain=${location.hostname};Path=/device-bound-session-credentials`;
+    addCookieAndSessionCleanup(t);
+
+    // Prompt starting a session, and wait until registration completes.
+    const loginResponse = await fetch('login.py');
+    assert_equals(loginResponse.status, 200);
+    await waitForCookie(expectedCookieAndValue, /*expectCookie=*/true);
+
+    // Expire the cookies
+    expireCookie(expectedCookieAndAttributes);
+
+    // Setup for receiving messages
+    let messageCallbacks = [];
+    function messageListener(event) {
+      if (messageCallbacks.length > 0) {
+          messageCallbacks[0](event.data);
+          messageCallbacks.shift();
+      }
+    };
+    window.addEventListener("message", messageListener);
+    t.add_cleanup(() => {
+      window.removeEventListener("message", messageListener);
+    });
+
+    function getMessage() {
+        return new Promise((resolve, reject) => {
+            messageCallbacks.push(resolve);
+        });
+    }
+
+    // Create a cross-site iframe that's going to try to fetch without credentials
+    let iframe = document.createElement('iframe');
+    iframe.src = `${get_host_info().HTTPS_NOTSAMESITE_ORIGIN}/device-bound-session-credentials/fetch-verify-authenticated.https.html`;
+    document.body.appendChild(iframe);
+
+    let statusCode = await getMessage();
+    assert_equals(statusCode, 401);
+
+    // We should not have refreshed.
+    assert_false(documentHasCookie(expectedCookieAndValue));
+  }, "A cross-site fetch without credentials should not refresh");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/device-bound-session-credentials/fetch-verify-authenticated.https.html b/third_party/blink/web_tests/external/wpt/device-bound-session-credentials/fetch-verify-authenticated.https.html
new file mode 100644
index 0000000..d9e3df9f
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/device-bound-session-credentials/fetch-verify-authenticated.https.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<script src="/common/get-host-info.sub.js"></script>
+<body>
+  <script>
+    async function onload() {
+      let base_origin = get_host_info().ORIGIN;
+      let response = await fetch(`${base_origin}/device-bound-session-credentials/verify_authenticated.py`, {credentials: "omit"});
+      window.parent.postMessage(response.status, base_origin);
+    }
+    onload();
+  </script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/dom/nodes/moveBefore/tentative/focus-preserve.html b/third_party/blink/web_tests/external/wpt/dom/nodes/moveBefore/focus-preserve.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/dom/nodes/moveBefore/tentative/focus-preserve.html
rename to third_party/blink/web_tests/external/wpt/dom/nodes/moveBefore/focus-preserve.html
diff --git a/third_party/blink/web_tests/external/wpt/html/editing/the-hidden-attribute/beforematch-element-fragment-navigation.html b/third_party/blink/web_tests/external/wpt/html/editing/the-hidden-attribute/beforematch-element-fragment-navigation.html
index 812a55f3..9be5f9b 100644
--- a/third_party/blink/web_tests/external/wpt/html/editing/the-hidden-attribute/beforematch-element-fragment-navigation.html
+++ b/third_party/blink/web_tests/external/wpt/html/editing/the-hidden-attribute/beforematch-element-fragment-navigation.html
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <meta charset="utf-8">
 <link rel="author" title="Joey Arhar" href="mailto:jarhar@chromium.org">
-<link rel="help" href="https://github.com/WICG/display-locking">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/interaction.html#the-hidden-attribute:event-beforematch">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 
diff --git a/third_party/blink/web_tests/external/wpt/html/editing/the-hidden-attribute/beforematch-scroll-to-text-fragment.html b/third_party/blink/web_tests/external/wpt/html/editing/the-hidden-attribute/beforematch-scroll-to-text-fragment.html
index dddab4c..618493add 100644
--- a/third_party/blink/web_tests/external/wpt/html/editing/the-hidden-attribute/beforematch-scroll-to-text-fragment.html
+++ b/third_party/blink/web_tests/external/wpt/html/editing/the-hidden-attribute/beforematch-scroll-to-text-fragment.html
@@ -2,7 +2,7 @@
 <meta charset="utf-8">
 <title>beforematch fired on ScrollToTextFragment</title>
 <link rel="author" title="Joey Arhar" href="mailto:jarhar@chromium.org">
-<link rel="help" href="https://github.com/WICG/display-locking">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/interaction.html#the-hidden-attribute:event-beforematch">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/resources/testdriver.js"></script>
diff --git a/third_party/blink/web_tests/external/wpt/html/editing/the-hidden-attribute/hidden-until-found-001.html b/third_party/blink/web_tests/external/wpt/html/editing/the-hidden-attribute/hidden-until-found-001.html
index 3c87b98..0fa46f5 100644
--- a/third_party/blink/web_tests/external/wpt/html/editing/the-hidden-attribute/hidden-until-found-001.html
+++ b/third_party/blink/web_tests/external/wpt/html/editing/the-hidden-attribute/hidden-until-found-001.html
@@ -4,7 +4,7 @@
 <meta charset="utf8">
 <title>content-visibility changes after a delay</title>
 <link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
-<link rel="help" href="https://github.com/WICG/display-locking">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/interaction.html#attr-hidden-until-found">
 <link rel="match" href="./resources/container-ref.html">
 <meta name="assert" content="scrollIntoView has no effect on hidden=until-found">
 <script src="/common/reftest-wait.js"></script>
diff --git a/third_party/blink/web_tests/external/wpt/html/editing/the-hidden-attribute/hidden-until-found-002.html b/third_party/blink/web_tests/external/wpt/html/editing/the-hidden-attribute/hidden-until-found-002.html
index a454e3a..e947274 100644
--- a/third_party/blink/web_tests/external/wpt/html/editing/the-hidden-attribute/hidden-until-found-002.html
+++ b/third_party/blink/web_tests/external/wpt/html/editing/the-hidden-attribute/hidden-until-found-002.html
@@ -2,7 +2,7 @@
 <meta charset="utf8">
 <title>Content Visibility: tab order navigation ignores hidden=until-found subtrees</title>
 <link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
-<link rel="help" href="https://github.com/WICG/display-locking">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/interaction.html#attr-hidden-until-found">
 <meta name="assert" content="tab order navigation ignores hidden=until-found subtrees.">
 
 <script src="/resources/testharness.js"></script>
diff --git a/third_party/blink/web_tests/external/wpt/html/editing/the-hidden-attribute/hidden-until-found-004.html b/third_party/blink/web_tests/external/wpt/html/editing/the-hidden-attribute/hidden-until-found-004.html
index 3ca6feb..2c0c643c3 100644
--- a/third_party/blink/web_tests/external/wpt/html/editing/the-hidden-attribute/hidden-until-found-004.html
+++ b/third_party/blink/web_tests/external/wpt/html/editing/the-hidden-attribute/hidden-until-found-004.html
@@ -3,7 +3,7 @@
 <meta charset="utf8">
 <title>hidden=until-found does not paint</title>
 <link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
-<link rel="help" href="https://github.com/WICG/display-locking">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/interaction.html#attr-hidden-until-found">
 <link rel="match" href="./resources/container-ref.html">
 <meta name="assert" content="content-visibility subtrees are not painted">
 
diff --git a/third_party/blink/web_tests/external/wpt/html/editing/the-hidden-attribute/hidden-until-found-005-ref.html b/third_party/blink/web_tests/external/wpt/html/editing/the-hidden-attribute/hidden-until-found-005-ref.html
index 0ca7784..e128e1e 100644
--- a/third_party/blink/web_tests/external/wpt/html/editing/the-hidden-attribute/hidden-until-found-005-ref.html
+++ b/third_party/blink/web_tests/external/wpt/html/editing/the-hidden-attribute/hidden-until-found-005-ref.html
@@ -3,7 +3,7 @@
 <meta charset="utf8">
 <title>Content Visibility: hidden-matchable and size contained (reference)</title>
 <link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
-<link rel="help" href="https://github.com/WICG/display-locking">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/interaction.html#attr-hidden-until-found">
 
 <style>
 div {
diff --git a/third_party/blink/web_tests/external/wpt/html/editing/the-hidden-attribute/hidden-until-found-005.html b/third_party/blink/web_tests/external/wpt/html/editing/the-hidden-attribute/hidden-until-found-005.html
index fabf636..2c4df95 100644
--- a/third_party/blink/web_tests/external/wpt/html/editing/the-hidden-attribute/hidden-until-found-005.html
+++ b/third_party/blink/web_tests/external/wpt/html/editing/the-hidden-attribute/hidden-until-found-005.html
@@ -3,7 +3,7 @@
 <meta charset="utf8">
 <title>hidden=until-found and size contained</title>
 <link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
-<link rel="help" href="https://github.com/WICG/display-locking">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/interaction.html#attr-hidden-until-found">
 <link rel="match" href="hidden-until-found-005-ref.html">
 <meta name="assert" content="hidden=until-found puts in size containment">
 
diff --git a/third_party/blink/web_tests/external/wpt/html/editing/the-hidden-attribute/hidden-until-found-006-ref.html b/third_party/blink/web_tests/external/wpt/html/editing/the-hidden-attribute/hidden-until-found-006-ref.html
index e14ba82c..d8b9761 100644
--- a/third_party/blink/web_tests/external/wpt/html/editing/the-hidden-attribute/hidden-until-found-006-ref.html
+++ b/third_party/blink/web_tests/external/wpt/html/editing/the-hidden-attribute/hidden-until-found-006-ref.html
@@ -3,7 +3,7 @@
 <meta charset="utf8">
 <title>content-visibility hidden-matchable + scrollIntoView (reference)</title>
 <link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
-<link rel="help" href="https://github.com/WICG/display-locking">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/interaction.html#attr-hidden-until-found">
 
 <style>
 .spacer {
diff --git a/third_party/blink/web_tests/external/wpt/html/editing/the-hidden-attribute/hidden-until-found-007.html b/third_party/blink/web_tests/external/wpt/html/editing/the-hidden-attribute/hidden-until-found-007.html
index 7b11a92..64c39c4 100644
--- a/third_party/blink/web_tests/external/wpt/html/editing/the-hidden-attribute/hidden-until-found-007.html
+++ b/third_party/blink/web_tests/external/wpt/html/editing/the-hidden-attribute/hidden-until-found-007.html
@@ -4,7 +4,7 @@
 <meta charset="utf8">
 <title>hidden=until-found + focus</title>
 <link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
-<link rel="help" href="https://github.com/WICG/display-locking">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/interaction.html#attr-hidden-until-found">
 <link rel="match" href="./resources/spacer-and-container-ref.html">
 <meta name="assert" content="focus does not scroll or focus element under hidden=until-found">
 <script src="/common/reftest-wait.js"></script>
diff --git a/third_party/blink/web_tests/external/wpt/html/editing/the-hidden-attribute/hidden-until-found-text-fragment.html b/third_party/blink/web_tests/external/wpt/html/editing/the-hidden-attribute/hidden-until-found-text-fragment.html
index 05220f7..aa8a9895 100644
--- a/third_party/blink/web_tests/external/wpt/html/editing/the-hidden-attribute/hidden-until-found-text-fragment.html
+++ b/third_party/blink/web_tests/external/wpt/html/editing/the-hidden-attribute/hidden-until-found-text-fragment.html
@@ -2,7 +2,7 @@
 <meta charset="utf-8">
 <title>beforematch fired on ScrollToTextFragment</title>
 <link rel="author" title="Joey Arhar" href="mailto:jarhar@chromium.org">
-<link rel="help" href="https://github.com/WICG/display-locking">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/interaction.html#attr-hidden-until-found">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/resources/testdriver.js"></script>
diff --git a/third_party/blink/web_tests/external/wpt/html/editing/the-hidden-attribute/resources/container-ref.html b/third_party/blink/web_tests/external/wpt/html/editing/the-hidden-attribute/resources/container-ref.html
index e2df5901..53986abd 100644
--- a/third_party/blink/web_tests/external/wpt/html/editing/the-hidden-attribute/resources/container-ref.html
+++ b/third_party/blink/web_tests/external/wpt/html/editing/the-hidden-attribute/resources/container-ref.html
@@ -3,7 +3,7 @@
 <meta charset="utf8">
 <title>CSS Content Visibility: container (reference)</title>
 <link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
-<link rel="help" href="https://github.com/WICG/display-locking">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/interaction.html#attr-hidden-until-found">
 
 <style>
 #container {
diff --git a/third_party/blink/web_tests/external/wpt/html/editing/the-hidden-attribute/resources/spacer-and-container-ref.html b/third_party/blink/web_tests/external/wpt/html/editing/the-hidden-attribute/resources/spacer-and-container-ref.html
index 816a6c9..474da70 100644
--- a/third_party/blink/web_tests/external/wpt/html/editing/the-hidden-attribute/resources/spacer-and-container-ref.html
+++ b/third_party/blink/web_tests/external/wpt/html/editing/the-hidden-attribute/resources/spacer-and-container-ref.html
@@ -3,7 +3,7 @@
 <meta charset="utf8">
 <title>Content Visibility: spacer and a container (reference)</title>
 <link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
-<link rel="help" href="https://github.com/WICG/display-locking">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/interaction.html#attr-hidden-until-found">
 
 <style>
 .spacer {
diff --git a/third_party/blink/web_tests/external/wpt/html/rendering/the-details-element/auto-expand-details-text-fragment.html b/third_party/blink/web_tests/external/wpt/html/rendering/the-details-element/auto-expand-details-text-fragment.html
index 321d82c0..7cc3cc2 100644
--- a/third_party/blink/web_tests/external/wpt/html/rendering/the-details-element/auto-expand-details-text-fragment.html
+++ b/third_party/blink/web_tests/external/wpt/html/rendering/the-details-element/auto-expand-details-text-fragment.html
@@ -2,7 +2,7 @@
 <meta charset="utf-8">
 <title>beforematch fired on ScrollToTextFragment</title>
 <link rel="author" title="Joey Arhar" href="mailto:jarhar@chromium.org">
-<link rel="help" href="https://github.com/WICG/display-locking">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/interactive-elements.html#the-details-element">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/resources/testdriver.js"></script>
diff --git a/third_party/blink/web_tests/external/wpt/navigation-api/commit-behavior/after-transition-push.html b/third_party/blink/web_tests/external/wpt/navigation-api/commit-behavior/after-transition-push.html
deleted file mode 100644
index 34bdcf7..0000000
--- a/third_party/blink/web_tests/external/wpt/navigation-api/commit-behavior/after-transition-push.html
+++ /dev/null
@@ -1,23 +0,0 @@
-<!doctype html>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="../navigation-methods/return-value/resources/helpers.js"></script>
-<script src="resources/after-transition-commit-helpers.js"></script>
-<body>
-<script>
-let tests = [
-  { mode: "rejectBeforeCommit",      description: "{ commit: 'after-transition' } for a push navigation, reject before commit" },
-  { mode: "rejectAfterCommit",       description: "{ commit: 'after-transition' } for a push navigation, reject after commit" },
-  { mode: "successExplicitCommit",   description: "{ commit: 'after-transition' } for a push navigation, explicit commit()" },
-  { mode: "successNoExplicitCommit", description: "{ commit: 'after-transition' } for a push navigation, commit when handler resolves" }
-];
-
-let onload_promise = new Promise(resolve => window.onload = resolve);
-for (let test of tests) {
-  promise_test(async t => {
-    await onload_promise;
-    await testAfterTransitionCommit(t, "push", test.mode);
-  }, test.description);
-}
-</script>
-</body>
diff --git a/third_party/blink/web_tests/external/wpt/navigation-api/commit-behavior/after-transition-redirect-explicit-commit.html b/third_party/blink/web_tests/external/wpt/navigation-api/commit-behavior/after-transition-redirect-explicit-commit.html
deleted file mode 100644
index 7fa3567..0000000
--- a/third_party/blink/web_tests/external/wpt/navigation-api/commit-behavior/after-transition-redirect-explicit-commit.html
+++ /dev/null
@@ -1,39 +0,0 @@
-<!doctype html>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<body>
-<script>
-promise_test(async t => {
-  // Wait for after the load event so that the navigation doesn't get converted
-  // into a replace navigation.
-  await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0));
-
-  let start_length = navigation.entries().length;
-  let start_hash = location.hash;
-  navigation.onnavigate = t.step_func(e => {
-    e.intercept({
-      handler: t.step_func(() => {
-        assert_equals(location.hash, start_hash);
-        assert_equals(new URL(e.destination.url).hash, "#push");
-
-        e.redirect("#redirect1");
-        assert_equals(location.hash, start_hash);
-        assert_equals(new URL(e.destination.url).hash, "#redirect1");
-
-        e.redirect("#redirect2");
-        assert_equals(location.hash, start_hash);
-        assert_equals(new URL(e.destination.url).hash, "#redirect2");
-
-        e.commit();
-        assert_equals(location.hash, "#redirect2");
-        assert_equals(new URL(e.destination.url).hash, "#redirect2");
-      }),
-      commit: "after-transition"
-    });
-  });
-  await navigation.navigate("#push").committed;
-  assert_equals(location.hash, "#redirect2");
-  assert_equals(navigation.entries().length, start_length + 1);
-}, "redirect() then commit()");
-</script>
-</body>
diff --git a/third_party/blink/web_tests/external/wpt/navigation-api/commit-behavior/after-transition-reload.html b/third_party/blink/web_tests/external/wpt/navigation-api/commit-behavior/after-transition-reload.html
deleted file mode 100644
index 203150e..0000000
--- a/third_party/blink/web_tests/external/wpt/navigation-api/commit-behavior/after-transition-reload.html
+++ /dev/null
@@ -1,19 +0,0 @@
-<!doctype html>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="../navigation-methods/return-value/resources/helpers.js"></script>
-<script src="resources/after-transition-commit-helpers.js"></script>
-<body>
-<script>
-let tests = [
-  { mode: "rejectBeforeCommit",      description: "{ commit: 'after-transition' } for a reload navigation, reject before commit" },
-  { mode: "rejectAfterCommit",       description: "{ commit: 'after-transition' } for a reload navigation, reject after commit" },
-  { mode: "successExplicitCommit",   description: "{ commit: 'after-transition' } for a reload navigation, explicit commit()" },
-  { mode: "successNoExplicitCommit", description: "{ commit: 'after-transition' } for a reload navigation, commit when handler resolves" }
-];
-
-for (let test of tests) {
-  promise_test(t => testAfterTransitionCommit(t, "reload", test.mode), test.description);
-}
-</script>
-</body>
diff --git a/third_party/blink/web_tests/external/wpt/navigation-api/commit-behavior/after-transition-replace.html b/third_party/blink/web_tests/external/wpt/navigation-api/commit-behavior/after-transition-replace.html
deleted file mode 100644
index 2fd4873..0000000
--- a/third_party/blink/web_tests/external/wpt/navigation-api/commit-behavior/after-transition-replace.html
+++ /dev/null
@@ -1,19 +0,0 @@
-<!doctype html>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="../navigation-methods/return-value/resources/helpers.js"></script>
-<script src="resources/after-transition-commit-helpers.js"></script>
-<body>
-<script>
-let tests = [
-  { mode: "rejectBeforeCommit",      description: "{ commit: 'after-transition' } for a replace navigation, reject before commit" },
-  { mode: "rejectAfterCommit",       description: "{ commit: 'after-transition' } for a replace navigation, reject after commit" },
-  { mode: "successExplicitCommit",   description: "{ commit: 'after-transition' } for a replace navigation, explicit commit()" },
-  { mode: "successNoExplicitCommit", description: "{ commit: 'after-transition' } for a replace navigation, commit when handler resolves" }
-];
-
-for (let test of tests) {
-  promise_test(t => testAfterTransitionCommit(t, "replace", test.mode), test.description);
-}
-</script>
-</body>
diff --git a/third_party/blink/web_tests/external/wpt/navigation-api/commit-behavior/after-transition-traverse.html b/third_party/blink/web_tests/external/wpt/navigation-api/commit-behavior/after-transition-traverse.html
deleted file mode 100644
index d348a21..0000000
--- a/third_party/blink/web_tests/external/wpt/navigation-api/commit-behavior/after-transition-traverse.html
+++ /dev/null
@@ -1,31 +0,0 @@
-<!doctype html>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="../navigation-methods/return-value/resources/helpers.js"></script>
-<script src="resources/after-transition-commit-helpers.js"></script>
-<body>
-<script>
-let start_index = navigation.currentEntry.index;
-
-let tests = [
-  { mode: "rejectBeforeCommit",      destinationIndex: start_index, description: "{ commit: 'after-transition' } for a traverse navigation, reject before commit" },
-  { mode: "rejectAfterCommit",       destinationIndex: start_index + 1, description: "{ commit: 'after-transition' } for a traverse navigation, reject after commit" },
-  { mode: "successExplicitCommit",   destinationIndex: start_index + 2, description: "{ commit: 'after-transition' } for a traverse navigation, explicit commit()" },
-  { mode: "successNoExplicitCommit", destinationIndex: start_index + 3, description: "{ commit: 'after-transition' } for a traverse navigation, commit when handler resolves" }
-];
-
-// Push a bunch of history entries so each test case can target a unique entry.
-history.pushState("", "", "#1");
-history.pushState("", "", "#2");
-history.pushState("", "", "#3");
-history.pushState("", "", "#4");
-
-let onload_promise = new Promise(resolve => window.onload = resolve);
-for (let test of tests) {
-  promise_test(async t => {
-    await onload_promise;
-    await testAfterTransitionCommit(t, "traverse", test.mode, test.destinationIndex);
-  }, test.description);
-}
-</script>
-</body>
diff --git a/third_party/blink/web_tests/external/wpt/navigation-api/commit-behavior/commit-throws.html b/third_party/blink/web_tests/external/wpt/navigation-api/commit-behavior/commit-throws.html
deleted file mode 100644
index 54abdbf..0000000
--- a/third_party/blink/web_tests/external/wpt/navigation-api/commit-behavior/commit-throws.html
+++ /dev/null
@@ -1,95 +0,0 @@
-<!doctype html>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<body>
-<script>
-promise_test(async t => {
-  navigation.onnavigate = t.step_func(e => {
-    assert_throws_dom("InvalidStateError", () => e.commit());
-  });
-  await navigation.navigate("#").finished;
-}, "commit() before intercept()");
-
-promise_test(async t => {
-  navigation.onnavigate = t.step_func(e => {
-    e.intercept({ handler: t.step_func(() => {
-      assert_throws_dom("InvalidStateError", () => e.commit());
-    }) });
-  });
-  await navigation.navigate("#").finished;
-}, "commit() without commit behavior specified");
-
-promise_test(async t => {
-  navigation.onnavigate = t.step_func(e => {
-    e.intercept({
-      handler: t.step_func(() => {
-        assert_throws_dom("InvalidStateError", () => e.commit());
-      }),
-      commit: "immediate"
-    });
-  });
-  await navigation.navigate("#").finished;
-}, "commit() with { commit: immediate }");
-
-promise_test(async t => {
-  navigation.onnavigate = t.step_func(e => {
-    e.intercept({ commit: "after-transition" });
-    assert_throws_dom("InvalidStateError", () => e.commit());
-  });
-  await navigation.navigate("#").finished;
-}, "commit() during event dispatch");
-
-promise_test(async t => {
-  let navigate_event;
-  navigation.onnavigate = t.step_func(e => {
-    e.intercept({ commit: "after-transition" });
-    navigate_event = e;
-  });
-  await navigation.navigate("#").finished;
-  assert_throws_dom("InvalidStateError", () => navigate_event.commit());
-}, "commit() after finish");
-
-promise_test(async t => {
-  navigation.onnavigate = t.step_func(e => {
-    e.intercept({
-      handler: t.step_func(() => {
-        e.commit();
-        assert_throws_dom("InvalidStateError", () => e.commit());
-      }),
-      commit: "after-transition"
-    });
-  });
-  await navigation.navigate("#").finished;
-}, "commit() twice");
-
-promise_test(async t => {
-  // We need to grab an NavigationDestination to construct the event.
-  navigation.onnavigate = t.step_func(e => {
-    const event = new NavigateEvent("navigate", {
-      destination: e.destination,
-      signal: (new AbortController()).signal
-    });
-
-    assert_throws_dom("SecurityError", () => event.commit());
-  });
-  await navigation.navigate("#").finished;
-}, "commit() on synthetic NavigateEvent");
-
-promise_test(async t => {
-  let i = document.createElement("iframe");
-  i.src = "about:blank";
-  document.body.appendChild(i);
-  i.contentWindow.navigation.onnavigate = t.step_func(e => {
-    e.intercept({
-      handler: t.step_func(() => {
-        let iframe_constructor = i.contentWindow.DOMException;
-        i.remove();
-        assert_throws_dom("InvalidStateError", iframe_constructor, () => e.commit());
-      }),
-      commit: "after-transition"
-    });
-  });
-  i.contentWindow.navigation.navigate("#");
-}, "commit() in detached iframe");
-</script>
-</body>
diff --git a/third_party/blink/web_tests/external/wpt/navigation-api/commit-behavior/redirect-throws.html b/third_party/blink/web_tests/external/wpt/navigation-api/commit-behavior/redirect-throws.html
deleted file mode 100644
index 4bb6f96..0000000
--- a/third_party/blink/web_tests/external/wpt/navigation-api/commit-behavior/redirect-throws.html
+++ /dev/null
@@ -1,141 +0,0 @@
-<!doctype html>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<body>
-<script>
-promise_test(async t => {
-  navigation.onnavigate = t.step_func(e => {
-    assert_throws_dom("InvalidStateError", () => e.redirect("#"));
-  });
-  await navigation.navigate("#").finished;
-}, "redirect() before intercept()");
-
-promise_test(async t => {
-  navigation.onnavigate = t.step_func(e => {
-    e.intercept({ handler: t.step_func(() => {
-      assert_throws_dom("InvalidStateError", () => e.redirect("#"));
-    }) });
-  });
-  await navigation.navigate("#").finished;
-}, "redirect() without commit behavior specified");
-
-promise_test(async t => {
-  navigation.onnavigate = t.step_func(e => {
-    e.intercept({
-      handler: t.step_func(() => {
-        assert_throws_dom("InvalidStateError", () => e.redirect("#"));
-      }),
-      commit: "immediate"
-    });
-  });
-  await navigation.navigate("#").finished;
-}, "redirect() with { commit: immediate }");
-
-promise_test(async t => {
-  navigation.onnavigate = t.step_func(e => {
-    e.intercept({ commit: "after-transition" });
-    assert_throws_dom("InvalidStateError", () => e.redirect("#"));
-  });
-  await navigation.navigate("#").finished;
-}, "redirect() during event dispatch");
-
-promise_test(async t => {
-  let navigate_event;
-  navigation.onnavigate = t.step_func(e => {
-    e.intercept({ commit: "after-transition" });
-    navigate_event = e;
-  });
-  await navigation.navigate("#").finished;
-  assert_throws_dom("InvalidStateError", () => navigate_event.redirect("#"));
-}, "redirect() after finish");
-
-promise_test(async t => {
-  navigation.onnavigate = t.step_func(e => {
-    e.intercept({
-      handler: t.step_func(() => {
-        e.commit();
-        assert_throws_dom("InvalidStateError", () => e.redirect("#"));
-      }),
-      commit: "after-transition"
-    });
-  });
-  await navigation.navigate("#").finished;
-}, "redirect() after commit()");
-
-promise_test(async t => {
-  // We need to grab an NavigationDestination to construct the event.
-  navigation.onnavigate = t.step_func(e => {
-    const event = new NavigateEvent("navigate", {
-      destination: e.destination,
-      signal: (new AbortController()).signal
-    });
-
-    assert_throws_dom("SecurityError", () => event.redirect("#"));
-  });
-  await navigation.navigate("#").finished;
-}, "redirect() on synthetic NavigateEvent");
-
-promise_test(async t => {
-  let i = document.createElement("iframe");
-  i.src = "about:blank";
-  document.body.appendChild(i);
-  i.contentWindow.navigation.onnavigate = t.step_func(e => {
-    e.intercept({
-      handler: t.step_func(() => {
-        let iframe_constructor = i.contentWindow.DOMException;
-        i.remove();
-        assert_throws_dom("InvalidStateError", iframe_constructor, () => e.redirect("#"));
-      }),
-      commit: "after-transition"
-    });
-  });
-  i.contentWindow.navigation.navigate("#");
-}, "redirect() in detached iframe");
-
-promise_test(async t => {
-  navigation.onnavigate = t.step_func(e => {
-    e.intercept({ handler: t.step_func(() => {
-      assert_throws_dom("SyntaxError", () => e.redirect("https://example.com\u0000mozilla.org"));
-    }),
-    commit: "after-transition" });
-  });
-  await navigation.navigate("#").finished;
-}, "redirect() to invalid url");
-
-promise_test(async t => {
-  navigation.onnavigate = t.step_func(e => {
-    e.intercept({ handler: t.step_func(() => {
-      assert_throws_dom("SecurityError", () => e.redirect("https://example.com"));
-    }),
-    commit: "after-transition" });
-  });
-  await navigation.navigate("#").finished;
-}, "redirect() to cross-origin url");
-
-promise_test(async t => {
-  navigation.onnavigate = t.step_func(e => {
-    e.intercept({ handler: t.step_func(() => {
-      assert_throws_dom("InvalidStateError", () => e.redirect("#"));
-    }),
-    commit: "after-transition" });
-  });
-  await navigation.reload().finished;
-}, "redirect() reload");
-
-promise_test(async t => {
-  // Wait for after the load event so that the navigation doesn't get converted
-  // into a replace navigation.
-  await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0));
-
-  await navigation.navigate("#forward").finished;
-
-  navigation.onnavigate = t.step_func(e => {
-    e.intercept({ handler: t.step_func(() => {
-      assert_throws_dom("InvalidStateError", () => e.redirect("#"));
-    }),
-    commit: "after-transition" });
-  });
-  await navigation.back().finished;
-}, "redirect() traverse");
-</script>
-</body>
diff --git a/third_party/blink/web_tests/external/wpt/navigation-api/ordering-and-transition/navigate-commit-after-transition-intercept-with-redirect.html b/third_party/blink/web_tests/external/wpt/navigation-api/ordering-and-transition/navigate-intercept-precommitHandler-redirect.html
similarity index 65%
rename from third_party/blink/web_tests/external/wpt/navigation-api/ordering-and-transition/navigate-commit-after-transition-intercept-with-redirect.html
rename to third_party/blink/web_tests/external/wpt/navigation-api/ordering-and-transition/navigate-intercept-precommitHandler-redirect.html
index 1d7c47b5..90cf813d 100644
--- a/third_party/blink/web_tests/external/wpt/navigation-api/ordering-and-transition/navigate-commit-after-transition-intercept-with-redirect.html
+++ b/third_party/blink/web_tests/external/wpt/navigation-api/ordering-and-transition/navigate-intercept-precommitHandler-redirect.html
@@ -22,19 +22,17 @@
   recorder.setUpNavigationAPIListeners();
 
   navigation.addEventListener("navigate", e => {
-    e.intercept({ commit: "after-transition",
+    e.intercept({ async precommitHandler(controller) {
+                    recorder.record("precommitHandler start");
+                    await new Promise(r => t.step_timeout(r, 0));
+                    recorder.record("precommitHandler async step 1a");
+                    controller.redirect("#2");
+                    recorder.record("precommitHandler async step 1b");
+                  },
                   async handler() {
                     recorder.record("handler start");
                     await new Promise(r => t.step_timeout(r, 0));
-                    recorder.record("handler async step 1a");
-                    e.redirect("#2");
-                    recorder.record("handler async step 1b");
-                    await new Promise(r => t.step_timeout(r, 0));
-                    recorder.record("handler async step 2a");
-                    e.commit();
-                    recorder.record("handler async step 2b");
-                    await new Promise(r => t.step_timeout(r, 0));
-                    recorder.record("handler async step 3");
+                    recorder.record("handler async step 1");
                   }
                 });
   });
@@ -49,18 +47,17 @@
   recorder.assert([
     /* event name, location.hash value, navigation.transition properties */
     ["navigate", "", null],
-    ["handler start", "", { from, navigationType: "push" }],
+    ["precommitHandler start", "", { from, navigationType: "push" }],
     ["promise microtask", "", { from, navigationType: "push" }],
-    ["handler async step 1a", "", { from, navigationType: "push" }],
-    ["handler async step 1b", "", { from, navigationType: "push" }],
-    ["handler async step 2a", "", { from, navigationType: "push" }],
+    ["precommitHandler async step 1a", "", { from, navigationType: "push" }],
+    ["precommitHandler async step 1b", "", { from, navigationType: "push" }],
     ["currententrychange", "#2", { from, navigationType: "push" }],
-    ["handler async step 2b", "#2", { from, navigationType: "push" }],
+    ["handler start", "#2", { from, navigationType: "push" }],
     ["committed fulfilled", "#2", { from, navigationType: "push" }],
-    ["handler async step 3", "#2", { from, navigationType: "push" }],
+    ["handler async step 1", "#2", { from, navigationType: "push" }],
     ["navigatesuccess", "#2", { from, navigationType: "push" }],
     ["finished fulfilled", "#2", null],
     ["transition.finished fulfilled", "#2", null],
   ]);
-}, "event and promise ordering for same-document navigation.navigate() intercepted by intercept() with { commit: 'after-transition' } and a redirect()");
+}, "event and promise ordering for same-document navigation.navigate() intercepted by intercept() with a precommitHandler that redirects");
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/navigation-api/ordering-and-transition/navigate-commit-after-transition-intercept.html b/third_party/blink/web_tests/external/wpt/navigation-api/ordering-and-transition/navigate-intercept-precommitHandler.html
similarity index 75%
rename from third_party/blink/web_tests/external/wpt/navigation-api/ordering-and-transition/navigate-commit-after-transition-intercept.html
rename to third_party/blink/web_tests/external/wpt/navigation-api/ordering-and-transition/navigate-intercept-precommitHandler.html
index 16c8796..27e317c 100644
--- a/third_party/blink/web_tests/external/wpt/navigation-api/ordering-and-transition/navigate-commit-after-transition-intercept.html
+++ b/third_party/blink/web_tests/external/wpt/navigation-api/ordering-and-transition/navigate-intercept-precommitHandler.html
@@ -22,15 +22,15 @@
   recorder.setUpNavigationAPIListeners();
 
   navigation.addEventListener("navigate", e => {
-    e.intercept({ commit: "after-transition",
+    e.intercept({ async precommitHandler() {
+                    recorder.record("precommitHandler start");
+                    await new Promise(r => t.step_timeout(r, 0));
+                    recorder.record("precommitHandler async step");
+                  },
                   async handler() {
                     recorder.record("handler start");
                     await new Promise(r => t.step_timeout(r, 0));
-                    recorder.record("handler async step 1a");
-                    e.commit();
-                    recorder.record("handler async step 1b");
-                    await new Promise(r => t.step_timeout(r, 0));
-                    recorder.record("handler async step 2");
+                    recorder.record("handler async step");
                   }
                 });
   });
@@ -45,16 +45,16 @@
   recorder.assert([
     /* event name, location.hash value, navigation.transition properties */
     ["navigate", "", null],
-    ["handler start", "", { from, navigationType: "push" }],
+    ["precommitHandler start", "", { from, navigationType: "push" }],
     ["promise microtask", "", { from, navigationType: "push" }],
-    ["handler async step 1a", "", { from, navigationType: "push" }],
+    ["precommitHandler async step", "", { from, navigationType: "push" }],
     ["currententrychange", "#1", { from, navigationType: "push" }],
-    ["handler async step 1b", "#1", { from, navigationType: "push" }],
+    ["handler start", "#1", { from, navigationType: "push" }],
     ["committed fulfilled", "#1", { from, navigationType: "push" }],
-    ["handler async step 2", "#1", { from, navigationType: "push" }],
+    ["handler async step", "#1", { from, navigationType: "push" }],
     ["navigatesuccess", "#1", { from, navigationType: "push" }],
     ["finished fulfilled", "#1", null],
     ["transition.finished fulfilled", "#1", null],
   ]);
-}, "event and promise ordering for same-document navigation.navigate() intercepted by intercept() with { commit: 'after-transition' }");
+}, "event and promise ordering for same-document navigation.navigate() intercepted by intercept() with a precommitHandler");
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/navigation-api/commit-behavior/multiple-intercept.html b/third_party/blink/web_tests/external/wpt/navigation-api/precommit-handler/multiple-intercept.html
similarity index 61%
rename from third_party/blink/web_tests/external/wpt/navigation-api/commit-behavior/multiple-intercept.html
rename to third_party/blink/web_tests/external/wpt/navigation-api/precommit-handler/multiple-intercept.html
index 848af6a6..b7fd910 100644
--- a/third_party/blink/web_tests/external/wpt/navigation-api/commit-behavior/multiple-intercept.html
+++ b/third_party/blink/web_tests/external/wpt/navigation-api/precommit-handler/multiple-intercept.html
@@ -26,58 +26,58 @@
 
 promise_test(async t => {
   await testUrlDidNotChangeImmediately(e => {
-    e.intercept({ commit: "after-transition" });
-    e.intercept({ commit: "after-transition" });
+    e.intercept({ precommitHandler: async () => {} });
+    e.intercept({ precommitHandler: async () => {} });
   });
-}, "after-transition + after-transition");
+}, "precommitHandler + precommitHandler");
 
 promise_test(async t => {
   await testUrlDidNotChangeImmediately(e => {
-    e.intercept({ commit: "after-transition" });
+    e.intercept({ precommitHandler: async () => {} });
     e.intercept();
   });
-}, "after-transition + (not provided)");
-
-promise_test(async t => {
-  await testUrlDidChangeImmediately(e => {
-    e.intercept({ commit: "after-transition" });
-    e.intercept({ commit: "immediate" });
-  });
-}, "after-transition + immediate");
+}, "precommitHandler + (not provided)");
 
 promise_test(async t => {
   await testUrlDidNotChangeImmediately(e => {
-    e.intercept({ commit: "immediate" });
-    e.intercept({ commit: "after-transition" });
+    e.intercept({ precommitHandler: async () => {} });
+    e.intercept({ handler: async () => {} });
   });
-}, "immediate + after-transition");
+}, "precommitHandler + handler");
+
+promise_test(async t => {
+  await testUrlDidNotChangeImmediately(e => {
+    e.intercept({ handler: async () => {} });
+    e.intercept({ precommitHandler: async () => {} });
+  });
+}, "handler + precommitHandler");
 
 promise_test(async t => {
   await testUrlDidChangeImmediately(e => {
-    e.intercept({ commit: "immediate" });
+    e.intercept({ handler: async () => {} });
     e.intercept();
   });
-}, "immediate + (not provided)");
+}, "handler + (not provided)");
 
 promise_test(async t => {
   await testUrlDidChangeImmediately(e => {
-    e.intercept({ commit: "immediate" });
-    e.intercept({ commit: "immediate" });
+    e.intercept({ handler: async () => {} });
+    e.intercept({ handler: async () => {} });
   });
-}, "immediate + immediate");
+}, "handler + handler");
 
 promise_test(async t => {
   await testUrlDidNotChangeImmediately(e => {
     e.intercept();
-    e.intercept({ commit: "after-transition" });
+    e.intercept({ precommitHandler: async () => {} });
   });
-}, "(not provided) + after-transition");
+}, "(not provided) + precommitHandler");
 
 promise_test(async t => {
   await testUrlDidChangeImmediately(e => {
     e.intercept();
-    e.intercept({ commit: "immediate" });
+    e.intercept({ handler: async () => {} });
   });
-}, "(not provided) + immediate");
+}, "(not provided) + handler");
 </script>
 </body>
diff --git a/third_party/blink/web_tests/external/wpt/navigation-api/commit-behavior/after-transition-new-navigation-before-commit.html b/third_party/blink/web_tests/external/wpt/navigation-api/precommit-handler/precommitHandler-new-navigation-before-commit.html
similarity index 78%
rename from third_party/blink/web_tests/external/wpt/navigation-api/commit-behavior/after-transition-new-navigation-before-commit.html
rename to third_party/blink/web_tests/external/wpt/navigation-api/precommit-handler/precommitHandler-new-navigation-before-commit.html
index 2d09d40..89e9895 100644
--- a/third_party/blink/web_tests/external/wpt/navigation-api/commit-behavior/after-transition-new-navigation-before-commit.html
+++ b/third_party/blink/web_tests/external/wpt/navigation-api/precommit-handler/precommitHandler-new-navigation-before-commit.html
@@ -5,7 +5,7 @@
 <body>
 <script>
 promise_test(async t => {
-  navigation.addEventListener("navigate", e => e.intercept({ commit: "after-transition" }), { once: "true" });
+  navigation.addEventListener("navigate", e => e.intercept({ precommitHandler: async () => {} }), { once: "true" });
 
   let navigateerror_called = false;
   navigation.onnavigateerror = t.step_func(() => {
@@ -20,6 +20,6 @@
 
   assert_equals(location.hash, "#1");
   assert_true(navigateerror_called);
-}, "Cancel a { commit: 'after-transition' } navigation before commit() by starting a new navigation");
+}, "Cancel a navigation with a precommitHandler before commit by starting a new navigation");
 </script>
 </body>
diff --git a/third_party/blink/web_tests/external/wpt/navigation-api/precommit-handler/precommitHandler-push.html b/third_party/blink/web_tests/external/wpt/navigation-api/precommit-handler/precommitHandler-push.html
new file mode 100644
index 0000000..41856ed6
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/navigation-api/precommit-handler/precommitHandler-push.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../navigation-methods/return-value/resources/helpers.js"></script>
+<script src="resources/precommitHandler-helpers.js"></script>
+<body>
+<script>
+let tests = [
+  { mode: "rejectBeforeCommit", description: "precommitHandler for a push navigation, reject before commit" },
+  { mode: "rejectAfterCommit",  description: "precommitHandler for a push navigation, reject after commit" },
+  { mode: "success",            description: "precommitHandler for a push navigation, success" },
+];
+
+let onload_promise = new Promise(resolve => window.onload = resolve);
+for (let test of tests) {
+  promise_test(async t => {
+    await onload_promise;
+    await testDeferredCommit(t, "push", test.mode);
+  }, test.description);
+}
+</script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/navigation-api/commit-behavior/after-transition-redirect-push.html b/third_party/blink/web_tests/external/wpt/navigation-api/precommit-handler/precommitHandler-redirect-push.html
similarity index 85%
rename from third_party/blink/web_tests/external/wpt/navigation-api/commit-behavior/after-transition-redirect-push.html
rename to third_party/blink/web_tests/external/wpt/navigation-api/precommit-handler/precommitHandler-redirect-push.html
index 79b46ea..de75198 100644
--- a/third_party/blink/web_tests/external/wpt/navigation-api/commit-behavior/after-transition-redirect-push.html
+++ b/third_party/blink/web_tests/external/wpt/navigation-api/precommit-handler/precommitHandler-redirect-push.html
@@ -12,24 +12,23 @@
   let start_hash = location.hash;
   navigation.onnavigate = t.step_func(e => {
     e.intercept({
-      handler: t.step_func(() => {
+      precommitHandler: t.step_func(controller => {
         assert_equals(location.hash, start_hash);
         assert_equals(new URL(e.destination.url).hash, "#push");
 
-        e.redirect("#redirect1");
+        controller.redirect("#redirect1");
         assert_equals(location.hash, start_hash);
         assert_equals(new URL(e.destination.url).hash, "#redirect1");
 
-        e.redirect("#redirect2");
+        controller.redirect("#redirect2");
         assert_equals(location.hash, start_hash);
         assert_equals(new URL(e.destination.url).hash, "#redirect2");
       }),
-      commit: "after-transition"
     });
   });
   await navigation.navigate("#push").committed;
   assert_equals(location.hash, "#redirect2");
   assert_equals(navigation.entries().length, start_length + 1);
-}, "redirect() push");
+}, "precommitHandler redirect() push");
 </script>
 </body>
diff --git a/third_party/blink/web_tests/external/wpt/navigation-api/commit-behavior/after-transition-redirect-replace.html b/third_party/blink/web_tests/external/wpt/navigation-api/precommit-handler/precommitHandler-redirect-replace.html
similarity index 85%
rename from third_party/blink/web_tests/external/wpt/navigation-api/commit-behavior/after-transition-redirect-replace.html
rename to third_party/blink/web_tests/external/wpt/navigation-api/precommit-handler/precommitHandler-redirect-replace.html
index e31f5884..049909fe 100644
--- a/third_party/blink/web_tests/external/wpt/navigation-api/commit-behavior/after-transition-redirect-replace.html
+++ b/third_party/blink/web_tests/external/wpt/navigation-api/precommit-handler/precommitHandler-redirect-replace.html
@@ -12,24 +12,23 @@
   let start_hash = location.hash;
   navigation.onnavigate = t.step_func(e => {
     e.intercept({
-      handler: t.step_func(() => {
+      precommitHandler: t.step_func(controller => {
         assert_equals(location.hash, start_hash);
         assert_equals(new URL(e.destination.url).hash, "#replace");
 
-        e.redirect("#redirect1");
+        controller.redirect("#redirect1");
         assert_equals(location.hash, start_hash);
         assert_equals(new URL(e.destination.url).hash, "#redirect1");
 
-        e.redirect("#redirect2");
+        controller.redirect("#redirect2");
         assert_equals(location.hash, start_hash);
         assert_equals(new URL(e.destination.url).hash, "#redirect2");
       }),
-      commit: "after-transition"
     });
   });
   await navigation.navigate("#replace", { history: "replace" }).committed;
   assert_equals(location.hash, "#redirect2");
   assert_equals(navigation.entries().length, start_length);
-}, "redirect() replace");
+}, "precommitHandler redirect() replace");
 </script>
 </body>
diff --git a/third_party/blink/web_tests/external/wpt/navigation-api/precommit-handler/precommitHandler-redirect-throws.html b/third_party/blink/web_tests/external/wpt/navigation-api/precommit-handler/precommitHandler-redirect-throws.html
new file mode 100644
index 0000000..353daa1a
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/navigation-api/precommit-handler/precommitHandler-redirect-throws.html
@@ -0,0 +1,87 @@
+<!doctype html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<script>
+
+promise_test(async t => {
+  let precommit_controller;
+  navigation.onnavigate = t.step_func(e => {
+    e.intercept({ precommitHandler: async controller => precommit_controller = controller });
+  });
+  await navigation.navigate("#").finished;
+  assert_throws_dom("InvalidStateError", () => precommit_controller.redirect("#"));
+}, "redirect() after finish");
+
+promise_test(async t => {
+  let precommit_controller;
+  navigation.onnavigate = t.step_func(e => {
+    e.intercept({
+      precommitHandler: async controller => precommit_controller = controller,
+      handler: t.step_func(async () => {
+        assert_throws_dom("InvalidStateError", () => precommit_controller.redirect("#"));
+      })
+    });
+  });
+  await navigation.navigate("#").finished;
+}, "redirect() after commit");
+
+promise_test(async t => {
+  let i = document.createElement("iframe");
+  i.src = "about:blank";
+  document.body.appendChild(i);
+  i.contentWindow.navigation.onnavigate = t.step_func(e => {
+    e.intercept({
+      precommitHandler: t.step_func(controller => {
+        let iframe_constructor = i.contentWindow.DOMException;
+        i.remove();
+        assert_throws_dom("InvalidStateError", iframe_constructor, () => controller.redirect("#"));
+      })
+    });
+  });
+  i.contentWindow.navigation.navigate("#");
+}, "redirect() in detached iframe");
+
+promise_test(async t => {
+  navigation.onnavigate = t.step_func(e => {
+    e.intercept({ precommitHandler: t.step_func(controller => {
+      assert_throws_dom("SyntaxError", () => controller.redirect("https://example.com\u0000mozilla.org"));
+    })});
+  });
+  await navigation.navigate("#").finished;
+}, "redirect() to invalid url");
+
+promise_test(async t => {
+  navigation.onnavigate = t.step_func(e => {
+    e.intercept({ precommitHandler: t.step_func(controller => {
+      assert_throws_dom("SecurityError", () => controller.redirect("https://example.com"));
+    })});
+  });
+  await navigation.navigate("#").finished;
+}, "redirect() to cross-origin url");
+
+promise_test(async t => {
+  navigation.onnavigate = t.step_func(e => {
+    e.intercept({ precommitHandler: t.step_func(controller => {
+      assert_throws_dom("InvalidStateError", () => controller.redirect("#"));
+    })});
+  });
+  await navigation.reload().finished;
+}, "redirect() reload");
+
+promise_test(async t => {
+  // Wait for after the load event so that the navigation doesn't get converted
+  // into a replace navigation.
+  await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0));
+
+  await navigation.navigate("#forward").finished;
+
+  navigation.onnavigate = t.step_func(e => {
+    e.intercept({ precommitHandler: t.step_func(controller => {
+      assert_throws_dom("InvalidStateError", () => controller.redirect("#"));
+    })});
+  });
+  await navigation.back().finished;
+}, "redirect() traverse");
+</script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/navigation-api/precommit-handler/precommitHandler-reload.html b/third_party/blink/web_tests/external/wpt/navigation-api/precommit-handler/precommitHandler-reload.html
new file mode 100644
index 0000000..0fd5da3
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/navigation-api/precommit-handler/precommitHandler-reload.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../navigation-methods/return-value/resources/helpers.js"></script>
+<script src="resources/precommitHandler-helpers.js"></script>
+<body>
+<script>
+let tests = [
+  { mode: "rejectBeforeCommit", description: "precommitHandler for a reload navigation, reject before commit" },
+  { mode: "rejectAfterCommit",  description: "precommitHandler for a reload navigation, reject after commit" },
+  { mode: "success",            description: "precommitHandler for a reload navigation, success" },
+];
+
+for (let test of tests) {
+  promise_test(t => testDeferredCommit(t, "reload", test.mode), test.description);
+}
+</script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/navigation-api/precommit-handler/precommitHandler-replace.html b/third_party/blink/web_tests/external/wpt/navigation-api/precommit-handler/precommitHandler-replace.html
new file mode 100644
index 0000000..7c28c99a
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/navigation-api/precommit-handler/precommitHandler-replace.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../navigation-methods/return-value/resources/helpers.js"></script>
+<script src="resources/precommitHandler-helpers.js"></script>
+<body>
+<script>
+let tests = [
+  { mode: "rejectBeforeCommit", description: "precommitHandler for a replace navigation, reject before commit" },
+  { mode: "rejectAfterCommit",  description: "precommitHandler for a replace navigation, reject after commit" },
+  { mode: "success",            description: "precommitHandler for a replace navigation, success" },
+];
+
+for (let test of tests) {
+  promise_test(t => testDeferredCommit(t, "replace", test.mode), test.description);
+}
+</script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/navigation-api/commit-behavior/after-transition-traversal-commit-new-navigation-before-commit.html b/third_party/blink/web_tests/external/wpt/navigation-api/precommit-handler/precommitHandler-traversal-commit-new-navigation-before-commit.html
similarity index 83%
rename from third_party/blink/web_tests/external/wpt/navigation-api/commit-behavior/after-transition-traversal-commit-new-navigation-before-commit.html
rename to third_party/blink/web_tests/external/wpt/navigation-api/precommit-handler/precommitHandler-traversal-commit-new-navigation-before-commit.html
index 9e74e10..226f332 100644
--- a/third_party/blink/web_tests/external/wpt/navigation-api/commit-behavior/after-transition-traversal-commit-new-navigation-before-commit.html
+++ b/third_party/blink/web_tests/external/wpt/navigation-api/precommit-handler/precommitHandler-traversal-commit-new-navigation-before-commit.html
@@ -19,7 +19,7 @@
 
   // Go back and wait for the navigate event to fire. This traversal will be deferred.
   let promises_should_not_commit = assertBothRejectDOM(t, navigation.back(), "AbortError");
-  navigation.addEventListener("navigate", e => e.intercept({ commit: "after-transition", handler: () => new Promise(r => t.step_timeout(r, 1000)) }), { once: "true" });
+  navigation.addEventListener("navigate", e => e.intercept({ precommitHandler: () => new Promise(r => t.step_timeout(r, 1000)) }), { once: "true" });
   await new Promise(resolve => navigation.addEventListener("navigate", resolve, { once: "true" }));
 
   // While the traversal is deferred, start a new navigation and commit immediately.
@@ -31,6 +31,6 @@
 
   assert_equals(location.hash, "#2");
   assert_true(navigateerror_called);
-}, "Cancel a { commit: 'after-transition' } traversal before commit() by starting a new navigation");
+}, "Cancel a precommitHandler traversal before commit by starting a new navigation");
 </script>
 </body>
diff --git a/third_party/blink/web_tests/external/wpt/navigation-api/commit-behavior/after-transition-traversal-window-stop-before-commit.html b/third_party/blink/web_tests/external/wpt/navigation-api/precommit-handler/precommitHandler-traversal-window-stop-before-commit.html
similarity index 90%
rename from third_party/blink/web_tests/external/wpt/navigation-api/commit-behavior/after-transition-traversal-window-stop-before-commit.html
rename to third_party/blink/web_tests/external/wpt/navigation-api/precommit-handler/precommitHandler-traversal-window-stop-before-commit.html
index 282f806..c45f826 100644
--- a/third_party/blink/web_tests/external/wpt/navigation-api/commit-behavior/after-transition-traversal-window-stop-before-commit.html
+++ b/third_party/blink/web_tests/external/wpt/navigation-api/precommit-handler/precommitHandler-traversal-window-stop-before-commit.html
@@ -23,8 +23,7 @@
       stopped_first_traverse = true;
       t.step_timeout(() => window.stop(), 0);
     }
-    e.intercept({ commit: "after-transition",
-                  handler: () => new Promise(r => t.step_timeout(r, 10)) });
+    e.intercept({ precommitHandler: () => new Promise(r => t.step_timeout(r, 10)) });
   });
 
   let navigatesuccess_called = false;
@@ -52,6 +51,6 @@
   assert_equals(navigation.currentEntry.index, start_index + 1);
   assert_equals(location.hash, "#1");
   assert_true(navigatesuccess_called);
-}, " { commit: 'after-transition' } traverse with window.stop() before commit");
+}, " precommitHandler traverse with window.stop() before commit");
 </script>
 </body>
diff --git a/third_party/blink/web_tests/external/wpt/navigation-api/precommit-handler/precommitHandler-traverse.html b/third_party/blink/web_tests/external/wpt/navigation-api/precommit-handler/precommitHandler-traverse.html
new file mode 100644
index 0000000..f152129c
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/navigation-api/precommit-handler/precommitHandler-traverse.html
@@ -0,0 +1,35 @@
+<!doctype html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../navigation-methods/return-value/resources/helpers.js"></script>
+<script src="resources/precommitHandler-helpers.js"></script>
+<body>
+<script>
+let start_index = navigation.currentEntry.index;
+
+// Note that `destinationIndex` is chosen relative to `start_index`, and the
+// tests begin at `start_index` + 4. So:
+// * "rejectBeforeCommit" will try to go back 4 steps and fail.
+// * "rejectAfterCommit" will go back 3 steps, then reject after commit.
+// * "success" will go forward 2 steps
+let tests = [
+  { mode: "rejectBeforeCommit", destinationIndex: start_index, description: "precommitHandler for a traverse navigation, reject before commit" },
+  { mode: "rejectAfterCommit",  destinationIndex: start_index + 1, description: "precommitHandler for a traverse navigation, reject after commit" },
+  { mode: "success",            destinationIndex: start_index + 3, description: "precommitHandler for a traverse navigation, success" }
+];
+
+// Push a bunch of history entries so each test case can target a unique entry.
+history.pushState("", "", "#1");
+history.pushState("", "", "#2");
+history.pushState("", "", "#3");
+history.pushState("", "", "#4");
+
+let onload_promise = new Promise(resolve => window.onload = resolve);
+for (let test of tests) {
+  promise_test(async t => {
+    await onload_promise;
+    await testDeferredCommit(t, "traverse", test.mode, test.destinationIndex);
+  }, test.description);
+}
+</script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/navigation-api/commit-behavior/after-transition-uncancelable.html b/third_party/blink/web_tests/external/wpt/navigation-api/precommit-handler/precommitHandler-uncancelable.html
similarity index 70%
rename from third_party/blink/web_tests/external/wpt/navigation-api/commit-behavior/after-transition-uncancelable.html
rename to third_party/blink/web_tests/external/wpt/navigation-api/precommit-handler/precommitHandler-uncancelable.html
index 45bde9f..af320b4 100644
--- a/third_party/blink/web_tests/external/wpt/navigation-api/commit-behavior/after-transition-uncancelable.html
+++ b/third_party/blink/web_tests/external/wpt/navigation-api/precommit-handler/precommitHandler-uncancelable.html
@@ -13,11 +13,12 @@
 
   i.contentWindow.navigation.onnavigate = t.step_func(e => {
     assert_false(e.cancelable);
-    // intercept() should throw with commit: "after-transition" because e.cancelable is false.
+    // intercept() with a precommitHandler is forbidden because e.cancelable is false.
     let iframe_constructor = i.contentWindow.DOMException;
-    assert_throws_dom("InvalidStateError", iframe_constructor, () => e.intercept({ commit: "after-transition" }));
+    assert_throws_dom("InvalidStateError", iframe_constructor,
+                      () => e.intercept({ precommitHandler: async () => {} }));
   });
   await i.contentWindow.navigation.back().finished;
-}, "{ commit: 'after-transition' } for an uncancelable traverse navigation");
+}, "precommitHandler for an uncancelable traverse navigation");
 </script>
 </body>
diff --git a/third_party/blink/web_tests/external/wpt/navigation-api/commit-behavior/after-transition-window-stop-before-commit.html b/third_party/blink/web_tests/external/wpt/navigation-api/precommit-handler/precommitHandler-window-stop-before-commit.html
similarity index 88%
rename from third_party/blink/web_tests/external/wpt/navigation-api/commit-behavior/after-transition-window-stop-before-commit.html
rename to third_party/blink/web_tests/external/wpt/navigation-api/precommit-handler/precommitHandler-window-stop-before-commit.html
index 0f5e57d5..857a21e 100644
--- a/third_party/blink/web_tests/external/wpt/navigation-api/commit-behavior/after-transition-window-stop-before-commit.html
+++ b/third_party/blink/web_tests/external/wpt/navigation-api/precommit-handler/precommitHandler-window-stop-before-commit.html
@@ -10,7 +10,7 @@
   await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0));
 
   navigation.onnavigate = e => {
-    e.intercept({ commit: "after-transition" });
+    e.intercept({ precommitHandler: async () => {} });
   };
 
   navigation.onnavigatesuccess = t.unreached_func("navigatesuccess must not fire");
@@ -28,6 +28,6 @@
 
   assert_equals(location.hash, "");
   assert_true(navigateerror_called);
-}, " { commit: 'after-transition' } with window.stop() before commit");
+}, " precommitHandler with window.stop() before commit");
 </script>
 </body>
diff --git a/third_party/blink/web_tests/external/wpt/navigation-api/commit-behavior/resources/after-transition-commit-helpers.js b/third_party/blink/web_tests/external/wpt/navigation-api/precommit-handler/resources/precommitHandler-helpers.js
similarity index 73%
rename from third_party/blink/web_tests/external/wpt/navigation-api/commit-behavior/resources/after-transition-commit-helpers.js
rename to third_party/blink/web_tests/external/wpt/navigation-api/precommit-handler/resources/precommitHandler-helpers.js
index 2be6a03ba..13386176 100644
--- a/third_party/blink/web_tests/external/wpt/navigation-api/commit-behavior/resources/after-transition-commit-helpers.js
+++ b/third_party/blink/web_tests/external/wpt/navigation-api/precommit-handler/resources/precommitHandler-helpers.js
@@ -1,4 +1,4 @@
-window.testAfterTransitionCommit = async (t, navigationType, mode, destinationIndex = 0) => {
+window.testDeferredCommit = async (t, navigationType, mode, destinationIndex = 0) => {
   let startHash = location.hash;
   let destinationHash;
   const err = new Error("boo!");
@@ -12,35 +12,25 @@
 
   // mode-specific logic for the navigate event handler
   let navigate_helpers = {
-    rejectBeforeCommit : async (e) => {
-      return Promise.reject(err);
-    },
+    rejectBeforeCommit : async (e) => { return Promise.reject("Should never run") },
     rejectAfterCommit : async (e) => {
-      e.commit();
       assert_equals(location.hash, destinationHash, "hash after commit");
-      assert_equals(navigationType == "traverse", popstate_fired, "popstate fired after commit");
+      assert_equals(false, popstate_fired, "popstate before handler starts");
       await new Promise(resolve => t.step_timeout(resolve, 0));
+      assert_equals(navigationType == "traverse", popstate_fired, "popstate fired after handler async step");
       return Promise.reject(err);
     },
-    successExplicitCommit : async (e) => {
-      e.commit();
+    success : async (e) => {
       assert_equals(location.hash, destinationHash, "hash after commit");
-      assert_equals(navigationType == "traverse", popstate_fired, "popstate fired after commit");
+      assert_equals(false, popstate_fired, "popstate before handler starts");
+      await new Promise(resolve => t.step_timeout(resolve, 0));
+      assert_equals(navigationType == "traverse", popstate_fired, "popstate fired after handler async step");
       return new Promise(resolve => t.step_timeout(resolve, 0));
     },
-    successNoExplicitCommit : async (e) => {
-      assert_equals(location.hash, startHash, "start has after first async step");
-      assert_false(popstate_fired, "popstate fired after first async step");
-      await new Promise(resolve => t.step_timeout(resolve, 0));
-      assert_equals(location.hash, startHash, "start has after second async step");
-      assert_false(popstate_fired, "popstate fired after second async step");
-      return new Promise(resolve => t.step_timeout(resolve, 0));
-    }
   }
 
   navigation.addEventListener("navigate", e => {
-    e.intercept({ commit: "after-transition",
-                  handler: t.step_func(async () => {
+    e.intercept({ precommitHandler: t.step_func(async () => {
                     assert_equals(e.navigationType, navigationType);
                     assert_equals(location.hash, startHash, "start hash");
                     assert_false(popstate_fired, "popstate fired at handler start");
@@ -49,21 +39,31 @@
                     assert_equals(location.hash, startHash, "hash after first async step");
                     assert_false(popstate_fired, "popstate fired after first async step");
 
-                    return navigate_helpers[mode](e);
-                  })});
+                    if (mode == "rejectBeforeCommit")
+                      return Promise.reject(err);
+                  }),
+                  handler: t.step_func(navigate_helpers[mode])
+                });
   }, { once: true });
 
+  let startingIndex = navigation.currentEntry.index;
+  let expectedIndexOnCommit;
+
   let promises;
   if (navigationType === "push" || navigationType === "replace") {
     destinationHash = (startHash === "" ? "#" : startHash) + "a";
     promises = navigation.navigate(destinationHash, { history: navigationType });
+    expectedIndexOnCommit = (navigationType === "push") ? startingIndex + 1
+                                                        : startingIndex;
   } else if (navigationType === "reload") {
     destinationHash = startHash;
     promises = navigation.reload();
+    expectedIndexOnCommit = startingIndex;
   } else if (navigationType === "traverse") {
     let destinationEntry = navigation.entries()[destinationIndex];
     destinationHash = new URL(destinationEntry.url).hash;
     promises = navigation.traverseTo(destinationEntry.key);
+    expectedIndexOnCommit = destinationIndex;
   }
 
   if (mode === "rejectBeforeCommit") {
@@ -72,6 +72,7 @@
     assert_false(popstate_fired, "popstate fired after promise resolution");
     assert_false(navigatesuccess_fired, "navigatesuccess fired");
     assert_true(navigateerror_fired, "navigateerror fired");
+    assert_equals(navigation.currentEntry.index, startingIndex);
   } else if (mode === "rejectAfterCommit") {
     await promises.committed;
     await assertCommittedFulfillsFinishedRejectsExactly(t, promises, navigation.currentEntry, err);
@@ -79,6 +80,7 @@
     assert_equals(navigationType == "traverse", popstate_fired, "popstate fired after promise resolution");
     assert_false(navigatesuccess_fired, "navigatesuccess fired");
     assert_true(navigateerror_fired, "navigateerror fired");
+    assert_equals(navigation.currentEntry.index, expectedIndexOnCommit);
   } else {
     await promises.committed;
     await assertBothFulfill(t, promises, navigation.currentEntry);
@@ -86,5 +88,6 @@
     assert_equals(navigationType == "traverse", popstate_fired, "popstate fired after promise resolution");
     assert_true(navigatesuccess_fired, "navigatesuccess fired");
     assert_false(navigateerror_fired, "navigateerror fired");
+    assert_equals(navigation.currentEntry.index, expectedIndexOnCommit);
   }
 }
diff --git a/third_party/blink/web_tests/external/wpt/navigation-api/scroll-behavior/manual-scroll-before-after-transition-commit.html b/third_party/blink/web_tests/external/wpt/navigation-api/scroll-behavior/manual-scroll-in-precommit-handler.html
similarity index 89%
rename from third_party/blink/web_tests/external/wpt/navigation-api/scroll-behavior/manual-scroll-before-after-transition-commit.html
rename to third_party/blink/web_tests/external/wpt/navigation-api/scroll-behavior/manual-scroll-in-precommit-handler.html
index 3b32e72..3f92b52 100644
--- a/third_party/blink/web_tests/external/wpt/navigation-api/scroll-behavior/manual-scroll-before-after-transition-commit.html
+++ b/third_party/blink/web_tests/external/wpt/navigation-api/scroll-behavior/manual-scroll-in-precommit-handler.html
@@ -15,11 +15,11 @@
   navigation.onnavigate = e => {
     e.intercept({
       scroll: "manual",
-      commit: "after-transition",
-      handler: t.step_func(() => {
+      precommitHandler: t.step_func(() => {
         assert_throws_dom("InvalidStateError", () => e.scroll());
         assert_not_equals(window.scrollY, 0);
-        e.commit();
+      }),
+      handler: t.step_func(() => {
         e.scroll();
         assert_equals(window.scrollY, 0);
       })
@@ -27,6 +27,6 @@
   }
   await navigation.back().finished;
   assert_equals(window.scrollY, 0);
-}, "scroll: scroll() before commit()");
+}, "scroll: scroll() in precommitHandler throws");
 </script>
 </body>
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/animation-trigger/animation-trigger-fill-mode-both-ref.html b/third_party/blink/web_tests/external/wpt/scroll-animations/animation-trigger/animation-trigger-fill-mode-both-ref.html
new file mode 100644
index 0000000..62b74fb
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/scroll-animations/animation-trigger/animation-trigger-fill-mode-both-ref.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <link rel="help" href="https://drafts.csswg.org/web-animations-2/#animation-trigger">
+  </head>
+  <body>
+    <style>
+      .scroller {
+        overflow-y: scroll;
+        border: solid 1px;
+        place-self: center;
+        height: 300px;
+        width: 200px;
+        position: relative;
+      }
+      .target {
+        height: 100px;
+        width: 100%;
+        background-color: blue;
+        position: absolute;
+        left: -50px;
+      }
+      .space {
+        height: 250px;
+        width: 50%;
+      }
+    </style>
+    <div id="scroller" class="scroller">
+      <div class="space"></div>
+      <div id="target" class="target"></div>
+      <div class="space"></div>
+    </div>
+  </body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/animation-trigger/animation-trigger-fill-mode-both.tentative.html b/third_party/blink/web_tests/external/wpt/scroll-animations/animation-trigger/animation-trigger-fill-mode-both.tentative.html
new file mode 100644
index 0000000..237bbb9
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/scroll-animations/animation-trigger/animation-trigger-fill-mode-both.tentative.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <link rel="help" href="https://drafts.csswg.org/web-animations-2#animation-trigger">
+    <link rel="match" href="animation-trigger-fill-mode-both-ref.html">
+  </head>
+  <body>
+    <style>
+      .scroller {
+        overflow-y: scroll;
+        border: solid 1px;
+        place-self: center;
+        height: 300px;
+        width: 200px;
+        position: relative;
+      }
+      @keyframes slide-in {
+        from {
+          transform: translateX(-50px);
+        }
+      }
+      .target {
+        height: 100px;
+        width: 100%;
+        background-color: blue;
+        animation: slide-in 0.3s both;
+        animation-trigger: view() alternate contain 0% contain 100%;
+        position: absolute;
+      }
+      .space {
+        height: 250px;
+        width: 50%;
+      }
+
+    </style>
+    <div id="scroller" class="scroller">
+      <div class="space"></div>
+      <div id="target" class="target"></div>
+      <div class="space"></div>
+    </div>
+  </body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/animation-trigger/animation-trigger-fill-mode-none-ref.html b/third_party/blink/web_tests/external/wpt/scroll-animations/animation-trigger/animation-trigger-fill-mode-none-ref.html
new file mode 100644
index 0000000..229a31d
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/scroll-animations/animation-trigger/animation-trigger-fill-mode-none-ref.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+  <body>
+    <style>
+      .scroller {
+        overflow-y: scroll;
+        border: solid 1px;
+        place-self: center;
+        height: 300px;
+        width: 200px;
+        position: relative;
+      }
+      .target {
+        height: 100px;
+        width: 100%;
+        background-color: blue;
+      }
+      .space {
+        height: 250px;
+        width: 50%;
+      }
+
+    </style>
+    <div id="scroller" class="scroller">
+      <div class="space"></div>
+      <div id="target" class="target"></div>
+      <div class="space"></div>
+    </div>
+  </body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/animation-trigger/animation-trigger-fill-mode-none.tentative.html b/third_party/blink/web_tests/external/wpt/scroll-animations/animation-trigger/animation-trigger-fill-mode-none.tentative.html
new file mode 100644
index 0000000..81dae8a
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/scroll-animations/animation-trigger/animation-trigger-fill-mode-none.tentative.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <link rel="help" href="https://drafts.csswg.org/web-animations-2#animation-trigger">
+    <link rel="match" href="animation-trigger-fill-mode-none-ref.html">
+  </head>
+  <body>
+    <style>
+      .scroller {
+        overflow-y: scroll;
+        border: solid 1px;
+        place-self: center;
+        height: 300px;
+        width: 200px;
+      }
+      @keyframes slide-in {
+        from {
+          transform: translateX(-50px);
+        }
+      }
+      .target {
+        height: 100px;
+        width: 100%;
+        background-color: blue;
+        animation: slide-in 3s none;
+        animation-trigger: view() alternate contain 0% contain 100%;
+      }
+      .space {
+        height: 250px;
+        width: 50%;
+      }
+
+    </style>
+    <div id="scroller" class="scroller">
+      <div class="space"></div>
+      <div id="target" class="target"></div>
+      <div class="space"></div>
+    </div>
+  </body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/soft-navigation-heuristics/navigation-api-after-transition-commit.tentative.html b/third_party/blink/web_tests/external/wpt/soft-navigation-heuristics/navigation-api-precommit.tentative.html
similarity index 78%
rename from third_party/blink/web_tests/external/wpt/soft-navigation-heuristics/navigation-api-after-transition-commit.tentative.html
rename to third_party/blink/web_tests/external/wpt/soft-navigation-heuristics/navigation-api-precommit.tentative.html
index ae17db7..b5d6077 100644
--- a/third_party/blink/web_tests/external/wpt/soft-navigation-heuristics/navigation-api-after-transition-commit.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/soft-navigation-heuristics/navigation-api-precommit.tentative.html
@@ -15,11 +15,8 @@
   </main>
   <script>
     const link = document.getElementById("link");
-    testNavigationApi("Test soft navigation when navigate event intecepts with { commit: 'after-transition' }", e => {
-        e.intercept({commit: "after-transition", handler: async () => {
-          await addImageToMain();
-          e.commit();
-        }});
+    testNavigationApi("Test soft navigation when navigate event intecepts with a precommit handler", e => {
+        e.intercept({precommitHandler: async () => addImageToMain() });
         timestamps[counter]["eventEnd"] = performance.now();
       }, link);
   </script>
diff --git a/third_party/blink/web_tests/external/wpt/streams/readable-byte-streams/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/streams/readable-byte-streams/WEB_FEATURES.yml
new file mode 100644
index 0000000..a35508f
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/streams/readable-byte-streams/WEB_FEATURES.yml
@@ -0,0 +1,3 @@
+features:
+- name: readable-byte-streams
+  files: "**"
diff --git a/third_party/blink/web_tests/external/wpt/streams/transferable/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/streams/transferable/WEB_FEATURES.yml
new file mode 100644
index 0000000..4ecacf5
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/streams/transferable/WEB_FEATURES.yml
@@ -0,0 +1,3 @@
+features:
+- name: transferable-streams
+  files: "**"
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/should-trusted-type-policy-creation-be-blocked-by-csp-001.html b/third_party/blink/web_tests/external/wpt/trusted-types/should-trusted-type-policy-creation-be-blocked-by-csp-001.html
index 6e51bd5..1816a347 100644
--- a/third_party/blink/web_tests/external/wpt/trusted-types/should-trusted-type-policy-creation-be-blocked-by-csp-001.html
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/should-trusted-type-policy-creation-be-blocked-by-csp-001.html
@@ -345,4 +345,19 @@
     });
   }, `single report-only policy with directive "trusted-type tt-policy-name1 tt-policy-name2 tt-policy-name3"`);
 
+  // Verify unquoted none is treated as a tt-policy-names.
+  ['none', 'allow-duplicates'].forEach(unquoted_keyword => {
+    promise_test(async t => {
+      let results = await tryCreatingTrustedTypePoliciesWithCSP(
+        [unquoted_keyword, "other-tt-policy-name"],
+        `header(Content-Security-Policy,trusted-types ${unquoted_keyword},True)`
+      );
+      assert_equals(results.length, 2);
+      assert_equals(results[0].exception, null);
+      assert_equals(results[0].violatedPolicies.length, 0);
+      assert_true(results[1].exception instanceof TypeError);
+      assert_equals(results[1].violatedPolicies.length, 1);
+    }, `Single enforce policy with directive "trusted-type ${unquoted_keyword}"`);
+  });
+
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/should-trusted-type-policy-creation-be-blocked-by-csp-002-expected.txt b/third_party/blink/web_tests/external/wpt/trusted-types/should-trusted-type-policy-creation-be-blocked-by-csp-002-expected.txt
index 0d1fa5f..8944397 100644
--- a/third_party/blink/web_tests/external/wpt/trusted-types/should-trusted-type-policy-creation-be-blocked-by-csp-002-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/should-trusted-type-policy-creation-be-blocked-by-csp-002-expected.txt
@@ -1,7 +1,5 @@
 This is a testharness.js-based test.
-[FAIL] invalid tt-policy-name name "política"
-  assert_true: createPolicy() should throw a TypeError. expected true got false
-[FAIL] directive "trusted-type _TTP1_%09_TTP2_%0A_TTP3_%0C_TTP4_%0D_TTP5_%20_TTP6_" (required-ascii-whitespace)
-  assert_equals: expected null but got object "TypeError: Failed to execute 'createPolicy' on 'TrustedTypePolicyFactory': Policy "_TTP3_" disallowed."
+[FAIL] directive "trusted-type _TTP1_%09_TTP2_%0C_TTP3_%0D_TTP4_%20_TTP5_" (required-ascii-whitespace)
+  assert_equals: expected null but got object "TypeError: Failed to execute 'createPolicy' on 'TrustedTypePolicyFactory': Policy "_TTP4_" disallowed."
 Harness: the test ran to completion.
 
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/should-trusted-type-policy-creation-be-blocked-by-csp-002.html b/third_party/blink/web_tests/external/wpt/trusted-types/should-trusted-type-policy-creation-be-blocked-by-csp-002.html
index ef1a4bde..fc1906a 100644
--- a/third_party/blink/web_tests/external/wpt/trusted-types/should-trusted-type-policy-creation-be-blocked-by-csp-002.html
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/should-trusted-type-policy-creation-be-blocked-by-csp-002.html
@@ -50,7 +50,6 @@
     "policy$name",
     "policy?name",
     "policy!name",
-    "política",
   ];
   invalidTrustedTypePolicyNames.forEach(trustedTypePolicyName => {
     promise_test(async t => {
@@ -72,15 +71,15 @@
   // https://w3c.github.io/webappsec-csp/#grammardef-required-ascii-whitespace
   promise_test(async t => {
     let results = await tryCreatingTrustedTypePoliciesWithCSP(
-      ["_TTP1_", "_TTP2_", "_TTP3_", "_TTP4_", "_TTP5_", "_TTP6_"],
-      "header(Content-Security-Policy,trusted-types _TTP1_%09_TTP2_%0A_TTP3_%0C_TTP4_%0D_TTP5_%20_TTP6_,True)"
+      ["_TTP1_", "_TTP2_", "_TTP3_", "_TTP4_", "_TTP5_"],
+      "header(Content-Security-Policy,trusted-types _TTP1_%09_TTP2_%0C_TTP3_%0D_TTP4_%20_TTP5_,True)"
     );
-    assert_equals(results.length, 6);
+    assert_equals(results.length, 5);
     results.forEach((result, index) => {
       assert_equals(result.exception, null);
       assert_equals(result.violatedPolicies.length, 0);
     });
-  }, `directive "trusted-type _TTP1_%09_TTP2_%0A_TTP3_%0C_TTP4_%0D_TTP5_%20_TTP6_" (required-ascii-whitespace)`);
+  }, `directive "trusted-type _TTP1_%09_TTP2_%0C_TTP3_%0D_TTP4_%20_TTP5_" (required-ascii-whitespace)`);
 
   // tt-expressions must be separated by a required-ascii-whitespace:
   promise_test(async t => {
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/should-trusted-type-policy-creation-be-blocked-by-csp-003.html b/third_party/blink/web_tests/external/wpt/trusted-types/should-trusted-type-policy-creation-be-blocked-by-csp-003.html
index 55f1479..052fbb8 100644
--- a/third_party/blink/web_tests/external/wpt/trusted-types/should-trusted-type-policy-creation-be-blocked-by-csp-003.html
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/should-trusted-type-policy-creation-be-blocked-by-csp-003.html
@@ -16,10 +16,10 @@
 header(Content-Security-Policy,trusted-types tt-policy-name *,True)|\
 header(Content-Security-Policy,trusted-types * tt-policy-name,True)|\
 header(Content-Security-Policy,trusted-types * *,True)|\
-header(Content-Security-Policy,trusted-types tt-policy-name none,True)|\
-header(Content-Security-Policy,trusted-types none tt-policy-name,True)|\
-header(Content-Security-Policy,trusted-types * none,True)|\
-header(Content-Security-Policy,trusted-types none *,True)|\
+header(Content-Security-Policy,trusted-types tt-policy-name 'none',True)|\
+header(Content-Security-Policy,trusted-types 'none' tt-policy-name,True)|\
+header(Content-Security-Policy,trusted-types * 'none',True)|\
+header(Content-Security-Policy,trusted-types 'none' *,True)|\
 `
     );
     assert_equals(results.length, 1);
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker.html b/third_party/blink/web_tests/external/wpt/trusted-types/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker.html
index 7e928d2..5726db94b 100644
--- a/third_party/blink/web_tests/external/wpt/trusted-types/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker.html
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker.html
@@ -5,7 +5,34 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script>
-  fetch_tests_from_worker(new Worker(
-    "support/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker.js"
-  ));
+  // WebKit test runner assumes the tests always run in the same order, so make
+  // sure fetch_tests_from_worker tests run sequentially.
+  setup({explicit_done: true});
+  (async function() {
+    await fetch_tests_from_worker(new Worker(
+    "support/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-tt-policy-name.js"
+    ));
+    await fetch_tests_from_worker(new Worker(
+      "support/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-none.js"
+    ));
+    await fetch_tests_from_worker(new Worker(
+      "support/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-tt-policy-name-none.js"
+    ));
+    await fetch_tests_from_worker(new Worker(
+      "support/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-wildcard.js"
+    ));
+    await fetch_tests_from_worker(new Worker(
+      "support/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-tt-policy-name-allow-duplicates.js"
+    ));
+    await fetch_tests_from_worker(new Worker(
+      "support/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-allow-duplicates-tt-policy-name.js"
+    ));
+    await fetch_tests_from_worker(new Worker(
+      "support/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-multiple-violations.js"
+    ));
+    await fetch_tests_from_worker(new Worker(
+      "support/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-location.js"
+    ));
+    done();
+  })();
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/should-trusted-type-policy-creation-be-blocked-by-csp-005.html b/third_party/blink/web_tests/external/wpt/trusted-types/should-trusted-type-policy-creation-be-blocked-by-csp-005.html
new file mode 100644
index 0000000..8d10bcb
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/should-trusted-type-policy-creation-be-blocked-by-csp-005.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<meta name="timeout" content="long">
+<link rel="help" href="https://w3c.github.io/trusted-types/dist/spec/#should-block-create-policy">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="./support/csp-violations.js"></script>
+<script src="./support/should-trusted-type-policy-creation-be-blocked-by-csp-location.js"></script>
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/should-trusted-type-policy-creation-be-blocked-by-csp-005.html.headers b/third_party/blink/web_tests/external/wpt/trusted-types/should-trusted-type-policy-creation-be-blocked-by-csp-005.html.headers
new file mode 100644
index 0000000..7e28a4d
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/should-trusted-type-policy-creation-be-blocked-by-csp-005.html.headers
@@ -0,0 +1,2 @@
+Content-Security-Policy: connect-src 'none'
+Content-Security-Policy: trusted-types 'none'
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/support/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-allow-duplicates-tt-policy-name.js b/third_party/blink/web_tests/external/wpt/trusted-types/support/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-allow-duplicates-tt-policy-name.js
new file mode 100644
index 0000000..59d9711
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/support/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-allow-duplicates-tt-policy-name.js
@@ -0,0 +1,20 @@
+importScripts("/resources/testharness.js");
+importScripts("csp-violations.js");
+
+// For CSP applying to this file, please refer to
+// should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-allow-duplicates-tt-policy-name.js.headers
+const tt_directive = `'allow-duplicates' tt-policy-name`;
+
+promise_test(async () => {
+  await no_trusted_type_violation_for(_ => trustedTypes.createPolicy("tt-policy-name"));
+} , `No violation/exception for allowed policy name (${tt_directive}).`);
+
+promise_test(async () => {
+  await no_trusted_type_violation_for(_ => trustedTypes.createPolicy("tt-policy-name"));
+} , `No violation/exception for duplicate policy name (${tt_directive}).`);
+
+promise_test(async () => {
+  await trusted_type_violation_for(TypeError, _ => trustedTypes.createPolicy("duplicate"));
+}, `Violation and exception for forbidden policy name 'duplicate' ${tt_directive}.`);
+
+done();
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/support/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-allow-duplicates-tt-policy-name.js.headers b/third_party/blink/web_tests/external/wpt/trusted-types/support/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-allow-duplicates-tt-policy-name.js.headers
new file mode 100644
index 0000000..9fa44d420
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/support/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-allow-duplicates-tt-policy-name.js.headers
@@ -0,0 +1,2 @@
+Content-Security-Policy: connect-src 'none'
+Content-Security-Policy: trusted-types 'allow-duplicates' tt-policy-name
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/support/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-location.js b/third_party/blink/web_tests/external/wpt/trusted-types/support/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-location.js
new file mode 100644
index 0000000..fc4773f
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/support/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-location.js
@@ -0,0 +1,9 @@
+importScripts("/resources/testharness.js");
+importScripts("csp-violations.js");
+
+// For CSP applying to this file, please refer to
+// should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-location.js.headers
+
+importScripts("should-trusted-type-policy-creation-be-blocked-by-csp-location.js");
+
+done();
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/support/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-location.js.headers b/third_party/blink/web_tests/external/wpt/trusted-types/support/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-location.js.headers
new file mode 100644
index 0000000..7e28a4d
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/support/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-location.js.headers
@@ -0,0 +1,2 @@
+Content-Security-Policy: connect-src 'none'
+Content-Security-Policy: trusted-types 'none'
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/support/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker.js b/third_party/blink/web_tests/external/wpt/trusted-types/support/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-multiple-violations.js
similarity index 81%
rename from third_party/blink/web_tests/external/wpt/trusted-types/support/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker.js
rename to third_party/blink/web_tests/external/wpt/trusted-types/support/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-multiple-violations.js
index c5c512d9..74d4429c 100644
--- a/third_party/blink/web_tests/external/wpt/trusted-types/support/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker.js
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/support/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-multiple-violations.js
@@ -2,7 +2,7 @@
 importScripts("csp-violations.js");
 
 // For CSP applying to this file, please refer to
-// should-trusted-type-policy-creation-be-blocked-by-csp-004-worker.js.headers
+// should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-multiple-violation.js.headers
 
 promise_test(async () => {
   let {violations, exception} = await trusted_type_violations_and_exception_for(_ => trustedTypes.createPolicy("tt-policy-name"));
@@ -17,12 +17,12 @@
   assert_equals(sorted_violations.length, 4);
   assert_equals(sorted_violations[0].policy, "trusted-types other-policy-name");
   assert_equals(sorted_violations[0].disposition, "enforce");
-  assert_equals(sorted_violations[1].policy, "trusted-types none");
+  assert_equals(sorted_violations[1].policy, "trusted-types 'none'");
   assert_equals(sorted_violations[1].disposition, "enforce");
   assert_equals(sorted_violations[2].policy, "trusted-types other-policy-name");
   assert_equals(sorted_violations[2].disposition, "report");
-  assert_equals(sorted_violations[3].policy, "trusted-types none");
+  assert_equals(sorted_violations[3].policy, "trusted-types 'none'");
   assert_equals(sorted_violations[3].disposition, "report");
-}, "Checking reported violations for createPolicy('tt-policy-name') from DedicatedWorker");
+}, "Exception and violations for CSP with multiple enforce and report-only policies.");
 
 done();
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/support/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker.js.headers b/third_party/blink/web_tests/external/wpt/trusted-types/support/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-multiple-violations.js.headers
similarity index 78%
rename from third_party/blink/web_tests/external/wpt/trusted-types/support/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker.js.headers
rename to third_party/blink/web_tests/external/wpt/trusted-types/support/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-multiple-violations.js.headers
index c27bd11..5edaafc1 100644
--- a/third_party/blink/web_tests/external/wpt/trusted-types/support/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker.js.headers
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/support/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-multiple-violations.js.headers
@@ -5,5 +5,5 @@
 Content-Security-Policy-Report-Only: trusted-types other-policy-name
 Content-Security-Policy: trusted-types *
 Content-Security-Policy-Report-Only: trusted-types *
-Content-Security-Policy: trusted-types none
-Content-Security-Policy-Report-Only: trusted-types none
+Content-Security-Policy: trusted-types 'none'
+Content-Security-Policy-Report-Only: trusted-types 'none'
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/support/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-none.js b/third_party/blink/web_tests/external/wpt/trusted-types/support/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-none.js
new file mode 100644
index 0000000..8d0ba3d
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/support/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-none.js
@@ -0,0 +1,16 @@
+importScripts("/resources/testharness.js");
+importScripts("csp-violations.js");
+
+// For CSP applying to this file, please refer to
+// should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-none.js.headers
+const tt_directive = `'none'`;
+
+promise_test(async () => {
+  await trusted_type_violation_for(TypeError, _ => trustedTypes.createPolicy("tt-policy-name"));
+} , `Violation and exception for policy name "tt-policy-name" (${tt_directive}).`);
+
+promise_test(async () => {
+  await trusted_type_violation_for(TypeError, _ => trustedTypes.createPolicy("none"));
+} , `Violation and exception for policy name "none" (${tt_directive}).`);
+
+done();
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/support/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-none.js.headers b/third_party/blink/web_tests/external/wpt/trusted-types/support/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-none.js.headers
new file mode 100644
index 0000000..7e28a4d
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/support/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-none.js.headers
@@ -0,0 +1,2 @@
+Content-Security-Policy: connect-src 'none'
+Content-Security-Policy: trusted-types 'none'
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/support/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-tt-policy-name-allow-duplicates.js b/third_party/blink/web_tests/external/wpt/trusted-types/support/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-tt-policy-name-allow-duplicates.js
new file mode 100644
index 0000000..ed35240
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/support/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-tt-policy-name-allow-duplicates.js
@@ -0,0 +1,23 @@
+importScripts("/resources/testharness.js");
+importScripts("csp-violations.js");
+
+// This test is similar to allow-duplicates-tt-policy-name.js but with a
+// different ordering of tt-expressions.
+
+// For CSP applying to this file, please refer to
+// should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-tt-policy-name-allow-duplicates.js.headers
+const tt_directive = `tt-policy-name 'allow-duplicates'`;
+
+promise_test(async () => {
+  await no_trusted_type_violation_for(_ => trustedTypes.createPolicy("tt-policy-name"));
+} , `No violation/exception for allowed policy name (${tt_directive}).`);
+
+promise_test(async () => {
+  await no_trusted_type_violation_for(_ => trustedTypes.createPolicy("tt-policy-name"));
+} , `No violation/exception for duplicate policy name (${tt_directive}).`);
+
+promise_test(async () => {
+  await trusted_type_violation_for(TypeError, _ => trustedTypes.createPolicy("duplicate"));
+}, `Violation and exception for forbidden policy name 'duplicate' ${tt_directive}.`);
+
+done();
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/support/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-tt-policy-name-allow-duplicates.js.headers b/third_party/blink/web_tests/external/wpt/trusted-types/support/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-tt-policy-name-allow-duplicates.js.headers
new file mode 100644
index 0000000..673b327
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/support/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-tt-policy-name-allow-duplicates.js.headers
@@ -0,0 +1,2 @@
+Content-Security-Policy: connect-src 'none'
+Content-Security-Policy: trusted-types tt-policy-name 'allow-duplicates'
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/support/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-tt-policy-name-none.js b/third_party/blink/web_tests/external/wpt/trusted-types/support/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-tt-policy-name-none.js
new file mode 100644
index 0000000..d34bd04e
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/support/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-tt-policy-name-none.js
@@ -0,0 +1,19 @@
+importScripts("/resources/testharness.js");
+importScripts("csp-violations.js");
+
+// This test verifies that 'none' keyword is ignored if other tt-expression is
+// present.
+
+// For CSP applying to this file, please refer to
+// should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-tt-policy-name-none.js.headers
+const tt_directive = `tt-policy-name 'none'`;
+
+promise_test(async () => {
+  await no_trusted_type_violation_for(_ => trustedTypes.createPolicy("tt-policy-name"));
+} , `No violation/exception for allowed policy names (${tt_directive}).`);
+
+promise_test(async () => {
+  await trusted_type_violation_for(TypeError, _ => trustedTypes.createPolicy("other-policy-name"));
+}, `Violation and exception for forbidden policy name (${tt_directive}).`);
+
+done();
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/support/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-tt-policy-name-none.js.headers b/third_party/blink/web_tests/external/wpt/trusted-types/support/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-tt-policy-name-none.js.headers
new file mode 100644
index 0000000..55cf94e1
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/support/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-tt-policy-name-none.js.headers
@@ -0,0 +1,2 @@
+Content-Security-Policy: connect-src 'none'
+Content-Security-Policy: trusted-types tt-policy-name 'none'
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/support/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-tt-policy-name.js b/third_party/blink/web_tests/external/wpt/trusted-types/support/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-tt-policy-name.js
new file mode 100644
index 0000000..985ee43
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/support/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-tt-policy-name.js
@@ -0,0 +1,24 @@
+importScripts("/resources/testharness.js");
+importScripts("csp-violations.js");
+
+// For CSP applying to this file, please refer to
+// should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-tt-policy-name.js.headers
+const tt_directive = `tt-policy-name-1 tt-policy-name-2 tt-policy-name-3`;
+
+promise_test(async () => {
+  await no_trusted_type_violation_for(_ => trustedTypes.createPolicy("tt-policy-name-1"));
+  await no_trusted_type_violation_for(_ => trustedTypes.createPolicy("tt-policy-name-2"));
+  await no_trusted_type_violation_for(_ => trustedTypes.createPolicy("tt-policy-name-3"));
+} , `No violation/exception for allowed policy names (${tt_directive}).`);
+
+promise_test(async () => {
+  await trusted_type_violation_for(TypeError, _ => trustedTypes.createPolicy("tt-policy-name-1"));
+  await trusted_type_violation_for(TypeError, _ => trustedTypes.createPolicy("tt-policy-name-2"));
+  await trusted_type_violation_for(TypeError, _ => trustedTypes.createPolicy("tt-policy-name-3"));
+}, `Violation and exception for duplicate policy names (${tt_directive}).`);
+
+promise_test(async () => {
+  await trusted_type_violation_for(TypeError, _ => trustedTypes.createPolicy("tt-policy-name-4"));
+}, `Violation and exception for forbidden policy name (${tt_directive}).`);
+
+done();
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/support/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-tt-policy-name.js.headers b/third_party/blink/web_tests/external/wpt/trusted-types/support/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-tt-policy-name.js.headers
new file mode 100644
index 0000000..96b79d7
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/support/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-tt-policy-name.js.headers
@@ -0,0 +1,2 @@
+Content-Security-Policy: connect-src 'none'
+Content-Security-Policy: trusted-types tt-policy-name-1 tt-policy-name-2 tt-policy-name-3
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/support/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-wildcard.js b/third_party/blink/web_tests/external/wpt/trusted-types/support/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-wildcard.js
new file mode 100644
index 0000000..004530d
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/support/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-wildcard.js
@@ -0,0 +1,15 @@
+importScripts("/resources/testharness.js");
+importScripts("csp-violations.js");
+
+// For CSP applying to this file, please refer to
+// should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-wildcard.js.headers
+const tt_directive = `tt-policy-name-1 * tt-policy-name-3`;
+
+promise_test(async () => {
+  await no_trusted_type_violation_for(TypeError, _ => trustedTypes.createPolicy("tt-policy-name-1"));
+  await no_trusted_type_violation_for(TypeError, _ => trustedTypes.createPolicy("tt-policy-name-2"));
+  await no_trusted_type_violation_for(TypeError, _ => trustedTypes.createPolicy("tt-policy-name-3"));
+  await no_trusted_type_violation_for(TypeError, _ => trustedTypes.createPolicy("other-policy-name"));
+} , `No violation and exception for allowed policy names (${tt_directive}).`);
+
+done();
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/support/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-wildcard.js.headers b/third_party/blink/web_tests/external/wpt/trusted-types/support/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-wildcard.js.headers
new file mode 100644
index 0000000..b93b85d4a
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/support/should-trusted-type-policy-creation-be-blocked-by-csp-004-worker-wildcard.js.headers
@@ -0,0 +1,2 @@
+Content-Security-Policy: connect-src 'none'
+Content-Security-Policy: trusted-types tt-policy-name-1 * tt-policy-name-3
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/support/should-trusted-type-policy-creation-be-blocked-by-csp-location.js b/third_party/blink/web_tests/external/wpt/trusted-types/support/should-trusted-type-policy-creation-be-blocked-by-csp-location.js
new file mode 100644
index 0000000..11390a63
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/support/should-trusted-type-policy-creation-be-blocked-by-csp-location.js
@@ -0,0 +1,22 @@
+function createForbiddenPolicy() {                  // 1
+  return trusted_type_violation_for(TypeError, _ => // 2
+    trustedTypes.                                   // 3
+      createPolicy      ("tt-policy-name")          //_4
+/*    |
+1234567890123456789012345
+*/
+  );
+}
+
+promise_test(async () => {
+  let violation = await createForbiddenPolicy();
+  let baseURL = (new URL(location.href)).origin;
+  let sourceFile = new URL("/trusted-types/support/should-trusted-type-policy-creation-be-blocked-by-csp-location.js", baseURL).toString();
+  assert_equals(violation.sourceFile, sourceFile, "source file");
+  assert_equals(violation.lineNumber, 4, "line number");
+  // https://w3c.github.io/webappsec-csp/#create-violation-for-global does not
+  // say how to determine the location and browsers provide inconsistent values
+  // for column number, so just check it's at least the offset of the 'c'
+  // character of createPolicy.
+  assert_greater_than_equal(violation.columnNumber, 7, "column number");
+} , `Location of trusted-types violations.`);
diff --git a/third_party/blink/web_tests/external/wpt/webrtc-encoded-transform/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/webrtc-encoded-transform/WEB_FEATURES.yml
new file mode 100644
index 0000000..0df5a2c
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/webrtc-encoded-transform/WEB_FEATURES.yml
@@ -0,0 +1,3 @@
+features:
+- name: webrtc-encoded-transform
+  files: "**"
diff --git a/third_party/blink/web_tests/fast/navigation-api/commit-console-warning-expected.txt b/third_party/blink/web_tests/fast/navigation-api/commit-console-warning-expected.txt
deleted file mode 100644
index a27b3d1c..0000000
--- a/third_party/blink/web_tests/fast/navigation-api/commit-console-warning-expected.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-CONSOLE WARNING: The "after-transition" value for intercept()'s commit option will override the previously-passed value of "immediate".
-CONSOLE WARNING: The "immediate" value for intercept()'s commit option will override the previously-passed value of "after-transition".
-This is a testharness.js-based test.
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/fast/navigation-api/commit-console-warning.html b/third_party/blink/web_tests/fast/navigation-api/commit-console-warning.html
deleted file mode 100644
index 998e728..0000000
--- a/third_party/blink/web_tests/fast/navigation-api/commit-console-warning.html
+++ /dev/null
@@ -1,78 +0,0 @@
-<!doctype html>
-<script src="../../resources/testharness.js"></script>
-<script src="../../resources/testharnessreport.js"></script>
-<iframe id="i" src="../history/resources/dummy.html"></iframe>
-
-<script>
-promise_test(async t => {
-  navigation.addEventListener("navigate", e => {
-    e.intercept();
-  }, { once: true });
-
-  navigation.addEventListener("navigate", e => {
-    e.intercept({ commit: "immediate" });
-  }, { once: true });
-
-  await navigation.navigate("#1").finished;
-}, "(not provided) + immediate");
-
-promise_test(async t => {
-  navigation.addEventListener("navigate", e => {
-    e.intercept();
-  }, { once: true });
-
-  navigation.addEventListener("navigate", e => {
-    e.intercept({ commit: "after-transition" });
-  }, { once: true });
-
-  await navigation.navigate("#2").finished;
-}, "(not provided) + after-transition");
-
-promise_test(async t => {
-  navigation.addEventListener("navigate", e => {
-    e.intercept({ commit: "immediate" });
-  }, { once: true });
-
-  navigation.addEventListener("navigate", e => {
-    e.intercept({ commit: "after-transition" });
-  }, { once: true });
-
-  await navigation.navigate("#3").finished;
-}, "immediate + after-transition");
-
-promise_test(async t => {
-  navigation.addEventListener("navigate", e => {
-    e.intercept({ commit: "immediate" });
-  }, { once: true });
-
-  navigation.addEventListener("navigate", e => {
-    e.intercept();
-  }, { once: true });
-
-  await navigation.navigate("#4").finished;
-}, "immediate + (not provided)");
-
-promise_test(async t => {
-  navigation.addEventListener("navigate", e => {
-    e.intercept({ commit: "after-transition" });
-  }, { once: true });
-
-  navigation.addEventListener("navigate", e => {
-    e.intercept({ commit: "immediate" });
-  }, { once: true });
-
-  await navigation.navigate("#5").finished;
-}, "after-transition + immediate");
-
-promise_test(async t => {
-  navigation.addEventListener("navigate", e => {
-    e.intercept({ commit: "after-transition" });
-  }, { once: true });
-
-  navigation.addEventListener("navigate", e => {
-    e.intercept();
-  }, { once: true });
-
-  await navigation.navigate("#6").finished;
-}, "after-transition + (not provided)");
-</script>
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/page/navigate-same-document-navigate-event-commit-after-transition.js b/third_party/blink/web_tests/http/tests/inspector-protocol/page/navigate-same-document-navigate-event-commit-after-transition.js
index 64fef88..f7c0566 100644
--- a/third_party/blink/web_tests/http/tests/inspector-protocol/page/navigate-same-document-navigate-event-commit-after-transition.js
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/page/navigate-same-document-navigate-event-commit-after-transition.js
@@ -6,10 +6,8 @@
   await session.evaluate(`
     navigation.onnavigate = e => {
       e.intercept({
-        commit: 'after-transition',
-        handler: async () => {
+        precommitHandler: async () => {
           await new Promise(r => setTimeout(r, 1000));
-          e.commit();
         }
       });
     };
diff --git a/third_party/blink/web_tests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt b/third_party/blink/web_tests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
index c5062e62..4ca403f 100644
--- a/third_party/blink/web_tests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
+++ b/third_party/blink/web_tests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
@@ -1,7 +1,6 @@
 [INTERFACES]
 interface AI
     attribute @@toStringTag
-    getter languageDetector
     getter languageModel
     method constructor
 interface AICreateMonitor : EventTarget
@@ -9,11 +8,6 @@
     getter ondownloadprogress
     method constructor
     setter ondownloadprogress
-interface AILanguageDetectorFactory
-    attribute @@toStringTag
-    method availability
-    method constructor
-    method create
 interface AILanguageModel : EventTarget
     attribute @@toStringTag
     getter inputQuota
@@ -1239,6 +1233,8 @@
     method addRoutes
     method constructor
 interface LanguageDetector
+    static method availability
+    static method create
     attribute @@toStringTag
     getter inputQuota
     method constructor
diff --git a/third_party/blink/web_tests/webexposed/global-interface-listing-dedicated-worker-expected.txt b/third_party/blink/web_tests/webexposed/global-interface-listing-dedicated-worker-expected.txt
index eee6a4f4..693c0e6 100644
--- a/third_party/blink/web_tests/webexposed/global-interface-listing-dedicated-worker-expected.txt
+++ b/third_party/blink/web_tests/webexposed/global-interface-listing-dedicated-worker-expected.txt
@@ -6,7 +6,6 @@
 [Worker] [INTERFACES]
 [Worker] interface AI
 [Worker]     attribute @@toStringTag
-[Worker]     getter languageDetector
 [Worker]     getter languageModel
 [Worker]     method constructor
 [Worker] interface AICreateMonitor : EventTarget
@@ -14,11 +13,6 @@
 [Worker]     getter ondownloadprogress
 [Worker]     method constructor
 [Worker]     setter ondownloadprogress
-[Worker] interface AILanguageDetectorFactory
-[Worker]     attribute @@toStringTag
-[Worker]     method availability
-[Worker]     method constructor
-[Worker]     method create
 [Worker] interface AILanguageModel : EventTarget
 [Worker]     attribute @@toStringTag
 [Worker]     getter inputQuota
@@ -1317,6 +1311,8 @@
 [Worker]     method @@iterator
 [Worker]     method constructor
 [Worker] interface LanguageDetector
+[Worker]     static method availability
+[Worker]     static method create
 [Worker]     attribute @@toStringTag
 [Worker]     getter inputQuota
 [Worker]     method constructor
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 b0ec638f..588e5fd 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
@@ -5,7 +5,6 @@
 [INTERFACES]
 interface AI
     attribute @@toStringTag
-    getter languageDetector
     getter languageModel
     method constructor
 interface AICreateMonitor : EventTarget
@@ -13,11 +12,6 @@
     getter ondownloadprogress
     method constructor
     setter ondownloadprogress
-interface AILanguageDetectorFactory
-    attribute @@toStringTag
-    method availability
-    method constructor
-    method create
 interface AILanguageModel : EventTarget
     attribute @@toStringTag
     getter inputQuota
@@ -5755,6 +5749,8 @@
     setter pseudoElement
     setter target
 interface LanguageDetector
+    static method availability
+    static method create
     attribute @@toStringTag
     getter inputQuota
     method constructor
@@ -6592,10 +6588,8 @@
     getter signal
     getter sourceElement
     getter userInitiated
-    method commit
     method constructor
     method intercept
-    method redirect
     method scroll
 interface Navigation : EventTarget
     attribute @@toStringTag
@@ -6651,6 +6645,10 @@
     method constructor
     method getState
     setter ondispose
+interface NavigationPrecommitController
+    attribute @@toStringTag
+    method constructor
+    method redirect
 interface NavigationPreloadManager
     attribute @@toStringTag
     method constructor
diff --git a/third_party/blink/web_tests/webexposed/global-interface-listing-shared-worker-expected.txt b/third_party/blink/web_tests/webexposed/global-interface-listing-shared-worker-expected.txt
index 1148448..fd25924c 100644
--- a/third_party/blink/web_tests/webexposed/global-interface-listing-shared-worker-expected.txt
+++ b/third_party/blink/web_tests/webexposed/global-interface-listing-shared-worker-expected.txt
@@ -6,7 +6,6 @@
 [Worker] [INTERFACES]
 [Worker] interface AI
 [Worker]     attribute @@toStringTag
-[Worker]     getter languageDetector
 [Worker]     getter languageModel
 [Worker]     method constructor
 [Worker] interface AICreateMonitor : EventTarget
@@ -14,11 +13,6 @@
 [Worker]     getter ondownloadprogress
 [Worker]     method constructor
 [Worker]     setter ondownloadprogress
-[Worker] interface AILanguageDetectorFactory
-[Worker]     attribute @@toStringTag
-[Worker]     method availability
-[Worker]     method constructor
-[Worker]     method create
 [Worker] interface AILanguageModel : EventTarget
 [Worker]     attribute @@toStringTag
 [Worker]     getter inputQuota
@@ -1177,6 +1171,8 @@
 [Worker]     getter width
 [Worker]     method constructor
 [Worker] interface LanguageDetector
+[Worker]     static method availability
+[Worker]     static method create
 [Worker]     attribute @@toStringTag
 [Worker]     getter inputQuota
 [Worker]     method constructor
diff --git a/third_party/catapult b/third_party/catapult
index aec920c..5b40976 160000
--- a/third_party/catapult
+++ b/third_party/catapult
@@ -1 +1 @@
-Subproject commit aec920c93d0ebfb84bad983ff5cf4841e2e9ca5b
+Subproject commit 5b409767f0b2a05044d7139c3c000773da013775
diff --git a/third_party/chromite b/third_party/chromite
index 655d8bb..acb1aa6 160000
--- a/third_party/chromite
+++ b/third_party/chromite
@@ -1 +1 @@
-Subproject commit 655d8bb8027c64ec839a8a4408c02438957fdd78
+Subproject commit acb1aa60a1f64474043bab4fc259b046e8935f42
diff --git a/third_party/dawn b/third_party/dawn
index 8826cf6c..025b5011 160000
--- a/third_party/dawn
+++ b/third_party/dawn
@@ -1 +1 @@
-Subproject commit 8826cf6cfeb7ed83a5d3ab541f7eff117a254976
+Subproject commit 025b5011849104e079510121385aa099125ea65c
diff --git a/third_party/devtools-frontend/src b/third_party/devtools-frontend/src
index e44baae..75529f1 160000
--- a/third_party/devtools-frontend/src
+++ b/third_party/devtools-frontend/src
@@ -1 +1 @@
-Subproject commit e44baaee68cab3a5092a4900c8b6600a5476f19c
+Subproject commit 75529f1cd97224cf610bc19c4d6205f82190eb2c
diff --git a/third_party/lens_server_proto/README.chromium b/third_party/lens_server_proto/README.chromium
index a16bbdc6..d9198677 100644
--- a/third_party/lens_server_proto/README.chromium
+++ b/third_party/lens_server_proto/README.chromium
@@ -1,8 +1,8 @@
 Name: Lens Protos
 Short Name: lens_overlay_proto
 URL: This is the canonical public repository
-Version: 740789356
-Date: 2025-03-26
+Version: 741226731
+Date: 2025-03-27
 License: BSD-3-Clause
 License File: LICENSE
 Shipped: yes
diff --git a/third_party/lens_server_proto/lens_overlay_service_deps.proto b/third_party/lens_server_proto/lens_overlay_service_deps.proto
index 13166dc..73715cd 100644
--- a/third_party/lens_server_proto/lens_overlay_service_deps.proto
+++ b/third_party/lens_server_proto/lens_overlay_service_deps.proto
@@ -46,6 +46,45 @@
   reserved 2;
 }
 
+message LensOverlayUploadChunkRequest {
+  // Required. Chunks of the same payload should have the same request
+  // context.
+  LensOverlayRequestContext request_context = 1;
+
+  // Optional. Debug options for the request.
+  ChunkDebugOptions debug_options = 6;
+
+  // Required. The id of the chunk. This should start from 0 for the first
+  // chunk and go up to (total_chunks - 1) in sequential chunk order.
+  int64 chunk_id = 3;
+
+  // Required. The bytes of the payload chunk to upload.
+  bytes chunk_bytes = 4;
+}
+
+message LensOverlayUploadChunkResponse {
+  // Debug metadata from the upload chunk response.
+  ChunkDebugMetadata debug_metadata = 2;
+}
+
+message ChunkDebugOptions {
+  // Required in first chunk request of the payload. Optional afterwards.
+  // Total number of chunks that will be uploaded to Lens server for the given
+  // payload.
+  int64 total_chunks = 1;
+  // Optional. When true, Lens server will return a repeated list of remaining
+  // chunk ids that it expects to receive to complete the payload. Should only
+  // be used for debugging purposes.
+  bool query_chunks = 2;
+}
+
+message ChunkDebugMetadata {
+  // Only populated if ChunkDebugOptions.query_chunks is true in the
+  // UploadChunk request. List of chunk ids that Lens server is expecting to
+  // complete the payload. Should only be used for debugging purposes.
+  repeated int64 remaining_chunks = 1;
+}
+
 message LensOverlayObjectsResponse {
   // Overlay objects.
   repeated OverlayObject overlay_objects = 2;
diff --git a/third_party/perfetto b/third_party/perfetto
index b3fc262..40b52992 160000
--- a/third_party/perfetto
+++ b/third_party/perfetto
@@ -1 +1 @@
-Subproject commit b3fc262d1f1294a2653e98b909cebc836eb88f97
+Subproject commit 40b529923598b739b2892a536a7692eedbed5685
diff --git a/third_party/protobuf/DIR_METADATA b/third_party/protobuf/DIR_METADATA
new file mode 100644
index 0000000..744e51f
--- /dev/null
+++ b/third_party/protobuf/DIR_METADATA
@@ -0,0 +1,6 @@
+monorail: {
+  component: "Internals>Core"
+}
+buganizer_public: {
+  component_id: 1456128
+}
diff --git a/third_party/protobuf/OWNERS b/third_party/protobuf/OWNERS
new file mode 100644
index 0000000..58e6ae0f
--- /dev/null
+++ b/third_party/protobuf/OWNERS
@@ -0,0 +1,5 @@
+holte@chromium.org
+sophiechang@chromium.org
+wittman@chromium.org
+
+evanstade@microsoft.com #{LAST_RESORT_SUGGESTION}
diff --git a/third_party/protobuf/patches/0031-workaround-cfi-unrelated-cast.patch b/third_party/protobuf/patches/0031-workaround-cfi-unrelated-cast.patch
index 8202c74..40b33b2b 100644
--- a/third_party/protobuf/patches/0031-workaround-cfi-unrelated-cast.patch
+++ b/third_party/protobuf/patches/0031-workaround-cfi-unrelated-cast.patch
@@ -37,7 +37,7 @@
  #define PROTOBUF_UNUSED
  #endif
  
-+#if __has_attribute(no_sanitize)
++#if defined(__has_attribute) && __has_attribute(no_sanitize)
 +#define PROTOBUF_NO_SANITIZE(...) __attribute__((no_sanitize(__VA_ARGS__)))
 +#else
 +#define PROTOBUF_NO_SANITIZE(...)
diff --git a/third_party/protobuf/src/google/protobuf/port_def.inc b/third_party/protobuf/src/google/protobuf/port_def.inc
index 6420abe..a8815fb9 100644
--- a/third_party/protobuf/src/google/protobuf/port_def.inc
+++ b/third_party/protobuf/src/google/protobuf/port_def.inc
@@ -590,7 +590,7 @@
 #define PROTOBUF_PREFETCH_WITH_OFFSET(base, offset)
 #endif
 
-#if __has_attribute(no_sanitize)
+#if defined(__has_attribute) && __has_attribute(no_sanitize)
 #define PROTOBUF_NO_SANITIZE(...) __attribute__((no_sanitize(__VA_ARGS__)))
 #else
 #define PROTOBUF_NO_SANITIZE(...)
diff --git a/third_party/rust/chromium_crates_io/Cargo.toml b/third_party/rust/chromium_crates_io/Cargo.toml
index 79fd0da..c8c2ba7 100644
--- a/third_party/rust/chromium_crates_io/Cargo.toml
+++ b/third_party/rust/chromium_crates_io/Cargo.toml
@@ -48,7 +48,7 @@
 [dependencies.icu_capi]
 version = "2.0.0-beta2"
 default-features = false
-features = ["calendar", "compiled_data", "experimental"]
+features = ["calendar", "compiled_data", "experimental", "casemap"]
 
 [dependencies.tinyvec]
 version = "1.6.0"
diff --git a/third_party/rust/chromium_crates_io/supply-chain/config.toml b/third_party/rust/chromium_crates_io/supply-chain/config.toml
index 37b6d1a..a657e01 100644
--- a/third_party/rust/chromium_crates_io/supply-chain/config.toml
+++ b/third_party/rust/chromium_crates_io/supply-chain/config.toml
@@ -180,10 +180,10 @@
 criteria = ["crypto-safe", "safe-to-deploy", "ub-risk-2"]
 
 [policy."icu_casemap:2.0.0-beta2"]
-criteria = []
+criteria = ["crypto-safe", "safe-to-deploy", "ub-risk-2"]
 
 [policy."icu_casemap_data:2.0.0-beta2"]
-criteria = []
+criteria = ["crypto-safe", "safe-to-deploy", "ub-risk-2"]
 
 [policy."icu_collator:2.0.0-beta2"]
 criteria = []
diff --git a/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/.cargo_vcs_info.json b/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/.cargo_vcs_info.json
new file mode 100644
index 0000000..4933123
--- /dev/null
+++ b/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/.cargo_vcs_info.json
@@ -0,0 +1,6 @@
+{
+  "git": {
+    "sha1": "610757581c7d141a6f20f97fe839ef171c320bb1"
+  },
+  "path_in_vcs": "components/casemap"
+}
\ No newline at end of file
diff --git a/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/Cargo.lock b/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/Cargo.lock
new file mode 100644
index 0000000..dfc3e79
--- /dev/null
+++ b/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/Cargo.lock
@@ -0,0 +1,880 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "anes"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
+
+[[package]]
+name = "anstyle"
+version = "1.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
+
+[[package]]
+name = "autocfg"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
+
+[[package]]
+name = "bumpalo"
+version = "3.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
+
+[[package]]
+name = "cast"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "ciborium"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e"
+dependencies = [
+ "ciborium-io",
+ "ciborium-ll",
+ "serde",
+]
+
+[[package]]
+name = "ciborium-io"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757"
+
+[[package]]
+name = "ciborium-ll"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9"
+dependencies = [
+ "ciborium-io",
+ "half",
+]
+
+[[package]]
+name = "clap"
+version = "4.5.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "027bb0d98429ae334a8698531da7077bdf906419543a35a55c2cb1b66437d767"
+dependencies = [
+ "clap_builder",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.5.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5589e0cba072e0f3d23791efac0fd8627b49c829c196a492e88168e6a669d863"
+dependencies = [
+ "anstyle",
+ "clap_lex",
+]
+
+[[package]]
+name = "clap_lex"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
+
+[[package]]
+name = "criterion"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f"
+dependencies = [
+ "anes",
+ "cast",
+ "ciborium",
+ "clap",
+ "criterion-plot",
+ "is-terminal",
+ "itertools",
+ "num-traits",
+ "once_cell",
+ "oorandom",
+ "plotters",
+ "rayon",
+ "regex",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "tinytemplate",
+ "walkdir",
+]
+
+[[package]]
+name = "criterion-plot"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1"
+dependencies = [
+ "cast",
+ "itertools",
+]
+
+[[package]]
+name = "crossbeam-deque"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
+dependencies = [
+ "crossbeam-epoch",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.9.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
+
+[[package]]
+name = "crunchy"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929"
+
+[[package]]
+name = "databake"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff6ee9e2d2afb173bcdeee45934c89ec341ab26f91c9933774fc15c2b58f83ef"
+dependencies = [
+ "databake-derive",
+ "proc-macro2",
+ "quote",
+]
+
+[[package]]
+name = "databake-derive"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6834770958c7b84223607e49758ec0dde273c4df915e734aad50f62968a4c134"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "synstructure",
+]
+
+[[package]]
+name = "displaydoc"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "either"
+version = "1.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7914353092ddf589ad78f25c5c1c21b7f80b0ff8621e7c814c3485b5306da9d"
+
+[[package]]
+name = "half"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888"
+dependencies = [
+ "cfg-if",
+ "crunchy",
+]
+
+[[package]]
+name = "hermit-abi"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc"
+
+[[package]]
+name = "icu_casemap"
+version = "2.0.0-beta2"
+dependencies = [
+ "criterion",
+ "databake",
+ "displaydoc",
+ "icu_casemap_data",
+ "icu_collections",
+ "icu_locale_core",
+ "icu_properties",
+ "icu_provider",
+ "potential_utf",
+ "serde",
+ "writeable",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_casemap_data"
+version = "2.0.0-beta2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c73c14a74f7902d0101a574879a9994b27f34c75b4e6fcc5bc5259d5423b22d4"
+dependencies = [
+ "icu_provider_baked",
+]
+
+[[package]]
+name = "icu_collections"
+version = "2.0.0-beta2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63df3227b8f369b3f7cc4003f0bdd9ca0083b871e2672811f699d69b473cc174"
+dependencies = [
+ "databake",
+ "displaydoc",
+ "potential_utf",
+ "serde",
+ "yoke",
+ "zerofrom",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_locale_core"
+version = "2.0.0-beta2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b80161b66511e4eb415ef110c67ea8cab4400b749f9e30c8691fff1354934b6b"
+dependencies = [
+ "displaydoc",
+ "litemap",
+ "serde",
+ "tinystr",
+ "writeable",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_properties"
+version = "2.0.0-beta2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d38e872f460f80a6bc564b3545399898df1fcad0503f9da4486a5109ad0e3fe4"
+dependencies = [
+ "displaydoc",
+ "icu_collections",
+ "icu_locale_core",
+ "icu_properties_data",
+ "icu_provider",
+ "potential_utf",
+ "serde",
+ "zerotrie",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_properties_data"
+version = "2.0.0-beta2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2e35a8b7b4647edd7905c9e38c54bf699214fdd143a5df8cecd82d444ec3b94"
+dependencies = [
+ "icu_provider_baked",
+]
+
+[[package]]
+name = "icu_provider"
+version = "2.0.0-beta2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e0d462aad52985bb71e3140fcc44e54d816cf7f2c3f25cd9b090cc77a9798504"
+dependencies = [
+ "displaydoc",
+ "icu_locale_core",
+ "serde",
+ "stable_deref_trait",
+ "tinystr",
+ "writeable",
+ "yoke",
+ "zerofrom",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_provider_baked"
+version = "2.0.0-beta2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2794f00ee1999495f4f1a1e35aee8f54fe7cfcbcf909ec05b60522377200aecb"
+dependencies = [
+ "icu_provider",
+ "writeable",
+ "zerotrie",
+ "zerovec",
+]
+
+[[package]]
+name = "is-terminal"
+version = "0.4.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e19b23d53f35ce9f56aebc7d1bb4e6ac1e9c0db7ac85c8d1760c04379edced37"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "windows-sys",
+]
+
+[[package]]
+name = "itertools"
+version = "0.10.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
+
+[[package]]
+name = "js-sys"
+version = "0.3.77"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
+dependencies = [
+ "once_cell",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.170"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828"
+
+[[package]]
+name = "litemap"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "log"
+version = "0.4.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e"
+
+[[package]]
+name = "memchr"
+version = "2.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
+
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.20.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e"
+
+[[package]]
+name = "oorandom"
+version = "11.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9"
+
+[[package]]
+name = "plotters"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747"
+dependencies = [
+ "num-traits",
+ "plotters-backend",
+ "plotters-svg",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "plotters-backend"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a"
+
+[[package]]
+name = "plotters-svg"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670"
+dependencies = [
+ "plotters-backend",
+]
+
+[[package]]
+name = "potential_utf"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585"
+dependencies = [
+ "serde",
+ "zerovec",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.93"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.38"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rayon"
+version = "1.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
+dependencies = [
+ "either",
+ "rayon-core",
+]
+
+[[package]]
+name = "rayon-core"
+version = "1.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
+dependencies = [
+ "crossbeam-deque",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "regex"
+version = "1.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
+
+[[package]]
+name = "rustversion"
+version = "1.0.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4"
+
+[[package]]
+name = "ryu"
+version = "1.0.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd"
+
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.218"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.218"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.139"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44f86c3acccc9c65b153fe1b85a3be07fe5515274ec9f0653b4a0875731c72a6"
+dependencies = [
+ "itoa",
+ "memchr",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "stable_deref_trait"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
+
+[[package]]
+name = "syn"
+version = "2.0.98"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "synstructure"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tinystr"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b"
+dependencies = [
+ "displaydoc",
+ "serde",
+ "zerovec",
+]
+
+[[package]]
+name = "tinytemplate"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
+dependencies = [
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe"
+
+[[package]]
+name = "walkdir"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
+dependencies = [
+ "same-file",
+ "winapi-util",
+]
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+ "rustversion",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
+dependencies = [
+ "bumpalo",
+ "log",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "web-sys"
+version = "0.3.77"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "winapi-util"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
+dependencies = [
+ "windows-sys",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.59.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_gnullvm",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+
+[[package]]
+name = "writeable"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb"
+
+[[package]]
+name = "yoke"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc"
+dependencies = [
+ "serde",
+ "stable_deref_trait",
+ "yoke-derive",
+ "zerofrom",
+]
+
+[[package]]
+name = "yoke-derive"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "synstructure",
+]
+
+[[package]]
+name = "zerofrom"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5"
+dependencies = [
+ "zerofrom-derive",
+]
+
+[[package]]
+name = "zerofrom-derive"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "synstructure",
+]
+
+[[package]]
+name = "zerotrie"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b7a6cf4865aac8394f19ad46e37f60b929c1ba5eed798b96a32820aa9392929"
+dependencies = [
+ "displaydoc",
+ "litemap",
+ "serde",
+ "yoke",
+ "zerofrom",
+ "zerovec",
+]
+
+[[package]]
+name = "zerovec"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94e62113720e311984f461c56b00457ae9981c0bc7859d22306cc2ae2f95571c"
+dependencies = [
+ "databake",
+ "serde",
+ "yoke",
+ "zerofrom",
+ "zerovec-derive",
+]
+
+[[package]]
+name = "zerovec-derive"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
diff --git a/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/Cargo.toml b/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/Cargo.toml
index 7fe15630..10cdcb9 100644
--- a/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/Cargo.toml
+++ b/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/Cargo.toml
@@ -1,56 +1,161 @@
-# 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.
-
-# @generated from third_party/rust/chromium_crates_io/removed_Cargo.toml
-# by tools/crates/gnrt. Do not edit!
-
-# This is an empty crate that has replaced the 'icu_casemap' crate, since
-# it was listed in `resolve.remove_crates` in gnrt_config.toml.
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies.
+#
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
 
 [package]
+edition = "2021"
+rust-version = "1.81"
 name = "icu_casemap"
 version = "2.0.0-beta2"
+authors = ["The ICU4X Project Developers"]
+build = false
+include = [
+    "data/**/*",
+    "src/**/*",
+    "examples/**/*",
+    "benches/**/*",
+    "tests/**/*",
+    "Cargo.toml",
+    "LICENSE",
+    "README.md",
+]
+autolib = false
+autobins = false
+autoexamples = false
+autotests = false
+autobenches = false
+description = "Unicode case mapping and folding algorithms"
+homepage = "https://icu4x.unicode.org"
+readme = "README.md"
+categories = ["internationalization"]
+license = "Unicode-3.0"
+repository = "https://github.com/unicode-org/icu4x"
+
+[package.metadata.cargo-all-features]
+skip_optional_dependencies = true
+
+[package.metadata.docs.rs]
+all-features = true
 
 [features]
-"compiled_data" = []
-"datagen" = []
-"default" = []
-"serde" = []
+compiled_data = [
+    "dep:icu_casemap_data",
+    "icu_properties/compiled_data",
+]
+datagen = [
+    "serde",
+    "dep:databake",
+    "zerovec/databake",
+    "icu_collections/databake",
+]
+default = ["compiled_data"]
+serde = [
+    "dep:serde",
+    "zerovec/serde",
+    "icu_collections/serde",
+    "icu_provider/serde",
+    "icu_properties/serde",
+    "potential_utf/serde",
+]
+
+[lib]
+name = "icu_casemap"
+path = "src/lib.rs"
+
+[[example]]
+name = "casemapping"
+path = "examples/casemapping.rs"
+
+[[test]]
+name = "conversions"
+path = "tests/conversions.rs"
+required-features = ["compiled_data"]
+
+[[test]]
+name = "gen_greek_to_me"
+path = "tests/gen_greek_to_me.rs"
+harness = false
+required-features = [
+    "compiled_data",
+    "datagen",
+]
+
+[[bench]]
+name = "casemap"
+path = "benches/casemap.rs"
+harness = false
+required-features = ["compiled_data"]
+
+[dependencies.databake]
+version = "0.2.0"
+features = ["derive"]
+optional = true
+default-features = false
 
 [dependencies.displaydoc]
-version = "^0.2.3"
-default_features = false
-features = []
+version = "0.2.3"
+default-features = false
+
 [dependencies.icu_casemap_data]
 version = "~2.0.0-beta2"
-default_features = false
-features = []
+optional = true
+default-features = false
+
 [dependencies.icu_collections]
 version = "~2.0.0-beta2"
-default_features = false
-features = []
+features = ["alloc"]
+default-features = false
+
 [dependencies.icu_locale_core]
-version = "^2.0.0-beta2"
-default_features = false
-features = []
+version = "2.0.0-beta2"
+features = ["alloc"]
+default-features = false
+
 [dependencies.icu_properties]
 version = "~2.0.0-beta2"
-default_features = false
-features = []
+default-features = false
+
 [dependencies.icu_provider]
-version = "^2.0.0-beta2"
-default_features = false
-features = []
+version = "2.0.0-beta2"
+default-features = false
+
 [dependencies.potential_utf]
-version = "^0.1.1"
-default_features = false
-features = []
+version = "0.1.1"
+features = [
+    "alloc",
+    "zerovec",
+]
+default-features = false
+
+[dependencies.serde]
+version = "1.0.110"
+features = [
+    "derive",
+    "alloc",
+]
+optional = true
+default-features = false
+
 [dependencies.writeable]
-version = "^0.6.0"
-default_features = false
-features = []
+version = "0.6.0"
+default-features = false
+
 [dependencies.zerovec]
-version = "^0.11.1"
-default_features = false
-features = []
+version = "0.11.1"
+features = [
+    "alloc",
+    "yoke",
+]
+default-features = false
+
+[dev-dependencies]
+
+[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies.criterion]
+version = "0.5.0"
diff --git a/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/Cargo.toml.orig b/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/Cargo.toml.orig
new file mode 100644
index 0000000..324abc8b
--- /dev/null
+++ b/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/Cargo.toml.orig
@@ -0,0 +1,68 @@
+# This file is part of ICU4X. For terms of use, please see the file
+# called LICENSE at the top level of the ICU4X source tree
+# (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
+
+[package]
+name = "icu_casemap"
+description = "Unicode case mapping and folding algorithms"
+
+authors.workspace = true
+categories.workspace = true
+edition.workspace = true
+homepage.workspace = true
+include.workspace = true
+license.workspace = true
+repository.workspace = true
+rust-version.workspace = true
+version.workspace = true
+
+[package.metadata.docs.rs]
+all-features = true
+
+[dependencies]
+displaydoc = { workspace = true }
+icu_collections = { workspace = true, features = ["alloc"] }
+icu_locale_core = { workspace = true, features = ["alloc"] }
+icu_properties = { workspace = true }
+icu_provider = { workspace = true }
+potential_utf = { workspace = true, features = ["alloc", "zerovec"] }
+writeable = { workspace = true }
+zerovec = { workspace = true, features = ["alloc", "yoke"] }
+
+databake = { workspace = true, features = ["derive"], optional = true}
+serde = { workspace = true, features = ["derive", "alloc"], optional = true }
+
+icu_casemap_data = { workspace = true, optional = true }
+
+[dev-dependencies]
+icu = { path = "../../components/icu", default-features = false }
+icu_normalizer = { path = "../../components/normalizer", features = ["compiled_data"]}
+icu_benchmark_macros = { path = "../../tools/benchmark/macros" }
+icu_collections = { path = "../../components/collections", features = ["databake"] }
+
+[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
+criterion = { workspace = true }
+
+[features]
+default = ["compiled_data"]
+serde = ["dep:serde", "zerovec/serde", "icu_collections/serde", "icu_provider/serde", "icu_properties/serde", "potential_utf/serde"]
+datagen = ["serde", "dep:databake", "zerovec/databake", "icu_collections/databake"]
+compiled_data = ["dep:icu_casemap_data", "icu_properties/compiled_data"]
+
+[package.metadata.cargo-all-features]
+skip_optional_dependencies = true
+
+[[test]]
+name = "conversions"
+required-features = ["compiled_data"]
+
+[[test]]
+name = "gen_greek_to_me"
+harness = false
+required-features = ["compiled_data", "datagen"] # datagen bound can be removed after #3624
+
+
+[[bench]]
+name = "casemap"
+harness = false
+required-features = ["compiled_data"]
diff --git a/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/LICENSE b/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/LICENSE
new file mode 100644
index 0000000..c9be601
--- /dev/null
+++ b/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/LICENSE
@@ -0,0 +1,46 @@
+UNICODE LICENSE V3
+
+COPYRIGHT AND PERMISSION NOTICE
+
+Copyright © 2020-2024 Unicode, Inc.
+
+NOTICE TO USER: Carefully read the following legal agreement. BY
+DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR
+SOFTWARE, YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE
+TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT
+DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE.
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of data files and any associated documentation (the "Data Files") or
+software and any associated documentation (the "Software") to deal in the
+Data Files or Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, and/or sell
+copies of the Data Files or Software, and to permit persons to whom the
+Data Files or Software are furnished to do so, provided that either (a)
+this copyright and permission notice appear with all copies of the Data
+Files or Software, or (b) this copyright and permission notice appear in
+associated Documentation.
+
+THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
+THIRD PARTY RIGHTS.
+
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE
+BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES,
+OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA
+FILES OR SOFTWARE.
+
+Except as contained in this notice, the name of a copyright holder shall
+not be used in advertising or otherwise to promote the sale, use or other
+dealings in these Data Files or Software without prior written
+authorization of the copyright holder.
+
+SPDX-License-Identifier: Unicode-3.0
+
+—
+
+Portions of ICU4X may have been adapted from ICU4C and/or ICU4J.
+ICU 1.8.1 to ICU 57.1 © 1995-2016 International Business Machines Corporation and others.
diff --git a/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/README.md b/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/README.md
new file mode 100644
index 0000000..51f1480c
--- /dev/null
+++ b/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/README.md
@@ -0,0 +1,34 @@
+# icu_casemap [![crates.io](https://img.shields.io/crates/v/icu_casemap)](https://crates.io/crates/icu_casemap)
+
+<!-- cargo-rdme start -->
+
+Case mapping for Unicode characters and strings.
+
+This module is published as its own crate ([`icu_casemap`](https://docs.rs/icu_casemap/latest/icu_casemap/))
+and as part of the [`icu`](https://docs.rs/icu/latest/icu/) crate. See the latter for more details on the ICU4X project.
+
+## Examples
+
+```rust
+use icu::casemap::CaseMapper;
+use icu::locale::langid;
+
+let cm = CaseMapper::new();
+
+assert_eq!(
+    cm.uppercase_to_string("hello world", &langid!("und")),
+    "HELLO WORLD"
+);
+assert_eq!(
+    cm.lowercase_to_string("Γειά σου Κόσμε", &langid!("und")),
+    "γειά σου κόσμε"
+);
+```
+
+[`ICU4X`]: ../icu/index.html
+
+<!-- cargo-rdme end -->
+
+## More Information
+
+For more information on development, authorship, contributing etc. please visit [`ICU4X home page`](https://github.com/unicode-org/icu4x).
diff --git a/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/benches/casemap.rs b/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/benches/casemap.rs
new file mode 100644
index 0000000..4037666
--- /dev/null
+++ b/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/benches/casemap.rs
@@ -0,0 +1,121 @@
+// This file is part of ICU4X. For terms of use, please see the file
+// called LICENSE at the top level of the ICU4X source tree
+// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
+
+use criterion::{black_box, criterion_group, criterion_main, Criterion};
+use icu_casemap::CaseMapper;
+use icu_locale_core::langid;
+use icu_normalizer::DecomposingNormalizerBorrowed;
+
+const TEST_STRING_EN: &str = "One of the key design principles of ICU4X is to make locale data small and portable, allowing it to be pulled from multiple sources depending on the needs of the application.  This document explains how that goal can be achieved.";
+
+// First 50 lines of the Iliad, in precomposed Greek
+// (The Iliad is thousands of years old and public domain)
+// Sources can be found in https://www.perseus.tufts.edu/hopper/text?doc=Perseus:text:1999.01.0133 or https://www.sacred-texts.com/cla/homer/greek/ili01.htm
+const ILIAD: &str = include_str!("data/Iliad.txt");
+
+fn overview_bench(c: &mut Criterion) {
+    let casemapper = CaseMapper::new();
+    let root = langid!("und");
+    let tr = langid!("tr");
+    c.bench_function("icu_casemap/overview", |b| {
+        b.iter(|| {
+            black_box(casemapper.lowercase_to_string(black_box(TEST_STRING_EN), &root));
+            black_box(casemapper.uppercase_to_string(black_box(TEST_STRING_EN), &root));
+        });
+    });
+
+    c.bench_function("icu_casemap/titlecase_segment", |b| {
+        b.iter(|| {
+            for s in TEST_STRING_EN.split(' ') {
+                black_box(casemapper.titlecase_segment_with_only_case_data_to_string(
+                    black_box(s),
+                    &root,
+                    Default::default(),
+                ));
+            }
+        });
+    });
+
+    c.bench_function("icu_casemap/folding", |b| {
+        b.iter(|| {
+            black_box(casemapper.fold_string(black_box(TEST_STRING_EN)));
+        });
+    });
+    c.bench_function("icu_casemap/uppercase_tr", |b| {
+        b.iter(|| {
+            black_box(casemapper.uppercase_to_string(black_box(TEST_STRING_EN), &tr));
+        });
+    });
+}
+fn greek_uppercasing(_c: &mut Criterion) {
+    {
+        let c = _c;
+        let casemapper = CaseMapper::new();
+        let root = langid!("und");
+        let el = langid!("el");
+
+        let iliad_lowercase = casemapper.lowercase_to_string(ILIAD, &root);
+        let decomposer = DecomposingNormalizerBorrowed::new_nfd();
+        let nfd = decomposer.normalize_utf8(ILIAD.as_bytes());
+        let nfd_lowercase = decomposer.normalize_utf8(iliad_lowercase.as_bytes());
+
+        let mut group =
+            c.benchmark_group("icu_casemap/greek_uppercasing/precomposed/upper_from_title");
+        group.bench_function("root", |b| {
+            b.iter(|| {
+                black_box(casemapper.uppercase_to_string(black_box(ILIAD), &root));
+            });
+        });
+        group.bench_function("greek", |b| {
+            b.iter(|| {
+                black_box(casemapper.uppercase_to_string(black_box(ILIAD), &el));
+            });
+        });
+        group.finish();
+
+        let mut group =
+            c.benchmark_group("icu_casemap/greek_uppercasing/precomposed/upper_from_lower");
+        group.bench_function("root", |b| {
+            b.iter(|| {
+                black_box(casemapper.uppercase_to_string(black_box(&iliad_lowercase), &root));
+            });
+        });
+        group.bench_function("greek", |b| {
+            b.iter(|| {
+                black_box(casemapper.uppercase_to_string(black_box(&iliad_lowercase), &el));
+            });
+        });
+        group.finish();
+
+        let mut group =
+            c.benchmark_group("icu_casemap/greek_uppercasing/decomposed/upper_from_title");
+        group.bench_function("root", |b| {
+            b.iter(|| {
+                black_box(casemapper.uppercase_to_string(black_box(&nfd), &root));
+            });
+        });
+        group.bench_function("greek", |b| {
+            b.iter(|| {
+                black_box(casemapper.uppercase_to_string(black_box(&nfd), &el));
+            });
+        });
+        group.finish();
+
+        let mut group =
+            c.benchmark_group("icu_casemap/greek_uppercasing/decomposed/upper_from_lower");
+        group.bench_function("root", |b| {
+            b.iter(|| {
+                black_box(casemapper.uppercase_to_string(black_box(&nfd_lowercase), &root));
+            });
+        });
+        group.bench_function("greek", |b| {
+            b.iter(|| {
+                black_box(casemapper.uppercase_to_string(black_box(&nfd_lowercase), &el));
+            });
+        });
+        group.finish();
+    }
+}
+criterion_group!(benches, overview_bench, greek_uppercasing);
+criterion_main!(benches);
diff --git a/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/benches/data/Iliad.txt b/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/benches/data/Iliad.txt
new file mode 100644
index 0000000..ad38eee
--- /dev/null
+++ b/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/benches/data/Iliad.txt
@@ -0,0 +1,49 @@
+μῆνιν ἄειδε θεὰ Πηληϊάδεω Ἀχιλῆος
+οὐλομένην, ἣ μυρί᾽ Ἀχαιοῖς ἄλγε᾽ ἔθηκε,
+πολλὰς δ᾽ ἰφθίμους ψυχὰς Ἄϊδι προΐαψεν
+ἡρώων, αὐτοὺς δὲ ἑλώρια τεῦχε κύνεσσιν
+5οἰωνοῖσί τε πᾶσι, Διὸς δ᾽ ἐτελείετο βουλή,
+ἐξ οὗ δὴ τὰ πρῶτα διαστήτην ἐρίσαντε
+Ἀτρεΐδης τε ἄναξ ἀνδρῶν καὶ δῖος Ἀχιλλεύς.
+τίς τ᾽ ἄρ σφωε θεῶν ἔριδι ξυνέηκε μάχεσθαι;
+Λητοῦς καὶ Διὸς υἱός: ὃ γὰρ βασιλῆϊ χολωθεὶς
+10νοῦσον ἀνὰ στρατὸν ὄρσε κακήν, ὀλέκοντο δὲ λαοί,
+οὕνεκα τὸν Χρύσην ἠτίμασεν ἀρητῆρα
+Ἀτρεΐδης: ὃ γὰρ ἦλθε θοὰς ἐπὶ νῆας Ἀχαιῶν
+λυσόμενός τε θύγατρα φέρων τ᾽ ἀπερείσι᾽ ἄποινα,
+στέμματ᾽ ἔχων ἐν χερσὶν ἑκηβόλου Ἀπόλλωνος
+15χρυσέῳ ἀνὰ σκήπτρῳ, καὶ λίσσετο πάντας Ἀχαιούς,
+Ἀτρεΐδα δὲ μάλιστα δύω, κοσμήτορε λαῶν:
+Ἀτρεΐδαι τε καὶ ἄλλοι ἐϋκνήμιδες Ἀχαιοί,
+ὑμῖν μὲν θεοὶ δοῖεν Ὀλύμπια δώματ᾽ ἔχοντες
+ἐκπέρσαι Πριάμοιο πόλιν, εὖ δ᾽ οἴκαδ᾽ ἱκέσθαι:
+20παῖδα δ᾽ ἐμοὶ λύσαιτε φίλην, τὰ δ᾽ ἄποινα δέχεσθαι,
+ἁζόμενοι Διὸς υἱὸν ἑκηβόλον Ἀπόλλωνα.
+ἔνθ᾽ ἄλλοι μὲν πάντες ἐπευφήμησαν Ἀχαιοὶ
+αἰδεῖσθαί θ᾽ ἱερῆα καὶ ἀγλαὰ δέχθαι ἄποινα:
+ἀλλ᾽ οὐκ Ἀτρεΐδῃ Ἀγαμέμνονι ἥνδανε θυμῷ,
+25ἀλλὰ κακῶς ἀφίει, κρατερὸν δ᾽ ἐπὶ μῦθον ἔτελλε:
+μή σε γέρον κοίλῃσιν ἐγὼ παρὰ νηυσὶ κιχείω
+ἢ νῦν δηθύνοντ᾽ ἢ ὕστερον αὖτις ἰόντα,
+μή νύ τοι οὐ χραίσμῃ σκῆπτρον καὶ στέμμα θεοῖο:
+τὴν δ᾽ ἐγὼ οὐ λύσω: πρίν μιν καὶ γῆρας ἔπεισιν
+30ἡμετέρῳ ἐνὶ οἴκῳ ἐν Ἄργεϊ τηλόθι πάτρης
+ἱστὸν ἐποιχομένην καὶ ἐμὸν λέχος ἀντιόωσαν:
+ἀλλ᾽ ἴθι μή μ᾽ ἐρέθιζε σαώτερος ὥς κε νέηαι.
+ὣς ἔφατ᾽, ἔδεισεν δ᾽ ὃ γέρων καὶ ἐπείθετο μύθῳ:
+βῆ δ᾽ ἀκέων παρὰ θῖνα πολυφλοίσβοιο θαλάσσης:
+35πολλὰ δ᾽ ἔπειτ᾽ ἀπάνευθε κιὼν ἠρᾶθ᾽ ὃ γεραιὸς
+Ἀπόλλωνι ἄνακτι, τὸν ἠΰκομος τέκε Λητώ:
+κλῦθί μευ ἀργυρότοξ᾽, ὃς Χρύσην ἀμφιβέβηκας
+Κίλλάν τε ζαθέην Τενέδοιό τε ἶφι ἀνάσσεις,
+Σμινθεῦ εἴ ποτέ τοι χαρίεντ᾽ ἐπὶ νηὸν ἔρεψα,
+40ἢ εἰ δή ποτέ τοι κατὰ πίονα μηρί᾽ ἔκηα
+ταύρων ἠδ᾽ αἰγῶν, τὸ δέ μοι κρήηνον ἐέλδωρ:
+τίσειαν Δαναοὶ ἐμὰ δάκρυα σοῖσι βέλεσσιν.
+ὣς ἔφατ᾽ εὐχόμενος, τοῦ δ᾽ ἔκλυε Φοῖβος Ἀπόλλων,
+βῆ δὲ κατ᾽ Οὐλύμποιο καρήνων χωόμενος κῆρ,
+45τόξ᾽ ὤμοισιν ἔχων ἀμφηρεφέα τε φαρέτρην:
+ἔκλαγξαν δ᾽ ἄρ᾽ ὀϊστοὶ ἐπ᾽ ὤμων χωομένοιο,
+αὐτοῦ κινηθέντος: ὃ δ᾽ ἤϊε νυκτὶ ἐοικώς.
+ἕζετ᾽ ἔπειτ᾽ ἀπάνευθε νεῶν, μετὰ δ᾽ ἰὸν ἕηκε:
+δεινὴ δὲ κλαγγὴ γένετ᾽ ἀργυρέοιο βιοῖο:
\ No newline at end of file
diff --git a/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/examples/casemapping.rs b/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/examples/casemapping.rs
new file mode 100644
index 0000000..5202ec5
--- /dev/null
+++ b/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/examples/casemapping.rs
@@ -0,0 +1,23 @@
+// This file is part of ICU4X. For terms of use, please see the file
+// called LICENSE at the top level of the ICU4X source tree
+// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
+
+#![no_main] // https://github.com/unicode-org/icu4x/issues/395
+icu_benchmark_macros::instrument!();
+use icu_benchmark_macros::println;
+
+use icu::casemap::CaseMapper;
+use icu_locale_core::langid;
+
+fn main() {
+    let cm = CaseMapper::new();
+
+    println!(
+        r#"The uppercase of "hello world" is "{}""#,
+        cm.uppercase_to_string("hello world", &langid!("und"))
+    );
+    println!(
+        r#"The lowercase of "Γειά σου Κόσμε" is "{}""#,
+        cm.lowercase_to_string("Γειά σου Κόσμε", &langid!("und"))
+    );
+}
diff --git a/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/src/casemapper.rs b/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/src/casemapper.rs
new file mode 100644
index 0000000..7ba7c27c
--- /dev/null
+++ b/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/src/casemapper.rs
@@ -0,0 +1,797 @@
+// This file is part of ICU4X. For terms of use, please see the file
+// called LICENSE at the top level of the ICU4X source tree
+// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
+
+use crate::internals::{CaseMapLocale, FoldOptions, FullCaseWriteable, StringAndWriteable};
+use crate::provider::data::MappingKind;
+use crate::provider::CaseMap;
+use crate::provider::CaseMapV1;
+use crate::set::ClosureSink;
+use crate::titlecase::{LeadingAdjustment, TitlecaseOptions, TrailingCase};
+use alloc::string::String;
+use icu_locale_core::LanguageIdentifier;
+use icu_provider::prelude::*;
+use writeable::Writeable;
+
+/// A struct with the ability to convert characters and strings to uppercase or lowercase,
+/// or fold them to a normalized form for case-insensitive comparison.
+///
+/// Most methods for this type live on [`CaseMapperBorrowed`], which you can obtain via
+/// [`CaseMapper::new()`] or [`CaseMapper::as_borrowed()`].
+///
+/// # Examples
+///
+/// ```rust
+/// use icu::casemap::CaseMapper;
+/// use icu::locale::langid;
+///
+/// let cm = CaseMapper::new();
+///
+/// assert_eq!(
+///     cm.uppercase_to_string("hello world", &langid!("und")),
+///     "HELLO WORLD"
+/// );
+/// assert_eq!(
+///     cm.lowercase_to_string("Γειά σου Κόσμε", &langid!("und")),
+///     "γειά σου κόσμε"
+/// );
+/// ```
+#[derive(Clone, Debug)]
+pub struct CaseMapper {
+    pub(crate) data: DataPayload<CaseMapV1>,
+}
+
+impl AsRef<CaseMapper> for CaseMapper {
+    fn as_ref(&self) -> &CaseMapper {
+        self
+    }
+}
+
+/// A struct with the ability to convert characters and strings to uppercase or lowercase,
+/// or fold them to a normalized form for case-insensitive comparison, borrowed version.
+///
+/// See methods or [`CaseMapper`] for examples.
+#[derive(Clone, Debug, Copy)]
+pub struct CaseMapperBorrowed<'a> {
+    pub(crate) data: &'a CaseMap<'a>,
+}
+
+impl CaseMapperBorrowed<'static> {
+    /// Cheaply converts a [`CaseMapperBorrowed<'static>`] into a [`CaseMapper`].
+    ///
+    /// Note: Due to branching and indirection, using [`CaseMapper`] might inhibit some
+    /// compile-time optimizations that are possible with [`CaseMapperBorrowed`].
+    pub const fn static_to_owned(self) -> CaseMapper {
+        CaseMapper {
+            data: DataPayload::from_static_ref(self.data),
+        }
+    }
+    /// Creates a [`CaseMapperBorrowed`] using compiled data.
+    ///
+    /// ✨ *Enabled with the `compiled_data` Cargo feature.*
+    ///
+    /// [📚 Help choosing a constructor](icu_provider::constructors)
+    ///
+    /// # Examples
+    ///
+    /// ```rust
+    /// use icu::casemap::CaseMapper;
+    /// use icu::locale::langid;
+    ///
+    /// let cm = CaseMapper::new();
+    ///
+    /// assert_eq!(
+    ///     cm.uppercase_to_string("hello world", &langid!("und")),
+    ///     "HELLO WORLD"
+    /// );
+    /// ```
+    #[cfg(feature = "compiled_data")]
+    pub const fn new() -> Self {
+        Self {
+            data: crate::provider::Baked::SINGLETON_CASE_MAP_V1,
+        }
+    }
+}
+
+#[cfg(feature = "compiled_data")]
+impl Default for CaseMapperBorrowed<'static> {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+impl<'a> CaseMapperBorrowed<'a> {
+    /// Returns the full lowercase mapping of the given string as a [`Writeable`].
+    /// This function is context and language sensitive. Callers should pass the text's language
+    /// as a `LanguageIdentifier` (usually the `id` field of the `Locale`) if available, or
+    /// `Default::default()` for the root locale.
+    ///
+    /// See [`Self::lowercase_to_string()`] for the equivalent convenience function that returns a String,
+    /// as well as for an example.
+    pub fn lowercase(self, src: &'a str, langid: &LanguageIdentifier) -> impl Writeable + 'a {
+        self.data.full_helper_writeable::<false>(
+            src,
+            CaseMapLocale::from_langid(langid),
+            MappingKind::Lower,
+            TrailingCase::default(),
+        )
+    }
+
+    /// Returns the full uppercase mapping of the given string as a [`Writeable`].
+    /// This function is context and language sensitive. Callers should pass the text's language
+    /// as a `LanguageIdentifier` (usually the `id` field of the `Locale`) if available, or
+    /// `Default::default()` for the root locale.
+    ///
+    /// See [`Self::uppercase_to_string()`] for the equivalent convenience function that returns a String,
+    /// as well as for an example.
+    pub fn uppercase(self, src: &'a str, langid: &LanguageIdentifier) -> impl Writeable + 'a {
+        self.data.full_helper_writeable::<false>(
+            src,
+            CaseMapLocale::from_langid(langid),
+            MappingKind::Upper,
+            TrailingCase::default(),
+        )
+    }
+
+    /// Returns the full titlecase mapping of the given string as a [`Writeable`], treating
+    /// the string as a single segment (and thus only titlecasing the beginning of it). Performs
+    /// the specified leading adjustment behavior from the options without loading additional data.
+    ///
+    /// This should typically be used as a lower-level helper to construct the titlecasing operation desired
+    /// by the application, for example one can titlecase on a per-word basis by mixing this with
+    /// a `WordSegmenter`.
+    ///
+    /// This function is context and language sensitive. Callers should pass the text's language
+    /// as a `LanguageIdentifier` (usually the `id` field of the `Locale`) if available, or
+    /// `Default::default()` for the root locale.
+    ///
+    /// This function performs "adjust to cased" leading adjustment behavior when [`LeadingAdjustment::Auto`] or [`LeadingAdjustment::ToCased`]
+    /// is set. Auto mode is not able to pick the "adjust to letter/number/symbol" behavior as this type does not load
+    /// the data to do so, use [`TitlecaseMapper`] if such behavior is desired. See
+    /// the docs of [`TitlecaseMapper`] for more information on what this means. There is no difference between
+    /// the behavior of this function and the equivalent ones on [`TitlecaseMapper`] when the head adjustment mode
+    /// is [`LeadingAdjustment::None`].
+    ///
+    /// See [`Self::titlecase_segment_with_only_case_data_to_string()`] for the equivalent convenience function that returns a String,
+    /// as well as for an example.
+    ///
+    /// [`TitlecaseMapper`]: crate::TitlecaseMapper
+    pub fn titlecase_segment_with_only_case_data(
+        self,
+        src: &'a str,
+        langid: &LanguageIdentifier,
+        options: TitlecaseOptions,
+    ) -> impl Writeable + 'a {
+        self.titlecase_segment_with_adjustment(src, langid, options, |data, ch| data.is_cased(ch))
+    }
+
+    /// Helper to support different leading adjustment behaviors,
+    /// `char_is_lead` is a function that returns true for a character that is allowed to be the
+    /// first relevant character in a titlecasing string, when `leading_adjustment != None`
+    ///
+    /// We return a concrete type instead of `impl Trait` so the return value can be mixed with that of other calls
+    /// to this function with different closures
+    pub(crate) fn titlecase_segment_with_adjustment(
+        self,
+        src: &'a str,
+        langid: &LanguageIdentifier,
+        options: TitlecaseOptions,
+        char_is_lead: impl Fn(&CaseMap, char) -> bool,
+    ) -> StringAndWriteable<'a, FullCaseWriteable<'a, true>> {
+        let (head, rest) = match options.leading_adjustment.unwrap_or_default() {
+            LeadingAdjustment::Auto | LeadingAdjustment::ToCased => {
+                let first_cased = src
+                    .char_indices()
+                    .find(|(_i, ch)| char_is_lead(self.data, *ch));
+                if let Some((first_cased, _ch)) = first_cased {
+                    (
+                        src.get(..first_cased).unwrap_or(""),
+                        src.get(first_cased..).unwrap_or(""),
+                    )
+                } else {
+                    (src, "")
+                }
+            }
+            LeadingAdjustment::None => ("", src),
+        };
+        let writeable = self.data.full_helper_writeable::<true>(
+            rest,
+            CaseMapLocale::from_langid(langid),
+            MappingKind::Title,
+            options.trailing_case.unwrap_or_default(),
+        );
+        StringAndWriteable {
+            string: head,
+            writeable,
+        }
+    }
+    /// Case-folds the characters in the given string as a [`Writeable`].
+    /// This function is locale-independent and context-insensitive.
+    ///
+    /// Can be used to test if two strings are case-insensitively equivalent.
+    ///
+    /// See [`Self::fold_string()`] for the equivalent convenience function that returns a String,
+    /// as well as for an example.
+    pub fn fold(self, src: &'a str) -> impl Writeable + 'a {
+        self.data.full_helper_writeable::<false>(
+            src,
+            CaseMapLocale::Root,
+            MappingKind::Fold,
+            TrailingCase::default(),
+        )
+    }
+
+    /// Case-folds the characters in the given string as a [`Writeable`],
+    /// using Turkic (T) mappings for dotted/dotless I.
+    /// This function is locale-independent and context-insensitive.
+    ///
+    /// Can be used to test if two strings are case-insensitively equivalent.
+    ///
+    /// See [`Self::fold_turkic_string()`] for the equivalent convenience function that returns a String,
+    /// as well as for an example.
+    pub fn fold_turkic(self, src: &'a str) -> impl Writeable + 'a {
+        self.data.full_helper_writeable::<false>(
+            src,
+            CaseMapLocale::Turkish,
+            MappingKind::Fold,
+            TrailingCase::default(),
+        )
+    }
+
+    /// Returns the full lowercase mapping of the given string as a String.
+    ///
+    /// This function is context and language sensitive. Callers should pass the text's language
+    /// as a `LanguageIdentifier` (usually the `id` field of the `Locale`) if available, or
+    /// `Default::default()` for the root locale.
+    ///
+    /// See [`Self::lowercase()`] for the equivalent lower-level function that returns a [`Writeable`]
+    ///
+    /// # Examples
+    ///
+    /// ```rust
+    /// use icu::casemap::CaseMapper;
+    /// use icu::locale::langid;
+    ///
+    /// let cm = CaseMapper::new();
+    /// let root = langid!("und");
+    ///
+    /// assert_eq!(cm.lowercase_to_string("hEllO WorLd", &root), "hello world");
+    /// assert_eq!(cm.lowercase_to_string("Γειά σου Κόσμε", &root), "γειά σου κόσμε");
+    /// assert_eq!(cm.lowercase_to_string("नमस्ते दुनिया", &root), "नमस्ते दुनिया");
+    /// assert_eq!(cm.lowercase_to_string("Привет мир", &root), "привет мир");
+    ///
+    /// // Some behavior is language-sensitive
+    /// assert_eq!(cm.lowercase_to_string("CONSTANTINOPLE", &root), "constantinople");
+    /// assert_eq!(cm.lowercase_to_string("CONSTANTINOPLE", &langid!("tr")), "constantınople");
+    /// ```
+    pub fn lowercase_to_string(self, src: &str, langid: &LanguageIdentifier) -> String {
+        self.lowercase(src, langid).write_to_string().into_owned()
+    }
+
+    /// Returns the full uppercase mapping of the given string as a String.
+    ///
+    /// This function is context and language sensitive. Callers should pass the text's language
+    /// as a `LanguageIdentifier` (usually the `id` field of the `Locale`) if available, or
+    /// `Default::default()` for the root locale.
+    ///
+    /// See [`Self::uppercase()`] for the equivalent lower-level function that returns a [`Writeable`]
+    ///
+    /// # Examples
+    ///
+    /// ```rust
+    /// use icu::casemap::CaseMapper;
+    /// use icu::locale::langid;
+    ///
+    /// let cm = CaseMapper::new();
+    /// let root = langid!("und");
+    ///
+    /// assert_eq!(cm.uppercase_to_string("hEllO WorLd", &root), "HELLO WORLD");
+    /// assert_eq!(cm.uppercase_to_string("Γειά σου Κόσμε", &root), "ΓΕΙΆ ΣΟΥ ΚΌΣΜΕ");
+    /// assert_eq!(cm.uppercase_to_string("नमस्ते दुनिया", &root), "नमस्ते दुनिया");
+    /// assert_eq!(cm.uppercase_to_string("Привет мир", &root), "ПРИВЕТ МИР");
+    ///
+    /// // Some behavior is language-sensitive
+    /// assert_eq!(cm.uppercase_to_string("istanbul", &root), "ISTANBUL");
+    /// assert_eq!(cm.uppercase_to_string("istanbul", &langid!("tr")), "İSTANBUL"); // Turkish dotted i
+    ///
+    /// assert_eq!(cm.uppercase_to_string("և Երևանի", &root), "ԵՒ ԵՐԵՒԱՆԻ");
+    /// assert_eq!(cm.uppercase_to_string("և Երևանի", &langid!("hy")), "ԵՎ ԵՐԵՎԱՆԻ"); // Eastern Armenian ech-yiwn ligature
+    /// ```
+    pub fn uppercase_to_string(self, src: &str, langid: &LanguageIdentifier) -> String {
+        self.uppercase(src, langid).write_to_string().into_owned()
+    }
+
+    /// Returns the full titlecase mapping of the given string as a [`Writeable`], treating
+    /// the string as a single segment (and thus only titlecasing the beginning of it). Performs
+    /// the specified leading adjustment behavior from the options without loading additional data.
+    ///
+    /// Note that [`TitlecaseMapper`] has better behavior, most users should consider using
+    /// it instead. This method primarily exists for people who care about the amount of data being loaded.
+    ///
+    /// This should typically be used as a lower-level helper to construct the titlecasing operation desired
+    /// by the application, for example one can titlecase on a per-word basis by mixing this with
+    /// a `WordSegmenter`.
+    ///
+    /// This function is context and language sensitive. Callers should pass the text's language
+    /// as a `LanguageIdentifier` (usually the `id` field of the `Locale`) if available, or
+    /// `Default::default()` for the root locale.
+    ///
+    /// This function performs "adjust to cased" leading adjustment behavior when [`LeadingAdjustment::Auto`] or [`LeadingAdjustment::ToCased`]
+    /// is set. Auto mode is not able to pick the "adjust to letter/number/symbol" behavior as this type does not load
+    /// the data to do so, use [`TitlecaseMapper`] if such behavior is desired. See
+    /// the docs of [`TitlecaseMapper`] for more information on what this means. There is no difference between
+    /// the behavior of this function and the equivalent ones on [`TitlecaseMapper`] when the head adjustment mode
+    /// is [`LeadingAdjustment::None`].
+    ///
+    /// See [`Self::titlecase_segment_with_only_case_data()`] for the equivalent lower-level function that returns a [`Writeable`]
+    ///
+    /// # Examples
+    ///
+    /// ```rust
+    /// use icu::casemap::CaseMapper;
+    /// use icu::locale::langid;
+    ///
+    /// let cm = CaseMapper::new();
+    /// let root = langid!("und");
+    ///
+    /// let default_options = Default::default();
+    ///
+    /// // note that the subsequent words are not titlecased, this function assumes
+    /// // that the entire string is a single segment and only titlecases at the beginning.
+    /// assert_eq!(cm.titlecase_segment_with_only_case_data_to_string("hEllO WorLd", &root, default_options), "Hello world");
+    /// assert_eq!(cm.titlecase_segment_with_only_case_data_to_string("Γειά σου Κόσμε", &root, default_options), "Γειά σου κόσμε");
+    /// assert_eq!(cm.titlecase_segment_with_only_case_data_to_string("नमस्ते दुनिया", &root, default_options), "नमस्ते दुनिया");
+    /// assert_eq!(cm.titlecase_segment_with_only_case_data_to_string("Привет мир", &root, default_options), "Привет мир");
+    ///
+    /// // Some behavior is language-sensitive
+    /// assert_eq!(cm.titlecase_segment_with_only_case_data_to_string("istanbul", &root, default_options), "Istanbul");
+    /// assert_eq!(cm.titlecase_segment_with_only_case_data_to_string("istanbul", &langid!("tr"), default_options), "İstanbul"); // Turkish dotted i
+    ///
+    /// assert_eq!(cm.titlecase_segment_with_only_case_data_to_string("և Երևանի", &root, default_options), "Եւ երևանի");
+    /// assert_eq!(cm.titlecase_segment_with_only_case_data_to_string("և Երևանի", &langid!("hy"), default_options), "Եվ երևանի"); // Eastern Armenian ech-yiwn ligature
+    ///
+    /// assert_eq!(cm.titlecase_segment_with_only_case_data_to_string("ijkdijk", &root, default_options), "Ijkdijk");
+    /// assert_eq!(cm.titlecase_segment_with_only_case_data_to_string("ijkdijk", &langid!("nl"), default_options), "IJkdijk"); // Dutch IJ digraph
+    /// ```
+    ///
+    /// [`TitlecaseMapper`]: crate::TitlecaseMapper
+    pub fn titlecase_segment_with_only_case_data_to_string(
+        self,
+        src: &str,
+        langid: &LanguageIdentifier,
+        options: TitlecaseOptions,
+    ) -> String {
+        self.titlecase_segment_with_only_case_data(src, langid, options)
+            .write_to_string()
+            .into_owned()
+    }
+
+    /// Case-folds the characters in the given string as a String.
+    /// This function is locale-independent and context-insensitive.
+    ///
+    /// Can be used to test if two strings are case-insensitively equivalent.
+    ///
+    /// See [`Self::fold()`] for the equivalent lower-level function that returns a [`Writeable`]
+    ///s s
+    /// # Examples
+    ///
+    /// ```rust
+    /// use icu::casemap::CaseMapper;
+    ///
+    /// let cm = CaseMapper::new();
+    ///
+    /// // Check if two strings are equivalent case insensitively
+    /// assert_eq!(cm.fold_string("hEllO WorLd"), cm.fold_string("HELLO worlD"));
+    ///
+    /// assert_eq!(cm.fold_string("hEllO WorLd"), "hello world");
+    /// assert_eq!(cm.fold_string("Γειά σου Κόσμε"), "γειά σου κόσμε");
+    /// assert_eq!(cm.fold_string("नमस्ते दुनिया"), "नमस्ते दुनिया");
+    /// assert_eq!(cm.fold_string("Привет мир"), "привет мир");
+    /// ```
+    pub fn fold_string(self, src: &str) -> String {
+        self.fold(src).write_to_string().into_owned()
+    }
+
+    /// Case-folds the characters in the given string as a String,
+    /// using Turkic (T) mappings for dotted/dotless I.
+    /// This function is locale-independent and context-insensitive.
+    ///
+    /// Can be used to test if two strings are case-insensitively equivalent.
+    ///
+    /// See [`Self::fold_turkic()`] for the equivalent lower-level function that returns a [`Writeable`]
+    ///
+    /// # Examples
+    ///
+    /// ```rust
+    /// use icu::casemap::CaseMapper;
+    ///
+    /// let cm = CaseMapper::new();
+    ///
+    /// // Check if two strings are equivalent case insensitively
+    /// assert_eq!(cm.fold_turkic_string("İstanbul"), cm.fold_turkic_string("iSTANBUL"));
+    ///
+    /// assert_eq!(cm.fold_turkic_string("İstanbul not Constantinople"), "istanbul not constantinople");
+    /// assert_eq!(cm.fold_turkic_string("Istanbul not Constantınople"), "ıstanbul not constantınople");
+    ///
+    /// assert_eq!(cm.fold_turkic_string("hEllO WorLd"), "hello world");
+    /// assert_eq!(cm.fold_turkic_string("Γειά σου Κόσμε"), "γειά σου κόσμε");
+    /// assert_eq!(cm.fold_turkic_string("नमस्ते दुनिया"), "नमस्ते दुनिया");
+    /// assert_eq!(cm.fold_turkic_string("Привет мир"), "привет мир");
+    /// ```
+    pub fn fold_turkic_string(self, src: &str) -> String {
+        self.fold_turkic(src).write_to_string().into_owned()
+    }
+
+    /// Adds all simple case mappings and the full case folding for `c` to `set`.
+    /// Also adds special case closure mappings.
+    ///
+    /// Identical to [`CaseMapCloserBorrowed::add_case_closure_to()`], see docs there for more information.
+    /// This method is duplicated so that one does not need to load extra unfold data
+    /// if they only need this and not also [`CaseMapCloserBorrowed::add_string_case_closure_to()`].
+    ///
+    ///
+    /// # Examples
+    ///
+    /// ```rust
+    /// use icu::casemap::CaseMapper;
+    /// use icu::collections::codepointinvlist::CodePointInversionListBuilder;
+    ///
+    /// let cm = CaseMapper::new();
+    /// let mut builder = CodePointInversionListBuilder::new();
+    /// cm.add_case_closure_to('s', &mut builder);
+    ///
+    /// let set = builder.build();
+    ///
+    /// assert!(set.contains('S'));
+    /// assert!(set.contains('ſ'));
+    /// assert!(!set.contains('s')); // does not contain itself
+    /// ```
+    ///
+    /// [`CaseMapCloserBorrowed::add_case_closure_to()`]: crate::CaseMapCloserBorrowed::add_case_closure_to
+    /// [`CaseMapCloserBorrowed::add_string_case_closure_to()`]: crate::CaseMapCloserBorrowed::add_string_case_closure_to
+    pub fn add_case_closure_to<S: ClosureSink>(self, c: char, set: &mut S) {
+        self.data.add_case_closure_to(c, set);
+    }
+
+    /// Returns the lowercase mapping of the given `char`.
+    /// This function only implements simple and common mappings. Full mappings,
+    /// which can map one `char` to a string, are not included.
+    /// For full mappings, use [`CaseMapperBorrowed::lowercase`].
+    ///
+    /// # Examples
+    ///
+    /// ```rust
+    /// use icu::casemap::CaseMapper;
+    ///
+    /// let cm = CaseMapper::new();
+    ///
+    /// assert_eq!(cm.simple_lowercase('C'), 'c');
+    /// assert_eq!(cm.simple_lowercase('c'), 'c');
+    /// assert_eq!(cm.simple_lowercase('Ć'), 'ć');
+    /// assert_eq!(cm.simple_lowercase('Γ'), 'γ');
+    /// ```
+    pub fn simple_lowercase(self, c: char) -> char {
+        self.data.simple_lower(c)
+    }
+
+    /// Returns the uppercase mapping of the given `char`.
+    /// This function only implements simple and common mappings. Full mappings,
+    /// which can map one `char` to a string, are not included.
+    /// For full mappings, use [`CaseMapperBorrowed::uppercase`].
+    ///
+    /// # Examples
+    ///
+    /// ```rust
+    /// use icu::casemap::CaseMapper;
+    ///
+    /// let cm = CaseMapper::new();
+    ///
+    /// assert_eq!(cm.simple_uppercase('c'), 'C');
+    /// assert_eq!(cm.simple_uppercase('C'), 'C');
+    /// assert_eq!(cm.simple_uppercase('ć'), 'Ć');
+    /// assert_eq!(cm.simple_uppercase('γ'), 'Γ');
+    ///
+    /// assert_eq!(cm.simple_uppercase('dz'), 'DZ');
+    /// ```
+    pub fn simple_uppercase(self, c: char) -> char {
+        self.data.simple_upper(c)
+    }
+
+    /// Returns the titlecase mapping of the given `char`.
+    /// This function only implements simple and common mappings. Full mappings,
+    /// which can map one `char` to a string, are not included.
+    ///
+    /// # Examples
+    ///
+    /// ```rust
+    /// use icu::casemap::CaseMapper;
+    ///
+    /// let cm = CaseMapper::new();
+    ///
+    /// assert_eq!(cm.simple_titlecase('dz'), 'Dz');
+    ///
+    /// assert_eq!(cm.simple_titlecase('c'), 'C');
+    /// assert_eq!(cm.simple_titlecase('C'), 'C');
+    /// assert_eq!(cm.simple_titlecase('ć'), 'Ć');
+    /// assert_eq!(cm.simple_titlecase('γ'), 'Γ');
+    /// ```
+    pub fn simple_titlecase(self, c: char) -> char {
+        self.data.simple_title(c)
+    }
+
+    /// Returns the simple case folding of the given char.
+    /// For full mappings, use [`CaseMapperBorrowed::fold`].
+    ///
+    /// This function can be used to perform caseless matches on
+    /// individual characters.
+    /// > *Note:* With Unicode 15.0 data, there are three
+    /// > pairs of characters for which equivalence under this
+    /// > function is inconsistent with equivalence of the
+    /// > one-character strings under [`CaseMapperBorrowed::fold`].
+    /// > This is resolved in Unicode 15.1 and later.
+    ///
+    /// For compatibility applications where simple case folding
+    /// of strings is required, this function can be applied to
+    /// each character of a string.  Note that the resulting
+    /// equivalence relation is different from that obtained
+    /// by [`CaseMapperBorrowed::fold`]:
+    /// The strings "Straße" and "STRASSE" are distinct
+    /// under simple case folding, but are equivalent under
+    /// default (full) case folding.
+    ///
+    /// # Examples
+    ///
+    /// ```rust
+    /// use icu::casemap::CaseMapper;
+    ///
+    /// let cm = CaseMapper::new();
+    ///
+    /// // perform case insensitive checks
+    /// assert_eq!(cm.simple_fold('σ'), cm.simple_fold('ς'));
+    /// assert_eq!(cm.simple_fold('Σ'), cm.simple_fold('ς'));
+    ///
+    /// assert_eq!(cm.simple_fold('c'), 'c');
+    /// assert_eq!(cm.simple_fold('Ć'), 'ć');
+    /// assert_eq!(cm.simple_fold('Γ'), 'γ');
+    /// assert_eq!(cm.simple_fold('ς'), 'σ');
+    ///
+    /// assert_eq!(cm.simple_fold('ß'), 'ß');
+    /// assert_eq!(cm.simple_fold('I'), 'i');
+    /// assert_eq!(cm.simple_fold('İ'), 'İ');
+    /// assert_eq!(cm.simple_fold('ı'), 'ı');
+    /// ```
+    pub fn simple_fold(self, c: char) -> char {
+        self.data.simple_fold(c, FoldOptions::default())
+    }
+
+    /// Returns the simple case folding of the given char, using Turkic (T) mappings for
+    /// dotted/dotless i. This function does not fold `i` and `I` to the same character. Instead,
+    /// `I` will fold to `ı`, and `İ` will fold to `i`. Otherwise, this is the same as
+    /// [`CaseMapperBorrowed::fold()`].
+    ///
+    /// You can use the case folding to perform Turkic caseless matches on characters
+    /// provided they don't full-casefold to strings. To avoid that situation,
+    /// convert to a string and use [`CaseMapperBorrowed::fold_turkic`].
+    ///
+    ///
+    /// # Examples
+    ///
+    /// ```rust
+    /// use icu::casemap::CaseMapper;
+    ///
+    /// let cm = CaseMapper::new();
+    ///
+    /// assert_eq!(cm.simple_fold_turkic('I'), 'ı');
+    /// assert_eq!(cm.simple_fold_turkic('İ'), 'i');
+    /// ```
+    pub fn simple_fold_turkic(self, c: char) -> char {
+        self.data
+            .simple_fold(c, FoldOptions::with_turkic_mappings())
+    }
+}
+
+impl CaseMapper {
+    /// Creates a [`CaseMapperBorrowed`] using compiled data.
+    ///
+    /// ✨ *Enabled with the `compiled_data` Cargo feature.*
+    ///
+    /// [📚 Help choosing a constructor](icu_provider::constructors)
+    ///
+    /// # Examples
+    ///
+    /// ```rust
+    /// use icu::casemap::CaseMapper;
+    /// use icu::locale::langid;
+    ///
+    /// let cm = CaseMapper::new();
+    ///
+    /// assert_eq!(
+    ///     cm.uppercase_to_string("hello world", &langid!("und")),
+    ///     "HELLO WORLD"
+    /// );
+    /// ```
+    #[cfg(feature = "compiled_data")]
+    #[allow(clippy::new_ret_no_self)] // Intentional
+    pub const fn new() -> CaseMapperBorrowed<'static> {
+        CaseMapperBorrowed::new()
+    }
+
+    /// Constructs a borrowed version of this type for more efficient querying.
+    pub fn as_borrowed(&self) -> CaseMapperBorrowed<'_> {
+        CaseMapperBorrowed {
+            data: self.data.get(),
+        }
+    }
+
+    icu_provider::gen_buffer_data_constructors!(() -> error: DataError,
+    functions: [
+        new: skip,
+        try_new_with_buffer_provider,
+        try_new_unstable,
+        Self,
+    ]);
+
+    #[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::new)]
+    pub fn try_new_unstable<P>(provider: &P) -> Result<CaseMapper, DataError>
+    where
+        P: DataProvider<CaseMapV1> + ?Sized,
+    {
+        let data = provider.load(Default::default())?.payload;
+        Ok(Self { data })
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use icu_locale_core::langid;
+
+    #[test]
+    /// Tests for SpecialCasing.txt. Some of the special cases are data-driven, some are code-driven
+    fn test_special_cases() {
+        let cm = CaseMapper::new();
+        let root = langid!("und");
+        let default_options = Default::default();
+
+        // Ligatures
+
+        // U+FB00 LATIN SMALL LIGATURE FF
+        assert_eq!(cm.uppercase_to_string("ff", &root), "FF");
+        // U+FB05 LATIN SMALL LIGATURE LONG S T
+        assert_eq!(cm.uppercase_to_string("ſt", &root), "ST");
+
+        // No corresponding uppercased character
+
+        // U+0149 LATIN SMALL LETTER N PRECEDED BY APOSTROPHE
+        assert_eq!(cm.uppercase_to_string("ʼn", &root), "ʼN");
+
+        // U+1F50 GREEK SMALL LETTER UPSILON WITH PSILI
+        assert_eq!(cm.uppercase_to_string("ὐ", &root), "Υ̓");
+        // U+1FF6 GREEK SMALL LETTER OMEGA WITH PERISPOMENI
+        assert_eq!(cm.uppercase_to_string("ῶ", &root), "Ω͂");
+
+        // YPOGEGRAMMENI / PROSGEGRAMMENI special cases
+
+        // E.g. <alpha><iota_subscript><acute> is uppercased to <ALPHA><acute><IOTA>
+        assert_eq!(
+            cm.uppercase_to_string("α\u{0313}\u{0345}", &root),
+            "Α\u{0313}Ι"
+        );
+        // but the YPOGEGRAMMENI should not titlecase
+        assert_eq!(
+            cm.titlecase_segment_with_only_case_data_to_string(
+                "α\u{0313}\u{0345}",
+                &root,
+                default_options
+            ),
+            "Α\u{0313}\u{0345}"
+        );
+
+        // U+1F80 GREEK SMALL LETTER ALPHA WITH PSILI AND YPOGEGRAMMENI
+        assert_eq!(
+            cm.titlecase_segment_with_only_case_data_to_string("ᾀ", &root, default_options),
+            "ᾈ"
+        );
+        assert_eq!(cm.uppercase_to_string("ᾀ", &root), "ἈΙ");
+
+        // U+1FFC GREEK CAPITAL LETTER OMEGA WITH PROSGEGRAMMENI
+        assert_eq!(cm.lowercase_to_string("ῼ", &root), "ῳ");
+        assert_eq!(
+            cm.titlecase_segment_with_only_case_data_to_string("ῼ", &root, default_options),
+            "ῼ"
+        );
+        assert_eq!(cm.uppercase_to_string("ῼ", &root), "ΩΙ");
+
+        // U+1F98 GREEK CAPITAL LETTER ETA WITH PSILI AND PROSGEGRAMMENI
+        assert_eq!(cm.lowercase_to_string("ᾘ", &root), "ᾐ");
+        assert_eq!(
+            cm.titlecase_segment_with_only_case_data_to_string("ᾘ", &root, default_options),
+            "ᾘ"
+        );
+        assert_eq!(cm.uppercase_to_string("ᾘ", &root), "ἨΙ");
+
+        // U+1FB2 GREEK SMALL LETTER ALPHA WITH VARIA AND YPOGEGRAMMENI
+        assert_eq!(cm.lowercase_to_string("ᾲ", &root), "ᾲ");
+        assert_eq!(
+            cm.titlecase_segment_with_only_case_data_to_string("ᾲ", &root, default_options),
+            "Ὰ\u{345}"
+        );
+        assert_eq!(cm.uppercase_to_string("ᾲ", &root), "ᾺΙ");
+
+        // Final sigma test
+        // U+03A3 GREEK CAPITAL LETTER SIGMA in Final_Sigma context
+        assert_eq!(cm.lowercase_to_string("ΙΙΙΣ", &root), "ιιις");
+
+        // Turkish / Azeri
+        let tr = langid!("tr");
+        let az = langid!("az");
+        // U+0130 LATIN CAPITAL LETTER I WITH DOT ABOVE
+        assert_eq!(cm.lowercase_to_string("İ", &tr), "i");
+        assert_eq!(cm.lowercase_to_string("İ", &az), "i");
+        assert_eq!(
+            cm.titlecase_segment_with_only_case_data_to_string("İ", &tr, default_options),
+            "İ"
+        );
+        assert_eq!(
+            cm.titlecase_segment_with_only_case_data_to_string("İ", &az, default_options),
+            "İ"
+        );
+        assert_eq!(cm.uppercase_to_string("İ", &tr), "İ");
+        assert_eq!(cm.uppercase_to_string("İ", &az), "İ");
+
+        // U+0049 LATIN CAPITAL LETTER I and U+0307 COMBINING DOT ABOVE
+        assert_eq!(cm.lowercase_to_string("I\u{0307}", &tr), "i");
+        assert_eq!(cm.lowercase_to_string("I\u{0307}", &az), "i");
+        assert_eq!(
+            cm.titlecase_segment_with_only_case_data_to_string("I\u{0307}", &tr, default_options),
+            "I\u{0307}"
+        );
+        assert_eq!(
+            cm.titlecase_segment_with_only_case_data_to_string("I\u{0307}", &az, default_options),
+            "I\u{0307}"
+        );
+        assert_eq!(cm.uppercase_to_string("I\u{0307}", &tr), "I\u{0307}");
+        assert_eq!(cm.uppercase_to_string("I\u{0307}", &az), "I\u{0307}");
+
+        // U+0049 LATIN CAPITAL LETTER I
+        assert_eq!(cm.lowercase_to_string("I", &tr), "ı");
+        assert_eq!(cm.lowercase_to_string("I", &az), "ı");
+        assert_eq!(
+            cm.titlecase_segment_with_only_case_data_to_string("I", &tr, default_options),
+            "I"
+        );
+        assert_eq!(
+            cm.titlecase_segment_with_only_case_data_to_string("I", &az, default_options),
+            "I"
+        );
+        assert_eq!(cm.uppercase_to_string("I", &tr), "I");
+        assert_eq!(cm.uppercase_to_string("I", &az), "I");
+
+        // U+0069 LATIN SMALL LETTER I
+        assert_eq!(cm.lowercase_to_string("i", &tr), "i");
+        assert_eq!(cm.lowercase_to_string("i", &az), "i");
+        assert_eq!(
+            cm.titlecase_segment_with_only_case_data_to_string("i", &tr, default_options),
+            "İ"
+        );
+        assert_eq!(
+            cm.titlecase_segment_with_only_case_data_to_string("i", &az, default_options),
+            "İ"
+        );
+        assert_eq!(cm.uppercase_to_string("i", &tr), "İ");
+        assert_eq!(cm.uppercase_to_string("i", &az), "İ");
+    }
+
+    #[test]
+    fn test_cherokee_case_folding() {
+        let case_mapping = CaseMapper::new();
+        assert_eq!(case_mapping.simple_fold('Ꭰ'), 'Ꭰ');
+        assert_eq!(case_mapping.simple_fold('ꭰ'), 'Ꭰ');
+        assert_eq!(case_mapping.simple_fold_turkic('Ꭰ'), 'Ꭰ');
+        assert_eq!(case_mapping.simple_fold_turkic('ꭰ'), 'Ꭰ');
+        assert_eq!(case_mapping.fold_string("Ꭰ"), "Ꭰ");
+        assert_eq!(case_mapping.fold_string("ꭰ"), "Ꭰ");
+        assert_eq!(case_mapping.fold_turkic_string("Ꭰ"), "Ꭰ");
+        assert_eq!(case_mapping.fold_turkic_string("ꭰ"), "Ꭰ");
+    }
+}
diff --git a/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/src/closer.rs b/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/src/closer.rs
new file mode 100644
index 0000000..8092edd
--- /dev/null
+++ b/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/src/closer.rs
@@ -0,0 +1,287 @@
+// This file is part of ICU4X. For terms of use, please see the file
+// called LICENSE at the top level of the ICU4X source tree
+// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
+
+use crate::provider::{CaseMapUnfold, CaseMapUnfoldV1, CaseMapV1};
+use crate::set::ClosureSink;
+use crate::{CaseMapper, CaseMapperBorrowed};
+
+use icu_provider::prelude::*;
+
+/// A wrapper around [`CaseMapper`] that can produce case mapping closures
+/// over a character or string. This wrapper can be constructed directly, or
+/// by wrapping a reference to an existing [`CaseMapper`].
+///
+/// Most methods for this type live on [`CaseMapCloserBorrowed`], which you can obtain via
+/// [`CaseMapCloser::new()`] or [`CaseMapCloser::as_borrowed()`].
+///
+/// # Examples
+///
+/// ```rust
+/// use icu::casemap::CaseMapCloser;
+/// use icu::collections::codepointinvlist::CodePointInversionListBuilder;
+///
+/// let cm = CaseMapCloser::new();
+/// let mut builder = CodePointInversionListBuilder::new();
+/// let found = cm.add_string_case_closure_to("ffi", &mut builder);
+/// assert!(found);
+/// let set = builder.build();
+///
+/// assert!(set.contains('ffi'));
+///
+/// let mut builder = CodePointInversionListBuilder::new();
+/// let found = cm.add_string_case_closure_to("ss", &mut builder);
+/// assert!(found);
+/// let set = builder.build();
+///
+/// assert!(set.contains('ß'));
+/// assert!(set.contains('ẞ'));
+/// ```
+#[derive(Clone, Debug)]
+pub struct CaseMapCloser<CM> {
+    cm: CM,
+    unfold: DataPayload<CaseMapUnfoldV1>,
+}
+
+impl CaseMapCloser<CaseMapper> {
+    icu_provider::gen_buffer_data_constructors!(() -> error: DataError,
+    functions: [
+        new: skip,
+        try_new_with_buffer_provider,
+        try_new_unstable,
+        Self,
+    ]);
+
+    #[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::new)]
+    pub fn try_new_unstable<P>(provider: &P) -> Result<Self, DataError>
+    where
+        P: DataProvider<CaseMapV1> + DataProvider<CaseMapUnfoldV1> + ?Sized,
+    {
+        let cm = CaseMapper::try_new_unstable(provider)?;
+        let unfold = provider.load(Default::default())?.payload;
+        Ok(Self { cm, unfold })
+    }
+}
+
+impl CaseMapCloser<CaseMapper> {
+    /// A constructor which creates a [`CaseMapCloserBorrowed`] using compiled data.
+    ///
+    /// # Examples
+    ///
+    /// ```rust
+    /// use icu::casemap::CaseMapCloser;
+    /// use icu::collections::codepointinvlist::CodePointInversionListBuilder;
+    ///
+    /// let cm = CaseMapCloser::new();
+    /// let mut builder = CodePointInversionListBuilder::new();
+    /// let found = cm.add_string_case_closure_to("ffi", &mut builder);
+    /// assert!(found);
+    /// let set = builder.build();
+    ///
+    /// assert!(set.contains('ffi'));
+    ///
+    /// let mut builder = CodePointInversionListBuilder::new();
+    /// let found = cm.add_string_case_closure_to("ss", &mut builder);
+    /// assert!(found);
+    /// let set = builder.build();
+    ///
+    /// assert!(set.contains('ß'));
+    /// assert!(set.contains('ẞ'));
+    /// ```
+    ///
+    /// ✨ *Enabled with the `compiled_data` Cargo feature.*
+    ///
+    /// [📚 Help choosing a constructor](icu_provider::constructors)
+    #[cfg(feature = "compiled_data")]
+    #[allow(clippy::new_ret_no_self)] // Intentional
+    pub const fn new() -> CaseMapCloserBorrowed<'static> {
+        CaseMapCloserBorrowed::new()
+    }
+}
+
+// We use Borrow, not AsRef, since we want the blanket impl on T
+impl<CM: AsRef<CaseMapper>> CaseMapCloser<CM> {
+    icu_provider::gen_buffer_data_constructors!((casemapper: CM) -> error: DataError,
+    functions: [
+        new_with_mapper: skip,
+        try_new_with_mapper_with_buffer_provider,
+        try_new_with_mapper_unstable,
+        Self,
+    ]);
+
+    /// A constructor which creates a [`CaseMapCloser`] from an existing [`CaseMapper`]
+    /// (either owned or as a reference)
+    ///
+    /// ✨ *Enabled with the `compiled_data` Cargo feature.*
+    ///
+    /// [📚 Help choosing a constructor](icu_provider::constructors)
+    #[cfg(feature = "compiled_data")]
+    pub const fn new_with_mapper(casemapper: CM) -> Self {
+        Self {
+            cm: casemapper,
+            unfold: DataPayload::from_static_ref(
+                crate::provider::Baked::SINGLETON_CASE_MAP_UNFOLD_V1,
+            ),
+        }
+    }
+
+    /// Construct this object to wrap an existing CaseMapper (or a reference to one), loading additional data as needed.
+    #[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::new_with_mapper)]
+    pub fn try_new_with_mapper_unstable<P>(provider: &P, casemapper: CM) -> Result<Self, DataError>
+    where
+        P: DataProvider<CaseMapV1> + DataProvider<CaseMapUnfoldV1> + ?Sized,
+    {
+        let unfold = provider.load(Default::default())?.payload;
+        Ok(Self {
+            cm: casemapper,
+            unfold,
+        })
+    }
+
+    /// Constructs a borrowed version of this type for more efficient querying.
+    pub fn as_borrowed(&self) -> CaseMapCloserBorrowed<'_> {
+        CaseMapCloserBorrowed {
+            cm: self.cm.as_ref().as_borrowed(),
+            unfold: self.unfold.get(),
+        }
+    }
+}
+
+/// A borrowed [`CaseMapCloser`].
+///
+/// See methods or [`CaseMapCloser`] for examples.
+#[derive(Clone, Debug, Copy)]
+pub struct CaseMapCloserBorrowed<'a> {
+    cm: CaseMapperBorrowed<'a>,
+    unfold: &'a CaseMapUnfold<'a>,
+}
+
+impl CaseMapCloserBorrowed<'static> {
+    /// A constructor which creates a [`CaseMapCloserBorrowed`] using compiled data.
+    ///
+    /// # Examples
+    ///
+    /// ```rust
+    /// use icu::casemap::CaseMapCloser;
+    /// use icu::collections::codepointinvlist::CodePointInversionListBuilder;
+    ///
+    /// let cm = CaseMapCloser::new();
+    /// let mut builder = CodePointInversionListBuilder::new();
+    /// let found = cm.add_string_case_closure_to("ffi", &mut builder);
+    /// assert!(found);
+    /// let set = builder.build();
+    ///
+    /// assert!(set.contains('ffi'));
+    ///
+    /// let mut builder = CodePointInversionListBuilder::new();
+    /// let found = cm.add_string_case_closure_to("ss", &mut builder);
+    /// assert!(found);
+    /// let set = builder.build();
+    ///
+    /// assert!(set.contains('ß'));
+    /// assert!(set.contains('ẞ'));
+    /// ```
+    ///
+    /// ✨ *Enabled with the `compiled_data` Cargo feature.*
+    ///
+    /// [📚 Help choosing a constructor](icu_provider::constructors)
+    #[cfg(feature = "compiled_data")]
+    pub const fn new() -> CaseMapCloserBorrowed<'static> {
+        CaseMapCloserBorrowed {
+            cm: CaseMapper::new(),
+            unfold: crate::provider::Baked::SINGLETON_CASE_MAP_UNFOLD_V1,
+        }
+    }
+    /// Cheaply converts a [`CaseMapCloserBorrowed<'static>`] into a [`CaseMapCloser`].
+    ///
+    /// Note: Due to branching and indirection, using [`CaseMapCloser`] might inhibit some
+    /// compile-time optimizations that are possible with [`CaseMapCloserBorrowed`].
+    pub const fn static_to_owned(self) -> CaseMapCloser<CaseMapper> {
+        CaseMapCloser {
+            cm: self.cm.static_to_owned(),
+            unfold: DataPayload::from_static_ref(self.unfold),
+        }
+    }
+}
+
+#[cfg(feature = "compiled_data")]
+impl Default for CaseMapCloserBorrowed<'static> {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+impl CaseMapCloserBorrowed<'_> {
+    /// Adds all simple case mappings and the full case folding for `c` to `set`.
+    /// Also adds special case closure mappings.
+    ///
+    /// In other words, this adds all strings/characters that this casemaps to, as
+    /// well as all characters that may casemap to this one.
+    ///
+    /// The character itself is not added.
+    ///
+    /// For example, the mappings
+    /// - for s include long s
+    /// - for sharp s include ss
+    /// - for k include the Kelvin sign
+    ///
+    /// This function is identical to [`CaseMapperBorrowed::add_case_closure_to()`]; if you don't
+    /// need [`Self::add_string_case_closure_to()`] consider using a [`CaseMapper`] to avoid
+    /// loading additional data.
+    ///
+    /// # Examples
+    ///
+    /// ```rust
+    /// use icu::casemap::CaseMapCloser;
+    /// use icu::collections::codepointinvlist::CodePointInversionListBuilder;
+    ///
+    /// let cm = CaseMapCloser::new();
+    /// let mut builder = CodePointInversionListBuilder::new();
+    /// cm.add_case_closure_to('s', &mut builder);
+    ///
+    /// let set = builder.build();
+    ///
+    /// assert!(set.contains('S'));
+    /// assert!(set.contains('ſ'));
+    /// assert!(!set.contains('s')); // does not contain itself
+    /// ```
+    pub fn add_case_closure_to<S: ClosureSink>(self, c: char, set: &mut S) {
+        self.cm.add_case_closure_to(c, set);
+    }
+
+    /// Finds all characters and strings which may casemap to `s` as their full case folding string
+    /// and adds them to the set. Includes the full case closure of each character mapping.
+    ///
+    /// In other words, this performs a reverse full case folding and then
+    /// adds the case closure items of the resulting code points.
+    ///
+    /// The string itself is not added to the set.
+    ///
+    /// Returns true if the string was found
+    ///
+    /// # Examples
+    ///
+    /// ```rust
+    /// use icu::casemap::CaseMapCloser;
+    /// use icu::collections::codepointinvlist::CodePointInversionListBuilder;
+    ///
+    /// let cm = CaseMapCloser::new();
+    /// let mut builder = CodePointInversionListBuilder::new();
+    /// let found = cm.add_string_case_closure_to("ffi", &mut builder);
+    /// assert!(found);
+    /// let set = builder.build();
+    ///
+    /// assert!(set.contains('ffi'));
+    ///
+    /// let mut builder = CodePointInversionListBuilder::new();
+    /// let found = cm.add_string_case_closure_to("ss", &mut builder);
+    /// assert!(found);
+    /// let set = builder.build();
+    ///
+    /// assert!(set.contains('ß'));
+    /// assert!(set.contains('ẞ'));
+    /// ```
+    pub fn add_string_case_closure_to<S: ClosureSink>(self, s: &str, set: &mut S) -> bool {
+        self.cm.data.add_string_case_closure_to(s, set, self.unfold)
+    }
+}
diff --git a/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/src/greek_to_me/data.rs b/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/src/greek_to_me/data.rs
new file mode 100644
index 0000000..05a1e06
--- /dev/null
+++ b/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/src/greek_to_me/data.rs
@@ -0,0 +1,24 @@
+// This file is part of ICU4X. For terms of use, please see the file
+// called LICENSE at the top level of the ICU4X source tree
+// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
+
+// This file is generated by running `cargo test --test gen_greek_to_me --features compiled_data,datagen
+//
+// Do not edit manually
+
+// All u8s in this file are PackedGreekPrecomposedLetterDatas, see parent module
+
+/// Data for characters in U+370-U+3FF
+pub(crate) const DATA_370: [u8; 0x90] = [128, 128, 128, 128, 0, 0, 128, 128, 0, 0, 128, 128, 128, 128, 0, 128, 0, 0, 0, 0, 0, 0, 65, 0, 66, 67, 68, 0, 69, 0, 70, 71, 100, 1, 128, 128, 128, 2, 128, 3, 128, 4, 128, 128, 128, 128, 128, 5, 128, 129, 0, 128, 128, 6, 128, 128, 128, 7, 36, 38, 65, 66, 67, 68, 102, 1, 128, 128, 128, 2, 128, 3, 128, 4, 128, 128, 128, 128, 128, 5, 128, 129, 128, 128, 128, 6, 128, 128, 128, 7, 36, 38, 69, 70, 71, 128, 128, 128, 8, 72, 40, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 129, 128, 128, 128, 2, 0, 128, 128, 128, 128, 128, 128, 128, 128, 128];
+/// Data for characters in U+1F00-U+1FFF
+pub(crate) const DATA_1F00: [u8; 0xfd] = [1, 1, 65, 65, 65, 65, 65, 65, 1, 1, 65, 65, 65, 65, 65, 65, 2, 2, 66, 66, 66, 66, 0, 0, 2, 2, 66, 66, 66, 66, 0, 0, 3, 3, 67, 67, 67, 67, 67, 67, 3, 3, 67, 67, 67, 67, 67, 67, 4, 4, 68, 68, 68, 68, 68, 68, 4, 4, 68, 68, 68, 68, 68, 68, 5, 5, 69, 69, 69, 69, 0, 0, 5, 5, 69, 69, 69, 69, 0, 0, 6, 6, 70, 70, 70, 70, 70, 70, 0, 6, 0, 70, 0, 70, 0, 70, 7, 7, 71, 71, 71, 71, 71, 71, 7, 7, 71, 71, 71, 71, 71, 71, 65, 65, 66, 66, 67, 67, 68, 68, 69, 69, 70, 70, 71, 71, 0, 0, 17, 17, 81, 81, 81, 81, 81, 81, 17, 17, 81, 81, 81, 81, 81, 81, 19, 19, 83, 83, 83, 83, 83, 83, 19, 19, 83, 83, 83, 83, 83, 83, 23, 23, 87, 87, 87, 87, 87, 87, 23, 23, 87, 87, 87, 87, 87, 87, 1, 1, 81, 17, 81, 0, 65, 81, 1, 1, 65, 65, 17, 0, 4, 0, 0, 0, 83, 19, 83, 0, 67, 83, 66, 66, 67, 67, 19, 0, 0, 0, 4, 4, 100, 100, 0, 0, 68, 100, 4, 4, 68, 68, 0, 0, 0, 0, 6, 6, 102, 102, 129, 129, 70, 102, 6, 6, 70, 70, 129, 0, 0, 0, 0, 0, 87, 23, 87, 0, 71, 87, 69, 69, 71, 71, 23];
+
+/// Characters like the ohm sign that do not belong in the two blocks above
+pub(crate) fn match_extras(ch: char) -> Option<u8> {
+    Some(match ch {
+        'Ω' => 7,
+
+        _ => return None
+    })
+}
+
diff --git a/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/src/greek_to_me/mod.rs b/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/src/greek_to_me/mod.rs
new file mode 100644
index 0000000..ecefa78
--- /dev/null
+++ b/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/src/greek_to_me/mod.rs
@@ -0,0 +1,321 @@
+// This file is part of ICU4X. For terms of use, please see the file
+// called LICENSE at the top level of the ICU4X source tree
+// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
+
+//! This module contains internal data handling tools for the special-cased Greek uppercasing
+//! code. The Greek uppercasing algorithm is code-driven, not user-data-driven, however the code
+//! relies on a CodePointTrie generated based on some Unicode rules.
+//!
+//! We try to keep most of the Greek-specific logic in here, though the actual logic to remove
+//! accents is in full_helper() as it must integrate with the control flow.
+//!
+//! This is public and doc(hidden) so that it can be accessed from the gen_greek_to_me test file,
+//! and should not be used otherwise.
+
+#[rustfmt::skip]
+mod data;
+
+pub(crate) fn get_data(ch: char) -> Option<GreekPrecomposedLetterData> {
+    let ch_i = ch as usize;
+    let packed = if (0x370..=0x3FF).contains(&ch_i) {
+        *data::DATA_370.get(ch_i - 0x370)?
+    } else if (0x1f00..0x1fff).contains(&ch_i) {
+        *data::DATA_1F00.get(ch_i - 0x1f00)?
+    } else {
+        data::match_extras(ch)?
+    };
+
+    let packed = PackedGreekPrecomposedLetterData(packed);
+
+    GreekPrecomposedLetterData::try_from(packed).ok()
+}
+
+/// A packed representation of [`GreekPrecomposedLetterData`]
+///
+/// Bit layout:
+///
+/// ```text
+///   7       6   5   4     3   2   1       0
+/// discr=0 | [diacritics]  | [vowel            ]  
+/// discr=1 | [  unused = 0     ]      | [is_rho]
+/// ```
+///
+/// Bit 7 is the discriminant. if 0, it is a vowel, else, it is a consonant.
+/// If the whole thing is a zero then it is assumed to be an empty entry.
+///
+/// In the vowel case, the next three bits are the next three elements of GreekDiacritics,
+/// in order (accented, dialytika, ypogegrammeni), and the four bits after that identify
+/// a GreekVowel value.
+///
+/// In the consonant case, the remaining seven bits identify a GreekConsonant value.
+#[derive(Debug, Clone, Copy)]
+pub struct PackedGreekPrecomposedLetterData(pub u8);
+
+impl TryFrom<PackedGreekPrecomposedLetterData> for GreekPrecomposedLetterData {
+    type Error = ();
+    fn try_from(other: PackedGreekPrecomposedLetterData) -> Result<GreekPrecomposedLetterData, ()> {
+        if other.0 == 0 {
+            return Err(());
+        }
+        if other.0 & 0x80 == 0 {
+            // vowel
+            let diacritics = GreekDiacritics {
+                accented: other.0 & 0x40 != 0,
+                dialytika: other.0 & 0x20 != 0,
+                ypogegrammeni: other.0 & 0x10 != 0,
+            };
+            let vowel = GreekVowel::try_from(other.0 & 0b1111);
+            debug_assert!(vowel.is_ok());
+            let vowel = vowel.unwrap_or(GreekVowel::Α);
+            Ok(GreekPrecomposedLetterData::Vowel(vowel, diacritics))
+        } else {
+            // consonant
+            // 0x80 is is_rho = false, 0x81 is is_rho = true
+            Ok(GreekPrecomposedLetterData::Consonant(other.0 == 0x81))
+        }
+    }
+}
+
+impl From<GreekPrecomposedLetterData> for PackedGreekPrecomposedLetterData {
+    fn from(other: GreekPrecomposedLetterData) -> Self {
+        match other {
+            GreekPrecomposedLetterData::Vowel(vowel, diacritics) => {
+                let mut bits = 0;
+                if diacritics.accented {
+                    bits |= 0x40;
+                }
+                if diacritics.dialytika {
+                    bits |= 0x20;
+                }
+                if diacritics.ypogegrammeni {
+                    bits |= 0x10;
+                }
+                bits |= vowel as u8;
+                PackedGreekPrecomposedLetterData(bits)
+            }
+            GreekPrecomposedLetterData::Consonant(is_rho) => {
+                PackedGreekPrecomposedLetterData(0x80 + is_rho as u8)
+            }
+        }
+    }
+}
+
+/// The precomposed letter data stored in the hardcoded data in `mod data`
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+pub enum GreekPrecomposedLetterData {
+    /// A vowel, with a capitalized base letter, and the diacritics found
+    Vowel(GreekVowel, GreekDiacritics),
+    /// A consonant or vowel that does not take diacritics
+    ///
+    /// The boolean is true when the consonant is a rho, which is handled specially since
+    /// it can take breathing marks (but is *not* a vowel)
+    Consonant(bool),
+}
+
+/// n.b. these are Greek capital letters, not Latin
+#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq)]
+pub enum GreekVowel {
+    // 0 is purposely left out so that the all-zero case is unambiguous
+    Α = 1,
+    Ε = 2,
+    Η = 3,
+    Ι = 4,
+    Ο = 5,
+    Υ = 6,
+    Ω = 7,
+    ϒ = 8,
+}
+pub const CAPITAL_RHO: char = 'Ρ';
+
+impl From<GreekVowel> for char {
+    fn from(other: GreekVowel) -> Self {
+        match other {
+            GreekVowel::Α => 'Α',
+            GreekVowel::Ε => 'Ε',
+            GreekVowel::Η => 'Η',
+            GreekVowel::Ι => 'Ι',
+            GreekVowel::Ο => 'Ο',
+            GreekVowel::Υ => 'Υ',
+            GreekVowel::Ω => 'Ω',
+            GreekVowel::ϒ => 'ϒ',
+        }
+    }
+}
+
+impl TryFrom<char> for GreekVowel {
+    type Error = ();
+    fn try_from(other: char) -> Result<Self, ()> {
+        Ok(match other {
+            'Α' => GreekVowel::Α,
+            'Ε' => GreekVowel::Ε,
+            'Η' => GreekVowel::Η,
+            'Ι' => GreekVowel::Ι,
+            'Ο' => GreekVowel::Ο,
+            'Υ' => GreekVowel::Υ,
+            'Ω' => GreekVowel::Ω,
+            'ϒ' => GreekVowel::ϒ,
+            _ => return Err(()),
+        })
+    }
+}
+
+impl TryFrom<u8> for GreekVowel {
+    type Error = ();
+    fn try_from(other: u8) -> Result<Self, ()> {
+        Ok(match other {
+            1 => Self::Α,
+            2 => Self::Ε,
+            3 => Self::Η,
+            4 => Self::Ι,
+            5 => Self::Ο,
+            6 => Self::Υ,
+            7 => Self::Ω,
+            8 => Self::ϒ,
+            _ => return Err(()),
+        })
+    }
+}
+
+/// General diacritic information about a character or combining character sequence.
+#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
+pub struct GreekDiacritics {
+    /// Whether it has an accent.
+    pub accented: bool,
+    /// Whether it has a dialytika.
+    pub dialytika: bool,
+    /// Whether it has a ypogegrammeni.
+    pub ypogegrammeni: bool,
+}
+
+/// General diacritic information about a combining character sequence,
+/// identifying the source of the diacritics.
+#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
+pub struct GreekCombiningCharacterSequenceDiacritics {
+    // Diacritics precomposed on the base.
+    pub precomposed: GreekDiacritics,
+    // Combining diacritics.
+    pub combining: GreekDiacritics,
+}
+
+pub const TONOS: char = '\u{0301}';
+pub const DIALYTIKA: char = '\u{0308}';
+pub const DIALYTIKA_TONOS: char = '\u{0344}';
+pub const YPOGEGRAMMENI: char = '\u{0345}';
+
+#[macro_export]
+#[doc(hidden)] // macro
+macro_rules! diacritics {
+    // Accents.
+    // These are mostly removed when uppercasing, but their presence may require
+    // adding a διαλυτικά to a following vowel.
+    (ACCENTS) => {
+        // https://util.unicode.org/UnicodeJsps/list-unicodeset.jsp?a=%5B%5Cu0300+%5Cu0301+%5Cu0342+%5Cu0302+%5Cu0303+%5Cu0311%5D&g=&i=
+        '\u{0300}' // Polytonic βαρεία (varia), grave accent.
+        | $crate::greek_to_me::TONOS // Polytonic οξεία (oxia) unified with monotonic τόνος (tonos), acute accent.
+        | '\u{0342}' // Polytonic περισπωμένη (perispomeni), often translated to circumflex.
+        | '\u{0302}' // Circumflex accent, sometimes a lookalike of the περισπωμένη.
+        | '\u{0303}' // Tilde, sometimes a lookalike of the περισπωμένη.
+        | '\u{0311}' // Inverted breve, sometimes a lookalike of the περισπωμένη.
+    };
+    // Breathings and length marks.
+    // These expected to occur in Greek combining sequences, and are removed when uppercasing.
+    // This removal has no other effect.
+    (BREATHING_AND_LENGTH) => {
+        // https://util.unicode.org/UnicodeJsps/list-unicodeset.jsp?a=%5B%5Cu0304+%5Cu0306+%5Cu0313+%5Cu0314+%5Cu0343%5D&g=&i=
+        '\u{0304}'  // Macron, marking long vowels.
+        | '\u{0306}'  // Breve, marking short vowels.
+        | '\u{0313}'  // Comma above, smooth breathing or κορωνίς marking crasis.
+        | '\u{0314}'  // Reversed comma above, rough breathing.
+        | '\u{0343}'  // κορωνίς (koronis), canonically decomposes to comma above.
+    };
+    // All diacritics containing a dialytika
+    (DIALYTIKA_ALL) => { $crate::greek_to_me::DIALYTIKA | $crate::greek_to_me::DIALYTIKA_TONOS };
+    (DIALYTIKA) => { $crate::greek_to_me::DIALYTIKA };
+    (DIALYTIKA_TONOS) => { $crate::greek_to_me::DIALYTIKA_TONOS };
+    (YPOGEGRAMMENI) => { $crate::greek_to_me::YPOGEGRAMMENI };
+    ($($i:ident)|+) => { $(diacritics!($i))|+};
+}
+
+/// Macro that generates match arms for various diacritic groupings.
+///
+/// Groupings supported:
+///
+/// - ACCENTS
+/// - BREATHING_AND_LENGTH
+/// - DIALYTIKA, DIALYTIKA_TONOS, and DIALITYKA_ALL
+/// - YPOGEGRAMMENI
+///
+/// This is a macro to make it easy to keep the lists of accents in sync.
+pub use crate::diacritics;
+
+impl GreekDiacritics {
+    /// Whilst forwards-iterating from an existing character,
+    /// consume all further greek diacritics and store their existence into this struct.
+    pub(crate) fn consume_greek_diacritics(&mut self, context_after: &str) {
+        for c in context_after.chars() {
+            match c {
+                diacritics!(ACCENTS) => self.accented = true,
+                DIALYTIKA_TONOS => {
+                    self.dialytika = true;
+                    self.accented = true;
+                }
+                DIALYTIKA => self.dialytika = true,
+                YPOGEGRAMMENI => self.ypogegrammeni = true,
+                // Ignore other accent marks that are expected to co-occur with Greek.
+                diacritics!(BREATHING_AND_LENGTH) => (),
+                _ => break,
+            }
+        }
+    }
+}
+
+/// Given the context before a character, check if it is preceded by a Greek letter.
+pub(crate) fn preceded_by_greek_letter(context_before: &str) -> bool {
+    for c in context_before.chars().rev() {
+        match c {
+            diacritics!(ACCENTS | BREATHING_AND_LENGTH | DIALYTIKA_ALL | YPOGEGRAMMENI) => continue,
+            _ => return get_data(c).is_some(),
+        }
+    }
+    false
+}
+
+/// Returns diacritic information for the combining character sequence preceding the current character
+/// if it that preceding combining character sequence is a greek vowel.
+pub(crate) fn preceding_greek_vowel_diacritics(
+    context_before: &str,
+) -> Option<GreekCombiningCharacterSequenceDiacritics> {
+    let mut combining: GreekDiacritics = Default::default();
+    for c in context_before.chars().rev() {
+        match c {
+            diacritics!(ACCENTS) => combining.accented = true,
+            diacritics!(DIALYTIKA_TONOS) => {
+                combining.dialytika = true;
+                combining.accented = true;
+            }
+            diacritics!(DIALYTIKA) => combining.dialytika = true,
+            diacritics!(BREATHING_AND_LENGTH) => continue,
+            _ => {
+                let data = get_data(c);
+                if let Some(GreekPrecomposedLetterData::Vowel(_vowel, diacritics)) = data {
+                    return Some(GreekCombiningCharacterSequenceDiacritics {
+                        precomposed: diacritics,
+                        combining,
+                    });
+                } else {
+                    // Not a greek vowel.
+                    return None;
+                }
+            }
+        }
+    }
+    None
+}
+
+/// Is the character a diacritic expected to be used with greek (except ypogegrammeni).
+pub(crate) fn is_greek_diacritic_except_ypogegrammeni(c: char) -> bool {
+    matches!(
+        c,
+        diacritics!(ACCENTS | BREATHING_AND_LENGTH | DIALYTIKA_ALL)
+    )
+}
diff --git a/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/src/internals.rs b/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/src/internals.rs
new file mode 100644
index 0000000..3919719
--- /dev/null
+++ b/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/src/internals.rs
@@ -0,0 +1,862 @@
+// This file is part of ICU4X. For terms of use, please see the file
+// called LICENSE at the top level of the ICU4X source tree
+// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
+
+//! This module contains most of the actual algorithms for case mapping.
+//!
+//! Primarily, it implements methods on `CaseMap`, which contains the data model.
+
+use crate::greek_to_me::{
+    self, GreekCombiningCharacterSequenceDiacritics, GreekDiacritics, GreekPrecomposedLetterData,
+    GreekVowel,
+};
+use crate::provider::data::{DotType, MappingKind};
+use crate::provider::exception_helpers::ExceptionSlot;
+use crate::provider::{CaseMap, CaseMapUnfold};
+use crate::set::ClosureSink;
+use crate::titlecase::TrailingCase;
+use core::fmt;
+use icu_locale_core::LanguageIdentifier;
+use writeable::Writeable;
+
+const ACUTE: char = '\u{301}';
+
+// Used to control the behavior of CaseMapper::fold.
+// Currently only used to decide whether to use Turkic (T) mappings for dotted/dotless i.
+#[derive(Copy, Clone, Default)]
+pub(crate) struct FoldOptions {
+    exclude_special_i: bool,
+}
+
+impl FoldOptions {
+    pub fn with_turkic_mappings() -> Self {
+        Self {
+            exclude_special_i: true,
+        }
+    }
+}
+
+/// Helper type that wraps a writeable in a prefix string
+pub(crate) struct StringAndWriteable<'a, W> {
+    pub string: &'a str,
+    pub writeable: W,
+}
+
+impl<Wr: Writeable> Writeable for StringAndWriteable<'_, Wr> {
+    fn write_to<W: fmt::Write + ?Sized>(&self, sink: &mut W) -> fmt::Result {
+        sink.write_str(self.string)?;
+        self.writeable.write_to(sink)
+    }
+    fn writeable_length_hint(&self) -> writeable::LengthHint {
+        writeable::LengthHint::exact(self.string.len()) + self.writeable.writeable_length_hint()
+    }
+}
+
+pub(crate) struct FullCaseWriteable<'a, const IS_TITLE_CONTEXT: bool> {
+    data: &'a CaseMap<'a>,
+    src: &'a str,
+    locale: CaseMapLocale,
+    mapping: MappingKind,
+    titlecase_tail_casing: TrailingCase,
+}
+
+impl<const IS_TITLE_CONTEXT: bool> Writeable for FullCaseWriteable<'_, IS_TITLE_CONTEXT> {
+    #[allow(clippy::indexing_slicing)] // last_uncopied_index and i are known to be in bounds
+    fn write_to<W: fmt::Write + ?Sized>(&self, sink: &mut W) -> fmt::Result {
+        let src = self.src;
+        let mut mapping = self.mapping;
+        let mut iter = src.char_indices();
+        for (i, c) in &mut iter {
+            let context = ContextIterator::new(&src[..i], &src[i..]);
+            self.data
+                .full_helper::<IS_TITLE_CONTEXT, W>(c, context, self.locale, mapping, sink)?;
+            if IS_TITLE_CONTEXT {
+                if self.titlecase_tail_casing == TrailingCase::Lower {
+                    mapping = MappingKind::Lower;
+                } else {
+                    break;
+                }
+            }
+        }
+        // Write the rest of the string
+        if IS_TITLE_CONTEXT && self.titlecase_tail_casing == TrailingCase::Unchanged {
+            sink.write_str(iter.as_str())?;
+        }
+        Ok(())
+    }
+    fn writeable_length_hint(&self) -> writeable::LengthHint {
+        writeable::LengthHint::at_least(self.src.len())
+    }
+}
+
+impl<'data> CaseMap<'data> {
+    fn simple_helper(&self, c: char, kind: MappingKind) -> char {
+        let data = self.lookup_data(c);
+        if !data.has_exception() {
+            if data.is_relevant_to(kind) {
+                let folded = c as i32 + data.delta() as i32;
+                // GIGO: delta should be valid
+                char::from_u32(folded as u32).unwrap_or(c)
+            } else {
+                c
+            }
+        } else {
+            let idx = data.exception_index();
+            let exception = self.exceptions.get(idx);
+            if data.is_relevant_to(kind) {
+                if let Some(simple) = exception.get_simple_case_slot_for(c) {
+                    return simple;
+                }
+            }
+            exception.slot_char_for_kind(kind).unwrap_or(c)
+        }
+    }
+
+    // Returns the lowercase mapping of the given `char`.
+    #[inline]
+    pub(crate) fn simple_lower(&self, c: char) -> char {
+        self.simple_helper(c, MappingKind::Lower)
+    }
+
+    // Returns the uppercase mapping of the given `char`.
+    #[inline]
+    pub(crate) fn simple_upper(&self, c: char) -> char {
+        self.simple_helper(c, MappingKind::Upper)
+    }
+
+    // Returns the titlecase mapping of the given `char`.
+    #[inline]
+    pub(crate) fn simple_title(&self, c: char) -> char {
+        self.simple_helper(c, MappingKind::Title)
+    }
+
+    // Return the simple case folding mapping of the given char.
+    #[inline]
+    pub(crate) fn simple_fold(&self, c: char, options: FoldOptions) -> char {
+        let data = self.lookup_data(c);
+        if !data.has_exception() {
+            if data.is_upper_or_title() {
+                let folded = c as i32 + data.delta() as i32;
+                // GIGO: delta should be valid
+                char::from_u32(folded as u32).unwrap_or(c)
+            } else {
+                c
+            }
+        } else {
+            // TODO: if we move conditional fold and no_simple_case_folding into
+            // simple_helper, this function can just call simple_helper.
+            let idx = data.exception_index();
+            let exception = self.exceptions.get(idx);
+            if exception.bits.has_conditional_fold() {
+                self.simple_fold_special_case(c, options)
+            } else if exception.bits.no_simple_case_folding() {
+                c
+            } else if data.is_upper_or_title() && exception.has_slot(ExceptionSlot::Delta) {
+                // unwrap_or case should never happen but best to avoid panics
+                exception.get_simple_case_slot_for(c).unwrap_or('\0')
+            } else if let Some(slot_char) = exception.slot_char_for_kind(MappingKind::Fold) {
+                slot_char
+            } else {
+                c
+            }
+        }
+    }
+
+    fn dot_type(&self, c: char) -> DotType {
+        let data = self.lookup_data(c);
+        if !data.has_exception() {
+            data.dot_type()
+        } else {
+            let idx = data.exception_index();
+            self.exceptions.get(idx).bits.dot_type()
+        }
+    }
+
+    // Returns true if this code point is is case-sensitive.
+    // This is not currently exposed.
+    #[allow(dead_code)]
+    fn is_case_sensitive(&self, c: char) -> bool {
+        let data = self.lookup_data(c);
+        if !data.has_exception() {
+            data.is_sensitive()
+        } else {
+            let idx = data.exception_index();
+            self.exceptions.get(idx).bits.is_sensitive()
+        }
+    }
+
+    /// Returns whether the character is cased
+    pub(crate) fn is_cased(&self, c: char) -> bool {
+        self.lookup_data(c).case_type().is_some()
+    }
+
+    #[inline(always)]
+    // IS_TITLE_CONTEXT must be true if kind is MappingKind::Title
+    // The kind may be a different kind with IS_TITLE_CONTEXT still true because
+    // titlecasing a segment involves switching to lowercase later
+    fn full_helper<const IS_TITLE_CONTEXT: bool, W: fmt::Write + ?Sized>(
+        &self,
+        c: char,
+        context: ContextIterator,
+        locale: CaseMapLocale,
+        kind: MappingKind,
+        sink: &mut W,
+    ) -> fmt::Result {
+        // If using a title mapping IS_TITLE_CONTEXT must be true
+        debug_assert!(kind != MappingKind::Title || IS_TITLE_CONTEXT);
+        // In a title context, kind MUST be Title or Lower
+        debug_assert!(
+            !IS_TITLE_CONTEXT || kind == MappingKind::Title || kind == MappingKind::Lower
+        );
+
+        // ICU4C's non-standard extension for Dutch IJ titlecasing
+        // handled here instead of in full_lower_special_case because J does not have conditional
+        // special casemapping.
+        if IS_TITLE_CONTEXT && locale == CaseMapLocale::Dutch && kind == MappingKind::Lower {
+            // When titlecasing, a J found immediately after an I at the beginning of the segment
+            // should also uppercase. They are both allowed to have an acute accent but it must
+            // be present on both letters or neither. They may not have any other combining marks.
+            if (c == 'j' || c == 'J') && context.is_dutch_ij_pair_at_beginning(self) {
+                return sink.write_char('J');
+            }
+        }
+
+        // ICU4C's non-standard extension for Greek uppercasing:
+        // https://icu.unicode.org/design/case/greek-upper.
+        // Effectively removes Greek accents from Greek vowels during uppercasing,
+        // whilst attempting to preserve additional marks like the dialytika (diæresis)
+        // and ypogegrammeni (combining small iota).
+        if !IS_TITLE_CONTEXT && locale == CaseMapLocale::Greek && kind == MappingKind::Upper {
+            // Remove all combining diacritics on a Greek letter.
+            // Ypogegrammeni is not an accent mark and is handled by regular casemapping (it turns into
+            // a capital iota).
+            // The dialytika is removed here, but it may be added again when the base letter is being processed.
+            if greek_to_me::is_greek_diacritic_except_ypogegrammeni(c)
+                && context.preceded_by_greek_letter()
+            {
+                return Ok(());
+            }
+            let data = greek_to_me::get_data(c);
+            // Check if the character is a Greek vowel
+            match data {
+                Some(GreekPrecomposedLetterData::Vowel(vowel, mut precomposed_diacritics)) => {
+                    // Get the diacritics on the character itself, and add any further combining diacritics
+                    // from the context.
+                    let mut diacritics = context.add_greek_diacritics(precomposed_diacritics);
+                    // If the previous vowel had an accent (which would be removed) but no dialytika,
+                    // and this is an iota or upsilon, add a dialytika since it is necessary to disambiguate
+                    // the now-unaccented adjacent vowels from a digraph/diphthong.
+                    // Use a precomposed dialytika if the accent was precomposed, and a combining dialytika
+                    // if the accent was combining, so as to map NFD to NFD and NFC to NFC.
+                    if !diacritics.dialytika && (vowel == GreekVowel::Ι || vowel == GreekVowel::Υ)
+                    {
+                        if let Some(preceding_vowel) = context.preceding_greek_vowel_diacritics() {
+                            if !preceding_vowel.combining.dialytika
+                                && !preceding_vowel.precomposed.dialytika
+                            {
+                                if preceding_vowel.combining.accented {
+                                    diacritics.dialytika = true;
+                                } else {
+                                    precomposed_diacritics.dialytika =
+                                        preceding_vowel.precomposed.accented;
+                                }
+                            }
+                        }
+                    }
+                    // Write the base of the uppercased combining character sequence.
+                    // In most branches this is [`upper_base`], i.e., the uppercase letter with all accents removed.
+                    // In some branches the base has a precomposed diacritic.
+                    // In the case of the Greek disjunctive "or", a combining tonos may also be written.
+                    match vowel {
+                        GreekVowel::Η => {
+                            // The letter η (eta) is allowed to retain a tonos when it is form a single-letter word to distinguish
+                            // the feminine definite article ἡ (monotonic η) from the disjunctive "or" ἤ (monotonic ή).
+                            //
+                            // A lone η with an accent other than the oxia/tonos is not expected,
+                            // so there is no need to special-case the oxia/tonos.
+                            // The ancient ᾖ (exist.PRS.SUBJ.3s) has a iota subscript as well as the circumflex,
+                            // so it would not be given an oxia/tonos under this rule, and the subjunctive is formed with a particle
+                            // (e.g. να είναι) since Byzantine times anyway.
+                            if diacritics.accented
+                                && !context.followed_by_cased_letter(self)
+                                && !context.preceded_by_cased_letter(self)
+                                && !diacritics.ypogegrammeni
+                            {
+                                if precomposed_diacritics.accented {
+                                    sink.write_char('Ή')?;
+                                } else {
+                                    sink.write_char('Η')?;
+                                    sink.write_char(greek_to_me::TONOS)?;
+                                }
+                            } else {
+                                sink.write_char('Η')?;
+                            }
+                        }
+                        GreekVowel::Ι => sink.write_char(if precomposed_diacritics.dialytika {
+                            diacritics.dialytika = false;
+                            'Ϊ'
+                        } else {
+                            vowel.into()
+                        })?,
+                        GreekVowel::Υ => sink.write_char(if precomposed_diacritics.dialytika {
+                            diacritics.dialytika = false;
+                            'Ϋ'
+                        } else {
+                            vowel.into()
+                        })?,
+                        _ => sink.write_char(vowel.into())?,
+                    };
+                    if diacritics.dialytika {
+                        sink.write_char(greek_to_me::DIALYTIKA)?;
+                    }
+                    if precomposed_diacritics.ypogegrammeni {
+                        sink.write_char('Ι')?;
+                    }
+
+                    return Ok(());
+                }
+                // Rho might have breathing marks, we handle it specially
+                // to remove them
+                Some(GreekPrecomposedLetterData::Consonant(true)) => {
+                    sink.write_char(greek_to_me::CAPITAL_RHO)?;
+                    return Ok(());
+                }
+                _ => (),
+            }
+        }
+
+        let data = self.lookup_data(c);
+        if !data.has_exception() {
+            if data.is_relevant_to(kind) {
+                let mapped = c as i32 + data.delta() as i32;
+                // GIGO: delta should be valid
+                let mapped = char::from_u32(mapped as u32).unwrap_or(c);
+                sink.write_char(mapped)
+            } else {
+                sink.write_char(c)
+            }
+        } else {
+            let idx = data.exception_index();
+            let exception = self.exceptions.get(idx);
+            if exception.bits.has_conditional_special() {
+                if let Some(special) = match kind {
+                    MappingKind::Lower => {
+                        self.full_lower_special_case::<IS_TITLE_CONTEXT>(c, context, locale)
+                    }
+                    MappingKind::Fold => self.full_fold_special_case(c, context, locale),
+                    MappingKind::Upper | MappingKind::Title => self
+                        .full_upper_or_title_special_case::<IS_TITLE_CONTEXT>(c, context, locale),
+                } {
+                    return special.write_to(sink);
+                }
+            }
+            if let Some(mapped_string) = exception.get_fullmappings_slot_for_kind(kind) {
+                if !mapped_string.is_empty() {
+                    return sink.write_str(mapped_string);
+                }
+            }
+
+            if kind == MappingKind::Fold && exception.bits.no_simple_case_folding() {
+                return sink.write_char(c);
+            }
+
+            if data.is_relevant_to(kind) {
+                if let Some(simple) = exception.get_simple_case_slot_for(c) {
+                    return sink.write_char(simple);
+                }
+            }
+
+            if let Some(slot_char) = exception.slot_char_for_kind(kind) {
+                sink.write_char(slot_char)
+            } else {
+                sink.write_char(c)
+            }
+        }
+    }
+
+    // These constants are used for hardcoded locale-specific foldings.
+    const I_DOT: &'static str = "\u{69}\u{307}";
+    const J_DOT: &'static str = "\u{6a}\u{307}";
+    const I_OGONEK_DOT: &'static str = "\u{12f}\u{307}";
+    const I_DOT_GRAVE: &'static str = "\u{69}\u{307}\u{300}";
+    const I_DOT_ACUTE: &'static str = "\u{69}\u{307}\u{301}";
+    const I_DOT_TILDE: &'static str = "\u{69}\u{307}\u{303}";
+
+    // Special case folding mappings, hardcoded.
+    // This handles the special Turkic mappings for uppercase I and dotted uppercase I
+    // For non-Turkic languages, this mapping is normally not used.
+    // For Turkic languages (tr, az), this mapping can be used instead of the normal mapping for these characters.
+    fn simple_fold_special_case(&self, c: char, options: FoldOptions) -> char {
+        debug_assert!(c == '\u{49}' || c == '\u{130}');
+        let is_turkic = options.exclude_special_i;
+        match (c, is_turkic) {
+            // Turkic mappings
+            ('\u{49}', true) => '\u{131}', // 0049; T; 0131; # LATIN CAPITAL LETTER I
+            ('\u{130}', true) => '\u{69}', /* 0130; T; 0069; # LATIN CAPITAL LETTER I WITH DOT ABOVE */
+
+            // Default mappings
+            ('\u{49}', false) => '\u{69}', // 0049; C; 0069; # LATIN CAPITAL LETTER I
+
+            // There is no simple case folding for U+130.
+            (c, _) => c,
+        }
+    }
+
+    fn full_lower_special_case<const IS_TITLE_CONTEXT: bool>(
+        &self,
+        c: char,
+        context: ContextIterator,
+        locale: CaseMapLocale,
+    ) -> Option<FullMappingResult> {
+        if locale == CaseMapLocale::Lithuanian {
+            // Lithuanian retains the dot in a lowercase i when followed by accents.
+            // Introduce an explicit dot above when lowercasing capital I's and J's
+            // whenever there are more accents above (of the accents used in
+            // Lithuanian: grave, acute, and tilde above).
+
+            // Check for accents above I, J, and I-with-ogonek.
+            if c == 'I' && context.followed_by_more_above(self) {
+                return Some(FullMappingResult::String(Self::I_DOT));
+            } else if c == 'J' && context.followed_by_more_above(self) {
+                return Some(FullMappingResult::String(Self::J_DOT));
+            } else if c == '\u{12e}' && context.followed_by_more_above(self) {
+                return Some(FullMappingResult::String(Self::I_OGONEK_DOT));
+            }
+
+            // These characters are precomposed with accents above, so we don't
+            // have to look at the context.
+            if c == '\u{cc}' {
+                return Some(FullMappingResult::String(Self::I_DOT_GRAVE));
+            } else if c == '\u{cd}' {
+                return Some(FullMappingResult::String(Self::I_DOT_ACUTE));
+            } else if c == '\u{128}' {
+                return Some(FullMappingResult::String(Self::I_DOT_TILDE));
+            }
+        }
+
+        if locale == CaseMapLocale::Turkish {
+            if c == '\u{130}' {
+                // I and i-dotless; I-dot and i are case pairs in Turkish and Azeri
+                return Some(FullMappingResult::CodePoint('i'));
+            } else if c == '\u{307}' && context.preceded_by_capital_i::<IS_TITLE_CONTEXT>(self) {
+                // When lowercasing, remove dot_above in the sequence I + dot_above,
+                // which will turn into i. This matches the behaviour of the
+                // canonically equivalent I-dot_above.
+                //
+                // In a titlecase context, we do not want to apply this behavior to cases where the I
+                // was at the beginning of the string, as that I and its marks should be handled by the
+                // uppercasing rules (which ignore it, see below)
+
+                return Some(FullMappingResult::Remove);
+            } else if c == 'I' && !context.followed_by_dot_above(self) {
+                // When lowercasing, unless an I is before a dot_above, it turns
+                // into a dotless i.
+                return Some(FullMappingResult::CodePoint('\u{131}'));
+            }
+        }
+
+        if c == '\u{130}' {
+            // Preserve canonical equivalence for I with dot. Turkic is handled above.
+            return Some(FullMappingResult::String(Self::I_DOT));
+        }
+
+        if c == '\u{3a3}'
+            && context.preceded_by_cased_letter(self)
+            && !context.followed_by_cased_letter(self)
+        {
+            // Greek capital sigman maps depending on surrounding cased letters.
+            return Some(FullMappingResult::CodePoint('\u{3c2}'));
+        }
+
+        // No relevant special case mapping. Use a normal mapping.
+        None
+    }
+
+    fn full_upper_or_title_special_case<const IS_TITLE_CONTEXT: bool>(
+        &self,
+        c: char,
+        context: ContextIterator,
+        locale: CaseMapLocale,
+    ) -> Option<FullMappingResult> {
+        if locale == CaseMapLocale::Turkish && c == 'i' {
+            // In Turkic languages, i turns into a dotted capital I.
+            return Some(FullMappingResult::CodePoint('\u{130}'));
+        }
+        if locale == CaseMapLocale::Lithuanian
+            && c == '\u{307}'
+            && context.preceded_by_soft_dotted(self)
+        {
+            // Lithuanian retains the dot in a lowercase i when followed by accents.
+            // Remove dot_above after i with upper or titlecase.
+            return Some(FullMappingResult::Remove);
+        }
+        // ICU4C's non-standard extension for Armenian ligature ech-yiwn.
+        if c == '\u{587}' {
+            return match (locale, IS_TITLE_CONTEXT) {
+                (CaseMapLocale::Armenian, false) => Some(FullMappingResult::String("ԵՎ")),
+                (CaseMapLocale::Armenian, true) => Some(FullMappingResult::String("Եվ")),
+                (_, false) => Some(FullMappingResult::String("ԵՒ")),
+                (_, true) => Some(FullMappingResult::String("Եւ")),
+            };
+        }
+        None
+    }
+
+    fn full_fold_special_case(
+        &self,
+        c: char,
+        _context: ContextIterator,
+        locale: CaseMapLocale,
+    ) -> Option<FullMappingResult> {
+        let is_turkic = locale == CaseMapLocale::Turkish;
+        match (c, is_turkic) {
+            // Turkic mappings
+            ('\u{49}', true) => Some(FullMappingResult::CodePoint('\u{131}')),
+            ('\u{130}', true) => Some(FullMappingResult::CodePoint('\u{69}')),
+
+            // Default mappings
+            ('\u{49}', false) => Some(FullMappingResult::CodePoint('\u{69}')),
+            ('\u{130}', false) => Some(FullMappingResult::String(Self::I_DOT)),
+            (_, _) => None,
+        }
+    }
+    /// IS_TITLE_CONTEXT is true iff the mapping is MappingKind::Title, primarily exists
+    /// to avoid perf impacts on other more common modes of operation
+    ///
+    /// titlecase_tail_casing is only read in IS_TITLE_CONTEXT
+    pub(crate) fn full_helper_writeable<'a: 'data, const IS_TITLE_CONTEXT: bool>(
+        &'a self,
+        src: &'a str,
+        locale: CaseMapLocale,
+        mapping: MappingKind,
+        titlecase_tail_casing: TrailingCase,
+    ) -> FullCaseWriteable<'a, IS_TITLE_CONTEXT> {
+        // Ensure that they are either both true or both false, i.e. an XNOR operation
+        debug_assert!(!(IS_TITLE_CONTEXT ^ (mapping == MappingKind::Title)));
+
+        FullCaseWriteable::<IS_TITLE_CONTEXT> {
+            data: self,
+            src,
+            locale,
+            mapping,
+            titlecase_tail_casing,
+        }
+    }
+
+    /// Adds all simple case mappings and the full case folding for `c` to `set`.
+    /// Also adds special case closure mappings.
+    /// The character itself is not added.
+    /// For example, the mappings
+    /// - for s include long s
+    /// - for sharp s include ss
+    /// - for k include the Kelvin sign
+    pub(crate) fn add_case_closure_to<S: ClosureSink>(&self, c: char, set: &mut S) {
+        // Hardcode the case closure of i and its relatives and ignore the
+        // data file data for these characters.
+        // The Turkic dotless i and dotted I with their case mapping conditions
+        // and case folding option make the related characters behave specially.
+        // This code matches their closure behavior to their case folding behavior.
+        match c {
+            // Regular i and I are in one equivalence class.
+            '\u{49}' => {
+                set.add_char('\u{69}');
+                return;
+            }
+            '\u{69}' => {
+                set.add_char('\u{49}');
+                return;
+            }
+
+            // Dotted I is in a class with <0069 0307> (for canonical equivalence with <0049 0307>)
+            '\u{130}' => {
+                set.add_string(Self::I_DOT);
+                return;
+            }
+
+            // Dotless i is in a class by itself
+            '\u{131}' => {
+                return;
+            }
+
+            _ => {}
+        }
+
+        let data = self.lookup_data(c);
+        if !data.has_exception() {
+            if data.case_type().is_some() {
+                let delta = data.delta() as i32;
+                if delta != 0 {
+                    // Add the one simple case mapping, no matter what type it is.
+                    let codepoint = c as i32 + delta;
+                    // GIGO: delta should be valid
+                    let mapped = char::from_u32(codepoint as u32).unwrap_or(c);
+                    set.add_char(mapped);
+                }
+            }
+            return;
+        }
+
+        // c has exceptions, so there may be multiple simple and/or full case mappings.
+        let idx = data.exception_index();
+        let exception = self.exceptions.get(idx);
+
+        // Add all simple case mappings.
+        for slot in [
+            ExceptionSlot::Lower,
+            ExceptionSlot::Fold,
+            ExceptionSlot::Upper,
+            ExceptionSlot::Title,
+        ] {
+            if let Some(simple) = exception.get_char_slot(slot) {
+                set.add_char(simple);
+            }
+        }
+        if let Some(simple) = exception.get_simple_case_slot_for(c) {
+            set.add_char(simple);
+        }
+
+        exception.add_full_and_closure_mappings(set);
+    }
+
+    /// Maps the string to single code points and adds the associated case closure
+    /// mappings.
+    ///
+    /// (see docs on CaseMapper::add_string_case_closure_to)
+    pub(crate) fn add_string_case_closure_to<S: ClosureSink>(
+        &self,
+        s: &str,
+        set: &mut S,
+        unfold_data: &CaseMapUnfold,
+    ) -> bool {
+        if s.chars().count() <= 1 {
+            // The string is too short to find any match.
+            return false;
+        }
+        match unfold_data.get(s) {
+            Some(closure_string) => {
+                for c in closure_string.chars() {
+                    set.add_char(c);
+                    self.add_case_closure_to(c, set);
+                }
+                true
+            }
+            None => false,
+        }
+    }
+}
+
+// An internal representation of locale. Non-Root values of this
+// enumeration imply that hard-coded special cases exist for this
+// language.
+#[derive(Copy, Clone, Eq, PartialEq, Debug)]
+pub enum CaseMapLocale {
+    Root,
+    Turkish,
+    Lithuanian,
+    Greek,
+    Dutch,
+    Armenian,
+}
+
+impl CaseMapLocale {
+    pub const fn from_langid(langid: &LanguageIdentifier) -> Self {
+        use icu_locale_core::subtags::{language, Language};
+        const TR: Language = language!("tr");
+        const AZ: Language = language!("az");
+        const LT: Language = language!("lt");
+        const EL: Language = language!("el");
+        const NL: Language = language!("nl");
+        const HY: Language = language!("hy");
+        match langid.language {
+            TR | AZ => Self::Turkish,
+            LT => Self::Lithuanian,
+            EL => Self::Greek,
+            NL => Self::Dutch,
+            HY => Self::Armenian,
+            _ => Self::Root,
+        }
+    }
+}
+
+pub enum FullMappingResult<'a> {
+    Remove,
+    CodePoint(char),
+    String(&'a str),
+}
+
+impl FullMappingResult<'_> {
+    #[allow(dead_code)]
+    fn add_to_set<S: ClosureSink>(&self, set: &mut S) {
+        match *self {
+            FullMappingResult::CodePoint(c) => set.add_char(c),
+            FullMappingResult::String(s) => set.add_string(s),
+            FullMappingResult::Remove => {}
+        }
+    }
+}
+
+impl Writeable for FullMappingResult<'_> {
+    fn write_to<W: fmt::Write + ?Sized>(&self, sink: &mut W) -> fmt::Result {
+        match *self {
+            FullMappingResult::CodePoint(c) => sink.write_char(c),
+            FullMappingResult::String(s) => sink.write_str(s),
+            FullMappingResult::Remove => Ok(()),
+        }
+    }
+}
+
+pub(crate) struct ContextIterator<'a> {
+    before: &'a str,
+    after: &'a str,
+}
+
+impl<'a> ContextIterator<'a> {
+    // Returns a context iterator with the characters before
+    // and after the character at a given index, given the preceding
+    // string and the succeeding string including the character itself
+    pub fn new(before: &'a str, char_and_after: &'a str) -> Self {
+        let mut char_and_after = char_and_after.chars();
+        char_and_after.next(); // skip the character itself
+        let after = char_and_after.as_str();
+        Self { before, after }
+    }
+
+    fn add_greek_diacritics(&self, mut diacritics: GreekDiacritics) -> GreekDiacritics {
+        diacritics.consume_greek_diacritics(self.after);
+        diacritics
+    }
+
+    fn preceded_by_greek_letter(&self) -> bool {
+        greek_to_me::preceded_by_greek_letter(self.before)
+    }
+
+    fn preceding_greek_vowel_diacritics(
+        &self,
+    ) -> Option<GreekCombiningCharacterSequenceDiacritics> {
+        greek_to_me::preceding_greek_vowel_diacritics(self.before)
+    }
+
+    fn preceded_by_soft_dotted(&self, mapping: &CaseMap) -> bool {
+        for c in self.before.chars().rev() {
+            match mapping.dot_type(c) {
+                DotType::SoftDotted => return true,
+                DotType::OtherAccent => continue,
+                _ => return false,
+            }
+        }
+        false
+    }
+    /// Checks if the preceding character is a capital I, allowing for non-Above combining characters in between.
+    ///
+    /// If I_MUST_NOT_START_STRING is true, additionally will require that the capital I does not start the string
+    fn preceded_by_capital_i<const I_MUST_NOT_START_STRING: bool>(
+        &self,
+        mapping: &CaseMap,
+    ) -> bool {
+        let mut iter = self.before.chars().rev();
+        while let Some(c) = iter.next() {
+            if c == 'I' {
+                if I_MUST_NOT_START_STRING {
+                    return iter.next().is_some();
+                } else {
+                    return true;
+                }
+            }
+            if mapping.dot_type(c) != DotType::OtherAccent {
+                break;
+            }
+        }
+        false
+    }
+    fn preceded_by_cased_letter(&self, mapping: &CaseMap) -> bool {
+        for c in self.before.chars().rev() {
+            let data = mapping.lookup_data(c);
+            if !data.is_ignorable() {
+                return data.case_type().is_some();
+            }
+        }
+        false
+    }
+    fn followed_by_cased_letter(&self, mapping: &CaseMap) -> bool {
+        for c in self.after.chars() {
+            let data = mapping.lookup_data(c);
+            if !data.is_ignorable() {
+                return data.case_type().is_some();
+            }
+        }
+        false
+    }
+    fn followed_by_more_above(&self, mapping: &CaseMap) -> bool {
+        for c in self.after.chars() {
+            match mapping.dot_type(c) {
+                DotType::Above => return true,
+                DotType::OtherAccent => continue,
+                _ => return false,
+            }
+        }
+        false
+    }
+    fn followed_by_dot_above(&self, mapping: &CaseMap) -> bool {
+        for c in self.after.chars() {
+            if c == '\u{307}' {
+                return true;
+            }
+            if mapping.dot_type(c) != DotType::OtherAccent {
+                return false;
+            }
+        }
+        false
+    }
+
+    /// Checks the preceding and surrounding context of a j or J
+    /// and returns true if it is preceded by an i or I at the start of the string.
+    /// If one has an acute accent,
+    /// both must have the accent for this to return true. No other accents are handled.
+    fn is_dutch_ij_pair_at_beginning(&self, mapping: &CaseMap) -> bool {
+        let mut before = self.before.chars().rev();
+        let mut i_has_acute = false;
+        loop {
+            match before.next() {
+                Some('i') | Some('I') => break,
+                Some('í') | Some('Í') => {
+                    i_has_acute = true;
+                    break;
+                }
+                Some(ACUTE) => i_has_acute = true,
+                _ => return false,
+            }
+        }
+
+        if before.next().is_some() {
+            // not at the beginning of a string, doesn't matter
+            return false;
+        }
+        let mut j_has_acute = false;
+        for c in self.after.chars() {
+            if c == ACUTE {
+                j_has_acute = true;
+                continue;
+            }
+            // We are supposed to check that `j` has no other combining marks aside
+            // from potentially an acute accent. Once we hit the first non-combining mark
+            // we are done.
+            //
+            // ICU4C checks for `gc=Mn` to determine if something is a combining mark,
+            // however this requires extra data (and is the *only* point in the casemapping algorithm
+            // where there is a direct dependency on properties data not mediated by the casemapping data trie).
+            //
+            // Instead, we can check for ccc via dot_type, the same way the rest of the algorithm does.
+            //
+            // See https://unicode-org.atlassian.net/browse/ICU-22429
+            match mapping.dot_type(c) {
+                // Not a combining character; ccc = 0
+                DotType::NoDot | DotType::SoftDotted => break,
+                // found combining character, bail
+                _ => return false,
+            }
+        }
+
+        // either both should have an acute accent, or none. this is an XNOR operation
+        !(j_has_acute ^ i_has_acute)
+    }
+}
diff --git a/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/src/lib.rs b/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/src/lib.rs
index 92d5662..3ac3148 100644
--- a/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/src/lib.rs
+++ b/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/src/lib.rs
@@ -1,9 +1,70 @@
-// 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.
+// This file is part of ICU4X. For terms of use, please see the file
+// called LICENSE at the top level of the ICU4X source tree
+// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
 
-// @generated from third_party/rust/chromium_crates_io/removed_lib.rs
-// by tools/crates/gnrt. Do not edit!
+//! Case mapping for Unicode characters and strings.
+//!
+//! This module is published as its own crate ([`icu_casemap`](https://docs.rs/icu_casemap/latest/icu_casemap/))
+//! and as part of the [`icu`](https://docs.rs/icu/latest/icu/) crate. See the latter for more details on the ICU4X project.
+//!
+//! # Examples
+//!
+//! ```rust
+//! use icu::casemap::CaseMapper;
+//! use icu::locale::langid;
+//!
+//! let cm = CaseMapper::new();
+//!
+//! assert_eq!(
+//!     cm.uppercase_to_string("hello world", &langid!("und")),
+//!     "HELLO WORLD"
+//! );
+//! assert_eq!(
+//!     cm.lowercase_to_string("Γειά σου Κόσμε", &langid!("und")),
+//!     "γειά σου κόσμε"
+//! );
+//! ```
+//!
+//! [`ICU4X`]: ../icu/index.html
 
-// This is an empty crate that has replaced the 'icu_casemap' crate, since
-// it was listed in `resolve.remove_crates` in gnrt_config.toml.
+// https://github.com/unicode-org/icu4x/blob/main/documents/process/boilerplate.md#library-annotations
+#![cfg_attr(not(any(test, doc)), no_std)]
+#![cfg_attr(
+    not(test),
+    deny(
+        clippy::indexing_slicing,
+        clippy::unwrap_used,
+        clippy::expect_used,
+        clippy::panic,
+        clippy::exhaustive_structs,
+        clippy::exhaustive_enums,
+        clippy::trivially_copy_pass_by_ref,
+        missing_debug_implementations,
+    )
+)]
+#![warn(missing_docs)]
+// We're using Greek identifiers here on purpose. These lints can only be disabled at the crate level
+#![allow(confusable_idents, uncommon_codepoints)]
+
+extern crate alloc;
+
+mod casemapper;
+mod closer;
+pub mod provider;
+mod set;
+pub(crate) mod titlecase;
+
+#[doc(hidden)] // testing
+#[allow(clippy::exhaustive_structs, clippy::exhaustive_enums)]
+pub mod greek_to_me;
+mod internals;
+
+pub use casemapper::{CaseMapper, CaseMapperBorrowed};
+pub use closer::{CaseMapCloser, CaseMapCloserBorrowed};
+pub use set::ClosureSink;
+pub use titlecase::{TitlecaseMapper, TitlecaseMapperBorrowed};
+
+/// Options used by types in this crate
+pub mod options {
+    pub use crate::titlecase::{LeadingAdjustment, TitlecaseOptions, TrailingCase};
+}
diff --git a/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/src/provider/data.rs b/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/src/provider/data.rs
new file mode 100644
index 0000000..dc44463
--- /dev/null
+++ b/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/src/provider/data.rs
@@ -0,0 +1,527 @@
+// This file is part of ICU4X. For terms of use, please see the file
+// called LICENSE at the top level of the ICU4X source tree
+// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
+
+//! The primary per-codepoint casefolding data
+
+#[cfg(feature = "datagen")]
+use alloc::collections::BTreeMap;
+use core::num::TryFromIntError;
+use icu_collections::codepointtrie::TrieValue;
+use zerovec::ule::{AsULE, RawBytesULE, UleError, ULE};
+
+/// The case of a Unicode character
+///
+/// <div class="stab unstable">
+/// 🚧 This code is considered unstable; it may change at any time, in breaking or non-breaking ways,
+/// including in SemVer minor releases. While the serde representation of data structs is guaranteed
+/// to be stable, their Rust representation might not be. Use with caution.
+/// </div>
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
+#[cfg_attr(feature = "datagen", derive(serde::Serialize, databake::Bake))]
+#[cfg_attr(feature = "datagen", databake(path = icu_casemap::provider::data))]
+pub enum CaseType {
+    /// Lowercase letter
+    Lower = 1,
+    /// Uppercase letter
+    Upper = 2,
+    /// Titlecase letter
+    Title = 3,
+}
+
+impl CaseType {
+    pub(crate) const CASE_MASK: u16 = 0x3;
+
+    // The casetype is stored in the codepoint trie as two bits.
+    // After masking them to get a value between 0 and 3, this
+    // function converts to `CaseType`.
+    //
+    // Returns `None` for uncased
+    #[inline]
+    pub(crate) fn from_masked_bits(b: u16) -> Option<Self> {
+        debug_assert!(b & Self::CASE_MASK == b);
+        match b {
+            0 => None,
+            1 => Some(CaseType::Lower),
+            2 => Some(CaseType::Upper),
+            _ => Some(CaseType::Title),
+        }
+    }
+}
+
+/// The dot type of a Unicode character. This indicates how dotted
+/// letters (like `i` and `j`) combine with accents placed above the
+/// letter.
+///
+/// <div class="stab unstable">
+/// 🚧 This code is considered unstable; it may change at any time, in breaking or non-breaking ways,
+/// including in SemVer minor releases. While the serde representation of data structs is guaranteed
+/// to be stable, their Rust representation might not be. Use with caution.
+/// </div>
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
+#[cfg_attr(feature = "datagen", derive(serde::Serialize, databake::Bake))]
+#[cfg_attr(feature = "datagen", databake(path = icu_casemap::provider::data))]
+#[derive(Default)]
+pub enum DotType {
+    /// Normal characters with combining class 0
+    #[default]
+    NoDot = 0,
+    /// Soft-dotted characters with combining class 0
+    SoftDotted = 1,
+    /// "Above" accents with combining class 230
+    Above = 2,
+    /// Other accent characters
+    OtherAccent = 3,
+}
+
+impl DotType {
+    pub(crate) const DOT_MASK: u16 = 0x3;
+
+    // The dot type is stored in either the codepoint trie or the
+    // exception table as two bits.  After shifting and masking them
+    // to get a value between 0 and 3, this function converts to
+    // DotType.
+    #[inline]
+    pub(crate) fn from_masked_bits(b: u16) -> Self {
+        debug_assert!(b & Self::DOT_MASK == b);
+        match b {
+            0 => DotType::NoDot,
+            1 => DotType::SoftDotted,
+            2 => DotType::Above,
+            _ => DotType::OtherAccent,
+        }
+    }
+}
+
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+pub(crate) enum MappingKind {
+    Lower = 0,
+    Fold = 1,
+    Upper = 2,
+    Title = 3,
+}
+
+/// Case mapping data associated with a single code point
+///
+/// <div class="stab unstable">
+/// 🚧 This code is considered unstable; it may change at any time, in breaking or non-breaking ways,
+/// including in SemVer minor releases. While the serde representation of data structs is guaranteed
+/// to be stable, their Rust representation might not be. Use with caution.
+/// </div>
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
+#[cfg_attr(feature = "datagen", derive(serde::Serialize, databake::Bake))]
+#[cfg_attr(feature = "datagen", databake(path = icu_casemap::provider::data))]
+pub struct CaseMapData {
+    /// Whether this is default-ignoreable
+    pub ignoreable: bool,
+    /// The rest of the case mapping data
+    pub kind: CaseMapDataKind,
+}
+
+/// A subset of case mapping data associated with a single code point
+///
+/// <div class="stab unstable">
+/// 🚧 This code is considered unstable; it may change at any time, in breaking or non-breaking ways,
+/// including in SemVer minor releases. While the serde representation of data structs is guaranteed
+/// to be stable, their Rust representation might not be. Use with caution.
+/// </div>
+#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
+#[cfg_attr(feature = "datagen", derive(serde::Serialize, databake::Bake))]
+#[cfg_attr(feature = "datagen", databake(path = icu_casemap::provider::data))]
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+pub enum CaseMapDataKind {
+    /// This code point is an exception. Provides the case type of its own case
+    /// and the exception index stored in [`CaseMapExceptions`]
+    ///
+    /// [`CaseMapExceptions`]: crate::provider::exceptions::CaseMapExceptions
+    Exception(Option<CaseType>, u16),
+    /// This code point is uncased, and has the following extra data
+    Uncased(NonExceptionData),
+    /// This code point is cased. We store the extra data, its case type, and a *delta*
+    /// that can be used to get its casemapped codepoint.
+    Delta(NonExceptionData, CaseType, i16),
+}
+
+/// Data that is stored in CaseMapData when it is *not* an exception
+///
+/// <div class="stab unstable">
+/// 🚧 This code is considered unstable; it may change at any time, in breaking or non-breaking ways,
+/// including in SemVer minor releases. While the serde representation of data structs is guaranteed
+/// to be stable, their Rust representation might not be. Use with caution.
+/// </div>
+#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
+#[cfg_attr(feature = "datagen", derive(serde::Serialize, databake::Bake))]
+#[cfg_attr(feature = "datagen", databake(path = icu_casemap::provider::data))]
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+pub struct NonExceptionData {
+    /// Whether or not the type is case-sensitive
+    pub sensitive: bool,
+    /// The "dot type"
+    pub dot_type: DotType,
+}
+
+impl CaseMapData {
+    #[inline]
+    pub(crate) fn case_type(self) -> Option<CaseType> {
+        match self.kind {
+            CaseMapDataKind::Exception(case_type, ..) => case_type,
+            CaseMapDataKind::Delta(_, case_type, _) => Some(case_type),
+            CaseMapDataKind::Uncased(..) => None,
+        }
+    }
+
+    #[inline]
+    pub(crate) fn is_upper_or_title(self) -> bool {
+        match self.case_type() {
+            None | Some(CaseType::Lower) => false,
+            Some(CaseType::Upper) | Some(CaseType::Title) => true,
+        }
+    }
+
+    #[inline]
+    pub(crate) fn is_relevant_to(self, kind: MappingKind) -> bool {
+        match kind {
+            MappingKind::Lower | MappingKind::Fold => self.is_upper_or_title(),
+            MappingKind::Upper | MappingKind::Title => self.case_type() == Some(CaseType::Lower),
+        }
+    }
+
+    #[inline]
+    pub(crate) fn is_ignorable(self) -> bool {
+        self.ignoreable
+    }
+
+    #[inline]
+    pub(crate) fn has_exception(self) -> bool {
+        matches!(self.kind, CaseMapDataKind::Exception(..))
+    }
+
+    // Returns true if this code point is case-sensitive.
+    // only in the non-exception case
+    // This is not currently exposed.
+    #[inline]
+    pub(crate) fn is_sensitive(self) -> bool {
+        match self.kind {
+            CaseMapDataKind::Exception(..) => false,
+            CaseMapDataKind::Delta(ned, ..) => ned.sensitive,
+            CaseMapDataKind::Uncased(ned) => ned.sensitive,
+        }
+    }
+
+    #[inline]
+    pub(crate) fn dot_type(self) -> DotType {
+        match self.kind {
+            CaseMapDataKind::Exception(..) => DotType::NoDot,
+            CaseMapDataKind::Delta(ned, ..) => ned.dot_type,
+            CaseMapDataKind::Uncased(ned) => ned.dot_type,
+        }
+    }
+
+    // The delta between this code point and its upper/lowercase equivalent.
+    // This should only be called for codepoints without exception data.
+    //
+    // Returns 0 for uncased types
+    #[inline]
+    pub(crate) fn delta(self) -> i16 {
+        debug_assert!(!self.has_exception());
+        match self.kind {
+            CaseMapDataKind::Exception(..) => 0,
+            CaseMapDataKind::Delta(.., delta) => delta,
+            CaseMapDataKind::Uncased(..) => 0,
+        }
+    }
+
+    // The index of the exception data for this codepoint in the exception
+    // table. This should only be called for codepoints with exception data.
+    #[inline]
+    pub(crate) fn exception_index(self) -> u16 {
+        debug_assert!(self.has_exception());
+        if let CaseMapDataKind::Exception(_, i) = self.kind {
+            i
+        } else {
+            0
+        }
+    }
+
+    // CaseMapExceptionsBuilder moves the full mapping and closure
+    // strings out of the exception table itself. This means that the
+    // exception index for a code point in ICU4X will be different
+    // from the exception index for the same codepoint in ICU4C. Given
+    // a mapping from old to new, this function updates the exception
+    // index if necessary.
+    #[cfg(feature = "datagen")]
+    pub(crate) fn with_updated_exception(self, updates: &BTreeMap<u16, u16>) -> Self {
+        let kind = if let CaseMapDataKind::Exception(ty, index) = self.kind {
+            if let Some(updated_exception) = updates.get(&index) {
+                CaseMapDataKind::Exception(ty, *updated_exception)
+            } else {
+                self.kind
+            }
+        } else {
+            self.kind
+        };
+
+        Self { kind, ..self }
+    }
+
+    /// Attempt to construct from ICU-format integer
+    #[cfg(any(feature = "datagen", test))]
+    pub(crate) fn try_from_icu_integer(int: u16) -> Result<Self, UleError> {
+        let raw = int.to_unaligned();
+        CaseMapDataULE::validate_bytes(raw.as_bytes())?;
+
+        let this = Self::from_unaligned(CaseMapDataULE(raw));
+        Ok(this)
+    }
+}
+
+impl TrieValue for CaseMapData {
+    type TryFromU32Error = TryFromIntError;
+
+    fn try_from_u32(i: u32) -> Result<Self, Self::TryFromU32Error> {
+        u16::try_from(i).map(|u| AsULE::from_unaligned(CaseMapDataULE(u.to_unaligned())))
+    }
+
+    fn to_u32(self) -> u32 {
+        u32::from(self.to_unaligned().0.as_unsigned_int())
+    }
+}
+
+/// Packed casemappingdata type
+///
+/// Data format, copied from ICU4C casepropsbuilder.cpp:
+///
+/// ```text
+/// Trie data word:
+/// Bits
+/// if(exception) {
+///     15..4   unsigned exception index
+/// } else {
+///     if(not uncased) {
+///         15..7   signed delta to simple case mapping code point
+///                 (add delta to input code point)
+///     } else {
+///         15..7   reserved, 0
+///     }
+///      6..5   0 normal character with cc=0
+///             1 soft-dotted character
+///             2 cc=230
+///             3 other cc
+///             The runtime code relies on these two bits to be adjacent with this encoding.
+/// }
+///     4   case-sensitive
+///     3   exception
+///     2   case-ignorable
+///  1..0   0 uncased
+///         1 lowercase
+///         2 uppercase
+///         3 titlecase
+///         The runtime code relies on the case-ignorable and case type bits 2..0
+///         to be the lowest bits with this encoding.
+/// ```
+///
+/// <div class="stab unstable">
+/// 🚧 This code is considered unstable; it may change at any time, in breaking or non-breaking ways,
+/// including in SemVer minor releases. While the serde representation of data structs is guaranteed
+/// to be stable, their Rust representation might not be. Use with caution.
+/// </div>
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+#[repr(transparent)]
+pub struct CaseMapDataULE(RawBytesULE<2>);
+
+impl CaseMapDataULE {
+    // 1..0 case type
+    const CASE_TYPE_BITS: u16 = 0x3;
+    // 2 case-ignorable
+    const CASE_IGNOREABLE_BIT: u16 = 0x4;
+    // 3 exception
+    const EXCEPTION_BIT: u16 = 0x8;
+    // 4 case-sensitive
+    const CASE_SENSITIVE_BIT: u16 = 0x10;
+    // 15..4 unsigned exception index
+    const EXCEPTION_SHIFT: u16 = 4;
+    // 15..7 signed-delta to simple case mapping code point (or reserved)
+    const DELTA_SHIFT: u16 = 7;
+    // 6..5 dot type
+    const DOT_TYPE_BITS: u16 = 0x60;
+    const DOT_SHIFT: u16 = 5;
+}
+
+/// # Safety
+///
+/// Safety checklist for `ULE`:
+///
+/// 1. The type *must not* include any uninitialized or padding bytes: repr(transparent)
+///    wrapper around ULE type
+/// 2. The type must have an alignment of 1 byte: repr(transparent) wrapper around ULE type
+/// 3. The impl of [`ULE::validate_bytes()`] *must* return an error if the given byte slice
+///    would not represent a valid slice of this type: It does
+/// 4. The impl of [`ULE::validate_bytes()`] *must* return an error if the given byte slice
+///    cannot be used in its entirety (if its length is not a multiple of `size_of::<Self>()`):
+///    it does, due to the RawBytesULE parse call
+/// 5. All other methods *must* be left with their default impl, or else implemented according to
+///    their respective safety guidelines: They have been
+/// 6. The equality invariant is satisfied
+unsafe impl ULE for CaseMapDataULE {
+    fn validate_bytes(bytes: &[u8]) -> Result<(), UleError> {
+        let sixteens = RawBytesULE::<2>::parse_bytes_to_slice(bytes)?;
+
+        for sixteen in sixteens {
+            let sixteen = sixteen.as_unsigned_int();
+            // The type has reserved bits in the
+            // uncased + not exception case
+            if sixteen & Self::EXCEPTION_BIT == 0 {
+                // not an exception
+                if sixteen & Self::CASE_TYPE_BITS == 0 {
+                    // uncased
+                    if sixteen >> Self::DELTA_SHIFT != 0 {
+                        // We have some used bits in the reserved zone!
+                        return Err(UleError::parse::<Self>());
+                    }
+                }
+            }
+        }
+        Ok(())
+    }
+}
+
+impl AsULE for CaseMapData {
+    type ULE = CaseMapDataULE;
+
+    fn from_unaligned(ule: Self::ULE) -> Self {
+        let sixteen = ule.0.as_unsigned_int();
+
+        let ignoreable = (sixteen & CaseMapDataULE::CASE_IGNOREABLE_BIT) != 0;
+        let exception = (sixteen & CaseMapDataULE::EXCEPTION_BIT) != 0;
+
+        let case_type = sixteen & CaseMapDataULE::CASE_TYPE_BITS;
+        let case_type = CaseType::from_masked_bits(case_type);
+        let kind = if exception {
+            // No need to mask first since the exception bits start at 15
+            let exception = sixteen >> CaseMapDataULE::EXCEPTION_SHIFT;
+            CaseMapDataKind::Exception(case_type, exception)
+        } else {
+            let dot_type = (sixteen & CaseMapDataULE::DOT_TYPE_BITS) >> CaseMapDataULE::DOT_SHIFT;
+            let dot_type = DotType::from_masked_bits(dot_type);
+            let sensitive = (sixteen & CaseMapDataULE::CASE_SENSITIVE_BIT) != 0;
+            let ned = NonExceptionData {
+                dot_type,
+                sensitive,
+            };
+            if let Some(case_type) = case_type {
+                // no need to mask first since the delta bits start at 15
+                // We can also cast as i16 first so we do not have to
+                // sign-extend later
+                let delta = (sixteen as i16) >> CaseMapDataULE::DELTA_SHIFT;
+                CaseMapDataKind::Delta(ned, case_type, delta)
+            } else {
+                CaseMapDataKind::Uncased(ned)
+            }
+        };
+        CaseMapData { ignoreable, kind }
+    }
+
+    fn to_unaligned(self) -> Self::ULE {
+        let mut sixteen = 0;
+        if self.ignoreable {
+            sixteen |= CaseMapDataULE::CASE_IGNOREABLE_BIT;
+        }
+        match self.kind {
+            CaseMapDataKind::Exception(case_type, e) => {
+                sixteen |= CaseMapDataULE::EXCEPTION_BIT;
+                sixteen |= e << CaseMapDataULE::EXCEPTION_SHIFT;
+                sixteen |= case_type.map(|c| c as u16).unwrap_or(0);
+            }
+            CaseMapDataKind::Uncased(ned) => {
+                sixteen |= (ned.dot_type as u16) << CaseMapDataULE::DOT_SHIFT;
+                if ned.sensitive {
+                    sixteen |= CaseMapDataULE::CASE_SENSITIVE_BIT;
+                }
+                // Remaining bytes are left at zero
+                // case_type is Uncased (0)
+            }
+            CaseMapDataKind::Delta(ned, case_type, delta) => {
+                // First shift (which keeps the signedness), then cast to the
+                // right type
+                sixteen |= (delta << CaseMapDataULE::DELTA_SHIFT) as u16;
+                sixteen |= (ned.dot_type as u16) << CaseMapDataULE::DOT_SHIFT;
+                if ned.sensitive {
+                    sixteen |= CaseMapDataULE::CASE_SENSITIVE_BIT;
+                }
+                sixteen |= case_type as u16;
+            }
+        }
+        CaseMapDataULE(sixteen.to_unaligned())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_roundtrip() {
+        const TESTCASES: &[CaseMapData] = &[
+            CaseMapData {
+                ignoreable: true,
+                kind: CaseMapDataKind::Exception(Some(CaseType::Title), 923),
+            },
+            CaseMapData {
+                ignoreable: false,
+                kind: CaseMapDataKind::Exception(None, 923),
+            },
+            CaseMapData {
+                ignoreable: true,
+                kind: CaseMapDataKind::Delta(
+                    NonExceptionData {
+                        sensitive: true,
+                        dot_type: DotType::SoftDotted,
+                    },
+                    CaseType::Upper,
+                    50,
+                ),
+            },
+            CaseMapData {
+                ignoreable: false,
+                kind: CaseMapDataKind::Delta(
+                    NonExceptionData {
+                        sensitive: true,
+                        dot_type: DotType::SoftDotted,
+                    },
+                    CaseType::Upper,
+                    -50,
+                ),
+            },
+            CaseMapData {
+                ignoreable: false,
+                kind: CaseMapDataKind::Uncased(NonExceptionData {
+                    sensitive: false,
+                    dot_type: DotType::SoftDotted,
+                }),
+            },
+        ];
+
+        for case in TESTCASES {
+            let ule = case.to_unaligned();
+            let roundtrip = CaseMapData::from_unaligned(ule);
+            assert_eq!(*case, roundtrip);
+            let integer = ule.0.as_unsigned_int();
+            let roundtrip2 = CaseMapData::try_from_icu_integer(integer).unwrap();
+            assert_eq!(*case, roundtrip2);
+        }
+    }
+    #[test]
+    fn test_integer_roundtrip() {
+        // Buggy roundtrip cases go here
+        fn test_single_integer(int: u16) {
+            let cmd = CaseMapData::try_from_icu_integer(int).unwrap();
+            assert_eq!(int, cmd.to_unaligned().0.as_unsigned_int())
+        }
+
+        test_single_integer(84);
+        test_single_integer(2503);
+    }
+}
diff --git a/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/src/provider/exception_helpers.rs b/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/src/provider/exception_helpers.rs
new file mode 100644
index 0000000..5406d7a
--- /dev/null
+++ b/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/src/provider/exception_helpers.rs
@@ -0,0 +1,279 @@
+// This file is part of ICU4X. For terms of use, please see the file
+// called LICENSE at the top level of the ICU4X source tree
+// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
+
+//! This module contains various types for the header part of casemapping exception data
+//!
+//! This is both used in datagen to decode ICU4C's data, and natively in ICU4X's
+//! own data model.
+//!
+//! [`ExceptionBits`] is the bag of bits associated with exceptions, and [`SlotPresence`]
+//! marks the presence or absence of various "slots" in a given exception.
+//!
+//! The `exceptions_builder` module of this crate handles decoding ICU4C data using the exception
+//! header, and [`crate::provider::exceptions`] handles.
+
+use crate::provider::data::{DotType, MappingKind};
+use zerovec::ule::{AsULE, ULE};
+
+/// A bunch of bits associated with each exception.
+///
+/// <div class="stab unstable">
+/// 🚧 This code is considered unstable; it may change at any time, in breaking or non-breaking ways,
+/// including in SemVer minor releases. While the serde representation of data structs is guaranteed
+/// to be stable, their Rust representation might not be. Use with caution.
+/// </div>
+#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)]
+#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
+#[cfg_attr(feature = "datagen", derive(serde::Serialize))]
+pub struct ExceptionBits {
+    /// Whether or not the slots are double-width.
+    ///
+    /// Unused in ICU4X
+    pub double_width_slots: bool,
+    /// There is no simple casefolding, even if there is a simple lowercase mapping
+    pub no_simple_case_folding: bool,
+    /// The delta stored in the `Delta` slot is negative
+    pub negative_delta: bool,
+    /// If the character is case sensitive
+    pub is_sensitive: bool,
+    /// The dot type of the character
+    pub dot_type: DotType,
+    /// If the character has conditional special casing
+    pub has_conditional_special: bool,
+    /// If the character has conditional case folding
+    pub has_conditional_fold: bool,
+}
+
+impl ExceptionBits {
+    /// Extract from the upper half of an ICU4C-format u16
+    pub(crate) fn from_integer(int: u8) -> Self {
+        let ule = ExceptionBitsULE(int);
+        let double_width_slots = ule.double_width_slots();
+        let no_simple_case_folding = ule.no_simple_case_folding();
+        let negative_delta = ule.negative_delta();
+        let is_sensitive = ule.is_sensitive();
+        let has_conditional_special = ule.has_conditional_special();
+        let has_conditional_fold = ule.has_conditional_fold();
+        let dot_type = ule.dot_type();
+
+        Self {
+            double_width_slots,
+            no_simple_case_folding,
+            negative_delta,
+            is_sensitive,
+            dot_type,
+            has_conditional_special,
+            has_conditional_fold,
+        }
+    }
+
+    /// Convert to an ICU4C-format upper half of u16
+    pub(crate) fn to_integer(self) -> u8 {
+        let mut int = 0;
+        let dot_data = (self.dot_type as u8) << ExceptionBitsULE::DOT_SHIFT;
+        int |= dot_data;
+
+        if self.double_width_slots {
+            int |= ExceptionBitsULE::DOUBLE_SLOTS_FLAG
+        }
+        if self.no_simple_case_folding {
+            int |= ExceptionBitsULE::NO_SIMPLE_CASE_FOLDING_FLAG
+        }
+        if self.negative_delta {
+            int |= ExceptionBitsULE::NEGATIVE_DELTA_FLAG
+        }
+        if self.is_sensitive {
+            int |= ExceptionBitsULE::SENSITIVE_FLAG
+        }
+        if self.has_conditional_special {
+            int |= ExceptionBitsULE::CONDITIONAL_SPECIAL_FLAG
+        }
+        if self.has_conditional_fold {
+            int |= ExceptionBitsULE::CONDITIONAL_FOLD_FLAG
+        }
+        int
+    }
+}
+
+/// Packed slot presence marker
+///
+/// All bits are valid, though bit 4 is unused and reserved
+///
+/// Bits:
+///
+/// ```text
+///               0: Lowercase mapping (code point)
+///               1: Case folding (code point)
+///               2: Uppercase mapping (code point)
+///               3: Titlecase mapping (code point)
+///               4: Delta to simple case mapping (code point) (sign stored separately)
+///               5: RESERVED
+///               6: Closure mappings (string; see below)
+///               7: Full mappings (strings; see below)
+/// ```
+///
+/// <div class="stab unstable">
+/// 🚧 This code is considered unstable; it may change at any time, in breaking or non-breaking ways,
+/// including in SemVer minor releases. While the serde representation of data structs is guaranteed
+/// to be stable, their Rust representation might not be. Use with caution.
+/// </div>
+#[derive(Copy, Clone, PartialEq, Eq, ULE, Debug, Default)]
+#[repr(transparent)]
+#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
+#[cfg_attr(feature = "datagen", derive(serde::Serialize))]
+pub struct SlotPresence(pub u8);
+
+impl SlotPresence {
+    pub(crate) fn add_slot(&mut self, slot: ExceptionSlot) {
+        self.0 |= 1 << slot as u8;
+    }
+    pub(crate) fn has_slot(self, slot: ExceptionSlot) -> bool {
+        let bit = 1 << (slot as u8);
+        self.0 & bit != 0
+    }
+}
+
+/// The bitflags on an exception header.
+///
+/// Format from icu4c, documented in casepropsbuilder.cpp, shifted 8 bits since ICU4C has this packed
+/// alongside a SlotPresence
+///
+/// ```text
+///            0  Double-width slots. If set, then each optional slot is stored as two
+///               elements of the array (high and low halves of 32-bit values) instead of
+///               a single element.
+///            1  Has no simple case folding, even if there is a simple lowercase mapping
+///           2  The value in the delta slot is negative
+///           3  Is case-sensitive (not exposed)
+///       4..5  Dot type
+///           6  Has conditional special casing
+///           7  Has conditional case folding
+/// ```
+///
+/// All bits are valid, though in ICU4X data bits 0 and 2 are not used
+///
+/// <div class="stab unstable">
+/// 🚧 This code is considered unstable; it may change at any time, in breaking or non-breaking ways,
+/// including in SemVer minor releases. While the serde representation of data structs is guaranteed
+/// to be stable, their Rust representation might not be. Use with caution.
+/// </div>
+#[derive(Copy, Clone, PartialEq, Eq, ULE, Debug)]
+#[repr(transparent)]
+pub struct ExceptionBitsULE(pub u8);
+
+impl ExceptionBitsULE {
+    const DOUBLE_SLOTS_FLAG: u8 = 0x1;
+
+    const NO_SIMPLE_CASE_FOLDING_FLAG: u8 = 0x2;
+    const NEGATIVE_DELTA_FLAG: u8 = 0x4;
+    const SENSITIVE_FLAG: u8 = 0x8;
+
+    const DOT_SHIFT: u8 = 4;
+
+    const CONDITIONAL_SPECIAL_FLAG: u8 = 0x40;
+    const CONDITIONAL_FOLD_FLAG: u8 = 0x80;
+}
+
+impl ExceptionBitsULE {
+    /// Whether or not the slots are double-width.
+    ///
+    /// Unused in ICU4X
+    pub fn double_width_slots(self) -> bool {
+        self.0 & Self::DOUBLE_SLOTS_FLAG != 0
+    }
+
+    /// There is no simple casefolding, even if there is a simple lowercase mapping
+    pub fn no_simple_case_folding(self) -> bool {
+        self.0 & Self::NO_SIMPLE_CASE_FOLDING_FLAG != 0
+    }
+
+    /// The delta stored in the `Delta` slot is negative
+    pub fn negative_delta(self) -> bool {
+        self.0 & Self::NEGATIVE_DELTA_FLAG != 0
+    }
+
+    /// If the character is case sensitive
+    pub fn is_sensitive(self) -> bool {
+        self.0 & Self::SENSITIVE_FLAG != 0
+    }
+
+    /// If the character has conditional special casing
+    pub fn has_conditional_special(self) -> bool {
+        self.0 & Self::CONDITIONAL_SPECIAL_FLAG != 0
+    }
+
+    /// If the character has conditional case folding
+    pub fn has_conditional_fold(self) -> bool {
+        self.0 & Self::CONDITIONAL_FOLD_FLAG != 0
+    }
+
+    /// The dot type of the character
+    pub fn dot_type(self) -> DotType {
+        DotType::from_masked_bits((u16::from(self.0 >> Self::DOT_SHIFT)) & DotType::DOT_MASK)
+    }
+}
+
+impl AsULE for ExceptionBits {
+    type ULE = ExceptionBitsULE;
+    fn from_unaligned(u: ExceptionBitsULE) -> Self {
+        ExceptionBits::from_integer(u.0)
+    }
+
+    fn to_unaligned(self) -> ExceptionBitsULE {
+        ExceptionBitsULE(self.to_integer())
+    }
+}
+
+impl AsULE for SlotPresence {
+    type ULE = SlotPresence;
+    fn from_unaligned(u: Self) -> Self {
+        u
+    }
+
+    fn to_unaligned(self) -> Self {
+        self
+    }
+}
+
+/// The different slots that may be present in slot-based exception data
+///
+/// <div class="stab unstable">
+/// 🚧 This code is considered unstable; it may change at any time, in breaking or non-breaking ways,
+/// including in SemVer minor releases. While the serde representation of data structs is guaranteed
+/// to be stable, their Rust representation might not be. Use with caution.
+/// </div>
+#[derive(Copy, Clone, Debug, PartialOrd, Ord, PartialEq, Eq)]
+pub(crate) enum ExceptionSlot {
+    /// Lowercase mapping
+    Lower = 0,
+    /// Case folding
+    Fold = 1,
+    /// Uppercase mapping
+    Upper = 2,
+    /// Titlecase mapping
+    Title = 3,
+    /// The delta to the simple case folding
+    Delta = 4,
+    // Slot 5 is reserved
+    /// The closure set
+    Closure = 6,
+    /// The four full-mappings
+    FullMappings = 7,
+}
+
+impl ExceptionSlot {
+    /// Where the string slots begin
+    pub(crate) const STRING_SLOTS_START: Self = Self::Closure;
+}
+
+impl From<MappingKind> for ExceptionSlot {
+    fn from(full: MappingKind) -> Self {
+        match full {
+            MappingKind::Lower => Self::Lower,
+            MappingKind::Fold => Self::Fold,
+            MappingKind::Upper => Self::Upper,
+            MappingKind::Title => Self::Title,
+        }
+    }
+}
diff --git a/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/src/provider/exceptions.rs b/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/src/provider/exceptions.rs
new file mode 100644
index 0000000..f1e43efcd
--- /dev/null
+++ b/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/src/provider/exceptions.rs
@@ -0,0 +1,566 @@
+// This file is part of ICU4X. For terms of use, please see the file
+// called LICENSE at the top level of the ICU4X source tree
+// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
+
+//! This is the main module pertaining to casemapping exceptions.
+//!
+//! A single exception is represented by the [`Exception`] type and its ULE equivalent.
+//!
+//! The storage format is complicated (and documented on [`Exception`]), but the data format is
+//! represented equally by [`DecodedException`], which is more human-readable.
+use icu_provider::prelude::*;
+
+use super::data::MappingKind;
+use super::exception_helpers::{ExceptionBits, ExceptionSlot, SlotPresence};
+use crate::set::ClosureSink;
+use alloc::borrow::Cow;
+use core::fmt;
+#[cfg(any(feature = "serde", feature = "datagen"))]
+use core::ops::Range;
+use core::ptr;
+use zerovec::ule::AsULE;
+use zerovec::VarZeroVec;
+
+const SURROGATES_START: u32 = 0xD800;
+const SURROGATES_LEN: u32 = 0xDFFF - SURROGATES_START + 1;
+
+/// This represents case mapping exceptions that can't be represented as a delta applied to
+/// the original code point. The codepoint
+/// trie in CaseMapper stores indices into this VarZeroVec.
+///
+/// <div class="stab unstable">
+/// 🚧 This code is considered unstable; it may change at any time, in breaking or non-breaking ways,
+/// including in SemVer minor releases. While the serde representation of data structs is guaranteed
+/// to be stable, their Rust representation might not be. Use with caution.
+/// </div>
+#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
+#[cfg_attr(feature = "datagen", derive(serde::Serialize, databake::Bake))]
+#[cfg_attr(feature = "datagen", databake(path = icu_casemap::provider::exceptions))]
+#[derive(Debug, Eq, PartialEq, Clone, yoke::Yokeable, zerofrom::ZeroFrom)]
+pub struct CaseMapExceptions<'data> {
+    #[cfg_attr(feature = "serde", serde(borrow))]
+    /// The list of exceptions
+    pub exceptions: VarZeroVec<'data, ExceptionULE>,
+}
+
+impl CaseMapExceptions<'_> {
+    /// Obtain the exception at index `idx`. Will
+    /// return a default value if not present (GIGO behavior),
+    /// as these indices should come from a paired CaseMapData object
+    ///
+    /// Will also panic in debug mode
+    pub fn get(&self, idx: u16) -> &ExceptionULE {
+        let exception = self.exceptions.get(idx.into());
+        debug_assert!(exception.is_some());
+
+        exception.unwrap_or(ExceptionULE::empty_exception())
+    }
+
+    #[cfg(any(feature = "serde", feature = "datagen"))]
+    pub(crate) fn validate(&self) -> Result<Range<u16>, &'static str> {
+        for exception in self.exceptions.iter() {
+            exception.validate()?;
+        }
+        u16::try_from(self.exceptions.len())
+            .map_err(|_| "Too many exceptions")
+            .map(|l| 0..l)
+    }
+}
+/// A type representing the wire format of `Exception`. The data contained is
+/// equivalently represented by [`DecodedException`].
+///
+/// This type is itself not used that much, most of its relevant methods live
+/// on [`ExceptionULE`].
+///
+/// The `bits` contain supplementary data, whereas
+/// `slot_presence` marks te presence of various extra data
+/// in the `data` field.
+///
+/// The `data` field is not validated to contain all of this data,
+/// this type will have GIGO behavior when constructed with invalid `data`.
+///
+/// The format of `data` is documented on the field
+///
+/// <div class="stab unstable">
+/// 🚧 This code is considered unstable; it may change at any time, in breaking or non-breaking ways,
+/// including in SemVer minor releases. While the serde representation of data structs is guaranteed
+/// to be stable, their Rust representation might not be. Use with caution.
+/// </div>
+#[zerovec::make_varule(ExceptionULE)]
+#[derive(PartialEq, Eq, Clone, Default, Debug)]
+#[zerovec::skip_derive(Ord)]
+#[cfg_attr(
+    feature = "serde",
+    derive(serde::Deserialize),
+    zerovec::derive(Deserialize)
+)]
+#[cfg_attr(
+    feature = "datagen",
+    derive(serde::Serialize),
+    zerovec::derive(Serialize)
+)]
+pub struct Exception<'a> {
+    /// The various bit based exception data associated with this.
+    ///
+    /// Format: Just a u8 of bitflags, some flags unused. See [`ExceptionBits`] and its ULE type for more.
+    pub bits: ExceptionBits,
+    /// Which slots are present in `data`.
+    ///
+    /// Format: a u8 of bitflags
+    pub slot_presence: SlotPresence,
+    /// Format : `[char slots] [optional closure length] [ closure slot ] [ full mappings data ]`
+    ///
+    /// For each set SlotPresence bit, except for the two stringy slots (Closure/FullMapping),
+    /// this will have one entry in the string, packed together.
+    ///
+    /// Note that the simple_case delta is stored as a u32 normalized to a `char`, where u32s
+    /// which are from or beyond the surrogate range 0xD800-0xDFFF are stored as chars
+    /// starting from 0xE000. The sign is stored in bits.negative_delta.
+    ///
+    /// If both Closure/FullMapping are present, the next char will be the length of the closure slot,
+    /// bisecting the rest of the data.
+    /// If only one is present, the rest of the data represents that slot.
+    ///
+    /// The closure slot simply represents one string. The full-mappings slot represents four strings,
+    /// packed in a way similar to VarZeroVec, in the following format:
+    /// `i1 i2 i3 [ str0 ] [ str1 ] [ str2 ] [ str3 ]`
+    ///
+    /// where `i1 i2 i3` are the indices of the relevant mappings string. The strings are stored in
+    /// the order corresponding to the MappingKind enum.
+    pub data: Cow<'a, str>,
+}
+
+impl ExceptionULE {
+    #[inline]
+    fn empty_exception() -> &'static Self {
+        static EMPTY_BYTES: &[u8] = &[0, 0];
+        // Safety:
+        // ExceptionULE is a packed DST with `(u8, u8, unsized)` fields. All bit patterns are valid for the two u8s
+        //
+        // An "empty" one can be constructed from a slice of two u8s
+        unsafe {
+            let slice: *const [u8] = ptr::slice_from_raw_parts(EMPTY_BYTES.as_ptr(), 0);
+            &*(slice as *const Self)
+        }
+    }
+    pub(crate) fn has_slot(&self, slot: ExceptionSlot) -> bool {
+        self.slot_presence.has_slot(slot)
+    }
+    /// Obtain a `char` slot, if occupied. If `slot` represents a string slot,
+    /// will return `None`
+    pub(crate) fn get_char_slot(&self, slot: ExceptionSlot) -> Option<char> {
+        if slot >= ExceptionSlot::STRING_SLOTS_START {
+            return None;
+        }
+        let bit = 1 << (slot as u8);
+        // check if slot is occupied
+        if self.slot_presence.0 & bit == 0 {
+            return None;
+        }
+
+        let previous_slot_mask = bit - 1;
+        let previous_slots = self.slot_presence.0 & previous_slot_mask;
+        let slot_num = previous_slots.count_ones() as usize;
+        self.data.chars().nth(slot_num)
+    }
+
+    /// Get the `simple_case` delta (i.e. the `delta` slot), given the character
+    /// this data belongs to.
+    ///
+    /// Normalizes the delta from char-format to u32 format
+    ///
+    /// Does *not* handle the sign of the delta; see self.bits.negative_delta
+    fn get_simple_case_delta(&self) -> Option<u32> {
+        let delta_ch = self.get_char_slot(ExceptionSlot::Delta)?;
+        let mut delta = u32::from(delta_ch);
+        // We "fill in" the surrogates range by offsetting deltas greater than it
+        if delta >= SURROGATES_START {
+            delta -= SURROGATES_LEN;
+        }
+        Some(delta)
+    }
+
+    /// Get the `simple_case` value (i.e. the `delta` slot), given the character
+    /// this data belongs to.
+    ///
+    /// The data is stored as a delta so the character must be provided.
+    ///
+    /// The data cannot be stored directly as a character because the trie is more
+    /// compact with adjacent characters sharing deltas.
+    pub(crate) fn get_simple_case_slot_for(&self, ch: char) -> Option<char> {
+        let delta = self.get_simple_case_delta()?;
+        let mut delta = i32::try_from(delta).ok()?;
+        if self.bits.negative_delta() {
+            delta = -delta;
+        }
+
+        let new_ch = i32::try_from(u32::from(ch)).ok()? + delta;
+
+        char::try_from(u32::try_from(new_ch).ok()?).ok()
+    }
+
+    /// Returns *all* the data in the closure/full slots, including length metadata
+    fn get_stringy_data(&self) -> Option<&str> {
+        const CHAR_MASK: u8 = (1 << ExceptionSlot::STRING_SLOTS_START as u8) - 1;
+        let char_slot_count = (self.slot_presence.0 & CHAR_MASK).count_ones() as usize;
+        let mut chars = self.data.chars();
+        for _ in 0..char_slot_count {
+            let res = chars.next();
+            res?;
+        }
+        Some(chars.as_str())
+    }
+
+    /// Returns a single stringy slot, either ExceptionSlot::Closure
+    /// or ExceptionSlot::FullMappings.
+    fn get_stringy_slot(&self, slot: ExceptionSlot) -> Option<&str> {
+        debug_assert!(slot == ExceptionSlot::Closure || slot == ExceptionSlot::FullMappings);
+        let other_slot = if slot == ExceptionSlot::Closure {
+            ExceptionSlot::FullMappings
+        } else {
+            ExceptionSlot::Closure
+        };
+        if !self.slot_presence.has_slot(slot) {
+            return None;
+        }
+        let stringy_data = self.get_stringy_data()?;
+
+        if self.slot_presence.has_slot(other_slot) {
+            // both stringy slots are used, we need a length
+            let mut chars = stringy_data.chars();
+            // GIGO: to have two strings there must be a length, if not present return None
+            let length_char = chars.next()?;
+
+            let length = usize::try_from(u32::from(length_char)).unwrap_or(0);
+            // The length indexes into the string after the first char
+            let remaining_slice = chars.as_str();
+            // GIGO: will return none if there wasn't enough space in this slot
+            if slot == ExceptionSlot::Closure {
+                remaining_slice.get(0..length)
+            } else {
+                remaining_slice.get(length..)
+            }
+        } else {
+            // only a single stringy slot, there is no length stored
+            Some(stringy_data)
+        }
+    }
+
+    /// Get the data behind the `closure` slot
+    pub(crate) fn get_closure_slot(&self) -> Option<&str> {
+        self.get_stringy_slot(ExceptionSlot::Closure)
+    }
+
+    /// Get all the slot data for the FullMappings slot
+    ///
+    /// This needs to be further segmented into four based on length metadata
+    fn get_fullmappings_slot_data(&self) -> Option<&str> {
+        self.get_stringy_slot(ExceptionSlot::FullMappings)
+    }
+
+    /// Get a specific FullMappings slot value
+    pub(crate) fn get_fullmappings_slot_for_kind(&self, kind: MappingKind) -> Option<&str> {
+        let data = self.get_fullmappings_slot_data()?;
+
+        let mut chars = data.chars();
+        // GIGO: must have three index strings, else return None
+        let i1 = usize::try_from(u32::from(chars.next()?)).ok()?;
+        let i2 = usize::try_from(u32::from(chars.next()?)).ok()?;
+        let i3 = usize::try_from(u32::from(chars.next()?)).ok()?;
+        let remaining_slice = chars.as_str();
+        // GIGO: if the indices are wrong, return None
+        match kind {
+            MappingKind::Lower => remaining_slice.get(..i1),
+            MappingKind::Fold => remaining_slice.get(i1..i2),
+            MappingKind::Upper => remaining_slice.get(i2..i3),
+            MappingKind::Title => remaining_slice.get(i3..),
+        }
+    }
+
+    // convenience function that lets us use the ? operator
+    fn get_all_fullmapping_slots(&self) -> Option<[Cow<'_, str>; 4]> {
+        Some([
+            self.get_fullmappings_slot_for_kind(MappingKind::Lower)?
+                .into(),
+            self.get_fullmappings_slot_for_kind(MappingKind::Fold)?
+                .into(),
+            self.get_fullmappings_slot_for_kind(MappingKind::Upper)?
+                .into(),
+            self.get_fullmappings_slot_for_kind(MappingKind::Title)?
+                .into(),
+        ])
+    }
+
+    // Given a mapping kind, returns the character for that kind, if it exists. Fold falls
+    // back to Lower; Title falls back to Upper.
+    #[inline]
+    pub(crate) fn slot_char_for_kind(&self, kind: MappingKind) -> Option<char> {
+        match kind {
+            MappingKind::Lower | MappingKind::Upper => self.get_char_slot(kind.into()),
+            MappingKind::Fold => self
+                .get_char_slot(ExceptionSlot::Fold)
+                .or_else(|| self.get_char_slot(ExceptionSlot::Lower)),
+            MappingKind::Title => self
+                .get_char_slot(ExceptionSlot::Title)
+                .or_else(|| self.get_char_slot(ExceptionSlot::Upper)),
+        }
+    }
+
+    pub(crate) fn add_full_and_closure_mappings<S: ClosureSink>(&self, set: &mut S) {
+        if let Some(full) = self.get_fullmappings_slot_for_kind(MappingKind::Fold) {
+            if !full.is_empty() {
+                set.add_string(full);
+            }
+        };
+        if let Some(closure) = self.get_closure_slot() {
+            for c in closure.chars() {
+                set.add_char(c);
+            }
+        };
+    }
+
+    /// Extract all the data out into a structured form
+    ///
+    /// Useful for serialization and debugging
+    pub fn decode(&self) -> DecodedException<'_> {
+        // Potential future optimization: This can
+        // directly access each bit one after the other and iterate the string
+        // which avoids recomputing slot offsets over and over again.
+        //
+        // If we're doing so we may wish to retain this older impl so that we can still roundtrip test
+        let bits = self.bits;
+        let lowercase = self.get_char_slot(ExceptionSlot::Lower);
+        let casefold = self.get_char_slot(ExceptionSlot::Fold);
+        let uppercase = self.get_char_slot(ExceptionSlot::Upper);
+        let titlecase = self.get_char_slot(ExceptionSlot::Title);
+        let simple_case_delta = self.get_simple_case_delta();
+        let closure = self.get_closure_slot().map(Into::into);
+        let full = self.get_all_fullmapping_slots();
+
+        DecodedException {
+            bits: ExceptionBits::from_unaligned(bits),
+            lowercase,
+            casefold,
+            uppercase,
+            titlecase,
+            simple_case_delta,
+            closure,
+            full,
+        }
+    }
+
+    #[cfg(any(feature = "serde", feature = "datagen"))]
+    pub(crate) fn validate(&self) -> Result<(), &'static str> {
+        // check that ICU4C specific fields are not set
+        // check that there is enough space for all the offsets
+        if self.bits.double_width_slots() {
+            return Err("double-width-slots should not be used in ICU4C");
+        }
+
+        // just run all of the slot getters at once and then check
+        let decoded = self.decode();
+
+        for (slot, decoded_slot) in [
+            (ExceptionSlot::Lower, &decoded.lowercase),
+            (ExceptionSlot::Fold, &decoded.casefold),
+            (ExceptionSlot::Upper, &decoded.uppercase),
+            (ExceptionSlot::Title, &decoded.titlecase),
+        ] {
+            if self.has_slot(slot) && decoded_slot.is_none() {
+                // decoding hit GIGO behavior, oops!
+                return Err("Slot decoding failed");
+            }
+        }
+        if self.has_slot(ExceptionSlot::Delta) && decoded.simple_case_delta.is_none() {
+            // decoding hit GIGO behavior, oops!
+            return Err("Slot decoding failed");
+        }
+
+        if self.has_slot(ExceptionSlot::Closure) && decoded.closure.is_none() {
+            return Err("Slot decoding failed");
+        }
+
+        if self.has_slot(ExceptionSlot::FullMappings) {
+            if decoded.full.is_some() {
+                let data = self
+                    .get_fullmappings_slot_data()
+                    .ok_or("fullmappings slot doesn't parse")?;
+                let mut chars = data.chars();
+                let i1 = u32::from(chars.next().ok_or("fullmappings string too small")?);
+                let i2 = u32::from(chars.next().ok_or("fullmappings string too small")?);
+                let i3 = u32::from(chars.next().ok_or("fullmappings string too small")?);
+
+                if i2 < i1 || i3 < i2 {
+                    return Err("fullmappings string contains non-sequential indices");
+                }
+                let rest = chars.as_str();
+                let len = u32::try_from(rest.len()).map_err(|_| "len too large for u32")?;
+
+                if i1 > len || i2 > len || i3 > len {
+                    return Err("fullmappings string contains out-of-bounds indices");
+                }
+            } else {
+                return Err("Slot decoding failed");
+            }
+        }
+
+        Ok(())
+    }
+}
+
+impl fmt::Debug for ExceptionULE {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        self.decode().fmt(f)
+    }
+}
+
+/// A decoded [`Exception`] type, with all of the data parsed out into
+/// separate fields.
+///
+/// <div class="stab unstable">
+/// 🚧 This code is considered unstable; it may change at any time, in breaking or non-breaking ways,
+/// including in SemVer minor releases. While the serde representation of data structs is guaranteed
+/// to be stable, their Rust representation might not be. Use with caution.
+/// </div>
+#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
+#[cfg_attr(feature = "datagen", derive(serde::Serialize))]
+#[derive(Debug, Clone, PartialEq, Eq, Default)]
+pub struct DecodedException<'a> {
+    /// The various bit-based data associated with this exception
+    pub bits: ExceptionBits,
+    /// Lowercase mapping
+    pub lowercase: Option<char>,
+    /// Case folding
+    pub casefold: Option<char>,
+    /// Uppercase mapping
+    pub uppercase: Option<char>,
+    /// Titlecase mapping
+    pub titlecase: Option<char>,
+    /// The simple casefold delta. Its sign is stored in bits.negative_delta
+    pub simple_case_delta: Option<u32>,
+    /// Closure mappings
+    pub closure: Option<Cow<'a, str>>,
+    /// The four full-mappings strings, indexed by MappingKind u8 value
+    pub full: Option<[Cow<'a, str>; 4]>,
+}
+
+impl DecodedException<'_> {
+    /// Convert to a wire-format encodeable (VarULE-encodeable) [`Exception`]
+    pub fn encode(&self) -> Exception<'static> {
+        let bits = self.bits;
+        let mut slot_presence = SlotPresence(0);
+        let mut data = alloc::string::String::new();
+        if let Some(lowercase) = self.lowercase {
+            slot_presence.add_slot(ExceptionSlot::Lower);
+            data.push(lowercase)
+        }
+        if let Some(casefold) = self.casefold {
+            slot_presence.add_slot(ExceptionSlot::Fold);
+            data.push(casefold)
+        }
+        if let Some(uppercase) = self.uppercase {
+            slot_presence.add_slot(ExceptionSlot::Upper);
+            data.push(uppercase)
+        }
+        if let Some(titlecase) = self.titlecase {
+            slot_presence.add_slot(ExceptionSlot::Title);
+            data.push(titlecase)
+        }
+        if let Some(mut simple_case_delta) = self.simple_case_delta {
+            slot_presence.add_slot(ExceptionSlot::Delta);
+
+            if simple_case_delta >= SURROGATES_START {
+                simple_case_delta += SURROGATES_LEN;
+            }
+            let simple_case_delta = char::try_from(simple_case_delta).unwrap_or('\0');
+            data.push(simple_case_delta)
+        }
+
+        if let Some(ref closure) = self.closure {
+            slot_presence.add_slot(ExceptionSlot::Closure);
+            if self.full.is_some() {
+                // GIGO: if the closure length is more than 0xD800 this will error. Plenty of space.
+                debug_assert!(
+                    closure.len() < 0xD800,
+                    "Found overlarge closure value when encoding exception"
+                );
+                let len_char = u32::try_from(closure.len())
+                    .ok()
+                    .and_then(|c| char::try_from(c).ok())
+                    .unwrap_or('\0');
+                data.push(len_char);
+            }
+            data.push_str(closure);
+        }
+        if let Some(ref full) = self.full {
+            slot_presence.add_slot(ExceptionSlot::FullMappings);
+            let mut idx = 0;
+            // iterate all elements except the last, whose length we can calculate from context
+            for mapping in full.iter().take(3) {
+                idx += mapping.len();
+                data.push(char::try_from(u32::try_from(idx).unwrap_or(0)).unwrap_or('\0'));
+            }
+            for mapping in full {
+                data.push_str(mapping);
+            }
+        }
+        Exception {
+            bits,
+            slot_presence,
+            data: data.into(),
+        }
+    }
+
+    // Potential optimization: Write an `EncodeAsVarULE` that
+    // directly produces an ExceptionULE
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    fn test_roundtrip_once(exception: DecodedException) {
+        let encoded = exception.encode();
+        let encoded = zerovec::ule::encode_varule_to_box(&encoded);
+        let decoded = encoded.decode();
+        assert_eq!(decoded, exception);
+    }
+
+    #[test]
+    fn test_roundtrip() {
+        test_roundtrip_once(DecodedException {
+            lowercase: Some('ø'),
+            ..Default::default()
+        });
+        test_roundtrip_once(DecodedException {
+            titlecase: Some('X'),
+            lowercase: Some('ø'),
+            ..Default::default()
+        });
+        test_roundtrip_once(DecodedException {
+            titlecase: Some('X'),
+            ..Default::default()
+        });
+        test_roundtrip_once(DecodedException {
+            titlecase: Some('X'),
+            simple_case_delta: Some(0xE999),
+            closure: Some("hello world".into()),
+            ..Default::default()
+        });
+        test_roundtrip_once(DecodedException {
+            simple_case_delta: Some(10),
+            closure: Some("hello world".into()),
+            full: Some(["你好世界".into(), "".into(), "hi".into(), "å".into()]),
+            ..Default::default()
+        });
+        test_roundtrip_once(DecodedException {
+            closure: Some("hello world".into()),
+            full: Some(["aa".into(), "ț".into(), "".into(), "å".into()]),
+            ..Default::default()
+        });
+        test_roundtrip_once(DecodedException {
+            full: Some(["你好世界".into(), "".into(), "hi".into(), "å".into()]),
+            ..Default::default()
+        });
+    }
+}
diff --git a/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/src/provider/exceptions_builder.rs b/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/src/provider/exceptions_builder.rs
new file mode 100644
index 0000000..b49699a
--- /dev/null
+++ b/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/src/provider/exceptions_builder.rs
@@ -0,0 +1,290 @@
+// This file is part of ICU4X. For terms of use, please see the file
+// called LICENSE at the top level of the ICU4X source tree
+// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
+
+use crate::provider::exception_helpers::{
+    ExceptionBits, ExceptionBitsULE, ExceptionSlot, SlotPresence,
+};
+use crate::provider::exceptions::{CaseMapExceptions, DecodedException};
+use alloc::borrow::Cow;
+use alloc::collections::BTreeMap;
+use alloc::string::String;
+use alloc::vec::Vec;
+use icu_provider::DataError;
+use zerovec::ule::{AsULE, ULE};
+
+/// The header for exception types as found in ICU4C data. See [`ExceptionHeaderULE`]
+/// for the wire format
+#[derive(Copy, Clone, PartialEq, Eq)]
+pub struct ExceptionHeader {
+    /// The various slots that are present, masked by ExceptionSlot
+    ///
+    /// We still store this as a bitmask since it's more convenient to access as one
+    pub slot_presence: SlotPresence,
+    pub bits: ExceptionBits,
+}
+
+impl ExceptionHeader {
+    /// Construct from an ICU4C-format u16.
+    pub(crate) fn from_integer(int: u16) -> Self {
+        let slot_presence =
+            SlotPresence(u8::try_from(int & ExceptionHeaderULE::SLOTS_MASK).unwrap_or(0));
+        let bits = ExceptionBits::from_integer(
+            u8::try_from(int >> ExceptionHeaderULE::BITS_SHIFT).unwrap_or(0),
+        );
+        Self {
+            slot_presence,
+            bits,
+        }
+    }
+
+    // Returns true if the given slot exists for this exception
+    pub(crate) fn has_slot(self, slot: ExceptionSlot) -> bool {
+        self.slot_presence.has_slot(slot)
+    }
+}
+
+/// Packed exception header (format from icu4c, documented in casepropsbuilder.cpp)
+///
+/// ```text
+///       Bits:
+///         0..7  Flag bits indicating which optional slots are present (if any):
+///               0: Lowercase mapping (code point)
+///               1: Case folding (code point)
+///               2: Uppercase mapping (code point)
+///               3: Titlecase mapping (code point)
+///               4: Delta to simple case mapping (code point) (sign stored separately)
+///               5: RESERVED
+///               6: Closure mappings (string; see below)
+///               7: Full mappings (strings; see below)
+///            8  Double-width slots. If set, then each optional slot is stored as two
+///               elements of the array (high and low halves of 32-bit values) instead of
+///               a single element.
+///            9  Has no simple case folding, even if there is a simple lowercase mapping
+///           10  The value in the delta slot is negative
+///           11  Is case-sensitive (not exposed)
+///       12..13  Dot type
+///           14  Has conditional special casing
+///           15  Has conditional case folding
+/// ```
+///
+/// In this struct the RESERVED bit is still allowed to be set, and it will produce a different
+/// exception header, but it will not have any other effects.
+#[derive(Copy, Clone, PartialEq, Eq, ULE)]
+#[repr(C, packed)]
+pub struct ExceptionHeaderULE {
+    slot_presence: SlotPresence,
+    bits: ExceptionBitsULE,
+}
+
+impl ExceptionHeaderULE {
+    const SLOTS_MASK: u16 = 0xff;
+    const BITS_SHIFT: u16 = 8;
+}
+
+impl AsULE for ExceptionHeader {
+    type ULE = ExceptionHeaderULE;
+    fn from_unaligned(u: ExceptionHeaderULE) -> Self {
+        Self {
+            slot_presence: u.slot_presence,
+            bits: ExceptionBits::from_integer(u.bits.0),
+        }
+    }
+
+    fn to_unaligned(self) -> ExceptionHeaderULE {
+        ExceptionHeaderULE {
+            slot_presence: self.slot_presence,
+            bits: ExceptionBitsULE(self.bits.to_integer()),
+        }
+    }
+}
+// CaseMapExceptionsBuilder consumes the exceptions data produced by
+// casepropsbuilder.cpp in ICU4C. It generates an instance of CaseMapExceptions. The
+// primary difference is that the ICU4C representation stores full mapping and closure
+// strings inline in the data, while CaseMapExceptions uses a side table. As a result,
+// the starting index of each exception in the resulting CaseMapExceptions may have
+// changed, so we also produce a map from old indices to new indices that will be used to
+// update the data stored in the code point trie.
+pub struct CaseMapExceptionsBuilder<'a> {
+    raw_data: &'a [u16],
+    raw_data_idx: usize,
+    double_slots: bool,
+}
+
+impl<'a> CaseMapExceptionsBuilder<'a> {
+    const MAPPINGS_ALL_LENGTHS_MASK: u32 = 0xffff;
+    const FULL_MAPPINGS_LENGTH_MASK: u32 = 0xf;
+    const FULL_MAPPINGS_LENGTH_SHIFT: u32 = 4;
+
+    const CLOSURE_MAX_LENGTH: u32 = 0xf;
+
+    pub fn new(raw_data: &'a [u16]) -> Self {
+        Self {
+            raw_data,
+            raw_data_idx: 0,
+            double_slots: false,
+        }
+    }
+
+    fn done(&self) -> bool {
+        self.raw_data_idx >= self.raw_data.len()
+    }
+    fn read_raw(&mut self) -> Result<u16, DataError> {
+        let result = self
+            .raw_data
+            .get(self.raw_data_idx)
+            .ok_or(DataError::custom("Incomplete exception data"))?;
+        self.raw_data_idx += 1;
+        Ok(*result)
+    }
+
+    fn read_slot(&mut self) -> Result<u32, DataError> {
+        if self.double_slots {
+            let hi = self.read_raw()? as u32;
+            let lo = self.read_raw()? as u32;
+            Ok((hi << 16) | lo)
+        } else {
+            Ok(self.read_raw()? as u32)
+        }
+    }
+
+    // After reading a string out of the raw data, advance raw_data_idx.
+    fn skip_string(&mut self, s: &str) {
+        for c in s.chars() {
+            self.raw_data_idx += c.len_utf16();
+        }
+    }
+
+    pub(crate) fn build(
+        mut self,
+    ) -> Result<(CaseMapExceptions<'static>, BTreeMap<u16, u16>), DataError> {
+        let mut exceptions = Vec::new();
+        let mut idx_map = BTreeMap::new();
+        // The format of the raw data from ICU4C is the same as the format described in
+        // exceptions.rs, with the exception of full mapping and closure strings. The
+        // header and non-string slots can be copied over without modification. For string
+        // slots, we read the length information from the ICU4C slot (described below),
+        // read the strings, add the strings to the CaseMapExceptions string table,
+        // and write an updated slot value containing the index of the string in the
+        // table. In the case of full mappings, we store the index of the lowercase
+        // mapping; the remaining mappings are stored at sequential indices.
+        //
+        // Full mappings: If there is at least one full (string) case mapping, then the
+        // lengths of the mappings are encoded as nibbles in the full mappings slot:
+        //     Bits:
+        //        0..4   Length of lowercase string
+        //        5..7   Length of case folding string
+        //        8..11  Length of uppercase string
+        //        12..15 Length of titlecase string
+        // Mappings that do not exist have length 0. The strings themselves are stored in
+        // the above order immediately following the last optional slot, encoded as UTF16.
+        //
+        // Case closure: If the case closure for a code point includes code points that
+        // are not included in the simple or full mappings, then bits 0..3 of the closure
+        // mappings slot will contain the number of codepoints in the closure string.
+        // (Other bits are reserved.) The closure string itself is encoded as UTF16 and
+        // stored following the full mappings data (if it exists) or the final optional
+        // slot.
+        while !self.done() {
+            let old_idx = self.raw_data_idx as u16;
+
+            let mut exception = DecodedException::default();
+
+            // Copy header.
+            let header = ExceptionHeader::from_integer(self.read_raw()?);
+            self.double_slots = header.bits.double_width_slots;
+
+            // Copy unmodified slots.
+            for (slot, output) in [
+                (ExceptionSlot::Lower, &mut exception.lowercase),
+                (ExceptionSlot::Fold, &mut exception.casefold),
+                (ExceptionSlot::Upper, &mut exception.uppercase),
+                (ExceptionSlot::Title, &mut exception.titlecase),
+            ] {
+                if header.has_slot(slot) {
+                    let value = self.read_slot()?;
+                    if let Ok(ch) = char::try_from(value) {
+                        *output = Some(ch)
+                    } else {
+                        return Err(DataError::custom(
+                            "Found non-char value in casemapping exceptions data",
+                        ));
+                    }
+                }
+            }
+            if header.has_slot(ExceptionSlot::Delta) {
+                let delta = self.read_slot()?;
+
+                exception.simple_case_delta = Some(delta)
+            }
+
+            // Read the closure and full mappings slots, if they exist.
+            let closure_length = if header.has_slot(ExceptionSlot::Closure) {
+                Some((self.read_slot()? & Self::CLOSURE_MAX_LENGTH) as usize)
+            } else {
+                None
+            };
+            let mappings_lengths = if header.has_slot(ExceptionSlot::FullMappings) {
+                Some(self.read_slot()? & Self::MAPPINGS_ALL_LENGTHS_MASK)
+            } else {
+                None
+            };
+
+            // Copy the full mappings strings into the strings table, if they exist.
+            if let Some(mut lengths) = mappings_lengths {
+                let mut arr: [Cow<_>; 4] = Default::default();
+                for mapping in &mut arr {
+                    let len = lengths & Self::FULL_MAPPINGS_LENGTH_MASK;
+                    lengths >>= Self::FULL_MAPPINGS_LENGTH_SHIFT;
+
+                    let start = self.raw_data_idx;
+                    let end = start + len as usize;
+                    let slice = &self
+                        .raw_data
+                        .get(start..end)
+                        .ok_or(DataError::custom("Incomplete string data"))?;
+                    let string = char::decode_utf16(slice.iter().copied())
+                        .collect::<Result<String, _>>()
+                        .map_err(|_| DataError::custom("Found non-utf16 exceptions data"))?;
+                    self.skip_string(&string);
+                    *mapping = string.into()
+                }
+                exception.full = Some(arr)
+            }
+
+            // Copy the closure string into the strings table, if it exists.
+            if let Some(len) = closure_length {
+                let start = self.raw_data_idx;
+                let slice = &self
+                    .raw_data
+                    .get(start..)
+                    .ok_or(DataError::custom("Incomplete string data"))?;
+                let string = char::decode_utf16(slice.iter().copied())
+                    .take(len)
+                    .collect::<Result<String, _>>()
+                    .map_err(|_| DataError::custom("Found non-utf16 exceptions data"))?;
+                self.skip_string(&string);
+                exception.closure = Some(string.into())
+            }
+
+            exception.bits = header.bits;
+            // unused bits in ICU4X
+            exception.bits.double_width_slots = false;
+
+            let new_exception_index = if let Ok(idx) = u16::try_from(exceptions.len()) {
+                idx
+            } else {
+                return Err(DataError::custom("More than u16 exceptions"));
+            };
+            idx_map.insert(old_idx, new_exception_index);
+            exceptions.push(exception.encode());
+        }
+
+        Ok((
+            CaseMapExceptions {
+                exceptions: (&exceptions).into(),
+            },
+            idx_map,
+        ))
+    }
+}
diff --git a/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/src/provider/mod.rs b/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/src/provider/mod.rs
new file mode 100644
index 0000000..9fab7a4
--- /dev/null
+++ b/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/src/provider/mod.rs
@@ -0,0 +1,202 @@
+// This file is part of ICU4X. For terms of use, please see the file
+// called LICENSE at the top level of the ICU4X source tree
+// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
+
+//! 🚧 \[Unstable\] Data provider struct definitions for this ICU4X component.
+//!
+//! <div class="stab unstable">
+//! 🚧 This code is considered unstable; it may change at any time, in breaking or non-breaking ways,
+//! including in SemVer minor releases. While the serde representation of data structs is guaranteed
+//! to be stable, their Rust representation might not be. Use with caution.
+//! </div>
+//!
+//! Read more about data providers: [`icu_provider`]
+
+// Provider structs must be stable
+#![allow(clippy::exhaustive_structs, clippy::exhaustive_enums)]
+
+use icu_provider::prelude::*;
+
+use crate::provider::data::CaseMapData;
+use crate::provider::exceptions::CaseMapExceptions;
+use icu_collections::codepointtrie::CodePointTrie;
+#[cfg(feature = "datagen")]
+use icu_collections::codepointtrie::CodePointTrieHeader;
+
+pub mod data;
+pub mod exception_helpers;
+pub mod exceptions;
+#[cfg(feature = "datagen")]
+mod exceptions_builder;
+mod unfold;
+
+#[cfg(feature = "compiled_data")]
+#[derive(Debug)]
+/// Baked data
+///
+/// <div class="stab unstable">
+/// 🚧 This code is considered unstable; it may change at any time, in breaking or non-breaking ways,
+/// including in SemVer minor releases. In particular, the `DataProvider` implementations are only
+/// guaranteed to match with this version's `*_unstable` providers. Use with caution.
+/// </div>
+pub struct Baked;
+
+#[cfg(feature = "compiled_data")]
+#[allow(unused_imports)]
+const _: () = {
+    use icu_casemap_data::*;
+    pub mod icu {
+        pub use crate as casemap;
+        pub use icu_collections as collections;
+    }
+    make_provider!(Baked);
+    impl_case_map_v1!(Baked);
+    impl_case_map_unfold_v1!(Baked);
+};
+
+icu_provider::data_marker!(
+    /// Marker for casemapping data.
+    CaseMapV1,
+    "case/map/v1",
+    CaseMap<'static>,
+    is_singleton = true
+);
+
+icu_provider::data_marker!(
+    /// Reverse case mapping data.
+    CaseMapUnfoldV1,
+    "case/map/unfold/v1",
+    CaseMapUnfold<'static>,
+    is_singleton = true
+);
+
+#[cfg(feature = "datagen")]
+/// The latest minimum set of markers required by this component.
+pub const MARKERS: &[DataMarkerInfo] = &[CaseMapUnfoldV1::INFO, CaseMapV1::INFO];
+
+pub use self::unfold::CaseMapUnfold;
+
+/// This type contains all of the casemapping data
+///
+/// The methods in the provider module are primarily about accessing its data,
+/// however the full algorithms are also implemented as methods on this type in
+/// the `internals` module of this crate.
+///
+/// <div class="stab unstable">
+/// 🚧 This code is considered unstable; it may change at any time, in breaking or non-breaking ways,
+/// including in SemVer minor releases. While the serde representation of data structs is guaranteed
+/// to be stable, their Rust representation might not be. Use with caution.
+/// </div>
+#[derive(Debug, PartialEq, Clone, yoke::Yokeable, zerofrom::ZeroFrom)]
+#[cfg_attr(feature = "datagen", derive(serde::Serialize, databake::Bake))]
+#[cfg_attr(feature = "datagen", databake(path = icu_casemap::provider))]
+#[yoke(prove_covariance_manually)]
+/// CaseMapper provides low-level access to the data necessary to
+/// convert characters and strings to upper, lower, or title case.
+pub struct CaseMap<'data> {
+    /// Case mapping data
+    pub trie: CodePointTrie<'data, CaseMapData>,
+    /// Exceptions to the case mapping data
+    pub exceptions: CaseMapExceptions<'data>,
+}
+
+icu_provider::data_struct!(
+    CaseMap<'_>,
+    #[cfg(feature = "datagen")]
+);
+
+#[cfg(feature = "serde")]
+impl<'de> serde::Deserialize<'de> for CaseMap<'de> {
+    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
+        #[derive(serde::Deserialize)]
+        pub struct Raw<'data> {
+            #[serde(borrow)]
+            pub trie: CodePointTrie<'data, CaseMapData>,
+            #[serde(borrow)]
+            pub exceptions: CaseMapExceptions<'data>,
+        }
+
+        let Raw { trie, exceptions } = Raw::deserialize(deserializer)?;
+        let result = Self { trie, exceptions };
+        debug_assert!(result.validate().is_ok());
+        Ok(result)
+    }
+}
+
+impl CaseMap<'_> {
+    /// Creates a new CaseMap using data exported by the
+    // `icuexportdata` tool in ICU4C. Validates that the data is
+    // consistent.
+    #[cfg(feature = "datagen")]
+    pub fn try_from_icu(
+        trie_header: CodePointTrieHeader,
+        trie_index: &[u16],
+        trie_data: &[u16],
+        exceptions: &[u16],
+    ) -> Result<Self, DataError> {
+        use self::exceptions_builder::CaseMapExceptionsBuilder;
+        use zerovec::ZeroVec;
+        let exceptions_builder = CaseMapExceptionsBuilder::new(exceptions);
+        let (exceptions, idx_map) = exceptions_builder.build()?;
+
+        let trie_index = ZeroVec::alloc_from_slice(trie_index);
+
+        #[allow(clippy::unwrap_used)] // datagen only
+        let trie_data = trie_data
+            .iter()
+            .map(|&i| {
+                CaseMapData::try_from_icu_integer(i)
+                    .unwrap()
+                    .with_updated_exception(&idx_map)
+            })
+            .collect::<ZeroVec<_>>();
+
+        let trie = CodePointTrie::try_new(trie_header, trie_index, trie_data)
+            .map_err(|_| DataError::custom("Casemapping data does not form valid trie"))?;
+
+        let result = Self { trie, exceptions };
+        result.validate().map_err(DataError::custom)?;
+        Ok(result)
+    }
+
+    /// Given an existing CaseMapper, validates that the data is
+    /// consistent. A CaseMapper created by the ICU transformer has
+    /// already been validated. Calling this function is only
+    /// necessary if you are concerned about data corruption after
+    /// deserializing.
+    #[cfg(any(feature = "serde", feature = "datagen"))]
+    #[allow(unused)] // is only used in debug mode for serde
+    pub(crate) fn validate(&self) -> Result<(), &'static str> {
+        // First, validate that exception data is well-formed.
+        let valid_exception_indices = self.exceptions.validate()?;
+
+        let validate_delta = |c: char, delta: i32| -> Result<(), &'static str> {
+            let new_c =
+                u32::try_from(c as i32 + delta).map_err(|_| "Delta larger than character")?;
+            char::from_u32(new_c).ok_or("Invalid delta")?;
+            Ok(())
+        };
+
+        for i in 0..char::MAX as u32 {
+            if let Some(c) = char::from_u32(i) {
+                let data = self.lookup_data(c);
+                if data.has_exception() {
+                    let idx = data.exception_index();
+                    let exception = self.exceptions.get(idx);
+                    // Verify that the exception index points to a valid exception header.
+                    if !valid_exception_indices.contains(&idx) {
+                        return Err("Invalid exception index in trie data");
+                    }
+                    exception.validate()?;
+                } else {
+                    validate_delta(c, data.delta() as i32)?;
+                }
+            }
+        }
+        Ok(())
+    }
+
+    pub(crate) fn lookup_data(&self, c: char) -> CaseMapData {
+        self.trie.get32(c as u32)
+    }
+}
diff --git a/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/src/provider/unfold.rs b/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/src/provider/unfold.rs
new file mode 100644
index 0000000..e36a3d1
--- /dev/null
+++ b/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/src/provider/unfold.rs
@@ -0,0 +1,105 @@
+// This file is part of ICU4X. For terms of use, please see the file
+// called LICENSE at the top level of the ICU4X source tree
+// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
+
+//! Data for reverse folding
+
+#[cfg(feature = "datagen")]
+use alloc::string::String;
+use icu_provider::prelude::*;
+use potential_utf::PotentialUtf8;
+use zerovec::ZeroMap;
+
+/// Reverse case folding data. Maps from multi-character strings back
+/// to code-points that fold to those strings.
+///
+/// <div class="stab unstable">
+/// 🚧 This code is considered unstable; it may change at any time, in breaking or non-breaking ways,
+/// including in SemVer minor releases. While the serde representation of data structs is guaranteed
+/// to be stable, their Rust representation might not be. Use with caution.
+/// </div>
+#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
+#[cfg_attr(feature = "datagen", derive(serde::Serialize, databake::Bake))]
+#[cfg_attr(feature = "datagen", databake(path = icu_casemap::provider))]
+#[derive(Debug, PartialEq, Clone, yoke::Yokeable, zerofrom::ZeroFrom)]
+#[yoke(prove_covariance_manually)]
+pub struct CaseMapUnfold<'data> {
+    #[cfg_attr(feature = "serde", serde(borrow))]
+    /// The actual map. Maps from strings to a list of codepoints, stored as a contiguous UTF-8 string
+    pub map: ZeroMap<'data, PotentialUtf8, str>,
+}
+
+icu_provider::data_struct!(
+    CaseMapUnfold<'_>,
+    #[cfg(feature = "datagen")]
+);
+
+impl CaseMapUnfold<'_> {
+    /// Creates a new CaseMapUnfold using data exported by the `icuexportdata` tool in ICU4C.
+    ///
+    /// Unfold data is exported by ICU as an array of 16-bit values, representing a short
+    /// header followed by a two-column key/value table. The header indicates:
+    /// - The number of rows.
+    /// - The number of UTF16 code units per row.
+    /// - The number of UTF16 code units in the first (key) column.
+    ///   (The number of code units in the value column can be derived from the above.)
+    ///
+    /// The key in the first column is the case folding of each of the code points in
+    /// the second column. Keys/values that are shorter than the column width are
+    /// null-terminated. The table is sorted by key. Binary search is used to find the value.
+    ///
+    /// Rust strings are UTF8 by default. To avoid the cost of converting from UTF16 on access,
+    /// we convert the ICU data into a more convenient format during construction.
+    #[cfg(feature = "datagen")]
+    #[allow(clippy::indexing_slicing)] // panics are ok in datagen
+    pub fn try_from_icu(raw: &[u16]) -> Result<Self, DataError> {
+        const ROWS_INDEX: usize = 0;
+        const ROW_WIDTH_INDEX: usize = 1;
+        const STRING_WIDTH_INDEX: usize = 2;
+
+        if raw.len() <= STRING_WIDTH_INDEX {
+            return Err(DataError::custom("Unfold: header missing"));
+        }
+
+        let num_rows = raw[ROWS_INDEX] as usize;
+        let row_width = raw[ROW_WIDTH_INDEX] as usize;
+        let string_width = raw[STRING_WIDTH_INDEX] as usize;
+
+        if row_width == 0 {
+            return Err(DataError::custom("Unfold: invalid row width"));
+        }
+
+        // Header takes up one row.
+        let row_data = &raw[row_width..];
+
+        let mut map = ZeroMap::new();
+
+        debug_assert!(num_rows == row_data.chunks_exact(row_width).count());
+        for row in row_data.chunks_exact(row_width) {
+            let key = Self::decode_string(&row[..string_width])
+                .ok_or(DataError::custom("Unfold: unpaired surrogate in key"))?;
+            let val = Self::decode_string(&row[string_width..])
+                .ok_or(DataError::custom("Unfold: unpaired surrogate in value"))?;
+            if map
+                .try_append(PotentialUtf8::from_str(&key), val.as_ref())
+                .is_some()
+            {
+                return Err(DataError::custom("Unfold: keys not sorted/unique"));
+            }
+        }
+        Ok(Self { map })
+    }
+
+    // Decode a zero-terminated UTF16 string from a slice of u16.
+    #[cfg(feature = "datagen")]
+    pub(crate) fn decode_string(slice: &[u16]) -> Option<String> {
+        let iter = slice.iter().copied().take_while(|&c| c != 0);
+        char::decode_utf16(iter).collect::<Result<String, _>>().ok()
+    }
+
+    // Given a string, returns another string representing the set of characters
+    // that case fold to that string.
+    pub(crate) fn get(&self, key: &str) -> Option<&str> {
+        self.map.get(PotentialUtf8::from_str(key))
+    }
+}
diff --git a/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/src/set.rs b/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/src/set.rs
new file mode 100644
index 0000000..d5d02eb7
--- /dev/null
+++ b/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/src/set.rs
@@ -0,0 +1,36 @@
+// This file is part of ICU4X. For terms of use, please see the file
+// called LICENSE at the top level of the ICU4X source tree
+// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
+
+use icu_collections::codepointinvlist::CodePointInversionListBuilder;
+
+/// An object that accepts characters and/or strings
+/// to be used with [`CaseMapCloserBorrowed::add_string_case_closure_to()`]
+/// and [`CaseMapCloserBorrowed::add_case_closure_to()`].
+///
+/// Usually this object
+/// will be some kind of set over codepoints and strings, or something that
+/// can be built into one.
+///
+/// An implementation is provided for [`CodePointInversionListBuilder`], but users are encouraged
+/// to implement this trait on their own collections as needed.
+///
+/// [`CaseMapCloserBorrowed::add_string_case_closure_to()`]: crate::CaseMapCloserBorrowed::add_string_case_closure_to
+/// [`CaseMapCloserBorrowed::add_case_closure_to()`]: crate::CaseMapCloserBorrowed::add_case_closure_to
+pub trait ClosureSink {
+    /// Add a character to the set
+    fn add_char(&mut self, c: char);
+    /// Add a string to the set
+    fn add_string(&mut self, string: &str);
+}
+
+impl ClosureSink for CodePointInversionListBuilder {
+    fn add_char(&mut self, c: char) {
+        self.add_char(c)
+    }
+
+    // The current version of CodePointInversionList doesn't include strings.
+    // Trying to add a string is a no-op that will be optimized away.
+    #[inline]
+    fn add_string(&mut self, _string: &str) {}
+}
diff --git a/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/src/titlecase.rs b/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/src/titlecase.rs
new file mode 100644
index 0000000..7de65b9
--- /dev/null
+++ b/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/src/titlecase.rs
@@ -0,0 +1,485 @@
+// This file is part of ICU4X. For terms of use, please see the file
+// called LICENSE at the top level of the ICU4X source tree
+// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
+
+//! Titlecasing-specific
+use crate::provider::CaseMapV1;
+use crate::{CaseMapper, CaseMapperBorrowed};
+use alloc::string::String;
+use icu_locale_core::LanguageIdentifier;
+use icu_properties::props::{GeneralCategory, GeneralCategoryGroup};
+use icu_properties::provider::GeneralCategoryV1;
+use icu_properties::{CodePointMapData, CodePointMapDataBorrowed};
+use icu_provider::prelude::*;
+use writeable::Writeable;
+
+/// How to handle the rest of the string once the beginning of the
+/// string has been titlecased.
+///
+/// # Examples
+///
+/// ```rust
+/// use icu::casemap::options::{TitlecaseOptions, TrailingCase};
+/// use icu::casemap::TitlecaseMapper;
+/// use icu::locale::langid;
+///
+/// let cm = TitlecaseMapper::new();
+/// let root = langid!("und");
+///
+/// let default_options = Default::default();
+/// let mut preserve_case: TitlecaseOptions = Default::default();
+/// preserve_case.trailing_case = Some(TrailingCase::Unchanged);
+///
+/// // Exhibits trailing case when set:
+/// assert_eq!(
+///     cm.titlecase_segment_to_string("spOngeBoB", &root, default_options),
+///     "Spongebob"
+/// );
+/// assert_eq!(
+///     cm.titlecase_segment_to_string("spOngeBoB", &root, preserve_case),
+///     "SpOngeBoB"
+/// );
+/// ```
+#[non_exhaustive]
+#[derive(Copy, Clone, Default, PartialEq, Eq, Hash, Debug)]
+pub enum TrailingCase {
+    /// Preserve the casing of the rest of the string ("spoNgEBoB" -> "SpoNgEBoB")
+    Unchanged,
+    /// Lowercase the rest of the string ("spoNgEBoB" -> "Spongebob")
+    #[default]
+    Lower,
+}
+
+/// Where to start casing the string.
+///
+/// [`TitlecaseMapper`] by default performs "leading adjustment", where it searches for the first "relevant" character
+/// in the string before initializing the actual titlecasing. For example, it will skip punctuation at the beginning
+/// of a string, allowing for strings like `'twas` or `«hello»` to be appropriately titlecased.
+///
+/// Opinions on exactly what is a "relevant" character may differ. In "adjust to cased" mode the first cased character is considered "relevant",
+/// whereas in the "auto" mode, it is the first character that is a letter, number, symbol, or private use character. This means
+/// that the strings `49ers` and `«丰(abc)»` will titlecase in "adjust to cased" mode to `49Ers` and `«丰(Abc)»`, whereas in the "auto" mode they stay unchanged.
+/// This difference largely matters for things that mix numbers and letters, or mix writing systems, within a single segment.
+///
+/// # Examples
+///
+/// ```rust
+/// use icu::casemap::options::{LeadingAdjustment, TitlecaseOptions};
+/// use icu::casemap::TitlecaseMapper;
+/// use icu::locale::langid;
+///
+/// let cm = TitlecaseMapper::new();
+/// let root = langid!("und");
+///
+/// let default_options = Default::default(); // head adjustment set to Auto
+/// let mut no_adjust: TitlecaseOptions = Default::default();
+/// let mut adjust_to_cased: TitlecaseOptions = Default::default();
+/// no_adjust.leading_adjustment = Some(LeadingAdjustment::None);
+/// adjust_to_cased.leading_adjustment = Some(LeadingAdjustment::ToCased);
+///
+/// // Exhibits leading adjustment when set:
+/// assert_eq!(
+///     cm.titlecase_segment_to_string("«hello»", &root, default_options),
+///     "«Hello»"
+/// );
+/// assert_eq!(
+///     cm.titlecase_segment_to_string("«hello»", &root, adjust_to_cased),
+///     "«Hello»"
+/// );
+/// assert_eq!(
+///     cm.titlecase_segment_to_string("«hello»", &root, no_adjust),
+///     "«hello»"
+/// );
+///
+/// // Only changed in adjust-to-cased mode:
+/// assert_eq!(
+///     cm.titlecase_segment_to_string("丰(abc)", &root, default_options),
+///     "丰(abc)"
+/// );
+/// assert_eq!(
+///     cm.titlecase_segment_to_string("丰(abc)", &root, adjust_to_cased),
+///     "丰(Abc)"
+/// );
+/// assert_eq!(
+///     cm.titlecase_segment_to_string("丰(abc)", &root, no_adjust),
+///     "丰(abc)"
+/// );
+///
+/// // Only changed in adjust-to-cased mode:
+/// assert_eq!(
+///     cm.titlecase_segment_to_string("49ers", &root, default_options),
+///     "49ers"
+/// );
+/// assert_eq!(
+///     cm.titlecase_segment_to_string("49ers", &root, adjust_to_cased),
+///     "49Ers"
+/// );
+/// assert_eq!(
+///     cm.titlecase_segment_to_string("49ers", &root, no_adjust),
+///     "49ers"
+/// );
+/// ```
+#[non_exhaustive]
+#[derive(Copy, Clone, Default, PartialEq, Eq, Hash, Debug)]
+pub enum LeadingAdjustment {
+    /// Start titlecasing immediately, even if the character is not one that is relevant for casing
+    /// ("'twixt" -> "'twixt", "twixt" -> "Twixt")
+    None,
+    /// Adjust the string to the first relevant character before beginning to apply casing
+    /// ("'twixt" -> "'Twixt"). "Relevant" character is picked by best available algorithm,
+    /// by default will adjust to first letter, number, symbol, or private use character,
+    /// but if no data is available (e.g. this API is being called via [`CaseMapperBorrowed::titlecase_segment_with_only_case_data()`]),
+    /// then may be equivalent to "adjust to cased".
+    ///
+    /// This is the default
+    #[default]
+    Auto,
+    /// Adjust the string to the first cased character before beginning to apply casing
+    /// ("'twixt" -> "'Twixt")
+    ToCased,
+}
+
+/// Various options for controlling titlecasing
+///
+/// See docs of [`TitlecaseMapper`] for examples.
+#[non_exhaustive]
+#[derive(Copy, Clone, Default, PartialEq, Eq, Hash, Debug)]
+pub struct TitlecaseOptions {
+    /// How to handle the rest of the string once the head of the
+    /// string has been titlecased
+    ///
+    /// Default is [`TrailingCase::Lower`]
+    pub trailing_case: Option<TrailingCase>,
+    /// Whether to start casing at the beginning of the string or at the first
+    /// relevant character.
+    ///
+    /// Default is [`LeadingAdjustment::Auto`]
+    pub leading_adjustment: Option<LeadingAdjustment>,
+}
+
+/// A wrapper around [`CaseMapper`] that can compute titlecasing stuff, and is able to load additional data
+/// to support the non-legacy "head adjustment" behavior.
+///
+///
+/// Most methods for this type live on [`TitlecaseMapperBorrowed`], which you can obtain via
+/// [`TitlecaseMapper::new()`] or [`TitlecaseMapper::as_borrowed()`].
+///
+/// By default, [`TitlecaseMapperBorrowed::titlecase_segment()`] and [`TitlecaseMapperBorrowed::titlecase_segment_to_string()`] perform "leading adjustment",
+/// where they wait till the first relevant character to begin titlecasing. For example, in the string `'twixt`, the apostrophe
+/// is ignored because the word starts at the first "t", which will get titlecased (producing `'Twixt`). Other punctuation will
+/// also be ignored, like in the string `«hello»`, which will get titlecased to `«Hello»`.
+///
+/// This is a separate type from [`CaseMapper`] because it loads the additional data
+/// required by [`LeadingAdjustment::Auto`] to perform the best possible leading adjustment.
+///
+/// If you are planning on only using [`LeadingAdjustment::None`] or [`LeadingAdjustment::ToCased`], consider using [`CaseMapper`] directly; this
+/// type will have no additional behavior.
+///
+/// # Examples
+///
+/// Basic casemapping behavior:
+///
+/// ```rust
+/// use icu::casemap::TitlecaseMapper;
+/// use icu::locale::langid;
+///
+/// let cm = TitlecaseMapper::new();
+/// let root = langid!("und");
+///
+/// let default_options = Default::default();
+///
+/// // note that the subsequent words are not titlecased, this function assumes
+/// // that the entire string is a single segment and only titlecases at the beginning.
+/// assert_eq!(cm.titlecase_segment_to_string("hEllO WorLd", &root, default_options), "Hello world");
+/// assert_eq!(cm.titlecase_segment_to_string("Γειά σου Κόσμε", &root, default_options), "Γειά σου κόσμε");
+/// assert_eq!(cm.titlecase_segment_to_string("नमस्ते दुनिया", &root, default_options), "नमस्ते दुनिया");
+/// assert_eq!(cm.titlecase_segment_to_string("Привет мир", &root, default_options), "Привет мир");
+///
+/// // Some behavior is language-sensitive
+/// assert_eq!(cm.titlecase_segment_to_string("istanbul", &root, default_options), "Istanbul");
+/// assert_eq!(cm.titlecase_segment_to_string("istanbul", &langid!("tr"), default_options), "İstanbul"); // Turkish dotted i
+///
+/// assert_eq!(cm.titlecase_segment_to_string("և Երևանի", &root, default_options), "Եւ երևանի");
+/// assert_eq!(cm.titlecase_segment_to_string("և Երևանի", &langid!("hy"), default_options), "Եվ երևանի"); // Eastern Armenian ech-yiwn ligature
+///
+/// assert_eq!(cm.titlecase_segment_to_string("ijkdijk", &root, default_options), "Ijkdijk");
+/// assert_eq!(cm.titlecase_segment_to_string("ijkdijk", &langid!("nl"), default_options), "IJkdijk"); // Dutch IJ digraph
+/// ```
+#[derive(Clone, Debug)]
+pub struct TitlecaseMapper<CM> {
+    cm: CM,
+    gc: CodePointMapData<GeneralCategory>,
+}
+
+impl TitlecaseMapper<CaseMapper> {
+    icu_provider::gen_buffer_data_constructors!(() -> error: DataError,
+    functions: [
+        new: skip,
+                try_new_with_buffer_provider,
+        try_new_unstable,
+        Self,
+    ]);
+
+    #[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::new)]
+    pub fn try_new_unstable<P>(provider: &P) -> Result<Self, DataError>
+    where
+        P: DataProvider<CaseMapV1> + DataProvider<GeneralCategoryV1> + ?Sized,
+    {
+        let cm = CaseMapper::try_new_unstable(provider)?;
+        let gc = icu_properties::CodePointMapData::<icu_properties::props::GeneralCategory>::try_new_unstable(provider)?;
+        Ok(Self { cm, gc })
+    }
+}
+
+impl TitlecaseMapper<CaseMapper> {
+    /// A constructor which creates a [`TitlecaseMapperBorrowed`] using compiled data
+    ///
+    /// ✨ *Enabled with the `compiled_data` Cargo feature.*
+    ///
+    /// [📚 Help choosing a constructor](icu_provider::constructors)
+    #[cfg(feature = "compiled_data")]
+    #[allow(clippy::new_ret_no_self)] // Intentional
+    pub const fn new() -> TitlecaseMapperBorrowed<'static> {
+        TitlecaseMapperBorrowed::new()
+    }
+}
+// We use Borrow, not AsRef, since we want the blanket impl on T
+impl<CM: AsRef<CaseMapper>> TitlecaseMapper<CM> {
+    icu_provider::gen_buffer_data_constructors!((casemapper: CM) -> error: DataError,
+    functions: [
+        new_with_mapper: skip,
+        try_new_with_mapper_with_buffer_provider,
+        try_new_with_mapper_unstable,
+        Self,
+    ]);
+
+    /// A constructor which creates a [`TitlecaseMapper`] from an existing [`CaseMapper`]
+    /// (either owned or as a reference) and compiled data
+    ///
+    /// ✨ *Enabled with the `compiled_data` Cargo feature.*
+    ///
+    /// [📚 Help choosing a constructor](icu_provider::constructors)
+    #[cfg(feature = "compiled_data")]
+    pub const fn new_with_mapper(casemapper: CM) -> Self {
+        Self {
+            cm: casemapper,
+            gc: icu_properties::CodePointMapData::<icu_properties::props::GeneralCategory>::new()
+                .static_to_owned(),
+        }
+    }
+
+    /// Construct this object to wrap an existing CaseMapper (or a reference to one), loading additional data as needed.
+    #[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::new_with_mapper)]
+    pub fn try_new_with_mapper_unstable<P>(provider: &P, casemapper: CM) -> Result<Self, DataError>
+    where
+        P: DataProvider<CaseMapV1> + DataProvider<GeneralCategoryV1> + ?Sized,
+    {
+        let gc = icu_properties::CodePointMapData::<icu_properties::props::GeneralCategory>::try_new_unstable(provider)?;
+        Ok(Self { cm: casemapper, gc })
+    }
+
+    /// Constructs a borrowed version of this type for more efficient querying.
+    pub fn as_borrowed(&self) -> TitlecaseMapperBorrowed<'_> {
+        TitlecaseMapperBorrowed {
+            cm: self.cm.as_ref().as_borrowed(),
+            gc: self.gc.as_borrowed(),
+        }
+    }
+}
+
+/// A borrowed [`TitlecaseMapper`].
+///
+/// See methods or [`TitlecaseMapper`] for examples.
+#[derive(Clone, Debug, Copy)]
+pub struct TitlecaseMapperBorrowed<'a> {
+    cm: CaseMapperBorrowed<'a>,
+    gc: CodePointMapDataBorrowed<'a, GeneralCategory>,
+}
+
+impl TitlecaseMapperBorrowed<'static> {
+    /// A constructor which creates a [`TitlecaseMapperBorrowed`] using compiled data
+    ///
+    /// ✨ *Enabled with the `compiled_data` Cargo feature.*
+    ///
+    /// [📚 Help choosing a constructor](icu_provider::constructors)
+    #[cfg(feature = "compiled_data")]
+    pub const fn new() -> Self {
+        Self {
+            cm: CaseMapper::new(),
+            gc: icu_properties::CodePointMapData::<icu_properties::props::GeneralCategory>::new(),
+        }
+    }
+    /// Cheaply converts a [`TitlecaseMapperBorrowed<'static>`] into a [`TitlecaseMapper`].
+    ///
+    /// Note: Due to branching and indirection, using [`TitlecaseMapper`] might inhibit some
+    /// compile-time optimizations that are possible with [`TitlecaseMapper`].
+    pub const fn static_to_owned(self) -> TitlecaseMapper<CaseMapper> {
+        TitlecaseMapper {
+            cm: self.cm.static_to_owned(),
+            gc: self.gc.static_to_owned(),
+        }
+    }
+}
+
+#[cfg(feature = "compiled_data")]
+impl Default for TitlecaseMapperBorrowed<'static> {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+impl<'a> TitlecaseMapperBorrowed<'a> {
+    /// Returns the full titlecase mapping of the given string as a [`Writeable`], treating
+    /// the string as a single segment (and thus only titlecasing the beginning of it).
+    ///
+    /// This should typically be used as a lower-level helper to construct the titlecasing operation desired
+    /// by the application, for example one can titlecase on a per-word basis by mixing this with
+    /// a `WordSegmenter`.
+    ///
+    /// This function is context and language sensitive. Callers should pass the text's language
+    /// as a `LanguageIdentifier` (usually the `id` field of the `Locale`) if available, or
+    /// `Default::default()` for the root locale.
+    ///
+    /// See [`TitlecaseMapperBorrowed::titlecase_segment_to_string()`] for the equivalent convenience function that returns a String,
+    /// as well as for an example.
+    pub fn titlecase_segment(
+        self,
+        src: &'a str,
+        langid: &LanguageIdentifier,
+        options: TitlecaseOptions,
+    ) -> impl Writeable + 'a {
+        if options.leading_adjustment.unwrap_or_default() == LeadingAdjustment::Auto {
+            // letter, number, symbol, or private use code point
+            const HEAD_GROUPS: GeneralCategoryGroup = GeneralCategoryGroup::Letter
+                .union(GeneralCategoryGroup::Number)
+                .union(GeneralCategoryGroup::Symbol)
+                .union(GeneralCategoryGroup::PrivateUse);
+            self.cm
+                .titlecase_segment_with_adjustment(src, langid, options, |_data, ch| {
+                    HEAD_GROUPS.contains(self.gc.get(ch))
+                })
+        } else {
+            self.cm
+                .titlecase_segment_with_adjustment(src, langid, options, |data, ch| {
+                    data.is_cased(ch)
+                })
+        }
+    }
+
+    /// Returns the full titlecase mapping of the given string as a String, treating
+    /// the string as a single segment (and thus only titlecasing the beginning of it).
+    ///
+    /// This should typically be used as a lower-level helper to construct the titlecasing operation desired
+    /// by the application, for example one can titlecase on a per-word basis by mixing this with
+    /// a `WordSegmenter`.
+    ///
+    /// This function is context and language sensitive. Callers should pass the text's language
+    /// as a `LanguageIdentifier` (usually the `id` field of the `Locale`) if available, or
+    /// `Default::default()` for the root locale.
+    ///
+    /// See [`TitlecaseMapperBorrowed::titlecase_segment()`] for the equivalent lower-level function that returns a [`Writeable`]
+    ///
+    /// # Examples
+    ///
+    /// ```rust
+    /// use icu::casemap::TitlecaseMapper;
+    /// use icu::locale::langid;
+    ///
+    /// let cm = TitlecaseMapper::new();
+    /// let root = langid!("und");
+    ///
+    /// let default_options = Default::default();
+    ///
+    /// // note that the subsequent words are not titlecased, this function assumes
+    /// // that the entire string is a single segment and only titlecases at the beginning.
+    /// assert_eq!(cm.titlecase_segment_to_string("hEllO WorLd", &root, default_options), "Hello world");
+    /// assert_eq!(cm.titlecase_segment_to_string("Γειά σου Κόσμε", &root, default_options), "Γειά σου κόσμε");
+    /// assert_eq!(cm.titlecase_segment_to_string("नमस्ते दुनिया", &root, default_options), "नमस्ते दुनिया");
+    /// assert_eq!(cm.titlecase_segment_to_string("Привет мир", &root, default_options), "Привет мир");
+    ///
+    /// // Some behavior is language-sensitive
+    /// assert_eq!(cm.titlecase_segment_to_string("istanbul", &root, default_options), "Istanbul");
+    /// assert_eq!(cm.titlecase_segment_to_string("istanbul", &langid!("tr"), default_options), "İstanbul"); // Turkish dotted i
+    ///
+    /// assert_eq!(cm.titlecase_segment_to_string("և Երևանի", &root, default_options), "Եւ երևանի");
+    /// assert_eq!(cm.titlecase_segment_to_string("և Երևանի", &langid!("hy"), default_options), "Եվ երևանի"); // Eastern Armenian ech-yiwn ligature
+    ///
+    /// assert_eq!(cm.titlecase_segment_to_string("ijkdijk", &root, default_options), "Ijkdijk");
+    /// assert_eq!(cm.titlecase_segment_to_string("ijkdijk", &langid!("nl"), default_options), "IJkdijk"); // Dutch IJ digraph
+    /// ```
+    ///
+    /// Leading adjustment behaviors:
+    ///
+    /// ```rust
+    /// use icu::casemap::options::{LeadingAdjustment, TitlecaseOptions};
+    /// use icu::casemap::TitlecaseMapper;
+    /// use icu::locale::langid;
+    ///
+    /// let cm = TitlecaseMapper::new();
+    /// let root = langid!("und");
+    ///
+    /// let default_options = Default::default();
+    /// let mut no_adjust: TitlecaseOptions = Default::default();
+    /// no_adjust.leading_adjustment = Some(LeadingAdjustment::None);
+    ///
+    /// // Exhibits leading adjustment when set:
+    /// assert_eq!(
+    ///     cm.titlecase_segment_to_string("«hello»", &root, default_options),
+    ///     "«Hello»"
+    /// );
+    /// assert_eq!(
+    ///     cm.titlecase_segment_to_string("«hello»", &root, no_adjust),
+    ///     "«hello»"
+    /// );
+    ///
+    /// assert_eq!(
+    ///     cm.titlecase_segment_to_string("'Twas", &root, default_options),
+    ///     "'Twas"
+    /// );
+    /// assert_eq!(
+    ///     cm.titlecase_segment_to_string("'Twas", &root, no_adjust),
+    ///     "'twas"
+    /// );
+    ///
+    /// assert_eq!(
+    ///     cm.titlecase_segment_to_string("", &root, default_options),
+    ///     ""
+    /// );
+    /// assert_eq!(cm.titlecase_segment_to_string("", &root, no_adjust), "");
+    /// ```
+    ///
+    /// Tail casing behaviors:
+    ///
+    /// ```rust
+    /// use icu::casemap::options::{TitlecaseOptions, TrailingCase};
+    /// use icu::casemap::TitlecaseMapper;
+    /// use icu::locale::langid;
+    ///
+    /// let cm = TitlecaseMapper::new();
+    /// let root = langid!("und");
+    ///
+    /// let default_options = Default::default();
+    /// let mut preserve_case: TitlecaseOptions = Default::default();
+    /// preserve_case.trailing_case = Some(TrailingCase::Unchanged);
+    ///
+    /// // Exhibits trailing case when set:
+    /// assert_eq!(
+    ///     cm.titlecase_segment_to_string("spOngeBoB", &root, default_options),
+    ///     "Spongebob"
+    /// );
+    /// assert_eq!(
+    ///     cm.titlecase_segment_to_string("spOngeBoB", &root, preserve_case),
+    ///     "SpOngeBoB"
+    /// );
+    /// ```
+    pub fn titlecase_segment_to_string(
+        self,
+        src: &str,
+        langid: &LanguageIdentifier,
+        options: TitlecaseOptions,
+    ) -> String {
+        self.titlecase_segment(src, langid, options)
+            .write_to_string()
+            .into_owned()
+    }
+}
diff --git a/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/tests/conversions.rs b/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/tests/conversions.rs
new file mode 100644
index 0000000..285cdfb
--- /dev/null
+++ b/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/tests/conversions.rs
@@ -0,0 +1,409 @@
+// This file is part of ICU4X. For terms of use, please see the file
+// called LICENSE at the top level of the ICU4X source tree
+// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
+
+use icu_casemap::CaseMapper;
+use icu_locale_core::langid;
+
+#[test]
+fn test_simple_mappings() {
+    let case_mapping = CaseMapper::new();
+
+    // Basic case mapping
+    assert_eq!(case_mapping.simple_uppercase('a'), 'A');
+    assert_eq!(case_mapping.simple_lowercase('a'), 'a');
+    assert_eq!(case_mapping.simple_titlecase('a'), 'A');
+    assert_eq!(case_mapping.simple_fold('a'), 'a');
+    assert_eq!(case_mapping.simple_uppercase('A'), 'A');
+    assert_eq!(case_mapping.simple_lowercase('A'), 'a');
+    assert_eq!(case_mapping.simple_titlecase('A'), 'A');
+    assert_eq!(case_mapping.simple_fold('A'), 'a');
+
+    // Case mapping of titlecase character
+    assert_eq!(case_mapping.simple_uppercase('\u{1c4}'), '\u{1c4}');
+    assert_eq!(case_mapping.simple_titlecase('\u{1c4}'), '\u{1c5}');
+    assert_eq!(case_mapping.simple_lowercase('\u{1c4}'), '\u{1c6}');
+    assert_eq!(case_mapping.simple_uppercase('\u{1c5}'), '\u{1c4}');
+    assert_eq!(case_mapping.simple_titlecase('\u{1c5}'), '\u{1c5}');
+    assert_eq!(case_mapping.simple_lowercase('\u{1c5}'), '\u{1c6}');
+    assert_eq!(case_mapping.simple_uppercase('\u{1c6}'), '\u{1c4}');
+    assert_eq!(case_mapping.simple_titlecase('\u{1c6}'), '\u{1c5}');
+    assert_eq!(case_mapping.simple_lowercase('\u{1c6}'), '\u{1c6}');
+
+    // Turkic case folding
+    assert_eq!(case_mapping.simple_fold('I'), 'i');
+    assert_eq!(case_mapping.simple_fold_turkic('I'), 'ı');
+    assert_eq!(case_mapping.simple_fold('İ'), 'İ');
+    assert_eq!(case_mapping.simple_fold_turkic('İ'), 'i');
+
+    // Supplementary code points (Deseret)
+    assert_eq!(case_mapping.simple_uppercase('\u{1043c}'), '\u{10414}');
+    assert_eq!(case_mapping.simple_lowercase('\u{1043c}'), '\u{1043c}');
+    assert_eq!(case_mapping.simple_titlecase('\u{1043c}'), '\u{10414}');
+    assert_eq!(case_mapping.simple_fold('\u{1043c}'), '\u{1043c}');
+    assert_eq!(case_mapping.simple_uppercase('\u{10414}'), '\u{10414}');
+    assert_eq!(case_mapping.simple_lowercase('\u{10414}'), '\u{1043c}');
+    assert_eq!(case_mapping.simple_titlecase('\u{10414}'), '\u{10414}');
+    assert_eq!(case_mapping.simple_fold('\u{10414}'), '\u{1043c}');
+}
+
+// These and the below tests are taken from StringCaseTest::TestCaseConversion in ICU4C.
+#[test]
+fn test_full_mappings() {
+    let case_mapping = CaseMapper::new();
+    let root = langid!("und");
+    let tr = langid!("tr");
+    let lt = langid!("lt");
+
+    let uppercase_greek = "ΙΕΣΥΣ ΧΡΙΣΤΟΣ"; // "IESUS CHRISTOS"
+    let lowercase_greek = "ιεσυς χριστος"; // "IESUS CHRISTOS"
+    assert_eq!(
+        case_mapping.uppercase_to_string(lowercase_greek, &root),
+        uppercase_greek
+    );
+    assert_eq!(
+        case_mapping.lowercase_to_string(uppercase_greek, &root),
+        lowercase_greek
+    );
+    assert_eq!(
+        case_mapping.fold_string(uppercase_greek),
+        case_mapping.fold_string(lowercase_greek)
+    );
+
+    let lowercase_turkish_1 = "istanbul, not constantınople";
+    let uppercase_turkish_1 = "İSTANBUL, NOT CONSTANTINOPLE";
+    assert_eq!(
+        case_mapping.lowercase_to_string(uppercase_turkish_1, &root),
+        "i\u{307}stanbul, not constantinople"
+    );
+    assert_eq!(
+        case_mapping.lowercase_to_string(uppercase_turkish_1, &tr),
+        lowercase_turkish_1
+    );
+
+    let lowercase_turkish_2 = "topkapı palace, istanbul";
+    let uppercase_turkish_2 = "TOPKAPI PALACE, İSTANBUL";
+    assert_eq!(
+        case_mapping.uppercase_to_string(lowercase_turkish_2, &root),
+        "TOPKAPI PALACE, ISTANBUL"
+    );
+    assert_eq!(
+        case_mapping.uppercase_to_string(lowercase_turkish_2, &tr),
+        uppercase_turkish_2
+    );
+
+    let initial_german = "Süßmayrstraße";
+    let uppercase_german = "SÜSSMAYRSTRASSE";
+    assert_eq!(
+        case_mapping.uppercase_to_string(initial_german, &root),
+        uppercase_german
+    );
+
+    let before = "aBIΣßΣ/\u{5ffff}";
+    let after = "abiσßς/\u{5ffff}";
+    let after_turkish = "abıσßς/\u{5ffff}";
+    assert_eq!(case_mapping.lowercase_to_string(before, &root), after);
+    assert_eq!(case_mapping.lowercase_to_string(before, &tr), after_turkish);
+
+    let before = "aBiςßσ/\u{fb03}\u{fb03}\u{fb03}\u{5ffff}";
+    let after = "ABIΣSSΣ/FFIFFIFFI\u{5ffff}";
+    let after_turkish = "ABİΣSSΣ/FFIFFIFFI\u{5ffff}";
+    assert_eq!(case_mapping.uppercase_to_string(before, &root), after);
+    assert_eq!(case_mapping.uppercase_to_string(before, &tr), after_turkish);
+
+    let before = "ßa";
+    let after = "SSA";
+    assert_eq!(case_mapping.uppercase_to_string(before, &root), after);
+
+    let initial_deseret = "\u{1043c}\u{10414}";
+    let upper_deseret = "\u{10414}\u{10414}";
+    let lower_deseret = "\u{1043c}\u{1043c}";
+    assert_eq!(
+        case_mapping.uppercase_to_string(initial_deseret, &root),
+        upper_deseret
+    );
+    assert_eq!(
+        case_mapping.lowercase_to_string(initial_deseret, &root),
+        lower_deseret
+    );
+
+    // lj ligature
+    let initial_ligature = "\u{1c7}\u{1c8}\u{1c9}";
+    let lower_ligature = "\u{1c9}\u{1c9}\u{1c9}";
+    let upper_ligature = "\u{1c7}\u{1c7}\u{1c7}";
+    assert_eq!(
+        case_mapping.uppercase_to_string(initial_ligature, &root),
+        upper_ligature
+    );
+    assert_eq!(
+        case_mapping.lowercase_to_string(initial_ligature, &root),
+        lower_ligature
+    );
+
+    // Sigmas preceded and/or followed by cased letters
+    let initial_sigmas = "i\u{307}\u{3a3}\u{308}j \u{307}\u{3a3}\u{308}j i\u{ad}\u{3a3}\u{308} \u{307}\u{3a3}\u{308}";
+    let lower_sigmas = "i\u{307}\u{3c3}\u{308}j \u{307}\u{3c3}\u{308}j i\u{ad}\u{3c2}\u{308} \u{307}\u{3c3}\u{308}";
+    let upper_sigmas = "I\u{307}\u{3a3}\u{308}J \u{307}\u{3a3}\u{308}J I\u{ad}\u{3a3}\u{308} \u{307}\u{3a3}\u{308}";
+    assert_eq!(
+        case_mapping.uppercase_to_string(initial_sigmas, &root),
+        upper_sigmas
+    );
+    assert_eq!(
+        case_mapping.lowercase_to_string(initial_sigmas, &root),
+        lower_sigmas
+    );
+
+    // Turkish & Azerbaijani dotless i & dotted I:
+    // Remove dot above if there was a capital I before and there are no more accents above.
+    let initial_dots = "I İ I\u{307} I\u{327}\u{307} I\u{301}\u{307} I\u{327}\u{307}\u{301}";
+    let after = "i i\u{307} i\u{307} i\u{327}\u{307} i\u{301}\u{307} i\u{327}\u{307}\u{301}";
+    let after_turkish = "ı i i i\u{327} ı\u{301}\u{307} i\u{327}\u{301}";
+    assert_eq!(case_mapping.lowercase_to_string(initial_dots, &root), after);
+    assert_eq!(
+        case_mapping.lowercase_to_string(initial_dots, &tr),
+        after_turkish
+    );
+
+    // Lithuanian dot above in uppercasing
+    let initial_dots = "a\u{307} \u{307} i\u{307} j\u{327}\u{307} j\u{301}\u{307}";
+    let after = "A\u{307} \u{307} I\u{307} J\u{327}\u{307} J\u{301}\u{307}";
+    let after_lithuanian = "A\u{307} \u{307} I J\u{327} J\u{301}\u{307}";
+    assert_eq!(case_mapping.uppercase_to_string(initial_dots, &root), after);
+    assert_eq!(
+        case_mapping.uppercase_to_string(initial_dots, &lt),
+        after_lithuanian
+    );
+
+    // Lithuanian adds dot above to i in lowercasing if there are more above accents
+    let initial_dots = "I I\u{301} J J\u{301} \u{12e} \u{12e}\u{301} \u{cc}\u{cd}\u{128}";
+    let after = "i i\u{301} j j\u{301} \u{12f} \u{12f}\u{301} \u{ec}\u{ed}\u{129}";
+    let after_lithuanian = "i i\u{307}\u{301} j j\u{307}\u{301} \u{12f} \u{12f}\u{307}\u{301} i\u{307}\u{300}i\u{307}\u{301}i\u{307}\u{303}";
+    assert_eq!(case_mapping.lowercase_to_string(initial_dots, &root), after);
+    assert_eq!(
+        case_mapping.lowercase_to_string(initial_dots, &lt),
+        after_lithuanian
+    );
+
+    // Test case folding
+    let initial = "Aßµ\u{fb03}\u{1040c}İı";
+    let simple = "assμffi\u{10434}i\u{307}ı";
+    let turkic = "assμffi\u{10434}iı";
+    assert_eq!(case_mapping.fold_string(initial), simple);
+    assert_eq!(case_mapping.fold_turkic_string(initial), turkic);
+}
+
+#[test]
+fn test_armenian() {
+    let cm = CaseMapper::new();
+    let root = langid!("und");
+    let east = langid!("hy");
+    let west = langid!("hyw");
+    let default_options = Default::default();
+
+    let s = "և Երևանի";
+
+    assert_eq!(cm.uppercase_to_string(s, &root), "ԵՒ ԵՐԵՒԱՆԻ");
+    assert_eq!(cm.uppercase_to_string(s, &east), "ԵՎ ԵՐԵՎԱՆԻ");
+    assert_eq!(cm.uppercase_to_string(s, &west), "ԵՒ ԵՐԵՒԱՆԻ");
+
+    let ew = "և";
+    let yerevan = "Երևանի";
+    assert_eq!(
+        cm.titlecase_segment_with_only_case_data_to_string(ew, &root, default_options),
+        "Եւ"
+    );
+    assert_eq!(
+        cm.titlecase_segment_with_only_case_data_to_string(yerevan, &root, default_options),
+        "Երևանի"
+    );
+    assert_eq!(
+        cm.titlecase_segment_with_only_case_data_to_string(ew, &east, default_options),
+        "Եվ"
+    );
+    assert_eq!(
+        cm.titlecase_segment_with_only_case_data_to_string(yerevan, &east, default_options),
+        "Երևանի"
+    );
+    assert_eq!(
+        cm.titlecase_segment_with_only_case_data_to_string(ew, &west, default_options),
+        "Եւ"
+    );
+    assert_eq!(
+        cm.titlecase_segment_with_only_case_data_to_string(yerevan, &west, default_options),
+        "Երևանի"
+    );
+}
+
+#[test]
+fn test_dutch() {
+    let cm = CaseMapper::new();
+    let nl = langid!("nl");
+    let default_options = Default::default();
+
+    assert_eq!(
+        cm.titlecase_segment_with_only_case_data_to_string("ijssel", &nl, default_options),
+        "IJssel"
+    );
+    assert_eq!(
+        cm.titlecase_segment_with_only_case_data_to_string("igloo", &nl, default_options),
+        "Igloo"
+    );
+    assert_eq!(
+        cm.titlecase_segment_with_only_case_data_to_string("IJMUIDEN", &nl, default_options),
+        "IJmuiden"
+    );
+
+    assert_eq!(
+        cm.titlecase_segment_with_only_case_data_to_string("ij", &nl, default_options),
+        "IJ"
+    );
+    assert_eq!(
+        cm.titlecase_segment_with_only_case_data_to_string("IJ", &nl, default_options),
+        "IJ"
+    );
+    assert_eq!(
+        cm.titlecase_segment_with_only_case_data_to_string("íj́", &nl, default_options),
+        "ÍJ́"
+    );
+    assert_eq!(
+        cm.titlecase_segment_with_only_case_data_to_string("ÍJ́", &nl, default_options),
+        "ÍJ́"
+    );
+    assert_eq!(
+        cm.titlecase_segment_with_only_case_data_to_string("íJ́", &nl, default_options),
+        "ÍJ́"
+    );
+    assert_eq!(
+        cm.titlecase_segment_with_only_case_data_to_string("Ij́", &nl, default_options),
+        "Ij́"
+    );
+    assert_eq!(
+        cm.titlecase_segment_with_only_case_data_to_string("ij́", &nl, default_options),
+        "Ij́"
+    );
+    assert_eq!(
+        cm.titlecase_segment_with_only_case_data_to_string("ïj́", &nl, default_options),
+        "Ïj́"
+    );
+    assert_eq!(
+        cm.titlecase_segment_with_only_case_data_to_string("íj\u{0308}", &nl, default_options),
+        "Íj\u{0308}"
+    );
+    assert_eq!(
+        cm.titlecase_segment_with_only_case_data_to_string("íj́\u{1D16E}", &nl, default_options),
+        "Íj́\u{1D16E}"
+    );
+    assert_eq!(
+        cm.titlecase_segment_with_only_case_data_to_string("íj\u{1ABE}", &nl, default_options),
+        "Íj\u{1ABE}"
+    );
+
+    assert_eq!(
+        cm.titlecase_segment_with_only_case_data_to_string("ijabc", &nl, default_options),
+        "IJabc"
+    );
+    assert_eq!(
+        cm.titlecase_segment_with_only_case_data_to_string("IJabc", &nl, default_options),
+        "IJabc"
+    );
+    assert_eq!(
+        cm.titlecase_segment_with_only_case_data_to_string("íj́abc", &nl, default_options),
+        "ÍJ́abc"
+    );
+    assert_eq!(
+        cm.titlecase_segment_with_only_case_data_to_string("ÍJ́abc", &nl, default_options),
+        "ÍJ́abc"
+    );
+    assert_eq!(
+        cm.titlecase_segment_with_only_case_data_to_string("íJ́abc", &nl, default_options),
+        "ÍJ́abc"
+    );
+    assert_eq!(
+        cm.titlecase_segment_with_only_case_data_to_string("Ij́abc", &nl, default_options),
+        "Ij́abc"
+    );
+    assert_eq!(
+        cm.titlecase_segment_with_only_case_data_to_string("ij́abc", &nl, default_options),
+        "Ij́abc"
+    );
+    assert_eq!(
+        cm.titlecase_segment_with_only_case_data_to_string("ïj́abc", &nl, default_options),
+        "Ïj́abc"
+    );
+    assert_eq!(
+        cm.titlecase_segment_with_only_case_data_to_string("íjabc\u{0308}", &nl, default_options),
+        "Íjabc\u{0308}"
+    );
+    assert_eq!(
+        cm.titlecase_segment_with_only_case_data_to_string("íj́abc\u{1D16E}", &nl, default_options),
+        "ÍJ́abc\u{1D16E}"
+    );
+    assert_eq!(
+        cm.titlecase_segment_with_only_case_data_to_string("íjabc\u{1ABE}", &nl, default_options),
+        "Íjabc\u{1ABE}"
+    );
+}
+
+#[test]
+fn test_greek_upper() {
+    let nfc = icu_normalizer::ComposingNormalizerBorrowed::new_nfc();
+    let nfd = icu_normalizer::DecomposingNormalizerBorrowed::new_nfd();
+
+    let cm = CaseMapper::new();
+    let modern_greek = &langid!("el");
+
+    let assert_greek_uppercase = |input: &str, expected: &str| {
+        assert_eq!(
+            cm.uppercase_to_string(nfc.normalize(input).as_ref(), modern_greek),
+            nfc.normalize(expected)
+        );
+        assert_eq!(
+            cm.uppercase_to_string(nfd.normalize(input).as_ref(), modern_greek),
+            nfd.normalize(expected)
+        );
+    };
+
+    // https://unicode-org.atlassian.net/browse/ICU-5456
+    assert_greek_uppercase("άδικος, κείμενο, ίριδα", "ΑΔΙΚΟΣ, ΚΕΙΜΕΝΟ, ΙΡΙΔΑ");
+    // https://bugzilla.mozilla.org/show_bug.cgi?id=307039
+    // https://bug307039.bmoattachments.org/attachment.cgi?id=194893
+    assert_greek_uppercase("Πατάτα", "ΠΑΤΑΤΑ");
+    assert_greek_uppercase("Αέρας, Μυστήριο, Ωραίο", "ΑΕΡΑΣ, ΜΥΣΤΗΡΙΟ, ΩΡΑΙΟ");
+    assert_greek_uppercase("Μαΐου, Πόρος, Ρύθμιση", "ΜΑΪΟΥ, ΠΟΡΟΣ, ΡΥΘΜΙΣΗ");
+    assert_greek_uppercase("ΰ, Τηρώ, Μάιος", "Ϋ, ΤΗΡΩ, ΜΑΪΟΣ");
+    assert_greek_uppercase("άυλος", "ΑΫΛΟΣ");
+    assert_greek_uppercase("ΑΫΛΟΣ", "ΑΫΛΟΣ");
+    assert_greek_uppercase(
+        "Άκλιτα ρήματα ή άκλιτες μετοχές",
+        "ΑΚΛΙΤΑ ΡΗΜΑΤΑ Ή ΑΚΛΙΤΕΣ ΜΕΤΟΧΕΣ",
+    );
+    // http://www.unicode.org/udhr/d/udhr_ell_monotonic.html
+    assert_greek_uppercase(
+        "Επειδή η αναγνώριση της αξιοπρέπειας",
+        "ΕΠΕΙΔΗ Η ΑΝΑΓΝΩΡΙΣΗ ΤΗΣ ΑΞΙΟΠΡΕΠΕΙΑΣ",
+    );
+    assert_greek_uppercase("νομικού ή διεθνούς", "ΝΟΜΙΚΟΥ Ή ΔΙΕΘΝΟΥΣ");
+    // http://unicode.org/udhr/d/udhr_ell_polytonic.html
+    assert_greek_uppercase("Ἐπειδὴ ἡ ἀναγνώριση", "ΕΠΕΙΔΗ Η ΑΝΑΓΝΩΡΙΣΗ");
+    assert_greek_uppercase("νομικοῦ ἢ διεθνοῦς", "ΝΟΜΙΚΟΥ Ή ΔΙΕΘΝΟΥΣ");
+    // From Google bug report
+    assert_greek_uppercase("Νέο, Δημιουργία", "ΝΕΟ, ΔΗΜΙΟΥΡΓΙΑ");
+    // http://crbug.com/234797
+    assert_greek_uppercase(
+        "Ελάτε να φάτε τα καλύτερα παϊδάκια!",
+        "ΕΛΑΤΕ ΝΑ ΦΑΤΕ ΤΑ ΚΑΛΥΤΕΡΑ ΠΑΪΔΑΚΙΑ!",
+    );
+    assert_greek_uppercase("Μαΐου, τρόλεϊ", "ΜΑΪΟΥ, ΤΡΟΛΕΪ");
+    assert_greek_uppercase("Το ένα ή το άλλο.", "ΤΟ ΕΝΑ Ή ΤΟ ΑΛΛΟ.");
+    // http://multilingualtypesetting.co.uk/blog/greek-typesetting-tips/
+    assert_greek_uppercase("ρωμέικα", "ΡΩΜΕΪΚΑ");
+    assert_greek_uppercase("ή.", "Ή.");
+
+    // The ὑπογεγραμμέναι become Ι as in default case conversion, but they are
+    // specially handled by the implementation.
+    assert_greek_uppercase("ᾠδή, -ήν, -ῆς, -ῇ", "ΩΙΔΗ, -ΗΝ, -ΗΣ, -ΗΙ");
+    assert_greek_uppercase("ᾍδης", "ΑΙΔΗΣ");
+
+    // Handle breathing marks on rho
+    assert_greek_uppercase("ῥήματα ῤήματα", "ΡΗΜΑΤΑ ΡΗΜΑΤΑ");
+}
diff --git a/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/tests/gen_greek_to_me.rs b/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/tests/gen_greek_to_me.rs
new file mode 100644
index 0000000..f42b4fb
--- /dev/null
+++ b/third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/tests/gen_greek_to_me.rs
@@ -0,0 +1,199 @@
+// This file is part of ICU4X. For terms of use, please see the file
+// called LICENSE at the top level of the ICU4X source tree
+// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
+
+use icu_casemap::greek_to_me::{
+    self, GreekDiacritics, GreekPrecomposedLetterData, GreekVowel, PackedGreekPrecomposedLetterData,
+};
+use icu_casemap::CaseMapper;
+use icu_normalizer::DecomposingNormalizerBorrowed;
+use icu_properties::{
+    props::{GeneralCategory, GeneralCategoryGroup, Script},
+    CodePointMapData,
+};
+use std::collections::BTreeMap;
+use std::fmt::Write;
+
+fn main() {
+    let decomposer = DecomposingNormalizerBorrowed::new_nfd();
+    let script = CodePointMapData::<Script>::new();
+    let gc = CodePointMapData::<GeneralCategory>::new();
+    let cm = CaseMapper::new();
+
+    let mut vec_370 = vec![0; 0x400 - 0x370];
+    let mut vec_1f00 = vec![0; 0x100];
+    let mut extras = BTreeMap::new();
+
+    // Loop over all codepoints
+    for range in script.iter_ranges_for_value(Script::Greek) {
+        for ch in range {
+            if let Ok(ch) = char::try_from(ch) {
+                if !GeneralCategoryGroup::Letter.contains(gc.get(ch)) {
+                    continue;
+                }
+                let mut buf = [0u8; 4];
+                let nfd = decomposer.normalize_utf8(ch.encode_utf8(&mut buf).as_bytes());
+                let mut data: Option<GreekPrecomposedLetterData> = None;
+
+                for nfd_ch in nfd.chars() {
+                    match nfd_ch {
+                        // accented: [:toNFD=/[\u0300\u0301\u0342\u0302\u0303\u0311]/:]&[:Grek:]&[:L:] (from the JSPs: toNFD is an extension).
+                        greek_to_me::diacritics!(ACCENTS) => {
+                            if let Some(GreekPrecomposedLetterData::Vowel(_, ref mut diacritics)) =
+                                data
+                            {
+                                diacritics.accented = true;
+                            } else {
+                                panic!("Found character {ch} that has diacritics but is not a Greek vowel");
+                            }
+                        }
+                        // dialytika: [:toNFD=/[\u0308]/:]&[:Grek:]&[:L:] (from the JSPs: toNFD is an extension).
+                        greek_to_me::diacritics!(DIALYTIKA) => {
+                            if let Some(GreekPrecomposedLetterData::Vowel(_, ref mut diacritics)) =
+                                data
+                            {
+                                diacritics.dialytika = true;
+                            } else {
+                                panic!("Found character {ch} that has diacritics but is not a Greek vowel");
+                            }
+                        }
+                        // precomposed_ypogegrammeni [:toNFD=/[\u0345]/:]&[:Grek:]&[:L:] (from the JSPs: toNFD is an extension).
+                        greek_to_me::diacritics!(YPOGEGRAMMENI) => {
+                            if let Some(GreekPrecomposedLetterData::Vowel(_, ref mut diacritics)) =
+                                data
+                            {
+                                diacritics.ypogegrammeni = true;
+                            } else {
+                                panic!("Found character {ch} that has diacritics but is not a Greek vowel");
+                            }
+                        }
+                        greek_to_me::diacritics!(BREATHING_AND_LENGTH)
+                        | greek_to_me::diacritics!(ACCENTS) => {
+                            // Rho takes breathing marks but other consonants should not
+                            if let Some(GreekPrecomposedLetterData::Consonant(false)) = data {
+                                panic!("Found character {ch} that has diacritics but is not a Greek vowel");
+                            }
+                        }
+                        // Ignore all small letters
+                        '\u{1D00}'..='\u{1DBF}' | '\u{AB65}' => (),
+                        // caps: [[:Grek:]&[:L:]-[\u1D00-\u1DBF\uAB65]] . NFD, remove non-letters, uppercase
+                        letter if GeneralCategoryGroup::Letter.contains(gc.get(letter)) => {
+                            let uppercased = cm.uppercase_to_string(
+                                letter.encode_utf8(&mut [0; 4]),
+                                &Default::default(),
+                            );
+                            let mut iter = uppercased.chars();
+                            let uppercased = iter.next().unwrap();
+                            assert!(
+                                iter.next().is_none(),
+                                "{letter} Should uppercase to a single letter char, instead uppercased to {uppercased:?}"
+                            );
+
+                            if let Ok(vowel) = GreekVowel::try_from(uppercased) {
+                                data = Some(GreekPrecomposedLetterData::Vowel(
+                                    vowel,
+                                    GreekDiacritics::default(),
+                                ))
+                            } else {
+                                let is_rho = uppercased == greek_to_me::CAPITAL_RHO;
+                                data = Some(GreekPrecomposedLetterData::Consonant(is_rho))
+                            };
+                        }
+                        _ => (),
+                    }
+                }
+
+                if let Some(data) = data {
+                    let packed = PackedGreekPrecomposedLetterData::from(data);
+                    assert_eq!(
+                        Ok(data),
+                        packed.try_into(),
+                        "packed data for {ch} ({packed:?}) must roundtrip!"
+                    );
+                    let ch_i = ch as usize;
+                    if (0x370..=0x3FF).contains(&ch_i) {
+                        vec_370[ch_i - 0x370] = packed.0;
+                    } else if (0x1f00..0x1fff).contains(&ch_i) {
+                        vec_1f00[ch_i - 0x1f00] = packed.0;
+                    } else {
+                        extras.insert(ch, packed.0);
+                    }
+                }
+            }
+        }
+    }
+
+    vec_370.truncate(
+        vec_370
+            .iter()
+            .rposition(|x| *x != 0)
+            .expect("must have some greek data")
+            + 1,
+    );
+    vec_1f00.truncate(
+        vec_1f00
+            .iter()
+            .rposition(|x| *x != 0)
+            .expect("must have some greek data")
+            + 1,
+    );
+
+    let mut others = String::new();
+    for (ch, data) in extras {
+        writeln!(&mut others, "        '{ch}' => {data},").unwrap();
+    }
+
+    let output = format!(
+        r#"// This file is part of ICU4X. For terms of use, please see the file
+// called LICENSE at the top level of the ICU4X source tree
+// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
+
+// This file is generated by running `cargo test --test gen_greek_to_me --features compiled_data,datagen
+//
+// Do not edit manually
+
+// All u8s in this file are PackedGreekPrecomposedLetterDatas, see parent module
+
+/// Data for characters in U+370-U+3FF
+pub(crate) const DATA_370: [u8; 0x{:x}] = {vec_370:?};
+/// Data for characters in U+1F00-U+1FFF
+pub(crate) const DATA_1F00: [u8; 0x{:x}] = {vec_1f00:?};
+
+/// Characters like the ohm sign that do not belong in the two blocks above
+pub(crate) fn match_extras(ch: char) -> Option<u8> {{
+    Some(match ch {{
+{others}
+        _ => return None
+    }})
+}}
+"#,
+        vec_370.len(),
+        vec_1f00.len(),
+    );
+
+    let local = include_str!("../src/greek_to_me/data.rs");
+
+    // We cannot just check if the two are unequal because on Windows core.autocrlf
+    // may have messed with the line endings on the file, or it may have not (either
+    // due to a changed setting, or due to the code being in a cargo cache/vendor. We also
+    // cannot fix this by passing `--config newline_style=unix` to rustfmt. We must
+    // perform a `\r`-agnostic comparison.
+    //
+    // (technically this should only catch `\r\n` and not just `\r` but for a golden
+    // test on rustfmt output it does not matter)
+    if local
+        .trim()
+        .chars()
+        .filter(|&x| x != '\r')
+        .ne(output.trim().chars().filter(|&x| x != '\r'))
+    {
+        println!(
+            r#"Please copy the following file to src/greek_to_me/data.rs:
+========================================================
+{output}
+========================================================"#
+        );
+
+        panic!("Found mismatch between generated Greek specialcasing data and checked-in data. Please check in the updated file shown above.");
+    }
+}
diff --git a/third_party/rust/chromium_crates_io/vendor/icu_casemap_data-2.0.0-beta2/.cargo_vcs_info.json b/third_party/rust/chromium_crates_io/vendor/icu_casemap_data-2.0.0-beta2/.cargo_vcs_info.json
new file mode 100644
index 0000000..ad2855c
--- /dev/null
+++ b/third_party/rust/chromium_crates_io/vendor/icu_casemap_data-2.0.0-beta2/.cargo_vcs_info.json
@@ -0,0 +1,6 @@
+{
+  "git": {
+    "sha1": "610757581c7d141a6f20f97fe839ef171c320bb1"
+  },
+  "path_in_vcs": "provider/data/casemap"
+}
\ No newline at end of file
diff --git a/third_party/rust/chromium_crates_io/vendor/icu_casemap_data-2.0.0-beta2/Cargo.lock b/third_party/rust/chromium_crates_io/vendor/icu_casemap_data-2.0.0-beta2/Cargo.lock
new file mode 100644
index 0000000..0edf428
--- /dev/null
+++ b/third_party/rust/chromium_crates_io/vendor/icu_casemap_data-2.0.0-beta2/Cargo.lock
@@ -0,0 +1,230 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "displaydoc"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "icu_casemap_data"
+version = "2.0.0-beta2"
+dependencies = [
+ "icu_provider_baked",
+]
+
+[[package]]
+name = "icu_locale_core"
+version = "2.0.0-beta2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b80161b66511e4eb415ef110c67ea8cab4400b749f9e30c8691fff1354934b6b"
+dependencies = [
+ "displaydoc",
+ "litemap",
+ "tinystr",
+ "writeable",
+]
+
+[[package]]
+name = "icu_provider"
+version = "2.0.0-beta2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e0d462aad52985bb71e3140fcc44e54d816cf7f2c3f25cd9b090cc77a9798504"
+dependencies = [
+ "displaydoc",
+ "icu_locale_core",
+ "stable_deref_trait",
+ "tinystr",
+ "writeable",
+ "yoke",
+ "zerofrom",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_provider_baked"
+version = "2.0.0-beta2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2794f00ee1999495f4f1a1e35aee8f54fe7cfcbcf909ec05b60522377200aecb"
+dependencies = [
+ "icu_provider",
+ "writeable",
+ "zerotrie",
+ "zerovec",
+]
+
+[[package]]
+name = "litemap"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.93"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.38"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.218"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.218"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "stable_deref_trait"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
+
+[[package]]
+name = "syn"
+version = "2.0.98"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "synstructure"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tinystr"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b"
+dependencies = [
+ "displaydoc",
+ "zerovec",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe"
+
+[[package]]
+name = "writeable"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb"
+
+[[package]]
+name = "yoke"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc"
+dependencies = [
+ "serde",
+ "stable_deref_trait",
+ "yoke-derive",
+ "zerofrom",
+]
+
+[[package]]
+name = "yoke-derive"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "synstructure",
+]
+
+[[package]]
+name = "zerofrom"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5"
+dependencies = [
+ "zerofrom-derive",
+]
+
+[[package]]
+name = "zerofrom-derive"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "synstructure",
+]
+
+[[package]]
+name = "zerotrie"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b7a6cf4865aac8394f19ad46e37f60b929c1ba5eed798b96a32820aa9392929"
+dependencies = [
+ "displaydoc",
+]
+
+[[package]]
+name = "zerovec"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94e62113720e311984f461c56b00457ae9981c0bc7859d22306cc2ae2f95571c"
+dependencies = [
+ "zerofrom",
+ "zerovec-derive",
+]
+
+[[package]]
+name = "zerovec-derive"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
diff --git a/third_party/rust/chromium_crates_io/vendor/icu_casemap_data-2.0.0-beta2/Cargo.toml b/third_party/rust/chromium_crates_io/vendor/icu_casemap_data-2.0.0-beta2/Cargo.toml
index bcd3a39..3ed425c2 100644
--- a/third_party/rust/chromium_crates_io/vendor/icu_casemap_data-2.0.0-beta2/Cargo.toml
+++ b/third_party/rust/chromium_crates_io/vendor/icu_casemap_data-2.0.0-beta2/Cargo.toml
@@ -1,20 +1,61 @@
-# 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.
-
-# @generated from third_party/rust/chromium_crates_io/removed_Cargo.toml
-# by tools/crates/gnrt. Do not edit!
-
-# This is an empty crate that has replaced the 'icu_casemap_data' crate, since
-# it was listed in `resolve.remove_crates` in gnrt_config.toml.
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies.
+#
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
 
 [package]
+edition = "2021"
+rust-version = "1.81"
 name = "icu_casemap_data"
 version = "2.0.0-beta2"
+authors = ["The ICU4X Project Developers"]
+build = false
+include = [
+    "data/**/*",
+    "src/**/*",
+    "examples/**/*",
+    "benches/**/*",
+    "tests/**/*",
+    "Cargo.toml",
+    "LICENSE",
+    "README.md",
+]
+autolib = false
+autobins = false
+autoexamples = false
+autotests = false
+autobenches = false
+description = "Data for the icu_casemap crate"
+homepage = "https://icu4x.unicode.org"
+readme = "README.md"
+categories = ["internationalization"]
+license = "Unicode-3.0"
+repository = "https://github.com/unicode-org/icu4x"
 
-[features]
+[package.metadata.sources.cldr]
+tagged = "47.0.0-BETA1"
+
+[package.metadata.sources.icuexport]
+tagged = "release-77-rc"
+
+[package.metadata.sources.segmenter_lstm]
+tagged = "v0.1.0"
+
+[lib]
+name = "icu_casemap_data"
+path = "src/lib.rs"
 
 [dependencies.icu_provider_baked]
 version = "~2.0.0-beta2"
-default_features = false
-features = []
+default-features = false
+
+[lints.rust.unexpected_cfgs]
+level = "warn"
+priority = 0
+check-cfg = ["cfg(icu4x_custom_data)"]
diff --git a/third_party/rust/chromium_crates_io/vendor/icu_casemap_data-2.0.0-beta2/Cargo.toml.orig b/third_party/rust/chromium_crates_io/vendor/icu_casemap_data-2.0.0-beta2/Cargo.toml.orig
new file mode 100644
index 0000000..a4e5f4d
--- /dev/null
+++ b/third_party/rust/chromium_crates_io/vendor/icu_casemap_data-2.0.0-beta2/Cargo.toml.orig
@@ -0,0 +1,28 @@
+# This file is part of ICU4X. For terms of use, please see the file
+# called LICENSE at the top level of the ICU4X source tree
+# (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
+
+[package]
+name = "icu_casemap_data"
+description = "Data for the icu_casemap crate"
+license = "Unicode-3.0"
+version.workspace = true
+
+authors.workspace = true
+categories.workspace = true
+edition.workspace = true
+homepage.workspace = true
+include.workspace = true
+repository.workspace = true
+rust-version.workspace = true
+
+[package.metadata.sources]
+cldr = { tagged = "47.0.0-BETA1" }
+icuexport = { tagged = "release-77-rc" }
+segmenter_lstm = { tagged = "v0.1.0" }
+
+[lints.rust]
+unexpected_cfgs = { level = "warn", check-cfg = ['cfg(icu4x_custom_data)'] }
+
+[dependencies]
+icu_provider_baked = { workspace = true }
diff --git a/third_party/rust/chromium_crates_io/vendor/icu_casemap_data-2.0.0-beta2/LICENSE b/third_party/rust/chromium_crates_io/vendor/icu_casemap_data-2.0.0-beta2/LICENSE
new file mode 100644
index 0000000..c9be601
--- /dev/null
+++ b/third_party/rust/chromium_crates_io/vendor/icu_casemap_data-2.0.0-beta2/LICENSE
@@ -0,0 +1,46 @@
+UNICODE LICENSE V3
+
+COPYRIGHT AND PERMISSION NOTICE
+
+Copyright © 2020-2024 Unicode, Inc.
+
+NOTICE TO USER: Carefully read the following legal agreement. BY
+DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR
+SOFTWARE, YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE
+TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT
+DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE.
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of data files and any associated documentation (the "Data Files") or
+software and any associated documentation (the "Software") to deal in the
+Data Files or Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, and/or sell
+copies of the Data Files or Software, and to permit persons to whom the
+Data Files or Software are furnished to do so, provided that either (a)
+this copyright and permission notice appear with all copies of the Data
+Files or Software, or (b) this copyright and permission notice appear in
+associated Documentation.
+
+THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
+THIRD PARTY RIGHTS.
+
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE
+BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES,
+OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA
+FILES OR SOFTWARE.
+
+Except as contained in this notice, the name of a copyright holder shall
+not be used in advertising or otherwise to promote the sale, use or other
+dealings in these Data Files or Software without prior written
+authorization of the copyright holder.
+
+SPDX-License-Identifier: Unicode-3.0
+
+—
+
+Portions of ICU4X may have been adapted from ICU4C and/or ICU4J.
+ICU 1.8.1 to ICU 57.1 © 1995-2016 International Business Machines Corporation and others.
diff --git a/third_party/rust/chromium_crates_io/vendor/icu_casemap_data-2.0.0-beta2/README.md b/third_party/rust/chromium_crates_io/vendor/icu_casemap_data-2.0.0-beta2/README.md
new file mode 100644
index 0000000..0bedeaff
--- /dev/null
+++ b/third_party/rust/chromium_crates_io/vendor/icu_casemap_data-2.0.0-beta2/README.md
@@ -0,0 +1,14 @@
+# icu_casemap_data [![crates.io](https://img.shields.io/crates/v/icu_casemap_data)](https://crates.io/crates/icu_casemap_data)
+
+<!-- cargo-rdme start -->
+
+Data for the `icu_casemap` crate
+
+This data was generated with CLDR version 47.0.0-BETA1, ICU version release-77-rc, and
+LSTM segmenter version v0.1.0.
+
+<!-- cargo-rdme end -->
+
+## More Information
+
+For more information on development, authorship, contributing etc. please visit [`ICU4X home page`](https://github.com/unicode-org/icu4x).
diff --git a/third_party/rust/chromium_crates_io/vendor/icu_casemap_data-2.0.0-beta2/data/case_map_unfold_v1.rs.data b/third_party/rust/chromium_crates_io/vendor/icu_casemap_data-2.0.0-beta2/data/case_map_unfold_v1.rs.data
new file mode 100644
index 0000000..7bfe589
--- /dev/null
+++ b/third_party/rust/chromium_crates_io/vendor/icu_casemap_data-2.0.0-beta2/data/case_map_unfold_v1.rs.data
@@ -0,0 +1,80 @@
+// @generated
+/// Implement `DataProvider<CaseMapUnfoldV1>` on the given struct using the data
+/// hardcoded in this file. This allows the struct to be used with
+/// `icu`'s `_unstable` constructors.
+///
+/// Using this implementation will embed the following data in the binary's data segment:
+/// * 968B[^1] for the singleton data struct
+///
+/// [^1]: these numbers can be smaller in practice due to linker deduplication
+#[doc(hidden)]
+#[macro_export]
+macro_rules! __impl_case_map_unfold_v1 {
+    ($ provider : ty) => {
+        #[clippy::msrv = "1.81"]
+        const _: () = <$provider>::MUST_USE_MAKE_PROVIDER_MACRO;
+        #[clippy::msrv = "1.81"]
+        impl $provider {
+            #[doc(hidden)]
+            pub const SINGLETON_CASE_MAP_UNFOLD_V1: &'static <icu::casemap::provider::CaseMapUnfoldV1 as icu_provider::DynamicDataMarker>::DataStruct = &icu::casemap::provider::CaseMapUnfold {
+                map: unsafe {
+                    #[allow(unused_unsafe)]
+                    zerovec::ZeroMap::from_parts_unchecked(unsafe { zerovec::vecs::VarZeroVec16::from_bytes_unchecked(b"I\0\x03\0\x05\0\x08\0\x0B\0\r\0\x0F\0\x12\0\x15\0\x18\0\x1A\0\x1C\0\x1F\0\"\0%\0(\0,\x000\x004\0:\0>\0B\0H\0L\0R\0X\0^\0b\0f\0l\0r\0x\0|\0\x82\0\x88\0\x8E\0\x92\0\x96\0\x9C\0\xA0\0\xA4\0\xA8\0\xAC\0\xB0\0\xB4\0\xB8\0\xBC\0\xC1\0\xC6\0\xCB\0\xD0\0\xD5\0\xDA\0\xDF\0\xE4\0\xE9\0\xEE\0\xF3\0\xF8\0\xFD\0\x02\x01\x07\x01\x0C\x01\x11\x01\x16\x01\x1B\x01 \x01%\x01*\x01/\x014\x019\x01>\x01a\xCA\xBEffffifflfiflh\xCC\xB1i\xCC\x87j\xCC\x8Cssstt\xCC\x88w\xCC\x8Ay\xCC\x8A\xCA\xBCn\xCE\xAC\xCE\xB9\xCE\xAE\xCE\xB9\xCE\xB1\xCD\x82\xCE\xB1\xCD\x82\xCE\xB9\xCE\xB1\xCE\xB9\xCE\xB7\xCD\x82\xCE\xB7\xCD\x82\xCE\xB9\xCE\xB7\xCE\xB9\xCE\xB9\xCC\x88\xCC\x80\xCE\xB9\xCC\x88\xCC\x81\xCE\xB9\xCC\x88\xCD\x82\xCE\xB9\xCD\x82\xCF\x81\xCC\x93\xCF\x85\xCC\x88\xCC\x80\xCF\x85\xCC\x88\xCC\x81\xCF\x85\xCC\x88\xCD\x82\xCF\x85\xCC\x93\xCF\x85\xCC\x93\xCC\x80\xCF\x85\xCC\x93\xCC\x81\xCF\x85\xCC\x93\xCD\x82\xCF\x85\xCD\x82\xCF\x89\xCD\x82\xCF\x89\xCD\x82\xCE\xB9\xCF\x89\xCE\xB9\xCF\x8E\xCE\xB9\xD5\xA5\xD6\x82\xD5\xB4\xD5\xA5\xD5\xB4\xD5\xAB\xD5\xB4\xD5\xAD\xD5\xB4\xD5\xB6\xD5\xBE\xD5\xB6\xE1\xBC\x80\xCE\xB9\xE1\xBC\x81\xCE\xB9\xE1\xBC\x82\xCE\xB9\xE1\xBC\x83\xCE\xB9\xE1\xBC\x84\xCE\xB9\xE1\xBC\x85\xCE\xB9\xE1\xBC\x86\xCE\xB9\xE1\xBC\x87\xCE\xB9\xE1\xBC\xA0\xCE\xB9\xE1\xBC\xA1\xCE\xB9\xE1\xBC\xA2\xCE\xB9\xE1\xBC\xA3\xCE\xB9\xE1\xBC\xA4\xCE\xB9\xE1\xBC\xA5\xCE\xB9\xE1\xBC\xA6\xCE\xB9\xE1\xBC\xA7\xCE\xB9\xE1\xBD\xA0\xCE\xB9\xE1\xBD\xA1\xCE\xB9\xE1\xBD\xA2\xCE\xB9\xE1\xBD\xA3\xCE\xB9\xE1\xBD\xA4\xCE\xB9\xE1\xBD\xA5\xCE\xB9\xE1\xBD\xA6\xCE\xB9\xE1\xBD\xA7\xCE\xB9\xE1\xBD\xB0\xCE\xB9\xE1\xBD\xB4\xCE\xB9\xE1\xBD\xBC\xCE\xB9") }, unsafe { zerovec::vecs::VarZeroVec16::from_bytes_unchecked(b"I\0\x03\0\x06\0\t\0\x0C\0\x0F\0\x12\0\x15\0\x17\0\x19\0\x1E\0$\0'\0*\0-\0/\x002\x005\08\0;\0A\0D\0G\0M\0P\0U\0X\0[\0^\0a\0f\0i\0l\0o\0r\0u\0x\0{\0~\0\x84\0\x87\0\x89\0\x8C\0\x8F\0\x92\0\x95\0\x98\0\x9E\0\xA4\0\xAA\0\xB0\0\xB6\0\xBC\0\xC2\0\xC8\0\xCE\0\xD4\0\xDA\0\xE0\0\xE6\0\xEC\0\xF2\0\xF8\0\xFE\0\x04\x01\n\x01\x10\x01\x16\x01\x1C\x01\"\x01(\x01+\x01.\x01\xE1\xBA\x9A\xEF\xAC\x80\xEF\xAC\x83\xEF\xAC\x84\xEF\xAC\x81\xEF\xAC\x82\xE1\xBA\x96\xC4\xB0\xC7\xB0\xC3\x9F\xE1\xBA\x9E\xEF\xAC\x85\xEF\xAC\x86\xE1\xBA\x97\xE1\xBA\x98\xE1\xBA\x99\xC5\x89\xE1\xBE\xB4\xE1\xBF\x84\xE1\xBE\xB6\xE1\xBE\xB7\xE1\xBE\xB3\xE1\xBE\xBC\xE1\xBF\x86\xE1\xBF\x87\xE1\xBF\x83\xE1\xBF\x8C\xE1\xBF\x92\xCE\x90\xE1\xBF\x93\xE1\xBF\x97\xE1\xBF\x96\xE1\xBF\xA4\xE1\xBF\xA2\xCE\xB0\xE1\xBF\xA3\xE1\xBF\xA7\xE1\xBD\x90\xE1\xBD\x92\xE1\xBD\x94\xE1\xBD\x96\xE1\xBF\xA6\xE1\xBF\xB6\xE1\xBF\xB7\xE1\xBF\xB3\xE1\xBF\xBC\xE1\xBF\xB4\xD6\x87\xEF\xAC\x94\xEF\xAC\x95\xEF\xAC\x97\xEF\xAC\x93\xEF\xAC\x96\xE1\xBE\x80\xE1\xBE\x88\xE1\xBE\x81\xE1\xBE\x89\xE1\xBE\x82\xE1\xBE\x8A\xE1\xBE\x83\xE1\xBE\x8B\xE1\xBE\x84\xE1\xBE\x8C\xE1\xBE\x85\xE1\xBE\x8D\xE1\xBE\x86\xE1\xBE\x8E\xE1\xBE\x87\xE1\xBE\x8F\xE1\xBE\x90\xE1\xBE\x98\xE1\xBE\x91\xE1\xBE\x99\xE1\xBE\x92\xE1\xBE\x9A\xE1\xBE\x93\xE1\xBE\x9B\xE1\xBE\x94\xE1\xBE\x9C\xE1\xBE\x95\xE1\xBE\x9D\xE1\xBE\x96\xE1\xBE\x9E\xE1\xBE\x97\xE1\xBE\x9F\xE1\xBE\xA0\xE1\xBE\xA8\xE1\xBE\xA1\xE1\xBE\xA9\xE1\xBE\xA2\xE1\xBE\xAA\xE1\xBE\xA3\xE1\xBE\xAB\xE1\xBE\xA4\xE1\xBE\xAC\xE1\xBE\xA5\xE1\xBE\xAD\xE1\xBE\xA6\xE1\xBE\xAE\xE1\xBE\xA7\xE1\xBE\xAF\xE1\xBE\xB2\xE1\xBF\x82\xE1\xBF\xB2") })
+                },
+            };
+        }
+        #[clippy::msrv = "1.81"]
+        impl icu_provider::DataProvider<icu::casemap::provider::CaseMapUnfoldV1> for $provider {
+            fn load(&self, req: icu_provider::DataRequest) -> Result<icu_provider::DataResponse<icu::casemap::provider::CaseMapUnfoldV1>, icu_provider::DataError> {
+                if req.id.locale.is_default() {
+                    Ok(icu_provider::DataResponse { payload: icu_provider::DataPayload::from_static_ref(Self::SINGLETON_CASE_MAP_UNFOLD_V1), metadata: icu_provider::DataResponseMetadata::default() })
+                } else {
+                    Err(icu_provider::DataErrorKind::InvalidRequest.with_req(<icu::casemap::provider::CaseMapUnfoldV1 as icu_provider::DataMarker>::INFO, req))
+                }
+            }
+        }
+    };
+    ($ provider : ty , ITER) => {
+        __impl_case_map_unfold_v1!($provider);
+        #[clippy::msrv = "1.81"]
+        impl icu_provider::IterableDataProvider<icu::casemap::provider::CaseMapUnfoldV1> for $provider {
+            fn iter_ids(&self) -> Result<std::collections::BtreeSet<icu_provider::DataIdentifierCow<'static>>, icu_provider::DataError> {
+                Ok([Default::default()].into_iter().collect())
+            }
+        }
+    };
+    ($ provider : ty , DRY) => {
+        __impl_case_map_unfold_v1!($provider);
+        #[clippy::msrv = "1.81"]
+        impl icu_provider::DryDataProvider<icu::casemap::provider::CaseMapUnfoldV1> for $provider {
+            fn dry_load(&self, req: icu_provider::DataRequest) -> Result<icu_provider::DataResponseMetadata, icu_provider::DataError> {
+                if req.id.locale.is_default() {
+                    Ok(icu_provider::DataResponseMetadata::default())
+                } else {
+                    Err(icu_provider::DataErrorKind::InvalidRequest.with_req(<icu::casemap::provider::CaseMapUnfoldV1 as icu_provider::DataMarker>::INFO, req))
+                }
+            }
+        }
+    };
+    ($ provider : ty , DRY , ITER) => {
+        __impl_case_map_unfold_v1!($provider);
+        #[clippy::msrv = "1.81"]
+        impl icu_provider::DryDataProvider<icu::casemap::provider::CaseMapUnfoldV1> for $provider {
+            fn dry_load(&self, req: icu_provider::DataRequest) -> Result<icu_provider::DataResponseMetadata, icu_provider::DataError> {
+                if req.id.locale.is_default() {
+                    Ok(icu_provider::DataResponseMetadata::default())
+                } else {
+                    Err(icu_provider::DataErrorKind::InvalidRequest.with_req(<icu::casemap::provider::CaseMapUnfoldV1 as icu_provider::DataMarker>::INFO, req))
+                }
+            }
+        }
+        #[clippy::msrv = "1.81"]
+        impl icu_provider::IterableDataProvider<icu::casemap::provider::CaseMapUnfoldV1> for $provider {
+            fn iter_ids(&self) -> Result<std::collections::BtreeSet<icu_provider::DataIdentifierCow<'static>>, icu_provider::DataError> {
+                Ok([Default::default()].into_iter().collect())
+            }
+        }
+    };
+}
+#[doc(inline)]
+pub use __impl_case_map_unfold_v1 as impl_case_map_unfold_v1;
diff --git a/third_party/rust/chromium_crates_io/vendor/icu_casemap_data-2.0.0-beta2/data/case_map_v1.rs.data b/third_party/rust/chromium_crates_io/vendor/icu_casemap_data-2.0.0-beta2/data/case_map_v1.rs.data
new file mode 100644
index 0000000..66f763d0
--- /dev/null
+++ b/third_party/rust/chromium_crates_io/vendor/icu_casemap_data-2.0.0-beta2/data/case_map_v1.rs.data
@@ -0,0 +1,75 @@
+// @generated
+/// Implement `DataProvider<CaseMapV1>` on the given struct using the data
+/// hardcoded in this file. This allows the struct to be used with
+/// `icu`'s `_unstable` constructors.
+///
+/// Using this implementation will embed the following data in the binary's data segment:
+/// * 22731B[^1] for the singleton data struct
+///
+/// [^1]: these numbers can be smaller in practice due to linker deduplication
+#[doc(hidden)]
+#[macro_export]
+macro_rules! __impl_case_map_v1 {
+    ($ provider : ty) => {
+        #[clippy::msrv = "1.81"]
+        const _: () = <$provider>::MUST_USE_MAKE_PROVIDER_MACRO;
+        #[clippy::msrv = "1.81"]
+        impl $provider {
+            #[doc(hidden)]
+            pub const SINGLETON_CASE_MAP_V1: &'static <icu::casemap::provider::CaseMapV1 as icu_provider::DynamicDataMarker>::DataStruct = &icu::casemap::provider::CaseMap { trie: icu::collections::codepointtrie::CodePointTrie::from_parts(icu::collections::codepointtrie::CodePointTrieHeader { high_start: 918016u32, shifted12_high_start: 225u16, index3_null_offset: 425u16, data_null_offset: 1552u32, null_value: 0u32, trie_type: icu::collections::codepointtrie::TrieType::Small }, unsafe { zerovec::ZeroVec::from_bytes_unchecked(b"\0\0@\0{\0\xBB\0\xFB\x005\x01u\x01\xB5\x01\xED\x01,\x02l\x02\xAC\x02\xEC\x02*\x03j\x03\xAA\x03\xEA\x03)\x04g\x04\xA7\x04\xB7\x04\xE8\x04!\x05_\x05\x9F\x05\xDF\x05\x10\x06:\x06k\x06\xAA\x06\xC4\x06\xF5\x063\x07a\x07\x99\x07\xD0\x07\x10\x08O\x08\x7F\x08\xBE\x08\xFD\x08<\t\xFD\x08{\t\xBB\t\xF9\t7\nw\n\xB7\n\xF6\n\xBB\t0\x0BR\x0B\x91\x0B\xD0\x0B\x06\x0C\x1D\x0CX\x0Cg\x0C\xA4\x0C\xCC\x0C\x06\rF\r\t\x06\x91\x07\xAA\x07\xBA\x07\xD0\x07\xF0\x07\x0B\x08#\x08B\x08\xAA\x07\xAA\x07\xAA\x07\xAA\x07\xAA\x07\xAA\x07\xAA\x07\xAA\x07\xAA\x07\xAA\x07\xAA\x07\xAA\x07\xAA\x07\xAA\x07\xAA\x07\xAA\x07\xAA\x07\xAA\x07\xAA\x07\xAA\x07\xAA\x07\xAA\x07\xAA\x07\xAA\x07\xAA\x07\xAA\x07\xAA\x07\xAA\x07\xAA\x07\xAA\x07\xAA\x07\xAA\x07\xAA\x07\xAA\x07\xAA\x07\xAA\x07\xAA\x07\xAA\x07\xAA\x07\xAA\x07\xAA\x07\xAA\x07\xAA\x07\xAA\x07\xAA\x07\xAA\x07\xAA\x07\xAA\x07\xFE\0\0\0\x10\0 \x000\0@\0P\0`\0p\0{\0\x8B\0\x9B\0\xAB\0\xBB\0\xCB\0\xDB\0\xEB\0\xFB\0\x0B\x01\x1B\x01+\x015\x01E\x01U\x01e\x01u\x01\x85\x01\x95\x01\xA5\x01\xB5\x01\xC5\x01\xD5\x01\xE5\x01\xED\x01\xFD\x01\r\x02\x1D\x02,\x02<\x02L\x02\\\x02l\x02|\x02\x8C\x02\x9C\x02\xAC\x02\xBC\x02\xCC\x02\xDC\x02\xEC\x02\xFC\x02\x0C\x03\x1C\x03*\x03:\x03J\x03Z\x03j\x03z\x03\x8A\x03\x9A\x03\xAA\x03\xBA\x03\xCA\x03\xDA\x03\xEA\x03\xFA\x03\n\x04\x1A\x04)\x049\x04I\x04Y\x04g\x04w\x04\x87\x04\x97\x04\xA7\x04\xB7\x04\xC7\x04\xD7\x04\xB7\x04\xC7\x04\xD7\x04\xE7\x04\xE8\x04\xF8\x04\x08\x05\x18\x05!\x051\x05A\x05Q\x05_\x05o\x05\x7F\x05\x8F\x05\x9F\x05\xAF\x05\xBF\x05\xCF\x05\xDF\x05\xEF\x05\xFF\x05\x0F\x06\x10\x06 \x060\x06@\x06:\x06J\x06Z\x06j\x06k\x06{\x06\x8B\x06\x9B\x06\xAA\x06\xBA\x06\xCA\x06\xDA\x06\xC4\x06\xD4\x06\xE4\x06\xF4\x06\xF5\x06\x05\x07\x15\x07%\x073\x07C\x07S\x07c\x07a\x07q\x07\x81\x07\x91\x07\x99\x07\xA9\x07\xB9\x07\xC9\x07\xD0\x07\xE0\x07\xF0\x07\0\x08\x10\x08 \x080\x08@\x08O\x08_\x08o\x08\x7F\x08\x7F\x08\x8F\x08\x9F\x08\xAF\x08\xBE\x08\xCE\x08\xDE\x08\xEE\x08\xFD\x08\r\t\x1D\t-\t<\tL\t\\\tl\t\xFD\x08\r\t\x1D\t-\t{\t\x8B\t\x9B\t\xAB\t\xBB\t\xCB\t\xDB\t\xEB\t\xF9\t\t\n\x19\n)\n7\nG\nW\ng\nw\n\x87\n\x97\n\xA7\n\xB7\n\xC7\n\xD7\n\xE7\n\xF6\n\x06\x0B\x16\x0B&\x0B\xBB\t\xCB\t\xDB\t\xEB\t0\x0B@\x0BP\x0B`\x0BR\x0Bb\x0Br\x0B\x82\x0B\x91\x0B\xA1\x0B\xB1\x0B\xC1\x0B\xD0\x0B\xE0\x0B\xF0\x0B\0\x0C\x06\x0C\x16\x0C&\x0C6\x0C\x1D\x0C-\x0C=\x0CM\x0CX\x0Ch\x0Cx\x0C\x88\x0Cg\x0Cw\x0C\x87\x0C\x97\x0C\xA4\x0C\xB4\x0C\xC4\x0C\xD4\x0C\xCC\x0C\xDC\x0C\xEC\x0C\xFC\x0C\x06\r\x16\r&\r6\rF\rV\rf\rv\r\t\x06\x19\x06)\x069\x06\x10\x06\x10\x06\xDD\x06\x86\r\x10\x06\x95\r\xBB\x05\xA2\r\xB0\r\x1A\0\xC0\r\xC0\r\xCA\r\xDA\r\xEA\r\xFA\r\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06C\x06\x10\x06\x10\x06\x10\x06\x10\x06\n\x0E\n\x0E\n\x0E\n\x0E\n\x0E\x1A\x0E\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06(\x0E\x10\x066\x0E\x10\x06o\x08\x10\x06o\x08\x10\x06\x10\x06\x10\x06B\x0EP\x0E^\x0E\x10\x06\x10\x06\x94\x05\x10\x06\x10\x06\x10\x06}\x08\x10\x06\x10\x06\x10\x06l\x08\x10\x06\x06\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06n\x0E|\x0E\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x88\x0E\x10\x06\x10\x06\x10\x06\x94\x0E\xA4\x0E\xB1\x0E\x10\x06\x10\x06\x8C\x05\xC1\x0E\xD0\x0E\x10\x06\x10\x06\x10\x06\xF1\x06\x10\x06\x10\x06\xDF\x0E\xED\x0E\x10\x06\xF2\x0E]\x07\xF3\x06\x10\x06\x02\x0F\x10\x06\x10\x06\x10\x06\x10\x0F\x1F\x0F\x10\x06\x10\x06\xDE\x06/\x0F\x10\x06\x10\x06\x10\x06\x97\x05?\x0FO\x0FO\x0FT\x0F\x10\x06d\x0Ft\x0F\x82\x0F\x8B\x02\x8B\x02\x92\x0F\x9E\x0F\x9E\x0F\x9E\x0F\xAC\x0F\xB7\x0F\xC5\x0F\xD4\x0F\xE0\x0F\x9E\x0F\xF0\x0F\xFF\x0F\0\x10\n\x10\xFB\0\xFB\0\x1A\x10\xFB\0\xFB\0\xFB\0*\x10\xFB\0\xFB\x004\x10\xFB\0\xFB\0D\x10\xFB\0\xFB\0\xFB\0T\x10d\x10T\x10T\x10d\x10t\x10T\x10\x84\x10\x94\x10\xA4\x10\xB4\x10\xC4\x10\xD3\x10\xE3\x10\xF3\x10\x03\x11\x94\x05i\x08\x12\x11\x10\x06\x10\x06\x10\x06Y\r!\x11\x10\x060\x11\x10\x06\x10\x06\x10\x06@\x11O\x11\xB4\x06_\x11o\x11}\x11\x8D\x11\x9D\x11\x10\x06\xAD\x11\xBD\x11\xCD\x11\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\xD7\x11\xDD\x11\xED\x11\xF3\x11\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\xE8\x04\xE8\x04\xE8\x04\t\x05\t\x05\t\x05\x03\x12\x13\x12\xFB\0\xFB\0\xFB\0\xFB\0\xFB\0\xFB\0#\x122\x12B\x12B\x12L\x12\x10\x06\x10\x06\x10\x06\x18\0\0\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\0\x10\0\x10\x10\x06\x10\x06\x18\0\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06)\0\x10\x06Z\x12j\x12\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06v\x12\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x85\x12\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06)\0\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x97\x05\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x1B\0\x10\x06\x10\x06\x10\x06\x95\x12\xFB\0[\x04\xA5\x12\xFB\0\xB5\x12\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\xB3\x06\xAE\x02\xAE\x02o\x04\xC5\x12\xFB\0\xFB\0\xFB\0\xD5\x12\xE3\x12\xF3\x12\xF9\x12\t\x13\x15\x13%\x13\x10\x063\x13A\x13\x10\x06M\x13\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06Z\x13\x10\x06\0\x10j\x13\x10\x06\x10\x06s\x13\x10\x06\xE3\x06\x83\x13\x10\x06\x10\x06\xF2\x06\x10\x06\x10\x06\x90\x13\xA0\x13\x10\x06l\x08\x10\x06\x10\x06\x96\x05\xAE\x13\xBB\x13\x10\x06\x10\x06.\0\x10\x06\x10\x06\x10\x06\xCB\x13\xDB\x13\x1A\0\xD4\x08\xE8\x13\x10\x06\x10\x06\x10\x06\x8B\x02\x8B\x02\xF8\x13\x08\x14\x18\x14(\x148\x14H\x14X\x14\x10\x06\x10\x06h\x14\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06x\x14\x85\x14\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x94\x14\xF2\x06\x10\x06\x10\x06\x10\x06\xAE\x02}\x08\xA4\x14\x10\x06\x10\x06\xB4\x14\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x18\0 \0\x1D\0\xC3\x14\xC8\x14\xD8\x14\xDD\x14\x10\x06\xBB\x05\x10\x06\xDC\x06\x10\x06\x10\x06\x10\x06\x10\x06}\x08\xE8\x14\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x02\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06f\x05\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\xF4\x14\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x04\x15\x04\x15\x0C\x15\x14\x15\x14\x15\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x04\x15\x04\x15$\x15\x14\x15,\x15\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06<\x15<\x15I\x15W\x15e\x15\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06u\x15\x9E\x0F\x9E\x0Fz\x15\x10\x06\x10\x06\x10\x06\x10\x06\x89\x15\x10\x06\x10\x06\x99\x15\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\xA9\x15\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\xB9\x15\xB9\x15\xB9\x15\xC6\x15\xD6\x15\xD6\x15\xD6\x15\xE3\x15\x10\x06\x10\x06\xEF\x15\x10\x06\x19\0\xFF\x15\t\x16\xE1\0u\0\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x19\x16\x10\x06\x10\x06\x10\x06\x10\x06&\x16\x10\x06\x10\x06\x10\x06\x10\x066\x16f\x05\x10\x06\x10\x06F\x16\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\xBA\x05\x10\x06\x10\x06\xE2\x06V\x16\x10\x06\x10\x06f\x16\xF3\x06\x10\x06\x10\x06v\x16\x81\x16\x10\x06\x10\x06\x10\x06^\x07\x10\x06\x8F\x16\x9C\x16\x10\x06\x10\x06\x10\x06\x0C\x06\xF3\x06\x10\x06\x10\x06\xA6\x16\xB6\x16\x10\x06\x10\x06\x10\x06\x18\0\xC1\x16\xBA\x05\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x18\0\xD0\x16\x10\x06\xF3\x06\x10\x06\x10\x06\x82\x0B\xE0\x16\x10\x06\xEE\x16\\\x07\x10\x06\x10\x06\x10\x06\x94\x05\xFE\x16\xB9\x05p\x08\x10\x06\x10\x06\x10\x06\xE2\x06\x0E\x17\xEE\x08\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x1B\x17*\x17\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x068\x17f\x05\xD4\x08\x10\x06\x10\x06\x10\x06H\x17\xBB\x05\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06X\x17h\x17\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06p\x17\x80\x17\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x18\0\x90\x17\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\xFF\x15\xFF\x15\xE1\0\xE1\0\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x9B\x17\x0C\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\xAA\x17f\x05\x10\x06\xB9\x17\x10\x06\x10\x06\xC6\x17\x08\x06\xD5\x17\x10\x06\x10\x06\x95\x05\xE5\x17\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\xF5\x17\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x94\x14\x05\x18\x14\x18\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06#\x182\x18\x10\x06\x10\x06\x10\x06\x10\x06B\x18\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06n\x08\xF3\x06\x10\x06\x10\x06L\x18m\x17\x1D\0\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\xAE\x02V\x18\x9F\x05\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\xDC\x06]\x18\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06l\x18\x10\x06\x10\x06\x10\x06|\x18\xF1\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\xF2\x06\x10\x06\xD5\x08\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\xFF\x15\xFF\x15\xE1\0\xE1\0\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x18\0\x10\x06\x10\x06\x10\x06\x18\0\xAE\x02\x10\x06\x10\x06\x10\x06\x10\x06\x8C\x18\x9C\x18\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\xAC\x18\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\xBB\x18\xF1\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\xAE\x02\xAE\x02u\r\xAE\x02\xEE\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\xCA\x18\xD7\x18\xE4\x18\x10\x06\xE9\x15\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\xF2\x18\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x02\x19\x08\x19\x16\x19\"\x19\x04\x192\x19\x1E\x19\x02\x19B\x19N\x19^\x19l\x19|\x19\x02\x19\x08\x19\x16\x19\x88\x19\x95\x19\x12\x19\x84\x19\xA5\x19\xB4\x19\x1A\x19\x02\x19\xC4\x19\x8B\x02\x02\x19\x08\x19\x16\x19\"\x19\x04\x19\x12\x19\x1E\x19\x02\x19B\x19\x1A\x19\x02\x19\xC4\x19\x8B\x02\x02\x19\x08\x19\x16\x19\xD4\x19\x02\x19\xE3\x19\xE8\x19\xF6\x19\xFB\x19\x8B\x02\x07\x1A\x02\x19\xDF\x19\x17\x1A \x19'\x1A\x8B\x027\x1A\x02\x19-\x1A\x8B\x02=\x1A\x10\x06\x10\x06\x10\x06\xAE\x02\xAE\x02\xAE\x02M\x1A\xAE\x02\xAE\x02v\r)\0*\0\x94\x05^\r\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06]\x1Ah\x1Aw\x1A\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x87\x1A\x8F\x1A\x9D\x1A\x9E\x0F\xAD\x1A\x9E\x0F\xBB\x1A\x10\x06A\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\xCB\x1A\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\xEE\x08\x10\x06\x10\x06\x10\x06D\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\xD9\x1A\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x8D\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\xE9\x1A\x10\x06\x10\x06\xF9\x1A\xF9\x1A\x07\x1B\t\x1B\x15\x1B\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x02\x19%\x1B\x02\x19%\x1B\x02\x19%\x1B\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x94\x05\xBA\x05\x10\x06\xAE\x02\xAE\x02\xAE\x02\xAE\x02\xAE\x02\xAE\x02\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\x10\x06\xAE\x02\xAE\x02\xAE\x02\xAE\x02\xAE\x02\xAE\x02\xAE\x02\xAE\x02\xAE\x02\xAE\x02\xAE\x02\xAE\x02\xAE\x02\xAE\x02\xAE\x02\x10\x06y\0\x99\0\xB9\0\xD9\0\xF9\0\x19\x019\x01Y\x01y\x01\x89\x01\xA9\x01\xB8\x01\xD8\x01\xF7\x01\x17\x027\x02W\x02\xA9\x01p\x02\xA9\x01\xA9\x01\xA9\x01\x90\x02\xB0\x02\xD0\x02\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xEF\x02\xA9\x01\0\x03 \x03@\x03_\x03\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01~\x03\xA9\x01\x9E\x03\xBE\x03\xDE\x03\xFE\x03\x1A\x04\xA9\x01:\x04R\x04k\x04\x8B\x04\xA9\x04\xC8\x04\xE6\x04\x04\x05$\x05A\x05[\x05\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01x\x05\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\x87\x05\xA9\x01\xA9\x01\xA9\x01\xA9\x01\x9A\x05\xAF\x05\xCB\x05\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xEB\x05\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\x0B\x06\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\x1B\x060\x06L\x06l\x06\x8C\x06\xA9\x01\xAC\x06\xA9\x01\xBC\x06\xDC\x06\xF2\x06\x04\x07\xA9\x01#\x07\xA9\x01\xA9\x01\xA9\x018\x07Q\x07\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01\xA9\x01") }, unsafe { zerovec::ZeroVec::from_bytes_unchecked(b"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\x12\x10\x12\x10\x12\x10\x12\x10\x12\x10\x12\x10\x12\x10\x12\x10\n\0\x1A\0*\0\x12\x10\x12\x10\x12\x10\x12\x10\x12\x10\x12\x10\x12\x10:\0\x12\x10\x12\x10\x12\x10\x12\x10\x12\x10\x12\x10\x12\x10\0\0\0\0\0\0\x04\0\0\0\x04\0\x11\xF0\x11\xF0\x11\xF0\x11\xF0\x11\xF0\x11\xF0\x11\xF0\x11\xF0I\x001\xF0Y\0\x11\xF0\x11\xF0\x11\xF0\x11\xF0\x11\xF0\x11\xF0\x11\xF0i\0\x11\xF0\x11\xF0\x11\xF0\x11\xF0\x11\xF0\x11\xF0\x11\xF0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\0\0\x01\0\0\0\0\0\x04\0\0\0\x04\0\0\0\0\0\0\0\0\0\x04\0y\0\0\0\x04\0\x04\0\0\0\x01\0\0\0\0\0\0\0\0\0\0\0\x12\x10\x12\x10\x12\x10\x12\x10\x12\x10\x8A\0\x12\x10\x12\x10\x12\x10\x12\x10\x12\x10\x12\x10\x1A\0\x1A\0\x12\x10\x12\x10\x12\x10\x12\x10\x12\x10\x12\x10\x12\x10\x12\x10\x12\x10\0\0\x12\x10\x12\x10\x12\x10\x12\x10\x12\x10\x12\x10\x12\x10\x99\0\x11\xF0\x11\xF0\x11\xF0\x11\xF0\x11\xF0\xA9\0\x11\xF0\x11\xF0\x11\xF0\x11\xF0\x11\xF0\x11\xF0\x11\xF0\x11\xF0\x11\xF0\x11\xF0\x11\xF0\x11\xF0\x11\xF0\x11\xF0\x11\xF0\x11\xF0\x11\xF0\0\0\x11\xF0\x11\xF0\x11\xF0\x11\xF0\x11\xF0\x11\xF0\x11\xF0\x91<\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\xBA\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\xBA\0\xB1\xFF\xCA\0\xD9\0\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x01\0\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\xE9\0\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\xC3\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\xF9\0\x91a\x12i\x92\0\x91\xFF\x92\0\x91\xFF\x12g\x92\0\x91\xFF\x92f\x92f\x92\0\x91\xFF\x01\0\x92'\x12e\x92e\x92\0\x91\xFF\x92f\x92g\x910\x92i\x92h\x92\0\x91\xFF\x91Q\t\x01\x92i\x92j\x11A\x12k\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x12m\x92\0\x91\xFF\x12m\x01\0\x01\0\x92\0\x91\xFF\x12m\x92\0\x91\xFF\x92l\x92l\x92\0\x91\xFF\x92\0\x91\xFF\x92m\x92\0\x91\xFF\x01\0\0\0\x92\0\x91\xFF\x01\0\x11\x1C\0\0\0\0\0\0\0\0\x1A\x01+\x019\x01J\x01[\x01i\x01z\x01\x8B\x01\x99\x01\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x91\xD8\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\xA9\x01\xBA\x01\xCB\x01\xD9\x01\x92\0\x91\xFF\x92\xCF\x12\xE4\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x12\xBF\x01\0\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\xEA\x01\x92\0\x91\xFF\x92\xAE\xFA\x01\t\x02\x92\0\x91\xFF\x92\x9E\x92\"\x92#\x92\0\x91\xFF\x92\0\xB1\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x19\x02)\x029\x02\x11\x97\x11\x99\x01\0\x91\x99\x91\x99\x01\0\x11\x9B\x01\0\x91\x9AI\x02\x01\0\x01\0\x01\0\x91\x99Y\x02\x01\0\x91\x98i\x02y\x02\x89\x02\x01\0\xB1\x97\x91\x96\x89\x02\x99\x02\xA9\x02\x01\0\x01\0\x91\x96\x01\0\xB9\x02\x91\x95\x01\0\x01\0\x11\x95\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\xC9\x02\x01\0\x01\0\x11\x93\x01\0\xD9\x02\x11\x93\x01\0\x01\0\x01\0\xE9\x02\x11\x93\x91\xDD\x91\x93\x91\x93\x91\xDC\x01\0\x01\0\x01\0\x01\0\x01\0\x91\x92\x01\0\0\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\xF9\x02\t\x03\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x05\0\x05\0%\0\x05\0\x05\0\x05\0\x05\0\x05\0\x05\0\x04\0\x04\0\x04\0\x14\0\x04\0\x14\0\x04\0\x05\0\x05\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x05\0\x05\0\x05\0\x05\0\x05\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0T\0T\0D\0D\0D\0D\0D\0\x1C\x03T\0D\0T\0D\0T\0D\0D\0D\0D\0D\0D\0T\0D\0d\0d\0d\0d\0d\0d\0d\0d\0d\0d\0d\0d\0d\0d\0d\0d\0d\0d\0d\0d\0d\0d\0d\0d\0d\0d\0d\0d\0t\0d\0d\0d\0d\0d\0d\0d\0d\0d\0d\0d\0D\0D\0D\0T\0D\0D\0-\x03D\0d\0d\0d\0D\0D\0D\0d\0d\0\x04\0D\0D\0D\0d\0d\0d\0d\0D\0d\0d\0d\0D\0d\0d\0d\0d\0d\0d\0d\0D\0D\0D\0D\0D\0D\0D\0D\0D\0D\0D\0D\0D\0\x92\0\x91\xFF\x92\0\x91\xFF\x04\0\x04\0\x92\0\x91\xFF\0\0\0\0\x05\0\x11A\x11A\x11A\0\0\x12:\0\0\0\0\0\0\0\0\x04\0\x04\0\x12\x13\x04\0\x92\x12\x92\x12\x92\x12\0\0\x12 \0\0\x92\x1F\x92\x1F9\x03\x12\x10J\x03\x12\x10\x12\x10Z\x03\x12\x10\x12\x10j\x03z\x03\x8A\x03\x12\x10\x9A\x03\x12\x10\x12\x10\x12\x10\xAA\x03\xBA\x03\0\0\xCA\x03\x12\x10\x12\x10\xDA\x03\x12\x10\x12\x10\xEA\x03\x12\x10\x12\x10\x11\xED\x91\xED\x91\xED\x91\xED\xF9\x03\x11\xF0\t\x04\x11\xF0\x11\xF0\x19\x04\x11\xF0\x11\xF0)\x049\x04I\x04\x11\xF0Y\x04\x11\xF0\x11\xF0\x11\xF0i\x04y\x04\x89\x04\x99\x04\x11\xF0\x11\xF0\xA9\x04\x11\xF0\x11\xF0\xB9\x04\x11\xF0\x11\xF0\x11\xE0\x91\xE0\x91\xE0\x12\x04\xC9\x04\xD9\x04\x02\0\x02\0\x02\0\xE9\x04\xF9\x04\x11\xFC\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\t\x05\x19\x05\x91\x031\xC6*\x059\x05\0\0\x92\0\x91\xFF\x92\xFC\x92\0\x91\xFF\x01\0\x12\xBF\x12\xBF\x12\xBF\x12(\x12(\x12(\x12(\x12(\x12(\x12(\x12(\x12(\x12(\x12(\x12(\x12(\x12(\x12(\x12(\x12\x10\x12\x10J\x05\x12\x10Z\x05\x12\x10\x12\x10\x12\x10\x12\x10\x12\x10\x12\x10\x12\x10\x12\x10\x12\x10j\x05\x12\x10\x12\x10z\x05\x8A\x05\x12\x10\x12\x10\x12\x10\x12\x10\x12\x10\x12\x10\x12\x10\x9A\x05\x12\x10\x12\x10\x12\x10\x12\x10\x12\x10\x11\xF0\x11\xF0\xA9\x05\x11\xF0\xB9\x05\x11\xF0\x11\xF0\x11\xF0\x11\xF0\x11\xF0\x11\xF0\x11\xF0\x11\xF0\x11\xF0\xC9\x05\x11\xF0\xD9\x05\xE9\x05\x11\xF0\x11\xF0\x11\xF0\x11\xF0\x11\xF0\x11\xF0\x11\xF0\xF9\x05\x11\xF0\x11\xF0\x11\xF0\x11\xF0\x11\xF0\x11\xD8\x11\xD8\x11\xD8\x11\xD8\x11\xD8\x11\xD81\xD8\x11\xD81\xD8\x11\xD8\x11\xD8\x11\xD8\x11\xD8\x11\xD8\x11\xD8\x11\xD8\x92\0\x91\xFF\n\x06\x19\x06\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\0\0D\0D\0D\0D\0D\0\x04\0\x04\0\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\x07\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x91\xF8\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\0\0\x12\x18\x12\x18\x12\x18\x12\x18\x12\x18\x12\x18\x12\x18\x12\x18\x12\x18\x12\x18\x12\x18\x12\x18\x12\x18\x12\x18\x12\x18\x12\x18\x12\x18\x12\x18\x12\x18\x12\x18\x12\x18\x12\x18\x12\x18\0\0\0\0\x04\0\0\0\0\0\0\0\0\0\0\0\x04\0\x01\0\x11\xE8\x11\xE8\x11\xE8\x11\xE8\x11\xE8\x11\xE8\x11\xE8\x11\xE8\x11\xE8\x11\xE8\x11\xE8\x11\xE8\x11\xE8\x11\xE8\x11\xE8\x11\xE8\x11\xE8\x11\xE8\x11\xE8\x11\xE8\x11\xE8\x11\xE8\x11\xE8\x11\xE8\x11\xE8\x11\xE8\x11\xE8\x11\xE8\x11\xE8\x11\xE8\x11\xE8)\x06\x01\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0d\0D\0D\0D\0D\0d\0D\0D\0D\0d\0d\0D\0D\0D\0D\0D\0D\0d\0d\0d\0d\0d\0d\0D\0D\0d\0D\0D\0d\0d\0D\0d\0d\0d\0d\0d\0d\0d\0d\0d\0d\0d\0d\0d\0d\0\0\0d\0d\0\0\0D\0d\0\0\0d\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0D\0D\0D\0D\0D\0D\0D\0D\0d\0d\0d\0\0\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0d\0d\0d\0d\0d\0d\0d\0d\0D\0D\0d\0d\0D\0D\0D\0D\0D\0d\0D\0D\0d\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0d\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0D\0D\0D\0D\0D\0D\0D\0\x04\0\0\0D\0D\0D\0D\0d\0D\0\x04\0\x04\0D\0D\0\0\0d\0D\0D\0d\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\0\0d\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0D\0d\0D\0D\0d\0D\0D\0d\0d\0d\0D\0d\0d\0D\0d\0D\0D\0d\0D\0d\0D\0d\0D\0d\0D\0D\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0D\0D\0D\0D\0D\0D\0D\0d\0D\0\x04\0\x04\0\0\0\0\0\0\0\0\0\x04\0\0\0\0\0d\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0D\0D\0D\0D\0\x04\0D\0D\0D\0D\0D\0D\0D\0D\0D\0\x04\0D\0D\0D\0\x04\0D\0D\0D\0D\0D\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0d\0d\0d\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\x04\0\0\0\0\0\0\0\0\0\0\0D\0D\0d\0d\0d\0D\0D\0D\0D\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0D\0D\0D\0D\0D\0d\0d\0d\0d\0d\0D\0D\0D\0D\0D\0D\0D\0D\0D\0D\0D\0D\0D\0D\0\x04\0d\0D\0D\0d\0D\0D\0d\0D\0D\0D\0d\0d\0d\0d\0d\0d\0D\0D\0D\0d\0D\0D\0d\0d\0D\0D\0D\0D\0D\0\x04\0\x04\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\0\0d\0\0\0\0\0\0\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\0\0\0\0\0\0\0\0d\0\0\0\0\0\0\0D\0d\0D\0D\0\x04\0\x04\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0d\0\0\0\0\0\0\0\x04\0\x04\0\x04\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0d\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0D\0\0\0\x04\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0d\0\0\0\0\0\0\0\x04\0\x04\0\0\0\0\0\0\0\0\0\x04\0\x04\0\0\0\0\0\x04\0\x04\0d\0\0\0\0\0\0\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\x04\0\0\0\0\0\0\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\x04\0\x04\0\x04\0\x04\0\0\0\x04\0\x04\0\0\0\0\0\0\0\0\0d\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\0\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0d\0\0\0\0\0\x04\0\x04\0\x04\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0d\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0d\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\0\0\0\0\0\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0d\0\0\0\x04\0\x04\0\0\0\0\0\0\0\0\0\0\0\x04\0\x04\0\x04\0\0\0\x04\0\x04\0\x04\0d\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0d\0d\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\0\0\0\0\0\0\0\0\0\0\x04\0d\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0d\0d\0\0\0\0\0\0\0\x04\0\x04\0\x04\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0d\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0d\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\x04\0\x04\0\0\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\0\0\0\0\x04\0\x04\0\x04\0\x04\0d\0d\0d\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\x04\0d\0d\0d\0d\0\x04\0\x04\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\0\0\0\0\x04\0\x04\0\x04\0\x04\0d\0d\0d\0\x04\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\0\0d\0d\0d\0d\0\x04\0\x04\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0d\0d\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0d\0\0\0d\0\0\0d\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0d\0d\0\x04\0d\0\x04\0\x04\0\x04\0\x04\0\x04\0d\0d\0d\0d\0\x04\0\0\0d\0\x04\0D\0D\0d\0\0\0D\0D\0\0\0\0\0\0\0\0\0\0\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\0\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\0\0\0\0\0\0\x04\0\0\0\x04\0\x04\0\x04\0\x04\0\x04\0d\0\0\0d\0d\0\0\0\0\0\x04\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\x04\0\0\0\0\0\0\0\0\0\x04\0\x04\0\x04\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\0\0\0\0\x04\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0d\0\0\0\0\0:\x06:\x06:\x06:\x06:\x06:\x06:\x06:\x06:\x06:\x06:\x06:\x06:\x06:\x06:\x06:\x06\0\0:\x06\0\0\0\0\0\0\0\0\0\0:\x06\0\0\0\0I\x06Y\x06i\x06y\x06\x89\x06\x99\x06\xA9\x06\xB9\x06\xC9\x06\xD9\x06\xE9\x06\xF9\x06\t\x07\x19\x07)\x079\x07I\x07Y\x07i\x07y\x07\x89\x07\x99\x07\xA9\x07\xB9\x07\xC9\x07\xD9\x07\xE9\x07\xF9\x07\t\x08\x19\x08)\x089\x08I\x08Y\x08i\x08y\x08\x89\x08\x99\x08\xA9\x08\xB9\x08\xC9\x08\xD9\x08\xE9\x08\0\0\x05\0\xF9\x08\t\t\x19\t*\t*\t*\t*\t*\t*\t*\t*\t*\t*\t*\t*\t*\t*\t*\t*\t:\t:\t:\t:\t:\t:\t\0\0\0\0I\tY\ti\ty\t\x89\t\x99\t\0\0\0\0\x04\0\x04\0d\0`\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\x04\0`\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\x04\0\0\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\0\0\0\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0d\0\x04\0\0\0\0\0\0\0\x04\0\0\0\0\0\0\0\0\0\0\0D\0\0\0\0\0\x04\0\x04\0\x04\0\0\0\0\0\0\0\0\0\x04\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0d\0D\0d\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0D\0d\0\0\0\0\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\0\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\0\0d\0\0\0\x04\0\0\0\0\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\0\0\0\0\0\0\x04\0\x04\0D\0D\0D\0D\0D\0D\0D\0D\0\0\0\0\0d\0D\0D\0D\0D\0D\0d\0d\0d\0d\0d\0d\0D\0D\0d\0\x04\0d\0D\0D\0d\0d\0D\0D\0D\0D\0D\0d\0D\0D\0D\0D\0\0\0\0\0\0\0\0\0d\0\0\0\x04\0\x04\0\x04\0\x04\0\x04\0\0\0\x04\0\0\0\0\0\0\0\x04\0\0\0`\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0D\0d\0D\0D\0D\0\0\0\0\0\x04\0\x04\0\x04\0\x04\0\0\0\0\0\x04\0\x04\0`\0d\0\x04\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0d\0\0\0\x04\0\x04\0\0\0\0\0\0\0\x04\0\0\0\x04\0\x04\0`\0`\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\x04\0\x04\0\x04\0\0\0\0\0\x04\0d\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\xA9\t\xB9\t\xC9\t\xD9\t\xE9\t\xF9\t\t\n\x19\n)\n\x92\0\x91\xFF\0\0\0\0\0\0\0\0\0\0:\n:\n:\n:\n:\n:\n:\n:\n:\n:\n:\n:\n:\n:\n:\n:\n\0\0\0\0:\n:\n:\nD\0D\0D\0\0\0d\0d\0d\0d\0d\0d\0D\0D\0d\0d\0d\0d\0D\0\0\0d\0d\0d\0d\0d\0d\0d\0\0\0\0\0\0\0\0\0d\0\0\0\0\0\0\0\0\0D\0\0\0\0\0\0\0D\0D\0\0\0\0\0\0\0\0\0\0\0\0\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x05\0\x05\0\x05\0\x05\0\x05\0\x05\0\x05\0\x05\0\x05\0\x05\0\x05\0\x05\0\x05\0\x05\0\x05\0\x05\0%\0\x05\0\x05\0\x05\0\x05\0\x05\0\x05\0\x05\0\x05\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x05\0I\n\x01\0\x01\0\x01\0Y\n\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0i\n\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0!\0\x01\0\x01\0\x01\0\x01\0\x05\0\x05\0\x05\0\x05\0\x05\0%\0\x05\0\x05\0\x05\0%\0\x05\0\x05\0\x05\0\x05\0\x05\0\x05\0\x05\0D\0D\0d\0D\0D\0D\0D\0D\0D\0D\0d\0D\0D\0d\0d\0d\0D\0D\0D\0D\0D\0D\0D\0D\0D\0D\0D\0D\0D\0D\0D\0D\0d\0d\0d\0d\0d\0D\0d\0d\0D\0d\0\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\xB1\xFF\x92\0\x91\xFFz\n\x89\n\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x99\n\xA9\n\xB9\n\xC9\n\xD9\n\xE9\n\x01\0\x01\0\xFA\n\x01\0\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\xB1\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x11\x04\x11\x04\x11\x04\x11\x04\x11\x04\x11\x04\x11\x04\x11\x04\x12\xFC\x12\xFC\x12\xFC\x12\xFC\x12\xFC\x12\xFC\x12\xFC\x12\xFC\x11\x04\x11\x04\x11\x04\x11\x04\x11\x04\x11\x04\0\0\0\0\x12\xFC\x12\xFC\x12\xFC\x12\xFC\x12\xFC\x12\xFC\0\0\0\0\t\x0B\x11\x04\x19\x0B\x11\x04)\x0B\x11\x049\x0B\x11\x04\0\0\x12\xFC\0\0\x12\xFC\0\0\x12\xFC\0\0\x12\xFC\x11%\x11%\x11+\x11+\x11+\x11+\x112\x112\x11@\x11@\x118\x118\x11?\x11?\0\0\0\0I\x0BY\x0Bi\x0By\x0B\x89\x0B\x99\x0B\xA9\x0B\xB9\x0B\xCB\x0B\xDB\x0B\xEB\x0B\xFB\x0B\x0B\x0C\x1B\x0C+\x0C;\x0CI\x0CY\x0Ci\x0Cy\x0C\x89\x0C\x99\x0C\xA9\x0C\xB9\x0C\xCB\x0C\xDB\x0C\xEB\x0C\xFB\x0C\x0B\r\x1B\r+\r;\rI\rY\ri\ry\r\x89\r\x99\r\xA9\r\xB9\r\xCB\r\xDB\r\xEB\r\xFB\r\x0B\x0E\x1B\x0E+\x0E;\x0E\x11\x04\x11\x04I\x0EY\x0Ei\x0E\0\0y\x0E\x89\x0E\x12\xFC\x12\xFC\x12\xDB\x12\xDB\x9B\x0E\x04\0\xA9\x0E\x04\0\x04\0\xB9\x0E\xC9\x0E\xD9\x0E\0\0\xE9\x0E\xF9\x0E\x12\xD5\x12\xD5\x12\xD5\x12\xD5\x0B\x0F\x04\0\x04\0\x04\0\x11\x04\x11\x04\x19\x0F)\x0F\0\0\0\09\x0FI\x0F\x12\xFC\x12\xFC\x12\xCE\x12\xCE\0\0\x04\0\x04\0\x04\0\x11\x04\x11\x04Y\x0Fi\x0Fy\x0F\x91\x03\x89\x0F\x99\x0F\x12\xFC\x12\xFC\x12\xC8\x12\xC8\x92\xFC\x04\0\x04\0\x04\0\0\0\0\0\xA9\x0F\xB9\x0F\xC9\x0F\0\0\xD9\x0F\xE9\x0F\x12\xC0\x12\xC0\x12\xC1\x12\xC1\xFB\x0F\x04\0\x04\0\0\0\0\0\0\0\0\0\x04\0\0\0\0\0\x04\0\0\0\0\0\x04\0\x04\0\x04\0\x04\0\x04\0\0\0%\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x05\0\x05\0\x05\0\x05\0\x05\0\x05\0\x05\0\x05\0\x05\0\x05\0\x05\0\x05\0\x05\0\0\0\0\0\0\0D\0D\0d\0d\0D\0D\0D\0D\0d\0d\0d\0D\0D\0\x04\0\x04\0\x04\0D\0\x04\0\x04\0\x04\0d\0d\0D\0d\0D\0d\0d\0d\0d\0d\0d\0\0\0\0\0\x02\0\0\0\0\0\0\0\0\0\x02\0\0\0\0\0\x01\0\x02\0\x02\0\x02\0\x01\0\x01\0\x02\0\x02\0\x02\0\x01\0\0\0\x02\0\0\0\0\0\0\0\x02\0\x02\0\x02\0\x02\0\x02\0\0\0\0\0\0\0\0\0\x02\0\0\0\n\x10\0\0\x02\0\0\0\x1A\x10*\x10\x02\0\x02\0\0\0\x01\0\x02\0\x02\0\x12\x0E\x02\0\x01\0\0\0\0\0\0\0\0\0\x01\0\0\0\0\0\x01\0\x01\0\x02\0\x02\0\0\0\0\0\0\0\0\0\0\0\x02\0\x01\0\x01\0!\0!\0\0\0\0\0\0\0\0\0\x11\xF2\0\0\x12\x08\x12\x08\x12\x08\x12\x08\x12\x08\x12\x08\x12\x08\x12\x08\x12\x08\x12\x08\x12\x08\x12\x08\x12\x08\x12\x08\x12\x08\x12\x08\x11\xF8\x11\xF8\x11\xF8\x11\xF8\x11\xF8\x11\xF8\x11\xF8\x11\xF8\x11\xF8\x11\xF8\x11\xF8\x11\xF8\x11\xF8\x11\xF8\x11\xF8\x11\xF8\0\0\0\0\0\0\x92\0\x91\xFF\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x12\r\x12\r\x12\r\x12\r\x12\r\x12\r\x12\r\x12\r\x12\r\x12\r\x12\r\x12\r\x12\r\x12\r\x12\r\x12\r\x11\xF3\x11\xF3\x11\xF3\x11\xF3\x11\xF3\x11\xF3\x11\xF3\x11\xF3\x11\xF3\x11\xF3\x11\xF3\x11\xF3\x11\xF3\x11\xF3\x11\xF3\x11\xF3\0\0\0\0\0\0\0\0\0\0\0\0\x92\0\x91\xFF:\x10J\x10Z\x10i\x10y\x10\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x8A\x10\x9A\x10\xAA\x10\xBA\x10\x01\0\x92\0\x91\xFF\x01\0\x92\0\x91\xFF\x01\0\x01\0\x01\0\x01\0\x01\0%\0\x05\0\xCA\x10\xCA\x10\x92\0\x91\xFF\x92\0\x91\xFF\x01\0\0\0\0\0\0\0\0\0\0\0\0\0\x92\0\x91\xFF\x92\0\x91\xFFD\0D\0\x92\0\x91\xFF\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\xD9\x10\xD9\x10\xD9\x10\xD9\x10\xD9\x10\xD9\x10\xD9\x10\xD9\x10\xD9\x10\xD9\x10\xD9\x10\xD9\x10\xD9\x10\xD9\x10\xD9\x10\xD9\x10\0\0\xD9\x10\0\0\0\0\0\0\0\0\0\0\xD9\x10\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0d\0d\0d\0d\0`\0`\0\0\0\x04\0\x04\0\x04\0\x04\0\x04\0\0\0\0\0\0\0\0\0\0\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0d\0d\0\x04\0\x04\0\x04\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\x04\0\x04\0\0\0\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\xEA\x10\xF9\x10\x92\0\x91\xFF\x92\0\x91\xFF\x04\0\x04\0\x04\0\0\0D\0D\0D\0D\0D\0D\0D\0D\0D\0D\0\0\0\x04\0\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x05\0\x05\0D\0D\0\x01\0\x01\0\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x05\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x92\0\x91\xFF\x92\0\x91\xFF\n\x11\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x04\0\x04\0\x04\0\x92\0\x91\xFF\x1A\x11\x01\0\0\0\x92\0\x91\xFF\x92\0\x91\xFF\x11\x18\x01\0\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF*\x11:\x11J\x11Z\x11*\x11\x01\0j\x11z\x11\x8A\x11\x9A\x11\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\x12\xE8\xAA\x11\xBA\x11\x92\0\x91\xFF\x92\0\x91\xFF\xCA\x11\x92\0\x91\xFF\0\0\0\0\x92\0\x91\xFF\0\0\x01\0\0\0\x01\0\x92\0\x91\xFF\x92\0\x91\xFF\x92\0\x91\xFF\xDA\x11\0\0\0\0\0\0\x05\0\x05\0\x05\0\x92\0\x91\xFF\0\0\x05\0\x05\0\x01\0\0\0\0\0\0\0\0\0\0\0\x04\0\0\0\0\0\0\0d\0\0\0\0\0\0\0\0\0\x04\0\0\0\0\0\0\0\0\0\0\0\x04\0\x04\0\0\0\0\0\0\0\0\0\0\0d\0\0\0\0\0\0\0\0\0d\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0D\0D\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\x04\0\x04\0\x04\0\x04\0d\0d\0d\0\0\0\0\0\x04\0\x04\0\0\0`\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0d\0\0\0\0\0\x04\0\x04\0\x04\0\x04\0\0\0\0\0\x04\0\x04\0\0\0\0\0`\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\x04\0\0\0\0\0\x04\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\0\0\0\0\0\0D\0\0\0D\0D\0d\0\0\0\0\0D\0D\0\0\0\0\0\0\0\0\0\0\0D\0D\0\0\0D\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\x04\0\0\0d\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x01\0\x01\0\x01\0\xE9\x11\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x04\0\x05\0\x05\0\x05\0\x05\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x05\0\x04\0\x04\0\0\0\0\0\0\0\0\0\xF9\x11\t\x12\x19\x12)\x129\x12I\x12Y\x12i\x12y\x12\x89\x12\x99\x12\xA9\x12\xB9\x12\xC9\x12\xD9\x12\xE9\x12\xF9\x12\t\x13\x19\x13)\x139\x13I\x13Y\x13i\x13y\x13\x89\x13\x99\x13\xA9\x13\xB9\x13\xC9\x13\xD9\x13\xE9\x13\xF9\x13\t\x14\x19\x14)\x149\x14I\x14Y\x14i\x14y\x14\x89\x14\x99\x14\xA9\x14\xB9\x14\xC9\x14\xD9\x14\xE9\x14\xF9\x14\t\x15\x19\x15)\x159\x15I\x15Y\x15i\x15y\x15\x89\x15\x99\x15\xA9\x15\xB9\x15\xC9\x15\xD9\x15\xE9\x15\xF9\x15\t\x16\x19\x16)\x169\x16I\x16Y\x16i\x16y\x16\x89\x16\x99\x16\xA9\x16\xB9\x16\xC9\x16\xD9\x16\xE9\x16\0\0\0\0\0\0\0\0\0\0\x04\0\0\0\0\0\x04\0\0\0\0\0\0\0\0\0d\0\0\0\0\0\xF9\x16\t\x17\x19\x17)\x179\x17I\x17Y\x17\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0i\x17y\x17\x89\x17\x99\x17\xA9\x17\0\0\0\0\0\0\0\0\0\0\0\0d\0\0\0\0\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0D\0D\0D\0D\0D\0D\0D\0d\0d\0d\0d\0d\0d\0d\0D\0D\0\0\0\0\0\x04\0\0\0\0\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x12\x10\x12\x10\x12\x10\x12\x10\x12\x10\x12\x10\x12\x10\x12\x10\x12\x10\x12\x10\x12\x10\x12\x10\x12\x10\x12\x10\x12\x10\0\0\0\0\0\0\x04\0\0\0\x04\0\x11\xF0\x11\xF0\x11\xF0\x11\xF0\x11\xF0\x11\xF0\x11\xF0\x11\xF0\x11\xF0\x11\xF0\x11\xF0\x11\xF0\x11\xF0\x11\xF0\x11\xF0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\x04\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0D\0D\0D\0D\0D\0\0\0\0\0\0\0\0\0\0\0\x12\x14\x12\x14\x12\x14\x12\x14\x12\x14\x12\x14\x12\x14\x12\x14\x12\x14\x12\x14\x12\x14\x12\x14\x12\x14\x12\x14\x12\x14\x12\x14\x11\xEC\x11\xEC\x11\xEC\x11\xEC\x11\xEC\x11\xEC\x11\xEC\x11\xEC\x11\xEC\x11\xEC\x11\xEC\x11\xEC\x11\xEC\x11\xEC\x11\xEC\x11\xEC\x12\x14\x12\x14\x12\x14\x12\x14\0\0\0\0\0\0\0\0\x11\xEC\x11\xEC\x11\xEC\x11\xEC\x11\xEC\x11\xEC\x11\xEC\x11\xEC\x11\xEC\x11\xEC\x11\xEC\x11\xEC\0\0\0\0\0\0\0\0\x92\x13\x92\x13\x92\x13\x92\x13\x92\x13\x92\x13\x92\x13\x92\x13\x92\x13\x92\x13\x92\x13\0\0\x92\x13\x92\x13\x92\x13\x92\x13\0\0\x92\x13\x92\x13\0\0\x91\xEC\x91\xEC\x91\xEC\x91\xEC\x91\xEC\x91\xEC\x91\xEC\x91\xEC\x91\xEC\0\0\x91\xEC\x91\xEC\x91\xEC\x91\xEC\x91\xEC\x91\xEC\x91\xEC\x91\xEC\x91\xEC\x91\xEC\x91\xEC\x91\xEC\x91\xEC\0\0\x91\xEC\x91\xEC\x91\xEC\x91\xEC\x91\xEC\x91\xEC\x91\xEC\0\0\x91\xEC\x91\xEC\0\0\0\0\0\0\x05\0\x04\0\x04\0\x05\0\x05\0\x05\0\0\0\x05\0\x05\0\x05\0\x05\0\x05\0\x05\0\x05\0\x05\0\x05\0\0\0\0\0\0\0\0\0\0\0\x04\0\x04\0\x04\0\0\0\x04\0\x04\0\0\0\0\0\0\0\0\0\0\0\x04\0d\0\x04\0D\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0D\0d\0d\0\0\0\0\0\0\0\0\0d\0\0\0\0\0\0\0\0\0\0\0D\0d\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x12 \x12 \x12 \x12 \x12 \x12 \x12 \x12 \x12 \x12 \x12 \x12 \x12 \x12 \x12 \x12 \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x11\xE0\x11\xE0\x11\xE0\x11\xE0\x11\xE0\x11\xE0\x11\xE0\x11\xE0\x11\xE0\x11\xE0\x11\xE0\x11\xE0\x11\xE0\x11\xE0\x11\xE0\x11\xE0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0D\0D\0D\0D\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x12\x10\x12\x10\x12\x10\x12\x10\x12\x10\x12\x10\x12\x10\x12\x10\x12\x10\x12\x10\x12\x10\x12\x10\x12\x10\x12\x10\x12\x10\x12\x10\0\0\0\0\0\0D\0D\0D\0D\0D\0\0\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0D\0D\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0d\0d\0d\0\0\0\0\0\0\0\0\0\0\0\0\0d\0d\0D\0D\0D\0d\0D\0d\0d\0d\0\0\0\0\0D\0d\0D\0d\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0d\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0d\0\0\0\0\0\x04\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0d\0\0\0\0\0\0\0\x04\0\x04\0\x04\0\x04\0\0\0\0\0d\0d\0\0\0\0\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\x04\0\x04\0\x04\0\x04\0\0\0\x04\0\x04\0\x04\0d\0d\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\0\0`\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0d\0\x04\0\x04\0\0\0\0\0\x04\0`\0d\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\0\0\0\0\0\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0d\0d\0\0\0\0\0\0\0\0\0\0\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0`\0\0\0\0\0\0\0\0\0\0\0\0\0D\0D\0D\0D\0D\0D\0D\0\0\0\0\0\0\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0d\0`\0\0\0\0\0d\0\x04\0\x04\0\0\0d\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\0\0\x04\0\0\0\0\0\0\0\0\0\x04\0\0\0d\0d\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\x04\0\x04\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\x04\0\0\0d\0\0\0\0\0\0\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\0\0\0\0\x04\0\0\0d\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\0\0\x04\0\0\0\0\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0`\0d\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\0\0\x04\0\0\0\0\0\x04\0\x04\0\x04\0\x04\0\0\0\x04\0\x04\0\x04\0\x04\0d\0\0\0\0\0\0\0\0\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\0\0d\0d\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\x04\0`\0d\0\0\0\0\0\0\0\0\0\x04\0\x04\0\x04\0\x04\0\0\0\0\0\x04\0\x04\0\0\0\0\0\0\0\0\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\0\0\0\0\0\0\0\0\0\0\x04\0d\0\x04\0\x04\0\x04\0\x04\0\0\0\0\0\x04\0\x04\0\x04\0\x04\0\0\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\0\0\0\0\x04\0\x04\0\x04\0\0\0\0\0\0\0\0\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\0\0\x04\0d\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\0\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\0\0d\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\0\0\0\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\0\0\x04\0\x04\0\0\0\x04\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\0\0\0\0\0\0\x04\0\0\0\x04\0\x04\0\0\0\x04\0\x04\0d\0\x04\0d\0d\0\0\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\x04\0\0\0\0\0\0\0\x04\0\0\0d\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\x04\0\x04\0\x04\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\0\0\0\0\0\0\x04\0\x04\0d\0d\0d\0d\0d\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0D\0D\0D\0D\0D\0D\0D\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\x04\0\0\0\x04\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0`\0`\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\x04\0\x04\0\x04\0\0\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\0\0\x04\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0d\0\0\0\0\0\0\0\0\0\0\0`\0`\0d\0d\0d\0\0\0\0\0\0\0`\0`\0`\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0d\0d\0d\0d\0d\0\0\0\0\0D\0D\0D\0D\0D\0d\0d\0\0\0\0\0\0\0\0\0D\0D\0D\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x02\0\x02\0\x02\0\x02\0\x02\0\x02\0\x02\0\x02\0\x02\0\x02\0\x02\0\x02\0\x02\0\x02\0\x02\0\x02\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0!\0!\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x02\0\x02\0\x02\0\x02\0\x02\0\x02\0\x02\0\x02\0\x02\0\x02\0\x02\0\x02\0\x01\0\x01\0\x01\0\x01\0\x01\0\0\0!\0!\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x02\0\x02\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0!\0!\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x02\0\0\0\x02\0\x02\0\0\0\0\0\x02\0\0\0\0\0\x02\0\x02\0\0\0\0\0\x02\0\x02\0\x02\0\x02\0\0\0\x02\0\x02\0\x02\0\x02\0\x02\0\x02\0\x01\0\x01\0\x01\0\x01\0\0\0\x01\0\0\0\x01\0!\0!\0\x01\0\x01\0\x01\0\x01\0\0\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x02\0\x02\0\0\0\x02\0\x02\0\x02\0\x02\0\0\0\0\0\x02\0\x02\0\x02\0\x02\0\x02\0\0\0\x02\0\x02\0\x02\0\x02\0\x02\0\x02\0\x02\0\0\0\x01\0\x01\0\x02\0\x02\0\x02\0\x02\0\x02\0\0\0\x02\0\0\0\0\0\0\0\x02\0\x02\0\x02\0\x02\0\x02\0\x02\0\0\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0!\0!\0\x01\0\x01\0\x01\0\x01\0\x02\0\x02\0\x02\0\x02\0\x02\0\x02\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0!\0!\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\0\0\0\0\x02\0\x02\0\x02\0\x02\0\x02\0\x02\0\x02\0\x02\0\0\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\0\0\x01\0\x01\0\x01\0\x01\0\x02\0\x02\0\x02\0\x02\0\x02\0\x02\0\x02\0\x02\0\x02\0\x02\0\x02\0\x02\0\x02\0\x02\0\0\0\x01\0\x01\0\x01\0\x01\0\x01\0\0\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x02\0\x02\0\x02\0\x02\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\0\0\x02\0\x02\0\x02\0\x02\0\x02\0\x02\0\x02\0\x02\0\x02\0\x02\0\x02\0\x02\0\x02\0\x02\0\x02\0\0\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\0\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x02\0\x01\0\0\0\0\0\0\0\0\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\0\0\0\0\0\0\0\0\x04\0\x04\0\x04\0\x04\0\x04\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\0\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0!\0\x01\0\x01\0\x01\0\x01\0\0\0\0\0\0\0\0\0\0\0\x01\0\x01\0\x01\0\x01\0\x01\0\x01\0\0\0\0\0\0\0\0\0\0\0D\0D\0D\0D\0D\0D\0D\0\0\0D\0D\0D\0D\0D\0D\0D\0D\0D\0\0\0\0\0D\0D\0D\0D\0D\0\0\0D\0D\0\0\0D\0D\0D\0D\0D\0\0\0\0\0\0\0\0\0\0\0\x05\0\x05\0\x05\0\x05\0\x05\0\x05\0\x05\0\x05\0\x05\0\x05\0\x05\0\x05\0%\0%\0\x05\0\x05\0\x05\0\x05\0\x05\0\x05\0\x05\0\x05\0%\0\x05\0\x05\0\x05\0\x05\0\x05\0\0\0\0\0D\0D\0D\0D\0D\0D\0D\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0d\0d\0d\0D\0d\0d\0d\0d\0d\0d\0d\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x12\x11\x12\x11\x12\x11\x12\x11\x12\x11\x12\x11\x12\x11\x12\x11\x12\x11\x12\x11\x12\x11\x12\x11\x12\x11\x12\x11\x12\x11\x12\x11\x11\xEF\x11\xEF\x11\xEF\x11\xEF\x11\xEF\x11\xEF\x11\xEF\x11\xEF\x11\xEF\x11\xEF\x11\xEF\x11\xEF\x11\xEF\x11\xEF\x11\xEF\x11\xEFD\0D\0D\0D\0D\0D\0d\0\x04\0\0\0\0\0\0\0\0\0\x02\0\x02\0\x02\0\x02\0\x02\0\x02\0\x02\0\x02\0\x02\0\x02\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0") }, icu::casemap::provider::data::CaseMapData { ignoreable: false, kind: icu::casemap::provider::data::CaseMapDataKind::Uncased(icu::casemap::provider::data::NonExceptionData { sensitive: false, dot_type: icu::casemap::provider::data::DotType::NoDot }) }), exceptions: icu::casemap::provider::exceptions::CaseMapExceptions { exceptions: unsafe { zerovec::vecs::VarZeroVec16::from_bytes_unchecked(b"{\x01\x07\0\n\0\x10\0\x15\0\x1C\0\"\0'\0-\x004\0C\0J\0M\0T\0Z\0h\0l\0q\0w\0\x7F\0\x85\0\x8B\0\x93\0\x99\0\x9F\0\xA7\0\xAD\0\xBB\0\xC1\0\xC9\0\xCF\0\xD4\0\xD9\0\xDE\0\xE3\0\xE8\0\xED\0\xF2\0\xF7\0\xFC\0\x01\x01\x06\x01\x0B\x01\x10\x01\x15\x01\x1A\x01\x1F\x01$\x01)\x01.\x010\x019\x01T\x01Z\x01`\x01h\x01q\x01w\x01}\x01\x83\x01\x89\x01\x8E\x01\x94\x01\x9B\x01\xB6\x01\xBC\x01\xC2\x01\xCA\x01\xD3\x01\xD9\x01\xDF\x01\xE5\x01\xEB\x01\xF1\x01\xF7\x01\xFD\x01\x04\x02\n\x02\x12\x02\x18\x02\x1E\x02$\x02*\x022\x028\x02?\x02F\x02M\x02T\x02^\x02e\x02l\x02s\x02z\x02\x81\x02\x8B\x02\x92\x02\x99\x02\xA0\x02\xA9\x02\xAE\x02\xB6\x02\xBE\x02\xC6\x02\xCE\x02\xD6\x02\xDE\x02\xE6\x02\xEE\x02\xF6\x02\xFE\x02\x06\x03\x0E\x03\x16\x03\x1E\x03&\x03.\x036\x03>\x03F\x03N\x03V\x03^\x03f\x03n\x03v\x03~\x03\x86\x03\x8E\x03\x96\x03\x9E\x03\xA6\x03\xAE\x03\xB6\x03\xBE\x03\xC6\x03\xCE\x03\xD6\x03\xDE\x03\xE6\x03\xEE\x03\xF6\x03\xFE\x03\x06\x04\x0E\x04\x16\x04\x1E\x04#\x04&\x04.\x046\x04>\x04F\x04N\x04V\x04\\\x04b\x04h\x04n\x04w\x04\x80\x04\x86\x04\x8C\x04\x94\x04\x99\x04\x9E\x04\xA3\x04\xA8\x04\xB0\x04\xB8\x04\xC6\x04\xD4\x04\xE2\x04\xF0\x04\xFE\x04\x06\x05\x10\x05!\x058\x05O\x05f\x05v\x05\x86\x05\x96\x05\xA6\x05\xB6\x05\xC6\x05\xD6\x05\xE6\x05\xF6\x05\x06\x06\x16\x06&\x066\x06F\x06V\x06f\x06v\x06\x86\x06\x96\x06\xA6\x06\xB6\x06\xC6\x06\xD6\x06\xE6\x06\xF6\x06\x06\x07\x16\x07&\x076\x07F\x07V\x07f\x07v\x07\x86\x07\x96\x07\xA6\x07\xB6\x07\xC6\x07\xD6\x07\xE6\x07\xF6\x07\x06\x08\x16\x08&\x086\x08F\x08V\x08f\x08z\x08\x88\x08\x99\x08\xAA\x08\xC1\x08\xCF\x08\xD7\x08\xEB\x08\xF9\x08\n\t\x1B\t2\t@\tW\tp\t\x81\t\x98\t\xAF\t\xC8\t\xD9\t\xEA\t\x01\n\x15\n#\n4\nE\n\\\nj\nq\nw\n~\n\x83\n\x88\n\x8D\n\x92\n\x97\n\x9C\n\xA1\n\xA6\n\xAB\n\xB0\n\xB5\n\xBD\n\xC5\n\xCA\n\xCF\n\xD4\n\xD9\n\xDE\n\xE3\n\xE8\n\xED\n\xF2\n\xF6\n\xFB\n\0\x0B\x05\x0B\n\x0B\x0E\x0B\x16\x0B\x1E\x0B&\x0B.\x0B6\x0B>\x0BF\x0BN\x0BV\x0B^\x0Bf\x0Bn\x0Bv\x0B~\x0B\x86\x0B\x8E\x0B\x96\x0B\x9E\x0B\xA6\x0B\xAE\x0B\xB6\x0B\xBE\x0B\xC6\x0B\xCE\x0B\xD6\x0B\xDE\x0B\xE6\x0B\xEE\x0B\xF6\x0B\xFE\x0B\x06\x0C\x0E\x0C\x16\x0C\x1E\x0C&\x0C.\x0C6\x0C>\x0CF\x0CN\x0CV\x0C^\x0Cf\x0Cn\x0Cv\x0C~\x0C\x86\x0C\x8E\x0C\x96\x0C\x9E\x0C\xA6\x0C\xAE\x0C\xB6\x0C\xBE\x0C\xC6\x0C\xCE\x0C\xD6\x0C\xDE\x0C\xE6\x0C\xEE\x0C\xF6\x0C\xFE\x0C\x06\r\x0E\r\x16\r\x1E\r&\r.\r6\r>\rF\rN\rV\r^\rf\rn\rv\r~\r\x86\r\x8E\r\x99\r\xA4\r\xAF\r\xBD\r\xCB\r\xD9\r\xE8\r\xF9\r\n\x0E\x1B\x0E,\x0E\xC8P \xC4\xB0\xC4\xB1H\x10 \x08Ak\xE2\x84\xAA\x08As\xC5\xBF\\P \xC4\xB0\xC4\xB1\x08DK\xE2\x84\xAA\x08DS\xC5\xBF\x08\x06\xCE\xBC\xCE\x9C\x08A\xC3\xA5\xE2\x84\xAB\x08\xC0\x03\xE1\xBA\x9E\0\x02\x04ssSSSs\x08D\xC3\x85\xE2\x84\xABH\x10\x01\xCEP\xC3\x87I\xC4\xB1\x08DIi\xC4\xB0\x08\x80\0\x03\x06\xCA\xBCn\xCA\xBCN\xCA\xBCN\x08\x06sS\x08\x10\xEA\x99\x81\x08\t\xC7\x86\xC7\x85\x08\r\xC7\x86\xC7\x84\xC7\x85\x08\x0C\xC7\x84\xC7\x85\x08\t\xC7\x89\xC7\x88\x08\r\xC7\x89\xC7\x87\xC7\x88\x08\x0C\xC7\x87\xC7\x88\x08\t\xC7\x8C\xC7\x8B\x08\r\xC7\x8C\xC7\x8A\xC7\x8B\x08\x0C\xC7\x8A\xC7\x8B\x08\x80\0\x03\x06j\xCC\x8CJ\xCC\x8CJ\xCC\x8C\x08\t\xC7\xB3\xC7\xB2\x08\r\xC7\xB3\xC7\xB1\xC7\xB2\x08\x0C\xC7\xB1\xC7\xB2\x08\x10\xE2\xA8\xAB\x08\x10\xE2\xA8\xA8\x08\x10\xE2\xA8\xBF\x08\x10\xE2\xA8\x9F\x08\x10\xE2\xA8\x9C\x08\x10\xE2\xA8\x9E\x08\x10\xEA\x95\x8F\x08\x10\xEA\x95\x8B\x08\x10\xEA\x95\xA7\x08\x10\xEA\x94\xA8\x08\x10\xEA\x95\x84\x08\x10\xE2\xA7\xB7\x08\x10\xEA\x95\x81\x08\x10\xE2\xA7\xBD\x08\x10\xE2\xA7\xA7\x08\x10\xEA\x95\x83\x08\x10\xEA\x94\xAA\x18\x10\xEA\x94\x95\x08\x10\xEA\x94\x92h\08F\xCE\xB9\xCE\x99\xE1\xBE\xBE\x08\xC0\x03\xE1\xBF\x93\0\x06\x0C\xCE\xB9\xCC\x88\xCC\x81\xCE\x99\xCC\x88\xCC\x81\xCE\x99\xCC\x88\xCC\x81\x08A\xCE\xB2\xCF\x90\x08A\xCE\xB5\xCF\xB5\x08A\xCE\xB8\xCF\x91\xCF\xB4\x08A\xCE\xB9\xCD\x85\xE1\xBE\xBE\x08A\xCE\xBA\xCF\xB0\x08A\xCE\xBC\xC2\xB5\x08A\xCF\x80\xCF\x96\x08A\xCF\x81\xCF\xB1HP \xCF\x82\x08A\xCF\x86\xCF\x95\x08A\xCF\x89\xE2\x84\xA6\x08\xC0\x03\xE1\xBF\xA3\0\x06\x0C\xCF\x85\xCC\x88\xCC\x81\xCE\xA5\xCC\x88\xCC\x81\xCE\xA5\xCC\x88\xCC\x81\x08D\xCE\x92\xCF\x90\x08D\xCE\x95\xCF\xB5\x08D\xCE\x98\xCF\x91\xCF\xB4\x08D\xCE\x99\xCD\x85\xE1\xBE\xBE\x08D\xCE\x9A\xCF\xB0\x08D\xCE\x9C\xC2\xB5\x08D\xCE\xA0\xCF\x96\x08D\xCE\xA1\xCF\xB1\x08\x06\xCF\x83\xCE\xA3\x08D\xCE\xA3\xCF\x82\x08D\xCE\xA6\xCF\x95\x08D\xCE\xA9\xE2\x84\xA6\x08\x06\xCE\xB2\xCE\x92\x08F\xCE\xB8\xCE\x98\xCF\xB4\x08\x06\xCF\x86\xCE\xA6\x08\x06\xCF\x80\xCE\xA0\x08\x06\xCE\xBA\xCE\x9A\x08\x06\xCF\x81\xCE\xA1\x08A\xCE\xB8\xCE\x98\xCF\x91\x08\x06\xCE\xB5\xCE\x95\x08A\xD0\xB2\xE1\xB2\x80\x08A\xD0\xB4\xE1\xB2\x81\x08A\xD0\xBE\xE1\xB2\x82\x08A\xD1\x81\xE1\xB2\x83\x08A\xD1\x82\xE1\xB2\x84\xE1\xB2\x85\x08A\xD1\x8A\xE1\xB2\x86\x08D\xD0\x92\xE1\xB2\x80\x08D\xD0\x94\xE1\xB2\x81\x08D\xD0\x9E\xE1\xB2\x82\x08D\xD0\xA1\xE1\xB2\x83\x08D\xD0\xA2\xE1\xB2\x84\xE1\xB2\x85\x08D\xD0\xAA\xE1\xB2\x86\x08A\xD1\xA3\xE1\xB2\x87\x08D\xD1\xA2\xE1\xB2\x87H\x80\0\x04\x04\xD5\xA5\xD6\x82\x08\x10\xE1\xB1\xA0\x08\x0C\xE1\xB2\x90\xE1\x83\x90\x08\x0C\xE1\xB2\x91\xE1\x83\x91\x08\x0C\xE1\xB2\x92\xE1\x83\x92\x08\x0C\xE1\xB2\x93\xE1\x83\x93\x08\x0C\xE1\xB2\x94\xE1\x83\x94\x08\x0C\xE1\xB2\x95\xE1\x83\x95\x08\x0C\xE1\xB2\x96\xE1\x83\x96\x08\x0C\xE1\xB2\x97\xE1\x83\x97\x08\x0C\xE1\xB2\x98\xE1\x83\x98\x08\x0C\xE1\xB2\x99\xE1\x83\x99\x08\x0C\xE1\xB2\x9A\xE1\x83\x9A\x08\x0C\xE1\xB2\x9B\xE1\x83\x9B\x08\x0C\xE1\xB2\x9C\xE1\x83\x9C\x08\x0C\xE1\xB2\x9D\xE1\x83\x9D\x08\x0C\xE1\xB2\x9E\xE1\x83\x9E\x08\x0C\xE1\xB2\x9F\xE1\x83\x9F\x08\x0C\xE1\xB2\xA0\xE1\x83\xA0\x08\x0C\xE1\xB2\xA1\xE1\x83\xA1\x08\x0C\xE1\xB2\xA2\xE1\x83\xA2\x08\x0C\xE1\xB2\xA3\xE1\x83\xA3\x08\x0C\xE1\xB2\xA4\xE1\x83\xA4\x08\x0C\xE1\xB2\xA5\xE1\x83\xA5\x08\x0C\xE1\xB2\xA6\xE1\x83\xA6\x08\x0C\xE1\xB2\xA7\xE1\x83\xA7\x08\x0C\xE1\xB2\xA8\xE1\x83\xA8\x08\x0C\xE1\xB2\xA9\xE1\x83\xA9\x08\x0C\xE1\xB2\xAA\xE1\x83\xAA\x08\x0C\xE1\xB2\xAB\xE1\x83\xAB\x08\x0C\xE1\xB2\xAC\xE1\x83\xAC\x08\x0C\xE1\xB2\xAD\xE1\x83\xAD\x08\x0C\xE1\xB2\xAE\xE1\x83\xAE\x08\x0C\xE1\xB2\xAF\xE1\x83\xAF\x08\x0C\xE1\xB2\xB0\xE1\x83\xB0\x08\x0C\xE1\xB2\xB1\xE1\x83\xB1\x08\x0C\xE1\xB2\xB2\xE1\x83\xB2\x08\x0C\xE1\xB2\xB3\xE1\x83\xB3\x08\x0C\xE1\xB2\xB4\xE1\x83\xB4\x08\x0C\xE1\xB2\xB5\xE1\x83\xB5\x08\x0C\xE1\xB2\xB6\xE1\x83\xB6\x08\x0C\xE1\xB2\xB7\xE1\x83\xB7\x08\x0C\xE1\xB2\xB8\xE1\x83\xB8\x08\x0C\xE1\xB2\xB9\xE1\x83\xB9\x08\x0C\xE1\xB2\xBA\xE1\x83\xBA\x08\x0C\xE1\xB2\xBD\xE1\x83\xBD\x08\x0C\xE1\xB2\xBE\xE1\x83\xBE\x08\x0C\xE1\xB2\xBF\xE1\x83\xBF\n\x10\xE9\x9F\x90\n\x10\x08\x08\x06\xE1\x8F\xB0\xE1\x8F\xB0\x08\x06\xE1\x8F\xB1\xE1\x8F\xB1\x08\x06\xE1\x8F\xB2\xE1\x8F\xB2\x08\x06\xE1\x8F\xB3\xE1\x8F\xB3\x08\x06\xE1\x8F\xB4\xE1\x8F\xB4\x08\x06\xE1\x8F\xB5\xE1\x8F\xB5\x08\x06\xD0\xB2\xD0\x92\x08\x06\xD0\xB4\xD0\x94\x08\x06\xD0\xBE\xD0\x9E\x08\x06\xD1\x81\xD0\xA1\x08F\xD1\x82\xD0\xA2\xE1\xB2\x85\x08F\xD1\x82\xD0\xA2\xE1\xB2\x84\x08\x06\xD1\x8A\xD0\xAA\x08\x06\xD1\xA3\xD1\xA2\x08\x06\xEA\x99\x8B\xEA\x99\x8A\x0C\x10\xE0\xAF\x80\x08\x10\xE8\xA8\x84\x08\x10\xE0\xBB\xA6\x08\x10\xE8\xA8\xB8\x08A\xE1\xB9\xA1\xE1\xBA\x9B\x08D\xE1\xB9\xA0\xE1\xBA\x9B\x08\x80\0\x03\x06h\xCC\xB1H\xCC\xB1H\xCC\xB1\x08\x80\0\x03\x06t\xCC\x88T\xCC\x88T\xCC\x88\x08\x80\0\x03\x06w\xCC\x8AW\xCC\x8AW\xCC\x8A\x08\x80\0\x03\x06y\xCC\x8AY\xCC\x8AY\xCC\x8A\x08\x80\0\x03\x06a\xCA\xBEA\xCA\xBEA\xCA\xBE\x08\x06\xE1\xB9\xA1\xE1\xB9\xA0\x0C\x90\xE1\xB6\xBF\0\x02\x02ss\x08\x80\0\x04\x08\xCF\x85\xCC\x93\xCE\xA5\xCC\x93\xCE\xA5\xCC\x93\x08\x80\0\x06\x0C\xCF\x85\xCC\x93\xCC\x80\xCE\xA5\xCC\x93\xCC\x80\xCE\xA5\xCC\x93\xCC\x80\x08\x80\0\x06\x0C\xCF\x85\xCC\x93\xCC\x81\xCE\xA5\xCC\x93\xCC\x81\xCE\xA5\xCC\x93\xCC\x81\x08\x80\0\x06\x0C\xCF\x85\xCC\x93\xCD\x82\xCE\xA5\xCC\x93\xCD\x82\xCE\xA5\xCC\x93\xCD\x82\x08\x90\x08\0\x05\n\xE1\xBC\x80\xCE\xB9\xE1\xBC\x88\xCE\x99\x08\x90\x08\0\x05\n\xE1\xBC\x81\xCE\xB9\xE1\xBC\x89\xCE\x99\x08\x90\x08\0\x05\n\xE1\xBC\x82\xCE\xB9\xE1\xBC\x8A\xCE\x99\x08\x90\x08\0\x05\n\xE1\xBC\x83\xCE\xB9\xE1\xBC\x8B\xCE\x99\x08\x90\x08\0\x05\n\xE1\xBC\x84\xCE\xB9\xE1\xBC\x8C\xCE\x99\x08\x90\x08\0\x05\n\xE1\xBC\x85\xCE\xB9\xE1\xBC\x8D\xCE\x99\x08\x90\x08\0\x05\n\xE1\xBC\x86\xCE\xB9\xE1\xBC\x8E\xCE\x99\x08\x90\x08\0\x05\n\xE1\xBC\x87\xCE\xB9\xE1\xBC\x8F\xCE\x99\x0C\x90\x08\0\x05\n\xE1\xBC\x80\xCE\xB9\xE1\xBC\x88\xCE\x99\x0C\x90\x08\0\x05\n\xE1\xBC\x81\xCE\xB9\xE1\xBC\x89\xCE\x99\x0C\x90\x08\0\x05\n\xE1\xBC\x82\xCE\xB9\xE1\xBC\x8A\xCE\x99\x0C\x90\x08\0\x05\n\xE1\xBC\x83\xCE\xB9\xE1\xBC\x8B\xCE\x99\x0C\x90\x08\0\x05\n\xE1\xBC\x84\xCE\xB9\xE1\xBC\x8C\xCE\x99\x0C\x90\x08\0\x05\n\xE1\xBC\x85\xCE\xB9\xE1\xBC\x8D\xCE\x99\x0C\x90\x08\0\x05\n\xE1\xBC\x86\xCE\xB9\xE1\xBC\x8E\xCE\x99\x0C\x90\x08\0\x05\n\xE1\xBC\x87\xCE\xB9\xE1\xBC\x8F\xCE\x99\x08\x90\x08\0\x05\n\xE1\xBC\xA0\xCE\xB9\xE1\xBC\xA8\xCE\x99\x08\x90\x08\0\x05\n\xE1\xBC\xA1\xCE\xB9\xE1\xBC\xA9\xCE\x99\x08\x90\x08\0\x05\n\xE1\xBC\xA2\xCE\xB9\xE1\xBC\xAA\xCE\x99\x08\x90\x08\0\x05\n\xE1\xBC\xA3\xCE\xB9\xE1\xBC\xAB\xCE\x99\x08\x90\x08\0\x05\n\xE1\xBC\xA4\xCE\xB9\xE1\xBC\xAC\xCE\x99\x08\x90\x08\0\x05\n\xE1\xBC\xA5\xCE\xB9\xE1\xBC\xAD\xCE\x99\x08\x90\x08\0\x05\n\xE1\xBC\xA6\xCE\xB9\xE1\xBC\xAE\xCE\x99\x08\x90\x08\0\x05\n\xE1\xBC\xA7\xCE\xB9\xE1\xBC\xAF\xCE\x99\x0C\x90\x08\0\x05\n\xE1\xBC\xA0\xCE\xB9\xE1\xBC\xA8\xCE\x99\x0C\x90\x08\0\x05\n\xE1\xBC\xA1\xCE\xB9\xE1\xBC\xA9\xCE\x99\x0C\x90\x08\0\x05\n\xE1\xBC\xA2\xCE\xB9\xE1\xBC\xAA\xCE\x99\x0C\x90\x08\0\x05\n\xE1\xBC\xA3\xCE\xB9\xE1\xBC\xAB\xCE\x99\x0C\x90\x08\0\x05\n\xE1\xBC\xA4\xCE\xB9\xE1\xBC\xAC\xCE\x99\x0C\x90\x08\0\x05\n\xE1\xBC\xA5\xCE\xB9\xE1\xBC\xAD\xCE\x99\x0C\x90\x08\0\x05\n\xE1\xBC\xA6\xCE\xB9\xE1\xBC\xAE\xCE\x99\x0C\x90\x08\0\x05\n\xE1\xBC\xA7\xCE\xB9\xE1\xBC\xAF\xCE\x99\x08\x90\x08\0\x05\n\xE1\xBD\xA0\xCE\xB9\xE1\xBD\xA8\xCE\x99\x08\x90\x08\0\x05\n\xE1\xBD\xA1\xCE\xB9\xE1\xBD\xA9\xCE\x99\x08\x90\x08\0\x05\n\xE1\xBD\xA2\xCE\xB9\xE1\xBD\xAA\xCE\x99\x08\x90\x08\0\x05\n\xE1\xBD\xA3\xCE\xB9\xE1\xBD\xAB\xCE\x99\x08\x90\x08\0\x05\n\xE1\xBD\xA4\xCE\xB9\xE1\xBD\xAC\xCE\x99\x08\x90\x08\0\x05\n\xE1\xBD\xA5\xCE\xB9\xE1\xBD\xAD\xCE\x99\x08\x90\x08\0\x05\n\xE1\xBD\xA6\xCE\xB9\xE1\xBD\xAE\xCE\x99\x08\x90\x08\0\x05\n\xE1\xBD\xA7\xCE\xB9\xE1\xBD\xAF\xCE\x99\x0C\x90\x08\0\x05\n\xE1\xBD\xA0\xCE\xB9\xE1\xBD\xA8\xCE\x99\x0C\x90\x08\0\x05\n\xE1\xBD\xA1\xCE\xB9\xE1\xBD\xA9\xCE\x99\x0C\x90\x08\0\x05\n\xE1\xBD\xA2\xCE\xB9\xE1\xBD\xAA\xCE\x99\x0C\x90\x08\0\x05\n\xE1\xBD\xA3\xCE\xB9\xE1\xBD\xAB\xCE\x99\x0C\x90\x08\0\x05\n\xE1\xBD\xA4\xCE\xB9\xE1\xBD\xAC\xCE\x99\x0C\x90\x08\0\x05\n\xE1\xBD\xA5\xCE\xB9\xE1\xBD\xAD\xCE\x99\x0C\x90\x08\0\x05\n\xE1\xBD\xA6\xCE\xB9\xE1\xBD\xAE\xCE\x99\x0C\x90\x08\0\x05\n\xE1\xBD\xA7\xCE\xB9\xE1\xBD\xAF\xCE\x99\x08\x80\0\x05\n\xE1\xBD\xB0\xCE\xB9\xE1\xBE\xBA\xCE\x99\xE1\xBE\xBA\xCD\x85\x08\x90\t\0\x04\x08\xCE\xB1\xCE\xB9\xCE\x91\xCE\x99\x08\x80\0\x04\x08\xCE\xAC\xCE\xB9\xCE\x86\xCE\x99\xCE\x86\xCD\x85\x08\x80\0\x04\x08\xCE\xB1\xCD\x82\xCE\x91\xCD\x82\xCE\x91\xCD\x82\x08\x80\0\x06\x0C\xCE\xB1\xCD\x82\xCE\xB9\xCE\x91\xCD\x82\xCE\x99\xCE\x91\xCD\x82\xCD\x85\x0C\x90\t\0\x04\x08\xCE\xB1\xCE\xB9\xCE\x91\xCE\x99\x08F\xCE\xB9\xCE\x99\xCD\x85\x08\x80\0\x05\n\xE1\xBD\xB4\xCE\xB9\xE1\xBF\x8A\xCE\x99\xE1\xBF\x8A\xCD\x85\x08\x90\t\0\x04\x08\xCE\xB7\xCE\xB9\xCE\x97\xCE\x99\x08\x80\0\x04\x08\xCE\xAE\xCE\xB9\xCE\x89\xCE\x99\xCE\x89\xCD\x85\x08\x80\0\x04\x08\xCE\xB7\xCD\x82\xCE\x97\xCD\x82\xCE\x97\xCD\x82\x08\x80\0\x06\x0C\xCE\xB7\xCD\x82\xCE\xB9\xCE\x97\xCD\x82\xCE\x99\xCE\x97\xCD\x82\xCD\x85\x0C\x90\t\0\x04\x08\xCE\xB7\xCE\xB9\xCE\x97\xCE\x99\x08\x80\0\x06\x0C\xCE\xB9\xCC\x88\xCC\x80\xCE\x99\xCC\x88\xCC\x80\xCE\x99\xCC\x88\xCC\x80\x08\x82\xCE\x90\0\x06\x0C\xCE\xB9\xCC\x88\xCC\x81\xCE\x99\xCC\x88\xCC\x81\xCE\x99\xCC\x88\xCC\x81\x08\x80\0\x04\x08\xCE\xB9\xCD\x82\xCE\x99\xCD\x82\xCE\x99\xCD\x82\x08\x80\0\x06\x0C\xCE\xB9\xCC\x88\xCD\x82\xCE\x99\xCC\x88\xCD\x82\xCE\x99\xCC\x88\xCD\x82\x08\x80\0\x06\x0C\xCF\x85\xCC\x88\xCC\x80\xCE\xA5\xCC\x88\xCC\x80\xCE\xA5\xCC\x88\xCC\x80\x08\x82\xCE\xB0\0\x06\x0C\xCF\x85\xCC\x88\xCC\x81\xCE\xA5\xCC\x88\xCC\x81\xCE\xA5\xCC\x88\xCC\x81\x08\x80\0\x04\x08\xCF\x81\xCC\x93\xCE\xA1\xCC\x93\xCE\xA1\xCC\x93\x08\x80\0\x04\x08\xCF\x85\xCD\x82\xCE\xA5\xCD\x82\xCE\xA5\xCD\x82\x08\x80\0\x06\x0C\xCF\x85\xCC\x88\xCD\x82\xCE\xA5\xCC\x88\xCD\x82\xCE\xA5\xCC\x88\xCD\x82\x08\x80\0\x05\n\xE1\xBD\xBC\xCE\xB9\xE1\xBF\xBA\xCE\x99\xE1\xBF\xBA\xCD\x85\x08\x90\t\0\x04\x08\xCF\x89\xCE\xB9\xCE\xA9\xCE\x99\x08\x80\0\x04\x08\xCF\x8E\xCE\xB9\xCE\x8F\xCE\x99\xCE\x8F\xCD\x85\x08\x80\0\x04\x08\xCF\x89\xCD\x82\xCE\xA9\xCD\x82\xCE\xA9\xCD\x82\x08\x80\0\x06\x0C\xCF\x89\xCD\x82\xCE\xB9\xCE\xA9\xCD\x82\xCE\x99\xCE\xA9\xCD\x82\xCD\x85\x0C\x90\t\0\x04\x08\xCF\x89\xCE\xB9\xCE\xA9\xCE\x99\x0CP\xE1\xB5\x9D\xCE\xA9\x0CP\xE2\x82\xBFK\x0CP\xE2\x81\x86\xC3\x85\x0C\x10\xE2\xA7\xB7\x0C\x10\xE0\xBB\xA6\x0C\x10\xE2\xA7\xA7\x0C\x10\xE2\xA8\xAB\x0C\x10\xE2\xA8\xA8\x0C\x10\xE2\xA8\x9C\x0C\x10\xE2\xA7\xBD\x0C\x10\xE2\xA8\x9F\x0C\x10\xE2\xA8\x9E\x0C\x10\xE2\xA8\xBF\x0C\x10\xE1\xB1\xA0\x08A\xEA\x99\x8B\xE1\xB2\x88\x08D\xEA\x99\x8A\xE1\xB2\x88\x0C\x10\xE8\xA8\x84\x0C\x10\xEA\x94\xA8\x0C\x10\xEA\x95\x84\x0C\x10\xEA\x95\x8F\x0C\x10\xEA\x95\x8B\x0C\x10\xEA\x95\x81\x0C\x10\xEA\x94\x92\x0C\x10\xEA\x94\xAA\x0C\x10\xEA\x94\x95\x08\x10\xCE\xA0\x0C\x10\xEA\x95\x83\x0C\x10\xE8\xA8\xB8\x0C\x10\xEA\x95\xA7\x0C\x10\xEA\x99\x81\x0C\x10\xCE\xA0\x08\x06\xE1\x8E\xA0\xE1\x8E\xA0\x08\x06\xE1\x8E\xA1\xE1\x8E\xA1\x08\x06\xE1\x8E\xA2\xE1\x8E\xA2\x08\x06\xE1\x8E\xA3\xE1\x8E\xA3\x08\x06\xE1\x8E\xA4\xE1\x8E\xA4\x08\x06\xE1\x8E\xA5\xE1\x8E\xA5\x08\x06\xE1\x8E\xA6\xE1\x8E\xA6\x08\x06\xE1\x8E\xA7\xE1\x8E\xA7\x08\x06\xE1\x8E\xA8\xE1\x8E\xA8\x08\x06\xE1\x8E\xA9\xE1\x8E\xA9\x08\x06\xE1\x8E\xAA\xE1\x8E\xAA\x08\x06\xE1\x8E\xAB\xE1\x8E\xAB\x08\x06\xE1\x8E\xAC\xE1\x8E\xAC\x08\x06\xE1\x8E\xAD\xE1\x8E\xAD\x08\x06\xE1\x8E\xAE\xE1\x8E\xAE\x08\x06\xE1\x8E\xAF\xE1\x8E\xAF\x08\x06\xE1\x8E\xB0\xE1\x8E\xB0\x08\x06\xE1\x8E\xB1\xE1\x8E\xB1\x08\x06\xE1\x8E\xB2\xE1\x8E\xB2\x08\x06\xE1\x8E\xB3\xE1\x8E\xB3\x08\x06\xE1\x8E\xB4\xE1\x8E\xB4\x08\x06\xE1\x8E\xB5\xE1\x8E\xB5\x08\x06\xE1\x8E\xB6\xE1\x8E\xB6\x08\x06\xE1\x8E\xB7\xE1\x8E\xB7\x08\x06\xE1\x8E\xB8\xE1\x8E\xB8\x08\x06\xE1\x8E\xB9\xE1\x8E\xB9\x08\x06\xE1\x8E\xBA\xE1\x8E\xBA\x08\x06\xE1\x8E\xBB\xE1\x8E\xBB\x08\x06\xE1\x8E\xBC\xE1\x8E\xBC\x08\x06\xE1\x8E\xBD\xE1\x8E\xBD\x08\x06\xE1\x8E\xBE\xE1\x8E\xBE\x08\x06\xE1\x8E\xBF\xE1\x8E\xBF\x08\x06\xE1\x8F\x80\xE1\x8F\x80\x08\x06\xE1\x8F\x81\xE1\x8F\x81\x08\x06\xE1\x8F\x82\xE1\x8F\x82\x08\x06\xE1\x8F\x83\xE1\x8F\x83\x08\x06\xE1\x8F\x84\xE1\x8F\x84\x08\x06\xE1\x8F\x85\xE1\x8F\x85\x08\x06\xE1\x8F\x86\xE1\x8F\x86\x08\x06\xE1\x8F\x87\xE1\x8F\x87\x08\x06\xE1\x8F\x88\xE1\x8F\x88\x08\x06\xE1\x8F\x89\xE1\x8F\x89\x08\x06\xE1\x8F\x8A\xE1\x8F\x8A\x08\x06\xE1\x8F\x8B\xE1\x8F\x8B\x08\x06\xE1\x8F\x8C\xE1\x8F\x8C\x08\x06\xE1\x8F\x8D\xE1\x8F\x8D\x08\x06\xE1\x8F\x8E\xE1\x8F\x8E\x08\x06\xE1\x8F\x8F\xE1\x8F\x8F\x08\x06\xE1\x8F\x90\xE1\x8F\x90\x08\x06\xE1\x8F\x91\xE1\x8F\x91\x08\x06\xE1\x8F\x92\xE1\x8F\x92\x08\x06\xE1\x8F\x93\xE1\x8F\x93\x08\x06\xE1\x8F\x94\xE1\x8F\x94\x08\x06\xE1\x8F\x95\xE1\x8F\x95\x08\x06\xE1\x8F\x96\xE1\x8F\x96\x08\x06\xE1\x8F\x97\xE1\x8F\x97\x08\x06\xE1\x8F\x98\xE1\x8F\x98\x08\x06\xE1\x8F\x99\xE1\x8F\x99\x08\x06\xE1\x8F\x9A\xE1\x8F\x9A\x08\x06\xE1\x8F\x9B\xE1\x8F\x9B\x08\x06\xE1\x8F\x9C\xE1\x8F\x9C\x08\x06\xE1\x8F\x9D\xE1\x8F\x9D\x08\x06\xE1\x8F\x9E\xE1\x8F\x9E\x08\x06\xE1\x8F\x9F\xE1\x8F\x9F\x08\x06\xE1\x8F\xA0\xE1\x8F\xA0\x08\x06\xE1\x8F\xA1\xE1\x8F\xA1\x08\x06\xE1\x8F\xA2\xE1\x8F\xA2\x08\x06\xE1\x8F\xA3\xE1\x8F\xA3\x08\x06\xE1\x8F\xA4\xE1\x8F\xA4\x08\x06\xE1\x8F\xA5\xE1\x8F\xA5\x08\x06\xE1\x8F\xA6\xE1\x8F\xA6\x08\x06\xE1\x8F\xA7\xE1\x8F\xA7\x08\x06\xE1\x8F\xA8\xE1\x8F\xA8\x08\x06\xE1\x8F\xA9\xE1\x8F\xA9\x08\x06\xE1\x8F\xAA\xE1\x8F\xAA\x08\x06\xE1\x8F\xAB\xE1\x8F\xAB\x08\x06\xE1\x8F\xAC\xE1\x8F\xAC\x08\x06\xE1\x8F\xAD\xE1\x8F\xAD\x08\x06\xE1\x8F\xAE\xE1\x8F\xAE\x08\x06\xE1\x8F\xAF\xE1\x8F\xAF\x08\x80\0\x02\x04ffFFFf\x08\x80\0\x02\x04fiFIFi\x08\x80\0\x02\x04flFLFl\x08\x80\0\x03\x06ffiFFIFfi\x08\x80\0\x03\x06fflFFLFfl\x08\x82\xEF\xAC\x86\0\x02\x04stSTSt\x08\xC0\x03\xEF\xAC\x85\0\x02\x04stSTSt\x08\x80\0\x04\x08\xD5\xB4\xD5\xB6\xD5\x84\xD5\x86\xD5\x84\xD5\xB6\x08\x80\0\x04\x08\xD5\xB4\xD5\xA5\xD5\x84\xD4\xB5\xD5\x84\xD5\xA5\x08\x80\0\x04\x08\xD5\xB4\xD5\xAB\xD5\x84\xD4\xBB\xD5\x84\xD5\xAB\x08\x80\0\x04\x08\xD5\xBE\xD5\xB6\xD5\x8E\xD5\x86\xD5\x8E\xD5\xB6\x08\x80\0\x04\x08\xD5\xB4\xD5\xAD\xD5\x84\xD4\xBD\xD5\x84\xD5\xAD") } } };
+        }
+        #[clippy::msrv = "1.81"]
+        impl icu_provider::DataProvider<icu::casemap::provider::CaseMapV1> for $provider {
+            fn load(&self, req: icu_provider::DataRequest) -> Result<icu_provider::DataResponse<icu::casemap::provider::CaseMapV1>, icu_provider::DataError> {
+                if req.id.locale.is_default() {
+                    Ok(icu_provider::DataResponse { payload: icu_provider::DataPayload::from_static_ref(Self::SINGLETON_CASE_MAP_V1), metadata: icu_provider::DataResponseMetadata::default() })
+                } else {
+                    Err(icu_provider::DataErrorKind::InvalidRequest.with_req(<icu::casemap::provider::CaseMapV1 as icu_provider::DataMarker>::INFO, req))
+                }
+            }
+        }
+    };
+    ($ provider : ty , ITER) => {
+        __impl_case_map_v1!($provider);
+        #[clippy::msrv = "1.81"]
+        impl icu_provider::IterableDataProvider<icu::casemap::provider::CaseMapV1> for $provider {
+            fn iter_ids(&self) -> Result<std::collections::BtreeSet<icu_provider::DataIdentifierCow<'static>>, icu_provider::DataError> {
+                Ok([Default::default()].into_iter().collect())
+            }
+        }
+    };
+    ($ provider : ty , DRY) => {
+        __impl_case_map_v1!($provider);
+        #[clippy::msrv = "1.81"]
+        impl icu_provider::DryDataProvider<icu::casemap::provider::CaseMapV1> for $provider {
+            fn dry_load(&self, req: icu_provider::DataRequest) -> Result<icu_provider::DataResponseMetadata, icu_provider::DataError> {
+                if req.id.locale.is_default() {
+                    Ok(icu_provider::DataResponseMetadata::default())
+                } else {
+                    Err(icu_provider::DataErrorKind::InvalidRequest.with_req(<icu::casemap::provider::CaseMapV1 as icu_provider::DataMarker>::INFO, req))
+                }
+            }
+        }
+    };
+    ($ provider : ty , DRY , ITER) => {
+        __impl_case_map_v1!($provider);
+        #[clippy::msrv = "1.81"]
+        impl icu_provider::DryDataProvider<icu::casemap::provider::CaseMapV1> for $provider {
+            fn dry_load(&self, req: icu_provider::DataRequest) -> Result<icu_provider::DataResponseMetadata, icu_provider::DataError> {
+                if req.id.locale.is_default() {
+                    Ok(icu_provider::DataResponseMetadata::default())
+                } else {
+                    Err(icu_provider::DataErrorKind::InvalidRequest.with_req(<icu::casemap::provider::CaseMapV1 as icu_provider::DataMarker>::INFO, req))
+                }
+            }
+        }
+        #[clippy::msrv = "1.81"]
+        impl icu_provider::IterableDataProvider<icu::casemap::provider::CaseMapV1> for $provider {
+            fn iter_ids(&self) -> Result<std::collections::BtreeSet<icu_provider::DataIdentifierCow<'static>>, icu_provider::DataError> {
+                Ok([Default::default()].into_iter().collect())
+            }
+        }
+    };
+}
+#[doc(inline)]
+pub use __impl_case_map_v1 as impl_case_map_v1;
diff --git a/third_party/rust/chromium_crates_io/vendor/icu_casemap_data-2.0.0-beta2/data/mod.rs b/third_party/rust/chromium_crates_io/vendor/icu_casemap_data-2.0.0-beta2/data/mod.rs
new file mode 100644
index 0000000..6c47727
--- /dev/null
+++ b/third_party/rust/chromium_crates_io/vendor/icu_casemap_data-2.0.0-beta2/data/mod.rs
@@ -0,0 +1,36 @@
+// @generated
+include!("case_map_unfold_v1.rs.data");
+include!("case_map_v1.rs.data");
+/// Marks a type as a data provider. You can then use macros like
+/// `impl_core_helloworld_v1` to add implementations.
+///
+/// ```ignore
+/// struct MyProvider;
+/// const _: () = {
+///     include!("path/to/generated/macros.rs");
+///     make_provider!(MyProvider);
+///     impl_core_helloworld_v1!(MyProvider);
+/// }
+/// ```
+#[doc(hidden)]
+#[macro_export]
+macro_rules! __make_provider {
+    ($ name : ty) => {
+        #[clippy::msrv = "1.81"]
+        impl $name {
+            #[allow(dead_code)]
+            pub(crate) const MUST_USE_MAKE_PROVIDER_MACRO: () = ();
+        }
+        icu_provider::marker::impl_data_provider_never_marker!($name);
+    };
+}
+#[doc(inline)]
+pub use __make_provider as make_provider;
+#[allow(unused_macros)]
+macro_rules! impl_data_provider {
+    ($ provider : ty) => {
+        make_provider!($provider);
+        impl_case_map_unfold_v1!($provider);
+        impl_case_map_v1!($provider);
+    };
+}
diff --git a/third_party/rust/chromium_crates_io/vendor/icu_casemap_data-2.0.0-beta2/src/lib.rs b/third_party/rust/chromium_crates_io/vendor/icu_casemap_data-2.0.0-beta2/src/lib.rs
index a5931027..bb68fa5 100644
--- a/third_party/rust/chromium_crates_io/vendor/icu_casemap_data-2.0.0-beta2/src/lib.rs
+++ b/third_party/rust/chromium_crates_io/vendor/icu_casemap_data-2.0.0-beta2/src/lib.rs
@@ -1,9 +1,20 @@
-// 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.
+// This file is part of ICU4X. For terms of use, please see the file
+// called LICENSE at the top level of the ICU4X source tree
+// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
 
-// @generated from third_party/rust/chromium_crates_io/removed_lib.rs
-// by tools/crates/gnrt. Do not edit!
+//! Data for the `icu_casemap` crate
+//!
+//! This data was generated with CLDR version 47.0.0-BETA1, ICU version release-77-rc, and
+//! LSTM segmenter version v0.1.0.
 
-// This is an empty crate that has replaced the 'icu_casemap_data' crate, since
-// it was listed in `resolve.remove_crates` in gnrt_config.toml.
+#![no_std]
+// The source is not readable and is massive as HTML.
+#![doc(html_no_source)]
+
+#[cfg(icu4x_custom_data)]
+include!(concat!(core::env!("ICU4X_DATA_DIR"), "/mod.rs"));
+#[cfg(not(icu4x_custom_data))]
+include!("../data/mod.rs");
+
+#[rustfmt::skip]
+pub use icu_provider_baked;
diff --git a/third_party/rust/icu_capi/v2/BUILD.gn b/third_party/rust/icu_capi/v2/BUILD.gn
index ce29c76..07bc3e79 100644
--- a/third_party/rust/icu_capi/v2/BUILD.gn
+++ b/third_party/rust/icu_capi/v2/BUILD.gn
@@ -80,6 +80,7 @@
     "//third_party/rust/diplomat/v0_10:lib",
     "//third_party/rust/diplomat_runtime/v0_10:lib",
     "//third_party/rust/icu_calendar/v2:lib",
+    "//third_party/rust/icu_casemap/v2:lib",
     "//third_party/rust/icu_experimental/v0_3:lib",
     "//third_party/rust/icu_locale_core/v2:lib",
     "//third_party/rust/icu_provider/v2:lib",
@@ -92,6 +93,7 @@
   ]
   features = [
     "calendar",
+    "casemap",
     "compiled_data",
     "experimental",
   ]
diff --git a/third_party/rust/icu_casemap/v2/BUILD.gn b/third_party/rust/icu_casemap/v2/BUILD.gn
new file mode 100644
index 0000000..b225644
--- /dev/null
+++ b/third_party/rust/icu_casemap/v2/BUILD.gn
@@ -0,0 +1,67 @@
+# 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.
+
+# @generated from third_party/rust/chromium_crates_io/BUILD.gn.hbs by
+# tools/crates/gnrt.
+# Do not edit!
+
+import("//build/rust/cargo_crate.gni")
+
+cargo_crate("lib") {
+  crate_name = "icu_casemap"
+  epoch = "2"
+  crate_type = "rlib"
+  crate_root = "//third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/src/lib.rs"
+  sources = [
+    "//third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/src/casemapper.rs",
+    "//third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/src/closer.rs",
+    "//third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/src/greek_to_me/data.rs",
+    "//third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/src/greek_to_me/mod.rs",
+    "//third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/src/internals.rs",
+    "//third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/src/lib.rs",
+    "//third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/src/provider/data.rs",
+    "//third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/src/provider/exception_helpers.rs",
+    "//third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/src/provider/exceptions.rs",
+    "//third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/src/provider/exceptions_builder.rs",
+    "//third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/src/provider/mod.rs",
+    "//third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/src/provider/unfold.rs",
+    "//third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/src/set.rs",
+    "//third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/src/titlecase.rs",
+  ]
+  inputs = []
+
+  build_native_rust_unit_tests = false
+  edition = "2021"
+  cargo_pkg_version = "2.0.0-beta2"
+  cargo_pkg_authors = "The ICU4X Project Developers"
+  cargo_pkg_name = "icu_casemap"
+  cargo_pkg_description = "Unicode case mapping and folding algorithms"
+  library_configs -= [ "//build/config/coverage:default_coverage" ]
+  library_configs -= [ "//build/config/compiler:chromium_code" ]
+  library_configs += [ "//build/config/compiler:no_chromium_code" ]
+  executable_configs -= [ "//build/config/compiler:chromium_code" ]
+  executable_configs += [ "//build/config/compiler:no_chromium_code" ]
+  proc_macro_configs -= [ "//build/config/compiler:chromium_code" ]
+  proc_macro_configs += [ "//build/config/compiler:no_chromium_code" ]
+  deps = [
+    "//third_party/rust/displaydoc/v0_2:lib",
+    "//third_party/rust/icu_casemap_data/v2:lib",
+    "//third_party/rust/icu_collections/v2:lib",
+    "//third_party/rust/icu_locale_core/v2:lib",
+    "//third_party/rust/icu_properties/v2:lib",
+    "//third_party/rust/icu_provider/v2:lib",
+    "//third_party/rust/potential_utf/v0_1:lib",
+    "//third_party/rust/writeable/v0_6:lib",
+    "//third_party/rust/zerovec/v0_11:lib",
+  ]
+  features = [ "compiled_data" ]
+  rustflags = [
+    "--cap-lints=allow",  # Suppress all warnings in crates.io crates
+  ]
+
+  # Only for usage from third-party crates. Add the crate to
+  # //third_party/rust/chromium_crates_io/Cargo.toml to use
+  # it from first-party code.
+  visibility = [ "//third_party/rust/*" ]
+}
diff --git a/third_party/rust/icu_casemap/v2/README.chromium b/third_party/rust/icu_casemap/v2/README.chromium
new file mode 100644
index 0000000..7fbd23c
--- /dev/null
+++ b/third_party/rust/icu_casemap/v2/README.chromium
@@ -0,0 +1,10 @@
+Name: icu_casemap
+URL: https://crates.io/crates/icu_casemap
+Version: 2.0.0-beta2
+Revision: 610757581c7d141a6f20f97fe839ef171c320bb1
+License: Unicode-3.0
+License File: //third_party/rust/chromium_crates_io/vendor/icu_casemap-2.0.0-beta2/LICENSE
+Shipped: yes
+Security Critical: yes
+
+Description: Unicode case mapping and folding algorithms
diff --git a/third_party/rust/icu_casemap_data/v2/BUILD.gn b/third_party/rust/icu_casemap_data/v2/BUILD.gn
new file mode 100644
index 0000000..900b5f1f
--- /dev/null
+++ b/third_party/rust/icu_casemap_data/v2/BUILD.gn
@@ -0,0 +1,47 @@
+# 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.
+
+# @generated from third_party/rust/chromium_crates_io/BUILD.gn.hbs by
+# tools/crates/gnrt.
+# Do not edit!
+
+import("//build/rust/cargo_crate.gni")
+
+cargo_crate("lib") {
+  crate_name = "icu_casemap_data"
+  epoch = "2"
+  crate_type = "rlib"
+  crate_root = "//third_party/rust/chromium_crates_io/vendor/icu_casemap_data-2.0.0-beta2/src/lib.rs"
+  sources = [ "//third_party/rust/chromium_crates_io/vendor/icu_casemap_data-2.0.0-beta2/src/lib.rs" ]
+  inputs = [
+    "//third_party/rust/chromium_crates_io/vendor/icu_casemap_data-2.0.0-beta2/src/../data/case_map_unfold_v1.rs.data",
+    "//third_party/rust/chromium_crates_io/vendor/icu_casemap_data-2.0.0-beta2/src/../data/case_map_unfold_v1.rs.data",
+    "//third_party/rust/chromium_crates_io/vendor/icu_casemap_data-2.0.0-beta2/src/../data/case_map_v1.rs.data",
+    "//third_party/rust/chromium_crates_io/vendor/icu_casemap_data-2.0.0-beta2/src/../data/case_map_v1.rs.data",
+    "//third_party/rust/chromium_crates_io/vendor/icu_casemap_data-2.0.0-beta2/src/../data/mod.rs",
+  ]
+
+  build_native_rust_unit_tests = false
+  edition = "2021"
+  cargo_pkg_version = "2.0.0-beta2"
+  cargo_pkg_authors = "The ICU4X Project Developers"
+  cargo_pkg_name = "icu_casemap_data"
+  cargo_pkg_description = "Data for the icu_casemap crate"
+  library_configs -= [ "//build/config/coverage:default_coverage" ]
+  library_configs -= [ "//build/config/compiler:chromium_code" ]
+  library_configs += [ "//build/config/compiler:no_chromium_code" ]
+  executable_configs -= [ "//build/config/compiler:chromium_code" ]
+  executable_configs += [ "//build/config/compiler:no_chromium_code" ]
+  proc_macro_configs -= [ "//build/config/compiler:chromium_code" ]
+  proc_macro_configs += [ "//build/config/compiler:no_chromium_code" ]
+  deps = [ "//third_party/rust/icu_provider_baked/v2:lib" ]
+  rustflags = [
+    "--cap-lints=allow",  # Suppress all warnings in crates.io crates
+  ]
+
+  # Only for usage from third-party crates. Add the crate to
+  # //third_party/rust/chromium_crates_io/Cargo.toml to use
+  # it from first-party code.
+  visibility = [ "//third_party/rust/*" ]
+}
diff --git a/third_party/rust/icu_casemap_data/v2/README.chromium b/third_party/rust/icu_casemap_data/v2/README.chromium
new file mode 100644
index 0000000..9cfec9e
--- /dev/null
+++ b/third_party/rust/icu_casemap_data/v2/README.chromium
@@ -0,0 +1,10 @@
+Name: icu_casemap_data
+URL: https://crates.io/crates/icu_casemap_data
+Version: 2.0.0-beta2
+Revision: 610757581c7d141a6f20f97fe839ef171c320bb1
+License: Unicode-3.0
+License File: //third_party/rust/chromium_crates_io/vendor/icu_casemap_data-2.0.0-beta2/LICENSE
+Shipped: yes
+Security Critical: yes
+
+Description: Data for the icu_casemap crate
diff --git a/third_party/skia b/third_party/skia
index abc8d14..6276cc1b 160000
--- a/third_party/skia
+++ b/third_party/skia
@@ -1 +1 @@
-Subproject commit abc8d1471f7b24158121769fac3e6121e47b32bc
+Subproject commit 6276cc1b23fac891b89cd18ea452cad93a69fc8e
diff --git a/third_party/spirv-tools/src b/third_party/spirv-tools/src
index a48b473..393d5c7 160000
--- a/third_party/spirv-tools/src
+++ b/third_party/spirv-tools/src
@@ -1 +1 @@
-Subproject commit a48b473403b0990c62ff3175f1e63cbd8c906184
+Subproject commit 393d5c7df150532045c50affffea2df22e8231b0
diff --git a/third_party/vulkan-deps b/third_party/vulkan-deps
index 7f9757f..b9840c7 160000
--- a/third_party/vulkan-deps
+++ b/third_party/vulkan-deps
@@ -1 +1 @@
-Subproject commit 7f9757f8082d3a6d870e769934ed5962f6b5eea5
+Subproject commit b9840c73fad2fd81fe88dd40806b3ef0ddbf5c13
diff --git a/third_party/vulkan-tools/src b/third_party/vulkan-tools/src
index 32ee3e6..da7c7db 160000
--- a/third_party/vulkan-tools/src
+++ b/third_party/vulkan-tools/src
@@ -1 +1 @@
-Subproject commit 32ee3e6e333a4bc4064fe64cfdfdcf6e71a92743
+Subproject commit da7c7db28d36f66dff88f00412dceb480ccc77ea
diff --git a/third_party/vulkan-validation-layers/src b/third_party/vulkan-validation-layers/src
index 52b492f..2e6787d 160000
--- a/third_party/vulkan-validation-layers/src
+++ b/third_party/vulkan-validation-layers/src
@@ -1 +1 @@
-Subproject commit 52b492f11e74dcd44ad3e906125feae79d440c99
+Subproject commit 2e6787d498d65bc20c195d667b8cd3c63e1a8aa9
diff --git a/third_party/webgpu-cts/src b/third_party/webgpu-cts/src
index 645c35e..2c09e17 160000
--- a/third_party/webgpu-cts/src
+++ b/third_party/webgpu-cts/src
@@ -1 +1 @@
-Subproject commit 645c35eb32a795d7e3deccf9d1580a1136b939d8
+Subproject commit 2c09e17c32931273808700d7544ec28470e5d5fe
diff --git a/third_party/webrtc b/third_party/webrtc
index 25636d1..13e4923 160000
--- a/third_party/webrtc
+++ b/third_party/webrtc
@@ -1 +1 @@
-Subproject commit 25636d19d22291470dd7b10cec7d6f12082b678e
+Subproject commit 13e4923c12596c16018fc7bc5143e3f6e01a00c8
diff --git a/tools/bisect-builds.py b/tools/bisect-builds.py
index 98104cf..fb4eaa9 100755
--- a/tools/bisect-builds.py
+++ b/tools/bisect-builds.py
@@ -10,6 +10,9 @@
 revision. It will then binary search across this revision range by downloading,
 unzipping, and opening Chromium for you. After testing the specific revision,
 it will ask you whether it is good or bad before continuing the search.
+
+Docs: https://www.chromium.org/developers/bisect-builds-py/
+Googlers: go/chrome-bisect
 """
 
 import abc
diff --git a/tools/crates/create_update_cl.md b/tools/crates/create_update_cl.md
index 74a8e62..6a89dbfee1 100644
--- a/tools/crates/create_update_cl.md
+++ b/tools/crates/create_update_cl.md
@@ -246,7 +246,7 @@
         ```
         $ git checkout rust-crates-update--last-successful-update
         $ git checkout -b fix-patches-for-foo
-        $ git git branch --set-upstream-to=rust-crates-update--last-successful-update
+        $ git branch --set-upstream-to=rust-crates-update--last-successful-update
         ```
     - Fix the patches and upload as a temporary / throw-away CL
       (this CL can't be landed on its own - it needs to be combined
@@ -260,8 +260,9 @@
       as-is / on its own - it needs to be combined with the fixed patches
       in the step below) with `--upstream-branch` parameter:
         ```
-        $ tools/crates/create_update_cl.py auto -- name-of-failed-crate \
-            --upstream-branch=fix-patches-for-foo
+        $ tools/crates/create_update_cl.py auto \
+            --upstream-branch=fix-patches-for-foo \
+            -- name-of-failed-crate
         ```
     - Combine the branches:
         ```
diff --git a/tools/crates/gnrt/lib/inherit.rs b/tools/crates/gnrt/lib/inherit.rs
index b5e181e0..aaf5a23 100644
--- a/tools/crates/gnrt/lib/inherit.rs
+++ b/tools/crates/gnrt/lib/inherit.rs
@@ -31,6 +31,66 @@
     config.per_crate_config.get(&packages[id].name)?.group
 }
 
+// TODO(https://crbug.com/395924069): Delete this functiona and use `collect_dependencies` result
+// instead.  This will be possible once `gnrt vendor` is also transitioned to
+// use `guppy` and `collect_dependencies` instead of working directly with
+// `cargo_metadata`.
+pub fn find_inherited_privilege_group(
+    id: &PackageId,
+    root: &PackageId,
+    packages: &HashMap<&PackageId, &Package>,
+    nodes: &HashMap<&PackageId, &Node>,
+    config: &config::BuildConfig,
+) -> Group {
+    // A group is inherited from its ancestors and its dependencies, including
+    // from itself.
+    // - It inherits the highest privilege of any ancestor. If everything only uses
+    //   it in the sandbox, then it only needs to be in the sandbox. Same for tests.
+    // - It inherits the lowest privilege of any dependency. If a dependency that is
+    //   part of it needs a sandbox, then so does it.
+    // - If the group is specified on the crate itself, it replaces all ancestors.
+    let mut ancestor_groups = Vec::<Group>::new();
+    let mut dependency_groups = Vec::<Group>::new();
+
+    for each_id in packages.keys() {
+        let found_group = get_group(each_id, packages, config).or_else(|| {
+            if nodes[root].deps.iter().any(|d| d.pkg == **each_id) {
+                // If the dependency is a top-level dep of Chromium, then it defaults to this
+                // privilege level.
+                // TODO: Default should be sandbox??
+                Some(Group::Safe)
+            } else {
+                None
+            }
+        });
+
+        if let Some(group) = found_group {
+            if id == *each_id || is_ancestor(each_id, id, nodes) {
+                // `each_id` is an ancestor of `id`, or is the same crate.
+                log::debug!("{} ance {} ({:?})", packages[id].name, packages[each_id].name, group);
+                ancestor_groups.push(group);
+            } else if is_ancestor(id, each_id, nodes) {
+                // `each_id` is an descendent of `id`, or is the same crate.
+                log::debug!("{} depe {} ({:?})", packages[id].name, packages[each_id].name, group);
+                dependency_groups.push(group);
+            }
+        };
+    }
+
+    if let Some(self_group) = get_group(id, packages, config) {
+        ancestor_groups.clear();
+        ancestor_groups.push(self_group);
+    }
+
+    // Combine the privileges together. Ancestors work to increase privilege,
+    // and dependencies work to decrease it.
+    let ancestor_privilege = ancestor_groups.into_iter().fold(Group::Test, std::cmp::max);
+    let depedency_privilege = dependency_groups.into_iter().fold(Group::Safe, std::cmp::min);
+    let privilege = std::cmp::min(ancestor_privilege, depedency_privilege);
+    log::debug!("privilege = {:?}", privilege);
+    privilege
+}
+
 /// Finds the value of a config flag for a crate that is inherited from
 /// ancestors. The inherited value will be true if its true for the crate
 /// itself or for any ancestor.
diff --git a/tools/crates/gnrt/lib/readme.rs b/tools/crates/gnrt/lib/readme.rs
index bcb24a3..db2cbf3f 100644
--- a/tools/crates/gnrt/lib/readme.rs
+++ b/tools/crates/gnrt/lib/readme.rs
@@ -36,7 +36,7 @@
     deps: impl IntoIterator<Item = &'a cargo_metadata::Package>,
     paths: &paths::ChromiumPaths,
     extra_config: &BuildConfig,
-    mut find_group: impl FnMut(&'a cargo_metadata::Package) -> Group,
+    mut find_group: impl FnMut(&'a cargo_metadata::PackageId) -> Group,
     mut find_security_critical: impl FnMut(&'a cargo_metadata::PackageId) -> Option<bool>,
     mut find_shipped: impl FnMut(&'a cargo_metadata::PackageId) -> Option<bool>,
 ) -> Result<HashMap<PathBuf, ReadmeFile>> {
@@ -61,7 +61,7 @@
     package: &'a cargo_metadata::Package,
     paths: &paths::ChromiumPaths,
     extra_config: &BuildConfig,
-    mut find_group: impl FnMut(&'a cargo_metadata::Package) -> Group,
+    find_group: &mut dyn FnMut(&'a cargo_metadata::PackageId) -> Group,
     find_security_critical: &mut dyn FnMut(&'a cargo_metadata::PackageId) -> Option<bool>,
     find_shipped: &mut dyn FnMut(&'a cargo_metadata::PackageId) -> Option<bool>,
 ) -> Result<(PathBuf, ReadmeFile)> {
@@ -76,7 +76,7 @@
         .third_party_cargo_root
         .join("vendor")
         .join(format!("{}-{}", package.name, package.version));
-    let group = find_group(package);
+    let group = find_group(&package.id);
 
     let security_critical = find_security_critical(&package.id).unwrap_or(match group {
         Group::Safe | Group::Sandbox => true,
diff --git a/tools/crates/gnrt/lib/vet.rs b/tools/crates/gnrt/lib/vet.rs
index e246c975..43f9cca 100644
--- a/tools/crates/gnrt/lib/vet.rs
+++ b/tools/crates/gnrt/lib/vet.rs
@@ -83,12 +83,12 @@
 pub fn create_vet_config<'a>(
     packages: impl IntoIterator<Item = &'a cargo_metadata::Package>,
     is_removed: impl Fn(&'a cargo_metadata::PackageId) -> bool,
-    mut find_group: impl FnMut(&'a cargo_metadata::Package) -> Group,
+    mut find_group: impl FnMut(&'a cargo_metadata::PackageId) -> Group,
     mut find_shipped: impl FnMut(&'a cargo_metadata::PackageId) -> Option<bool>,
 ) -> Result<VetConfigToml> {
     let mut vet_config_toml = VetConfigToml { policies: Vec::new() };
     for package in packages {
-        let group = find_group(package);
+        let group = find_group(&package.id);
         let shipped = find_shipped(&package.id);
 
         let mut crate_name = package.name.clone();
diff --git a/tools/crates/gnrt/vendor.rs b/tools/crates/gnrt/vendor.rs
index 123af80..ef28a252 100644
--- a/tools/crates/gnrt/vendor.rs
+++ b/tools/crates/gnrt/vendor.rs
@@ -5,7 +5,10 @@
 use crate::config;
 use crate::crates::Epoch;
 use crate::deps;
-use crate::inherit::{find_inherited_security_critical_flag, find_inherited_shipped_flag};
+use crate::inherit::{
+    find_inherited_privilege_group, find_inherited_security_critical_flag,
+    find_inherited_shipped_flag,
+};
 use crate::metadata_util::{metadata_nodes, metadata_packages};
 use crate::paths;
 use crate::readme;
@@ -48,11 +51,11 @@
     let nodes: HashMap<_, _> = metadata_nodes(&metadata);
     let root = metadata.resolve.as_ref().unwrap().root.as_ref().unwrap();
 
-    let guppy_resolved_packages = get_guppy_resolved_packages(&config, paths)?;
+    let guppy_resolved_package_ids = get_guppy_resolved_package_ids(&config, paths)?;
     let is_removed = |cargo_package_id: &cargo_metadata::PackageId| -> bool {
         let p = packages[cargo_package_id];
         config.resolve.remove_crates.contains(&p.name)
-            || !guppy_resolved_packages.contains_key(&p.into())
+            || !guppy_resolved_package_ids.contains(&p.into())
     };
 
     // Running cargo commands against actual crates.io will put checksum into
@@ -115,7 +118,7 @@
             .with_context(|| format!("removing {}", d))?
     }
 
-    let find_group = |p: &cargo_metadata::Package| guppy_resolved_packages[&p.into()].group;
+    let find_group = |id| find_inherited_privilege_group(id, root, &packages, &nodes, &config);
     let find_security_critical =
         |id| find_inherited_security_critical_flag(id, root, &packages, &nodes, &config);
     let find_shipped = |id| find_inherited_shipped_flag(id, root, &packages, &nodes, &config);
@@ -215,10 +218,10 @@
     Ok(())
 }
 
-fn get_guppy_resolved_packages(
+fn get_guppy_resolved_package_ids(
     config: &config::BuildConfig,
     paths: &paths::ChromiumPaths,
-) -> Result<HashMap<deps::PackageId, deps::Package>> {
+) -> Result<HashSet<deps::PackageId>> {
     // `gnrt vendor` (unlike `gnrt gen`) doesn't need to pass `--offline` nor
     // `--locked` to `cargo`.
     let cargo_extra_options = vec![];
@@ -231,7 +234,7 @@
         &config.resolve.root,
         config,
     )?;
-    Ok(dependencies.into_iter().map(|p| ((&p).into(), p)).collect())
+    Ok(dependencies.iter().map(|p| p.into()).collect())
 }
 
 fn download_crate(
diff --git a/tools/metrics/PRESUBMIT.py b/tools/metrics/PRESUBMIT.py
index dd2c719d..9311872 100644
--- a/tools/metrics/PRESUBMIT.py
+++ b/tools/metrics/PRESUBMIT.py
@@ -14,13 +14,13 @@
 
 def CheckChange(input_api, output_api):
   """Checks that ukm/ukm.xml is validated on changes to histograms/enums.xml"""
-  for f in input_api.AffectedTextFiles():
+  for f in input_api.AffectedFiles():
     p = f.AbsoluteLocalPath()
     # Early return if the ukm file is changed, then the presubmit script in the
     # ukm directory would run and report the errors.
     if (UKM_XML in p):
       return []
-  for f in input_api.AffectedTextFiles():
+  for f in input_api.AffectedFiles():
     p = f.AbsoluteLocalPath()
     filepath = input_api.os_path.relpath(p, input_api.PresubmitLocalPath())
     if (ENUMS_XML in filepath):
diff --git a/tools/metrics/actions/PRESUBMIT.py b/tools/metrics/actions/PRESUBMIT.py
index daeb1b4..9be4688 100644
--- a/tools/metrics/actions/PRESUBMIT.py
+++ b/tools/metrics/actions/PRESUBMIT.py
@@ -11,7 +11,7 @@
 
 def CheckChange(input_api, output_api):
   """Checks that actions.xml is up to date and pretty-printed."""
-  for f in input_api.AffectedTextFiles():
+  for f in input_api.AffectedFiles():
     p = f.AbsoluteLocalPath()
     if (input_api.basename(p) == 'actions.xml'
         and input_api.os_path.dirname(p) == input_api.PresubmitLocalPath()):
diff --git a/tools/metrics/common/presubmit_util.py b/tools/metrics/common/presubmit_util.py
index 587dc6f..5e5595a 100644
--- a/tools/metrics/common/presubmit_util.py
+++ b/tools/metrics/common/presubmit_util.py
@@ -114,7 +114,7 @@
 
 def CheckChange(xml_file, input_api, output_api):
   """Checks that xml is pretty-printed and well-formatted."""
-  for f in input_api.AffectedTextFiles():
+  for f in input_api.AffectedFiles():
     p = f.AbsoluteLocalPath()
     if (input_api.basename(p) == xml_file
         and input_api.os_path.dirname(p) == input_api.PresubmitLocalPath()):
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 66e5296..805e623 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -10788,6 +10788,7 @@
   <int value="-1329586063"
       label="MigrateDefaultChromeAppToWebAppsNonGSuite:disabled"/>
   <int value="-1328090640" label="DesktopPWAsAppHomePage:enabled"/>
+  <int value="-1327982727" label="IsolatedWebAppAllowlist:enabled"/>
   <int value="-1327676774" label="disable-accelerated-mjpeg-decode"/>
   <int value="-1326671705" label="TrustTokens:enabled"/>
   <int value="-1326486858" label="ChromeWebuiRefresh2023:enabled"/>
@@ -18533,6 +18534,7 @@
   <int value="1675683700" label="PartitionVisitedLinkDatabase:enabled"/>
   <int value="1675848452" label="BluetoothQualityReport:disabled"/>
   <int value="1675857410" label="PrivateNetworkAccessNullIpAddress:enabled"/>
+  <int value="1676345689" label="IsolatedWebAppAllowlist:disabled"/>
   <int value="1677167062" label="AutomaticPasswordGeneration:enabled"/>
   <int value="1677258310" label="DragAppsInTabletMode:disabled"/>
   <int value="1677610330" label="OmniboxExpandedStateHeight:enabled"/>
diff --git a/tools/metrics/histograms/metadata/accessibility/enums.xml b/tools/metrics/histograms/metadata/accessibility/enums.xml
index 8fbdf52..ff6c216b4 100644
--- a/tools/metrics/histograms/metadata/accessibility/enums.xml
+++ b/tools/metrics/histograms/metadata/accessibility/enums.xml
@@ -1219,6 +1219,20 @@
 
 <!-- LINT.ThenChange(//ash/public/cpp/accessibility_controller_enums.h:AutoclickEventType) -->
 
+<!-- LINT.IfChange(AXTreeFixingClientScreenAIRequestType) -->
+
+<enum name="AXTreeFixingClientScreenAIRequestType">
+  <summary>
+    The various types of requests that an AXTreeFixing client can make to
+    ScreenAI.
+  </summary>
+  <int value="0" label="Invalid tree (contains kMain)"/>
+  <int value="1" label="Request before service initialized"/>
+  <int value="2" label="Valid"/>
+</enum>
+
+<!-- LINT.ThenChange(//chrome/browser/accessibility/tree_fixing/internal/ax_tree_fixing_screen_ai_service.cc:AXTreeFixingClientScreenAIRequestType) -->
+
 <!-- LINT.IfChange(AXTreeSnapshotErrorReason) -->
 
 <enum name="AXTreeSnapshotErrorReason">
@@ -2151,6 +2165,24 @@
 
 <!-- LINT.ThenChange(//chrome/browser/resources/side_panel/read_anything/metrics_browser_proxy.ts:ReadAnythingSpeechError) -->
 
+<!-- LINT.IfChange(ReadAnythingSpeechStopSource) -->
+
+<enum name="ReadAnythingSpeechStopSource">
+  <int value="0" label="Used pause button via mouse or keyboard"/>
+  <int value="1" label="Used keyboard shortcut k"/>
+  <int value="2" label="Closed Reading mode"/>
+  <int value="3" label="Closed tab or window"/>
+  <int value="4" label="Reloaded the page"/>
+  <int value="5" label="Changed page content"/>
+  <int value="6" label="Speech engine interrupted"/>
+  <int value="7" label="Speech engine error"/>
+  <int value="8" label="Finished reading content"/>
+  <int value="9" label="ChromeOS device locked"/>
+  <int value="10" label="Unexpectedly updated content while speaking"/>
+</enum>
+
+<!-- LINT.ThenChange(//chrome/renderer/accessibility/read_anything/read_aloud_app_model.h:ReadAloudStopSource) -->
+
 <!-- LINT.IfChange(ReaderModeEntryPoint) -->
 
 <enum name="ReaderModeEntryPoint">
diff --git a/tools/metrics/histograms/metadata/accessibility/histograms.xml b/tools/metrics/histograms/metadata/accessibility/histograms.xml
index d2872613..15d52549 100644
--- a/tools/metrics/histograms/metadata/accessibility/histograms.xml
+++ b/tools/metrics/histograms/metadata/accessibility/histograms.xml
@@ -659,6 +659,136 @@
   </summary>
 </histogram>
 
+<histogram
+    name="Accessibility.AXTreeFixing.ScreenAI.Disconnect.{DisconnectCount}"
+    enum="BooleanOccurred" expires_after="2025-12-01">
+  <owner>mschillaci@google.com</owner>
+  <owner>zork@google.com</owner>
+  <owner>nektar@chromium.org</owner>
+  <owner>chrome-a11y-core@google.com</owner>
+  <summary>
+    This histogram measures the amount of times that the ScreenAI service
+    disconnects {DisconnectCount} from the AXTreeFixing service after it had
+    previously been initialized and connected.
+  </summary>
+  <token key="DisconnectCount">
+    <variant name="First" summary="the first time"/>
+    <variant name="Multiple" summary="more than once in a single session"/>
+  </token>
+</histogram>
+
+<histogram name="Accessibility.AXTreeFixing.ScreenAI.FoundMainNode"
+    enum="BooleanFound" expires_after="2025-12-01">
+  <owner>mschillaci@google.com</owner>
+  <owner>zork@google.com</owner>
+  <owner>nektar@chromium.org</owner>
+  <owner>chrome-a11y-core@google.com</owner>
+  <summary>
+    This histogram measures whether or not a main node was identified after the
+    AXTreeFixing service made a request to the ScreenAI service and successfully
+    received a response from the service.
+  </summary>
+</histogram>
+
+<histogram name="Accessibility.AXTreeFixing.ScreenAI.InitializationAttempt"
+    enum="BooleanAttempted" expires_after="2025-12-01">
+  <owner>mschillaci@google.com</owner>
+  <owner>zork@google.com</owner>
+  <owner>nektar@chromium.org</owner>
+  <owner>chrome-a11y-core@google.com</owner>
+  <summary>
+    This histogram measures the amount of times that the AXTreeFixing service
+    attempts to initialize the ScreenAI service.
+  </summary>
+</histogram>
+
+<histogram name="Accessibility.AXTreeFixing.ScreenAI.InitializedFailed"
+    enum="BooleanOccurred" expires_after="2025-12-01">
+  <owner>mschillaci@google.com</owner>
+  <owner>zork@google.com</owner>
+  <owner>nektar@chromium.org</owner>
+  <owner>chrome-a11y-core@google.com</owner>
+  <summary>
+    This histogram measures the amount of times that the AXTreeFixing service
+    failed to initialize the ScreenAI service after 3 failed attempts.
+  </summary>
+</histogram>
+
+<histogram name="Accessibility.AXTreeFixing.ScreenAI.InitializedOnAttempt"
+    units="count" expires_after="2025-12-01">
+  <owner>mschillaci@google.com</owner>
+  <owner>zork@google.com</owner>
+  <owner>nektar@chromium.org</owner>
+  <owner>chrome-a11y-core@google.com</owner>
+  <summary>
+    This histogram measures the amount of times that it took for the ScreenAI
+    service to successfully initialize for the AXTreeFixing service. A value of
+    1 means that it initialized on the first try (happy-path). We attempt to
+    initialize 3 times before stopping.
+  </summary>
+</histogram>
+
+<histogram
+    name="Accessibility.AXTreeFixing.ScreenAI.MainNodeIdentification.ClientRequestType"
+    enum="AXTreeFixingClientScreenAIRequestType" expires_after="2025-12-01">
+  <owner>mschillaci@google.com</owner>
+  <owner>zork@google.com</owner>
+  <owner>nektar@chromium.org</owner>
+  <owner>chrome-a11y-core@google.com</owner>
+  <summary>
+    This histogram records the type of request that clients made to the
+    AXTreeFixing service for ScreenAI specifically for main node identification.
+    Requests can come before initialization, or with an invalid tree that
+    already contains a main node, etc.
+  </summary>
+</histogram>
+
+<histogram
+    name="Accessibility.AXTreeFixing.ScreenAI.MainNodeIdentification.RoundTripTime"
+    units="ms" expires_after="2025-12-01">
+  <owner>mschillaci@google.com</owner>
+  <owner>zork@google.com</owner>
+  <owner>nektar@chromium.org</owner>
+  <owner>chrome-a11y-core@google.com</owner>
+  <summary>
+    This histogram measures the total time (in ms) that a request for main node
+    identification from the AXTreeFixing service to the ScreenAI service took to
+    complete.
+  </summary>
+</histogram>
+
+<histogram
+    name="Accessibility.AXTreeFixing.ScreenAI.MainNodeIdentification.{RequestResponseType}"
+    enum="BooleanOccurred" expires_after="2025-12-01">
+  <owner>mschillaci@google.com</owner>
+  <owner>zork@google.com</owner>
+  <owner>nektar@chromium.org</owner>
+  <owner>chrome-a11y-core@google.com</owner>
+  <summary>
+    This histogram records the number of times that the AXTreeFixing service
+    {RequestResponseType} to identify the main node of a tree.
+  </summary>
+  <token key="RequestResponseType">
+    <variant name="Request" summary="made a request to the ScreenAI service"/>
+    <variant name="Response"
+        summary="gets a response from the ScreenAI service after making a
+                 request"/>
+  </token>
+</histogram>
+
+<histogram name="Accessibility.AXTreeFixing.ScreenAI.MainNodeInitialRole"
+    enum="AccessibilityRole" expires_after="2025-12-01">
+  <owner>mschillaci@google.com</owner>
+  <owner>zork@google.com</owner>
+  <owner>nektar@chromium.org</owner>
+  <owner>chrome-a11y-core@google.com</owner>
+  <summary>
+    This histogram measures the initial Role of an identified main node after
+    the AXTreeFixing service made a request to the ScreenAI service for main
+    node identification.
+  </summary>
+</histogram>
+
 <histogram name="Accessibility.AXTreeSnapshotter.Snapshot.Error"
     enum="AXTreeSnapshotErrorReason" expires_after="2025-08-10">
   <owner>mschillaci@google.com</owner>
@@ -778,7 +908,7 @@
 </histogram>
 
 <histogram name="Accessibility.CrosColorCorrection.FilterAmount" units="%"
-    expires_after="2025-06-29">
+    expires_after="2026-03-30">
   <owner>katie@chromium.org</owner>
   <owner>chrome-a11y-core@google.com</owner>
   <summary>
@@ -790,7 +920,7 @@
 </histogram>
 
 <histogram name="Accessibility.CrosColorCorrection.FilterType"
-    enum="ColorCorrectionFilterTypes" expires_after="2025-06-29">
+    enum="ColorCorrectionFilterTypes" expires_after="2026-03-30">
   <owner>katie@chromium.org</owner>
   <owner>chrome-a11y-core@google.com</owner>
   <summary>
@@ -930,7 +1060,7 @@
 </histogram>
 
 <histogram name="Accessibility.CrosSelectToSpeak.BackgroundShading"
-    enum="BooleanEnabled" expires_after="2025-06-22">
+    enum="BooleanEnabled" expires_after="2026-03-30">
   <owner>katie@chromium.org</owner>
   <owner>chrome-a11y-core@google.com</owner>
   <summary>
@@ -939,7 +1069,7 @@
 </histogram>
 
 <histogram name="Accessibility.CrosSelectToSpeak.BubbleButtonPress"
-    enum="CrosSelectToSpeakAction" expires_after="2025-05-04">
+    enum="CrosSelectToSpeakAction" expires_after="2026-03-30">
   <owner>anastasi@google.com</owner>
   <owner>katie@chromium.org</owner>
   <owner>chrome-a11y-core@google.com</owner>
@@ -949,7 +1079,7 @@
 </histogram>
 
 <histogram name="Accessibility.CrosSelectToSpeak.BubbleDismissMethod"
-    enum="CrosSelectToSpeakActivationMethod" expires_after="2025-05-04">
+    enum="CrosSelectToSpeakActivationMethod" expires_after="2026-03-30">
   <owner>ajitnarayanan@google.com</owner>
   <owner>chrome-a11y-core@google.com</owner>
   <summary>
@@ -971,7 +1101,7 @@
 </histogram>
 
 <histogram name="Accessibility.CrosSelectToSpeak.NavigationControls"
-    enum="BooleanEnabled" expires_after="2025-05-04">
+    enum="BooleanEnabled" expires_after="2026-03-30">
   <owner>ajitnarayanan@google.com</owner>
   <owner>chrome-a11y-core@google.com</owner>
   <summary>
@@ -997,7 +1127,7 @@
 </histogram>
 
 <histogram name="Accessibility.CrosSelectToSpeak.ParagraphNavigationMethod"
-    enum="CrosSelectToSpeakActivationMethod" expires_after="2025-05-04">
+    enum="CrosSelectToSpeakActivationMethod" expires_after="2026-03-30">
   <owner>ajitnarayanan@google.com</owner>
   <owner>chrome-a11y-core@google.com</owner>
   <summary>
@@ -1009,7 +1139,7 @@
 </histogram>
 
 <histogram name="Accessibility.CrosSelectToSpeak.SentenceNavigationMethod"
-    enum="CrosSelectToSpeakActivationMethod" expires_after="2025-06-22">
+    enum="CrosSelectToSpeakActivationMethod" expires_after="2026-03-30">
   <owner>ajitnarayanan@google.com</owner>
   <owner>chrome-a11y-core@google.com</owner>
   <summary>
@@ -1033,7 +1163,7 @@
 
 <histogram name="Accessibility.CrosSelectToSpeak.SpeedSetFromBubble"
     enum="CrosSelectToSpeakOverrideSpeechRateMultiplier"
-    expires_after="2025-05-04">
+    expires_after="2026-03-30">
   <owner>anastasi@google.com</owner>
   <owner>katie@chromium.org</owner>
   <owner>chrome-a11y-core@google.com</owner>
@@ -1043,7 +1173,7 @@
 </histogram>
 
 <histogram name="Accessibility.CrosSelectToSpeak.StartSpeechMethod"
-    enum="CrosSelectToSpeakStartSpeechMethod" expires_after="2025-05-04">
+    enum="CrosSelectToSpeakStartSpeechMethod" expires_after="2026-03-30">
   <owner>katie@chromium.org</owner>
   <owner>chrome-a11y-core@google.com</owner>
   <summary>
@@ -1197,7 +1327,7 @@
 </histogram>
 
 <histogram name="Accessibility.CrosSwitchAccess.AutoScan.KeyboardSpeedMs"
-    units="ms" expires_after="2025-05-04">
+    units="ms" expires_after="2026-03-30">
   <owner>dtseng@chromium.org</owner>
   <owner>anastasi@google.com</owner>
   <owner>chrome-a11y-core@google.com</owner>
@@ -1254,7 +1384,7 @@
 </histogram>
 
 <histogram name="Accessibility.CrosSwitchAccess.SelectKeyCode" enum="KeyCode"
-    expires_after="2025-05-04">
+    expires_after="2026-03-30">
   <owner>dtseng@chromium.org</owner>
   <owner>anastasi@google.com</owner>
   <owner>chrome-a11y-core@google.com</owner>
@@ -1408,7 +1538,7 @@
 </histogram>
 
 <histogram name="Accessibility.DlcInstallerFaceGazeAssetsInstallationDuration"
-    units="ms" expires_after="2025-05-04">
+    units="ms" expires_after="2026-03-30">
   <owner>akihiroota@chromium.org</owner>
   <owner>chrome-a11y-core@google.com</owner>
   <summary>
@@ -1732,7 +1862,7 @@
 </histogram>
 
 <histogram name="Accessibility.LanguageDetection.LangsPerPage" units="count"
-    expires_after="2025-05-04">
+    expires_after="2026-03-30">
   <owner>chrishall@chromium.org</owner>
   <owner>dtseng@chromium.org</owner>
   <owner>chrome-a11y-core@google.com</owner>
@@ -1758,7 +1888,7 @@
 </histogram>
 
 <histogram name="Accessibility.LanguageDetection.PercentageLanguageDetected"
-    units="%" expires_after="2025-05-04">
+    units="%" expires_after="2026-03-30">
   <owner>chrishall@chromium.org</owner>
   <owner>dtseng@chromium.org</owner>
   <owner>chrome-a11y-core@google.com</owner>
@@ -2071,7 +2201,7 @@
 </histogram>
 
 <histogram name="Accessibility.OOBEStartupSoundEnabled" enum="BooleanEnabled"
-    expires_after="2025-07-28">
+    expires_after="2026-03-30">
   <owner>katie@chromium.org</owner>
   <owner>chrome-a11y-core@google.com</owner>
   <summary>
@@ -2134,7 +2264,7 @@
 </histogram>
 
 <histogram name="Accessibility.PdfOcr.CrosSelectToSpeak.PagesOcred"
-    units="count" expires_after="2025-07-31">
+    units="count" expires_after="2026-03-30">
   <owner>kyungjunlee@google.com</owner>
   <owner>chrome-a11y-core@google.com</owner>
   <summary>
@@ -2957,6 +3087,13 @@
   </summary>
 </histogram>
 
+<histogram name="Accessibility.ReadAnything.SpeechStopSource"
+    enum="ReadAnythingSpeechError" expires_after="2025-12-01">
+  <owner>kristislee@google.com</owner>
+  <owner>komo-eng@google.com</owner>
+  <summary>Records when speech stops and why.</summary>
+</histogram>
+
 <histogram
     name="Accessibility.ReadAnything.TimeFromEntryTriggeredToWebUIConnected"
     units="ms" expires_after="2025-08-24">
@@ -3671,13 +3808,25 @@
   </summary>
 </histogram>
 
+<histogram name="DomDistiller.Time.TimeToProvideResultToAccumulator" units="ms"
+    expires_after="2025-08-24">
+  <owner>wylieb@google.com</owner>
+  <owner>chrome-reader-mode-team@google.com</owner>
+  <summary>
+    Records the amount of time it takes for the reader mode signal to make it to
+    SignalAccumulator. Recorded when the distillation result is passed to CPA.
+    Only recorded on Android.
+  </summary>
+</histogram>
+
 <histogram name="DomDistiller.Time.ViewingReaderModePage" units="ms"
     expires_after="2025-08-24">
   <owner>mdjones@chromium.org</owner>
   <owner>wylieb@google.com</owner>
   <owner>chrome-reader-mode-team@google.com</owner>
   <summary>
-    Records the amount of time a user spent on a Reader Mode Page.
+    Records the amount of time a user spent on a Reader Mode Page. Recorded when
+    the user exits reader mode. Only recorded on Android.
   </summary>
 </histogram>
 
@@ -3759,7 +3908,7 @@
 </histogram>
 
 <histogram name="TextToSpeech.Event" enum="TextToSpeechEvent"
-    expires_after="2025-07-28">
+    expires_after="2026-03-30">
   <owner>dtseng@chromium.org</owner>
   <owner>katie@chromium.org</owner>
   <owner>chrome-a11y-core@google.com</owner>
@@ -3771,7 +3920,7 @@
 </histogram>
 
 <histogram name="TextToSpeech.ExtensionNetworkSpeechSynthesis.Playback"
-    enum="AudioPlayback" expires_after="2025-07-28">
+    enum="AudioPlayback" expires_after="2026-03-30">
   <owner>dtseng@chromium.org</owner>
   <owner>akihiroota@chromium.org</owner>
   <owner>chrome-a11y-core@google.com</owner>
@@ -3782,7 +3931,7 @@
 </histogram>
 
 <histogram name="TextToSpeech.Settings.GetVoiceBytes"
-    enum="TextToSpeechGetVoiceBytes" expires_after="2025-07-28">
+    enum="TextToSpeechGetVoiceBytes" expires_after="2026-03-30">
   <owner>akihiroota@chromium.org</owner>
   <owner>chrome-a11y-core@google.com</owner>
   <summary>
@@ -3793,7 +3942,7 @@
 </histogram>
 
 <histogram name="TextToSpeech.Utterance.FromExtensionAPI"
-    enum="TextToSpeechFromExtensionAPI" expires_after="2025-07-28">
+    enum="TextToSpeechFromExtensionAPI" expires_after="2026-03-30">
   <owner>dtseng@chromium.org</owner>
   <owner>katie@chromium.org</owner>
   <owner>chrome-a11y-core@google.com</owner>
@@ -3805,7 +3954,7 @@
 </histogram>
 
 <histogram name="TextToSpeech.Utterance.HasVoiceName"
-    enum="TextToSpeechHasVoiceName" expires_after="2025-07-28">
+    enum="TextToSpeechHasVoiceName" expires_after="2026-03-30">
   <owner>katie@chromium.org</owner>
   <owner>chrome-a11y-core@google.com</owner>
   <summary>
@@ -3816,7 +3965,7 @@
 </histogram>
 
 <histogram name="TextToSpeech.Utterance.Native" enum="TextToSpeechNative"
-    expires_after="2025-07-28">
+    expires_after="2026-03-30">
   <owner>katie@chromium.org</owner>
   <owner>chrome-a11y-core@google.com</owner>
   <summary>
@@ -3828,7 +3977,7 @@
 </histogram>
 
 <histogram name="TextToSpeech.Utterance.Rate" units="count"
-    expires_after="2025-07-28">
+    expires_after="2026-03-30">
   <owner>dtseng@chromium.org</owner>
   <owner>chrome-a11y-core@google.com</owner>
   <summary>
@@ -3839,7 +3988,7 @@
 </histogram>
 
 <histogram name="TextToSpeech.Utterance.Source" enum="TextToSpeechSource"
-    expires_after="2025-07-28">
+    expires_after="2026-03-30">
   <owner>joelriley@google.com</owner>
   <owner>katie@chromium.org</owner>
   <owner>chrome-a11y-core@google.com</owner>
@@ -3852,7 +4001,7 @@
 </histogram>
 
 <histogram name="TextToSpeech.Utterance.TextLength" units="bytes"
-    expires_after="2025-07-28">
+    expires_after="2026-03-30">
   <owner>katie@chromium.org</owner>
   <owner>chrome-a11y-core@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/ash/enums.xml b/tools/metrics/histograms/metadata/ash/enums.xml
index c24c8ce1..be132441 100644
--- a/tools/metrics/histograms/metadata/ash/enums.xml
+++ b/tools/metrics/histograms/metadata/ash/enums.xml
@@ -243,6 +243,19 @@
   <int value="17" label="Nothing to Challenge-response"/>
 </enum>
 
+<!-- LINT.IfChange(BabelOrcaStreamEndReason) -->
+
+<enum name="BabelOrcaStreamEndReason">
+  <int value="0" label="Ended by client"/>
+  <int value="1" label="Connection closed with success"/>
+  <int value="2" label="Connection closed with error"/>
+  <int value="3" label="Stream parsing error"/>
+  <int value="4" label="Parsing service disconnected"/>
+  <int value="5" label="Timeout waiting for data"/>
+</enum>
+
+<!-- LINT.ThenChange(//chromeos/ash/components/boca/babelorca/tachyon_streaming_client.h:StreamEndReason) -->
+
 <enum name="BackGestureEndScenarioType">
   <int value="0" label="Swiped back on non-snapped window and aborted"/>
   <int value="1" label="Swiped back on non-snapped window and went back"/>
diff --git a/tools/metrics/histograms/metadata/ash/histograms.xml b/tools/metrics/histograms/metadata/ash/histograms.xml
index 4432a2b..b9d9117 100644
--- a/tools/metrics/histograms/metadata/ash/histograms.xml
+++ b/tools/metrics/histograms/metadata/ash/histograms.xml
@@ -1229,6 +1229,13 @@
   </summary>
 </histogram>
 
+<histogram name="Ash.Boca.Babelorca.StreamEndReason"
+    enum="BabelOrcaStreamEndReason" expires_after="2026-03-21">
+  <owner>anasr@google.com</owner>
+  <owner>cros-edu-eng@google.com</owner>
+  <summary>Records why the captions receiving stream was ended.</summary>
+</histogram>
+
 <histogram name="Ash.Boca.Babelorca.{Request}.HttpResponseCodeOrNetError"
     enum="CombinedHttpResponseAndNetErrorCode" expires_after="2026-03-21">
   <owner>anasr@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/blink/enums.xml b/tools/metrics/histograms/metadata/blink/enums.xml
index b53d58f..28b8f58c 100644
--- a/tools/metrics/histograms/metadata/blink/enums.xml
+++ b/tools/metrics/histograms/metadata/blink/enums.xml
@@ -6082,6 +6082,7 @@
   <int value="5456" label="Summarizer_OutputLanguage"/>
   <int value="5457" label="Summarizer_SharedContext"/>
   <int value="5458" label="Summarizer_Type"/>
+  <int value="5459" label="CrossOriginSameSiteCookieAccessViaStorageAccessAPI"/>
 </enum>
 
 <!-- LINT.ThenChange(//third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom:WebFeature) -->
diff --git a/tools/metrics/histograms/metadata/collaboration_service/enums.xml b/tools/metrics/histograms/metadata/collaboration_service/enums.xml
index 9527244..149a85a 100644
--- a/tools/metrics/histograms/metadata/collaboration_service/enums.xml
+++ b/tools/metrics/histograms/metadata/collaboration_service/enums.xml
@@ -70,6 +70,7 @@
   <int value="30" label="Device policy disables signin"/>
   <int value="31" label="Managed account signed in"/>
   <int value="32" label="Account info is not ready upon signin"/>
+  <int value="33" label="Read new group result: user is already member"/>
 </enum>
 
 <!-- LINT.ThenChange(//components/collaboration/internal/metrics.h:CollaborationServiceJoinEvent) -->
diff --git a/tools/metrics/histograms/metadata/compositing/histograms.xml b/tools/metrics/histograms/metadata/compositing/histograms.xml
index cf81c5b..f421012 100644
--- a/tools/metrics/histograms/metadata/compositing/histograms.xml
+++ b/tools/metrics/histograms/metadata/compositing/histograms.xml
@@ -82,6 +82,11 @@
   <variant name="Renderer"/>
 </variants>
 
+<variants name="VizCodePathName">
+  <variant name="Aggregate"/>
+  <variant name="DrawAndSwap"/>
+</variants>
+
 <histogram name="Compositing.Browser.LayersUpdateTime" units="microseconds"
     expires_after="2025-09-14">
   <owner>pdr@chromium.org</owner>
@@ -606,6 +611,20 @@
   </summary>
 </histogram>
 
+<histogram name="Compositing.Display.VizCodePath.{VizCodePathName}"
+    enum="Boolean" expires_after="2025-08-01">
+  <owner>harthuang@google.com</owner>
+  <owner>fangzhoug@chromium.org</owner>
+  <owner>petermcneeley@chromium.org</owner>
+  <owner>chrome-gpu-metric-alerts@chromium.org</owner>
+  <summary>
+    Track usage of different code paths in Viz display compositor. We want to
+    monitor code paths and see if some code paths will not be executed each
+    frame.
+  </summary>
+  <token key="VizCodePathName" variants="VizCodePathName"/>
+</histogram>
+
 <histogram name="Compositing.Display.VizDependencyResolvedToGpuStartedDrawUs"
     units="microseconds" expires_after="2024-10-20">
   <owner>vasilyt@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/gpu/histograms.xml b/tools/metrics/histograms/metadata/gpu/histograms.xml
index 7966a70..db6eab0 100644
--- a/tools/metrics/histograms/metadata/gpu/histograms.xml
+++ b/tools/metrics/histograms/metadata/gpu/histograms.xml
@@ -1793,7 +1793,7 @@
 </histogram>
 
 <histogram name="Viz.ExternalBeginFrameSource.Interval" units="ms"
-    expires_after="2025-03-18">
+    expires_after="2025-09-18">
   <owner>lizeb@chromium.org</owner>
   <owner>chromeos-gfx@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/media/histograms.xml b/tools/metrics/histograms/metadata/media/histograms.xml
index 0b3aa60..ff5d27c 100644
--- a/tools/metrics/histograms/metadata/media/histograms.xml
+++ b/tools/metrics/histograms/metadata/media/histograms.xml
@@ -7152,7 +7152,7 @@
 </histogram>
 
 <histogram name="MediaPreviews.UI.DeviceSelection.PageInfo.Camera.NumDevices"
-    units="devices" expires_after="2025-06-08">
+    units="devices" expires_after="2025-11-30">
   <owner>bryantchandler@chromium.org</owner>
   <owner>openscreen-eng@google.com</owner>
   <summary>
@@ -7164,7 +7164,7 @@
 </histogram>
 
 <histogram name="MediaPreviews.UI.DeviceSelection.PageInfo.Mic.NumDevices"
-    units="devices" expires_after="2025-04-01">
+    units="devices" expires_after="2025-11-30">
   <owner>bryantchandler@chromium.org</owner>
   <owner>openscreen-eng@google.com</owner>
   <summary>
@@ -7177,7 +7177,7 @@
 
 <histogram
     name="MediaPreviews.UI.DeviceSelection.Permissions.Camera.NumDevices"
-    units="devices" expires_after="2025-08-10">
+    units="devices" expires_after="2025-11-30">
   <owner>bryantchandler@chromium.org</owner>
   <owner>openscreen-eng@google.com</owner>
   <summary>
@@ -7189,7 +7189,7 @@
 </histogram>
 
 <histogram name="MediaPreviews.UI.DeviceSelection.Permissions.Mic.NumDevices"
-    units="devices" expires_after="2025-08-10">
+    units="devices" expires_after="2025-11-30">
   <owner>bryantchandler@chromium.org</owner>
   <owner>openscreen-eng@google.com</owner>
   <summary>
@@ -7201,7 +7201,7 @@
 </histogram>
 
 <histogram name="MediaPreviews.UI.DeviceSelection.{UiLocation}.{Type}.Action"
-    enum="MediaPreviewDeviceSelectionUserAction" expires_after="2025-08-10">
+    enum="MediaPreviewDeviceSelectionUserAction" expires_after="2025-11-30">
   <owner>ahmedmoussa@google.com</owner>
   <owner>openscreen-eng@google.com</owner>
   <summary>
@@ -7219,7 +7219,7 @@
 </histogram>
 
 <histogram name="MediaPreviews.UI.PageInfo.Camera.NumInUseDevices"
-    units="devices" expires_after="2025-06-08">
+    units="devices" expires_after="2025-11-30">
   <owner>bryantchandler@chromium.org</owner>
   <owner>openscreen-eng@google.com</owner>
   <summary>
@@ -7228,7 +7228,7 @@
 </histogram>
 
 <histogram name="MediaPreviews.UI.PageInfo.Camera.PixelHeight" units="pixels"
-    expires_after="2025-08-10">
+    expires_after="2025-11-30">
   <owner>bryantchandler@chromium.org</owner>
   <owner>openscreen-eng@google.com</owner>
   <summary>
@@ -7238,7 +7238,7 @@
 </histogram>
 
 <histogram name="MediaPreviews.UI.PageInfo.Mic.NumInUseDevices" units="devices"
-    expires_after="2025-04-01">
+    expires_after="2025-11-30">
   <owner>bryantchandler@chromium.org</owner>
   <owner>openscreen-eng@google.com</owner>
   <summary>
@@ -7248,7 +7248,7 @@
 </histogram>
 
 <histogram name="MediaPreviews.UI.Permissions.Camera.PixelHeight"
-    units="pixels" expires_after="2025-08-10">
+    units="pixels" expires_after="2025-11-30">
   <owner>bryantchandler@chromium.org</owner>
   <owner>openscreen-eng@google.com</owner>
   <summary>
@@ -7258,7 +7258,7 @@
 </histogram>
 
 <histogram name="MediaPreviews.UI.Preview.{UiLocation}.Video.ActualFPS"
-    units="fps" expires_after="2025-08-10">
+    units="fps" expires_after="2025-11-30">
   <owner>bryantchandler@chromium.org</owner>
   <owner>openscreen-eng@google.com</owner>
   <summary>
@@ -7272,7 +7272,7 @@
 </histogram>
 
 <histogram name="MediaPreviews.UI.Preview.{UiLocation}.Video.Delay" units="ms"
-    expires_after="2025-08-31">
+    expires_after="2025-11-30">
   <owner>ahmedmoussa@google.com</owner>
   <owner>openscreen-eng@google.com</owner>
   <summary>
@@ -7286,7 +7286,7 @@
 </histogram>
 
 <histogram name="MediaPreviews.UI.Preview.{UiLocation}.Video.ExpectedFPS"
-    units="fps" expires_after="2025-08-10">
+    units="fps" expires_after="2025-11-30">
   <owner>bryantchandler@chromium.org</owner>
   <owner>openscreen-eng@google.com</owner>
   <summary>
@@ -7301,7 +7301,7 @@
 </histogram>
 
 <histogram name="MediaPreviews.UI.Preview.{UiLocation}.Video.RenderedPercent"
-    units="%" expires_after="2025-08-10">
+    units="%" expires_after="2025-11-30">
   <owner>bryantchandler@chromium.org</owner>
   <owner>openscreen-eng@google.com</owner>
   <summary>
@@ -7317,7 +7317,7 @@
 
 <histogram
     name="MediaPreviews.UI.Preview.{UiLocation}.Video.TimeToActionWithoutPreview"
-    units="ms" expires_after="2025-08-31">
+    units="ms" expires_after="2025-11-30">
   <owner>ahmedmoussa@google.com</owner>
   <owner>openscreen-eng@google.com</owner>
   <summary>
@@ -7332,7 +7332,7 @@
 
 <histogram
     name="MediaPreviews.UI.Preview.{UiLocation}.Video.TotalVisibleDuration"
-    units="ms" expires_after="2025-08-31">
+    units="ms" expires_after="2025-11-30">
   <owner>ahmedmoussa@google.com</owner>
   <owner>openscreen-eng@google.com</owner>
   <summary>
@@ -7349,7 +7349,7 @@
 </histogram>
 
 <histogram name="MediaPreviews.UI.Preview.{UiLocation}.VideoCaptureError"
-    units="VideoCaptureError" expires_after="2025-11-11">
+    units="VideoCaptureError" expires_after="2025-11-30">
   <owner>ahmedmoussa@google.com</owner>
   <owner>openscreen-eng@google.com</owner>
   <summary>
@@ -7362,7 +7362,7 @@
 </histogram>
 
 <histogram name="MediaPreviews.UI.{UiLocation}.OriginTrialAllowed"
-    enum="Boolean" expires_after="2025-09-14">
+    enum="Boolean" expires_after="2025-11-30">
   <owner>mfoltz@chromium.org</owner>
   <owner>openscreen-eng@google.com</owner>
   <summary>
@@ -7377,7 +7377,7 @@
 </histogram>
 
 <histogram name="MediaPreviews.UI.{UiLocation}.{Type}.Duration" units="s"
-    expires_after="2025-08-10">
+    expires_after="2025-11-30">
   <owner>ahmedmoussa@google.com</owner>
   <owner>openscreen-eng@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/pdf/enums.xml b/tools/metrics/histograms/metadata/pdf/enums.xml
index 69bcaec..673aa59 100644
--- a/tools/metrics/histograms/metadata/pdf/enums.xml
+++ b/tools/metrics/histograms/metadata/pdf/enums.xml
@@ -128,6 +128,10 @@
   <int value="98" label="OpenInk2BottomToolbar"/>
   <int value="99" label="SaveSearchifiedFirst"/>
   <int value="100" label="SaveSearchified"/>
+  <int value="101" label="EnterInk2TextAnnotationModeFirst"/>
+  <int value="102" label="EnterInk2TextAnnotationMode"/>
+  <int value="103" label="ExitInk2TextAnnotationModeFirst"/>
+  <int value="104" label="ExitInk2TextAnnotationMode"/>
 </enum>
 
 <enum name="ChromePDFViewerLoadStatus">
diff --git a/tools/metrics/histograms/metadata/search/enums.xml b/tools/metrics/histograms/metadata/search/enums.xml
index c866a81f..04747e40 100644
--- a/tools/metrics/histograms/metadata/search/enums.xml
+++ b/tools/metrics/histograms/metadata/search/enums.xml
@@ -400,6 +400,7 @@
   <int value="2" label="Invalid choice version"/>
   <int value="3" label="Force reprompt"/>
   <int value="4" label="Command line flag"/>
+  <int value="5" label="Device restore (iOS only)"/>
 </enum>
 
 <enum name="SearchPolicyConflictType">
diff --git a/tools/metrics/histograms/metadata/ui/enums.xml b/tools/metrics/histograms/metadata/ui/enums.xml
index a029b97..c7551e39 100644
--- a/tools/metrics/histograms/metadata/ui/enums.xml
+++ b/tools/metrics/histograms/metadata/ui/enums.xml
@@ -1023,6 +1023,7 @@
   <int value="91" label="Open extension settings (Safety Hub)"/>
   <int value="92" label="Show customize chrome side panel"/>
   <int value="93" label="Show tab search declutter page"/>
+  <int value="94" label="Open glic"/>
 </enum>
 
 <!-- LINT.ThenChange(/chrome/browser/ui/toolbar/app_menu_model.h:AppMenuAction) -->
diff --git a/tools/metrics/structured/PRESUBMIT.py b/tools/metrics/structured/PRESUBMIT.py
index 77fea75..6444992d 100644
--- a/tools/metrics/structured/PRESUBMIT.py
+++ b/tools/metrics/structured/PRESUBMIT.py
@@ -16,7 +16,7 @@
   """ Checks that structured.xml is pretty-printed and well-formatted. """
   errors = []
 
-  for file in input_api.AffectedTextFiles():
+  for file in input_api.AffectedFiles():
     path = file.AbsoluteLocalPath()
     basename = input_api.basename(path)
     if input_api.os_path.dirname(path) != input_api.PresubmitLocalPath():
diff --git a/tools/metrics/ukm/ukm.xml b/tools/metrics/ukm/ukm.xml
index 5000f2a..dad4897 100644
--- a/tools/metrics/ukm/ukm.xml
+++ b/tools/metrics/ukm/ukm.xml
@@ -3775,6 +3775,14 @@
       user's sign-in state, record the state stored in the browser.
     </summary>
   </metric>
+  <metric name="IdentityProvidersCount">
+    <summary>
+      Records the count of identity providers right before fetching endpoints
+      with multi IDP flag enabled. It counts distinct providers including the
+      ones a user is not signed in to. Recorded at most once per API call before
+      the browser starts fetching from the IdPs.
+    </summary>
+  </metric>
   <metric name="LoginHint.NumMatchingAccounts" enum="FedCmNumAccounts">
     <summary>
       Records the number of accounts that match a login hint. Records at the
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index fa62d2f..e347d82 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/v49.0/linux-arm64/trace_processor_shell"
         },
         "win": {
-            "hash": "12a239b5b25deff206a34bc5d25b68328ed85148",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/b3fc262d1f1294a2653e98b909cebc836eb88f97/trace_processor_shell.exe"
+            "hash": "7c6729ccdceb918382b66c4e3473ffba0d0642e6",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/40b529923598b739b2892a536a7692eedbed5685/trace_processor_shell.exe"
         },
         "linux_arm": {
             "hash": "a15d8362d80cfd7cd8d785cf6afc22586de688cd",
@@ -21,8 +21,8 @@
             "full_remote_path": "perfetto-luci-artifacts/v49.0/mac-arm64/trace_processor_shell"
         },
         "linux": {
-            "hash": "4da35d1949c9f3a9c4bc860bfd3cf0eb59af734a",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/b3fc262d1f1294a2653e98b909cebc836eb88f97/trace_processor_shell"
+            "hash": "9b8ad3cb5e274d153c74f0a6d913eda777e3b0d1",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/40b529923598b739b2892a536a7692eedbed5685/trace_processor_shell"
         }
     },
     "power_profile.sql": {
diff --git a/tools/traffic_annotation/safe_list.txt b/tools/traffic_annotation/safe_list.txt
index e6d0fc37..dd9854d 100644
--- a/tools/traffic_annotation/safe_list.txt
+++ b/tools/traffic_annotation/safe_list.txt
@@ -137,7 +137,6 @@
 missing_new_fields,chrome/browser/profile_resetter/reset_report_uploader.cc
 missing_new_fields,chrome/browser/profiles/profile_avatar_downloader.cc
 missing_new_fields,chrome/browser/renderer_context_menu/render_view_context_menu.cc
-missing_new_fields,chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service.cc
 missing_new_fields,chrome/browser/safe_browsing/download_protection/check_client_download_request_base.cc
 missing_new_fields,chrome/browser/safe_browsing/download_protection/download_feedback.cc
 missing_new_fields,chrome/browser/safe_browsing/download_protection/ppapi_download_request.cc
@@ -224,6 +223,7 @@
 missing_new_fields,components/safe_browsing/core/browser/db/v4_update_protocol_manager.cc
 missing_new_fields,components/safe_browsing/core/browser/password_protection/password_protection_request.cc
 missing_new_fields,components/safe_browsing/core/browser/ping_manager.cc
+missing_new_fields,components/safe_browsing/core/browser/realtime/chrome_enterprise_url_lookup_service.cc
 missing_new_fields,components/safe_browsing/core/browser/realtime/url_lookup_service.cc
 missing_new_fields,components/safe_browsing/core/browser/tailored_security_service/tailored_security_service.cc
 missing_new_fields,components/safety_check/update_check_helper.cc
diff --git a/tools/traffic_annotation/summary/annotations.xml b/tools/traffic_annotation/summary/annotations.xml
index 5397da7..99966a9d 100644
--- a/tools/traffic_annotation/summary/annotations.xml
+++ b/tools/traffic_annotation/summary/annotations.xml
@@ -68,7 +68,7 @@
  <item id="drag_download_file" added_in_milestone="62" content_hash_code="078a20ba" os_list="linux,windows,chromeos,android" file_path="content/browser/download/drag_download_file.cc" />
  <item id="drive_service" added_in_milestone="90" content_hash_code="059e2023" os_list="linux,windows,chromeos" file_path="chrome/browser/new_tab_page/modules/file_suggestion/drive_service.cc" />
  <item id="early_hints_preload" added_in_milestone="91" content_hash_code="075686e5" os_list="linux,windows,chromeos,android" file_path="content/browser/loader/navigation_early_hints_manager.cc" />
- <item id="enterprise_safe_browsing_realtime_url_lookup" added_in_milestone="86" content_hash_code="00d66dca" os_list="linux,windows,chromeos,android" file_path="chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service.cc" />
+ <item id="enterprise_safe_browsing_realtime_url_lookup" added_in_milestone="86" content_hash_code="00d66dca" os_list="linux,windows,android,chromeos" file_path="components/safe_browsing/core/browser/realtime/chrome_enterprise_url_lookup_service.cc" />
  <item id="extension_blacklist" added_in_milestone="62" content_hash_code="06f7911b" os_list="linux,windows,chromeos" file_path="chrome/browser/extensions/blocklist_state_fetcher.cc" />
  <item id="extension_crx_fetcher" added_in_milestone="62" content_hash_code="05d3e86d" os_list="linux,windows,chromeos" file_path="extensions/browser/updater/extension_downloader.cc" />
  <item id="extension_install_signer" added_in_milestone="62" content_hash_code="065c4bce" os_list="linux,windows,chromeos" file_path="chrome/browser/extensions/install_signer.cc" />
@@ -511,4 +511,5 @@
  <item id="chromeos_lens_web_image_search" added_in_milestone="136" content_hash_code="0448d253" os_list="chromeos" file_path="chrome/browser/ui/ash/capture_mode/chrome_capture_mode_delegate.cc" />
  <item id="chromeos_inline_image_request" added_in_milestone="136" content_hash_code="014aba50" os_list="chromeos" file_path="chrome/browser/ash/lobster/lobster_image_provider_from_snapper.cc" />
  <item id="baguette_image_download" added_in_milestone="136" content_hash_code="0416bbbe" os_list="chromeos" file_path="chrome/browser/ash/crostini/baguette_download.cc" />
+ <item id="chromeos_walrus_provider" added_in_milestone="136" content_hash_code="0671ae94" os_list="chromeos" file_path="components/manta/walrus_provider.cc" />
 </annotations>
diff --git a/tools/traffic_annotation/summary/grouping.xml b/tools/traffic_annotation/summary/grouping.xml
index c24ab634..bf4769a 100644
--- a/tools/traffic_annotation/summary/grouping.xml
+++ b/tools/traffic_annotation/summary/grouping.xml
@@ -376,6 +376,7 @@
       <annotation id="chromeos_lens_web_image_search"/>
       <annotation id="chromeos_inline_image_request"/>
       <annotation id="baguette_image_download"/>
+      <annotation id="chromeos_walrus_provider"/>
     </sender>
   </group>
   <group name="Admin Features" hidden="true">
diff --git a/ui/android/BUILD.gn b/ui/android/BUILD.gn
index 252a5d92..80c4ca6 100644
--- a/ui/android/BUILD.gn
+++ b/ui/android/BUILD.gn
@@ -311,7 +311,6 @@
   sources = [
     "java/src/org/chromium/ui/KeyboardUtils.java",
     "java/src/org/chromium/ui/KeyboardVisibilityDelegate.java",
-    "java/src/org/chromium/ui/MotionEventUtils.java",
     "java/src/org/chromium/ui/UiUtils.java",
   ]
   deps = [
@@ -488,6 +487,7 @@
     "java/src/org/chromium/ui/util/AttrUtils.java",
     "java/src/org/chromium/ui/util/ColorBlendAnimationFactory.java",
     "java/src/org/chromium/ui/util/ColorUtils.java",
+    "java/src/org/chromium/ui/util/MotionEventUtils.java",
     "java/src/org/chromium/ui/util/RunnableTimer.java",
     "java/src/org/chromium/ui/util/StyleUtils.java",
     "java/src/org/chromium/ui/util/TokenHolder.java",
diff --git a/ui/android/java/src/org/chromium/ui/base/EventForwarder.java b/ui/android/java/src/org/chromium/ui/base/EventForwarder.java
index f10e9e91..a9f65f8 100644
--- a/ui/android/java/src/org/chromium/ui/base/EventForwarder.java
+++ b/ui/android/java/src/org/chromium/ui/base/EventForwarder.java
@@ -27,7 +27,7 @@
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.build.annotations.NullMarked;
 import org.chromium.build.annotations.Nullable;
-import org.chromium.ui.MotionEventUtils;
+import org.chromium.ui.util.MotionEventUtils;
 
 import java.lang.reflect.UndeclaredThrowableException;
 import java.util.ArrayList;
diff --git a/ui/android/java/src/org/chromium/ui/MotionEventUtils.java b/ui/android/java/src/org/chromium/ui/util/MotionEventUtils.java
similarity index 97%
rename from ui/android/java/src/org/chromium/ui/MotionEventUtils.java
rename to ui/android/java/src/org/chromium/ui/util/MotionEventUtils.java
index 4d68f58..a49df88 100644
--- a/ui/android/java/src/org/chromium/ui/MotionEventUtils.java
+++ b/ui/android/java/src/org/chromium/ui/util/MotionEventUtils.java
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-package org.chromium.ui;
+package org.chromium.ui.util;
 
 import android.os.Build;
 import android.view.InputDevice;
@@ -140,6 +140,7 @@
         return (buttons & MotionEvent.BUTTON_TERTIARY) != 0;
     }
 
+    /** Checks if the motion event was generated by a secondary button (middle mouse button). */
     public static boolean isSecondaryClick(int buttons) {
         return (buttons & MotionEvent.BUTTON_SECONDARY) != 0;
     }
diff --git a/ui/android/java/src/org/chromium/ui/util/OWNERS b/ui/android/java/src/org/chromium/ui/util/OWNERS
index 79e1827a..b0456f4 100644
--- a/ui/android/java/src/org/chromium/ui/util/OWNERS
+++ b/ui/android/java/src/org/chromium/ui/util/OWNERS
@@ -1 +1,3 @@
 per-file XrUtils.java=file://components/webxr/OWNERS
+per-file MotionEventUtils.java=skavuluru@google.com
+per-file MotionEventUtils.java=aishwaryarj@google.com
diff --git a/ui/android/junit/src/org/chromium/ui/base/EventForwarderTest.java b/ui/android/junit/src/org/chromium/ui/base/EventForwarderTest.java
index 278b65f5..1c3bff5 100644
--- a/ui/android/junit/src/org/chromium/ui/base/EventForwarderTest.java
+++ b/ui/android/junit/src/org/chromium/ui/base/EventForwarderTest.java
@@ -35,7 +35,7 @@
 
 import org.chromium.base.test.BaseRobolectricTestRunner;
 import org.chromium.base.test.util.HistogramWatcher;
-import org.chromium.ui.MotionEventUtils;
+import org.chromium.ui.util.MotionEventUtils;
 
 /** Tests logic in the {@link EventForwarder} class. */
 @RunWith(BaseRobolectricTestRunner.class)
diff --git a/ui/ozone/platform/drm/ozone_platform_drm.cc b/ui/ozone/platform/drm/ozone_platform_drm.cc
index 17e57c3..ccf9df1 100644
--- a/ui/ozone/platform/drm/ozone_platform_drm.cc
+++ b/ui/ozone/platform/drm/ozone_platform_drm.cc
@@ -7,11 +7,13 @@
 #include <gbm.h>
 #include <stdlib.h>
 #include <xf86drm.h>
+
 #include <memory>
 #include <utility>
 
 #include "base/check.h"
 #include "base/command_line.h"
+#include "base/containers/flat_set.h"
 #include "base/functional/bind.h"
 #include "base/memory/weak_ptr.h"
 #include "base/notreached.h"
@@ -26,6 +28,7 @@
 #include "ui/events/ozone/layout/keyboard_layout_engine_manager.h"
 #include "ui/gfx/linux/client_native_pixmap_dmabuf.h"
 #include "ui/gfx/native_widget_types.h"
+#include "ui/ozone/common/base_keyboard_hook.h"
 #include "ui/ozone/common/bitmap_cursor_factory.h"
 #include "ui/ozone/platform/drm/common/drm_util.h"
 #include "ui/ozone/platform/drm/gpu/drm_device_generator.h"
@@ -294,6 +297,20 @@
     StartDrmThread(block_for_drm_thread);
   }
 
+  std::unique_ptr<PlatformKeyboardHook> CreateKeyboardHook(
+      PlatformKeyboardHookTypes type,
+      base::RepeatingCallback<void(KeyEvent* event)> callback,
+      std::optional<base::flat_set<DomCode>> dom_codes,
+      gfx::AcceleratedWidget accelerated_widget) override {
+    switch (type) {
+      case PlatformKeyboardHookTypes::kModifier:
+        return std::make_unique<BaseKeyboardHook>(std::move(dom_codes),
+                                                  std::move(callback));
+      case PlatformKeyboardHookTypes::kMedia:
+        return nullptr;
+    }
+  }
+
  private:
   // Starts the DRM thread. |blocking| determines if the call should be blocked
   // until the thread is started.
diff --git a/ui/views/controls/tabbed_pane/tabbed_pane.cc b/ui/views/controls/tabbed_pane/tabbed_pane.cc
index ec481a4..e57f763 100644
--- a/ui/views/controls/tabbed_pane/tabbed_pane.cc
+++ b/ui/views/controls/tabbed_pane/tabbed_pane.cc
@@ -459,10 +459,6 @@
   // See |selectionBar.expand| and |selectionBar.contract|.
   expand_animation_->SetDuration(base::Milliseconds(150));
   contract_animation_->SetDuration(base::Milliseconds(180));
-
-  // Callback when the enabled state changes.
-  enabled_changed_subscription_ = AddEnabledChangedCallback(base::BindRepeating(
-      &TabbedPaneTabStrip::OnEnableChanged, base::Unretained(this)));
 }
 
 TabbedPaneTabStrip::~TabbedPaneTabStrip() = default;
@@ -498,16 +494,6 @@
   }
 }
 
-void TabbedPaneTabStrip::OnEnableChanged() {
-  const bool enabled = GetEnabled();
-
-  for (size_t i = 0; i < GetTabCount(); ++i) {
-    auto* tab = GetTabAtIndex(i);
-    tab->SetEnabled(enabled);
-    tab->UpdateEnabledColor(enabled);
-  }
-}
-
 // Computes the starting and ending points of the selection slider for a given
 // tab from the origin.
 //
diff --git a/ui/views/controls/tabbed_pane/tabbed_pane.h b/ui/views/controls/tabbed_pane/tabbed_pane.h
index eaeee73..6964929 100644
--- a/ui/views/controls/tabbed_pane/tabbed_pane.h
+++ b/ui/views/controls/tabbed_pane/tabbed_pane.h
@@ -282,9 +282,6 @@
   void AnimationProgressed(const gfx::Animation* animation) override;
   void AnimationEnded(const gfx::Animation* animation) override;
 
-  // Called by TabbedPaneTabStrip when its enable status changes.
-  void OnEnableChanged();
-
   // Called by TabbedPaneTabStrip when the selected tab changes. This function
   // is only called if |from_tab| is not null, i.e., there was a previously
   // selected tab.
@@ -388,9 +385,6 @@
   // Whether to draw the unselected divider below the tabs. Useful for when
   // the caller wants to use a custom divider instead.
   bool draw_tab_divider_ = true;
-
-  // Listener to monitor `SetEnabled` and propagate the state changes.
-  base::CallbackListSubscription enabled_changed_subscription_;
 };
 
 BEGIN_VIEW_BUILDER(VIEWS_EXPORT, TabbedPane, FlexLayoutView)
diff --git a/v8 b/v8
index be3c061..d154fab 160000
--- a/v8
+++ b/v8
@@ -1 +1 @@
-Subproject commit be3c06167b01bc7cd70edb4e44ec76ce8058390d
+Subproject commit d154fab24bf5e8d933132b71bda7b1541998b9dd