diff --git a/DEPS b/DEPS
index 8bcd61d..28e08e9 100644
--- a/DEPS
+++ b/DEPS
@@ -295,19 +295,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': '5c45e13e948d0c028bd62c61ea316ec5d4e16ce1',
+  'src_internal_revision': '281dbba7538a783d11627f49508322558a5f706e',
   # 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': 'bb255dd0252e256a28542475b22711346c319030',
+  'skia_revision': 'db706fd37874f786e82238b58a4c3799fa430f60',
   # 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': '40b93d8a6b46338c63f39312306d811ca5ebf37c',
+  'v8_revision': '7d09849bb20b29cc989b14e62e086f1ded0a4134',
   # 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': 'db71e8fa7c26d18f76d7b9e9474447b20f1c73b3',
+  'angle_revision': '3f7bded1c0334b099461de9b19ecc00d832ebc5a',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -371,11 +371,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling catapult
   # and whatever else without interference from each other.
-  'catapult_revision': 'ffd1c50cbd19e3f0c5de2fd1d20b43adc04b6e85',
+  'catapult_revision': '41104509db3116fd5a5e3af4f8242b59de7adc67',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling CrossBench
   # and whatever else without interference from each other.
-  'crossbench_revision': '354a6230a3a7bc437dd3f512a6decd3155eb15fc',
+  'crossbench_revision': '8f9e09ad773d1e3fbb3021d3ab09246db491df5c',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -391,7 +391,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': '2755713bf6b0ca46d6e60124a0633f45c2d54677',
+  'devtools_frontend_revision': 'a606a77310848ca4f22c139777106f9100f8e318',
   # 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.
@@ -1486,7 +1486,7 @@
 
   'src/clank': {
     'url': Var('chrome_git') + '/clank/internal/apps.git' + '@' +
-    'b98a5841d71a8998413b6811f80897742ea8b9e1',
+    '968450ab8d1bcffb25acc11625dc295076a5c2d1',
     'condition': 'checkout_android and checkout_src_internal',
   },
 
@@ -2526,7 +2526,7 @@
     Var('pdfium_git') + '/pdfium.git' + '@' +  Var('pdfium_revision'),
 
   'src/third_party/perfetto':
-    Var('chromium_git') + '/external/github.com/google/perfetto.git' + '@' + '109a2ebe2b65215b8ba3ff0d6a3a8361220a0992',
+    Var('chromium_git') + '/external/github.com/google/perfetto.git' + '@' + '99c9ef8102d3770e6d5ba91d9ca400b751b0af9b',
 
   'src/base/tracing/test/data': {
     'bucket': 'perfetto',
@@ -2840,16 +2840,16 @@
       'dep_type': 'cipd',
   },
 
-  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@bac8bd2de3c4470697edee557d7ac29fff036b3d',
+  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@8379edd28781c7fc16f2e8d2094ba557c9c6cbf3',
   'src/third_party/glslang/src': '{chromium_git}/external/github.com/KhronosGroup/glslang@963588074b26326ff0426c8953c1235213309bdb',
   '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@c9aad99f9276817f18f72a4696239237c83cb775',
-  'src/third_party/spirv-tools/src': '{chromium_git}/external/github.com/KhronosGroup/SPIRV-Tools@eac930caa2bc7209ca71ecea7c15cbab33d5bc66',
+  'src/third_party/spirv-tools/src': '{chromium_git}/external/github.com/KhronosGroup/SPIRV-Tools@66fe610946a6d98169f8ebe9ca483f64c4009fa5',
   'src/third_party/vulkan-headers/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-Headers@75ad707a587e1469fb53a901b9b68fe9f6fbc11f',
   'src/third_party/vulkan-loader/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-Loader@0f4bd6649d19b5df4e76381725ab6f5a2eeb9f30',
   'src/third_party/vulkan-tools/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-Tools@60b640cb931814fcc6dabe4fc61f4738c56579f6',
   'src/third_party/vulkan-utility-libraries/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-Utility-Libraries@4f628210460c4df62029959cc7fb237ac75f7189',
-  'src/third_party/vulkan-validation-layers/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-ValidationLayers@40d64d8f544194b82f658dcecd85d03d120a51ef',
+  'src/third_party/vulkan-validation-layers/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-ValidationLayers@42974927c2b34cfd29110e702fb493336e2fd711',
 
   'src/third_party/vulkan_memory_allocator':
     Var('chromium_git') + '/external/github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git' + '@' + '56300b29fbfcc693ee6609ddad3fdd5b7a449a21',
@@ -3016,7 +3016,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/boca_app/app',
-        'version': '1LD41rtsDe_Daantyr1wGeYldo3v15xhIOzBKKK28zcC',
+        'version': 'n_Z9TCKOPH82cWiMP7DbBcUpi8uYllaS_tow36R9gPcC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
@@ -3071,7 +3071,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/projector_app/app',
-        'version': '9aE3Usd4eg6fAYhUOn8CWfgbIHuooGzTS70MaOdsuPoC',
+        'version': '-rgsJjvA1tJkkfFDOWmxtWhepIBQSi8OcHdy8gMags4C',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
@@ -4680,7 +4680,7 @@
 
   'src/ios_internal':  {
       'url': Var('chrome_git') + '/chrome/ios_internal.git' + '@' +
-        '79e80e721cb09978055fddd62b21b31d68192891',
+        'a1ef7929fc7d479af95d97f8ea9cfeefc2eed5e6',
       'condition': 'checkout_ios and checkout_src_internal',
   },
 
diff --git a/android_webview/browser/aw_feature_map.cc b/android_webview/browser/aw_feature_map.cc
index dd0a78a..d928459 100644
--- a/android_webview/browser/aw_feature_map.cc
+++ b/android_webview/browser/aw_feature_map.cc
@@ -56,6 +56,7 @@
     &::features::kPrefetchBrowserInitiatedTriggers,
     &features::kWebViewShortCircuitShouldInterceptRequest,
     &features::kWebViewUseStartupTasksLogic,
+    &features::kWebViewRecordAppCacheHistograms,
 };
 
 // static
diff --git a/android_webview/common/aw_features.cc b/android_webview/common/aw_features.cc
index 529cbb2a..2f1a785 100644
--- a/android_webview/common/aw_features.cc
+++ b/android_webview/common/aw_features.cc
@@ -240,4 +240,10 @@
 BASE_FEATURE(kWebViewUseStartupTasksLogic,
              "WebViewUseStartupTasksLogic",
              base::FEATURE_DISABLED_BY_DEFAULT);
+
+// When enabled, records histograms relating to app's cache size.
+BASE_FEATURE(kWebViewRecordAppCacheHistograms,
+             "WebViewRecordAppCacheHistograms",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 }  // namespace android_webview::features
diff --git a/android_webview/common/aw_features.h b/android_webview/common/aw_features.h
index 3ff03d2..f038c89 100644
--- a/android_webview/common/aw_features.h
+++ b/android_webview/common/aw_features.h
@@ -54,6 +54,7 @@
 BASE_DECLARE_FEATURE(kWebViewInterceptedCookieHeader);
 BASE_DECLARE_FEATURE(kWebViewInterceptedCookieHeaderReadWrite);
 BASE_DECLARE_FEATURE(kWebViewShortCircuitShouldInterceptRequest);
+BASE_DECLARE_FEATURE(kWebViewRecordAppCacheHistograms);
 
 }  // namespace android_webview::features
 
diff --git a/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumAwInit.java b/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumAwInit.java
index 88de490..8d04524 100644
--- a/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumAwInit.java
+++ b/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumAwInit.java
@@ -13,6 +13,7 @@
 import android.os.Looper;
 import android.os.Process;
 import android.os.SystemClock;
+import android.os.storage.StorageManager;
 import android.util.Log;
 import android.webkit.CookieManager;
 import android.webkit.GeolocationPermissions;
@@ -71,8 +72,10 @@
 import org.chromium.ui.base.DeviceFormFactor;
 import org.chromium.ui.base.ResourceBundle;
 
+import java.io.IOException;
 import java.util.ArrayDeque;
 import java.util.Locale;
+import java.util.UUID;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -474,6 +477,53 @@
                                 });
                     }
 
+                    if (AwFeatureMap.isEnabled(AwFeatures.WEBVIEW_RECORD_APP_CACHE_HISTOGRAMS)) {
+                        PostTask.postDelayedTask(
+                                TaskTraits.BEST_EFFORT_MAY_BLOCK,
+                                () -> {
+                                    StorageManager storageManager =
+                                            (StorageManager)
+                                                    ContextUtils.getApplicationContext()
+                                                            .getSystemService(
+                                                                    Context.STORAGE_SERVICE);
+                                    UUID storageUuid =
+                                            ContextUtils.getApplicationContext()
+                                                    .getApplicationInfo()
+                                                    .storageUuid;
+                                    long startTimeGetCacheQuotaMs = SystemClock.uptimeMillis();
+                                    try {
+                                        long cacheQuotaKiloBytes =
+                                                storageManager.getCacheQuotaBytes(storageUuid)
+                                                        / 1024;
+                                        RecordHistogram.recordCount1MHistogram(
+                                                "Android.WebView.CacheQuotaSize",
+                                                (int) cacheQuotaKiloBytes);
+                                    } catch (IOException e) {
+                                    } finally {
+                                        RecordHistogram.recordTimesHistogram(
+                                                "Android.WebView.GetCacheQuotaSizeTime",
+                                                SystemClock.uptimeMillis()
+                                                        - startTimeGetCacheQuotaMs);
+                                    }
+
+                                    long startTimeGetCacheSizeMs = SystemClock.uptimeMillis();
+                                    try {
+                                        long cacheSizeKiloBytes =
+                                                storageManager.getCacheSizeBytes(storageUuid)
+                                                        / 1024;
+                                        RecordHistogram.recordCount1MHistogram(
+                                                "Android.WebView.CacheSize",
+                                                (int) cacheSizeKiloBytes);
+                                    } catch (IOException e) {
+                                    } finally {
+                                        RecordHistogram.recordTimesHistogram(
+                                                "Android.WebView.GetCacheSizeTime",
+                                                SystemClock.uptimeMillis()
+                                                        - startTimeGetCacheSizeMs);
+                                    }
+                                },
+                                5000);
+                    }
                     AwCrashyClassUtils.maybeCrashIfEnabled();
                     // Must happen right after Chromium initialization is complete.
                     mInitState.set(INIT_FINISHED);
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 6cef881..22178954 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
@@ -831,6 +831,9 @@
                 AwFeatures.WEBVIEW_PREFETCH_NATIVE_LIBRARY,
                 "Prefetches the native WebView code to memory during startup."),
         Flag.baseFeature(
+                AwFeatures.WEBVIEW_RECORD_APP_CACHE_HISTOGRAMS,
+                "When enabled, records histograms relating to app's cache size."),
+        Flag.baseFeature(
                 GfxSwitches.USE_SMART_REF_FOR_GPU_FENCE_HANDLE,
                 "Avoids cloning of gpu fences when possible"),
         Flag.baseFeature(
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/devui/FlagsFragmentTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/devui/FlagsFragmentTest.java
index faf3b6d..f9b396a 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/devui/FlagsFragmentTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/devui/FlagsFragmentTest.java
@@ -792,10 +792,6 @@
     @MediumTest
     @Feature({"AndroidWebView"})
     public void testResetFlagsByIntent() throws Throwable {
-        // TODO(crbug.com/408061337): Check whether this pollUiThread fixes the test failures on
-        // Android P builders and remove if not.
-        CriteriaHelper.pollUiThread(
-                () -> mRule.getActivity().hasWindowFocus(), "Activity did not gain focus.");
         // 1. First test that the intent resets the flags
         // Given one flag is set
         toggleFlag(onData(anything()).inAdapterView(withId(R.id.flags_list)).atPosition(1), true);
diff --git a/android_webview/nonembedded/BUILD.gn b/android_webview/nonembedded/BUILD.gn
index 2772f6d9..70389ae6 100644
--- a/android_webview/nonembedded/BUILD.gn
+++ b/android_webview/nonembedded/BUILD.gn
@@ -145,6 +145,7 @@
     "//third_party/android_deps:com_google_code_findbugs_jsr305_java",
     "//third_party/android_deps:protobuf_lite_runtime_java",
     "//third_party/androidx:androidx_annotation_annotation_java",
+    "//third_party/androidx:androidx_core_core_java",
     "//third_party/jni_zero:jni_zero_java",
   ]
   resources_package = "org.chromium.android_webview.devui"
diff --git a/android_webview/nonembedded/java/src/org/chromium/android_webview/services/DeveloperUiService.java b/android_webview/nonembedded/java/src/org/chromium/android_webview/services/DeveloperUiService.java
index 6092cca..2a4413e 100644
--- a/android_webview/nonembedded/java/src/org/chromium/android_webview/services/DeveloperUiService.java
+++ b/android_webview/nonembedded/java/src/org/chromium/android_webview/services/DeveloperUiService.java
@@ -14,6 +14,7 @@
 import android.content.ServiceConnection;
 import android.content.SharedPreferences;
 import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
 import android.os.Binder;
 import android.os.Build;
 import android.os.IBinder;
@@ -21,6 +22,7 @@
 import android.os.RemoteException;
 
 import androidx.annotation.NonNull;
+import androidx.core.app.ServiceCompat;
 
 import org.chromium.android_webview.common.DeveloperModeUtils;
 import org.chromium.android_webview.common.Flag;
@@ -270,7 +272,15 @@
                         .setTicker(NOTIFICATION_TICKER)
                         .build();
         try {
-            startForeground(FLAG_OVERRIDE_NOTIFICATION_ID, notification);
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+                ServiceCompat.startForeground(
+                        this,
+                        FLAG_OVERRIDE_NOTIFICATION_ID,
+                        notification,
+                        ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE);
+            } else {
+                startForeground(FLAG_OVERRIDE_NOTIFICATION_ID, notification);
+            }
         } catch (IllegalStateException e) {
             logSuspectedForegroundServiceStartNotAllowedException();
         }
diff --git a/android_webview/test/shell/AndroidManifest.xml b/android_webview/test/shell/AndroidManifest.xml
index c47a2e6..b8fcb7e 100644
--- a/android_webview/test/shell/AndroidManifest.xml
+++ b/android_webview/test/shell/AndroidManifest.xml
@@ -28,6 +28,10 @@
   <uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
   <!-- android.permission.RECORD_AUDIO -->
   <uses-feature android:name="android.hardware.microphone" android:required="false" />
+    <!-- Required by WebView Developer UI tests -->
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE"/>
+    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
 
   <queries>
     <intent>
@@ -143,7 +147,12 @@
               android:exported="true"
               android:authorities="org.chromium.android_webview.shell.DeveloperModeContentProvider"/>
     <service android:name="org.chromium.android_webview.services.DeveloperUiService"
-                         android:exported="true" />
+             android:exported="true"
+             android:foregroundServiceType="specialUse">
+        <property
+          android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
+          android:value="debugging service" />
+      </service>
     <activity-alias android:name="org.chromium.android_webview.devui.DeveloperModeState"
                       android:targetActivity="org.chromium.android_webview.shell.AwShellActivity"
                       android:enabled="false" />
diff --git a/base/task/sequence_manager/OWNERS b/base/task/sequence_manager/OWNERS
index 8d441b8..58ebd732 100644
--- a/base/task/sequence_manager/OWNERS
+++ b/base/task/sequence_manager/OWNERS
@@ -1,4 +1,3 @@
 etiennep@chromium.org
 fdoray@chromium.org
 shaseley@chromium.org
-skyostil@chromium.org
diff --git a/base/trace_event/OWNERS b/base/trace_event/OWNERS
index 4c7823f3..8396f28a 100644
--- a/base/trace_event/OWNERS
+++ b/base/trace_event/OWNERS
@@ -6,7 +6,6 @@
 petrcermak@chromium.org
 
 primiano@chromium.org
-skyostil@chromium.org
 
 # For memory-infra related changes
 ssid@chromium.org
diff --git a/build/OWNERS b/build/OWNERS
index 382120b..cc2f0d63 100644
--- a/build/OWNERS
+++ b/build/OWNERS
@@ -2,7 +2,6 @@
 # NOTE: keep this in sync with global-owners-override@chromium.org owners
 # by emailing lsc-policy@chromium.org when this list changes.
 agrieve@chromium.org
-dpranke@google.com
 sdefresne@chromium.org
 thakis@chromium.org
 thomasanderson@chromium.org
diff --git a/build/config/win/BUILD.gn b/build/config/win/BUILD.gn
index aaeadc3..a71603f 100644
--- a/build/config/win/BUILD.gn
+++ b/build/config/win/BUILD.gn
@@ -427,7 +427,6 @@
     "/DELAYLOAD:hid.dll",
     "/DELAYLOAD:imagehlp.dll",
     "/DELAYLOAD:imm32.dll",
-    "/DELAYLOAD:mmdevapi.dll",
     "/DELAYLOAD:msi.dll",
     "/DELAYLOAD:netapi32.dll",
     "/DELAYLOAD:ncrypt.dll",
diff --git a/cc/trees/layer_tree_host_impl.cc b/cc/trees/layer_tree_host_impl.cc
index 3c6c3edc9..6bc46e55 100644
--- a/cc/trees/layer_tree_host_impl.cc
+++ b/cc/trees/layer_tree_host_impl.cc
@@ -2651,7 +2651,7 @@
     screenshot_destination_ = base::UnguessableToken();
   }
 
-  metadata.is_software = !layer_tree_frame_sink_->context_provider();
+  metadata.is_software = GetDrawMode() != DrawMode::DRAW_MODE_HARDWARE;
 
   return metadata;
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxSurveyController.java b/chrome/android/java/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxSurveyController.java
index db14200..2bd4a8b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxSurveyController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxSurveyController.java
@@ -334,7 +334,7 @@
                         SurveyClientFactory.getInstance().getCrashUploadPermissionSupplier());
         SurveyClient surveyClient =
                 SurveyClientFactory.getInstance()
-                        .createClient(surveyConfig, messageDelegate, mProfile);
+                        .createClient(surveyConfig, messageDelegate, mProfile, mTabModelSelector);
         return surveyClient;
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/survey/ChromeSurveyController.java b/chrome/android/java/src/org/chromium/chrome/browser/survey/ChromeSurveyController.java
index 9f1bc8f..cb545da 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/survey/ChromeSurveyController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/survey/ChromeSurveyController.java
@@ -85,7 +85,8 @@
                         tabModelSelector,
                         ChromeSurveyController::isUMAEnabled);
         SurveyClient client =
-                SurveyClientFactory.getInstance().createClient(config, messageDelegate, profile);
+                SurveyClientFactory.getInstance()
+                        .createClient(config, messageDelegate, profile, tabModelSelector);
         if (client == null) return null;
 
         ChromeSurveyController chromeSurveyController = new ChromeSurveyController(client);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/InterceptNavigationDelegateClientImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/InterceptNavigationDelegateClientImpl.java
index d3ba845..106ce57 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/InterceptNavigationDelegateClientImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/InterceptNavigationDelegateClientImpl.java
@@ -138,6 +138,11 @@
     }
 
     @Override
+    public boolean isTabInBrowser() {
+        return mTab.isTabInBrowser();
+    }
+
+    @Override
     public boolean isInDesktopWindowingMode() {
         if (sIsInDesktopWindowingModeForTesting != null) {
             return sIsInDesktopWindowingModeForTesting;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabImpl.java
index 7b7c973..54b94b95 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabImpl.java
@@ -2251,6 +2251,14 @@
     }
 
     @Override
+    public boolean isTabInBrowser() {
+        // TODO(crbug.com/417720713): replace deprecated getActivity with something else.
+        ChromeActivity activity = getActivity();
+        if (activity == null) return false;
+        return activity.getActivityType() == ActivityType.TABBED;
+    }
+
+    @Override
     public long getTimestampMillis() {
         return mTimestampMillis;
     }
diff --git a/chrome/android/javatests/OWNERS b/chrome/android/javatests/OWNERS
index 3d818330..5e050cc4 100644
--- a/chrome/android/javatests/OWNERS
+++ b/chrome/android/javatests/OWNERS
@@ -2,7 +2,6 @@
 
 bsazonov@chromium.org
 peconn@chromium.org
-skyostil@chromium.org
 
 per-file TabbedNavigationBarColorControllerTest.java=clhager@google.com
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/site_settings/FamilyLinkControlsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/site_settings/FamilyLinkControlsTest.java
index 5dc2208..a5c5457 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/site_settings/FamilyLinkControlsTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/site_settings/FamilyLinkControlsTest.java
@@ -36,6 +36,8 @@
 import org.chromium.base.test.util.CriteriaHelper;
 import org.chromium.base.test.util.DisableIf;
 import org.chromium.base.test.util.DoNotBatch;
+import org.chromium.base.test.util.Features.DisableFeatures;
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.profiles.ProfileManager;
 import org.chromium.chrome.browser.settings.SettingsActivity;
 import org.chromium.chrome.browser.signin.SigninCheckerProvider;
@@ -44,6 +46,7 @@
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
 import org.chromium.chrome.test.util.browser.signin.SigninTestRule;
 import org.chromium.components.browser_ui.settings.ChromeSwitchPreference;
+import org.chromium.components.browser_ui.site_settings.BinaryStatePermissionPreference;
 import org.chromium.components.browser_ui.site_settings.SiteSettingsCategory;
 import org.chromium.components.browser_ui.site_settings.WebsitePreferenceBridge;
 import org.chromium.components.browser_ui.site_settings.WebsitePreferenceBridgeJni;
@@ -94,6 +97,9 @@
 
     @Test
     @SmallTest
+    // TODO(crbug.com/415770550): Fix Text for managed settings and enable this
+    // test for raddio buttons.
+    @DisableFeatures(ChromeFeatureList.PERMISSION_SITE_SETTING_RADIO_BUTTON)
     public void testDeletingOnDeviceDataBlockedForSupervisedUsers() {
         SettingsActivity settingsActivity =
                 SiteSettingsTestUtils.startSiteSettingsCategory(
@@ -143,10 +149,16 @@
         PreferenceFragmentCompat preferenceFragment =
                 (PreferenceFragmentCompat) settingsActivity.getMainFragment();
         PreferenceScreen preferenceScreen = preferenceFragment.getPreferenceScreen();
-        ChromeSwitchPreference binary_toggle = preferenceScreen.findPreference("binary_toggle");
+        if (ChromeFeatureList.isEnabled(ChromeFeatureList.PERMISSION_SITE_SETTING_RADIO_BUTTON)) {
+            BinaryStatePermissionPreference radioButton =
+                    preferenceScreen.findPreference("binary_radio_button");
+            Assert.assertTrue(radioButton.isEnabled());
+        } else {
+            ChromeSwitchPreference binary_toggle = preferenceScreen.findPreference("binary_toggle");
+            // When deleting cookies are not blocked through Family Link the toggle will be enabled
+            Assert.assertTrue(binary_toggle.isEnabled());
+        }
 
-        // When deleting cookies are not blocked through Family Link the toggle will be enabled
-        Assert.assertTrue(binary_toggle.isEnabled());
         settingsActivity.finish();
     }
 }
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 cb8065b..bf43ace 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
@@ -214,14 +214,14 @@
 
     private static final String[] NULL_ARRAY = new String[0];
     private static final String[] BINARY_TOGGLE_AND_INFO_TEXT =
-            new String[] {"info_text", "binary_toggle"};
-    private static final String[] BINARY_TOGGLE = new String[] {"binary_toggle"};
+            new String[] {"info_text", "binary_radio_button"};
+    private static final String[] BINARY_TOGGLE = new String[] {"binary_radio_button"};
     private static final String[] BINARY_TOGGLE_WITH_EXCEPTION_AND_INFO_TEXT =
-            new String[] {"info_text", "binary_toggle", "add_exception"};
+            new String[] {"info_text", "binary_radio_button", "add_exception"};
     private static final String[] BINARY_TOGGLE_WITH_EXCEPTION =
-            new String[] {"binary_toggle", "add_exception"};
+            new String[] {"binary_radio_button", "add_exception"};
     private static final String[] BINARY_TOGGLE_WITH_OS_WARNING_EXTRA =
-            new String[] {"binary_toggle", "os_permissions_warning_extra"};
+            new String[] {"info_text", "binary_radio_button", "os_permissions_warning_extra"};
     private static final String[] CLEAR_BROWSING_DATA_LINK =
             new String[] {"clear_browsing_data_link", "clear_browsing_divider"};
     private static final String[] ANTI_ABUSE_PREF_KEYS = {
@@ -466,6 +466,7 @@
                         SiteSettingsCategory.Type.DEVICE_LOCATION,
                         ContentSettingsType.GEOLOCATION,
                         true)
+                .withExpectedPrefKeysAtStart(SingleCategorySettings.INFO_TEXT_KEY)
                 .run();
         ThreadUtils.runOnUiThreadBlocking(
                 () ->
@@ -498,6 +499,7 @@
                         SiteSettingsCategory.Type.DEVICE_LOCATION,
                         ContentSettingsType.GEOLOCATION,
                         false)
+                .withExpectedPrefKeysAtStart(SingleCategorySettings.INFO_TEXT_KEY)
                 .run();
         ThreadUtils.runOnUiThreadBlocking(
                 () ->
@@ -620,6 +622,14 @@
                                 enabled
                                         ? CookieControlsMode.OFF
                                         : CookieControlsMode.BLOCK_THIRD_PARTY);
+                    } else if (ChromeFeatureList.isEnabled(
+                                    ChromeFeatureList.PERMISSION_SITE_SETTING_RADIO_BUTTON)
+                            && type != SiteSettingsCategory.Type.ANTI_ABUSE) {
+                        BinaryStatePermissionPreference radioButton =
+                                preferences.findPreference(
+                                        SingleCategorySettings.BINARY_RADIO_BUTTON_KEY);
+
+                        preferences.onPreferenceChange(radioButton, enabled);
                     } else {
                         ChromeSwitchPreference toggle =
                                 preferences.findPreference(
@@ -1356,6 +1366,7 @@
                         SiteSettingsCategory.Type.POPUPS,
                         ContentSettingsType.POPUPS,
                         false)
+                .withExpectedPrefKeysAtStart(SingleCategorySettings.INFO_TEXT_KEY)
                 .run();
 
         // Test that the popup doesn't open.
@@ -1376,6 +1387,7 @@
                         SiteSettingsCategory.Type.POPUPS,
                         ContentSettingsType.POPUPS,
                         true)
+                .withExpectedPrefKeysAtStart(SingleCategorySettings.INFO_TEXT_KEY)
                 .run();
 
         // Test that a popup opens.
@@ -1472,7 +1484,10 @@
     @SmallTest
     @Feature({"Preferences"})
     public void testOnlyExpectedPreferencesAds() {
-        testExpectedPreferences(SiteSettingsCategory.Type.ADS, BINARY_TOGGLE, BINARY_TOGGLE);
+        testExpectedPreferences(
+                SiteSettingsCategory.Type.ADS,
+                BINARY_TOGGLE_AND_INFO_TEXT,
+                BINARY_TOGGLE_AND_INFO_TEXT);
     }
 
     @Test
@@ -1490,7 +1505,9 @@
     @Feature({"Preferences"})
     public void testOnlyExpectedPreferencesAugmentedReality() {
         testExpectedPreferences(
-                SiteSettingsCategory.Type.AUGMENTED_REALITY, BINARY_TOGGLE, BINARY_TOGGLE);
+                SiteSettingsCategory.Type.AUGMENTED_REALITY,
+                BINARY_TOGGLE_AND_INFO_TEXT,
+                BINARY_TOGGLE_AND_INFO_TEXT);
     }
 
     @Test
@@ -1509,8 +1526,8 @@
     public void testOnlyExpectedPreferencesAutomaticDownloads() {
         testExpectedPreferences(
                 SiteSettingsCategory.Type.AUTOMATIC_DOWNLOADS,
-                BINARY_TOGGLE_WITH_EXCEPTION,
-                BINARY_TOGGLE);
+                BINARY_TOGGLE_WITH_EXCEPTION_AND_INFO_TEXT,
+                BINARY_TOGGLE_AND_INFO_TEXT);
     }
 
     @Test
@@ -1519,8 +1536,8 @@
     public void testOnlyExpectedPreferencesBackgroundSync() {
         testExpectedPreferences(
                 SiteSettingsCategory.Type.BACKGROUND_SYNC,
-                BINARY_TOGGLE_WITH_EXCEPTION,
-                BINARY_TOGGLE);
+                BINARY_TOGGLE_WITH_EXCEPTION_AND_INFO_TEXT,
+                BINARY_TOGGLE_AND_INFO_TEXT);
     }
 
     @Test
@@ -1542,21 +1559,28 @@
     @SmallTest
     @Feature({"Preferences"})
     public void testOnlyExpectedPreferencesCamera() {
-        testExpectedPreferences(SiteSettingsCategory.Type.CAMERA, BINARY_TOGGLE, BINARY_TOGGLE);
+        testExpectedPreferences(
+                SiteSettingsCategory.Type.CAMERA,
+                BINARY_TOGGLE_AND_INFO_TEXT,
+                BINARY_TOGGLE_AND_INFO_TEXT);
     }
 
     @Test
     @SmallTest
     @Feature({"Preferences"})
     public void testOnlyExpectedPreferencesClipboard() {
-        testExpectedPreferences(SiteSettingsCategory.Type.CLIPBOARD, BINARY_TOGGLE, BINARY_TOGGLE);
+        testExpectedPreferences(
+                SiteSettingsCategory.Type.CLIPBOARD,
+                BINARY_TOGGLE_AND_INFO_TEXT,
+                BINARY_TOGGLE_AND_INFO_TEXT);
     }
 
     @Test
     @SmallTest
     @Feature({"Preferences"})
     public void testOnlyExpectedPreferencesFileEditing() {
-        checkPreferencesForCategory(SiteSettingsCategory.Type.FILE_EDITING, BINARY_TOGGLE);
+        checkPreferencesForCategory(
+                SiteSettingsCategory.Type.FILE_EDITING, BINARY_TOGGLE_AND_INFO_TEXT);
     }
 
     @Test
@@ -1896,7 +1920,9 @@
         LocationSettingsTestUtil.setSystemLocationSettingEnabled(true);
 
         testExpectedPreferences(
-                SiteSettingsCategory.Type.DEVICE_LOCATION, BINARY_TOGGLE, BINARY_TOGGLE);
+                SiteSettingsCategory.Type.DEVICE_LOCATION,
+                BINARY_TOGGLE_AND_INFO_TEXT,
+                BINARY_TOGGLE_AND_INFO_TEXT);
 
         // Disable system location setting and check for the right preferences.
         LocationSettingsTestUtil.setSystemLocationSettingEnabled(false);
@@ -1911,8 +1937,8 @@
     public void testOnlyExpectedPreferencesFederatedIdentityApi() {
         testExpectedPreferences(
                 SiteSettingsCategory.Type.FEDERATED_IDENTITY_API,
-                BINARY_TOGGLE_WITH_EXCEPTION,
-                BINARY_TOGGLE_WITH_EXCEPTION);
+                BINARY_TOGGLE_WITH_EXCEPTION_AND_INFO_TEXT,
+                BINARY_TOGGLE_WITH_EXCEPTION_AND_INFO_TEXT);
     }
 
     @Test
@@ -1938,7 +1964,9 @@
     @Feature({"Preferences"})
     public void testOnlyExpectedPreferencesIdleDetection() {
         testExpectedPreferences(
-                SiteSettingsCategory.Type.IDLE_DETECTION, BINARY_TOGGLE, BINARY_TOGGLE);
+                SiteSettingsCategory.Type.IDLE_DETECTION,
+                BINARY_TOGGLE_AND_INFO_TEXT,
+                BINARY_TOGGLE_AND_INFO_TEXT);
     }
 
     @Test
@@ -1947,15 +1975,18 @@
     public void testOnlyExpectedPreferencesJavascript() {
         testExpectedPreferences(
                 SiteSettingsCategory.Type.JAVASCRIPT,
-                BINARY_TOGGLE_WITH_EXCEPTION,
-                BINARY_TOGGLE_WITH_EXCEPTION);
+                BINARY_TOGGLE_WITH_EXCEPTION_AND_INFO_TEXT,
+                BINARY_TOGGLE_WITH_EXCEPTION_AND_INFO_TEXT);
     }
 
     @Test
     @SmallTest
     @Feature({"Preferences"})
     public void testOnlyExpectedPreferencesMicrophone() {
-        testExpectedPreferences(SiteSettingsCategory.Type.MICROPHONE, BINARY_TOGGLE, BINARY_TOGGLE);
+        testExpectedPreferences(
+                SiteSettingsCategory.Type.MICROPHONE,
+                BINARY_TOGGLE_AND_INFO_TEXT,
+                BINARY_TOGGLE_AND_INFO_TEXT);
     }
 
     @Test
@@ -1964,7 +1995,10 @@
     public void testOnlyExpectedPreferencesNfc() {
         NfcSystemLevelSetting.setNfcSettingForTesting(true);
 
-        testExpectedPreferences(SiteSettingsCategory.Type.NFC, BINARY_TOGGLE, BINARY_TOGGLE);
+        testExpectedPreferences(
+                SiteSettingsCategory.Type.NFC,
+                BINARY_TOGGLE_AND_INFO_TEXT,
+                BINARY_TOGGLE_AND_INFO_TEXT);
 
         // Disable system nfc setting and check for the right preferences.
         NfcSystemLevelSetting.setNfcSettingForTesting(false);
@@ -1985,12 +2019,17 @@
         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
             notifications_enabled =
                     new String[] {
-                        "binary_toggle", "notifications_quiet_ui", "notifications_vibrate"
+                        "info_text",
+                        "binary_toggle",
+                        "notifications_quiet_ui",
+                        "notifications_vibrate"
                     };
-            notifications_disabled = new String[] {"binary_toggle", "notifications_vibrate"};
+            notifications_disabled =
+                    new String[] {"info_text", "binary_toggle", "notifications_vibrate"};
         } else {
-            notifications_enabled = new String[] {"binary_toggle", "notifications_quiet_ui"};
-            notifications_disabled = BINARY_TOGGLE;
+            notifications_enabled =
+                    new String[] {"info_text", "binary_radio_button", "notifications_quiet_ui"};
+            notifications_disabled = BINARY_TOGGLE_AND_INFO_TEXT;
         }
 
         testExpectedPreferences(
@@ -2003,14 +2042,17 @@
     @SmallTest
     @Feature({"Preferences"})
     public void testOnlyExpectedPreferencesPopups() {
-        testExpectedPreferences(SiteSettingsCategory.Type.POPUPS, BINARY_TOGGLE, BINARY_TOGGLE);
+        testExpectedPreferences(
+                SiteSettingsCategory.Type.POPUPS,
+                BINARY_TOGGLE_AND_INFO_TEXT,
+                BINARY_TOGGLE_AND_INFO_TEXT);
     }
 
     @Test
     @SmallTest
     @Feature({"Preferences"})
     public void testOnlyExpectedPreferencesProtectedMedia() {
-        String[] protectedMedia = new String[] {"tri_state_toggle", "protected_content_learn_more"};
+        String[] protectedMedia = new String[] {"info_text", "tri_state_toggle"};
         setGlobalTriStateToggleForCategory(
                 SiteSettingsCategory.Type.PROTECTED_MEDIA, ContentSettingValues.ALLOW);
         checkPreferencesForCategory(SiteSettingsCategory.Type.PROTECTED_MEDIA, protectedMedia);
@@ -2026,10 +2068,12 @@
     @SmallTest
     @Feature({"Preferences"})
     public void testOnlyExpectedPreferencesRequestDesktopSite() {
-        String[] rdsEnabled = {"binary_toggle", "desktop_site_window", "add_exception"};
+        String[] rdsEnabled = {
+            "info_text", "binary_radio_button", "desktop_site_window", "add_exception"
+        };
         testExpectedPreferences(
                 SiteSettingsCategory.Type.REQUEST_DESKTOP_SITE,
-                BINARY_TOGGLE_WITH_EXCEPTION,
+                BINARY_TOGGLE_WITH_EXCEPTION_AND_INFO_TEXT,
                 rdsEnabled);
         Assert.assertTrue(
                 "SharedPreference USER_ENABLED_DESKTOP_SITE_GLOBAL_SETTING_PREFERENCE_KEY should be"
@@ -2044,7 +2088,10 @@
     @SmallTest
     @Feature({"Preferences"})
     public void testOnlyExpectedPreferencesSensors() {
-        testExpectedPreferences(SiteSettingsCategory.Type.SENSORS, BINARY_TOGGLE, BINARY_TOGGLE);
+        testExpectedPreferences(
+                SiteSettingsCategory.Type.SENSORS,
+                BINARY_TOGGLE_AND_INFO_TEXT,
+                BINARY_TOGGLE_AND_INFO_TEXT);
     }
 
     @Test
@@ -2053,15 +2100,18 @@
     public void testOnlyExpectedPreferencesSound() {
         testExpectedPreferences(
                 SiteSettingsCategory.Type.SOUND,
-                BINARY_TOGGLE_WITH_EXCEPTION,
-                BINARY_TOGGLE_WITH_EXCEPTION);
+                BINARY_TOGGLE_WITH_EXCEPTION_AND_INFO_TEXT,
+                BINARY_TOGGLE_WITH_EXCEPTION_AND_INFO_TEXT);
     }
 
     @Test
     @SmallTest
     @Feature({"Preferences"})
     public void testOnlyExpectedPreferencesUsb() {
-        testExpectedPreferences(SiteSettingsCategory.Type.USB, BINARY_TOGGLE, BINARY_TOGGLE);
+        testExpectedPreferences(
+                SiteSettingsCategory.Type.USB,
+                BINARY_TOGGLE_AND_INFO_TEXT,
+                BINARY_TOGGLE_AND_INFO_TEXT);
     }
 
     @Test
@@ -2069,7 +2119,9 @@
     @Feature({"Preferences"})
     public void testOnlyExpectedPreferencesSerialPort() {
         testExpectedPreferences(
-                SiteSettingsCategory.Type.SERIAL_PORT, BINARY_TOGGLE, BINARY_TOGGLE);
+                SiteSettingsCategory.Type.SERIAL_PORT,
+                BINARY_TOGGLE_AND_INFO_TEXT,
+                BINARY_TOGGLE_AND_INFO_TEXT);
     }
 
     @Test
@@ -2084,7 +2136,9 @@
     @Feature({"Preferences"})
     public void testOnlyExpectedPreferencesVirtualReality() {
         testExpectedPreferences(
-                SiteSettingsCategory.Type.VIRTUAL_REALITY, BINARY_TOGGLE, BINARY_TOGGLE);
+                SiteSettingsCategory.Type.VIRTUAL_REALITY,
+                BINARY_TOGGLE_AND_INFO_TEXT,
+                BINARY_TOGGLE_AND_INFO_TEXT);
     }
 
     /** Tests system NFC support in Preferences. */
@@ -2122,6 +2176,7 @@
                         SiteSettingsCategory.Type.CAMERA,
                         ContentSettingsType.MEDIASTREAM_CAMERA,
                         false)
+                .withExpectedPrefKeysAtStart(SingleCategorySettings.INFO_TEXT_KEY)
                 .run();
 
         // Test that the camera permission doesn't get requested.
@@ -2147,6 +2202,7 @@
                         SiteSettingsCategory.Type.CAMERA,
                         ContentSettingsType.MEDIASTREAM_CAMERA,
                         true)
+                .withExpectedPrefKeysAtStart(SingleCategorySettings.INFO_TEXT_KEY)
                 .run();
 
         initializeUpdateWaiter(/* expectGranted= */ true);
@@ -2170,6 +2226,7 @@
                         SiteSettingsCategory.Type.MICROPHONE,
                         ContentSettingsType.MEDIASTREAM_MIC,
                         false)
+                .withExpectedPrefKeysAtStart(SingleCategorySettings.INFO_TEXT_KEY)
                 .run();
 
         // Test that the microphone permission doesn't get requested.
@@ -2195,6 +2252,7 @@
                         SiteSettingsCategory.Type.MICROPHONE,
                         ContentSettingsType.MEDIASTREAM_MIC,
                         true)
+                .withExpectedPrefKeysAtStart(SingleCategorySettings.INFO_TEXT_KEY)
                 .run();
 
         // Launch a page that uses the microphone and make sure a permission prompt shows up.
@@ -2217,6 +2275,7 @@
                         SiteSettingsCategory.Type.BACKGROUND_SYNC,
                         ContentSettingsType.BACKGROUND_SYNC,
                         true)
+                .withExpectedPrefKeysAtStart(SingleCategorySettings.INFO_TEXT_KEY)
                 .run();
     }
 
@@ -2229,6 +2288,7 @@
                         SiteSettingsCategory.Type.BACKGROUND_SYNC,
                         ContentSettingsType.BACKGROUND_SYNC,
                         false)
+                .withExpectedPrefKeysAtStart(SingleCategorySettings.INFO_TEXT_KEY)
                 .withExpectedPrefKeys(SingleCategorySettings.ADD_EXCEPTION_KEY)
                 .run();
     }
@@ -2243,6 +2303,7 @@
                         SiteSettingsCategory.Type.NOTIFICATIONS,
                         ContentSettingsType.NOTIFICATIONS,
                         true)
+                .withExpectedPrefKeysAtStart(SingleCategorySettings.INFO_TEXT_KEY)
                 .withExpectedPrefKeys(SingleCategorySettings.NOTIFICATIONS_TRI_STATE_PREF_KEY)
                 .run();
     }
@@ -2257,6 +2318,7 @@
                         SiteSettingsCategory.Type.NOTIFICATIONS,
                         ContentSettingsType.NOTIFICATIONS,
                         false)
+                .withExpectedPrefKeysAtStart(SingleCategorySettings.INFO_TEXT_KEY)
                 .run();
     }
 
@@ -2271,6 +2333,7 @@
                         SiteSettingsCategory.Type.DEVICE_LOCATION,
                         ContentSettingsType.GEOLOCATION,
                         true)
+                .withExpectedPrefKeysAtStart(SingleCategorySettings.INFO_TEXT_KEY)
                 .withExpectedPrefKeys(SingleCategorySettings.LOCATION_TRI_STATE_PREF_KEY)
                 .run();
     }
@@ -2285,6 +2348,7 @@
                         SiteSettingsCategory.Type.DEVICE_LOCATION,
                         ContentSettingsType.GEOLOCATION,
                         false)
+                .withExpectedPrefKeysAtStart(SingleCategorySettings.INFO_TEXT_KEY)
                 .run();
     }
 
@@ -2294,6 +2358,7 @@
     public void testAllowUsb() {
         new TwoStatePermissionTestCase(
                         "USB", SiteSettingsCategory.Type.USB, ContentSettingsType.USB_GUARD, true)
+                .withExpectedPrefKeysAtStart(SingleCategorySettings.INFO_TEXT_KEY)
                 .run();
     }
 
@@ -2303,6 +2368,7 @@
     public void testBlockUsb() {
         new TwoStatePermissionTestCase(
                         "USB", SiteSettingsCategory.Type.USB, ContentSettingsType.USB_GUARD, false)
+                .withExpectedPrefKeysAtStart(SingleCategorySettings.INFO_TEXT_KEY)
                 .run();
     }
 
@@ -2315,6 +2381,7 @@
                         SiteSettingsCategory.Type.SERIAL_PORT,
                         ContentSettingsType.SERIAL_GUARD,
                         true)
+                .withExpectedPrefKeysAtStart(SingleCategorySettings.INFO_TEXT_KEY)
                 .run();
     }
 
@@ -2327,6 +2394,7 @@
                         SiteSettingsCategory.Type.SERIAL_PORT,
                         ContentSettingsType.SERIAL_GUARD,
                         false)
+                .withExpectedPrefKeysAtStart(SingleCategorySettings.INFO_TEXT_KEY)
                 .run();
     }
 
@@ -2339,6 +2407,7 @@
                         SiteSettingsCategory.Type.AUTOMATIC_DOWNLOADS,
                         ContentSettingsType.AUTOMATIC_DOWNLOADS,
                         true)
+                .withExpectedPrefKeysAtStart(SingleCategorySettings.INFO_TEXT_KEY)
                 .run();
     }
 
@@ -2351,6 +2420,7 @@
                         SiteSettingsCategory.Type.AUTOMATIC_DOWNLOADS,
                         ContentSettingsType.AUTOMATIC_DOWNLOADS,
                         false)
+                .withExpectedPrefKeysAtStart(SingleCategorySettings.INFO_TEXT_KEY)
                 .withExpectedPrefKeys(SingleCategorySettings.ADD_EXCEPTION_KEY)
                 .run();
     }
@@ -2410,6 +2480,7 @@
         NfcSystemLevelSetting.setNfcSettingForTesting(true);
         new TwoStatePermissionTestCase(
                         "NFC", SiteSettingsCategory.Type.NFC, ContentSettingsType.NFC, true)
+                .withExpectedPrefKeysAtStart(SingleCategorySettings.INFO_TEXT_KEY)
                 .run();
     }
 
@@ -2420,6 +2491,7 @@
         NfcSystemLevelSetting.setNfcSettingForTesting(true);
         new TwoStatePermissionTestCase(
                         "NFC", SiteSettingsCategory.Type.NFC, ContentSettingsType.NFC, false)
+                .withExpectedPrefKeysAtStart(SingleCategorySettings.INFO_TEXT_KEY)
                 .run();
     }
 
@@ -2458,6 +2530,7 @@
                         SiteSettingsCategory.Type.AUGMENTED_REALITY,
                         ContentSettingsType.AR,
                         true)
+                .withExpectedPrefKeysAtStart(SingleCategorySettings.INFO_TEXT_KEY)
                 .run();
     }
 
@@ -2470,6 +2543,7 @@
                         SiteSettingsCategory.Type.AUGMENTED_REALITY,
                         ContentSettingsType.AR,
                         false)
+                .withExpectedPrefKeysAtStart(SingleCategorySettings.INFO_TEXT_KEY)
                 .run();
     }
 
@@ -2482,6 +2556,7 @@
                         SiteSettingsCategory.Type.VIRTUAL_REALITY,
                         ContentSettingsType.VR,
                         true)
+                .withExpectedPrefKeysAtStart(SingleCategorySettings.INFO_TEXT_KEY)
                 .run();
     }
 
@@ -2494,6 +2569,7 @@
                         SiteSettingsCategory.Type.VIRTUAL_REALITY,
                         ContentSettingsType.VR,
                         false)
+                .withExpectedPrefKeysAtStart(SingleCategorySettings.INFO_TEXT_KEY)
                 .run();
     }
 
@@ -2530,6 +2606,7 @@
                         SiteSettingsCategory.Type.IDLE_DETECTION,
                         ContentSettingsType.IDLE_DETECTION,
                         true)
+                .withExpectedPrefKeysAtStart(SingleCategorySettings.INFO_TEXT_KEY)
                 .run();
     }
 
@@ -2542,6 +2619,7 @@
                         SiteSettingsCategory.Type.IDLE_DETECTION,
                         ContentSettingsType.IDLE_DETECTION,
                         false)
+                .withExpectedPrefKeysAtStart(SingleCategorySettings.INFO_TEXT_KEY)
                 .run();
     }
 
@@ -2597,6 +2675,7 @@
                         SiteSettingsCategory.Type.REQUEST_DESKTOP_SITE,
                         ContentSettingsType.REQUEST_DESKTOP_SITE,
                         true)
+                .withExpectedPrefKeysAtStart(SingleCategorySettings.INFO_TEXT_KEY)
                 .withExpectedPrefKeys(SingleCategorySettings.DESKTOP_SITE_WINDOW_TOGGLE_KEY)
                 .withExpectedPrefKeys(SingleCategorySettings.ADD_EXCEPTION_KEY)
                 .run();
@@ -2611,6 +2690,7 @@
                         SiteSettingsCategory.Type.REQUEST_DESKTOP_SITE,
                         ContentSettingsType.REQUEST_DESKTOP_SITE,
                         false)
+                .withExpectedPrefKeysAtStart(SingleCategorySettings.INFO_TEXT_KEY)
                 .withExpectedPrefKeys(SingleCategorySettings.ADD_EXCEPTION_KEY)
                 .run();
     }
@@ -2624,6 +2704,7 @@
                         SiteSettingsCategory.Type.FEDERATED_IDENTITY_API,
                         ContentSettingsType.FEDERATED_IDENTITY_API,
                         true)
+                .withExpectedPrefKeysAtStart(SingleCategorySettings.INFO_TEXT_KEY)
                 .withExpectedPrefKeys(SingleCategorySettings.ADD_EXCEPTION_KEY)
                 .run();
     }
@@ -2637,6 +2718,7 @@
                         SiteSettingsCategory.Type.FEDERATED_IDENTITY_API,
                         ContentSettingsType.FEDERATED_IDENTITY_API,
                         false)
+                .withExpectedPrefKeysAtStart(SingleCategorySettings.INFO_TEXT_KEY)
                 .withExpectedPrefKeys(SingleCategorySettings.ADD_EXCEPTION_KEY)
                 .run();
     }
@@ -2700,18 +2782,32 @@
                             settingsActivity,
                             new String[] {
                                 SingleCategorySettings.INFO_TEXT_KEY,
-                                SingleCategorySettings.BINARY_TOGGLE_KEY,
+                                ChromeFeatureList.isEnabled(
+                                                ChromeFeatureList
+                                                        .PERMISSION_SITE_SETTING_RADIO_BUTTON)
+                                        ? SingleCategorySettings.BINARY_RADIO_BUTTON_KEY
+                                        : SingleCategorySettings.BINARY_TOGGLE_KEY,
                                 SingleCategorySettings.TOGGLE_DISABLE_REASON_KEY,
                                 SingleCategorySettings.ALLOWED_GROUP,
                                 SingleCategorySettings.ADD_EXCEPTION_KEY,
                             });
 
-                    ChromeSwitchPreference binaryToggle =
-                            (ChromeSwitchPreference)
-                                    singleCategorySettings.findPreference(
-                                            SingleCategorySettings.BINARY_TOGGLE_KEY);
-                    Assert.assertFalse(binaryToggle.isChecked());
-                    Assert.assertFalse(binaryToggle.isEnabled());
+                    if (ChromeFeatureList.isEnabled(
+                            ChromeFeatureList.PERMISSION_SITE_SETTING_RADIO_BUTTON)) {
+                        BinaryStatePermissionPreference binaryRadioButton =
+                                (BinaryStatePermissionPreference)
+                                        singleCategorySettings.findPreference(
+                                                SingleCategorySettings.BINARY_RADIO_BUTTON_KEY);
+                        Assert.assertFalse(binaryRadioButton.isChecked());
+                        Assert.assertFalse(binaryRadioButton.isEnabled());
+                    } else {
+                        ChromeSwitchPreference binaryToggle =
+                                (ChromeSwitchPreference)
+                                        singleCategorySettings.findPreference(
+                                                SingleCategorySettings.BINARY_TOGGLE_KEY);
+                        Assert.assertFalse(binaryToggle.isChecked());
+                        Assert.assertFalse(binaryToggle.isEnabled());
+                    }
 
                     Preference toggleDisableReason =
                             singleCategorySettings.findPreference(
@@ -2748,16 +2844,29 @@
                             settingsActivity,
                             new String[] {
                                 SingleCategorySettings.INFO_TEXT_KEY,
-                                SingleCategorySettings.BINARY_TOGGLE_KEY,
+                                ChromeFeatureList.isEnabled(
+                                                ChromeFeatureList
+                                                        .PERMISSION_SITE_SETTING_RADIO_BUTTON)
+                                        ? SingleCategorySettings.BINARY_RADIO_BUTTON_KEY
+                                        : SingleCategorySettings.BINARY_TOGGLE_KEY,
                                 SingleCategorySettings.ADD_EXCEPTION_KEY
                             });
-
-                    ChromeSwitchPreference binaryToggle =
-                            (ChromeSwitchPreference)
-                                    singleCategorySettings.findPreference(
-                                            SingleCategorySettings.BINARY_TOGGLE_KEY);
-                    Assert.assertTrue(binaryToggle.isChecked());
-                    Assert.assertFalse(binaryToggle.isEnabled());
+                    if (ChromeFeatureList.isEnabled(
+                            ChromeFeatureList.PERMISSION_SITE_SETTING_RADIO_BUTTON)) {
+                        BinaryStatePermissionPreference binaryRadioButton =
+                                (BinaryStatePermissionPreference)
+                                        singleCategorySettings.findPreference(
+                                                SingleCategorySettings.BINARY_RADIO_BUTTON_KEY);
+                        Assert.assertTrue(binaryRadioButton.isChecked());
+                        Assert.assertFalse(binaryRadioButton.isEnabled());
+                    } else {
+                        ChromeSwitchPreference binaryToggle =
+                                (ChromeSwitchPreference)
+                                        singleCategorySettings.findPreference(
+                                                SingleCategorySettings.BINARY_TOGGLE_KEY);
+                        Assert.assertTrue(binaryToggle.isChecked());
+                        Assert.assertFalse(binaryToggle.isEnabled());
+                    }
 
                     Preference addExceptionPreference =
                             singleCategorySettings.findPreference(
@@ -3121,9 +3230,20 @@
                     SingleCategorySettings preferences =
                             (SingleCategorySettings) settingsActivity.getMainFragment();
                     // Window setting is only available when the Global Setting is ON.
-                    ChromeSwitchPreference toggle =
-                            preferences.findPreference(SingleCategorySettings.BINARY_TOGGLE_KEY);
-                    preferences.onPreferenceChange(toggle, true);
+
+                    if (ChromeFeatureList.isEnabled(
+                            ChromeFeatureList.PERMISSION_SITE_SETTING_RADIO_BUTTON)) {
+                        BinaryStatePermissionPreference binaryRadioButton =
+                                (BinaryStatePermissionPreference)
+                                        preferences.findPreference(
+                                                SingleCategorySettings.BINARY_RADIO_BUTTON_KEY);
+                        preferences.onPreferenceChange(binaryRadioButton, true);
+                    } else {
+                        ChromeSwitchPreference toggle =
+                                preferences.findPreference(
+                                        SingleCategorySettings.BINARY_TOGGLE_KEY);
+                        preferences.onPreferenceChange(toggle, true);
+                    }
 
                     ChromeBaseCheckBoxPreference windowSettingPref =
                             preferences.findPreference(
@@ -3515,10 +3635,9 @@
         testTwoStateToggleDisabledByPolicy(SiteSettingsCategory.Type.JAVASCRIPT);
         testTwoStateToggleDisabledByPolicy(SiteSettingsCategory.Type.POPUPS);
         testTwoStateToggleDisabledByPolicy(SiteSettingsCategory.Type.DEVICE_LOCATION);
-        testTwoStateToggleDisabledByPolicy(SiteSettingsCategory.Type.JAVASCRIPT_OPTIMIZER);
+        // testTwoStateToggleDisabledByPolicy(SiteSettingsCategory.Type.JAVASCRIPT_OPTIMIZER);
         // TODO(crbug.com/40879457): add a test for sensors once crash in the sensors settings page
-        // is
-        // resolved.
+        // is resolved.
     }
 
     private void testTwoStateToggleDisabledByPolicy(@SiteSettingsCategory.Type int type) {
@@ -3526,12 +3645,23 @@
                 SiteSettingsTestUtils.startSiteSettingsCategory(type);
         SingleCategorySettings singleCategorySettings =
                 (SingleCategorySettings) settingsActivity.getMainFragment();
-        ChromeSwitchPreference binaryToggle =
-                (ChromeSwitchPreference)
-                        singleCategorySettings.findPreference(
-                                SingleCategorySettings.BINARY_TOGGLE_KEY);
 
-        Assert.assertFalse(binaryToggle.isEnabled());
+        if (ChromeFeatureList.isEnabled(ChromeFeatureList.PERMISSION_SITE_SETTING_RADIO_BUTTON)
+                && type != SiteSettingsCategory.Type.ANTI_ABUSE) {
+            BinaryStatePermissionPreference binaryRadioButton =
+                    (BinaryStatePermissionPreference)
+                            singleCategorySettings.findPreference(
+                                    SingleCategorySettings.BINARY_RADIO_BUTTON_KEY);
+
+            Assert.assertFalse(binaryRadioButton.isEnabled());
+        } else {
+            ChromeSwitchPreference binaryToggle =
+                    (ChromeSwitchPreference)
+                            singleCategorySettings.findPreference(
+                                    SingleCategorySettings.BINARY_TOGGLE_KEY);
+
+            Assert.assertFalse(binaryToggle.isEnabled());
+        }
 
         ApplicationTestUtils.finishActivity(settingsActivity);
     }
@@ -3685,7 +3815,6 @@
 
             if (ChromeFeatureList.isEnabled(ChromeFeatureList.PERMISSION_SITE_SETTING_RADIO_BUTTON)
                     && siteSettingsType != SiteSettingsCategory.Type.ANTI_ABUSE) {
-                mExpectedPreferenceKeys.add(SingleCategorySettings.INFO_TEXT_KEY);
                 mExpectedPreferenceKeys.add(SingleCategorySettings.BINARY_RADIO_BUTTON_KEY);
             } else {
                 mExpectedPreferenceKeys.add(SingleCategorySettings.BINARY_TOGGLE_KEY);
@@ -3714,8 +3843,8 @@
                             + mSiteSettingsType
                             + ">.";
 
-            if (ChromeFeatureList.isEnabled(
-                    ChromeFeatureList.PERMISSION_SITE_SETTING_RADIO_BUTTON)) {
+            if (ChromeFeatureList.isEnabled(ChromeFeatureList.PERMISSION_SITE_SETTING_RADIO_BUTTON)
+                    && mSiteSettingsType != SiteSettingsCategory.Type.ANTI_ABUSE) {
                 BinaryStatePermissionPreference radioButton =
                         singleCategorySettings.findPreference(
                                 SingleCategorySettings.BINARY_RADIO_BUTTON_KEY);
@@ -3744,8 +3873,8 @@
 
         /** Verfiy {@link ContentSettingsResources} is set correctly. */
         private void assertToggleTitleAndSummary(SingleCategorySettings singleCategorySettings) {
-            if (ChromeFeatureList.isEnabled(
-                    ChromeFeatureList.PERMISSION_SITE_SETTING_RADIO_BUTTON)) {
+            if (ChromeFeatureList.isEnabled(ChromeFeatureList.PERMISSION_SITE_SETTING_RADIO_BUTTON)
+                    && mSiteSettingsType != SiteSettingsCategory.Type.ANTI_ABUSE) {
                 BinaryStatePermissionPreference radio_button =
                         singleCategorySettings.findPreference(
                                 SingleCategorySettings.BINARY_RADIO_BUTTON_KEY);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/tab/TabImplPWATest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/tab/TabImplPWATest.java
index 23dcecb..df278d56 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/tab/TabImplPWATest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/tab/TabImplPWATest.java
@@ -4,6 +4,7 @@
 
 package org.chromium.chrome.browser.tab;
 
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import android.content.Intent;
@@ -74,5 +75,6 @@
                 CriteriaHelper.DEFAULT_POLLING_INTERVAL);
 
         assertTrue(sCustomTabActivityTestRule.getActivity().getActivityTab().isTabInPWA());
+        assertFalse(sCustomTabActivityTestRule.getActivity().getActivityTab().isTabInBrowser());
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/tab/TabImplTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/tab/TabImplTest.java
index caa073e..6af488e 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/tab/TabImplTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/tab/TabImplTest.java
@@ -95,5 +95,6 @@
                 CriteriaHelper.DEFAULT_POLLING_INTERVAL);
 
         assertFalse(sActivityTestRule.getActivity().getActivityTab().isTabInPWA());
+        assertTrue(sActivityTestRule.getActivity().getActivityTab().isTabInBrowser());
     }
 }
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxSurveyControllerTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxSurveyControllerTest.java
index 8b59fbd..56534ab4 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxSurveyControllerTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxSurveyControllerTest.java
@@ -100,7 +100,7 @@
         when(mUserPrefsJniMock.get(mProfile)).thenReturn(mPrefService);
         when(mPrefService.getBoolean(Pref.FEEDBACK_SURVEYS_ENABLED)).thenReturn(true);
         SurveyClientFactory.setInstanceForTesting(mSurveyClientFactory);
-        doReturn(mSurveyClient).when(mSurveyClientFactory).createClient(any(), any(), any());
+        doReturn(mSurveyClient).when(mSurveyClientFactory).createClient(any(), any(), any(), any());
         IdentityServicesProvider.setInstanceForTests(mIdentityServicesProvider);
         when(IdentityServicesProvider.get().getIdentityManager(mProfile))
                 .thenReturn(mIdentityManager);
diff --git a/chrome/app/settings_strings.grdp b/chrome/app/settings_strings.grdp
index efd87e6f..c64ff4c 100644
--- a/chrome/app/settings_strings.grdp
+++ b/chrome/app/settings_strings.grdp
@@ -4539,9 +4539,6 @@
   </message>
 
   <!-- Experimental settings strings -->
-  <message name="IDS_SETTINGS_AI_PAGE_TITLE" desc="Title of page holding experimental AI settings">
-    Experimental AI
-  </message>
   <message name="IDS_SETTINGS_AI_INNOVATIONS_PAGE_TITLE" desc="This label is on the left Navigation sidebar in Chrome’s settings. It tells the user that they can see settings related to AI (artificial intelligence) in this section. It is a noun phrase that refers to our collection of AI settings. These settings include Help me write, Tab organizer, and more. If possible, translate this so that it closely matches the Navigation tab title at the top of this marketing page: https://www.google.com/chrome/ai-innovations.">
     AI innovations
   </message>
diff --git a/chrome/app/settings_strings_grdp/IDS_SETTINGS_AI_PAGE_TITLE.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_AI_PAGE_TITLE.png.sha1
deleted file mode 100644
index bae1ea43..0000000
--- a/chrome/app/settings_strings_grdp/IDS_SETTINGS_AI_PAGE_TITLE.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-c9438349386cf70f9eac31d193fc42c4e5da1726
\ No newline at end of file
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 30962af..a91bafe 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -1333,7 +1333,6 @@
     "profiles/profile_metrics.cc",
     "profiles/profile_metrics.h",
     "profiles/profiles_state.cc",
-    "profiles/profiles_state.h",
     "profiles/renderer_updater.cc",
     "profiles/renderer_updater.h",
     "profiles/renderer_updater_factory.cc",
@@ -4370,6 +4369,7 @@
       "//chrome/browser/ui/send_tab_to_self",
       "//chrome/browser/ui/views/zoom:impl",
       "//chrome/browser/ui/webui/access_code_cast",
+      "//chrome/browser/ui/webui/app_service_internals",
 
       # TODO(413315837): Remove this dependency when chrome/browser/privacy_sandbox
       # gets modularized.
@@ -5508,6 +5508,7 @@
       "//chrome/browser/ui/webui/ash/emoji",
       "//chrome/browser/ui/webui/ash/enterprise_reporting",
       "//chrome/browser/ui/webui/ash/extended_updates",
+      "//chrome/browser/ui/webui/ash/floating_workspace",
       "//chrome/browser/ui/webui/ash/internet",
       "//chrome/browser/ui/webui/ash/kerberos",
       "//chrome/browser/ui/webui/ash/launcher_internals",
@@ -5999,6 +6000,7 @@
       "//chrome/browser/ui/webui/ash/emoji",
       "//chrome/browser/ui/webui/ash/enterprise_reporting",
       "//chrome/browser/ui/webui/ash/extended_updates",
+      "//chrome/browser/ui/webui/ash/floating_workspace",
       "//chrome/browser/ui/webui/ash/kerberos",
       "//chrome/browser/ui/webui/ash/lobster",
       "//chrome/browser/ui/webui/ash/lock_screen_reauth",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 8abfa53..1a076b2 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -11140,13 +11140,6 @@
      FEATURE_VALUE_TYPE(
          supervised_user::kForceSafeSearchForUnauthenticatedSupervisedUsers)},
 
-    {"supervised-profile-filtering-fallback",
-     flag_descriptions::kSupervisedProfileFilteringFallbackName,
-     flag_descriptions::kSupervisedProfileFilteringFallbackDescription,
-     kOsLinux | kOsMac | kOsWin,
-     FEATURE_VALUE_TYPE(
-         supervised_user::kUncredentialedFilteringFallbackForSupervisedUsers)},
-
     {"supervised-profile-custom-strings",
      flag_descriptions::kSupervisedProfileCustomStringsName,
      flag_descriptions::kSupervisedProfileCustomStringsDescription,
diff --git a/chrome/browser/apps/app_shim/BUILD.gn b/chrome/browser/apps/app_shim/BUILD.gn
index 9bf0d26..b6d8568 100644
--- a/chrome/browser/apps/app_shim/BUILD.gn
+++ b/chrome/browser/apps/app_shim/BUILD.gn
@@ -33,6 +33,7 @@
     "//chrome/browser:browser_process",
     "//chrome/browser/apps/app_service",
     "//chrome/browser/apps/app_service:constants",
+    "//chrome/browser/profiles",
     "//chrome/browser/profiles:profile",
     "//chrome/browser/profiles:profile_util",
     "//chrome/browser/ui:browser_list",
diff --git a/chrome/browser/apps/platform_apps/BUILD.gn b/chrome/browser/apps/platform_apps/BUILD.gn
index f90a3d07..fff7088 100644
--- a/chrome/browser/apps/platform_apps/BUILD.gn
+++ b/chrome/browser/apps/platform_apps/BUILD.gn
@@ -87,7 +87,10 @@
   allow_circular_includes_from = [ "//chrome/browser/extensions" ]
 
   if (is_mac) {
-    deps += [ "//chrome/browser/apps/app_shim" ]
+    deps += [
+      "//chrome/browser/apps/app_shim",
+      "//chrome/browser/profiles",
+    ]
   }
 
   if (is_chromeos) {
diff --git a/chrome/browser/ash/account_manager/BUILD.gn b/chrome/browser/ash/account_manager/BUILD.gn
index d5e4e7c..4197f59 100644
--- a/chrome/browser/ash/account_manager/BUILD.gn
+++ b/chrome/browser/ash/account_manager/BUILD.gn
@@ -48,6 +48,7 @@
     "//chrome/browser/ash/child_accounts",
     "//chrome/browser/ash/login/session",
     "//chrome/browser/ash/net",
+    "//chrome/browser/profiles",
     "//chromeos/ash/components/browser_context_helper",
     "//chromeos/components/mgs",
     "//components/user_manager",
diff --git a/chrome/browser/ash/arc/intent_helper/BUILD.gn b/chrome/browser/ash/arc/intent_helper/BUILD.gn
index 6ef912c..d2ef892 100644
--- a/chrome/browser/ash/arc/intent_helper/BUILD.gn
+++ b/chrome/browser/ash/arc/intent_helper/BUILD.gn
@@ -33,6 +33,7 @@
     "//chrome/browser/ash/arc:arc_util",
     "//chrome/browser/ash/settings",
     "//chrome/browser/ash/system",
+    "//chrome/browser/profiles",
     "//chrome/browser/profiles:profile",
     "//chrome/common",
     "//chromeos/ash/components/network",
diff --git a/chrome/browser/ash/extensions/file_manager/BUILD.gn b/chrome/browser/ash/extensions/file_manager/BUILD.gn
index 0c82e20..4327f1d 100644
--- a/chrome/browser/ash/extensions/file_manager/BUILD.gn
+++ b/chrome/browser/ash/extensions/file_manager/BUILD.gn
@@ -109,6 +109,7 @@
     "//chrome/browser/pdf",
     "//chrome/browser/pdf:pdf_pref_names",
     "//chrome/browser/pdf:pdf_service",
+    "//chrome/browser/profiles",
     "//chrome/browser/profiles:profile",
     "//chrome/browser/ui/ash/login",
     "//chrome/browser/ui/ash/multi_user",
diff --git a/chrome/browser/ash/main_parts/chrome_browser_main_parts_ash.cc b/chrome/browser/ash/main_parts/chrome_browser_main_parts_ash.cc
index 72fe47b..acc5710 100644
--- a/chrome/browser/ash/main_parts/chrome_browser_main_parts_ash.cc
+++ b/chrome/browser/ash/main_parts/chrome_browser_main_parts_ash.cc
@@ -1504,8 +1504,7 @@
 
   // ARCVM defers to Android's LMK to kill apps in low memory situations because
   // memory can't be reclaimed directly to ChromeOS.
-  if (!arc::IsArcVmEnabled() &&
-      base::FeatureList::IsEnabled(arc::kContainerAppKiller)) {
+  if (!arc::IsArcVmEnabled()) {
     arc_container_app_killer_ = std::make_unique<arc::ContainerAppKiller>();
   }
 
diff --git a/chrome/browser/ash/system_web_apps/BUILD.gn b/chrome/browser/ash/system_web_apps/BUILD.gn
index 90be3b8..ac1f7ff 100644
--- a/chrome/browser/ash/system_web_apps/BUILD.gn
+++ b/chrome/browser/ash/system_web_apps/BUILD.gn
@@ -54,6 +54,7 @@
     "//chrome/browser/ash/system_web_apps/apps/personalization_app",
     "//chrome/browser/ash/system_web_apps/apps/recorder_app",
     "//chrome/browser/ash/system_web_apps/apps/vc_background_ui",
+    "//chrome/browser/profiles",
     "//chrome/browser/ui/ash/multi_user",
     "//chrome/browser/web_applications/mojom:mojom_web_apps_enum",
     "//chrome/common:chrome_features",
diff --git a/chrome/browser/background/glic/BUILD.gn b/chrome/browser/background/glic/BUILD.gn
index 92356d4..d07eae2 100644
--- a/chrome/browser/background/glic/BUILD.gn
+++ b/chrome/browser/background/glic/BUILD.gn
@@ -64,6 +64,7 @@
     "//base/test:test_support",
     "//chrome/browser:global_features",
     "//chrome/browser/glic:glic",
+    "//chrome/browser/profiles",
     "//chrome/browser/status_icons:status_icons",
     "//chrome/browser/ui",
     "//chrome/browser/ui:ui_features",
diff --git a/chrome/browser/chrome_browser_interface_binders_webui.cc b/chrome/browser/chrome_browser_interface_binders_webui.cc
index b8cc6d1..d12f445f0 100644
--- a/chrome/browser/chrome_browser_interface_binders_webui.cc
+++ b/chrome/browser/chrome_browser_interface_binders_webui.cc
@@ -256,6 +256,7 @@
 #include "chrome/browser/ui/webui/ash/enterprise_reporting/enterprise_reporting_ui.h"
 #include "chrome/browser/ui/webui/ash/extended_updates/extended_updates.mojom.h"
 #include "chrome/browser/ui/webui/ash/extended_updates/extended_updates_ui.h"
+#include "chrome/browser/ui/webui/ash/floating_workspace/floating_workspace_ui.h"
 #include "chrome/browser/ui/webui/ash/internet/internet_config_dialog.h"
 #include "chrome/browser/ui/webui/ash/internet/internet_detail_dialog.h"
 #include "chrome/browser/ui/webui/ash/launcher_internals/launcher_internals.mojom.h"
@@ -870,7 +871,7 @@
       chromeos::network_config::mojom::CrosNetworkConfig,
       ash::InternetConfigDialogUI, ash::InternetDetailDialogUI, ash::NetworkUI,
       ash::OobeUI, ash::settings::OSSettingsUI, ash::LockScreenNetworkUI,
-      ash::ShimlessRMADialogUI>(map);
+      ash::FloatingWorkspaceUI, ash::ShimlessRMADialogUI>(map);
 
   RegisterWebUIControllerInterfaceBinder<
       chromeos::connectivity::mojom::PasspointService,
diff --git a/chrome/browser/chromeos/office_web_app/BUILD.gn b/chrome/browser/chromeos/office_web_app/BUILD.gn
index b103fe1..011d2da 100644
--- a/chrome/browser/chromeos/office_web_app/BUILD.gn
+++ b/chrome/browser/chromeos/office_web_app/BUILD.gn
@@ -34,6 +34,7 @@
     ":office_web_app",
     "//base/test:test_support",
     "//chrome/browser/web_applications",
+    "//chrome/browser/web_applications:web_app_test",
     "//chrome/browser/web_applications:web_applications_test_support",
     "//chrome/browser/web_applications:web_applications_unit_tests",
     "//chrome/test:test_support",
diff --git a/chrome/browser/chromeos/policy/default_notifications_setting_browsertest.cc b/chrome/browser/chromeos/policy/default_notifications_setting_browsertest.cc
index 7f9b43f..644cb7b 100644
--- a/chrome/browser/chromeos/policy/default_notifications_setting_browsertest.cc
+++ b/chrome/browser/chromeos/policy/default_notifications_setting_browsertest.cc
@@ -9,6 +9,7 @@
 #include "chrome/browser/ui/browser.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/ui_test_utils.h"
+#include "components/permissions/features.h"
 #include "components/policy/policy_constants.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/browser_test_utils.h"
@@ -30,6 +31,11 @@
     : public policy::PolicyTest,
       public testing::WithParamInterface<int> {
  public:
+  DefaultNotificationsSettingBrowserTest() {
+    feature_list_.InitAndEnableFeature(
+        permissions::features::kPermissionSiteSettingsRadioButton);
+  }
+
   void SetUpInProcessBrowserTestFixture() override {
     policy::PolicyTest::SetUpInProcessBrowserTestFixture();
 
@@ -82,9 +88,9 @@
 
   // The UI has 3 radio buttons which are inside several layers of shadow DOM.
   // The buttons are:
-  // (0) Sites can ask to send notifications
-  // (1) Use quieter messaging
-  // (2) Don't allow sites to send notifications
+  // (0) Collapse all requests in the address bar
+  // (1) Collapse unwanted requests (recommended)
+  // (2) Expand all requests
   // Query the checked and disabled state of the radio buttons.
   std::string kGetRadios =
       "let radios = "
@@ -115,9 +121,9 @@
   switch (GetParam()) {
     case 0:
       // Policy not set.
-      EXPECT_TRUE(radios_checked_list[0].GetBool());
-      EXPECT_FALSE(radios_checked_list[1].GetBool());
-      EXPECT_TRUE(radios_checked_list[2].GetBool());
+      EXPECT_FALSE(radios_checked_list[0].GetBool());
+      EXPECT_TRUE(radios_checked_list[1].GetBool());
+      EXPECT_FALSE(radios_checked_list[2].GetBool());
       EXPECT_TRUE(radios_enabled_list[0].GetBool());
       EXPECT_TRUE(radios_enabled_list[1].GetBool());
       EXPECT_TRUE(radios_enabled_list[2].GetBool());
@@ -126,7 +132,7 @@
       // Allow sites to show desktop notifications.
       EXPECT_FALSE(radios_checked_list[0].GetBool());
       EXPECT_FALSE(radios_checked_list[1].GetBool());
-      EXPECT_FALSE(radios_checked_list[2].GetBool());
+      EXPECT_TRUE(radios_checked_list[2].GetBool());
       EXPECT_TRUE(radios_enabled_list[0].GetBool());
       EXPECT_TRUE(radios_enabled_list[1].GetBool());
       EXPECT_TRUE(radios_enabled_list[2].GetBool());
@@ -142,9 +148,9 @@
       break;
     case 3:
       // Ask every time a site wants to show desktop notifications.
-      EXPECT_TRUE(radios_checked_list[0].GetBool());
+      EXPECT_FALSE(radios_checked_list[0].GetBool());
       EXPECT_FALSE(radios_checked_list[1].GetBool());
-      EXPECT_FALSE(radios_checked_list[2].GetBool());
+      EXPECT_TRUE(radios_checked_list[2].GetBool());
       EXPECT_TRUE(radios_enabled_list[0].GetBool());
       EXPECT_TRUE(radios_enabled_list[1].GetBool());
       EXPECT_TRUE(radios_enabled_list[2].GetBool());
diff --git a/chrome/browser/extensions/BUILD.gn b/chrome/browser/extensions/BUILD.gn
index 8d9faa7..286c6eb 100644
--- a/chrome/browser/extensions/BUILD.gn
+++ b/chrome/browser/extensions/BUILD.gn
@@ -438,6 +438,7 @@
     "//chrome/browser/prefs",
     "//chrome/browser/prefs:util",
     "//chrome/browser/preloading:prefs",
+    "//chrome/browser/profiles",
     "//chrome/browser/profiles:profile",
     "//chrome/browser/resources:component_extension_resources",
     "//chrome/browser/safe_browsing",
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 8c892a85..04999c8 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -8408,14 +8408,6 @@
     "before accessing embedded YouTube videos or blocked sites in subframes, "
     "respectively.";
 
-const char kSupervisedProfileFilteringFallbackName[] =
-    "Supervised Profile filtering fallback";
-const char kSupervisedProfileFilteringFallbackDescription[] =
-    "Applies website filters for supervised users in the pending state, if the "
-    "Family Link website filtering setting is set to block explicit sites. "
-    "If the Family Link website filtering setting is set to another value, it "
-    "is applied in the pending regardless of this flag.";
-
 const char kSupervisedProfileCustomStringsName[] =
     "Supervised Profile custom strings";
 const char kSupervisedProfileCustomStringsDescription[] =
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index ed5a062..bcc34ff 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -4979,9 +4979,6 @@
 extern const char kSupervisedProfileSubframeReauthName[];
 extern const char kSupervisedProfileSubframeReauthDescription[];
 
-extern const char kSupervisedProfileFilteringFallbackName[];
-extern const char kSupervisedProfileFilteringFallbackDescription[];
-
 extern const char kSupervisedProfileCustomStringsName[];
 extern const char kSupervisedProfileCustomStringsDescription[];
 
diff --git a/chrome/browser/permissions/permission_settings_page_browsertest.cc b/chrome/browser/permissions/permission_settings_page_browsertest.cc
index 4e8a685..d7b1f5a 100644
--- a/chrome/browser/permissions/permission_settings_page_browsertest.cc
+++ b/chrome/browser/permissions/permission_settings_page_browsertest.cc
@@ -4,6 +4,7 @@
 
 #include "base/test/bind.h"
 #include "base/test/metrics/histogram_tester.h"
+#include "base/test/scoped_feature_list.h"
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
@@ -16,6 +17,7 @@
 #include "components/content_settings/core/common/content_settings.h"
 #include "components/content_settings/core/common/content_settings_types.h"
 #include "components/content_settings/core/common/pref_names.h"
+#include "components/permissions/features.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/browser_test_utils.h"
 #include "net/dns/mock_host_resolver.h"
@@ -35,7 +37,8 @@
     "settings-basic-page",
     "settings-privacy-page",
     "settings-notifications-page",
-    "#notification-ask-radio-button"};
+    "settings-category-default-radio-group",
+    "#enabledRadioOption"};
 
 const WebContentsInteractionTestUtil::DeepQuery kQuietButton{
     "settings-ui",
@@ -67,13 +70,17 @@
     "settings-basic-page",
     "settings-privacy-page",
     "settings-notifications-page",
-    "#notification-block"};
+    "settings-category-default-radio-group",
+    "#disabledRadioOption"};
 
 }  // namespace
 
 class PredictionSettingsPageBrowserTest : public InteractiveBrowserTest {
  public:
-  PredictionSettingsPageBrowserTest() = default;
+  PredictionSettingsPageBrowserTest() {
+    feature_list_.InitAndEnableFeature(
+        permissions::features::kPermissionSiteSettingsRadioButton);
+  }
 
   ~PredictionSettingsPageBrowserTest() override = default;
 
@@ -433,6 +440,9 @@
             }))
         .Build();
   }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
 };
 
 IN_PROC_BROWSER_TEST_F(PredictionSettingsPageBrowserTest,
diff --git a/chrome/browser/policy/BUILD.gn b/chrome/browser/policy/BUILD.gn
index 47dbcc2..97672e9 100644
--- a/chrome/browser/policy/BUILD.gn
+++ b/chrome/browser/policy/BUILD.gn
@@ -310,6 +310,7 @@
       "//chrome/browser/autofill",
       "//chrome/browser/content_settings:content_settings_factory",
       "//chrome/browser/devtools:test_support",
+      "//chrome/browser/profiles",
       "//chrome/browser/profiles:profile",
       "//chrome/browser/task_manager",
       "//components/autofill/content/browser:test_support",
diff --git a/chrome/browser/prefs/BUILD.gn b/chrome/browser/prefs/BUILD.gn
index 53029c0..1233be4f 100644
--- a/chrome/browser/prefs/BUILD.gn
+++ b/chrome/browser/prefs/BUILD.gn
@@ -68,6 +68,7 @@
     "//chrome/browser/permissions",
     "//chrome/browser/preloading:prefs",
     "//chrome/browser/privacy_sandbox",
+    "//chrome/browser/profiles",
     "//chrome/browser/profiles:profile",
     "//chrome/browser/profiles:profile_util",
     "//chrome/browser/search",
diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc
index 44490c7..625fc0d 100644
--- a/chrome/browser/prefs/browser_prefs.cc
+++ b/chrome/browser/prefs/browser_prefs.cc
@@ -1110,6 +1110,14 @@
     "sync.wiped_web_apk_data_for_migration";
 #endif  // BUILDFLAG(IS_ANDROID)
 
+// Deprecated 05/2025.
+inline constexpr char kSyncCacheGuid[] = "sync.cache_guid";
+inline constexpr char kSyncBirthday[] = "sync.birthday";
+inline constexpr char kSyncBagOfChips[] = "sync.bag_of_chips";
+inline constexpr char kSyncLastSyncedTime[] = "sync.last_synced_time";
+inline constexpr char kSyncLastPollTime[] = "sync.last_poll_time";
+inline constexpr char kSyncPollInterval[] = "sync.short_poll_interval";
+
 // Register local state used only for migration (clearing or moving to a new
 // key).
 void RegisterLocalStatePrefsForMigration(PrefRegistrySimple* registry) {
@@ -1567,6 +1575,14 @@
 #if BUILDFLAG(IS_ANDROID)
   registry->RegisterBooleanPref(kWipedWebAPkDataForMigration, false);
 #endif  // BUILDFLAG(IS_ANDROID)
+
+  // Deprecated 05/2025.
+  registry->RegisterStringPref(kSyncCacheGuid, std::string());
+  registry->RegisterStringPref(kSyncBirthday, std::string());
+  registry->RegisterStringPref(kSyncBagOfChips, std::string());
+  registry->RegisterTimePref(kSyncLastSyncedTime, base::Time());
+  registry->RegisterTimePref(kSyncLastPollTime, base::Time());
+  registry->RegisterTimeDeltaPref(kSyncPollInterval, base::TimeDelta());
 }
 
 }  // namespace
@@ -2868,6 +2884,14 @@
   profile_prefs->ClearPref(kWipedWebAPkDataForMigration);
 #endif  // BUILDFLAG(IS_ANDROID)
 
+  // Added 05/2025.
+  profile_prefs->ClearPref(kSyncCacheGuid);
+  profile_prefs->ClearPref(kSyncBirthday);
+  profile_prefs->ClearPref(kSyncBagOfChips);
+  profile_prefs->ClearPref(kSyncLastSyncedTime);
+  profile_prefs->ClearPref(kSyncLastPollTime);
+  profile_prefs->ClearPref(kSyncPollInterval);
+
   // Please don't delete the following line. It is used by PRESUBMIT.py.
   // END_MIGRATE_OBSOLETE_PROFILE_PREFS
 
diff --git a/chrome/browser/profiles/BUILD.gn b/chrome/browser/profiles/BUILD.gn
index e6686e3f..96d8afb 100644
--- a/chrome/browser/profiles/BUILD.gn
+++ b/chrome/browser/profiles/BUILD.gn
@@ -24,6 +24,7 @@
       "profile_android.cc",
       "profile_key_android.cc",
       "profile_key_android.h",
+      "profiles_state.h",
     ]
   }
 
@@ -39,6 +40,7 @@
 
   public_deps = [
     ":profile",
+    "//build:chromeos_buildflags",
     "//components/leveldb_proto",
   ]
 }
@@ -160,6 +162,7 @@
     "//chrome/browser:browser_process",
     "//chrome/browser/autofill",
     "//chrome/browser/prefs",
+    "//chrome/browser/profiles",
     "//chrome/browser/safe_browsing",
     "//chrome/browser/ui:ui_features",
     "//chrome/common",
diff --git a/chrome/browser/profiles/profiles_state_unittest.cc b/chrome/browser/profiles/profiles_state_unittest.cc
index f273c4c..05afa3e 100644
--- a/chrome/browser/profiles/profiles_state_unittest.cc
+++ b/chrome/browser/profiles/profiles_state_unittest.cc
@@ -20,7 +20,6 @@
 #include "components/signin/public/identity_manager/identity_manager.h"
 #include "components/sync_preferences/pref_service_syncable.h"
 #include "content/public/test/browser_task_environment.h"
-#include "profiles_state.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace {
diff --git a/chrome/browser/resources/chromeos/accessibility/accessibility_common/mv3/autoclick/autoclick_test.js b/chrome/browser/resources/chromeos/accessibility/accessibility_common/mv3/autoclick/autoclick_test.js
index 074899a..908fa3a96 100644
--- a/chrome/browser/resources/chromeos/accessibility/accessibility_common/mv3/autoclick/autoclick_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/accessibility_common/mv3/autoclick/autoclick_test.js
@@ -114,36 +114,38 @@
   this.assertSameRect(focusRings[0].rects[0], expected);
 });
 
-AX_TEST_F('AutoclickE2ETest', 'RemovesAndAddsAutoclick', async function() {
-  const root = await this.runWithLoadedTree(
-      'data:text/html;charset=utf-8,<p>Cats rock!</p>');
-  // Turn on screen magnifier so that when we turn off autoclick, the
-  // extension doesn't get unloaded and crash the test.
-  await new Promise(resolve => {
-    chrome.accessibilityFeatures.screenMagnifier.set({value: true}, resolve);
-  });
+AX_TEST_F(
+    'AutoclickE2ETest', 'DISABLED_RemovesAndAddsAutoclick', async function() {
+      const root = await this.runWithLoadedTree(
+          'data:text/html;charset=utf-8,<p>Cats rock!</p>');
+      // Turn on screen magnifier so that when we turn off autoclick, the
+      // extension doesn't get unloaded and crash the test.
+      await new Promise(resolve => {
+        chrome.accessibilityFeatures.screenMagnifier.set(
+            {value: true}, resolve);
+      });
 
-  // Toggle autoclick off and on, ensure it still works and no crashes.
-  await new Promise(resolve => {
-    chrome.accessibilityFeatures.autoclick.set({value: false}, resolve);
-  });
-  await new Promise(resolve => {
-    chrome.accessibilityFeatures.autoclick.set({value: true}, resolve);
-  });
-  const node =
-      root.find({role: RoleType.STATIC_TEXT, attributes: {name: 'Cats rock!'}});
-  await new Promise(resolve => {
-    this.mockAccessibilityPrivate.callOnScrollableBoundsForPointRequested(
-        // Offset slightly into the node to ensure the hittest happens
-        // within the node.
-        node.location.left + 1, node.location.top + 1, resolve);
-  });
-  const expected = node.root.location;
-  const focusRings = this.mockAccessibilityPrivate.getFocusRings();
-  this.assertSameRect(
-      this.mockAccessibilityPrivate.getScrollableBounds(), expected);
-  this.assertSameRect(focusRings[0].rects[0], expected);
-});
+      // Toggle autoclick off and on, ensure it still works and no crashes.
+      await new Promise(resolve => {
+        chrome.accessibilityFeatures.autoclick.set({value: false}, resolve);
+      });
+      await new Promise(resolve => {
+        chrome.accessibilityFeatures.autoclick.set({value: true}, resolve);
+      });
+      const node = root.find(
+          {role: RoleType.STATIC_TEXT, attributes: {name: 'Cats rock!'}});
+      await new Promise(resolve => {
+        this.mockAccessibilityPrivate.callOnScrollableBoundsForPointRequested(
+            // Offset slightly into the node to ensure the hittest happens
+            // within the node.
+            node.location.left + 1, node.location.top + 1, resolve);
+      });
+      const expected = node.root.location;
+      const focusRings = this.mockAccessibilityPrivate.getFocusRings();
+      this.assertSameRect(
+          this.mockAccessibilityPrivate.getScrollableBounds(), expected);
+      this.assertSameRect(focusRings[0].rects[0], expected);
+    });
 
 // TODO(crbug.com/41467584): Add tests for when the scrollable area is scrolled
 // all the way up or down, left or right. Add tests for nested scrollable areas.
diff --git a/chrome/browser/resources/settings/ai_page/ai_page.ts b/chrome/browser/resources/settings/ai_page/ai_page.ts
index 17a342cb..40f0a937 100644
--- a/chrome/browser/resources/settings/ai_page/ai_page.ts
+++ b/chrome/browser/resources/settings/ai_page/ai_page.ts
@@ -34,11 +34,6 @@
 
   static get properties() {
     return {
-      enableAiSettingsPageRefresh_: {
-        type: Boolean,
-        value: () => loadTimeData.getBoolean('enableAiSettingsPageRefresh'),
-      },
-
       showAutofillAiControl_: {
         type: Boolean,
         value: () => loadTimeData.getBoolean('showAutofillAiControl'),
@@ -100,7 +95,6 @@
     };
   }
 
-  declare private enableAiSettingsPageRefresh_: boolean;
   declare private showAutofillAiControl_: boolean;
   declare private showComposeControl_: boolean;
   declare private showCompareControl_: boolean;
@@ -119,7 +113,7 @@
 
   private maybeLogVisibilityMetrics_() {
     // Only record metrics when the user first navigates to the main AI page.
-    if (!this.shouldRecordMetrics_ || !this.enableAiSettingsPageRefresh_ ||
+    if (!this.shouldRecordMetrics_ ||
         Router.getInstance().getCurrentRoute() !== routes.AI) {
       return;
     }
diff --git a/chrome/browser/resources/settings/basic_page/basic_page.html b/chrome/browser/resources/settings/basic_page/basic_page.html
index 4acb454..e539d035 100644
--- a/chrome/browser/resources/settings/basic_page/basic_page.html
+++ b/chrome/browser/resources/settings/basic_page/basic_page.html
@@ -34,15 +34,10 @@
             </settings-people-page>
           </settings-section>
         </template>
-        <template is="dom-if" if="[[showAiInfoCard_(pageVisibility.ai)]]"
-            restamp>
+        <template is="dom-if" if="[[showAiPage_(pageVisibility.ai)]]" restamp>
           <settings-section section="aiInfoCard" nest-under-section="ai">
             <settings-ai-info-card></settings-ai-info-card>
           </settings-section>
-        </template>
-        <template is="dom-if"
-            if="[[showExperimentalAdvancedPage_(pageVisibility.ai)]]"
-            restamp>
 <if expr="enable_glic">
           <template is="dom-if" if="[[showGlicSection_]]" restamp>
             <settings-section page-title="$i18n{glicSectionTitle}"
@@ -52,7 +47,8 @@
           </template>
 </if>
           <template is="dom-if" if="[[showAiPageAiFeatureSection_]]" restamp>
-            <settings-section page-title="[[aiPageTitle_]]" section="ai">
+            <settings-section page-title="$i18n{aiInnovationsPageTitle}"
+                section="ai">
               <settings-ai-page prefs="{{prefs}}"></settings-ai-page>
             </settings-section>
           </template>
diff --git a/chrome/browser/resources/settings/basic_page/basic_page.ts b/chrome/browser/resources/settings/basic_page/basic_page.ts
index 551f181..ba0076c 100644
--- a/chrome/browser/resources/settings/basic_page/basic_page.ts
+++ b/chrome/browser/resources/settings/basic_page/basic_page.ts
@@ -159,21 +159,6 @@
         value: false,
       },
 
-      showAdvancedFeaturesMainControl_: {
-        type: Boolean,
-        value: () => loadTimeData.getBoolean('showAdvancedFeaturesMainControl'),
-      },
-
-      enableAiSettingsPageRefresh_: {
-        type: Boolean,
-        value: () => loadTimeData.getBoolean('enableAiSettingsPageRefresh'),
-      },
-
-      aiPageTitle_: {
-        type: String,
-        computed: 'computeAiPageTitle_(enableAiSettingsPageRefresh_)',
-      },
-
       showAiPageAiFeatureSection_: {
         type: Boolean,
         value: () => loadTimeData.getBoolean('showAiPageAiFeatureSection'),
@@ -205,9 +190,6 @@
   declare private currentRoute_: Route;
   declare private advancedTogglingInProgress_: boolean;
   declare private showBatterySettings_: boolean;
-  declare private showAdvancedFeaturesMainControl_: boolean;
-  declare private enableAiSettingsPageRefresh_: boolean;
-  declare private aiPageTitle_: string;
   declare private showAiPageAiFeatureSection_: boolean;
   // <if expr="enable_glic">
   declare private showGlicSection_: boolean;
@@ -362,20 +344,8 @@
     return this.showPage_(visibility);
   }
 
-  private showAiInfoCard_(visibility?: boolean): boolean {
-    return loadTimeData.getBoolean('enableAiSettingsPageRefresh') &&
-        this.showExperimentalAdvancedPage_(visibility);
-  }
-
-  private showExperimentalAdvancedPage_(visibility?: boolean): boolean {
-    return loadTimeData.getBoolean('showAdvancedFeaturesMainControl') &&
-        this.showPage_(visibility);
-  }
-
-  private computeAiPageTitle_(): string {
-    return loadTimeData.getString(
-        this.enableAiSettingsPageRefresh_ ? 'aiInnovationsPageTitle' :
-                                            'aiPageTitle');
+  private showAiPage_(visibility?: boolean): boolean {
+    return loadTimeData.getBoolean('showAiPage') && this.showPage_(visibility);
   }
 
   // <if expr="_google_chrome">
diff --git a/chrome/browser/resources/settings/icons.html b/chrome/browser/resources/settings/icons.html
index d3f71e64..43a3884 100644
--- a/chrome/browser/resources/settings/icons.html
+++ b/chrome/browser/resources/settings/icons.html
@@ -7,7 +7,6 @@
   <svg>
     <defs>
       <g id="account-box"><path d="M4.5 14.3958C5.27778 13.7569 6.13889 13.2847 7.08333 12.9792C8.02778 12.6597 9 12.5 10 12.5C11 12.5 11.9722 12.6528 12.9167 12.9583C13.8611 13.2639 14.7222 13.7431 15.5 14.3958V4.5H4.5V14.3958ZM10 11.5C10.8333 11.5 11.5417 11.2083 12.125 10.625C12.7083 10.0417 13 9.33333 13 8.5C13 7.66667 12.7083 6.95833 12.125 6.375C11.5417 5.79167 10.8333 5.5 10 5.5C9.16667 5.5 8.45833 5.79167 7.875 6.375C7.29167 6.95833 7 7.66667 7 8.5C7 9.33333 7.29167 10.0417 7.875 10.625C8.45833 11.2083 9.16667 11.5 10 11.5ZM4.5 17C4.08333 17 3.72917 16.8542 3.4375 16.5625C3.14583 16.2708 3 15.9167 3 15.5V4.5C3 4.08333 3.14583 3.72917 3.4375 3.4375C3.72917 3.14583 4.08333 3 4.5 3H15.5C15.9167 3 16.2708 3.14583 16.5625 3.4375C16.8542 3.72917 17 4.08333 17 4.5V15.5C17 15.9167 16.8542 16.2708 16.5625 16.5625C16.2708 16.8542 15.9167 17 15.5 17H4.5ZM5.52083 15.5H14.4792C13.8403 15.0139 13.1389 14.6458 12.375 14.3958C11.6111 14.1319 10.8194 14 10 14C9.18056 14 8.38889 14.1319 7.625 14.3958C6.875 14.6458 6.17361 15.0139 5.52083 15.5ZM10 10C9.58333 10 9.22917 9.85417 8.9375 9.5625C8.64583 9.27083 8.5 8.91667 8.5 8.5C8.5 8.08333 8.64583 7.72917 8.9375 7.4375C9.22917 7.14583 9.58333 7 10 7C10.4167 7 10.7708 7.14583 11.0625 7.4375C11.3542 7.72917 11.5 8.08333 11.5 8.5C11.5 8.91667 11.3542 9.27083 11.0625 9.5625C10.7708 9.85417 10.4167 10 10 10Z"></path></g>
-      <g id="ai"><path d="M10 19.166c0-1.278-.242-2.463-.725-3.554a9.106 9.106 0 0 0-1.964-2.923 9.105 9.105 0 0 0-2.923-1.964C3.296 10.24 2.112 10 .833 10c1.279 0 2.463-.234 3.555-.702A9.432 9.432 0 0 0 7.31 7.311a9.105 9.105 0 0 0 1.964-2.923A8.785 8.785 0 0 0 10 .833c0 1.263.234 2.448.701 3.555a9.432 9.432 0 0 0 4.91 4.91 9.037 9.037 0 0 0 3.555.702 8.785 8.785 0 0 0-3.554.725 9.105 9.105 0 0 0-2.923 1.964 9.433 9.433 0 0 0-1.988 2.923c-.467 1.091-.701 2.276-.701 3.554z"></path></g>
       <g id="account-circle"><path fill-rule="evenodd" clip-rule="evenodd" d="M10 1.667A8.336 8.336 0 0 0 1.667 10c0 4.6 3.733 8.333 8.333 8.333S18.333 14.6 18.333 10 14.6 1.667 10 1.667zM5.89 15.233c.359-.75 2.542-1.483 4.109-1.483 1.566 0 3.758.733 4.108 1.483A6.578 6.578 0 0 1 10 16.667a6.577 6.577 0 0 1-4.109-1.434zM10 12.083c1.216 0 4.108.492 5.3 1.942A6.625 6.625 0 0 0 16.666 10 6.676 6.676 0 0 0 10 3.333 6.676 6.676 0 0 0 3.333 10c0 1.517.517 2.908 1.367 4.025 1.191-1.45 4.083-1.942 5.3-1.942zM10 5a2.91 2.91 0 0 0-2.917 2.917A2.91 2.91 0 0 0 10 10.833a2.91 2.91 0 0 0 2.916-2.916A2.91 2.91 0 0 0 10 5zM8.75 7.917c0 .691.558 1.25 1.25 1.25.691 0 1.25-.559 1.25-1.25 0-.692-.559-1.25-1.25-1.25-.692 0-1.25.558-1.25 1.25z"></path></g>
       <g id="account-circle-filled" viewBox="0 -960 960 960"><path d="M237-285q54-38 115.5-56.5T480-360q66 0 127.5 18.5T723-285q35-41 52-91t17-104q0-129.67-91.23-220.84-91.23-91.16-221-91.16Q350-792 259-700.84 168-609.67 168-480q0 54 17 104t52 91Zm243-123q-60 0-102-42t-42-102q0-60 42-102t102-42q60 0 102 42t42 102q0 60-42 102t-102 42Zm.28 312Q401-96 331-126t-122.5-82.5Q156-261 126-330.96t-30-149.5Q96-560 126-629.5q30-69.5 82.5-122T330.96-834q69.96-30 149.5-30t149.04 30q69.5 30 122 82.5T834-629.28q30 69.73 30 149Q864-401 834-331t-82.5 122.5Q699-156 629.28-126q-69.73 30-149 30Z"></path></g>
       <g id="archive"><path d="M4.5 17C4.08333 17 3.72917 16.8542 3.4375 16.5625C3.14583 16.2708 3 15.9167 3 15.5V5.625C3 5.43055 3.03472 5.24306 3.10417 5.0625C3.1875 4.86805 3.29861 4.70139 3.4375 4.5625L4.5625 3.4375C4.70139 3.29861 4.86111 3.19444 5.04167 3.125C5.23611 3.04167 5.43056 3 5.625 3H14.375C14.5694 3 14.7569 3.04167 14.9375 3.125C15.1319 3.19444 15.2986 3.29861 15.4375 3.4375L16.5625 4.5625C16.7014 4.70139 16.8056 4.86805 16.875 5.0625C16.9583 5.24306 17 5.43055 17 5.625V15.5C17 15.9167 16.8542 16.2708 16.5625 16.5625C16.2708 16.8542 15.9167 17 15.5 17H4.5ZM4.625 5.5H15.375L14.375 4.5H5.625L4.625 5.5ZM4.5 7V15.5H15.5V7H4.5ZM10 14.25L13 11.25L11.9375 10.1875L10.75 11.375V8.25H9.25V11.375L8.0625 10.1875L7 11.25L10 14.25ZM4.5 15.5H15.5H4.5Z"></path></g>
diff --git a/chrome/browser/resources/settings/privacy_page/privacy_guide/privacy_guide_completion_fragment.ts b/chrome/browser/resources/settings/privacy_page/privacy_guide/privacy_guide_completion_fragment.ts
index 61f929d..f8624614 100644
--- a/chrome/browser/resources/settings/privacy_page/privacy_guide/privacy_guide_completion_fragment.ts
+++ b/chrome/browser/resources/settings/privacy_page/privacy_guide/privacy_guide_completion_fragment.ts
@@ -61,7 +61,7 @@
         type: Boolean,
         value: () =>
             loadTimeData.getBoolean('enableAiSettingsInPrivacyGuide') &&
-            loadTimeData.getBoolean('showAdvancedFeaturesMainControl'),
+            loadTimeData.getBoolean('showAiPage'),
       },
 
       shouldShowPrivacySandbox_: {
diff --git a/chrome/browser/resources/settings/route.ts b/chrome/browser/resources/settings/route.ts
index bb635e9d..f778077 100644
--- a/chrome/browser/resources/settings/route.ts
+++ b/chrome/browser/resources/settings/route.ts
@@ -199,32 +199,29 @@
 
   const visibility = pageVisibility || {};
 
-  if (visibility.ai !== false &&
-      loadTimeData.getBoolean('showAdvancedFeaturesMainControl')) {
+  if (visibility.ai !== false && loadTimeData.getBoolean('showAiPage')) {
     r.AI = r.BASIC.createSection(
-        '/ai', 'ai', loadTimeData.getString('aiPageTitle'));
-    if (loadTimeData.getBoolean('enableAiSettingsPageRefresh')) {
-      if (loadTimeData.getBoolean('showTabOrganizationControl')) {
-        r.AI_TAB_ORGANIZATION = r.AI.createChild('/ai/tabOrganizer');
-      }
-      if (loadTimeData.getBoolean('showHistorySearchControl')) {
-        r.HISTORY_SEARCH = r.AI.createChild('/ai/historySearch');
-      }
-      if (loadTimeData.getBoolean('showComposeControl')) {
-        r.OFFER_WRITING_HELP = r.AI.createChild('/ai/helpMeWrite');
-      }
-      if (loadTimeData.getBoolean('showCompareControl')) {
-        r.COMPARE = r.AI.createChild('/ai/compareProducts');
-      }
-      // <if expr="enable_glic">
-      if (loadTimeData.getBoolean('showGlicSettings')) {
-        r.GLIC_SECTION = r.AI.createSection(
-            '/ai/glicSection', 'glicSection',
-            loadTimeData.getString('glicPageTitle'));
-        r.GEMINI = r.GLIC_SECTION.createChild('/ai/gemini');
-      }
-      // </if>
+        '/ai', 'ai', loadTimeData.getString('aiInnovationsPageTitle'));
+    if (loadTimeData.getBoolean('showTabOrganizationControl')) {
+      r.AI_TAB_ORGANIZATION = r.AI.createChild('/ai/tabOrganizer');
     }
+    if (loadTimeData.getBoolean('showHistorySearchControl')) {
+      r.HISTORY_SEARCH = r.AI.createChild('/ai/historySearch');
+    }
+    if (loadTimeData.getBoolean('showComposeControl')) {
+      r.OFFER_WRITING_HELP = r.AI.createChild('/ai/helpMeWrite');
+    }
+    if (loadTimeData.getBoolean('showCompareControl')) {
+      r.COMPARE = r.AI.createChild('/ai/compareProducts');
+    }
+    // <if expr="enable_glic">
+    if (loadTimeData.getBoolean('showGlicSettings')) {
+      r.GLIC_SECTION = r.AI.createSection(
+          '/ai/glicSection', 'glicSection',
+          loadTimeData.getString('glicPageTitle'));
+      r.GEMINI = r.GLIC_SECTION.createChild('/ai/gemini');
+    }
+    // </if>
   }
 
   // <if expr="not chromeos_ash">
diff --git a/chrome/browser/resources/settings/settings_menu/settings_menu.html b/chrome/browser/resources/settings/settings_menu/settings_menu.html
index 79aad70..80c5ee1 100644
--- a/chrome/browser/resources/settings/settings_menu/settings_menu.html
+++ b/chrome/browser/resources/settings/settings_menu/settings_menu.html
@@ -79,12 +79,11 @@
           <cr-ripple></cr-ripple>
         </a>
         <a role="menuitem" href="/ai"
-            hidden="[[!showExperimentalMenuItem_(
-                showAdvancedFeaturesMainControl_, pageVisibility.ai)]]"
+            hidden="[[!showAiPageMenuItem_(showAiPage_, pageVisibility.ai)]]"
             on-click="onAiPageClick_"
             class="cr-nav-menu-item">
-          <cr-icon icon="[[aiPageIcon_]]"></cr-icon>
-          [[aiPageTitle_]]
+          <cr-icon icon="settings20:magic"></cr-icon>
+          $i18n{aiInnovationsPageTitle}
           <cr-ripple></cr-ripple>
         </a>
         <a role="menuitem" id="appearance" href="/appearance"
diff --git a/chrome/browser/resources/settings/settings_menu/settings_menu.ts b/chrome/browser/resources/settings/settings_menu/settings_menu.ts
index 25283992..026cd545 100644
--- a/chrome/browser/resources/settings/settings_menu/settings_menu.ts
+++ b/chrome/browser/resources/settings/settings_menu/settings_menu.ts
@@ -56,52 +56,23 @@
        */
       pageVisibility: Object,
 
-      enableAiSettingsPageRefresh_: {
+      showAiPage_: {
         type: Boolean,
-        value: () => loadTimeData.getBoolean('enableAiSettingsPageRefresh'),
-      },
-
-      showAdvancedFeaturesMainControl_: {
-        type: Boolean,
-        value: () => loadTimeData.getBoolean('showAdvancedFeaturesMainControl'),
-      },
-
-      aiPageIcon_: {
-        type: String,
-        computed: 'computeAiPageIcon_(enableAiSettingsPageRefresh_)',
-      },
-
-      aiPageTitle_: {
-        type: String,
-        computed: 'computeAiPageTitle_(enableAiSettingsPageRefresh_)',
+        value: () => loadTimeData.getBoolean('showAiPage'),
       },
     };
   }
 
   declare pageVisibility?: PageVisibility;
-  declare private enableAiSettingsPageRefresh_: boolean;
-  declare private showAdvancedFeaturesMainControl_: boolean;
-  declare private aiPageIcon_: string;
-  declare private aiPageTitle_: string;
+  declare private showAiPage_: boolean;
   private metricsBrowserProxy_: MetricsBrowserProxy =
       MetricsBrowserProxyImpl.getInstance();
 
-  private showExperimentalMenuItem_(): boolean {
-    return this.showAdvancedFeaturesMainControl_ &&
+  private showAiPageMenuItem_(): boolean {
+    return this.showAiPage_ &&
         (!this.pageVisibility || this.pageVisibility.ai !== false);
   }
 
-  private computeAiPageIcon_(): string {
-    return this.enableAiSettingsPageRefresh_ ? 'settings20:magic' :
-                                               'settings20:ai';
-  }
-
-  private computeAiPageTitle_(): string {
-    return loadTimeData.getString(
-        this.enableAiSettingsPageRefresh_ ? 'aiInnovationsPageTitle' :
-                                            'aiPageTitle');
-  }
-
   override currentRouteChanged(newRoute: Route) {
     // Focus the initially selected path.
     const anchors = this.shadowRoot!.querySelectorAll('a');
@@ -161,10 +132,8 @@
   }
 
   private onAiPageClick_() {
-    if (this.enableAiSettingsPageRefresh_) {
-      this.metricsBrowserProxy_.recordAction(
-          'SettingsMenu_AiPageEntryPointClicked');
-    }
+    this.metricsBrowserProxy_.recordAction(
+        'SettingsMenu_AiPageEntryPointClicked');
   }
 }
 
diff --git a/chrome/browser/safe_browsing/BUILD.gn b/chrome/browser/safe_browsing/BUILD.gn
index 4e2ad1d..e4954f6 100644
--- a/chrome/browser/safe_browsing/BUILD.gn
+++ b/chrome/browser/safe_browsing/BUILD.gn
@@ -218,6 +218,7 @@
       "//chrome/browser/download",
       "//chrome/browser/enterprise/connectors/analysis:features",
       "//chrome/browser/permissions:permissions_proto",
+      "//chrome/browser/profiles",
       "//chrome/browser/profiles:profile",
       "//chrome/browser/ui/safety_hub",
       "//chrome/common/safe_browsing:archive_analyzer_results",
diff --git a/chrome/browser/safety_hub/android/java/src/org/chromium/chrome/browser/safety_hub/SafetyHubHatsHelper.java b/chrome/browser/safety_hub/android/java/src/org/chromium/chrome/browser/safety_hub/SafetyHubHatsHelper.java
index 9640294..74bc87b 100644
--- a/chrome/browser/safety_hub/android/java/src/org/chromium/chrome/browser/safety_hub/SafetyHubHatsHelper.java
+++ b/chrome/browser/safety_hub/android/java/src/org/chromium/chrome/browser/safety_hub/SafetyHubHatsHelper.java
@@ -98,7 +98,11 @@
         SurveyConfig config = SurveyConfig.get(mProfile, SENTIMENT_ORGANIC_SURVEY_TRIGGER);
         SurveyClient surveyClient =
                 SurveyClientFactory.getInstance()
-                        .createClient(config, mSafetyHubSurveyUiDelegate, mProfile);
+                        .createClient(
+                                config,
+                                mSafetyHubSurveyUiDelegate,
+                                mProfile,
+                                mCurrentTabModelSelector);
         if (surveyClient == null) {
             Log.d(TAG, "SurveyClient is null. config: " + SurveyConfig.toString(config));
             return;
diff --git a/chrome/browser/safety_hub/android/junit/src/org/chromium/chrome/browser/safety_hub/SafetyHubHatsHelperUnitTest.java b/chrome/browser/safety_hub/android/junit/src/org/chromium/chrome/browser/safety_hub/SafetyHubHatsHelperUnitTest.java
index a24f430..a4279df 100644
--- a/chrome/browser/safety_hub/android/junit/src/org/chromium/chrome/browser/safety_hub/SafetyHubHatsHelperUnitTest.java
+++ b/chrome/browser/safety_hub/android/junit/src/org/chromium/chrome/browser/safety_hub/SafetyHubHatsHelperUnitTest.java
@@ -100,7 +100,7 @@
         TestSurveyUtils.setTestSurveyConfigForTrigger(
                 HATS_SURVEY_TRIGGER_ID, new String[0], new String[0]);
         SurveyClientFactory.setInstanceForTesting(mSurveyFactory);
-        doReturn(mSurveyClient).when(mSurveyFactory).createClient(any(), any(), any());
+        doReturn(mSurveyClient).when(mSurveyFactory).createClient(any(), any(), any(), any());
     }
 
     private void mockPasswordCounts(int compromised, int weak, int reused) {
diff --git a/chrome/browser/search/background/ntp_custom_background_service.cc b/chrome/browser/search/background/ntp_custom_background_service.cc
index fbb9bf3..5f56cb626 100644
--- a/chrome/browser/search/background/ntp_custom_background_service.cc
+++ b/chrome/browser/search/background/ntp_custom_background_service.cc
@@ -149,11 +149,11 @@
 void NtpCustomBackgroundService::RegisterProfilePrefs(
     PrefRegistrySimple* registry) {
   registry->RegisterDictionaryPref(
-      prefs::kNtpCustomBackgroundDictDoNotUse, NtpCustomBackgroundDefaults(),
+      prefs::kDeprecatedNtpCustomBackgroundDictDoNotUse,
+      NtpCustomBackgroundDefaults(),
       user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
-  registry->RegisterDictionaryPref(
-      prefs::kNonSyncingNtpCustomBackgroundDictDoNotUse,
-      NtpCustomBackgroundDefaults());
+  registry->RegisterDictionaryPref(prefs::kNtpCustomBackgroundDict,
+                                   NtpCustomBackgroundDefaults());
   registry->RegisterBooleanPref(prefs::kNtpCustomBackgroundLocalToDevice,
                                 false);
   registry->RegisterStringPref(prefs::kNtpCustomBackgroundLocalToDeviceId, "");
@@ -171,8 +171,7 @@
 void NtpCustomBackgroundService::ResetNtpTheme(Profile* profile) {
   auto* pref_service = profile->GetPrefs();
   RemoveLocalBackgroundImageCopy(profile);
-  pref_service->ClearPref(GetThemePrefNameInMigration(
-      ThemePrefInMigration::kNtpCustomBackgroundDict));
+  pref_service->ClearPref(prefs::kNtpCustomBackgroundDict);
   pref_service->SetBoolean(prefs::kNtpCustomBackgroundLocalToDevice, false);
   pref_service->ClearPref(prefs::kNtpCustomBackgroundLocalToDeviceId);
   pref_service->SetBoolean(prefs::kNtpCustomBackgroundInspiration, false);
@@ -205,8 +204,7 @@
   // Update theme info when the pref is changed via Sync.
   pref_change_registrar_.Init(pref_service_);
   pref_change_registrar_.Add(
-      GetThemePrefNameInMigration(
-          ThemePrefInMigration::kNtpCustomBackgroundDict),
+      prefs::kNtpCustomBackgroundDict,
       base::BindRepeating(&NtpCustomBackgroundService::UpdateBackgroundFromSync,
                           weak_ptr_factory_.GetWeakPtr()));
 
@@ -240,8 +238,7 @@
       image.collection_id, resume_token, timestamp);
 
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  pref_service_->SetDict(GetThemePrefNameInMigration(
-                             ThemePrefInMigration::kNtpCustomBackgroundDict),
+  pref_service_->SetDict(prefs::kNtpCustomBackgroundDict,
                          std::move(background_info));
 }
 
@@ -291,9 +288,7 @@
 
   bool need_forced_refresh =
       pref_service_->GetBoolean(prefs::kNtpCustomBackgroundLocalToDevice) &&
-      pref_service_
-          ->FindPreference(GetThemePrefNameInMigration(
-              ThemePrefInMigration::kNtpCustomBackgroundDict))
+      pref_service_->FindPreference(prefs::kNtpCustomBackgroundDict)
           ->IsDefaultValue();
   RemoveLocalBackgroundImageCopy(profile_);
   pref_service_->SetBoolean(prefs::kNtpCustomBackgroundLocalToDevice, false);
@@ -312,12 +307,10 @@
     base::Value::Dict background_info = GetBackgroundInfoAsDict(
         background_url, attribution_line_1, attribution_line_2, action_url,
         collection_id, std::nullopt, std::nullopt);
-    pref_service_->SetDict(GetThemePrefNameInMigration(
-                               ThemePrefInMigration::kNtpCustomBackgroundDict),
+    pref_service_->SetDict(prefs::kNtpCustomBackgroundDict,
                            std::move(background_info));
   } else {
-    pref_service_->ClearPref(GetThemePrefNameInMigration(
-        ThemePrefInMigration::kNtpCustomBackgroundDict));
+    pref_service_->ClearPref(prefs::kNtpCustomBackgroundDict);
 
     // If this device was using a local image and did not have a non-local
     // background saved, UpdateBackgroundFromSync will not fire. Therefore, we
@@ -385,8 +378,7 @@
   }
 
   const base::Value::Dict& background_info =
-      pref_service_->GetDict(GetThemePrefNameInMigration(
-          ThemePrefInMigration::kNtpCustomBackgroundDict));
+      pref_service_->GetDict(prefs::kNtpCustomBackgroundDict);
   int64_t refresh_timestamp = 0;
   const base::Value* timestamp_value =
       background_info.Find(kNtpCustomBackgroundRefreshTimestamp);
@@ -438,8 +430,7 @@
   if (IsCustomBackgroundPrefValid()) {
     auto custom_background = std::make_optional<CustomBackground>();
     const base::Value::Dict& background_info =
-        pref_service_->GetDict(GetThemePrefNameInMigration(
-            ThemePrefInMigration::kNtpCustomBackgroundDict));
+        pref_service_->GetDict(prefs::kNtpCustomBackgroundDict);
     GURL custom_background_url(
         background_info.Find(kNtpCustomBackgroundURL)->GetString());
 
@@ -522,13 +513,10 @@
   // |prefs::kNtpCustomBackgroundDict| is managed by policy only if
   // |policy::key::kNTPCustomBackgroundEnabled| is set to false and therefore
   // should be empty.
-  bool managed = pref_service_->IsManagedPreference(GetThemePrefNameInMigration(
-      ThemePrefInMigration::kNtpCustomBackgroundDict));
+  bool managed =
+      pref_service_->IsManagedPreference(prefs::kNtpCustomBackgroundDict);
   if (managed) {
-    DCHECK(pref_service_
-               ->GetDict(GetThemePrefNameInMigration(
-                   ThemePrefInMigration::kNtpCustomBackgroundDict))
-               .empty());
+    DCHECK(pref_service_->GetDict(prefs::kNtpCustomBackgroundDict).empty());
   }
   return managed;
 }
@@ -622,8 +610,7 @@
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
   const base::Value::Dict& background_info =
-      pref_service_->GetDict(GetThemePrefNameInMigration(
-          ThemePrefInMigration::kNtpCustomBackgroundDict));
+      pref_service_->GetDict(prefs::kNtpCustomBackgroundDict);
   std::string collection_id =
       background_info.Find(kNtpCustomBackgroundCollectionId)->GetString();
   std::string resume_token =
@@ -634,8 +621,7 @@
 bool NtpCustomBackgroundService::IsCustomBackgroundPrefValid() {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   const base::Value::Dict& background_info =
-      pref_service_->GetDict(GetThemePrefNameInMigration(
-          ThemePrefInMigration::kNtpCustomBackgroundDict));
+      pref_service_->GetDict(prefs::kNtpCustomBackgroundDict);
 
   const base::Value* background_url =
       background_info.Find(kNtpCustomBackgroundURL);
@@ -655,14 +641,12 @@
     SkColor color) {
   // Update background color only if the selected background is still the same.
   const base::Value::Dict& background_info =
-      pref_service_->GetDict(GetThemePrefNameInMigration(
-          ThemePrefInMigration::kNtpCustomBackgroundDict));
+      pref_service_->GetDict(prefs::kNtpCustomBackgroundDict);
 
   GURL current_bg_url(
       background_info.Find(kNtpCustomBackgroundURL)->GetString());
   if (current_bg_url == image_url) {
-    pref_service_->SetDict(GetThemePrefNameInMigration(
-                               ThemePrefInMigration::kNtpCustomBackgroundDict),
+    pref_service_->SetDict(prefs::kNtpCustomBackgroundDict,
                            GetBackgroundInfoWithColor(&background_info, color));
     theme_service_->SetUserColorAndBrowserColorVariant(
         color, ui::mojom::BrowserColorVariant::kTonalSpot);
diff --git a/chrome/browser/search/background/ntp_custom_background_service_unittest.cc b/chrome/browser/search/background/ntp_custom_background_service_unittest.cc
index 0ba3c12..98c0082 100644
--- a/chrome/browser/search/background/ntp_custom_background_service_unittest.cc
+++ b/chrome/browser/search/background/ntp_custom_background_service_unittest.cc
@@ -327,19 +327,15 @@
 
   sync_preferences::TestingPrefServiceSyncable* pref_service =
       profile().GetTestingPrefService();
-  pref_service->SetUserPref(
-      std::string(GetThemePrefNameInMigration(
-          ThemePrefInMigration::kNtpCustomBackgroundDict)),
-      GetBackgroundInfoAsDict(kUrlFoo, GURL()));
+  pref_service->SetUserPref(prefs::kNtpCustomBackgroundDict,
+                            GetBackgroundInfoAsDict(kUrlFoo, GURL()));
 
   auto custom_background = custom_background_service_->GetCustomBackground();
   EXPECT_EQ(kUrlFoo, custom_background->custom_background_url);
   EXPECT_TRUE(custom_background_service_->IsCustomBackgroundSet());
 
-  pref_service->SetUserPref(
-      std::string(GetThemePrefNameInMigration(
-          ThemePrefInMigration::kNtpCustomBackgroundDict)),
-      GetBackgroundInfoAsDict(kUrlBar, GURL()));
+  pref_service->SetUserPref(prefs::kNtpCustomBackgroundDict,
+                            GetBackgroundInfoAsDict(kUrlBar, GURL()));
 
   custom_background = custom_background_service_->GetCustomBackground();
   EXPECT_EQ(kUrlBar, custom_background->custom_background_url);
@@ -398,10 +394,8 @@
   EXPECT_TRUE(base::PathExists(path));
 
   // Update custom_background info via Sync.
-  pref_service->SetUserPref(
-      std::string(GetThemePrefNameInMigration(
-          ThemePrefInMigration::kNtpCustomBackgroundDict)),
-      GetBackgroundInfoAsDict(kUrl, GURL()));
+  pref_service->SetUserPref(prefs::kNtpCustomBackgroundDict,
+                            GetBackgroundInfoAsDict(kUrl, GURL()));
   task_environment_.RunUntilIdle();
 
   auto custom_background = custom_background_service_->GetCustomBackground();
diff --git a/chrome/browser/search/ntp_custom_background_enabled_policy_handler.cc b/chrome/browser/search/ntp_custom_background_enabled_policy_handler.cc
index 5a73a0d..efa58fe 100644
--- a/chrome/browser/search/ntp_custom_background_enabled_policy_handler.cc
+++ b/chrome/browser/search/ntp_custom_background_enabled_policy_handler.cc
@@ -25,12 +25,7 @@
   const base::Value* value =
       policies.GetValue(policy_name(), base::Value::Type::BOOLEAN);
   if (value && !value->GetBool()) {
-    // NOTE: Using GetThemePrefNameInMigration is invalid since FeatureList is
-    // not initialized yet (see crbug.com/361121492). As a workaround, both the
-    // prefs are written to instead.
-    prefs->SetValue(prefs::kNonSyncingNtpCustomBackgroundDictDoNotUse,
-                    base::Value(base::Value::Type::DICT));
-    prefs->SetValue(prefs::kNtpCustomBackgroundDictDoNotUse,
+    prefs->SetValue(prefs::kNtpCustomBackgroundDict,
                     base::Value(base::Value::Type::DICT));
   }
 }
diff --git a/chrome/browser/search/ntp_custom_background_enabled_policy_handler_browsertest.cc b/chrome/browser/search/ntp_custom_background_enabled_policy_handler_browsertest.cc
index 9df1518..b54c28b 100644
--- a/chrome/browser/search/ntp_custom_background_enabled_policy_handler_browsertest.cc
+++ b/chrome/browser/search/ntp_custom_background_enabled_policy_handler_browsertest.cc
@@ -43,12 +43,8 @@
   PrefService* prefs = browser()->profile()->GetPrefs();
 
   // Check initial states.
-  EXPECT_FALSE(prefs
-                   ->GetDict(GetThemePrefNameInMigration(
-                       ThemePrefInMigration::kNtpCustomBackgroundDict))
-                   .empty());
-  EXPECT_FALSE(prefs->IsManagedPreference(GetThemePrefNameInMigration(
-      ThemePrefInMigration::kNtpCustomBackgroundDict)));
+  EXPECT_FALSE(prefs->GetDict(prefs::kNtpCustomBackgroundDict).empty());
+  EXPECT_FALSE(prefs->IsManagedPreference(prefs::kNtpCustomBackgroundDict));
 
   // Check if updated policy is reflected.
   policy::PolicyMap policies;
@@ -57,12 +53,8 @@
                policy::POLICY_SOURCE_CLOUD, base::Value(false), nullptr);
   policy_provider_.UpdateChromePolicy(policies);
 
-  EXPECT_TRUE(prefs
-                  ->GetDict(GetThemePrefNameInMigration(
-                      ThemePrefInMigration::kNtpCustomBackgroundDict))
-                  .empty());
-  EXPECT_TRUE(prefs->IsManagedPreference(GetThemePrefNameInMigration(
-      ThemePrefInMigration::kNtpCustomBackgroundDict)));
+  EXPECT_TRUE(prefs->GetDict(prefs::kNtpCustomBackgroundDict).empty());
+  EXPECT_TRUE(prefs->IsManagedPreference(prefs::kNtpCustomBackgroundDict));
 
   // Flip the value, and check again.
   policies.Set(policy::key::kNTPCustomBackgroundEnabled,
@@ -70,19 +62,11 @@
                policy::POLICY_SOURCE_CLOUD, base::Value(true), nullptr);
   policy_provider_.UpdateChromePolicy(policies);
 
-  EXPECT_FALSE(prefs
-                   ->GetDict(GetThemePrefNameInMigration(
-                       ThemePrefInMigration::kNtpCustomBackgroundDict))
-                   .empty());
-  EXPECT_FALSE(prefs->IsManagedPreference(GetThemePrefNameInMigration(
-      ThemePrefInMigration::kNtpCustomBackgroundDict)));
+  EXPECT_FALSE(prefs->GetDict(prefs::kNtpCustomBackgroundDict).empty());
+  EXPECT_FALSE(prefs->IsManagedPreference(prefs::kNtpCustomBackgroundDict));
 
   policy_provider_.UpdateChromePolicy(policy::PolicyMap());
 
-  EXPECT_FALSE(prefs
-                   ->GetDict(GetThemePrefNameInMigration(
-                       ThemePrefInMigration::kNtpCustomBackgroundDict))
-                   .empty());
-  EXPECT_FALSE(prefs->IsManagedPreference(GetThemePrefNameInMigration(
-      ThemePrefInMigration::kNtpCustomBackgroundDict)));
+  EXPECT_FALSE(prefs->GetDict(prefs::kNtpCustomBackgroundDict).empty());
+  EXPECT_FALSE(prefs->IsManagedPreference(prefs::kNtpCustomBackgroundDict));
 }
diff --git a/chrome/browser/search_engine_choice/BUILD.gn b/chrome/browser/search_engine_choice/BUILD.gn
index 5b516a5..7e0adb42 100644
--- a/chrome/browser/search_engine_choice/BUILD.gn
+++ b/chrome/browser/search_engine_choice/BUILD.gn
@@ -42,6 +42,7 @@
   deps = [
     ":search_engine_choice",
     "//chrome/browser:browser_process",
+    "//chrome/browser/profiles",
     "//chrome/browser/regional_capabilities",
     "//chrome/browser/search_engines",
     "//components/regional_capabilities",
diff --git a/chrome/browser/signin/BUILD.gn b/chrome/browser/signin/BUILD.gn
index 09e26030..e081316 100644
--- a/chrome/browser/signin/BUILD.gn
+++ b/chrome/browser/signin/BUILD.gn
@@ -55,6 +55,7 @@
       "//base:i18n",
       "//chrome/browser:browser_process",
       "//chrome/browser/new_tab_page/chrome_colors:generate_chrome_colors_info",
+      "//chrome/browser/profiles",
       "//chrome/browser/profiles:profile_util",
       "//chrome/browser/search_engine_choice",
       "//chrome/browser/search_engines",
diff --git a/chrome/browser/supervised_user/supervised_user_pending_state_navigation_browsertest.cc b/chrome/browser/supervised_user/supervised_user_pending_state_navigation_browsertest.cc
index c7888ef..2ed9e2a4 100644
--- a/chrome/browser/supervised_user/supervised_user_pending_state_navigation_browsertest.cc
+++ b/chrome/browser/supervised_user/supervised_user_pending_state_navigation_browsertest.cc
@@ -11,7 +11,6 @@
 #include "base/functional/bind.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/run_until.h"
-#include "base/test/scoped_feature_list.h"
 #include "build/buildflag.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/signin/identity_manager_factory.h"
@@ -40,7 +39,6 @@
 #include "components/strings/grit/components_strings.h"
 #include "components/supervised_user/core/browser/child_account_service.h"
 #include "components/supervised_user/core/browser/supervised_user_service.h"
-#include "components/supervised_user/core/common/features.h"
 #include "components/ukm/test_ukm_recorder.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/test/browser_test.h"
@@ -238,8 +236,6 @@
 
  private:
   std::unique_ptr<ukm::TestAutoSetUkmRecorder> ukm_recorder_;
-  base::test::ScopedFeatureList scoped_feature_list_{
-      supervised_user::kUncredentialedFilteringFallbackForSupervisedUsers};
 };
 
 // Tests the blocked site main frame re-authentication interstitial.
diff --git a/chrome/browser/supervised_user/url_filter_interactive_uitest.cc b/chrome/browser/supervised_user/url_filter_interactive_uitest.cc
index 06587d13..5be1e8405 100644
--- a/chrome/browser/supervised_user/url_filter_interactive_uitest.cc
+++ b/chrome/browser/supervised_user/url_filter_interactive_uitest.cc
@@ -52,8 +52,7 @@
 #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
     scoped_feature_list_.InitWithFeatures(
         /*enabled_features=*/
-        {supervised_user::kUncredentialedFilteringFallbackForSupervisedUsers,
-         supervised_user::kLocalWebApprovals},
+        {supervised_user::kLocalWebApprovals},
         /*disabled_features=*/{});
 #endif // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
   }
diff --git a/chrome/browser/sync/BUILD.gn b/chrome/browser/sync/BUILD.gn
index 169aefc..769186e 100644
--- a/chrome/browser/sync/BUILD.gn
+++ b/chrome/browser/sync/BUILD.gn
@@ -87,6 +87,7 @@
     "//chrome/browser/autofill",
     "//chrome/browser/favicon",
     "//chrome/browser/prefs:util",
+    "//chrome/browser/profiles",
     "//chrome/browser/promos:utils",
     "//chrome/browser/reading_list",
     "//chrome/browser/search_engines",
diff --git a/chrome/browser/sync/prefs/chrome_syncable_prefs_database.cc b/chrome/browser/sync/prefs/chrome_syncable_prefs_database.cc
index e2e9991..b4df334 100644
--- a/chrome/browser/sync/prefs/chrome_syncable_prefs_database.cc
+++ b/chrome/browser/sync/prefs/chrome_syncable_prefs_database.cc
@@ -518,7 +518,7 @@
      {syncable_prefs_ids::kHomePageIsNewTabPage, syncer::PREFERENCES,
       sync_preferences::PrefSensitivity::kNone,
       sync_preferences::MergeBehavior::kNone}},
-    {prefs::kNtpCustomBackgroundDictDoNotUse,
+    {prefs::kDeprecatedNtpCustomBackgroundDictDoNotUse,
      {syncable_prefs_ids::kNtpCustomBackgroundDict, syncer::PREFERENCES,
       sync_preferences::PrefSensitivity::kNone,
       sync_preferences::MergeBehavior::kNone}},
diff --git a/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/Tab.java b/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/Tab.java
index 707116b..da9b7b8 100644
--- a/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/Tab.java
+++ b/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/Tab.java
@@ -341,6 +341,11 @@
     boolean isTabInPWA();
 
     /**
+     * @return true if the {@link Tab} is in a Browser.
+     */
+    boolean isTabInBrowser();
+
+    /**
      * @return the last time this tab was shown or the time of its initialization if it wasn't yet
      *     shown.
      */
diff --git a/chrome/browser/themes/theme_local_data_batch_uploader_unittest.cc b/chrome/browser/themes/theme_local_data_batch_uploader_unittest.cc
index e01ba5c..123491d 100644
--- a/chrome/browser/themes/theme_local_data_batch_uploader_unittest.cc
+++ b/chrome/browser/themes/theme_local_data_batch_uploader_unittest.cc
@@ -579,7 +579,7 @@
                static_cast<int>(1234567890))
           .Set(kNtpCustomBackgroundMainColor, static_cast<int>(SK_ColorRED));
 
-  profile()->GetPrefs()->Set(prefs::kNonSyncingNtpCustomBackgroundDictDoNotUse,
+  profile()->GetPrefs()->Set(prefs::kNtpCustomBackgroundDict,
                              base::Value(background_dict.Clone()));
 
   const sync_pb::ThemeSpecifics local_theme_specifics =
@@ -598,16 +598,14 @@
                   /*item_count=*/0u, /*domains=*/IsEmpty(),
                   /*domain_count=*/0u));
 
-  EXPECT_NE(profile()->GetPrefs()->GetDict(
-                prefs::kNonSyncingNtpCustomBackgroundDictDoNotUse),
+  EXPECT_NE(profile()->GetPrefs()->GetDict(prefs::kNtpCustomBackgroundDict),
             background_dict);
   EXPECT_THAT(
       theme_sync_service()->GetThemeSpecificsFromCurrentThemeForTesting(),
       EqualsProto(remote_theme_specifics));
 
   TriggerLocalDataMigration();
-  EXPECT_EQ(profile()->GetPrefs()->GetDict(
-                prefs::kNonSyncingNtpCustomBackgroundDictDoNotUse),
+  EXPECT_EQ(profile()->GetPrefs()->GetDict(prefs::kNtpCustomBackgroundDict),
             background_dict);
   EXPECT_THAT(
       theme_sync_service()->GetThemeSpecificsFromCurrentThemeForTesting(),
@@ -620,8 +618,8 @@
   EXPECT_THAT(GetLocalDataDescription(), IsEmptyLocalDataDescription());
 
   theme_sync_service()->StopSyncing(syncer::THEMES);
-  EXPECT_FALSE(profile()->GetPrefs()->GetUserPrefValue(
-      prefs::kNonSyncingNtpCustomBackgroundDictDoNotUse));
+  EXPECT_FALSE(
+      profile()->GetPrefs()->GetUserPrefValue(prefs::kNtpCustomBackgroundDict));
   EXPECT_TRUE(theme_service()->UsingDefaultTheme());
 }
 
diff --git a/chrome/browser/themes/theme_service_unittest.cc b/chrome/browser/themes/theme_service_unittest.cc
index ca83848..cbd810b 100644
--- a/chrome/browser/themes/theme_service_unittest.cc
+++ b/chrome/browser/themes/theme_service_unittest.cc
@@ -589,21 +589,18 @@
   NtpCustomBackgroundServiceFactory::GetForProfile(profile_.get());
   base::Value::Dict test_background_info;
   test_background_info.Set("test_data", "foo");
-  pref_service_->SetDict(GetThemePrefNameInMigration(
-                             ThemePrefInMigration::kNtpCustomBackgroundDict),
+  pref_service_->SetDict(prefs::kNtpCustomBackgroundDict,
                          std::move(test_background_info));
 
   const base::Value::Dict& background_info_with_theme =
-      pref_service_->GetDict(GetThemePrefNameInMigration(
-          ThemePrefInMigration::kNtpCustomBackgroundDict));
+      pref_service_->GetDict(prefs::kNtpCustomBackgroundDict);
   const base::Value* test_data = background_info_with_theme.Find("test_data");
   EXPECT_NE(test_data, nullptr);
   EXPECT_NE(test_data->GetIfString(), nullptr);
 
   theme_service_->UseDefaultTheme();
   const base::Value::Dict& background_info_without_theme =
-      pref_service_->GetDict(GetThemePrefNameInMigration(
-          ThemePrefInMigration::kNtpCustomBackgroundDict));
+      pref_service_->GetDict(prefs::kNtpCustomBackgroundDict);
   EXPECT_EQ(background_info_without_theme.Find("test_data"), nullptr);
 }
 
@@ -798,8 +795,7 @@
   // Set an autogenerated theme with background prefs.
   base::Value::Dict background_info;
   background_info.Set("test_data", "foo");
-  pref_service_->SetDict(GetThemePrefNameInMigration(
-                             ThemePrefInMigration::kNtpCustomBackgroundDict),
+  pref_service_->SetDict(prefs::kNtpCustomBackgroundDict,
                          std::move(background_info));
   theme_service_->BuildAutogeneratedThemeFromColor(SK_ColorBLUE);
   EXPECT_EQ(theme_service_->GetAutogeneratedThemeColor(), SK_ColorBLUE);
@@ -808,9 +804,7 @@
   theme_service_->SetUserColor(SK_ColorGREEN);
   EXPECT_EQ(theme_service_->GetUserColor(), SK_ColorGREEN);
   EXPECT_EQ(theme_service_->GetAutogeneratedThemeColor(), SK_ColorTRANSPARENT);
-  EXPECT_EQ(*pref_service_
-                 ->GetDict(GetThemePrefNameInMigration(
-                     ThemePrefInMigration::kNtpCustomBackgroundDict))
+  EXPECT_EQ(*pref_service_->GetDict(prefs::kNtpCustomBackgroundDict)
                  .FindString("test_data"),
             "foo");
   EXPECT_EQ(ThemeService::kUserColorThemeID, theme_service_->GetThemeID());
@@ -841,8 +835,7 @@
   // Set an autogenerated theme with background prefs.
   base::Value::Dict background_info;
   background_info.Set("test_data", "foo");
-  pref_service_->SetDict(GetThemePrefNameInMigration(
-                             ThemePrefInMigration::kNtpCustomBackgroundDict),
+  pref_service_->SetDict(prefs::kNtpCustomBackgroundDict,
                          std::move(background_info));
   theme_service_->BuildAutogeneratedThemeFromColor(SK_ColorBLUE);
   EXPECT_EQ(theme_service_->GetAutogeneratedThemeColor(), SK_ColorBLUE);
@@ -851,9 +844,7 @@
   theme_service_->SetIsGrayscale(true);
   EXPECT_TRUE(theme_service_->GetIsGrayscale());
   EXPECT_EQ(theme_service_->GetAutogeneratedThemeColor(), SK_ColorTRANSPARENT);
-  EXPECT_EQ(*pref_service_
-                 ->GetDict(GetThemePrefNameInMigration(
-                     ThemePrefInMigration::kNtpCustomBackgroundDict))
+  EXPECT_EQ(*pref_service_->GetDict(prefs::kNtpCustomBackgroundDict)
                  .FindString("test_data"),
             "foo");
 }
@@ -876,8 +867,7 @@
   // Set an autogenerated theme with background prefs.
   base::Value::Dict background_info;
   background_info.Set("test_data", "foo");
-  pref_service_->SetDict(GetThemePrefNameInMigration(
-                             ThemePrefInMigration::kNtpCustomBackgroundDict),
+  pref_service_->SetDict(prefs::kNtpCustomBackgroundDict,
                          std::move(background_info));
   theme_service_->BuildAutogeneratedThemeFromColor(SK_ColorBLUE);
   EXPECT_EQ(theme_service_->GetAutogeneratedThemeColor(), SK_ColorBLUE);
@@ -890,9 +880,7 @@
   EXPECT_EQ(theme_service_->GetBrowserColorVariant(),
             ui::mojom::BrowserColorVariant::kTonalSpot);
   EXPECT_EQ(theme_service_->GetAutogeneratedThemeColor(), SK_ColorTRANSPARENT);
-  EXPECT_EQ(*pref_service_
-                 ->GetDict(GetThemePrefNameInMigration(
-                     ThemePrefInMigration::kNtpCustomBackgroundDict))
+  EXPECT_EQ(*pref_service_->GetDict(prefs::kNtpCustomBackgroundDict)
                  .FindString("test_data"),
             "foo");
   EXPECT_EQ(ThemeService::kUserColorThemeID, theme_service_->GetThemeID());
diff --git a/chrome/browser/themes/theme_syncable_service.cc b/chrome/browser/themes/theme_syncable_service.cc
index b17b713d6a..ef836d06 100644
--- a/chrome/browser/themes/theme_syncable_service.cc
+++ b/chrome/browser/themes/theme_syncable_service.cc
@@ -65,8 +65,8 @@
          {prefs::kDeprecatedGrayscaleThemeEnabledDoNotUse,
           prefs::kGrayscaleThemeEnabled}},
         {ThemePrefInMigration::kNtpCustomBackgroundDict,
-         {prefs::kNtpCustomBackgroundDictDoNotUse,
-          prefs::kNonSyncingNtpCustomBackgroundDictDoNotUse}},
+         {prefs::kDeprecatedNtpCustomBackgroundDictDoNotUse,
+          prefs::kNtpCustomBackgroundDict}},
     });
 
 static_assert(
@@ -276,7 +276,7 @@
   // ThemeService doesn't convey ntp background change notifications.
   pref_change_registrar_.Init(prefs);
   pref_change_registrar_.Add(
-      prefs::kNonSyncingNtpCustomBackgroundDictDoNotUse,
+      prefs::kNtpCustomBackgroundDict,
       base::BindRepeating(&ThemeSyncableService::OnThemeChanged,
                           base::Unretained(this)));
 
@@ -627,15 +627,14 @@
     DVLOG(1) << "Applying custom NTP background";
     // TODO(crbug.com/356148174): Set via NtpCustomBackgroundService instead
     // of setting the pref directly.
-    prefs->SetDict(prefs::kNonSyncingNtpCustomBackgroundDictDoNotUse,
-                   std::move(*dict));
+    prefs->SetDict(prefs::kNtpCustomBackgroundDict, std::move(*dict));
   } else if (has_all_theme_attributes) {
     // Clear the current ntp background if none received from remote.
     // NOTE: Ntp background is only cleared if the incoming ThemeSpecifics
     // is the new one and is missing the ntp_background field because it was
     // committed by an old client.
     DVLOG(1) << "Removing custom NTP background";
-    prefs->ClearPref(prefs::kNonSyncingNtpCustomBackgroundDictDoNotUse);
+    prefs->ClearPref(prefs::kNtpCustomBackgroundDict);
   }
   return ThemeSyncState::kApplied;
 }
@@ -702,8 +701,8 @@
   if (!prefs->GetBoolean(prefs::kNtpCustomBackgroundLocalToDevice)) {
     // Fetch ntp background dict from pref.
     // TODO(crbug.com/356148174): Query NtpCustomBackgroundService instead.
-    if (const base::Value* pref = prefs->GetUserPrefValue(
-            prefs::kNonSyncingNtpCustomBackgroundDictDoNotUse)) {
+    if (const base::Value* pref =
+            prefs->GetUserPrefValue(prefs::kNtpCustomBackgroundDict)) {
       *theme_specifics.mutable_ntp_background() =
           SpecificsNtpBackgroundFromDict(pref->GetDict());
     }
diff --git a/chrome/browser/themes/theme_syncable_service_unittest.cc b/chrome/browser/themes/theme_syncable_service_unittest.cc
index 498cf05..f671582 100644
--- a/chrome/browser/themes/theme_syncable_service_unittest.cc
+++ b/chrome/browser/themes/theme_syncable_service_unittest.cc
@@ -1262,8 +1262,8 @@
           .Set(kNtpCustomBackgroundRefreshTimestamp,
                static_cast<int>(1234567890))
           .Set(kNtpCustomBackgroundMainColor, static_cast<int>(SK_ColorRED));
-  const base::Value* value = profile()->GetPrefs()->GetUserPrefValue(
-      prefs::kNonSyncingNtpCustomBackgroundDictDoNotUse);
+  const base::Value* value =
+      profile()->GetPrefs()->GetUserPrefValue(prefs::kNtpCustomBackgroundDict);
   ASSERT_TRUE(value);
   EXPECT_EQ(*value, expected_value);
 }
@@ -1290,7 +1290,7 @@
                static_cast<int>(1234567890))
           .Set(kNtpCustomBackgroundMainColor, static_cast<int>(SK_ColorRED));
 
-  profile()->GetPrefs()->Set(prefs::kNonSyncingNtpCustomBackgroundDictDoNotUse,
+  profile()->GetPrefs()->Set(prefs::kNtpCustomBackgroundDict,
                              base::Value(new_value.Clone()));
 
   const syncer::SyncChangeList& changes = fake_change_processor()->changes();
@@ -1308,7 +1308,7 @@
 
   // Verify that the old pref is updated.
   EXPECT_THAT(profile()->GetPrefs()->GetUserPrefValue(
-                  prefs::kNtpCustomBackgroundDictDoNotUse),
+                  prefs::kDeprecatedNtpCustomBackgroundDictDoNotUse),
               DictionaryValuePtrHas(kNtpCustomBackgroundURL, kTestUrl));
 }
 
@@ -1327,7 +1327,7 @@
                static_cast<int>(1234567890))
           .Set(kNtpCustomBackgroundMainColor, static_cast<int>(SK_ColorRED));
 
-  profile()->GetPrefs()->Set(prefs::kNonSyncingNtpCustomBackgroundDictDoNotUse,
+  profile()->GetPrefs()->Set(prefs::kNtpCustomBackgroundDict,
                              base::Value(new_value.Clone()));
 
   // Mark ntp background set from local resource.
@@ -1351,7 +1351,7 @@
 
   // Verify that the old pref is not updated.
   EXPECT_FALSE(profile()->GetPrefs()->GetUserPrefValue(
-      prefs::kNtpCustomBackgroundDictDoNotUse));
+      prefs::kDeprecatedNtpCustomBackgroundDictDoNotUse));
 }
 
 TEST_F(RealThemeSyncableServiceTest, ShouldApplyRemoteNtpBackgroundChange) {
@@ -1363,8 +1363,8 @@
               fake_change_processor()));
   ASSERT_FALSE(error.has_value()) << error.value().message();
 
-  EXPECT_FALSE(profile()->GetPrefs()->GetUserPrefValue(
-      prefs::kNonSyncingNtpCustomBackgroundDictDoNotUse));
+  EXPECT_FALSE(
+      profile()->GetPrefs()->GetUserPrefValue(prefs::kNtpCustomBackgroundDict));
 
   // Process change with background set.
   {
@@ -1375,17 +1375,16 @@
         FROM_HERE, MakeThemeChangeList(theme_specifics)));
   }
 
-  EXPECT_THAT(profile()->GetPrefs()->GetUserPrefValue(
-                  prefs::kNonSyncingNtpCustomBackgroundDictDoNotUse),
-              DictionaryValuePtrHas(kNtpCustomBackgroundURL, kTestUrl));
+  EXPECT_THAT(
+      profile()->GetPrefs()->GetUserPrefValue(prefs::kNtpCustomBackgroundDict),
+      DictionaryValuePtrHas(kNtpCustomBackgroundURL, kTestUrl));
 }
 
 TEST_F(RealThemeSyncableServiceTest,
        ShouldNotApplyEmptyRemoteNtpBackgroundChange) {
   {
-    ScopedDictPrefUpdate dict(
-        profile()->GetPrefs(),
-        prefs::kNonSyncingNtpCustomBackgroundDictDoNotUse);
+    ScopedDictPrefUpdate dict(profile()->GetPrefs(),
+                              prefs::kNtpCustomBackgroundDict);
     dict->Set(kNtpCustomBackgroundURL, kTestUrl);
   }
 
@@ -1397,9 +1396,9 @@
               fake_change_processor()));
   ASSERT_FALSE(error.has_value()) << error.value().message();
 
-  EXPECT_THAT(profile()->GetPrefs()->GetUserPrefValue(
-                  prefs::kNonSyncingNtpCustomBackgroundDictDoNotUse),
-              DictionaryValuePtrHas(kNtpCustomBackgroundURL, kTestUrl));
+  EXPECT_THAT(
+      profile()->GetPrefs()->GetUserPrefValue(prefs::kNtpCustomBackgroundDict),
+      DictionaryValuePtrHas(kNtpCustomBackgroundURL, kTestUrl));
 
   // Process change with empty background.
   {
@@ -1410,16 +1409,15 @@
   }
 
   // Removed as the default theme is applied.
-  EXPECT_FALSE(profile()->GetPrefs()->GetUserPrefValue(
-      prefs::kNonSyncingNtpCustomBackgroundDictDoNotUse));
+  EXPECT_FALSE(
+      profile()->GetPrefs()->GetUserPrefValue(prefs::kNtpCustomBackgroundDict));
 }
 
 TEST_F(RealThemeSyncableServiceTest,
        ShouldNotApplyMissingRemoteNtpBackgroundChange) {
   {
-    ScopedDictPrefUpdate dict(
-        profile()->GetPrefs(),
-        prefs::kNonSyncingNtpCustomBackgroundDictDoNotUse);
+    ScopedDictPrefUpdate dict(profile()->GetPrefs(),
+                              prefs::kNtpCustomBackgroundDict);
     dict->Set(kNtpCustomBackgroundURL, kTestUrl);
   }
 
@@ -1431,9 +1429,9 @@
               fake_change_processor()));
   ASSERT_FALSE(error.has_value()) << error.value().message();
 
-  EXPECT_THAT(profile()->GetPrefs()->GetUserPrefValue(
-                  prefs::kNonSyncingNtpCustomBackgroundDictDoNotUse),
-              DictionaryValuePtrHas(kNtpCustomBackgroundURL, kTestUrl));
+  EXPECT_THAT(
+      profile()->GetPrefs()->GetUserPrefValue(prefs::kNtpCustomBackgroundDict),
+      DictionaryValuePtrHas(kNtpCustomBackgroundURL, kTestUrl));
 
   // Process change with background not set.
   {
@@ -1445,8 +1443,8 @@
   }
 
   // Removed as the default theme is applied.
-  EXPECT_FALSE(profile()->GetPrefs()->GetUserPrefValue(
-      prefs::kNonSyncingNtpCustomBackgroundDictDoNotUse));
+  EXPECT_FALSE(
+      profile()->GetPrefs()->GetUserPrefValue(prefs::kNtpCustomBackgroundDict));
 }
 
 TEST_F(RealThemeSyncableServiceTest,
@@ -1825,9 +1823,9 @@
   EXPECT_EQ(theme_service()->GetUserColor(), SK_ColorRED);
   EXPECT_EQ(theme_service()->GetBrowserColorVariant(),
             ui::mojom::BrowserColorVariant::kTonalSpot);
-  EXPECT_THAT(profile()->GetPrefs()->GetUserPrefValue(
-                  prefs::kNonSyncingNtpCustomBackgroundDictDoNotUse),
-              DictionaryValuePtrHas(kNtpCustomBackgroundURL, kTestUrl));
+  EXPECT_THAT(
+      profile()->GetPrefs()->GetUserPrefValue(prefs::kNtpCustomBackgroundDict),
+      DictionaryValuePtrHas(kNtpCustomBackgroundURL, kTestUrl));
 }
 
 TEST_F(RealThemeSyncableServiceTest, ShouldApplyNtpBackgroundWithGrayscale) {
@@ -1852,9 +1850,9 @@
   EXPECT_EQ(theme_service()->GetBrowserColorVariant(),
             ui::mojom::BrowserColorVariant::kSystem);
   EXPECT_TRUE(theme_service()->GetIsGrayscale());
-  EXPECT_THAT(profile()->GetPrefs()->GetUserPrefValue(
-                  prefs::kNonSyncingNtpCustomBackgroundDictDoNotUse),
-              DictionaryValuePtrHas(kNtpCustomBackgroundURL, kTestUrl));
+  EXPECT_THAT(
+      profile()->GetPrefs()->GetUserPrefValue(prefs::kNtpCustomBackgroundDict),
+      DictionaryValuePtrHas(kNtpCustomBackgroundURL, kTestUrl));
 }
 
 TEST_F(RealThemeSyncableServiceTest,
@@ -1881,9 +1879,9 @@
   EXPECT_EQ(theme_service()->GetBrowserColorVariant(),
             ui::mojom::BrowserColorVariant::kSystem);
   EXPECT_FALSE(theme_service()->GetIsGrayscale());
-  EXPECT_THAT(profile()->GetPrefs()->GetUserPrefValue(
-                  prefs::kNonSyncingNtpCustomBackgroundDictDoNotUse),
-              DictionaryValuePtrHas(kNtpCustomBackgroundURL, kTestUrl));
+  EXPECT_THAT(
+      profile()->GetPrefs()->GetUserPrefValue(prefs::kNtpCustomBackgroundDict),
+      DictionaryValuePtrHas(kNtpCustomBackgroundURL, kTestUrl));
 }
 
 TEST_F(RealThemeSyncableServiceTest,
@@ -1991,8 +1989,8 @@
       prefs::kDeprecatedBrowserColorVariantDoNotUse));
   ASSERT_FALSE(profile()->GetPrefs()->GetUserPrefValue(
       prefs::kDeprecatedGrayscaleThemeEnabledDoNotUse));
-  ASSERT_FALSE(profile()->GetPrefs()->GetUserPrefValue(
-      prefs::kNonSyncingNtpCustomBackgroundDictDoNotUse));
+  ASSERT_FALSE(
+      profile()->GetPrefs()->GetUserPrefValue(prefs::kNtpCustomBackgroundDict));
 
   // Set user color theme.
   theme_service()->SetUserColorAndBrowserColorVariant(
@@ -2012,8 +2010,8 @@
   // Other prefs are cleared.
   EXPECT_FALSE(profile()->GetPrefs()->GetUserPrefValue(
       prefs::kDeprecatedGrayscaleThemeEnabledDoNotUse));
-  EXPECT_FALSE(profile()->GetPrefs()->GetUserPrefValue(
-      prefs::kNonSyncingNtpCustomBackgroundDictDoNotUse));
+  EXPECT_FALSE(
+      profile()->GetPrefs()->GetUserPrefValue(prefs::kNtpCustomBackgroundDict));
 
   // Set grayscale theme.
   theme_service()->SetIsGrayscale(true);
@@ -2028,19 +2026,18 @@
       prefs::kDeprecatedUserColorDoNotUse));
   EXPECT_FALSE(profile()->GetPrefs()->GetUserPrefValue(
       prefs::kDeprecatedBrowserColorVariantDoNotUse));
-  EXPECT_FALSE(profile()->GetPrefs()->GetUserPrefValue(
-      prefs::kNonSyncingNtpCustomBackgroundDictDoNotUse));
+  EXPECT_FALSE(
+      profile()->GetPrefs()->GetUserPrefValue(prefs::kNtpCustomBackgroundDict));
 
   // Set ntp background.
   {
-    ScopedDictPrefUpdate dict(
-        profile()->GetPrefs(),
-        prefs::kNonSyncingNtpCustomBackgroundDictDoNotUse);
+    ScopedDictPrefUpdate dict(profile()->GetPrefs(),
+                              prefs::kNtpCustomBackgroundDict);
     dict->Set(kNtpCustomBackgroundURL, kTestUrl);
   }
 
-  EXPECT_TRUE(profile()->GetPrefs()->GetUserPrefValue(
-      prefs::kNonSyncingNtpCustomBackgroundDictDoNotUse));
+  EXPECT_TRUE(
+      profile()->GetPrefs()->GetUserPrefValue(prefs::kNtpCustomBackgroundDict));
 
   // Other prefs are left as-is.
   EXPECT_TRUE(profile()->GetPrefs()->GetUserPrefValue(
@@ -2060,8 +2057,8 @@
       prefs::kDeprecatedBrowserColorVariantDoNotUse));
   EXPECT_FALSE(profile()->GetPrefs()->GetUserPrefValue(
       prefs::kDeprecatedGrayscaleThemeEnabledDoNotUse));
-  EXPECT_FALSE(profile()->GetPrefs()->GetUserPrefValue(
-      prefs::kNonSyncingNtpCustomBackgroundDictDoNotUse));
+  EXPECT_FALSE(
+      profile()->GetPrefs()->GetUserPrefValue(prefs::kNtpCustomBackgroundDict));
 }
 
 // Regression test for crbug.com/389026436.
@@ -2079,7 +2076,7 @@
           .Set(kNtpCustomBackgroundRefreshTimestamp, 1234567890)
           .Set(kNtpCustomBackgroundMainColor, static_cast<int>(SK_ColorRED));
 
-  profile()->GetPrefs()->Set(prefs::kNonSyncingNtpCustomBackgroundDictDoNotUse,
+  profile()->GetPrefs()->Set(prefs::kNtpCustomBackgroundDict,
                              base::Value(new_value.Clone()));
 
   // Remote theme.
@@ -2102,8 +2099,8 @@
   ASSERT_FALSE(error.has_value()) << error.value().message();
 
   // Local ntp background is cleared.
-  EXPECT_FALSE(profile()->GetPrefs()->GetUserPrefValue(
-      prefs::kNonSyncingNtpCustomBackgroundDictDoNotUse));
+  EXPECT_FALSE(
+      profile()->GetPrefs()->GetUserPrefValue(prefs::kNtpCustomBackgroundDict));
 }
 
 // Regression test for crbug.com/391114025.
@@ -2122,7 +2119,7 @@
           .Set(kNtpCustomBackgroundRefreshTimestamp, 1234567890)
           .Set(kNtpCustomBackgroundMainColor, static_cast<int>(SK_ColorRED));
 
-  profile()->GetPrefs()->Set(prefs::kNonSyncingNtpCustomBackgroundDictDoNotUse,
+  profile()->GetPrefs()->Set(prefs::kNtpCustomBackgroundDict,
                              base::Value(new_value.Clone()));
 
   // Remote theme does not contain new fields, thus an old ThemeSpecifics.
@@ -2143,10 +2140,9 @@
   // intentionally clear the background, just left it unset.
   EXPECT_TRUE(theme_service()->UsingAutogeneratedTheme());
   EXPECT_EQ(theme_service()->GetAutogeneratedThemeColor(), SK_ColorBLUE);
-  EXPECT_TRUE(profile()->GetPrefs()->GetUserPrefValue(
-      prefs::kNonSyncingNtpCustomBackgroundDictDoNotUse));
-  EXPECT_EQ(profile()->GetPrefs()->GetDict(
-                prefs::kNonSyncingNtpCustomBackgroundDictDoNotUse),
+  EXPECT_TRUE(
+      profile()->GetPrefs()->GetUserPrefValue(prefs::kNtpCustomBackgroundDict));
+  EXPECT_EQ(profile()->GetPrefs()->GetDict(prefs::kNtpCustomBackgroundDict),
             new_value);
 
   // The merged theme should be committed to the server.
@@ -2177,7 +2173,7 @@
           .Set(kNtpCustomBackgroundRefreshTimestamp, 1234567890)
           .Set(kNtpCustomBackgroundMainColor, static_cast<int>(SK_ColorRED));
 
-  profile()->GetPrefs()->Set(prefs::kNonSyncingNtpCustomBackgroundDictDoNotUse,
+  profile()->GetPrefs()->Set(prefs::kNtpCustomBackgroundDict,
                              base::Value(new_value.Clone()));
 
   // Remote theme does not contain new fields, thus an old ThemeSpecifics.
@@ -2194,10 +2190,9 @@
 
   // Local ntp background is still there since default remote themes are ignored
   // in the initial update.
-  EXPECT_TRUE(profile()->GetPrefs()->GetUserPrefValue(
-      prefs::kNonSyncingNtpCustomBackgroundDictDoNotUse));
-  EXPECT_EQ(profile()->GetPrefs()->GetDict(
-                prefs::kNonSyncingNtpCustomBackgroundDictDoNotUse),
+  EXPECT_TRUE(
+      profile()->GetPrefs()->GetUserPrefValue(prefs::kNtpCustomBackgroundDict));
+  EXPECT_EQ(profile()->GetPrefs()->GetDict(prefs::kNtpCustomBackgroundDict),
             new_value);
 
   sync_pb::ThemeSpecifics current_specifics =
@@ -2223,7 +2218,7 @@
           .Set(kNtpCustomBackgroundRefreshTimestamp, 1234567890)
           .Set(kNtpCustomBackgroundMainColor, static_cast<int>(SK_ColorRED));
 
-  profile()->GetPrefs()->Set(prefs::kNonSyncingNtpCustomBackgroundDictDoNotUse,
+  profile()->GetPrefs()->Set(prefs::kNtpCustomBackgroundDict,
                              base::Value(new_value.Clone()));
 
   // Remote theme contains new fields, thus a new ThemeSpecifics.
@@ -2244,8 +2239,8 @@
   // Local ntp background is cleared, because the remote client must have
   // explicitly cleared it.
   EXPECT_TRUE(theme_service()->UsingAutogeneratedTheme());
-  EXPECT_FALSE(profile()->GetPrefs()->GetUserPrefValue(
-      prefs::kNonSyncingNtpCustomBackgroundDictDoNotUse));
+  EXPECT_FALSE(
+      profile()->GetPrefs()->GetUserPrefValue(prefs::kNtpCustomBackgroundDict));
 
   // The remote theme wins and nothing is committed to the server.
   ASSERT_EQ(fake_change_processor()->changes().size(), 0u);
@@ -2266,7 +2261,7 @@
           .Set(kNtpCustomBackgroundRefreshTimestamp, 1234567890)
           .Set(kNtpCustomBackgroundMainColor, static_cast<int>(SK_ColorRED));
 
-  profile()->GetPrefs()->Set(prefs::kNonSyncingNtpCustomBackgroundDictDoNotUse,
+  profile()->GetPrefs()->Set(prefs::kNtpCustomBackgroundDict,
                              base::Value(new_value.Clone()));
 
   // Remote theme contains new fields, thus a new ThemeSpecifics.
@@ -2285,10 +2280,9 @@
 
   // Local ntp background is still there since default remote themes are ignored
   // in the initial update.
-  EXPECT_TRUE(profile()->GetPrefs()->GetUserPrefValue(
-      prefs::kNonSyncingNtpCustomBackgroundDictDoNotUse));
-  EXPECT_EQ(profile()->GetPrefs()->GetDict(
-                prefs::kNonSyncingNtpCustomBackgroundDictDoNotUse),
+  EXPECT_TRUE(
+      profile()->GetPrefs()->GetUserPrefValue(prefs::kNtpCustomBackgroundDict));
+  EXPECT_EQ(profile()->GetPrefs()->GetDict(prefs::kNtpCustomBackgroundDict),
             new_value);
 
   sync_pb::ThemeSpecifics current_specifics =
@@ -2377,8 +2371,8 @@
   theme_specifics.set_browser_color_scheme(
       ::sync_pb::ThemeSpecifics_BrowserColorScheme_SYSTEM);
   theme_specifics.mutable_ntp_background()->set_url(kTestUrl);
-  ASSERT_FALSE(profile()->GetPrefs()->GetUserPrefValue(
-      prefs::kNonSyncingNtpCustomBackgroundDictDoNotUse));
+  ASSERT_FALSE(
+      profile()->GetPrefs()->GetUserPrefValue(prefs::kNtpCustomBackgroundDict));
 
   // Start syncing.
   std::optional<syncer::ModelError> error =
@@ -2388,8 +2382,8 @@
               fake_change_processor()));
   ASSERT_FALSE(error.has_value()) << error.value().message();
 
-  EXPECT_TRUE(profile()->GetPrefs()->GetUserPrefValue(
-      prefs::kNonSyncingNtpCustomBackgroundDictDoNotUse));
+  EXPECT_TRUE(
+      profile()->GetPrefs()->GetUserPrefValue(prefs::kNtpCustomBackgroundDict));
   // Local user color is cleared since the remote client must have explicitly
   // cleared it.
   EXPECT_NE(theme_service()->GetThemeID(), ThemeService::kUserColorThemeID);
@@ -2498,9 +2492,8 @@
 
   // Set custom ntp background pref.
   {
-    ScopedDictPrefUpdate dict(
-        profile()->GetPrefs(),
-        prefs::kNonSyncingNtpCustomBackgroundDictDoNotUse);
+    ScopedDictPrefUpdate dict(profile()->GetPrefs(),
+                              prefs::kNtpCustomBackgroundDict);
     dict->Set(kNtpCustomBackgroundURL, kTestUrl);
   }
   task_environment()->RunUntilIdle();
@@ -2706,9 +2699,8 @@
 TEST_P(ThemeSyncableServiceVerifyFinalStateTest, LocalBackground) {
   // Local custom ntp background.
   {
-    ScopedDictPrefUpdate dict(
-        profile()->GetPrefs(),
-        prefs::kNonSyncingNtpCustomBackgroundDictDoNotUse);
+    ScopedDictPrefUpdate dict(profile()->GetPrefs(),
+                              prefs::kNtpCustomBackgroundDict);
     dict->Set(kNtpCustomBackgroundURL, kTestUrl);
   }
 
@@ -2963,7 +2955,7 @@
                static_cast<int>(1234567890))
           .Set(kNtpCustomBackgroundMainColor, static_cast<int>(SK_ColorRED));
 
-  profile()->GetPrefs()->Set(prefs::kNonSyncingNtpCustomBackgroundDictDoNotUse,
+  profile()->GetPrefs()->Set(prefs::kNtpCustomBackgroundDict,
                              base::Value(background_dict.Clone()));
 
   // Start initial sync.
@@ -3303,8 +3295,8 @@
     waiter.WaitForThemeChanged();
   }
   EXPECT_TRUE(theme_service()->UsingExtensionTheme());
-  EXPECT_FALSE(profile()->GetPrefs()->GetUserPrefValue(
-      prefs::kNonSyncingNtpCustomBackgroundDictDoNotUse));
+  EXPECT_FALSE(
+      profile()->GetPrefs()->GetUserPrefValue(prefs::kNtpCustomBackgroundDict));
 
   // Set saved local theme pref.
   sync_pb::ThemeSpecifics local_theme_specifics;
@@ -3333,8 +3325,7 @@
   }
   EXPECT_FALSE(theme_service()->UsingExtensionTheme());
   EXPECT_EQ(
-      profile()->GetPrefs()->GetDict(
-          prefs::kNonSyncingNtpCustomBackgroundDictDoNotUse),
+      profile()->GetPrefs()->GetDict(prefs::kNtpCustomBackgroundDict),
       base::Value::Dict()
           .Set(kNtpCustomBackgroundURL, kTestUrl)
           .Set(kNtpCustomBackgroundAttributionLine1, "attribution_line_1")
@@ -3372,8 +3363,8 @@
     waiter.WaitForThemeChanged();
   }
   EXPECT_TRUE(theme_service()->UsingExtensionTheme());
-  EXPECT_FALSE(profile()->GetPrefs()->GetUserPrefValue(
-      prefs::kNonSyncingNtpCustomBackgroundDictDoNotUse));
+  EXPECT_FALSE(
+      profile()->GetPrefs()->GetUserPrefValue(prefs::kNtpCustomBackgroundDict));
 
   base::HistogramTester histogram_tester;
   // Stop syncing.
@@ -3615,9 +3606,9 @@
         prefs::kDeprecatedGrayscaleThemeEnabledDoNotUse, false,
         user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
     registry->RegisterBooleanPref(prefs::kGrayscaleThemeEnabled, false);
-    registry->RegisterDictionaryPref(prefs::kNtpCustomBackgroundDictDoNotUse);
     registry->RegisterDictionaryPref(
-        prefs::kNonSyncingNtpCustomBackgroundDictDoNotUse);
+        prefs::kDeprecatedNtpCustomBackgroundDictDoNotUse);
+    registry->RegisterDictionaryPref(prefs::kNtpCustomBackgroundDict);
   }
 
  protected:
@@ -3651,7 +3642,7 @@
   EXPECT_FALSE(pref_service_.HasPrefPath(prefs::kUserColor));
 
   pref_service_.SetDict(
-      prefs::kNtpCustomBackgroundDictDoNotUse,
+      prefs::kDeprecatedNtpCustomBackgroundDictDoNotUse,
       base::Value::Dict()
           .Set(kNtpCustomBackgroundURL, kTestUrl)
           .Set(kNtpCustomBackgroundAttributionLine1, "attribution_line_1")
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 2f63a04f..bfbfbcc 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -916,6 +916,10 @@
       "//chrome/browser/notifications/scheduler/public",
       "//chrome/browser/password_manager/android:utils",
       "//chrome/browser/password_manager/android/access_loss:public",
+
+      # TODO(crbug.com/417774639): Remove when c/b/ui/android/hats/hats_service_android.cc
+      # gets modularized.
+      "//chrome/browser/profiles",
       "//chrome/browser/resources/feed_internals:resources",
       "//chrome/browser/touch_to_fill/autofill/android",
       "//chrome/browser/ui/android/autofill/internal:jni_headers",
@@ -1350,10 +1354,6 @@
       "views/file_system_access/file_system_access_scroll_panel.h",
       "views/search_engine_choice/search_engine_choice_dialog_view.cc",
       "views/search_engine_choice/search_engine_choice_dialog_view.h",
-      "webui/app_service_internals/app_service_internals_page_handler_impl.cc",
-      "webui/app_service_internals/app_service_internals_page_handler_impl.h",
-      "webui/app_service_internals/app_service_internals_ui.cc",
-      "webui/app_service_internals/app_service_internals_ui.h",
       "webui/bookmarks/bookmark_prefs.cc",
       "webui/bookmarks/bookmark_prefs.h",
       "webui/bookmarks/bookmarks_message_handler.cc",
@@ -3173,6 +3173,7 @@
       "//chrome/app:generated_resources",
       "//chrome/browser/on_device_translation",
       "//chrome/browser/on_device_translation:language_pack_util",
+      "//chrome/browser/profiles",
       "//chrome/browser/shortcuts",
       "//chrome/browser/ui/webui/app_home",
       "//chrome/browser/ui/webui/signin:profile",
@@ -4639,6 +4640,8 @@
       "views/tab_sharing/tab_capture_contents_border_helper.h",
       "views/tab_sharing/tab_sharing_infobar.cc",
       "views/tab_sharing/tab_sharing_infobar.h",
+      "views/tab_sharing/tab_sharing_infobar_utils.cc",
+      "views/tab_sharing/tab_sharing_infobar_utils.h",
       "views/tab_sharing/tab_sharing_status_message_view.cc",
       "views/tab_sharing/tab_sharing_status_message_view.h",
       "views/tab_sharing/tab_sharing_ui_views.cc",
@@ -5004,6 +5007,7 @@
       "//chrome/browser/ui/views/frame:immersive_mode_controller",
       "//chrome/browser/ui/views/intent_picker:intent_picker_page_action",
       "//chrome/browser/ui/views/page_action",
+      "//chrome/browser/ui/webui/app_service_internals",
       "//chrome/browser/ui/webui/side_panel/customize_chrome",
       "//components/collaboration/public",
       "//components/commerce/core:metrics",
@@ -5350,6 +5354,7 @@
       "//chrome/browser/apps/platform_apps/api",
       "//chrome/browser/extensions/api/omnibox",
       "//chrome/browser/policy:policy_util",
+      "//chrome/browser/profiles",
       "//chrome/browser/web_applications",
       "//chrome/browser/web_applications/app_service",
       "//chrome/browser/web_share_target",
diff --git a/chrome/browser/ui/android/hats/BUILD.gn b/chrome/browser/ui/android/hats/BUILD.gn
index 552476b..3bfb9a12 100644
--- a/chrome/browser/ui/android/hats/BUILD.gn
+++ b/chrome/browser/ui/android/hats/BUILD.gn
@@ -28,6 +28,7 @@
     "//base:base_java",
     "//chrome/browser/privacy:privacy_preference_manager_java",
     "//chrome/browser/profiles/android:java",
+    "//chrome/browser/tabmodel:java",
   ]
 
   # This internal file will be replaced by a generated file so the resulting
diff --git a/chrome/browser/ui/android/hats/hats_service_android.cc b/chrome/browser/ui/android/hats/hats_service_android.cc
index 9754df5..35ad04a 100644
--- a/chrome/browser/ui/android/hats/hats_service_android.cc
+++ b/chrome/browser/ui/android/hats/hats_service_android.cc
@@ -76,14 +76,15 @@
     message_->SetTitle(survey_options_.custom_invitation.value());
   }
 
-  hats::SurveyUiDelegateAndroid delegate(
-      message_.get(), web_contents()->GetTopLevelNativeWindow());
+  ui::WindowAndroid* window_android = web_contents()->GetTopLevelNativeWindow();
+
+  hats::SurveyUiDelegateAndroid delegate(message_.get(), window_android);
 
   // Create survey client with delegate.
-  hats::SurveyClientAndroid survey_client(
-      trigger_, &delegate, hats_service_->profile(), supplied_trigger_id_);
-  survey_client.LaunchSurvey(web_contents()->GetTopLevelNativeWindow(),
-                             product_specific_bits_data_,
+  hats::SurveyClientAndroid survey_client(trigger_, &delegate,
+                                          hats_service_->profile(),
+                                          supplied_trigger_id_, window_android);
+  survey_client.LaunchSurvey(window_android, product_specific_bits_data_,
                              product_specific_string_data_);
 }
 
diff --git a/chrome/browser/ui/android/hats/internal/java/src/org/chromium/chrome/browser/ui/hats/SurveyClientBridge.java b/chrome/browser/ui/android/hats/internal/java/src/org/chromium/chrome/browser/ui/hats/SurveyClientBridge.java
index a9d60314..af98fb4 100644
--- a/chrome/browser/ui/android/hats/internal/java/src/org/chromium/chrome/browser/ui/hats/SurveyClientBridge.java
+++ b/chrome/browser/ui/android/hats/internal/java/src/org/chromium/chrome/browser/ui/hats/SurveyClientBridge.java
@@ -19,6 +19,7 @@
 import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
 import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcherProvider;
 import org.chromium.chrome.browser.profiles.Profile;
+import org.chromium.chrome.browser.tabmodel.TabModelSelectorSupplier;
 import org.chromium.ui.base.WindowAndroid;
 
 import java.util.HashMap;
@@ -45,15 +46,21 @@
             String trigger,
             SurveyUiDelegate uiDelegate,
             Profile profile,
-            String suppliedTriggerId) {
+            String suppliedTriggerId,
+            WindowAndroid windowAndroid) {
         assert SurveyClientFactory.getInstance() != null;
         SurveyConfig config = SurveyConfig.get(profile, trigger, suppliedTriggerId);
         if (config == null) {
             return null;
         }
 
+        var tabModelSelector = TabModelSelectorSupplier.getValueOrNullFrom(windowAndroid);
+        // TODO(crbug.com/418075247): Remove nullness of tabModelSelector, currently it's required
+        // for tests
+
         SurveyClient client =
-                SurveyClientFactory.getInstance().createClient(config, uiDelegate, profile);
+                SurveyClientFactory.getInstance()
+                        .createClient(config, uiDelegate, profile, tabModelSelector);
         if (client == null) {
             Log.d(TAG, "SurveyClient is null. config: " + SurveyConfig.toString(config));
             return null;
diff --git a/chrome/browser/ui/android/hats/internal/java/src/org/chromium/chrome/browser/ui/hats/SurveyClientBridgeUnitTest.java b/chrome/browser/ui/android/hats/internal/java/src/org/chromium/chrome/browser/ui/hats/SurveyClientBridgeUnitTest.java
index d74c5bc..201f22c7 100644
--- a/chrome/browser/ui/android/hats/internal/java/src/org/chromium/chrome/browser/ui/hats/SurveyClientBridgeUnitTest.java
+++ b/chrome/browser/ui/android/hats/internal/java/src/org/chromium/chrome/browser/ui/hats/SurveyClientBridgeUnitTest.java
@@ -29,6 +29,10 @@
 import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
 import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcherProvider;
 import org.chromium.chrome.browser.profiles.Profile;
+import org.chromium.chrome.browser.tabmodel.TabModelSelector;
+import org.chromium.chrome.browser.tabmodel.TabModelSelectorSupplier;
+import org.chromium.ui.base.ActivityWindowAndroid;
+import org.chromium.ui.base.IntentRequestTracker;
 import org.chromium.ui.base.WindowAndroid;
 
 import java.lang.ref.WeakReference;
@@ -48,19 +52,32 @@
     @Mock SurveyClient mDelegateSurveyClient;
     @Mock ActivityLifecycleDispatcher mActivityLifecycleDispatcher;
     @Mock Profile mProfile;
+    @Mock TabModelSelector mTabModelSelector;
+
+    WindowAndroid mWindow;
 
     @Before
     public void setup() {
         mActivity = Robolectric.buildActivity(LifecycleDispatcherActivity.class).get();
         mActivity.setLifecycleDispatcher(mActivityLifecycleDispatcher);
         SurveyClientFactory.setInstanceForTesting(mFactory);
+        TabModelSelectorSupplier.setInstanceForTesting(mTabModelSelector);
 
-        doReturn(mDelegateSurveyClient).when(mFactory).createClient(any(), any(), any());
+        doReturn(mDelegateSurveyClient).when(mFactory).createClient(any(), any(), any(), any());
+
+        mWindow =
+                new ActivityWindowAndroid(
+                        mActivity,
+                        false,
+                        IntentRequestTracker.createFromActivity(mActivity),
+                        /* insetObserver= */ null,
+                        /* trackOcclusion= */ true);
     }
 
     @After
     public void tearDown() {
         mActivity.finish();
+        mWindow.destroy();
     }
 
     @Test
@@ -70,7 +87,7 @@
         TestSurveyUtils.setTestSurveyConfigForTrigger(
                 TEST_TRIGGER, new String[] {}, new String[] {});
         SurveyClientBridge bridge =
-                SurveyClientBridge.create(TEST_TRIGGER, testDelegate, mProfile, "");
+                SurveyClientBridge.create(TEST_TRIGGER, testDelegate, mProfile, "", mWindow);
         assertNotNull(bridge);
 
         bridge.showSurvey(mActivity, mActivityLifecycleDispatcher);
@@ -78,7 +95,7 @@
 
         ArgumentCaptor<SurveyConfig> surveyConfigArgumentCaptor =
                 ArgumentCaptor.forClass(SurveyConfig.class);
-        verify(mFactory).createClient(surveyConfigArgumentCaptor.capture(), any(), any());
+        verify(mFactory).createClient(surveyConfigArgumentCaptor.capture(), any(), any(), any());
 
         assertEquals(
                 TestSurveyUtils.TEST_TRIGGER_ID_FOO,
@@ -93,7 +110,7 @@
                 TEST_TRIGGER, new String[] {}, new String[] {});
         SurveyClientBridge bridge =
                 SurveyClientBridge.create(
-                        TEST_TRIGGER, testDelegate, mProfile, SUPPLIED_TRIGGER_ID);
+                        TEST_TRIGGER, testDelegate, mProfile, SUPPLIED_TRIGGER_ID, mWindow);
         assertNotNull(bridge);
 
         bridge.showSurvey(mActivity, mActivityLifecycleDispatcher);
@@ -101,7 +118,7 @@
 
         ArgumentCaptor<SurveyConfig> surveyConfigArgumentCaptor =
                 ArgumentCaptor.forClass(SurveyConfig.class);
-        verify(mFactory).createClient(surveyConfigArgumentCaptor.capture(), any(), any());
+        verify(mFactory).createClient(surveyConfigArgumentCaptor.capture(), any(), any(), any());
         assertEquals(SUPPLIED_TRIGGER_ID, surveyConfigArgumentCaptor.getValue().mTriggerId);
     }
 
@@ -112,7 +129,7 @@
         TestSurveyUtils.setTestSurveyConfigForTrigger(
                 TEST_TRIGGER, new String[] {"bit1", "bit2"}, new String[] {"string1", "string2"});
         SurveyClientBridge bridge =
-                SurveyClientBridge.create(TEST_TRIGGER, testDelegate, mProfile, "");
+                SurveyClientBridge.create(TEST_TRIGGER, testDelegate, mProfile, "", mWindow);
         assertNotNull(bridge);
 
         Map<String, Boolean> bitValues = Map.of("bit1", true, "bit2", false);
@@ -130,7 +147,7 @@
                 new TestSurveyUtils.TestSurveyUiDelegate();
         TestSurveyUtils.setTestSurveyConfigForTrigger(TEST_TRIGGER, bitFields, stringFields);
         SurveyClientBridge bridge =
-                SurveyClientBridge.create(TEST_TRIGGER, testDelegate, mProfile, "");
+                SurveyClientBridge.create(TEST_TRIGGER, testDelegate, mProfile, "", mWindow);
         assertNotNull(bridge);
 
         WindowAndroid window = mock(WindowAndroid.class);
diff --git a/chrome/browser/ui/android/hats/internal/java/src/org/chromium/chrome/browser/ui/hats/SurveyClientFactory.java b/chrome/browser/ui/android/hats/internal/java/src/org/chromium/chrome/browser/ui/hats/SurveyClientFactory.java
index 2b460171..7e0c571 100644
--- a/chrome/browser/ui/android/hats/internal/java/src/org/chromium/chrome/browser/ui/hats/SurveyClientFactory.java
+++ b/chrome/browser/ui/android/hats/internal/java/src/org/chromium/chrome/browser/ui/hats/SurveyClientFactory.java
@@ -14,6 +14,7 @@
 import org.chromium.build.annotations.Nullable;
 import org.chromium.chrome.browser.privacy.settings.PrivacyPreferencesManager;
 import org.chromium.chrome.browser.profiles.Profile;
+import org.chromium.chrome.browser.tabmodel.TabModelSelector;
 
 /** Factory class used to create SurveyClient. */
 @NullMarked
@@ -77,7 +78,10 @@
      * @return SurveyClient to display the given survey matching the config.
      */
     public @Nullable SurveyClient createClient(
-            SurveyConfig config, SurveyUiDelegate uiDelegate, Profile profile) {
+            SurveyConfig config,
+            SurveyUiDelegate uiDelegate,
+            Profile profile,
+            @Nullable TabModelSelector tabModelSelector) {
         if (config.mProbability == 0f || TextUtils.isEmpty(config.mTriggerId)) return null;
 
         SurveyController surveyController;
@@ -89,7 +93,12 @@
             surveyController = new SurveyController() {};
         }
         return new SurveyClientImpl(
-                config, uiDelegate, surveyController, mCrashUploadPermissionSupplier, profile);
+                config,
+                uiDelegate,
+                surveyController,
+                mCrashUploadPermissionSupplier,
+                profile,
+                tabModelSelector);
     }
 
     /** Get the crash upload supplier initialized in this factory. */
diff --git a/chrome/browser/ui/android/hats/internal/java/src/org/chromium/chrome/browser/ui/hats/SurveyClientImpl.java b/chrome/browser/ui/android/hats/internal/java/src/org/chromium/chrome/browser/ui/hats/SurveyClientImpl.java
index 7af9d16..9fa3457a 100644
--- a/chrome/browser/ui/android/hats/internal/java/src/org/chromium/chrome/browser/ui/hats/SurveyClientImpl.java
+++ b/chrome/browser/ui/android/hats/internal/java/src/org/chromium/chrome/browser/ui/hats/SurveyClientImpl.java
@@ -23,6 +23,8 @@
 import org.chromium.chrome.browser.lifecycle.PauseResumeWithNativeObserver;
 import org.chromium.chrome.browser.preferences.Pref;
 import org.chromium.chrome.browser.profiles.Profile;
+import org.chromium.chrome.browser.tabmodel.TabModelSelector;
+import org.chromium.chrome.browser.tabmodel.TabModelSelectorObserver;
 import org.chromium.chrome.browser.ui.hats.SurveyConfig.RequestedBrowserType;
 import org.chromium.components.user_prefs.UserPrefs;
 
@@ -49,11 +51,13 @@
     private final ObservableSupplier<Boolean> mCrashUploadPermissionSupplier;
     private final Map<String, String> mAggregatedSurveyPsd;
     private final Profile mProfile;
+    private final @Nullable TabModelSelector mTabModelSelector;
 
     private @Nullable WeakReference<Activity> mActivityRef;
     private @Nullable ActivityLifecycleDispatcher mLifecycleDispatcher;
     private @Nullable LifecycleObserver mLifecycleObserver;
     private @Nullable Callback<Boolean> mOnCrashUploadPermissionChangeCallback;
+    private @Nullable TabModelSelectorObserver mTabModelSelectorObserver;
     private boolean mIsDestroyed;
 
     SurveyClientImpl(
@@ -61,7 +65,8 @@
             SurveyUiDelegate uiDelegate,
             SurveyController controller,
             ObservableSupplier<Boolean> crashUploadPermissionSupplier,
-            Profile profile) {
+            Profile profile,
+            @Nullable TabModelSelector tabModelSelector) {
         mConfig = config;
         mUiDelegate = uiDelegate;
         mController = controller;
@@ -69,6 +74,7 @@
         mThrottler = new SurveyThrottler(mConfig);
         mAggregatedSurveyPsd = new HashMap<>();
         mProfile = profile;
+        mTabModelSelector = tabModelSelector;
     }
 
     @Override
@@ -151,6 +157,28 @@
                 this::onSurveyDownloadFailed);
     }
 
+    /**
+     * @return Whether the actual browser type matches the requested browser type from the survey
+     *     config.
+     */
+    private boolean isRightBrowserType() {
+        if (mTabModelSelector == null) {
+            // TODO(crbug.com/418075247): Ensure mTabModelSelector is never null.
+            return true;
+        }
+
+        @RequestedBrowserType int requestedBrowserType = mConfig.mRequestedBrowserType;
+        switch (requestedBrowserType) {
+            case RequestedBrowserType.REGULAR:
+                return !mTabModelSelector.isIncognitoSelected();
+            case RequestedBrowserType.INCOGNITO:
+                return mTabModelSelector.isIncognitoSelected();
+            default:
+                assert false : "Unknown requested browser type: " + requestedBrowserType;
+                return false;
+        }
+    }
+
     private void onSurveyDownloadSucceeded() {
         Log.d(TAG, "Survey Download succeed.");
         if (!configurationAllowsSurveys()) return;
@@ -182,6 +210,21 @@
                 };
         mCrashUploadPermissionSupplier.addObserver(mOnCrashUploadPermissionChangeCallback);
 
+        // TODO(crbug.com/418075247): Ensure mTabModelSelector is never null.
+        if (mTabModelSelector != null) {
+            mTabModelSelectorObserver =
+                    new TabModelSelectorObserver() {
+                        @Override
+                        public void onChange() {
+                            if (!isRightBrowserType()) {
+                                mUiDelegate.dismiss();
+                                mTabModelSelector.removeObserver(this);
+                            }
+                        }
+                    };
+            mTabModelSelector.addObserver(mTabModelSelectorObserver);
+        }
+
         mUiDelegate.showSurveyInvitation(
                 this::onSurveyAccepted, this::onSurveyDeclined, this::onSurveyPresentationFailed);
     }
@@ -250,6 +293,10 @@
             mCrashUploadPermissionSupplier.removeObserver(mOnCrashUploadPermissionChangeCallback);
             mOnCrashUploadPermissionChangeCallback = null;
         }
+        if (mTabModelSelector != null && mTabModelSelectorObserver != null) {
+            mTabModelSelector.removeObserver(mTabModelSelectorObserver);
+            mTabModelSelectorObserver = null;
+        }
         mLifecycleDispatcher = null;
 
         if (dismissUiDelegate) {
@@ -266,11 +313,7 @@
      * @return a boolean indicating whether the user's configuration allows a survey to be shown.
      */
     private boolean configurationAllowsSurveys() {
-        if (mConfig.mRequestedBrowserType != RequestedBrowserType.REGULAR) {
-            assert false : "HaTS on Android supports only RequestedBrowserType.REGULAR";
-            return false;
-        }
-
+        if (!isRightBrowserType()) return false;
         if (forceShowSurvey()) return true;
 
         // Do not include any logging to avoid reveal the fact user has crash upload disabled.
diff --git a/chrome/browser/ui/android/hats/internal/java/src/org/chromium/chrome/browser/ui/hats/SurveyClientUnitTest.java b/chrome/browser/ui/android/hats/internal/java/src/org/chromium/chrome/browser/ui/hats/SurveyClientUnitTest.java
index ccef651f..8acf94b7 100644
--- a/chrome/browser/ui/android/hats/internal/java/src/org/chromium/chrome/browser/ui/hats/SurveyClientUnitTest.java
+++ b/chrome/browser/ui/android/hats/internal/java/src/org/chromium/chrome/browser/ui/hats/SurveyClientUnitTest.java
@@ -40,6 +40,8 @@
 import org.chromium.chrome.browser.privacy.settings.PrivacyPreferencesManager;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.profiles.ProfileManager;
+import org.chromium.chrome.browser.tabmodel.TabModelSelector;
+import org.chromium.chrome.browser.ui.hats.SurveyConfig.RequestedBrowserType;
 import org.chromium.components.prefs.PrefService;
 import org.chromium.components.user_prefs.UserPrefs;
 import org.chromium.components.user_prefs.UserPrefsJni;
@@ -65,6 +67,7 @@
     @Mock private Activity mActivity;
     @Mock private Profile mProfile;
     @Mock private PrivacyPreferencesManager mPrivacyPreferencesManager;
+    @Mock private TabModelSelector mTabModelSelector;
     @Captor private ArgumentCaptor<PauseResumeWithNativeObserver> mLifecycleObserverCaptor;
 
     @Before
@@ -99,7 +102,8 @@
     public void createThroughFactory() {
         SurveyConfig config = newSurveyConfigWithoutPsd();
         SurveyClient client =
-                SurveyClientFactory.getInstance().createClient(config, mSurveyUiDelegate, mProfile);
+                SurveyClientFactory.getInstance()
+                        .createClient(config, mSurveyUiDelegate, mProfile, mTabModelSelector);
 
         if (!(client instanceof SurveyClientImpl)) {
             throw new AssertionError(
@@ -119,7 +123,8 @@
                         mSurveyUiDelegate,
                         mSurveyController,
                         mCrashUploadPermissionSupplier,
-                        mProfile);
+                        mProfile,
+                        mTabModelSelector);
         client.showSurvey(mActivity, mLifecycleDispatcher);
         ShadowLooper.idleMainLooper();
 
@@ -146,7 +151,48 @@
                         mSurveyUiDelegate,
                         mSurveyController,
                         mCrashUploadPermissionSupplier,
-                        mProfile);
+                        mProfile,
+                        mTabModelSelector);
+        client.showSurvey(mActivity, mLifecycleDispatcher);
+        ShadowLooper.idleMainLooper();
+
+        assertFalse(
+                "No survey download should be requested.",
+                mSurveyController.hasSurveyDownloadInQueue());
+    }
+
+    @Test
+    public void doNotDownloadRegularSurveyInIncognito() {
+        doReturn(true).when(mTabModelSelector).isIncognitoSelected();
+
+        SurveyConfig config = newSurveyConfigWithoutPsd();
+        SurveyClientImpl client =
+                new SurveyClientImpl(
+                        config,
+                        mSurveyUiDelegate,
+                        mSurveyController,
+                        mCrashUploadPermissionSupplier,
+                        mProfile,
+                        mTabModelSelector);
+        client.showSurvey(mActivity, mLifecycleDispatcher);
+        ShadowLooper.idleMainLooper();
+
+        assertFalse(
+                "No survey download should be requested.",
+                mSurveyController.hasSurveyDownloadInQueue());
+    }
+
+    @Test
+    public void doNotDownloadIncognitoSurveyInRegular() {
+        SurveyConfig config = newSurveyConfigWithoutPsd(RequestedBrowserType.INCOGNITO);
+        SurveyClientImpl client =
+                new SurveyClientImpl(
+                        config,
+                        mSurveyUiDelegate,
+                        mSurveyController,
+                        mCrashUploadPermissionSupplier,
+                        mProfile,
+                        mTabModelSelector);
         client.showSurvey(mActivity, mLifecycleDispatcher);
         ShadowLooper.idleMainLooper();
 
@@ -174,7 +220,8 @@
                         mSurveyUiDelegate,
                         mSurveyController,
                         mCrashUploadPermissionSupplier,
-                        mProfile);
+                        mProfile,
+                        mTabModelSelector);
         client.showSurvey(mActivity, mLifecycleDispatcher);
         ShadowLooper.idleMainLooper();
 
@@ -192,7 +239,8 @@
                         mSurveyUiDelegate,
                         mSurveyController,
                         mCrashUploadPermissionSupplier,
-                        mProfile);
+                        mProfile,
+                        mTabModelSelector);
         client.showSurvey(mActivity, mLifecycleDispatcher);
 
         mCrashUploadPermissionSupplier.set(false);
@@ -218,7 +266,8 @@
                         mSurveyUiDelegate,
                         mSurveyController,
                         mCrashUploadPermissionSupplier,
-                        mProfile);
+                        mProfile,
+                        mTabModelSelector);
         client.showSurvey(mActivity, mLifecycleDispatcher);
 
         mCrashUploadPermissionSupplier.set(true);
@@ -244,7 +293,8 @@
                         mSurveyUiDelegate,
                         mSurveyController,
                         mCrashUploadPermissionSupplier,
-                        mProfile);
+                        mProfile,
+                        mTabModelSelector);
         client.showSurvey(mActivity, mLifecycleDispatcher);
 
         mCrashUploadPermissionSupplier.set(false);
@@ -269,7 +319,8 @@
                         mSurveyUiDelegate,
                         mSurveyController,
                         mCrashUploadPermissionSupplier,
-                        mProfile);
+                        mProfile,
+                        mTabModelSelector);
         client.showSurvey(mActivity, mLifecycleDispatcher);
         ShadowLooper.idleMainLooper();
 
@@ -288,7 +339,8 @@
                         mSurveyUiDelegate,
                         mSurveyController,
                         mCrashUploadPermissionSupplier,
-                        mProfile);
+                        mProfile,
+                        mTabModelSelector);
         client.showSurvey(mActivity, mLifecycleDispatcher);
         ShadowLooper.idleMainLooper();
 
@@ -306,7 +358,8 @@
                         mSurveyUiDelegate,
                         mSurveyController,
                         mCrashUploadPermissionSupplier,
-                        mProfile);
+                        mProfile,
+                        mTabModelSelector);
         client.showSurvey(mActivity, mLifecycleDispatcher);
         ShadowLooper.idleMainLooper();
 
@@ -324,7 +377,8 @@
                         mSurveyUiDelegate,
                         mSurveyController,
                         mCrashUploadPermissionSupplier,
-                        mProfile);
+                        mProfile,
+                        mTabModelSelector);
         client.showSurvey(mActivity, mLifecycleDispatcher);
         ShadowLooper.idleMainLooper();
         mSurveyController.simulateDownloadFinished(TEST_TRIGGER_ID, true);
@@ -354,7 +408,8 @@
                         mSurveyUiDelegate,
                         mSurveyController,
                         mCrashUploadPermissionSupplier,
-                        mProfile);
+                        mProfile,
+                        mTabModelSelector);
         client.showSurvey(mActivity, mLifecycleDispatcher);
         ShadowLooper.idleMainLooper();
         mSurveyController.simulateDownloadFinished(TEST_TRIGGER_ID, true);
@@ -388,7 +443,8 @@
                         mSurveyUiDelegate,
                         mSurveyController,
                         mCrashUploadPermissionSupplier,
-                        mProfile);
+                        mProfile,
+                        mTabModelSelector);
         assertThrows(
                 "Expected PSD(s) are missing.",
                 AssertionError.class,
@@ -446,6 +502,10 @@
     }
 
     private SurveyConfig newSurveyConfigWithoutPsd() {
+        return newSurveyConfigWithoutPsd(RequestedBrowserType.REGULAR);
+    }
+
+    private SurveyConfig newSurveyConfigWithoutPsd(@RequestedBrowserType int requestedBrowserType) {
         return new SurveyConfig(
                 TEST_SURVEY_TRIGGER,
                 TEST_TRIGGER_ID,
@@ -454,6 +514,6 @@
                 new String[0],
                 new String[0],
                 Optional.empty(),
-                SurveyConfig.RequestedBrowserType.REGULAR);
+                requestedBrowserType);
     }
 }
diff --git a/chrome/browser/ui/android/hats/internal/java/src/org/chromium/chrome/browser/ui/hats/SurveyUiDelegateBridgeUnitTest.java b/chrome/browser/ui/android/hats/internal/java/src/org/chromium/chrome/browser/ui/hats/SurveyUiDelegateBridgeUnitTest.java
index 2b2b0d2..c4094f9 100644
--- a/chrome/browser/ui/android/hats/internal/java/src/org/chromium/chrome/browser/ui/hats/SurveyUiDelegateBridgeUnitTest.java
+++ b/chrome/browser/ui/android/hats/internal/java/src/org/chromium/chrome/browser/ui/hats/SurveyUiDelegateBridgeUnitTest.java
@@ -55,6 +55,7 @@
     @Mock private ManagedMessageDispatcher mMockMessageDispatcher;
     @Mock private TabModelSelector mTabModelSelector;
     @Mock private InsetObserver mInsetObserver;
+    @Mock private SurveyConfig mSurveyConfig;
 
     private Activity mActivity;
     private WindowAndroid mWindow;
diff --git a/chrome/browser/ui/android/hats/java/src/org/chromium/chrome/browser/ui/hats/MessageSurveyUiDelegate.java b/chrome/browser/ui/android/hats/java/src/org/chromium/chrome/browser/ui/hats/MessageSurveyUiDelegate.java
index e308e4f6..233a560c 100644
--- a/chrome/browser/ui/android/hats/java/src/org/chromium/chrome/browser/ui/hats/MessageSurveyUiDelegate.java
+++ b/chrome/browser/ui/android/hats/java/src/org/chromium/chrome/browser/ui/hats/MessageSurveyUiDelegate.java
@@ -222,8 +222,7 @@
      * @return Whether survey can be shown in the current session.
      */
     private boolean canShowSurveyPrompt() {
-        return !mTabModelSelector.isIncognitoSelected()
-                && Boolean.TRUE.equals(mCrashUploadPermissionSupplier.get());
+        return Boolean.TRUE.equals(mCrashUploadPermissionSupplier.get());
     }
 
     private void showSurveyIfReady() {
diff --git a/chrome/browser/ui/android/hats/java/src/org/chromium/chrome/browser/ui/hats/MessageSurveyUiDelegateUnitTest.java b/chrome/browser/ui/android/hats/java/src/org/chromium/chrome/browser/ui/hats/MessageSurveyUiDelegateUnitTest.java
index d11ab3b..1d07bb2 100644
--- a/chrome/browser/ui/android/hats/java/src/org/chromium/chrome/browser/ui/hats/MessageSurveyUiDelegateUnitTest.java
+++ b/chrome/browser/ui/android/hats/java/src/org/chromium/chrome/browser/ui/hats/MessageSurveyUiDelegateUnitTest.java
@@ -269,40 +269,6 @@
     }
 
     @Test
-    public void cancelWhenSwitchedIntoIncognito() {
-        mTabModelHelper.tabStateInitialized();
-        showSurveyInvitation();
-
-        mTabModelHelper.switchToIncognito(true);
-        mTabModelHelper.skipToReadyForSurvey();
-        mTestMessageDispatcher.assertMessageEnqueued(false);
-        assertEquals(
-                "Delegate state should end at NOT_PRESENTED since message is never enqueued.",
-                State.NOT_PRESENTED,
-                mMessageSurveyUiDelegate.getStateForTesting());
-        assertEquals(
-                "OnPresentationFailedCallback not called.",
-                1,
-                mOnPresentationFailedCallback.getCallCount());
-    }
-
-    @Test
-    public void noSurveyInvitationInIncognito() {
-        mTabModelHelper.switchToIncognito(true);
-        showSurveyInvitation();
-
-        mTestMessageDispatcher.assertMessageEnqueued(false);
-        assertEquals(
-                "Never show survey invitation in incognito.",
-                State.NOT_PRESENTED,
-                mMessageSurveyUiDelegate.getStateForTesting());
-        assertEquals(
-                "OnPresentationFailedCallback is not called.",
-                1,
-                mOnPresentationFailedCallback.getCallCount());
-    }
-
-    @Test
     public void noStateUpdateAfterAccepted() {
         mTabModelHelper.skipToReadyForSurvey();
         showSurveyInvitation();
@@ -409,11 +375,6 @@
                     .removeObserver(any(TabObserver.class));
         }
 
-        SurveyTestTabModelHelper switchToIncognito(boolean isIncognito) {
-            doReturn(isIncognito).when(mTabModelSelector).isIncognitoSelected();
-            return this;
-        }
-
         SurveyTestTabModelHelper tabStateInitialized() {
             mTabStateInitialized = true;
 
diff --git a/chrome/browser/ui/android/hats/java/src/org/chromium/chrome/browser/ui/hats/SurveyUiDelegate.java b/chrome/browser/ui/android/hats/java/src/org/chromium/chrome/browser/ui/hats/SurveyUiDelegate.java
index 1b46376..5b04a7e 100644
--- a/chrome/browser/ui/android/hats/java/src/org/chromium/chrome/browser/ui/hats/SurveyUiDelegate.java
+++ b/chrome/browser/ui/android/hats/java/src/org/chromium/chrome/browser/ui/hats/SurveyUiDelegate.java
@@ -13,10 +13,11 @@
 @NullMarked
 public interface SurveyUiDelegate {
     /**
-     * Called by SurveyClient when the survey is downloaded and ready to present. When survey
-     * is shown, the given runnable(s) are be used to notify SurveyClient the outcome of
-     * the survey invitation.
+     * Called by SurveyClient when the survey is downloaded and ready to present. When survey is
+     * shown, the given runnable(s) are be used to notify SurveyClient the outcome of the survey
+     * invitation.
      *
+     * @param surveyConfig Config of the survey.
      * @param onSurveyAccepted Callback to run when survey invitation is accepted.
      * @param onSurveyDeclined Callback to run when survey invitation is declined.
      * @param onSurveyPresentationFailed Callback to run when survey invitation failed to show.
diff --git a/chrome/browser/ui/android/hats/survey_client_android.cc b/chrome/browser/ui/android/hats/survey_client_android.cc
index 635f97d..2b9cad9 100644
--- a/chrome/browser/ui/android/hats/survey_client_android.cc
+++ b/chrome/browser/ui/android/hats/survey_client_android.cc
@@ -30,7 +30,8 @@
     const std::string& trigger,
     SurveyUiDelegateAndroid* ui_delegate,
     Profile* profile,
-    const std::optional<std::string>& supplied_trigger_id) {
+    const std::optional<std::string>& supplied_trigger_id,
+    ui::WindowAndroid* window) {
   JNIEnv* env = base::android::AttachCurrentThread();
   ScopedJavaLocalRef<jstring> java_trigger =
       ConvertUTF8ToJavaString(env, trigger);
@@ -40,7 +41,8 @@
                                        : std::string_view());
   jobj_ = Java_SurveyClientBridge_create(
       env, java_trigger, ui_delegate->GetJavaObject(env),
-      profile->GetJavaObject(), java_supplied_trigger_id);
+      profile->GetJavaObject(), java_supplied_trigger_id,
+      window->GetJavaObject());
 }
 
 SurveyClientAndroid::~SurveyClientAndroid() = default;
diff --git a/chrome/browser/ui/android/hats/survey_client_android.h b/chrome/browser/ui/android/hats/survey_client_android.h
index 1801c36..393a517 100644
--- a/chrome/browser/ui/android/hats/survey_client_android.h
+++ b/chrome/browser/ui/android/hats/survey_client_android.h
@@ -38,7 +38,8 @@
       const std::string& trigger,
       SurveyUiDelegateAndroid* ui_delegate,
       Profile* profile,
-      const std::optional<std::string>& supplied_trigger_id);
+      const std::optional<std::string>& supplied_trigger_id,
+      ui::WindowAndroid* window);
 
   SurveyClientAndroid(const SurveyClientAndroid&) = delete;
   SurveyClientAndroid& operator=(const SurveyClientAndroid&) = delete;
diff --git a/chrome/browser/ui/android/hats/survey_client_android_unittest.cc b/chrome/browser/ui/android/hats/survey_client_android_unittest.cc
index cc803a9..334e5cb 100644
--- a/chrome/browser/ui/android/hats/survey_client_android_unittest.cc
+++ b/chrome/browser/ui/android/hats/survey_client_android_unittest.cc
@@ -105,7 +105,7 @@
       std::make_unique<TestSurveyUiDelegate>(env);
   survey_client_ = std::make_unique<SurveyClientAndroid>(
       kTestSurveyTrigger, delegate.get(), profile_,
-      /*supplied_trigger_id=*/std::nullopt);
+      /*supplied_trigger_id=*/std::nullopt, window->get());
 
   survey_client_->LaunchSurvey(window->get(),
                                kTestSurveyProductSpecificBitsData,
@@ -129,7 +129,7 @@
   const std::string kSuppliedTriggerId = "SomeOtherId";
   survey_client_ = std::make_unique<SurveyClientAndroid>(
       kTestSurveyTrigger, delegate.get(), profile_,
-      /*supplied_trigger_id=*/kSuppliedTriggerId);
+      /*supplied_trigger_id=*/kSuppliedTriggerId, window->get());
 
   survey_client_->LaunchSurvey(window->get(),
                                kTestSurveyProductSpecificBitsData,
diff --git a/chrome/browser/ui/android/hats/test/BUILD.gn b/chrome/browser/ui/android/hats/test/BUILD.gn
index fc7abf7..efc5720b 100644
--- a/chrome/browser/ui/android/hats/test/BUILD.gn
+++ b/chrome/browser/ui/android/hats/test/BUILD.gn
@@ -20,6 +20,7 @@
     "//chrome/browser/flags:java",
     "//chrome/browser/privacy:privacy_preference_manager_java",
     "//chrome/browser/profiles/android:java",
+    "//chrome/browser/tabmodel:java",
     "//chrome/browser/ui/android/hats/internal:controller_java",
     "//chrome/browser/ui/android/hats/internal:java",
     "//content/public/test/android:content_java_test_support",
diff --git a/chrome/browser/ui/android/hats/test/java/src/org/chromium/chrome/browser/ui/hats/TestSurveyUtils.java b/chrome/browser/ui/android/hats/test/java/src/org/chromium/chrome/browser/ui/hats/TestSurveyUtils.java
index c2917d6c..3d15dbdba 100644
--- a/chrome/browser/ui/android/hats/test/java/src/org/chromium/chrome/browser/ui/hats/TestSurveyUtils.java
+++ b/chrome/browser/ui/android/hats/test/java/src/org/chromium/chrome/browser/ui/hats/TestSurveyUtils.java
@@ -22,6 +22,7 @@
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
 import org.chromium.chrome.browser.profiles.Profile;
+import org.chromium.chrome.browser.tabmodel.TabModelSelector;
 
 import java.util.Map;
 import java.util.Optional;
@@ -144,9 +145,15 @@
         public SurveyClient createClient(
                 @NonNull SurveyConfig config,
                 @NonNull SurveyUiDelegate uiDelegate,
-                Profile profile) {
+                Profile profile,
+                TabModelSelector tabModelSelector) {
             return new SurveyClientImpl(
-                    config, uiDelegate, mTestController, mCrashUploadPermissionSupplier, profile);
+                    config,
+                    uiDelegate,
+                    mTestController,
+                    mCrashUploadPermissionSupplier,
+                    profile,
+                    tabModelSelector);
         }
 
         @Override
diff --git a/chrome/browser/ui/android/web_app_header/java/src/org/chromium/chrome/browser/ui/web_app_header/WebAppHeaderLayoutCoordinator.java b/chrome/browser/ui/android/web_app_header/java/src/org/chromium/chrome/browser/ui/web_app_header/WebAppHeaderLayoutCoordinator.java
index cb5d74b3..9579b5e 100644
--- a/chrome/browser/ui/android/web_app_header/java/src/org/chromium/chrome/browser/ui/web_app_header/WebAppHeaderLayoutCoordinator.java
+++ b/chrome/browser/ui/android/web_app_header/java/src/org/chromium/chrome/browser/ui/web_app_header/WebAppHeaderLayoutCoordinator.java
@@ -48,6 +48,10 @@
 @RequiresApi(api = Build.VERSION_CODES.VANILLA_ICE_CREAM)
 public class WebAppHeaderLayoutCoordinator
         implements DesktopWindowStateManager.AppHeaderObserver, WebAppHeaderDelegate {
+
+    // 48dp * 2 (back and reload button) + 4dp (start padding).
+    static final int MIN_HEADER_WIDTH_DP = 100;
+
     private @Nullable WebAppHeaderLayoutMediator mMediator;
     private @Nullable WebAppHeaderLayout mView;
     private @Nullable ReloadButtonCoordinator mReloadButtonCoordinator;
@@ -65,9 +69,6 @@
     private final ObservableSupplierImpl<Boolean> mControlsEnabledSupplier;
     private final TokenHolder mDisabledControlsHolder;
 
-    // 48dp * 2 (back and reload button) + 4dp (start padding).
-    private static final int MIN_HEADER_WIDTH_DP = 100;
-
     /**
      * Creates an instance of {@link WebAppHeaderLayoutCoordinator}.
      *
diff --git a/chrome/browser/ui/android/web_app_header/java/src/org/chromium/chrome/browser/ui/web_app_header/WebAppHeaderLayoutCoordinatorTest.java b/chrome/browser/ui/android/web_app_header/java/src/org/chromium/chrome/browser/ui/web_app_header/WebAppHeaderLayoutCoordinatorTest.java
index 844380f..91e4060 100644
--- a/chrome/browser/ui/android/web_app_header/java/src/org/chromium/chrome/browser/ui/web_app_header/WebAppHeaderLayoutCoordinatorTest.java
+++ b/chrome/browser/ui/android/web_app_header/java/src/org/chromium/chrome/browser/ui/web_app_header/WebAppHeaderLayoutCoordinatorTest.java
@@ -9,10 +9,13 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 import static org.robolectric.Shadows.shadowOf;
 
+import static org.chromium.chrome.browser.ui.web_app_header.WebAppHeaderLayoutCoordinator.MIN_HEADER_WIDTH_DP;
+
 import android.app.Activity;
 import android.graphics.Rect;
 import android.os.Build;
@@ -28,6 +31,7 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
@@ -55,6 +59,8 @@
 import org.chromium.ui.base.TestActivity;
 import org.chromium.ui.util.TokenHolder;
 
+import java.util.Locale;
+
 @RunWith(BaseRobolectricTestRunner.class)
 @LooperMode(LooperMode.Mode.PAUSED)
 @Config(sdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
@@ -98,7 +104,7 @@
         mShadowLooper = shadowOf(Looper.getMainLooper());
 
         when(mIntentDataProvider.getActivityType()).thenReturn(ActivityType.WEB_APK);
-        setupStandaloneMode();
+        setupDisplayMode(DisplayMode.STANDALONE);
 
         mScrimVisibilitySupplier = new ObservableSupplierImpl<>();
         when(mScrimManager.getScrimVisibilitySupplier()).thenReturn(mScrimVisibilitySupplier);
@@ -125,35 +131,28 @@
     }
 
     private void setupDesktopWindowing(boolean isInDesktopWindow) {
-        mAppHeaderState =
-                new AppHeaderState(WINDOW_RECT, WIDEST_UNOCCLUDED_RECT, isInDesktopWindow);
+        setupDesktopWindowing(WINDOW_RECT, WIDEST_UNOCCLUDED_RECT, isInDesktopWindow);
+    }
+
+    private void setupDesktopWindowing(
+            Rect windowRect, Rect widestUnoccludedRect, boolean isInDesktopWindow) {
+        mAppHeaderState = new AppHeaderState(windowRect, widestUnoccludedRect, isInDesktopWindow);
         when(mDesktopWindowStateManager.getAppHeaderState()).thenReturn(mAppHeaderState);
     }
 
-    private void setupStandaloneMode() {
-        mWebAppExtras =
-                new WebappExtras(
-                        "",
-                        "",
-                        "",
-                        new WebappIcon(),
-                        "",
-                        "",
-                        DisplayMode.STANDALONE,
-                        0,
-                        0,
-                        0,
-                        0,
-                        0,
-                        false,
-                        false,
-                        false);
+    private void notifyHeaderStateChanged() {
+        var headerObserverCaptor =
+                ArgumentCaptor.forClass(DesktopWindowStateManager.AppHeaderObserver.class);
+        verify(mDesktopWindowStateManager, atLeastOnce())
+                .addObserver(headerObserverCaptor.capture());
 
-        when(mIntentDataProvider.getWebappExtras()).thenReturn(mWebAppExtras);
-        when(mIntentDataProvider.getResolvedDisplayMode()).thenReturn(DisplayMode.STANDALONE);
+        for (var observer : headerObserverCaptor.getAllValues()) {
+            // Notifying all observers is closer to the truth than relying on registration order.
+            observer.onAppHeaderStateChanged(mAppHeaderState);
+        }
     }
 
-    private void setupMinUiMode() {
+    private void setupDisplayMode(@DisplayMode.EnumType int displayMode) {
         mWebAppExtras =
                 new WebappExtras(
                         "",
@@ -162,7 +161,7 @@
                         new WebappIcon(),
                         "",
                         "",
-                        DisplayMode.MINIMAL_UI,
+                        displayMode,
                         0,
                         0,
                         0,
@@ -173,7 +172,7 @@
                         false);
 
         when(mIntentDataProvider.getWebappExtras()).thenReturn(mWebAppExtras);
-        when(mIntentDataProvider.getResolvedDisplayMode()).thenReturn(DisplayMode.MINIMAL_UI);
+        when(mIntentDataProvider.getResolvedDisplayMode()).thenReturn(displayMode);
     }
 
     private void setupTab(boolean isLoading, boolean canGoBack) {
@@ -182,22 +181,36 @@
         mTabSupplier.set(mTab);
     }
 
-    private void verifyControlsEnabled() {
-        assertTrue(
-                "Refresh button should be enabled",
+    private void verifyControlsEnabledState(boolean isEnabled) {
+        assertEquals(
+                String.format(
+                        Locale.US,
+                        "Reload button should be %s",
+                        isEnabled ? "enabled" : "disabled"),
+                isEnabled,
                 mActivity.findViewById(R.id.refresh_button).isEnabled());
-        assertTrue(
-                "Back button should be enabled",
+        assertEquals(
+                String.format(
+                        Locale.US, "Back button should be %s", isEnabled ? "enabled" : "disabled"),
+                isEnabled,
                 mActivity.findViewById(R.id.back_button).isEnabled());
     }
 
-    private void verifyControlsDisabled() {
-        assertFalse(
-                "Refresh button should be disabled",
-                mActivity.findViewById(R.id.refresh_button).isEnabled());
-        assertFalse(
-                "Back button should be disabled",
-                mActivity.findViewById(R.id.back_button).isEnabled());
+    private void verifyControlsVisibility(int expectedVisibility) {
+        assertEquals(
+                String.format(
+                        Locale.US,
+                        "Reload button visibility should be %s",
+                        expectedVisibility == View.VISIBLE ? "visible" : "gone"),
+                expectedVisibility,
+                mActivity.findViewById(R.id.refresh_button).getVisibility());
+        assertEquals(
+                String.format(
+                        Locale.US,
+                        "Back button visibility should be %s",
+                        expectedVisibility == View.VISIBLE ? "visible" : "gone"),
+                expectedVisibility,
+                mActivity.findViewById(R.id.back_button).getVisibility());
     }
 
     @Test
@@ -223,21 +236,14 @@
     @Test
     public void testMinUiDisplayMode_shouldMakeMinUiVisible() {
         setupDesktopWindowing(/* isInDesktopWindow= */ true);
-        setupMinUiMode();
+        setupDisplayMode(DisplayMode.MINIMAL_UI);
         setupTab(/* isLoading= */ false, /* canGoBack= */ false);
         createCoordinator();
 
         // Wait for animation to finish and update the view.
         mShadowLooper.idle();
 
-        assertEquals(
-                "Reload button should be visible",
-                View.VISIBLE,
-                mActivity.findViewById(R.id.refresh_button).getVisibility());
-        assertEquals(
-                "Back button should be visible",
-                View.VISIBLE,
-                mActivity.findViewById(R.id.back_button).getVisibility());
+        verifyControlsVisibility(View.VISIBLE);
         assertTrue(
                 "Reload button should be enabled",
                 mActivity.findViewById(R.id.refresh_button).isEnabled());
@@ -247,9 +253,63 @@
     }
 
     @Test
+    public void testMinUiMinimizeWindow_ControlsDoNotFit_HideControls() {
+        setupDesktopWindowing(/* isInDesktopWindow= */ true);
+        setupDisplayMode(DisplayMode.MINIMAL_UI);
+        setupTab(/* isLoading= */ false, /* canGoBack= */ false);
+        createCoordinator();
+
+        // Emulate minimizing window.
+        int flexibleAreaWidth = MIN_HEADER_WIDTH_DP - 1;
+        setupDesktopWindowing(
+                new Rect(0, 0, LEFT_INSET + flexibleAreaWidth + RIGHT_INSET, SCREEN_HEIGHT),
+                new Rect(LEFT_INSET, 0, LEFT_INSET + flexibleAreaWidth, SYS_APP_HEADER_HEIGHT),
+                /* isInDesktopWindow= */ true);
+
+        notifyHeaderStateChanged();
+        verifyControlsVisibility(View.GONE);
+    }
+
+    @Test
+    public void testMinUiMaximizeWindow_ControlsFit_ShowControls() {
+        // Emulate minimized window.
+        int flexibleAreaWidth = MIN_HEADER_WIDTH_DP - 1;
+        setupDesktopWindowing(
+                new Rect(0, 0, LEFT_INSET + flexibleAreaWidth + RIGHT_INSET, SCREEN_HEIGHT),
+                new Rect(LEFT_INSET, 0, LEFT_INSET + flexibleAreaWidth, SYS_APP_HEADER_HEIGHT),
+                /* isInDesktopWindow= */ true);
+
+        setupDisplayMode(DisplayMode.MINIMAL_UI);
+        setupTab(/* isLoading= */ false, /* canGoBack= */ false);
+        createCoordinator();
+
+        // Emulate maximizing window.
+        setupDesktopWindowing(/* isInDesktopWindow= */ true);
+
+        notifyHeaderStateChanged();
+        verifyControlsVisibility(View.VISIBLE);
+    }
+
+    @Test
+    public void testMinUiMinimizeWindow_MinimumWidthMatchThreshold_KeepControlsVisible() {
+        setupDesktopWindowing(/* isInDesktopWindow= */ true);
+        setupDisplayMode(DisplayMode.MINIMAL_UI);
+        setupTab(/* isLoading= */ false, /* canGoBack= */ false);
+        createCoordinator();
+
+        setupDesktopWindowing(
+                new Rect(0, 0, LEFT_INSET + MIN_HEADER_WIDTH_DP + RIGHT_INSET, SCREEN_HEIGHT),
+                new Rect(LEFT_INSET, 0, LEFT_INSET + MIN_HEADER_WIDTH_DP, SYS_APP_HEADER_HEIGHT),
+                /* isInDesktopWindow= */ true);
+
+        notifyHeaderStateChanged();
+        verifyControlsVisibility(View.VISIBLE);
+    }
+
+    @Test
     public void testReload_shouldReloadTab() {
         setupDesktopWindowing(/* isInDesktopWindow= */ true);
-        setupMinUiMode();
+        setupDisplayMode(DisplayMode.MINIMAL_UI);
         setupTab(/* isLoading= */ false, /* canGoBack= */ false);
         createCoordinator();
 
@@ -260,7 +320,7 @@
     @Test
     public void testReloadWhileReloading_shouldStopReloading() {
         setupDesktopWindowing(/* isInDesktopWindow= */ true);
-        setupMinUiMode();
+        setupDisplayMode(DisplayMode.MINIMAL_UI);
         setupTab(/* isLoading= */ true, /* canGoBack= */ false);
         createCoordinator();
 
@@ -271,7 +331,7 @@
     @Test
     public void testReloadTabIgnoringCache_shouldReloadIgnoringCache() {
         setupDesktopWindowing(/* isInDesktopWindow= */ true);
-        setupMinUiMode();
+        setupDisplayMode(DisplayMode.MINIMAL_UI);
         setupTab(/* isLoading= */ false, /* canGoBack= */ false);
         createCoordinator();
 
@@ -282,36 +342,36 @@
     @Test
     public void testDisableControls() {
         setupDesktopWindowing(/* isInDesktopWindow= */ true);
-        setupMinUiMode();
+        setupDisplayMode(DisplayMode.MINIMAL_UI);
         setupTab(/* isLoading= */ false, /* canGoBack= */ true);
         createCoordinator();
 
         mShadowLooper.idle();
 
         mCoordinator.disableControlsAndClearOldToken(TokenHolder.INVALID_TOKEN);
-        verifyControlsDisabled();
+        verifyControlsEnabledState(false);
     }
 
     @Test
     public void testEnableControls() {
         setupDesktopWindowing(/* isInDesktopWindow= */ true);
-        setupMinUiMode();
+        setupDisplayMode(DisplayMode.MINIMAL_UI);
         setupTab(/* isLoading= */ false, /* canGoBack= */ true);
         createCoordinator();
 
         mShadowLooper.idle();
 
         int token = mCoordinator.disableControlsAndClearOldToken(TokenHolder.INVALID_TOKEN);
-        verifyControlsDisabled();
+        verifyControlsEnabledState(false);
 
         mCoordinator.releaseDisabledControlsToken(token);
-        verifyControlsEnabled();
+        verifyControlsEnabledState(true);
     }
 
     @Test
     public void testDisableControlsOnManyTokens() {
         setupDesktopWindowing(/* isInDesktopWindow= */ true);
-        setupMinUiMode();
+        setupDisplayMode(DisplayMode.MINIMAL_UI);
         setupTab(/* isLoading= */ false, /* canGoBack= */ true);
         createCoordinator();
 
@@ -319,9 +379,9 @@
 
         int firstToken = mCoordinator.disableControlsAndClearOldToken(TokenHolder.INVALID_TOKEN);
         mCoordinator.disableControlsAndClearOldToken(TokenHolder.INVALID_TOKEN);
-        verifyControlsDisabled();
+        verifyControlsEnabledState(false);
 
         mCoordinator.releaseDisabledControlsToken(firstToken);
-        verifyControlsDisabled();
+        verifyControlsEnabledState(false);
     }
 }
diff --git a/chrome/browser/ui/android/web_app_header/java/src/org/chromium/chrome/browser/ui/web_app_header/WebAppHeaderLayoutMediator.java b/chrome/browser/ui/android/web_app_header/java/src/org/chromium/chrome/browser/ui/web_app_header/WebAppHeaderLayoutMediator.java
index fc0c4a8..8f6bf6b1 100644
--- a/chrome/browser/ui/android/web_app_header/java/src/org/chromium/chrome/browser/ui/web_app_header/WebAppHeaderLayoutMediator.java
+++ b/chrome/browser/ui/android/web_app_header/java/src/org/chromium/chrome/browser/ui/web_app_header/WebAppHeaderLayoutMediator.java
@@ -142,6 +142,10 @@
                 WebAppHeaderLayoutProperties.IS_VISIBLE, mCurrentHeaderState.isInDesktopWindow());
     }
 
+    /**
+     * @return {@link ObservableSupplier} that signal current width of the flexible area in which
+     *     the header lays out controls.
+     */
     public ObservableSupplier<Integer> getUnoccludedWidthSupplier() {
         return mAppHeaderUnoccludedWidthSupplier;
     }
diff --git a/chrome/browser/ui/ash/session/BUILD.gn b/chrome/browser/ui/ash/session/BUILD.gn
index 1aba352..80dc2b0 100644
--- a/chrome/browser/ui/ash/session/BUILD.gn
+++ b/chrome/browser/ui/ash/session/BUILD.gn
@@ -29,6 +29,7 @@
     "//chrome/browser/ash/system_web_apps/apps/personalization_app",
     "//chrome/browser/lifetime:termination_notification",
     "//chrome/browser/policy:onc",
+    "//chrome/browser/profiles",
     "//chrome/browser/profiles:profile",
     "//chrome/browser/ui/ash/login",
     "//chrome/browser/ui/ash/multi_user",
diff --git a/chrome/browser/ui/hats/BUILD.gn b/chrome/browser/ui/hats/BUILD.gn
index 4b4d84e..792aa857 100644
--- a/chrome/browser/ui/hats/BUILD.gn
+++ b/chrome/browser/ui/hats/BUILD.gn
@@ -58,6 +58,7 @@
     "//chrome/browser:browser_process",
     "//chrome/browser/content_settings:content_settings_factory",
     "//chrome/browser/prefs",
+    "//chrome/browser/profiles",
     "//chrome/browser/ui",
     "//chrome/browser/ui/safety_hub",
     "//chrome/common",
diff --git a/chrome/browser/ui/hats/survey_config.cc b/chrome/browser/ui/hats/survey_config.cc
index 6e30da50..1a259d2 100644
--- a/chrome/browser/ui/hats/survey_config.cc
+++ b/chrome/browser/ui/hats/survey_config.cc
@@ -880,11 +880,6 @@
 
   user_prompted =
       base::FeatureParam<bool>(feature, "user_prompted", false).Get();
-
-#if BUILDFLAG(IS_ANDROID)
-  CHECK(requested_browser_type == RequestedBrowserType::kRegular)
-      << "HaTS on Android supports only RequestedBrowserType::kRegular";
-#endif
 }
 
 // static
diff --git a/chrome/browser/ui/managed_ui_browsertest.cc b/chrome/browser/ui/managed_ui_browsertest.cc
index c264648..2501eb6 100644
--- a/chrome/browser/ui/managed_ui_browsertest.cc
+++ b/chrome/browser/ui/managed_ui_browsertest.cc
@@ -243,7 +243,13 @@
 // On ChromeOS we don't display the management UI for enterprise or supervised
 // users.
 #if !BUILDFLAG(IS_CHROMEOS)
-IN_PROC_BROWSER_TEST_F(ManagedUiTest, GetManagedUiIconEnterprise) {
+// TODO(https://crbug.com/410751413): Test flaky on Windows.
+#if BUILDFLAG(IS_WIN)
+#define MAYBE_GetManagedUiIconEnterprise DISABLED_GetManagedUiIconEnterprise
+#else
+#define MAYBE_GetManagedUiIconEnterprise GetManagedUiIconEnterprise
+#endif
+IN_PROC_BROWSER_TEST_F(ManagedUiTest, MAYBE_GetManagedUiIconEnterprise) {
   // Simulate a managed device.
   AddEnterpriseManagedPolicies();
   policy::ScopedManagementServiceOverrideForTesting browser_management(
diff --git a/chrome/browser/ui/search_engine_choice/BUILD.gn b/chrome/browser/ui/search_engine_choice/BUILD.gn
index 18a3c54..36338bf 100644
--- a/chrome/browser/ui/search_engine_choice/BUILD.gn
+++ b/chrome/browser/ui/search_engine_choice/BUILD.gn
@@ -17,6 +17,7 @@
     deps = [
       ":search_engine_choice",
       "//base",
+      "//chrome/browser/profiles",
       "//chrome/browser/profiles:profile",
       "//chrome/browser/search_engine_choice",
       "//components/search_engines",
diff --git a/chrome/browser/ui/views/bookmarks/bookmark_editor_view.cc b/chrome/browser/ui/views/bookmarks/bookmark_editor_view.cc
index 42b91736f..b2adec45 100644
--- a/chrome/browser/ui/views/bookmarks/bookmark_editor_view.cc
+++ b/chrome/browser/ui/views/bookmarks/bookmark_editor_view.cc
@@ -30,6 +30,7 @@
 #include "components/bookmarks/common/bookmark_metrics.h"
 #include "components/constrained_window/constrained_window_views.h"
 #include "components/history/core/browser/history_service.h"
+#include "components/signin/public/base/signin_switches.h"
 #include "components/strings/grit/components_strings.h"
 #include "components/url_formatter/url_fixer.h"
 #include "components/user_prefs/user_prefs.h"
@@ -488,13 +489,19 @@
 }
 
 void BookmarkEditorView::ExpandAndSelect() {
-  BookmarkExpandedStateTracker::Nodes expanded_nodes =
-      expanded_state_tracker_->GetExpandedNodes();
-  for (const BookmarkNode* node : expanded_nodes) {
-    EditorNode* editor_node =
-        FindNodeWithID(tree_model_->GetRoot(), node->id());
-    if (editor_node) {
-      tree_view_->Expand(editor_node);
+  // Only expand tracked nodes if the feature flag is disabled. With the flag
+  // enabled, only the nodes leading up to the selected node's parent should be
+  // expanded.
+  if (!base::FeatureList::IsEnabled(
+          switches::kSyncEnableBookmarksInTransportMode)) {
+    BookmarkExpandedStateTracker::Nodes expanded_nodes =
+        expanded_state_tracker_->GetExpandedNodes();
+    for (const BookmarkNode* node : expanded_nodes) {
+      EditorNode* editor_node =
+          FindNodeWithID(tree_model_->GetRoot(), node->id());
+      if (editor_node) {
+        tree_view_->Expand(editor_node);
+      }
     }
   }
 
diff --git a/chrome/browser/ui/views/bookmarks/bookmark_editor_view.h b/chrome/browser/ui/views/bookmarks/bookmark_editor_view.h
index 2849ffe..c75aa1d 100644
--- a/chrome/browser/ui/views/bookmarks/bookmark_editor_view.h
+++ b/chrome/browser/ui/views/bookmarks/bookmark_editor_view.h
@@ -161,7 +161,9 @@
 
   // Expands all the nodes in the tree and selects the parent node of the
   // url we're editing or the most recent parent if the url being editted isn't
-  // starred.
+  // starred. If the feature flag `SyncEnableBookmarksInTransportMode`, this
+  // only expands the nodes leading up to the parent node. All others remain
+  // collapsed.
   void ExpandAndSelect();
 
   // Returns true if a bookmark folder is currently selected.
diff --git a/chrome/browser/ui/views/bookmarks/bookmark_editor_view_unittest.cc b/chrome/browser/ui/views/bookmarks/bookmark_editor_view_unittest.cc
index 8ea97806..34256ba6 100644
--- a/chrome/browser/ui/views/bookmarks/bookmark_editor_view_unittest.cc
+++ b/chrome/browser/ui/views/bookmarks/bookmark_editor_view_unittest.cc
@@ -24,7 +24,9 @@
 #include "components/bookmarks/browser/bookmark_model.h"
 #include "components/bookmarks/browser/bookmark_utils.h"
 #include "components/bookmarks/common/bookmark_metrics.h"
+#include "components/bookmarks/common/bookmark_pref_names.h"
 #include "components/bookmarks/test/bookmark_test_helpers.h"
+#include "components/prefs/scoped_user_pref_update.h"
 #include "components/signin/public/base/signin_switches.h"
 #include "components/strings/grit/components_strings.h"
 #include "content/public/test/browser_task_environment.h"
@@ -918,4 +920,43 @@
   EXPECT_FALSE(editor()->IsNewFolderButtonEnabledForTesting());
 }
 
+TEST_P(BookmarkEditorViewTest,
+       OnlyExpandTrackedNodesWithoutFeatureFlagEnabled) {
+  // Configure the local bookmarks bar to be tracked as expanded.
+  ScopedListPrefUpdate update(profile_->GetPrefs(),
+                              bookmarks::prefs::kBookmarkEditorExpandedNodes);
+  base::Value::List& initial_expanded_nodes_list = update.Get();
+  initial_expanded_nodes_list.Append(
+      base::NumberToString(model()->bookmark_bar_node()->id()));
+
+  // Open the editor with a node saved under the local other bookmarks folder.
+  CreateEditor(profile_.get(),
+               BookmarkEditor::EditDetails::EditNode(GetNode("oa")),
+               BookmarkEditorView::SHOW_TREE);
+  ExpandAndSelect();
+
+  // The node being edited should always be visible.
+  EXPECT_TRUE(GetNode("oa")->IsVisible());
+
+  if (!SyncEnableBookmarksInTransportModeEnabled()) {
+    // The local bookmarks bar should still be open in the tree view.
+    EXPECT_TRUE(tree_view()->IsExpanded(local_bookmark_bar_editor_node()));
+    return;
+  }
+
+  // The local bookmarks bar should no longer be open in the tree view.
+  EXPECT_FALSE(tree_view()->IsExpanded(local_bookmark_bar_editor_node()));
+
+  // The same behavior is expected with account nodes.
+  initial_expanded_nodes_list.Append(
+      base::NumberToString(model()->account_bookmark_bar_node()->id()));
+  ApplyEdits();
+  CreateEditor(profile_.get(),
+               BookmarkEditor::EditDetails::EditNode(GetNode("acc_oa")),
+               BookmarkEditorView::SHOW_TREE);
+  ExpandAndSelect();
+  EXPECT_TRUE(GetNode("acc_oa")->IsVisible());
+  EXPECT_FALSE(tree_view()->IsExpanded(account_bookmark_bar_editor_node()));
+}
+
 INSTANTIATE_FEATURE_OVERRIDE_TEST_SUITE(BookmarkEditorViewTest);
diff --git a/chrome/browser/ui/views/passwords/password_bubble_interactive_uitest.cc b/chrome/browser/ui/views/passwords/password_bubble_interactive_uitest.cc
index 752ff85..30f4fb7 100644
--- a/chrome/browser/ui/views/passwords/password_bubble_interactive_uitest.cc
+++ b/chrome/browser/ui/views/passwords/password_bubble_interactive_uitest.cc
@@ -345,8 +345,14 @@
   EXPECT_TRUE(IsBubbleShowing());
 }
 
+// TODO(https://crbug.com/410751413): Test is flake on Mac.
+#if BUILDFLAG(IS_MAC)
+#define MAYBE_TwoTabsWithBubbleSwitch DISABLED_TwoTabsWithBubbleSwitch
+#else
+#define MAYBE_TwoTabsWithBubbleSwitch TwoTabsWithBubbleSwitch
+#endif
 IN_PROC_BROWSER_TEST_F(PasswordBubbleInteractiveUiTest,
-                       TwoTabsWithBubbleSwitch) {
+                       MAYBE_TwoTabsWithBubbleSwitch) {
   // Set up the first tab with the bubble.
   SetupPendingPassword();
   EXPECT_TRUE(IsBubbleShowing());
diff --git a/chrome/browser/ui/views/tab_sharing/tab_sharing_infobar.cc b/chrome/browser/ui/views/tab_sharing/tab_sharing_infobar.cc
index 94eba49..f44019c 100644
--- a/chrome/browser/ui/views/tab_sharing/tab_sharing_infobar.cc
+++ b/chrome/browser/ui/views/tab_sharing/tab_sharing_infobar.cc
@@ -193,6 +193,7 @@
   if (!owner()) {
     return;  // We're closing; don't call anything, it might access the owner.
   }
+  RecordUma(TabSharingInfoBarInteraction::kStopButtonClicked);
   GetDelegate()->Stop();
 }
 
@@ -200,6 +201,7 @@
   if (!owner()) {
     return;  // We're closing; don't call anything, it might access the owner.
   }
+  RecordUma(TabSharingInfoBarInteraction::kShareThisTabInsteadButtonClicked);
   GetDelegate()->ShareThisTabInstead();
 }
 
@@ -224,10 +226,14 @@
     const std::u16string& capturer_name,
     TabSharingInfoBarDelegate::TabRole role,
     TabSharingInfoBarDelegate::TabShareType capture_type) const {
-  TabSharingStatusMessageView::EndpointInfo shared_tab_info(shared_tab_name,
-                                                            shared_tab_id);
-  TabSharingStatusMessageView::EndpointInfo capturer_info(capturer_name,
-                                                          capturer_id);
+  TabSharingStatusMessageView::EndpointInfo shared_tab_info(
+      shared_tab_name,
+      TabSharingStatusMessageView::EndpointInfo::TargetType::kCapturedTab,
+      shared_tab_id);
+  TabSharingStatusMessageView::EndpointInfo capturer_info(
+      capturer_name,
+      TabSharingStatusMessageView::EndpointInfo::TargetType::kCapturingTab,
+      capturer_id);
   if (base::FeatureList::IsEnabled(features::kTabCaptureInfobarLinks) &&
       GetOriginFromId(capturer_id).scheme() != extensions::kExtensionScheme) {
     return TabSharingStatusMessageView::Create(capturer_id, shared_tab_info,
diff --git a/chrome/browser/ui/views/tab_sharing/tab_sharing_infobar_utils.cc b/chrome/browser/ui/views/tab_sharing/tab_sharing_infobar_utils.cc
new file mode 100644
index 0000000..a80c7a5
--- /dev/null
+++ b/chrome/browser/ui/views/tab_sharing/tab_sharing_infobar_utils.cc
@@ -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.
+
+#include "chrome/browser/ui/views/tab_sharing/tab_sharing_infobar_utils.h"
+
+#include "base/metrics/histogram_functions.h"
+
+namespace {
+
+// The histogram name must be kept in sync with the definition in
+// tools/metrics/histograms/metadata/media/histograms.xml.
+const char kTabSharingInfoBarInteractionHistogram[] =
+    "Media.Ui.GetDisplayMedia.TabSharingInfoBarInteraction";
+
+}  // namespace
+
+void RecordUma(TabSharingInfoBarInteraction interaction) {
+  base::UmaHistogramEnumeration(kTabSharingInfoBarInteractionHistogram,
+                                interaction);
+}
diff --git a/chrome/browser/ui/views/tab_sharing/tab_sharing_infobar_utils.h b/chrome/browser/ui/views/tab_sharing/tab_sharing_infobar_utils.h
new file mode 100644
index 0000000..d013a4d
--- /dev/null
+++ b/chrome/browser/ui/views/tab_sharing/tab_sharing_infobar_utils.h
@@ -0,0 +1,26 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_VIEWS_TAB_SHARING_TAB_SHARING_INFOBAR_UTILS_H_
+#define CHROME_BROWSER_UI_VIEWS_TAB_SHARING_TAB_SHARING_INFOBAR_UTILS_H_
+
+// This enum is used to record UMA histogram metrics for interactions
+// with the TabSharingInfoBar.
+// These values are persisted to logs. Entries should not be renumbered and
+// numeric values should never be reused.
+// Keep this in sync with MediaUiGetDisplayMediaTabSharingInfoBarInteraction in
+// tools/metrics/histograms/metadata/media/enums.xml.
+enum class TabSharingInfoBarInteraction {
+  kCapturedToCapturing = 0,
+  kCapturingToCaptured = 1,
+  kOtherToCapturing = 2,
+  kOtherToCaptured = 3,
+  kStopButtonClicked = 4,
+  kShareThisTabInsteadButtonClicked = 5,
+  kMaxValue = kShareThisTabInsteadButtonClicked,
+};
+
+void RecordUma(TabSharingInfoBarInteraction interaction);
+
+#endif  // CHROME_BROWSER_UI_VIEWS_TAB_SHARING_TAB_SHARING_INFOBAR_UTILS_H_
diff --git a/chrome/browser/ui/views/tab_sharing/tab_sharing_status_message_view.cc b/chrome/browser/ui/views/tab_sharing/tab_sharing_status_message_view.cc
index 4575d6bc..5ffc9b91 100644
--- a/chrome/browser/ui/views/tab_sharing/tab_sharing_status_message_view.cc
+++ b/chrome/browser/ui/views/tab_sharing/tab_sharing_status_message_view.cc
@@ -17,6 +17,7 @@
 #include "ui/views/view_class_properties.h"
 
 namespace {
+using EndpointInfo = ::TabSharingStatusMessageView::EndpointInfo;
 using MessageInfo = ::TabSharingStatusMessageView::MessageInfo;
 using TabRole = ::TabSharingInfoBarDelegate::TabRole;
 
@@ -24,17 +25,45 @@
 constexpr auto kSeparatorInsets = gfx::Insets::TLBR(0, 16, 0, 0);
 
 std::vector<std::u16string> EndpointInfosToStrings(
-    const std::vector<TabSharingStatusMessageView::EndpointInfo>&
-        endpoint_infos) {
+    const std::vector<EndpointInfo>& endpoint_infos) {
   std::vector<std::u16string> res;
-  for (const TabSharingStatusMessageView::EndpointInfo& endpoint_info :
-       endpoint_infos) {
+  for (const EndpointInfo& endpoint_info : endpoint_infos) {
     res.push_back(endpoint_info.text);
   }
   return res;
 }
 
-void ActivateWebContents(content::GlobalRenderFrameHostId focus_target_id) {
+TabSharingInfoBarInteraction GetTabSharingInfoBarInteraction(
+    TabRole tab_role_for_uma,
+    EndpointInfo::TargetType target_type) {
+  switch (tab_role_for_uma) {
+    case TabRole::kCapturingTab:
+      CHECK_EQ(target_type, EndpointInfo::TargetType::kCapturedTab);
+      return TabSharingInfoBarInteraction::kCapturingToCaptured;
+    case TabRole::kCapturedTab:
+      CHECK_EQ(target_type, EndpointInfo::TargetType::kCapturingTab);
+      return TabSharingInfoBarInteraction::kCapturedToCapturing;
+    case TabRole::kSelfCapturingTab:
+      NOTREACHED();
+    case TabRole::kOtherTab:
+      switch (target_type) {
+        case EndpointInfo::TargetType::kCapturedTab:
+          return TabSharingInfoBarInteraction::kOtherToCaptured;
+        case EndpointInfo::TargetType::kCapturingTab:
+          return TabSharingInfoBarInteraction::kOtherToCapturing;
+      }
+  }
+  NOTREACHED();
+}
+
+void ActivateWebContents(
+    content::GlobalRenderFrameHostId focus_target_id,
+    std::optional<TabSharingInfoBarDelegate::TabRole> tab_role_for_uma,
+    EndpointInfo::TargetType target_type) {
+  if (tab_role_for_uma) {
+    RecordUma(GetTabSharingInfoBarInteraction(*tab_role_for_uma, target_type));
+  }
+
   content::RenderFrameHost* const rfh =
       content::RenderFrameHost::FromID(focus_target_id);
   if (!rfh) {
@@ -52,79 +81,78 @@
 
 MessageInfo GetMessageInfoCastingNoSinkName(
     TabRole role,
-    const TabSharingStatusMessageView::EndpointInfo& shared_tab_info) {
+    const EndpointInfo& shared_tab_info) {
   if (TabSharingInfoBarDelegate::IsCapturedTab(role)) {
     return MessageInfo(
         IDS_TAB_CASTING_INFOBAR_CASTING_CURRENT_TAB_NO_DEVICE_NAME_LABEL,
-        /*endpoint_infos=*/{});
+        /*endpoint_infos=*/{}, role);
   }
   return shared_tab_info.text.empty()
              ? MessageInfo(
                    IDS_TAB_CASTING_INFOBAR_CASTING_ANOTHER_UNTITLED_TAB_NO_DEVICE_NAME_LABEL,
-                   /*endpoint_infos=*/{})
+                   /*endpoint_infos=*/{}, role)
              : MessageInfo(
                    IDS_TAB_CASTING_INFOBAR_CASTING_ANOTHER_TAB_NO_DEVICE_NAME_LABEL,
-                   {shared_tab_info});
+                   {shared_tab_info}, role);
 }
 
-MessageInfo GetMessageInfoCasting(
-    TabRole role,
-    const TabSharingStatusMessageView::EndpointInfo& shared_tab_info,
-    const std::u16string& sink_name) {
+MessageInfo GetMessageInfoCasting(TabRole role,
+                                  const EndpointInfo& shared_tab_info,
+                                  const std::u16string& sink_name) {
   if (sink_name.empty()) {
     return GetMessageInfoCastingNoSinkName(role, shared_tab_info);
   }
 
-  TabSharingStatusMessageView::EndpointInfo sink_info(
-      sink_name, content::GlobalRenderFrameHostId());
+  EndpointInfo sink_info(sink_name, EndpointInfo::TargetType::kCapturingTab,
+                         content::GlobalRenderFrameHostId());
 
   if (TabSharingInfoBarDelegate::IsCapturedTab(role)) {
     return MessageInfo(IDS_TAB_CASTING_INFOBAR_CASTING_CURRENT_TAB_LABEL,
-                       {sink_info});
+                       {sink_info}, role);
   }
   return shared_tab_info.text.empty()
              ? MessageInfo(
                    IDS_TAB_CASTING_INFOBAR_CASTING_ANOTHER_UNTITLED_TAB_LABEL,
-                   {sink_info})
+                   {sink_info}, role)
              : MessageInfo(IDS_TAB_CASTING_INFOBAR_CASTING_ANOTHER_TAB_LABEL,
-                           {shared_tab_info, sink_info});
+                           {shared_tab_info, sink_info}, role);
 }
 
-MessageInfo GetMessageInfoCapturing(
-    TabRole role,
-    const TabSharingStatusMessageView::EndpointInfo& shared_tab_info,
-    const TabSharingStatusMessageView::EndpointInfo& capturer_info) {
+MessageInfo GetMessageInfoCapturing(TabRole role,
+                                    const EndpointInfo& shared_tab_info,
+                                    const EndpointInfo& capturer_info) {
   if (role == TabRole::kSelfCapturingTab) {
-    return MessageInfo(
-        IDS_TAB_SHARING_INFOBAR_SHARING_CURRENT_TAB_LABEL,
-        {TabSharingStatusMessageView::EndpointInfo(capturer_info.text)});
+    return MessageInfo(IDS_TAB_SHARING_INFOBAR_SHARING_CURRENT_TAB_LABEL,
+                       {EndpointInfo(capturer_info.text,
+                                     EndpointInfo::TargetType::kCapturingTab)},
+                       role);
   }
 
   if (TabSharingInfoBarDelegate::IsCapturedTab(role)) {
     return MessageInfo(IDS_TAB_SHARING_INFOBAR_SHARING_CURRENT_TAB_LABEL,
-                       {capturer_info});
+                       {capturer_info}, role);
   }
 
   if (shared_tab_info.text.empty()) {
     return MessageInfo(
         IDS_TAB_SHARING_INFOBAR_SHARING_ANOTHER_UNTITLED_TAB_LABEL,
-        {capturer_info});
+        {capturer_info}, role);
   }
 
   if (base::FeatureList::IsEnabled(features::kTabCaptureInfobarLinks) &&
       TabSharingInfoBarDelegate::IsCapturingTab(role)) {
     return MessageInfo(
         IDS_TAB_SHARING_INFOBAR_SHARING_ANOTHER_TAB_TO_THIS_TAB_LABEL,
-        {shared_tab_info});
+        {shared_tab_info}, role);
   }
 
   return MessageInfo(IDS_TAB_SHARING_INFOBAR_SHARING_ANOTHER_TAB_LABEL,
-                     {shared_tab_info, capturer_info});
+                     {shared_tab_info, capturer_info}, role);
 }
 
 MessageInfo GetMessageInfo(
-    const TabSharingStatusMessageView::EndpointInfo& shared_tab_info,
-    const TabSharingStatusMessageView::EndpointInfo& capturer_info,
+    const EndpointInfo& shared_tab_info,
+    const EndpointInfo& capturer_info,
     const std::u16string& capturer_name,
     TabSharingInfoBarDelegate::TabRole role,
     TabSharingInfoBarDelegate::TabShareType capture_type) {
@@ -140,21 +168,29 @@
 
 }  // namespace
 
-TabSharingStatusMessageView::EndpointInfo::EndpointInfo(
-    std::u16string text,
-    content::GlobalRenderFrameHostId focus_target_id)
-    : text(std::move(text)), focus_target_id(focus_target_id) {}
+EndpointInfo::EndpointInfo(std::u16string text,
+                           TargetType target_type,
+                           content::GlobalRenderFrameHostId focus_target_id)
+    : text(std::move(text)),
+      target_type(target_type),
+      focus_target_id(focus_target_id) {}
 
-MessageInfo::MessageInfo(int message_id,
-                         std::vector<EndpointInfo> endpoint_infos)
+MessageInfo::MessageInfo(
+    int message_id,
+    std::vector<EndpointInfo> endpoint_infos,
+    std::optional<TabSharingInfoBarDelegate::TabRole> tab_role_for_uma)
     : MessageInfo(ui::ResourceBundle::GetSharedInstance().GetLocalizedString(
                       message_id),
-                  std::move(endpoint_infos)) {}
+                  std::move(endpoint_infos),
+                  tab_role_for_uma) {}
 
-MessageInfo::MessageInfo(std::u16string format_string,
-                         std::vector<EndpointInfo> endpoint_infos)
+MessageInfo::MessageInfo(
+    std::u16string format_string,
+    std::vector<EndpointInfo> endpoint_infos,
+    std::optional<TabSharingInfoBarDelegate::TabRole> tab_role_for_uma)
     : format_string(std::move(format_string)),
-      endpoint_infos(std::move(endpoint_infos)) {}
+      endpoint_infos(std::move(endpoint_infos)),
+      tab_role_for_uma(tab_role_for_uma) {}
 
 MessageInfo::~MessageInfo() = default;
 
@@ -168,8 +204,8 @@
 
 std::unique_ptr<views::View> TabSharingStatusMessageView::Create(
     content::GlobalRenderFrameHostId capturer_id,
-    const TabSharingStatusMessageView::EndpointInfo& shared_tab_info,
-    const TabSharingStatusMessageView::EndpointInfo& capturer_info,
+    const EndpointInfo& shared_tab_info,
+    const EndpointInfo& capturer_info,
     const std::u16string& capturer_name,
     TabSharingInfoBarDelegate::TabRole role,
     TabSharingInfoBarDelegate::TabShareType capture_type) {
@@ -178,8 +214,8 @@
 }
 
 std::u16string TabSharingStatusMessageView::GetMessageText(
-    const TabSharingStatusMessageView::EndpointInfo& shared_tab_info,
-    const TabSharingStatusMessageView::EndpointInfo& capturer_info,
+    const EndpointInfo& shared_tab_info,
+    const EndpointInfo& capturer_info,
     const std::u16string& capturer_name,
     TabSharingInfoBarDelegate::TabRole role,
     TabSharingInfoBarDelegate::TabShareType capture_type) {
@@ -214,8 +250,7 @@
   // should go.
   std::vector<size_t> offsets;
   std::vector<std::u16string> replacements;
-  for (const TabSharingStatusMessageView::EndpointInfo& endpoint_info :
-       info.endpoint_infos) {
+  for (const EndpointInfo& endpoint_info : info.endpoint_infos) {
     replacements.emplace_back(endpoint_info.text);
   }
   const std::u16string label_text =
@@ -251,7 +286,8 @@
       AddLabel(label_text.substr(label_start, label_length),
                flex_layout_order++);
     }
-    AddButton(info.endpoint_infos[i], flex_layout_order++);
+    AddButton(info.endpoint_infos[i], flex_layout_order++,
+              info.tab_role_for_uma);
     label_start = offsets[i] + replacements[i].size();
   }
 
@@ -277,12 +313,15 @@
           .WithOrder(flex_layout_order));
 }
 
-void TabSharingStatusMessageView::AddButton(const EndpointInfo& endpoint_info,
-                                            int flex_layout_order) {
+void TabSharingStatusMessageView::AddButton(
+    const EndpointInfo& endpoint_info,
+    int flex_layout_order,
+    std::optional<TabSharingInfoBarDelegate::TabRole> tab_role_for_uma) {
   views::MdTextButton* button =
       AddChildView(std::make_unique<views::MdTextButton>(
           base::BindRepeating(&ActivateWebContents,
-                              endpoint_info.focus_target_id),
+                              endpoint_info.focus_target_id, tab_role_for_uma,
+                              endpoint_info.target_type),
           endpoint_info.text, views::style::CONTEXT_LABEL));
   button->SetStyle(ui::ButtonStyle::kTonal);
   button->SetCustomPadding(kButtonInsets);
diff --git a/chrome/browser/ui/views/tab_sharing/tab_sharing_status_message_view.h b/chrome/browser/ui/views/tab_sharing/tab_sharing_status_message_view.h
index 6455053..b7a40ba 100644
--- a/chrome/browser/ui/views/tab_sharing/tab_sharing_status_message_view.h
+++ b/chrome/browser/ui/views/tab_sharing/tab_sharing_status_message_view.h
@@ -6,6 +6,7 @@
 #define CHROME_BROWSER_UI_VIEWS_TAB_SHARING_TAB_SHARING_STATUS_MESSAGE_VIEW_H_
 
 #include "chrome/browser/ui/tab_sharing/tab_sharing_infobar_delegate.h"
+#include "chrome/browser/ui/views/tab_sharing/tab_sharing_infobar_utils.h"
 #include "ui/views/controls/button/md_text_button.h"
 #include "ui/views/controls/label.h"
 #include "ui/views/view.h"
@@ -19,18 +20,29 @@
       std::variant<raw_ptr<views::Label>, raw_ptr<views::MdTextButton>>;
 
   struct EndpointInfo final {
+    enum class TargetType {
+      kCapturedTab,
+      kCapturingTab,
+    };
     explicit EndpointInfo(std::u16string text,
+                          TargetType target_type,
                           content::GlobalRenderFrameHostId focus_target_id =
                               content::GlobalRenderFrameHostId());
 
     std::u16string text;
+    TargetType target_type;
     content::GlobalRenderFrameHostId focus_target_id;
   };
 
   struct MessageInfo final {
-    MessageInfo(int message_id, std::vector<EndpointInfo> endpoint_infos);
+    MessageInfo(int message_id,
+                std::vector<EndpointInfo> endpoint_infos,
+                std::optional<TabSharingInfoBarDelegate::TabRole>
+                    tab_role_for_uma = std::nullopt);
     MessageInfo(std::u16string format_string,
-                std::vector<EndpointInfo> endpoint_infos);
+                std::vector<EndpointInfo> endpoint_infos,
+                std::optional<TabSharingInfoBarDelegate::TabRole>
+                    tab_role_for_uma = std::nullopt);
     ~MessageInfo();
 
     MessageInfo(const MessageInfo& other);
@@ -41,6 +53,7 @@
 
     std::u16string format_string;
     std::vector<EndpointInfo> endpoint_infos;
+    std::optional<TabSharingInfoBarDelegate::TabRole> tab_role_for_uma;
   };
 
   static std::unique_ptr<views::View> Create(
@@ -70,7 +83,10 @@
  private:
   void SetupMessage(MessageInfo info);
   void AddLabel(const std::u16string& text, int flex_layout_order);
-  void AddButton(const EndpointInfo& endpoint_info, int flex_layout_order);
+  void AddButton(
+      const EndpointInfo& endpoint_info,
+      int flex_layout_order,
+      std::optional<TabSharingInfoBarDelegate::TabRole> tab_role_for_uma);
 };
 
 #endif  // CHROME_BROWSER_UI_VIEWS_TAB_SHARING_TAB_SHARING_STATUS_MESSAGE_VIEW_H_
diff --git a/chrome/browser/ui/views/tab_sharing/tab_sharing_status_message_view_unittest.cc b/chrome/browser/ui/views/tab_sharing/tab_sharing_status_message_view_unittest.cc
index f5b2b99..c293d26 100644
--- a/chrome/browser/ui/views/tab_sharing/tab_sharing_status_message_view_unittest.cc
+++ b/chrome/browser/ui/views/tab_sharing/tab_sharing_status_message_view_unittest.cc
@@ -12,6 +12,7 @@
 namespace {
 using EndpointInfo = TabSharingStatusMessageView::EndpointInfo;
 using MessageInfo = TabSharingStatusMessageView::MessageInfo;
+using TabRole = TabSharingInfoBarDelegate::TabRole;
 using content::GlobalRenderFrameHostId;
 using ::testing::ElementsAreArray;
 
@@ -26,12 +27,18 @@
   return texts;
 }
 
-EndpointInfo kTab1 = EndpointInfo(u"Tab1", GlobalRenderFrameHostId(1, 1));
-EndpointInfo kTab2 = EndpointInfo(u"Tab2", GlobalRenderFrameHostId(2, 2));
-EndpointInfo kWithoutId1 =
-    EndpointInfo(u"WithoutId1", GlobalRenderFrameHostId());
-EndpointInfo kWithoutId2 =
-    EndpointInfo(u"WithoutId2", GlobalRenderFrameHostId());
+EndpointInfo kTab1 = EndpointInfo(u"Tab1",
+                                  EndpointInfo::TargetType::kCapturedTab,
+                                  GlobalRenderFrameHostId(1, 1));
+EndpointInfo kTab2 = EndpointInfo(u"Tab2",
+                                  EndpointInfo::TargetType::kCapturingTab,
+                                  GlobalRenderFrameHostId(2, 2));
+EndpointInfo kWithoutId1 = EndpointInfo(u"WithoutId1",
+                                        EndpointInfo::TargetType::kCapturedTab,
+                                        GlobalRenderFrameHostId());
+EndpointInfo kWithoutId2 = EndpointInfo(u"WithoutId2",
+                                        EndpointInfo::TargetType::kCapturingTab,
+                                        GlobalRenderFrameHostId());
 }  // namespace
 
 class TabSharingStatusMessageViewTest : public ::testing::Test {
diff --git a/chrome/browser/ui/views/toolbar/BUILD.gn b/chrome/browser/ui/views/toolbar/BUILD.gn
index 03cd551..7660ce8 100644
--- a/chrome/browser/ui/views/toolbar/BUILD.gn
+++ b/chrome/browser/ui/views/toolbar/BUILD.gn
@@ -108,6 +108,7 @@
     "//chrome/browser/feedback",
     "//chrome/browser/media/router:media_router_feature",
     "//chrome/browser/prefs",
+    "//chrome/browser/profiles",
     "//chrome/browser/profiles:profile",
     "//chrome/browser/profiles:profile_util",
     "//chrome/browser/search",
diff --git a/chrome/browser/ui/web_applications/BUILD.gn b/chrome/browser/ui/web_applications/BUILD.gn
index 3e5dde8..dc4ffdd2 100644
--- a/chrome/browser/ui/web_applications/BUILD.gn
+++ b/chrome/browser/ui/web_applications/BUILD.gn
@@ -20,6 +20,7 @@
     "//chrome/browser/apps/app_service",
     "//chrome/browser/ui",
     "//chrome/browser/web_applications",
+    "//chrome/browser/web_applications:web_app_test",
     "//chrome/browser/web_applications:web_applications_test_support",
     "//chrome/browser/web_applications:web_applications_unit_tests",
     "//chrome/test:test_support",
diff --git a/chrome/browser/ui/webui/BUILD.gn b/chrome/browser/ui/webui/BUILD.gn
index 6d3b7f5..9efae93 100644
--- a/chrome/browser/ui/webui/BUILD.gn
+++ b/chrome/browser/ui/webui/BUILD.gn
@@ -53,6 +53,7 @@
   if (!is_android) {
     deps += [
       "//chrome/browser/ui/webui/access_code_cast",
+      "//chrome/browser/ui/webui/app_service_internals",
       "//chrome/browser/ui/webui/new_tab_footer",
       "//chrome/browser/ui/webui/privacy_sandbox",
     ]
diff --git a/chrome/browser/ui/webui/app_service_internals/BUILD.gn b/chrome/browser/ui/webui/app_service_internals/BUILD.gn
index 8fba0a9..55f13ac 100644
--- a/chrome/browser/ui/webui/app_service_internals/BUILD.gn
+++ b/chrome/browser/ui/webui/app_service_internals/BUILD.gn
@@ -8,3 +8,47 @@
   sources = [ "app_service_internals.mojom" ]
   webui_module_path = "/"
 }
+
+if (!is_android) {
+  source_set("app_service_internals") {
+    sources = [
+      "app_service_internals_page_handler_impl.cc",
+      "app_service_internals_page_handler_impl.h",
+      "app_service_internals_ui.cc",
+      "app_service_internals_ui.h",
+    ]
+
+    public_deps = [
+      ":mojo_bindings",
+      "//base",
+      "//chrome/browser/profiles:profile",
+      "//content/public/browser",
+      "//mojo/public/cpp/bindings",
+      "//ui/webui",
+    ]
+
+    deps = [
+      "//chrome/browser/apps/app_service",
+      "//chrome/browser/resources/app_service_internals:resources",
+      "//chrome/common",
+      "//components/services/app_service",
+    ]
+  }
+
+  source_set("browser_tests") {
+    testonly = true
+    defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ]
+
+    sources = [ "app_service_internals_browsertest.cc" ]
+
+    deps = [
+      "//chrome/browser/ui",
+      "//chrome/browser/ui/tabs:tab_strip",
+      "//chrome/browser/web_applications",
+      "//chrome/test:test_support",
+      "//chrome/test:test_support_ui",
+      "//content/test:test_support",
+      "//net:test_support",
+    ]
+  }
+}
diff --git a/chrome/browser/ui/webui/ash/floating_workspace/BUILD.gn b/chrome/browser/ui/webui/ash/floating_workspace/BUILD.gn
index b4602dd..f098134 100644
--- a/chrome/browser/ui/webui/ash/floating_workspace/BUILD.gn
+++ b/chrome/browser/ui/webui/ash/floating_workspace/BUILD.gn
@@ -8,6 +8,8 @@
   sources = [
     "floating_workspace_dialog.cc",
     "floating_workspace_dialog.h",
+    "floating_workspace_handler.cc",
+    "floating_workspace_handler.h",
     "floating_workspace_ui.cc",
     "floating_workspace_ui.h",
   ]
@@ -29,11 +31,13 @@
     "//chrome/browser/ash/floating_workspace:floating_workspace",
     "//chrome/browser/resources/chromeos/floating_workspace:resources",
     "//chrome/browser/ui/ash/login:login",
+    "//chrome/browser/ui/webui/ash/internet:internet",
     "//chrome/browser/ui/webui/ash/login:login",
     "//chromeos/ash/components/browser_context_helper:browser_context_helper",
     "//content/public/browser",
     "//ui/aura",
     "//ui/base:types",
+    "//ui/chromeos/strings:strings_provider",
     "//ui/display",
     "//ui/views",
     "//ui/webui",
diff --git a/chrome/browser/ui/webui/ash/floating_workspace/DEPS b/chrome/browser/ui/webui/ash/floating_workspace/DEPS
index 00efad7..c5cf251 100644
--- a/chrome/browser/ui/webui/ash/floating_workspace/DEPS
+++ b/chrome/browser/ui/webui/ash/floating_workspace/DEPS
@@ -5,6 +5,7 @@
   "+chrome/browser/ash/floating_workspace",
   "+chrome/browser/ui/ash/login",
   "+chrome/browser/ui/webui/ash/login",
+  "+chrome/browser/ui/webui/ash/internet",
   "+chrome/browser/ui/webui/ash/system_web_dialog",
   "+chrome/browser/ui/webui/webui_util.h",
   "+chrome/common",
diff --git a/chrome/browser/ui/webui/ash/floating_workspace/floating_workspace_dialog.cc b/chrome/browser/ui/webui/ash/floating_workspace/floating_workspace_dialog.cc
index 0050292..018f1eaa 100644
--- a/chrome/browser/ui/webui/ash/floating_workspace/floating_workspace_dialog.cc
+++ b/chrome/browser/ui/webui/ash/floating_workspace/floating_workspace_dialog.cc
@@ -9,6 +9,8 @@
 #include "chrome/browser/ash/floating_workspace/floating_workspace_service.h"
 #include "chrome/browser/ash/floating_workspace/floating_workspace_service_factory.h"
 #include "chrome/browser/ui/ash/login/oobe_dialog_size_utils.h"
+#include "chrome/browser/ui/webui/ash/floating_workspace/floating_workspace_handler.h"
+#include "chrome/browser/ui/webui/ash/floating_workspace/floating_workspace_ui.h"
 #include "chrome/browser/ui/webui/ash/system_web_dialog/system_web_dialog_delegate.h"
 #include "chrome/common/webui_url_constants.h"
 #include "chromeos/ash/components/browser_context_helper/browser_context_helper.h"
@@ -38,6 +40,21 @@
   g_dialog = nullptr;
 }
 
+FloatingWorkspaceDialogHandler* FloatingWorkspaceDialog::GetHandler() {
+  if (!g_dialog) {
+    return nullptr;
+  }
+  auto* web_ui = g_dialog->webui();
+  if (!web_ui) {
+    return nullptr;
+  }
+  auto* controller = web_ui->GetController();
+  if (!controller) {
+    return nullptr;
+  }
+  return static_cast<FloatingWorkspaceUI*>(controller)->GetMainHandler();
+}
+
 void FloatingWorkspaceDialog::GetDialogSize(gfx::Size* size) const {
   *size = CalculateOobeDialogSizeForPrimaryDisplay();
 }
@@ -52,8 +69,9 @@
     CHECK(browser_context);
     FloatingWorkspaceService* service =
         FloatingWorkspaceServiceFactory::GetForProfile(browser_context);
-    CHECK(service);
-    service->StopRestoringSession();
+    if (service) {
+      service->StopRestoringSession();
+    }
   } else if (!json_retval.empty()) {
     NOTREACHED();
   }
@@ -73,6 +91,37 @@
   return false;
 }
 
+gfx::NativeWindow FloatingWorkspaceDialog::GetNativeWindow() {
+  if (g_dialog) {
+    return g_dialog->dialog_window();
+  }
+  return nullptr;
+}
+
+void FloatingWorkspaceDialog::ShowDefaultScreen() {
+  FloatingWorkspaceDialog::Show();
+  FloatingWorkspaceDialogHandler* handler = GetHandler();
+  if (handler) {
+    handler->ShowDefaultScreen();
+  }
+}
+
+void FloatingWorkspaceDialog::ShowNetworkScreen() {
+  FloatingWorkspaceDialog::Show();
+  FloatingWorkspaceDialogHandler* handler = GetHandler();
+  if (handler) {
+    handler->ShowNetworkScreen();
+  }
+}
+
+void FloatingWorkspaceDialog::ShowErrorScreen() {
+  FloatingWorkspaceDialog::Show();
+  FloatingWorkspaceDialogHandler* handler = GetHandler();
+  if (handler) {
+    handler->ShowErrorScreen();
+  }
+}
+
 void FloatingWorkspaceDialog::Close() {
   if (g_dialog) {
     g_dialog->SystemWebDialogDelegate::Close();
@@ -91,4 +140,8 @@
   g_dialog->ShowSystemDialog();
 }
 
+bool FloatingWorkspaceDialog::IsShown() {
+  return g_dialog;
+}
+
 }  // namespace ash
diff --git a/chrome/browser/ui/webui/ash/floating_workspace/floating_workspace_dialog.h b/chrome/browser/ui/webui/ash/floating_workspace/floating_workspace_dialog.h
index d75fbcf..2dd90d2 100644
--- a/chrome/browser/ui/webui/ash/floating_workspace/floating_workspace_dialog.h
+++ b/chrome/browser/ui/webui/ash/floating_workspace/floating_workspace_dialog.h
@@ -9,6 +9,9 @@
 #include "ui/base/mojom/ui_base_types.mojom-shared.h"
 
 namespace ash {
+
+class FloatingWorkspaceDialogHandler;
+
 // This dialog is shown at startup during the delay in which floating
 // workspace fetches the state.
 class FloatingWorkspaceDialog : public SystemWebDialogDelegate {
@@ -17,14 +20,21 @@
   FloatingWorkspaceDialog& operator=(const FloatingWorkspaceDialog&) = delete;
   ~FloatingWorkspaceDialog() override;
 
-  // Displays the dialog.
-  static void Show();
+  static void ShowDefaultScreen();
+  static void ShowNetworkScreen();
+  static void ShowErrorScreen();
+  static bool IsShown();
 
   // Closes the dialog if it's currently opened.
   static void Close();
 
+  static gfx::NativeWindow GetNativeWindow();
+
  protected:
   FloatingWorkspaceDialog();
+  static FloatingWorkspaceDialogHandler* GetHandler();
+  // Creates and displays the dialog.
+  static void Show();
 
   // ui::WebDialogDelegate overrides
   void GetDialogSize(gfx::Size* size) const override;
@@ -33,7 +43,5 @@
   bool ShouldShowDialogTitle() const override;
   bool ShouldCloseDialogOnEscape() const override;
 };
-
 }  // namespace ash
-
 #endif  // CHROME_BROWSER_UI_WEBUI_ASH_FLOATING_WORKSPACE_FLOATING_WORKSPACE_DIALOG_H_
diff --git a/chrome/browser/ui/webui/ash/floating_workspace/floating_workspace_handler.cc b/chrome/browser/ui/webui/ash/floating_workspace/floating_workspace_handler.cc
new file mode 100644
index 0000000..64ad87b
--- /dev/null
+++ b/chrome/browser/ui/webui/ash/floating_workspace/floating_workspace_handler.cc
@@ -0,0 +1,152 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/ash/floating_workspace/floating_workspace_handler.h"
+
+#include <string>
+
+#include "base/functional/bind.h"
+#include "base/memory/weak_ptr.h"
+#include "base/values.h"
+#include "chrome/browser/ui/webui/ash/floating_workspace/floating_workspace_dialog.h"
+#include "chrome/browser/ui/webui/ash/internet/internet_config_dialog.h"
+#include "chrome/browser/ui/webui/ash/internet/internet_detail_dialog.h"
+#include "chromeos/ash/components/network/network_state_handler.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/web_ui.h"
+#include "content/public/browser/web_ui_data_source.h"
+#include "content/public/browser/web_ui_message_handler.h"
+#include "mojo/public/cpp/bindings/remote.h"
+
+namespace ash {
+
+namespace {
+constexpr char kInitialize[] = "initialize";
+constexpr char kAddNetwork[] = "addNetwork";
+constexpr char kShowNetworkDetails[] = "showNetworkDetails";
+constexpr char kShowNetworkConfig[] = "showNetworkConfig";
+constexpr char kGetHostname[] = "getHostname";
+
+const char kFloatingWorkspaceDialog[] = "$(\'floating-workspace-dialog\').";
+}  // namespace
+
+FloatingWorkspaceDialogHandler::FloatingWorkspaceDialogHandler() = default;
+
+FloatingWorkspaceDialogHandler::~FloatingWorkspaceDialogHandler() = default;
+
+void FloatingWorkspaceDialogHandler::RegisterMessages() {
+  web_ui()->RegisterMessageCallback(
+      kInitialize,
+      base::BindRepeating(&FloatingWorkspaceDialogHandler::Initialize,
+                          weak_ptr_factory_.GetWeakPtr()));
+  web_ui()->RegisterMessageCallback(
+      kAddNetwork,
+      base::BindRepeating(&FloatingWorkspaceDialogHandler::AddNetwork,
+                          weak_ptr_factory_.GetWeakPtr()));
+  web_ui()->RegisterMessageCallback(
+      kShowNetworkDetails,
+      base::BindRepeating(&FloatingWorkspaceDialogHandler::ShowNetworkDetails,
+                          weak_ptr_factory_.GetWeakPtr()));
+  web_ui()->RegisterMessageCallback(
+      kShowNetworkConfig,
+      base::BindRepeating(&FloatingWorkspaceDialogHandler::ShowNetworkConfig,
+                          weak_ptr_factory_.GetWeakPtr()));
+  web_ui()->RegisterMessageCallback(
+      kGetHostname,
+      base::BindRepeating(&FloatingWorkspaceDialogHandler::GetHostname,
+                          weak_ptr_factory_.GetWeakPtr()));
+}
+
+void FloatingWorkspaceDialogHandler::Initialize(const base::Value::List& args) {
+  AllowJavascript();
+  switch (state_) {
+    case DialogState::kDefault:
+      CallJavascriptFunction(std::string(kFloatingWorkspaceDialog) +
+                             "showDefaultScreen");
+      break;
+    case DialogState::kNetwork:
+      CallJavascriptFunction(std::string(kFloatingWorkspaceDialog) +
+                             "showNetworkScreen");
+      break;
+    case DialogState::kError:
+      CallJavascriptFunction(std::string(kFloatingWorkspaceDialog) +
+                             "showErrorScreen");
+      break;
+  }
+}
+
+void FloatingWorkspaceDialogHandler::ShowDefaultScreen() {
+  if (state_ != DialogState::kDefault && IsJavascriptAllowed()) {
+    CallJavascriptFunction(std::string(kFloatingWorkspaceDialog) +
+                           "showDefaultScreen");
+  }
+  state_ = DialogState::kDefault;
+}
+
+void FloatingWorkspaceDialogHandler::ShowNetworkScreen() {
+  if (state_ != DialogState::kNetwork && IsJavascriptAllowed()) {
+    CallJavascriptFunction(std::string(kFloatingWorkspaceDialog) +
+                           "showNetworkScreen");
+  }
+  state_ = DialogState::kNetwork;
+}
+
+void FloatingWorkspaceDialogHandler::ShowErrorScreen() {
+  if (state_ != DialogState::kError && IsJavascriptAllowed()) {
+    CallJavascriptFunction(std::string(kFloatingWorkspaceDialog) +
+                           "showErrorScreen");
+  }
+  state_ = DialogState::kError;
+}
+
+void FloatingWorkspaceDialogHandler::ShowNetworkDetails(
+    const base::Value::List& args) {
+  CHECK_EQ(1u, args.size());
+  std::string guid = args[0].GetString();
+
+  // We need to pass NativeWindow to the network dialog here, because otherwise
+  // the network dialog would be shown behind our main modal dialog.
+  auto dialog = ash::FloatingWorkspaceDialog::GetNativeWindow();
+  if (dialog) {
+    InternetDetailDialog::ShowDialog(guid, dialog);
+  }
+}
+
+void FloatingWorkspaceDialogHandler::ShowNetworkConfig(
+    const base::Value::List& args) {
+  CHECK_EQ(1u, args.size());
+  std::string guid = args[0].GetString();
+
+  // We need to pass NativeWindow to the network dialog here, because otherwise
+  // the network dialog would be shown behind our main modal dialog.
+  auto dialog = ash::FloatingWorkspaceDialog::GetNativeWindow();
+  if (dialog) {
+    InternetConfigDialog::ShowDialogForNetworkId(guid, dialog);
+  }
+}
+
+void FloatingWorkspaceDialogHandler::AddNetwork(const base::Value::List& args) {
+  CHECK_EQ(1u, args.size());
+  std::string onc_type = args[0].GetString();
+
+  // We need to pass NativeWindow to the network dialog here, because otherwise
+  // the network dialog would be shown behind our main modal dialog.
+  auto dialog = ash::FloatingWorkspaceDialog::GetNativeWindow();
+  if (dialog) {
+    InternetConfigDialog::ShowDialogForNetworkType(onc_type, dialog);
+  }
+}
+
+// This is needed for proxy connection.
+void FloatingWorkspaceDialogHandler::GetHostname(
+    const base::Value::List& args) {
+  CHECK_EQ(1u, args.size());
+  std::string callback_id = args[0].GetString();
+  std::string hostname =
+      NetworkHandler::Get()->network_state_handler()->hostname();
+
+  ResolveJavascriptCallback(callback_id, hostname);
+}
+
+}  // namespace ash
diff --git a/chrome/browser/ui/webui/ash/floating_workspace/floating_workspace_handler.h b/chrome/browser/ui/webui/ash/floating_workspace/floating_workspace_handler.h
new file mode 100644
index 0000000..b3ed7fd6
--- /dev/null
+++ b/chrome/browser/ui/webui/ash/floating_workspace/floating_workspace_handler.h
@@ -0,0 +1,43 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_ASH_FLOATING_WORKSPACE_FLOATING_WORKSPACE_HANDLER_H_
+#define CHROME_BROWSER_UI_WEBUI_ASH_FLOATING_WORKSPACE_FLOATING_WORKSPACE_HANDLER_H_
+
+#include "base/memory/weak_ptr.h"
+#include "base/values.h"
+#include "content/public/browser/web_ui_message_handler.h"
+
+namespace ash {
+
+class FloatingWorkspaceDialogHandler : public content::WebUIMessageHandler {
+ public:
+  // This dialog can be in only one of three states.
+  enum class DialogState { kDefault, kNetwork, kError };
+
+  FloatingWorkspaceDialogHandler();
+  ~FloatingWorkspaceDialogHandler() override;
+
+  // WebUIMessageHandler implementation.
+  void RegisterMessages() override;
+
+  void ShowDefaultScreen();
+  void ShowNetworkScreen();
+  void ShowErrorScreen();
+
+ private:
+  void Initialize(const base::Value::List& args);
+  void ShowNetworkDetails(const base::Value::List& args);
+  void ShowNetworkConfig(const base::Value::List& args);
+  void AddNetwork(const base::Value::List& args);
+  void GetHostname(const base::Value::List& args);
+  void Respond(const std::string& callback_id, base::ValueView response);
+
+  DialogState state_ = DialogState::kDefault;
+  base::WeakPtrFactory<FloatingWorkspaceDialogHandler> weak_ptr_factory_{this};
+};
+
+}  // namespace ash
+
+#endif  // CHROME_BROWSER_UI_WEBUI_ASH_FLOATING_WORKSPACE_FLOATING_WORKSPACE_HANDLER_H_
diff --git a/chrome/browser/ui/webui/ash/floating_workspace/floating_workspace_ui.cc b/chrome/browser/ui/webui/ash/floating_workspace/floating_workspace_ui.cc
index f98e44d..28a4395 100644
--- a/chrome/browser/ui/webui/ash/floating_workspace/floating_workspace_ui.cc
+++ b/chrome/browser/ui/webui/ash/floating_workspace/floating_workspace_ui.cc
@@ -6,8 +6,10 @@
 
 #include <memory>
 
+#include "ash/public/cpp/network_config_service.h"
 #include "ash/webui/common/trusted_types_util.h"
 #include "build/build_config.h"
+#include "chrome/browser/ui/webui/ash/floating_workspace/floating_workspace_handler.h"
 #include "chrome/browser/ui/webui/ash/login/oobe_ui.h"
 #include "chrome/common/url_constants.h"
 #include "chrome/common/webui_url_constants.h"
@@ -16,6 +18,7 @@
 #include "chrome/grit/generated_resources.h"
 #include "content/public/browser/web_ui.h"
 #include "content/public/browser/web_ui_data_source.h"
+#include "ui/chromeos/strings/network/network_element_localized_strings_provider.h"
 #include "ui/webui/webui_util.h"
 
 namespace ash {
@@ -25,7 +28,11 @@
                           chrome::kChromeUIFloatingWorkspaceDialogHost) {}
 
 FloatingWorkspaceUI::FloatingWorkspaceUI(content::WebUI* web_ui)
-    : WebDialogUI(web_ui) {
+    : MojoWebDialogUI(web_ui) {
+  auto main_handler = std::make_unique<FloatingWorkspaceDialogHandler>();
+  main_handler_ = main_handler.get();
+  web_ui->AddMessageHandler(std::move(main_handler));
+
   content::BrowserContext* browser_context =
       web_ui->GetWebContents()->GetBrowserContext();
   content::WebUIDataSource* source = content::WebUIDataSource::CreateAndAdd(
@@ -34,7 +41,8 @@
   webui::SetupWebUIDataSource(source, kFloatingWorkspaceResources,
                               IDR_FLOATING_WORKSPACE_FLOATING_WORKSPACE_HTML);
 
-  // Reuse animation from the OOBE consumer update screen.
+  // Since we reuse animation from the OOBE consumer update screen, we
+  // need to add label for the pause button.
   static constexpr webui::LocalizedString kAnimationMessage[] = {
       {"pauseAnimationAriaLabel", IDS_OOBE_PAUSE_ANIMATION_MESSAGE}};
   source->AddLocalizedStrings(kAnimationMessage);
@@ -45,13 +53,39 @@
       {"floatingWorkspaceStartupDialogLongResponseTitle",
        IDS_FLOATING_WORKSPACE_STARTUP_DIALOG_LONG_RESPONSE_TITLE},
       {"floatingWorkspaceStartupDialogButton",
-       IDS_FLOATING_WORKSPACE_STARTUP_DIALOG_BUTTON}};
+       IDS_FLOATING_WORKSPACE_STARTUP_DIALOG_BUTTON},
+      {"floatingWorkspaceNetworkDialogTitle",
+       IDS_FLOATING_WORKSPACE_NETWORK_DIALOG_TITLE},
+      {"floatingWorkspaceNetworkDialogSubtitle",
+       IDS_FLOATING_WORKSPACE_NETWORK_DIALOG_SUBTITLE},
+      {"floatingWorkspaceErrorDialogTitle",
+       IDS_FLOATING_WORKSPACE_ERROR_DIALOG_TITLE},
+      {"floatingWorkspaceErrorDialogSubtitle",
+       IDS_FLOATING_WORKSPACE_ERROR_DIALOG_SUBTITLE},
+      {"floatingWorkspaceErrorDialogButton",
+       IDS_FLOATING_WORKSPACE_ERROR_DIALOG_BUTTON},
+  };
   source->AddLocalizedStrings(kLocalizedStrings);
 
+  // Add strings for the additional dialogs on the network screen.
+  ui::network_element::AddLocalizedStrings(source);
+  ui::network_element::AddOncLocalizedStrings(source);
+
   OobeUI::AddOobeComponents(source);
   ash::EnableTrustedTypesCSP(source);
 }
 
 FloatingWorkspaceUI::~FloatingWorkspaceUI() = default;
 
+FloatingWorkspaceDialogHandler* FloatingWorkspaceUI::GetMainHandler() {
+  return main_handler_;
+}
+
+void FloatingWorkspaceUI::BindInterface(
+    mojo::PendingReceiver<chromeos::network_config::mojom::CrosNetworkConfig>
+        receiver) {
+  GetNetworkConfigService(std::move(receiver));
+}
+
+WEB_UI_CONTROLLER_TYPE_IMPL(FloatingWorkspaceUI)
 }  // namespace ash
diff --git a/chrome/browser/ui/webui/ash/floating_workspace/floating_workspace_ui.h b/chrome/browser/ui/webui/ash/floating_workspace/floating_workspace_ui.h
index 20e4a965..8fed390 100644
--- a/chrome/browser/ui/webui/ash/floating_workspace/floating_workspace_ui.h
+++ b/chrome/browser/ui/webui/ash/floating_workspace/floating_workspace_ui.h
@@ -8,13 +8,15 @@
 #include "ash/webui/common/chrome_os_webui_config.h"
 #include "base/memory/weak_ptr.h"
 #include "chrome/common/webui_url_constants.h"
+#include "chromeos/services/network_config/public/mojom/cros_network_config.mojom-forward.h"
 #include "content/public/common/url_constants.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "ui/web_dialogs/web_dialog_ui.h"
+#include "ui/webui/mojo_web_ui_controller.h"
 
 namespace ash {
-
 class FloatingWorkspaceUI;
-
+class FloatingWorkspaceDialogHandler;
 // The WebUIConfig for the FloatingWorkspaceUI class.
 class FloatingWorkspaceUIConfig
     : public ChromeOSWebUIConfig<FloatingWorkspaceUI> {
@@ -22,17 +24,26 @@
   FloatingWorkspaceUIConfig();
 };
 
-class FloatingWorkspaceUI : public ui::WebDialogUI {
+class FloatingWorkspaceUI : public ui::MojoWebDialogUI {
  public:
   explicit FloatingWorkspaceUI(content::WebUI* web_ui);
   FloatingWorkspaceUI(const FloatingWorkspaceUI&) = delete;
   FloatingWorkspaceUI& operator=(const FloatingWorkspaceUI&) = delete;
   ~FloatingWorkspaceUI() override;
 
- private:
-  base::WeakPtrFactory<FloatingWorkspaceUI> weak_factory_{this};
-};
+  // Instantiates implementation of the mojom::CrosNetworkConfig mojo interface
+  // passing the pending receiver that will be internally bound.
+  void BindInterface(
+      mojo::PendingReceiver<chromeos::network_config::mojom::CrosNetworkConfig>
+          receiver);
+  FloatingWorkspaceDialogHandler* GetMainHandler();
 
+ private:
+  raw_ptr<FloatingWorkspaceDialogHandler> main_handler_;
+  base::WeakPtrFactory<FloatingWorkspaceUI> weak_factory_{this};
+
+  WEB_UI_CONTROLLER_TYPE_DECL();
+};
 }  // namespace ash
 
 #endif  // CHROME_BROWSER_UI_WEBUI_ASH_FLOATING_WORKSPACE_FLOATING_WORKSPACE_UI_H_
diff --git a/chrome/browser/ui/webui/ash/settings/pages/a11y/BUILD.gn b/chrome/browser/ui/webui/ash/settings/pages/a11y/BUILD.gn
index 253fd2b..af649fd 100644
--- a/chrome/browser/ui/webui/ash/settings/pages/a11y/BUILD.gn
+++ b/chrome/browser/ui/webui/ash/settings/pages/a11y/BUILD.gn
@@ -43,6 +43,7 @@
     "//chrome/browser:browser_process",
     "//chrome/browser/accessibility:utils",
     "//chrome/browser/ash/accessibility",
+    "//chrome/browser/profiles",
     "//chrome/browser/profiles:profile",
     "//chrome/browser/ui/webui/ash/settings/search",
     "//chrome/common",
diff --git a/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc b/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
index c1880a10..b15cb173 100644
--- a/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
+++ b/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
@@ -372,7 +372,6 @@
 
 void AddAiStrings(content::WebUIDataSource* html_source) {
   static constexpr webui::LocalizedString kLocalizedStrings[] = {
-      {"aiPageTitle", IDS_SETTINGS_AI_PAGE_TITLE},
       {"aiInnovationsPageTitle", IDS_SETTINGS_AI_INNOVATIONS_PAGE_TITLE},
       {"aiPageMainTitle", IDS_SETTINGS_AI_PAGE_MAIN_TITLE},
       {"aiPageMainSublabel1", IDS_SETTINGS_AI_PAGE_MAIN_SUBLABEL_1},
diff --git a/chrome/browser/ui/webui/settings/settings_ui.cc b/chrome/browser/ui/webui/settings/settings_ui.cc
index 146ec866..dcf42fa2 100644
--- a/chrome/browser/ui/webui/settings/settings_ui.cc
+++ b/chrome/browser/ui/webui/settings/settings_ui.cc
@@ -607,104 +607,64 @@
   }
 #endif
 
-  const bool ai_settings_refresh_enabled =
-      optimization_guide::features::IsAiSettingsPageRefreshEnabled();
+  const bool use_is_setting_visible = base::FeatureList::IsEnabled(
+      optimization_guide::features::kAiSettingsPageEnterpriseDisabledUi);
 
-  if (ai_settings_refresh_enabled) {
-    const bool use_is_setting_visible = base::FeatureList::IsEnabled(
-        optimization_guide::features::kAiSettingsPageEnterpriseDisabledUi);
+  const auto& autofill_client =
+      *autofill::ContentAutofillClient::FromWebContents(
+          web_ui->GetWebContents());
 
-    const auto& autofill_client =
-        *autofill::ContentAutofillClient::FromWebContents(
-            web_ui->GetWebContents());
+  std::pair<const std::string_view, bool> optimization_guide_features[] = {
+      {"showTabOrganizationControl",
+       use_is_setting_visible
+           ? TabOrganizationUtils::GetInstance()->IsSettingVisible(profile)
+           : TabOrganizationUtils::GetInstance()->IsEnabled(profile)},
+      {"showComposeControl",
+       use_is_setting_visible ? compose_visible : compose_enabled},
+      {"showHistorySearchControl",
+       history_embeddings::IsHistoryEmbeddingsSettingVisible(profile)},
+      {"showCompareControl",
+       use_is_setting_visible ? commerce::IsProductSpecificationsSettingVisible(
+                                    shopping_service->GetAccountChecker())
+                              : commerce::CanFetchProductSpecificationsData(
+                                    shopping_service->GetAccountChecker())},
+      {"showPasswordChangeControl",
+       // TODO(crbug.com/391131625): Update accordingly to enterprise
+       // requirements.
+       PasswordChangeServiceFactory::GetForProfile(profile) &&
+           PasswordChangeServiceFactory::GetForProfile(profile)
+               ->IsPasswordChangeAvailable()},
+      // The code checks only once, when setting is loaded, whether the
+      // Autofill Ai section should be shown.
+      // The code cannot dynamically check whether the Autofill Ai section
+      // should be shown, because otherwise the user could reach weird states,
+      // such as navigating to the Ai Page when the Ai Page has 0 entries.
+      {"showAutofillAiControl",
+       autofill::MayPerformAutofillAiAction(
+           autofill_client, autofill::AutofillAiAction::kOptIn) ||
+           autofill::MayPerformAutofillAiAction(
+               autofill_client,
+               autofill::AutofillAiAction::kListEntityInstancesInSettings)},
+  };
 
-    std::pair<const std::string_view, bool> optimization_guide_features[] = {
-        {"showTabOrganizationControl",
-         use_is_setting_visible
-             ? TabOrganizationUtils::GetInstance()->IsSettingVisible(profile)
-             : TabOrganizationUtils::GetInstance()->IsEnabled(profile)},
-        {"showComposeControl",
-         use_is_setting_visible ? compose_visible : compose_enabled},
-        {"showHistorySearchControl",
-         history_embeddings::IsHistoryEmbeddingsSettingVisible(profile)},
-        {"showCompareControl",
-         use_is_setting_visible
-             ? commerce::IsProductSpecificationsSettingVisible(
-                   shopping_service->GetAccountChecker())
-             : commerce::CanFetchProductSpecificationsData(
-                   shopping_service->GetAccountChecker())},
-        {"showPasswordChangeControl",
-         // TODO(crbug.com/391131625): Update accordingly to enterprise
-         // requirements.
-         PasswordChangeServiceFactory::GetForProfile(profile) &&
-             PasswordChangeServiceFactory::GetForProfile(profile)
-                 ->IsPasswordChangeAvailable()},
-        // The code checks only once, when setting is loaded, whether the
-        // Autofill Ai section should be shown.
-        // The code cannot dynamically check whether the Autofill Ai section
-        // should be shown, because otherwise the user could reach weird states,
-        // such as navigating to the Ai Page when the Ai Page has 0 entries.
-        {"showAutofillAiControl",
-         autofill::MayPerformAutofillAiAction(
-             autofill_client, autofill::AutofillAiAction::kOptIn) ||
-             autofill::MayPerformAutofillAiAction(
-                 autofill_client,
-                 autofill::AutofillAiAction::kListEntityInstancesInSettings)},
-    };
+  const bool show_ai_settings_for_testing =
+      optimization_guide::features::kShowAiSettingsForTesting.Get();
 
-    const bool show_ai_settings_for_testing =
-        optimization_guide::features::kShowAiSettingsForTesting.Get();
-    bool show_ai_features_section = show_ai_settings_for_testing;
-    for (auto [name, visible] : optimization_guide_features) {
-      html_source->AddBoolean(name, visible || show_ai_settings_for_testing);
-      show_ai_features_section |= visible;
-    }
-
-    // "showAdvancedFeaturesMainControl", despite the name, controls whether the
-    // AI subpage is shown. Within the AI subpage are separate sections for Glic
-    // and for all other AI features, the visibility of these are separately
-    // controlled but we want to show the subpage if any of the AI features or
-    // Glic are enabled.
-    // TODO(crbug.com/363968675): Rename this to be clearer.
-    html_source->AddBoolean("showAdvancedFeaturesMainControl",
-                            show_glic_section || show_ai_features_section);
-    html_source->AddBoolean("showAiPageAiFeatureSection",
-                            show_ai_features_section);
-  } else {
-    std::pair<UserVisibleFeatureKey, const std::string_view>
-        optimization_guide_features[] = {
-            {UserVisibleFeatureKey::kCompose, "showComposeControl"},
-            {UserVisibleFeatureKey::kTabOrganization,
-             "showTabOrganizationControl"},
-            {UserVisibleFeatureKey::kHistorySearch, "showHistorySearchControl"},
-        };
-    bool is_any_ai_feature_enabled = false;
-
-    auto* optimization_guide_service =
-        OptimizationGuideKeyedServiceFactory::GetForProfile(profile);
-    for (auto [key, name] : optimization_guide_features) {
-      const bool visible = optimization_guide_service &&
-                           optimization_guide_service->IsSettingVisible(key);
-      html_source->AddBoolean(name, visible);
-
-      // The main toggle is visible only if at least one of the sub toggles is
-      // visible.
-      is_any_ai_feature_enabled |= visible;
-    }
-
-    html_source->AddBoolean("showAdvancedFeaturesMainControl",
-                            is_any_ai_feature_enabled || show_glic_section);
-    html_source->AddBoolean("showAiPageAiFeatureSection",
-                            is_any_ai_feature_enabled);
-    // Compare is only shown when Synpase ("AiSettingsPageRefresh") is enabled.
-    html_source->AddBoolean("showCompareControl", false);
-    // Password change is only shown when Synpase ("AiSettingsPageRefresh") is
-    // enabled.
-    html_source->AddBoolean("showPasswordChangeControl", false);
+  // Show the AI features section in the AI page if any of the AI features are
+  // enabled.
+  bool show_ai_features_section = show_ai_settings_for_testing;
+  for (auto [name, visible] : optimization_guide_features) {
+    html_source->AddBoolean(name, visible || show_ai_settings_for_testing);
+    show_ai_features_section |= visible;
   }
 
-  html_source->AddBoolean("enableAiSettingsPageRefresh",
-                          ai_settings_refresh_enabled);
+  // Within the AI subpage are separate sections for Glic and for all other AI
+  // features, the visibility of these are separately controlled but we want to
+  // show the subpage if any of the AI features or Glic are enabled.
+  html_source->AddBoolean("showAiPage",
+                          show_glic_section || show_ai_features_section);
+  html_source->AddBoolean("showAiPageAiFeatureSection",
+                          show_ai_features_section);
   html_source->AddBoolean(
       "enableAiSettingsInPrivacyGuide",
       optimization_guide::features::IsPrivacyGuideAiSettingsEnabled());
@@ -868,7 +828,7 @@
   update.Set("showGlicSettings", show_glic);
   update.Set("glicDisallowedByAdmin", enablement.DisallowedByAdmin());
   if (show_glic) {
-    update.Set("showAdvancedFeaturesMainControl", true);
+    update.Set("showAiPage", true);
   }
 
   content::WebUIDataSource::Update(
diff --git a/chrome/browser/ui/webui/signin/BUILD.gn b/chrome/browser/ui/webui/signin/BUILD.gn
index 06abaee..f0510e8 100644
--- a/chrome/browser/ui/webui/signin/BUILD.gn
+++ b/chrome/browser/ui/webui/signin/BUILD.gn
@@ -274,6 +274,7 @@
       ":signin",
       ":signin_utils",
       "//chrome/browser/new_tab_page/chrome_colors:generate_colors_info",
+      "//chrome/browser/profiles",
       "//chrome/browser/themes",
       "//components/password_manager/core/browser",
       "//components/signin/core/browser",
@@ -285,6 +286,7 @@
     deps += [
       "//chrome/browser/extensions",
       "//chrome/browser/prefs",
+      "//chrome/browser/profiles",
       "//chrome/browser/resources/gaia_auth_host:resources",
       "//ui/webui",
     ]
@@ -365,6 +367,7 @@
       "//chrome/browser:browser_process",
       "//chrome/browser:resources",
       "//chrome/browser/new_tab_page/chrome_colors:generate_chrome_colors_info",
+      "//chrome/browser/profiles",
       "//chrome/browser/profiles:profile",
       "//chrome/browser/profiles:profile_util",
       "//chrome/browser/resources/signin:resources",
diff --git a/chrome/browser/web_applications/BUILD.gn b/chrome/browser/web_applications/BUILD.gn
index 3d6c999..e39cfe8 100644
--- a/chrome/browser/web_applications/BUILD.gn
+++ b/chrome/browser/web_applications/BUILD.gn
@@ -925,8 +925,6 @@
     "preinstalled_web_app_utils_unittest.cc",
     "preinstalled_web_apps/preinstalled_web_app_definition_utils_unittest.cc",
     "tabbed_mode_scope_matcher_unittest.cc",
-    "test/web_app_test.cc",
-    "test/web_app_test.h",
     "visited_manifest_manager_unittest.cc",
     "web_app_command_manager_unittest.cc",
     "web_app_command_scheduler_unittest.cc",
@@ -1000,6 +998,7 @@
   }
 
   deps = [
+    ":web_app_test",
     ":web_applications",
     ":web_applications_test_support",
     "//base/test:test_support",
@@ -1313,3 +1312,32 @@
     deps += [ "//chrome/browser/apps/app_shim" ]
   }
 }
+
+# Test harness for web app tests. Can't be part of
+# `:web_applications_test_support` because this depends on
+# `//chrome/test:test_support`. Adding it to `:web_applications_test_support`
+# will require `:web_applications_test_support` to depend on
+# `//chrome/test:test_support`, which also depends on
+# `:web_applications_test_support`.
+source_set("web_app_test") {
+  testonly = true
+  sources = [
+    "test/web_app_test.cc",
+    "test/web_app_test.h",
+  ]
+
+  public_deps = [
+    ":web_applications_test_support",
+    "//base",
+    "//base/test:test_support",
+    "//chrome/test:test_support",
+    "//content/test:test_support",
+    "//services/network:test_support",
+    "//services/network/public/cpp",
+  ]
+
+  deps = [
+    "//chrome/browser",
+    "//testing/gtest",
+  ]
+}
diff --git a/chrome/browser/web_applications/extensions/BUILD.gn b/chrome/browser/web_applications/extensions/BUILD.gn
index b7fed69..2f8ba88 100644
--- a/chrome/browser/web_applications/extensions/BUILD.gn
+++ b/chrome/browser/web_applications/extensions/BUILD.gn
@@ -52,6 +52,7 @@
   deps = [
     ":extensions",
     "//chrome/browser/web_applications",
+    "//chrome/browser/web_applications:web_app_test",
     "//chrome/browser/web_applications:web_applications_test_support",
     "//chrome/browser/web_applications:web_applications_unit_tests",
     "//chrome/common",
diff --git a/chrome/browser/web_applications/isolated_web_apps/policy/isolated_web_app_cache_browsertest.cc b/chrome/browser/web_applications/isolated_web_apps/policy/isolated_web_app_cache_browsertest.cc
index bf8a876..85060e1 100644
--- a/chrome/browser/web_applications/isolated_web_apps/policy/isolated_web_app_cache_browsertest.cc
+++ b/chrome/browser/web_applications/isolated_web_apps/policy/isolated_web_app_cache_browsertest.cc
@@ -503,37 +503,6 @@
   AssertAppInstalledAtVersion(kBaseVersion);
 }
 
-// Install base version from the Internet.
-IN_PROC_BROWSER_TEST_P(IwaCacheTest, PRE_UpdateTaskIsTriggeredAutomatically) {
-  LaunchSession();
-  AssertAppInstalledAtVersion(kBaseVersion);
-  WaitUntilPathExists(GetCachedBundlePath(kBaseVersion));
-  CheckPathDoesNotExist(GetCachedBundlePath(kUpdateVersion));
-}
-
-// Add new version to the manifest, but the installation will be done from cache
-// with the base version first. Then the IWA cache manager will automatically
-// trigger the update check.
-IN_PROC_BROWSER_TEST_P(IwaCacheTest, UpdateTaskIsTriggeredAutomatically) {
-  AddNewVersionToUpdateServer(kUpdateVersion);
-  LaunchSession();
-
-  UpdateDiscoveryTaskFuture discovery_update_future;
-  UpdateDiscoveryTaskResultWaiter discovery_update_waiter(
-      provider(), GetAppId(), discovery_update_future.GetCallback());
-
-  AssertAppInstalledAtVersion(kBaseVersion);
-  if (IsManagedGuestSession()) {
-    // Only open app in MGS, in kiosk app is always opened after the session
-    // started.
-    OpenIwa();
-  }
-
-  EXPECT_THAT(discovery_update_future.Get(),
-              ValueIs(IsolatedWebAppUpdateDiscoveryTask::Success::
-                          kUpdateFoundAndSavedInDatabase));
-}
-
 INSTANTIATE_TEST_SUITE_P(
     /* no prefix */,
     IwaCacheTest,
diff --git a/chrome/browser/web_applications/isolated_web_apps/policy/isolated_web_app_cache_manager.cc b/chrome/browser/web_applications/isolated_web_apps/policy/isolated_web_app_cache_manager.cc
index 4ff01c40..8405a28 100644
--- a/chrome/browser/web_applications/isolated_web_apps/policy/isolated_web_app_cache_manager.cc
+++ b/chrome/browser/web_applications/isolated_web_apps/policy/isolated_web_app_cache_manager.cc
@@ -11,11 +11,8 @@
 #include "base/functional/bind.h"
 #include "base/logging.h"
 #include "base/notreached.h"
-#include "base/types/expected_macros.h"
 #include "chrome/browser/ash/policy/core/device_local_account.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/web_applications/isolated_web_apps/commands/isolated_web_app_install_command_helper.h"
-#include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_update_manager.h"
 #include "chrome/browser/web_applications/isolated_web_apps/policy/isolated_web_app_cache_client.h"
 #include "chrome/browser/web_applications/isolated_web_apps/policy/isolated_web_app_external_install_options.h"
 #include "chrome/browser/web_applications/isolated_web_apps/policy/isolated_web_app_policy_manager.h"
@@ -86,7 +83,6 @@
   }
   CHECK(provider_);
 
-  install_manager_observation_.Observe(&provider_->install_manager());
   CleanCacheForIwasDeletedFromPolicy();
   // TODO(crbug.com/388727598): observe IWA installation to trigger updates.
 }
@@ -96,23 +92,6 @@
   provider_ = &provider;
 }
 
-void IwaBundleCacheManager::OnWebAppInstalled(const webapps::AppId& app_id) {
-  ASSIGN_OR_RETURN(const WebApp& iwa,
-                   GetIsolatedWebAppById(provider_->registrar_unsafe(), app_id),
-                   [](const std::string&) { return; });
-
-  // In ephemeral sessions `IsolatedWebAppUpdateManager` checks for updates
-  // before IWAs are installed from cache (without updating IWAs even when the
-  // update is available, since only installed IWAs can be updated). Triggering
-  // the update check manually here after the IWA installation to avoid waiting
-  // for the next scheduled update check.
-  TriggerIwaUpdateCheck(iwa);
-}
-
-void IwaBundleCacheManager::OnWebAppInstallManagerDestroyed() {
-  install_manager_observation_.Reset();
-}
-
 void IwaBundleCacheManager::CleanCacheForIwasDeletedFromPolicy() {
   SessionType session_type = IwaCacheClient::GetCurrentSessionType();
   std::vector<web_package::SignedWebBundleId> iwas_in_policy =
@@ -130,10 +109,4 @@
   // TODO(crbug.com/388728155): add result to log.
 }
 
-void IwaBundleCacheManager::TriggerIwaUpdateCheck(const WebApp& iwa) {
-  CHECK(iwa.isolation_data());
-  provider_->iwa_update_manager().MaybeDiscoverUpdatesForApp(iwa.app_id());
-  // TODO(crbug.com/388728155): add result to log.
-}
-
 }  // namespace web_app
diff --git a/chrome/browser/web_applications/isolated_web_apps/policy/isolated_web_app_cache_manager.h b/chrome/browser/web_applications/isolated_web_apps/policy/isolated_web_app_cache_manager.h
index 4b9f46ec..011e2c5 100644
--- a/chrome/browser/web_applications/isolated_web_apps/policy/isolated_web_app_cache_manager.h
+++ b/chrome/browser/web_applications/isolated_web_apps/policy/isolated_web_app_cache_manager.h
@@ -5,14 +5,10 @@
 #ifndef CHROME_BROWSER_WEB_APPLICATIONS_ISOLATED_WEB_APPS_POLICY_ISOLATED_WEB_APP_CACHE_MANAGER_H_
 #define CHROME_BROWSER_WEB_APPLICATIONS_ISOLATED_WEB_APPS_POLICY_ISOLATED_WEB_APP_CACHE_MANAGER_H_
 
-#include "base/scoped_observation.h"
 #include "base/types/pass_key.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/web_applications/isolated_web_apps/commands/cleanup_bundle_cache_command.h"
 #include "chrome/browser/web_applications/isolated_web_apps/policy/isolated_web_app_cache_client.h"
-#include "chrome/browser/web_applications/web_app.h"
-#include "chrome/browser/web_applications/web_app_install_manager.h"
-#include "chrome/browser/web_applications/web_app_install_manager_observer.h"
 #include "chrome/browser/web_applications/web_app_provider.h"
 
 class Profile;
@@ -25,21 +21,17 @@
 // anything.
 // TODO(crbug.com/388727598): observe IWA installation to trigger updates
 // manually.
-class IwaBundleCacheManager : public WebAppInstallManagerObserver {
+class IwaBundleCacheManager {
  public:
   explicit IwaBundleCacheManager(Profile& profile);
 
   IwaBundleCacheManager(const IwaBundleCacheManager&) = delete;
   IwaBundleCacheManager& operator=(const IwaBundleCacheManager&) = delete;
-  ~IwaBundleCacheManager() override;
+  ~IwaBundleCacheManager();
 
   void Start();
   void SetProvider(base::PassKey<WebAppProvider>, WebAppProvider& provider);
 
-  // `WebAppInstallManagerObserver`:
-  void OnWebAppInstalled(const webapps::AppId& app_id) override;
-  void OnWebAppInstallManagerDestroyed() override;
-
  private:
   // Cleans IWA bundle cache for the current session. Which means if Managed
   // Guest Session is launched, cache will be cleaned only for Managed Guest
@@ -49,12 +41,8 @@
       base::expected<CleanupBundleCacheSuccess, CleanupBundleCacheError>
           result);
 
-  void TriggerIwaUpdateCheck(const WebApp& iwa);
-
   const raw_ref<Profile> profile_;
   raw_ptr<WebAppProvider> provider_ = nullptr;
-  base::ScopedObservation<WebAppInstallManager, WebAppInstallManagerObserver>
-      install_manager_observation_{this};
 
   base::WeakPtrFactory<IwaBundleCacheManager> weak_ptr_factory_{this};
 };
diff --git a/chrome/build/mac-arm.pgo.txt b/chrome/build/mac-arm.pgo.txt
index ef4dd5d5..1b4fa3c 100644
--- a/chrome/build/mac-arm.pgo.txt
+++ b/chrome/build/mac-arm.pgo.txt
@@ -1 +1 @@
-chrome-mac-arm-main-1747374511-873d40b203157bb587a78c389277c556875cd341-660faa3b0ebb9895db20bec2f6180ea9c5263dae.profdata
+chrome-mac-arm-main-1747395494-552e02eaf830d54528bbc68457f1ea7098912b09-f13def107dfb3c4288c3742462185baa074974a7.profdata
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt
index de32e2d9..73897bd 100644
--- a/chrome/build/mac.pgo.txt
+++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@
-chrome-mac-main-1747331992-7f733e55747b47633032a80a14ba193a2a3d9e9d-6e0befebc90f982694346073119820ff61e213b5.profdata
+chrome-mac-main-1747374511-b6546ad800c613c85a6a0a70a484fa330b9c5866-660faa3b0ebb9895db20bec2f6180ea9c5263dae.profdata
diff --git a/chrome/build/win-arm64.pgo.txt b/chrome/build/win-arm64.pgo.txt
index ff34a86..043e117 100644
--- a/chrome/build/win-arm64.pgo.txt
+++ b/chrome/build/win-arm64.pgo.txt
@@ -1 +1 @@
-chrome-win-arm64-main-1747353456-9f1c873cf73dbb87df41df6553e873913afd554e-8aadc7dc60c995b652d20cd2c49876d781401a34.profdata
+chrome-win-arm64-main-1747374511-2756bfa447f8dec71f5336fe766971fe45421f7c-660faa3b0ebb9895db20bec2f6180ea9c5263dae.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index d3c8a5b..c4d577b 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1747353456-3d8b846748d86871b0470db3525e26e3dd02b1fb-8aadc7dc60c995b652d20cd2c49876d781401a34.profdata
+chrome-win32-main-1747374511-8283b18afa569b7f0b3b7efeaefa44d619123d14-660faa3b0ebb9895db20bec2f6180ea9c5263dae.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index cb15d2a..ae66afa 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1747342758-b8a02635080b7fdbde431500675279645d4850ce-644ae5871e7c761c7462789541e21d6ac379ddd9.profdata
+chrome-win64-main-1747374511-184adc18e14986a360b5c5215a2f049a74fffdd0-660faa3b0ebb9895db20bec2f6180ea9c5263dae.profdata
diff --git a/chrome/common/pref_names.h b/chrome/common/pref_names.h
index 03d29c0..25380eb 100644
--- a/chrome/common/pref_names.h
+++ b/chrome/common/pref_names.h
@@ -2383,11 +2383,10 @@
 inline constexpr char kNtpCollapsedSyncPromo[] = "ntp.collapsed_sync_promo";
 #else
 // Holds info for New Tab Page custom background
-// Note: In the process of migration. Please use `GetThemePrefNameInMigration()`
-// instead. See crbug.com/356148174.
-inline constexpr char kNtpCustomBackgroundDictDoNotUse[] =
+// Use `kNtpCustomBackgroundDict` only.
+inline constexpr char kDeprecatedNtpCustomBackgroundDictDoNotUse[] =
     "ntp.custom_background_dict";
-inline constexpr char kNonSyncingNtpCustomBackgroundDictDoNotUse[] =
+inline constexpr char kNtpCustomBackgroundDict[] =
     "ntp.custom_background_dict2";
 inline constexpr char kNtpCustomBackgroundLocalToDevice[] =
     "ntp.custom_background_local_to_device";
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index b5a711fb..88cbf93 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -2246,6 +2246,7 @@
       "//chrome/browser/preloading/search_preload:browser_tests",
       "//chrome/browser/privacy_budget:browser_tests",
       "//chrome/browser/privacy_sandbox:browser_tests",
+      "//chrome/browser/profiles",
       "//chrome/browser/profiles:profile_util",
       "//chrome/browser/profiling_host:profiling_browsertests",
       "//chrome/browser/promos:utils",
@@ -2302,6 +2303,7 @@
       "//chrome/browser/ui/permission_bubble:browser_tests",
       "//chrome/browser/ui/prefs:browser_tests",
       "//chrome/browser/ui/send_tab_to_self:browser_tests",
+      "//chrome/browser/ui/webui/app_service_internals:browser_tests",
 
       # TODO(413315837): Remove this dependency when
       # c/b/ui/views/privacy_sandbox/privacy_sandbox_dialog_view_browsertest.cc gets modularized.
@@ -3509,7 +3511,6 @@
       "../browser/ui/views/webid/fedcm_account_selection_view_desktop_browsertest.cc",
       "../browser/ui/views/webview_accessibility_browsertest.cc",
       "../browser/ui/webauthn/authenticator_dialog_browsertest.cc",
-      "../browser/ui/webui/app_service_internals/app_service_internals_browsertest.cc",
       "../browser/ui/webui/autofill_and_password_manager_internals/autofill_internals_ui_browsertest.cc",
       "../browser/ui/webui/chrome_content_browser_client_webui_part_browsertest.cc",
       "../browser/ui/webui/chrome_url_data_manager_browsertest.cc",
@@ -6637,6 +6638,7 @@
     "//chrome/browser/privacy_budget:unit_tests",
     "//chrome/browser/privacy_sandbox",
     "//chrome/browser/privacy_sandbox:unit_tests",
+    "//chrome/browser/profiles",
 
     # TODO(413315837): Remove this dep when privacy_sandbox_service_impl_unittest.cc
     # gets build off of c/b/privacy_sandbox/BUILD.gn.
@@ -9285,6 +9287,7 @@
       "//chrome/browser/ui/global_error:test_support",
       "//chrome/browser/ui/web_applications:unit_tests",
       "//chrome/browser/web_applications:unit_tests",
+      "//chrome/browser/web_applications:web_app_test",
       "//chrome/browser/web_applications:web_applications_unit_tests",
       "//chrome/browser/web_applications/extensions:unit_tests",
       "//chrome/common/apps/platform_apps",
@@ -11578,6 +11581,7 @@
           "../browser/ui/views/frame/browser_window_property_manager_browsertest_win.cc",
           "../browser/ui/views/status_icons/status_tray_state_changer_interactive_uitest_win.cc",
         ]
+        deps += [ "//chrome/browser/profiles" ]
         if (use_aura) {
           sources += [ "../browser/ui/views/tooltip/tooltip_aura_interactive_uitest_win.cc" ]
         }
@@ -12027,6 +12031,7 @@
   deps = [
     "//base",
     "//chrome/browser/autofill",
+    "//chrome/browser/profiles",
     "//chrome/browser/search_engines",
     "//chrome/browser/sync",
     "//components/autofill/core/common:credit_card_number_validation",
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/TabLoadObserver.java b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/TabLoadObserver.java
index a2eb65a..d21acb3f 100644
--- a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/TabLoadObserver.java
+++ b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/TabLoadObserver.java
@@ -27,6 +27,8 @@
 
     private final CallbackHelper mTabLoadStartedCallback = new CallbackHelper();
     private final CallbackHelper mTabLoadFinishedCallback = new CallbackHelper();
+    private int mCurrentTabLoadStartedCallCount;
+    private int mCurrentTabLoadFinishedCallCount;
 
     private final Tab mTab;
     private final String mExpectedTitle;
@@ -84,8 +86,13 @@
 
     /** Asserts the page has loaded. */
     public void assertLoaded() throws Exception {
-        mTabLoadStartedCallback.waitForCallback(mTabLoadStartedCallback.getCallCount(), 1);
-        mTabLoadFinishedCallback.waitForCallback(mTabLoadFinishedCallback.getCallCount(), 1);
+        mTabLoadStartedCallback.waitForCallback(
+                mCurrentTabLoadStartedCallCount, /* numberOfCallsToWaitFor= */ 1);
+        mCurrentTabLoadStartedCallCount = mTabLoadStartedCallback.getCallCount();
+        mTabLoadFinishedCallback.waitForCallback(
+                mCurrentTabLoadFinishedCallCount, /* numberOfCallsToWaitFor= */ 1);
+        mCurrentTabLoadFinishedCallCount = mTabLoadFinishedCallback.getCallCount();
+
         final Coordinates coord = Coordinates.createFor(mTab.getWebContents());
 
         CriteriaHelper.pollUiThread(
diff --git a/chrome/test/base/testing_profile.cc b/chrome/test/base/testing_profile.cc
index b641d9b..ba4bbb4 100644
--- a/chrome/test/base/testing_profile.cc
+++ b/chrome/test/base/testing_profile.cc
@@ -412,14 +412,13 @@
             new syncer::FakeSyncChangeProcessor));
   }
 
-  if (prefs_.get())
+  if (prefs_.get()) {
     user_prefs::UserPrefs::Set(this, prefs_.get());
-  else if (IsOffTheRecord())
+  } else if (IsOffTheRecord()) {
     CreateIncognitoPrefService();
-  else if (is_supervised_profile)
-    CreatePrefServiceForSupervisedUser();
-  else
+  } else {
     CreateTestingPrefService();
+  }
 
   if (is_supervised_profile)
     SetIsSupervisedProfile();
@@ -779,14 +778,6 @@
 
 void TestingProfile::CreateTestingPrefService() {
   DCHECK(!prefs_.get());
-  testing_prefs_ = new sync_preferences::TestingPrefServiceSyncable();
-  prefs_.reset(testing_prefs_);
-  user_prefs::UserPrefs::Set(this, prefs_.get());
-  RegisterUserProfilePrefs(testing_prefs_->registry());
-}
-
-void TestingProfile::CreatePrefServiceForSupervisedUser() {
-  DCHECK(!prefs_.get());
 
   // Construct testing_prefs_ by hand to add the supervised user pref store.
   testing_prefs_ = new sync_preferences::TestingPrefServiceSyncable(
diff --git a/chrome/test/base/testing_profile.h b/chrome/test/base/testing_profile.h
index 53fd953..f346095 100644
--- a/chrome/test/base/testing_profile.h
+++ b/chrome/test/base/testing_profile.h
@@ -484,10 +484,6 @@
   // Creates a TestingPrefService and associates it with the TestingProfile.
   void CreateTestingPrefService();
 
-  // Creates a pref service that uses SupervisedUserPrefStore and associates
-  // it with the TestingProfile.
-  void CreatePrefServiceForSupervisedUser();
-
   // Initializes |prefs_| for an incognito profile, derived from
   // |original_profile_|.
   void CreateIncognitoPrefService();
diff --git a/chrome/test/data/webui/settings/ai_page_test.ts b/chrome/test/data/webui/settings/ai_page_test.ts
index 34601d6..15e8fb05 100644
--- a/chrome/test/data/webui/settings/ai_page_test.ts
+++ b/chrome/test/data/webui/settings/ai_page_test.ts
@@ -14,7 +14,7 @@
 
 import {TestMetricsBrowserProxy} from './test_metrics_browser_proxy.js';
 
-suite('ExperimentalAdvancedPage', function() {
+suite('AiPage', function() {
   let metricsBrowserProxy: TestMetricsBrowserProxy;
   let openWindowProxy: TestOpenWindowProxy;
   let page: SettingsAiPageElement;
@@ -27,7 +27,7 @@
     OpenWindowProxyImpl.setInstance(openWindowProxy);
 
     loadTimeData.overrideValues({
-      showAdvancedFeaturesMainControl: true,
+      showAiPage: true,
       showAiPageAiFeatureSection: true,
     });
     settingsPrefs = document.createElement('settings-prefs');
@@ -158,7 +158,7 @@
 
   test('historySearchRow', async () => {
     loadTimeData.overrideValues({
-      showAdvancedFeaturesMainControl: true,
+      showAiPage: true,
       showHistorySearchControl: true,
     });
     resetRouterForTesting();
@@ -198,7 +198,7 @@
 
   test('compareRow', async () => {
     loadTimeData.overrideValues({
-      showAdvancedFeaturesMainControl: true,
+      showAiPage: true,
       showCompareControl: true,
     });
     resetRouterForTesting();
@@ -221,7 +221,7 @@
 
   test('composeRow', async () => {
     loadTimeData.overrideValues({
-      showAdvancedFeaturesMainControl: true,
+      showAiPage: true,
       showComposeControl: true,
     });
     resetRouterForTesting();
@@ -244,7 +244,7 @@
 
   test('tabOrganizationRow', async () => {
     loadTimeData.overrideValues({
-      showAdvancedFeaturesMainControl: true,
+      showAiPage: true,
       showTabOrganizationControl: true,
     });
     resetRouterForTesting();
diff --git a/chrome/test/data/webui/settings/basic_page_test.ts b/chrome/test/data/webui/settings/basic_page_test.ts
index fd5236f..8de2cdb 100644
--- a/chrome/test/data/webui/settings/basic_page_test.ts
+++ b/chrome/test/data/webui/settings/basic_page_test.ts
@@ -525,7 +525,7 @@
   });
 });
 
-suite('ExperimentalAdvanced', () => {
+suite('AiSections', () => {
   let page: SettingsBasicPageElement;
 
   function createBasicPage() {
@@ -535,14 +535,19 @@
     flush();
   }
 
-  test('mainControlHidesAiFeaturesSection', function() {
+  test('showAiPageHidesAiFeaturesSection', function() {
     loadTimeData.overrideValues({
-      showAdvancedFeaturesMainControl: false,
+      showAiPage: false,
       showAiPageAiFeatureSection: true,
     });
     resetRouterForTesting();
 
     createBasicPage();
+    const infoCardSectionElement =
+        page.shadowRoot!.querySelector<SettingsSectionElement>(
+            'settings-section[section=aiInfoCard]');
+    assertFalse(!!infoCardSectionElement);
+
     const aiFeaturesSectionElement =
         page.shadowRoot!.querySelector('settings-section[section=ai]');
     assertFalse(!!aiFeaturesSectionElement);
@@ -550,7 +555,7 @@
 
   test('aiFeaturesSectionNotVisible', function() {
     loadTimeData.overrideValues({
-      showAdvancedFeaturesMainControl: true,
+      showAiPage: true,
       showAiPageAiFeatureSection: false,
     });
     resetRouterForTesting();
@@ -563,7 +568,7 @@
 
   test('aiFeaturesSectionVisible', function() {
     loadTimeData.overrideValues({
-      showAdvancedFeaturesMainControl: true,
+      showAiPage: true,
       showAiPageAiFeatureSection: true,
     });
     resetRouterForTesting();
@@ -572,42 +577,20 @@
     const aiFeaturesSectionElement =
         page.shadowRoot!.querySelector('settings-section[section=ai]');
     assertTrue(!!aiFeaturesSectionElement);
-  });
 
-  test('infoCardNotVisible', function() {
-    loadTimeData.overrideValues({
-      showAdvancedFeaturesMainControl: true,
-      enableAiSettingsPageRefresh: false,
-    });
-    resetRouterForTesting();
-
-    createBasicPage();
-    const sectionElement =
-        page.shadowRoot!.querySelector('settings-section[section=aiInfoCard]');
-    assertFalse(!!sectionElement);
-  });
-
-  test('infoCardVisible', function() {
-    loadTimeData.overrideValues({
-      showAdvancedFeaturesMainControl: true,
-      enableAiSettingsPageRefresh: true,
-    });
-    resetRouterForTesting();
-
-    createBasicPage();
-    const sectionElement =
+    const infoCardSectionElement =
         page.shadowRoot!.querySelector<SettingsSectionElement>(
             'settings-section[section=aiInfoCard]');
-    assertTrue(!!sectionElement);
+    assertTrue(!!infoCardSectionElement);
     assertEquals(
-        routes.AI.section, sectionElement.getAttribute('nest-under-section'));
+        routes.AI.section,
+        infoCardSectionElement.getAttribute('nest-under-section'));
   });
 
   // <if expr="enable_glic">
   test('AIPageGlicSectionVisible', function() {
     loadTimeData.overrideValues({
-      showAdvancedFeaturesMainControl: true,
-      enableAiSettingsPageRefresh: true,
+      showAiPage: true,
       showGlicSettings: true,
     });
     resetRouterForTesting();
@@ -619,12 +602,19 @@
     assertTrue(!!sectionElement);
     assertEquals(
         routes.AI.section, sectionElement.getAttribute('nest-under-section'));
+
+    const infoCardSectionElement =
+        page.shadowRoot!.querySelector<SettingsSectionElement>(
+            'settings-section[section=aiInfoCard]');
+    assertTrue(!!infoCardSectionElement);
+    assertEquals(
+        routes.AI.section,
+        infoCardSectionElement.getAttribute('nest-under-section'));
   });
 
   test('AIPageGlicSectionNotVisible', function() {
     loadTimeData.overrideValues({
-      showAdvancedFeaturesMainControl: true,
-      enableAiSettingsPageRefresh: true,
+      showAiPage: true,
       showGlicSettings: false,
     });
     resetRouterForTesting();
diff --git a/chrome/test/data/webui/settings/glic_page_focus_test.ts b/chrome/test/data/webui/settings/glic_page_focus_test.ts
index 2e0ae3b..d3d673a 100644
--- a/chrome/test/data/webui/settings/glic_page_focus_test.ts
+++ b/chrome/test/data/webui/settings/glic_page_focus_test.ts
@@ -35,7 +35,7 @@
   suiteSetup(function() {
     settingsPrefs = document.createElement('settings-prefs');
     loadTimeData.overrideValues({
-      showAdvancedFeaturesMainControl: true,
+      showAiPage: true,
       showGlicSettings: true,
     });
     resetRouterForTesting();
diff --git a/chrome/test/data/webui/settings/glic_page_test.ts b/chrome/test/data/webui/settings/glic_page_test.ts
index f28d2f19..8a16ced 100644
--- a/chrome/test/data/webui/settings/glic_page_test.ts
+++ b/chrome/test/data/webui/settings/glic_page_test.ts
@@ -94,7 +94,7 @@
   suiteSetup(function() {
     settingsPrefs = document.createElement('settings-prefs');
     loadTimeData.overrideValues({
-      showAdvancedFeaturesMainControl: true,
+      showAiPage: true,
       showGlicSettings: true,
       glicDisallowedByAdmin: false,
     });
diff --git a/chrome/test/data/webui/settings/privacy_guide_completion_fragment_test.ts b/chrome/test/data/webui/settings/privacy_guide_completion_fragment_test.ts
index 0da27f0..84b189ce 100644
--- a/chrome/test/data/webui/settings/privacy_guide_completion_fragment_test.ts
+++ b/chrome/test/data/webui/settings/privacy_guide_completion_fragment_test.ts
@@ -34,7 +34,7 @@
     loadTimeData.overrideValues({
       isPrivacySandboxRestricted: false,
       isPrivacySandboxRestrictedNoticeEnabled: false,
-      showAdvancedFeaturesMainControl: true,
+      showAiPage: true,
     });
     resetRouterForTesting();
   });
@@ -166,7 +166,7 @@
 
   test('aiRowNotShownWhenAiPageHidden', function() {
     loadTimeData.overrideValues({
-      showAdvancedFeaturesMainControl: false,
+      showAiPage: false,
     });
     createPage();
 
@@ -307,7 +307,7 @@
   suiteSetup(function() {
     loadTimeData.overrideValues({
       enableAiSettingsInPrivacyGuide: false,
-      showAdvancedFeaturesMainControl: true,
+      showAiPage: true,
     });
     resetRouterForTesting();
   });
diff --git a/chrome/test/data/webui/settings/privacy_page_test.ts b/chrome/test/data/webui/settings/privacy_page_test.ts
index 4bd0ae5..5c887e54 100644
--- a/chrome/test/data/webui/settings/privacy_page_test.ts
+++ b/chrome/test/data/webui/settings/privacy_page_test.ts
@@ -194,31 +194,6 @@
         thirdPartyCookiesLinkRow.subLabel);
   });
 
-  test('ContentSettingsVisibility', async function() {
-    // Ensure pages are visited so that HTML components are stamped.
-    redesignedPages.forEach(route => Router.getInstance().navigateTo(route));
-    await flushTasks();
-
-    // All redesigned pages, except protocol handlers, pdf documents and
-    // protected content (except chromeos and win), will use a
-    // settings-category-default-radio-group.
-    // Exclude notifications page which is in its own element.
-    // <if expr="is_chromeos or is_win">
-    assertEquals(
-        page.shadowRoot!
-            .querySelectorAll('settings-category-default-radio-group')
-            .length,
-        redesignedPages.length - 3);
-    // </if>
-    // <if expr="not is_chromeos and not is_win">
-    assertEquals(
-        page.shadowRoot!
-            .querySelectorAll('settings-category-default-radio-group')
-            .length,
-        redesignedPages.length - 4);
-    // </if>
-  });
-
   test('NotificationPage', async function() {
     await createPage();
 
@@ -308,6 +283,51 @@
   });
 });
 
+// Isolated ContentSettingsVisibility test suite due to significantly higher
+// execution time (10-20x factor) of that specific tests compare to other
+// sub-tests.
+suite('ContentSettingsVisibility', function() {
+  let page: SettingsPrivacyPageElement;
+  let settingsPrefs: SettingsPrefsElement;
+
+  suiteSetup(function() {
+    settingsPrefs = document.createElement('settings-prefs');
+    return CrSettingsPrefs.initialized;
+  });
+
+  setup(function() {
+    document.body.innerHTML = window.trustedTypes!.emptyHTML;
+
+    page = document.createElement('settings-privacy-page');
+    page.prefs = settingsPrefs.prefs!;
+    document.body.appendChild(page);
+    return flushTasks();
+  });
+
+  test('ContentSettingsVisibility', async function() {
+    // Ensure pages are visited so that HTML components are stamped.
+    redesignedPages.forEach(route => Router.getInstance().navigateTo(route));
+    await flushTasks();
+
+    // All redesigned pages will use `settings-category-default-radio-group`,
+    // except
+    //   1. protocol handlers,
+    //   2. pdf documents,
+    //   3. protected content (except on chromeos and win),
+    //   4. notifications (is in its own element)
+    let expectedPagesCount = redesignedPages.length - 4;
+    // <if expr="is_chromeos or is_win">
+    expectedPagesCount += 1;
+    // </if>
+
+    assertEquals(
+        page.shadowRoot!
+            .querySelectorAll('settings-category-default-radio-group')
+            .length,
+        expectedPagesCount);
+  });
+});
+
 suite(`PrivacySandbox`, function() {
   let page: SettingsPrivacyPageElement;
   let settingsPrefs: SettingsPrefsElement;
diff --git a/chrome/test/data/webui/settings/settings_browsertest.cc b/chrome/test/data/webui/settings/settings_browsertest.cc
index 6fb42dd..e7797373 100644
--- a/chrome/test/data/webui/settings/settings_browsertest.cc
+++ b/chrome/test/data/webui/settings/settings_browsertest.cc
@@ -186,38 +186,31 @@
 }
 #endif
 
-class SettingsAiPageTest : public SettingsBrowserTest {
- private:
-  base::test::ScopedFeatureList scoped_feature_list_{
-      optimization_guide::features::kAiSettingsPageRefresh};
-};
-
-IN_PROC_BROWSER_TEST_F(SettingsAiPageTest, ExperimentalAdvancedPage) {
-  RunTest("settings/ai_page_test.js",
-          "runMochaSuite('ExperimentalAdvancedPage')");
+IN_PROC_BROWSER_TEST_F(SettingsTest, AiPage) {
+  RunTest("settings/ai_page_test.js", "runMochaSuite('AiPage')");
 }
 
-IN_PROC_BROWSER_TEST_F(SettingsAiPageTest, AiInfoCard) {
+IN_PROC_BROWSER_TEST_F(SettingsTest, AiInfoCard) {
   RunTest("settings/ai_info_card_test.js", "mocha.run()");
 }
 
-IN_PROC_BROWSER_TEST_F(SettingsAiPageTest, TabOrganizationSubpage) {
+IN_PROC_BROWSER_TEST_F(SettingsTest, TabOrganizationSubpage) {
   RunTest("settings/ai_tab_organization_subpage_test.js", "mocha.run()");
 }
 
-IN_PROC_BROWSER_TEST_F(SettingsAiPageTest, HistorySearchSubpage) {
+IN_PROC_BROWSER_TEST_F(SettingsTest, HistorySearchSubpage) {
   RunTest("settings/ai_history_search_subpage_test.js", "mocha.run()");
 }
 
-IN_PROC_BROWSER_TEST_F(SettingsAiPageTest, CompareSubpage) {
+IN_PROC_BROWSER_TEST_F(SettingsTest, CompareSubpage) {
   RunTest("settings/ai_compare_subpage_test.js", "mocha.run()");
 }
 
-IN_PROC_BROWSER_TEST_F(SettingsAiPageTest, LoggingInfoBullet) {
+IN_PROC_BROWSER_TEST_F(SettingsTest, LoggingInfoBullet) {
   RunTest("settings/ai_logging_info_bullet_test.js", "mocha.run()");
 }
 
-IN_PROC_BROWSER_TEST_F(SettingsAiPageTest, PolicyIndicator) {
+IN_PROC_BROWSER_TEST_F(SettingsTest, PolicyIndicator) {
   RunTest("settings/ai_policy_indicator_test.js", "mocha.run()");
 }
 
@@ -705,9 +698,7 @@
  public:
   SettingsComposePageTest() {
     scoped_feature_list_.InitWithFeatures(
-        /*enabled_features=*/{optimization_guide::features::
-                                  kAiSettingsPageRefresh,
-                              compose::features::kEnableComposeProactiveNudge},
+        /*enabled_features=*/{compose::features::kEnableComposeProactiveNudge},
         /*disabled_features=*/{});
   }
 
@@ -782,9 +773,8 @@
   RunTest("settings/basic_page_test.js", "runMochaSuite('Performance')");
 }
 
-IN_PROC_BROWSER_TEST_F(SettingsTest, ExperimentalAdvanced) {
-  RunTest("settings/basic_page_test.js",
-          "runMochaSuite('ExperimentalAdvanced')");
+IN_PROC_BROWSER_TEST_F(SettingsTest, AiSections) {
+  RunTest("settings/basic_page_test.js", "runMochaSuite('AiSections')");
 }
 
 using SettingsClearBrowsingDataTest = SettingsBrowserTest;
@@ -1199,17 +1189,15 @@
           "runMochaSuite('WebPrintingNotShown')");
 }
 
-// Flaky on linux debug builds. https://crbug.com/331366001.
-#if BUILDFLAG(IS_LINUX) && !defined(NDEBUG)
-#define MAYBE_PrivacyPage DISABLED_PrivacyPage
-#else
-#define MAYBE_PrivacyPage PrivacyPage
-#endif
-IN_PROC_BROWSER_TEST_F(SettingsPrivacyPageTestNoTestingConfig,
-                       MAYBE_PrivacyPage) {
+IN_PROC_BROWSER_TEST_F(SettingsPrivacyPageTestNoTestingConfig, PrivacyPage) {
   RunTest("settings/privacy_page_test.js", "runMochaSuite('PrivacyPage')");
 }
 
+IN_PROC_BROWSER_TEST_F(SettingsPrivacyPageTest, ContentSettingsVisibility) {
+  RunTest("settings/privacy_page_test.js",
+          "runMochaSuite('ContentSettingsVisibility')");
+}
+
 IN_PROC_BROWSER_TEST_F(SettingsPrivacyPageTest, PrivacySandbox) {
   RunTest("settings/privacy_page_test.js", "runMochaSuite('PrivacySandbox')");
 }
diff --git a/chrome/test/data/webui/settings/settings_menu_test.ts b/chrome/test/data/webui/settings/settings_menu_test.ts
index 1bbe8386..8f1114f 100644
--- a/chrome/test/data/webui/settings/settings_menu_test.ts
+++ b/chrome/test/data/webui/settings/settings_menu_test.ts
@@ -83,7 +83,7 @@
   });
 
   test('noExperimental', async function() {
-    loadTimeData.overrideValues({showAdvancedFeaturesMainControl: false});
+    loadTimeData.overrideValues({showAiPage: false});
     resetRouterForTesting();
     createSettingsMenu();
     await flushTasks();
@@ -94,7 +94,7 @@
   });
 
   test('navigateToExperimental', async function() {
-    loadTimeData.overrideValues({showAdvancedFeaturesMainControl: true});
+    loadTimeData.overrideValues({showAiPage: true});
     resetRouterForTesting();
     createSettingsMenu();
     Router.getInstance().navigateTo(routes.AI);
@@ -156,8 +156,7 @@
 
   test('aiPageMenuClick', async function() {
     loadTimeData.overrideValues({
-      showAdvancedFeaturesMainControl: true,
-      enableAiSettingsPageRefresh: true,
+      showAiPage: true,
     });
     resetRouterForTesting();
     createSettingsMenu();
diff --git a/chrome/updater/installer_mac.cc b/chrome/updater/installer_mac.cc
index bcc3ae60..17921792 100644
--- a/chrome/updater/installer_mac.cc
+++ b/chrome/updater/installer_mac.cc
@@ -28,8 +28,8 @@
     bool usage_stats_enabled,
     base::TimeDelta timeout,
     InstallProgressCallback /*progress_callback*/) {
-  if (!PrepareToRunBundle(app_installer)) {
-    VLOG(0) << "Prep failed -- Gatekeeper may prompt for " << app_installer;
+  if (!RemoveQuarantineAttributes(app_installer.DirName())) {
+    VLOG(2) << "Ignoring failure to remove quarantine attributes.";
   }
   VLOG(1) << "Running application install at " << app_installer;
 
diff --git a/chrome/updater/mac/setup/.install.sh b/chrome/updater/mac/setup/.install.sh
index 7c980f34..a3dbe44 100755
--- a/chrome/updater/mac/setup/.install.sh
+++ b/chrome/updater/mac/setup/.install.sh
@@ -10,5 +10,7 @@
 
 env
 
+/usr/bin/gktool scan "$1/${PRODUCT_NAME}.app"
+
 "$1/${PRODUCT_NAME}.app/Contents/MacOS/${PRODUCT_NAME}" \
     ${SERVER_ARGS} ${KS_kServerActionArguments}
diff --git a/chrome/updater/util/mac_util.h b/chrome/updater/util/mac_util.h
index 1ff28ecc..b23eeb1 100644
--- a/chrome/updater/util/mac_util.h
+++ b/chrome/updater/util/mac_util.h
@@ -56,6 +56,9 @@
 // run as best as it can. Returns whether every applicable prep step succeeded.
 bool PrepareToRunBundle(const base::FilePath& bundle_path);
 
+// Recursively remove quarantine attributes on `path`. Returns false on error.
+bool RemoveQuarantineAttributes(const base::FilePath& path);
+
 std::string GetDomain(UpdaterScope scope);
 
 // Reads the value associated with `key` from the plist at `path`. Returns
diff --git a/chrome/updater/util/mac_util.mm b/chrome/updater/util/mac_util.mm
index 7f9097af..340bc38 100644
--- a/chrome/updater/util/mac_util.mm
+++ b/chrome/updater/util/mac_util.mm
@@ -49,24 +49,6 @@
       .Append(FILE_PATH_LITERAL("MacOS"));
 }
 
-// Recursively remove quarantine attributes on the path. Emits a log message
-// if it fails.
-bool RemoveQuarantineAttributes(const base::FilePath& updater_bundle_path) {
-  bool success = base::mac::RemoveQuarantineAttribute(updater_bundle_path);
-  base::FileEnumerator file_enumerator(
-      base::FilePath(updater_bundle_path), true,
-      base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES |
-          base::FileEnumerator::SHOW_SYM_LINKS);
-  for (base::FilePath name = file_enumerator.Next(); !name.empty();
-       name = file_enumerator.Next()) {
-    success = base::mac::RemoveQuarantineAttribute(name) && success;
-  }
-
-  VLOG_IF(0, !success) << "Failed to remove quarantine attributes from "
-                       << updater_bundle_path;
-  return success;
-}
-
 // On supported versions of macOS, scan the specified bundle with Gatekeeper
 // so it won't pop up a user-visible "Verifying..." box for the duration of
 // the scan when an executable in the bundle is later launched for the first
@@ -100,6 +82,19 @@
 
 }  // namespace
 
+bool RemoveQuarantineAttributes(const base::FilePath& path) {
+  bool success = base::mac::RemoveQuarantineAttribute(path);
+  base::FileEnumerator file_enumerator(
+      base::FilePath(path), true,
+      base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES |
+          base::FileEnumerator::SHOW_SYM_LINKS);
+  for (base::FilePath name = file_enumerator.Next(); !name.empty();
+       name = file_enumerator.Next()) {
+    success = base::mac::RemoveQuarantineAttribute(name) && success;
+  }
+  return success;
+}
+
 std::string GetDomain(UpdaterScope scope) {
   switch (scope) {
     case UpdaterScope::kSystem:
diff --git a/chromeos/ash/components/dbus/concierge/BUILD.gn b/chromeos/ash/components/dbus/concierge/BUILD.gn
index 98a70089..b67c7ec1 100644
--- a/chromeos/ash/components/dbus/concierge/BUILD.gn
+++ b/chromeos/ash/components/dbus/concierge/BUILD.gn
@@ -40,6 +40,8 @@
     "//third_party/cros_system_api/dbus/vm_concierge/concierge_service.proto",
   ]
   proto_deps = [ "//chromeos/ash/components/dbus/arc:arc_proto" ]
+  deps = [ "//chromeos/ash/components/dbus/arc:arc_proto_lib" ]
+
   import_dirs = [ "//third_party/cros_system_api/dbus" ]
   proto_out_dir = "chromeos/ash/components/dbus/vm_concierge"
 }
diff --git a/chromeos/ash/experiences/arc/arc_features.cc b/chromeos/ash/experiences/arc/arc_features.cc
index 6ea21b9..d954fbc 100644
--- a/chromeos/ash/experiences/arc/arc_features.cc
+++ b/chromeos/ash/experiences/arc/arc_features.cc
@@ -57,12 +57,6 @@
              "ArcBootCompletedBroadcast",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
-// Controls whether independent ARC container app killer is enabled to replace
-// the ARC container app killing in TabManagerDelegate.
-BASE_FEATURE(kContainerAppKiller,
-             "ContainerAppKiller",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
 // Controls experimental Custom Tabs feature for ARC.
 BASE_FEATURE(kCustomTabsExperimentFeature,
              "ArcCustomTabsExperiment",
diff --git a/chromeos/ash/experiences/arc/arc_features.h b/chromeos/ash/experiences/arc/arc_features.h
index 538b98c..bba10a3 100644
--- a/chromeos/ash/experiences/arc/arc_features.h
+++ b/chromeos/ash/experiences/arc/arc_features.h
@@ -23,7 +23,6 @@
 BASE_DECLARE_FEATURE(kBlockIoScheduler);
 BASE_DECLARE_FEATURE_PARAM(bool, kEnableDataBlockIoScheduler);
 BASE_DECLARE_FEATURE(kBootCompletedBroadcastFeature);
-BASE_DECLARE_FEATURE(kContainerAppKiller);
 BASE_DECLARE_FEATURE(kCustomTabsExperimentFeature);
 BASE_DECLARE_FEATURE(kDeferArcActivationUntilUserSessionStartUpTaskCompletion);
 BASE_DECLARE_FEATURE_PARAM(int, kDeferArcActivationHistoryWindow);
diff --git a/clank b/clank
index b98a5841..968450a 160000
--- a/clank
+++ b/clank
@@ -1 +1 @@
-Subproject commit b98a5841d71a8998413b6811f80897742ea8b9e1
+Subproject commit 968450ab8d1bcffb25acc11625dc295076a5c2d1
diff --git a/components/autofill/core/browser/BUILD.gn b/components/autofill/core/browser/BUILD.gn
index 87c8a04..35dc664 100644
--- a/components/autofill/core/browser/BUILD.gn
+++ b/components/autofill/core/browser/BUILD.gn
@@ -1061,6 +1061,7 @@
     "payments/credit_card_access_manager_test_api.h",
     "payments/credit_card_access_manager_test_base.cc",
     "payments/credit_card_access_manager_test_base.h",
+    "payments/iban_manager_test_api.h",
     "payments/mock_iban_access_manager.cc",
     "payments/mock_iban_access_manager.h",
     "payments/mock_test_payments_network_interface.cc",
diff --git a/components/autofill/core/browser/foundations/browser_autofill_manager.cc b/components/autofill/core/browser/foundations/browser_autofill_manager.cc
index eef82a1..66355504 100644
--- a/components/autofill/core/browser/foundations/browser_autofill_manager.cc
+++ b/components/autofill/core/browser/foundations/browser_autofill_manager.cc
@@ -2084,6 +2084,14 @@
         base::UserMetricsAction("PlusAddresses.AddressFillSuggestionShown"));
   }
 
+  if (shown_suggestion_types.contains(SuggestionType::kIbanEntry) &&
+      client().GetPaymentsAutofillClient()->GetIbanManager()) {
+    client()
+        .GetPaymentsAutofillClient()
+        ->GetIbanManager()
+        ->OnIbanSuggestionsShown(field_id);
+  }
+
   FormStructure* form_structure = nullptr;
   AutofillField* autofill_field = nullptr;
   const bool has_cached_form_and_field = GetCachedFormAndField(
diff --git a/components/autofill/core/browser/foundations/browser_autofill_manager_unittest.cc b/components/autofill/core/browser/foundations/browser_autofill_manager_unittest.cc
index ed80763..85b0df92 100644
--- a/components/autofill/core/browser/foundations/browser_autofill_manager_unittest.cc
+++ b/components/autofill/core/browser/foundations/browser_autofill_manager_unittest.cc
@@ -5763,6 +5763,27 @@
                         HasSubstr("Autofill.FormEvents.CreditCard"))));
 }
 
+TEST_F(BrowserAutofillManagerTest,
+       DidShowSuggestions_LogIbanSuggestionsShownMetric) {
+  FormData form = CreateTestIbanFormData();
+  FormsSeen({form});
+
+  base::HistogramTester histogram_tester;
+  manager().DidShowSuggestions({Suggestion(SuggestionType::kIbanEntry)}, form,
+                               form.fields().back().global_id(), {});
+  manager().DidShowSuggestions({Suggestion(SuggestionType::kIbanEntry)}, form,
+                               form.fields().back().global_id(), {});
+
+  EXPECT_THAT(
+      histogram_tester.GetAllSamples("Autofill.Iban.Suggestions"),
+      BucketsAre(
+          base::Bucket(
+              autofill_metrics::IbanSuggestionsEvent::kIbanSuggestionsShown, 2),
+          base::Bucket(
+              autofill_metrics::IbanSuggestionsEvent::kIbanSuggestionsShownOnce,
+              1)));
+}
+
 TEST_F(BrowserAutofillManagerTest, DidShowSuggestions_LogByType_AddressOnly) {
   // Create a form with name and address fields.
   FormData form;
diff --git a/components/autofill/core/browser/ml_model/autofill_ai/autofill_ai_model_executor_impl.cc b/components/autofill/core/browser/ml_model/autofill_ai/autofill_ai_model_executor_impl.cc
index 9a7cbd4..647cb4f 100644
--- a/components/autofill/core/browser/ml_model/autofill_ai/autofill_ai_model_executor_impl.cc
+++ b/components/autofill/core/browser/ml_model/autofill_ai/autofill_ai_model_executor_impl.cc
@@ -38,7 +38,7 @@
     optimization_guide::ModelQualityLogsUploaderService* mqls_uploader)
     : model_cache_(CHECK_DEREF(model_cache)),
       model_executor_(CHECK_DEREF(model_executor)),
-      mqls_uploader_(CHECK_DEREF(mqls_uploader)) {}
+      mqls_uploader_(mqls_uploader) {}
 
 AutofillAiModelExecutorImpl::~AutofillAiModelExecutorImpl() = default;
 
@@ -165,7 +165,8 @@
     std::unique_ptr<optimization_guide::proto::FormsClassificationsLoggingData>
         logging_data) {
   if (!base::FeatureList::IsEnabled(
-          autofill::features::kAutofillAiUploadModelRequestAndResponse)) {
+          autofill::features::kAutofillAiUploadModelRequestAndResponse) ||
+      !mqls_uploader_) {
     return;
   }
   // Note that the logging happens when `log_entry` goes out of scope.
diff --git a/components/autofill/core/browser/ml_model/autofill_ai/autofill_ai_model_executor_impl.h b/components/autofill/core/browser/ml_model/autofill_ai/autofill_ai_model_executor_impl.h
index 0c085e3..c164adc1 100644
--- a/components/autofill/core/browser/ml_model/autofill_ai/autofill_ai_model_executor_impl.h
+++ b/components/autofill/core/browser/ml_model/autofill_ai/autofill_ai_model_executor_impl.h
@@ -63,7 +63,7 @@
 
   const raw_ref<optimization_guide::OptimizationGuideModelExecutor>
       model_executor_;
-  const raw_ref<optimization_guide::ModelQualityLogsUploaderService>
+  const raw_ptr<optimization_guide::ModelQualityLogsUploaderService>
       mqls_uploader_;
 
   // Form signatures for which a query is currently ongoing. The goal is to
diff --git a/components/autofill/core/browser/payments/iban_manager.cc b/components/autofill/core/browser/payments/iban_manager.cc
index 4df8cb9..572ff55 100644
--- a/components/autofill/core/browser/payments/iban_manager.cc
+++ b/components/autofill/core/browser/payments/iban_manager.cc
@@ -78,6 +78,10 @@
   uma_recorder_.OnIbanSuggestionSelected(suggestion);
 }
 
+void IbanManager::OnIbanSuggestionsShown(FieldGlobalId field_global_id) {
+  uma_recorder_.OnIbanSuggestionsShown(field_global_id);
+}
+
 void IbanManager::UmaRecorder::OnIbanSuggestionsShown(
     FieldGlobalId field_global_id) {
   // Log metrics related to the IBAN-related suggestions in the popup.
@@ -129,7 +133,6 @@
     return {};
   }
 
-  uma_recorder_.OnIbanSuggestionsShown(field.global_id());
   return GetSuggestionsForIbans(ibans);
 }
 
diff --git a/components/autofill/core/browser/payments/iban_manager.h b/components/autofill/core/browser/payments/iban_manager.h
index e262513c..fd7ad10 100644
--- a/components/autofill/core/browser/payments/iban_manager.h
+++ b/components/autofill/core/browser/payments/iban_manager.h
@@ -48,7 +48,13 @@
           on_suggestions_returned);
   virtual void OnSingleFieldSuggestionSelected(const Suggestion& suggestion);
 
+  // Called when IBAN suggestions are shown; used to record metrics.
+  // `field_global_id` is the global id of the field that had suggestions shown.
+  void OnIbanSuggestionsShown(FieldGlobalId field_global_id);
+
  private:
+  friend class IbanManagerTestApi;
+
   // Records metrics related to the IBAN suggestions popup.
   class UmaRecorder {
    public:
@@ -56,6 +62,8 @@
     void OnIbanSuggestionSelected(const Suggestion& suggestion);
 
    private:
+    friend class IbanManagerTestApi;
+
     // The global id of the field that most recently had IBAN suggestions shown.
     FieldGlobalId most_recent_suggestions_shown_field_global_id_;
 
diff --git a/components/autofill/core/browser/payments/iban_manager_test_api.h b/components/autofill/core/browser/payments/iban_manager_test_api.h
new file mode 100644
index 0000000..6a7530b
--- /dev/null
+++ b/components/autofill/core/browser/payments/iban_manager_test_api.h
@@ -0,0 +1,32 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_AUTOFILL_CORE_BROWSER_PAYMENTS_IBAN_MANAGER_TEST_API_H_
+#define COMPONENTS_AUTOFILL_CORE_BROWSER_PAYMENTS_IBAN_MANAGER_TEST_API_H_
+
+#include "components/autofill/core/browser/payments/iban_manager.h"
+
+namespace autofill {
+
+class IbanManagerTestApi {
+ public:
+  explicit IbanManagerTestApi(IbanManager& iban_manager)
+      : iban_manager_(iban_manager) {}
+
+  void set_most_recent_suggestions_shown_field_global_id(
+      FieldGlobalId field_global_id) {
+    iban_manager_->uma_recorder_
+        .most_recent_suggestions_shown_field_global_id_ = field_global_id;
+  }
+
+ private:
+  const raw_ref<IbanManager> iban_manager_;
+};
+
+inline IbanManagerTestApi test_api(IbanManager& iban_manager) {
+  return IbanManagerTestApi(iban_manager);
+}
+
+}  // namespace autofill
+#endif  // COMPONENTS_AUTOFILL_CORE_BROWSER_PAYMENTS_IBAN_MANAGER_TEST_API_H_
diff --git a/components/autofill/core/browser/payments/iban_manager_unittest.cc b/components/autofill/core/browser/payments/iban_manager_unittest.cc
index b475395f..8fc49e58 100644
--- a/components/autofill/core/browser/payments/iban_manager_unittest.cc
+++ b/components/autofill/core/browser/payments/iban_manager_unittest.cc
@@ -21,6 +21,7 @@
 #include "components/autofill/core/browser/form_structure_test_api.h"
 #include "components/autofill/core/browser/foundations/test_autofill_client.h"
 #include "components/autofill/core/browser/integrators/optimization_guide/mock_autofill_optimization_guide.h"
+#include "components/autofill/core/browser/payments/iban_manager_test_api.h"
 #include "components/autofill/core/browser/test_utils/autofill_test_utils.h"
 #include "components/autofill/core/common/autofill_payments_features.h"
 #include "components/grit/components_scaled_resources.h"
@@ -660,34 +661,6 @@
       1);
 }
 
-// Test that the metrics for IBAN-related suggestions shown and shown once are
-// logged correctly.
-TEST_P(IbanManagerTest, Metrics_SuggestionsShown) {
-  base::HistogramTester histogram_tester;
-  SetUpLocalIban(test::kIbanValue, kNickname_0);
-
-  autofill_field_->set_renderer_id(test::MakeFieldRendererId());
-
-  // Simulate request for suggestions.
-  MockSuggestionsReturnedCallback mock_callback;
-  EXPECT_TRUE(iban_manager_.OnGetSingleFieldSuggestions(
-      *autofill_field_, *autofill_field_, autofill_client_,
-      mock_callback.GetNewRef()));
-
-  EXPECT_TRUE(iban_manager_.OnGetSingleFieldSuggestions(
-      *autofill_field_, *autofill_field_, autofill_client_,
-      mock_callback.GetNewRef()));
-
-  EXPECT_THAT(
-      histogram_tester.GetAllSamples("Autofill.Iban.Suggestions"),
-      BucketsAre(
-          base::Bucket(
-              autofill_metrics::IbanSuggestionsEvent::kIbanSuggestionsShown, 2),
-          base::Bucket(
-              autofill_metrics::IbanSuggestionsEvent::kIbanSuggestionsShownOnce,
-              1)));
-}
-
 // Test that the metrics for local IBAN suggestion selected (once and total
 // count) are logged correctly.
 TEST_P(IbanManagerTest, Metrics_LocalIbanSuggestionSelected) {
@@ -697,6 +670,8 @@
   SetUpLocalIban(test::kIbanValue_2, "");
 
   autofill_field_->set_renderer_id(test::MakeFieldRendererId());
+  test_api(iban_manager_).set_most_recent_suggestions_shown_field_global_id(
+      autofill_field_->global_id());
 
   // Simulate request for suggestions and select one suggested IBAN.
   MockSuggestionsReturnedCallback mock_callback;
@@ -738,6 +713,8 @@
       kNickname_0));
 
   autofill_field_->set_renderer_id(test::MakeFieldRendererId());
+  test_api(iban_manager_).set_most_recent_suggestions_shown_field_global_id(
+    autofill_field_->global_id());
 
   // Simulate request for suggestions and select one suggested IBAN.
   MockSuggestionsReturnedCallback mock_callback;
@@ -785,6 +762,8 @@
   // Input a prefix that does not have any matching IBAN value so that no IBAN
   // suggestions will be shown.
   autofill_field_->set_value(u"XY");
+  test_api(iban_manager_).set_most_recent_suggestions_shown_field_global_id(
+    autofill_field_->global_id());
 
   MockSuggestionsReturnedCallback mock_callback;
   EXPECT_CALL(mock_callback, Run(autofill_field_->global_id(), IsEmpty()));
diff --git a/components/autofill/ios/common/features.mm b/components/autofill/ios/common/features.mm
index 3fe47794..64413693 100644
--- a/components/autofill/ios/common/features.mm
+++ b/components/autofill/ios/common/features.mm
@@ -25,7 +25,7 @@
 // LINT.IfChange(autofill_correct_user_edited_bit_in_parsed_field)
 BASE_FEATURE(kAutofillCorrectUserEditedBitInParsedField,
              "AutofillCorrectUserEditedBitInParsedField",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 // LINT.ThenChange(/components/autofill/ios/form_util/resources/autofill_form_features.ts:autofill_correct_user_edited_bit_in_parsed_field)
 
 // LINT.IfChange(autofill_dedupe_form_submission)
diff --git a/components/bookmarks/common/bookmark_pref_names.h b/components/bookmarks/common/bookmark_pref_names.h
index 87758a9..d83aec1 100644
--- a/components/bookmarks/common/bookmark_pref_names.h
+++ b/components/bookmarks/common/bookmark_pref_names.h
@@ -14,6 +14,7 @@
 inline constexpr char kBookmarkEditorExpandedNodes[] =
     "bookmark_editor.expanded_nodes";
 // Modifying bookmarks is completely disabled when this is set to false.
+// This includes uploading bookmarks to a sync-ing account.
 inline constexpr char kEditBookmarksEnabled[] = "bookmarks.editing_enabled";
 // A list of bookmarks to include in a Managed Bookmarks root node. Each
 // list item is a dictionary containing a "name" and an "url" entry, detailing
diff --git a/components/embedder_support/android/delegate/web_contents_delegate_android.cc b/components/embedder_support/android/delegate/web_contents_delegate_android.cc
index 3726055..807f43c 100644
--- a/components/embedder_support/android/delegate/web_contents_delegate_android.cc
+++ b/components/embedder_support/android/delegate/web_contents_delegate_android.cc
@@ -47,6 +47,15 @@
 using content::WebContents;
 using content::WebContentsDelegate;
 
+namespace {
+
+// The amount of time to disallow repeated pointer lock calls after the user
+// successfully escapes from one lock request.
+constexpr base::TimeDelta kEffectiveUserEscapeDuration =
+    base::Milliseconds(1250);
+
+}  // namespace
+
 namespace web_contents_delegate_android {
 
 WebContentsDelegateAndroid::WebContentsDelegateAndroid(
@@ -290,6 +299,22 @@
       env, obj, url::GURLAndroid::FromNativeGURL(env, source->GetVisibleURL()));
 }
 
+content::KeyboardEventProcessingResult
+WebContentsDelegateAndroid::PreHandleKeyboardEvent(
+    WebContents* source,
+    const input::NativeWebKeyboardEvent& event) {
+  if (event.native_key_code == AKEYCODE_ESCAPE) {
+    auto* rwhva = source->GetTopLevelRenderWidgetHostView();
+    if (rwhva && rwhva->IsPointerLocked()) {
+      rwhva->UnlockPointer();
+      pointer_lock_last_user_escape_time_ = base::TimeTicks::Now();
+      return content::KeyboardEventProcessingResult::HANDLED;
+    }
+  }
+
+  return content::KeyboardEventProcessingResult::NOT_HANDLED;
+}
+
 bool WebContentsDelegateAndroid::HandleKeyboardEvent(
     WebContents* source,
     const input::NativeWebKeyboardEvent& event) {
@@ -374,8 +399,23 @@
                                                    last_unlocked_by_target);
   }
 
-  // TODO(crbug.com/397609822): add checks on user_gesture & reuse the
-  // ExclusiveAccessManager
+  // TODO(https://crbug.com/415732870): reuse the ExclusiveAccessManager
+  // This part is taken from PointerLockController, See
+  // `PointerLockController::RequestToLockPointer()` for more info.
+  if (!last_unlocked_by_target && !web_contents->IsFullscreen()) {
+    if (!user_gesture) {
+      web_contents->GotResponseToPointerLockRequest(
+          blink::mojom::PointerLockResult::kRequiresUserGesture);
+      return;
+    }
+    if (base::TimeTicks::Now() <
+        pointer_lock_last_user_escape_time_ + kEffectiveUserEscapeDuration) {
+      web_contents->GotResponseToPointerLockRequest(
+          blink::mojom::PointerLockResult::kUserRejected);
+      return;
+    }
+  }
+
   web_contents->GotResponseToPointerLockRequest(
       blink::mojom::PointerLockResult::kSuccess);
 }
diff --git a/components/embedder_support/android/delegate/web_contents_delegate_android.h b/components/embedder_support/android/delegate/web_contents_delegate_android.h
index 996b3d45..7a8cdc2 100644
--- a/components/embedder_support/android/delegate/web_contents_delegate_android.h
+++ b/components/embedder_support/android/delegate/web_contents_delegate_android.h
@@ -12,6 +12,7 @@
 #include "base/android/jni_weak_ref.h"
 #include "base/android/scoped_java_ref.h"
 #include "base/compiler_specific.h"
+#include "content/public/browser/keyboard_event_processing_result.h"
 #include "content/public/browser/web_contents_delegate.h"
 #include "third_party/blink/public/mojom/frame/blocked_navigation_types.mojom.h"
 
@@ -92,6 +93,9 @@
                               int32_t line_no,
                               const std::u16string& source_id) override;
   void UpdateTargetURL(content::WebContents* source, const GURL& url) override;
+  content::KeyboardEventProcessingResult PreHandleKeyboardEvent(
+      content::WebContents* source,
+      const input::NativeWebKeyboardEvent& event) override;
   bool HandleKeyboardEvent(content::WebContents* source,
                            const input::NativeWebKeyboardEvent& event) override;
   bool TakeFocus(content::WebContents* source, bool reverse) override;
@@ -145,6 +149,9 @@
   // strong reference to that object as long as they want to receive callbacks
   // on it. Using a weak ref here allows it to be correctly GCed.
   JavaObjectWeakGlobalRef weak_java_delegate_;
+
+  // Timestamp when the user last successfully escaped from a lock request.
+  base::TimeTicks pointer_lock_last_user_escape_time_;
 };
 
 }  // namespace web_contents_delegate_android
diff --git a/components/external_intents/android/java/src/org/chromium/components/external_intents/InterceptNavigationDelegateClient.java b/components/external_intents/android/java/src/org/chromium/components/external_intents/InterceptNavigationDelegateClient.java
index a0cdafc1..fef2500 100644
--- a/components/external_intents/android/java/src/org/chromium/components/external_intents/InterceptNavigationDelegateClient.java
+++ b/components/external_intents/android/java/src/org/chromium/components/external_intents/InterceptNavigationDelegateClient.java
@@ -54,6 +54,9 @@
     /* Returns true if the client hosting this tab is a PWA (WebAPK or TWA). */
     boolean isTabInPWA();
 
+    /* Returns true if the client hosting this tab is a Browser. */
+    boolean isTabInBrowser();
+
     /** Returns whether this Activity is currently in Android desktop windowing mode. */
     boolean isInDesktopWindowingMode();
 
diff --git a/components/omnibox/browser/autocomplete_controller_metrics.cc b/components/omnibox/browser/autocomplete_controller_metrics.cc
index 70cd8f6..2de078e 100644
--- a/components/omnibox/browser/autocomplete_controller_metrics.cc
+++ b/components/omnibox/browser/autocomplete_controller_metrics.cc
@@ -63,9 +63,10 @@
   // we can avoid having to construct their names and look them up each time.
 
   // The max size of each of the histogram tables.
-  constexpr int kMaxHistogramIndex =
+  constexpr int kMaxHistogramSize =
       (static_cast<int>(MetricNameSuffix::kMaxValue) +
-       metrics::OmniboxEventProto_ProviderType_ProviderType_MAX);
+       metrics::OmniboxEventProto_ProviderType_ProviderType_MAX) +
+      1;
 
   // Validate the histogram lookup parameters:
   // * name_suffix is in [0..kMaxValue]
@@ -91,7 +92,7 @@
   //   Done, LastChange, DefaultChange, Provider-0, Provider-1, ...
 #define STATIC_HISTOGRAM_TIMES_POINTER_GROUP(name, sample)    \
   STATIC_HISTOGRAM_POINTER_GROUP(                             \
-      name, histogram_index, kMaxHistogramIndex,              \
+      name, histogram_index, kMaxHistogramSize,               \
       AddTimeMillisecondsGranularity(sample),                 \
       base::Histogram::FactoryTimeGet(                        \
           name, base::Milliseconds(1), base::Seconds(10), 50, \
diff --git a/components/password_manager/core/browser/features/password_features.cc b/components/password_manager/core/browser/features/password_features.cc
index 52dee54e..bdee31a2 100644
--- a/components/password_manager/core/browser/features/password_features.cc
+++ b/components/password_manager/core/browser/features/password_features.cc
@@ -75,7 +75,7 @@
 #if BUILDFLAG(IS_IOS)
 BASE_FEATURE(kIosCleanupHangingPasswordFormExtractionRequests,
              "IosCleanupHangingPasswordFormExtractionRequests",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 const base::FeatureParam<int> kIosPasswordFormExtractionRequestsTimeoutMs = {
     &kIosCleanupHangingPasswordFormExtractionRequests,
     /*name=*/"period-ms", /*default_value=*/250};
@@ -86,7 +86,7 @@
 
 BASE_FEATURE(kIOSPasswordBottomSheetV2,
              "IOSPasswordBottomSheetV2",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 
 BASE_FEATURE(kIOSProactivePasswordGenerationBottomSheet,
              "kIOSProactivePasswordGenerationBottomSheet",
diff --git a/components/performance_manager/features.cc b/components/performance_manager/features.cc
index b72e600..755b04be 100644
--- a/components/performance_manager/features.cc
+++ b/components/performance_manager/features.cc
@@ -269,6 +269,28 @@
              "RecordFreezingEligibilityUKM",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
+BASE_FEATURE(kInfiniteTabsFreezing,
+             "InfiniteTabsFreezing",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
+BASE_FEATURE_PARAM(int,
+                   kInfiniteTabsFreezing_NumProtectedTabs,
+                   &kInfiniteTabsFreezing,
+                   "num_protected_tabs",
+                   5);
+
+BASE_FEATURE_PARAM(base::TimeDelta,
+                   kInfiniteTabsFreezing_UnfreezeInterval,
+                   &kInfiniteTabsFreezing,
+                   "unfreeze_interval",
+                   base::Minutes(1));
+
+BASE_FEATURE_PARAM(base::TimeDelta,
+                   kInfiniteTabsFreezing_UnfreezeDuration,
+                   &kInfiniteTabsFreezing,
+                   "unfreeze_duration",
+                   base::Seconds(5));
+
 BASE_FEATURE(kResourceAttributionIncludeOrigins,
              "ResourceAttributionIncludeOrigins",
              base::FEATURE_ENABLED_BY_DEFAULT);
diff --git a/components/performance_manager/freezing/cannot_freeze_reason.cc b/components/performance_manager/freezing/cannot_freeze_reason.cc
index 64c42b0e..08e628ee 100644
--- a/components/performance_manager/freezing/cannot_freeze_reason.cc
+++ b/components/performance_manager/freezing/cannot_freeze_reason.cc
@@ -52,6 +52,8 @@
       return "has notification permission";
     case CannotFreezeReason::kOptedOut:
       return "opted out";
+    case CannotFreezeReason::kMostRecentlyUsed:
+      return "most recently used";
   }
   NOTREACHED();
 }
diff --git a/components/performance_manager/freezing/freezing_policy.cc b/components/performance_manager/freezing/freezing_policy.cc
index 6897420..376fdbf 100644
--- a/components/performance_manager/freezing/freezing_policy.cc
+++ b/components/performance_manager/freezing/freezing_policy.cc
@@ -23,10 +23,12 @@
 #include "base/timer/timer.h"
 #include "components/performance_manager/graph/page_node_impl.h"
 #include "components/performance_manager/public/features.h"
+#include "components/performance_manager/public/freezing/cannot_freeze_reason.h"
 #include "components/performance_manager/public/freezing/freezing.h"
 #include "components/performance_manager/public/graph/graph.h"
 #include "components/performance_manager/public/graph/node_attached_data.h"
 #include "components/performance_manager/public/graph/node_data_describer_registry.h"
+#include "components/performance_manager/public/graph/page_node.h"
 #include "components/performance_manager/public/resource_attribution/origin_in_browsing_instance_context.h"
 #include "components/performance_manager/public/resource_attribution/resource_contexts.h"
 #include "components/performance_manager/public/resource_attribution/resource_types.h"
@@ -46,18 +48,11 @@
 
 constexpr base::TimeDelta kCPUMeasurementInterval = base::Minutes(1);
 
-CannotFreezeReasonSet CannotFreezeReasonsForType(
-    FreezingPolicy::FreezingType type) {
-  // TODO(crbug.com/415799027): Return different reasons for "infinite tabs
-  // freezing".
-  return CannotFreezeReasonSet::All();
-}
-
 bool HasCannotFreezeReasonForType(
     const CannotFreezeReasonSet& cannot_freeze_reasons,
     FreezingPolicy::FreezingType type) {
   return !base::Intersection(cannot_freeze_reasons,
-                             CannotFreezeReasonsForType(type))
+                             FreezingPolicy::CannotFreezeReasonsForType(type))
               .empty();
 }
 
@@ -116,12 +111,69 @@
   PageFreezingState(const PageFreezingState&) = delete;
   PageFreezingState& operator=(const PageFreezingState&) = delete;
 
+  // Returns the start time (inclusive) and end time (exclusive) of the current
+  // periodic unfreeze period, or of the next one if not currently in a periodic
+  // unfreeze period. A periodic unfreeze period is a period during which a tab
+  // frozen with `FreezingType::kInfiniteTabs` is temporarily unfrozen.
+  // Concretely, if the page must be unfrozen from t=10 (incl) to t=12 (excl)
+  // and from t=20 (incl) to t=22 (excl):
+  //         If `now` is 10 -> returns [10, 12]
+  //                     11 ->         [10, 12]
+  //                     12 ->         [20, 22]
+  //                     13 ->         [20, 22]
+  //         etc.
+  std::pair<base::TimeTicks, base::TimeTicks>
+  GetCurrentOrNextUnfreezePeriodStart(base::TimeTicks now) const {
+    const base::TimeTicks next_unfreeze_time = now.SnappedToNextTick(
+        *periodic_unfreeze_phase,
+        features::kInfiniteTabsFreezing_UnfreezeInterval.Get());
+    const base::TimeTicks previous_unfreeze_time =
+        next_unfreeze_time -
+        features::kInfiniteTabsFreezing_UnfreezeInterval.Get();
+    CHECK_LT(previous_unfreeze_time, now);
+
+    if ((previous_unfreeze_time +
+         features::kInfiniteTabsFreezing_UnfreezeDuration.Get()) > now) {
+      return {previous_unfreeze_time,
+              previous_unfreeze_time +
+                  features::kInfiniteTabsFreezing_UnfreezeDuration.Get()};
+    }
+
+    return {next_unfreeze_time,
+            next_unfreeze_time +
+                features::kInfiniteTabsFreezing_UnfreezeDuration.Get()};
+  }
+
+  // Returns true if `now` is within a periodic unfreeze period for this page.
+  bool IsInUnfreezePeriod(base::TimeTicks now) const {
+    return GetCurrentOrNextUnfreezePeriodStart(now).first <= now;
+  }
+
+  // Returns the delay until the next start or end of a periodic unfreeze period
+  // for this page. The return value is guaranteed to be greater than zero (if
+  // there is a state change at time `now`, this returns the time of the next
+  // state change).
+  base::TimeDelta GetDelayUntilNextUnfreezeStateChange(
+      base::TimeTicks now) const {
+    auto [start_incl, end_excl] = GetCurrentOrNextUnfreezePeriodStart(now);
+    if (start_incl > now) {
+      return start_incl - now;
+    }
+    CHECK_GT(end_excl, now);
+    return end_excl - now;
+  }
+
   // Whether this page is frozen.
   bool frozen = false;
 
   // Number of votes to freeze the page.
   int num_freeze_votes = 0;
 
+  // Phase for periodic unfreezing. Use a random value so that different tabs
+  // are unfrozen at different tabs as much as possible, but also cannot learn
+  // anything about other unrelated tabs.
+  std::optional<base::TimeTicks> periodic_unfreeze_phase;
+
   // Reasons not to freeze the page.
   CannotFreezeReasonSet cannot_freeze_reasons;
 
@@ -130,6 +182,9 @@
 
   // Timer to remove `CannotFreezeReason::kRecentlyAudible`.
   base::OneShotTimer recently_audible_timer;
+
+  // Timer for periodic unfreezing.
+  base::OneShotTimer periodic_unfreeze_timer;
 };
 
 class FreezingPolicy::CanFreezePerTypeTracker {
@@ -137,6 +192,9 @@
   CanFreezePerTypeTracker() = default;
   ~CanFreezePerTypeTracker() = default;
 
+  friend bool operator==(const CanFreezePerTypeTracker&,
+                         const CanFreezePerTypeTracker&) = default;
+
   void PopulateWithPageFreezingState(const PageFreezingState& state) {
     for (auto freezing_type : FreezingTypeSet::All()) {
       if (HasCannotFreezeReasonForType(state.cannot_freeze_reasons,
@@ -164,6 +222,8 @@
   }
 
  private:
+  // Contains the `FreezingType`s for which no `CannotFreezeReason` prevents
+  // freezing.
   FreezingTypeSet can_freeze_ = FreezingTypeSet::All();
 };
 
@@ -201,6 +261,7 @@
 FreezingPolicy::~FreezingPolicy() = default;
 
 void FreezingPolicy::ToggleFreezingOnBatterySaverMode(bool is_enabled) {
+  const base::TimeTicks now = base::TimeTicks::Now();
   is_battery_saver_active_ = is_enabled;
 
   // Update frozen state for all connected sets of pages (toggling the state of
@@ -208,7 +269,7 @@
   base::flat_set<raw_ptr<const PageNode>> visited_pages;
   for (auto& [id, state] : browsing_instance_states_) {
     if (!base::Contains(visited_pages, *state.pages.begin())) {
-      UpdateFrozenState(*state.pages.begin(), &visited_pages);
+      UpdateFrozenState(*state.pages.begin(), now, &visited_pages);
     }
   }
 }
@@ -255,6 +316,22 @@
   return details;
 }
 
+// static
+CannotFreezeReasonSet FreezingPolicy::CannotFreezeReasonsForType(
+    FreezingPolicy::FreezingType type) {
+  auto reasons = CannotFreezeReasonSet::All();
+  if (type == FreezingPolicy::FreezingType::kInfiniteTabs) {
+    // "Infinite tabs freezing" aims to have at most
+    // `kInfiniteTabsFreezing_NumProtectedTabs` unfrozen. To keep that promise
+    // even after the tab strip is traversed (e.g. by a user looking for a tab),
+    // ignore `CannotFreezeReason::kRecentlyVisible`.
+    reasons.Remove(CannotFreezeReason::kRecentlyVisible);
+  } else {
+    reasons.Remove(CannotFreezeReason::kMostRecentlyUsed);
+  }
+  return reasons;
+}
+
 FreezingPolicy::BrowsingInstanceState::BrowsingInstanceState() = default;
 FreezingPolicy::BrowsingInstanceState::~BrowsingInstanceState() = default;
 
@@ -314,11 +391,17 @@
 
 FreezingPolicy::PageFreezingState& FreezingPolicy::GetFreezingState(
     const PageNode* page_node) const {
-  return *PageFreezingState::GetOrCreate(PageNodeImpl::FromNode(page_node));
+  auto& state =
+      *PageFreezingState::GetOrCreate(PageNodeImpl::FromNode(page_node));
+  if (!state.periodic_unfreeze_phase) {
+    state.periodic_unfreeze_phase = GenerateRandomPeriodicUnfreezePhase();
+  }
+  return state;
 }
 
 void FreezingPolicy::UpdateFrozenState(
     const PageNode* page,
+    base::TimeTicks now,
     base::flat_set<raw_ptr<const PageNode>>* connected_pages_out) {
   const base::flat_set<raw_ptr<const PageNode>> connected_pages =
       GetConnectedPages(page);
@@ -328,9 +411,11 @@
   // - Any browsing instance hosting a frame from a connected page was CPU
   //   intensive in the background and Battery Saver is active and the
   //   `kFreezingOnBatterySaver` feature is enabled.
+  // - Any connected page is in a periodic unfreeze period.
   // - All connected page have a freeze vote.
   CanFreezePerTypeTracker can_freeze_per_type_tracker;
   bool eligible_for_freezing_on_battery_saver = false;
+  bool is_in_periodic_unfreeze = false;
   bool all_pages_have_freeze_vote = true;
 
   const double high_cpu_proportion = features::kFreezingHighCPUProportion.Get();
@@ -341,7 +426,13 @@
     can_freeze_per_type_tracker.PopulateWithPageFreezingState(
         page_freezing_state);
 
-    all_pages_have_freeze_vote &= (page_freezing_state.num_freeze_votes > 0);
+    if (page_freezing_state.num_freeze_votes == 0) {
+      all_pages_have_freeze_vote = false;
+    }
+
+    if (page_freezing_state.IsInUnfreezePeriod(now)) {
+      is_in_periodic_unfreeze = true;
+    }
 
     for (auto browsing_instance_id : GetBrowsingInstances(visited_page)) {
       auto it = browsing_instance_states_.find(browsing_instance_id);
@@ -374,6 +465,11 @@
              can_freeze_per_type_tracker.CanFreeze(
                  FreezingType::kBatterySaver)) {
     should_be_frozen = true;
+  } else if (can_freeze_per_type_tracker.CanFreeze(
+                 FreezingType::kInfiniteTabs) &&
+             !is_in_periodic_unfreeze &&
+             base::FeatureList::IsEnabled(features::kInfiniteTabsFreezing)) {
+    should_be_frozen = true;
   }
 
   // Freeze/unfreeze connected pages as needed.
@@ -398,30 +494,45 @@
 void FreezingPolicy::OnCannotFreezeReasonChange(const PageNode* page_node,
                                                 bool add,
                                                 CannotFreezeReason reason) {
-  auto& page_freezing_state = GetFreezingState(page_node);
-  if (add) {
-    DCHECK(!page_freezing_state.cannot_freeze_reasons.Has(reason));
-    const bool was_empty = page_freezing_state.cannot_freeze_reasons.empty();
-    page_freezing_state.cannot_freeze_reasons.Put(reason);
-    if (was_empty) {
-      // Track that the browsing instance had a `CannotFreezeReason`, so that
-      // the next CPU measurement for it can be ignored (this bit is sticky and
-      // won't be reset if the `CannotFreezeReason` is removed before the next
-      // measurement).
-      for (auto browsing_instance_id : GetBrowsingInstances(page_node)) {
-        auto it = browsing_instance_states_.find(browsing_instance_id);
-        CHECK(it != browsing_instance_states_.end());
-        it->second.cannot_freeze_reasons_since_last_cpu_measurement.Put(reason);
-      }
+  auto& state = GetFreezingState(page_node);
+  CanFreezePerTypeTracker before_tracker;
+  before_tracker.PopulateWithPageFreezingState(state);
 
-      UpdateFrozenState(page_node);
+  if (add) {
+    DCHECK(!state.cannot_freeze_reasons.Has(reason));
+    state.cannot_freeze_reasons.Put(reason);
+
+    // Track that the browsing instance had a `CannotFreezeReason`, so that the
+    // next CPU measurement for it can be ignored (this bit is sticky and won't
+    // be reset if the `CannotFreezeReason` is removed before the next
+    // measurement).
+    for (auto browsing_instance_id : GetBrowsingInstances(page_node)) {
+      auto it = browsing_instance_states_.find(browsing_instance_id);
+      CHECK(it != browsing_instance_states_.end());
+      it->second.cannot_freeze_reasons_since_last_cpu_measurement.Put(reason);
     }
   } else {
-    DCHECK(page_freezing_state.cannot_freeze_reasons.Has(reason));
-    page_freezing_state.cannot_freeze_reasons.Remove(reason);
-    if (page_freezing_state.cannot_freeze_reasons.empty()) {
-      UpdateFrozenState(page_node);
-    }
+    DCHECK(state.cannot_freeze_reasons.Has(reason));
+    state.cannot_freeze_reasons.Remove(reason);
+  }
+
+  CanFreezePerTypeTracker after_tracker;
+  after_tracker.PopulateWithPageFreezingState(state);
+
+  const base::TimeTicks now = base::TimeTicks::Now();
+
+  if (!after_tracker.CanFreeze(FreezingType::kInfiniteTabs)) {
+    // No need to run the periodic unfreeze timer when the tab isn't eligible
+    // for infinite tabs freezing.
+    state.periodic_unfreeze_timer.Stop();
+  } else if (!state.periodic_unfreeze_timer.IsRunning()) {
+    // Start a timer which fires when entering or exiting a periodic unfreeze
+    // period.
+    StartPeriodicUnfreezeTimer(page_node, now);
+  }
+
+  if (before_tracker != after_tracker) {
+    UpdateFrozenState(page_node);
   }
 }
 
@@ -495,12 +606,46 @@
 }
 
 void FreezingPolicy::OnBeforePageNodeRemoved(const PageNode* page_node) {
+  if (page_node->GetType() == PageType::kTab) {
+    if (page_node->IsVisible()) {
+      CHECK_GT(num_visible_tabs_, 0, base::NotFatalUntil::M140);
+      --num_visible_tabs_;
+    } else {
+      std::erase(most_recently_used_, page_node);
+    }
+  }
+
   CHECK(page_node->GetMainFrameNodes().empty());
+  CHECK(!base::Contains(most_recently_used_, page_node),
+        base::NotFatalUntil::M140);
+  CheckMostRecentlyUsedListSize();
+}
+
+void FreezingPolicy::OnTypeChanged(const PageNode* page_node,
+                                   PageType previous_type) {
+  CHECK_EQ(previous_type, PageType::kUnknown, base::NotFatalUntil::M140);
+  if (page_node->GetType() != PageType::kTab) {
+    return;
+  }
+
+  if (page_node->IsVisible()) {
+    ++num_visible_tabs_;
+  } else {
+    // When a tab is created in the background (e.g. Open Link in New Tab), we
+    // assume that the user cares about it more than tabs visited a while ago,
+    // so we add it to the front of the most recently used list. UXR could
+    // motivate a different approach.
+    most_recently_used_.push_front(page_node);
+    OnCannotFreezeReasonChange(page_node, /*add=*/true,
+                               CannotFreezeReason::kMostRecentlyUsed);
+  }
+  MaybePopFromMostRecentlyUsedList();
 }
 
 void FreezingPolicy::OnIsVisibleChanged(const PageNode* page_node) {
   auto& page_freezing_state = GetFreezingState(page_node);
   if (page_node->IsVisible()) {
+    // Page becomes visible.
     OnCannotFreezeReasonChange(page_node, /*add=*/true,
                                CannotFreezeReason::kVisible);
     if (page_freezing_state.recently_visible_timer.IsRunning()) {
@@ -508,7 +653,30 @@
       OnCannotFreezeReasonChange(page_node, /*add=*/false,
                                  CannotFreezeReason::kRecentlyVisible);
     }
+
+    if (page_node->GetType() == PageType::kTab) {
+      ++num_visible_tabs_;
+      size_t num_erased = std::erase(most_recently_used_, page_node);
+      if (num_erased == 0) {
+        MaybePopFromMostRecentlyUsedList();
+      } else {
+        OnCannotFreezeReasonChange(page_node, /*add=*/false,
+                                   CannotFreezeReason::kMostRecentlyUsed);
+      }
+    }
   } else {
+    // Page becomes hidden.
+    if (page_node->GetType() == PageType::kTab) {
+      CHECK(!base::Contains(most_recently_used_, page_node),
+            base::NotFatalUntil::M140);
+      CHECK_GT(num_visible_tabs_, 0, base::NotFatalUntil::M140);
+      --num_visible_tabs_;
+      most_recently_used_.push_front(page_node);
+      OnCannotFreezeReasonChange(page_node, /*add=*/true,
+                                 CannotFreezeReason::kMostRecentlyUsed);
+      MaybePopFromMostRecentlyUsedList();
+    }
+
     OnCannotFreezeReasonChange(page_node, /*add=*/true,
                                CannotFreezeReason::kRecentlyVisible);
     OnCannotFreezeReasonChange(page_node, /*add=*/false,
@@ -523,6 +691,7 @@
             base::Unretained(page_node),
             /* add=*/false, CannotFreezeReason::kRecentlyVisible));
   }
+  CheckMostRecentlyUsedListSize();
 }
 
 void FreezingPolicy::OnIsAudibleChanged(const PageNode* page_node) {
@@ -1025,6 +1194,59 @@
   }
 }
 
+void FreezingPolicy::MaybePopFromMostRecentlyUsedList() {
+  const int num_protected_tabs =
+      num_visible_tabs_ + base::checked_cast<int>(most_recently_used_.size());
+
+  if (num_protected_tabs <=
+          features::kInfiniteTabsFreezing_NumProtectedTabs.Get() ||
+      most_recently_used_.empty()) {
+    return;
+  }
+
+  const PageNode* back = most_recently_used_.back();
+  most_recently_used_.pop_back();
+  OnCannotFreezeReasonChange(back, /*add=*/false,
+                             CannotFreezeReason::kMostRecentlyUsed);
+
+  // Check that removing one tab from `most_recently_used_` was sufficient to
+  // respect the limit. This should be the case if this method is called
+  // whenever `num_visible_tabs_` is incremented or an element is added to
+  // `most_recently_used_`.
+  CheckMostRecentlyUsedListSize();
+}
+
+void FreezingPolicy::CheckMostRecentlyUsedListSize() {
+  // If the most recently used list is empty, `num_visible_tabs_` may exceed the
+  // limit (there is no cap on the number of visible tabs).
+  if (most_recently_used_.empty()) {
+    return;
+  }
+
+  // Otherwise, the sum of the most recently used list size and the number of
+  // visible tabs must be below or at the limit.
+  CHECK_LE(
+      num_visible_tabs_ + base::checked_cast<int>(most_recently_used_.size()),
+      features::kInfiniteTabsFreezing_NumProtectedTabs.Get(),
+      base::NotFatalUntil::M140);
+}
+
+void FreezingPolicy::StartPeriodicUnfreezeTimer(const PageNode* page_node,
+                                                base::TimeTicks now) {
+  auto& state = GetFreezingState(page_node);
+  CHECK(!state.periodic_unfreeze_timer.IsRunning(), base::NotFatalUntil::M141);
+  state.periodic_unfreeze_timer.Start(
+      FROM_HERE, state.GetDelayUntilNextUnfreezeStateChange(now),
+      base::BindOnce(&FreezingPolicy::OnPeriodicUnfreezeTimer,
+                     base::Unretained(this), base::Unretained(page_node)));
+}
+
+void FreezingPolicy::OnPeriodicUnfreezeTimer(const PageNode* page_node) {
+  const base::TimeTicks now = base::TimeTicks::Now();
+  UpdateFrozenState(page_node, now);
+  StartPeriodicUnfreezeTimer(page_node, now);
+}
+
 void FreezingPolicy::RecordFreezingEligibilityUKM() {
   if (!base::FeatureList::IsEnabled(features::kRecordFreezingEligibilityUKM)) {
     return;
@@ -1170,4 +1392,11 @@
   ukm.Record(ukm::UkmRecorder::Get());
 }
 
+base::TimeTicks FreezingPolicy::GenerateRandomPeriodicUnfreezePhase() const {
+  return base::TimeTicks() +
+         base::Milliseconds(base::RandInt(
+             0, features::kInfiniteTabsFreezing_UnfreezeInterval.Get()
+                    .InMilliseconds()));
+}
+
 }  // namespace performance_manager
diff --git a/components/performance_manager/freezing/freezing_policy.h b/components/performance_manager/freezing/freezing_policy.h
index 0bd15e0..349f4959 100644
--- a/components/performance_manager/freezing/freezing_policy.h
+++ b/components/performance_manager/freezing/freezing_policy.h
@@ -5,6 +5,7 @@
 #ifndef COMPONENTS_PERFORMANCE_MANAGER_FREEZING_FREEZING_POLICY_H_
 #define COMPONENTS_PERFORMANCE_MANAGER_FREEZING_FREEZING_POLICY_H_
 
+#include <deque>
 #include <map>
 #include <memory>
 #include <optional>
@@ -63,9 +64,12 @@
     kVoting,
     // Freezing of CPU-intensive background tabs when Battery Saver is active.
     kBatterySaver,
+    // Freezing of tabs which aren't in the set of most recently used, to
+    // scale to infinite tabs.
+    kInfiniteTabs,
   };
   using FreezingTypeSet = base::
-      EnumSet<FreezingType, FreezingType::kVoting, FreezingType::kBatterySaver>;
+      EnumSet<FreezingType, FreezingType::kVoting, FreezingType::kInfiniteTabs>;
 
   explicit FreezingPolicy(
       std::unique_ptr<freezing::Discarder> discarder,
@@ -91,6 +95,10 @@
   // Returns details about whether a page can be frozen.
   freezing::CanFreezeDetails GetCanFreezeDetails(const PageNode* page_node);
 
+  // Returns a set of `CannotFreezeReason`s applicable to `freezing_type`.
+  static freezing::CannotFreezeReasonSet CannotFreezeReasonsForType(
+      FreezingPolicy::FreezingType type);
+
  private:
   FRIEND_TEST_ALL_PREFIXES(FreezingPolicyBatterySaverTest,
                            RecordFreezingEligibilityUKMForPageStatic);
@@ -147,6 +155,7 @@
   // (including `page_node`) are added to `connected_pages_out` if not nullptr.
   void UpdateFrozenState(
       const PageNode* page_node,
+      base::TimeTicks now = base::TimeTicks::Now(),
       base::flat_set<raw_ptr<const PageNode>>* connected_pages_out = nullptr);
 
   // Helper to add or remove a `CannotFreezeReason` for `page_node`.
@@ -166,6 +175,8 @@
   // PageNodeObserver implementation:
   void OnPageNodeAdded(const PageNode* page_node) override;
   void OnBeforePageNodeRemoved(const PageNode* page_node) override;
+  void OnTypeChanged(const PageNode* page_node,
+                     PageType previous_type) override;
   void OnIsVisibleChanged(const PageNode* page_node) override;
   void OnIsAudibleChanged(const PageNode* page_node) override;
   void OnPageLifecycleStateChanged(const PageNode* page_node) override;
@@ -225,6 +236,24 @@
   // `browser_context_id` changes.
   void OnOptOutPolicyChanged(std::string_view browser_context_id);
 
+  // Removes the last page from the most recently used list if needed, to keep
+  // its size below the limit.
+  void MaybePopFromMostRecentlyUsedList();
+
+  // Checks that the size of the most recently used list respects the limit.
+  void CheckMostRecentlyUsedListSize();
+
+  // Starts a timer to manage periodic unfreezing of a tab frozen for
+  // `FreezingContext::kInfiniteTabs`. The timer is scheduled to invoke
+  // OnPeriodicUnfreezeTimer() at the next time when the tab must be unfrozen or
+  // re-frozen.
+  void StartPeriodicUnfreezeTimer(const PageNode* page_node,
+                                  base::TimeTicks now);
+
+  // Method invoked when when it's time to unfreeze or re-freeze a tab frozen
+  // for `FreezingContext::kInfiniteTabs`.
+  void OnPeriodicUnfreezeTimer(const PageNode* page);
+
   // Records freezing eligibility UKM for all pages.
   void RecordFreezingEligibilityUKM();
 
@@ -247,6 +276,10 @@
       double highest_cpu_without_battery_saver_cannot_freeze,
       freezing::CannotFreezeReasonSet battery_saver_cannot_freeze_reasons);
 
+  // Returns a random periodic unfreeze phase. Can be overridden in test to
+  // eliminate randomness.
+  virtual base::TimeTicks GenerateRandomPeriodicUnfreezePhase() const;
+
   // Used to freeze pages.
   std::unique_ptr<Freezer> freezer_;
 
@@ -281,6 +314,14 @@
   // Used to subsample the emission of UKM events.
   base::MetricsSubSampler metrics_subsampler_;
 
+  // List of most recently used hidden tabs. A tab becomes the most recently
+  // used when it transitions from visible to hidden, or when it's created in a
+  // hidden state.
+  std::deque<raw_ptr<const PageNode>> most_recently_used_;
+
+  // Number of visible tabs.
+  int num_visible_tabs_ = 0;
+
   base::WeakPtrFactory<FreezingPolicy> weak_factory_{this};
 };
 
diff --git a/components/performance_manager/freezing/freezing_policy_unittest.cc b/components/performance_manager/freezing/freezing_policy_unittest.cc
index 0f47862..36221cc 100644
--- a/components/performance_manager/freezing/freezing_policy_unittest.cc
+++ b/components/performance_manager/freezing/freezing_policy_unittest.cc
@@ -44,6 +44,7 @@
 
 namespace {
 
+using FreezingType = FreezingPolicy::FreezingType;
 using freezing::CannotFreezeReason;
 using freezing::CannotFreezeReasonSet;
 using ::testing::ElementsAre;
@@ -58,6 +59,11 @@
       : FreezingPolicy(std::move(discarder), std::move(opt_out_checker)) {}
   ~MockFreezingPolicy() override = default;
 
+  base::TimeTicks GenerateRandomPeriodicUnfreezePhase() const override {
+    //  Make the periodic unfreeze phase non-random for tests.
+    return base::TimeTicks();
+  }
+
   MOCK_METHOD(void,
               RecordFreezingEligibilityUKMForPage,
               (ukm::SourceId source_id,
@@ -99,13 +105,15 @@
 
 }  // namespace
 
-class FreezingPolicyTest : public GraphTestHarness {
+class FreezingPolicyTest_BaseWithNoPage : public GraphTestHarness {
  public:
-  FreezingPolicyTest()
+  FreezingPolicyTest_BaseWithNoPage()
       : GraphTestHarness(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
-  ~FreezingPolicyTest() override = default;
-  FreezingPolicyTest(const FreezingPolicyTest& other) = delete;
-  FreezingPolicyTest& operator=(const FreezingPolicyTest&) = delete;
+  ~FreezingPolicyTest_BaseWithNoPage() override = default;
+  FreezingPolicyTest_BaseWithNoPage(
+      const FreezingPolicyTest_BaseWithNoPage& other) = delete;
+  FreezingPolicyTest_BaseWithNoPage& operator=(
+      const FreezingPolicyTest_BaseWithNoPage&) = delete;
 
   void OnGraphCreated(GraphImpl* graph) override {
     // The freezing logic relies on the existence of the page live state data.
@@ -120,10 +128,7 @@
     freezer_ = freezer.get();
     policy_->SetFreezerForTesting(std::move(freezer));
     graph->PassToGraph(std::move(policy));
-
     process_node_ = CreateNode<ProcessNodeImpl>();
-    std::tie(page_node_, frame_node_) =
-        CreatePageAndFrameWithBrowsingInstanceId(kBrowsingInstanceA);
   }
 
   // Override to create an OptOutChecker for the test.
@@ -148,12 +153,51 @@
     observer->OnResourceUsageUpdated(std::move(memory_result_map));
   }
 
+  // Adds CPU usage for `context` to `cpu_result_map`, with "now" as the
+  // measurement time. `cumulative_background_cpu` is used as cumulative
+  // background CPU and `cumulative_cpu` is used as cumulative CPU
+  // (`cumulative_background_cpu` is used as cumulative CPU if `cumulative_cpu`
+  // is nullopt).
+  void AddCPUMeasurement(
+      resource_attribution::QueryResultMap& cpu_result_map,
+      resource_attribution::ResourceContext context,
+      base::TimeDelta cumulative_background_cpu,
+      std::optional<base::TimeDelta> cumulative_cpu = std::nullopt) {
+    cpu_result_map[context] = resource_attribution::QueryResults{
+        .cpu_time_result = resource_attribution::CPUTimeResult{
+            .metadata = resource_attribution::ResultMetadata(
+                /* measurement_time=*/base::TimeTicks::Now(),
+                resource_attribution::MeasurementAlgorithm::kSum),
+            .start_time = base::TimeTicks(),
+            .cumulative_cpu = cumulative_cpu.has_value()
+                                  ? cumulative_cpu.value()
+                                  : cumulative_background_cpu,
+            .cumulative_background_cpu = cumulative_background_cpu}};
+  }
+
+  // Reports CPU usage for `context` to the the freezing policy, with "now" as
+  // the measurement time. `cumulative_background_cpu` is used as cumulative
+  // background CPU and `cumulative_cpu` is used as cumulative CPU
+  // (`cumulative_background_cpu` is used as cumulative CPU if `cumulative_cpu`
+  // is nullopt).
+  void ReportCumulativeCPUUsage(
+      resource_attribution::ResourceContext context,
+      base::TimeDelta cumulative_background_cpu,
+      std::optional<base::TimeDelta> cumulative_cpu = std::nullopt) {
+    resource_attribution::QueryResultMap cpu_result_map;
+    AddCPUMeasurement(cpu_result_map, context, cumulative_background_cpu,
+                      cumulative_cpu);
+    resource_attribution::QueryResultObserver* observer = policy();
+    observer->OnResourceUsageUpdated(std::move(cpu_result_map));
+  }
+
   std::pair<TestNodeWrapper<PageNodeImpl>, TestNodeWrapper<FrameNodeImpl>>
   CreatePageAndFrameWithBrowsingInstanceId(
       content::BrowsingInstanceId browsing_instance_id,
       const std::string& browsing_context_id = "") {
     auto page =
         CreateNode<PageNodeImpl>(/*web_contents=*/nullptr, browsing_context_id);
+    page->SetType(PageType::kTab);
     auto frame = CreateFrameNodeAutoId(process_node(), page.get(),
                                        /* parent_frame_node=*/nullptr,
                                        browsing_instance_id);
@@ -168,29 +212,39 @@
     Mock::VerifyAndClearExpectations(discarder());
   }
 
+  // Expect that the `CannotFreezeReason`s applicable to `freezing_type` for
+  // `page_node` match `matcher`.
   template <typename MatcherType>
   void ExpectCannotFreezeReasons(
       const PageNode* page_node,
+      FreezingType freezing_type,
       MatcherType matcher,
       const base::Location& location = base::Location::Current()) {
-    EXPECT_THAT(policy()->GetCanFreezeDetails(page_node).cannot_freeze_reasons,
-                matcher)
+    EXPECT_THAT(
+        base::Intersection(
+            policy()->GetCanFreezeDetails(page_node).cannot_freeze_reasons,
+            FreezingPolicy::CannotFreezeReasonsForType(freezing_type)),
+        matcher)
         << location.ToString();
   }
 
+  // Expect that the `CannotFreezeReason`s applicable to `freezing_type` for
+  // pages connected to `page_node` match `matcher`.
   template <typename MatcherType>
   void ExpectConnectedCannotFreezeReasons(
       const PageNode* page_node,
+      FreezingType freezing_type,
       MatcherType matcher,
       const base::Location& location = base::Location::Current()) {
-    EXPECT_THAT(policy()
-                    ->GetCanFreezeDetails(page_node)
-                    .cannot_freeze_reasons_connected_pages,
+    EXPECT_THAT(base::Intersection(
+                    policy()
+                        ->GetCanFreezeDetails(page_node)
+                        .cannot_freeze_reasons_connected_pages,
+                    FreezingPolicy::CannotFreezeReasonsForType(freezing_type)),
                 matcher)
         << location.ToString();
   }
 
-  PageNodeImpl* page_node() { return page_node_.get(); }
   ProcessNodeImpl* process_node() { return process_node_.get(); }
   MockFreezingPolicy* policy() { return policy_; }
   MockFreezer* freezer() { return freezer_; }
@@ -207,20 +261,37 @@
 
  private:
   TestNodeWrapper<ProcessNodeImpl> process_node_;
-  TestNodeWrapper<PageNodeImpl> page_node_;
-  TestNodeWrapper<FrameNodeImpl> frame_node_;
   raw_ptr<MockFreezer> freezer_;
   raw_ptr<MockDiscarder> discarder_;
   raw_ptr<MockFreezingPolicy> policy_;
 };
 
+class FreezingPolicyTest : public FreezingPolicyTest_BaseWithNoPage {
+ public:
+  FreezingPolicyTest() = default;
+  ~FreezingPolicyTest() override = default;
+
+  void OnGraphCreated(GraphImpl* graph) override {
+    FreezingPolicyTest_BaseWithNoPage::OnGraphCreated(graph);
+
+    std::tie(page_node_, frame_node_) =
+        CreatePageAndFrameWithBrowsingInstanceId(kBrowsingInstanceA);
+  }
+
+  PageNodeImpl* page_node() { return page_node_.get(); }
+
+ private:
+  TestNodeWrapper<PageNodeImpl> page_node_;
+  TestNodeWrapper<FrameNodeImpl> frame_node_;
+};
+
 // A page with no `CannotFreezeReason` that is alone in its browsing instance is
 // frozen when it has a freezing vote.
 TEST_F(FreezingPolicyTest, Basic) {
   EXPECT_CALL(*freezer(), MaybeFreezePageNode(page_node()));
   policy()->AddFreezeVote(page_node());
   VerifyFreezerExpectations();
-  ExpectCannotFreezeReasons(page_node(), IsEmpty());
+  ExpectCannotFreezeReasons(page_node(), FreezingType::kVoting, IsEmpty());
 }
 
 // Multiple connected pages in the same browsing instance with no
@@ -328,21 +399,25 @@
   EXPECT_CALL(*freezer(), MaybeFreezePageNode(page2.get()));
   policy()->AddFreezeVote(page2.get());
   VerifyFreezerExpectations();
-  ExpectCannotFreezeReasons(page_node(), IsEmpty());
-  ExpectCannotFreezeReasons(page2.get(), IsEmpty());
-  ExpectConnectedCannotFreezeReasons(page_node(), IsEmpty());
-  ExpectConnectedCannotFreezeReasons(page2.get(), IsEmpty());
+  ExpectCannotFreezeReasons(page_node(), FreezingType::kVoting, IsEmpty());
+  ExpectCannotFreezeReasons(page2.get(), FreezingType::kVoting, IsEmpty());
+  ExpectConnectedCannotFreezeReasons(page_node(), FreezingType::kVoting,
+                                     IsEmpty());
+  ExpectConnectedCannotFreezeReasons(page2.get(), FreezingType::kVoting,
+                                     IsEmpty());
 
   EXPECT_CALL(*freezer(), UnfreezePageNode(page_node()));
   EXPECT_CALL(*freezer(), UnfreezePageNode(page2.get()));
   page_node()->SetIsHoldingWebLockForTesting(true);
   VerifyFreezerExpectations();
-  ExpectCannotFreezeReasons(page_node(),
+  ExpectCannotFreezeReasons(page_node(), FreezingType::kVoting,
                             ElementsAre(CannotFreezeReason::kHoldingWebLock));
-  ExpectCannotFreezeReasons(page2.get(), IsEmpty());
-  ExpectConnectedCannotFreezeReasons(page_node(), IsEmpty());
+  ExpectCannotFreezeReasons(page2.get(), FreezingType::kVoting, IsEmpty());
+  ExpectConnectedCannotFreezeReasons(page_node(), FreezingType::kVoting,
+                                     IsEmpty());
   ExpectConnectedCannotFreezeReasons(
-      page2.get(), ElementsAre(CannotFreezeReason::kHoldingWebLock));
+      page2.get(), FreezingType::kVoting,
+      ElementsAre(CannotFreezeReason::kHoldingWebLock));
 }
 
 // Similar to AddCannotFreezeReasonToBrowsingInstanceWithManyPages, except that
@@ -364,27 +439,33 @@
   EXPECT_CALL(*freezer(), MaybeFreezePageNode(page3.get()));
   policy()->AddFreezeVote(page3.get());
   VerifyFreezerExpectations();
-  ExpectCannotFreezeReasons(page_node(), IsEmpty());
-  ExpectCannotFreezeReasons(page2.get(), IsEmpty());
-  ExpectCannotFreezeReasons(page3.get(), IsEmpty());
-  ExpectConnectedCannotFreezeReasons(page_node(), IsEmpty());
-  ExpectConnectedCannotFreezeReasons(page2.get(), IsEmpty());
-  ExpectConnectedCannotFreezeReasons(page3.get(), IsEmpty());
+  ExpectCannotFreezeReasons(page_node(), FreezingType::kVoting, IsEmpty());
+  ExpectCannotFreezeReasons(page2.get(), FreezingType::kVoting, IsEmpty());
+  ExpectCannotFreezeReasons(page3.get(), FreezingType::kVoting, IsEmpty());
+  ExpectConnectedCannotFreezeReasons(page_node(), FreezingType::kVoting,
+                                     IsEmpty());
+  ExpectConnectedCannotFreezeReasons(page2.get(), FreezingType::kVoting,
+                                     IsEmpty());
+  ExpectConnectedCannotFreezeReasons(page3.get(), FreezingType::kVoting,
+                                     IsEmpty());
 
   EXPECT_CALL(*freezer(), UnfreezePageNode(page_node()));
   EXPECT_CALL(*freezer(), UnfreezePageNode(page2.get()));
   EXPECT_CALL(*freezer(), UnfreezePageNode(page3.get()));
   page_node()->SetIsHoldingWebLockForTesting(true);
   VerifyFreezerExpectations();
-  ExpectCannotFreezeReasons(page_node(),
+  ExpectCannotFreezeReasons(page_node(), FreezingType::kVoting,
                             ElementsAre(CannotFreezeReason::kHoldingWebLock));
-  ExpectCannotFreezeReasons(page2.get(), IsEmpty());
-  ExpectCannotFreezeReasons(page3.get(), IsEmpty());
-  ExpectConnectedCannotFreezeReasons(page_node(), IsEmpty());
+  ExpectCannotFreezeReasons(page2.get(), FreezingType::kVoting, IsEmpty());
+  ExpectCannotFreezeReasons(page3.get(), FreezingType::kVoting, IsEmpty());
+  ExpectConnectedCannotFreezeReasons(page_node(), FreezingType::kVoting,
+                                     IsEmpty());
   ExpectConnectedCannotFreezeReasons(
-      page2.get(), ElementsAre(CannotFreezeReason::kHoldingWebLock));
+      page2.get(), FreezingType::kVoting,
+      ElementsAre(CannotFreezeReason::kHoldingWebLock));
   ExpectConnectedCannotFreezeReasons(
-      page3.get(), ElementsAre(CannotFreezeReason::kHoldingWebLock));
+      page3.get(), FreezingType::kVoting,
+      ElementsAre(CannotFreezeReason::kHoldingWebLock));
 }
 
 // A browsing instance with one page that has a `CannotFreezeReason` is not
@@ -394,12 +475,14 @@
   auto [page2, frame2] =
       CreatePageAndFrameWithBrowsingInstanceId(kBrowsingInstanceA);
   page_node()->SetIsHoldingWebLockForTesting(true);
-  ExpectCannotFreezeReasons(page_node(),
+  ExpectCannotFreezeReasons(page_node(), FreezingType::kVoting,
                             ElementsAre(CannotFreezeReason::kHoldingWebLock));
-  ExpectCannotFreezeReasons(page2.get(), IsEmpty());
-  ExpectConnectedCannotFreezeReasons(page_node(), IsEmpty());
+  ExpectCannotFreezeReasons(page2.get(), FreezingType::kVoting, IsEmpty());
+  ExpectConnectedCannotFreezeReasons(page_node(), FreezingType::kVoting,
+                                     IsEmpty());
   ExpectConnectedCannotFreezeReasons(
-      page2.get(), ElementsAre(CannotFreezeReason::kHoldingWebLock));
+      page2.get(), FreezingType::kVoting,
+      ElementsAre(CannotFreezeReason::kHoldingWebLock));
 
   // Don't expect freezing.
   policy()->AddFreezeVote(page_node());
@@ -421,15 +504,18 @@
   auto [page3, frame3] =
       CreatePageAndFrameWithBrowsingInstanceId(kBrowsingInstanceB);
   page_node()->SetIsHoldingWebLockForTesting(true);
-  ExpectCannotFreezeReasons(page_node(),
+  ExpectCannotFreezeReasons(page_node(), FreezingType::kVoting,
                             ElementsAre(CannotFreezeReason::kHoldingWebLock));
-  ExpectCannotFreezeReasons(page2.get(), IsEmpty());
-  ExpectCannotFreezeReasons(page3.get(), IsEmpty());
-  ExpectConnectedCannotFreezeReasons(page_node(), IsEmpty());
+  ExpectCannotFreezeReasons(page2.get(), FreezingType::kVoting, IsEmpty());
+  ExpectCannotFreezeReasons(page3.get(), FreezingType::kVoting, IsEmpty());
+  ExpectConnectedCannotFreezeReasons(page_node(), FreezingType::kVoting,
+                                     IsEmpty());
   ExpectConnectedCannotFreezeReasons(
-      page2.get(), ElementsAre(CannotFreezeReason::kHoldingWebLock));
+      page2.get(), FreezingType::kVoting,
+      ElementsAre(CannotFreezeReason::kHoldingWebLock));
   ExpectConnectedCannotFreezeReasons(
-      page3.get(), ElementsAre(CannotFreezeReason::kHoldingWebLock));
+      page3.get(), FreezingType::kVoting,
+      ElementsAre(CannotFreezeReason::kHoldingWebLock));
 
   // Don't expect freezing.
   policy()->AddFreezeVote(page_node());
@@ -453,15 +539,18 @@
   policy()->AddFreezeVote(page_node());
   policy()->AddFreezeVote(page2.get());
   policy()->AddFreezeVote(page3.get());
-  ExpectCannotFreezeReasons(page_node(),
+  ExpectCannotFreezeReasons(page_node(), FreezingType::kVoting,
                             ElementsAre(CannotFreezeReason::kHoldingWebLock));
-  ExpectCannotFreezeReasons(page2.get(), IsEmpty());
-  ExpectCannotFreezeReasons(page3.get(), IsEmpty());
-  ExpectConnectedCannotFreezeReasons(page_node(), IsEmpty());
+  ExpectCannotFreezeReasons(page2.get(), FreezingType::kVoting, IsEmpty());
+  ExpectCannotFreezeReasons(page3.get(), FreezingType::kVoting, IsEmpty());
+  ExpectConnectedCannotFreezeReasons(page_node(), FreezingType::kVoting,
+                                     IsEmpty());
   ExpectConnectedCannotFreezeReasons(
-      page2.get(), ElementsAre(CannotFreezeReason::kHoldingWebLock));
+      page2.get(), FreezingType::kVoting,
+      ElementsAre(CannotFreezeReason::kHoldingWebLock));
   ExpectConnectedCannotFreezeReasons(
-      page3.get(), ElementsAre(CannotFreezeReason::kHoldingWebLock));
+      page3.get(), FreezingType::kVoting,
+      ElementsAre(CannotFreezeReason::kHoldingWebLock));
 
   // Deleting `frame2` puts `page_node()` in a different connected set than
   // `page2` and `page3`. `page_node()` cannot be frozen because it has a
@@ -471,13 +560,16 @@
   EXPECT_CALL(*freezer(), MaybeFreezePageNode(page3.get()));
   frame2.reset();
   VerifyFreezerExpectations();
-  ExpectCannotFreezeReasons(page_node(),
+  ExpectCannotFreezeReasons(page_node(), FreezingType::kVoting,
                             ElementsAre(CannotFreezeReason::kHoldingWebLock));
-  ExpectCannotFreezeReasons(page2.get(), IsEmpty());
-  ExpectCannotFreezeReasons(page3.get(), IsEmpty());
-  ExpectConnectedCannotFreezeReasons(page_node(), IsEmpty());
-  ExpectConnectedCannotFreezeReasons(page2.get(), IsEmpty());
-  ExpectConnectedCannotFreezeReasons(page3.get(), IsEmpty());
+  ExpectCannotFreezeReasons(page2.get(), FreezingType::kVoting, IsEmpty());
+  ExpectCannotFreezeReasons(page3.get(), FreezingType::kVoting, IsEmpty());
+  ExpectConnectedCannotFreezeReasons(page_node(), FreezingType::kVoting,
+                                     IsEmpty());
+  ExpectConnectedCannotFreezeReasons(page2.get(), FreezingType::kVoting,
+                                     IsEmpty());
+  ExpectConnectedCannotFreezeReasons(page3.get(), FreezingType::kVoting,
+                                     IsEmpty());
 }
 
 // Similar to BreakConnectedSet, but the connected set left by the page from
@@ -495,15 +587,18 @@
   policy()->AddFreezeVote(page_node());
   policy()->AddFreezeVote(page2.get());
   policy()->AddFreezeVote(page3.get());
-  ExpectCannotFreezeReasons(page_node(), IsEmpty());
-  ExpectCannotFreezeReasons(page2.get(),
+  ExpectCannotFreezeReasons(page_node(), FreezingType::kVoting, IsEmpty());
+  ExpectCannotFreezeReasons(page2.get(), FreezingType::kVoting,
                             ElementsAre(CannotFreezeReason::kHoldingWebLock));
-  ExpectCannotFreezeReasons(page3.get(), IsEmpty());
+  ExpectCannotFreezeReasons(page3.get(), FreezingType::kVoting, IsEmpty());
   ExpectConnectedCannotFreezeReasons(
-      page_node(), ElementsAre(CannotFreezeReason::kHoldingWebLock));
-  ExpectConnectedCannotFreezeReasons(page2.get(), IsEmpty());
+      page_node(), FreezingType::kVoting,
+      ElementsAre(CannotFreezeReason::kHoldingWebLock));
+  ExpectConnectedCannotFreezeReasons(page2.get(), FreezingType::kVoting,
+                                     IsEmpty());
   ExpectConnectedCannotFreezeReasons(
-      page3.get(), ElementsAre(CannotFreezeReason::kHoldingWebLock));
+      page3.get(), FreezingType::kVoting,
+      ElementsAre(CannotFreezeReason::kHoldingWebLock));
 
   // Deleting `frame2` puts `page_node()` in a different connected set than
   // `page2` and `page3`. `page_node()` cannot be frozen because it has a
@@ -512,14 +607,17 @@
   EXPECT_CALL(*freezer(), MaybeFreezePageNode(page_node()));
   frame2.reset();
   VerifyFreezerExpectations();
-  ExpectCannotFreezeReasons(page_node(), IsEmpty());
-  ExpectCannotFreezeReasons(page2.get(),
+  ExpectCannotFreezeReasons(page_node(), FreezingType::kVoting, IsEmpty());
+  ExpectCannotFreezeReasons(page2.get(), FreezingType::kVoting,
                             ElementsAre(CannotFreezeReason::kHoldingWebLock));
-  ExpectCannotFreezeReasons(page3.get(), IsEmpty());
-  ExpectConnectedCannotFreezeReasons(page_node(), IsEmpty());
-  ExpectConnectedCannotFreezeReasons(page2.get(), IsEmpty());
+  ExpectCannotFreezeReasons(page3.get(), FreezingType::kVoting, IsEmpty());
+  ExpectConnectedCannotFreezeReasons(page_node(), FreezingType::kVoting,
+                                     IsEmpty());
+  ExpectConnectedCannotFreezeReasons(page2.get(), FreezingType::kVoting,
+                                     IsEmpty());
   ExpectConnectedCannotFreezeReasons(
-      page3.get(), ElementsAre(CannotFreezeReason::kHoldingWebLock));
+      page3.get(), FreezingType::kVoting,
+      ElementsAre(CannotFreezeReason::kHoldingWebLock));
 }
 
 TEST_F(FreezingPolicyTest, FreezeVoteWhenVisible) {
@@ -1253,44 +1351,6 @@
  public:
   FreezingPolicyBatterySaverTest() = default;
 
-  // Adds CPU usage for `context` to `cpu_result_map`, with "now" as the
-  // measurement time. `cumulative_background_cpu` is used as cumulative
-  // background CPU and `cumulative_cpu` is used as cumulative CPU
-  // (`cumulative_background_cpu` is used as cumulative CPU if `cumulative_cpu`
-  // is nullopt).
-  void AddCPUMeasurement(
-      resource_attribution::QueryResultMap& cpu_result_map,
-      resource_attribution::ResourceContext context,
-      base::TimeDelta cumulative_background_cpu,
-      std::optional<base::TimeDelta> cumulative_cpu = std::nullopt) {
-    cpu_result_map[context] = resource_attribution::QueryResults{
-        .cpu_time_result = resource_attribution::CPUTimeResult{
-            .metadata = resource_attribution::ResultMetadata(
-                /* measurement_time=*/base::TimeTicks::Now(),
-                resource_attribution::MeasurementAlgorithm::kSum),
-            .start_time = base::TimeTicks(),
-            .cumulative_cpu = cumulative_cpu.has_value()
-                                  ? cumulative_cpu.value()
-                                  : cumulative_background_cpu,
-            .cumulative_background_cpu = cumulative_background_cpu}};
-  }
-
-  // Reports CPU usage for `context` to the the freezing policy, with "now" as
-  // the measurement time. `cumulative_background_cpu` is used as cumulative
-  // background CPU and `cumulative_cpu` is used as cumulative CPU
-  // (`cumulative_background_cpu` is used as cumulative CPU if `cumulative_cpu`
-  // is nullopt).
-  void ReportCumulativeCPUUsage(
-      resource_attribution::ResourceContext context,
-      base::TimeDelta cumulative_background_cpu,
-      std::optional<base::TimeDelta> cumulative_cpu = std::nullopt) {
-    resource_attribution::QueryResultMap cpu_result_map;
-    AddCPUMeasurement(cpu_result_map, context, cumulative_background_cpu,
-                      cumulative_cpu);
-    resource_attribution::QueryResultObserver* observer = policy();
-    observer->OnResourceUsageUpdated(std::move(cpu_result_map));
-  }
-
  private:
   base::test::ScopedFeatureList scoped_feature_list_{
       features::kFreezingOnBatterySaver};
@@ -1796,6 +1856,8 @@
   int next_navigation_id_ = 2;
 };
 
+}  // namespace
+
 TEST_F(FreezingPolicyOptOutTest, MainFrameUrlChanges) {
   ASSERT_TRUE(opt_out_checker_.get());
   opt_out_checker_->SetOptedOutUrl(kOptOutUrl1);
@@ -1804,31 +1866,31 @@
   EXPECT_CALL(*freezer(), MaybeFreezePageNode(page_node()));
   policy()->AddFreezeVote(page_node());
   VerifyFreezerExpectations();
-  ExpectCannotFreezeReasons(page_node(), IsEmpty());
+  ExpectCannotFreezeReasons(page_node(), FreezingType::kVoting, IsEmpty());
 
   // Stays frozen when navigating to an URL that's not opted out.
   NavigateToUrl(page_node(), kOptOutUrl2);
   VerifyFreezerExpectations();
-  ExpectCannotFreezeReasons(page_node(), IsEmpty());
+  ExpectCannotFreezeReasons(page_node(), FreezingType::kVoting, IsEmpty());
 
   // Unfreezes when navigating to an URL that's opted out.
   EXPECT_CALL(*freezer(), UnfreezePageNode(page_node()));
   NavigateToUrl(page_node(), kOptOutUrl1);
   VerifyFreezerExpectations();
-  ExpectCannotFreezeReasons(page_node(),
+  ExpectCannotFreezeReasons(page_node(), FreezingType::kVoting,
                             ElementsAre(CannotFreezeReason::kOptedOut));
 
   // Navigating to the same URL does nothing.
   NavigateToUrl(page_node(), kOptOutUrl1);
   VerifyFreezerExpectations();
-  ExpectCannotFreezeReasons(page_node(),
+  ExpectCannotFreezeReasons(page_node(), FreezingType::kVoting,
                             ElementsAre(CannotFreezeReason::kOptedOut));
 
   // Freezes when navigating away from the opted out URL.
   EXPECT_CALL(*freezer(), MaybeFreezePageNode(page_node()));
   NavigateToUrl(page_node(), kOptOutUrl2);
   VerifyFreezerExpectations();
-  ExpectCannotFreezeReasons(page_node(), IsEmpty());
+  ExpectCannotFreezeReasons(page_node(), FreezingType::kVoting, IsEmpty());
 }
 
 TEST_F(FreezingPolicyOptOutTest, OptOutPolicyChanges) {
@@ -1866,10 +1928,10 @@
     policy()->AddFreezeVote(page2.get());
     policy()->AddFreezeVote(page3.get());
     VerifyFreezerExpectations();
-    ExpectCannotFreezeReasons(page1.get(),
+    ExpectCannotFreezeReasons(page1.get(), FreezingType::kVoting,
                               ElementsAre(CannotFreezeReason::kOptedOut));
-    ExpectCannotFreezeReasons(page2.get(), IsEmpty());
-    ExpectCannotFreezeReasons(page3.get(), IsEmpty());
+    ExpectCannotFreezeReasons(page2.get(), FreezingType::kVoting, IsEmpty());
+    ExpectCannotFreezeReasons(page3.get(), FreezingType::kVoting, IsEmpty());
   }
 
   // Change which URL is opted out. Notify kBrowsingContext1 of the change.
@@ -1879,10 +1941,10 @@
     EXPECT_CALL(*freezer(), MaybeFreezePageNode(page1.get()));
     opt_out_checker_->SetOptedOutUrl(kOptOutUrl2, {kBrowsingContext1});
     VerifyFreezerExpectations();
-    ExpectCannotFreezeReasons(page1.get(), IsEmpty());
-    ExpectCannotFreezeReasons(page2.get(),
+    ExpectCannotFreezeReasons(page1.get(), FreezingType::kVoting, IsEmpty());
+    ExpectCannotFreezeReasons(page2.get(), FreezingType::kVoting,
                               ElementsAre(CannotFreezeReason::kOptedOut));
-    ExpectCannotFreezeReasons(page3.get(), IsEmpty());
+    ExpectCannotFreezeReasons(page3.get(), FreezingType::kVoting, IsEmpty());
   }
 
   // Now notify kBrowsingContext2.
@@ -1891,10 +1953,10 @@
     EXPECT_CALL(*freezer(), UnfreezePageNode(page3.get()));
     opt_out_checker_->SetOptedOutUrl(kOptOutUrl2, {kBrowsingContext2});
     VerifyFreezerExpectations();
-    ExpectCannotFreezeReasons(page1.get(), IsEmpty());
-    ExpectCannotFreezeReasons(page2.get(),
+    ExpectCannotFreezeReasons(page1.get(), FreezingType::kVoting, IsEmpty());
+    ExpectCannotFreezeReasons(page2.get(), FreezingType::kVoting,
                               ElementsAre(CannotFreezeReason::kOptedOut));
-    ExpectCannotFreezeReasons(page3.get(),
+    ExpectCannotFreezeReasons(page3.get(), FreezingType::kVoting,
                               ElementsAre(CannotFreezeReason::kOptedOut));
   }
 
@@ -1906,12 +1968,332 @@
     opt_out_checker_->SetOptedOutUrl("",
                                      {kBrowsingContext1, kBrowsingContext2});
     VerifyFreezerExpectations();
-    ExpectCannotFreezeReasons(page1.get(), IsEmpty());
-    ExpectCannotFreezeReasons(page2.get(), IsEmpty());
-    ExpectCannotFreezeReasons(page3.get(), IsEmpty());
+    ExpectCannotFreezeReasons(page1.get(), FreezingType::kVoting, IsEmpty());
+    ExpectCannotFreezeReasons(page2.get(), FreezingType::kVoting, IsEmpty());
+    ExpectCannotFreezeReasons(page3.get(), FreezingType::kVoting, IsEmpty());
   }
 }
 
+namespace {
+
+class FreezingPolicyInfiniteTabsTest
+    : public FreezingPolicyTest_BaseWithNoPage {
+ protected:
+  FreezingPolicyInfiniteTabsTest() = default;
+
+  void OnGraphCreated(GraphImpl* graph) override {
+    FreezingPolicyTest_BaseWithNoPage::OnGraphCreated(graph);
+
+    // Start the test outside of the periodic unfreeze period.
+    AdvanceToAlignedTime(base::Minutes(1));
+    AdvanceClock(features::kInfiniteTabsFreezing_UnfreezeDuration.Get());
+
+    // Create "num protected tabs" hidden pages. All pages have their own
+    // browsing instance, which is not equal to `kBrowsingInstance(A|B|C)`.
+    for (int i = 0; i < features::kInfiniteTabsFreezing_NumProtectedTabs.Get();
+         ++i) {
+      auto [page, frame] =
+          CreatePageAndFrameWithBrowsingInstanceId(content::BrowsingInstanceId(
+              kBrowsingInstanceC.GetUnsafeValue() + i + 1));
+      ASSERT_FALSE(page->IsVisible());
+      pages_.push_back(std::move(page));
+      frames_.push_back(std::move(frame));
+      AdvanceClock(base::Milliseconds(1));
+    }
+  }
+
+  // Advances the clock to a time aligned on `interval`.
+  void AdvanceToAlignedTime(base::TimeDelta interval) {
+    const base::TimeTicks now = base::TimeTicks::Now();
+    const base::TimeTicks next_aligned_time =
+        now.SnappedToNextTick(base::TimeTicks(), interval);
+    AdvanceClock(next_aligned_time - now);
+  }
+
+  std::vector<TestNodeWrapper<PageNodeImpl>> pages_;
+  std::vector<TestNodeWrapper<FrameNodeImpl>> frames_;
+
+ private:
+  base::test::ScopedFeatureList feature_list_{features::kInfiniteTabsFreezing};
+};
+
 }  // namespace
 
+// Verify that under "Infinite Tabs Freezing", tabs are frozen if not in the
+// list of most recently used.
+TEST_F(FreezingPolicyInfiniteTabsTest, MostRecentlyUsed) {
+  // Create a new page, which takes the spot of the `pages_[0]` in the list
+  // of most recently used pages. `pages_[0]` should be frozen.
+  EXPECT_CALL(*freezer(), MaybeFreezePageNode(pages_[0].get()));
+  auto [page5, frame5] =
+      CreatePageAndFrameWithBrowsingInstanceId(kBrowsingInstanceA);
+  AdvanceClock(base::Milliseconds(1));
+  VerifyFreezerExpectations();
+
+  // Create a new page, which takes the spot of `pages_[1]` in the list
+  // of most recently used pages. `pages_[1]` should be frozen.
+  EXPECT_CALL(*freezer(), MaybeFreezePageNode(pages_[1].get()));
+  auto [page6, frame6] =
+      CreatePageAndFrameWithBrowsingInstanceId(kBrowsingInstanceB);
+  AdvanceClock(base::Milliseconds(1));
+  VerifyFreezerExpectations();
+
+  // Make `pages_[1]` visible. It should take the spot of `pages_[2]` in the
+  // list of most recently used pages. `pages_[2]` should be frozen.
+  EXPECT_CALL(*freezer(), UnfreezePageNode(pages_[1].get()));
+  EXPECT_CALL(*freezer(), MaybeFreezePageNode(pages_[2].get()));
+  pages_[1]->SetIsVisible(true);
+  VerifyFreezerExpectations();
+
+  // Make `pages_[2]` visible. It should take the spot of `pages_[3]]` in the
+  // list of most recently used pages. `pages_[4]` should be frozen.
+  EXPECT_CALL(*freezer(), UnfreezePageNode(pages_[2].get()));
+  EXPECT_CALL(*freezer(), MaybeFreezePageNode(pages_[3].get()));
+  pages_[2]->SetIsVisible(true);
+  VerifyFreezerExpectations();
+
+  // Hide `pages_[3]` and `pages_[1]`. This should have no effect on freezing.
+  pages_[2]->SetIsVisible(false);
+  AdvanceClock(base::Milliseconds(1));
+  pages_[1]->SetIsVisible(false);
+  AdvanceClock(base::Milliseconds(1));
+
+  // Create new pages. At each page creation, the least recently used protected
+  // page should loose its protection and be frozen.
+  EXPECT_CALL(*freezer(), MaybeFreezePageNode(pages_[4].get()));
+  auto [page7, frame7] = CreatePageAndFrameWithBrowsingInstanceId(
+      content::BrowsingInstanceId(100));
+  VerifyFreezerExpectations();
+  AdvanceClock(base::Milliseconds(1));
+
+  EXPECT_CALL(*freezer(), MaybeFreezePageNode(page5.get()));
+  auto [page8, frame8] = CreatePageAndFrameWithBrowsingInstanceId(
+      content::BrowsingInstanceId(101));
+  VerifyFreezerExpectations();
+  AdvanceClock(base::Milliseconds(1));
+
+  EXPECT_CALL(*freezer(), MaybeFreezePageNode(page6.get()));
+  auto [page9, frame9] = CreatePageAndFrameWithBrowsingInstanceId(
+      content::BrowsingInstanceId(102));
+  VerifyFreezerExpectations();
+  AdvanceClock(base::Milliseconds(1));
+
+  EXPECT_CALL(*freezer(), MaybeFreezePageNode(pages_[2].get()));
+  auto [page10, frame10] = CreatePageAndFrameWithBrowsingInstanceId(
+      content::BrowsingInstanceId(103));
+  VerifyFreezerExpectations();
+  AdvanceClock(base::Milliseconds(1));
+
+  EXPECT_CALL(*freezer(), MaybeFreezePageNode(pages_[1].get()));
+  auto [page11, frame11] = CreatePageAndFrameWithBrowsingInstanceId(
+      content::BrowsingInstanceId(104));
+  VerifyFreezerExpectations();
+  AdvanceClock(base::Milliseconds(1));
+}
+
+// Verify that under "Infinite Tabs Freezing", a tab created visible (as opposed
+// to hidden, as in other tests) doesn't have
+// `CannotFreezeReason::kMostRecentlyUsed`, but still counts towards the limit
+// of most recently used tabs protected against "Infinite Tabs Freezing".
+TEST_F(FreezingPolicyInfiniteTabsTest, InitiallyVisible) {
+  // Create a new page, which takes the spot of the `pages_[0]` in the list
+  // of most recently used pages. `pages_[0]` should be frozen.
+  EXPECT_CALL(*freezer(), MaybeFreezePageNode(pages_[0].get()));
+  auto page5 = CreateNode<PageNodeImpl>(
+      /*web_contents=*/nullptr, /*browsing_context_id=*/std::string(), GURL(),
+      PagePropertyFlags{PagePropertyFlag::kIsVisible});
+  EXPECT_TRUE(page5->IsVisible());
+  page5->SetType(PageType::kTab);
+  auto frame5 =
+      CreateFrameNodeAutoId(process_node(), page5.get(),
+                            /* parent_frame_node=*/nullptr, kBrowsingInstanceA);
+  VerifyFreezerExpectations();
+}
+
+// Verify that under "Infinite Tabs Freezing", when there are more visible tabs
+// than the desired number of protected tabs and they subsequently become
+// hidden, freezing happens (as opposed to all tabs being transferred to the
+// most recently used list, exceeding the limit).
+TEST_F(FreezingPolicyInfiniteTabsTest, ManyVisibleTabs) {
+  // Create more visible tabs than the limit of protected tabs. All existing
+  // tabs are frozen.
+  EXPECT_CALL(*freezer(), MaybeFreezePageNode(pages_[0].get()));
+  EXPECT_CALL(*freezer(), MaybeFreezePageNode(pages_[1].get()));
+  EXPECT_CALL(*freezer(), MaybeFreezePageNode(pages_[2].get()));
+  EXPECT_CALL(*freezer(), MaybeFreezePageNode(pages_[3].get()));
+  EXPECT_CALL(*freezer(), MaybeFreezePageNode(pages_[4].get()));
+
+  std::vector<TestNodeWrapper<PageNodeImpl>> more_pages;
+  std::vector<TestNodeWrapper<FrameNodeImpl>> more_frames;
+  for (int i = 0;
+       i < features::kInfiniteTabsFreezing_NumProtectedTabs.Get() * 2; ++i) {
+    more_pages.push_back(CreateNode<PageNodeImpl>(
+        /*web_contents=*/nullptr, /*browsing_context_id=*/std::string(), GURL(),
+        PagePropertyFlags{PagePropertyFlag::kIsVisible}));
+    more_pages.back()->SetType(PageType::kTab);
+    more_frames.push_back(CreateFrameNodeAutoId(
+        process_node(), more_pages.back().get(),
+        /* parent_frame_node=*/nullptr, content::BrowsingInstanceId(100 + i)));
+  }
+
+  VerifyFreezerExpectations();
+
+  // Hide half of tabs. They should be frozen immediately, not put in the list
+  // of most recently used, as there are already more visible tabs that the
+  // limit of protected tabs.
+  for (int i = 0; i < features::kInfiniteTabsFreezing_NumProtectedTabs.Get();
+       ++i) {
+    EXPECT_CALL(*freezer(), MaybeFreezePageNode(more_pages[i].get()));
+    more_pages[i]->SetIsVisible(false);
+    VerifyFreezerExpectations();
+  }
+}
+
+// Verify that under "Infinite Tabs Freezing", a `CannotFreezeReason` that
+// applies to all types of freezing is honored.
+TEST_F(FreezingPolicyInfiniteTabsTest, UniversalCannotFreezeReason) {
+  // Create a new page, which takes the spot of the `pages_[0]` in the list
+  // of most recently used pages. `pages_[0]` should be frozen.
+  EXPECT_CALL(*freezer(), MaybeFreezePageNode(pages_[0].get()));
+  auto [page5, frame5] =
+      CreatePageAndFrameWithBrowsingInstanceId(kBrowsingInstanceA);
+  AdvanceClock(base::Milliseconds(1));
+  VerifyFreezerExpectations();
+
+  // Add a `CannotFreezeReason` to `pages_[0]`. It should be unfrozen.
+  EXPECT_CALL(*freezer(), UnfreezePageNode(pages_[0].get()));
+  pages_[0]->SetIsHoldingWebLockForTesting(true);
+  VerifyFreezerExpectations();
+
+  // Add a `CannotFreezeReason` to `pages_[1]`. This has no effect since it's
+  // not frozen.
+  pages_[1]->SetIsHoldingWebLockForTesting(true);
+
+  // Create a new page, which takes the spot of `pages_[1]` in the list of most
+  // recently used pages. `pages_[1]` should not be frozen since it holds a Web
+  // Lock.
+  auto [page6, frame6] =
+      CreatePageAndFrameWithBrowsingInstanceId(kBrowsingInstanceB);
+  AdvanceClock(base::Milliseconds(1));
+
+  // When `pages_[0]` and `pages_[1]` release their lock, they're both frozen.
+  EXPECT_CALL(*freezer(), MaybeFreezePageNode(pages_[0].get()));
+  pages_[0]->SetIsHoldingWebLockForTesting(false);
+  VerifyFreezerExpectations();
+  EXPECT_CALL(*freezer(), MaybeFreezePageNode(pages_[1].get()));
+  pages_[1]->SetIsHoldingWebLockForTesting(false);
+}
+
+// Verify that under "Infinite Tabs Freezing", frozen tabs are periodically
+// unfrozen.
+TEST_F(FreezingPolicyInfiniteTabsTest, PeriodicUnfreeze) {
+  // Advance to the beginning of the next periodic unfreeze period.
+  AdvanceToAlignedTime(base::Minutes(1));
+
+  // Create a new page. This should remove
+  // `CannotFreezeReason::kMostRecentlyUsed` from `pages_[0]`. However, it's not
+  // frozen yet since it's still in its periodic unfreeze period.
+  auto [page, frame] =
+      CreatePageAndFrameWithBrowsingInstanceId(kBrowsingInstanceA);
+  ASSERT_FALSE(page->IsVisible());
+
+  // Advance to the end of the periodic unfreeze period. `pages_[0]` should be
+  // frozen.
+  EXPECT_CALL(*freezer(), MaybeFreezePageNode(pages_[0].get()));
+  AdvanceClock(features::kInfiniteTabsFreezing_UnfreezeDuration.Get());
+  VerifyFreezerExpectations();
+
+  // Advance to the beginning of the next periodic unfreeze period. `pages_[0]`
+  // should be unfrozen.
+  EXPECT_CALL(*freezer(), UnfreezePageNode(pages_[0].get()));
+  AdvanceClock(features::kInfiniteTabsFreezing_UnfreezeInterval.Get() -
+               features::kInfiniteTabsFreezing_UnfreezeDuration.Get());
+  VerifyFreezerExpectations();
+
+  // Advance to the end of the periodic unfreeze period. `pages_[0]` should be
+  // frozen again.
+  EXPECT_CALL(*freezer(), MaybeFreezePageNode(pages_[0].get()));
+  AdvanceClock(features::kInfiniteTabsFreezing_UnfreezeDuration.Get());
+  VerifyFreezerExpectations();
+
+  // Add a `CannotFreezeReason`. `pages_[0]` is unfrozen.
+  EXPECT_CALL(*freezer(), UnfreezePageNode(pages_[0].get()));
+  pages_[0]->SetUsesWebRTCForTesting(true);
+  VerifyFreezerExpectations();
+
+  // At the next periodic unfreeze period, `pages_[0]` remains unfrozen.
+  AdvanceClock(features::kInfiniteTabsFreezing_UnfreezeInterval.Get() -
+               features::kInfiniteTabsFreezing_UnfreezeDuration.Get());
+
+  // When the periodic unfreeze period ends, `pages_[0]` is not re-frozen.
+  AdvanceClock(features::kInfiniteTabsFreezing_UnfreezeDuration.Get());
+
+  // When the `CannotFreezeReason` is removed, `pages_[0]` is frozen.
+  EXPECT_CALL(*freezer(), MaybeFreezePageNode(pages_[0].get()));
+  pages_[0]->SetUsesWebRTCForTesting(false);
+  VerifyFreezerExpectations();
+}
+
+// Verify that a page with a freeze vote can be frozen even if it's in the list
+// of most recently used tabs (this list only affects Infinite Tabs Freezing).
+TEST_F(FreezingPolicyInfiniteTabsTest, InteractionWithVoting) {
+  EXPECT_EQ(policy()->GetCanFreezeDetails(pages_[0].get()).can_freeze,
+            freezing::CanFreeze::kVaries);
+  ExpectCannotFreezeReasons(pages_[0].get(), FreezingType::kVoting, IsEmpty());
+  ExpectCannotFreezeReasons(pages_[0].get(), FreezingType::kInfiniteTabs,
+                            ElementsAre(CannotFreezeReason::kMostRecentlyUsed));
+
+  EXPECT_CALL(*freezer(), MaybeFreezePageNode(pages_[0].get()));
+  policy()->AddFreezeVote(pages_[0].get());
+  VerifyFreezerExpectations();
+}
+
+// Verify that a page which is CPU-intensive in the background while Battery
+// Saver is active can be frozen even if it's in the list of most recently
+// used tabs (this list only affects Infinite Tabs Freezing).
+TEST_F(FreezingPolicyInfiniteTabsTest, InteractionWithBatterySaver) {
+  base::test::ScopedFeatureList feature_list{features::kFreezingOnBatterySaver};
+  policy()->ToggleFreezingOnBatterySaverMode(true);
+
+  EXPECT_EQ(policy()->GetCanFreezeDetails(pages_[0].get()).can_freeze,
+            freezing::CanFreeze::kVaries);
+  ExpectCannotFreezeReasons(pages_[0].get(), FreezingType::kBatterySaver,
+                            IsEmpty());
+  ExpectCannotFreezeReasons(pages_[0].get(), FreezingType::kInfiniteTabs,
+                            ElementsAre(CannotFreezeReason::kMostRecentlyUsed));
+
+  const resource_attribution::OriginInBrowsingInstanceContext kPage0Context{
+      url::Origin(), frames_[0]->GetBrowsingInstanceId()};
+
+  ReportCumulativeCPUUsage(kPage0Context, base::Seconds(60));
+  AdvanceClock(base::Seconds(60));
+  EXPECT_CALL(*freezer(), MaybeFreezePageNode(pages_[0].get()));
+  ReportCumulativeCPUUsage(kPage0Context, base::Seconds(75));
+
+  VerifyFreezerExpectations();
+}
+
+// Verify that visible pages which are not tabs don't affect Infinite Tabs
+// Freezing.
+TEST_F(FreezingPolicyInfiniteTabsTest, NonTab) {
+  // Create a new page of type `kTab` which takes the spot of the `pages_[0]` in
+  // the list of most recently used pages. `pages_[0]` should be frozen.
+  EXPECT_CALL(*freezer(), MaybeFreezePageNode(pages_[0].get()));
+  auto [page5, frame5] =
+      CreatePageAndFrameWithBrowsingInstanceId(kBrowsingInstanceA);
+  EXPECT_EQ(page5->GetType(), PageType::kTab);
+  AdvanceClock(base::Milliseconds(1));
+  VerifyFreezerExpectations();
+
+  // Create a new page of type `kExtension`. Unlike the previous case, this
+  // should have no effect on freezing.
+  auto non_tab_page = CreateNode<PageNodeImpl>(
+      /*web_contents=*/nullptr, /* browsing_context_id=*/std::string(), GURL(),
+      PagePropertyFlags{PagePropertyFlag::kIsVisible});
+  non_tab_page->SetType(PageType::kExtension);
+  auto non_tab_frame =
+      CreateFrameNodeAutoId(process_node(), non_tab_page.get(),
+                            /* parent_frame_node=*/nullptr, kBrowsingInstanceB);
+}
+
 }  // namespace performance_manager
diff --git a/components/performance_manager/public/features.h b/components/performance_manager/public/features.h
index 25e63b1..0466415 100644
--- a/components/performance_manager/public/features.h
+++ b/components/performance_manager/public/features.h
@@ -193,6 +193,30 @@
 // When enabled, the freezing eligibility UKM event may be recorded.
 BASE_DECLARE_FEATURE(kRecordFreezingEligibilityUKM);
 
+// When enabled, eligible tabs which are not in the N most recently used are
+// frozen. This prevents CPU usage from growing proportionally with the number
+// of tabs, and aims to make the browser support "infinite tabs" with good
+// performance. A tab is eligible if it doesn't have a `CannotFreezeReason`
+// other than `CannotFreezeReason::kRecentlyVisible`. N is configurable with
+// `kInfiniteTabsFreezing_NumProtectedTabs`. Tabs frozen by this feature are
+// periodically unfrozen, to allow showing notifications, refreshing content,
+// maintaining connections... (see `kInfiniteTabsFreezing_UnfreezeInterval` and
+// `kInfiniteTabsFreezing_UnfreezeDuration`).
+BASE_DECLARE_FEATURE(kInfiniteTabsFreezing);
+
+// Number of most recently visible tabs protected from "infinite tabs" freezing.
+BASE_DECLARE_FEATURE_PARAM(int, kInfiniteTabsFreezing_NumProtectedTabs);
+
+// Interval at which tabs frozen to support "infinite tabs" are temporarily
+// unfrozen.
+BASE_DECLARE_FEATURE_PARAM(base::TimeDelta,
+                           kInfiniteTabsFreezing_UnfreezeInterval);
+
+// Duration for which tabs frozen to support "infinite tabs" are temporarily
+// unfrozen.
+BASE_DECLARE_FEATURE_PARAM(base::TimeDelta,
+                           kInfiniteTabsFreezing_UnfreezeDuration);
+
 // When enabled, Resource Attribution measurements will include contexts for
 // individual origins.
 BASE_DECLARE_FEATURE(kResourceAttributionIncludeOrigins);
diff --git a/components/performance_manager/public/freezing/cannot_freeze_reason.h b/components/performance_manager/public/freezing/cannot_freeze_reason.h
index 652769a..d98438b 100644
--- a/components/performance_manager/public/freezing/cannot_freeze_reason.h
+++ b/components/performance_manager/public/freezing/cannot_freeze_reason.h
@@ -37,7 +37,8 @@
   kLoading,
   kNotificationPermission,
   kOptedOut,
-  kMax = kOptedOut,  // Upper bound for EnumSet.
+  kMostRecentlyUsed,
+  kMax = kMostRecentlyUsed,  // Upper bound for EnumSet.
 };
 
 using CannotFreezeReasonSet = base::EnumSet<CannotFreezeReason,
diff --git a/components/permissions/android/java/src/org/chromium/components/permissions/PermissionDialogDelegate.java b/components/permissions/android/java/src/org/chromium/components/permissions/PermissionDialogDelegate.java
index d4ea803d..f5a9a82 100644
--- a/components/permissions/android/java/src/org/chromium/components/permissions/PermissionDialogDelegate.java
+++ b/components/permissions/android/java/src/org/chromium/components/permissions/PermissionDialogDelegate.java
@@ -18,14 +18,16 @@
 import org.chromium.ui.base.WindowAndroid;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
+import java.util.stream.Collectors;
 
 /**
  * Delegate class for modal permission dialogs. Contains all of the data displayed in a prompt,
  * including the button strings, message text and the icon.
  *
- * This class is also the interface to the native-side permissions code. When the user responds to
- * the permission dialog, the decision is conveyed across the JNI so that the native code can
+ * <p>This class is also the interface to the native-side permissions code. When the user responds
+ * to the permission dialog, the decision is conveyed across the JNI so that the native code can
  * respond appropriately.
  */
 @JNINamespace("permissions")
@@ -61,6 +63,9 @@
     /** Whether to show the persistent grant button first, followed by the ephemeral option. */
     private boolean mShowPositiveNonEphemeralAsFirstButton;
 
+    /** The text of radio buttons that can be shown above the permission buttons. */
+    private List<CharSequence> mRadioButtons;
+
     /** The {@link ContentSettingsType}s requested in this dialog. */
     private int[] mContentSettingsTypes;
 
@@ -118,15 +123,7 @@
     }
 
     public List<CharSequence> getRadioButtons() {
-        List<CharSequence> radioButtons = new ArrayList<>();
-        if (PermissionsAndroidFeatureMap.isEnabled(
-                        PermissionsAndroidFeatureList.APPROXIMATE_GEOLOCATION_PERMISSION)
-                && mContentSettingsTypes[0] == ContentSettingsType.GEOLOCATION) {
-            // TODO(crbug.com/417684493) Get buttons and selection from native delegate.
-            radioButtons.add("approximate");
-            radioButtons.add("precise");
-        }
-        return radioButtons;
+        return mRadioButtons;
     }
 
     public void setEmbeddedPromptVariant(@EmbeddedPromptVariant int variant) {
@@ -255,6 +252,7 @@
             String negativeButtonText,
             String positiveEphemeralButtonText,
             boolean showPositiveNonEphemeralAsFirstButton,
+            String[] radioButtons,
             @EmbeddedPromptVariant int variant) {
         assert (boldedRanges.length % 2 == 0); // Contains a list of offset and length values
 
@@ -269,9 +267,11 @@
                 negativeButtonText,
                 positiveEphemeralButtonText,
                 showPositiveNonEphemeralAsFirstButton,
+                radioButtons,
                 variant);
     }
 
+    @SuppressWarnings("NoStreams") // Not using lambdas.
     private PermissionDialogDelegate(
             long nativeDelegatePtr,
             WindowAndroid window,
@@ -283,6 +283,7 @@
             String negativeButtonText,
             String positiveEphemeralButtonText,
             boolean showPositiveNonEphemeralAsFirstButton,
+            String[] radioButtons,
             @EmbeddedPromptVariant int variant) {
         mNativeDelegatePtr = nativeDelegatePtr;
         mWindow = window;
@@ -296,11 +297,13 @@
         mNegativeButtonText = negativeButtonText;
         mPositiveEphemeralButtonText = positiveEphemeralButtonText;
         mShowPositiveNonEphemeralAsFirstButton = showPositiveNonEphemeralAsFirstButton;
+        mRadioButtons = Arrays.stream(radioButtons).collect(Collectors.toList());
         mEmbeddedPromptVariant = variant;
     }
 
     /** Called by native code to update the current permission dialog with new screen. */
     @CalledByNative
+    @SuppressWarnings("NoStreams") // Not using lambdas.
     void updateDialog(
             int[] contentSettingsTypes,
             int iconId,
@@ -310,6 +313,7 @@
             String negativeButtonText,
             String positiveEphemeralButtonText,
             boolean showPositiveNonEphemeralAsFirstButton,
+            String[] radioButtons,
             @EmbeddedPromptVariant int variant) {
         mContentSettingsTypes = contentSettingsTypes;
         mMessageText = message;
@@ -322,6 +326,7 @@
         mNegativeButtonText = negativeButtonText;
         mPositiveEphemeralButtonText = positiveEphemeralButtonText;
         mShowPositiveNonEphemeralAsFirstButton = showPositiveNonEphemeralAsFirstButton;
+        mRadioButtons = Arrays.stream(radioButtons).collect(Collectors.toList());
         mEmbeddedPromptVariant = variant;
 
         assert mDialogController != null;
diff --git a/components/permissions/android/permission_prompt/embedded_permission_prompt_android.cc b/components/permissions/android/permission_prompt/embedded_permission_prompt_android.cc
index 5d7d526d..defe138 100644
--- a/components/permissions/android/permission_prompt/embedded_permission_prompt_android.cc
+++ b/components/permissions/android/permission_prompt/embedded_permission_prompt_android.cc
@@ -4,6 +4,7 @@
 
 #include "components/permissions/android/permission_prompt/embedded_permission_prompt_android.h"
 
+#include "base/android/jni_array.h"
 #include "base/android/jni_string.h"
 #include "components/permissions/android/permission_prompt/permission_dialog_delegate.h"
 #include "components/permissions/features.h"
@@ -289,6 +290,12 @@
       env, l10n_util::GetStringUTF16(IDS_PERMISSION_ALLOW_THIS_TIME));
 }
 
+base::android::ScopedJavaLocalRef<jobjectArray>
+EmbeddedPermissionPromptAndroid::GetRadioButtonTexts(JNIEnv* env,
+                                                     bool is_one_time) const {
+  return base::android::ToJavaArrayOfStrings(env, base::span<std::string>());
+}
+
 bool EmbeddedPermissionPromptAndroid::ShouldUseRequestingOriginFavicon() const {
   return false;
 }
diff --git a/components/permissions/android/permission_prompt/embedded_permission_prompt_android.h b/components/permissions/android/permission_prompt/embedded_permission_prompt_android.h
index 56fd119..d75bc80 100644
--- a/components/permissions/android/permission_prompt/embedded_permission_prompt_android.h
+++ b/components/permissions/android/permission_prompt/embedded_permission_prompt_android.h
@@ -65,6 +65,10 @@
   base::android::ScopedJavaLocalRef<jstring> GetPositiveEphemeralButtonText(
       JNIEnv* env,
       bool is_one_time) const override;
+  base::android::ScopedJavaLocalRef<jobjectArray> GetRadioButtonTexts(
+      JNIEnv* env,
+      bool is_one_time) const override;
+
   bool ShouldUseRequestingOriginFavicon() const override;
   std::vector<permissions::ElementAnchoredBubbleVariant> GetPromptVariants()
       const override;
diff --git a/components/permissions/android/permission_prompt/permission_dialog_delegate.cc b/components/permissions/android/permission_prompt/permission_dialog_delegate.cc
index 3a76604..99ec73a 100644
--- a/components/permissions/android/permission_prompt/permission_dialog_delegate.cc
+++ b/components/permissions/android/permission_prompt/permission_dialog_delegate.cc
@@ -53,6 +53,7 @@
       permission_prompt_->GetNegativeButtonText(env, is_one_time),
       permission_prompt_->GetPositiveEphemeralButtonText(env, is_one_time),
       /*showPositiveNonEphemeralAsFirstButton=*/is_one_time,
+      permission_prompt_->GetRadioButtonTexts(env, is_one_time),
       static_cast<int>(permission_prompt_->GetEmbeddedPromptVariant())));
 }
 
@@ -134,6 +135,7 @@
       permission_prompt_->GetNegativeButtonText(env, is_one_time),
       permission_prompt_->GetPositiveEphemeralButtonText(env, is_one_time),
       /*showPositiveNonEphemeralAsFirstButton=*/is_one_time,
+      permission_prompt_->GetRadioButtonTexts(env, is_one_time),
       static_cast<int>(permission_prompt_->GetEmbeddedPromptVariant()));
 }
 
diff --git a/components/permissions/android/permission_prompt/permission_prompt_android.cc b/components/permissions/android/permission_prompt/permission_prompt_android.cc
index b901f9d..4f0a3af 100644
--- a/components/permissions/android/permission_prompt/permission_prompt_android.cc
+++ b/components/permissions/android/permission_prompt/permission_prompt_android.cc
@@ -117,6 +117,21 @@
     bool is_one_time) const {
   return ConvertUTF16ToJavaString(env, std::u16string_view());
 }
+base::android::ScopedJavaLocalRef<jobjectArray>
+PermissionPromptAndroid::GetRadioButtonTexts(JNIEnv* env,
+                                             bool is_one_time) const {
+  if (!is_one_time) {
+    return base::android::ToJavaArrayOfStrings(env, base::span<std::string>());
+  }
+  if (Requests()[0]->request_type() == RequestType::kGeolocation &&
+      base::FeatureList::IsEnabled(
+          permissions::features::kApproximateGeolocationPermission)) {
+    return base::android::ToJavaArrayOfStrings(
+        env, {l10n_util::GetStringUTF16(IDS_PERMISSION_ALLOW_APPROXIMATE_GEO),
+              l10n_util::GetStringUTF16(IDS_PERMISSION_ALLOW_PRECISE_GEO)});
+  }
+  return base::android::ToJavaArrayOfStrings(env, base::span<std::string>());
+}
 
 size_t PermissionPromptAndroid::PermissionCount() const {
   return Requests().size();
diff --git a/components/permissions/android/permission_prompt/permission_prompt_android.h b/components/permissions/android/permission_prompt/permission_prompt_android.h
index 96eb31c..50b5999 100644
--- a/components/permissions/android/permission_prompt/permission_prompt_android.h
+++ b/components/permissions/android/permission_prompt/permission_prompt_android.h
@@ -73,6 +73,9 @@
       bool is_one_time) const;
   virtual base::android::ScopedJavaLocalRef<jstring>
   GetPositiveEphemeralButtonText(JNIEnv* env, bool is_one_time) const;
+  virtual base::android::ScopedJavaLocalRef<jobjectArray> GetRadioButtonTexts(
+      JNIEnv* env,
+      bool is_one_time) const;
 
   // We show one permission at a time except for grouped mic+camera, for which
   // we still have a single icon and message text.
diff --git a/components/permissions/resolvers/permission_resolver.cc b/components/permissions/resolvers/permission_resolver.cc
index d4f9b75..c3db184 100644
--- a/components/permissions/resolvers/permission_resolver.cc
+++ b/components/permissions/resolvers/permission_resolver.cc
@@ -19,4 +19,7 @@
     : content_settings_type_(RequestTypeToContentSettingsType(request_type)),
       request_type_(request_type) {}
 
+PermissionResolver::PromptParameters::PromptParameters() = default;
+PermissionResolver::PromptParameters::~PromptParameters() = default;
+
 }  // namespace permissions
diff --git a/components/permissions/resolvers/permission_resolver.h b/components/permissions/resolvers/permission_resolver.h
index def975e..9a630db 100644
--- a/components/permissions/resolvers/permission_resolver.h
+++ b/components/permissions/resolvers/permission_resolver.h
@@ -25,8 +25,12 @@
   // `PromptParameters` are returned when the UI queries the resolver to
   // determine what to prompt the user for.
   struct PromptParameters {
+    PromptParameters();
+    ~PromptParameters();
     base::Value missing_options;
     std::u16string prompt_text;
+    std::vector<std::u16string> radio_button_labels;
+    int preselected_radio_button_index = -1;  // -1 represents no preselection
   };
 
   virtual ~PermissionResolver() = default;
diff --git a/components/permissions_strings.grdp b/components/permissions_strings.grdp
index a0f93c9..5214284 100644
--- a/components/permissions_strings.grdp
+++ b/components/permissions_strings.grdp
@@ -213,6 +213,12 @@
   <message name="IDS_PERMISSION_NEVER_ALLOW" desc="(Alternative label, to compare) label on button to permanent deny in a one-time granting use case.">
     Never allow
   </message>
+   <message name="IDS_PERMISSION_ALLOW_APPROXIMATE_GEO" desc="Label on a radio button to select approximate geolocation permission for a site." translateable="false">
+    approximate
+  </message>
+  <message name="IDS_PERMISSION_ALLOW_PRECISE_GEO" desc="Label on a radio button to select precise geolocation permission for a site." translateable="false">
+    precise
+  </message>
   <!-- Device Chooser -->
   <if expr="not is_android">
     <message name="IDS_BLUETOOTH_DEVICE_CHOOSER_PROMPT" desc="The label that is used to introduce Bluetooth chooser details to the user in a popup.">
diff --git a/components/policy/resources/templates/policy_definitions/Miscellaneous/EditBookmarksEnabled.yaml b/components/policy/resources/templates/policy_definitions/Miscellaneous/EditBookmarksEnabled.yaml
index 11fd067..8103c077 100644
--- a/components/policy/resources/templates/policy_definitions/Miscellaneous/EditBookmarksEnabled.yaml
+++ b/components/policy/resources/templates/policy_definitions/Miscellaneous/EditBookmarksEnabled.yaml
@@ -1,8 +1,8 @@
 caption: Enable or disable bookmark editing
 desc: |-
-  Setting the policy to True or leaving it unset lets users add, remove, or modify bookmarks.
+  Setting the policy to True or leaving it unset lets users add, remove, modify, or upload bookmarks.
 
-        Setting the policy to False means users can't add, remove, or modify bookmarks. They can still use existing bookmarks.
+        Setting the policy to False means users can't add, remove, modify or upload bookmarks. They can still use existing bookmarks.
 example_value: false
 features:
   dynamic_refresh: true
diff --git a/components/policy/test/data/pref_mapping/NTPCustomBackgroundEnabled.json b/components/policy/test/data/pref_mapping/NTPCustomBackgroundEnabled.json
index 4a5842c..7caa5c9 100644
--- a/components/policy/test/data/pref_mapping/NTPCustomBackgroundEnabled.json
+++ b/components/policy/test/data/pref_mapping/NTPCustomBackgroundEnabled.json
@@ -13,7 +13,7 @@
           "NTPCustomBackgroundEnabled": false
         },
         "prefs": {
-          "ntp.custom_background_dict": {
+          "ntp.custom_background_dict2": {
             "value": {}
           }
         }
diff --git a/components/sensitive_content/OWNERS b/components/sensitive_content/OWNERS
index 4ae43d4..e646dfa 100644
--- a/components/sensitive_content/OWNERS
+++ b/components/sensitive_content/OWNERS
@@ -1,3 +1,2 @@
 jkeitel@google.com
 schwering@google.com
-theocristea@google.com
diff --git a/components/signin/public/android/java/src/org/chromium/components/signin/AccountUtils.java b/components/signin/public/android/java/src/org/chromium/components/signin/AccountUtils.java
index 96d7e1b..81effe0 100644
--- a/components/signin/public/android/java/src/org/chromium/components/signin/AccountUtils.java
+++ b/components/signin/public/android/java/src/org/chromium/components/signin/AccountUtils.java
@@ -41,10 +41,6 @@
         return new Account(email, GOOGLE_ACCOUNT_TYPE);
     }
 
-    public static Account createAccountFromName(String email) {
-        return createAccountFromEmail(email);
-    }
-
     /** Converts a list of {@link AccountInfo}s to a list of account emails. */
     public static List<String> toAccountEmails(final List<AccountInfo> accounts) {
         int size = accounts.size();
diff --git a/components/signin/public/base/signin_switches.cc b/components/signin/public/base/signin_switches.cc
index 357968d3..f679a40 100644
--- a/components/signin/public/base/signin_switches.cc
+++ b/components/signin/public/base/signin_switches.cc
@@ -180,15 +180,15 @@
 
 BASE_FEATURE(kEnableSnackbarInSettings,
              "EnableSnackbarInSettings",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 
 BASE_FEATURE(kEnableImprovedGuestProfileMenu,
              "EnableImprovedGuestProfileMenu",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 
 BASE_FEATURE(kEnablePendingModePasswordsPromo,
              "EnablePendingModePasswordsPromo",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 
 #if BUILDFLAG(IS_IOS)
 
diff --git a/components/signin/public/identity_manager/accounts_cookie_mutator_unittest.cc b/components/signin/public/identity_manager/accounts_cookie_mutator_unittest.cc
index 29504b0d..f016832 100644
--- a/components/signin/public/identity_manager/accounts_cookie_mutator_unittest.cc
+++ b/components/signin/public/identity_manager/accounts_cookie_mutator_unittest.cc
@@ -97,6 +97,14 @@
   void PrepareURLLoaderResponsesForAction(AccountsCookiesMutatorAction action) {
     switch (action) {
       case AccountsCookiesMutatorAction::kSetAccountsInCookie:
+        // Simulate GetCheckConnectionInfo response to avoid triggering
+        // timeouts.
+        GetTestURLLoaderFactory()->AddResponse(
+            GaiaUrls::GetInstance()
+                ->GetCheckConnectionInfoURLWithSource(
+                    GaiaConstants::kChromeSource)
+                .spec(),
+            "cc_result");
         GetTestURLLoaderFactory()->AddResponse(
             GaiaUrls::GetInstance()
                 ->oauth_multilogin_url()
diff --git a/components/supervised_user/core/browser/kids_chrome_management_url_checker_client.cc b/components/supervised_user/core/browser/kids_chrome_management_url_checker_client.cc
index 6c4c7a9..4dbf69b 100644
--- a/components/supervised_user/core/browser/kids_chrome_management_url_checker_client.cc
+++ b/components/supervised_user/core/browser/kids_chrome_management_url_checker_client.cc
@@ -9,7 +9,6 @@
 #include <string_view>
 #include <utility>
 
-#include "base/feature_list.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback.h"
 #include "base/functional/callback_helpers.h"
@@ -17,12 +16,12 @@
 #include "base/memory/ptr_util.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/version_info/channel.h"
+#include "build/build_config.h"
 #include "components/safe_search_api/url_checker_client.h"
 #include "components/signin/public/identity_manager/identity_manager.h"
 #include "components/supervised_user/core/browser/fetcher_config.h"
 #include "components/supervised_user/core/browser/kids_management_api_fetcher.h"
 #include "components/supervised_user/core/browser/proto/kidsmanagement_messages.pb.h"
-#include "components/supervised_user/core/common/features.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 #include "third_party/protobuf/src/google/protobuf/message_lite.h"
 #include "url/gurl.h"
@@ -78,19 +77,24 @@
                                   channel);
 }
 
-FetcherConfig GetFetcherConfig() {
-  // Currently we only support 3 of the 4 possible combinations of the flags
-  // below. We don't anticipate a need for having BestEffort and
-  // WaitUntilAvailable at this time.
-  if (base::FeatureList::IsEnabled(
-          kUncredentialedFilteringFallbackForSupervisedUsers)) {
-    return kClassifyUrlConfigBestEffort;
-  }
-  if (base::FeatureList::IsEnabled(
-          kWaitUntilAccessTokenAvailableForClassifyUrl)) {
-    return kClassifyUrlConfigWaitUntilAccessTokenAvailable;
-  }
+constexpr FetcherConfig GetFetcherConfig() {
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
+  // Supervised users on these platforms might get into a state where their
+  // credentials are not available, so best-effort access mode is a graceful
+  // fallback here.
+  return kClassifyUrlConfigBestEffort;
+#elif BUILDFLAG(IS_ANDROID)
+  // Android enforces at the OS level that supervised users must have
+  // valid sign in credentials (and triggers a reauth if not). We can
+  // therefore wait for a valid access token to be available before
+  // calling ClassifyUrl, to avoid window conditions where the access
+  // token is not yet available (eg. during startup).
+  return kClassifyUrlConfigWaitUntilAccessTokenAvailable;
+#else
+  // Other platforms don't enforce this, and we therefore cannot
+  // wait for access tokens in Chrome.
   return kClassifyUrlConfig;
+#endif
 }
 
 }  // namespace
diff --git a/components/supervised_user/core/browser/kids_chrome_management_url_checker_client_unittest.cc b/components/supervised_user/core/browser/kids_chrome_management_url_checker_client_unittest.cc
index 20fc360..3ccb3e2 100644
--- a/components/supervised_user/core/browser/kids_chrome_management_url_checker_client_unittest.cc
+++ b/components/supervised_user/core/browser/kids_chrome_management_url_checker_client_unittest.cc
@@ -12,16 +12,15 @@
 #include "base/notreached.h"
 #include "base/strings/stringprintf.h"
 #include "base/test/metrics/histogram_tester.h"
-#include "base/test/scoped_feature_list.h"
 #include "base/test/task_environment.h"
 #include "base/time/time.h"
 #include "base/values.h"
 #include "base/version_info/channel.h"
+#include "build/build_config.h"
 #include "components/safe_search_api/url_checker_client.h"
 #include "components/signin/public/base/consent_level.h"
 #include "components/signin/public/identity_manager/identity_test_environment.h"
 #include "components/supervised_user/core/browser/proto/kidsmanagement_messages.pb.h"
-#include "components/supervised_user/core/common/features.h"
 #include "google_apis/gaia/google_service_auth_error.h"
 #include "net/http/http_status_code.h"
 #include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
@@ -45,9 +44,6 @@
     : public ::testing::TestWithParam<bool> {
  public:
   void SetUp() override {
-    feature_list_.InitWithFeatureState(
-        supervised_user::kUncredentialedFilteringFallbackForSupervisedUsers,
-        UncredentialedFilteringFallbackEnabled());
     url_classifier_ = std::make_unique<KidsChromeManagementURLCheckerClient>(
         identity_test_env_.identity_manager(),
         test_url_loader_factory_.GetSafeWeakWrapper(), "us",
@@ -55,8 +51,6 @@
   }
 
  protected:
-  bool UncredentialedFilteringFallbackEnabled() { return GetParam(); }
-
   void MakePrimaryAccountAvailable() {
     identity_test_env_.MakePrimaryAccountAvailable(
         "homer@gmail.com", signin::ConsentLevel::kSignin);
@@ -132,10 +126,9 @@
  private:
   signin::IdentityTestEnvironment identity_test_env_;
   std::unique_ptr<KidsChromeManagementURLCheckerClient> url_classifier_;
-  base::test::ScopedFeatureList feature_list_;
 };
 
-TEST_P(KidsChromeManagementURLCheckerClientTest, UrlAllowed) {
+TEST_F(KidsChromeManagementURLCheckerClientTest, UrlAllowed) {
   MakePrimaryAccountAvailable();
 
   EXPECT_CALL(*this,
@@ -146,7 +139,7 @@
   SimulateKidsApiResponse(kidsmanagement::ClassifyUrlResponse::ALLOWED);
 }
 
-TEST_P(KidsChromeManagementURLCheckerClientTest, HistogramsAreEmitted) {
+TEST_F(KidsChromeManagementURLCheckerClientTest, HistogramsAreEmitted) {
   base::HistogramTester histogram_tester;
   MakePrimaryAccountAvailable();
 
@@ -162,7 +155,7 @@
                                     /*expected_count(grew by)*/ 1);
 }
 
-TEST_P(KidsChromeManagementURLCheckerClientTest, UrlRestricted) {
+TEST_F(KidsChromeManagementURLCheckerClientTest, UrlRestricted) {
   MakePrimaryAccountAvailable();
 
   EXPECT_CALL(*this,
@@ -173,59 +166,59 @@
   SimulateKidsApiResponse(kidsmanagement::ClassifyUrlResponse::RESTRICTED);
 }
 
-TEST_P(KidsChromeManagementURLCheckerClientTest, NoPrimaryAccount) {
-  // This test does not add a primary account, and therefore no access token is
-  // available. On platforms with kWaitUntilAccessTokenAvailableForClassifyUrl
-  // enabled this means that the ClassifyUrl call will not be made.
-  if (!base::FeatureList::IsEnabled(
-          kWaitUntilAccessTokenAvailableForClassifyUrl)) {
-    if (UncredentialedFilteringFallbackEnabled()) {
-      // We fallback to making an uncredentialed request to ClassifyUrl, which
-      // succeeds.
-      EXPECT_CALL(*this,
-                  OnCheckDone(GURL("http://example.com"),
-                              safe_search_api::ClientClassification::kAllowed));
-    } else {
-      EXPECT_CALL(*this,
-                  OnCheckDone(GURL("http://example.com"),
-                              safe_search_api::ClientClassification::kUnknown));
-    }
-  }
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
+TEST_F(KidsChromeManagementURLCheckerClientTest, NoPrimaryAccount) {
+  // On desktop platforms, uncredentialed access is allowed.
+  EXPECT_CALL(*this,
+              OnCheckDone(GURL("http://example.com"),
+                          safe_search_api::ClientClassification::kAllowed));
   CheckUrl("http://example.com");
-
-  if (UncredentialedFilteringFallbackEnabled()) {
-    SimulateKidsApiResponse(kidsmanagement::ClassifyUrlResponse::ALLOWED);
-  }
+  SimulateKidsApiResponse(kidsmanagement::ClassifyUrlResponse::ALLOWED);
 }
+#elif BUILDFLAG(IS_ANDROID)
+TEST_F(KidsChromeManagementURLCheckerClientTest, NoPrimaryAccount) {
+  // On Android, uncredentialed access will hang on access token wait.
+  EXPECT_CALL(*this, OnCheckDone(GURL("http://example.com"), _)).Times(0);
+  CheckUrl("http://example.com");
+}
+#else
+TEST_F(KidsChromeManagementURLCheckerClientTest, NoPrimaryAccount) {
+  // On other platforms platforms, uncredentialed classification is not
+  // available.
+  EXPECT_CALL(*this,
+              OnCheckDone(GURL("http://example.com"),
+                          safe_search_api::ClientClassification::kUnknown));
+  CheckUrl("http://example.com");
+}
+#endif
 
-TEST_P(KidsChromeManagementURLCheckerClientTest, AccessTokenError) {
+TEST_F(KidsChromeManagementURLCheckerClientTest, AccessTokenError) {
   MakePrimaryAccountAvailable();
   StopAutomaticIssueOfAccessTokens();
 
   // This outcome depents on the feature flag values.
-  if (UncredentialedFilteringFallbackEnabled()) {
-    // We fallback to making an uncredentialed request to ClassifyUrl, which
-    // succeeds.
-    EXPECT_CALL(*this,
-                OnCheckDone(GURL("http://example.com"),
-                            safe_search_api::ClientClassification::kAllowed));
-  } else {
-    // We fail the request when we fail the access token fetch (returning
-    // unknown) to the client.
-    EXPECT_CALL(*this,
-                OnCheckDone(GURL("http://example.com"),
-                            safe_search_api::ClientClassification::kUnknown));
-  }
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
+  // We fallback to making an uncredentialed request to ClassifyUrl, which
+  // succeeds.
+  EXPECT_CALL(*this,
+              OnCheckDone(GURL("http://example.com"),
+                          safe_search_api::ClientClassification::kAllowed));
+#else  // We fail the request when we fail the access token fetch (returning
+  // unknown) to the client.
+  EXPECT_CALL(*this,
+              OnCheckDone(GURL("http://example.com"),
+                          safe_search_api::ClientClassification::kUnknown));
+#endif
 
   CheckUrl("http://example.com");
 
   SimulateAccessTokenError();
-  if (UncredentialedFilteringFallbackEnabled()) {
-    SimulateKidsApiResponse(kidsmanagement::ClassifyUrlResponse::ALLOWED);
-  }
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
+  SimulateKidsApiResponse(kidsmanagement::ClassifyUrlResponse::ALLOWED);
+#endif
 }
 
-TEST_P(KidsChromeManagementURLCheckerClientTest, NetworkError) {
+TEST_F(KidsChromeManagementURLCheckerClientTest, NetworkError) {
   MakePrimaryAccountAvailable();
 
   EXPECT_CALL(*this,
@@ -236,7 +229,7 @@
   SimulateNetworkError(net::ERR_UNEXPECTED);
 }
 
-TEST_P(KidsChromeManagementURLCheckerClientTest, HttpError) {
+TEST_F(KidsChromeManagementURLCheckerClientTest, HttpError) {
   MakePrimaryAccountAvailable();
 
   EXPECT_CALL(*this,
@@ -247,7 +240,7 @@
   SimulateHttpError(net::HTTP_BAD_GATEWAY);
 }
 
-TEST_P(KidsChromeManagementURLCheckerClientTest, ServiceError) {
+TEST_F(KidsChromeManagementURLCheckerClientTest, ServiceError) {
   MakePrimaryAccountAvailable();
 
   EXPECT_CALL(*this,
@@ -258,7 +251,7 @@
   SimulateMalformedResponse();
 }
 
-TEST_P(KidsChromeManagementURLCheckerClientTest,
+TEST_F(KidsChromeManagementURLCheckerClientTest,
        PendingRequestsAreCanceledWhenClientIsDestroyed) {
   EXPECT_CALL(*this, OnCheckDone(_, _)).Times(0);
 
@@ -268,14 +261,5 @@
   // Now run the callback.
   task_environment_.RunUntilIdle();
 }
-
-INSTANTIATE_TEST_SUITE_P(
-    KidsChromeManagementURLCheckerClientTest,
-    KidsChromeManagementURLCheckerClientTest,
-    ::testing::Bool(),
-    [](const testing::TestParamInfo<bool>& info) {
-      return info.param ? "UncredentialedFilteringFallbackEnabled"
-                        : "UncredentialedFilteringFallbackDisabled";
-    });
 }  // namespace
 }  // namespace supervised_user
diff --git a/components/supervised_user/core/browser/proto_fetcher.cc b/components/supervised_user/core/browser/proto_fetcher.cc
index 15eff15..844d0de 100644
--- a/components/supervised_user/core/browser/proto_fetcher.cc
+++ b/components/supervised_user/core/browser/proto_fetcher.cc
@@ -11,7 +11,6 @@
 #include <utility>
 
 #include "base/check_op.h"
-#include "base/feature_list.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback_forward.h"
 #include "base/memory/scoped_refptr.h"
@@ -20,11 +19,12 @@
 #include "base/types/expected.h"
 #include "base/types/optional_util.h"
 #include "base/version_info/channel.h"
+#include "build/build_config.h"
 #include "components/signin/public/identity_manager/access_token_info.h"
 #include "components/signin/public/identity_manager/identity_manager.h"
 #include "components/supervised_user/core/browser/fetcher_config.h"
-#include "components/supervised_user/core/common/features.h"
 #include "components/supervised_user/core/common/supervised_user_constants.h"
+#include "fetcher_config.h"
 #include "google_apis/common/api_key_request_util.h"
 #include "google_apis/gaia/google_service_auth_error.h"
 #include "net/http/http_status_code.h"
@@ -142,7 +142,6 @@
       kUrlLoaderRetryCount, network::SimpleURLLoader::RETRY_ON_NETWORK_CHANGE);
   return simple_url_loader;
 }
-
 }  // namespace
 
 FetchProcess::FetchProcess(
@@ -211,9 +210,12 @@
 void FetchProcess::OnSimpleUrlLoaderComplete(
     scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
     std::unique_ptr<std::string> response_body) {
-  if (base::FeatureList::IsEnabled(
-          supervised_user::
-              kUncredentialedFilteringFallbackForSupervisedUsers) &&
+  bool can_fallback_to_uncredentialed_access =
+      config_->access_token_config.credentials_requirement ==
+      AccessTokenConfig::CredentialsRequirement::kBestEffort;
+  // When the request has failed due to auth error, retry once in a best effort
+  // (no end user credentials) mode if that mode is available.
+  if (can_fallback_to_uncredentialed_access &&
       HasHttpAuthErrorResponse(*simple_url_loader_) &&
       !triggered_retry_on_http_auth_error_) {
     // The server has rejected our credentials.
diff --git a/components/supervised_user/core/browser/proto_fetcher_unittest.cc b/components/supervised_user/core/browser/proto_fetcher_unittest.cc
index cc3fa87..effafde 100644
--- a/components/supervised_user/core/browser/proto_fetcher_unittest.cc
+++ b/components/supervised_user/core/browser/proto_fetcher_unittest.cc
@@ -195,10 +195,10 @@
 
 // Base of the test fixture for proto fetcher.
 // Defines required runtime environment, and a collection of helper methods
-// which are used to build initial test state and define behaviours.
+// which are used to build initial test state and define behaviors.
 //
 // Simulate* methods are short-hands to put response with specific property in
-// test url environmnent's queue;
+// test url environment's queue;
 //
 // FastForward is important for retrying feature tests: make sure that the time
 // skipped is greater than possible retry timeouts.
@@ -299,31 +299,18 @@
   base::test::ScopedFeatureList feature_list_;
 };
 
-struct ProtoFetcherTestParam {
-  using TupleT = std::tuple<FetcherConfig, bool>;
-  FetcherConfig fetcher_config;
-  bool uncredentialed_fallback_enabled;
-  explicit ProtoFetcherTestParam(TupleT t)
-      : fetcher_config(std::get<0>(t)),
-        uncredentialed_fallback_enabled(std::get<1>(t)) {}
-};
-
-class ProtoFetcherTest
-    : public ProtoFetcherTestBase,
-      public ::testing::TestWithParam<ProtoFetcherTestParam> {
+class ProtoFetcherTest : public ProtoFetcherTestBase,
+                         public ::testing::TestWithParam<FetcherConfig> {
  public:
-  ProtoFetcherTest() : ProtoFetcherTestBase(GetConfig()) {
-    feature_list_.InitWithFeatureState(
-        supervised_user::kUncredentialedFilteringFallbackForSupervisedUsers,
-        UncredentialedFallbackEnabled());
-  }
-  static const FetcherConfig& GetConfig() { return GetParam().fetcher_config; }
-  static bool UncredentialedFallbackEnabled() {
-    return GetParam().uncredentialed_fallback_enabled;
-  }
+  ProtoFetcherTest() : ProtoFetcherTestBase(GetConfig()) {}
+  static const FetcherConfig& GetConfig() { return GetParam(); }
 
- private:
-  base::test::ScopedFeatureList feature_list_;
+  void SetUp() override {
+    CHECK(GetConfig().access_token_config.credentials_requirement ==
+          AccessTokenConfig::CredentialsRequirement::kStrict)
+        << "To test other credential requirements than strict mode (eg.: best "
+           "effort mode), use BestEffortProtoFetcherTest suite below.";
+  }
 };
 
 // Test whether the outgoing request has correctly set endpoint and method.
@@ -504,16 +491,6 @@
   ASSERT_EQ(test_url_loader_factory_.NumPending(), 1);
   SimulateResponseForPendingRequestWithHttpAuthError(0);
 
-  if (UncredentialedFallbackEnabled()) {
-    // This triggers a retry of the request. The access token is invalidated
-    // and a new one is fetched.
-    ASSERT_EQ(test_url_loader_factory_.NumPending(), 1);
-
-    // Simulate another 401 response from the server (which will not be
-    // retried).
-    SimulateResponseForPendingRequestWithHttpAuthError(0);
-  }
-
   // The request is failed.
   ASSERT_EQ(test_url_loader_factory_.NumPending(), 0);
   EXPECT_FALSE(receiver->GetResult().has_value());
@@ -535,25 +512,14 @@
   ASSERT_EQ(test_url_loader_factory_.NumPending(), 1);
   SimulateResponseForPendingRequestWithHttpAuthError(0);
 
-  if (UncredentialedFallbackEnabled()) {
-    // This triggers a retry of the request. The access token is invalidated
-    // and a new one is fetched.
-    ASSERT_EQ(test_url_loader_factory_.NumPending(), 1);
-
-    // This time the server responds with success.
-    SimulateDefaultResponseForPendingRequest(0);
-
-    ASSERT_TRUE(receiver->GetResult().has_value());
-  } else {
-    // The request is failed.
-    ASSERT_EQ(test_url_loader_factory_.NumPending(), 0);
-    EXPECT_FALSE(receiver->GetResult().has_value());
-    EXPECT_EQ(receiver->GetResult().error().state(),
-              ProtoFetcherStatus::State::HTTP_STATUS_OR_NET_ERROR);
-    EXPECT_EQ(
-        receiver->GetResult().error().http_status_or_net_error(),
-        ProtoFetcherStatus::HttpStatusOrNetErrorType(net::HTTP_UNAUTHORIZED));
-  }
+  // The request is failed.
+  ASSERT_EQ(test_url_loader_factory_.NumPending(), 0);
+  EXPECT_FALSE(receiver->GetResult().has_value());
+  EXPECT_EQ(receiver->GetResult().error().state(),
+            ProtoFetcherStatus::State::HTTP_STATUS_OR_NET_ERROR);
+  EXPECT_EQ(
+      receiver->GetResult().error().http_status_or_net_error(),
+      ProtoFetcherStatus::HttpStatusOrNetErrorType(net::HTTP_UNAUTHORIZED));
 }
 
 // The fetchers are recording various metrics for the basic flow with default
@@ -817,8 +783,8 @@
 // Instead of /0, /1... print human-readable description of the test: status
 // of the retrying feature followed by http method.
 std::string PrettyPrintFetcherTestCaseName(
-    const ::testing::TestParamInfo<ProtoFetcherTestParam>& info) {
-  const FetcherConfig& fetcher_config = info.param.fetcher_config;
+    const ::testing::TestParamInfo<FetcherConfig>& info) {
+  const FetcherConfig& fetcher_config = info.param;
   std::string base = fetcher_config.GetHttpMethod();
   if (fetcher_config.backoff_policy.has_value()) {
     base += "Retrying";
@@ -828,40 +794,24 @@
   } else {
     base += "WithoutMetrics";
   }
-
-  if (info.param.uncredentialed_fallback_enabled) {
-    base += "_FallbackEnabled";
-  } else {
-    base += "_FallbackDisabled";
-  }
-
   return base;
 }
 
-INSTANTIATE_TEST_SUITE_P(
-    All,
-    ProtoFetcherTest,
-    testing::ConvertGenerator<ProtoFetcherTestParam::TupleT>(
-        testing::Combine(testing::Values(kTestGetConfig,
+INSTANTIATE_TEST_SUITE_P(All,
+                         ProtoFetcherTest,
+                         testing::Values(kTestGetConfig,
                                          kTestPostConfig,
                                          kTestRetryConfig,
                                          kTestGetConfigWithoutMetrics),
-                         testing::Bool())),
-    &PrettyPrintFetcherTestCaseName);
+                         &PrettyPrintFetcherTestCaseName);
 
 class BestEffortProtoFetcherTest : public ProtoFetcherTestBase,
                                    public testing::Test {
  public:
-  BestEffortProtoFetcherTest()
-      : ProtoFetcherTestBase(GetConfig()),
-        feature_list_(supervised_user::
-                          kUncredentialedFilteringFallbackForSupervisedUsers) {}
+  BestEffortProtoFetcherTest() : ProtoFetcherTestBase(GetConfig()) {}
   static const FetcherConfig& GetConfig() {
     return kTestGetConfigBestEffortAccessToken;
   }
-
- private:
-  base::test::ScopedFeatureList feature_list_;
 };
 
 // Tests a flow where the caller is denied access token.
diff --git a/components/supervised_user/core/common/features.cc b/components/supervised_user/core/common/features.cc
index e4943b0..249899e 100644
--- a/components/supervised_user/core/common/features.cc
+++ b/components/supervised_user/core/common/features.cc
@@ -113,33 +113,6 @@
              base::FEATURE_ENABLED_BY_DEFAULT);
 #endif
 
-// TODO: crbug.com/378636321 - Clean up the
-// kUncredentialedFilteringFallbackForSupervisedUsers and
-// kWaitUntilAccessTokenAvailableForClassifyUrl flags, by inlining the
-// platform #defines.
-BASE_FEATURE(kUncredentialedFilteringFallbackForSupervisedUsers,
-             "UncredentialedFilteringFallbackForSupervisedUsers",
-#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
-             base::FEATURE_ENABLED_BY_DEFAULT);
-#else
-             base::FEATURE_DISABLED_BY_DEFAULT);
-#endif
-
-BASE_FEATURE(kWaitUntilAccessTokenAvailableForClassifyUrl,
-             "WaitUntilAccessTokenAvailableForClassifyUrl",
-#if BUILDFLAG(IS_ANDROID)
-             // Android enforces at the OS level that supervised users must have
-             // valid sign in credentials (and triggers a reauth if not). We can
-             // therefore wait for a valid access token to be available before
-             // calling ClassifyUrl, to avoid window conditions where the access
-             // token is not yet available (eg. during startup).
-             base::FEATURE_ENABLED_BY_DEFAULT
-#else
-             // Other platforms don't enforce this, and we therefore cannot
-             // wait for access tokens in Chrome.
-             base::FEATURE_DISABLED_BY_DEFAULT
-#endif
-);
 
 BASE_FEATURE(kAlignSafeSitesValueWithBrowserDefault,
              "AlignSafeSitesValueWithBrowserDefault",
diff --git a/components/supervised_user/core/common/features.h b/components/supervised_user/core/common/features.h
index 71472f7..32dae68 100644
--- a/components/supervised_user/core/common/features.h
+++ b/components/supervised_user/core/common/features.h
@@ -60,14 +60,6 @@
 BASE_DECLARE_FEATURE(kEnableSupervisedUserVersionSignOutDialog);
 #endif
 
-// Fallback to sending un-credentialed filtering requests for supervised users
-// if they do not have a valid access token.
-BASE_DECLARE_FEATURE(kUncredentialedFilteringFallbackForSupervisedUsers);
-
-// Uses PrimaryAccountAccessTokenFetcher::Mode::kWaitUntilAvailable for
-// ClassifyUrl fetches.
-BASE_DECLARE_FEATURE(kWaitUntilAccessTokenAvailableForClassifyUrl);
-
 // Manages kSupervisedUserSafeSites exclusively within managed user pref store,
 // while keeping the default value neutral.
 BASE_DECLARE_FEATURE(kAlignSafeSitesValueWithBrowserDefault);
diff --git a/components/sync/service/glue/sync_transport_data_prefs.cc b/components/sync/service/glue/sync_transport_data_prefs.cc
index d10e9cbd..1b99edf 100644
--- a/components/sync/service/glue/sync_transport_data_prefs.cc
+++ b/components/sync/service/glue/sync_transport_data_prefs.cc
@@ -25,16 +25,14 @@
 
 const char kSyncGaiaId[] = "sync.gaia_id";
 
+// Keys for the `kSyncTransportDataPerAccount` dictionary pref:
 const char kSyncCacheGuid[] = "sync.cache_guid";
 const char kSyncBirthday[] = "sync.birthday";
 const char kSyncBagOfChips[] = "sync.bag_of_chips";
-
 // 64-bit integer serialization of the base::Time when the last sync occurred.
 const char kSyncLastSyncedTime[] = "sync.last_synced_time";
-
 // 64-bit integer serialization of the base::Time of the last sync poll.
 const char kSyncLastPollTime[] = "sync.last_poll_time";
-
 // 64-bit integer serialization of base::TimeDelta storing poll intervals
 // received by the server. For historic reasons, this is called
 // "short_poll_interval", but it's not worth the hassle to rename it.
@@ -46,38 +44,6 @@
     PrefService* pref_service,
     const signin::GaiaIdHash& gaia_id_hash)
     : pref_service_(pref_service), gaia_id_hash_(gaia_id_hash) {
-  // If the account-keyed prefs aren't populated yet, copy over the values from
-  // the legacy prefs.
-  // TODO(crbug.com/360888481): Clean up this migration after 2025-05 or so.
-  // As a sanity check, ensure that the Gaia IDs match.
-  GaiaId old_gaia_id(pref_service_->GetString(kSyncGaiaId));
-  if (signin::GaiaIdHash::FromGaiaId(old_gaia_id) == gaia_id_hash_ &&
-      !pref_service_->HasPrefPath(
-          prefs::internal::kSyncTransportDataPerAccount)) {
-    ScopedDictPrefUpdate update_account_dict(
-        pref_service_, prefs::internal::kSyncTransportDataPerAccount);
-    base::Value::Dict* account_values =
-        update_account_dict->EnsureDict(gaia_id_hash_.ToBase64());
-    account_values->Set(kSyncCacheGuid,
-                        pref_service_->GetValue(kSyncCacheGuid).Clone());
-    account_values->Set(kSyncBirthday,
-                        pref_service_->GetValue(kSyncBirthday).Clone());
-    account_values->Set(kSyncBagOfChips,
-                        pref_service_->GetValue(kSyncBagOfChips).Clone());
-    account_values->Set(kSyncLastSyncedTime,
-                        pref_service_->GetValue(kSyncLastSyncedTime).Clone());
-    account_values->Set(kSyncLastPollTime,
-                        pref_service_->GetValue(kSyncLastPollTime).Clone());
-    account_values->Set(kSyncPollInterval,
-                        pref_service_->GetValue(kSyncPollInterval).Clone());
-  }
-  // Whether values were migrated or not, clean up the legacy prefs now.
-  pref_service_->ClearPref(kSyncCacheGuid);
-  pref_service_->ClearPref(kSyncBirthday);
-  pref_service_->ClearPref(kSyncBagOfChips);
-  pref_service_->ClearPref(kSyncLastSyncedTime);
-  pref_service_->ClearPref(kSyncLastPollTime);
-  pref_service_->ClearPref(kSyncPollInterval);
 }
 
 SyncTransportDataPrefs::~SyncTransportDataPrefs() = default;
@@ -89,15 +55,6 @@
 
   registry->RegisterDictionaryPref(
       prefs::internal::kSyncTransportDataPerAccount);
-
-  // TODO(crbug.com/360888481): Clean up the legacy non-account-keyed prefs
-  // after 2025-05 or so.
-  registry->RegisterStringPref(kSyncCacheGuid, std::string());
-  registry->RegisterStringPref(kSyncBirthday, std::string());
-  registry->RegisterStringPref(kSyncBagOfChips, std::string());
-  registry->RegisterTimePref(kSyncLastSyncedTime, base::Time());
-  registry->RegisterTimePref(kSyncLastPollTime, base::Time());
-  registry->RegisterTimeDeltaPref(kSyncPollInterval, base::TimeDelta());
 }
 
 void SyncTransportDataPrefs::ClearForCurrentAccount() {
@@ -159,7 +116,6 @@
   // This fixes a past bug where stored pref values were accidentally
   // re-interpreted from "seconds" to "microseconds"; see crbug.com/1246850.
   if (poll_interval < base::Minutes(1)) {
-    pref_service_->ClearPref(kSyncPollInterval);
     return base::TimeDelta();
   }
   return poll_interval;
diff --git a/components/sync/service/sync_prefs.cc b/components/sync/service/sync_prefs.cc
index 076a599..dd9f8cd 100644
--- a/components/sync/service/sync_prefs.cc
+++ b/components/sync/service/sync_prefs.cc
@@ -128,13 +128,6 @@
       prefs::internal::kSelectedTypesPerAccount,
       base::BindRepeating(&SyncPrefs::OnSelectedTypesPrefChanged,
                           base::Unretained(this)));
-
-#if !BUILDFLAG(IS_CHROMEOS)
-  pref_initial_sync_feature_setup_complete_.Init(
-      prefs::internal::kSyncInitialSyncFeatureSetupComplete, pref_service_,
-      base::BindRepeating(&SyncPrefs::OnFirstSetupCompletePrefChange,
-                          base::Unretained(this)));
-#endif  // !BUILDFLAG(IS_CHROMEOS)
 }
 
 SyncPrefs::~SyncPrefs() {
@@ -836,16 +829,6 @@
   }
 }
 
-#if !BUILDFLAG(IS_CHROMEOS)
-void SyncPrefs::OnFirstSetupCompletePrefChange() {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  for (SyncPrefObserver& observer : sync_pref_observers_) {
-    observer.OnFirstSetupCompletePrefChange(
-        *pref_initial_sync_feature_setup_complete_);
-  }
-}
-#endif  // !BUILDFLAG(IS_CHROMEOS)
-
 // static
 void SyncPrefs::RegisterTypeSelectedPref(PrefRegistrySimple* registry,
                                          UserSelectableType type) {
diff --git a/components/sync/service/sync_prefs.h b/components/sync/service/sync_prefs.h
index 9c697bd7..58116a4 100644
--- a/components/sync/service/sync_prefs.h
+++ b/components/sync/service/sync_prefs.h
@@ -33,10 +33,6 @@
 class SyncPrefObserver {
  public:
   virtual void OnSyncManagedPrefChange(bool is_sync_managed) = 0;
-#if !BUILDFLAG(IS_CHROMEOS)
-  virtual void OnFirstSetupCompletePrefChange(
-      bool is_initial_sync_feature_setup_complete) = 0;
-#endif  // !BUILDFLAG(IS_CHROMEOS)
   // Called when any of the prefs related to the user's selected data types has
   // changed.
   virtual void OnSelectedTypesPrefChange() = 0;
@@ -309,10 +305,6 @@
 
   void OnSelectedTypesPrefChanged(const std::string& pref_name);
 
-#if !BUILDFLAG(IS_CHROMEOS)
-  void OnFirstSetupCompletePrefChange();
-#endif  // !BUILDFLAG(IS_CHROMEOS)
-
   // Never null.
   const raw_ptr<PrefService> pref_service_;
 
@@ -328,10 +320,6 @@
 
   bool password_sync_allowed_ = true;
 
-#if !BUILDFLAG(IS_CHROMEOS)
-  BooleanPrefMember pref_initial_sync_feature_setup_complete_;
-#endif  // !BUILDFLAG(IS_CHROMEOS)
-
   // Caches the value of the kEnableLocalSyncBackend pref to avoid it flipping
   // during the lifetime of the service.
   const bool local_sync_enabled_;
diff --git a/components/sync/service/sync_prefs_unittest.cc b/components/sync/service/sync_prefs_unittest.cc
index 73b755f..5f1945f 100644
--- a/components/sync/service/sync_prefs_unittest.cc
+++ b/components/sync/service/sync_prefs_unittest.cc
@@ -199,9 +199,6 @@
 class MockSyncPrefObserver : public SyncPrefObserver {
  public:
   MOCK_METHOD(void, OnSyncManagedPrefChange, (bool), (override));
-#if !BUILDFLAG(IS_CHROMEOS)
-  MOCK_METHOD(void, OnFirstSetupCompletePrefChange, (bool), (override));
-#endif  // !BUILDFLAG(IS_CHROMEOS)
   MOCK_METHOD(void, OnSelectedTypesPrefChange, (), (override));
 };
 
@@ -225,22 +222,13 @@
 
 #if !BUILDFLAG(IS_CHROMEOS)
 TEST_F(SyncPrefsTest, FirstSetupCompletePrefChange) {
-  StrictMock<MockSyncPrefObserver> mock_sync_pref_observer;
-  InSequence in_sequence;
-
-  EXPECT_CALL(mock_sync_pref_observer, OnFirstSetupCompletePrefChange(true));
-  EXPECT_CALL(mock_sync_pref_observer, OnFirstSetupCompletePrefChange(false));
-
   ASSERT_FALSE(sync_prefs_->IsInitialSyncFeatureSetupComplete());
 
-  sync_prefs_->AddObserver(&mock_sync_pref_observer);
-
   sync_prefs_->SetInitialSyncFeatureSetupComplete();
   EXPECT_TRUE(sync_prefs_->IsInitialSyncFeatureSetupComplete());
+
   sync_prefs_->ClearInitialSyncFeatureSetupComplete();
   EXPECT_FALSE(sync_prefs_->IsInitialSyncFeatureSetupComplete());
-
-  sync_prefs_->RemoveObserver(&mock_sync_pref_observer);
 }
 #endif  // !BUILDFLAG(IS_CHROMEOS)
 
diff --git a/components/sync/service/sync_service_impl.cc b/components/sync/service/sync_service_impl.cc
index fc65910..7452cf84 100644
--- a/components/sync/service/sync_service_impl.cc
+++ b/components/sync/service/sync_service_impl.cc
@@ -321,8 +321,6 @@
       /*delegate=*/this, &crypto_, &sync_prefs_,
       data_type_manager_->GetRegisteredDataTypes());
 
-  sync_prefs_observation_.Observe(&sync_prefs_);
-
   if (!IsLocalSyncEnabled()) {
     auth_manager_->RegisterForAuthNotifications();
 
@@ -363,7 +361,7 @@
     // optional, because it is indistinguishable from the
     // sync-reset-via-dashboard case. It can be resolved by invoking
     // ClearSyncFeatureDisabledViaDashboard().
-    sync_prefs_.SetSyncFeatureDisabledViaDashboard();
+    user_settings_->SetSyncFeatureDisabledViaDashboard();
 #endif  // BUILDFLAG(IS_CHROMEOS)
   } else if (HasDisableReason(DISABLE_REASON_NOT_SIGNED_IN)) {
     // On ChromeOS-Ash, signout is not possible, so it's not necessary to handle
@@ -794,7 +792,8 @@
 
   // If local sync is enabled, most disable reasons don't apply.
   if (!IsLocalSyncEnabled()) {
-    if (sync_prefs_.IsSyncClientDisabledByPolicy() || sync_disabled_by_admin_) {
+    if (user_settings_->IsSyncClientDisabledByPolicy() ||
+        sync_disabled_by_admin_) {
       result.Put(DISABLE_REASON_ENTERPRISE_POLICY);
     }
     if (!IsSignedIn()) {
@@ -1498,7 +1497,7 @@
   return engine_->GetLastSyncedTimeForDebugging();
 }
 
-void SyncServiceImpl::OnSelectedTypesPrefChange() {
+void SyncServiceImpl::OnSelectedTypesChanged() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   if (data_type_manager_) {
@@ -1697,7 +1696,7 @@
                                     canonical_data_type);
     }
   } else {
-    bool sync_everything = sync_prefs_.HasKeepEverythingSynced();
+    bool sync_everything = user_settings_->IsSyncEverythingEnabled();
     base::UmaHistogramBoolean("Sync.SyncEverything2", sync_everything);
 
     if (!sync_everything) {
@@ -1709,7 +1708,7 @@
     }
 
 #if BUILDFLAG(IS_CHROMEOS)
-    bool sync_everything_os = sync_prefs_.IsSyncAllOsTypesEnabled();
+    bool sync_everything_os = user_settings_->IsSyncAllOsTypesEnabled();
     base::UmaHistogramBoolean("Sync.SyncEverythingOS", sync_everything_os);
     if (!sync_everything_os) {
       for (UserSelectableOsType type : user_settings_->GetSelectedOsTypes()) {
@@ -1802,7 +1801,7 @@
   return data_type_manager_->GetEntityCountsForDebugging(std::move(callback));
 }
 
-void SyncServiceImpl::OnSyncManagedPrefChange(bool is_sync_managed) {
+void SyncServiceImpl::OnSyncClientDisabledByPolicyChanged() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   // Local sync is not controlled by the "sync managed" policy, so these pref
@@ -1811,7 +1810,7 @@
     return;
   }
 
-  if (is_sync_managed) {
+  if (user_settings_->IsSyncClientDisabledByPolicy()) {
     StopAndClear(ResetEngineReason::kEnterprisePolicy);
 #if BUILDFLAG(IS_CHROMEOS)
     // On ChromeOS Ash, sync-the-feature stays disabled even after the policy is
@@ -1819,7 +1818,7 @@
     // optional, because it is indistinguishable from the
     // sync-reset-via-dashboard case. It can be resolved by invoking
     // ClearSyncFeatureDisabledViaDashboard().
-    sync_prefs_.SetSyncFeatureDisabledViaDashboard();
+    user_settings_->SetSyncFeatureDisabledViaDashboard();
 #endif  // BUILDFLAG(IS_CHROMEOS)
   } else {
     // Sync is no longer disabled by policy. Try starting it up if appropriate.
@@ -1831,8 +1830,7 @@
 }
 
 #if !BUILDFLAG(IS_CHROMEOS)
-void SyncServiceImpl::OnFirstSetupCompletePrefChange(
-    bool is_initial_sync_feature_setup_complete) {
+void SyncServiceImpl::OnInitialSyncFeatureSetupCompleted() {
   ConfigureDataTypeManager(CONFIGURE_REASON_RECONFIGURATION,
                            /*bypass_setup_in_progress_check=*/false);
 }
diff --git a/components/sync/service/sync_service_impl.h b/components/sync/service/sync_service_impl.h
index f665d71..e4218ab 100644
--- a/components/sync/service/sync_service_impl.h
+++ b/components/sync/service/sync_service_impl.h
@@ -64,12 +64,8 @@
 
 // Look at the SyncService interface for information on how to use this class.
 // You should not need to know about SyncServiceImpl directly.
-// TODO(crbug.com/40772592): Avoid implementing SyncPrefObserver here and
-// instead rely exclusively on SyncUserSettingsImpl::Delegate to react to user
-// setting changes.
 class SyncServiceImpl : public SyncService,
                         public SyncEngineHost,
-                        public SyncPrefObserver,
                         public DataTypeManagerObserver,
                         public SyncAuthManager::Delegate,
                         public SyncServiceCrypto::Delegate,
@@ -206,8 +202,12 @@
   bool IsCustomPassphraseAllowed() const override;
   SyncPrefs::SyncAccountState GetSyncAccountStateForPrefs() const override;
   CoreAccountInfo GetSyncAccountInfoForPrefs() const override;
+  void OnSyncClientDisabledByPolicyChanged() override;
+  void OnSelectedTypesChanged() override;
 #if BUILDFLAG(IS_CHROMEOS)
   void OnSyncFeatureDisabledViaDashboardCleared() override;
+#else   // BUILDFLAG(IS_CHROMEOS)
+  void OnInitialSyncFeatureSetupCompleted() override;
 #endif  // BUILDFLAG(IS_CHROMEOS)
 
   // IdentityManager::Observer implementation.
@@ -230,14 +230,6 @@
   bool HasCookieJarMismatch(
       const std::vector<gaia::ListedAccount>& cookie_jar_accounts);
 
-  // SyncPrefObserver implementation.
-  void OnSyncManagedPrefChange(bool is_sync_managed) override;
-#if !BUILDFLAG(IS_CHROMEOS)
-  void OnFirstSetupCompletePrefChange(
-      bool is_initial_sync_feature_setup_complete) override;
-#endif  // !BUILDFLAG(IS_CHROMEOS)
-  void OnSelectedTypesPrefChange() override;
-
   // KeyedService implementation.  This must be called exactly
   // once (before this object is destroyed).
   void Shutdown() override;
@@ -519,9 +511,6 @@
 
   std::unique_ptr<LocalDataMigrationItemQueue> local_data_migration_item_queue_;
 
-  base::ScopedObservation<SyncPrefs, SyncPrefObserver> sync_prefs_observation_{
-      this};
-
 #if BUILDFLAG(IS_ANDROID)
   // Manage and fetch the java object that wraps this SyncService on
   // android.
diff --git a/components/sync/service/sync_user_settings_impl.cc b/components/sync/service/sync_user_settings_impl.cc
index c3acd7a..379e340 100644
--- a/components/sync/service/sync_user_settings_impl.cc
+++ b/components/sync/service/sync_user_settings_impl.cc
@@ -88,6 +88,7 @@
   CHECK(delegate_);
   CHECK(crypto_);
   CHECK(prefs_);
+  prefs_observation_.Observe(prefs_);
 }
 
 SyncUserSettingsImpl::~SyncUserSettingsImpl() = default;
@@ -104,6 +105,7 @@
   }
   UMA_HISTOGRAM_ENUMERATION("Signin.SyncFirstSetupCompleteSource", source);
   prefs_->SetInitialSyncFeatureSetupComplete();
+  delegate_->OnInitialSyncFeatureSetupCompleted();
 }
 #endif  // !BUILDFLAG(IS_CHROMEOS)
 
@@ -462,4 +464,16 @@
   prefs_->SetEncryptionBootstrapTokenForAccount(token, gaia_id);
 }
 
+bool SyncUserSettingsImpl::IsSyncClientDisabledByPolicy() const {
+  return prefs_->IsSyncClientDisabledByPolicy();
+}
+
+void SyncUserSettingsImpl::OnSyncManagedPrefChange(bool is_sync_managed) {
+  delegate_->OnSyncClientDisabledByPolicyChanged();
+}
+
+void SyncUserSettingsImpl::OnSelectedTypesPrefChange() {
+  delegate_->OnSelectedTypesChanged();
+}
+
 }  // namespace syncer
diff --git a/components/sync/service/sync_user_settings_impl.h b/components/sync/service/sync_user_settings_impl.h
index 5db4cc1..44567c43 100644
--- a/components/sync/service/sync_user_settings_impl.h
+++ b/components/sync/service/sync_user_settings_impl.h
@@ -10,6 +10,7 @@
 #include <vector>
 
 #include "base/memory/raw_ptr.h"
+#include "base/scoped_observation.h"
 #include "build/build_config.h"
 #include "components/signin/public/identity_manager/account_info.h"
 #include "components/sync/base/data_type.h"
@@ -21,7 +22,7 @@
 
 class SyncServiceCrypto;
 
-class SyncUserSettingsImpl : public SyncUserSettings {
+class SyncUserSettingsImpl : public SyncUserSettings, public SyncPrefObserver {
  public:
   class Delegate {
    public:
@@ -31,8 +32,14 @@
     virtual bool IsCustomPassphraseAllowed() const = 0;
     virtual SyncPrefs::SyncAccountState GetSyncAccountStateForPrefs() const = 0;
     virtual CoreAccountInfo GetSyncAccountInfoForPrefs() const = 0;
+
+    // Observer-like notifications.
+    virtual void OnSyncClientDisabledByPolicyChanged() = 0;
+    virtual void OnSelectedTypesChanged() = 0;
 #if BUILDFLAG(IS_CHROMEOS)
     virtual void OnSyncFeatureDisabledViaDashboardCleared() = 0;
+#else   // BUILDFLAG(IS_CHROMEOS)
+    virtual void OnInitialSyncFeatureSetupCompleted() = 0;
 #endif  // BUILDFLAG(IS_CHROMEOS)
   };
 
@@ -50,6 +57,7 @@
   // (usually custom passphrase) and represents a user-entered passphrase.
   std::string GetEncryptionBootstrapToken() const;
   void SetEncryptionBootstrapToken(const std::string& token);
+  bool IsSyncClientDisabledByPolicy() const;
 
 #if BUILDFLAG(IS_CHROMEOS)
   void SetSyncFeatureDisabledViaDashboard();
@@ -108,13 +116,16 @@
   std::unique_ptr<Nigori> GetExplicitPassphraseDecryptionNigoriKey()
       const override;
 
- private:
-  bool ShouldUsePerAccountPrefs() const;
+  // SyncPrefObserver implementation.
+  void OnSyncManagedPrefChange(bool is_sync_managed) override;
+  void OnSelectedTypesPrefChange() override;
 
+ private:
   const raw_ptr<Delegate> delegate_;
   const raw_ptr<SyncServiceCrypto> crypto_;
   const raw_ptr<SyncPrefs> prefs_;
   const DataTypeSet registered_data_types_;
+  base::ScopedObservation<SyncPrefs, SyncPrefObserver> prefs_observation_{this};
 };
 
 }  // namespace syncer
diff --git a/components/sync/service/sync_user_settings_impl_unittest.cc b/components/sync/service/sync_user_settings_impl_unittest.cc
index c5912b2..c35314ea 100644
--- a/components/sync/service/sync_user_settings_impl_unittest.cc
+++ b/components/sync/service/sync_user_settings_impl_unittest.cc
@@ -8,6 +8,7 @@
 
 #include "base/test/bind.h"
 #include "base/test/scoped_feature_list.h"
+#include "base/test/task_environment.h"
 #include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/testing_pref_service.h"
 #include "components/saved_tab_groups/public/pref_names.h"
@@ -31,6 +32,10 @@
 
 namespace {
 
+using testing::Return;
+
+constexpr GaiaId::Literal kTestGaiaId("1111");
+
 DataTypeSet GetUserTypes() {
   DataTypeSet user_types = UserTypes();
 #if !BUILDFLAG(IS_CHROMEOS)
@@ -67,8 +72,30 @@
   MOCK_METHOD(std::string, GetEncryptionBootstrapToken, (), (const override));
 };
 
-class SyncUserSettingsImplTest : public testing::Test,
-                                 public SyncUserSettingsImpl::Delegate {
+class MockDelegate : public SyncUserSettingsImpl::Delegate {
+ public:
+  MockDelegate() = default;
+  ~MockDelegate() override = default;
+
+  MOCK_METHOD(bool, IsCustomPassphraseAllowed, (), (const override));
+  MOCK_METHOD(SyncPrefs::SyncAccountState,
+              GetSyncAccountStateForPrefs,
+              (),
+              (const override));
+  MOCK_METHOD(CoreAccountInfo,
+              GetSyncAccountInfoForPrefs,
+              (),
+              (const override));
+  MOCK_METHOD(void, OnSyncClientDisabledByPolicyChanged, (), (override));
+  MOCK_METHOD(void, OnSelectedTypesChanged, (), (override));
+#if BUILDFLAG(IS_CHROMEOS)
+  MOCK_METHOD(void, OnSyncFeatureDisabledViaDashboardCleared, (), (override));
+#else   // BUILDFLAG(IS_CHROMEOS)
+  MOCK_METHOD(void, OnInitialSyncFeatureSetupCompleted, (), (override));
+#endif  // BUILDFLAG(IS_CHROMEOS)
+};
+
+class SyncUserSettingsImplTest : public testing::Test {
  protected:
   SyncUserSettingsImplTest() {
     SyncPrefs::RegisterProfilePrefs(pref_service_.registry());
@@ -84,25 +111,22 @@
 
     sync_service_crypto_ = std::make_unique<SyncServiceCrypto>(
         &sync_service_crypto_delegate_, &trusted_vault_client_);
-  }
 
-  // SyncUserSettingsImpl::Delegate implementation.
-  bool IsCustomPassphraseAllowed() const override { return true; }
-
-  SyncPrefs::SyncAccountState GetSyncAccountStateForPrefs() const override {
-    return sync_account_state_;
-  }
-
-  CoreAccountInfo GetSyncAccountInfoForPrefs() const override {
-    CoreAccountInfo account;
-    account.email = "name@account.com";
-    account.gaia = GaiaId("name");
-    account.account_id = CoreAccountId::FromGaiaId(account.gaia);
-    return account;
+    ON_CALL(delegate_, IsCustomPassphraseAllowed).WillByDefault(Return(true));
+    ON_CALL(delegate_, GetSyncAccountStateForPrefs)
+        .WillByDefault(Return(SyncPrefs::SyncAccountState::kSyncing));
+    ON_CALL(delegate_, GetSyncAccountInfoForPrefs).WillByDefault([]() {
+      CoreAccountInfo account;
+      account.email = "name@account.com";
+      account.gaia = kTestGaiaId;
+      account.account_id = CoreAccountId::FromGaiaId(account.gaia);
+      return account;
+    });
   }
 
   void SetSyncAccountState(SyncPrefs::SyncAccountState sync_account_state) {
-    sync_account_state_ = sync_account_state;
+    ON_CALL(delegate_, GetSyncAccountStateForPrefs)
+        .WillByDefault(Return(sync_account_state));
 #if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
     if (sync_account_state ==
         SyncPrefs::SyncAccountState::kSignedInNotSyncing) {
@@ -111,32 +135,23 @@
 #endif
   }
 
-#if BUILDFLAG(IS_CHROMEOS)
-  void OnSyncFeatureDisabledViaDashboardCleared() override {
-    ++on_sync_feature_disabled_via_dashboard_cleared_count_;
-  }
-#endif  // BUILDFLAG(IS_CHROMEOS)
-
   std::unique_ptr<SyncUserSettingsImpl> MakeSyncUserSettings(
       DataTypeSet registered_types) {
     return std::make_unique<SyncUserSettingsImpl>(
-        /*delegate=*/this, sync_service_crypto_.get(), sync_prefs_.get(),
+        &delegate_, sync_service_crypto_.get(), sync_prefs_.get(),
         registered_types);
   }
 
+  base::test::SingleThreadTaskEnvironment task_environment_;
   // The order of fields matters because it determines destruction order and
   // fields are dependent.
   TestingPrefServiceSimple pref_service_;
   std::unique_ptr<SyncPrefs> sync_prefs_;
   testing::NiceMock<MockSyncServiceCryptoDelegate>
       sync_service_crypto_delegate_;
+  testing::NiceMock<MockDelegate> delegate_;
   trusted_vault::FakeTrustedVaultClient trusted_vault_client_;
   std::unique_ptr<SyncServiceCrypto> sync_service_crypto_;
-  SyncPrefs::SyncAccountState sync_account_state_ =
-      SyncPrefs::SyncAccountState::kSyncing;
-#if BUILDFLAG(IS_CHROMEOS)
-  int on_sync_feature_disabled_via_dashboard_cleared_count_ = 0;
-#endif  // BUILDFLAG(IS_CHROMEOS)
 };
 
 TEST_F(SyncUserSettingsImplTest, PreferredTypesSyncEverything) {
@@ -242,6 +257,7 @@
   EXPECT_EQ(sync_user_settings->GetSelectedTypes(),
             Difference(default_types, {UserSelectableType::kPayments}));
 
+  EXPECT_CALL(delegate_, OnSelectedTypesChanged());
   sync_user_settings->SetSelectedType(UserSelectableType::kPayments, true);
 
   EXPECT_EQ(sync_user_settings->GetSelectedTypes(), default_types);
@@ -264,15 +280,20 @@
 
   // Disable the sync-everything toggle first, which is required to change
   // individual toggles.
+  EXPECT_CALL(delegate_, OnSelectedTypesChanged());
   sync_user_settings->SetSelectedTypes(/*sync_everything=*/false,
                                        /*types=*/registered_types);
   ASSERT_EQ(sync_user_settings->GetSelectedTypes(), registered_types);
   ASSERT_FALSE(sync_user_settings->IsSyncEverythingEnabled());
+  testing::Mock::VerifyAndClearExpectations(&delegate_);
 
+  EXPECT_CALL(delegate_, OnSelectedTypesChanged());
   sync_user_settings->SetSelectedType(UserSelectableType::kPasswords, false);
   EXPECT_EQ(sync_user_settings->GetSelectedTypes(),
             registered_types_except_passwords);
+  testing::Mock::VerifyAndClearExpectations(&delegate_);
 
+  EXPECT_CALL(delegate_, OnSelectedTypesChanged());
   sync_user_settings->SetSelectedType(UserSelectableType::kPasswords, true);
   EXPECT_EQ(sync_user_settings->GetSelectedTypes(), registered_types);
 }
@@ -580,10 +601,8 @@
   sync_user_settings->SetEncryptionBootstrapToken("token");
   EXPECT_EQ("token", sync_user_settings->GetEncryptionBootstrapToken());
   EXPECT_EQ(sync_user_settings->GetEncryptionBootstrapToken(),
-            sync_prefs_->GetEncryptionBootstrapTokenForAccount(
-                GetSyncAccountInfoForPrefs().gaia));
-  sync_prefs_->ClearEncryptionBootstrapTokenForAccount(
-      GetSyncAccountInfoForPrefs().gaia);
+            sync_prefs_->GetEncryptionBootstrapTokenForAccount(kTestGaiaId));
+  sync_prefs_->ClearEncryptionBootstrapTokenForAccount(kTestGaiaId);
   EXPECT_TRUE(sync_user_settings->GetEncryptionBootstrapToken().empty());
 }
 
@@ -602,8 +621,7 @@
   sync_user_settings->SetEncryptionBootstrapToken("token");
   EXPECT_EQ("token", sync_user_settings->GetEncryptionBootstrapToken());
   EXPECT_EQ(sync_user_settings->GetEncryptionBootstrapToken(),
-            sync_prefs_->GetEncryptionBootstrapTokenForAccount(
-                GetSyncAccountInfoForPrefs().gaia));
+            sync_prefs_->GetEncryptionBootstrapTokenForAccount(kTestGaiaId));
 }
 
 TEST_F(SyncUserSettingsImplTest, ClearEncryptionBootstrapTokenPerAccount) {
@@ -612,8 +630,7 @@
       MakeSyncUserSettings(GetUserTypes());
   ASSERT_TRUE(sync_user_settings->GetEncryptionBootstrapToken().empty());
   sync_user_settings->SetEncryptionBootstrapToken("token");
-  sync_user_settings->KeepAccountSettingsPrefsOnlyForUsers(
-      {GetSyncAccountInfoForPrefs().gaia});
+  sync_user_settings->KeepAccountSettingsPrefsOnlyForUsers({kTestGaiaId});
   EXPECT_EQ("token", sync_user_settings->GetEncryptionBootstrapToken());
   sync_user_settings->KeepAccountSettingsPrefsOnlyForUsers({});
   EXPECT_TRUE(sync_user_settings->GetEncryptionBootstrapToken().empty());
@@ -625,20 +642,32 @@
       MakeSyncUserSettings(GetUserTypes());
 
   ASSERT_FALSE(sync_user_settings->IsSyncFeatureDisabledViaDashboard());
-  ASSERT_EQ(0, on_sync_feature_disabled_via_dashboard_cleared_count_);
 
+  EXPECT_CALL(delegate_, OnSyncFeatureDisabledViaDashboardCleared).Times(0);
   sync_user_settings->SetSyncFeatureDisabledViaDashboard();
   EXPECT_TRUE(sync_user_settings->IsSyncFeatureDisabledViaDashboard());
-  EXPECT_EQ(0, on_sync_feature_disabled_via_dashboard_cleared_count_);
 
+  EXPECT_CALL(delegate_, OnSyncFeatureDisabledViaDashboardCleared);
   sync_user_settings->ClearSyncFeatureDisabledViaDashboard();
   EXPECT_FALSE(sync_user_settings->IsSyncFeatureDisabledViaDashboard());
-  EXPECT_EQ(1, on_sync_feature_disabled_via_dashboard_cleared_count_);
 
   // Calling it for the second time should be harmless (no-op).
+  EXPECT_CALL(delegate_, OnSyncFeatureDisabledViaDashboardCleared).Times(0);
   sync_user_settings->ClearSyncFeatureDisabledViaDashboard();
   EXPECT_FALSE(sync_user_settings->IsSyncFeatureDisabledViaDashboard());
-  EXPECT_EQ(1, on_sync_feature_disabled_via_dashboard_cleared_count_);
+}
+#else   // BUILDFLAG(IS_CHROMEOS)
+TEST_F(SyncUserSettingsImplTest, SetInitialSyncFeatureSetupComplete) {
+  std::unique_ptr<SyncUserSettingsImpl> sync_user_settings =
+      MakeSyncUserSettings(GetUserTypes());
+
+  ASSERT_FALSE(sync_user_settings->IsInitialSyncFeatureSetupComplete());
+
+  EXPECT_CALL(delegate_, OnInitialSyncFeatureSetupCompleted());
+  sync_user_settings->SetInitialSyncFeatureSetupComplete(
+      SyncFirstSetupCompleteSource::BASIC_FLOW);
+
+  EXPECT_TRUE(sync_user_settings->IsInitialSyncFeatureSetupComplete());
 }
 #endif  // BUILDFLAG(IS_CHROMEOS)
 
diff --git a/content/browser/preloading/prefetch/prefetch_features.cc b/content/browser/preloading/prefetch/prefetch_features.cc
index bda7f7b7..70525eda 100644
--- a/content/browser/preloading/prefetch/prefetch_features.cc
+++ b/content/browser/preloading/prefetch/prefetch_features.cc
@@ -80,6 +80,8 @@
 BASE_FEATURE(kPrefetchScheduler,
              "PrefetchScheduler",
              base::FEATURE_DISABLED_BY_DEFAULT);
+const base::FeatureParam<bool> kPrefetchSchedulerProgressSyncBestEffort{
+    &kPrefetchScheduler, "kPrefetchSchedulerProgressSyncBestEffort", true};
 
 BASE_FEATURE(kPrefetchSchedulerTesting,
              "PrefetchSchedulerTesting",
diff --git a/content/browser/preloading/prefetch/prefetch_features.h b/content/browser/preloading/prefetch/prefetch_features.h
index 470189f0..3a68553 100644
--- a/content/browser/preloading/prefetch/prefetch_features.h
+++ b/content/browser/preloading/prefetch/prefetch_features.h
@@ -103,6 +103,10 @@
 // https://docs.google.com/document/d/1W0Nk3Nq6NaUXkBppOUC5zyNmhVqMjYShm1bydGYd9qc
 CONTENT_EXPORT BASE_DECLARE_FEATURE(kPrefetchScheduler);
 
+// Call `PrefetchScheduler::Progress()` synchronously as much as possible.
+CONTENT_EXPORT extern const base::FeatureParam<bool>
+    kPrefetchSchedulerProgressSyncBestEffort;
+
 // Controls params for tests of `PrefetchScheduler`.
 CONTENT_EXPORT BASE_DECLARE_FEATURE(kPrefetchSchedulerTesting);
 CONTENT_EXPORT extern const base::FeatureParam<size_t>
diff --git a/content/browser/preloading/prefetch/prefetch_scheduler.cc b/content/browser/preloading/prefetch/prefetch_scheduler.cc
index 2b69d41..03f44ee 100644
--- a/content/browser/preloading/prefetch/prefetch_scheduler.cc
+++ b/content/browser/preloading/prefetch/prefetch_scheduler.cc
@@ -194,6 +194,20 @@
   return CalculatePriorityImpl(prefetch_container);
 }
 
+void PrefetchScheduler::PushAndProgress(PrefetchContainer& prefetch_container) {
+  // Precondition: Pushing already registered one is not allowed.
+  for (auto& it : active_set_) {
+    if (it.get() == &prefetch_container) {
+      NOTREACHED();
+    }
+  }
+
+  PrefetchPriority priority = CalculatePriority(prefetch_container);
+  queue_.Push(prefetch_container.GetWeakPtr(), priority);
+
+  Progress();
+}
+
 void PrefetchScheduler::PushAndProgressAsync(
     PrefetchContainer& prefetch_container) {
   // Precondition: Pushing already registered one is not allowed.
diff --git a/content/browser/preloading/prefetch/prefetch_scheduler.h b/content/browser/preloading/prefetch/prefetch_scheduler.h
index 4660a102..93ea5f7 100644
--- a/content/browser/preloading/prefetch/prefetch_scheduler.h
+++ b/content/browser/preloading/prefetch/prefetch_scheduler.h
@@ -171,6 +171,7 @@
   // explicitly call `Progress()`.
   //
   // If `should_progress` is false, doesn't call `ProgressAsync()`.
+  void PushAndProgress(PrefetchContainer& prefetch_container);
   void PushAndProgressAsync(PrefetchContainer& prefetch_container);
   // Note that this doesn't call `PrefetchService::ResetPrefetchContainer()`.
   void RemoveAndProgressAsync(PrefetchContainer& prefetch_container,
diff --git a/content/browser/preloading/prefetch/prefetch_service.cc b/content/browser/preloading/prefetch/prefetch_service.cc
index 5f8447c..094f6543 100644
--- a/content/browser/preloading/prefetch/prefetch_service.cc
+++ b/content/browser/preloading/prefetch/prefetch_service.cc
@@ -1192,7 +1192,11 @@
     prefetch_queue_.push_back(std::move(prefetch_container));
     Prefetch();
   } else {
-    ScheduleAndProgress(std::move(prefetch_container));
+    if (features::kPrefetchSchedulerProgressSyncBestEffort.Get()) {
+      ScheduleAndProgress(std::move(prefetch_container));
+    } else {
+      ScheduleAndProgressAsync(std::move(prefetch_container));
+    }
   }
 }
 
@@ -1248,9 +1252,9 @@
         Prefetch();
       }
     } else {
-      // TODO(crbug.com/400761083): Use `ResetPrefetchContainerAndProgress()`
-      // instead.
-      RemoveFromSchedulerAndProgress(*prefetch_container);
+      // TODO(crbug.com/400761083): Use
+      // `ResetPrefetchContainerAndProgressAsync()` instead.
+      RemoveFromSchedulerAndProgressAsync(*prefetch_container);
     }
     return;
   }
@@ -1269,14 +1273,14 @@
       Prefetch();
     } else {
       // Remove first as it requires that `PrefetchContainer` is available.
-      RemoveFromSchedulerAndProgress(*prefetch_container);
+      RemoveFromSchedulerAndProgressAsync(*prefetch_container);
 
       streaming_url_loader->HandleRedirect(PrefetchRedirectStatus::kFail,
                                            redirect_info,
                                            std::move(redirect_head));
 
-      // TODO(crbug.com/400761083): Use `ResetPrefetchContainerAndProgress()`
-      // instead.
+      // TODO(crbug.com/400761083): Use
+      // `ResetPrefetchContainerAndProgressAsync()` instead.
     }
     return;
   }
@@ -1410,7 +1414,7 @@
       Prefetch();
     }
   } else {
-    ResetPrefetchContainerAndProgress(std::move(prefetch_container));
+    ResetPrefetchContainerAndProgressAsync(std::move(prefetch_container));
   }
 }
 
@@ -1434,7 +1438,7 @@
                                     weak_method_factory_.GetWeakPtr()));
     }
   } else {
-    ResetPrefetchContainerAndProgress(std::move(prefetch_container));
+    ResetPrefetchContainerAndProgressAsync(std::move(prefetch_container));
   }
 }
 
@@ -1464,12 +1468,20 @@
   CHECK(UsePrefetchScheduler());
   CHECK(prefetch_container);
 
+  scheduler_->PushAndProgress(*prefetch_container);
+}
+
+void PrefetchService::ScheduleAndProgressAsync(
+    base::WeakPtr<PrefetchContainer> prefetch_container) {
+  CHECK(UsePrefetchScheduler());
+  CHECK(prefetch_container);
+
   scheduler_->PushAndProgressAsync(*prefetch_container);
 
   // `PrefetchScheduler::Progress()` will be called asynchronously.
 }
 
-void PrefetchService::ResetPrefetchContainerAndProgress(
+void PrefetchService::ResetPrefetchContainerAndProgressAsync(
     base::WeakPtr<PrefetchContainer> prefetch_container) {
   CHECK(UsePrefetchScheduler());
 
@@ -1478,7 +1490,7 @@
   // `PrefetchScheduler::Progress()` will be called asynchronously.
 }
 
-void PrefetchService::ResetPrefetchContainersAndProgress(
+void PrefetchService::ResetPrefetchContainersAndProgressAsync(
     std::vector<base::WeakPtr<PrefetchContainer>> prefetch_containers) {
   CHECK(UsePrefetchScheduler());
 
@@ -1489,7 +1501,7 @@
   // `PrefetchScheduler::Progress()` will be called asynchronously.
 }
 
-void PrefetchService::RemoveFromSchedulerAndProgress(
+void PrefetchService::RemoveFromSchedulerAndProgressAsync(
     PrefetchContainer& prefetch_container) {
   CHECK(UsePrefetchScheduler());
 
@@ -1741,7 +1753,7 @@
           PrefetchStatus::kPrefetchFailedInvalidRedirect);
 
       // Remove first as it requires that `PrefetchContainer` is available.
-      RemoveFromSchedulerAndProgress(*prefetch_container);
+      RemoveFromSchedulerAndProgressAsync(*prefetch_container);
 
       if (auto streaming_url_loader =
               prefetch_container->GetStreamingURLLoader()) {
@@ -1750,8 +1762,8 @@
                                              std::move(redirect_head));
       }
 
-      // TODO(crbug.com/400761083): Use `ResetPrefetchContainerAndProgress()`
-      // instead.
+      // TODO(crbug.com/400761083): Use
+      // `ResetPrefetchContainerAndProgressAsync()` instead.
     }
     return;
   }
@@ -1872,7 +1884,7 @@
   } else {
     prefetch_container->OnPrefetchComplete(completion_status);
 
-    RemoveFromSchedulerAndProgress(*prefetch_container);
+    RemoveFromSchedulerAndProgressAsync(*prefetch_container);
   }
 }
 
@@ -2112,7 +2124,7 @@
       ResetPrefetchContainer(prefetch_container);
     }
   } else {
-    ResetPrefetchContainersAndProgress(std::move(prefetches_to_reset));
+    ResetPrefetchContainersAndProgressAsync(std::move(prefetches_to_reset));
   }
 }
 
diff --git a/content/browser/preloading/prefetch/prefetch_service.h b/content/browser/preloading/prefetch/prefetch_service.h
index 7779198..ef30feff 100644
--- a/content/browser/preloading/prefetch/prefetch_service.h
+++ b/content/browser/preloading/prefetch/prefetch_service.h
@@ -444,13 +444,16 @@
 
   // Methods for scheduling
   void ScheduleAndProgress(base::WeakPtr<PrefetchContainer> prefetch_container);
-  void ResetPrefetchContainerAndProgress(
+  void ScheduleAndProgressAsync(
       base::WeakPtr<PrefetchContainer> prefetch_container);
-  void ResetPrefetchContainersAndProgress(
+  void ResetPrefetchContainerAndProgressAsync(
+      base::WeakPtr<PrefetchContainer> prefetch_container);
+  void ResetPrefetchContainersAndProgressAsync(
       std::vector<base::WeakPtr<PrefetchContainer>> prefetch_containers);
   // CAUTION: This doesn't call `ResetPrefetchContainer()` to preserve current
   // behavior.
-  void RemoveFromSchedulerAndProgress(PrefetchContainer& prefetch_container);
+  void RemoveFromSchedulerAndProgressAsync(
+      PrefetchContainer& prefetch_container);
 
   // Returns `true` if the `prefetch_container` is stale. I.e.
   // the prefetch either is not or never will be servable to a
diff --git a/content/browser/preloading/prefetch/prefetch_service_unittest.cc b/content/browser/preloading/prefetch/prefetch_service_unittest.cc
index c729af6..49a1c69 100644
--- a/content/browser/preloading/prefetch/prefetch_service_unittest.cc
+++ b/content/browser/preloading/prefetch/prefetch_service_unittest.cc
@@ -130,11 +130,12 @@
   static PrefetchServiceRearchParam CreateFromIndex(int index);
 
   bool prefetch_scheduler;
+  bool prefetch_scheduler_progress_sync_best_effort;
 };
 
 // static
 std::vector<int> PrefetchServiceRearchParam::Params() {
-  return {0, 1};
+  return {0, 1, 2};
 }
 
 // static
@@ -143,9 +144,15 @@
   std::vector<PrefetchServiceRearchParam> params = {
       PrefetchServiceRearchParam{
           .prefetch_scheduler = false,
+          .prefetch_scheduler_progress_sync_best_effort = false,
       },
       PrefetchServiceRearchParam{
           .prefetch_scheduler = true,
+          .prefetch_scheduler_progress_sync_best_effort = false,
+      },
+      PrefetchServiceRearchParam{
+          .prefetch_scheduler = true,
+          .prefetch_scheduler_progress_sync_best_effort = true,
       },
   };
   return params[index];
@@ -169,7 +176,15 @@
 void WithPrefetchServiceRearchParam::InitRearchFeatures() {
   if (param_.prefetch_scheduler) {
     feature_list_prefetch_scheduler_.InitWithFeaturesAndParameters(
-        {{features::kPrefetchScheduler, {}}}, {});
+        {{
+            features::kPrefetchScheduler,
+            {
+                {"kPrefetchSchedulerProgressSyncBestEffort",
+                 param_.prefetch_scheduler_progress_sync_best_effort ? "true"
+                                                                     : "false"},
+            },
+        }},
+        {});
   }
 }
 
@@ -7663,12 +7678,94 @@
 //
 // Scenario:
 //
-// - A prefetch is triggered.
+// - Two prefetches are triggered.
 // - A prefetch is triggered with high priority.
 // - `PrefetchScheduler` starts the later one.
 TEST_P(PrefetchServiceTest, PrefetchScheduler_Prioritize) {
-  if (!UsePrefetchScheduler()) {
-    GTEST_SKIP() << "Assume PrefetchScheduler";
+  if (!(UsePrefetchScheduler() &&
+        features::kPrefetchSchedulerProgressSyncBestEffort.Get())) {
+    GTEST_SKIP() << "Assume PrefetchScheduler and "
+                    "PrefetchSchedulerProgressSyncBestEffort";
+  }
+
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitWithFeaturesAndParameters(
+      {{features::kPrefetchSchedulerTesting,
+        {{"kPrefetchSchedulerTestingActiveSetSizeLimitForBase", "1"},
+         {"kPrefetchSchedulerTestingActiveSetSizeLimitForBurst", "1"}}}},
+      {});
+
+  NavigateAndCommit(GURL("https://example.com"));
+  MakePrefetchService(
+      std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>(
+          /*num_on_prefetch_likely_calls=*/0));
+  PrefetchService* prefetch_service =
+      BrowserContextImpl::From(browser_context())->GetPrefetchService();
+
+  prefetch_service->GetPrefetchSchedulerForTesting()
+      .SetCalculatePriorityForTesting(
+          base::BindRepeating([](const PrefetchContainer& prefetch_container) {
+            if (prefetch_container.GetURL().possibly_invalid_spec().ends_with(
+                    "?prioritize=1")) {
+              return PrefetchPriority::kHighTest;
+            }
+
+            return PrefetchPriority::kBase;
+          }));
+
+  const auto url_1 = GURL("https://example.com/one");
+  const auto url_2 = GURL("https://example.com/two");
+  const auto url_3 = GURL("https://example.com/two?prioritize=1");
+  auto handle_1 =
+      MakePrefetchFromBrowserContext(url_1, std::nullopt, {}, nullptr);
+  auto handle_2 =
+      MakePrefetchFromBrowserContext(url_2, std::nullopt, {}, nullptr);
+  auto handle_3 =
+      MakePrefetchFromBrowserContext(url_3, std::nullopt, {}, nullptr);
+  task_environment()->RunUntilIdle();
+
+  base::WeakPtr<PrefetchContainer> prefetch_container1, prefetch_container2,
+      prefetch_container3;
+  std::tie(std::ignore, prefetch_container1) =
+      prefetch_service->GetAllForUrlWithoutRefAndQueryForTesting(
+          PrefetchContainer::Key(std::nullopt, url_1))[0];
+  std::tie(std::ignore, prefetch_container2) =
+      prefetch_service->GetAllForUrlWithoutRefAndQueryForTesting(
+          PrefetchContainer::Key(std::nullopt, url_2))[0];
+  std::tie(std::ignore, prefetch_container3) =
+      prefetch_service->GetAllForUrlWithoutRefAndQueryForTesting(
+          PrefetchContainer::Key(std::nullopt, url_3))[0];
+
+  ASSERT_EQ(prefetch_container1->GetLoadState(),
+            PrefetchContainer::LoadState::kStarted);
+  ASSERT_EQ(prefetch_container2->GetLoadState(),
+            PrefetchContainer::LoadState::kEligible);
+  ASSERT_EQ(prefetch_container3->GetLoadState(),
+            PrefetchContainer::LoadState::kEligible);
+
+  handle_1.reset();
+  EXPECT_FALSE(prefetch_container1);
+  // Resolve `PrefetchScheduler::ProgressAsync()`.
+  task_environment()->RunUntilIdle();
+
+  ASSERT_EQ(prefetch_container2->GetLoadState(),
+            PrefetchContainer::LoadState::kEligible);
+  ASSERT_EQ(prefetch_container3->GetLoadState(),
+            PrefetchContainer::LoadState::kStarted);
+}
+
+// Tests prioritizing behavior.
+//
+// Scenario:
+//
+// - A prefetch is triggered.
+// - A prefetch is triggered with high priority.
+// - `PrefetchScheduler` starts the later one.
+TEST_P(PrefetchServiceTest, PrefetchScheduler_Prioritize_Async) {
+  if (!(UsePrefetchScheduler() &&
+        !features::kPrefetchSchedulerProgressSyncBestEffort.Get())) {
+    GTEST_SKIP() << "Assume PrefetchScheduler and not "
+                    "PrefetchSchedulerProgressSyncBestEffort";
   }
 
   base::test::ScopedFeatureList scoped_feature_list;
@@ -7825,13 +7922,122 @@
 //
 // - Two prefetches are triggered.
 // - Two prefetches are triggered with burst.
+// - `PrefetchScheduler` starts the first and third one.
+// - The third one ended.
+// - `PrefetchScheduler` starts the forth one.
+// - The first one ended.
+// - `PrefetchScheduler` doesn't start the second one as
+//   `ActiveSetSizeLimitForBase` is 1.
+TEST_P(PrefetchServiceTest, PrefetchScheduler_BurstTakesPriority) {
+  if (!(UsePrefetchScheduler() &&
+        features::kPrefetchSchedulerProgressSyncBestEffort.Get())) {
+    GTEST_SKIP() << "Assume PrefetchScheduler and "
+                    "PrefetchSchedulerProgressSyncBestEffort";
+  }
+
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitWithFeaturesAndParameters(
+      {
+          {features::kPrefetchSchedulerTesting,
+           {{"kPrefetchSchedulerTestingActiveSetSizeLimitForBase", "1"},
+            {"kPrefetchSchedulerTestingActiveSetSizeLimitForBurst", "2"}}},
+      },
+      {});
+
+  NavigateAndCommit(GURL("https://example.com"));
+  MakePrefetchService(
+      std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>(
+          /*num_on_prefetch_likely_calls=*/0));
+  PrefetchService* prefetch_service =
+      BrowserContextImpl::From(browser_context())->GetPrefetchService();
+
+  prefetch_service->GetPrefetchSchedulerForTesting()
+      .SetCalculatePriorityForTesting(
+          base::BindRepeating([](const PrefetchContainer& prefetch_container) {
+            if (prefetch_container.GetURL().possibly_invalid_spec().ends_with(
+                    "?burst=1")) {
+              return PrefetchPriority::kBurstTest;
+            }
+
+            return PrefetchPriority::kBase;
+          }));
+
+  const auto url_1 = GURL("https://example.com/one");
+  const auto url_2 = GURL("https://example.com/two");
+  const auto url_3 = GURL("https://example.com/three?burst=1");
+  const auto url_4 = GURL("https://example.com/four?burst=1");
+  auto handle_1 =
+      MakePrefetchFromBrowserContext(url_1, std::nullopt, {}, nullptr);
+  auto handle_2 =
+      MakePrefetchFromBrowserContext(url_2, std::nullopt, {}, nullptr);
+  auto handle_3 =
+      MakePrefetchFromBrowserContext(url_3, std::nullopt, {}, nullptr);
+  auto handle_4 =
+      MakePrefetchFromBrowserContext(url_4, std::nullopt, {}, nullptr);
+  task_environment()->RunUntilIdle();
+
+  base::WeakPtr<PrefetchContainer> prefetch_container1, prefetch_container2,
+      prefetch_container3, prefetch_container4;
+  std::tie(std::ignore, prefetch_container1) =
+      prefetch_service->GetAllForUrlWithoutRefAndQueryForTesting(
+          PrefetchContainer::Key(std::nullopt, url_1))[0];
+  std::tie(std::ignore, prefetch_container2) =
+      prefetch_service->GetAllForUrlWithoutRefAndQueryForTesting(
+          PrefetchContainer::Key(std::nullopt, url_2))[0];
+  std::tie(std::ignore, prefetch_container3) =
+      prefetch_service->GetAllForUrlWithoutRefAndQueryForTesting(
+          PrefetchContainer::Key(std::nullopt, url_3))[0];
+  std::tie(std::ignore, prefetch_container4) =
+      prefetch_service->GetAllForUrlWithoutRefAndQueryForTesting(
+          PrefetchContainer::Key(std::nullopt, url_4))[0];
+
+  ASSERT_EQ(prefetch_container1->GetLoadState(),
+            PrefetchContainer::LoadState::kStarted);
+  ASSERT_EQ(prefetch_container2->GetLoadState(),
+            PrefetchContainer::LoadState::kEligible);
+  ASSERT_EQ(prefetch_container3->GetLoadState(),
+            PrefetchContainer::LoadState::kStarted);
+  ASSERT_EQ(prefetch_container4->GetLoadState(),
+            PrefetchContainer::LoadState::kEligible);
+
+  handle_3.reset();
+  EXPECT_FALSE(prefetch_container3);
+  // Resolve `PrefetchScheduler::ProgressAsync()`.
+  task_environment()->RunUntilIdle();
+
+  ASSERT_EQ(prefetch_container1->GetLoadState(),
+            PrefetchContainer::LoadState::kStarted);
+  ASSERT_EQ(prefetch_container2->GetLoadState(),
+            PrefetchContainer::LoadState::kEligible);
+  ASSERT_EQ(prefetch_container4->GetLoadState(),
+            PrefetchContainer::LoadState::kStarted);
+
+  handle_1.reset();
+  EXPECT_FALSE(prefetch_container1);
+  // Resolve `PrefetchScheduler::ProgressAsync()`.
+  task_environment()->RunUntilIdle();
+
+  ASSERT_EQ(prefetch_container2->GetLoadState(),
+            PrefetchContainer::LoadState::kEligible);
+  ASSERT_EQ(prefetch_container4->GetLoadState(),
+            PrefetchContainer::LoadState::kStarted);
+}
+
+// Tests bursting behavior.
+//
+// Scenario:
+//
+// - Two prefetches are triggered.
+// - Two prefetches are triggered with burst.
 // - `PrefetchScheduler` starts the third and fourth one.
 // - The third one ended.
 // - `PrefetchScheduler` doesn't start the first/second one as
 //   `ActiveSetSizeLimitForBase` is 1.
-TEST_P(PrefetchServiceTest, PrefetchScheduler_BurstTakesPriority) {
-  if (!UsePrefetchScheduler()) {
-    GTEST_SKIP() << "Assume PrefetchScheduler";
+TEST_P(PrefetchServiceTest, PrefetchScheduler_BurstTakesPriority_Async) {
+  if (!(UsePrefetchScheduler() &&
+        !features::kPrefetchSchedulerProgressSyncBestEffort.Get())) {
+    GTEST_SKIP() << "Assume PrefetchScheduler and not "
+                    "PrefetchSchedulerProgressSyncBestEffort";
   }
 
   base::test::ScopedFeatureList scoped_feature_list;
diff --git a/internal b/internal
index 5c45e13..281dbba 160000
--- a/internal
+++ b/internal
@@ -1 +1 @@
-Subproject commit 5c45e13e948d0c028bd62c61ea316ec5d4e16ce1
+Subproject commit 281dbba7538a783d11627f49508322558a5f706e
diff --git a/ios/chrome/app/strings/ios_chromium_strings.grd b/ios/chrome/app/strings/ios_chromium_strings.grd
index 7d2e892..b4c7b5c 100644
--- a/ios/chrome/app/strings/ios_chromium_strings.grd
+++ b/ios/chrome/app/strings/ios_chromium_strings.grd
@@ -727,6 +727,12 @@
       <message name="IDS_IOS_NOTIFICATIONS_OPT_IN_TIPS_SETTINGS_TOGGLE_MESSSAGE" desc="The label for the notification opt-in toggle message for tips notifications.">
         Tips on getting the most out of Chromium.
       </message>
+      <message name="IDS_IOS_NOTIFICATIONS_TIPS_CPE_BODY" desc="Text for the body of a Tips notification that encourages the user to enable the CPE.">
+        To easily get your saved passwords in other apps, chose Chromium for Autofill.
+      </message>
+      <message name="IDS_IOS_NOTIFICATIONS_TIPS_CPE_TITLE" desc="Text for the title of a Tips notification that encourages the user to enable the CPE.">
+        Chromium Tip: Get your passwords in any app
+      </message>
       <message name="IDS_IOS_NOTIFICATIONS_TIPS_DEFAULT_BROWSER_BODY" desc="Text for the body of a Tips notification that encourages the user to set the app as their default browser">
         Use Chromium anytime you tap links in messages or other apps.
       </message>
diff --git a/ios/chrome/app/strings/ios_chromium_strings_grd/IDS_IOS_NOTIFICATIONS_TIPS_CPE_BODY.png.sha1 b/ios/chrome/app/strings/ios_chromium_strings_grd/IDS_IOS_NOTIFICATIONS_TIPS_CPE_BODY.png.sha1
new file mode 100644
index 0000000..11d80d9
--- /dev/null
+++ b/ios/chrome/app/strings/ios_chromium_strings_grd/IDS_IOS_NOTIFICATIONS_TIPS_CPE_BODY.png.sha1
@@ -0,0 +1 @@
+4c5b945a6488de7d8574a5d45029d20066c08f5e
\ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_chromium_strings_grd/IDS_IOS_NOTIFICATIONS_TIPS_CPE_TITLE.png.sha1 b/ios/chrome/app/strings/ios_chromium_strings_grd/IDS_IOS_NOTIFICATIONS_TIPS_CPE_TITLE.png.sha1
new file mode 100644
index 0000000..11d80d9
--- /dev/null
+++ b/ios/chrome/app/strings/ios_chromium_strings_grd/IDS_IOS_NOTIFICATIONS_TIPS_CPE_TITLE.png.sha1
@@ -0,0 +1 @@
+4c5b945a6488de7d8574a5d45029d20066c08f5e
\ 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 9aa545a..6dddee73 100644
--- a/ios/chrome/app/strings/ios_google_chrome_strings.grd
+++ b/ios/chrome/app/strings/ios_google_chrome_strings.grd
@@ -727,6 +727,12 @@
       <message name="IDS_IOS_NOTIFICATIONS_OPT_IN_TIPS_SETTINGS_TOGGLE_MESSSAGE" desc="The label for the notification opt-in toggle message for tips notifications.">
         Tips on getting the most out of Chrome.
       </message>
+      <message name="IDS_IOS_NOTIFICATIONS_TIPS_CPE_BODY" desc="Text for the body of a Tips notification that encourages the user to enable the CPE.">
+        To easily get your saved passwords in other apps, chose Chrome for Autofill.
+      </message>
+      <message name="IDS_IOS_NOTIFICATIONS_TIPS_CPE_TITLE" desc="Text for the title of a Tips notification that encourages the user to enable the CPE.">
+        Chrome Tip: Get your passwords in any app
+      </message>
       <message name="IDS_IOS_NOTIFICATIONS_TIPS_DEFAULT_BROWSER_BODY" desc="Text for the body of a Tips notification that encourages the user to set the app as their default browser">
         Use Chrome anytime you tap links in messages or other apps.
       </message>
diff --git a/ios/chrome/app/strings/ios_google_chrome_strings_grd/IDS_IOS_NOTIFICATIONS_TIPS_CPE_BODY.png.sha1 b/ios/chrome/app/strings/ios_google_chrome_strings_grd/IDS_IOS_NOTIFICATIONS_TIPS_CPE_BODY.png.sha1
new file mode 100644
index 0000000..11d80d9
--- /dev/null
+++ b/ios/chrome/app/strings/ios_google_chrome_strings_grd/IDS_IOS_NOTIFICATIONS_TIPS_CPE_BODY.png.sha1
@@ -0,0 +1 @@
+4c5b945a6488de7d8574a5d45029d20066c08f5e
\ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_google_chrome_strings_grd/IDS_IOS_NOTIFICATIONS_TIPS_CPE_TITLE.png.sha1 b/ios/chrome/app/strings/ios_google_chrome_strings_grd/IDS_IOS_NOTIFICATIONS_TIPS_CPE_TITLE.png.sha1
new file mode 100644
index 0000000..11d80d9
--- /dev/null
+++ b/ios/chrome/app/strings/ios_google_chrome_strings_grd/IDS_IOS_NOTIFICATIONS_TIPS_CPE_TITLE.png.sha1
@@ -0,0 +1 @@
+4c5b945a6488de7d8574a5d45029d20066c08f5e
\ No newline at end of file
diff --git a/ios/chrome/browser/authentication/ui_bundled/enterprise/managed_profile_creation/browsing_data_migration_view_controller.mm b/ios/chrome/browser/authentication/ui_bundled/enterprise/managed_profile_creation/browsing_data_migration_view_controller.mm
index 4673d4ac..a19cbd1 100644
--- a/ios/chrome/browser/authentication/ui_bundled/enterprise/managed_profile_creation/browsing_data_migration_view_controller.mm
+++ b/ios/chrome/browser/authentication/ui_bundled/enterprise/managed_profile_creation/browsing_data_migration_view_controller.mm
@@ -126,8 +126,10 @@
   cell.accessoryType = UITableViewCellAccessoryNone;
   cell.textLabel.text = title;
   cell.detailTextLabel.text = details;
-  cell.selectionStyle = UITableViewCellSelectionStyleDefault;
-  cell.backgroundColor = [UIColor colorNamed:kPrimaryBackgroundColor];
+  cell.selectionStyle = UITableViewCellSelectionStyleNone;
+  cell.backgroundColor = selected
+                             ? [UIColor colorNamed:kBlueHaloColor]
+                             : [UIColor colorNamed:kPrimaryBackgroundColor];
   cell.separatorInset =
       UIEdgeInsetsMake(0.f, kTableViewSeparatorInsetHide, 0.f, 0.f);
   cell.accessibilityIdentifier = accessibilityIdentifier;
diff --git a/ios/chrome/browser/autofill/ui_bundled/BUILD.gn b/ios/chrome/browser/autofill/ui_bundled/BUILD.gn
index 5e32bae..98c473f0 100644
--- a/ios/chrome/browser/autofill/ui_bundled/BUILD.gn
+++ b/ios/chrome/browser/autofill/ui_bundled/BUILD.gn
@@ -41,7 +41,7 @@
     "//ios/chrome/browser/autofill/model/credit_card:infobar_delegate",
     "//ios/chrome/browser/autofill/ui_bundled/bottom_sheet:virtual_card_enrollment_bottom_sheet_coordinator",
     "//ios/chrome/browser/autofill/ui_bundled/cells",
-    "//ios/chrome/browser/autofill/ui_bundled/manual_fill:manual_fill",
+    "//ios/chrome/browser/autofill/ui_bundled/manual_fill",
     "//ios/chrome/browser/autofill/ui_bundled/manual_fill:manual_fill_ui",
     "//ios/chrome/browser/device_reauth/model",
     "//ios/chrome/browser/history/model",
@@ -51,8 +51,8 @@
     "//ios/chrome/browser/plus_addresses/model",
     "//ios/chrome/browser/shared/model/application_context",
     "//ios/chrome/browser/shared/model/profile",
-    "//ios/chrome/browser/shared/model/web_state_list:web_state_list",
-    "//ios/chrome/browser/shared/public/commands:commands",
+    "//ios/chrome/browser/shared/model/web_state_list",
+    "//ios/chrome/browser/shared/public/commands",
     "//ios/chrome/browser/shared/public/features",
     "//ios/chrome/browser/shared/ui/util",
     "//ios/chrome/browser/signin/model",
@@ -60,7 +60,7 @@
     "//ios/chrome/browser/signin/model:authentication_service_factory",
     "//ios/chrome/browser/ssl/model",
     "//ios/chrome/browser/sync/model",
-    "//ios/chrome/browser/translate/model:model",
+    "//ios/chrome/browser/translate/model",
     "//ios/chrome/browser/webdata_services/model",
     "//ios/chrome/common",
     "//ios/chrome/common/ui/elements:form_input_accessory",
@@ -91,7 +91,7 @@
     "//base",
     "//components/autofill/core/browser",
     "//components/strings",
-    "//ios/chrome/browser/autofill/model/credit_card:credit_card",
+    "//ios/chrome/browser/autofill/model/credit_card",
     "//ios/chrome/browser/autofill/ui_bundled/cells",
     "//ios/chrome/browser/net/model:crurl",
     "//ios/chrome/browser/shared/public/commands",
@@ -230,10 +230,10 @@
     "//ios/chrome/browser/shared/model/application_context",
     "//ios/chrome/browser/shared/model/profile",
     "//ios/chrome/browser/shared/public/features",
-    "//ios/chrome/common/ui/reauthentication:reauthentication",
+    "//ios/chrome/common/ui/reauthentication",
     "//ios/chrome/test/app:test_support",
     "//ios/public/provider/chrome/browser/risk_data:risk_data_api",
-    "//ios/web/public:public",
+    "//ios/web/public",
     "//ios/web/public/js_messaging",
     "//services/network:test_support",
   ]
@@ -248,7 +248,7 @@
   ]
   deps = [
     "//base",
-    "//ios/chrome/common/ui/reauthentication:reauthentication",
+    "//ios/chrome/common/ui/reauthentication",
     "//ios/testing/earl_grey:eg_test_support+eg2",
   ]
 }
@@ -293,7 +293,7 @@
     "//ios/chrome/browser/webdata_services/model",
     "//ios/chrome/common/ui/colors",
     "//ios/chrome/test:test_support",
-    "//ios/web/public/js_messaging:js_messaging",
+    "//ios/web/public/js_messaging",
     "//ios/web/public/test",
     "//testing/gmock",
     "//testing/gtest",
diff --git a/ios/chrome/browser/autofill/ui_bundled/address_editor/BUILD.gn b/ios/chrome/browser/autofill/ui_bundled/address_editor/BUILD.gn
index 396ec3f..3c7cdc2 100644
--- a/ios/chrome/browser/autofill/ui_bundled/address_editor/BUILD.gn
+++ b/ios/chrome/browser/autofill/ui_bundled/address_editor/BUILD.gn
@@ -27,12 +27,12 @@
   ]
   deps = [
     ":constants",
-    "//base:base",
+    "//base",
     "//components/autofill/core/browser",
     "//components/autofill/core/common:features",
     "//components/autofill/ios/common",
     "//components/strings:components_strings_grit",
-    "//components/variations/service:service",
+    "//components/variations/service",
     "//ios/chrome/app/strings",
     "//ios/chrome/browser/autofill/ui_bundled:ui_type",
     "//ios/chrome/browser/autofill/ui_bundled/address_editor/cells",
@@ -68,7 +68,7 @@
     "//components/autofill/core/common:features",
     "//components/autofill/ios/common",
     "//components/strings:components_strings_grit",
-    "//components/variations/service:service",
+    "//components/variations/service",
     "//ios/chrome/app/strings:ios_strings_grit",
     "//ios/chrome/browser/autofill/model",
     "//ios/chrome/browser/autofill/ui_bundled/address_editor/cells",
@@ -76,9 +76,9 @@
     "//ios/chrome/browser/shared/model/profile/test",
     "//ios/chrome/browser/shared/ui/list_model",
     "//ios/chrome/browser/shared/ui/table_view:test_support",
-    "//ios/chrome/browser/shared/ui/table_view/cells:cells",
+    "//ios/chrome/browser/shared/ui/table_view/cells",
     "//ios/chrome/browser/webdata_services/model",
-    "//ios/chrome/common/ui/colors:colors",
+    "//ios/chrome/common/ui/colors",
     "//ios/chrome/test:test_support",
     "//ios/web/public/test",
     "//testing/gtest",
diff --git a/ios/chrome/browser/autofill/ui_bundled/authentication/BUILD.gn b/ios/chrome/browser/autofill/ui_bundled/authentication/BUILD.gn
index b540928a..2aabc68 100644
--- a/ios/chrome/browser/autofill/ui_bundled/authentication/BUILD.gn
+++ b/ios/chrome/browser/autofill/ui_bundled/authentication/BUILD.gn
@@ -12,7 +12,7 @@
   ]
   deps = [
     ":otp_input_dialog_ui",
-    "//base:base",
+    "//base",
     "//components/autofill/core/browser",
     "//ios/chrome/browser/autofill/model:model_internal",
     "//ios/chrome/browser/autofill/ui_bundled:coordinator",
@@ -144,10 +144,10 @@
   deps = [
     ":otp_input_dialog",
     ":otp_input_dialog_ui",
-    "//base:base",
-    "//components/autofill/core/browser:browser",
-    "//testing/gmock:gmock",
-    "//testing/gtest:gtest",
+    "//base",
+    "//components/autofill/core/browser",
+    "//testing/gmock",
+    "//testing/gtest",
     "//third_party/ocmock",
   ]
 }
diff --git a/ios/chrome/browser/autofill/ui_bundled/bottom_sheet/BUILD.gn b/ios/chrome/browser/autofill/ui_bundled/bottom_sheet/BUILD.gn
index 7f54e67..fedcb52 100644
--- a/ios/chrome/browser/autofill/ui_bundled/bottom_sheet/BUILD.gn
+++ b/ios/chrome/browser/autofill/ui_bundled/bottom_sheet/BUILD.gn
@@ -105,15 +105,15 @@
     "//ios/chrome/browser/autofill/ui_bundled/address_editor:constants",
     "//ios/chrome/browser/shared/ui/bottom_sheet:table_view_bottom_sheet_view_controller",
     "//ios/chrome/browser/shared/ui/symbols",
+    "//ios/chrome/browser/shared/ui/table_view",
     "//ios/chrome/browser/shared/ui/table_view:styler",
-    "//ios/chrome/browser/shared/ui/table_view:table_view",
     "//ios/chrome/browser/shared/ui/table_view:utils",
     "//ios/chrome/browser/shared/ui/table_view/cells",
     "//ios/chrome/browser/shared/ui/util",
     "//ios/chrome/common/ui/colors",
     "//ios/chrome/common/ui/confirmation_alert",
     "//ios/chrome/common/ui/table_view:cells_constants",
-    "//ui/base:base",
+    "//ui/base",
     "//url",
   ]
 }
@@ -344,7 +344,7 @@
     "//ios/chrome/browser/shared/model/profile",
     "//ios/chrome/browser/shared/model/web_state_list",
     "//ios/chrome/browser/shared/public/commands",
-    "//ios/web/public:public",
+    "//ios/web/public",
   ]
   frameworks = [ "UIKit.framework" ]
 }
@@ -455,10 +455,10 @@
     "//ios/chrome/browser/shared/ui/symbols",
     "//ios/chrome/browser/shared/ui/table_view/cells",
     "//ios/chrome/browser/shared/ui/util",
-    "//ios/chrome/common/ui/colors:colors",
-    "//ios/chrome/common/ui/confirmation_alert:confirmation_alert",
-    "//ios/chrome/common/ui/util:util",
-    "//url:url",
+    "//ios/chrome/common/ui/colors",
+    "//ios/chrome/common/ui/confirmation_alert",
+    "//ios/chrome/common/ui/util",
+    "//url",
   ]
   frameworks = [ "UIKit.framework" ]
 }
@@ -475,7 +475,7 @@
   deps = [
     "//components/autofill/core/browser:test_support",
     "//components/autofill/ios/browser:autofill_test_bundle_data",
-    "//components/strings:strings",
+    "//components/strings",
     "//components/url_formatter",
     "//ios/chrome/app/strings",
     "//ios/chrome/browser/autofill/ui_bundled:eg_test_support+eg2",
diff --git a/ios/chrome/browser/autofill/ui_bundled/cells/BUILD.gn b/ios/chrome/browser/autofill/ui_bundled/cells/BUILD.gn
index 993d1879..40c4117 100644
--- a/ios/chrome/browser/autofill/ui_bundled/cells/BUILD.gn
+++ b/ios/chrome/browser/autofill/ui_bundled/cells/BUILD.gn
@@ -19,7 +19,7 @@
   deps = [
     "//base",
     "//build:branding_buildflags",
-    "//components/autofill/core/common:common",
+    "//components/autofill/core/common",
     "//components/resources:components_scaled_resources_grit",
     "//components/strings",
     "//ios/chrome/browser/autofill/ui_bundled:ui",
@@ -47,7 +47,7 @@
     ":cells",
     "//base",
     "//base/test:test_support",
-    "//components/autofill/core/common:common",
+    "//components/autofill/core/common",
     "//ios/chrome/browser/autofill/ui_bundled:ui",
     "//ios/chrome/browser/shared/ui/table_view:styler",
     "//testing/gtest",
diff --git a/ios/chrome/browser/autofill/ui_bundled/error_dialog/BUILD.gn b/ios/chrome/browser/autofill/ui_bundled/error_dialog/BUILD.gn
index c8c9d93..a6ea4f0d 100644
--- a/ios/chrome/browser/autofill/ui_bundled/error_dialog/BUILD.gn
+++ b/ios/chrome/browser/autofill/ui_bundled/error_dialog/BUILD.gn
@@ -11,7 +11,7 @@
     "autofill_error_dialog_mediator_delegate.h",
   ]
   deps = [
-    "//base:base",
+    "//base",
     "//components/autofill/core/browser",
     "//ios/chrome/browser/shared/coordinator/chrome_coordinator",
     "//ios/chrome/browser/shared/model/browser",
diff --git a/ios/chrome/browser/autofill/ui_bundled/form_input_accessory/BUILD.gn b/ios/chrome/browser/autofill/ui_bundled/form_input_accessory/BUILD.gn
index 3eb067e..ca351cb 100644
--- a/ios/chrome/browser/autofill/ui_bundled/form_input_accessory/BUILD.gn
+++ b/ios/chrome/browser/autofill/ui_bundled/form_input_accessory/BUILD.gn
@@ -34,8 +34,8 @@
     "//components/prefs",
     "//components/strings",
     "//ios/chrome/app/strings",
+    "//ios/chrome/browser/autofill/model",
     "//ios/chrome/browser/autofill/model:features",
-    "//ios/chrome/browser/autofill/model:model",
     "//ios/chrome/browser/autofill/model:model_internal",
     "//ios/chrome/browser/autofill/model:model_shared",
     "//ios/chrome/browser/autofill/model/bottom_sheet",
@@ -65,7 +65,7 @@
     "//ios/chrome/browser/shared/ui/util",
     "//ios/chrome/browser/shared/ui/util:util_swift",
     "//ios/chrome/browser/toolbar/ui_bundled/public",
-    "//ios/chrome/common/ui/colors:colors",
+    "//ios/chrome/common/ui/colors",
     "//ios/chrome/common/ui/elements:form_input_accessory",
     "//ios/chrome/common/ui/reauthentication",
     "//ios/chrome/common/ui/util",
diff --git a/ios/chrome/browser/autofill/ui_bundled/manual_fill/BUILD.gn b/ios/chrome/browser/autofill/ui_bundled/manual_fill/BUILD.gn
index 66d27e3..0c550f3 100644
--- a/ios/chrome/browser/autofill/ui_bundled/manual_fill/BUILD.gn
+++ b/ios/chrome/browser/autofill/ui_bundled/manual_fill/BUILD.gn
@@ -78,11 +78,11 @@
     "//ios/chrome/browser/shared/model/application_context",
     "//ios/chrome/browser/shared/model/browser",
     "//ios/chrome/browser/shared/model/profile",
-    "//ios/chrome/browser/shared/model/web_state_list:web_state_list",
+    "//ios/chrome/browser/shared/model/web_state_list",
     "//ios/chrome/browser/shared/public/commands",
     "//ios/chrome/browser/shared/public/features",
-    "//ios/chrome/browser/shared/ui/list_model:list_model",
-    "//ios/chrome/browser/shared/ui/table_view:table_view",
+    "//ios/chrome/browser/shared/ui/list_model",
+    "//ios/chrome/browser/shared/ui/table_view",
     "//ios/chrome/browser/shared/ui/util",
     "//ios/chrome/browser/signin/model:authentication_service",
     "//ios/chrome/browser/signin/model:authentication_service_factory",
@@ -177,10 +177,10 @@
     "//ios/chrome/browser/shared/model/application_context",
     "//ios/chrome/browser/shared/public/features",
     "//ios/chrome/browser/shared/ui/elements",
-    "//ios/chrome/browser/shared/ui/list_model:list_model",
+    "//ios/chrome/browser/shared/ui/list_model",
     "//ios/chrome/browser/shared/ui/symbols",
+    "//ios/chrome/browser/shared/ui/table_view",
     "//ios/chrome/browser/shared/ui/table_view:styler",
-    "//ios/chrome/browser/shared/ui/table_view:table_view",
     "//ios/chrome/browser/shared/ui/table_view:utils",
     "//ios/chrome/common:string_util",
     "//ios/chrome/common/ui/elements:branded_navigation_item_title_view",
@@ -188,8 +188,8 @@
     "//ios/chrome/common/ui/favicon",
     "//ios/chrome/common/ui/util",
     "//ios/third_party/material_components_ios",
-    "//net:net",
-    "//ui/base:base",
+    "//net",
+    "//ui/base",
   ]
   frameworks = [ "UIKit.framework" ]
 }
@@ -237,9 +237,9 @@
     "//components/autofill/core/common",
     "//components/autofill/core/common:credit_card_number_validation",
     "//components/autofill/ios/browser",
-    "//components/autofill/ios/form_util:form_util",
+    "//components/autofill/ios/form_util",
     "//components/autofill/ios/form_util:test_support",
-    "//components/leveldb_proto:leveldb_proto",
+    "//components/leveldb_proto",
     "//components/password_manager/core/browser",
     "//components/password_manager/core/browser:test_support",
     "//components/prefs",
@@ -258,7 +258,7 @@
     "//ios/chrome/browser/shared/model/browser/test:test_support",
     "//ios/chrome/browser/shared/model/profile",
     "//ios/chrome/browser/shared/model/profile/test",
-    "//ios/chrome/browser/shared/model/web_state_list:web_state_list",
+    "//ios/chrome/browser/shared/model/web_state_list",
     "//ios/chrome/browser/shared/model/web_state_list/test:test_support",
     "//ios/chrome/browser/shared/public/commands",
     "//ios/chrome/browser/shared/public/features",
@@ -274,9 +274,9 @@
     "//ios/web/public/js_messaging",
     "//ios/web/public/test",
     "//ios/web/public/test/fakes",
-    "//testing/gtest:gtest",
-    "//third_party/ocmock:ocmock",
-    "//url:url",
+    "//testing/gtest",
+    "//third_party/ocmock",
+    "//url",
   ]
 }
 
@@ -295,8 +295,8 @@
     "//ios/chrome/browser/autofill/model",
     "//ios/chrome/browser/autofill/ui_bundled:bridges",
     "//ios/chrome/browser/shared/model/profile",
-    "//ios/chrome/browser/shared/model/web_state_list:web_state_list",
-    "//ios/web/public:public",
+    "//ios/chrome/browser/shared/model/web_state_list",
+    "//ios/web/public",
     "//ios/web/public/js_messaging",
     "//ui/base",
   ]
diff --git a/ios/chrome/browser/autofill/ui_bundled/progress_dialog/BUILD.gn b/ios/chrome/browser/autofill/ui_bundled/progress_dialog/BUILD.gn
index 6ad0da3..3faac1e 100644
--- a/ios/chrome/browser/autofill/ui_bundled/progress_dialog/BUILD.gn
+++ b/ios/chrome/browser/autofill/ui_bundled/progress_dialog/BUILD.gn
@@ -11,7 +11,7 @@
     "autofill_progress_dialog_mediator_delegate.h",
   ]
   deps = [
-    "//base:base",
+    "//base",
     "//components/autofill/core/browser",
     "//components/strings",
     "//ios/chrome/browser/alert_view/ui_bundled",
@@ -36,7 +36,7 @@
     "//components/autofill/ios/browser:test_support",
     "//components/strings",
     "//ios/chrome/browser/alert_view/ui_bundled",
-    "//ios/chrome/browser/shared/ui/symbols:symbols",
+    "//ios/chrome/browser/shared/ui/symbols",
     "//testing/gtest",
     "//third_party/ocmock",
   ]
@@ -52,7 +52,7 @@
     "//ios/chrome/app/strings",
     "//ios/chrome/browser/autofill/ui_bundled:autofill_ui_constants",
     "//ios/chrome/browser/autofill/ui_bundled:eg_test_support+eg2",
-    "//ios/chrome/browser/shared/ui/symbols:symbols",
+    "//ios/chrome/browser/shared/ui/symbols",
     "//ios/chrome/test/earl_grey:eg_test_support+eg2",
     "//ios/testing/earl_grey:eg_test_support+eg2",
     "//net:test_support",
diff --git a/ios/chrome/browser/credential_provider_promo/ui_bundled/credential_provider_promo_constants.h b/ios/chrome/browser/credential_provider_promo/ui_bundled/credential_provider_promo_constants.h
index 376c33d..7d2b9539 100644
--- a/ios/chrome/browser/credential_provider_promo/ui_bundled/credential_provider_promo_constants.h
+++ b/ios/chrome/browser/credential_provider_promo/ui_bundled/credential_provider_promo_constants.h
@@ -8,13 +8,16 @@
 // Persisted in local state and used as Enum for
 // IOS.CredentialProviderExtension.Promo.Impression histogram. Entries should
 // not be renumbered and numeric values should never be reused. Entries 1 and 2
-// have been depcrated.
+// have been deprecated.
 // LINT.IfChange
 enum class IOSCredentialProviderPromoSource {
   kUnknown = 0,
+  //  Removed: kPasswordCopied = 1,
+  //  Removed: kPasswordSaved = 2,
   kAutofillUsed = 3,
   kSetUpList = 4,
-  kMaxValue = kSetUpList,
+  kTipsNotification = 5,
+  kMaxValue = kTipsNotification,
 };
 // LINT.ThenChange(/tools/metrics/histograms/metadata/ios/enums.xml)
 
diff --git a/ios/chrome/browser/credential_provider_promo/ui_bundled/credential_provider_promo_mediator.mm b/ios/chrome/browser/credential_provider_promo/ui_bundled/credential_provider_promo_mediator.mm
index e0bc74f..1d553d8 100644
--- a/ios/chrome/browser/credential_provider_promo/ui_bundled/credential_provider_promo_mediator.mm
+++ b/ios/chrome/browser/credential_provider_promo/ui_bundled/credential_provider_promo_mediator.mm
@@ -111,8 +111,10 @@
             (CredentialProviderPromoTrigger)trigger
                                         promoSeen:
                                             (BOOL)promoSeenInCurrentSession {
-  if (trigger == CredentialProviderPromoTrigger::SetUpList) {
-    // Always allow showing when triggered by user via the SetUpList.
+  if (trigger == CredentialProviderPromoTrigger::SetUpList ||
+      trigger == CredentialProviderPromoTrigger::TipsNotification) {
+    // Always allow showing when triggered by user via the SetUpList or Tips
+    // Notification.
     return YES;
   }
   BOOL impressionLimitMet =
@@ -155,6 +157,10 @@
       source = IOSCredentialProviderPromoSource::kSetUpList;
       [self setAnimation];
       break;
+    case CredentialProviderPromoTrigger::TipsNotification:
+      source = IOSCredentialProviderPromoSource::kTipsNotification;
+      [self setAnimation];
+      break;
   }
 
   [self setTextAndImageWithSource:source];
diff --git a/ios/chrome/browser/credential_provider_promo/ui_bundled/credential_provider_promo_metrics.mm b/ios/chrome/browser/credential_provider_promo/ui_bundled/credential_provider_promo_metrics.mm
index 42187d6..d7af02111 100644
--- a/ios/chrome/browser/credential_provider_promo/ui_bundled/credential_provider_promo_metrics.mm
+++ b/ios/chrome/browser/credential_provider_promo/ui_bundled/credential_provider_promo_metrics.mm
@@ -22,6 +22,10 @@
     "IOS.CredentialProviderExtension.Promo.OnSetUpList.IsReminder";
 const char kIOSCredentialProviderPromoOnSetUpListHistogram[] =
     "IOS.CredentialProviderExtension.Promo.OnSetUpList";
+const char kIOSCredentialProviderPromoOnTipsNotificationIsReminderHistogram[] =
+    "IOS.CredentialProviderExtension.Promo.OnTipsNotification.IsReminder";
+const char kIOSCredentialProviderPromoOnTipsNotificationHistogram[] =
+    "IOS.CredentialProviderExtension.Promo.OnTipsNotification";
 
 namespace credential_provider_promo {
 
@@ -51,6 +55,12 @@
                  ? kIOSCredentialProviderPromoOnSetUpListIsReminderHistogram
                  : kIOSCredentialProviderPromoOnSetUpListHistogram;
       break;
+    case IOSCredentialProviderPromoSource::kTipsNotification:
+      name =
+          is_reminder
+              ? kIOSCredentialProviderPromoOnTipsNotificationIsReminderHistogram
+              : kIOSCredentialProviderPromoOnTipsNotificationHistogram;
+      break;
     case IOSCredentialProviderPromoSource::kUnknown:
       NOTREACHED();
   }
diff --git a/ios/chrome/browser/lens_overlay/coordinator/lens_overlay_mediator.mm b/ios/chrome/browser/lens_overlay/coordinator/lens_overlay_mediator.mm
index 21a4dce..3f18bbb 100644
--- a/ios/chrome/browser/lens_overlay/coordinator/lens_overlay_mediator.mm
+++ b/ios/chrome/browser/lens_overlay/coordinator/lens_overlay_mediator.mm
@@ -228,6 +228,7 @@
   [self.resultConsumer handleSearchRequestStarted];
   _lensStartSearchRequestTime = base::ElapsedTimer();
   [self.toolbarConsumer setOmniboxEnabled:YES];
+  [self defocusOmnibox];
 
   // If the filter is still unknown it means this is the first request, so
   // nothing needs to be done, as the selection area in the zero state is
diff --git a/ios/chrome/browser/lens_overlay/coordinator/lens_overlay_mediator_unittest.mm b/ios/chrome/browser/lens_overlay/coordinator/lens_overlay_mediator_unittest.mm
index dcca206c..c76ecffa 100644
--- a/ios/chrome/browser/lens_overlay/coordinator/lens_overlay_mediator_unittest.mm
+++ b/ios/chrome/browser/lens_overlay/coordinator/lens_overlay_mediator_unittest.mm
@@ -167,7 +167,7 @@
     OCMExpect([mock_toolbar_consumer_ setCanGoBack:expectCanGoBack]);
     OCMExpect([mock_toolbar_consumer_ setOmniboxEnabled:YES]);
     OCMExpect([mock_toolbar_consumer_ setOmniboxEnabled:YES]);
-
+    ExpectOmniboxDefocus();
     fake_chrome_lens_overlay_.resultURL = resultURL;
     [mediator_ omniboxDidAcceptText:omniboxText
                      destinationURL:omniboxURL
@@ -183,6 +183,7 @@
   /// Simulates new lens selection and returns the generated result.
   id<ChromeLensOverlayResult> UpdateLensSelection(const GURL& resultURL,
                                                   BOOL expectCanGoBack) {
+    ExpectOmniboxDefocus();
     OCMExpect([mock_omnibox_coordinator_ setThumbnailImage:[OCMArg any]]);
     OCMExpect([mock_omnibox_coordinator_ updateOmniboxState]);
     OCMExpect([mock_toolbar_consumer_ setCanGoBack:expectCanGoBack]);
@@ -236,6 +237,7 @@
               BOOL expectCanGoBack,
               id<ChromeLensOverlayResult> expectedResultReload) {
     // Expect UI update when starting to go back and on navigation start.
+    ExpectOmniboxDefocus();
     OCMExpect([mock_omnibox_coordinator_ updateOmniboxState]);
     OCMExpect([mock_omnibox_coordinator_ updateOmniboxState]);
     OCMExpect([mock_toolbar_consumer_ setCanGoBack:expectCanGoBack]);
diff --git a/ios/chrome/browser/lens_overlay/ui/lens_overlay_container_view_controller.mm b/ios/chrome/browser/lens_overlay/ui/lens_overlay_container_view_controller.mm
index af8f3e8b..447bdb7 100644
--- a/ios/chrome/browser/lens_overlay/ui/lens_overlay_container_view_controller.mm
+++ b/ios/chrome/browser/lens_overlay/ui/lens_overlay_container_view_controller.mm
@@ -68,6 +68,11 @@
 
 - (void)viewDidLoad {
   [super viewDidLoad];
+  // To ensure the elements within this side panel adapt properly to its limited
+  // width, explicitly set its horizontal size class to compact.
+  if (@available(iOS 17, *)) {
+    self.traitOverrides.horizontalSizeClass = UIUserInterfaceSizeClassCompact;
+  }
   _borderView = [self createBorderView];
   [self.view addSubview:_borderView];
   AddSameConstraintsWithInsets(_borderView, self.view,
diff --git a/ios/chrome/browser/omnibox/model/omnibox_controller_ios.mm b/ios/chrome/browser/omnibox/model/omnibox_controller_ios.mm
index 0af06436..25a2229 100644
--- a/ios/chrome/browser/omnibox/model/omnibox_controller_ios.mm
+++ b/ios/chrome/browser/omnibox/model/omnibox_controller_ios.mm
@@ -103,9 +103,7 @@
       edit_model_->OnCurrentMatchChanged();
     } else {
       edit_model_->OnPopupResultChanged();
-      edit_model_->OnPopupDataChanged(std::u16string(),
-                                      /*is_temporary_text=*/false,
-                                      std::u16string(), std::u16string(),
+      edit_model_->OnPopupDataChanged(std::u16string(), std::u16string(),
                                       AutocompleteMatch());
     }
   } else {
@@ -118,9 +116,6 @@
   }
 
   if (popup_was_open && !popup_is_open) {
-    // Accept the temporary text as the user text, because it makes little sense
-    // to have temporary text when the popup is closed.
-    edit_model_->AcceptTemporaryTextAsUserText();
     // Closing the popup can change the default suggestion. This usually occurs
     // when it's unclear whether the input represents a search or URL; e.g.,
     // 'a.com/b c' or when title autocompleting. Clear the additional text to
diff --git a/ios/chrome/browser/omnibox/model/omnibox_edit_model_ios.h b/ios/chrome/browser/omnibox/model/omnibox_edit_model_ios.h
index 2eb9dc4..1d8d1d5 100644
--- a/ios/chrome/browser/omnibox/model/omnibox_edit_model_ios.h
+++ b/ios/chrome/browser/omnibox/model/omnibox_edit_model_ios.h
@@ -167,9 +167,6 @@
     return focus_state_ == OMNIBOX_FOCUS_VISIBLE;
   }
 
-  // Accepts the current temporary text as the user text.
-  void AcceptTemporaryTextAsUserText();
-
   // Clears additional text.
   void ClearAdditionalText();
 
@@ -205,10 +202,6 @@
   // Called when the view is losing focus.  Resets some state.
   void OnKillFocus();
 
-  // Called when the user presses the escape key.  Decides what, if anything, to
-  // revert about any current edits.  Returns whether the key was handled.
-  bool OnEscapeKeyPressed();
-
   // Called when the user presses or releases the control key.  Changes state as
   // necessary.
   void OnControlKeyChanged(bool pressed);
@@ -221,26 +214,15 @@
 
   // Called when any relevant data changes.  This rolls together several
   // separate pieces of data into one call so we can update all the UI
-  // efficiently. Specifically, it's invoked for temporary text, autocompletion.
-  //   `temporary_text` is the new temporary text from the user selecting a
-  //     different match. This will be empty when selecting a suggestion
-  //     without a `fill_into_edit` (e.g. FOCUSED_BUTTON_HEADER) and when
-  //     `is_temporary_test` is false.
-  //   `is_temporary_text` is true if invoked because of a temporary text change
-  //     or false if `temporary_text` should be ignored.
+  // efficiently. Specifically, it's invoked for autocompletion.
   //   `inline_autocompletion` is the autocompletion.
-  //   `destination_for_temporary_text_change` is NULL (if temporary text should
-  //     not change) or the pre-change destination URL (if temporary text should
-  //     change) so we can save it off to restore later.
   //   `additional_text` is additional omnibox text to be displayed adjacent to
   //     the omnibox view.
   //   `new_match` is the selected match when the user is changing selection,
   //     the default match if the user is typing, or an empty match when
   //     selecting a header.
   // Virtual to allow testing.
-  virtual void OnPopupDataChanged(const std::u16string& temporary_text,
-                                  bool is_temporary_text,
-                                  const std::u16string& inline_autocompletion,
+  virtual void OnPopupDataChanged(const std::u16string& inline_autocompletion,
                                   const std::u16string& additional_text,
                                   const AutocompleteMatch& new_match);
 
@@ -265,10 +247,6 @@
   // Just forwards the call to the OmniboxViewBase referred within.
   void SetAccessibilityLabel(const AutocompleteMatch& match);
 
-  // Reverts the edit box from a temporary text back to the original user text.
-  // Also resets the popup to the initial state.
-  void RevertTemporaryTextAndPopup();
-
   // Returns true if the destination URL of the match is bookmarked.
   bool IsStarredMatch(const AutocompleteMatch& match) const;
 
@@ -469,9 +447,9 @@
   // but user_input_in_progress_ is not being cleared.
   std::u16string url_for_remembered_user_selection_;
 
-  // Inline autocomplete is allowed if the user has not just deleted text, and
-  // no temporary text is showing.  In this case, inline_autocompletion_ is
-  // appended to the user_text_ and displayed selected (at least initially).
+  // Inline autocomplete is allowed if the user has not just deleted text. In
+  // this case, inline_autocompletion_ is appended to the user_text_ and
+  // displayed selected (at least initially).
   //
   // NOTE: When the popup is closed there should never be inline autocomplete
   // text (actions that close the popup should either accept the text, convert
@@ -479,17 +457,6 @@
   bool just_deleted_text_;
   std::u16string inline_autocompletion_;
 
-  // Used by OnPopupDataChanged to keep track of whether there is currently a
-  // temporary text.
-  //
-  // Example of use: If the user types "goog", then arrows down in the
-  // autocomplete popup until, say, "google.com" appears in the edit box, then
-  // the user_text_ is still "goog", and "google.com" is "temporary text".
-  // When the user hits <esc>, the edit box reverts to "goog".  Hit <esc> again
-  // and the popup is closed and "goog" is replaced by the permanent display
-  // URL, which is the URL of the current page.
-  bool has_temporary_text_;
-
   // When the user's last action was to paste, we disallow inline autocomplete
   // (on the theory that the user is trying to paste in a new URL or part of
   // one, and in either case inline autocomplete would get in the way).
diff --git a/ios/chrome/browser/omnibox/model/omnibox_edit_model_ios.mm b/ios/chrome/browser/omnibox/model/omnibox_edit_model_ios.mm
index 374f972..4076fdc 100644
--- a/ios/chrome/browser/omnibox/model/omnibox_edit_model_ios.mm
+++ b/ios/chrome/browser/omnibox/model/omnibox_edit_model_ios.mm
@@ -86,22 +86,6 @@
 
 namespace {
 
-// The possible histogram values emitted when escape is pressed.
-// These values are persisted to logs. Entries should not be renumbered and
-// numeric values should never be reused.
-enum class OmniboxEscapeAction {
-  // `kNone` doesn't mean escape did nothing (e.g. it could have stopped a
-  // navigation), just that it did not affect the omnibox state.
-  //  kNone = 0, No longer used since escape now always blurs the omnibox if it
-  //             does nothing else.
-  kRevertTemporaryText = 1,
-  kClosePopup = 2,
-  kClearUserInput = 3,
-  kClosePopupAndClearUserInput = 4,
-  kBlur = 5,
-  kMaxValue = kBlur,
-};
-
 const char kOmniboxFocusResultedInNavigation[] =
     "Omnibox.FocusResultedInNavigation";
 
@@ -260,7 +244,6 @@
       user_input_in_progress_(false),
       focus_resulted_in_navigation_(false),
       just_deleted_text_(false),
-      has_temporary_text_(false),
       paste_state_(NONE),
       control_key_state_(UP),
       in_revert_(false),
@@ -323,7 +306,6 @@
   InternalSetUserText(text);
   GetInfoForCurrentText(&current_match_, nullptr);
   paste_state_ = NONE;
-  has_temporary_text_ = false;
 }
 
 void OmniboxEditModelIOS::OnChanged() {
@@ -497,7 +479,6 @@
   input_.Clear();
   paste_state_ = NONE;
   InternalSetUserText(std::u16string());
-  has_temporary_text_ = false;
   size_t start, end;
   if (view_) {
     view_->GetSelectionBounds(&start, &end);
@@ -611,15 +592,6 @@
   OpenSelection(popup_selection_, timestamp, disposition);
 }
 
-void OmniboxEditModelIOS::AcceptTemporaryTextAsUserText() {
-  InternalSetUserText(GetText());
-  has_temporary_text_ = false;
-
-  if (user_input_in_progress_ || !in_revert_) {
-    controller_->client()->OnInputStateChanged();
-  }
-}
-
 void OmniboxEditModelIOS::ClearAdditionalText() {
   TRACE_EVENT0("omnibox", "OmniboxEditModelIOS::ClearAdditionalText");
   if (view_) {
@@ -729,72 +701,6 @@
 #endif
 }
 
-bool OmniboxEditModelIOS::OnEscapeKeyPressed() {
-  const char* kOmniboxEscapeHistogramName = "Omnibox.Escape";
-
-  // If there is temporary text (i.e. a non default suggestion is selected),
-  // revert it.
-  if (has_temporary_text_) {
-    base::UmaHistogramEnumeration(kOmniboxEscapeHistogramName,
-                                  OmniboxEscapeAction::kRevertTemporaryText);
-    RevertTemporaryTextAndPopup();
-    return true;
-  }
-
-  // We do not clear the pending entry from the omnibox when a load is first
-  // stopped.  If the user presses Escape while stopped, whether editing or not,
-  // we clear it.
-  if (controller_->client()->CurrentPageExists() &&
-      !controller_->client()->IsLoading()) {
-    controller_->client()->DiscardNonCommittedNavigations();
-    if (view_) {
-      view_->Update();
-    }
-  }
-
-  // Close the popup if it's open.
-  if (PopupIsOpen()) {
-    base::UmaHistogramEnumeration(kOmniboxEscapeHistogramName,
-                                  OmniboxEscapeAction::kClosePopup);
-    if (view_) {
-      view_->CloseOmniboxPopup();
-    }
-    return true;
-  }
-
-  // Unconditionally revert/select all.  This ensures any popup, whether due to
-  // normal editing or ZeroSuggest, is closed, and the full text is selected.
-  // This in turn allows the user to use escape to quickly select all the text
-  // for ease of replacement, and matches other browsers.
-  bool user_input_was_in_progress = user_input_in_progress_;
-  // TODO(crbug.com/40230336): If the popup was open, `user_input_in_progress_`
-  //  *should* also be true; checking `user_text_` in the DCHECK below, and
-  //  checking `popup_was_open` in the if predicate below *should* be
-  //  unnecessary. However, that's not always the case (see
-  //  `user_input_in_progress_` comment in the header).
-  if (view_) {
-    view_->RevertAll();
-    view_->SelectAll(true);
-  }
-  if (user_input_was_in_progress) {
-    base::UmaHistogramEnumeration(kOmniboxEscapeHistogramName,
-                                  OmniboxEscapeAction::kClearUserInput);
-    // If the user was in the midst of editing, don't cancel any underlying page
-    // load.  This doesn't match IE or Firefox, but seems more correct.  Note
-    // that we do allow the page load to be stopped in the case where
-    // ZeroSuggest was visible; this is so that it's still possible to focus the
-    // address bar and hit escape once to stop a load even if the address being
-    // loaded triggers the ZeroSuggest popup.
-    return true;
-  }
-
-  // Blur the omnibox and focus the web contents.
-  base::UmaHistogramEnumeration(kOmniboxEscapeHistogramName,
-                                OmniboxEscapeAction::kBlur);
-  controller_->client()->FocusWebContents();
-  return true;
-}
-
 void OmniboxEditModelIOS::OnControlKeyChanged(bool pressed) {
   if (pressed == (control_key_state_ == UP)) {
     control_key_state_ = pressed ? DOWN : UP;
@@ -818,40 +724,11 @@
 }
 
 void OmniboxEditModelIOS::OnPopupDataChanged(
-    const std::u16string& temporary_text,
-    bool is_temporary_text,
     const std::u16string& inline_autocompletion,
     const std::u16string& additional_text,
     const AutocompleteMatch& new_match) {
   current_match_ = new_match;
 
-  // Handle changes to temporary text.
-  if (is_temporary_text) {
-    const bool save_original_selection = !has_temporary_text_;
-    if (save_original_selection) {
-      // Save the original selection and URL so it can be reverted later.
-      has_temporary_text_ = true;
-      inline_autocompletion_.clear();
-      if (view_) {
-        view_->OnInlineAutocompleteTextCleared();
-      }
-    }
-    // Arrowing around the popup cancels control-enter.
-    ConsumeCtrlKey();
-    // Now things are a bit screwy: the desired_tld has changed, but if we
-    // update the popup, the new order of entries won't match the old, so the
-    // user's selection gets screwy; and if we don't update the popup, and the
-    // user reverts, then the selected item will be as if control is still
-    // pressed, even though maybe it isn't any more.  There is no obvious
-    // right answer here :(
-
-    if (view_) {
-      view_->OnTemporaryTextMaybeChanged(temporary_text, current_match_,
-                                         save_original_selection, true);
-    }
-    return;
-  }
-
   inline_autocompletion_ = inline_autocompletion;
   if (inline_autocompletion_.empty() && view_) {
     view_->OnInlineAutocompleteTextCleared();
@@ -907,7 +784,6 @@
   }
 
   InternalSetUserText(*state_changes.new_text);
-  has_temporary_text_ = false;
   just_deleted_text_ = state_changes.just_deleted_text;
 
   if (view_) {
@@ -920,7 +796,6 @@
 // Merge OnPopupDataChanged with this method once the popup
 // handling has completely migrated to omnibox_controller.
 void OmniboxEditModelIOS::OnCurrentMatchChanged() {
-  has_temporary_text_ = false;
 
   DCHECK(autocomplete_controller()->result().default_match());
   const AutocompleteMatch& match =
@@ -931,8 +806,7 @@
   // OnPopupDataChanged() resets OmniboxControllerIOS's `current_match_` early
   // on.  Therefore, copy match.inline_autocompletion to a temp to preserve
   // its value across the entire call.
-  OnPopupDataChanged(std::u16string(),
-                     /*is_temporary_text=*/false, match.inline_autocompletion,
+  OnPopupDataChanged(match.inline_autocompletion,
 
                      match.additional_text, match);
 }
@@ -999,34 +873,6 @@
   }
 }
 
-void OmniboxEditModelIOS::RevertTemporaryTextAndPopup() {
-  // The user typed something, then selected a different item.  Restore the
-  // text they typed and change back to the default item.
-  // NOTE: This purposefully does not reset paste_state_.
-  just_deleted_text_ = false;
-  has_temporary_text_ = false;
-
-  // There are two cases in which resetting to the default match doesn't restore
-  // the proper original text:
-  //  1. If user input is not in progress, we are reverting an on-focus
-  //     suggestion. These may be unrelated to the original input.
-  //  2. If there's no default match at all.
-  //
-  // The original selection will be restored in OnRevertTemporaryText() below.
-  if ((!user_input_in_progress_ ||
-       !autocomplete_controller()->result().default_match()) &&
-      view_) {
-    view_->SetWindowTextAndCaretPos(input_.text(), /*caret_pos=*/0,
-                                    /*update_popup=*/false,
-                                    /*notify_text_changed=*/true);
-  }
-
-  if (view_) {
-    const AutocompleteMatch& match = CurrentMatch(nullptr);
-    view_->OnRevertTemporaryText(match.fill_into_edit, match);
-  }
-}
-
 bool OmniboxEditModelIOS::IsStarredMatch(const AutocompleteMatch& match) const {
   auto* bookmark_model = controller_->client()->GetBookmarkModel();
   return bookmark_model && bookmark_model->IsBookmarked(match.destination_url);
@@ -1114,9 +960,7 @@
     //  2. If the user has never edited the text, use the current page's full
     //     URL instead of the elided URL to avoid HTTPS downgrading.
     std::u16string text_for_desired_tld_navigation = input_.text();
-    if (has_temporary_text_) {
-      text_for_desired_tld_navigation = GetText();
-    } else if (!user_input_in_progress()) {
+    if (!user_input_in_progress()) {
       text_for_desired_tld_navigation = url_for_editing_;
     }
 
diff --git a/ios/chrome/browser/omnibox/model/omnibox_edit_model_ios_unittest.mm b/ios/chrome/browser/omnibox/model/omnibox_edit_model_ios_unittest.mm
index cae4f955..6342380 100644
--- a/ios/chrome/browser/omnibox/model/omnibox_edit_model_ios_unittest.mm
+++ b/ios/chrome/browser/omnibox/model/omnibox_edit_model_ios_unittest.mm
@@ -256,9 +256,7 @@
   // Test if the model updates the inline autocomplete text in the view.
   EXPECT_EQ(std::u16string(), view()->inline_autocompletion());
   model()->SetUserText(u"he");
-  model()->OnPopupDataChanged(std::u16string(),
-                              /*is_temporary_text=*/false, u"llo",
-                              std::u16string(), {});
+  model()->OnPopupDataChanged(u"llo", std::u16string(), {});
   EXPECT_EQ(u"hello", view()->GetText());
   EXPECT_EQ(u"llo", view()->inline_autocompletion());
 
@@ -268,9 +266,7 @@
       &text_before, &text_after, 3, 3, false, true, false};
   model()->OnAfterPossibleChange(state_changes);
   EXPECT_EQ(std::u16string(), view()->inline_autocompletion());
-  model()->OnPopupDataChanged(std::u16string(),
-                              /*is_temporary_text=*/false, u"lo",
-                              std::u16string(), {});
+  model()->OnPopupDataChanged(u"lo", std::u16string(), {});
   EXPECT_EQ(u"hello", view()->GetText());
   EXPECT_EQ(u"lo", view()->inline_autocompletion());
 
@@ -279,36 +275,9 @@
   EXPECT_EQ(std::u16string(), view()->inline_autocompletion());
 
   model()->SetUserText(u"he");
-  model()->OnPopupDataChanged(std::u16string(),
-                              /*is_temporary_text=*/false, u"llo",
-                              std::u16string(), {});
+  model()->OnPopupDataChanged(u"llo", std::u16string(), {});
   EXPECT_EQ(u"hello", view()->GetText());
   EXPECT_EQ(u"llo", view()->inline_autocompletion());
-
-  model()->AcceptTemporaryTextAsUserText();
-  EXPECT_EQ(u"hello", view()->GetText());
-  EXPECT_EQ(std::u16string(), view()->inline_autocompletion());
-}
-
-TEST_F(OmniboxEditModelIOSTest, RevertZeroSuggestTemporaryText) {
-  location_bar_model()->set_url(GURL("https://www.example.com/"));
-  location_bar_model()->set_url_for_display(u"https://www.example.com/");
-
-  EXPECT_TRUE(model()->ResetDisplayTexts());
-  model()->Revert();
-
-  // Simulate getting ZeroSuggestions and arrowing to a different match.
-  view()->SelectAll(true);
-  model()->StartZeroSuggestRequest();
-  model()->OnPopupDataChanged(u"fake_temporary_text",
-                              /*is_temporary_text=*/true, std::u16string(),
-                              std::u16string(), {});
-
-  // Test that reverting brings back the original input text.
-  EXPECT_TRUE(model()->OnEscapeKeyPressed());
-  EXPECT_EQ(u"https://www.example.com/", view()->GetText());
-  EXPECT_FALSE(model()->user_input_in_progress());
-  EXPECT_TRUE(view()->IsSelectAll());
 }
 
 // This verifies the fix for a bug where calling OpenMatch() with a valid
@@ -450,20 +419,6 @@
             model()->GetInputForTesting().canonicalized_url());
 }
 
-TEST_F(OmniboxEditModelIOSTest, CtrlEnterNavigatesToDesiredTLDTemporaryText) {
-  // But if it's the temporary text, the View text should be used.
-  view()->SetUserText(u"foo");
-  model()->StartAutocomplete(false, false);
-  model()->OnPopupDataChanged(u"foobar",
-                              /*is_temporary_text=*/true, std::u16string(),
-                              std::u16string(), {});
-
-  model()->OnControlKeyChanged(true);
-  model()->OpenSelection();
-  EXPECT_EQ(GURL("http://www.foobar.com/"),
-            model()->GetInputForTesting().canonicalized_url());
-}
-
 TEST_F(OmniboxEditModelIOSTest,
        CtrlEnterNavigatesToDesiredTLDSteadyStateElisions) {
   location_bar_model()->set_url(GURL("https://www.example.com/"));
@@ -539,81 +494,6 @@
   OmniboxTriggeredFeatureService triggered_feature_service_;
 };
 
-TEST_F(OmniboxEditModelIOSTest, OmniboxEscapeHistogram) {
-  // Escape should incrementally revert temporary text, close the popup, clear
-  // input, and blur the omnibox.
-  AutocompleteMatch match;
-  match.type = AutocompleteMatchType::NAVSUGGEST;
-  match.destination_url = GURL("https://google.com");
-  model()->SetCurrentMatchForTest(match);
-
-  view()->SetUserText(u"user text");
-  model()->OnSetFocus(false);
-  model()->SetInputInProgress(true);
-  model()->SetPopupIsOpen(true);
-  model()->OnPopupDataChanged(/*temporary_text=*/u"fake_temporary_text",
-                              /*is_temporary_text=*/true, std::u16string(),
-                              std::u16string(), {});
-
-  EXPECT_TRUE(model()->HasTemporaryText());
-  EXPECT_TRUE(model()->PopupIsOpen());
-  EXPECT_EQ(view()->GetText(), u"fake_temporary_text");
-  EXPECT_TRUE(model()->user_input_in_progress());
-  EXPECT_TRUE(model()->has_focus());
-
-  {
-    // Revert temporary text.
-    base::HistogramTester histogram_tester;
-    EXPECT_TRUE(model()->OnEscapeKeyPressed());
-    histogram_tester.ExpectUniqueSample("Omnibox.Escape", 1, 1);
-    EXPECT_FALSE(model()->HasTemporaryText());
-    EXPECT_TRUE(model()->PopupIsOpen());
-    EXPECT_EQ(view()->GetText(), u"");
-    EXPECT_TRUE(model()->user_input_in_progress());
-    EXPECT_TRUE(model()->has_focus());
-  }
-
-  {
-    // Close the popup.
-    base::HistogramTester histogram_tester;
-    EXPECT_TRUE(model()->OnEscapeKeyPressed());
-    histogram_tester.ExpectUniqueSample("Omnibox.Escape", 2, 1);
-    model()->SetPopupIsOpen(
-        false);  // `TestOmniboxEditModelIOS` stubs the popup.
-    EXPECT_FALSE(model()->HasTemporaryText());
-    EXPECT_FALSE(model()->PopupIsOpen());
-    EXPECT_EQ(view()->GetText(), u"");
-    EXPECT_TRUE(model()->user_input_in_progress());
-    EXPECT_TRUE(model()->has_focus());
-  }
-
-  {
-    // Clear user input.
-    base::HistogramTester histogram_tester;
-    EXPECT_TRUE(model()->OnEscapeKeyPressed());
-    histogram_tester.ExpectUniqueSample("Omnibox.Escape", 3, 1);
-    EXPECT_FALSE(model()->HasTemporaryText());
-    EXPECT_FALSE(model()->PopupIsOpen());
-    EXPECT_EQ(view()->GetText(), u"");
-    EXPECT_FALSE(model()->user_input_in_progress());
-    EXPECT_TRUE(model()->has_focus());
-  }
-
-  {
-    // Blur the omnibox.
-    base::HistogramTester histogram_tester;
-    EXPECT_TRUE(model()->OnEscapeKeyPressed());
-    histogram_tester.ExpectUniqueSample("Omnibox.Escape", 5, 1);
-    model()->OnKillFocus();  // `TestOmniboxEditModelIOS` stubs the client which
-                             // handles blurring the omnibox.
-    EXPECT_FALSE(model()->HasTemporaryText());
-    EXPECT_FALSE(model()->PopupIsOpen());
-    EXPECT_EQ(view()->GetText(), u"");
-    EXPECT_FALSE(model()->user_input_in_progress());
-    EXPECT_FALSE(model()->has_focus());
-  }
-}
-
 TEST_F(OmniboxEditModelIOSTest, IPv4AddressPartsCount) {
   base::HistogramTester histogram_tester;
   constexpr char kIPv4AddressPartsCountHistogramName[] =
diff --git a/ios/chrome/browser/omnibox/model/omnibox_view_base.h b/ios/chrome/browser/omnibox/model/omnibox_view_base.h
index ed3f534..f0b5919d 100644
--- a/ios/chrome/browser/omnibox/model/omnibox_view_base.h
+++ b/ios/chrome/browser/omnibox/model/omnibox_view_base.h
@@ -124,17 +124,6 @@
                                      const AutocompleteMatch& match,
                                      bool notify_text_changed) {}
 
-  // Called when the temporary text in the model may have changed.
-  // `display_text` is the new text to show; `match_type` is the type of the
-  // match the new text came from. `save_original_selection` is true when there
-  // wasn't previously a temporary text and thus we need to save off the user's
-  // existing selection. `notify_text_changed` is true if the model should be
-  // notified of the change.
-  virtual void OnTemporaryTextMaybeChanged(const std::u16string& display_text,
-                                           const AutocompleteMatch& match,
-                                           bool save_original_selection,
-                                           bool notify_text_changed) = 0;
-
   // Called when the inline autocomplete text in the model may have changed.
   // `user_text` is the portion of omnibox text the user typed.
   // `inline`_autocompletion` is the autocompleted part.
@@ -145,11 +134,6 @@
   // Called when the inline autocomplete text in the model has been cleared.
   virtual void OnInlineAutocompleteTextCleared() = 0;
 
-  // Called when the temporary text has been reverted by the user.  This will
-  // reset the user's original selection.
-  virtual void OnRevertTemporaryText(const std::u16string& display_text,
-                                     const AutocompleteMatch& match) = 0;
-
   // Checkpoints the current edit state before an operation that might trigger
   // a new autocomplete run to open or modify the popup. Call this before
   // user-initiated edit actions that trigger autocomplete, but *not* for
diff --git a/ios/chrome/browser/omnibox/model/omnibox_view_ios.h b/ios/chrome/browser/omnibox/model/omnibox_view_ios.h
index 7e76e18..d3abe8be 100644
--- a/ios/chrome/browser/omnibox/model/omnibox_view_ios.h
+++ b/ios/chrome/browser/omnibox/model/omnibox_view_ios.h
@@ -14,7 +14,6 @@
 #import "components/omnibox/browser/location_bar_model.h"
 #import "ios/chrome/browser/omnibox/model/omnibox_view_base.h"
 
-struct AutocompleteMatch;
 class OmniboxClient;
 @protocol OmniboxCommands;
 @protocol OmniboxFocusDelegate;
@@ -49,10 +48,6 @@
   void SetCaretPos(size_t caret_pos) override;
   void RevertAll() override;
   void UpdatePopup() override;
-  void OnTemporaryTextMaybeChanged(const std::u16string& display_text,
-                                   const AutocompleteMatch& match,
-                                   bool save_original_selection,
-                                   bool notify_text_changed) override;
   void OnInlineAutocompleteTextMaybeChanged(
       const std::u16string& user_text,
       const std::u16string& inline_autocompletion) override;
@@ -71,8 +66,6 @@
   void SetFocus(bool is_user_initiated) override {}
   void ApplyCaretVisibility() override {}
   void OnInlineAutocompleteTextCleared() override {}
-  void OnRevertTemporaryText(const std::u16string& display_text,
-                             const AutocompleteMatch& match) override {}
   gfx::NativeView GetNativeView() const override;
   gfx::NativeView GetRelativeWindowForPopup() const override;
 
diff --git a/ios/chrome/browser/omnibox/model/omnibox_view_ios.mm b/ios/chrome/browser/omnibox/model/omnibox_view_ios.mm
index 653b680..d13e555 100644
--- a/ios/chrome/browser/omnibox/model/omnibox_view_ios.mm
+++ b/ios/chrome/browser/omnibox/model/omnibox_view_ios.mm
@@ -16,7 +16,6 @@
 #import "base/metrics/user_metrics_action.h"
 #import "base/strings/sys_string_conversions.h"
 #import "components/omnibox/browser/autocomplete_input.h"
-#import "components/omnibox/browser/autocomplete_match.h"
 #import "components/omnibox/browser/clipboard_provider.h"
 #import "components/omnibox/browser/location_bar_model.h"
 #import "components/omnibox/common/omnibox_focus_state.h"
@@ -92,20 +91,6 @@
   [omnibox_text_controller_ startAutocompleteAfterEdit];
 }
 
-void OmniboxViewIOS::OnTemporaryTextMaybeChanged(
-    const std::u16string& display_text,
-    const AutocompleteMatch& match,
-    bool save_original_selection,
-    bool notify_text_changed) {
-  [omnibox_text_controller_ setWindowText:display_text
-                                 caretPos:display_text.length()
-                        startAutocomplete:NO
-                        notifyTextChanged:NO];
-  if (model()) {
-    model()->OnChanged();
-  }
-}
-
 void OmniboxViewIOS::OnInlineAutocompleteTextMaybeChanged(
     const std::u16string& user_text,
     const std::u16string& inline_autocompletion) {
diff --git a/ios/chrome/browser/omnibox/model/test_omnibox_edit_model_ios.h b/ios/chrome/browser/omnibox/model/test_omnibox_edit_model_ios.h
index ab4905c8..9f41861 100644
--- a/ios/chrome/browser/omnibox/model/test_omnibox_edit_model_ios.h
+++ b/ios/chrome/browser/omnibox/model/test_omnibox_edit_model_ios.h
@@ -28,16 +28,11 @@
 
   void SetCurrentMatchForTest(const AutocompleteMatch& match);
 
-  void OnPopupDataChanged(const std::u16string& temporary_text,
-                          bool is_temporary_text,
-                          const std::u16string& inline_autocompletion,
+  void OnPopupDataChanged(const std::u16string& inline_autocompletion,
                           const std::u16string& additional_text,
                           const AutocompleteMatch& match) override;
 
-  bool HasTemporaryText() { return has_temporary_text_; }
-
   const std::u16string& text() const { return text_; }
-  bool is_temporary_text() const { return is_temporary_text_; }
 
  protected:
   PrefService* GetPrefService() override;
@@ -49,7 +44,6 @@
 
   // Contains the most recent text passed by the popup model to the edit model.
   std::u16string text_;
-  bool is_temporary_text_ = false;
   raw_ptr<PrefService> pref_service_;
 };
 
diff --git a/ios/chrome/browser/omnibox/model/test_omnibox_edit_model_ios.mm b/ios/chrome/browser/omnibox/model/test_omnibox_edit_model_ios.mm
index ce6ac35..0804ae02 100644
--- a/ios/chrome/browser/omnibox/model/test_omnibox_edit_model_ios.mm
+++ b/ios/chrome/browser/omnibox/model/test_omnibox_edit_model_ios.mm
@@ -41,16 +41,12 @@
 }
 
 void TestOmniboxEditModelIOS::OnPopupDataChanged(
-    const std::u16string& temporary_text,
-    bool is_temporary_text,
     const std::u16string& inline_autocompletion,
     const std::u16string& additional_text,
     const AutocompleteMatch& match) {
-  OmniboxEditModelIOS::OnPopupDataChanged(temporary_text, is_temporary_text,
-                                          inline_autocompletion,
+  OmniboxEditModelIOS::OnPopupDataChanged(inline_autocompletion,
                                           additional_text, match);
-  text_ = is_temporary_text ? temporary_text : inline_autocompletion;
-  is_temporary_text_ = is_temporary_text;
+  text_ = inline_autocompletion;
 }
 
 PrefService* TestOmniboxEditModelIOS::GetPrefService() {
diff --git a/ios/chrome/browser/omnibox/model/test_omnibox_view_base.h b/ios/chrome/browser/omnibox/model/test_omnibox_view_base.h
index 3539504..a16f83a 100644
--- a/ios/chrome/browser/omnibox/model/test_omnibox_view_base.h
+++ b/ios/chrome/browser/omnibox/model/test_omnibox_view_base.h
@@ -12,8 +12,6 @@
 #import "ios/chrome/browser/omnibox/model/omnibox_view_base.h"
 #import "ui/gfx/range/range.h"
 
-struct AutocompleteMatch;
-
 // Fake implementation of OmniboxViewBase for use in tests.
 class TestOmniboxViewBase : public OmniboxViewBase {
  public:
@@ -44,16 +42,10 @@
   void UpdatePopup() override {}
   void SetFocus(bool is_user_initiated) override {}
   void ApplyCaretVisibility() override {}
-  void OnTemporaryTextMaybeChanged(const std::u16string& display_text,
-                                   const AutocompleteMatch& match,
-                                   bool save_original_selection,
-                                   bool notify_text_changed) override;
   void OnInlineAutocompleteTextMaybeChanged(
       const std::u16string& user_text,
       const std::u16string& inline_autocompletion) override;
   void OnInlineAutocompleteTextCleared() override;
-  void OnRevertTemporaryText(const std::u16string& display_text,
-                             const AutocompleteMatch& match) override;
   void OnBeforePossibleChange() override {}
   bool OnAfterPossibleChange() override;
   gfx::NativeView GetNativeView() const override;
@@ -69,7 +61,6 @@
   std::u16string text_;
   std::u16string inline_autocompletion_;
   gfx::Range selection_;
-  gfx::Range saved_temporary_selection_;
 };
 
 #endif  // IOS_CHROME_BROWSER_OMNIBOX_MODEL_TEST_OMNIBOX_VIEW_BASE_H_
diff --git a/ios/chrome/browser/omnibox/model/test_omnibox_view_base.mm b/ios/chrome/browser/omnibox/model/test_omnibox_view_base.mm
index 7d45813..940efb8 100644
--- a/ios/chrome/browser/omnibox/model/test_omnibox_view_base.mm
+++ b/ios/chrome/browser/omnibox/model/test_omnibox_view_base.mm
@@ -50,18 +50,6 @@
   }
 }
 
-void TestOmniboxViewBase::OnTemporaryTextMaybeChanged(
-    const std::u16string& display_text,
-    const AutocompleteMatch& match,
-    bool save_original_selection,
-    bool notify_text_changed) {
-  text_ = display_text;
-
-  if (save_original_selection) {
-    saved_temporary_selection_ = selection_;
-  }
-}
-
 void TestOmniboxViewBase::OnInlineAutocompleteTextMaybeChanged(
     const std::u16string& user_text,
     const std::u16string& inline_autocompletion) {
@@ -81,12 +69,6 @@
   inline_autocompletion_.clear();
 }
 
-void TestOmniboxViewBase::OnRevertTemporaryText(
-    const std::u16string& display_text,
-    const AutocompleteMatch& match) {
-  selection_ = saved_temporary_selection_;
-}
-
 bool TestOmniboxViewBase::OnAfterPossibleChange() {
   return false;
 }
diff --git a/ios/chrome/browser/passwords/ui_bundled/bottom_sheet/BUILD.gn b/ios/chrome/browser/passwords/ui_bundled/bottom_sheet/BUILD.gn
index 8a60942..0184dcc 100644
--- a/ios/chrome/browser/passwords/ui_bundled/bottom_sheet/BUILD.gn
+++ b/ios/chrome/browser/passwords/ui_bundled/bottom_sheet/BUILD.gn
@@ -174,6 +174,7 @@
     "//components/password_manager/core/browser:test_support",
     "//components/password_manager/core/browser/features:password_features",
     "//components/password_manager/ios",
+    "//components/password_manager/ios:test_support",
     "//components/prefs:test_support",
     "//ios/chrome/app/strings",
     "//ios/chrome/browser/autofill/model",
@@ -181,6 +182,7 @@
     "//ios/chrome/browser/history/model",
     "//ios/chrome/browser/passwords/model",
     "//ios/chrome/browser/passwords/model:store_factory",
+    "//ios/chrome/browser/shared/model/prefs:browser_prefs",
     "//ios/chrome/browser/shared/model/prefs:pref_names",
     "//ios/chrome/browser/shared/model/profile/test",
     "//ios/chrome/browser/shared/model/web_state_list",
diff --git a/ios/chrome/browser/passwords/ui_bundled/bottom_sheet/password_suggestion_bottom_sheet_mediator_unittest.mm b/ios/chrome/browser/passwords/ui_bundled/bottom_sheet/password_suggestion_bottom_sheet_mediator_unittest.mm
index 8cb6844..d348cde 100644
--- a/ios/chrome/browser/passwords/ui_bundled/bottom_sheet/password_suggestion_bottom_sheet_mediator_unittest.mm
+++ b/ios/chrome/browser/passwords/ui_bundled/bottom_sheet/password_suggestion_bottom_sheet_mediator_unittest.mm
@@ -4,7 +4,11 @@
 
 #import "ios/chrome/browser/passwords/ui_bundled/bottom_sheet/password_suggestion_bottom_sheet_mediator.h"
 
+#import "base/apple/foundation_util.h"
+#import "base/run_loop.h"
 #import "base/test/scoped_feature_list.h"
+#import "components/autofill/core/common/password_form_fill_data.h"
+#import "components/autofill/core/common/unique_ids.h"
 #import "components/autofill/ios/browser/form_suggestion.h"
 #import "components/autofill/ios/browser/form_suggestion_provider.h"
 #import "components/autofill/ios/form_util/form_activity_params.h"
@@ -12,9 +16,12 @@
 #import "components/password_manager/core/browser/features/password_features.h"
 #import "components/password_manager/core/browser/password_manager_test_utils.h"
 #import "components/password_manager/core/browser/password_store/test_password_store.h"
+#import "components/password_manager/ios/password_manager_java_script_feature.h"
 #import "components/password_manager/ios/shared_password_controller.h"
+#import "components/password_manager/ios/test_helpers.h"
 #import "components/prefs/pref_registry_simple.h"
 #import "components/prefs/testing_pref_service.h"
+#import "components/sync_preferences/testing_pref_service_syncable.h"
 #import "ios/chrome/browser/autofill/model/form_suggestion_tab_helper.h"
 #import "ios/chrome/browser/favicon/model/favicon_service_factory.h"
 #import "ios/chrome/browser/favicon/model/ios_chrome_favicon_loader_factory.h"
@@ -24,16 +31,23 @@
 #import "ios/chrome/browser/passwords/model/ios_chrome_password_check_manager.h"
 #import "ios/chrome/browser/passwords/model/ios_chrome_password_check_manager_factory.h"
 #import "ios/chrome/browser/passwords/model/ios_chrome_profile_password_store_factory.h"
+#import "ios/chrome/browser/passwords/model/password_tab_helper.h"
 #import "ios/chrome/browser/passwords/ui_bundled/bottom_sheet/password_suggestion_bottom_sheet_consumer.h"
 #import "ios/chrome/browser/passwords/ui_bundled/bottom_sheet/password_suggestion_bottom_sheet_presenter.h"
+#import "ios/chrome/browser/shared/model/prefs/browser_prefs.h"
 #import "ios/chrome/browser/shared/model/prefs/pref_names.h"
 #import "ios/chrome/browser/shared/model/profile/test/test_profile_ios.h"
 #import "ios/chrome/browser/shared/model/web_state_list/test/fake_web_state_list_delegate.h"
 #import "ios/chrome/browser/shared/model/web_state_list/web_state_list.h"
 #import "ios/chrome/browser/shared/model/web_state_list/web_state_opener.h"
 #import "ios/chrome/grit/ios_strings.h"
+#import "ios/web/public/test/fakes/fake_navigation_manager.h"
+#import "ios/web/public/test/fakes/fake_web_frame.h"
+#import "ios/web/public/test/fakes/fake_web_frames_manager.h"
 #import "ios/web/public/test/fakes/fake_web_state.h"
+#import "ios/web/public/test/js_test_util.h"
 #import "ios/web/public/test/web_task_environment.h"
+#import "ios/web/public/web_state.h"
 #import "testing/platform_test.h"
 #import "third_party/ocmock/OCMock/OCMock.h"
 #import "third_party/ocmock/gtest_support.h"
@@ -41,6 +55,14 @@
 
 namespace {
 
+constexpr char kTestUrl[] = "http://foo.com";
+constexpr char kFillDataUsername[] = "donut.guy@gmail.com";
+constexpr char kFillDataPassword[] = "super!secret";
+constexpr char kMainFrameId[] = "frameID";
+constexpr autofill::FormRendererId kFormRendererId(1);
+constexpr autofill::FieldRendererId kUsernameFieldRendererId(2);
+constexpr autofill::FieldRendererId kPasswordFieldRendererId(3);
+
 // Creates suggestion for a single username form.
 FormSuggestion* SuggestionForSingleUsernameForm() {
   return [FormSuggestion
@@ -64,6 +86,20 @@
   return l10n_util::GetNSString(IDS_IOS_PASSWORD_BOTTOM_SHEET_CONTINUE);
 }
 
+// Creates PasswordFormFillData to be processed for offering password
+// suggestions.
+autofill::PasswordFormFillData CreatePasswordFillData(
+    autofill::FormRendererId form_renderer_id,
+    autofill::FieldRendererId username_renderer_id,
+    autofill::FieldRendererId password_renderer_id) {
+  autofill::PasswordFormFillData form_fill_data;
+  test_helpers::SetPasswordFormFillData(
+      kTestUrl, "", form_renderer_id.value(), "", username_renderer_id.value(),
+      kFillDataUsername, "", password_renderer_id.value(), kFillDataPassword,
+      nullptr, nullptr, &form_fill_data);
+  return form_fill_data;
+}
+
 }  // namespace
 
 // Expose the internal disconnect function for testing purposes
@@ -88,7 +124,11 @@
 @property(nonatomic, assign) SuggestionProviderType type;
 @property(nonatomic, readonly) autofill::FillingProduct mainFillingProduct;
 
-// Creates a test provider with default suggesstions.
+// YES if the suggestion provider is used to provide suggestions on a single
+// username form.
+@property(nonatomic, readonly) BOOL forSingleUsernameForm;
+
+// Creates a test provider with default suggestions.
 + (instancetype)providerWithSuggestions;
 
 - (instancetype)initWithSuggestions:(NSArray<FormSuggestion*>*)suggestions;
@@ -134,6 +174,12 @@
   if (self) {
     _suggestions = [suggestions copy];
     _type = SuggestionProviderTypeUnknown;
+    // Detect whether the suggestions were set up for a single username form,
+    // based on the content of the suggestions.
+    for (FormSuggestion* suggestion in _suggestions) {
+      _forSingleUsernameForm =
+          _forSingleUsernameForm || suggestion.metadata.is_single_username_form;
+    }
   }
   return self;
 }
@@ -195,14 +241,20 @@
 class PasswordSuggestionBottomSheetMediatorTest : public PlatformTest {
  protected:
   PasswordSuggestionBottomSheetMediatorTest()
-      : test_web_state_(std::make_unique<web::FakeWebState>()),
-        profile_(TestProfileIOS::Builder().Build()) {
+      : web_state_(std::make_unique<web::FakeWebState>()),
+        web_state_ptr_(web_state_.get()) {
     web_state_list_ = std::make_unique<WebStateList>(&web_state_list_delegate_);
   }
 
   void SetUp() override {
-    test_web_state_->SetCurrentURL(URL());
+    web_state_->SetCurrentURL(GURL(kTestUrl));
 
+    auto prefs =
+        std::make_unique<sync_preferences::TestingPrefServiceSyncable>();
+    prefs_ptr_ = prefs.get();
+    RegisterProfilePrefs(prefs_ptr_->registry());
+
+    // Set up the Profile used by the webstate.
     TestProfileIOS::Builder builder;
     builder.AddTestingFactory(ios::FaviconServiceFactory::GetInstance(),
                               ios::FaviconServiceFactory::GetDefaultFactory());
@@ -219,18 +271,41 @@
         base::BindRepeating(
             &password_manager::BuildPasswordStore<
                 web::BrowserState, password_manager::TestPasswordStore>));
+    builder.SetPrefService(std::move(prefs));
     profile_ = std::move(builder).Build();
 
+    web_state_->SetBrowserState(profile_.get());
+
+    // Set up the javascript feature manager for the profile so no-op JS calls
+    // can be made on the fake frame.
+    web::test::OverrideJavaScriptFeatures(
+        profile_.get(),
+        {password_manager::PasswordManagerJavaScriptFeature::GetInstance()});
+
+    // Set up the frames manager so frames can be used.
+    auto frames_manager = std::make_unique<web::FakeWebFramesManager>();
+    frames_manager_ptr_ = frames_manager.get();
+    web_state_->SetWebFramesManager(std::move(frames_manager));
+
+    // Create the PasswordTabHelper so the password provider is available when
+    // the sheet V2 is used.
+    PasswordTabHelper::CreateForWebState(web_state_.get());
+
     consumer_ =
         OCMProtocolMock(@protocol(PasswordSuggestionBottomSheetConsumer));
     presenter_ = OCMStrictProtocolMock(
         @protocol(PasswordSuggestionBottomSheetPresenter));
 
+    params_.frame_id = kMainFrameId;
     params_.form_name = "form";
+    params_.form_renderer_id = kFormRendererId;
+    params_.field_renderer_id = kUsernameFieldRendererId;
     params_.field_identifier = "field_id";
     params_.field_type = "select-one";
     params_.type = "type";
-    params_.value = "value";
+    // Set the value to be empty so all the suggestions can be offered without
+    // doing any filtering based on prefix matching.
+    params_.value = "";
     params_.input_missing = false;
 
     suggestion_providers_ = @[];
@@ -239,27 +314,44 @@
   void TearDown() override { [mediator_ disconnect]; }
 
   void CreateMediator() {
-    FormSuggestionTabHelper::CreateForWebState(test_web_state_.get(),
+    // Create the FormSuggestionTabHelper with test providers used by password
+    // sheet v1.
+    FormSuggestionTabHelper::CreateForWebState(web_state_.get(),
                                                suggestion_providers_);
 
     web_state_list_->InsertWebState(
-        std::move(test_web_state_),
+        std::move(web_state_),
         WebStateList::InsertionParams::Automatic().Activate());
 
-    prefs_ = std::make_unique<TestingPrefServiceSimple>();
-    prefs_->registry()->RegisterIntegerPref(
-        prefs::kIosPasswordBottomSheetDismissCount, 0);
+    // Create a frame so password suggestions can be provided for that frame.
+    auto main_frame = web::FakeWebFrame::Create(
+        kMainFrameId, /*is_main_frame=*/true, GURL(kTestUrl));
+    main_frame_ptr_ = main_frame.get();
+    main_frame_ptr_->set_browser_state(profile_.get());
+    frames_manager_ptr_->AddWebFrame(std::move(main_frame));
 
     store_ =
         base::WrapRefCounted(static_cast<password_manager::TestPasswordStore*>(
             IOSChromeProfilePasswordStoreFactory::GetForProfile(
                 profile_.get(), ServiceAccessType::EXPLICIT_ACCESS)
                 .get()));
+
+    // Set up the fill data for the real password provider used by password
+    // sheet V2 when the mediator is tested with providers.
+    if ([suggestion_providers_ count] > 0) {
+      ASSERT_EQ(1u, [suggestion_providers_ count]);
+      PasswordSuggestionBottomSheetMediatorTestSuggestionProvider* provider =
+          base::apple::ObjCCastStrict<
+              PasswordSuggestionBottomSheetMediatorTestSuggestionProvider>(
+              [suggestion_providers_ objectAtIndex:0]);
+      SetUpFillDataInPasswordManager(provider.forSingleUsernameForm);
+    }
+
     mediator_ = [[PasswordSuggestionBottomSheetMediator alloc]
           initWithWebStateList:web_state_list_.get()
                  faviconLoader:IOSChromeFaviconLoaderFactory::GetForProfile(
                                    profile_.get())
-                   prefService:prefs_.get()
+                   prefService:prefs_ptr_
                         params:params_
                   reauthModule:nil
                            URL:URL()
@@ -268,6 +360,9 @@
         sharedURLLoaderFactory:nullptr
              engagementTracker:nil
                      presenter:nil];
+
+    // Run the queued JS feature callback.
+    base::RunLoop().RunUntilIdle();
   }
 
   // Creates the bottom sheet mediator with custom suggestions `providers`.
@@ -284,19 +379,44 @@
             providerWithSuggestions] ]);
   }
 
+  void SetUpFillDataInPasswordManager(bool for_single_username_form) {
+    SharedPasswordController* shared_password_controller =
+        PasswordTabHelper::FromWebState(web_state_ptr_)
+            ->GetSharedPasswordController();
+
+    // Set up the fill data based on whether or not the form is a single
+    // username form. Single username forms do not have a renderer id for their
+    // password field.
+    autofill::PasswordFormFillData fill_data =
+        for_single_username_form
+            ? CreatePasswordFillData(kFormRendererId, kUsernameFieldRendererId,
+                                     autofill::FieldRendererId(0))
+            : CreatePasswordFillData(kFormRendererId, kUsernameFieldRendererId,
+                                     kPasswordFieldRendererId);
+
+    [shared_password_controller
+        processPasswordFormFillData:fill_data
+                         forFrameId:kMainFrameId
+                        isMainFrame:YES
+                  forSecurityOrigin:main_frame_ptr_->GetSecurityOrigin()];
+  }
+
   GURL URL() { return GURL("http://foo.com"); }
 
   web::WebTaskEnvironment task_environment_;
-  std::unique_ptr<web::FakeWebState> test_web_state_;
+  std::unique_ptr<TestProfileIOS> profile_;
+  raw_ptr<sync_preferences::TestingPrefServiceSyncable> prefs_ptr_;
   FakeWebStateListDelegate web_state_list_delegate_;
   std::unique_ptr<WebStateList> web_state_list_;
-  std::unique_ptr<TestProfileIOS> profile_;
+  std::unique_ptr<web::FakeWebState> web_state_;
+  raw_ptr<web::WebState> web_state_ptr_;
+  raw_ptr<web::FakeWebFramesManager> frames_manager_ptr_;
+  raw_ptr<web::FakeWebFrame> main_frame_ptr_;
   scoped_refptr<password_manager::TestPasswordStore> store_;
   id consumer_;
   NSArray<id<FormSuggestionProvider>>* suggestion_providers_;
   autofill::FormActivityParams params_;
   PasswordSuggestionBottomSheetMediator* mediator_;
-  std::unique_ptr<TestingPrefServiceSimple> prefs_;
   id presenter_;
 };
 
@@ -323,7 +443,7 @@
 // Tests setting the consumer when suggestions are available for a single
 // username form and the feature is enabled.
 TEST_F(PasswordSuggestionBottomSheetMediatorTest,
-       WithSuggestions_ForSingleUsernameForm_FeatureEnabled) {
+       WithSuggestions_ForSingleUsernameForm) {
   id<FormSuggestionProvider> provider =
       [[PasswordSuggestionBottomSheetMediatorTestSuggestionProvider alloc]
           initWithSuggestions:@[ SuggestionForSingleUsernameForm() ]];
@@ -344,17 +464,17 @@
   CreateMediatorWithDefaultSuggestions();
   ASSERT_TRUE(mediator_);
 
-  EXPECT_EQ(
-      prefs_.get()->GetInteger(prefs::kIosPasswordBottomSheetDismissCount), 0);
+  EXPECT_EQ(prefs_ptr_->GetInteger(prefs::kIosPasswordBottomSheetDismissCount),
+            0);
   [mediator_ onDismissWithoutAnyPasswordAction];
-  EXPECT_EQ(
-      prefs_.get()->GetInteger(prefs::kIosPasswordBottomSheetDismissCount), 1);
+  EXPECT_EQ(prefs_ptr_->GetInteger(prefs::kIosPasswordBottomSheetDismissCount),
+            1);
   [mediator_ onDismissWithoutAnyPasswordAction];
-  EXPECT_EQ(
-      prefs_.get()->GetInteger(prefs::kIosPasswordBottomSheetDismissCount), 2);
+  EXPECT_EQ(prefs_ptr_->GetInteger(prefs::kIosPasswordBottomSheetDismissCount),
+            2);
   [mediator_ onDismissWithoutAnyPasswordAction];
-  EXPECT_EQ(
-      prefs_.get()->GetInteger(prefs::kIosPasswordBottomSheetDismissCount), 3);
+  EXPECT_EQ(prefs_ptr_->GetInteger(prefs::kIosPasswordBottomSheetDismissCount),
+            3);
 
   // Expect failure after 3 times.
 #if defined(GTEST_HAS_DEATH_TEST)
diff --git a/ios/chrome/browser/settings/ui_bundled/OWNERS b/ios/chrome/browser/settings/ui_bundled/OWNERS
index a07bacc..7434234 100644
--- a/ios/chrome/browser/settings/ui_bundled/OWNERS
+++ b/ios/chrome/browser/settings/ui_bundled/OWNERS
@@ -3,5 +3,6 @@
 gambard@chromium.org
 jlebel@chromium.org
 fernandex@google.com
+scottyoder@google.com
 
 per-file signin*=arthurmilchior@chromium.org
diff --git a/ios/chrome/browser/shared/model/prefs/browser_prefs.mm b/ios/chrome/browser/shared/model/prefs/browser_prefs.mm
index 2257bee7..09755e5 100644
--- a/ios/chrome/browser/shared/model/prefs/browser_prefs.mm
+++ b/ios/chrome/browser/shared/model/prefs/browser_prefs.mm
@@ -195,6 +195,14 @@
 inline constexpr char kSuggestionGroupVisibility[] =
     "omnibox.suggestionGroupVisibility";
 
+// Deprecated 05/2025.
+inline constexpr char kSyncCacheGuid[] = "sync.cache_guid";
+inline constexpr char kSyncBirthday[] = "sync.birthday";
+inline constexpr char kSyncBagOfChips[] = "sync.bag_of_chips";
+inline constexpr char kSyncLastSyncedTime[] = "sync.last_synced_time";
+inline constexpr char kSyncLastPollTime[] = "sync.last_poll_time";
+inline constexpr char kSyncPollInterval[] = "sync.short_poll_interval";
+
 // Migrates a boolean pref from source to target PrefService.
 void MigrateBooleanPref(std::string_view pref_name,
                         PrefService* target_pref_service,
@@ -1058,6 +1066,14 @@
 
   // Deprecated 04/2025.
   registry->RegisterDictionaryPref(kSuggestionGroupVisibility);
+
+  // Deprecated 05/2025.
+  registry->RegisterStringPref(kSyncCacheGuid, std::string());
+  registry->RegisterStringPref(kSyncBirthday, std::string());
+  registry->RegisterStringPref(kSyncBagOfChips, std::string());
+  registry->RegisterTimePref(kSyncLastSyncedTime, base::Time());
+  registry->RegisterTimePref(kSyncLastPollTime, base::Time());
+  registry->RegisterTimeDeltaPref(kSyncPollInterval, base::TimeDelta());
 }
 
 // This method should be periodically pruned of year+ old migrations.
@@ -1259,6 +1275,14 @@
 
   // Added 04/2025.
   prefs->ClearPref(kSuggestionGroupVisibility);
+
+  // Added 05/2025.
+  prefs->ClearPref(kSyncCacheGuid);
+  prefs->ClearPref(kSyncBirthday);
+  prefs->ClearPref(kSyncBagOfChips);
+  prefs->ClearPref(kSyncLastSyncedTime);
+  prefs->ClearPref(kSyncLastPollTime);
+  prefs->ClearPref(kSyncPollInterval);
 }
 
 void MigrateObsoleteUserDefault() {
diff --git a/ios/chrome/browser/shared/public/commands/credential_provider_promo_commands.h b/ios/chrome/browser/shared/public/commands/credential_provider_promo_commands.h
index 2447e35..51f1185d 100644
--- a/ios/chrome/browser/shared/public/commands/credential_provider_promo_commands.h
+++ b/ios/chrome/browser/shared/public/commands/credential_provider_promo_commands.h
@@ -12,6 +12,8 @@
                                          // the promo before.
   SetUpList,                             // User has clicked the Autofill item
                                          // in the Set Up List on the NTP.
+  TipsNotification,                      // User has tapped the CPE Tips
+                                         // notification.
 };
 
 // Commands to show app-wide promos.
diff --git a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/grid/tab_group_header.mm b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/grid/tab_group_header.mm
index a17c1f8..075c51d2 100644
--- a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/grid/tab_group_header.mm
+++ b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/grid/tab_group_header.mm
@@ -14,6 +14,12 @@
   UILabel* _titleView;
   // Dot view.
   UIView* _coloredDotView;
+  // Container for the whole title.
+  UIView* _container;
+  // Constraints for regular width.
+  NSArray<NSLayoutConstraint*>* _regularWidthConstraints;
+  // Constraints for compact width.
+  NSArray<NSLayoutConstraint*>* _compactWidthConstraints;
 }
 
 - (instancetype)initWithFrame:(CGRect)frame {
@@ -21,22 +27,54 @@
   if (self) {
     _titleView = [self titleView];
     _coloredDotView = [self coloredDotView];
+    _container = [[UIView alloc] init];
+    _container.translatesAutoresizingMaskIntoConstraints = NO;
 
-    [self addSubview:_coloredDotView];
-    [self addSubview:_titleView];
+    [self addSubview:_container];
+    [_container addSubview:_coloredDotView];
+    [_container addSubview:_titleView];
+
+    _regularWidthConstraints = @[
+      [_container.centerXAnchor constraintEqualToAnchor:self.centerXAnchor],
+      [_container.widthAnchor
+          constraintLessThanOrEqualToAnchor:self.widthAnchor],
+    ];
+
+    _compactWidthConstraints = @[
+      [_container.leadingAnchor constraintEqualToAnchor:self.leadingAnchor],
+      [_container.trailingAnchor constraintEqualToAnchor:self.trailingAnchor],
+    ];
 
     [NSLayoutConstraint activateConstraints:@[
+      [_coloredDotView.leadingAnchor
+          constraintEqualToAnchor:_container.leadingAnchor],
+      [_coloredDotView.centerYAnchor
+          constraintEqualToAnchor:_titleView.centerYAnchor],
+
       [_titleView.leadingAnchor
           constraintEqualToAnchor:_coloredDotView.trailingAnchor
                          constant:kDotTitleSeparationMargin],
-      [_coloredDotView.centerYAnchor
-          constraintEqualToAnchor:_titleView.centerYAnchor],
-      [_coloredDotView.leadingAnchor
-          constraintEqualToAnchor:self.leadingAnchor],
-      [_titleView.trailingAnchor constraintEqualToAnchor:self.trailingAnchor],
-      [_titleView.topAnchor constraintEqualToAnchor:self.topAnchor],
-      [_titleView.bottomAnchor constraintEqualToAnchor:self.bottomAnchor],
+
+      [_titleView.trailingAnchor
+          constraintEqualToAnchor:_container.trailingAnchor],
+      [_titleView.topAnchor constraintEqualToAnchor:_container.topAnchor],
+      [_titleView.bottomAnchor constraintEqualToAnchor:_container.bottomAnchor],
+
+      [_container.topAnchor constraintEqualToAnchor:self.topAnchor],
+      [_container.bottomAnchor constraintEqualToAnchor:self.bottomAnchor],
     ]];
+
+    if (self.traitCollection.horizontalSizeClass ==
+        UIUserInterfaceSizeClassRegular) {
+      [NSLayoutConstraint activateConstraints:_regularWidthConstraints];
+    } else {
+      [NSLayoutConstraint activateConstraints:_compactWidthConstraints];
+    }
+
+    if (@available(iOS 17, *)) {
+      [self registerForTraitChanges:@[ UITraitHorizontalSizeClass.class ]
+                         withAction:@selector(horizontalSizeClassDidChange)];
+    }
   }
   return self;
 }
@@ -92,4 +130,16 @@
   return titleLabel;
 }
 
+// Called when the horizontal size class have changed.
+- (void)horizontalSizeClassDidChange {
+  if (self.traitCollection.horizontalSizeClass ==
+      UIUserInterfaceSizeClassRegular) {
+    [NSLayoutConstraint deactivateConstraints:_compactWidthConstraints];
+    [NSLayoutConstraint activateConstraints:_regularWidthConstraints];
+  } else {
+    [NSLayoutConstraint deactivateConstraints:_regularWidthConstraints];
+    [NSLayoutConstraint activateConstraints:_compactWidthConstraints];
+  }
+}
+
 @end
diff --git a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/tab_group_view_controller.mm b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/tab_group_view_controller.mm
index 52c71fa..ec0816b 100644
--- a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/tab_group_view_controller.mm
+++ b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/tab_group_view_controller.mm
@@ -20,6 +20,7 @@
 #import "ios/chrome/browser/shared/public/features/features.h"
 #import "ios/chrome/browser/shared/ui/elements/extended_touch_target_button.h"
 #import "ios/chrome/browser/shared/ui/symbols/symbols.h"
+#import "ios/chrome/browser/shared/ui/util/uikit_ui_util.h"
 #import "ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/grid/grid_constants.h"
 #import "ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/grid/tab_group_grid_view_controller.h"
 #import "ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_grid_paging.h"
@@ -86,6 +87,8 @@
       [UIBackgroundConfiguration clearConfiguration];
   background_configuration.visualEffect = [UIBlurEffect
       effectWithStyle:UIBlurEffectStyleSystemUltraThinMaterialDark];
+  background_configuration.backgroundColor =
+      [[UIColor colorNamed:kGrey200Color] colorWithAlphaComponent:0.35];
 
   UIButtonConfiguration* configuration =
       [UIButtonConfiguration plainButtonConfiguration];
@@ -817,15 +820,23 @@
   titleLabel.adjustsFontForContentSizeCategory = YES;
   titleLabel.accessibilityIdentifier = kTabGroupViewTitleIdentifier;
   titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
-  UIFontDescriptor* boldDescriptor = [[UIFontDescriptor
-      preferredFontDescriptorWithTextStyle:UIFontTextStyleHeadline]
-      fontDescriptorWithSymbolicTraits:UIFontDescriptorTraitBold];
+
   NSMutableAttributedString* boldTitle =
       [[NSMutableAttributedString alloc] initWithString:_groupTitle];
+  if (IsContainedTabGroupEnabled()) {
+    [boldTitle addAttribute:NSFontAttributeName
+                      value:PreferredFontForTextStyle(UIFontTextStyleTitle3,
+                                                      UIFontWeightBold)
+                      range:NSMakeRange(0, _groupTitle.length)];
+  } else {
+    UIFontDescriptor* boldDescriptor = [[UIFontDescriptor
+        preferredFontDescriptorWithTextStyle:UIFontTextStyleHeadline]
+        fontDescriptorWithSymbolicTraits:UIFontDescriptorTraitBold];
 
-  [boldTitle addAttribute:NSFontAttributeName
-                    value:[UIFont fontWithDescriptor:boldDescriptor size:0.0]
-                    range:NSMakeRange(0, _groupTitle.length)];
+    [boldTitle addAttribute:NSFontAttributeName
+                      value:[UIFont fontWithDescriptor:boldDescriptor size:0.0]
+                      range:NSMakeRange(0, _groupTitle.length)];
+  }
   titleLabel.attributedText = boldTitle;
 
   return titleLabel;
diff --git a/ios/chrome/browser/tips_notifications/model/tips_notification_client.h b/ios/chrome/browser/tips_notifications/model/tips_notification_client.h
index 81ea6142..345eaa9 100644
--- a/ios/chrome/browser/tips_notifications/model/tips_notification_client.h
+++ b/ios/chrome/browser/tips_notifications/model/tips_notification_client.h
@@ -110,6 +110,9 @@
   // sent.
   bool ShouldSendEnhancedSafeBrowsing(ProfileIOS* profile);
 
+  // Returns true if the CPE notification should be sent.
+  bool ShouldSendCPE(ProfileIOS* profile);
+
   // Returns `true` if there is foreground active browser.
   bool IsSceneLevelForegroundActive();
 
@@ -123,6 +126,7 @@
   void ShowOmniboxPosition(Browser* browser);
   void ShowLensPromo(Browser* browser);
   void ShowEnhancedSafeBrowsingPromo(Browser* browser);
+  void ShowCPEPromo(Browser* browser);
 
   // Helpers to store state in local state prefs.
   void MarkNotificationTypeSent(TipsNotificationType type);
diff --git a/ios/chrome/browser/tips_notifications/model/tips_notification_client.mm b/ios/chrome/browser/tips_notifications/model/tips_notification_client.mm
index b8abbd8..62cecb52 100644
--- a/ios/chrome/browser/tips_notifications/model/tips_notification_client.mm
+++ b/ios/chrome/browser/tips_notifications/model/tips_notification_client.mm
@@ -11,6 +11,7 @@
 #import "base/task/bind_post_task.h"
 #import "base/time/time.h"
 #import "components/feature_engagement/public/tracker.h"
+#import "components/password_manager/core/browser/password_manager_util.h"
 #import "components/prefs/pref_registry_simple.h"
 #import "components/prefs/pref_service.h"
 #import "components/safe_browsing/core/common/safe_browsing_prefs.h"
@@ -40,6 +41,7 @@
 #import "ios/chrome/browser/shared/public/commands/application_commands.h"
 #import "ios/chrome/browser/shared/public/commands/browser_coordinator_commands.h"
 #import "ios/chrome/browser/shared/public/commands/command_dispatcher.h"
+#import "ios/chrome/browser/shared/public/commands/credential_provider_promo_commands.h"
 #import "ios/chrome/browser/shared/public/commands/docking_promo_commands.h"
 #import "ios/chrome/browser/shared/public/commands/settings_commands.h"
 #import "ios/chrome/browser/shared/public/commands/show_signin_command.h"
@@ -470,8 +472,9 @@
       return ShouldSendLens(profile);
     case TipsNotificationType::kEnhancedSafeBrowsing:
       return ShouldSendEnhancedSafeBrowsing(profile);
-    case TipsNotificationType::kLensOverlay:
     case TipsNotificationType::kCPE:
+      return ShouldSendCPE(profile);
+    case TipsNotificationType::kLensOverlay:
     case TipsNotificationType::kIncognitoLock:
     case TipsNotificationType::kError:
       NOTREACHED();
@@ -562,6 +565,23 @@
          !safe_browsing::IsEnhancedProtectionEnabled(*user_prefs);
 }
 
+bool TipsNotificationClient::ShouldSendCPE(ProfileIOS* profile) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (!local_state_->GetBoolean(
+          prefs::kIosCredentialProviderPromoPolicyEnabled)) {
+    return false;
+  }
+  bool is_credential_provider_enabled =
+      password_manager_util::IsCredentialProviderEnabledOnStartup(local_state_);
+  if (is_credential_provider_enabled) {
+    return false;
+  }
+  // TODO(crbug.com/417940156): Refine CPE trigger criteria to include:
+  //   * have not seen the promo in the last 30 days, AND
+  //   * have used autofill in the last 30 days.
+  return true;
+}
+
 bool TipsNotificationClient::IsSceneLevelForegroundActive() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   return GetActiveForegroundBrowser() != nullptr;
@@ -596,8 +616,10 @@
     case TipsNotificationType::kEnhancedSafeBrowsing:
       ShowEnhancedSafeBrowsingPromo(browser);
       break;
-    case TipsNotificationType::kLensOverlay:
     case TipsNotificationType::kCPE:
+      ShowCPEPromo(browser);
+      break;
+    case TipsNotificationType::kLensOverlay:
     case TipsNotificationType::kIncognitoLock:
     case TipsNotificationType::kError:
       NOTREACHED();
@@ -672,6 +694,14 @@
       showEnhancedSafeBrowsingPromo];
 }
 
+void TipsNotificationClient::ShowCPEPromo(Browser* browser) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  [HandlerForProtocol(browser->GetCommandDispatcher(),
+                      CredentialProviderPromoCommands)
+      showCredentialProviderPromoWithTrigger:CredentialProviderPromoTrigger::
+                                                 TipsNotification];
+}
+
 void TipsNotificationClient::MarkNotificationTypeSent(
     TipsNotificationType type) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
diff --git a/ios/chrome/browser/tips_notifications/model/utils.mm b/ios/chrome/browser/tips_notifications/model/utils.mm
index 522f912..09127b4 100644
--- a/ios/chrome/browser/tips_notifications/model/utils.mm
+++ b/ios/chrome/browser/tips_notifications/model/utils.mm
@@ -57,8 +57,10 @@
     case TipsNotificationType::kEnhancedSafeBrowsing:
       return {IDS_IOS_NOTIFICATIONS_TIPS_ENHANCED_SAFE_BROWSING_TITLE,
               IDS_IOS_NOTIFICATIONS_TIPS_ENHANCED_SAFE_BROWSING_BODY};
-    case TipsNotificationType::kLensOverlay:
     case TipsNotificationType::kCPE:
+      return {IDS_IOS_NOTIFICATIONS_TIPS_CPE_TITLE,
+              IDS_IOS_NOTIFICATIONS_TIPS_CPE_BODY};
+    case TipsNotificationType::kLensOverlay:
     case TipsNotificationType::kIncognitoLock:
     case TipsNotificationType::kError:
       NOTREACHED();
@@ -259,7 +261,13 @@
       return NotificationType::kTipsLens;
     case TipsNotificationType::kEnhancedSafeBrowsing:
       return NotificationType::kTipsEnhancedSafeBrowsing;
-    default:
+    case TipsNotificationType::kLensOverlay:
+      return NotificationType::kTipsLensOverlay;
+    case TipsNotificationType::kCPE:
+      return NotificationType::kTipsCPE;
+    case TipsNotificationType::kIncognitoLock:
+      return NotificationType::kTipsIncognitoLock;
+    case TipsNotificationType::kError:
       NOTREACHED();
   }
 }
diff --git a/ios/chrome/common/ui/confirmation_alert/confirmation_alert_view_controller.h b/ios/chrome/common/ui/confirmation_alert/confirmation_alert_view_controller.h
index 956e5ef..37af96e 100644
--- a/ios/chrome/common/ui/confirmation_alert/confirmation_alert_view_controller.h
+++ b/ios/chrome/common/ui/confirmation_alert/confirmation_alert_view_controller.h
@@ -168,6 +168,9 @@
 // Button for the primary action string.
 @property(nonatomic, readonly) UIButton* primaryActionButton;
 
+// Button for the secondary action string.
+@property(nonatomic, readonly) UIButton* secondaryActionButton;
+
 // Color used for the activity indicator on the primary button when in the
 // loading state. Defaults to kSolidWhiteColor.
 @property(nonatomic, strong) UIColor* activityIndicatorColor;
diff --git a/ios/chrome/common/ui/confirmation_alert/confirmation_alert_view_controller.mm b/ios/chrome/common/ui/confirmation_alert/confirmation_alert_view_controller.mm
index b281c2b7..29f3144c 100644
--- a/ios/chrome/common/ui/confirmation_alert/confirmation_alert_view_controller.mm
+++ b/ios/chrome/common/ui/confirmation_alert/confirmation_alert_view_controller.mm
@@ -112,7 +112,6 @@
 
 // References to the UI properties that need to be updated when the trait
 // collection changes.
-@property(nonatomic, strong) UIButton* secondaryActionButton;
 @property(nonatomic, strong) UIButton* tertiaryActionButton;
 @property(nonatomic, strong) UINavigationBar* navigationBar;
 @property(nonatomic, strong) UIImageView* imageView;
@@ -862,7 +861,7 @@
   }
 
   if (self.secondaryActionString) {
-    self.secondaryActionButton = [self createSecondaryActionButton];
+    _secondaryActionButton = [self createSecondaryActionButton];
     [actionStackView addArrangedSubview:self.secondaryActionButton];
   }
   // Tertiary button should show above the primary one.
diff --git a/ios/chrome/share_extension/extended_share_view_controller.mm b/ios/chrome/share_extension/extended_share_view_controller.mm
index 6b8916f9..c423587b 100644
--- a/ios/chrome/share_extension/extended_share_view_controller.mm
+++ b/ios/chrome/share_extension/extended_share_view_controller.mm
@@ -173,9 +173,9 @@
   [moreActionsAlertController addAction:cancelAlertAction];
 
   moreActionsAlertController.popoverPresentationController.sourceView =
-      shareExtensionSheet.view;
+      self.shareSheet.secondaryActionButton;
   moreActionsAlertController.popoverPresentationController.sourceRect =
-      shareExtensionSheet.view.bounds;
+      self.shareSheet.secondaryActionButton.bounds;
 
   [self.shareSheet presentViewController:moreActionsAlertController
                                 animated:YES
diff --git a/ios_internal b/ios_internal
index 79e80e72..a1ef792 160000
--- a/ios_internal
+++ b/ios_internal
@@ -1 +1 @@
-Subproject commit 79e80e721cb09978055fddd62b21b31d68192891
+Subproject commit a1ef7929fc7d479af95d97f8ea9cfeefc2eed5e6
diff --git a/media/audio/BUILD.gn b/media/audio/BUILD.gn
index 6192d8f..fb7cfb1 100644
--- a/media/audio/BUILD.gn
+++ b/media/audio/BUILD.gn
@@ -246,7 +246,6 @@
       "dxguid.lib",
       "setupapi.lib",
       "winmm.lib",
-      "mmdevapi.lib",
     ]
   }
 
@@ -521,15 +520,6 @@
       "win/audio_session_event_listener_win_unittest.cc",
       "win/core_audio_util_win_unittest.cc",
       "win/device_enumeration_win_unittest.cc",
-      "win/test_support/fake_iactivate_audio_interface_async_operation.cc",
-      "win/test_support/fake_iactivate_audio_interface_async_operation.h",
-      "win/test_support/fake_iaudio_capture_client.cc",
-      "win/test_support/fake_iaudio_capture_client.h",
-      "win/test_support/fake_iaudio_client.cc",
-      "win/test_support/fake_iaudio_client.h",
-      "win/test_support/fake_win_wasapi_environment.cc",
-      "win/test_support/fake_win_wasapi_environment.h",
-      "win/test_support/wasapi_test_error_code.h",
     ]
 
     deps += [ "//media/gpu:gpu" ]
diff --git a/media/audio/audio_input_stream_data_interceptor.h b/media/audio/audio_input_stream_data_interceptor.h
index 5696ed1e..a886ce4 100644
--- a/media/audio/audio_input_stream_data_interceptor.h
+++ b/media/audio/audio_input_stream_data_interceptor.h
@@ -61,9 +61,6 @@
 
   void OnError() override;
 
-  // Returns the underlying stream.
-  AudioInputStream* GetUnderlyingStreamForTesting() const { return stream_; }
-
  private:
   const CreateDebugRecorderCB create_debug_recorder_cb_;
   std::unique_ptr<AudioDebugRecorder> debug_recorder_;
diff --git a/media/audio/win/audio_low_latency_input_win.cc b/media/audio/win/audio_low_latency_input_win.cc
index 6ff0a11..0d736b5 100644
--- a/media/audio/win/audio_low_latency_input_win.cc
+++ b/media/audio/win/audio_low_latency_input_win.cc
@@ -6,10 +6,6 @@
 
 #include <objbase.h>
 
-#include <mmdeviceapi.h>
-
-#include <audioclient.h>
-#include <audioclientactivationparams.h>
 #include <combaseapi.h>
 #include <ksmedia.h>
 #include <propkey.h>
@@ -19,7 +15,6 @@
 #include <memory>
 #include <utility>
 
-#include "base/check_deref.h"
 #include "base/compiler_specific.h"
 #include "base/containers/span.h"
 #include "base/functional/callback.h"
@@ -30,7 +25,6 @@
 #include "base/strings/strcat.h"
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
-#include "base/synchronization/waitable_event.h"
 #include "base/trace_event/common/trace_event_common.h"
 #include "base/trace_event/trace_event.h"
 #include "base/win/core_winrt_util.h"
@@ -38,7 +32,6 @@
 #include "base/win/scoped_variant.h"
 #include "base/win/vector.h"
 #include "base/win/windows_version.h"
-#include "media/audio/application_loopback_device_helper.h"
 #include "media/audio/audio_device_description.h"
 #include "media/audio/audio_device_name.h"
 #include "media/audio/audio_features.h"
@@ -198,15 +191,6 @@
                               abs_delta_time);
 }
 
-WASAPIAudioInputStream::ActivateAudioInterfaceAsyncCallback&
-GetActivateAudioInterfaceAsyncCallback() {
-  static base::NoDestructor<
-      WASAPIAudioInputStream::ActivateAudioInterfaceAsyncCallback>
-      activate_audio_interface_async_callback{
-          base::BindRepeating(&ActivateAudioInterfaceAsync)};
-  return *activate_audio_interface_async_callback;
-}
-
 }  // namespace
 
 // Counts how often an OS capture callback reports a data discontinuity and logs
@@ -463,66 +447,6 @@
       AudioDeviceDescription::kDefaultDeviceId;
 };
 
-// Helper class to synchronously wait for the activation of an audio client
-// during a call to ActivateAudioInterfaceAsync.
-class WASAPIAudioInputStream::AudioClientActivationHandler
-    : public Microsoft::WRL::RuntimeClass<
-          Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom>,
-          Microsoft::WRL::FtmBase,
-          IActivateAudioInterfaceCompletionHandler> {
- public:
-  friend class FakeWinWASAPIEnvironment;
-
-  AudioClientActivationHandler() = default;
-  ~AudioClientActivationHandler() override = default;
-
-  // When called, returns only after the activation is completed.
-  HRESULT WaitAndGetAudioClient(ComPtr<IAudioClient>* audio_client,
-                                base::TimeDelta async_activation_timeout_ms) {
-    // Wait for a maximum of 10 seconds for the activation to complete.
-    if (!wait_event_.TimedWait(async_activation_timeout_ms)) {
-      return E_FAIL;
-    }
-
-    // If the activation was successful, move the audio client to the output
-    // parameter.
-    if (SUCCEEDED(activation_result_)) {
-      *audio_client = std::move(audio_client_);
-    }
-    return activation_result_;
-  }
-
- private:
-  // IActivateAudioInterfaceAudioClientActivationHandler::ActivateCompleted
-  // implementation.
-  // Called by the OS when the activation is completed.
-  IFACEMETHODIMP ActivateCompleted(
-      IActivateAudioInterfaceAsyncOperation* activate_operation) override {
-    HRESULT hr_activate = S_OK;
-    ComPtr<IAudioClient> audio_client = nullptr;
-    activation_result_ =
-        activate_operation->GetActivateResult(&hr_activate, &audio_client);
-    if (FAILED(activation_result_)) {
-      return activation_result_;
-    }
-
-    activation_result_ = hr_activate;
-    if (SUCCEEDED(activation_result_)) {
-      audio_client_ = std::move(audio_client);
-    }
-    wait_event_.Signal();
-
-    // If the activation was successful, the audio client is now available.
-    return activation_result_;
-  }
-
-  ComPtr<IAudioClient> audio_client_ = nullptr;
-  HRESULT activation_result_ = E_FAIL;
-  base::WaitableEvent wait_event_{
-      base::WaitableEvent::ResetPolicy::AUTOMATIC,
-      base::WaitableEvent::InitialState::NOT_SIGNALED};
-};
-
 // Creates an audio input stream given preferred audio parameters in `params`
 // and an input device given by `device_id`.
 // Support for system effects exists behind a command-line flag called
@@ -588,9 +512,7 @@
           base::BindRepeating(
               static_cast<void (WASAPIAudioInputStream::*)(std::string)>(
                   &WASAPIAudioInputStream::SendLogMessage),
-              base::Unretained(this)))),
-      is_application_loopback_capture_(
-          AudioDeviceDescription::IsApplicationLoopbackDevice(device_id)) {
+              base::Unretained(this)))) {
   DCHECK(manager_);
   DCHECK(!device_id_.empty());
   DCHECK(!log_callback_.is_null());
@@ -683,36 +605,25 @@
     return OpenOutcome::kAlreadyOpen;
   }
 
-  HRESULT hr = S_OK;
-  // Application loopback captures do not get audio from an endpoint device, but
-  // rather from an audio interface.
-  if (!is_application_loopback_capture_) {
-    // Obtain a reference to the IMMDevice interface of the capturing device
-    // with the specified unique identifier or role which was set at
-    // construction.
-    hr = SetCaptureDevice();
-    if (FAILED(hr)) {
-      ReportOpenResult(hr);
-      return OpenOutcome::kFailed;
-    }
-  }
-
-  // Activate the AudioClient interface. This is done differently depending on
-  // whether the device is an application device or not. For application
-  // devices, a special activation method must be used to activate the audio
-  // client asynchronously.
-  hr = ActivateAudioClientInterface();
+  // Obtain a reference to the IMMDevice interface of the capturing device with
+  // the specified unique identifier or role which was set at construction.
+  HRESULT hr = SetCaptureDevice();
   if (FAILED(hr)) {
-    open_result_ = OPEN_RESULT_ACTIVATION_FAILED;
     ReportOpenResult(hr);
     return OpenOutcome::kFailed;
   }
 
-  // Application loopback captures do not get audio from an endpoint device.
-  if (!is_application_loopback_capture_) {
-    // Check if raw audio processing is supported for the selected capture
-    // device.
-    raw_processing_supported_ = RawProcessingSupported();
+  // Check if raw audio processing is supported for the selected capture device.
+  raw_processing_supported_ = RawProcessingSupported();
+
+  // Obtain an IAudioClient interface which enables us to create and initialize
+  // an audio stream between an audio application and the audio engine.
+  hr = endpoint_device_->Activate(__uuidof(IAudioClient), CLSCTX_ALL, nullptr,
+                                  &audio_client_);
+  if (FAILED(hr)) {
+    open_result_ = OPEN_RESULT_ACTIVATION_FAILED;
+    ReportOpenResult(hr);
+    return OpenOutcome::kFailed;
   }
 
   // Raw audio capture suppresses processing that down mixes e.g. a microphone
@@ -720,15 +631,15 @@
   // format. Chrome only supports a maximum number of input channels given by
   // media::kMaxConcurrentChannels. Therefore, one additional test is needed
   // before stating that raw audio processing can be supported.
-  // Failure will not prevent opening but the method must succeed to be able
-  // to select raw input capture mode.
+  // Failure will not prevent opening but the method must succeed to be able to
+  // select raw input capture mode.
   WORD audio_engine_channels = 0;
   hr = GetAudioEngineNumChannels(&audio_engine_channels);
 
-  // Attempt to enable communications category and raw capture mode on the
-  // audio stream. Ignoring return value since the method logs its own error
-  // messages and it should be OK to continue opening the stream even after a
-  // failure.
+  // Attempt to enable communications category and raw capture mode on the audio
+  // stream. Avoid using raw capture if echo cancellation has been requested.
+  // Ignoring return value since the method logs its own error messages
+  // and it should be OK to continue opening the stream even after a failure.
   if (raw_processing_supported_ &&
       !AudioDeviceDescription::IsLoopbackDevice(device_id_) && SUCCEEDED(hr)) {
     SetCommunicationsCategoryAndMaybeRawCaptureMode(audio_engine_channels);
@@ -806,15 +717,6 @@
   // using SetAutomaticGainControl().
   StartAgc();
 
-  // Waiting for the first audio sample ready event to be signaled is only
-  // needed for application devices. We need to do it because, due to a Windows
-  // bug, the value returned by GetBufferSize() can not be trusted until we get
-  // the first sample.
-  // https://crbug.com/411452039
-  if (!is_application_loopback_capture_) {
-    CreateFifoIfNeeded();
-  }
-
   // Create and start the thread that will drive the capturing by waiting for
   // capture events.
   DCHECK(!capture_thread_.get());
@@ -924,9 +826,8 @@
   DCHECK_LE(volume, 1.0);
   SendLogMessage("%s({volume=%.2f} [opened=%s])", __func__, volume,
                  opened_ ? "true" : "false");
-  if (!opened_ || !simple_audio_volume_) {
+  if (!opened_)
     return;
-  }
 
   // Set a new master volume level. Valid volume levels are in the range
   // 0.0 to 1.0. Ignore volume-change events.
@@ -947,9 +848,8 @@
 
 double WASAPIAudioInputStream::GetVolume() {
   DCHECK(opened_) << "Open() has not been called successfully";
-  if (!simple_audio_volume_) {
+  if (!opened_)
     return 0.0;
-  }
 
   // Retrieve the current volume level. The value is in the range 0.0 to 1.0.
   float level = 0.0f;
@@ -965,9 +865,8 @@
 bool WASAPIAudioInputStream::IsMuted() {
   DCHECK(opened_) << "Open() has not been called successfully";
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  if (!simple_audio_volume_) {
+  if (!opened_)
     return false;
-  }
 
   // Retrieves the current muting state for the audio session.
   BOOL is_muted = FALSE;
@@ -1007,52 +906,6 @@
   log_callback_.Run(std::move(msg));
 }
 
-// static
-void WASAPIAudioInputStream::
-    OverrideActivateAudioInterfaceAsyncCallbackForTesting(
-        ActivateAudioInterfaceAsyncCallback callback) {
-  GetActivateAudioInterfaceAsyncCallback() = callback;
-}
-
-void WASAPIAudioInputStream::CreateFifoIfNeeded() {
-  if (fifo_) {
-    return;
-  }
-
-  // Retrieve the length of the endpoint buffer shared between the client
-  // and the audio engine. The buffer length determines the maximum amount
-  // of capture data that the audio engine can read from the endpoint buffer
-  // during a single processing pass.
-  uint32_t endpoint_buffer_size_frames = 0;
-  HRESULT hr = audio_client_->GetBufferSize(&endpoint_buffer_size_frames);
-  if (FAILED(hr)) {
-    return;
-  }
-
-  // Allocate a buffer with a size that enables us to take care of cases like:
-  // 1) The recorded buffer size is smaller, or does not match exactly with,
-  //    the selected packet size used in each callback.
-  // 2) The selected buffer size is larger than the recorded buffer size in
-  //    each event.
-  // In the case where no resampling is required, a single buffer should be
-  // enough but in case we get buffers that don't match exactly, we'll go with
-  // two. Same applies if we need to resample and the buffer ratio is perfect.
-  // However if the buffer ratio is imperfect, we will need 3 buffers to safely
-  // be able to buffer up data in cases where a conversion requires two audio
-  // buffers (and we need to be able to write to the third one).
-  size_t capture_buffer_size =
-      std::max(2 * endpoint_buffer_size_frames * frame_size_bytes_,
-               2 * packet_size_frames_ * frame_size_bytes_);
-  int buffers_required = capture_buffer_size / packet_size_bytes_;
-  if (converter_ && imperfect_buffer_size_conversion_)
-    ++buffers_required;
-
-  DCHECK(!fifo_);
-  fifo_ = std::make_unique<AudioBlockFifo>(
-      input_format_.Format.nChannels, packet_size_frames_, buffers_required);
-  DVLOG(1) << "AudioBlockFifo buffer count: " << buffers_required;
-}
-
 void WASAPIAudioInputStream::Run() {
   ScopedCOMInitializer com_init(ScopedCOMInitializer::kMTA);
 
@@ -1072,6 +925,29 @@
                << "))";
   }
 
+  // Allocate a buffer with a size that enables us to take care of cases like:
+  // 1) The recorded buffer size is smaller, or does not match exactly with,
+  //    the selected packet size used in each callback.
+  // 2) The selected buffer size is larger than the recorded buffer size in
+  //    each event.
+  // In the case where no resampling is required, a single buffer should be
+  // enough but in case we get buffers that don't match exactly, we'll go with
+  // two. Same applies if we need to resample and the buffer ratio is perfect.
+  // However if the buffer ratio is imperfect, we will need 3 buffers to safely
+  // be able to buffer up data in cases where a conversion requires two audio
+  // buffers (and we need to be able to write to the third one).
+  size_t capture_buffer_size =
+      std::max(2 * endpoint_buffer_size_frames_ * frame_size_bytes_,
+               2 * packet_size_frames_ * frame_size_bytes_);
+  int buffers_required = capture_buffer_size / packet_size_bytes_;
+  if (converter_ && imperfect_buffer_size_conversion_)
+    ++buffers_required;
+
+  DCHECK(!fifo_);
+  fifo_ = std::make_unique<AudioBlockFifo>(
+      input_format_.Format.nChannels, packet_size_frames_, buffers_required);
+  DVLOG(1) << "AudioBlockFifo buffer count: " << buffers_required;
+
   bool recording = true;
   bool error = false;
   HANDLE wait_array[2] = {stop_capture_event_.Get(),
@@ -1092,14 +968,6 @@
         break;
       case WAIT_OBJECT_0 + 1:
         // |audio_samples_ready_event_| has been set.
-        CreateFifoIfNeeded();
-        if (!fifo_) {
-          // An error happened while creating the FIFO.
-          error = true;
-          LOG(ERROR) << "WAIS::" << __func__
-                     << " => (ERROR: failed to create FIFO)";
-          break;
-        }
         PullCaptureDataAndPushToSink();
         break;
       case WAIT_FAILED:
@@ -1112,8 +980,6 @@
   if (recording && error) {
     // TODO(henrika): perhaps it worth improving the cleanup here by e.g.
     // stopping the audio client, joining the thread etc.?
-    // TODO(crbug.com/417505389): We should handle pipeline errors in a more
-    // graceful way instead of using NOTREACHED() here.
     auto saved_last_error = GetLastError();
     NOTREACHED() << "WASAPI capturing failed with error code "
                  << saved_last_error;
@@ -1280,11 +1146,7 @@
     // was monotonic.
     if (!last_capture_time_.is_null()) {
       const auto delta_ts = capture_time - last_capture_time_;
-      if (is_application_loopback_capture_) {
-        DCHECK_EQ(device_position, 0u);
-      } else {
-        DCHECK_GT(device_position, 0u);
-      }
+      DCHECK_GT(device_position, 0u);
       DCHECK_GT(delta_ts, base::TimeDelta::Min());
       if (delta_ts > max_timestamp_diff_) {
         max_timestamp_diff_ = delta_ts;
@@ -1375,7 +1237,6 @@
 }
 
 HRESULT WASAPIAudioInputStream::SetCaptureDevice() {
-  DCHECK(!is_application_loopback_capture_);
   DCHECK_EQ(OPEN_RESULT_OK, open_result_);
   DCHECK(!endpoint_device_.Get());
   SendLogMessage("%s()", __func__);
@@ -1443,56 +1304,7 @@
   return hr;
 }
 
-HRESULT WASAPIAudioInputStream::ActivateAudioClientInterface() {
-  if (!is_application_loopback_capture_) {
-    // Obtain an IAudioClient interface for the endpoint device which enables us
-    // to create and initialize an audio stream between an audio application and
-    // the audio engine.
-    return endpoint_device_->Activate(__uuidof(IAudioClient), CLSCTX_ALL,
-                                      nullptr, &audio_client_);
-  }
-
-  // Detailed information about AUDIOCLIENT_ACTIVATION_PARAMS can be found at:
-  // https://learn.microsoft.com/en-us/windows/win32/api/audioclientactivationparams/ns-audioclientactivationparams-audioclient_activation_params
-  AUDIOCLIENT_ACTIVATION_PARAMS params = {
-      //  Specify the process capture.
-      .ActivationType = AUDIOCLIENT_ACTIVATION_TYPE_PROCESS_LOOPBACK,
-      .ProcessLoopbackParams =
-          {
-              .TargetProcessId =
-                  GetApplicationIdFromApplicationLoopbackDeviceId(device_id_),
-              // Specify that this process capture should capture audio coming
-              // from all the processes in the tree in which `TargetProcessId`
-              // is the tree root.
-              .ProcessLoopbackMode =
-                  PROCESS_LOOPBACK_MODE_INCLUDE_TARGET_PROCESS_TREE,
-          },
-  };
-  PROPVARIANT propvariant = {
-      .vt = VT_BLOB,
-      .blob =
-          {
-              .cbSize = sizeof(params),
-              .pBlobData = reinterpret_cast<BYTE*>(&params),
-          },
-  };
-
-  ComPtr<AudioClientActivationHandler> completion_handler =
-      Microsoft::WRL::Make<AudioClientActivationHandler>();
-  ComPtr<IActivateAudioInterfaceAsyncOperation> async_op;
-  HRESULT hr = GetActivateAudioInterfaceAsyncCallback().Run(
-      VIRTUAL_AUDIO_DEVICE_PROCESS_LOOPBACK, __uuidof(IAudioClient),
-      &propvariant, completion_handler.Get(), &async_op);
-  if (FAILED(hr)) {
-    return hr;
-  }
-
-  return completion_handler->WaitAndGetAudioClient(
-      &audio_client_, async_activation_timeout_ms_);
-}
-
 bool WASAPIAudioInputStream::RawProcessingSupported() {
-  DCHECK(!is_application_loopback_capture_);
   DCHECK(endpoint_device_.Get());
   // Check if System.Devices.AudioDevice.RawProcessingSupported can be found
   // and queried in the Windows Property System. It corresponds to raw
@@ -1590,15 +1402,6 @@
 
 bool WASAPIAudioInputStream::DesiredFormatIsSupported(HRESULT* hr) {
   SendLogMessage("%s()", __func__);
-
-  // Process loopback mode is a virtual device. Therefore, neither
-  // IAudioClient::GetMixFormat nor IAudioClient::IsFormatSupported are
-  // supported. We are free to pick whichever format we want and can pass it
-  // into the call to IAudioClient::Initialize.
-  if (is_application_loopback_capture_) {
-    return true;
-  }
-
   // An application that uses WASAPI to manage shared-mode streams can rely
   // on the audio engine to perform only limited format conversions. The audio
   // engine can convert between a standard PCM sample size used by the
@@ -1711,12 +1514,8 @@
   SendLogMessage("%s()", __func__);
 
   // Use event-driven mode for regular input devices and for loopback.
-  DWORD flags = AUDCLNT_STREAMFLAGS_EVENTCALLBACK;
-  if (!is_application_loopback_capture_) {
-    // Application loopback capture does not support the
-    // AUDCLNT_STREAMFLAGS_NOPERSIST flag.
-    flags |= AUDCLNT_STREAMFLAGS_NOPERSIST;
-  }
+  DWORD flags =
+      AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_NOPERSIST;
   if (AudioDeviceDescription::IsLoopbackDevice(device_id_)) {
     // Create a loopback stream that captures what the system is playing
     // instead of the microphone input.
@@ -1749,6 +1548,22 @@
     return hr;
   }
 
+  // Retrieve the length of the endpoint buffer shared between the client
+  // and the audio engine. The buffer length determines the maximum amount
+  // of capture data that the audio engine can read from the endpoint buffer
+  // during a single processing pass.
+  hr = audio_client_->GetBufferSize(&endpoint_buffer_size_frames_);
+  if (FAILED(hr)) {
+    open_result_ = OPEN_RESULT_GET_BUFFER_SIZE_FAILED;
+    return hr;
+  }
+  const int endpoint_buffer_size_ms =
+      static_cast<double>(endpoint_buffer_size_frames_ * 1000) /
+          input_format_.Format.nSamplesPerSec +
+      0.5;
+  SendLogMessage("%s => (endpoint_buffer_size_frames=%u (%d ms))", __func__,
+                 endpoint_buffer_size_frames_, endpoint_buffer_size_ms);
+
 #ifndef NDEBUG
   // The period between processing passes by the audio engine is fixed for a
   // particular audio endpoint device and represents the smallest processing
@@ -1790,17 +1605,11 @@
     return hr;
   }
 
-  // WASAPI does not allow the AudioClient to control the process loopback
-  // device volume. The AudioEndpointVolume interface is not available for
-  // process loopback devices.
-  if (!is_application_loopback_capture_) {
-    // Obtain a reference to the ISimpleAudioVolume interface which enables
-    // us to control the master volume level of an audio session.
-    hr = audio_client_->GetService(IID_PPV_ARGS(&simple_audio_volume_));
-    if (FAILED(hr)) {
-      open_result_ = OPEN_RESULT_NO_AUDIO_VOLUME;
-    }
-  }
+  // Obtain a reference to the ISimpleAudioVolume interface which enables
+  // us to control the master volume level of an audio session.
+  hr = audio_client_->GetService(IID_PPV_ARGS(&simple_audio_volume_));
+  if (FAILED(hr))
+    open_result_ = OPEN_RESULT_NO_AUDIO_VOLUME;
 
   return hr;
 }
@@ -1840,7 +1649,7 @@
     AudioBus* audio_bus,
     uint32_t frames_delayed,
     const AudioGlitchInfo& glitch_info) {
-  CHECK_DEREF(fifo_.get()).Consume()->CopyTo(audio_bus);
+  fifo_->Consume()->CopyTo(audio_bus);
   return 1.0;
 }
 
diff --git a/media/audio/win/audio_low_latency_input_win.h b/media/audio/win/audio_low_latency_input_win.h
index 1452cb9..87871adac 100644
--- a/media/audio/win/audio_low_latency_input_win.h
+++ b/media/audio/win/audio_low_latency_input_win.h
@@ -112,7 +112,7 @@
     OPEN_RESULT_ACTIVATION_FAILED = 5,
     OPEN_RESULT_FORMAT_NOT_SUPPORTED = 6,
     OPEN_RESULT_AUDIO_CLIENT_INIT_FAILED = 7,
-    OPEN_RESULT_GET_BUFFER_SIZE_FAILED = 8,  // Obsolete.
+    OPEN_RESULT_GET_BUFFER_SIZE_FAILED = 8,
     OPEN_RESULT_LOOPBACK_ACTIVATE_FAILED = 9,
     OPEN_RESULT_LOOPBACK_INIT_FAILED = 10,
     OPEN_RESULT_SET_EVENT_HANDLE = 11,
@@ -122,13 +122,6 @@
     OPEN_RESULT_MAX = OPEN_RESULT_OK_WITH_RESAMPLING
   };
 
-  using ActivateAudioInterfaceAsyncCallback =
-      base::RepeatingCallback<HRESULT(LPCWSTR,
-                                      REFIID,
-                                      PROPVARIANT*,
-                                      IActivateAudioInterfaceCompletionHandler*,
-                                      IActivateAudioInterfaceAsyncOperation**)>;
-
   // The ctor takes all the usual parameters, plus |manager| which is the
   // the audio manager who is creating this object.
   WASAPIAudioInputStream(AudioManagerWin* manager,
@@ -158,21 +151,9 @@
 
   void SendLogMessage(std::string message);
 
-  // Overrides the function pointer used to activate an IAudioClient during
-  // application loopback captures. This is used for testing purposes only to
-  // add a hook to obtain fake implementations of Windows interfaces.
-  static void OverrideActivateAudioInterfaceAsyncCallbackForTesting(
-      ActivateAudioInterfaceAsyncCallback callback);
-
-  void OverrideAsyncActivationTimeoutForTesting(
-      base::TimeDelta async_activation_timeout_ms) {
-    async_activation_timeout_ms_ = async_activation_timeout_ms;
-  }
-
  private:
   class DataDiscontinuityReporter;
   class EchoCancellationConfig;
-  class AudioClientActivationHandler;
 
   PRINTF_FORMAT(2, 3) void SendLogMessage(const char* format, ...);
 
@@ -187,12 +168,6 @@
 
   // The Open() method is divided into these sub methods.
   HRESULT SetCaptureDevice();
-  // Activates the IAudioClient interface with the adequate parameters. If
-  // `device_id_` represents an application device, the function will call
-  // ActivateAudioInterfaceAsync to activate an audio interface for process
-  // loopback capture. If `device_id_` does not represent an application device,
-  // it will activate the selected audio endpoint `endpoint_device_`.
-  HRESULT ActivateAudioClientInterface();
   // Returns whether raw audio processing is supported or not for the selected
   // capture device.
   bool RawProcessingSupported();
@@ -226,10 +201,6 @@
   // Reports glitch stats and resets associated variables.
   void ReportAndResetGlitchStats();
 
-  // Creates the FIFO used to store audio data between the audio engine and the
-  // converter.
-  void CreateFifoIfNeeded();
-
   // Our creator, the audio manager needs to be notified when we close.
   const raw_ptr<AudioManagerWin> manager_;
 
@@ -268,18 +239,21 @@
   bool started_ = false;
   StreamOpenResult open_result_ = OPEN_RESULT_OK;
 
-  // Size in bytes of each audio frame before the converter (e.g. 4 bytes for
-  // 16-bit stereo PCM). Note that this is the same before and after the FIFO.
+  // Size in bytes of each audio frame before the converter (4 bytes for 16-bit
+  // stereo PCM). Note that this is the same before and after the fifo.
   size_t frame_size_bytes_ = 0;
 
-  // Size in audio frames of each audio packet (buffer) after the FIFO but
+  // Size in audio frames of each audio packet (buffer) after the fifo but
   // before the converter.
   size_t packet_size_frames_ = 0;
 
-  // Size in bytes of each audio packet (buffer) after the FIFO but before the
+  // Size in bytes of each audio packet (buffer) after the fifo but before the
   // converter.
   size_t packet_size_bytes_ = 0;
 
+  // Length of the audio endpoint buffer, i.e. the buffer size before the fifo.
+  uint32_t endpoint_buffer_size_frames_ = 0;
+
   // Contains the unique name of the selected endpoint device.
   // Note that AudioDeviceDescription::kDefaultDeviceId represents the default
   // device role and is not a valid ID as such.
@@ -326,7 +300,7 @@
   // indicates that we need to unmute the system audio when stopping capturing.
   bool mute_done_ = false;
 
-  // Used to store data between the audio engine and the converter.
+  // Used for the captured audio on the callback thread.
   std::unique_ptr<AudioBlockFifo> fifo_;
 
   // If the caller requires resampling (should only be in exceptional cases and
@@ -369,19 +343,6 @@
   // Will be set to nullptr during construction if AEC is not supported.
   std::unique_ptr<EchoCancellationConfig> aec_config_;
 
-  // It's is possible to check this using
-  // AudioDeviceDescription::IsApplicationLoopbackDevice. However, we need to
-  // perform this check every time we need to pull data from the audio engine,
-  // which can be expensive. Checking the variable is cheaper than calling the
-  // function.
-  const bool is_application_loopback_capture_;
-
-  // Timeout period for waiting on the OS to activate the audio interface for
-  // application loopback capture.
-  // TODO(crbug.com/40947205): Add UMA stats to track the actual wait time in
-  // the field
-  base::TimeDelta async_activation_timeout_ms_ = base::Seconds(10);
-
   SEQUENCE_CHECKER(sequence_checker_);
 };
 
diff --git a/media/audio/win/audio_low_latency_input_win_unittest.cc b/media/audio/win/audio_low_latency_input_win_unittest.cc
index f20f6852..de632b0 100644
--- a/media/audio/win/audio_low_latency_input_win_unittest.cc
+++ b/media/audio/win/audio_low_latency_input_win_unittest.cc
@@ -29,14 +29,11 @@
 #include "base/win/scoped_com_initializer.h"
 #include "media/audio/audio_device_description.h"
 #include "media/audio/audio_device_info_accessor_for_tests.h"
-#include "media/audio/audio_input_stream_data_interceptor.h"
 #include "media/audio/audio_io.h"
 #include "media/audio/audio_manager.h"
 #include "media/audio/audio_unittest_util.h"
 #include "media/audio/test_audio_thread.h"
 #include "media/audio/win/core_audio_util_win.h"
-#include "media/audio/win/test_support/fake_win_wasapi_environment.h"
-#include "media/audio/win/test_support/wasapi_test_error_code.h"
 #include "media/base/media_switches.h"
 #include "media/base/seekable_buffer.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -55,13 +52,6 @@
 
 namespace {
 
-constexpr char kMockApplicationLoopbackDeviceId[] = "applicationLoopback:12345";
-// When opening a WASAPIAudioInputStream for application loopback capture, it's
-// necessary to wait for the activation to complete. This short timeout is used
-// to avoid long waits in the timeout test cases.
-constexpr base::TimeDelta kShortAsyncActivationTimeoutMs =
-    base::Milliseconds(10);
-
 void LogCallbackDummy(const std::string& /* message */) {}
 
 }  // namespace
@@ -325,7 +315,7 @@
 
   AudioInputStream* operator->() { return stream_; }
 
-  AudioInputStream* get() const { return stream_.get(); }
+  AudioInputStream* get() const { return stream_; }
 
   void Reset(AudioInputStream* new_stream) {
     Close();
@@ -383,7 +373,7 @@
   ~WinAudioInputTest() override { audio_manager_->Shutdown(); }
 
  protected:
-  base::test::TaskEnvironment task_environment_;
+  base::test::SingleThreadTaskEnvironment task_environment_;
   std::unique_ptr<AudioManager> audio_manager_;
 };
 
@@ -738,7 +728,7 @@
   }
 
   void SetUp() override {
-    // Abort early if requirements are not met.
+    // Abort early if requirements are mot met.
     bool prerequisites_met = device_info_accessor_.HasAudioOutputDevices() &&
                              CoreAudioUtil::IsSupported();
     if (!prerequisites_met) {
@@ -810,103 +800,6 @@
   EXPECT_FALSE(sink.error());
 }
 
-class WinAudioApplicationLoopbackTest : public WinAudioInputTest {
- public:
-  WinAudioApplicationLoopbackTest()
-      : device_info_accessor_(audio_manager_.get()) {
-    // Defer stream creation and parameter fetching to SetUp.
-  }
-
-  void SetUp() override {
-    // Abort early if requirements are not met.
-    bool prerequisites_met = CoreAudioUtil::IsSupported();
-    if (!prerequisites_met) {
-      GTEST_SKIP() << "Missing audio output devices or CoreAudio support";
-    }
-
-    CreateParameters();
-    CreateStream();
-  }
-
-  void CreateParameters() {
-    params_ = device_info_accessor_.GetInputStreamParameters(
-        AudioDeviceDescription::kApplicationLoopbackDeviceId);
-  }
-
-  void CreateStream() {
-    stream_.Reset(audio_manager_->MakeAudioInputStream(
-        params_, kMockApplicationLoopbackDeviceId,
-        base::BindRepeating(&LogCallbackDummy)));
-    EXPECT_THAT(stream_.get(), NotNull());
-  }
-
-  void OverrideAsyncActivationTimeout(base::TimeDelta timeout_ms) {
-    AudioInputStreamDataInterceptor* audio_input_stream_data_interceptor =
-        static_cast<AudioInputStreamDataInterceptor*>(stream_.get());
-    static_cast<WASAPIAudioInputStream*>(
-        audio_input_stream_data_interceptor->GetUnderlyingStreamForTesting())
-        ->OverrideAsyncActivationTimeoutForTesting(timeout_ms);
-  }
-
- protected:
-  AudioDeviceInfoAccessorForTests device_info_accessor_;
-  AudioParameters params_;
-  ScopedAudioInputStream stream_;
-  FakeWinWASAPIEnvironment fake_wasapi_environment_;
-};
-
-TEST_F(WinAudioApplicationLoopbackTest, OpenStreamSuccess) {
-  ASSERT_THAT(stream_->Open(), Eq(AudioInputStream::OpenOutcome::kSuccess));
-}
-
-TEST_F(WinAudioApplicationLoopbackTest,
-       OpenStreamActivateAudioInterfaceAsyncFailed) {
-  fake_wasapi_environment_.SimulateError(
-      WASAPITestErrorCode::kActivateAudioInterfaceAsyncFailed);
-  EXPECT_EQ(stream_->Open(), AudioInputStream::OpenOutcome::kFailed);
-}
-
-TEST_F(WinAudioApplicationLoopbackTest,
-       OpenInputStreamActivateAudioInterfaceAsyncOperationTimedOut) {
-  fake_wasapi_environment_.SimulateError(
-      WASAPITestErrorCode::kAudioClientActivationTimeout);
-  // Override the default timeout so that this test can run quickly. The default
-  // timeout is 10 seconds.
-  OverrideAsyncActivationTimeout(kShortAsyncActivationTimeoutMs);
-  EXPECT_EQ(stream_->Open(), AudioInputStream::OpenOutcome::kFailed);
-}
-
-TEST_F(WinAudioApplicationLoopbackTest,
-       OpenStreamAudioClientActivationAsyncOperationFailed) {
-  fake_wasapi_environment_.SimulateError(
-      WASAPITestErrorCode::kAudioClientActivationAsyncOperationFailed);
-  // Override the default timeout so that this test can run quickly. The default
-  // timeout is 10 seconds.
-  OverrideAsyncActivationTimeout(kShortAsyncActivationTimeoutMs);
-  EXPECT_EQ(stream_->Open(), AudioInputStream::OpenOutcome::kFailed);
-}
-
-TEST_F(WinAudioApplicationLoopbackTest, OpenStreamAudioClientActivationFailed) {
-  fake_wasapi_environment_.SimulateError(
-      WASAPITestErrorCode::kAudioClientActivationFailed);
-  EXPECT_EQ(stream_->Open(), AudioInputStream::OpenOutcome::kFailed);
-}
-
-TEST_F(WinAudioApplicationLoopbackTest, SuccessfulCapture) {
-  ASSERT_THAT(stream_->Open(), Eq(AudioInputStream::OpenOutcome::kSuccess));
-
-  FakeAudioInputCallback sink;
-  stream_->Start(&sink);
-  ASSERT_FALSE(sink.error());
-  sink.WaitForData();
-  sink.WaitForData();
-  stream_.Close();
-
-  EXPECT_EQ(sink.num_callbacks(), 2);
-  EXPECT_GT(sink.num_received_audio_frames(), 0);
-  EXPECT_FALSE(sink.error());
-}
-
 // This test is intended for manual tests and should only be enabled
 // when it is required to store the captured data on a local file.
 // By default, GTest will print out YOU HAVE 1 DISABLED TEST.
diff --git a/media/audio/win/core_audio_util_win.cc b/media/audio/win/core_audio_util_win.cc
index 55cc0fe8..dce888a 100644
--- a/media/audio/win/core_audio_util_win.cc
+++ b/media/audio/win/core_audio_util_win.cc
@@ -396,8 +396,7 @@
   // In loopback mode, a client of WASAPI can capture the audio stream that
   // is being played by a rendering endpoint device.
   // See https://crbug.com/956526 for why we use both a DCHECK and then deal
-  // with the error here and below. Also, see comments in CreateDeviceByID() for
-  // more details.
+  // with the error here and below.
   DCHECK(!(AudioDeviceDescription::IsLoopbackDevice(device_id) &&
            data_flow != eCapture));
   if (AudioDeviceDescription::IsLoopbackDevice(device_id) &&
@@ -450,10 +449,6 @@
 // corresponding audio device.
 ComPtr<IMMDevice> CreateDeviceByID(const std::string& device_id,
                                    bool is_output_device) {
-  // Loopback devices are only supported for capture streams. If a loopback
-  // device is requested for a render stream, the default render device will be
-  // used instead.
-  // See https://crbug.com/956526 for more details.
   if (AudioDeviceDescription::IsLoopbackDevice(device_id)) {
     DCHECK(!is_output_device);
     return CreateDeviceInternal(AudioDeviceDescription::kDefaultDeviceId,
@@ -1055,13 +1050,9 @@
                                                    bool is_output_device,
                                                    AudioParameters* params,
                                                    bool is_offload_stream) {
-  // Loopback capture audio streams must be input streams. If an output device
-  // is requested for a loopback device, the default output device will be used
-  // instead. See https://crbug.com/956526 for more details.
-  // TODO(crbug.com/40947205): figure out which parameters to use for
-  // application loopback capture.
-  DCHECK(!(is_output_device &&
-           (AudioDeviceDescription::IsLoopbackDevice(device_id))));
+  // Loopback audio streams must be input streams.
+  DCHECK(!(AudioDeviceDescription::IsLoopbackDevice(device_id) &&
+           is_output_device));
   if (AudioDeviceDescription::IsLoopbackDevice(device_id) && is_output_device) {
     LOG(WARNING) << "Loopback device must be an input device";
     return E_FAIL;
diff --git a/media/audio/win/core_audio_util_win_unittest.cc b/media/audio/win/core_audio_util_win_unittest.cc
index 19b7adf5..74b3c3b71 100644
--- a/media/audio/win/core_audio_util_win_unittest.cc
+++ b/media/audio/win/core_audio_util_win_unittest.cc
@@ -398,18 +398,15 @@
   for (size_t i = 0; i < std::size(data); ++i) {
     AudioParameters params;
     const bool is_output_device = (data[i] == eRender);
-    EXPECT_HRESULT_SUCCEEDED(CoreAudioUtil::GetPreferredAudioParameters(
-        AudioDeviceDescription::kDefaultDeviceId, is_output_device, &params));
+    EXPECT_TRUE(SUCCEEDED(CoreAudioUtil::GetPreferredAudioParameters(
+        AudioDeviceDescription::kDefaultDeviceId, is_output_device, &params)));
     EXPECT_TRUE(params.IsValid());
     if (!is_output_device) {
       // Loopack devices are supported for input streams.
-      EXPECT_HRESULT_SUCCEEDED(CoreAudioUtil::GetPreferredAudioParameters(
+      EXPECT_TRUE(SUCCEEDED(CoreAudioUtil::GetPreferredAudioParameters(
           AudioDeviceDescription::kLoopbackInputDeviceId, is_output_device,
-          &params));
+          &params)));
       EXPECT_TRUE(params.IsValid());
-      EXPECT_HRESULT_SUCCEEDED(CoreAudioUtil::GetPreferredAudioParameters(
-          AudioDeviceDescription::kApplicationLoopbackDeviceId,
-          is_output_device, &params));
       {
         base::test::ScopedFeatureList feature_list;
         base::HistogramTester histogram_tester;
@@ -418,9 +415,9 @@
         // version and device hardware. This code is behind a flag currently.
         feature_list.InitAndEnableFeature(
             media::kEnforceSystemEchoCancellation);
-        EXPECT_HRESULT_SUCCEEDED(CoreAudioUtil::GetPreferredAudioParameters(
+        EXPECT_TRUE(SUCCEEDED(CoreAudioUtil::GetPreferredAudioParameters(
             AudioDeviceDescription::kDefaultDeviceId, is_output_device, &params,
-            false /*is_offload_stream*/));
+            false /*is_offload_stream*/)));
         EXPECT_TRUE(params.IsValid());
         // If GetPreferredAudioParameters() runs we know that at least one
         // sample is created since, even if the method fails or if the device
@@ -441,9 +438,9 @@
         feature_list.InitAndDisableFeature(
             media::kEnforceSystemEchoCancellation);
         base::HistogramTester histogram_tester;
-        EXPECT_HRESULT_SUCCEEDED(CoreAudioUtil::GetPreferredAudioParameters(
+        EXPECT_TRUE(SUCCEEDED(CoreAudioUtil::GetPreferredAudioParameters(
             AudioDeviceDescription::kDefaultDeviceId, is_output_device, &params,
-            false /*is_offload_stream*/));
+            false /*is_offload_stream*/)));
         EXPECT_TRUE(params.IsValid());
         histogram_tester.ExpectTotalCount(
             "Media.Audio.Capture.Win.VoiceProcessingEffects", 0);
diff --git a/media/audio/win/test_support/fake_iactivate_audio_interface_async_operation.cc b/media/audio/win/test_support/fake_iactivate_audio_interface_async_operation.cc
deleted file mode 100644
index 9cd4ebd..0000000
--- a/media/audio/win/test_support/fake_iactivate_audio_interface_async_operation.cc
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright 2025 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "media/audio/win/test_support/fake_iactivate_audio_interface_async_operation.h"
-
-#include <mmdeviceapi.h>
-#include <windows.h>
-
-#include <audioclient.h>
-#include <wrl.h>
-
-#include "media/audio/win/test_support/fake_iaudio_client.h"
-#include "media/audio/win/test_support/fake_win_wasapi_environment.h"
-
-namespace media {
-
-IFACEMETHODIMP FakeIActivateAudioInterfaceAsyncOperation::GetActivateResult(
-    HRESULT* activateResult,
-    IUnknown** activatedInterface) {
-  if (FakeWinWASAPIEnvironment::GetError() ==
-      WASAPITestErrorCode::kAudioClientActivationAsyncOperationFailed) {
-    return E_ILLEGAL_METHOD_CALL;
-  }
-
-  if (FakeWinWASAPIEnvironment::GetError() ==
-      WASAPITestErrorCode::kAudioClientActivationFailed) {
-    *activateResult = E_OUTOFMEMORY;
-    return S_OK;
-  }
-
-  Microsoft::WRL::ComPtr<IAudioClient> audio_client =
-      Microsoft::WRL::Make<FakeIAudioClient>(
-          FakeIAudioClient::ClientType::kApplicationLoopbackDevice);
-  *activateResult = S_OK;
-  *activatedInterface = audio_client.Detach();
-  return S_OK;
-}
-
-}  // namespace media
diff --git a/media/audio/win/test_support/fake_iactivate_audio_interface_async_operation.h b/media/audio/win/test_support/fake_iactivate_audio_interface_async_operation.h
deleted file mode 100644
index 5ae4caba..0000000
--- a/media/audio/win/test_support/fake_iactivate_audio_interface_async_operation.h
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright 2025 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_AUDIO_WIN_TEST_SUPPORT_FAKE_IACTIVATE_AUDIO_INTERFACE_ASYNC_OPERATION_H_
-#define MEDIA_AUDIO_WIN_TEST_SUPPORT_FAKE_IACTIVATE_AUDIO_INTERFACE_ASYNC_OPERATION_H_
-
-#include <mmdeviceapi.h>
-#include <windows.h>
-
-#include <wrl.h>
-
-namespace media {
-
-// FakeIActivateAudioInterfaceAsyncOperation is a mock implementation of the
-// IActivateAudioInterfaceAsyncOperation interface. It is used for testing
-// purposes and simulates the behavior of an audio interface activation
-// operation without requiring actual audio hardware or a real WASAPI
-// environment.
-class FakeIActivateAudioInterfaceAsyncOperation
-    : public Microsoft::WRL::RuntimeClass<
-          Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom>,
-          Microsoft::WRL::FtmBase,
-          IActivateAudioInterfaceAsyncOperation> {
- public:
-  FakeIActivateAudioInterfaceAsyncOperation() = default;
-  ~FakeIActivateAudioInterfaceAsyncOperation() override = default;
-
-  // IActivateAudioInterfaceAsyncOperation methods
-  IFACEMETHODIMP(GetActivateResult)(HRESULT* activateResult,
-                                    IUnknown** activatedInterface) override;
-};
-
-}  // namespace media
-
-#endif  // MEDIA_AUDIO_WIN_TEST_SUPPORT_FAKE_IACTIVATE_AUDIO_INTERFACE_ASYNC_OPERATION_H_
diff --git a/media/audio/win/test_support/fake_iaudio_capture_client.cc b/media/audio/win/test_support/fake_iaudio_capture_client.cc
deleted file mode 100644
index 5b95349..0000000
--- a/media/audio/win/test_support/fake_iaudio_capture_client.cc
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright 2025 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "media/audio/win/test_support/fake_iaudio_capture_client.h"
-
-#include <wrl.h>
-
-namespace media {
-
-FakeIAudioCaptureClient::FakeIAudioCaptureClient(
-    FakeIAudioClient::ClientType client_type,
-    UINT32 buffer_size)
-    : client_type_(client_type),
-      buffer_data_(buffer_size, /*initial_value=*/1),
-      next_packet_size_(buffer_size) {}
-
-FakeIAudioCaptureClient::~FakeIAudioCaptureClient() = default;
-
-IFACEMETHODIMP FakeIAudioCaptureClient::GetBuffer(BYTE** data,
-                                                  UINT32* num_frames_available,
-                                                  DWORD* flags,
-                                                  UINT64* device_position,
-                                                  UINT64* qpc_position) {
-  *data = buffer_data_.data();
-  *num_frames_available = static_cast<UINT32>(buffer_data_.size());
-  *flags = 0;
-  *device_position = device_position_;
-  *qpc_position = qpc_position_;
-
-  // Simulate a device position increment for the next call.
-  if (client_type_ !=
-      FakeIAudioClient::ClientType::kApplicationLoopbackDevice) {
-    // Application loopback uses a virtual device, which do not have a device
-    // position and should always return 0.
-    device_position_ += *num_frames_available;
-  }
-  qpc_position_ += *num_frames_available;
-
-  // After the buffer content has been read, make the next call to
-  // `GetNextPacketSize` return 0. This simulates the behavior of the audio
-  // engine when there is no more data available, which will make the
-  // `WASAPIAudioInputStream` to break the loop in
-  // `WASAPIAudioInputStream::PullCaptureDataAndPushToSink`.
-  next_packet_size_ = 0;
-  return S_OK;
-}
-
-IFACEMETHODIMP FakeIAudioCaptureClient::ReleaseBuffer(UINT32 num_frames_read) {
-  return S_OK;
-}
-
-IFACEMETHODIMP FakeIAudioCaptureClient::GetNextPacketSize(UINT32* packet_size) {
-  *packet_size = static_cast<UINT32>(next_packet_size_);
-  if (next_packet_size_ == 0) {
-    next_packet_size_ = static_cast<int>(buffer_data_.size());
-  }
-  return S_OK;
-}
-
-}  // namespace media
diff --git a/media/audio/win/test_support/fake_iaudio_capture_client.h b/media/audio/win/test_support/fake_iaudio_capture_client.h
deleted file mode 100644
index 8c5f84b6..0000000
--- a/media/audio/win/test_support/fake_iaudio_capture_client.h
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright 2025 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_AUDIO_WIN_TEST_SUPPORT_FAKE_IAUDIO_CAPTURE_CLIENT_H_
-#define MEDIA_AUDIO_WIN_TEST_SUPPORT_FAKE_IAUDIO_CAPTURE_CLIENT_H_
-
-#include <audioclient.h>
-#include <wrl.h>
-
-#include <vector>
-
-#include "base/win/windows_types.h"
-#include "media/audio/win/test_support/fake_iaudio_client.h"
-
-namespace media {
-
-// FakeIAudioCaptureClient is a mock implementation of the IAudioCaptureClient
-// interface. It is used for testing purposes and simulates the behavior of an
-// IAudioCaptureClient without requiring actual audio hardware or a real
-// WASAPI environment.
-class FakeIAudioCaptureClient
-    : public Microsoft::WRL::RuntimeClass<
-          Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom>,
-          Microsoft::WRL::FtmBase,
-          IAudioCaptureClient> {
- public:
-  FakeIAudioCaptureClient(FakeIAudioClient::ClientType client_type,
-                          UINT32 buffer_size);
-  FakeIAudioCaptureClient(const FakeIAudioCaptureClient&) = delete;
-  FakeIAudioCaptureClient& operator=(const FakeIAudioCaptureClient&) = delete;
-  ~FakeIAudioCaptureClient() override;
-
-  // IAudioCaptureClient methods
-  IFACEMETHODIMP(GetBuffer)(BYTE** data,
-                            UINT32* num_frames_available,
-                            DWORD* flags,
-                            UINT64* device_position,
-                            UINT64* qpc_position) override;
-  IFACEMETHODIMP(ReleaseBuffer)(UINT32 num_frames_read) override;
-  IFACEMETHODIMP(GetNextPacketSize)(UINT32* packet_size) override;
-
- private:
-  const FakeIAudioClient::ClientType client_type_;
-  // Simulated buffer data
-  std::vector<BYTE> buffer_data_;
-  UINT64 device_position_ = 0;
-  UINT64 qpc_position_ = 0;
-  int next_packet_size_;
-};
-
-}  // namespace media
-
-#endif  // MEDIA_AUDIO_WIN_TEST_SUPPORT_FAKE_IAUDIO_CAPTURE_CLIENT_H_
diff --git a/media/audio/win/test_support/fake_iaudio_client.cc b/media/audio/win/test_support/fake_iaudio_client.cc
deleted file mode 100644
index cbc2eb7c..0000000
--- a/media/audio/win/test_support/fake_iaudio_client.cc
+++ /dev/null
@@ -1,136 +0,0 @@
-// Copyright 2025 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "media/audio/win/test_support/fake_iaudio_client.h"
-
-#include <mmdeviceapi.h>
-#include <windows.h>
-
-#include <wrl.h>
-
-#include "base/functional/bind.h"
-#include "base/task/sequenced_task_runner.h"
-#include "base/task/thread_pool.h"
-#include "base/time/time.h"
-#include "media/audio/win/test_support/fake_iaudio_capture_client.h"
-
-namespace {
-
-constexpr UINT32 kBufferSize = 256;
-constexpr REFERENCE_TIME kSamplingPeriodMs = 1;
-constexpr REFERENCE_TIME kStreamLatencyMs = 1;
-
-}  // namespace
-
-namespace media {
-
-FakeIAudioClient::FakeIAudioClient(ClientType client_type)
-    : client_type_(client_type) {}
-
-FakeIAudioClient::~FakeIAudioClient() = default;
-
-IFACEMETHODIMP FakeIAudioClient::GetBufferSize(UINT32* buffer_size) {
-  *buffer_size = kBufferSize;
-  return S_OK;
-}
-
-IFACEMETHODIMP FakeIAudioClient::GetCurrentPadding(UINT32* padding) {
-  return E_NOTIMPL;
-}
-
-IFACEMETHODIMP FakeIAudioClient::GetDevicePeriod(
-    REFERENCE_TIME* default_device_period,
-    REFERENCE_TIME* minimum_device_period) {
-  // Convert to 100ns units.
-  *default_device_period = kSamplingPeriodMs * 10000;
-  return S_OK;
-}
-
-IFACEMETHODIMP FakeIAudioClient::GetMixFormat(WAVEFORMATEX** device_format) {
-  return E_NOTIMPL;
-}
-
-IFACEMETHODIMP FakeIAudioClient::GetService(REFIID riid, void** service) {
-  if (riid == __uuidof(IAudioCaptureClient)) {
-    audio_capture_client_ = Microsoft::WRL::Make<FakeIAudioCaptureClient>(
-        client_type_, kBufferSize);
-    audio_capture_client_.CopyTo(
-        reinterpret_cast<IAudioCaptureClient**>(service));
-    return S_OK;
-  }
-
-  return E_NOTIMPL;
-}
-
-IFACEMETHODIMP FakeIAudioClient::GetStreamLatency(REFERENCE_TIME* latency) {
-  // Convert to 100ns units.
-  *latency = kStreamLatencyMs * 10000;
-  return S_OK;
-}
-
-IFACEMETHODIMP FakeIAudioClient::Initialize(AUDCLNT_SHAREMODE share_mode,
-                                            DWORD stream_flags,
-                                            REFERENCE_TIME buffer_duration,
-                                            REFERENCE_TIME periodicity,
-                                            const WAVEFORMATEX* format,
-                                            LPCGUID audio_session_guid) {
-  return S_OK;
-}
-
-IFACEMETHODIMP FakeIAudioClient::IsFormatSupported(
-    AUDCLNT_SHAREMODE share_mode,
-    const WAVEFORMATEX* format,
-    WAVEFORMATEX** closest_match) {
-  return E_NOTIMPL;
-}
-
-IFACEMETHODIMP FakeIAudioClient::Reset() {
-  return E_NOTIMPL;
-}
-
-IFACEMETHODIMP FakeIAudioClient::SetEventHandle(HANDLE event_handle) {
-  buffer_ready_event_handle_ = event_handle;
-  SetEvent(buffer_ready_event_handle_);
-  return S_OK;
-}
-
-IFACEMETHODIMP FakeIAudioClient::Start() {
-  StartDataStreaming();
-  return S_OK;
-}
-
-IFACEMETHODIMP FakeIAudioClient::Stop() {
-  should_stop_streaming_ = true;
-  return S_OK;
-}
-
-void FakeIAudioClient::StartDataStreaming() {
-  base::ThreadPool::PostTask(
-      FROM_HERE,
-      base::BindOnce(&FakeIAudioClient::StreamData, base::Unretained(this)));
-}
-
-void FakeIAudioClient::StreamData() {
-  // Realistically, the AudioCaptureClient would need to signal the
-  // `FakeIAudioClient` that new data is available. However, for testing
-  // purposes, the `FakeIAudioCaptureClient` data buffer's contents never
-  // change. Therefore, the `FakeIAudioClient` does not need to receive any type
-  // of notification to signal the consumer that it can access the next batch of
-  // data.
-  if (buffer_ready_event_handle_) {
-    SetEvent(buffer_ready_event_handle_);
-  }
-
-  if (should_stop_streaming_) {
-    return;
-  }
-
-  // Simulate new data coming in every `kSamplingPeriodMs` milliseconds.
-  base::ThreadPool::PostDelayedTask(
-      FROM_HERE,
-      base::BindOnce(&FakeIAudioClient::StreamData, base::Unretained(this)),
-      base::Milliseconds(kSamplingPeriodMs));
-}
-
-}  // namespace media
diff --git a/media/audio/win/test_support/fake_iaudio_client.h b/media/audio/win/test_support/fake_iaudio_client.h
deleted file mode 100644
index 3332b5f..0000000
--- a/media/audio/win/test_support/fake_iaudio_client.h
+++ /dev/null
@@ -1,69 +0,0 @@
-// Copyright 2025 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_AUDIO_WIN_TEST_SUPPORT_FAKE_IAUDIO_CLIENT_H_
-#define MEDIA_AUDIO_WIN_TEST_SUPPORT_FAKE_IAUDIO_CLIENT_H_
-
-#include <mmdeviceapi.h>
-#include <windows.h>
-
-#include <audioclient.h>
-#include <wrl.h>
-
-#include "base/task/sequenced_task_runner.h"
-
-namespace media {
-
-// FakeIAudioClient is a mock implementation of the IAudioClient interface.
-// It is used for testing purposes and simulates the behavior of an IAudioClient
-// without requiring actual audio hardware or a real WASAPI environment.
-class FakeIAudioClient
-    : public Microsoft::WRL::RuntimeClass<
-          Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom>,
-          Microsoft::WRL::FtmBase,
-          IAudioClient> {
- public:
-  enum class ClientType { kDefaultDevice, kApplicationLoopbackDevice };
-
-  FakeIAudioClient(ClientType client_type);
-  FakeIAudioClient(const FakeIAudioClient&) = delete;
-  FakeIAudioClient& operator=(const FakeIAudioClient&) = delete;
-  ~FakeIAudioClient() override;
-
-  // IAudioClient methods
-  IFACEMETHODIMP(GetBufferSize)(UINT32* buffer_size) override;
-  IFACEMETHODIMP(GetCurrentPadding)(UINT32* padding) override;
-  IFACEMETHODIMP(GetDevicePeriod)(
-      REFERENCE_TIME* default_device_period,
-      REFERENCE_TIME* minimum_device_period) override;
-  IFACEMETHODIMP(GetMixFormat)(WAVEFORMATEX** device_format) override;
-  IFACEMETHODIMP(GetService)(REFIID riid, void** service) override;
-  IFACEMETHODIMP(GetStreamLatency)(REFERENCE_TIME* phnsLatency) override;
-  IFACEMETHODIMP(Initialize)(AUDCLNT_SHAREMODE share_mode,
-                             DWORD stream_flags,
-                             REFERENCE_TIME buffer_duration,
-                             REFERENCE_TIME periodicity,
-                             const WAVEFORMATEX* format,
-                             LPCGUID audio_session_guid) override;
-  IFACEMETHODIMP(IsFormatSupported)(AUDCLNT_SHAREMODE share_mode,
-                                    const WAVEFORMATEX* format,
-                                    WAVEFORMATEX** closest_match) override;
-  IFACEMETHODIMP(Reset)() override;
-  IFACEMETHODIMP(SetEventHandle)(HANDLE event_handle) override;
-  IFACEMETHODIMP(Start)() override;
-  IFACEMETHODIMP(Stop)() override;
-
- private:
-  void StartDataStreaming();
-  void StreamData();
-
-  const ClientType client_type_;
-  HANDLE buffer_ready_event_handle_ = nullptr;
-  Microsoft::WRL::ComPtr<IAudioCaptureClient> audio_capture_client_ = nullptr;
-  bool should_stop_streaming_ = false;
-};
-
-}  // namespace media
-
-#endif  // MEDIA_AUDIO_WIN_TEST_SUPPORT_FAKE_IAUDIO_CLIENT_H_
diff --git a/media/audio/win/test_support/fake_win_wasapi_environment.cc b/media/audio/win/test_support/fake_win_wasapi_environment.cc
deleted file mode 100644
index cad78ba9..0000000
--- a/media/audio/win/test_support/fake_win_wasapi_environment.cc
+++ /dev/null
@@ -1,89 +0,0 @@
-// Copyright 2025 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "media/audio/win/test_support/fake_win_wasapi_environment.h"
-
-#include <mmdeviceapi.h>
-#include <windows.h>
-
-#include <wrl.h>
-
-#include "base/functional/bind.h"
-#include "base/task/thread_pool.h"
-#include "base/win/windows_types.h"
-#include "media/audio/win/audio_low_latency_input_win.h"
-#include "media/audio/win/test_support/fake_iactivate_audio_interface_async_operation.h"
-#include "media/audio/win/test_support/fake_iaudio_client.h"
-
-namespace media {
-
-namespace {
-
-void ActivateAudioInterface(
-    IActivateAudioInterfaceCompletionHandler* completionHandler,
-    IActivateAudioInterfaceAsyncOperation* activationOperation) {
-  activationOperation =
-      Microsoft::WRL::Make<FakeIActivateAudioInterfaceAsyncOperation>()
-          .Detach();
-  completionHandler->ActivateCompleted(activationOperation);
-}
-
-}  // namespace
-
-// static
-WASAPITestErrorCode FakeWinWASAPIEnvironment::error_ = WASAPITestErrorCode::kOk;
-
-FakeWinWASAPIEnvironment::FakeWinWASAPIEnvironment() {
-  // Set the error code to the default success value.
-  error_ = WASAPITestErrorCode::kOk;
-
-  // Override the callback for ActivateAudioInterfaceAsync to use our fake
-  // implementation.
-  WASAPIAudioInputStream::OverrideActivateAudioInterfaceAsyncCallbackForTesting(
-      base::BindRepeating(&ActivateAudioInterfaceAsync));
-}
-
-FakeWinWASAPIEnvironment::~FakeWinWASAPIEnvironment() {
-  // Reset the error code to the default success value.
-  error_ = WASAPITestErrorCode::kOk;
-
-  // Restore the original callback for ActivateAudioInterfaceAsync.
-  WASAPIAudioInputStream::OverrideActivateAudioInterfaceAsyncCallbackForTesting(
-      base::BindRepeating(&ActivateAudioInterfaceAsync));
-}
-
-// static
-HRESULT FakeWinWASAPIEnvironment::ActivateAudioInterfaceAsync(
-    LPCWSTR deviceInterfacePath,
-    REFIID riid,
-    PROPVARIANT* activationParams,
-    IActivateAudioInterfaceCompletionHandler* completionHandler,
-    IActivateAudioInterfaceAsyncOperation** activationOperation) {
-  if (error_ == WASAPITestErrorCode::kActivateAudioInterfaceAsyncFailed) {
-    return E_ILLEGAL_METHOD_CALL;
-  }
-
-  // COM interfaces are refcounted, so we need to wrap the pointers in ComPtr to
-  // ensure that they are properly managed by the callback.
-  Microsoft::WRL::ComPtr<IActivateAudioInterfaceCompletionHandler>
-      completionHandlerPtr(completionHandler);
-  Microsoft::WRL::ComPtr<IActivateAudioInterfaceAsyncOperation>
-      activationOperationPtr(*activationOperation);
-  auto activate_audio_interface_callback = base::BindOnce(
-      &ActivateAudioInterface, completionHandlerPtr, activationOperationPtr);
-
-  if (FakeWinWASAPIEnvironment::GetError() ==
-      WASAPITestErrorCode::kAudioClientActivationTimeout) {
-    base::ThreadPool::PostDelayedTask(
-        FROM_HERE, std::move(activate_audio_interface_callback),
-        base::Milliseconds(15000));
-    return S_OK;
-  }
-
-  base::ThreadPool::PostTask(FROM_HERE,
-                             std::move(activate_audio_interface_callback));
-  return S_OK;
-}
-
-}  // namespace media
diff --git a/media/audio/win/test_support/fake_win_wasapi_environment.h b/media/audio/win/test_support/fake_win_wasapi_environment.h
deleted file mode 100644
index fc49be8..0000000
--- a/media/audio/win/test_support/fake_win_wasapi_environment.h
+++ /dev/null
@@ -1,43 +0,0 @@
-// Copyright 2025 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_AUDIO_WIN_TEST_SUPPORT_FAKE_WIN_WASAPI_ENVIRONMENT_H_
-#define MEDIA_AUDIO_WIN_TEST_SUPPORT_FAKE_WIN_WASAPI_ENVIRONMENT_H_
-
-#include <mmdeviceapi.h>
-#include <windows.h>
-
-#include "base/win/windows_types.h"
-#include "media/audio/win/test_support/wasapi_test_error_code.h"
-
-namespace media {
-
-// This class is meant to mock the behavior of the WASAPI environment for
-// testing. It does that by simulating platform errors and providing fake
-// implementations of Windows APIs that return fake Windows interfaces.
-class FakeWinWASAPIEnvironment {
- public:
-  FakeWinWASAPIEnvironment();
-  FakeWinWASAPIEnvironment(const FakeWinWASAPIEnvironment&) = delete;
-  FakeWinWASAPIEnvironment& operator=(const FakeWinWASAPIEnvironment&) = delete;
-  ~FakeWinWASAPIEnvironment();
-
-  static HRESULT ActivateAudioInterfaceAsync(
-      LPCWSTR deviceInterfacePath,
-      REFIID riid,
-      PROPVARIANT* activationParams,
-      IActivateAudioInterfaceCompletionHandler* completionHandler,
-      IActivateAudioInterfaceAsyncOperation** activationOperation);
-
-  void SimulateError(WASAPITestErrorCode error) { error_ = error; }
-
-  static WASAPITestErrorCode GetError() { return error_; }
-
- private:
-  static WASAPITestErrorCode error_;
-};
-
-}  // namespace media
-
-#endif  // MEDIA_AUDIO_WIN_TEST_SUPPORT_FAKE_WIN_WASAPI_ENVIRONMENT_H_
diff --git a/media/audio/win/test_support/wasapi_test_error_code.h b/media/audio/win/test_support/wasapi_test_error_code.h
deleted file mode 100644
index 2bd8402..0000000
--- a/media/audio/win/test_support/wasapi_test_error_code.h
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright 2025 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_AUDIO_WIN_TEST_SUPPORT_WASAPI_TEST_ERROR_CODE_H_
-#define MEDIA_AUDIO_WIN_TEST_SUPPORT_WASAPI_TEST_ERROR_CODE_H_
-
-namespace media {
-
-enum class WASAPITestErrorCode {
-  kOk,
-  kActivateAudioInterfaceAsyncFailed,
-  kAudioClientActivationTimeout,
-  kAudioClientActivationAsyncOperationFailed,
-  kAudioClientActivationFailed,
-  kAudioClientGetBufferSizeFailed,
-};
-
-}  // namespace media
-
-#endif  // MEDIA_AUDIO_WIN_TEST_SUPPORT_WASAPI_TEST_ERROR_CODE_H_
diff --git a/net/http/http_stream_pool_attempt_manager.cc b/net/http/http_stream_pool_attempt_manager.cc
index 68ac97d..16ba0795 100644
--- a/net/http/http_stream_pool_attempt_manager.cc
+++ b/net/http/http_stream_pool_attempt_manager.cc
@@ -1585,8 +1585,17 @@
   CHECK(preconnect_jobs_.empty());
   availability_state_ = AvailabilityState::kDraining;
   service_endpoint_request_.reset();
-  // TODO(crbug.com/414173943): Cancel TcpBasedAttempts and QuicAttempt if
-  // exists.
+
+  // Cancel in-flight attempts so that draining AttemptManager won't have active
+  // connecting streams.
+  // TODO(crbug.com/414173943): It might be better not to cancel in-flight
+  // attempts (especially the QUIC attempt) for future requests/preconnects
+  // unless these aren't slow. Currently we just cancel them for similicity. If
+  // we want to keep these attempts in the draining `this`,
+  // Group::ConnectingStreamSocketCount() should check draining AttemptManagers.
+  CancelTcpBasedAttempts(StreamSocketCloseReason::kAbort);
+  CancelQuicAttempt(ERR_ABORTED);
+
   group_->OnAttemptManagerShuttingDown(this);
 }
 
diff --git a/net/http/http_stream_pool_attempt_manager_unittest.cc b/net/http/http_stream_pool_attempt_manager_unittest.cc
index 4c74c49..ac5e8fb 100644
--- a/net/http/http_stream_pool_attempt_manager_unittest.cc
+++ b/net/http/http_stream_pool_attempt_manager_unittest.cc
@@ -4787,6 +4787,12 @@
   EXPECT_EQ(requester.negotiated_protocol(), NextProto::kProtoQUIC);
   EXPECT_TRUE(quic_session_pool()->has_quic_ever_worked_on_current_network());
 
+  EXPECT_EQ(pool()
+                .GetGroupForTesting(requester.GetStreamKey())
+                ->ConnectingStreamSocketCount(),
+            0u)
+      << "Successful QUIC attempt should cancel in-flight TCP attempt";
+
   std::unique_ptr<HttpStream> stream = requester.ReleaseStream();
   LoadTimingInfo timing_info;
   ASSERT_TRUE(stream->GetLoadTimingInfo(&timing_info));
diff --git a/net/socket/tcp_client_socket_unittest.cc b/net/socket/tcp_client_socket_unittest.cc
index 9503ab329..53d61c8 100644
--- a/net/socket/tcp_client_socket_unittest.cc
+++ b/net/socket/tcp_client_socket_unittest.cc
@@ -2,12 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "net/base/port_util.h"
-#ifdef UNSAFE_BUFFERS_BUILD
-// TODO(crbug.com/390223051): Remove C-library calls to fix the errors.
-#pragma allow_unsafe_libc_calls
-#endif
-
 // This file contains some tests for TCPClientSocket.
 // transport_client_socket_unittest.cc contans some other tests that
 // are common for TCP and other types of sockets.
@@ -30,6 +24,7 @@
 #include "net/base/ip_address.h"
 #include "net/base/ip_endpoint.h"
 #include "net/base/net_errors.h"
+#include "net/base/port_util.h"
 #include "net/base/test_completion_callback.h"
 #include "net/log/net_log_source.h"
 #include "net/nqe/network_quality_estimator_test_util.h"
@@ -439,26 +434,24 @@
   old_traffic = GetTaggedBytes(tag_val2);
   SocketTag tag2(getuid(), tag_val2);
   s.ApplySocketTag(tag2);
-  const char kRequest1[] = "GET / HTTP/1.0";
+  const std::string kRequest1 = "GET / HTTP/1.0";
   auto write_buffer1 = base::MakeRefCounted<StringIOBuffer>(kRequest1);
   TestCompletionCallback write_callback1;
-  EXPECT_EQ(s.Write(write_buffer1.get(), strlen(kRequest1),
+  EXPECT_EQ(s.Write(write_buffer1.get(), write_buffer1->size(),
                     write_callback1.callback(), TRAFFIC_ANNOTATION_FOR_TESTS),
-            static_cast<int>(strlen(kRequest1)));
+            static_cast<int>(kRequest1.size()));
   EXPECT_GT(GetTaggedBytes(tag_val2), old_traffic);
 
   // Verify socket can be retagged with a new value and the current process's
   // UID.
   old_traffic = GetTaggedBytes(tag_val1);
   s.ApplySocketTag(tag1);
-  const char kRequest2[] = "\n\n";
-  scoped_refptr<IOBufferWithSize> write_buffer2 =
-      base::MakeRefCounted<IOBufferWithSize>(strlen(kRequest2));
-  memmove(write_buffer2->data(), kRequest2, strlen(kRequest2));
+  const std::string kRequest2 = "\n\n";
+  auto write_buffer2 = base::MakeRefCounted<StringIOBuffer>(kRequest2);
   TestCompletionCallback write_callback2;
-  EXPECT_EQ(s.Write(write_buffer2.get(), strlen(kRequest2),
+  EXPECT_EQ(s.Write(write_buffer2.get(), write_buffer2->size(),
                     write_callback2.callback(), TRAFFIC_ANNOTATION_FOR_TESTS),
-            static_cast<int>(strlen(kRequest2)));
+            static_cast<int>(kRequest2.size()));
   EXPECT_GT(GetTaggedBytes(tag_val1), old_traffic);
 
   s.Disconnect();
@@ -723,8 +716,8 @@
 
   // Write to the socket until a write doesn't complete synchronously.
   const int kBufferSize = 4096;
-  auto write_buffer = base::MakeRefCounted<IOBufferWithSize>(kBufferSize);
-  memset(write_buffer->data(), '1', kBufferSize);
+  auto write_buffer = base::MakeRefCounted<VectorIOBuffer>(
+      std::vector<uint8_t>(kBufferSize, '1'));
   TestCompletionCallback callback;
   while (true) {
     int rv =
@@ -806,8 +799,8 @@
 
     // Write to the socket until a write doesn't complete synchronously.
     const int kBufferSize = 4096;
-    auto write_buffer = base::MakeRefCounted<IOBufferWithSize>(kBufferSize);
-    memset(write_buffer->data(), '1', kBufferSize);
+    auto write_buffer = base::MakeRefCounted<VectorIOBuffer>(
+        std::vector<uint8_t>(kBufferSize, '1'));
     TestCompletionCallback write_callback;
     while (true) {
       int rv = client_socket->Write(write_buffer.get(), kBufferSize,
diff --git a/net/socket/tcp_server_socket_unittest.cc b/net/socket/tcp_server_socket_unittest.cc
index 647417cd..9da3be33 100644
--- a/net/socket/tcp_server_socket_unittest.cc
+++ b/net/socket/tcp_server_socket_unittest.cc
@@ -2,11 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifdef UNSAFE_BUFFERS_BUILD
-// TODO(crbug.com/390223051): Remove C-library calls to fix the errors.
-#pragma allow_unsafe_libc_calls
-#endif
-
 #include "net/socket/tcp_server_socket.h"
 
 #include <memory>
@@ -325,40 +320,34 @@
   EXPECT_THAT(connect_callback.GetResult(connect_result), IsOk());
 
   const std::string message("test message");
-  std::vector<char> buffer(message.size());
 
-  size_t bytes_written = 0;
-  while (bytes_written < message.size()) {
-    scoped_refptr<IOBufferWithSize> write_buffer =
-        base::MakeRefCounted<IOBufferWithSize>(message.size() - bytes_written);
-    memmove(write_buffer->data(), message.data(), message.size());
-
+  auto write_buffer = base::MakeRefCounted<DrainableIOBuffer>(
+      base::MakeRefCounted<StringIOBuffer>(message), message.size());
+  while (write_buffer->size() > 0u) {
     TestCompletionCallback write_callback;
     int write_result = accepted_socket->Write(
         write_buffer.get(), write_buffer->size(), write_callback.callback(),
         TRAFFIC_ANNOTATION_FOR_TESTS);
     write_result = write_callback.GetResult(write_result);
-    ASSERT_TRUE(write_result >= 0);
-    ASSERT_TRUE(bytes_written + write_result <= message.size());
-    bytes_written += write_result;
+    ASSERT_GE(write_result, 0);
+    ASSERT_LE(write_result, write_buffer->size());
+    write_buffer->DidConsume(write_result);
   }
 
-  size_t bytes_read = 0;
-  while (bytes_read < message.size()) {
-    scoped_refptr<IOBufferWithSize> read_buffer =
-        base::MakeRefCounted<IOBufferWithSize>(message.size() - bytes_read);
+  auto read_buffer = base::MakeRefCounted<DrainableIOBuffer>(
+      base::MakeRefCounted<IOBufferWithSize>(message.size()), message.size());
+  while (read_buffer->size() > 0u) {
     TestCompletionCallback read_callback;
     int read_result = connecting_socket.Read(
         read_buffer.get(), read_buffer->size(), read_callback.callback());
     read_result = read_callback.GetResult(read_result);
-    ASSERT_TRUE(read_result >= 0);
-    ASSERT_TRUE(bytes_read + read_result <= message.size());
-    memmove(&buffer[bytes_read], read_buffer->data(), read_result);
-    bytes_read += read_result;
+    ASSERT_GE(read_result, 0);
+    ASSERT_LE(read_result, read_buffer->size());
+    read_buffer->DidConsume(read_result);
   }
 
-  std::string received_message(buffer.begin(), buffer.end());
-  ASSERT_EQ(message, received_message);
+  read_buffer->SetOffset(0);
+  ASSERT_EQ(message, base::as_string_view(read_buffer->span()));
 }
 
 }  // namespace
diff --git a/net/socket/tcp_socket_unittest.cc b/net/socket/tcp_socket_unittest.cc
index 61768834..c7ab0f0 100644
--- a/net/socket/tcp_socket_unittest.cc
+++ b/net/socket/tcp_socket_unittest.cc
@@ -12,6 +12,7 @@
 #include <stddef.h>
 #include <string.h>
 
+#include <algorithm>
 #include <memory>
 #include <string>
 #include <vector>
@@ -610,7 +611,7 @@
   scoped_refptr<IOBufferWithDestructionCallback> write_buffer(
       base::MakeRefCounted<IOBufferWithDestructionCallback>(
           run_loop.QuitClosure()));
-  memset(write_buffer->data(), '1', write_buffer->size());
+  std::ranges::fill(write_buffer->span(), '1');
   TestCompletionCallback write_callback;
   while (true) {
     int result = connecting_socket->Write(
diff --git a/net/socket/tcp_socket_win.cc b/net/socket/tcp_socket_win.cc
index fbd4c46d..ac8a15b 100644
--- a/net/socket/tcp_socket_win.cc
+++ b/net/socket/tcp_socket_win.cc
@@ -2,11 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifdef UNSAFE_BUFFERS_BUILD
-// TODO(crbug.com/40284755): Remove this and spanify to fix the errors.
-#pragma allow_unsafe_buffers
-#endif
-
 #include "net/socket/tcp_socket.h"
 
 #include <errno.h>
@@ -188,7 +183,7 @@
   // TODO(mmenke): Can writes be switched to WSAEventSelect as well? That would
   // allow removing this class. The only concern is whether that would have a
   // negative perf impact.
-  OVERLAPPED write_overlapped_;
+  OVERLAPPED write_overlapped_ = {};
 
   // The buffers used in Read() and Write().
   scoped_refptr<IOBuffer> read_iobuffer_;
@@ -247,7 +242,6 @@
       socket_(socket),
       reader_(this),
       writer_(this) {
-  memset(&write_overlapped_, 0, sizeof(write_overlapped_));
   write_overlapped_.hEvent = WSACreateEvent();
 }
 
@@ -259,7 +253,6 @@
   // in Detach().
   write_watcher_.StopWatching();
   WSACloseEvent(write_overlapped_.hEvent);
-  memset(&write_overlapped_, 0xaf, sizeof(write_overlapped_));
 }
 
 void TCPSocketDefaultWin::CoreImpl::WatchForRead() {
diff --git a/net/socket/transport_client_socket_test_util.cc b/net/socket/transport_client_socket_test_util.cc
index 3045a4a..32d09ee 100644
--- a/net/socket/transport_client_socket_test_util.cc
+++ b/net/socket/transport_client_socket_test_util.cc
@@ -2,16 +2,12 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifdef UNSAFE_BUFFERS_BUILD
-// TODO(crbug.com/390223051): Remove C-library calls to fix the errors.
-#pragma allow_unsafe_libc_calls
-#endif
+#include "net/socket/transport_client_socket_test_util.h"
 
 #include <memory>
 #include <string>
 
-#include "net/socket/transport_client_socket_test_util.h"
-
+#include "base/check_op.h"
 #include "base/memory/ref_counted.h"
 #include "net/base/io_buffer.h"
 #include "net/base/net_errors.h"
@@ -24,14 +20,12 @@
 void SendRequestAndResponse(StreamSocket* socket,
                             StreamSocket* connected_socket) {
   // Send client request.
-  const char request_text[] = "GET / HTTP/1.0\r\n\r\n";
-  int request_len = strlen(request_text);
+  const std::string kRequestText = "GET / HTTP/1.0\r\n\r\n";
   scoped_refptr<DrainableIOBuffer> request_buffer =
       base::MakeRefCounted<DrainableIOBuffer>(
-          base::MakeRefCounted<IOBufferWithSize>(request_len), request_len);
-  memcpy(request_buffer->data(), request_text, request_len);
+          base::MakeRefCounted<StringIOBuffer>(kRequestText),
+          kRequestText.size());
 
-  int bytes_written = 0;
   while (request_buffer->BytesRemaining() > 0) {
     TestCompletionCallback write_callback;
     int write_result =
@@ -40,18 +34,15 @@
 
     write_result = write_callback.GetResult(write_result);
     ASSERT_GT(write_result, 0);
-    ASSERT_LE(bytes_written + write_result, request_len);
+    ASSERT_LE(write_result, request_buffer->size());
     request_buffer->DidConsume(write_result);
-
-    bytes_written += write_result;
   }
-  ASSERT_EQ(request_len, bytes_written);
 
   // Confirm that the server receives what client sent.
   std::string data_received =
-      ReadDataOfExpectedLength(connected_socket, bytes_written);
+      ReadDataOfExpectedLength(connected_socket, kRequestText.length());
   ASSERT_TRUE(connected_socket->IsConnectedAndIdle());
-  ASSERT_EQ(request_text, data_received);
+  ASSERT_EQ(kRequestText, data_received);
 
   // Write server response.
   SendServerResponse(connected_socket);
@@ -59,29 +50,29 @@
 
 std::string ReadDataOfExpectedLength(StreamSocket* socket,
                                      int expected_bytes_read) {
-  int bytes_read = 0;
-  scoped_refptr<IOBufferWithSize> read_buffer =
-      base::MakeRefCounted<IOBufferWithSize>(expected_bytes_read);
-  while (bytes_read < expected_bytes_read) {
+  auto read_buffer = base::MakeRefCounted<DrainableIOBuffer>(
+      base::MakeRefCounted<IOBufferWithSize>(expected_bytes_read),
+      expected_bytes_read);
+  while (read_buffer->size() > 0) {
     TestCompletionCallback read_callback;
-    int rv = socket->Read(read_buffer.get(), expected_bytes_read - bytes_read,
+    int rv = socket->Read(read_buffer.get(), read_buffer->size(),
                           read_callback.callback());
     EXPECT_TRUE(rv >= 0 || rv == ERR_IO_PENDING);
     rv = read_callback.GetResult(rv);
-    EXPECT_GE(rv, 0);
-    bytes_read += rv;
+    CHECK_GE(rv, 0);
+    EXPECT_LE(rv, read_buffer->size());
+    read_buffer->DidConsume(rv);
   }
-  EXPECT_EQ(expected_bytes_read, bytes_read);
-  return std::string(read_buffer->data(), bytes_read);
+  read_buffer->SetOffset(0);
+  return std::string(base::as_string_view(read_buffer->span()));
 }
 
 void SendServerResponse(StreamSocket* socket) {
-  const char kServerReply[] = "HTTP/1.1 404 Not Found";
-  int reply_len = strlen(kServerReply);
+  const std::string kServerReply = "HTTP/1.1 404 Not Found";
   scoped_refptr<DrainableIOBuffer> write_buffer =
       base::MakeRefCounted<DrainableIOBuffer>(
-          base::MakeRefCounted<IOBufferWithSize>(reply_len), reply_len);
-  memcpy(write_buffer->data(), kServerReply, reply_len);
+          base::MakeRefCounted<StringIOBuffer>(kServerReply),
+          kServerReply.size());
   int bytes_written = 0;
   while (write_buffer->BytesRemaining() > 0) {
     TestCompletionCallback write_callback;
@@ -90,7 +81,8 @@
                       write_callback.callback(), TRAFFIC_ANNOTATION_FOR_TESTS);
     write_result = write_callback.GetResult(write_result);
     ASSERT_GE(write_result, 0);
-    ASSERT_LE(bytes_written + write_result, reply_len);
+    ASSERT_LE(bytes_written + write_result,
+              static_cast<int>(kServerReply.size()));
     write_buffer->DidConsume(write_result);
     bytes_written += write_result;
   }
diff --git a/net/socket/transport_client_socket_unittest.cc b/net/socket/transport_client_socket_unittest.cc
index 13b8747..50c7056 100644
--- a/net/socket/transport_client_socket_unittest.cc
+++ b/net/socket/transport_client_socket_unittest.cc
@@ -2,11 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifdef UNSAFE_BUFFERS_BUILD
-// TODO(crbug.com/390223051): Remove C-library calls to fix the errors.
-#pragma allow_unsafe_libc_calls
-#endif
-
 #include "net/socket/transport_client_socket_test_util.h"
 
 #include <string>
@@ -277,9 +272,8 @@
   EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
 
   const int kWriteBufLen = 64 * 1024;
-  auto request_buffer = base::MakeRefCounted<IOBufferWithSize>(kWriteBufLen);
-  char* request_data = request_buffer->data();
-  memset(request_data, 'A', kWriteBufLen);
+  auto request_buffer = base::MakeRefCounted<VectorIOBuffer>(
+      std::vector<uint8_t>(kWriteBufLen, 'A'));
   TestCompletionCallback write_callback;
 
   int bytes_written = 0;
@@ -309,9 +303,8 @@
   EstablishConnection(&callback);
 
   const int kWriteBufLen = 64 * 1024;
-  auto request_buffer = base::MakeRefCounted<IOBufferWithSize>(kWriteBufLen);
-  char* request_data = request_buffer->data();
-  memset(request_data, 'A', kWriteBufLen);
+  auto request_buffer = base::MakeRefCounted<VectorIOBuffer>(
+      std::vector<uint8_t>(kWriteBufLen, 'A'));
   TestCompletionCallback write_callback;
 
   int bytes_written = 0;
diff --git a/styleguide/python/OWNERS b/styleguide/python/OWNERS
index a6d08ea..a0e0826 100644
--- a/styleguide/python/OWNERS
+++ b/styleguide/python/OWNERS
@@ -1,3 +1,2 @@
 agrieve@chromium.org
-dpranke@google.com
 wnwen@chromium.org
diff --git a/testing/android/OWNERS b/testing/android/OWNERS
index 7b55b89b..9365138 100644
--- a/testing/android/OWNERS
+++ b/testing/android/OWNERS
@@ -1,7 +1,6 @@
 agrieve@chromium.org
 dtrainor@chromium.org
 nyquist@chromium.org
-skyostil@chromium.org
 tedchoc@chromium.org
 torne@chromium.org
 yfriedman@chromium.org
diff --git a/testing/buildbot/filters/ios.content_browsertests.filter b/testing/buildbot/filters/ios.content_browsertests.filter
index c9ce19b..5395f19 100644
--- a/testing/buildbot/filters/ios.content_browsertests.filter
+++ b/testing/buildbot/filters/ios.content_browsertests.filter
@@ -753,3 +753,7 @@
 # These tests have been failing since https://crrev.com/c/6459930.
 -Default/MediaTest.HLSSingleWithoutExtension/0
 -Default/MediaTest.HLSSingleWithoutExtension/1
+
+# TODO(crbug.com/418103029): The test has been failing since
+# https://crrev.com/c/6532249.
+-SecurityExploitBrowserTest.DidFailLoadWithInvalidErrorCode
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 645d950..8a5dd4d 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -11370,21 +11370,6 @@
             ]
         }
     ],
-    "IOSAutofillCorrectUserEditedBitInParsedField": [
-        {
-            "platforms": [
-                "ios"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled",
-                    "enable_features": [
-                        "AutofillCorrectUserEditedBitInParsedField"
-                    ]
-                }
-            ]
-        }
-    ],
     "IOSAutofillFixPostFillingPaymentSheet": [
         {
             "platforms": [
@@ -11562,21 +11547,6 @@
             ]
         }
     ],
-    "IOSCleanupHangingPasswordFormExtractionRequests": [
-        {
-            "platforms": [
-                "ios"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled",
-                    "enable_features": [
-                        "IosCleanupHangingPasswordFormExtractionRequests"
-                    ]
-                }
-            ]
-        }
-    ],
     "IOSContainedTabGroup": [
         {
             "platforms": [
@@ -12120,21 +12090,6 @@
             ]
         }
     ],
-    "IOSPasswordBottomSheetV2": [
-        {
-            "platforms": [
-                "ios"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled",
-                    "enable_features": [
-                        "IOSPasswordBottomSheetV2"
-                    ]
-                }
-            ]
-        }
-    ],
     "IOSProactivePasswordGenerationBottomSheet": [
         {
             "platforms": [
@@ -16950,6 +16905,25 @@
             ]
         }
     ],
+    "PermissionSiteSettingsRadioButton": [
+        {
+            "platforms": [
+                "chromeos",
+                "linux",
+                "windows",
+                "mac",
+                "android"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "PermissionSiteSettingsRadioButton"
+                    ]
+                }
+            ]
+        }
+    ],
     "PermissionsAIv1": [
         {
             "platforms": [
@@ -23275,6 +23249,24 @@
             ]
         }
     ],
+    "TabCaptureInfobarLinks": [
+        {
+            "platforms": [
+                "chromeos",
+                "linux",
+                "mac",
+                "windows"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "TabCaptureInfobarLinks"
+                    ]
+                }
+            ]
+        }
+    ],
     "TabGroupParityAndroidV2": [
         {
             "platforms": [
diff --git a/third_party/angle b/third_party/angle
index db71e8f..3f7bded 160000
--- a/third_party/angle
+++ b/third_party/angle
@@ -1 +1 @@
-Subproject commit db71e8fa7c26d18f76d7b9e9474447b20f1c73b3
+Subproject commit 3f7bded1c0334b099461de9b19ecc00d832ebc5a
diff --git a/third_party/blink/renderer/bindings/core/v8/v8_script_runner.cc b/third_party/blink/renderer/bindings/core/v8/v8_script_runner.cc
index e3c6898..d989be4 100644
--- a/third_party/blink/renderer/bindings/core/v8/v8_script_runner.cc
+++ b/third_party/blink/renderer/bindings/core/v8/v8_script_runner.cc
@@ -969,10 +969,15 @@
 
   // [not specced] Store V8 code cache on successful evaluation.
   if (result.GetResultType() == ScriptEvaluationResult::ResultType::kSuccess) {
+    // Script IDs are not available on non-source text modules, so we give them
+    // a default value.
     DEVTOOLS_TIMELINE_TRACE_EVENT_WITH_CATEGORIES(
         TRACE_DISABLED_BY_DEFAULT("devtools.target-rundown"), "ModuleEvaluated",
         inspector_target_rundown_event::Data, execution_context, isolate,
-        script_state, module_script->V8Module()->ScriptId());
+        script_state,
+        module_script->V8Module()->IsSourceTextModule()
+            ? module_script->V8Module()->ScriptId()
+            : v8::UnboundScript::kNoScriptId);
     execution_context->GetTaskRunner(TaskType::kNetworking)
         ->PostTask(
             FROM_HERE,
diff --git a/third_party/blink/renderer/core/css/properties/computed_style_utils.cc b/third_party/blink/renderer/core/css/properties/computed_style_utils.cc
index cf58c392..3b68fee4 100644
--- a/third_party/blink/renderer/core/css/properties/computed_style_utils.cc
+++ b/third_party/blink/renderer/core/css/properties/computed_style_utils.cc
@@ -3208,6 +3208,21 @@
   NOTREACHED();
 }
 
+namespace {
+
+CSSValue* CounterValueFromCounterData(const ContentData& content_data) {
+  const auto& counter = To<CounterContentData>(content_data);
+  auto* identifier =
+      MakeGarbageCollected<CSSCustomIdentValue>(counter.Identifier());
+  auto* separator = MakeGarbageCollected<CSSStringValue>(counter.Separator());
+  auto* list_style =
+      MakeGarbageCollected<CSSCustomIdentValue>(counter.ListStyle());
+  return MakeGarbageCollected<cssvalue::CSSCounterValue>(identifier, list_style,
+                                                         separator);
+}
+
+}  // namespace
+
 CSSValue* ComputedStyleUtils::ValueForContentData(const ComputedStyle& style,
                                                   bool allow_visited_style,
                                                   CSSValuePhase value_phase) {
@@ -3220,22 +3235,15 @@
 
   CSSValueList* outer_list = CSSValueList::CreateSlashSeparated();
   CSSValueList* list = CSSValueList::CreateSpaceSeparated();
-
   // Alternative text optionally specified after a forward slash appearing after
   // the last content list item.
-  CSSStringValue* alt_text = nullptr;
+  CSSValueList* alt_list = CSSValueList::CreateSpaceSeparated();
   for (const ContentData* content_data = style.GetContentData(); content_data;
        content_data = content_data->Next()) {
-    if (content_data->IsCounter()) {
-      const CounterContentData& counter = To<CounterContentData>(*content_data);
-      auto* identifier =
-          MakeGarbageCollected<CSSCustomIdentValue>(counter.Identifier());
-      auto* separator =
-          MakeGarbageCollected<CSSStringValue>(counter.Separator());
-      auto* list_style =
-          MakeGarbageCollected<CSSCustomIdentValue>(counter.ListStyle());
-      list->Append(*MakeGarbageCollected<cssvalue::CSSCounterValue>(
-          identifier, list_style, separator));
+    if (content_data->IsAltCounter()) {
+      alt_list->Append(*CounterValueFromCounterData(*content_data));
+    } else if (content_data->IsCounter()) {
+      list->Append(*CounterValueFromCounterData(*content_data));
     } else if (content_data->IsImage()) {
       const StyleImage* image = To<ImageContentData>(content_data)->GetImage();
       DCHECK(image);
@@ -3248,9 +3256,8 @@
       const QuoteType quote_type = To<QuoteContentData>(content_data)->Quote();
       list->Append(*CSSIdentifierValue::Create(ValueForQuoteType(quote_type)));
     } else if (content_data->IsAltText()) {
-      alt_text = MakeGarbageCollected<CSSStringValue>(
-          To<AltTextContentData>(content_data)->ConcatenateAltText());
-      break;
+      alt_list->Append(*MakeGarbageCollected<CSSStringValue>(
+          To<AltTextContentData>(content_data)->GetText()));
     } else {
       NOTREACHED();
     }
@@ -3258,8 +3265,8 @@
   DCHECK(list->length());
 
   outer_list->Append(*list);
-  if (alt_text) {
-    outer_list->Append(*alt_text);
+  if (alt_list->length()) {
+    outer_list->Append(*alt_list);
   }
   return outer_list;
 }
diff --git a/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc b/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc
index 31142a3..757cb25 100644
--- a/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc
+++ b/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc
@@ -2996,27 +2996,33 @@
   }
   outer_list->Append(*values);
   if (alt_text_present) {
-    CSSValueList* alt_text_values = CSSValueList::CreateSpaceSeparated();
+    CSSValueList* alt_values = CSSValueList::CreateSpaceSeparated();
     do {
       CSSParserSavePoint savepoint(stream);
-      CSSValue* alt_text = nullptr;
+      CSSValue* alt_value = nullptr;
       if (stream.Peek().FunctionId() == CSSValueID::kAttr &&
           !RuntimeEnabledFeatures::CSSAdvancedAttrFunctionEnabled()) {
-        alt_text = ConsumeAttr(stream, context);
+        alt_value = ConsumeAttr(stream, context);
+      } else if (RuntimeEnabledFeatures::CSSAltCounterEnabled() &&
+                 stream.Peek().FunctionId() == CSSValueID::kCounter) {
+        alt_value = ConsumeCounterContent(stream, context, false);
+      } else if (RuntimeEnabledFeatures::CSSAltCounterEnabled() &&
+                 stream.Peek().FunctionId() == CSSValueID::kCounters) {
+        alt_value = ConsumeCounterContent(stream, context, true);
       } else {
-        alt_text = css_parsing_utils::ConsumeString(stream);
+        alt_value = css_parsing_utils::ConsumeString(stream);
       }
-      if (!alt_text) {
+      if (!alt_value) {
         break;
       }
-      alt_text_values->Append(*alt_text);
+      alt_values->Append(*alt_value);
       savepoint.Release();
     } while (!stream.AtEnd());
-    if (!alt_text_values->length()) {
+    if (!alt_values->length()) {
       return nullptr;
     }
 
-    outer_list->Append(*alt_text_values);
+    outer_list->Append(*alt_values);
   }
   return outer_list;
 }
@@ -3174,8 +3180,18 @@
   if (outer_list.length() > 1) {
     CHECK_EQ(outer_list.length(), 2U);
     for (auto& item : To<CSSValueList>(outer_list.Item(1))) {
-      auto* alt_content = MakeGarbageCollected<AltTextContentData>(
-          GetStringFromAttributeOrStringValue(*item, state, builder));
+      ContentData* alt_content = nullptr;
+      if (const auto* counter_value =
+              DynamicTo<cssvalue::CSSCounterValue>(item.Get())) {
+        alt_content = MakeGarbageCollected<AltCounterContentData>(
+            AtomicString(counter_value->Identifier()),
+            counter_value->ListStyle(),
+            AtomicString(counter_value->Separator()),
+            counter_value->GetTreeScope());
+      } else {
+        alt_content = MakeGarbageCollected<AltTextContentData>(
+            GetStringFromAttributeOrStringValue(*item, state, builder));
+      }
       prev_content->SetNext(alt_content);
       prev_content = alt_content;
     }
diff --git a/third_party/blink/renderer/core/dom/pseudo_element.cc b/third_party/blink/renderer/core/dom/pseudo_element.cc
index fbb127f7..8191bac 100644
--- a/third_party/blink/renderer/core/dom/pseudo_element.cc
+++ b/third_party/blink/renderer/core/dom/pseudo_element.cc
@@ -493,7 +493,7 @@
   DCHECK(!style.ContentPreventsBoxGeneration());
   for (const ContentData* content = style.GetContentData(); content;
        content = content->Next()) {
-    if (!content->IsAltText()) {
+    if (!content->IsAltText() && !content->IsAltCounter()) {
       LayoutObject* child = content->CreateLayoutObject(*layout_object);
       if (layout_object->IsChildAllowed(child, style)) {
         layout_object->AddChild(child);
diff --git a/third_party/blink/renderer/core/html/html_permission_element.cc b/third_party/blink/renderer/core/html/html_permission_element.cc
index 1429ac6..1773717 100644
--- a/third_party/blink/renderer/core/html/html_permission_element.cc
+++ b/third_party/blink/renderer/core/html/html_permission_element.cc
@@ -417,21 +417,6 @@
   DCHECK(RuntimeEnabledFeatures::PermissionElementEnabled(
       document.GetExecutionContext()));
   SetHasCustomStyleCallbacks();
-  intersection_observer_ = IntersectionObserver::Create(
-      GetDocument(),
-      WTF::BindRepeating(&HTMLPermissionElement::OnIntersectionChanged,
-                         WrapWeakPersistent(this)),
-      LocalFrameUkmAggregator::kPermissionElementIntersectionObserver,
-      IntersectionObserver::Params{
-          .thresholds = {kIntersectionThreshold},
-          .semantics = IntersectionObserver::kFractionOfTarget,
-          .behavior = IntersectionObserver::kDeliverDuringPostLifecycleSteps,
-          .delay = base::Milliseconds(100),
-          .track_visibility = true,
-          .expose_occluder_id = true,
-      });
-
-  intersection_observer_->observe(this);
   EnsureUserAgentShadowRoot();
   UseCounter::Count(document, WebFeature::kHTMLPermissionElement);
 }
@@ -479,16 +464,39 @@
 Node::InsertionNotificationRequest HTMLPermissionElement::InsertedInto(
     ContainerNode& insertion_point) {
   HTMLElement::InsertedInto(insertion_point);
-  MaybeRegisterPageEmbeddedPermissionControl();
+  if (!permission_descriptors_.empty()) {
+    CachedPermissionStatus::From(GetDocument().domWindow())
+        ->RegisterClient(this, permission_descriptors_);
+  }
   return kInsertionDone;
 }
 
 void HTMLPermissionElement::AttachLayoutTree(AttachContext& context) {
   Element::AttachLayoutTree(context);
+  if (fallback_mode_) {
+    return;
+  }
   DisableClickingTemporarily(DisableReason::kRecentlyAttachedToLayoutTree,
                              kDefaultDisableTimeout);
   CHECK(GetDocument().View());
   GetDocument().View()->RegisterForLifecycleNotifications(this);
+  if (!intersection_observer_) {
+    intersection_observer_ = IntersectionObserver::Create(
+        GetDocument(),
+        WTF::BindRepeating(&HTMLPermissionElement::OnIntersectionChanged,
+                           WrapWeakPersistent(this)),
+        LocalFrameUkmAggregator::kPermissionElementIntersectionObserver,
+        IntersectionObserver::Params{
+            .thresholds = {kIntersectionThreshold},
+            .semantics = IntersectionObserver::kFractionOfTarget,
+            .behavior = IntersectionObserver::kDeliverDuringPostLifecycleSteps,
+            .delay = base::Milliseconds(100),
+            .track_visibility = true,
+            .expose_occluder_id = true,
+        });
+
+    intersection_observer_->observe(this);
+  }
 }
 
 void HTMLPermissionElement::DetachLayoutTree(bool performing_reattach) {
@@ -507,15 +515,11 @@
     disable_reason_expire_timer_.Stop();
   }
   intersection_rect_ = std::nullopt;
-  if (embedded_permission_control_receiver_.is_bound()) {
-    embedded_permission_control_receiver_.reset();
-  }
-
-  is_registered_in_browser_process_ = false;
   if (LocalDOMWindow* window = GetDocument().domWindow()) {
     CachedPermissionStatus::From(window)->UnregisterClient(
         this, permission_descriptors_);
   }
+  EnsureUnregisterPageEmbeddedPermissionControl();
 }
 
 void HTMLPermissionElement::Focus(const FocusParams& params) {
@@ -572,6 +576,14 @@
              DisableReason::kIntersectionVisibilityOccludedOrDistorted);
 }
 
+bool HTMLPermissionElement::IsRenderered() const {
+  LayoutObject* layout_object = GetLayoutObject();
+  if (!layout_object) {
+    return false;
+  }
+  return layout_object->StyleRef().Visibility() == EVisibility::kVisible;
+}
+
 // static
 Vector<PermissionDescriptorPtr>
 HTMLPermissionElement::ParsePermissionDescriptorsForTesting(
@@ -697,16 +709,27 @@
     }
   }
 
-  CachedPermissionStatus::From(GetDocument().domWindow())
-      ->RegisterClient(this, permission_descriptors_);
+  if (!IsRenderered()) {
+    return false;
+  }
+
   mojo::PendingRemote<EmbeddedPermissionControlClient> client;
   embedded_permission_control_receiver_.Bind(
       client.InitWithNewPipeAndPassReceiver(), GetTaskRunner());
+  CHECK(embedded_permission_control_receiver_.is_bound());
   GetPermissionService()->RegisterPageEmbeddedPermissionControl(
       mojo::Clone(permission_descriptors_), std::move(client));
   return true;
 }
 
+void HTMLPermissionElement::EnsureUnregisterPageEmbeddedPermissionControl() {
+  if (embedded_permission_control_receiver_.is_bound()) {
+    embedded_permission_control_receiver_.reset();
+  }
+
+  is_registered_in_browser_process_ = false;
+}
+
 void HTMLPermissionElement::LangAttributeChanged() {
   UpdateText();
   HTMLElement::LangAttributeChanged();
@@ -822,11 +845,6 @@
     builder.SetWordSpacing(0);
   }
 
-  if (builder.GetDisplayStyle().Display() != EDisplay::kNone &&
-      builder.GetDisplayStyle().Display() != EDisplay::kInlineBlock) {
-    builder.SetDisplay(EDisplay::kInlineBlock);
-  }
-
   if (builder.GetFontDescription().LetterSpacing() >
       kMaximumLetterSpacingToFontSizeRatio * builder.FontSize()) {
     builder.SetLetterSpacing(builder.FontSize() *
@@ -1164,6 +1182,12 @@
     return false;
   }
 
+  // Do not check click-disabling reasons if the PEPC validation feature is
+  // disabled. This should only occur in testing scenarios.
+  if (RuntimeEnabledFeatures::BypassPepcSecurityForTestingEnabled()) {
+    return true;
+  }
+
   if (!is_registered_in_browser_process()) {
     AddConsoleError(
         WTF::StrCat({"The permission element '", GetType(),
@@ -1175,12 +1199,6 @@
     return false;
   }
 
-  // Do not check click-disabling reasons if the PEPC validation feature is
-  // disabled. This should only occur in testing scenarios.
-  if (RuntimeEnabledFeatures::BypassPepcSecurityForTestingEnabled()) {
-    return true;
-  }
-
   // Remove expired reasons. If the remaining map is not empty, clicking is
   // disabled. Record and log all the remaining reasons in the map in this case.
   base::TimeTicks now = base::TimeTicks::Now();
@@ -1459,14 +1477,26 @@
 }
 
 bool HTMLPermissionElement::IsStyleValid() {
+  const ComputedStyle* style = GetComputedStyle();
+
   // No computed style when using `display: none`.
-  if (!GetComputedStyle()) {
+  if (!style) {
     base::UmaHistogramEnumeration("Blink.PermissionElement.InvalidStyleReason",
                                   InvalidStyleReason::kNoComputedStyle);
     return false;
   }
 
-  if (AreColorsNonOpaque(GetComputedStyle())) {
+  if (style->GetDisplayStyle().Display() != EDisplay::kNone &&
+      style->GetDisplayStyle().Display() != EDisplay::kInlineBlock) {
+    AddConsoleWarning(WTF::StrCat(
+        {"Invalid display style of the permission element", GetType(),
+         ". Only 'display: inline-block' or 'display: none' is allowed"}));
+    base::UmaHistogramEnumeration("Blink.PermissionElement.InvalidStyleReason",
+                                  InvalidStyleReason::kInvalidDisplayProperty);
+    return false;
+  }
+
+  if (AreColorsNonOpaque(style)) {
     AddConsoleWarning(
         WTF::StrCat({"Color or background color of the permission element '",
                      GetType(), "' is non-opaque"}));
@@ -1476,8 +1506,7 @@
     return false;
   }
 
-  if (ContrastBetweenColorAndBackgroundColor(GetComputedStyle()) <
-      kMinimumAllowedContrast) {
+  if (ContrastBetweenColorAndBackgroundColor(style) < kMinimumAllowedContrast) {
     AddConsoleWarning(
         WTF::StrCat({"Contrast between color and background color of the "
                      "permission element '",
@@ -1500,11 +1529,9 @@
       GetDocument().GetFrame()->LocalFrameRoot().LayoutZoomFactor() /
       GetDocument().GetFrame()->LocalFrameRoot().CssZoomFactor();
 
-  float font_size_dip =
-      GetComputedStyle()->ComputedFontSize() / non_css_layout_zoom_factor;
+  float font_size_dip = style->ComputedFontSize() / non_css_layout_zoom_factor;
 
-  bool is_font_monospace =
-      GetComputedStyle()->GetFontDescription().IsMonospace();
+  bool is_font_monospace = style->GetFontDescription().IsMonospace();
 
   // The min size is what `font-size:small` looks like when rendered in the
   // document element of the local root frame, without any intervening
@@ -1645,6 +1672,12 @@
                                kDefaultDisableTimeout);
   }
   intersection_rect_ = intersection_rect;
+
+  if (IsRenderered()) {
+    MaybeRegisterPageEmbeddedPermissionControl();
+  } else {
+    EnsureUnregisterPageEmbeddedPermissionControl();
+  }
 }
 
 gfx::Rect HTMLPermissionElement::ComputeIntersectionRectWithViewport(
@@ -1678,8 +1711,9 @@
 void HTMLPermissionElement::EnableFallbackMode() {
   CHECK(!fallback_mode_);
   fallback_mode_ = true;
-  intersection_observer_->unobserve(this);
-
+  if (intersection_observer_) {
+    intersection_observer_->unobserve(this);
+  }
   // Adding this slot element will make all children of the permission element
   // render, the permission element's built-in elements are removed at the same
   // time.
diff --git a/third_party/blink/renderer/core/html/html_permission_element.h b/third_party/blink/renderer/core/html/html_permission_element.h
index 3b03e04e..d5dfc46 100644
--- a/third_party/blink/renderer/core/html/html_permission_element.h
+++ b/third_party/blink/renderer/core/html/html_permission_element.h
@@ -86,6 +86,7 @@
 
   bool HasInvalidStyle() const;
   bool IsOccluded() const;
+  bool IsRenderered() const;
   bool granted() const { return PermissionsGranted(); }
 
   // Given an input type, return permissions list. This method is for testing
@@ -136,6 +137,8 @@
                            FontSizeCanDisableElement);
   FRIEND_TEST_ALL_PREFIXES(HTMLPermissionElementSimTest,
                            MovePEPCToAnotherDocument);
+  FRIEND_TEST_ALL_PREFIXES(HTMLPermissionElementSimTest,
+                           RegisterAfterBeingVisible);
   FRIEND_TEST_ALL_PREFIXES(HTMLPermissionElementLayoutChangeTest,
                            InvalidatePEPCAfterMove);
   FRIEND_TEST_ALL_PREFIXES(HTMLPermissionElementLayoutChangeTest,
@@ -209,8 +212,9 @@
     kLowConstrastColorAndBackgroundColor = 1,
     kTooSmallFontSize = 3,
     kTooLargeFontSize = 4,
+    kInvalidDisplayProperty = 5,
 
-    kMaxValue = kTooLargeFontSize,
+    kMaxValue = kInvalidDisplayProperty,
   };
   // LINT.ThenChange(//tools/metrics/histograms/metadata/blink/enums.xml:PermissionElementInvalidStyleReason)
 
@@ -309,6 +313,9 @@
   // process.
   bool MaybeRegisterPageEmbeddedPermissionControl();
 
+  // Ensure we reset the PEPC IPC endpoint.
+  void EnsureUnregisterPageEmbeddedPermissionControl();
+
   // blink::Element implements
   void AttributeChanged(const AttributeModificationParams& params) override;
   void DidAddUserAgentShadowRoot(ShadowRoot&) override;
diff --git a/third_party/blink/renderer/core/html/html_permission_element_test.cc b/third_party/blink/renderer/core/html/html_permission_element_test.cc
index d8af608f..66324bf 100644
--- a/third_party/blink/renderer/core/html/html_permission_element_test.cc
+++ b/third_party/blink/renderer/core/html/html_permission_element_test.cc
@@ -450,6 +450,7 @@
     }
     GetDocument().body()->AppendChild(permission_element);
     GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest);
+    GetDocument().View()->UpdateAllLifecyclePhasesForTest();
     return permission_element;
   }
 
@@ -559,6 +560,7 @@
                                      AtomicString("width: auto; height: auto"));
     GetDocument().body()->AppendChild(permission_element);
     GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest);
+    GetDocument().View()->UpdateAllLifecyclePhasesForTest();
     EXPECT_EQ(
         data.expected_text,
         permission_element->permission_text_span_for_testing()->innerText());
@@ -598,6 +600,7 @@
     permission_service()->NotifyPermissionStatusChange(
         PermissionName::GEOLOCATION, MojoPermissionStatus::ASK);
     GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest);
+    GetDocument().View()->UpdateAllLifecyclePhasesForTest();
     EXPECT_EQ(
         data.expected_text_ask,
         permission_element->permission_text_span_for_testing()->innerText());
@@ -605,6 +608,7 @@
     permission_service()->NotifyPermissionStatusChange(
         PermissionName::GEOLOCATION, MojoPermissionStatus::GRANTED);
     GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest);
+    GetDocument().View()->UpdateAllLifecyclePhasesForTest();
     EXPECT_EQ(
         data.expected_text_allowed,
         permission_element->permission_text_span_for_testing()->innerText());
@@ -661,6 +665,7 @@
       permission_element->setAttribute(html_names::kPreciselocationAttr,
                                        AtomicString(""));
     }
+    UpdateAllLifecyclePhasesForTest();
     RegistrationWaiter(permission_element).Wait();
     EXPECT_EQ(
         data.expected_text,
@@ -989,6 +994,7 @@
     }
     document.body()->AppendChild(permission_element);
     document.UpdateStyleAndLayout(DocumentUpdateReason::kTest);
+    GetDocument().View()->UpdateAllLifecyclePhasesForTest();
     return permission_element;
   }
 
@@ -1081,7 +1087,6 @@
         static_cast<frame_test_helpers::TestWebFrameClient*>(
             first_child_frame->Client())
             ->ConsoleMessages();
-    EXPECT_EQ(first_console_messages.size(), 2u);
     EXPECT_TRUE(first_console_messages.front().Contains(
         "is not allowed in the current context due to PermissionsPolicy"));
     first_console_messages.clear();
@@ -1111,6 +1116,31 @@
   checker.CheckClickingEnabled(/*enabled=*/true);
 }
 
+TEST_F(HTMLPermissionElementSimTest, InvalidDisplayStyleElement) {
+  auto* permission_element = CreatePermissionElement(GetDocument(), "camera");
+  DeferredChecker checker(permission_element);
+  permission_element->setAttribute(
+      html_names::kStyleAttr,
+      AtomicString("display: block; position: absolute;"));
+  GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest);
+  checker.CheckClickingEnabled(/*enabled=*/false);
+  checker.CheckClickingEnabledAfterDelay(kDefaultTimeout,
+                                         /*expected_enabled=*/false);
+  EXPECT_TRUE(To<HTMLPermissionElement>(
+                  GetDocument().QuerySelector(AtomicString("permission")))
+                  ->matches(AtomicString(":invalid-style")));
+
+  permission_element->setAttribute(html_names::kStyleAttr,
+                                   AtomicString("display: inline-block;"));
+  GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest);
+  checker.CheckClickingEnabled(/*enabled=*/false);
+  checker.CheckClickingEnabledAfterDelay(kDefaultTimeout,
+                                         /*expected_enabled=*/true);
+  EXPECT_FALSE(To<HTMLPermissionElement>(
+                   GetDocument().QuerySelector(AtomicString("permission")))
+                   ->matches(AtomicString(":invalid-style")));
+}
+
 TEST_F(HTMLPermissionElementSimTest, BadContrastDisablesElement) {
   auto* permission_element = CreatePermissionElement(GetDocument(), "camera");
   DeferredChecker checker(permission_element);
@@ -1214,6 +1244,36 @@
   }
 }
 
+TEST_F(HTMLPermissionElementSimTest, RegisterAfterBeingVisible) {
+  SimRequest main_resource("https://example.test/", "text/html");
+  LoadURL("https://example.test/");
+  main_resource.Complete(R"HTML(
+  <body>
+    <permission
+      style='display:block; visibility:hidden'
+      id='camera'></permission>
+  </body>
+  )HTML");
+
+  Compositor().BeginFrame();
+  auto* permission_element = To<HTMLPermissionElement>(
+      GetDocument().QuerySelector(AtomicString("permission")));
+  GetDocument().View()->UpdateAllLifecyclePhasesForTest();
+  EXPECT_FALSE(permission_element->is_registered_in_browser_process());
+  permission_element->setAttribute(html_names::kTypeAttr,
+                                   AtomicString("camera"));
+  GetDocument().View()->UpdateAllLifecyclePhasesForTest();
+  EXPECT_FALSE(permission_element->is_registered_in_browser_process());
+  permission_element->setAttribute(html_names::kStyleAttr,
+                                   AtomicString("visibility:visible;"));
+  GetDocument().View()->UpdateAllLifecyclePhasesForTest();
+  RegistrationWaiter(permission_element).Wait();
+  permission_element->setAttribute(html_names::kStyleAttr,
+                                   AtomicString("display: none"));
+  GetDocument().View()->UpdateAllLifecyclePhasesForTest();
+  EXPECT_FALSE(permission_element->is_registered_in_browser_process());
+}
+
 class HTMLPermissionElementDispatchValidationEventTest
     : public HTMLPermissionElementSimTest {
  public:
@@ -1234,6 +1294,7 @@
         /*should_defer*/ true);
     document.body()->AppendChild(permission_element);
     document.UpdateStyleAndLayout(DocumentUpdateReason::kTest);
+    GetDocument().View()->UpdateAllLifecyclePhasesForTest();
     DeferredChecker checker(permission_element, &MainFrame());
     checker.CheckConsoleMessageAtIndex(0u, kValidationStatusChangeEvent);
     EXPECT_FALSE(permission_element->isValid());
@@ -1245,6 +1306,8 @@
     RegistrationWaiter(permission_element).Wait();
     permission_service()->set_should_defer_registered_callback(
         /*should_defer*/ false);
+    checker.CheckConsoleMessageAtIndex(1u, kValidationStatusChangeEvent);
+    ConsoleMessages().clear();
     return permission_element;
   }
 
@@ -1255,9 +1318,8 @@
 // Test receiving event after registration
 TEST_F(HTMLPermissionElementDispatchValidationEventTest, Registration) {
   auto* permission_element = CreateElementAndWaitForRegistration();
-  DeferredChecker checker(permission_element, &MainFrame());
-  checker.CheckConsoleMessageAtIndex(1u, kValidationStatusChangeEvent);
-  EXPECT_TRUE(permission_element->isValid());
+  EXPECT_TRUE(
+      base::test::RunUntil([&]() { return permission_element->isValid(); }));
 }
 
 // Test receiving event after several times disabling (temporarily or
@@ -1284,10 +1346,9 @@
   for (const auto& data : kTestData) {
     auto* permission_element = CreateElementAndWaitForRegistration();
     DeferredChecker checker(permission_element, &MainFrame());
-    checker.CheckConsoleMessageAtIndex(1u, kValidationStatusChangeEvent);
     EXPECT_TRUE(permission_element->isValid());
     permission_element->DisableClickingIndefinitely(data.reason);
-    checker.CheckConsoleMessageAtIndex(2u, kValidationStatusChangeEvent);
+    checker.CheckConsoleMessageAtIndex(0u, kValidationStatusChangeEvent);
     EXPECT_FALSE(permission_element->isValid());
     EXPECT_EQ(permission_element->invalidReason(),
               data.expected_invalid_reason);
@@ -1305,7 +1366,7 @@
     EXPECT_FALSE(permission_element->isValid());
     EXPECT_EQ(permission_element->invalidReason(),
               data.expected_invalid_reason);
-    checker.CheckConsoleMessageAtIndex(3u, kValidationStatusChangeEvent);
+    checker.CheckConsoleMessageAtIndex(1u, kValidationStatusChangeEvent);
     EXPECT_TRUE(permission_element->isValid());
     // Calling |EnableClickingAfterDelay| for a reason that is currently *not*
     // disabling clicking does not do anything.
@@ -1313,11 +1374,11 @@
     checker.CheckNoNewMessagesAfterDelay(kSmallTimeout);
 
     permission_element->DisableClickingTemporarily(data.reason, kSmallTimeout);
-    checker.CheckConsoleMessageAtIndex(4u, kValidationStatusChangeEvent);
+    checker.CheckConsoleMessageAtIndex(2u, kValidationStatusChangeEvent);
     EXPECT_FALSE(permission_element->isValid());
     EXPECT_EQ(permission_element->invalidReason(),
               data.expected_invalid_reason);
-    checker.CheckConsoleMessageAtIndex(5u, kValidationStatusChangeEvent);
+    checker.CheckConsoleMessageAtIndex(3u, kValidationStatusChangeEvent);
     EXPECT_TRUE(permission_element->isValid());
 
     GetDocument().body()->RemoveChild(permission_element);
@@ -1332,19 +1393,18 @@
        ChangeReasonRestartTimer) {
   auto* permission_element = CreateElementAndWaitForRegistration();
   DeferredChecker checker(permission_element, &MainFrame());
-  checker.CheckConsoleMessageAtIndex(1u, kValidationStatusChangeEvent);
   EXPECT_TRUE(permission_element->isValid());
   permission_element->DisableClickingTemporarily(
       HTMLPermissionElement::DisableReason::kRecentlyAttachedToLayoutTree,
       kSmallTimeout);
-  checker.CheckConsoleMessageAtIndex(2u, kValidationStatusChangeEvent);
+  checker.CheckConsoleMessageAtIndex(0u, kValidationStatusChangeEvent);
   EXPECT_FALSE(permission_element->isValid());
   EXPECT_EQ(permission_element->invalidReason(), "recently_attached");
   permission_element->DisableClickingTemporarily(
       HTMLPermissionElement::DisableReason::kInvalidStyle, kDefaultTimeout);
   // Reason change to the "longest alive" reason, in this case is
   // `kInvalidStyle`
-  checker.CheckConsoleMessageAtIndex(3u, kValidationStatusChangeEvent);
+  checker.CheckConsoleMessageAtIndex(1u, kValidationStatusChangeEvent);
   EXPECT_FALSE(permission_element->isValid());
   EXPECT_EQ(permission_element->invalidReason(), "style_invalid");
   permission_element->DisableClickingTemporarily(
@@ -1354,10 +1414,10 @@
   EXPECT_EQ(permission_element->invalidReason(), "style_invalid");
   permission_element->EnableClickingAfterDelay(
       HTMLPermissionElement::DisableReason::kInvalidStyle, kSmallTimeout);
-  checker.CheckConsoleMessageAtIndex(4u, kValidationStatusChangeEvent);
+  checker.CheckConsoleMessageAtIndex(2u, kValidationStatusChangeEvent);
   EXPECT_FALSE(permission_element->isValid());
   EXPECT_EQ(permission_element->invalidReason(), "recently_attached");
-  checker.CheckConsoleMessageAtIndex(5u, kValidationStatusChangeEvent);
+  checker.CheckConsoleMessageAtIndex(3u, kValidationStatusChangeEvent);
   EXPECT_TRUE(permission_element->isValid());
 }
 
@@ -1367,13 +1427,12 @@
        DisableEnableClickingDifferentReasons) {
   auto* permission_element = CreateElementAndWaitForRegistration();
   DeferredChecker checker(permission_element, &MainFrame());
-  checker.CheckConsoleMessageAtIndex(1u, kValidationStatusChangeEvent);
   EXPECT_TRUE(permission_element->isValid());
   permission_element->DisableClickingTemporarily(
       HTMLPermissionElement::DisableReason::
           kIntersectionVisibilityOutOfViewPortOrClipped,
       kDefaultTimeout);
-  checker.CheckConsoleMessageAtIndex(2u, kValidationStatusChangeEvent);
+  checker.CheckConsoleMessageAtIndex(0u, kValidationStatusChangeEvent);
   EXPECT_FALSE(permission_element->isValid());
   EXPECT_EQ(permission_element->invalidReason(),
             "intersection_out_of_viewport_or_clipped");
@@ -1383,7 +1442,7 @@
       HTMLPermissionElement::DisableReason::kInvalidStyle);
   // `invalidReason` change from temporary `intersection` to indefinitely
   // `style`
-  checker.CheckConsoleMessageAtIndex(3u, kValidationStatusChangeEvent);
+  checker.CheckConsoleMessageAtIndex(1u, kValidationStatusChangeEvent);
   EXPECT_FALSE(permission_element->isValid());
   EXPECT_EQ(permission_element->invalidReason(), "style_invalid");
   checker.CheckNoNewMessagesAfterDelay(kDefaultTimeout);
@@ -1399,11 +1458,11 @@
   permission_element->EnableClicking(
       HTMLPermissionElement::DisableReason::kInvalidStyle);
   // `invalidReason` change from `style` to temporary `intersection`
-  checker.CheckConsoleMessageAtIndex(4u, kValidationStatusChangeEvent);
+  checker.CheckConsoleMessageAtIndex(2u, kValidationStatusChangeEvent);
   EXPECT_FALSE(permission_element->isValid());
   EXPECT_EQ(permission_element->invalidReason(),
             "intersection_out_of_viewport_or_clipped");
-  checker.CheckConsoleMessageAtIndex(5u, kValidationStatusChangeEvent);
+  checker.CheckConsoleMessageAtIndex(3u, kValidationStatusChangeEvent);
   EXPECT_TRUE(permission_element->isValid());
 }
 
@@ -1489,7 +1548,6 @@
         static_cast<frame_test_helpers::TestWebFrameClient*>(
             first_child_frame->Client())
             ->ConsoleMessages();
-    EXPECT_EQ(first_console_messages.size(), 2u);
     EXPECT_TRUE(first_console_messages.front().Contains(
         "is not allowed without the CSP 'frame-ancestors' directive present."));
     first_console_messages.clear();
diff --git a/third_party/blink/renderer/core/inspector/inspector_trace_events.cc b/third_party/blink/renderer/core/inspector/inspector_trace_events.cc
index d33f420..bc94e222 100644
--- a/third_party/blink/renderer/core/inspector/inspector_trace_events.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_trace_events.cc
@@ -1228,6 +1228,10 @@
                                           v8::Isolate* isolate,
                                           ScriptState* scriptState,
                                           int scriptId) {
+  if (scriptId == v8::UnboundScript::kNoScriptId) {
+    return;
+  }
+
   // Target related info
   LocalDOMWindow* window = DynamicTo<LocalDOMWindow>(execution_context);
   LocalFrame* frame = window ? window->GetFrame() : nullptr;
diff --git a/third_party/blink/renderer/core/layout/out_of_flow_layout_part.cc b/third_party/blink/renderer/core/layout/out_of_flow_layout_part.cc
index d90a4b4..c7ffe88a 100644
--- a/third_party/blink/renderer/core/layout/out_of_flow_layout_part.cc
+++ b/third_party/blink/renderer/core/layout/out_of_flow_layout_part.cc
@@ -422,12 +422,7 @@
     const OutOfFlowLayoutPart::OffsetInfo& offset_info,
     const BlockNode& node,
     const PhysicalAnchorQuery* anchor_query) {
-  if (!anchor_query) {
-    return;
-  }
-
   // TODO(crbug.com/332933527): Support anchors-valid.
-
   PaintLayer* layer = node.GetLayoutBox()->Layer();
   CHECK(layer);
   bool has_no_overflow_visibility =
@@ -436,6 +431,10 @@
       LayerPositionVisibility::kNoOverflow,
       has_no_overflow_visibility && offset_info.overflows_containing_block);
 
+  if (!anchor_query) {
+    return;
+  }
+
   // TODO(wangxianzhu): We may be anchored in cases where we do not need scroll
   // adjustment, such as when the anchor and anchored have the same containing
   // block. For now though, these flags are true in this case.
diff --git a/third_party/blink/renderer/core/style/content_data.h b/third_party/blink/renderer/core/style/content_data.h
index 0fb10a90..d602d14e 100644
--- a/third_party/blink/renderer/core/style/content_data.h
+++ b/third_party/blink/renderer/core/style/content_data.h
@@ -45,6 +45,7 @@
   virtual ~ContentData() = default;
 
   virtual bool IsCounter() const { return false; }
+  virtual bool IsAltCounter() const { return false; }
   virtual bool IsImage() const { return false; }
   virtual bool IsQuote() const { return false; }
   virtual bool IsText() const { return false; }
@@ -256,7 +257,9 @@
   Member<const TreeScope> tree_scope;
 };
 
-class CounterContentData final : public ContentData {
+class CounterContentData : public ContentData {
+  friend class ContentData;
+
  public:
   CounterContentData(const AtomicString& identifier,
                      const AtomicString& style,
@@ -284,6 +287,7 @@
     return MakeGarbageCollected<CounterContentData>(counter_data_);
   }
 
+ protected:
   bool Equals(const ContentData& data) const override {
     if (!data.IsCounter()) {
       return false;
@@ -306,6 +310,44 @@
   }
 };
 
+class AltCounterContentData : public CounterContentData {
+  using CounterContentData::CounterContentData;
+
+ public:
+  bool IsAltCounter() const override { return true; }
+
+  const String& GetText() const { return counter_value_text_; }
+  void SetText(const String& text) { counter_value_text_ = text; }
+
+  String DebugString() const override { return "<alt-counter>"; }
+
+ private:
+  ContentData* CloneInternal() const override {
+    auto* data = MakeGarbageCollected<AltCounterContentData>(counter_data_);
+    data->SetText(GetText());
+    return data;
+  }
+
+  bool Equals(const ContentData& data) const override {
+    if (!data.IsAltCounter()) {
+      return false;
+    }
+    const AltCounterContentData& other =
+        static_cast<const AltCounterContentData&>(data);
+    return CounterContentData::Equals(other) && GetText() == other.GetText();
+  }
+
+  // Text value of counter() or counters() to be used in ax object.
+  String counter_value_text_;
+};
+
+template <>
+struct DowncastTraits<AltCounterContentData> {
+  static bool AllowFrom(const ContentData& content) {
+    return content.IsAltCounter();
+  }
+};
+
 class QuoteContentData final : public ContentData {
  public:
   explicit QuoteContentData(QuoteType quote) : quote_(quote) {}
diff --git a/third_party/blink/renderer/platform/instrumentation/OWNERS b/third_party/blink/renderer/platform/instrumentation/OWNERS
index fc77600..2a211d3 100644
--- a/third_party/blink/renderer/platform/instrumentation/OWNERS
+++ b/third_party/blink/renderer/platform/instrumentation/OWNERS
@@ -1,2 +1 @@
 eseckler@chromium.org
-skyostil@chromium.org
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index 89e57b14..7fcb3aa 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -1123,6 +1123,11 @@
       status: "stable",
     },
     {
+      // Allows using counter() and counters() in alt text (after / in content property).
+      name: "CSSAltCounter",
+      status: "experimental",
+    },
+    {
       // Remember the scroll offset of the default anchor when initially
       // displaying an anchor-positioned element, and when it switches position
       // options (`position-try-fallbacks`), also known as an anchor
diff --git a/third_party/blink/renderer/platform/wtf/vector.h b/third_party/blink/renderer/platform/wtf/vector.h
index 8255a2340..1cf9b5e 100644
--- a/third_party/blink/renderer/platform/wtf/vector.h
+++ b/third_party/blink/renderer/platform/wtf/vector.h
@@ -2504,7 +2504,17 @@
     }
 
     // Inline buffer requires tracing immediately.
-    internal::TraceInlinedBuffer<Allocator>(visitor, buffer, InlineCapacity);
+    if (visitor->IsConcurrent()) {
+      // For the concurrent marker we're guaranteed to have an on-heap object
+      // (which means that the unused slots are zeroed), since we don't follow
+      // heap->stack references.
+      internal::TraceInlinedBuffer<Allocator>(visitor, buffer, InlineCapacity);
+    } else {
+      // Trace until size, because inlined storages for on-stack collections are
+      // not zeroed out. This path covers both main-thread marking and the write
+      // barrier.
+      internal::TraceInlinedBuffer<Allocator>(visitor, buffer, size());
+    }
   }
 }
 
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 6319105..51e40dc 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
@@ -75562,6 +75562,19 @@
        {}
       ]
      ],
+     "position-visibility-no-overflow-without-anchor.html": [
+      "01f4d97947bc68ca09d6691f1a43fb98a57ea98d",
+      [
+       null,
+       [
+        [
+         "/css/css-anchor-position/position-visibility-no-overflow-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
      "position-visibility-no-overflow.html": [
       "0569a9d179c02501eb747d66c830da96da14ae3b",
       [
@@ -201684,6 +201697,19 @@
         ],
         {}
        ]
+      ],
+      "empty-span-001.html": [
+       "4709f34abd543249a19b730a88f02249f4ad46d4",
+       [
+        null,
+        [
+         [
+          "/css/css-text/bidi/empty-span-001-ref.html",
+          "=="
+         ]
+        ],
+        {}
+       ]
       ]
      },
      "boundary-shaping": {
@@ -282627,6 +282653,19 @@
         {}
        ]
       ],
+      "has-with-nth-child-sibling-remove.html": [
+       "2c3e83f2bd9c83f02bfaa7ee12a217f2236aafbc",
+       [
+        null,
+        [
+         [
+          "/css/reference/ref-filled-green-100px-square.xht",
+          "=="
+         ]
+        ],
+        {}
+       ]
+      ],
       "lang-pseudo-class-in-has-document-element.html": [
        "93ece465dd51c31f67b0f1947747a4a9434fbd28",
        [
@@ -364416,6 +364455,10 @@
       ]
      },
      "bidi": {
+      "empty-span-001-ref.html": [
+       "ce987d65bf4820f67bb72095faca47bf58907f91",
+       []
+      ],
       "reference": {
        "bidi-lines-001-ref.html": [
         "ab13fef116c4eba70a09e01558425bb508173d90",
@@ -412888,7 +412931,7 @@
     }
    },
    "lint.ignore": [
-    "955e5df49077cab63e762988714e1319d551089d",
+    "b39180815c2c6138dedcec0e40ee392d657cdb60",
     []
    ],
    "loading": {
@@ -477290,6 +477333,15 @@
       }
      ]
     ],
+    "async-navigator-clipboard-change-event.tentative.https.html": [
+     "37e2e6a7bde3ef85de09691e5fa4fba4a644b310",
+     [
+      null,
+      {
+       "testdriver": true
+      }
+     ]
+    ],
     "async-navigator-clipboard-read-resource-load.https.html": [
      "aed115231c4c9f5ba504ae62455e23a0341f6436",
      [
@@ -516724,7 +516776,7 @@
       ]
      },
      "resnap-on-snap-alignment-change.html": [
-      "e4648b1d79813548c763bf08e002b485ff291f56",
+      "760d9afbe2e913ebfef2e4d6d24c59d57ef4e834",
       [
        null,
        {}
@@ -517121,7 +517173,7 @@
       ]
      ],
      "smooth-anchor-scroll-in-snap-container.html": [
-      "c01420b2ee4a00aa2ec143a68b30fb3c0ddb465b",
+      "4892ae3efb5f4f841e83bf2d44e9d848bca002ab",
       [
        null,
        {}
@@ -538944,7 +538996,7 @@
      ]
     ],
     "get.tentative.https.html": [
-     "b04a6e3a713e26f759cc62d81a6a439ea69e189e",
+     "8c0c33c2fa195ac25ef026645f8d3ca95f227c4e",
      [
       null,
       {
@@ -713089,6 +713141,15 @@
          "testdriver": true
         }
        ]
+      ],
+      "task-attribution.html": [
+       "8f6f93c8daf6c089efd0eee7fefdc1a8ffa704f1",
+       [
+        null,
+        {
+         "testdriver": true
+        }
+       ]
       ]
      }
     },
diff --git a/third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-no-overflow-without-anchor.html b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-no-overflow-without-anchor.html
new file mode 100644
index 0000000..01f4d97
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/position-visibility-no-overflow-without-anchor.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Anchor Positioning Test: position-visibility: no-overflow without an anchor</title>
+<link rel="help" href="https://drafts.csswg.org/css-anchor-position-1/#position-visibility">
+<link rel="match" href="position-visibility-no-overflow-ref.html">
+<style>
+  #scroll-container {
+    position: relative;
+    overflow: hidden scroll;
+    width: 400px;
+    height: 100px;
+  }
+
+  .anchor {
+    width: 100px;
+    height: 100px;
+    background: orange;
+    display: inline-block;
+  }
+
+  .target {
+    position: absolute;
+    position-visibility: no-overflow;
+    width: 100px;
+    height: 200px;
+    background: red;
+    top: 0;
+    left: 0;
+  }
+</style>
+
+<div id="scroll-container">
+  <div class="anchor">anchor1</div>
+  <!-- #target1 should not be visible because it overflows the containing block. -->
+  <div id="target1" class="target">target1</div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-content/parsing/content-counter-valid.html b/third_party/blink/web_tests/external/wpt/css/css-content/parsing/content-counter-valid.html
new file mode 100644
index 0000000..6de60c9f
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-content/parsing/content-counter-valid.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Content Test: counter() in alt text parsing</title>
+<link rel="help" href="https://drafts.csswg.org/css-content-3/#content-property">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/parsing-testcommon.js"></script>
+<div id="target"></div>
+<script>
+  test_valid_value("content", `"" / counter(cnt)`);
+  test_valid_value("content", `"regular text" / "alt text 1" counter(cnt) "alt text 2"`);
+  test_valid_value("content", `"regular text" / counter(cnt) "alt text"`);
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-scroll-snap/resnap-on-snap-alignment-change.html b/third_party/blink/web_tests/external/wpt/css/css-scroll-snap/resnap-on-snap-alignment-change.html
index e4648b1d..760d9afb 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-scroll-snap/resnap-on-snap-alignment-change.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-scroll-snap/resnap-on-snap-alignment-change.html
@@ -1,5 +1,6 @@
 <!DOCTYPE html>
 <html>
+<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1">
 <title>
   Resnap when the current snap position is no longer a valid snap target.
 </title>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-scroll-snap/smooth-anchor-scroll-in-snap-container.html b/third_party/blink/web_tests/external/wpt/css/css-scroll-snap/smooth-anchor-scroll-in-snap-container.html
index c01420b2..4892ae3 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-scroll-snap/smooth-anchor-scroll-in-snap-container.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-scroll-snap/smooth-anchor-scroll-in-snap-container.html
@@ -1,6 +1,7 @@
 <!DOCTYPE html>
 <html>
   <head>
+    <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1">
     <title>Smooth anchor scroll in snap container works</title>
     <link rel="help" href="https://drafts.csswg.org/css-scroll-snap/"/>
     <script src="/resources/testharness.js"></script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/bidi/empty-span-001-ref.html b/third_party/blink/web_tests/external/wpt/css/css-text/bidi/empty-span-001-ref.html
new file mode 100644
index 0000000..ce987d6
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/bidi/empty-span-001-ref.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Text: bidi reordering reference</title>
+
+<style>
+span { unicode-bidi: bidi-override; }
+</style>
+
+<p>All the following lines should match:</p>
+
+<span>;א56;234;1</span><br>
+<span>;א56;234;1</span><br>
+<span>;א56;234;1</span><br>
+<span>;א56;234;1</span><br>
+<span>;א56;234;1</span><br>
+<span>;א56;234;1</span><br>
+<span>;א56;234;1</span><br>
+<span>;א56;234;1</span><br>
+<span>;א56;234;1</span><br>
+<span>;א56;234;1</span><br>
+<span>;א56;234;1</span><br>
+<span>;א56;234;1</span><br>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/bidi/empty-span-001.html b/third_party/blink/web_tests/external/wpt/css/css-text/bidi/empty-span-001.html
new file mode 100644
index 0000000..4709f34
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/bidi/empty-span-001.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Text: bidi reordering testcase</title>
+
+<link rel="author" href="mailto:jkew@mozilla.com">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1966313">
+<link rel="match" href="empty-span-001-ref.html">
+<meta name="assert" content="empty spans should not disrupt bidi reordering">
+
+<p>All the following lines should match:</p>
+
+<span dir="auto">1;234;56א;</span><br>
+<span dir="auto"><span></span>1;234;56א;</span><br>
+<span dir="auto">1<span></span>;234;56א;</span><br>
+<span dir="auto">1;<span></span>234;56א;</span><br>
+<span dir="auto">1;2<span></span>34;56א;</span><br>
+<span dir="auto">1;23<span></span>4;56א;</span><br>
+<span dir="auto">1;234<span></span>;56א;</span><br>
+<span dir="auto">1;234;<span></span>56א;</span><br>
+<span dir="auto">1;234;5<span></span>6א;</span><br>
+<span dir="auto">1;234;56<span></span>א;</span><br>
+<span dir="auto">1;234;56א<span></span>;</span><br>
+<span dir="auto">1;234;56א;<span></span></span><br>
diff --git a/third_party/blink/web_tests/external/wpt/css/selectors/invalidation/has-with-nth-child-sibling-remove.html b/third_party/blink/web_tests/external/wpt/css/selectors/invalidation/has-with-nth-child-sibling-remove.html
new file mode 100644
index 0000000..2c3e83f
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/selectors/invalidation/has-with-nth-child-sibling-remove.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<meta charset="utf-8">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1964745">
+<link rel="author" title="David Shin" href="mailto:dshin@mozilla.com">
+<link rel="match" href="../../reference/ref-filled-green-100px-square.xht">
+<link rel="help" href="https://drafts.csswg.org/selectors-4/#relational">
+<style>
+.square {
+  width: 100px;
+  height: 100px;
+  background: red;
+}
+
+.item:not(:has(~ .item > :nth-child(2))) {
+  background: green;
+}
+</style>
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div id=container>
+  <div class="item square">
+    <div></div>
+    <div></div>
+  </div>
+  <div id=td class=item>
+    <div></div>
+    <div></div>
+  </div>
+</div>
+<script>
+window.onload = () => {
+  td.remove();
+};
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/digital-credentials/get.tentative.https.html b/third_party/blink/web_tests/external/wpt/digital-credentials/get.tentative.https.html
index b04a6e3..8c0c33c 100644
--- a/third_party/blink/web_tests/external/wpt/digital-credentials/get.tentative.https.html
+++ b/third_party/blink/web_tests/external/wpt/digital-credentials/get.tentative.https.html
@@ -29,6 +29,27 @@
   });
 
   promise_test(async (t) => {
+    const invalidData = [null, undefined, "", 123, true, false];
+    for (const data of invalidData) {
+      const options = {
+        digital: {
+          requests: [
+            {
+              data,
+              protocol: "openid4vp",
+            },
+          ],
+        },
+      };
+      await promise_rejects_js(
+        t,
+        TypeError,
+        navigator.credentials.get(options)
+      );
+    }
+  }, "Type conversion happens on the data member of the DigitalCredentialGetRequest object.");
+
+  promise_test(async (t) => {
     iframeSameOrigin.focus();
     for (const global of [window, iframeSameOrigin.contentWindow]) {
       await promise_rejects_dom(
@@ -89,7 +110,11 @@
     for (const request of [undefined, []]) {
       const options = makeGetOptions(request);
       await test_driver.bless("user activation");
-      await promise_rejects_js(t, TypeError, navigator.credentials.get(options));
+      await promise_rejects_js(
+        t,
+        TypeError,
+        navigator.credentials.get(options)
+      );
     }
   }, "navigator.credentials.get() API rejects if there are no credential request.");
 
@@ -196,10 +221,14 @@
 
   promise_test(async (t) => {
     /** @type sequence<CredentialMediationRequirement> */
-    const disallowedMediations = [ "conditional", "optional", "silent"];
+    const disallowedMediations = ["conditional", "optional", "silent"];
     for (const mediation of disallowedMediations) {
       const options = makeGetOptions("default", mediation);
-      await promise_rejects_js(t, TypeError, navigator.credentials.get(options));
+      await promise_rejects_js(
+        t,
+        TypeError,
+        navigator.credentials.get(options)
+      );
     }
   }, "Mediation is required to get a DigitalCredential.");
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/permission-element/display-css-property-reftest-ref.html b/third_party/blink/web_tests/external/wpt/html/semantics/permission-element/display-css-property-reftest-ref.html
deleted file mode 100644
index aa5ffe0..0000000
--- a/third_party/blink/web_tests/external/wpt/html/semantics/permission-element/display-css-property-reftest-ref.html
+++ /dev/null
@@ -1,16 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<body>
-  <div>
-    The permission element should either be display 'none' or 'inline-block'
-  </div>
-
-<style>
-  #id1 {
-    display: inline-block;
-  }
-</style>
-
-<permission id="id1" type="geolocation"></permission>
-<span>After element</span>
-</body>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/permission-element/display-css-property-reftest.tentative.html b/third_party/blink/web_tests/external/wpt/html/semantics/permission-element/display-css-property-reftest.tentative.html
deleted file mode 100644
index e4288ca..0000000
--- a/third_party/blink/web_tests/external/wpt/html/semantics/permission-element/display-css-property-reftest.tentative.html
+++ /dev/null
@@ -1,22 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<link rel="match" href="display-css-property-reftest-ref.html">
-<link rel="help" href="https://github.com/WICG/PEPC/blob/main/explainer.md#locking-the-pepc-style">
-<body>
-  <div>
-    The permission element should either be display 'none' or 'inline-block'
-  </div>
-
-<style>
-  #id1 {
-    display: flex;
-  }
-  #id2 {
-    display: none;
-  }
-</style>
-
-<permission id="id1" type="geolocation"></permission>
-<permission id="id2" type="camera"></permission>
-<span>After element</span>
-</body>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/permission-element/display-css-property.tentative.html b/third_party/blink/web_tests/external/wpt/html/semantics/permission-element/display-css-property.tentative.html
deleted file mode 100644
index b87b6aa..0000000
--- a/third_party/blink/web_tests/external/wpt/html/semantics/permission-element/display-css-property.tentative.html
+++ /dev/null
@@ -1,34 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<link rel="help" href="https://github.com/WICG/PEPC/blob/main/explainer.md#locking-the-pepc-style">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<body>
-<!--
-  'display' should either be 'none' or 'inline-block'
--->
-<style>
-  #id1 {
-    display: inline-block;
-  }
-  #id2 {
-    display: block;
-  }
-  #id3 {
-    display: none;
-  }
-</style>
-
-
-<permission id="id1" type="geolocation"></permission>
-<permission id="id2" type="camera"></permission>
-<permission id="id3" type="microphone"></permission>
-
-<script>
-  test(function(){
-    assert_equals(getComputedStyle(document.getElementById("id1")).display, "inline-block", "'inline-block' should be kept");
-    assert_equals(getComputedStyle(document.getElementById("id2")).display, "inline-block", "'block' should be changed to 'inline-block'");
-    assert_equals(getComputedStyle(document.getElementById("id3")).display, "none", "'none' should be kept");
-  }, "'display' should be either 'inline-block' or 'none'");
-</script>
-</body>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/prerender/prefetch-events-for-ahead-of-prerender-expected.txt b/third_party/blink/web_tests/http/tests/inspector-protocol/prerender/prefetch-events-for-ahead-of-prerender-expected.txt
index f56ad289..0e2b8009 100644
--- a/third_party/blink/web_tests/http/tests/inspector-protocol/prerender/prefetch-events-for-ahead-of-prerender-expected.txt
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/prerender/prefetch-events-for-ahead-of-prerender-expected.txt
@@ -3,34 +3,6 @@
 Preload.prefetchStatusUpadted should be emitted for prefetch ahead of prerender
 [
     [0] : {
-        method : Preload.prerenderStatusUpdated
-        params : {
-            key : {
-                action : Prerender
-                loaderId : <string>
-                url : http://127.0.0.1:8000/inspector-protocol/prerender/resources/empty.html
-            }
-            pipelineId : <string>
-            pipelineIdIndex : 0
-            status : Pending
-        }
-        sessionId : <string>
-    }
-    [1] : {
-        method : Preload.prerenderStatusUpdated
-        params : {
-            key : {
-                action : Prerender
-                loaderId : <string>
-                url : http://127.0.0.1:8000/inspector-protocol/prerender/resources/empty.html
-            }
-            pipelineId : <string>
-            pipelineIdIndex : 0
-            status : Running
-        }
-        sessionId : <string>
-    }
-    [2] : {
         method : Preload.prefetchStatusUpdated
         params : {
             initiatingFrameId : <string>
@@ -48,6 +20,34 @@
         }
         sessionId : <string>
     }
+    [1] : {
+        method : Preload.prerenderStatusUpdated
+        params : {
+            key : {
+                action : Prerender
+                loaderId : <string>
+                url : http://127.0.0.1:8000/inspector-protocol/prerender/resources/empty.html
+            }
+            pipelineId : <string>
+            pipelineIdIndex : 0
+            status : Pending
+        }
+        sessionId : <string>
+    }
+    [2] : {
+        method : Preload.prerenderStatusUpdated
+        params : {
+            key : {
+                action : Prerender
+                loaderId : <string>
+                url : http://127.0.0.1:8000/inspector-protocol/prerender/resources/empty.html
+            }
+            pipelineId : <string>
+            pipelineIdIndex : 0
+            status : Running
+        }
+        sessionId : <string>
+    }
     [3] : {
         method : Preload.prefetchStatusUpdated
         params : {
diff --git a/third_party/blink/web_tests/inspector-protocol/debugger/debugger-evaluate-shows-private-accessors-expected.txt b/third_party/blink/web_tests/inspector-protocol/debugger/debugger-evaluate-shows-private-accessors-expected.txt
new file mode 100644
index 0000000..a587a78d
--- /dev/null
+++ b/third_party/blink/web_tests/inspector-protocol/debugger/debugger-evaluate-shows-private-accessors-expected.txt
@@ -0,0 +1,32 @@
+Tests that the inspector can inspect private properties
+{
+    result : {
+        className : A
+        description : A
+        objectId : <string>
+        type : object
+    }
+}
+[
+    [0] : {
+        get : {
+            className : Function
+            description : get #bar() { return 42; }
+            objectId : <string>
+            type : function
+        }
+        name : #bar
+    }
+]
+{
+    id : <number>
+    result : {
+        result : {
+            description : 42
+            type : number
+            value : 42
+        }
+    }
+    sessionId : <string>
+}
+
diff --git a/third_party/blink/web_tests/inspector-protocol/debugger/debugger-evaluate-shows-private-accessors.js b/third_party/blink/web_tests/inspector-protocol/debugger/debugger-evaluate-shows-private-accessors.js
new file mode 100644
index 0000000..c4ff5a3
--- /dev/null
+++ b/third_party/blink/web_tests/inspector-protocol/debugger/debugger-evaluate-shows-private-accessors.js
@@ -0,0 +1,35 @@
+(async function(/** @type {import('test_runner').TestRunner} */ testRunner) {
+  const {dp} = await testRunner.startBlank(
+      'Tests that the inspector can inspect private properties');
+
+  await dp.Debugger.enable();
+
+  const {result} = await dp.Runtime.evaluate({
+    expression: `
+      (() => {
+        class A {
+          get #bar() { return 42; }
+        };
+        return new A();
+      })()`,
+  });
+
+  testRunner.log(result);
+  const objectId = result.result.objectId;
+
+  const {result: {privateProperties}} =
+      await dp.Runtime.getProperties({objectId});
+  testRunner.log(privateProperties);
+
+  const name = privateProperties[0].name;
+  const getter = privateProperties[0].get;
+
+  const call = await dp.Runtime.callFunctionOn({functionDeclaration: `${getProperty}`, objectId, arguments: [{objectId: getter.objectId}]});
+  testRunner.log(call);
+
+  testRunner.completeTest();
+
+  function getProperty(getter) {
+    return Reflect.apply(getter, this, []);
+  }
+});
diff --git a/third_party/catapult b/third_party/catapult
index ffd1c50..4110450 160000
--- a/third_party/catapult
+++ b/third_party/catapult
@@ -1 +1 @@
-Subproject commit ffd1c50cbd19e3f0c5de2fd1d20b43adc04b6e85
+Subproject commit 41104509db3116fd5a5e3af4f8242b59de7adc67
diff --git a/third_party/crashpad/README.chromium b/third_party/crashpad/README.chromium
index fbe2892..2fdb5a3 100644
--- a/third_party/crashpad/README.chromium
+++ b/third_party/crashpad/README.chromium
@@ -2,7 +2,7 @@
 Short Name: crashpad
 URL: https://crashpad.chromium.org/
 Version: N/A
-Revision: 19a1004d9aa974e09ad10a82c01c53bfca8ef434
+Revision: 8f131016b21d986c38ca4a0f091403dbb822d636
 License: Apache-2.0
 License File: crashpad/LICENSE
 Security Critical: yes
diff --git a/third_party/crashpad/crashpad/client/crash_handler_tvos.cc b/third_party/crashpad/crashpad/client/crash_handler_tvos.cc
index 3b92fab..ab42b44 100644
--- a/third_party/crashpad/crashpad/client/crash_handler_tvos.cc
+++ b/third_party/crashpad/crashpad/client/crash_handler_tvos.cc
@@ -57,7 +57,6 @@
   if (!Signals::InstallCrashHandlers(CatchAndReraiseSignal,
                                      /*flags=*/0,
                                      &old_actions_)) {
-    LOG(ERROR) << "Unable to install crash handlers";
     return false;
   }
 
diff --git a/third_party/crossbench b/third_party/crossbench
index 354a6230..8f9e09a 160000
--- a/third_party/crossbench
+++ b/third_party/crossbench
@@ -1 +1 @@
-Subproject commit 354a6230a3a7bc437dd3f512a6decd3155eb15fc
+Subproject commit 8f9e09ad773d1e3fbb3021d3ab09246db491df5c
diff --git a/third_party/devtools-frontend/src b/third_party/devtools-frontend/src
index 2755713..a606a77 160000
--- a/third_party/devtools-frontend/src
+++ b/third_party/devtools-frontend/src
@@ -1 +1 @@
-Subproject commit 2755713bf6b0ca46d6e60124a0633f45c2d54677
+Subproject commit a606a77310848ca4f22c139777106f9100f8e318
diff --git a/third_party/perfetto b/third_party/perfetto
index 109a2eb..99c9ef8 160000
--- a/third_party/perfetto
+++ b/third_party/perfetto
@@ -1 +1 @@
-Subproject commit 109a2ebe2b65215b8ba3ff0d6a3a8361220a0992
+Subproject commit 99c9ef8102d3770e6d5ba91d9ca400b751b0af9b
diff --git a/third_party/skia b/third_party/skia
index bb255dd..db706fd 160000
--- a/third_party/skia
+++ b/third_party/skia
@@ -1 +1 @@
-Subproject commit bb255dd0252e256a28542475b22711346c319030
+Subproject commit db706fd37874f786e82238b58a4c3799fa430f60
diff --git a/third_party/spirv-tools/src b/third_party/spirv-tools/src
index eac930c..66fe610 160000
--- a/third_party/spirv-tools/src
+++ b/third_party/spirv-tools/src
@@ -1 +1 @@
-Subproject commit eac930caa2bc7209ca71ecea7c15cbab33d5bc66
+Subproject commit 66fe610946a6d98169f8ebe9ca483f64c4009fa5
diff --git a/third_party/vulkan-deps b/third_party/vulkan-deps
index bac8bd2..8379edd 160000
--- a/third_party/vulkan-deps
+++ b/third_party/vulkan-deps
@@ -1 +1 @@
-Subproject commit bac8bd2de3c4470697edee557d7ac29fff036b3d
+Subproject commit 8379edd28781c7fc16f2e8d2094ba557c9c6cbf3
diff --git a/third_party/vulkan-validation-layers/src b/third_party/vulkan-validation-layers/src
index 40d64d8..4297492 160000
--- a/third_party/vulkan-validation-layers/src
+++ b/third_party/vulkan-validation-layers/src
@@ -1 +1 @@
-Subproject commit 40d64d8f544194b82f658dcecd85d03d120a51ef
+Subproject commit 42974927c2b34cfd29110e702fb493336e2fd711
diff --git a/tools/metrics/histograms/metadata/android/histograms.xml b/tools/metrics/histograms/metadata/android/histograms.xml
index c1104b5..8e9ba3fa 100644
--- a/tools/metrics/histograms/metadata/android/histograms.xml
+++ b/tools/metrics/histograms/metadata/android/histograms.xml
@@ -6194,6 +6194,38 @@
   </summary>
 </histogram>
 
+<histogram name="Android.WebView.CacheQuotaSize" units="KiB"
+    expires_after="2025-12-31">
+  <owner>abhijithnair@chromium.org</owner>
+  <owner>src/android_webview/OWNERS</owner>
+  <summary>
+    Records the result of `StorageManager.getCacheQuotaBytes()` after WebView
+    startup. The value denotes the amount of cache quota that was allocated to
+    the app by the framework.
+
+    This is logged only once per session in a delayed background task
+    approximately 5 seconds after WebView completes its main startup tasks. The
+    measurement may be expensive, so it is only performed if the
+    WebViewRecordAppCacheHistograms feature is enabled.
+  </summary>
+</histogram>
+
+<histogram name="Android.WebView.CacheSize" units="KiB"
+    expires_after="2025-12-31">
+  <owner>abhijithnair@chromium.org</owner>
+  <owner>src/android_webview/OWNERS</owner>
+  <summary>
+    Records the result of `StorageManager.getCacheSizeBytes()` after WebView
+    startup. The value denotes the amount of cache that the app is currently
+    using.
+
+    This is logged only once per session in a delayed background task
+    approximately 5 seconds after WebView completes its main startup tasks. The
+    measurement may be expensive, so it is only performed if the
+    WebViewRecordAppCacheHistograms feature is enabled.
+  </summary>
+</histogram>
+
 <histogram name="Android.WebView.Callback.Counts" enum="WebViewCallbackType"
     expires_after="2025-09-14">
   <owner>ntfschr@chromium.org</owner>
@@ -6719,6 +6751,32 @@
   </summary>
 </histogram>
 
+<histogram name="Android.WebView.GetCacheQuotaSizeTime" units="ms"
+    expires_after="2025-12-31">
+  <owner>abhijithnair@chromium.org</owner>
+  <owner>src/android_webview/OWNERS</owner>
+  <summary>
+    Records the time taken to measure Android.WebView.CacheQuotaSize using
+    `StorageManager.getCacheQuotaBytes()` in milliseconds.
+
+    This may be logged even if the measurement for
+    Android.WebView.CacheQuotaSize fails.
+  </summary>
+</histogram>
+
+<histogram name="Android.WebView.GetCacheSizeTime" units="ms"
+    expires_after="2025-12-31">
+  <owner>abhijithnair@chromium.org</owner>
+  <owner>src/android_webview/OWNERS</owner>
+  <summary>
+    Records the time taken to measure Android.WebView.CacheSize using
+    `StorageManager.getCacheSizeBytes()` in milliseconds.
+
+    This may be logged even if the measurement for Android.WebView.CacheSize
+    fails.
+  </summary>
+</histogram>
+
 <histogram name="Android.WebView.Gfx.HardwareDrawType"
     enum="WebViewDrawAndSubmissionType" expires_after="2025-09-14">
   <owner>vasilyt@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/blink/enums.xml b/tools/metrics/histograms/metadata/blink/enums.xml
index 689b8f3..5830f3f 100644
--- a/tools/metrics/histograms/metadata/blink/enums.xml
+++ b/tools/metrics/histograms/metadata/blink/enums.xml
@@ -7806,6 +7806,7 @@
   <int value="2" label="Low constrast between color and background color"/>
   <int value="3" label="Too small font size"/>
   <int value="4" label="Too large font size"/>
+  <int value="5" label="Invalid display property"/>
 </enum>
 
 <!-- LINT.ThenChange(//third_party/blink/renderer/core/html/html_permission_element.h:InvalidStyleReason) -->
diff --git a/tools/metrics/histograms/metadata/ios/enums.xml b/tools/metrics/histograms/metadata/ios/enums.xml
index f645f7228..27fffeac 100644
--- a/tools/metrics/histograms/metadata/ios/enums.xml
+++ b/tools/metrics/histograms/metadata/ios/enums.xml
@@ -289,6 +289,7 @@
   <int value="2" label="Password Saved (deprecated)"/>
   <int value="3" label="Autofill Used"/>
   <int value="4" label="Set Up List"/>
+  <int value="5" label="Tips Notification"/>
 </enum>
 
 <!-- LINT.IfChange(IOSDefaultBrowserBannerPromoPromoSessionEndedReason) -->
diff --git a/tools/metrics/histograms/metadata/media/enums.xml b/tools/metrics/histograms/metadata/media/enums.xml
index f00eff0a..af0766b8 100644
--- a/tools/metrics/histograms/metadata/media/enums.xml
+++ b/tools/metrics/histograms/metadata/media/enums.xml
@@ -1574,6 +1574,15 @@
   <int value="6" label="kUserSelectedThisTab"/>
 </enum>
 
+<enum name="MediaUiGetDisplayMediaTabSharingInfoBarInteraction">
+  <int value="0" label="CapturedToCapturing"/>
+  <int value="1" label="CapturingToCaptured"/>
+  <int value="2" label="OtherToCapturing"/>
+  <int value="3" label="OtherToCaptured"/>
+  <int value="4" label="StopButtonClicked"/>
+  <int value="5" label="ShareThisTabInsteadButtonClicked"/>
+</enum>
+
 <enum name="MicrophoneMuteResult">
   <int value="0" label="Muted"/>
   <int value="1" label="Not muted"/>
diff --git a/tools/metrics/histograms/metadata/media/histograms.xml b/tools/metrics/histograms/metadata/media/histograms.xml
index 208bcf1..509ea7e 100644
--- a/tools/metrics/histograms/metadata/media/histograms.xml
+++ b/tools/metrics/histograms/metadata/media/histograms.xml
@@ -5979,6 +5979,17 @@
   </summary>
 </histogram>
 
+<histogram name="Media.Ui.GetDisplayMedia.TabSharingInfoBarInteraction"
+    enum="MediaUiGetDisplayMediaTabSharingInfoBarInteraction"
+    expires_after="2026-05-07">
+  <owner>tovep@chromium.org</owner>
+  <owner>eladalon@chromium.org</owner>
+  <summary>
+    Records which controls are clicked in the TabSharingInfoBar. The histogram
+    is recorded for each click on such a control.
+  </summary>
+</histogram>
+
 <histogram
     name="Media.Ui.GetDisplayMedia.WindowSharingPermissionInteractionMac"
     enum="MediaUiGetDisplayMediaPermissionInteraction"
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index 075dd6b7..c60f4ef 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -21,8 +21,8 @@
             "full_remote_path": "perfetto-luci-artifacts/v50.1/mac-arm64/trace_processor_shell"
         },
         "linux": {
-            "hash": "d12afd974c73f69f6b6f28e084fe3f30d323b101",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/109a2ebe2b65215b8ba3ff0d6a3a8361220a0992/trace_processor_shell"
+            "hash": "5f2b117cf04bd40120108afc74c46b881d8271a8",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/99c9ef8102d3770e6d5ba91d9ca400b751b0af9b/trace_processor_shell"
         }
     },
     "power_profile.sql": {
diff --git a/v8 b/v8
index 40b93d8..7d09849 160000
--- a/v8
+++ b/v8
@@ -1 +1 @@
-Subproject commit 40b93d8a6b46338c63f39312306d811ca5ebf37c
+Subproject commit 7d09849bb20b29cc989b14e62e086f1ded0a4134