diff --git a/BUILD.gn b/BUILD.gn
index 85f6428..b8b5fdd 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -1031,6 +1031,7 @@
       }
       if (use_v4l2_codec) {
         data_deps += [
+          "//media/gpu/chromeos:image_processor_perf_test",
           "//media/gpu/v4l2:v4l2_stateless_decoder",
           "//media/gpu/v4l2:v4l2_unittest",
         ]
diff --git a/DEPS b/DEPS
index 3dd12d16..8ca0ab4 100644
--- a/DEPS
+++ b/DEPS
@@ -300,19 +300,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': 'caa0380f0b26c677fedd3a3829236c0d8414c8ae',
+  'src_internal_revision': '1159092e81cadd8225e03fa98287eee03668e3d9',
   # 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': 'c8d3a52bbd70f74d807f0b1654461d80cf8c2896',
+  'skia_revision': 'b80cbfaf81f32b1e4abfb28454894d4bd1044dd7',
   # 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': 'bffc2731f3ec976ee2258d98ac9e2f29d8ce48d1',
+  'v8_revision': '89367f3ab6f26cdb3ce757d765dbe70c597ad26a',
   # 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': '7e075469ff0296e0e3bdaae25a40fd6792485b16',
+  'angle_revision': '18010f58be60bfcb233f70f125c91265d33ab4a7',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # 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': '7bc64b80aad58846a712b8925777ddb31d02d154',
+  'devtools_frontend_revision': '81e8f798e7b0638ea521ce8d3b8fa2c6b5af5061',
   # 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.
@@ -499,7 +499,7 @@
 
   # If you change this, also update the libc++ revision in
   # //buildtools/deps_revisions.gni.
-  'libcxx_revision':       '055b2e17ae4f0e2c025ad0c7508b01787df17758',
+  'libcxx_revision':       '38a8ad0f7ea659edb28a57dfd59a7e5399dabeab',
 
   # GN CIPD package version.
   'gn_version': 'git_revision:1cd35c1b722472e714c30d12031af81443bb20ae',
@@ -906,7 +906,7 @@
       'packages': [
         {
           'package': 'chromium/rts/model/linux-amd64',
-          'version': 'X6L8kjxxpyVVOWapUi3yTD2J_fOIVD8qB-nz6K7BugQC',
+          'version': 'HFsh1P6Q9AZxEBnZbZ8zC_v5zztnXXWNmyjn0MaThHUC',
         },
       ],
       'dep_type': 'cipd',
@@ -917,7 +917,7 @@
       'packages': [
         {
           'package': 'chromium/rts/model/mac-amd64',
-          'version': 'cMJMbmLkWCKPUlsjocwFI8btEz0xrmzfIFElA6HgCK0C',
+          'version': '9rAXeZlEUT08AdM7PN2TtSEqW7a2DWPdWcPA9AF5IqMC',
         },
       ],
       'dep_type': 'cipd',
@@ -928,7 +928,7 @@
       'packages': [
         {
           'package': 'chromium/rts/model/windows-amd64',
-          'version': 'zeYqXvuMMPbAg-KKhf6Mf5EhLLeA03x1TaLSyuPtvvoC',
+          'version': '8lKx6IDFtNPp1wUoEzdh8TChcpYO7mxFW6EiSyHdCzgC',
         },
       ],
       'dep_type': 'cipd',
@@ -1208,7 +1208,7 @@
   # Tools used when building Chrome for Chrome OS. This affects both the Simple
   # Chrome workflow, as well as the chromeos-chrome ebuild.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '547201a5e52de27e0842e79a5f71e162814fb474',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '2b8358b431c655230d9a8d9ef643fc8f6e48c370',
       'condition': 'checkout_chromeos',
   },
 
@@ -1249,7 +1249,7 @@
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
 
   'src/third_party/devtools-frontend-internal': {
-      'url': Var('chrome_git') + '/devtools/devtools-internal.git' + '@' + '3b3b3efd5bf2bc8bd5f294c21584249ed12a56c9',
+      'url': Var('chrome_git') + '/devtools/devtools-internal.git' + '@' + 'c7069f35b34cc6e018c3ee37412fafd2338c0459',
     'condition': 'checkout_src_internal',
   },
 
@@ -1580,7 +1580,7 @@
     Var('chromium_git') + '/webm/libwebp.git' + '@' +  'fd7b5d48464475408d32d2611bdb6947d4246b97',
 
   'src/third_party/libyuv':
-    Var('chromium_git') + '/libyuv/libyuv.git' + '@' + '873eaa3bbf5296f57193686573395e6b5cc99d74',
+    Var('chromium_git') + '/libyuv/libyuv.git' + '@' + '2a5d7e2fbc6735d633d50fb9711ac887e415eae3',
 
   'src/third_party/lighttpd': {
       'url': Var('chromium_git') + '/chromium/deps/lighttpd.git' + '@' + Var('lighttpd_revision'),
@@ -1705,7 +1705,7 @@
     Var('chromium_git') + '/external/github.com/cisco/openh264' + '@' + '09a4f3ec842a8932341b195c5b01e141c8a16eb7',
 
   'src/third_party/openscreen/src':
-    Var('chromium_git') + '/openscreen' + '@' + '03ab64e47e14f7b4c8ac50e651b2ef1cbb1f9b92',
+    Var('chromium_git') + '/openscreen' + '@' + 'a3689b39e7d0945d351588f6ccdba01c9ddf533b',
 
   'src/third_party/openxr/src': {
     'url': Var('chromium_git') + '/external/github.com/KhronosGroup/OpenXR-SDK' + '@' + '58a00cf85c39ad5ec4dc43a769624e420c06179a',
@@ -1716,7 +1716,7 @@
     Var('pdfium_git') + '/pdfium.git' + '@' +  Var('pdfium_revision'),
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + 'a4e9e26e3958f4d15e12ae84073252ba3f9f471b',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + 'd019594d65a235c4a9cba35d48a48ec8dd065da2',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1901,7 +1901,7 @@
     Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + 'cf7f6bbb113147b698ad23d06eda62aa091f4ffb',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + 'cfa0f817b73012fad470660c51e9fe0e67bda521',
+    Var('webrtc_git') + '/src.git' + '@' + '1f235d6efe8574ac319bf20556dbacc5a12b708c',
 
   # Wuffs' canonical repository is at github.com/google/wuffs, but we use
   # Skia's mirror of Wuffs, the same as in upstream Skia's DEPS file.
@@ -2026,7 +2026,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/help_app/app',
-        'version': 'wlLXergE09RdlhIbKmtnWz6B05e14UxhZsFEzfrUKVcC',
+        'version': 'HTQXGmqOjSBe-mEIko8ytu8V_mp8UDjM1Rk8AE6XWJwC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
@@ -2037,7 +2037,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/media_app/app',
-        'version': 'nk7uqhf0SSRPUO6Eous9JnINEUxt33Y-aBEOhyw_ltwC',
+        'version': 'rCTSNpbeNxtJLvVp9XGiUP90cuioMBYiLSAPN_q3R-EC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
@@ -3965,20 +3965,10 @@
   # Dependencies from src_internal
   'src/chromeos/assistant/internal': {
       'url': Var('chrome_git') + '/chrome/assistant.git' + '@' +
-        '9c3775a18cfdb63fa605f4568690885cb02aa9dc',
+        'ecafc1ad7b48348f55a68d53f1913cb554220de2',
       'condition': 'checkout_src_internal and checkout_chromeos',
     },
 
-  'src/chromeos/assistant/libassistant/src': {
-      'url': Var('chrome_git') + '/external/gob/libassistant-internal/standalone/src.git' + '@' + 'e38dcf1c89c9a6b3e1d3e6745985564598a5bce4',
-      'condition': 'checkout_src_internal and checkout_chromeos',
-  },
-
-  'src/libassistant': {
-      'url': Var('chrome_git') + '/chrome/libassistant.git' + '@' + '4d223a10506e6439d9c0fc734a54b049827a5d29',
-      'condition': 'checkout_src_internal and checkout_chromeos',
-  },
-
   'src/ui/gl/resources/angle-metal': {
     'packages': [{
        'package': 'chromium/gpu/angle-metal-shader-libraries',
@@ -4195,7 +4185,7 @@
 
   'src/ios_internal':  {
       'url': Var('chrome_git') + '/chrome/ios_internal.git' + '@' +
-        '8043fd2f7f7bb5fb3193e5734289e7255797c4b9',
+        'cdcc9a378a9b22a35e98b52f94b1f1fbe2092d28',
       'condition': 'checkout_ios and checkout_src_internal',
   },
 
@@ -5653,8 +5643,6 @@
   # since the roller does not run tests.
   'src/clank',
   'src/chromeos/assistant/internal',
-  'src/chromeos/assistant/libassistant/src',
-  'src/libassistant',
   'src/chrome/chrome_cleaner/internal',
   'src/components/optimization_guide/internal',
   'src/ios_internal',
diff --git a/android_webview/browser/aw_feature_map.cc b/android_webview/browser/aw_feature_map.cc
index 5195df57..07c6ff8a 100644
--- a/android_webview/browser/aw_feature_map.cc
+++ b/android_webview/browser/aw_feature_map.cc
@@ -34,6 +34,7 @@
     &metrics::kAndroidMetricsAsyncMetricLogging,
     &features::kWebViewZoomKeyboardShortcuts,
     &features::kWebViewClearFunctorInBackground,
+    &features::kWebViewReportFrameMetrics,
 };
 
 // static
diff --git a/android_webview/browser/aw_ssl_host_state_delegate.cc b/android_webview/browser/aw_ssl_host_state_delegate.cc
index 18cb23e..cf89eae 100644
--- a/android_webview/browser/aw_ssl_host_state_delegate.cc
+++ b/android_webview/browser/aw_ssl_host_state_delegate.cc
@@ -121,9 +121,11 @@
     const net::X509Certificate& cert,
     int error,
     content::StoragePartition* storage_partition) {
-  return cert_policy_for_host_[host].Check(cert, error)
-             ? SSLHostStateDelegate::ALLOWED
-             : SSLHostStateDelegate::DENIED;
+  auto iter = cert_policy_for_host_.find(host);
+  if (iter != cert_policy_for_host_.end() && iter->second.Check(cert, error)) {
+    return SSLHostStateDelegate::ALLOWED;
+  }
+  return SSLHostStateDelegate::DENIED;
 }
 
 void AwSSLHostStateDelegate::RevokeUserAllowExceptions(
@@ -139,4 +141,14 @@
          policy_iterator->second.HasAllowException();
 }
 
+bool AwSSLHostStateDelegate::HasAllowExceptionForAnyHost(
+    content::StoragePartition* storage_partition) {
+  for (auto const& it : cert_policy_for_host_) {
+    if (it.second.HasAllowException()) {
+      return true;
+    }
+  }
+  return false;
+}
+
 }  // namespace android_webview
diff --git a/android_webview/browser/aw_ssl_host_state_delegate.h b/android_webview/browser/aw_ssl_host_state_delegate.h
index 1ec3cc3..e5b1f36 100644
--- a/android_webview/browser/aw_ssl_host_state_delegate.h
+++ b/android_webview/browser/aw_ssl_host_state_delegate.h
@@ -101,6 +101,11 @@
   bool HasAllowException(const std::string& host,
                          content::StoragePartition* storage_partition) override;
 
+  // Returns whether the user has allowed any certificate error exception or
+  // HTTP exception for any host in |storage_partition|.
+  bool HasAllowExceptionForAnyHost(
+      content::StoragePartition* storage_partition) override;
+
  private:
   // Certificate policies for each host.
   std::map<std::string, internal::CertPolicy> cert_policy_for_host_;
diff --git a/android_webview/common/aw_features.cc b/android_webview/common/aw_features.cc
index d326df7..56b6e12 100644
--- a/android_webview/common/aw_features.cc
+++ b/android_webview/common/aw_features.cc
@@ -105,6 +105,12 @@
              "WebViewRecordAppDataDirectorySize",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
+// Whether to report frame metrics to the Android.Jank.FrameDuration and
+// Android.Jank.FrameJankStatus histograms.
+BASE_FEATURE(kWebViewReportFrameMetrics,
+             "WebViewReportFrameMetrics",
+             base::FEATURE_ENABLED_BY_DEFAULT);
+
 // Flag to restrict main frame Web Content to verified web content. Verification
 // happens via Digital Asset Links.
 BASE_FEATURE(kWebViewRestrictSensitiveContent,
diff --git a/android_webview/common/aw_features.h b/android_webview/common/aw_features.h
index 9ee465dd..4df6fe60 100644
--- a/android_webview/common/aw_features.h
+++ b/android_webview/common/aw_features.h
@@ -33,6 +33,7 @@
 BASE_DECLARE_FEATURE(kWebViewMixedContentAutoupgrades);
 BASE_DECLARE_FEATURE(kWebViewOriginTrials);
 BASE_DECLARE_FEATURE(kWebViewRecordAppDataDirectorySize);
+BASE_DECLARE_FEATURE(kWebViewReportFrameMetrics);
 BASE_DECLARE_FEATURE(kWebViewRestrictSensitiveContent);
 BASE_DECLARE_FEATURE(kWebViewSafeBrowsingSafeMode);
 BASE_DECLARE_FEATURE(kWebViewSuppressDifferentOriginSubframeJSDialogs);
diff --git a/android_webview/java/src/org/chromium/android_webview/AwContents.java b/android_webview/java/src/org/chromium/android_webview/AwContents.java
index 177b9e4..fb08b32b 100644
--- a/android_webview/java/src/org/chromium/android_webview/AwContents.java
+++ b/android_webview/java/src/org/chromium/android_webview/AwContents.java
@@ -36,6 +36,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewStructure;
+import android.view.Window;
 import android.view.accessibility.AccessibilityNodeProvider;
 import android.view.animation.AnimationUtils;
 import android.view.autofill.AutofillValue;
@@ -74,6 +75,8 @@
 import org.chromium.base.annotations.CalledByNativeUnchecked;
 import org.chromium.base.annotations.JNINamespace;
 import org.chromium.base.annotations.NativeMethods;
+import org.chromium.base.jank_tracker.FrameMetricsListener;
+import org.chromium.base.jank_tracker.FrameMetricsStore;
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.metrics.ScopedSysTraceEvent;
 import org.chromium.base.task.AsyncTask;
@@ -487,6 +490,8 @@
     // attached to the Window and size tracking is enabled. It will be null otherwise.
     private AwWindowCoverageTracker mAwWindowCoverageTracker;
 
+    private AwFrameMetricsListener mAwFrameMetricsListener;
+
     private AwDarkMode mAwDarkMode;
     private AwWebContentsMetricsRecorder mAwWebContentsMetricsRecorder;
 
@@ -1002,6 +1007,34 @@
         }
     }
 
+    private class AwFrameMetricsListener {
+        private FrameMetricsListener mFrameMetricsListener;
+        private boolean mAttached;
+
+        public AwFrameMetricsListener() {
+            FrameMetricsStore metricsStore = new FrameMetricsStore();
+            mFrameMetricsListener = new FrameMetricsListener(metricsStore);
+            mAttached = false;
+        }
+
+        public void attachListener(Window window) {
+            if (mAttached) return;
+            final Handler handler = new Handler();
+            window.addOnFrameMetricsAvailableListener(mFrameMetricsListener, handler);
+            mAttached = true;
+        }
+
+        public void detachListener(Window window) {
+            if (!mAttached) return;
+            window.removeOnFrameMetricsAvailableListener(mFrameMetricsListener);
+            mAttached = false;
+        }
+
+        public FrameMetricsListener getFrameMetricsListener() {
+            return mFrameMetricsListener;
+        }
+    }
+
     //--------------------------------------------------------------------------------------------
     /**
      * @param browserContext the browsing context to associate this view contents with.
@@ -1131,6 +1164,10 @@
 
             onContainerViewChanged();
         }
+
+        if (AwFeatureMap.isEnabled(AwFeatures.WEBVIEW_REPORT_FRAME_METRICS)) {
+            mAwFrameMetricsListener = new AwFrameMetricsListener();
+        }
     }
 
     private void initWebContents(ViewAndroidDelegate viewDelegate,
@@ -3218,6 +3255,13 @@
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
             if (mDisplayCutoutController != null) mDisplayCutoutController.onAttachedToWindow();
         }
+
+        if (mAwFrameMetricsListener != null) {
+            Activity activity = getActivity();
+            if (activity != null) {
+                mAwFrameMetricsListener.attachListener(activity.getWindow());
+            }
+        }
     }
 
     private void detachWindowCoverageTracker() {
@@ -3235,6 +3279,13 @@
         detachWindowCoverageTracker();
         mWindowAndroid.getWindowAndroid().getDisplay().removeObserver(mDisplayObserver);
         mAwViewMethods.onDetachedFromWindow();
+
+        if (mAwFrameMetricsListener != null) {
+            Activity activity = getActivity();
+            if (activity != null) {
+                mAwFrameMetricsListener.detachListener(activity.getWindow());
+            }
+        }
     }
 
     /**
@@ -3303,6 +3354,14 @@
         mIsWindowVisible = visible;
         if (!isDestroyed(NO_WARN)) {
             AwContentsJni.get().setWindowVisibility(mNativeAwContents, mIsWindowVisible);
+
+            if (mAwFrameMetricsListener != null) {
+                if (mIsWindowVisible) {
+                    mAwFrameMetricsListener.getFrameMetricsListener().setIsListenerRecording(true);
+                } else {
+                    mAwFrameMetricsListener.getFrameMetricsListener().setIsListenerRecording(false);
+                }
+            }
         }
         // Using TimeUtils to allow it being overridden in tests.
         mLastWindowVisibleTime = visible ? CURRENTLY_VISIBLE : TimeUtils.uptimeMillis();
@@ -4049,6 +4108,11 @@
         }
     }
 
+    private Activity getActivity() {
+        Context context = mWindowAndroid.getWindowAndroid().getContext().get();
+        return ContextUtils.activityFromContext(context);
+    }
+
     // --------------------------------------------------------------------------------------------
     // This is the AwViewMethods implementation that does real work. The AwViewMethodsImpl is
     // hooked up to the WebView in embedded mode and to the FullScreenView in fullscreen mode,
diff --git a/android_webview/java/src/org/chromium/android_webview/AwContentsClientBridge.java b/android_webview/java/src/org/chromium/android_webview/AwContentsClientBridge.java
index 7391f8f5..e73dded 100644
--- a/android_webview/java/src/org/chromium/android_webview/AwContentsClientBridge.java
+++ b/android_webview/java/src/org/chromium/android_webview/AwContentsClientBridge.java
@@ -23,7 +23,6 @@
 import org.chromium.base.annotations.CalledByNativeUnchecked;
 import org.chromium.base.annotations.JNINamespace;
 import org.chromium.base.annotations.NativeMethods;
-import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.task.PostTask;
 import org.chromium.base.task.TaskTraits;
 import org.chromium.components.embedder_support.util.WebResourceResponseInfo;
@@ -414,10 +413,6 @@
         WebResourceResponseInfo response = new WebResourceResponseInfo(
                 mimeType, encoding, null, statusCode, reasonPhrase, responseHeaders);
         mClient.getCallbackHelper().postOnReceivedHttpError(request, response);
-
-        // Record UMA on http response status.
-        RecordHistogram.recordSparseHistogram(
-                "Android.WebView.onReceivedHttpError.StatusCode", statusCode);
     }
 
     @CalledByNativeUnchecked
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 29c37cb..bee8c655 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
@@ -437,6 +437,8 @@
             Flag.baseFeature(VizFeatures.ON_BEGIN_FRAME_THROTTLE_VIDEO,
                     "Enables throttling OnBeginFrame for video frame sinks"
                             + "with a preferred framerate defined."),
+            Flag.baseFeature(AwFeatures.WEBVIEW_REPORT_FRAME_METRICS,
+                    "Report frame metrics to Google, if metrics reporting has been enabled."),
             // Add new commandline switches and features above. The final entry should have a
             // trailing comma for cleaner diffs.
     };
diff --git a/ash/accessibility/a11y_feature_type.h b/ash/accessibility/a11y_feature_type.h
index 85ce5eb1..403a151 100644
--- a/ash/accessibility/a11y_feature_type.h
+++ b/ash/accessibility/a11y_feature_type.h
@@ -29,6 +29,7 @@
   kStickyKeys,
   kSwitchAccess,
   kVirtualKeyboard,
+  kColorCorrection,
 
   kFeatureCount,
 
diff --git a/ash/accessibility/accessibility_controller_impl.cc b/ash/accessibility/accessibility_controller_impl.cc
index 6d83d3f..d53f2a34a 100644
--- a/ash/accessibility/accessibility_controller_impl.cc
+++ b/ash/accessibility/accessibility_controller_impl.cc
@@ -34,6 +34,7 @@
 #include "ash/public/cpp/notification_utils.h"
 #include "ash/public/cpp/session/session_observer.h"
 #include "ash/public/cpp/shell_window_ids.h"
+#include "ash/public/cpp/system_tray_client.h"
 #include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/root_window_controller.h"
 #include "ash/session/session_controller_impl.h"
@@ -45,6 +46,7 @@
 #include "ash/system/accessibility/floating_accessibility_controller.h"
 #include "ash/system/accessibility/select_to_speak/select_to_speak_menu_bubble_controller.h"
 #include "ash/system/accessibility/switch_access/switch_access_menu_bubble_controller.h"
+#include "ash/system/model/system_tray_model.h"
 #include "ash/system/power/backlights_forced_off_setter.h"
 #include "ash/system/power/power_status.h"
 #include "ash/system/power/scoped_backlights_forced_off.h"
@@ -109,6 +111,8 @@
      nullptr},
     {FeatureType::kDictation, prefs::kAccessibilityDictationEnabled,
      &kDictationMenuIcon},
+    {FeatureType::kColorCorrection, prefs::kAccessibilityColorFiltering,
+     &kColorCorrectionIcon},
     {FeatureType::kFocusHighlight, prefs::kAccessibilityFocusHighlightEnabled,
      nullptr, /* conflicting_feature= */ FeatureType::kSpokenFeedback},
     {FeatureType::kFloatingMenu, prefs::kAccessibilityFloatingMenuEnabled,
@@ -198,6 +202,7 @@
     prefs::kAccessibilityChromeVoxVirtualBrailleColumns,
     prefs::kAccessibilityChromeVoxVirtualBrailleRows,
     prefs::kAccessibilityChromeVoxVoiceName,
+    prefs::kAccessibilityColorFiltering,
     prefs::kAccessibilityCursorHighlightEnabled,
     prefs::kAccessibilityCursorColorEnabled,
     prefs::kAccessibilityCursorColor,
@@ -807,6 +812,12 @@
   PrefService* prefs = owner_->active_user_prefs_;
   DCHECK(prefs);
 
+  if (pref_name_ == prefs::kAccessibilityColorFiltering &&
+      !::features::
+          AreExperimentalAccessibilityColorEnhancementSettingsEnabled()) {
+    return;
+  }
+
   bool enabled = prefs->GetBoolean(pref_name_);
 
   if (conflicting_feature_ != FeatureType::kNoConflictingFeature &&
@@ -1024,6 +1035,8 @@
   if (::features::
           AreExperimentalAccessibilityColorEnhancementSettingsEnabled()) {
     registry->RegisterBooleanPref(prefs::kAccessibilityColorFiltering, false);
+    registry->RegisterBooleanPref(
+        prefs::kAccessibilityColorFilteringHasBeenSetup, false);
   }
 
   // TODO(b/266816160): Make ChromeVox prefs are syncable, to so that ChromeOS
@@ -1294,6 +1307,11 @@
 }
 
 AccessibilityControllerImpl::Feature&
+AccessibilityControllerImpl::color_correction() const {
+  return GetFeature(FeatureType::kColorCorrection);
+}
+
+AccessibilityControllerImpl::Feature&
 AccessibilityControllerImpl::focus_highlight() const {
   return GetFeature(FeatureType::kFocusHighlight);
 }
@@ -1373,6 +1391,7 @@
   return (IsSpokenFeedbackSettingVisibleInTray() ||
           IsSelectToSpeakSettingVisibleInTray() ||
           IsDictationSettingVisibleInTray() ||
+          IsColorCorrectionSettingVisibleInTray() ||
           IsHighContrastSettingVisibleInTray() ||
           IsFullScreenMagnifierSettingVisibleInTray() ||
           IsDockedMagnifierSettingVisibleInTray() ||
@@ -1453,6 +1472,25 @@
   return high_contrast().IsEnterpriseIconVisible();
 }
 
+bool AccessibilityControllerImpl::IsColorCorrectionSettingVisibleInTray() {
+  if (!::features::
+          AreExperimentalAccessibilityColorEnhancementSettingsEnabled()) {
+    return false;
+  }
+  if (!color_correction().enabled() &&
+      Shell::Get()->session_controller()->login_status() ==
+          ash::LoginStatus::NOT_LOGGED_IN) {
+    // Don't allow users to enable this on not logged in profiles because it
+    // requires set-up in settings the first time it is run.
+    return false;
+  }
+  return color_correction().IsVisibleInTray();
+}
+
+bool AccessibilityControllerImpl::IsEnterpriseIconVisibleForColorCorrection() {
+  return color_correction().IsEnterpriseIconVisible();
+}
+
 bool AccessibilityControllerImpl::IsLargeCursorSettingVisibleInTray() {
   return large_cursor().IsVisibleInTray();
 }
@@ -2024,11 +2062,6 @@
           base::Unretained(this)));
   if (color_enhancement_feature_enabled) {
     pref_change_registrar_->Add(
-        prefs::kAccessibilityColorFiltering,
-        base::BindRepeating(
-            &AccessibilityControllerImpl::UpdateColorFilteringFromPrefs,
-            base::Unretained(this)));
-    pref_change_registrar_->Add(
         prefs::kAccessibilityColorVisionCorrectionAmount,
         base::BindRepeating(
             &AccessibilityControllerImpl::UpdateColorFilteringFromPrefs,
@@ -2698,6 +2731,18 @@
     case FeatureType::kCursorColor:
       UpdateCursorColorFromPrefs();
       break;
+    case FeatureType::kColorCorrection:
+      if (enabled && !active_user_prefs_->GetBoolean(
+                         prefs::kAccessibilityColorFilteringHasBeenSetup)) {
+        Shell::Get()
+            ->system_tray_model()
+            ->client()
+            ->ShowColorCorrectionSettings();
+        active_user_prefs_->SetBoolean(
+            prefs::kAccessibilityColorFilteringHasBeenSetup, true);
+      }
+      UpdateColorFilteringFromPrefs();
+      break;
     case FeatureType::kFeatureCount:
     case FeatureType::kNoConflictingFeature:
       NOTREACHED();
diff --git a/ash/accessibility/accessibility_controller_impl.h b/ash/accessibility/accessibility_controller_impl.h
index e1bf3ca..4086338e 100644
--- a/ash/accessibility/accessibility_controller_impl.h
+++ b/ash/accessibility/accessibility_controller_impl.h
@@ -210,6 +210,7 @@
   Feature& caret_highlight() const;
   Feature& cursor_highlight() const;
   Feature& dictation() const;
+  Feature& color_correction() const;
   Feature& floating_menu() const;
   Feature& focus_highlight() const;
   FeatureWithDialog& fullscreen_magnifier() const;
@@ -275,6 +276,9 @@
   bool IsHighContrastSettingVisibleInTray();
   bool IsEnterpriseIconVisibleForHighContrast();
 
+  bool IsColorCorrectionSettingVisibleInTray();
+  bool IsEnterpriseIconVisibleForColorCorrection();
+
   bool IsLargeCursorSettingVisibleInTray();
   bool IsEnterpriseIconVisibleForLargeCursor();
 
diff --git a/ash/accessibility/accessibility_controller_unittest.cc b/ash/accessibility/accessibility_controller_unittest.cc
index f03ede4..5050797 100644
--- a/ash/accessibility/accessibility_controller_unittest.cc
+++ b/ash/accessibility/accessibility_controller_unittest.cc
@@ -16,6 +16,7 @@
 #include "ash/constants/ash_pref_names.h"
 #include "ash/display/cursor_window_controller.h"
 #include "ash/keyboard/ui/keyboard_util.h"
+#include "ash/public/cpp/test/test_system_tray_client.h"
 #include "ash/session/session_controller_impl.h"
 #include "ash/session/test_pref_service_provider.h"
 #include "ash/shell.h"
@@ -180,6 +181,8 @@
           AreExperimentalAccessibilityColorEnhancementSettingsEnabled()) {
     EXPECT_TRUE(prefs->FindPreference(prefs::kAccessibilityColorFiltering));
     EXPECT_TRUE(
+        prefs->FindPreference(prefs::kAccessibilityColorFilteringHasBeenSetup));
+    EXPECT_TRUE(
         prefs->FindPreference(prefs::kAccessibilityColorVisionDeficiencyType));
     EXPECT_TRUE(prefs->FindPreference(
         prefs::kAccessibilityColorVisionCorrectionAmount));
@@ -226,6 +229,42 @@
   controller->RemoveObserver(&observer);
 }
 
+TEST_F(AccessibilityControllerTest, SetColorCorrectionEnabled) {
+  AccessibilityControllerImpl* controller =
+      Shell::Get()->accessibility_controller();
+  EXPECT_FALSE(controller->color_correction().enabled());
+
+  TestAccessibilityObserver observer;
+  controller->AddObserver(&observer);
+  EXPECT_EQ(0, observer.status_changed_count_);
+
+  EXPECT_EQ(0, GetSystemTrayClient()->show_color_correction_settings_count());
+
+  controller->color_correction().SetEnabled(true);
+  EXPECT_TRUE(controller->color_correction().enabled());
+  EXPECT_EQ(1, observer.status_changed_count_);
+
+  // The first time we should show the settings for color correction.
+  EXPECT_EQ(1, GetSystemTrayClient()->show_color_correction_settings_count());
+
+  controller->color_correction().SetEnabled(false);
+  EXPECT_FALSE(controller->color_correction().enabled());
+  EXPECT_EQ(2, observer.status_changed_count_);
+
+  controller->color_correction().SetEnabled(true);
+  EXPECT_TRUE(controller->color_correction().enabled());
+  EXPECT_EQ(3, observer.status_changed_count_);
+
+  // The second time, the settings window should not be opened.
+  EXPECT_EQ(1, GetSystemTrayClient()->show_color_correction_settings_count());
+
+  controller->color_correction().SetEnabled(false);
+  EXPECT_FALSE(controller->color_correction().enabled());
+  EXPECT_EQ(4, observer.status_changed_count_);
+
+  controller->RemoveObserver(&observer);
+}
+
 TEST_F(AccessibilityControllerTest, SetCursorHighlightEnabled) {
   AccessibilityControllerImpl* controller =
       Shell::Get()->accessibility_controller();
diff --git a/ash/ash_strings.grd b/ash/ash_strings.grd
index ff43056..e56b690 100644
--- a/ash/ash_strings.grd
+++ b/ash/ash_strings.grd
@@ -1080,6 +1080,9 @@
       <message name="IDS_ASH_STATUS_TRAY_ACCESSIBILITY_DICTATION" desc="The label used in the accessibility menu of the system tray to toggle on/off the speak to type feature.">
         Dictation
       </message>
+      <message name="IDS_ASH_STATUS_TRAY_ACCESSIBILITY_COLOR_CORRECTION" desc="The label used in the accessibility menu of the system tray to toggle on/off the color correction feature.">
+        Color correction
+      </message>
       <message name="IDS_ASH_DICTATION_LANGUAGE_SUPPORTED_OFFLINE_NUDGE" desc="The message shown for existing Dictation users when their Dictation language has been upgraded in the background to work offline.">
         <ph name="language">$1<ex>English</ex></ph> speech is now processed locally and works offline. You can change your Dictation language in Settings > Accessibility.
       </message>
diff --git a/ash/ash_strings_grd/IDS_ASH_STATUS_TRAY_ACCESSIBILITY_COLOR_CORRECTION.png.sha1 b/ash/ash_strings_grd/IDS_ASH_STATUS_TRAY_ACCESSIBILITY_COLOR_CORRECTION.png.sha1
new file mode 100644
index 0000000..8a760ba
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_ASH_STATUS_TRAY_ACCESSIBILITY_COLOR_CORRECTION.png.sha1
@@ -0,0 +1 @@
+d19925dbfff558c4de7473b27d7b393dd3b05306
\ No newline at end of file
diff --git a/ash/components/arc/mojom/notifications.mojom b/ash/components/arc/mojom/notifications.mojom
index e5154df0aa..297b080 100644
--- a/ash/components/arc/mojom/notifications.mojom
+++ b/ash/components/arc/mojom/notifications.mojom
@@ -127,10 +127,11 @@
   ArcNotificationPriority priority;
   // Timestamp related to the notification
   int64 time;
-  // DEPRECATED: The current value of progress, must be [0, progress_max].
-  int32 deprecated_progress_current;
-  // DEPRECATED: The maximum value of progress.
-  int32 deprecated_progress_max;
+  // The current value of progress. If in progress, the value must be in
+  // [0, progress_max]. Otherwise set to -1.
+  int32 progress_current;
+  // The maximum value of progress.
+  int32 progress_max;
   // Action buttons, provided when the notification is action enabled and
   // rendered on Chrome.
   array<ArcNotificationButton>? buttons;
diff --git a/ash/constants/ash_features.cc b/ash/constants/ash_features.cc
index 8a104a04..fd1ed93 100644
--- a/ash/constants/ash_features.cc
+++ b/ash/constants/ash_features.cc
@@ -1060,6 +1060,9 @@
         &kFloatingWorkspaceV2, "PeriodicJobIntervalInSeconds",
         base::Seconds(30)};
 
+// Enables or disables Focus Mode feature on ChromeOS.
+BASE_FEATURE(kFocusMode, "FocusMode", base::FEATURE_DISABLED_BY_DEFAULT);
+
 // If enabled, makes the Projector app use server side speech
 // recognition instead of on-device speech recognition.
 BASE_FEATURE(kForceEnableServerSideSpeechRecognitionForDev,
@@ -1702,6 +1705,11 @@
              "OnlyShowNewShortcutsApp",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
+// Enables or disables search customizable shortcuts in launcher.
+BASE_FEATURE(kSearchCustomizableShortcutsInLauncher,
+             "SearchCustomizableShortcutsInLauncher",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 BASE_FEATURE(kSearchInShortcutsApp,
              "SearchInShortcutsApp",
              base::FEATURE_ENABLED_BY_DEFAULT);
@@ -2917,6 +2925,10 @@
   return base::FeatureList::IsEnabled(kFloatingWorkspaceV2);
 }
 
+bool IsFocusModeEnabled() {
+  return base::FeatureList::IsEnabled(kFocusMode);
+}
+
 bool ShouldForceEnableServerSideSpeechRecognitionForDev() {
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
   return base::FeatureList::IsEnabled(
@@ -3700,6 +3712,10 @@
   return base::FeatureList::IsEnabled(kOnlyShowNewShortcutsApp);
 }
 
+bool isSearchCustomizableShortcutsInLauncherEnabled() {
+  return base::FeatureList::IsEnabled(kSearchCustomizableShortcutsInLauncher);
+}
+
 bool IsSearchInShortcutsAppEnabled() {
   return base::FeatureList::IsEnabled(kSearchInShortcutsApp);
 }
diff --git a/ash/constants/ash_features.h b/ash/constants/ash_features.h
index 35bf3cb..e0f22e5 100644
--- a/ash/constants/ash_features.h
+++ b/ash/constants/ash_features.h
@@ -332,6 +332,7 @@
 COMPONENT_EXPORT(ASH_CONSTANTS)
 extern const base::FeatureParam<base::TimeDelta>
     kEcheConnectionStatusResetTimeout;
+COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kFocusMode);
 COMPONENT_EXPORT(ASH_CONSTANTS)
 BASE_DECLARE_FEATURE(kForceEnableServerSideSpeechRecognitionForDev);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kForceReSyncDrive);
@@ -483,6 +484,8 @@
 COMPONENT_EXPORT(ASH_CONSTANTS)
 BASE_DECLARE_FEATURE(kOnDeviceSpeechRecognition);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kOnlyShowNewShortcutsApp);
+COMPONENT_EXPORT(ASH_CONSTANTS)
+BASE_DECLARE_FEATURE(kSearchCustomizableShortcutsInLauncher);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kSearchInShortcutsApp);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kOsFeedback);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kOobeChoobe);
@@ -806,6 +809,7 @@
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsFilesSearchV2Enabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsFloatingWorkspaceEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsFloatingWorkspaceV2Enabled();
+COMPONENT_EXPORT(ASH_CONSTANTS) bool IsFocusModeEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS)
 bool ShouldForceEnableServerSideSpeechRecognitionForDev();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsForceReSyncDriveEnabled();
@@ -1021,6 +1025,8 @@
 COMPONENT_EXPORT(ASH_CONSTANTS) bool ShouldUseAndroidStagingSmds();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool ShouldUseStorkSmds();
 COMPONENT_EXPORT(ASH_CONSTANTS)
+bool isSearchCustomizableShortcutsInLauncherEnabled();
+COMPONENT_EXPORT(ASH_CONSTANTS)
 bool IsSearchInShortcutsAppEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS)
 bool IsFeatureAwareDeviceDemoModeEnabled();
diff --git a/ash/constants/ash_pref_names.cc b/ash/constants/ash_pref_names.cc
index 8a53808..566bb83 100644
--- a/ash/constants/ash_pref_names.cc
+++ b/ash/constants/ash_pref_names.cc
@@ -431,6 +431,10 @@
 // Whether to enable color filtering settings.
 const char kAccessibilityColorFiltering[] =
     "settings.a11y.color_filtering.enabled";
+// Whether color filtering has been set up yet. It should be set up on first
+// use.
+const char kAccessibilityColorFilteringHasBeenSetup[] =
+    "settings.a11y.color_filtering.setup";
 // The amount of a color vision correction filter to apply.
 const char kAccessibilityColorVisionCorrectionAmount[] =
     "settings.a11y.color_filtering.color_vision_correction_amount";
diff --git a/ash/constants/ash_pref_names.h b/ash/constants/ash_pref_names.h
index 5ed2d05c1..686b7fa 100644
--- a/ash/constants/ash_pref_names.h
+++ b/ash/constants/ash_pref_names.h
@@ -212,6 +212,8 @@
 COMPONENT_EXPORT(ASH_CONSTANTS)
 extern const char kAccessibilityColorFiltering[];
 COMPONENT_EXPORT(ASH_CONSTANTS)
+extern const char kAccessibilityColorFilteringHasBeenSetup[];
+COMPONENT_EXPORT(ASH_CONSTANTS)
 extern const char kAccessibilityColorVisionCorrectionAmount[];
 COMPONENT_EXPORT(ASH_CONSTANTS)
 extern const char kAccessibilityColorVisionDeficiencyType[];
diff --git a/ash/public/cpp/system_tray_client.h b/ash/public/cpp/system_tray_client.h
index e90a7d6..fe7c0d1 100644
--- a/ash/public/cpp/system_tray_client.h
+++ b/ash/public/cpp/system_tray_client.h
@@ -95,6 +95,9 @@
   // Shows the settings related to accessibility.
   virtual void ShowAccessibilitySettings() = 0;
 
+  // Shows the settings related to color correction.
+  virtual void ShowColorCorrectionSettings() = 0;
+
   // Shows gesture education help.
   virtual void ShowGestureEducationHelp() = 0;
 
diff --git a/ash/public/cpp/test/test_system_tray_client.cc b/ash/public/cpp/test/test_system_tray_client.cc
index 5604a6c..4690431d 100644
--- a/ash/public/cpp/test/test_system_tray_client.cc
+++ b/ash/public/cpp/test/test_system_tray_client.cc
@@ -60,6 +60,10 @@
 
 void TestSystemTrayClient::ShowAccessibilitySettings() {}
 
+void TestSystemTrayClient::ShowColorCorrectionSettings() {
+  show_color_correction_settings_count_++;
+}
+
 void TestSystemTrayClient::ShowGestureEducationHelp() {}
 
 void TestSystemTrayClient::ShowPaletteHelp() {}
diff --git a/ash/public/cpp/test/test_system_tray_client.h b/ash/public/cpp/test/test_system_tray_client.h
index 1c1841d..93c9faf9 100644
--- a/ash/public/cpp/test/test_system_tray_client.h
+++ b/ash/public/cpp/test/test_system_tray_client.h
@@ -44,6 +44,7 @@
   void ShowAboutChromeOSDetails() override;
   void ShowAccessibilityHelp() override;
   void ShowAccessibilitySettings() override;
+  void ShowColorCorrectionSettings() override;
   void ShowGestureEducationHelp() override;
   void ShowPaletteHelp() override;
   void ShowPaletteSettings() override;
@@ -177,6 +178,10 @@
 
   int show_eol_info_count() const { return show_eol_info_count_; }
 
+  int show_color_correction_settings_count() const {
+    return show_color_correction_settings_count_;
+  }
+
  private:
   int show_network_settings_count_ = 0;
   int show_bluetooth_settings_count_ = 0;
@@ -207,6 +212,7 @@
   int show_audio_settings_count_ = 0;
   bool user_feedback_enabled_ = false;
   int show_eol_info_count_ = 0;
+  int show_color_correction_settings_count_ = 0;
 };
 
 }  // namespace ash
diff --git a/ash/resources/vector_icons/BUILD.gn b/ash/resources/vector_icons/BUILD.gn
index 7bbc466..66e633a 100644
--- a/ash/resources/vector_icons/BUILD.gn
+++ b/ash/resources/vector_icons/BUILD.gn
@@ -84,6 +84,7 @@
     "clipboard_launcher.icon",
     "clipboard_launcher_no_assistant.icon",
     "clipboard_search.icon",
+    "color_correction.icon",
     "combine_desks.icon",
     "continue_files_dark.icon",
     "continue_files_light.icon",
diff --git a/ash/resources/vector_icons/color_correction.icon b/ash/resources/vector_icons/color_correction.icon
new file mode 100644
index 0000000..ac09e29
--- /dev/null
+++ b/ash/resources/vector_icons/color_correction.icon
@@ -0,0 +1,39 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+CANVAS_DIMENSIONS, 14,
+MOVE_TO, 0, 14,
+R_V_LINE_TO, -4,
+R_LINE_TO, 7, -7,
+R_LINE_TO, -1, -1,
+R_LINE_TO, 1, -1,
+R_LINE_TO, 2, 2,
+R_LINE_TO, 2, -3,
+R_H_LINE_TO, 1,
+R_LINE_TO, 2, 2,
+R_V_LINE_TO, 1,
+R_LINE_TO, -2, 3,
+R_LINE_TO, 1, 1,
+R_LINE_TO, -1, 1,
+R_LINE_TO, -1, -1,
+R_LINE_TO, -7, 7,
+H_LINE_TO, 0,
+CLOSE,
+R_MOVE_TO, 2, -1,
+R_H_LINE_TO, 1,
+R_LINE_TO, 7, -7,
+R_LINE_TO, -2, -2,
+R_LINE_TO, -6, 7,
+R_V_LINE_TO, 2,
+CLOSE,
+R_MOVE_TO, 8, -8,
+R_LINE_TO, 2, -2,
+R_LINE_TO, -1, -1,
+R_LINE_TO, -1, 2,
+R_V_LINE_TO, 1,
+CLOSE,
+R_MOVE_TO, 0, 0,
+V_LINE_TO, 4,
+R_V_LINE_TO, 1,
+CLOSE
\ No newline at end of file
diff --git a/ash/style/system_dialog_delegate_view.cc b/ash/style/system_dialog_delegate_view.cc
index aadb8d23..d366cf2 100644
--- a/ash/style/system_dialog_delegate_view.cc
+++ b/ash/style/system_dialog_delegate_view.cc
@@ -108,6 +108,15 @@
 
 }  // namespace
 
+DEFINE_CLASS_ELEMENT_IDENTIFIER_VALUE(SystemDialogDelegateView,
+                                      kAcceptButtonIdForTesting);
+DEFINE_CLASS_ELEMENT_IDENTIFIER_VALUE(SystemDialogDelegateView,
+                                      kCancelButtonIdForTesting);
+DEFINE_CLASS_ELEMENT_IDENTIFIER_VALUE(SystemDialogDelegateView,
+                                      kDescriptionTextIdForTesting);
+DEFINE_CLASS_ELEMENT_IDENTIFIER_VALUE(SystemDialogDelegateView,
+                                      kTitleTextIdForTesting);
+
 //------------------------------------------------------------------------------
 // SystemDialogDelegateView::ButtonContainer:
 // The container includes an accept button and a cancel button. The buttons are
@@ -138,8 +147,12 @@
 
     cancel_button_->SetID(
         ViewID::VIEW_ID_STYLE_SYSTEM_DIALOG_DELEGATE_CANCEL_BUTTON);
+    cancel_button_->SetProperty(views::kElementIdentifierKey,
+                                kCancelButtonIdForTesting);
     accept_button_->SetID(
         ViewID::VIEW_ID_STYLE_SYSTEM_DIALOG_DELEGATE_ACCEPT_BUTTON);
+    accept_button_->SetProperty(views::kElementIdentifierKey,
+                                kAcceptButtonIdForTesting);
   }
 
   ButtonContainer(const ButtonContainer&) = delete;
@@ -225,6 +238,7 @@
   title_->SetAutoColorReadabilityEnabled(false);
   title_->SetEnabledColorId(kTitleColorId);
   title_->SetVisible(false);
+  title_->SetProperty(views::kElementIdentifierKey, kTitleTextIdForTesting);
 
   description_ = AddChildView(std::make_unique<views::Label>());
   SetViewLayoutSpecs(
@@ -240,6 +254,8 @@
   description_->SetAutoColorReadabilityEnabled(false);
   description_->SetEnabledColorId(kBodyColorId);
   description_->SetVisible(false);
+  description_->SetProperty(views::kElementIdentifierKey,
+                            kDescriptionTextIdForTesting);
 
   button_container_ = AddChildView(std::make_unique<ButtonContainer>(this));
   SetViewLayoutSpecs(
diff --git a/ash/style/system_dialog_delegate_view.h b/ash/style/system_dialog_delegate_view.h
index a149aed..47861b45 100644
--- a/ash/style/system_dialog_delegate_view.h
+++ b/ash/style/system_dialog_delegate_view.h
@@ -10,6 +10,7 @@
 #include "ash/ash_export.h"
 #include "ash/style/system_shadow.h"
 #include "base/functional/callback_forward.h"
+#include "ui/base/interaction/element_identifier.h"
 #include "ui/base/metadata/metadata_header_macros.h"
 #include "ui/gfx/vector_icon_types.h"
 #include "ui/views/layout/layout_types.h"
@@ -53,6 +54,11 @@
  public:
   METADATA_HEADER(SystemDialogDelegateView);
 
+  DECLARE_CLASS_ELEMENT_IDENTIFIER_VALUE(kAcceptButtonIdForTesting);
+  DECLARE_CLASS_ELEMENT_IDENTIFIER_VALUE(kCancelButtonIdForTesting);
+  DECLARE_CLASS_ELEMENT_IDENTIFIER_VALUE(kDescriptionTextIdForTesting);
+  DECLARE_CLASS_ELEMENT_IDENTIFIER_VALUE(kTitleTextIdForTesting);
+
   SystemDialogDelegateView();
   SystemDialogDelegateView(const SystemDialogDelegateView&) = delete;
   SystemDialogDelegateView& operator=(const SystemDialogDelegateView&) = delete;
diff --git a/ash/system/accessibility/accessibility_detailed_view.cc b/ash/system/accessibility/accessibility_detailed_view.cc
index 148b28f..7d51248 100644
--- a/ash/system/accessibility/accessibility_detailed_view.cc
+++ b/ash/system/accessibility/accessibility_detailed_view.cc
@@ -191,6 +191,16 @@
     UpdateFeatureState(checked, dictation_view_, dictation_top_view_);
   }
 
+  if (controller->IsColorCorrectionSettingVisibleInTray()) {
+    if (!::features::
+            AreExperimentalAccessibilityColorEnhancementSettingsEnabled()) {
+      return;
+    }
+    bool checked = controller->color_correction().enabled();
+    UpdateFeatureState(checked, color_correction_view_,
+                       color_correction_top_view_);
+  }
+
   if (controller->IsHighContrastSettingVisibleInTray()) {
     bool checked = controller->high_contrast().enabled();
     UpdateFeatureState(checked, high_contrast_view_, high_contrast_top_view_);
@@ -305,6 +315,10 @@
       controller->dictation().enabled()) {
     dictation_top_view_ = AddDictationView(container);
   }
+  if (controller->IsColorCorrectionSettingVisibleInTray() &&
+      controller->color_correction().enabled()) {
+    color_correction_top_view_ = AddColorCorrectionView(container);
+  }
   if (controller->IsHighContrastSettingVisibleInTray() &&
       controller->high_contrast().enabled()) {
     high_contrast_top_view_ = AddHighContrastView(container);
@@ -376,6 +390,10 @@
     dictation_view_ = AddDictationView(container);
   }
 
+  if (controller->IsColorCorrectionSettingVisibleInTray()) {
+    color_correction_view_ = AddColorCorrectionView(container);
+  }
+
   if (controller->IsHighContrastSettingVisibleInTray()) {
     high_contrast_view_ = AddHighContrastView(container);
   }
@@ -477,6 +495,17 @@
       checked, controller->IsEnterpriseIconVisibleForDictation());
 }
 
+HoverHighlightView* AccessibilityDetailedView::AddColorCorrectionView(
+    views::View* container) {
+  auto* controller = Shell::Get()->accessibility_controller();
+  bool checked = controller->color_correction().enabled();
+  return AddScrollListFeatureItem(
+      container, kColorCorrectionIcon,
+      l10n_util::GetStringUTF16(
+          IDS_ASH_STATUS_TRAY_ACCESSIBILITY_COLOR_CORRECTION),
+      checked, controller->IsEnterpriseIconVisibleForColorCorrection());
+}
+
 HoverHighlightView* AccessibilityDetailedView::AddHighContrastView(
     views::View* container) {
   auto* controller = Shell::Get()->accessibility_controller();
@@ -712,6 +741,14 @@
     RecordAction(new_state ? UserMetricsAction("StatusArea_DictationEnabled")
                            : UserMetricsAction("StatusArea_DictationDisabled"));
     controller->dictation().SetEnabled(new_state);
+  } else if ((view == color_correction_view_ ||
+              view == color_correction_top_view_) &&
+             !controller->IsEnterpriseIconVisibleForColorCorrection()) {
+    bool new_state = !controller->color_correction().enabled();
+    RecordAction(new_state
+                     ? UserMetricsAction("StatusArea_ColorCorrectionEnabled")
+                     : UserMetricsAction("StatusArea_ColorCorrectionDisabled"));
+    controller->color_correction().SetEnabled(new_state);
   } else if ((view == high_contrast_top_view_ || view == high_contrast_view_) &&
              !controller->IsEnterpriseIconVisibleForHighContrast()) {
     bool new_state = !controller->high_contrast().enabled();
diff --git a/ash/system/accessibility/accessibility_detailed_view.h b/ash/system/accessibility/accessibility_detailed_view.h
index 6bdc571..9c7098d0 100644
--- a/ash/system/accessibility/accessibility_detailed_view.h
+++ b/ash/system/accessibility/accessibility_detailed_view.h
@@ -11,6 +11,7 @@
 #include "ash/accessibility/accessibility_observer.h"
 #include "ash/public/cpp/session/session_observer.h"
 #include "ash/system/tray/tray_detailed_view.h"
+#include "base/allocator/partition_allocator/pointers/raw_ptr.h"
 #include "base/memory/raw_ptr.h"
 #include "components/soda/soda_installer.h"
 #include "ui/gfx/font.h"
@@ -83,6 +84,7 @@
   HoverHighlightView* AddSpokenFeedbackView(views::View* container);
   HoverHighlightView* AddSelectToSpeakView(views::View* container);
   HoverHighlightView* AddDictationView(views::View* container);
+  HoverHighlightView* AddColorCorrectionView(views::View* container);
   HoverHighlightView* AddHighContrastView(views::View* container);
   HoverHighlightView* AddScreenMagnifierView(views::View* container);
   HoverHighlightView* AddDockedMagnifierView(views::View* container);
@@ -131,6 +133,7 @@
   raw_ptr<HoverHighlightView, ExperimentalAsh> spoken_feedback_view_ = nullptr;
   raw_ptr<HoverHighlightView, ExperimentalAsh> select_to_speak_view_ = nullptr;
   raw_ptr<HoverHighlightView, ExperimentalAsh> dictation_view_ = nullptr;
+  raw_ptr<HoverHighlightView, ExperimentalAsh> color_correction_view_ = nullptr;
   raw_ptr<HoverHighlightView, ExperimentalAsh> high_contrast_view_ = nullptr;
   raw_ptr<HoverHighlightView, ExperimentalAsh> screen_magnifier_view_ = nullptr;
   raw_ptr<HoverHighlightView, ExperimentalAsh> docked_magnifier_view_ = nullptr;
@@ -155,6 +158,8 @@
   raw_ptr<HoverHighlightView, ExperimentalAsh> select_to_speak_top_view_ =
       nullptr;
   raw_ptr<HoverHighlightView, ExperimentalAsh> dictation_top_view_ = nullptr;
+  raw_ptr<HoverHighlightView, ExperimentalAsh> color_correction_top_view_ =
+      nullptr;
   raw_ptr<HoverHighlightView, ExperimentalAsh> high_contrast_top_view_ =
       nullptr;
   raw_ptr<HoverHighlightView, ExperimentalAsh> screen_magnifier_top_view_ =
diff --git a/ash/system/accessibility/accessibility_detailed_view_unittest.cc b/ash/system/accessibility/accessibility_detailed_view_unittest.cc
index fb33656..f3a91ea 100644
--- a/ash/system/accessibility/accessibility_detailed_view_unittest.cc
+++ b/ash/system/accessibility/accessibility_detailed_view_unittest.cc
@@ -24,6 +24,7 @@
 #include "components/prefs/pref_service.h"
 #include "components/soda/soda_installer_impl_chromeos.h"
 #include "media/base/media_switches.h"
+#include "ui/accessibility/accessibility_features.h"
 #include "ui/accessibility/ax_enums.mojom-shared.h"
 #include "ui/accessibility/ax_node_data.h"
 #include "ui/views/controls/label.h"
@@ -112,6 +113,11 @@
   Shell::Get()->accessibility_controller()->switch_access().SetEnabled(enabled);
 }
 
+void EnableColorCorrection(bool enabled) {
+  Shell::Get()->accessibility_controller()->color_correction().SetEnabled(
+      enabled);
+}
+
 speech::LanguageCode en_us() {
   return speech::LanguageCode::kEnUs;
 }
@@ -144,7 +150,8 @@
   AccessibilityDetailedViewTest() {
     scoped_feature_list_.InitWithFeatures(
         {media::kLiveCaption, media::kLiveCaptionSystemWideOnChromeOS,
-         ash::features::kOnDeviceSpeechRecognition},
+         ash::features::kOnDeviceSpeechRecognition,
+         ::features::kExperimentalAccessibilityColorEnhancementSettings},
         {});
   }
   AccessibilityDetailedViewTest(const AccessibilityDetailedViewTest&) = delete;
@@ -253,6 +260,10 @@
     ClickView(detailed_menu_->dictation_view_);
   }
 
+  void ClickColorCorrectionOnDetailMenu() {
+    ClickView(detailed_menu_->color_correction_view_);
+  }
+
   bool IsSpokenFeedbackMenuShownOnDetailMenu() const {
     return detailed_menu_->spoken_feedback_view_;
   }
@@ -317,6 +328,10 @@
     return detailed_menu_->switch_access_view_;
   }
 
+  bool IsColorCorrectionShownOnDetailMenu() const {
+    return detailed_menu_->color_correction_view_;
+  }
+
   // In material design we show the help button but theme it as disabled if
   // it is not possible to load the help page.
   bool IsHelpAvailableOnDetailMenu() {
@@ -434,6 +449,11 @@
                                  detailed_menu_->switch_access_view_);
   }
 
+  bool IsColorCorrectionEnabledOnDetailMenu() const {
+    return IsEnabledOnDetailMenu(controller_->color_correction().enabled(),
+                                 detailed_menu_->color_correction_view_);
+  }
+
   const char* GetDetailedViewClassName() {
     return detailed_menu_->GetClassName();
   }
@@ -491,6 +511,9 @@
   views::View* switch_access_view() const {
     return detailed_menu_->switch_access_view_;
   }
+  views::View* color_correction_view() const {
+    return detailed_menu_->color_correction_view_;
+  }
 
   // Accessors for the top views listing enabled items.
   HoverHighlightView* spoken_feedback_top_view() const {
@@ -541,6 +564,9 @@
   HoverHighlightView* switch_access_top_view() const {
     return detailed_menu_->switch_access_top_view_;
   }
+  HoverHighlightView* color_correction_top_view() const {
+    return detailed_menu_->color_correction_top_view_;
+  }
 
  private:
   // AccessibilityObserver:
@@ -592,6 +618,7 @@
   EXPECT_TRUE(has_rounded_container_parent(highlight_keyboard_focus_view()));
   EXPECT_TRUE(has_rounded_container_parent(sticky_keys_view()));
   EXPECT_TRUE(has_rounded_container_parent(switch_access_view()));
+  EXPECT_TRUE(has_rounded_container_parent(color_correction_view()));
   CloseDetailMenu();
 }
 
@@ -632,6 +659,7 @@
   EXPECT_FALSE(highlight_keyboard_focus_top_view());
   EXPECT_FALSE(sticky_keys_top_view());
   EXPECT_FALSE(switch_access_top_view());
+  EXPECT_FALSE(color_correction_top_view());
 }
 
 // Verifies that pressing the tab key moves from row to row. In particular,
@@ -645,6 +673,8 @@
   PressAndReleaseKey(ui::VKEY_TAB);
   EXPECT_TRUE(dictation_view()->HasFocus());
   PressAndReleaseKey(ui::VKEY_TAB);
+  EXPECT_TRUE(color_correction_view()->HasFocus());
+  PressAndReleaseKey(ui::VKEY_TAB);
   EXPECT_TRUE(high_contrast_view()->HasFocus());
   PressAndReleaseKey(ui::VKEY_TAB);
   EXPECT_TRUE(screen_magnifier_view()->HasFocus());
@@ -888,6 +918,19 @@
   EXPECT_FALSE(controller()->switch_access().enabled());
 }
 
+TEST_F(AccessibilityDetailedViewQsRevampTest, ColorCorrectionTopView) {
+  EnableColorCorrection(true);
+  CreateDetailedMenu();
+  ASSERT_TRUE(color_correction_top_view());
+  EXPECT_TRUE(IsSwitchToggled(color_correction_top_view()));
+  EXPECT_TRUE(IsCheckedForAccessibility(color_correction_top_view()));
+
+  ClickView(color_correction_top_view());
+  EXPECT_FALSE(IsSwitchToggled(color_correction_top_view()));
+  EXPECT_FALSE(IsCheckedForAccessibility(color_correction_top_view()));
+  EXPECT_FALSE(controller()->color_correction().enabled());
+}
+
 TEST_F(AccessibilityDetailedViewTest, CheckMenuVisibilityOnDetailMenu) {
   // Except help & settings, others should be kept the same
   // in LOGIN | NOT LOGIN | LOCKED. https://crbug.com/632107.
@@ -910,6 +953,7 @@
   EXPECT_TRUE(IsHighlightKeyboardFocusMenuShownOnDetailMenu());
   EXPECT_TRUE(IsStickyKeysMenuShownOnDetailMenu());
   EXPECT_TRUE(IsSwitchAccessShownOnDetailMenu());
+  EXPECT_TRUE(IsColorCorrectionShownOnDetailMenu());
   CloseDetailMenu();
 
   // Simulate screen lock.
@@ -933,6 +977,7 @@
   EXPECT_TRUE(IsHighlightKeyboardFocusMenuShownOnDetailMenu());
   EXPECT_TRUE(IsStickyKeysMenuShownOnDetailMenu());
   EXPECT_TRUE(IsSwitchAccessShownOnDetailMenu());
+  EXPECT_TRUE(IsColorCorrectionShownOnDetailMenu());
   CloseDetailMenu();
   UnblockUserSession();
 
@@ -957,6 +1002,7 @@
   EXPECT_TRUE(IsHighlightKeyboardFocusMenuShownOnDetailMenu());
   EXPECT_TRUE(IsStickyKeysMenuShownOnDetailMenu());
   EXPECT_TRUE(IsSwitchAccessShownOnDetailMenu());
+  EXPECT_TRUE(IsColorCorrectionShownOnDetailMenu());
   CloseDetailMenu();
   UnblockUserSession();
 }
@@ -1139,6 +1185,17 @@
   CreateDetailedMenu();
   ClickDictationOnDetailMenu();
   EXPECT_FALSE(accessibility_controller->dictation().enabled());
+
+  // Confirms that the check item toggles color correction.
+  EXPECT_FALSE(accessibility_controller->color_correction().enabled());
+
+  CreateDetailedMenu();
+  ClickColorCorrectionOnDetailMenu();
+  EXPECT_TRUE(accessibility_controller->color_correction().enabled());
+
+  CreateDetailedMenu();
+  ClickColorCorrectionOnDetailMenu();
+  EXPECT_FALSE(accessibility_controller->color_correction().enabled());
 }
 
 // Trivial test to increase code coverage.
@@ -1334,6 +1391,8 @@
   // Switch Access is currently cannot be enabled from the login screen.
   // TODO(crbug.com/1108808): Uncomment once issue is addressed.
   // EXPECT_FALSE(IsSwitchAccessEnabledOnDetailMenu());
+  // Color correction cannot be enabled from the login screen.
+  EXPECT_FALSE(IsColorCorrectionShownOnDetailMenu());
   CloseDetailMenu();
 }
 
@@ -1359,6 +1418,8 @@
   // Switch Access is currently cannot be enabled from the login screen.
   // TODO(crbug.com/1108808): Uncomment once issue is addressed.
   // EXPECT_FALSE(IsSwitchAccessEnabledOnDetailMenu());
+  // Color correction cannot be enabled from the login screen.
+  EXPECT_FALSE(IsColorCorrectionShownOnDetailMenu());
   CloseDetailMenu();
 
   // Disabling spoken feedback.
@@ -1382,6 +1443,8 @@
   // Switch Access is currently cannot be enabled from the login screen.
   // TODO(crbug.com/1108808): Uncomment once issue is addressed.
   // EXPECT_FALSE(IsSwitchAccessEnabledOnDetailMenu());
+  // Color correction cannot be enabled from the login screen.
+  EXPECT_FALSE(IsColorCorrectionShownOnDetailMenu());
   CloseDetailMenu();
 }
 
@@ -1431,6 +1494,8 @@
   // Switch Access is currently cannot be enabled from the login screen.
   // TODO(crbug.com/1108808): Uncomment once issue is addressed.
   // EXPECT_FALSE(IsSwitchAccessEnabledOnDetailMenu());
+  // Color correction cannot be enabled from the login screen.
+  EXPECT_FALSE(IsColorCorrectionShownOnDetailMenu());
   CloseDetailMenu();
 
   // Disabling select to speak.
@@ -1454,6 +1519,8 @@
   // Switch Access is currently cannot be enabled from the login screen.
   // TODO(crbug.com/1108808): Uncomment once issue is addressed.
   // EXPECT_FALSE(IsSwitchAccessEnabledOnDetailMenu());
+  // Color correction cannot be enabled from the login screen.
+  EXPECT_FALSE(IsColorCorrectionShownOnDetailMenu());
   CloseDetailMenu();
 }
 
@@ -1479,6 +1546,8 @@
   // Switch Access is currently cannot be enabled from the login screen.
   // TODO(crbug.com/1108808): Uncomment once issue is addressed.
   // EXPECT_FALSE(IsSwitchAccessEnabledOnDetailMenu());
+  // Color correction cannot be enabled from the login screen.
+  EXPECT_FALSE(IsColorCorrectionShownOnDetailMenu());
   CloseDetailMenu();
 
   // Disabling dictation.
@@ -1502,6 +1571,8 @@
   // Switch Access is currently cannot be enabled from the login screen.
   // TODO(crbug.com/1108808): Uncomment once issue is addressed.
   // EXPECT_FALSE(IsSwitchAccessEnabledOnDetailMenu());
+  // Color correction cannot be enabled from the login screen.
+  EXPECT_FALSE(IsColorCorrectionShownOnDetailMenu());
   CloseDetailMenu();
 }
 
@@ -1527,6 +1598,8 @@
   // Switch Access is currently cannot be enabled from the login screen.
   // TODO(crbug.com/1108808): Uncomment once issue is addressed.
   // EXPECT_FALSE(IsSwitchAccessEnabledOnDetailMenu());
+  // Color correction cannot be enabled from the login screen.
+  EXPECT_FALSE(IsColorCorrectionShownOnDetailMenu());
   CloseDetailMenu();
 
   // Disabling high contrast.
@@ -1550,6 +1623,8 @@
   // Switch Access is currently cannot be enabled from the login screen.
   // TODO(crbug.com/1108808): Uncomment once issue is addressed.
   // EXPECT_FALSE(IsSwitchAccessEnabledOnDetailMenu());
+  // Color correction cannot be enabled from the login screen.
+  EXPECT_FALSE(IsColorCorrectionShownOnDetailMenu());
   CloseDetailMenu();
 }
 
@@ -1575,6 +1650,8 @@
   // Switch Access is currently cannot be enabled from the login screen.
   // TODO(crbug.com/1108808): Uncomment once issue is addressed.
   // EXPECT_FALSE(IsSwitchAccessEnabledOnDetailMenu());
+  // Color correction cannot be enabled from the login screen.
+  EXPECT_FALSE(IsColorCorrectionShownOnDetailMenu());
   CloseDetailMenu();
 
   // Disabling screen magnifier.
@@ -1598,6 +1675,8 @@
   // Switch Access is currently cannot be enabled from the login screen.
   // TODO(crbug.com/1108808): Uncomment once issue is addressed.
   // EXPECT_FALSE(IsSwitchAccessEnabledOnDetailMenu());
+  // Color correction cannot be enabled from the login screen.
+  EXPECT_FALSE(IsColorCorrectionShownOnDetailMenu());
   CloseDetailMenu();
 }
 
@@ -1623,6 +1702,8 @@
   // Switch Access is currently cannot be enabled from the login screen.
   // TODO(crbug.com/1108808): Uncomment once issue is addressed.
   // EXPECT_FALSE(IsSwitchAccessEnabledOnDetailMenu());
+  // Color correction cannot be enabled from the login screen.
+  EXPECT_FALSE(IsColorCorrectionShownOnDetailMenu());
   CloseDetailMenu();
 
   // Disabling docked magnifier.
@@ -1646,6 +1727,8 @@
   // Switch Access is currently cannot be enabled from the login screen.
   // TODO(crbug.com/1108808): Uncomment once issue is addressed.
   // EXPECT_FALSE(IsSwitchAccessEnabledOnDetailMenu());
+  // Color correction cannot be enabled from the login screen.
+  EXPECT_FALSE(IsColorCorrectionShownOnDetailMenu());
   CloseDetailMenu();
 }
 
@@ -1671,6 +1754,8 @@
   // Switch Access is currently cannot be enabled from the login screen.
   // TODO(crbug.com/1108808): Uncomment once issue is addressed.
   // EXPECT_FALSE(IsSwitchAccessEnabledOnDetailMenu());
+  // Color correction cannot be enabled from the login screen.
+  EXPECT_FALSE(IsColorCorrectionShownOnDetailMenu());
   CloseDetailMenu();
 
   // Disabling large cursor.
@@ -1694,6 +1779,8 @@
   // Switch Access is currently cannot be enabled from the login screen.
   // TODO(crbug.com/1108808): Uncomment once issue is addressed.
   // EXPECT_FALSE(IsSwitchAccessEnabledOnDetailMenu());
+  // Color correction cannot be enabled from the login screen.
+  EXPECT_FALSE(IsColorCorrectionShownOnDetailMenu());
   CloseDetailMenu();
 }
 
@@ -1719,6 +1806,8 @@
   // Switch Access is currently cannot be enabled from the login screen.
   // TODO(crbug.com/1108808): Uncomment once issue is addressed.
   // EXPECT_FALSE(IsSwitchAccessEnabledOnDetailMenu());
+  // Color correction cannot be enabled from the login screen.
+  EXPECT_FALSE(IsColorCorrectionShownOnDetailMenu());
   CloseDetailMenu();
 
   // Disabling Live Caption.
@@ -1742,6 +1831,8 @@
   // Switch Access is currently cannot be enabled from the login screen.
   // TODO(crbug.com/1108808): Uncomment once issue is addressed.
   // EXPECT_FALSE(IsSwitchAccessEnabledOnDetailMenu());
+  // Color correction cannot be enabled from the login screen.
+  EXPECT_FALSE(IsColorCorrectionShownOnDetailMenu());
   CloseDetailMenu();
 }
 
@@ -1767,6 +1858,8 @@
   // Switch Access is currently cannot be enabled from the login screen.
   // TODO(crbug.com/1108808): Uncomment once issue is addressed.
   // EXPECT_FALSE(IsSwitchAccessEnabledOnDetailMenu());
+  // Color correction cannot be enabled from the login screen.
+  EXPECT_FALSE(IsColorCorrectionShownOnDetailMenu());
   CloseDetailMenu();
 
   // Disable on-screen keyboard.
@@ -1790,6 +1883,8 @@
   // Switch Access is currently cannot be enabled from the login screen.
   // TODO(crbug.com/1108808): Uncomment once issue is addressed.
   // EXPECT_FALSE(IsSwitchAccessEnabledOnDetailMenu());
+  // Color correction cannot be enabled from the login screen.
+  EXPECT_FALSE(IsColorCorrectionShownOnDetailMenu());
   CloseDetailMenu();
 }
 
@@ -1815,6 +1910,8 @@
   // Switch Access is currently cannot be enabled from the login screen.
   // TODO(crbug.com/1108808): Uncomment once issue is addressed.
   // EXPECT_FALSE(IsSwitchAccessEnabledOnDetailMenu());
+  // Color correction cannot be enabled from the login screen.
+  EXPECT_FALSE(IsColorCorrectionShownOnDetailMenu());
   CloseDetailMenu();
 
   // Disabling mono audio.
@@ -1838,6 +1935,8 @@
   // Switch Access is currently cannot be enabled from the login screen.
   // TODO(crbug.com/1108808): Uncomment once issue is addressed.
   // EXPECT_FALSE(IsSwitchAccessEnabledOnDetailMenu());
+  // Color correction cannot be enabled from the login screen.
+  EXPECT_FALSE(IsColorCorrectionShownOnDetailMenu());
   CloseDetailMenu();
 }
 
@@ -1863,6 +1962,8 @@
   // Switch Access is currently cannot be enabled from the login screen.
   // TODO(crbug.com/1108808): Uncomment once issue is addressed.
   // EXPECT_FALSE(IsSwitchAccessEnabledOnDetailMenu());
+  // Color correction cannot be enabled from the login screen.
+  EXPECT_FALSE(IsColorCorrectionShownOnDetailMenu());
   CloseDetailMenu();
 
   // Disabling caret highlight.
@@ -1886,6 +1987,8 @@
   // Switch Access is currently cannot be enabled from the login screen.
   // TODO(crbug.com/1108808): Uncomment once issue is addressed.
   // EXPECT_FALSE(IsSwitchAccessEnabledOnDetailMenu());
+  // Color correction cannot be enabled from the login screen.
+  EXPECT_FALSE(IsColorCorrectionShownOnDetailMenu());
   CloseDetailMenu();
 }
 
@@ -1911,6 +2014,8 @@
   // Switch Access is currently cannot be enabled from the login screen.
   // TODO(crbug.com/1108808): Uncomment once issue is addressed.
   // EXPECT_FALSE(IsSwitchAccessEnabledOnDetailMenu());
+  // Color correction cannot be enabled from the login screen.
+  EXPECT_FALSE(IsColorCorrectionShownOnDetailMenu());
   CloseDetailMenu();
 
   // Disabling highlight mouse cursor.
@@ -1934,6 +2039,8 @@
   // Switch Access is currently cannot be enabled from the login screen.
   // TODO(crbug.com/1108808): Uncomment once issue is addressed.
   // EXPECT_FALSE(IsSwitchAccessEnabledOnDetailMenu());
+  // Color correction cannot be enabled from the login screen.
+  EXPECT_FALSE(IsColorCorrectionShownOnDetailMenu());
   CloseDetailMenu();
 }
 
@@ -1959,6 +2066,8 @@
   // Switch Access is currently cannot be enabled from the login screen.
   // TODO(crbug.com/1108808): Uncomment once issue is addressed.
   // EXPECT_FALSE(IsSwitchAccessEnabledOnDetailMenu());
+  // Color correction cannot be enabled from the login screen.
+  EXPECT_FALSE(IsColorCorrectionShownOnDetailMenu());
   CloseDetailMenu();
 
   // Disabling highlight keyboard focus.
@@ -1982,6 +2091,8 @@
   // Switch Access is currently cannot be enabled from the login screen.
   // TODO(crbug.com/1108808): Uncomment once issue is addressed.
   // EXPECT_FALSE(IsSwitchAccessEnabledOnDetailMenu());
+  // Color correction cannot be enabled from the login screen.
+  EXPECT_FALSE(IsColorCorrectionShownOnDetailMenu());
   CloseDetailMenu();
 }
 
@@ -2007,6 +2118,8 @@
   // Switch Access is currently cannot be enabled from the login screen.
   // TODO(crbug.com/1108808): Uncomment once issue is addressed.
   // EXPECT_FALSE(IsSwitchAccessEnabledOnDetailMenu());
+  // Color correction cannot be enabled from the login screen.
+  EXPECT_FALSE(IsColorCorrectionShownOnDetailMenu());
   CloseDetailMenu();
 
   // Disabling sticky keys.
@@ -2030,6 +2143,8 @@
   // Switch Access is currently cannot be enabled from the login screen.
   // TODO(crbug.com/1108808): Uncomment once issue is addressed.
   // EXPECT_FALSE(IsSwitchAccessEnabledOnDetailMenu());
+  // Color correction cannot be enabled from the login screen.
+  EXPECT_FALSE(IsColorCorrectionShownOnDetailMenu());
   CloseDetailMenu();
 }
 
@@ -2156,6 +2271,8 @@
   // Switch Access is currently cannot be enabled from the login screen.
   // TODO(crbug.com/1108808): Uncomment once issue is addressed.
   // EXPECT_FALSE(IsSwitchAccessEnabledOnDetailMenu());
+  // Color correction cannot be enabled from the login screen.
+  EXPECT_FALSE(IsColorCorrectionShownOnDetailMenu());
   CloseDetailMenu();
 }
 
@@ -2181,6 +2298,8 @@
   // Switch Access is currently not available on the login screen.
   // TODO(crbug.com/1108808): Uncomment once issue is addressed.
   // EXPECT_FALSE(IsSwitchAccessEnabledOnDetailMenu());
+  // Color correction cannot be enabled from the login screen.
+  EXPECT_FALSE(IsColorCorrectionShownOnDetailMenu());
   CloseDetailMenu();
 
   // Disabling autoclick.
@@ -2204,6 +2323,33 @@
   // Switch Access is currently not available on the login screen.
   // TODO(crbug.com/1108808): Uncomment once issue is addressed.
   // EXPECT_FALSE(IsSwitchAccessEnabledOnDetailMenu());
+  // Color correction cannot be enabled from the login screen.
+  EXPECT_FALSE(IsColorCorrectionShownOnDetailMenu());
+  CloseDetailMenu();
+}
+
+class AccessibilityDetailedViewWithoutColorCorrectionTest
+    : public AccessibilityDetailedViewTest {
+ public:
+  AccessibilityDetailedViewWithoutColorCorrectionTest() {
+    feature_list_.InitAndDisableFeature(
+        ::features::kExperimentalAccessibilityColorEnhancementSettings);
+  }
+  AccessibilityDetailedViewWithoutColorCorrectionTest(
+      const AccessibilityDetailedViewWithoutColorCorrectionTest&) = delete;
+  AccessibilityDetailedViewWithoutColorCorrectionTest& operator=(
+      const AccessibilityDetailedViewWithoutColorCorrectionTest&) = delete;
+  ~AccessibilityDetailedViewWithoutColorCorrectionTest() override = default;
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+TEST_F(AccessibilityDetailedViewWithoutColorCorrectionTest,
+       NoColorCorrectionIfFlagNotSet) {
+  CreateDetailedMenu();
+  EXPECT_FALSE(IsColorCorrectionShownOnDetailMenu());
+  EXPECT_FALSE(color_correction_top_view());
   CloseDetailMenu();
 }
 
diff --git a/ash/system/unified/classroom_bubble_view.cc b/ash/system/unified/classroom_bubble_view.cc
index e3ff9cd..7d2cc43 100644
--- a/ash/system/unified/classroom_bubble_view.cc
+++ b/ash/system/unified/classroom_bubble_view.cc
@@ -6,6 +6,7 @@
 
 #include "ash/public/cpp/style/color_provider.h"
 #include "ash/style/ash_color_id.h"
+#include "ash/system/tray/tray_constants.h"
 #include "chromeos/constants/chromeos_features.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/chromeos/styles/cros_tokens_color_mappings.h"
@@ -26,6 +27,12 @@
   node_data->SetName(u"Glanceables Bubble Classroom View Accessible Name");
 }
 
+gfx::Size ClassroomBubbleView::CalculatePreferredSize() const {
+  // TODO(b:277268122): Scale height based on classroom contents.
+  return gfx::Size(kRevampedTrayMenuWidth - 2 * kGlanceablesLeftRightMargin,
+                   kGlanceableMinHeight - 2 * kGlanceablesVerticalMargin);
+}
+
 BEGIN_METADATA(ClassroomBubbleView, views::View)
 END_METADATA
 
diff --git a/ash/system/unified/classroom_bubble_view.h b/ash/system/unified/classroom_bubble_view.h
index 819fc6f..5dd0c4f7 100644
--- a/ash/system/unified/classroom_bubble_view.h
+++ b/ash/system/unified/classroom_bubble_view.h
@@ -23,6 +23,7 @@
 
   // views::View:
   void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
+  gfx::Size CalculatePreferredSize() const override;
 };
 
 }  // namespace ash
diff --git a/ash/system/unified/glanceable_tray_bubble_view.cc b/ash/system/unified/glanceable_tray_bubble_view.cc
index 060e5b05..6ebb5ec 100644
--- a/ash/system/unified/glanceable_tray_bubble_view.cc
+++ b/ash/system/unified/glanceable_tray_bubble_view.cc
@@ -10,7 +10,10 @@
 #include "ash/system/tray/tray_utils.h"
 #include "ash/system/unified/classroom_bubble_view.h"
 #include "ash/system/unified/tasks_bubble_view.h"
+#include "ui/compositor/layer.h"
+#include "ui/gfx/geometry/rounded_corners_f.h"
 #include "ui/views/controls/label.h"
+#include "ui/views/controls/scroll_view.h"
 
 namespace ash {
 
@@ -20,18 +23,40 @@
     : TrayBubbleView(init_params), shelf_(shelf) {}
 
 void GlanceableTrayBubbleView::UpdateBubble() {
+  scroll_view_ = AddChildView(std::make_unique<views::ScrollView>(
+      views::ScrollView::ScrollWithLayers::kEnabled));
+  scroll_view_->SetPaintToLayer();
+  scroll_view_->layer()->SetFillsBoundsOpaquely(false);
+  scroll_view_->ClipHeightTo(0, std::numeric_limits<int>::max());
+  scroll_view_->SetBackgroundColor(absl::nullopt);
+  scroll_view_->layer()->SetIsFastRoundedCorner(true);
+
+  // TODO(b:286941809): Setting rounded corners here, can break the background
+  // blur applied to child bubble views.
+  scroll_view_->layer()->SetRoundedCornerRadius(gfx::RoundedCornersF(24));
+
+  auto child_glanceable_container = std::make_unique<views::FlexLayoutView>();
+  child_glanceable_container->SetOrientation(
+      views::LayoutOrientation::kVertical);
+
   // TODO(b:277268122): set real contents for glanceables view.
   if (!tasks_bubble_view_) {
-    tasks_bubble_view_ = AddChildView(std::make_unique<TasksBubbleView>());
+    tasks_bubble_view_ = child_glanceable_container->AddChildView(
+        std::make_unique<TasksBubbleView>());
   }
 
   // TODO(b:283370562): only add teacher/student classroom glanceables when
   // the user is enrolled in courses.
   if (!classroom_bubble_view_) {
-    classroom_bubble_view_ =
-        AddChildView(std::make_unique<ClassroomBubbleView>());
+    classroom_bubble_view_ = child_glanceable_container->AddChildView(
+        std::make_unique<ClassroomBubbleView>());
+    // Add spacing between the classroom bubble and the previous bubble.
+    classroom_bubble_view_->SetProperty(views::kMarginsKey,
+                                        gfx::Insets::TLBR(8, 0, 0, 0));
   }
 
+  scroll_view_->SetContents(std::move(child_glanceable_container));
+
   int max_height = CalculateMaxTrayBubbleHeight(shelf_->GetWindow());
   SetMaxHeight(max_height);
   ChangeAnchorAlignment(shelf_->alignment());
@@ -42,9 +67,4 @@
   return true;
 }
 
-gfx::Size GlanceableTrayBubbleView::CalculatePreferredSize() const {
-  // TODO(b:277268122): Scale height based on task_items_list_view_ contents.
-  return gfx::Size(kRevampedTrayMenuWidth, kGlanceableMinHeight);
-}
-
 }  // namespace ash
diff --git a/ash/system/unified/glanceable_tray_bubble_view.h b/ash/system/unified/glanceable_tray_bubble_view.h
index 1b823be4..a9cf709 100644
--- a/ash/system/unified/glanceable_tray_bubble_view.h
+++ b/ash/system/unified/glanceable_tray_bubble_view.h
@@ -32,9 +32,6 @@
   // TrayBubbleView:
   bool CanActivate() const override;
 
-  // views::View:
-  gfx::Size CalculatePreferredSize() const override;
-
  private:
   const raw_ptr<Shelf, ExperimentalAsh> shelf_;
 
@@ -42,6 +39,9 @@
   // TODO(b:277268122): Remove and replace with actual glanceable content.
   raw_ptr<views::Label, ExperimentalAsh> title_label_ = nullptr;
 
+  // A scrollable view which contains the individual glanceables.
+  raw_ptr<views::ScrollView, ExperimentalAsh> scroll_view_ = nullptr;
+
   // Child bubble view for the tasks glanceable. Owned by bubble_view_.
   raw_ptr<TasksBubbleView, ExperimentalAsh> tasks_bubble_view_ = nullptr;
 
diff --git a/ash/system/unified/glanceable_tray_child_bubble.cc b/ash/system/unified/glanceable_tray_child_bubble.cc
index ad49d618b..37ae3ab 100644
--- a/ash/system/unified/glanceable_tray_child_bubble.cc
+++ b/ash/system/unified/glanceable_tray_child_bubble.cc
@@ -23,16 +23,15 @@
 GlanceableTrayChildBubble::GlanceableTrayChildBubble() {
   SetPaintToLayer();
   layer()->SetFillsBoundsOpaquely(false);
+  layer()->SetIsFastRoundedCorner(true);
   layer()->SetRoundedCornerRadius(
       gfx::RoundedCornersF{static_cast<float>(bubble_corner_radius)});
-  layer()->SetIsFastRoundedCorner(true);
+  // TODO(b:286941809): Setting blur here, can break the rounded corners
+  // applied to the parent scroll view.
   layer()->SetBackgroundBlur(ColorProvider::kBackgroundBlurSigma);
 
-  SetBackground(views::CreateThemedRoundedRectBackground(
-      chromeos::features::IsJellyEnabled()
-          ? static_cast<ui::ColorId>(cros_tokens::kCrosSysSystemBaseElevated)
-          : kColorAshShieldAndBase80,
-      bubble_corner_radius));
+  SetBackground(views::CreateThemedSolidBackground(
+      static_cast<ui::ColorId>(cros_tokens::kCrosSysSystemBaseElevated)));
   SetBorder(std::make_unique<views::HighlightBorder>(
       bubble_corner_radius,
       chromeos::features::IsJellyrollEnabled()
diff --git a/ash/user_education/welcome_tour/welcome_tour_controller.cc b/ash/user_education/welcome_tour/welcome_tour_controller.cc
index 8bbbd3fc..523e1a0 100644
--- a/ash/user_education/welcome_tour/welcome_tour_controller.cc
+++ b/ash/user_education/welcome_tour/welcome_tour_controller.cc
@@ -14,8 +14,10 @@
 #include "ash/user_education/user_education_types.h"
 #include "ash/user_education/user_education_util.h"
 #include "ash/user_education/welcome_tour/welcome_tour_controller_observer.h"
+#include "ash/user_education/welcome_tour/welcome_tour_dialog.h"
 #include "ash/user_education/welcome_tour/welcome_tour_scrim.h"
 #include "base/check_op.h"
+#include "base/functional/bind.h"
 #include "components/user_education/common/help_bubble.h"
 #include "components/user_education/common/tutorial_description.h"
 #include "ui/base/interaction/element_identifier.h"
@@ -73,7 +75,7 @@
   g_instance = this;
 
   session_observation_.Observe(Shell::Get()->session_controller());
-  MaybeStartTutorial();
+  MaybeShowDialog();
 }
 
 WelcomeTourController::~WelcomeTourController() {
@@ -215,7 +217,7 @@
 
 void WelcomeTourController::OnActiveUserSessionChanged(
     const AccountId& account_id) {
-  MaybeStartTutorial();
+  MaybeShowDialog();
 }
 
 void WelcomeTourController::OnChromeTerminating() {
@@ -224,20 +226,36 @@
 
 void WelcomeTourController::OnSessionStateChanged(
     session_manager::SessionState session_state) {
-  MaybeStartTutorial();
+  MaybeShowDialog();
 }
 
-void WelcomeTourController::MaybeStartTutorial() {
+void WelcomeTourController::MaybeShowDialog() {
   // NOTE: User education in Ash is currently only supported for the primary
   // user profile. This is a self-imposed restriction.
   if (!user_education_util::IsPrimaryAccountActive()) {
     return;
   }
 
-  // We can stop observations since we only observe sessions in order to start
-  // the tutorial when the primary user session is activated for the first time.
+  // We can stop observations since we only observe sessions in order to show
+  // the dialog when the primary user session is activated for the first time.
   session_observation_.Reset();
 
+  WelcomeTourDialog::CreateAndShow(
+      /*accept_callback=*/base::BindOnce(&WelcomeTourController::StartTutorial,
+                                         weak_ptr_factory_.GetWeakPtr()),
+      /*cancel_callback=*/
+      base::BindOnce(&WelcomeTourController::OnWelcomeTourEnded,
+                     weak_ptr_factory_.GetWeakPtr()),
+      /*close_callback=*/
+      base::BindOnce(&WelcomeTourController::OnWelcomeTourEnded,
+                     weak_ptr_factory_.GetWeakPtr()));
+
+  // `WelcomeTourDialog` is part of the Welcome Tour. Therefore, when the dialog
+  // shows, the tour has indeed been started.
+  OnWelcomeTourStarted();
+}
+
+void WelcomeTourController::StartTutorial() {
   // NOTE: It is theoretically possible for the tutorial to outlive `this`
   // controller during the destruction sequence.
   UserEducationController::Get()->StartTutorial(
@@ -249,10 +267,6 @@
       /*aborted_callback=*/
       base::BindOnce(&WelcomeTourController::OnWelcomeTourEnded,
                      weak_ptr_factory_.GetWeakPtr()));
-
-  // The attempt to start the tutorial above is guaranteed to succeed or crash.
-  // If this line of code is reached, the tutorial has indeed been started.
-  OnWelcomeTourStarted();
 }
 
 // TODO(http://b/277091006): Stabilize app launches.
diff --git a/ash/user_education/welcome_tour/welcome_tour_controller.h b/ash/user_education/welcome_tour/welcome_tour_controller.h
index c53b406..6c7816b 100644
--- a/ash/user_education/welcome_tour/welcome_tour_controller.h
+++ b/ash/user_education/welcome_tour/welcome_tour_controller.h
@@ -54,8 +54,12 @@
   void OnChromeTerminating() override;
   void OnSessionStateChanged(session_manager::SessionState) override;
 
-  // Starts the Welcome Tour tutorial iff the primary user session is active.
-  void MaybeStartTutorial();
+  // Shows the Welcome Tour dialog iff the primary user session is active.
+  void MaybeShowDialog();
+
+  // Starts the Welcome Tour tutorial. The tutorial should start when the accept
+  // button of the Welcome Tour dialog is clicked.
+  void StartTutorial();
 
   // Invoked when the Welcome Tour is started/ended. The latter is called
   // regardless of whether the tour was completed or aborted.
@@ -71,12 +75,12 @@
   base::ObserverList<WelcomeTourControllerObserver> observer_list_;
 
   // Sessions are observed only until the primary user session is activated for
-  // the first time at which point the Welcome Tour tutorial is started.
+  // the first time at which point the Welcome Tour dialog is shown.
   base::ScopedObservation<SessionController, SessionObserver>
       session_observation_{this};
 
-  // It is theoretically possible for the Welcome Tour tutorial to outlive
-  // `this` controller during the destruction sequence.
+  // It is theoretically possible for the Welcome Tour dialog/tutorial to
+  // outlive `this` controller during the destruction sequence.
   base::WeakPtrFactory<WelcomeTourController> weak_ptr_factory_{this};
 };
 
diff --git a/ash/user_education/welcome_tour/welcome_tour_controller_unittest.cc b/ash/user_education/welcome_tour/welcome_tour_controller_unittest.cc
index 64bbd6a..f8525c0 100644
--- a/ash/user_education/welcome_tour/welcome_tour_controller_unittest.cc
+++ b/ash/user_education/welcome_tour/welcome_tour_controller_unittest.cc
@@ -18,6 +18,7 @@
 #include "ash/user_education/user_education_util.h"
 #include "ash/user_education/welcome_tour/mock_welcome_tour_controller_observer.h"
 #include "ash/user_education/welcome_tour/welcome_tour_controller_observer.h"
+#include "ash/user_education/welcome_tour/welcome_tour_dialog.h"
 #include "ash/user_education/welcome_tour/welcome_tour_test_util.h"
 #include "base/functional/callback.h"
 #include "base/functional/overloaded.h"
@@ -33,6 +34,7 @@
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/abseil-cpp/absl/types/variant.h"
+#include "ui/views/test/widget_test.h"
 
 namespace ash {
 namespace {
@@ -192,19 +194,20 @@
                              /*has_next_button=*/false))))));
 }
 
-// Verifies that the Welcome Tour tutorial is started when the primary user
-// session is first activated and then never again, as well as that start/end
-// events are propagated to observers appropriately.
-TEST_F(WelcomeTourControllerTest, StartsTutorialAndPropagatesEvents) {
-  AccountId primary_account_id = AccountId::FromUserEmail("primary@test");
-  AccountId secondary_account_id = AccountId::FromUserEmail("secondary@test");
+// Verifies that the Welcome Tour is started when the primary user session is
+// first activated and then never again, as well as that start/end events are
+// propagated to observers appropriately.
+TEST_F(WelcomeTourControllerTest, StartsTourAndPropagatesEvents) {
+  const AccountId primary_account_id = AccountId::FromUserEmail("primary@test");
+  const AccountId secondary_account_id =
+      AccountId::FromUserEmail("secondary@test");
 
   // Ensure controller exists.
-  auto* welcome_tour_controller = WelcomeTourController::Get();
+  auto* const welcome_tour_controller = WelcomeTourController::Get();
   ASSERT_TRUE(welcome_tour_controller);
 
   // Ensure delegate exists and disallow any unexpected tutorial starts.
-  auto* user_education_delegate = this->user_education_delegate();
+  auto* const user_education_delegate = this->user_education_delegate();
   ASSERT_TRUE(user_education_delegate);
   EXPECT_CALL(*user_education_delegate, StartTutorial).Times(0);
 
@@ -215,15 +218,22 @@
   observation.Observe(welcome_tour_controller);
 
   // Add a primary and secondary user session. This should *not* trigger the
-  // Welcome Tour tutorial to start.
-  auto* session_controller_client = GetSessionControllerClient();
+  // Welcome Tour to start.
+  auto* const session_controller_client = GetSessionControllerClient();
   session_controller_client->AddUserSession(primary_account_id.GetUserEmail());
   session_controller_client->AddUserSession(
       secondary_account_id.GetUserEmail());
 
-  // Activate the primary user session. This *should* trigger the Welcome Tour
-  // tutorial to start as well as notify observers. Note that completed/aborted
-  // callbacks are cached for later verification.
+  // Activate the primary user session. The shown dialog marks the start of the
+  // Welcome Tour and the observers are notified.
+  EXPECT_CALL(observer, OnWelcomeTourStarted);
+  session_controller_client->SetSessionState(SessionState::ACTIVE);
+  EXPECT_TRUE(WelcomeTourDialog::Get());
+  testing::Mock::VerifyAndClearExpectations(&observer);
+
+  // Click `accept_button`. This *should* trigger the Welcome Tour tutorial to
+  // start. Note that the tutorial completed/aborted callbacks are cached for
+  // later verification.
   base::OnceClosure ended_callbacks[2u];
   EXPECT_CALL(
       *user_education_delegate,
@@ -233,23 +243,32 @@
                     /*completed_callback=*/_,
                     /*aborted_callback=*/_))
       .WillOnce(MoveArgs<3, 4>(&ended_callbacks[0u], &ended_callbacks[1u]));
-  EXPECT_CALL(observer, OnWelcomeTourStarted);
-  session_controller_client->SetSessionState(SessionState::ACTIVE);
+  const views::View* const accept_button = GetDialogAcceptButton();
+  ASSERT_TRUE(accept_button);
+  LeftClickOn(accept_button);
   testing::Mock::VerifyAndClearExpectations(user_education_delegate);
-  testing::Mock::VerifyAndClearExpectations(&observer);
+
+  // Wait until `welcome_tour_dialog` gets destroyed.
+  views::test::WidgetDestroyedWaiter(WelcomeTourDialog::Get()->GetWidget())
+      .Wait();
+  EXPECT_FALSE(WelcomeTourDialog::Get());
 
   // Disallow any unexpected tutorial starts.
   EXPECT_CALL(*user_education_delegate, StartTutorial).Times(0);
 
   // Switch to the secondary user session and back again. This should *not*
-  // trigger the Welcome Tour tutorial to start.
+  // either show the dialog or start the Welcome Tour tutorial.
   session_controller_client->SwitchActiveUser(secondary_account_id);
+  EXPECT_FALSE(WelcomeTourDialog::Get());
   session_controller_client->SwitchActiveUser(primary_account_id);
+  EXPECT_FALSE(WelcomeTourDialog::Get());
 
   // Deactivate and then reactivate the primary user session. This should *not*
-  // trigger the Welcome Tour tutorial to start.
+  // either show the dialog or start the Welcome Tour tutorial.
   session_controller_client->SetSessionState(SessionState::LOCKED);
+  EXPECT_FALSE(WelcomeTourDialog::Get());
   session_controller_client->SetSessionState(SessionState::ACTIVE);
+  EXPECT_FALSE(WelcomeTourDialog::Get());
 
   // Verify that the same event is propagated to observers regardless of whether
   // user education services in the browser indicate the tour was completed or
@@ -262,6 +281,27 @@
   }
 }
 
+// Verifies that the Welcome Tour ends without starting the tutorial after
+// clicking the dialog cancel button.
+TEST_F(WelcomeTourControllerTest, CancelsTourAndPropagatesEvents) {
+  SimulateUserLogin("primary@test");
+
+  // Observe the `WelcomeTourController` for end events.
+  StrictMock<MockWelcomeTourControllerObserver> observer;
+  base::ScopedObservation<WelcomeTourController, WelcomeTourControllerObserver>
+      observation{&observer};
+  observation.Observe(WelcomeTourController::Get());
+
+  base::test::TestFuture<void> ended_future;
+  EXPECT_CALL(observer, OnWelcomeTourEnded)
+      .WillOnce(RunOnceClosure(ended_future.GetCallback()));
+
+  const views::View* const cancel_button = GetDialogCancelButton();
+  ASSERT_TRUE(cancel_button);
+  LeftClickOn(cancel_button);
+  EXPECT_TRUE(ended_future.Wait());
+}
+
 // WelcomeTourControllerRunTest ------------------------------------------------
 
 // Base class for tests of the `WelcomeTourController` that run the Welcome
@@ -295,8 +335,8 @@
     EXPECT_CALL(observer, OnWelcomeTourEnded)
         .WillOnce(RunOnceClosure(ended_future.GetCallback()));
 
-    // When the Welcome Tour is started, cache the callback to invoke to
-    // complete the tutorial.
+    // When the Welcome Tour tutorial is started, cache the callback to invoke
+    // to complete the tutorial.
     base::OnceClosure completed_callback;
     EXPECT_CALL(
         *delegate,
@@ -308,6 +348,11 @@
     SimulateUserLogin("primary@test");
     EXPECT_TRUE(started_future.Wait());
 
+    // Click the dialog's accept button to start the tutorial.
+    const views::View* const accept_button = GetDialogAcceptButton();
+    ASSERT_TRUE(accept_button);
+    LeftClickOn(accept_button);
+
     // Invoke the `in_progress_callback` so that tests can assert expectations
     // while the Welcome Tour is in progress.
     std::move(in_progress_callback).Run();
diff --git a/ash/user_education/welcome_tour/welcome_tour_dialog.cc b/ash/user_education/welcome_tour/welcome_tour_dialog.cc
index 319cec9e8..5ab132cf 100644
--- a/ash/user_education/welcome_tour/welcome_tour_dialog.cc
+++ b/ash/user_education/welcome_tour/welcome_tour_dialog.cc
@@ -6,6 +6,7 @@
 
 #include <utility>
 
+#include "ash/constants/ash_features.h"
 #include "ash/public/cpp/resources/grit/ash_public_unscaled_resources.h"
 #include "ash/public/cpp/shell_window_ids.h"
 #include "ash/shell.h"
@@ -14,6 +15,7 @@
 #include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/base/resource/resource_bundle.h"
 #include "ui/views/controls/image_view.h"
+#include "ui/views/view_class_properties.h"
 #include "ui/views/widget/widget.h"
 
 namespace ash {
@@ -29,13 +31,19 @@
 
 // WelcomeTourDialog -----------------------------------------------------------
 
+DEFINE_CLASS_ELEMENT_IDENTIFIER_VALUE(WelcomeTourDialog,
+                                      kWelcomeTourDialogElementIdForTesting);
+
 // static
-void WelcomeTourDialog::CreateAndShow(
-    base::OnceClosure start_tutorial_callback) {
+void WelcomeTourDialog::CreateAndShow(base::OnceClosure accept_callback,
+                                      base::OnceClosure cancel_callback,
+                                      base::OnceClosure close_callback) {
   views::Widget::InitParams params;
   params.parent = Shell::GetPrimaryRootWindow()->GetChildById(
       kShellWindowId_HelpBubbleContainer);
-  params.delegate = new WelcomeTourDialog(std::move(start_tutorial_callback));
+  params.delegate = new WelcomeTourDialog(std::move(accept_callback),
+                                          std::move(cancel_callback),
+                                          std::move(close_callback));
   params.type = views::Widget::InitParams::TYPE_WINDOW_FRAMELESS;
 
   auto* widget = new views::Widget;
@@ -51,10 +59,10 @@
   return g_instance;
 }
 
-WelcomeTourDialog::WelcomeTourDialog(
-    base::OnceClosure start_tutorial_callback) {
-  // TODO(http://b/285027636): Check the Welcome Tour feature flag after this
-  // class is integrated with the Welcome Tour tutorial.
+WelcomeTourDialog::WelcomeTourDialog(base::OnceClosure accept_callback,
+                                     base::OnceClosure cancel_callback,
+                                     base::OnceClosure close_callback) {
+  CHECK(features::IsWelcomeTourEnabled());
 
   CHECK_EQ(g_instance, nullptr);
   g_instance = this;
@@ -64,11 +72,15 @@
   views::Builder<SystemDialogDelegateView>(this)
       .SetAcceptButtonText(l10n_util::GetStringUTF16(
           IDS_ASH_WELCOME_TOUR_DIALOG_ACCEPT_BUTTON_TEXT))
-      .SetAcceptCallback(std::move(start_tutorial_callback))
+      .SetAcceptCallback(std::move(accept_callback))
       .SetCancelButtonText(l10n_util::GetStringUTF16(
           IDS_ASH_WELCOME_TOUR_DIALOG_CANCEL_BUTTON_TEXT))
+      .SetCancelCallback(std::move(cancel_callback))
+      .SetCloseCallback(std::move(close_callback))
       .SetDescription(l10n_util::GetStringUTF16(
           IDS_ASH_WELCOME_TOUR_DIALOG_DESCRIPTION_TEXT))
+      .SetProperty(views::kElementIdentifierKey,
+                   kWelcomeTourDialogElementIdForTesting)
       .SetTitleText(
           l10n_util::GetStringUTF16(IDS_ASH_WELCOME_TOUR_DIALOG_TITLE_TEXT))
       .SetTopContentView(views::Builder<views::ImageView>()
diff --git a/ash/user_education/welcome_tour/welcome_tour_dialog.h b/ash/user_education/welcome_tour/welcome_tour_dialog.h
index d8a0e73..e03c642 100644
--- a/ash/user_education/welcome_tour/welcome_tour_dialog.h
+++ b/ash/user_education/welcome_tour/welcome_tour_dialog.h
@@ -8,19 +8,27 @@
 #include "ash/ash_export.h"
 #include "ash/style/system_dialog_delegate_view.h"
 #include "base/functional/callback_forward.h"
+#include "ui/base/interaction/element_identifier.h"
 
 namespace ash {
 
-// A singleton dialog view where a user can choose to start the Welcome Tour
-// tutorial. Used if and only if the Welcome Tour feature is enabled.
+// A singleton dialog view which serves as a part of the Welcome Tour. From this
+// dialog, a user can choose to accept or cancel the Welcome Tour tutorial. Used
+// if and only if the Welcome Tour feature is enabled.
 class ASH_EXPORT WelcomeTourDialog : public SystemDialogDelegateView {
  public:
   METADATA_HEADER(WelcomeTourDialog);
 
+  DECLARE_CLASS_ELEMENT_IDENTIFIER_VALUE(kWelcomeTourDialogElementIdForTesting);
+
   // Creates and shows the Welcome Tour dialog at the center of the primary
-  // display. `start_tutorial_callback` is the callback that runs to start the
-  // Welcome Tour tutorial.
-  static void CreateAndShow(base::OnceClosure start_tutorial_callback);
+  // display. `accept_callback` is the callback that runs when the accept button
+  // is clicked. `cancel_callback` is the callback that runs when the cancel
+  // button is clicked. `close_callback` is the callback that runs when a user
+  // closes the dialog without clicking the accept button or the cancel button.
+  static void CreateAndShow(base::OnceClosure accept_callback,
+                            base::OnceClosure cancel_callback,
+                            base::OnceClosure close_callback);
 
   // Returns a pointer to the `WelcomeTourDialog` instance. Returns `nullptr` if
   // the instance does not exist.
@@ -31,7 +39,9 @@
   ~WelcomeTourDialog() override;
 
  private:
-  explicit WelcomeTourDialog(base::OnceClosure start_tutorial_callback);
+  WelcomeTourDialog(base::OnceClosure accept_callback,
+                    base::OnceClosure cancel_callback,
+                    base::OnceClosure close_callback);
 };
 
 }  // namespace ash
diff --git a/ash/user_education/welcome_tour/welcome_tour_dialog_pixeltest.cc b/ash/user_education/welcome_tour/welcome_tour_dialog_pixeltest.cc
index bd25769..0cd5324 100644
--- a/ash/user_education/welcome_tour/welcome_tour_dialog_pixeltest.cc
+++ b/ash/user_education/welcome_tour/welcome_tour_dialog_pixeltest.cc
@@ -4,31 +4,38 @@
 
 #include "ash/user_education/welcome_tour/welcome_tour_dialog.h"
 
-#include "ash/test/ash_test_base.h"
+#include "ash/constants/ash_features.h"
 #include "ash/test/pixel/ash_pixel_differ.h"
-#include "base/functional/callback_helpers.h"
+#include "ash/user_education/user_education_ash_test_base.h"
+#include "base/test/scoped_feature_list.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace ash {
 
-class WelcomeTourDialogPixelTest : public AshTestBase {
+class WelcomeTourDialogPixelTest : public UserEducationAshTestBase {
  private:
   // UserEducationAshTestBase:
   absl::optional<pixel_test::InitParams> CreatePixelTestInitParams()
       const override {
     return pixel_test::InitParams();
   }
+
+  void SetUp() override {
+    scoped_feature_list_.InitAndEnableFeature(features::kWelcomeTour);
+    UserEducationAshTestBase::SetUp();
+    SimulateUserLogin("primary@test");
+  }
+
+  base::test::ScopedFeatureList scoped_feature_list_;
 };
 
 TEST_F(WelcomeTourDialogPixelTest, Appearance) {
-  WelcomeTourDialog::CreateAndShow(
-      /*start_tutorial_callback=*/base::DoNothing());
   ASSERT_TRUE(WelcomeTourDialog::Get());
 
   // Take a screenshot of the Welcome Tour dialog.
   EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
       "welcome_tour_dialog",
-      /*revision_number=*/0, WelcomeTourDialog::Get()));
+      /*revision_number=*/1, WelcomeTourDialog::Get()));
 }
 
 }  // namespace ash
diff --git a/ash/user_education/welcome_tour/welcome_tour_dialog_unittest.cc b/ash/user_education/welcome_tour/welcome_tour_dialog_unittest.cc
index 811432c..34206a03 100644
--- a/ash/user_education/welcome_tour/welcome_tour_dialog_unittest.cc
+++ b/ash/user_education/welcome_tour/welcome_tour_dialog_unittest.cc
@@ -4,29 +4,43 @@
 
 #include "ash/user_education/welcome_tour/welcome_tour_dialog.h"
 
-#include "ash/public/cpp/ash_view_ids.h"
-#include "ash/test/ash_test_base.h"
+#include "ash/constants/ash_features.h"
+#include "ash/user_education/user_education_ash_test_base.h"
+#include "ash/user_education/welcome_tour/welcome_tour_test_util.h"
 #include "base/test/mock_callback.h"
+#include "base/test/scoped_feature_list.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/interaction/expect_call_in_scope.h"
 #include "ui/views/test/widget_test.h"
 
 namespace ash {
 
-using WelcomeTourDialogTest = AshTestBase;
+// The test suite to check the dialog's features that are independent of the
+// Welcome Tour.
+class WelcomeTourDialogTest : public UserEducationAshTestBase {
+ public:
+  WelcomeTourDialogTest() {
+    scoped_feature_list_.InitAndEnableFeature(features::kWelcomeTour);
+  }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
 
 TEST_F(WelcomeTourDialogTest, OnAcceptButtonClicked) {
-  base::MockOnceClosure mock_start_tutorial;
-  WelcomeTourDialog::CreateAndShow(mock_start_tutorial.Get());
+  UNCALLED_MOCK_CALLBACK(base::OnceClosure, accept_callback);
+  UNCALLED_MOCK_CALLBACK(base::OnceClosure, cancel_callback);
+  UNCALLED_MOCK_CALLBACK(base::OnceClosure, close_callback);
+  WelcomeTourDialog::CreateAndShow(accept_callback.Get(), cancel_callback.Get(),
+                                   close_callback.Get());
   auto* const welcome_tour_dialog = WelcomeTourDialog::Get();
   ASSERT_TRUE(welcome_tour_dialog);
 
-  // Click the accept button. `mock_start_tutorial` should be called.
-  views::View* const accept_button = welcome_tour_dialog->GetViewByID(
-      ViewID::VIEW_ID_STYLE_SYSTEM_DIALOG_DELEGATE_ACCEPT_BUTTON);
+  // Click the accept button. `accept_callback` should be called.
+  const views::View* const accept_button = GetDialogAcceptButton();
   ASSERT_TRUE(accept_button);
-  EXPECT_CALL(mock_start_tutorial, Run).Times(1);
-  LeftClickOn(accept_button);
+  EXPECT_CALL_IN_SCOPE(accept_callback, Run, LeftClickOn(accept_button));
 
   // Wait until `welcome_tour_dialog` gets destroyed.
   views::test::WidgetDestroyedWaiter(welcome_tour_dialog->GetWidget()).Wait();
@@ -34,17 +48,37 @@
 }
 
 TEST_F(WelcomeTourDialogTest, OnCancelButtonClicked) {
-  base::MockOnceClosure mock_start_tutorial;
-  WelcomeTourDialog::CreateAndShow(mock_start_tutorial.Get());
+  UNCALLED_MOCK_CALLBACK(base::OnceClosure, accept_callback);
+  UNCALLED_MOCK_CALLBACK(base::OnceClosure, cancel_callback);
+  UNCALLED_MOCK_CALLBACK(base::OnceClosure, close_callback);
+  WelcomeTourDialog::CreateAndShow(accept_callback.Get(), cancel_callback.Get(),
+                                   close_callback.Get());
   auto* const welcome_tour_dialog = WelcomeTourDialog::Get();
   ASSERT_TRUE(welcome_tour_dialog);
 
-  // Click the cancel button. `mock_start_tutorial` should NOT be called.
-  views::View* const cancel_button = welcome_tour_dialog->GetViewByID(
-      ViewID::VIEW_ID_STYLE_SYSTEM_DIALOG_DELEGATE_CANCEL_BUTTON);
+  // Click the cancel button. `cancel_callback` should be called.
+  const views::View* const cancel_button = GetDialogCancelButton();
   ASSERT_TRUE(cancel_button);
-  EXPECT_CALL(mock_start_tutorial, Run).Times(0);
-  LeftClickOn(cancel_button);
+  EXPECT_CALL_IN_SCOPE(cancel_callback, Run, LeftClickOn(cancel_button));
+
+  // Wait until `welcome_tour_dialog` gets destroyed.
+  views::test::WidgetDestroyedWaiter(welcome_tour_dialog->GetWidget()).Wait();
+  EXPECT_FALSE(WelcomeTourDialog::Get());
+}
+
+TEST_F(WelcomeTourDialogTest, OnDialogClosedWithoutButtonClicked) {
+  UNCALLED_MOCK_CALLBACK(base::OnceClosure, accept_callback);
+  UNCALLED_MOCK_CALLBACK(base::OnceClosure, cancel_callback);
+  UNCALLED_MOCK_CALLBACK(base::OnceClosure, close_callback);
+  WelcomeTourDialog::CreateAndShow(accept_callback.Get(), cancel_callback.Get(),
+                                   close_callback.Get());
+  auto* const welcome_tour_dialog = WelcomeTourDialog::Get();
+  ASSERT_TRUE(welcome_tour_dialog);
+
+  // Close the dialog without clicking any dialog buttons. `close_callback`
+  // should be called.
+  EXPECT_CALL_IN_SCOPE(close_callback, Run,
+                       welcome_tour_dialog->GetWidget()->Close());
 
   // Wait until `welcome_tour_dialog` gets destroyed.
   views::test::WidgetDestroyedWaiter(welcome_tour_dialog->GetWidget()).Wait();
diff --git a/ash/user_education/welcome_tour/welcome_tour_test_util.cc b/ash/user_education/welcome_tour/welcome_tour_test_util.cc
index e8a9d93..661d5868 100644
--- a/ash/user_education/welcome_tour/welcome_tour_test_util.cc
+++ b/ash/user_education/welcome_tour/welcome_tour_test_util.cc
@@ -8,10 +8,12 @@
 #include <vector>
 
 #include "ash/display/window_tree_host_manager.h"
+#include "ash/public/cpp/ash_view_ids.h"
 #include "ash/public/cpp/shell_window_ids.h"
 #include "ash/root_window_controller.h"
 #include "ash/shell.h"
 #include "ash/style/ash_color_provider_source.h"
+#include "ash/user_education/welcome_tour/welcome_tour_dialog.h"
 #include "ash/user_education/welcome_tour/welcome_tour_scrim.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -97,4 +99,20 @@
   }
 }
 
+const views::View* GetDialogAcceptButton() {
+  if (auto* const dialog = WelcomeTourDialog::Get()) {
+    return dialog->GetViewByID(
+        ViewID::VIEW_ID_STYLE_SYSTEM_DIALOG_DELEGATE_ACCEPT_BUTTON);
+  }
+  return nullptr;
+}
+
+const views::View* GetDialogCancelButton() {
+  if (auto* const dialog = WelcomeTourDialog::Get()) {
+    return dialog->GetViewByID(
+        ViewID::VIEW_ID_STYLE_SYSTEM_DIALOG_DELEGATE_CANCEL_BUTTON);
+  }
+  return nullptr;
+}
+
 }  // namespace ash
diff --git a/ash/user_education/welcome_tour/welcome_tour_test_util.h b/ash/user_education/welcome_tour/welcome_tour_test_util.h
index 427d47f..169d9b8 100644
--- a/ash/user_education/welcome_tour/welcome_tour_test_util.h
+++ b/ash/user_education/welcome_tour/welcome_tour_test_util.h
@@ -7,11 +7,23 @@
 
 #include "ash/ash_export.h"
 
+namespace views {
+class View;
+}  // namespace views
+
 namespace ash {
 
 // Expects that scrims `exist` on all root windows with expected properties.
 ASH_EXPORT void ExpectScrimsOnAllRootWindows(bool exist);
 
+// Returns the Welcome Tour dialog's accept button. If the dialog does not
+// exist, returns `nullptr`.
+ASH_EXPORT const views::View* GetDialogAcceptButton();
+
+// Returns the Welcome Tour dialog's cancel button. If the dialog does not
+// exist, returns `nullptr`.
+ASH_EXPORT const views::View* GetDialogCancelButton();
+
 }  // namespace ash
 
 #endif  // ASH_USER_EDUCATION_WELCOME_TOUR_WELCOME_TOUR_TEST_UTIL_H_
diff --git a/ash/webui/common/resources/bluetooth/bluetooth_base_page.html b/ash/webui/common/resources/bluetooth/bluetooth_base_page.html
index 6895514..ae173d6 100644
--- a/ash/webui/common/resources/bluetooth/bluetooth_base_page.html
+++ b/ash/webui/common/resources/bluetooth/bluetooth_base_page.html
@@ -14,6 +14,11 @@
                                            var(--cros-second-tone-opacity));
   }
 
+  :host-context(body.jelly-enabled) paper-progress {
+    --paper-progress-active-color: var(--cros-color-primary);
+    --paper-progress-container-color: var(--cros-highlight-color);
+  }
+
   #buttonBar {
     align-self: flex-end;
     margin-top: 16px;
diff --git a/ash/webui/personalization_app/resources/common/icons.html b/ash/webui/personalization_app/resources/common/icons.html
index 73f0a969..552fb29 100644
--- a/ash/webui/personalization_app/resources/common/icons.html
+++ b/ash/webui/personalization_app/resources/common/icons.html
@@ -147,11 +147,12 @@
         <path d="M8.854 13.812L14.729 7.93801L13.5 6.72901L8.875 11.354L6.521 9.00001L5.292 10.229L8.854 13.812ZM10 18.333C8.84733 18.333 7.764 18.1143 6.75 17.677C5.736 17.2397 4.854 16.646 4.104 15.896C3.354 15.146 2.76033 14.264 2.323 13.25C1.88567 12.236 1.667 11.1527 1.667 10C1.667 8.84734 1.88567 7.76401 2.323 6.75001C2.76033 5.73601 3.354 4.85401 4.104 4.10401C4.854 3.35401 5.736 2.76034 6.75 2.32301C7.764 1.88567 8.84733 1.66701 10 1.66701C11.1527 1.66701 12.236 1.88567 13.25 2.32301C14.264 2.76034 15.146 3.35401 15.896 4.10401C16.646 4.85401 17.2397 5.73601 17.677 6.75001C18.1143 7.76401 18.333 8.84734 18.333 10C18.333 11.1527 18.1143 12.236 17.677 13.25C17.2397 14.264 16.646 15.146 15.896 15.896C15.146 16.646 14.264 17.2397 13.25 17.677C12.236 18.1143 11.1527 18.333 10 18.333ZM10 16.583C11.8193 16.583 13.3713 15.9407 14.656 14.656C15.9407 13.3713 16.583 11.8193 16.583 10C16.583 8.18067 15.9407 6.62867 14.656 5.34401C13.3713 4.05934 11.8193 3.41701 10 3.41701C8.18067 3.41701 6.62867 4.05934 5.344 5.34401C4.05933 6.62867 3.417 8.18067 3.417 10C3.417 11.8193 4.05933 13.3713 5.344 14.656C6.62867 15.9407 8.18067 16.583 10 16.583Z">
         </path>
       </g>
-      <g id="managed">
+      <!-- TODO(b/286908871): delete after Jelly is launched -->
+      <g id="managed-old">
         <path d="M2 17V3H12V7H18V17H2ZM8 9H10V11H8V9ZM12 11H14V13H12V15H16V9H12V11ZM8 13H10V15H8V13ZM6 13H4V15H6V13ZM6 9H4V11H6V9ZM8 5H10V7H8V5ZM6 5H4V7H6V5Z">
         </path>
       </g>
-      <g id="managed2">
+      <g id="managed">
         <path d="M 10.230469 4 L 10.230469 0 L 0.488281 0 L 0.488281 18 L 19.972656 18 L 19.972656 4 Z M 8.28125 16 L 2.4375 16 L 2.4375 14 L 8.28125 14 Z M 8.28125 12 L 2.4375 12 L 2.4375 10 L 8.28125 10 Z M 8.28125 8 L 2.4375 8 L 2.4375 6 L 8.28125 6 Z M 8.28125 4 L 2.4375 4 L 2.4375 2 L 8.28125 2 Z M 18.027344 16 L 10.230469 16 L 10.230469 6 L 18.027344 6 Z M 16.078125 8 L 12.179688 8 L 12.179688 10 L 16.078125 10 Z M 16.078125 12 L 12.179688 12 L 12.179688 14 L 16.078125 14 Z M 16.078125 12">
         </path>
       </g>
diff --git a/ash/webui/personalization_app/resources/js/user/user_preview_element.html b/ash/webui/personalization_app/resources/js/user/user_preview_element.html
index 02969c9..73ac49a 100644
--- a/ash/webui/personalization_app/resources/js/user/user_preview_element.html
+++ b/ash/webui/personalization_app/resources/js/user/user_preview_element.html
@@ -213,11 +213,11 @@
             alt="$i18n{managedSetting}" title="$i18n{managedSetting}">
         <div id="enterpriseIconContainer">
           <template is="dom-if" if="[[!isPersonalizationJellyEnabled_]]">
-            <iron-icon icon="personalization:managed">
+            <iron-icon icon="personalization:managed-old">
             </iron-icon>
           </template>
           <template is="dom-if" if="[[isPersonalizationJellyEnabled_]]">
-            <iron-icon icon="personalization:managed2"
+            <iron-icon icon="personalization:managed"
                 title$="$i18n{managedSetting}">
             </iron-icon>
           </template>
diff --git a/ash/webui/personalization_app/resources/js/wallpaper/wallpaper_collections_element.html b/ash/webui/personalization_app/resources/js/wallpaper/wallpaper_collections_element.html
index 041f373..05ef246 100644
--- a/ash/webui/personalization_app/resources/js/wallpaper/wallpaper_collections_element.html
+++ b/ash/webui/personalization_app/resources/js/wallpaper/wallpaper_collections_element.html
@@ -145,12 +145,12 @@
             <template is="dom-if" if="[[!isSelectableTile_(item)]]" restamp>
               <div slot="text" class="primary-text">
                 <template is="dom-if" if="[[!isPersonalizationJellyEnabled_]]">
-                  <iron-icon icon="personalization:managed"
+                  <iron-icon icon="personalization:managed-old"
                       title$="$i18n{managedSetting}">
                   </iron-icon>
                 </template>
                 <template is="dom-if" if="[[isPersonalizationJellyEnabled_]]">
-                  <iron-icon icon="personalization:managed2"
+                  <iron-icon icon="personalization:managed"
                       title$="$i18n{managedSetting}">
                   </iron-icon>
                 </template>
diff --git a/ash/webui/personalization_app/resources/js/wallpaper/wallpaper_preview_element.html b/ash/webui/personalization_app/resources/js/wallpaper/wallpaper_preview_element.html
index 935cc8c6..71442ea 100644
--- a/ash/webui/personalization_app/resources/js/wallpaper/wallpaper_preview_element.html
+++ b/ash/webui/personalization_app/resources/js/wallpaper/wallpaper_preview_element.html
@@ -38,7 +38,7 @@
     margin: 12px 0;
   }
 
-  iron-icon[icon='personalization:managed'] {
+  iron-icon[icon='personalization:managed-old'] {
     --iron-icon-fill-color: var(--cros-icon-color-secondary);
   }
 
@@ -87,7 +87,7 @@
     min-width: 278px;
   }
 
-  iron-icon[icon='personalization:managed2'] {
+  iron-icon[icon='personalization:managed'] {
     --iron-icon-height: 16px;
     --iron-icon-width: 16px;
   }
@@ -122,7 +122,7 @@
       <template is="dom-if" if="[[!isPersonalizationJellyEnabled_]]">
         <div id="wallpaperLabel">
           <p>$i18n{wallpaperLabel}</p>
-          <iron-icon icon="personalization:managed" title="$i18n{managedSetting}">
+          <iron-icon icon="personalization:managed-old" title="$i18n{managedSetting}">
           </iron-icon>
         </div>
         <div id="imageContainer" class="photo-images-container">
@@ -135,7 +135,7 @@
       <template is="dom-if" if="[[isPersonalizationJellyEnabled_]]">
         <div id="wallpaperLabel" class="disabled">
           <p>$i18n{wallpaperLabel}</p>
-          <iron-icon icon="personalization:managed2" disabled
+          <iron-icon icon="personalization:managed" disabled
               title="$i18n{managedSetting}">
           </iron-icon>
         </div>
diff --git a/ash/wm/desks/templates/admin_template_launch_tracker.cc b/ash/wm/desks/templates/admin_template_launch_tracker.cc
index c286a15..2b00ff6 100644
--- a/ash/wm/desks/templates/admin_template_launch_tracker.cc
+++ b/ash/wm/desks/templates/admin_template_launch_tracker.cc
@@ -467,6 +467,10 @@
   }
 }
 
+bool AdminTemplateLaunchTracker::IsActive() const {
+  return !window_observers_.empty();
+}
+
 void AdminTemplateLaunchTracker::OnObserverCreated(
     std::unique_ptr<base::CheckedObserver> observer) {
   window_observers_.push_back(std::move(observer));
diff --git a/ash/wm/desks/templates/admin_template_launch_tracker.h b/ash/wm/desks/templates/admin_template_launch_tracker.h
index 1f5f9f8..80ee523 100644
--- a/ash/wm/desks/templates/admin_template_launch_tracker.h
+++ b/ash/wm/desks/templates/admin_template_launch_tracker.h
@@ -90,6 +90,13 @@
   // this is a no-op.
   void FlushPendingUpdate();
 
+  // Returns true if there are launched windows from this tracker that are still
+  // open. When this returns false, there are no more windows that can generate
+  // updates. Note that there may still be a pending update, so
+  // `FlushPendingUpdate` should typically be called before the tracker is
+  // destroyed.
+  bool IsActive() const;
+
  private:
   // Called when an observer is created (either a desk or window observer).
   void OnObserverCreated(std::unique_ptr<base::CheckedObserver> observer);
diff --git a/ash/wm/desks/templates/saved_desk_controller.cc b/ash/wm/desks/templates/saved_desk_controller.cc
index ff64258..ca5dc37 100644
--- a/ash/wm/desks/templates/saved_desk_controller.cc
+++ b/ash/wm/desks/templates/saved_desk_controller.cc
@@ -97,6 +97,11 @@
     admin_template_launch_trackers_.erase(it);
   }
 
+  // This will remove any admin template trackers that are no longer tracking
+  // any windows. Note that for the common case of only having a single admin
+  // template, the previous operation will have already removed the tracker.
+  RemoveInactiveAdminTemplateTrackers();
+
   auto admin_template = GetAdminTemplate(template_uuid);
   if (!admin_template) {
     return false;
@@ -199,9 +204,6 @@
       kAdminTemplateUpdateDelay);
   tracker->LaunchTemplate(Shell::Get()->saved_desk_delegate(),
                           default_display_id);
-
-  // TODO(dandersson): Remove the launch tracker when all its windows have been
-  // closed.
 }
 
 std::unique_ptr<DeskTemplate> SavedDeskController::GetAdminTemplate(
@@ -227,6 +229,18 @@
   return nullptr;
 }
 
+void SavedDeskController::RemoveInactiveAdminTemplateTrackers() {
+  for (auto it = admin_template_launch_trackers_.begin();
+       it != admin_template_launch_trackers_.end();) {
+    if (!it->second->IsActive()) {
+      it->second->FlushPendingUpdate();
+      it = admin_template_launch_trackers_.erase(it);
+    } else {
+      ++it;
+    }
+  }
+}
+
 void SavedDeskController::SetAdminTemplateForTesting(
     std::unique_ptr<DeskTemplate> admin_template) {
   admin_template_for_testing_ = std::move(admin_template);
diff --git a/ash/wm/desks/templates/saved_desk_controller.h b/ash/wm/desks/templates/saved_desk_controller.h
index 60ab998..9ff9439 100644
--- a/ash/wm/desks/templates/saved_desk_controller.h
+++ b/ash/wm/desks/templates/saved_desk_controller.h
@@ -92,6 +92,10 @@
   std::unique_ptr<DeskTemplate> GetAdminTemplate(
       const base::Uuid& template_uuid) const;
 
+  // Removes all inactive admin template trackers. These are trackers that are
+  // no longer tracking any windows.
+  void RemoveInactiveAdminTemplateTrackers();
+
   // Install an admin template that can be used by `LaunchAdminTemplate`.
   void SetAdminTemplateForTesting(std::unique_ptr<DeskTemplate> admin_template);
 
diff --git a/ash/wm/desks/templates/saved_desk_library_view.cc b/ash/wm/desks/templates/saved_desk_library_view.cc
index 8ac59d1..90b0a80676 100644
--- a/ash/wm/desks/templates/saved_desk_library_view.cc
+++ b/ash/wm/desks/templates/saved_desk_library_view.cc
@@ -63,6 +63,9 @@
 // The size of the gradient applied to the top and bottom of the scroll view.
 constexpr int kScrollViewGradientSize = 32;
 
+// Elevation for the grid label text's shadow.
+constexpr int kLabelTextShadowElevation = 4;
+
 // Insets of Library page scroll content view. Note: the bottom inset is there
 // to slightly adjust the otherwise vertically centered scroll content up a tad.
 constexpr gfx::Insets kLibraryPageScrollContentsInsets =
@@ -129,6 +132,10 @@
 std::unique_ptr<views::Label> MakeGridLabel(int label_string_id) {
   auto label = std::make_unique<views::Label>(
       l10n_util::GetStringUTF16(label_string_id));
+  gfx::ShadowValues shadows =
+      gfx::ShadowValue::MakeChromeOSSystemUIShadowValues(
+          kLabelTextShadowElevation);
+  label->SetShadows(shadows);
   TypographyProvider::Get()->StyleLabel(TypographyToken::kCrosTitle1, *label);
   label->SetEnabledColorId(cros_tokens::kCrosSysOnSurface);
   label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
@@ -564,7 +571,6 @@
     grid_labels_[i]->SetVisible(!grid_views_[i]->grid_items().empty());
     grid_labels_[i]->SetPreferredSize(landscape ? kLabelSizeLandscape
                                                 : kLabelSizePortrait);
-
     total_saved_desks += grid_views_[i]->grid_items().size();
   }
 
diff --git a/ash/wm/window_state.cc b/ash/wm/window_state.cc
index 2309c4c..a4283d4 100644
--- a/ash/wm/window_state.cc
+++ b/ash/wm/window_state.cc
@@ -452,9 +452,10 @@
 }
 
 bool WindowState::CanSnapOnDisplay(display::Display display) const {
-  const bool can_resizable_snap = !IsPip() && CanResize() && CanMaximize();
+  const bool can_resize = CanResize();
+  const bool can_resizable_snap = !IsPip() && can_resize && CanMaximize();
   return can_resizable_snap ||
-         (!CanResize() && CanUnresizableSnapOnDisplay(display));
+         (!can_resize && CanUnresizableSnapOnDisplay(display));
 }
 
 bool WindowState::HasRestoreBounds() const {
diff --git a/base/BUILD.gn b/base/BUILD.gn
index 83cdc61..ba907d5 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -1266,6 +1266,8 @@
       "android/important_file_writer_android.cc",
       "android/int_string_callback.cc",
       "android/int_string_callback.h",
+      "android/jank_metric_uma_recorder.cc",
+      "android/jank_metric_uma_recorder.h",
       "android/java_handler_thread.cc",
       "android/java_handler_thread.h",
       "android/java_heap_dump_generator.cc",
@@ -3670,6 +3672,7 @@
       "android/application_status_listener_unittest.cc",
       "android/child_process_unittest.cc",
       "android/content_uri_utils_unittest.cc",
+      "android/jank_metric_uma_recorder_unittest.cc",
       "android/java_handler_thread_unittest.cc",
       "android/jni_android_unittest.cc",
       "android/jni_array_unittest.cc",
@@ -4156,6 +4159,7 @@
       "android/java/src/org/chromium/base/TimezoneUtils.java",
       "android/java/src/org/chromium/base/TraceEvent.java",
       "android/java/src/org/chromium/base/UnguessableToken.java",
+      "android/java/src/org/chromium/base/jank_tracker/JankMetricUMARecorder.java",
       "android/java/src/org/chromium/base/library_loader/LibraryLoader.java",
       "android/java/src/org/chromium/base/library_loader/LibraryPrefetcher.java",
       "android/java/src/org/chromium/base/memory/JavaHeapDumpGenerator.java",
@@ -4344,6 +4348,10 @@
       "android/java/src/org/chromium/base/compat/ApiHelperForQ.java",
       "android/java/src/org/chromium/base/compat/ApiHelperForR.java",
       "android/java/src/org/chromium/base/compat/ApiHelperForS.java",
+      "android/java/src/org/chromium/base/jank_tracker/FrameMetricsListener.java",
+      "android/java/src/org/chromium/base/jank_tracker/FrameMetricsStore.java",
+      "android/java/src/org/chromium/base/jank_tracker/JankMetricUMARecorder.java",
+      "android/java/src/org/chromium/base/jank_tracker/JankMetrics.java",
       "android/java/src/org/chromium/base/library_loader/LibraryLoader.java",
       "android/java/src/org/chromium/base/library_loader/LibraryPrefetcher.java",
       "android/java/src/org/chromium/base/library_loader/Linker.java",
@@ -4686,6 +4694,9 @@
       "android/junit/src/org/chromium/base/TraceEventTest.java",
       "android/junit/src/org/chromium/base/UnownedUserDataHostTest.java",
       "android/junit/src/org/chromium/base/UnownedUserDataKeyTest.java",
+      "android/junit/src/org/chromium/base/jank_tracker/FrameMetricsListenerTest.java",
+      "android/junit/src/org/chromium/base/jank_tracker/FrameMetricsStoreTest.java",
+      "android/junit/src/org/chromium/base/jank_tracker/JankMetricUMARecorderTest.java",
       "android/junit/src/org/chromium/base/library_loader/LinkerTest.java",
       "android/junit/src/org/chromium/base/memory/MemoryPressureMonitorTest.java",
       "android/junit/src/org/chromium/base/memory/MemoryPurgeManagerTest.java",
diff --git a/base/android/jank_metric_uma_recorder.cc b/base/android/jank_metric_uma_recorder.cc
new file mode 100644
index 0000000..85bc834b
--- /dev/null
+++ b/base/android/jank_metric_uma_recorder.cc
@@ -0,0 +1,104 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/android/jank_metric_uma_recorder.h"
+
+#include <cstdint>
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_array.h"
+#include "base/base_jni/JankMetricUMARecorder_jni.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/time/time.h"
+#include "base/trace_event/base_tracing.h"
+
+namespace base::android {
+
+namespace {
+
+void RecordJankMetricReportingIntervalTraceEvent(
+    int64_t reporting_interval_start_time,
+    int64_t reporting_interval_duration,
+    uint64_t janky_frame_count,
+    uint64_t non_janky_frame_count) {
+  if (reporting_interval_start_time < 0) {
+    return;
+  }
+
+  // The following code does nothing if base tracing is disabled.
+  [[maybe_unused]] auto t =
+      perfetto::Track(static_cast<uint64_t>(reporting_interval_start_time));
+  TRACE_EVENT_BEGIN(
+      "android_webview.timeline", "JankMetricsReportingInterval", t,
+      base::TimeTicks::FromUptimeMillis(reporting_interval_start_time),
+      "janky_frames", janky_frame_count, "non_janky_frames",
+      non_janky_frame_count);
+  TRACE_EVENT_END(
+      "android_webview.timeline", t,
+      base::TimeTicks::FromUptimeMillis(
+          (reporting_interval_start_time + reporting_interval_duration)));
+}
+
+}  // namespace
+
+// These values are persisted to logs. Entries should not be renumbered and
+// numeric values should never be reused.
+enum class FrameJankStatus {
+  kJanky = 0,
+  kNonJanky = 1,
+  kMaxValue = kNonJanky,
+};
+
+// This function is called from Java with JNI, it's declared in
+// base/base_jni/JankMetricUMARecorder_jni.h which is an autogenerated
+// header. The actual implementation is in RecordJankMetrics for simpler
+// testing.
+void JNI_JankMetricUMARecorder_RecordJankMetrics(
+    JNIEnv* env,
+    const base::android::JavaParamRef<jlongArray>& java_durations_ns,
+    const base::android::JavaParamRef<jbooleanArray>& java_jank_status,
+    jlong java_reporting_interval_start_time,
+    jlong java_reporting_interval_duration) {
+  RecordJankMetrics(env, java_durations_ns, java_jank_status,
+                    java_reporting_interval_start_time,
+                    java_reporting_interval_duration);
+}
+
+void RecordJankMetrics(
+    JNIEnv* env,
+    const base::android::JavaParamRef<jlongArray>& java_durations_ns,
+    const base::android::JavaParamRef<jbooleanArray>& java_jank_status,
+    jlong java_reporting_interval_start_time,
+    jlong java_reporting_interval_duration) {
+  std::vector<int64_t> durations_ns;
+  JavaLongArrayToInt64Vector(env, java_durations_ns, &durations_ns);
+
+  std::vector<bool> jank_status;
+  JavaBooleanArrayToBoolVector(env, java_jank_status, &jank_status);
+
+  std::string frame_duration_histogram_name = "Android.Jank.FrameDuration";
+  std::string janky_frames_histogram_name = "Android.Jank.FrameJankStatus";
+
+  for (const int64_t frame_duration_ns : durations_ns) {
+    base::UmaHistogramTimes(frame_duration_histogram_name,
+                            base::Nanoseconds(frame_duration_ns));
+  }
+
+  uint64_t janky_frame_count = 0;
+
+  for (bool is_janky : jank_status) {
+    base::UmaHistogramEnumeration(
+        janky_frames_histogram_name,
+        is_janky ? FrameJankStatus::kJanky : FrameJankStatus::kNonJanky);
+    if (is_janky) {
+      ++janky_frame_count;
+    }
+  }
+
+  RecordJankMetricReportingIntervalTraceEvent(
+      java_reporting_interval_start_time, java_reporting_interval_duration,
+      janky_frame_count, jank_status.size() - janky_frame_count);
+}
+
+}  // namespace base::android
diff --git a/base/android/jank_metric_uma_recorder.h b/base/android/jank_metric_uma_recorder.h
new file mode 100644
index 0000000..f6011fe
--- /dev/null
+++ b/base/android/jank_metric_uma_recorder.h
@@ -0,0 +1,20 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_ANDROID_JANK_METRIC_UMA_RECORDER_H_
+#define BASE_ANDROID_JANK_METRIC_UMA_RECORDER_H_
+
+#include "base/android/jni_android.h"
+#include "base/base_export.h"
+
+namespace base::android {
+
+BASE_EXPORT void RecordJankMetrics(
+    JNIEnv* env,
+    const base::android::JavaParamRef<jlongArray>& java_durations_ns,
+    const base::android::JavaParamRef<jbooleanArray>& java_jank_status,
+    jlong java_reporting_interval_start_time,
+    jlong java_reporting_interval_duration);
+}  // namespace base::android
+#endif  // BASE_ANDROID_JANK_METRIC_UMA_RECORDER_H_
diff --git a/base/android/jank_metric_uma_recorder_unittest.cc b/base/android/jank_metric_uma_recorder_unittest.cc
new file mode 100644
index 0000000..0dcc152
--- /dev/null
+++ b/base/android/jank_metric_uma_recorder_unittest.cc
@@ -0,0 +1,93 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/android/jank_metric_uma_recorder.h"
+
+#include <jni.h>
+
+#include <cstddef>
+#include <cstdint>
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_array.h"
+#include "base/android/jni_string.h"
+#include "base/metrics/histogram.h"
+#include "base/test/metrics/histogram_tester.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::ElementsAre;
+
+namespace base::android {
+namespace {
+
+jlongArray GenerateJavaLongArray(JNIEnv* env,
+                                 const int64_t long_array[],
+                                 const size_t array_length) {
+  ScopedJavaLocalRef<jlongArray> java_long_array =
+      ToJavaLongArray(env, long_array, array_length);
+
+  return java_long_array.Release();
+}
+
+// Durations are received in nanoseconds, but are recorded to UMA in
+// milliseconds.
+const int64_t kDurations[] = {
+    1'000'000,   // 1ms
+    2'000'000,   // 2ms
+    30'000'000,  // 30ms
+    10'000'000,  // 10ms
+    60'000'000,  // 60ms
+    1'000'000,   // 1ms
+    1'000'000,   // 1ms
+    20'000'000,  // 20ms
+};
+const size_t kDurationsLen = std::size(kDurations);
+
+jbooleanArray GenerateJavaBooleanArray(JNIEnv* env,
+                                       const bool bool_array[],
+                                       const size_t array_length) {
+  ScopedJavaLocalRef<jbooleanArray> java_bool_array =
+      ToJavaBooleanArray(env, bool_array, array_length);
+
+  return java_bool_array.Release();
+}
+
+const bool kJankStatus[] = {
+    false, false, true, false, true, false, false, false,
+};
+
+const size_t kJankStatusLen = kDurationsLen;
+
+}  // namespace
+
+TEST(JankMetricUMARecorder, TestUMARecording) {
+  HistogramTester histogram_tester;
+
+  JNIEnv* env = AttachCurrentThread();
+
+  jlongArray java_durations =
+      GenerateJavaLongArray(env, kDurations, kDurationsLen);
+
+  jbooleanArray java_jank_status =
+      GenerateJavaBooleanArray(env, kJankStatus, kJankStatusLen);
+
+  RecordJankMetrics(
+      env,
+      /* java_durations_ns= */
+      base::android::JavaParamRef<jlongArray>(env, java_durations),
+      /* java_jank_status = */
+      base::android::JavaParamRef<jbooleanArray>(env, java_jank_status),
+      /* java_reporting_interval_start_time = */ 0,
+      /* java_reporting_interval_duration = */ 1000);
+
+  EXPECT_THAT(histogram_tester.GetAllSamples("Android.Jank.FrameDuration"),
+              ElementsAre(Bucket(1, 3), Bucket(2, 1), Bucket(10, 1),
+                          Bucket(20, 1), Bucket(29, 1), Bucket(57, 1)));
+
+  EXPECT_THAT(histogram_tester.GetAllSamples("Android.Jank.FrameJankStatus"),
+              ElementsAre(Bucket(0, 2), Bucket(1, 6)));
+}
+
+}  // namespace base::android
diff --git a/base/android/java/src/org/chromium/base/jank_tracker/FrameMetricsListener.java b/base/android/java/src/org/chromium/base/jank_tracker/FrameMetricsListener.java
new file mode 100644
index 0000000..dadbcd7
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/jank_tracker/FrameMetricsListener.java
@@ -0,0 +1,82 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.jank_tracker;
+
+import android.os.Build.VERSION_CODES;
+import android.os.SystemClock;
+import android.view.FrameMetrics;
+import android.view.Window;
+import android.view.Window.OnFrameMetricsAvailableListener;
+
+import androidx.annotation.RequiresApi;
+
+import org.chromium.base.ThreadUtils;
+import org.chromium.base.TraceEvent;
+
+/**
+ * This class receives OnFrameMetricsAvailableListener.onFrameMetricsAvailable() callbacks and
+ * records frame durations in a FrameMetricsStore instance.
+ */
+@RequiresApi(api = VERSION_CODES.N)
+public class FrameMetricsListener implements OnFrameMetricsAvailableListener {
+    private final FrameMetricsStore mFrameMetricsStore;
+    private boolean mIsRecording;
+
+    // The reporting interval start and duration are passed to the reporting code and used in the
+    // 'JankMetricsReportingInterval' trace event.
+    private long mReportingIntervalStartTime;
+    private long mReportingIntervalDurationMillis;
+
+    private final ThreadUtils.ThreadChecker mThreadChecker = new ThreadUtils.ThreadChecker();
+
+    public FrameMetricsListener(FrameMetricsStore frameMetricsStore) {
+        mFrameMetricsStore = frameMetricsStore;
+    }
+
+    /**
+     * Toggles recording into FrameMetricsStore. When recording is stopped, reports accumulated
+     * metrics.
+     * @param isRecording
+     */
+    public void setIsListenerRecording(boolean isRecording) {
+        mThreadChecker.assertOnValidThread();
+        mIsRecording = isRecording;
+        if (isRecording && mReportingIntervalStartTime == 0) {
+            mReportingIntervalStartTime = SystemClock.uptimeMillis();
+        } else if (!isRecording) {
+            if (mReportingIntervalStartTime != 0) {
+                mReportingIntervalDurationMillis =
+                        SystemClock.uptimeMillis() - mReportingIntervalStartTime;
+            }
+            reportMetrics();
+        }
+    }
+
+    @RequiresApi(api = VERSION_CODES.N)
+    @Override
+    public void onFrameMetricsAvailable(
+            Window window, FrameMetrics frameMetrics, int dropCountSinceLastInvocation) {
+        mThreadChecker.assertOnValidThread();
+        if (!mIsRecording) {
+            return;
+        }
+
+        long frameTotalDurationNs = frameMetrics.getMetric(FrameMetrics.TOTAL_DURATION);
+
+        try (TraceEvent e = TraceEvent.scoped(
+                     "onFrameMetricsAvailable", Long.toString(frameTotalDurationNs))) {
+            long deadlineNs = frameMetrics.getMetric(FrameMetrics.DEADLINE);
+            boolean isJanky = frameTotalDurationNs >= deadlineNs;
+            mFrameMetricsStore.addFrameMeasurement(frameTotalDurationNs, isJanky);
+        }
+    }
+
+    private void reportMetrics() {
+        JankMetricUMARecorder.recordJankMetricsToUMA(mFrameMetricsStore.takeMetrics(),
+                mReportingIntervalStartTime, mReportingIntervalDurationMillis);
+        mReportingIntervalStartTime = 0;
+        mReportingIntervalDurationMillis = 0;
+    }
+}
diff --git a/base/android/java/src/org/chromium/base/jank_tracker/FrameMetricsStore.java b/base/android/java/src/org/chromium/base/jank_tracker/FrameMetricsStore.java
new file mode 100644
index 0000000..bc8ad2b
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/jank_tracker/FrameMetricsStore.java
@@ -0,0 +1,55 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.jank_tracker;
+
+import java.util.ArrayList;
+
+/**
+ * This class stores relevant metrics from FrameMetrics between the calls to UMA reporting methods.
+ */
+public class FrameMetricsStore {
+    // Array of total durations stored in nanoseconds, they represent how long each frame took to
+    // draw.
+    private final ArrayList<Long> mTotalDurationsNs = new ArrayList<>();
+
+    // Array of boolean values denoting whether a given frame is janky or not. Must always be the
+    // same size as mTotalDurationsNs.
+    private final ArrayList<Boolean> mIsJanky = new ArrayList<>();
+
+    /**
+     * Records the total draw duration and jankiness for a single frame.
+     */
+    void addFrameMeasurement(long totalDurationNs, boolean isJanky) {
+        mTotalDurationsNs.add(totalDurationNs);
+        mIsJanky.add(isJanky);
+    }
+
+    /**
+     * Returns a copy of accumulated metrics and clears the internal storage.
+     */
+    JankMetrics takeMetrics() {
+        Long[] longDurations;
+        Boolean[] booleanIsJanky;
+
+        longDurations = mTotalDurationsNs.toArray(new Long[mTotalDurationsNs.size()]);
+        mTotalDurationsNs.clear();
+
+        booleanIsJanky = mIsJanky.toArray(new Boolean[mIsJanky.size()]);
+        mIsJanky.clear();
+
+        long[] durations = new long[longDurations.length];
+        for (int i = 0; i < longDurations.length; i++) {
+            durations[i] = longDurations[i].longValue();
+        }
+
+        boolean[] isJanky = new boolean[booleanIsJanky.length];
+        for (int i = 0; i < booleanIsJanky.length; i++) {
+            isJanky[i] = booleanIsJanky[i].booleanValue();
+        }
+
+        JankMetrics jankMetrics = new JankMetrics(durations, isJanky);
+        return jankMetrics;
+    }
+}
diff --git a/base/android/java/src/org/chromium/base/jank_tracker/JankMetricUMARecorder.java b/base/android/java/src/org/chromium/base/jank_tracker/JankMetricUMARecorder.java
new file mode 100644
index 0000000..9bb6892a
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/jank_tracker/JankMetricUMARecorder.java
@@ -0,0 +1,30 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.jank_tracker;
+
+import org.chromium.base.annotations.JNINamespace;
+import org.chromium.base.annotations.NativeMethods;
+
+/**
+ * Sends Android jank metrics to native to be recorded using UMA.
+ */
+@JNINamespace("base::android")
+public class JankMetricUMARecorder {
+    public static void recordJankMetricsToUMA(
+            JankMetrics metric, long reportingIntervalStartTime, long reportingIntervalDuration) {
+        if (metric == null) {
+            return;
+        }
+
+        JankMetricUMARecorderJni.get().recordJankMetrics(metric.durationsNs, metric.isJanky,
+                reportingIntervalStartTime, reportingIntervalDuration);
+    }
+
+    @NativeMethods
+    public interface Natives {
+        void recordJankMetrics(long[] durationsNs, boolean[] jankStatus,
+                long reportingIntervalStartTime, long reportingIntervalDuration);
+    }
+}
diff --git a/base/android/java/src/org/chromium/base/jank_tracker/JankMetrics.java b/base/android/java/src/org/chromium/base/jank_tracker/JankMetrics.java
new file mode 100644
index 0000000..6c94177
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/jank_tracker/JankMetrics.java
@@ -0,0 +1,19 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.jank_tracker;
+
+/**
+ * This class is a container for jank metrics, which are processed FrameMetrics ready to be uploaded
+ * to UMA.
+ */
+class JankMetrics {
+    public final long[] durationsNs;
+    public final boolean[] isJanky;
+
+    public JankMetrics(long[] durationsNs, boolean[] isJanky) {
+        this.durationsNs = durationsNs;
+        this.isJanky = isJanky;
+    }
+}
diff --git a/base/android/junit/src/org/chromium/base/jank_tracker/FrameMetricsListenerTest.java b/base/android/junit/src/org/chromium/base/jank_tracker/FrameMetricsListenerTest.java
new file mode 100644
index 0000000..5bd24662
--- /dev/null
+++ b/base/android/junit/src/org/chromium/base/jank_tracker/FrameMetricsListenerTest.java
@@ -0,0 +1,55 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.jank_tracker;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.view.FrameMetrics;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+import org.chromium.base.test.BaseRobolectricTestRunner;
+
+/**
+ *  Tests for FrameMetricsListener.
+ */
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+public class FrameMetricsListenerTest {
+    @Test
+    public void testMetricRecording_OffByDefault() {
+        FrameMetricsStore store = new FrameMetricsStore();
+        FrameMetricsListener metricsListener = new FrameMetricsListener(store);
+        FrameMetrics frameMetrics = mock(FrameMetrics.class);
+
+        when(frameMetrics.getMetric(FrameMetrics.TOTAL_DURATION)).thenReturn(10_000_000L);
+
+        metricsListener.onFrameMetricsAvailable(null, frameMetrics, 0);
+
+        // By default metrics shouldn't be logged.
+        Assert.assertEquals(0, store.takeMetrics().durationsNs.length);
+        verifyNoMoreInteractions(frameMetrics);
+    }
+
+    @Test
+    public void testMetricRecording_EnableRecording() {
+        FrameMetricsStore store = new FrameMetricsStore();
+
+        FrameMetricsListener metricsListener = new FrameMetricsListener(store);
+        FrameMetrics frameMetrics = mock(FrameMetrics.class);
+
+        when(frameMetrics.getMetric(FrameMetrics.TOTAL_DURATION)).thenReturn(10_000_000L);
+
+        metricsListener.setIsListenerRecording(true);
+        metricsListener.onFrameMetricsAvailable(null, frameMetrics, 0);
+
+        Assert.assertArrayEquals(new long[] {10_000_000L}, store.takeMetrics().durationsNs);
+    }
+}
diff --git a/base/android/junit/src/org/chromium/base/jank_tracker/FrameMetricsStoreTest.java b/base/android/junit/src/org/chromium/base/jank_tracker/FrameMetricsStoreTest.java
new file mode 100644
index 0000000..931ce7c0
--- /dev/null
+++ b/base/android/junit/src/org/chromium/base/jank_tracker/FrameMetricsStoreTest.java
@@ -0,0 +1,48 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.jank_tracker;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+import org.chromium.base.test.BaseRobolectricTestRunner;
+
+/**
+ *  Tests for FrameMetricsStore.
+ */
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+public class FrameMetricsStoreTest {
+    @Test
+    public void addFrameMeasurementTest() {
+        FrameMetricsStore store = new FrameMetricsStore();
+
+        store.addFrameMeasurement(10_000_000L, false);
+        store.addFrameMeasurement(12_000_000L, false);
+        store.addFrameMeasurement(20_000_000L, true);
+        store.addFrameMeasurement(8_000_000L, true);
+
+        JankMetrics metrics = store.takeMetrics();
+
+        assertArrayEquals(new long[] {10_000_000L, 12_000_000L, 20_000_000L, 8_000_000L},
+                metrics.durationsNs);
+        assertArrayEquals(new boolean[] {false, false, true, true}, metrics.isJanky);
+
+        metrics = store.takeMetrics();
+        assertEquals(0, metrics.durationsNs.length);
+    }
+
+    @Test
+    public void takeMetrics_getMetricsWithoutAnyFrames() {
+        FrameMetricsStore store = new FrameMetricsStore();
+        JankMetrics metrics = store.takeMetrics();
+
+        assertEquals(0, metrics.durationsNs.length);
+    }
+}
diff --git a/base/android/junit/src/org/chromium/base/jank_tracker/JankMetricUMARecorderTest.java b/base/android/junit/src/org/chromium/base/jank_tracker/JankMetricUMARecorderTest.java
new file mode 100644
index 0000000..e8b1f0f
--- /dev/null
+++ b/base/android/junit/src/org/chromium/base/jank_tracker/JankMetricUMARecorderTest.java
@@ -0,0 +1,60 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.jank_tracker;
+
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatchers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+
+import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.base.test.util.JniMocker;
+
+/**
+ *  Tests for JankMetricUMARecorder.
+ */
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+public class JankMetricUMARecorderTest {
+    @Rule
+    public JniMocker mocker = new JniMocker();
+
+    @Mock
+    JankMetricUMARecorder.Natives mNativeMock;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mocker.mock(JankMetricUMARecorderJni.TEST_HOOKS, mNativeMock);
+    }
+
+    @Test
+    public void testRecordMetricsToNative() {
+        long[] durationsNs = new long[] {5_000_000L, 8_000_000L, 30_000_000L};
+        boolean[] jankyFrames = new boolean[] {false, false, true};
+
+        JankMetrics metric = new JankMetrics(durationsNs, jankyFrames);
+
+        JankMetricUMARecorder.recordJankMetricsToUMA(metric, 0, 1000);
+
+        // Ensure that the relevant fields are sent down to native.
+        verify(mNativeMock).recordJankMetrics(durationsNs, jankyFrames, 0, 1000);
+    }
+
+    @Test
+    public void testRecordNullMetrics() {
+        JankMetricUMARecorder.recordJankMetricsToUMA(null, 0, 0);
+        verify(mNativeMock, never())
+                .recordJankMetrics(ArgumentMatchers.any(), ArgumentMatchers.any(),
+                        ArgumentMatchers.anyLong(), ArgumentMatchers.anyLong());
+    }
+}
diff --git a/base/trace_event/trace_event_stub.h b/base/trace_event/trace_event_stub.h
index 9169fc69..5fd64f3 100644
--- a/base/trace_event/trace_event_stub.h
+++ b/base/trace_event/trace_event_stub.h
@@ -161,7 +161,8 @@
 }  // namespace base
 
 // Stub implementation for
-// perfetto::StaticString/ThreadTrack/TracedValue/TracedDictionary/TracedArray.
+// perfetto::StaticString/ThreadTrack/TracedValue/TracedDictionary/TracedArray/
+// Track.
 namespace perfetto {
 
 class TracedArray;
@@ -225,6 +226,10 @@
 template <class T>
 void WriteIntoTracedValue(TracedValue, T&&) {}
 
+struct Track {
+  explicit Track(uint64_t id) {}
+};
+
 namespace protos::pbzero {
 namespace SequenceManagerTask {
 
diff --git a/build/config/android/rules.gni b/build/config/android/rules.gni
index 685e3d1..d12617a3 100644
--- a/build/config/android/rules.gni
+++ b/build/config/android/rules.gni
@@ -23,6 +23,8 @@
   _sanitizer_arch = "aarch64"
 } else if (target_cpu == "x86") {
   _sanitizer_arch = "i686"
+} else if (target_cpu == "riscv64") {
+  _sanitizer_arch = "riscv64"
 }
 
 _sanitizer_runtimes = []
@@ -4367,6 +4369,13 @@
 
     _use_scanned_assets = !_ignore_assets && _scanned_files.assets != []
     _has_resources = _scanned_files.resources != []
+    _common_deps = [ ":$_unpack_target_name" ]
+    if (defined(invoker.deps)) {
+      _common_deps += invoker.deps
+    }
+    if (defined(invoker.public_deps)) {
+      _common_deps += invoker.public_deps
+    }
 
     assert(_ignore_aidl || _scanned_files.aidl == [],
            "android_aar_prebuilt() aidl not yet supported." +
@@ -4443,7 +4452,7 @@
                                  "testonly",
                                  "strip_drawables",
                                ])
-        public_deps = [ ":$_unpack_target_name" ]
+        deps = _common_deps
         if (_should_process_manifest) {
           android_manifest_dep = ":$_unpack_target_name"
           android_manifest = "${_output_path}/AndroidManifest.xml"
@@ -4513,7 +4522,7 @@
         _subjar_targets += [ ":$_current_target" ]
         java_prebuilt(_current_target) {
           forward_variables_from(invoker, _java_library_vars)
-          deps = [ ":$_unpack_target_name" ]
+          deps = _common_deps
           if (!defined(requires_android)) {
             requires_android = true
           }
@@ -4530,15 +4539,11 @@
         forward_variables_from(invoker, _java_library_vars)
         forward_variables_from(invoker,
                                [
-                                 "deps",
                                  "input_jars_paths",
                                  "mergeable_android_manifests",
                                  "proguard_configs",
                                ])
-        if (!defined(deps)) {
-          deps = []
-        }
-        deps += _subjar_targets + [ ":$_unpack_target_name" ]
+        deps = _common_deps + _subjar_targets
         if (defined(_res_target_name)) {
           deps += [ ":$_res_target_name" ]
         }
diff --git a/build/config/clang/BUILD.gn b/build/config/clang/BUILD.gn
index bdf4303..9bca13d 100644
--- a/build/config/clang/BUILD.gn
+++ b/build/config/clang/BUILD.gn
@@ -172,6 +172,8 @@
           _suffix = "-arm-android"
         } else if (current_cpu == "arm64") {
           _suffix = "-aarch64-android"
+        } else if (current_cpu == "riscv64") {
+          _suffix = "-riscv64-android"
         } else {
           assert(false)  # Unhandled cpu type
         }
diff --git a/build/config/compiler/compiler.gni b/build/config/compiler/compiler.gni
index 4738ee8..18717014 100644
--- a/build/config/compiler/compiler.gni
+++ b/build/config/compiler/compiler.gni
@@ -249,7 +249,8 @@
                 current_cpu == "arm" || current_cpu == "arm64" ||
                 current_cpu == "mipsel" || current_cpu == "mips64el")) ||
               (is_android && (current_cpu == "x86" || current_cpu == "x64" ||
-                              current_cpu == "arm" || current_cpu == "arm64")))
+                              current_cpu == "arm" || current_cpu == "arm64" ||
+                              current_cpu == "riscv64")))
 }
 
 # Use relative paths for debug info. This is important to make the build
diff --git a/build/config/sysroot.gni b/build/config/sysroot.gni
index 0d313fe..055c2a82 100644
--- a/build/config/sysroot.gni
+++ b/build/config/sysroot.gni
@@ -19,9 +19,10 @@
 
   # Controls default is_linux sysroot. If set to true, and sysroot
   # is empty, default sysroot is calculated.
-  use_sysroot = current_cpu == "x86" || current_cpu == "x64" ||
-                current_cpu == "arm" || current_cpu == "arm64" ||
-                current_cpu == "mipsel" || current_cpu == "mips64el"
+  use_sysroot =
+      current_cpu == "x86" || current_cpu == "x64" || current_cpu == "arm" ||
+      current_cpu == "arm64" || current_cpu == "mipsel" ||
+      current_cpu == "mips64el" || current_cpu == "riscv64"
 }
 
 if (sysroot == "") {
diff --git a/build/gn_ast/.style.yapf b/build/gn_ast/.style.yapf
new file mode 100644
index 0000000..557fa7b
--- /dev/null
+++ b/build/gn_ast/.style.yapf
@@ -0,0 +1,2 @@
+[style]
+based_on_style = pep8
diff --git a/build/gn_ast/README.md b/build/gn_ast/README.md
new file mode 100644
index 0000000..994e37a
--- /dev/null
+++ b/build/gn_ast/README.md
@@ -0,0 +1,20 @@
+# GN AST
+
+A Python library for working with GN files via abstract syntax tree (AST).
+
+## JNI Refactor Example
+
+This library was originally created to perform the refactor within
+`jni_refactor.py`. The file is left as an example.
+
+```sh
+# To apply to all files:
+find -name BUILD.gn > file-list.txt
+# To apply to those that match a pattern:
+grep -r --files-with-matches --include "BUILD.gn" "some pattern" > file-list.txt
+
+# To run one-at-a-time:
+for f in $(cat file-list.txt); do python3 jni_refactor.py "$f"; done
+# To run in parallel:
+parallel python3 jni_refactor.py -- $(cat file-list.txt)
+```
diff --git a/build/gn_ast/gn_ast.py b/build/gn_ast/gn_ast.py
new file mode 100644
index 0000000..aaabbf0
--- /dev/null
+++ b/build/gn_ast/gn_ast.py
@@ -0,0 +1,376 @@
+# Lint as: python3
+# Copyright 2023 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+"""Helper script to use GN's JSON interface to make changes.
+
+AST implementation details:
+  https://gn.googlesource.com/gn/+/refs/heads/main/src/gn/parse_tree.cc
+
+To dump an AST:
+  gn format --dump-tree=json BUILD.gn > foo.json
+"""
+
+from __future__ import annotations
+
+import dataclasses
+import functools
+import json
+import os
+import re
+import shutil
+import subprocess
+import sys
+from typing import Dict, List, Optional
+
+_DIR_SOURCE_ROOT = os.path.normpath(
+    os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
+
+NODE_CHILD = 'child'
+NODE_TYPE = 'type'
+NODE_VALUE = 'value'
+
+
+def _create_location_node(begin_line=1):
+    return {
+        'begin_column': 1,
+        'begin_line': begin_line,
+        'end_column': 2,
+        'end_line': begin_line,
+    }
+
+
+def _wrap(node):
+    kind = node[NODE_TYPE]
+    if kind == 'LIST':
+        return StringList(node)
+    if kind == 'BLOCK':
+        return BlockWrapper(node)
+    return NodeWrapper(node)
+
+
+def _unwrap(thing):
+    if isinstance(thing, NodeWrapper):
+        return thing.node
+    return thing
+
+
+def _find_node(root_node, target_node):
+    def recurse(node):
+        children = node.get(NODE_CHILD)
+        if children:
+            for i, child in enumerate(children):
+                if child is target_node:
+                    return node, i
+                ret = recurse(child)
+                if ret is not None:
+                    return ret
+        return None
+
+    ret = recurse(root_node)
+    if ret is None:
+        raise Exception(
+            f'Node not found: {node_to_remove}\nLooked in: {root_node}')
+    return ret
+
+
+@dataclasses.dataclass
+class NodeWrapper:
+    """Base class for all wrappers."""
+    node: dict = None
+
+    @property
+    def node_type(self):
+        return self.node[NODE_TYPE]
+
+    @property
+    def node_value(self):
+        return self.node[NODE_VALUE]
+
+    @property
+    def node_children(self):
+        return self.node[NODE_CHILD]
+
+    @functools.cached_property
+    def first_child(self):
+        return _wrap(self.node[NODE_CHILD][0])
+
+    @functools.cached_property
+    def second_child(self):
+        return _wrap(self.node[NODE_CHILD][1])
+
+    def is_list(self):
+        return self.node_type == 'LIST'
+
+    def is_identifier(self):
+        return self.node_type == 'IDENTIFIER'
+
+    def visit_nodes(self, callback) -> List:
+        ret = []
+
+        def recurse(root):
+            value = callback(root)
+            if value is not None:
+                ret.append(value)
+                return
+            children = root.get(NODE_CHILD)
+            if children:
+                for child in children:
+                    recurse(child)
+
+        recurse(self.node)
+        return ret
+
+    def set_location_recursive(self, line):
+        def helper(n):
+            loc = n.get('location')
+            if loc:
+                loc['begin_line'] = line
+                loc['end_line'] = line
+
+        self.visit_nodes(helper)
+
+    def add_child(self, node, *, before=None):
+        node = _unwrap(node)
+        if before is None:
+            self.node_children.append(node)
+        else:
+            before = _unwrap(before)
+            parent_node, child_idx = _find_node(self.node, before)
+            parent_node[NODE_CHILD].insert(child_idx, node)
+
+            # Prevent blank lines between |before| and |node|.
+            target_line = before['location']['begin_line']
+            _wrap(node).set_location_recursive(target_line)
+
+    def remove_child(self, node):
+        node = _unwrap(node)
+        parent_node, child_idx = _find_node(self.node, node)
+        parent_node[NODE_CHILD].pop(child_idx)
+
+
+@dataclasses.dataclass
+class BlockWrapper(NodeWrapper):
+    """Wraps a BLOCK node."""
+    def __post_init__(self):
+        assert self.node_type == 'BLOCK'
+
+    def find_assignments(self, var_name=None):
+        def match_fn(node):
+            assignment = AssignmentWrapper.from_node(node)
+            if not assignment:
+                return None
+            if var_name is None or var_name == assignment.variable_name:
+                return assignment
+            return None
+
+        return self.visit_nodes(match_fn)
+
+
+@dataclasses.dataclass
+class AssignmentWrapper(NodeWrapper):
+    """Wraps a =, +=, or -= BINARY node where the LHS is an identifier."""
+    def __post_init__(self):
+        assert self.node_type == 'BINARY'
+
+    @property
+    def variable_name(self):
+        return self.first_child.node_value
+
+    @property
+    def value(self):
+        return self.second_child
+
+    @property
+    def list_value(self):
+        ret = self.second_child
+        assert isinstance(ret, StringList), 'Found: ' + ret.node_type
+        return ret
+
+    @property
+    def operation(self):
+        """The assignment operation. Either "=" or "+="."""
+        return self.node_value
+
+    @property
+    def is_append(self):
+        return self.operation == '+='
+
+    def value_as_string_list(self):
+        return StringList(self.value_node)
+
+    @staticmethod
+    def from_node(node) -> Optional[AssignmentWrapper]:
+        if node.get(NODE_TYPE) != 'BINARY':
+            return None
+        children = node[NODE_CHILD]
+        assert len(children) == 2, (
+            'Binary nodes should have two child nodes, but the node is: '
+            f'{node}')
+        left_child, right_child = children
+        if left_child.get(NODE_TYPE) != 'IDENTIFIER':
+            return None
+        if node.get(NODE_VALUE) not in ('=', '+=', '-='):
+            return None
+        return AssignmentWrapper(node)
+
+    @staticmethod
+    def create(variable_name, value, operation='='):
+        value_node = _unwrap(value)
+        id_node = {
+            'location': _create_location_node(),
+            'type': 'IDENTIFIER',
+            'value': variable_name,
+        }
+        return AssignmentWrapper({
+            'location': _create_location_node(),
+            'child': [id_node, value_node],
+            'type': 'BINARY',
+            'value': operation,
+        })
+
+    @staticmethod
+    def create_list(variable_name, operation='='):
+        return AssignmentWrapper.create(variable_name,
+                                        StringList.create(),
+                                        operation=operation)
+
+
+@dataclasses.dataclass
+class StringList(NodeWrapper):
+    """Wraps a list node that contains only string literals."""
+    def __post_init__(self):
+        assert self.is_list()
+
+        self.literals = [
+            x[NODE_VALUE].strip('"') for x in self.node_children
+            if x[NODE_TYPE] == 'LITERAL'
+        ]
+
+    def add_literal(self, value: str):
+        # For lists of deps, gn format will sort entries, but it will not
+        # move entries past comment boundaries. Insert at the front by default
+        # so that if sorting moves the value, and there is a comment boundary,
+        # it will end up before the comment instead of immediately after the
+        # comment (which likely does not apply to it).
+        self.literals.insert(0, value)
+        self.node_children.insert(
+            0, {
+                'location': _create_location_node(),
+                'type': 'LITERAL',
+                'value': f'"{value}"',
+            })
+
+    def remove_literal(self, value: str):
+        self.literals.remove(value)
+        quoted = f'"{value}"'
+        children = self.node_children
+        for i, node in enumerate(children):
+            if node[NODE_VALUE] == quoted:
+                children.pop(i)
+                break
+        else:
+            raise ValueError(f'Did not find child with value {quoted}')
+
+    @staticmethod
+    def create() -> StringList:
+        return StringList({
+            'location': _create_location_node(),
+            'begin_token': '[',
+            'child': [],
+            'end': {
+                'location': _create_location_node(),
+                'type': 'END',
+                'value': ']'
+            },
+            'type': 'LIST',
+        })
+
+
+class Target(NodeWrapper):
+    """Wraps a target node.
+
+    A target node is any function besides "template" with exactly two children:
+      * Child 1: LIST with single string literal child
+      * Child 2: BLOCK
+
+    This does not actually find all targets. E.g. ignores those that use an
+    expression for a name, or that use "target(type, name)".
+    """
+    def __init__(self, function_node, name_node):
+        super().__init__(function_node)
+        self.name_node = name_node
+
+    @property
+    def name(self):
+        return self.name_node[NODE_VALUE].strip('"')
+
+    # E.g. "android_library"
+    @property
+    def type(self):
+        return self.node[NODE_VALUE]
+
+    @property
+    def block(self):
+        return self.second_child
+
+    def set_name(self, value):
+        self.name_node[NODE_VALUE] = f'"{value}"'
+
+    @staticmethod
+    def from_node(node) -> Optional[Target]:
+        """Returns a Target if |node| is a target, None otherwise."""
+        if node.get(NODE_TYPE) != 'FUNCTION':
+            return None
+        if node.get(NODE_VALUE) == 'template':
+            return None
+        children = node.get(NODE_CHILD)
+        if not children or len(children) != 2:
+            return None
+        func_params_node, block_node = children
+        if block_node.get(NODE_TYPE) != 'BLOCK':
+            return None
+        if func_params_node.get(NODE_TYPE) != 'LIST':
+            return None
+        param_nodes = func_params_node.get(NODE_CHILD)
+        if param_nodes is None or len(param_nodes) != 1:
+            return None
+        name_node = param_nodes[0]
+        if name_node.get(NODE_TYPE) != 'LITERAL':
+            return None
+        return Target(function_node=node, name_node=name_node)
+
+
+class BuildFile:
+    """Represents the contents of a BUILD.gn file."""
+    def __init__(self, path, root_node):
+        self.block = BlockWrapper(root_node)
+        self.path = path
+        self._original_content = json.dumps(root_node)
+
+    def write_changes(self) -> bool:
+        """Returns whether there were any changes."""
+        new_content = json.dumps(self.block.node)
+        if new_content == self._original_content:
+            return False
+        output = subprocess.check_output(
+            ['gn', 'format', '--read-tree=json', self.path],
+            text=True,
+            input=new_content)
+        if 'Wrote rebuilt from json to' not in output:
+            raise Exception('JSON was invalid')
+        return True
+
+    @functools.cached_property
+    def targets(self) -> List[Target]:
+        return self.block.visit_nodes(Target.from_node)
+
+    @functools.cached_property
+    def targets_by_name(self) -> Dict[str, Target]:
+        return {t.name: t for t in self.targets}
+
+    @staticmethod
+    def from_file(path):
+        output = subprocess.check_output(
+            ['gn', 'format', '--dump-tree=json', path], text=True)
+        return BuildFile(path, json.loads(output))
diff --git a/build/gn_ast/jni_refactor.py b/build/gn_ast/jni_refactor.py
new file mode 100644
index 0000000..eaa9861
--- /dev/null
+++ b/build/gn_ast/jni_refactor.py
@@ -0,0 +1,155 @@
+# Copyright 2023 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+"""Refactors BUILD.gn files for our Annotation Processor -> .srcjar migration.
+
+1) Finds all generate_jni() targets
+2) Finds all android_library() targets with that use ":jni_processor"
+3) Compares lists of sources between them
+4) Removes the annotation_processor_deps entry
+5) Adds the generate_jni target as a srcjar_dep
+6) Updates visibility of generate_jni to allow the dep
+
+This script has already done its job, but is left as an example of using gn_ast.
+"""
+
+import argparse
+import sys
+
+import gn_ast
+
+_PROCESSOR_DEP = '//base/android/jni_generator:jni_processor'
+
+
+class RefactorException(Exception):
+    pass
+
+
+def find_processor_assignment(target):
+    for assignment in target.block.find_assignments(
+            'annotation_processor_deps'):
+        processors = assignment.list_value.literals
+        if _PROCESSOR_DEP in processors:
+            return assignment
+    return None
+
+
+def find_all_sources(target, build_file):
+    ret = []
+
+    def helper(assignments):
+        for assignment in assignments:
+            if assignment.operation not in ('=', '+='):
+                raise RefactorException(
+                    f'{target.name}: sources has a {assignment.operation}.')
+
+            value = assignment.value
+            if value.is_identifier():
+                helper(build_file.block.find_assignments(value.node_value))
+            elif not value.is_list():
+                raise RefactorException(f'{target.name}: sources not a list.')
+            else:
+                ret.extend(value.literals)
+
+    helper(target.block.find_assignments('sources'))
+    return ret
+
+
+def find_matching_jni_target(library_target, jni_target_to_sources,
+                             build_file):
+    all_sources = set(find_all_sources(library_target, build_file))
+    matches = []
+    for jni_target_name, jni_sources in jni_target_to_sources.items():
+        if all(s in all_sources for s in jni_sources):
+            matches.append(jni_target_name)
+    if len(matches) == 1:
+        return matches[0]
+    if len(matches) > 1:
+        raise RefactorException(
+            f'{library_target.name}: Matched multiple generate_jni().')
+    if jni_target_to_sources:
+        raise RefactorException(
+            f'{library_target.name}: No matching generate_jni().')
+    raise RefactorException('No sources found for generate_jni().')
+
+
+def fix_visibility(target):
+    for assignment in target.block.find_assignments('visibility'):
+        if not assignment.value.is_list():
+            continue
+        list_value = assignment.list_value
+        for value in list(list_value.literals):
+            if value.startswith(':'):
+                list_value.remove_literal(value)
+        list_value.add_literal(':*')
+
+
+def refactor(lib_target, jni_target):
+    assignments = lib_target.block.find_assignments('srcjar_deps')
+    srcjar_deps = assignments[0] if assignments else None
+    if srcjar_deps is None:
+        srcjar_deps = gn_ast.AssignmentWrapper.create_list('srcjar_deps')
+        first_source_assignment = lib_target.block.find_assignments(
+            'sources')[0]
+        lib_target.block.add_child(srcjar_deps, before=first_source_assignment)
+    elif not srcjar_deps.value.is_list():
+        raise RefactorException(
+            f'{lib_target.name}: srcjar_deps is not a list.')
+    srcjar_deps.list_value.add_literal(f':{jni_target.name}')
+
+    processor_assignment = find_processor_assignment(lib_target)
+    processors = processor_assignment.list_value.literals
+    if len(processors) == 1:
+        lib_target.block.remove_child(processor_assignment.node)
+    else:
+        processor_assignment.list_value.remove_literal(_PROCESSOR_DEP)
+
+    fix_visibility(jni_target)
+
+
+def analyze(build_file):
+    targets = build_file.targets
+    jni_targets = [t for t in targets if t.type == 'generate_jni']
+    lib_targets = [t for t in targets if find_processor_assignment(t)]
+
+    if len(jni_targets) == 0 and len(lib_targets) == 0:
+        return
+    # Match up target when there are only one, even when targets use variables
+    # for list values.
+    if len(jni_targets) == 1 and len(lib_targets) == 1:
+        refactor(lib_targets[0], jni_targets[0])
+        return
+
+    jni_target_to_sources = {
+        t.name: find_all_sources(t, build_file)
+        for t in jni_targets
+    }
+    for lib_target in lib_targets:
+        jni_target_name = find_matching_jni_target(lib_target,
+                                                   jni_target_to_sources,
+                                                   build_file)
+        jni_target = build_file.targets_by_name[jni_target_name]
+        refactor(lib_target, jni_target)
+
+
+def main():
+    parser = argparse.ArgumentParser()
+    parser.add_argument('path')
+    args = parser.parse_args()
+    try:
+        build_file = gn_ast.BuildFile.from_file(args.path)
+        analyze(build_file)
+        if build_file.write_changes():
+            print(f'{args.path}: Changes applied.')
+        else:
+            print(f'{args.path}: No changes necessary.')
+    except RefactorException as e:
+        print(f'{args.path}: {e}')
+        sys.exit(1)
+    except Exception:
+        print('Failure on', args.path)
+        raise
+
+
+if __name__ == '__main__':
+    main()
diff --git a/buildtools/deps_revisions.gni b/buildtools/deps_revisions.gni
index 9ab7aaf..cd429cf 100644
--- a/buildtools/deps_revisions.gni
+++ b/buildtools/deps_revisions.gni
@@ -5,5 +5,5 @@
 declare_args() {
   # Used to cause full rebuilds on libc++ rolls. This should be kept in sync
   # with the libcxx_revision vars in //DEPS.
-  libcxx_revision = "055b2e17ae4f0e2c025ad0c7508b01787df17758"
+  libcxx_revision = "38a8ad0f7ea659edb28a57dfd59a7e5399dabeab"
 }
diff --git a/buildtools/third_party/libc++/BUILD.gn b/buildtools/third_party/libc++/BUILD.gn
index 027ed52..306b38c 100644
--- a/buildtools/third_party/libc++/BUILD.gn
+++ b/buildtools/third_party/libc++/BUILD.gn
@@ -81,7 +81,6 @@
     "trunk/src/condition_variable.cpp",
     "trunk/src/condition_variable_destructor.cpp",
     "trunk/src/exception.cpp",
-    "trunk/src/format.cpp",
     "trunk/src/functional.cpp",
     "trunk/src/future.cpp",
     "trunk/src/hash.cpp",
diff --git a/cc/layers/picture_layer_impl_unittest.cc b/cc/layers/picture_layer_impl_unittest.cc
index 32d7762..0ea2bf4 100644
--- a/cc/layers/picture_layer_impl_unittest.cc
+++ b/cc/layers/picture_layer_impl_unittest.cc
@@ -1490,8 +1490,9 @@
     worker_context_provider->GetTestRasterInterface()->set_max_texture_size(
         140);
   }
-  ResetLayerTreeFrameSink(FakeLayerTreeFrameSink::Create3d(
-      viz::TestContextProvider::Create(), std::move(worker_context_provider)));
+  ResetLayerTreeFrameSink(
+      FakeLayerTreeFrameSink::Create3d(viz::TestContextProvider::CreateRaster(),
+                                       std::move(worker_context_provider)));
 
   SetupDrawPropertiesAndUpdateTiles(pending_layer(), 1.f, 1.f, 1.f);
   ASSERT_EQ(1u, pending_layer()->tilings()->num_tilings());
@@ -1529,8 +1530,9 @@
     worker_context_provider->GetTestRasterInterface()->set_max_texture_size(
         140);
   }
-  ResetLayerTreeFrameSink(FakeLayerTreeFrameSink::Create3d(
-      viz::TestContextProvider::Create(), std::move(worker_context_provider)));
+  ResetLayerTreeFrameSink(
+      FakeLayerTreeFrameSink::Create3d(viz::TestContextProvider::CreateRaster(),
+                                       std::move(worker_context_provider)));
 
   SetupDrawPropertiesAndUpdateTiles(active_layer(), 1.f, 1.f, 1.f);
   ASSERT_LE(1u, active_layer()->tilings()->num_tilings());
diff --git a/cc/layers/scrollbar_layer_unittest.cc b/cc/layers/scrollbar_layer_unittest.cc
index b3808e3d..a9af93f4 100644
--- a/cc/layers/scrollbar_layer_unittest.cc
+++ b/cc/layers/scrollbar_layer_unittest.cc
@@ -40,6 +40,7 @@
 #include "components/viz/common/quads/solid_color_draw_quad.h"
 #include "components/viz/test/test_context_provider.h"
 #include "components/viz/test/test_gles2_interface.h"
+#include "components/viz/test/test_raster_interface.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -1421,14 +1422,13 @@
   // Try something extreme to be larger than max texture size, and make it a
   // non-integer for funsies.
   scoped_refptr<viz::TestContextProvider> context =
-      viz::TestContextProvider::Create();
+      viz::TestContextProvider::CreateRaster();
   // Keep the max texture size reasonable so we don't OOM on low end devices
   // (crbug.com/642333).
-  context->UnboundTestContextGL()->set_max_texture_size(512);
+  constexpr int max_texture_size = 512;
+
+  context->UnboundTestRasterInterface()->set_max_texture_size(max_texture_size);
   context->BindToCurrentSequence();
-  int max_texture_size = 0;
-  context->ContextGL()->GetIntegerv(GL_MAX_TEXTURE_SIZE, &max_texture_size);
-  EXPECT_EQ(512, max_texture_size);
   TestResourceUpload(max_texture_size / 9.9f);
 }
 
diff --git a/cc/mojo_embedder/async_layer_tree_frame_sink_unittest.cc b/cc/mojo_embedder/async_layer_tree_frame_sink_unittest.cc
index ef37ae9..840f2524 100644
--- a/cc/mojo_embedder/async_layer_tree_frame_sink_unittest.cc
+++ b/cc/mojo_embedder/async_layer_tree_frame_sink_unittest.cc
@@ -66,7 +66,7 @@
   bg_thread.Start();
 
   scoped_refptr<viz::TestContextProvider> provider =
-      viz::TestContextProvider::Create();
+      viz::TestContextProvider::CreateRaster();
   viz::TestGpuMemoryBufferManager test_gpu_memory_buffer_manager;
 
   mojo::PendingRemote<viz::mojom::CompositorFrameSink> sink_remote;
@@ -131,7 +131,7 @@
       : task_runner_(base::MakeRefCounted<base::TestMockTimeTaskRunner>(
             base::TestMockTimeTaskRunner::Type::kStandalone)),
         display_rect_(1, 1) {
-    auto context_provider = viz::TestContextProvider::Create();
+    auto context_provider = viz::TestContextProvider::CreateRaster();
 
     mojo::PendingRemote<viz::mojom::CompositorFrameSink> sink_remote;
     mojo::PendingReceiver<viz::mojom::CompositorFrameSink> sink_receiver =
diff --git a/cc/paint/paint_op_buffer_fuzzer.cc b/cc/paint/paint_op_buffer_fuzzer.cc
index e4846b9..3f96616e 100644
--- a/cc/paint/paint_op_buffer_fuzzer.cc
+++ b/cc/paint/paint_op_buffer_fuzzer.cc
@@ -144,7 +144,7 @@
                               bytes_for_fonts, &locked_handles);
   }
 
-  auto context_provider_no_support = viz::TestContextProvider::Create();
+  auto context_provider_no_support = viz::TestContextProvider::CreateRaster();
   context_provider_no_support->BindToCurrentSequence();
   CHECK(!context_provider_no_support->GrContext()->supportsDistanceFieldText());
   Raster(context_provider_no_support, font_manager->strike_client(),
diff --git a/cc/paint/transfer_cache_fuzzer.cc b/cc/paint/transfer_cache_fuzzer.cc
index 4bbbf4a..5f21a68 100644
--- a/cc/paint/transfer_cache_fuzzer.cc
+++ b/cc/paint/transfer_cache_fuzzer.cc
@@ -22,7 +22,7 @@
   }
 
   scoped_refptr<viz::TestContextProvider> context_provider =
-      viz::TestContextProvider::Create();
+      viz::TestContextProvider::CreateRaster();
   context_provider->BindToCurrentSequence();
 
   cc::TransferCacheEntryType entry_type =
diff --git a/cc/raster/raster_buffer_provider_perftest.cc b/cc/raster/raster_buffer_provider_perftest.cc
index f9c911dc..4c71a10a 100644
--- a/cc/raster/raster_buffer_provider_perftest.cc
+++ b/cc/raster/raster_buffer_provider_perftest.cc
@@ -110,13 +110,13 @@
   gpu::ContextSupport* ContextSupport() override { return &support_; }
   class GrDirectContext* GrContext() override {
     if (!test_context_provider_) {
-      test_context_provider_ = viz::TestContextProvider::Create();
+      test_context_provider_ = viz::TestContextProvider::CreateRaster();
     }
     return test_context_provider_->GrContext();
   }
   gpu::SharedImageInterface* SharedImageInterface() override {
     if (!test_context_provider_) {
-      test_context_provider_ = viz::TestContextProvider::Create();
+      test_context_provider_ = viz::TestContextProvider::CreateRaster();
     }
     return test_context_provider_->SharedImageInterface();
   }
diff --git a/cc/resources/resource_pool_unittest.cc b/cc/resources/resource_pool_unittest.cc
index 26a223f..2e76151 100644
--- a/cc/resources/resource_pool_unittest.cc
+++ b/cc/resources/resource_pool_unittest.cc
@@ -31,7 +31,7 @@
     auto context_support = std::make_unique<MockContextSupport>();
     context_support_ = context_support.get();
     context_provider_ =
-        viz::TestContextProvider::Create(std::move(context_support));
+        viz::TestContextProvider::CreateRaster(std::move(context_support));
     context_provider_->BindToCurrentSequence();
     resource_provider_ = std::make_unique<viz::ClientResourceProvider>();
     test_task_runner_ = base::MakeRefCounted<base::TestMockTimeTaskRunner>();
diff --git a/cc/slim/test_frame_sink_impl.cc b/cc/slim/test_frame_sink_impl.cc
index 4c4f474..f2f6048 100644
--- a/cc/slim/test_frame_sink_impl.cc
+++ b/cc/slim/test_frame_sink_impl.cc
@@ -87,7 +87,7 @@
   mojo::PendingAssociatedReceiver<viz::mojom::CompositorFrameSink>
       sink_receiver = sink_remote.InitWithNewEndpointAndPassReceiver();
   mojo::PendingReceiver<viz::mojom::CompositorFrameSinkClient> client;
-  auto context_provider = viz::TestContextProvider::Create();
+  auto context_provider = viz::TestContextProvider::CreateRaster();
 
   return base::WrapUnique(new TestFrameSinkImpl(
       std::move(task_runner), std::move(sink_remote), std::move(client),
diff --git a/cc/test/fake_layer_tree_frame_sink.cc b/cc/test/fake_layer_tree_frame_sink.cc
index 8977c7a..7709e5f 100644
--- a/cc/test/fake_layer_tree_frame_sink.cc
+++ b/cc/test/fake_layer_tree_frame_sink.cc
@@ -21,7 +21,7 @@
 namespace cc {
 
 FakeLayerTreeFrameSink::Builder::Builder()
-    : compositor_context_provider_(viz::TestContextProvider::Create()),
+    : compositor_context_provider_(viz::TestContextProvider::CreateRaster()),
       worker_context_provider_(viz::TestContextProvider::CreateWorker()) {}
 
 FakeLayerTreeFrameSink::Builder::~Builder() = default;
diff --git a/cc/test/fake_layer_tree_frame_sink.h b/cc/test/fake_layer_tree_frame_sink.h
index 803d0f6..3199b69 100644
--- a/cc/test/fake_layer_tree_frame_sink.h
+++ b/cc/test/fake_layer_tree_frame_sink.h
@@ -45,15 +45,13 @@
 
     // Calls a function on both the compositor and worker context.
     template <typename... Args>
-    Builder& AllContexts(
-        void (viz::TestGLES2Interface::*compositor_fn)(Args...),
-        void (viz::TestRasterInterface::*worker_fn)(Args...),
-        Args... args) {
+    Builder& AllContexts(void (viz::TestRasterInterface::*context_fn)(Args...),
+                         Args... args) {
       DCHECK(compositor_context_provider_);
-      (compositor_context_provider_->UnboundTestContextGL()->*compositor_fn)(
+      (compositor_context_provider_->UnboundTestRasterInterface()->*context_fn)(
           std::forward<Args>(args)...);
       DCHECK(worker_context_provider_);
-      (worker_context_provider_->UnboundTestRasterInterface()->*worker_fn)(
+      (worker_context_provider_->UnboundTestRasterInterface()->*context_fn)(
           std::forward<Args>(args)...);
 
       return *this;
@@ -94,8 +92,7 @@
 
   static std::unique_ptr<FakeLayerTreeFrameSink> Create3dForGpuRasterization() {
     return Builder()
-        .AllContexts(&viz::TestGLES2Interface::set_gpu_rasterization,
-                     &viz::TestRasterInterface::set_gpu_rasterization, true)
+        .AllContexts(&viz::TestRasterInterface::set_gpu_rasterization, true)
         .Build();
   }
 
diff --git a/cc/test/layer_tree_test.cc b/cc/test/layer_tree_test.cc
index 24de94e..7789ded 100644
--- a/cc/test/layer_tree_test.cc
+++ b/cc/test/layer_tree_test.cc
@@ -1177,7 +1177,8 @@
 
 void LayerTreeTest::RequestNewLayerTreeFrameSink() {
   scoped_refptr<viz::TestContextProvider> shared_context_provider =
-      use_software_renderer() ? nullptr : viz::TestContextProvider::Create();
+      use_software_renderer() ? nullptr
+                              : viz::TestContextProvider::CreateRaster();
   scoped_refptr<viz::TestContextProvider> worker_context_provider =
       use_software_renderer() ? nullptr
                               : viz::TestContextProvider::CreateWorker();
diff --git a/cc/tiles/picture_layer_tiling_set_unittest.cc b/cc/tiles/picture_layer_tiling_set_unittest.cc
index 4621461..bd2f07d 100644
--- a/cc/tiles/picture_layer_tiling_set_unittest.cc
+++ b/cc/tiles/picture_layer_tiling_set_unittest.cc
@@ -243,7 +243,7 @@
                float ideal_contents_scale,
                float expected_scale) {
     scoped_refptr<viz::TestContextProvider> context_provider =
-        viz::TestContextProvider::Create();
+        viz::TestContextProvider::CreateRaster();
     ASSERT_EQ(context_provider->BindToCurrentSequence(),
               gpu::ContextResult::kSuccess);
     std::unique_ptr<viz::ClientResourceProvider> resource_provider =
diff --git a/cc/trees/layer_tree_frame_sink_unittest.cc b/cc/trees/layer_tree_frame_sink_unittest.cc
index ab6ba6f..e04e724 100644
--- a/cc/trees/layer_tree_frame_sink_unittest.cc
+++ b/cc/trees/layer_tree_frame_sink_unittest.cc
@@ -52,7 +52,7 @@
 
 TEST(LayerTreeFrameSinkTest, ContextLossInformsClient) {
   scoped_refptr<viz::TestContextProvider> provider =
-      viz::TestContextProvider::Create();
+      viz::TestContextProvider::CreateRaster();
   scoped_refptr<viz::TestContextProvider> worker_provider =
       viz::TestContextProvider::CreateWorker();
   auto task_runner = base::MakeRefCounted<base::TestSimpleTaskRunner>();
@@ -76,12 +76,12 @@
 
 TEST(LayerTreeFrameSinkTest, ContextLossFailsBind) {
   scoped_refptr<viz::TestContextProvider> context_provider =
-      viz::TestContextProvider::Create();
+      viz::TestContextProvider::CreateRaster();
   scoped_refptr<viz::TestContextProvider> worker_provider =
       viz::TestContextProvider::CreateWorker();
 
   // Lose the context so BindToClient fails.
-  context_provider->UnboundTestContextGL()->set_context_lost(true);
+  context_provider->UnboundTestRasterInterface()->set_context_lost(true);
 
   auto task_runner = base::MakeRefCounted<base::TestSimpleTaskRunner>();
   StubLayerTreeFrameSink layer_tree_frame_sink(context_provider,
@@ -95,7 +95,7 @@
 
 TEST(LayerTreeFrameSinkTest, WorkerContextLossInformsClient) {
   scoped_refptr<viz::TestContextProvider> provider =
-      viz::TestContextProvider::Create();
+      viz::TestContextProvider::CreateRaster();
   scoped_refptr<viz::TestContextProvider> worker_provider =
       viz::TestContextProvider::CreateWorker();
   auto task_runner = base::MakeRefCounted<base::TestSimpleTaskRunner>();
@@ -122,7 +122,7 @@
 
 TEST(LayerTreeFrameSinkTest, WorkerContextLossFailsBind) {
   scoped_refptr<viz::TestContextProvider> context_provider =
-      viz::TestContextProvider::Create();
+      viz::TestContextProvider::CreateRaster();
   scoped_refptr<viz::TestContextProvider> worker_provider =
       viz::TestContextProvider::CreateWorker();
 
diff --git a/cc/trees/layer_tree_host_impl_unittest.cc b/cc/trees/layer_tree_host_impl_unittest.cc
index 59215210..80eca8f3 100644
--- a/cc/trees/layer_tree_host_impl_unittest.cc
+++ b/cc/trees/layer_tree_host_impl_unittest.cc
@@ -11922,7 +11922,7 @@
 }
 
 TEST_P(ScrollUnifiedLayerTreeHostImplTest, UIResourceManagement) {
-  auto test_context_provider = viz::TestContextProvider::Create();
+  auto test_context_provider = viz::TestContextProvider::CreateRaster();
   viz::TestSharedImageInterface* sii =
       test_context_provider->SharedImageInterface();
   CreateHostImpl(DefaultSettings(), FakeLayerTreeFrameSink::Create3d(
@@ -11966,7 +11966,7 @@
 }
 
 TEST_P(ScrollUnifiedLayerTreeHostImplTest, CreateETC1UIResource) {
-  auto test_context_provider = viz::TestContextProvider::Create();
+  auto test_context_provider = viz::TestContextProvider::CreateRaster();
   viz::TestSharedImageInterface* sii =
       test_context_provider->SharedImageInterface();
   CreateHostImpl(DefaultSettings(), FakeLayerTreeFrameSink::Create3d(
@@ -16029,13 +16029,10 @@
     settings.gpu_rasterization_msaa_sample_count = 4;
     auto frame_sink =
         FakeLayerTreeFrameSink::Builder()
-            .AllContexts(&viz::TestGLES2Interface::set_msaa_is_slow,
-                         &viz::TestRasterInterface::set_msaa_is_slow,
+            .AllContexts(&viz::TestRasterInterface::set_msaa_is_slow,
                          msaa_is_slow)
-            .AllContexts(&viz::TestGLES2Interface::set_gpu_rasterization,
-                         &viz::TestRasterInterface::set_gpu_rasterization, true)
-            .AllContexts(&viz::TestGLES2Interface::set_avoid_stencil_buffers,
-                         &viz::TestRasterInterface::set_avoid_stencil_buffers,
+            .AllContexts(&viz::TestRasterInterface::set_gpu_rasterization, true)
+            .AllContexts(&viz::TestRasterInterface::set_avoid_stencil_buffers,
                          avoid_stencil_buffers)
             .Build();
     EXPECT_TRUE(CreateHostImpl(settings, std::move(frame_sink)));
@@ -16080,11 +16077,9 @@
     auto frame_sink =
         FakeLayerTreeFrameSink::Builder()
             .AllContexts(
-                &viz::TestGLES2Interface::set_support_multisample_compatibility,
                 &viz::TestRasterInterface::set_multisample_compatibility,
                 support_multisample_compatibility)
-            .AllContexts(&viz::TestGLES2Interface::set_gpu_rasterization,
-                         &viz::TestRasterInterface::set_gpu_rasterization, true)
+            .AllContexts(&viz::TestRasterInterface::set_gpu_rasterization, true)
             .Build();
     EXPECT_TRUE(CreateHostImpl(settings, std::move(frame_sink)));
   }
diff --git a/cc/trees/layer_tree_host_unittest.cc b/cc/trees/layer_tree_host_unittest.cc
index 2d18a1d9..bfe2e6b 100644
--- a/cc/trees/layer_tree_host_unittest.cc
+++ b/cc/trees/layer_tree_host_unittest.cc
@@ -596,7 +596,7 @@
     auto main_support = std::make_unique<MockContextSupport>();
     mock_main_context_support_ = main_support.get();
     auto test_main_context_provider =
-        viz::TestContextProvider::Create(std::move(main_support));
+        viz::TestContextProvider::CreateRaster(std::move(main_support));
 
     // Create the main viz::RasterContextProvider with a MockContextSupport.
     auto worker_support = std::make_unique<MockContextSupport>();
@@ -2575,7 +2575,7 @@
   void SetUpUnboundContextProviders(
       viz::TestContextProvider* context_provider,
       viz::TestContextProvider* worker_provider) override {
-    context_provider->UnboundTestContextGL()->set_gpu_rasterization(true);
+    context_provider->UnboundTestRasterInterface()->set_gpu_rasterization(true);
     worker_provider->UnboundTestRasterInterface()->set_gpu_rasterization(true);
   }
 
@@ -6632,7 +6632,7 @@
       viz::TestContextProvider* worker_provider) override {
     // The test contexts have gpu raster disabled by default.
     gpu::Capabilities caps =
-        context_provider->UnboundTestContextGL()->test_capabilities();
+        context_provider->UnboundTestRasterInterface()->capabilities();
     EXPECT_FALSE(caps.gpu_rasterization);
     gpu::Capabilities worker_caps =
         worker_provider->UnboundTestRasterInterface()->capabilities();
@@ -6684,7 +6684,7 @@
   void SetUpUnboundContextProviders(
       viz::TestContextProvider* context_provider,
       viz::TestContextProvider* worker_provider) override {
-    context_provider->UnboundTestContextGL()->set_gpu_rasterization(true);
+    context_provider->UnboundTestRasterInterface()->set_gpu_rasterization(true);
     worker_provider->UnboundTestRasterInterface()->set_gpu_rasterization(true);
   }
 
@@ -6735,7 +6735,7 @@
   void SetUpUnboundContextProviders(
       viz::TestContextProvider* context_provider,
       viz::TestContextProvider* worker_provider) override {
-    context_provider->UnboundTestContextGL()->set_gpu_rasterization(true);
+    context_provider->UnboundTestRasterInterface()->set_gpu_rasterization(true);
     worker_provider->UnboundTestRasterInterface()->set_gpu_rasterization(true);
   }
 
@@ -6784,9 +6784,9 @@
   void SetUpUnboundContextProviders(
       viz::TestContextProvider* context_provider,
       viz::TestContextProvider* worker_provider) override {
-    viz::TestGLES2Interface* gl = context_provider->UnboundTestContextGL();
-    gl->set_gpu_rasterization(true);
-    gl->set_support_multisample_compatibility(false);
+    auto* compositor = context_provider->UnboundTestRasterInterface();
+    compositor->set_gpu_rasterization(true);
+    compositor->set_multisample_compatibility(false);
     auto* worker = worker_provider->UnboundTestRasterInterface();
     worker->set_gpu_rasterization(true);
     worker->set_multisample_compatibility(false);
@@ -7449,7 +7449,7 @@
   void SetUpUnboundContextProviders(
       viz::TestContextProvider* context_provider,
       viz::TestContextProvider* worker_provider) override {
-    context_provider->UnboundTestContextGL()->set_gpu_rasterization(true);
+    context_provider->UnboundTestRasterInterface()->set_gpu_rasterization(true);
     worker_provider->UnboundTestRasterInterface()->set_gpu_rasterization(true);
   }
 
@@ -7495,7 +7495,7 @@
   void SetUpUnboundContextProviders(
       viz::TestContextProvider* context_provider,
       viz::TestContextProvider* worker_provider) override {
-    context_provider->UnboundTestContextGL()->set_gpu_rasterization(true);
+    context_provider->UnboundTestRasterInterface()->set_gpu_rasterization(true);
     worker_provider->UnboundTestRasterInterface()->set_gpu_rasterization(true);
   }
 
@@ -8146,7 +8146,7 @@
   void SetUpUnboundContextProviders(
       viz::TestContextProvider* context_provider,
       viz::TestContextProvider* worker_provider) override {
-    context_provider->UnboundTestContextGL()->set_gpu_rasterization(true);
+    context_provider->UnboundTestRasterInterface()->set_gpu_rasterization(true);
     worker_provider->UnboundTestRasterInterface()->set_gpu_rasterization(true);
   }
 
@@ -8190,9 +8190,8 @@
         host_impl->layer_tree_frame_sink()->context_provider();
     ASSERT_TRUE(context_provider);
 
-    auto* gr_context = context_provider->GrContext();
-    ASSERT_TRUE(gr_context);
-    const uint32_t max_texture_size = gr_context->maxTextureSize();
+    const uint32_t max_texture_size =
+        context_provider->ContextCapabilities().max_texture_size;
     ASSERT_GT(static_cast<uint32_t>(large_image_size_.width()),
               max_texture_size);
   }
diff --git a/cc/trees/layer_tree_host_unittest_context.cc b/cc/trees/layer_tree_host_unittest_context.cc
index 6ec2cc77..03ec905 100644
--- a/cc/trees/layer_tree_host_unittest_context.cc
+++ b/cc/trees/layer_tree_host_unittest_context.cc
@@ -817,7 +817,7 @@
   LayerTreeHostContextTestDontUseLostResources() : lost_context_(false) {
     context_should_support_io_surface_ = true;
 
-    child_context_provider_ = viz::TestContextProvider::Create();
+    child_context_provider_ = viz::TestContextProvider::CreateRaster();
     auto result = child_context_provider_->BindToCurrentSequence();
     CHECK_EQ(result, gpu::ContextResult::kSuccess);
     shared_bitmap_manager_ = std::make_unique<viz::TestSharedBitmapManager>();
diff --git a/cc/trees/layer_tree_host_unittest_picture.cc b/cc/trees/layer_tree_host_unittest_picture.cc
index baf3a7516..94b6459 100644
--- a/cc/trees/layer_tree_host_unittest_picture.cc
+++ b/cc/trees/layer_tree_host_unittest_picture.cc
@@ -154,7 +154,7 @@
   void SetUpUnboundContextProviders(
       viz::TestContextProvider* context_provider,
       viz::TestContextProvider* worker_provider) override {
-    context_provider->UnboundTestContextGL()->set_gpu_rasterization(true);
+    context_provider->UnboundTestRasterInterface()->set_gpu_rasterization(true);
     worker_provider->UnboundTestRasterInterface()->set_gpu_rasterization(true);
   }
 
diff --git a/cc/trees/property_tree.cc b/cc/trees/property_tree.cc
index 5311145..5007b75 100644
--- a/cc/trees/property_tree.cc
+++ b/cc/trees/property_tree.cc
@@ -348,10 +348,10 @@
       property_trees()->scroll_tree().Node(sticky_data->scroll_ancestor);
   const TransformNode* transform_node = Node(scroll_node->transform_id);
   DCHECK(transform_node);
-  gfx::PointF scroll_offset =
-      property_trees()->scroll_tree().current_scroll_offset(
-          scroll_node->element_id);
-  gfx::PointF scroll_position(scroll_offset.x(), scroll_offset.y());
+  // We need the scroll offset from the transform tree, not the scroll tree.
+  // Tracking the scroll tree here would make sticky elements run "ahead" of a
+  // main-repainted scroll.
+  gfx::PointF scroll_position = transform_node->scroll_offset;
   if (transform_node->scrolls) {
     // The scroll position does not include snapping which shifts the scroll
     // offset to align to a pixel boundary, we need to manually include it here.
diff --git a/cc/trees/single_thread_proxy.cc b/cc/trees/single_thread_proxy.cc
index f2a4c1de..6005bcde 100644
--- a/cc/trees/single_thread_proxy.cc
+++ b/cc/trees/single_thread_proxy.cc
@@ -724,6 +724,21 @@
   }
 }
 
+viz::BeginFrameArgs SingleThreadProxy::BeginImplFrameForTest(
+    base::TimeTicks frame_begin_time) {
+  viz::BeginFrameArgs begin_frame_args(viz::BeginFrameArgs::Create(
+      BEGINFRAME_FROM_HERE, viz::BeginFrameArgs::kManualSourceId,
+      begin_frame_sequence_number_++, frame_begin_time, base::TimeTicks(),
+      viz::BeginFrameArgs::DefaultInterval(), viz::BeginFrameArgs::NORMAL));
+
+  // Start the impl frame.
+  {
+    DebugScopedSetImplThread impl(task_runner_provider_);
+    WillBeginImplFrame(begin_frame_args);
+  }
+  return begin_frame_args;
+}
+
 void SingleThreadProxy::CompositeImmediatelyForTest(
     base::TimeTicks frame_begin_time,
     bool raster,
@@ -749,16 +764,8 @@
     }
   }
 
-  viz::BeginFrameArgs begin_frame_args(viz::BeginFrameArgs::Create(
-      BEGINFRAME_FROM_HERE, viz::BeginFrameArgs::kManualSourceId,
-      begin_frame_sequence_number_++, frame_begin_time, base::TimeTicks(),
-      viz::BeginFrameArgs::DefaultInterval(), viz::BeginFrameArgs::NORMAL));
-
-  // Start the impl frame.
-  {
-    DebugScopedSetImplThread impl(task_runner_provider_);
-    WillBeginImplFrame(begin_frame_args);
-  }
+  viz::BeginFrameArgs begin_frame_args =
+      BeginImplFrameForTest(frame_begin_time);  // IN-TEST
 
   // Run the "main thread" and get it to commit.
   {
diff --git a/cc/trees/single_thread_proxy.h b/cc/trees/single_thread_proxy.h
index d3c28a9a..9c7dc8a7 100644
--- a/cc/trees/single_thread_proxy.h
+++ b/cc/trees/single_thread_proxy.h
@@ -162,6 +162,8 @@
     return host_impl_.get();
   }
 
+  viz::BeginFrameArgs BeginImplFrameForTest(base::TimeTicks frame_begin_time);
+
  protected:
   SingleThreadProxy(LayerTreeHost* layer_tree_host,
                     LayerTreeHostSingleThreadClient* client,
diff --git a/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/tasks/SingleTabSwitcherCoordinator.java b/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/tasks/SingleTabSwitcherCoordinator.java
index 9dbf5ea..7e94417 100644
--- a/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/tasks/SingleTabSwitcherCoordinator.java
+++ b/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/tasks/SingleTabSwitcherCoordinator.java
@@ -156,23 +156,29 @@
         };
 
         if (mLastActiveTab != null) {
-            mLastActiveTabObserver = new EmptyTabObserver() {
-                @Override
-                public void onClosingStateChanged(Tab tab, boolean closing) {
-                    if (closing) {
-                        updateTrackingTab(null);
-                        setVisibility(false);
-                        mLastActiveTab.removeObserver(mLastActiveTabObserver);
-                        mLastActiveTab = null;
-                        mLastActiveTabObserver = null;
-                        if (mSnapshotParentViewRunnable != null) {
-                            mSnapshotParentViewRunnable.run();
-                        }
+            beginObserving();
+        }
+    }
+
+    private void beginObserving() {
+        if (mLastActiveTab == null) return;
+
+        mLastActiveTabObserver = new EmptyTabObserver() {
+            @Override
+            public void onClosingStateChanged(Tab tab, boolean closing) {
+                if (closing) {
+                    updateTrackingTab(null);
+                    setVisibility(false);
+                    mLastActiveTab.removeObserver(mLastActiveTabObserver);
+                    mLastActiveTab = null;
+                    mLastActiveTabObserver = null;
+                    if (mSnapshotParentViewRunnable != null) {
+                        mSnapshotParentViewRunnable.run();
                     }
                 }
-            };
-            mLastActiveTab.addObserver(mLastActiveTabObserver);
-        }
+            }
+        };
+        mLastActiveTab.addObserver(mLastActiveTabObserver);
     }
 
     // TabSwitcher implementation.
@@ -231,7 +237,12 @@
      */
     public boolean updateTrackingTab(Tab tabToTrack) {
         assert mIsTablet;
-        return mMediatorOnTablet.setTab(tabToTrack);
+        boolean hasTabToTrack = mMediatorOnTablet.setTab(tabToTrack);
+        if (hasTabToTrack && mLastActiveTab == null) {
+            mLastActiveTab = tabToTrack;
+            beginObserving();
+        }
+        return hasTabToTrack;
     }
 
     public void destroy() {
diff --git a/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceOnTabletTest.java b/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceOnTabletTest.java
index d21c159..b70b25ba 100644
--- a/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceOnTabletTest.java
+++ b/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceOnTabletTest.java
@@ -80,6 +80,7 @@
     @Rule
     public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
     private static final String TAB_URL = "https://foo.com/";
+    private static final String TAB_URL_1 = "https://bar.com/";
 
     @Test
     @MediumTest
@@ -211,22 +212,35 @@
     @Feature({"StartSurface"})
     @CommandLineFlags.Add({START_SURFACE_ON_TABLET_TEST_PARAMS})
     public void testSingleTabCardGoneAfterTabClosed() throws IOException {
-        StartSurfaceTestUtils.prepareTabStateMetadataFile(new int[] {0}, new String[] {TAB_URL}, 0);
+        StartSurfaceTestUtils.prepareTabStateMetadataFile(
+                new int[] {0, 1}, new String[] {TAB_URL, TAB_URL_1}, 0);
         StartSurfaceTestUtils.startMainActivityFromLauncher(mActivityTestRule);
         ChromeTabbedActivity cta = mActivityTestRule.getActivity();
         StartSurfaceTestUtils.waitForTabModel(cta);
 
         // Verifies that a new NTP is created and set as the active Tab.
         verifyTabCountAndActiveTabUrl(
-                cta, 2, UrlConstants.NTP_URL, true /* expectHomeSurfaceUiShown */);
+                cta, 3, UrlConstants.NTP_URL, true /* expectHomeSurfaceUiShown */);
         waitForNtpLoaded(cta.getActivityTab());
 
         NewTabPage ntp = (NewTabPage) cta.getActivityTab().getNativePage();
         Assert.assertTrue(ntp.isSingleTabCardVisibleForTesting());
 
+        // Verifies that closing the tracking Tab will remove the "continue browsing" card from
+        // the NTP.
         Tab lastActiveTab = cta.getCurrentTabModel().getTabAt(0);
         TestThreadUtils.runOnUiThreadBlocking(
                 () -> { cta.getCurrentTabModel().closeTab(lastActiveTab); });
+        Assert.assertEquals(2, cta.getCurrentTabModel().getCount());
+        Assert.assertFalse(ntp.isSingleTabCardVisibleForTesting());
+
+        // Tests to set another tracking Tab on the NTP.
+        Tab newTrackingTab = cta.getCurrentTabModel().getTabAt(0);
+        TestThreadUtils.runOnUiThreadBlocking(() -> { ntp.showHomeSurfaceUi(newTrackingTab); });
+        Assert.assertTrue(ntp.isSingleTabCardVisibleForTesting());
+
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { cta.getCurrentTabModel().closeTab(newTrackingTab); });
         Assert.assertEquals(1, cta.getCurrentTabModel().getCount());
         Assert.assertFalse(ntp.isSingleTabCardVisibleForTesting());
     }
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogView.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogView.java
index cfe4b08..1b49057 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogView.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogView.java
@@ -40,7 +40,6 @@
 import org.chromium.ui.KeyboardVisibilityDelegate;
 import org.chromium.ui.base.DeviceFormFactor;
 import org.chromium.ui.base.ViewUtils;
-import org.chromium.ui.interpolators.BakedBezierInterpolator;
 import org.chromium.ui.modelutil.PropertyModel;
 
 import java.lang.annotation.Retention;
@@ -198,14 +197,14 @@
         ObjectAnimator dialogFadeInAnimator =
                 ObjectAnimator.ofFloat(mDialogContainerView, View.ALPHA, 0f, 1f);
         mBasicFadeInAnimation.play(dialogFadeInAnimator);
-        mBasicFadeInAnimation.setInterpolator(BakedBezierInterpolator.FADE_IN_CURVE);
+        mBasicFadeInAnimation.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN_INTERPOLATOR);
         mBasicFadeInAnimation.setDuration(DIALOG_ANIMATION_DURATION);
 
         mBasicFadeOutAnimation = new AnimatorSet();
         ObjectAnimator dialogFadeOutAnimator =
                 ObjectAnimator.ofFloat(mDialogContainerView, View.ALPHA, 1f, 0f);
         mBasicFadeOutAnimation.play(dialogFadeOutAnimator);
-        mBasicFadeOutAnimation.setInterpolator(BakedBezierInterpolator.FADE_OUT_CURVE);
+        mBasicFadeOutAnimation.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN_INTERPOLATOR);
         mBasicFadeOutAnimation.setDuration(DIALOG_ANIMATION_DURATION);
         mBasicFadeOutAnimation.addListener(new AnimatorListenerAdapter() {
             @Override
@@ -361,7 +360,7 @@
         Rect parentRect = new Rect();
         mParent.getGlobalVisibleRect(parentRect);
         rect.offset(0, -parentRect.top);
-        // Setup a dummy animation card that looks the same as the original tab grid card for
+        // Setup a stand-in animation card that looks the same as the original tab grid card for
         // animation.
         updateAnimationCardView(mItemView);
 
@@ -677,7 +676,7 @@
             return;
         }
 
-        // Update the dummy animation card view with the actual item view from grid tab switcher
+        // Update the stand-in animation card view with the actual item view from grid tab switcher
         // recyclerView.
         FrameLayout.LayoutParams params =
                 (FrameLayout.LayoutParams) mAnimationCardView.getLayoutParams();
diff --git a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogViewTest.java b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogViewTest.java
index 2dd6803..d60f3fe 100644
--- a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogViewTest.java
+++ b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogViewTest.java
@@ -25,10 +25,10 @@
 import org.junit.runner.RunWith;
 
 import org.chromium.base.test.UiThreadTest;
+import org.chromium.base.test.util.Batch;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.Criteria;
 import org.chromium.base.test.util.CriteriaHelper;
-import org.chromium.base.test.util.DisabledTest;
 import org.chromium.chrome.browser.tasks.tab_management.TabGridDialogView.VisibilityListener;
 import org.chromium.chrome.tab_ui.R;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
@@ -43,11 +43,13 @@
  * BlankUiTestActivity Tests for the {@link TabGridDialogView}.
  */
 @RunWith(ChromeJUnit4ClassRunner.class)
+@Batch(Batch.UNIT_TESTS)
 public class TabGridDialogViewTest extends BlankUiTestActivityTestCase {
     private int mToolbarHeight;
     private int mTopMargin;
     private int mSideMargin;
-    private FrameLayout mDummyParent;
+    private FrameLayout mTestParent;
+    private View mSourceView;
     private View mUngroupBar;
     private View mAnimationCardView;
     private View mBackgroundFrameView;
@@ -60,12 +62,12 @@
     public void setUpTest() throws Exception {
         super.setUpTest();
         TestThreadUtils.runOnUiThreadBlocking(() -> {
-            mDummyParent = new FrameLayout(getActivity());
-            getActivity().setContentView(mDummyParent);
+            mTestParent = new FrameLayout(getActivity());
+            getActivity().setContentView(mTestParent);
             LayoutInflater.from(getActivity())
-                    .inflate(R.layout.tab_grid_dialog_layout, mDummyParent, true);
+                    .inflate(R.layout.tab_grid_dialog_layout, mTestParent, true);
 
-            mTabGridDialogView = mDummyParent.findViewById(R.id.dialog_parent_view);
+            mTabGridDialogView = mTestParent.findViewById(R.id.dialog_parent_view);
             mTabGridDialogContainer = mTabGridDialogView.findViewById(R.id.dialog_container_view);
             mUngroupBar = mTabGridDialogContainer.findViewById(R.id.dialog_ungroup_bar);
             mUngroupBarTextView = mUngroupBar.findViewById(R.id.dialog_ungroup_bar_text);
@@ -73,7 +75,7 @@
             mAnimationCardView = mTabGridDialogView.findViewById(R.id.dialog_animation_card_view);
             mBackgroundFrameView = mTabGridDialogView.findViewById(R.id.dialog_frame);
             ScrimCoordinator scrimCoordinator =
-                    new ScrimCoordinator(getActivity(), null, mDummyParent, Color.RED);
+                    new ScrimCoordinator(getActivity(), null, mTestParent, Color.RED);
             mTabGridDialogView.setupScrimCoordinator(scrimCoordinator);
             mTabGridDialogView.setScrimClickRunnable(() -> {});
 
@@ -151,7 +153,7 @@
     public void testUpdateUngroupBar() {
         AtomicReference<ColorStateList> showTextColorReference = new AtomicReference<>();
         AtomicReference<ColorStateList> hoverTextColorReference = new AtomicReference<>();
-        // Initialize the dialog with dummy views.
+        // Initialize the dialog with stand-in views.
         TestThreadUtils.runOnUiThreadBlocking(() -> {
             mTabGridDialogContainer.removeAllViews();
             View toolbarView = new View(getActivity());
@@ -275,16 +277,18 @@
 
     @Test
     @MediumTest
-    @DisabledTest(message = "https://crbug.com/1036552")
     public void testDialog_ZoomInZoomOut() {
         // TODO(crbug.com/1075677): figure out a stable way to separate different stages of the
         // animation so that we can verify the alpha and view hierarchy of the animation-related
         // views.
         AtomicReference<ViewGroup> parentViewReference = new AtomicReference<>();
-        // Setup the animation with a dummy animation source view.
+        // Setup the animation with a stand-in animation source view.
         TestThreadUtils.runOnUiThreadBlocking(() -> {
-            View sourceView = new View(getActivity());
-            mTabGridDialogView.setupDialogAnimation(sourceView);
+            mSourceView = new View(getActivity());
+            mTestParent.addView(mSourceView, 0, new FrameLayout.LayoutParams(100, 100));
+        });
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mTabGridDialogView.setupDialogAnimation(mSourceView);
             parentViewReference.set((ViewGroup) mTabGridDialogContainer.getParent());
             Assert.assertFalse(mTabGridDialogContainer.isFocused());
         });
@@ -350,12 +354,14 @@
 
     @Test
     @MediumTest
-    @DisabledTest(message = "https://crbug.com/1036552")
     public void testDialog_ZoomInFadeOut() {
-        // Setup the animation with a dummy animation source view.
+        // Setup the animation with a stand-in animation source view.
         TestThreadUtils.runOnUiThreadBlocking(() -> {
-            View sourceView = new View(getActivity());
-            mTabGridDialogView.setupDialogAnimation(sourceView);
+            mSourceView = new View(getActivity());
+            mTestParent.addView(mSourceView, 0, new FrameLayout.LayoutParams(100, 100));
+        });
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mTabGridDialogView.setupDialogAnimation(mSourceView);
             Assert.assertFalse(mTabGridDialogContainer.isFocused());
         });
         // Show the dialog.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkManagerMediator.java b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkManagerMediator.java
index 0ff69aed..1e7d176 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkManagerMediator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkManagerMediator.java
@@ -1035,11 +1035,9 @@
     }
 
     private ListItem buildSearchBoxRow() {
-        // TODO(https://crbug.com/1439583): On search, also hide back button, update title, toolbar
-        // menu buttons.
         PropertyModel propertyModel =
                 new PropertyModel.Builder(BookmarkSearchBoxRowProperties.ALL_KEYS)
-                        .with(BookmarkSearchBoxRowProperties.QUERY_CALLBACK, this::search)
+                        .with(BookmarkSearchBoxRowProperties.QUERY_CALLBACK, this::onQueryCallback)
                         .build();
         return new ListItem(ViewType.SEARCH_BOX, propertyModel);
     }
@@ -1093,7 +1091,7 @@
         propertyModel.set(ImprovedBookmarkRowProperties.SELECTED, false);
         propertyModel.set(ImprovedBookmarkRowProperties.SELECTION_ACTIVE, false);
         propertyModel.set(ImprovedBookmarkRowProperties.DRAG_ENABLED, false);
-        // TODO(crbug.com/1442044): Invesigate caching ModelList for the menu.
+        // TODO(crbug.com/1442044): Investigate caching ModelList for the menu.
         propertyModel.set(ImprovedBookmarkRowProperties.LIST_MENU_BUTTON_DELEGATE,
                 () -> createListMenuForBookmark(propertyModel));
         propertyModel.set(ImprovedBookmarkRowProperties.EDITABLE, item.isEditable());
@@ -1128,7 +1126,7 @@
 
         if (item.isFolder()) {
             int type = item.getId().getType();
-            Drawable folderDrawable = null;
+            Drawable folderDrawable;
             if (useImages) {
                 model.set(ImprovedBookmarkRowProperties.FOLDER_CHILD_COUNT,
                         BookmarkUtils.getChildCountForDisplay(item.getId(), mBookmarkModel));
@@ -1200,8 +1198,6 @@
         listItems.add(buildMenuListItem(R.string.bookmark_item_move, 0, 0, canMove));
         listItems.add(buildMenuListItem(R.string.bookmark_item_delete, 0, 0));
 
-        boolean canReorder = bookmarkItem != null && bookmarkItem.isReorderable()
-                && !Objects.equals(getCurrentFolderId(), mBookmarkModel.getRootFolderId());
         if (getCurrentUiMode() == BookmarkUiMode.SEARCHING) {
             listItems.add(buildMenuListItem(R.string.bookmark_show_in_folder, 0, 0));
         }
@@ -1278,9 +1274,6 @@
 
     void setPriceTrackingEnabled(PropertyModel model, boolean enabled) {
         BookmarkListEntry entry = model.get(BookmarkManagerProperties.BOOKMARK_LIST_ENTRY);
-        PowerBookmarkMeta meta = entry.getPowerBookmarkMeta();
-        CommerceSubscription sub =
-                PowerBookmarkUtils.createCommerceSubscriptionForPowerBookmarkMeta(meta);
 
         Callback<Boolean> callback = success -> {
             if (!success) return;
@@ -1303,6 +1296,17 @@
         }
     }
 
+    private void onQueryCallback(String text) {
+        final @BookmarkUiMode int currentUiMode = getCurrentUiMode();
+        if (!TextUtils.isEmpty(text)) {
+            // #setState will no-op if we're already in a search state.
+            setState(BookmarkUiState.createSearchState());
+            search(text);
+        } else if (currentUiMode == BookmarkUiMode.SEARCHING) {
+            onEndSearch();
+        }
+    }
+
     // Testing methods.
 
     /** Whether to prevent the bookmark model from fully loading for testing. */
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbar.java b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbar.java
index 19b0dbc..f7ddee7 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbar.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbar.java
@@ -88,10 +88,12 @@
             showNormalView();
         }
 
-        if (mBookmarkUiMode == BookmarkUiMode.SEARCHING) {
-            showSearchView(mSoftKeyboardVisible);
-        } else {
-            hideSearchView(/*notify=*/false);
+        if (!BookmarkFeatures.isAndroidImprovedBookmarksEnabled()) {
+            if (mBookmarkUiMode == BookmarkUiMode.SEARCHING) {
+                showSearchView(mSoftKeyboardVisible);
+            } else {
+                hideSearchView(/*notify=*/false);
+            }
         }
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarMediator.java b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarMediator.java
index 7bea70f5..09a98cf 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarMediator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarMediator.java
@@ -207,8 +207,13 @@
 
     @Override
     public void onUiModeChanged(@BookmarkUiMode int mode) {
-        mModel.set(
-                BookmarkToolbarProperties.SOFT_KEYBOARD_VISIBLE, mode == BookmarkUiMode.SEARCHING);
+        if (BookmarkFeatures.isAndroidImprovedBookmarksEnabled()) {
+            // TODO(https://crbug.com/1439583): Update title and buttons.
+        } else {
+            mModel.set(BookmarkToolbarProperties.SOFT_KEYBOARD_VISIBLE,
+                    mode == BookmarkUiMode.SEARCHING);
+        }
+
         mModel.set(BookmarkToolbarProperties.BOOKMARK_UI_MODE, mode);
         if (mode == BookmarkUiMode.LOADING) {
             mModel.set(BookmarkToolbarProperties.NAVIGATION_BUTTON_STATE, NavigationButton.NONE);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarTest.java
index df9acbc3..fd4f57c 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarTest.java
@@ -4,6 +4,11 @@
 
 package org.chromium.chrome.browser.bookmarks;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.when;
 
 import android.app.Activity;
@@ -19,10 +24,10 @@
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import org.junit.After;
-import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.TestRule;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.Mockito;
@@ -36,9 +41,12 @@
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.bookmarks.BookmarkUiState.BookmarkUiMode;
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.browser.incognito.IncognitoUtils;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
+import org.chromium.chrome.test.util.browser.Features;
+import org.chromium.chrome.test.util.browser.Features.EnableFeatures;
 import org.chromium.components.bookmarks.BookmarkId;
 import org.chromium.components.bookmarks.BookmarkItem;
 import org.chromium.components.bookmarks.BookmarkType;
@@ -76,6 +84,8 @@
 
     @Rule
     public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+    @Rule
+    public TestRule mFeaturesProcessorRule = new Features.JUnitProcessor();
 
     @Mock
     BookmarkDelegate mBookmarkDelegate;
@@ -94,6 +104,7 @@
     private WindowAndroid mWindowAndroid;
     private ViewGroup mContentView;
     private BookmarkToolbar mBookmarkToolbar;
+
     private final List<ActivityMonitor> mActivityMonitorList = new ArrayList<>();
 
     @Before
@@ -182,13 +193,6 @@
         when(mSelectionDelegate.getSelectedItems()).thenReturn(new HashSet<>(bookmarkIdList));
     }
 
-    private ActivityMonitor addBlockingActivityMonitor(Class clazz) {
-        ActivityMonitor activityMonitor = new ActivityMonitor(clazz.getName(), null, true);
-        InstrumentationRegistry.getInstrumentation().addMonitor(activityMonitor);
-        mActivityMonitorList.add(activityMonitor);
-        return activityMonitor;
-    }
-
     private void verifySelectionMenuVisibility(int... hiddenMenuIds) {
         verifyMenuVisibility(SELECTION_MENU_IDS, hiddenMenuIds);
     }
@@ -198,8 +202,8 @@
         for (int menuId : applicableMenuIds) {
             boolean isVisible = !hiddenIdSet.contains(menuId);
             MenuItem menuItem = mBookmarkToolbar.getMenu().findItem(menuId);
-            Assert.assertNotNull(menuId);
-            Assert.assertEquals("Mismatched visibility for menu item " + menuItem, isVisible,
+            assertNotNull(menuId);
+            assertEquals("Mismatched visibility for menu item " + menuItem, isVisible,
                     menuItem.isVisible());
         }
     }
@@ -221,7 +225,7 @@
         initializeNormal();
         mBookmarkToolbar.setBookmarkUiMode(BookmarkUiMode.SEARCHING);
         mBookmarkToolbar.onNavigationBack();
-        Assert.assertFalse(mBookmarkToolbar.isSearching());
+        assertFalse(mBookmarkToolbar.isSearching());
     }
 
     @Test
@@ -231,7 +235,7 @@
         initializeNormal();
 
         MenuItem menuItem = mBookmarkToolbar.getMenu().findItem(R.id.close_menu_id);
-        Assert.assertNotNull(menuItem);
+        assertNotNull(menuItem);
     }
 
     @Test
@@ -242,7 +246,7 @@
         mBookmarkToolbar.setIsDialogUi(false);
 
         MenuItem menuItem = mBookmarkToolbar.getMenu().findItem(R.id.close_menu_id);
-        Assert.assertNull(menuItem);
+        assertNull(menuItem);
     }
 
     @Test
@@ -366,7 +370,7 @@
     public void testOnSelectionStateChange_selectedThenNot_searching() {
         initializeNormal();
         mBookmarkToolbar.setBookmarkUiMode(BookmarkUiMode.SEARCHING);
-        Assert.assertTrue(mBookmarkToolbar.isSearching());
+        assertTrue(mBookmarkToolbar.isSearching());
 
         when(mSelectionDelegate.isSelectionEnabled()).thenReturn(true);
         mBookmarkToolbar.onSelectionStateChange(Collections.singletonList(BOOKMARK_ID_ONE));
@@ -377,7 +381,7 @@
         verifySelectionMenuVisibility(R.id.selection_mode_edit_menu_id,
                 R.id.selection_mode_move_menu_id, R.id.selection_mode_delete_menu_id,
                 R.id.selection_open_in_new_tab_id, R.id.selection_open_in_incognito_tab_id);
-        Assert.assertTrue(mBookmarkToolbar.isSearching());
+        assertTrue(mBookmarkToolbar.isSearching());
     }
 
     @Test
@@ -387,11 +391,21 @@
         initializeNormal();
 
         mBookmarkToolbar.setDragEnabled(true);
-        Assert.assertFalse(
+        assertFalse(
                 mBookmarkToolbar.getMenu().findItem(R.id.selection_mode_edit_menu_id).isEnabled());
 
         mBookmarkToolbar.setDragEnabled(false);
-        Assert.assertTrue(
+        assertTrue(
                 mBookmarkToolbar.getMenu().findItem(R.id.selection_mode_edit_menu_id).isEnabled());
     }
+
+    @Test
+    @SmallTest
+    @UiThreadTest
+    @EnableFeatures(ChromeFeatureList.ANDROID_IMPROVED_BOOKMARKS)
+    public void testSearching_improvedBookmarks() {
+        initializeNormal();
+        mBookmarkToolbar.setBookmarkUiMode(BookmarkUiMode.SEARCHING);
+        assertFalse(mBookmarkToolbar.isSearching());
+    }
 }
\ No newline at end of file
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTest.java
index 1e5bbc87..65bfac10 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTest.java
@@ -513,11 +513,9 @@
      * Test if an action button is shown with correct image and size, and clicking it sends the
      * correct {@link PendingIntent}.
      */
-    // TODO(crbug.com/1420991): Re-enable this test after fixing/diagnosing flakiness.
     @Test
     @SmallTest
     @Feature({"UiCatalogue"})
-    @DisabledTest(message = "https://crbug.com/1420991")
     public void testMultipleActionButtons() throws TimeoutException {
         Bitmap expectedIcon1 = createVectorDrawableBitmap(R.drawable.ic_content_copy_black, 48, 48);
         Bitmap expectedIcon2 =
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadTest.java
index 808e3fb5..b4f6fbc 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadTest.java
@@ -222,7 +222,6 @@
     @Test
     @MediumTest
     @Feature({"Downloads"})
-    @DisabledTest(message = "https://crbug.com/1287296")
     public void testHttpGetDownload() throws Exception {
         mDownloadTestRule.loadUrl(mTestServer.getURL(TEST_DOWNLOAD_DIRECTORY + "get.html"));
         waitForFocus();
@@ -231,13 +230,12 @@
         int callCount = mDownloadTestRule.getChromeDownloadCallCount();
         TouchCommon.singleClickView(currentView);
         Assert.assertTrue(mDownloadTestRule.waitForChromeDownloadToFinish(callCount));
-        Assert.assertTrue(mDownloadTestRule.hasDownload(FILENAME_GZIP, null));
+        Assert.assertTrue(mDownloadTestRule.hasDownloaded(FILENAME_GZIP, null));
     }
 
     @Test
     @MediumTest
     @Feature({"Downloads"})
-    @DisabledTest(message = "https://crbug.com/1287296")
     public void testHttpPostDownload() throws Exception {
         mDownloadTestRule.loadUrl(mTestServer.getURL(TEST_DOWNLOAD_DIRECTORY + "post.html"));
         waitForFocus();
@@ -246,7 +244,7 @@
         int callCount = mDownloadTestRule.getChromeDownloadCallCount();
         TouchCommon.singleClickView(currentView);
         Assert.assertTrue(mDownloadTestRule.waitForChromeDownloadToFinish(callCount));
-        Assert.assertTrue(mDownloadTestRule.hasDownload(FILENAME_TEXT, SUPERBO_CONTENTS));
+        Assert.assertTrue(mDownloadTestRule.hasDownloaded(FILENAME_TEXT, SUPERBO_CONTENTS));
     }
 
     @Test
@@ -287,7 +285,6 @@
     @Test
     @MediumTest
     @Feature({"Downloads"})
-    @DisabledTest(message = "https://crbug.com/1287296")
     public void testUrlEscaping() throws Exception {
         mDownloadTestRule.loadUrl(mTestServer.getURL(TEST_DOWNLOAD_DIRECTORY + "urlescaping.html"));
         waitForFocus();
@@ -296,13 +293,12 @@
         int callCount = mDownloadTestRule.getChromeDownloadCallCount();
         TouchCommon.singleClickView(currentView);
         Assert.assertTrue(mDownloadTestRule.waitForChromeDownloadToFinish(callCount));
-        Assert.assertTrue(mDownloadTestRule.hasDownload(FILENAME_WALLPAPER, null));
+        Assert.assertTrue(mDownloadTestRule.hasDownloaded(FILENAME_WALLPAPER, null));
     }
 
     @Test
     @MediumTest
     @Feature({"Navigation"})
-    @DisabledTest(message = "crbug.com/1261941")
     public void testOMADownloadInterception() throws Exception {
         TestWebServer webServer = TestWebServer.start();
         try {
@@ -324,7 +320,7 @@
             CriteriaHelper.pollUiThread(() -> {
                 Criteria.checkThat(interceptor.mDownloadItem, Matchers.notNullValue());
                 Criteria.checkThat(
-                        interceptor.mDownloadItem.getDownloadInfo().getUrl(), Matchers.is(url));
+                        interceptor.mDownloadItem.getDownloadInfo().getUrl().getSpec(), Matchers.is(url));
             });
         } finally {
             webServer.shutdown();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadTestRule.java b/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadTestRule.java
index 43724892..3e4df0a 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadTestRule.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadTestRule.java
@@ -60,6 +60,26 @@
     }
 
     /**
+     * Checks if a file has downloaded. Is agnostic to the mechanism by which the file
+     * has downloaded.
+     * @param fileName Expected file name. Path is built by appending filename to
+     * the system downloads path.
+     * @param expectedContents Expected contents of the file, or null if the contents should not be
+     * checked.
+     * @throws IOException if there is a problem accessing the file.
+     */
+    public boolean hasDownloaded(String fileName, String expectedContents) throws IOException {
+        File downloadedFile = getDownloadedPath(fileName);
+        if (!downloadedFile.exists()) {
+            return false;
+        }
+        if (expectedContents != null) {
+            checkFileContents(downloadedFile.getAbsolutePath(), expectedContents);
+        }
+        return true;
+    }
+
+    /**
      * Check the download exists in DownloadManager by matching the local file
      * path.
      *
@@ -70,14 +90,11 @@
      * checked.
      */
     public boolean hasDownload(String fileName, String expectedContents) throws IOException {
-        File downloadedFile = new File(DOWNLOAD_DIRECTORY, fileName);
+        File downloadedFile = getDownloadedPath(fileName);
         if (!downloadedFile.exists()) {
-            Log.d(TAG, "The file " + fileName + " does not exist");
             return false;
         }
 
-        String fullPath = downloadedFile.getAbsolutePath();
-
         DownloadManager manager =
                 (DownloadManager) getActivity().getSystemService(Context.DOWNLOAD_SERVICE);
         Cursor cursor = manager.query(new DownloadManager.Query());
@@ -87,16 +104,7 @@
         while (!cursor.isAfterLast()) {
             if (fileName.equals(getTitleFromCursor(cursor))) {
                 if (expectedContents != null) {
-                    FileInputStream stream = new FileInputStream(new File(fullPath));
-                    byte[] data =
-                            new byte[ApiCompatibilityUtils.getBytesUtf8(expectedContents).length];
-                    try {
-                        Assert.assertEquals(stream.read(data), data.length);
-                        String contents = new String(data);
-                        Assert.assertEquals(expectedContents, contents);
-                    } finally {
-                        stream.close();
-                    }
+                    checkFileContents(downloadedFile.getAbsolutePath(), expectedContents);
                 }
                 result = true;
                 break;
@@ -107,6 +115,27 @@
         return result;
     }
 
+    private static File getDownloadedPath(String fileName) {
+        File downloadedFile = new File(DOWNLOAD_DIRECTORY, fileName);
+        if (!downloadedFile.exists()) {
+            Log.d(TAG, "The file " + fileName + " does not exist");
+        }
+        return downloadedFile;
+    }
+
+    private static void checkFileContents(String fullPath, String expectedContents)
+            throws IOException {
+        FileInputStream stream = new FileInputStream(new File(fullPath));
+        byte[] data = new byte[ApiCompatibilityUtils.getBytesUtf8(expectedContents).length];
+        try {
+            Assert.assertEquals(stream.read(data), data.length);
+            String contents = new String(data);
+            Assert.assertEquals(expectedContents, contents);
+        } finally {
+            stream.close();
+        }
+    }
+
     /**
      * Check the last download matches the given name and exists in DownloadManager.
      */
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/status_indicator/StatusIndicatorTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/status_indicator/StatusIndicatorTest.java
index ba4df8f8..26b8a66 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/status_indicator/StatusIndicatorTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/status_indicator/StatusIndicatorTest.java
@@ -34,7 +34,6 @@
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Criteria;
 import org.chromium.base.test.util.CriteriaHelper;
-import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.Restriction;
 import org.chromium.chrome.browser.browser_controls.BrowserControlsStateProvider;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
@@ -153,7 +152,6 @@
 
     @Test
     @MediumTest
-    @DisabledTest(message = "https://crbug.com/1188377")
     public void testShowAfterHide() {
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhoneTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhoneTest.java
index 7095e6d..98a991b 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhoneTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhoneTest.java
@@ -49,7 +49,6 @@
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Criteria;
 import org.chromium.base.test.util.CriteriaHelper;
-import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.Restriction;
 import org.chromium.chrome.browser.ChromeTabbedActivity;
 import org.chromium.chrome.browser.browser_controls.BrowserStateBrowserControlsVisibilityDelegate;
@@ -199,7 +198,7 @@
                             + "if it is visible",
                     offsetWhenParentInvisible, offsetWhenParentVisible, 0);
 
-            // Sanity check that the offset is different when menu button is invisible
+            // Confidence check that the offset is different when menu button is invisible.
             realMenuButtonCoordinator.getMenuButton().setVisibility(View.INVISIBLE);
             assertEquals(realMenuButtonCoordinator.isVisible(), false);
             float offsetWhenButtonInvisible = mToolbar.getLocationBarWidthOffsetForOptionalButton();
@@ -709,7 +708,6 @@
 
     @Test
     @MediumTest
-    @DisabledTest(message = "crbug.com/1440715")
     public void testOptionalButton_DrawnWhenVisible() {
         Drawable drawable = AppCompatResources.getDrawable(
                 mActivityTestRule.getActivity(), R.drawable.ic_toolbar_share_offset_24dp);
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/bookmarks/BookmarkManagerMediatorTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/bookmarks/BookmarkManagerMediatorTest.java
index 4700387..56ebd047 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/bookmarks/BookmarkManagerMediatorTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/bookmarks/BookmarkManagerMediatorTest.java
@@ -1026,4 +1026,27 @@
         // Mostly just verifying that #syncAdapterAndSelectionDelegate() doesn't crash.
         assertEquals(2, mModelList.size());
     }
+
+    @Test
+    @EnableFeatures(ChromeFeatureList.ANDROID_IMPROVED_BOOKMARKS)
+    public void testQueryCallback() {
+        finishLoading();
+        mMediator.openFolder(mFolderId1);
+        assertEquals(3, mModelList.size());
+        assertEquals(ViewType.SEARCH_BOX, mModelList.get(0).type);
+
+        PropertyModel propertyModel = mModelList.get(0).model;
+        Callback<String> queryCallback =
+                propertyModel.get(BookmarkSearchBoxRowProperties.QUERY_CALLBACK);
+        assertNotNull(queryCallback);
+
+        queryCallback.onResult("foo");
+        assertEquals(BookmarkUiMode.SEARCHING, mMediator.getCurrentUiMode());
+        verify(mBookmarkModel).searchBookmarks(eq("foo"), anyInt());
+
+        queryCallback.onResult("");
+        assertEquals(BookmarkUiMode.FOLDER, mMediator.getCurrentUiMode());
+        verify(mBookmarkModel, never()).searchBookmarks(eq(""), anyInt());
+        verify(mBookmarkModel, never()).searchBookmarks(eq(null), anyInt());
+    }
 }
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarMediatorTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarMediatorTest.java
index 38d24de..af154f39 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarMediatorTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/bookmarks/BookmarkToolbarMediatorTest.java
@@ -11,6 +11,7 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 
@@ -54,7 +55,9 @@
 import org.chromium.components.browser_ui.widget.selectable_list.SelectableListToolbar.NavigationButton;
 import org.chromium.components.browser_ui.widget.selectable_list.SelectionDelegate;
 import org.chromium.ui.base.TestActivity;
+import org.chromium.ui.modelutil.PropertyKey;
 import org.chromium.ui.modelutil.PropertyModel;
+import org.chromium.ui.modelutil.PropertyObservable.PropertyObserver;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -95,6 +98,9 @@
     private BookmarkUiPrefs mBookmarkUiPrefs;
     @Mock
     private BookmarkAddNewFolderCoordinator mBookmarkAddNewFolderCoordinator;
+    @Mock
+    private PropertyObserver<PropertyKey> mPropertyObserver;
+
     @Spy
     private Context mContext;
 
@@ -188,6 +194,7 @@
     }
 
     @Test
+    @Features.DisableFeatures({ChromeFeatureList.ANDROID_IMPROVED_BOOKMARKS})
     public void selectionStateChangeHidesKeyboard() {
         mMediator.onUiModeChanged(BookmarkUiMode.SEARCHING);
         assertEquals(true, mModel.get(BookmarkToolbarProperties.SOFT_KEYBOARD_VISIBLE));
@@ -197,6 +204,22 @@
     }
 
     @Test
+    @Features.EnableFeatures({ChromeFeatureList.ANDROID_IMPROVED_BOOKMARKS})
+    public void selectionStateChangeHidesKeyboard_improvedBookmarks() {
+        mModel.addObserver(mPropertyObserver);
+
+        mMediator.onUiModeChanged(BookmarkUiMode.SEARCHING);
+        verify(mPropertyObserver, never())
+                .onPropertyChanged(any(), eq(BookmarkToolbarProperties.SOFT_KEYBOARD_VISIBLE));
+
+        mMediator.onUiModeChanged(BookmarkUiMode.FOLDER);
+        verify(mPropertyObserver, never())
+                .onPropertyChanged(any(), eq(BookmarkToolbarProperties.SOFT_KEYBOARD_VISIBLE));
+
+        mModel.removeObserver(mPropertyObserver);
+    }
+
+    @Test
     public void onFolderStateSet_CurrentFolderIsRoot() {
         doReturn(mBookmarkId).when(mBookmarkModel).getRootFolderId();
         doReturn(false).when(mBookmarkItem).isEditable();
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/features/partialcustomtab/PartialCustomTabBottomSheetStrategyTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/features/partialcustomtab/PartialCustomTabBottomSheetStrategyTest.java
index a16b738c..23dcb22 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/features/partialcustomtab/PartialCustomTabBottomSheetStrategyTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/features/partialcustomtab/PartialCustomTabBottomSheetStrategyTest.java
@@ -60,7 +60,6 @@
 import org.chromium.base.SysUtils;
 import org.chromium.base.test.BaseRobolectricTestRunner;
 import org.chromium.base.test.util.CommandLineFlags;
-import org.chromium.base.test.util.DisableIf;
 import org.chromium.base.test.util.HistogramWatcher;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.customtabs.features.partialcustomtab.PartialCustomTabBaseStrategy.ResizeType;
@@ -690,7 +689,7 @@
     }
 
     @Test
-    @DisableIf.Build(sdk_is_greater_than = Build.VERSION_CODES.Q)
+    @Config(sdk = Build.VERSION_CODES.P)
     public void fixedHeightReactsToSoftKeyboardBelowR() {
         PartialCustomTabBottomSheetStrategy strategy = createPcctAtHeight(500, true);
         assertTabIsAtInitialPos(getWindowAttributes());
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index a0f5b79..3adb8f3 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -5329,6 +5329,9 @@
         <message name="IDS_EXTENSIONS_REQUEST_ACCESS_BUTTON" desc="The text of the request acces button that appears on the toolbar when an extension requests access to the site">
           Allow <ph name="EXTENSIONS_REQUESTING_ACCESS_COUNT">$1<ex>3</ex></ph>?
         </message>
+        <message name="IDS_EXTENSIONS_REQUEST_ACCESS_BUTTON_DISMISSED_TEXT" desc="The text of the button that appears on the toolbar after the user grants access to an extension that requested access to the current site.">
+          Allowed
+        </message>
         <message name="IDS_EXTENSIONS_REQUEST_ACCESS_BUTTON_TOOLTIP_SINGLE_EXTENSION" desc="The tooltip text of the request access button that appears on the toolbar when an extension requests access to the site">
           Click to allow "<ph name="EXTENSIONS_REQUESTING_ACCESS">$1<ex>Extension A</ex></ph>" on <ph name="ORIGIN">$2<ex>google.com</ex></ph>
         </message>
diff --git a/chrome/app/generated_resources_grd/IDS_EXTENSIONS_REQUEST_ACCESS_BUTTON_DISMISSED_TEXT.png.sha1 b/chrome/app/generated_resources_grd/IDS_EXTENSIONS_REQUEST_ACCESS_BUTTON_DISMISSED_TEXT.png.sha1
new file mode 100644
index 0000000..e6673a0
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_EXTENSIONS_REQUEST_ACCESS_BUTTON_DISMISSED_TEXT.png.sha1
@@ -0,0 +1 @@
+56674d659ebb0147c25aab1bbf395f39a35f0de6
\ No newline at end of file
diff --git a/chrome/app/password_manager_ui_strings.grdp b/chrome/app/password_manager_ui_strings.grdp
index d5eff7d..dd011a2f 100644
--- a/chrome/app/password_manager_ui_strings.grdp
+++ b/chrome/app/password_manager_ui_strings.grdp
@@ -583,4 +583,16 @@
   <message name="IDS_PASSWORD_MANAGER_UI_NO_PASSWORDS_FOUND" desc="Text displayed in the passwords page when there are no passwords matching search query.">
     No passwords found
   </message>
+  <message name="IDS_PASSWORD_MANAGER_UI_EDIT_PASSKEY" desc="The title for a dialog, which allows a user to edit existing passkey entry. This dialog includes other details, such as username and website. For consistency, the word 'passkey' is in the glossary with translations already suggested.">
+    Edit passkey
+  </message>
+  <message name="IDS_PASSWORD_MANAGER_UI_DISPLAY_NAME_LABEL" desc="Label for a passkey's display name. This is usually the user's real name, e.g. 'John Doe'.">
+    Display name
+  </message>
+  <message name="IDS_PASSWORD_MANAGER_UI_DISPLAY_NAME_PLACEHOLDER" desc="Placeholder shown when the user has no display name set on a passkey.">
+    No display name
+  </message>
+  <message name="IDS_PASSWORD_MANAGER_UI_USERNAME_PLACEHOLDER" desc="Placeholder shown when the user has no username set on a passkey.">
+    No username
+  </message>
 </grit-part>
diff --git a/chrome/app/password_manager_ui_strings_grdp/IDS_PASSWORD_MANAGER_UI_DISPLAY_NAME_LABEL.png.sha1 b/chrome/app/password_manager_ui_strings_grdp/IDS_PASSWORD_MANAGER_UI_DISPLAY_NAME_LABEL.png.sha1
new file mode 100644
index 0000000..cf858f5
--- /dev/null
+++ b/chrome/app/password_manager_ui_strings_grdp/IDS_PASSWORD_MANAGER_UI_DISPLAY_NAME_LABEL.png.sha1
@@ -0,0 +1 @@
+9c95e8df069b318d072541756a620e05e4dbd6b4
\ No newline at end of file
diff --git a/chrome/app/password_manager_ui_strings_grdp/IDS_PASSWORD_MANAGER_UI_DISPLAY_NAME_PLACEHOLDER.png.sha1 b/chrome/app/password_manager_ui_strings_grdp/IDS_PASSWORD_MANAGER_UI_DISPLAY_NAME_PLACEHOLDER.png.sha1
new file mode 100644
index 0000000..cf858f5
--- /dev/null
+++ b/chrome/app/password_manager_ui_strings_grdp/IDS_PASSWORD_MANAGER_UI_DISPLAY_NAME_PLACEHOLDER.png.sha1
@@ -0,0 +1 @@
+9c95e8df069b318d072541756a620e05e4dbd6b4
\ No newline at end of file
diff --git a/chrome/app/password_manager_ui_strings_grdp/IDS_PASSWORD_MANAGER_UI_EDIT_PASSKEY.png.sha1 b/chrome/app/password_manager_ui_strings_grdp/IDS_PASSWORD_MANAGER_UI_EDIT_PASSKEY.png.sha1
new file mode 100644
index 0000000..cf858f5
--- /dev/null
+++ b/chrome/app/password_manager_ui_strings_grdp/IDS_PASSWORD_MANAGER_UI_EDIT_PASSKEY.png.sha1
@@ -0,0 +1 @@
+9c95e8df069b318d072541756a620e05e4dbd6b4
\ No newline at end of file
diff --git a/chrome/app/password_manager_ui_strings_grdp/IDS_PASSWORD_MANAGER_UI_USERNAME_PLACEHOLDER.png.sha1 b/chrome/app/password_manager_ui_strings_grdp/IDS_PASSWORD_MANAGER_UI_USERNAME_PLACEHOLDER.png.sha1
new file mode 100644
index 0000000..cf858f5
--- /dev/null
+++ b/chrome/app/password_manager_ui_strings_grdp/IDS_PASSWORD_MANAGER_UI_USERNAME_PLACEHOLDER.png.sha1
@@ -0,0 +1 @@
+9c95e8df069b318d072541756a620e05e4dbd6b4
\ No newline at end of file
diff --git a/chrome/app/settings_strings.grdp b/chrome/app/settings_strings.grdp
index fb05652c..14b4c8d 100644
--- a/chrome/app/settings_strings.grdp
+++ b/chrome/app/settings_strings.grdp
@@ -1579,6 +1579,9 @@
   <message name="IDS_SETTINGS_PERFORMANCE_TAB_DISCARDING_EXCEPTIONS_ADD_DIALOG_MANUAL" desc="Label for the second tab of the tab discarding exception list add dialog, where users can manually input a site to be added to the list.">
     Add sites manually
   </message>
+  <message name="IDS_SETTINGS_PERFORMANCE_TAB_DISCARDING_EXCEPTIONS_ACTIVE_SITE_ARIA_DESCRIPTION" desc="Accessibilty description which will be read by screen readers when focusing a currently opened site in the list under the first tab of the add exception dialog.">
+    Active site
+  </message>
   <message name="IDS_SETTINGS_PERFORMANCE_TAB_DISCARDING_EXCEPTIONS_ADD_DIALOG_HELP" desc="Label for button that expands the tab discarding exceptions list to show all entries. Sites added to the list will not be discarded by memory saver.">
     Sites you add will always stay active and memory won't be freed up from them. <ph name="BEGIN_LINK">&lt;a href="$1" target="_blank"&gt;</ph>Learn more about site exclusion<ph name="END_LINK">&lt;/a&gt;</ph>
   </message>
diff --git a/chrome/app/settings_strings_grdp/IDS_SETTINGS_PERFORMANCE_TAB_DISCARDING_EXCEPTIONS_ACTIVE_SITE_ARIA_DESCRIPTION.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_PERFORMANCE_TAB_DISCARDING_EXCEPTIONS_ACTIVE_SITE_ARIA_DESCRIPTION.png.sha1
new file mode 100644
index 0000000..418526245
--- /dev/null
+++ b/chrome/app/settings_strings_grdp/IDS_SETTINGS_PERFORMANCE_TAB_DISCARDING_EXCEPTIONS_ACTIVE_SITE_ARIA_DESCRIPTION.png.sha1
@@ -0,0 +1 @@
+b1f46af8eacbd73c35965a9a5fa49f6e15da375f
\ No newline at end of file
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 097a309..d96f137 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -2935,8 +2935,6 @@
       "enterprise/reporting/browser_report_generator_android.h",
       "enterprise/reporting/profile_report_generator_android.cc",
       "enterprise/reporting/profile_report_generator_android.h",
-      "enterprise/reporting/real_time_report_controller_android.cc",
-      "enterprise/reporting/real_time_report_controller_android.h",
       "enterprise/reporting/report_scheduler_android.cc",
       "enterprise/reporting/report_scheduler_android.h",
       "enterprise/reporting/reporting_delegate_factory_android.cc",
@@ -3684,8 +3682,6 @@
       "enterprise/reporting/extension_request/extension_request_report_generator.h",
       "enterprise/reporting/profile_report_generator_desktop.cc",
       "enterprise/reporting/profile_report_generator_desktop.h",
-      "enterprise/reporting/real_time_report_controller_desktop.cc",
-      "enterprise/reporting/real_time_report_controller_desktop.h",
       "enterprise/reporting/real_time_report_generator_desktop.cc",
       "enterprise/reporting/real_time_report_generator_desktop.h",
       "enterprise/reporting/report_generator_desktop.cc",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 7cf8ecd..d331991 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -7372,6 +7372,12 @@
      flag_descriptions::kEnableShortcutCustomizationDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(features::kShortcutCustomization)},
 
+    {"enable-search-customizable-shortcuts-in-launcher",
+     flag_descriptions::kEnableSearchCustomizableShortcutsInLauncherName,
+     flag_descriptions::kEnableSearchCustomizableShortcutsInLauncherDescription,
+     kOsCrOS,
+     FEATURE_VALUE_TYPE(ash::features::kSearchCustomizableShortcutsInLauncher)},
+
     {"enable-search-in-shortcuts-app",
      flag_descriptions::kEnableSearchInShortcutsAppName,
      flag_descriptions::kEnableSearchInShortcutsAppDescription, kOsCrOS,
@@ -10440,6 +10446,14 @@
      kOsDesktop | kOsAndroid,
      FEATURE_VALUE_TYPE(features::kProcessPerSiteUpToMainFrameThreshold)},
 
+#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX) || \
+    BUILDFLAG(IS_FUCHSIA)
+    {"camera-mic-preview", flag_descriptions::kCameraMicPreviewName,
+     flag_descriptions::kCameraMicPreviewDescription,
+     static_cast<unsigned short>(kOsMac | kOsWin | kOsLinux | kOsFuchsia),
+     FEATURE_VALUE_TYPE(features::kCameraMicPreview)},
+#endif
+
 #if BUILDFLAG(IS_CHROMEOS_ASH)
     {"cros-battery-saver-always-on",
      flag_descriptions::kCrosBatterySaverAlwaysOnName,
diff --git a/chrome/browser/about_flags_browsertest.cc b/chrome/browser/about_flags_browsertest.cc
index b0c084df..8252c2f7 100644
--- a/chrome/browser/about_flags_browsertest.cc
+++ b/chrome/browser/about_flags_browsertest.cc
@@ -76,14 +76,13 @@
                           bool enable) {
   EXPECT_TRUE(content::ExecJs(
       contents,
-      base::StringPrintf("var k = document.getElementById('%s');"
-                         "var s = "
-                         "k.querySelector('flags-experiment').shadowRoot."
-                         "querySelector('.experiment-enable-disable');"
-                         "s.focus();"
-                         "s.selectedIndex = %d;"
-                         "s.onchange();",
-                         experiment_id, enable ? 1 : 0)));
+      base::StringPrintf(
+          "var k = document.getElementById('%s');"
+          "var s = k.getElementsByClassName('experiment-enable-disable')[0];"
+          "s.focus();"
+          "s.selectedIndex = %d;"
+          "s.onchange();",
+          experiment_id, enable ? 1 : 0)));
 }
 
 std::string GetOriginListText(content::WebContents* contents,
@@ -381,15 +380,14 @@
   // See https://crbug.com/1038638 for more details.
   EXPECT_TRUE(content::ExecJs(
       contents,
-      base::StringPrintf("var k = document.getElementById('%s');"
-                         "var s = "
-                         "k.querySelector('flags-experiment').shadowRoot."
-                         "querySelector('.experiment-enable-disable');"
-                         "delete s.internal_name;"
-                         "const e = document.createEvent('HTMLEvents');"
-                         "e.initEvent('change', true, true);"
-                         "s.dispatchEvent(e);",
-                         kFlagWithOptionSelectorName),
+      base::StringPrintf(
+          "var k = document.getElementById('%s');"
+          "var s = k.getElementsByClassName('experiment-enable-disable')[0];"
+          "delete s.internal_name;"
+          "const e = document.createEvent('HTMLEvents');"
+          "e.initEvent('change', true, true);"
+          "s.dispatchEvent(e);",
+          kFlagWithOptionSelectorName),
       // Execute script in an isolated world to avoid causing a Trusted Types
       // violation due to eval.
       content::EXECUTE_SCRIPT_DEFAULT_OPTIONS, /*world_id=*/1));
diff --git a/chrome/browser/app_controller_mac_browsertest.mm b/chrome/browser/app_controller_mac_browsertest.mm
index fbeeef6..91567b0 100644
--- a/chrome/browser/app_controller_mac_browsertest.mm
+++ b/chrome/browser/app_controller_mac_browsertest.mm
@@ -2,8 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "base/memory/raw_ptr.h"
-
 #import <Cocoa/Cocoa.h>
 #import <Foundation/Foundation.h>
 #import <objc/runtime.h>
@@ -15,8 +13,8 @@
 #include "base/functional/bind.h"
 #include "base/functional/callback_helpers.h"
 #include "base/mac/foundation_util.h"
-#include "base/mac/scoped_nsobject.h"
 #include "base/mac/scoped_objc_class_swizzler.h"
+#include "base/memory/raw_ptr.h"
 #include "base/path_service.h"
 #include "base/run_loop.h"
 #include "base/scoped_observation.h"
@@ -97,6 +95,10 @@
 #include "ui/views/widget/any_widget_observer.h"
 #include "ui/views/widget/widget.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 namespace {
 
 GURL g_open_shortcut_url = GURL::EmptyGURL();
@@ -448,10 +450,8 @@
   // in the menu.
   PrefService* local_state = g_browser_process->local_state();
   local_state->SetBoolean(prefs::kBrowserGuestModeEnabled, false);
-  base::scoped_nsobject<NSMenuItem> about_menu_item(
-      [[[[NSApp mainMenu] itemWithTag:IDC_CHROME_MENU] submenu]
-          itemWithTag:IDC_ABOUT],
-      base::scoped_policy::RETAIN);
+  NSMenuItem* about_menu_item = [[[NSApp.mainMenu itemWithTag:IDC_CHROME_MENU]
+      submenu] itemWithTag:IDC_ABOUT];
   EXPECT_FALSE([AppController.sharedController
       validateUserInterfaceItem:about_menu_item]);
 }
@@ -523,11 +523,9 @@
   Browser* browser = active_browser_list()->get(0);
   EXPECT_FALSE(browser->profile()->IsGuestSession());
   // "About Chrome" is not available in the menu.
-  base::scoped_nsobject<NSMenu> chrome_submenu(
-      [[NSApp.mainMenu itemWithTag:IDC_CHROME_MENU] submenu],
-      base::scoped_policy::RETAIN);
-  base::scoped_nsobject<NSMenuItem> about_menu_item(
-      [chrome_submenu itemWithTag:IDC_ABOUT], base::scoped_policy::RETAIN);
+  NSMenu* chrome_submenu =
+      [[NSApp.mainMenu itemWithTag:IDC_CHROME_MENU] submenu];
+  NSMenuItem* about_menu_item = [chrome_submenu itemWithTag:IDC_ABOUT];
   EXPECT_FALSE([app_controller validateUserInterfaceItem:about_menu_item]);
   [chrome_submenu update];
   EXPECT_FALSE([about_menu_item isEnabled]);
@@ -588,18 +586,14 @@
   AppController* app_controller = AppController.sharedController;
 
   // Unhandled menu items are disabled.
-  base::scoped_nsobject<NSMenu> file_submenu(
-      [[NSApp.mainMenu itemWithTag:IDC_FILE_MENU] submenu],
-      base::scoped_policy::RETAIN);
-  base::scoped_nsobject<NSMenuItem> close_tab_menu_item(
-      [file_submenu itemWithTag:IDC_CLOSE_TAB], base::scoped_policy::RETAIN);
+  NSMenu* file_submenu = [[NSApp.mainMenu itemWithTag:IDC_FILE_MENU] submenu];
+  NSMenuItem* close_tab_menu_item = [file_submenu itemWithTag:IDC_CLOSE_TAB];
   EXPECT_FALSE([app_controller validateUserInterfaceItem:close_tab_menu_item]);
   [file_submenu update];
   EXPECT_FALSE([close_tab_menu_item isEnabled]);
 
   // Enabled menu items work.
-  base::scoped_nsobject<NSMenuItem> new_window_menu_item(
-      [file_submenu itemWithTag:IDC_NEW_WINDOW], base::scoped_policy::RETAIN);
+  NSMenuItem* new_window_menu_item = [file_submenu itemWithTag:IDC_NEW_WINDOW];
   EXPECT_TRUE([new_window_menu_item isEnabled]);
   EXPECT_TRUE([app_controller validateUserInterfaceItem:new_window_menu_item]);
   // Click on the item and checks that a new browser is opened.
diff --git a/chrome/browser/ash/accessibility/accessibility_manager.cc b/chrome/browser/ash/accessibility/accessibility_manager.cc
index b9118ab..0440d88 100644
--- a/chrome/browser/ash/accessibility/accessibility_manager.cc
+++ b/chrome/browser/ash/accessibility/accessibility_manager.cc
@@ -563,6 +563,11 @@
         prefs->GetBoolean(prefs::kDockedMagnifierEnabled)) {
       return true;
     }
+    if (::features::
+            AreExperimentalAccessibilityColorEnhancementSettingsEnabled() &&
+        prefs->GetBoolean(prefs::kAccessibilityColorFiltering)) {
+      return true;
+    }
   }
   return false;
 }
diff --git a/chrome/browser/ash/accessibility/accessibility_manager_browsertest.cc b/chrome/browser/ash/accessibility/accessibility_manager_browsertest.cc
index 406ace3..e213a12 100644
--- a/chrome/browser/ash/accessibility/accessibility_manager_browsertest.cc
+++ b/chrome/browser/ash/accessibility/accessibility_manager_browsertest.cc
@@ -217,6 +217,15 @@
   return AccessibilityManager::Get()->IsMonoAudioEnabled();
 }
 
+bool IsColorCorrectionEnabled() {
+  return GetActiveUserPrefs()->GetBoolean(prefs::kAccessibilityColorFiltering);
+}
+
+void SetColorCorrectionEnabled(bool enabled) {
+  GetActiveUserPrefs()->SetBoolean(prefs::kAccessibilityColorFiltering,
+                                   enabled);
+}
+
 void SetSelectToSpeakEnabled(bool enabled) {
   AccessibilityManager::Get()->SetSelectToSpeakEnabled(enabled);
 }
@@ -433,7 +442,9 @@
 
   void SetUpCommandLine(base::CommandLine* command_line) override {
     scoped_feature_list_.InitWithFeatures(
-        {features::kOnDeviceSpeechRecognition}, {});
+        {features::kOnDeviceSpeechRecognition,
+         ::features::kExperimentalAccessibilityColorEnhancementSettings},
+        {});
     MixinBasedInProcessBrowserTest::SetUpCommandLine(command_line);
   }
 
@@ -772,9 +783,10 @@
   EXPECT_FALSE(IsSpokenFeedbackEnabled());
   EXPECT_FALSE(IsHighContrastEnabled());
   EXPECT_FALSE(IsAutoclickEnabled());
-  EXPECT_FALSE(ShouldShowAccessibilityMenu());
   EXPECT_FALSE(IsVirtualKeyboardEnabled());
   EXPECT_FALSE(IsMonoAudioEnabled());
+  EXPECT_FALSE(IsColorCorrectionEnabled());
+  EXPECT_FALSE(ShouldShowAccessibilityMenu());
 
   EXPECT_FALSE(ShouldShowAccessibilityMenu());
   SetAlwaysShowMenuEnabledPref(true);
@@ -821,6 +833,11 @@
   EXPECT_TRUE(ShouldShowAccessibilityMenu());
   SetSelectToSpeakEnabled(false);
   EXPECT_FALSE(ShouldShowAccessibilityMenu());
+
+  SetColorCorrectionEnabled(true);
+  EXPECT_TRUE(ShouldShowAccessibilityMenu());
+  SetColorCorrectionEnabled(false);
+  EXPECT_FALSE(ShouldShowAccessibilityMenu());
 }
 
 IN_PROC_BROWSER_TEST_F(AccessibilityManagerTest,
diff --git a/chrome/browser/ash/accessibility/service/accessibility_service_client_browsertest.cc b/chrome/browser/ash/accessibility/service/accessibility_service_client_browsertest.cc
index cd14998..717dc36 100644
--- a/chrome/browser/ash/accessibility/service/accessibility_service_client_browsertest.cc
+++ b/chrome/browser/ash/accessibility/service/accessibility_service_client_browsertest.cc
@@ -16,8 +16,10 @@
 #include "chrome/common/extensions/extension_constants.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "components/keyed_service/core/keyed_service.h"
+#include "content/public/browser/tts_utterance.h"
 #include "content/public/test/browser_test.h"
 #include "services/accessibility/public/mojom/accessibility_service.mojom.h"
+#include "services/accessibility/public/mojom/tts.mojom-shared.h"
 #include "services/accessibility/public/mojom/tts.mojom.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/accessibility/accessibility_features.h"
@@ -94,6 +96,9 @@
              base::OnceCallback<void(bool)> speech_started_callback) override {
     utterance_id_ = utterance_id;
     utterance_ = utterance;
+    lang_ = lang;
+    voice_ = voice;
+    params_ = params;
     content::TtsController::GetInstance()->OnTtsEvent(
         utterance_id, /*event_type=*/content::TTS_EVENT_START, /*char_index=*/0,
         /*length=*/static_cast<int>(utterance.size()),
@@ -137,21 +142,24 @@
   bool IsSpeaking() override { return utterance_id_ != -1; }
 
   void GetVoices(std::vector<content::VoiceData>* voices) override {
-    voices->emplace_back();
-    content::VoiceData& voice = voices->back();
-    voice.native = true;
-    voice.name = "TestyMcTestFace";
-    voice.engine_id = extension_misc::kGoogleSpeechSynthesisExtensionId;
-    voice.events.insert(content::TTS_EVENT_END);
-    voice.events.insert(content::TTS_EVENT_START);
-    voice.events.insert(content::TTS_EVENT_PAUSE);
-    voice.events.insert(content::TTS_EVENT_RESUME);
-    voice.events.insert(content::TTS_EVENT_INTERRUPTED);
-    voice.events.insert(content::TTS_EVENT_WORD);
-    voice.events.insert(content::TTS_EVENT_SENTENCE);
-    voice.events.insert(content::TTS_EVENT_MARKER);
-    voice.events.insert(content::TTS_EVENT_CANCELLED);
-    voice.events.insert(content::TTS_EVENT_ERROR);
+    for (int i = 0; i < 3; i++) {
+      voices->emplace_back();
+      content::VoiceData& voice = voices->back();
+      voice.native = true;
+      voice.name = "TestyMcTestFace" + base::NumberToString(i);
+      voice.lang = "en-NZ";
+      voice.engine_id = extension_misc::kGoogleSpeechSynthesisExtensionId;
+      voice.events.insert(content::TTS_EVENT_END);
+      voice.events.insert(content::TTS_EVENT_START);
+      voice.events.insert(content::TTS_EVENT_PAUSE);
+      voice.events.insert(content::TTS_EVENT_RESUME);
+      voice.events.insert(content::TTS_EVENT_INTERRUPTED);
+      voice.events.insert(content::TTS_EVENT_WORD);
+      voice.events.insert(content::TTS_EVENT_SENTENCE);
+      voice.events.insert(content::TTS_EVENT_MARKER);
+      voice.events.insert(content::TTS_EVENT_CANCELLED);
+      voice.events.insert(content::TTS_EVENT_ERROR);
+    }
   }
 
   void Shutdown() override {}
@@ -177,10 +185,16 @@
   void SetNextUtteranceError(const std::string& error) {
     next_utterance_error_ = error;
   }
+  const std::string& lang() { return lang_; }
+  const content::VoiceData& voice() { return voice_; }
+  const content::UtteranceContinuousParameters& params() { return params_; }
 
  private:
   std::string utterance_ = "";
   int utterance_id_ = -1;
+  std::string lang_ = "";
+  content::VoiceData voice_;
+  content::UtteranceContinuousParameters params_;
   std::string error_ = "";
   std::string next_utterance_error_ = "";
 };
@@ -424,9 +438,9 @@
   fake_service_->RequestTtsVoices(base::BindLambdaForTesting(
       [&waiter](std::vector<ax::mojom::TtsVoicePtr> voices) {
         waiter.Quit();
-        ASSERT_EQ(voices.size(), 1u);
+        ASSERT_EQ(voices.size(), 3u);
         auto& voice = voices[0];
-        EXPECT_EQ(voice->voice_name, "TestyMcTestFace");
+        EXPECT_EQ(voice->voice_name, "TestyMcTestFace0");
         EXPECT_EQ(voice->engine_id,
                   extension_misc::kGoogleSpeechSynthesisExtensionId);
         ASSERT_TRUE(voice->event_types);
@@ -636,4 +650,186 @@
   waiter.Run();
 }
 
+IN_PROC_BROWSER_TEST_F(AccessibilityServiceClientTest, TtsOptions) {
+  MockTtsPlatformImpl tts_platform;
+  auto client = TurnOnAccessibilityService(AssistiveTechnologyType::kChromeVox);
+  fake_service_->BindAnotherTts();
+  base::RunLoop waiter;
+
+  auto options = ax::mojom::TtsOptions::New();
+  options->rate = .5;
+  options->pitch = 1.5;
+  options->volume = .8;
+  options->enqueue = true;
+  options->voice_name = "TestyMcTestFace2";
+  options->engine_id = extension_misc::kGoogleSpeechSynthesisExtensionId;
+  options->lang = "en-NZ";
+  options->on_event = false;
+
+  fake_service_->RequestSpeak(
+      "I can't recall the taste of strawberries", std::move(options),
+      base::BindLambdaForTesting([&waiter, &tts_platform](
+                                     ax::mojom::TtsSpeakResultPtr result) {
+        waiter.Quit();
+        content::UtteranceContinuousParameters params = tts_platform.params();
+        EXPECT_EQ(params.rate, .5);
+        EXPECT_EQ(params.pitch, 1.5);
+        EXPECT_EQ(params.volume, .8);
+        content::VoiceData voice = tts_platform.voice();
+        EXPECT_EQ(voice.name, "TestyMcTestFace2");
+        EXPECT_EQ(tts_platform.lang(), "en-NZ");
+      }));
+  waiter.Run();
+}
+
+IN_PROC_BROWSER_TEST_F(AccessibilityServiceClientTest, TtsOptionsPitchError) {
+  auto client = TurnOnAccessibilityService(AssistiveTechnologyType::kChromeVox);
+  fake_service_->BindAnotherTts();
+  base::RunLoop waiter;
+  auto options = ax::mojom::TtsOptions::New();
+  options->pitch = 3.0;
+
+  fake_service_->RequestSpeak(
+      "You shall not pass", std::move(options),
+      base::BindLambdaForTesting(
+          [&waiter](ax::mojom::TtsSpeakResultPtr result) {
+            waiter.Quit();
+            EXPECT_EQ(result->error, ax::mojom::TtsError::kErrorInvalidPitch);
+          }));
+
+  waiter.Run();
+}
+
+IN_PROC_BROWSER_TEST_F(AccessibilityServiceClientTest, TtsOptionsRateError) {
+  auto client = TurnOnAccessibilityService(AssistiveTechnologyType::kChromeVox);
+  fake_service_->BindAnotherTts();
+  base::RunLoop waiter;
+  auto options = ax::mojom::TtsOptions::New();
+  options->rate = 0.01;
+  fake_service_->RequestSpeak(
+      "For frodo", std::move(options),
+      base::BindLambdaForTesting(
+          [&waiter](ax::mojom::TtsSpeakResultPtr result) {
+            waiter.Quit();
+            EXPECT_EQ(result->error, ax::mojom::TtsError::kErrorInvalidRate);
+          }));
+
+  waiter.Run();
+}
+
+IN_PROC_BROWSER_TEST_F(AccessibilityServiceClientTest, TtsOptionsVolumeError) {
+  auto client = TurnOnAccessibilityService(AssistiveTechnologyType::kChromeVox);
+  fake_service_->BindAnotherTts();
+  base::RunLoop waiter;
+  auto options = ax::mojom::TtsOptions::New();
+  options->volume = 1.5;
+  fake_service_->RequestSpeak(
+      "The board is set. The pieces are moving.", std::move(options),
+      base::BindLambdaForTesting(
+          [&waiter](ax::mojom::TtsSpeakResultPtr result) {
+            waiter.Quit();
+            EXPECT_EQ(result->error, ax::mojom::TtsError::kErrorInvalidVolume);
+          }));
+
+  waiter.Run();
+}
+
+// Starts two requests for speech, the second starting just after the first
+// is in progress. With the option to enqueue, they should not interrupt.
+IN_PROC_BROWSER_TEST_F(AccessibilityServiceClientTest, TtsEnqueue) {
+  MockTtsPlatformImpl tts_platform;
+  auto client =
+      TurnOnAccessibilityService(AssistiveTechnologyType::kSelectToSpeak);
+  fake_service_->BindAnotherTts();
+  base::RunLoop waiter;
+
+  base::RepeatingCallback<void(ax::mojom::TtsEventPtr event)> first_callback =
+      base::BindLambdaForTesting([](ax::mojom::TtsEventPtr event) {
+        EXPECT_EQ(event->type, ax::mojom::TtsEventType::kStart);
+      });
+  auto first_options = ax::mojom::TtsOptions::New();
+  first_options->enqueue = true;
+  first_options->on_event = true;
+  std::unique_ptr<TtsUtteranceClientImpl> first_utterance_client;
+  fake_service_->RequestSpeak(
+      "Shadowfax, show us the meaning of haste.", std::move(first_options),
+      base::BindLambdaForTesting([&first_callback, &first_utterance_client](
+                                     ax::mojom::TtsSpeakResultPtr result) {
+        first_utterance_client = std::make_unique<TtsUtteranceClientImpl>(
+            std::move(result->utterance_client), std::move(first_callback));
+      }));
+  EXPECT_EQ(content::TtsController::GetInstance()->QueueSize(), 0);
+
+  auto second_options = ax::mojom::TtsOptions::New();
+  second_options->enqueue = true;
+  second_options->on_event = true;
+  std::unique_ptr<TtsUtteranceClientImpl> second_utterance_client;
+  fake_service_->RequestSpeak(
+      "Keep it secret. Keep it safe.", std::move(second_options),
+      base::BindLambdaForTesting(
+          [&waiter](ax::mojom::TtsSpeakResultPtr result) {
+            EXPECT_EQ(content::TtsController::GetInstance()->QueueSize(), 1);
+            waiter.Quit();
+          }));
+  waiter.Run();
+}
+
+// Starts two requests for speech, the second starting just after the first
+// is in progress. With the the option to enqueue false, the second interrupts
+// the first.
+IN_PROC_BROWSER_TEST_F(AccessibilityServiceClientTest, TtsInterrupt) {
+  MockTtsPlatformImpl tts_platform;
+  auto client =
+      TurnOnAccessibilityService(AssistiveTechnologyType::kSelectToSpeak);
+  fake_service_->BindAnotherTts();
+  base::RunLoop waiter;
+  int start_count = 0;
+  base::RepeatingCallback<void(ax::mojom::TtsEventPtr event)> first_callback =
+      base::BindLambdaForTesting([&start_count](ax::mojom::TtsEventPtr event) {
+        if (event->type == ax::mojom::TtsEventType::kStart) {
+          // The first event should be started.
+          EXPECT_EQ(start_count, 0);
+          start_count++;
+          return;
+        }
+        // And then interrupted.
+        EXPECT_EQ(event->type, ax::mojom::TtsEventType::kInterrupted);
+      });
+  auto first_options = ax::mojom::TtsOptions::New();
+  first_options->enqueue = true;
+  first_options->on_event = true;
+  std::unique_ptr<TtsUtteranceClientImpl> first_utterance_client;
+  fake_service_->RequestSpeak(
+      "Shadowfax, show us the meaning of haste.", std::move(first_options),
+      base::BindLambdaForTesting([&first_callback, &first_utterance_client](
+                                     ax::mojom::TtsSpeakResultPtr result) {
+        first_utterance_client = std::make_unique<TtsUtteranceClientImpl>(
+            std::move(result->utterance_client), std::move(first_callback));
+      }));
+
+  base::RepeatingCallback<void(ax::mojom::TtsEventPtr event)> second_callback =
+      base::BindLambdaForTesting(
+          [&start_count, &waiter](ax::mojom::TtsEventPtr event) {
+            EXPECT_EQ(event->type, ax::mojom::TtsEventType::kStart);
+            // The second utterance should start after the first started.
+            EXPECT_EQ(start_count, 1);
+            waiter.Quit();
+          });
+
+  auto second_options = ax::mojom::TtsOptions::New();
+  second_options->enqueue = false;
+  second_options->on_event = true;
+  std::unique_ptr<TtsUtteranceClientImpl> second_utterance_client;
+  fake_service_->RequestSpeak(
+      "Keep it secret. Keep it safe.", std::move(second_options),
+      base::BindLambdaForTesting([&second_utterance_client, &second_callback](
+                                     ax::mojom::TtsSpeakResultPtr result) {
+        EXPECT_EQ(content::TtsController::GetInstance()->QueueSize(), 0);
+        second_utterance_client = std::make_unique<TtsUtteranceClientImpl>(
+            std::move(result->utterance_client), std::move(second_callback));
+      }));
+
+  waiter.Run();
+}
+
 }  // namespace ash
diff --git a/chrome/browser/ash/accessibility/service/fake_accessibility_service.cc b/chrome/browser/ash/accessibility/service/fake_accessibility_service.cc
index c11ea47..685ec15 100644
--- a/chrome/browser/ash/accessibility/service/fake_accessibility_service.cc
+++ b/chrome/browser/ash/accessibility/service/fake_accessibility_service.cc
@@ -117,9 +117,18 @@
 void FakeAccessibilityService::RequestSpeak(
     const std::string& utterance,
     base::OnceCallback<void(ax::mojom::TtsSpeakResultPtr)> callback) {
+  auto options = ax::mojom::TtsOptions::New();
+  options->on_event = true;
+  RequestSpeak(utterance, std::move(options), std::move(callback));
+}
+
+void FakeAccessibilityService::RequestSpeak(
+    const std::string& utterance,
+    ax::mojom::TtsOptionsPtr options,
+    base::OnceCallback<void(ax::mojom::TtsSpeakResultPtr)> callback) {
   CHECK_EQ(tts_remotes_.size(), 1u);
   for (auto& tts_client : tts_remotes_) {
-    tts_client->Speak(utterance, std::move(callback));
+    tts_client->Speak(utterance, std::move(options), std::move(callback));
   }
 }
 
diff --git a/chrome/browser/ash/accessibility/service/fake_accessibility_service.h b/chrome/browser/ash/accessibility/service/fake_accessibility_service.h
index d569e518..1470c84 100644
--- a/chrome/browser/ash/accessibility/service/fake_accessibility_service.h
+++ b/chrome/browser/ash/accessibility/service/fake_accessibility_service.h
@@ -86,11 +86,18 @@
   // Waits for Automation events to come in.
   void WaitForAutomationEvents();
 
-  // Sends a request for speech.
+  // Sends a request for speech using the default options, but with on_event
+  // set to true.
   void RequestSpeak(
       const std::string& utterance,
       base::OnceCallback<void(ax::mojom::TtsSpeakResultPtr)> callback);
 
+  // Sends a request for speech using the given options.
+  void RequestSpeak(
+      const std::string& utterance,
+      ax::mojom::TtsOptionsPtr options,
+      base::OnceCallback<void(ax::mojom::TtsSpeakResultPtr)> callback);
+
   // Sends a request to stop speech.
   void RequestStop();
 
diff --git a/chrome/browser/ash/accessibility/service/tts_client_impl.cc b/chrome/browser/ash/accessibility/service/tts_client_impl.cc
index 220908d..b8c26bf 100644
--- a/chrome/browser/ash/accessibility/service/tts_client_impl.cc
+++ b/chrome/browser/ash/accessibility/service/tts_client_impl.cc
@@ -7,6 +7,7 @@
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/tts_controller.h"
 #include "services/accessibility/public/mojom/tts.mojom.h"
+#include "ui/base/l10n/l10n_util.h"
 
 namespace ash {
 
@@ -14,6 +15,14 @@
 
 // The max utterance length allowed by the TTS extension API.
 const int kMaxUtteranceLength = 32768;
+// The minimum speech rate allowed by the TTS extension API.
+const double kMinRate = 0.1;
+// The maximum speech rate allowed by the TTS extension API.
+const double kMaxRate = 10.0;
+// The maximum speech pitch allowed by the TTS extension API.
+const double kMaxPitch = 2.0;
+// The maximum speech volume allowed by the TTS extension API.
+const double kMaxVolume = 1.0;
 
 ax::mojom::TtsEventType ToMojo(content::TtsEventType event_type) {
   switch (event_type) {
@@ -93,6 +102,7 @@
 }
 
 void TtsClientImpl::Speak(const std::string& utterance,
+                          ax::mojom::TtsOptionsPtr options,
                           SpeakCallback callback) {
   auto result = ax::mojom::TtsSpeakResult::New();
   if (utterance.size() > kMaxUtteranceLength) {
@@ -101,15 +111,60 @@
     return;
   }
 
+  // Check for errors in options.
+  // TODO(crbug.com/651711): Centralize the struct validation.
+  if (options->rate < kMinRate || options->rate > kMaxRate) {
+    result->error = ax::mojom::TtsError::kErrorInvalidRate;
+    std::move(callback).Run(std::move(result));
+    return;
+  }
+  if (options->pitch < 0.0 || options->pitch > kMaxPitch) {
+    result->error = ax::mojom::TtsError::kErrorInvalidPitch;
+    std::move(callback).Run(std::move(result));
+    return;
+  }
+  if (options->volume < 0.0 || options->volume > kMaxVolume) {
+    result->error = ax::mojom::TtsError::kErrorInvalidVolume;
+    std::move(callback).Run(std::move(result));
+    return;
+  }
+
+  // Only make the utterance once we know we aren't going to return early.
   std::unique_ptr<content::TtsUtterance> tts_utterance =
       content::TtsUtterance::Create(profile_);
   tts_utterance->SetText(utterance);
   // TODO(b:277221897): Pass a fake GURL matching the ash extension URL.
+  // This will support both UMA and using enhanced network voices in ATP
+  // select-to-speak.
   tts_utterance->SetSrcUrl(GURL(""));
 
-  auto* atpTtsEventHandler = AtpTtsEventHandler::Create();
-  result->utterance_client = atpTtsEventHandler->PassReceiver();
-  tts_utterance->SetEventDelegate(atpTtsEventHandler);
+  tts_utterance->SetContinuousParameters(options->rate, options->pitch,
+                                         options->volume);
+  tts_utterance->SetShouldClearQueue(!options->enqueue);
+  if (options->lang) {
+    std::string lang = options->lang.value();
+    if (!lang.empty() && !l10n_util::IsValidLocaleSyntax(lang)) {
+      result->error = ax::mojom::TtsError::kErrorInvalidLang;
+      std::move(callback).Run(std::move(result));
+      return;
+    }
+    tts_utterance->SetLang(options->lang.value());
+  }
+  if (options->voice_name) {
+    tts_utterance->SetVoiceName(options->voice_name.value());
+  }
+  if (options->engine_id) {
+    tts_utterance->SetEngineId(options->engine_id.value());
+  }
+  if (options->on_event) {
+    auto* atpTtsEventHandler = AtpTtsEventHandler::Create();
+    result->utterance_client = atpTtsEventHandler->PassReceiver();
+    tts_utterance->SetEventDelegate(atpTtsEventHandler);
+  }
+  // Note: we don't need desired/required event types because they aren't
+  // passed by ChromeVox or STS. We don't need an options_dict, it's redundant,
+  // and we don't need a src_id because each ATP utterance has its own callback.
+
   // Send the callback back to ATP with the utterance client.
   result->error = ax::mojom::TtsError::kNoError;
   std::move(callback).Run(std::move(result));
diff --git a/chrome/browser/ash/accessibility/service/tts_client_impl.h b/chrome/browser/ash/accessibility/service/tts_client_impl.h
index e90392e..0adcc5a 100644
--- a/chrome/browser/ash/accessibility/service/tts_client_impl.h
+++ b/chrome/browser/ash/accessibility/service/tts_client_impl.h
@@ -27,7 +27,9 @@
   void Bind(mojo::PendingReceiver<Tts> tts_receiver);
 
   // ax::mojom::Tts:
-  void Speak(const std::string& utterance, SpeakCallback callback) override;
+  void Speak(const std::string& utterance,
+             ax::mojom::TtsOptionsPtr options,
+             SpeakCallback callback) override;
   void Stop() override;
   void Pause() override;
   void Resume() override;
diff --git a/chrome/browser/ash/policy/core/user_cloud_policy_manager_ash.cc b/chrome/browser/ash/policy/core/user_cloud_policy_manager_ash.cc
index b99d501..aac14d45 100644
--- a/chrome/browser/ash/policy/core/user_cloud_policy_manager_ash.cc
+++ b/chrome/browser/ash/policy/core/user_cloud_policy_manager_ash.cc
@@ -40,7 +40,6 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/common/chrome_content_client.h"
 #include "chrome/common/chrome_features.h"
-#include "components/enterprise/browser/reporting/real_time_report_controller.h"
 #include "components/enterprise/browser/reporting/report_generator.h"
 #include "components/enterprise/browser/reporting/report_scheduler.h"
 #include "components/invalidation/impl/profile_invalidation_provider.h"
@@ -722,14 +721,13 @@
   enterprise_reporting::ReportingDelegateFactoryDesktop delegate_factory;
   enterprise_reporting::ReportScheduler::CreateParams params;
   params.client = client();
-  params.delegate = delegate_factory.GetReportSchedulerDelegate();
+  params.delegate =
+      std::make_unique<enterprise_reporting::ReportSchedulerDesktop>(profile_);
   params.report_generator =
       std::make_unique<enterprise_reporting::ReportGenerator>(
           &delegate_factory);
-
-  delegate_factory.SetProfileForRealTimeController(profile_);
-  params.real_time_report_controller =
-      std::make_unique<enterprise_reporting::RealTimeReportController>(
+  params.real_time_report_generator =
+      std::make_unique<enterprise_reporting::RealTimeReportGenerator>(
           &delegate_factory);
 
   report_scheduler_ = std::make_unique<enterprise_reporting::ReportScheduler>(
diff --git a/chrome/browser/ash/policy/dlp/files_policy_notification_manager.cc b/chrome/browser/ash/policy/dlp/files_policy_notification_manager.cc
index b1d498e..902c04a 100644
--- a/chrome/browser/ash/policy/dlp/files_policy_notification_manager.cc
+++ b/chrome/browser/ash/policy/dlp/files_policy_notification_manager.cc
@@ -333,13 +333,14 @@
 
 FilesPolicyNotificationManager::WarningInfo::~WarningInfo() = default;
 
-FilesPolicyNotificationManager::IOTaskInfo::IOTaskInfo(dlp::FileAction action)
+FilesPolicyNotificationManager::FileTaskInfo::FileTaskInfo(
+    dlp::FileAction action)
     : action(action) {}
 
-FilesPolicyNotificationManager::IOTaskInfo::IOTaskInfo(IOTaskInfo&& other) =
-    default;
+FilesPolicyNotificationManager::FileTaskInfo::FileTaskInfo(
+    FileTaskInfo&& other) = default;
 
-FilesPolicyNotificationManager::IOTaskInfo::~IOTaskInfo() = default;
+FilesPolicyNotificationManager::FileTaskInfo::~FileTaskInfo() = default;
 
 void FilesPolicyNotificationManager::HandleFilesPolicyWarningNotificationClick(
     file_manager::io_task::IOTaskId task_id,
@@ -459,7 +460,7 @@
 void FilesPolicyNotificationManager::AddIOTask(
     file_manager::io_task::IOTaskId task_id,
     dlp::FileAction action) {
-  io_tasks_.emplace(std::move(task_id), IOTaskInfo(action));
+  io_tasks_.emplace(std::move(task_id), FileTaskInfo(action));
 }
 
 void FilesPolicyNotificationManager::OnBrowserSetLastActive(Browser* browser) {
diff --git a/chrome/browser/ash/policy/dlp/files_policy_notification_manager.h b/chrome/browser/ash/policy/dlp/files_policy_notification_manager.h
index d7dfd989b..2110126d 100644
--- a/chrome/browser/ash/policy/dlp/files_policy_notification_manager.h
+++ b/chrome/browser/ash/policy/dlp/files_policy_notification_manager.h
@@ -85,7 +85,7 @@
   size_t notification_count_ = 0;
 
  private:
-  // Holds all information related to IO task warning. Any extra information
+  // Holds all information related to file task warning. Any extra information
   // needed for custom messaging should be added here.
   struct WarningInfo {
     WarningInfo() = delete;
@@ -104,11 +104,11 @@
     OnDlpRestrictionCheckedCallback warning_callback;
   };
 
-  // Holds needed information for each tracked IO task.
-  struct IOTaskInfo {
-    explicit IOTaskInfo(dlp::FileAction action);
-    IOTaskInfo(IOTaskInfo&& other);
-    ~IOTaskInfo();
+  // Holds needed information for each tracked file task.
+  struct FileTaskInfo {
+    explicit FileTaskInfo(dlp::FileAction action);
+    FileTaskInfo(FileTaskInfo&& other);
+    ~FileTaskInfo();
 
     // Should have value only if there's warning.
     absl::optional<WarningInfo> warning_info;
@@ -201,7 +201,7 @@
   content::BrowserContext* context_;
 
   // A map from tracked IO tasks ids to their info.
-  std::map<file_manager::io_task::IOTaskId, IOTaskInfo> io_tasks_;
+  std::map<file_manager::io_task::IOTaskId, FileTaskInfo> io_tasks_;
 
   base::WeakPtrFactory<FilesPolicyNotificationManager> weak_factory_{this};
 };
diff --git a/chrome/browser/ash/policy/reporting/metrics_reporting/apps/app_events_observer.cc b/chrome/browser/ash/policy/reporting/metrics_reporting/apps/app_events_observer.cc
index 5f35e4dc..5d097b1 100644
--- a/chrome/browser/ash/policy/reporting/metrics_reporting/apps/app_events_observer.cc
+++ b/chrome/browser/ash/policy/reporting/metrics_reporting/apps/app_events_observer.cc
@@ -6,6 +6,7 @@
 
 #include <memory>
 
+#include "base/check.h"
 #include "base/containers/contains.h"
 #include "base/memory/ptr_util.h"
 #include "base/memory/weak_ptr.h"
@@ -19,6 +20,7 @@
 #include "chrome/browser/ash/policy/reporting/metrics_reporting/apps/app_platform_metrics_retriever.h"
 #include "chrome/browser/ash/policy/reporting/metrics_reporting/metric_reporting_prefs.h"
 #include "chrome/browser/profiles/profile.h"
+#include "components/prefs/scoped_user_pref_update.h"
 #include "components/reporting/metrics/metric_event_observer.h"
 #include "components/reporting/metrics/reporting_settings.h"
 #include "components/reporting/proto/synced/metric_data.pb.h"
@@ -53,11 +55,50 @@
       reporting_settings));
 }
 
+AppEventsObserver::AppInstallTracker::AppInstallTracker(
+    base::WeakPtr<Profile> profile)
+    : profile_(profile) {}
+
+AppEventsObserver::AppInstallTracker::~AppInstallTracker() = default;
+
+void AppEventsObserver::AppInstallTracker::Add(base::StringPiece app_id) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (!profile_) {
+    // Profile destroyed. Skip.
+    return;
+  }
+  DCHECK(!Contains(app_id)) << "App already being tracked";
+  ScopedListPrefUpdate apps_installed_pref(profile_->GetPrefs(),
+                                           ::ash::reporting::kAppsInstalled);
+  apps_installed_pref->Append(app_id);
+}
+
+void AppEventsObserver::AppInstallTracker::Remove(base::StringPiece app_id) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (!profile_) {
+    // Profile destroyed. Skip.
+    return;
+  }
+  DCHECK(Contains(app_id)) << "App not being tracked";
+  ScopedListPrefUpdate apps_installed_pref(profile_->GetPrefs(),
+                                           ::ash::reporting::kAppsInstalled);
+  apps_installed_pref->EraseValue(base::Value(app_id));
+}
+
+bool AppEventsObserver::AppInstallTracker::Contains(
+    base::StringPiece app_id) const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(profile_);
+  return base::Contains(
+      profile_->GetPrefs()->GetList(::ash::reporting::kAppsInstalled), app_id);
+}
+
 AppEventsObserver::AppEventsObserver(
     base::WeakPtr<Profile> profile,
     std::unique_ptr<AppPlatformMetricsRetriever> app_platform_metrics_retriever,
     const ReportingSettings* reporting_settings)
     : profile_(profile),
+      app_install_tracker_(std::make_unique<AppInstallTracker>(profile)),
       app_platform_metrics_retriever_(
           std::move(app_platform_metrics_retriever)),
       reporting_settings_(reporting_settings) {
@@ -100,9 +141,17 @@
                                        ::apps::InstallTime app_install_time) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(reporting_settings_);
-  if (!profile_ || !::ash::reporting::IsAppTypeAllowed(
-                       app_type, reporting_settings_.get(),
-                       ::ash::reporting::kReportAppInventory)) {
+  if (!profile_ || app_install_tracker_->Contains(app_id)) {
+    // Either the profile was destroyed or the app was already installed
+    // (likely in a prior session). Skip.
+    return;
+  }
+
+  // Track app install to prevent future install event reports.
+  app_install_tracker_->Add(app_id);
+  if (!::ash::reporting::IsAppTypeAllowed(
+          app_type, reporting_settings_.get(),
+          ::ash::reporting::kReportAppInventory)) {
     return;
   }
 
@@ -173,9 +222,17 @@
     ::apps::UninstallSource app_uninstall_source) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(reporting_settings_);
-  if (!profile_ || !::ash::reporting::IsAppTypeAllowed(
-                       app_type, reporting_settings_.get(),
-                       ::ash::reporting::kReportAppInventory)) {
+  if (!profile_) {
+    // Profile destroyed. Return.
+    return;
+  }
+  if (app_install_tracker_->Contains(app_id)) {
+    // Stop tracking app install if it is being tracked.
+    app_install_tracker_->Remove(app_id);
+  }
+  if (!::ash::reporting::IsAppTypeAllowed(
+          app_type, reporting_settings_.get(),
+          ::ash::reporting::kReportAppInventory)) {
     return;
   }
 
diff --git a/chrome/browser/ash/policy/reporting/metrics_reporting/apps/app_events_observer.h b/chrome/browser/ash/policy/reporting/metrics_reporting/apps/app_events_observer.h
index 2685ad15..0d9757a 100644
--- a/chrome/browser/ash/policy/reporting/metrics_reporting/apps/app_events_observer.h
+++ b/chrome/browser/ash/policy/reporting/metrics_reporting/apps/app_events_observer.h
@@ -11,6 +11,7 @@
 #include "base/memory/weak_ptr.h"
 #include "base/scoped_observation.h"
 #include "base/sequence_checker.h"
+#include "base/strings/string_piece_forward.h"
 #include "base/thread_annotations.h"
 #include "chrome/browser/apps/app_service/metrics/app_platform_metrics.h"
 #include "chrome/browser/ash/policy/reporting/metrics_reporting/apps/app_platform_metrics_retriever.h"
@@ -53,6 +54,39 @@
   void SetReportingEnabled(bool is_enabled) override;
 
  private:
+  // Tracker that tracks app installs in the user pref store and helps
+  // determine if the app was already installed. The `AppRegistryCache` attempts
+  // to notify observers of app updates that facilitate tracking new installs,
+  // but only if the component is initialized before app is actually installed.
+  // It normally reports a list of apps registered on the device on init once
+  // app publishers report them, and the tracker helps identify apps that were
+  // newly installed so we can ignore the ones that were previously installed.
+  // TODO (go/add-app-storage-in-app-service): This will be deprecated in favor
+  // of the unified app storage setup in the app service once it is implemented.
+  class AppInstallTracker {
+   public:
+    explicit AppInstallTracker(base::WeakPtr<Profile> profile);
+    AppInstallTracker(const AppInstallTracker& other) = delete;
+    AppInstallTracker& operator=(const AppInstallTracker& other) = delete;
+    ~AppInstallTracker();
+
+    // Adds the specified app id for tracking purposes.
+    void Add(base::StringPiece app_id);
+
+    // Removes the specified app id.
+    void Remove(base::StringPiece app_id);
+
+    // Returns true if the specified app is being tracked in the user pref
+    // store. False otherwise.
+    bool Contains(base::StringPiece app_id) const;
+
+   private:
+    SEQUENCE_CHECKER(sequence_checker_);
+
+    // Weak pointer to the user profile. Needed to access the user pref store.
+    const base::WeakPtr<Profile> profile_;
+  };
+
   AppEventsObserver(base::WeakPtr<Profile> profile,
                     std::unique_ptr<AppPlatformMetricsRetriever>
                         app_platform_metrics_retriever,
@@ -88,15 +122,21 @@
   // for reporting purposes.
   base::WeakPtr<Profile> profile_;
 
+  // App install tracker used by the event observer to filter out install event
+  // notifications that include pre-installed apps.
+  const std::unique_ptr<AppInstallTracker> app_install_tracker_
+      GUARDED_BY_CONTEXT(sequence_checker_);
+
   // Retriever that retrieves the `AppPlatformMetrics` component so the
   // `AppEventsObserver` can start observing app events.
   const std::unique_ptr<AppPlatformMetricsRetriever>
-      app_platform_metrics_retriever_;
+      app_platform_metrics_retriever_ GUARDED_BY_CONTEXT(sequence_checker_);
 
   // Pointer to the reporting settings that controls app inventory event
   // reporting. Guaranteed to outlive the observer because it is managed by the
   // `MetricReportingManager`.
-  const raw_ptr<const ReportingSettings> reporting_settings_;
+  const raw_ptr<const ReportingSettings> reporting_settings_
+      GUARDED_BY_CONTEXT(sequence_checker_);
 
   // Observer for tracking app events. Will be reset if the `AppPlatformMetrics`
   // component gets destructed before the event observer.
diff --git a/chrome/browser/ash/policy/reporting/metrics_reporting/apps/app_events_observer_browsertest.cc b/chrome/browser/ash/policy/reporting/metrics_reporting/apps/app_events_observer_browsertest.cc
index f634e1d..93c1433 100644
--- a/chrome/browser/ash/policy/reporting/metrics_reporting/apps/app_events_observer_browsertest.cc
+++ b/chrome/browser/ash/policy/reporting/metrics_reporting/apps/app_events_observer_browsertest.cc
@@ -6,6 +6,8 @@
 #include <string>
 #include <vector>
 
+#include "ash/shell.h"
+#include "base/test/scoped_feature_list.h"
 #include "base/values.h"
 #include "chrome/browser/apps/app_service/app_service_proxy.h"
 #include "chrome/browser/apps/app_service/app_service_proxy_ash.h"
@@ -14,6 +16,7 @@
 #include "chrome/browser/ash/policy/affiliation/affiliation_mixin.h"
 #include "chrome/browser/ash/policy/affiliation/affiliation_test_helper.h"
 #include "chrome/browser/ash/policy/core/device_policy_cros_browser_test.h"
+#include "chrome/browser/ash/policy/reporting/metrics_reporting/metric_reporting_manager.h"
 #include "chrome/browser/ash/policy/reporting/metrics_reporting/metric_reporting_prefs.h"
 #include "chrome/browser/ash/profiles/profile_helper.h"
 #include "chrome/browser/policy/dm_token_utils.h"
@@ -34,12 +37,15 @@
 #include "components/services/app_service/public/protos/app_types.pb.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/test_launcher.h"
+#include "content/public/test/test_utils.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/mojom/manifest/display_mode.mojom-shared.h"
 #include "url/gurl.h"
 
+using ::testing::Contains;
 using ::testing::Eq;
+using ::testing::SizeIs;
 using ::testing::StrEq;
 
 namespace reporting {
@@ -89,6 +95,7 @@
     crypto_home_mixin_.MarkUserAsExisting(affiliation_mixin_.account_id());
     ::policy::SetDMTokenForTesting(
         ::policy::DMToken::CreateValidToken(kDMToken));
+    scoped_feature_list_.InitAndEnableFeature(kEnableAppEventsObserver);
   }
 
   void SetUpCommandLine(base::CommandLine* command_line) override {
@@ -97,20 +104,6 @@
     ::policy::DevicePolicyCrosBrowserTest::SetUpCommandLine(command_line);
   }
 
-  void SetUpOnMainThread() override {
-    ::policy::DevicePolicyCrosBrowserTest::SetUpOnMainThread();
-    if (content::IsPreTest()) {
-      // Preliminary setup - set up affiliated user.
-      ::policy::AffiliationTestHelper::PreLoginUser(
-          affiliation_mixin_.account_id());
-      return;
-    }
-
-    // Login as affiliated user otherwise.
-    ::policy::AffiliationTestHelper::LoginUser(affiliation_mixin_.account_id());
-    SetAllowedAppReportingTypes({::ash::reporting::kAppCategoryPWA});
-  }
-
   // Helper that installs a standalone webapp with the specified start url.
   ::web_app::AppId InstallStandaloneWebApp(const GURL& start_url) {
     auto web_app_info = std::make_unique<WebAppInstallInfo>();
@@ -145,17 +138,23 @@
   ::policy::DevicePolicyCrosTestHelper test_helper_;
   ::policy::AffiliationMixin affiliation_mixin_{&mixin_host_, &test_helper_};
   ::ash::CryptohomeMixin crypto_home_mixin_{&mixin_host_};
+  base::test::ScopedFeatureList scoped_feature_list_;
 };
 
 IN_PROC_BROWSER_TEST_F(AppEventsObserverBrowserTest, PRE_ReportInstalledApp) {
-  // Dummy case that sets up the affiliated user through SetUpOnMainThread
-  // PRE-condition.
+  // Set up affiliated user.
+  ::policy::AffiliationTestHelper::PreLoginUser(
+      affiliation_mixin_.account_id());
 }
 
 IN_PROC_BROWSER_TEST_F(AppEventsObserverBrowserTest, ReportInstalledApp) {
+  // Login as affiliated user and set policy.
+  ::policy::AffiliationTestHelper::LoginUser(affiliation_mixin_.account_id());
+  SetAllowedAppReportingTypes({::ash::reporting::kAppCategoryPWA});
+
   ::chromeos::MissiveClientTestObserver missive_observer(base::BindRepeating(
       &IsMetricEventOfType, MetricEventType::APP_INSTALLED));
-  const auto& app_id = InstallStandaloneWebApp(GURL(kWebAppUrl));
+  const auto app_id = InstallStandaloneWebApp(GURL(kWebAppUrl));
   const auto [priority, record] = missive_observer.GetNextEnqueuedRecord();
   const auto metric_data = AssertEvent(priority, record);
   ASSERT_TRUE(
@@ -174,15 +173,54 @@
   EXPECT_THAT(
       app_install_data.app_install_time(),
       Eq(::apps::ApplicationInstallTime::APPLICATION_INSTALL_TIME_RUNNING));
+  EXPECT_THAT(profile()->GetPrefs()->GetList(::ash::reporting::kAppsInstalled),
+              Contains(app_id).Times(1));
+}
+
+IN_PROC_BROWSER_TEST_F(AppEventsObserverBrowserTest,
+                       PRE_PRE_ReportPreinstalledApp) {
+  // Set up affiliated user.
+  ::policy::AffiliationTestHelper::PreLoginUser(
+      affiliation_mixin_.account_id());
+}
+
+IN_PROC_BROWSER_TEST_F(AppEventsObserverBrowserTest,
+                       PRE_ReportPreinstalledApp) {
+  // Login as affiliated user and install app before closing the session.
+  ::policy::AffiliationTestHelper::LoginUser(affiliation_mixin_.account_id());
+  const auto app_id = InstallStandaloneWebApp(GURL(kWebAppUrl));
+  ASSERT_THAT(profile()->GetPrefs()->GetList(::ash::reporting::kAppsInstalled),
+              Contains(app_id).Times(1));
+  ::ash::Shell::Get()->session_controller()->RequestSignOut();
+}
+
+IN_PROC_BROWSER_TEST_F(AppEventsObserverBrowserTest, ReportPreinstalledApp) {
+  ::chromeos::MissiveClientTestObserver missive_observer(base::BindRepeating(
+      &IsMetricEventOfType, MetricEventType::APP_INSTALLED));
+  ::policy::AffiliationTestHelper::LoginUser(affiliation_mixin_.account_id());
+  SetAllowedAppReportingTypes({::ash::reporting::kAppCategoryPWA});
+  ASSERT_THAT(profile()->GetPrefs()->GetList(::ash::reporting::kAppsInstalled),
+              SizeIs(1));
+
+  const auto app_id = InstallStandaloneWebApp(GURL(kWebAppUrl));
+  ::content::RunAllTasksUntilIdle();
+  ASSERT_FALSE(missive_observer.HasNewEnqueuedRecords());
+  EXPECT_THAT(profile()->GetPrefs()->GetList(::ash::reporting::kAppsInstalled),
+              Contains(app_id).Times(1));
 }
 
 IN_PROC_BROWSER_TEST_F(AppEventsObserverBrowserTest, PRE_ReportLaunchedApp) {
-  // Dummy case that sets up the affiliated user through SetUpOnMainThread
-  // PRE-condition.
+  // Set up affiliated user.
+  ::policy::AffiliationTestHelper::PreLoginUser(
+      affiliation_mixin_.account_id());
 }
 
 IN_PROC_BROWSER_TEST_F(AppEventsObserverBrowserTest, ReportLaunchedApp) {
-  const auto& app_id = InstallStandaloneWebApp(GURL(kWebAppUrl));
+  // Login as affiliated user and set policy.
+  ::policy::AffiliationTestHelper::LoginUser(affiliation_mixin_.account_id());
+  SetAllowedAppReportingTypes({::ash::reporting::kAppCategoryPWA});
+
+  const auto app_id = InstallStandaloneWebApp(GURL(kWebAppUrl));
   ::chromeos::MissiveClientTestObserver missive_observer(
       base::BindRepeating(&IsMetricEventOfType, MetricEventType::APP_LAUNCHED));
   ::web_app::LaunchWebAppBrowser(profile(), app_id);
@@ -201,12 +239,20 @@
 }
 
 IN_PROC_BROWSER_TEST_F(AppEventsObserverBrowserTest, PRE_ReportUninstalledApp) {
-  // Dummy case that sets up the affiliated user through SetUpOnMainThread
-  // PRE-condition.
+  // Set up affiliated user.
+  ::policy::AffiliationTestHelper::PreLoginUser(
+      affiliation_mixin_.account_id());
 }
 
 IN_PROC_BROWSER_TEST_F(AppEventsObserverBrowserTest, ReportUninstalledApp) {
-  const auto& app_id = InstallStandaloneWebApp(GURL(kWebAppUrl));
+  // Login as affiliated user and set policy.
+  ::policy::AffiliationTestHelper::LoginUser(affiliation_mixin_.account_id());
+  SetAllowedAppReportingTypes({::ash::reporting::kAppCategoryPWA});
+
+  const auto app_id = InstallStandaloneWebApp(GURL(kWebAppUrl));
+  ASSERT_THAT(profile()->GetPrefs()->GetList(::ash::reporting::kAppsInstalled),
+              Contains(app_id).Times(1));
+
   ::chromeos::MissiveClientTestObserver missive_observer(base::BindRepeating(
       &IsMetricEventOfType, MetricEventType::APP_UNINSTALLED));
   UninstallStandaloneWebApp(app_id);
@@ -222,6 +268,8 @@
   EXPECT_THAT(app_uninstall_data.app_id(), StrEq(kWebAppUrl));
   EXPECT_THAT(app_uninstall_data.app_type(),
               Eq(::apps::ApplicationType::APPLICATION_TYPE_WEB));
+  EXPECT_THAT(profile()->GetPrefs()->GetList(::ash::reporting::kAppsInstalled),
+              Contains(app_id).Times(0));
 }
 
 }  // namespace
diff --git a/chrome/browser/ash/policy/reporting/metrics_reporting/apps/app_events_observer_unittest.cc b/chrome/browser/ash/policy/reporting/metrics_reporting/apps/app_events_observer_unittest.cc
index 5a557c36..456e855 100644
--- a/chrome/browser/ash/policy/reporting/metrics_reporting/apps/app_events_observer_unittest.cc
+++ b/chrome/browser/ash/policy/reporting/metrics_reporting/apps/app_events_observer_unittest.cc
@@ -8,6 +8,7 @@
 #include <vector>
 
 #include "base/memory/raw_ptr.h"
+#include "base/test/repeating_test_future.h"
 #include "base/values.h"
 #include "chrome/browser/apps/app_service/app_launch_params.h"
 #include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
@@ -17,9 +18,9 @@
 #include "chrome/browser/apps/app_service/publishers/app_publisher.h"
 #include "chrome/browser/ash/policy/reporting/metrics_reporting/apps/app_platform_metrics_retriever.h"
 #include "chrome/browser/ash/policy/reporting/metrics_reporting/metric_reporting_prefs.h"
+#include "components/prefs/scoped_user_pref_update.h"
 #include "components/reporting/metrics/fakes/fake_reporting_settings.h"
 #include "components/reporting/proto/synced/metric_data.pb.h"
-#include "components/reporting/util/test_support_callbacks.h"
 #include "components/services/app_service/public/cpp/app_launch_util.h"
 #include "components/services/app_service/public/cpp/app_types.h"
 #include "components/services/app_service/public/cpp/icon_types.h"
@@ -28,6 +29,7 @@
 #include "testing/gtest/include/gtest/gtest.h"
 
 using ::testing::_;
+using ::testing::Contains;
 using ::testing::Eq;
 using ::testing::StrEq;
 
@@ -88,10 +90,6 @@
   void SetUp() override {
     ::apps::AppPlatformMetricsServiceTestBase::SetUp();
 
-    // Pre-install app so it can be used by tests.
-    InstallOneApp(kTestAppId, ::apps::AppType::kArc, kTestAppPublisherId,
-                  ::apps::Readiness::kReady, ::apps::InstallSource::kPlayStore);
-
     // Set up `AppEventsObserver` with relevant test params.
     auto mock_app_platform_metrics_retriever =
         std::make_unique<MockAppPlatformMetricsRetriever>();
@@ -104,6 +102,12 @@
     app_events_observer_ = AppEventsObserver::CreateForTest(
         profile(), std::move(mock_app_platform_metrics_retriever),
         &reporting_settings_);
+
+    // Pre-install app so it can be used by tests. Initialized after the event
+    // observer to simplify testing. Scenarios that test app installs across
+    // sessions are covered by browser tests.
+    InstallOneApp(kTestAppId, ::apps::AppType::kArc, kTestAppPublisherId,
+                  ::apps::Readiness::kReady, ::apps::InstallSource::kPlayStore);
   }
 
   void TearDown() override {
@@ -130,17 +134,17 @@
 
 TEST_F(AppEventsObserverTest, OnAppInstalled) {
   SetAllowedAppReportingTypes({::ash::reporting::kAppCategoryBrowser});
-  test::TestEvent<MetricData> test_event;
-  app_events_observer_->SetOnEventObservedCallback(test_event.repeating_cb());
+  base::test::RepeatingTestFuture<MetricData> test_future;
+  app_events_observer_->SetOnEventObservedCallback(test_future.GetCallback());
 
   // Install new app.
-  static constexpr char app_id[] = "TestNewApp";
-  InstallOneApp(app_id, ::apps::AppType::kStandaloneBrowser,
+  static constexpr char kAppId[] = "TestNewApp";
+  InstallOneApp(kAppId, ::apps::AppType::kStandaloneBrowser,
                 /*publisher_id=*/"", ::apps::Readiness::kReady,
                 ::apps::InstallSource::kBrowser);
 
   // Verify data being reported.
-  const MetricData& result = test_event.result();
+  const MetricData& result = test_future.Take();
   ASSERT_TRUE(result.has_event_data());
   EXPECT_THAT(result.event_data().type(), Eq(MetricEventType::APP_INSTALLED));
   ASSERT_TRUE(result.has_telemetry_data());
@@ -149,7 +153,7 @@
 
   const AppInstallData& app_install_data =
       result.telemetry_data().app_telemetry().app_install_data();
-  EXPECT_THAT(app_install_data.app_id(), StrEq(app_id));
+  EXPECT_THAT(app_install_data.app_id(), StrEq(kAppId));
   EXPECT_THAT(app_install_data.app_type(),
               Eq(::apps::ApplicationType::APPLICATION_TYPE_STANDALONE_BROWSER));
   EXPECT_THAT(
@@ -161,43 +165,51 @@
   EXPECT_THAT(
       app_install_data.app_install_time(),
       Eq(::apps::ApplicationInstallTime::APPLICATION_INSTALL_TIME_INIT));
+
+  // Also verify that the app install is being tracked.
+  EXPECT_THAT(profile()->GetPrefs()->GetList(::ash::reporting::kAppsInstalled),
+              Contains(kAppId).Times(1));
 }
 
 TEST_F(AppEventsObserverTest, OnAppInstalled_UnsetPolicy) {
-  test::TestEvent<MetricData> test_event;
-  app_events_observer_->SetOnEventObservedCallback(test_event.repeating_cb());
+  base::test::RepeatingTestFuture<MetricData> test_future;
+  app_events_observer_->SetOnEventObservedCallback(test_future.GetCallback());
 
   // Install new app.
-  static constexpr char app_id[] = "TestNewApp";
-  InstallOneApp(app_id, ::apps::AppType::kStandaloneBrowser,
+  static constexpr char kAppId[] = "TestNewApp";
+  InstallOneApp(kAppId, ::apps::AppType::kStandaloneBrowser,
                 /*publisher_id=*/"", ::apps::Readiness::kReady,
                 ::apps::InstallSource::kBrowser);
 
-  // Verify no data is being reported.
-  ASSERT_TRUE(test_event.no_result());
+  // Verify no data is being reported and the app install is being tracked.
+  ASSERT_TRUE(test_future.IsEmpty());
+  EXPECT_THAT(profile()->GetPrefs()->GetList(::ash::reporting::kAppsInstalled),
+              Contains(kAppId).Times(1));
 }
 
 TEST_F(AppEventsObserverTest, OnAppInstalled_DisallowedAppType) {
   // Set policy to enable reporting for a different app type than the one being
   // tested.
   SetAllowedAppReportingTypes({::ash::reporting::kAppCategoryAndroidApps});
-  test::TestEvent<MetricData> test_event;
-  app_events_observer_->SetOnEventObservedCallback(test_event.repeating_cb());
+  base::test::RepeatingTestFuture<MetricData> test_future;
+  app_events_observer_->SetOnEventObservedCallback(test_future.GetCallback());
 
   // Install new app.
-  static constexpr char app_id[] = "TestNewApp";
-  InstallOneApp(app_id, ::apps::AppType::kStandaloneBrowser,
+  static constexpr char kAppId[] = "TestNewApp";
+  InstallOneApp(kAppId, ::apps::AppType::kStandaloneBrowser,
                 /*publisher_id=*/"", ::apps::Readiness::kReady,
                 ::apps::InstallSource::kBrowser);
 
-  // Verify no data is being reported.
-  ASSERT_TRUE(test_event.no_result());
+  // Verify no data is being reported and the app install is being tracked.
+  ASSERT_TRUE(test_future.IsEmpty());
+  EXPECT_THAT(profile()->GetPrefs()->GetList(::ash::reporting::kAppsInstalled),
+              Contains(kAppId).Times(1));
 }
 
 TEST_F(AppEventsObserverTest, OnAppInstalledWithPublisherId) {
   SetAllowedAppReportingTypes({::ash::reporting::kAppCategoryAndroidApps});
-  test::TestEvent<MetricData> test_event;
-  app_events_observer_->SetOnEventObservedCallback(test_event.repeating_cb());
+  base::test::RepeatingTestFuture<MetricData> test_future;
+  app_events_observer_->SetOnEventObservedCallback(test_future.GetCallback());
 
   // Install new app.
   static constexpr char kNewAppId[] = "TestNewApp";
@@ -206,7 +218,7 @@
                 ::apps::Readiness::kReady, ::apps::InstallSource::kBrowser);
 
   // Verify data being reported.
-  const MetricData& result = test_event.result();
+  const MetricData& result = test_future.Take();
   ASSERT_TRUE(result.has_event_data());
   EXPECT_THAT(result.event_data().type(), Eq(MetricEventType::APP_INSTALLED));
   ASSERT_TRUE(result.has_telemetry_data());
@@ -227,12 +239,42 @@
   EXPECT_THAT(
       app_install_data.app_install_time(),
       Eq(::apps::ApplicationInstallTime::APPLICATION_INSTALL_TIME_INIT));
+
+  // Also verify the app install is being tracked.
+  EXPECT_THAT(profile()->GetPrefs()->GetList(::ash::reporting::kAppsInstalled),
+              Contains(kNewAppId).Times(1));
+}
+
+TEST_F(AppEventsObserverTest, OnAppInstalled_PreinstalledApp) {
+  SetAllowedAppReportingTypes({::ash::reporting::kAppCategoryBrowser});
+  static constexpr char kAppId[] = "TestNewApp";
+
+  // Directly track new app install in pref store. We cannot use the app service
+  // to simulate app install here because observers are only notified of new
+  // app installs only based on update deltas tracked by the app registry cache.
+  {
+    ScopedListPrefUpdate apps_installed_pref(profile()->GetPrefs(),
+                                             ::ash::reporting::kAppsInstalled);
+    apps_installed_pref->Append(kAppId);
+  }
+
+  // Attempt to install the app being tracked above.
+  base::test::RepeatingTestFuture<MetricData> test_future;
+  app_events_observer_->SetOnEventObservedCallback(test_future.GetCallback());
+  InstallOneApp(kAppId, ::apps::AppType::kStandaloneBrowser,
+                /*publisher_id=*/"", ::apps::Readiness::kReady,
+                ::apps::InstallSource::kBrowser);
+
+  // Verify that no data is being reported.
+  ASSERT_TRUE(test_future.IsEmpty());
+  EXPECT_THAT(profile()->GetPrefs()->GetList(::ash::reporting::kAppsInstalled),
+              Contains(kAppId).Times(1));
 }
 
 TEST_F(AppEventsObserverTest, OnAppLaunched) {
   SetAllowedAppReportingTypes({::ash::reporting::kAppCategoryAndroidApps});
-  test::TestEvent<MetricData> test_event;
-  app_events_observer_->SetOnEventObservedCallback(test_event.repeating_cb());
+  base::test::RepeatingTestFuture<MetricData> test_future;
+  app_events_observer_->SetOnEventObservedCallback(test_future.GetCallback());
 
   // Simulate app launch for pre-installed app.
   auto* const proxy = ::apps::AppServiceProxyFactory::GetForProfile(profile());
@@ -242,7 +284,7 @@
                 nullptr);
 
   // Verify data being reported.
-  const MetricData& result = test_event.result();
+  const MetricData& result = test_future.Take();
   ASSERT_TRUE(result.has_event_data());
   EXPECT_THAT(result.event_data().type(), Eq(MetricEventType::APP_LAUNCHED));
   ASSERT_TRUE(result.has_telemetry_data());
@@ -260,8 +302,8 @@
 }
 
 TEST_F(AppEventsObserverTest, OnAppLaunched_UnsetPolicy) {
-  test::TestEvent<MetricData> test_event;
-  app_events_observer_->SetOnEventObservedCallback(test_event.repeating_cb());
+  base::test::RepeatingTestFuture<MetricData> test_future;
+  app_events_observer_->SetOnEventObservedCallback(test_future.GetCallback());
 
   // Simulate app launch for pre-installed app.
   auto* const proxy = ::apps::AppServiceProxyFactory::GetForProfile(profile());
@@ -271,15 +313,15 @@
                 nullptr);
 
   // Verify no data is being reported.
-  ASSERT_TRUE(test_event.no_result());
+  ASSERT_TRUE(test_future.IsEmpty());
 }
 
 TEST_F(AppEventsObserverTest, OnAppLaunched_DisallowedAppType) {
   // Set policy to enable reporting for a different app type than the one being
   // tested.
   SetAllowedAppReportingTypes({::ash::reporting::kAppCategoryGames});
-  test::TestEvent<MetricData> test_event;
-  app_events_observer_->SetOnEventObservedCallback(test_event.repeating_cb());
+  base::test::RepeatingTestFuture<MetricData> test_future;
+  app_events_observer_->SetOnEventObservedCallback(test_future.GetCallback());
 
   // Simulate app launch for pre-installed app.
   auto* const proxy = ::apps::AppServiceProxyFactory::GetForProfile(profile());
@@ -289,13 +331,16 @@
                 nullptr);
 
   // Verify no data is being reported.
-  ASSERT_TRUE(test_event.no_result());
+  ASSERT_TRUE(test_future.IsEmpty());
 }
 
 TEST_F(AppEventsObserverTest, OnAppUninstalled) {
   SetAllowedAppReportingTypes({::ash::reporting::kAppCategoryAndroidApps});
-  test::TestEvent<MetricData> test_event;
-  app_events_observer_->SetOnEventObservedCallback(test_event.repeating_cb());
+  ASSERT_THAT(profile()->GetPrefs()->GetList(::ash::reporting::kAppsInstalled),
+              Contains(kTestAppId).Times(1));
+
+  base::test::RepeatingTestFuture<MetricData> test_future;
+  app_events_observer_->SetOnEventObservedCallback(test_future.GetCallback());
 
   // Simulate app uninstall for pre-installed app.
   auto* const proxy = ::apps::AppServiceProxyFactory::GetForProfile(profile());
@@ -304,7 +349,7 @@
   proxy->UninstallSilently(kTestAppId, ::apps::UninstallSource::kAppList);
 
   // Verify data being reported.
-  const MetricData& result = test_event.result();
+  const MetricData& result = test_future.Take();
   ASSERT_TRUE(result.has_event_data());
   EXPECT_THAT(result.event_data().type(), Eq(MetricEventType::APP_UNINSTALLED));
   ASSERT_TRUE(result.has_telemetry_data());
@@ -319,11 +364,20 @@
   EXPECT_THAT(app_uninstall_data.app_uninstall_source(),
               Eq(::apps::ApplicationUninstallSource::
                      APPLICATION_UNINSTALL_SOURCE_APP_LIST));
+
+  // Also verify the app is no longer being tracked.
+  EXPECT_THAT(profile()->GetPrefs()->GetList(::ash::reporting::kAppsInstalled),
+              Contains(kTestAppId).Times(0));
 }
 
 TEST_F(AppEventsObserverTest, OnAppUninstalled_UnsetPolicy) {
-  test::TestEvent<MetricData> test_event;
-  app_events_observer_->SetOnEventObservedCallback(test_event.repeating_cb());
+  // Verify that the pre-installed app is being tracked by the app install
+  // tracker.
+  ASSERT_THAT(profile()->GetPrefs()->GetList(::ash::reporting::kAppsInstalled),
+              Contains(kTestAppId).Times(1));
+
+  base::test::RepeatingTestFuture<MetricData> test_future;
+  app_events_observer_->SetOnEventObservedCallback(test_future.GetCallback());
 
   // Simulate app uninstall for pre-installed app.
   auto* const proxy = ::apps::AppServiceProxyFactory::GetForProfile(profile());
@@ -331,16 +385,21 @@
   FakePublisher fake_publisher(proxy, ::apps::AppType::kArc);
   proxy->UninstallSilently(kTestAppId, ::apps::UninstallSource::kAppList);
 
-  // Verify no data is being reported.
-  ASSERT_TRUE(test_event.no_result());
+  // Verify no data is being reported and the app is no longer being tracked.
+  ASSERT_TRUE(test_future.IsEmpty());
+  EXPECT_THAT(profile()->GetPrefs()->GetList(::ash::reporting::kAppsInstalled),
+              Contains(kTestAppId).Times(0));
 }
 
 TEST_F(AppEventsObserverTest, OnAppUninstalled_DisallowedAppType) {
   // Set policy to enable reporting for a different app type than the one being
   // tested.
   SetAllowedAppReportingTypes({::ash::reporting::kAppCategoryGames});
-  test::TestEvent<MetricData> test_event;
-  app_events_observer_->SetOnEventObservedCallback(test_event.repeating_cb());
+  ASSERT_THAT(profile()->GetPrefs()->GetList(::ash::reporting::kAppsInstalled),
+              Contains(kTestAppId).Times(1));
+
+  base::test::RepeatingTestFuture<MetricData> test_future;
+  app_events_observer_->SetOnEventObservedCallback(test_future.GetCallback());
 
   // Simulate app uninstall for pre-installed app.
   auto* const proxy = ::apps::AppServiceProxyFactory::GetForProfile(profile());
@@ -348,14 +407,16 @@
   FakePublisher fake_publisher(proxy, ::apps::AppType::kArc);
   proxy->UninstallSilently(kTestAppId, ::apps::UninstallSource::kAppList);
 
-  // Verify no data is being reported.
-  ASSERT_TRUE(test_event.no_result());
+  // Verify no data is being reported and the app is no longer being tracked.
+  ASSERT_TRUE(test_future.IsEmpty());
+  EXPECT_THAT(profile()->GetPrefs()->GetList(::ash::reporting::kAppsInstalled),
+              Contains(kTestAppId).Times(0));
 }
 
 TEST_F(AppEventsObserverTest, OnAppPlatformMetricsDestroyed) {
   SetAllowedAppReportingTypes({::ash::reporting::kAppCategoryBrowser});
-  test::TestEvent<MetricData> test_event;
-  app_events_observer_->SetOnEventObservedCallback(test_event.repeating_cb());
+  base::test::RepeatingTestFuture<MetricData> test_future;
+  app_events_observer_->SetOnEventObservedCallback(test_future.GetCallback());
 
   // Reset `AppPlatformMetricsService` to destroy the `AppPlatformMetrics`
   // component.
@@ -367,7 +428,7 @@
   InstallOneApp(app_id, ::apps::AppType::kStandaloneBrowser,
                 /*publisher_id=*/"", ::apps::Readiness::kReady,
                 ::apps::InstallSource::kBrowser);
-  ASSERT_TRUE(test_event.no_result());
+  ASSERT_TRUE(test_future.IsEmpty());
 }
 
 }  // namespace
diff --git a/chrome/browser/ash/policy/reporting/metrics_reporting/metric_reporting_manager.cc b/chrome/browser/ash/policy/reporting/metrics_reporting/metric_reporting_manager.cc
index deede507..a6978e8b 100644
--- a/chrome/browser/ash/policy/reporting/metrics_reporting/metric_reporting_manager.cc
+++ b/chrome/browser/ash/policy/reporting/metrics_reporting/metric_reporting_manager.cc
@@ -77,6 +77,11 @@
 
 }  // namespace
 
+// static
+BASE_FEATURE(kEnableAppEventsObserver,
+             "EnableAppEventsObserver",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 bool MetricReportingManager::Delegate::IsAffiliated(Profile* profile) const {
   const user_manager::User* const user =
       ::ash::ProfileHelper::Get()->GetUserByProfile(profile);
@@ -519,14 +524,16 @@
   DCHECK(user_reporting_settings_);
   DCHECK(user_telemetry_report_queue_);
   // App events.
-  auto app_events_observer = AppEventsObserver::CreateForProfile(
-      profile, user_reporting_settings_.get());
-  InitEventObserverManager(
-      std::move(app_events_observer), user_event_report_queue_.get(),
-      user_reporting_settings_.get(),
-      /*enable_setting_path=*/::ash::reporting::kReportAppInventory,
-      metrics::kReportAppInventoryEnabledDefaultValue,
-      /*init_delay=*/base::TimeDelta());
+  if (base::FeatureList::IsEnabled(kEnableAppEventsObserver)) {
+    auto app_events_observer = AppEventsObserver::CreateForProfile(
+        profile, user_reporting_settings_.get());
+    InitEventObserverManager(
+        std::move(app_events_observer), user_event_report_queue_.get(),
+        user_reporting_settings_.get(),
+        /*enable_setting_path=*/::ash::reporting::kReportAppInventory,
+        metrics::kReportAppInventoryEnabledDefaultValue,
+        /*init_delay=*/base::TimeDelta());
+  }
 
   // App telemetry.
   app_usage_observer_ =
diff --git a/chrome/browser/ash/policy/reporting/metrics_reporting/metric_reporting_manager.h b/chrome/browser/ash/policy/reporting/metrics_reporting/metric_reporting_manager.h
index b22394e7..35879d3 100644
--- a/chrome/browser/ash/policy/reporting/metrics_reporting/metric_reporting_manager.h
+++ b/chrome/browser/ash/policy/reporting/metrics_reporting/metric_reporting_manager.h
@@ -37,6 +37,8 @@
 class CollectorBase;
 class Sampler;
 
+BASE_DECLARE_FEATURE(kEnableAppEventsObserver);
+
 // Class to initialize and start info, event, and telemetry collection and
 // reporting.
 class MetricReportingManager : public policy::ManagedSessionService::Observer,
diff --git a/chrome/browser/ash/policy/reporting/metrics_reporting/metric_reporting_manager_unittest.cc b/chrome/browser/ash/policy/reporting/metrics_reporting/metric_reporting_manager_unittest.cc
index a94237d3..46f3eaee 100644
--- a/chrome/browser/ash/policy/reporting/metrics_reporting/metric_reporting_manager_unittest.cc
+++ b/chrome/browser/ash/policy/reporting/metrics_reporting/metric_reporting_manager_unittest.cc
@@ -502,6 +502,9 @@
 
 TEST_F(MetricReportingManagerEventTest,
        ShouldNotCreateAppEventObserverWhenAppServiceUnavailable) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndEnableFeature(kEnableAppEventsObserver);
+
   // Setup appropriate mocks and stubs.
   auto fake_reporting_settings =
       std::make_unique<test::FakeReportingSettings>();
@@ -594,7 +597,21 @@
           /*is_affiliated=*/true, app_event_settings,
           /*has_init_delay=*/false,
           /*expected_count_before_login=*/0,
-          /*expected_count_after_login=*/1}}),
+          /*expected_count_after_login=*/0},
+         {"AppEvents_FeatureFlagEnabled",
+          /*enabled_features=*/{kEnableAppEventsObserver},
+          /*disabled_features=*/{},
+          /*is_affiliated=*/true, app_event_settings,
+          /*has_init_delay=*/false,
+          /*expected_count_before_login=*/0,
+          /*expected_count_after_login=*/1},
+         {"AppEvents_FeatureFlagDisabled",
+          /*enabled_features=*/{},
+          /*disabled_features=*/{kEnableAppEventsObserver},
+          /*is_affiliated=*/true, app_event_settings,
+          /*has_init_delay=*/false,
+          /*expected_count_before_login=*/0,
+          /*expected_count_after_login=*/0}}),
     [](const testing::TestParamInfo<MetricReportingManagerInfoTest::ParamType>&
            info) { return info.param.test_name; });
 
diff --git a/chrome/browser/ash/policy/reporting/metrics_reporting/metric_reporting_prefs.cc b/chrome/browser/ash/policy/reporting/metrics_reporting/metric_reporting_prefs.cc
index da08287..385d928 100644
--- a/chrome/browser/ash/policy/reporting/metrics_reporting/metric_reporting_prefs.cc
+++ b/chrome/browser/ash/policy/reporting/metrics_reporting/metric_reporting_prefs.cc
@@ -22,6 +22,7 @@
       kReportAppUsageCollectionRateMs,
       ::reporting::metrics::kDefaultAppUsageTelemetryCollectionRate
           .InMilliseconds());
+  registry->RegisterListPref(kAppsInstalled);
 }
 
 absl::optional<std::string> GetAppReportingCategoryForType(
diff --git a/chrome/browser/ash/policy/reporting/metrics_reporting/metric_reporting_prefs.h b/chrome/browser/ash/policy/reporting/metrics_reporting/metric_reporting_prefs.h
index 307f63b..4e1c3da 100644
--- a/chrome/browser/ash/policy/reporting/metrics_reporting/metric_reporting_prefs.h
+++ b/chrome/browser/ash/policy/reporting/metrics_reporting/metric_reporting_prefs.h
@@ -36,6 +36,9 @@
 constexpr char kReportAppUsageCollectionRateMs[] =
     "reporting.report_app_usage_collection_rate_ms";
 
+// A list pref used to track installed apps for a particular user.
+constexpr char kAppsInstalled[] = "reporting.apps_installed";
+
 // Application category types tracked by the app metric reporting user policies.
 constexpr char kAppCategoryAndroidApps[] = "android_apps";
 constexpr char kAppCategoryBrowser[] = "browser";
diff --git a/chrome/browser/ash/web_applications/personalization_app/personalization_app_ambient_provider_impl.cc b/chrome/browser/ash/web_applications/personalization_app/personalization_app_ambient_provider_impl.cc
index 6ebbaa5..8b502de 100644
--- a/chrome/browser/ash/web_applications/personalization_app/personalization_app_ambient_provider_impl.cc
+++ b/chrome/browser/ash/web_applications/personalization_app/personalization_app_ambient_provider_impl.cc
@@ -131,7 +131,7 @@
   ambient_observer_remote_.Bind(std::move(observer));
 
   // Call it once to get the current ambient mode enabled status.
-  OnAmbientModeEnabledChanged();
+  BroadcastAmbientModeEnabledStatus(IsAmbientModeEnabled());
 
   // Call it once to get the current ambient ui settings.
   OnAmbientUiSettingsChanged();
@@ -332,6 +332,21 @@
 
 void PersonalizationAppAmbientProviderImpl::OnAmbientModeEnabledChanged() {
   const bool enabled = IsAmbientModeEnabled();
+  if (enabled) {
+    // The usage metrics for the user's `AmbientUiSettings` should be
+    // incremented whenever ambient mode is enabled. They should not be
+    // incremented though every time the hub is simply opened.
+    AmbientUiSettings current_ui_settings = GetCurrentUiSettings();
+    LogAmbientModeTheme(current_ui_settings.theme());
+    if (current_ui_settings.theme() == AmbientTheme::kVideo) {
+      LogAmbientModeVideo(*current_ui_settings.video());
+    }
+  }
+  BroadcastAmbientModeEnabledStatus(enabled);
+}
+
+void PersonalizationAppAmbientProviderImpl::BroadcastAmbientModeEnabledStatus(
+    bool enabled) {
   if (ambient_observer_remote_.is_bound()) {
     ambient_observer_remote_->OnAmbientModeEnabledChanged(enabled);
   }
diff --git a/chrome/browser/ash/web_applications/personalization_app/personalization_app_ambient_provider_impl.h b/chrome/browser/ash/web_applications/personalization_app/personalization_app_ambient_provider_impl.h
index 1382c84..696885f 100644
--- a/chrome/browser/ash/web_applications/personalization_app/personalization_app_ambient_provider_impl.h
+++ b/chrome/browser/ash/web_applications/personalization_app/personalization_app_ambient_provider_impl.h
@@ -128,6 +128,8 @@
   // non-video theme (ex: slideshow).
   AmbientModeTopicSource GetCurrentTopicSource() const;
 
+  void BroadcastAmbientModeEnabledStatus(bool enabled);
+
   mojo::Receiver<ash::personalization_app::mojom::AmbientProvider>
       ambient_receiver_{this};
 
diff --git a/chrome/browser/ash/web_applications/personalization_app/personalization_app_ambient_provider_impl_unittest.cc b/chrome/browser/ash/web_applications/personalization_app/personalization_app_ambient_provider_impl_unittest.cc
index 048a820..a953cda2 100644
--- a/chrome/browser/ash/web_applications/personalization_app/personalization_app_ambient_provider_impl_unittest.cc
+++ b/chrome/browser/ash/web_applications/personalization_app/personalization_app_ambient_provider_impl_unittest.cc
@@ -478,6 +478,14 @@
 
 TEST_F(PersonalizationAppAmbientProviderImplTest,
        ShouldCallOnAnimationThemeChanged) {
+  // When ambient mode is first enabled during test set up, the video theme
+  // should become active by default since the corresponding experiment flags
+  // are on. That should count as +1 in the usage metrics for the video theme.
+  histogram_tester().ExpectBucketCount(kAmbientModeAnimationThemeHistogramName,
+                                       ash::AmbientTheme::kVideo, 1);
+  histogram_tester().ExpectBucketCount(kAmbientModeVideoHistogramName,
+                                       ash::kDefaultAmbientVideo, 1);
+
   SetAmbientObserver();
   FetchSettings();
   SetAnimationTheme(ash::AmbientTheme::kSlideshow);
@@ -493,9 +501,21 @@
   SetAnimationTheme(ash::AmbientTheme::kVideo);
   EXPECT_EQ(ash::AmbientTheme::kVideo, ObservedAnimationTheme());
   histogram_tester().ExpectBucketCount(kAmbientModeAnimationThemeHistogramName,
-                                       ash::AmbientTheme::kVideo, 1);
+                                       ash::AmbientTheme::kVideo, 2);
   histogram_tester().ExpectBucketCount(kAmbientModeVideoHistogramName,
-                                       ash::kDefaultAmbientVideo, 1);
+                                       ash::kDefaultAmbientVideo, 2);
+}
+
+TEST_F(PersonalizationAppAmbientProviderImplTest,
+       RestoresOldThemeAfterReenabling) {
+  SetAmbientObserver();
+  FetchSettings();
+  SetAnimationTheme(ash::AmbientTheme::kFeelTheBreeze);
+  SetEnabledPref(false);
+  SetEnabledPref(true);
+  EXPECT_EQ(ash::AmbientTheme::kFeelTheBreeze, ObservedAnimationTheme());
+  histogram_tester().ExpectBucketCount(kAmbientModeAnimationThemeHistogramName,
+                                       ash::AmbientTheme::kFeelTheBreeze, 2);
 }
 
 TEST_F(PersonalizationAppAmbientProviderImplTest, FetchPreviewImages) {
@@ -919,6 +939,12 @@
                                           Eq(new_mexico_select))))}));
   };
 
+  // When ambient mode is first enabled during test set up, the video theme
+  // should become active by default since the corresponding experiment flags
+  // are on. That should count as +1 in the usage metrics for the video theme.
+  histogram_tester().ExpectBucketCount(kAmbientModeVideoHistogramName,
+                                       ash::AmbientVideo::kNewMexico, 1);
+
   SetAmbientObserver();
   FetchSettings();
   ReplyFetchSettingsAndAlbums(/*success=*/true);
@@ -945,7 +971,7 @@
                          /*new_mexico_selected=*/true);
 
   histogram_tester().ExpectBucketCount(kAmbientModeVideoHistogramName,
-                                       ash::AmbientVideo::kNewMexico, 1);
+                                       ash::AmbientVideo::kNewMexico, 2);
   histogram_tester().ExpectBucketCount(kAmbientModeVideoHistogramName,
                                        ash::AmbientVideo::kClouds, 1);
 }
diff --git a/chrome/browser/ash/web_applications/personalization_app/personalization_app_metrics.h b/chrome/browser/ash/web_applications/personalization_app/personalization_app_metrics.h
index 4fc686a..e439eba6 100644
--- a/chrome/browser/ash/web_applications/personalization_app/personalization_app_metrics.h
+++ b/chrome/browser/ash/web_applications/personalization_app/personalization_app_metrics.h
@@ -27,9 +27,9 @@
 };
 
 constexpr char kAmbientModeAnimationThemeHistogramName[] =
-    "Ash.Personalization.AmbientMode.AnimationTheme";
+    "Ash.Personalization.AmbientMode.AnimationTheme2";
 constexpr char kAmbientModeVideoHistogramName[] =
-    "Ash.Personalization.AmbientMode.Video";
+    "Ash.Personalization.AmbientMode.Video2";
 constexpr char kPersonalizationThemeColorModeHistogramName[] =
     "Ash.Personalization.Theme.ColorMode";
 constexpr char kPersonalizationKeyboardBacklightColorHistogramName[] =
diff --git a/chrome/browser/autofill/autofill_across_iframes_browsertest.cc b/chrome/browser/autofill/autofill_across_iframes_browsertest.cc
index dda32c8e..196d368 100644
--- a/chrome/browser/autofill/autofill_across_iframes_browsertest.cc
+++ b/chrome/browser/autofill/autofill_across_iframes_browsertest.cc
@@ -679,8 +679,14 @@
 };
 
 // Tests that a large and deeply nested form is extracted and filled correctly.
+// The test makes heavy use of abbreviations to make it easier to spot the
+// pattern in the form.
 IN_PROC_BROWSER_TEST_F(AutofillAcrossIframesTest_NestedAndLargeForm,
                        FillAllFieldsOnTriggeredOrigin) {
+  // The `n` in `n.html` is the height of the frame sub-tree, i.e., a frame that
+  // loads `1.html` is a leaf frame, `2.html` has child frames but no
+  // grandchildren, and so on.
+  // The origins are picked arbitrarily.
   SetUrlContent("/", MakeCss(3) +
                          R"(<iframe src="$4/3.html"></iframe>
                             <iframe src="$3/3.html"></iframe>
@@ -698,7 +704,7 @@
                                R"(<form>
                                   <input autocomplete=cc-number>
                                   <input>
-                                  <iframe src="$1/1.html"></iframe>
+                                  <iframe src="$5/1.html"></iframe>
                                   <input>
                                   <input autocomplete=cc-exp>
                                   </form>)");
@@ -730,54 +736,54 @@
     // clang-format off
     EXPECT_THAT(form->fields(),
                 ElementsAre(
-                    // $3/3.html
+                    // $4/3.html
                     m("d.com", name),
                     m("d.com", unspecified),
                       m("b.com", num),
                       m("b.com", unspecified),
-                        m("a.com", name),
-                        m("a.com", num),
-                        m("a.com", exp),
-                        m("a.com", cvc),
+                        m("e.com", name),
+                        m("e.com", num),
+                        m("e.com", exp),
+                        m("e.com", cvc),
                       m("b.com", unspecified),
                       m("b.com", exp),
                     m("d.com", unspecified),
                     m("d.com", cvc),
-                    // $4/3.html
+                    // $3/3.html
                     m("c.com", name),
                     m("c.com", unspecified),
                       m("b.com", num),
                       m("b.com", unspecified),
-                        m("a.com", name),
-                        m("a.com", num),
-                        m("a.com", exp),
-                        m("a.com", cvc),
+                        m("e.com", name),
+                        m("e.com", num),
+                        m("e.com", exp),
+                        m("e.com", cvc),
                       m("b.com", unspecified),
                       m("b.com", exp),
                     m("c.com", unspecified),
                     m("c.com", cvc),
-                    // $5/3.html
+                    // $2/3.html
                     m("b.com", name),
                     m("b.com", unspecified),
                       m("b.com", num),
                       m("b.com", unspecified),
-                        m("a.com", name),
-                        m("a.com", num),
-                        m("a.com", exp),
-                        m("a.com", cvc),
+                        m("e.com", name),
+                        m("e.com", num),
+                        m("e.com", exp),
+                        m("e.com", cvc),
                       m("b.com", unspecified),
                       m("b.com", exp),
                     m("b.com", unspecified),
                     m("b.com", cvc),
-                    // $6/3.html
+                    // $1/3.html
                     m("a.com", name),
                     m("a.com", unspecified),
                       m("b.com", num),
                       m("b.com", unspecified),
-                        m("a.com", name),
-                        m("a.com", num),
-                        m("a.com", exp),
-                        m("a.com", cvc),
+                        m("e.com", name),
+                        m("e.com", num),
+                        m("e.com", exp),
+                        m("e.com", cvc),
                       m("b.com", unspecified),
                       m("b.com", exp),
                     m("a.com", unspecified),
@@ -786,22 +792,24 @@
     // clang-format on
   }
   const FormData& form_data = form->ToFormData();
-  ASSERT_EQ("a.com", form_data.fields[4].origin.host());
+  ASSERT_EQ("e.com", form_data.fields[4].origin.host());
   ASSERT_EQ("cc-name", form_data.fields[4].autocomplete_attribute);
   FillCard(main_frame(), form_data, form_data.fields[4]);
   EXPECT_TRUE(main_autofill_manager()->WaitForAutofill(5));
   {
-    const auto* name = kNameFull;
-    const auto* num = kNumber;
-    const auto* exp = kExp;
-    const auto* cvc = kCvc;
+    // `rat` represents a value that is not filled only due to rationalization.
+    constexpr const char* rat = "";
+    constexpr const char* name = kNameFull;
+    constexpr const char* num = kNumber;
+    constexpr const char* exp = kExp;
+    constexpr const char* cvc = kCvc;
     std::vector<std::string> values = AllFieldValues(web_contents(), form_data);
     EXPECT_THAT(
         values,
         ElementsAre("", "", "", "", name, num, exp, cvc, "", "", "", "",  //
                     "", "", "", "", name, num, exp, cvc, "", "", "", "",  //
                     "", "", "", "", name, num, exp, cvc, "", "", "", "",  //
-                    name, "", "", "", name, num, exp, cvc, "", "", "", cvc));
+                    name, "", "", "", name, num, exp, cvc, "", "", "", rat));
   }
 }
 
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index 4dcc2fdd..5bad4289 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -3648,6 +3648,14 @@
   if (force_light) {
     web_prefs->preferred_color_scheme =
         blink::mojom::PreferredColorScheme::kLight;
+  } else if (url.SchemeIs(content::kChromeUIScheme)) {
+    // If color scheme is not forced, WebUI should track the color mode of the
+    // ColorProvider associated with `web_contents`.
+    web_prefs->preferred_color_scheme =
+        web_contents->GetColorMode() ==
+                ui::ColorProviderManager::ColorMode::kLight
+            ? blink::mojom::PreferredColorScheme::kLight
+            : blink::mojom::PreferredColorScheme::kDark;
   }
 
   return old_preferred_color_scheme != web_prefs->preferred_color_scheme;
diff --git a/chrome/browser/chrome_content_browser_client_browsertest.cc b/chrome/browser/chrome_content_browser_client_browsertest.cc
index bc8836f..6415cdc 100644
--- a/chrome/browser/chrome_content_browser_client_browsertest.cc
+++ b/chrome/browser/chrome_content_browser_client_browsertest.cc
@@ -54,6 +54,9 @@
 #include "net/test/embedded_test_server/embedded_test_server.h"
 #include "net/test/embedded_test_server/http_request.h"
 #include "net/test/embedded_test_server/http_response.h"
+#include "ui/color/color_provider.h"
+#include "ui/color/color_provider_manager.h"
+#include "ui/color/color_provider_source.h"
 #include "ui/native_theme/native_theme.h"
 #include "ui/native_theme/test_native_theme.h"
 #include "url/gurl.h"
@@ -329,25 +332,52 @@
       opened_tab->GetPrimaryMainFrame()->GetProcess()->GetID()));
 }
 
-class PrefersColorSchemeTest : public testing::WithParamInterface<bool>,
-                               public InProcessBrowserTest {
+// Tests for the preferred color scheme for a given WebContents. The first param
+// controls whether the web NativeTheme is light or dark the second controls
+// whether the color mode on the associated color provider is light or dark.
+class PrefersColorSchemeTest
+    : public testing::WithParamInterface<std::tuple<bool, bool>>,
+      public InProcessBrowserTest {
  protected:
-  PrefersColorSchemeTest() : theme_client_(&test_theme_) {}
-
+  PrefersColorSchemeTest()
+      : theme_client_(&test_theme_),
+        color_provider_source_(GetIsDarkColorProviderColorMode()) {
+    test_theme_.SetDarkMode(GetIsDarkNativeTheme());
+  }
   ~PrefersColorSchemeTest() override {
     CHECK_EQ(&theme_client_, SetBrowserClientForTesting(original_client_));
   }
 
   const char* ExpectedColorScheme() const {
-    return GetParam() ? "dark" : "light";
+    // WebUI's preferred color scheme should reflect the color mode of their
+    // associated ColorProvider, and not the preferred color scheme of the web
+    // NativeTheme.
+    const GURL& last_committed_url = browser()
+                                         ->tab_strip_model()
+                                         ->GetActiveWebContents()
+                                         ->GetLastCommittedURL();
+    if (last_committed_url.SchemeIs(content::kChromeUIScheme)) {
+      return GetIsDarkColorProviderColorMode() ? "dark" : "light";
+    }
+    return GetIsDarkNativeTheme() ? "dark" : "light";
   }
 
   void SetUpOnMainThread() override {
     InProcessBrowserTest::SetUpOnMainThread();
     original_client_ = SetBrowserClientForTesting(&theme_client_);
+    test_theme_.SetDarkMode(GetIsDarkNativeTheme());
+    browser()
+        ->tab_strip_model()
+        ->GetActiveWebContents()
+        ->SetColorProviderSource(&color_provider_source_);
   }
 
  protected:
+  bool GetIsDarkNativeTheme() const { return std::get<0>(GetParam()); }
+  bool GetIsDarkColorProviderColorMode() const {
+    return std::get<1>(GetParam());
+  }
+
   ui::TestNativeTheme test_theme_;
 
  private:
@@ -367,12 +397,36 @@
     const raw_ptr<const ui::NativeTheme> theme_;
   };
 
+  class MockColorProviderSource : public ui::ColorProviderSource {
+   public:
+    explicit MockColorProviderSource(bool is_dark) {
+      key_.color_mode = is_dark ? ui::ColorProviderManager::ColorMode::kDark
+                                : ui::ColorProviderManager::ColorMode::kLight;
+      provider_.GenerateColorMap();
+    }
+    MockColorProviderSource(const MockColorProviderSource&) = delete;
+    MockColorProviderSource& operator=(const MockColorProviderSource&) = delete;
+    ~MockColorProviderSource() override = default;
+
+    // ui::ColorProviderSource:
+    const ui::ColorProvider* GetColorProvider() const override {
+      return &provider_;
+    }
+    ui::ColorProviderManager::Key GetColorProviderKey() const override {
+      return key_;
+    }
+
+   private:
+    ui::ColorProvider provider_;
+    ui::ColorProviderManager::Key key_;
+  };
+
   base::test::ScopedFeatureList feature_list_;
   ChromeContentBrowserClientWithWebTheme theme_client_;
+  MockColorProviderSource color_provider_source_;
 };
 
 IN_PROC_BROWSER_TEST_P(PrefersColorSchemeTest, PrefersColorScheme) {
-  test_theme_.SetDarkMode(GetParam());
   browser()
       ->tab_strip_model()
       ->GetActiveWebContents()
@@ -388,7 +442,6 @@
 }
 
 IN_PROC_BROWSER_TEST_P(PrefersColorSchemeTest, FeatureOverridesChromeSchemes) {
-  test_theme_.SetDarkMode(GetParam());
   browser()
       ->tab_strip_model()
       ->GetActiveWebContents()
@@ -407,7 +460,6 @@
 
 #if BUILDFLAG(ENABLE_EXTENSIONS)
 IN_PROC_BROWSER_TEST_P(PrefersColorSchemeTest, FeatureOverridesPdfUI) {
-  test_theme_.SetDarkMode(GetParam());
   browser()
       ->tab_strip_model()
       ->GetActiveWebContents()
@@ -428,7 +480,9 @@
 }
 #endif
 
-INSTANTIATE_TEST_SUITE_P(All, PrefersColorSchemeTest, testing::Bool());
+INSTANTIATE_TEST_SUITE_P(All,
+                         PrefersColorSchemeTest,
+                         testing::Combine(testing::Bool(), testing::Bool()));
 
 class PrefersContrastTest
     : public testing::WithParamInterface<ui::NativeTheme::PreferredContrast>,
diff --git a/chrome/browser/device_reauth/chrome_device_authenticator_common_unittest.cc b/chrome/browser/device_reauth/chrome_device_authenticator_common_unittest.cc
index cefdf57..21e425f4 100644
--- a/chrome/browser/device_reauth/chrome_device_authenticator_common_unittest.cc
+++ b/chrome/browser/device_reauth/chrome_device_authenticator_common_unittest.cc
@@ -29,7 +29,7 @@
 
   bool CanAuthenticateWithBiometrics() override;
 
-#if BUILDFLAG(IS_ANDROID)
+#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_MAC)
   bool CanAuthenticateWithBiometricOrScreenLock() override;
 #endif
 
@@ -56,7 +56,7 @@
   return false;
 }
 
-#if BUILDFLAG(IS_ANDROID)
+#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_MAC)
 bool FakeChromeDeviceAuthenticatorCommon::
     CanAuthenticateWithBiometricOrScreenLock() {
   NOTIMPLEMENTED();
diff --git a/chrome/browser/device_reauth/mac/authenticator_mac.h b/chrome/browser/device_reauth/mac/authenticator_mac.h
index 4521f37b..54d1169 100644
--- a/chrome/browser/device_reauth/mac/authenticator_mac.h
+++ b/chrome/browser/device_reauth/mac/authenticator_mac.h
@@ -13,6 +13,7 @@
  public:
   virtual ~AuthenticatorMacInterface() = default;
   virtual bool CheckIfBiometricsAvailable() = 0;
+  virtual bool CheckIfBiometricsOrScreenLockAvailable() = 0;
   virtual bool AuthenticateUserWithNonBiometrics(
       const std::u16string& message) = 0;
 };
@@ -23,6 +24,7 @@
   AuthenticatorMac();
   ~AuthenticatorMac() override;
   bool CheckIfBiometricsAvailable() override;
+  bool CheckIfBiometricsOrScreenLockAvailable() override;
   bool AuthenticateUserWithNonBiometrics(
       const std::u16string& message) override;
 };
diff --git a/chrome/browser/device_reauth/mac/authenticator_mac.mm b/chrome/browser/device_reauth/mac/authenticator_mac.mm
index c42c74a8..72f309e 100644
--- a/chrome/browser/device_reauth/mac/authenticator_mac.mm
+++ b/chrome/browser/device_reauth/mac/authenticator_mac.mm
@@ -24,6 +24,12 @@
                            error:nil];
 }
 
+bool AuthenticatorMac::CheckIfBiometricsOrScreenLockAvailable() {
+  LAContext* context = [[LAContext alloc] init];
+  return [context canEvaluatePolicy:LAPolicyDeviceOwnerAuthentication
+                              error:nil];
+}
+
 bool AuthenticatorMac::AuthenticateUserWithNonBiometrics(
     const std::u16string& message) {
   return password_manager_util_mac::AuthenticateUser(message);
diff --git a/chrome/browser/device_reauth/mac/device_authenticator_mac.h b/chrome/browser/device_reauth/mac/device_authenticator_mac.h
index d614aea..063a6083d 100644
--- a/chrome/browser/device_reauth/mac/device_authenticator_mac.h
+++ b/chrome/browser/device_reauth/mac/device_authenticator_mac.h
@@ -26,6 +26,8 @@
 
   bool CanAuthenticateWithBiometrics() override;
 
+  bool CanAuthenticateWithBiometricOrScreenLock() override;
+
   // Triggers an authentication flow based on biometrics, with the
   // screen lock as fallback. Note: this only supports one authentication
   // request at a time.
diff --git a/chrome/browser/device_reauth/mac/device_authenticator_mac.mm b/chrome/browser/device_reauth/mac/device_authenticator_mac.mm
index e9fc8920..89b00d9 100644
--- a/chrome/browser/device_reauth/mac/device_authenticator_mac.mm
+++ b/chrome/browser/device_reauth/mac/device_authenticator_mac.mm
@@ -51,6 +51,20 @@
   return is_available;
 }
 
+bool DeviceAuthenticatorMac::CanAuthenticateWithBiometricOrScreenLock() {
+  // We check if we can authenticate strictly with biometrics first as this
+  // function has important side effects such as logging metrics related to how
+  // often users have biometrics available, and setting a pref that denotes that
+  // at one point biometrics was available on this device.
+  if (CanAuthenticateWithBiometrics()) {
+    return true;
+  }
+
+  // TODO(crbug.com/4555994): Add metrics logging for the only screen lock
+  // available case.
+  return authenticator_->CheckIfBiometricsOrScreenLockAvailable();
+}
+
 void DeviceAuthenticatorMac::Authenticate(
     device_reauth::DeviceAuthRequester requester,
     AuthenticateCallback callback,
diff --git a/chrome/browser/device_reauth/mac/device_authenticator_mac_unittest.mm b/chrome/browser/device_reauth/mac/device_authenticator_mac_unittest.mm
index 0ff5b52..a33f02c 100644
--- a/chrome/browser/device_reauth/mac/device_authenticator_mac_unittest.mm
+++ b/chrome/browser/device_reauth/mac/device_authenticator_mac_unittest.mm
@@ -33,14 +33,17 @@
 class MockSystemAuthenticator : public AuthenticatorMacInterface {
  public:
   MOCK_METHOD(bool, CheckIfBiometricsAvailable, (), (override));
+  MOCK_METHOD(bool, CheckIfBiometricsOrScreenLockAvailable, (), (override));
   MOCK_METHOD(bool,
               AuthenticateUserWithNonBiometrics,
               (const std::u16string&),
               (override));
 };
 
-// Test params decides whether biometric authentication is available.
-class DeviceAuthenticatorMacTest : public ::testing::TestWithParam<bool> {
+// Test params decides whether biometric authentication and screen lock are
+// available.
+class DeviceAuthenticatorMacTest
+    : public ::testing::TestWithParam<std::tuple<bool, bool>> {
  public:
   DeviceAuthenticatorMacTest()
       : testing_local_state_(TestingBrowserProcess::GetGlobal()) {
@@ -51,9 +54,13 @@
         std::move(system_authenticator));
     ON_CALL(*system_authenticator_, CheckIfBiometricsAvailable)
         .WillByDefault(testing::Return(is_biometric_available()));
+    ON_CALL(*system_authenticator_, CheckIfBiometricsOrScreenLockAvailable)
+        .WillByDefault(testing::Return(is_biometric_available() ||
+                                       is_screen_lock_available()));
   }
 
-  bool is_biometric_available() { return GetParam(); }
+  bool is_biometric_available() { return std::get<0>(GetParam()); }
+  bool is_screen_lock_available() { return std::get<1>(GetParam()); }
 
   void SimulateReauthSuccess() {
     if (is_biometric_available()) {
@@ -213,4 +220,22 @@
                 password_manager::prefs::kHadBiometricsAvailable));
 }
 
-INSTANTIATE_TEST_SUITE_P(, DeviceAuthenticatorMacTest, ::testing::Bool());
+TEST_P(DeviceAuthenticatorMacTest,
+       BiometricAndScreenLockAuthenticationAvailablity) {
+  if (is_biometric_available()) {
+    EXPECT_CALL(system_authenticator(), CheckIfBiometricsAvailable);
+  } else {
+    EXPECT_CALL(system_authenticator(), CheckIfBiometricsOrScreenLockAvailable);
+  }
+
+  EXPECT_EQ(authenticator()->CanAuthenticateWithBiometricOrScreenLock(),
+            is_biometric_available() || is_screen_lock_available());
+  EXPECT_EQ(is_biometric_available(),
+            local_state().Get()->GetBoolean(
+                password_manager::prefs::kHadBiometricsAvailable));
+}
+
+INSTANTIATE_TEST_SUITE_P(,
+                         DeviceAuthenticatorMacTest,
+                         ::testing::Combine(::testing::Bool(),
+                                            ::testing::Bool()));
diff --git a/chrome/browser/dips/dips_service.cc b/chrome/browser/dips/dips_service.cc
index e4bf4dc..b3a2003 100644
--- a/chrome/browser/dips/dips_service.cc
+++ b/chrome/browser/dips/dips_service.cc
@@ -326,6 +326,15 @@
                            content_settings_callback));
 }
 
+void DIPSService::DidSiteHaveInteractionSince(
+    const GURL& url,
+    base::Time bound,
+    CheckInteractionCallback callback) const {
+  storage_.AsyncCall(&DIPSStorage::DidSiteHaveInteractionSince)
+      .WithArgs(url, bound)
+      .Then(std::move(callback));
+}
+
 void DIPSService::GotState(
     std::vector<DIPSRedirectInfoPtr> redirects,
     DIPSRedirectChainInfoPtr chain,
diff --git a/chrome/browser/dips/dips_service.h b/chrome/browser/dips/dips_service.h
index c1592a7..ce3cf6d 100644
--- a/chrome/browser/dips/dips_service.h
+++ b/chrome/browser/dips/dips_service.h
@@ -46,6 +46,7 @@
       base::RepeatingCallback<void(const GURL&)> content_settings_callback)>;
   using DeletedSitesCallback =
       base::OnceCallback<void(const std::vector<std::string>& sites)>;
+  using CheckInteractionCallback = base::OnceCallback<void(bool)>;
 
   ~DIPSService() override;
 
@@ -84,6 +85,10 @@
       DIPSRedirectChainInfoPtr chain,
       base::RepeatingCallback<void(const GURL&)> content_settings_callback);
 
+  void DidSiteHaveInteractionSince(const GURL& url,
+                                   base::Time bound,
+                                   CheckInteractionCallback callback) const;
+
   // This allows unit-testing the metrics emitted by HandleRedirect() without
   // instantiating DIPSService.
   static void HandleRedirectForTesting(const DIPSRedirectInfo& redirect,
diff --git a/chrome/browser/dips/dips_storage.cc b/chrome/browser/dips/dips_storage.cc
index d3328d0..2ee2be87 100644
--- a/chrome/browser/dips/dips_storage.cc
+++ b/chrome/browser/dips/dips_storage.cc
@@ -274,6 +274,13 @@
   return sites_to_clear;
 }
 
+bool DIPSStorage::DidSiteHaveInteractionSince(const GURL& url,
+                                              base::Time bound) {
+  const DIPSState state = Read(url);
+  return state.user_interaction_times().has_value() &&
+         state.user_interaction_times()->second >= bound;
+}
+
 /* static */
 void DIPSStorage::DeleteDatabaseFiles(base::FilePath path,
                                       base::OnceClosure on_complete) {
diff --git a/chrome/browser/dips/dips_storage.h b/chrome/browser/dips/dips_storage.h
index 1a50913..c03862f 100644
--- a/chrome/browser/dips/dips_storage.h
+++ b/chrome/browser/dips/dips_storage.h
@@ -77,6 +77,9 @@
   std::vector<std::string> GetSitesToClear(
       absl::optional<base::TimeDelta> grace_period) const;
 
+  // Returns true if `url`'s site has had user interaction since `bound`.
+  bool DidSiteHaveInteractionSince(const GURL& url, base::Time bound);
+
   // Utility Methods -----------------------------------------------------------
 
   static void DeleteDatabaseFiles(base::FilePath path,
diff --git a/chrome/browser/dips/dips_storage_unittest.cc b/chrome/browser/dips/dips_storage_unittest.cc
index 6eee698..4181d99c 100644
--- a/chrome/browser/dips/dips_storage_unittest.cc
+++ b/chrome/browser/dips/dips_storage_unittest.cc
@@ -677,6 +677,34 @@
   EXPECT_FALSE(storage_.Read(url2).was_loaded());
 }
 
+TEST_F(DIPSStorageTest, DidSiteHaveInteractionSince) {
+  GURL url1("https://example1.com");
+
+  EXPECT_FALSE(
+      storage_.DidSiteHaveInteractionSince(url1, base::Time::FromDoubleT(0)));
+
+  storage_.WriteForTesting(
+      url1, StateValue{.site_storage_times{{base::Time::FromDoubleT(1),
+                                            base::Time::FromDoubleT(1)}},
+                       .user_interaction_times{{base::Time::FromDoubleT(2),
+                                                base::Time::FromDoubleT(2)}},
+                       .stateful_bounce_times{{base::Time::FromDoubleT(3),
+                                               base::Time::FromDoubleT(3)}},
+                       .bounce_times{{base::Time::FromDoubleT(3),
+                                      base::Time::FromDoubleT(4)}}});
+
+  EXPECT_TRUE(
+      storage_.DidSiteHaveInteractionSince(url1, base::Time::FromDoubleT(0)));
+  EXPECT_TRUE(
+      storage_.DidSiteHaveInteractionSince(url1, base::Time::FromDoubleT(1)));
+  EXPECT_TRUE(
+      storage_.DidSiteHaveInteractionSince(url1, base::Time::FromDoubleT(2)));
+  EXPECT_FALSE(
+      storage_.DidSiteHaveInteractionSince(url1, base::Time::FromDoubleT(3)));
+  EXPECT_FALSE(
+      storage_.DidSiteHaveInteractionSince(url1, base::Time::FromDoubleT(4)));
+}
+
 class DIPSStoragePrepopulateTest : public testing::Test {
  public:
   DIPSStoragePrepopulateTest()
diff --git a/chrome/browser/enterprise/reporting/real_time_report_controller_android.cc b/chrome/browser/enterprise/reporting/real_time_report_controller_android.cc
deleted file mode 100644
index fbc962b8..0000000
--- a/chrome/browser/enterprise/reporting/real_time_report_controller_android.cc
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright 2023 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/enterprise/reporting/real_time_report_controller_android.h"
-
-namespace enterprise_reporting {
-
-RealTimeReportControllerAndroid::RealTimeReportControllerAndroid() = default;
-RealTimeReportControllerAndroid::~RealTimeReportControllerAndroid() = default;
-
-void RealTimeReportControllerAndroid::StartWatchingExtensionRequestIfNeeded() {
-  // No-op because extensions are not supported on Android.
-}
-
-void RealTimeReportControllerAndroid::StopWatchingExtensionRequest() {
-  // No-op because extensions are not supported on Android.
-}
-
-}  // namespace enterprise_reporting
diff --git a/chrome/browser/enterprise/reporting/real_time_report_controller_android.h b/chrome/browser/enterprise/reporting/real_time_report_controller_android.h
deleted file mode 100644
index af9eb1f4..0000000
--- a/chrome/browser/enterprise/reporting/real_time_report_controller_android.h
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2023 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_ENTERPRISE_REPORTING_REAL_TIME_REPORT_CONTROLLER_ANDROID_H_
-#define CHROME_BROWSER_ENTERPRISE_REPORTING_REAL_TIME_REPORT_CONTROLLER_ANDROID_H_
-
-#include "components/enterprise/browser/reporting/real_time_report_controller.h"
-
-namespace enterprise_reporting {
-
-class RealTimeReportControllerAndroid
-    : public RealTimeReportController::Delegate {
- public:
-  RealTimeReportControllerAndroid();
-  RealTimeReportControllerAndroid(const RealTimeReportControllerAndroid&) =
-      delete;
-  RealTimeReportControllerAndroid& operator=(
-      const RealTimeReportControllerAndroid&) = delete;
-  ~RealTimeReportControllerAndroid() override;
-
- private:
-  // RealTimeReportController::Delegate
-  void StartWatchingExtensionRequestIfNeeded() override;
-  void StopWatchingExtensionRequest() override;
-};
-
-}  // namespace enterprise_reporting
-
-#endif  // CHROME_BROWSER_ENTERPRISE_REPORTING_REAL_TIME_REPORT_CONTROLLER_ANDROID_H_
diff --git a/chrome/browser/enterprise/reporting/real_time_report_controller_desktop.cc b/chrome/browser/enterprise/reporting/real_time_report_controller_desktop.cc
deleted file mode 100644
index 4b4349c..0000000
--- a/chrome/browser/enterprise/reporting/real_time_report_controller_desktop.cc
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright 2023 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/enterprise/reporting/real_time_report_controller_desktop.h"
-
-#include <memory>
-
-#include "chrome/browser/enterprise/reporting/extension_request/extension_request_observer_factory.h"
-#include "chrome/browser/enterprise/reporting/extension_request/extension_request_report_generator.h"
-#include "components/enterprise/browser/reporting/real_time_report_controller.h"
-
-namespace enterprise_reporting {
-
-RealTimeReportControllerDesktop::RealTimeReportControllerDesktop(
-    Profile* profile)
-    : extension_request_observer_factory_(
-          std::make_unique<ExtensionRequestObserverFactory>(profile)) {}
-
-RealTimeReportControllerDesktop::~RealTimeReportControllerDesktop() = default;
-
-void RealTimeReportControllerDesktop::StartWatchingExtensionRequestIfNeeded() {
-  if (!extension_request_observer_factory_) {
-    return;
-  }
-
-  // On CrOS, the function may be called twice during startup.
-  if (extension_request_observer_factory_->IsReportEnabled()) {
-    return;
-  }
-
-  // Unretained is safe here as the callback will always be called synchronously
-  // while the owner will be deleted before the controller.
-  extension_request_observer_factory_->EnableReport(base::BindRepeating(
-      &RealTimeReportControllerDesktop::TriggerExtensionRequest,
-      base::Unretained(this)));
-}
-
-void RealTimeReportControllerDesktop::StopWatchingExtensionRequest() {
-  if (extension_request_observer_factory_) {
-    extension_request_observer_factory_->DisableReport();
-  }
-}
-
-void RealTimeReportControllerDesktop::TriggerExtensionRequest(
-    Profile* profile) {
-  if (!trigger_callback_.is_null()) {
-    trigger_callback_.Run(
-        RealTimeReportController::ReportTrigger::kExtensionRequest,
-        ExtensionRequestReportGenerator::ExtensionRequestData(profile));
-  }
-}
-
-}  // namespace enterprise_reporting
diff --git a/chrome/browser/enterprise/reporting/real_time_report_controller_desktop.h b/chrome/browser/enterprise/reporting/real_time_report_controller_desktop.h
deleted file mode 100644
index 2e57e00..0000000
--- a/chrome/browser/enterprise/reporting/real_time_report_controller_desktop.h
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright 2023 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_ENTERPRISE_REPORTING_REAL_TIME_REPORT_CONTROLLER_DESKTOP_H_
-#define CHROME_BROWSER_ENTERPRISE_REPORTING_REAL_TIME_REPORT_CONTROLLER_DESKTOP_H_
-
-#include "chrome/browser/enterprise/reporting/extension_request/extension_request_observer_factory.h"
-#include "components/enterprise/browser/reporting/real_time_report_controller.h"
-
-namespace enterprise_reporting {
-
-class RealTimeReportControllerDesktop
-    : public RealTimeReportController::Delegate {
- public:
-  explicit RealTimeReportControllerDesktop(Profile* profile = nullptr);
-  RealTimeReportControllerDesktop(const RealTimeReportControllerDesktop&) =
-      delete;
-  RealTimeReportControllerDesktop& operator=(
-      const RealTimeReportControllerDesktop&) = delete;
-  ~RealTimeReportControllerDesktop() override;
-
-  // RealTimeReportController::Delegate
-  void StartWatchingExtensionRequestIfNeeded() override;
-  void StopWatchingExtensionRequest() override;
-
-  void TriggerExtensionRequest(Profile* profile);
-
- private:
-  std::unique_ptr<ExtensionRequestObserverFactory>
-      extension_request_observer_factory_;
-};
-
-}  // namespace enterprise_reporting
-
-#endif  // CHROME_BROWSER_ENTERPRISE_REPORTING_REAL_TIME_REPORT_CONTROLLER_DESKTOP_H_
diff --git a/chrome/browser/enterprise/reporting/real_time_report_controller_unittest.cc b/chrome/browser/enterprise/reporting/real_time_report_controller_unittest.cc
deleted file mode 100644
index 453e613..0000000
--- a/chrome/browser/enterprise/reporting/real_time_report_controller_unittest.cc
+++ /dev/null
@@ -1,107 +0,0 @@
-// Copyright 2023 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/enterprise/browser/reporting/real_time_report_controller.h"
-
-#include <memory>
-#include <vector>
-
-#include "build/build_config.h"
-#include "chrome/browser/enterprise/reporting/extension_request/extension_request_report_generator.h"
-#include "chrome/browser/enterprise/reporting/real_time_report_controller_desktop.h"
-#include "chrome/browser/enterprise/reporting/reporting_delegate_factory_desktop.h"
-#include "chrome/test/base/testing_browser_process.h"
-#include "chrome/test/base/testing_profile_manager.h"
-#include "components/enterprise/browser/reporting/real_time_uploader.h"
-#include "components/enterprise/common/proto/extensions_workflow_events.pb.h"
-#include "components/policy/core/common/cloud/dm_token.h"
-#include "content/public/test/browser_task_environment.h"
-#include "testing/gmock/include/gmock/gmock.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-using ::testing::_;
-using ::testing::ByMove;
-using ::testing::DoAll;
-using ::testing::Invoke;
-using ::testing::Return;
-using ::testing::WithArgs;
-
-namespace enterprise_reporting {
-
-namespace {
-
-class MockRealTimeReportGenerator : public RealTimeReportGenerator {
- public:
-  explicit MockRealTimeReportGenerator(
-      ReportingDelegateFactory* delegate_factory)
-      : RealTimeReportGenerator(delegate_factory) {}
-
-  MOCK_METHOD2(Generate,
-               std::vector<std::unique_ptr<google::protobuf::MessageLite>>(
-                   ReportType type,
-                   const RealTimeReportGenerator::Data& data));
-};
-
-class MockRealTimeUploader : public RealTimeUploader {
- public:
-  MockRealTimeUploader() : RealTimeUploader(reporting::Priority::FAST_BATCH) {}
-
-  MOCK_METHOD2(Upload,
-               void(std::unique_ptr<google::protobuf::MessageLite> report,
-                    EnqueueCallback callback));
-};
-
-}  // namespace
-
-class RealTimeReportControllerTest : public ::testing::Test {
- public:
-  RealTimeReportControllerTest() = default;
-  ~RealTimeReportControllerTest() override = default;
-
-  void SetUp() override { ASSERT_TRUE(profile_manager_.SetUp()); }
-
-  content::BrowserTaskEnvironment task_environment_;
-  TestingProfileManager profile_manager_{TestingBrowserProcess::GetGlobal()};
-  ReportingDelegateFactoryDesktop delegate_factory_;
-};
-
-TEST_F(RealTimeReportControllerTest, ExtensionRequest) {
-  std::vector<std::unique_ptr<google::protobuf::MessageLite>> reports;
-  reports.push_back(std::make_unique<ExtensionsWorkflowEvent>());
-  reports.push_back(std::make_unique<ExtensionsWorkflowEvent>());
-
-  Profile* profile = profile_manager_.CreateTestingProfile("profile");
-
-  auto report_generator =
-      std::make_unique<MockRealTimeReportGenerator>(&delegate_factory_);
-  auto report_uploader = std::make_unique<MockRealTimeUploader>();
-
-  RealTimeReportController report_controller{&delegate_factory_};
-
-  EXPECT_CALL(
-      *report_generator.get(),
-      Generate(RealTimeReportGenerator::ReportType::kExtensionRequest, _))
-      .WillOnce(DoAll(
-          WithArgs<1>(
-              Invoke([profile](const MockRealTimeReportGenerator::Data& data) {
-                EXPECT_EQ(profile,
-                          static_cast<const ExtensionRequestReportGenerator::
-                                          ExtensionRequestData&>(data)
-                              .profile);
-              })),
-          Return(ByMove(std::move(reports)))));
-  EXPECT_CALL(*report_uploader, Upload(_, _)).Times(2);
-
-  report_controller.SetExtensionRequestUploaderForTesting(
-      std::move(report_uploader));
-  report_controller.SetReportGeneratorForTesting(std::move(report_generator));
-  report_controller.OnDMTokenUpdated(
-      policy::DMToken::CreateValidToken("dm-token"));
-
-  static_cast<RealTimeReportControllerDesktop*>(
-      report_controller.GetDelegateForTesting())
-      ->TriggerExtensionRequest(profile);
-}
-
-}  // namespace enterprise_reporting
diff --git a/chrome/browser/enterprise/reporting/report_scheduler_android.cc b/chrome/browser/enterprise/reporting/report_scheduler_android.cc
index 9210a7c9..ce5e625 100644
--- a/chrome/browser/enterprise/reporting/report_scheduler_android.cc
+++ b/chrome/browser/enterprise/reporting/report_scheduler_android.cc
@@ -37,6 +37,18 @@
   // No-op because in-app auto-update is not supported on Android.
 }
 
+void ReportSchedulerAndroid::StartWatchingExtensionRequestIfNeeded() {
+  // No-op because extensions are not supported on Android.
+}
+
+void ReportSchedulerAndroid::StopWatchingExtensionRequest() {
+  // No-op because extensions are not supported on Android.
+}
+
+void ReportSchedulerAndroid::OnExtensionRequestUploaded() {
+  // No-op because extensions are not supported on Android.
+}
+
 policy::DMToken ReportSchedulerAndroid::GetProfileDMToken() {
   absl::optional<std::string> dm_token = reporting::GetUserDmToken(profile_);
   if (!dm_token || dm_token->empty())
diff --git a/chrome/browser/enterprise/reporting/report_scheduler_android.h b/chrome/browser/enterprise/reporting/report_scheduler_android.h
index 204bdea2..68b964124 100644
--- a/chrome/browser/enterprise/reporting/report_scheduler_android.h
+++ b/chrome/browser/enterprise/reporting/report_scheduler_android.h
@@ -27,6 +27,9 @@
                                     base::TimeDelta upload_interval) override;
   void StopWatchingUpdates() override;
   void OnBrowserVersionUploaded() override;
+  void StartWatchingExtensionRequestIfNeeded() override;
+  void StopWatchingExtensionRequest() override;
+  void OnExtensionRequestUploaded() override;
   policy::DMToken GetProfileDMToken() override;
   std::string GetProfileClientId() override;
 
diff --git a/chrome/browser/enterprise/reporting/report_scheduler_desktop.cc b/chrome/browser/enterprise/reporting/report_scheduler_desktop.cc
index bcbd3b4..b77b2cef 100644
--- a/chrome/browser/enterprise/reporting/report_scheduler_desktop.cc
+++ b/chrome/browser/enterprise/reporting/report_scheduler_desktop.cc
@@ -10,6 +10,7 @@
 #include "base/notreached.h"
 #include "build/chromeos_buildflags.h"
 #include "chrome/browser/browser_process.h"
+#include "chrome/browser/enterprise/reporting/extension_request/extension_request_report_generator.h"
 #include "chrome/browser/enterprise/reporting/prefs.h"
 #include "chrome/browser/profiles/reporting_util.h"
 #include "chrome/browser/upgrade_detector/build_state.h"
@@ -46,15 +47,24 @@
 }  // namespace
 
 ReportSchedulerDesktop::ReportSchedulerDesktop()
-    : profile_(nullptr), prefs_(LocalState()) {}
+    : ReportSchedulerDesktop(nullptr, false) {}
 
-ReportSchedulerDesktop::ReportSchedulerDesktop(Profile* profile)
-    : profile_(profile), prefs_(profile->GetPrefs()) {
-  if (profile) {
+ReportSchedulerDesktop::ReportSchedulerDesktop(Profile* profile,
+                                               bool profile_reporting) {
+  if (profile_reporting) {
 #if BUILDFLAG(IS_CHROMEOS_ASH)
     // Profile reporting is on LaCrOs instead of Ash.
     NOTREACHED();
 #endif
+    profile_ = profile;
+    prefs_ = profile->GetPrefs();
+    // Extension request hasn't support profile report yet. When we do, we also
+    // need to refactor the code to avoid multiple extension request observer.
+  } else {
+    profile_ = nullptr;
+    prefs_ = LocalState();
+    extension_request_observer_factory_ =
+        std::make_unique<ExtensionRequestObserverFactory>(profile);
   }
 }
 
@@ -110,6 +120,26 @@
   }
 }
 
+void ReportSchedulerDesktop::StartWatchingExtensionRequestIfNeeded() {
+  if (!extension_request_observer_factory_)
+    return;
+
+  // On CrOS, the function may be called twice during startup.
+  if (extension_request_observer_factory_->IsReportEnabled())
+    return;
+
+  extension_request_observer_factory_->EnableReport(
+      base::BindRepeating(&ReportSchedulerDesktop::TriggerExtensionRequest,
+                          base::Unretained(this)));
+}
+
+void ReportSchedulerDesktop::StopWatchingExtensionRequest() {
+  if (extension_request_observer_factory_)
+    extension_request_observer_factory_->DisableReport();
+}
+
+void ReportSchedulerDesktop::OnExtensionRequestUploaded() {}
+
 policy::DMToken ReportSchedulerDesktop::GetProfileDMToken() {
   absl::optional<std::string> dm_token = reporting::GetUserDmToken(profile_);
   if (!dm_token || dm_token->empty())
@@ -132,4 +162,12 @@
   }
 }
 
+void ReportSchedulerDesktop::TriggerExtensionRequest(Profile* profile) {
+  if (!trigger_realtime_report_callback_.is_null()) {
+    trigger_realtime_report_callback_.Run(
+        ReportScheduler::ReportTrigger::kTriggerExtensionRequestRealTime,
+        ExtensionRequestReportGenerator::ExtensionRequestData(profile));
+  }
+}
+
 }  // namespace enterprise_reporting
diff --git a/chrome/browser/enterprise/reporting/report_scheduler_desktop.h b/chrome/browser/enterprise/reporting/report_scheduler_desktop.h
index 3f6fc63b..7476a0e 100644
--- a/chrome/browser/enterprise/reporting/report_scheduler_desktop.h
+++ b/chrome/browser/enterprise/reporting/report_scheduler_desktop.h
@@ -20,8 +20,10 @@
                                public BuildStateObserver {
  public:
   ReportSchedulerDesktop();
-  /* `profile` is used for profile reporting */
-  explicit ReportSchedulerDesktop(Profile* profile);
+  /* `profile` is used for profile reporting or Chrome OS session.
+   * `profile_reporting` should be set to false for Chrome OS only.*/
+  explicit ReportSchedulerDesktop(Profile* profile,
+                                  bool profile_reporting = false);
   ReportSchedulerDesktop(const ReportSchedulerDesktop&) = delete;
   ReportSchedulerDesktop& operator=(const ReportSchedulerDesktop&) = delete;
 
@@ -34,15 +36,22 @@
   void StopWatchingUpdates() override;
   void OnBrowserVersionUploaded() override;
 
+  void StartWatchingExtensionRequestIfNeeded() override;
+  void StopWatchingExtensionRequest() override;
+  void OnExtensionRequestUploaded() override;
   policy::DMToken GetProfileDMToken() override;
   std::string GetProfileClientId() override;
 
   // BuildStateObserver implementation.
   void OnUpdate(const BuildState* build_state) override;
 
+  void TriggerExtensionRequest(Profile* profile);
+
  private:
   raw_ptr<Profile> profile_;
   raw_ptr<PrefService> prefs_;
+  std::unique_ptr<ExtensionRequestObserverFactory>
+      extension_request_observer_factory_;
 };
 
 }  // namespace enterprise_reporting
diff --git a/chrome/browser/enterprise/reporting/report_scheduler_unittest.cc b/chrome/browser/enterprise/reporting/report_scheduler_unittest.cc
index 428363e..baa141a2 100644
--- a/chrome/browser/enterprise/reporting/report_scheduler_unittest.cc
+++ b/chrome/browser/enterprise/reporting/report_scheduler_unittest.cc
@@ -17,6 +17,7 @@
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
 #include "chrome/browser/browser_process.h"
+#include "chrome/browser/enterprise/reporting/extension_request/extension_request_report_generator.h"
 #include "chrome/browser/enterprise/reporting/prefs.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/upgrade_detector/build_state.h"
@@ -28,8 +29,11 @@
 #include "components/enterprise/browser/controller/fake_browser_dm_token_storage.h"
 #include "components/enterprise/browser/reporting/chrome_profile_request_generator.h"
 #include "components/enterprise/browser/reporting/common_pref_names.h"
+#include "components/enterprise/browser/reporting/real_time_report_generator.h"
+#include "components/enterprise/browser/reporting/real_time_uploader.h"
 #include "components/enterprise/browser/reporting/report_generator.h"
 #include "components/enterprise/browser/reporting/report_request.h"
+#include "components/enterprise/common/proto/extensions_workflow_events.pb.h"
 #include "components/policy/core/common/cloud/mock_cloud_policy_client.h"
 #include "components/reporting/client/report_queue_provider.h"
 #include "components/reporting/proto/synced/record_constants.pb.h"
@@ -117,6 +121,38 @@
                void(ReportType, ReportRequestQueue, ReportCallback));
 };
 
+class MockRealTimeReportGenerator : public RealTimeReportGenerator {
+ public:
+#if BUILDFLAG(IS_ANDROID)
+  explicit MockRealTimeReportGenerator(
+      ReportingDelegateFactoryAndroid* delegate_factory)
+      : RealTimeReportGenerator(delegate_factory) {}
+#else
+  explicit MockRealTimeReportGenerator(
+      ReportingDelegateFactoryDesktop* delegate_factory)
+      : RealTimeReportGenerator(delegate_factory) {}
+#endif  // BUILDFLAG(IS_ANDROID)
+
+  MOCK_METHOD2(Generate,
+               std::vector<std::unique_ptr<google::protobuf::MessageLite>>(
+                   ReportType type,
+                   const RealTimeReportGenerator::Data& data));
+};
+
+class MockRealTimeUploader : public RealTimeUploader {
+ public:
+  MockRealTimeUploader() : RealTimeUploader(reporting::Priority::FAST_BATCH) {}
+
+  void Upload(std::unique_ptr<google::protobuf::MessageLite> report,
+              EnqueueCallback callback) override {
+    OnUpload(report.get(), callback);
+  }
+
+  MOCK_METHOD2(OnUpload,
+               void(google::protobuf::MessageLite* report,
+                    EnqueueCallback& callback));
+};
+
 class MockChromeProfileRequestGenerator : public ChromeProfileRequestGenerator {
  public:
 #if BUILDFLAG(IS_ANDROID)
@@ -156,6 +192,12 @@
     uploader_ptr_ = std::make_unique<MockReportUploader>();
     uploader_ = uploader_ptr_.get();
 
+    real_time_generator_ptr_ = std::make_unique<MockRealTimeReportGenerator>(
+        &report_delegate_factory_);
+    real_time_generator_ = real_time_generator_ptr_.get();
+    extension_request_uploader_ptr_ = std::make_unique<MockRealTimeUploader>();
+    extension_request_uploader_ = extension_request_uploader_ptr_.get();
+
     profile_request_generator_ptr_ =
         std::make_unique<MockChromeProfileRequestGenerator>(
             &report_delegate_factory_);
@@ -182,8 +224,11 @@
     params.client = client_;
     params.delegate = report_delegate_factory_.GetReportSchedulerDelegate();
     params.report_generator = std::move(generator_ptr_);
+    params.real_time_report_generator = std::move(real_time_generator_ptr_);
     scheduler_ = std::make_unique<ReportScheduler>(std::move(params));
     scheduler_->SetReportUploaderForTesting(std::move(uploader_ptr_));
+    scheduler_->SetExtensionRequestUploaderForTesting(
+        std::move(extension_request_uploader_ptr_));
   }
 
 #if !BUILDFLAG(IS_CHROMEOS_ASH)
@@ -195,7 +240,8 @@
 #if BUILDFLAG(IS_ANDROID)
         std::make_unique<ReportSchedulerAndroid>(profile);
 #else
-        std::make_unique<ReportSchedulerDesktop>(profile);
+        std::make_unique<ReportSchedulerDesktop>(profile,
+                                                 /*profile_reporting=*/true);
 #endif  // BUILDFLAG(IS_ANDROID)
     params.profile_request_generator =
         std::move(profile_request_generator_ptr_);
@@ -254,12 +300,27 @@
 #if BUILDFLAG(IS_CHROMEOS_ASH)
     EXPECT_CALL(*client_, SetupRegistration(_, _, _)).Times(0);
 #else
+    EXPECT_CALL(*client_, SetupRegistration(kDMToken, kClientId, _));
+#endif
+  }
+
+  void EXPECT_CALL_SetupRegistrationWithSetDMToken() {
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+    EXPECT_CALL(*client_, SetupRegistration(_, _, _)).Times(0);
+#else
     EXPECT_CALL(*client_, SetupRegistration(kDMToken, kClientId, _))
         .WillOnce(WithArgs<0>(
             Invoke(client_.get(), &policy::MockCloudPolicyClient::SetDMToken)));
 #endif
   }
 
+#if !BUILDFLAG(IS_ANDROID)
+  void TriggerExtensionRequestReport(Profile* profile) {
+    static_cast<ReportSchedulerDesktop*>(scheduler_->GetDelegateForTesting())
+        ->TriggerExtensionRequest(profile);
+  }
+#endif  // !BUILDFLAG(IS_ANDROID)
+
   content::BrowserTaskEnvironment task_environment_;
   ScopedTestingLocalState local_state_;
   TestingProfileManager profile_manager_;
@@ -273,6 +334,8 @@
   raw_ptr<policy::MockCloudPolicyClient, DanglingUntriaged> client_;
   raw_ptr<MockReportGenerator, DanglingUntriaged> generator_;
   raw_ptr<MockReportUploader, DanglingUntriaged> uploader_;
+  raw_ptr<MockRealTimeReportGenerator, DanglingUntriaged> real_time_generator_;
+  raw_ptr<MockRealTimeUploader, DanglingUntriaged> extension_request_uploader_;
   raw_ptr<MockChromeProfileRequestGenerator, DanglingUntriaged>
       profile_request_generator_;
 #if !BUILDFLAG(IS_CHROMEOS_ASH)
@@ -285,6 +348,8 @@
   std::unique_ptr<policy::MockCloudPolicyClient> client_ptr_;
   std::unique_ptr<MockReportGenerator> generator_ptr_;
   std::unique_ptr<MockReportUploader> uploader_ptr_;
+  std::unique_ptr<MockRealTimeReportGenerator> real_time_generator_ptr_;
+  std::unique_ptr<MockRealTimeUploader> extension_request_uploader_ptr_;
   std::unique_ptr<MockChromeProfileRequestGenerator>
       profile_request_generator_ptr_;
 };
@@ -381,7 +446,7 @@
 }
 
 TEST_F(ReportSchedulerTest, UploadReportPersistentError) {
-  EXPECT_CALL_SetupRegistration();
+  EXPECT_CALL_SetupRegistrationWithSetDMToken();
   EXPECT_CALL(*generator_, OnGenerate(ReportType::kFull, _))
       .WillOnce(WithArgs<1>(ScheduleGeneratorCallback(1)));
   EXPECT_CALL(*uploader_, SetRequestAndUpload(ReportType::kFull, _, _))
@@ -407,7 +472,7 @@
 }
 
 TEST_F(ReportSchedulerTest, NoReportGenerate) {
-  EXPECT_CALL_SetupRegistration();
+  EXPECT_CALL_SetupRegistrationWithSetDMToken();
   EXPECT_CALL(*generator_, OnGenerate(ReportType::kFull, _))
       .WillOnce(WithArgs<1>(ScheduleGeneratorCallback(0)));
   EXPECT_CALL(*uploader_, SetRequestAndUpload(_, _, _)).Times(0);
@@ -644,8 +709,10 @@
   ::testing::Mock::VerifyAndClearExpectations(uploader_);
 }
 
-// Android does not support version updates
-#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_CHROMEOS_ASH)
+// Android does not support version updates nor extensions
+#if !BUILDFLAG(IS_ANDROID)
+
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
 
 // Tests that a basic report is generated and uploaded when a browser update is
 // detected.
@@ -822,6 +889,41 @@
   histogram_tester_.ExpectUniqueSample(kUploadTriggerMetricName, 1, 1);
 }
 
-#endif  // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_CHROMEOS_ASH)
+#endif  // !BUILDFLAG(IS_CHROMEOS_ASH)
+
+TEST_F(ReportSchedulerTest, ExtensionRequestWithRealTimePipeline) {
+  EXPECT_CALL_SetupRegistration();
+  EXPECT_CALL(*generator_, OnGenerate(_, _)).Times(0);
+  EXPECT_CALL(*uploader_, SetRequestAndUpload(_, _, _)).Times(0);
+
+  Profile* profile = profile_manager_.CreateTestingProfile("profile");
+
+  std::vector<std::unique_ptr<google::protobuf::MessageLite>> reports;
+  reports.push_back(std::make_unique<ExtensionsWorkflowEvent>());
+  reports.push_back(std::make_unique<ExtensionsWorkflowEvent>());
+
+  EXPECT_CALL(
+      *real_time_generator_,
+      Generate(RealTimeReportGenerator::ReportType::kExtensionRequest, _))
+      .WillOnce(DoAll(
+          WithArgs<1>(
+              Invoke([profile](const MockRealTimeReportGenerator::Data& data) {
+                EXPECT_EQ(profile,
+                          static_cast<const ExtensionRequestReportGenerator::
+                                          ExtensionRequestData&>(data)
+                              .profile);
+              })),
+          Return(ByMove(std::move(reports)))));
+  EXPECT_CALL(*extension_request_uploader_, OnUpload(_, _)).Times(2);
+  CreateScheduler();
+
+  TriggerExtensionRequestReport(profile);
+
+  ExpectLastUploadTimestampUpdated(false);
+
+  histogram_tester_.ExpectUniqueSample(kUploadTriggerMetricName, 5, 1);
+}
+
+#endif  // !BUILDFLAG(IS_ANDROID)
 
 }  // namespace enterprise_reporting
diff --git a/chrome/browser/enterprise/reporting/reporting_delegate_factory_android.cc b/chrome/browser/enterprise/reporting/reporting_delegate_factory_android.cc
index 84f4ff4..b01eb60 100644
--- a/chrome/browser/enterprise/reporting/reporting_delegate_factory_android.cc
+++ b/chrome/browser/enterprise/reporting/reporting_delegate_factory_android.cc
@@ -4,12 +4,10 @@
 
 #include "chrome/browser/enterprise/reporting/reporting_delegate_factory_android.h"
 
-#include <memory>
 #include <utility>
 
 #include "chrome/browser/enterprise/reporting/browser_report_generator_android.h"
 #include "chrome/browser/enterprise/reporting/profile_report_generator_android.h"
-#include "chrome/browser/enterprise/reporting/real_time_report_controller_android.h"
 #include "chrome/browser/enterprise/reporting/report_scheduler_android.h"
 
 namespace enterprise_reporting {
@@ -41,11 +39,6 @@
   return nullptr;
 }
 
-std::unique_ptr<RealTimeReportController::Delegate>
-ReportingDelegateFactoryAndroid::GetRealTimeReportControllerDelegate() {
-  return std::make_unique<RealTimeReportControllerAndroid>();
-}
-
 std::unique_ptr<ReportScheduler::Delegate>
 ReportingDelegateFactoryAndroid::GetReportSchedulerDelegate(Profile* profile) {
   return std::make_unique<ReportSchedulerAndroid>(profile);
diff --git a/chrome/browser/enterprise/reporting/reporting_delegate_factory_android.h b/chrome/browser/enterprise/reporting/reporting_delegate_factory_android.h
index a4ab89b..d687774 100644
--- a/chrome/browser/enterprise/reporting/reporting_delegate_factory_android.h
+++ b/chrome/browser/enterprise/reporting/reporting_delegate_factory_android.h
@@ -35,8 +35,6 @@
       override;
   std::unique_ptr<RealTimeReportGenerator::Delegate>
   GetRealTimeReportGeneratorDelegate() override;
-  std::unique_ptr<RealTimeReportController::Delegate>
-  GetRealTimeReportControllerDelegate() override;
 
   std::unique_ptr<ReportScheduler::Delegate> GetReportSchedulerDelegate(
       Profile* profile);
diff --git a/chrome/browser/enterprise/reporting/reporting_delegate_factory_desktop.cc b/chrome/browser/enterprise/reporting/reporting_delegate_factory_desktop.cc
index c968a0b..f32b93c9 100644
--- a/chrome/browser/enterprise/reporting/reporting_delegate_factory_desktop.cc
+++ b/chrome/browser/enterprise/reporting/reporting_delegate_factory_desktop.cc
@@ -4,15 +4,11 @@
 
 #include "chrome/browser/enterprise/reporting/reporting_delegate_factory_desktop.h"
 
-#include <memory>
-
 #include "chrome/browser/enterprise/reporting/browser_report_generator_desktop.h"
 #include "chrome/browser/enterprise/reporting/profile_report_generator_desktop.h"
-#include "chrome/browser/enterprise/reporting/real_time_report_controller_desktop.h"
 #include "chrome/browser/enterprise/reporting/real_time_report_generator_desktop.h"
 #include "chrome/browser/enterprise/reporting/report_generator_desktop.h"
 #include "chrome/browser/enterprise/reporting/report_scheduler_desktop.h"
-#include "components/enterprise/browser/reporting/real_time_report_controller.h"
 
 namespace enterprise_reporting {
 
@@ -41,19 +37,10 @@
   return std::make_unique<RealTimeReportGeneratorDesktop>();
 }
 
-std::unique_ptr<RealTimeReportController::Delegate>
-ReportingDelegateFactoryDesktop::GetRealTimeReportControllerDelegate() {
-  return std::make_unique<RealTimeReportControllerDesktop>(profile_);
-}
-
 std::unique_ptr<ReportScheduler::Delegate>
 ReportingDelegateFactoryDesktop::GetReportSchedulerDelegate(Profile* profile) {
-  return std::make_unique<ReportSchedulerDesktop>(profile);
-}
-
-void ReportingDelegateFactoryDesktop::SetProfileForRealTimeController(
-    Profile* profile) {
-  profile_ = profile;
+  return std::make_unique<ReportSchedulerDesktop>(profile,
+                                                  /*profile_reporting=*/true);
 }
 
 }  // namespace enterprise_reporting
diff --git a/chrome/browser/enterprise/reporting/reporting_delegate_factory_desktop.h b/chrome/browser/enterprise/reporting/reporting_delegate_factory_desktop.h
index d601273..4d3ded9 100644
--- a/chrome/browser/enterprise/reporting/reporting_delegate_factory_desktop.h
+++ b/chrome/browser/enterprise/reporting/reporting_delegate_factory_desktop.h
@@ -9,10 +9,8 @@
 
 #include <memory>
 
-#include "base/allocator/partition_allocator/pointers/raw_ptr.h"
 #include "components/enterprise/browser/reporting/browser_report_generator.h"
 #include "components/enterprise/browser/reporting/profile_report_generator.h"
-#include "components/enterprise/browser/reporting/real_time_report_controller.h"
 #include "components/enterprise/browser/reporting/real_time_report_generator.h"
 #include "components/enterprise/browser/reporting/report_generator.h"
 #include "components/enterprise/browser/reporting/report_scheduler.h"
@@ -47,16 +45,8 @@
   std::unique_ptr<RealTimeReportGenerator::Delegate>
   GetRealTimeReportGeneratorDelegate() override;
 
-  std::unique_ptr<RealTimeReportController::Delegate>
-  GetRealTimeReportControllerDelegate() override;
-
   std::unique_ptr<ReportScheduler::Delegate> GetReportSchedulerDelegate(
       Profile* profile);
-
-  void SetProfileForRealTimeController(Profile* profile);
-
- private:
-  raw_ptr<Profile> profile_;
 };
 
 }  // namespace enterprise_reporting
diff --git a/chrome/browser/extensions/extension_service.cc b/chrome/browser/extensions/extension_service.cc
index 348face..bb4de7d8 100644
--- a/chrome/browser/extensions/extension_service.cc
+++ b/chrome/browser/extensions/extension_service.cc
@@ -76,6 +76,7 @@
 #include "chrome/common/url_constants.h"
 #include "components/crx_file/id_util.h"
 #include "components/favicon_base/favicon_url_parser.h"
+#include "components/safe_browsing/core/common/safe_browsing_prefs.h"
 #include "components/supervised_user/core/common/buildflags.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/notification_service.h"
@@ -531,8 +532,14 @@
   OnInstalledExtensionsLoaded();
 
   LoadExtensionsFromCommandLineFlag(::switches::kDisableExtensionsExcept);
-  if (load_command_line_extensions)
-    LoadExtensionsFromCommandLineFlag(switches::kLoadExtension);
+  if (load_command_line_extensions) {
+    if (safe_browsing::IsEnhancedProtectionEnabled(*profile_->GetPrefs())) {
+      VLOG(1) << "--load-extension is not allowed for users opted into "
+              << "Enhanced Safe Browsing, ignoring.";
+    } else {
+      LoadExtensionsFromCommandLineFlag(switches::kLoadExtension);
+    }
+  }
   EnabledReloadableExtensions();
   MaybeFinishShutdownDelayed();
   SetReadyAndNotifyListeners();
diff --git a/chrome/browser/extensions/extension_service_unittest.cc b/chrome/browser/extensions/extension_service_unittest.cc
index 73b1c96..256d6ad1 100644
--- a/chrome/browser/extensions/extension_service_unittest.cc
+++ b/chrome/browser/extensions/extension_service_unittest.cc
@@ -5865,6 +5865,42 @@
   EXPECT_TRUE(registry()->GenerateInstalledExtensionsSet().empty());
 }
 
+// Tests that --load-extension is ignored for users opted in to Enhanced Safe
+// Browsing (ESB).
+TEST_F(ExtensionServiceTest, WillNotLoadFromCommandLineForESBUsers) {
+  InitializeEmptyExtensionServiceWithTestingPrefs();
+  // Enable ESB.
+  profile()->GetPrefs()->SetBoolean(prefs::kSafeBrowsingEnabled, true);
+  profile()->GetPrefs()->SetBoolean(prefs::kSafeBrowsingEnhanced, true);
+  // Try to load an extension from command line.
+  base::FilePath path =
+      base::MakeAbsoluteFilePath(data_dir().AppendASCII("good_unpacked"));
+  base::CommandLine::ForCurrentProcess()->AppendSwitchPath(
+      switches::kLoadExtension, path);
+  service()->Init();
+  task_environment()->RunUntilIdle();
+  ASSERT_EQ(0u, loaded_extensions().size());
+  ValidatePrefKeyCount(0);
+}
+
+// Tests --load-extension works for non-ESB users.
+TEST_F(ExtensionServiceTest, LoadsFromCommandLineForNonESBUsers) {
+  InitializeEmptyExtensionServiceWithTestingPrefs();
+  // Disable ESB.
+  profile()->GetPrefs()->SetBoolean(prefs::kSafeBrowsingEnabled, false);
+  profile()->GetPrefs()->SetBoolean(prefs::kSafeBrowsingEnhanced, false);
+  // Try to load an extension from command line.
+  base::FilePath path =
+      base::MakeAbsoluteFilePath(data_dir().AppendASCII("good_unpacked"));
+  base::CommandLine::ForCurrentProcess()->AppendSwitchPath(
+      switches::kLoadExtension, path);
+  service()->Init();
+  task_environment()->RunUntilIdle();
+  EXPECT_EQ(0u, GetErrors().size());
+  ASSERT_EQ(1u, loaded_extensions().size());
+  ValidatePrefKeyCount(1);
+}
+
 // Tests that we generate IDs when they are not specified in the manifest for
 // --load-extension.
 TEST_F(ExtensionServiceTest, GenerateID) {
diff --git a/chrome/browser/feed/feed_service_factory.cc b/chrome/browser/feed/feed_service_factory.cc
index a66edaa..993a7e5 100644
--- a/chrome/browser/feed/feed_service_factory.cc
+++ b/chrome/browser/feed/feed_service_factory.cc
@@ -18,6 +18,7 @@
 #include "chrome/browser/metrics/chrome_metrics_service_accessor.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_key.h"
+#include "chrome/browser/search_engines/template_url_service_factory.h"
 #include "chrome/browser/signin/identity_manager_factory.h"
 #include "chrome/common/channel_info.h"
 #include "chrome/common/chrome_version.h"
@@ -242,7 +243,8 @@
       HistoryServiceFactory::GetForProfile(profile,
                                            ServiceAccessType::IMPLICIT_ACCESS),
       storage_partition->GetURLLoaderFactoryForBrowserProcess(),
-      background_task_runner, api_key, chrome_info);
+      background_task_runner, api_key, chrome_info,
+      TemplateURLServiceFactory::GetForProfile(profile));
 }
 
 bool FeedServiceFactory::ServiceIsNULLWhileTesting() const {
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index d52e777..d3c7e2e 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -945,6 +945,14 @@
     "expiry_milestone": 120
   },
   {
+    "name": "camera-mic-preview",
+    "owners": [
+      "bryantchandler",
+      "openscreen-eng"
+    ],
+    "expiry_milestone": 140
+  },
+  {
     "name": "canvas-2d-layers",
     "owners": [ "fserb", "jpgravel", "juanmihd", "yiyix" ],
     "expiry_milestone": 120
@@ -3271,6 +3279,11 @@
     "expiry_milestone": 120
   },
   {
+    "name": "enable-search-customizable-shortcuts-in-launcher",
+    "owners": [ "xiangdongkong", "cros-peripherals@google.com"],
+    "expiry_milestone": 130
+  },
+  {
     "name": "enable-search-in-shortcuts-app",
     "owners": [ "jimmyxgong", "zentaro", "cros-peripherals@google.com"],
     "expiry_milestone": 120
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 2b2cbf8..3b5c8a9 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -745,6 +745,12 @@
     "When enabled, Chrome will enable the Service Worker Static Routing API. "
     "https://chromestatus.com/feature/5185352976826368";
 
+#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_CHROMEOS)
+const char kCameraMicPreviewName[] = "Camera and Mic Preview";
+const char kCameraMicPreviewDescription[] =
+    "Enables camera and mic preview in permission bubble and site settings.";
+#endif
+
 const char kChromeLabsName[] = "Chrome Labs";
 const char kChromeLabsDescription[] =
     "Access Chrome Labs through the toolbar menu to see featured user-facing "
@@ -1166,6 +1172,11 @@
 extern const char kEnableShortcutCustomizationDescription[] =
     "Enable customization of shortcuts in the new shortcuts app.";
 
+extern const char kEnableSearchCustomizableShortcutsInLauncherName[] =
+    "Enable search for customizable shortcuts in launcher";
+extern const char kEnableSearchCustomizableShortcutsInLauncherDescription[] =
+    "Enable searching for customizable shortcuts in launcher.";
+
 extern const char kEnableSearchInShortcutsAppName[] =
     "Enable search in new shortcuts app";
 extern const char kEnableSearchInShortcutsAppDescription[] =
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index c8d9a86..2b7601a 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -406,6 +406,11 @@
 extern const char kServiceWorkerStaticRouterName[];
 extern const char kServiceWorkerStaticRouterDescription[];
 
+#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_CHROMEOS)
+extern const char kCameraMicPreviewName[];
+extern const char kCameraMicPreviewDescription[];
+#endif
+
 extern const char kCanvasOopRasterizationName[];
 extern const char kCanvasOopRasterizationDescription[];
 
@@ -764,6 +769,9 @@
 extern const char kEnableShortcutCustomizationName[];
 extern const char kEnableShortcutCustomizationDescription[];
 
+extern const char kEnableSearchCustomizableShortcutsInLauncherName[];
+extern const char kEnableSearchCustomizableShortcutsInLauncherDescription[];
+
 extern const char kEnableSearchInShortcutsAppName[];
 extern const char kEnableSearchInShortcutsAppDescription[];
 
diff --git a/chrome/browser/lens/java/src/org/chromium/chrome/browser/lens/LensMetrics.java b/chrome/browser/lens/java/src/org/chromium/chrome/browser/lens/LensMetrics.java
index f8a8b2c..0a692156 100644
--- a/chrome/browser/lens/java/src/org/chromium/chrome/browser/lens/LensMetrics.java
+++ b/chrome/browser/lens/java/src/org/chromium/chrome/browser/lens/LensMetrics.java
@@ -77,7 +77,8 @@
     // Only add new values at the end, right before NUM_ENTRIES.
     @IntDef({CameraOpenEntryPoint.OMNIBOX, CameraOpenEntryPoint.NEW_TAB_PAGE,
             CameraOpenEntryPoint.QUICK_ACTION_SEARCH_WIDGET, CameraOpenEntryPoint.TASKS_SURFACE,
-            CameraOpenEntryPoint.KEYBOARD, CameraOpenEntryPoint.NUM_ENTRIES})
+            CameraOpenEntryPoint.KEYBOARD, CameraOpenEntryPoint.SPOTLIGHT,
+            CameraOpenEntryPoint.NUM_ENTRIES})
     @Retention(RetentionPolicy.SOURCE)
     public static @interface CameraOpenEntryPoint {
         int OMNIBOX = 0;
@@ -85,7 +86,8 @@
         int QUICK_ACTION_SEARCH_WIDGET = 2;
         int TASKS_SURFACE = 3;
         int KEYBOARD = 4;
-        int NUM_ENTRIES = 5;
+        int SPOTLIGHT = 5;
+        int NUM_ENTRIES = 6;
     }
 
     /**
diff --git a/chrome/browser/mac/auth_session_request_browsertest.mm b/chrome/browser/mac/auth_session_request_browsertest.mm
index fd1e7878..bfa6536 100644
--- a/chrome/browser/mac/auth_session_request_browsertest.mm
+++ b/chrome/browser/mac/auth_session_request_browsertest.mm
@@ -5,7 +5,6 @@
 #import "chrome/browser/app_controller_mac.h"
 
 #include "base/mac/foundation_util.h"
-#import "base/mac/scoped_nsobject.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/profiles/profile_test_util.h"
@@ -25,6 +24,10 @@
 #include "testing/gtest_mac.h"
 #include "ui/base/page_transition_types.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 namespace {
 
 Profile& CreateAndWaitForProfile(const base::FilePath& profile_dir) {
@@ -38,13 +41,11 @@
 }
 
 void SetGuestProfileAsLastProfile() {
-  AppController* ac = base::mac::ObjCCast<AppController>(
-      [[NSApplication sharedApplication] delegate]);
-  ASSERT_TRUE(ac);
+  AppController* app_controller = AppController.sharedController;
   // Create the guest profile, and set it as the last used profile.
   Profile& guest_profile = CreateAndWaitForGuestProfile();
-  [ac setLastProfile:&guest_profile];
-  Profile* profile = [ac lastProfileIfLoaded];
+  [app_controller setLastProfile:&guest_profile];
+  Profile* profile = [app_controller lastProfileIfLoaded];
   ASSERT_TRUE(profile);
   EXPECT_EQ(guest_profile.GetPath(), profile->GetPath());
   EXPECT_TRUE(profile->IsGuestSession());
@@ -59,11 +60,11 @@
 using AuthSessionBrowserTest = InProcessBrowserTest;
 
 @interface MockASWebAuthenticationSessionRequest : NSObject {
-  base::scoped_nsobject<NSUUID> _uuid;
-  base::scoped_nsobject<NSURL> _initialURL;
+  NSUUID* __strong _uuid;
+  NSURL* __strong _initialURL;
 
-  base::scoped_nsobject<NSURL> _callbackURL;
-  base::scoped_nsobject<NSError> _cancellationError;
+  NSURL* __strong _callbackURL;
+  NSError* __strong _cancellationError;
 }
 
 // ASWebAuthenticationSessionRequest:
@@ -87,8 +88,8 @@
 
 - (instancetype)initWithInitialURL:(NSURL*)initialURL {
   if (self = [super init]) {
-    _uuid.reset([[NSUUID alloc] init]);
-    _initialURL.reset(initialURL, base::scoped_policy::RETAIN);
+    _uuid = [[NSUUID alloc] init];
+    _initialURL = initialURL;
   }
   return self;
 }
@@ -107,23 +108,23 @@
 }
 
 - (NSUUID*)UUID {
-  return _uuid.get();
+  return _uuid;
 }
 
 - (void)completeWithCallbackURL:(NSURL*)url {
-  _callbackURL.reset(url, base::scoped_policy::RETAIN);
+  _callbackURL = url;
 }
 
 - (void)cancelWithError:(NSError*)error {
-  _cancellationError.reset(error, base::scoped_policy::RETAIN);
+  _cancellationError = error;
 }
 
 - (NSURL*)callbackURL {
-  return _callbackURL.get();
+  return _callbackURL;
 }
 
 - (NSError*)cancellationError {
-  return _cancellationError.get();
+  return _cancellationError;
 }
 
 @end
@@ -134,9 +135,9 @@
     auto* browser_list = BrowserList::GetInstance();
     size_t start_browser_count = browser_list->size();
 
-    base::scoped_nsobject<MockASWebAuthenticationSessionRequest>
-        session_request([[MockASWebAuthenticationSessionRequest alloc]
-            initWithInitialURL:[NSURL URLWithString:@"about:blank"]]);
+    MockASWebAuthenticationSessionRequest* session_request =
+        [[MockASWebAuthenticationSessionRequest alloc]
+            initWithInitialURL:[NSURL URLWithString:@"about:blank"]];
     id<ASWebAuthenticationSessionWebBrowserSessionHandling> session_handler =
         ASWebAuthenticationSessionWebBrowserSessionManager.sharedManager
             .sessionHandler;
@@ -144,7 +145,7 @@
 
     // Ask the app controller to start handling our session request.
 
-    id request = session_request.get();
+    id request = session_request;
     [session_handler beginHandlingWebAuthenticationSessionRequest:request];
 
     // Expect a browser window to be opened.
@@ -163,12 +164,12 @@
 
     // Expect there to have been the user cancellation callback.
 
-    EXPECT_EQ(nil, session_request.get().callbackURL);
-    ASSERT_NE(nil, session_request.get().cancellationError);
+    EXPECT_EQ(nil, session_request.callbackURL);
+    ASSERT_NE(nil, session_request.cancellationError);
     EXPECT_EQ(ASWebAuthenticationSessionErrorDomain,
-              session_request.get().cancellationError.domain);
+              session_request.cancellationError.domain);
     EXPECT_EQ(ASWebAuthenticationSessionErrorCodeCanceledLogin,
-              session_request.get().cancellationError.code);
+              session_request.cancellationError.code);
   } else {
     GTEST_SKIP() << "ASWebAuthenticationSessionRequest is only available on "
                     "macOS 10.15 and higher.";
@@ -181,9 +182,9 @@
     auto* browser_list = BrowserList::GetInstance();
     size_t start_browser_count = browser_list->size();
 
-    base::scoped_nsobject<MockASWebAuthenticationSessionRequest>
-        session_request([[MockASWebAuthenticationSessionRequest alloc]
-            initWithInitialURL:[NSURL URLWithString:@"about:blank"]]);
+    MockASWebAuthenticationSessionRequest* session_request =
+        [[MockASWebAuthenticationSessionRequest alloc]
+            initWithInitialURL:[NSURL URLWithString:@"about:blank"]];
     id<ASWebAuthenticationSessionWebBrowserSessionHandling> session_handler =
         ASWebAuthenticationSessionWebBrowserSessionManager.sharedManager
             .sessionHandler;
@@ -191,7 +192,7 @@
 
     // Ask the app controller to start handling our session request.
 
-    id request = session_request.get();
+    id request = session_request;
     [session_handler beginHandlingWebAuthenticationSessionRequest:request];
 
     // Expect a browser window to be opened.
@@ -210,12 +211,12 @@
 
     // Expect there to have been the user cancellation callback.
 
-    EXPECT_EQ(nil, session_request.get().callbackURL);
-    ASSERT_NE(nil, session_request.get().cancellationError);
+    EXPECT_EQ(nil, session_request.callbackURL);
+    ASSERT_NE(nil, session_request.cancellationError);
     EXPECT_EQ(ASWebAuthenticationSessionErrorDomain,
-              session_request.get().cancellationError.domain);
+              session_request.cancellationError.domain);
     EXPECT_EQ(ASWebAuthenticationSessionErrorCodeCanceledLogin,
-              session_request.get().cancellationError.code);
+              session_request.cancellationError.code);
   } else {
     GTEST_SKIP() << "ASWebAuthenticationSessionRequest is only available on "
                     "macOS 10.15 and higher.";
@@ -230,10 +231,8 @@
 
     // Clear the last profile. It will be set by default since NSApp in browser
     // tests can activate.
-    AppController* ac = base::mac::ObjCCast<AppController>(
-        [[NSApplication sharedApplication] delegate]);
-    ASSERT_TRUE(ac);
-    [ac setLastProfile:nullptr];
+    AppController* app_controller = AppController.sharedController;
+    [app_controller setLastProfile:nullptr];
 
     // Use a profile that is not loaded yet.
     const std::string kProfileName = "Profile 2";
@@ -245,14 +244,14 @@
         g_browser_process->profile_manager()->GetProfileByPath(kProfilePath));
 
     // Ask the app controller to start handling our session request.
-    base::scoped_nsobject<MockASWebAuthenticationSessionRequest>
-        session_request([[MockASWebAuthenticationSessionRequest alloc]
-            initWithInitialURL:[NSURL URLWithString:@"about:blank"]]);
+    MockASWebAuthenticationSessionRequest* session_request =
+        [[MockASWebAuthenticationSessionRequest alloc]
+            initWithInitialURL:[NSURL URLWithString:@"about:blank"]];
     id<ASWebAuthenticationSessionWebBrowserSessionHandling> session_handler =
         ASWebAuthenticationSessionWebBrowserSessionManager.sharedManager
             .sessionHandler;
     ASSERT_NE(nil, session_handler);
-    id request = session_request.get();
+    id request = session_request;
     [session_handler beginHandlingWebAuthenticationSessionRequest:request];
 
     // Expect the profile to be loaded and browser window to be opened.
@@ -285,14 +284,14 @@
     ASSERT_FALSE(ProfilePicker::IsOpen());
 
     // Ask the app controller to start handling our session request.
-    base::scoped_nsobject<MockASWebAuthenticationSessionRequest>
-        session_request([[MockASWebAuthenticationSessionRequest alloc]
-            initWithInitialURL:[NSURL URLWithString:@"about:blank"]]);
+    MockASWebAuthenticationSessionRequest* session_request =
+        [[MockASWebAuthenticationSessionRequest alloc]
+            initWithInitialURL:[NSURL URLWithString:@"about:blank"]];
     id<ASWebAuthenticationSessionWebBrowserSessionHandling> session_handler =
         ASWebAuthenticationSessionWebBrowserSessionManager.sharedManager
             .sessionHandler;
     ASSERT_NE(nil, session_handler);
-    id request = session_request.get();
+    id request = session_request;
     [session_handler beginHandlingWebAuthenticationSessionRequest:request];
 
     // Expect the profile picker to be opened, no browser was created, and the
@@ -300,12 +299,12 @@
     run_loop.Run();
     EXPECT_TRUE(ProfilePicker::IsOpen());
     EXPECT_EQ(start_browser_count, browser_list->size());
-    EXPECT_EQ(nil, session_request.get().callbackURL);
-    ASSERT_NE(nil, session_request.get().cancellationError);
+    EXPECT_EQ(nil, session_request.callbackURL);
+    ASSERT_NE(nil, session_request.cancellationError);
     EXPECT_EQ(ASWebAuthenticationSessionErrorDomain,
-              session_request.get().cancellationError.domain);
+              session_request.cancellationError.domain);
     EXPECT_EQ(ASWebAuthenticationSessionErrorCodePresentationContextInvalid,
-              session_request.get().cancellationError.code);
+              session_request.cancellationError.code);
 
   } else {
     GTEST_SKIP() << "ASWebAuthenticationSessionRequest is only available on "
@@ -319,9 +318,9 @@
     auto* browser_list = BrowserList::GetInstance();
     size_t start_browser_count = browser_list->size();
 
-    base::scoped_nsobject<MockASWebAuthenticationSessionRequest>
-        session_request([[MockASWebAuthenticationSessionRequest alloc]
-            initWithInitialURL:[NSURL URLWithString:@"about:blank"]]);
+    MockASWebAuthenticationSessionRequest* session_request =
+        [[MockASWebAuthenticationSessionRequest alloc]
+            initWithInitialURL:[NSURL URLWithString:@"about:blank"]];
     id<ASWebAuthenticationSessionWebBrowserSessionHandling> session_handler =
         ASWebAuthenticationSessionWebBrowserSessionManager.sharedManager
             .sessionHandler;
@@ -329,7 +328,7 @@
 
     // Ask the app controller to start handling our session request.
 
-    id request = session_request.get();
+    id request = session_request;
     [session_handler beginHandlingWebAuthenticationSessionRequest:request];
 
     // Expect a browser window to be opened.
@@ -352,10 +351,9 @@
 
     // Expect there to have been the success callback.
 
-    ASSERT_NE(nil, session_request.get().callbackURL);
-    EXPECT_EQ(nil, session_request.get().cancellationError);
-    EXPECT_NSEQ(net::NSURLWithGURL(success_url),
-                session_request.get().callbackURL);
+    ASSERT_NE(nil, session_request.callbackURL);
+    EXPECT_EQ(nil, session_request.cancellationError);
+    EXPECT_NSEQ(net::NSURLWithGURL(success_url), session_request.callbackURL);
   } else {
     GTEST_SKIP() << "ASWebAuthenticationSessionRequest is only available on "
                     "macOS 10.15 and higher.";
@@ -388,9 +386,9 @@
     auto* browser_list = BrowserList::GetInstance();
     size_t start_browser_count = browser_list->size();
 
-    base::scoped_nsobject<MockASWebAuthenticationSessionRequest>
-        session_request([[MockASWebAuthenticationSessionRequest alloc]
-            initWithInitialURL:[NSURL URLWithString:@"about:blank"]]);
+    MockASWebAuthenticationSessionRequest* session_request =
+        [[MockASWebAuthenticationSessionRequest alloc]
+            initWithInitialURL:[NSURL URLWithString:@"about:blank"]];
     id<ASWebAuthenticationSessionWebBrowserSessionHandling> session_handler =
         ASWebAuthenticationSessionWebBrowserSessionManager.sharedManager
             .sessionHandler;
@@ -398,7 +396,7 @@
 
     // Ask the app controller to start handling our session request.
 
-    id request = session_request.get();
+    id request = session_request;
     [session_handler beginHandlingWebAuthenticationSessionRequest:request];
 
     // Expect a browser window to be opened.
@@ -420,10 +418,9 @@
 
     // Expect there to have been the success callback.
 
-    ASSERT_NE(nil, session_request.get().callbackURL);
-    EXPECT_EQ(nil, session_request.get().cancellationError);
-    EXPECT_NSEQ(net::NSURLWithGURL(success_url),
-                session_request.get().callbackURL);
+    ASSERT_NE(nil, session_request.callbackURL);
+    EXPECT_EQ(nil, session_request.cancellationError);
+    EXPECT_NSEQ(net::NSURLWithGURL(success_url), session_request.callbackURL);
   } else {
     GTEST_SKIP() << "ASWebAuthenticationSessionRequest is only available on "
                     "macOS 10.15 and higher.";
@@ -445,9 +442,9 @@
     size_t start_browser_count = browser_list->size();
 
     GURL url = embedded_test_server.GetURL("/something");
-    base::scoped_nsobject<MockASWebAuthenticationSessionRequest>
-        session_request([[MockASWebAuthenticationSessionRequest alloc]
-            initWithInitialURL:net::NSURLWithGURL(url)]);
+    MockASWebAuthenticationSessionRequest* session_request =
+        [[MockASWebAuthenticationSessionRequest alloc]
+            initWithInitialURL:net::NSURLWithGURL(url)];
     id<ASWebAuthenticationSessionWebBrowserSessionHandling> session_handler =
         ASWebAuthenticationSessionWebBrowserSessionManager.sharedManager
             .sessionHandler;
@@ -455,7 +452,7 @@
 
     // Ask the app controller to start handling our session request.
 
-    id request = session_request.get();
+    id request = session_request;
     [session_handler beginHandlingWebAuthenticationSessionRequest:request];
 
     // Expect a browser window to be opened.
@@ -470,10 +467,9 @@
 
     // Expect there to have been the success callback.
 
-    ASSERT_NE(nil, session_request.get().callbackURL);
-    EXPECT_EQ(nil, session_request.get().cancellationError);
-    EXPECT_NSEQ(net::NSURLWithGURL(success_url),
-                session_request.get().callbackURL);
+    ASSERT_NE(nil, session_request.callbackURL);
+    EXPECT_EQ(nil, session_request.cancellationError);
+    EXPECT_NSEQ(net::NSURLWithGURL(success_url), session_request.callbackURL);
   } else {
     GTEST_SKIP() << "ASWebAuthenticationSessionRequest is only available on "
                     "macOS 10.15 and higher.";
diff --git a/chrome/browser/pdf/pdf_extension_printing_test.cc b/chrome/browser/pdf/pdf_extension_printing_test.cc
index 9f39b72..7869a48 100644
--- a/chrome/browser/pdf/pdf_extension_printing_test.cc
+++ b/chrome/browser/pdf/pdf_extension_printing_test.cc
@@ -151,7 +151,8 @@
   printing::BrowserPrintingContextFactoryForTest test_printing_context_factory_;
 };
 
-IN_PROC_BROWSER_TEST_P(PDFExtensionPrintingTest, BasicPrintCommand) {
+// Flaky. See crbug.com/1258561
+IN_PROC_BROWSER_TEST_P(PDFExtensionPrintingTest, DISABLED_BasicPrintCommand) {
   MimeHandlerViewGuest* guest = LoadPdfGetMimeHandlerView(
       embedded_test_server()->GetURL("/pdf/test.pdf"));
   content::RenderFrameHost* frame = GetPluginFrame(guest);
diff --git a/chrome/browser/renderer_host/chrome_render_widget_host_view_mac_history_swiper_browsertest.mm b/chrome/browser/renderer_host/chrome_render_widget_host_view_mac_history_swiper_browsertest.mm
index 2c00fce57..b0a49340 100644
--- a/chrome/browser/renderer_host/chrome_render_widget_host_view_mac_history_swiper_browsertest.mm
+++ b/chrome/browser/renderer_host/chrome_render_widget_host_view_mac_history_swiper_browsertest.mm
@@ -7,7 +7,6 @@
 #include "base/files/file_path.h"
 #include "base/functional/bind.h"
 #include "base/mac/scoped_cftyperef.h"
-#include "base/mac/scoped_nsobject.h"
 #include "base/notreached.h"
 #include "base/run_loop.h"
 #include "base/test/simple_test_tick_clock.h"
@@ -27,6 +26,10 @@
 #include "ui/events/base_event_utils.h"
 #include "url/gurl.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 namespace {
 
 // Refers to how the event is going to be sent to the NSView. There are 3
@@ -51,27 +54,22 @@
 }  // namespace
 
 // A wrapper object for events queued for replay.
-@interface QueuedEvent : NSObject {
-  BOOL _runMessageLoop;
-  Deployment _deployment;
-  NSEvent* _event;
-}
+@interface QueuedEvent : NSObject
+
 // Whether the message loop should be run after this event has been replayed.
 @property(nonatomic, assign) BOOL runMessageLoop;
 // How this event should be replayed.
 @property(nonatomic, assign) Deployment deployment;
 // The event to be replayed.
-@property(nonatomic, retain) NSEvent* event;
+@property(nonatomic, strong) NSEvent* event;
 @end
 
 @implementation QueuedEvent
+
 @synthesize deployment = _deployment;
 @synthesize event = _event;
 @synthesize runMessageLoop = _runMessageLoop;
-- (void)dealloc {
-  [_event release];
-  [super dealloc];
-}
+
 @end
 
 class ChromeRenderWidgetHostViewMacHistorySwiperTest
@@ -94,7 +92,7 @@
       const ChromeRenderWidgetHostViewMacHistorySwiperTest&) = delete;
 
   void SetUpOnMainThread() override {
-    event_queue_.reset([[NSMutableArray alloc] init]);
+    event_queue_ = [[NSMutableArray alloc] init];
     touch_ = CGPointMake(0.5, 0.5);
 
     // Ensure that the navigation stack is not empty.
@@ -109,7 +107,7 @@
 
   void TearDownOnMainThread() override {
     ui::SetEventTickClockForTesting(nullptr);
-    event_queue_.reset();
+    event_queue_ = nil;
   }
 
  protected:
@@ -213,7 +211,7 @@
   // Queue events for playback -------------------------------------------------
 
   void QueueEvent(id event, Deployment deployment, BOOL run_message_loop) {
-    QueuedEvent* queued_event = [[[QueuedEvent alloc] init] autorelease];
+    QueuedEvent* queued_event = [[QueuedEvent alloc] init];
     queued_event.event = event;
     queued_event.deployment = deployment;
     queued_event.runMessageLoop = run_message_loop;
@@ -380,7 +378,7 @@
   GURL url1_;
   GURL url2_;
   GURL url_iframe_;
-  base::scoped_nsobject<NSMutableArray> event_queue_;
+  NSMutableArray* __strong event_queue_;
   // The current location of the user's fingers on the track pad.
   CGPoint touch_;
 };
diff --git a/chrome/browser/resources/chromeos/login/components/oobe_vars/oobe_custom_vars.css b/chrome/browser/resources/chromeos/login/components/oobe_vars/oobe_custom_vars.css
index 11cb025..ffe44cf 100644
--- a/chrome/browser/resources/chromeos/login/components/oobe_vars/oobe_custom_vars.css
+++ b/chrome/browser/resources/chromeos/login/components/oobe_vars/oobe_custom_vars.css
@@ -65,6 +65,14 @@
       --oobe-modal-dialog-content-font-weight: var(--oobe-default-font-weight);
       --oobe-modal-dialog-content-line-height: var(--oobe-default-line-height);
 
+      --oobe-sync-consent-list-item-title-font-size: 14px;
+      --oobe-sync-consent-list-item-title-font-weight: 500;
+      --oobe-sync-consent-list-item-title-line-height: 24px;
+
+      --oobe-sync-consent-card-title-font: var(--cros-headline-1-font);
+      --oobe-sync-consent-card-subtitle-font: var(--cros-body-1-font);
+      --oobe-sync-consent-tooltip-text-font: var(--cros-body-2-font);
+
       /* Select */
       --oobe-select-height: 32px;
 
@@ -83,8 +91,8 @@
       /* Fonts */
       --oobe-default-font-family: var(--cros-body-2-font-family);
       --oobe-default-font-size: var(--cros-body-2-font-size);
-      --oobe-default-line-height: var(--cros-body-2-line-height);
       --oobe-default-font-weight: var(--cros-body-2-font-weight);
+      --oobe-default-line-height: var(--cros-body-2-line-height);
 
       --oobe-header-font-size: var(--cros-display-3_regular-font-size);
       --oobe-header-line-height: var(--cros-display-3_regular-line-height);
@@ -110,6 +118,17 @@
       --oobe-modal-dialog-content-font-size: var(--cros-body-1-font-size);
       --oobe-modal-dialog-content-font-weight: var(--cros-body-1-font-weight);
       --oobe-modal-dialog-content-line-height: var(--cros-body-1-line-height);
+
+      --oobe-sync-consent-list-item-title-font-size:
+          var(--oobe-default-font-size);
+      --oobe-sync-consent-list-item-title-font-weight:
+          var(--oobe-default-font-weight);
+      --oobe-sync-consent-list-item-title-line-height:
+          var(--oobe-default-line-height);
+
+      --oobe-sync-consent-card-title-font: var(--cros-button-1-font);
+      --oobe-sync-consent-card-subtitle-font: var(--cros-annotation-1-font);
+      --oobe-sync-consent-tooltip-text-font: var(--cros-body-2-font);
     }
 
     html.simon-enabled {
diff --git a/chrome/browser/resources/chromeos/login/components/oobe_vars/oobe_custom_vars_remora.css b/chrome/browser/resources/chromeos/login/components/oobe_vars/oobe_custom_vars_remora.css
index ea26fcc..58beee4 100644
--- a/chrome/browser/resources/chromeos/login/components/oobe_vars/oobe_custom_vars_remora.css
+++ b/chrome/browser/resources/chromeos/login/components/oobe_vars/oobe_custom_vars_remora.css
@@ -62,6 +62,14 @@
       --oobe-modal-dialog-content-font-weight: var(--oobe-default-font-weight);
       --oobe-modal-dialog-content-line-height: var(--oobe-default-line-height);
 
+      --oobe-sync-consent-list-item-title-font-size: 14px;
+      --oobe-sync-consent-list-item-title-font-weight: 500;
+      --oobe-sync-consent-list-item-title-line-height: 24px;
+
+      --oobe-sync-consent-card-title-font: var(--cros-headline-1-font);
+      --oobe-sync-consent-card-subtitle-font: var(--cros-body-1-font);
+      --oobe-sync-consent-tooltip-text-font: var(--cros-body-2-font);
+
       /* Select */
       --oobe-select-height: 44px;
 
diff --git a/chrome/browser/resources/chromeos/login/screens/common/sync_consent.html b/chrome/browser/resources/chromeos/login/screens/common/sync_consent.html
index 0f30b7b..062e08c 100644
--- a/chrome/browser/resources/chromeos/login/screens/common/sync_consent.html
+++ b/chrome/browser/resources/chromeos/login/screens/common/sync_consent.html
@@ -21,9 +21,9 @@
   }
 
   .overview-list-item-title {
-    font-size: 14px;
-    font-weight: 500; /* roboto-medium */
-    line-height: 24px;
+    font-size: var(--oobe-sync-consent-list-item-title-font-size);
+    font-weight: var(--oobe-sync-consent-list-item-title-font-weight);
+    line-height: var(--oobe-sync-consent-list-item-title-line-height);
     margin-bottom: 4px;
   }
 
@@ -92,16 +92,16 @@
 
   .card-title {
     color: var(--cros-text-color-primary);
-    font: var(--cros-headline-1-font);
+    font: var(--oobe-sync-consent-card-title-font);
   }
 
   .card-subtitle {
     color: var(--cros-text-color-secondary);
-    font: var(--cros-body-1-font);
+    font: var(--oobe-sync-consent-card-subtitle-font);
   }
 
   .tooltip-text {
-    font: var(--cros-body-2-font);
+    font: var(--oobe-sync-consent-tooltip-text-font);
   }
 
   #tooltip-element {
@@ -359,4 +359,4 @@
       on-click="onNextClicked_">
     </oobe-next-button>
   </div>
-</oobe-adaptive-dialog>
\ No newline at end of file
+</oobe-adaptive-dialog>
diff --git a/chrome/browser/resources/new_tab_page/realbox/realbox.html b/chrome/browser/resources/new_tab_page/realbox/realbox.html
index 987694c..734cea8 100644
--- a/chrome/browser/resources/new_tab_page/realbox/realbox.html
+++ b/chrome/browser/resources/new_tab_page/realbox/realbox.html
@@ -214,9 +214,10 @@
       aria-live="[[inputAriaLive_]]" role="combobox"
       aria-expanded="[[dropdownIsVisible]]" aria-controls="matches"
       placeholder="$i18n{searchBoxHint}" on-copy="onInputCutCopy_"
-      on-cut="onInputCutCopy_" on-focus="onInputFocus_" on-input="onInputInput_"
-      on-keydown="onInputKeydown_" on-keyup="onInputKeyup_"
-      on-mousedown="onInputMouseDown_" on-paste="onInputPaste_">
+      on-cut="onInputCutCopy_" on-focus="onInputFocus_"
+      on-input="onInputInput_" on-keydown="onInputKeydown_"
+      on-keyup="onInputKeyup_" on-mousedown="onInputMouseDown_"
+      on-paste="onInputPaste_">
   <cr-realbox-icon id="icon" match="[[selectedMatch_]]"
       default-icon="[[realboxIcon_]]" in-searchbox>
   </cr-realbox-icon>
diff --git a/chrome/browser/resources/new_tab_page/realbox/realbox.ts b/chrome/browser/resources/new_tab_page/realbox/realbox.ts
index e360346..c2cf45ed 100644
--- a/chrome/browser/resources/new_tab_page/realbox/realbox.ts
+++ b/chrome/browser/resources/new_tab_page/realbox/realbox.ts
@@ -15,7 +15,7 @@
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {loadTimeData} from '../i18n_setup.js';
-import {decodeString16, mojoString16, mojoTimeDelta} from '../utils.js';
+import {decodeString16, mojoString16} from '../utils.js';
 
 import {getTemplate} from './realbox.html.js';
 
@@ -151,15 +151,6 @@
         value: {text: '', inline: ''},
       },
 
-      /**
-       * The time at which the input was last focused in milliseconds. Passed to
-       * the browser when navigating to a match.
-       */
-      lastInputFocusTime_: {
-        type: Number,
-        value: null,
-      },
-
       /** The last queried input text. */
       lastQueriedInput_: {
         type: String,
@@ -235,7 +226,6 @@
   private isDeletingInput_: boolean;
   private lastIgnoredEnterEvent_: KeyboardEvent|null;
   private lastInput_: Input;
-  private lastInputFocusTime_: number|null;
   private lastQueriedInput_: string|null;
   private pastedInInput_: boolean;
   private realboxIcon_: string;
@@ -373,7 +363,7 @@
   }
 
   private onInputFocus_() {
-    this.lastInputFocusTime_ = window.performance.now();
+    this.pageHandler_.onFocusChanged(true);
   }
 
   private onInputInput_(e: InputEvent) {
@@ -496,6 +486,7 @@
         // focusing and pressing 'Enter'.
         this.pageHandler_.stopAutocomplete(/*clearResult=*/ false);
       }
+      this.pageHandler_.onFocusChanged(false);
     }
   }
 
@@ -692,11 +683,8 @@
     assert(matchIndex >= 0);
     const match = this.result_!.matches[matchIndex];
     assert(match);
-    assert(this.lastInputFocusTime_);
-    const delta =
-        mojoTimeDelta(window.performance.now() - this.lastInputFocusTime_);
     this.pageHandler_.openAutocompleteMatch(
-        matchIndex, match.destinationUrl, this.dropdownIsVisible, delta,
+        matchIndex, match.destinationUrl, this.dropdownIsVisible,
         (e as MouseEvent).button || 0, e.altKey, e.ctrlKey, e.metaKey,
         e.shiftKey);
     e.preventDefault();
diff --git a/chrome/browser/resources/password_manager/BUILD.gn b/chrome/browser/resources/password_manager/BUILD.gn
index 56ad436..8e9e3ea 100644
--- a/chrome/browser/resources/password_manager/BUILD.gn
+++ b/chrome/browser/resources/password_manager/BUILD.gn
@@ -41,6 +41,7 @@
     "dialogs/add_password_dialog.ts",
     "dialogs/auth_timed_out_dialog.ts",
     "dialogs/delete_password_disclaimer_dialog.ts",
+    "dialogs/edit_passkey_dialog.ts",
     "dialogs/edit_password_dialog.ts",
     "dialogs/edit_password_disclaimer_dialog.ts",
     "dialogs/multi_store_delete_password_dialog.ts",
diff --git a/chrome/browser/resources/password_manager/credential_details/passkey_details_card.html b/chrome/browser/resources/password_manager/credential_details/passkey_details_card.html
index af71685..0408e603 100644
--- a/chrome/browser/resources/password_manager/credential_details/passkey_details_card.html
+++ b/chrome/browser/resources/password_manager/credential_details/passkey_details_card.html
@@ -50,10 +50,15 @@
   </div>
   <div class="button-container">
     <cr-button id="editButton" class="edit-button" on-click="onEditClicked_">
-      $i18n{editPassword}
+      $i18n{edit}
     </cr-button>
     <cr-button id="deleteButton" on-click="onDeleteClick_">
-      $i18n{deletePassword}
+      $i18n{delete}
     </cr-button>
   </div>
 </div>
+<template is="dom-if" if="[[showEditPasskeyDialog_]]" restamp>
+  <edit-passkey-dialog on-close="onEditPasskeyDialogClosed_"
+      id="editPasskeyDialog" passkey="{{passkey}}">
+  </edit-passkey-dialog>
+</template>
diff --git a/chrome/browser/resources/password_manager/credential_details/passkey_details_card.ts b/chrome/browser/resources/password_manager/credential_details/passkey_details_card.ts
index 628be95..d59b56b 100644
--- a/chrome/browser/resources/password_manager/credential_details/passkey_details_card.ts
+++ b/chrome/browser/resources/password_manager/credential_details/passkey_details_card.ts
@@ -9,6 +9,7 @@
 import 'chrome://resources/cr_elements/cr_shared_style.css.js';
 import '../shared_style.css.js';
 import './credential_details_card.css.js';
+import '../dialogs/edit_passkey_dialog.js';
 
 import {CrButtonElement} from 'chrome://resources/cr_elements/cr_button/cr_button.js';
 import {I18nMixin} from 'chrome://resources/cr_elements/i18n_mixin.js';
@@ -50,10 +51,12 @@
         type: Object,
         value: PasswordViewPageInteractions,
       },
+      showEditPasskeyDialog_: Boolean,
     };
   }
 
   passkey: chrome.passwordsPrivate.PasswordUiEntry;
+  private showEditPasskeyDialog_: boolean;
 
   private getPasskeyUsageInfoString_(): string {
     const website = this.passkey.affiliatedDomains?.[0]?.name;
@@ -83,6 +86,7 @@
 
   private onDeleteClick_() {
     // TODO(crbug.com/1432717): show a modal dialog instead.
+    PasswordManagerImpl.getInstance().extendAuthValidity();
     PasswordManagerImpl.getInstance().recordPasswordViewInteraction(
         PasswordViewPageInteractions.PASSKEY_DELETE_BUTTON_CLICKED);
     PasswordManagerImpl.getInstance().removeCredential(
@@ -90,7 +94,15 @@
   }
 
   private onEditClicked_() {
-    // TODO(crbug.com/1432717): fill this in.
+    this.showEditPasskeyDialog_ = true;
+    PasswordManagerImpl.getInstance().extendAuthValidity();
+    PasswordManagerImpl.getInstance().recordPasswordViewInteraction(
+        PasswordViewPageInteractions.PASSKEY_EDIT_BUTTON_CLICKED);
+  }
+
+  private onEditPasskeyDialogClosed_() {
+    this.showEditPasskeyDialog_ = false;
+    PasswordManagerImpl.getInstance().extendAuthValidity();
   }
 }
 
diff --git a/chrome/browser/resources/password_manager/dialogs/edit_passkey_dialog.html b/chrome/browser/resources/password_manager/dialogs/edit_passkey_dialog.html
new file mode 100644
index 0000000..9736a3c
--- /dev/null
+++ b/chrome/browser/resources/password_manager/dialogs/edit_passkey_dialog.html
@@ -0,0 +1,45 @@
+<style include="shared-style cr-shared-style">
+  cr-input:not(:first-of-type) {
+    margin-top: var(--cr-form-field-bottom-spacing);
+  }
+
+  cr-input {
+    --cr-input-error-display: none;
+  }
+
+  #usernameInput[invalid] {
+    --cr-input-error-display: block;
+  }
+
+  #displayNameInput {
+    margin-top: var(--cr-form-field-bottom-spacing);
+  }
+</style>
+<cr-dialog id="dialog" show-on-attach>
+  <h1 slot="title" id="title" class="dialog-title">$i18n{editPasskeyTitle}</h1>
+  <div slot="body">
+    <div class="cr-form-field-label">$i18n{sitesLabel}</div>
+    <template id="links" is="dom-repeat"
+        items="[[passkey.affiliatedDomains]]">
+      <div class="elide-left">
+        <a href="[[item.url]]" class="site-link" target="_blank">
+          [[item.name]]
+        </a>
+      </div>
+    </template>
+    <cr-input id="displayNameInput" label="$i18n{displayNameLabel}" autofocus
+        value="{{displayName_}}" placeholder="$i18n{displayNamePlaceholder}">
+    </cr-input>
+    <cr-input id="usernameInput" label="$i18n{usernameLabel}"
+        value="{{username_}}" placeholder="$i18n{usernamePlaceholder}">
+    </cr-input>
+  </div>
+  <div slot="button-container">
+    <cr-button id="cancelButton" class="cancel-button" on-click="onCancel_">
+      $i18n{cancel}
+    </cr-button>
+    <cr-button id="saveButton" class="action-button" on-click="onEditClick_">
+      $i18n{save}
+    </cr-button>
+  </div>
+</cr-dialog>
diff --git a/chrome/browser/resources/password_manager/dialogs/edit_passkey_dialog.ts b/chrome/browser/resources/password_manager/dialogs/edit_passkey_dialog.ts
new file mode 100644
index 0000000..295f1241
--- /dev/null
+++ b/chrome/browser/resources/password_manager/dialogs/edit_passkey_dialog.ts
@@ -0,0 +1,84 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+import 'chrome://resources/cr_elements/cr_button/cr_button.js';
+import 'chrome://resources/cr_elements/cr_dialog/cr_dialog.js';
+import 'chrome://resources/cr_elements/cr_input/cr_input.js';
+import 'chrome://resources/cr_elements/cr_icons.css.js';
+import 'chrome://resources/cr_elements/cr_shared_style.css.js';
+import '../shared_style.css.js';
+
+import {CrButtonElement} from 'chrome://resources/cr_elements/cr_button/cr_button.js';
+import {CrDialogElement} from 'chrome://resources/cr_elements/cr_dialog/cr_dialog.js';
+import {CrInputElement} from 'chrome://resources/cr_elements/cr_input/cr_input.js';
+import {I18nMixin} from 'chrome://resources/cr_elements/i18n_mixin.js';
+import {assert} from 'chrome://resources/js/assert_ts.js';
+import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {PasswordManagerImpl} from '../password_manager_proxy.js';
+
+import {getTemplate} from './edit_passkey_dialog.html.js';
+
+export interface EditPasskeyDialogElement {
+  $: {
+    cancelButton: CrButtonElement,
+    dialog: CrDialogElement,
+    saveButton: CrButtonElement,
+    usernameInput: CrInputElement,
+    displayNameInput: CrInputElement,
+  };
+}
+
+const EditPasskeyDialogElementBase = I18nMixin(PolymerElement);
+
+export class EditPasskeyDialogElement extends EditPasskeyDialogElementBase {
+  static get is() {
+    return 'edit-passkey-dialog';
+  }
+
+  static get template() {
+    return getTemplate();
+  }
+
+  static get properties() {
+    return {
+      passkey: Object,
+      username_: String,
+      displayName_: String,
+    };
+  }
+
+  passkey: chrome.passwordsPrivate.PasswordUiEntry;
+  private username_: string;
+  private displayName_: string;
+
+  override ready() {
+    super.ready();
+    assert(this.passkey.isPasskey);
+
+    this.username_ = this.passkey.username;
+    this.displayName_ = this.passkey.displayName || '';
+  }
+
+  private onCancel_() {
+    this.$.dialog.close();
+  }
+
+  private onEditClick_() {
+    this.passkey.username = this.username_;
+    this.passkey.displayName = this.displayName_;
+    PasswordManagerImpl.getInstance()
+        .changeCredential(this.passkey)
+        .finally(() => {
+          this.$.dialog.close();
+        });
+  }
+}
+
+declare global {
+  interface HTMLElementTagNameMap {
+    'edit-passkey-dialog': EditPasskeyDialogElement;
+  }
+}
+
+customElements.define(EditPasskeyDialogElement.is, EditPasskeyDialogElement);
diff --git a/chrome/browser/resources/password_manager/password_manager.ts b/chrome/browser/resources/password_manager/password_manager.ts
index da584004..7aaeb478 100644
--- a/chrome/browser/resources/password_manager/password_manager.ts
+++ b/chrome/browser/resources/password_manager/password_manager.ts
@@ -18,6 +18,7 @@
 export {PasswordDetailsCardElement} from './credential_details/password_details_card.js';
 export {AddPasswordDialogElement} from './dialogs/add_password_dialog.js';
 export {AuthTimedOutDialogElement} from './dialogs/auth_timed_out_dialog.js';
+export {EditPasskeyDialogElement} from './dialogs/edit_passkey_dialog.js';
 export {EditPasswordDialogElement} from './dialogs/edit_password_dialog.js';
 // <if expr="is_win or is_macosx">
 export {PasskeysBrowserProxy, PasskeysBrowserProxyImpl} from './passkeys_browser_proxy.js';
diff --git a/chrome/browser/resources/password_manager/password_manager_proxy.ts b/chrome/browser/resources/password_manager/password_manager_proxy.ts
index de729b13..0f5f8683 100644
--- a/chrome/browser/resources/password_manager/password_manager_proxy.ts
+++ b/chrome/browser/resources/password_manager/password_manager_proxy.ts
@@ -66,8 +66,9 @@
   CREDENTIAL_REQUESTED_BY_URL = 11,
   PASSKEY_DISPLAY_NAME_COPY_BUTTON_CLICKED = 12,
   PASSKEY_DELETE_BUTTON_CLICKED = 13,
+  PASSKEY_EDIT_BUTTON_CLICKED = 14,
   // Must be last.
-  COUNT = 14,
+  COUNT = 15,
 }
 
 /**
diff --git a/chrome/browser/resources/settings/BUILD.gn b/chrome/browser/resources/settings/BUILD.gn
index 6a9689b..d86fe84 100644
--- a/chrome/browser/resources/settings/BUILD.gn
+++ b/chrome/browser/resources/settings/BUILD.gn
@@ -115,6 +115,7 @@
     "performance_page/performance_page.ts",
     "performance_page/tab_discard_exception_add_dialog.ts",
     "performance_page/tab_discard_exception_add_input.ts",
+    "performance_page/tab_discard_exception_current_sites_entry.ts",
     "performance_page/tab_discard_exception_current_sites_list.ts",
     "performance_page/tab_discard_exception_edit_dialog.ts",
     "performance_page/tab_discard_exception_edit_input.ts",
diff --git a/chrome/browser/resources/settings/chromeos/os_settings.ts b/chrome/browser/resources/settings/chromeos/os_settings.ts
index fb9fb726..18eb282 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings.ts
+++ b/chrome/browser/resources/settings/chromeos/os_settings.ts
@@ -116,6 +116,7 @@
 export {OsSettingsCellularSetupDialogElement} from './internet_page/cellular_setup_dialog.js';
 export {HotspotConfigDialogElement, WiFiSecurityType} from './internet_page/hotspot_config_dialog.js';
 export {HotspotSummaryItemElement} from './internet_page/hotspot_summary_item.js';
+export {InternetConfigElement} from './internet_page/internet_config.js';
 export {InternetPageBrowserProxy, InternetPageBrowserProxyImpl} from './internet_page/internet_page_browser_proxy.js';
 export {NetworkSummaryElement} from './internet_page/network_summary.js';
 export {NetworkSummaryItemElement} from './internet_page/network_summary_item.js';
diff --git a/chrome/browser/resources/settings/performance_page/tab_discard_exception_current_sites_entry.html b/chrome/browser/resources/settings/performance_page/tab_discard_exception_current_sites_entry.html
new file mode 100644
index 0000000..5edf221
--- /dev/null
+++ b/chrome/browser/resources/settings/performance_page/tab_discard_exception_current_sites_entry.html
@@ -0,0 +1,27 @@
+<style include="settings-shared">
+  /* add padding so that checkbox ripple is not cut off */
+  .ripple-padding {
+    padding-inline-end: 20px;
+    padding-inline-start: 20px;
+  }
+
+  cr-checkbox::part(label-container) {
+    min-width: 0;
+  }
+
+  .label-slot {
+    align-items: center;
+    display: flex;
+  }
+
+  .checkbox-label {
+    margin-inline-start: 10px;
+  }
+</style>
+<cr-checkbox id="checkbox" class="list-item no-outline ripple-padding"
+    tab-index="-1">
+  <div class="label-slot">
+    <site-favicon url="[[item]]"></site-favicon>
+    <div class="checkbox-label text-elide">[[item]]</div>
+  </div>
+</cr-checkbox>
diff --git a/chrome/browser/resources/settings/performance_page/tab_discard_exception_current_sites_entry.ts b/chrome/browser/resources/settings/performance_page/tab_discard_exception_current_sites_entry.ts
new file mode 100644
index 0000000..db4fb0f
--- /dev/null
+++ b/chrome/browser/resources/settings/performance_page/tab_discard_exception_current_sites_entry.ts
@@ -0,0 +1,87 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
+import '../settings_shared.css.js';
+import '../site_favicon.js';
+
+import {CrCheckboxElement} from 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
+import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {getTemplate} from './tab_discard_exception_current_sites_entry.html.js';
+
+export interface TabDiscardExceptionCurrentSitesEntryElement {
+  $: {
+    checkbox: CrCheckboxElement,
+  };
+}
+
+export class TabDiscardExceptionCurrentSitesEntryElement extends
+    PolymerElement {
+  static get is() {
+    return 'tab-discard-exception-current-sites-entry';
+  }
+
+  static get template() {
+    return getTemplate();
+  }
+
+  static get properties() {
+    return {
+      item: String,
+    };
+  }
+
+  private item: string;
+
+  override connectedCallback() {
+    super.connectedCallback();
+    this.addEventListener('keydown', this.onKeyDown_);
+    this.addEventListener('keyup', this.onKeyUp_);
+  }
+
+  override disconnectedCallback(): void {
+    super.disconnectedCallback();
+    this.removeEventListener('keydown', this.onKeyDown_);
+    this.removeEventListener('keyup', this.onKeyUp_);
+  }
+
+  private onKeyDown_(e: KeyboardEvent) {
+    if (e.key !== ' ' && e.key !== 'Enter') {
+      return;
+    }
+
+    e.preventDefault();
+    e.stopPropagation();
+    if (e.repeat) {
+      return;
+    }
+
+    if (e.key === 'Enter') {
+      this.$.checkbox.click();
+    }
+  }
+
+  private onKeyUp_(e: KeyboardEvent) {
+    if (e.key === ' ' || e.key === 'Enter') {
+      e.preventDefault();
+      e.stopPropagation();
+    }
+
+    if (e.key === ' ') {
+      this.$.checkbox.click();
+    }
+  }
+}
+
+declare global {
+  interface HTMLElementTagNameMap {
+    'tab-discard-exception-current-sites-entry':
+        TabDiscardExceptionCurrentSitesEntryElement;
+  }
+}
+
+customElements.define(
+    TabDiscardExceptionCurrentSitesEntryElement.is,
+    TabDiscardExceptionCurrentSitesEntryElement);
diff --git a/chrome/browser/resources/settings/performance_page/tab_discard_exception_current_sites_list.html b/chrome/browser/resources/settings/performance_page/tab_discard_exception_current_sites_list.html
index e7398b8..1f41e55 100644
--- a/chrome/browser/resources/settings/performance_page/tab_discard_exception_current_sites_list.html
+++ b/chrome/browser/resources/settings/performance_page/tab_discard_exception_current_sites_list.html
@@ -1,39 +1,14 @@
-<style include="settings-shared">
-  /* add padding so that checkbox ripple is not cut off */
-  .ripple-padding {
-    padding-inline-end: 20px;
-    padding-inline-start: 20px;
-  }
-
-  cr-checkbox::part(label-container) {
-    min-width: 0;
-  }
-
-  .label-slot {
-    align-items: center;
-    display: flex;
-  }
-
-  .checkbox-label {
-    margin-inline-start: 10px;
-  }
-</style>
 <div id="container" scrollable>
-  <iron-list id="list" scroll-target="container" role="grid"
+  <iron-list id="list" scroll-target="container" role="listbox"
       items="[[currentSites_]]" selected-items="{{selectedSites_}}"
-      selection-enabled multi-selection
-      aria-rowcount$="[[currentSites_.length]]"
+      selection-enabled multi-selection aria-multiselectable="true"
       hidden$="[[!currentSites_.length]]">
     <template>
-      <cr-checkbox class="list-item no-outline ripple-padding" role="row"
-          tab-index="[[tabIndex]]"
-          aria-rowindex$="[[getAriaRowindex_(index)]]"
-          on-change="onToggleSelection_" checked="{{selected}}">
-        <div class="label-slot">
-          <site-favicon url="[[item]]"></site-favicon>
-          <div class="checkbox-label text-elide">[[item]]</div>
-        </div>
-      </cr-checkbox>
+      <tab-discard-exception-current-sites-entry item="[[item]]" role="option"
+          aria-description="$i18n{tabDiscardingExceptionsActiveSiteAriaDescription}"
+          aria-selected="[[selected]]" tab-index="[[tabIndex]]"
+          on-change="onToggleSelection_">
+      </tab-discard-exception-current-sites-entry>
     </template>
   </iron-list>
 </div>
diff --git a/chrome/browser/resources/settings/performance_page/tab_discard_exception_current_sites_list.ts b/chrome/browser/resources/settings/performance_page/tab_discard_exception_current_sites_list.ts
index bde24b04..1b88db5 100644
--- a/chrome/browser/resources/settings/performance_page/tab_discard_exception_current_sites_list.ts
+++ b/chrome/browser/resources/settings/performance_page/tab_discard_exception_current_sites_list.ts
@@ -2,10 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 import 'chrome://resources/polymer/v3_0/iron-list/iron-list.js';
-import '../settings_shared.css.js';
 import '../site_favicon.js';
+import './tab_discard_exception_current_sites_entry.js';
 
 import {PrefsMixin, PrefsMixinInterface} from 'chrome://resources/cr_components/settings_prefs/prefs_mixin.js';
 import {CrScrollableMixin, CrScrollableMixinInterface} from 'chrome://resources/cr_elements/cr_scrollable_mixin.js';
diff --git a/chrome/browser/resources/settings/settings.ts b/chrome/browser/resources/settings/settings.ts
index ce29834..25f9ab70 100644
--- a/chrome/browser/resources/settings/settings.ts
+++ b/chrome/browser/resources/settings/settings.ts
@@ -70,6 +70,7 @@
 export {BatterySaverModeState, HighEfficiencyModeExceptionListAction, HighEfficiencyModeState, PerformanceMetricsProxy, PerformanceMetricsProxyImpl} from './performance_page/performance_metrics_proxy.js';
 export {HIGH_EFFICIENCY_MODE_PREF, SettingsPerformancePageElement} from './performance_page/performance_page.js';
 export {TabDiscardExceptionAddDialogElement} from './performance_page/tab_discard_exception_add_dialog.js';
+export {TabDiscardExceptionCurrentSitesEntryElement} from './performance_page/tab_discard_exception_current_sites_entry.js';
 export {TabDiscardExceptionEditDialogElement} from './performance_page/tab_discard_exception_edit_dialog.js';
 export {TabDiscardExceptionEntryElement} from './performance_page/tab_discard_exception_entry.js';
 export {TAB_DISCARD_EXCEPTIONS_OVERFLOW_SIZE, TabDiscardExceptionListElement} from './performance_page/tab_discard_exception_list.js';
diff --git a/chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service_factory.cc b/chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service_factory.cc
index 1fec03e..8b600f5 100644
--- a/chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service_factory.cc
+++ b/chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service_factory.cc
@@ -56,6 +56,7 @@
   DependsOn(VerdictCacheManagerFactory::GetInstance());
   DependsOn(enterprise_connectors::ConnectorsServiceFactory::GetInstance());
   DependsOn(SafeBrowsingNavigationObserverManagerFactory::GetInstance());
+  DependsOn(IdentityManagerFactory::GetInstance());
 }
 
 KeyedService*
diff --git a/chrome/browser/ssl/https_only_mode_browsertest.cc b/chrome/browser/ssl/https_only_mode_browsertest.cc
index 01d92c2a..4c636eb 100644
--- a/chrome/browser/ssl/https_only_mode_browsertest.cc
+++ b/chrome/browser/ssl/https_only_mode_browsertest.cc
@@ -28,6 +28,7 @@
 #include "components/variations/active_field_trials.h"
 #include "components/variations/hashing.h"
 #include "content/public/browser/storage_partition.h"
+#include "content/public/common/content_features.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/browser_test_utils.h"
 #include "content/public/test/content_browser_test_utils.h"
@@ -1180,3 +1181,81 @@
             contents));
   }
 }
+
+class HttpsOnlyModeTestSubresourceNotifications
+    : public HttpsOnlyModeBrowserTest {
+ public:
+  HttpsOnlyModeTestSubresourceNotifications() = default;
+  ~HttpsOnlyModeTestSubresourceNotifications() override = default;
+
+  void SetUp() override {
+    feature_list_.InitWithFeatures(
+        /*enabled_features=*/{features::kHttpsOnlyMode,
+                              features::kReduceSubresourceResponseStartedIPC},
+        /*disabled_features=*/{features::kHttpsFirstModeV2,
+                               features::kHttpsUpgrades});
+    InProcessBrowserTest::SetUp();
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+// Tests that if the user bypasses the HTTPS-First Mode interstitial, and then
+// later the server fixes their HTTPS support and the user successfully connects
+// over HTTPS, the allowlist entry is cleared. Once
+// `renderer_preferences_.send_subresource_notification_` is set, we keep on
+// sending subresource notification until we have cleared all exceptions and the
+// browser is restarted.
+IN_PROC_BROWSER_TEST_F(HttpsOnlyModeTestSubresourceNotifications,
+                       PRE_BadHttpsFollowedByGoodHttpsFollowedByRestart) {
+  GURL http_url = http_server()->GetURL("foo.test", "/close-socket");
+  GURL bad_https_url = https_server()->GetURL("foo.test", "/close-socket");
+  GURL good_https_url = https_server()->GetURL("foo.test", "/ssl/google.html");
+
+  ASSERT_EQ(http_url.host(), bad_https_url.host());
+  ASSERT_EQ(bad_https_url.host(), good_https_url.host());
+
+  auto* tab = browser()->tab_strip_model()->GetActiveWebContents();
+  auto* profile = Profile::FromBrowserContext(tab->GetBrowserContext());
+  auto* state = static_cast<StatefulSSLHostStateDelegate*>(
+      profile->GetSSLHostStateDelegate());
+
+  ASSERT_FALSE(tab->GetSendSubresourceNotification());
+
+  // Main frame requests revoke the decision.
+
+  // Navigate to `http_url`, which will get upgraded to `bad_https_url`.
+  EXPECT_FALSE(content::NavigateToURL(tab, http_url));
+
+  ASSERT_TRUE(
+      chrome_browser_interstitials::IsShowingHttpsFirstModeInterstitial(tab));
+  ProceedThroughInterstitial(tab);
+
+  EXPECT_TRUE(state->HasAllowExceptionForAnyHost(
+      tab->GetPrimaryMainFrame()->GetStoragePartition()));
+  EXPECT_TRUE(tab->GetSendSubresourceNotification());
+
+  EXPECT_TRUE(content::NavigateToURL(tab, good_https_url));
+  EXPECT_FALSE(state->HasAllowExceptionForAnyHost(
+      tab->GetPrimaryMainFrame()->GetStoragePartition()));
+  // Revoking decisions does not change `send_subresource_notifications_` in the
+  // same browsing session. This is because allowing and revoking exceptions
+  // happens rarely. So we continue sending subresource notifications to the
+  // browser.
+  EXPECT_TRUE(tab->GetSendSubresourceNotification());
+}
+
+IN_PROC_BROWSER_TEST_F(HttpsOnlyModeTestSubresourceNotifications,
+                       BadHttpsFollowedByGoodHttpsFollowedByRestart) {
+  // Verifies `renderer_preferences_.send_subresource_notification_` is updated
+  // w.r.t. the state of allowed exceptions when the browser restarts.
+  auto* tab = browser()->tab_strip_model()->GetActiveWebContents();
+  auto* profile = Profile::FromBrowserContext(tab->GetBrowserContext());
+  auto* state = static_cast<StatefulSSLHostStateDelegate*>(
+      profile->GetSSLHostStateDelegate());
+
+  EXPECT_FALSE(state->HasAllowExceptionForAnyHost(
+      tab->GetPrimaryMainFrame()->GetStoragePartition()));
+  EXPECT_FALSE(tab->GetSendSubresourceNotification());
+}
diff --git a/chrome/browser/ssl/https_only_mode_controller_client.cc b/chrome/browser/ssl/https_only_mode_controller_client.cc
index 4da7753..5d709001 100644
--- a/chrome/browser/ssl/https_only_mode_controller_client.cc
+++ b/chrome/browser/ssl/https_only_mode_controller_client.cc
@@ -52,6 +52,10 @@
           profile->GetSSLHostStateDelegate());
   // StatefulSSLHostStateDelegate can be null during tests.
   if (state) {
+    // Notifies the browser process when a HTTP exception is allowed in
+    // HTTPS-First Mode.
+    web_contents_->SetAlwaysSendSubresourceNotifications();
+
     state->AllowHttpForHost(
         request_url_.host(),
         web_contents_->GetPrimaryMainFrame()->GetStoragePartition());
diff --git a/chrome/browser/ssl/ssl_browsertest.cc b/chrome/browser/ssl/ssl_browsertest.cc
index 77d779e8..455adfc 100644
--- a/chrome/browser/ssl/ssl_browsertest.cc
+++ b/chrome/browser/ssl/ssl_browsertest.cc
@@ -143,6 +143,7 @@
 #include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "content/public/common/content_client.h"
+#include "content/public/common/content_features.h"
 #include "content/public/common/content_switches.h"
 #include "content/public/common/url_constants.h"
 #include "content/public/test/browser_test.h"
@@ -1027,6 +1028,20 @@
     ssl_test_util::SetHSTSForHostName(browser()->profile(), kHstsTestHostName);
   }
 };
+class SSLUITestReduceSubresourceNotifications : public SSLUITestBase {
+ public:
+  SSLUITestReduceSubresourceNotifications() {
+    scoped_feature_list_.InitWithFeatures(
+        /* enabled_features */ {features::kReduceSubresourceResponseStartedIPC},
+        /* disabled_features */ {blink::features::kMixedContentAutoupgrade});
+  }
+
+  SSLUITestReduceSubresourceNotifications(const SSLUITest&) = delete;
+  SSLUITestReduceSubresourceNotifications& operator=(const SSLUITest&) = delete;
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
 
 // Visits a regular page over http.
 IN_PROC_BROWSER_TEST_F(SSLUITest, TestHTTP) {
@@ -4169,6 +4184,222 @@
   EXPECT_TRUE(tab->GetRenderWidgetHostView()->IsShowing());
 }
 
+// Verifies that if a bad certificate is seen for any host and the user proceeds
+// through the interstitial, the decision to proceed is initially remembered.
+// However, if this is followed by another visit, and a good certificate is seen
+// for the same host, the original exception is forgotten.
+IN_PROC_BROWSER_TEST_F(SSLUITestReduceSubresourceNotifications,
+                       HasAllowExceptionForAnyHost) {
+  ASSERT_TRUE(https_server_expired_.Start());
+  ASSERT_TRUE(https_server_.Start());
+
+  std::string https_server_expired_host =
+      https_server_expired_.GetURL("/ssl/google.html").host();
+  std::string https_server_host =
+      https_server_.GetURL("/ssl/google.html").host();
+  ASSERT_EQ(https_server_expired_host, https_server_host);
+
+  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
+
+  Profile* profile = Profile::FromBrowserContext(tab->GetBrowserContext());
+  StatefulSSLHostStateDelegate* state =
+      static_cast<StatefulSSLHostStateDelegate*>(
+          profile->GetSSLHostStateDelegate());
+
+  // First check that frame requests revoke the decision.
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(
+      browser(), https_server_expired_.GetURL("/ssl/google.html")));
+
+  ProceedThroughInterstitial(tab);
+  EXPECT_TRUE(state->HasAllowExceptionForAnyHost(
+      tab->GetPrimaryMainFrame()->GetStoragePartition()));
+
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(
+      browser(), https_server_.GetURL("/ssl/google.html")));
+  EXPECT_FALSE(state->HasAllowExceptionForAnyHost(
+      tab->GetPrimaryMainFrame()->GetStoragePartition()));
+}
+
+// Verifies that if a bad certificate is seen for any host and the user proceeds
+// through the interstitial, the decision to proceed is initially remembered.
+// However, if this is followed by another visit, and a good certificate is seen
+// for the same host, the original exception is forgotten. The state of
+// send_subresource_notification does not change in the Webcontents even after a
+// good certificate has been seen until the browser process is restarted.
+IN_PROC_BROWSER_TEST_F(
+    SSLUITestReduceSubresourceNotifications,
+    PRE_BadCertFollowedByGoodCertNavigationFollowedByRestart) {
+  ASSERT_TRUE(https_server_expired_.Start());
+  ASSERT_TRUE(https_server_.Start());
+
+  std::string https_server_expired_host =
+      https_server_expired_.GetURL("/ssl/google.html").host();
+  std::string https_server_host =
+      https_server_.GetURL("/ssl/google.html").host();
+  ASSERT_EQ(https_server_expired_host, https_server_host);
+
+  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
+  Profile* profile = Profile::FromBrowserContext(tab->GetBrowserContext());
+  StatefulSSLHostStateDelegate* state =
+      static_cast<StatefulSSLHostStateDelegate*>(
+          profile->GetSSLHostStateDelegate());
+
+  // HTTPS-related warning exceptions have not been allowed by the user.
+  ASSERT_FALSE(state->HasAllowExceptionForAnyHost(
+      tab->GetPrimaryMainFrame()->GetStoragePartition()));
+  // Not sending subresource notifications since no HTTPS-exceptions have been
+  // allowed by the user.
+  ASSERT_FALSE(tab->GetSendSubresourceNotification());
+
+  // Navigate to a page with a certificate error.
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(
+      browser(), https_server_expired_.GetURL("/ssl/google.html")));
+  ssl_test_util::CheckAuthenticationBrokenState(
+      tab, net::CERT_STATUS_DATE_INVALID, AuthState::SHOWING_INTERSTITIAL);
+
+  // Click through the interstitial.
+  ProceedThroughInterstitial(tab);
+
+  // HTTPS-related warning exception has been allowed by the user.
+  EXPECT_TRUE(state->HasAllowExceptionForAnyHost(
+      tab->GetPrimaryMainFrame()->GetStoragePartition()));
+
+  // State in browser process has been updated, i.e., start renderers need to
+  // start sending subresource notifications.
+  EXPECT_TRUE(tab->GetSendSubresourceNotification());
+
+  // See a good certificate for the same host. This removes the allowed
+  // exception but `renderer_preferences_.send_subresource_notification_` is not
+  // set to false. This is because allowing and revoking HTTPS related warning
+  // exception is rare, and thus is update at the startup of the browser.
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(
+      browser(), https_server_.GetURL("/ssl/google.html")));
+  EXPECT_FALSE(state->HasAllowExceptionForAnyHost(
+      tab->GetPrimaryMainFrame()->GetStoragePartition()));
+
+  EXPECT_TRUE(tab->GetSendSubresourceNotification());
+}
+
+// Verifies that on browser restarts, we update `renderer_preferences_`
+// according to state of allowed exceptions at browser start-up.
+IN_PROC_BROWSER_TEST_F(SSLUITestReduceSubresourceNotifications,
+                       BadCertFollowedByGoodCertNavigationFollowedByRestart) {
+  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
+  Profile* profile = Profile::FromBrowserContext(tab->GetBrowserContext());
+
+  StatefulSSLHostStateDelegate* state =
+      static_cast<StatefulSSLHostStateDelegate*>(
+          profile->GetSSLHostStateDelegate());
+
+  EXPECT_FALSE(state->HasAllowExceptionForAnyHost(
+      tab->GetPrimaryMainFrame()->GetStoragePartition()));
+
+  EXPECT_FALSE(tab->GetSendSubresourceNotification());
+}
+
+// Tests whether any certificate error exceptions made are persisted across
+// sessions. This also verifies persistence of `send_subresource_notification_`.
+IN_PROC_BROWSER_TEST_F(SSLUITestReduceSubresourceNotifications,
+                       PRE_CertDecisionPersistsSessions) {
+  ASSERT_TRUE(https_server_expired_.Start());
+
+  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
+  Profile* profile = Profile::FromBrowserContext(tab->GetBrowserContext());
+  StatefulSSLHostStateDelegate* state =
+      static_cast<StatefulSSLHostStateDelegate*>(
+          profile->GetSSLHostStateDelegate());
+
+  // HTTPS-related warning exceptions have not been allowed by the user.
+  ASSERT_FALSE(state->HasAllowExceptionForAnyHost(
+      tab->GetPrimaryMainFrame()->GetStoragePartition()));
+  // Not sending subresource notifications since no HTTPS-exceptions have been
+  // allowed by the user.
+  ASSERT_FALSE(tab->GetSendSubresourceNotification());
+
+  // Navigate to a page with a certificate error.
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(
+      browser(), https_server_expired_.GetURL("/ssl/google.html")));
+  ssl_test_util::CheckAuthenticationBrokenState(
+      tab, net::CERT_STATUS_DATE_INVALID, AuthState::SHOWING_INTERSTITIAL);
+
+  // Click through the interstitial.
+  ProceedThroughInterstitial(tab);
+
+  // HTTPS-related warning exception has been allowed by the user.
+  EXPECT_TRUE(state->HasAllowExceptionForAnyHost(
+      tab->GetPrimaryMainFrame()->GetStoragePartition()));
+
+  // renderer_preferences_.send_subresource_notification_ state updated.
+  EXPECT_TRUE(tab->GetSendSubresourceNotification());
+}
+
+IN_PROC_BROWSER_TEST_F(SSLUITestReduceSubresourceNotifications,
+                       CertDecisionPersistsSessions) {
+  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
+  Profile* profile = Profile::FromBrowserContext(tab->GetBrowserContext());
+  StatefulSSLHostStateDelegate* state =
+      static_cast<StatefulSSLHostStateDelegate*>(
+          profile->GetSSLHostStateDelegate());
+
+  // HTTPS-related warning exceptions has been allowed by the user in the past
+  // which has not expired.
+  EXPECT_TRUE(state->HasAllowExceptionForAnyHost(
+      tab->GetPrimaryMainFrame()->GetStoragePartition()));
+
+  // State in browser persists after restart.
+  ASSERT_TRUE(tab->GetSendSubresourceNotification());
+}
+
+// Tests persistence of `send_subresource_notification_` when multiple bad
+// certificates are allowed by the user.
+IN_PROC_BROWSER_TEST_F(SSLUITestReduceSubresourceNotifications,
+                       MultipleBadCertNavigations) {
+  ASSERT_TRUE(https_server_expired_.Start());
+
+  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
+  Profile* profile = Profile::FromBrowserContext(tab->GetBrowserContext());
+  StatefulSSLHostStateDelegate* state =
+      static_cast<StatefulSSLHostStateDelegate*>(
+          profile->GetSSLHostStateDelegate());
+
+  // HTTPS-related warning exceptions have not been allowed by the user.
+  ASSERT_FALSE(state->HasAllowExceptionForAnyHost(
+      tab->GetPrimaryMainFrame()->GetStoragePartition()));
+  // Not sending subresource notifications since no HTTPS-exceptions have been
+  // allowed by the user.
+  ASSERT_FALSE(tab->GetSendSubresourceNotification());
+
+  // Navigate to a page with a certificate error.
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(
+      browser(), https_server_expired_.GetURL("/ssl/google.html")));
+  ssl_test_util::CheckAuthenticationBrokenState(
+      tab, net::CERT_STATUS_DATE_INVALID, AuthState::SHOWING_INTERSTITIAL);
+
+  // Click through the interstitial.
+  ProceedThroughInterstitial(tab);
+
+  // HTTPS-related warning exception has been allowed by the user.
+  EXPECT_TRUE(state->HasAllowExceptionForAnyHost(
+      tab->GetPrimaryMainFrame()->GetStoragePartition()));
+
+  // renderer_preferences_.send_subresource_notification_ state updated.
+  EXPECT_TRUE(tab->GetSendSubresourceNotification());
+
+  // Navigate to a page with a certificate error, and click through the
+  // interstitial so the certificate is allowlisted.
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(
+      browser(),
+      GURL("https://site.test:" + std::to_string(https_server_expired_.port()) +
+           "/ssl/blank_page.html")));
+  ProceedThroughInterstitial(tab);
+
+  // HTTPS-related warning exception has been allowed by the user.
+  EXPECT_TRUE(state->HasAllowExceptionForAnyHost(
+      tab->GetPrimaryMainFrame()->GetStoragePartition()));
+
+  EXPECT_TRUE(tab->GetSendSubresourceNotification());
+}
+
 // Verifies that if a bad certificate is seen for a host and the user proceeds
 // through the interstitial, the decision to proceed is initially remembered.
 // However, if this is followed by another visit, and a good certificate
diff --git a/chrome/browser/ssl/ssl_error_controller_client.cc b/chrome/browser/ssl/ssl_error_controller_client.cc
index 19c4d806..0d55440 100644
--- a/chrome/browser/ssl/ssl_error_controller_client.cc
+++ b/chrome/browser/ssl/ssl_error_controller_client.cc
@@ -109,6 +109,9 @@
           profile->GetSSLHostStateDelegate());
   // StatefulSSLHostStateDelegate can be null during tests.
   if (state) {
+    // Notifies the browser process when a certificate exception is allowed.
+    web_contents_->SetAlwaysSendSubresourceNotifications();
+
     state->AllowCert(
         request_url_.host(), *ssl_info_.cert.get(), cert_error_,
         web_contents_->GetPrimaryMainFrame()->GetStoragePartition());
diff --git a/chrome/browser/ssl/stateful_ssl_host_state_delegate_test.cc b/chrome/browser/ssl/stateful_ssl_host_state_delegate_test.cc
index c68e7e8a..29a32fd39 100644
--- a/chrome/browser/ssl/stateful_ssl_host_state_delegate_test.cc
+++ b/chrome/browser/ssl/stateful_ssl_host_state_delegate_test.cc
@@ -129,6 +129,34 @@
                                storage_partition));
 }
 
+// Tests the expected behavior of calling HasAllowExceptionForAnyHost on the
+// SSLHostStateDelegate class after setting website settings for
+// different ContentSettingsType.
+IN_PROC_BROWSER_TEST_F(StatefulSSLHostStateDelegateTest,
+                       HasAllowExceptionForAnyHost) {
+  scoped_refptr<net::X509Certificate> cert = GetOkCert();
+  content::WebContents* tab =
+      browser()->tab_strip_model()->GetActiveWebContents();
+  Profile* profile = Profile::FromBrowserContext(tab->GetBrowserContext());
+  content::SSLHostStateDelegate* state = profile->GetSSLHostStateDelegate();
+  auto* storage_partition = tab->GetPrimaryMainFrame()->GetStoragePartition();
+  auto* host_content_settings_map =
+      HostContentSettingsMapFactory::GetForProfile(profile);
+  GURL url = GURL("https://example1.com/");
+
+  EXPECT_EQ(false, state->HasAllowExceptionForAnyHost(storage_partition));
+
+  host_content_settings_map->SetContentSettingDefaultScope(
+      url, url, ContentSettingsType::COOKIES, CONTENT_SETTING_DEFAULT);
+  EXPECT_EQ(false, state->HasAllowExceptionForAnyHost(storage_partition));
+
+  // Simulate a user decision to allow an invalid certificate exception for
+  // kWWWGoogleHost.
+  state->AllowCert(kWWWGoogleHost, *cert, net::ERR_CERT_DATE_INVALID,
+                   storage_partition);
+  EXPECT_EQ(true, state->HasAllowExceptionForAnyHost(storage_partition));
+}
+
 // Tests the expected behavior of calling IsHttpAllowedForHost on the
 // SSLHostStateDelegate class after various HTTP decisions have been made.
 IN_PROC_BROWSER_TEST_F(StatefulSSLHostStateDelegateTest, HttpAllowlisting) {
diff --git a/chrome/browser/storage_access_api/api_browsertest.cc b/chrome/browser/storage_access_api/api_browsertest.cc
index 1b0a79a..26d606a 100644
--- a/chrome/browser/storage_access_api/api_browsertest.cc
+++ b/chrome/browser/storage_access_api/api_browsertest.cc
@@ -220,6 +220,11 @@
     // assumption by setting up the auto-response themselves.
     prompt_factory_->set_response_type(
         permissions::PermissionRequestManager::NONE);
+
+    // Most of these tests invoke document.requestStorageAccess from a kHostB
+    // iframe. We pre-seed that site with user interaction, to avoid being
+    // blocked by the top-level user interaction heuristic.
+    EnsureUserInteractionOn(kHostB);
   }
 
   void TearDownOnMainThread() override { prompt_factory_.reset(); }
@@ -407,6 +412,15 @@
     return ChildFrameAt(GetPrimaryMainFrame(), 1);
   }
 
+  void EnsureUserInteractionOn(base::StringPiece host) {
+    ASSERT_TRUE(ui_test_utils::NavigateToURL(
+        browser(), https_server_.GetURL(host, "/empty.html")));
+    // ExecJs runs with a synthetic user interaction (by default), which is all
+    // we need, so our script is a no-op.
+    ASSERT_TRUE(content::ExecJs(
+        browser()->tab_strip_model()->GetActiveWebContents(), ""));
+  }
+
   net::test_server::EmbeddedTestServer& https_server() { return https_server_; }
 
   bool IsStoragePartitioned() const { return is_storage_partitioned_; }
@@ -482,6 +496,8 @@
 IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest, PermissionQueryCrossSite) {
   SetBlockThirdPartyCookies(true);
 
+  EnsureUserInteractionOn(kHostA);
+
   NavigateToPageWithFrame(kHostB);
   NavigateFrameTo(kHostA, "/echoheader?cookie");
 
@@ -646,6 +662,7 @@
     StorageAccessAPIBrowserTest,
     ThirdPartyCookiesIFrameRequestsAccess_NestedCrossSiteIframe_DistinctSites) {
   SetBlockThirdPartyCookies(true);
+  EnsureUserInteractionOn(kHostC);
 
   NavigateToPageWithFrame(kHostA);
   NavigateFrameTo(kHostB, "/iframe.html");
@@ -668,6 +685,8 @@
 // other's granted permission.
 IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
                        ThirdPartyCookiesCrossSiteSiblingIFrameRequestsAccess) {
+  EnsureUserInteractionOn(kHostC);
+
   NavigateToPageWithTwoFrames(kHostA);
   NavigateFirstFrameTo(EchoCookiesURL(kHostB));
   NavigateSecondFrameTo(EchoCookiesURL(kHostC));
@@ -763,6 +782,8 @@
 IN_PROC_BROWSER_TEST_F(
     StorageAccessAPIBrowserTest,
     ThirdPartyCookiesIFrameThirdPartyExceptions_NestedCrossSite) {
+  EnsureUserInteractionOn(kHostC);
+
   SetBlockThirdPartyCookies(true);
   BlockAllCookiesOnHost(kHostC);
 
@@ -1383,6 +1404,8 @@
 
 IN_PROC_BROWSER_TEST_P(StorageAccessAPIStorageBrowserTest,
                        NestedThirdPartyIFrameStorage) {
+  EnsureUserInteractionOn(kHostC);
+
   NavigateToPageWithFrame(kHostA);
   NavigateFrameTo(kHostB, "/iframe.html");
   NavigateNestedFrameTo(kHostC, "/browsing_data/site_data.html");
@@ -1837,6 +1860,37 @@
   }
 }
 
+IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
+                       TopLevelUserInteractionRequired) {
+  SetBlockThirdPartyCookies(true);
+
+  // The test fixture pre-seeds kHostB with top-level user interaction, but not
+  // the other hosts. We intentionally use kHostA as the embed, since it has not
+  // been seeded with a top-level user interaction.
+
+  NavigateToPageWithFrame(kHostB);
+  NavigateFrameTo(EchoCookiesURL(kHostA));
+
+  ASSERT_EQ(ReadCookiesAndContent(GetFrame(), kHostA), NoCookiesWithContent());
+
+  prompt_factory()->set_response_type(
+      permissions::PermissionRequestManager::ACCEPT_ALL);
+
+  EXPECT_FALSE(content::ExecJs(GetFrame(), "document.requestStorageAccess()"));
+
+  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
+  EXPECT_EQ(ReadCookies(GetFrame(), kHostA), NoCookies());
+
+  // If kHostA has a top-level interaction, it can request storage access. The
+  // user interaction should be tracked by site, not origin.
+  EnsureUserInteractionOn(kHostASubdomain);
+  NavigateToPageWithFrame(kHostB);
+  NavigateFrameTo(EchoCookiesURL(kHostA));
+
+  EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));
+  EXPECT_EQ(ReadCookies(GetFrame(), kHostA), CookieBundle("cross-site=a.test"));
+}
+
 class StorageAccessAPIWithImplicitGrantsBrowserTest
     : public StorageAccessAPIBaseBrowserTest {
  public:
diff --git a/chrome/browser/storage_access_api/storage_access_grant_permission_context.cc b/chrome/browser/storage_access_api/storage_access_grant_permission_context.cc
index 55d0fc1d..317b114 100644
--- a/chrome/browser/storage_access_api/storage_access_grant_permission_context.cc
+++ b/chrome/browser/storage_access_api/storage_access_grant_permission_context.cc
@@ -13,6 +13,7 @@
 #include "base/notreached.h"
 #include "base/ranges/algorithm.h"
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
+#include "chrome/browser/dips/dips_service.h"
 #include "chrome/browser/first_party_sets/first_party_sets_policy_service.h"
 #include "chrome/browser/first_party_sets/first_party_sets_policy_service_factory.h"
 #include "chrome/browser/profiles/profile.h"
@@ -45,6 +46,8 @@
 
 constexpr base::TimeDelta kImplicitGrantDuration = base::Hours(24);
 constexpr base::TimeDelta kExplicitGrantDuration = base::Days(30);
+constexpr base::TimeDelta kTopLevelUserInteractionHeuristicBound =
+    base::Days(30);
 
 // Returns true iff the request was answered implicitly (assuming it met some
 // other baseline prerequisites).
@@ -97,6 +100,7 @@
     case RequestOutcome::kDeniedByFirstPartySet:
     case RequestOutcome::kDeniedByPrerequisites:
     case RequestOutcome::kReusedPreviousDecision:
+    case RequestOutcome::kDeniedByTopLevelInteractionHeuristic:
       NOTREACHED_NORETURN();
     case RequestOutcome::kGrantedByUser:
     case RequestOutcome::kDeniedByUser:
@@ -286,6 +290,43 @@
     return;
   }
 
+  // We haven't found a reason to auto-grant permission, but before we prompt
+  // there's one more hurdle: the user must have interacted with the requesting
+  // site in a top-level context recently.
+  DIPSService* dips_service = DIPSService::Get(browser_context());
+  if (dips_service) {
+    dips_service->DidSiteHaveInteractionSince(
+        requesting_origin,
+        base::Time::Now() - kTopLevelUserInteractionHeuristicBound,
+        base::BindOnce(&StorageAccessGrantPermissionContext::
+                           OnCheckedUserInteractionHeuristic,
+                       weak_factory_.GetWeakPtr(), id, requesting_origin,
+                       embedding_origin, user_gesture, std::move(callback)));
+    return;
+  }
+
+  // If we don't have access to this kind of historical info, we waive the
+  // requirement, and show the prompt.
+  PermissionContextBase::DecidePermission(id, requesting_origin,
+                                          embedding_origin, user_gesture,
+                                          std::move(callback));
+}
+
+void StorageAccessGrantPermissionContext::OnCheckedUserInteractionHeuristic(
+    const permissions::PermissionRequestID& id,
+    const GURL& requesting_origin,
+    const GURL& embedding_origin,
+    bool user_gesture,
+    permissions::BrowserPermissionCallback callback,
+    bool had_top_level_user_interaction) {
+  if (!had_top_level_user_interaction) {
+    NotifyPermissionSetInternal(
+        id, requesting_origin, embedding_origin, std::move(callback),
+        /*persist=*/false, CONTENT_SETTING_BLOCK,
+        RequestOutcome::kDeniedByTopLevelInteractionHeuristic);
+    return;
+  }
+
   // Show prompt.
   PermissionContextBase::DecidePermission(id, requesting_origin,
                                           embedding_origin, user_gesture,
diff --git a/chrome/browser/storage_access_api/storage_access_grant_permission_context.h b/chrome/browser/storage_access_api/storage_access_grant_permission_context.h
index 3da12366..38230dc 100644
--- a/chrome/browser/storage_access_api/storage_access_grant_permission_context.h
+++ b/chrome/browser/storage_access_api/storage_access_grant_permission_context.h
@@ -42,7 +42,10 @@
   // The user has already been asked and made a choice (and was not asked
   // again).
   kReusedPreviousDecision = 7,
-  kMaxValue = kReusedPreviousDecision,
+  // The request was denied because the most recent top-level interaction on
+  // the embedded site was too long ago, or there is no such interaction.
+  kDeniedByTopLevelInteractionHeuristic = 8,
+  kMaxValue = kDeniedByTopLevelInteractionHeuristic,
 };
 
 class StorageAccessGrantPermissionContext
@@ -103,7 +106,7 @@
 
   // Checks First-Party Sets metadata to determine if auto-grants or
   // auto-denials are applicable. If no autogrant or autodenial is applicable,
-  // this tries to to use an implicit grant, and finally prompts the user if
+  // this tries to to use an implicit grant, and finally may prompt the user if
   // necessary.
   void CheckForAutoGrantOrAutoDenial(
       const permissions::PermissionRequestID& id,
@@ -113,7 +116,7 @@
       permissions::BrowserPermissionCallback callback,
       net::FirstPartySetMetadata metadata);
 
-  // Determines whether an implicit grant is available, and otherwise prompts
+  // Determines whether an implicit grant is available, and otherwise may prompt
   // the user.
   void UseImplicitGrantOrPrompt(
       const permissions::PermissionRequestID& id,
@@ -122,6 +125,16 @@
       bool user_gesture,
       permissions::BrowserPermissionCallback callback);
 
+  // Determines whether the top-level user-interaction heuristic was satisfied,
+  // and if so, prompts the user.
+  void OnCheckedUserInteractionHeuristic(
+      const permissions::PermissionRequestID& id,
+      const GURL& requesting_origin,
+      const GURL& embedding_origin,
+      bool user_gesture,
+      permissions::BrowserPermissionCallback callback,
+      bool had_top_level_user_interaction);
+
   base::WeakPtrFactory<StorageAccessGrantPermissionContext> weak_factory_{this};
 };
 
diff --git a/chrome/browser/storage_access_api/storage_access_grant_permission_context_unittest.cc b/chrome/browser/storage_access_api/storage_access_grant_permission_context_unittest.cc
index 9006bcf..87f713d 100644
--- a/chrome/browser/storage_access_api/storage_access_grant_permission_context_unittest.cc
+++ b/chrome/browser/storage_access_api/storage_access_grant_permission_context_unittest.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/storage_access_api/storage_access_grant_permission_context.h"
 
 #include "base/barrier_callback.h"
+#include "base/run_loop.h"
 #include "base/test/bind.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
@@ -12,6 +13,8 @@
 #include "base/version.h"
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
 #include "chrome/browser/content_settings/page_specific_content_settings_delegate.h"
+#include "chrome/browser/dips/dips_service.h"
+#include "chrome/browser/dips/dips_utils.h"
 #include "chrome/browser/first_party_sets/scoped_mock_first_party_sets_handler.h"
 #include "chrome/test/base/chrome_render_view_host_test_harness.h"
 #include "components/content_settings/browser/page_specific_content_settings.h"
@@ -122,6 +125,16 @@
         web_contents(),
         std::make_unique<chrome::PageSpecificContentSettingsDelegate>(
             web_contents()));
+
+    DIPSService* dips_service = DIPSService::Get(browser_context());
+    CHECK(dips_service);
+    base::RunLoop run_loop;
+    dips_service->storage()
+        ->AsyncCall(&DIPSStorage::RecordInteraction)
+        .WithArgs(GetRequesterURL(), base::Time::Now(),
+                  DIPSCookieMode::kBlock3PC)
+        .Then(run_loop.QuitClosure());
+    run_loop.Run();
   }
 
   void TearDown() override {
@@ -167,12 +180,28 @@
         request_id_generator_.GenerateNextId());
   }
 
+  void WaitUntilPrompt() {
+    mock_permission_prompt_factory_->WaitForPermissionBubble();
+    ASSERT_TRUE(request_manager()->IsRequestInProgress());
+  }
+
   content_settings::PageSpecificContentSettings*
   page_specific_content_settings() {
     return content_settings::PageSpecificContentSettings::GetForFrame(
         web_contents()->GetPrimaryMainFrame());
   }
 
+  permissions::PermissionRequestManager* request_manager() {
+    permissions::PermissionRequestManager* manager =
+        permissions::PermissionRequestManager::FromWebContents(web_contents());
+    CHECK(manager);
+    return manager;
+  }
+
+  permissions::MockPermissionPromptFactory& prompt_factory() {
+    return *mock_permission_prompt_factory_;
+  }
+
  private:
   base::test::ScopedFeatureList features_;
   std::unique_ptr<permissions::MockPermissionPromptFactory>
@@ -245,17 +274,11 @@
       CreateFakeID(), GetRequesterURL(), GetTopLevelURL(),
       /*user_gesture=*/true, future.GetCallback());
 
-  // Run until the prompt is ready.
-  base::RunLoop().RunUntilIdle();
-
-  permissions::PermissionRequestManager* manager =
-      permissions::PermissionRequestManager::FromWebContents(web_contents());
-  ASSERT_TRUE(manager);
-  ASSERT_TRUE(manager->IsRequestInProgress());
+  WaitUntilPrompt();
 
   // Accept the prompt and validate we get the expected setting back in our
   // callback.
-  manager->Accept();
+  request_manager()->Accept();
   EXPECT_EQ(CONTENT_SETTING_ALLOW, future.Get());
 
   histogram_tester().ExpectUniqueSample(kGrantIsImplicitHistogram,
@@ -286,22 +309,17 @@
       fake_id, GetRequesterURL(), GetTopLevelURL(),
       /*user_gesture=*/true, future.GetCallback());
 
-  // Run until the prompt is ready.
-  base::RunLoop().RunUntilIdle();
+  WaitUntilPrompt();
 
-  permissions::PermissionRequestManager* manager =
-      permissions::PermissionRequestManager::FromWebContents(web_contents());
-  ASSERT_TRUE(manager);
-  ASSERT_TRUE(manager->IsRequestInProgress());
-
-  permissions::PermissionRequest* request = manager->Requests().front();
+  permissions::PermissionRequest* request =
+      request_manager()->Requests().front();
   ASSERT_TRUE(request);
-  ASSERT_EQ(1u, manager->Requests().size());
+  ASSERT_EQ(1u, request_manager()->Requests().size());
   // Prompt should have both origins.
-  EXPECT_EQ(GetRequesterURL(), manager->GetRequestingOrigin());
-  EXPECT_EQ(GetTopLevelURL(), manager->GetEmbeddingOrigin());
+  EXPECT_EQ(GetRequesterURL(), request_manager()->GetRequestingOrigin());
+  EXPECT_EQ(GetTopLevelURL(), request_manager()->GetEmbeddingOrigin());
 
-  manager->Dismiss();
+  request_manager()->Dismiss();
   EXPECT_EQ(CONTENT_SETTING_ASK, future.Get());
   histogram_tester().ExpectUniqueSample(kRequestOutcomeHistogram,
                                         RequestOutcome::kDismissedByUser, 1);
@@ -385,9 +403,6 @@
       StorageAccessGrantPermissionContext& permission_context) {
     permissions::PermissionRequestID fake_id = CreateFakeID();
 
-    permissions::PermissionRequestManager* manager =
-        permissions::PermissionRequestManager::FromWebContents(web_contents());
-    DCHECK(manager);
     const int implicit_grant_limit =
         blink::features::kStorageAccessAPIImplicitGrantLimit.Get();
     base::RunLoop run_loop;
@@ -403,7 +418,7 @@
           /*user_gesture=*/true, barrier);
     }
     run_loop.Run();
-    EXPECT_FALSE(manager->IsRequestInProgress());
+    EXPECT_FALSE(request_manager()->IsRequestInProgress());
   }
 
  private:
@@ -426,9 +441,6 @@
   EXPECT_EQ(histogram_tester().GetBucketCount(
                 kRequestOutcomeHistogram, RequestOutcome::kGrantedByAllowance),
             5);
-  permissions::PermissionRequestManager* manager =
-      permissions::PermissionRequestManager::FromWebContents(web_contents());
-  ASSERT_TRUE(manager);
 
   EXPECT_THAT(page_specific_content_settings()->GetTwoSiteRequests(
                   ContentSettingsType::STORAGE_ACCESS),
@@ -440,14 +452,11 @@
         fake_id, GetRequesterURL(), GetTopLevelURL(),
         /*user_gesture=*/true, future.GetCallback());
 
-    // Run until the prompt is ready.
-    base::RunLoop().RunUntilIdle();
-
-    ASSERT_TRUE(manager->IsRequestInProgress());
+    WaitUntilPrompt();
 
     // Close the prompt and validate we get the expected setting back in our
     // callback.
-    manager->Dismiss();
+    request_manager()->Dismiss();
     EXPECT_EQ(CONTENT_SETTING_ASK, future.Get());
   }
   EXPECT_EQ(histogram_tester().GetBucketCount(kRequestOutcomeHistogram,
@@ -473,7 +482,7 @@
 
   // We should have no prompts still and our latest result should be an allow.
   EXPECT_EQ(CONTENT_SETTING_ALLOW, future.Get());
-  EXPECT_FALSE(manager->IsRequestInProgress());
+  EXPECT_FALSE(request_manager()->IsRequestInProgress());
   EXPECT_EQ(histogram_tester().GetBucketCount(
                 kRequestOutcomeHistogram, RequestOutcome::kGrantedByAllowance),
             6);
@@ -495,9 +504,6 @@
 
   ExhaustImplicitGrants(GetRequesterURL(), permission_context);
 
-  permissions::PermissionRequestManager* manager =
-      permissions::PermissionRequestManager::FromWebContents(web_contents());
-
   content::WebContentsTester::For(web_contents())
       ->NavigateAndCommit(GetDummyEmbeddingUrlWithSubdomain());
 
@@ -514,7 +520,7 @@
 
   // We should have no prompts still and our latest result should be an allow.
   EXPECT_EQ(CONTENT_SETTING_ALLOW, future.Get());
-  EXPECT_FALSE(manager->IsRequestInProgress());
+  EXPECT_FALSE(request_manager()->IsRequestInProgress());
   EXPECT_EQ(histogram_tester().GetBucketCount(
                 kRequestOutcomeHistogram, RequestOutcome::kGrantedByAllowance),
             implicit_grant_limit);
@@ -546,17 +552,11 @@
       fake_id, GetRequesterURL(), GetTopLevelURL(),
       /*user_gesture=*/true, future.GetCallback());
 
-  // Run until the prompt is ready.
-  base::RunLoop().RunUntilIdle();
-
-  permissions::PermissionRequestManager* manager =
-      permissions::PermissionRequestManager::FromWebContents(web_contents());
-  ASSERT_TRUE(manager);
-  ASSERT_TRUE(manager->IsRequestInProgress());
+  WaitUntilPrompt();
 
   // Deny the prompt and validate we get the expected setting back in our
   // callback.
-  manager->Deny();
+  request_manager()->Deny();
   EXPECT_EQ(CONTENT_SETTING_BLOCK, future.Get());
 
   histogram_tester().ExpectTotalCount(kGrantIsImplicitHistogram, 0);
@@ -584,18 +584,16 @@
       GetRequesterURL(), GetTopLevelURL(), ContentSettingsType::STORAGE_ACCESS,
       CONTENT_SETTING_BLOCK);
 
+  prompt_factory().set_response_type(
+      permissions::PermissionRequestManager::AutoResponseType::NONE);
+
   base::test::TestFuture<ContentSetting> future;
   permission_context.DecidePermissionForTesting(
       fake_id, GetRequesterURL(), GetTopLevelURL(),
       /*user_gesture=*/true, future.GetCallback());
 
   // Ensure the prompt is not shown.
-  base::RunLoop().RunUntilIdle();
-
-  permissions::PermissionRequestManager* manager =
-      permissions::PermissionRequestManager::FromWebContents(web_contents());
-  ASSERT_TRUE(manager);
-  ASSERT_FALSE(manager->IsRequestInProgress());
+  ASSERT_FALSE(request_manager()->IsRequestInProgress());
   EXPECT_EQ(CONTENT_SETTING_BLOCK, future.Get());
 
   // However, ensure that the user's denial is not exposed when querying the
@@ -623,17 +621,11 @@
       fake_id, GetRequesterURL(), GetTopLevelURL(),
       /*user_gesture=*/true, future.GetCallback());
 
-  // Run until the prompt is ready.
-  base::RunLoop().RunUntilIdle();
-
-  permissions::PermissionRequestManager* manager =
-      permissions::PermissionRequestManager::FromWebContents(web_contents());
-  ASSERT_TRUE(manager);
-  ASSERT_TRUE(manager->IsRequestInProgress());
+  WaitUntilPrompt();
 
   // Accept the prompt and validate we get the expected setting back in our
   // callback.
-  manager->Accept();
+  request_manager()->Accept();
   EXPECT_EQ(CONTENT_SETTING_ALLOW, future.Get());
 
   histogram_tester().ExpectUniqueSample(kGrantIsImplicitHistogram,
diff --git a/chrome/browser/sync/prefs/chrome_syncable_prefs_database.cc b/chrome/browser/sync/prefs/chrome_syncable_prefs_database.cc
index 4fe75255..41b9e6e 100644
--- a/chrome/browser/sync/prefs/chrome_syncable_prefs_database.cc
+++ b/chrome/browser/sync/prefs/chrome_syncable_prefs_database.cc
@@ -253,6 +253,12 @@
   kAccessibilityColorVisionCorrectionAmount = 100201,
   kAccessibilityColorVisionDeficiencyType = 100202,
   kShowDeskButtonInShelf = 100203,
+  // See components/sync_preferences/README.md about adding new entries here.
+  // vvvvv IMPORTANT! vvvvv
+  // Note to the reviewer: IT IS YOUR RESPONSIBILITY to ensure that new syncable
+  // prefs follow privacy guidelines! See the readme file linked above for
+  // guidance and escalation path in case anything is unclear.
+  // ^^^^^ IMPORTANT! ^^^^^
 };
 }  // namespace syncable_prefs_ids
 
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 59e8e8f5..75f2103 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -5941,6 +5941,7 @@
       "//extensions/strings",
       "//ui/base/cursor",
       "//ui/color:color",
+      "//ui/color:mixers",
     ]
     allow_circular_includes_from += [
       "//chrome/browser/apps/platform_apps",
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/OmniboxFeatures.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/OmniboxFeatures.java
index d70004e..c5bfb704 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/OmniboxFeatures.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/OmniboxFeatures.java
@@ -6,9 +6,13 @@
 
 import android.content.Context;
 
+import org.chromium.base.BaseSwitches;
+import org.chromium.base.CommandLine;
+import org.chromium.base.SysUtils;
 import org.chromium.chrome.browser.flags.BooleanCachedFieldTrialParameter;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.flags.MutableFlagWithSafeDefault;
+import org.chromium.components.browser_ui.util.ConversionUtils;
 import org.chromium.ui.base.DeviceFormFactor;
 
 /**
@@ -16,6 +20,16 @@
  *   List of Omnibox features and parameters.
  */
 public class OmniboxFeatures {
+    // Threshold for low RAM devices. We won't be showing suggestion images
+    // on devices that have less RAM than this to avoid bloat and reduce user-visible
+    // slowdown while spinning up an image decompression process.
+    // We set the threshold to 1.5GB to reduce number of users affected by this restriction.
+    private static final int LOW_MEMORY_THRESHOLD_KB =
+            (int) (1.5 * ConversionUtils.KILOBYTES_PER_GIGABYTE);
+
+    /// Holds the information whether logic should focus on preserving memory on this device.
+    private static Boolean sIsLowMemoryDevice;
+
     public static final BooleanCachedFieldTrialParameter ENABLE_MODERNIZE_VISUAL_UPDATE_ON_TABLET =
             new BooleanCachedFieldTrialParameter(ChromeFeatureList.OMNIBOX_MODERNIZE_VISUAL_UPDATE,
                     "enable_modernize_visual_update_on_tablet", false);
@@ -185,4 +199,16 @@
     public static boolean shouldPreWarmRecyclerViewPool() {
         return sWarmRecycledViewPoolFlag.isEnabled();
     }
+
+    /**
+     * Returns whether the device is to be considered low-end for any memory intensive operations.
+     */
+    public static boolean isLowMemoryDevice() {
+        if (sIsLowMemoryDevice == null) {
+            sIsLowMemoryDevice = (SysUtils.amountOfPhysicalMemoryKB() < LOW_MEMORY_THRESHOLD_KB
+                    && !CommandLine.getInstance().hasSwitch(
+                            BaseSwitches.DISABLE_LOW_END_DEVICE_MODE));
+        }
+        return sIsLowMemoryDevice;
+    }
 }
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/DropdownItemViewInfoListBuilder.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/DropdownItemViewInfoListBuilder.java
index 522dbc3..59f22a8 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/DropdownItemViewInfoListBuilder.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/DropdownItemViewInfoListBuilder.java
@@ -91,7 +91,9 @@
         final Supplier<ShareDelegate> shareSupplier =
                 () -> mShareDelegateSupplier == null ? null : mShareDelegateSupplier.get();
 
-        mFaviconFetcher = new FaviconFetcher(context, iconBridgeSupplier);
+        if (!OmniboxFeatures.isLowMemoryDevice()) {
+            mFaviconFetcher = new FaviconFetcher(context, iconBridgeSupplier);
+        }
 
         if (OmniboxFeatures.shouldShowModernizeVisualUpdate(context)
                 && !OmniboxFeatures.shouldShowActiveColorOnOmnibox()) {
@@ -177,10 +179,14 @@
             mFaviconFetcher.clearCache();
         }
 
-        mIconBridge = new LargeIconBridge(profile);
-        mImageFetcher = ImageFetcherFactory.createImageFetcher(ImageFetcherConfig.IN_MEMORY_ONLY,
-                profile.getProfileKey(), GlobalDiscardableReferencePool.getReferencePool(),
-                MAX_IMAGE_CACHE_SIZE);
+        if (!OmniboxFeatures.isLowMemoryDevice()) {
+            mIconBridge = new LargeIconBridge(profile);
+            mIconBridge.createCache(MAX_IMAGE_CACHE_SIZE);
+
+            mImageFetcher = ImageFetcherFactory.createImageFetcher(
+                    ImageFetcherConfig.IN_MEMORY_ONLY, profile.getProfileKey(),
+                    GlobalDiscardableReferencePool.getReferencePool(), MAX_IMAGE_CACHE_SIZE);
+        }
     }
 
     /**
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionViewProcessor.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionViewProcessor.java
index 39cb6e6d..859b4da 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionViewProcessor.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionViewProcessor.java
@@ -223,7 +223,8 @@
      * @param url Target URL the suggestion points to.
      */
     protected void fetchSuggestionFavicon(PropertyModel model, GURL url) {
-        assert mFaviconFetcher != null : "You must supply the FaviconFetcher in order to use it";
+        if (mFaviconFetcher == null) return;
+
         mFaviconFetcher.fetchFaviconWithBackoff(url, false, (icon, type) -> {
             if (icon != null) {
                 setSuggestionDrawableState(
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/entity/EntitySuggestionProcessor.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/entity/EntitySuggestionProcessor.java
index 7972823..c8e8082 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/entity/EntitySuggestionProcessor.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/entity/EntitySuggestionProcessor.java
@@ -11,16 +11,13 @@
 
 import androidx.annotation.VisibleForTesting;
 
-import org.chromium.base.BaseSwitches;
-import org.chromium.base.CommandLine;
-import org.chromium.base.SysUtils;
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.supplier.Supplier;
+import org.chromium.chrome.browser.omnibox.OmniboxFeatures;
 import org.chromium.chrome.browser.omnibox.R;
 import org.chromium.chrome.browser.omnibox.suggestions.SuggestionHost;
 import org.chromium.chrome.browser.omnibox.suggestions.base.BaseSuggestionViewProcessor;
 import org.chromium.chrome.browser.omnibox.suggestions.base.SuggestionDrawableState;
-import org.chromium.components.browser_ui.util.ConversionUtils;
 import org.chromium.components.image_fetcher.ImageFetcher;
 import org.chromium.components.omnibox.AutocompleteMatch;
 import org.chromium.components.omnibox.OmniboxSuggestionType;
@@ -37,14 +34,6 @@
 public class EntitySuggestionProcessor extends BaseSuggestionViewProcessor {
     private final Map<GURL, List<PropertyModel>> mPendingImageRequests;
     private final Supplier<ImageFetcher> mImageFetcherSupplier;
-    // Threshold for low RAM devices. We won't be showing entity suggestion images
-    // on devices that have less RAM than this to avoid bloat and reduce user-visible
-    // slowdown while spinning up an image decompression process.
-    // We set the threshold to 1.5GB to reduce number of users affected by this restriction.
-    // TODO(crbug.com/979426): This threshold is set arbitrarily and should be revisited once
-    // we have more data about relation between system memory and performance.
-    private static final int LOW_MEMORY_THRESHOLD_KB =
-            (int) (1.5 * ConversionUtils.KILOBYTES_PER_GIGABYTE);
 
     /**
      * @param context An Android context.
@@ -144,8 +133,7 @@
                         .setAllowTint(true)
                         .build());
 
-        if (SysUtils.amountOfPhysicalMemoryKB() >= LOW_MEMORY_THRESHOLD_KB
-                || CommandLine.getInstance().hasSwitch(BaseSwitches.DISABLE_LOW_END_DEVICE_MODE)) {
+        if (!OmniboxFeatures.isLowMemoryDevice()) {
             applyImageDominantColor(suggestion.getImageDominantColor(), model);
             fetchEntityImage(suggestion, model);
         }
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/mostvisited/MostVisitedTilesProcessor.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/mostvisited/MostVisitedTilesProcessor.java
index 3944953..62eaffe6 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/mostvisited/MostVisitedTilesProcessor.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/mostvisited/MostVisitedTilesProcessor.java
@@ -53,7 +53,7 @@
 
     private final @NonNull Context mContext;
     private final @NonNull SuggestionHost mSuggestionHost;
-    private final @NonNull FaviconFetcher mFaviconFetcher;
+    private final @Nullable FaviconFetcher mFaviconFetcher;
     private final int mMinCarouselItemViewHeight;
     private @Nullable RecycledViewPool mMostVisitedTilesRecycledViewPool;
     private boolean mEnableOrganicRepeatableQueries;
@@ -66,7 +66,7 @@
      * @param faviconFetcher Class retrieving favicons for the MV Tiles.
      */
     public MostVisitedTilesProcessor(@NonNull Context context, @NonNull SuggestionHost host,
-            @NonNull FaviconFetcher faviconFetcher) {
+            @Nullable FaviconFetcher faviconFetcher) {
         super(context);
         mContext = context;
         mSuggestionHost = host;
@@ -142,13 +142,13 @@
                 return true;
             });
 
+            tileModel.set(TileViewProperties.ICON_TINT,
+                    ChromeColors.getSecondaryIconTint(mContext, /* isIncognito= */ false));
             if (isSearch) {
                 // Note: we should never show most visited tiles in incognito mode. Catch this early
                 // if we ever do.
                 assert model.get(SuggestionCommonProperties.COLOR_SCHEME)
                         != BrandedColorScheme.INCOGNITO;
-                tileModel.set(TileViewProperties.ICON_TINT,
-                        ChromeColors.getSecondaryIconTint(mContext, /* isIncognito= */ false));
                 tileModel.set(TileViewProperties.ICON,
                         OmniboxResourceProvider.getDrawable(
                                 mContext, R.drawable.ic_suggestion_magnifier));
@@ -156,7 +156,8 @@
                         OmniboxResourceProvider.getString(mContext,
                                 R.string.accessibility_omnibox_most_visited_tile_search, title));
             } else {
-                tileModel.set(TileViewProperties.ICON_TINT, null);
+                tileModel.set(TileViewProperties.ICON,
+                        OmniboxResourceProvider.getDrawable(mContext, R.drawable.ic_globe_24dp));
                 tileModel.set(TileViewProperties.CONTENT_DESCRIPTION,
                         OmniboxResourceProvider.getString(mContext,
                                 R.string.accessibility_omnibox_most_visited_tile_navigate, title,
@@ -165,9 +166,12 @@
                 tileModel.set(TileViewProperties.SMALL_ICON_ROUNDING_RADIUS,
                         mContext.getResources().getDimensionPixelSize(
                                 R.dimen.omnibox_carousel_icon_rounding_radius));
-                mFaviconFetcher.fetchFaviconWithBackoff(url, true, (icon, type) -> {
-                    tileModel.set(TileViewProperties.ICON, new BitmapDrawable(icon));
-                });
+                if (mFaviconFetcher != null) {
+                    mFaviconFetcher.fetchFaviconWithBackoff(url, true, (icon, type) -> {
+                        tileModel.set(TileViewProperties.ICON, new BitmapDrawable(icon));
+                        tileModel.set(TileViewProperties.ICON_TINT, null);
+                    });
+                }
             }
 
             tileList.add(new ListItem(
diff --git a/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/SignOutDialogCoordinator.java b/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/SignOutDialogCoordinator.java
index 6938df9..3491764 100644
--- a/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/SignOutDialogCoordinator.java
+++ b/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/SignOutDialogCoordinator.java
@@ -15,6 +15,7 @@
 import androidx.annotation.StringRes;
 import androidx.annotation.VisibleForTesting;
 
+import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.chrome.browser.preferences.Pref;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.profiles.ProfileAccountManagementMetrics;
@@ -173,6 +174,10 @@
             @Override
             public void onClick(PropertyModel model, int buttonType) {
                 if (buttonType == ButtonType.POSITIVE) {
+                    if (mCheckBox.getVisibility() == View.VISIBLE) {
+                        RecordHistogram.recordBooleanHistogram(
+                                "Signin.UserRequestedWipeDataOnSignout", mCheckBox.isChecked());
+                    }
                     mListener.onSignOutClicked(
                             mCheckBox.getVisibility() == View.VISIBLE && mCheckBox.isChecked());
                     mDialogManager.dismissDialog(
diff --git a/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/SignOutDialogTest.java b/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/SignOutDialogTest.java
index 4c247cc..21cb5a6 100644
--- a/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/SignOutDialogTest.java
+++ b/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/SignOutDialogTest.java
@@ -35,6 +35,7 @@
 import org.mockito.quality.Strictness;
 
 import org.chromium.base.test.BaseActivityTestRule;
+import org.chromium.base.test.util.HistogramWatcher;
 import org.chromium.base.test.util.JniMocker;
 import org.chromium.chrome.browser.preferences.Pref;
 import org.chromium.chrome.browser.profiles.Profile;
@@ -220,11 +221,14 @@
     @MediumTest
     public void testPositiveButtonWhenAccountIsNotManagedAndRemoveLocalDataNotChecked() {
         mockAllowDeletingBrowserHistoryPref(true);
+        var histogramWatcher = HistogramWatcher.newSingleRecordWatcher(
+                "Signin.UserRequestedWipeDataOnSignout", false);
         showSignOutDialog();
         onView(withId(R.id.remove_local_data)).inRoot(isDialog()).check(matches(isDisplayed()));
 
         onView(withText(R.string.continue_button)).inRoot(isDialog()).perform(click());
 
+        histogramWatcher.assertExpected();
         verify(mSigninMetricsUtilsNativeMock)
                 .logProfileAccountManagementMenu(ProfileAccountManagementMetrics.SIGNOUT_SIGNOUT,
                         GAIAServiceType.GAIA_SERVICE_TYPE_NONE);
@@ -235,11 +239,14 @@
     @MediumTest
     public void testPositiveButtonWhenAccountIsNotManagedAndRemoveLocalDataChecked() {
         mockAllowDeletingBrowserHistoryPref(true);
+        var histogramWatcher = HistogramWatcher.newSingleRecordWatcher(
+                "Signin.UserRequestedWipeDataOnSignout", true);
         showSignOutDialog();
 
         onView(withId(R.id.remove_local_data)).inRoot(isDialog()).perform(click());
         onView(withText(R.string.continue_button)).inRoot(isDialog()).perform(click());
 
+        histogramWatcher.assertExpected();
         verify(mSigninMetricsUtilsNativeMock)
                 .logProfileAccountManagementMenu(ProfileAccountManagementMetrics.SIGNOUT_SIGNOUT,
                         GAIAServiceType.GAIA_SERVICE_TYPE_NONE);
diff --git a/chrome/browser/ui/ash/app_access_notifier.cc b/chrome/browser/ui/ash/app_access_notifier.cc
index 3ff7cad..e76418f 100644
--- a/chrome/browser/ui/ash/app_access_notifier.cc
+++ b/chrome/browser/ui/ash/app_access_notifier.cc
@@ -23,10 +23,12 @@
 #include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/browser/ui/chrome_pages.h"
 #include "components/account_id/account_id.h"
 #include "components/services/app_service/public/cpp/app_capability_access_cache.h"
 #include "components/services/app_service/public/cpp/app_capability_access_cache_wrapper.h"
 #include "components/services/app_service/public/cpp/app_registry_cache.h"
+#include "components/services/app_service/public/cpp/app_types.h"
 #include "components/session_manager/core/session_manager.h"
 #include "components/session_manager/session_manager_types.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
@@ -186,23 +188,33 @@
 
   if (ash::features::IsPrivacyIndicatorsEnabled()) {
     // TODO(b/251686202): Finish Launch App functionality.
-    auto launch_app = absl::nullopt;
-    auto launch_settings =
-        base::BindRepeating(&AppAccessNotifier::LaunchAppSettings, app_id);
+    auto launch_app_callback = absl::nullopt;
+
+    auto* registry_cache = GetActiveUserAppRegistryCache();
+    if (!registry_cache) {
+      return;
+    }
+
+    auto app_type = registry_cache->GetAppType(app_id);
+    absl::optional<base::RepeatingClosure> launch_settings_callback;
+    if (app_type == apps::AppType::kSystemWeb) {
+      // We don't have the capability to launch privacy settings for system web
+      // app, so we will disable the settings button for this type of app.
+      launch_settings_callback = absl::nullopt;
+    } else {
+      launch_settings_callback =
+          base::BindRepeating(&AppAccessNotifier::LaunchAppSettings, app_id);
+    }
 
     ash::PrivacyIndicatorsController::Get()->UpdatePrivacyIndicators(
         app_id, /*app_name=*/GetAppShortNameFromAppId(app_id), is_camera_used,
         is_microphone_used, /*delegate=*/
         base::MakeRefCounted<ash::PrivacyIndicatorsNotificationDelegate>(
-            launch_app, launch_settings),
+            launch_app_callback, launch_settings_callback),
         ash::PrivacyIndicatorsSource::kApps);
 
-    auto* registry_cache = GetActiveUserAppRegistryCache();
-    if (registry_cache) {
-      base::UmaHistogramEnumeration(
-          "Ash.PrivacyIndicators.AppAccessUpdate.Type",
-          registry_cache->GetAppType(app_id));
-    }
+    base::UmaHistogramEnumeration("Ash.PrivacyIndicators.AppAccessUpdate.Type",
+                                  registry_cache->GetAppType(app_id));
   }
 }
 
@@ -259,14 +271,28 @@
     return;
   }
 
-  apps::AppServiceProxyFactory::GetForProfile(profile)->OpenNativeSettings(
-      app_id);
-
   auto* registry_cache = GetActiveUserAppRegistryCache();
-  if (registry_cache) {
-    base::UmaHistogramEnumeration("Ash.PrivacyIndicators.LaunchSettings",
-                                  registry_cache->GetAppType(app_id));
+  if (!registry_cache) {
+    return;
   }
+
+  auto app_type = registry_cache->GetAppType(app_id);
+
+  // We don't have the capability to launch privacy settings for system web
+  // app, so settings button is disabled for this type of app.
+  DCHECK(app_type != apps::AppType::kSystemWeb);
+
+  if (app_type == apps::AppType::kWeb) {
+    chrome::ShowAppManagementPage(profile, app_id,
+                                  ash::settings::AppManagementEntryPoint::
+                                      kPrivacyIndicatorsNotificationSettings);
+  } else {
+    apps::AppServiceProxyFactory::GetForProfile(profile)->OpenNativeSettings(
+        app_id);
+  }
+
+  base::UmaHistogramEnumeration("Ash.PrivacyIndicators.LaunchSettings",
+                                registry_cache->GetAppType(app_id));
 }
 
 AccountId AppAccessNotifier::GetActiveUserAccountId() {
diff --git a/chrome/browser/ui/ash/app_access_notifier_unittest.cc b/chrome/browser/ui/ash/app_access_notifier_unittest.cc
index 00128b97..741b2c0 100644
--- a/chrome/browser/ui/ash/app_access_notifier_unittest.cc
+++ b/chrome/browser/ui/ash/app_access_notifier_unittest.cc
@@ -697,3 +697,26 @@
   histograms.ExpectBucketCount(kPrivacyIndicatorsLaunchSettingsHistogramName,
                                apps::AppType::kChromeApp, 1);
 }
+
+// Tests that the privacy indicators notification of a system web app should not
+// have a launch settings callback (thus it will not have a launch settings
+// button).
+TEST_P(AppAccessNotifierPrivacyIndicatorTest,
+       SystemWebAppWithoutSettingsCallback) {
+  const std::string app_id = "test_app_id";
+  LaunchAppUsingCameraOrMicrophone(app_id, "test_app_name",
+                                   /*use_camera=*/true,
+                                   /*use_microphone=*/false,
+                                   /*app_type=*/apps::AppType::kSystemWeb);
+  const std::string notification_id =
+      ash::GetPrivacyIndicatorsNotificationId(app_id);
+
+  auto* notification =
+      message_center::MessageCenter::Get()->FindNotificationById(
+          notification_id);
+  ASSERT_TRUE(notification);
+
+  auto* delegate = static_cast<ash::PrivacyIndicatorsNotificationDelegate*>(
+      notification->delegate());
+  EXPECT_FALSE(delegate->launch_settings_callback());
+}
diff --git a/chrome/browser/ui/ash/system_tray_client_impl.cc b/chrome/browser/ui/ash/system_tray_client_impl.cc
index 487d64a..039a6b3 100644
--- a/chrome/browser/ui/ash/system_tray_client_impl.cc
+++ b/chrome/browser/ui/ash/system_tray_client_impl.cc
@@ -520,6 +520,16 @@
           : chromeos::settings::mojom::kAccessibilitySectionPath);
 }
 
+void SystemTrayClientImpl::ShowColorCorrectionSettings() {
+  if (user_manager::UserManager::Get()->IsLoggedInAsAnyKioskApp()) {
+    // TODO(b/259370808): Color correction settings subpage not available in
+    // Kiosk.
+    return;
+  }
+  ShowSettingsSubPageForActiveUser(
+      chromeos::settings::mojom::kDisplayAndMagnificationSubpagePath);
+}
+
 void SystemTrayClientImpl::ShowGestureEducationHelp() {
   base::RecordAction(base::UserMetricsAction("ShowGestureEducationHelp"));
   Profile* profile = ProfileManager::GetActiveUserProfile();
diff --git a/chrome/browser/ui/ash/system_tray_client_impl.h b/chrome/browser/ui/ash/system_tray_client_impl.h
index b03fb13..79405d1 100644
--- a/chrome/browser/ui/ash/system_tray_client_impl.h
+++ b/chrome/browser/ui/ash/system_tray_client_impl.h
@@ -83,6 +83,7 @@
   void ShowAboutChromeOSDetails() override;
   void ShowAccessibilityHelp() override;
   void ShowAccessibilitySettings() override;
+  void ShowColorCorrectionSettings() override;
   void ShowGestureEducationHelp() override;
   void ShowPaletteHelp() override;
   void ShowPaletteSettings() override;
diff --git a/chrome/browser/ui/ash/user_education/welcome_tour/welcome_tour_interactive_uitest.cc b/chrome/browser/ui/ash/user_education/welcome_tour/welcome_tour_interactive_uitest.cc
index 7831981..c5977382 100644
--- a/chrome/browser/ui/ash/user_education/welcome_tour/welcome_tour_interactive_uitest.cc
+++ b/chrome/browser/ui/ash/user_education/welcome_tour/welcome_tour_interactive_uitest.cc
@@ -5,9 +5,12 @@
 #include "ash/constants/ash_features.h"
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
+#include "ash/style/pill_button.h"
+#include "ash/style/system_dialog_delegate_view.h"
 #include "ash/user_education/user_education_constants.h"
 #include "ash/user_education/views/help_bubble_view_ash.h"
 #include "ash/user_education/welcome_tour/welcome_tour_controller.h"
+#include "ash/user_education/welcome_tour/welcome_tour_dialog.h"
 #include "base/test/scoped_feature_list.h"
 #include "chrome/browser/ash/app_list/app_list_client_impl.h"
 #include "chrome/browser/ash/system_web_apps/system_web_app_manager.h"
@@ -80,11 +83,55 @@
             ash::WelcomeTourController::Get()->GetInitialElementContext()));
   }
 
+  // Returns a builder for an interaction step that waits for the dialog.
+  [[nodiscard]] static auto WaitForDialog() {
+    return WaitForShow(
+        ash::WelcomeTourDialog::kWelcomeTourDialogElementIdForTesting);
+  }
+
   // Returns a builder for an interaction step that waits for a help bubble.
   [[nodiscard]] static auto WaitForHelpBubble() {
     return WaitForShow(ash::HelpBubbleViewAsh::kHelpBubbleElementIdForTesting);
   }
 
+  // Returns a builder for an interaction step that checks the dialog accept
+  // button text.
+  [[nodiscard]] static auto CheckDialogAcceptButtonText() {
+    return CheckViewProperty(
+        ash::SystemDialogDelegateView::kAcceptButtonIdForTesting,
+        &ash::PillButton::GetText,
+        l10n_util::GetStringUTF16(
+            IDS_ASH_WELCOME_TOUR_DIALOG_ACCEPT_BUTTON_TEXT));
+  }
+
+  // Returns a builder for an interaction step that checks the dialog cancel
+  // button text.
+  [[nodiscard]] static auto CheckDialogCancelButtonText() {
+    return CheckViewProperty(
+        ash::SystemDialogDelegateView::kCancelButtonIdForTesting,
+        &ash::PillButton::GetText,
+        l10n_util::GetStringUTF16(
+            IDS_ASH_WELCOME_TOUR_DIALOG_CANCEL_BUTTON_TEXT));
+  }
+
+  // Returns a builder for an interaction step that checks the dialog
+  // description.
+  [[nodiscard]] static auto CheckDialogDescription() {
+    return CheckViewProperty(
+        ash::SystemDialogDelegateView::kDescriptionTextIdForTesting,
+        &views::Label::GetText,
+        l10n_util::GetStringUTF16(
+            IDS_ASH_WELCOME_TOUR_DIALOG_DESCRIPTION_TEXT));
+  }
+
+  // Returns a builder for an interaction step that checks the dialog title.
+  [[nodiscard]] static auto CheckDialogTitle() {
+    return CheckViewProperty(
+        ash::SystemDialogDelegateView::kTitleTextIdForTesting,
+        &views::Label::GetText,
+        l10n_util::GetStringUTF16(IDS_ASH_WELCOME_TOUR_DIALOG_TITLE_TEXT));
+  }
+
   // Returns a builder for an interaction step that checks that the anchor of a
   // help bubble (a) matches the specified `element_id`, and (b) is contained
   // within the primary root window.
@@ -113,6 +160,13 @@
                              l10n_util::GetStringUTF16(message_id));
   }
 
+  // Returns a builder for an interaction step that presses the dialog accept
+  // button.
+  [[nodiscard]] auto PressDialogAcceptButton() {
+    return PressButton(
+        ash::SystemDialogDelegateView::kAcceptButtonIdForTesting);
+  }
+
   // Returns a builder for an interaction step that presses the default button
   // of a help bubble.
   [[nodiscard]] auto PressHelpBubbleDefaultButton() {
@@ -128,6 +182,13 @@
 // An interactive UI test that exercises the entire Welcome Tour.
 IN_PROC_BROWSER_TEST_F(WelcomeTourInteractiveUiTest, WelcomeTour) {
   RunTestSequence(
+      // Step 0: Dialog.
+      InAnyContext(WaitForDialog()),
+      InSameContext(Steps(CheckDialogAcceptButtonText(),
+                          CheckDialogCancelButtonText(),
+                          CheckDialogDescription(), CheckDialogTitle(),
+                          PressDialogAcceptButton(), FlushEvents())),
+
       // Step 1: Shelf.
       InAnyContext(WaitForHelpBubble()),
       InSameContext(Steps(
diff --git a/chrome/browser/ui/cocoa/accelerators_cocoa_browsertest.mm b/chrome/browser/ui/cocoa/accelerators_cocoa_browsertest.mm
index 267ccb2..9892dad 100644
--- a/chrome/browser/ui/cocoa/accelerators_cocoa_browsertest.mm
+++ b/chrome/browser/ui/cocoa/accelerators_cocoa_browsertest.mm
@@ -15,13 +15,17 @@
 #include "ui/base/l10n/l10n_util_mac.h"
 #import "ui/events/keycodes/keyboard_code_conversion_mac.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 using AcceleratorsCocoaBrowserTest = InProcessBrowserTest;
 
 namespace {
 
 // Adds all NSMenuItems with an accelerator to the array.
 void AddAcceleratorItemsToArray(NSMenu* menu, NSMutableArray* array) {
-  for (NSMenuItem* item in [menu itemArray]) {
+  for (NSMenuItem* item in menu.itemArray) {
     NSMenu* submenu = item.submenu;
     if (submenu)
       AddAcceleratorItemsToArray(submenu, array);
@@ -53,7 +57,7 @@
 NSMenuItem* MenuContainsAccelerator(NSMenu* menu,
                                     NSString* key_equivalent,
                                     NSUInteger modifier_mask) {
-  for (NSMenuItem* item in [menu itemArray]) {
+  for (NSMenuItem* item in menu.itemArray) {
     NSMenu* submenu = item.submenu;
     if (submenu) {
       NSMenuItem* result =
diff --git a/chrome/browser/ui/cocoa/apps/app_shim_menu_controller_mac_browsertest.mm b/chrome/browser/ui/cocoa/apps/app_shim_menu_controller_mac_browsertest.mm
index d973a601..b665ec3 100644
--- a/chrome/browser/ui/cocoa/apps/app_shim_menu_controller_mac_browsertest.mm
+++ b/chrome/browser/ui/cocoa/apps/app_shim_menu_controller_mac_browsertest.mm
@@ -2,16 +2,14 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "base/memory/raw_ptr.h"
-
 #import "chrome/browser/ui/cocoa/apps/app_shim_menu_controller_mac.h"
 
 #import <Cocoa/Cocoa.h>
 
 #include "base/command_line.h"
 #import "base/mac/foundation_util.h"
-#import "base/mac/scoped_nsobject.h"
 #import "base/mac/scoped_objc_class_swizzler.h"
+#include "base/memory/raw_ptr.h"
 #include "base/strings/sys_string_conversions.h"
 #include "chrome/app/chrome_command_ids.h"
 #include "chrome/browser/apps/app_shim/app_shim_manager_mac.h"
@@ -29,6 +27,10 @@
 #include "extensions/test/extension_test_message_listener.h"
 #import "ui/base/test/scoped_fake_nswindow_focus.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 namespace {
 
 class AppShimMenuControllerBrowserTest
diff --git a/chrome/browser/ui/cocoa/apps/native_app_window_cocoa_browsertest.mm b/chrome/browser/ui/cocoa/apps/native_app_window_cocoa_browsertest.mm
index 0e27100e..737f19a6ca 100644
--- a/chrome/browser/ui/cocoa/apps/native_app_window_cocoa_browsertest.mm
+++ b/chrome/browser/ui/cocoa/apps/native_app_window_cocoa_browsertest.mm
@@ -5,12 +5,12 @@
 #include "extensions/browser/app_window/native_app_window.h"
 
 #import <Cocoa/Cocoa.h>
+
 #include <memory>
 
 #include "base/functional/callback_helpers.h"
 #import "base/mac/foundation_util.h"
 #import "base/mac/scoped_cftyperef.h"
-#import "base/mac/scoped_nsobject.h"
 #include "base/memory/raw_ptr.h"
 #include "chrome/browser/apps/app_service/app_launch_params.h"
 #include "chrome/browser/apps/app_service/app_service_proxy.h"
@@ -38,6 +38,10 @@
 #import "ui/base/test/windowed_nsnotification_observer.h"
 #include "ui/views/widget/widget_interactive_uitest_utils.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 using extensions::AppWindow;
 using extensions::PlatformAppBrowserTest;
 
@@ -244,10 +248,10 @@
   EXPECT_FALSE([ns_window isMiniaturized]);
 
   // Native minimize, Restore.
-  base::scoped_nsobject<WindowedNSNotificationObserver> miniaturizationObserver(
+  WindowedNSNotificationObserver* miniaturizationObserver =
       [[WindowedNSNotificationObserver alloc]
           initForNotification:NSWindowDidMiniaturizeNotification
-                       object:ns_window]);
+                       object:ns_window];
   [ns_window miniaturize:nil];
   [miniaturizationObserver wait];
   EXPECT_NSEQ(initial_frame, [ns_window frame]);
@@ -276,10 +280,10 @@
   EXPECT_TRUE(window->IsMinimized());
   EXPECT_TRUE([ns_window isMiniaturized]);
 
-  base::scoped_nsobject<WindowedNSNotificationObserver>
-      deminiaturizationObserver([[WindowedNSNotificationObserver alloc]
+  WindowedNSNotificationObserver* deminiaturizationObserver =
+      [[WindowedNSNotificationObserver alloc]
           initForNotification:NSWindowDidDeminiaturizeNotification
-                       object:ns_window]);
+                       object:ns_window];
   [ns_window deminiaturize:nil];
   [deminiaturizationObserver wait];
   EXPECT_NSEQ(initial_frame, [ns_window frame]);
@@ -293,7 +297,6 @@
   AppWindow* app_window = GetFirstAppWindow();
   extensions::NativeAppWindow* window = app_window->GetBaseWindow();
   NSWindow* ns_window = app_window->GetNativeWindow().GetNativeNSWindow();
-  base::scoped_nsobject<WindowedNSNotificationObserver> watcher;
 
   gfx::Rect initial_restored_bounds = window->GetRestoredBounds();
   NSRect initial_frame = [ns_window frame];
@@ -302,18 +305,19 @@
   EXPECT_FALSE(window->IsMaximized());
 
   // Native maximize, Restore.
-  watcher.reset([[WindowedNSNotificationObserver alloc]
-      initForNotification:NSWindowDidResizeNotification
-                   object:ns_window]);
+  WindowedNSNotificationObserver* watcher =
+      [[WindowedNSNotificationObserver alloc]
+          initForNotification:NSWindowDidResizeNotification
+                       object:ns_window];
   [ns_window zoom:nil];
   [watcher wait];
   EXPECT_EQ(initial_restored_bounds, window->GetRestoredBounds());
   EXPECT_NSEQ(maximized_frame, [ns_window frame]);
   EXPECT_TRUE(window->IsMaximized());
 
-  watcher.reset([[WindowedNSNotificationObserver alloc]
+  watcher = [[WindowedNSNotificationObserver alloc]
       initForNotification:NSWindowDidResizeNotification
-                   object:ns_window]);
+                   object:ns_window];
   app_window->Restore();
   [watcher wait];
   EXPECT_EQ(initial_restored_bounds, window->GetRestoredBounds());
@@ -321,18 +325,18 @@
   EXPECT_FALSE(window->IsMaximized());
 
   // Maximize, native restore.
-  watcher.reset([[WindowedNSNotificationObserver alloc]
+  watcher = [[WindowedNSNotificationObserver alloc]
       initForNotification:NSWindowDidResizeNotification
-                   object:ns_window]);
+                   object:ns_window];
   app_window->Maximize();
   [watcher wait];
   EXPECT_EQ(initial_restored_bounds, window->GetRestoredBounds());
   EXPECT_NSEQ(maximized_frame, [ns_window frame]);
   EXPECT_TRUE(window->IsMaximized());
 
-  watcher.reset([[WindowedNSNotificationObserver alloc]
+  watcher = [[WindowedNSNotificationObserver alloc]
       initForNotification:NSWindowDidResizeNotification
-                   object:ns_window]);
+                   object:ns_window];
   [ns_window zoom:nil];
   [watcher wait];
   EXPECT_EQ(initial_restored_bounds, window->GetRestoredBounds());
@@ -349,7 +353,6 @@
       "{\"outerBounds\": {\"maxWidth\":200, \"maxHeight\":300}}");
   extensions::NativeAppWindow* window = app_window->GetBaseWindow();
   NSWindow* ns_window = app_window->GetNativeWindow().GetNativeNSWindow();
-  base::scoped_nsobject<WindowedNSNotificationObserver> watcher;
 
   gfx::Rect initial_restored_bounds = window->GetRestoredBounds();
   NSRect initial_frame = [ns_window frame];
@@ -358,18 +361,19 @@
   EXPECT_FALSE(window->IsMaximized());
 
   // Maximize, Restore.
-  watcher.reset([[WindowedNSNotificationObserver alloc]
-      initForNotification:NSWindowDidResizeNotification
-                   object:ns_window]);
+  WindowedNSNotificationObserver* watcher =
+      [[WindowedNSNotificationObserver alloc]
+          initForNotification:NSWindowDidResizeNotification
+                       object:ns_window];
   app_window->Maximize();
   [watcher wait];
   EXPECT_EQ(initial_restored_bounds, window->GetRestoredBounds());
   EXPECT_NSEQ(maximized_frame, [ns_window frame]);
   EXPECT_TRUE(window->IsMaximized());
 
-  watcher.reset([[WindowedNSNotificationObserver alloc]
+  watcher = [[WindowedNSNotificationObserver alloc]
       initForNotification:NSWindowDidResizeNotification
-                   object:ns_window]);
+                   object:ns_window];
   app_window->Restore();
   [watcher wait];
   EXPECT_EQ(initial_restored_bounds, window->GetRestoredBounds());
@@ -383,7 +387,6 @@
   AppWindow* app_window = GetFirstAppWindow();
   extensions::NativeAppWindow* window = app_window->GetBaseWindow();
   NSWindow* ns_window = app_window->GetNativeWindow().GetNativeNSWindow();
-  base::scoped_nsobject<WindowedNSNotificationObserver> watcher;
 
   NSRect initial_frame = [ns_window frame];
   NSRect maximized_frame = [[ns_window screen] visibleFrame];
@@ -393,9 +396,10 @@
   EXPECT_FALSE([ns_window isMiniaturized]);
 
   // Maximize, Minimize, Restore.
-  watcher.reset([[WindowedNSNotificationObserver alloc]
-      initForNotification:NSWindowDidResizeNotification
-                   object:ns_window]);
+  WindowedNSNotificationObserver* watcher =
+      [[WindowedNSNotificationObserver alloc]
+          initForNotification:NSWindowDidResizeNotification
+                       object:ns_window];
   app_window->Maximize();
   [watcher wait];
   EXPECT_NSEQ(maximized_frame, [ns_window frame]);
@@ -438,10 +442,10 @@
   EXPECT_TRUE(window->IsMinimized());
   EXPECT_TRUE([ns_window isMiniaturized]);
 
-  base::scoped_nsobject<WindowedNSNotificationObserver>
-      deminiaturizationObserver([[WindowedNSNotificationObserver alloc]
+  WindowedNSNotificationObserver* deminiaturizationObserver =
+      [[WindowedNSNotificationObserver alloc]
           initForNotification:NSWindowDidDeminiaturizeNotification
-                       object:ns_window]);
+                       object:ns_window];
   app_window->Maximize();
   [deminiaturizationObserver wait];
   EXPECT_TRUE([ns_window isVisible]);
@@ -459,7 +463,6 @@
   AppWindow* app_window = GetFirstAppWindow();
   extensions::NativeAppWindow* window = app_window->GetBaseWindow();
   NSWindow* ns_window = app_window->GetNativeWindow().GetNativeNSWindow();
-  base::scoped_nsobject<WindowedNSNotificationObserver> watcher;
   ui::NSWindowFullscreenNotificationWaiter waiter(
       app_window->GetNativeWindow());
 
@@ -470,9 +473,10 @@
   EXPECT_FALSE(window->IsFullscreen());
 
   // Maximize, Fullscreen, Restore, Restore.
-  watcher.reset([[WindowedNSNotificationObserver alloc]
-      initForNotification:NSWindowDidResizeNotification
-                   object:ns_window]);
+  WindowedNSNotificationObserver* watcher =
+      [[WindowedNSNotificationObserver alloc]
+          initForNotification:NSWindowDidResizeNotification
+                       object:ns_window];
   app_window->Maximize();
   [watcher wait];
   EXPECT_NSEQ(maximized_frame, [ns_window frame]);
diff --git a/chrome/browser/ui/cocoa/browser_window_mac_browsertest.mm b/chrome/browser/ui/cocoa/browser_window_mac_browsertest.mm
index 6eb919c0..af29ba46 100644
--- a/chrome/browser/ui/cocoa/browser_window_mac_browsertest.mm
+++ b/chrome/browser/ui/cocoa/browser_window_mac_browsertest.mm
@@ -8,7 +8,6 @@
 
 #import <Cocoa/Cocoa.h>
 
-#import "base/mac/scoped_nsobject.h"
 #include "chrome/app/chrome_command_ids.h"
 #include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/lifetime/application_lifetime_desktop.h"
@@ -21,10 +20,14 @@
 #import "ui/base/cocoa/window_size_constants.h"
 #include "ui/base/test/ns_ax_tree_validator.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 // Test harness for Mac-specific behaviors of BrowserWindow.
 class BrowserWindowMacTest : public InProcessBrowserTest {
  public:
-  BrowserWindowMacTest() {}
+  BrowserWindowMacTest() = default;
 
   BrowserWindowMacTest(const BrowserWindowMacTest&) = delete;
   BrowserWindowMacTest& operator=(const BrowserWindowMacTest&) = delete;
@@ -34,16 +37,13 @@
 // that is destroyed.
 IN_PROC_BROWSER_TEST_F(BrowserWindowMacTest, MenuCommandsAfterDestroy) {
   // Simulate AppKit (e.g. NSMenu) retaining an NSWindow.
-  base::scoped_nsobject<NSWindow> window(
-      browser()->window()->GetNativeWindow().GetNativeNSWindow(),
-      base::scoped_policy::RETAIN);
-  base::scoped_nsobject<NSMenuItem> bookmark_menu_item(
+  NSWindow* window = browser()->window()->GetNativeWindow().GetNativeNSWindow();
+  NSMenuItem* bookmark_menu_item =
       [[[[NSApp mainMenu] itemWithTag:IDC_BOOKMARKS_MENU] submenu]
-          itemWithTag:IDC_BOOKMARK_THIS_TAB],
-      base::scoped_policy::RETAIN);
+          itemWithTag:IDC_BOOKMARK_THIS_TAB];
 
-  EXPECT_TRUE(window.get());
-  EXPECT_TRUE(bookmark_menu_item.get());
+  EXPECT_TRUE(window);
+  EXPECT_TRUE(bookmark_menu_item);
 
   chrome::CloseAllBrowsersAndQuit();
   ui_test_utils::WaitForBrowserToClose();
@@ -67,12 +67,12 @@
           browser()->window()->GetNativeWindow().GetNativeNSWindow());
 
   // Create a child window.
-  base::scoped_nsobject<NativeWidgetMacNSWindow> child_window(
-      [[NativeWidgetMacNSWindow alloc]
-          initWithContentRect:ui::kWindowSizeDeterminedLater
-                    styleMask:NSWindowStyleMaskBorderless
-                      backing:NSBackingStoreBuffered
-                        defer:NO]);
+  NativeWidgetMacNSWindow* child_window = [[NativeWidgetMacNSWindow alloc]
+      initWithContentRect:ui::kWindowSizeDeterminedLater
+                styleMask:NSWindowStyleMaskBorderless
+                  backing:NSBackingStoreBuffered
+                    defer:NO];
+  child_window.releasedWhenClosed = NO;
   [window addChildWindow:child_window ordered:NSWindowAbove];
 
   NSMenuItem* show_bookmark_bar_menu_item =
diff --git a/chrome/browser/ui/cocoa/color_panel_cocoa_unittest.mm b/chrome/browser/ui/cocoa/color_panel_cocoa_unittest.mm
index d6788ea..eb684af 100644
--- a/chrome/browser/ui/cocoa/color_panel_cocoa_unittest.mm
+++ b/chrome/browser/ui/cocoa/color_panel_cocoa_unittest.mm
@@ -13,6 +13,10 @@
 #include "testing/gtest_mac.h"
 #include "testing/platform_test.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 @interface NSColorPanel (Private)
 // Private method returning the NSColorPanel's target.
 - (id)__target;
@@ -27,14 +31,14 @@
     // window list to include it. The NSColorPanel cannot be dealloced, so
     // without this step the tests will fail complaining that not all
     // windows were closed.
-    [[NSColorPanel sharedColorPanel] makeKeyAndOrderFront:nil];
+    [NSColorPanel.sharedColorPanel makeKeyAndOrderFront:nil];
     MarkCurrentWindowsAsInitial();
   }
   base::test::TaskEnvironment task_environment_;
 };
 
 TEST_F(ColorPanelCocoaTest, ClearTargetOnEnd) {
-  NSColorPanel* nscolor_panel = [NSColorPanel sharedColorPanel];
+  NSColorPanel* nscolor_panel = NSColorPanel.sharedColorPanel;
   @autoreleasepool {
     ASSERT_TRUE([nscolor_panel respondsToSelector:@selector(__target)]);
     EXPECT_FALSE([nscolor_panel __target]);
@@ -57,10 +61,10 @@
 
 TEST_F(ColorPanelCocoaTest, SetColor) {
   // Set the NSColor panel up with an initial color.
-  NSColor* blue_color = [NSColor blueColor];
-  NSColorPanel* nscolor_panel = [NSColorPanel sharedColorPanel];
-  [nscolor_panel setColor:blue_color];
-  EXPECT_TRUE([[nscolor_panel color] isEqual:blue_color]);
+  NSColor* blue_color = NSColor.blueColor;
+  NSColorPanel* nscolor_panel = NSColorPanel.sharedColorPanel;
+  nscolor_panel.color = blue_color;
+  EXPECT_TRUE([nscolor_panel.color isEqual:blue_color]);
 
   // Create a ColorChooserMac and confirm the NSColorPanel gets its initial
   // color.
@@ -70,8 +74,7 @@
       nullptr, SK_ColorBLACK, run_loop_create.QuitClosure());
   run_loop_create.Run();
 
-  EXPECT_NSEQ([nscolor_panel color],
-              skia::SkColorToDeviceNSColor(initial_color));
+  EXPECT_NSEQ(nscolor_panel.color, skia::SkColorToDeviceNSColor(initial_color));
 
   // Confirm that -[ColorPanelCocoa setColor:] sets the NSColorPanel's color.
   SkColor test_color = SK_ColorRED;
@@ -79,7 +82,7 @@
   color_chooser_mac->SetSelectedColor(test_color, run_loop_set.QuitClosure());
   run_loop_set.Run();
 
-  EXPECT_NSEQ([nscolor_panel color], skia::SkColorToDeviceNSColor(test_color));
+  EXPECT_NSEQ(nscolor_panel.color, skia::SkColorToDeviceNSColor(test_color));
 
   // Clean up.
   color_chooser_mac->End();
diff --git a/chrome/browser/ui/cocoa/first_run_dialog_controller_unittest.mm b/chrome/browser/ui/cocoa/first_run_dialog_controller_unittest.mm
index 5aa1b04..2178da5 100644
--- a/chrome/browser/ui/cocoa/first_run_dialog_controller_unittest.mm
+++ b/chrome/browser/ui/cocoa/first_run_dialog_controller_unittest.mm
@@ -3,47 +3,52 @@
 // found in the LICENSE file.
 
 #include "chrome/browser/ui/cocoa/first_run_dialog_controller.h"
+
 #include "base/command_line.h"
-#include "base/mac/scoped_nsobject.h"
 #include "chrome/browser/ui/cocoa/test/cocoa_test_helper.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/base/resource/resource_bundle.h"
 #include "ui/base/test/view_tree_validator.h"
 #include "ui/base/ui_base_switches.h"
 
-using FirstRunDialogControllerTest = CocoaTest;
-using TestController = base::scoped_nsobject<FirstRunDialogViewController>;
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
 
-TestController MakeTestController(BOOL stats) {
-  return TestController([[FirstRunDialogViewController alloc]
-      initWithStatsCheckboxInitiallyChecked:stats]);
+using FirstRunDialogControllerTest = CocoaTest;
+
+FirstRunDialogViewController* MakeTestController(BOOL stats) {
+  return [[FirstRunDialogViewController alloc]
+      initWithStatsCheckboxInitiallyChecked:stats];
 }
 
 NSView* FindBrowserButton(NSView* view) {
   for (NSView* subview : [view subviews]) {
-    if (![subview isKindOfClass:[NSButton class]])
+    if (![subview isKindOfClass:[NSButton class]]) {
       continue;
+    }
     NSString* title = [(NSButton*)subview title];
-    if ([title rangeOfString:@"browser"].location != NSNotFound)
+    if ([title rangeOfString:@"browser"].location != NSNotFound) {
       return subview;
+    }
   }
   return nil;
 }
 
 TEST(FirstRunDialogControllerTest, SetStatsDefault) {
-  TestController controller(MakeTestController(YES));
+  FirstRunDialogViewController* controller = MakeTestController(YES);
   [controller view];  // Make sure view is actually loaded.
   EXPECT_TRUE([controller isStatsReportingEnabled]);
 }
 
 TEST(FirstRunDialogControllerTest, MakeDefaultBrowserDefault) {
-  TestController controller(MakeTestController(YES));
+  FirstRunDialogViewController* controller = MakeTestController(YES);
   [controller view];
   EXPECT_TRUE([controller isMakeDefaultBrowserEnabled]);
 }
 
 TEST(FirstRunDialogControllerTest, ShowBrowser) {
-  TestController controller(MakeTestController(YES));
+  FirstRunDialogViewController* controller = MakeTestController(YES);
   NSView* checkbox = FindBrowserButton([controller view]);
   EXPECT_FALSE(checkbox.hidden);
 }
@@ -52,12 +57,12 @@
   // It's necessary to call |view| on the controller before mangling the
   // strings, since otherwise the controller will lazily construct its view,
   // which might happen after the call to |set_mangle_localized_strings|.
-  TestController defaultController(MakeTestController(YES));
+  FirstRunDialogViewController* defaultController = MakeTestController(YES);
   NSView* defaultView = [defaultController view];
 
   ui::ResourceBundle::GetSharedInstance().set_mangle_localized_strings_for_test(
       true);
-  TestController longController(MakeTestController(YES));
+  FirstRunDialogViewController* longController = MakeTestController(YES);
   NSView* longView = [longController view];
 
   // Ensure that the mangled strings actually do change the height!
diff --git a/chrome/browser/ui/cocoa/renderer_context_menu/render_view_context_menu_mac_cocoa_browsertest.mm b/chrome/browser/ui/cocoa/renderer_context_menu/render_view_context_menu_mac_cocoa_browsertest.mm
index 281018e..7d461ca0 100644
--- a/chrome/browser/ui/cocoa/renderer_context_menu/render_view_context_menu_mac_cocoa_browsertest.mm
+++ b/chrome/browser/ui/cocoa/renderer_context_menu/render_view_context_menu_mac_cocoa_browsertest.mm
@@ -13,9 +13,13 @@
 #include "content/public/test/test_utils.h"
 #import "testing/gtest_mac.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 class RenderViewContextMenuMacCocoaBrowserTest : public InProcessBrowserTest {
  public:
-  RenderViewContextMenuMacCocoaBrowserTest() {}
+  RenderViewContextMenuMacCocoaBrowserTest() = default;
 
   RenderViewContextMenuMacCocoaBrowserTest(
       const RenderViewContextMenuMacCocoaBrowserTest&) = delete;
@@ -24,29 +28,29 @@
 
  protected:
   void SetUpOnMainThread() override {
-    filteredItems_.reset([[NSMutableArray alloc] init]);
+    filtered_items_ = [[NSMutableArray alloc] init];
     [ChromeSwizzleServicesMenuUpdater
-        storeFilteredEntriesForTestingInArray:filteredItems_];
+        storeFilteredEntriesForTestingInArray:filtered_items_];
 
     // Add a textfield, which we'll use to present a contextual menu for
     // testing. Fill it with a URL, as the services that need to be filtered
     // primarily appear for URLs.
-    textField_.reset(
-        [[NSTextField alloc] initWithFrame:NSMakeRect(20, 20, 100, 20)]);
-    [textField_ setStringValue:@"http://someurl.com/"];
+    text_field_ =
+        [[NSTextField alloc] initWithFrame:NSMakeRect(20, 20, 100, 20)];
+    [text_field_ setStringValue:@"http://someurl.com/"];
     NSWindow* window =
         browser()->window()->GetNativeWindow().GetNativeNSWindow();
-    [[window contentView] addSubview:textField_];
+    [[window contentView] addSubview:text_field_];
   }
 
   void TearDownOnMainThread() override {
-    [textField_ removeFromSuperview];
+    [text_field_ removeFromSuperview];
     [ChromeSwizzleServicesMenuUpdater
         storeFilteredEntriesForTestingInArray:nil];
   }
 
-  base::scoped_nsobject<NSMutableArray> filteredItems_;
-  base::scoped_nsobject<NSTextField> textField_;
+  NSMutableArray* __strong filtered_items_;
+  NSTextField* __strong text_field_;
 };
 
 // Confirm that the private classes used to filter Safari's redundant Services
@@ -75,13 +79,12 @@
   // the application Services menu). So to test, we just need a control with a
   // bit of selected text.
   NSWindow* window = browser()->window()->GetNativeWindow().GetNativeNSWindow();
-  [window makeFirstResponder:textField_];
-  [textField_ selectText:nil];
+  [window makeFirstResponder:text_field_];
+  [text_field_ selectText:nil];
 
   // Create a contextual menu.
-  base::scoped_nsobject<NSMenu> popupMenu(
-      [[NSMenu alloc] initWithTitle:@"menu"]);
-  [popupMenu addItemWithTitle:@"Menu Item" action:0 keyEquivalent:@""];
+  NSMenu* popupMenu = [[NSMenu alloc] initWithTitle:@"menu"];
+  [popupMenu addItemWithTitle:@"Menu Item" action:nullptr keyEquivalent:@""];
 
   // Arrange to dismiss the contextual menu in the future (to break out of the
   // upcoming modal loop).
@@ -101,7 +104,7 @@
   bool was_safari_item_removed = false;
   bool was_open_url_item_removed = false;
 
-  for (id item in filteredItems_.get()) {
+  for (id item in filtered_items_) {
     if ([[item valueForKey:@"bundleIdentifier"]
             isEqualToString:@"com.apple.Safari"]) {
       was_safari_item_removed = true;
diff --git a/chrome/browser/ui/cocoa/task_manager_mac_browsertest.mm b/chrome/browser/ui/cocoa/task_manager_mac_browsertest.mm
index 0916a11f..107f9d7 100644
--- a/chrome/browser/ui/cocoa/task_manager_mac_browsertest.mm
+++ b/chrome/browser/ui/cocoa/task_manager_mac_browsertest.mm
@@ -36,6 +36,10 @@
 #include "ui/base/test/ui_controls.h"
 #include "url/gurl.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 namespace task_manager {
 
 using browsertest_util::WaitForTaskManagerRows;
diff --git a/chrome/browser/ui/color/chrome_color_id.h b/chrome/browser/ui/color/chrome_color_id.h
index e545dc0..1936eff 100644
--- a/chrome/browser/ui/color/chrome_color_id.h
+++ b/chrome/browser/ui/color/chrome_color_id.h
@@ -56,6 +56,7 @@
   /* Download bubble colors. */\
   E_CPONLY(kColorDownloadBubbleInfoBackground) \
   E_CPONLY(kColorDownloadBubbleInfoIcon) \
+  E_CPONLY(kColorDownloadBubbleRowHover) \
   /* Download shelf colors. */ \
   E_CPONLY(kColorDownloadItemForeground) \
   E_CPONLY(kColorDownloadItemForegroundDangerous) \
diff --git a/chrome/browser/ui/color/chrome_color_mixer.cc b/chrome/browser/ui/color/chrome_color_mixer.cc
index dbf2b21..be3528c 100644
--- a/chrome/browser/ui/color/chrome_color_mixer.cc
+++ b/chrome/browser/ui/color/chrome_color_mixer.cc
@@ -262,7 +262,7 @@
   mixer[kColorExtensionMenuPinButtonIconDisabled] = ui::SetAlpha(
       kColorExtensionMenuPinButtonIcon, gfx::kDisabledControlAlpha);
   mixer[kColorExtensionsMenuHighlightedBackground] = {
-      kColorToolbarBackgroundSubtleEmphasis};
+      ui::kColorSysNeutralContainer};
   mixer[kColorExtensionsToolbarControlsBackground] = {
       kColorToolbarBackgroundSubtleEmphasis};
   mixer[kColorEyedropperBoundary] = {SK_ColorDKGRAY};
diff --git a/chrome/browser/ui/color/material_chrome_color_mixer.cc b/chrome/browser/ui/color/material_chrome_color_mixer.cc
index 83eb0820..bf1ce46 100644
--- a/chrome/browser/ui/color/material_chrome_color_mixer.cc
+++ b/chrome/browser/ui/color/material_chrome_color_mixer.cc
@@ -80,6 +80,7 @@
   mixer[kColorBookmarkDragImageBackground] = {ui::kColorSysPrimary};
   mixer[kColorBookmarkFolderIcon] = {kColorBookmarkBarForeground};
   mixer[kColorCapturedTabContentsBorder] = {ui::kColorSysPrimary};
+  mixer[kColorDownloadBubbleRowHover] = {ui::kColorSysStateHoverOnSubtle};
   mixer[kColorDownloadItemForegroundDisabled] = BlendForMinContrast(
       ui::GetResultingPaintColor(ui::kColorSysStateDisabled,
                                  kColorDownloadShelfBackground),
diff --git a/chrome/browser/ui/extensions/extensions_container.h b/chrome/browser/ui/extensions/extensions_container.h
index 992c8be0..431903d 100644
--- a/chrome/browser/ui/extensions/extensions_container.h
+++ b/chrome/browser/ui/extensions/extensions_container.h
@@ -81,6 +81,11 @@
   virtual void UpdateToolbarActionHoverCard(
       ToolbarActionView* action_view,
       ToolbarActionHoverCardUpdateType update_type) = 0;
+
+  // Collapses the confirmation on the request access button, effectively
+  // hiding the button. Does nothing if the confirmation is not showing
+  // anymore.
+  virtual void CollapseConfirmation() = 0;
 };
 
 #endif  // CHROME_BROWSER_UI_EXTENSIONS_EXTENSIONS_CONTAINER_H_
diff --git a/chrome/browser/ui/find_bar/find_bar_platform_helper_mac_browsertest.mm b/chrome/browser/ui/find_bar/find_bar_platform_helper_mac_browsertest.mm
index 07ad9e1..0ae2a33 100644
--- a/chrome/browser/ui/find_bar/find_bar_platform_helper_mac_browsertest.mm
+++ b/chrome/browser/ui/find_bar/find_bar_platform_helper_mac_browsertest.mm
@@ -18,6 +18,10 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #import "ui/base/cocoa/find_pasteboard.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 const char kSimple[] = "simple.html";
 
 GURL GetURL(const std::string& filename) {
diff --git a/chrome/browser/ui/test/test_browser_dialog_mac.mm b/chrome/browser/ui/test/test_browser_dialog_mac.mm
index b06771b..b6b671f 100644
--- a/chrome/browser/ui/test/test_browser_dialog_mac.mm
+++ b/chrome/browser/ui/test/test_browser_dialog_mac.mm
@@ -6,10 +6,14 @@
 
 #import <Cocoa/Cocoa.h>
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 namespace internal {
 
 void TestBrowserDialogInteractiveSetUp() {
-  [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
+  NSApp.activationPolicy = NSApplicationActivationPolicyRegular;
   [NSApp activateIgnoringOtherApps:YES];
 }
 
diff --git a/chrome/browser/ui/views/download/bubble/download_bubble_row_view.cc b/chrome/browser/ui/views/download/bubble/download_bubble_row_view.cc
index 97cefea..cecae2f 100644
--- a/chrome/browser/ui/views/download/bubble/download_bubble_row_view.cc
+++ b/chrome/browser/ui/views/download/bubble/download_bubble_row_view.cc
@@ -391,8 +391,13 @@
   views::InkDrop::UseInkDropForFloodFillRipple(views::InkDrop::Get(this),
                                                /*highlight_on_hover=*/true,
                                                /*highlight_on_focus=*/true);
-  views::InkDrop::Get(this)->SetBaseColorId(views::style::GetColorId(
-      views::style::CONTEXT_BUTTON, views::style::STYLE_SECONDARY));
+  if (features::IsChromeRefresh2023()) {
+    views::InkDrop::Get(this)->SetBaseColorId(kColorDownloadBubbleRowHover);
+    views::InkDrop::Get(this)->SetHighlightOpacity(1.0f);
+  } else {
+    views::InkDrop::Get(this)->SetBaseColorId(views::style::GetColorId(
+        views::style::CONTEXT_BUTTON, views::style::STYLE_SECONDARY));
+  }
 
   const int icon_label_spacing = ChromeLayoutProvider::Get()->GetDistanceMetric(
       views::DISTANCE_RELATED_LABEL_HORIZONTAL);
diff --git a/chrome/browser/ui/views/extensions/extensions_request_access_button.cc b/chrome/browser/ui/views/extensions/extensions_request_access_button.cc
index b1f37cae..abc4fc49 100644
--- a/chrome/browser/ui/views/extensions/extensions_request_access_button.cc
+++ b/chrome/browser/ui/views/extensions/extensions_request_access_button.cc
@@ -27,6 +27,10 @@
 
 namespace {
 
+// TODO(crbug.com/1452171): Same as permission's ChipController. Pull out to a
+// shared location.
+constexpr auto kConfirmationDisplayDuration = base::Seconds(4);
+
 std::vector<const extensions::Extension*> GetExtensions(
     Profile* profile,
     std::vector<extensions::ExtensionId>& extension_ids) {
@@ -56,6 +60,8 @@
 
 void ExtensionsRequestAccessButton::Update(
     std::vector<extensions::ExtensionId>& extension_ids) {
+  CHECK(!IsShowingConfirmation());
+
   extension_ids_ = extension_ids;
   SetVisible(!extension_ids_.empty());
 
@@ -71,6 +77,7 @@
       l10n_util::GetStringFUTF16Int(IDS_EXTENSIONS_REQUEST_ACCESS_BUTTON,
                                     static_cast<int>(extension_ids_.size())),
       color);
+  SetEnabled(true);
 }
 
 // TODO(crbug.com/1390952): Remove hover card once
@@ -86,6 +93,31 @@
                                       extensions_container_, extension_ids_);
 }
 
+void ExtensionsRequestAccessButton::ResetConfirmation() {
+  SetVisible(false);
+  confirmation_origin_ = absl::nullopt;
+  collapse_timer_.AbandonAndStop();
+}
+
+bool ExtensionsRequestAccessButton::IsShowingConfirmation() const {
+  if (!confirmation_origin_.has_value()) {
+    return false;
+  }
+
+  CHECK(GetVisible());
+  return confirmation_origin_.has_value();
+}
+
+bool ExtensionsRequestAccessButton::IsShowingConfirmationFor(
+    const url::Origin& origin) const {
+  if (!confirmation_origin_.has_value()) {
+    return false;
+  }
+
+  CHECK(GetVisible());
+  return confirmation_origin_ == origin;
+}
+
 std::u16string ExtensionsRequestAccessButton::GetTooltipText(
     const gfx::Point& p) const {
   std::vector<std::u16string> tooltip_parts;
@@ -108,6 +140,13 @@
     return;
   }
 
+  // Make sure we set this before granting tab permissions, since that will
+  // trigger an update to the request access button for each extension that is
+  // granted access.
+  confirmation_origin_ =
+      web_contents->GetPrimaryMainFrame()->GetLastCommittedOrigin();
+
+  // Grant tab permission to all extensions.
   DCHECK_GT(extension_ids_.size(), 0u);
   std::vector<const extensions::Extension*> extensions_to_run =
       GetExtensions(browser_->profile(), extension_ids_);
@@ -115,6 +154,23 @@
   base::RecordAction(base::UserMetricsAction(
       "Extensions.Toolbar.ExtensionsActivatedFromRequestAccessButton"));
   action_runner->GrantTabPermissions(extensions_to_run);
+
+  // Show confirmation message, and disable the button, for a specific duration.
+  absl::optional<SkColor> color;
+  SetHighlight(l10n_util::GetStringUTF16(
+                   IDS_EXTENSIONS_REQUEST_ACCESS_BUTTON_DISMISSED_TEXT),
+               color);
+  SetEnabled(false);
+
+  base::TimeDelta collapse_duration = remove_confirmation_for_testing_
+                                          ? base::Seconds(0)
+                                          : kConfirmationDisplayDuration;
+  // base::Unretained() below is safe because this view is tied to the lifetime
+  // of `extensions_container_`.
+  collapse_timer_.Start(
+      FROM_HERE, collapse_duration,
+      base::BindOnce(&ExtensionsContainer::CollapseConfirmation,
+                     base::Unretained(extensions_container_)));
 }
 
 content::WebContents* ExtensionsRequestAccessButton::GetActiveWebContents()
diff --git a/chrome/browser/ui/views/extensions/extensions_request_access_button.h b/chrome/browser/ui/views/extensions/extensions_request_access_button.h
index ddf05b4..3c13a23 100644
--- a/chrome/browser/ui/views/extensions/extensions_request_access_button.h
+++ b/chrome/browser/ui/views/extensions/extensions_request_access_button.h
@@ -5,8 +5,11 @@
 #ifndef CHROME_BROWSER_UI_VIEWS_EXTENSIONS_EXTENSIONS_REQUEST_ACCESS_BUTTON_H_
 #define CHROME_BROWSER_UI_VIEWS_EXTENSIONS_EXTENSIONS_REQUEST_ACCESS_BUTTON_H_
 
+#include "base/timer/timer.h"
 #include "chrome/browser/ui/views/toolbar/toolbar_button.h"
 #include "extensions/common/extension_id.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+#include "url/origin.h"
 
 namespace content {
 class WebContents;
@@ -34,6 +37,15 @@
   // Displays the button's hover card, if possible.
   void MaybeShowHoverCard();
 
+  // Hides the button and resets the `confirmation_origin_` variable.
+  void ResetConfirmation();
+
+  // Returns whether the button is showing a confirmation message.
+  bool IsShowingConfirmation() const;
+
+  // Returns whether the button is showing a confirmation message for `origin`.
+  bool IsShowingConfirmationFor(const url::Origin& origin) const;
+
   // ToolbarButton:
   std::u16string GetTooltipText(const gfx::Point& p) const override;
 
@@ -45,9 +57,13 @@
   GetHoverCardCoordinatorForTesting() {
     return hover_card_coordinator_.get();
   }
+  void remove_confirmation_for_testing(bool remove_confirmation) {
+    remove_confirmation_for_testing_ = remove_confirmation;
+  }
 
  private:
-  // Runs `extension_ids_` actions in the current site.
+  // Grants one-time site access to `extension_ids` and shows a confirmation
+  // message on the button.
   void OnButtonPressed();
 
   content::WebContents* GetActiveWebContents() const;
@@ -60,6 +76,16 @@
 
   // Extensions included in the request access button.
   std::vector<extensions::ExtensionId> extension_ids_;
+
+  // The origin for which the button is displaying a confirmation message, if
+  // any.
+  absl::optional<url::Origin> confirmation_origin_;
+
+  // A timer used to collapse the button after showing a confirmation message.
+  base::OneShotTimer collapse_timer_;
+
+  // Flag to not show confirmation message in tests.
+  bool remove_confirmation_for_testing_{false};
 };
 
 #endif  // CHROME_BROWSER_UI_VIEWS_EXTENSIONS_EXTENSIONS_REQUEST_ACCESS_BUTTON_H_
diff --git a/chrome/browser/ui/views/extensions/extensions_toolbar_container.cc b/chrome/browser/ui/views/extensions/extensions_toolbar_container.cc
index 22b0904..3855d0d 100644
--- a/chrome/browser/ui/views/extensions/extensions_toolbar_container.cc
+++ b/chrome/browser/ui/views/extensions/extensions_toolbar_container.cc
@@ -512,6 +512,35 @@
 
   extensions::MaybeShowExtensionControlledNewTabPage(browser_,
                                                      selection.new_contents);
+
+  // Request access button confirmation is tab-specific. Therefore, we need to
+  // reset if the active tab changes.
+  if (extensions_controls_ && extensions_controls_->IsShowingConfirmation()) {
+    extensions_controls_->ResetConfirmation();
+    UpdateControlsVisibility();
+  }
+}
+
+void ExtensionsToolbarContainer::TabChangedAt(content::WebContents* contents,
+                                              int index,
+                                              TabChangeType change_type) {
+  // Ignore changes that don't affect all the tab contents (e.g loading
+  // changes).
+  if (change_type != TabChangeType::kAll) {
+    return;
+  }
+
+  // Request access button confirmation is tab-specific for a specific origin.
+  // Therefore, we need to reset it if it's currently showing, we are on the
+  // same tab and we have navigated to another origin.
+  // Note: When we switch tabs, `OnTabStripModelChanged` is called before
+  // `TabChangedAt` and takes care of resetting the confirmation if shown.
+  if (extensions_controls_ && extensions_controls_->IsShowingConfirmation() &&
+      !extensions_controls_->IsShowingConfirmationFor(
+          contents->GetPrimaryMainFrame()->GetLastCommittedOrigin())) {
+    extensions_controls_->ResetConfirmation();
+    UpdateControlsVisibility();
+  }
 }
 
 void ExtensionsToolbarContainer::OnToolbarActionAdded(
@@ -976,6 +1005,15 @@
   action_hover_card_controller_->UpdateHoverCard(action_view, update_type);
 }
 
+void ExtensionsToolbarContainer::CollapseConfirmation() {
+  if (!extensions_controls_->IsShowingConfirmation()) {
+    return;
+  }
+
+  extensions_controls_->ResetConfirmation();
+  UpdateControlsVisibility();
+}
+
 void ExtensionsToolbarContainer::OnMouseExited(const ui::MouseEvent& event) {
   UpdateToolbarActionHoverCard(nullptr,
                                ToolbarActionHoverCardUpdateType::kHover);
diff --git a/chrome/browser/ui/views/extensions/extensions_toolbar_container.h b/chrome/browser/ui/views/extensions/extensions_toolbar_container.h
index 65e2e62..0f45497 100644
--- a/chrome/browser/ui/views/extensions/extensions_toolbar_container.h
+++ b/chrome/browser/ui/views/extensions/extensions_toolbar_container.h
@@ -167,6 +167,7 @@
   void UpdateToolbarActionHoverCard(
       ToolbarActionView* action_view,
       ToolbarActionHoverCardUpdateType update_type) override;
+  void CollapseConfirmation() override;
 
   // ToolbarActionView::Delegate:
   content::WebContents* GetCurrentWebContents() override;
@@ -261,6 +262,9 @@
       TabStripModel* tab_strip_model,
       const TabStripModelChange& change,
       const TabStripSelectionChange& selection) override;
+  void TabChangedAt(content::WebContents* contents,
+                    int index,
+                    TabChangeType change_type) override;
 
   // ToolbarActionsModel::Observer:
   void OnToolbarActionAdded(
diff --git a/chrome/browser/ui/views/extensions/extensions_toolbar_container_interactive_uitest.cc b/chrome/browser/ui/views/extensions/extensions_toolbar_container_interactive_uitest.cc
index 6a3b7451..7aac0728 100644
--- a/chrome/browser/ui/views/extensions/extensions_toolbar_container_interactive_uitest.cc
+++ b/chrome/browser/ui/views/extensions/extensions_toolbar_container_interactive_uitest.cc
@@ -976,6 +976,10 @@
   EXPECT_EQ(permissions_manager->GetUserSiteAccess(*extensionC, url),
             UserSiteAccess::kOnAllSites);
 
+  // Don't show the confirmation since it's dependent on time, and we have other
+  // tests for it.
+  request_access_button()->remove_confirmation_for_testing(true);
+
   // Click the request access button to grant one-time access. A reload page
   // dialog will appear since extension A needs a page reload to run its action.
   auto* action_runner =
@@ -993,10 +997,11 @@
     EXPECT_TRUE(script_injection_listener.WaitUntilSatisfied());
     EXPECT_FALSE(request_access_button()->GetVisible());
   } else {
-    // Site interaction should change but script should not be injected
-    // since permission was granted but page was reloaded. The request access
-    // button should remain visible since we didn't reload.
-    EXPECT_TRUE(request_access_button()->GetVisible());
+    // Site interaction should change but script should not be injected since
+    // permission was granted but page was not reloaded. The request access
+    // button should be hidden, even without a reload, because the user granted
+    // access to the extensions.
+    EXPECT_FALSE(request_access_button()->GetVisible());
     // TODO(crbug.com/1400812): Is there a way to confirm we didn't inject the
     // script besides reusing the
     // chrome/test/data/extensions/blocked_actions/content_scripts/ test
@@ -1090,6 +1095,10 @@
   EXPECT_EQ(permissions_manager->GetUserSiteAccess(*extensionC, url),
             UserSiteAccess::kOnAllSites);
 
+  // Don't show the confirmation since it's dependent on time, and we have other
+  // tests for it.
+  request_access_button()->remove_confirmation_for_testing(true);
+
   // Click the request access button to grant one-time access. Since no
   // extensions need page refresh to run their actions, it immediately grants
   // access and the script is injected.
@@ -1133,3 +1142,55 @@
   EXPECT_EQ(permissions_helper.GetSiteInteraction(*extensionC, web_contents()),
             SiteInteraction::kGranted);
 }
+
+// Tests that when the user clicks on the request access button and immediately
+// navigates to a different site, the confirmation text is collapsed and the
+// button displays the extensions requesting access to the new site (if any).
+IN_PROC_BROWSER_TEST_F(
+    ExtensionsToolbarContainerFeatureUITest,
+    ClickingRequestAccessButton_ConfirmationCollapsedOnNavigation) {
+  auto extension = InstallExtensionWithHostPermissions(
+      "Extension", "<all_urls>", "document_idle");
+  extensions::ScriptingPermissionsModifier(profile(), extension)
+      .SetWithholdHostPermissions(true);
+
+  // Navigate to a site where the extension has withheld access.
+  GURL url = embedded_test_server()->GetURL("example.com", "/title1.html");
+  NavigateToUrl(url);
+
+  // Verify request access button is visible because extension is requesting
+  // site access.
+  EXPECT_TRUE(request_access_button()->GetVisible());
+  EXPECT_THAT(request_access_button()->GetExtensionIdsForTesting(),
+              testing::ElementsAre(extension->id()));
+
+  // Click the button to grant one-time access on example.com. Verify
+  // confirmation message appears on the request access button.
+  ClickButton(request_access_button());
+  EXPECT_TRUE(request_access_button()->GetVisible());
+  EXPECT_EQ(request_access_button()->GetText(),
+            l10n_util::GetStringUTF16(
+                IDS_EXTENSIONS_REQUEST_ACCESS_BUTTON_DISMISSED_TEXT));
+
+  // While the confirmation message is still visible, navigate to a site where
+  // the extension has withheld access. Verify the confirmation is not longer
+  // shown, and the button shows the extension requesting access.
+  NavigateToUrl(embedded_test_server()->GetURL("other.com", "/title1.html"));
+  EXPECT_TRUE(request_access_button()->GetVisible());
+  EXPECT_THAT(request_access_button()->GetExtensionIdsForTesting(),
+              testing::ElementsAre(extension->id()));
+
+  // Click the button to grant one-time access on example.com. Verify
+  // confirmation message appears on the request access button.
+  ClickButton(request_access_button());
+  EXPECT_TRUE(request_access_button()->GetVisible());
+  EXPECT_EQ(request_access_button()->GetText(),
+            l10n_util::GetStringUTF16(
+                IDS_EXTENSIONS_REQUEST_ACCESS_BUTTON_DISMISSED_TEXT));
+
+  // While the confirmation message is still visible, navigate to a site where
+  // no extensions are allowed while the confirmation message is still visible.
+  // Verify the confirmation is not longer shown and the button is hidden.
+  NavigateToUrl(GURL("chrome://extensions"));
+  EXPECT_FALSE(request_access_button()->GetVisible());
+}
diff --git a/chrome/browser/ui/views/extensions/extensions_toolbar_controls.cc b/chrome/browser/ui/views/extensions/extensions_toolbar_controls.cc
index 4c451c7..4c3766ed 100644
--- a/chrome/browser/ui/views/extensions/extensions_toolbar_controls.cc
+++ b/chrome/browser/ui/views/extensions/extensions_toolbar_controls.cc
@@ -81,6 +81,13 @@
     const std::vector<std::unique_ptr<ToolbarActionViewController>>& actions,
     extensions::PermissionsManager::UserSiteSetting site_setting,
     content::WebContents* web_contents) {
+  // Don't update the button if the confirmation message is currently showing;
+  // it'll go away after a few seconds. Once the confirmation is collapsed,
+  // button should be updated again.
+  if (request_access_button_->IsShowingConfirmation()) {
+    return;
+  }
+
   // Extensions are included in the request access button only when the site
   // allows customizing site access by extension, and when the extension
   // itself can show access requests in the toolbar and hasn't been dismissed.
@@ -101,5 +108,18 @@
   request_access_button_->Update(extensions);
 }
 
+void ExtensionsToolbarControls::ResetConfirmation() {
+  request_access_button_->ResetConfirmation();
+}
+
+bool ExtensionsToolbarControls::IsShowingConfirmation() const {
+  return request_access_button_->IsShowingConfirmation();
+}
+
+bool ExtensionsToolbarControls::IsShowingConfirmationFor(
+    const url::Origin& origin) const {
+  return request_access_button_->IsShowingConfirmationFor(origin);
+}
+
 BEGIN_METADATA(ExtensionsToolbarControls, ToolbarIconContainerView)
 END_METADATA
diff --git a/chrome/browser/ui/views/extensions/extensions_toolbar_controls.h b/chrome/browser/ui/views/extensions/extensions_toolbar_controls.h
index 260f82a..124c828 100644
--- a/chrome/browser/ui/views/extensions/extensions_toolbar_controls.h
+++ b/chrome/browser/ui/views/extensions/extensions_toolbar_controls.h
@@ -55,6 +55,15 @@
       extensions::PermissionsManager::UserSiteSetting site_setting,
       content::WebContents* web_contents);
 
+  // Hides the confirmation message in the request access button.
+  void ResetConfirmation();
+
+  // Returns whether the button is showing a confirmation message.
+  bool IsShowingConfirmation() const;
+
+  // Returns whether the button is showing a confirmation message for `origin`.
+  bool IsShowingConfirmationFor(const url::Origin& origin) const;
+
   // ToolbarIconContainerView:
   void UpdateAllIcons() override;
 
diff --git a/chrome/browser/ui/views/extensions/extensions_toolbar_controls_unittest.cc b/chrome/browser/ui/views/extensions/extensions_toolbar_controls_unittest.cc
index 65225ca..ff05ac1 100644
--- a/chrome/browser/ui/views/extensions/extensions_toolbar_controls_unittest.cc
+++ b/chrome/browser/ui/views/extensions/extensions_toolbar_controls_unittest.cc
@@ -20,8 +20,17 @@
 #include "extensions/test/permissions_manager_waiter.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/l10n/l10n_util.h"
 #include "url/origin.h"
 
+namespace {
+
+// TODO(crbug.com/1452171): Same as permission's ChipController. Pull out to a
+// shared location.
+base::TimeDelta kConfirmationDisplayDuration = base::Seconds(4);
+
+}  // namespace
+
 class ExtensionsToolbarControlsUnitTest : public ExtensionsToolbarUnitTest {
  public:
   ExtensionsToolbarControlsUnitTest();
@@ -48,7 +57,9 @@
   raw_ptr<content::WebContentsTester, DanglingUntriaged> web_contents_tester_;
 };
 
-ExtensionsToolbarControlsUnitTest::ExtensionsToolbarControlsUnitTest() {
+ExtensionsToolbarControlsUnitTest::ExtensionsToolbarControlsUnitTest()
+    : ExtensionsToolbarUnitTest(
+          base::test::TaskEnvironment::TimeSource::MOCK_TIME) {
   scoped_feature_list_.InitAndEnableFeature(
       extensions_features::kExtensionsMenuAccessControl);
 }
@@ -440,7 +451,7 @@
   base::UserActionTester user_action_tester;
   auto* permissions = extensions::PermissionsManager::Get(profile());
 
-  // Request access button is visible because extension A is requesting
+  // Request access button is visible because the extension is requesting
   // access.
   ASSERT_TRUE(request_access_button()->GetVisible());
   EXPECT_EQ(user_action_tester.GetActionCount(kActivatedUserAction), 0);
@@ -456,18 +467,86 @@
   WaitForAnimation();
   LayoutContainerIfNecessary();
 
-  // Verify request access button is hidden since extension executed its
-  // action. Extension's site access should have not changed, since clicking the
-  // button grants one time access.
-  ASSERT_FALSE(request_access_button()->GetVisible());
+  // Verify extension was executed and extensions menu button has "any
+  // extension  has access" state. Extension's site access should have not
+  // changed, since clicking the button grants one time access.
   EXPECT_EQ(user_action_tester.GetActionCount(kActivatedUserAction), 1);
+  EXPECT_EQ(extensions_button()->GetStateForTesting(),
+            ExtensionsToolbarButton::State::kAnyExtensionHasAccess);
   EXPECT_EQ(permissions->GetUserSiteAccess(*extension, url),
             extensions::PermissionsManager::UserSiteAccess::kOnClick);
 
-  // Verify extensions menu button has "any extension  has access" state, since
-  // the extension executed its action.
-  EXPECT_EQ(extensions_button()->GetStateForTesting(),
-            ExtensionsToolbarButton::State::kAnyExtensionHasAccess);
+  // Verify confirmation message appears on the request access button.
+  EXPECT_TRUE(request_access_button()->GetVisible());
+  EXPECT_EQ(request_access_button()->GetText(),
+            l10n_util::GetStringUTF16(
+                IDS_EXTENSIONS_REQUEST_ACCESS_BUTTON_DISMISSED_TEXT));
+
+  // Force the confirmation to be collapsed.
+  task_environment()->AdvanceClock(kConfirmationDisplayDuration);
+  base::RunLoop().RunUntilIdle();
+
+  // Verify the request access button is hidden.
+  ASSERT_FALSE(request_access_button()->GetVisible());
+}
+
+// Tests that if an update comes in between the request access button is clicked
+// and the confirmation is collapsed, the button is updated afterwards with the
+// correct information.
+TEST_F(ExtensionsToolbarControlsUnitTest,
+       RequestAccessButton_UpdateInBetweenClickAndConfirmationCollapse) {
+  auto extension_A =
+      InstallExtensionWithHostPermissions("Extension A", {"<all_urls>"});
+  auto extension_B =
+      InstallExtensionWithHostPermissions("Extension B", {"<all_urls>"});
+  auto extension_C =
+      InstallExtensionWithHostPermissions("Extension C", {"<all_urls>"});
+  WithholdHostPermissions(extension_A.get());
+  WithholdHostPermissions(extension_B.get());
+
+  const GURL url("http://www.example.com");
+  NavigateAndCommit(url);
+  LayoutContainerIfNecessary();
+
+  // Request access button is visible because extension A and B are requesting
+  // access.
+  EXPECT_TRUE(request_access_button()->GetVisible());
+  EXPECT_THAT(request_access_button()->GetExtensionIdsForTesting(),
+              testing::ElementsAre(extension_A->id(), extension_B->id()));
+
+  ClickButton(request_access_button());
+  WaitForAnimation();
+  LayoutContainerIfNecessary();
+
+  // Verify confirmation message appears on the request access button after
+  // clicking on it
+  EXPECT_TRUE(request_access_button()->GetVisible());
+  EXPECT_EQ(request_access_button()->GetText(),
+            l10n_util::GetStringUTF16(
+                IDS_EXTENSIONS_REQUEST_ACCESS_BUTTON_DISMISSED_TEXT));
+
+  // Update a different extension before the confirmation is collapsed.
+  content::WebContents* web_contents =
+      browser()->tab_strip_model()->GetActiveWebContents();
+  UpdateUserSiteAccess(
+      *extension_C, web_contents,
+      extensions::PermissionsManager::UserSiteAccess::kOnClick);
+
+  // Confirmation is still showing since collapse time hasn't elapsed.
+  EXPECT_TRUE(request_access_button()->GetVisible());
+  EXPECT_EQ(request_access_button()->GetText(),
+            l10n_util::GetStringUTF16(
+                IDS_EXTENSIONS_REQUEST_ACCESS_BUTTON_DISMISSED_TEXT));
+
+  // Force the confirmation to be collapsed.
+  task_environment()->AdvanceClock(kConfirmationDisplayDuration);
+  base::RunLoop().RunUntilIdle();
+
+  // Verify the request access button is visible since extension C is now
+  // requesting access.
+  EXPECT_TRUE(request_access_button()->GetVisible());
+  EXPECT_THAT(request_access_button()->GetExtensionIdsForTesting(),
+              testing::ElementsAre(extension_C->id()));
 }
 
 class ExtensionsToolbarControlsWithPermittedSitesUnitTest
diff --git a/chrome/browser/ui/views/extensions/extensions_toolbar_unittest.cc b/chrome/browser/ui/views/extensions/extensions_toolbar_unittest.cc
index aa8c5d6d..33a99f0 100644
--- a/chrome/browser/ui/views/extensions/extensions_toolbar_unittest.cc
+++ b/chrome/browser/ui/views/extensions/extensions_toolbar_unittest.cc
@@ -41,6 +41,10 @@
 
 }  // namespace
 
+ExtensionsToolbarUnitTest::ExtensionsToolbarUnitTest(
+    base::test::TaskEnvironment::TimeSource time_source)
+    : TestWithBrowserView(time_source) {}
+
 void ExtensionsToolbarUnitTest::SetUp() {
   TestWithBrowserView::SetUp();
 
diff --git a/chrome/browser/ui/views/extensions/extensions_toolbar_unittest.h b/chrome/browser/ui/views/extensions/extensions_toolbar_unittest.h
index b0595bc5..d2fcc350 100644
--- a/chrome/browser/ui/views/extensions/extensions_toolbar_unittest.h
+++ b/chrome/browser/ui/views/extensions/extensions_toolbar_unittest.h
@@ -26,6 +26,8 @@
 class ExtensionsToolbarUnitTest : public TestWithBrowserView {
  public:
   ExtensionsToolbarUnitTest() = default;
+  explicit ExtensionsToolbarUnitTest(
+      base::test::TaskEnvironment::TimeSource time_source);
   ~ExtensionsToolbarUnitTest() override = default;
   ExtensionsToolbarUnitTest(const ExtensionsToolbarUnitTest&) = delete;
   const ExtensionsToolbarUnitTest& operator=(const ExtensionsToolbarUnitTest&) =
diff --git a/chrome/browser/ui/views/frame/browser_non_client_frame_view_mac_browsertest.mm b/chrome/browser/ui/views/frame/browser_non_client_frame_view_mac_browsertest.mm
index d107517..f4c6eea 100644
--- a/chrome/browser/ui/views/frame/browser_non_client_frame_view_mac_browsertest.mm
+++ b/chrome/browser/ui/views/frame/browser_non_client_frame_view_mac_browsertest.mm
@@ -42,6 +42,10 @@
 #include "ui/views/window/non_client_view.h"
 #include "url/gurl.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 namespace {
 
 class TextChangeWaiter {
diff --git a/chrome/browser/ui/views/frame/immersive_mode_controller_mac_browsertest.mm b/chrome/browser/ui/views/frame/immersive_mode_controller_mac_browsertest.mm
index 56dfab1..61ea5ae 100644
--- a/chrome/browser/ui/views/frame/immersive_mode_controller_mac_browsertest.mm
+++ b/chrome/browser/ui/views/frame/immersive_mode_controller_mac_browsertest.mm
@@ -17,6 +17,10 @@
 #import "ui/views/cocoa/native_widget_mac_ns_window_host.h"
 #include "ui/views/widget/widget.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 class ImmersiveModeControllerMacBrowserTest : public InProcessBrowserTest {
  public:
   ImmersiveModeControllerMacBrowserTest() {
@@ -29,7 +33,7 @@
       const ImmersiveModeControllerMacBrowserTest&) = delete;
 
   NSView* GetMovedContentViewForWidget(views::Widget* overlay_widget) {
-    return (NSView*)overlay_widget->GetNativeWindowProperty(
+    return (__bridge NSView*)overlay_widget->GetNativeWindowProperty(
         views::NativeWidgetMacNSWindowHost::kMovedContentNSView);
   }
 
diff --git a/chrome/browser/ui/views/javascript_tab_modal_dialog_view_views_browsertest_mac.mm b/chrome/browser/ui/views/javascript_tab_modal_dialog_view_views_browsertest_mac.mm
index 15a691b..bbf5ba0 100644
--- a/chrome/browser/ui/views/javascript_tab_modal_dialog_view_views_browsertest_mac.mm
+++ b/chrome/browser/ui/views/javascript_tab_modal_dialog_view_views_browsertest_mac.mm
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #import <Accessibility/Accessibility.h>
+
 #include <string>
 
 #include "base/functional/bind.h"
@@ -14,6 +15,10 @@
 #include "ui/views/accessibility/view_accessibility.h"
 #include "ui/views/bubble/bubble_frame_view.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 using JavaScriptTabModalDialogViewViewsBrowserTestMac = InProcessBrowserTest;
 
 IN_PROC_BROWSER_TEST_F(JavaScriptTabModalDialogViewViewsBrowserTestMac,
diff --git a/chrome/browser/ui/views/status_bubble_views_browsertest_mac.mm b/chrome/browser/ui/views/status_bubble_views_browsertest_mac.mm
index aa4bcf0e..2291dac 100644
--- a/chrome/browser/ui/views/status_bubble_views_browsertest_mac.mm
+++ b/chrome/browser/ui/views/status_bubble_views_browsertest_mac.mm
@@ -6,10 +6,14 @@
 
 #import <Cocoa/Cocoa.h>
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 namespace test {
 
 float GetNativeWindowAlphaValue(gfx::NativeWindow window) {
-  return [window.GetNativeNSWindow() alphaValue];
+  return window.GetNativeNSWindow().alphaValue;
 }
 
 }  // namespace test
diff --git a/chrome/browser/ui/views/web_apps/web_app_integration_browsertest_cros.cc b/chrome/browser/ui/views/web_apps/web_app_integration_browsertest_cros.cc
index 7c747186..e47314f 100644
--- a/chrome/browser/ui/views/web_apps/web_app_integration_browsertest_cros.cc
+++ b/chrome/browser/ui/views/web_apps/web_app_integration_browsertest_cros.cc
@@ -442,9 +442,10 @@
   helper_.CheckAppNotInList(Site::kSubApp1);
 }
 
+// TODO(crbug.com/1454377): Re-enable once the bug is fixed.
 IN_PROC_BROWSER_TEST_F(
     WebAppIntegration,
-    WAI_32HasSubAppsWithShortcutWindowedWebApp_142HasSubApps_138HasSubAppsSubApp1UserAllow_12SubApp1_140HasSubAppsSubApp1_7SubApp1_138HasSubAppsSubApp2UserAllow_140HasSubAppsSubApp1_140HasSubAppsSubApp2) {
+    DISABLED_WAI_32HasSubAppsWithShortcutWindowedWebApp_142HasSubApps_138HasSubAppsSubApp1UserAllow_12SubApp1_140HasSubAppsSubApp1_7SubApp1_138HasSubAppsSubApp2UserAllow_140HasSubAppsSubApp1_140HasSubAppsSubApp2) {
   // Test contents are generated by script. Please do not modify!
   // See `docs/webapps/why-is-this-test-failing.md` or
   // `docs/webapps/integration-testing-framework` for more info.
@@ -502,9 +503,10 @@
   helper_.CheckAppNotInList(Site::kSubApp1);
 }
 
+// TODO(crbug.com/1454377): Re-enable once the bug is fixed.
 IN_PROC_BROWSER_TEST_F(
     WebAppIntegration,
-    WAI_32HasSubAppsWithShortcutBrowserWebApp_142HasSubApps_138HasSubAppsSubApp1UserAllow_12SubApp1_140HasSubAppsSubApp1_7SubApp1_138HasSubAppsSubApp2UserAllow_140HasSubAppsSubApp1_140HasSubAppsSubApp2) {
+    DISABLED_WAI_32HasSubAppsWithShortcutBrowserWebApp_142HasSubApps_138HasSubAppsSubApp1UserAllow_12SubApp1_140HasSubAppsSubApp1_7SubApp1_138HasSubAppsSubApp2UserAllow_140HasSubAppsSubApp1_140HasSubAppsSubApp2) {
   // Test contents are generated by script. Please do not modify!
   // See `docs/webapps/why-is-this-test-failing.md` or
   // `docs/webapps/integration-testing-framework` for more info.
@@ -562,9 +564,10 @@
   helper_.CheckAppNotInList(Site::kSubApp1);
 }
 
+// TODO(crbug.com/1454377): Re-enable once the bug is fixed.
 IN_PROC_BROWSER_TEST_F(
     WebAppIntegration,
-    WAI_32HasSubAppsNoShortcutWindowedWebApp_142HasSubApps_138HasSubAppsSubApp1UserAllow_12SubApp1_140HasSubAppsSubApp1_7SubApp1_138HasSubAppsSubApp2UserAllow_140HasSubAppsSubApp1_140HasSubAppsSubApp2) {
+    DISABLED_WAI_32HasSubAppsNoShortcutWindowedWebApp_142HasSubApps_138HasSubAppsSubApp1UserAllow_12SubApp1_140HasSubAppsSubApp1_7SubApp1_138HasSubAppsSubApp2UserAllow_140HasSubAppsSubApp1_140HasSubAppsSubApp2) {
   // Test contents are generated by script. Please do not modify!
   // See `docs/webapps/why-is-this-test-failing.md` or
   // `docs/webapps/integration-testing-framework` for more info.
@@ -622,9 +625,10 @@
   helper_.CheckAppNotInList(Site::kSubApp1);
 }
 
+// TODO(crbug.com/1454377): Re-enable once the bug is fixed.
 IN_PROC_BROWSER_TEST_F(
     WebAppIntegration,
-    WAI_32HasSubAppsNoShortcutBrowserWebApp_142HasSubApps_138HasSubAppsSubApp1UserAllow_12SubApp1_140HasSubAppsSubApp1_7SubApp1_138HasSubAppsSubApp2UserAllow_140HasSubAppsSubApp1_140HasSubAppsSubApp2) {
+    DISABLED_WAI_32HasSubAppsNoShortcutBrowserWebApp_142HasSubApps_138HasSubAppsSubApp1UserAllow_12SubApp1_140HasSubAppsSubApp1_7SubApp1_138HasSubAppsSubApp2UserAllow_140HasSubAppsSubApp1_140HasSubAppsSubApp2) {
   // Test contents are generated by script. Please do not modify!
   // See `docs/webapps/why-is-this-test-failing.md` or
   // `docs/webapps/integration-testing-framework` for more info.
diff --git a/chrome/browser/ui/webui/flags/flags_ui.cc b/chrome/browser/ui/webui/flags/flags_ui.cc
index a9c2518..f3cf3653 100644
--- a/chrome/browser/ui/webui/flags/flags_ui.cc
+++ b/chrome/browser/ui/webui/flags/flags_ui.cc
@@ -65,13 +65,12 @@
 content::WebUIDataSource* CreateAndAddFlagsUIHTMLSource(Profile* profile) {
   content::WebUIDataSource* source = content::WebUIDataSource::CreateAndAdd(
       profile, chrome::kChromeUIFlagsHost);
-  source->EnableReplaceI18nInJS();
   source->OverrideContentSecurityPolicy(
       network::mojom::CSPDirectiveName::ScriptSrc,
       "script-src chrome://resources 'self' 'unsafe-eval';");
   source->OverrideContentSecurityPolicy(
       network::mojom::CSPDirectiveName::TrustedTypes,
-      "trusted-types jstemplate static-types;");
+      "trusted-types jstemplate;");
   source->AddString(flags_ui::kVersion,
                     std::string(version_info::GetVersionNumber()));
 
diff --git a/chrome/browser/ui/webui/password_manager/password_manager_ui.cc b/chrome/browser/ui/webui/password_manager/password_manager_ui.cc
index d7d3544..abf18dca 100644
--- a/chrome/browser/ui/webui/password_manager/password_manager_ui.cc
+++ b/chrome/browser/ui/webui/password_manager/password_manager_ui.cc
@@ -140,6 +140,7 @@
     {"controlledByExtension", IDS_SETTINGS_CONTROLLED_BY_EXTENSION},
     {"copyPassword", IDS_PASSWORD_MANAGER_UI_COPY_PASSWORD},
     {"copyUsername", IDS_PASSWORD_MANAGER_UI_COPY_USERNAME},
+    {"delete", IDS_DELETE},
     {"deletePassword", IDS_DELETE},
     {"deletePasswordConfirmationDescription",
      IDS_PASSWORD_MANAGER_UI_DELETE_PASSWORD_CONFIRMATION_DESCRIPTION},
@@ -152,11 +153,16 @@
      IDS_PASSWORD_MANAGER_UI_DELETE_DIALOG_FROM_ACCOUNT_CHECKBOX_LABEL},
     {"deletePasswordDialogTitle", IDS_PASSWORD_MANAGER_UI_DELETE_DIALOG_TITLE},
     {"disable", IDS_DISABLE},
+    {"displayNameLabel", IDS_PASSWORD_MANAGER_UI_DISPLAY_NAME_LABEL},
+    {"displayNamePlaceholder",
+     IDS_PASSWORD_MANAGER_UI_DISPLAY_NAME_PLACEHOLDER},
     {"downloadFile", IDS_PASSWORD_MANAGER_UI_DOWNLOAD_FILE},
     {"downloadLinkShow", IDS_DOWNLOAD_LINK_SHOW},
+    {"edit", IDS_EDIT},
     {"editDisclaimerDescription",
      IDS_PASSWORD_MANAGER_UI_EDIT_DISCLAIMER_DESCRIPTION},
     {"editDisclaimerTitle", IDS_PASSWORD_MANAGER_UI_EDIT_DISCLAIMER_TITLE},
+    {"editPasskeyTitle", IDS_PASSWORD_MANAGER_UI_EDIT_PASSKEY},
     {"editPassword", IDS_EDIT},
     {"editPasswordFootnote", IDS_PASSWORD_MANAGER_UI_PASSWORD_EDIT_FOOTNOTE},
     {"editPasswordTitle", IDS_PASSWORD_MANAGER_UI_EDIT_PASSWORD},
@@ -297,6 +303,7 @@
     {"usernameCopiedToClipboard",
      IDS_PASSWORD_MANAGER_UI_USERNAME_COPIED_TO_CLIPBOARD},
     {"usernameLabel", IDS_PASSWORD_MANAGER_UI_USERNAME_LABEL},
+    {"usernamePlaceholder", IDS_PASSWORD_MANAGER_UI_USERNAME_PLACEHOLDER},
     {"viewExistingPassword", IDS_PASSWORD_MANAGER_UI_VIEW_EXISTING_PASSWORD},
     {"viewExistingPasswordAriaDescription",
      IDS_PASSWORD_MANAGER_UI_VIEW_EXISTING_PASSWORD_ARIA_DESCRIPTION},
diff --git a/chrome/browser/ui/webui/realbox/realbox_handler.cc b/chrome/browser/ui/webui/realbox/realbox_handler.cc
index 0300efe..bdb671d 100644
--- a/chrome/browser/ui/webui/realbox/realbox_handler.cc
+++ b/chrome/browser/ui/webui/realbox/realbox_handler.cc
@@ -790,6 +790,15 @@
   page_.Bind(std::move(pending_page));
 }
 
+void RealboxHandler::OnFocusChanged(bool focused) {
+  if (focused) {
+    edit_model()->OnSetFocus(false);
+  } else {
+    edit_model()->OnWillKillFocus();
+    edit_model()->OnKillFocus();
+  }
+}
+
 void RealboxHandler::QueryAutocomplete(const std::u16string& input,
                                        bool prevent_inline_autocomplete) {
   // TODO(tommycli): We use the input being empty as a signal we are requesting
@@ -826,16 +835,14 @@
   autocomplete_controller()->Stop(clear_result);
 }
 
-void RealboxHandler::OpenAutocompleteMatch(
-    uint8_t line,
-    const GURL& url,
-    bool are_matches_showing,
-    base::TimeDelta time_elapsed_since_last_focus,
-    uint8_t mouse_button,
-    bool alt_key,
-    bool ctrl_key,
-    bool meta_key,
-    bool shift_key) {
+void RealboxHandler::OpenAutocompleteMatch(uint8_t line,
+                                           const GURL& url,
+                                           bool are_matches_showing,
+                                           uint8_t mouse_button,
+                                           bool alt_key,
+                                           bool ctrl_key,
+                                           bool meta_key,
+                                           bool shift_key) {
   const AutocompleteMatch* match = GetMatchWithUrl(line, url);
   if (!match) {
     // This can happen due to asynchronous updates changing the result while
diff --git a/chrome/browser/ui/webui/realbox/realbox_handler.h b/chrome/browser/ui/webui/realbox/realbox_handler.h
index 067701e..3fa87482 100644
--- a/chrome/browser/ui/webui/realbox/realbox_handler.h
+++ b/chrome/browser/ui/webui/realbox/realbox_handler.h
@@ -66,13 +66,13 @@
 
   // omnibox::mojom::PageHandler:
   void SetPage(mojo::PendingRemote<omnibox::mojom::Page> pending_page) override;
+  void OnFocusChanged(bool focused) override;
   void QueryAutocomplete(const std::u16string& input,
                          bool prevent_inline_autocomplete) override;
   void StopAutocomplete(bool clear_result) override;
   void OpenAutocompleteMatch(uint8_t line,
                              const GURL& url,
                              bool are_matches_showing,
-                             base::TimeDelta time_elapsed_since_last_focus,
                              uint8_t mouse_button,
                              bool alt_key,
                              bool ctrl_key,
diff --git a/chrome/browser/ui/webui/settings/ash/app_management/app_management_uma.h b/chrome/browser/ui/webui/settings/ash/app_management/app_management_uma.h
index bfe5980..ef9f8c1e 100644
--- a/chrome/browser/ui/webui/settings/ash/app_management/app_management_uma.h
+++ b/chrome/browser/ui/webui/settings/ash/app_management/app_management_uma.h
@@ -28,7 +28,8 @@
   kNotificationPluginVm = 12,
   kAppManagementMainViewBorealis = 13,
   kPageInfoView = 14,
-  kMaxValue = kPageInfoView,
+  kPrivacyIndicatorsNotificationSettings = 15,
+  kMaxValue = kPrivacyIndicatorsNotificationSettings,
 };
 
 }  // namespace settings
diff --git a/chrome/browser/ui/webui/settings/ash/cups_printers_handler.cc b/chrome/browser/ui/webui/settings/ash/cups_printers_handler.cc
index bbb423c..dccf95e0 100644
--- a/chrome/browser/ui/webui/settings/ash/cups_printers_handler.cc
+++ b/chrome/browser/ui/webui/settings/ash/cups_printers_handler.cc
@@ -1501,6 +1501,10 @@
 void CupsPrintersHandler::OnPrinterStatusReceived(
     const std::string& callback_id,
     const chromeos::CupsPrinterStatus& printer_status) {
+  if (!IsJavascriptAllowed()) {
+    return;
+  }
+
   ResolveJavascriptCallback(base::Value(callback_id),
                             printer_status.ConvertToValue());
 }
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 928ff9f..2855c644 100644
--- a/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
+++ b/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
@@ -698,6 +698,8 @@
        IDS_SETTINGS_PERFORMANCE_TAB_DISCARDING_EXCEPTIONS_ADD_DIALOG_CURRENT_TABS},
       {"tabDiscardingExceptionsAddDialogManual",
        IDS_SETTINGS_PERFORMANCE_TAB_DISCARDING_EXCEPTIONS_ADD_DIALOG_MANUAL},
+      {"tabDiscardingExceptionsActiveSiteAriaDescription",
+       IDS_SETTINGS_PERFORMANCE_TAB_DISCARDING_EXCEPTIONS_ACTIVE_SITE_ARIA_DESCRIPTION},
   };
   html_source->AddLocalizedStrings(kLocalizedStrings);
 
diff --git a/chrome/browser/web_applications/policy/web_app_policy_manager_browsertest.cc b/chrome/browser/web_applications/policy/web_app_policy_manager_browsertest.cc
index 5da48c7..b37d6a44 100644
--- a/chrome/browser/web_applications/policy/web_app_policy_manager_browsertest.cc
+++ b/chrome/browser/web_applications/policy/web_app_policy_manager_browsertest.cc
@@ -172,6 +172,35 @@
   EXPECT_EQ(0u, manifest->icons.size());
 }
 
+// Ensure the manifest start_url is used as the manifest id when the manifest id
+// is not present.
+IN_PROC_BROWSER_TEST_F(WebAppPolicyManagerBrowserTest, AppIdWhenNoManifestId) {
+  WebAppProvider& provider = *WebAppProvider::GetForTest(profile());
+
+  base::test::TestFuture<void> future;
+  provider.policy_manager().SetOnAppsSynchronizedCompletedCallbackForTesting(
+      future.GetCallback());
+  const GURL install_url =
+      https_server()->GetURL("/web_apps/get_manifest.html?no_manifest_id.json");
+  profile()->GetPrefs()->SetList(
+      prefs::kWebAppInstallForceList,
+      base::Value::List().Append(
+          base::Value::Dict().Set(kUrlKey, install_url.spec())));
+  future.Get();
+
+  const GURL start_url = https_server()->GetURL("/web_apps/basic.html");
+  const AppId app_id = GenerateAppIdFromManifestId(
+      GenerateManifestIdFromStartUrlOnly(start_url));
+  const WebApp* app = provider.registrar_unsafe().GetAppById(app_id);
+
+  ASSERT_TRUE(app);
+  EXPECT_EQ(app->management_to_external_config_map(),
+            (WebApp::ExternalConfigMap{{WebAppManagement::Type::kPolicy,
+                                        {/*is_placeholder=*/false,
+                                         /*install_urls=*/{install_url},
+                                         /*additional_policy_ids=*/{}}}}));
+}
+
 // Scenario: App with install_url kInstallUrl has a start_url kStartUrl
 // specified in manifest. Next time we navigate to kStartUrl, but we still
 // need to override the manifest even though the policy key is kInstallUrl.
diff --git a/chrome/build/mac-arm.pgo.txt b/chrome/build/mac-arm.pgo.txt
index 47afc1d4..767b3eb 100644
--- a/chrome/build/mac-arm.pgo.txt
+++ b/chrome/build/mac-arm.pgo.txt
@@ -1 +1 @@
-chrome-mac-arm-main-1686664165-f131b4e75e5858a4c032a9e3e36ec414341756e3.profdata
+chrome-mac-arm-main-1686671915-f57d526d51a94ee5a559d6811ac713b0a4ae0f8e.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index 01c4918..ef1cc46 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1686657517-05553064cc9a41ef296e221b265c2362c4254216.profdata
+chrome-win32-main-1686668293-636d121caa311b2a70ca16e74db8bab74bc34891.profdata
diff --git a/chrome/common/mac/app_mode_chrome_locator_browsertest.mm b/chrome/common/mac/app_mode_chrome_locator_browsertest.mm
index ddc843b..64d1052 100644
--- a/chrome/common/mac/app_mode_chrome_locator_browsertest.mm
+++ b/chrome/common/mac/app_mode_chrome_locator_browsertest.mm
@@ -14,6 +14,10 @@
 #include "components/version_info/version_info.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 namespace {
 
 // This needs to be a browser test because it expects to find a Chrome.app
diff --git a/chrome/installer/util/auto_launch_util.cc b/chrome/installer/util/auto_launch_util.cc
index a0b26739..93320d0 100644
--- a/chrome/installer/util/auto_launch_util.cc
+++ b/chrome/installer/util/auto_launch_util.cc
@@ -10,8 +10,8 @@
 
 #include "base/command_line.h"
 #include "base/files/file_path.h"
-#include "base/notreached.h"
 #include "base/path_service.h"
+#include "base/strings/strcat.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/win/win_util.h"
@@ -24,15 +24,17 @@
 namespace {
 
 // The prefix of the Chrome Auto-launch key under the Run key.
-const wchar_t kAutolaunchKeyValue[] = L"GoogleChromeAutoLaunch";
+constexpr wchar_t kAutolaunchKeyValue[] = L"GoogleChromeAutoLaunch";
 
 // Builds a registry key name to use when deciding where to read/write the auto-
 // launch value to/from. It takes into account the path of the profile so that
-// different installations of Chrome don't conflict.
+// different installations of Chrome don't conflict. Returns an empty string if
+// the key name cannot be determined.
 std::wstring GetAutoLaunchKeyName() {
   base::FilePath path;
-  if (!base::PathService::Get(chrome::DIR_USER_DATA, &path))
-    NOTREACHED();
+  if (!base::PathService::Get(chrome::DIR_USER_DATA, &path)) {
+    return {};
+  }
   // Background auto-launch is only supported for the Default profile at the
   // moment, but keep the door opened to a multi-profile implementation by
   // encoding the Default profile in the hash.
@@ -41,8 +43,9 @@
   std::string input(path.AsUTF8Unsafe());
   uint8_t hash[16];
   crypto::SHA256HashString(input, hash, std::size(hash));
-  return std::wstring(kAutolaunchKeyValue) + L"_" +
-         base::ASCIIToWide(base::HexEncode(hash, std::size(hash)));
+  return base::StrCat(
+      {kAutolaunchKeyValue, L"_",
+       base::ASCIIToWide(base::HexEncode(hash, std::size(hash)))});
 }
 
 }  // namespace
@@ -51,20 +54,24 @@
 
 void EnableBackgroundStartAtLogin() {
   base::FilePath application_dir;
-  if (!base::PathService::Get(base::DIR_EXE, &application_dir))
-    NOTREACHED();
+  if (!base::PathService::Get(base::DIR_EXE, &application_dir)) {
+    return;
+  }
 
   base::CommandLine cmd_line(application_dir.Append(installer::kChromeExe));
   cmd_line.AppendSwitch(switches::kNoStartupWindow);
   cmd_line.AppendArg(switches::kPrefetchArgumentBrowserBackground);
 
-  base::win::AddCommandToAutoRun(HKEY_CURRENT_USER, GetAutoLaunchKeyName(),
-                                 cmd_line.GetCommandLineString());
+  if (auto key_name = GetAutoLaunchKeyName(); !key_name.empty()) {
+    base::win::AddCommandToAutoRun(HKEY_CURRENT_USER, key_name,
+                                   cmd_line.GetCommandLineString());
+  }
 }
 
 void DisableBackgroundStartAtLogin() {
-  base::win::RemoveCommandFromAutoRun(HKEY_CURRENT_USER,
-                                      GetAutoLaunchKeyName());
+  if (auto key_name = GetAutoLaunchKeyName(); !key_name.empty()) {
+    base::win::RemoveCommandFromAutoRun(HKEY_CURRENT_USER, key_name);
+  }
 }
 
 }  // namespace auto_launch_util
diff --git a/chrome/renderer/BUILD.gn b/chrome/renderer/BUILD.gn
index 80e1c2b..cc6c78974 100644
--- a/chrome/renderer/BUILD.gn
+++ b/chrome/renderer/BUILD.gn
@@ -66,6 +66,8 @@
     "chrome_render_frame_observer.h",
     "chrome_render_thread_observer.cc",
     "chrome_render_thread_observer.h",
+    "companion/visual_search/visual_search_eligibility.cc",
+    "companion/visual_search/visual_search_eligibility.h",
     "custom_menu_commands.h",
     "google_accounts_private_api_extension.cc",
     "google_accounts_private_api_extension.h",
@@ -140,6 +142,7 @@
     "//chrome/common",
     "//chrome/common:mojo_bindings",
     "//chrome/common/cart:mojo_bindings",
+    "//chrome/common/companion:proto",
     "//chrome/common/net",
     "//chrome/common/search:mojo_bindings",
     "//chrome/services/speech/buildflags",
@@ -226,6 +229,7 @@
     "//third_party/libwebp",
     "//third_party/re2",
     "//third_party/widevine/cdm:buildflags",
+    "//ui/gfx/geometry:geometry",
     "//ui/surface",
     "//v8",
   ]
diff --git a/chrome/renderer/companion/visual_search/visual_search_eligibility.cc b/chrome/renderer/companion/visual_search/visual_search_eligibility.cc
new file mode 100644
index 0000000..8d89c7a
--- /dev/null
+++ b/chrome/renderer/companion/visual_search/visual_search_eligibility.cc
@@ -0,0 +1,456 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/companion/visual_search/visual_search_eligibility.h"
+
+#include <algorithm>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/containers/flat_map.h"
+#include "base/notreached.h"
+
+namespace companion::visual_search {
+constexpr char kNormalizedPrefix[] = "normalized_";
+constexpr int kMaxNumStored = 200;
+
+EligibilityModule::EligibilityModule(const EligibilitySpec& spec)
+    : spec_(spec), have_run_first_pass_(false) {}
+
+EligibilityModule::~EligibilityModule() = default;
+
+std::vector<std::string>
+EligibilityModule::RunFirstPassEligibilityAndCacheFeatureValues(
+    const SizeF& viewport_image_size,
+    const std::vector<SingleImageGeometryFeatures>& images) {
+  Clear();
+  have_run_first_pass_ = true;
+  viewport_width_ = viewport_image_size.width();
+  viewport_height_ = viewport_image_size.height();
+  ComputeNormalizingFeatures(images);
+  std::vector<std::string> eligible_images;
+  int count = 0;
+  for (const SingleImageGeometryFeatures& image : images) {
+    // Ensure that we don't store features for too many images.
+    if (count++ > kMaxNumStored) {
+      break;
+    }
+
+    // First compute the features so that then we can evaluate the rules based
+    // on cached feature values.
+    ComputeFeaturesForOrOfThresholdingRules(spec_.cheap_pruning_rules(), image);
+    if (!IsEligible(spec_.cheap_pruning_rules(), image.image_identifier)) {
+      continue;
+    }
+    eligible_after_first_pass_.insert(image.image_identifier);
+    eligible_images.push_back(image.image_identifier);
+    ComputeFeaturesForOrOfThresholdingRules(spec_.classifier_score_rules(),
+                                            image);
+    ComputeFeaturesForOrOfThresholdingRules(spec_.post_renormalization_rules(),
+                                            image);
+  }
+
+  return eligible_images;
+}
+
+std::vector<std::string>
+EligibilityModule::RunSecondPassPostClassificationEligibility(
+    const base::flat_map<std::string, double>& shopping_classifier_scores,
+    const base::flat_map<std::string, double>& sensitivity_classifier_scores) {
+  CHECK(have_run_first_pass_);
+  have_run_first_pass_ = false;
+  // Cache the scores so that they can be looked up when computing the rules.
+  for (const auto& each_pair : shopping_classifier_scores) {
+    if (image_level_features_[each_pair.first].size() < kMaxNumStored) {
+      image_level_features_[each_pair.first]
+                           [FeatureLibrary::SHOPPING_CLASSIFIER_SCORE] =
+                               each_pair.second;
+    }
+  }
+  for (const auto& each_pair : sensitivity_classifier_scores) {
+    if (image_level_features_[each_pair.first].size() < kMaxNumStored) {
+      image_level_features_[each_pair.first]
+                           [FeatureLibrary::SENS_CLASSIFIER_SCORE] =
+                               each_pair.second;
+    }
+  }
+
+  for (const std::string& image_id : eligible_after_first_pass_) {
+    if (IsEligible(spec_.classifier_score_rules(), image_id)) {
+      eligible_after_second_pass_.insert(image_id);
+    }
+  }
+  RenormalizeForThirdPass();
+  std::vector<std::string> eligible_image_ids;
+  for (const std::string& image_id : eligible_after_second_pass_) {
+    if (IsEligible(spec_.post_renormalization_rules(), image_id)) {
+      eligible_image_ids.push_back(image_id);
+    }
+  }
+  return eligible_image_ids;
+}
+
+base::flat_map<std::string, double>
+EligibilityModule::GetDebugFeatureValuesForImage(const std::string& image_id) {
+  base::flat_map<std::string, double> output_map;
+  GetDebugFeatureValuesForRules(image_id, spec_.cheap_pruning_rules(),
+                                output_map);
+  GetDebugFeatureValuesForRules(image_id, spec_.classifier_score_rules(),
+                                output_map);
+  GetDebugFeatureValuesForRules(image_id, spec_.post_renormalization_rules(),
+                                output_map);
+  return output_map;
+}
+
+// Private methods.
+void EligibilityModule::Clear() {
+  image_level_features_.clear();
+  page_level_features_.clear();
+  eligible_after_first_pass_.clear();
+  eligible_after_second_pass_.clear();
+  have_run_first_pass_ = false;
+}
+
+void EligibilityModule::ComputeNormalizingFeatures(
+    const std::vector<SingleImageGeometryFeatures>& images) {
+  const bool second_pass_only = false;
+  for (const auto& eligibility_rule : spec_.cheap_pruning_rules()) {
+    for (const auto& thresholding_rule : eligibility_rule.rules()) {
+      if (thresholding_rule.has_normalizing_feature_name()) {
+        ComputeAndGetPageLevelFeatureValue(
+            thresholding_rule.normalizing_feature_name(), images,
+            second_pass_only);
+      }
+    }
+  }
+
+  for (const auto& second_pass_rule : spec_.classifier_score_rules()) {
+    for (const auto& thresholding_rule : second_pass_rule.rules()) {
+      if (thresholding_rule.has_normalizing_feature_name()) {
+        ComputeAndGetPageLevelFeatureValue(
+            thresholding_rule.normalizing_feature_name(), images,
+            second_pass_only);
+      }
+    }
+  }
+
+  for (const auto& third_pass_rule : spec_.post_renormalization_rules()) {
+    for (const auto& thresholding_rule : third_pass_rule.rules()) {
+      if (thresholding_rule.has_normalizing_feature_name()) {
+        ComputeAndGetPageLevelFeatureValue(
+            thresholding_rule.normalizing_feature_name(), images,
+            second_pass_only);
+      }
+    }
+  }
+}
+
+bool EligibilityModule::IsEligible(
+    const google::protobuf::RepeatedPtrField<OrOfThresholdingRules>& rules,
+    const std::string& image_id) {
+  for (const auto& rule : rules) {
+    if (!EvaluateEligibilityRule(rule, image_id)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool EligibilityModule::EvaluateEligibilityRule(
+    const OrOfThresholdingRules& eligibility_rule,
+    const std::string& image_id) {
+  // Compute the OR of the thresholding rules.
+  for (const auto& thresholding_rule : eligibility_rule.rules()) {
+    if (EvaluateThresholdingRule(thresholding_rule, image_id)) {
+      return true;
+    }
+  }
+  return false;
+}
+
+bool EligibilityModule::EvaluateThresholdingRule(
+    const ThresholdingRule& thresholding_rule,
+    const std::string& image_id) {
+  double feature_value =
+      RetrieveImageFeatureOrDie(thresholding_rule.feature_name(), image_id);
+  if (thresholding_rule.has_normalizing_feature_name()) {
+    const double normalizing_feature = RetrievePageLevelFeatureOrDie(
+        thresholding_rule.normalizing_feature_name());
+    if (normalizing_feature != 0) {
+      feature_value = feature_value / normalizing_feature;
+    } else {
+      feature_value = 0;
+    }
+  }
+  if (thresholding_rule.op() == FeatureLibrary::GT) {
+    return feature_value > thresholding_rule.threshold();
+  } else if (thresholding_rule.op() == FeatureLibrary::LT) {
+    return feature_value < thresholding_rule.threshold();
+  } else {
+    NOTREACHED();
+  }
+  return false;
+}
+
+void EligibilityModule::ComputeFeaturesForOrOfThresholdingRules(
+    const google::protobuf::RepeatedPtrField<OrOfThresholdingRules>& rules,
+    const SingleImageGeometryFeatures& image) {
+  for (const auto& rule : rules) {
+    for (const auto& thresholding_rule : rule.rules()) {
+      const auto feature_name = thresholding_rule.feature_name();
+      if (feature_name != FeatureLibrary::SHOPPING_CLASSIFIER_SCORE &&
+          feature_name != FeatureLibrary::SENS_CLASSIFIER_SCORE) {
+        GetImageFeatureValue(thresholding_rule.feature_name(), image);
+      }
+    }
+  }
+}
+
+double EligibilityModule::GetMaxFeatureValue(
+    FeatureLibrary::PageLevelFeatureName page_level_feature_name,
+    FeatureLibrary::ImageLevelFeatureName corresponding_image_feature_name,
+    const std::vector<SingleImageGeometryFeatures>& images) {
+  if (const auto it = page_level_features_.find(page_level_feature_name);
+      it != page_level_features_.end()) {
+    return it->second;
+  }
+  double max_value = 0.0;
+  int count = 0;
+  for (const auto& image : images) {
+    // Don't let the size of cached features grow too much.
+    if (count++ > kMaxNumStored) {
+      break;
+    }
+    const double value =
+        GetImageFeatureValue(corresponding_image_feature_name, image);
+    if (value > max_value) {
+      max_value = value;
+    }
+  }
+  if (page_level_features_.size() < kMaxNumStored) {
+    page_level_features_[page_level_feature_name] = max_value;
+  }
+  return max_value;
+}
+
+double EligibilityModule::MaxFeatureValueAfterSecondPass(
+    FeatureLibrary::ImageLevelFeatureName image_feature_name) {
+  double max_value = 0.0;
+  for (const std::string& image_id : eligible_after_second_pass_) {
+    const double value =
+        RetrieveImageFeatureOrDie(image_feature_name, image_id);
+    if (value > max_value) {
+      max_value = value;
+    }
+  }
+  return max_value;
+}
+
+double EligibilityModule::GetImageFeatureValue(
+    FeatureLibrary::ImageLevelFeatureName feature_name,
+    const SingleImageGeometryFeatures& image) {
+  // See if we have cached it.
+  absl::optional<double> feature_opt =
+      RetrieveImageFeatureIfPresent(feature_name, image.image_identifier);
+  if (feature_opt.has_value()) {
+    return feature_opt.value();
+  }
+
+  // Else we need to compute.
+  double feature_value = 0;
+  double height = 0;
+  double width = 0;
+  Rect viewport_rect;
+  switch (feature_name) {
+    case FeatureLibrary::IMAGE_ONPAGE_AREA:
+      // Corresponding methods in Chrome are height() and width().
+      feature_value = static_cast<double>(image.onpage_rect.height()) *
+                      static_cast<double>(image.onpage_rect.width());
+      break;
+    case FeatureLibrary::IMAGE_ONPAGE_ASPECT_RATIO:
+      // Corresponding methods in Chrome are height() and width().
+      height = static_cast<double>(image.onpage_rect.height());
+      width = static_cast<double>(image.onpage_rect.width());
+      if (height != 0.0 && width != 0.0) {
+        feature_value = std::max(height, width) / std::min(height, width);
+      }
+      break;
+    case FeatureLibrary::IMAGE_ORIGINAL_AREA:
+      feature_value = image.original_image_size.Area64();
+      break;
+    case FeatureLibrary::IMAGE_ORIGINAL_ASPECT_RATIO:
+      height = static_cast<double>(image.original_image_size.height());
+      width = static_cast<double>(image.original_image_size.width());
+      if (height != 0.0 && width != 0.0) {
+        feature_value = std::max(height, width) / std::min(height, width);
+      }
+      break;
+    case FeatureLibrary::IMAGE_VISIBLE_AREA:
+      viewport_rect = Rect(0, 0, static_cast<int>(viewport_width_),
+                           static_cast<int>(viewport_height_));
+      viewport_rect.Intersect(image.onpage_rect);
+      feature_value = static_cast<double>(viewport_rect.height()) *
+                      static_cast<double>(viewport_rect.width());
+      break;
+    case FeatureLibrary::IMAGE_FRACTION_VISIBLE:
+      if (GetImageFeatureValue(FeatureLibrary::IMAGE_ONPAGE_AREA, image) == 0) {
+        feature_value = 0;
+      } else {
+        feature_value =
+            GetImageFeatureValue(FeatureLibrary::IMAGE_VISIBLE_AREA, image) /
+            GetImageFeatureValue(FeatureLibrary::IMAGE_ONPAGE_AREA, image);
+      }
+      break;
+    case FeatureLibrary::IMAGE_ORIGINAL_HEIGHT:
+      feature_value = static_cast<double>(image.original_image_size.height());
+      break;
+    case FeatureLibrary::IMAGE_ORIGINAL_WIDTH:
+      feature_value = static_cast<double>(image.original_image_size.width());
+      break;
+    case FeatureLibrary::IMAGE_ONPAGE_HEIGHT:
+      feature_value = static_cast<double>(image.onpage_rect.height());
+      break;
+    case FeatureLibrary::IMAGE_ONPAGE_WIDTH:
+      feature_value = static_cast<double>(image.onpage_rect.width());
+      break;
+    case FeatureLibrary::IMAGE_LEVEL_UNSPECIFIED:
+    case FeatureLibrary::SHOPPING_CLASSIFIER_SCORE:
+    case FeatureLibrary::SENS_CLASSIFIER_SCORE:
+      NOTREACHED();
+      break;
+  }
+  // Cache it and return.
+  if (image_level_features_[image.image_identifier].size() < kMaxNumStored) {
+    image_level_features_[image.image_identifier][feature_name] = feature_value;
+  }
+  return feature_value;
+}
+
+absl::optional<double> EligibilityModule::RetrieveImageFeatureIfPresent(
+    FeatureLibrary::ImageLevelFeatureName feature_name,
+    const std::string& image_id) {
+  if (const auto& feature_to_value_it = image_level_features_.find(image_id);
+      feature_to_value_it != image_level_features_.end()) {
+    if (const auto& value_it = feature_to_value_it->second.find(feature_name);
+        value_it != feature_to_value_it->second.end()) {
+      return value_it->second;
+    }
+  }
+  return {};
+}
+
+double EligibilityModule::RetrieveImageFeatureOrDie(
+    FeatureLibrary::ImageLevelFeatureName feature_name,
+    const std::string& image_id) {
+  absl::optional<double> feature_opt =
+      RetrieveImageFeatureIfPresent(feature_name, image_id);
+  CHECK(feature_opt.has_value()) << "Did not find image feature.";
+  return feature_opt.value();
+}
+
+double EligibilityModule::RetrievePageLevelFeatureOrDie(
+    FeatureLibrary::PageLevelFeatureName feature_name) {
+  if (const auto it = page_level_features_.find(feature_name);
+      it != page_level_features_.end()) {
+    return it->second;
+  }
+  CHECK(false) << "Did not find page-level feature.";
+  return 1;
+}
+
+double EligibilityModule::ComputeAndGetPageLevelFeatureValue(
+    FeatureLibrary::PageLevelFeatureName feature_name,
+    const std::vector<SingleImageGeometryFeatures>& images,
+    bool limit_to_second_pass_eligible) {
+  const base::flat_map<FeatureLibrary::PageLevelFeatureName,
+                       FeatureLibrary::ImageLevelFeatureName>
+      features_map = {{FeatureLibrary::MAX_IMAGE_ORIGINAL_AREA,
+                       FeatureLibrary::IMAGE_ORIGINAL_AREA},
+                      {FeatureLibrary::MAX_IMAGE_ORIGINAL_ASPECT_RATIO,
+                       FeatureLibrary::IMAGE_ORIGINAL_ASPECT_RATIO},
+                      {FeatureLibrary::MAX_IMAGE_ONPAGE_AREA,
+                       FeatureLibrary::IMAGE_ONPAGE_AREA},
+                      {FeatureLibrary::MAX_IMAGE_ONPAGE_ASPECT_RATIO,
+                       FeatureLibrary::IMAGE_ONPAGE_ASPECT_RATIO},
+                      {FeatureLibrary::MAX_IMAGE_VISIBLE_AREA,
+                       FeatureLibrary::IMAGE_VISIBLE_AREA},
+                      {FeatureLibrary::MAX_IMAGE_FRACTION_VISIBLE,
+                       FeatureLibrary::IMAGE_FRACTION_VISIBLE}};
+  double viewport_area = 0;
+  switch (feature_name) {
+    case FeatureLibrary::VIEWPORT_AREA:
+      viewport_area = viewport_width_ * viewport_height_;
+      if (page_level_features_.size() < kMaxNumStored) {
+        page_level_features_[FeatureLibrary::VIEWPORT_AREA] = viewport_area;
+      }
+      return viewport_area;
+    case FeatureLibrary::MAX_IMAGE_ORIGINAL_AREA:
+    case FeatureLibrary::MAX_IMAGE_ORIGINAL_ASPECT_RATIO:
+    case FeatureLibrary::MAX_IMAGE_ONPAGE_AREA:
+    case FeatureLibrary::MAX_IMAGE_ONPAGE_ASPECT_RATIO:
+    case FeatureLibrary::MAX_IMAGE_VISIBLE_AREA:
+    case FeatureLibrary::MAX_IMAGE_FRACTION_VISIBLE:
+      if (!limit_to_second_pass_eligible) {
+        return GetMaxFeatureValue(feature_name, features_map.at(feature_name),
+                                  images);
+      } else {
+        return MaxFeatureValueAfterSecondPass(features_map.at(feature_name));
+      }
+    case FeatureLibrary::PAGE_LEVEL_UNSPECIFIED:
+      NOTREACHED();
+      return 1.0;
+  }
+}
+
+void EligibilityModule::GetDebugFeatureValuesForRules(
+    const std::string& image_id,
+    const google::protobuf::RepeatedPtrField<OrOfThresholdingRules>& rules,
+    base::flat_map<std::string, double>& output_map) {
+  for (const auto& rule : rules) {
+    for (const auto& ored_rule : rule.rules()) {
+      const FeatureLibrary::ImageLevelFeatureName feature_name =
+          ored_rule.feature_name();
+      if (feature_name == FeatureLibrary::SHOPPING_CLASSIFIER_SCORE ||
+          feature_name == FeatureLibrary::SENS_CLASSIFIER_SCORE) {
+        continue;
+      }
+      const double feature_value =
+          RetrieveImageFeatureOrDie(feature_name, image_id);
+      output_map[FeatureLibrary::ImageLevelFeatureName_Name(feature_name)] =
+          feature_value;
+      if (ored_rule.has_normalizing_feature_name()) {
+        const FeatureLibrary::PageLevelFeatureName normalizing_name =
+            ored_rule.normalizing_feature_name();
+        const double normalizing_value =
+            RetrievePageLevelFeatureOrDie(normalizing_name);
+        output_map[FeatureLibrary::PageLevelFeatureName_Name(
+            normalizing_name)] = normalizing_value;
+        if (normalizing_value != 0) {
+          output_map[kNormalizedPrefix +
+                     FeatureLibrary::ImageLevelFeatureName_Name(feature_name)] =
+              feature_value / normalizing_value;
+        }
+      }
+    }
+  }
+}
+
+void EligibilityModule::RenormalizeForThirdPass() {
+  for (const auto& third_pass_rule : spec_.post_renormalization_rules()) {
+    for (const auto& thresholding_rule : third_pass_rule.rules()) {
+      if (thresholding_rule.has_normalizing_feature_name() &&
+          thresholding_rule.normalizing_feature_name() !=
+              FeatureLibrary::VIEWPORT_AREA) {
+        const auto page_feature_name =
+            thresholding_rule.normalizing_feature_name();
+        if (page_level_features_.size() < kMaxNumStored) {
+          page_level_features_[page_feature_name] =
+              ComputeAndGetPageLevelFeatureValue(page_feature_name, {}, true);
+        }
+      }
+    }
+  }
+}
+}  // namespace companion::visual_search
diff --git a/chrome/renderer/companion/visual_search/visual_search_eligibility.h b/chrome/renderer/companion/visual_search/visual_search_eligibility.h
new file mode 100644
index 0000000..5c46c80
--- /dev/null
+++ b/chrome/renderer/companion/visual_search/visual_search_eligibility.h
@@ -0,0 +1,138 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_RENDERER_COMPANION_VISUAL_SEARCH_VISUAL_SEARCH_ELIGIBILITY_H_
+#define CHROME_RENDERER_COMPANION_VISUAL_SEARCH_VISUAL_SEARCH_ELIGIBILITY_H_
+
+#include <algorithm>
+#include <limits>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/containers/flat_map.h"
+#include "base/containers/flat_set.h"
+#include "base/gtest_prod_util.h"
+#include "chrome/common/companion/eligibility_spec.pb.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+#include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/geometry/size.h"
+#include "ui/gfx/geometry/size_f.h"
+
+namespace companion::visual_search {
+
+using ::gfx::Rect;
+using ::gfx::Size;
+using ::gfx::SizeF;
+
+// Stores the raw features of a single image.
+struct SingleImageGeometryFeatures {
+  std::string image_identifier;
+  Size original_image_size;
+  Rect onpage_rect = Rect(0, 0, 0, 0);
+  ~SingleImageGeometryFeatures() = default;
+};
+
+// This class is used to determine which images are eligible to be surfaced in
+// the CSC side bar according to settings set in the config proto.
+class EligibilityModule {
+ public:
+  // Create the module using a spec.
+  explicit EligibilityModule(const EligibilitySpec& spec);
+
+  // Applies the cheap_pruning_rules from the eligibility spec. Outputs a list
+  // of image identifiers that pass eligibility in no particular order. Caches
+  // the values of all features that are needed across all rule sets in the
+  // spec to avoid having to pass them throughout.
+  std::vector<std::string> RunFirstPassEligibilityAndCacheFeatureValues(
+      const SizeF& viewport_image_size,
+      const std::vector<SingleImageGeometryFeatures>& images);
+
+  // Applies the classifier_score_rules and post_renormalization_rules from the
+  // eligibility spec and outputs the list of image identifiers that pass, in
+  // no particular order. Should be run after RunFirstPassEligibility above
+  // is run and only if the image geometry features have not changed since
+  // that method was called.
+  std::vector<std::string> RunSecondPassPostClassificationEligibility(
+      const base::flat_map<std::string, double>& shopping_classifier_scores,
+      const base::flat_map<std::string, double>& sensitivity_classifier_scores);
+
+  // Returns a map from formatted-as-string feature name to feature value for
+  // the given image_identifier.
+  base::flat_map<std::string, double> GetDebugFeatureValuesForImage(
+      const std::string& image_id);
+
+  EligibilityModule(const EligibilityModule&) = delete;
+  EligibilityModule& operator=(const EligibilityModule&) = delete;
+  ~EligibilityModule();
+
+ private:
+  FRIEND_TEST_ALL_PREFIXES(EligibilityModuleTest, TestImageFeatureComputation);
+  FRIEND_TEST_ALL_PREFIXES(EligibilityModuleTest, TestPageFeatureComputation);
+  void Clear();
+  void ComputeNormalizingFeatures(
+      const std::vector<SingleImageGeometryFeatures>& images);
+  void RenormalizeForThirdPass();
+  void ComputeFeaturesForOrOfThresholdingRules(
+      const google::protobuf::RepeatedPtrField<OrOfThresholdingRules>& rules,
+      const SingleImageGeometryFeatures& image);
+  // Eligibility evaluation methods.
+  bool IsEligible(
+      const google::protobuf::RepeatedPtrField<OrOfThresholdingRules>& rules,
+      const std::string& image_id);
+  bool EvaluateEligibilityRule(const OrOfThresholdingRules& eligibility_rule,
+                               const std::string& image_id);
+  bool EvaluateThresholdingRule(const ThresholdingRule& thresholding_rule,
+                                const std::string& image_id);
+  // Convenient methods for getting and caching feature values.
+  double GetImageFeatureValue(
+      FeatureLibrary::ImageLevelFeatureName feature_name,
+      const SingleImageGeometryFeatures& image);
+  absl::optional<double> RetrieveImageFeatureIfPresent(
+      FeatureLibrary::ImageLevelFeatureName feature_name,
+      const std::string& image_id);
+  double RetrieveImageFeatureOrDie(
+      FeatureLibrary::ImageLevelFeatureName feature_name,
+      const std::string& image_id);
+  double RetrievePageLevelFeatureOrDie(
+      FeatureLibrary::PageLevelFeatureName feature_name);
+  double ComputeAndGetPageLevelFeatureValue(
+      FeatureLibrary::PageLevelFeatureName feature_name,
+      const std::vector<SingleImageGeometryFeatures>& images,
+      bool limit_to_second_pass_eligible);
+  double GetMaxFeatureValue(
+      FeatureLibrary::PageLevelFeatureName page_level_feature_name,
+      FeatureLibrary::ImageLevelFeatureName corresponding_image_feature_name,
+      const std::vector<SingleImageGeometryFeatures>& images);
+  double MaxFeatureValueAfterSecondPass(
+      FeatureLibrary::ImageLevelFeatureName image_feature_name);
+  void GetDebugFeatureValuesForRules(
+      const std::string& image_id,
+      const google::protobuf::RepeatedPtrField<OrOfThresholdingRules>& rules,
+      base::flat_map<std::string, double>& output_map);
+
+  EligibilitySpec spec_;
+  // Cache for features that are computed individually for each image.
+  // TODO(lilymihal): Add metrics about the size of these flat_map and sets.
+  base::flat_map<std::string,
+                 base::flat_map<FeatureLibrary::ImageLevelFeatureName, double>>
+      image_level_features_;
+  // Cache for features that are computed at the level of the whole page.
+  base::flat_map<FeatureLibrary::PageLevelFeatureName, double>
+      page_level_features_;
+  // Keep track of what images were eligible after the first and second passes.
+  base::flat_set<std::string> eligible_after_first_pass_;
+  base::flat_set<std::string> eligible_after_second_pass_;
+
+  // Cache the viewport size so we don't have to pass it around. This gets set
+  // in RunFirstPassEligibilityAndCacheFeatureValues.
+  float viewport_width_;
+  float viewport_height_;
+
+  // Keeps track of whether the first pass has run since the last time we ran
+  // the second pass.
+  bool have_run_first_pass_;
+};
+}  // namespace companion::visual_search
+#endif
diff --git a/chrome/renderer/companion/visual_search/visual_search_eligibility_unittest.cc b/chrome/renderer/companion/visual_search/visual_search_eligibility_unittest.cc
new file mode 100644
index 0000000..2f0e25a
--- /dev/null
+++ b/chrome/renderer/companion/visual_search/visual_search_eligibility_unittest.cc
@@ -0,0 +1,473 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/companion/visual_search/visual_search_eligibility.h"
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/geometry/size.h"
+#include "ui/gfx/geometry/size_f.h"
+
+namespace companion::visual_search {
+
+using ::gfx::Rect;
+using ::gfx::Size;
+using ::gfx::SizeF;
+using ::testing::DoubleNear;
+using ::testing::Pair;
+using ::testing::UnorderedElementsAre;
+
+TEST(EligibilityModuleTest, E2eExample) {
+  EligibilitySpec spec;
+  auto* rules = spec.add_cheap_pruning_rules()->add_rules();
+  rules->set_feature_name(FeatureLibrary::IMAGE_ONPAGE_AREA);
+  rules->set_op(FeatureLibrary::GT);
+  rules->set_threshold(44);
+  rules = spec.add_cheap_pruning_rules()->add_rules();
+  rules->set_feature_name(FeatureLibrary::IMAGE_ONPAGE_ASPECT_RATIO);
+  rules->set_op(FeatureLibrary::LT);
+  rules->set_threshold(3);
+  rules = spec.add_classifier_score_rules()->add_rules();
+  rules->set_feature_name(FeatureLibrary::SHOPPING_CLASSIFIER_SCORE);
+  rules->set_op(FeatureLibrary::GT);
+  rules->set_threshold(0.6);
+  rules = spec.add_classifier_score_rules()->add_rules();
+  rules->set_feature_name(FeatureLibrary::SENS_CLASSIFIER_SCORE);
+  rules->set_op(FeatureLibrary::LT);
+  rules->set_threshold(0.5);
+  rules = spec.add_post_renormalization_rules()->add_rules();
+  rules->set_feature_name(FeatureLibrary::IMAGE_ONPAGE_AREA);
+  rules->set_normalizing_feature_name(FeatureLibrary::MAX_IMAGE_ONPAGE_AREA);
+  rules->set_op(FeatureLibrary::GT);
+  rules->set_threshold(0.999);
+
+  EligibilityModule module(spec);
+  SizeF viewport_size(100.0, 50.0);
+  std::vector<SingleImageGeometryFeatures> images;
+  images.reserve(3);
+  SingleImageGeometryFeatures image1;
+  image1.image_identifier = "image1";
+  image1.onpage_rect = Rect(0, 0, 5, 10);
+  images.push_back(std::move(image1));
+  SingleImageGeometryFeatures image2;
+  image2.image_identifier = "image2";
+  image2.onpage_rect = Rect(0, 0, 15, 3);
+  images.push_back(std::move(image2));
+  // Identical to image 1, passes eligibility as well, but will have non-passing
+  // shopping score.
+  SingleImageGeometryFeatures image3;
+  image3.image_identifier = "image3";
+  image3.onpage_rect = Rect(0, 0, 5, 10);
+  images.push_back(std::move(image3));
+  // Identical to image 1, passes eligibility as well, but will have non-passing
+  // sensitivity score.
+  SingleImageGeometryFeatures image4;
+  image4.image_identifier = "image4";
+  image4.onpage_rect = Rect(0, 0, 5, 10);
+  images.push_back(std::move(image4));
+  // A large image that passes first pass, but not second pass. Its area should
+  // not participate in normalization when applying third pass.
+  SingleImageGeometryFeatures image5;
+  image5.image_identifier = "image5";
+  image5.onpage_rect = Rect(0, 0, 1000, 1000);
+  images.push_back(std::move(image5));
+  // Image that passes first and second pass but not third.
+  SingleImageGeometryFeatures image6;
+  image6.image_identifier = "image6";
+  image6.onpage_rect = Rect(0, 0, 5, 9);
+  images.push_back(std::move(image6));
+
+  const std::vector<std::string> simple_pruning_image_ids =
+      module.RunFirstPassEligibilityAndCacheFeatureValues(viewport_size,
+                                                          images);
+  ASSERT_EQ(simple_pruning_image_ids.size(), 5U);
+  EXPECT_EQ(simple_pruning_image_ids.at(0), "image1");
+  EXPECT_EQ(simple_pruning_image_ids.at(1), "image3");
+  EXPECT_EQ(simple_pruning_image_ids.at(2), "image4");
+  EXPECT_EQ(simple_pruning_image_ids.at(3), "image5");
+  EXPECT_EQ(simple_pruning_image_ids.at(4), "image6");
+
+  const base::flat_map<std::string, double> shopping_scores = {{"image1", 0.7},
+                                                               {"image3", 0.5},
+                                                               {"image4", 0.7},
+                                                               {"image5", 0.0},
+                                                               {"image6", 0.7}};
+  const base::flat_map<std::string, double> sens_scores = {{"image1", 0.4},
+                                                           {"image3", 0.4},
+                                                           {"image4", 0.8},
+                                                           {"image5", 0.0},
+                                                           {"image6", 0.4}};
+  const std::vector<std::string> second_pass_eligible_image_ids =
+      module.RunSecondPassPostClassificationEligibility(shopping_scores,
+                                                        sens_scores);
+
+  ASSERT_EQ(second_pass_eligible_image_ids.size(), 1U);
+  EXPECT_EQ(second_pass_eligible_image_ids.at(0), "image1");
+  const base::flat_map<std::string, double> image1_features_after_third =
+      module.GetDebugFeatureValuesForImage("image1");
+  EXPECT_THAT(
+      image1_features_after_third,
+      UnorderedElementsAre(
+          Pair("IMAGE_ONPAGE_AREA", 50), Pair("MAX_IMAGE_ONPAGE_AREA", 50),
+          Pair("normalized_IMAGE_ONPAGE_AREA", DoubleNear(1, 0.01)),
+          Pair("IMAGE_ONPAGE_ASPECT_RATIO", 2)));
+}
+
+TEST(EligibilityModuleTest, TestWithFeatureNormalization) {
+  EligibilitySpec spec;
+  auto* rules = spec.add_cheap_pruning_rules()->add_rules();
+  rules->set_feature_name(FeatureLibrary::IMAGE_ONPAGE_AREA);
+  rules->set_normalizing_feature_name(FeatureLibrary::MAX_IMAGE_ONPAGE_AREA);
+  rules->set_op(FeatureLibrary::GT);
+  rules->set_threshold(0.5);
+
+  EligibilityModule module(spec);
+
+  SizeF viewport_size(100.0, 50.0);
+  std::vector<SingleImageGeometryFeatures> images;
+  images.reserve(3);
+  SingleImageGeometryFeatures image1;
+  image1.image_identifier = "image1";
+  image1.onpage_rect = Rect(0, 0, 10, 10);
+  images.push_back(std::move(image1));
+  SingleImageGeometryFeatures image2;
+  image2.image_identifier = "image2";
+  image2.onpage_rect = Rect(0, 0, 6, 10);
+  images.push_back(std::move(image2));
+  // Identical to image 1, passes eligibility as well, but will have non-passing
+  // shopping score.
+  SingleImageGeometryFeatures image3;
+  image3.image_identifier = "image3";
+  image3.onpage_rect = Rect(0, 0, 4, 10);
+  images.push_back(std::move(image3));
+  const std::vector<std::string> eligible_image_ids =
+      module.RunFirstPassEligibilityAndCacheFeatureValues(viewport_size,
+                                                          images);
+  ASSERT_EQ(eligible_image_ids.size(), 2U);
+  EXPECT_EQ(eligible_image_ids.at(0), "image1");
+  EXPECT_EQ(eligible_image_ids.at(1), "image2");
+
+  const base::flat_map<std::string, double> image2_features =
+      module.GetDebugFeatureValuesForImage("image2");
+  EXPECT_THAT(
+      image2_features,
+      UnorderedElementsAre(
+          Pair("IMAGE_ONPAGE_AREA", 60), Pair("MAX_IMAGE_ONPAGE_AREA", 100),
+          Pair("normalized_IMAGE_ONPAGE_AREA", DoubleNear(0.6, 0.01))));
+}
+
+TEST(EligibilityModuleTest, TestOringRules) {
+  EligibilitySpec spec;
+  auto* ored_rules = spec.add_cheap_pruning_rules();
+  auto* rules = ored_rules->add_rules();
+  rules->set_feature_name(FeatureLibrary::IMAGE_ONPAGE_AREA);
+  rules->set_normalizing_feature_name(FeatureLibrary::MAX_IMAGE_ONPAGE_AREA);
+  rules->set_op(FeatureLibrary::GT);
+  rules->set_threshold(0.5);
+  rules = ored_rules->add_rules();
+  rules->set_feature_name(FeatureLibrary::IMAGE_ONPAGE_AREA);
+  rules->set_op(FeatureLibrary::LT);
+  rules->set_threshold(45);
+
+  EligibilityModule module(spec);
+  SizeF viewport_size(100.0, 50.0);
+  std::vector<SingleImageGeometryFeatures> images;
+  images.reserve(3);
+  SingleImageGeometryFeatures image1;
+  image1.image_identifier = "image1";
+  image1.onpage_rect = Rect(0, 0, 10, 10);
+  images.push_back(std::move(image1));
+  SingleImageGeometryFeatures image2;
+  image2.image_identifier = "image2";
+  image2.onpage_rect = Rect(0, 0, 6, 10);
+  images.push_back(std::move(image2));
+  SingleImageGeometryFeatures image3;
+  image3.image_identifier = "image3";
+  image3.onpage_rect = Rect(0, 0, 4, 10);
+  images.push_back(std::move(image3));
+  const std::vector<std::string> eligible_image_ids =
+      module.RunFirstPassEligibilityAndCacheFeatureValues(viewport_size,
+                                                          images);
+  ASSERT_EQ(eligible_image_ids.size(), 3U);
+  EXPECT_EQ(eligible_image_ids.at(0), "image1");
+  EXPECT_EQ(eligible_image_ids.at(1), "image2");
+  EXPECT_EQ(eligible_image_ids.at(2), "image3");
+}
+
+TEST(EligibilityModuleTest, TestImageVisibleArea) {
+  EligibilitySpec spec;
+  auto* rules = spec.add_cheap_pruning_rules()->add_rules();
+  rules->set_feature_name(FeatureLibrary::IMAGE_VISIBLE_AREA);
+  rules->set_op(FeatureLibrary::GT);
+  rules->set_threshold(1.1);
+
+  EligibilityModule module(spec);
+  SizeF viewport_size(3.0, 3.0);
+  std::vector<SingleImageGeometryFeatures> images;
+  images.reserve(2);
+  SingleImageGeometryFeatures image1;
+  image1.image_identifier = "image1";
+  image1.onpage_rect = Rect(1, 1, 3, 3);
+  images.push_back(std::move(image1));
+  SingleImageGeometryFeatures image2;
+  image2.image_identifier = "image2";
+  image2.onpage_rect = Rect(2, 2, 2, 2);
+  images.push_back(std::move(image2));
+
+  const std::vector<std::string> eligible_image_ids =
+      module.RunFirstPassEligibilityAndCacheFeatureValues(viewport_size,
+                                                          images);
+  ASSERT_EQ(eligible_image_ids.size(), 1U);
+  EXPECT_EQ(eligible_image_ids.at(0), "image1");
+}
+
+TEST(EligibilityModuleTest, TestFeaturesForSecondPassCached) {
+  EligibilitySpec spec;
+  auto* rules = spec.add_cheap_pruning_rules()->add_rules();
+  rules->set_feature_name(FeatureLibrary::IMAGE_ONPAGE_ASPECT_RATIO);
+  rules->set_op(FeatureLibrary::GT);
+  rules->set_threshold(4);
+  rules = spec.add_classifier_score_rules()->add_rules();
+  rules->set_feature_name(FeatureLibrary::IMAGE_ONPAGE_AREA);
+  rules->set_normalizing_feature_name(FeatureLibrary::MAX_IMAGE_ONPAGE_AREA);
+  rules->set_op(FeatureLibrary::GT);
+  rules->set_threshold(0.5);
+
+  EligibilityModule module(spec);
+  SizeF viewport_size(100.0, 50.0);
+  std::vector<SingleImageGeometryFeatures> images;
+  images.reserve(1);
+  SingleImageGeometryFeatures image1;
+  image1.image_identifier = "image1";
+  image1.onpage_rect = Rect(0, 0, 50, 10);
+  images.push_back(std::move(image1));
+  const std::vector<std::string> eligible_image_ids =
+      module.RunFirstPassEligibilityAndCacheFeatureValues(viewport_size,
+                                                          images);
+  ASSERT_EQ(eligible_image_ids.size(), 1U);
+  EXPECT_EQ(eligible_image_ids.at(0), "image1");
+  const std::vector<std::string> second_pass_eligible_image_ids =
+      module.RunSecondPassPostClassificationEligibility({}, {});
+  ASSERT_EQ(second_pass_eligible_image_ids.size(), 1U);
+  EXPECT_EQ(second_pass_eligible_image_ids.at(0), "image1");
+}
+
+TEST(EligibilityModuleTest, TestReuseModuleBetweenImageSets) {
+  EligibilitySpec spec;
+  auto* rules = spec.add_cheap_pruning_rules()->add_rules();
+  rules->set_feature_name(FeatureLibrary::IMAGE_ONPAGE_AREA);
+  rules->set_op(FeatureLibrary::GT);
+  rules->set_threshold(100);
+  rules = spec.add_classifier_score_rules()->add_rules();
+  rules->set_feature_name(FeatureLibrary::SHOPPING_CLASSIFIER_SCORE);
+  rules->set_op(FeatureLibrary::GT);
+  rules->set_threshold(0.6);
+  rules = spec.add_post_renormalization_rules()->add_rules();
+  rules->set_feature_name(FeatureLibrary::IMAGE_ONPAGE_AREA);
+  rules->set_op(FeatureLibrary::GT);
+  rules->set_threshold(100);
+
+  EligibilityModule module(spec);
+  SizeF viewport_size(100.0, 50.0);
+  {
+    // Run 1 with the module. One image, which passes.
+    std::vector<SingleImageGeometryFeatures> images;
+    images.reserve(2);
+    // Both image1 and image3 pass the filters here. In the second run, we'll
+    // have images with the same names, but they will not pass the first and
+    // the second pass respectively.
+    SingleImageGeometryFeatures image1;
+    image1.image_identifier = "image1";
+    image1.onpage_rect = Rect(0, 0, 20, 10);
+    images.push_back(std::move(image1));
+    SingleImageGeometryFeatures image3;
+    image3.image_identifier = "image3";
+    image3.onpage_rect = Rect(0, 0, 20, 10);
+    images.push_back(std::move(image3));
+    const std::vector<std::string> eligible_image_ids =
+        module.RunFirstPassEligibilityAndCacheFeatureValues(viewport_size,
+                                                            images);
+    ASSERT_EQ(eligible_image_ids.size(), 2U);
+    EXPECT_THAT(eligible_image_ids, UnorderedElementsAre("image1", "image3"));
+    const base::flat_map<std::string, double> shopping_scores = {
+        {"image1", 0.7}, {"image3", 0.7}};
+    const base::flat_map<std::string, double> sens_scores = {{"image1", 0.1},
+                                                             {"image3", 0.1}};
+    const std::vector<std::string> second_pass_eligible_image_ids =
+        module.RunSecondPassPostClassificationEligibility(shopping_scores,
+                                                          sens_scores);
+    ASSERT_EQ(second_pass_eligible_image_ids.size(), 2U);
+    EXPECT_THAT(second_pass_eligible_image_ids,
+                UnorderedElementsAre("image1", "image3"));
+  }
+  {
+    // Run 2 with the module.
+    std::vector<SingleImageGeometryFeatures> images;
+    images.reserve(3);
+    SingleImageGeometryFeatures image1;
+    // Gets excluded in the first pass.
+    image1.image_identifier = "image1";
+    image1.onpage_rect = Rect(0, 0, 2, 10);
+    images.push_back(std::move(image1));
+    SingleImageGeometryFeatures image2;
+    image2.image_identifier = "image2";
+    image2.onpage_rect = Rect(0, 0, 20, 10);
+    images.push_back(std::move(image2));
+    // Gets excluded in the second pass.
+    SingleImageGeometryFeatures image3;
+    image3.image_identifier = "image3";
+    image3.onpage_rect = Rect(0, 0, 20, 10);
+    images.push_back(std::move(image3));
+    const std::vector<std::string> eligible_image_ids =
+        module.RunFirstPassEligibilityAndCacheFeatureValues(viewport_size,
+                                                            images);
+    ASSERT_EQ(eligible_image_ids.size(), 2U);
+    EXPECT_THAT(eligible_image_ids, UnorderedElementsAre("image2", "image3"));
+    // Image3 doesn't pass the shoppy filter here.
+    const base::flat_map<std::string, double> shopping_scores = {
+        {"image2", 0.7}, {"image3", 0.1}};
+    const base::flat_map<std::string, double> sens_scores = {{"image2", 0.1},
+                                                             {"image3", 0.1}};
+    const std::vector<std::string> second_pass_eligible_image_ids =
+        module.RunSecondPassPostClassificationEligibility(shopping_scores,
+                                                          sens_scores);
+    ASSERT_EQ(second_pass_eligible_image_ids.size(), 1U);
+    EXPECT_EQ(second_pass_eligible_image_ids.at(0), "image2");
+  }
+}
+
+TEST(EligibilityModuleTest, TestImageFractionVisible) {
+  EligibilitySpec spec;
+  auto* rules = spec.add_cheap_pruning_rules()->add_rules();
+  rules->set_feature_name(FeatureLibrary::IMAGE_FRACTION_VISIBLE);
+  rules->set_op(FeatureLibrary::GT);
+  rules->set_threshold(0.26);
+
+  EligibilityModule module(spec);
+
+  SizeF viewport_size(3.0, 3.0);
+  std::vector<SingleImageGeometryFeatures> images;
+  images.reserve(2);
+  SingleImageGeometryFeatures image1;
+  image1.image_identifier = "image1";
+  image1.onpage_rect = Rect(2, 1, 2, 2);
+  images.push_back(std::move(image1));
+  SingleImageGeometryFeatures image2;
+  image2.image_identifier = "image2";
+  image2.onpage_rect = Rect(2, 2, 2, 2);
+  images.push_back(std::move(image2));
+
+  const std::vector<std::string> eligible_image_ids =
+      module.RunFirstPassEligibilityAndCacheFeatureValues(viewport_size,
+                                                          images);
+  ASSERT_EQ(eligible_image_ids.size(), 1U);
+  EXPECT_EQ(eligible_image_ids.at(0), "image1");
+}
+
+TEST(EligibilityModuleTest, TestImageFeatureComputation) {
+  // The spec doesn't matter here. Just make an empty one.
+  const EligibilitySpec spec;
+  EligibilityModule module(spec);
+  SingleImageGeometryFeatures image;
+  image.image_identifier = "image";
+  image.original_image_size = Size(10, 20);
+  image.onpage_rect = Rect(1, 1, 100, 400);
+
+  EXPECT_EQ(
+      module.GetImageFeatureValue(FeatureLibrary::IMAGE_ORIGINAL_AREA, image),
+      200);
+  EXPECT_EQ(module.GetImageFeatureValue(
+                FeatureLibrary::IMAGE_ORIGINAL_ASPECT_RATIO, image),
+            2);
+  EXPECT_EQ(
+      module.GetImageFeatureValue(FeatureLibrary::IMAGE_ONPAGE_AREA, image),
+      40000);
+  EXPECT_EQ(module.GetImageFeatureValue(
+                FeatureLibrary::IMAGE_ONPAGE_ASPECT_RATIO, image),
+            4);
+  EXPECT_EQ(
+      module.GetImageFeatureValue(FeatureLibrary::IMAGE_ONPAGE_HEIGHT, image),
+      400);
+  EXPECT_EQ(
+      module.GetImageFeatureValue(FeatureLibrary::IMAGE_ONPAGE_WIDTH, image),
+      100);
+  EXPECT_EQ(
+      module.GetImageFeatureValue(FeatureLibrary::IMAGE_ORIGINAL_HEIGHT, image),
+      20);
+  EXPECT_EQ(
+      module.GetImageFeatureValue(FeatureLibrary::IMAGE_ORIGINAL_WIDTH, image),
+      10);
+
+  // These should now be cached.
+  EXPECT_EQ(module.RetrieveImageFeatureOrDie(
+                FeatureLibrary::IMAGE_ORIGINAL_AREA, "image"),
+            200);
+  EXPECT_EQ(module.RetrieveImageFeatureOrDie(
+                FeatureLibrary::IMAGE_ORIGINAL_ASPECT_RATIO, "image"),
+            2);
+  EXPECT_EQ(module.RetrieveImageFeatureOrDie(FeatureLibrary::IMAGE_ONPAGE_AREA,
+                                             "image"),
+            40000);
+  EXPECT_EQ(module.RetrieveImageFeatureOrDie(
+                FeatureLibrary::IMAGE_ONPAGE_ASPECT_RATIO, "image"),
+            4);
+  EXPECT_EQ(module.RetrieveImageFeatureOrDie(
+                FeatureLibrary::IMAGE_ONPAGE_HEIGHT, "image"),
+            400);
+  EXPECT_EQ(module.RetrieveImageFeatureOrDie(FeatureLibrary::IMAGE_ONPAGE_WIDTH,
+                                             "image"),
+            100);
+  EXPECT_EQ(module.RetrieveImageFeatureOrDie(
+                FeatureLibrary::IMAGE_ORIGINAL_HEIGHT, "image"),
+            20);
+  EXPECT_EQ(module.RetrieveImageFeatureOrDie(
+                FeatureLibrary::IMAGE_ORIGINAL_WIDTH, "image"),
+            10);
+}
+
+TEST(EligibilityModuleTest, TestPageFeatureComputation) {
+  const EligibilitySpec spec;
+  EligibilityModule module(spec);
+  SingleImageGeometryFeatures image1;
+  image1.image_identifier = "image1";
+  image1.original_image_size = Size(10, 20);
+  image1.onpage_rect = Rect(90, 90, 40, 40);
+  SingleImageGeometryFeatures image2;
+  image2.image_identifier = "image2";
+  image2.original_image_size = Size(10, 200);
+  image2.onpage_rect = Rect(80, 80, 40, 400);
+  std::vector<SingleImageGeometryFeatures> images;
+  images.push_back(std::move(image1));
+  images.push_back(std::move(image2));
+
+  // Artificially set viewport dimensions.
+  module.viewport_width_ = 100;
+  module.viewport_height_ = 100;
+  EXPECT_EQ(module.ComputeAndGetPageLevelFeatureValue(
+                FeatureLibrary::VIEWPORT_AREA, images, false),
+            10000);
+  EXPECT_EQ(module.ComputeAndGetPageLevelFeatureValue(
+                FeatureLibrary::MAX_IMAGE_ORIGINAL_AREA, images, false),
+            2000);
+  EXPECT_EQ(module.ComputeAndGetPageLevelFeatureValue(
+                FeatureLibrary::MAX_IMAGE_ORIGINAL_ASPECT_RATIO, images, false),
+            20);
+  EXPECT_EQ(module.ComputeAndGetPageLevelFeatureValue(
+                FeatureLibrary::MAX_IMAGE_ORIGINAL_ASPECT_RATIO, images, false),
+            20);
+  EXPECT_EQ(module.ComputeAndGetPageLevelFeatureValue(
+                FeatureLibrary::MAX_IMAGE_ONPAGE_AREA, images, false),
+            16000);
+  EXPECT_EQ(module.ComputeAndGetPageLevelFeatureValue(
+                FeatureLibrary::MAX_IMAGE_ONPAGE_ASPECT_RATIO, images, false),
+            10);
+  EXPECT_EQ(module.ComputeAndGetPageLevelFeatureValue(
+                FeatureLibrary::MAX_IMAGE_VISIBLE_AREA, images, false),
+            400);
+  EXPECT_EQ(module.ComputeAndGetPageLevelFeatureValue(
+                FeatureLibrary::MAX_IMAGE_FRACTION_VISIBLE, images, false),
+            0.0625);
+}
+}  // namespace companion::visual_search
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 6cb502b..4d945c6 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -1313,56 +1313,6 @@
 }
 
 if (!is_android) {
-  if (is_mac) {
-    # TODO(https://crbug.com/1280317): Merge back into browser_tests once all
-    # .mm files are ARCed.
-    source_set("browser_tests_arc") {
-      testonly = true
-      sources = [
-        "../browser/apps/app_shim/app_shim_listener_browsertest_mac.mm",
-        "../browser/ui/cocoa/applescript/bookmark_applescript_test_utils.h",
-        "../browser/ui/cocoa/applescript/bookmark_applescript_test_utils.mm",
-        "../browser/ui/cocoa/applescript/bookmark_folder_applescript_browsertest.mm",
-        "../browser/ui/cocoa/applescript/bookmark_item_applescript_browsertest.mm",
-        "../browser/ui/cocoa/applescript/browsercrapplication+applescript_browsertest.mm",
-        "../browser/ui/cocoa/applescript/tab_applescript_browsertest.mm",
-        "../browser/ui/cocoa/applescript/window_applescript_browsertest.mm",
-        "../browser/ui/cocoa/share_menu_controller_browsertest.mm",
-        "../browser/ui/cocoa/touchbar/browser_window_touch_bar_controller_browsertest.mm",
-      ]
-      configs += [ "//build/config/compiler:enable_arc" ]
-      defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ]
-      deps = [
-        "//base",
-        "//base/test:test_support",
-        "//chrome/app:command_ids",
-        "//chrome/app_shim",
-        "//chrome/browser",
-        "//chrome/browser:browser_process",
-        "//chrome/browser/apps/app_shim",
-        "//chrome/browser/profiles:profile",
-        "//chrome/browser/ui",
-        "//chrome/browser/ui:test_support",
-        "//chrome/common:app_mode_app_support",
-        "//chrome/common:constants",
-        "//chrome/common:mojo_bindings",
-        "//chrome/test:test_support",
-        "//chrome/test:test_support_ui",
-        "//components/bookmarks/browser",
-        "//components/bookmarks/test",
-        "//components/prefs",
-        "//components/remote_cocoa/app_shim",
-        "//components/version_info",
-        "//content/test:test_support",
-        "//ipc",
-        "//mojo/core/embedder",
-        "//skia",
-        "//third_party/abseil-cpp:absl",
-        "//ui/events:test_support",
-      ]
-    }
-  }
-
   test("browser_tests") {
     use_xvfb = use_xvfb_in_this_config
 
@@ -1721,6 +1671,7 @@
       "//ui/base/clipboard:clipboard_test_support",
       "//ui/base/dragdrop/mojom",
       "//ui/color:color",
+      "//ui/color:mixers",
       "//ui/compositor:test_support",
       "//ui/display:test_support",
       "//ui/native_theme:test_support",
@@ -2815,21 +2766,35 @@
       }
     }
 
+    if (is_apple) {
+      configs += [ "//build/config/compiler:enable_arc" ]
+    }
+
     if (is_mac) {
       sources += [
         "../browser/app_controller_mac_browsertest.mm",
+        "../browser/apps/app_shim/app_shim_listener_browsertest_mac.mm",
         "../browser/apps/app_shim/test/app_shim_listener_test_api_mac.cc",
         "../browser/apps/app_shim/test/app_shim_listener_test_api_mac.h",
         "../browser/mac/auth_session_request_browsertest.mm",
         "../browser/renderer_host/chrome_render_widget_host_view_mac_history_swiper_browsertest.mm",
         "../browser/spellchecker/spell_check_host_chrome_impl_mac_browsertest.cc",
         "../browser/ui/cocoa/accelerators_cocoa_browsertest.mm",
+        "../browser/ui/cocoa/applescript/bookmark_applescript_test_utils.h",
+        "../browser/ui/cocoa/applescript/bookmark_applescript_test_utils.mm",
+        "../browser/ui/cocoa/applescript/bookmark_folder_applescript_browsertest.mm",
+        "../browser/ui/cocoa/applescript/bookmark_item_applescript_browsertest.mm",
+        "../browser/ui/cocoa/applescript/browsercrapplication+applescript_browsertest.mm",
+        "../browser/ui/cocoa/applescript/tab_applescript_browsertest.mm",
+        "../browser/ui/cocoa/applescript/window_applescript_browsertest.mm",
         "../browser/ui/cocoa/apps/app_shim_menu_controller_mac_browsertest.mm",
         "../browser/ui/cocoa/apps/native_app_window_cocoa_browsertest.mm",
         "../browser/ui/cocoa/browser_window_mac_browsertest.mm",
         "../browser/ui/cocoa/color_panel_cocoa_unittest.mm",
         "../browser/ui/cocoa/renderer_context_menu/render_view_context_menu_mac_cocoa_browsertest.mm",
+        "../browser/ui/cocoa/share_menu_controller_browsertest.mm",
         "../browser/ui/cocoa/task_manager_mac_browsertest.mm",
+        "../browser/ui/cocoa/touchbar/browser_window_touch_bar_controller_browsertest.mm",
         "../browser/ui/find_bar/find_bar_platform_helper_mac_browsertest.mm",
         "../browser/ui/test/test_browser_dialog_mac.h",
         "../browser/ui/test/test_browser_dialog_mac.mm",
@@ -2852,7 +2817,6 @@
       ]
 
       deps += [
-        ":browser_tests_arc",
         "//chrome/app_shim",
         "//chrome/browser/apps/app_shim",
         "//chrome/browser/renderer_host:history_swiper",
@@ -2863,7 +2827,6 @@
         "//third_party/ocmock",
         "//ui/accelerated_widget_mac",
       ]
-      allow_circular_includes_from = [ ":browser_tests_arc" ]
     } else {
       # !is_mac
       sources += [
@@ -5609,6 +5572,7 @@
       "../browser/mac/auth_session_request_unittest.mm",
       "../browser/mac/keystone_glue_unittest.mm",
       "../browser/ui/cocoa/applescript/apple_event_util_unittest.mm",
+      "../browser/ui/cocoa/first_run_dialog_controller_unittest.mm",
       "../browser/ui/cocoa/scoped_menu_bar_lock_unittest.mm",
       "../browser/ui/cocoa/screentime/screentime_tab_helper_unittest.mm",
       "../browser/ui/cocoa/status_icons/status_icon_mac_unittest.mm",
@@ -6056,6 +6020,7 @@
     "../renderer/cart/commerce_hint_agent_unittest.cc",
     "../renderer/chrome_content_renderer_client_unittest.cc",
     "../renderer/chrome_render_frame_observer_unittest.cc",
+    "../renderer/companion/visual_search/visual_search_eligibility_unittest.cc",
     "../renderer/instant_restricted_id_cache_unittest.cc",
     "../renderer/media/flash_embed_rewrite_unittest.cc",
     "../renderer/net/net_error_helper_core_unittest.cc",
@@ -6539,6 +6504,7 @@
     "//ui/base:test_support",
     "//ui/base/clipboard:clipboard_test_support",
     "//ui/color:color",
+    "//ui/color:mixers",
     "//ui/display:test_support",
     "//ui/events:gesture_detection",
     "//ui/events:test_support",
@@ -6710,7 +6676,6 @@
       "../browser/ui/cocoa/bookmarks/bookmark_menu_cocoa_controller_unittest.mm",
       "../browser/ui/cocoa/confirm_quit_panel_controller_unittest.mm",
       "../browser/ui/cocoa/find_pasteboard_unittest.mm",
-      "../browser/ui/cocoa/first_run_dialog_controller_unittest.mm",
       "../browser/ui/cocoa/history_menu_bridge_unittest.mm",
       "../browser/ui/cocoa/history_menu_cocoa_controller_unittest.mm",
       "../browser/ui/cocoa/history_overlay_controller_unittest.mm",
@@ -7074,7 +7039,6 @@
       "../browser/enterprise/reporting/extension_request/extension_request_observer_factory_unittest.cc",
       "../browser/enterprise/reporting/extension_request/extension_request_observer_unittest.cc",
       "../browser/enterprise/reporting/extension_request/extension_request_report_generator_unittest.cc",
-      "../browser/enterprise/reporting/real_time_report_controller_unittest.cc",
       "../browser/enterprise/reporting/real_time_report_generator_unittest.cc",
       "../browser/enterprise/signals/client_certificate_fetcher_unittest.cc",
       "../browser/first_run/first_run_unittest.cc",
@@ -9593,14 +9557,6 @@
       ]
     }
 
-    if (is_mac) {
-      sources += [
-        "../browser/ui/test/test_browser_dialog_mac.h",
-        "../browser/ui/test/test_browser_dialog_mac.mm",
-        "base/in_process_browser_test_mac.mm",
-      ]
-    }
-
     configs += [ "//build/config:precompiled_headers" ]
 
     public_deps = [
@@ -9707,9 +9663,18 @@
       ]
     }
 
+    if (is_apple) {
+      configs += [ "//build/config/compiler:enable_arc" ]
+    }
+
     if (is_mac) {
+      sources += [
+        "../browser/ui/test/test_browser_dialog_mac.h",
+        "../browser/ui/test/test_browser_dialog_mac.mm",
+        "base/in_process_browser_test_mac.mm",
+        "base/interactive_test_utils_mac.mm",
+      ]
       deps += [ "//chrome/browser/apps/app_shim:app_shim" ]
-      sources += [ "base/interactive_test_utils_mac.mm" ]
     }
 
     if (is_win) {
diff --git a/chrome/test/base/in_process_browser_test_mac.mm b/chrome/test/base/in_process_browser_test_mac.mm
index 394141965..5816342 100644
--- a/chrome/test/base/in_process_browser_test_mac.mm
+++ b/chrome/test/base/in_process_browser_test_mac.mm
@@ -13,6 +13,10 @@
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "content/public/test/test_navigation_observer.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 void InProcessBrowserTest::OpenDevToolsWindow(
     content::WebContents* web_contents) {
   // Opening a Devtools Window can cause AppKit to throw objects into the
@@ -84,7 +88,8 @@
   // autorelease pool. Flush the pool when this function returns.
   @autoreleasepool {
     Browser* browser = Browser::Create(Browser::CreateParams::CreateForApp(
-        app_name, false /* trusted_source */, gfx::Rect(), profile, true));
+        app_name, /*trusted_source=*/false, gfx::Rect(), profile,
+        /*user_gesture=*/true));
     AddBlankTabAndShow(browser);
     return browser;
   }
diff --git a/chrome/test/base/interactive_test_utils_mac.mm b/chrome/test/base/interactive_test_utils_mac.mm
index 0063eaf..dcebc6a2 100644
--- a/chrome/test/base/interactive_test_utils_mac.mm
+++ b/chrome/test/base/interactive_test_utils_mac.mm
@@ -19,6 +19,10 @@
 #include "ui/events/cocoa/cocoa_event_utils.h"
 #include "ui/events/event_constants.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 @interface NSApplication (Private)
 // (Apparently) forces the application to activate itself.
 - (void)_handleActivatedEvent:(id)arg1;
@@ -175,15 +179,15 @@
   // We used to call [NSApp activateIgnoringOtherApps:YES] but this
   // would not reliably activate the app, causing the window to never
   // become key. This bit of private API appears to be the secret
-  // incantation that gets us what we want. See crbug.com/1215570 .
-  [[NSApplication sharedApplication] _handleActivatedEvent:nil];
+  // incantation that gets us what we want. See https://crbug.com/1215570.
+  [NSApplication.sharedApplication _handleActivatedEvent:nil];
 
-  base::scoped_nsobject<WindowedNSNotificationObserver> async_waiter;
-  if (![window isKeyWindow]) {
+  WindowedNSNotificationObserver* async_waiter;
+  if (!window.keyWindow) {
     // Only wait when expecting a change to actually occur.
-    async_waiter.reset([[WindowedNSNotificationObserver alloc]
+    async_waiter = [[WindowedNSNotificationObserver alloc]
         initForNotification:NSWindowDidBecomeKeyNotification
-                     object:window]);
+                     object:window];
   }
   [window makeKeyAndOrderFront:nil];
 
@@ -193,8 +197,8 @@
   // events are sent via ui_test_utils::SendKeyPressSync.
   BOOL notification_observed = [async_waiter wait];
   base::RunLoop().RunUntilIdle();  // There may be other events queued. Flush.
-  NSMenu* file_menu = [[[NSApp mainMenu] itemWithTag:IDC_FILE_MENU] submenu];
-  [[file_menu delegate] menuNeedsUpdate:file_menu];
+  NSMenu* file_menu = [[NSApp.mainMenu itemWithTag:IDC_FILE_MENU] submenu];
+  [file_menu.delegate menuNeedsUpdate:file_menu];
 
   return !async_waiter || notification_observed;
 }
diff --git a/chrome/test/data/pdf/annotations_feature_enabled_test.ts b/chrome/test/data/pdf/annotations_feature_enabled_test.ts
index da5fb4f..cfffbea 100644
--- a/chrome/test/data/pdf/annotations_feature_enabled_test.ts
+++ b/chrome/test/data/pdf/annotations_feature_enabled_test.ts
@@ -72,8 +72,8 @@
         dark_light_left: -105.75,
         right: 718.5,
         dark_light_right: 717.75,
-        bottom: -412.5,
-        dark_light_bottom: -411.75,
+        bottom: -408.75,
+        dark_light_bottom: -408.0,
       },
       {
         top: 2.25,
@@ -82,8 +82,8 @@
         dark_light_left: -3.75,
         right: 408.75,
         dark_light_right: 408,
-        bottom: -205.125,
-        dark_light_bottom: -204.75,
+        bottom: -203.25,
+        dark_light_bottom: -202.875,
       },
       {
         top: -35.25,
@@ -92,8 +92,8 @@
         dark_light_left: 33.75,
         right: 446.25,
         dark_light_right: 445.5,
-        bottom: -242.625,
-        dark_light_bottom: -242.25,
+        bottom: -240.75,
+        dark_light_bottom: -240.375,
       },
     ];
 
diff --git a/chrome/test/data/web_apps/no_manifest_id.json b/chrome/test/data/web_apps/no_manifest_id.json
new file mode 100644
index 0000000..4f2b46cba
--- /dev/null
+++ b/chrome/test/data/web_apps/no_manifest_id.json
@@ -0,0 +1,12 @@
+{
+  "name": "Web app with no manifest id",
+  "icons": [
+    {
+      "src": "basic-192.png",
+      "sizes": "192x192",
+      "type": "image/png"
+    }
+  ],
+  "start_url": "basic.html",
+  "display": "standalone"
+}
diff --git a/chrome/test/data/webui/chromeos/personalization_app/personalization_theme_element_test.ts b/chrome/test/data/webui/chromeos/personalization_app/personalization_theme_element_test.ts
index 4762e34..c2df73b 100644
--- a/chrome/test/data/webui/chromeos/personalization_app/personalization_theme_element_test.ts
+++ b/chrome/test/data/webui/chromeos/personalization_app/personalization_theme_element_test.ts
@@ -42,9 +42,9 @@
     personalizationThemeElement = initElement(PersonalizationThemeElement);
     await waitAfterNextRender(personalizationThemeElement);
 
-    assertEquals(
-        personalizationThemeElement.i18n('themeLabel'),
-        personalizationThemeElement.shadowRoot!.querySelector('h2')!.innerText);
+    const radioButton =
+        personalizationThemeElement.shadowRoot!.getElementById('darkMode');
+    assertTrue(!!radioButton);
   });
 
   test('sets color mode in store on first load', async () => {
diff --git a/chrome/test/data/webui/new_tab_page/realbox/lens_test.ts b/chrome/test/data/webui/new_tab_page/realbox/lens_test.ts
index 0319f23..b469e8d 100644
--- a/chrome/test/data/webui/new_tab_page/realbox/lens_test.ts
+++ b/chrome/test/data/webui/new_tab_page/realbox/lens_test.ts
@@ -81,11 +81,8 @@
     document.body.appendChild(realbox);
   });
 
-  test('Lens search button is visible when feature is flipped', async () => {
+  test('Lens search button is visible by default', async () => {
     // Arrange.
-    loadTimeData.overrideValues({
-      realboxLensSearch: true,
-    });
     document.body.innerHTML = window.trustedTypes!.emptyHTML;
     realbox = document.createElement('ntp-realbox');
     document.body.appendChild(realbox);
diff --git a/chrome/test/data/webui/new_tab_page/realbox/realbox_test.ts b/chrome/test/data/webui/new_tab_page/realbox/realbox_test.ts
index c88a64a4..0fe31e2 100644
--- a/chrome/test/data/webui/new_tab_page/realbox/realbox_test.ts
+++ b/chrome/test/data/webui/new_tab_page/realbox/realbox_test.ts
@@ -184,6 +184,7 @@
   }
 
   test('when created is not focused and matches are not showing', async () => {
+    assertEquals(0, testProxy.handler.getCallCount('onFocusChanged'));
     assertFalse(realbox.hidden);
     assertNotEquals(realbox, getDeepActiveElement());
     assertFalse(areMatchesShowing());
@@ -282,6 +283,7 @@
     // Left click does not query autocomplete when matches are showing.
     realbox.$.input.dispatchEvent(new MouseEvent('mousedown', {button: 0}));
     assertEquals(0, testProxy.handler.getCallCount('queryAutocomplete'));
+    assertEquals(1, testProxy.handler.getCallCount('onFocusChanged'));
 
     // Hide the matches by focusing out.
     matchEls[0]!.dispatchEvent(new FocusEvent('focusout', {
@@ -294,6 +296,7 @@
     // Right click does not query autocomplete.
     realbox.$.input.dispatchEvent(new MouseEvent('mousedown', {button: 1}));
     assertEquals(0, testProxy.handler.getCallCount('queryAutocomplete'));
+    assertEquals(2, testProxy.handler.getCallCount('onFocusChanged'));
 
     // Left click does not query autocomplete when input is non-empty.
     realbox.$.input.value = '   ';
@@ -302,10 +305,12 @@
   });
 
   test('focusing the input does not query autocomplete', async () => {
+    assertEquals(0, testProxy.handler.getCallCount('onFocusChanged'));
     realbox.$.input.value = '';
     realbox.$.input.focus();
     assertEquals(realbox.$.input, getDeepActiveElement());
     assertEquals(0, testProxy.handler.getCallCount('queryAutocomplete'));
+    assertEquals(1, testProxy.handler.getCallCount('onFocusChanged'));
   });
 
   test('tabbing into empty input queries autocomplete', async () => {
@@ -949,7 +954,6 @@
       assertEquals(matches[0]!.destinationUrl.url, args.url.url);
       assertTrue(args.areMatchesShowing);
       assertTrue(args.shiftKey);
-      assertTrue(args.timeElapsedSinceLastFocus.microseconds > 0);
     });
     assertEquals(1, testProxy.handler.getCallCount('openAutocompleteMatch'));
   });
@@ -1029,7 +1033,6 @@
               assertEquals(matches[0]!.destinationUrl.url, args.url.url);
               assertFalse(args.areMatchesShowing);
               assertTrue(args.shiftKey);
-              assertTrue(args.timeElapsedSinceLastFocus.microseconds > 0);
             });
         assertEquals(
             1, testProxy.handler.getCallCount('openAutocompleteMatch'));
@@ -1205,7 +1208,6 @@
       assertEquals(matches[0]!.destinationUrl.url, args.url.url);
       assertTrue(args.areMatchesShowing);
       assertTrue(args.shiftKey);
-      assertTrue(args.timeElapsedSinceLastFocus.microseconds > 0);
     });
     assertEquals(1, testProxy.handler.getCallCount('openAutocompleteMatch'));
   });
@@ -1254,7 +1256,6 @@
       assertEquals(matches[0]!.destinationUrl.url, args.url.url);
       assertTrue(args.areMatchesShowing);
       assertTrue(args.shiftKey);
-      assertTrue(args.timeElapsedSinceLastFocus.microseconds > 0);
     });
     assertEquals(1, testProxy.handler.getCallCount('openAutocompleteMatch'));
   });
@@ -1305,7 +1306,6 @@
       assertEquals(matches[0]!.destinationUrl.url, args.url.url);
       assertTrue(args.areMatchesShowing);
       assertEquals(1, args.mouseButton);
-      assertTrue(args.timeElapsedSinceLastFocus.microseconds > 0);
     });
     assertEquals(1, testProxy.handler.getCallCount('openAutocompleteMatch'));
 
@@ -1326,7 +1326,6 @@
       assertEquals(matches[0]!.destinationUrl.url, args.url.url);
       assertTrue(args.areMatchesShowing);
       assertEquals(0, args.mouseButton);
-      assertTrue(args.timeElapsedSinceLastFocus.microseconds > 0);
     });
     assertEquals(1, testProxy.handler.getCallCount('openAutocompleteMatch'));
   });
@@ -1729,6 +1728,7 @@
     realbox.$.input.focus();
     realbox.$.input.value = 'hello';
     realbox.$.input.dispatchEvent(new InputEvent('input'));
+    assertEquals(1, testProxy.handler.getCallCount('onFocusChanged'));
 
     const matches = [createSearchMatch(), createUrlMatch()];
     testProxy.callbackRouterRemote.autocompleteResultChanged({
@@ -1816,6 +1816,10 @@
     assertTrue(matchEls[0]!.hasAttribute(Attributes.SELECTED));
     assertEquals('hello world', realbox.$.input.value);
     assertEquals(matchEls[0], realbox.$.matches.shadowRoot!.activeElement);
+
+    // Changing match selection doesn't result in another onFocusChanged call
+    // because focus is for the whole realbox (including input container).
+    assertEquals(1, testProxy.handler.getCallCount('onFocusChanged'));
   });
 
   test('focus indicator', async () => {
diff --git a/chrome/test/data/webui/new_tab_page/realbox/test_realbox_browser_proxy.ts b/chrome/test/data/webui/new_tab_page/realbox/test_realbox_browser_proxy.ts
index 01f0a10..7d712c20 100644
--- a/chrome/test/data/webui/new_tab_page/realbox/test_realbox_browser_proxy.ts
+++ b/chrome/test/data/webui/new_tab_page/realbox/test_realbox_browser_proxy.ts
@@ -4,7 +4,7 @@
 
 import {NavigationPredictor, PageCallbackRouter, PageHandlerInterface, PageRemote} from 'chrome://resources/cr_components/omnibox/omnibox.mojom-webui.js';
 import {String16} from 'chrome://resources/mojo/mojo/public/mojom/base/string16.mojom-webui.js';
-import {TimeDelta, TimeTicks} from 'chrome://resources/mojo/mojo/public/mojom/base/time.mojom-webui.js';
+import {TimeTicks} from 'chrome://resources/mojo/mojo/public/mojom/base/time.mojom-webui.js';
 import {Url} from 'chrome://resources/mojo/url/mojom/url.mojom-webui.js';
 import {TestBrowserProxy} from 'chrome://webui-test/test_browser_proxy.js';
 
@@ -25,6 +25,7 @@
       'queryAutocomplete',
       'stopAutocomplete',
       'toggleSuggestionGroupIdVisibility',
+      'onFocusChanged',
     ]);
   }
 
@@ -32,6 +33,10 @@
     this.methodCalled('setPage', page);
   }
 
+  onFocusChanged(focused: boolean) {
+    this.methodCalled('onFocusChanged', {focused});
+  }
+
   deleteAutocompleteMatch(line: number, url: Url) {
     this.methodCalled('deleteAutocompleteMatch', {line, url});
   }
@@ -54,14 +59,12 @@
   }
 
   openAutocompleteMatch(
-      line: number, url: Url, areMatchesShowing: boolean,
-      timeElapsedSinceLastFocus: TimeDelta, mouseButton: number,
+      line: number, url: Url, areMatchesShowing: boolean, mouseButton: number,
       altKey: boolean, ctrlKey: boolean, metaKey: boolean, shiftKey: boolean) {
     this.methodCalled('openAutocompleteMatch', {
       line,
       url,
       areMatchesShowing,
-      timeElapsedSinceLastFocus,
       mouseButton,
       altKey,
       ctrlKey,
diff --git a/chrome/test/data/webui/password_manager/BUILD.gn b/chrome/test/data/webui/password_manager/BUILD.gn
index 4d151d8..a25c629 100644
--- a/chrome/test/data/webui/password_manager/BUILD.gn
+++ b/chrome/test/data/webui/password_manager/BUILD.gn
@@ -14,6 +14,7 @@
     "checkup_details_section_test.ts",
     "checkup_section_test.ts",
     "edit_password_dialog_test.ts",
+    "edit_passkey_dialog_test.ts",
     "move_passwords_dialog_test.ts",
     "credential_field_test.ts",
     "credential_note_test.ts",
diff --git a/chrome/test/data/webui/password_manager/edit_passkey_dialog_test.ts b/chrome/test/data/webui/password_manager/edit_passkey_dialog_test.ts
new file mode 100644
index 0000000..18302cd
--- /dev/null
+++ b/chrome/test/data/webui/password_manager/edit_passkey_dialog_test.ts
@@ -0,0 +1,72 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'chrome://password-manager/password_manager.js';
+
+import {EditPasskeyDialogElement, PasswordManagerImpl} from 'chrome://password-manager/password_manager.js';
+import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
+import {assertEquals, assertFalse} from 'chrome://webui-test/chai_assert.js';
+import {flushTasks} from 'chrome://webui-test/polymer_test_util.js';
+
+import {TestPasswordManagerProxy} from './test_password_manager_proxy.js';
+import {createAffiliatedDomain, createPasswordEntry} from './test_util.js';
+
+suite('EditPasskeyDialogTest', function() {
+  let passwordManager: TestPasswordManagerProxy;
+  let passkey: chrome.passwordsPrivate.PasswordUiEntry;
+  let dialog: EditPasskeyDialogElement;
+
+  setup(async function() {
+    document.body.innerHTML = window.trustedTypes!.emptyHTML;
+    passwordManager = new TestPasswordManagerProxy();
+    PasswordManagerImpl.setInstance(passwordManager);
+    await flushTasks();
+
+    passkey = createPasswordEntry({
+      id: 0,
+      username: 'pikari',
+      displayName: 'Hikari Kohinata',
+      isPasskey: true,
+    });
+    passkey.affiliatedDomains = [createAffiliatedDomain('test.com')];
+    dialog = document.createElement('edit-passkey-dialog');
+    dialog.passkey = passkey;
+    document.body.appendChild(dialog);
+    await flushTasks();
+  });
+
+  test('passkey displayed correctly', async function() {
+    assertEquals(dialog.$.usernameInput.value, passkey.username);
+    assertEquals(
+        dialog.$.usernameInput.placeholder,
+        loadTimeData.getString('usernamePlaceholder'));
+    assertEquals(dialog.$.displayNameInput.value, passkey.displayName);
+    assertEquals(
+        dialog.$.displayNameInput.placeholder,
+        loadTimeData.getString('displayNamePlaceholder'));
+
+    const listItemElements =
+        dialog.shadowRoot!.querySelectorAll<HTMLAnchorElement>('a.site-link');
+    assertEquals(listItemElements.length, 1);
+    assertEquals(listItemElements[0]!.textContent!.trim(), 'test.com');
+    assertEquals(listItemElements[0]!.href, passkey.affiliatedDomains![0]!.url);
+  });
+
+  test('passkey is updated', async function() {
+    dialog.$.usernameInput.value = 'teko';
+    dialog.$.displayNameInput.value = 'Futaba Ooki';
+
+    assertFalse(dialog.$.saveButton.disabled);
+    dialog.$.saveButton.click();
+
+    const updatedCredential =
+        await passwordManager.whenCalled('changeCredential');
+
+    assertEquals(updatedCredential.id, passkey.id);
+    assertEquals(updatedCredential.username, dialog.$.usernameInput.value);
+    assertEquals(
+        updatedCredential.displayName, dialog.$.displayNameInput.value);
+    assertFalse(dialog.$.dialog.open);
+  });
+});
diff --git a/chrome/test/data/webui/password_manager/passkey_details_card_test.ts b/chrome/test/data/webui/password_manager/passkey_details_card_test.ts
index 09b6a55..2e7e831 100644
--- a/chrome/test/data/webui/password_manager/passkey_details_card_test.ts
+++ b/chrome/test/data/webui/password_manager/passkey_details_card_test.ts
@@ -4,10 +4,10 @@
 
 import 'chrome://password-manager/password_manager.js';
 
-import {Page, PasswordManagerImpl, Router} from 'chrome://password-manager/password_manager.js';
+import {EditPasskeyDialogElement, Page, PasswordManagerImpl, PasswordViewPageInteractions, Router} from 'chrome://password-manager/password_manager.js';
 import {assertEquals, assertTrue} from 'chrome://webui-test/chai_assert.js';
 import {flushTasks} from 'chrome://webui-test/polymer_test_util.js';
-import {isVisible} from 'chrome://webui-test/test_util.js';
+import {eventToPromise, isVisible} from 'chrome://webui-test/test_util.js';
 
 import {TestPasswordManagerProxy} from './test_password_manager_proxy.js';
 import {createAffiliatedDomain, createPasswordEntry} from './test_util.js';
@@ -52,4 +52,34 @@
         passkey.affiliatedDomains![0]!.name, domains[0]!.textContent!.trim());
     assertEquals(passkey.affiliatedDomains![0]!.url, domains[0]!.href);
   });
+
+  test('Clicking edit button opens an edit dialog', async function() {
+    const passkey = createPasswordEntry({
+      url: 'test.com',
+      username: 'reimu',
+      isPasskey: true,
+      displayName: 'Reimu Hakurei',
+      note: 'Remember the milk',
+      affiliatedDomains: [createAffiliatedDomain('test.com')],
+    });
+
+    const card = document.createElement('passkey-details-card');
+    card.passkey = passkey;
+    document.body.appendChild(card);
+    await flushTasks();
+
+    card.$.editButton.click();
+    await eventToPromise('cr-dialog-open', card);
+    await passwordManager.whenCalled('extendAuthValidity');
+    assertEquals(
+        PasswordViewPageInteractions.PASSKEY_EDIT_BUTTON_CLICKED,
+        await passwordManager.whenCalled('recordPasswordViewInteraction'));
+    await flushTasks();
+
+    const editDialog = card.shadowRoot!.querySelector<EditPasskeyDialogElement>(
+        'edit-passkey-dialog');
+    assertTrue(!!editDialog);
+    assertTrue(editDialog.$.dialog.open);
+  });
+
 });
diff --git a/chrome/test/data/webui/password_manager/password_manager_browsertest.js b/chrome/test/data/webui/password_manager/password_manager_browsertest.js
index aef4ddb..02f0a4f 100644
--- a/chrome/test/data/webui/password_manager/password_manager_browsertest.js
+++ b/chrome/test/data/webui/password_manager/password_manager_browsertest.js
@@ -31,6 +31,7 @@
  ['CheckupDetails', 'checkup_details_section_test.js'],
  ['CredentialField', 'credential_field_test.js'],
  ['CredentialNote', 'credential_note_test.js'],
+ ['EditPasskey', 'edit_passkey_dialog_test.js'],
  ['EditPassword', 'edit_password_dialog_test.js'],
  ['MovePasswordsDialog', 'move_passwords_dialog_test.js'],
  ['PasskeyCard', 'passkey_details_card_test.js'],
diff --git a/chrome/test/data/webui/settings/chromeos/BUILD.gn b/chrome/test/data/webui/settings/chromeos/BUILD.gn
index 838aa59..309acc8 100644
--- a/chrome/test/data/webui/settings/chromeos/BUILD.gn
+++ b/chrome/test/data/webui/settings/chromeos/BUILD.gn
@@ -45,7 +45,6 @@
     "fake_user_action_recorder.js",
     "fake_users_private.js",
     "input_page_test.js",
-    "internet_config_test.js",
     "internet_detail_menu_test.js",
     "internet_known_networks_subpage_tests.js",
     "internet_page_tests.js",
@@ -145,6 +144,7 @@
     "internet_page/hotspot_config_dialog_test.ts",
     "internet_page/hotspot_subpage_test.ts",
     "internet_page/hotspot_summary_item_test.ts",
+    "internet_page/internet_config_test.ts",
     "internet_page/internet_detail_subpage_tests.js",
     "internet_page/network_always_on_vpn_test.ts",
     "internet_page/network_summary_item_test.ts",
diff --git a/chrome/test/data/webui/settings/chromeos/internet_config_test.js b/chrome/test/data/webui/settings/chromeos/internet_config_test.js
deleted file mode 100644
index 3bf3cdb..0000000
--- a/chrome/test/data/webui/settings/chromeos/internet_config_test.js
+++ /dev/null
@@ -1,78 +0,0 @@
-// Copyright 2020 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import {setUserActionRecorderForTesting, userActionRecorderMojom} from 'chrome://os-settings/os_settings.js';
-import {MojoInterfaceProviderImpl} from 'chrome://resources/ash/common/network/mojo_interface_provider.js';
-import {OncMojo} from 'chrome://resources/ash/common/network/onc_mojo.js';
-import {CrosNetworkConfigRemote} from 'chrome://resources/mojo/chromeos/services/network_config/public/mojom/cros_network_config.mojom-webui.js';
-import {NetworkType} from 'chrome://resources/mojo/chromeos/services/network_config/public/mojom/network_types.mojom-webui.js';
-import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
-import {FakeNetworkConfig} from 'chrome://webui-test/chromeos/fake_network_config_mojom.js';
-
-import {FakeUserActionRecorder} from './fake_user_action_recorder.js';
-
-suite('InternetConfig', function() {
-  /** @type {!InternetConfig|undefined} */
-  let internetConfig;
-
-  /** @type {!CrosNetworkConfigRemote|undefined} */
-  let mojoApi_;
-
-  /** @type {?userActionRecorderMojom.UserActionRecorderInterface} */
-  let userActionRecorder;
-
-  suiteSetup(function() {
-    mojoApi_ = new FakeNetworkConfig();
-    MojoInterfaceProviderImpl.getInstance().remote_ = mojoApi_;
-  });
-
-  setup(function() {
-    internetConfig = document.createElement('internet-config');
-    internetConfig.type = OncMojo.getNetworkTypeString(NetworkType.kWiFi);
-    document.body.appendChild(internetConfig);
-    flush();
-
-    userActionRecorder = new FakeUserActionRecorder();
-    setUserActionRecorderForTesting(userActionRecorder);
-  });
-
-  test('Cancel button closes the dialog', function() {
-    internetConfig.open();
-    assertTrue(internetConfig.$.dialog.open);
-
-    internetConfig.shadowRoot.querySelector('cr-button.cancel-button').click();
-    assertFalse(internetConfig.$.dialog.open);
-  });
-
-  test('Connect button click increments settings change count', function() {
-    internetConfig.open();
-    internetConfig.showConnect = true;
-    flush();
-
-    const connectBtn =
-        internetConfig.shadowRoot.querySelector('#connectButton');
-    connectBtn.disabled = false;
-    flush();
-
-    assertFalse(connectBtn.disabled);
-    assertEquals(userActionRecorder.settingChangeCount, 0);
-    internetConfig.shadowRoot.querySelector('cr-button.action-button').click();
-    assertEquals(userActionRecorder.settingChangeCount, 1);
-  });
-
-  test('Save button click increments settings change count', function() {
-    internetConfig.open();
-    internetConfig.showConnect = false;
-    flush();
-
-    const saveBtn = internetConfig.shadowRoot.querySelector('#saveButton');
-    saveBtn.disabled = false;
-    flush();
-
-    assertFalse(saveBtn.disabled);
-    assertEquals(userActionRecorder.settingChangeCount, 0);
-    internetConfig.shadowRoot.querySelector('cr-button.action-button').click();
-    assertEquals(userActionRecorder.settingChangeCount, 1);
-  });
-});
diff --git a/chrome/test/data/webui/settings/chromeos/internet_page/internet_config_test.ts b/chrome/test/data/webui/settings/chromeos/internet_page/internet_config_test.ts
new file mode 100644
index 0000000..1c76f2f5
--- /dev/null
+++ b/chrome/test/data/webui/settings/chromeos/internet_page/internet_config_test.ts
@@ -0,0 +1,92 @@
+// Copyright 2020 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import {InternetConfigElement, setUserActionRecorderForTesting} from 'chrome://os-settings/os_settings.js';
+import {MojoInterfaceProviderImpl} from 'chrome://resources/ash/common/network/mojo_interface_provider.js';
+import {OncMojo} from 'chrome://resources/ash/common/network/onc_mojo.js';
+import {CrButtonElement} from 'chrome://resources/cr_elements/cr_button/cr_button.js';
+import {NetworkType} from 'chrome://resources/mojo/chromeos/services/network_config/public/mojom/network_types.mojom-webui.js';
+import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
+import {FakeNetworkConfig} from 'chrome://webui-test/chromeos/fake_network_config_mojom.js';
+
+import {FakeUserActionRecorder} from '../fake_user_action_recorder.js';
+
+suite('<internet-config>', () => {
+  let internetConfig: InternetConfigElement;
+  let mojoApi_: FakeNetworkConfig;
+  let userActionRecorder: FakeUserActionRecorder;
+
+  suiteSetup(() => {
+    mojoApi_ = new FakeNetworkConfig();
+    const mojoInstance = new MojoInterfaceProviderImpl();
+    mojoInstance.setMojoServiceRemoteForTest(mojoApi_);
+  });
+
+  setup(() => {
+    userActionRecorder = new FakeUserActionRecorder();
+    setUserActionRecorderForTesting(userActionRecorder);
+
+    internetConfig = document.createElement('internet-config');
+    internetConfig.type = OncMojo.getNetworkTypeString(NetworkType.kWiFi);
+    document.body.appendChild(internetConfig);
+    flush();
+  });
+
+  teardown(() => {
+    internetConfig.remove();
+  });
+
+  test('Cancel button closes the dialog', () => {
+    internetConfig.open();
+    assertTrue(internetConfig.$.dialog.open);
+
+    const button = internetConfig.shadowRoot!.querySelector<CrButtonElement>(
+        'cr-button.cancel-button');
+    assertTrue(!!button);
+    button.click();
+    assertFalse(internetConfig.$.dialog.open);
+  });
+
+  test('Connect button click increments settings change count', () => {
+    internetConfig.open();
+    internetConfig.showConnect = true;
+    flush();
+
+    const connectBtn =
+        internetConfig.shadowRoot!.querySelector<CrButtonElement>(
+            '#connectButton');
+    assertTrue(!!connectBtn);
+    connectBtn.disabled = false;
+    flush();
+
+    assertFalse(connectBtn.disabled);
+    assertEquals(0, userActionRecorder.settingChangeCount);
+    const button = internetConfig.shadowRoot!.querySelector<CrButtonElement>(
+        'cr-button.action-button');
+    assertTrue(!!button);
+    button.click();
+    assertEquals(1, userActionRecorder.settingChangeCount);
+  });
+
+  test('Save button click increments settings change count', () => {
+    internetConfig.open();
+    internetConfig.showConnect = false;
+    flush();
+
+    const saveBtn = internetConfig.shadowRoot!.querySelector<CrButtonElement>(
+        '#saveButton');
+    assertTrue(!!saveBtn);
+    saveBtn.disabled = false;
+    flush();
+
+    assertFalse(saveBtn.disabled);
+    assertEquals(0, userActionRecorder.settingChangeCount);
+    const button = internetConfig.shadowRoot!.querySelector<CrButtonElement>(
+        'cr-button.action-button');
+    assertTrue(!!button);
+    button.click();
+    assertEquals(1, userActionRecorder.settingChangeCount);
+  });
+});
diff --git a/chrome/test/data/webui/settings/chromeos/os_settings_browsertest.js b/chrome/test/data/webui/settings/chromeos/os_settings_browsertest.js
index ac2428b..47af7646 100644
--- a/chrome/test/data/webui/settings/chromeos/os_settings_browsertest.js
+++ b/chrome/test/data/webui/settings/chromeos/os_settings_browsertest.js
@@ -265,7 +265,6 @@
  ['GuestOsSharedPaths', 'guest_os/guest_os_shared_paths_test.js'],
  ['GuestOsSharedUsbDevices', 'guest_os/guest_os_shared_usb_devices_test.js'],
  ['InputPage', 'input_page_test.js'],
- ['InternetConfig', 'internet_config_test.js'],
  ['InternetDetailMenu', 'internet_detail_menu_test.js'],
  [
    'InternetKnownNetworksSubpage', 'internet_known_networks_subpage_tests.js', {
@@ -302,6 +301,7 @@
    'internet_page/hotspot_summary_item_test.js',
    {enabled: ['ash::features::kHotspot']},
  ],
+ ['InternetPageInternetConfig', 'internet_page/internet_config_test.js'],
  [
    'InternetPageInternetDetailSubpage',
    'internet_page/internet_detail_subpage_tests.js', {
diff --git a/chrome/test/data/webui/settings/performance_page_test.ts b/chrome/test/data/webui/settings/performance_page_test.ts
index 40cb4ab..526bdf58 100644
--- a/chrome/test/data/webui/settings/performance_page_test.ts
+++ b/chrome/test/data/webui/settings/performance_page_test.ts
@@ -7,8 +7,8 @@
 
 import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
 import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
-import {CrCheckboxElement, CrIconButtonElement, IronCollapseElement, SettingsRadioGroupElement} from 'chrome://settings/lazy_load.js';
-import {HIGH_EFFICIENCY_MODE_PREF, HighEfficiencyModeExceptionListAction, HighEfficiencyModeState, PerformanceBrowserProxyImpl, PerformanceMetricsProxyImpl, SettingsDropdownMenuElement, SettingsPerformancePageElement, TAB_DISCARD_EXCEPTIONS_MANAGED_PREF, TAB_DISCARD_EXCEPTIONS_OVERFLOW_SIZE, TAB_DISCARD_EXCEPTIONS_PREF, TabDiscardExceptionAddDialogElement, TabDiscardExceptionEditDialogElement, TabDiscardExceptionEntryElement, TabDiscardExceptionListElement, TabDiscardExceptionTabbedAddDialogElement} from 'chrome://settings/settings.js';
+import {CrIconButtonElement, IronCollapseElement, SettingsRadioGroupElement} from 'chrome://settings/lazy_load.js';
+import {HIGH_EFFICIENCY_MODE_PREF, HighEfficiencyModeExceptionListAction, HighEfficiencyModeState, PerformanceBrowserProxyImpl, PerformanceMetricsProxyImpl, SettingsDropdownMenuElement, SettingsPerformancePageElement, TAB_DISCARD_EXCEPTIONS_MANAGED_PREF, TAB_DISCARD_EXCEPTIONS_OVERFLOW_SIZE, TAB_DISCARD_EXCEPTIONS_PREF, TabDiscardExceptionAddDialogElement, TabDiscardExceptionCurrentSitesEntryElement, TabDiscardExceptionEditDialogElement, TabDiscardExceptionEntryElement, TabDiscardExceptionListElement, TabDiscardExceptionTabbedAddDialogElement} from 'chrome://settings/settings.js';
 import {assertDeepEquals, assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
 import {flushTasks} from 'chrome://webui-test/polymer_test_util.js';
 import {eventToPromise} from 'chrome://webui-test/test_util.js';
@@ -528,9 +528,10 @@
     flush();
 
     addDialog.$.list.$.list
-        .querySelectorAll<CrCheckboxElement>('cr-checkbox:not([hidden])')
-        .forEach(checkboxElement => {
-          checkboxElement.click();
+        .querySelectorAll<TabDiscardExceptionCurrentSitesEntryElement>(
+            'tab-discard-exception-current-sites-entry:not([hidden])')
+        .forEach(currentSitesEntryElement => {
+          currentSitesEntryElement.click();
         });
     assertFalse(addDialog.$.actionButton.disabled);
     addDialog.$.actionButton.click();
diff --git a/chrome/test/data/webui/settings/tab_discard_exception_dialog_test.ts b/chrome/test/data/webui/settings/tab_discard_exception_dialog_test.ts
index b7641ad..36ba639f 100644
--- a/chrome/test/data/webui/settings/tab_discard_exception_dialog_test.ts
+++ b/chrome/test/data/webui/settings/tab_discard_exception_dialog_test.ts
@@ -2,12 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import 'chrome://settings/lazy_load.js';
 import 'chrome://settings/settings.js';
 
 import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
-import {CrCheckboxElement} from 'chrome://settings/lazy_load.js';
-import {HighEfficiencyModeExceptionListAction, MAX_TAB_DISCARD_EXCEPTION_RULE_LENGTH, PerformanceBrowserProxyImpl, PerformanceMetricsProxyImpl, TAB_DISCARD_EXCEPTIONS_OVERFLOW_SIZE, TAB_DISCARD_EXCEPTIONS_PREF, TabDiscardExceptionAddDialogElement, TabDiscardExceptionAddDialogTabs, TabDiscardExceptionEditDialogElement, TabDiscardExceptionTabbedAddDialogElement} from 'chrome://settings/settings.js';
+import {HighEfficiencyModeExceptionListAction, MAX_TAB_DISCARD_EXCEPTION_RULE_LENGTH, PerformanceBrowserProxyImpl, PerformanceMetricsProxyImpl, TAB_DISCARD_EXCEPTIONS_OVERFLOW_SIZE, TAB_DISCARD_EXCEPTIONS_PREF, TabDiscardExceptionAddDialogElement, TabDiscardExceptionAddDialogTabs, TabDiscardExceptionCurrentSitesEntryElement, TabDiscardExceptionEditDialogElement, TabDiscardExceptionTabbedAddDialogElement} from 'chrome://settings/settings.js';
 import {assertDeepEquals, assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
 import {eventToPromise} from 'chrome://webui-test/test_util.js';
 
@@ -234,9 +232,12 @@
 
   function getRulesListEntry(
       dialog: TabDiscardExceptionTabbedAddDialogElement,
-      idx: number): CrCheckboxElement {
-    const entry = [...dialog.$.list.$.list.querySelectorAll<CrCheckboxElement>(
-        'cr-checkbox:not([hidden])')][idx];
+      idx: number): TabDiscardExceptionCurrentSitesEntryElement {
+    const entry = [
+      ...dialog.$.list.$.list
+          .querySelectorAll<TabDiscardExceptionCurrentSitesEntryElement>(
+              'tab-discard-exception-current-sites-entry:not([hidden])'),
+    ][idx];
     assertTrue(!!entry);
     return entry;
   }
diff --git a/chrome/updater/app/app.cc b/chrome/updater/app/app.cc
index cad6370..c3ac3275 100644
--- a/chrome/updater/app/app.cc
+++ b/chrome/updater/app/app.cc
@@ -38,10 +38,14 @@
 }
 
 void App::Shutdown(int exit_code) {
-  CHECK(!quit_.is_null()) << "App was shutdown previously.";
+  if (quit_.is_null()) {
+    // It's possible for shutdown to be called twice, since the runloop exits
+    // only when idle. The exit code of the first shutdown will be used.
+    return;
+  }
 
   // TODO(crbug.com/1422360): for non-silent scenarios where UI is not
-  // otherwise shown, some UI is needed here.
+  // otherwise shown, some UI is needed here if exit_code indicates a failure.
   std::move(quit_).Run(exit_code);
 }
 
diff --git a/chrome/updater/test/integration_test_commands.h b/chrome/updater/test/integration_test_commands.h
index f84d5ea4..d6e24032 100644
--- a/chrome/updater/test/integration_test_commands.h
+++ b/chrome/updater/test/integration_test_commands.h
@@ -30,7 +30,8 @@
  public:
   virtual void EnterTestMode(const GURL& update_url,
                              const GURL& crash_upload_url,
-                             const GURL& device_management_url) const = 0;
+                             const GURL& device_management_url,
+                             const base::TimeDelta& idle_timeout) const = 0;
   virtual void ExitTestMode() const = 0;
   virtual void SetGroupPolicies(const base::Value::Dict& values) const = 0;
   virtual void Clean() const = 0;
diff --git a/chrome/updater/test/integration_test_commands_system.cc b/chrome/updater/test/integration_test_commands_system.cc
index a870a8f..93680cba 100644
--- a/chrome/updater/test/integration_test_commands_system.cc
+++ b/chrome/updater/test/integration_test_commands_system.cc
@@ -88,11 +88,14 @@
 
   void EnterTestMode(const GURL& update_url,
                      const GURL& crash_upload_url,
-                     const GURL& device_management_url) const override {
+                     const GURL& device_management_url,
+                     const base::TimeDelta& idle_timeout) const override {
     RunCommand("enter_test_mode",
                {Param("update_url", update_url.spec()),
                 Param("crash_upload_url", crash_upload_url.spec()),
-                Param("device_management_url", device_management_url.spec())});
+                Param("device_management_url", device_management_url.spec()),
+                Param("idle_timeout",
+                      base::NumberToString(idle_timeout.InSeconds()))});
   }
 
   void ExitTestMode() const override { RunCommand("exit_test_mode"); }
diff --git a/chrome/updater/test/integration_test_commands_user.cc b/chrome/updater/test/integration_test_commands_user.cc
index 039a60d..8e69865 100644
--- a/chrome/updater/test/integration_test_commands_user.cc
+++ b/chrome/updater/test/integration_test_commands_user.cc
@@ -67,9 +67,10 @@
 
   void EnterTestMode(const GURL& update_url,
                      const GURL& crash_upload_url,
-                     const GURL& device_management_url) const override {
+                     const GURL& device_management_url,
+                     const base::TimeDelta& idle_timeout) const override {
     updater::test::EnterTestMode(update_url, crash_upload_url,
-                                 device_management_url);
+                                 device_management_url, idle_timeout);
   }
 
   void ExitTestMode() const override {
diff --git a/chrome/updater/test/integration_tests.cc b/chrome/updater/test/integration_tests.cc
index 08555fe..f1d2c16 100644
--- a/chrome/updater/test/integration_tests.cc
+++ b/chrome/updater/test/integration_tests.cc
@@ -100,9 +100,9 @@
     ASSERT_TRUE(WaitForUpdaterExit());
     ASSERT_NO_FATAL_FAILURE(Clean());
     ASSERT_NO_FATAL_FAILURE(ExpectClean());
-    ASSERT_NO_FATAL_FAILURE(EnterTestMode(GURL("http://localhost:1234"),
-                                          GURL("http://localhost:1235"),
-                                          GURL("http://localhost:1236")));
+    ASSERT_NO_FATAL_FAILURE(EnterTestMode(
+        GURL("http://localhost:1234"), GURL("http://localhost:1235"),
+        GURL("http://localhost:1236"), base::Minutes(5)));
 #if BUILDFLAG(IS_LINUX)
     // On LUCI the XDG_RUNTIME_DIR and DBUS_SESSION_BUS_ADDRESS environment
     // variables may not be set. These are required for systemctl to connect to
@@ -175,9 +175,10 @@
 
   void EnterTestMode(const GURL& update_url,
                      const GURL& crash_upload_url,
-                     const GURL& device_management_url) {
+                     const GURL& device_management_url,
+                     const base::TimeDelta& idle_timeout) {
     test_commands_->EnterTestMode(update_url, crash_upload_url,
-                                  device_management_url);
+                                  device_management_url, idle_timeout);
   }
 
   void ExitTestMode() { test_commands_->ExitTestMode(); }
@@ -1036,6 +1037,9 @@
     GTEST_SKIP() << "System server startup is complicated on Windows.";
   }
 #endif
+  ASSERT_NO_FATAL_FAILURE(EnterTestMode(
+      GURL("http://localhost:1234"), GURL("http://localhost:1234"),
+      GURL("http://localhost:1234"), base::Seconds(1)));
   ASSERT_NO_FATAL_FAILURE(Install());
   ASSERT_NO_FATAL_FAILURE(ExpectInstalled());
   ASSERT_NO_FATAL_FAILURE(RunServer(kErrorIdle, true));
diff --git a/chrome/updater/test/integration_tests_helper.cc b/chrome/updater/test/integration_tests_helper.cc
index da97704c..aba9d03 100644
--- a/chrome/updater/test/integration_tests_helper.cc
+++ b/chrome/updater/test/integration_tests_helper.cc
@@ -148,6 +148,22 @@
       }));
 }
 
+// Overload for TimeDelta switches.
+template <typename... Args>
+base::RepeatingCallback<bool(Args...)> WithSwitch(
+    const std::string& flag,
+    base::RepeatingCallback<bool(const base::TimeDelta&, Args...)> callback) {
+  return WithSwitch(
+      flag,
+      base::BindLambdaForTesting([=](const std::string& flag, Args... args) {
+        int flag_value;
+        if (base::StringToInt(flag, &flag_value)) {
+          return callback.Run(base::Seconds(flag_value), std::move(args)...);
+        }
+        return false;
+      }));
+}
+
 // Overload for base::Value::Dict switches.
 template <typename... Args>
 base::RepeatingCallback<bool(Args...)> WithSwitch(
@@ -255,9 +271,11 @@
     // then use the With* helper functions to provide its arguments.
     {"clean", WithSystemScope(Wrap(&Clean))},
     {"enter_test_mode",
-     WithSwitch("device_management_url",
-                WithSwitch("crash_upload_url",
-                           WithSwitch("update_url", Wrap(&EnterTestMode))))},
+     WithSwitch("idle_timeout",
+                WithSwitch("device_management_url",
+                           WithSwitch("crash_upload_url",
+                                      WithSwitch("update_url",
+                                                 Wrap(&EnterTestMode)))))},
     {"exit_test_mode", WithSystemScope(Wrap(&ExitTestMode))},
     {"set_group_policies", WithSwitch("values", Wrap(&SetGroupPolicies))},
     {"fill_log", WithSystemScope(Wrap(&FillLog))},
diff --git a/chrome/updater/test/integration_tests_impl.h b/chrome/updater/test/integration_tests_impl.h
index 5001341..10a88f9 100644
--- a/chrome/updater/test/integration_tests_impl.h
+++ b/chrome/updater/test/integration_tests_impl.h
@@ -66,7 +66,8 @@
 // Places the updater into test mode (redirect server URLs and disable CUP).
 void EnterTestMode(const GURL& update_url,
                    const GURL& crash_upload_url,
-                   const GURL& device_management_url);
+                   const GURL& device_management_url,
+                   const base::TimeDelta& idle_timeout);
 
 // Takes the updater our of the test mode by deleting the external constants
 // JSON file.
diff --git a/chrome/updater/test/integration_tests_linux.cc b/chrome/updater/test/integration_tests_linux.cc
index ef545662..e8b44ea9 100644
--- a/chrome/updater/test/integration_tests_linux.cc
+++ b/chrome/updater/test/integration_tests_linux.cc
@@ -140,7 +140,8 @@
 
 void EnterTestMode(const GURL& update_url,
                    const GURL& crash_upload_url,
-                   const GURL& device_management_url) {
+                   const GURL& device_management_url,
+                   const base::TimeDelta& idle_timeout) {
   ASSERT_TRUE(ExternalConstantsBuilder()
                   .SetUpdateURL({update_url.spec()})
                   .SetCrashUploadURL(crash_upload_url.spec())
@@ -150,7 +151,7 @@
                   .SetServerKeepAliveTime(base::Seconds(1))
                   .SetCrxVerifierFormat(crx_file::VerifierFormat::CRX3)
                   .SetOverinstallTimeout(TestTimeouts::action_timeout())
-                  .SetIdleCheckPeriod(base::Seconds(10))
+                  .SetIdleCheckPeriod(idle_timeout)
                   .Modify());
 }
 
diff --git a/chrome/updater/test/integration_tests_mac.mm b/chrome/updater/test/integration_tests_mac.mm
index e4b21d2..dcf1d4a 100644
--- a/chrome/updater/test/integration_tests_mac.mm
+++ b/chrome/updater/test/integration_tests_mac.mm
@@ -78,7 +78,8 @@
 
 void EnterTestMode(const GURL& update_url,
                    const GURL& crash_upload_url,
-                   const GURL& device_management_url) {
+                   const GURL& device_management_url,
+                   const base::TimeDelta& idle_timeout) {
   ASSERT_TRUE(ExternalConstantsBuilder()
                   .SetUpdateURL(std::vector<std::string>{update_url.spec()})
                   .SetCrashUploadURL(crash_upload_url.spec())
@@ -88,7 +89,7 @@
                   .SetServerKeepAliveTime(base::Seconds(1))
                   .SetCrxVerifierFormat(crx_file::VerifierFormat::CRX3)
                   .SetOverinstallTimeout(base::Seconds(5))
-                  .SetIdleCheckPeriod(base::Seconds(10))
+                  .SetIdleCheckPeriod(idle_timeout)
                   .Modify());
 }
 
diff --git a/chrome/updater/test/integration_tests_win.cc b/chrome/updater/test/integration_tests_win.cc
index ced6dfe3..e7e99b8 100644
--- a/chrome/updater/test/integration_tests_win.cc
+++ b/chrome/updater/test/integration_tests_win.cc
@@ -618,7 +618,8 @@
 
 void EnterTestMode(const GURL& update_url,
                    const GURL& crash_upload_url,
-                   const GURL& device_management_url) {
+                   const GURL& device_management_url,
+                   const base::TimeDelta& idle_timeout) {
   ASSERT_TRUE(ExternalConstantsBuilder()
                   .SetUpdateURL(std::vector<std::string>{update_url.spec()})
                   .SetCrashUploadURL(crash_upload_url.spec())
@@ -628,7 +629,7 @@
                   .SetServerKeepAliveTime(base::Seconds(1))
                   .SetCrxVerifierFormat(crx_file::VerifierFormat::CRX3)
                   .SetOverinstallTimeout(base::Seconds(11))
-                  .SetIdleCheckPeriod(base::Seconds(10))
+                  .SetIdleCheckPeriod(idle_timeout)
                   .Modify());
 }
 
diff --git a/chrome/updater/test/server.cc b/chrome/updater/test/server.cc
index 6fef1374..edcd86f 100644
--- a/chrome/updater/test/server.cc
+++ b/chrome/updater/test/server.cc
@@ -17,6 +17,7 @@
 #include "base/ranges/algorithm.h"
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
+#include "base/time/time.h"
 #include "chrome/updater/test/http_request.h"
 #include "chrome/updater/test/integration_test_commands.h"
 #include "chrome/updater/test/integration_tests_impl.h"
@@ -59,7 +60,8 @@
   EXPECT_TRUE((test_server_handle_ = test_server_->StartAndReturnHandle()));
 
   integration_test_commands_->EnterTestMode(update_url(), crash_upload_url(),
-                                            device_management_url());
+                                            device_management_url(),
+                                            base::Minutes(5));
 }
 
 ScopedServer::~ScopedServer() {
diff --git a/chromeos/ash/components/dbus/shill/fake_shill_manager_client.cc b/chromeos/ash/components/dbus/shill/fake_shill_manager_client.cc
index 8595703..cb5e9a279 100644
--- a/chromeos/ash/components/dbus/shill/fake_shill_manager_client.cc
+++ b/chromeos/ash/components/dbus/shill/fake_shill_manager_client.cc
@@ -414,7 +414,8 @@
       if (!hex_name.empty()) {
         std::vector<uint8_t> bytes;
         if (base::HexStringToBytes(hex_name, &bytes)) {
-          name.assign(reinterpret_cast<const char*>(&bytes[0]), bytes.size());
+          name.assign(reinterpret_cast<const char*>(bytes.data()),
+                      bytes.size());
         }
       }
     }
diff --git a/chromeos/ash/components/sync_wifi/network_type_conversions.cc b/chromeos/ash/components/sync_wifi/network_type_conversions.cc
index 8084fd3..5f54bcf 100644
--- a/chromeos/ash/components/sync_wifi/network_type_conversions.cc
+++ b/chromeos/ash/components/sync_wifi/network_type_conversions.cc
@@ -48,7 +48,7 @@
     return std::string();
   }
 
-  decoded.assign(reinterpret_cast<const char*>(&v[0]), v.size());
+  decoded.assign(reinterpret_cast<const char*>(v.data()), v.size());
   return decoded;
 }
 
diff --git a/chromeos/ash/services/libassistant/conversation_state_listener_impl.cc b/chromeos/ash/services/libassistant/conversation_state_listener_impl.cc
index 13f839e..15a94e8 100644
--- a/chromeos/ash/services/libassistant/conversation_state_listener_impl.cc
+++ b/chromeos/ash/services/libassistant/conversation_state_listener_impl.cc
@@ -121,6 +121,7 @@
       return;
     // This is only applicable in longform barge-in mode, which we do not use.
     case Resolution::LONGFORM_KEEP_MIC_OPEN:
+    case Resolution::BLUE_STEEL_ON_DEVICE_REJECTION:
       NOTREACHED();
       return;
   }
diff --git a/components/BUILD.gn b/components/BUILD.gn
index 434592b..2889d81 100644
--- a/components/BUILD.gn
+++ b/components/BUILD.gn
@@ -221,6 +221,7 @@
     "//components/reporting/storage:unit_tests",
     "//components/reporting/util:unit_tests",
     "//components/safe_browsing/core/browser:safe_browsing_metrics_collector_unittest",
+    "//components/safe_browsing/core/browser/hashprefix_realtime:unit_tests",
     "//components/safe_browsing/core/browser/password_protection:unit_tests",
     "//components/safe_search_api:unit_tests",
     "//components/saved_tab_groups:unit_tests",
@@ -462,7 +463,6 @@
       "//components/safe_browsing/content/browser/triggers:unit_tests",
       "//components/safe_browsing/content/browser/web_ui:unit_tests",
       "//components/safe_browsing/core/browser:token_fetcher_unit_tests",
-      "//components/safe_browsing/core/browser/hashprefix_realtime:unit_tests",
       "//components/safe_browsing/core/browser/realtime:unit_tests",
       "//components/safe_browsing/core/browser/tailored_security_service:unit_tests",
       "//components/safe_browsing/core/browser/utils:unit_tests",
diff --git a/components/aggregation_service/aggregation_coordinator_utils.cc b/components/aggregation_service/aggregation_coordinator_utils.cc
index 4726c1d..4bc8ce6 100644
--- a/components/aggregation_service/aggregation_coordinator_utils.cc
+++ b/components/aggregation_service/aggregation_coordinator_utils.cc
@@ -21,9 +21,10 @@
 // An identifier to specify the deployment option for the aggregation service.
 enum AggregationCoordinator {
   kAwsCloud,
+  kGcpCloud,
 
   kMinValue = kAwsCloud,
-  kMaxValue = kAwsCloud,
+  kMaxValue = kGcpCloud,
   kDefault = kAwsCloud,
 };
 
@@ -40,14 +41,21 @@
 
 url::Origin GetAggregationCoordinatorOrigin(
     AggregationCoordinator aggregation_coordinator) {
+  url::Origin origin;
   switch (aggregation_coordinator) {
     case AggregationCoordinator::kAwsCloud:
-      url::Origin origin = GetAggregationCoordinatorOriginFromString(
+      origin = GetAggregationCoordinatorOriginFromString(
           kAggregationServiceCoordinatorAwsCloud.Get(),
           kDefaultAggregationCoordinatorAwsCloud);
-      CHECK(attribution_reporting::IsOriginSuitable(origin));
-      return origin;
+      break;
+    case AggregationCoordinator::kGcpCloud:
+      origin = GetAggregationCoordinatorOriginFromString(
+          kAggregationServiceCoordinatorGcpCloud.Get(),
+          kDefaultAggregationCoordinatorGcpCloud);
+      break;
   }
+  CHECK(attribution_reporting::IsOriginSuitable(origin));
+  return origin;
 }
 
 }  // namespace
diff --git a/components/aggregation_service/aggregation_coordinator_utils.h b/components/aggregation_service/aggregation_coordinator_utils.h
index b4a60fb7..e70204e 100644
--- a/components/aggregation_service/aggregation_coordinator_utils.h
+++ b/components/aggregation_service/aggregation_coordinator_utils.h
@@ -16,6 +16,9 @@
 constexpr char kDefaultAggregationCoordinatorAwsCloud[] =
     "https://publickeyservice.aws.privacysandboxservices.com";
 
+constexpr char kDefaultAggregationCoordinatorGcpCloud[] =
+    "https://gcp-server.example";
+
 COMPONENT_EXPORT(AGGREGATION_SERVICE)
 url::Origin GetDefaultAggregationCoordinatorOrigin();
 
diff --git a/components/aggregation_service/aggregation_coordinator_utils_unittest.cc b/components/aggregation_service/aggregation_coordinator_utils_unittest.cc
index 4d89d8b..dca9d2f5 100644
--- a/components/aggregation_service/aggregation_coordinator_utils_unittest.cc
+++ b/components/aggregation_service/aggregation_coordinator_utils_unittest.cc
@@ -62,6 +62,10 @@
           .expected = true,
       },
       {
+          .origin = url::Origin::Create(GURL("https://gcp.example.test")),
+          .expected = true,
+      },
+      {
           .origin = url::Origin::Create(GURL("https://a.test")),
           .expected = false,
       },
@@ -70,7 +74,8 @@
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitAndEnableFeatureWithParameters(
       ::aggregation_service::kAggregationServiceMultipleCloudProviders,
-      {{"aws_cloud", "https://aws.example.test"}});
+      {{"aws_cloud", "https://aws.example.test"},
+       {"gcp_cloud", "https://gcp.example.test"}});
 
   for (const auto& test_case : kTestCases) {
     EXPECT_EQ(IsAggregationCoordinatorOriginAllowed(test_case.origin),
diff --git a/components/aggregation_service/features.cc b/components/aggregation_service/features.cc
index 8dfd816b..6096ff3 100644
--- a/components/aggregation_service/features.cc
+++ b/components/aggregation_service/features.cc
@@ -20,4 +20,8 @@
     &kAggregationServiceMultipleCloudProviders, "aws_cloud",
     kDefaultAggregationCoordinatorAwsCloud};
 
+const base::FeatureParam<std::string> kAggregationServiceCoordinatorGcpCloud{
+    &kAggregationServiceMultipleCloudProviders, "gcp_cloud",
+    kDefaultAggregationCoordinatorGcpCloud};
+
 }  // namespace aggregation_service
diff --git a/components/aggregation_service/features.h b/components/aggregation_service/features.h
index ee6905638..f08cc65 100644
--- a/components/aggregation_service/features.h
+++ b/components/aggregation_service/features.h
@@ -20,6 +20,10 @@
 extern const base::FeatureParam<std::string>
     kAggregationServiceCoordinatorAwsCloud;
 
+COMPONENT_EXPORT(AGGREGATION_SERVICE)
+extern const base::FeatureParam<std::string>
+    kAggregationServiceCoordinatorGcpCloud;
+
 }  // namespace aggregation_service
 
 #endif  // COMPONENTS_AGGREGATION_SERVICE_FEATURES_H_
diff --git a/components/autofill/core/browser/form_structure.cc b/components/autofill/core/browser/form_structure.cc
index 625882e..a1a8a59 100644
--- a/components/autofill/core/browser/form_structure.cc
+++ b/components/autofill/core/browser/form_structure.cc
@@ -382,7 +382,7 @@
     rationalizer.RationalizeRepeatedFields(
         form_signature_, form_interactions_ukm_logger, log_manager);
   }
-  rationalizer.RationalizeFieldTypePredictions(log_manager);
+  rationalizer.RationalizeFieldTypePredictions(main_frame_origin_, log_manager);
 
   // Log the field type predicted by rationalization.
   // The sections are mapped to consecutive natural numbers starting at 1.
@@ -702,7 +702,8 @@
     rationalizer.RationalizeAutocompleteAttributes(log_manager);
     rationalizer.RationalizeRepeatedFields(
         form->form_signature_, form_interactions_ukm_logger, log_manager);
-    rationalizer.RationalizeFieldTypePredictions(log_manager);
+    rationalizer.RationalizeFieldTypePredictions(form->main_frame_origin_,
+                                                 log_manager);
     // TODO(crbug.com/1154080): By calling this with true, autocomplete section
     // attributes will be ignored.
     form->IdentifySections(/*ignore_autocomplete=*/true);
diff --git a/components/autofill/core/browser/form_structure_rationalizer.cc b/components/autofill/core/browser/form_structure_rationalizer.cc
index c2d3ac7..774bbaf 100644
--- a/components/autofill/core/browser/form_structure_rationalizer.cc
+++ b/components/autofill/core/browser/form_structure_rationalizer.cc
@@ -5,6 +5,7 @@
 #include "components/autofill/core/browser/form_structure_rationalizer.h"
 
 #include "base/containers/contains.h"
+#include "base/ranges/algorithm.h"
 #include "components/autofill/core/browser/form_parsing/credit_card_field.h"
 #include "components/autofill/core/browser/logging/log_manager.h"
 #include "components/autofill/core/browser/rationalization_util.h"
@@ -331,6 +332,43 @@
   }
 }
 
+void FormStructureRationalizer::RationalizeMultiOriginCreditCardFields(
+    const url::Origin& main_origin,
+    LogManager* log_manager) {
+  auto is_in_subframe = [&main_origin](const FormFieldData& field) {
+    return field.origin != main_origin;
+  };
+  auto rationalize = [&](ServerFieldType relevant_type) {
+    auto is_relevant = [relevant_type](const AutofillField& field) {
+      return field.ComputedType().GetStorableType() == relevant_type;
+    };
+    auto is_relevant_in_subframe = [&](const auto& field) {
+      return is_relevant(*field) && is_in_subframe(*field);
+    };
+    // If a relevant field exists in a sub-frame, we can ignore the
+    // corresponding field in the main frame as it is probably a
+    // misclassification.
+    if (base::ranges::any_of(*fields_, is_relevant_in_subframe)) {
+      for (auto& field : *fields_) {
+        if (is_relevant(*field) && !is_in_subframe(*field)) {
+          field->SetTypeTo(AutofillType(UNKNOWN_TYPE));
+          LOG_AF(log_manager)
+              << LoggingScope::kRationalization << LogMessage::kRationalization
+              << "Multi-origin Credit Card Rationalization: Converting type of "
+              << field->global_id() << " from "
+              << AutofillType::ServerFieldTypeToString(relevant_type)
+              << " to UNKNOWN_TYPE";
+        }
+      }
+    }
+  };
+  // These fields do usually not occur on the main frame's origin due to
+  // PCI-DSS. By contrast, cardholder name and expiration dates do commonly
+  // appear in the main frame and in cross-origin iframes.
+  rationalize(CREDIT_CARD_NUMBER);
+  rationalize(CREDIT_CARD_VERIFICATION_CODE);
+}
+
 void FormStructureRationalizer::RationalizeCreditCardNumberOffsets(
     LogManager* log_manager) {
   // Credit card numbers are 8 to 19 digits in length.
@@ -810,8 +848,10 @@
 }
 
 void FormStructureRationalizer::RationalizeFieldTypePredictions(
+    const url::Origin& main_origin,
     LogManager* log_manager) {
   RationalizeCreditCardFieldPredictions(log_manager);
+  RationalizeMultiOriginCreditCardFields(main_origin, log_manager);
   if (base::FeatureList::IsEnabled(
           features::kAutofillSplitCreditCardNumbersCautiously)) {
     RationalizeCreditCardNumberOffsets(log_manager);
diff --git a/components/autofill/core/browser/form_structure_rationalizer.h b/components/autofill/core/browser/form_structure_rationalizer.h
index 8e7b7461..3511cc5 100644
--- a/components/autofill/core/browser/form_structure_rationalizer.h
+++ b/components/autofill/core/browser/form_structure_rationalizer.h
@@ -13,6 +13,7 @@
 #include "components/autofill/core/browser/field_types.h"
 #include "components/autofill/core/browser/metrics/autofill_metrics.h"
 #include "components/autofill/core/common/form_field_data.h"
+#include "url/origin.h"
 
 namespace autofill {
 
@@ -45,7 +46,8 @@
 
   // A helper function to review the predictions and do appropriate adjustments
   // when it considers necessary.
-  void RationalizeFieldTypePredictions(LogManager* log_manager);
+  void RationalizeFieldTypePredictions(const url::Origin& main_origin,
+                                       LogManager* log_manager);
 
   // Ensures that only a single phone number (which can be split across multiple
   // fields) is autofilled in the `section`. If the section contains multiple
@@ -67,6 +69,12 @@
   // correct, the function will override that prediction.
   void RationalizeCreditCardFieldPredictions(LogManager* log_manager);
 
+  // Eradicates the type of credit card number and CVC fields on the main
+  // frame's origin if there are also fields of this type in a child frame.
+  // See crbug.com/1450502 for details.
+  void RationalizeMultiOriginCreditCardFields(const url::Origin& main_origin,
+                                              LogManager* log_manager);
+
   // Sets the offsets of adjacent credit card number fields. For example:
   // four adjacent card fields with `FormFieldData::max_length == 4` should
   // likely be filled with the the first, second, third, and fourth,
diff --git a/components/autofill/core/browser/form_structure_rationalizer_unittest.cc b/components/autofill/core/browser/form_structure_rationalizer_unittest.cc
index 29797315..46998273 100644
--- a/components/autofill/core/browser/form_structure_rationalizer_unittest.cc
+++ b/components/autofill/core/browser/form_structure_rationalizer_unittest.cc
@@ -52,6 +52,7 @@
   bool is_focusable = true;
   size_t max_length = std::numeric_limits<int>::max();
   FormFieldData::RoleAttribute role = FormFieldData::RoleAttribute::kOther;
+  absl::optional<url::Origin> subframe_origin;
   absl::optional<FormGlobalId> host_form;
 };
 
@@ -96,6 +97,8 @@
     field.max_length = field_template.max_length;
     field.parsed_autocomplete = field_template.parsed_autocomplete;
     field.role = field_template.role;
+    field.origin =
+        field_template.subframe_origin.value_or(form.main_frame_origin);
     field.host_frame =
         field_template.host_form.value_or(form.global_id()).frame_token;
     field.host_form_id =
@@ -692,6 +695,79 @@
                           ADDRESS_HOME_STATE));
 }
 
+// Tests the rationalization that ignores certain types on the main origin. The
+// underlying assumption is that the field in the main frame misclassified
+// because such fields usually do not occur on the main frame's origin due to
+// PCI-DSS.
+class FormStructureRationalizerTestMultiOriginCreditCardFields
+    : public FormStructureRationalizerTest,
+      public ::testing::WithParamInterface<ServerFieldType> {
+ public:
+  ServerFieldType sensitive_type() const { return GetParam(); }
+};
+
+INSTANTIATE_TEST_SUITE_P(
+    FormStructureRationalizerTest,
+    FormStructureRationalizerTestMultiOriginCreditCardFields,
+    ::testing::Values(CREDIT_CARD_NUMBER, CREDIT_CARD_VERIFICATION_CODE));
+
+// Tests that if a sensitive type appears in both the main and a cross-origin
+// iframe, the field in the main frame is rationalized to UNKNOWN_TYPE.
+TEST_P(FormStructureRationalizerTestMultiOriginCreditCardFields,
+       RationalizeIfSensitiveFieldsOnMainAndCrossOrigin) {
+  EXPECT_THAT(
+      *BuildFormStructure(
+          CreateFormAndServerClassification({
+              {.field_type = CREDIT_CARD_NAME_FULL},
+              {.field_type = sensitive_type()},
+              {.field_type = sensitive_type(),
+               .subframe_origin = url::Origin::Create(GURL("https://psp.com"))},
+              {.field_type = CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR},
+          }),
+          /*run_heuristics=*/false),
+      AreFields(HasType(CREDIT_CARD_NAME_FULL),
+                HasType(UNKNOWN_TYPE),  // Because there are sub-frames.
+                HasType(sensitive_type()),
+                HasType(CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR)));
+}
+
+// Tests that if there is no sensitive type in a cross-origin iframe, the
+// multi-origin rationalization does *not* apply, i.e., all fields keep their
+// types.
+TEST_P(FormStructureRationalizerTestMultiOriginCreditCardFields,
+       DoNotRationalizeIfSensitiveFieldsOnlyOnMainOrigin) {
+  EXPECT_THAT(
+      *BuildFormStructure(CreateFormAndServerClassification({
+                              {.field_type = CREDIT_CARD_NAME_FULL},
+                              {.field_type = sensitive_type()},
+                              {.field_type = CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR},
+                          }),
+                          /*run_heuristics=*/false),
+      AreFields(HasType(CREDIT_CARD_NAME_FULL), HasType(sensitive_type()),
+                HasType(CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR)));
+}
+
+// Tests that if there is no sensitive field in the main frame, the multi-origin
+// rationalization does *not* apply, i.e., all fields keep their types.
+TEST_P(FormStructureRationalizerTestMultiOriginCreditCardFields,
+       DoNotRationalizeIfSensitiveFieldsOnlyOnCrossOrigins) {
+  EXPECT_THAT(*BuildFormStructure(
+                  CreateFormAndServerClassification({
+                      {.field_type = CREDIT_CARD_NAME_FULL},
+                      {.field_type = sensitive_type(),
+                       .subframe_origin =
+                           url::Origin::Create(GURL("https://psp1.com"))},
+                      {.field_type = sensitive_type(),
+                       .subframe_origin =
+                           url::Origin::Create(GURL("https://psp2.com"))},
+                      {.field_type = CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR},
+                  }),
+                  /*run_heuristics=*/false),
+              AreFields(HasType(CREDIT_CARD_NAME_FULL),
+                        HasType(sensitive_type()), HasType(sensitive_type()),
+                        HasType(CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR)));
+}
+
 // Tests that the offset of a cc-number field is not affected by non-adjacent
 // <input maxlength=1>.
 TEST_F(
diff --git a/components/autofill/core/browser/metrics/form_events/credit_card_form_event_logger.cc b/components/autofill/core/browser/metrics/form_events/credit_card_form_event_logger.cc
index 98d98c7..a36db8e 100644
--- a/components/autofill/core/browser/metrics/form_events/credit_card_form_event_logger.cc
+++ b/components/autofill/core/browser/metrics/form_events/credit_card_form_event_logger.cc
@@ -204,7 +204,7 @@
   // was filled.
   LogCardWithMetadataFormEventMetric(
       autofill_metrics::CardMetadataLoggingEvent::kFilled,
-      metadata_logging_context_, HasBeenLogged());
+      metadata_logging_context_, HasBeenLogged(has_logged_suggestion_filled_));
 
   if (!has_logged_suggestion_filled_) {
     has_logged_suggestion_filled_ = true;
@@ -243,6 +243,11 @@
         Log(FORM_EVENT_VIRTUAL_CARD_SUGGESTION_FILLED_ONCE, form);
         break;
     }
+    // Log if filled suggestions had metadata, logged once per page load
+    Log(metadata_logging_context_.card_metadata_available
+            ? FORM_EVENT_CARD_SUGGESTION_WITH_METADATA_FILLED_ONCE
+            : FORM_EVENT_CARD_SUGGESTION_WITHOUT_METADATA_FILLED_ONCE,
+        form);
   }
 
   base::RecordAction(
@@ -297,12 +302,18 @@
     Log(FORM_EVENT_LOCAL_SUGGESTION_WILL_SUBMIT_ONCE, form);
   }
 
-  // Log issuer-specific metrics on whether a card suggestion with metadata
-  // was filled before submission.
   if (has_logged_suggestion_filled_) {
+    // Log issuer-specific metrics on whether a card suggestion with metadata
+    // was filled before submission.
     LogCardWithMetadataFormEventMetric(
         autofill_metrics::CardMetadataLoggingEvent::kWillSubmit,
         metadata_logging_context_, HasBeenLogged(false));
+    // If a card suggestion was filled before submission, log it for metadata.
+    // This event can only be triggered once per page load
+    Log(metadata_logging_context_.card_metadata_available
+            ? FORM_EVENT_CARD_SUGGESTION_WITH_METADATA_WILL_SUBMIT_ONCE
+            : FORM_EVENT_CARD_SUGGESTION_WITHOUT_METADATA_WILL_SUBMIT_ONCE,
+        form);
   }
 }
 
diff --git a/components/autofill/core/browser/metrics/form_events/form_events.h b/components/autofill/core/browser/metrics/form_events/form_events.h
index 55f591a..33a4db3 100644
--- a/components/autofill/core/browser/metrics/form_events/form_events.h
+++ b/components/autofill/core/browser/metrics/form_events/form_events.h
@@ -189,6 +189,19 @@
   FORM_EVENT_SERVER_CARD_FILLED_FOR_AN_EXISTING_LOCAL_CARD_ONCE = 73,
   FORM_EVENT_SERVER_CARD_SUBMITTED_FOR_AN_EXISTING_LOCAL_CARD_ONCE = 74,
 
+  // The filled credit card suggestion had metadata. Logged once per
+  // page load.
+  FORM_EVENT_CARD_SUGGESTION_WITH_METADATA_FILLED_ONCE = 75,
+  // The filled credit card suggestion had no metadata. Logged once per
+  // page load.
+  FORM_EVENT_CARD_SUGGESTION_WITHOUT_METADATA_FILLED_ONCE = 76,
+  // A credit card was about to be submitted after a suggestion was filled,
+  // and the suggested card had metadata. Logged once per page load.
+  FORM_EVENT_CARD_SUGGESTION_WITH_METADATA_WILL_SUBMIT_ONCE = 77,
+  // A credit card was about to be submitted after a suggestion was filled,
+  // and the suggested card had no metadata. Logged once per page load.
+  FORM_EVENT_CARD_SUGGESTION_WITHOUT_METADATA_WILL_SUBMIT_ONCE = 78,
+
   NUM_FORM_EVENTS,
 };
 
diff --git a/components/autofill/core/browser/metrics/payments/card_metadata_metrics.cc b/components/autofill/core/browser/metrics/payments/card_metadata_metrics.cc
index 9ccc86e..64201668 100644
--- a/components/autofill/core/browser/metrics/payments/card_metadata_metrics.cc
+++ b/components/autofill/core/browser/metrics/payments/card_metadata_metrics.cc
@@ -152,6 +152,12 @@
                                       GetCardIssuerIdSuffix(issuer) +
                                       ".FilledWithMetadata",
                                   has_metadata);
+        if (!has_been_logged.value()) {
+          base::UmaHistogramBoolean("Autofill.CreditCard." +
+                                        GetCardIssuerIdSuffix(issuer) +
+                                        ".FilledWithMetadataOnce",
+                                    has_metadata);
+        }
         break;
       case CardMetadataLoggingEvent::kWillSubmit:
         base::UmaHistogramBoolean("Autofill.CreditCard." +
diff --git a/components/autofill/core/browser/metrics/payments/card_metadata_metrics_unittest.cc b/components/autofill/core/browser/metrics/payments/card_metadata_metrics_unittest.cc
index c455b7de..64862ee 100644
--- a/components/autofill/core/browser/metrics/payments/card_metadata_metrics_unittest.cc
+++ b/components/autofill/core/browser/metrics/payments/card_metadata_metrics_unittest.cc
@@ -238,22 +238,60 @@
 
   // Verify that:
   // 1. if the card suggestion filled had issuer id and metadata,
-  // `FORM_EVENT_CARD_SUGGESTION_WITH_METADATA_FILLED` is logged.
+  // `FORM_EVENT_CARD_SUGGESTION_WITH_METADATA_FILLED` is logged as many times
+  // as the suggestions are filled, and
+  // `FORM_EVENT_CARD_SUGGESTION_WITH_METADATA_FILLED_ONCE` is logged only
+  // once.
   // 2. if the card suggestion filled did not have either issuer id or metadata,
-  // `FORM_EVENT_CARD_SUGGESTION_WITHOUT_METADATA_FILLED` is logged.
+  // `FORM_EVENT_CARD_SUGGESTION_WITHOUT_METADATA_FILLED` is logged as many
+  // times as the suggestions are filled, and
+  // `FORM_EVENT_CARD_SUGGESTION_WITHOUT_METADATA_FILLED_ONCE` is logged only
+  // once.
   // 3. if the card suggestion filled had issuer id, two histograms are logged
   // which tell if the card from the issuer had metadata.
+  const bool should_log_for_metadata =
+      card_issuer_available() && card_metadata_available();
   EXPECT_THAT(
       histogram_tester.GetAllSamples("Autofill.FormEvents.CreditCard"),
       BucketsInclude(
           Bucket(FORM_EVENT_MASKED_SERVER_CARD_SUGGESTION_FILLED, 1),
           Bucket(FORM_EVENT_CARD_SUGGESTION_WITH_METADATA_FILLED,
-                 card_issuer_available() && card_metadata_available()),
+                 should_log_for_metadata ? 1 : 0),
           Bucket(FORM_EVENT_CARD_SUGGESTION_WITHOUT_METADATA_FILLED,
-                 !card_issuer_available() || !card_metadata_available())));
+                 should_log_for_metadata ? 0 : 1),
+          Bucket(FORM_EVENT_CARD_SUGGESTION_WITH_METADATA_FILLED_ONCE,
+                 should_log_for_metadata ? 1 : 0),
+          Bucket(FORM_EVENT_CARD_SUGGESTION_WITHOUT_METADATA_FILLED_ONCE,
+                 should_log_for_metadata ? 0 : 1)));
   histogram_tester.ExpectUniqueSample(
       "Autofill.CreditCard.CapitalOne.FilledWithMetadata",
       card_metadata_available(), card_issuer_available() ? 1 : 0);
+  histogram_tester.ExpectUniqueSample(
+      "Autofill.CreditCard.CapitalOne.FilledWithMetadataOnce",
+      card_metadata_available(), card_issuer_available() ? 1 : 0);
+
+  // Fill the suggestion again.
+  autofill_manager().OnCreditCardFetchedForTest(CreditCardFetchResult::kSuccess,
+                                                &card(), u"123");
+
+  EXPECT_THAT(
+      histogram_tester.GetAllSamples("Autofill.FormEvents.CreditCard"),
+      BucketsInclude(
+          Bucket(FORM_EVENT_MASKED_SERVER_CARD_SUGGESTION_FILLED, 2),
+          Bucket(FORM_EVENT_CARD_SUGGESTION_WITH_METADATA_FILLED,
+                 should_log_for_metadata ? 2 : 0),
+          Bucket(FORM_EVENT_CARD_SUGGESTION_WITHOUT_METADATA_FILLED,
+                 should_log_for_metadata ? 0 : 2),
+          Bucket(FORM_EVENT_CARD_SUGGESTION_WITH_METADATA_FILLED_ONCE,
+                 should_log_for_metadata ? 1 : 0),
+          Bucket(FORM_EVENT_CARD_SUGGESTION_WITHOUT_METADATA_FILLED_ONCE,
+                 should_log_for_metadata ? 0 : 1)));
+  histogram_tester.ExpectUniqueSample(
+      "Autofill.CreditCard.CapitalOne.FilledWithMetadata",
+      card_metadata_available(), card_issuer_available() ? 2 : 0);
+  histogram_tester.ExpectUniqueSample(
+      "Autofill.CreditCard.CapitalOne.FilledWithMetadataOnce",
+      card_metadata_available(), card_issuer_available() ? 1 : 0);
 }
 
 // Test metadata will submit and submitted metrics are correctly logged.
@@ -272,21 +310,30 @@
   // Verify that:
   // 1. if the form was submitted after a card suggestion with isser id and
   // metadata was filled,
-  // `FORM_EVENT_CARD_SUGGESTION_WITH_METADATA_SUBMITTED_ONCE` is logged.
+  // `FORM_EVENT_CARD_SUGGESTION_WITH_METADATA_WILL_SUBMIT_ONCE` and
+  // `FORM_EVENT_CARD_SUGGESTION_WITH_METADATA_SUBMITTED_ONCE` are logged.
   // 2. if the form was submitted after a card suggestion without isser id or
   // metadata was filled,
-  // `FORM_EVENT_CARD_SUGGESTION_WITHOUT_METADATA_WILL_SUBMIT_ONCE` is logged.
+  // `FORM_EVENT_CARD_SUGGESTION_WITHOUT_METADATA_WILL_SUBMIT_ONCE` and
+  // `FORM_EVENT_CARD_SUGGESTION_WITHOUT_METADATA_SUBMITTED_ONCE` are logged.
   // 3. if the form was submitted after a card suggestion with issuer id was
   // filled, two histograms are logged which tell if the card from the issuer
   // had metadata.
+  const bool should_log_for_metadata =
+      card_issuer_available() && card_metadata_available();
   EXPECT_THAT(
       histogram_tester.GetAllSamples("Autofill.FormEvents.CreditCard"),
       BucketsInclude(
+          Bucket(FORM_EVENT_MASKED_SERVER_CARD_SUGGESTION_WILL_SUBMIT_ONCE, 1),
+          Bucket(FORM_EVENT_CARD_SUGGESTION_WITH_METADATA_WILL_SUBMIT_ONCE,
+                 should_log_for_metadata),
+          Bucket(FORM_EVENT_CARD_SUGGESTION_WITHOUT_METADATA_WILL_SUBMIT_ONCE,
+                 !should_log_for_metadata),
           Bucket(FORM_EVENT_MASKED_SERVER_CARD_SUGGESTION_SUBMITTED_ONCE, 1),
           Bucket(FORM_EVENT_CARD_SUGGESTION_WITH_METADATA_SUBMITTED_ONCE,
-                 card_issuer_available() && card_metadata_available()),
+                 should_log_for_metadata),
           Bucket(FORM_EVENT_CARD_SUGGESTION_WITHOUT_METADATA_SUBMITTED_ONCE,
-                 !card_issuer_available() || !card_metadata_available())));
+                 !should_log_for_metadata)));
   histogram_tester.ExpectUniqueSample(
       "Autofill.CreditCard.CapitalOne.WillSubmitWithMetadataOnce",
       card_metadata_available(), card_issuer_available() ? 1 : 0);
diff --git a/components/autofill/ios/browser/BUILD.gn b/components/autofill/ios/browser/BUILD.gn
index db5ec4f..f9916b6 100644
--- a/components/autofill/ios/browser/BUILD.gn
+++ b/components/autofill/ios/browser/BUILD.gn
@@ -98,9 +98,10 @@
   ]
 }
 
-optimize_js("suggestion_controller_js") {
-  primary_script = "resources/suggestion_controller.js"
-  sources = [ "resources/suggestion_controller.js" ]
+optimize_ts("suggestion_controller_js") {
+  sources = [ "resources/suggestion_controller.ts" ]
+
+  deps = [ "//ios/web/public/js_messaging:gcrweb" ]
 }
 
 source_set("test_support") {
diff --git a/components/autofill/ios/browser/resources/suggestion_controller.js b/components/autofill/ios/browser/resources/suggestion_controller.js
deleted file mode 100644
index 45baa03..0000000
--- a/components/autofill/ios/browser/resources/suggestion_controller.js
+++ /dev/null
@@ -1,387 +0,0 @@
-// Copyright 2013 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview Installs suggestion management functions on the
- * __gCrWeb object.
- */
-
-/**
- * Namespace for this file. It depends on |__gCrWeb| having already been
- * injected.
- */
-__gCrWeb.suggestion = {};
-
-// Store suggestion namespace object in a global __gCrWeb object referenced by a
-// string, so it does not get renamed by closure compiler during the
-// minification.
-__gCrWeb['suggestion'] = __gCrWeb.suggestion;
-
-/**
- * Returns the element with the specified name that is a child of the
- * specified parent element.
- * @param {Element} parent The parent of the desired element.
- * @param {string} name The name of the desired element.
- * @return {Element} The element if found, otherwise null;
- */
-const getElementByNameWithParent = function(parent, name) {
-  if (parent.name === name) return parent;
-
-  let el;
-  for (let i = 0; i < parent.children.length; i++) {
-    el = getElementByNameWithParent(parent.children[i], name);
-    if (el) return el;
-  }
-  return null;
-};
-
-/**
- * Returns the first element in |elements| that is later than |elementToCompare|
- * in tab order.
- *
- * @param {Element} elementToCompare The element to start searching forward in
- *     tab order from.
- * @param {NodeList} elementList Elements in which the first element that is
- *     later than |elementToCompare| in tab order is to be returned if there is
- *     one; |elements| should be sorted in DOM tree order and should contain
- *     |elementToCompare|.
- * @return {Element} the first element in |elements| that is later than
- *     |elementToCompare| in tab order if there is one; null otherwise.
- */
-__gCrWeb.suggestion.getNextElementInTabOrder = function(
-    elementToCompare, elementList) {
-  const elements = [];
-  for (let i = 0; i < elementList.length; ++i) {
-    elements[i] = elementList[i];
-  }
-  // There is no defined behavior if the element is not reachable. Here the
-  // next reachable element in DOM tree order is returned. (This is what is
-  // observed in Mobile Safari and Chrome Desktop, if |elementToCompare| is not
-  // the last element in DOM tree order).
-  // TODO(chenyu): investigate and simulate Mobile Safari's behavior when
-  // |elementToCompare| is the last one in DOM tree order.
-  if (!__gCrWeb.suggestion.isSequentiallyReachable(elementToCompare)) {
-    const indexToCompare = elements.indexOf(elementToCompare);
-    if (indexToCompare === elements.length - 1 || indexToCompare === -1) {
-      return null;
-    }
-    for (let index = indexToCompare + 1; index < elements.length; ++index) {
-      const element = elements[index];
-      if (__gCrWeb.suggestion.isSequentiallyReachable(element)) {
-        return element;
-      }
-    }
-    return null;
-  }
-
-  // Returns true iff |element1| that has DOM tree position |index1| is after
-  // |element2| that has DOM tree position |index2| in tab order. It is assumed
-  // |index1 !== index2|.
-  const comparator = function(element1, index1, element2, index2) {
-    const tabOrder1 = __gCrWeb.suggestion.getTabOrder(element1);
-    const tabOrder2 = __gCrWeb.suggestion.getTabOrder(element2);
-    return tabOrder1 > tabOrder2 ||
-        (tabOrder1 === tabOrder2 && index1 > index2);
-  };
-  return __gCrWeb.suggestion.getFormElementAfter(
-      elementToCompare, elements, comparator);
-};
-
-/**
- * Returns the last element in |elements| that is earlier than
- * |elementToCompare| in tab order.
- *
- * @param {Element} elementToCompare The element to start searching backward in
- *     tab order from.
- * @param {NodeList} elementList Elements in which the last element that is
- *     earlier than |elementToCompare| in tab order is to be returned if
- *     there is one; |elements| should be sorted in DOM tree order and it should
- *     contain |elementToCompare|.
- * @return {Element} the last element in |elements| that is earlier than
- *     |elementToCompare| in tab order if there is one; null otherwise.
- */
-__gCrWeb.suggestion.getPreviousElementInTabOrder = function(
-    elementToCompare, elementList) {
-  const elements = [];
-  for (let i = 0; i < elementList.length; ++i) {
-    elements[i] = elementList[i];
-  }
-
-  // There is no defined behavior if the element is not reachable. Here the
-  // previous reachable element in DOM tree order is returned.
-  if (!__gCrWeb.suggestion.isSequentiallyReachable(elementToCompare)) {
-    const indexToCompare = elements.indexOf(elementToCompare);
-    if (indexToCompare <= 0) {  // Ignore if first or no element is found.
-      return null;
-    }
-    for (let index = indexToCompare - 1; index >= 0; --index) {
-      const element = elements[index];
-      if (__gCrWeb.suggestion.isSequentiallyReachable(element)) {
-        return element;
-      }
-    }
-    return null;
-  }
-
-  // Returns true iff |element1| that has DOM tree position |index1| is before
-  // |element2| that has DOM tree position |index2| in tab order. It is assumed
-  // |index1 !== index2|.
-  const comparator = function(element1, index1, element2, index2) {
-    const tabOrder1 = __gCrWeb.suggestion.getTabOrder(element1);
-    const tabOrder2 = __gCrWeb.suggestion.getTabOrder(element2);
-    return tabOrder1 < tabOrder2 ||
-        (tabOrder1 === tabOrder2 && index1 < index2);
-  };
-
-  return __gCrWeb.suggestion.getFormElementAfter(
-      elementToCompare, elements, comparator);
-};
-
-/**
- * Given an element |elementToCompare|, such as
- * |__gCrWeb.suggestion.isSequentiallyReachable(elementToCompare)|, and a list
- * of |elements| which are sorted in DOM tree order and contains
- * |elementToCompare|, this method returns the next element in |elements| after
- * |elementToCompare| in the order defined by |comparator|, where an element is
- * said to be 'after' anotherElement if and only if
- * comparator(element, indexOfElement, anotherElement, anotherIndex) is true.
- *
- * @param {Element} elementToCompare The element to be compared.
- * @param {Array<Element>} elements Elements to compare; |elements| should be
- *     sorted in DOM tree order and it should contain |elementToCompare|.
- * @param {function(Element, number, Element, number):boolean} comparator A
- *     function that returns a boolean, given an Element |element1|, an integer
- *     that represents |element1|'s position in DOM tree order, an Element
- *     |element2| and an integer that represents |element2|'s position in DOM
- *     tree order.
- * @return {Element} The element that satisfies the conditions given above.
- */
-__gCrWeb.suggestion.getFormElementAfter = function(
-    elementToCompare, elements, comparator) {
-  // Computes the index |indexToCompare| of |elementToCompare| in |element|.
-  const indexToCompare = elements.indexOf(elementToCompare);
-  if (indexToCompare === -1) {
-    return null;
-  }
-
-  let result = null;
-  let resultIndex = -1;
-  for (let index = 0; index < elements.length; ++index) {
-    if (index === indexToCompare) {
-      continue;
-    }
-    const element = elements[index];
-    if (!__gCrWeb.suggestion.isSequentiallyReachable(element)) {
-      continue;
-    }
-
-    if (comparator(element, index, elementToCompare, indexToCompare)) {
-      if (!result) {
-        result = element;
-        resultIndex = index;
-      } else {
-        if (comparator(result, resultIndex, element, index)) {
-          result = element;
-          resultIndex = index;
-        }
-      }
-    }
-  }
-  return result;
-};
-
-/**
- * Tests an element's visibility. This test is expensive so should be used
- * sparingly.
- *
- * @param {Element} element A DOM element.
- * @return {boolean} true if the |element| is currently part of the visible
- * DOM.
- */
-const isElementVisible = function(element) {
-  /** @type {Node} */
-  let node = element;
-  while (node && node !== document) {
-    if (node.nodeType === Node.ELEMENT_NODE) {
-      const style = window.getComputedStyle(/** @type {Element} */ (node));
-      if (style.display === 'none' || style.visibility === 'hidden') {
-        return false;
-      }
-    }
-    // Move up the tree and test again.
-    node = node.parentNode;
-  }
-  // Test reached the top of the DOM without finding a concealed ancestor.
-  return true;
-};
-
-/**
- * Returns if an element is reachable in sequential navigation.
- *
- * @param {Element} element The element that is to be examined.
- * @return {boolean} Whether an element is reachable in sequential navigation.
- */
-__gCrWeb.suggestion.isSequentiallyReachable = function(element) {
-  const tabIndex = element.tabIndex;
-  // It is proposed in W3C that if tabIndex is omitted or parsing the value
-  // returns an error, the user agent should follow platform conventions to
-  // determine whether the element can be reached using sequential focus
-  // navigation, and if so, what its relative order should be. No document is
-  // found on the platform conventions in this case on iOS.
-  //
-  // There is a list of elements for which the tabIndex focus flags are
-  // suggested to be set in this case in W3C proposal. It is observed that in
-  // UIWebview parsing the tabIndex of an element in this list returns 0 if it
-  // is omitted or it is set to be an invalid value, undefined, null or NaN. So
-  // here it is assumed that all the elements that have invalid tabIndex is
-  // not reachable in sequential focus navigation.
-  //
-  // It is proposed in W3C that if tabIndex is a negative integer, the user
-  // agent should not allow the element to be reached using sequential focus
-  // navigation.
-  if ((!tabIndex && tabIndex !== 0) || tabIndex < 0) {
-    return false;
-  }
-  if (element.type === 'hidden' || element.hasAttribute('disabled')) {
-    return false;
-  }
-
-  // false is returned if |element| is neither an input nor a select. Note based
-  // on this condition, false is returned for an iframe (as Mobile Safari does
-  // not navigate to elements in an iframe, there is no need to recursively
-  // check if there is a reachable element in an iframe).
-  if (element.tagName !== 'INPUT' && element.tagName !== 'SELECT' &&
-      element.tagName !== 'TEXTAREA') {
-    return false;
-  }
-
-  // The following elements are skipped when navigating using 'Prev' and "Next'
-  // buttons in Mobile Safari.
-  if (element.tagName === 'INPUT' &&
-      (element.type === 'submit' || element.type === 'reset' ||
-       element.type === 'image' || element.type === 'button' ||
-       element.type === 'range' || element.type === 'radio' ||
-       element.type === 'checkbox')) {
-    return false;
-  }
-
-  // Expensive, final check that the element is not concealed.
-  return isElementVisible(element);
-};
-
-/**
- * It is proposed in W3C an element that has a tabIndex greater than zero should
- * be placed before any focusable element whose tabIndex is equal to zero in
- * sequential focus navigation order. Here a value adjusted from tabIndex that
- * reflects this order is returned. That is, given |element1| and |element2|,
- * if |__gCrWeb.suggestion.getTabOrder(element1) >
- * __gCrWeb.suggestion.getTabOrder(element2)|, then |element1| is after
- * |element2| in sequential navigation.
- *
- * @param {Element} element The element of which the sequential navigation order
- *     information is returned.
- * @return {number} An adjusted value that reflect |element|'s position in the
- *     sequential navigation.
- */
-__gCrWeb.suggestion.getTabOrder = function(element) {
-  const tabIndex = element.tabIndex;
-  if (tabIndex === 0) {
-    return Number.MAX_VALUE;
-  }
-  return tabIndex;
-};
-
-/**
- * Returns the element named |fieldName| in the form specified by |formName|,
- * if it exists.
- *
- * @param {string} formName The name of the form containing the element.
- * @param {string} fieldName The name of the field containing the element.
- * @return {Element} The element if found, otherwise null.
- */
-__gCrWeb.suggestion.getFormElement = function(formName, fieldName) {
-  const form = __gCrWeb.form.getFormElementFromIdentifier(formName);
-  if (!form) return null;
-  return getElementByNameWithParent(form, fieldName);
-};
-
-/**
- * Focuses the next element in the sequential focus navigation. No operation
- * if there is no such element.
- */
-__gCrWeb.suggestion['selectNextElement'] = function(formName, fieldName) {
-  const currentElement = formName ?
-      __gCrWeb.suggestion.getFormElement(formName, fieldName) :
-      document.activeElement;
-  const nextElement = __gCrWeb.suggestion.getNextElementInTabOrder(
-      currentElement, document.all);
-  if (nextElement) {
-    nextElement.focus();
-  }
-};
-
-/**
- * Focuses the previous element in the sequential focus navigation. No
- * operation if there is no such element.
- */
-__gCrWeb.suggestion['selectPreviousElement'] = function(formName, fieldName) {
-  const currentElement = formName ?
-      __gCrWeb.suggestion.getFormElement(formName, fieldName) :
-      document.activeElement;
-  const prevElement = __gCrWeb.suggestion.getPreviousElementInTabOrder(
-      currentElement, document.all);
-  if (prevElement) {
-    prevElement.focus();
-  }
-};
-
-/**
- * @param {string} formName The name of the form containing the element.
- * @param {string} fieldName The name of the field containing the element.
- * @return {boolean} Whether there is an element in the sequential navigation
- *     after the currently active element.
- */
-__gCrWeb.suggestion['hasNextElement'] = function(formName, fieldName) {
-  const currentElement = formName ?
-      __gCrWeb.suggestion.getFormElement(formName, fieldName) :
-      document.activeElement;
-  return __gCrWeb.suggestion.getNextElementInTabOrder(
-             currentElement, document.all) !== null;
-};
-
-/**
- * @param {string} formName The name of the form containing the element.
- * @param {string} fieldName The name of the field containing the element.
- * @return {boolean} Whether there is an element in the sequential navigation
- *     before the currently active element.
- */
-__gCrWeb.suggestion['hasPreviousElement'] = function(formName, fieldName) {
-  const currentElement = formName ?
-      __gCrWeb.suggestion.getFormElement(formName, fieldName) :
-      document.activeElement;
-  return __gCrWeb.suggestion.getPreviousElementInTabOrder(
-             currentElement, document.all) !== null;
-};
-
-/**
- * @param {string} formName The name of the form containing the element.
- * @param {string} fieldName The name of the field containing the element.
- * @return {string} Whether there is an element in the sequential navigation
- *     before and after currently active element. The result is returned as a
- *     comma separated string of the strings |true| and |false|.
- *     TODO(crbug.com/893368): Return a dictionary with the values instead.
- */
-__gCrWeb.suggestion['hasPreviousNextElements'] = function(formName, fieldName) {
-  return [
-    __gCrWeb.suggestion.hasPreviousElement(formName, fieldName),
-    __gCrWeb.suggestion.hasNextElement(formName, fieldName),
-  ].toString();
-};
-
-/**
- * Blurs the |activeElement| of the current document.
- */
-__gCrWeb.suggestion['blurActiveElement'] = function() {
-  document.activeElement.blur();
-};
diff --git a/components/autofill/ios/browser/resources/suggestion_controller.ts b/components/autofill/ios/browser/resources/suggestion_controller.ts
new file mode 100644
index 0000000..71d1aea
--- /dev/null
+++ b/components/autofill/ios/browser/resources/suggestion_controller.ts
@@ -0,0 +1,393 @@
+// Copyright 2013 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import {gCrWeb} from '//ios/web/public/js_messaging/resources/gcrweb.js';
+
+/**
+ * @fileoverview Installs suggestion management functions on the
+ * __gCrWeb object.
+ */
+
+/**
+ * Returns the element with the specified name that is a child of the
+ * specified parent element. This function searches through the whole subtree
+ * and, in the event that multiple elements match, the first element in NLR
+ * order is returned.
+ * @param parent The parent of the desired element.
+ * @param name The name of the desired element.
+ * @return The element if found, otherwise null;
+ */
+function getElementByNameWithParent(parent: Element, name: string): Element|
+    null {
+  if ('name' in parent && parent.name === name) {
+    return parent;
+  }
+
+  for (const child of parent.children) {
+    const element = getElementByNameWithParent(child, name);
+    if (element) {
+      return element;
+    }
+  }
+  return null;
+}
+
+/**
+ * Returns the first element in `elements` that is later than `elementToCompare`
+ * in tab order.
+ *
+ * @param elementToCompare The element to start searching forward in tab order
+ *     from.
+ * @param elementList Elements in which the first element that is later than
+ *     `elementToCompare` in tab order is to be returned if there is one;
+ *     `elements` should be sorted in DOM NLR tree order and should contain
+ *     `elementToCompare`.
+ * @return the first element in `elements` that is later than `elementToCompare`
+ *     in tab order if there is one; null otherwise.
+ */
+function getNextElementInTabOrder(
+    elementToCompare: Element, elementList: NodeListOf<Element>): Element|null {
+  // There is no defined behavior if the element is not reachable. Here the
+  // next reachable element in DOM tree order is returned. (This is what is
+  // observed in Mobile Safari and Chrome Desktop, if `elementToCompare` is not
+  // the last element in DOM tree order).
+  // TODO(chenyu): investigate and simulate Mobile Safari's behavior when
+  // `elementToCompare` is the last one in DOM tree order.
+  const elements = Array.from(elementList);
+  if (!isSequentiallyReachable(elementToCompare)) {
+    const indexToCompare = elements.indexOf(elementToCompare);
+    if (indexToCompare === elements.length - 1 || indexToCompare === -1) {
+      return null;
+    }
+    for (let index = indexToCompare + 1; index < elements.length; ++index) {
+      const element = elements[index]!;
+      if (element instanceof HTMLElement && isSequentiallyReachable(element)) {
+        return element;
+      }
+    }
+    return null;
+  }
+  // Returns true iff `element1` that has DOM tree position `index1` is after
+  // `element2` that has DOM tree position `index2` in tab order. It is assumed
+  // `index1 !== index2`.
+  const comparator = function(
+      element1: Element, index1: number, element2: Element, index2: number) {
+    const tabOrder1 = getTabOrder(element1);
+    const tabOrder2 = getTabOrder(element2);
+    return tabOrder1 > tabOrder2 ||
+        (tabOrder1 === tabOrder2 && index1 > index2);
+  };
+  return getFormElementAfter(elementToCompare, elements, comparator);
+}
+
+/**
+ * Returns the last element in `elements` that is earlier than
+ * `elementToCompare` in tab order.
+ *
+ * @param elementToCompare The element to start searching backward in tab order
+ *     from.
+ * @param elementList Elements in which the last element that is earlier than
+ *     `elementToCompare` in tab order is to be returned if there is one;
+ *     `elements` should be sorted in DOM tree order and it should contain
+ *     `elementToCompare`.
+ * @return the last element in `elements` that is earlier than
+ *     `elementToCompare` in tab order if there is one; null otherwise.
+ */
+function getPreviousElementInTabOrder(
+    elementToCompare: Element, elementList: NodeListOf<Element>): Element|null {
+  const elements = Array.from(elementList);
+  // There is no defined behavior if the element is not reachable. Here the
+  // previous reachable element in DOM tree order is returned.
+  if (!isSequentiallyReachable(elementToCompare)) {
+    const indexToCompare = elements.indexOf(elementToCompare);
+    if (indexToCompare <= 0) {  // Ignore if first or no element is found.
+      return null;
+    }
+    for (let index = indexToCompare - 1; index >= 0; --index) {
+      const element = elements[index];
+      if (element && element instanceof HTMLElement &&
+          isSequentiallyReachable(element)) {
+        return element;
+      }
+    }
+    return null;
+  }
+  // Returns true iff `element1` that has DOM tree position `index1` is before
+  // `element2` that has DOM tree position `index2` in tab order. It is assumed
+  // `index1 !== index2`.
+  const comparator = function(
+      element1: Element, index1: number, element2: Element,
+      index2: number): boolean {
+    const tabOrder1 = getTabOrder(element1);
+    const tabOrder2 = getTabOrder(element2);
+    return tabOrder1 < tabOrder2 ||
+        (tabOrder1 === tabOrder2 && index1 < index2);
+  };
+  return getFormElementAfter(elementToCompare, elements, comparator);
+}
+
+/**
+ * Given an element `elementToCompare`, such as
+ * `__gCrWeb.suggestion.isSequentiallyReachable(elementToCompare)`, and a list
+ * of `elements` which are sorted in DOM tree order and contains
+ * `elementToCompare`, this method returns the next element in `elements` after
+ * `elementToCompare` in the order defined by `comparator`, where an element is
+ * said to be 'after' anotherElement if and only if
+ * comparator(element, indexOfElement, anotherElement, anotherIndex) is true.
+ *
+ * @param elementToCompare The element to be compared.
+ * @param elementList Elements to compare; `elements` should be
+ *     sorted in DOM tree order and it should contain `elementToCompare`.
+ * @param comparator A function that returns a boolean, given an Element
+ *     `element1`, an integer that represents `element1`'s position in DOM tree
+ *     order, an Element `element2` and an integer that represents `element2`'s
+ *     position in DOM tree order.
+ * @return The element that satisfies the conditions given above.
+ */
+function getFormElementAfter(
+    elementToCompare: Element, elementList: Element[],
+    comparator: (
+        element1: Element, index1: number, element2: Element, index2: number) =>
+        boolean): Element|null {
+  // Computes the index `indexToCompare` of `elementToCompare` in `element`.
+  const indexToCompare = elementList.indexOf(elementToCompare);
+  if (indexToCompare === -1) {
+    return null;
+  }
+  let result: Element|null = null;
+  let resultIndex = -1;
+  for (let index = 0; index < elementList.length; ++index) {
+    if (index === indexToCompare) {
+      continue;
+    }
+    const element = elementList[index];
+    if (!element || !isSequentiallyReachable(element)) {
+      continue;
+    }
+    if (comparator(element, index, elementToCompare, indexToCompare)) {
+      if (!result) {
+        result = element;
+        resultIndex = index;
+      } else {
+        if (comparator(result, resultIndex, element, index)) {
+          result = element;
+          resultIndex = index;
+        }
+      }
+    }
+  }
+  return result;
+}
+
+/**
+ * Tests an element's visibility. This test is expensive so should be used
+ * sparingly.
+ *
+ * @param element A DOM element.
+ * @return true if the `element` is currently part of the visible
+ * DOM.
+ */
+function isElementVisible(element: Element): boolean {
+  let node: Node|null = element as Node;
+  while (node && node !== document) {
+    if (node.nodeType === Node.ELEMENT_NODE) {
+      const style = window.getComputedStyle(node as Element);
+      if (style.display === 'none' || style.visibility === 'hidden') {
+        return false;
+      }
+    }
+    // Move up the tree and test again.
+    node = node.parentNode;
+  }
+  // Test reached the top of the DOM without finding a concealed
+  // ancestor.
+  return true;
+}
+
+/**
+ * Returns if an element is reachable in sequential navigation.
+ *
+ * @param element The element that is to be examined.
+ * @return Whether an element is reachable in sequential navigation.
+ */
+function isSequentiallyReachable(element: Element): boolean {
+  // It is proposed in W3C that if tabIndex is omitted or parsing the
+  // value returns an error, the user agent should follow platform
+  // conventions to determine whether the element can be reached using
+  // sequential focus navigation, and if so, what its relative order
+  // should be. No document is found on the platform conventions in this
+  // case on iOS.
+  //
+  // There is a list of elements for which the tabIndex focus flags are
+  // suggested to be set in this case in W3C proposal. It is observed
+  // that in WKWebView parsing the tabIndex of an element in this list
+  // returns 0 if it is omitted or it is set to be an invalid value,
+  // undefined, null or NaN. So here it is assumed that all the elements
+  // that have invalid tabIndex are not reachable in sequential focus
+  // navigation.
+  //
+  // It is proposed in W3C that if tabIndex is a negative integer, the
+  // user agent should not allow the element to be reached using
+  // sequential focus navigation.
+  const tabIndex: number|null =
+      'tabIndex' in element ? element.tabIndex as number : null;
+  if ((!tabIndex && tabIndex !== 0) || tabIndex < 0) {
+    return false;
+  }
+  const elementType: string|null =
+      'type' in element ? element.type as string : null;
+  if (elementType === 'hidden' || element.hasAttribute('disabled')) {
+    return false;
+  }
+  // false is returned if `element` is neither an input nor a select.
+  // Note based on this condition, false is returned for an iframe (as
+  // Mobile Safari does not navigate to elements in an iframe, there is
+  // no need to recursively check if there is a reachable element in an
+  // iframe).
+  if (element.tagName !== 'INPUT' && element.tagName !== 'SELECT' &&
+      element.tagName !== 'TEXTAREA') {
+    return false;
+  }
+  // The following elements are skipped when navigating using 'Prev' and
+  // "Next' buttons in Mobile Safari.
+  if (element.tagName === 'INPUT' &&
+      (elementType === 'submit' || elementType === 'reset' ||
+       elementType === 'image' || elementType === 'button' ||
+       elementType === 'range' || elementType === 'radio' ||
+       elementType === 'checkbox')) {
+    return false;
+  }
+  // Expensive, final check that the element is not concealed.
+  return isElementVisible(element);
+}
+
+/**
+ * It is proposed in W3C an element that has a tabIndex greater than zero
+ * should be placed before any focusable element whose tabIndex is equal to
+ * zero in sequential focus navigation order. Here a value adjusted from
+ * tabIndex that reflects this order is returned. That is, given `element1`
+ * and `element2`, if `__gCrWeb.suggestion.getTabOrder(element1) >
+ * __gCrWeb.suggestion.getTabOrder(element2)`, then `element1` is after
+ * `element2` in sequential navigation.
+ *
+ * @param element The element of which the sequential navigation order
+ *     information is returned.
+ * @return An adjusted value that reflect `element`'s position in the sequential
+ *     navigation.
+ */
+function getTabOrder(element: Element): number {
+  const tabIndex: number|null =
+      'tabIndex' in element ? element.tabIndex as number : null;
+  if (tabIndex === 0) {
+    return Number.MAX_VALUE;
+  }
+  return tabIndex as number;
+}
+
+/**
+ * Returns the element named `fieldName` in the form specified by
+ * `formName`, if it exists.
+ *
+ * @param formName The name of the form containing the element.
+ * @param fieldName The name of the field containing the element.
+ * @return The element if found, otherwise null.
+ */
+function getFormElement(formName: string, fieldName: string): Element|null {
+  const form = gCrWeb.form.getFormElementFromIdentifier(formName);
+  if (!form) {
+    return null;
+  }
+  return getElementByNameWithParent(form, fieldName);
+}
+
+/**
+ * Focuses the next element in the sequential focus navigation. No operation
+ * if there is no such element.
+ */
+function selectNextElement(formName: string, fieldName: string) {
+  const currentElement =
+      formName ? getFormElement(formName, fieldName) : document.activeElement;
+  if (!currentElement) {
+    return;
+  }
+  const nextElement =
+      getNextElementInTabOrder(currentElement, document.querySelectorAll('*'));
+  if (nextElement && nextElement instanceof HTMLElement) {
+    nextElement.focus();
+  }
+}
+
+/**
+ * Focuses the previous element in the sequential focus navigation. No
+ * operation if there is no such element.
+ */
+function selectPreviousElement(formName: string, fieldName: string) {
+  const currentElement =
+      formName ? getFormElement(formName, fieldName) : document.activeElement;
+  if (!currentElement) {
+    return;
+  }
+  const prevElement = getPreviousElementInTabOrder(
+      currentElement, document.querySelectorAll('*'));
+  if (prevElement && prevElement instanceof HTMLElement) {
+    prevElement.focus();
+  }
+}
+
+/**
+ * @param formName The name of the form containing the element.
+ * @param fieldName The name of the field containing the element.
+ * @return Whether there is an element in the sequential navigation after the
+ *     currently active element.
+ */
+function hasNextElement(formName: string, fieldName: string): boolean {
+  const currentElement =
+      formName ? getFormElement(formName, fieldName) : document.activeElement;
+  if (!currentElement) {
+    return false;
+  }
+  return getNextElementInTabOrder(
+             currentElement, document.querySelectorAll('*')) !== null;
+}
+
+/**
+ * @param formName The name of the form containing the element.
+ * @param fieldName The name of the field containing the element.
+ * @return Whether there is an element in the sequential navigation before the
+ *     currently active element.
+ */
+function hasPreviousElement(formName: string, fieldName: string): boolean {
+  const currentElement =
+      formName ? getFormElement(formName, fieldName) : document.activeElement;
+  if (!currentElement) {
+    return false;
+  }
+  return getPreviousElementInTabOrder(
+             currentElement, document.querySelectorAll('*')) !== null;
+}
+
+/**
+ * @param formName The name of the form containing the element.
+ * @param fieldName The name of the field containing the element.
+ * @return Whether there is an element in the sequential navigation before and
+ *     after currently active element. The result is returned as a comma
+ *     separated string of the strings `true` and `false`.
+ *     TODO(crbug.com/893368): Return a dictionary with the values instead.
+ */
+function hasPreviousNextElements(formName: string, fieldName: string): string {
+  return [
+    hasPreviousElement(formName, fieldName),
+    hasNextElement(formName, fieldName),
+  ].toString();
+}
+
+gCrWeb.suggestion = {
+  getNextElementInTabOrder,
+  getPreviousElementInTabOrder,
+  selectNextElement,
+  selectPreviousElement,
+  hasNextElement,
+  hasPreviousElement,
+  hasPreviousNextElements,
+};
diff --git a/components/device_reauth/device_authenticator.h b/components/device_reauth/device_authenticator.h
index 8d0ea1c9..759f3724 100644
--- a/components/device_reauth/device_authenticator.h
+++ b/components/device_reauth/device_authenticator.h
@@ -82,7 +82,7 @@
   // Returns whether biometrics are available for a given device.
   virtual bool CanAuthenticateWithBiometrics() = 0;
 
-#if BUILDFLAG(IS_ANDROID)
+#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_MAC)
   // Returns whether biometrics or screenlock are available for a given device.
   virtual bool CanAuthenticateWithBiometricOrScreenLock() = 0;
 #endif
diff --git a/components/device_reauth/mock_device_authenticator.h b/components/device_reauth/mock_device_authenticator.h
index 5500729..fcef54cf 100644
--- a/components/device_reauth/mock_device_authenticator.h
+++ b/components/device_reauth/mock_device_authenticator.h
@@ -17,7 +17,7 @@
   MockDeviceAuthenticator();
 
   MOCK_METHOD(bool, CanAuthenticateWithBiometrics, (), (override));
-#if BUILDFLAG(IS_ANDROID)
+#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_MAC)
   MOCK_METHOD(bool, CanAuthenticateWithBiometricOrScreenLock, (), (override));
 #endif
   MOCK_METHOD(void,
diff --git a/components/enterprise/BUILD.gn b/components/enterprise/BUILD.gn
index a546329..b6bf3ea 100644
--- a/components/enterprise/BUILD.gn
+++ b/components/enterprise/BUILD.gn
@@ -22,8 +22,6 @@
     "browser/reporting/policy_info.h",
     "browser/reporting/profile_report_generator.cc",
     "browser/reporting/profile_report_generator.h",
-    "browser/reporting/real_time_report_controller.cc",
-    "browser/reporting/real_time_report_controller.h",
     "browser/reporting/real_time_report_generator.cc",
     "browser/reporting/real_time_report_generator.h",
     "browser/reporting/real_time_uploader.cc",
diff --git a/components/enterprise/browser/controller/chrome_browser_cloud_management_controller.cc b/components/enterprise/browser/controller/chrome_browser_cloud_management_controller.cc
index c52cc28..7b9ee9b 100644
--- a/components/enterprise/browser/controller/chrome_browser_cloud_management_controller.cc
+++ b/components/enterprise/browser/controller/chrome_browser_cloud_management_controller.cc
@@ -21,7 +21,6 @@
 #include "components/enterprise/browser/controller/browser_dm_token_storage.h"
 #include "components/enterprise/browser/controller/chrome_browser_cloud_management_helper.h"
 #include "components/enterprise/browser/enterprise_switches.h"
-#include "components/enterprise/browser/reporting/real_time_report_controller.h"
 #include "components/policy/core/browser/browser_policy_connector.h"
 #include "components/policy/core/common/cloud/client_data_delegate.h"
 #include "components/policy/core/common/cloud/cloud_external_data_manager.h"
@@ -505,10 +504,9 @@
   params.report_generator =
       std::make_unique<enterprise_reporting::ReportGenerator>(
           reporting_delegate_factory.get());
-  params.real_time_report_controller =
-      std::make_unique<enterprise_reporting::RealTimeReportController>(
+  params.real_time_report_generator =
+      std::make_unique<enterprise_reporting::RealTimeReportGenerator>(
           reporting_delegate_factory.get());
-
   report_scheduler_ = std::make_unique<enterprise_reporting::ReportScheduler>(
       std::move(params));
 
diff --git a/components/enterprise/browser/reporting/fake_browser_report_generator_delegate.cc b/components/enterprise/browser/reporting/fake_browser_report_generator_delegate.cc
index a421b62f..45a57769 100644
--- a/components/enterprise/browser/reporting/fake_browser_report_generator_delegate.cc
+++ b/components/enterprise/browser/reporting/fake_browser_report_generator_delegate.cc
@@ -11,7 +11,6 @@
 #include "base/files/file_path.h"
 #include "base/strings/string_piece.h"
 #include "components/enterprise/browser/reporting/browser_report_generator.h"
-#include "components/enterprise/browser/reporting/real_time_report_controller.h"
 #include "components/enterprise/browser/reporting/report_util.h"
 #include "components/enterprise/browser/reporting/reporting_delegate_factory.h"
 #include "components/version_info/channel.h"
@@ -114,9 +113,4 @@
   return nullptr;
 }
 
-std::unique_ptr<RealTimeReportController::Delegate>
-FakeReportingDelegateFactory::GetRealTimeReportControllerDelegate() {
-  return nullptr;
-}
-
 }  // namespace enterprise_reporting::test
diff --git a/components/enterprise/browser/reporting/fake_browser_report_generator_delegate.h b/components/enterprise/browser/reporting/fake_browser_report_generator_delegate.h
index 82b14633..084d8a0 100644
--- a/components/enterprise/browser/reporting/fake_browser_report_generator_delegate.h
+++ b/components/enterprise/browser/reporting/fake_browser_report_generator_delegate.h
@@ -11,7 +11,6 @@
 #include "base/files/file_path.h"
 #include "base/strings/string_piece.h"
 #include "components/enterprise/browser/reporting/browser_report_generator.h"
-#include "components/enterprise/browser/reporting/real_time_report_controller.h"
 #include "components/enterprise/browser/reporting/report_util.h"
 #include "components/enterprise/browser/reporting/reporting_delegate_factory.h"
 #include "components/version_info/channel.h"
@@ -94,9 +93,6 @@
   std::unique_ptr<RealTimeReportGenerator::Delegate>
   GetRealTimeReportGeneratorDelegate() override;
 
-  std::unique_ptr<RealTimeReportController::Delegate>
-  GetRealTimeReportControllerDelegate() override;
-
  private:
   const std::string executable_path_;
 };
diff --git a/components/enterprise/browser/reporting/real_time_report_controller.cc b/components/enterprise/browser/reporting/real_time_report_controller.cc
deleted file mode 100644
index 5dc1429..0000000
--- a/components/enterprise/browser/reporting/real_time_report_controller.cc
+++ /dev/null
@@ -1,113 +0,0 @@
-// Copyright 2023 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/enterprise/browser/reporting/real_time_report_controller.h"
-
-#include "components/enterprise/browser/reporting/real_time_report_generator.h"
-#include "components/enterprise/browser/reporting/real_time_uploader.h"
-#include "components/enterprise/browser/reporting/reporting_delegate_factory.h"
-#include "components/policy/core/common/cloud/dm_token.h"
-
-namespace enterprise_reporting {
-
-namespace {
-void OnExtensionRequestEnqueued(bool success) {
-  // So far, there is nothing handle the enqueue failure as the CBCM status
-  // report will cover all failed requests. However, we may need a retry logic
-  // here if Extension workflow is decoupled from the status report.
-  if (!success) {
-    LOG(ERROR) << "Extension request failed to be added to the pipeline.";
-  }
-}
-
-}  // namespace
-
-RealTimeReportController::RealTimeReportController(
-    ReportingDelegateFactory* delegate_factory)
-    : real_time_report_generator_(
-          std::make_unique<RealTimeReportGenerator>(delegate_factory)),
-      delegate_(delegate_factory->GetRealTimeReportControllerDelegate()) {
-  if (delegate_) {
-    delegate_->SetTriggerCallback(
-        base::BindRepeating(&RealTimeReportController::GenerateAndUploadReport,
-                            weak_ptr_factory_.GetWeakPtr()));
-  }
-}
-RealTimeReportController::~RealTimeReportController() = default;
-
-RealTimeReportController::Delegate::Delegate() = default;
-RealTimeReportController::Delegate::~Delegate() = default;
-
-void RealTimeReportController::Delegate::SetTriggerCallback(
-    RealTimeReportController::TriggerCallback callback) {
-  DCHECK(!trigger_callback_);
-  DCHECK(callback);
-  trigger_callback_ = std::move(callback);
-}
-
-void RealTimeReportController::OnDMTokenUpdated(policy::DMToken&& dm_token) {
-  if (!delegate_) {
-    return;
-  }
-
-  dm_token_ = dm_token;
-  if (dm_token_.is_valid()) {
-    delegate_->StartWatchingExtensionRequestIfNeeded();
-  } else {
-    delegate_->StopWatchingExtensionRequest();
-    extension_request_uploader_.reset();
-  }
-}
-
-void RealTimeReportController::GenerateAndUploadReport(
-    ReportTrigger trigger,
-    const RealTimeReportGenerator::Data& data) {
-  if (!dm_token_.is_valid()) {
-    return;
-  }
-
-  if (trigger == RealTimeReportController::ReportTrigger::kExtensionRequest) {
-    UploadExtensionRequests(data);
-  }
-}
-
-void RealTimeReportController::UploadExtensionRequests(
-    const RealTimeReportGenerator::Data& data) {
-  DCHECK(real_time_report_generator_);
-  VLOG(1) << "Create extension request and add it to the pipeline.";
-
-  if (!dm_token_.is_valid()) {
-    return;
-  }
-
-  if (!extension_request_uploader_) {
-    extension_request_uploader_ = RealTimeUploader::Create(
-        dm_token_.value(), reporting::Destination::EXTENSIONS_WORKFLOW,
-        reporting::Priority::FAST_BATCH);
-  }
-  auto reports = real_time_report_generator_->Generate(
-      RealTimeReportGenerator::ReportType::kExtensionRequest, data);
-
-  for (auto& report : reports) {
-    extension_request_uploader_->Upload(
-        std::move(report), base::BindOnce(&OnExtensionRequestEnqueued));
-  }
-}
-
-void RealTimeReportController::SetExtensionRequestUploaderForTesting(
-    std::unique_ptr<RealTimeUploader> uploader) {
-  extension_request_uploader_ = std::move(uploader);
-}
-
-void RealTimeReportController::SetReportGeneratorForTesting(
-    std::unique_ptr<RealTimeReportGenerator> generator) {
-  real_time_report_generator_ = std::move(generator);
-}
-
-RealTimeReportController::Delegate*
-RealTimeReportController::GetDelegateForTesting() {
-  return delegate_.get();
-}
-
-}  // namespace enterprise_reporting
diff --git a/components/enterprise/browser/reporting/real_time_report_controller.h b/components/enterprise/browser/reporting/real_time_report_controller.h
deleted file mode 100644
index 2ba533e2..0000000
--- a/components/enterprise/browser/reporting/real_time_report_controller.h
+++ /dev/null
@@ -1,77 +0,0 @@
-// Copyright 2023 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef COMPONENTS_ENTERPRISE_BROWSER_REPORTING_REAL_TIME_REPORT_CONTROLLER_H_
-#define COMPONENTS_ENTERPRISE_BROWSER_REPORTING_REAL_TIME_REPORT_CONTROLLER_H_
-
-#include <string>
-
-#include "base/functional/callback.h"
-#include "base/memory/weak_ptr.h"
-#include "components/enterprise/browser/reporting/real_time_report_generator.h"
-#include "components/policy/core/common/cloud/dm_token.h"
-
-namespace enterprise_reporting {
-
-class RealTimeUploader;
-class ReportingDelegateFactory;
-
-class RealTimeReportController {
- public:
-  explicit RealTimeReportController(ReportingDelegateFactory* delegate_factory);
-  RealTimeReportController(const RealTimeReportController&) = delete;
-  RealTimeReportController& operator=(const RealTimeReportController&) = delete;
-  ~RealTimeReportController();
-
-  enum ReportTrigger {
-    kExtensionRequest,
-  };
-
-  using TriggerCallback =
-      base::RepeatingCallback<void(ReportTrigger,
-                                   const RealTimeReportGenerator::Data&)>;
-
-  class Delegate {
-   public:
-    Delegate();
-    Delegate(const Delegate&) = delete;
-    Delegate& operator=(const Delegate&) = delete;
-    virtual ~Delegate();
-
-    void SetTriggerCallback(TriggerCallback callback);
-
-    // Extension request
-    virtual void StartWatchingExtensionRequestIfNeeded() = 0;
-    virtual void StopWatchingExtensionRequest() = 0;
-
-   protected:
-    TriggerCallback trigger_callback_;
-  };
-
-  void OnDMTokenUpdated(policy::DMToken&& dm_token);
-
-  void GenerateAndUploadReport(ReportTrigger trigger,
-                               const RealTimeReportGenerator::Data& data);
-
-  void SetExtensionRequestUploaderForTesting(
-      std::unique_ptr<RealTimeUploader> uploader);
-  void SetReportGeneratorForTesting(
-      std::unique_ptr<RealTimeReportGenerator> generator);
-  Delegate* GetDelegateForTesting();
-
- private:
-  // Creates and uploads extension requests with real time reporting pipeline.
-  void UploadExtensionRequests(const RealTimeReportGenerator::Data& data);
-
-  policy::DMToken dm_token_ = policy::DMToken::CreateEmptyToken();
-  std::unique_ptr<RealTimeUploader> extension_request_uploader_;
-  std::unique_ptr<RealTimeReportGenerator> real_time_report_generator_;
-
-  std::unique_ptr<Delegate> delegate_;
-
-  base::WeakPtrFactory<RealTimeReportController> weak_ptr_factory_{this};
-};
-}  // namespace enterprise_reporting
-
-#endif  // COMPONENTS_ENTERPRISE_BROWSER_REPORTING_REAL_TIME_REPORT_CONTROLLER_H_
diff --git a/components/enterprise/browser/reporting/report_scheduler.cc b/components/enterprise/browser/reporting/report_scheduler.cc
index 7b2d8d3..d10ba55 100644
--- a/components/enterprise/browser/reporting/report_scheduler.cc
+++ b/components/enterprise/browser/reporting/report_scheduler.cc
@@ -16,10 +16,11 @@
 #include "build/chromeos_buildflags.h"
 #include "components/enterprise/browser/controller/browser_dm_token_storage.h"
 #include "components/enterprise/browser/reporting/common_pref_names.h"
+#include "components/enterprise/browser/reporting/real_time_report_generator.h"
+#include "components/enterprise/browser/reporting/real_time_uploader.h"
 #include "components/enterprise/browser/reporting/reporting_delegate_factory.h"
 #include "components/policy/core/common/cloud/cloud_policy_client.h"
 #include "components/policy/core/common/cloud/device_management_service.h"
-#include "components/policy/core/common/cloud/dm_token.h"
 #include "components/prefs/pref_service.h"
 
 namespace em = enterprise_management;
@@ -38,10 +39,32 @@
     case ReportScheduler::kTriggerNewVersion:
       return true;
     case ReportScheduler::kTriggerNone:
+    case ReportScheduler::kTriggerExtensionRequestRealTime:
       return false;
   }
 }
 
+bool IsExtensionRequestUploaded(ReportScheduler::ReportTrigger trigger) {
+  switch (trigger) {
+    case ReportScheduler::kTriggerTimer:
+    case ReportScheduler::kTriggerManual:
+    case ReportScheduler::kTriggerExtensionRequestRealTime:
+      return true;
+    case ReportScheduler::kTriggerNone:
+    case ReportScheduler::kTriggerUpdate:
+    case ReportScheduler::kTriggerNewVersion:
+      return false;
+  }
+}
+
+void OnExtensionRequestEnqueued(bool success) {
+  // So far, there is nothing handle the enqueue failure as the CBCM status
+  // report will cover all failed requests. However, we may need a retry logic
+  // here if Extension workflow is decoupled from the status report.
+  if (!success)
+    LOG(ERROR) << "Extension request failed to be added to the pipeline.";
+}
+
 }  // namespace
 
 ReportScheduler::Delegate::Delegate() = default;
@@ -60,13 +83,19 @@
   trigger_report_callback_ = std::move(callback);
 }
 
+void ReportScheduler::Delegate::SetRealtimeReportTriggerCallback(
+    ReportScheduler::RealtimeReportTriggerCallback callback) {
+  DCHECK(trigger_realtime_report_callback_.is_null());
+  trigger_realtime_report_callback_ = std::move(callback);
+}
+
 ReportScheduler::ReportScheduler(CreateParams params)
     : delegate_(std::move(params.delegate)),
       cloud_policy_client_(params.client),
       report_generator_(std::move(params.report_generator)),
       profile_request_generator_(std::move(params.profile_request_generator)),
-      real_time_report_controller_(
-          std::move(params.real_time_report_controller)) {
+      real_time_report_generator_(
+          std::move(params.real_time_report_generator)) {
   DCHECK(cloud_policy_client_);
   DCHECK(delegate_);
 
@@ -81,6 +110,10 @@
   delegate_->SetReportTriggerCallback(
       base::BindRepeating(&ReportScheduler::GenerateAndUploadReport,
                           weak_ptr_factory_.GetWeakPtr()));
+  delegate_->SetRealtimeReportTriggerCallback(
+      base::BindRepeating(&ReportScheduler::GenerateAndUploadRealtimeReport,
+                          weak_ptr_factory_.GetWeakPtr()));
+
   RegisterPrefObserver();
 }
 
@@ -104,15 +137,17 @@
   report_uploader_ = std::move(uploader);
 }
 
+void ReportScheduler::SetExtensionRequestUploaderForTesting(
+    std::unique_ptr<RealTimeUploader> uploader) {
+  extension_request_uploader_ = std::move(uploader);
+}
+
 ReportScheduler::Delegate* ReportScheduler::GetDelegateForTesting() {
   return delegate_.get();
 }
 
 void ReportScheduler::OnDMTokenUpdated() {
   OnReportEnabledPrefChanged();
-  if (real_time_report_controller_) {
-    real_time_report_controller_->OnDMTokenUpdated(GetDMToken());
-  }
 }
 
 void ReportScheduler::UploadFullReport(base::OnceClosure on_report_uploaded) {
@@ -138,7 +173,7 @@
       base::BindRepeating(&ReportScheduler::OnReportEnabledPrefChanged,
                           base::Unretained(this)));
   // Trigger first pref check during launch process.
-  OnDMTokenUpdated();
+  OnReportEnabledPrefChanged();
 }
 
 void ReportScheduler::OnReportEnabledPrefChanged() {
@@ -167,7 +202,7 @@
   }
 
   // Only device report generator support real time partial version report with
-  // DM Server. With longer term, this should use `real_time_report_controller_`
+  // DM Server. With longer term, this should use `real_time_report_generator_`
   // instead.
   if (report_generator_) {
     delegate_->StartWatchingUpdatesIfNeeded(
@@ -175,12 +210,18 @@
         delegate_->GetPrefService()->GetTimeDelta(
             kCloudReportingUploadFrequency));
   }
+
+  // Enable real time report if the generator is provided.
+  if (real_time_report_generator_)
+    delegate_->StartWatchingExtensionRequestIfNeeded();
 }
 
 void ReportScheduler::Stop() {
   request_timer_.Stop();
   if (report_generator_)
     delegate_->StopWatchingUpdates();
+  delegate_->StopWatchingExtensionRequest();
+  extension_request_uploader_.reset();
   report_uploader_.reset();
   if (pref_change_registrar_.IsObserved(kCloudReportingUploadFrequency))
     pref_change_registrar_.Remove(kCloudReportingUploadFrequency);
@@ -195,14 +236,16 @@
   if (cloud_policy_client_->is_registered())
     return true;
 
-  auto dm_token = GetDMToken();
+  auto dm_token = policy::DMToken::CreateEmptyToken();
   std::string client_id;
   if (profile_request_generator_) {
     // Get token for profile reporting
+    dm_token = delegate_->GetProfileDMToken();
     client_id = delegate_->GetProfileClientId();
   } else {
     // Get token for browser reporting
 #if !BUILDFLAG(IS_CHROMEOS_ASH)
+    dm_token = policy::BrowserDMTokenStorage::Get()->RetrieveDMToken();
     client_id = policy::BrowserDMTokenStorage::Get()->RetrieveClientId();
 #else
     NOTREACHED();
@@ -260,6 +303,15 @@
   }
 }
 
+void ReportScheduler::GenerateAndUploadRealtimeReport(
+    ReportTrigger trigger,
+    const RealTimeReportGenerator::Data& data) {
+  if (trigger == kTriggerExtensionRequestRealTime) {
+    UploadExtensionRequests(data);
+    return;
+  }
+}
+
 void ReportScheduler::OnReportGenerated(ReportRequestQueue requests) {
   DCHECK_NE(active_trigger_, kTriggerNone);
   if (requests.empty()) {
@@ -294,6 +346,9 @@
       if (IsBrowserVersionUploaded(active_trigger_))
         delegate_->OnBrowserVersionUploaded();
 
+      if (IsExtensionRequestUploaded(active_trigger_))
+        delegate_->OnExtensionRequestUploaded();
+
       delegate_->GetPrefService()->SetTime(kLastUploadSucceededTimestamp,
                                            base::Time::Now());
       [[fallthrough]];
@@ -355,6 +410,28 @@
   GenerateAndUploadReport(trigger);
 }
 
+void ReportScheduler::UploadExtensionRequests(
+    const RealTimeReportGenerator::Data& data) {
+  RecordUploadTrigger(kTriggerExtensionRequestRealTime);
+  DCHECK(real_time_report_generator_);
+  VLOG(1) << "Create extension request and add it to the pipeline.";
+  if (!extension_request_uploader_) {
+    extension_request_uploader_ =
+        RealTimeUploader::Create(cloud_policy_client_->dm_token(),
+                                 reporting::Destination::EXTENSIONS_WORKFLOW,
+                                 reporting::Priority::FAST_BATCH);
+  }
+  auto reports = real_time_report_generator_->Generate(
+      RealTimeReportGenerator::ReportType::kExtensionRequest, data);
+
+  for (auto& report : reports) {
+    extension_request_uploader_->Upload(
+        std::move(report), base::BindOnce(&OnExtensionRequestEnqueued));
+  }
+
+  delegate_->OnExtensionRequestUploaded();
+}
+
 // static
 void ReportScheduler::RecordUploadTrigger(ReportTrigger trigger) {
   // These values are persisted to logs. Entries should not be renumbered and
@@ -364,8 +441,8 @@
     kTimer = 1,
     kUpdate = 2,
     kNewVersion = 3,
-    kExtensionRequest = 4,          // Deprecated.
-    kExtensionRequestRealTime = 5,  // Deprecated.
+    kExtensionRequest = 4,  // Deprecated.
+    kExtensionRequestRealTime = 5,
     kManual = 6,
     kMaxValue = kManual
   } sample = Sample::kNone;
@@ -384,6 +461,9 @@
     case kTriggerNewVersion:
       sample = Sample::kNewVersion;
       break;
+    case kTriggerExtensionRequestRealTime:
+      sample = Sample::kExtensionRequestRealTime;
+      break;
   }
   base::UmaHistogramEnumeration("Enterprise.CloudReportingUploadTrigger",
                                 sample);
@@ -393,6 +473,7 @@
     ReportScheduler::ReportTrigger trigger) {
   switch (trigger) {
     case ReportScheduler::kTriggerNone:
+    case ReportScheduler::kTriggerExtensionRequestRealTime:
       NOTREACHED();
       [[fallthrough]];
     case ReportScheduler::kTriggerTimer:
@@ -405,16 +486,4 @@
   }
 }
 
-policy::DMToken ReportScheduler::GetDMToken() {
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-  return policy::DMToken::CreateValidToken(cloud_policy_client_->dm_token());
-#else
-  if (profile_request_generator_) {
-    return delegate_->GetProfileDMToken();
-  } else {
-    return policy::BrowserDMTokenStorage::Get()->RetrieveDMToken();
-  }
-#endif
-}
-
 }  // namespace enterprise_reporting
diff --git a/components/enterprise/browser/reporting/report_scheduler.h b/components/enterprise/browser/reporting/report_scheduler.h
index b70a08b..f218b82f 100644
--- a/components/enterprise/browser/reporting/report_scheduler.h
+++ b/components/enterprise/browser/reporting/report_scheduler.h
@@ -14,7 +14,7 @@
 #include "base/time/time.h"
 #include "base/timer/wall_clock_timer.h"
 #include "components/enterprise/browser/reporting/chrome_profile_request_generator.h"
-#include "components/enterprise/browser/reporting/real_time_report_controller.h"
+#include "components/enterprise/browser/reporting/real_time_report_generator.h"
 #include "components/enterprise/browser/reporting/report_generator.h"
 #include "components/enterprise/browser/reporting/report_uploader.h"
 #include "components/policy/core/common/cloud/dm_token.h"
@@ -22,12 +22,11 @@
 
 namespace policy {
 class CloudPolicyClient;
-class DMToken;
 }  // namespace policy
 
 namespace enterprise_reporting {
 
-class RealTimeReportController;
+class RealTimeUploader;
 
 // Schedules report generation and upload every 24 hours (and upon browser
 // update for desktop Chrome) while cloud reporting is enabled via
@@ -44,9 +43,14 @@
     kTriggerUpdate = 1U << 1,      // An update was detected.
     kTriggerNewVersion = 1U << 2,  // A new version is running.
     kTriggerManual = 1U << 3,      // Trigger manually.
+    // Pending extension requests updated, with encrypted realtime pipeline.
+    kTriggerExtensionRequestRealTime = 1U << 4,
   };
 
   using ReportTriggerCallback = base::RepeatingCallback<void(ReportTrigger)>;
+  using RealtimeReportTriggerCallback =
+      base::RepeatingCallback<void(ReportTrigger,
+                                   const RealTimeReportGenerator::Data&)>;
 
   class Delegate {
    public:
@@ -57,6 +61,8 @@
     virtual ~Delegate();
 
     void SetReportTriggerCallback(ReportTriggerCallback callback);
+    void SetRealtimeReportTriggerCallback(
+        RealtimeReportTriggerCallback callback);
 
     virtual PrefService* GetPrefService() = 0;
 
@@ -67,11 +73,17 @@
     virtual void StopWatchingUpdates() = 0;
     virtual void OnBrowserVersionUploaded() = 0;
 
+    // Extension request
+    virtual void StartWatchingExtensionRequestIfNeeded() = 0;
+    virtual void StopWatchingExtensionRequest() = 0;
+    virtual void OnExtensionRequestUploaded() = 0;
+
     virtual policy::DMToken GetProfileDMToken() = 0;
     virtual std::string GetProfileClientId() = 0;
 
    protected:
     ReportTriggerCallback trigger_report_callback_;
+    RealtimeReportTriggerCallback trigger_realtime_report_callback_;
   };
 
   struct CreateParams {
@@ -84,7 +96,7 @@
 
     raw_ptr<policy::CloudPolicyClient> client;
     std::unique_ptr<ReportGenerator> report_generator;
-    std::unique_ptr<RealTimeReportController> real_time_report_controller;
+    std::unique_ptr<RealTimeReportGenerator> real_time_report_generator;
     std::unique_ptr<ChromeProfileRequestGenerator> profile_request_generator;
     std::unique_ptr<ReportScheduler::Delegate> delegate;
   };
@@ -107,6 +119,8 @@
   ReportTrigger GetActiveTriggerForTesting() const;
 
   void SetReportUploaderForTesting(std::unique_ptr<ReportUploader> uploader);
+  void SetExtensionRequestUploaderForTesting(
+      std::unique_ptr<RealTimeUploader> uploader);
   Delegate* GetDelegateForTesting();
 
   void OnDMTokenUpdated();
@@ -137,6 +151,9 @@
 
   // Starts report generation in response to |trigger|.
   void GenerateAndUploadReport(ReportTrigger trigger);
+  void GenerateAndUploadRealtimeReport(
+      ReportTrigger trigger,
+      const RealTimeReportGenerator::Data& data);
 
   // Continues processing a report (contained in the |requests| collection) by
   // sending it to the uploader.
@@ -150,13 +167,14 @@
   // of another report.
   void RunPendingTriggers();
 
+  // Creates and uploads extension requests with real time reporting pipeline.
+  void UploadExtensionRequests(const RealTimeReportGenerator::Data& data);
+
   // Records that |trigger| was responsible for an upload attempt.
   static void RecordUploadTrigger(ReportTrigger trigger);
 
   ReportType TriggerToReportType(ReportTrigger trigger);
 
-  policy::DMToken GetDMToken();
-
   std::unique_ptr<Delegate> delegate_;
 
   // Policy value watcher
@@ -167,10 +185,11 @@
   base::WallClockTimer request_timer_;
 
   std::unique_ptr<ReportUploader> report_uploader_;
+  std::unique_ptr<RealTimeUploader> extension_request_uploader_;
 
   std::unique_ptr<ReportGenerator> report_generator_;
   std::unique_ptr<ChromeProfileRequestGenerator> profile_request_generator_;
-  std::unique_ptr<RealTimeReportController> real_time_report_controller_;
+  std::unique_ptr<RealTimeReportGenerator> real_time_report_generator_;
 
   // The trigger responsible for initiating active report generation.
   ReportTrigger active_trigger_ = kTriggerNone;
diff --git a/components/enterprise/browser/reporting/reporting_delegate_factory.h b/components/enterprise/browser/reporting/reporting_delegate_factory.h
index 615fa1b..eeca01f 100644
--- a/components/enterprise/browser/reporting/reporting_delegate_factory.h
+++ b/components/enterprise/browser/reporting/reporting_delegate_factory.h
@@ -9,7 +9,6 @@
 
 #include "components/enterprise/browser/reporting/browser_report_generator.h"
 #include "components/enterprise/browser/reporting/profile_report_generator.h"
-#include "components/enterprise/browser/reporting/real_time_report_controller.h"
 #include "components/enterprise/browser/reporting/real_time_report_generator.h"
 #include "components/enterprise/browser/reporting/report_generator.h"
 #include "components/enterprise/browser/reporting/report_scheduler.h"
@@ -38,9 +37,6 @@
 
   virtual std::unique_ptr<RealTimeReportGenerator::Delegate>
   GetRealTimeReportGeneratorDelegate() = 0;
-
-  virtual std::unique_ptr<RealTimeReportController::Delegate>
-  GetRealTimeReportControllerDelegate() = 0;
 };
 
 }  // namespace enterprise_reporting
diff --git a/components/feed/core/v2/BUILD.gn b/components/feed/core/v2/BUILD.gn
index 3085e49..bc8cbbe8 100644
--- a/components/feed/core/v2/BUILD.gn
+++ b/components/feed/core/v2/BUILD.gn
@@ -159,6 +159,7 @@
     "//components/offline_pages/task:task",
     "//components/prefs",
     "//components/reading_list/features:flags",
+    "//components/search_engines",
     "//components/signin/public/identity_manager",
     "//components/url_matcher:url_matcher",
     "//components/variations",
@@ -269,6 +270,7 @@
     "//components/offline_pages/core:test_support",
     "//components/prefs:test_support",
     "//components/reading_list/features:flags",
+    "//components/search_engines",
     "//components/signin/public/identity_manager",
     "//components/signin/public/identity_manager:test_support",
     "//components/sync/base",
diff --git a/components/feed/core/v2/DEPS b/components/feed/core/v2/DEPS
index 14e5343..833d0912 100644
--- a/components/feed/core/v2/DEPS
+++ b/components/feed/core/v2/DEPS
@@ -3,6 +3,7 @@
   "-components/feed/core/v2/tasks",
   "-components/feed/core/v2/web_feed_subscriptions",
   "+components/reading_list/features",
+  "+components/search_engines",
  ]
 
 specific_include_rules = {
diff --git a/components/feed/core/v2/api_test/feed_api_stream_unittest.cc b/components/feed/core/v2/api_test/feed_api_stream_unittest.cc
index 00e7e34..b332482 100644
--- a/components/feed/core/v2/api_test/feed_api_stream_unittest.cc
+++ b/components/feed/core/v2/api_test/feed_api_stream_unittest.cc
@@ -4095,6 +4095,16 @@
             feedwire::ChromeSignInStatus::SIGNED_IN_WITHOUT_SYNC);
 }
 
+TEST_F(FeedApiTest, GetRequestMetadataWithDefaultSearchEngine) {
+  TestForYouSurface surface(stream_.get());
+  RequestMetadata metadata =
+      stream_->GetRequestMetadata(StreamType(StreamKind::kForYou), false);
+
+  // Defaults to Google for tests.
+  ASSERT_EQ(metadata.default_search_engine,
+            feedwire::DefaultSearchEngine::ENGINE_GOOGLE);
+}
+
 TEST_F(FeedApiTest, ClearAllOnSigninAllowedPrefChange) {
   CallbackReceiver<> on_clear_all;
   on_clear_all_ = on_clear_all.BindRepeating();
diff --git a/components/feed/core/v2/api_test/feed_api_test.cc b/components/feed/core/v2/api_test/feed_api_test.cc
index f673da8..052d759 100644
--- a/components/feed/core/v2/api_test/feed_api_test.cc
+++ b/components/feed/core/v2/api_test/feed_api_test.cc
@@ -913,6 +913,10 @@
   image_fetcher_ =
       std::make_unique<TestImageFetcher>(shared_url_loader_factory_);
 
+  // Test initialization of TemplateURLService that defaults to Google as
+  // the default search engine.
+  template_url_service_ = std::make_unique<TemplateURLService>(nullptr, 0);
+
   CreateStream();
 }
 
@@ -990,7 +994,8 @@
   stream_ = std::make_unique<FeedStream>(
       &refresh_scheduler_, metrics_reporter_.get(), this, &profile_prefs_,
       &network_, image_fetcher_.get(), store_.get(),
-      persistent_key_value_store_.get(), chrome_info);
+      persistent_key_value_store_.get(), template_url_service_.get(),
+      chrome_info);
   stream_->SetWireResponseTranslatorForTesting(&response_translator_);
 
   if (wait_for_initialization)
diff --git a/components/feed/core/v2/api_test/feed_api_test.h b/components/feed/core/v2/api_test/feed_api_test.h
index d67bd2b..bfdb6aa 100644
--- a/components/feed/core/v2/api_test/feed_api_test.h
+++ b/components/feed/core/v2/api_test/feed_api_test.h
@@ -39,6 +39,7 @@
 #include "components/feed/core/v2/wire_response_translator.h"
 #include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/testing_pref_service.h"
+#include "components/search_engines/template_url_service.h"
 #include "components/signin/public/base/signin_pref_names.h"
 #include "net/http/http_status_code.h"
 #include "services/network/test/test_url_loader_factory.h"
@@ -542,6 +543,8 @@
               /*db_dir=*/{},
               task_environment_.GetMainThreadTaskRunner()));
 
+  std::unique_ptr<TemplateURLService> template_url_service_;
+
   FakeRefreshTaskScheduler refresh_scheduler_;
   StreamModel::Context stream_model_context_;
   std::unique_ptr<FeedStream> stream_;
diff --git a/components/feed/core/v2/feed_stream.cc b/components/feed/core/v2/feed_stream.cc
index 694c9b9..e88ce212 100644
--- a/components/feed/core/v2/feed_stream.cc
+++ b/components/feed/core/v2/feed_stream.cc
@@ -140,6 +140,7 @@
                        ImageFetcher* image_fetcher,
                        FeedStore* feed_store,
                        PersistentKeyValueStoreImpl* persistent_key_value_store,
+                       TemplateURLService* template_url_service,
                        const ChromeInfo& chrome_info)
     : refresh_task_scheduler_(refresh_task_scheduler),
       metrics_reporter_(metrics_reporter),
@@ -149,6 +150,7 @@
       image_fetcher_(image_fetcher),
       store_(feed_store),
       persistent_key_value_store_(persistent_key_value_store),
+      template_url_service_(template_url_service),
       chrome_info_(chrome_info),
       task_queue_(this),
       request_throttler_(profile_prefs),
@@ -1009,6 +1011,20 @@
   return feedwire::ChromeSignInStatus::NOT_SIGNED_IN;
 }
 
+feedwire::DefaultSearchEngine::SearchEngine FeedStream::GetDefaultSearchEngine()
+    const {
+  const TemplateURL* template_url =
+      template_url_service_->GetDefaultSearchProvider();
+  if (template_url) {
+    SearchEngineType engine_type =
+        template_url->GetEngineType(template_url_service_->search_terms_data());
+    if (engine_type == SEARCH_ENGINE_GOOGLE) {
+      return feedwire::DefaultSearchEngine::ENGINE_GOOGLE;
+    }
+  }
+  return feedwire::DefaultSearchEngine::ENGINE_OTHER;
+}
+
 RequestMetadata FeedStream::GetCommonRequestMetadata(
     bool signed_in_request,
     bool allow_expired_session_id) const {
@@ -1076,6 +1092,8 @@
   // Set sign in status for request metadata
   result.sign_in_status = GetSignInStatus();
 
+  result.default_search_engine = GetDefaultSearchEngine();
+
   return result;
 }
 
diff --git a/components/feed/core/v2/feed_stream.h b/components/feed/core/v2/feed_stream.h
index aa78fb0..a8f7b1c 100644
--- a/components/feed/core/v2/feed_stream.h
+++ b/components/feed/core/v2/feed_stream.h
@@ -44,6 +44,7 @@
 #include "components/feed/core/v2/xsurface_datastore.h"
 #include "components/offline_pages/task/task_queue.h"
 #include "components/prefs/pref_member.h"
+#include "components/search_engines/template_url_service.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
 class PrefService;
@@ -100,6 +101,7 @@
              ImageFetcher* image_fetcher,
              FeedStore* feed_store,
              PersistentKeyValueStoreImpl* persistent_key_value_store,
+             TemplateURLService* template_url_service,
              const ChromeInfo& chrome_info);
   ~FeedStream() override;
 
@@ -432,6 +434,8 @@
   void CheckDuplicatedContentsOnRefresh();
   void AddViewedContentHashes(const feedstore::Content& content);
 
+  feedwire::DefaultSearchEngine::SearchEngine GetDefaultSearchEngine() const;
+
   // Unowned.
 
   raw_ptr<RefreshTaskScheduler> refresh_task_scheduler_;
@@ -444,6 +448,7 @@
   raw_ptr<PersistentKeyValueStoreImpl, DanglingUntriaged>
       persistent_key_value_store_;
   raw_ptr<const WireResponseTranslator> wire_response_translator_;
+  raw_ptr<TemplateURLService> template_url_service_;
 
   StreamModel::Context stream_model_context_;
   // For Xsurface datastore data which applies to all `StreamType`s.
diff --git a/components/feed/core/v2/proto_util.cc b/components/feed/core/v2/proto_util.cc
index c654be7..b7f9ad73 100644
--- a/components/feed/core/v2/proto_util.cc
+++ b/components/feed/core/v2/proto_util.cc
@@ -313,6 +313,16 @@
       ->set_sign_in_status(request_metadata.sign_in_status);
 }
 
+// Set the default search engine currently set in Chrome.
+void SetDefaultSearchEngine(feedwire::Request* request,
+                            const RequestMetadata& request_metadata) {
+  request->mutable_feed_request()
+      ->mutable_feed_query()
+      ->mutable_chrome_fulfillment_info()
+      ->mutable_default_search_engine()
+      ->set_search_engine(request_metadata.default_search_engine);
+}
+
 void WriteDocIdsTable(const std::vector<DocViewCount> doc_view_counts,
                       feedwire::Table& table) {
   table.set_name("url_all_ondevice");
@@ -442,6 +452,7 @@
   SetInfoCardTrackingStates(&request, request_metadata);
   SetTimesFollowedFromWebPageMenu(&request, request_metadata);
   SetChromeSignInStatus(&request, request_metadata);
+  SetDefaultSearchEngine(&request, request_metadata);
 
   if (!doc_view_counts.empty()) {
     WriteDocIdsTable(doc_view_counts, *request.mutable_feed_request()
diff --git a/components/feed/core/v2/proto_util_unittest.cc b/components/feed/core/v2/proto_util_unittest.cc
index 8d94cde81..db16e03c 100644
--- a/components/feed/core/v2/proto_util_unittest.cc
+++ b/components/feed/core/v2/proto_util_unittest.cc
@@ -420,5 +420,25 @@
       ToTextProto(request.client_user_profiles().view_demotion_profile()));
 }
 
+TEST(ProtoUtilTest, DefaultSearchEngineSetOnRequest) {
+  RequestMetadata request_metadata;
+  request_metadata.default_search_engine =
+      feedwire::DefaultSearchEngine::ENGINE_GOOGLE;
+
+  feedwire::Request request = CreateFeedQueryRefreshRequest(
+      StreamType(StreamKind::kForYou), feedwire::FeedQuery::MANUAL_REFRESH,
+      request_metadata,
+      /*consistency_token=*/std::string(), SingleWebFeedEntryPoint::kOther,
+      /*doc_view_counts=*/{});
+
+  feedwire::DefaultSearchEngine::SearchEngine search_engine =
+      request.feed_request()
+          .feed_query()
+          .chrome_fulfillment_info()
+          .default_search_engine()
+          .search_engine();
+  ASSERT_EQ(search_engine, request_metadata.default_search_engine);
+}
+
 }  // namespace
 }  // namespace feed
diff --git a/components/feed/core/v2/public/feed_service.cc b/components/feed/core/v2/public/feed_service.cc
index 62d439e..425833b4 100644
--- a/components/feed/core/v2/public/feed_service.cc
+++ b/components/feed/core/v2/public/feed_service.cc
@@ -247,7 +247,8 @@
     scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
     scoped_refptr<base::SequencedTaskRunner> background_task_runner,
     const std::string& api_key,
-    const ChromeInfo& chrome_info)
+    const ChromeInfo& chrome_info,
+    TemplateURLService* template_url_service)
     : delegate_(std::move(delegate)),
       refresh_task_scheduler_(std::move(refresh_task_scheduler)) {
   stream_delegate_ = std::make_unique<StreamDelegateImpl>(
@@ -267,7 +268,7 @@
       refresh_task_scheduler_.get(), metrics_reporter_.get(),
       stream_delegate_.get(), profile_prefs, feed_network_.get(),
       image_fetcher_.get(), store_.get(), persistent_key_value_store_.get(),
-      chrome_info);
+      template_url_service, chrome_info);
   api_ = stream_.get();
 
   history_observer_ = std::make_unique<HistoryObserverImpl>(
diff --git a/components/feed/core/v2/public/feed_service.h b/components/feed/core/v2/public/feed_service.h
index 5adc649..9f2bfcc 100644
--- a/components/feed/core/v2/public/feed_service.h
+++ b/components/feed/core/v2/public/feed_service.h
@@ -16,6 +16,7 @@
 #include "components/feed/core/v2/public/types.h"
 #include "components/keyed_service/core/keyed_service.h"
 #include "components/leveldb_proto/public/proto_database.h"
+#include "components/search_engines/template_url_service.h"
 #include "components/signin/public/base/consent_level.h"
 #include "components/web_resource/eula_accepted_notifier.h"
 
@@ -103,7 +104,8 @@
       scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
       scoped_refptr<base::SequencedTaskRunner> background_task_runner,
       const std::string& api_key,
-      const ChromeInfo& chrome_info);
+      const ChromeInfo& chrome_info,
+      TemplateURLService* template_url_service);
   static std::unique_ptr<FeedService> CreateForTesting(FeedApi* api);
   ~FeedService() override;
   FeedService(const FeedService&) = delete;
diff --git a/components/feed/core/v2/types.h b/components/feed/core/v2/types.h
index fcf1dbe..06ad88f 100644
--- a/components/feed/core/v2/types.h
+++ b/components/feed/core/v2/types.h
@@ -15,6 +15,7 @@
 #include "base/types/id_type.h"
 #include "base/values.h"
 #include "components/feed/core/proto/v2/store.pb.h"
+// #include "components/feed/core/proto/v2/wire/chrome_fulfillment_info.pb.h"
 #include "components/feed/core/proto/v2/wire/client_info.pb.h"
 #include "components/feed/core/proto/v2/wire/info_card.pb.h"
 #include "components/feed/core/proto/v2/wire/reliability_logging_enums.pb.h"
@@ -65,6 +66,8 @@
   std::vector<feedwire::InfoCardTrackingState> info_card_tracking_states;
   feedwire::ChromeSignInStatus::SignInStatus sign_in_status =
       feedwire::ChromeSignInStatus::SIGNED_IN_STATUS_UNSPECIFIED;
+  feedwire::DefaultSearchEngine::SearchEngine default_search_engine =
+      feedwire::DefaultSearchEngine::ENGINE_UNSPECIFIED;
 };
 
 // Data internal to MetricsReporter which is persisted to Prefs.
diff --git a/components/flags_ui/resources/BUILD.gn b/components/flags_ui/resources/BUILD.gn
index 4a74599..ae62d1c 100644
--- a/components/flags_ui/resources/BUILD.gn
+++ b/components/flags_ui/resources/BUILD.gn
@@ -12,9 +12,6 @@
     "flags.html",
   ]
 
-  web_component_files = [ "experiment.ts" ]
-  html_to_wrapper_template = "native"
-
   non_web_component_files = [ "flags.ts" ]
 
   ts_definitions = [ "//tools/typescript/definitions/chrome_send.d.ts" ]
diff --git a/components/flags_ui/resources/experiment.html b/components/flags_ui/resources/experiment.html
deleted file mode 100644
index bc5997fe..0000000
--- a/components/flags_ui/resources/experiment.html
+++ /dev/null
@@ -1,194 +0,0 @@
-<style>
-  .experiment {
-    color: var(--secondary-color);
-    line-height: 1.45;
-    width: 100%;
-  }
-
-  .experiment .flex-container {
-    display: flex;
-    gap: 8px;
-    padding: 0.8em 4px 16px 0;
-  }
-
-  .experiment .flex-container .flex:first-child {
-    max-width: 540px;
-  }
-
-  .experiment p {
-    margin: .2em 0;
-  }
-
-  .experiment-name {
-    color: var(--primary-color);
-    display: inline-block;
-    font-size: .8125rem;
-    font-weight: 500;
-    line-height: 1.5;
-    margin: 0;
-    padding: 0;
-  }
-
-  .experiment-switched .experiment-name::before {
-    --end-margin: 4px;
-    --width: 12px;
-    color: var(--interactive-color);
-    content: '•';
-    display: inline-block;
-    font-size: 40px;
-    line-height: 0;
-    margin-inline-end: var(--end-margin);
-    margin-inline-start: calc(-1 * var(--end-margin) - var(--width));
-    vertical-align: middle;
-    width: var(--width);
-  }
-
-
-  .match,
-  :host(.referenced) .experiment-name {
-    /* This UI is intentionally the same in light and dark mode. */
-    background: yellow;
-    color: var(--google-grey-900);
-  }
-
-  .flex {
-    align-self: center;
-    flex: 1 1 auto;
-  }
-
-  .experiment-actions {
-    align-self: center;
-    flex: 0 0 auto;
-    padding-inline-start: 5px;
-    text-align: right; /* csschecker-disable-line left-right */
-    width: 150px;
-  }
-
-  .experiment-origin-list-value {
-    resize: none;
-  }
-
-  select {
-    background: white;
-    border: 1px solid var(--link-color);
-    color: var(--link-color);
-    font-size: .8125rem;
-    height: 1.625rem;
-    letter-spacing: .01em;
-    max-width: 150px;
-    text-align-last: center;
-    width: 100%;
-  }
-
-  @media (prefers-color-scheme: dark) {
-    select {
-      background: var(--input-background);
-      border: none;
-      color: var(--primary-color);
-    }
-
-    option {
-      background: var(--toolbar-color);
-    }
-  }
-
-  textarea {
-    background: var(--input-background);
-    border-radius: 3px;
-    box-sizing: border-box;
-    color: inherit;
-    font-size: .8125rem;
-    margin: 0;
-    min-height: 3em;
-    padding: 8px;
-    width: 100%;
-  }
-
-  @media (prefers-color-scheme: dark) {
-    textarea {
-      border: 1px solid var(--secondary-color);
-    }
-  }
-
-  .experiment-switched select {
-    background: var(--link-color);
-    color: white;
-  }
-
-  @media (prefers-color-scheme: dark) {
-    .experiment-switched select {
-      color: var(--google-grey-900);
-    }
-
-    .experiment-switched option {
-      background: inherit;
-      color: inherit;
-    }
-  }
-
-  .experiment-no-match {
-    display: none;
-    position: absolute;
-  }
-
-  .permalink {
-    color: var(--secondary-color);
-  }
-
-  .hidden {
-    display: none;
-  }
-
-  @media (max-width: 480px) {
-    .experiment {
-      border-bottom: 1px solid var(--separator-color);
-    }
-
-    .experiment-name {
-      cursor: pointer;
-    }
-
-    .experiment .flex-container {
-      flex-flow: column;
-    }
-
-    .experiment .flex {
-      width: 100%;
-    }
-
-    .experiment .experiment-actions {
-      max-width: 100%;
-      padding-top: 12px;
-      text-align: left; /* csschecker-disable-line left-right */
-      width: 100%;
-    }
-
-    /* Hide the overflow description text */
-    .experiment p {
-      overflow: hidden;
-      text-overflow: ellipsis;
-      white-space: nowrap;
-      width: 100%;
-    }
-  }
-
-  @media (max-width: 732px) {
-    .experiment-switched .experiment-name::before {
-      margin-inline-start: 0;
-    }
-  }
-</style>
-
-<div class="experiment">
-  <div class="experiment-default flex-container">
-    <div class="flex">
-      <h2 class="experiment-name"></h2>
-      <p part="description">
-        <span class="description"></span> – <span class="platforms"></span>
-      </p>
-      <div class="textarea-container"></div>
-      <a class="permalink" tabindex="7"></a>
-    </div>
-    <div class="flex experiment-actions"></div>
-  </div>
-</div>
diff --git a/components/flags_ui/resources/experiment.ts b/components/flags_ui/resources/experiment.ts
deleted file mode 100644
index b85f6ff5..0000000
--- a/components/flags_ui/resources/experiment.ts
+++ /dev/null
@@ -1,117 +0,0 @@
-// Copyright 2023 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import {assert} from 'chrome://resources/js/assert_ts.js';
-import {CustomElement} from 'chrome://resources/js/custom_element.js';
-import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
-
-import {getTemplate} from './experiment.html.js';
-import {Feature} from './flags.js';
-
-export class FlagsExperimentElement extends CustomElement {
-  static override get template() {
-    return getTemplate();
-  }
-
-  set data(feature: Feature) {
-    const container = this.getRequiredElement('.experiment');
-    container.id = feature.internal_name;
-
-    const experimentDefault = this.getRequiredElement('.experiment-default');
-    experimentDefault.classList.toggle(
-        'experiment-default', feature.is_default);
-    experimentDefault.classList.toggle(
-        'experiment-switched', !feature.is_default);
-
-    const experimentName = this.getRequiredElement('.experiment-name');
-    experimentName.id = `${feature.internal_name}_name`;
-    experimentName.title =
-        feature.is_default ? '' : loadTimeData.getString('experiment-enabled');
-    experimentName.textContent = feature.name;
-
-    const description = this.getRequiredElement('.description');
-    description.textContent = feature.description;
-    const platforms = this.getRequiredElement('.platforms');
-    platforms.textContent = feature.supported_platforms.join(', ');
-
-    if (feature.origin_list_value !== undefined) {
-      const textarea = document.createElement('textarea');
-      textarea.dataset['internalName'] = feature.internal_name;
-      textarea.classList.add('experiment-origin-list-value');
-      textarea.value = feature.origin_list_value;
-      textarea.setAttribute('aria-labelledby', `${feature.internal_name}_name`);
-      this.getRequiredElement('.textarea-container').appendChild(textarea);
-    }
-
-    const permalink = this.getRequiredElement<HTMLAnchorElement>('.permalink');
-    permalink.href = `#${feature.internal_name}`;
-    permalink.textContent = `#${feature.internal_name}`;
-
-    if (this.hasAttribute('unsupported')) {
-      this.getRequiredElement('.experiment-actions').textContent =
-          loadTimeData.getString('not-available-platform');
-      return;
-    }
-
-    if (feature.options && feature.options.length > 0) {
-      const experimentSelect = document.createElement('select');
-      experimentSelect.dataset['internalName'] = feature.internal_name;
-      experimentSelect.classList.add('experiment-select');
-      experimentSelect.disabled = feature.enabled === false;
-      experimentSelect.setAttribute(
-          'aria-labelledby', `${feature.internal_name}_name`);
-
-      experimentSelect.innerHTML = window.trustedTypes!.emptyHTML;
-      for (let i = 0; i < feature.options.length; i++) {
-        const option = feature.options[i]!;
-        const optionEl = document.createElement('option');
-        optionEl.selected = option.selected;
-        optionEl.textContent = option.description;
-        experimentSelect.appendChild(optionEl);
-      }
-
-      this.getRequiredElement('.experiment-actions')
-          .appendChild(experimentSelect);
-      return;
-    }
-
-    assert(feature.options === undefined || feature.options.length === 0);
-    const experimentEnableDisable = document.createElement('select');
-    experimentEnableDisable.dataset['internalName'] = feature.internal_name;
-    experimentEnableDisable.classList.add('experiment-enable-disable');
-    experimentEnableDisable.setAttribute(
-        'aria-labelledby', `${feature.internal_name}_name`);
-
-    const disabledOptionEl = document.createElement('option');
-    disabledOptionEl.value = 'disabled';
-    disabledOptionEl.selected = !feature.enabled;
-    disabledOptionEl.textContent = loadTimeData.getString('disabled');
-    disabledOptionEl.dataset['default'] = feature.is_default ?
-        (!feature.enabled ? '1' : '0') :
-        !feature.enabled ? '0' :
-                           '1';
-    experimentEnableDisable.appendChild(disabledOptionEl);
-
-    const enabledOptionEl = document.createElement('option');
-    enabledOptionEl.value = 'enabled';
-    enabledOptionEl.selected = feature.enabled;
-    enabledOptionEl.textContent = loadTimeData.getString('enabled');
-    enabledOptionEl.dataset['default'] = feature.is_default ?
-        (feature.enabled ? '1' : '0') :
-        feature.enabled ? '0' :
-                          '1';
-    experimentEnableDisable.appendChild(enabledOptionEl);
-
-    this.getRequiredElement('.experiment-actions')
-        .appendChild(experimentEnableDisable);
-  }
-}
-
-declare global {
-  interface HTMLElementTagNameMap {
-    'flags-experiment': FlagsExperimentElement;
-  }
-}
-
-customElements.define('flags-experiment', FlagsExperimentElement);
diff --git a/components/flags_ui/resources/flags.css b/components/flags_ui/resources/flags.css
index e2879f3..0010ecba 100644
--- a/components/flags_ui/resources/flags.css
+++ b/components/flags_ui/resources/flags.css
@@ -283,6 +283,135 @@
   }
 }
 
+.experiment {
+  color: var(--secondary-color);
+  line-height: 1.45;
+  width: 100%;
+}
+
+.experiment .flex-container {
+  padding: 0.8em 4px 16px 0;
+}
+
+.experiment .flex-container .flex:first-child {
+  box-sizing: border-box;
+  max-width: 540px;
+  padding-inline-end: 8px;
+}
+
+.experiment p {
+  margin: .2em 0;
+}
+
+.experiment-name {
+  color: var(--primary-color);
+  display: inline-block;
+  font-size: .8125rem;
+  font-weight: 500;
+  line-height: 1.5;
+  margin: 0;
+  padding: 0;
+}
+
+.experiment-switched .experiment-name::before {
+  --end-margin: 4px;
+  --diameter: 10px;
+  background-color: var(--interactive-color);
+  border-radius: 50%;
+  content: '';
+  display: inline-block;
+  height: var(--diameter);
+  margin-inline-end: var(--end-margin);
+  margin-inline-start: calc(-1 * var(--end-margin) - var(--diameter));
+  width: var(--diameter);
+}
+
+
+.match,
+.referenced h2 {
+  /* This UI is intentionally the same in light and dark mode. */
+  background: yellow;
+  color: var(--google-grey-900);
+}
+
+.experiment-actions {
+  flex: 0 0 auto;
+  padding-inline-start: 5px;
+  text-align: right; /* csschecker-disable-line left-right */
+  width: 150px;
+}
+
+.experiment-origin-list-value {
+  resize: none;
+}
+
+select {
+  background: white;
+  border: 1px solid var(--link-color);
+  color: var(--link-color);
+  font-size: .8125rem;
+  height: 1.625rem;
+  letter-spacing: .01em;
+  max-width: 150px;
+  text-align-last: center;
+  width: 100%;
+}
+
+@media (prefers-color-scheme: dark) {
+  select {
+    background: var(--input-background);
+    border: none;
+    color: var(--primary-color);
+  }
+
+  option {
+    background: var(--toolbar-color);
+  }
+}
+
+textarea {
+  background: var(--input-background);
+  border-radius: 3px;
+  box-sizing: border-box;
+  color: inherit;
+  font-size: .8125rem;
+  margin: 0;
+  min-height: 3em;
+  padding: 8px;
+  width: 100%;
+}
+
+@media (prefers-color-scheme: dark) {
+  textarea {
+    border: 1px solid var(--secondary-color);
+  }
+}
+
+.experiment-switched select {
+  background: var(--link-color);
+  color: white;
+}
+
+@media (prefers-color-scheme: dark) {
+  .experiment-switched select {
+    color: var(--google-grey-900);
+  }
+
+  .experiment-switched option {
+    background: inherit;
+    color: inherit;
+  }
+}
+
+.experiment-no-match {
+  display: none;
+  position: absolute;
+}
+
+.permalink {
+  color: var(--secondary-color);
+}
+
 .tabs {
   display: flex;
   width: 100%;
@@ -430,6 +559,29 @@
 }
 
 @media (max-width: 480px) {
+  .experiment {
+    border-bottom: 1px solid var(--separator-color);
+  }
+
+  .experiment-name {
+    cursor: pointer;
+  }
+
+  .experiment .flex-container {
+    flex-flow: column;
+  }
+
+  .experiment .flex {
+    width: 100%;
+  }
+
+  .experiment .experiment-actions {
+    max-width: 100%;
+    padding-top: 12px;
+    text-align: left; /* csschecker-disable-line left-right */
+    width: 100%;
+  }
+
   #flagsTemplate > .flex-container:first-child:not('.version') {
     flex-direction: column;
     text-align: left; /* csschecker-disable-line left-right */
@@ -452,13 +604,26 @@
     padding: 4px;
   }
 
-  .searching flags-experiment::part(description) {
+  /* Hide the overflow description text */
+  .experiment p {
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+    width: 100%;
+  }
+
+  .searching .experiment p,
+  .experiment .expand p {
     overflow: visible;
     white-space: normal;
   }
 }
 
 @media (max-width: 732px) {
+  .experiment-switched .experiment-name::before {
+    margin-inline-start: 0;
+  }
+
   #version,
   .blurb-warning {
     display: block;
diff --git a/components/flags_ui/resources/flags.html b/components/flags_ui/resources/flags.html
index 926d10e..cdaa7c20 100644
--- a/components/flags_ui/resources/flags.html
+++ b/components/flags_ui/resources/flags.html
@@ -102,13 +102,96 @@
         <div jsselect="supportedFeatures"
             jsvalues="id:internal_name; class: is_default ? 'hidden' : 'experiment'"
             jsdisplay="!is_default">
-          <flags-experiment jsvalues=".data:$this"></flags-experiment>
+          <div class="experiment-default"
+              jsvalues="class: is_default ? 'experiment-default flex-container'
+                  : 'experiment-switched flex-container'">
+            <div class="flex">
+              <h2 class="experiment-name" jscontent="name"
+                  jsvalues="title: is_default ? '' : '$i18n{experiment-enabled}';
+                      id:internal_name + '_name'"></h2>
+              <p>
+                <span jsvalues=".textContent:description"></span> –
+                <span class="platforms" jscontent="supported_platforms.join(', ')"></span>
+              </p>
+              <div jsdisplay="origin_list_value!==null">
+                <textarea class="experiment-origin-list-value"
+                    jsvalues=".internal_name:internal_name; .value:origin_list_value;
+                        aria-labelledby:internal_name + '_name'"
+                    tabindex="7"></textarea>
+              </div>
+              <a class="permalink" jsvalues="href: '#' + internal_name"
+                  jscontent="'#' + internal_name" tabindex="7"></a>
+            </div>
+            <div class="flex experiment-actions">
+              <div jsdisplay="options && options.length > 0">
+                <select class="experiment-select" tabindex="7"
+                    jsvalues=".internal_name:internal_name;.disabled:!enabled;
+                        aria-labelledby:internal_name + '_name'">
+                  <option jsvalues=".selected:selected;"
+                      jsselect="options"
+                      jscontent="description">
+                  </option>
+                </select>
+              </div>
+              <select class="experiment-enable-disable" tabindex="7"
+                  jsdisplay="enabled !== undefined"
+                  jsvalues=".internal_name:internal_name;
+                      aria-labelledby:internal_name + '_name'">
+                <option jsvalues=".selected:!enabled; data-default: enabled ? 1 : 0"
+                    value="disabled">$i18n{disabled}</option>
+                <option jsvalues=".selected:enabled; data-default: !enabled ? 1 : 0"
+                    value="enabled">$i18n{enabled}</option>
+              </select>
+            </div>
+          </div>
         </div>
         <!-- Experiments with default settings. -->
         <div class="experiment" jsselect="supportedFeatures"
             jsvalues="id:internal_name; class: is_default ? 'experiment' : 'hidden'"
             jsdisplay="is_default">
-          <flags-experiment jsvalues=".data:$this"></flags-experiment>
+          <div class="experiment-default"
+              jsvalues="class: is_default ? 'experiment-default flex-container'
+                  : 'experiment-switched flex-container'">
+            <div class="flex">
+              <h2 class="experiment-name" jscontent="name"
+                  jsvalues="title: is_default ? '' : '$i18n{experiment-enabled}';
+                      id:internal_name + '_name'"></h2>
+              <p>
+                <span jsvalues=".textContent:description"></span> –
+                <span class="platforms" jscontent="supported_platforms.join(', ')"></span>
+              </p>
+              <div jsdisplay="origin_list_value!==null">
+                <textarea class="experiment-origin-list-value"
+                    jsvalues=".internal_name:internal_name; .value:origin_list_value;
+                        aria-labelledby:internal_name + '_name'"
+                    tabindex="7"></textarea>
+              </div>
+              <a class="permalink" jsvalues="href: '#' + internal_name"
+                  jscontent="'#' + internal_name" tabindex="7"></a>
+            </div>
+            <div class="flex experiment-actions">
+              <div jsdisplay="options && options.length > 0">
+                <select class="experiment-select" tabindex="7"
+                    jsvalues=".internal_name:internal_name;.disabled:!enabled;
+                        aria-labelledby:internal_name + '_name'">
+                  <option jsvalues=".selected:selected;"
+                      jsselect="options"
+                      jscontent="description">
+                  </option>
+                </select>
+              </div>
+              <!-- Represent enabled / disabled options in a drop down -->
+              <select class="experiment-enable-disable" tabindex="7"
+                  jsdisplay="enabled !== undefined"
+                  jsvalues=".internal_name:internal_name;
+                      aria-labelledby:internal_name + '_name'">
+                <option jsvalues=".selected:!enabled; data-default:!enabled ? 1 : 0"
+                    value="disabled">$i18n{disabled}</option>
+                <option jsvalues=".selected:enabled; data-default: enabled ? 1 : 0"
+                    value="enabled">$i18n{enabled}</option>
+              </select>
+            </div>
+          </div>
         </div>
         <div class="no-match hidden" role="alert">$i18n{no-results}</div>
       </div>
@@ -118,7 +201,22 @@
         <div class="experiment"
             jsselect="unsupportedFeatures"
             jsvalues="id:internal_name">
-          <flags-experiment jsvalues=".data:$this" unsupported></flags-experiment>
+          <div class="experiment-default flex-container"
+              jsvalues="class: is_default ? 'experiment-default flex-container'
+                  : 'experiment-switched flex-container'">
+            <div class="flex">
+              <h2 class="experiment-name"
+                  jscontent="name"></h2>
+              <p>
+                <span jsvalues=".textContent:description"></span>
+                <span class="platforms" jscontent="supported_platforms.join(', ')"></span>
+              </p>
+              <a class="permalink"
+                  jsvalues="href: '#' + internal_name"
+                  jscontent="'#' + internal_name" tabindex="9"></a>
+            </div>
+            <div class="flex experiment-actions">$i18n{not-available-platform}</div>
+          </div>
         </div>
         <div class="no-match hidden" role="alert">
           $i18n{no-results}
diff --git a/components/flags_ui/resources/flags.ts b/components/flags_ui/resources/flags.ts
index 6ec9f73..5a02ecf4 100644
--- a/components/flags_ui/resources/flags.ts
+++ b/components/flags_ui/resources/flags.ts
@@ -8,7 +8,6 @@
 
 import 'chrome://resources/js/jstemplate_compiled.js';
 import './strings.m.js';
-import './experiment.js';
 
 import {assert} from 'chrome://resources/js/assert_ts.js';
 import {sendWithPromise} from 'chrome://resources/js/cr.js';
@@ -16,9 +15,7 @@
 import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
 import {isIOS} from 'chrome://resources/js/platform.js';
 import {PromiseResolver} from 'chrome://resources/js/promise_resolver.js';
-import {$, getDeepActiveElement, getRequiredElement} from 'chrome://resources/js/util_ts.js';
-
-import {FlagsExperimentElement} from './experiment.js';
+import {$, getRequiredElement} from 'chrome://resources/js/util_ts.js';
 
 let lastChanged: HTMLElement|null = null;
 let lastFocused: HTMLElement|null = null;
@@ -33,6 +30,12 @@
     window,
     {experimentalFeaturesReadyForTest: experimentalFeaturesResolver.promise});
 
+// Declare properties that are augmented on some HTMLElement instances by
+// jstemplate.
+interface WithExtras {
+  internal_name: string;
+}
+
 interface Tab {
   tabEl: HTMLElement;
   panelEl: HTMLElement;
@@ -106,31 +109,38 @@
   showRestartToast(experimentalFeaturesData.needsRestart);
 
   // Add handlers to dynamically created HTML elements.
-  const experiments = document.body.querySelectorAll('flags-experiment');
-  for (const experiment of experiments) {
-    const select =
-        experiment.shadowRoot!.querySelector<HTMLSelectElement>('select');
-    if (select) {
-      select.onchange = function() {
-        if (select.classList.contains('experiment-select')) {
-          handleSelectExperimentalFeatureChoice(select, select.selectedIndex);
-        } else {
-          handleEnableExperimentalFeature(
-              select, select.options[select.selectedIndex]!.value == 'enabled');
-        }
-        lastChanged = select;
-        return false;
-      };
-      registerFocusEvents(select);
-    }
-    const textarea =
-        experiment.shadowRoot!.querySelector<HTMLTextAreaElement>('textarea');
-    if (textarea) {
-      textarea.onchange = function() {
-        handleSetOriginListFlag(textarea, textarea.value);
-        return false;
-      };
-    }
+  let selectElements =
+      document.body.querySelectorAll<HTMLSelectElement&WithExtras>(
+          '.experiment-select');
+  for (const element of selectElements) {
+    element.onchange = function() {
+      handleSelectExperimentalFeatureChoice(element, element.selectedIndex);
+      lastChanged = element;
+      return false;
+    };
+    registerFocusEvents(element);
+  }
+
+  selectElements = document.body.querySelectorAll<HTMLSelectElement&WithExtras>(
+      '.experiment-enable-disable');
+  for (const element of selectElements) {
+    element.onchange = function() {
+      handleEnableExperimentalFeature(
+          element, element.options[element.selectedIndex]!.value == 'enabled');
+      lastChanged = element;
+      return false;
+    };
+    registerFocusEvents(element);
+  }
+
+  const textAreaElements =
+      document.body.querySelectorAll<HTMLTextAreaElement&WithExtras>(
+          '.experiment-origin-list-value');
+  for (const element of textAreaElements) {
+    element.onchange = function() {
+      handleSetOriginListFlag(element, element.value);
+      return false;
+    };
   }
 
   assert(restartButton || isIOS);
@@ -203,18 +213,14 @@
 function highlightReferencedFlag() {
   if (window.location.hash) {
     const el = document.body.querySelector(window.location.hash);
-    if (!el) {
-      return;
-    }
-    const experiment = el.querySelector('flags-experiment');
-    if (experiment && !experiment.classList.contains('referenced')) {
+    if (el && !el.classList.contains('referenced')) {
       // Unhighlight whatever's highlighted.
       if (document.body.querySelector('.referenced')) {
         document.body.querySelector('.referenced')!.classList.remove(
             'referenced');
       }
       // Highlight the referenced element.
-      experiment.classList.add('referenced');
+      el.classList.add('referenced');
 
       // <if expr="not is_ios">
       // Switch to unavailable tab if the flag is in this section.
@@ -284,17 +290,16 @@
  * `enabled` and `is_default` are only set if the feature is single valued.
  * `enabled` is true if the feature is currently enabled.
  * `is_default` is true if the feature is in its default state.
- * `options` is only set if the entry has multiple values.
+ * `choices` is only set if the entry has multiple values.
  */
-export interface Feature {
+interface Feature {
   internal_name: string;
   name: string;
   description: string;
   enabled: boolean;
   is_default: boolean;
   supported_platforms: string[];
-  origin_list_value?: string;
-  options?: Array<{
+  choices?: Array<{
     internal_name: string,
     description: string,
     selected: boolean,
@@ -351,16 +356,11 @@
  * @param node The select node for the experiment being changed.
  * @param index The selected option index.
  */
-function experimentChangesUiUpdates(node: HTMLSelectElement, index: number) {
+function experimentChangesUiUpdates(
+    node: HTMLSelectElement&WithExtras, index: number) {
   const selected = node.options[index]!;
-  const internalName = node.dataset['internalName'];
-  if (!internalName) {
-    return;
-  }
   const experimentContainerEl =
-      getRequiredElement(internalName)
-          .firstElementChild!.shadowRoot!.querySelector(
-              '.experiment-default, .experiment-switched')!;
+      getRequiredElement(node.internal_name).firstElementChild!;
   const isDefault =
       ('default' in selected.dataset && selected.dataset['default'] === '1') ||
       (!('default' in selected.dataset) && index === 0);
@@ -376,26 +376,25 @@
  * @param enable Whether to enable or disable the experiment.
  */
 function handleEnableExperimentalFeature(
-    node: HTMLSelectElement, enable: boolean) {
+    node: HTMLSelectElement&WithExtras, enable: boolean) {
   /* This function is an onchange handler, which can be invoked during page
    * restore - see https://crbug.com/1038638. */
-  const internalName = node.dataset['internalName'];
-  if (!internalName) {
+  if (!node.internal_name) {
     return;
   }
   chrome.send(
-      'enableExperimentalFeature', [String(internalName), String(enable)]);
+      'enableExperimentalFeature',
+      [String(node.internal_name), String(enable)]);
   experimentChangesUiUpdates(node, enable ? 1 : 0);
 }
 
-function handleSetOriginListFlag(node: HTMLElement, value: string) {
+function handleSetOriginListFlag(node: HTMLElement&WithExtras, value: string) {
   /* This function is an onchange handler, which can be invoked during page
    * restore - see https://crbug.com/1038638. */
-  const internalName = node.dataset['internalName'];
-  if (!internalName) {
+  if (!node.internal_name) {
     return;
   }
-  chrome.send('setOriginListFlag', [String(internalName), value]);
+  chrome.send('setOriginListFlag', [String(node.internal_name), value]);
   showRestartToast(true);
 }
 
@@ -406,30 +405,29 @@
  * @param index The index of the option that was selected.
  */
 function handleSelectExperimentalFeatureChoice(
-    node: HTMLSelectElement, index: number) {
+    node: HTMLSelectElement&WithExtras, index: number) {
   /* This function is an onchange handler, which can be invoked during page
    * restore - see https://crbug.com/1038638. */
-  const internalName = node.dataset['internalName'];
-  if (!internalName) {
+  if (!node.internal_name) {
     return;
   }
   chrome.send(
       'enableExperimentalFeature',
-      [String(internalName) + '@' + index, 'true']);
+      [String(node.internal_name) + '@' + index, 'true']);
   experimentChangesUiUpdates(node, index);
 }
 
 /** Type for storing the elements which are searched on. */
 interface SearchContent {
-  link: HTMLElement[];
-  title: HTMLElement[];
-  description: HTMLElement[];
+  link: NodeListOf<HTMLElement>|null;
+  title: NodeListOf<HTMLElement>|null;
+  description: NodeListOf<HTMLElement>|null;
 }
 
 const emptySearchContent: SearchContent = Object.freeze({
-  link: [],
-  title: [],
-  description: [],
+  link: null,
+  title: null,
+  description: null,
 });
 
 // Delay in ms following a keypress, before a search is made.
@@ -440,10 +438,8 @@
  */
 class FlagSearch {
   private experiments_: SearchContent = Object.assign({}, emptySearchContent);
-  // <if expr="not is_ios">
   private unavailableExperiments_: SearchContent =
       Object.assign({}, emptySearchContent);
-  // </if>
   private searchIntervalId_: number|null = null;
 
   private searchBox_: HTMLInputElement;
@@ -461,11 +457,19 @@
    * collates the text elements used for string matching.
    */
   init() {
-    this.experiments_ = this.getSearchableElements('tab-content-available');
-    // <if expr="not is_ios">
-    this.unavailableExperiments_ =
-        this.getSearchableElements('tab-content-unavailable');
-    // </if>
+    this.experiments_.link =
+        document.body.querySelectorAll('#tab-content-available .permalink');
+    this.experiments_.title = document.body.querySelectorAll(
+        '#tab-content-available .experiment-name');
+    this.experiments_.description =
+        document.body.querySelectorAll('#tab-content-available p');
+
+    this.unavailableExperiments_.link =
+        document.body.querySelectorAll('#tab-content-unavailable .permalink');
+    this.unavailableExperiments_.title = document.body.querySelectorAll(
+        '#tab-content-unavailable .experiment-name');
+    this.unavailableExperiments_.description =
+        document.body.querySelectorAll('#tab-content-unavailable p');
 
     if (!this.initialized) {
       this.searchBox_.addEventListener('input', this.debounceSearch.bind(this));
@@ -474,8 +478,7 @@
           'click', this.clearSearch.bind(this));
 
       window.addEventListener('keyup', e => {
-        // Check for an active textarea inside a <flags-experiment>.
-        if (getDeepActiveElement()!.nodeName === 'TEXTAREA') {
+        if (document.activeElement!.nodeName === 'TEXTAREA') {
           return;
         }
         switch (e.key) {
@@ -493,23 +496,6 @@
     }
   }
 
-  getSearchableElements(tabId: string): SearchContent {
-    const content = Object.assign({}, emptySearchContent);
-    const experiments = document.body.querySelectorAll<FlagsExperimentElement>(
-        `#${tabId} flags-experiment`);
-    for (const experiment of experiments) {
-      const link = experiment.getRequiredElement('.permalink');
-      const title = experiment.getRequiredElement('.experiment-name');
-      const description = experiment.getRequiredElement('p');
-
-      content.link.push(link);
-      content.title.push(title);
-      content.description.push(description);
-    }
-
-    return content;
-  }
-
   /**
    * Clears a search showing all experiments.
    */
@@ -586,6 +572,9 @@
   highlightAllMatches(searchContent: SearchContent, searchTerm: string):
       number {
     let matches = 0;
+    assert(searchContent.description);
+    assert(searchContent.link);
+    assert(searchContent.title);
     for (let i = 0, j = searchContent.link.length; i < j; i++) {
       if (this.highlightMatchInElement(searchTerm, searchContent.title[i]!)) {
         this.resetHighlights(
diff --git a/components/lens/lens_metrics.h b/components/lens/lens_metrics.h
index 017dec7..70a9d9f 100644
--- a/components/lens/lens_metrics.h
+++ b/components/lens/lens_metrics.h
@@ -39,7 +39,8 @@
   WIDGET = 2,
   TASKS_SURFACE = 3,
   KEYBOARD = 4,
-  kMaxValue = KEYBOARD
+  SPOTLIGHT = 5,
+  kMaxValue = SPOTLIGHT
 };
 
 // Needs to be kept in sync with CameraResult enum in
diff --git a/components/omnibox/browser/omnibox.mojom b/components/omnibox/browser/omnibox.mojom
index 578e886..c47d9b0 100644
--- a/components/omnibox/browser/omnibox.mojom
+++ b/components/omnibox/browser/omnibox.mojom
@@ -101,6 +101,8 @@
 interface PageHandler {
   // The RealboxBrowserProxy singleton calls this when it's first initialized.
   SetPage(pending_remote<Page> page);
+  // Informs the handler and model about focus state changes.
+  OnFocusChanged(bool focused);
   // Queries autocomplete matches from the browser.
   QueryAutocomplete(mojo_base.mojom.String16 input,
                     bool prevent_inline_autocomplete);
@@ -115,7 +117,6 @@
   OpenAutocompleteMatch(uint8 line,
                         url.mojom.Url url,
                         bool are_matches_showing,
-                        mojo_base.mojom.TimeDelta time_elapsed_since_last_focus,
                         uint8 mouse_button,
                         bool alt_key,
                         bool ctrl_key,
diff --git a/components/password_manager/core/browser/password_manager_metrics_util.h b/components/password_manager/core/browser/password_manager_metrics_util.h
index 497b4e98..73ef43d8 100644
--- a/components/password_manager/core/browser/password_manager_metrics_util.h
+++ b/components/password_manager/core/browser/password_manager_metrics_util.h
@@ -608,7 +608,9 @@
   kPasskeyDisplayNameCopyButtonClicked = 12,
   // The delete button in a passkey view page is clicked.
   kPasskeyDeleteButtonClicked = 13,
-  kMaxValue = kPasskeyDeleteButtonClicked,
+  // The edit button in a passkey view page is clicked.
+  kPasskeyEditButtonClicked = 14,
+  kMaxValue = kPasskeyEditButtonClicked,
 };
 
 // These values are persisted to logs. Entries should not be renumbered and
diff --git a/components/performance_manager/embedder/graph_features.h b/components/performance_manager/embedder/graph_features.h
index 761edc56..52b500b 100644
--- a/components/performance_manager/embedder/graph_features.h
+++ b/components/performance_manager/embedder/graph_features.h
@@ -60,6 +60,7 @@
 
   constexpr GraphFeatures& EnableExecutionContextPriorityDecorator() {
     EnableExecutionContextRegistry();
+    EnableFrameVisibilityDecorator();
     flags_.execution_context_priority_decorator = true;
     return *this;
   }
@@ -138,6 +139,7 @@
     EnableSiteDataRecorder();
     EnableTabPropertiesDecorator();
     EnableV8ContextTracker();
+    EnableExecutionContextPriorityDecorator();
     return *this;
   }
 
diff --git a/components/performance_manager/execution_context_priority/ad_frame_voter.cc b/components/performance_manager/execution_context_priority/ad_frame_voter.cc
index b930487c..f145b4ab2 100644
--- a/components/performance_manager/execution_context_priority/ad_frame_voter.cc
+++ b/components/performance_manager/execution_context_priority/ad_frame_voter.cc
@@ -34,7 +34,7 @@
   voting_channel_ = std::move(voting_channel);
 }
 
-void AdFrameVoter::OnFrameNodeAdded(const FrameNode* frame_node) {
+void AdFrameVoter::OnFrameNodeInitializing(const FrameNode* frame_node) {
   if (!frame_node->IsAdFrame())
     return;
 
@@ -42,7 +42,7 @@
   voting_channel_.SubmitVote(GetExecutionContext(frame_node), vote);
 }
 
-void AdFrameVoter::OnBeforeFrameNodeRemoved(const FrameNode* frame_node) {
+void AdFrameVoter::OnFrameNodeTearingDown(const FrameNode* frame_node) {
   if (!frame_node->IsAdFrame())
     return;
 
diff --git a/components/performance_manager/execution_context_priority/ad_frame_voter.h b/components/performance_manager/execution_context_priority/ad_frame_voter.h
index ab35601..b8ff64a 100644
--- a/components/performance_manager/execution_context_priority/ad_frame_voter.h
+++ b/components/performance_manager/execution_context_priority/ad_frame_voter.h
@@ -5,15 +5,17 @@
 #ifndef COMPONENTS_PERFORMANCE_MANAGER_EXECUTION_CONTEXT_PRIORITY_AD_FRAME_VOTER_H_
 #define COMPONENTS_PERFORMANCE_MANAGER_EXECUTION_CONTEXT_PRIORITY_AD_FRAME_VOTER_H_
 
+#include "components/performance_manager/graph/initializing_frame_node_observer.h"
 #include "components/performance_manager/public/execution_context_priority/execution_context_priority.h"
-#include "components/performance_manager/public/graph/frame_node.h"
 
 namespace performance_manager {
 namespace execution_context_priority {
 
 // This voter tracks frame nodes and casts a TaskPriority::LOWEST vote for each
 // ad frame. No votes will be cast for non-ad frames.
-class AdFrameVoter : public FrameNode::ObserverDefaultImpl {
+// Uses `InitializingFrameNodeObserver` because it can affect the initial
+// priority of a frame.
+class AdFrameVoter : public InitializingFrameNodeObserver {
  public:
   static const char kAdFrameReason[];
 
@@ -26,9 +28,9 @@
   // Sets the voting channel where the votes will be cast.
   void SetVotingChannel(VotingChannel voting_channel);
 
-  // FrameNodeObserver:
-  void OnFrameNodeAdded(const FrameNode* frame_node) override;
-  void OnBeforeFrameNodeRemoved(const FrameNode* frame_node) override;
+  // InitializingFrameNodeObserver:
+  void OnFrameNodeInitializing(const FrameNode* frame_node) override;
+  void OnFrameNodeTearingDown(const FrameNode* frame_node) override;
   void OnIsAdFrameChanged(const FrameNode* frame_node) override;
 
  private:
diff --git a/components/performance_manager/execution_context_priority/ad_frame_voter_unittest.cc b/components/performance_manager/execution_context_priority/ad_frame_voter_unittest.cc
index 69ea5d8..f2defb6 100644
--- a/components/performance_manager/execution_context_priority/ad_frame_voter_unittest.cc
+++ b/components/performance_manager/execution_context_priority/ad_frame_voter_unittest.cc
@@ -40,10 +40,10 @@
 
   // GraphOwned:
   void OnPassedToGraph(Graph* graph) override {
-    graph->AddFrameNodeObserver(&ad_frame_voter_);
+    graph->AddInitializingFrameNodeObserver(&ad_frame_voter_);
   }
   void OnTakenFromGraph(Graph* graph) override {
-    graph->RemoveFrameNodeObserver(&ad_frame_voter_);
+    graph->RemoveInitializingFrameNodeObserver(&ad_frame_voter_);
   }
 
   // Exposes the DummyVoteObserver to validate expectations.
diff --git a/components/performance_manager/execution_context_priority/execution_context_priority_decorator.cc b/components/performance_manager/execution_context_priority/execution_context_priority_decorator.cc
index da7518a..4f4fd3a 100644
--- a/components/performance_manager/execution_context_priority/execution_context_priority_decorator.cc
+++ b/components/performance_manager/execution_context_priority/execution_context_priority_decorator.cc
@@ -54,9 +54,9 @@
 
 void ExecutionContextPriorityDecorator::OnPassedToGraph(Graph* graph) {
   // Subscribe voters to the graph.
-  graph->AddFrameNodeObserver(&ad_frame_voter_);
-  graph->AddFrameNodeObserver(&frame_visibility_voter_);
-  graph->AddFrameNodeObserver(&frame_audible_voter_);
+  graph->AddInitializingFrameNodeObserver(&ad_frame_voter_);
+  graph->AddInitializingFrameNodeObserver(&frame_visibility_voter_);
+  graph->AddInitializingFrameNodeObserver(&frame_audible_voter_);
   graph->AddFrameNodeObserver(&inherit_client_priority_voter_);
   graph->AddWorkerNodeObserver(&inherit_client_priority_voter_);
 }
@@ -65,9 +65,9 @@
   // Unsubscribe voters from the graph.
   graph->RemoveWorkerNodeObserver(&inherit_client_priority_voter_);
   graph->RemoveFrameNodeObserver(&inherit_client_priority_voter_);
-  graph->RemoveFrameNodeObserver(&frame_audible_voter_);
-  graph->RemoveFrameNodeObserver(&frame_visibility_voter_);
-  graph->RemoveFrameNodeObserver(&ad_frame_voter_);
+  graph->RemoveInitializingFrameNodeObserver(&frame_audible_voter_);
+  graph->RemoveInitializingFrameNodeObserver(&frame_visibility_voter_);
+  graph->RemoveInitializingFrameNodeObserver(&ad_frame_voter_);
 }
 
 }  // namespace execution_context_priority
diff --git a/components/performance_manager/execution_context_priority/frame_audible_voter.cc b/components/performance_manager/execution_context_priority/frame_audible_voter.cc
index c1ed0a54..22baf9b 100644
--- a/components/performance_manager/execution_context_priority/frame_audible_voter.cc
+++ b/components/performance_manager/execution_context_priority/frame_audible_voter.cc
@@ -41,12 +41,12 @@
   voting_channel_ = std::move(voting_channel);
 }
 
-void FrameAudibleVoter::OnFrameNodeAdded(const FrameNode* frame_node) {
+void FrameAudibleVoter::OnFrameNodeInitializing(const FrameNode* frame_node) {
   const Vote vote = GetVote(frame_node->IsAudible());
   voting_channel_.SubmitVote(GetExecutionContext(frame_node), vote);
 }
 
-void FrameAudibleVoter::OnBeforeFrameNodeRemoved(const FrameNode* frame_node) {
+void FrameAudibleVoter::OnFrameNodeTearingDown(const FrameNode* frame_node) {
   voting_channel_.InvalidateVote(GetExecutionContext(frame_node));
 }
 
diff --git a/components/performance_manager/execution_context_priority/frame_audible_voter.h b/components/performance_manager/execution_context_priority/frame_audible_voter.h
index 423c638..f2f788a 100644
--- a/components/performance_manager/execution_context_priority/frame_audible_voter.h
+++ b/components/performance_manager/execution_context_priority/frame_audible_voter.h
@@ -5,15 +5,17 @@
 #ifndef COMPONENTS_PERFORMANCE_MANAGER_EXECUTION_CONTEXT_PRIORITY_FRAME_AUDIBLE_VOTER_H_
 #define COMPONENTS_PERFORMANCE_MANAGER_EXECUTION_CONTEXT_PRIORITY_FRAME_AUDIBLE_VOTER_H_
 
+#include "components/performance_manager/graph/initializing_frame_node_observer.h"
 #include "components/performance_manager/public/execution_context_priority/execution_context_priority.h"
-#include "components/performance_manager/public/graph/frame_node.h"
 
 namespace performance_manager {
 namespace execution_context_priority {
 
 // This voter casts a TaskPriority::USER_VISIBLE vote to all audible frames, and
 // a TaskPriority::LOWEST vote to non-audible frames.
-class FrameAudibleVoter : public FrameNode::ObserverDefaultImpl {
+// Note: Uses `InitializingFrameNodeObserver` because it can affect the initial
+// priority of a frame.
+class FrameAudibleVoter : public InitializingFrameNodeObserver {
  public:
   static const char kFrameAudibleReason[];
 
@@ -26,9 +28,9 @@
   // Sets the voting channel where the votes will be cast.
   void SetVotingChannel(VotingChannel voting_channel);
 
-  // FrameNodeObserver:
-  void OnFrameNodeAdded(const FrameNode* frame_node) override;
-  void OnBeforeFrameNodeRemoved(const FrameNode* frame_node) override;
+  // InitializingFrameNodeObserver:
+  void OnFrameNodeInitializing(const FrameNode* frame_node) override;
+  void OnFrameNodeTearingDown(const FrameNode* frame_node) override;
   void OnIsAudibleChanged(const FrameNode* frame_node) override;
 
  private:
diff --git a/components/performance_manager/execution_context_priority/frame_audible_voter_unittest.cc b/components/performance_manager/execution_context_priority/frame_audible_voter_unittest.cc
index 4771943..f9501212 100644
--- a/components/performance_manager/execution_context_priority/frame_audible_voter_unittest.cc
+++ b/components/performance_manager/execution_context_priority/frame_audible_voter_unittest.cc
@@ -41,10 +41,10 @@
 
   // GraphOwned:
   void OnPassedToGraph(Graph* graph) override {
-    graph->AddFrameNodeObserver(&frame_audible_voter_);
+    graph->AddInitializingFrameNodeObserver(&frame_audible_voter_);
   }
   void OnTakenFromGraph(Graph* graph) override {
-    graph->RemoveFrameNodeObserver(&frame_audible_voter_);
+    graph->RemoveInitializingFrameNodeObserver(&frame_audible_voter_);
   }
 
   // Exposes the DummyVoteObserver to validate expectations.
diff --git a/components/performance_manager/execution_context_priority/frame_visibility_voter.cc b/components/performance_manager/execution_context_priority/frame_visibility_voter.cc
index 6691770..1abbba2 100644
--- a/components/performance_manager/execution_context_priority/frame_visibility_voter.cc
+++ b/components/performance_manager/execution_context_priority/frame_visibility_voter.cc
@@ -52,13 +52,13 @@
   voting_channel_ = std::move(voting_channel);
 }
 
-void FrameVisibilityVoter::OnFrameNodeAdded(const FrameNode* frame_node) {
+void FrameVisibilityVoter::OnFrameNodeInitializing(
+    const FrameNode* frame_node) {
   const Vote vote = GetVote(frame_node->GetVisibility());
   voting_channel_.SubmitVote(GetExecutionContext(frame_node), vote);
 }
 
-void FrameVisibilityVoter::OnBeforeFrameNodeRemoved(
-    const FrameNode* frame_node) {
+void FrameVisibilityVoter::OnFrameNodeTearingDown(const FrameNode* frame_node) {
   voting_channel_.InvalidateVote(GetExecutionContext(frame_node));
 }
 
diff --git a/components/performance_manager/execution_context_priority/frame_visibility_voter.h b/components/performance_manager/execution_context_priority/frame_visibility_voter.h
index 05b27a0..1567f35 100644
--- a/components/performance_manager/execution_context_priority/frame_visibility_voter.h
+++ b/components/performance_manager/execution_context_priority/frame_visibility_voter.h
@@ -5,8 +5,8 @@
 #ifndef COMPONENTS_PERFORMANCE_MANAGER_EXECUTION_CONTEXT_PRIORITY_FRAME_VISIBILITY_VOTER_H_
 #define COMPONENTS_PERFORMANCE_MANAGER_EXECUTION_CONTEXT_PRIORITY_FRAME_VISIBILITY_VOTER_H_
 
+#include "components/performance_manager/graph/initializing_frame_node_observer.h"
 #include "components/performance_manager/public/execution_context_priority/execution_context_priority.h"
-#include "components/performance_manager/public/graph/frame_node.h"
 
 namespace performance_manager {
 namespace execution_context_priority {
@@ -15,7 +15,9 @@
 // depends on their visibility. A visible frame will receive a
 // TaskPriority::USER_VISIBLE vote, while a non-visible frame will receive a
 // TaskPriority::LOWEST vote.
-class FrameVisibilityVoter : public FrameNode::ObserverDefaultImpl {
+// Note: Uses `InitializingFrameNodeObserver` because it can affect the initial
+// priority of a frame.
+class FrameVisibilityVoter : public InitializingFrameNodeObserver {
  public:
   static const char kFrameVisibilityReason[];
 
@@ -28,9 +30,9 @@
   // Sets the voting channel where the votes will be cast.
   void SetVotingChannel(VotingChannel voting_channel);
 
-  // FrameNodeObserver:
-  void OnFrameNodeAdded(const FrameNode* frame_node) override;
-  void OnBeforeFrameNodeRemoved(const FrameNode* frame_node) override;
+  // InitializingFrameNodeObserver:
+  void OnFrameNodeInitializing(const FrameNode* frame_node) override;
+  void OnFrameNodeTearingDown(const FrameNode* frame_node) override;
   void OnFrameVisibilityChanged(const FrameNode* frame_node,
                                 FrameNode::Visibility previous_value) override;
 
diff --git a/components/performance_manager/execution_context_priority/frame_visibility_voter_unittest.cc b/components/performance_manager/execution_context_priority/frame_visibility_voter_unittest.cc
index f9c0369b..79e9976 100644
--- a/components/performance_manager/execution_context_priority/frame_visibility_voter_unittest.cc
+++ b/components/performance_manager/execution_context_priority/frame_visibility_voter_unittest.cc
@@ -41,10 +41,10 @@
 
   // GraphOwned:
   void OnPassedToGraph(Graph* graph) override {
-    graph->AddFrameNodeObserver(&frame_visibility_voter_);
+    graph->AddInitializingFrameNodeObserver(&frame_visibility_voter_);
   }
   void OnTakenFromGraph(Graph* graph) override {
-    graph->RemoveFrameNodeObserver(&frame_visibility_voter_);
+    graph->RemoveInitializingFrameNodeObserver(&frame_visibility_voter_);
   }
 
   // Exposes the DummyVoteObserver to validate expectations.
diff --git a/components/performance_manager/execution_context_priority/inherit_client_priority_voter.cc b/components/performance_manager/execution_context_priority/inherit_client_priority_voter.cc
index 3b637f8..70daf574 100644
--- a/components/performance_manager/execution_context_priority/inherit_client_priority_voter.cc
+++ b/components/performance_manager/execution_context_priority/inherit_client_priority_voter.cc
@@ -71,13 +71,7 @@
   // priority.
 
   auto it = voting_channels_.find(GetExecutionContext(frame_node));
-
-  // Unknown |frame_node|. Just ignore it until we get notified of its existence
-  // via OnFrameNodeAdded(). This can happen because another voter received the
-  // OnFrameNodeAdded() call first and thus was able to change its priority very
-  // early.
-  if (it == voting_channels_.end())
-    return;
+  DCHECK(it != voting_channels_.end());
 
   auto& voting_channel = it->second;
 
diff --git a/components/performance_manager/graph/frame_node_impl.cc b/components/performance_manager/graph/frame_node_impl.cc
index 11f30628..cc091458 100644
--- a/components/performance_manager/graph/frame_node_impl.cc
+++ b/components/performance_manager/graph/frame_node_impl.cc
@@ -383,6 +383,14 @@
 void FrameNodeImpl::SetPriorityAndReason(
     const PriorityAndReason& priority_and_reason) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  // This is also called during initialization to set the initial value. In this
+  // case, do not notify the observers as they aren't even aware of this frame
+  // node anyways.
+  if (CanSetProperty()) {
+    priority_and_reason_.Set(this, priority_and_reason);
+    return;
+  }
   priority_and_reason_.SetAndMaybeNotify(this, priority_and_reason);
 }
 
@@ -583,11 +591,6 @@
   return true;
 }
 
-const PriorityAndReason& FrameNodeImpl::GetPriorityAndReason() const {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  return priority_and_reason();
-}
-
 bool FrameNodeImpl::HadFormInteraction() const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   return had_form_interaction();
@@ -624,6 +627,11 @@
   return private_footprint_kb_estimate();
 }
 
+const PriorityAndReason& FrameNodeImpl::GetPriorityAndReason() const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  return priority_and_reason();
+}
+
 void FrameNodeImpl::AddChildFrame(FrameNodeImpl* child_frame_node) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(child_frame_node);
diff --git a/components/performance_manager/graph/frame_node_impl.h b/components/performance_manager/graph/frame_node_impl.h
index 15aca126..951be62 100644
--- a/components/performance_manager/graph/frame_node_impl.h
+++ b/components/performance_manager/graph/frame_node_impl.h
@@ -190,7 +190,6 @@
   const base::flat_set<const WorkerNode*> GetChildWorkerNodes() const override;
   bool VisitChildDedicatedWorkers(
       const WorkerNodeVisitor& visitor) const override;
-  const PriorityAndReason& GetPriorityAndReason() const override;
   bool HadFormInteraction() const override;
   bool HadUserEdits() const override;
   bool IsAudible() const override;
@@ -198,6 +197,7 @@
   Visibility GetVisibility() const override;
   uint64_t GetResidentSetKbEstimate() const override;
   uint64_t GetPrivateFootprintKbEstimate() const override;
+  const PriorityAndReason& GetPriorityAndReason() const override;
 
   // Properties associated with a Document, which are reset when a
   // different-document navigation is committed in the frame.
diff --git a/components/performance_manager/graph_features_unittest.cc b/components/performance_manager/graph_features_unittest.cc
index 910ed2f6f..c823c05 100644
--- a/components/performance_manager/graph_features_unittest.cc
+++ b/components/performance_manager/graph_features_unittest.cc
@@ -53,7 +53,7 @@
       execution_context::ExecutionContextRegistry::GetFromGraph(&graph));
   EXPECT_FALSE(v8_memory::V8ContextTracker::GetFromGraph(&graph));
 
-  size_t graph_owned_count = 11;
+  size_t graph_owned_count = 12;
 #if !BUILDFLAG(IS_ANDROID)
   // The SiteDataRecorder is not available on Android.
   graph_owned_count++;
diff --git a/components/performance_manager/public/graph/frame_node.h b/components/performance_manager/public/graph/frame_node.h
index 2d6f54e..173f2df 100644
--- a/components/performance_manager/public/graph/frame_node.h
+++ b/components/performance_manager/public/graph/frame_node.h
@@ -31,6 +31,10 @@
 
 using execution_context_priority::PriorityAndReason;
 
+namespace execution_context_priority {
+class InheritClientPriorityVoter;
+}
+
 // Frame nodes form a tree structure, each FrameNode at most has one parent
 // that is a FrameNode. Conceptually, a FrameNode corresponds to a
 // content::RenderFrameHost (RFH) in the browser, and a
@@ -196,10 +200,6 @@
   virtual bool VisitChildDedicatedWorkers(
       const WorkerNodeVisitor& visitor) const = 0;
 
-  // Returns the current priority of the frame, and the reason for the frame
-  // having that particular priority.
-  virtual const PriorityAndReason& GetPriorityAndReason() const = 0;
-
   // Returns true if at least one form of the frame has been interacted with.
   virtual bool HadFormInteraction() const = 0;
 
@@ -236,6 +236,14 @@
   // kilobytes. This is an estimate because it is computed by process, and a
   // process can host multiple frames.
   virtual uint64_t GetPrivateFootprintKbEstimate() const = 0;
+
+ private:
+  friend class execution_context_priority::InheritClientPriorityVoter;
+
+  // Returns the current priority of the frame, and the reason for the frame
+  // having that particular priority.
+  // Note: Do not use, not ready for prime time.
+  virtual const PriorityAndReason& GetPriorityAndReason() const = 0;
 };
 
 // Pure virtual observer interface. Derive from this if you want to be forced to
diff --git a/components/performance_manager/public/graph/worker_node.h b/components/performance_manager/public/graph/worker_node.h
index b7f49264..290c3e1f 100644
--- a/components/performance_manager/public/graph/worker_node.h
+++ b/components/performance_manager/public/graph/worker_node.h
@@ -25,6 +25,10 @@
 
 using execution_context_priority::PriorityAndReason;
 
+namespace execution_context_priority {
+class InheritClientPriorityVoter;
+}
+
 // Represents a running instance of a WorkerGlobalScope.
 // See https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope.
 //
@@ -114,10 +118,6 @@
   virtual bool VisitChildDedicatedWorkers(
       const WorkerNodeVisitor& visitor) const = 0;
 
-  // Returns the current priority of the worker, and the reason for the worker
-  // having that particular priority.
-  virtual const PriorityAndReason& GetPriorityAndReason() const = 0;
-
   // TODO(joenotcharles): Move the resource usage estimates to a separate
   // class.
 
@@ -130,6 +130,14 @@
   // kilobytes. This is an estimate because PMF is computed by process, and a
   // process can host multiple workers.
   virtual uint64_t GetPrivateFootprintKbEstimate() const = 0;
+
+ private:
+  friend class execution_context_priority::InheritClientPriorityVoter;
+
+  // Returns the current priority of the worker, and the reason for the worker
+  // having that particular priority.
+  // Note: Do not use, not ready for prime time.
+  virtual const PriorityAndReason& GetPriorityAndReason() const = 0;
 };
 
 // Pure virtual observer interface. Derive from this if you want to be forced to
diff --git a/components/policy/core/common/BUILD.gn b/components/policy/core/common/BUILD.gn
index 4d3f2bf..f35d8d1c 100644
--- a/components/policy/core/common/BUILD.gn
+++ b/components/policy/core/common/BUILD.gn
@@ -159,6 +159,8 @@
     "features.h",
     "json_schema_constants.cc",
     "json_schema_constants.h",
+    "local_test_policy_provider.cc",
+    "local_test_policy_provider.h",
     "management/management_service.cc",
     "management/management_service.h",
     "management/platform_management_service.cc",
@@ -170,6 +172,8 @@
     "policy_load_status.h",
     "policy_loader_command_line.cc",
     "policy_loader_command_line.h",
+    "policy_loader_local_test.cc",
+    "policy_loader_local_test.h",
     "policy_logger.cc",
     "policy_logger.h",
     "policy_map.cc",
diff --git a/components/policy/core/common/local_test_policy_provider.cc b/components/policy/core/common/local_test_policy_provider.cc
new file mode 100644
index 0000000..0bed6d76
--- /dev/null
+++ b/components/policy/core/common/local_test_policy_provider.cc
@@ -0,0 +1,50 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/local_test_policy_provider.h"
+
+#include "base/memory/ptr_util.h"
+#include "build/build_config.h"
+#include "components/policy/core/common/policy_bundle.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_namespace.h"
+#include "components/policy/core/common/policy_service_impl.h"
+
+namespace policy {
+
+// static
+std::unique_ptr<LocalTestPolicyProvider>
+LocalTestPolicyProvider::CreateIfAllowed(version_info::Channel channel) {
+  if (channel == version_info::Channel::CANARY ||
+      channel == version_info::Channel::DEFAULT) {
+    return base::WrapUnique(new LocalTestPolicyProvider());
+  }
+
+#if BUILDFLAG(IS_IOS)
+  if (channel == version_info::Channel::BETA) {
+    return base::WrapUnique(new LocalTestPolicyProvider());
+  }
+#endif
+
+  return nullptr;
+}
+
+LocalTestPolicyProvider::~LocalTestPolicyProvider() = default;
+
+void LocalTestPolicyProvider::RefreshPolicies() {
+  PolicyBundle bundle = loader_.Load();
+  first_policies_loaded_ = true;
+  UpdatePolicy(std::move(bundle));
+}
+
+bool LocalTestPolicyProvider::IsFirstPolicyLoadComplete(
+    PolicyDomain domain) const {
+  return first_policies_loaded_;
+}
+
+LocalTestPolicyProvider::LocalTestPolicyProvider() {
+  RefreshPolicies();
+}
+
+}  // namespace policy
diff --git a/components/policy/core/common/local_test_policy_provider.h b/components/policy/core/common/local_test_policy_provider.h
new file mode 100644
index 0000000..380ea8f
--- /dev/null
+++ b/components/policy/core/common/local_test_policy_provider.h
@@ -0,0 +1,43 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_LOCAL_TEST_POLICY_PROVIDER_H_
+#define COMPONENTS_POLICY_CORE_COMMON_LOCAL_TEST_POLICY_PROVIDER_H_
+
+#include "components/policy/core/common/configuration_policy_provider.h"
+#include "components/policy/core/common/policy_loader_local_test.h"
+#include "components/policy/policy_export.h"
+#include "components/version_info/channel.h"
+
+namespace policy {
+
+// The policy provider for testing policies through the policy test page.
+// When provider is in use the policies from all other policy providers
+// will be disabled.
+class POLICY_EXPORT LocalTestPolicyProvider
+    : public ConfigurationPolicyProvider {
+ public:
+  // Creates the PolicyTestPolicyProvider
+  static std::unique_ptr<LocalTestPolicyProvider> CreateIfAllowed(
+      version_info::Channel channel);
+
+  LocalTestPolicyProvider(const LocalTestPolicyProvider&) = delete;
+  LocalTestPolicyProvider& operator=(const LocalTestPolicyProvider&) = delete;
+
+  ~LocalTestPolicyProvider() override;
+
+  // ConfigurationPolicyProvider implementation
+  void RefreshPolicies() override;
+  bool IsFirstPolicyLoadComplete(PolicyDomain domain) const override;
+
+ private:
+  explicit LocalTestPolicyProvider();
+
+  bool first_policies_loaded_;
+  PolicyLoaderLocalTest loader_;
+};
+
+}  // namespace policy
+
+#endif  // COMPONENTS_POLICY_CORE_COMMON_LOCAL_TEST_POLICY_PROVIDER_H_
diff --git a/components/policy/core/common/policy_loader_local_test.cc b/components/policy/core/common/policy_loader_local_test.cc
new file mode 100644
index 0000000..b17795e
--- /dev/null
+++ b/components/policy/core/common/policy_loader_local_test.cc
@@ -0,0 +1,28 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/policy/core/common/policy_loader_local_test.h"
+
+#include "base/json/json_reader.h"
+#include "components/policy/core/common/policy_bundle.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_namespace.h"
+#include "components/policy/core/common/policy_types.h"
+#include "components/policy/policy_constants.h"
+
+namespace policy {
+
+PolicyLoaderLocalTest::PolicyLoaderLocalTest() = default;
+
+PolicyLoaderLocalTest::~PolicyLoaderLocalTest() = default;
+
+PolicyBundle PolicyLoaderLocalTest::Load() {
+  PolicyBundle bundle;
+
+  // TODO (b:286422730): Load policies from json file.
+
+  return bundle;
+}
+
+}  // namespace policy
diff --git a/components/policy/core/common/policy_loader_local_test.h b/components/policy/core/common/policy_loader_local_test.h
new file mode 100644
index 0000000..6409ebf
--- /dev/null
+++ b/components/policy/core/common/policy_loader_local_test.h
@@ -0,0 +1,26 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_POLICY_CORE_COMMON_POLICY_LOADER_LOCAL_TEST_H_
+#define COMPONENTS_POLICY_CORE_COMMON_POLICY_LOADER_LOCAL_TEST_H_
+
+#include "components/policy/policy_export.h"
+
+namespace policy {
+
+class PolicyBundle;
+
+// Loads policies from policy testing page
+class POLICY_EXPORT PolicyLoaderLocalTest {
+ public:
+  explicit PolicyLoaderLocalTest();
+
+  ~PolicyLoaderLocalTest();
+
+  PolicyBundle Load();
+};
+
+}  // namespace policy
+
+#endif  // COMPONENTS_POLICY_CORE_COMMON_POLICY_LOADER_LOCAL_TEST_H_
diff --git a/components/policy/resources/templates/policy_definitions/Miscellaneous/AllowBackForwardCacheForCacheControlNoStorePageEnabled.yaml b/components/policy/resources/templates/policy_definitions/Miscellaneous/AllowBackForwardCacheForCacheControlNoStorePageEnabled.yaml
index 2f04e89..5fbfa59 100644
--- a/components/policy/resources/templates/policy_definitions/Miscellaneous/AllowBackForwardCacheForCacheControlNoStorePageEnabled.yaml
+++ b/components/policy/resources/templates/policy_definitions/Miscellaneous/AllowBackForwardCacheForCacheControlNoStorePageEnabled.yaml
@@ -4,7 +4,7 @@
 caption: |-
   Allow pages with <ph name="CACHE_CONTROL_NO_STORE_NAME">Cache-Control: no-store</ph> header to enter <ph name="BACK_FORWARD_CACHE_NAME">back/forward cache</ph>
 desc: |-
-  This policy controls if a page with <ph name="CACHE_CONTROL_NO_STORE_NAME">Cache-Control: no-store</ph> (CCNS) header can be stored in <ph name="BACK_FORWARD_CACHE_NAME">back/forward cache</ph>. The website setting this header may not expect the page to be restored from <ph name="BACK_FORWARD_CACHE_NAME">back/forward cache</ph> since some sensitive information could still be display after the restoration even if it is no longer accessible.
+  This policy controls if a page with <ph name="CACHE_CONTROL_NO_STORE_NAME">Cache-Control: no-store</ph> (CCNS) header can be stored in <ph name="BACK_FORWARD_CACHE_NAME">back/forward cache</ph>. The website setting this header may not expect the page to be restored from <ph name="BACK_FORWARD_CACHE_NAME">back/forward cache</ph> since some sensitive information could still be displayed after the restoration even if it is no longer accessible.
 
   If the policy is enabled or unset, the page with CCNS header might be restored from <ph name="BACK_FORWARD_CACHE_NAME">back/forward cache</ph> unless the cache eviction is triggered (e.g. when there is HTTP-only cookie change to the site).
 
@@ -36,4 +36,4 @@
   value: false
 default: true
 example_value: true
-tags: []
\ No newline at end of file
+tags: []
diff --git a/components/search/ntp_features.cc b/components/search/ntp_features.cc
index b5ee79c..73796c3 100644
--- a/components/search/ntp_features.cc
+++ b/components/search/ntp_features.cc
@@ -219,7 +219,7 @@
 // If enabled, Google Lens image search will be shown in the NTP Realbox.
 BASE_FEATURE(kNtpRealboxLensSearch,
              "NtpRealboxLensSearch",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 
 // If enabled, recipe tasks module will be shown.
 BASE_FEATURE(kNtpRecipeTasksModule,
diff --git a/components/security_interstitials/content/cert_logger.proto b/components/security_interstitials/content/cert_logger.proto
index fa46211e..47039b0 100644
--- a/components/security_interstitials/content/cert_logger.proto
+++ b/components/security_interstitials/content/cert_logger.proto
@@ -266,6 +266,11 @@
   optional SlotFilterType slot_filter_type = 3;
 }
 
+message AiaFetchDebugInfo {
+  optional int32 aia_fetch_failure = 1;
+  optional int32 aia_fetch_success = 2;
+}
+
 // Contains the results of verification by the trial verifier. All fields
 // have the same meaning as those of the same name in CertLoggerRequest.
 message TrialVerificationInfo {
@@ -362,4 +367,12 @@
 
   // The Linux distribution name, if report is from Linux.
   optional string linux_distro = 19;
+
+  // Information about AIA fetching from the trial cert verifier reports.
+  // TODO(hchao): primary AIA fetching information should probably go in
+  // CertLoggerRequest, but the code currently only provides this information if
+  // we're running a Trial Cert Verifier. If this changes, move the primary
+  // information out of here into CertLoggerRequest.
+  optional AiaFetchDebugInfo primary_aia_fetch_debug_info = 20;
+  optional AiaFetchDebugInfo trial_aia_fetch_debug_info = 21;
 }
diff --git a/components/security_interstitials/content/certificate_error_report.cc b/components/security_interstitials/content/certificate_error_report.cc
index b09db9ff..4a418e8 100644
--- a/components/security_interstitials/content/certificate_error_report.cc
+++ b/components/security_interstitials/content/certificate_error_report.cc
@@ -199,6 +199,17 @@
       chrome_root_store_debug_info->chrome_root_store_version);
 }
 #endif
+
+void AddAIADebugInfoToReport(
+    const cert_verifier::mojom::AiaFetchDebugInfoPtr& aia_debug_info,
+    chrome_browser_ssl::AiaFetchDebugInfo* report_info) {
+  if (!aia_debug_info) {
+    return;
+  }
+
+  report_info->set_aia_fetch_failure(aia_debug_info->aia_fetch_failure);
+  report_info->set_aia_fetch_success(aia_debug_info->aia_fetch_success);
+}
 #endif  // BUILDFLAG(TRIAL_COMPARISON_CERT_VERIFIER_SUPPORTED)
 
 bool CertificateChainToString(const net::X509Certificate& cert,
@@ -298,6 +309,11 @@
     trial_report->set_trial_der_verification_time(
         debug_info->trial_der_verification_time);
   }
+
+  AddAIADebugInfoToReport(debug_info->primary_aia_fetch_debug_info,
+                          trial_report->mutable_primary_aia_fetch_debug_info());
+  AddAIADebugInfoToReport(debug_info->trial_aia_fetch_debug_info,
+                          trial_report->mutable_trial_aia_fetch_debug_info());
 }
 #endif  // BUILDFLAG(TRIAL_COMPARISON_CERT_VERIFIER_SUPPORTED)
 
diff --git a/components/security_interstitials/content/certificate_error_report_unittest.cc b/components/security_interstitials/content/certificate_error_report_unittest.cc
index 13e83a0..7f9d122 100644
--- a/components/security_interstitials/content/certificate_error_report_unittest.cc
+++ b/components/security_interstitials/content/certificate_error_report_unittest.cc
@@ -400,6 +400,15 @@
       kTestChromeRootVersion;
 #endif
 
+  debug_info->primary_aia_fetch_debug_info =
+      cert_verifier::mojom::AiaFetchDebugInfo::New();
+  debug_info->primary_aia_fetch_debug_info->aia_fetch_failure = 1;
+  debug_info->primary_aia_fetch_debug_info->aia_fetch_success = 2;
+  debug_info->trial_aia_fetch_debug_info =
+      cert_verifier::mojom::AiaFetchDebugInfo::New();
+  debug_info->trial_aia_fetch_debug_info->aia_fetch_failure = 3;
+  debug_info->trial_aia_fetch_debug_info->aia_fetch_success = 4;
+
   base::Time time = base::Time::Now();
   debug_info->trial_verification_time = time;
   debug_info->trial_der_verification_time = "it's just a string";
@@ -482,6 +491,13 @@
             trial_info.trial_verification_time_usec());
   ASSERT_TRUE(trial_info.has_trial_der_verification_time());
   EXPECT_EQ("it's just a string", trial_info.trial_der_verification_time());
+
+  ASSERT_TRUE(trial_info.has_primary_aia_fetch_debug_info());
+  EXPECT_EQ(trial_info.primary_aia_fetch_debug_info().aia_fetch_failure(), 1);
+  EXPECT_EQ(trial_info.primary_aia_fetch_debug_info().aia_fetch_success(), 2);
+  ASSERT_TRUE(trial_info.has_trial_aia_fetch_debug_info());
+  EXPECT_EQ(trial_info.trial_aia_fetch_debug_info().aia_fetch_failure(), 3);
+  EXPECT_EQ(trial_info.trial_aia_fetch_debug_info().aia_fetch_success(), 4);
 }
 #endif  // BUILDFLAG(TRIAL_COMPARISON_CERT_VERIFIER_SUPPORTED)
 
diff --git a/components/security_interstitials/content/stateful_ssl_host_state_delegate.cc b/components/security_interstitials/content/stateful_ssl_host_state_delegate.cc
index e604abb4..9fc23d47 100644
--- a/components/security_interstitials/content/stateful_ssl_host_state_delegate.cc
+++ b/components/security_interstitials/content/stateful_ssl_host_state_delegate.cc
@@ -25,6 +25,7 @@
 #include "base/time/time.h"
 #include "base/values.h"
 #include "build/build_config.h"
+#include "components/content_settings/core/browser/content_settings_pref_provider.h"
 #include "components/content_settings/core/browser/host_content_settings_map.h"
 #include "components/content_settings/core/common/content_settings_types.h"
 #include "components/pref_registry/pref_registry_syncable.h"
@@ -421,6 +422,35 @@
          IsHttpAllowedForHost(host, storage_partition);
 }
 
+bool StatefulSSLHostStateDelegate::HasAllowExceptionForAnyHost(
+    content::StoragePartition* storage_partition) {
+  return HasCertAllowExceptionForAnyHost(storage_partition) ||
+         IsHttpAllowedForAnyHost(storage_partition);
+}
+
+bool StatefulSSLHostStateDelegate::HasCertAllowExceptionForAnyHost(
+    content::StoragePartition* storage_partition) {
+  if (!storage_partition ||
+      storage_partition != browser_context_->GetDefaultStoragePartition()) {
+    return !allowed_certs_for_non_default_storage_partitions_.empty();
+  }
+
+  ContentSettingsForOneType content_settings_list;
+  host_content_settings_map_->GetSettingsForOneType(
+      ContentSettingsType::SSL_CERT_DECISIONS, &content_settings_list);
+  return !content_settings_list.empty();
+}
+
+bool StatefulSSLHostStateDelegate::IsHttpAllowedForAnyHost(
+    content::StoragePartition* storage_partition) {
+  bool is_nondefault_storage =
+      !storage_partition ||
+      storage_partition != browser_context_->GetDefaultStoragePartition();
+
+  return https_only_mode_allowlist_.IsHttpAllowedForAnyHost(
+      is_nondefault_storage);
+}
+
 // TODO(jww): This will revoke all of the decisions in the browser context.
 // However, the networking stack actually keeps track of its own list of
 // exceptions per-HttpNetworkTransaction in the SSLConfig structure (see the
diff --git a/components/security_interstitials/content/stateful_ssl_host_state_delegate.h b/components/security_interstitials/content/stateful_ssl_host_state_delegate.h
index f4444fd..99eeade0 100644
--- a/components/security_interstitials/content/stateful_ssl_host_state_delegate.h
+++ b/components/security_interstitials/content/stateful_ssl_host_state_delegate.h
@@ -83,6 +83,10 @@
   void RevokeUserAllowExceptions(const std::string& host) override;
   bool HasAllowException(const std::string& host,
                          content::StoragePartition* storage_partition) override;
+  // Returns true if the user has allowed a certificate error exception or HTTP
+  // exception for any host.
+  bool HasAllowExceptionForAnyHost(
+      content::StoragePartition* storage_partition) override;
 
   void SetHttpsEnforcementForHost(
       const std::string& host,
@@ -159,6 +163,10 @@
       CreateDictionaryEntriesDisposition create_entries,
       base::Value::Dict& dict);
 
+  bool HasCertAllowExceptionForAnyHost(
+      content::StoragePartition* storage_partition);
+  bool IsHttpAllowedForAnyHost(content::StoragePartition* storage_partition);
+
   std::unique_ptr<base::Clock> clock_;
   raw_ptr<content::BrowserContext> browser_context_;
   raw_ptr<PrefService> pref_service_;
diff --git a/components/security_interstitials/core/https_only_mode_allowlist.cc b/components/security_interstitials/core/https_only_mode_allowlist.cc
index 1fe38600..0aa1ba1 100644
--- a/components/security_interstitials/core/https_only_mode_allowlist.cc
+++ b/components/security_interstitials/core/https_only_mode_allowlist.cc
@@ -8,6 +8,7 @@
 #include "base/json/values_util.h"
 #include "base/time/clock.h"
 #include "base/values.h"
+#include "components/content_settings/core/browser/content_settings_pref_provider.h"
 
 namespace {
 
@@ -58,6 +59,18 @@
       base::Value(std::move(dict)));
 }
 
+bool HttpsOnlyModeAllowlist::IsHttpAllowedForAnyHost(
+    bool is_nondefault_storage) const {
+  if (is_nondefault_storage) {
+    return !allowed_http_hosts_for_non_default_storage_partitions_.empty();
+  }
+
+  ContentSettingsForOneType content_settings_list;
+  host_content_settings_map_->GetSettingsForOneType(
+      ContentSettingsType::HTTP_ALLOWED, &content_settings_list);
+  return !content_settings_list.empty();
+}
+
 bool HttpsOnlyModeAllowlist::IsHttpAllowedForHost(
     const std::string& host,
     bool is_nondefault_storage) const {
diff --git a/components/security_interstitials/core/https_only_mode_allowlist.h b/components/security_interstitials/core/https_only_mode_allowlist.h
index d7d7746..2179b32e 100644
--- a/components/security_interstitials/core/https_only_mode_allowlist.h
+++ b/components/security_interstitials/core/https_only_mode_allowlist.h
@@ -41,6 +41,7 @@
   // attempt to upgrade it to HTTPS.
   bool IsHttpAllowedForHost(const std::string& host,
                             bool is_nondefault_storage) const;
+  bool IsHttpAllowedForAnyHost(bool is_nondefault_storage) const;
 
   // Revokes all HTTP exceptions made by the user for host.
   void RevokeUserAllowExceptions(const std::string& host);
diff --git a/components/sync_preferences/README.md b/components/sync_preferences/README.md
index 5cb33a1..6297517 100644
--- a/components/sync_preferences/README.md
+++ b/components/sync_preferences/README.md
@@ -1 +1,35 @@
-See components/sync/README.md.
+## Background
+
+Preferences are a generic system for storing configuration parameters in Chrome,
+see [Preferences] and [chrome/browser/prefs/README.md].
+
+Individual preferences may be declared as syncable, meaning they will be
+uploaded to the user's Google Account and propagate across signed-in/syncing
+devices from the same user.
+
+This folder contains the code for synchronizing preferences. See
+[components/sync/README.md] for background about Sync itself.
+
+## Adding syncable preferences
+
+Making a pref syncable requires a few things:
+* Specify appropriate [PrefRegistrationFlags] to the `Register*Pref` call.
+* Add an entry to the appropriate [SyncablePrefsDatabase]:
+  `ChromeSyncablePrefsDatabase` if the pref is in `chrome/`,
+  `IOSChromeSyncablePrefsDatabase` if it's in `ios/chrome/`, or
+  `CommonSyncablePrefsDatabase` if it's cross-platform.
+
+**Important**: Adding syncable prefs may have privacy impact. It's the
+  **responsibility of the code reviewer** to ensure that new syncable prefs
+  don't have undue privacy impact. In particular:
+* If the pref contains URLs (example: site permissions), it **must** be marked
+  as `is_history_opt_in_required = true`, and it will only be synced if the
+  user has opted in to history sync.
+* In any other cases that are unclear or questionable, reach out to
+  chrome-privacy-core@google.com, or to rainhard@ directly.
+
+[Preferences]: https://www.chromium.org/developers/design-documents/preferences/
+[chrome/browser/prefs/README.md]: ../../chrome/browser/prefs/README.md
+[components/sync/README.md]: ../../components/sync/README.md
+[PrefRegistrationFlags]: https://source.chromium.org/chromium/chromium/src/+/main:components/pref_registry/pref_registry_syncable.h?q=PrefRegistrationFlags
+[SyncablePrefsDatabase]: https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:components/sync_preferences/syncable_prefs_database.h
diff --git a/components/sync_preferences/common_syncable_prefs_database.cc b/components/sync_preferences/common_syncable_prefs_database.cc
index b50ffc8..a8bedcdc 100644
--- a/components/sync_preferences/common_syncable_prefs_database.cc
+++ b/components/sync_preferences/common_syncable_prefs_database.cc
@@ -106,6 +106,12 @@
   kPrefTranslateRecentTarget = 61,
   kPrefDogfoodGroups = 62,
   kSyncableMergeableDictPrefForTesting = 63,  // For tests.
+  // See components/sync_preferences/README.md about adding new entries here.
+  // vvvvv IMPORTANT! vvvvv
+  // Note to the reviewer: IT IS YOUR RESPONSIBILITY to ensure that new syncable
+  // prefs follow privacy guidelines! See the readme file linked above for
+  // guidance and escalation path in case anything is unclear.
+  // ^^^^^ IMPORTANT! ^^^^^
 };
 }  // namespace syncable_prefs_ids
 
diff --git a/components/viz/service/display/overlay_candidate.h b/components/viz/service/display/overlay_candidate.h
index 6e6b225..c9c9fdf 100644
--- a/components/viz/service/display/overlay_candidate.h
+++ b/components/viz/service/display/overlay_candidate.h
@@ -59,6 +59,7 @@
     kFailPriority,
     kFailNotSharedImage,
     kFailRoundedDisplayMasksNotSupported,
+    kFailMaskFilterNotSupported
   };
   using TrackingId = uint32_t;
   static constexpr TrackingId kDefaultTrackingId{0};
diff --git a/components/viz/service/display/overlay_candidate_factory.cc b/components/viz/service/display/overlay_candidate_factory.cc
index d54a998..d5a4fd5 100644
--- a/components/viz/service/display/overlay_candidate_factory.cc
+++ b/components/viz/service/display/overlay_candidate_factory.cc
@@ -132,7 +132,6 @@
     return CandidateStatus::kFailOpacity;
   }
   candidate.opacity = sqs->opacity;
-  candidate.rounded_corners = sqs->mask_filter_info.rounded_corner_bounds();
 
   // We support only kSrc (no blending) and kSrcOver (blending with premul).
   if (!(sqs->blend_mode == SkBlendMode::kSrc ||
@@ -140,6 +139,14 @@
     return CandidateStatus::kFailBlending;
   }
 
+  if (!sqs->mask_filter_info.IsEmpty() && !context_.supports_mask_filter) {
+    return CandidateStatus::kFailMaskFilterNotSupported;
+  }
+
+  candidate.has_mask_filter =
+      !quad->shared_quad_state->mask_filter_info.IsEmpty();
+  candidate.rounded_corners = sqs->mask_filter_info.rounded_corner_bounds();
+
   candidate.requires_overlay = OverlayCandidate::RequiresOverlay(quad);
   candidate.overlay_damage_index =
       sqs->overlay_damage_index.value_or(OverlayCandidate::kInvalidDamageIndex);
@@ -348,7 +355,6 @@
   candidate.clip_rect = sqs->clip_rect;
   candidate.is_opaque =
       !quad->ShouldDrawWithBlendingForReasonOtherThanMaskFilter();
-  candidate.has_mask_filter = !sqs->mask_filter_info.IsEmpty();
 
   if (resource_id != kInvalidResourceId) {
     candidate.resource_size_in_pixels =
@@ -503,8 +509,6 @@
   }
   candidate.is_opaque =
       !quad->ShouldDrawWithBlendingForReasonOtherThanMaskFilter();
-  candidate.has_mask_filter =
-      !quad->shared_quad_state->mask_filter_info.IsEmpty();
 
   AssignDamage(quad, candidate);
   candidate.tracking_id = base::FastHash(quad->overlay_plane_id.AsBytes());
diff --git a/components/viz/service/display/overlay_candidate_factory.h b/components/viz/service/display/overlay_candidate_factory.h
index 41f034b..5d5c506 100644
--- a/components/viz/service/display/overlay_candidate_factory.h
+++ b/components/viz/service/display/overlay_candidate_factory.h
@@ -51,6 +51,7 @@
     bool supports_clip_rect = false;
     bool supports_arbitrary_transform = false;
     bool supports_rounded_display_masks = false;
+    bool supports_mask_filter = false;
   };
 
   // The coordinate space of |render_pass| is the target space for candidates
diff --git a/components/viz/service/display/overlay_processor_delegated.cc b/components/viz/service/display/overlay_processor_delegated.cc
index d1d3c06..f583558 100644
--- a/components/viz/service/display/overlay_processor_delegated.cc
+++ b/components/viz/service/display/overlay_processor_delegated.cc
@@ -164,7 +164,9 @@
   }
 
   const OverlayCandidateFactory::OverlayContext context = {
-      .is_delegated_context = true, .supports_clip_rect = supports_clip_rect_};
+      .is_delegated_context = true,
+      .supports_clip_rect = supports_clip_rect_,
+      .supports_mask_filter = true};
 
   OverlayCandidateFactory candidate_factory = OverlayCandidateFactory(
       render_pass, resource_provider, surface_damage_rect_list,
diff --git a/components/viz/service/display/overlay_strategy_fullscreen.cc b/components/viz/service/display/overlay_strategy_fullscreen.cc
index ee0c3d2..6314f01 100644
--- a/components/viz/service/display/overlay_strategy_fullscreen.cc
+++ b/components/viz/service/display/overlay_strategy_fullscreen.cc
@@ -52,11 +52,13 @@
     return;
 
   OverlayCandidate candidate;
+  const OverlayCandidateFactory::OverlayContext context = {
+      .supports_mask_filter = false};
 
   OverlayCandidateFactory candidate_factory = OverlayCandidateFactory(
       render_pass, resource_provider, surface_damage_rect_list,
       &output_color_matrix, GetPrimaryPlaneDisplayRect(primary_plane),
-      &render_pass_filters, /*context=*/{});
+      &render_pass_filters, context);
   if (candidate_factory.FromDrawQuad(quad, candidate) !=
       OverlayCandidate::CandidateStatus::kSuccess) {
     return;
@@ -69,7 +71,7 @@
   }
   candidate.is_opaque = true;
   candidate.plane_z_order = 0;
-  candidates->push_back({front, candidate, this});
+  candidates->emplace_back(front, candidate, this);
 }
 
 bool OverlayStrategyFullscreen::Attempt(
diff --git a/components/viz/service/display/overlay_strategy_single_on_top.cc b/components/viz/service/display/overlay_strategy_single_on_top.cc
index 2906dfee..65488dd 100644
--- a/components/viz/service/display/overlay_strategy_single_on_top.cc
+++ b/components/viz/service/display/overlay_strategy_single_on_top.cc
@@ -101,7 +101,7 @@
   QuadList* quad_list = &render_pass->quad_list;
 
   const OverlayCandidateFactory::OverlayContext context = {
-      .supports_rounded_display_masks = true};
+      .supports_rounded_display_masks = true, .supports_mask_filter = false};
 
   // Build a list of candidates with the associated quad.
   OverlayCandidateFactory candidate_factory = OverlayCandidateFactory(
@@ -134,10 +134,6 @@
       continue;
     }
 
-    if (candidate.has_mask_filter) {
-      continue;
-    }
-
     if (candidate.has_rounded_display_masks) {
       // Quads with rounded-display masks are only considered as SingleOnTop
       // candidates if they are the top most quads.
diff --git a/components/viz/service/display/overlay_strategy_underlay.cc b/components/viz/service/display/overlay_strategy_underlay.cc
index a96848e..0e4a9a32 100644
--- a/components/viz/service/display/overlay_strategy_underlay.cc
+++ b/components/viz/service/display/overlay_strategy_underlay.cc
@@ -37,10 +37,13 @@
   auto* render_pass = render_pass_list->back().get();
   QuadList& quad_list = render_pass->quad_list;
 
+  const OverlayCandidateFactory::OverlayContext context = {
+      .supports_mask_filter = true};
+
   OverlayCandidateFactory candidate_factory = OverlayCandidateFactory(
       render_pass, resource_provider, surface_damage_rect_list,
       &output_color_matrix, GetPrimaryPlaneDisplayRect(primary_plane),
-      &render_pass_filters, /*context=*/{});
+      &render_pass_filters, context);
 
   for (auto it = quad_list.begin(); it != quad_list.end(); ++it) {
     OverlayCandidate candidate;
@@ -65,7 +68,7 @@
     candidate.damage_area_estimate = candidate_factory.EstimateVisibleDamage(
         *it, candidate, quad_list.begin(), it);
 
-    candidates->push_back({it, candidate, this});
+    candidates->emplace_back(it, candidate, this);
   }
 }
 
diff --git a/components/viz/service/display/overlay_strategy_underlay_cast.cc b/components/viz/service/display/overlay_strategy_underlay_cast.cc
index 2f43093..a426721 100644
--- a/components/viz/service/display/overlay_strategy_underlay_cast.cc
+++ b/components/viz/service/display/overlay_strategy_underlay_cast.cc
@@ -57,10 +57,12 @@
   OverlayCandidate candidate;
   auto overlay_iter = quad_list.end();
 
+  const OverlayCandidateFactory::OverlayContext context = {
+      .supports_mask_filter = true};
   OverlayCandidateFactory candidate_factory = OverlayCandidateFactory(
       render_pass, resource_provider, surface_damage_rect_list,
       &output_color_matrix, GetPrimaryPlaneDisplayRect(primary_plane),
-      &render_pass_filters, /*context=*/{});
+      &render_pass_filters, context);
 
   // Original code did reverse iteration.
   // Here we do forward but find the last one. which should be the same thing.
@@ -84,7 +86,7 @@
   }
 
   if (overlay_iter != quad_list.end()) {
-    candidates->push_back({overlay_iter, candidate, this});
+    candidates->emplace_back(overlay_iter, candidate, this);
   }
 }
 
@@ -107,10 +109,13 @@
   bool found_underlay = false;
   gfx::Rect content_rect;
 
+  const OverlayCandidateFactory::OverlayContext context = {
+      .supports_mask_filter = true};
+
   OverlayCandidateFactory candidate_factory = OverlayCandidateFactory(
       render_pass, resource_provider, surface_damage_rect_list,
       &output_color_matrix, GetPrimaryPlaneDisplayRect(primary_plane),
-      &render_pass_filters, /*context=*/{});
+      &render_pass_filters, context);
 
   for (const auto* quad : base::Reversed(quad_list)) {
     if (OverlayCandidate::IsInvisibleQuad(quad))
diff --git a/components/viz/test/test_context_provider.cc b/components/viz/test/test_context_provider.cc
index b4625c8..606a44c 100644
--- a/components/viz/test/test_context_provider.cc
+++ b/components/viz/test/test_context_provider.cc
@@ -299,6 +299,20 @@
 }
 
 // static
+scoped_refptr<TestContextProvider> TestContextProvider::CreateRaster() {
+  return CreateRaster(std::make_unique<TestContextSupport>());
+}
+
+// static
+scoped_refptr<TestContextProvider> TestContextProvider::CreateRaster(
+    std::unique_ptr<TestContextSupport> context_support) {
+  CHECK(context_support);
+  return base::MakeRefCounted<TestContextProvider>(
+      std::move(context_support), std::make_unique<TestRasterInterface>(),
+      /*support_locking=*/false);
+}
+
+// static
 scoped_refptr<TestContextProvider> TestContextProvider::CreateWorker() {
   return CreateWorker(std::make_unique<TestContextSupport>());
 }
diff --git a/components/viz/test/test_context_provider.h b/components/viz/test/test_context_provider.h
index 0433bf1..a025a5f 100644
--- a/components/viz/test/test_context_provider.h
+++ b/components/viz/test/test_context_provider.h
@@ -142,13 +142,9 @@
       public ContextProvider,
       public RasterContextProvider {
  public:
+  // Creates a context backed by TestGLES2Interface with no lock.
   static scoped_refptr<TestContextProvider> Create(
       std::string additional_extensions = std::string());
-  // Creates a worker context provider that can be used on any thread. This is
-  // equivalent to: Create(); BindToCurrentSequence().
-  static scoped_refptr<TestContextProvider> CreateWorker();
-  static scoped_refptr<TestContextProvider> CreateWorker(
-      std::unique_ptr<TestContextSupport> support);
   static scoped_refptr<TestContextProvider> Create(
       std::unique_ptr<TestGLES2Interface> gl);
   static scoped_refptr<TestContextProvider> Create(
@@ -156,6 +152,17 @@
   static scoped_refptr<TestContextProvider> Create(
       std::unique_ptr<TestContextSupport> support);
 
+  // Creates a context backed by TestRasterInterface with no lock.
+  static scoped_refptr<TestContextProvider> CreateRaster();
+  static scoped_refptr<TestContextProvider> CreateRaster(
+      std::unique_ptr<TestContextSupport> support);
+
+  // Creates a worker context provider that can be used on any thread. This is
+  // equivalent to: Create(); BindToCurrentSequence().
+  static scoped_refptr<TestContextProvider> CreateWorker();
+  static scoped_refptr<TestContextProvider> CreateWorker(
+      std::unique_ptr<TestContextSupport> support);
+
   explicit TestContextProvider(std::unique_ptr<TestContextSupport> support,
                                std::unique_ptr<TestRasterInterface> raster,
                                bool support_locking);
diff --git a/components/viz/test/test_gles2_interface.cc b/components/viz/test/test_gles2_interface.cc
index 2087d712..f2aa6f8a 100644
--- a/components/viz/test/test_gles2_interface.cc
+++ b/components/viz/test/test_gles2_interface.cc
@@ -455,10 +455,6 @@
   test_capabilities_.avoid_stencil_buffers = avoid_stencil_buffers;
 }
 
-void TestGLES2Interface::set_support_multisample_compatibility(bool support) {
-  test_capabilities_.multisample_compatibility = support;
-}
-
 void TestGLES2Interface::set_supports_scanout_shared_images(bool support) {
   test_capabilities_.supports_scanout_shared_images = support;
 }
diff --git a/components/viz/test/test_gles2_interface.h b/components/viz/test/test_gles2_interface.h
index f7df8b4..f2d31b0 100644
--- a/components/viz/test/test_gles2_interface.h
+++ b/components/viz/test/test_gles2_interface.h
@@ -133,7 +133,6 @@
   void set_msaa_is_slow(bool msaa_is_slow);
   void set_gpu_rasterization(bool gpu_rasterization);
   void set_avoid_stencil_buffers(bool avoid_stencil_buffers);
-  void set_support_multisample_compatibility(bool support);
   void set_supports_scanout_shared_images(bool support);
   void set_support_texture_npot(bool support);
   void set_supports_oop_raster(bool support);
diff --git a/components/webapps/browser/installable/metrics/site_quality_metrics_task.cc b/components/webapps/browser/installable/metrics/site_quality_metrics_task.cc
index a6de479..b73457e37 100644
--- a/components/webapps/browser/installable/metrics/site_quality_metrics_task.cc
+++ b/components/webapps/browser/installable/metrics/site_quality_metrics_task.cc
@@ -21,6 +21,7 @@
 #include "storage/browser/quota/quota_manager_impl.h"
 #include "storage/browser/quota/quota_manager_proxy.h"
 #include "third_party/blink/public/mojom/quota/quota_types.mojom.h"
+#include "url/gurl.h"
 #include "url/origin.h"
 
 namespace webapps {
@@ -29,6 +30,7 @@
 
 // static
 std::unique_ptr<SiteQualityMetricsTask> SiteQualityMetricsTask::CreateAndStart(
+    const GURL& site_url,
     content::WebContents& web_contents,
     content::StoragePartition& storage_partition,
     content::ServiceWorkerContext& service_worker_context,
@@ -36,19 +38,20 @@
     ResultCallback on_complete) {
   std::unique_ptr<SiteQualityMetricsTask> result =
       base::WrapUnique(new SiteQualityMetricsTask(
-          web_contents, storage_partition, service_worker_context, task_runner,
-          std::move(on_complete)));
+          site_url, web_contents, storage_partition, service_worker_context,
+          task_runner, std::move(on_complete)));
   result->Start();
   return result;
 }
 
 SiteQualityMetricsTask::SiteQualityMetricsTask(
+    const GURL& site_url,
     content::WebContents& web_contents,
     content::StoragePartition& storage_partition,
     content::ServiceWorkerContext& service_worker_context,
     scoped_refptr<base::SequencedTaskRunner> task_runner,
     ResultCallback on_complete)
-    : site_url_(web_contents.GetLastCommittedURL()),
+    : site_url_(site_url),
       web_contents_(web_contents),
       storage_partition_(storage_partition),
       service_worker_context_(service_worker_context),
diff --git a/components/webapps/browser/installable/metrics/site_quality_metrics_task.h b/components/webapps/browser/installable/metrics/site_quality_metrics_task.h
index 53a41a5..118f3700 100644
--- a/components/webapps/browser/installable/metrics/site_quality_metrics_task.h
+++ b/components/webapps/browser/installable/metrics/site_quality_metrics_task.h
@@ -71,6 +71,7 @@
   // that web_contents->GetLastCommittedURL() is not opaque when calling this
   // function.
   static std::unique_ptr<SiteQualityMetricsTask> CreateAndStart(
+      const GURL& site_url,
       content::WebContents& web_contents,
       content::StoragePartition& storage_partition,
       content::ServiceWorkerContext& service_worker_context,
@@ -78,7 +79,8 @@
       ResultCallback on_complete);
 
  private:
-  SiteQualityMetricsTask(content::WebContents& web_contents,
+  SiteQualityMetricsTask(const GURL& site_url,
+                         content::WebContents& web_contents,
                          content::StoragePartition& storage_partition,
                          content::ServiceWorkerContext& service_worker_context,
                          scoped_refptr<base::SequencedTaskRunner> task_runner,
diff --git a/components/webapps/browser/installable/ml_installability_promoter.cc b/components/webapps/browser/installable/ml_installability_promoter.cc
index f370811b..387d4d0b 100644
--- a/components/webapps/browser/installable/ml_installability_promoter.cc
+++ b/components/webapps/browser/installable/ml_installability_promoter.cc
@@ -89,7 +89,7 @@
   }
 
   site_quality_metrics_task_ = SiteQualityMetricsTask::CreateAndStart(
-      *web_contents(), *storage_partition_, *service_worker_context_,
+      site_url_, *web_contents(), *storage_partition_, *service_worker_context_,
       sequenced_task_runner_,
       base::BindOnce(&MLInstallabilityPromoter::OnDidCollectSiteQualityMetrics,
                      weak_factory_.GetWeakPtr()));
@@ -317,7 +317,7 @@
   // Restart the SiteQualityMetricsTask to read data from the QuotaManagerProxy
   // and the ServiceWorkerContext with registered service worker.
   site_quality_metrics_task_ = SiteQualityMetricsTask::CreateAndStart(
-      *web_contents(), *storage_partition_, *service_worker_context_,
+      site_url_, *web_contents(), *storage_partition_, *service_worker_context_,
       sequenced_task_runner_,
       base::BindOnce(&MLInstallabilityPromoter::OnDidCollectSiteQualityMetrics,
                      weak_factory_.GetWeakPtr()));
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn
index e027a9e..cc47fb1 100644
--- a/content/browser/BUILD.gn
+++ b/content/browser/BUILD.gn
@@ -150,6 +150,7 @@
     "//content/public/common:common_sources",
     "//content/public/common:content_descriptor_keys",
     "//content/public/common/zygote:buildflags",
+    "//content/services/auction_worklet/public/cpp",
     "//content/services/auction_worklet/public/mojom",
     "//crypto",
     "//device/base",
diff --git a/content/browser/aggregation_service/aggregatable_report_unittest.cc b/content/browser/aggregation_service/aggregatable_report_unittest.cc
index 232d3df..0deaab65 100644
--- a/content/browser/aggregation_service/aggregatable_report_unittest.cc
+++ b/content/browser/aggregation_service/aggregatable_report_unittest.cc
@@ -874,7 +874,8 @@
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitAndEnableFeatureWithParameters(
       ::aggregation_service::kAggregationServiceMultipleCloudProviders,
-      {{"aws_cloud", "https://aws.example.test"}});
+      {{"aws_cloud", "https://aws.example.test"},
+       {"gcp_cloud", "https://gcp.example.test"}});
 
   const struct {
     absl::optional<url::Origin> aggregation_coordinator_origin;
@@ -891,6 +892,11 @@
                 "public-keys")},
       },
       {
+          url::Origin::Create(GURL("https://gcp.example.test")),
+          {GURL("https://gcp.example.test/.well-known/aggregation-service/"
+                "public-keys")},
+      },
+      {
           url::Origin::Create(GURL("https://a.test")),
           {},
       },
diff --git a/content/browser/android/synchronous_compositor_host.cc b/content/browser/android/synchronous_compositor_host.cc
index 69219fb..03ffa64 100644
--- a/content/browser/android/synchronous_compositor_host.cc
+++ b/content/browser/android/synchronous_compositor_host.cc
@@ -337,7 +337,6 @@
       blink::mojom::SyncCompositorDemandDrawSwParams::New();  // Unused.
   uint32_t metadata_version = 0u;
   invalidate_needs_draw_ = false;
-  num_invalidates_since_last_draw_ = 0u;
   if (!IsReadyForSynchronousCall() ||
       !GetSynchronousCompositor()->DemandDrawSw(std::move(params),
                                                 &common_renderer_params,
@@ -377,6 +376,7 @@
 
 bool SynchronousCompositorHost::DemandDrawSw(SkCanvas* canvas,
                                              bool software_canvas) {
+  num_invalidates_since_last_draw_ = 0u;
   if (use_in_process_zero_copy_software_draw_)
     return DemandDrawSwInProc(canvas);
 
diff --git a/content/browser/devtools/devtools_url_loader_interceptor.cc b/content/browser/devtools/devtools_url_loader_interceptor.cc
index 574d802..9260304 100644
--- a/content/browser/devtools/devtools_url_loader_interceptor.cc
+++ b/content/browser/devtools/devtools_url_loader_interceptor.cc
@@ -1463,8 +1463,7 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(!waiting_for_resolution_);
   FetchCookies(base::BindOnce(&InterceptionJob::NotifyClientWithCookies,
-                              base::UnsafeDanglingUntriaged(this),
-                              std::move(request_info)));
+                              base::Unretained(this), std::move(request_info)));
 }
 
 void InterceptionJob::NotifyClientWithCookies(
diff --git a/content/browser/interest_group/DEPS b/content/browser/interest_group/DEPS
index 8ba2547..57aad5a 100644
--- a/content/browser/interest_group/DEPS
+++ b/content/browser/interest_group/DEPS
@@ -1,5 +1,6 @@
 include_rules = [
   "+content/services/auction_worklet/public/mojom",
+  "+content/services/auction_worklet/public/cpp",
 ]
 
 specific_include_rules = {
diff --git a/content/browser/interest_group/interest_group_browsertest.cc b/content/browser/interest_group/interest_group_browsertest.cc
index 255d30a5..c3103e1e 100644
--- a/content/browser/interest_group/interest_group_browsertest.cc
+++ b/content/browser/interest_group/interest_group_browsertest.cc
@@ -7035,6 +7035,7 @@
 
   const config = {
     seller: $1,
+    // Must have FLEDGE header and JS mime type.
     decisionLogicUrl: $2,
     interestGroupBuyers: [$1]
   };
diff --git a/content/browser/interest_group/interest_group_update_manager.cc b/content/browser/interest_group/interest_group_update_manager.cc
index ca1ce76..97cd6224 100644
--- a/content/browser/interest_group/interest_group_update_manager.cc
+++ b/content/browser/interest_group/interest_group_update_manager.cc
@@ -26,6 +26,7 @@
 #include "content/browser/interest_group/interest_group_storage.h"
 #include "content/browser/interest_group/interest_group_update.h"
 #include "content/browser/interest_group/storage_interest_group.h"
+#include "content/services/auction_worklet/public/cpp/auction_downloader.h"
 #include "net/base/isolation_info.h"
 #include "net/base/net_errors.h"
 #include "net/traffic_annotation/network_traffic_annotation.h"
diff --git a/content/browser/preloading/prefetch/prefetch_service.cc b/content/browser/preloading/prefetch/prefetch_service.cc
index 7dc80513..663c3960 100644
--- a/content/browser/preloading/prefetch/prefetch_service.cc
+++ b/content/browser/preloading/prefetch/prefetch_service.cc
@@ -364,17 +364,17 @@
         break;
       case PreloadingEligibility::kDataSaverEnabled:
         OnGotEligibilityResult(
-            prefetch_container->GetURL(), prefetch_container, false,
+            prefetch_container, false,
             PrefetchStatus::kPrefetchNotEligibleDataSaverEnabled);
         return;
       case PreloadingEligibility::kBatterySaverEnabled:
         OnGotEligibilityResult(
-            prefetch_container->GetURL(), prefetch_container, false,
+            prefetch_container, false,
             PrefetchStatus::kPrefetchNotEligibleBatterySaverEnabled);
         return;
       case PreloadingEligibility::kPreloadingDisabled:
         OnGotEligibilityResult(
-            prefetch_container->GetURL(), prefetch_container, false,
+            prefetch_container, false,
             PrefetchStatus::kPrefetchNotEligiblePreloadingDisabled);
         return;
       default:
@@ -441,7 +441,7 @@
 
   if (browser_context_->IsOffTheRecord()) {
     std::move(result_callback)
-        .Run(url, prefetch_container, false,
+        .Run(prefetch_container, false,
              PrefetchStatus::kPrefetchNotEligibleBrowserContextOffTheRecord);
     return;
   }
@@ -455,7 +455,7 @@
   if (!prefetch_container->GetPrefetchType().IsProxyBypassedForTesting() &&
       prefetch_container->IsProxyRequiredForURL(url) && is_host_non_unique) {
     std::move(result_callback)
-        .Run(url, prefetch_container, false,
+        .Run(prefetch_container, false,
              PrefetchStatus::kPrefetchNotEligibleHostIsNonUnique);
     return;
   }
@@ -470,7 +470,7 @@
                                      network::IsUrlPotentiallyTrustworthy(url));
   if (!is_secure_http) {
     std::move(result_callback)
-        .Run(url, prefetch_container, false,
+        .Run(prefetch_container, false,
              PrefetchStatus::kPrefetchNotEligibleSchemeIsNotHttps);
     return;
   }
@@ -480,7 +480,7 @@
       (!prefetch_proxy_configurator_ ||
        !prefetch_proxy_configurator_->IsPrefetchProxyAvailable())) {
     std::move(result_callback)
-        .Run(url, prefetch_container, false,
+        .Run(prefetch_container, false,
              PrefetchStatus::kPrefetchProxyNotAvailable);
     return;
   }
@@ -493,7 +493,7 @@
       browser_context_->GetStoragePartitionForUrl(url,
                                                   /*can_create=*/false)) {
     std::move(result_callback)
-        .Run(url, prefetch_container, false,
+        .Run(prefetch_container, false,
              PrefetchStatus::kPrefetchNotEligibleNonDefaultStoragePartition);
     return;
   }
@@ -502,7 +502,7 @@
   // send new prefetches.
   if (delegate_ && !delegate_->IsOriginOutsideRetryAfterWindow(url)) {
     std::move(result_callback)
-        .Run(url, prefetch_container, false,
+        .Run(prefetch_container, false,
              PrefetchStatus::kPrefetchIneligibleRetryAfter);
     return;
   }
@@ -521,7 +521,7 @@
           blink::StorageKey::CreateFirstParty(url::Origin::Create(url)));
   if (site_has_service_worker) {
     std::move(result_callback)
-        .Run(url, prefetch_container, false,
+        .Run(prefetch_container, false,
              PrefetchStatus::kPrefetchNotEligibleUserHasServiceWorker);
     return;
   }
@@ -536,7 +536,7 @@
       !prefetch_container
            ->IsIsolatedNetworkContextRequiredForCurrentPrefetch()) {
     std::move(result_callback)
-        .Run(url, prefetch_container, false,
+        .Run(prefetch_container, false,
              PrefetchStatus::
                  kPrefetchNotEligibleSameSiteCrossOriginPrefetchRequiredProxy);
     return;
@@ -546,8 +546,7 @@
   // isolated network context.
   if (!prefetch_container
            ->IsIsolatedNetworkContextRequiredForCurrentPrefetch()) {
-    std::move(result_callback)
-        .Run(url, prefetch_container, true, absl::nullopt);
+    std::move(result_callback).Run(prefetch_container, true, absl::nullopt);
     return;
   }
 
@@ -567,14 +566,13 @@
     const net::CookieAccessResultList& cookie_list,
     const net::CookieAccessResultList& excluded_cookies) const {
   if (!prefetch_container) {
-    std::move(result_callback)
-        .Run(url, prefetch_container, false, absl::nullopt);
+    std::move(result_callback).Run(prefetch_container, false, absl::nullopt);
     return;
   }
 
   if (!cookie_list.empty()) {
     std::move(result_callback)
-        .Run(url, prefetch_container, false,
+        .Run(prefetch_container, false,
              PrefetchStatus::kPrefetchNotEligibleUserHasCookies);
     return;
   }
@@ -599,7 +597,7 @@
 
   if (excluded_cookie_has_tld) {
     std::move(result_callback)
-        .Run(url, prefetch_container, false,
+        .Run(prefetch_container, false,
              PrefetchStatus::kPrefetchNotEligibleUserHasCookies);
     return;
   }
@@ -618,8 +616,7 @@
   // prefetches to be made using the existing proxy settings.
   if (!prefetch_container
            ->IsIsolatedNetworkContextRequiredForCurrentPrefetch()) {
-    std::move(result_callback)
-        .Run(url, prefetch_container, true, absl::nullopt);
+    std::move(result_callback).Run(prefetch_container, true, absl::nullopt);
     return;
   }
 
@@ -631,8 +628,8 @@
       std::make_unique<ProxyLookupClientImpl>(
           url, network_anonymization_key,
           base::BindOnce(&PrefetchService::OnGotProxyLookupResult,
-                         weak_method_factory_.GetWeakPtr(), url,
-                         prefetch_container, std::move(result_callback)),
+                         weak_method_factory_.GetWeakPtr(), prefetch_container,
+                         std::move(result_callback)),
           g_network_context_for_proxy_lookup_for_testing
               ? g_network_context_for_proxy_lookup_for_testing
               : browser_context_->GetDefaultStoragePartition()
@@ -640,28 +637,25 @@
 }
 
 void PrefetchService::OnGotProxyLookupResult(
-    const GURL& url,
     base::WeakPtr<PrefetchContainer> prefetch_container,
     OnEligibilityResultCallback result_callback,
     bool has_proxy) const {
   if (!prefetch_container) {
-    std::move(result_callback)
-        .Run(url, prefetch_container, false, absl::nullopt);
+    std::move(result_callback).Run(prefetch_container, false, absl::nullopt);
     return;
   }
 
   prefetch_container->ReleaseProxyLookupClient();
   if (has_proxy) {
     std::move(result_callback)
-        .Run(url, prefetch_container, false,
+        .Run(prefetch_container, false,
              PrefetchStatus::kPrefetchNotEligibleExistingProxy);
     return;
   }
-  std::move(result_callback).Run(url, prefetch_container, true, absl::nullopt);
+  std::move(result_callback).Run(prefetch_container, true, absl::nullopt);
 }
 
 void PrefetchService::OnGotEligibilityResult(
-    const GURL& url,
     base::WeakPtr<PrefetchContainer> prefetch_container,
     bool eligible,
     absl::optional<PrefetchStatus> status) {
@@ -674,7 +668,8 @@
     // Expect a status if the container is alive but prefetch not eligible.
     DCHECK(status.has_value());
     is_decoy =
-        prefetch_container->IsProxyRequiredForURL(url) &&
+        prefetch_container->IsProxyRequiredForURL(
+            prefetch_container->GetURL()) &&
         ShouldConsiderDecoyRequestForStatus(status.value()) &&
         PrefetchServiceSendDecoyRequestForIneligblePrefetch(
             delegate_ ? delegate_->DisableDecoysBasedOnUserSettings() : false);
@@ -718,7 +713,8 @@
 }
 
 void PrefetchService::OnGotEligibilityResultForRedirect(
-    const GURL& url,
+    const net::RedirectInfo& redirect_info,
+    network::mojom::URLResponseHeadPtr redirect_head,
     base::WeakPtr<PrefetchContainer> prefetch_container,
     bool eligible,
     absl::optional<PrefetchStatus> status) {
@@ -736,7 +732,7 @@
     // Expect a status if the container is alive but prefetch not eligible.
     DCHECK(status.has_value());
     is_decoy =
-        prefetch_container->IsProxyRequiredForURL(url) &&
+        prefetch_container->IsProxyRequiredForURL(redirect_info.new_url) &&
         ShouldConsiderDecoyRequestForStatus(status.value()) &&
         PrefetchServiceSendDecoyRequestForIneligblePrefetch(
             delegate_ ? delegate_->DisableDecoysBasedOnUserSettings() : false);
@@ -762,7 +758,8 @@
   if (!eligible && !prefetch_container->IsDecoy()) {
     active_prefetches_.erase(prefetch_container->GetPrefetchContainerKey());
     prefetch_container->GetLastStreamingURLLoader()->HandleRedirect(
-        PrefetchStreamingURLLoaderStatus::kFailedInvalidRedirect);
+        PrefetchStreamingURLLoaderStatus::kFailedInvalidRedirect, redirect_info,
+        std::move(redirect_head));
     prefetch_container->ResetAllStreamingURLLoaders();
 
     Prefetch();
@@ -779,15 +776,17 @@
           ->IsIsolatedNetworkContextRequiredForPreviousRedirectHop()) {
     prefetch_container->GetLastStreamingURLLoader()->HandleRedirect(
         PrefetchStreamingURLLoaderStatus::
-            kStopSwitchInNetworkContextForRedirect);
-    MakePrefetchRequest(prefetch_container, url);
+            kStopSwitchInNetworkContextForRedirect,
+        redirect_info, std::move(redirect_head));
+    MakePrefetchRequest(prefetch_container, redirect_info.new_url);
 
     return;
   }
 
   // Otherwise, follow the redirect in the same streaming URL loader.
   prefetch_container->GetLastStreamingURLLoader()->HandleRedirect(
-      PrefetchStreamingURLLoaderStatus::kFollowRedirect);
+      PrefetchStreamingURLLoaderStatus::kFollowRedirect, redirect_info,
+      std::move(redirect_head));
 }
 
 void PrefetchService::Prefetch() {
@@ -1096,7 +1095,7 @@
 void PrefetchService::OnPrefetchRedirect(
     base::WeakPtr<PrefetchContainer> prefetch_container,
     const net::RedirectInfo& redirect_info,
-    const network::mojom::URLResponseHead& response_head) {
+    network::mojom::URLResponseHeadPtr redirect_head) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   if (!prefetch_container) {
@@ -1126,34 +1125,32 @@
       IsReferrerPolicySufficientlyStrict(
           prefetch_container->GetReferrer().policy);
 
-  if (!base::FeatureList::IsEnabled(features::kPrefetchRedirects) ||
-      redirect_info.new_method != "GET" || !response_head.headers ||
-      response_head.headers->response_code() < 300 ||
-      response_head.headers->response_code() >= 400 ||
-      (previous_site != redirect_site &&
-       !is_referrer_policy_sufficiently_strict)) {
+  absl::optional<PrefetchRedirectResult> failure;
+
+  if (!base::FeatureList::IsEnabled(features::kPrefetchRedirects)) {
+    failure = PrefetchRedirectResult::kFailedRedirectsDisabled;
+  } else if (redirect_info.new_method != "GET") {
+    failure = PrefetchRedirectResult::kFailedInvalidMethod;
+  } else if (!redirect_head->headers ||
+             redirect_head->headers->response_code() < 300 ||
+             redirect_head->headers->response_code() >= 400) {
+    failure = PrefetchRedirectResult::kFailedInvalidResponseCode;
+  } else if (previous_site != redirect_site &&
+             !is_referrer_policy_sufficiently_strict) {
+    failure = PrefetchRedirectResult::kFailedInsufficientReferrerPolicy;
+  }
+
+  if (failure) {
     active_prefetches_.erase(prefetch_container->GetPrefetchContainerKey());
     prefetch_container->SetPrefetchStatus(
         PrefetchStatus::kPrefetchFailedInvalidRedirect);
     prefetch_container->GetLastStreamingURLLoader()->HandleRedirect(
-        PrefetchStreamingURLLoaderStatus::kFailedInvalidRedirect);
+        PrefetchStreamingURLLoaderStatus::kFailedInvalidRedirect, redirect_info,
+        std::move(redirect_head));
     prefetch_container->ResetAllStreamingURLLoaders();
 
     Prefetch();
-
-    if (!base::FeatureList::IsEnabled(features::kPrefetchRedirects)) {
-      RecordRedirectResult(PrefetchRedirectResult::kFailedRedirectsDisabled);
-    } else if (redirect_info.new_method != "GET") {
-      RecordRedirectResult(PrefetchRedirectResult::kFailedInvalidMethod);
-    } else if (response_head.headers->response_code() < 300 ||
-               response_head.headers->response_code() >= 400) {
-      RecordRedirectResult(PrefetchRedirectResult::kFailedInvalidResponseCode);
-    } else if (previous_site != redirect_site &&
-               !is_referrer_policy_sufficiently_strict) {
-      RecordRedirectResult(
-          PrefetchRedirectResult::kFailedInsufficientReferrerPolicy);
-    }
-
+    RecordRedirectResult(*failure);
     return;
   }
 
@@ -1165,7 +1162,8 @@
   CheckEligibilityOfPrefetch(
       redirect_info.new_url, prefetch_container,
       base::BindOnce(&PrefetchService::OnGotEligibilityResultForRedirect,
-                     base::Unretained(this)));
+                     base::Unretained(this), redirect_info,
+                     std::move(redirect_head)));
 }
 
 PrefetchStreamingURLLoaderStatus PrefetchService::OnPrefetchResponseStarted(
diff --git a/content/browser/preloading/prefetch/prefetch_service.h b/content/browser/preloading/prefetch/prefetch_service.h
index 17b14b5..a84a11d 100644
--- a/content/browser/preloading/prefetch/prefetch_service.h
+++ b/content/browser/preloading/prefetch/prefetch_service.h
@@ -152,8 +152,7 @@
   // with result and an optional status stating why the prefetch is not
   // eligible.
   using OnEligibilityResultCallback =
-      base::OnceCallback<void(const GURL& url,
-                              base::WeakPtr<PrefetchContainer>,
+      base::OnceCallback<void(base::WeakPtr<PrefetchContainer>,
                               bool eligible,
                               absl::optional<PrefetchStatus> status)>;
   void CheckEligibilityOfPrefetch(
@@ -183,7 +182,6 @@
   // |prefetch_container|. If there is an existing proxy, then the prefetch is
   // not eligible.
   void OnGotProxyLookupResult(
-      const GURL& url,
       base::WeakPtr<PrefetchContainer> prefetch_container,
       OnEligibilityResultCallback result_callback,
       bool has_proxy) const;
@@ -192,7 +190,6 @@
   // prefetch is eligible it is added to the queue to be prefetched. If it is
   // not eligible, then we consider making it a decoy request.
   void OnGotEligibilityResult(
-      const GURL& url,
       base::WeakPtr<PrefetchContainer> prefetch_container,
       bool eligible,
       absl::optional<PrefetchStatus> status);
@@ -201,7 +198,8 @@
   // determined. If its eligible, then the prefetch will continue, otherwise it
   // is stopped.
   void OnGotEligibilityResultForRedirect(
-      const GURL& url,
+      const net::RedirectInfo& redirect_info,
+      network::mojom::URLResponseHeadPtr redirect_head,
       base::WeakPtr<PrefetchContainer> prefetch_container,
       bool eligible,
       absl::optional<PrefetchStatus> status);
@@ -243,7 +241,7 @@
   // Called when the request for |prefetch_container| is redirected.
   void OnPrefetchRedirect(base::WeakPtr<PrefetchContainer> prefetch_container,
                           const net::RedirectInfo& redirect_info,
-                          const network::mojom::URLResponseHead& response_head);
+                          network::mojom::URLResponseHeadPtr redirect_head);
 
   // Called when the response for |prefetch_container| has started. Based on
   // |head|, returns a status to inform the |PrefetchStreamingURLLoader| whether
diff --git a/content/browser/preloading/prefetch/prefetch_streaming_url_loader.cc b/content/browser/preloading/prefetch/prefetch_streaming_url_loader.cc
index c8c4b6e..f9137b80 100644
--- a/content/browser/preloading/prefetch/prefetch_streaming_url_loader.cc
+++ b/content/browser/preloading/prefetch/prefetch_streaming_url_loader.cc
@@ -223,19 +223,16 @@
 
 void PrefetchStreamingURLLoader::OnReceiveRedirect(
     const net::RedirectInfo& redirect_info,
-    network::mojom::URLResponseHeadPtr head) {
+    network::mojom::URLResponseHeadPtr redirect_head) {
   DCHECK(on_prefetch_redirect_callback_);
-  DCHECK(!redirect_head_);
-
-  redirect_info_ = redirect_info;
-  redirect_head_ = std::move(head);
-
-  on_prefetch_redirect_callback_.Run(redirect_info, *redirect_head_.get());
+  on_prefetch_redirect_callback_.Run(redirect_info, std::move(redirect_head));
 }
 
 void PrefetchStreamingURLLoader::HandleRedirect(
-    PrefetchStreamingURLLoaderStatus new_status) {
-  DCHECK(redirect_head_);
+    PrefetchStreamingURLLoaderStatus new_status,
+    const net::RedirectInfo& redirect_info,
+    network::mojom::URLResponseHeadPtr redirect_head) {
+  DCHECK(redirect_head);
 
   // If the prefetch_url_loader_ is no longer connected, mark this as failed.
   if (!prefetch_url_loader_) {
@@ -255,8 +252,8 @@
       DCHECK(event_queue_status_ == EventQueueStatus::kNotStarted);
       AddEventToQueue(
           base::BindOnce(&PrefetchStreamingURLLoader::ForwardRedirect,
-                         base::Unretained(this), redirect_info_,
-                         std::move(redirect_head_)),
+                         base::Unretained(this), redirect_info,
+                         std::move(redirect_head)),
           /*pause_after_event=*/true);
       break;
     case PrefetchStreamingURLLoaderStatus::
@@ -272,8 +269,8 @@
       DCHECK(event_queue_status_ == EventQueueStatus::kNotStarted);
       AddEventToQueue(
           base::BindOnce(&PrefetchStreamingURLLoader::ForwardRedirect,
-                         base::Unretained(this), redirect_info_,
-                         std::move(redirect_head_)),
+                         base::Unretained(this), redirect_info,
+                         std::move(redirect_head)),
           /*pause_after_event=*/false);
       break;
     case PrefetchStreamingURLLoaderStatus::kFailedInvalidRedirect:
diff --git a/content/browser/preloading/prefetch/prefetch_streaming_url_loader.h b/content/browser/preloading/prefetch/prefetch_streaming_url_loader.h
index 099a631..a01287ab 100644
--- a/content/browser/preloading/prefetch/prefetch_streaming_url_loader.h
+++ b/content/browser/preloading/prefetch/prefetch_streaming_url_loader.h
@@ -37,7 +37,7 @@
   // how the redirect should be handled.
   using OnPrefetchRedirectCallback = base::RepeatingCallback<void(
       const net::RedirectInfo& redirect_info,
-      const network::mojom::URLResponseHead& response_head)>;
+      network::mojom::URLResponseHeadPtr response_head)>;
 
   PrefetchStreamingURLLoader(
       network::mojom::URLLoaderFactory* url_loader_factory,
@@ -63,7 +63,9 @@
   //   network context.
   // - |kFailedInvalidRedirect|, if the redirect should not be followed by
   //   |this|.
-  void HandleRedirect(PrefetchStreamingURLLoaderStatus new_status);
+  void HandleRedirect(PrefetchStreamingURLLoaderStatus new_status,
+                      const net::RedirectInfo& redirect_info,
+                      network::mojom::URLResponseHeadPtr redirect_head);
 
   // Registers a callback that is called once the head of the response is
   // received via either |OnReceiveResponse| or |OnReceiveRedirect|. The
@@ -200,12 +202,6 @@
   absl::optional<network::URLLoaderCompletionStatus> completion_status_;
   absl::optional<base::TimeTicks> response_complete_time_;
 
-  // These store the most recent redirect in the event that |this| needs to wait
-  // for the prefetch eligibility check to complete before deciding whether to
-  // follow the redirect or not.
-  net::RedirectInfo redirect_info_;
-  network::mojom::URLResponseHeadPtr redirect_head_;
-
   // The URL Loader events that occur before serving the prefetch are queued up
   // until the prefetch is served. The first value is the closure to run the
   // event, and the second value is whether or not the event queue should be
diff --git a/content/browser/preloading/prefetch/prefetch_streaming_url_loader_unittest.cc b/content/browser/preloading/prefetch/prefetch_streaming_url_loader_unittest.cc
index 634ba6f..7ec6bbb 100644
--- a/content/browser/preloading/prefetch/prefetch_streaming_url_loader_unittest.cc
+++ b/content/browser/preloading/prefetch/prefetch_streaming_url_loader_unittest.cc
@@ -6,6 +6,7 @@
 
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/task_environment.h"
+#include "content/browser/preloading/prefetch/prefetch_test_utils.h"
 #include "mojo/public/cpp/bindings/receiver_set.h"
 #include "mojo/public/cpp/system/data_pipe.h"
 #include "mojo/public/cpp/system/data_pipe_drainer.h"
@@ -326,7 +327,7 @@
               &on_response_complete_loop),
           base::BindRepeating(
               [](const net::RedirectInfo& redirect_info,
-                 const network::mojom::URLResponseHead& response_head) {
+                 network::mojom::URLResponseHeadPtr response_head) {
                 NOTREACHED();
               }));
 
@@ -437,7 +438,7 @@
               &on_response_complete_loop),
           base::BindRepeating(
               [](const net::RedirectInfo& redirect_info,
-                 const network::mojom::URLResponseHead& response_head) {
+                 network::mojom::URLResponseHeadPtr response_head) {
                 NOTREACHED();
               }));
 
@@ -562,7 +563,7 @@
               &on_response_complete_loop),
           base::BindRepeating(
               [](const net::RedirectInfo& redirect_info,
-                 const network::mojom::URLResponseHead& response_head) {
+                 network::mojom::URLResponseHeadPtr response_head) {
                 NOTREACHED();
               }));
 
@@ -623,7 +624,7 @@
               }),
           base::BindRepeating(
               [](const net::RedirectInfo& redirect_info,
-                 const network::mojom::URLResponseHead& response_head) {
+                 network::mojom::URLResponseHeadPtr response_head) {
                 NOTREACHED();
               }));
 
@@ -684,7 +685,7 @@
               &on_response_complete_loop),
           base::BindRepeating(
               [](const net::RedirectInfo& redirect_info,
-                 const network::mojom::URLResponseHead& response_head) {
+                 network::mojom::URLResponseHeadPtr response_head) {
                 NOTREACHED();
               }));
 
@@ -746,7 +747,7 @@
               &on_response_complete_loop),
           base::BindRepeating(
               [](const net::RedirectInfo& redirect_info,
-                 const network::mojom::URLResponseHead& response_head) {
+                 network::mojom::URLResponseHeadPtr response_head) {
                 NOTREACHED();
               }));
 
@@ -806,7 +807,7 @@
               &on_response_complete_loop),
           base::BindRepeating(
               [](const net::RedirectInfo& redirect_info,
-                 const network::mojom::URLResponseHead& response_head) {
+                 network::mojom::URLResponseHeadPtr response_head) {
                 NOTREACHED();
               }));
 
@@ -907,6 +908,9 @@
   base::RunLoop on_head_received_loop;
   base::RunLoop on_response_complete_loop;
 
+  net::RedirectInfo redirect_info;
+  network::mojom::URLResponseHeadPtr redirect_head;
+
   // Create the |PrefetchStreamingURLLoader| that is being tested.
   std::unique_ptr<PrefetchStreamingURLLoader> streaming_loader =
       std::make_unique<PrefetchStreamingURLLoader>(
@@ -926,13 +930,8 @@
                 on_response_complete_loop->Quit();
               },
               &on_response_complete_loop),
-          base::BindRepeating(
-              [](base::RunLoop* on_receive_redirect_loop,
-                 const net::RedirectInfo& redirect_info,
-                 const network::mojom::URLResponseHead& response_head) {
-                on_receive_redirect_loop->Quit();
-              },
-              &on_receive_redirect_loop));
+          CreatePrefetchRedirectCallbackForTest(
+              &on_receive_redirect_loop, &redirect_info, &redirect_head));
 
   if (GetParam()) {
     streaming_loader->SetOnReceivedHeadCallback(
@@ -949,7 +948,8 @@
   on_receive_redirect_loop.Run();
 
   streaming_loader->HandleRedirect(
-      PrefetchStreamingURLLoaderStatus::kFollowRedirect);
+      PrefetchStreamingURLLoaderStatus::kFollowRedirect, redirect_info,
+      std::move(redirect_head));
   on_follow_redirect_loop.Run();
 
   // Simulates receiving the prefetch after the redirect
@@ -1055,6 +1055,9 @@
   base::RunLoop on_receive_redirect_loop;
   base::RunLoop on_head_received_loop;
 
+  net::RedirectInfo redirect_info;
+  network::mojom::URLResponseHeadPtr redirect_head;
+
   // Create the |PrefetchStreamingURLLoader| that is being tested.
   std::unique_ptr<PrefetchStreamingURLLoader> streaming_loader =
       std::make_unique<PrefetchStreamingURLLoader>(
@@ -1068,13 +1071,8 @@
               [](const network::URLLoaderCompletionStatus& completion_status) {
                 NOTREACHED();
               }),
-          base::BindRepeating(
-              [](base::RunLoop* on_receive_redirect_loop,
-                 const net::RedirectInfo& redirect_info,
-                 const network::mojom::URLResponseHead& response_head) {
-                on_receive_redirect_loop->Quit();
-              },
-              &on_receive_redirect_loop));
+          CreatePrefetchRedirectCallbackForTest(
+              &on_receive_redirect_loop, &redirect_info, &redirect_head));
 
   if (GetParam()) {
     streaming_loader->SetOnReceivedHeadCallback(
@@ -1087,7 +1085,8 @@
   on_receive_redirect_loop.Run();
 
   streaming_loader->HandleRedirect(
-      PrefetchStreamingURLLoaderStatus::kFailedInvalidRedirect);
+      PrefetchStreamingURLLoaderStatus::kFailedInvalidRedirect, redirect_info,
+      std::move(redirect_head));
   if (GetParam()) {
     on_head_received_loop.Run();
   }
@@ -1113,6 +1112,9 @@
 
   base::RunLoop on_receive_redirect_loop;
 
+  net::RedirectInfo redirect_info;
+  network::mojom::URLResponseHeadPtr redirect_head;
+
   // Create the |PrefetchStreamingURLLoader| that is being tested.
   std::unique_ptr<PrefetchStreamingURLLoader> streaming_loader =
       std::make_unique<PrefetchStreamingURLLoader>(
@@ -1126,13 +1128,8 @@
               [](const network::URLLoaderCompletionStatus& completion_status) {
                 NOTREACHED();
               }),
-          base::BindRepeating(
-              [](base::RunLoop* on_receive_redirect_loop,
-                 const net::RedirectInfo& redirect_info,
-                 const network::mojom::URLResponseHead& response_head) {
-                on_receive_redirect_loop->Quit();
-              },
-              &on_receive_redirect_loop));
+          CreatePrefetchRedirectCallbackForTest(
+              &on_receive_redirect_loop, &redirect_info, &redirect_head));
 
   if (GetParam()) {
     // When a redirect causes a change in network context, the
@@ -1152,7 +1149,8 @@
   // context. When this happens the streaming_loader will stop the fetch, and a
   // new streaming URL loader would start to fetch the redirect URL.
   streaming_loader->HandleRedirect(
-      PrefetchStreamingURLLoaderStatus::kStopSwitchInNetworkContextForRedirect);
+      PrefetchStreamingURLLoaderStatus::kStopSwitchInNetworkContextForRedirect,
+      redirect_info, std::move(redirect_head));
 
   task_environment()->RunUntilIdle();
   EXPECT_FALSE(test_url_loader_factory()->IsURLLoaderClientConnected());
@@ -1215,6 +1213,9 @@
   base::RunLoop on_receive_redirect_loop;
   base::RunLoop on_head_received_loop;
 
+  net::RedirectInfo redirect_info;
+  network::mojom::URLResponseHeadPtr redirect_head;
+
   // Create the |PrefetchStreamingURLLoader| that is being tested.
   std::unique_ptr<PrefetchStreamingURLLoader> streaming_loader =
       std::make_unique<PrefetchStreamingURLLoader>(
@@ -1228,13 +1229,8 @@
               [](const network::URLLoaderCompletionStatus& completion_status) {
                 NOTREACHED();
               }),
-          base::BindRepeating(
-              [](base::RunLoop* on_receive_redirect_loop,
-                 const net::RedirectInfo& redirect_info,
-                 const network::mojom::URLResponseHead& response_head) {
-                on_receive_redirect_loop->Quit();
-              },
-              &on_receive_redirect_loop));
+          CreatePrefetchRedirectCallbackForTest(
+              &on_receive_redirect_loop, &redirect_info, &redirect_head));
 
   if (GetParam()) {
     streaming_loader->SetOnReceivedHeadCallback(
@@ -1253,7 +1249,8 @@
   task_environment()->RunUntilIdle();
 
   streaming_loader->HandleRedirect(
-      PrefetchStreamingURLLoaderStatus::kFollowRedirect);
+      PrefetchStreamingURLLoaderStatus::kFollowRedirect, redirect_info,
+      std::move(redirect_head));
   if (GetParam()) {
     on_head_received_loop.Run();
   }
@@ -1303,7 +1300,7 @@
               &on_response_complete_loop),
           base::BindRepeating(
               [](const net::RedirectInfo& redirect_info,
-                 const network::mojom::URLResponseHead& response_head) {
+                 network::mojom::URLResponseHeadPtr response_head) {
                 NOTREACHED();
               }));
 
@@ -1368,7 +1365,7 @@
               &on_response_complete_loop),
           base::BindRepeating(
               [](const net::RedirectInfo& redirect_info,
-                 const network::mojom::URLResponseHead& response_head) {
+                 network::mojom::URLResponseHeadPtr response_head) {
                 NOTREACHED();
               }));
 
@@ -1427,7 +1424,7 @@
               &on_response_complete_loop),
           base::BindRepeating(
               [](const net::RedirectInfo& redirect_info,
-                 const network::mojom::URLResponseHead& response_head) {
+                 network::mojom::URLResponseHeadPtr response_head) {
                 NOTREACHED();
               }));
 
@@ -1528,7 +1525,7 @@
               &on_response_complete_loop),
           base::BindRepeating(
               [](const net::RedirectInfo& redirect_info,
-                 const network::mojom::URLResponseHead& response_head) {
+                 network::mojom::URLResponseHeadPtr response_head) {
                 NOTREACHED();
               }));
 
@@ -1596,7 +1593,7 @@
               &on_response_complete_loop),
           base::BindRepeating(
               [](const net::RedirectInfo& redirect_info,
-                 const network::mojom::URLResponseHead& response_head) {
+                 network::mojom::URLResponseHeadPtr response_head) {
                 NOTREACHED();
               }));
 
diff --git a/content/browser/preloading/prefetch/prefetch_test_utils.cc b/content/browser/preloading/prefetch/prefetch_test_utils.cc
index bd89fde0..2335c83 100644
--- a/content/browser/preloading/prefetch/prefetch_test_utils.cc
+++ b/content/browser/preloading/prefetch/prefetch_test_utils.cc
@@ -6,7 +6,6 @@
 
 #include "base/run_loop.h"
 #include "base/time/time.h"
-#include "content/browser/preloading/prefetch/prefetch_streaming_url_loader.h"
 #include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
 #include "services/network/public/mojom/url_response_head.mojom.h"
 #include "services/network/test/test_url_loader_factory.h"
@@ -48,7 +47,7 @@
               &on_response_complete_loop),
           base::BindRepeating(
               [](const net::RedirectInfo& redirect_info,
-                 const network::mojom::URLResponseHead& response_head) {
+                 network::mojom::URLResponseHeadPtr response_head) {
                 NOTREACHED();
               }));
 
@@ -65,6 +64,24 @@
   return streaming_loader;
 }
 
+PrefetchStreamingURLLoader::OnPrefetchRedirectCallback
+CreatePrefetchRedirectCallbackForTest(
+    base::RunLoop* on_receive_redirect_loop,
+    net::RedirectInfo* out_redirect_info,
+    network::mojom::URLResponseHeadPtr* out_redirect_head) {
+  return base::BindRepeating(
+      [](base::RunLoop* on_receive_redirect_loop,
+         net::RedirectInfo* out_redirect_info,
+         network::mojom::URLResponseHeadPtr* out_redirect_head,
+         const net::RedirectInfo& redirect_info,
+         network::mojom::URLResponseHeadPtr redirect_head) {
+        *out_redirect_info = redirect_info;
+        *out_redirect_head = std::move(redirect_head);
+        on_receive_redirect_loop->Quit();
+      },
+      on_receive_redirect_loop, out_redirect_info, out_redirect_head);
+}
+
 std::unique_ptr<PrefetchStreamingURLLoader>
 MakeServableStreamingURLLoaderWithRedirectForTest(const GURL& original_url,
                                                   const GURL& redirect_url) {
@@ -78,6 +95,9 @@
   base::RunLoop on_response_received_loop;
   base::RunLoop on_response_complete_loop;
 
+  net::RedirectInfo redirect_info;
+  network::mojom::URLResponseHeadPtr redirect_head;
+
   std::unique_ptr<PrefetchStreamingURLLoader> streaming_loader =
       std::make_unique<PrefetchStreamingURLLoader>(
           &test_url_loader_factory, std::move(request),
@@ -96,28 +116,25 @@
                 on_response_complete_loop->Quit();
               },
               &on_response_complete_loop),
-          base::BindRepeating(
-              [](base::RunLoop* on_receive_redirect_loop,
-                 const net::RedirectInfo& redirect_info,
-                 const network::mojom::URLResponseHead& response_head) {
-                on_receive_redirect_loop->Quit();
-              },
-              &on_receive_redirect_loop));
+          CreatePrefetchRedirectCallbackForTest(
+              &on_receive_redirect_loop, &redirect_info, &redirect_head));
 
   network::URLLoaderCompletionStatus status(net::OK);
 
-  net::RedirectInfo redirect_info;
-  redirect_info.new_url = redirect_url;
+  net::RedirectInfo original_redirect_info;
+  original_redirect_info.new_url = redirect_url;
 
   network::TestURLLoaderFactory::Redirects redirects;
-  redirects.emplace_back(redirect_info, network::mojom::URLResponseHead::New());
+  redirects.emplace_back(original_redirect_info,
+                         network::mojom::URLResponseHead::New());
 
   test_url_loader_factory.AddResponse(
       original_url, network::mojom::URLResponseHead::New(), "test body", status,
       std::move(redirects), network::TestURLLoaderFactory::kResponseDefault);
   on_receive_redirect_loop.Run();
   streaming_loader->HandleRedirect(
-      PrefetchStreamingURLLoaderStatus::kFollowRedirect);
+      PrefetchStreamingURLLoaderStatus::kFollowRedirect, redirect_info,
+      std::move(redirect_head));
   on_response_received_loop.Run();
   on_response_complete_loop.Run();
 
@@ -139,6 +156,9 @@
 
   base::RunLoop on_receive_redirect_loop;
 
+  net::RedirectInfo redirect_info;
+  network::mojom::URLResponseHeadPtr redirect_head;
+
   // Simulate a PrefetchStreamingURLLoader that receives a redirect that
   // requires a change in a network context. When this happens, it will stop its
   // request, but can be used to serve the redirect. A new
@@ -155,19 +175,15 @@
           [](const network::URLLoaderCompletionStatus& completion_status) {
             NOTREACHED();
           }),
-      base::BindRepeating(
-          [](base::RunLoop* on_receive_redirect_loop,
-             const net::RedirectInfo& redirect_info,
-             const network::mojom::URLResponseHead& response_head) {
-            on_receive_redirect_loop->Quit();
-          },
-          &on_receive_redirect_loop)));
+      CreatePrefetchRedirectCallbackForTest(&on_receive_redirect_loop,
+                                            &redirect_info, &redirect_head)));
 
-  net::RedirectInfo redirect_info;
-  redirect_info.new_url = redirect_url;
+  net::RedirectInfo original_redirect_info;
+  original_redirect_info.new_url = redirect_url;
 
   network::TestURLLoaderFactory::Redirects redirects;
-  redirects.emplace_back(redirect_info, network::mojom::URLResponseHead::New());
+  redirects.emplace_back(original_redirect_info,
+                         network::mojom::URLResponseHead::New());
 
   test_url_loader_factory.AddResponse(
       original_url, nullptr, "", network::URLLoaderCompletionStatus(),
@@ -175,7 +191,8 @@
       network::TestURLLoaderFactory::kResponseOnlyRedirectsNoDestination);
   on_receive_redirect_loop.Run();
   streaming_loaders[0]->HandleRedirect(
-      PrefetchStreamingURLLoaderStatus::kStopSwitchInNetworkContextForRedirect);
+      PrefetchStreamingURLLoaderStatus::kStopSwitchInNetworkContextForRedirect,
+      redirect_info, std::move(redirect_head));
 
   std::unique_ptr<network::ResourceRequest> redirect_request =
       std::make_unique<network::ResourceRequest>();
@@ -202,11 +219,10 @@
             on_response_complete_loop->Quit();
           },
           &on_response_complete_loop),
-      base::BindRepeating(
-          [](const net::RedirectInfo& redirect_info,
-             const network::mojom::URLResponseHead& response_head) {
-            NOTREACHED();
-          })));
+      base::BindRepeating([](const net::RedirectInfo& redirect_info,
+                             network::mojom::URLResponseHeadPtr response_head) {
+        NOTREACHED();
+      })));
 
   network::URLLoaderCompletionStatus status(net::OK);
   test_url_loader_factory.AddResponse(
diff --git a/content/browser/preloading/prefetch/prefetch_test_utils.h b/content/browser/preloading/prefetch/prefetch_test_utils.h
index 2c7da51..42c808e 100644
--- a/content/browser/preloading/prefetch/prefetch_test_utils.h
+++ b/content/browser/preloading/prefetch/prefetch_test_utils.h
@@ -8,17 +8,26 @@
 #include <memory>
 #include <string>
 
+#include "content/browser/preloading/prefetch/prefetch_streaming_url_loader.h"
 #include "services/network/public/mojom/url_response_head.mojom-forward.h"
 #include "url/gurl.h"
 
-namespace content {
+namespace base {
+class RunLoop;
+}  // namespace base
 
-class PrefetchStreamingURLLoader;
+namespace content {
 
 std::unique_ptr<PrefetchStreamingURLLoader>
 MakeServableStreamingURLLoaderForTest(network::mojom::URLResponseHeadPtr head,
                                       const std::string body);
 
+PrefetchStreamingURLLoader::OnPrefetchRedirectCallback
+CreatePrefetchRedirectCallbackForTest(
+    base::RunLoop* on_receive_redirect_loop,
+    net::RedirectInfo* out_redirect_info,
+    network::mojom::URLResponseHeadPtr* out_redirect_head);
+
 std::unique_ptr<PrefetchStreamingURLLoader>
 MakeServableStreamingURLLoaderWithRedirectForTest(const GURL& original_url,
                                                   const GURL& redirect_url);
diff --git a/content/browser/ssl/ssl_manager.cc b/content/browser/ssl/ssl_manager.cc
index 3449d39..020118c6 100644
--- a/content/browser/ssl/ssl_manager.cc
+++ b/content/browser/ssl/ssl_manager.cc
@@ -317,6 +317,14 @@
   OnCertErrorInternal(std::move(handler));
 }
 
+bool SSLManager::HasAllowExceptionForAnyHost() {
+  if (!ssl_host_state_delegate_) {
+    return false;
+  }
+  return ssl_host_state_delegate_->HasAllowExceptionForAnyHost(
+      controller_->frame_tree().GetMainFrame()->GetStoragePartition());
+}
+
 bool SSLManager::DidStartResourceResponse(
     const url::SchemeHostPort& final_response_url,
     bool has_certificate_errors) {
diff --git a/content/browser/ssl/ssl_manager.h b/content/browser/ssl/ssl_manager.h
index 61ec340..e708ee08 100644
--- a/content/browser/ssl/ssl_manager.h
+++ b/content/browser/ssl/ssl_manager.h
@@ -92,6 +92,10 @@
   // An error occurred with the certificate in an SSL connection.
   void OnCertError(std::unique_ptr<SSLErrorHandler> handler);
 
+  // Returns true if any HTTPS-related warning exceptions has been allowed by
+  // the user for any host.
+  bool HasAllowExceptionForAnyHost();
+
  private:
   // Helper method for handling certificate errors.
   void OnCertErrorInternal(std::unique_ptr<SSLErrorHandler> handler);
diff --git a/content/browser/tracing/background_tracing_config_unittest.cc b/content/browser/tracing/background_tracing_config_unittest.cc
index efdafa1..ef49658 100644
--- a/content/browser/tracing/background_tracing_config_unittest.cc
+++ b/content/browser/tracing/background_tracing_config_unittest.cc
@@ -699,4 +699,84 @@
   rule->Uninstall();
 }
 
+TEST_F(BackgroundTracingConfigTest, RepeatingIntervalRuleFromValidProto) {
+  perfetto::protos::gen::TriggerRule config;
+  CreateRuleConfig(
+      R"pb(
+        name: "test_rule"
+        trigger_chance: 0.5
+        delay_ms: 500
+        repeating_interval: { period_ms: 1000 randomized: true }
+      )pb",
+      config);
+  auto rule = BackgroundTracingRule::Create(config);
+  auto result = rule->ToProtoForTesting();
+  EXPECT_EQ("test_rule", result.name());
+  EXPECT_EQ(0.5, result.trigger_chance());
+  EXPECT_EQ(500U, result.delay_ms());
+  EXPECT_TRUE(result.has_repeating_interval());
+  EXPECT_EQ(1000U, result.repeating_interval().period_ms());
+  EXPECT_TRUE(result.repeating_interval().randomized());
+}
+
+TEST_F(BackgroundTracingConfigTest, RepeatingIntervalRuleTriggersAfterDelay) {
+  perfetto::protos::gen::TriggerRule config;
+  CreateRuleConfig(R"pb(
+                     name: "test_rule"
+                     repeating_interval: { period_ms: 2000 }
+                   )pb",
+                   config);
+
+  base::TimeTicks start = base::TimeTicks::Now();
+  auto rule = BackgroundTracingRule::Create(config);
+  std::vector<base::TimeTicks> trigger_times;
+  auto callback = base::BindLambdaForTesting([&](const BackgroundTracingRule*) {
+    trigger_times.push_back(base::TimeTicks::Now());
+    return true;
+  });
+  rule->Install(callback);
+  task_environment_.FastForwardBy(base::Seconds(2));  // Triggers at 2s
+  rule->Uninstall();
+  task_environment_.FastForwardBy(base::Seconds(1));
+  rule->Install(callback);
+  task_environment_.FastForwardBy(base::Seconds(2));  // Triggers at 4s
+  rule->Uninstall();
+  task_environment_.FastForwardBy(base::Seconds(2));  // Skips 6s
+  rule->Install(callback);
+  task_environment_.FastForwardBy(base::Seconds(1));  // Triggers at 8s
+
+  EXPECT_EQ(std::vector<base::TimeTicks>({
+                start + base::Seconds(2),
+                start + base::Seconds(4),
+                start + base::Seconds(8),
+            }),
+            trigger_times);
+  rule->Uninstall();
+}
+
+TEST_F(BackgroundTracingConfigTest, RepeatingIntervalRuleTriggersRandomized) {
+  perfetto::protos::gen::TriggerRule config;
+  CreateRuleConfig(
+      R"pb(
+        name: "test_rule"
+        repeating_interval: { period_ms: 2000 randomized: true }
+      )pb",
+      config);
+
+  base::TimeTicks start = base::TimeTicks::Now();
+  auto rule = BackgroundTracingRule::Create(config);
+  std::vector<base::TimeTicks> trigger_times;
+  auto callback = base::BindLambdaForTesting([&](const BackgroundTracingRule*) {
+    trigger_times.push_back(base::TimeTicks::Now());
+    return true;
+  });
+  rule->Install(callback);
+  task_environment_.FastForwardBy(base::Seconds(4));  // Triggers twice
+  ASSERT_EQ(2U, trigger_times.size());
+  EXPECT_GE(trigger_times[0], start);
+  EXPECT_LT(trigger_times[0], trigger_times[1]);
+  EXPECT_GE(trigger_times[1], start + base::Seconds(2));
+  rule->Uninstall();
+}
+
 }  // namespace content
diff --git a/content/browser/tracing/background_tracing_rule.cc b/content/browser/tracing/background_tracing_rule.cc
index 12362719..dc03fe4 100644
--- a/content/browser/tracing/background_tracing_rule.cc
+++ b/content/browser/tracing/background_tracing_rule.cc
@@ -454,6 +454,107 @@
   }
 };
 
+class RepeatingIntervalRule : public BackgroundTracingRule {
+ private:
+  RepeatingIntervalRule(base::TimeDelta period, bool randomized)
+      : period_(period),
+        randomized_(randomized),
+        interval_phase_(base::TimeTicks::Now()) {}
+
+ public:
+  static std::unique_ptr<BackgroundTracingRule> Create(
+      const perfetto::protos::gen::TriggerRule& config) {
+    DCHECK(config.has_repeating_interval());
+
+    const auto& interval = config.repeating_interval();
+    if (!interval.has_period_ms()) {
+      return nullptr;
+    }
+    return base::WrapUnique<RepeatingIntervalRule>(new RepeatingIntervalRule(
+        base::Milliseconds(interval.period_ms()), interval.randomized()));
+  }
+
+  void DoInstall() override { ScheduleNextTick(); }
+  void DoUninstall() override { timer_.Stop(); }
+
+  perfetto::protos::gen::TriggerRule ToProtoForTesting() const override {
+    perfetto::protos::gen::TriggerRule config =
+        BackgroundTracingRule::ToProtoForTesting();
+    auto* histogram = config.mutable_repeating_interval();
+    histogram->set_period_ms(period_.InMilliseconds());
+    histogram->set_randomized(randomized_);
+    return config;
+  }
+
+  void GenerateMetadataProto(
+      BackgroundTracingRule::MetadataProto* out) const override {
+    DCHECK(out);
+    BackgroundTracingRule::GenerateMetadataProto(out);
+    out->set_trigger_type(MetadataProto::TRIGGER_UNSPECIFIED);
+  }
+
+ protected:
+  std::string GetDefaultRuleId() const override {
+    return "org.chromium.background_tracing.repeating_interval";
+  }
+
+ private:
+  base::TimeTicks GetFireTimeForInterval(base::TimeTicks interval_start) const {
+    if (randomized_) {
+      return interval_start +
+             base::Microseconds(base::RandInt(0, period_.InMicroseconds() - 1));
+    }
+    return interval_start;
+  }
+  base::TimeTicks GetNextIntervalStart(base::TimeTicks now) const {
+    base::TimeTicks next_interval_start =
+        now.SnappedToNextTick(interval_phase_, period_);
+    if (next_interval_start == now) {
+      return next_interval_start + period_;
+    }
+    return next_interval_start;
+  }
+  void UpdateNextFireTime(base::TimeTicks now) {
+    base::TimeTicks next_interval_start = GetNextIntervalStart(now);
+    base::TimeTicks prev_interval_start = next_interval_start - period_;
+    // If the previously scheduled fire time is in a past interval, the current
+    // interval can still fire. Compute a new fire time for the current
+    // interval and keep it if it hasn't passed, otherwise move on to the next
+    // interval.
+    if (randomized_ && (scheduled_fire_time_ < prev_interval_start)) {
+      scheduled_fire_time_ = GetFireTimeForInterval(prev_interval_start);
+      if (scheduled_fire_time_ >= now) {
+        return;
+      }
+    }
+    scheduled_fire_time_ = GetFireTimeForInterval(next_interval_start);
+  }
+
+  void ScheduleNextTick() {
+    base::TimeTicks now = base::TimeTicks::Now();
+    // Update the next fire time only if it's in the past.
+    if (scheduled_fire_time_ <= now) {
+      UpdateNextFireTime(now);
+    }
+
+    timer_.Start(
+        FROM_HERE, scheduled_fire_time_,
+        base::BindOnce(&RepeatingIntervalRule::OnTick, base::Unretained(this)),
+        base::subtle::DelayPolicy::kFlexibleNoSooner);
+  }
+
+  void OnTick() {
+    OnRuleTriggered();
+    ScheduleNextTick();
+  }
+
+  const base::TimeDelta period_;
+  const bool randomized_;
+  const base::TimeTicks interval_phase_;
+  base::TimeTicks scheduled_fire_time_;
+  base::DeadlineTimer timer_;
+};
+
 }  // namespace
 
 std::unique_ptr<BackgroundTracingRule>
@@ -481,6 +582,8 @@
     tracing_rule = NamedTriggerRule::Create(config);
   } else if (config.has_histogram()) {
     tracing_rule = HistogramRule::Create(config);
+  } else if (config.has_repeating_interval()) {
+    tracing_rule = RepeatingIntervalRule::Create(config);
   } else {
     tracing_rule = TimerRule::Create(config);
   }
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index 0a2c737..7d7a6353 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -1723,6 +1723,13 @@
   ColorProviderSourceObserver::Observe(source);
 }
 
+ui::ColorProviderManager::ColorMode WebContentsImpl::GetColorMode() const {
+  // A ColorProviderSource should always be set.
+  auto* source = GetColorProviderSource();
+  CHECK(source);
+  return source->GetColorMode();
+}
+
 void WebContentsImpl::SetAccessibilityMode(ui::AXMode mode) {
   OPTIONAL_TRACE_EVENT2("content", "WebContentsImpl::SetAccessibilityMode",
                         "mode", mode.ToString(), "previous_mode",
@@ -1881,6 +1888,30 @@
   return primary_frame_tree_.root()->current_frame_host()->web_ui();
 }
 
+void WebContentsImpl::SetAlwaysSendSubresourceNotifications() {
+  if (!base::FeatureList::IsEnabled(
+          features::kReduceSubresourceResponseStartedIPC)) {
+    return;
+  }
+
+  if (GetSendSubresourceNotification()) {
+    return;
+  }
+
+  // Updates all the renderers if the user allows certificate error or HTTP
+  // exception, but doesn't update renderers when all exceptions are revoked
+  // from all hosts since this causes superfluous IPCs.
+  for (WebContentsImpl* web_contents : GetAllWebContents()) {
+    DCHECK(!web_contents->GetSendSubresourceNotification());
+    web_contents->renderer_preferences_.send_subresource_notification = true;
+    web_contents->SyncRendererPrefs();
+  }
+}
+
+bool WebContentsImpl::GetSendSubresourceNotification() {
+  return GetRendererPrefs().send_subresource_notification;
+}
+
 void WebContentsImpl::SetUserAgentOverride(
     const blink::UserAgentOverride& ua_override,
     bool override_in_new_tabs) {
@@ -3274,6 +3305,15 @@
   // happens after RenderFrameHostManager::Init.
   NotifySwappedFromRenderManager(nullptr,
                                  GetRenderManager()->current_frame_host());
+
+  // Checks whether the associated ssl_manager has any certificate error or HTTP
+  // exceptions for any host and updates the renderer preferences.
+  if (base::FeatureList::IsEnabled(
+          features::kReduceSubresourceResponseStartedIPC) &&
+      GetController().ssl_manager()->HasAllowExceptionForAnyHost()) {
+    renderer_preferences_.send_subresource_notification = true;
+    SyncRendererPrefs();
+  }
 }
 
 void WebContentsImpl::OnWebContentsDestroyed(WebContentsImpl* web_contents) {
diff --git a/content/browser/web_contents/web_contents_impl.h b/content/browser/web_contents/web_contents_impl.h
index 1c87dc4..3f156cad 100644
--- a/content/browser/web_contents/web_contents_impl.h
+++ b/content/browser/web_contents/web_contents_impl.h
@@ -360,6 +360,7 @@
   absl::optional<SkColor> GetBackgroundColor() override;
   void SetPageBaseBackgroundColor(absl::optional<SkColor> color) override;
   void SetColorProviderSource(ui::ColorProviderSource* source) override;
+  ui::ColorProviderManager::ColorMode GetColorMode() const override;
   WebUI* GetWebUI() override;
   void SetUserAgentOverride(const blink::UserAgentOverride& ua_override,
                             bool override_in_new_tabs) override;
@@ -367,6 +368,8 @@
       NavigationController::UserAgentOverrideOption option) override;
   const blink::UserAgentOverride& GetUserAgentOverride() override;
   bool ShouldOverrideUserAgentForRendererInitiatedNavigation() override;
+  void SetAlwaysSendSubresourceNotifications() override;
+  bool GetSendSubresourceNotification() override;
   void EnableWebContentsOnlyAccessibilityMode() override;
   bool IsWebContentsOnlyAccessibilityModeForTesting() override;
   bool IsFullAccessibilityModeForTesting() override;
diff --git a/content/browser/webid/federated_auth_user_info_request.cc b/content/browser/webid/federated_auth_user_info_request.cc
index d87dfa7b..77c6170 100644
--- a/content/browser/webid/federated_auth_user_info_request.cc
+++ b/content/browser/webid/federated_auth_user_info_request.cc
@@ -44,7 +44,7 @@
     }
     case FederatedAuthUserInfoRequestResult::kInvalidConfigOrWellKnown: {
       return "getUserInfo() failed because the config and well-known files "
-             "resulted were invalid.";
+             "were invalid.";
     }
     case FederatedAuthUserInfoRequestResult::kInvalidAccountsResponse: {
       return "getUserInfo() failed because of an invalid accounts response.";
diff --git a/content/browser/webid/federated_auth_user_info_request_unittest.cc b/content/browser/webid/federated_auth_user_info_request_unittest.cc
index 96902e00..1c96ffd 100644
--- a/content/browser/webid/federated_auth_user_info_request_unittest.cc
+++ b/content/browser/webid/federated_auth_user_info_request_unittest.cc
@@ -438,8 +438,8 @@
       "Blink.FedCm.UserInfo.TimeToRequestCompleted", 0);
 
   ExpectConsoleMessage(
-      "getUserInfo() failed because the config and well-known files resulted "
-      "were invalid.");
+      "getUserInfo() failed because the config and well-known files were "
+      "invalid.");
   ExpectUniqueIssue(
       FederatedAuthUserInfoRequestResult::kInvalidConfigOrWellKnown);
 }
diff --git a/content/public/browser/browser_context.h b/content/public/browser/browser_context.h
index 3c97638..a7d7fab 100644
--- a/content/public/browser/browser_context.h
+++ b/content/public/browser/browser_context.h
@@ -64,11 +64,10 @@
 template <typename>
 class TracedProto;
 
-namespace protos {
-namespace pbzero {
+namespace protos::pbzero {
 class ChromeBrowserContext;
-}
-}  // namespace protos
+}  // namespace protos::pbzero
+
 }  // namespace perfetto
 
 namespace content {
diff --git a/content/public/browser/ssl_host_state_delegate.h b/content/public/browser/ssl_host_state_delegate.h
index 10b3ccbf..b189f4ab 100644
--- a/content/public/browser/ssl_host_state_delegate.h
+++ b/content/public/browser/ssl_host_state_delegate.h
@@ -100,6 +100,11 @@
   virtual bool HasAllowException(const std::string& host,
                                  StoragePartition* storage_partition) = 0;
 
+  // Returns true if the user has allowed a certificate error exception or HTTP
+  // exception for any host.
+  virtual bool HasAllowExceptionForAnyHost(
+      StoragePartition* storage_partition) = 0;
+
  protected:
   virtual ~SSLHostStateDelegate() {}
 };
diff --git a/content/public/browser/web_contents.h b/content/public/browser/web_contents.h
index eb08556..1c02ef7 100644
--- a/content/public/browser/web_contents.h
+++ b/content/public/browser/web_contents.h
@@ -47,6 +47,7 @@
 #include "third_party/skia/include/core/SkColor.h"
 #include "ui/accessibility/ax_mode.h"
 #include "ui/base/cursor/mojom/cursor_type.mojom-shared.h"
+#include "ui/color/color_provider_manager.h"
 #include "ui/display/types/display_constants.h"
 #include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/geometry/size.h"
@@ -525,6 +526,9 @@
   // always return a valid ColorProvider instance.
   virtual const ui::ColorProvider& GetColorProvider() const = 0;
 
+  // Gets the color mode for the ColorProvider associated with this WebContents.
+  virtual ui::ColorProviderManager::ColorMode GetColorMode() const = 0;
+
   // Returns the committed WebUI if one exists.
   virtual WebUI* GetWebUI() = 0;
 
@@ -551,6 +555,11 @@
 
   virtual const blink::UserAgentOverride& GetUserAgentOverride() = 0;
 
+  // Updates all renderers to start sending subresource notifications since a
+  // certificate error or HTTP exception has been allowed by the user.
+  virtual void SetAlwaysSendSubresourceNotifications() = 0;
+  virtual bool GetSendSubresourceNotification() = 0;
+
   // Set the accessibility mode so that accessibility events are forwarded
   // to each WebContentsObserver.
   virtual void EnableWebContentsOnlyAccessibilityMode() = 0;
diff --git a/content/public/common/content_features.cc b/content/public/common/content_features.cc
index 2f63633..02183fe 100644
--- a/content/public/common/content_features.cc
+++ b/content/public/common/content_features.cc
@@ -935,6 +935,18 @@
              "QueueNavigationsWhileWaitingForCommit",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
+// When enabled, sends SubresourceResponseStarted IPC only when the user has
+// allowed any HTTPS-related warning exceptions. From field data, (see
+// `SSL.Experimental.SubresourceResponse`), ~100% of subresource notifications
+// are not required, since allowing certificate exceptions by users is a rare
+// event. Hence, if user has never allowed any certificate or HTTP exceptions,
+// notifications are not sent to the browser. Once we start sending these
+// messages, we keep sending them until all exceptions are revoked and browser
+// restart occurs.
+BASE_FEATURE(kReduceSubresourceResponseStartedIPC,
+             "ReduceSubresourceResponseStartedIPC",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 // Causes hidden tabs with crashed subframes to be marked for reload, meaning
 // that if a user later switches to that tab, the current page will be
 // reloaded.  This will hide crashed subframes from the user at the cost of
diff --git a/content/public/common/content_features.h b/content/public/common/content_features.h
index e03e8dd..ddb5fcb8 100644
--- a/content/public/common/content_features.h
+++ b/content/public/common/content_features.h
@@ -196,6 +196,7 @@
 CONTENT_EXPORT BASE_DECLARE_FEATURE(kProcessSharingWithStrictSiteInstances);
 CONTENT_EXPORT BASE_DECLARE_FEATURE(kPushSubscriptionChangeEvent);
 CONTENT_EXPORT BASE_DECLARE_FEATURE(kQueueNavigationsWhileWaitingForCommit);
+CONTENT_EXPORT BASE_DECLARE_FEATURE(kReduceSubresourceResponseStartedIPC);
 CONTENT_EXPORT BASE_DECLARE_FEATURE(kReloadHiddenTabsWithCrashedSubframes);
 CONTENT_EXPORT BASE_DECLARE_FEATURE(kRenderDocument);
 CONTENT_EXPORT BASE_DECLARE_FEATURE(kRetryGetVideoCaptureDeviceInfos);
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc
index c7b6942..c493bb9 100644
--- a/content/renderer/render_frame_impl.cc
+++ b/content/renderer/render_frame_impl.cc
@@ -149,6 +149,7 @@
 #include "third_party/blink/public/common/navigation/navigation_policy.h"
 #include "third_party/blink/public/common/page_state/page_state.h"
 #include "third_party/blink/public/common/permissions_policy/permissions_policy.h"
+#include "third_party/blink/public/common/renderer_preferences/renderer_preferences.h"
 #include "third_party/blink/public/common/thread_safe_browser_interface_broker_proxy.h"
 #include "third_party/blink/public/mojom/blob/blob.mojom.h"
 #include "third_party/blink/public/mojom/blob/blob_url_store.mojom.h"
@@ -1180,6 +1181,14 @@
   return WindowOpenDisposition::IGNORE_ACTION;
 }
 
+bool ShouldNotifySubresourceResponseStarted(blink::RendererPreferences pref) {
+  if (!base::FeatureList::IsEnabled(
+          features::kReduceSubresourceResponseStartedIPC)) {
+    return true;
+  }
+  return pref.send_subresource_notification;
+}
+
 }  // namespace
 
 RenderFrameImpl::AssertNavigationCommits::AssertNavigationCommits(
@@ -2399,7 +2408,9 @@
     const url::SchemeHostPort& final_response_url,
     network::mojom::URLResponseHeadPtr response_head,
     network::mojom::RequestDestination request_destination) {
-  if (!blink::IsRequestDestinationFrame(request_destination)) {
+  if (!blink::IsRequestDestinationFrame(request_destination) &&
+      ShouldNotifySubresourceResponseStarted(
+          GetWebView()->GetRendererPreferences())) {
     GetFrameHost()->SubresourceResponseStarted(final_response_url,
                                                response_head->cert_status);
   }
diff --git a/content/services/auction_worklet/BUILD.gn b/content/services/auction_worklet/BUILD.gn
index d62f627..9817006b 100644
--- a/content/services/auction_worklet/BUILD.gn
+++ b/content/services/auction_worklet/BUILD.gn
@@ -32,8 +32,6 @@
 
 source_set("auction_worklet") {
   sources = [
-    "auction_downloader.cc",
-    "auction_downloader.h",
     "auction_v8_devtools_agent.cc",
     "auction_v8_devtools_agent.h",
     "auction_v8_devtools_session.cc",
@@ -104,7 +102,10 @@
     "//v8",
   ]
 
-  public_deps = [ "public/mojom" ]
+  public_deps = [
+    "public/cpp",
+    "public/mojom",
+  ]
 }
 
 # See comment at the top of //content/BUILD.gn for how this works.
@@ -129,11 +130,11 @@
   }
 
   sources = [
-    "auction_downloader_unittest.cc",
     "auction_v8_inspector_util_unittest.cc",
     "context_recycler_unittest.cc",
     "debug_command_queue_unittest.cc",
     "direct_from_seller_signals_requester_unittest.cc",
+    "public/cpp/auction_downloader_unittest.cc",
     "seller_worklet_unittest.cc",
     "trusted_signals_request_manager_unittest.cc",
     "trusted_signals_unittest.cc",
diff --git a/content/services/auction_worklet/direct_from_seller_signals_requester.cc b/content/services/auction_worklet/direct_from_seller_signals_requester.cc
index 221fdf1..d4ef09b 100644
--- a/content/services/auction_worklet/direct_from_seller_signals_requester.cc
+++ b/content/services/auction_worklet/direct_from_seller_signals_requester.cc
@@ -19,8 +19,8 @@
 #include "base/strings/stringprintf.h"
 #include "base/task/sequenced_task_runner.h"
 #include "base/time/time.h"
-#include "content/services/auction_worklet/auction_downloader.h"
 #include "content/services/auction_worklet/auction_v8_helper.h"
+#include "content/services/auction_worklet/public/cpp/auction_downloader.h"
 #include "net/http/http_response_headers.h"
 #include "services/network/public/mojom/url_loader_factory.mojom-forward.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
@@ -229,6 +229,7 @@
         signals_url,
         CoalescedDownload(std::make_unique<AuctionDownloader>(
             &url_loader_factory, signals_url,
+            AuctionDownloader::DownloadMode::kActualDownload,
             AuctionDownloader::MimeType::kJson,
             base::BindOnce(
                 &DirectFromSellerSignalsRequester::OnSignalsDownloaded,
diff --git a/content/services/auction_worklet/public/cpp/BUILD.gn b/content/services/auction_worklet/public/cpp/BUILD.gn
new file mode 100644
index 0000000..a9ef467
--- /dev/null
+++ b/content/services/auction_worklet/public/cpp/BUILD.gn
@@ -0,0 +1,21 @@
+source_set("cpp") {
+  sources = [
+    "auction_downloader.cc",
+    "auction_downloader.h",
+  ]
+
+  configs += [
+    "//build/config/compiler:wexit_time_destructors",
+    "//content:content_implementation",
+  ]
+
+  deps = [
+    "//base",
+    "//content:export",
+    "//content/common",
+    "//mojo/public/cpp/bindings",
+    "//net",
+    "//services/network/public/cpp",
+    "//url",
+  ]
+}
diff --git a/content/services/auction_worklet/auction_downloader.cc b/content/services/auction_worklet/public/cpp/auction_downloader.cc
similarity index 91%
rename from content/services/auction_worklet/auction_downloader.cc
rename to content/services/auction_worklet/public/cpp/auction_downloader.cc
index 540a873..8f9066d 100644
--- a/content/services/auction_worklet/auction_downloader.cc
+++ b/content/services/auction_worklet/public/cpp/auction_downloader.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "content/services/auction_worklet/auction_downloader.h"
+#include "content/services/auction_worklet/public/cpp/auction_downloader.h"
 
 #include <memory>
 #include <string>
@@ -39,6 +39,15 @@
             "Requested when running a FLEDGE auction."
           data: "URL associated with an interest group or seller."
           destination: WEBSITE
+          user_data: {
+            type: SENSITIVE_URL
+          }
+          internal {
+            contacts {
+              email: "privacy-sandbox-dev@chromium.org"
+            }
+          }
+          last_reviewed: "2023-06-12"
         }
         policy {
           cookies_allowed: NO
@@ -144,6 +153,7 @@
 AuctionDownloader::AuctionDownloader(
     network::mojom::URLLoaderFactory* url_loader_factory,
     const GURL& source_url,
+    DownloadMode download_mode,
     MimeType mime_type,
     AuctionDownloaderCallback auction_downloader_callback)
     : source_url_(source_url),
@@ -185,13 +195,30 @@
   simple_url_loader_->SetTimeoutDuration(base::Seconds(30));
 
   // TODO(mmenke): Consider limiting the size of response bodies.
-  simple_url_loader_->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
-      url_loader_factory, base::BindOnce(&AuctionDownloader::OnBodyReceived,
-                                         base::Unretained(this)));
+  if (download_mode == DownloadMode::kActualDownload) {
+    simple_url_loader_->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
+        url_loader_factory, base::BindOnce(&AuctionDownloader::OnBodyReceived,
+                                           base::Unretained(this)));
+  } else {
+    simple_url_loader_->DownloadHeadersOnly(
+        url_loader_factory,
+        base::BindOnce(&AuctionDownloader::OnHeadersOnlyReceived,
+                       base::Unretained(this)));
+  }
 }
 
 AuctionDownloader::~AuctionDownloader() = default;
 
+void AuctionDownloader::OnHeadersOnlyReceived(
+    scoped_refptr<net::HttpResponseHeaders> headers) {
+  // Pretend to have a response with empty body on success, so we can share the
+  // code with the actually-downloading path.
+  // (It will get the headers out of SimpleUrlLoader)
+  OnBodyReceived(headers && simple_url_loader_->NetError() == net::OK
+                     ? std::make_unique<std::string>()
+                     : nullptr);
+}
+
 void AuctionDownloader::OnBodyReceived(std::unique_ptr<std::string> body) {
   DCHECK(auction_downloader_callback_);
 
@@ -294,8 +321,9 @@
       "data", [&](perfetto::TracedValue dest) {
         perfetto::TracedDictionary dict = std::move(dest).WriteDictionary();
         dict.Add("requestId", GetRequestId());
-        if (response_head.headers)
+        if (response_head.headers) {
           dict.Add("statusCode", response_head.headers->response_code());
+        }
         dict.Add("mimeType", response_head.mime_type);
         dict.Add("encodedDataLength", response_head.encoded_data_length);
 
@@ -335,8 +363,9 @@
 }
 
 std::string AuctionDownloader::GetRequestId() {
-  if (!request_id_.has_value())
+  if (!request_id_.has_value()) {
     request_id_ = base::UnguessableToken::Create();
+  }
   return request_id_->ToString();
 }
 
@@ -352,8 +381,9 @@
         dict.Add("didFail", failure);
         dict.Add("encodedDataLength", encoded_data_length);
         dict.Add("decodedBodyLength", decoded_body_length);
-        if (!completion_time.is_null())
+        if (!completion_time.is_null()) {
           dict.Add("finishTime", completion_time.since_origin().InSecondsF());
+        }
       });
 }
 
diff --git a/content/services/auction_worklet/auction_downloader.h b/content/services/auction_worklet/public/cpp/auction_downloader.h
similarity index 81%
rename from content/services/auction_worklet/auction_downloader.h
rename to content/services/auction_worklet/public/cpp/auction_downloader.h
index d62a9ab..3a10e0f2 100644
--- a/content/services/auction_worklet/auction_downloader.h
+++ b/content/services/auction_worklet/public/cpp/auction_downloader.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CONTENT_SERVICES_AUCTION_WORKLET_AUCTION_DOWNLOADER_H_
-#define CONTENT_SERVICES_AUCTION_WORKLET_AUCTION_DOWNLOADER_H_
+#ifndef CONTENT_SERVICES_AUCTION_WORKLET_PUBLIC_CPP_AUCTION_DOWNLOADER_H_
+#define CONTENT_SERVICES_AUCTION_WORKLET_PUBLIC_CPP_AUCTION_DOWNLOADER_H_
 
 #include <memory>
 #include <string>
@@ -36,6 +36,16 @@
     kWebAssembly,
   };
 
+  // This determines how the downloaded data is handled.
+  enum class DownloadMode {
+    // The data is collected in memory and passed to the callback.
+    kActualDownload,
+
+    // The data is discarded as it comes in and the callback is invoked with an
+    // empty string.
+    kSimulatedDownload
+  };
+
   // Passes in nullptr on failure. Always invoked asynchronously. Will not be
   // invoked after the AuctionDownloader is destroyed.
   using AuctionDownloaderCallback =
@@ -47,6 +57,7 @@
   // asynchronously once the data has been fetched or an error has occurred.
   AuctionDownloader(network::mojom::URLLoaderFactory* url_loader_factory,
                     const GURL& source_url,
+                    DownloadMode download_mode,
                     MimeType mime_type,
                     AuctionDownloaderCallback auction_downloader_callback);
   explicit AuctionDownloader(const AuctionDownloader&) = delete;
@@ -56,6 +67,8 @@
   const GURL& source_url() const { return source_url_; }
 
  private:
+  void OnHeadersOnlyReceived(scoped_refptr<net::HttpResponseHeaders> headers);
+
   void OnBodyReceived(std::unique_ptr<std::string> body);
 
   void OnRedirect(const GURL& url_before_redirect,
@@ -81,4 +94,4 @@
 
 }  // namespace auction_worklet
 
-#endif  // CONTENT_SERVICES_AUCTION_WORKLET_AUCTION_DOWNLOADER_H_
+#endif  // CONTENT_SERVICES_AUCTION_WORKLET_PUBLIC_CPP_AUCTION_DOWNLOADER_H_
diff --git a/content/services/auction_worklet/auction_downloader_unittest.cc b/content/services/auction_worklet/public/cpp/auction_downloader_unittest.cc
similarity index 80%
rename from content/services/auction_worklet/auction_downloader_unittest.cc
rename to content/services/auction_worklet/public/cpp/auction_downloader_unittest.cc
index 760f6811..60cef0d7 100644
--- a/content/services/auction_worklet/auction_downloader_unittest.cc
+++ b/content/services/auction_worklet/public/cpp/auction_downloader_unittest.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "content/services/auction_worklet/auction_downloader.h"
+#include "content/services/auction_worklet/public/cpp/auction_downloader.h"
 
 #include <string>
 #include <utility>
@@ -31,16 +31,19 @@
 const char kAsciiCharset[] = "us-ascii";
 const char kUtf8Charset[] = "utf-8";
 
-class AuctionDownloaderTest : public testing::Test {
+class AuctionDownloaderTest
+    : public testing::TestWithParam<AuctionDownloader::DownloadMode> {
  public:
   AuctionDownloaderTest() = default;
   ~AuctionDownloaderTest() override = default;
 
+  AuctionDownloader::DownloadMode download_mode() { return GetParam(); }
+
   std::unique_ptr<std::string> RunRequest() {
     DCHECK(!run_loop_);
 
     AuctionDownloader downloader(
-        &url_loader_factory_, url_, mime_type_,
+        &url_loader_factory_, url_, download_mode(), mime_type_,
         base::BindOnce(&AuctionDownloaderTest::DownloadCompleteCallback,
                        base::Unretained(this)));
 
@@ -52,6 +55,13 @@
     return std::move(body_);
   }
 
+  std::string EmptyIfSimulated(std::string in) {
+    return download_mode() ==
+                   AuctionDownloader::DownloadMode::kSimulatedDownload
+               ? std::string()
+               : in;
+  }
+
   // Helper to avoid checking has_value all over the place.
   std::string last_error_msg() const {
     return error_.value_or("Not an error.");
@@ -85,7 +95,7 @@
   network::TestURLLoaderFactory url_loader_factory_;
 };
 
-TEST_F(AuctionDownloaderTest, NetworkError) {
+TEST_P(AuctionDownloaderTest, NetworkError) {
   network::URLLoaderCompletionStatus status;
   status.error_code = net::ERR_FAILED;
   url_loader_factory_.AddResponse(url_, /*head=*/nullptr, kAsciiResponseBody,
@@ -97,7 +107,7 @@
 }
 
 // HTTP 404 responses are trested as failures.
-TEST_F(AuctionDownloaderTest, HttpError) {
+TEST_P(AuctionDownloaderTest, HttpError) {
   // This is an unlikely response for an error case, but should fail if it ever
   // happens.
   AddResponse(&url_loader_factory_, url_, kJavascriptMimeType, kUtf8Charset,
@@ -108,7 +118,7 @@
       last_error_msg());
 }
 
-TEST_F(AuctionDownloaderTest, Timeout) {
+TEST_P(AuctionDownloaderTest, Timeout) {
   AddResponse(&url_loader_factory_, url_, kJavascriptMimeType, kUtf8Charset,
               kAsciiResponseBody, kAllowFledgeHeader,
               net::HTTP_REQUEST_TIMEOUT);
@@ -119,7 +129,7 @@
       last_error_msg());
 }
 
-TEST_F(AuctionDownloaderTest, AllowFledge) {
+TEST_P(AuctionDownloaderTest, AllowFledge) {
   AddResponse(&url_loader_factory_, url_, kJavascriptMimeType, kUtf8Charset,
               kAsciiResponseBody, "X-Allow-FLEDGE: true");
   EXPECT_TRUE(RunRequest());
@@ -203,7 +213,7 @@
       last_error_msg());
 }
 
-TEST_F(AuctionDownloaderTest, PassesHeaders) {
+TEST_P(AuctionDownloaderTest, PassesHeaders) {
   std::string allow_fledge_string;
   std::string data_version_string;
 
@@ -245,7 +255,7 @@
 }
 
 // Redirect responses are treated as failures.
-TEST_F(AuctionDownloaderTest, Redirect) {
+TEST_P(AuctionDownloaderTest, Redirect) {
   // None of these fields actually matter for this test, but a bit strange for
   // them not to be populated.
   net::RedirectInfo redirect_info;
@@ -264,16 +274,16 @@
             last_error_msg());
 }
 
-TEST_F(AuctionDownloaderTest, Success) {
+TEST_P(AuctionDownloaderTest, Success) {
   AddResponse(&url_loader_factory_, url_, kJavascriptMimeType, kUtf8Charset,
               kAsciiResponseBody);
   std::unique_ptr<std::string> body = RunRequest();
   ASSERT_TRUE(body);
-  EXPECT_EQ(kAsciiResponseBody, *body);
+  EXPECT_EQ(EmptyIfSimulated(kAsciiResponseBody), *body);
 }
 
 // Test `AuctionDownloader::MimeType` values work as expected.
-TEST_F(AuctionDownloaderTest, MimeType) {
+TEST_P(AuctionDownloaderTest, MimeType) {
   // Javascript request, JSON response type.
   AddResponse(&url_loader_factory_, url_, kJsonMimeType, kUtf8Charset,
               kAsciiResponseBody);
@@ -351,10 +361,10 @@
               kAsciiResponseBody);
   std::unique_ptr<std::string> body = RunRequest();
   ASSERT_TRUE(body);
-  EXPECT_EQ(kAsciiResponseBody, *body);
+  EXPECT_EQ(EmptyIfSimulated(kAsciiResponseBody), *body);
 }
 
-TEST_F(AuctionDownloaderTest, MimeTypeWasm) {
+TEST_P(AuctionDownloaderTest, MimeTypeWasm) {
   mime_type_ = AuctionDownloader::MimeType::kWebAssembly;
 
   // WASM request, Javascript response type.
@@ -389,14 +399,14 @@
               /*charset=*/absl::nullopt, kNonUtf8ResponseBody);
   std::unique_ptr<std::string> body = RunRequest();
   ASSERT_TRUE(body);
-  EXPECT_EQ(kNonUtf8ResponseBody, *body);
+  EXPECT_EQ(EmptyIfSimulated(kNonUtf8ResponseBody), *body);
 
   // Mimetypes are case insensitive.
   AddResponse(&url_loader_factory_, url_, "Application/WasM",
               /*charset=*/absl::nullopt, kNonUtf8ResponseBody);
   body = RunRequest();
   ASSERT_TRUE(body);
-  EXPECT_EQ(kNonUtf8ResponseBody, *body);
+  EXPECT_EQ(EmptyIfSimulated(kNonUtf8ResponseBody), *body);
 
   // No charset should be used (it's a binary format, after all).
   AddResponse(&url_loader_factory_, url_, kWasmMimeType,
@@ -418,9 +428,9 @@
       last_error_msg());
 }
 
-// Test all Javscript and JSON MIME type strings.
-TEST_F(AuctionDownloaderTest, MimeTypeVariants) {
-  // All supported Javscript MIME types, copied from blink's mime_util.cc.
+// Test all Javascript and JSON MIME type strings.
+TEST_P(AuctionDownloaderTest, MimeTypeVariants) {
+  // All supported Javascript MIME types, copied from blink's mime_util.cc.
   const char* kJavascriptMimeTypes[] = {
       "application/ecmascript",
       "application/javascript",
@@ -453,7 +463,7 @@
                 kAsciiResponseBody);
     std::unique_ptr<std::string> body = RunRequest();
     ASSERT_TRUE(body);
-    EXPECT_EQ(kAsciiResponseBody, *body);
+    EXPECT_EQ(EmptyIfSimulated(kAsciiResponseBody), *body);
 
     mime_type_ = AuctionDownloader::MimeType::kJson;
     AddResponse(&url_loader_factory_, url_, javascript_type, kUtf8Charset,
@@ -480,11 +490,11 @@
                 kAsciiResponseBody);
     std::unique_ptr<std::string> body = RunRequest();
     ASSERT_TRUE(body);
-    EXPECT_EQ(kAsciiResponseBody, *body);
+    EXPECT_EQ(EmptyIfSimulated(kAsciiResponseBody), *body);
   }
 }
 
-TEST_F(AuctionDownloaderTest, Charset) {
+TEST_P(AuctionDownloaderTest, Charset) {
   mime_type_ = AuctionDownloader::MimeType::kJson;
   // Unknown/unsupported charsets should result in failure.
   AddResponse(&url_loader_factory_, url_, kJsonMimeType, "fred",
@@ -502,87 +512,117 @@
       last_error_msg());
 
   // ASCII charset should restrict response bodies to ASCII characters.
+  // (Not relevant in kSimulatedDownload since that doesn't have a body).
   mime_type_ = AuctionDownloader::MimeType::kJavascript;
-  AddResponse(&url_loader_factory_, url_, kJavascriptMimeType, kAsciiCharset,
-              kUtf8ResponseBody);
-  EXPECT_FALSE(RunRequest());
-  EXPECT_EQ(
-      "Rejecting load of https://url.test/script.js due to unexpected charset.",
-      last_error_msg());
+  if (download_mode() != AuctionDownloader::DownloadMode::kSimulatedDownload) {
+    AddResponse(&url_loader_factory_, url_, kJavascriptMimeType, kAsciiCharset,
+                kUtf8ResponseBody);
+    EXPECT_FALSE(RunRequest());
+    EXPECT_EQ(
+        "Rejecting load of https://url.test/script.js due to unexpected "
+        "charset.",
+        last_error_msg());
+  }
 
   AddResponse(&url_loader_factory_, url_, kJavascriptMimeType, kAsciiCharset,
               kAsciiResponseBody);
   std::unique_ptr<std::string> body = RunRequest();
   ASSERT_TRUE(body);
-  EXPECT_EQ(kAsciiResponseBody, *body);
-  AddResponse(&url_loader_factory_, url_, kJavascriptMimeType, kAsciiCharset,
-              kUtf8ResponseBody);
-  EXPECT_FALSE(RunRequest());
-  EXPECT_EQ(
-      "Rejecting load of https://url.test/script.js due to unexpected charset.",
-      last_error_msg());
+  EXPECT_EQ(EmptyIfSimulated(kAsciiResponseBody), *body);
 
-  AddResponse(&url_loader_factory_, url_, kJavascriptMimeType, kAsciiCharset,
-              kNonUtf8ResponseBody);
-  EXPECT_FALSE(RunRequest());
-  EXPECT_EQ(
-      "Rejecting load of https://url.test/script.js due to unexpected charset.",
-      last_error_msg());
+  // (Not relevant in kSimulatedDownload since that doesn't have a body).
+  if (download_mode() != AuctionDownloader::DownloadMode::kSimulatedDownload) {
+    AddResponse(&url_loader_factory_, url_, kJavascriptMimeType, kAsciiCharset,
+                kUtf8ResponseBody);
+    EXPECT_FALSE(RunRequest());
+    EXPECT_EQ(
+        "Rejecting load of https://url.test/script.js due to unexpected "
+        "charset.",
+        last_error_msg());
+
+    AddResponse(&url_loader_factory_, url_, kJavascriptMimeType, kAsciiCharset,
+                kNonUtf8ResponseBody);
+    EXPECT_FALSE(RunRequest());
+    EXPECT_EQ(
+        "Rejecting load of https://url.test/script.js due to unexpected "
+        "charset.",
+        last_error_msg());
+  }
 
   // UTF-8 charset should restrict response bodies to valid UTF-8 characters.
   AddResponse(&url_loader_factory_, url_, kJavascriptMimeType, kUtf8Charset,
               kAsciiResponseBody);
   body = RunRequest();
   ASSERT_TRUE(body);
-  EXPECT_EQ(kAsciiResponseBody, *body);
+  EXPECT_EQ(EmptyIfSimulated(kAsciiResponseBody), *body);
   AddResponse(&url_loader_factory_, url_, kJavascriptMimeType, kUtf8Charset,
               kUtf8ResponseBody);
   body = RunRequest();
   ASSERT_TRUE(body);
-  EXPECT_EQ(kUtf8ResponseBody, *body);
-  AddResponse(&url_loader_factory_, url_, kJavascriptMimeType, kUtf8Charset,
-              kNonUtf8ResponseBody);
-  EXPECT_FALSE(RunRequest());
-  EXPECT_EQ(
-      "Rejecting load of https://url.test/script.js due to unexpected charset.",
-      last_error_msg());
+  EXPECT_EQ(EmptyIfSimulated(kUtf8ResponseBody), *body);
+
+  // (Not relevant in kSimulatedDownload since that doesn't have a body).
+  if (download_mode() != AuctionDownloader::DownloadMode::kSimulatedDownload) {
+    AddResponse(&url_loader_factory_, url_, kJavascriptMimeType, kUtf8Charset,
+                kNonUtf8ResponseBody);
+    EXPECT_FALSE(RunRequest());
+    EXPECT_EQ(
+        "Rejecting load of https://url.test/script.js due to unexpected "
+        "charset.",
+        last_error_msg());
+  }
 
   // Null charset should act like UTF-8.
   AddResponse(&url_loader_factory_, url_, kJavascriptMimeType, absl::nullopt,
               kAsciiResponseBody);
   body = RunRequest();
   ASSERT_TRUE(body);
-  EXPECT_EQ(kAsciiResponseBody, *body);
+  EXPECT_EQ(EmptyIfSimulated(kAsciiResponseBody), *body);
   AddResponse(&url_loader_factory_, url_, kJavascriptMimeType, absl::nullopt,
               kUtf8ResponseBody);
   body = RunRequest();
   ASSERT_TRUE(body);
-  EXPECT_EQ(kUtf8ResponseBody, *body);
-  AddResponse(&url_loader_factory_, url_, kJavascriptMimeType, absl::nullopt,
-              kNonUtf8ResponseBody);
-  EXPECT_FALSE(RunRequest());
-  EXPECT_EQ(
-      "Rejecting load of https://url.test/script.js due to unexpected charset.",
-      last_error_msg());
+  EXPECT_EQ(EmptyIfSimulated(kUtf8ResponseBody), *body);
+
+  // (Not relevant in kSimulatedDownload since that doesn't have a body).
+  if (download_mode() != AuctionDownloader::DownloadMode::kSimulatedDownload) {
+    AddResponse(&url_loader_factory_, url_, kJavascriptMimeType, absl::nullopt,
+                kNonUtf8ResponseBody);
+    EXPECT_FALSE(RunRequest());
+    EXPECT_EQ(
+        "Rejecting load of https://url.test/script.js due to unexpected "
+        "charset.",
+        last_error_msg());
+  }
 
   // Empty charset should act like UTF-8.
   AddResponse(&url_loader_factory_, url_, kJavascriptMimeType, "",
               kAsciiResponseBody);
   body = RunRequest();
   ASSERT_TRUE(body);
-  EXPECT_EQ(kAsciiResponseBody, *body);
+  EXPECT_EQ(EmptyIfSimulated(kAsciiResponseBody), *body);
   AddResponse(&url_loader_factory_, url_, kJavascriptMimeType, "",
               kUtf8ResponseBody);
   body = RunRequest();
   ASSERT_TRUE(body);
-  EXPECT_EQ(kUtf8ResponseBody, *body);
-  AddResponse(&url_loader_factory_, url_, kJavascriptMimeType, "",
-              kNonUtf8ResponseBody);
-  EXPECT_FALSE(RunRequest());
-  EXPECT_EQ(
-      "Rejecting load of https://url.test/script.js due to unexpected charset.",
-      last_error_msg());
+  EXPECT_EQ(EmptyIfSimulated(kUtf8ResponseBody), *body);
+  // (Not relevant in kSimulatedDownload since that doesn't have a body).
+  if (download_mode() != AuctionDownloader::DownloadMode::kSimulatedDownload) {
+    AddResponse(&url_loader_factory_, url_, kJavascriptMimeType, "",
+                kNonUtf8ResponseBody);
+    EXPECT_FALSE(RunRequest());
+    EXPECT_EQ(
+        "Rejecting load of https://url.test/script.js due to unexpected "
+        "charset.",
+        last_error_msg());
+  }
 }
 
+INSTANTIATE_TEST_SUITE_P(
+    /* no label */,
+    AuctionDownloaderTest,
+    testing::Values(AuctionDownloader::DownloadMode::kActualDownload,
+                    AuctionDownloader::DownloadMode::kSimulatedDownload));
+
 }  // namespace
 }  // namespace auction_worklet
diff --git a/content/services/auction_worklet/trusted_signals.cc b/content/services/auction_worklet/trusted_signals.cc
index 839a170..1690840 100644
--- a/content/services/auction_worklet/trusted_signals.cc
+++ b/content/services/auction_worklet/trusted_signals.cc
@@ -20,8 +20,8 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/stringprintf.h"
 #include "base/task/sequenced_task_runner.h"
-#include "content/services/auction_worklet/auction_downloader.h"
 #include "content/services/auction_worklet/auction_v8_helper.h"
+#include "content/services/auction_worklet/public/cpp/auction_downloader.h"
 #include "gin/converter.h"
 #include "net/base/parse_number.h"
 #include "services/network/public/mojom/url_loader_factory.mojom-forward.h"
@@ -445,7 +445,9 @@
     const GURL& full_signals_url) {
   download_start_time_ = base::TimeTicks::Now();
   auction_downloader_ = std::make_unique<AuctionDownloader>(
-      url_loader_factory, full_signals_url, AuctionDownloader::MimeType::kJson,
+      url_loader_factory, full_signals_url,
+      AuctionDownloader::DownloadMode::kActualDownload,
+      AuctionDownloader::MimeType::kJson,
       base::BindOnce(&TrustedSignals::OnDownloadComplete,
                      base::Unretained(this)));
 }
diff --git a/content/services/auction_worklet/worklet_loader.cc b/content/services/auction_worklet/worklet_loader.cc
index 541d6c2..e9a3a9d3 100644
--- a/content/services/auction_worklet/worklet_loader.cc
+++ b/content/services/auction_worklet/worklet_loader.cc
@@ -14,8 +14,8 @@
 #include "base/functional/bind.h"
 #include "base/functional/callback.h"
 #include "base/task/sequenced_task_runner.h"
-#include "content/services/auction_worklet/auction_downloader.h"
 #include "content/services/auction_worklet/auction_v8_helper.h"
+#include "content/services/auction_worklet/public/cpp/auction_downloader.h"
 #include "url/gurl.h"
 #include "v8/include/v8-context.h"
 #include "v8/include/v8-forward.h"
@@ -83,7 +83,8 @@
          mime_type == AuctionDownloader::MimeType::kWebAssembly);
 
   auction_downloader_ = std::make_unique<AuctionDownloader>(
-      url_loader_factory, source_url, mime_type,
+      url_loader_factory, source_url,
+      AuctionDownloader::DownloadMode::kActualDownload, mime_type,
       base::BindOnce(&WorkletLoaderBase::OnDownloadComplete,
                      base::Unretained(this)));
 }
diff --git a/content/services/auction_worklet/worklet_loader.h b/content/services/auction_worklet/worklet_loader.h
index 204910d..f155879 100644
--- a/content/services/auction_worklet/worklet_loader.h
+++ b/content/services/auction_worklet/worklet_loader.h
@@ -17,8 +17,8 @@
 #include "base/task/sequenced_task_runner.h"
 #include "base/time/time.h"
 #include "content/common/content_export.h"
-#include "content/services/auction_worklet/auction_downloader.h"
 #include "content/services/auction_worklet/auction_v8_helper.h"
+#include "content/services/auction_worklet/public/cpp/auction_downloader.h"
 #include "services/network/public/mojom/url_loader_factory.mojom-forward.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "url/gurl.h"
diff --git a/content/services/auction_worklet/worklet_test_util.cc b/content/services/auction_worklet/worklet_test_util.cc
index 58bd440..355c705 100644
--- a/content/services/auction_worklet/worklet_test_util.cc
+++ b/content/services/auction_worklet/worklet_test_util.cc
@@ -10,8 +10,8 @@
 #include "base/strings/strcat.h"
 #include "base/strings/stringprintf.h"
 #include "base/synchronization/waitable_event.h"
-#include "content/services/auction_worklet/auction_downloader.h"
 #include "content/services/auction_worklet/auction_v8_helper.h"
+#include "content/services/auction_worklet/public/cpp/auction_downloader.h"
 #include "net/http/http_response_headers.h"
 #include "net/http/http_status_code.h"
 #include "net/http/http_util.h"
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index 5cc8ee4..d27590a 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -2838,6 +2838,8 @@
     bundle_deps = [
       ":content_shell_pak_bundle_data",
       ":content_test_bundle_data",
+      ":content_test_perfetto_bundle_data",
+      ":content_test_proto_bundle_data",
       "//media/test:media_bundle_data",
     ]
   }
@@ -3001,16 +3003,20 @@
   ]
 
   data_deps = [
-    ":test_proto_descriptor",
     "//testing/buildbot/filters:content_unittests_filters",
     "//third_party/mesa_headers",
-    "//third_party/perfetto/protos/perfetto/config/chrome:scenario_descriptor",
   ]
 
-  data += [
-    "$root_gen_dir/third_party/perfetto/protos/perfetto/config/chrome/scenario_config.descriptor",
-    "$root_gen_dir/content/test/test_proto.descriptor",
-  ]
+  if (!is_ios) {
+    data_deps += [
+      ":test_proto_descriptor",
+      "//third_party/perfetto/protos/perfetto/config/chrome:scenario_descriptor",
+    ]
+    data += [
+      "$root_gen_dir/third_party/perfetto/protos/perfetto/config/chrome/scenario_config.descriptor",
+      "$root_gen_dir/content/test/test_proto.descriptor",
+    ]
+  }
 
   # Platforms where sqlite_dev_shell is defined.
   if (is_win || is_mac || is_linux || is_chromeos) {
@@ -3307,6 +3313,23 @@
     outputs = [ "{{bundle_resources_dir}}/content_shell.pak" ]
   }
 
+  bundle_data("content_test_proto_bundle_data") {
+    testonly = true
+    public_deps = [ ":test_proto_descriptor" ]
+    sources = [ "$root_gen_dir/content/test/test_proto.descriptor" ]
+    outputs =
+        [ "{{bundle_resources_dir}}/gen/content/test/{{source_file_part}}" ]
+  }
+
+  bundle_data("content_test_perfetto_bundle_data") {
+    testonly = true
+    _relative_path = "third_party/perfetto/protos/perfetto/config/chrome"
+    public_deps = [ "//third_party/perfetto/protos/perfetto/config/chrome:scenario_descriptor" ]
+    sources = [ "$root_gen_dir/$_relative_path/scenario_config.descriptor" ]
+    outputs =
+        [ "{{bundle_resources_dir}}/gen/$_relative_path/{{source_file_part}}" ]
+  }
+
   bundle_data_from_filelist("content_test_bundle_data") {
     testonly = true
     filelist_name = "content_test_bundle_data.filelist"
diff --git a/content/test/gpu/gpu_tests/gpu_integration_test.py b/content/test/gpu/gpu_tests/gpu_integration_test.py
index 39559ab..3f71eda 100644
--- a/content/test/gpu/gpu_tests/gpu_integration_test.py
+++ b/content/test/gpu/gpu_tests/gpu_integration_test.py
@@ -500,11 +500,14 @@
   # pylint: enable=no-self-use
 
   def _RunGpuTest(self, url: str, test_name: str, args: ct.TestArgs) -> None:
-    expected_results, should_retry_on_failure = (
-        self.GetExpectationsForTest()[:2])
-    should_retry_on_failure = (
-        should_retry_on_failure
-        or self._DetermineFirstTestRetryWorkaround(test_name))
+    def _GetExpectedResultsAndShouldRetry():
+      expected_results, should_retry_on_failure = (
+          self.GetExpectationsForTest()[:2])
+      should_retry_on_failure = (
+          should_retry_on_failure
+          or self._DetermineFirstTestRetryWorkaround(test_name))
+      return expected_results, should_retry_on_failure
+
     expected_crashes = {}
     try:
       expected_crashes = self.GetExpectedCrashes(args)
@@ -515,6 +518,12 @@
       # pylint: enable=attribute-defined-outside-init
       raise
     except Exception as e:
+      # We get these values here instead of at the beginning of the function
+      # because it's possible that RunActualGpuTest() will restart the browser
+      # with new browser args, causing any expectation-related data from before
+      # then to become invalid due to different typ tags.
+      (expected_results,
+       should_retry_on_failure) = _GetExpectedResultsAndShouldRetry()
       if not should_retry_on_failure and self._DetermineRetryWorkaround(e):
         should_retry_on_failure = True
         # Notify typ that it should retry this test.
@@ -528,6 +537,8 @@
         self._HandleUnexpectedFailure(test_name)
       raise
     else:
+      (expected_results,
+       should_retry_on_failure) = _GetExpectedResultsAndShouldRetry()
       self._HandlePass(test_name, expected_crashes, expected_results)
 
   def _HandleExpectedFailureOrFlake(self, test_name: str,
diff --git a/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt
index 4e771b0..5f0ea8b 100644
--- a/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt
@@ -580,12 +580,6 @@
 # These video tests are flakey on many configurations.
 crbug.com/1347077 [ android-nexus-5x android-nougat angle-opengles passthrough qualcomm renderer-skia-gl target-cpu-64 ] conformance/textures/misc/video-rotation.html [ Failure ]
 
-# Overlay scheduling issue can leave destroyed overlays around for tests that follow these. Creating confusing flakes.
-# Seen on Pixel-6 and Samsung, skipping generically for now.
-# Occurring with both passthrough and validating command decoders.
-crbug.com/1445548 [ android ] conformance/extensions/oes-texture-float-with-video.html [ Skip ]
-crbug.com/1445548 [ android ] conformance/extensions/oes-texture-half-float-with-video.html [ Skip ]
-
 ## Nexus 5X ##
 
 # Was timing out randomly on android_optional_gpu_tests_rel, but became so
diff --git a/content/test/mock_ssl_host_state_delegate.cc b/content/test/mock_ssl_host_state_delegate.cc
index a692701..954888f 100644
--- a/content/test/mock_ssl_host_state_delegate.cc
+++ b/content/test/mock_ssl_host_state_delegate.cc
@@ -102,4 +102,9 @@
   return exceptions_.find(host) != exceptions_.end();
 }
 
+bool MockSSLHostStateDelegate::HasAllowExceptionForAnyHost(
+    StoragePartition* storage_partition) {
+  return !exceptions_.empty();
+}
+
 }  // namespace content
diff --git a/content/test/mock_ssl_host_state_delegate.h b/content/test/mock_ssl_host_state_delegate.h
index 0a9507ef..264a872 100644
--- a/content/test/mock_ssl_host_state_delegate.h
+++ b/content/test/mock_ssl_host_state_delegate.h
@@ -54,6 +54,9 @@
   bool HasAllowException(const std::string& host,
                          StoragePartition* storage_partition) override;
 
+  bool HasAllowExceptionForAnyHost(
+      StoragePartition* storage_partition) override;
+
  private:
   std::set<std::string> exceptions_;
   std::set<std::string> hosts_ran_insecure_content_;
diff --git a/device/BUILD.gn b/device/BUILD.gn
index 59de793..bfe0aa3 100644
--- a/device/BUILD.gn
+++ b/device/BUILD.gn
@@ -18,34 +18,6 @@
 
 is_linux_without_udev = (is_linux || is_chromeos) && !use_udev
 
-if (is_mac) {
-  # TODO(https://crbug.com/1280317): Merge back into device_unittests once all
-  # .mm files are ARCed.
-  source_set("device_unittests_arc") {
-    testonly = true
-    sources = [
-      "fido/mac/authenticator_unittest.mm",
-      "fido/mac/browsing_data_deletion_unittest.mm",
-      "fido/mac/credential_store_unittest.mm",
-      "fido/mac/get_assertion_operation_unittest_mac.mm",
-      "fido/mac/icloud_keychain_unittest.mm",
-      "fido/mac/make_credential_operation_unittest_mac.mm",
-    ]
-    configs += [ "//build/config/compiler:enable_arc" ]
-    deps = [
-      "//base/test:test_support",
-      "//device/base:base",
-      "//device/fido",
-      "//device/fido:icloud_keychain_test_support",
-      "//device/fido:mocks",
-      "//device/fido:test_support",
-      "//services/data_decoder/public/cpp:test_support",
-    ]
-
-    data_deps = [ "fido/strings:fido_test_strings" ]
-  }
-}
-
 test("device_unittests") {
   sources = [
     "base/synchronization/one_writer_seqlock_unittest.cc",
@@ -107,6 +79,7 @@
       "bluetooth/test/mock_bluetooth_central_manager_mac.h",
       "bluetooth/test/mock_bluetooth_central_manager_mac.mm",
     ]
+    configs += [ "//build/config/compiler:enable_arc" ]
   }
 
   if (is_mac) {
@@ -170,10 +143,6 @@
     "//url",
   ]
 
-  if (is_mac) {
-    deps += [ ":device_unittests_arc" ]
-  }
-
   data_deps = [
     "bluetooth/strings:bluetooth_test_strings",
     "//testing/buildbot/filters:device_unittests_filters",
@@ -239,11 +208,16 @@
 
   if (is_mac) {
     sources += [
-      # TODO(https://crbug.com/1280317): Put the fido/mac/*.mm files from
-      # device_unittests_arc back here.
+      "fido/mac/authenticator_unittest.mm",
+      "fido/mac/browsing_data_deletion_unittest.mm",
       "fido/mac/credential_metadata_unittest.cc",
+      "fido/mac/credential_store_unittest.mm",
+      "fido/mac/get_assertion_operation_unittest_mac.mm",
+      "fido/mac/icloud_keychain_unittest.mm",
+      "fido/mac/make_credential_operation_unittest_mac.mm",
       "fido/mac/util_unittest.cc",
     ]
+    deps += [ "//device/fido:icloud_keychain_test_support" ]
   }
 
   if (is_win) {
diff --git a/device/bluetooth/BUILD.gn b/device/bluetooth/BUILD.gn
index 642afd7..d075d1f 100644
--- a/device/bluetooth/BUILD.gn
+++ b/device/bluetooth/BUILD.gn
@@ -240,6 +240,7 @@
       "IOKit.framework",
       "Foundation.framework",
     ]
+    configs += [ "//build/config/compiler:enable_arc" ]
   }
 
   if (is_mac) {
diff --git a/device/bluetooth/bluetooth_adapter_ios.mm b/device/bluetooth/bluetooth_adapter_ios.mm
index 630da30..93d411b 100644
--- a/device/bluetooth/bluetooth_adapter_ios.mm
+++ b/device/bluetooth/bluetooth_adapter_ios.mm
@@ -4,6 +4,10 @@
 
 #include "device/bluetooth/bluetooth_adapter_ios.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 namespace device {
 
 // static
diff --git a/device/bluetooth/bluetooth_adapter_mac.h b/device/bluetooth/bluetooth_adapter_mac.h
index fb4fe2e..4f5d698 100644
--- a/device/bluetooth/bluetooth_adapter_mac.h
+++ b/device/bluetooth/bluetooth_adapter_mac.h
@@ -10,7 +10,6 @@
 #include <memory>
 #include <string>
 
-#include "base/mac/scoped_nsobject.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/memory/weak_ptr.h"
 #include "base/task/single_thread_task_runner.h"
@@ -97,7 +96,7 @@
   void InitForTest(
       scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner) override;
   BluetoothLowEnergyAdapterApple::GetDevicePairedStatusCallback
-  GetDevicePariedStatus() const override;
+  GetDevicePairedStatus() const override;
 
   // Queries the state of the IOBluetoothHostController.
   HostControllerState GetHostControllerState();
diff --git a/device/bluetooth/bluetooth_adapter_mac.mm b/device/bluetooth/bluetooth_adapter_mac.mm
index 2c5387fb..79bf091 100644
--- a/device/bluetooth/bluetooth_adapter_mac.mm
+++ b/device/bluetooth/bluetooth_adapter_mac.mm
@@ -37,6 +37,10 @@
 #include "device/bluetooth/bluetooth_socket_mac.h"
 #include "device/bluetooth/public/cpp/bluetooth_address.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 extern "C" {
 // Undocumented IOBluetooth Preference API [1]. Used by `blueutil` [2] and
 // `Karabiner` [3] to programmatically control the Bluetooth state. Calling the
@@ -273,7 +277,7 @@
 }
 
 BluetoothLowEnergyAdapterApple::GetDevicePairedStatusCallback
-BluetoothAdapterMac::GetDevicePariedStatus() const {
+BluetoothAdapterMac::GetDevicePairedStatus() const {
   return device_paired_status_callback_;
 }
 
diff --git a/device/bluetooth/bluetooth_adapter_mac_unittest.mm b/device/bluetooth/bluetooth_adapter_mac_unittest.mm
index a070614..8a98591 100644
--- a/device/bluetooth/bluetooth_adapter_mac_unittest.mm
+++ b/device/bluetooth/bluetooth_adapter_mac_unittest.mm
@@ -14,6 +14,10 @@
 #import "device/bluetooth/test/test_bluetooth_adapter_observer.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 namespace device {
 
 class BluetoothAdapterMacTest : public testing::Test {
diff --git a/device/bluetooth/bluetooth_advertisement_mac.h b/device/bluetooth/bluetooth_advertisement_mac.h
index f803eee..f32691e 100644
--- a/device/bluetooth/bluetooth_advertisement_mac.h
+++ b/device/bluetooth/bluetooth_advertisement_mac.h
@@ -5,10 +5,9 @@
 #ifndef DEVICE_BLUETOOTH_BLUETOOTH_ADVERTISEMENT_MAC_H_
 #define DEVICE_BLUETOOTH_BLUETOOTH_ADVERTISEMENT_MAC_H_
 
-#include "base/memory/raw_ptr.h"
-
 #import <CoreBluetooth/CoreBluetooth.h>
 
+#include "base/memory/raw_ptr.h"
 #include "base/task/single_thread_task_runner.h"
 #include "dbus/object_path.h"
 #include "device/bluetooth/bluetooth_adapter.h"
diff --git a/device/bluetooth/bluetooth_advertisement_mac.mm b/device/bluetooth/bluetooth_advertisement_mac.mm
index 0014beef..64667c8b 100644
--- a/device/bluetooth/bluetooth_advertisement_mac.mm
+++ b/device/bluetooth/bluetooth_advertisement_mac.mm
@@ -8,6 +8,10 @@
 #import "base/task/single_thread_task_runner.h"
 #include "device/bluetooth/bluetooth_adapter_mac.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 namespace device {
 
 BluetoothAdvertisementMac::BluetoothAdvertisementMac(
diff --git a/device/bluetooth/bluetooth_channel_mac.h b/device/bluetooth/bluetooth_channel_mac.h
index 9c70f1db..d4ea3783d 100644
--- a/device/bluetooth/bluetooth_channel_mac.h
+++ b/device/bluetooth/bluetooth_channel_mac.h
@@ -5,13 +5,17 @@
 #ifndef DEVICE_BLUETOOTH_BLUETOOTH_CHANNEL_MAC_H_
 #define DEVICE_BLUETOOTH_BLUETOOTH_CHANNEL_MAC_H_
 
-#include "base/memory/raw_ptr.h"
-
 #import <IOKit/IOReturn.h>
 #include <stdint.h>
 
 #include <string>
 
+#include "base/memory/raw_ptr.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 @class IOBluetoothDevice;
 
 namespace device {
diff --git a/device/bluetooth/bluetooth_channel_mac.mm b/device/bluetooth/bluetooth_channel_mac.mm
index 38aa279..69a02d2 100644
--- a/device/bluetooth/bluetooth_channel_mac.mm
+++ b/device/bluetooth/bluetooth_channel_mac.mm
@@ -9,12 +9,15 @@
 #include "base/check.h"
 #include "device/bluetooth/bluetooth_classic_device_mac.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 namespace device {
 
 BluetoothChannelMac::BluetoothChannelMac() : socket_(nullptr) {}
 
-BluetoothChannelMac::~BluetoothChannelMac() {
-}
+BluetoothChannelMac::~BluetoothChannelMac() = default;
 
 void BluetoothChannelMac::SetSocket(BluetoothSocketMac* socket) {
   DCHECK(!socket_);
diff --git a/device/bluetooth/bluetooth_classic_device_mac.h b/device/bluetooth/bluetooth_classic_device_mac.h
index c98d8696..7b1ea92 100644
--- a/device/bluetooth/bluetooth_classic_device_mac.h
+++ b/device/bluetooth/bluetooth_classic_device_mac.h
@@ -10,11 +10,14 @@
 
 #include <string>
 
-#include "base/mac/scoped_nsobject.h"
 #include "base/time/time.h"
 #include "device/bluetooth/bluetooth_device_mac.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 @class IOBluetoothDevice;
 
 namespace device {
@@ -98,7 +101,7 @@
   int GetHostTransmitPower(
       BluetoothHCITransmitPowerLevelType power_level_type) const;
 
-  base::scoped_nsobject<IOBluetoothDevice> device_;
+  IOBluetoothDevice* __strong device_;
 };
 
 }  // namespace device
diff --git a/device/bluetooth/bluetooth_classic_device_mac.mm b/device/bluetooth/bluetooth_classic_device_mac.mm
index c1b5ed7..6cee3fc6 100644
--- a/device/bluetooth/bluetooth_classic_device_mac.mm
+++ b/device/bluetooth/bluetooth_classic_device_mac.mm
@@ -18,6 +18,10 @@
 #include "device/bluetooth/public/cpp/bluetooth_address.h"
 #include "device/bluetooth/public/cpp/bluetooth_uuid.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 // Undocumented API for accessing the Bluetooth transmit power level.
 // Similar to the API defined here [ http://goo.gl/20Q5vE ].
 @interface IOBluetoothHostController (UndocumentedAPI)
@@ -65,12 +69,11 @@
 BluetoothClassicDeviceMac::BluetoothClassicDeviceMac(
     BluetoothAdapterMac* adapter,
     IOBluetoothDevice* device)
-    : BluetoothDeviceMac(adapter), device_([device retain]) {
+    : BluetoothDeviceMac(adapter), device_(device) {
   UpdateTimestamp();
 }
 
-BluetoothClassicDeviceMac::~BluetoothClassicDeviceMac() {
-}
+BluetoothClassicDeviceMac::~BluetoothClassicDeviceMac() = default;
 
 uint32_t BluetoothClassicDeviceMac::GetBluetoothClass() const {
   return [device_ classOfDevice];
@@ -258,8 +261,7 @@
     ConnectToServiceCallback callback,
     ConnectToServiceErrorCallback error_callback) {
   scoped_refptr<BluetoothSocketMac> socket = BluetoothSocketMac::CreateSocket();
-  socket->Connect(device_.get(), uuid,
-                  base::BindOnce(std::move(callback), socket),
+  socket->Connect(device_, uuid, base::BindOnce(std::move(callback), socket),
                   std::move(error_callback));
 }
 
diff --git a/device/bluetooth/bluetooth_device_mac.h b/device/bluetooth/bluetooth_device_mac.h
index fd56644..5213f55 100644
--- a/device/bluetooth/bluetooth_device_mac.h
+++ b/device/bluetooth/bluetooth_device_mac.h
@@ -8,6 +8,10 @@
 #include "device/bluetooth/bluetooth_device.h"
 #include "device/bluetooth/bluetooth_gatt_service.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 @class NSError;
 
 namespace device {
@@ -33,7 +37,7 @@
   virtual bool IsLowEnergyDevice() = 0;
 
  protected:
-  BluetoothDeviceMac(BluetoothAdapter* adapter);
+  explicit BluetoothDeviceMac(BluetoothAdapter* adapter);
 };
 
 }  // namespace device
diff --git a/device/bluetooth/bluetooth_device_mac.mm b/device/bluetooth/bluetooth_device_mac.mm
index 3f45b4d..68a537e 100644
--- a/device/bluetooth/bluetooth_device_mac.mm
+++ b/device/bluetooth/bluetooth_device_mac.mm
@@ -8,6 +8,10 @@
 
 #include "device/bluetooth/bluetooth_adapter.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 static NSString* const kConnectErrorDomain = @"ConnectErrorCode";
 static NSString* const kGattErrorDomain = @"GattErrorCode";
 
@@ -16,8 +20,7 @@
 BluetoothDeviceMac::BluetoothDeviceMac(BluetoothAdapter* adapter)
     : BluetoothDevice(adapter) {}
 
-BluetoothDeviceMac::~BluetoothDeviceMac() {
-}
+BluetoothDeviceMac::~BluetoothDeviceMac() = default;
 
 NSError* BluetoothDeviceMac::GetNSErrorFromConnectErrorCode(
     BluetoothDevice::ConnectErrorCode error_code) {
diff --git a/device/bluetooth/bluetooth_discovery_manager_mac.h b/device/bluetooth/bluetooth_discovery_manager_mac.h
index 637b7e5..aee2b75 100644
--- a/device/bluetooth/bluetooth_discovery_manager_mac.h
+++ b/device/bluetooth/bluetooth_discovery_manager_mac.h
@@ -30,7 +30,7 @@
     virtual void ClassicDiscoveryStopped(bool unexpected) = 0;
 
    protected:
-    virtual ~Observer() {}
+    virtual ~Observer() = default;
   };
 
   BluetoothDiscoveryManagerMac(const BluetoothDiscoveryManagerMac&) = delete;
diff --git a/device/bluetooth/bluetooth_discovery_manager_mac.mm b/device/bluetooth/bluetooth_discovery_manager_mac.mm
index a8e6e0e2..6d8e7f6 100644
--- a/device/bluetooth/bluetooth_discovery_manager_mac.mm
+++ b/device/bluetooth/bluetooth_discovery_manager_mac.mm
@@ -4,14 +4,15 @@
 
 #include "device/bluetooth/bluetooth_discovery_manager_mac.h"
 
-#include "base/memory/raw_ptr.h"
-
-#import <IOBluetooth/objc/IOBluetoothDevice.h>
-#import <IOBluetooth/objc/IOBluetoothDeviceInquiry.h>
+#import <IOBluetooth/IOBluetooth.h>
 
 #include "base/check_op.h"
 #include "base/logging.h"
-#include "base/mac/scoped_nsobject.h"
+#include "base/memory/raw_ptr.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
 
 namespace device {
 
@@ -40,8 +41,6 @@
  public:
   explicit BluetoothDiscoveryManagerMacClassic(Observer* observer)
       : BluetoothDiscoveryManagerMac(observer),
-        should_do_discovery_(false),
-        inquiry_running_(false),
         inquiry_delegate_(
             [[BluetoothDeviceInquiryDelegate alloc] initWithManager:this]),
         inquiry_([[IOBluetoothDeviceInquiry alloc]
@@ -52,7 +51,7 @@
   BluetoothDiscoveryManagerMacClassic& operator=(
       const BluetoothDiscoveryManagerMacClassic&) = delete;
 
-  ~BluetoothDiscoveryManagerMacClassic() override {}
+  ~BluetoothDiscoveryManagerMacClassic() override = default;
 
   // BluetoothDiscoveryManagerMac override.
   bool IsDiscovering() const override { return should_do_discovery_; }
@@ -178,14 +177,14 @@
 
  private:
   // The requested discovery state.
-  bool should_do_discovery_;
+  bool should_do_discovery_ = false;
 
   // The current inquiry state.
-  bool inquiry_running_;
+  bool inquiry_running_ = false;
 
   // Objective-C objects for running and tracking device inquiry.
-  base::scoped_nsobject<BluetoothDeviceInquiryDelegate> inquiry_delegate_;
-  base::scoped_nsobject<IOBluetoothDeviceInquiry> inquiry_;
+  BluetoothDeviceInquiryDelegate* __strong inquiry_delegate_;
+  IOBluetoothDeviceInquiry* __strong inquiry_;
 };
 
 BluetoothDiscoveryManagerMac::BluetoothDiscoveryManagerMac(
@@ -193,8 +192,7 @@
   DCHECK(observer);
 }
 
-BluetoothDiscoveryManagerMac::~BluetoothDiscoveryManagerMac() {
-}
+BluetoothDiscoveryManagerMac::~BluetoothDiscoveryManagerMac() = default;
 
 // static
 BluetoothDiscoveryManagerMac* BluetoothDiscoveryManagerMac::CreateClassic(
diff --git a/device/bluetooth/bluetooth_l2cap_channel_mac.h b/device/bluetooth/bluetooth_l2cap_channel_mac.h
index 7111da7c..3e09dd74 100644
--- a/device/bluetooth/bluetooth_l2cap_channel_mac.h
+++ b/device/bluetooth/bluetooth_l2cap_channel_mac.h
@@ -12,9 +12,12 @@
 
 #include <memory>
 
-#include "base/mac/scoped_nsobject.h"
 #include "device/bluetooth/bluetooth_channel_mac.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 @class BluetoothL2capChannelDelegate;
 
 namespace device {
@@ -23,7 +26,6 @@
  public:
   // Creates a new L2CAP channel wrapper with the given |socket| and native
   // |channel|.
-  // NOTE: The |channel| is expected to already be retained.
   BluetoothL2capChannelMac(BluetoothSocketMac* socket,
                            IOBluetoothL2CAPChannel* channel);
 
@@ -60,10 +62,10 @@
 
  private:
   // The wrapped native L2CAP channel.
-  base::scoped_nsobject<IOBluetoothL2CAPChannel> channel_;
+  IOBluetoothL2CAPChannel* __strong channel_;
 
   // The delegate for the native channel.
-  base::scoped_nsobject<BluetoothL2capChannelDelegate> delegate_;
+  BluetoothL2capChannelDelegate* __strong delegate_;
 };
 
 }  // namespace device
diff --git a/device/bluetooth/bluetooth_l2cap_channel_mac.mm b/device/bluetooth/bluetooth_l2cap_channel_mac.mm
index ed9a1d3..1a343a2 100644
--- a/device/bluetooth/bluetooth_l2cap_channel_mac.mm
+++ b/device/bluetooth/bluetooth_l2cap_channel_mac.mm
@@ -11,6 +11,10 @@
 #include "device/bluetooth/bluetooth_classic_device_mac.h"
 #include "device/bluetooth/bluetooth_socket_mac.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 // A simple delegate class for an open L2CAP channel that forwards methods to
 // its wrapped |channel_|.
 @interface BluetoothL2capChannelDelegate
@@ -78,19 +82,15 @@
     IOReturn* status) {
   DCHECK(socket);
   std::unique_ptr<BluetoothL2capChannelMac> channel(
-      new BluetoothL2capChannelMac(socket, nil));
+      new BluetoothL2capChannelMac(socket, /*channel=*/nil));
 
-  // Retain the delegate, because IOBluetoothDevice's
-  // |-openL2CAPChannelAsync:withPSM:delegate:| assumes that it can take
-  // ownership of the delegate without calling |-retain| on it...
   DCHECK(channel->delegate_);
-  [channel->delegate_ retain];
   IOBluetoothL2CAPChannel* l2cap_channel;
   *status = [device openL2CAPChannelAsync:&l2cap_channel
                                   withPSM:psm
                                  delegate:channel->delegate_];
   if (*status == kIOReturnSuccess)
-    channel->channel_.reset([l2cap_channel retain]);
+    channel->channel_ = l2cap_channel;
   else
     channel.reset();
 
@@ -105,8 +105,7 @@
   // Now that the socket is set, it's safe to associate a delegate, which can
   // call back to the socket.
   DCHECK(!delegate_);
-  delegate_.reset(
-      [[BluetoothL2capChannelDelegate alloc] initWithChannel:this]);
+  delegate_ = [[BluetoothL2capChannelDelegate alloc] initWithChannel:this];
   [channel_ setDelegate:delegate_];
 }
 
diff --git a/device/bluetooth/bluetooth_low_energy_adapter_apple.h b/device/bluetooth/bluetooth_low_energy_adapter_apple.h
index 9709d1b1..daaeab83 100644
--- a/device/bluetooth/bluetooth_low_energy_adapter_apple.h
+++ b/device/bluetooth/bluetooth_low_energy_adapter_apple.h
@@ -11,7 +11,6 @@
 #include <unordered_map>
 #include <vector>
 
-#include "base/mac/scoped_nsobject.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/task/single_thread_task_runner.h"
 #include "device/bluetooth/bluetooth_adapter.h"
@@ -22,6 +21,10 @@
 #include "device/bluetooth/bluetooth_low_energy_discovery_manager_mac.h"
 #include "device/bluetooth/public/cpp/bluetooth_uuid.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 @class CBUUID;
 
 @class BluetoothLowEnergyCentralManagerDelegate;
@@ -111,7 +114,7 @@
   virtual void LazyInitialize();
   virtual void InitForTest(
       scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner);
-  virtual GetDevicePairedStatusCallback GetDevicePariedStatus() const;
+  virtual GetDevicePairedStatusCallback GetDevicePairedStatus() const;
   virtual base::WeakPtr<BluetoothLowEnergyAdapterApple>
   GetLowEnergyWeakPtr() = 0;
 
@@ -200,13 +203,13 @@
       low_energy_advertisement_manager_;
 
   // Underlying CoreBluetooth CBCentralManager and its delegate.
-  base::scoped_nsobject<CBCentralManager> low_energy_central_manager_;
-  base::scoped_nsobject<BluetoothLowEnergyCentralManagerDelegate>
+  CBCentralManager* __strong low_energy_central_manager_;
+  BluetoothLowEnergyCentralManagerDelegate* __strong
       low_energy_central_manager_delegate_;
 
   // Underlying CoreBluetooth CBPeripheralManager and its delegate.
-  base::scoped_nsobject<CBPeripheralManager> low_energy_peripheral_manager_;
-  base::scoped_nsobject<BluetoothLowEnergyPeripheralManagerDelegate>
+  CBPeripheralManager* __strong low_energy_peripheral_manager_;
+  BluetoothLowEnergyPeripheralManagerDelegate* __strong
       low_energy_peripheral_manager_delegate_;
 
   // Watches system file /Library/Preferences/com.apple.Bluetooth.plist to
diff --git a/device/bluetooth/bluetooth_low_energy_adapter_apple.mm b/device/bluetooth/bluetooth_low_energy_adapter_apple.mm
index 391e619..40c6232 100644
--- a/device/bluetooth/bluetooth_low_energy_adapter_apple.mm
+++ b/device/bluetooth/bluetooth_low_energy_adapter_apple.mm
@@ -37,6 +37,10 @@
 #include "device/bluetooth/bluetooth_low_energy_peripheral_manager_delegate.h"
 #include "device/bluetooth/public/cpp/bluetooth_address.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 namespace device {
 
 // static
@@ -84,7 +88,7 @@
   // Set low_energy_central_manager_ to nil so no devices will try to use it
   // while being destroyed after this method. |devices_| is owned by
   // BluetoothAdapter.
-  low_energy_central_manager_.reset();
+  low_energy_central_manager_ = nil;
 }
 
 std::string BluetoothLowEnergyAdapterApple::GetAddress() const {
@@ -217,14 +221,14 @@
     return;
   }
 
-  low_energy_central_manager_.reset([[CBCentralManager alloc]
+  low_energy_central_manager_ = [[CBCentralManager alloc]
       initWithDelegate:low_energy_central_manager_delegate_
-                 queue:dispatch_get_main_queue()]);
+                 queue:dispatch_get_main_queue()];
   low_energy_discovery_manager_->SetCentralManager(low_energy_central_manager_);
 
-  low_energy_peripheral_manager_.reset([[CBPeripheralManager alloc]
+  low_energy_peripheral_manager_ = [[CBPeripheralManager alloc]
       initWithDelegate:low_energy_peripheral_manager_delegate_
-                 queue:dispatch_get_main_queue()]);
+                 queue:dispatch_get_main_queue()];
 
   lazy_initialized_ = true;
 
@@ -269,8 +273,7 @@
 void BluetoothLowEnergyAdapterApple::SetCentralManagerForTesting(
     CBCentralManager* central_manager) {
   central_manager.delegate = low_energy_central_manager_delegate_;
-  low_energy_central_manager_.reset(central_manager,
-                                    base::scoped_policy::RETAIN);
+  low_energy_central_manager_ = central_manager;
   low_energy_discovery_manager_->SetCentralManager(low_energy_central_manager_);
 }
 
@@ -350,7 +353,7 @@
 }
 
 BluetoothLowEnergyAdapterApple::GetDevicePairedStatusCallback
-BluetoothLowEnergyAdapterApple::GetDevicePariedStatus() const {
+BluetoothLowEnergyAdapterApple::GetDevicePairedStatus() const {
   return base::NullCallbackAs<bool(const std::string&)>();
 }
 
@@ -614,8 +617,8 @@
     return false;
   }
 
-  if (GetDevicePariedStatus()) {
-    return GetDevicePariedStatus().Run(it->second);
+  if (GetDevicePairedStatus()) {
+    return GetDevicePairedStatus().Run(it->second);
   }
   return true;
 }
diff --git a/device/bluetooth/bluetooth_low_energy_adapter_apple_unittest.mm b/device/bluetooth/bluetooth_low_energy_adapter_apple_unittest.mm
index 6277c5a..2747142 100644
--- a/device/bluetooth/bluetooth_low_energy_adapter_apple_unittest.mm
+++ b/device/bluetooth/bluetooth_low_energy_adapter_apple_unittest.mm
@@ -16,7 +16,6 @@
 #include "base/memory/ptr_util.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/ref_counted.h"
-#import "base/task/sequenced_task_runner.h"
 #include "base/task/sequenced_task_runner.h"
 #include "base/test/bind.h"
 #include "base/test/task_environment.h"
@@ -39,6 +38,10 @@
 #include "device/bluetooth/bluetooth_adapter_mac.h"
 #endif
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 namespace {
 
 const char kTestPropertyListFileName[] = "test_property_list_file.plist";
@@ -48,7 +51,7 @@
 const char kTestHashAddress[] = "D1:6F:E3:22:FD:5B";
 const int kTestRssi = 0;
 
-NSDictionary* CreateTestPropertyListData() {
+NSDictionary* TestPropertyListData() {
   return @{
     @"CoreBluetoothCache" : @{
       @"00000000-1111-2222-3333-444444444444" : @{
@@ -97,7 +100,7 @@
 
     void ReadBluetoothPropertyListFile() override {
       low_energy_device_list_updated_callback().Run(
-          ParseBluetoothDevicePropertyListData(CreateTestPropertyListData()));
+          ParseBluetoothDevicePropertyListData(TestPropertyListData()));
     }
 
     base::WeakPtrFactory<FakeBluetoothLowEnergyDeviceWatcherMac>
@@ -156,17 +159,16 @@
   }
 
   CBPeripheral* CreateMockPeripheral(const char* identifier) {
-    base::scoped_nsobject<MockCBPeripheral> mock_peripheral(
-        [[MockCBPeripheral alloc] initWithUTF8StringIdentifier:identifier]);
-    return [[mock_peripheral peripheral] retain];
+    MockCBPeripheral* mock_peripheral =
+        [[MockCBPeripheral alloc] initWithUTF8StringIdentifier:identifier];
+    return [mock_peripheral peripheral];
   }
 
   NSDictionary* AdvertisementData() {
-    NSDictionary* advertisement_data = @{
+    return @{
       CBAdvertisementDataIsConnectable : @YES,
       CBAdvertisementDataServiceDataKey : @{},
     };
-    return [advertisement_data retain];
   }
 
   std::string GetHashAddress(CBPeripheral* peripheral) {
@@ -178,14 +180,14 @@
   bool DevicePresent(CBPeripheral* peripheral) {
     BluetoothDevice* device = adapter_low_energy_->GetDevice(
         BluetoothLowEnergyDeviceMac::GetPeripheralHashAddress(peripheral));
-    return (device != NULL);
+    return device;
   }
 
   bool SetMockCentralManager(CBManagerState desired_state) {
-    mock_central_manager_.reset([[MockCentralManager alloc] init]);
-    [mock_central_manager_ setState:desired_state];
+    mock_central_manager_ = [[MockCentralManager alloc] init];
+    mock_central_manager_.state = desired_state;
     CBCentralManager* centralManager =
-        static_cast<CBCentralManager*>(mock_central_manager_.get());
+        static_cast<CBCentralManager*>(mock_central_manager_);
     adapter_low_energy_->SetCentralManagerForTesting(centralManager);
     return true;
   }
@@ -223,7 +225,7 @@
   std::vector<std::unique_ptr<BluetoothDiscoverySession>> active_sessions_;
 
   // Owned by |adapter_low_energy_|.
-  base::scoped_nsobject<MockCentralManager> mock_central_manager_;
+  MockCentralManager* __strong mock_central_manager_;
 
   int callback_count_;
   int error_callback_count_;
@@ -353,8 +355,7 @@
   if (!SetMockCentralManager(CBManagerStatePoweredOn)) {
     return;
   }
-  base::scoped_nsobject<CBPeripheral> mock_peripheral(
-      CreateMockPeripheral(kTestNSUUID));
+  CBPeripheral* mock_peripheral = CreateMockPeripheral(kTestNSUUID);
   if (!mock_peripheral) {
     return;
   }
@@ -365,12 +366,11 @@
   if (!SetMockCentralManager(CBManagerStatePoweredOn)) {
     return;
   }
-  base::scoped_nsobject<CBPeripheral> mock_peripheral(
-      CreateMockPeripheral(kTestNSUUID));
+  CBPeripheral* mock_peripheral = CreateMockPeripheral(kTestNSUUID);
   if (!mock_peripheral) {
     return;
   }
-  base::scoped_nsobject<NSDictionary> advertisement_data(AdvertisementData());
+  NSDictionary* advertisement_data = AdvertisementData();
 
   EXPECT_EQ(0, NumDevices());
   EXPECT_FALSE(DevicePresent(mock_peripheral));
@@ -430,27 +430,22 @@
 
   ASSERT_TRUE(SetMockCentralManager(CBManagerStatePoweredOn));
 
-  base::scoped_nsobject<CBPeripheral> mock_peripheral_one(
-      CreateMockPeripheral(kTestNSUUID));
+  CBPeripheral* mock_peripheral_one = CreateMockPeripheral(kTestNSUUID);
   ASSERT_TRUE(mock_peripheral_one);
 
-  LowEnergyDeviceUpdated(
-      mock_peripheral_one,
-      base::scoped_nsobject<NSDictionary>(AdvertisementData()), kTestRssi);
+  LowEnergyDeviceUpdated(mock_peripheral_one, AdvertisementData(), kTestRssi);
 
-  base::scoped_nsobject<CBPeripheral> mock_peripheral_two(
-      CreateMockPeripheral(kTestAddedDeviceNSUUID));
+  CBPeripheral* mock_peripheral_two =
+      CreateMockPeripheral(kTestAddedDeviceNSUUID);
   ASSERT_TRUE(mock_peripheral_two);
 
-  LowEnergyDeviceUpdated(
-      mock_peripheral_two,
-      base::scoped_nsobject<NSDictionary>(AdvertisementData()), kTestRssi);
+  LowEnergyDeviceUpdated(mock_peripheral_two, AdvertisementData(), kTestRssi);
   observer_.Reset();
 
   // BluetoothAdapterMac only notifies observers of changed devices detected by
   // BluetoothLowEnergyDeviceWatcherMac if the device has been already known to
-  // the system(i.e. the changed device is in BluetoothAdatper::devices_). As
-  // so, add mock devices prior to setting BluetoothLowenergyDeviceWatcherMac.
+  // the system(i.e. the changed device is in BluetoothAdapter::devices_). As
+  // so, add mock devices prior to setting BluetoothLowEnergyDeviceWatcherMac.
   SetFakeLowEnergyDeviceWatcher();
 
   EXPECT_EQ(1, observer_.device_changed_count());
@@ -481,15 +476,12 @@
     return;
   }
 
-  base::scoped_nsobject<CBPeripheral> mock_peripheral(
-      CreateMockPeripheral(kTestNSUUID));
+  CBPeripheral* mock_peripheral = CreateMockPeripheral(kTestNSUUID);
   if (!mock_peripheral) {
     return;
   }
 
-  LowEnergyDeviceUpdated(
-      mock_peripheral, base::scoped_nsobject<NSDictionary>(AdvertisementData()),
-      kTestRssi);
+  LowEnergyDeviceUpdated(mock_peripheral, AdvertisementData(), kTestRssi);
   observer_.Reset();
 
   SetFakeLowEnergyDeviceWatcher();
diff --git a/device/bluetooth/bluetooth_low_energy_advertisement_manager_mac.mm b/device/bluetooth/bluetooth_low_energy_advertisement_manager_mac.mm
index 12807b2..c80ad50 100644
--- a/device/bluetooth/bluetooth_low_energy_advertisement_manager_mac.mm
+++ b/device/bluetooth/bluetooth_low_energy_advertisement_manager_mac.mm
@@ -6,19 +6,22 @@
 
 #include "base/functional/bind.h"
 #include "base/logging.h"
-#include "base/mac/scoped_nsobject.h"
 #include "base/strings/sys_string_conversions.h"
 #import "base/task/single_thread_task_runner.h"
 #include "device/bluetooth/bluetooth_advertisement.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 namespace device {
 
 BluetoothLowEnergyAdvertisementManagerMac::
-    BluetoothLowEnergyAdvertisementManagerMac() {}
+    BluetoothLowEnergyAdvertisementManagerMac() = default;
 
 BluetoothLowEnergyAdvertisementManagerMac::
-    ~BluetoothLowEnergyAdvertisementManagerMac() {}
+    ~BluetoothLowEnergyAdvertisementManagerMac() = default;
 
 void BluetoothLowEnergyAdvertisementManagerMac::Init(
     scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner,
@@ -87,9 +90,8 @@
 }
 
 void BluetoothLowEnergyAdvertisementManagerMac::StartAdvertising() {
-  base::scoped_nsobject<NSMutableArray> service_uuid_array(
-      [[NSMutableArray alloc]
-          initWithCapacity:active_advertisement_->service_uuids().size()]);
+  NSMutableArray* service_uuid_array = [[NSMutableArray alloc]
+      initWithCapacity:active_advertisement_->service_uuids().size()];
   for (const std::string& service_uuid :
        active_advertisement_->service_uuids()) {
     NSString* uuid_string = base::SysUTF8ToNSString(service_uuid);
diff --git a/device/bluetooth/bluetooth_low_energy_advertisement_manager_mac_unittest.mm b/device/bluetooth/bluetooth_low_energy_advertisement_manager_mac_unittest.mm
index aff5b2c..8e74e31 100644
--- a/device/bluetooth/bluetooth_low_energy_advertisement_manager_mac_unittest.mm
+++ b/device/bluetooth/bluetooth_low_energy_advertisement_manager_mac_unittest.mm
@@ -16,6 +16,10 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/ocmock/OCMock/OCMock.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 namespace {
 const char kTestUUID[] = "00000000-1111-2222-3333-444444444444";
 }  // namespace
@@ -100,8 +104,8 @@
  protected:
   scoped_refptr<base::TestSimpleTaskRunner> ui_task_runner_;
   BluetoothLowEnergyAdvertisementManagerMac advertisement_manager_;
-  CBPeripheralManager* peripheral_manager_;
-  id peripheral_manager_mock_;
+  CBPeripheralManager* __strong peripheral_manager_;
+  id __strong peripheral_manager_mock_;
   CBManagerState peripheral_manager_state_;
 
   scoped_refptr<BluetoothAdvertisement> advertisement_;
diff --git a/device/bluetooth/bluetooth_low_energy_central_manager_delegate.mm b/device/bluetooth/bluetooth_low_energy_central_manager_delegate.mm
index ce8e20a2..d37d3a7 100644
--- a/device/bluetooth/bluetooth_low_energy_central_manager_delegate.mm
+++ b/device/bluetooth/bluetooth_low_energy_central_manager_delegate.mm
@@ -11,6 +11,10 @@
 #include "device/bluetooth/bluetooth_low_energy_adapter_apple.h"
 #include "device/bluetooth/bluetooth_low_energy_discovery_manager_mac.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 namespace device {
 
 // This class exists to bridge between the Objective-C CBCentralManagerDelegate
@@ -23,7 +27,7 @@
       BluetoothLowEnergyAdapterApple* adapter)
       : discovery_manager_(discovery_manager), adapter_(adapter) {}
 
-  ~BluetoothLowEnergyCentralManagerBridge() {}
+  ~BluetoothLowEnergyCentralManagerBridge() = default;
 
   void DiscoveredPeripheral(CBPeripheral* peripheral,
                             NSDictionary* advertisementData,
diff --git a/device/bluetooth/bluetooth_low_energy_device_mac.h b/device/bluetooth/bluetooth_low_energy_device_mac.h
index 17a8664..aeb41e5 100644
--- a/device/bluetooth/bluetooth_low_energy_device_mac.h
+++ b/device/bluetooth/bluetooth_low_energy_device_mac.h
@@ -10,7 +10,6 @@
 
 #include <set>
 
-#include "base/mac/scoped_nsobject.h"
 #include "build/build_config.h"
 #include "crypto/sha2.h"
 #include "device/bluetooth/bluetooth_device_mac.h"
@@ -20,6 +19,10 @@
 #import <IOBluetooth/IOBluetooth.h>
 #endif
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 @class BluetoothLowEnergyPeripheralDelegate;
 
 namespace device {
@@ -89,7 +92,7 @@
  protected:
   // BluetoothDevice override.
   void CreateGattConnectionImpl(
-      absl::optional<BluetoothUUID> serivce_uuid) override;
+      absl::optional<BluetoothUUID> service_uuid) override;
   void DisconnectGatt() override;
 
   // Methods used by BluetoothLowEnergyPeripheralBridge.
@@ -154,11 +157,10 @@
   void DidDisconnectPeripheral(NSError* error);
 
   // CoreBluetooth data structure.
-  base::scoped_nsobject<CBPeripheral> peripheral_;
+  CBPeripheral* __strong peripheral_;
 
   // Objective-C delegate for the CBPeripheral.
-  base::scoped_nsobject<BluetoothLowEnergyPeripheralDelegate>
-      peripheral_delegate_;
+  BluetoothLowEnergyPeripheralDelegate* __strong peripheral_delegate_;
 
   // Whether the device is connected.
   bool connected_;
diff --git a/device/bluetooth/bluetooth_low_energy_device_mac.mm b/device/bluetooth/bluetooth_low_energy_device_mac.mm
index ca9d3bdc..b2409874 100644
--- a/device/bluetooth/bluetooth_low_energy_device_mac.mm
+++ b/device/bluetooth/bluetooth_low_energy_device_mac.mm
@@ -22,18 +22,22 @@
 #include "device/bluetooth/bluetooth_remote_gatt_service_mac.h"
 #include "device/bluetooth/public/cpp/bluetooth_address.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 namespace device {
 
 BluetoothLowEnergyDeviceMac::BluetoothLowEnergyDeviceMac(
     BluetoothAdapter* adapter,
     CBPeripheral* peripheral)
     : BluetoothDeviceMac(adapter),
-      peripheral_(peripheral, base::scoped_policy::RETAIN),
+      peripheral_(peripheral),
       connected_(false),
       discovery_pending_count_(0) {
   DCHECK(peripheral_);
-  peripheral_delegate_.reset([[BluetoothLowEnergyPeripheralDelegate alloc]
-      initWithBluetoothLowEnergyDeviceMac:this]);
+  peripheral_delegate_ = [[BluetoothLowEnergyPeripheralDelegate alloc]
+      initWithBluetoothLowEnergyDeviceMac:this];
   [peripheral_ setDelegate:peripheral_delegate_];
   identifier_ = GetPeripheralIdentifier(peripheral);
   hash_address_ = GetPeripheralHashAddress(peripheral);
@@ -255,8 +259,8 @@
     }
   }
   if (discovery_pending_count_ == 0) {
-    for (auto it = gatt_services_.begin(); it != gatt_services_.end(); ++it) {
-      BluetoothRemoteGattService* gatt_service = it->second.get();
+    for (auto& it : gatt_services_) {
+      BluetoothRemoteGattService* gatt_service = it.second.get();
       BluetoothRemoteGattServiceMac* gatt_service_mac =
           static_cast<BluetoothRemoteGattServiceMac*>(gatt_service);
       gatt_service_mac->DiscoverCharacteristics();
@@ -472,8 +476,8 @@
 BluetoothRemoteGattServiceMac*
 BluetoothLowEnergyDeviceMac::GetBluetoothRemoteGattServiceMac(
     CBService* cb_service) const {
-  for (auto it = gatt_services_.begin(); it != gatt_services_.end(); ++it) {
-    BluetoothRemoteGattService* gatt_service = it->second.get();
+  for (const auto& it : gatt_services_) {
+    BluetoothRemoteGattService* gatt_service = it.second.get();
     BluetoothRemoteGattServiceMac* gatt_service_mac =
         static_cast<BluetoothRemoteGattServiceMac*>(gatt_service);
     if (gatt_service_mac->GetService() == cb_service)
diff --git a/device/bluetooth/bluetooth_low_energy_device_watcher_mac.mm b/device/bluetooth/bluetooth_low_energy_device_watcher_mac.mm
index 7104a83..2130f9f5 100644
--- a/device/bluetooth/bluetooth_low_energy_device_watcher_mac.mm
+++ b/device/bluetooth/bluetooth_low_energy_device_watcher_mac.mm
@@ -14,6 +14,10 @@
 #include "base/task/task_traits.h"
 #include "device/bluetooth/bluetooth_adapter_mac.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 namespace device {
 
 namespace {
diff --git a/device/bluetooth/bluetooth_low_energy_discovery_manager_mac.h b/device/bluetooth/bluetooth_low_energy_discovery_manager_mac.h
index 2578643..0f844af 100644
--- a/device/bluetooth/bluetooth_low_energy_discovery_manager_mac.h
+++ b/device/bluetooth/bluetooth_low_energy_discovery_manager_mac.h
@@ -5,11 +5,9 @@
 #ifndef DEVICE_BLUETOOTH_BLUETOOTH_LOW_ENERGY_DISCOVERY_MANAGER_MAC_H_
 #define DEVICE_BLUETOOTH_BLUETOOTH_LOW_ENERGY_DISCOVERY_MANAGER_MAC_H_
 
-#include "base/memory/raw_ptr.h"
-
 #import <CoreBluetooth/CoreBluetooth.h>
 
-#include "base/mac/scoped_nsobject.h"
+#include "base/memory/raw_ptr.h"
 #include "build/build_config.h"
 #include "device/bluetooth/bluetooth_device.h"
 
@@ -31,7 +29,7 @@
                                         int rssi) = 0;
 
    protected:
-    virtual ~Observer() {}
+    virtual ~Observer() = default;
   };
 
   BluetoothLowEnergyDiscoveryManagerMac(
diff --git a/device/bluetooth/bluetooth_low_energy_discovery_manager_mac.mm b/device/bluetooth/bluetooth_low_energy_discovery_manager_mac.mm
index 41858a1c..c7f8aa4cf 100644
--- a/device/bluetooth/bluetooth_low_energy_discovery_manager_mac.mm
+++ b/device/bluetooth/bluetooth_low_energy_discovery_manager_mac.mm
@@ -12,11 +12,14 @@
 #include "device/bluetooth/bluetooth_adapter_mac.h"
 #include "device/bluetooth/bluetooth_low_energy_device_mac.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 namespace device {
 
 BluetoothLowEnergyDiscoveryManagerMac::
-    ~BluetoothLowEnergyDiscoveryManagerMac() {
-}
+    ~BluetoothLowEnergyDiscoveryManagerMac() = default;
 
 bool BluetoothLowEnergyDiscoveryManagerMac::IsDiscovering() const {
   return discovering_;
diff --git a/device/bluetooth/bluetooth_low_energy_peripheral_delegate.h b/device/bluetooth/bluetooth_low_energy_peripheral_delegate.h
index 623a787..156c8a3 100644
--- a/device/bluetooth/bluetooth_low_energy_peripheral_delegate.h
+++ b/device/bluetooth/bluetooth_low_energy_peripheral_delegate.h
@@ -15,6 +15,10 @@
 #import <IOBluetooth/IOBluetooth.h>
 #endif
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 namespace device {
 
 class BluetoothLowEnergyDeviceMac;
diff --git a/device/bluetooth/bluetooth_low_energy_peripheral_delegate.mm b/device/bluetooth/bluetooth_low_energy_peripheral_delegate.mm
index 36ecb3e..a484b6d 100644
--- a/device/bluetooth/bluetooth_low_energy_peripheral_delegate.mm
+++ b/device/bluetooth/bluetooth_low_energy_peripheral_delegate.mm
@@ -10,6 +10,10 @@
 #include "device/bluetooth/bluetooth_adapter_mac.h"
 #include "device/bluetooth/bluetooth_low_energy_discovery_manager_mac.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 namespace device {
 
 // This class exists to bridge between the Objective-C CBPeripheralDelegate
@@ -20,7 +24,7 @@
   BluetoothLowEnergyPeripheralBridge(BluetoothLowEnergyDeviceMac* device_mac)
       : device_mac_(device_mac) {}
 
-  ~BluetoothLowEnergyPeripheralBridge() {}
+  ~BluetoothLowEnergyPeripheralBridge() = default;
 
   void DidModifyServices(NSArray* invalidatedServices) {
     device_mac_->DidModifyServices(invalidatedServices);
diff --git a/device/bluetooth/bluetooth_low_energy_peripheral_manager_delegate.h b/device/bluetooth/bluetooth_low_energy_peripheral_manager_delegate.h
index 9d3deee..4cbc330 100644
--- a/device/bluetooth/bluetooth_low_energy_peripheral_manager_delegate.h
+++ b/device/bluetooth/bluetooth_low_energy_peripheral_manager_delegate.h
@@ -13,6 +13,10 @@
 #import <IOBluetooth/IOBluetooth.h>
 #endif
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 namespace device {
 class BluetoothLowEnergyAdapterApple;
 class BluetoothLowEnergyAdvertisementManagerMac;
diff --git a/device/bluetooth/bluetooth_low_energy_peripheral_manager_delegate.mm b/device/bluetooth/bluetooth_low_energy_peripheral_manager_delegate.mm
index 311c2ee..454961f6 100644
--- a/device/bluetooth/bluetooth_low_energy_peripheral_manager_delegate.mm
+++ b/device/bluetooth/bluetooth_low_energy_peripheral_manager_delegate.mm
@@ -9,6 +9,10 @@
 #include "base/memory/raw_ptr.h"
 #include "device/bluetooth/bluetooth_low_energy_adapter_apple.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 namespace device {
 
 // This class exists to bridge between the Objective-C
@@ -21,7 +25,7 @@
       BluetoothLowEnergyAdapterApple* adapter)
       : advertisement_manager_(advertisement_manager), adapter_(adapter) {}
 
-  ~BluetoothLowEnergyPeripheralManagerBridge() {}
+  ~BluetoothLowEnergyPeripheralManagerBridge() = default;
 
   void UpdatedState() {
     advertisement_manager_->OnPeripheralManagerStateChanged();
diff --git a/device/bluetooth/bluetooth_remote_gatt_characteristic_mac.h b/device/bluetooth/bluetooth_remote_gatt_characteristic_mac.h
index 9c87ce9e..2cc980f 100644
--- a/device/bluetooth/bluetooth_remote_gatt_characteristic_mac.h
+++ b/device/bluetooth/bluetooth_remote_gatt_characteristic_mac.h
@@ -5,17 +5,21 @@
 #ifndef DEVICE_BLUETOOTH_BLUETOOTH_REMOTE_GATT_CHARACTERISTIC_MAC_H_
 #define DEVICE_BLUETOOTH_BLUETOOTH_REMOTE_GATT_CHARACTERISTIC_MAC_H_
 
-#include "base/memory/raw_ptr.h"
 #include "device/bluetooth/bluetooth_remote_gatt_characteristic.h"
 
 #import <CoreBluetooth/CoreBluetooth.h>
+
 #include <string>
 #include <utility>
 #include <vector>
 
-#include "base/mac/scoped_nsobject.h"
+#include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 namespace device {
 
 class BluetoothLowEnergyAdapterApple;
@@ -122,7 +126,7 @@
   // gatt_service_ owns instances of this class.
   raw_ptr<BluetoothRemoteGattServiceMac> gatt_service_;
   // A characteristic from CBPeripheral.services.characteristics.
-  base::scoped_nsobject<CBCharacteristic> cb_characteristic_;
+  CBCharacteristic* __strong cb_characteristic_;
   // Characteristic identifier.
   std::string identifier_;
   // Service UUID.
diff --git a/device/bluetooth/bluetooth_remote_gatt_characteristic_mac.mm b/device/bluetooth/bluetooth_remote_gatt_characteristic_mac.mm
index d185828..3da63a2 100644
--- a/device/bluetooth/bluetooth_remote_gatt_characteristic_mac.mm
+++ b/device/bluetooth/bluetooth_remote_gatt_characteristic_mac.mm
@@ -16,6 +16,10 @@
 #include "device/bluetooth/bluetooth_remote_gatt_descriptor_mac.h"
 #include "device/bluetooth/bluetooth_remote_gatt_service_mac.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 namespace device {
 
 namespace {
@@ -74,13 +78,13 @@
     : is_discovery_complete_(false),
       discovery_pending_count_(0),
       gatt_service_(gatt_service),
-      cb_characteristic_(cb_characteristic, base::scoped_policy::RETAIN),
+      cb_characteristic_(cb_characteristic),
       weak_ptr_factory_(this) {
   uuid_ = BluetoothLowEnergyAdapterApple::BluetoothUUIDWithCBUUID(
       [cb_characteristic_ UUID]);
   identifier_ = base::SysNSStringToUTF8(
       [NSString stringWithFormat:@"%s-%p", uuid_.canonical_value().c_str(),
-                                 cb_characteristic_.get()]);
+                                 cb_characteristic_]);
 }
 
 BluetoothRemoteGattCharacteristicMac::~BluetoothRemoteGattCharacteristicMac() {
@@ -171,8 +175,8 @@
   DVLOG(1) << *this << ": Write characteristic.";
   write_characteristic_value_callbacks_ =
       std::make_pair(std::move(callback), std::move(error_callback));
-  base::scoped_nsobject<NSData> nsdata_value(
-      [[NSData alloc] initWithBytes:value.data() length:value.size()]);
+  NSData* nsdata_value = [[NSData alloc] initWithBytes:value.data()
+                                                length:value.size()];
 
   CBCharacteristicWriteType cb_write_type;
   switch (write_type) {
@@ -218,8 +222,8 @@
   DVLOG(1) << *this << ": Write characteristic.";
   write_characteristic_value_callbacks_ =
       std::make_pair(std::move(callback), std::move(error_callback));
-  base::scoped_nsobject<NSData> nsdata_value(
-      [[NSData alloc] initWithBytes:value.data() length:value.size()]);
+  NSData* nsdata_value = [[NSData alloc] initWithBytes:value.data()
+                                                length:value.size()];
   CBCharacteristicWriteType write_type = GetCBWriteType();
   [GetCBPeripheral() writeValue:nsdata_value
               forCharacteristic:cb_characteristic_
diff --git a/device/bluetooth/bluetooth_remote_gatt_descriptor_mac.h b/device/bluetooth/bluetooth_remote_gatt_descriptor_mac.h
index 8be9824..215d4f6 100644
--- a/device/bluetooth/bluetooth_remote_gatt_descriptor_mac.h
+++ b/device/bluetooth/bluetooth_remote_gatt_descriptor_mac.h
@@ -5,18 +5,15 @@
 #ifndef DEVICE_BLUETOOTH_BLUETOOTH_REMOTE_GATT_DESCRIPTOR_MAC_H_
 #define DEVICE_BLUETOOTH_BLUETOOTH_REMOTE_GATT_DESCRIPTOR_MAC_H_
 
-#include "base/memory/raw_ptr.h"
 #include "device/bluetooth/bluetooth_remote_gatt_descriptor.h"
 
+#import <CoreBluetooth/CoreBluetooth.h>
+
 #include <vector>
 
-#include "base/mac/scoped_nsobject.h"
+#include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
 
-#if defined(__OBJC__)
-#import <CoreBluetooth/CoreBluetooth.h>
-#endif  // defined(__OBJC__)
-
 namespace device {
 
 class BluetoothRemoteGattCharacteristicMac;
@@ -66,7 +63,7 @@
   // gatt_characteristic_ owns instances of this class.
   raw_ptr<BluetoothRemoteGattCharacteristicMac> gatt_characteristic_;
   // Descriptor from CoreBluetooth.
-  base::scoped_nsobject<CBDescriptor> cb_descriptor_;
+  CBDescriptor* __strong cb_descriptor_;
   // Descriptor identifier.
   std::string identifier_;
   // Descriptor UUID.
diff --git a/device/bluetooth/bluetooth_remote_gatt_descriptor_mac.mm b/device/bluetooth/bluetooth_remote_gatt_descriptor_mac.mm
index 408b331..0a1456a 100644
--- a/device/bluetooth/bluetooth_remote_gatt_descriptor_mac.mm
+++ b/device/bluetooth/bluetooth_remote_gatt_descriptor_mac.mm
@@ -12,6 +12,10 @@
 #include "device/bluetooth/bluetooth_low_energy_adapter_apple.h"
 #import "device/bluetooth/bluetooth_remote_gatt_characteristic_mac.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 using base::mac::ObjCCast;
 
 namespace device {
@@ -46,13 +50,12 @@
 BluetoothRemoteGattDescriptorMac::BluetoothRemoteGattDescriptorMac(
     BluetoothRemoteGattCharacteristicMac* characteristic,
     CBDescriptor* descriptor)
-    : gatt_characteristic_(characteristic),
-      cb_descriptor_(descriptor, base::scoped_policy::RETAIN) {
+    : gatt_characteristic_(characteristic), cb_descriptor_(descriptor) {
   uuid_ = BluetoothLowEnergyAdapterApple::BluetoothUUIDWithCBUUID(
       [cb_descriptor_ UUID]);
   identifier_ = base::SysNSStringToUTF8(
       [NSString stringWithFormat:@"%s-%p", uuid_.canonical_value().c_str(),
-                                 cb_descriptor_.get()]);
+                                 cb_descriptor_]);
 }
 
 std::string BluetoothRemoteGattDescriptorMac::GetIdentifier() const {
@@ -125,8 +128,8 @@
   DVLOG(1) << *this << ": Write value.";
   write_value_callbacks_ =
       std::make_pair(std::move(callback), std::move(error_callback));
-  base::scoped_nsobject<NSData> nsdata_value(
-      [[NSData alloc] initWithBytes:value.data() length:value.size()]);
+  NSData* nsdata_value = [[NSData alloc] initWithBytes:value.data()
+                                                length:value.size()];
   [GetCBPeripheral() writeValue:nsdata_value forDescriptor:GetCBDescriptor()];
 }
 
diff --git a/device/bluetooth/bluetooth_remote_gatt_service_mac.h b/device/bluetooth/bluetooth_remote_gatt_service_mac.h
index 795d3a48..2f68cef 100644
--- a/device/bluetooth/bluetooth_remote_gatt_service_mac.h
+++ b/device/bluetooth/bluetooth_remote_gatt_service_mac.h
@@ -10,10 +10,13 @@
 #include <string>
 #include <vector>
 
-#include "base/mac/scoped_nsobject.h"
 #include "base/memory/raw_ptr.h"
 #include "device/bluetooth/bluetooth_remote_gatt_service.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 @class CBCharacteristic;
 @class CBDescriptor;
 @class CBPeripheral;
@@ -81,7 +84,7 @@
   // bluetooth_device_mac_ owns instances of this class.
   raw_ptr<BluetoothLowEnergyDeviceMac> bluetooth_device_mac_;
   // A service from CBPeripheral.services.
-  base::scoped_nsobject<CBService> service_;
+  CBService* __strong service_;
   bool is_primary_;
   // Service identifier.
   std::string identifier_;
diff --git a/device/bluetooth/bluetooth_remote_gatt_service_mac.mm b/device/bluetooth/bluetooth_remote_gatt_service_mac.mm
index 83eb4a8..1ab4cc00 100644
--- a/device/bluetooth/bluetooth_remote_gatt_service_mac.mm
+++ b/device/bluetooth/bluetooth_remote_gatt_service_mac.mm
@@ -16,6 +16,10 @@
 #include "device/bluetooth/bluetooth_remote_gatt_characteristic_mac.h"
 #include "device/bluetooth/public/cpp/bluetooth_uuid.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 namespace device {
 
 BluetoothRemoteGattServiceMac::BluetoothRemoteGattServiceMac(
@@ -23,14 +27,13 @@
     CBService* service,
     bool is_primary)
     : bluetooth_device_mac_(bluetooth_device_mac),
-      service_(service, base::scoped_policy::RETAIN),
+      service_(service),
       is_primary_(is_primary),
       discovery_pending_count_(0) {
   uuid_ =
       BluetoothLowEnergyAdapterApple::BluetoothUUIDWithCBUUID([service_ UUID]);
-  identifier_ = base::SysNSStringToUTF8(
-      [NSString stringWithFormat:@"%s-%p", uuid_.canonical_value().c_str(),
-                                 service_.get()]);
+  identifier_ = base::SysNSStringToUTF8([NSString
+      stringWithFormat:@"%s-%p", uuid_.canonical_value().c_str(), service_]);
 }
 
 BluetoothRemoteGattServiceMac::~BluetoothRemoteGattServiceMac() {}
diff --git a/device/bluetooth/bluetooth_rfcomm_channel_mac.h b/device/bluetooth/bluetooth_rfcomm_channel_mac.h
index 0190cbf..8e2c9ce 100644
--- a/device/bluetooth/bluetooth_rfcomm_channel_mac.h
+++ b/device/bluetooth/bluetooth_rfcomm_channel_mac.h
@@ -12,9 +12,12 @@
 
 #include <memory>
 
-#include "base/mac/scoped_nsobject.h"
 #include "device/bluetooth/bluetooth_channel_mac.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 @class BluetoothRfcommChannelDelegate;
 
 namespace device {
@@ -23,7 +26,6 @@
  public:
   // Creates a new RFCOMM channel wrapper with the given |socket| and native
   // |channel|.
-  // NOTE: The |channel| is expected to already be retained.
   BluetoothRfcommChannelMac(BluetoothSocketMac* socket,
                             IOBluetoothRFCOMMChannel* channel);
 
@@ -61,10 +63,10 @@
 
  private:
   // The wrapped native RFCOMM channel.
-  base::scoped_nsobject<IOBluetoothRFCOMMChannel> channel_;
+  IOBluetoothRFCOMMChannel* __strong channel_;
 
   // The delegate for the native channel.
-  base::scoped_nsobject<BluetoothRfcommChannelDelegate> delegate_;
+  BluetoothRfcommChannelDelegate* __strong delegate_;
 };
 
 }  // namespace device
diff --git a/device/bluetooth/bluetooth_rfcomm_channel_mac.mm b/device/bluetooth/bluetooth_rfcomm_channel_mac.mm
index cacbbd146..fa681516 100644
--- a/device/bluetooth/bluetooth_rfcomm_channel_mac.mm
+++ b/device/bluetooth/bluetooth_rfcomm_channel_mac.mm
@@ -11,6 +11,10 @@
 #include "device/bluetooth/bluetooth_classic_device_mac.h"
 #include "device/bluetooth/bluetooth_socket_mac.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 // A simple delegate class for an open RFCOMM channel that forwards methods to
 // its wrapped |channel_|.
 @interface BluetoothRfcommChannelDelegate
@@ -78,21 +82,15 @@
     IOReturn* status) {
   DCHECK(socket);
   std::unique_ptr<BluetoothRfcommChannelMac> channel(
-      new BluetoothRfcommChannelMac(socket, nil));
+      new BluetoothRfcommChannelMac(socket, /*channel=*/nil));
 
-  // Retain the delegate, because IOBluetoothDevice's
-  // |-openRFCOMMChannelAsync:withChannelID:delegate:| assumes that it can take
-  // ownership of the delegate without calling |-retain| on it...
   DCHECK(channel->delegate_);
-  [channel->delegate_ retain];
   IOBluetoothRFCOMMChannel* rfcomm_channel;
   *status = [device openRFCOMMChannelAsync:&rfcomm_channel
                              withChannelID:channel_id
                                   delegate:channel->delegate_];
   if (*status == kIOReturnSuccess) {
-    // Note: No need to retain the |rfcomm_channel| -- the returned channel is
-    // already retained.
-    channel->channel_.reset(rfcomm_channel);
+    channel->channel_ = rfcomm_channel;
   } else {
     channel.reset();
   }
@@ -108,8 +106,7 @@
   // Now that the socket is set, it's safe to associate a delegate, which can
   // call back to the socket.
   DCHECK(!delegate_);
-  delegate_.reset(
-      [[BluetoothRfcommChannelDelegate alloc] initWithChannel:this]);
+  delegate_ = [[BluetoothRfcommChannelDelegate alloc] initWithChannel:this];
   [channel_ setDelegate:delegate_];
 }
 
diff --git a/device/bluetooth/bluetooth_socket_mac.h b/device/bluetooth/bluetooth_socket_mac.h
index f891d85..efdaa955b 100644
--- a/device/bluetooth/bluetooth_socket_mac.h
+++ b/device/bluetooth/bluetooth_socket_mac.h
@@ -13,13 +13,16 @@
 #include <string>
 
 #include "base/containers/queue.h"
-#include "base/mac/scoped_nsobject.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/threading/thread_checker.h"
 #include "device/bluetooth/bluetooth_adapter.h"
 #include "device/bluetooth/bluetooth_socket.h"
 #include "device/bluetooth/public/cpp/bluetooth_uuid.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 @class BluetoothRfcommConnectionListener;
 @class BluetoothL2capConnectionListener;
 
@@ -33,7 +36,7 @@
 class BluetoothAdapterMac;
 class BluetoothChannelMac;
 
-// Implements the BluetoothSocket class for the Mac OS X platform.
+// Implements the BluetoothSocket class for the macOS platform.
 class BluetoothSocketMac : public BluetoothSocket {
  public:
   static scoped_refptr<BluetoothSocketMac> CreateSocket();
@@ -125,9 +128,9 @@
     int buffer_size;
     SendCompletionCallback success_callback;
     ErrorCompletionCallback error_callback;
-    IOReturn status;
-    int active_async_writes;
-    bool error_signaled;
+    IOReturn status = kIOReturnSuccess;
+    int active_async_writes = 0;
+    bool error_signaled = false;
   };
 
   struct ReceiveCallbacks {
@@ -167,14 +170,12 @@
 
   // Simple helpers that register for OS notifications and forward them to
   // |this| profile.
-  base::scoped_nsobject<BluetoothRfcommConnectionListener>
-      rfcomm_connection_listener_;
-  base::scoped_nsobject<BluetoothL2capConnectionListener>
-      l2cap_connection_listener_;
+  BluetoothRfcommConnectionListener* __strong rfcomm_connection_listener_;
+  BluetoothL2capConnectionListener* __strong l2cap_connection_listener_;
 
   // The service record registered in the system SDP server, used to
   // eventually unregister the service.
-  base::scoped_nsobject<IOBluetoothSDPServiceRecord> service_record_;
+  IOBluetoothSDPServiceRecord* __strong service_record_;
 
   // The channel used to issue commands.
   std::unique_ptr<BluetoothChannelMac> channel_;
diff --git a/device/bluetooth/bluetooth_socket_mac.mm b/device/bluetooth/bluetooth_socket_mac.mm
index 229593c..58957ba 100644
--- a/device/bluetooth/bluetooth_socket_mac.mm
+++ b/device/bluetooth/bluetooth_socket_mac.mm
@@ -4,8 +4,6 @@
 
 #include "device/bluetooth/bluetooth_socket_mac.h"
 
-#include "base/memory/raw_ptr.h"
-
 #import <IOBluetooth/IOBluetooth.h>
 #include <stdint.h>
 
@@ -20,6 +18,7 @@
 #include "base/functional/callback.h"
 #include "base/functional/callback_helpers.h"
 #include "base/mac/scoped_cftyperef.h"
+#include "base/memory/raw_ptr.h"
 #include "base/memory/ref_counted.h"
 #include "base/numerics/safe_conversions.h"
 #include "base/strings/string_number_conversions.h"
@@ -35,6 +34,10 @@
 #include "net/base/net_errors.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 using device::BluetoothSocket;
 
 // A simple helper class that forwards SDP query completed notifications to its
@@ -49,7 +52,7 @@
   BluetoothSocket::ErrorCompletionCallback _error_callback;
 
   // The device being queried.
-  IOBluetoothDevice* _device;  // weak
+  IOBluetoothDevice* __weak _device;
 }
 
 - (instancetype)initWithSocket:(scoped_refptr<device::BluetoothSocketMac>)socket
@@ -84,7 +87,6 @@
     // target is specified.
     std::move(_error_callback).Run("No target");
   }
-  [super dealloc];
 }
 
 - (void)sdpQueryComplete:(IOBluetoothDevice*)device status:(IOReturn)status {
@@ -104,7 +106,7 @@
 
   // The OS mechanism used to subscribe to and unsubscribe from RFCOMM channel
   // creation notifications.
-  IOBluetoothUserNotification* _rfcommNewChannelNotification;  // weak
+  IOBluetoothUserNotification* __weak _rfcommNewChannelNotification;
 }
 
 - (instancetype)initWithSocket:(device::BluetoothSocketMac*)socket
@@ -137,7 +139,6 @@
 
 - (void)dealloc {
   [_rfcommNewChannelNotification unregister];
-  [super dealloc];
 }
 
 - (void)rfcommChannelOpened:(IOBluetoothUserNotification*)notification
@@ -152,7 +153,8 @@
   }
 
   _socket->OnChannelOpened(std::unique_ptr<device::BluetoothChannelMac>(
-      new device::BluetoothRfcommChannelMac(NULL, [rfcommChannel retain])));
+      new device::BluetoothRfcommChannelMac(/*socket=*/nullptr,
+                                            rfcommChannel)));
 }
 
 @end
@@ -166,7 +168,7 @@
 
   // The OS mechanism used to subscribe to and unsubscribe from L2CAP channel
   // creation notifications.
-  IOBluetoothUserNotification* _l2capNewChannelNotification;  // weak
+  IOBluetoothUserNotification* __weak _l2capNewChannelNotification;
 }
 
 - (instancetype)initWithSocket:(device::BluetoothSocketMac*)socket
@@ -199,7 +201,6 @@
 
 - (void)dealloc {
   [_l2capNewChannelNotification unregister];
-  [super dealloc];
 }
 
 - (void)l2capChannelOpened:(IOBluetoothUserNotification*)notification
@@ -214,7 +215,7 @@
   }
 
   _socket->OnChannelOpened(std::unique_ptr<device::BluetoothChannelMac>(
-      new device::BluetoothL2capChannelMac(NULL, [l2capChannel retain])));
+      new device::BluetoothL2capChannelMac(/*socket=*/nullptr, l2capChannel)));
 }
 
 @end
@@ -274,7 +275,7 @@
   }
 
   const int kUUIDsKey = kBluetoothSDPAttributeIdentifierServiceClassIDList;
-  NSArray* uuids = @[GetIOBluetoothSDPUUID(uuid)];
+  NSArray* uuids = @[ GetIOBluetoothSDPUUID(uuid) ];
   service_definition[IntToNSString(kUUIDsKey)] = uuids;
 
   const int kProtocolDefinitionsKey =
@@ -442,8 +443,7 @@
                                         device:device
                               success_callback:std::move(success_callback)
                                 error_callback:std::move(error_callback)];
-  [device performSDPQuery:[listener autorelease]
-                    uuids:@[GetIOBluetoothSDPUUID(uuid_)]];
+  [device performSDPQuery:listener uuids:@[ GetIOBluetoothSDPUUID(uuid_) ]];
 }
 
 void BluetoothSocketMac::ListenUsingRfcomm(
@@ -459,17 +459,16 @@
 
   DVLOG(1) << uuid_.canonical_value() << ": Registering RFCOMM service.";
   BluetoothRFCOMMChannelID registered_channel_id;
-  service_record_.reset(
-      RegisterRfcommService(uuid, options, &registered_channel_id));
-  if (!service_record_.get()) {
+  service_record_ =
+      RegisterRfcommService(uuid, options, &registered_channel_id);
+  if (!service_record_) {
     std::move(error_callback).Run(kInvalidOrUsedChannel);
     return;
   }
 
-  rfcomm_connection_listener_.reset(
-      [[BluetoothRfcommConnectionListener alloc]
-          initWithSocket:this
-               channelID:registered_channel_id]);
+  rfcomm_connection_listener_ = [[BluetoothRfcommConnectionListener alloc]
+      initWithSocket:this
+           channelID:registered_channel_id];
 
   std::move(success_callback).Run();
 }
@@ -487,15 +486,15 @@
 
   DVLOG(1) << uuid_.canonical_value() << ": Registering L2CAP service.";
   BluetoothL2CAPPSM registered_psm;
-  service_record_.reset(RegisterL2capService(uuid, options, &registered_psm));
-  if (!service_record_.get()) {
+  service_record_ = RegisterL2capService(uuid, options, &registered_psm);
+  if (!service_record_) {
     std::move(error_callback).Run(kInvalidOrUsedPsm);
     return;
   }
 
-  l2cap_connection_listener_.reset(
+  l2cap_connection_listener_ =
       [[BluetoothL2capConnectionListener alloc] initWithSocket:this
-                                                           psm:registered_psm]);
+                                                           psm:registered_psm];
 
   std::move(success_callback).Run();
 }
@@ -631,10 +630,11 @@
 void BluetoothSocketMac::Disconnect(base::OnceClosure callback) {
   DCHECK(thread_checker_.CalledOnValidThread());
 
-  if (channel_)
+  if (channel_) {
     ReleaseChannel();
-  else if (service_record_.get())
+  } else if (service_record_) {
     ReleaseListener();
+  }
 
   std::move(callback).Run();
 }
@@ -856,24 +856,23 @@
   DVLOG(1) << uuid_.canonical_value() << ": Accept complete.";
 }
 
-BluetoothSocketMac::AcceptRequest::AcceptRequest() {}
+BluetoothSocketMac::AcceptRequest::AcceptRequest() = default;
 
-BluetoothSocketMac::AcceptRequest::~AcceptRequest() {}
+BluetoothSocketMac::AcceptRequest::~AcceptRequest() = default;
 
-BluetoothSocketMac::SendRequest::SendRequest()
-    : status(kIOReturnSuccess), active_async_writes(0), error_signaled(false) {}
+BluetoothSocketMac::SendRequest::SendRequest() = default;
 
-BluetoothSocketMac::SendRequest::~SendRequest() {}
+BluetoothSocketMac::SendRequest::~SendRequest() = default;
 
-BluetoothSocketMac::ReceiveCallbacks::ReceiveCallbacks() {}
+BluetoothSocketMac::ReceiveCallbacks::ReceiveCallbacks() = default;
 
-BluetoothSocketMac::ReceiveCallbacks::~ReceiveCallbacks() {}
+BluetoothSocketMac::ReceiveCallbacks::~ReceiveCallbacks() = default;
 
-BluetoothSocketMac::ConnectCallbacks::ConnectCallbacks() {}
+BluetoothSocketMac::ConnectCallbacks::ConnectCallbacks() = default;
 
-BluetoothSocketMac::ConnectCallbacks::~ConnectCallbacks() {}
+BluetoothSocketMac::ConnectCallbacks::~ConnectCallbacks() = default;
 
-BluetoothSocketMac::BluetoothSocketMac() {}
+BluetoothSocketMac::BluetoothSocketMac() = default;
 
 BluetoothSocketMac::~BluetoothSocketMac() {
   DCHECK(thread_checker_.CalledOnValidThread());
@@ -895,12 +894,12 @@
 
 void BluetoothSocketMac::ReleaseListener() {
   DCHECK(thread_checker_.CalledOnValidThread());
-  DCHECK(service_record_.get());
+  DCHECK(service_record_);
 
   [service_record_ removeServiceRecord];
-  service_record_.reset();
-  rfcomm_connection_listener_.reset();
-  l2cap_connection_listener_.reset();
+  service_record_ = nil;
+  rfcomm_connection_listener_ = nil;
+  l2cap_connection_listener_ = nil;
 
   // Destroying the listener above prevents the callback delegate from being
   // called so it is now safe to release all callback state.
diff --git a/device/bluetooth/test/bluetooth_test_mac.h b/device/bluetooth/test/bluetooth_test_mac.h
index 9501888..1bd44293 100644
--- a/device/bluetooth/test/bluetooth_test_mac.h
+++ b/device/bluetooth/test/bluetooth_test_mac.h
@@ -15,10 +15,6 @@
 @class MockCBDescriptor;
 @class MockCBCharacteristic;
 @class MockCBPeripheral;
-#else   // __OBJC__
-class MockCBDescriptor;
-class MockCBCharacteristic;
-class MockCBPeripheral;
 #endif  // __OBJC__
 
 namespace device {
@@ -181,6 +177,8 @@
  protected:
   class ScopedMockCentralManager;
 
+#if __OBJC__
+
   // Returns MockCBPeripheral from BluetoothDevice.
   MockCBPeripheral* GetMockCBPeripheral(BluetoothDevice* device) const;
   // Returns MockCBPeripheral from BluetoothRemoteGattService.
@@ -199,6 +197,8 @@
   MockCBDescriptor* GetCBMockDescriptor(
       BluetoothRemoteGattDescriptor* descriptor) const;
 
+#endif  // __OBJC__
+
   // Utility function for finding CBUUIDs with relatively nice SHA256 hashes.
   std::string FindCBUUIDForHashTarget();
 
@@ -211,7 +211,7 @@
 };
 
 // Defines common test fixture name. Use TEST_F(BluetoothTest, YourTestName).
-typedef BluetoothTestMac BluetoothTest;
+using BluetoothTest = BluetoothTestMac;
 
 }  // namespace device
 
diff --git a/device/bluetooth/test/bluetooth_test_mac.mm b/device/bluetooth/test/bluetooth_test_mac.mm
index 1b53d72..62f23d0 100644
--- a/device/bluetooth/test/bluetooth_test_mac.mm
+++ b/device/bluetooth/test/bluetooth_test_mac.mm
@@ -16,6 +16,7 @@
 #include "build/build_config.h"
 #import "device/bluetooth/bluetooth_device_mac.h"
 #include "device/bluetooth/bluetooth_low_energy_adapter_apple.h"
+#include "device/bluetooth/bluetooth_low_energy_central_manager_delegate.h"
 #import "device/bluetooth/bluetooth_remote_gatt_characteristic_mac.h"
 #import "device/bluetooth/bluetooth_remote_gatt_descriptor_mac.h"
 #import "device/bluetooth/bluetooth_remote_gatt_service_mac.h"
@@ -32,8 +33,11 @@
 #import "device/bluetooth/bluetooth_adapter_mac.h"
 #endif
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 using base::mac::ObjCCast;
-using base::scoped_nsobject;
 
 namespace device {
 
@@ -41,7 +45,7 @@
 class BluetoothTestMac::ScopedMockCentralManager {
  public:
   explicit ScopedMockCentralManager(MockCentralManager* mock_central_manager) {
-    mock_central_manager_.reset(mock_central_manager);
+    mock_central_manager_ = mock_central_manager;
   }
 
   ScopedMockCentralManager(const ScopedMockCentralManager&) = delete;
@@ -51,21 +55,20 @@
   MockCentralManager* get() { return mock_central_manager_; }
 
  private:
-  scoped_nsobject<MockCentralManager> mock_central_manager_;
+  MockCentralManager* __strong mock_central_manager_;
 };
 
 namespace {
 
-scoped_nsobject<NSDictionary> CreateAdvertisementData(
-    NSString* name,
-    NSArray* uuids,
-    NSDictionary* service_data,
-    NSData* manufacturer_data,
-    NSNumber* tx_power) {
-  NSMutableDictionary* advertisement_data(
+NSDictionary* AdvertisementData(NSString* name,
+                                NSArray* uuids,
+                                NSDictionary* service_data,
+                                NSData* manufacturer_data,
+                                NSNumber* tx_power) {
+  NSMutableDictionary* advertisement_data =
       [NSMutableDictionary dictionaryWithDictionary:@{
         CBAdvertisementDataIsConnectable : @YES
-      }]);
+      }];
 
   if (name) {
     advertisement_data[CBAdvertisementDataLocalNameKey] = name;
@@ -88,8 +91,7 @@
     advertisement_data[CBAdvertisementDataTxPowerLevelKey] = tx_power;
   }
 
-  return scoped_nsobject<NSDictionary>(advertisement_data,
-                                       base::scoped_policy::RETAIN);
+  return advertisement_data;
 }
 
 }  // namespace
@@ -103,9 +105,9 @@
 // Fake error domain for testing error metrics
 static NSString* const kDisconnectErrorDomain = @"FakeDisconnectErrorDomain";
 
-BluetoothTestMac::BluetoothTestMac() : BluetoothTestBase() {}
+BluetoothTestMac::BluetoothTestMac() = default;
 
-BluetoothTestMac::~BluetoothTestMac() {}
+BluetoothTestMac::~BluetoothTestMac() = default;
 
 void BluetoothTestMac::SetUp() {}
 
@@ -281,15 +283,15 @@
       manufacturer_data = nil;
       tx_power = nil;
   }
-  scoped_nsobject<MockCBPeripheral> mock_peripheral([[MockCBPeripheral alloc]
-      initWithUTF8StringIdentifier:identifier
-                              name:name]);
+  MockCBPeripheral* mock_peripheral =
+      [[MockCBPeripheral alloc] initWithUTF8StringIdentifier:identifier
+                                                        name:name];
   [mock_peripheral setBluetoothTestMac:this];
   [central_manager_delegate
              centralManager:central_manager
       didDiscoverPeripheral:[mock_peripheral peripheral]
-          advertisementData:CreateAdvertisementData(name, uuids, service_data,
-                                                    manufacturer_data, tx_power)
+          advertisementData:AdvertisementData(name, uuids, service_data,
+                                              manufacturer_data, tx_power)
                        RSSI:rssi];
   return observer.last_device();
 }
@@ -307,7 +309,7 @@
     ConnectedDeviceType device_ordinal) {
   const char* identifier = nullptr;
   NSString* name = nil;
-  scoped_nsobject<NSMutableSet> cbUUIDs([[NSMutableSet alloc] init]);
+  NSMutableSet* cbUUIDs = [[NSMutableSet alloc] init];
   switch (device_ordinal) {
     case ConnectedDeviceType::GENERIC_DEVICE:
       name = @(kTestDeviceName);
@@ -324,9 +326,9 @@
   DCHECK(name);
   DCHECK(identifier);
   DCHECK([cbUUIDs count] > 0);
-  scoped_nsobject<MockCBPeripheral> mock_peripheral([[MockCBPeripheral alloc]
-      initWithUTF8StringIdentifier:identifier
-                              name:name]);
+  MockCBPeripheral* mock_peripheral =
+      [[MockCBPeripheral alloc] initWithUTF8StringIdentifier:identifier
+                                                        name:name];
   [mock_peripheral setBluetoothTestMac:this];
   [mock_central_manager_->get()
       setConnectedMockPeripheral:[mock_peripheral peripheral]
@@ -419,8 +421,8 @@
     const std::vector<uint8_t>& value) {
   MockCBCharacteristic* characteristic_mock =
       GetCBMockCharacteristic(characteristic);
-  scoped_nsobject<NSData> data(
-      [[NSData alloc] initWithBytes:value.data() length:value.size()]);
+  NSData* data = [[NSData alloc] initWithBytes:value.data()
+                                        length:value.size()];
   [characteristic_mock simulateReadWithValue:data error:nil];
 }
 
@@ -500,8 +502,8 @@
     const std::vector<uint8_t>& value) {
   MockCBCharacteristic* characteristic_mock =
       GetCBMockCharacteristic(characteristic);
-  scoped_nsobject<NSData> data(
-      [[NSData alloc] initWithBytes:value.data() length:value.size()]);
+  NSData* data = [[NSData alloc] initWithBytes:value.data()
+                                        length:value.size()];
   [characteristic_mock simulateGattCharacteristicChangedWithValue:data];
 }
 
@@ -633,8 +635,8 @@
 void BluetoothTestMac::SimulateGattDescriptorReadNSDataMac(
     BluetoothRemoteGattDescriptor* descriptor,
     const std::vector<uint8_t>& value) {
-  scoped_nsobject<NSData> data(
-      [[NSData alloc] initWithBytes:value.data() length:value.size()]);
+  NSData* data = [[NSData alloc] initWithBytes:value.data()
+                                        length:value.size()];
   [GetCBMockDescriptor(descriptor) simulateReadWithValue:data error:nil];
 }
 
@@ -689,7 +691,7 @@
 void BluetoothTest::AddServicesToDeviceMac(
     BluetoothDevice* device,
     const std::vector<std::string>& uuids) {
-  scoped_nsobject<NSMutableArray> services([[NSMutableArray alloc] init]);
+  NSMutableArray* services = [[NSMutableArray alloc] init];
   for (auto uuid : uuids) {
     CBUUID* cb_service_uuid = [CBUUID UUIDWithString:@(uuid.c_str())];
     [services addObject:cb_service_uuid];
diff --git a/device/bluetooth/test/mock_bluetooth_cbcharacteristic_mac.mm b/device/bluetooth/test/mock_bluetooth_cbcharacteristic_mac.mm
index 65e9a89..f7c9857f 100644
--- a/device/bluetooth/test/mock_bluetooth_cbcharacteristic_mac.mm
+++ b/device/bluetooth/test/mock_bluetooth_cbcharacteristic_mac.mm
@@ -5,12 +5,14 @@
 #include "device/bluetooth/test/mock_bluetooth_cbcharacteristic_mac.h"
 
 #include "base/mac/foundation_util.h"
-#include "base/mac/scoped_nsobject.h"
 #include "device/bluetooth/bluetooth_gatt_characteristic.h"
 #include "device/bluetooth/test/mock_bluetooth_cbdescriptor_mac.h"
 
 using base::mac::ObjCCast;
-using base::scoped_nsobject;
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
 
 namespace device {
 
@@ -92,10 +94,10 @@
 @interface MockCBCharacteristic () {
   // Owner of this instance.
   CBService* _service;
-  scoped_nsobject<CBUUID> _UUID;
+  CBUUID* __strong _UUID;
   CBCharacteristicProperties _cb_properties;
-  scoped_nsobject<NSMutableArray> _descriptors;
-  scoped_nsobject<NSObject> _value;
+  NSMutableArray* __strong _descriptors;
+  NSObject* __strong _value;
   BOOL _notifying;
 }
 @end
@@ -108,11 +110,11 @@
   self = [super init];
   if (self) {
     _service = service;
-    _UUID.reset([uuid retain]);
+    _UUID = uuid;
     _cb_properties =
         device::GattCharacteristicPropertyToCBCharacteristicProperty(
             properties);
-    _descriptors.reset([[NSMutableArray alloc] init]);
+    _descriptors = [[NSMutableArray alloc] init];
   }
   return self;
 }
@@ -134,7 +136,7 @@
 }
 
 - (void)simulateReadWithValue:(id)value error:(NSError*)error {
-  _value.reset([value copy]);
+  _value = [value copy];
   CBPeripheral* peripheral = _service.peripheral;
   [peripheral.delegate peripheral:peripheral
       didUpdateValueForCharacteristic:self.characteristic
@@ -181,7 +183,7 @@
 }
 
 - (void)simulateGattCharacteristicChangedWithValue:(NSData*)value {
-  _value.reset([value copy]);
+  _value = [value copy];
   CBPeripheral* peripheral = _service.peripheral;
   [peripheral.delegate peripheral:peripheral
       didUpdateValueForCharacteristic:self.characteristic
@@ -189,9 +191,9 @@
 }
 
 - (void)addDescriptorWithUUID:(CBUUID*)uuid {
-  scoped_nsobject<MockCBDescriptor> descriptor_mock([[MockCBDescriptor alloc]
-      initWithCharacteristic:self.characteristic
-                      CBUUID:uuid]);
+  MockCBDescriptor* descriptor_mock =
+      [[MockCBDescriptor alloc] initWithCharacteristic:self.characteristic
+                                                CBUUID:uuid];
   [_descriptors addObject:descriptor_mock];
 }
 
diff --git a/device/bluetooth/test/mock_bluetooth_cbdescriptor_mac.mm b/device/bluetooth/test/mock_bluetooth_cbdescriptor_mac.mm
index b17ebb9..2f6ef81 100644
--- a/device/bluetooth/test/mock_bluetooth_cbdescriptor_mac.mm
+++ b/device/bluetooth/test/mock_bluetooth_cbdescriptor_mac.mm
@@ -5,17 +5,19 @@
 #include "device/bluetooth/test/mock_bluetooth_cbdescriptor_mac.h"
 
 #include "base/mac/foundation_util.h"
-#include "base/mac/scoped_nsobject.h"
 #include "device/bluetooth/bluetooth_gatt_characteristic.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 using base::mac::ObjCCast;
-using base::scoped_nsobject;
 
 @interface MockCBDescriptor () {
   // Owner of this instance.
   CBCharacteristic* _characteristic;
-  scoped_nsobject<CBUUID> _UUID;
-  scoped_nsobject<NSData> _value;
+  CBUUID* __strong _UUID;
+  NSData* __strong _value;
 }
 @end
 
@@ -26,7 +28,7 @@
   self = [super init];
   if (self) {
     _characteristic = characteristic;
-    _UUID.reset([uuid retain]);
+    _UUID = uuid;
   }
   return self;
 }
@@ -64,7 +66,7 @@
 }
 
 - (void)simulateReadWithValue:(id)value error:(NSError*)error {
-  _value.reset([value copy]);
+  _value = [value copy];
   CBPeripheral* peripheral = _characteristic.service.peripheral;
   [peripheral.delegate peripheral:peripheral
       didUpdateValueForDescriptor:self.descriptor
diff --git a/device/bluetooth/test/mock_bluetooth_cbperipheral_mac.h b/device/bluetooth/test/mock_bluetooth_cbperipheral_mac.h
index 90525fc..e581127 100644
--- a/device/bluetooth/test/mock_bluetooth_cbperipheral_mac.h
+++ b/device/bluetooth/test/mock_bluetooth_cbperipheral_mac.h
@@ -20,11 +20,11 @@
 @interface MockCBPeripheral : NSObject
 
 @property(nonatomic, readonly) CBPeripheralState state;
-@property(nonatomic, readonly) NSUUID* identifier;
+@property(nonatomic, strong, readonly) NSUUID* identifier;
 @property(nonatomic, readonly) NSString* name;
-@property(nonatomic, assign) id<CBPeripheralDelegate> delegate;
+@property(nonatomic, weak) id<CBPeripheralDelegate> delegate;
 @property(nonatomic, readonly) CBPeripheral* peripheral;
-@property(retain, readonly) NSArray* services;
+@property(strong, readonly) NSArray* services;
 @property(nonatomic, assign) device::BluetoothTestMac* bluetoothTestMac;
 
 - (instancetype)init NS_UNAVAILABLE;
diff --git a/device/bluetooth/test/mock_bluetooth_cbperipheral_mac.mm b/device/bluetooth/test/mock_bluetooth_cbperipheral_mac.mm
index 617fd95..b4a25285 100644
--- a/device/bluetooth/test/mock_bluetooth_cbperipheral_mac.mm
+++ b/device/bluetooth/test/mock_bluetooth_cbperipheral_mac.mm
@@ -5,26 +5,24 @@
 #include "device/bluetooth/test/mock_bluetooth_cbperipheral_mac.h"
 
 #include "base/mac/foundation_util.h"
-#include "base/mac/scoped_nsobject.h"
 #include "device/bluetooth/test/bluetooth_test_mac.h"
 #include "device/bluetooth/test/mock_bluetooth_cbcharacteristic_mac.h"
 #include "device/bluetooth/test/mock_bluetooth_cbdescriptor_mac.h"
 #include "device/bluetooth/test/mock_bluetooth_cbservice_mac.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 using base::mac::ObjCCast;
-using base::scoped_nsobject;
 
-@interface MockCBPeripheral () {
-  scoped_nsobject<NSUUID> _identifier;
-  scoped_nsobject<NSString> _name;
-  id<CBPeripheralDelegate> _delegate;
-  scoped_nsobject<NSMutableArray> _services;
+@implementation MockCBPeripheral {
+  NSUUID* __strong _identifier;
+  NSString* __strong _name;
+  id<CBPeripheralDelegate> __weak _delegate;
+  NSMutableArray* __strong _services;
 }
 
-@end
-
-@implementation MockCBPeripheral
-
 @synthesize state = _state;
 @synthesize delegate = _delegate;
 @synthesize bluetoothTestMac = _bluetoothTestMac;
@@ -40,18 +38,15 @@
 
 - (instancetype)initWithUTF8StringIdentifier:(const char*)utf8Identifier
                                         name:(NSString*)name {
-  scoped_nsobject<NSUUID> identifier(
-      [[NSUUID alloc] initWithUUIDString:@(utf8Identifier)]);
+  NSUUID* identifier = [[NSUUID alloc] initWithUUIDString:@(utf8Identifier)];
   return [self initWithIdentifier:identifier name:name];
 }
 
 - (instancetype)initWithIdentifier:(NSUUID*)identifier name:(NSString*)name {
   self = [super init];
   if (self) {
-    _identifier.reset([identifier retain]);
-    if (name) {
-      _name.reset([name retain]);
-    }
+    _identifier = identifier;
+    _name = name;
     _state = CBPeripheralStateDisconnected;
   }
   return self;
@@ -76,7 +71,7 @@
 - (void)setState:(CBPeripheralState)state {
   _state = state;
   if (_state == CBPeripheralStateDisconnected) {
-    _services.reset();
+    _services = nil;
   }
 }
 
@@ -128,13 +123,13 @@
 
 - (void)addServices:(NSArray*)services {
   if (!_services) {
-    _services.reset([[NSMutableArray alloc] init]);
+    _services = [[NSMutableArray alloc] init];
   }
   for (CBUUID* uuid in services) {
-    base::scoped_nsobject<MockCBService> service([[MockCBService alloc]
-        initWithPeripheral:self.peripheral
-                    CBUUID:uuid
-                   primary:YES]);
+    MockCBService* service =
+        [[MockCBService alloc] initWithPeripheral:self.peripheral
+                                           CBUUID:uuid
+                                          primary:YES];
     [_services addObject:[service service]];
   }
 }
@@ -144,11 +139,9 @@
 }
 
 - (void)removeService:(CBService*)service {
-  base::scoped_nsobject<CBService> serviceToRemove(service,
-                                                   base::scoped_policy::RETAIN);
-  DCHECK(serviceToRemove);
-  [_services removeObject:serviceToRemove];
-  [self didModifyServices:@[ serviceToRemove ]];
+  DCHECK(service);
+  [_services removeObject:service];
+  [self didModifyServices:@[ service ]];
 }
 
 - (void)mockDidDiscoverServices {
@@ -189,7 +182,7 @@
   // -[CBPeripheral discoverCharacteristics:forService:] for each services,
   // so -[<CBPeripheralDelegate peripheral:didDiscoverCharacteristicsForService:
   // error:] needs to be called for all services.
-  for (CBService* service in _services.get()) {
+  for (CBService* service in _services) {
     [self mockDidDiscoverCharacteristicsForService:service];
     for (CBCharacteristic* characteristic in service.characteristics) {
       // After discovering services, BluetoothLowEnergyDeviceMac is expected to
diff --git a/device/bluetooth/test/mock_bluetooth_cbservice_mac.h b/device/bluetooth/test/mock_bluetooth_cbservice_mac.h
index 6a087de..3c4ab2a9 100644
--- a/device/bluetooth/test/mock_bluetooth_cbservice_mac.h
+++ b/device/bluetooth/test/mock_bluetooth_cbservice_mac.h
@@ -23,8 +23,8 @@
                            primary:(BOOL)isPrimary;
 
 // Creates and adds a mock characteristic.
-- (void)addCharacteristicWithUUID:(CBUUID*)cb_uuid properties:(int)properties;
-- (void)removeCharacteristicMock:(MockCBCharacteristic*)characteristic_mock;
+- (void)addCharacteristicWithUUID:(CBUUID*)uuid properties:(int)properties;
+- (void)removeCharacteristicMock:(MockCBCharacteristic*)characteristicMock;
 
 @end
 
diff --git a/device/bluetooth/test/mock_bluetooth_cbservice_mac.mm b/device/bluetooth/test/mock_bluetooth_cbservice_mac.mm
index fe41b91d..0ebc6178 100644
--- a/device/bluetooth/test/mock_bluetooth_cbservice_mac.mm
+++ b/device/bluetooth/test/mock_bluetooth_cbservice_mac.mm
@@ -5,19 +5,21 @@
 #include "device/bluetooth/test/mock_bluetooth_cbservice_mac.h"
 
 #include "base/mac/foundation_util.h"
-#include "base/mac/scoped_nsobject.h"
 #include "device/bluetooth/test/bluetooth_test.h"
 #include "device/bluetooth/test/mock_bluetooth_cbcharacteristic_mac.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 using base::mac::ObjCCast;
-using base::scoped_nsobject;
 
 @interface MockCBService () {
   // Owner of this instance.
-  CBPeripheral* _peripheral;
-  scoped_nsobject<CBUUID> _UUID;
+  CBPeripheral* __weak _peripheral;
+  CBUUID* __strong _UUID;
   BOOL _primary;
-  scoped_nsobject<NSMutableArray> _characteristics;
+  NSMutableArray* __strong _characteristics;
 }
 
 @end
@@ -31,10 +33,10 @@
                            primary:(BOOL)isPrimary {
   self = [super init];
   if (self) {
-    _UUID.reset([uuid retain]);
+    _UUID = uuid;
     _primary = isPrimary;
     _peripheral = peripheral;
-    _characteristics.reset([[NSMutableArray alloc] init]);
+    _characteristics = [[NSMutableArray alloc] init];
   }
   return self;
 }
@@ -64,15 +66,15 @@
 }
 
 - (void)addCharacteristicWithUUID:(CBUUID*)cb_uuid properties:(int)properties {
-  scoped_nsobject<MockCBCharacteristic> characteristic_mock(
+  MockCBCharacteristic* characteristicMock =
       [[MockCBCharacteristic alloc] initWithService:self.service
                                              CBUUID:cb_uuid
-                                         properties:properties]);
-  [_characteristics addObject:characteristic_mock];
+                                         properties:properties];
+  [_characteristics addObject:characteristicMock];
 }
 
-- (void)removeCharacteristicMock:(MockCBCharacteristic*)characteristic_mock {
-  [_characteristics removeObject:characteristic_mock];
+- (void)removeCharacteristicMock:(MockCBCharacteristic*)characteristicMock {
+  [_characteristics removeObject:characteristicMock];
 }
 
 - (CBService*)service {
diff --git a/device/bluetooth/test/mock_bluetooth_central_manager_mac.h b/device/bluetooth/test/mock_bluetooth_central_manager_mac.h
index 61702b0..8b0ad4f2 100644
--- a/device/bluetooth/test/mock_bluetooth_central_manager_mac.h
+++ b/device/bluetooth/test/mock_bluetooth_central_manager_mac.h
@@ -20,7 +20,7 @@
 
 @property(nonatomic, assign) NSInteger scanForPeripheralsCallCount;
 @property(nonatomic, assign) NSInteger stopScanCallCount;
-@property(nonatomic, assign) id<CBCentralManagerDelegate> delegate;
+@property(nonatomic, weak) id<CBCentralManagerDelegate> delegate;
 @property(nonatomic, assign) CBManagerState state;
 @property(nonatomic, assign) device::BluetoothTestMac* bluetoothTestMac;
 @property(nonatomic, readonly) NSArray* retrieveConnectedPeripheralServiceUUIDs;
diff --git a/device/bluetooth/test/mock_bluetooth_central_manager_mac.mm b/device/bluetooth/test/mock_bluetooth_central_manager_mac.mm
index 416d62a..4c4ccd225 100644
--- a/device/bluetooth/test/mock_bluetooth_central_manager_mac.mm
+++ b/device/bluetooth/test/mock_bluetooth_central_manager_mac.mm
@@ -5,15 +5,16 @@
 #import "device/bluetooth/test/mock_bluetooth_central_manager_mac.h"
 
 #import "base/mac/foundation_util.h"
-#import "base/mac/scoped_nsobject.h"
 #import "device/bluetooth/test/bluetooth_test_mac.h"
 #import "device/bluetooth/test/mock_bluetooth_cbperipheral_mac.h"
 
-using base::scoped_nsobject;
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
 
 @implementation MockCentralManager {
-  scoped_nsobject<NSMutableDictionary> _connectedMockPeripheralPerServiceUUID;
-  scoped_nsobject<NSMutableArray> _retrieveConnectedPeripheralServiceUUIDs;
+  NSMutableDictionary* __strong _connectedMockPeripheralPerServiceUUID;
+  NSMutableArray* __strong _retrieveConnectedPeripheralServiceUUIDs;
 }
 
 @synthesize scanForPeripheralsCallCount = _scanForPeripheralsCallCount;
@@ -25,10 +26,8 @@
 - (instancetype)init {
   self = [super init];
   if (self) {
-    _connectedMockPeripheralPerServiceUUID.reset(
-        [[NSMutableDictionary alloc] init]);
-    _retrieveConnectedPeripheralServiceUUIDs.reset(
-        [[NSMutableArray alloc] init]);
+    _connectedMockPeripheralPerServiceUUID = [[NSMutableDictionary alloc] init];
+    _retrieveConnectedPeripheralServiceUUIDs = [[NSMutableArray alloc] init];
   }
   return self;
 }
@@ -78,7 +77,7 @@
 }
 
 - (NSArray*)retrieveConnectedPeripheralServiceUUIDs {
-  return [[_retrieveConnectedPeripheralServiceUUIDs copy] autorelease];
+  return _retrieveConnectedPeripheralServiceUUIDs;
 }
 
 - (NSArray*)retrieveConnectedPeripheralsWithServices:(NSArray*)services {
diff --git a/gpu/command_buffer/client/shared_image_interface.h b/gpu/command_buffer/client/shared_image_interface.h
index aebfbd0..1d10692 100644
--- a/gpu/command_buffer/client/shared_image_interface.h
+++ b/gpu/command_buffer/client/shared_image_interface.h
@@ -73,6 +73,9 @@
   // which is used to populate the SharedImage.  |pixel_data| should have the
   // same format which would be passed to glTexImage2D to populate a similarly
   // specified texture.
+  // TODO(crbug.com/1447106): Have the caller specify a row span for
+  // |pixel_data| explicitly. Some backings have different row alignment
+  // requirements which the caller has to match exactly or it won't work.
   virtual Mailbox CreateSharedImage(viz::SharedImageFormat format,
                                     const gfx::Size& size,
                                     const gfx::ColorSpace& color_space,
diff --git a/gpu/command_buffer/service/service_transfer_cache.cc b/gpu/command_buffer/service/service_transfer_cache.cc
index b9755757..9b0d81a 100644
--- a/gpu/command_buffer/service/service_transfer_cache.cc
+++ b/gpu/command_buffer/service/service_transfer_cache.cc
@@ -256,10 +256,17 @@
   if (!found) {
     return nullptr;
   }
-  UMA_HISTOGRAM_LONG_TIMES("GPU.TransferCache.TimeSinceLastUse",
-                           base::TimeTicks::Now() - entry->second.last_use);
-  entry->second.last_use = base::TimeTicks::Now();
+  base::TimeTicks now = base::TimeTicks::Now();
+  base::TimeDelta last_use_delta = now - entry->second.last_use;
+  if (last_use_delta > entry->second.max_last_use_delta) {
+    entry->second.max_last_use_delta = last_use_delta;
+  }
+  entry->second.last_use = now;
   entry->second.num_reuse++;
+  UMA_HISTOGRAM_LONG_TIMES("GPU.TransferCache.TimeSinceLastUse",
+                           last_use_delta);
+  UMA_HISTOGRAM_LONG_TIMES("GPU.TransferCache.MaxHistoricalTimeSinceLastUse",
+                           entry->second.max_last_use_delta);
   return entry->second.entry.get();
 }
 
diff --git a/gpu/command_buffer/service/service_transfer_cache.h b/gpu/command_buffer/service/service_transfer_cache.h
index 6ac6424..f185630 100644
--- a/gpu/command_buffer/service/service_transfer_cache.h
+++ b/gpu/command_buffer/service/service_transfer_cache.h
@@ -119,6 +119,7 @@
     // For metrics.
     uint32_t num_reuse = 0u;
     base::TimeTicks last_use = base::TimeTicks::Now();
+    base::TimeDelta max_last_use_delta;
   };
 
   struct EntryKeyComp {
diff --git a/gpu/command_buffer/service/shared_image/ozone_image_backing_factory.cc b/gpu/command_buffer/service/shared_image/ozone_image_backing_factory.cc
index fe2ca650..894ce90 100644
--- a/gpu/command_buffer/service/shared_image/ozone_image_backing_factory.cc
+++ b/gpu/command_buffer/service/shared_image/ozone_image_backing_factory.cc
@@ -19,6 +19,8 @@
 #include "gpu/command_buffer/service/shared_image/ozone_image_backing.h"
 #include "gpu/command_buffer/service/shared_image/shared_image_format_service_utils.h"
 #include "gpu/command_buffer/service/shared_memory_region_wrapper.h"
+#include "gpu/config/gpu_finch_features.h"
+#include "ui/gfx/buffer_types.h"
 #include "ui/gfx/gpu_memory_buffer.h"
 #include "ui/gfx/native_pixmap.h"
 #include "ui/gl/buildflags.h"
@@ -40,6 +42,12 @@
     // Just use SCANOUT for WebGPU since the memory doesn't need to be linear.
     return gfx::BufferUsage::SCANOUT;
   } else if (usage & SHARED_IMAGE_USAGE_SCANOUT) {
+    if (base::FeatureList::IsEnabled(features::kOzoneFrontBufferUsage) &&
+        usage & SHARED_IMAGE_USAGE_CONCURRENT_READ_WRITE) {
+      // Example usage here is low latency (desynchronized) 2d canvas. Note that
+      // this does not imply CPU read/write.
+      return gfx::BufferUsage::SCANOUT_FRONT_RENDERING;
+    }
     return gfx::BufferUsage::SCANOUT;
   } else {
     return gfx::BufferUsage::GPU_READ;
diff --git a/gpu/config/gpu_finch_features.cc b/gpu/config/gpu_finch_features.cc
index e9707c05a..eb80578 100644
--- a/gpu/config/gpu_finch_features.cc
+++ b/gpu/config/gpu_finch_features.cc
@@ -154,6 +154,14 @@
 #endif
 );
 
+#if BUILDFLAG(IS_OZONE)
+// Detect front buffering condition and set buffer usage as such.
+// This is a killswitch to be removed once launched.
+BASE_FEATURE(kOzoneFrontBufferUsage,
+             "OzoneFrontBufferUsage",
+             base::FEATURE_ENABLED_BY_DEFAULT);
+#endif  // BUILDFLAG(IS_OZONE)
+
 // Enables the use of MSAA in skia on Ice Lake and later intel architectures.
 BASE_FEATURE(kEnableMSAAOnNewIntelGPUs,
              "EnableMSAAOnNewIntelGPUs",
diff --git a/gpu/config/gpu_finch_features.h b/gpu/config/gpu_finch_features.h
index d606e7f..7857915 100644
--- a/gpu/config/gpu_finch_features.h
+++ b/gpu/config/gpu_finch_features.h
@@ -32,6 +32,10 @@
 
 GPU_EXPORT BASE_DECLARE_FEATURE(kCanvasOopRasterization);
 
+#if BUILDFLAG(IS_OZONE)
+GPU_EXPORT BASE_DECLARE_FEATURE(kOzoneFrontBufferUsage);
+#endif
+
 GPU_EXPORT BASE_DECLARE_FEATURE(kEnableMSAAOnNewIntelGPUs);
 
 GPU_EXPORT BASE_DECLARE_FEATURE(kDefaultEnableANGLEValidation);
diff --git a/gpu/ipc/client/client_shared_image_interface.cc b/gpu/ipc/client/client_shared_image_interface.cc
index c9fba76..4cfa014 100644
--- a/gpu/ipc/client/client_shared_image_interface.cc
+++ b/gpu/ipc/client/client_shared_image_interface.cc
@@ -104,6 +104,11 @@
   // Pixel upload path only supports single-planar formats.
   DCHECK(format.is_single_plane());
   DCHECK(gpu::IsValidClientUsage(usage));
+
+  // EstimatedSizeInBytes() returns the minimum size in bytes needed to store
+  // `format` at `size` so if span is smaller there is a problem.
+  CHECK_GE(pixel_data.size(), format.EstimatedSizeInBytes(size));
+
   return AddMailbox(proxy_->CreateSharedImage(format, size, color_space,
                                               surface_origin, alpha_type, usage,
                                               debug_label, pixel_data));
diff --git a/infra/config/generated/builders/ci/lacros-arm64-generic-rel-skylab-fyi-tests/properties.json b/infra/config/generated/builders/ci/lacros-arm64-generic-rel-skylab-fyi-tests/properties.json
new file mode 100644
index 0000000..c1139ab
--- /dev/null
+++ b/infra/config/generated/builders/ci/lacros-arm64-generic-rel-skylab-fyi-tests/properties.json
@@ -0,0 +1,105 @@
+{
+  "$build/chromium_tests_builder_config": {
+    "builder_config": {
+      "builder_db": {
+        "entries": [
+          {
+            "builder_id": {
+              "bucket": "ci",
+              "builder": "lacros-arm64-generic-rel-skylab-fyi",
+              "project": "chromium"
+            },
+            "builder_spec": {
+              "build_gs_bucket": "chromium-fyi-archive",
+              "builder_group": "chromium.fyi",
+              "execution_mode": "COMPILE_AND_TEST",
+              "legacy_chromium_config": {
+                "apply_configs": [
+                  "mb",
+                  "mb_no_luci_auth"
+                ],
+                "config": "chromium",
+                "cros_boards_with_qemu_images": [
+                  "arm64-generic"
+                ],
+                "target_bits": 64,
+                "target_cros_boards": [
+                  "kevin:jacuzzi"
+                ],
+                "target_platform": "chromeos"
+              },
+              "legacy_gclient_config": {
+                "apply_configs": [
+                  "chromeos",
+                  "checkout_lacros_sdk"
+                ],
+                "config": "chromium"
+              },
+              "skylab_upload_location": {
+                "gs_bucket": "chromium-ci-skylab"
+              }
+            }
+          },
+          {
+            "builder_id": {
+              "bucket": "ci",
+              "builder": "lacros-arm64-generic-rel-skylab-fyi-tests",
+              "project": "chromium"
+            },
+            "builder_spec": {
+              "build_gs_bucket": "chromium-fyi-archive",
+              "builder_group": "chromium.fyi",
+              "execution_mode": "TEST",
+              "legacy_chromium_config": {
+                "apply_configs": [
+                  "mb",
+                  "mb_no_luci_auth"
+                ],
+                "config": "chromium",
+                "cros_boards_with_qemu_images": [
+                  "arm64-generic"
+                ],
+                "target_bits": 64,
+                "target_cros_boards": [
+                  "kevin:jacuzzi"
+                ],
+                "target_platform": "chromeos"
+              },
+              "legacy_gclient_config": {
+                "apply_configs": [
+                  "chromeos",
+                  "checkout_lacros_sdk"
+                ],
+                "config": "chromium"
+              },
+              "parent": {
+                "bucket": "ci",
+                "builder": "lacros-arm64-generic-rel-skylab-fyi",
+                "project": "chromium"
+              },
+              "skylab_upload_location": {
+                "gs_bucket": "chromium-ci-skylab"
+              }
+            }
+          }
+        ]
+      },
+      "builder_ids": [
+        {
+          "bucket": "ci",
+          "builder": "lacros-arm64-generic-rel-skylab-fyi-tests",
+          "project": "chromium"
+        }
+      ]
+    }
+  },
+  "$recipe_engine/resultdb/test_presentation": {
+    "column_keys": [],
+    "grouping_keys": [
+      "status",
+      "v.test_suite"
+    ]
+  },
+  "builder_group": "chromium.fyi",
+  "recipe": "chromium"
+}
\ No newline at end of file
diff --git a/infra/config/generated/builders/ci/lacros-arm64-generic-rel-skylab-fyi/properties.json b/infra/config/generated/builders/ci/lacros-arm64-generic-rel-skylab-fyi/properties.json
index 3bdd99dd..86058d4c 100644
--- a/infra/config/generated/builders/ci/lacros-arm64-generic-rel-skylab-fyi/properties.json
+++ b/infra/config/generated/builders/ci/lacros-arm64-generic-rel-skylab-fyi/properties.json
@@ -39,6 +39,48 @@
                 "gs_bucket": "chromium-ci-skylab"
               }
             }
+          },
+          {
+            "builder_id": {
+              "bucket": "ci",
+              "builder": "lacros-arm64-generic-rel-skylab-fyi-tests",
+              "project": "chromium"
+            },
+            "builder_spec": {
+              "build_gs_bucket": "chromium-fyi-archive",
+              "builder_group": "chromium.fyi",
+              "execution_mode": "TEST",
+              "legacy_chromium_config": {
+                "apply_configs": [
+                  "mb",
+                  "mb_no_luci_auth"
+                ],
+                "config": "chromium",
+                "cros_boards_with_qemu_images": [
+                  "arm64-generic"
+                ],
+                "target_bits": 64,
+                "target_cros_boards": [
+                  "kevin:jacuzzi"
+                ],
+                "target_platform": "chromeos"
+              },
+              "legacy_gclient_config": {
+                "apply_configs": [
+                  "chromeos",
+                  "checkout_lacros_sdk"
+                ],
+                "config": "chromium"
+              },
+              "parent": {
+                "bucket": "ci",
+                "builder": "lacros-arm64-generic-rel-skylab-fyi",
+                "project": "chromium"
+              },
+              "skylab_upload_location": {
+                "gs_bucket": "chromium-ci-skylab"
+              }
+            }
           }
         ]
       },
@@ -48,6 +90,13 @@
           "builder": "lacros-arm64-generic-rel-skylab-fyi",
           "project": "chromium"
         }
+      ],
+      "builder_ids_in_scope_for_testing": [
+        {
+          "bucket": "ci",
+          "builder": "lacros-arm64-generic-rel-skylab-fyi-tests",
+          "project": "chromium"
+        }
       ]
     }
   },
diff --git a/infra/config/generated/luci/cr-buildbucket.cfg b/infra/config/generated/luci/cr-buildbucket.cfg
index bc997a26..a0e344af 100644
--- a/infra/config/generated/luci/cr-buildbucket.cfg
+++ b/infra/config/generated/luci/cr-buildbucket.cfg
@@ -2242,7 +2242,7 @@
       dimensions: "cores:8"
       dimensions: "cpu:x86-64"
       dimensions: "free_space:high"
-      dimensions: "os:Ubuntu-18.04"
+      dimensions: "os:Ubuntu-22.04"
       dimensions: "pool:luci.chromium.ci"
       dimensions: "ssd:0"
       exe {
@@ -11035,7 +11035,7 @@
       dimensions: "builder:Libfuzzer Upload Linux UBSan"
       dimensions: "cores:8"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Ubuntu-18.04"
+      dimensions: "os:Ubuntu-22.04"
       dimensions: "pool:luci.chromium.ci"
       exe {
         cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
@@ -19934,6 +19934,7 @@
       name: "ToTMac"
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builderless:1"
+      dimensions: "cores:12"
       dimensions: "cpu:x86-64"
       dimensions: "free_space:standard"
       dimensions: "os:Mac-12|Mac-13"
@@ -20020,6 +20021,7 @@
       name: "ToTMac (dbg)"
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builderless:1"
+      dimensions: "cores:12"
       dimensions: "cpu:x86-64"
       dimensions: "free_space:standard"
       dimensions: "os:Mac-12|Mac-13"
@@ -20106,6 +20108,7 @@
       name: "ToTMacASan"
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builderless:1"
+      dimensions: "cores:12"
       dimensions: "cpu:x86-64"
       dimensions: "free_space:standard"
       dimensions: "os:Mac-12|Mac-13"
@@ -20192,6 +20195,7 @@
       name: "ToTMacCoverage"
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builderless:1"
+      dimensions: "cores:12"
       dimensions: "cpu:x86-64"
       dimensions: "free_space:standard"
       dimensions: "os:Mac-12|Mac-13"
@@ -27719,7 +27723,7 @@
       dimensions: "builder:android-fieldtrial-rel"
       dimensions: "cores:8"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Ubuntu-18.04"
+      dimensions: "os:Ubuntu-22.04"
       dimensions: "pool:luci.chromium.ci"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -35686,6 +35690,91 @@
       }
     }
     builders {
+      name: "lacros-arm64-generic-rel-skylab-fyi-tests"
+      swarming_host: "chromium-swarm.appspot.com"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "free_space:standard"
+      dimensions: "os:Ubuntu-18.04"
+      dimensions: "pool:luci.chromium.ci"
+      dimensions: "ssd:0"
+      exe {
+        cipd_package: "infra/chromium/bootstrapper/${platform}"
+        cipd_version: "latest"
+        cmd: "bootstrapper"
+      }
+      properties:
+        '{'
+        '  "$bootstrap/exe": {'
+        '    "exe": {'
+        '      "cipd_package": "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build",'
+        '      "cipd_version": "refs/heads/main",'
+        '      "cmd": ['
+        '        "luciexe"'
+        '      ]'
+        '    }'
+        '  },'
+        '  "$bootstrap/properties": {'
+        '    "properties_file": "infra/config/generated/builders/ci/lacros-arm64-generic-rel-skylab-fyi-tests/properties.json",'
+        '    "top_level_project": {'
+        '      "ref": "refs/heads/main",'
+        '      "repo": {'
+        '        "host": "chromium.googlesource.com",'
+        '        "project": "chromium/src"'
+        '      }'
+        '    }'
+        '  },'
+        '  "builder_group": "chromium.fyi",'
+        '  "led_builder_is_bootstrapped": true,'
+        '  "recipe": "chromium"'
+        '}'
+      priority: 35
+      execution_timeout_secs: 36000
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+      experiments {
+        key: "chromium_swarming.expose_merge_script_failures"
+        value: 100
+      }
+      experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      resultdb {
+        enable: true
+        bq_exports {
+          project: "chrome-luci-data"
+          dataset: "chromium"
+          table: "ci_test_results"
+          test_results {}
+        }
+        bq_exports {
+          project: "chrome-luci-data"
+          dataset: "chromium"
+          table: "gpu_ci_test_results"
+          test_results {
+            predicate {
+              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+            }
+          }
+        }
+        bq_exports {
+          project: "chrome-luci-data"
+          dataset: "chromium"
+          table: "blink_web_tests_ci_test_results"
+          test_results {
+            predicate {
+              test_id_regexp: "(ninja://[^/]*blink_web_tests/.+)|(ninja://[^/]*blink_wpt_tests/.+)"
+            }
+          }
+        }
+        history_options {
+          use_invocation_timestamp: true
+        }
+      }
+    }
+    builders {
       name: "lacros64-archive-rel"
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builderless:1"
@@ -80323,7 +80412,7 @@
       dimensions: "builder:linux_optional_gpu_tests_rel"
       dimensions: "cores:8"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Ubuntu-18.04"
+      dimensions: "os:Ubuntu-22.04"
       dimensions: "pool:luci.chromium.try"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
diff --git a/infra/config/generated/luci/luci-milo.cfg b/infra/config/generated/luci/luci-milo.cfg
index 0c09d57..cdd623a 100644
--- a/infra/config/generated/luci/luci-milo.cfg
+++ b/infra/config/generated/luci/luci-milo.cfg
@@ -9604,6 +9604,11 @@
     short_name: "arm"
   }
   builders {
+    name: "buildbucket/luci.chromium.ci/lacros-arm64-generic-rel-skylab-fyi-tests"
+    category: "lacros"
+    short_name: "fyi-tst"
+  }
+  builders {
     name: "buildbucket/luci.chromium.ci/lacros-arm64-generic-rel-skylab-fyi"
     category: "lacros"
     short_name: "larsf"
diff --git a/infra/config/generated/luci/luci-scheduler.cfg b/infra/config/generated/luci/luci-scheduler.cfg
index 341f3f8..08313fc 100644
--- a/infra/config/generated/luci/luci-scheduler.cfg
+++ b/infra/config/generated/luci/luci-scheduler.cfg
@@ -4523,6 +4523,15 @@
   }
 }
 job {
+  id: "lacros-arm64-generic-rel-skylab-fyi-tests"
+  realm: "ci"
+  buildbucket {
+    server: "cr-buildbucket.appspot.com"
+    bucket: "ci"
+    builder: "lacros-arm64-generic-rel-skylab-fyi-tests"
+  }
+}
+job {
   id: "lacros-coordinator"
   realm: "reviver"
   schedule: "0 3,5,7,9 * * *"
diff --git a/infra/config/generated/luci/realms.cfg b/infra/config/generated/luci/realms.cfg
index 441e1fe1..582a87f 100644
--- a/infra/config/generated/luci/realms.cfg
+++ b/infra/config/generated/luci/realms.cfg
@@ -164,6 +164,7 @@
         values: "android-webview-12-x64-dbg-tests"
         values: "android-webview-13-x64-dbg-tests"
         values: "fuchsia-fyi-x64-dbg-persistent-emulator"
+        values: "lacros-arm64-generic-rel-skylab-fyi-tests"
         values: "linux-lacros-dbg-tests-fyi"
         values: "linux-lacros-tester-fyi-rel"
         values: "linux-lacros-tester-rel"
diff --git a/infra/config/lib/linux-default.json b/infra/config/lib/linux-default.json
index bf8a953..5c24624 100644
--- a/infra/config/lib/linux-default.json
+++ b/infra/config/lib/linux-default.json
@@ -18,6 +18,7 @@
     "Android arm64 Builder (dbg)": "Ubuntu-22.04",
     "Android arm64 Builder All Targets (dbg)": "Ubuntu-22.04",
     "Android x64 Builder (dbg)": "Ubuntu-22.04",
+    "Android x64 Builder All Targets (dbg)": "Ubuntu-22.04",
     "Android x86 Builder (dbg)": "Ubuntu-22.04",
     "CFI Linux CF": "Ubuntu-22.04",
     "CFI Linux ToT": "Ubuntu-22.04",
@@ -82,6 +83,7 @@
     "Leak Detection Linux": "Ubuntu-22.04",
     "Libfuzzer Upload Linux ASan": "Ubuntu-22.04",
     "Libfuzzer Upload Linux ASan Debug": "Ubuntu-22.04",
+    "Libfuzzer Upload Linux UBSan": "Ubuntu-22.04",
     "Libfuzzer Upload Linux V8-ARM64 ASan": "Ubuntu-22.04",
     "Libfuzzer Upload Linux32 V8-ARM ASan Debug": "Ubuntu-22.04",
     "Linux ASan LSan Builder": "Ubuntu-22.04",
@@ -189,6 +191,7 @@
     "android-cronet-x86-dbg-oreo-tests": "Ubuntu-22.04",
     "android-cronet-x86-rel": "Ubuntu-22.04",
     "android-cronet-x86-rel-kitkat-tests": "Ubuntu-22.04",
+    "android-fieldtrial-rel": "Ubuntu-22.04",
     "android-nougat-x86-rel": "Ubuntu-22.04",
     "android-official": "Ubuntu-22.04",
     "android-perfetto-rel": "Ubuntu-22.04",
@@ -484,6 +487,7 @@
     "linux_chromium_tsan_rel_ng": "Ubuntu-22.04",
     "linux_chromium_tsan_rel_ng-compilator": "Ubuntu-22.04",
     "linux_chromium_ubsan_rel_ng": "Ubuntu-22.04",
+    "linux_optional_gpu_tests_rel": "Ubuntu-22.04",
     "linux_vr": "Ubuntu-22.04",
     "mac-rel": "Ubuntu-22.04",
     "network_service_linux": "Ubuntu-22.04",
diff --git a/infra/config/subprojects/chromium/ci/chromium.clang.star b/infra/config/subprojects/chromium/ci/chromium.clang.star
index 2b4f446..e9079bef 100644
--- a/infra/config/subprojects/chromium/ci/chromium.clang.star
+++ b/infra/config/subprojects/chromium/ci/chromium.clang.star
@@ -65,7 +65,7 @@
     ("clang-tot-device", "iOS|internal", "dev"),
 )]
 
-def clang_mac_builder(*, name, cores = 24, **kwargs):
+def clang_mac_builder(*, name, cores = 12, **kwargs):
     return ci.builder(
         name = name,
         cores = cores,
@@ -417,7 +417,6 @@
 
 clang_mac_builder(
     name = "ToTMac",
-    cores = None,
     console_view_entry = consoles.console_view_entry(
         category = "ToT Mac",
         short_name = "rel",
@@ -427,7 +426,6 @@
 
 clang_mac_builder(
     name = "ToTMac (dbg)",
-    cores = None,
     console_view_entry = consoles.console_view_entry(
         category = "ToT Mac",
         short_name = "dbg",
@@ -437,7 +435,6 @@
 
 clang_mac_builder(
     name = "ToTMacASan",
-    cores = None,
     console_view_entry = consoles.console_view_entry(
         category = "ToT Mac",
         short_name = "asn",
@@ -448,7 +445,6 @@
 clang_mac_builder(
     name = "ToTMacCoverage",
     executable = "recipe:chromium_clang_coverage_tot",
-    cores = None,
     console_view_entry = consoles.console_view_entry(
         category = "ToT Code Coverage",
         short_name = "mac",
diff --git a/infra/config/subprojects/chromium/ci/chromium.fyi.star b/infra/config/subprojects/chromium/ci/chromium.fyi.star
index f898dc36..0b721d96 100644
--- a/infra/config/subprojects/chromium/ci/chromium.fyi.star
+++ b/infra/config/subprojects/chromium/ci/chromium.fyi.star
@@ -243,6 +243,38 @@
     ),
 )
 
+ci.thin_tester(
+    name = "lacros-arm64-generic-rel-skylab-fyi-tests",
+    triggered_by = ["lacros-arm64-generic-rel-skylab-fyi"],
+    builder_spec = builder_config.builder_spec(
+        execution_mode = builder_config.execution_mode.TEST,
+        gclient_config = builder_config.gclient_config(
+            config = "chromium",
+            apply_configs = [
+                "chromeos",
+                "checkout_lacros_sdk",
+            ],
+        ),
+        chromium_config = builder_config.chromium_config(
+            config = "chromium",
+            apply_configs = ["mb", "mb_no_luci_auth"],
+            target_bits = 64,
+            target_platform = "chromeos",
+            target_cros_boards = "kevin:jacuzzi",
+            cros_boards_with_qemu_images = "arm64-generic",
+        ),
+        build_gs_bucket = "chromium-fyi-archive",
+        skylab_upload_location = builder_config.skylab_upload_location(
+            gs_bucket = "chromium-ci-skylab",
+        ),
+    ),
+    os = os.LINUX_DEFAULT,
+    console_view_entry = consoles.console_view_entry(
+        category = "lacros",
+        short_name = "fyi-tst",
+    ),
+)
+
 ci.builder(
     name = "linux-annotator-rel",
     builder_spec = builder_config.builder_spec(
@@ -481,7 +513,7 @@
         build_gs_bucket = "chromium-android-archive",
     ),
     builderless = False,
-    os = os.LINUX_BIONIC,
+    os = os.LINUX_DEFAULT,
     console_view_entry = consoles.console_view_entry(
         category = "android",
     ),
diff --git a/ios/chrome/app/app_startup_parameters.h b/ios/chrome/app/app_startup_parameters.h
index 2ffc5c10..820b58e 100644
--- a/ios/chrome/app/app_startup_parameters.h
+++ b/ios/chrome/app/app_startup_parameters.h
@@ -26,6 +26,7 @@
   START_QR_CODE_SCANNER,
   START_LENS_FROM_HOME_SCREEN_WIDGET,
   START_LENS_FROM_APP_ICON_LONG_PRESS,
+  START_LENS_FROM_SPOTLIGHT,
   FOCUS_OMNIBOX,
   SHOW_DEFAULT_BROWSER_SETTINGS,
   TAB_OPENING_POST_OPENING_ACTION_COUNT,
diff --git a/ios/chrome/app/app_startup_parameters.mm b/ios/chrome/app/app_startup_parameters.mm
index b09ed1a..0f75d76e5 100644
--- a/ios/chrome/app/app_startup_parameters.mm
+++ b/ios/chrome/app/app_startup_parameters.mm
@@ -80,6 +80,7 @@
       break;
     case START_LENS_FROM_APP_ICON_LONG_PRESS:
     case START_LENS_FROM_HOME_SCREEN_WIDGET:
+    case START_LENS_FROM_SPOTLIGHT:
       [description appendString:@", should launch Lens"];
       break;
     case START_VOICE_SEARCH:
diff --git a/ios/chrome/app/application_delegate/user_activity_handler.mm b/ios/chrome/app/application_delegate/user_activity_handler.mm
index fabf5c788..69f9125 100644
--- a/ios/chrome/app/application_delegate/user_activity_handler.mm
+++ b/ios/chrome/app/application_delegate/user_activity_handler.mm
@@ -55,7 +55,9 @@
 NSString* const kShortcutNewIncognitoSearch = @"OpenIncognitoSearch";
 NSString* const kShortcutVoiceSearch = @"OpenVoiceSearch";
 NSString* const kShortcutQRScanner = @"OpenQRScanner";
-NSString* const kShortcutLens = @"OpenLens";
+NSString* const kShortcutLensFromAppIconLongPress =
+    @"OpenLensFromAppIconLongPress";
+NSString* const kShortcutLensFromSpotlight = @"OpenLensFromSpotlight";
 
 // Constants for Siri shortcut.
 NSString* const kSiriShortcutOpenInChrome = @"OpenInChromeIntent";
@@ -80,7 +82,8 @@
       [activityType isEqualToString:kShortcutNewSearch] ||
       [activityType isEqualToString:kShortcutVoiceSearch] ||
       [activityType isEqualToString:kShortcutQRScanner] ||
-      [activityType isEqualToString:kShortcutLens] ||
+      [activityType isEqualToString:kShortcutLensFromAppIconLongPress] ||
+      [activityType isEqualToString:kShortcutLensFromSpotlight] ||
       [activityType isEqualToString:kSiriShortcutSearchInChrome] ||
       [activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) {
     return @[ kRegularMode, kIncognitoMode ];
@@ -630,14 +633,19 @@
     startupParams.postOpeningAction = START_QR_CODE_SCANNER;
     connectionInformation.startupParameters = startupParams;
     return YES;
-  } else if ([shortcutItem.type isEqualToString:kShortcutLens]) {
-    // Use a specific action id, as other Lens startup entry points may be added
-    // in the future (e.g. Spotlight).
+  } else if ([shortcutItem.type
+                 isEqualToString:kShortcutLensFromAppIconLongPress]) {
     base::RecordAction(UserMetricsAction(
         "ApplicationShortcut.LensPressedFromAppIconLongPress"));
     startupParams.postOpeningAction = START_LENS_FROM_APP_ICON_LONG_PRESS;
     connectionInformation.startupParameters = startupParams;
     return YES;
+  } else if ([shortcutItem.type isEqualToString:kShortcutLensFromSpotlight]) {
+    base::RecordAction(
+        UserMetricsAction("ApplicationShortcut.LensPressedFromSpotlight"));
+    startupParams.postOpeningAction = START_LENS_FROM_SPOTLIGHT;
+    connectionInformation.startupParameters = startupParams;
+    return YES;
   }
 
   // Use 16 as the maximum length of the reported value for this key (15
diff --git a/ios/chrome/app/application_delegate/user_activity_handler_unittest.mm b/ios/chrome/app/application_delegate/user_activity_handler_unittest.mm
index 34f52618..5892660 100644
--- a/ios/chrome/app/application_delegate/user_activity_handler_unittest.mm
+++ b/ios/chrome/app/application_delegate/user_activity_handler_unittest.mm
@@ -787,7 +787,12 @@
     @[ @"OpenNewSearch", @NO, @(FOCUS_OMNIBOX) ],
     @[ @"OpenIncognitoSearch", @YES, @(FOCUS_OMNIBOX) ],
     @[ @"OpenVoiceSearch", @NO, @(START_VOICE_SEARCH) ],
-    @[ @"OpenQRScanner", @NO, @(START_QR_CODE_SCANNER) ]
+    @[ @"OpenQRScanner", @NO, @(START_QR_CODE_SCANNER) ],
+    @[
+      @"OpenLensFromAppIconLongPress", @NO,
+      @(START_LENS_FROM_APP_ICON_LONG_PRESS)
+    ],
+    @[ @"OpenLensFromSpotlight", @NO, @(START_LENS_FROM_SPOTLIGHT) ]
   ];
 
   swizzleHandleStartupParameters();
diff --git a/ios/chrome/app/spotlight/BUILD.gn b/ios/chrome/app/spotlight/BUILD.gn
index e345931..7684fa0 100644
--- a/ios/chrome/app/spotlight/BUILD.gn
+++ b/ios/chrome/app/spotlight/BUILD.gn
@@ -34,6 +34,7 @@
     "//components/history/core/browser",
     "//components/reading_list/core",
     "//components/reading_list/ios",
+    "//components/search_engines",
     "//ios/chrome/app",
     "//ios/chrome/app/strings",
     "//ios/chrome/browser/bookmarks",
@@ -41,9 +42,12 @@
     "//ios/chrome/browser/flags:system_flags",
     "//ios/chrome/browser/history",
     "//ios/chrome/browser/reading_list",
+    "//ios/chrome/browser/search_engines:template_url_service_factory",
     "//ios/chrome/browser/shared/public/features",
     "//ios/chrome/browser/sync",
     "//ios/chrome/browser/ui/content_suggestions",
+    "//ios/chrome/browser/ui/lens:lens_availability",
+    "//ios/chrome/browser/ui/lens:lens_entrypoint",
     "//ios/chrome/common/app_group",
     "//ios/third_party/material_components_ios",
     "//net",
diff --git a/ios/chrome/app/spotlight/actions_spotlight_manager.h b/ios/chrome/app/spotlight/actions_spotlight_manager.h
index 7d7b80b..b72c2bf 100644
--- a/ios/chrome/app/spotlight/actions_spotlight_manager.h
+++ b/ios/chrome/app/spotlight/actions_spotlight_manager.h
@@ -36,7 +36,8 @@
 
 // Updates the index with the Spotlight actions if the EnableSpotlightActions
 // experimental flag is set. Otherwise the index is only cleared.
-- (void)indexActions;
+- (void)indexActionsWithIsGoogleDefaultSearchEngine:
+    (BOOL)isGoogleDefaultSearchEngine;
 
 @end
 
diff --git a/ios/chrome/app/spotlight/actions_spotlight_manager.mm b/ios/chrome/app/spotlight/actions_spotlight_manager.mm
index dc4bacb..56c476c 100644
--- a/ios/chrome/app/spotlight/actions_spotlight_manager.mm
+++ b/ios/chrome/app/spotlight/actions_spotlight_manager.mm
@@ -12,6 +12,8 @@
 #import "ios/chrome/app/app_startup_parameters.h"
 #import "ios/chrome/app/spotlight/spotlight_interface.h"
 #import "ios/chrome/app/spotlight/spotlight_logger.h"
+#import "ios/chrome/browser/ui/lens/lens_availability.h"
+#import "ios/chrome/browser/ui/lens/lens_entrypoint.h"
 #import "ios/chrome/common/app_group/app_group_constants.h"
 #import "ios/chrome/grit/ios_strings.h"
 #import "net/base/mac/url_conversions.h"
@@ -44,6 +46,7 @@
 const char kSpotlightActionVoiceSearch[] = "OpenVoiceSearch";
 const char kSpotlightActionQRScanner[] = "OpenQRScanner";
 const char kSpotlightActionSetDefaultBrowser[] = "SetDefaultBrowser";
+const char kSpotlightActionLens[] = "OpenLensFromSpotlight";
 
 // Enum is used to record the actions performed by the user.
 enum {
@@ -57,6 +60,8 @@
   SPOTLIGHT_ACTION_QR_CODE_SCANNER_PRESSED,
   // Recorded when a user pressed the Set Default Browser spotlight action.
   SPOTLIGHT_ACTION_SET_DEFAULT_BROWSER_PRESSED,
+  // Recorded when a user pressed the Lens spotlight action.
+  SPOTLIGHT_ACTION_LENS_PRESSED,
   // NOTE: Add new spotlight actions in sources only immediately above this
   // line. Also, make sure the enum list for histogram `SpotlightActions` in
   // histograms.xml is updated with any change in here.
@@ -107,6 +112,13 @@
                               URLWithString:UIApplicationOpenSettingsURLString]
                   options:{}
         completionHandler:nil];
+  } else if ([action isEqualToString:base::SysUTF8ToNSString(
+                                         kSpotlightActionLens)]) {
+    UMA_HISTOGRAM_ENUMERATION(kSpotlightActionsHistogram,
+                              SPOTLIGHT_ACTION_LENS_PRESSED,
+                              SPOTLIGHT_ACTION_COUNT);
+    [startupParams setApplicationMode:ApplicationModeForTabOpening::NORMAL];
+    [startupParams setPostOpeningAction:START_LENS_FROM_SPOTLIGHT];
   } else {
     return NO;
   }
@@ -120,7 +132,8 @@
 - (CSSearchableItem*)itemForAction:(NSString*)action title:(NSString*)title;
 
 // Clears and re-inserts all Spotlight actions.
-- (void)clearAndAddSpotlightActions;
+- (void)clearAndAddSpotlightActionsWithIsGoogleDefaultSearchEngine:
+    (BOOL)isGoogleDefaultSearchEngine;
 
 @end
 
@@ -135,19 +148,22 @@
 
 #pragma mark public methods
 
-- (void)indexActions {
+- (void)indexActionsWithIsGoogleDefaultSearchEngine:
+    (BOOL)isGoogleDefaultSearchEngine {
   __weak ActionsSpotlightManager* weakSelf = self;
   dispatch_after(
       dispatch_time(DISPATCH_TIME_NOW, static_cast<int64_t>(1 * NSEC_PER_SEC)),
       dispatch_get_main_queue(), ^{
         ActionsSpotlightManager* strongSelf = weakSelf;
-        [strongSelf clearAndAddSpotlightActions];
+        [strongSelf clearAndAddSpotlightActionsWithIsGoogleDefaultSearchEngine:
+                        isGoogleDefaultSearchEngine];
       });
 }
 
 #pragma mark private methods
 
-- (void)clearAndAddSpotlightActions {
+- (void)clearAndAddSpotlightActionsWithIsGoogleDefaultSearchEngine:
+    (BOOL)isGoogleDefaultSearchEngine {
   __weak ActionsSpotlightManager* weakSelf = self;
   [self clearAllSpotlightItems:^(NSError* error) {
     dispatch_after(
@@ -185,14 +201,29 @@
           NSString* defaultBrowserAction = base::SysUTF8ToNSString(
               spotlight::kSpotlightActionSetDefaultBrowser);
 
-          NSArray* spotlightItems = @[
+          NSMutableArray<CSSearchableItem*>* spotlightItems =
+              [NSMutableArray array];
+
+          [spotlightItems addObjectsFromArray:@[
             [strongSelf itemForAction:voiceSearchAction title:voiceSearchTitle],
             [strongSelf itemForAction:newTabAction title:newTabTitle],
             [strongSelf itemForAction:incognitoAction title:incognitoTitle],
             [strongSelf itemForAction:qrScannerAction title:qrScannerTitle],
             [strongSelf itemForAction:defaultBrowserAction
                                 title:defaultBrowserTitle],
-          ];
+          ]];
+
+          const BOOL useLens =
+              lens_availability::CheckAndLogAvailabilityForLensEntryPoint(
+                  LensEntrypoint::Spotlight, isGoogleDefaultSearchEngine);
+          if (useLens) {
+            NSString* lensTitle =
+                l10n_util::GetNSString(IDS_IOS_APPLICATION_SHORTCUT_LENS_TITLE);
+            NSString* lensAction =
+                base::SysUTF8ToNSString(spotlight::kSpotlightActionLens);
+            [spotlightItems addObject:[strongSelf itemForAction:lensAction
+                                                          title:lensTitle]];
+          }
 
           [self.spotlightInterface indexSearchableItems:spotlightItems];
         });
diff --git a/ios/chrome/app/spotlight/spotlight_manager.mm b/ios/chrome/app/spotlight/spotlight_manager.mm
index cc5b6d3..fba146d 100644
--- a/ios/chrome/app/spotlight/spotlight_manager.mm
+++ b/ios/chrome/app/spotlight/spotlight_manager.mm
@@ -5,11 +5,14 @@
 #import "ios/chrome/app/spotlight/spotlight_manager.h"
 
 #import "base/check.h"
+#import "components/search_engines/template_url.h"
+#import "components/search_engines/template_url_service.h"
 #import "ios/chrome/app/spotlight/actions_spotlight_manager.h"
 #import "ios/chrome/app/spotlight/bookmarks_spotlight_manager.h"
 #import "ios/chrome/app/spotlight/reading_list_spotlight_manager.h"
 #import "ios/chrome/app/spotlight/topsites_spotlight_manager.h"
 #import "ios/chrome/browser/flags/system_flags.h"
+#import "ios/chrome/browser/search_engines/template_url_service_factory.h"
 #import "ios/chrome/browser/shared/public/features/features.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
@@ -21,6 +24,7 @@
   BookmarksSpotlightManager* _bookmarkManager;
   TopSitesSpotlightManager* _topSitesManager;
   ActionsSpotlightManager* _actionsManager;
+  TemplateURLService* _templateURLService;
 }
 
 @property(nonatomic, strong) ReadingListSpotlightManager* readingListManager;
@@ -45,6 +49,8 @@
   DCHECK(spotlight::IsSpotlightAvailable());
   self = [super init];
   if (self) {
+    _templateURLService =
+        ios::TemplateURLServiceFactory::GetForBrowserState(browserState);
     _topSitesManager = [TopSitesSpotlightManager
         topSitesSpotlightManagerWithBrowserState:browserState];
     _bookmarkManager = [BookmarksSpotlightManager
@@ -64,11 +70,13 @@
   DCHECK(!_topSitesManager);
   DCHECK(!_actionsManager);
   DCHECK(!_readingListManager);
+  DCHECK(!_templateURLService);
 }
 
 - (void)resyncIndex {
   [_bookmarkManager reindexBookmarksIfNeeded];
-  [_actionsManager indexActions];
+  [_actionsManager indexActionsWithIsGoogleDefaultSearchEngine:
+                       [self isGoogleDefaultSearchEngine]];
   [self.readingListManager clearAndReindexReadingList];
 }
 
@@ -86,6 +94,23 @@
   _topSitesManager = nil;
   _actionsManager = nil;
   _readingListManager = nil;
+  _templateURLService = nullptr;
+}
+
+#pragma mark - Private
+
+- (BOOL)isGoogleDefaultSearchEngine {
+  if (!_templateURLService) {
+    return NO;
+  }
+
+  const TemplateURL* defaultURL =
+      _templateURLService->GetDefaultSearchProvider();
+  BOOL isGoogleDefaultSearchProvider =
+      defaultURL &&
+      defaultURL->GetEngineType(_templateURLService->search_terms_data()) ==
+          SEARCH_ENGINE_GOOGLE;
+  return isGoogleDefaultSearchProvider;
 }
 
 @end
diff --git a/ios/chrome/app/strings/ios_strings.grd b/ios/chrome/app/strings/ios_strings.grd
index 0ae0cfeb..6ce722c 100644
--- a/ios/chrome/app/strings/ios_strings.grd
+++ b/ios/chrome/app/strings/ios_strings.grd
@@ -2062,9 +2062,21 @@
       <message name="IDS_IOS_TRACKING_PRICE_EMAIL_NOTIFICATIONS_DETAILS" desc="Subtitle that describes to which email address price tracking notifications would be sent.">
         Send to <ph name="USER_EMAIL">$1<ex>janedoe@google.com</ex></ph>
       </message>
-      <message name="IDS_IOS_PRIVACY_LOCKDOWN_MODE_TITLE" desc="Title for Lockdown Mode.">
+      <message name="IDS_IOS_LOCKDOWN_MODE_TITLE" desc="Title for Lockdown Mode.">
         Lockdown Mode
       </message>
+      <message name="IDS_IOS_LOCKDOWN_MODE_SWITCH_BUTTON_SUMMARY" desc="Title for Lockdown Mode.">
+        Extreme optional protection for your browser. It should only be used if you believe you may be targeted by a highly sophisticated cyberattack.
+      </message>
+      <message name="IDS_IOS_LOCKDOWN_MODE_INFO_BUTTON_TITLE" desc="Title for Lockdown Mode.">
+        This setting is enabled on your device
+      </message>
+      <message name="IDS_IOS_LOCKDOWN_MODE_INFO_BUTTON_SUMMARY" desc="Title for Lockdown Mode.">
+        To disable Lockdown Mode in Chrome turn it off on your device.
+      </message>
+      <message name="IDS_IOS_LOCKDOWN_MODE_FOOTER" desc="Title for Lockdown Mode.">
+        When Chrome is in Lockdown Mode, certain web technologies are blocked, which might cause some websites to load more slowly or not operate correctly.
+      </message>
       <message name="IDS_IOS_PRIVACY_SAFE_BROWSING_ENHANCED_PROTECTION_SUMMARY" desc="Summary for Privacy Safe Browsing enhanced protection mode.">
         Faster, proactive protection against dangerous websites, downloads, and extensions. Warns you about password breaches. Requires browsing data to be sent to Google.
       </message>
diff --git a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_LOCKDOWN_MODE_FOOTER.png.sha1 b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_LOCKDOWN_MODE_FOOTER.png.sha1
new file mode 100644
index 0000000..55386d60
--- /dev/null
+++ b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_LOCKDOWN_MODE_FOOTER.png.sha1
@@ -0,0 +1 @@
+d782be990819ef550d0f883bf5b1825cbc94ccd2
\ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_LOCKDOWN_MODE_INFO_BUTTON_SUMMARY.png.sha1 b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_LOCKDOWN_MODE_INFO_BUTTON_SUMMARY.png.sha1
new file mode 100644
index 0000000..65e7d063
--- /dev/null
+++ b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_LOCKDOWN_MODE_INFO_BUTTON_SUMMARY.png.sha1
@@ -0,0 +1 @@
+b12b0cc061d65f65e17170b8c8e8af7c5a4f3a5c
\ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_LOCKDOWN_MODE_INFO_BUTTON_TITLE.png.sha1 b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_LOCKDOWN_MODE_INFO_BUTTON_TITLE.png.sha1
new file mode 100644
index 0000000..65e7d063
--- /dev/null
+++ b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_LOCKDOWN_MODE_INFO_BUTTON_TITLE.png.sha1
@@ -0,0 +1 @@
+b12b0cc061d65f65e17170b8c8e8af7c5a4f3a5c
\ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_LOCKDOWN_MODE_SWITCH_BUTTON_SUMMARY.png.sha1 b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_LOCKDOWN_MODE_SWITCH_BUTTON_SUMMARY.png.sha1
new file mode 100644
index 0000000..55386d60
--- /dev/null
+++ b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_LOCKDOWN_MODE_SWITCH_BUTTON_SUMMARY.png.sha1
@@ -0,0 +1 @@
+d782be990819ef550d0f883bf5b1825cbc94ccd2
\ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_LOCKDOWN_MODE_TITLE.png.sha1 b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_LOCKDOWN_MODE_TITLE.png.sha1
new file mode 100644
index 0000000..98bb0dd
--- /dev/null
+++ b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_LOCKDOWN_MODE_TITLE.png.sha1
@@ -0,0 +1 @@
+bda27e098aa6489ed3bf0c7016392f5047b8674e
\ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_PRIVACY_LOCKDOWN_MODE_TITLE.png.sha1 b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_PRIVACY_LOCKDOWN_MODE_TITLE.png.sha1
deleted file mode 100644
index a2f93d8..0000000
--- a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_PRIVACY_LOCKDOWN_MODE_TITLE.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-8f4903390a0632b0523f46cb08e6f57a8ad2c2b1
\ No newline at end of file
diff --git a/ios/chrome/browser/crash_report/breadcrumbs/breadcrumb_manager_browser_agent.h b/ios/chrome/browser/crash_report/breadcrumbs/breadcrumb_manager_browser_agent.h
index 3b4f9386..a6a83ee 100644
--- a/ios/chrome/browser/crash_report/breadcrumbs/breadcrumb_manager_browser_agent.h
+++ b/ios/chrome/browser/crash_report/breadcrumbs/breadcrumb_manager_browser_agent.h
@@ -72,10 +72,6 @@
   void WebStateListChanged(WebStateList* web_state_list,
                            const WebStateListChange& change,
                            const WebStateSelection& selection) override;
-  void WebStateMoved(WebStateList* web_state_list,
-                     web::WebState* web_state,
-                     int from_index,
-                     int to_index) override;
   void WillCloseWebStateAt(WebStateList* web_state_list,
                            web::WebState* web_state,
                            int index,
diff --git a/ios/chrome/browser/crash_report/breadcrumbs/breadcrumb_manager_browser_agent.mm b/ios/chrome/browser/crash_report/breadcrumbs/breadcrumb_manager_browser_agent.mm
index 9c76085..5ddd52ad 100644
--- a/ios/chrome/browser/crash_report/breadcrumbs/breadcrumb_manager_browser_agent.mm
+++ b/ios/chrome/browser/crash_report/breadcrumbs/breadcrumb_manager_browser_agent.mm
@@ -78,10 +78,13 @@
     case WebStateListChange::Type::kDetach:
       // Do nothing when a WebState is detached.
       break;
-    case WebStateListChange::Type::kMove:
-      // TODO(crbug.com/1442546): Move the implementation from
-      // WebStateMoved() to here.
+    case WebStateListChange::Type::kMove: {
+      const WebStateListChangeMove& move_change =
+          change.As<WebStateListChangeMove>();
+      LogTabMoved(GetTabId(move_change.moved_web_state()),
+                  move_change.moved_from_index(), selection.index);
       break;
+    }
     case WebStateListChange::Type::kReplace: {
       const WebStateListChangeReplace& replace_change =
           change.As<WebStateListChangeReplace>();
@@ -104,13 +107,6 @@
   }
 }
 
-void BreadcrumbManagerBrowserAgent::WebStateMoved(WebStateList* web_state_list,
-                                                  web::WebState* web_state,
-                                                  int from_index,
-                                                  int to_index) {
-  LogTabMoved(GetTabId(web_state), from_index, to_index);
-}
-
 void BreadcrumbManagerBrowserAgent::WillCloseWebStateAt(
     WebStateList* web_state_list,
     web::WebState* web_state,
diff --git a/ios/chrome/browser/default_browser/utils.h b/ios/chrome/browser/default_browser/utils.h
index 4cc2988c..748b659 100644
--- a/ios/chrome/browser/default_browser/utils.h
+++ b/ios/chrome/browser/default_browser/utils.h
@@ -32,7 +32,19 @@
   kActionButton = 0,
   kCancel = 1,
   kRemindMeLater = 2,
-  kMaxValue = kRemindMeLater,
+  kDismiss = 3,
+  kMaxValue = kDismiss,
+};
+
+// Enum for the tailored promo UMA histograms. These values are persisted to
+// logs. Entries should not be renumbered and numeric values should never be
+// reused.
+enum class DefaultPromoTypeForUMA {
+  kGeneral = 0,
+  kMadeForIOS = 1,
+  kStaySafe = 2,
+  kAllTabs = 3,
+  kMaxValue = kAllTabs,
 };
 
 // The feature parameter to activate the remind me later button.
@@ -122,6 +134,9 @@
 // non-modal promo before.
 NSInteger UserInteractionWithNonModalPromoCount();
 
+// Logs that one of default browser promos was displayed.
+void LogDefaultBrowserPromoDisplayed();
+
 // Logs that the user has interacted with the Fullscreen Promo.
 void LogUserInteractionWithFullscreenPromo();
 
@@ -198,4 +213,8 @@
 // pruned of cleanups that have been present for multiple milestones.
 void CleanupUnusedStorage();
 
+// Converts Default browser promo type NSEnum to an enum that can be used by
+// UMA.
+DefaultPromoTypeForUMA GetDefaultPromoTypeForUMA(DefaultPromoType type);
+
 #endif  // IOS_CHROME_BROWSER_DEFAULT_BROWSER_UTILS_H_
diff --git a/ios/chrome/browser/default_browser/utils.mm b/ios/chrome/browser/default_browser/utils.mm
index 8170987..42ee7fe2 100644
--- a/ios/chrome/browser/default_browser/utils.mm
+++ b/ios/chrome/browser/default_browser/utils.mm
@@ -489,12 +489,19 @@
   return number.integerValue;
 }
 
-void LogUserInteractionWithFullscreenPromo() {
+void LogDefaultBrowserPromoDisplayed() {
   const NSInteger displayed_promo_count = DisplayedPromoCount();
   NSDictionary<NSString*, NSObject*>* update = @{
+    kDisplayedPromoCount : @(displayed_promo_count + 1),
+  };
+
+  UpdateStorageWithDictionary(update);
+}
+
+void LogUserInteractionWithFullscreenPromo() {
+  NSDictionary<NSString*, NSObject*>* update = @{
     kUserHasInteractedWithFullscreenPromo : @YES,
     kLastTimeUserInteractedWithPromo : [NSDate date],
-    kDisplayedPromoCount : @(displayed_promo_count + 1),
   };
 
   UpdateStorageWithDictionary(update);
@@ -712,3 +719,18 @@
   // TODO(crbug.com/1445218): Remove in M116+.
   [defaults removeObjectForKey:kRemindMeLaterPromoActionInteraction];
 }
+
+DefaultPromoTypeForUMA GetDefaultPromoTypeForUMA(DefaultPromoType type) {
+  switch (type) {
+    case DefaultPromoTypeGeneral:
+      return DefaultPromoTypeForUMA::kGeneral;
+    case DefaultPromoTypeMadeForIOS:
+      return DefaultPromoTypeForUMA::kMadeForIOS;
+    case DefaultPromoTypeStaySafe:
+      return DefaultPromoTypeForUMA::kStaySafe;
+    case DefaultPromoTypeAllTabs:
+      return DefaultPromoTypeForUMA::kAllTabs;
+    default:
+      NOTREACHED_NORETURN();
+  }
+}
diff --git a/ios/chrome/browser/policy/reporting/report_scheduler_ios.h b/ios/chrome/browser/policy/reporting/report_scheduler_ios.h
index 3b697b6..88d1dbd 100644
--- a/ios/chrome/browser/policy/reporting/report_scheduler_ios.h
+++ b/ios/chrome/browser/policy/reporting/report_scheduler_ios.h
@@ -24,6 +24,9 @@
                                     base::TimeDelta upload_interval) override;
   void StopWatchingUpdates() override;
   void OnBrowserVersionUploaded() override;
+  void StartWatchingExtensionRequestIfNeeded() override;
+  void StopWatchingExtensionRequest() override;
+  void OnExtensionRequestUploaded() override;
   policy::DMToken GetProfileDMToken() override;
   std::string GetProfileClientId() override;
 };
diff --git a/ios/chrome/browser/policy/reporting/report_scheduler_ios.mm b/ios/chrome/browser/policy/reporting/report_scheduler_ios.mm
index 0afdadc9..c104854 100644
--- a/ios/chrome/browser/policy/reporting/report_scheduler_ios.mm
+++ b/ios/chrome/browser/policy/reporting/report_scheduler_ios.mm
@@ -34,6 +34,18 @@
   // Not used on iOS because there is no in-app auto-update.
 }
 
+void ReportSchedulerIOS::StartWatchingExtensionRequestIfNeeded() {
+  // Not used on iOS because there is no extension.
+}
+
+void ReportSchedulerIOS::StopWatchingExtensionRequest() {
+  // Not used on iOS because there is no extension.
+}
+
+void ReportSchedulerIOS::OnExtensionRequestUploaded() {
+  // Not used on iOS because there is no extension.
+}
+
 policy::DMToken ReportSchedulerIOS::GetProfileDMToken() {
   // Profile reporting is not supported.
   return policy::DMToken::CreateEmptyToken();
diff --git a/ios/chrome/browser/policy/reporting/report_scheduler_ios_unittest.mm b/ios/chrome/browser/policy/reporting/report_scheduler_ios_unittest.mm
index b5161f9..42454923 100644
--- a/ios/chrome/browser/policy/reporting/report_scheduler_ios_unittest.mm
+++ b/ios/chrome/browser/policy/reporting/report_scheduler_ios_unittest.mm
@@ -12,6 +12,7 @@
 #import "base/time/time.h"
 #import "components/enterprise/browser/controller/fake_browser_dm_token_storage.h"
 #import "components/enterprise/browser/reporting/common_pref_names.h"
+#import "components/enterprise/browser/reporting/real_time_report_generator.h"
 #import "components/enterprise/browser/reporting/report_request.h"
 #import "components/policy/core/common/cloud/mock_cloud_policy_client.h"
 #import "ios/chrome/browser/policy/reporting/reporting_delegate_factory_ios.h"
@@ -110,6 +111,8 @@
     params.client = client_;
     params.delegate = report_delegate_factory_.GetReportSchedulerDelegate();
     params.report_generator = std::move(generator_ptr_);
+    params.real_time_report_generator =
+        std::make_unique<RealTimeReportGenerator>(&report_delegate_factory_),
     scheduler_ = std::make_unique<ReportScheduler>(std::move(params));
     scheduler_->SetReportUploaderForTesting(std::move(uploader_ptr_));
   }
diff --git a/ios/chrome/browser/policy/reporting/reporting_delegate_factory_ios.h b/ios/chrome/browser/policy/reporting/reporting_delegate_factory_ios.h
index f2c1a41e..6ad1b67 100644
--- a/ios/chrome/browser/policy/reporting/reporting_delegate_factory_ios.h
+++ b/ios/chrome/browser/policy/reporting/reporting_delegate_factory_ios.h
@@ -11,7 +11,6 @@
 
 #include "components/enterprise/browser/reporting/browser_report_generator.h"
 #include "components/enterprise/browser/reporting/profile_report_generator.h"
-#include "components/enterprise/browser/reporting/real_time_report_controller.h"
 #include "components/enterprise/browser/reporting/real_time_report_generator.h"
 #include "components/enterprise/browser/reporting/report_generator.h"
 #include "components/enterprise/browser/reporting/report_scheduler.h"
@@ -42,9 +41,6 @@
 
   std::unique_ptr<RealTimeReportGenerator::Delegate>
   GetRealTimeReportGeneratorDelegate() override;
-
-  std::unique_ptr<RealTimeReportController::Delegate>
-  GetRealTimeReportControllerDelegate() override;
 };
 
 }  // namespace enterprise_reporting
diff --git a/ios/chrome/browser/policy/reporting/reporting_delegate_factory_ios.mm b/ios/chrome/browser/policy/reporting/reporting_delegate_factory_ios.mm
index beb210c0..baf423c 100644
--- a/ios/chrome/browser/policy/reporting/reporting_delegate_factory_ios.mm
+++ b/ios/chrome/browser/policy/reporting/reporting_delegate_factory_ios.mm
@@ -40,10 +40,4 @@
   return nullptr;
 }
 
-std::unique_ptr<RealTimeReportController::Delegate>
-ReportingDelegateFactoryIOS::GetRealTimeReportControllerDelegate() {
-  // Using nullptr as the new pipeline is not supported on iOS.
-  return nullptr;
-}
-
 }  // namespace enterprise_reporting
diff --git a/ios/chrome/browser/safe_browsing/password_protection_egtest.mm b/ios/chrome/browser/safe_browsing/password_protection_egtest.mm
index f1fd9bf..716956f 100644
--- a/ios/chrome/browser/safe_browsing/password_protection_egtest.mm
+++ b/ios/chrome/browser/safe_browsing/password_protection_egtest.mm
@@ -100,22 +100,18 @@
   [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
       performAction:chrome_test_util::TapWebElementWithId(kInputElement)];
 
-  [[EarlGrey selectElementWithMatcher:grey_accessibilityLabel(@"P")]
-      performAction:grey_tap()];
-  [[EarlGrey selectElementWithMatcher:grey_accessibilityLabel(@"a")]
-      performAction:grey_tap()];
-  [[EarlGrey selectElementWithMatcher:grey_accessibilityLabel(@"s")]
-      performAction:grey_tap()];
-  [[EarlGrey selectElementWithMatcher:grey_accessibilityLabel(@"s")]
-      performAction:grey_tap()];
-  [[EarlGrey selectElementWithMatcher:grey_accessibilityLabel(@"w")]
-      performAction:grey_tap()];
-  [[EarlGrey selectElementWithMatcher:grey_accessibilityLabel(@"o")]
-      performAction:grey_tap()];
-  [[EarlGrey selectElementWithMatcher:grey_accessibilityLabel(@"r")]
-      performAction:grey_tap()];
-  [[EarlGrey selectElementWithMatcher:grey_accessibilityLabel(@"d")]
-      performAction:grey_tap()];
+  for (NSString* character in
+       @[ @"P", @"a", @"s", @"s", @"w", @"o", @"r", @"d" ]) {
+    id<GREYMatcher> keyMatcher = grey_allOf(
+        grey_accessibilityLabel(character),
+        grey_accessibilityTrait(UIAccessibilityTraitKeyboardKey), nil);
+
+    [ChromeEarlGrey
+        waitForUIElementToAppearWithMatcher:keyMatcher
+                                    timeout:base::test::ios::
+                                                kWaitForUIElementTimeout];
+    [[EarlGrey selectElementWithMatcher:keyMatcher] performAction:grey_tap()];
+  }
 }
 
 // Tests that password protection UI is shown when saved password is reused on
diff --git a/ios/chrome/browser/sessions/session_restoration_browser_agent.h b/ios/chrome/browser/sessions/session_restoration_browser_agent.h
index a19937e0..c0c3428 100644
--- a/ios/chrome/browser/sessions/session_restoration_browser_agent.h
+++ b/ios/chrome/browser/sessions/session_restoration_browser_agent.h
@@ -117,10 +117,6 @@
   void WebStateDetachedAt(WebStateList* web_state_list,
                           web::WebState* web_state,
                           int index) override;
-  void WebStateMoved(WebStateList* web_state_list,
-                     web::WebState* web_state,
-                     int from_index,
-                     int to_index) override;
 
   // web::WebStateObserver methods.
   void DidFinishNavigation(web::WebState* web_state,
diff --git a/ios/chrome/browser/sessions/session_restoration_browser_agent.mm b/ios/chrome/browser/sessions/session_restoration_browser_agent.mm
index eb6bdf1f..fcbea3fb 100644
--- a/ios/chrome/browser/sessions/session_restoration_browser_agent.mm
+++ b/ios/chrome/browser/sessions/session_restoration_browser_agent.mm
@@ -350,10 +350,17 @@
       // TODO(crbug.com/1442546): Move the implementation from
       // WebStateDetachedAt() to here.
       break;
-    case WebStateListChange::Type::kMove:
-      // TODO(crbug.com/1442546): Move the implementation from
-      // WebStateMoved() to here.
+    case WebStateListChange::Type::kMove: {
+      const WebStateListChangeMove& move_change =
+          change.As<WebStateListChangeMove>();
+      if (move_change.moved_web_state()->IsLoading()) {
+        return;
+      }
+
+      // Persist the session state if the new web state is not loading.
+      SaveSession(/*immediately=*/false);
       break;
+    }
     case WebStateListChange::Type::kReplace: {
       const WebStateListChangeReplace& replace_change =
           change.As<WebStateListChangeReplace>();
@@ -404,17 +411,6 @@
   SaveSession(/*immediately=*/false);
 }
 
-void SessionRestorationBrowserAgent::WebStateMoved(WebStateList* web_state_list,
-                                                   web::WebState* web_state,
-                                                   int from_index,
-                                                   int to_index) {
-  if (web_state->IsLoading())
-    return;
-
-  // Persist the session state if the new web state is not loading.
-  SaveSession(/*immediately=*/false);
-}
-
 // WebStateObserver methods
 void SessionRestorationBrowserAgent::DidFinishNavigation(
     web::WebState* web_state,
diff --git a/ios/chrome/browser/shared/coordinator/scene/scene_controller.mm b/ios/chrome/browser/shared/coordinator/scene/scene_controller.mm
index 69d0b09..0e26b8d 100644
--- a/ios/chrome/browser/shared/coordinator/scene/scene_controller.mm
+++ b/ios/chrome/browser/shared/coordinator/scene/scene_controller.mm
@@ -2307,6 +2307,10 @@
       return ^{
         [weakSelf startLensWithEntryPoint:LensEntrypoint::AppIconLongPress];
       };
+    case START_LENS_FROM_SPOTLIGHT:
+      return ^{
+        [weakSelf startLensWithEntryPoint:LensEntrypoint::Spotlight];
+      };
     case FOCUS_OMNIBOX:
       return ^{
         [weakSelf focusOmnibox];
diff --git a/ios/chrome/browser/shared/model/web_state_list/web_state_list.mm b/ios/chrome/browser/shared/model/web_state_list/web_state_list.mm
index f3635a4..b502e40 100644
--- a/ios/chrome/browser/shared/model/web_state_list/web_state_list.mm
+++ b/ios/chrome/browser/shared/model/web_state_list/web_state_list.mm
@@ -403,8 +403,10 @@
     }
   }
 
+  const WebStateListChangeMove move_change(web_state, from_index);
+  const WebStateSelection selection = {.index = to_index, .activating = false};
   for (auto& observer : observers_) {
-    observer.WebStateMoved(this, web_state, from_index, to_index);
+    observer.WebStateListChanged(this, move_change, selection);
   }
 }
 
diff --git a/ios/chrome/browser/shared/model/web_state_list/web_state_list_observer.h b/ios/chrome/browser/shared/model/web_state_list/web_state_list_observer.h
index 07cf8b24..84c0878 100644
--- a/ios/chrome/browser/shared/model/web_state_list/web_state_list_observer.h
+++ b/ios/chrome/browser/shared/model/web_state_list/web_state_list_observer.h
@@ -13,8 +13,8 @@
 class WebState;
 }
 
-// Represent a generic change to the WebStateList. Use `type()` to determine its
-// type, then access the correct sub-class using `As()<...>` method.
+// Represents a generic change to the WebStateList. Use `type()` to determine
+// its type, then access the correct sub-class using `As()<...>` method.
 class WebStateListChange {
  public:
   enum class Type {
@@ -49,7 +49,34 @@
   WebStateListChange() = default;
 };
 
-// Represent a change that corresponds to replacing one WebState by another
+// Represents a change that corresponds to moving one WebState to a new index in
+// WebStateList. There is no change in the number of WebStates.
+class WebStateListChangeMove final : public WebStateListChange {
+ public:
+  static constexpr Type kType = Type::kMove;
+
+  WebStateListChangeMove(raw_ptr<web::WebState> moved_web_state,
+                         int moved_from_index);
+  ~WebStateListChangeMove() final = default;
+
+  Type type() const final;
+
+  // The WebState that is moved from the position of `moved_from_index` in
+  // WebStateListChangeMove to the position of `index` in WebStateSelection.
+  raw_ptr<web::WebState> moved_web_state() const {
+    CHECK(moved_web_state_);
+    return moved_web_state_;
+  }
+
+  // The index of the previous position of a WebState.
+  int moved_from_index() const { return moved_from_index_; }
+
+ private:
+  raw_ptr<web::WebState> moved_web_state_;
+  const int moved_from_index_;
+};
+
+// Represents a change that corresponds to replacing one WebState by another
 // WebState in-place. There is no change in the number of WebStates.
 class WebStateListChangeReplace final : public WebStateListChange {
  public:
@@ -80,7 +107,8 @@
   raw_ptr<web::WebState> inserted_web_state_;
 };
 
-// Represent a change that correspond to inserting one WebState to WebStateList.
+// Represents a change that corresponds to inserting one WebState to
+// WebStateList.
 class WebStateListChangeInsert final : public WebStateListChange {
  public:
   static constexpr Type kType = Type::kInsert;
@@ -145,13 +173,6 @@
                                    const WebStateListChange& change,
                                    const WebStateSelection& selection);
 
-  // Invoked after the WebState at the specified index is moved to another
-  // index.
-  virtual void WebStateMoved(WebStateList* web_state_list,
-                             web::WebState* web_state,
-                             int from_index,
-                             int to_index);
-
   // Invoked before the specified WebState is detached from the WebStateList.
   // The WebState is still valid and still in the WebStateList.
   virtual void WillDetachWebStateAt(WebStateList* web_state_list,
diff --git a/ios/chrome/browser/shared/model/web_state_list/web_state_list_observer.mm b/ios/chrome/browser/shared/model/web_state_list/web_state_list_observer.mm
index 271ac81..ce8c46d 100644
--- a/ios/chrome/browser/shared/model/web_state_list/web_state_list_observer.mm
+++ b/ios/chrome/browser/shared/model/web_state_list/web_state_list_observer.mm
@@ -12,6 +12,15 @@
 #error "This file requires ARC support."
 #endif
 
+WebStateListChangeMove::WebStateListChangeMove(
+    raw_ptr<web::WebState> moved_web_state,
+    int moved_from_index)
+    : moved_web_state_(moved_web_state), moved_from_index_(moved_from_index) {}
+
+WebStateListChange::Type WebStateListChangeMove::type() const {
+  return kType;
+}
+
 WebStateListChangeReplace::WebStateListChangeReplace(
     raw_ptr<web::WebState> replaced_web_state,
     raw_ptr<web::WebState> inserted_web_state)
@@ -43,11 +52,6 @@
     const WebStateListChange& change,
     const WebStateSelection& selection) {}
 
-void WebStateListObserver::WebStateMoved(WebStateList* web_state_list,
-                                         web::WebState* web_state,
-                                         int from_index,
-                                         int to_index) {}
-
 void WebStateListObserver::WillDetachWebStateAt(WebStateList* web_state_list,
                                                 web::WebState* web_state,
                                                 int index) {}
diff --git a/ios/chrome/browser/shared/model/web_state_list/web_state_list_observer_bridge.h b/ios/chrome/browser/shared/model/web_state_list/web_state_list_observer_bridge.h
index fa0a617..9e7d1934 100644
--- a/ios/chrome/browser/shared/model/web_state_list/web_state_list_observer_bridge.h
+++ b/ios/chrome/browser/shared/model/web_state_list/web_state_list_observer_bridge.h
@@ -96,10 +96,6 @@
   void WebStateListChanged(WebStateList* web_state_list,
                            const WebStateListChange& change,
                            const WebStateSelection& selection) override;
-  void WebStateMoved(WebStateList* web_state_list,
-                     web::WebState* web_state,
-                     int from_index,
-                     int to_index) final;
   void WillDetachWebStateAt(WebStateList* web_state_list,
                             web::WebState* web_state,
                             int index) final;
diff --git a/ios/chrome/browser/shared/model/web_state_list/web_state_list_observer_bridge.mm b/ios/chrome/browser/shared/model/web_state_list/web_state_list_observer_bridge.mm
index 1c6246f2..10f02d4 100644
--- a/ios/chrome/browser/shared/model/web_state_list/web_state_list_observer_bridge.mm
+++ b/ios/chrome/browser/shared/model/web_state_list/web_state_list_observer_bridge.mm
@@ -18,30 +18,45 @@
     WebStateList* web_state_list,
     const WebStateListChange& change,
     const WebStateSelection& selection) {
-  const SEL selector = @selector(didChangeWebStateList:change:selection:);
-  if (![observer_ respondsToSelector:selector]) {
-    return;
+  switch (change.type()) {
+    case WebStateListChange::Type::kDestroy:
+      // TODO(crbug.com/1442546): Move the implementation from
+      // webStateListDestroyed: to here.
+      break;
+    case WebStateListChange::Type::kDetach:
+      // TODO(crbug.com/1442546): Move the implementation from
+      // webStateList:didDetachWebState:atIndex: to here.
+      break;
+    case WebStateListChange::Type::kMove: {
+      const SEL selector = @selector(webStateList:
+                                  didMoveWebState:fromIndex:toIndex:);
+      if (![observer_ respondsToSelector:selector]) {
+        return;
+      }
+
+      // TODO(crbug.com/1442546): Replace this with
+      // -didChangeWebStateList:change:selection:.
+      const WebStateListChangeMove& move_change =
+          change.As<WebStateListChangeMove>();
+      [observer_ webStateList:web_state_list
+              didMoveWebState:move_change.moved_web_state()
+                    fromIndex:move_change.moved_from_index()
+                      toIndex:selection.index];
+      break;
+    }
+    case WebStateListChange::Type::kReplace:
+    case WebStateListChange::Type::kInsert: {
+      const SEL selector = @selector(didChangeWebStateList:change:selection:);
+      if (![observer_ respondsToSelector:selector]) {
+        return;
+      }
+
+      [observer_ didChangeWebStateList:web_state_list
+                                change:change
+                             selection:selection];
+      break;
+    }
   }
-
-  [observer_ didChangeWebStateList:web_state_list
-                            change:change
-                         selection:selection];
-}
-
-void WebStateListObserverBridge::WebStateMoved(WebStateList* web_state_list,
-                                               web::WebState* web_state,
-                                               int from_index,
-                                               int to_index) {
-  const SEL selector = @selector(webStateList:
-                              didMoveWebState:fromIndex:toIndex:);
-  if (![observer_ respondsToSelector:selector]) {
-    return;
-  }
-
-  [observer_ webStateList:web_state_list
-          didMoveWebState:web_state
-                fromIndex:from_index
-                  toIndex:to_index];
 }
 
 void WebStateListObserverBridge::WillDetachWebStateAt(
diff --git a/ios/chrome/browser/shared/model/web_state_list/web_state_list_unittest.mm b/ios/chrome/browser/shared/model/web_state_list/web_state_list_unittest.mm
index 8756ae1..abdbeb4 100644
--- a/ios/chrome/browser/shared/model/web_state_list/web_state_list_unittest.mm
+++ b/ios/chrome/browser/shared/model/web_state_list/web_state_list_unittest.mm
@@ -53,7 +53,7 @@
   // Returns whether the insertion operation was invoked.
   bool web_state_inserted_called() const { return web_state_inserted_called_; }
 
-  // Returns whether WebStateMoved was invoked.
+  // Returns whether the move operation was invoked.
   bool web_state_moved_called() const { return web_state_moved_called_; }
 
   // Returns whether the replacement operation was invoked.
@@ -91,8 +91,8 @@
         // WebStateDetachedAt() to here.
         break;
       case WebStateListChange::Type::kMove:
-        // TODO(crbug.com/1442546): Move the implementation from
-        // WebStateMoved() to here.
+        EXPECT_TRUE(web_state_list->IsMutating());
+        web_state_moved_called_ = true;
         break;
       case WebStateListChange::Type::kReplace:
         EXPECT_TRUE(web_state_list->IsMutating());
@@ -105,14 +105,6 @@
     }
   }
 
-  void WebStateMoved(WebStateList* web_state_list,
-                     web::WebState* web_state,
-                     int from_index,
-                     int to_index) override {
-    EXPECT_TRUE(web_state_list->IsMutating());
-    web_state_moved_called_ = true;
-  }
-
   void WebStateDetachedAt(WebStateList* web_state_list,
                           web::WebState* web_state,
                           int index) override {
diff --git a/ios/chrome/browser/sync/prefs/ios_chrome_syncable_prefs_database.cc b/ios/chrome/browser/sync/prefs/ios_chrome_syncable_prefs_database.cc
index 2397f7d9..3b3d5f4 100644
--- a/ios/chrome/browser/sync/prefs/ios_chrome_syncable_prefs_database.cc
+++ b/ios/chrome/browser/sync/prefs/ios_chrome_syncable_prefs_database.cc
@@ -36,6 +36,12 @@
   kSearchSuggestEnabled = 200008,
   kTrackPricesOnTabsEnabled = 200009,
   kVoiceSearchLocale = 200010
+  // See components/sync_preferences/README.md about adding new entries here.
+  // vvvvv IMPORTANT! vvvvv
+  // Note to the reviewer: IT IS YOUR RESPONSIBILITY to ensure that new syncable
+  // prefs follow privacy guidelines! See the readme file linked above for
+  // guidance and escalation path in case anything is unclear.
+  // ^^^^^ IMPORTANT! ^^^^^
 };
 }  // namespace syncable_prefs_ids
 
diff --git a/ios/chrome/browser/ui/authentication/signin/consistency_promo_signin/consistency_default_account/consistency_default_account_view_controller.mm b/ios/chrome/browser/ui/authentication/signin/consistency_promo_signin/consistency_default_account/consistency_default_account_view_controller.mm
index f6b51a4..c811d07 100644
--- a/ios/chrome/browser/ui/authentication/signin/consistency_promo_signin/consistency_default_account/consistency_default_account_view_controller.mm
+++ b/ios/chrome/browser/ui/authentication/signin/consistency_promo_signin/consistency_default_account/consistency_default_account_view_controller.mm
@@ -119,19 +119,9 @@
   titleLabel.adjustsFontSizeToFitWidth = YES;
   titleLabel.minimumScaleFactor = 0.1;
 
-  NSString* skipButtonTitle;
-  if (self.accessPoint ==
-      signin_metrics::AccessPoint::ACCESS_POINT_SEND_TAB_TO_SELF_PROMO) {
-    skipButtonTitle = l10n_util::GetNSString(IDS_CANCEL);
-  } else if (self.accessPoint == signin_metrics::AccessPoint::
-                                     ACCESS_POINT_NTP_FEED_CARD_MENU_PROMO) {
-    skipButtonTitle = l10n_util::GetNSString(IDS_CLOSE);
-  } else {
-    skipButtonTitle = l10n_util::GetNSString(IDS_IOS_CONSISTENCY_PROMO_SKIP);
-  }
-
   UIButton* skipButton = [UIButton buttonWithType:UIButtonTypeSystem];
-  [skipButton setTitle:skipButtonTitle forState:UIControlStateNormal];
+  [skipButton setTitle:l10n_util::GetNSString(IDS_CLOSE)
+              forState:UIControlStateNormal];
   skipButton.titleLabel.font =
       [UIFont preferredFontForTextStyle:UIFontTextStyleBody];
   [skipButton addTarget:self
diff --git a/ios/chrome/browser/ui/default_promo/default_browser_promo_coordinator.mm b/ios/chrome/browser/ui/default_promo/default_browser_promo_coordinator.mm
index ef84644..2c5973f 100644
--- a/ios/chrome/browser/ui/default_promo/default_browser_promo_coordinator.mm
+++ b/ios/chrome/browser/ui/default_promo/default_browser_promo_coordinator.mm
@@ -43,8 +43,7 @@
 
 - (void)start {
   [super start];
-  base::RecordAction(
-      base::UserMetricsAction("IOS.DefaultBrowserFullscreenPromo.Impression"));
+  [self recordDefaultBrowserPromoShown];
   self.defaultBrowerPromoViewController =
       [[DefaultBrowserPromoViewController alloc] init];
   self.defaultBrowerPromoViewController.actionHandler = self;
@@ -74,9 +73,10 @@
     (UIPresentationController*)presentationController {
   base::RecordAction(
       base::UserMetricsAction("IOS.DefaultBrowserFullscreenPromo.Dismissed"));
+  [self logDefaultBrowserFullscreenPromoHistogramForAction:
+            IOSDefaultBrowserFullscreenPromoAction::kDismiss];
   // This ensures that a modal swipe dismiss will also be logged.
   LogUserInteractionWithFullscreenPromo();
-  [self recordDefaultBrowserPromoShown];
 
   [self.handler hidePromo];
 }
@@ -90,7 +90,6 @@
   base::RecordAction(base::UserMetricsAction(
       "IOS.DefaultBrowserFullscreenPromo.PrimaryActionTapped"));
   LogUserInteractionWithFullscreenPromo();
-  [self recordDefaultBrowserPromoShown];
   [[UIApplication sharedApplication]
                 openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]
                 options:{}
@@ -104,15 +103,13 @@
   [self logDefaultBrowserFullscreenPromoHistogramForAction:
             IOSDefaultBrowserFullscreenPromoAction::kCancel];
   base::RecordAction(
-      base::UserMetricsAction("IOS.DefaultBrowserFullscreenPromo.Dismissed"));
-  [self recordDefaultBrowserPromoShown];
+      base::UserMetricsAction("IOS.DefaultBrowserFullscreenPromo.Cancel"));
   [self.handler hidePromo];
 }
 
 - (void)confirmationAlertLearnMoreAction {
   base::RecordAction(base::UserMetricsAction(
       "IOS.DefaultBrowserFullscreenPromo.MoreInfoTapped"));
-  [self recordDefaultBrowserPromoShown];
   NSString* message = GetDefaultBrowserLearnMoreText();
   self.learnMoreViewController =
       [[PopoverLabelViewController alloc] initWithMessage:message];
@@ -137,6 +134,12 @@
 
 // Records that a default browser promo has been shown.
 - (void)recordDefaultBrowserPromoShown {
+  base::RecordAction(
+      base::UserMetricsAction("IOS.DefaultBrowserFullscreenPromo.Impression"));
+  base::UmaHistogramEnumeration("IOS.DefaultBrowserPromo.Shown",
+                                DefaultPromoTypeForUMA::kGeneral);
+  LogDefaultBrowserPromoDisplayed();
+
   ChromeBrowserState* browserState = self.browser->GetBrowserState();
   LogToFETDefaultBrowserPromoShown(
       feature_engagement::TrackerFactory::GetForBrowserState(browserState));
diff --git a/ios/chrome/browser/ui/default_promo/tailored_promo_coordinator.mm b/ios/chrome/browser/ui/default_promo/tailored_promo_coordinator.mm
index 991b4fa..ee7cc47 100644
--- a/ios/chrome/browser/ui/default_promo/tailored_promo_coordinator.mm
+++ b/ios/chrome/browser/ui/default_promo/tailored_promo_coordinator.mm
@@ -29,34 +29,6 @@
 
 using l10n_util::GetNSString;
 
-namespace {
-
-// Enum for the tailored promo UMA histograms. These values are persisted to
-// logs. Entries should not be renumbered and numeric values should never be
-// reused.
-enum class DefaultPromoTypeForUMA {
-  kOther = 0,
-  kMadeForIOS = 1,
-  kStaySafe = 2,
-  kAllTabs = 3,
-  kMaxValue = kAllTabs,
-};
-
-DefaultPromoTypeForUMA DefaultPromoTypeForUMA(DefaultPromoType type) {
-  switch (type) {
-    case DefaultPromoTypeMadeForIOS:
-      return DefaultPromoTypeForUMA::kMadeForIOS;
-    case DefaultPromoTypeStaySafe:
-      return DefaultPromoTypeForUMA::kStaySafe;
-    case DefaultPromoTypeAllTabs:
-      return DefaultPromoTypeForUMA::kAllTabs;
-    default:
-      DCHECK(type == DefaultPromoTypeGeneral);
-      return DefaultPromoTypeForUMA::kOther;
-  }
-}
-}  // namespace
-
 @interface TailoredPromoCoordinator () <
     ConfirmationAlertActionHandler,
     UIAdaptivePresentationControllerDelegate>
@@ -130,7 +102,7 @@
   RecordAction(
       UserMetricsAction("IOS.DefaultBrowserPromo.TailoredFullscreen.Dismiss"));
   UmaHistogramEnumeration("IOS.DefaultBrowserPromo.TailoredFullscreen.Dismiss",
-                          DefaultPromoTypeForUMA(_promoType));
+                          GetDefaultPromoTypeForUMA(_promoType));
   [self logDefaultBrowserFullscreenPromoHistogramForAction:
             IOSDefaultBrowserFullscreenPromoAction::kCancel];
   // This ensures that a modal swipe dismiss will also be logged.
@@ -145,7 +117,7 @@
   RecordAction(
       UserMetricsAction("IOS.DefaultBrowserPromo.TailoredFullscreen.Accepted"));
   UmaHistogramEnumeration("IOS.DefaultBrowserPromo.TailoredFullscreen.Accepted",
-                          DefaultPromoTypeForUMA(_promoType));
+                          GetDefaultPromoTypeForUMA(_promoType));
   [self logDefaultBrowserFullscreenPromoHistogramForAction:
             IOSDefaultBrowserFullscreenPromoAction::kActionButton];
   LogUserInteractionWithTailoredFullscreenPromo();
@@ -162,7 +134,7 @@
   RecordAction(
       UserMetricsAction("IOS.DefaultBrowserPromo.TailoredFullscreen.Dismiss"));
   UmaHistogramEnumeration("IOS.DefaultBrowserPromo.TailoredFullscreen.Dismiss",
-                          DefaultPromoTypeForUMA(_promoType));
+                          GetDefaultPromoTypeForUMA(_promoType));
   [self logDefaultBrowserFullscreenPromoHistogramForAction:
             IOSDefaultBrowserFullscreenPromoAction::kCancel];
   LogUserInteractionWithTailoredFullscreenPromo();
diff --git a/ios/chrome/browser/ui/lens/lens_availability.mm b/ios/chrome/browser/ui/lens/lens_availability.mm
index 731ef28..3c3f7f9 100644
--- a/ios/chrome/browser/ui/lens/lens_availability.mm
+++ b/ios/chrome/browser/ui/lens/lens_availability.mm
@@ -23,6 +23,8 @@
     "Mobile.Keyboard.LensSupportStatus";
 const char kIOSLensNewTabPageSupportStatusHistogramName[] =
     "Mobile.NewTabPage.LensSupportStatus";
+const char kIOSSpotlightSupportStatusHistogramName[] =
+    "Mobile.Spotlight.LensSupportStatus";
 
 namespace lens_availability {
 bool CheckAndLogAvailabilityForLensEntryPoint(
@@ -67,7 +69,7 @@
       if (!base::FeatureList::IsEnabled(kEnableLensInHomeScreenWidget)) {
         flag_enabled = NO;
       }
-      // Spotlight cannot log availailability.
+      availability_metric_name = kIOSSpotlightSupportStatusHistogramName;
       break;
     default:
       NOTREACHED() << "Unsupported Lens Entry Point.";
diff --git a/ios/chrome/browser/ui/lens/lens_coordinator.mm b/ios/chrome/browser/ui/lens/lens_coordinator.mm
index 23d20ea..0ddb3da 100644
--- a/ios/chrome/browser/ui/lens/lens_coordinator.mm
+++ b/ios/chrome/browser/ui/lens/lens_coordinator.mm
@@ -252,6 +252,9 @@
     case LensEntrypoint::Keyboard:
       RecordCameraOpen(CameraOpenEntryPoint::KEYBOARD);
       break;
+    case LensEntrypoint::Spotlight:
+      RecordCameraOpen(CameraOpenEntryPoint::SPOTLIGHT);
+      break;
     default:
       // Do not record the camera open histogram for other entry points.
       break;
@@ -444,7 +447,7 @@
   NSString* shortcutTitle;
   UIApplicationShortcutIcon* shortcutIcon;
   if (useLens) {
-    shortcutType = @"OpenLens";
+    shortcutType = @"OpenLensFromAppIconLongPress";
     shortcutTitle = l10n_util::GetNSStringWithFixup(
         IDS_IOS_APPLICATION_SHORTCUT_LENS_TITLE);
     shortcutIcon =
diff --git a/ios/chrome/browser/ui/ntp/feed_header_view_controller.mm b/ios/chrome/browser/ui/ntp/feed_header_view_controller.mm
index 003f215..4dabe955 100644
--- a/ios/chrome/browser/ui/ntp/feed_header_view_controller.mm
+++ b/ios/chrome/browser/ui/ntp/feed_header_view_controller.mm
@@ -34,8 +34,6 @@
 // Trailing/leading margins for header buttons. Its used to align with the card
 // margins.
 const CGFloat kButtonHorizontalMargin = 14;
-// Font size for label text in header.
-const CGFloat kDiscoverFeedTitleFontSize = 16;
 // Font size for the custom search engine label.
 const CGFloat kCustomSearchEngineLabelFontSize = 13;
 // Font size for the hidden feed label.
@@ -377,9 +375,9 @@
 - (UILabel*)createTitleLabel {
   UILabel* titleLabel = [[UILabel alloc] init];
   titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
-  titleLabel.font = [UIFont systemFontOfSize:kDiscoverFeedTitleFontSize
-                                      weight:UIFontWeightMedium];
-  titleLabel.textColor = [UIColor colorNamed:kGrey700Color];
+  titleLabel.font =
+      [UIFont preferredFontForTextStyle:UIFontTextStyleSubheadline];
+  titleLabel.textColor = [UIColor colorNamed:kTextSecondaryColor];
   titleLabel.adjustsFontForContentSizeCategory = YES;
   titleLabel.accessibilityIdentifier =
       ntp_home::DiscoverHeaderTitleAccessibilityID();
@@ -400,8 +398,7 @@
   [segmentedControl setApportionsSegmentWidthsByContent:NO];
 
   // Set text font and color.
-  UIFont* font = [UIFont systemFontOfSize:kDiscoverFeedTitleFontSize
-                                   weight:UIFontWeightMedium];
+  UIFont* font = [UIFont preferredFontForTextStyle:UIFontTextStyleSubheadline];
   NSDictionary* attributes =
       [NSDictionary dictionaryWithObject:font forKey:NSFontAttributeName];
   [segmentedControl setTitleTextAttributes:attributes
diff --git a/ios/chrome/browser/ui/settings/privacy/BUILD.gn b/ios/chrome/browser/ui/settings/privacy/BUILD.gn
index 1fa9a57c..8de47eb 100644
--- a/ios/chrome/browser/ui/settings/privacy/BUILD.gn
+++ b/ios/chrome/browser/ui/settings/privacy/BUILD.gn
@@ -62,6 +62,7 @@
     "//ios/chrome/browser/ui/settings/clear_browsing_data",
     "//ios/chrome/browser/ui/settings/elements:enterprise_info_popover_view_controller",
     "//ios/chrome/browser/ui/settings/elements:info_popover_view_controller",
+    "//ios/chrome/browser/ui/settings/privacy/lockdown_mode",
     "//ios/chrome/browser/ui/settings/privacy/safe_browsing",
     "//ios/chrome/browser/ui/settings/utils",
     "//ios/chrome/browser/web:feature_flags",
@@ -104,6 +105,7 @@
     "//ios/chrome/browser/shared/ui/table_view/cells",
     "//ios/chrome/browser/ui/settings:settings_root",
     "//ios/chrome/browser/ui/settings/clear_browsing_data",
+    "//ios/chrome/browser/ui/settings/privacy/lockdown_mode",
     "//ios/chrome/browser/ui/settings/utils",
     "//ios/chrome/common/ui/reauthentication",
     "//ui/base",
diff --git a/ios/chrome/browser/ui/settings/privacy/lockdown_mode/BUILD.gn b/ios/chrome/browser/ui/settings/privacy/lockdown_mode/BUILD.gn
new file mode 100644
index 0000000..2a8c0c39
--- /dev/null
+++ b/ios/chrome/browser/ui/settings/privacy/lockdown_mode/BUILD.gn
@@ -0,0 +1,84 @@
+# Copyright 2023 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+source_set("lockdown_mode") {
+  configs += [ "//build/config/compiler:enable_arc" ]
+  sources = [
+    "lockdown_mode_consumer.h",
+    "lockdown_mode_coordinator.h",
+    "lockdown_mode_coordinator.mm",
+    "lockdown_mode_mediator.h",
+    "lockdown_mode_mediator.mm",
+    "lockdown_mode_view_controller.h",
+    "lockdown_mode_view_controller.mm",
+    "lockdown_mode_view_controller_delegate.h",
+  ]
+  deps = [
+    "//components/prefs",
+    "//ios/chrome/app/strings",
+    "//ios/chrome/browser/net:crurl",
+    "//ios/chrome/browser/shared/coordinator/chrome_coordinator",
+    "//ios/chrome/browser/shared/model/browser",
+    "//ios/chrome/browser/shared/model/browser_state",
+    "//ios/chrome/browser/shared/model/prefs:pref_names",
+    "//ios/chrome/browser/shared/ui/list_model",
+    "//ios/chrome/browser/shared/ui/table_view:utils",
+    "//ios/chrome/browser/shared/ui/table_view/cells",
+    "//ios/chrome/browser/ui/settings:settings_root",
+    "//ios/chrome/browser/ui/settings/cells",
+    "//ios/chrome/browser/ui/settings/elements:info_popover_view_controller",
+    "//ios/chrome/browser/ui/settings/utils",
+    "//ios/chrome/common:string_util",
+    "//ios/chrome/common/ui/colors",
+    "//ui/base",
+  ]
+}
+
+source_set("unit_tests") {
+  configs += [ "//build/config/compiler:enable_arc" ]
+  testonly = true
+  sources = [
+    "lockdown_mode_mediator_unittest.mm",
+    "lockdown_mode_view_controller_unittest.mm",
+  ]
+  deps = [
+    ":lockdown_mode",
+    "//components/prefs",
+    "//components/prefs:test_support",
+    "//ios/chrome/app/strings",
+    "//ios/chrome/browser/shared/model/browser_state:test_support",
+    "//ios/chrome/browser/shared/model/prefs:pref_names",
+    "//ios/chrome/browser/shared/ui/table_view:test_support",
+    "//ios/web/public/test",
+    "//testing/gmock",
+    "//testing/gtest",
+    "//third_party/ocmock",
+  ]
+}
+
+source_set("eg2_tests") {
+  configs += [
+    "//build/config/compiler:enable_arc",
+    "//build/config/ios:xctest_config",
+  ]
+  testonly = true
+  sources = [ "lockdown_mode_egtest.mm" ]
+  deps = [
+    "//base",
+    "//base/test:test_support",
+    "//components/strings",
+    "//ios/chrome/app/strings",
+    "//ios/chrome/browser/shared/model/prefs:pref_names",
+    "//ios/chrome/browser/signin:fake_system_identity",
+    "//ios/chrome/browser/ui/authentication:eg_test_support+eg2",
+    "//ios/chrome/browser/ui/popup_menu/overflow_menu:feature_flags",
+    "//ios/chrome/browser/ui/settings/privacy:privacy_constants",
+    "//ios/chrome/browser/web:feature_flags",
+    "//ios/chrome/common/ui/table_view:cells_constants",
+    "//ios/chrome/test/earl_grey:eg_test_support+eg2",
+    "//ios/testing/earl_grey:eg_test_support+eg2",
+    "//ui/base",
+  ]
+  frameworks = [ "UIKit.framework" ]
+}
diff --git a/ios/chrome/browser/ui/settings/privacy/lockdown_mode/lockdown_mode_consumer.h b/ios/chrome/browser/ui/settings/privacy/lockdown_mode/lockdown_mode_consumer.h
new file mode 100644
index 0000000..33556d6
--- /dev/null
+++ b/ios/chrome/browser/ui/settings/privacy/lockdown_mode/lockdown_mode_consumer.h
@@ -0,0 +1,23 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_CHROME_BROWSER_UI_SETTINGS_PRIVACY_LOCKDOWN_MODE_LOCKDOWN_MODE_CONSUMER_H_
+#define IOS_CHROME_BROWSER_UI_SETTINGS_PRIVACY_LOCKDOWN_MODE_LOCKDOWN_MODE_CONSUMER_H_
+
+#import <UIKit/UIKit.h>
+
+@protocol LockdownModeConsumer
+
+// Reload cells for items. Does nothing if the model is not loaded yet.
+- (void)reloadCellsForItems;
+
+// Sets Browser lockdown mode enabled.
+- (void)setBrowserLockdownModeEnabled:(BOOL)enabled;
+
+// Sets OS lockdown mode enabled.
+- (void)setOSLockdownModeEnabled:(BOOL)enabled;
+
+@end
+
+#endif  // IOS_CHROME_BROWSER_UI_SETTINGS_PRIVACY_LOCKDOWN_MODE_LOCKDOWN_MODE_CONSUMER_H_
diff --git a/ios/chrome/browser/ui/settings/privacy/lockdown_mode/lockdown_mode_coordinator.h b/ios/chrome/browser/ui/settings/privacy/lockdown_mode/lockdown_mode_coordinator.h
new file mode 100644
index 0000000..c7d4c81
--- /dev/null
+++ b/ios/chrome/browser/ui/settings/privacy/lockdown_mode/lockdown_mode_coordinator.h
@@ -0,0 +1,39 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_CHROME_BROWSER_UI_SETTINGS_PRIVACY_LOCKDOWN_MODE_LOCKDOWN_MODE_COORDINATOR_H_
+#define IOS_CHROME_BROWSER_UI_SETTINGS_PRIVACY_LOCKDOWN_MODE_LOCKDOWN_MODE_COORDINATOR_H_
+
+#import "ios/chrome/browser/shared/coordinator/chrome_coordinator/chrome_coordinator.h"
+
+@class LockdownModeCoordinator;
+
+// Delegate for LockdownModeCoordinator.
+@protocol LockdownModeCoordinatorDelegate
+
+// Called when the view controller is removed from navigation controller.
+- (void)lockdownModeCoordinatorDidRemove:(LockdownModeCoordinator*)coordinator;
+
+@end
+
+// Coordinator for the lockdown mode settings page view.
+@interface LockdownModeCoordinator : ChromeCoordinator
+
+- (instancetype)initWithBaseViewController:(UIViewController*)viewController
+                                   browser:(Browser*)browser NS_UNAVAILABLE;
+
+// Delegate.
+@property(nonatomic, weak) id<LockdownModeCoordinatorDelegate> delegate;
+
+// Designated initializer.
+// `viewController`: navigation controller.
+// `browser`: browser.
+- (instancetype)initWithBaseNavigationController:
+                    (UINavigationController*)navigationController
+                                         browser:(Browser*)browser
+    NS_DESIGNATED_INITIALIZER;
+
+@end
+
+#endif  // IOS_CHROME_BROWSER_UI_SETTINGS_PRIVACY_LOCKDOWN_MODE_LOCKDOWN_MODE_COORDINATOR_H_
diff --git a/ios/chrome/browser/ui/settings/privacy/lockdown_mode/lockdown_mode_coordinator.mm b/ios/chrome/browser/ui/settings/privacy/lockdown_mode/lockdown_mode_coordinator.mm
new file mode 100644
index 0000000..056e488
--- /dev/null
+++ b/ios/chrome/browser/ui/settings/privacy/lockdown_mode/lockdown_mode_coordinator.mm
@@ -0,0 +1,66 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/chrome/browser/ui/settings/privacy/lockdown_mode/lockdown_mode_coordinator.h"
+
+#import "base/mac/foundation_util.h"
+#import "ios/chrome/browser/shared/model/browser/browser.h"
+#import "ios/chrome/browser/shared/model/browser_state/chrome_browser_state.h"
+#import "ios/chrome/browser/shared/ui/table_view/table_view_utils.h"
+#import "ios/chrome/browser/ui/settings/privacy/lockdown_mode/lockdown_mode_mediator.h"
+#import "ios/chrome/browser/ui/settings/privacy/lockdown_mode/lockdown_mode_view_controller.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+@interface LockdownModeCoordinator () <
+    LockdownModeViewControllerPresentationDelegate>
+
+@property(nonatomic, strong) LockdownModeViewController* viewController;
+@property(nonatomic, strong) LockdownModeMediator* mediator;
+
+@end
+
+@implementation LockdownModeCoordinator
+
+@synthesize baseNavigationController = _baseNavigationController;
+
+- (instancetype)initWithBaseNavigationController:
+                    (UINavigationController*)navigationController
+                                         browser:(Browser*)browser {
+  self = [super initWithBaseViewController:navigationController
+                                   browser:browser];
+  if (self) {
+    _baseNavigationController = navigationController;
+  }
+  return self;
+}
+
+- (void)start {
+  self.viewController =
+      [[LockdownModeViewController alloc] initWithStyle:ChromeTableViewStyle()];
+  self.viewController.presentationDelegate = self;
+  self.mediator = [[LockdownModeMediator alloc]
+      initWithUserPrefService:self.browser->GetBrowserState()->GetPrefs()];
+  self.mediator.consumer = self.viewController;
+  self.viewController.modelDelegate = self.mediator;
+  DCHECK(self.baseNavigationController);
+  [self.baseNavigationController pushViewController:self.viewController
+                                           animated:YES];
+}
+
+- (void)stop {
+  [self.mediator disconnect];
+}
+
+#pragma mark - LockdownModeViewControllerPresentationDelegate
+
+- (void)lockdownModeViewControllerDidRemove:
+    (LockdownModeViewController*)controller {
+  DCHECK_EQ(self.viewController, controller);
+  [self.delegate lockdownModeCoordinatorDidRemove:self];
+}
+
+@end
diff --git a/ios/chrome/browser/ui/settings/privacy/lockdown_mode/lockdown_mode_egtest.mm b/ios/chrome/browser/ui/settings/privacy/lockdown_mode/lockdown_mode_egtest.mm
new file mode 100644
index 0000000..eb9b794
--- /dev/null
+++ b/ios/chrome/browser/ui/settings/privacy/lockdown_mode/lockdown_mode_egtest.mm
@@ -0,0 +1,177 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "base/test/ios/wait_util.h"
+#import "ios/chrome/browser/shared/model/prefs/pref_names.h"
+#import "ios/chrome/browser/ui/popup_menu/overflow_menu/feature_flags.h"
+#import "ios/chrome/browser/ui/settings/privacy/privacy_constants.h"
+#import "ios/chrome/browser/web/features.h"
+#import "ios/chrome/grit/ios_strings.h"
+#import "ios/chrome/test/earl_grey/chrome_actions.h"
+#import "ios/chrome/test/earl_grey/chrome_earl_grey.h"
+#import "ios/chrome/test/earl_grey/chrome_earl_grey_ui.h"
+#import "ios/chrome/test/earl_grey/chrome_matchers.h"
+#import "ios/chrome/test/earl_grey/chrome_test_case.h"
+#import "ios/testing/earl_grey/earl_grey_test.h"
+#import "ui/base/l10n/l10n_util.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+using chrome_test_util::ButtonWithAccessibilityLabelId;
+using chrome_test_util::SettingsMenuPrivacyButton;
+using chrome_test_util::SettingsPrivacyTableView;
+using chrome_test_util::SyncSwitchCell;
+using chrome_test_util::TableViewSwitchCell;
+
+namespace {
+NSString* const kLockdownModeTableViewId = @"kLockdownModeTableViewId";
+NSString* const kLockdownModeCellId = @"kLockdownModeCellId";
+
+// Waits until the pop up is shown.
+[[nodiscard]] bool WaitForPopupDisplay(NSString* popUpMessage) {
+  return base::test::ios::WaitUntilConditionOrTimeout(
+      base::test::ios::kWaitForUIElementTimeout, ^{
+        NSError* error = nil;
+        [[EarlGrey selectElementWithMatcher:grey_text(popUpMessage)]
+            assertWithMatcher:grey_notNil()
+                        error:&error];
+        return (error == nil);
+      });
+}
+
+}  // namespace
+
+// Integration tests using the Lockdown Mode settings screen.
+@interface LockdownModeTestCase : ChromeTestCase
+@end
+
+@implementation LockdownModeTestCase
+
+- (AppLaunchConfiguration)appConfigurationForTestCase {
+  AppLaunchConfiguration config;
+  // TODO (crbug.com/1285974) Remove when bug is resolved.
+  config.features_disabled.push_back(kNewOverflowMenu);
+  config.features_enabled.push_back(web::kBrowserLockdownModeAvailable);
+  return config;
+}
+
+- (void)setUp {
+  [super setUp];
+  // Ensure that Browser Lockdown Mode opt-out starts in its default (opted-out)
+  // state.
+  [ChromeEarlGrey setBoolValue:NO
+                   forUserPref:prefs::kBrowserLockdownModeEnabled];
+  // Ensure that OS Lockdown Mode opt-in starts in its default (opted-out)
+  // state.
+  [ChromeEarlGrey setBoolValue:NO forUserPref:prefs::kOSLockdownModeEnabled];
+}
+
+- (void)tearDown {
+  // Reset preferences back to default values.
+  [ChromeEarlGrey setBoolValue:NO
+                   forUserPref:prefs::kBrowserLockdownModeEnabled];
+  [ChromeEarlGrey setBoolValue:NO forUserPref:prefs::kOSLockdownModeEnabled];
+  [super tearDown];
+}
+
+// Tests the lockdown mode settings when OS lockdown mode is disabled. The
+// settings page should show a toggle button, press it, and check that row says
+// "Off" in the previous screen.
+- (void)testOSLockdownModeDisabled {
+  [self openLockdownModeSettings];
+
+  // Check that lockdown mode row shows an "off" text label.
+  [[EarlGrey
+      selectElementWithMatcher:TableViewSwitchCell(kLockdownModeCellId, NO)]
+      assertWithMatcher:grey_notNil()];
+
+  [self tapLockdownModeToggleButton:NO withNewValue:YES];
+  [[EarlGrey
+      selectElementWithMatcher:chrome_test_util::SettingsMenuBackButton()]
+      performAction:grey_tap()];
+
+  // Check that lockdown mode row shows an "on" text label.
+  [self checkPrivacyLockdownModeItemStatus:YES];
+  GREYAssertTrue(
+      [ChromeEarlGrey userBooleanPref:prefs::kBrowserLockdownModeEnabled],
+      @"Lockdown mode should be on.");
+}
+
+// Tests the lockdown mode settings when OS lockdown mode is enabled. The
+// settings page should show a row with an "i" button.
+- (void)testOSLockdownModeEnabled {
+  [ChromeEarlGrey setBoolValue:NO
+                   forUserPref:prefs::kBrowserLockdownModeEnabled];
+  [ChromeEarlGrey setBoolValue:YES forUserPref:prefs::kOSLockdownModeEnabled];
+  [self openLockdownModeSettings];
+
+  // Check that lockdown mode row shows an "on" text label.
+  id<GREYMatcher> lockdownModeInfoButtonItem = grey_allOf(
+      grey_accessibilityID(kLockdownModeCellId),
+      grey_accessibilityValue(l10n_util::GetNSString(IDS_IOS_SETTING_ON)),
+      grey_sufficientlyVisible(), nil);
+  [[EarlGrey selectElementWithMatcher:lockdownModeInfoButtonItem]
+      assertWithMatcher:grey_notNil()];
+
+  [[EarlGrey selectElementWithMatcher:lockdownModeInfoButtonItem]
+      performAction:grey_tap()];
+  GREYAssert(WaitForPopupDisplay(l10n_util::GetNSString(
+                 IDS_IOS_LOCKDOWN_MODE_INFO_BUTTON_TITLE)),
+             @"Lockdown mode 'i' button wasn't tapped");
+  [[EarlGrey
+      selectElementWithMatcher:grey_text(l10n_util::GetNSString(
+                                   IDS_IOS_LOCKDOWN_MODE_INFO_BUTTON_SUMMARY))]
+      assertWithMatcher:grey_notNil()];
+}
+
+#pragma mark - Helpers
+
+// Opens lockdown mode settings.
+- (void)openLockdownModeSettings {
+  [ChromeEarlGreyUI openSettingsMenu];
+  [ChromeEarlGreyUI tapSettingsMenuButton:SettingsMenuPrivacyButton()];
+  [ChromeEarlGreyUI tapPrivacyMenuButton:ButtonWithAccessibilityLabelId(
+                                             IDS_IOS_LOCKDOWN_MODE_TITLE)];
+}
+
+// Taps lockdown mode toggle button with the expected `currentToggleStatus`.
+- (void)tapLockdownModeToggleButton:(BOOL)currentToggleStatus
+                       withNewValue:(BOOL)newToggleStatus {
+  // Needs to scroll slowly to make sure to not miss a cell if it is not
+  // currently on the screen. It should not be bigger than the visible part
+  // of the collection view.
+  const CGFloat kPixelsToScroll = 300;
+  id<GREYAction> searchAction =
+      grey_scrollInDirection(kGREYDirectionDown, kPixelsToScroll);
+  id<GREYMatcher> lockdownSwitchButton = chrome_test_util::TableViewSwitchCell(
+      kLockdownModeCellId,
+      /*is_toggled_on=*/currentToggleStatus,
+      /*enabled=*/YES);
+  [[[EarlGrey selectElementWithMatcher:lockdownSwitchButton]
+         usingSearchAction:searchAction
+      onElementWithMatcher:grey_accessibilityID(kLockdownModeTableViewId)]
+      performAction:chrome_test_util::TurnTableViewSwitchOn(newToggleStatus)];
+}
+
+// Checks if the lockdown mode item in the privacy settings page shows on or
+// off. Scroll action is needed for smaller devices where the lockdown mode item
+// may not be shown initially.
+- (void)checkPrivacyLockdownModeItemStatus:(BOOL)status {
+  const CGFloat kPixelsToScroll = 300;
+  id<GREYAction> searchAction =
+      grey_scrollInDirection(kGREYDirectionDown, kPixelsToScroll);
+  NSString* statusLabel = status ? l10n_util::GetNSString(IDS_IOS_SETTING_ON)
+                                 : l10n_util::GetNSString(IDS_IOS_SETTING_OFF);
+  id<GREYMatcher> lockdownModeTableViewDetailItem = grey_allOf(
+      grey_accessibilityID(kPrivacyLockdownModeCellId),
+      grey_accessibilityValue(statusLabel), grey_sufficientlyVisible(), nil);
+  [[[EarlGrey selectElementWithMatcher:lockdownModeTableViewDetailItem]
+         usingSearchAction:searchAction
+      onElementWithMatcher:SettingsPrivacyTableView()]
+      assertWithMatcher:grey_notNil()];
+}
+
+@end
diff --git a/ios/chrome/browser/ui/settings/privacy/lockdown_mode/lockdown_mode_mediator.h b/ios/chrome/browser/ui/settings/privacy/lockdown_mode/lockdown_mode_mediator.h
new file mode 100644
index 0000000..e8811d06
--- /dev/null
+++ b/ios/chrome/browser/ui/settings/privacy/lockdown_mode/lockdown_mode_mediator.h
@@ -0,0 +1,32 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_CHROME_BROWSER_UI_SETTINGS_PRIVACY_LOCKDOWN_MODE_LOCKDOWN_MODE_MEDIATOR_H_
+#define IOS_CHROME_BROWSER_UI_SETTINGS_PRIVACY_LOCKDOWN_MODE_LOCKDOWN_MODE_MEDIATOR_H_
+#import <UIKit/UIKit.h>
+
+#import "ios/chrome/browser/ui/settings/privacy/lockdown_mode/lockdown_mode_consumer.h"
+#import "ios/chrome/browser/ui/settings/privacy/lockdown_mode/lockdown_mode_view_controller_delegate.h"
+
+class PrefService;
+
+// Mediator for the Lockdown Mode Settings UI.
+@interface LockdownModeMediator : NSObject <LockdownModeViewControllerDelegate>
+
+// Consumer for mediator.
+@property(nonatomic, weak) id<LockdownModeConsumer> consumer;
+
+// Designated initializer. All the parameters should not be null.
+// `userPrefService`: preference service from the browser state.
+- (instancetype)initWithUserPrefService:(PrefService*)userPrefService
+    NS_DESIGNATED_INITIALIZER;
+
+- (instancetype)init NS_UNAVAILABLE;
+
+// Cleans up anything before mediator shuts down.
+- (void)disconnect;
+
+@end
+
+#endif  // IOS_CHROME_BROWSER_UI_SETTINGS_PRIVACY_LOCKDOWN_MODE_LOCKDOWN_MODE_MEDIATOR_H_
diff --git a/ios/chrome/browser/ui/settings/privacy/lockdown_mode/lockdown_mode_mediator.mm b/ios/chrome/browser/ui/settings/privacy/lockdown_mode/lockdown_mode_mediator.mm
new file mode 100644
index 0000000..1c45da30
--- /dev/null
+++ b/ios/chrome/browser/ui/settings/privacy/lockdown_mode/lockdown_mode_mediator.mm
@@ -0,0 +1,74 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/chrome/browser/ui/settings/privacy/lockdown_mode/lockdown_mode_mediator.h"
+
+#import "base/mac/foundation_util.h"
+#import "base/notreached.h"
+#import "components/prefs/pref_service.h"
+#import "ios/chrome/browser/shared/model/prefs/pref_names.h"
+#import "ios/chrome/browser/shared/ui/list_model/list_model.h"
+#import "ios/chrome/browser/ui/settings/cells/sync_switch_item.h"
+#import "ios/chrome/browser/ui/settings/utils/observable_boolean.h"
+#import "ios/chrome/browser/ui/settings/utils/pref_backed_boolean.h"
+#import "ios/chrome/grit/ios_strings.h"
+#import "ui/base/l10n/l10n_util.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+namespace {}  // namespace
+
+@interface LockdownModeMediator () <BooleanObserver>
+
+// User pref service.
+@property(nonatomic, assign, readonly) PrefService* userPrefService;
+
+// Preference value for the Lockdown Mode feature.
+@property(nonatomic, strong, readonly)
+    PrefBackedBoolean* lockdownModePreference;
+
+@end
+
+@implementation LockdownModeMediator
+
+- (instancetype)initWithUserPrefService:(PrefService*)userPrefService {
+  self = [super init];
+  if (self) {
+    DCHECK(userPrefService);
+    _userPrefService = userPrefService;
+    _lockdownModePreference = [[PrefBackedBoolean alloc]
+        initWithPrefService:userPrefService
+                   prefName:prefs::kBrowserLockdownModeEnabled];
+    _lockdownModePreference.observer = self;
+  }
+  return self;
+}
+
+- (void)setConsumer:(id<LockdownModeConsumer>)consumer {
+  _consumer = consumer;
+  [_consumer setOSLockdownModeEnabled:self.userPrefService->GetBoolean(
+                                          prefs::kOSLockdownModeEnabled)];
+  [_consumer setBrowserLockdownModeEnabled:self.lockdownModePreference.value];
+}
+
+- (void)disconnect {
+  _lockdownModePreference = nil;
+}
+
+#pragma mark - LockdownModeViewControllerDelegate
+
+- (void)didEnableBrowserLockdownMode:(BOOL)enabled {
+  self.lockdownModePreference.value = enabled;
+}
+
+#pragma mark - BooleanObserver
+
+- (void)booleanDidChange:(id<ObservableBoolean>)observableBoolean {
+  [self.consumer
+      setBrowserLockdownModeEnabled:self.lockdownModePreference.value];
+}
+
+@end
diff --git a/ios/chrome/browser/ui/settings/privacy/lockdown_mode/lockdown_mode_mediator_unittest.mm b/ios/chrome/browser/ui/settings/privacy/lockdown_mode/lockdown_mode_mediator_unittest.mm
new file mode 100644
index 0000000..7a50aed0
--- /dev/null
+++ b/ios/chrome/browser/ui/settings/privacy/lockdown_mode/lockdown_mode_mediator_unittest.mm
@@ -0,0 +1,63 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/chrome/browser/ui/settings/privacy/lockdown_mode/lockdown_mode_mediator.h"
+
+#import "components/prefs/pref_registry_simple.h"
+#import "components/prefs/testing_pref_service.h"
+#import "ios/chrome/browser/shared/model/browser_state/test_chrome_browser_state.h"
+#import "ios/chrome/browser/shared/model/prefs/pref_names.h"
+#import "ios/chrome/browser/ui/settings/privacy/lockdown_mode/lockdown_mode_consumer.h"
+#import "ios/web/public/test/web_task_environment.h"
+#import "testing/gmock/include/gmock/gmock.h"
+#import "testing/gtest_mac.h"
+#import "testing/platform_test.h"
+#import "third_party/ocmock/OCMock/OCMock.h"
+#import "third_party/ocmock/gtest_support.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+class LockdownModeMediatorTest : public PlatformTest {
+ protected:
+  LockdownModeMediatorTest() = default;
+
+  void CreatePrefs() {
+    prefs_ = std::make_unique<TestingPrefServiceSimple>();
+    prefs_->registry()->RegisterBooleanPref(prefs::kBrowserLockdownModeEnabled,
+                                            /*default_value=*/false);
+    prefs_->registry()->RegisterBooleanPref(prefs::kOSLockdownModeEnabled,
+                                            /*default_value=*/false);
+  }
+
+  web::WebTaskEnvironment task_env_;
+  std::unique_ptr<TestingPrefServiceSimple> prefs_;
+};
+
+// Tests that the pref and the mediator are playing nicely together.
+TEST_F(LockdownModeMediatorTest, TestPref) {
+  CreatePrefs();
+  LockdownModeMediator* mediator =
+      [[LockdownModeMediator alloc] initWithUserPrefService:prefs_.get()];
+
+  // Check that the consumer is correctly updated when set.
+  id consumer = OCMProtocolMock(@protocol(LockdownModeConsumer));
+  OCMExpect([consumer setBrowserLockdownModeEnabled:NO]);
+  OCMExpect([consumer setOSLockdownModeEnabled:NO]);
+
+  mediator.consumer = consumer;
+  EXPECT_OCMOCK_VERIFY(consumer);
+
+  // Check that the consumer is correctly updated when the pref is changed.
+  OCMExpect([consumer setBrowserLockdownModeEnabled:YES]);
+  prefs_->SetBoolean(prefs::kBrowserLockdownModeEnabled, true);
+  EXPECT_OCMOCK_VERIFY(consumer);
+
+  // Check that the pref is correctly updated when the mediator is asked.
+  [mediator didEnableBrowserLockdownMode:NO];
+  EXPECT_FALSE(prefs_->GetBoolean(prefs::kBrowserLockdownModeEnabled));
+
+  [mediator disconnect];
+}
diff --git a/ios/chrome/browser/ui/settings/privacy/lockdown_mode/lockdown_mode_view_controller.h b/ios/chrome/browser/ui/settings/privacy/lockdown_mode/lockdown_mode_view_controller.h
new file mode 100644
index 0000000..97c22520
--- /dev/null
+++ b/ios/chrome/browser/ui/settings/privacy/lockdown_mode/lockdown_mode_view_controller.h
@@ -0,0 +1,38 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_CHROME_BROWSER_UI_SETTINGS_PRIVACY_LOCKDOWN_MODE_LOCKDOWN_MODE_VIEW_CONTROLLER_H_
+#define IOS_CHROME_BROWSER_UI_SETTINGS_PRIVACY_LOCKDOWN_MODE_LOCKDOWN_MODE_VIEW_CONTROLLER_H_
+
+#import "ios/chrome/browser/ui/settings/privacy/lockdown_mode/lockdown_mode_consumer.h"
+#import "ios/chrome/browser/ui/settings/privacy/lockdown_mode/lockdown_mode_view_controller_delegate.h"
+#import "ios/chrome/browser/ui/settings/settings_controller_protocol.h"
+#import "ios/chrome/browser/ui/settings/settings_root_table_view_controller.h"
+
+@class LockdownModeViewController;
+
+// Delegate for presentation events related to LockdownMode View Controller
+// which is usually handled by a class that holds the view controller.
+@protocol LockdownModeViewControllerPresentationDelegate <NSObject>
+
+// Called when the view controller is removed from its parent.
+- (void)lockdownModeViewControllerDidRemove:
+    (LockdownModeViewController*)controller;
+
+@end
+
+// View controller to related to Lockdown Mode setting.
+@interface LockdownModeViewController
+    : SettingsRootTableViewController <LockdownModeConsumer,
+                                       SettingsControllerProtocol>
+
+// Presentation delegate.
+@property(nonatomic, weak) id<LockdownModeViewControllerPresentationDelegate>
+    presentationDelegate;
+// Model delegate.
+@property(nonatomic, weak) id<LockdownModeViewControllerDelegate> modelDelegate;
+
+@end
+
+#endif  // IOS_CHROME_BROWSER_UI_SETTINGS_PRIVACY_LOCKDOWN_MODE_LOCKDOWN_MODE_VIEW_CONTROLLER_H_
diff --git a/ios/chrome/browser/ui/settings/privacy/lockdown_mode/lockdown_mode_view_controller.mm b/ios/chrome/browser/ui/settings/privacy/lockdown_mode/lockdown_mode_view_controller.mm
new file mode 100644
index 0000000..fcc0f14
--- /dev/null
+++ b/ios/chrome/browser/ui/settings/privacy/lockdown_mode/lockdown_mode_view_controller.mm
@@ -0,0 +1,264 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/chrome/browser/ui/settings/privacy/lockdown_mode/lockdown_mode_view_controller.h"
+
+#import "base/mac/foundation_util.h"
+#import "base/metrics/user_metrics.h"
+#import "base/metrics/user_metrics_action.h"
+#import "ios/chrome/browser/net/crurl.h"
+#import "ios/chrome/browser/shared/ui/table_view/cells/table_view_info_button_cell.h"
+#import "ios/chrome/browser/shared/ui/table_view/cells/table_view_info_button_item.h"
+#import "ios/chrome/browser/shared/ui/table_view/cells/table_view_switch_cell.h"
+#import "ios/chrome/browser/shared/ui/table_view/cells/table_view_switch_item.h"
+
+#import "ios/chrome/browser/ui/settings/elements/info_popover_view_controller.h"
+#import "ios/chrome/common/string_util.h"
+#import "ios/chrome/common/ui/colors/semantic_color_names.h"
+#import "ios/chrome/grit/ios_strings.h"
+#import "ui/base/l10n/l10n_util_mac.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+namespace {
+
+// List of sections.
+typedef NS_ENUM(NSInteger, SectionIdentifier) {
+  SectionIdentifierLockdownMode = kSectionIdentifierEnumZero,
+  SectionIdentifierFooter,
+};
+
+typedef NS_ENUM(NSInteger, ItemType) {
+  ItemTypeLockdownModeSwitch = kItemTypeEnumZero,
+  ItemTypeLockdownModeFooter,
+};
+
+NSString* const kLockdownModeTableViewId = @"kLockdownModeTableViewId";
+NSString* const kLockdownModeCellId = @"kLockdownModeCellId";
+
+}  // namespace
+
+@interface LockdownModeViewController ()
+
+// The item related to the switch for the lockdown mode setting.
+@property(nonatomic, strong) TableViewItem* lockdownModeItem;
+
+// Boolean related to dismissing settings.
+@property(nonatomic) BOOL settingsAreDismissed;
+
+// Boolean that is set through the model in order to tell the view if OS
+// Lockdown Mode is enabled.
+@property(nonatomic) BOOL osLockdownModeEnabled;
+
+@end
+
+@implementation LockdownModeViewController
+
+- (void)viewDidLoad {
+  [super viewDidLoad];
+  self.tableView.accessibilityIdentifier = kLockdownModeTableViewId;
+  self.title = l10n_util::GetNSString(IDS_IOS_LOCKDOWN_MODE_TITLE);
+  [self loadModel];
+}
+
+#pragma mark - CollectionViewController
+
+- (void)loadModel {
+  [super loadModel];
+  TableViewModel* model = self.tableViewModel;
+  [model addSectionWithIdentifier:SectionIdentifierLockdownMode];
+  [model addSectionWithIdentifier:SectionIdentifierFooter];
+  [model addItem:self.lockdownModeItem
+      toSectionWithIdentifier:SectionIdentifierLockdownMode];
+  [model setFooter:[self showLockdownModeFooter]
+      forSectionWithIdentifier:SectionIdentifierFooter];
+}
+
+#pragma mark - LockdownModeConsumer
+
+- (void)reloadCellsForItems {
+  if (!self.tableViewModel) {
+    // No need to reconfigure since the model has not been loaded yet.
+    return;
+  }
+  NSArray<TableViewItem*>* items = @[ self.lockdownModeItem ];
+  [self reloadCellsForItems:items withRowAnimation:UITableViewRowAnimationNone];
+}
+
+- (void)setBrowserLockdownModeEnabled:(BOOL)enabled {
+  if (!_osLockdownModeEnabled) {
+    TableViewSwitchItem* lockdownModeSwitchItem =
+        base::mac::ObjCCastStrict<TableViewSwitchItem>(self.lockdownModeItem);
+    lockdownModeSwitchItem.on = enabled;
+  }
+  [self reloadCellsForItems];
+}
+
+- (void)setOSLockdownModeEnabled:(BOOL)enabled {
+  // Cell isn't reloaded because the variable is only set during the initial
+  // view load. This boolean doesn't get changed unless the OS setting is
+  // changed which requires a device restart.
+  _osLockdownModeEnabled = enabled;
+}
+
+#pragma mark - Private
+
+// Called when switch is toggled.
+- (void)lockdownModeSwitchChanged:(UISwitch*)sender {
+  [self.modelDelegate didEnableBrowserLockdownMode:sender.isOn];
+}
+
+// Removes the view as a result of pressing "Done" button.
+- (void)dismiss {
+  [self dismissViewControllerAnimated:YES completion:nil];
+}
+
+// Returns the primary attributed string for the InfoPopOverViewController.
+- (NSAttributedString*)createPrimaryMessage {
+  NSDictionary* primaryAttributes = @{
+    NSForegroundColorAttributeName : [UIColor colorNamed:kTextPrimaryColor],
+    NSFontAttributeName :
+        [UIFont preferredFontForTextStyle:UIFontTextStyleSubheadline]
+  };
+
+  return [[NSAttributedString alloc]
+      initWithString:l10n_util::GetNSString(
+                         IDS_IOS_LOCKDOWN_MODE_INFO_BUTTON_TITLE)
+          attributes:primaryAttributes];
+}
+
+// Returns the secondary attributed string for the InfoPopOverViewController.
+- (NSAttributedString*)createSecondaryMessage {
+  // Create and format the text.
+  NSDictionary* textAttributes = @{
+    NSForegroundColorAttributeName : [UIColor colorNamed:kTextSecondaryColor],
+    NSFontAttributeName :
+        [UIFont preferredFontForTextStyle:UIFontTextStyleFootnote]
+  };
+
+  NSString* message;
+  message = l10n_util::GetNSString(IDS_IOS_LOCKDOWN_MODE_INFO_BUTTON_SUMMARY);
+  NSAttributedString* attributedString =
+      [[NSAttributedString alloc] initWithString:message
+                                      attributes:textAttributes];
+
+  return attributedString;
+}
+
+#pragma mark - Actions
+
+// Called when the user clicks on a managed information button.
+- (void)didTapUIInfoButton:(UIButton*)buttonView {
+  NSAttributedString* primaryString = [self createPrimaryMessage];
+  NSAttributedString* secondaryString = [self createSecondaryMessage];
+
+  InfoPopoverViewController* bubbleViewController =
+      [[InfoPopoverViewController alloc]
+          initWithPrimaryAttributedString:primaryString
+                secondaryAttributedString:secondaryString
+                                     icon:nil
+                   isPresentingFromButton:YES];
+
+  // Disable the button when showing the bubble.
+  buttonView.enabled = NO;
+
+  // Set the anchor and arrow direction of the bubble.
+  bubbleViewController.popoverPresentationController.sourceView = buttonView;
+  bubbleViewController.popoverPresentationController.sourceRect =
+      buttonView.bounds;
+  bubbleViewController.popoverPresentationController.permittedArrowDirections =
+      UIPopoverArrowDirectionAny;
+
+  [self presentViewController:bubbleViewController animated:YES completion:nil];
+}
+
+#pragma mark - UITableViewDataSource
+
+- (UITableViewCell*)tableView:(UITableView*)tableView
+        cellForRowAtIndexPath:(NSIndexPath*)indexPath {
+  UITableViewCell* cell = [super tableView:tableView
+                     cellForRowAtIndexPath:indexPath];
+  if ([cell isKindOfClass:[TableViewSwitchCell class]]) {
+    switch ([self.tableViewModel itemTypeForIndexPath:indexPath]) {
+      case ItemTypeLockdownModeSwitch: {
+        TableViewSwitchCell* switchCell =
+            base::mac::ObjCCastStrict<TableViewSwitchCell>(cell);
+        [switchCell.switchView addTarget:self
+                                  action:@selector(lockdownModeSwitchChanged:)
+                        forControlEvents:UIControlEventValueChanged];
+        break;
+      }
+    }
+  } else if ([cell isKindOfClass:[TableViewInfoButtonCell class]]) {
+    TableViewInfoButtonCell* infoCell =
+        base::mac::ObjCCastStrict<TableViewInfoButtonCell>(cell);
+    [infoCell.trailingButton addTarget:self
+                                action:@selector(didTapUIInfoButton:)
+                      forControlEvents:UIControlEventTouchUpInside];
+  }
+
+  return cell;
+}
+
+#pragma mark - UIViewController
+
+- (void)didMoveToParentViewController:(UIViewController*)parent {
+  [super didMoveToParentViewController:parent];
+  if (!parent) {
+    [self.presentationDelegate lockdownModeViewControllerDidRemove:self];
+  }
+}
+
+#pragma mark - SettingsControllerProtocol
+
+- (void)reportDismissalUserAction {
+  base::RecordAction(
+      base::UserMetricsAction("MobileLockdownModeSettingsClose"));
+}
+
+- (void)reportBackUserAction {
+  base::RecordAction(base::UserMetricsAction("MobileLockdownModeSettingsBack"));
+}
+
+#pragma mark - Properties
+
+- (TableViewItem*)lockdownModeItem {
+  if (!_lockdownModeItem) {
+    if (_osLockdownModeEnabled) {
+      TableViewInfoButtonItem* lockdownModeItem =
+          [[TableViewInfoButtonItem alloc]
+              initWithType:ItemTypeLockdownModeSwitch];
+      lockdownModeItem.statusText = l10n_util::GetNSString(IDS_IOS_SETTING_ON);
+      lockdownModeItem.text =
+          l10n_util::GetNSString(IDS_IOS_LOCKDOWN_MODE_TITLE);
+      lockdownModeItem.detailText =
+          l10n_util::GetNSString(IDS_IOS_LOCKDOWN_MODE_SWITCH_BUTTON_SUMMARY);
+      lockdownModeItem.accessibilityIdentifier = kLockdownModeCellId;
+      _lockdownModeItem = lockdownModeItem;
+    } else {
+      TableViewSwitchItem* lockdownModeItem =
+          [[TableViewSwitchItem alloc] initWithType:ItemTypeLockdownModeSwitch];
+      lockdownModeItem.text =
+          l10n_util::GetNSString(IDS_IOS_LOCKDOWN_MODE_TITLE);
+      lockdownModeItem.detailText =
+          l10n_util::GetNSString(IDS_IOS_LOCKDOWN_MODE_SWITCH_BUTTON_SUMMARY);
+      lockdownModeItem.accessibilityIdentifier = kLockdownModeCellId;
+      _lockdownModeItem = lockdownModeItem;
+    }
+  }
+  return _lockdownModeItem;
+}
+
+- (TableViewHeaderFooterItem*)showLockdownModeFooter {
+  TableViewLinkHeaderFooterItem* lockdownModeFooterItem =
+      [[TableViewLinkHeaderFooterItem alloc]
+          initWithType:ItemTypeLockdownModeFooter];
+  lockdownModeFooterItem.text =
+      l10n_util::GetNSString(IDS_IOS_LOCKDOWN_MODE_FOOTER);
+  return lockdownModeFooterItem;
+}
+
+@end
diff --git a/ios/chrome/browser/ui/settings/privacy/lockdown_mode/lockdown_mode_view_controller_delegate.h b/ios/chrome/browser/ui/settings/privacy/lockdown_mode/lockdown_mode_view_controller_delegate.h
new file mode 100644
index 0000000..1d76075
--- /dev/null
+++ b/ios/chrome/browser/ui/settings/privacy/lockdown_mode/lockdown_mode_view_controller_delegate.h
@@ -0,0 +1,19 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_CHROME_BROWSER_UI_SETTINGS_PRIVACY_LOCKDOWN_MODE_LOCKDOWN_MODE_VIEW_CONTROLLER_DELEGATE_H_
+#define IOS_CHROME_BROWSER_UI_SETTINGS_PRIVACY_LOCKDOWN_MODE_LOCKDOWN_MODE_VIEW_CONTROLLER_DELEGATE_H_
+
+#import <UIKit/UIKit.h>
+
+// Delegate for communicating actions that happen in the view controller. This
+// is usually used to relay information to the model.
+@protocol LockdownModeViewControllerDelegate
+
+// Sends switch toggle response to the model so that it can be updated.
+- (void)didEnableBrowserLockdownMode:(BOOL)enabled;
+
+@end
+
+#endif  // IOS_CHROME_BROWSER_UI_SETTINGS_PRIVACY_LOCKDOWN_MODE_LOCKDOWN_MODE_VIEW_CONTROLLER_DELEGATE_H_
diff --git a/ios/chrome/browser/ui/settings/privacy/lockdown_mode/lockdown_mode_view_controller_unittest.mm b/ios/chrome/browser/ui/settings/privacy/lockdown_mode/lockdown_mode_view_controller_unittest.mm
new file mode 100644
index 0000000..f3d9d563
--- /dev/null
+++ b/ios/chrome/browser/ui/settings/privacy/lockdown_mode/lockdown_mode_view_controller_unittest.mm
@@ -0,0 +1,68 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/chrome/browser/ui/settings/privacy/lockdown_mode/lockdown_mode_view_controller.h"
+
+#import "base/mac/foundation_util.h"
+#import "ios/chrome/browser/shared/ui/table_view/chrome_table_view_controller_test.h"
+#import "ios/chrome/grit/ios_strings.h"
+#import "testing/gtest_mac.h"
+#import "ui/base/l10n/l10n_util.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+class LockdownModeViewControllerTest : public ChromeTableViewControllerTest {
+ protected:
+  ChromeTableViewController* InstantiateController() override {
+    return [[LockdownModeViewController alloc]
+        initWithStyle:UITableViewStyleGrouped];
+  }
+};
+
+// Tests that there is a single item in the Table View.
+TEST_F(LockdownModeViewControllerTest, TestItems) {
+  CreateController();
+  CheckController();
+  CheckTitle(l10n_util::GetNSString(IDS_IOS_LOCKDOWN_MODE_TITLE));
+
+  ASSERT_EQ(2, NumberOfSections());
+  ASSERT_EQ(1, NumberOfItemsInSection(0));
+
+  CheckSwitchCellStateAndTextWithId(NO, IDS_IOS_LOCKDOWN_MODE_TITLE, 0, 0);
+}
+
+// Tests that the switch item gets correctly updated when its value is changed
+// before the model is loaded.
+TEST_F(LockdownModeViewControllerTest, TestSwitchItemAtLoad) {
+  // Load the controller manually as this is testing setting the DefaultPageMode
+  // before the model is loaded.
+  LockdownModeViewController* controller = [[LockdownModeViewController alloc]
+      initWithStyle:UITableViewStyleGrouped];
+
+  [controller setBrowserLockdownModeEnabled:YES];
+
+  [controller loadModel];
+  // Force the tableView to be built.
+  ASSERT_TRUE([controller view]);
+
+  id switch_item = [controller.tableViewModel
+      itemAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];
+  ASSERT_TRUE([switch_item respondsToSelector:@selector(isOn)]);
+  EXPECT_TRUE([switch_item isOn]);
+}
+
+// Tests that the switch item gets correctly updated.
+TEST_F(LockdownModeViewControllerTest, TestCheckmark) {
+  ChromeTableViewController* chrome_controller = controller();
+  LockdownModeViewController* controller =
+      base::mac::ObjCCastStrict<LockdownModeViewController>(chrome_controller);
+
+  [controller setBrowserLockdownModeEnabled:YES];
+  CheckSwitchCellStateAndTextWithId(YES, IDS_IOS_LOCKDOWN_MODE_TITLE, 0, 0);
+
+  [controller setBrowserLockdownModeEnabled:NO];
+  CheckSwitchCellStateAndTextWithId(NO, IDS_IOS_LOCKDOWN_MODE_TITLE, 0, 0);
+}
diff --git a/ios/chrome/browser/ui/settings/privacy/privacy_coordinator.mm b/ios/chrome/browser/ui/settings/privacy/privacy_coordinator.mm
index 7a0b43d9..6a825e9 100644
--- a/ios/chrome/browser/ui/settings/privacy/privacy_coordinator.mm
+++ b/ios/chrome/browser/ui/settings/privacy/privacy_coordinator.mm
@@ -14,6 +14,7 @@
 #import "ios/chrome/browser/shared/public/features/features.h"
 #import "ios/chrome/browser/ui/settings/clear_browsing_data/clear_browsing_data_coordinator.h"
 #import "ios/chrome/browser/ui/settings/privacy/handoff_table_view_controller.h"
+#import "ios/chrome/browser/ui/settings/privacy/lockdown_mode/lockdown_mode_coordinator.h"
 #import "ios/chrome/browser/ui/settings/privacy/privacy_navigation_commands.h"
 #import "ios/chrome/browser/ui/settings/privacy/privacy_safe_browsing_coordinator.h"
 #import "ios/chrome/browser/ui/settings/privacy/privacy_table_view_controller.h"
@@ -28,7 +29,8 @@
     ClearBrowsingDataCoordinatorDelegate,
     PrivacyNavigationCommands,
     PrivacySafeBrowsingCoordinatorDelegate,
-    PrivacyTableViewControllerPresentationDelegate>
+    PrivacyTableViewControllerPresentationDelegate,
+    LockdownModeCoordinatorDelegate>
 
 @property(nonatomic, strong) id<ApplicationCommands> handler;
 @property(nonatomic, strong) PrivacyTableViewController* viewController;
@@ -40,6 +42,9 @@
 @property(nonatomic, strong)
     ClearBrowsingDataCoordinator* clearBrowsingDataCoordinator;
 
+// Coordinator for Lockdown Mode settings.
+@property(nonatomic, strong) LockdownModeCoordinator* lockdownModeCoordinator;
+
 @end
 
 @implementation PrivacyCoordinator
@@ -121,6 +126,15 @@
   [self.safeBrowsingCoordinator start];
 }
 
+- (void)showLockdownMode {
+  DCHECK(!self.lockdownModeCoordinator);
+  self.lockdownModeCoordinator = [[LockdownModeCoordinator alloc]
+      initWithBaseNavigationController:self.baseNavigationController
+                               browser:self.browser];
+  self.lockdownModeCoordinator.delegate = self;
+  [self.lockdownModeCoordinator start];
+}
+
 #pragma mark - ClearBrowsingDataCoordinatorDelegate
 
 - (void)clearBrowsingDataCoordinatorViewControllerWasRemoved:
@@ -140,4 +154,13 @@
   self.safeBrowsingCoordinator = nil;
 }
 
+#pragma mark - LockdownModeCoordinatorDelegate
+
+- (void)lockdownModeCoordinatorDidRemove:(LockdownModeCoordinator*)coordinator {
+  DCHECK_EQ(self.lockdownModeCoordinator, coordinator);
+  [self.lockdownModeCoordinator stop];
+  self.lockdownModeCoordinator.delegate = nil;
+  self.lockdownModeCoordinator = nil;
+}
+
 @end
diff --git a/ios/chrome/browser/ui/settings/privacy/privacy_navigation_commands.h b/ios/chrome/browser/ui/settings/privacy/privacy_navigation_commands.h
index 1efdac9..601e5c9 100644
--- a/ios/chrome/browser/ui/settings/privacy/privacy_navigation_commands.h
+++ b/ios/chrome/browser/ui/settings/privacy/privacy_navigation_commands.h
@@ -15,9 +15,12 @@
 // Shows ClearBrowsingData screen.
 - (void)showClearBrowsingData;
 
-// Shows SafeBrowsing screen.
+// Shows Safe Browsing screen.
 - (void)showSafeBrowsing;
 
+// Shows Lockdown Mode screen.
+- (void)showLockdownMode;
+
 @end
 
 #endif  // IOS_CHROME_BROWSER_UI_SETTINGS_PRIVACY_PRIVACY_NAVIGATION_COMMANDS_H_
diff --git a/ios/chrome/browser/ui/settings/privacy/privacy_table_view_controller.mm b/ios/chrome/browser/ui/settings/privacy/privacy_table_view_controller.mm
index 4c15b87..82d1cc7f 100644
--- a/ios/chrome/browser/ui/settings/privacy/privacy_table_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/privacy/privacy_table_view_controller.mm
@@ -107,6 +107,8 @@
   TableViewDetailIconItem* _handoffDetailItem;
   // Safe Browsing item.
   TableViewDetailIconItem* _safeBrowsingDetailItem;
+  // Locdown Mode item.
+  TableViewDetailIconItem* _lockdownModeDetailItem;
 
   // Whether Settings have been dismissed.
   BOOL _settingsAreDismissed;
@@ -161,6 +163,8 @@
         prefs::kSafeBrowsingEnabled, &_prefChangeRegistrar);
     _prefObserverBridge->ObserveChangesForPreference(
         prefs::kSafeBrowsingEnhanced, &_prefChangeRegistrar);
+    _prefObserverBridge->ObserveChangesForPreference(
+        prefs::kBrowserLockdownModeEnabled, &_prefChangeRegistrar);
 
     _incognitoReauthPref = [[PrefBackedBoolean alloc]
         initWithPrefService:GetApplicationContext()->GetLocalState()
@@ -206,7 +210,6 @@
   TableViewModel* model = self.tableViewModel;
   [model addSectionWithIdentifier:SectionIdentifierPrivacyContent];
   [model addSectionWithIdentifier:SectionIdentifierSafeBrowsing];
-  [model addSectionWithIdentifier:SectionIdentifierLockdownMode];
 
   if (base::FeatureList::IsEnabled(
           security_interstitials::features::kHttpsOnlyMode)) {
@@ -219,6 +222,10 @@
   [model addSectionWithIdentifier:SectionIdentifierIncognitoAuth];
   [model addSectionWithIdentifier:SectionIdentifierIncognitoInterstitial];
 
+  if (web::IsBrowserLockdownModeEnabled()) {
+    [model addSectionWithIdentifier:SectionIdentifierLockdownMode];
+  }
+
   // Clear Browsing item.
   [model addItem:[self clearBrowsingDetailItem]
       toSectionWithIdentifier:SectionIdentifierPrivacyContent];
@@ -227,19 +234,10 @@
   [model addItem:[self safeBrowsingDetailItem]
       toSectionWithIdentifier:SectionIdentifierSafeBrowsing];
 
-  // Lockdown Mode item.
-  if (web::IsBrowserLockdownModeEnabled()) {
-    [model addItem:[self lockdownModeDetailItem]
-        toSectionWithIdentifier:SectionIdentifierLockdownMode];
-  }
-
   // Web Services item.
   [model addItem:[self handoffDetailItem]
       toSectionWithIdentifier:SectionIdentifierWebServices];
 
-  [model setFooter:[self showPrivacyFooterItem]
-      forSectionWithIdentifier:SectionIdentifierIncognitoInterstitial];
-
   // Incognito reauth item is added. If Incognito mode is disabled, or device
   // authentication is not supported, a disabled version is shown instead with
   // relevant information as a popover.
@@ -262,6 +260,17 @@
           : self.incognitoInterstitialItem;
   [model addItem:incognitoInterstitialItem
       toSectionWithIdentifier:SectionIdentifierIncognitoInterstitial];
+
+  // Lockdown Mode item.
+  if (web::IsBrowserLockdownModeEnabled()) {
+    [model addItem:[self lockdownModeDetailItem]
+        toSectionWithIdentifier:SectionIdentifierLockdownMode];
+    [model setFooter:[self showPrivacyFooterItem]
+        forSectionWithIdentifier:SectionIdentifierLockdownMode];
+  } else {
+    [model setFooter:[self showPrivacyFooterItem]
+        forSectionWithIdentifier:SectionIdentifierIncognitoInterstitial];
+  }
 }
 
 #pragma mark - Model Objects
@@ -373,12 +382,12 @@
       _browserState->GetPrefs()->GetBoolean(prefs::kBrowserLockdownModeEnabled)
           ? l10n_util::GetNSString(IDS_IOS_SETTING_ON)
           : l10n_util::GetNSString(IDS_IOS_SETTING_OFF);
-  TableViewDetailIconItem* lockdownModeDetailItem =
+  _lockdownModeDetailItem =
       [self detailItemWithType:ItemTypeLockdownMode
-                          titleId:IDS_IOS_PRIVACY_LOCKDOWN_MODE_TITLE
+                          titleId:IDS_IOS_LOCKDOWN_MODE_TITLE
                        detailText:detailText
           accessibilityIdentifier:kPrivacyLockdownModeCellId];
-  return lockdownModeDetailItem;
+  return _lockdownModeDetailItem;
 }
 
 - (TableViewSwitchItem*)incognitoReauthItem {
@@ -487,6 +496,9 @@
           "SafeBrowsing.Settings.ShowedFromParentSettings"));
       [self.handler showSafeBrowsing];
       break;
+    case ItemTypeLockdownMode:
+      [self.handler showLockdownMode];
+      break;
     default:
       break;
   }
@@ -562,6 +574,15 @@
     _safeBrowsingDetailItem.detailText = [self safeBrowsingDetailText];
     [self reconfigureCellsForItems:@[ _safeBrowsingDetailItem ]];
   }
+
+  if (preferenceName == prefs::kBrowserLockdownModeEnabled) {
+    NSString* detailText = _browserState->GetPrefs()->GetBoolean(preferenceName)
+                               ? l10n_util::GetNSString(IDS_IOS_SETTING_ON)
+                               : l10n_util::GetNSString(IDS_IOS_SETTING_OFF);
+    _lockdownModeDetailItem.detailText = detailText;
+    [self reconfigureCellsForItems:@[ _lockdownModeDetailItem ]];
+    return;
+  }
 }
 
 #pragma mark - BooleanObserver
diff --git a/ios/chrome/browser/ui/settings/privacy/privacy_table_view_controller_unittest.mm b/ios/chrome/browser/ui/settings/privacy/privacy_table_view_controller_unittest.mm
index ffc4de6..0116386 100644
--- a/ios/chrome/browser/ui/settings/privacy/privacy_table_view_controller_unittest.mm
+++ b/ios/chrome/browser/ui/settings/privacy/privacy_table_view_controller_unittest.mm
@@ -178,15 +178,6 @@
       l10n_util::GetNSString(IDS_IOS_PRIVACY_SAFE_BROWSING_TITLE),
       SafeBrowsingDetailText(), currentSection, 0);
 
-  // Lockdown Mode section.
-  if (base::FeatureList::IsEnabled(web::kBrowserLockdownModeAvailable)) {
-    currentSection++;
-    EXPECT_EQ(1, NumberOfItemsInSection(currentSection));
-    CheckTextCellTextAndDetailText(
-        l10n_util::GetNSString(IDS_IOS_PRIVACY_LOCKDOWN_MODE_TITLE),
-        l10n_util::GetNSString(IDS_IOS_SETTING_OFF), currentSection, 0);
-  }
-
   // HTTPS-Only Mode section.
   if (base::FeatureList::IsEnabled(
           security_interstitials::features::kHttpsOnlyMode)) {
@@ -236,6 +227,15 @@
         NO, IDS_IOS_OPTIONS_ENABLE_INCOGNITO_INTERSTITIAL, currentSection, 0);
   }
 
+  // Lockdown Mode section.
+  if (base::FeatureList::IsEnabled(web::kBrowserLockdownModeAvailable)) {
+    currentSection++;
+    EXPECT_EQ(1, NumberOfItemsInSection(currentSection));
+    CheckTextCellTextAndDetailText(
+        l10n_util::GetNSString(IDS_IOS_LOCKDOWN_MODE_TITLE),
+        l10n_util::GetNSString(IDS_IOS_SETTING_OFF), currentSection, 0);
+  }
+
   // Testing section index and text of the privacy footer.
   CheckSectionFooter(
       l10n_util::GetNSString(IDS_IOS_PRIVACY_GOOGLE_SERVICES_FOOTER),
diff --git a/ios/chrome/test/BUILD.gn b/ios/chrome/test/BUILD.gn
index cdb4796..71d127b 100644
--- a/ios/chrome/test/BUILD.gn
+++ b/ios/chrome/test/BUILD.gn
@@ -393,6 +393,7 @@
     "//ios/chrome/browser/ui/settings/password/password_settings:unit_tests",
     "//ios/chrome/browser/ui/settings/password/passwords_in_other_apps:unit_tests",
     "//ios/chrome/browser/ui/settings/privacy:unit_tests",
+    "//ios/chrome/browser/ui/settings/privacy/lockdown_mode:unit_tests",
     "//ios/chrome/browser/ui/settings/safety_check:unit_tests",
     "//ios/chrome/browser/ui/settings/sync:unit_tests",
     "//ios/chrome/browser/ui/settings/utils:unit_tests",
diff --git a/ios/chrome/test/earl_grey2/BUILD.gn b/ios/chrome/test/earl_grey2/BUILD.gn
index 1182326..b4958a4d 100644
--- a/ios/chrome/test/earl_grey2/BUILD.gn
+++ b/ios/chrome/test/earl_grey2/BUILD.gn
@@ -141,6 +141,7 @@
     "//ios/chrome/browser/ui/settings/password:eg2_tests",
     "//ios/chrome/browser/ui/settings/password/passwords_in_other_apps:eg2_tests",
     "//ios/chrome/browser/ui/settings/privacy:eg2_tests",
+    "//ios/chrome/browser/ui/settings/privacy/lockdown_mode:eg2_tests",
     "//ios/chrome/browser/ui/settings/sync:eg2_tests",
     "//ios/chrome/browser/ui/settings/tabs:eg2_tests",
     "//ios/chrome/browser/ui/settings/tabs/inactive_tabs:eg2_tests",
diff --git a/ios/web/web_state/ui/crw_web_controller_container_view.mm b/ios/web/web_state/ui/crw_web_controller_container_view.mm
index 1015869..d6ff7a79f 100644
--- a/ios/web/web_state/ui/crw_web_controller_container_view.mm
+++ b/ios/web/web_state/ui/crw_web_controller_container_view.mm
@@ -88,6 +88,20 @@
 
 - (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
   [super traitCollectionDidChange:previousTraitCollection];
+  if ((self.traitCollection.verticalSizeClass !=
+       previousTraitCollection.verticalSizeClass) ||
+      (self.traitCollection.horizontalSizeClass !=
+       previousTraitCollection.horizontalSizeClass) ||
+      self.traitCollection.preferredContentSizeCategory !=
+          previousTraitCollection.preferredContentSizeCategory) {
+    // Reset zoom scale when the window is resized (portrait to landscape,
+    // landscape to portrait or multi-window resizing), or if text size is
+    // modified as websites can adjust to the preferred content size (using
+    // font: -apple-system-body;). It avoids being in a different zoomed
+    // position from where the user initially zoomed.
+    UIScrollView* scrollView = self.contentViewProxy.contentView.scrollView;
+    scrollView.zoomScale = scrollView.minimumZoomScale;
+  }
   if (previousTraitCollection.preferredContentSizeCategory !=
       self.traitCollection.preferredContentSizeCategory) {
     // In case the preferred content size changes, the layout is dirty.
diff --git a/media/audio/win/audio_low_latency_output_win.cc b/media/audio/win/audio_low_latency_output_win.cc
index ea648eb..a717e10 100644
--- a/media/audio/win/audio_low_latency_output_win.cc
+++ b/media/audio/win/audio_low_latency_output_win.cc
@@ -754,7 +754,7 @@
         if (is_glitch) {
           // TODO(olka): Exploratory check. Run it for a couple of days on
           // Canary/Dev and remove.
-          CHECK_LE(gap_duration, base::Seconds(1));
+          CHECK_LE(gap_duration, base::Seconds(10));
           glitch_info_accumulator.Add({.duration = gap_duration, .count = 1});
         }
       }
diff --git a/media/capture/BUILD.gn b/media/capture/BUILD.gn
index 101ea5552..1e1337c 100644
--- a/media/capture/BUILD.gn
+++ b/media/capture/BUILD.gn
@@ -356,6 +356,7 @@
       "//media/capture/video/chromeos/mojom:cros_camera",
       "//third_party/libsync",
     ]
+    data_deps = [ ":mojo_service_manager_policy" ]
   }
 
   if (is_fuchsia) {
@@ -580,3 +581,8 @@
     ]
   }
 }
+
+copy("mojo_service_manager_policy") {
+  sources = [ "video/chromeos/camera_hal_dispatcher_mojo_policy.jsonc" ]
+  outputs = [ "$root_out_dir/mojo_service_manager/{{source_file_part}}" ]
+}
diff --git a/media/capture/video/chromeos/DEPS b/media/capture/video/chromeos/DEPS
index 69fb2cb..8fa852e 100644
--- a/media/capture/video/chromeos/DEPS
+++ b/media/capture/video/chromeos/DEPS
@@ -5,5 +5,6 @@
   "+chromeos/components/sensors",
   "+chromeos/dbus",
   "+components/chromeos_camera",
+  "+third_party/cros_system_api",
   "+third_party/libsync",
 ]
diff --git a/media/capture/video/chromeos/camera_hal_dispatcher_impl.cc b/media/capture/video/chromeos/camera_hal_dispatcher_impl.cc
index 49f8014..25137bc8 100644
--- a/media/capture/video/chromeos/camera_hal_dispatcher_impl.cc
+++ b/media/capture/video/chromeos/camera_hal_dispatcher_impl.cc
@@ -29,7 +29,6 @@
 #include "base/task/single_thread_task_runner.h"
 #include "base/trace_event/trace_event.h"
 #include "chromeos/ash/components/mojo_service_manager/connection.h"
-#include "chromeos/ash/components/mojo_service_manager/mojom/mojo_service_manager.mojom.h"
 #include "chromeos/components/sensors/sensor_util.h"
 #include "components/device_event_log/device_event_log.h"
 #include "media/capture/video/chromeos/mojom/camera_common.mojom.h"
@@ -42,6 +41,7 @@
 #include "mojo/public/cpp/platform/platform_channel.h"
 #include "mojo/public/cpp/platform/socket_utils_posix.h"
 #include "mojo/public/cpp/system/invitation.h"
+#include "third_party/cros_system_api/mojo/service_constants.h"
 
 namespace media {
 
@@ -270,7 +270,16 @@
     LOG(ERROR) << "Failed to generate authentication token for server as a "
                   "sensor client";
   }
-
+  // CameraHalDispatcher registers itself to Mojo Service Manager.
+  // TODO(b/258095854): Survey what we can do if mojo_service_manager is down.
+  if (ash::mojo_service_manager::IsServiceManagerBound()) {
+    auto* proxy = ash::mojo_service_manager::GetServiceManagerProxy();
+    proxy->Register(
+        /*service_name=*/chromeos::mojo_services::kCrosCameraHalDispatcher,
+        provider_receiver_.BindNewPipeAndPassRemote());
+  } else {
+    LOG(ERROR) << "Mojo Service Manager is not bound.";
+  }
   blocking_io_task_runner_->PostTask(
       FROM_HERE,
       base::BindOnce(&CameraHalDispatcherImpl::CreateSocket,
@@ -440,6 +449,24 @@
     mojo::PendingRemote<cros::mojom::CameraHalServer> camera_hal_server,
     const base::UnguessableToken& token,
     RegisterServerWithTokenCallback callback) {
+  base::UnguessableToken server_token = token;
+  // Unretained reference is safe here because CameraHalDispatcherImpl owns
+  // |proxy_thread_|.
+  // This function should be run on the thread which MojoServiceManagerProxy
+  // is bound on. Currently it is main thread.
+  proxy_task_runner_->PostTask(
+      FROM_HERE,
+      base::BindOnce(
+          &CameraHalDispatcherImpl::RegisterServerWithTokenOnProxyThread,
+          base::Unretained(this), std::move(camera_hal_server),
+          std::move(server_token),
+          base::BindPostTaskToCurrentDefault(std::move(callback))));
+}
+
+void CameraHalDispatcherImpl::RegisterServerWithTokenOnProxyThread(
+    mojo::PendingRemote<cros::mojom::CameraHalServer> camera_hal_server,
+    const base::UnguessableToken& token,
+    RegisterServerWithTokenCallback callback) {
   DCHECK(proxy_task_runner_->BelongsToCurrentThread());
 
   if (camera_hal_server_) {
@@ -524,6 +551,22 @@
     mojo::PendingRemote<chromeos::sensors::mojom::SensorHalClient> client,
     const base::UnguessableToken& auth_token,
     RegisterSensorClientWithTokenCallback callback) {
+  base::UnguessableToken client_auth_token = auth_token;
+  // Unretained reference is safe here because CameraHalDispatcherImpl owns
+  // |proxy_thread_|.
+  proxy_task_runner_->PostTask(
+      FROM_HERE,
+      base::BindOnce(
+          &CameraHalDispatcherImpl::RegisterSensorClientWithTokenOnProxyThread,
+          base::Unretained(this), std::move(client),
+          std::move(client_auth_token),
+          base::BindPostTaskToCurrentDefault(std::move(callback))));
+}
+
+void CameraHalDispatcherImpl::RegisterSensorClientWithTokenOnProxyThread(
+    mojo::PendingRemote<chromeos::sensors::mojom::SensorHalClient> client,
+    const base::UnguessableToken& auth_token,
+    RegisterSensorClientWithTokenCallback callback) {
   DCHECK(proxy_task_runner_->BelongsToCurrentThread());
 
   if (!sensor_enabled_) {
@@ -536,7 +579,7 @@
       base::BindOnce(
           &CameraHalDispatcherImpl::RegisterSensorClientWithTokenOnUIThread,
           weak_factory_.GetWeakPtr(), std::move(client), auth_token,
-          base::BindPostTaskToCurrentDefault(std::move(callback))));
+          std::move(callback)));
 }
 
 void CameraHalDispatcherImpl::BindServiceToMojoServiceManager(
@@ -1148,4 +1191,13 @@
       service_name, /*timeout=*/absl::nullopt, std::move(receiver));
 }
 
+void CameraHalDispatcherImpl::Request(
+    chromeos::mojo_service_manager::mojom::ProcessIdentityPtr identity,
+    mojo::ScopedMessagePipeHandle receiver) {
+  receiver_set_.Add(this,
+                    mojo::PendingReceiver<cros::mojom::CameraHalDispatcher>(
+                        std::move(receiver)));
+  VLOG(1) << "New CameraHalDispatcher binding added from Mojo Service Manager.";
+}
+
 }  // namespace media
diff --git a/media/capture/video/chromeos/camera_hal_dispatcher_impl.h b/media/capture/video/chromeos/camera_hal_dispatcher_impl.h
index 6799195d..5e37890 100644
--- a/media/capture/video/chromeos/camera_hal_dispatcher_impl.h
+++ b/media/capture/video/chromeos/camera_hal_dispatcher_impl.h
@@ -23,6 +23,7 @@
 #include "base/thread_annotations.h"
 #include "base/threading/thread.h"
 #include "base/unguessable_token.h"
+#include "chromeos/ash/components/mojo_service_manager/mojom/mojo_service_manager.mojom.h"
 #include "components/chromeos_camera/common/jpeg_encode_accelerator.mojom.h"
 #include "components/chromeos_camera/common/mjpeg_decode_accelerator.mojom.h"
 #include "media/capture/capture_export.h"
@@ -168,7 +169,8 @@
 // See https://crbug.com/891961.
 class CAPTURE_EXPORT CameraHalDispatcherImpl final
     : public cros::mojom::CameraHalDispatcher,
-      public cros::mojom::CameraHalServerCallbacks {
+      public cros::mojom::CameraHalServerCallbacks,
+      public chromeos::mojo_service_manager::mojom::ServiceProvider {
  public:
   using CameraEffectsControllerCallback =
       base::RepeatingCallback<void(cros::mojom::EffectsConfigPtr,
@@ -328,12 +330,6 @@
   void SetCameraSWPrivacySwitchStateOnProxyThread(
       cros::mojom::CameraPrivacySwitchState state);
 
-  void RegisterClientWithTokenOnProxyThread(
-      mojo::PendingRemote<cros::mojom::CameraHalClient> client,
-      cros::mojom::CameraClientType type,
-      base::UnguessableToken token,
-      RegisterClientWithTokenCallback callback);
-
   void AddClientObserverOnProxyThread(
       CameraClientObserver* observer,
       base::OnceCallback<void(int32_t)> result_callback,
@@ -354,6 +350,22 @@
       std::vector<CameraClientObserver*> client_observers,
       base::WaitableEvent* removed);
 
+  void RegisterServerWithTokenOnProxyThread(
+      mojo::PendingRemote<cros::mojom::CameraHalServer> camera_hal_server,
+      const base::UnguessableToken& token,
+      RegisterServerWithTokenCallback callback);
+
+  void RegisterClientWithTokenOnProxyThread(
+      mojo::PendingRemote<cros::mojom::CameraHalClient> client,
+      cros::mojom::CameraClientType type,
+      base::UnguessableToken token,
+      RegisterClientWithTokenCallback callback);
+
+  void RegisterSensorClientWithTokenOnProxyThread(
+      mojo::PendingRemote<chromeos::sensors::mojom::SensorHalClient> client,
+      const base::UnguessableToken& auth_token,
+      RegisterSensorClientWithTokenCallback callback);
+
   void RegisterSensorClientWithTokenOnUIThread(
       mojo::PendingRemote<chromeos::sensors::mojom::SensorHalClient> client,
       const base::UnguessableToken& auth_token,
@@ -382,6 +394,11 @@
   void OnCameraEffectsObserverAddOnProxyThread(
       CameraEffectObserverCallback camera_effect_observer_callback);
 
+  // chromeos::mojo_service_manager::mojom::ServiceProvider overrides.
+  void Request(
+      chromeos::mojo_service_manager::mojom::ProcessIdentityPtr identity,
+      mojo::ScopedMessagePipeHandle receiver) override;
+
   std::string GetDeviceIdFromCameraId(int32_t camera_id);
   base::flat_set<std::string> GetDeviceIdsFromCameraIds(
       base::flat_set<int32_t> camera_ids);
@@ -463,6 +480,10 @@
   base::flat_map<int32_t, std::string> camera_id_to_device_id_
       GUARDED_BY(camera_id_to_device_id_lock_);
 
+  // Receiver for mojo service manager service provider.
+  mojo::Receiver<chromeos::mojo_service_manager::mojom::ServiceProvider>
+      provider_receiver_{this};
+
   base::WeakPtrFactory<CameraHalDispatcherImpl> weak_factory_{this};
 };
 
diff --git a/media/capture/video/chromeos/camera_hal_dispatcher_mojo_policy.jsonc b/media/capture/video/chromeos/camera_hal_dispatcher_mojo_policy.jsonc
new file mode 100644
index 0000000..a331b52
--- /dev/null
+++ b/media/capture/video/chromeos/camera_hal_dispatcher_mojo_policy.jsonc
@@ -0,0 +1,14 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Start with an array of policies.
+// Validate this file in CrOS device: mojo_service_manager --check_policy
+[
+  {
+    "identity": "u:r:cros_browser:s0",
+    "own": [
+      "CrosCameraHalDispatcher"
+    ]
+  }
+]
diff --git a/media/gpu/chromeos/BUILD.gn b/media/gpu/chromeos/BUILD.gn
index 3a935e4..f07b44c 100644
--- a/media/gpu/chromeos/BUILD.gn
+++ b/media/gpu/chromeos/BUILD.gn
@@ -246,8 +246,8 @@
 }
 
 if (use_v4l2_codec) {
-  test("image_processor_perftest") {
-    sources = [ "image_processor_perftest.cc" ]
+  test("image_processor_perf_test") {
+    sources = [ "image_processor_perf_test.cc" ]
     deps = [
       ":chromeos",
       "//base/test:test_support",
diff --git a/media/gpu/chromeos/image_processor_perftest.cc b/media/gpu/chromeos/image_processor_perf_test.cc
similarity index 100%
rename from media/gpu/chromeos/image_processor_perftest.cc
rename to media/gpu/chromeos/image_processor_perf_test.cc
diff --git a/media/gpu/v4l2/v4l2_device.cc b/media/gpu/v4l2/v4l2_device.cc
index bb63ead..60e7240 100644
--- a/media/gpu/v4l2/v4l2_device.cc
+++ b/media/gpu/v4l2/v4l2_device.cc
@@ -93,7 +93,10 @@
   static scoped_refptr<V4L2Queue> CreateQueue(scoped_refptr<V4L2Device> dev,
                                               enum v4l2_buf_type type,
                                               base::OnceClosure destroy_cb) {
-    return new V4L2Queue(std::move(dev), type, std::move(destroy_cb));
+    return new V4L2Queue(base::BindRepeating(&V4L2Device::Ioctl, dev),
+                         base::BindRepeating(&V4L2Device::SchedulePoll, dev),
+                         base::BindRepeating(&V4L2Device::Mmap, dev), type,
+                         std::move(destroy_cb));
   }
 };
 
@@ -583,32 +586,6 @@
   return true;
 }
 
-std::vector<base::ScopedFD> V4L2Device::GetDmabufsForV4L2Buffer(
-    int index,
-    size_t num_planes,
-    enum v4l2_buf_type buf_type) {
-  DVLOGF(3);
-  DCHECK(V4L2_TYPE_IS_MULTIPLANAR(buf_type));
-
-  std::vector<base::ScopedFD> dmabuf_fds;
-  for (size_t i = 0; i < num_planes; ++i) {
-    struct v4l2_exportbuffer expbuf;
-    memset(&expbuf, 0, sizeof(expbuf));
-    expbuf.type = buf_type;
-    expbuf.index = index;
-    expbuf.plane = i;
-    expbuf.flags = O_CLOEXEC;
-    if (Ioctl(VIDIOC_EXPBUF, &expbuf) != 0) {
-      dmabuf_fds.clear();
-      break;
-    }
-
-    dmabuf_fds.push_back(base::ScopedFD(expbuf.fd));
-  }
-
-  return dmabuf_fds;
-}
-
 bool V4L2Device::CanCreateEGLImageFrom(const Fourcc fourcc) const {
   static uint32_t kEGLImageDrmFmtsSupported[] = {
     DRM_FORMAT_ARGB8888,
diff --git a/media/gpu/v4l2/v4l2_device.h b/media/gpu/v4l2/v4l2_device.h
index 45492ba..b029645 100644
--- a/media/gpu/v4l2/v4l2_device.h
+++ b/media/gpu/v4l2/v4l2_device.h
@@ -191,14 +191,6 @@
              unsigned int offset);
   void Munmap(void* addr, unsigned int len);
 
-  // Return a vector of dmabuf file descriptors, exported for V4L2 buffer with
-  // |index|, assuming the buffer contains |num_planes| V4L2 planes and is of
-  // |type|. Return an empty vector on failure.
-  // The caller is responsible for closing the file descriptors after use.
-  std::vector<base::ScopedFD> GetDmabufsForV4L2Buffer(int index,
-                                                      size_t num_planes,
-                                                      enum v4l2_buf_type type);
-
   // Return true if the given V4L2 pixfmt can be used in CreateEGLImage()
   // for the current platform.
   bool CanCreateEGLImageFrom(const Fourcc fourcc) const;
diff --git a/media/gpu/v4l2/v4l2_queue.cc b/media/gpu/v4l2/v4l2_queue.cc
index 833a5d3..5645fa6 100644
--- a/media/gpu/v4l2/v4l2_queue.cc
+++ b/media/gpu/v4l2/v4l2_queue.cc
@@ -5,6 +5,7 @@
 #include "media/gpu/v4l2/v4l2_queue.h"
 
 #include <errno.h>
+#include <fcntl.h>
 #include <libdrm/drm_fourcc.h>
 #include <linux/media.h>
 #include <poll.h>
@@ -17,7 +18,6 @@
 #include "media/gpu/chromeos/platform_video_frame_utils.h"
 #include "media/gpu/macros.h"
 #include "media/gpu/v4l2/v4l2_device.h"
-#include "media/gpu/v4l2/v4l2_utils.h"
 
 namespace media {
 
@@ -109,6 +109,37 @@
   }
 }
 
+// Returns a vector of dmabuf file descriptors, exported for V4L2 buffer with
+// |index|, assuming the buffer contains |num_planes| V4L2 planes and is of
+// |buf_type|. Returns an empty vector on failure. The caller is responsible for
+// closing the file descriptors after use.
+std::vector<base::ScopedFD> GetDmabufsForV4L2Buffer(
+    const IoctlAsCallback& ioctl_cb,
+    int index,
+    size_t num_planes,
+    enum v4l2_buf_type buf_type) {
+  DVLOGF(3);
+  DCHECK(V4L2_TYPE_IS_MULTIPLANAR(buf_type));
+
+  std::vector<base::ScopedFD> dmabuf_fds;
+  for (size_t i = 0; i < num_planes; ++i) {
+    struct v4l2_exportbuffer expbuf;
+    memset(&expbuf, 0, sizeof(expbuf));
+    expbuf.type = buf_type;
+    expbuf.index = index;
+    expbuf.plane = i;
+    expbuf.flags = O_CLOEXEC;
+    if (ioctl_cb.Run(VIDIOC_EXPBUF, &expbuf) != 0) {
+      dmabuf_fds.clear();
+      break;
+    }
+
+    dmabuf_fds.push_back(base::ScopedFD(expbuf.fd));
+  }
+
+  return dmabuf_fds;
+}
+
 }  // namespace
 
 V4L2ExtCtrl::V4L2ExtCtrl(uint32_t id) {
@@ -128,7 +159,8 @@
 // Also provides helper functions.
 class V4L2Buffer {
  public:
-  static std::unique_ptr<V4L2Buffer> Create(scoped_refptr<V4L2Device> device,
+  static std::unique_ptr<V4L2Buffer> Create(const IoctlAsCallback& ioctl_cb,
+                                            const MmapAsCallback& mmap_cb,
                                             enum v4l2_buf_type type,
                                             enum v4l2_memory memory,
                                             const struct v4l2_format& format,
@@ -145,7 +177,8 @@
   scoped_refptr<VideoFrame> GetVideoFrame();
 
  private:
-  V4L2Buffer(scoped_refptr<V4L2Device> device,
+  V4L2Buffer(const IoctlAsCallback& ioctl_cb,
+             const MmapAsCallback& mmap_cb,
              enum v4l2_buf_type type,
              enum v4l2_memory memory,
              const struct v4l2_format& format,
@@ -153,7 +186,8 @@
   bool Query();
   scoped_refptr<VideoFrame> CreateVideoFrame();
 
-  scoped_refptr<V4L2Device> device_;
+  const IoctlAsCallback ioctl_cb_;
+  const MmapAsCallback mmap_cb_;
   std::vector<void*> plane_mappings_;
 
   // V4L2 data as queried by QUERYBUF.
@@ -167,28 +201,27 @@
   scoped_refptr<VideoFrame> video_frame_;
 };
 
-std::unique_ptr<V4L2Buffer> V4L2Buffer::Create(scoped_refptr<V4L2Device> device,
+std::unique_ptr<V4L2Buffer> V4L2Buffer::Create(const IoctlAsCallback& ioctl_cb,
+                                               const MmapAsCallback& mmap_cb,
                                                enum v4l2_buf_type type,
                                                enum v4l2_memory memory,
                                                const struct v4l2_format& format,
                                                size_t buffer_id) {
   // Not using std::make_unique because constructor is private.
-  std::unique_ptr<V4L2Buffer> buffer(
-      new V4L2Buffer(device, type, memory, format, buffer_id));
+  std::unique_ptr<V4L2Buffer> buffer(new V4L2Buffer(std::move(ioctl_cb),
+                                                    std::move(mmap_cb), type,
+                                                    memory, format, buffer_id));
 
-  if (!buffer->Query()) {
-    return nullptr;
-  }
-
-  return buffer;
+  return buffer->Query() ? std::move(buffer) : nullptr;
 }
 
-V4L2Buffer::V4L2Buffer(scoped_refptr<V4L2Device> device,
+V4L2Buffer::V4L2Buffer(const IoctlAsCallback& ioctl_cb,
+                       const MmapAsCallback& mmap_cb,
                        enum v4l2_buf_type type,
                        enum v4l2_memory memory,
                        const struct v4l2_format& format,
                        size_t buffer_id)
-    : device_(device), format_(format) {
+    : ioctl_cb_(ioctl_cb), mmap_cb_(mmap_cb), format_(format) {
   DCHECK(V4L2_TYPE_IS_MULTIPLANAR(type));
   DCHECK_LE(format.fmt.pix_mp.num_planes, std::size(v4l2_planes_));
 
@@ -209,14 +242,14 @@
   if (v4l2_buffer_.memory == V4L2_MEMORY_MMAP) {
     for (size_t i = 0; i < plane_mappings_.size(); i++) {
       if (plane_mappings_[i] != nullptr) {
-        device_->Munmap(plane_mappings_[i], v4l2_buffer_.m.planes[i].length);
+        munmap(plane_mappings_[i], v4l2_buffer_.m.planes[i].length);
       }
     }
   }
 }
 
 bool V4L2Buffer::Query() {
-  int ret = device_->Ioctl(VIDIOC_QUERYBUF, &v4l2_buffer_);
+  int ret = ioctl_cb_.Run(VIDIOC_QUERYBUF, &v4l2_buffer_);
   if (ret) {
     VPLOGF(1) << "VIDIOC_QUERYBUF failed: ";
     return false;
@@ -245,9 +278,9 @@
     return nullptr;
   }
 
-  p = device_->Mmap(nullptr, v4l2_buffer_.m.planes[plane].length,
-                    PROT_READ | PROT_WRITE, MAP_SHARED,
-                    v4l2_buffer_.m.planes[plane].m.mem_offset);
+  p = mmap_cb_.Run(nullptr, v4l2_buffer_.m.planes[plane].length,
+                   PROT_READ | PROT_WRITE, MAP_SHARED,
+                   v4l2_buffer_.m.planes[plane].m.mem_offset);
   if (p == MAP_FAILED) {
     VPLOGF(1) << "mmap() failed: ";
     return nullptr;
@@ -272,8 +305,8 @@
     return nullptr;
   }
 
-  std::vector<base::ScopedFD> dmabuf_fds = device_->GetDmabufsForV4L2Buffer(
-      v4l2_buffer_.index, v4l2_buffer_.length,
+  std::vector<base::ScopedFD> dmabuf_fds = GetDmabufsForV4L2Buffer(
+      ioctl_cb_, v4l2_buffer_.index, v4l2_buffer_.length,
       static_cast<enum v4l2_buf_type>(v4l2_buffer_.type));
   if (dmabuf_fds.empty()) {
     VLOGF(1) << "Failed to get DMABUFs of V4L2 buffer";
@@ -949,11 +982,15 @@
 #define DVQLOGF(level) \
   DVLOGF(level) << "(" << V4L2BufferTypeToString(type_) << ") "
 
-V4L2Queue::V4L2Queue(scoped_refptr<V4L2Device> dev,
+V4L2Queue::V4L2Queue(const IoctlAsCallback& ioctl_cb,
+                     const base::RepeatingClosure& schedule_poll_cb,
+                     const MmapAsCallback& mmap_cb,
                      enum v4l2_buf_type type,
                      base::OnceClosure destroy_cb)
     : type_(type),
-      device_(dev),
+      ioctl_cb_(ioctl_cb),
+      schedule_poll_cb_(schedule_poll_cb),
+      mmap_cb_(mmap_cb),
       destroy_cb_(std::move(destroy_cb)),
       weak_this_factory_(this) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
@@ -964,7 +1001,7 @@
   reqbufs.count = 0;
   reqbufs.type = type;
   reqbufs.memory = V4L2_MEMORY_MMAP;
-  if (device_->Ioctl(VIDIOC_REQBUFS, &reqbufs) != 0) {
+  if (ioctl_cb_.Run(VIDIOC_REQBUFS, &reqbufs) != 0) {
     VPLOGF(1) << "Request support checks's VIDIOC_REQBUFS ioctl failed.";
     return;
   }
@@ -1001,8 +1038,9 @@
 absl::optional<struct v4l2_format> V4L2Queue::SetFormat(uint32_t fourcc,
                                                         const gfx::Size& size,
                                                         size_t buffer_size) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   struct v4l2_format format = BuildV4L2Format(type_, fourcc, size, buffer_size);
-  if (device_->Ioctl(VIDIOC_S_FMT, &format) != 0 ||
+  if (ioctl_cb_.Run(VIDIOC_S_FMT, &format) != 0 ||
       format.fmt.pix_mp.pixelformat != fourcc) {
     VPQLOGF(2) << "Failed to set format fourcc: " << FourccToString(fourcc);
     return absl::nullopt;
@@ -1015,8 +1053,9 @@
 absl::optional<struct v4l2_format> V4L2Queue::TryFormat(uint32_t fourcc,
                                                         const gfx::Size& size,
                                                         size_t buffer_size) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   struct v4l2_format format = BuildV4L2Format(type_, fourcc, size, buffer_size);
-  if (device_->Ioctl(VIDIOC_TRY_FMT, &format) != 0 ||
+  if (ioctl_cb_.Run(VIDIOC_TRY_FMT, &format) != 0 ||
       format.fmt.pix_mp.pixelformat != fourcc) {
     VPQLOGF(2) << "Failed to try format fourcc: " << FourccToString(fourcc);
     return absl::nullopt;
@@ -1026,10 +1065,11 @@
 }
 
 std::pair<absl::optional<struct v4l2_format>, int> V4L2Queue::GetFormat() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   struct v4l2_format format;
   memset(&format, 0, sizeof(format));
   format.type = type_;
-  if (device_->Ioctl(VIDIOC_G_FMT, &format) != 0) {
+  if (ioctl_cb_.Run(VIDIOC_G_FMT, &format) != 0) {
     VPQLOGF(2) << "Failed to get format";
     return std::make_pair(absl::nullopt, errno);
   }
@@ -1038,6 +1078,7 @@
 }
 
 absl::optional<gfx::Rect> V4L2Queue::GetVisibleRect() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   // Some drivers prior to 4.13 only accept the non-MPLANE variant when using
   // VIDIOC_G_SELECTION. This block can be removed once we stop supporting
   // kernels < 4.13.
@@ -1060,7 +1101,7 @@
   memset(&selection, 0, sizeof(selection));
   selection.type = compose_type;
   selection.target = V4L2_SEL_TGT_COMPOSE;
-  if (device_->Ioctl(VIDIOC_G_SELECTION, &selection) == 0) {
+  if (ioctl_cb_.Run(VIDIOC_G_SELECTION, &selection) == 0) {
     DVQLOGF(3) << "VIDIOC_G_SELECTION is supported";
     return V4L2RectToGfxRect(selection.r);
   }
@@ -1071,7 +1112,7 @@
   struct v4l2_crop crop;
   memset(&crop, 0, sizeof(crop));
   crop.type = type_;
-  if (device_->Ioctl(VIDIOC_G_CROP, &crop) == 0) {
+  if (ioctl_cb_.Run(VIDIOC_G_CROP, &crop) == 0) {
     return V4L2RectToGfxRect(crop.c);
   }
 
@@ -1124,7 +1165,7 @@
   DVQLOGF(3) << "Requesting " << count << " buffers.";
   DVQLOGF(3) << "Incoherent flag is " << incoherent << ".";
 
-  int ret = device_->Ioctl(VIDIOC_REQBUFS, &reqbufs);
+  int ret = ioctl_cb_.Run(VIDIOC_REQBUFS, &reqbufs);
   if (ret) {
     VPQLOGF(1) << "VIDIOC_REQBUFS failed";
     return 0;
@@ -1137,7 +1178,8 @@
 
   // Now query all buffer information.
   for (size_t i = 0; i < reqbufs.count; i++) {
-    auto buffer = V4L2Buffer::Create(device_, type_, memory_, *format, i);
+    auto buffer =
+        V4L2Buffer::Create(ioctl_cb_, mmap_cb_, type_, memory_, *format, i);
 
     if (!buffer) {
       if (!DeallocateBuffers()) {
@@ -1183,7 +1225,7 @@
   reqbufs.memory = memory_;
   reqbufs.flags = incoherent_ ? V4L2_MEMORY_FLAG_NON_COHERENT : 0;
 
-  int ret = device_->Ioctl(VIDIOC_REQBUFS, &reqbufs);
+  int ret = ioctl_cb_.Run(VIDIOC_REQBUFS, &reqbufs);
   if (ret) {
     VPQLOGF(1) << "VIDIOC_REQBUFS failed";
     return false;
@@ -1205,6 +1247,7 @@
 }
 
 v4l2_memory V4L2Queue::GetMemoryType() const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   return memory_;
 }
 
@@ -1307,7 +1350,7 @@
 
   V4L2ProcessingTrace(v4l2_buffer, /*start=*/true);
 
-  int ret = device_->Ioctl(VIDIOC_QBUF, v4l2_buffer);
+  int ret = ioctl_cb_.Run(VIDIOC_QBUF, v4l2_buffer);
   if (ret) {
     VPQLOGF(1) << "VIDIOC_QBUF failed";
     return false;
@@ -1317,7 +1360,7 @@
       queued_buffers_.emplace(v4l2_buffer->index, std::move(video_frame));
   DCHECK(inserted.second);
 
-  device_->SchedulePoll();
+  schedule_poll_cb_.Run();
 
   return true;
 }
@@ -1346,7 +1389,7 @@
   v4l2_buffer.memory = memory_;
   v4l2_buffer.m.planes = planes;
   v4l2_buffer.length = planes_count_;
-  int ret = device_->Ioctl(VIDIOC_DQBUF, &v4l2_buffer);
+  int ret = ioctl_cb_.Run(VIDIOC_DQBUF, &v4l2_buffer);
   if (ret) {
     // TODO(acourbot): we should not have to check for EPIPE as codec clients
     // should not call this method after the last buffer is dequeued.
@@ -1355,7 +1398,7 @@
       case EPIPE:
         // This is not an error so we'll need to continue polling but won't
         // provide a buffer.
-        device_->SchedulePoll();
+        schedule_poll_cb_.Run();
         return std::make_pair(true, nullptr);
       default:
         VPQLOGF(1) << "VIDIOC_DQBUF failed";
@@ -1371,7 +1414,7 @@
   V4L2ProcessingTrace(&v4l2_buffer, /*start=*/false);
 
   if (QueuedBuffersCount() > 0) {
-    device_->SchedulePoll();
+    schedule_poll_cb_.Run();
   }
 
   DCHECK(free_buffers_);
@@ -1394,7 +1437,7 @@
   }
 
   int arg = static_cast<int>(type_);
-  int ret = device_->Ioctl(VIDIOC_STREAMON, &arg);
+  int ret = ioctl_cb_.Run(VIDIOC_STREAMON, &arg);
   if (ret) {
     VPQLOGF(1) << "VIDIOC_STREAMON failed";
     return false;
@@ -1413,7 +1456,7 @@
   // need to do a VIDIOC_STREAMOFF on a stopped queue.
 
   int arg = static_cast<int>(type_);
-  int ret = device_->Ioctl(VIDIOC_STREAMOFF, &arg);
+  int ret = ioctl_cb_.Run(VIDIOC_STREAMOFF, &arg);
   if (ret) {
     VPQLOGF(1) << "VIDIOC_STREAMOFF failed";
     return false;
@@ -1462,6 +1505,7 @@
 absl::optional<struct v4l2_format> V4L2Queue::SetModifierFormat(
     uint64_t modifier,
     const gfx::Size& size) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (DRM_FORMAT_MOD_QCOM_COMPRESSED == modifier) {
     auto format = SetFormat(V4L2_PIX_FMT_QC08C, size, 0);
 
diff --git a/media/gpu/v4l2/v4l2_queue.h b/media/gpu/v4l2/v4l2_queue.h
index 784575c..cb8b9d4 100644
--- a/media/gpu/v4l2/v4l2_queue.h
+++ b/media/gpu/v4l2/v4l2_queue.h
@@ -23,6 +23,7 @@
 #include "media/base/video_frame.h"
 #include "media/gpu/chromeos/fourcc.h"
 #include "media/gpu/media_gpu_export.h"
+#include "media/gpu/v4l2/v4l2_utils.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "ui/gfx/generic_shared_memory_id.h"
 #include "ui/gfx/geometry/size.h"
@@ -33,7 +34,6 @@
 
 namespace media {
 
-class V4L2Device;
 class V4L2Queue;
 class V4L2Buffer;
 class V4L2BufferRefBase;
@@ -359,7 +359,7 @@
   SEQUENCE_CHECKER(sequence_checker_);
 };
 
-// Interface representing a specific queue of a |V4L2Device|. It provides free
+// Interface representing a specific V4L2 queue. It provides free
 // and queued buffer management that is commonly required by clients.
 //
 // Buffers managed by this class undergo the following cycle:
@@ -558,11 +558,17 @@
   std::map<gfx::GenericSharedMemoryId, size_t> free_buffers_indexes_
       GUARDED_BY_CONTEXT(sequence_checker_);
 
-  scoped_refptr<V4L2Device> device_;
+  const IoctlAsCallback ioctl_cb_ GUARDED_BY_CONTEXT(sequence_checker_);
+  const base::RepeatingClosure schedule_poll_cb_
+      GUARDED_BY_CONTEXT(sequence_checker_);
+  const MmapAsCallback mmap_cb_ GUARDED_BY_CONTEXT(sequence_checker_);
+
   // Callback to call in this queue's destructor.
   base::OnceClosure destroy_cb_;
 
-  V4L2Queue(scoped_refptr<V4L2Device> dev,
+  V4L2Queue(const IoctlAsCallback& ioctl_cb,
+            const base::RepeatingClosure& schedule_poll_cb,
+            const MmapAsCallback& mmap_cb,
             enum v4l2_buf_type type,
             base::OnceClosure destroy_cb);
   friend class V4L2QueueFactory;
diff --git a/media/gpu/v4l2/v4l2_utils.h b/media/gpu/v4l2/v4l2_utils.h
index 2863420..f6b56e4 100644
--- a/media/gpu/v4l2/v4l2_utils.h
+++ b/media/gpu/v4l2/v4l2_utils.h
@@ -8,6 +8,7 @@
 #include <string>
 
 #include <linux/videodev2.h>
+#include <sys/mman.h>
 
 #include "base/functional/callback.h"
 #include "media/base/video_codecs.h"
@@ -19,6 +20,13 @@
 
 using IoctlAsCallback = base::RepeatingCallback<int(int, void*)>;
 
+// Ideally this should be a decltype(mmap) (void *mmap(void *addr, size_t
+// length, int prot, int flags, int fd, off_t offset)), but the types of e.g.
+// V4L2Device::Mmap are wrong.
+// TODO(b/279980150): correct types and argument order and use decltype.
+using MmapAsCallback =
+    base::RepeatingCallback<void*(void*, unsigned int, int, int, unsigned int)>;
+
 // Returns a human readable description of |memory|.
 const char* V4L2MemoryToString(v4l2_memory memory);
 
diff --git a/media/gpu/vaapi/test/fake_libva_driver/BUILD.gn b/media/gpu/vaapi/test/fake_libva_driver/BUILD.gn
index 60ece4c..5682fab4 100644
--- a/media/gpu/vaapi/test/fake_libva_driver/BUILD.gn
+++ b/media/gpu/vaapi/test/fake_libva_driver/BUILD.gn
@@ -17,6 +17,8 @@
 source_set("fake_libva_driver_internals") {
   visibility = [ ":fake_drv_video" ]
   sources = [
+    "fake_buffer.cc",
+    "fake_buffer.h",
     "fake_config.cc",
     "fake_config.h",
     "fake_context.cc",
diff --git a/media/gpu/vaapi/test/fake_libva_driver/fake_buffer.cc b/media/gpu/vaapi/test/fake_libva_driver/fake_buffer.cc
new file mode 100644
index 0000000..19f9e6c
--- /dev/null
+++ b/media/gpu/vaapi/test/fake_libva_driver/fake_buffer.cc
@@ -0,0 +1,61 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/numerics/checked_math.h"
+
+#include "media/gpu/vaapi/test/fake_libva_driver/fake_buffer.h"
+
+namespace {
+
+size_t CalculateDataSize(unsigned int size_per_element,
+                         unsigned int num_elements) {
+  base::CheckedNumeric<size_t> data_size(
+      base::strict_cast<size_t>(size_per_element));
+  data_size *= base::strict_cast<size_t>(num_elements);
+  return data_size.ValueOrDie();
+}
+
+}  // namespace
+
+namespace media::internal {
+
+FakeBuffer::FakeBuffer(IdType id,
+                       VAContextID context,
+                       VABufferType type,
+                       unsigned int size_per_element,
+                       unsigned int num_elements,
+                       const void* data)
+    : id_(id),
+      context_(context),
+      type_(type),
+      data_size_(CalculateDataSize(size_per_element, num_elements)),
+      data_(std::make_unique<uint8_t[]>(data_size_)) {
+  if (data) {
+    memcpy(data_.get(), data, data_size_);
+  }
+}
+
+FakeBuffer::~FakeBuffer() = default;
+
+FakeBuffer::IdType FakeBuffer::GetID() const {
+  return id_;
+}
+
+VAContextID FakeBuffer::GetContextID() const {
+  return context_;
+}
+
+VABufferType FakeBuffer::GetType() const {
+  return type_;
+}
+
+size_t FakeBuffer::GetDataSize() const {
+  return data_size_;
+}
+
+void* FakeBuffer::GetData() const {
+  return data_.get();
+}
+
+}  // namespace media::internal
diff --git a/media/gpu/vaapi/test/fake_libva_driver/fake_buffer.h b/media/gpu/vaapi/test/fake_libva_driver/fake_buffer.h
new file mode 100644
index 0000000..96fcc0cd
--- /dev/null
+++ b/media/gpu/vaapi/test/fake_libva_driver/fake_buffer.h
@@ -0,0 +1,52 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MEDIA_GPU_VAAPI_TEST_FAKE_LIBVA_DRIVER_FAKE_BUFFER_H_
+#define MEDIA_GPU_VAAPI_TEST_FAKE_LIBVA_DRIVER_FAKE_BUFFER_H_
+
+#include <memory>
+
+#include <va/va.h>
+
+namespace media::internal {
+
+// Class used for tracking a VABuffer and all information relevant to it. All //
+// objects of this class are immutable in the sense that none of the members //
+// change in value throughout the lifetime of the object. However, the //
+// underlying buffer data may be changed by users of a FakeBuffer by using the
+// // pointer returned by GetData(). Such changes must be synchronized
+// externally, // but calls to the FakeBuffer public methods themselves are
+// thread-safe. Users // of FakeBuffer must not free the memory pointed to by
+// the pointer that // GetData() returns.
+class FakeBuffer {
+ public:
+  using IdType = VABufferID;
+
+  FakeBuffer(IdType id,
+             VAContextID context,
+             VABufferType type,
+             unsigned int size_per_element,
+             unsigned int num_elements,
+             const void* data);
+  FakeBuffer(const FakeBuffer&) = delete;
+  FakeBuffer& operator=(const FakeBuffer&) = delete;
+  ~FakeBuffer();
+
+  IdType GetID() const;
+  VAContextID GetContextID() const;
+  VABufferType GetType() const;
+  size_t GetDataSize() const;
+  void* GetData() const;
+
+ private:
+  const IdType id_;
+  const VAContextID context_;
+  const VABufferType type_;
+  const size_t data_size_;
+  const std::unique_ptr<uint8_t[]> data_;
+};
+
+}  // namespace media::internal
+
+#endif  // MEDIA_GPU_VAAPI_TEST_FAKE_LIBVA_DRIVER_FAKE_BUFFER_H_
diff --git a/media/gpu/vaapi/test/fake_libva_driver/fake_driver.cc b/media/gpu/vaapi/test/fake_libva_driver/fake_driver.cc
index ec96364..1754a921 100644
--- a/media/gpu/vaapi/test/fake_libva_driver/fake_driver.cc
+++ b/media/gpu/vaapi/test/fake_libva_driver/fake_driver.cc
@@ -70,4 +70,25 @@
   context_.DestroyObject(id);
 }
 
+FakeBuffer::IdType FakeDriver::CreateBuffer(VAContextID context,
+                                            VABufferType type,
+                                            unsigned int size_per_element,
+                                            unsigned int num_elements,
+                                            const void* data) {
+  return buffers_.CreateObject(context, type, size_per_element, num_elements,
+                               data);
+}
+
+bool FakeDriver::BufferExists(FakeBuffer::IdType id) {
+  return buffers_.ObjectExists(id);
+}
+
+const FakeBuffer& FakeDriver::GetBuffer(FakeBuffer::IdType id) {
+  return buffers_.GetObject(id);
+}
+
+void FakeDriver::DestroyBuffer(FakeBuffer::IdType id) {
+  buffers_.DestroyObject(id);
+}
+
 }  // namespace media::internal
diff --git a/media/gpu/vaapi/test/fake_libva_driver/fake_driver.h b/media/gpu/vaapi/test/fake_libva_driver/fake_driver.h
index d16929d..618b051 100644
--- a/media/gpu/vaapi/test/fake_libva_driver/fake_driver.h
+++ b/media/gpu/vaapi/test/fake_libva_driver/fake_driver.h
@@ -7,6 +7,7 @@
 
 #include <va/va.h>
 
+#include "media/gpu/vaapi/test/fake_libva_driver/fake_buffer.h"
 #include "media/gpu/vaapi/test/fake_libva_driver/fake_config.h"
 #include "media/gpu/vaapi/test/fake_libva_driver/fake_context.h"
 #include "media/gpu/vaapi/test/fake_libva_driver/fake_surface.h"
@@ -48,10 +49,20 @@
   const FakeContext& GetContext(FakeContext::IdType id);
   void DestroyContext(FakeContext::IdType id);
 
+  FakeBuffer::IdType CreateBuffer(VAContextID context,
+                                  VABufferType type,
+                                  unsigned int size_per_element,
+                                  unsigned int num_elements,
+                                  const void* data);
+  bool BufferExists(FakeBuffer::IdType id);
+  const FakeBuffer& GetBuffer(FakeBuffer::IdType id);
+  void DestroyBuffer(FakeBuffer::IdType id);
+
  private:
   ObjectTracker<FakeConfig> config_;
   ObjectTracker<FakeSurface> surface_;
   ObjectTracker<FakeContext> context_;
+  ObjectTracker<FakeBuffer> buffers_;
 };
 
 }  // namespace media::internal
diff --git a/media/gpu/vaapi/test/fake_libva_driver/fake_drv_video.cc b/media/gpu/vaapi/test/fake_libva_driver/fake_drv_video.cc
index 1ac77df..d9df5509 100644
--- a/media/gpu/vaapi/test/fake_libva_driver/fake_drv_video.cc
+++ b/media/gpu/vaapi/test/fake_libva_driver/fake_drv_video.cc
@@ -415,6 +415,9 @@
 
   CHECK(fdrv->ContextExists(context));
 
+  *buf_id = fdrv->CreateBuffer(context, type, /*size_per_element=*/size,
+                               num_elements, data);
+
   return VA_STATUS_SUCCESS;
 }
 
@@ -425,6 +428,9 @@
 }
 
 VAStatus FakeMapBuffer(VADriverContextP ctx, VABufferID buf_id, void** pbuf) {
+  media::internal::FakeDriver* fdrv =
+      static_cast<media::internal::FakeDriver*>(ctx->pDriverData);
+  *pbuf = fdrv->GetBuffer(buf_id).GetData();
   return VA_STATUS_SUCCESS;
 }
 
@@ -433,6 +439,11 @@
 }
 
 VAStatus FakeDestroyBuffer(VADriverContextP ctx, VABufferID buffer_id) {
+  media::internal::FakeDriver* fdrv =
+      static_cast<media::internal::FakeDriver*>(ctx->pDriverData);
+
+  fdrv->DestroyBuffer(buffer_id);
+
   return VA_STATUS_SUCCESS;
 }
 
diff --git a/net/cert/pki/cert_issuer_source_static_unittest.cc b/net/cert/pki/cert_issuer_source_static_unittest.cc
index c46acb4..04ed754 100644
--- a/net/cert/pki/cert_issuer_source_static_unittest.cc
+++ b/net/cert/pki/cert_issuer_source_static_unittest.cc
@@ -32,6 +32,9 @@
                                CertIssuerSourceSyncNormalizationTest,
                                CertIssuerSourceStaticTestDelegate);
 
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(
+    CertIssuerSourceSyncNotNormalizedTest);
+
 }  // namespace
 
 }  // namespace net
diff --git a/net/cert/pki/path_builder.cc b/net/cert/pki/path_builder.cc
index 9c04a7a..c373c8d 100644
--- a/net/cert/pki/path_builder.cc
+++ b/net/cert/pki/path_builder.cc
@@ -4,6 +4,7 @@
 
 #include "net/cert/pki/path_builder.h"
 
+#include <cassert>
 #include <memory>
 #include <set>
 #include <unordered_set>
@@ -154,6 +155,8 @@
       }
       break;
   }
+  assert(0);  // NOTREACHED
+  return -1;
 }
 
 // CertIssuersIter iterates through the intermediates from |cert_issuer_sources|
diff --git a/net/cert/pki/simple_path_builder_delegate.cc b/net/cert/pki/simple_path_builder_delegate.cc
index 8258f7d..83cd265 100644
--- a/net/cert/pki/simple_path_builder_delegate.cc
+++ b/net/cert/pki/simple_path_builder_delegate.cc
@@ -80,6 +80,7 @@
     case SignatureAlgorithm::kRsaPssSha512:
       return true;
   }
+  return false;
 }
 
 bool SimplePathBuilderDelegate::IsPublicKeyAcceptable(EVP_PKEY* public_key,
diff --git a/net/cert/pki/test_helpers.cc b/net/cert/pki/test_helpers.cc
index ac18beb9b..fc73344 100644
--- a/net/cert/pki/test_helpers.cc
+++ b/net/cert/pki/test_helpers.cc
@@ -4,6 +4,9 @@
 
 #include "net/cert/pki/test_helpers.h"
 
+#include <sstream>
+#include <string_view>
+
 #include "base/base_paths.h"
 #include "base/files/file_util.h"
 #include "base/path_service.h"
@@ -19,8 +22,6 @@
 #include "third_party/boringssl/src/include/openssl/mem.h"
 #include "third_party/boringssl/src/include/openssl/pool.h"
 
-#include <sstream>
-
 namespace net {
 
 namespace {
diff --git a/net/cert/pki/verify_certificate_chain.cc b/net/cert/pki/verify_certificate_chain.cc
index 72ffca0..432beea3 100644
--- a/net/cert/pki/verify_certificate_chain.cc
+++ b/net/cert/pki/verify_certificate_chain.cc
@@ -1,4 +1,4 @@
-/// Copyright 2015 The Chromium Authors
+// Copyright 2015 The Chromium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
diff --git a/net/cookies/cookie_util.cc b/net/cookies/cookie_util.cc
index 6de53b2b5..f4859b3 100644
--- a/net/cookies/cookie_util.cc
+++ b/net/cookies/cookie_util.cc
@@ -355,8 +355,9 @@
         base::EqualsCaseInsensitiveASCII("." + url_host, domain_string)))) {
     *result = url_host;
     // TODO(crbug.com/1453416): Once empty label support is implemented we can
-    // CHECK our assumptions here. For now, just dump to gather usage data.
-    DUMP_WILL_BE_CHECK(DomainIsHostOnly(*result));
+    // CHECK our assumptions here. For now, we DCHECK as DUMP_WILL_BE_CHECK is
+    // generating too many crash reports and already know why this is failing.
+    DCHECK(DomainIsHostOnly(*result));
     return true;
   }
 
diff --git a/net/http/transport_security_state_static.pins b/net/http/transport_security_state_static.pins
index 042ef14..b786ca4 100644
--- a/net/http/transport_security_state_static.pins
+++ b/net/http/transport_security_state_static.pins
@@ -43,9 +43,9 @@
 #   hash function for preloaded entries again (we have already done so once).
 #
 
-# Last updated: 2023-06-12 12:55 UTC
+# Last updated: 2023-06-13 12:54 UTC
 PinsListTimestamp
-1686574539
+1686660864
 
 TestSPKI
 sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
diff --git a/net/http/transport_security_state_static_pins.json b/net/http/transport_security_state_static_pins.json
index bb80852..288e4d2 100644
--- a/net/http/transport_security_state_static_pins.json
+++ b/net/http/transport_security_state_static_pins.json
@@ -31,7 +31,7 @@
 // the 'static_spki_hashes' and 'bad_static_spki_hashes' fields in 'pinsets'
 // refer to, and the timestamp at which the pins list was last updated.
 //
-// Last updated: 2023-06-12 12:55 UTC
+// Last updated: 2023-06-13 12:54 UTC
 //
 {
   "pinsets": [
diff --git a/remoting/protocol/webrtc_data_stream_adapter.cc b/remoting/protocol/webrtc_data_stream_adapter.cc
index 61d04874..d8d33a0 100644
--- a/remoting/protocol/webrtc_data_stream_adapter.cc
+++ b/remoting/protocol/webrtc_data_stream_adapter.cc
@@ -19,7 +19,7 @@
 
 namespace remoting::protocol {
 
-// On ChromeOS `channel_` is actually a sctp data channel which ends up
+// On ChromeOS `channel_` is actually an sctp data channel which ends up
 // posting all accessors to a different task, and wait for the response
 // (See `MethodCall::Marshal` inside third_party/webrtc/pc/proxy.h).
 class ScopedAllowSyncPrimitivesForWebRtcDataStreamAdapter
@@ -38,7 +38,9 @@
     channel_->Close();
 
     // Destroy |channel_| asynchronously as it may be on stack.
-    // TODO(dcheng): This could probably be ReleaseSoon.
+    // TODO(dcheng): This could probably be ReleaseSoon() however that method
+    // expects a scoped_refptr from //base whereas |channel_| is an
+    // rtc::scoped_refptr.
     base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
         FROM_HERE,
         base::BindOnce([](rtc::scoped_refptr<webrtc::DataChannelInterface>) {},
@@ -51,6 +53,11 @@
   DCHECK(event_handler);
 
   event_handler_ = event_handler;
+
+  if (pending_open_callback_) {
+    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
+        FROM_HERE, std::move(pending_open_callback_));
+  }
 }
 
 void WebrtcDataStreamAdapter::Send(google::protobuf::MessageLite* message,
@@ -99,14 +106,23 @@
     case webrtc::DataChannelInterface::kOpen:
       DCHECK(state_ == State::CONNECTING);
       state_ = State::OPEN;
-      base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
-          FROM_HERE, base::BindOnce(&WebrtcDataStreamAdapter::InvokeOpenEvent,
-                                    weak_ptr_factory_.GetWeakPtr()));
+      pending_open_callback_ =
+          base::BindOnce(&WebrtcDataStreamAdapter::InvokeOpenEvent,
+                         weak_ptr_factory_.GetWeakPtr());
+      // There appears to be a race condition between when Start() is called and
+      // the InvokeOpenEvent() callback is run so we post the InvokeOpenEvent()
+      // callback if an event_handler_ has been registered, otherwise we'll wait
+      // until Start() has been called. See crbug.com/1454494 for more info.
+      if (event_handler_) {
+        base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
+            FROM_HERE, std::move(pending_open_callback_));
+      }
       break;
 
     case webrtc::DataChannelInterface::kClosing:
       if (state_ != State::CLOSED) {
         state_ = State::CLOSED;
+        pending_open_callback_.Reset();
         base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
             FROM_HERE,
             base::BindOnce(&WebrtcDataStreamAdapter::InvokeClosedEvent,
diff --git a/remoting/protocol/webrtc_data_stream_adapter.h b/remoting/protocol/webrtc_data_stream_adapter.h
index 685a2ae..ae0ed03 100644
--- a/remoting/protocol/webrtc_data_stream_adapter.h
+++ b/remoting/protocol/webrtc_data_stream_adapter.h
@@ -77,6 +77,8 @@
   // The data and done callbacks for queued but not yet sent messages.
   base::queue<PendingMessage> pending_messages_;
 
+  base::OnceClosure pending_open_callback_;
+
   base::WeakPtrFactory<WebrtcDataStreamAdapter> weak_ptr_factory_{this};
 };
 
diff --git a/services/accessibility/fake_service_client.cc b/services/accessibility/fake_service_client.cc
index 0b940c81..e4a40c0 100644
--- a/services/accessibility/fake_service_client.cc
+++ b/services/accessibility/fake_service_client.cc
@@ -30,13 +30,14 @@
 }
 
 void FakeServiceClient::Speak(const std::string& utterance,
+                              ax::mojom::TtsOptionsPtr options,
                               SpeakCallback callback) {
   auto result = mojom::TtsSpeakResult::New();
   result->error = mojom::TtsError::kNoError;
   result->utterance_client = tts_utterance_client_.BindNewPipeAndPassReceiver();
   std::move(callback).Run(std::move(result));
   if (tts_speak_callback_) {
-    tts_speak_callback_.Run(utterance);
+    tts_speak_callback_.Run(utterance, std::move(options));
   }
 }
 
@@ -126,7 +127,8 @@
 }
 
 void FakeServiceClient::SetTtsSpeakCallback(
-    base::RepeatingCallback<void(const std::string&)> callback) {
+    base::RepeatingCallback<void(const std::string&, mojom::TtsOptionsPtr)>
+        callback) {
   tts_speak_callback_ = std::move(callback);
 }
 
diff --git a/services/accessibility/fake_service_client.h b/services/accessibility/fake_service_client.h
index b7a2337..d6ccb78 100644
--- a/services/accessibility/fake_service_client.h
+++ b/services/accessibility/fake_service_client.h
@@ -44,7 +44,9 @@
   void BindTts(mojo::PendingReceiver<ax::mojom::Tts> tts_receiver) override;
 
   // ax::mojom::Tts:
-  void Speak(const std::string& utterance, SpeakCallback callback) override;
+  void Speak(const std::string& utterance,
+             ax::mojom::TtsOptionsPtr options,
+             SpeakCallback callback) override;
   void Stop() override;
   void Pause() override;
   void Resume() override;
@@ -62,7 +64,8 @@
   void SetTtsBoundClosure(base::OnceClosure closure);
   bool TtsIsBound() const;
   void SetTtsSpeakCallback(
-      base::RepeatingCallback<void(const std::string&)> callback);
+      base::RepeatingCallback<void(const std::string&, mojom::TtsOptionsPtr)>
+          callback);
   void SendTtsUtteranceEvent(mojom::TtsEventPtr tts_event);
 #endif  // BUILDFLAG(SUPPORTS_OS_ACCESSIBILITY_SERVICE)
   base::WeakPtr<FakeServiceClient> GetWeakPtr() {
@@ -73,11 +76,12 @@
   raw_ptr<mojom::AccessibilityService> service_;
   base::OnceClosure automation_bound_closure_;
   base::OnceClosure tts_bound_closure_;
-  base::RepeatingCallback<void(const std::string&)> tts_speak_callback_;
 
   mojo::RemoteSet<mojom::Automation> automation_remotes_;
   mojo::ReceiverSet<mojom::AutomationClient> automation_client_receivers_;
 #if BUILDFLAG(SUPPORTS_OS_ACCESSIBILITY_SERVICE)
+  base::RepeatingCallback<void(const std::string&, mojom::TtsOptionsPtr)>
+      tts_speak_callback_;
   mojo::ReceiverSet<mojom::Tts> tts_receivers_;
   mojo::Remote<ax::mojom::TtsUtteranceClient> tts_utterance_client_;
 #endif  // BUILDFLAG(SUPPORTS_OS_ACCESSIBILITY_SERVICE)
diff --git a/services/accessibility/features/atp_js_api_test.cc b/services/accessibility/features/atp_js_api_test.cc
index 43b2e9f..b903954 100644
--- a/services/accessibility/features/atp_js_api_test.cc
+++ b/services/accessibility/features/atp_js_api_test.cc
@@ -16,6 +16,7 @@
 #include "services/accessibility/os_accessibility_service.h"
 #include "services/accessibility/public/mojom/accessibility_service.mojom-shared.h"
 #include "services/accessibility/public/mojom/accessibility_service.mojom.h"
+#include "services/accessibility/public/mojom/tts.mojom-forward.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace ax {
@@ -176,8 +177,8 @@
 // is consistent: if start is sent before end in C++, it should
 // be received before end in JS.
 TEST_F(TtsJSApiTest, TtsSpeakWithStartAndEndEvents) {
-  client_->SetTtsSpeakCallback(
-      base::BindLambdaForTesting([this](const std::string& text) {
+  client_->SetTtsSpeakCallback(base::BindLambdaForTesting(
+      [this](const std::string& text, mojom::TtsOptionsPtr options) {
         EXPECT_EQ(text, "Hello, world");
         auto start_event = ax::mojom::TtsEvent::New();
         start_event->type = mojom::TtsEventType::kStart;
@@ -205,8 +206,8 @@
 
 TEST_F(TtsJSApiTest, TtsSpeaksNumbers) {
   base::RunLoop waiter;
-  client_->SetTtsSpeakCallback(
-      base::BindLambdaForTesting([&waiter](const std::string& text) {
+  client_->SetTtsSpeakCallback(base::BindLambdaForTesting(
+      [&waiter](const std::string& text, mojom::TtsOptionsPtr options) {
         EXPECT_EQ(text, "42");
         waiter.Quit();
       }));
@@ -218,8 +219,8 @@
 }
 
 TEST_F(TtsJSApiTest, TtsSpeakPauseResumeStopEvents) {
-  client_->SetTtsSpeakCallback(
-      base::BindLambdaForTesting([this](const std::string& text) {
+  client_->SetTtsSpeakCallback(base::BindLambdaForTesting(
+      [this](const std::string& text, mojom::TtsOptionsPtr options) {
         EXPECT_EQ(text, "Green is the loneliest color");
         auto start_event = ax::mojom::TtsEvent::New();
         start_event->type = mojom::TtsEventType::kStart;
@@ -261,8 +262,8 @@
 
 // Test that parameters can be sent in an event.
 TEST_F(TtsJSApiTest, TtsEventPassesParams) {
-  client_->SetTtsSpeakCallback(
-      base::BindLambdaForTesting([this](const std::string& text) {
+  client_->SetTtsSpeakCallback(base::BindLambdaForTesting(
+      [this](const std::string& text, mojom::TtsOptionsPtr options) {
         EXPECT_EQ(text, "Hello, world");
         auto start_event = ax::mojom::TtsEvent::New();
         start_event->type = mojom::TtsEventType::kStart;
@@ -287,8 +288,8 @@
 }
 
 TEST_F(TtsJSApiTest, TtsIsSpeaking) {
-  client_->SetTtsSpeakCallback(
-      base::BindLambdaForTesting([this](const std::string& text) {
+  client_->SetTtsSpeakCallback(base::BindLambdaForTesting(
+      [this](const std::string& text, mojom::TtsOptionsPtr options) {
         EXPECT_EQ(text, "Pie in the sky");
         auto start_event = ax::mojom::TtsEvent::New();
         start_event->type = mojom::TtsEventType::kStart;
@@ -315,8 +316,8 @@
 }
 
 TEST_F(TtsJSApiTest, TtsUtteranceError) {
-  client_->SetTtsSpeakCallback(
-      base::BindLambdaForTesting([this](const std::string& text) {
+  client_->SetTtsSpeakCallback(base::BindLambdaForTesting(
+      [this](const std::string& text, mojom::TtsOptionsPtr options) {
         EXPECT_EQ(text, "No man can kill me");
         auto error_event = ax::mojom::TtsEvent::New();
         error_event->type = mojom::TtsEventType::kError;
@@ -338,4 +339,61 @@
   WaitForJSTestComplete();
 }
 
+TEST_F(TtsJSApiTest, DefaultTtsOptions) {
+  base::RunLoop waiter;
+  client_->SetTtsSpeakCallback(base::BindLambdaForTesting(
+      [&waiter](const std::string& text, mojom::TtsOptionsPtr options) {
+        waiter.Quit();
+        EXPECT_EQ(options->pitch, 1.0);
+        EXPECT_EQ(options->rate, 1.0);
+        EXPECT_EQ(options->volume, 1.0);
+        EXPECT_FALSE(options->enqueue);
+        EXPECT_FALSE(options->voice_name);
+        EXPECT_FALSE(options->engine_id);
+        EXPECT_FALSE(options->lang);
+        EXPECT_FALSE(options->on_event);
+      }));
+  ExecuteJS(R"JS(
+    const remote = axtest.mojom.TestBindingInterface.getRemote();
+    chrome.tts.speak('You have my ax');
+  )JS");
+
+  waiter.Run();
+}
+
+TEST_F(TtsJSApiTest, TtsOptions) {
+  base::RunLoop waiter;
+  client_->SetTtsSpeakCallback(base::BindLambdaForTesting(
+      [&waiter](const std::string& text, mojom::TtsOptionsPtr options) {
+        waiter.Quit();
+        EXPECT_EQ(options->pitch, 0.5);
+        EXPECT_EQ(options->rate, 1.5);
+        EXPECT_EQ(options->volume, 2.5);
+        EXPECT_TRUE(options->enqueue);
+        ASSERT_TRUE(options->voice_name);
+        EXPECT_EQ(options->voice_name.value(), "Gimli");
+        ASSERT_TRUE(options->engine_id);
+        EXPECT_EQ(options->engine_id.value(), "us_dwarf");
+        ASSERT_TRUE(options->lang);
+        EXPECT_EQ(options->lang.value(), "en-NZ");
+        EXPECT_TRUE(options->on_event);
+      }));
+  ExecuteJS(R"JS(
+    const remote = axtest.mojom.TestBindingInterface.getRemote();
+    const options = {
+      pitch: .5,
+      rate: 1.5,
+      volume: 2.5,
+      enqueue: true,
+      engineId: 'us_dwarf',
+      lang: 'en-NZ',
+      voiceName: 'Gimli',
+      onEvent: (ttsEvent) => {},
+    };
+    chrome.tts.speak('You have my ax', options);
+  )JS");
+
+  waiter.Run();
+}
+
 }  // namespace ax
diff --git a/services/accessibility/features/javascript/tts.js b/services/accessibility/features/javascript/tts.js
index 0cea868..9b46b2f4 100644
--- a/services/accessibility/features/javascript/tts.js
+++ b/services/accessibility/features/javascript/tts.js
@@ -43,38 +43,44 @@
    * @param {chrome.tts.TtsOptions} ttsOptions
    */
   speak(utterance, ttsOptions) {
-    // TODO(b:277221897): Parse and pass in ttsOptions.
-    this.remote_.speak(utterance).then(speakResult => {
-      if (speakResult.result.error != ax.mojom.TtsError.kNoError) {
-        console.error(
-            'Error when trying to speak', utterance,
-            speakResult.result.error);
-        return;
-      }
-      if (!speakResult.result.utteranceClient) {
-        console.error(
-            'UtteranceClient was unexpectedly missing from TtsSpeakResult.');
-        return;
-      }
-      const ttsEventObserver = new TtsEventObserver(
-          speakResult.result.utteranceClient, (ttsEvent) => {
-            if (ttsOptions.onEvent) {
-              let type = AtpTts.eventTypeToString_(ttsEvent.type);
-              ttsOptions.onEvent({
-                type,
-                charIndex: ttsEvent.charIndex,
-                length: ttsEvent.length,
-                errorMessage: ttsEvent.errorMessage
+    this.remote_.speak(utterance, AtpTts.createMojomOptions_(ttsOptions))
+        .then(speakResult => {
+          if (speakResult.result.error != ax.mojom.TtsError.kNoError) {
+            console.error(
+                'Error when trying to speak', utterance,
+                speakResult.result.error);
+            return;
+          }
+          if (!ttsOptions.onEvent) {
+            // No utterance client will be returned, no need to make an
+            // event observer.
+            return;
+          }
+          if (!speakResult.result.utteranceClient) {
+            console.error(
+                'UtteranceClient was unexpectedly ' +
+                'missing from TtsSpeakResult.');
+            return;
+          }
+          const ttsEventObserver = new TtsEventObserver(
+              speakResult.result.utteranceClient, (ttsEvent) => {
+                if (ttsOptions.onEvent) {
+                  let type = AtpTts.eventTypeToString_(ttsEvent.type);
+                  ttsOptions.onEvent({
+                    type,
+                    charIndex: ttsEvent.charIndex,
+                    length: ttsEvent.length,
+                    errorMessage: ttsEvent.errorMessage
+                  });
+                }
+                if (ttsEvent.isFinal) {
+                  // There will be no more events. Delete this observer from
+                  // observers.
+                  this.ttsEventObservers_.delete(ttsEventObserver);
+                }
               });
-            }
-            if (ttsEvent.isFinal) {
-              // There will be no more events. Delete this observer from
-              // observers.
-              this.ttsEventObservers_.delete(ttsEventObserver);
-            }
-          });
-      this.ttsEventObservers_.add(ttsEventObserver);
-    });
+          this.ttsEventObservers_.add(ttsEventObserver);
+        });
   }
 
   /**
@@ -164,6 +170,46 @@
         return 'resume';
     }
   }
+
+  /**
+   * Constructs a Mojom options object from a TtsOptions.
+   * @param {?chrome.tts.TtsOptions} ttsOptions
+   * @return {ax.mojom.TtsOptions}
+   * @private
+   */
+  static createMojomOptions_(ttsOptions) {
+    let options = new ax.mojom.TtsOptions();
+    if (ttsOptions === undefined) {
+      // Use default options.
+      return options;
+    }
+
+    if (ttsOptions.rate !== undefined) {
+      options.rate = ttsOptions.rate;
+    }
+    if (ttsOptions.pitch !== undefined) {
+      options.pitch = ttsOptions.pitch;
+    }
+    if (ttsOptions.volume !== undefined) {
+      options.volume = ttsOptions.volume;
+    }
+    if (ttsOptions.enqueue !== undefined) {
+      options.enqueue = ttsOptions.enqueue;
+    }
+    if (ttsOptions.voiceName !== undefined) {
+      options.voiceName = ttsOptions.voiceName;
+    }
+    if (ttsOptions.engineId !== undefined) {
+      options.engineId = ttsOptions.engineId;
+    }
+    if (ttsOptions.lang !== undefined) {
+      options.lang = ttsOptions.lang;
+    }
+    if (ttsOptions.onEvent !== undefined) {
+      options.onEvent = ttsOptions.onEvent !== undefined;
+    }
+    return options;
+  }
 }
 
 // Shim the TTS api onto the Chrome object to mimic chrome.tts in extensions.
diff --git a/services/accessibility/public/mojom/tts.mojom b/services/accessibility/public/mojom/tts.mojom
index 6e9a805e..13492ff 100644
--- a/services/accessibility/public/mojom/tts.mojom
+++ b/services/accessibility/public/mojom/tts.mojom
@@ -65,6 +65,46 @@
   array<TtsEventType>? event_types;
 };
 
+// The speech options for the TTS engine.
+struct TtsOptions {
+  // Speaking pitch between 0 and 2 inclusive, with 0 being lowest and 2
+  // being highest. 1.0 corresponds to a voice's default pitch.
+  double pitch = 1.0;
+
+  // Speaking rate relative to the default rate for this voice. 1.0 is the
+  // default rate, normally around 180 to 220 words per minute. 2.0 is
+  // twice as fast, and 0.5 is half as fast. Values below 0.1 or above
+  // 10.0 are strictly disallowed, but many voices will constrain the
+  // minimum and maximum rates further&mdash;for example a particular
+  // voice may not actually speak faster than 3 times normal even if you
+  // specify a value larger than 3.0.
+  double rate = 1.0;
+
+  // Speaking volume between 0 and 1 inclusive, with 0 being lowest and 1
+  // being highest, with a default of 1.0.
+  double volume = 1.0;
+
+  // If true, enqueues this utterance if TTS is already in progress. If
+  // false (the default), interrupts any current speech and flushes the
+  // speech queue before speaking this new utterance.
+  bool enqueue = false;
+
+  // The name of the voice to use for synthesis. If empty, uses any
+  // available voice.
+  string? voice_name;
+
+  // The engine ID of the speech engine to use, if known.
+  string? engine_id;
+
+  // The language to be used for synthesis, in the form
+  // <em>language</em>-<em>region</em>. Examples: 'en', 'en-US', 'en-GB',
+  // 'zh-CN'.
+  string? lang;
+
+  // If true, construct and return a TtsUtteranceClient.
+  bool on_event = false;
+};
+
 // An event from the TTS engine to communicate the status of an utterance.
 // This is constructed from the OS browser process and passed to the
 // Accessibility Service's TtsUtteranceClient.
@@ -113,8 +153,7 @@
   // Speaks text using a text-to-speech engine, and returns async right
   // away, before speech finishes, with a speak result that contains an
   // utterance client or an error.
-  // TODO(b:277221897): Pass in Text to Speech options.
-  Speak(string utterance) => (TtsSpeakResult result);
+  Speak(string utterance, TtsOptions options) => (TtsSpeakResult result);
 
   // Stops any current speech and flushes the queue of any pending utterances.
   // In addition, if speech was paused, it will now be un-paused for the next
diff --git a/services/cert_verifier/public/mojom/trial_comparison_cert_verifier.mojom b/services/cert_verifier/public/mojom/trial_comparison_cert_verifier.mojom
index fae13ba..b09aba5 100644
--- a/services/cert_verifier/public/mojom/trial_comparison_cert_verifier.mojom
+++ b/services/cert_verifier/public/mojom/trial_comparison_cert_verifier.mojom
@@ -38,6 +38,13 @@
   int64 chrome_root_store_version;
 };
 
+struct AiaFetchDebugInfo {
+  // Count of Authority Information Access(AIA) fetches that failed.
+  int32 aia_fetch_failure;
+  // Count of AIA fetches that succeeded.
+  int32 aia_fetch_success;
+};
+
 // Contains additional debugging data about the verification. This information
 // does not change the meaning of the results.
 struct CertVerifierDebugInfo {
@@ -76,6 +83,10 @@
   // exploded & encoded GeneralizedTime string.
   mojo_base.mojom.Time trial_verification_time;
   string trial_der_verification_time;
+
+  // Any Authority Information Access(AIA) fetch debug information.
+  AiaFetchDebugInfo? primary_aia_fetch_debug_info;
+  AiaFetchDebugInfo? trial_aia_fetch_debug_info;
 };
 
 // Sends reports of differences found in the cert verifier trial.
diff --git a/services/cert_verifier/trial_comparison_cert_verifier_mojo.cc b/services/cert_verifier/trial_comparison_cert_verifier_mojo.cc
index 69b3b320..eda0415 100644
--- a/services/cert_verifier/trial_comparison_cert_verifier_mojo.cc
+++ b/services/cert_verifier/trial_comparison_cert_verifier_mojo.cc
@@ -11,6 +11,7 @@
 #include "net/cert/cert_verify_proc_builtin.h"
 #include "net/cert/cert_verify_result.h"
 #include "net/cert/crl_set.h"
+#include "net/cert/internal/cert_issuer_source_aia.h"
 #include "net/cert/trial_comparison_cert_verifier.h"
 #include "net/der/encode_values.h"
 #include "net/der/parse_values.h"
@@ -68,6 +69,15 @@
 }
 #endif
 
+cert_verifier::mojom::AiaFetchDebugInfoPtr FillAiaFetchDebugInfo(
+    const net::CertIssuerSourceAia::AiaDebugData& debug_data) {
+  cert_verifier::mojom::AiaFetchDebugInfoPtr debug_info =
+      cert_verifier::mojom::AiaFetchDebugInfo::New();
+  debug_info->aia_fetch_failure = debug_data.aia_fetch_fail();
+  debug_info->aia_fetch_success = debug_data.aia_fetch_success();
+  return debug_info;
+}
+
 }  // namespace
 
 namespace cert_verifier {
@@ -202,6 +212,18 @@
 #endif
   }
 
+  if (const net::CertIssuerSourceAia::AiaDebugData*
+          primary_aia_fetch_debug_data =
+              net::CertIssuerSourceAia::AiaDebugData::Get(&primary_result)) {
+    debug_info->primary_aia_fetch_debug_info =
+        FillAiaFetchDebugInfo(*primary_aia_fetch_debug_data);
+  }
+  if (auto* trial_aia_fetch_debug_data =
+          net::CertIssuerSourceAia::AiaDebugData::Get(&trial_result)) {
+    debug_info->trial_aia_fetch_debug_info =
+        FillAiaFetchDebugInfo(*trial_aia_fetch_debug_data);
+  }
+
   report_client_->SendTrialReport(
       hostname, unverified_cert, enable_rev_checking,
       require_rev_checking_local_anchors, enable_sha1_local_anchors,
diff --git a/services/data_decoder/BUILD.gn b/services/data_decoder/BUILD.gn
index 743d2fd2..222a84a 100644
--- a/services/data_decoder/BUILD.gn
+++ b/services/data_decoder/BUILD.gn
@@ -18,6 +18,8 @@
   ]
 
   sources = [
+    "cbor_parser_impl.cc",
+    "cbor_parser_impl.h",
     "data_decoder_service.cc",
     "data_decoder_service.h",
     "gzipper.cc",
@@ -42,6 +44,7 @@
   deps = [
     "//base",
     "//build:chromeos_buildflags",
+    "//components/cbor",
     "//components/web_package",
     "//mojo/public/cpp/bindings",
     "//net",
@@ -69,6 +72,7 @@
   testonly = true
 
   sources = [
+    "cbor_parser_unittest.cc",
     "gzipper_unittest.cc",
     "public/cpp/data_decoder_unittest.cc",
     "public/cpp/json_sanitizer_unittest.cc",
@@ -85,6 +89,7 @@
     ":lib",
     "//base",
     "//base/test:test_support",
+    "//components/cbor",
     "//services/data_decoder/public/cpp",
     "//services/data_decoder/public/cpp:test_support",
     "//skia",
diff --git a/services/data_decoder/DEPS b/services/data_decoder/DEPS
index 32b0d0e..ec7c00f 100644
--- a/services/data_decoder/DEPS
+++ b/services/data_decoder/DEPS
@@ -1,4 +1,5 @@
 include_rules = [
+  "+components/cbor",
   "+components/web_package",
   "+device/bluetooth/public",
   "+gin",
diff --git a/services/data_decoder/cbor_parser_impl.cc b/services/data_decoder/cbor_parser_impl.cc
new file mode 100644
index 0000000..c305913
--- /dev/null
+++ b/services/data_decoder/cbor_parser_impl.cc
@@ -0,0 +1,123 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "services/data_decoder/cbor_parser_impl.h"
+
+#include <algorithm>
+#include <cstddef>
+#include <cstdint>
+#include <cstdio>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/value_iterators.h"
+#include "base/values.h"
+#include "components/cbor/reader.h"
+#include "components/cbor/values.h"
+#include "mojo/public/cpp/base/big_buffer.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace data_decoder {
+
+namespace {
+
+absl::optional<base::Value> ConvertToBaseValue(const cbor::Value& cbor_value) {
+  switch (cbor_value.type()) {
+    case cbor::Value::Type::UNSIGNED:
+    case cbor::Value::Type::NEGATIVE:
+      return base::Value(static_cast<int>(cbor_value.GetInteger()));
+
+    case cbor::Value::Type::ARRAY: {
+      const std::vector<cbor::Value>& input_array = cbor_value.GetArray();
+      base::Value::List output_array;
+
+      for (const auto& el : input_array) {
+        absl::optional<base::Value> converted_el = ConvertToBaseValue(el);
+        if (!converted_el.has_value()) {
+          return absl::nullopt;
+        }
+        output_array.Append(*std::move(converted_el));
+      }
+      return base::Value(std::move(output_array));
+    }
+
+    case cbor::Value::Type::MAP: {
+      const cbor::Value::MapValue& input_map = cbor_value.GetMap();
+      base::Value::Dict output_map;
+
+      for (const auto& el : input_map) {
+        std::string key;
+        if (el.first.is_string()) {
+          key = el.first.GetString();
+        } else if (el.first.is_bytestring()) {
+          key = el.first.GetBytestringAsString();
+        } else {
+          // not supporting anything that is not a string or a bytestring at the
+          // moment.
+          return absl::nullopt;
+        }
+
+        absl::optional<base::Value> converted_value =
+            ConvertToBaseValue(el.second);
+
+        if (!converted_value.has_value()) {
+          return absl::nullopt;
+        }
+        output_map.Set(key, *std::move(converted_value));
+      }
+
+      return base::Value(std::move(output_map));
+    }
+    case cbor::Value::Type::SIMPLE_VALUE:
+      switch (cbor_value.GetSimpleValue()) {
+        case cbor::Value::SimpleValue::FALSE_VALUE:
+          return base::Value(false);
+
+        case cbor::Value::SimpleValue::TRUE_VALUE:
+          return base::Value(true);
+
+        case cbor::Value::SimpleValue::UNDEFINED:
+        case cbor::Value::SimpleValue::NULL_VALUE:
+          return absl::nullopt;
+      }
+
+    case cbor::Value::Type::STRING:
+      return base::Value(cbor_value.GetString());
+
+    case cbor::Value::Type::BYTE_STRING:
+      return base::Value(cbor_value.GetBytestring());
+
+    default:
+      return absl::nullopt;
+  }
+}
+
+}  // namespace
+
+CborParserImpl::CborParserImpl() = default;
+
+CborParserImpl::~CborParserImpl() = default;
+
+void CborParserImpl::Parse(mojo_base::BigBuffer cbor, ParseCallback callback) {
+  cbor::Reader::DecoderError error;
+
+  auto ret = cbor::Reader::Read(cbor, &error);
+
+  if (!ret.has_value()) {
+    std::move(callback).Run(absl::nullopt,
+                            cbor::Reader::ErrorCodeToString(error));
+    return;
+  }
+
+  absl::optional<::base::Value> temp_value = ConvertToBaseValue(*ret);
+
+  if (temp_value.has_value()) {
+    std::move(callback).Run(std::move(*temp_value), absl::nullopt);
+  } else {
+    std::move(callback).Run(absl::nullopt, "Error unexpected CBOR value.");
+  }
+}
+}  // namespace data_decoder
diff --git a/services/data_decoder/cbor_parser_impl.h b/services/data_decoder/cbor_parser_impl.h
new file mode 100644
index 0000000..3ea74d0
--- /dev/null
+++ b/services/data_decoder/cbor_parser_impl.h
@@ -0,0 +1,33 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_DATA_DECODER_CBOR_PARSER_IMPL_H_
+#define SERVICES_DATA_DECODER_CBOR_PARSER_IMPL_H_
+
+#include "components/cbor/values.h"
+#include "mojo/public/cpp/base/big_buffer.h"
+#include "services/data_decoder/public/mojom/cbor_parser.mojom.h"
+
+namespace data_decoder {
+
+// This is a class used to parse and decode CBOR values.
+
+// Current Limitations:
+// - Does not support null or undefined values
+// - Integers must fit in the 'int' type
+// - Does not support float values (limitations in components/cbor library)
+// - The keys in Maps must be a string or bytestring
+// - If at least one Map key is invalid, an error will be returned
+class CborParserImpl : public mojom::CborParser {
+ public:
+  CborParserImpl();
+  ~CborParserImpl() override;
+
+  // Implementation for mojom::CborParser
+  void Parse(mojo_base::BigBuffer cbor, ParseCallback callback) override;
+};
+
+}  // namespace data_decoder
+
+#endif  // SERVICES_DATA_DECODER_CBOR_PARSER_IMPL_H_
diff --git a/services/data_decoder/cbor_parser_unittest.cc b/services/data_decoder/cbor_parser_unittest.cc
new file mode 100644
index 0000000..4a0ce6a54
--- /dev/null
+++ b/services/data_decoder/cbor_parser_unittest.cc
@@ -0,0 +1,131 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <cstdint>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/functional/bind.h"
+#include "base/functional/callback.h"
+#include "base/values.h"
+#include "components/cbor/reader.h"
+#include "components/cbor/values.h"
+#include "components/cbor/writer.h"
+#include "services/data_decoder/cbor_parser_impl.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace data_decoder {
+
+namespace {
+void CopyResultCallback(absl::optional<::base::Value>& output_result,
+                        absl::optional<std::string>& output_error,
+                        absl::optional<::base::Value> result,
+                        const absl::optional<std::string>& error) {
+  output_result = std::move(result);
+  output_error = error;
+}
+
+}  // namespace
+
+using CborToValueTest = testing::Test;
+
+TEST_F(CborToValueTest, SuccesfulParseValues) {
+  struct {
+    std::string name;
+    std::vector<uint8_t> input;
+    base::Value expected_result;
+  } test_cases[] = {
+      {
+          "Unsigned",
+          {0x18, 0x64},  // 100
+          base::Value(100),
+      },
+      {
+          "Negative",
+          {0x38, 0x63},  //-100
+          base::Value(-100),
+      },
+      {
+          "Array",  // [100, false, "string", {"k": "v"}]
+          {0x84, 0x18, 0x64, 0xF4, 0x66, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67,
+           0xA1, 0x61, 0x6B, 0x61, 0x76},
+          base::Value(base::Value::List()
+                          .Append(100)
+                          .Append(false)
+                          .Append("string")
+                          .Append(base::Value::Dict().Set("k", "v"))),
+      },
+      {"Map",  // {"bool": true, "array": [100], "number": 100}
+       {0xA3, 0x64, 0x62, 0x6F, 0x6F, 0x6C, 0xF5, 0x65, 0x61,
+        0x72, 0x72, 0x61, 0x79, 0x81, 0x18, 0x64, 0x66, 0x6E,
+        0x75, 0x6D, 0x62, 0x65, 0x72, 0x18, 0x64},
+       base::Value(base::Value::Dict()
+                       .Set("bool", true)
+                       .Set("array", base::Value::List().Append(100))
+                       .Set("number", 100))},
+      {"SimpleBooleanTrue", {0xF5}, base::Value(true)},
+      {"SimpleBooleanFalse", {0xF4}, base::Value(false)},
+      {"String", {0x64, 0x63, 0x62, 0x6F, 0x72}, base::Value("cbor")},
+      {"ByteString",
+       {0x44, 0x63, 0x62, 0x6f, 0x72},
+       base::Value(cbor::Value::BinaryValue({0x63, 0x62, 0x6f, 0x72}))}};
+
+  for (const auto& test_case : test_cases) {
+    SCOPED_TRACE(test_case.name);
+    CborParserImpl parser;
+    absl::optional<base::Value> result;
+    absl::optional<std::string> error;
+
+    parser.Parse(
+        test_case.input,
+        base::BindOnce(&CopyResultCallback, std::ref(result), std::ref(error)));
+
+    ASSERT_TRUE(result.has_value());
+    EXPECT_EQ(result.value(), test_case.expected_result);
+    EXPECT_FALSE(error.has_value());
+  }
+}
+
+TEST_F(CborToValueTest, FailingParseValues) {
+  const std::string invalid_error = "Error unexpected CBOR value.";
+  struct {
+    std::string name;
+    std::vector<uint8_t> input;
+    std::string expected_error;
+  } test_cases[] = {
+      {"Null", {0xF6}, invalid_error},
+
+      {"InvalidMapKeyType",  // {100: "100", "k": "v"} - 100 is an invalid key
+       {0xA2, 0x18, 0x64, 0x63, 0x31, 0x30, 0x30, 0x61, 0x6B, 0x61, 0x76},
+       invalid_error},
+
+      {"NestedInvalidKey",  // [{100: "100", "k": "v"}] - 100 is an invalid key
+       {0x81, 0xA2, 0x18, 0x64, 0x63, 0x31, 0x30, 0x30, 0x61, 0x6B, 0x61, 0x76},
+       invalid_error},
+
+      {"DuplicateMapKeys",  //  {"k": "v1", "k": "v2"}
+       {0xA2, 0x61, 0x6B, 0x62, 0x76, 0x31, 0x61, 0x6B, 0x62, 0x76, 0x32},
+       cbor::Reader::ErrorCodeToString(
+           cbor::Reader::DecoderError::DUPLICATE_KEY)}};
+
+  for (const auto& test_case : test_cases) {
+    SCOPED_TRACE(test_case.name);
+    CborParserImpl parser;
+
+    absl::optional<base::Value> result;
+    absl::optional<std::string> error;
+
+    parser.Parse(
+        test_case.input,
+        base::BindOnce(&CopyResultCallback, std::ref(result), std::ref(error)));
+
+    ASSERT_TRUE(error.has_value());
+    EXPECT_EQ(error.value(), test_case.expected_error);
+    EXPECT_FALSE(result.has_value());
+  }
+}
+
+}  // namespace data_decoder
diff --git a/services/data_decoder/public/mojom/BUILD.gn b/services/data_decoder/public/mojom/BUILD.gn
index 7bdb7051..87d77aa7 100644
--- a/services/data_decoder/public/mojom/BUILD.gn
+++ b/services/data_decoder/public/mojom/BUILD.gn
@@ -7,6 +7,7 @@
 
 mojom("mojom") {
   sources = [
+    "cbor_parser.mojom",
     "data_decoder_service.mojom",
     "gzipper.mojom",
     "image_decoder.mojom",
diff --git a/services/data_decoder/public/mojom/cbor_parser.mojom b/services/data_decoder/public/mojom/cbor_parser.mojom
new file mode 100644
index 0000000..603ee0c1
--- /dev/null
+++ b/services/data_decoder/public/mojom/cbor_parser.mojom
@@ -0,0 +1,24 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+module data_decoder.mojom;
+
+import "mojo/public/mojom/base/values.mojom";
+import "mojo/public/mojom/base/big_buffer.mojom";
+
+// Interface to parse CBOR documents into a base::Value structure.
+interface CborParser {
+  // Parses the input |cbor| accordingly and converts it into a base::Value.
+  // Returns the parsed |result| on success or a string
+  // |error| message on failure.
+
+  //   Current Limitations:
+  // - Does not support null or undefined values
+  // - Integers must fit in the 'int' type
+  // - Does not support float values (limitations in components/cbor library)
+  // - The keys in Maps must be a string or bytestring
+  // - If at least one Map key is invalid, an error will be returned
+  Parse(mojo_base.mojom.BigBuffer cbor) =>
+      (mojo_base.mojom.Value? result, string? error);
+};
diff --git a/services/network/public/cpp/features.cc b/services/network/public/cpp/features.cc
index ab266d17..58cb7279 100644
--- a/services/network/public/cpp/features.cc
+++ b/services/network/public/cpp/features.cc
@@ -327,7 +327,7 @@
 
 BASE_FEATURE(kLessChattyNetworkService,
              "LessChattyNetworkService",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 #if BUILDFLAG(IS_ANDROID)
 BASE_FEATURE(kNetworkServiceEmptyOutOfProcess,
              "NetworkServiceEmptyOutOfProcess",
diff --git a/testing/android/junit/java/src/org/chromium/testing/local/GtestFilter.java b/testing/android/junit/java/src/org/chromium/testing/local/GtestFilter.java
index e630bca..cab6bb0 100644
--- a/testing/android/junit/java/src/org/chromium/testing/local/GtestFilter.java
+++ b/testing/android/junit/java/src/org/chromium/testing/local/GtestFilter.java
@@ -30,13 +30,14 @@
     private static final Pattern OPEN_BRACKET = Pattern.compile("\\[");
     private static final Pattern CLOSED_BRACKET = Pattern.compile("\\]");
 
+    // Matches a test that can have an SDK version in the name: org.class.testInvalidMinidump[28]
+    private static final Pattern GTEST_NAME_REGEX = Pattern.compile("(.*)?\\[\\d+\\]$");
     /**
      *  Creates the filter and converts the provided googletest-style filter
      *  string into positive and negative regexes.
      */
     public GtestFilter(String filterString) {
         mFilterString = filterString;
-
         String[] filterStrings = DASH.split(filterString, 2);
         mPositiveRegexes = generatePatternSet(filterStrings[0]);
         if (filterStrings.length == 2) {
@@ -78,10 +79,14 @@
     public boolean shouldRun(Description description) {
         if (description.getMethodName() == null) return true;
 
+        String gtestSdkName = description.getClassName() + "." + description.getMethodName();
         String gtestName = description.getClassName() + "." + description.getMethodName();
-        // getDisplayName includes the SDK version when robolectric property
-        // alwaysIncludeVariantMarkersInTestName is set.
-        String gtestSdkName = description.getClassName() + "." + description.getDisplayName();
+        // Use regex to get test name without sdk appended to make filtering more intuitive.
+        // Some tests may not have an sdk version.
+        Matcher gtestNameMatcher = GTEST_NAME_REGEX.matcher(gtestName);
+        if (gtestNameMatcher.find()) {
+            gtestName = gtestNameMatcher.group(1);
+        }
 
         for (Pattern p : mNegativeRegexes) {
             if (p.matcher(gtestName).matches() || p.matcher(gtestSdkName).matches()) return false;
diff --git a/testing/buildbot/autoshard_exceptions.json b/testing/buildbot/autoshard_exceptions.json
index f3b9925..0498a8a 100644
--- a/testing/buildbot/autoshard_exceptions.json
+++ b/testing/buildbot/autoshard_exceptions.json
@@ -1,6 +1,20 @@
 {
     "chromium.android": {
         "android-12-x64-rel": {
+            "services_unittests": {
+                "debug": {
+                    "avg_num_builds_per_peak_hour": "80.0",
+                    "estimated_bot_hour_delta": "2.93",
+                    "prev_avg_pending_time_sec": "15.5",
+                    "prev_p50_pending_time_sec": "1.0",
+                    "prev_p90_pending_time_sec": "54.0",
+                    "prev_percentile_duration_minutes": "15.32",
+                    "prev_shard_count": "2",
+                    "simulated_max_shard_duration": "10.21",
+                    "try_builder": "android-12-x64-rel"
+                },
+                "shards": "3"
+            },
             "webview_instrumentation_test_apk": {
                 "debug": {
                     "avg_num_builds_per_peak_hour": "72.0",
@@ -14,6 +28,36 @@
                     "try_builder": "android-12-x64-rel"
                 },
                 "shards": "13"
+            },
+            "webview_trichrome_64_cts_tests full_mode": {
+                "debug": {
+                    "avg_num_builds_per_peak_hour": "80.0",
+                    "estimated_bot_hour_delta": "3.02",
+                    "prev_avg_pending_time_sec": "16.1",
+                    "prev_p50_pending_time_sec": "1.0",
+                    "prev_p90_pending_time_sec": "59.0",
+                    "prev_percentile_duration_minutes": "15.24",
+                    "prev_shard_count": "2",
+                    "simulated_max_shard_duration": "10.16",
+                    "try_builder": "android-12-x64-rel"
+                },
+                "shards": "3"
+            }
+        },
+        "android-nougat-x86-rel": {
+            "webview_instrumentation_test_apk": {
+                "debug": {
+                    "avg_num_builds_per_peak_hour": "79.0",
+                    "estimated_bot_hour_delta": "6.33",
+                    "prev_avg_pending_time_sec": "36.5",
+                    "prev_p50_pending_time_sec": "7.0",
+                    "prev_p90_pending_time_sec": "116.0",
+                    "prev_percentile_duration_minutes": "15.5",
+                    "prev_shard_count": "27",
+                    "simulated_max_shard_duration": "14.43",
+                    "try_builder": "android-nougat-x86-rel"
+                },
+                "shards": "29"
             }
         }
     },
@@ -21,49 +65,49 @@
         "chromeos-amd64-generic-rel": {
             "chrome_all_tast_tests": {
                 "debug": {
-                    "avg_num_builds_per_peak_hour": "75.0",
-                    "estimated_bot_hour_delta": "3.09",
-                    "prev_avg_pending_time_sec": "134.9",
-                    "prev_p50_pending_time_sec": "17.0",
-                    "prev_p90_pending_time_sec": "363.0",
-                    "prev_percentile_duration_minutes": "15.29",
-                    "prev_shard_count": "7",
-                    "simulated_max_shard_duration": "13.38",
+                    "avg_num_builds_per_peak_hour": "84.0",
+                    "estimated_bot_hour_delta": "10.25",
+                    "prev_avg_pending_time_sec": "119.1",
+                    "prev_p50_pending_time_sec": "23.0",
+                    "prev_p90_pending_time_sec": "330.0",
+                    "prev_percentile_duration_minutes": "19.94",
+                    "prev_shard_count": "8",
+                    "simulated_max_shard_duration": "14.5",
                     "try_builder": "chromeos-amd64-generic-rel"
                 },
-                "shards": "8"
+                "shards": "11"
             }
         },
         "linux-chromeos-rel": {
             "browser_tests": {
                 "debug": {
-                    "avg_num_builds_per_peak_hour": "71.0",
-                    "estimated_bot_hour_delta": "4.67",
-                    "prev_avg_pending_time_sec": "210.1",
-                    "prev_p50_pending_time_sec": "86.0",
-                    "prev_p90_pending_time_sec": "642.0",
-                    "prev_percentile_duration_minutes": "15.28",
-                    "prev_shard_count": "70",
-                    "simulated_max_shard_duration": "14.86",
+                    "avg_num_builds_per_peak_hour": "79.0",
+                    "estimated_bot_hour_delta": "11.85",
+                    "prev_avg_pending_time_sec": "131.7",
+                    "prev_p50_pending_time_sec": "38.0",
+                    "prev_p90_pending_time_sec": "360.0",
+                    "prev_percentile_duration_minutes": "15.8",
+                    "prev_shard_count": "72",
+                    "simulated_max_shard_duration": "14.77",
                     "try_builder": "linux-chromeos-rel"
                 },
-                "shards": "72"
+                "shards": "77"
             }
         },
         "linux-lacros-tester-rel": {
             "browser_tests": {
                 "debug": {
                     "avg_num_builds_per_peak_hour": "83.0",
-                    "estimated_bot_hour_delta": "4.3",
-                    "prev_avg_pending_time_sec": "82.3",
-                    "prev_p50_pending_time_sec": "2.0",
-                    "prev_p90_pending_time_sec": "268.0",
-                    "prev_percentile_duration_minutes": "15.98",
-                    "prev_shard_count": "20",
-                    "simulated_max_shard_duration": "14.53",
+                    "estimated_bot_hour_delta": "2.27",
+                    "prev_avg_pending_time_sec": "161.1",
+                    "prev_p50_pending_time_sec": "32.0",
+                    "prev_p90_pending_time_sec": "436.0",
+                    "prev_percentile_duration_minutes": "15.17",
+                    "prev_shard_count": "22",
+                    "simulated_max_shard_duration": "14.51",
                     "try_builder": "linux-lacros-rel"
                 },
-                "shards": "22"
+                "shards": "23"
             },
             "interactive_ui_tests": {
                 "debug": {
@@ -137,17 +181,17 @@
             },
             "lacros_chrome_browsertests_run_in_series": {
                 "debug": {
-                    "avg_num_builds_per_peak_hour": "77.0",
-                    "estimated_bot_hour_delta": "1.76",
-                    "prev_avg_pending_time_sec": "45.6",
-                    "prev_p50_pending_time_sec": "0.0",
-                    "prev_p90_pending_time_sec": "164.0",
-                    "prev_percentile_duration_minutes": "16.34",
-                    "prev_shard_count": "2",
-                    "simulated_max_shard_duration": "10.89",
+                    "avg_num_builds_per_peak_hour": "83.0",
+                    "estimated_bot_hour_delta": "1.97",
+                    "prev_avg_pending_time_sec": "155.3",
+                    "prev_p50_pending_time_sec": "20.0",
+                    "prev_p90_pending_time_sec": "432.0",
+                    "prev_percentile_duration_minutes": "15.65",
+                    "prev_shard_count": "3",
+                    "simulated_max_shard_duration": "11.74",
                     "try_builder": "linux-lacros-rel"
                 },
-                "shards": "3"
+                "shards": "4"
             },
             "lacros_chrome_browsertests_run_in_series Lacros version skew testing ash beta": {
                 "debug": {
@@ -305,17 +349,17 @@
         "Linux ASan LSan Tests (1)": {
             "content_browsertests": {
                 "debug": {
-                    "avg_num_builds_per_peak_hour": "71.0",
-                    "estimated_bot_hour_delta": "-3.33",
-                    "prev_avg_pending_time_sec": "195.2",
-                    "prev_p50_pending_time_sec": "59.0",
-                    "prev_p90_pending_time_sec": "512.0",
-                    "prev_percentile_duration_minutes": "14.47",
-                    "prev_shard_count": "29",
-                    "simulated_max_shard_duration": "14.99",
+                    "avg_num_builds_per_peak_hour": "78.0",
+                    "estimated_bot_hour_delta": "3.13",
+                    "prev_avg_pending_time_sec": "140.2",
+                    "prev_p50_pending_time_sec": "36.0",
+                    "prev_p90_pending_time_sec": "410.0",
+                    "prev_percentile_duration_minutes": "15.26",
+                    "prev_shard_count": "28",
+                    "simulated_max_shard_duration": "14.73",
                     "try_builder": "linux_chromium_asan_rel_ng"
                 },
-                "shards": "28"
+                "shards": "29"
             },
             "interactive_ui_tests": {
                 "debug": {
diff --git a/testing/buildbot/chromium.android.json b/testing/buildbot/chromium.android.json
index 1be19b7..e06d94f 100644
--- a/testing/buildbot/chromium.android.json
+++ b/testing/buildbot/chromium.android.json
@@ -9813,7 +9813,7 @@
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 2
+          "shards": 3
         },
         "test": "services_unittests",
         "test_id_prefix": "ninja://services:services_unittests/"
@@ -10802,7 +10802,7 @@
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 2
+          "shards": 3
         },
         "test": "webview_trichrome_64_cts_tests",
         "test_id_prefix": "ninja://android_webview/test:webview_trichrome_64_cts_tests/",
@@ -30798,7 +30798,7 @@
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 27
+          "shards": 29
         },
         "test": "webview_instrumentation_test_apk",
         "test_id_prefix": "ninja://android_webview/test:webview_instrumentation_test_apk/"
diff --git a/testing/buildbot/chromium.cft.json b/testing/buildbot/chromium.cft.json
index a43fe4f..0e0f89e8f5 100644
--- a/testing/buildbot/chromium.cft.json
+++ b/testing/buildbot/chromium.cft.json
@@ -4071,7 +4071,8 @@
           "--flag-specific=disable-site-isolation-trials",
           "--num-retries=3",
           "--write-run-histories-to=${ISOLATED_OUTDIR}/run_histories.json",
-          "--additional-env-var=LLVM_PROFILE_FILE=${ISOLATED_OUTDIR}/profraw/default-%2m.profraw"
+          "--additional-env-var=LLVM_PROFILE_FILE=${ISOLATED_OUTDIR}/profraw/default-%2m.profraw",
+          "--flag-specific=chrome-for-testing"
         ],
         "check_flakiness_for_new_tests": false,
         "isolate_name": "blink_web_tests",
diff --git a/testing/buildbot/chromium.chromiumos.json b/testing/buildbot/chromium.chromiumos.json
index d170b86..2936531 100644
--- a/testing/buildbot/chromium.chromiumos.json
+++ b/testing/buildbot/chromium.chromiumos.json
@@ -240,7 +240,7 @@
             ]
           },
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 8
+          "shards": 11
         },
         "test": "chrome_all_tast_tests",
         "test_id_prefix": "ninja://chromeos:chrome_all_tast_tests/"
@@ -3521,7 +3521,7 @@
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 72
+          "shards": 77
         },
         "test": "browser_tests",
         "test_id_prefix": "ninja://chrome/test:browser_tests/"
@@ -5124,7 +5124,7 @@
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 22
+          "shards": 23
         },
         "test": "browser_tests",
         "test_id_prefix": "ninja://chrome/test:browser_tests/"
@@ -5669,9 +5669,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5829.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5830.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 116.0.5829.0",
+        "description": "Run with ash-chrome version 116.0.5830.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -5682,8 +5682,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v116.0.5829.0",
-              "revision": "version:116.0.5829.0"
+              "location": "lacros_version_skew_tests_v116.0.5830.0",
+              "revision": "version:116.0.5830.0"
             }
           ],
           "dimension_sets": [
@@ -5834,9 +5834,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5829.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5830.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 116.0.5829.0",
+        "description": "Run with ash-chrome version 116.0.5830.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -5847,8 +5847,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v116.0.5829.0",
-              "revision": "version:116.0.5829.0"
+              "location": "lacros_version_skew_tests_v116.0.5830.0",
+              "revision": "version:116.0.5830.0"
             }
           ],
           "dimension_sets": [
@@ -5941,7 +5941,7 @@
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 3
+          "shards": 4
         },
         "test": "lacros_chrome_browsertests_run_in_series",
         "test_id_prefix": "ninja://chrome/test:lacros_chrome_browsertests_run_in_series/"
@@ -5981,9 +5981,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5829.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5830.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 116.0.5829.0",
+        "description": "Run with ash-chrome version 116.0.5830.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -5994,8 +5994,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v116.0.5829.0",
-              "revision": "version:116.0.5829.0"
+              "location": "lacros_version_skew_tests_v116.0.5830.0",
+              "revision": "version:116.0.5830.0"
             }
           ],
           "dimension_sets": [
diff --git a/testing/buildbot/chromium.coverage.json b/testing/buildbot/chromium.coverage.json
index d8d827d0..fe41d30 100644
--- a/testing/buildbot/chromium.coverage.json
+++ b/testing/buildbot/chromium.coverage.json
@@ -25493,9 +25493,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5829.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5830.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 116.0.5829.0",
+        "description": "Run with ash-chrome version 116.0.5830.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -25506,8 +25506,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v116.0.5829.0",
-              "revision": "version:116.0.5829.0"
+              "location": "lacros_version_skew_tests_v116.0.5830.0",
+              "revision": "version:116.0.5830.0"
             }
           ],
           "dimension_sets": [
@@ -25658,9 +25658,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5829.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5830.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 116.0.5829.0",
+        "description": "Run with ash-chrome version 116.0.5830.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -25671,8 +25671,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v116.0.5829.0",
-              "revision": "version:116.0.5829.0"
+              "location": "lacros_version_skew_tests_v116.0.5830.0",
+              "revision": "version:116.0.5830.0"
             }
           ],
           "dimension_sets": [
@@ -25805,9 +25805,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5829.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5830.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 116.0.5829.0",
+        "description": "Run with ash-chrome version 116.0.5830.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -25818,8 +25818,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v116.0.5829.0",
-              "revision": "version:116.0.5829.0"
+              "location": "lacros_version_skew_tests_v116.0.5830.0",
+              "revision": "version:116.0.5830.0"
             }
           ],
           "dimension_sets": [
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json
index 612331e..e642bfc4 100644
--- a/testing/buildbot/chromium.fyi.json
+++ b/testing/buildbot/chromium.fyi.json
@@ -35719,6 +35719,69 @@
       }
     ]
   },
+  "lacros-arm64-generic-rel-skylab-fyi-tests": {
+    "skylab_tests": [
+      {
+        "args": [],
+        "bucket": "chromiumos-image-archive",
+        "cros_board": "jacuzzi",
+        "cros_img": "jacuzzi-public/R115-15465.0.0",
+        "name": "lacros_all_tast_tests JACUZZI_PUBLIC_LKGM",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "shards": 2,
+        "swarming": {},
+        "tast_expr": "(\"group:mainline\" && (\"dep:lacros_stable\" || \"dep:lacros\") && !informational)",
+        "test": "lacros_all_tast_tests",
+        "test_id_prefix": "ninja://chromeos/lacros:lacros_all_tast_tests/",
+        "test_level_retries": 2,
+        "timeout_sec": 10800,
+        "variant_id": "JACUZZI_PUBLIC_LKGM"
+      },
+      {
+        "args": [],
+        "bucket": "chromiumos-image-archive",
+        "cros_board": "jacuzzi",
+        "cros_img": "jacuzzi-public/R115-15465.0.0",
+        "name": "lacros_all_tast_tests JACUZZI_CQ_PUBLIC_LKGM",
+        "public_builder": "cros_test_platform_public",
+        "public_builder_bucket": "testplatform-public",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "shards": 2,
+        "swarming": {},
+        "tast_expr": "(\"group:mainline\" && (\"dep:lacros_stable\" || \"dep:lacros\") && !informational)",
+        "test": "lacros_all_tast_tests",
+        "test_id_prefix": "ninja://chromeos/lacros:lacros_all_tast_tests/",
+        "test_level_retries": 2,
+        "timeout_sec": 10800,
+        "variant_id": "JACUZZI_CQ_PUBLIC_LKGM"
+      },
+      {
+        "args": [],
+        "bucket": "chromiumos-image-archive",
+        "cros_board": "kevin",
+        "cros_img": "kevin64-public/R115-15465.0.0",
+        "name": "lacros_all_tast_tests KEVIN64_PUBLIC_LKGM",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "shards": 2,
+        "swarming": {},
+        "tast_expr": "(\"group:mainline\" && (\"dep:lacros_stable\" || \"dep:lacros\") && !informational)",
+        "test": "lacros_all_tast_tests",
+        "test_id_prefix": "ninja://chromeos/lacros:lacros_all_tast_tests/",
+        "test_level_retries": 2,
+        "timeout_sec": 10800,
+        "variant_id": "KEVIN64_PUBLIC_LKGM"
+      }
+    ]
+  },
   "linux-annotator-rel": {
     "scripts": [
       {
@@ -38426,9 +38489,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5829.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5830.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 116.0.5829.0",
+        "description": "Run with ash-chrome version 116.0.5830.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -38438,8 +38501,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v116.0.5829.0",
-              "revision": "version:116.0.5829.0"
+              "location": "lacros_version_skew_tests_v116.0.5830.0",
+              "revision": "version:116.0.5830.0"
             }
           ],
           "dimension_sets": [
@@ -38591,9 +38654,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5829.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5830.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 116.0.5829.0",
+        "description": "Run with ash-chrome version 116.0.5830.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -38603,8 +38666,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v116.0.5829.0",
-              "revision": "version:116.0.5829.0"
+              "location": "lacros_version_skew_tests_v116.0.5830.0",
+              "revision": "version:116.0.5830.0"
             }
           ],
           "dimension_sets": [
@@ -38738,9 +38801,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5829.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5830.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 116.0.5829.0",
+        "description": "Run with ash-chrome version 116.0.5830.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -38750,8 +38813,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v116.0.5829.0",
-              "revision": "version:116.0.5829.0"
+              "location": "lacros_version_skew_tests_v116.0.5830.0",
+              "revision": "version:116.0.5830.0"
             }
           ],
           "dimension_sets": [
@@ -40215,9 +40278,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5829.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5830.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 116.0.5829.0",
+        "description": "Run with ash-chrome version 116.0.5830.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -40227,8 +40290,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v116.0.5829.0",
-              "revision": "version:116.0.5829.0"
+              "location": "lacros_version_skew_tests_v116.0.5830.0",
+              "revision": "version:116.0.5830.0"
             }
           ],
           "dimension_sets": [
@@ -40380,9 +40443,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5829.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5830.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 116.0.5829.0",
+        "description": "Run with ash-chrome version 116.0.5830.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -40392,8 +40455,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v116.0.5829.0",
-              "revision": "version:116.0.5829.0"
+              "location": "lacros_version_skew_tests_v116.0.5830.0",
+              "revision": "version:116.0.5830.0"
             }
           ],
           "dimension_sets": [
@@ -40527,9 +40590,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5829.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5830.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 116.0.5829.0",
+        "description": "Run with ash-chrome version 116.0.5830.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -40539,8 +40602,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v116.0.5829.0",
-              "revision": "version:116.0.5829.0"
+              "location": "lacros_version_skew_tests_v116.0.5830.0",
+              "revision": "version:116.0.5830.0"
             }
           ],
           "dimension_sets": [
@@ -41275,9 +41338,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5829.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5830.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 116.0.5829.0",
+        "description": "Run with ash-chrome version 116.0.5830.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -41287,8 +41350,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v116.0.5829.0",
-              "revision": "version:116.0.5829.0"
+              "location": "lacros_version_skew_tests_v116.0.5830.0",
+              "revision": "version:116.0.5830.0"
             }
           ],
           "dimension_sets": [
diff --git a/testing/buildbot/chromium.memory.json b/testing/buildbot/chromium.memory.json
index c2a51b6c..c6b1b46 100644
--- a/testing/buildbot/chromium.memory.json
+++ b/testing/buildbot/chromium.memory.json
@@ -462,7 +462,7 @@
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 28
+          "shards": 29
         },
         "test": "content_browsertests",
         "test_id_prefix": "ninja://content/test:content_browsertests/"
@@ -18080,12 +18080,12 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5829.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5830.0/test_ash_chrome",
           "--test-launcher-print-test-stdio=always",
           "--combine-ash-logs-on-bots",
           "--asan-symbolize-output"
         ],
-        "description": "Run with ash-chrome version 116.0.5829.0",
+        "description": "Run with ash-chrome version 116.0.5830.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -18096,8 +18096,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v116.0.5829.0",
-              "revision": "version:116.0.5829.0"
+              "location": "lacros_version_skew_tests_v116.0.5830.0",
+              "revision": "version:116.0.5830.0"
             }
           ],
           "dimension_sets": [
@@ -18265,12 +18265,12 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5829.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5830.0/test_ash_chrome",
           "--test-launcher-print-test-stdio=always",
           "--combine-ash-logs-on-bots",
           "--asan-symbolize-output"
         ],
-        "description": "Run with ash-chrome version 116.0.5829.0",
+        "description": "Run with ash-chrome version 116.0.5830.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -18281,8 +18281,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v116.0.5829.0",
-              "revision": "version:116.0.5829.0"
+              "location": "lacros_version_skew_tests_v116.0.5830.0",
+              "revision": "version:116.0.5830.0"
             }
           ],
           "dimension_sets": [
@@ -18427,12 +18427,12 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5829.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5830.0/test_ash_chrome",
           "--test-launcher-print-test-stdio=always",
           "--combine-ash-logs-on-bots",
           "--asan-symbolize-output"
         ],
-        "description": "Run with ash-chrome version 116.0.5829.0",
+        "description": "Run with ash-chrome version 116.0.5830.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -18443,8 +18443,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v116.0.5829.0",
-              "revision": "version:116.0.5829.0"
+              "location": "lacros_version_skew_tests_v116.0.5830.0",
+              "revision": "version:116.0.5830.0"
             }
           ],
           "dimension_sets": [
diff --git a/testing/buildbot/filters/android.pie_arm64_rel.chrome_public_test_apk.filter b/testing/buildbot/filters/android.pie_arm64_rel.chrome_public_test_apk.filter
index 94d55ccb..81ccbcb 100644
--- a/testing/buildbot/filters/android.pie_arm64_rel.chrome_public_test_apk.filter
+++ b/testing/buildbot/filters/android.pie_arm64_rel.chrome_public_test_apk.filter
@@ -6,9 +6,6 @@
 -org.chromium.chrome.browser.dom_distiller.DistilledPagePrefsTest.testSingleObserverFontScaling
 -org.chromium.chrome.browser.dom_distiller.DistilledPagePrefsTest.testSingleObserverTheme
 
-# crbug.com/963898
--org.chromium.chrome.browser.download.DownloadMediaParserTest.testParseVideoH264
-
 # crbug.com/1018181
 -org.chromium.chrome.browser.download.home.DownloadActivityV2Test.testLaunchingActivity
 
diff --git a/testing/buildbot/test_suite_exceptions.pyl b/testing/buildbot/test_suite_exceptions.pyl
index bbb44da..36cd0e8 100644
--- a/testing/buildbot/test_suite_exceptions.pyl
+++ b/testing/buildbot/test_suite_exceptions.pyl
@@ -924,6 +924,7 @@
       'linux-rel-cft': {
         "args": [
           "--additional-env-var=LLVM_PROFILE_FILE=${ISOLATED_OUTDIR}/profraw/default-%2m.profraw",
+          '--flag-specific=chrome-for-testing',
         ],
         'swarming': {
           'shards': 10,
@@ -3427,7 +3428,8 @@
       },
       'linux-rel-cft': {
         "args": [
-          "--additional-env-var=LLVM_PROFILE_FILE=${ISOLATED_OUTDIR}/profraw/default-%2m.profraw",
+          '--additional-env-var=LLVM_PROFILE_FILE=${ISOLATED_OUTDIR}/profraw/default-%2m.profraw',
+          '--flag-specific=chrome-for-testing',
         ],
       },
     },
diff --git a/testing/buildbot/variants.pyl b/testing/buildbot/variants.pyl
index 1b19ba8..95a8ae0 100644
--- a/testing/buildbot/variants.pyl
+++ b/testing/buildbot/variants.pyl
@@ -22,16 +22,16 @@
   },
   'LACROS_VERSION_SKEW_CANARY': {
     'args': [
-      '--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5829.0/test_ash_chrome',
+      '--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5830.0/test_ash_chrome',
     ],
-    'description': 'Run with ash-chrome version 116.0.5829.0',
+    'description': 'Run with ash-chrome version 116.0.5830.0',
     'identifier': 'Lacros version skew testing ash canary',
     'swarming': {
       'cipd_packages': [
         {
           'cipd_package': 'chromium/testing/linux-ash-chromium/x86_64/ash.zip',
-          'location': 'lacros_version_skew_tests_v116.0.5829.0',
-          'revision': 'version:116.0.5829.0',
+          'location': 'lacros_version_skew_tests_v116.0.5830.0',
+          'revision': 'version:116.0.5830.0',
         },
       ],
     },
diff --git a/testing/buildbot/waterfalls.pyl b/testing/buildbot/waterfalls.pyl
index 133686c..1a3b3ad 100644
--- a/testing/buildbot/waterfalls.pyl
+++ b/testing/buildbot/waterfalls.pyl
@@ -3513,6 +3513,12 @@
         },
         'os_type': 'chromeos',
       },
+      'lacros-arm64-generic-rel-skylab-fyi-tests': {
+        'test_suites': {
+          'skylab_tests': 'lacros_arm64_generic_rel_skylab_fyi',
+        },
+        'os_type': 'chromeos',
+      },
       'linux-annotator-rel': {
         'test_suites': {
           'scripts': 'test_traffic_annotation_auditor_script',
diff --git a/testing/variations/PRESUBMIT.py b/testing/variations/PRESUBMIT.py
index 0f87297..a84769f 100644
--- a/testing/variations/PRESUBMIT.py
+++ b/testing/variations/PRESUBMIT.py
@@ -399,12 +399,13 @@
                                                      cros_late_boot_features))
 
         if missing_features:
-          msg =("Study %s contains undeclared features."
-              " Please check the spelling of added features."
-              "\nIf the spelling is correct, please update "
-              "presubmit/find_features.py to include the file where the "
-              "feature(s) are defined.") % study_name
-          messages.append(output_api.PresubmitError(msg, missing_features))
+          msg = ("Presubmit was unable to verify existence of features in "
+                  "study %s.\nThis happens most commonly if the feature is "
+                  "defined by code generation.\n"
+                  "Please verify that the feature names have been spelled "
+                  "correctly before submitting. The affected features are:"
+              ) % study_name
+          messages.append(output_api.PresubmitResult(msg, missing_features))
 
   return messages
 
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 9690f7d1..6a1ac98 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -5132,21 +5132,6 @@
             ]
         }
     ],
-    "EnableBookmarksAccountStorage": [
-        {
-            "platforms": [
-                "ios"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled",
-                    "enable_features": [
-                        "EnableBookmarksAccountStorage"
-                    ]
-                }
-            ]
-        }
-    ],
     "EnableCrossSiteFlagNetworkIsolationKey": [
         {
             "platforms": [
@@ -8109,28 +8094,6 @@
             ]
         }
     ],
-    "LessChattyNetworkService": [
-        {
-            "platforms": [
-                "android",
-                "android_webview",
-                "chromeos",
-                "chromeos_lacros",
-                "ios",
-                "linux",
-                "mac",
-                "windows"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled",
-                    "enable_features": [
-                        "LessChattyNetworkService"
-                    ]
-                }
-            ]
-        }
-    ],
     "LimitImageDecodeCacheAge": [
         {
             "platforms": [
@@ -13775,6 +13738,30 @@
             ]
         }
     ],
+    "UseClientGmbInterface": [
+        {
+            "platforms": [
+                "android",
+                "android_webview",
+                "android_weblayer",
+                "chromeos",
+                "chromeos_lacros",
+                "fuchsia",
+                "ios",
+                "linux",
+                "mac",
+                "windows"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "UseClientGmbInterface"
+                    ]
+                }
+            ]
+        }
+    ],
     "UseDMSAAForTiles": [
         {
             "platforms": [
diff --git a/third_party/abseil-cpp/symbols_arm64_dbg.def b/third_party/abseil-cpp/symbols_arm64_dbg.def
index 86fc0592..882ee3c 100644
--- a/third_party/abseil-cpp/symbols_arm64_dbg.def
+++ b/third_party/abseil-cpp/symbols_arm64_dbg.def
@@ -684,12 +684,18 @@
     ??$__allocate_at_least@V?$allocator@UTransitionType@cctz@time_internal@absl@@@__Cr@std@@@__Cr@std@@YA?AU?$__allocation_result@PEAUTransitionType@cctz@time_internal@absl@@@01@AEAV?$allocator@UTransitionType@cctz@time_internal@absl@@@01@_K@Z
     ??$__allocate_at_least@V?$allocator@UViableSubstitution@strings_internal@absl@@@__Cr@std@@@__Cr@std@@YA?AU?$__allocation_result@PEAUViableSubstitution@strings_internal@absl@@@01@AEAV?$allocator@UViableSubstitution@strings_internal@absl@@@01@_K@Z
     ??$__allocate_at_least@V?$allocator@VFormatArgImpl@str_format_internal@absl@@@__Cr@std@@@__Cr@std@@YA?AU?$__allocation_result@PEAVFormatArgImpl@str_format_internal@absl@@@01@AEAV?$allocator@VFormatArgImpl@str_format_internal@absl@@@01@_K@Z
-    ??$__append@V?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEBU1234@AEBU1234@PEBQEBU1234@_J$0A@@__Cr@std@@@?$deque@UPrefixCrc@CrcCordState@crc_internal@absl@@V?$allocator@UPrefixCrc@CrcCordState@crc_internal@absl@@@__Cr@std@@@__Cr@std@@AEAAXV?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEBU1234@AEBU1234@PEBQEBU1234@_J$0A@@12@0PEAX@Z
-    ??$__construct_at_end@PEBVFormatArgImpl@str_format_internal@absl@@$0A@@?$vector@VFormatArgImpl@str_format_internal@absl@@V?$allocator@VFormatArgImpl@str_format_internal@absl@@@__Cr@std@@@__Cr@std@@AEAAXPEBVFormatArgImpl@str_format_internal@absl@@0_K@Z
+    ??$__append_with_size@V?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEBU1234@AEBU1234@PEBQEBU1234@_J$0A@@__Cr@std@@@?$deque@UPrefixCrc@CrcCordState@crc_internal@absl@@V?$allocator@UPrefixCrc@CrcCordState@crc_internal@absl@@@__Cr@std@@@__Cr@std@@AEAAXV?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEBU1234@AEBU1234@PEBQEBU1234@_J$0A@@12@_K@Z
+    ??$__assign_with_size@PEBVFormatArgImpl@str_format_internal@absl@@PEBV123@@?$vector@VFormatArgImpl@str_format_internal@absl@@V?$allocator@VFormatArgImpl@str_format_internal@absl@@@__Cr@std@@@__Cr@std@@AEAAXPEBVFormatArgImpl@str_format_internal@absl@@0_J@Z
+    ??$__assign_with_size_random_access@V?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEBU1234@AEBU1234@PEBQEBU1234@_J$0A@@__Cr@std@@@?$deque@UPrefixCrc@CrcCordState@crc_internal@absl@@V?$allocator@UPrefixCrc@CrcCordState@crc_internal@absl@@@__Cr@std@@@__Cr@std@@AEAAXV?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEBU1234@AEBU1234@PEBQEBU1234@_J$0A@@12@_J@Z
+    ??$__construct_at_end@PEBVFormatArgImpl@str_format_internal@absl@@PEBV123@@?$vector@VFormatArgImpl@str_format_internal@absl@@V?$allocator@VFormatArgImpl@str_format_internal@absl@@@__Cr@std@@@__Cr@std@@AEAAXPEBVFormatArgImpl@str_format_internal@absl@@0_K@Z
     ??$__construct_at_end@V?$move_iterator@PEAPEAPEBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@@?$__split_buffer@PEAPEBVImpl@time_zone@cctz@time_internal@absl@@AEAV?$allocator@PEAPEBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@@__Cr@std@@QEAAXV?$move_iterator@PEAPEAPEBVImpl@time_zone@cctz@time_internal@absl@@@12@0@Z
     ??$__construct_at_end@V?$move_iterator@PEAPEAUPrefixCrc@CrcCordState@crc_internal@absl@@@__Cr@std@@@?$__split_buffer@PEAUPrefixCrc@CrcCordState@crc_internal@absl@@AEAV?$allocator@PEAUPrefixCrc@CrcCordState@crc_internal@absl@@@__Cr@std@@@__Cr@std@@QEAAXV?$move_iterator@PEAPEAUPrefixCrc@CrcCordState@crc_internal@absl@@@12@0@Z
     ??$__construct_at_end@V?$move_iterator@PEAUTransition@cctz@time_internal@absl@@@__Cr@std@@@?$__split_buffer@UTransition@cctz@time_internal@absl@@AEAV?$allocator@UTransition@cctz@time_internal@absl@@@__Cr@std@@@__Cr@std@@QEAAXV?$move_iterator@PEAUTransition@cctz@time_internal@absl@@@12@0@Z
     ??$__construct_at_end@V?$move_iterator@PEAUTransitionType@cctz@time_internal@absl@@@__Cr@std@@@?$__split_buffer@UTransitionType@cctz@time_internal@absl@@AEAV?$allocator@UTransitionType@cctz@time_internal@absl@@@__Cr@std@@@__Cr@std@@QEAAXV?$move_iterator@PEAUTransitionType@cctz@time_internal@absl@@@12@0@Z
+    ??$__construct_at_end_with_size@V?$move_iterator@PEAPEAPEBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@@?$__split_buffer@PEAPEBVImpl@time_zone@cctz@time_internal@absl@@AEAV?$allocator@PEAPEBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@@__Cr@std@@QEAAXV?$move_iterator@PEAPEAPEBVImpl@time_zone@cctz@time_internal@absl@@@12@_K@Z
+    ??$__construct_at_end_with_size@V?$move_iterator@PEAPEAUPrefixCrc@CrcCordState@crc_internal@absl@@@__Cr@std@@@?$__split_buffer@PEAUPrefixCrc@CrcCordState@crc_internal@absl@@AEAV?$allocator@PEAUPrefixCrc@CrcCordState@crc_internal@absl@@@__Cr@std@@@__Cr@std@@QEAAXV?$move_iterator@PEAPEAUPrefixCrc@CrcCordState@crc_internal@absl@@@12@_K@Z
+    ??$__construct_at_end_with_size@V?$move_iterator@PEAUTransition@cctz@time_internal@absl@@@__Cr@std@@@?$__split_buffer@UTransition@cctz@time_internal@absl@@AEAV?$allocator@UTransition@cctz@time_internal@absl@@@__Cr@std@@@__Cr@std@@QEAAXV?$move_iterator@PEAUTransition@cctz@time_internal@absl@@@12@_K@Z
+    ??$__construct_at_end_with_size@V?$move_iterator@PEAUTransitionType@cctz@time_internal@absl@@@__Cr@std@@@?$__split_buffer@UTransitionType@cctz@time_internal@absl@@AEAV?$allocator@UTransitionType@cctz@time_internal@absl@@@__Cr@std@@@__Cr@std@@QEAAXV?$move_iterator@PEAUTransitionType@cctz@time_internal@absl@@@12@_K@Z
     ??$__construct_node_hash@AEBUpiecewise_construct_t@__Cr@std@@V?$tuple@AEBV?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@23@V?$tuple@$$V@23@@?$__hash_table@U?$__hash_value_type@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PEBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@V?$__unordered_map_hasher@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@U?$__hash_value_type@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PEBVImpl@time_zone@cctz@time_internal@absl@@@23@U?$hash@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@23@U?$equal_to@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@23@$00@23@V?$__unordered_map_equal@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@U?$__hash_value_type@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PEBVImpl@time_zone@cctz@time_internal@absl@@@23@U?$equal_to@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@23@U?$hash@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@23@$00@23@V?$allocator@U?$__hash_value_type@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PEBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@@23@@__Cr@std@@AEAA?AV?$unique_ptr@U?$__hash_node@U?$__hash_value_type@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PEBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@PEAX@__Cr@std@@V?$__hash_node_destructor@V?$allocator@U?$__hash_node@U?$__hash_value_type@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PEBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@PEAX@__Cr@std@@@__Cr@std@@@23@@12@_KAEBUpiecewise_construct_t@12@$$QEAV?$tuple@AEBV?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@12@$$QEAV?$tuple@$$V@12@@Z
     ??$__construct_one_at_end@$$V@?$vector@UTransition@cctz@time_internal@absl@@V?$allocator@UTransition@cctz@time_internal@absl@@@__Cr@std@@@__Cr@std@@AEAAXXZ
     ??$__construct_one_at_end@$$V@?$vector@UTransitionType@cctz@time_internal@absl@@V?$allocator@UTransitionType@cctz@time_internal@absl@@@__Cr@std@@@__Cr@std@@AEAAXXZ
@@ -789,7 +795,6 @@
     ??$__distance@PEBUPayload@status_internal@absl@@@__Cr@std@@YA_JPEBUPayload@status_internal@absl@@0Urandom_access_iterator_tag@01@@Z
     ??$__distance@PEBUTransition@cctz@time_internal@absl@@@__Cr@std@@YA_JPEBUTransition@cctz@time_internal@absl@@0Urandom_access_iterator_tag@01@@Z
     ??$__distance@PEBVFormatArgImpl@str_format_internal@absl@@@__Cr@std@@YA_JPEBVFormatArgImpl@str_format_internal@absl@@0Urandom_access_iterator_tag@01@@Z
-    ??$__distance@V?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEBU1234@AEBU1234@PEBQEBU1234@_J$0A@@__Cr@std@@@__Cr@std@@YA_JV?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEBU1234@AEBU1234@PEBQEBU1234@_J$0A@@01@0Urandom_access_iterator_tag@01@@Z
     ??$__distance@V?$move_iterator@PEAPEAPEBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@@__Cr@std@@YA_JV?$move_iterator@PEAPEAPEBVImpl@time_zone@cctz@time_internal@absl@@@01@0Urandom_access_iterator_tag@01@@Z
     ??$__distance@V?$move_iterator@PEAPEAUPrefixCrc@CrcCordState@crc_internal@absl@@@__Cr@std@@@__Cr@std@@YA_JV?$move_iterator@PEAPEAUPrefixCrc@CrcCordState@crc_internal@absl@@@01@0Urandom_access_iterator_tag@01@@Z
     ??$__distance@V?$move_iterator@PEAUTransition@cctz@time_internal@absl@@@__Cr@std@@@__Cr@std@@YA_JV?$move_iterator@PEAUTransition@cctz@time_internal@absl@@@01@0Urandom_access_iterator_tag@01@@Z
@@ -952,7 +957,7 @@
     ??$__validate_iter_reference@AEAV?$reverse_iterator@PEAUViableSubstitution@strings_internal@absl@@@__Cr@std@@@?$_IterOps@U_ClassicAlgPolicy@__Cr@std@@@__Cr@std@@SAXXZ
     ??$advance@PEBUTransition@cctz@time_internal@absl@@_J@?$_IterOps@U_ClassicAlgPolicy@__Cr@std@@@__Cr@std@@SAXAEAPEBUTransition@cctz@time_internal@absl@@_J@Z
     ??$advance@PEBUTransition@cctz@time_internal@absl@@_J_JX@__Cr@std@@YAXAEAPEBUTransition@cctz@time_internal@absl@@_J@Z
-    ??$advance@PEBVFormatArgImpl@str_format_internal@absl@@_K_KX@__Cr@std@@YAXAEAPEBVFormatArgImpl@str_format_internal@absl@@_K@Z
+    ??$advance@PEBVFormatArgImpl@str_format_internal@absl@@_J_JX@__Cr@std@@YAXAEAPEBVFormatArgImpl@str_format_internal@absl@@_J@Z
     ??$assign@PEBVFormatArgImpl@str_format_internal@absl@@$0A@@?$vector@VFormatArgImpl@str_format_internal@absl@@V?$allocator@VFormatArgImpl@str_format_internal@absl@@@__Cr@std@@@__Cr@std@@QEAAXPEBVFormatArgImpl@str_format_internal@absl@@0@Z
     ??$assign@V?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEBU1234@AEBU1234@PEBQEBU1234@_J$0A@@__Cr@std@@@?$deque@UPrefixCrc@CrcCordState@crc_internal@absl@@V?$allocator@UPrefixCrc@CrcCordState@crc_internal@absl@@@__Cr@std@@@__Cr@std@@QEAAXV?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEBU1234@AEBU1234@PEBQEBU1234@_J$0A@@12@0PEAX@Z
     ??$assign@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@?$optional_data_base@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@optional_internal@absl@@IEAAX$$QEAV?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@Z
@@ -1014,6 +1019,7 @@
     ??$construct_at@UViableSubstitution@strings_internal@absl@@AEAV?$basic_string_view@DU?$char_traits@D@__Cr@std@@@__Cr@std@@AEBV456@AEA_KPEAU123@@__Cr@std@@YAPEAUViableSubstitution@strings_internal@absl@@PEAU234@AEAV?$basic_string_view@DU?$char_traits@D@__Cr@std@@@01@AEBV501@AEA_K@Z
     ??$copy@PEBVFormatArgImpl@str_format_internal@absl@@PEAV123@@__Cr@std@@YAPEAVFormatArgImpl@str_format_internal@absl@@PEBV234@0PEAV234@@Z
     ??$copy@V?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEBU1234@AEBU1234@PEBQEBU1234@_J$0A@@__Cr@std@@V?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEAU1234@AEAU1234@PEAPEAU1234@_J$0A@@23@@__Cr@std@@YA?AV?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEAU1234@AEAU1234@PEAPEAU1234@_J$0A@@01@V?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEBU1234@AEBU1234@PEBQEBU1234@_J$0A@@01@0V201@@Z
+    ??$copy_n@V?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEBU1234@AEBU1234@PEBQEBU1234@_J$0A@@__Cr@std@@_JV?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEAU1234@AEAU1234@PEAPEAU1234@_J$0A@@23@@__Cr@std@@YA?AV?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEAU1234@AEAU1234@PEAPEAU1234@_J$0A@@01@V?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEBU1234@AEBU1234@PEBQEBU1234@_J$0A@@01@_JV201@@Z
     ??$countl_zero@_K@absl@@YAH_K@Z
     ??$countr_zero@I@absl@@YAHI@Z
     ??$countr_zero@_K@absl@@YAH_K@Z
@@ -1049,7 +1055,6 @@
     ??$distance@PEBUTransition@cctz@time_internal@absl@@@?$_IterOps@U_ClassicAlgPolicy@__Cr@std@@@__Cr@std@@SA_JPEBUTransition@cctz@time_internal@absl@@0@Z
     ??$distance@PEBUTransition@cctz@time_internal@absl@@@__Cr@std@@YA_JPEBUTransition@cctz@time_internal@absl@@0@Z
     ??$distance@PEBVFormatArgImpl@str_format_internal@absl@@@__Cr@std@@YA_JPEBVFormatArgImpl@str_format_internal@absl@@0@Z
-    ??$distance@V?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEBU1234@AEBU1234@PEBQEBU1234@_J$0A@@__Cr@std@@@__Cr@std@@YA_JV?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEBU1234@AEBU1234@PEBQEBU1234@_J$0A@@01@0@Z
     ??$distance@V?$move_iterator@PEAPEAPEBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@@__Cr@std@@YA_JV?$move_iterator@PEAPEAPEBVImpl@time_zone@cctz@time_internal@absl@@@01@0@Z
     ??$distance@V?$move_iterator@PEAPEAUPrefixCrc@CrcCordState@crc_internal@absl@@@__Cr@std@@@__Cr@std@@YA_JV?$move_iterator@PEAPEAUPrefixCrc@CrcCordState@crc_internal@absl@@@01@0@Z
     ??$distance@V?$move_iterator@PEAUTransition@cctz@time_internal@absl@@@__Cr@std@@@__Cr@std@@YA_JV?$move_iterator@PEAUTransition@cctz@time_internal@absl@@@01@0@Z
@@ -1184,6 +1189,7 @@
     ??$move_backward@PEAUTransitionType@cctz@time_internal@absl@@PEAU1234@@__Cr@std@@YAPEAUTransitionType@cctz@time_internal@absl@@PEAU2345@00@Z
     ??$next@AEAPEBUTransition@cctz@time_internal@absl@@@?$_IterOps@U_ClassicAlgPolicy@__Cr@std@@@__Cr@std@@SAPEBUTransition@cctz@time_internal@absl@@AEAPEBU3456@_J@Z
     ??$next@PEBUTransition@cctz@time_internal@absl@@@__Cr@std@@YAPEBUTransition@cctz@time_internal@absl@@PEBU2345@_J@Z
+    ??$next@PEBVFormatArgImpl@str_format_internal@absl@@@__Cr@std@@YAPEBVFormatArgImpl@str_format_internal@absl@@PEBV234@_J@Z
     ??$reset@PEAPEAU?$__hash_node_base@PEAU?$__hash_node@U?$__hash_value_type@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PEBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@PEAX@__Cr@std@@@__Cr@std@@@?$unique_ptr@$$BY0A@PEAU?$__hash_node_base@PEAU?$__hash_node@U?$__hash_value_type@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PEBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@PEAX@__Cr@std@@@__Cr@std@@V?$__bucket_list_deallocator@V?$allocator@PEAU?$__hash_node_base@PEAU?$__hash_node@U?$__hash_value_type@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PEBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@PEAX@__Cr@std@@@__Cr@std@@@__Cr@std@@@23@@__Cr@std@@QEAAXPEAPEAU?$__hash_node_base@PEAU?$__hash_node@U?$__hash_value_type@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PEBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@PEAX@__Cr@std@@@12@@Z
     ??$rotr@I@absl@@YAIIH@Z
     ??$rotr@_K@absl@@YA_K_KH@Z
diff --git a/third_party/abseil-cpp/symbols_arm64_rel.def b/third_party/abseil-cpp/symbols_arm64_rel.def
index fdef613..249c9c1 100644
--- a/third_party/abseil-cpp/symbols_arm64_rel.def
+++ b/third_party/abseil-cpp/symbols_arm64_rel.def
@@ -151,18 +151,19 @@
     ??$__allocate_at_least@V?$allocator@UTransitionType@cctz@time_internal@absl@@@__Cr@std@@@__Cr@std@@YA?AU?$__allocation_result@PEAUTransitionType@cctz@time_internal@absl@@@01@AEAV?$allocator@UTransitionType@cctz@time_internal@absl@@@01@_K@Z
     ??$__allocate_at_least@V?$allocator@UViableSubstitution@strings_internal@absl@@@__Cr@std@@@__Cr@std@@YA?AU?$__allocation_result@PEAUViableSubstitution@strings_internal@absl@@@01@AEAV?$allocator@UViableSubstitution@strings_internal@absl@@@01@_K@Z
     ??$__allocate_at_least@V?$allocator@VFormatArgImpl@str_format_internal@absl@@@__Cr@std@@@__Cr@std@@YA?AU?$__allocation_result@PEAVFormatArgImpl@str_format_internal@absl@@@01@AEAV?$allocator@VFormatArgImpl@str_format_internal@absl@@@01@_K@Z
-    ??$__append@V?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEBU1234@AEBU1234@PEBQEBU1234@_J$0A@@__Cr@std@@@?$deque@UPrefixCrc@CrcCordState@crc_internal@absl@@V?$allocator@UPrefixCrc@CrcCordState@crc_internal@absl@@@__Cr@std@@@__Cr@std@@AEAAXV?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEBU1234@AEBU1234@PEBQEBU1234@_J$0A@@12@0PEAX@Z
-    ??$__construct_at_end@V?$move_iterator@PEAPEAPEBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@@?$__split_buffer@PEAPEBVImpl@time_zone@cctz@time_internal@absl@@AEAV?$allocator@PEAPEBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@@__Cr@std@@QEAAXV?$move_iterator@PEAPEAPEBVImpl@time_zone@cctz@time_internal@absl@@@12@0@Z
-    ??$__construct_at_end@V?$move_iterator@PEAPEAUPrefixCrc@CrcCordState@crc_internal@absl@@@__Cr@std@@@?$__split_buffer@PEAUPrefixCrc@CrcCordState@crc_internal@absl@@AEAV?$allocator@PEAUPrefixCrc@CrcCordState@crc_internal@absl@@@__Cr@std@@@__Cr@std@@QEAAXV?$move_iterator@PEAPEAUPrefixCrc@CrcCordState@crc_internal@absl@@@12@0@Z
-    ??$__construct_at_end@V?$move_iterator@PEAUTransition@cctz@time_internal@absl@@@__Cr@std@@@?$__split_buffer@UTransition@cctz@time_internal@absl@@AEAV?$allocator@UTransition@cctz@time_internal@absl@@@__Cr@std@@@__Cr@std@@QEAAXV?$move_iterator@PEAUTransition@cctz@time_internal@absl@@@12@0@Z
-    ??$__construct_at_end@V?$move_iterator@PEAUTransitionType@cctz@time_internal@absl@@@__Cr@std@@@?$__split_buffer@UTransitionType@cctz@time_internal@absl@@AEAV?$allocator@UTransitionType@cctz@time_internal@absl@@@__Cr@std@@@__Cr@std@@QEAAXV?$move_iterator@PEAUTransitionType@cctz@time_internal@absl@@@12@0@Z
+    ??$__append_with_size@V?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEBU1234@AEBU1234@PEBQEBU1234@_J$0A@@__Cr@std@@@?$deque@UPrefixCrc@CrcCordState@crc_internal@absl@@V?$allocator@UPrefixCrc@CrcCordState@crc_internal@absl@@@__Cr@std@@@__Cr@std@@AEAAXV?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEBU1234@AEBU1234@PEBQEBU1234@_J$0A@@12@_K@Z
+    ??$__assign_with_size@PEBVFormatArgImpl@str_format_internal@absl@@PEBV123@@?$vector@VFormatArgImpl@str_format_internal@absl@@V?$allocator@VFormatArgImpl@str_format_internal@absl@@@__Cr@std@@@__Cr@std@@AEAAXPEBVFormatArgImpl@str_format_internal@absl@@0_J@Z
+    ??$__assign_with_size_random_access@V?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEBU1234@AEBU1234@PEBQEBU1234@_J$0A@@__Cr@std@@@?$deque@UPrefixCrc@CrcCordState@crc_internal@absl@@V?$allocator@UPrefixCrc@CrcCordState@crc_internal@absl@@@__Cr@std@@@__Cr@std@@AEAAXV?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEBU1234@AEBU1234@PEBQEBU1234@_J$0A@@12@_J@Z
+    ??$__construct_at_end_with_size@V?$move_iterator@PEAPEAPEBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@@?$__split_buffer@PEAPEBVImpl@time_zone@cctz@time_internal@absl@@AEAV?$allocator@PEAPEBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@@__Cr@std@@QEAAXV?$move_iterator@PEAPEAPEBVImpl@time_zone@cctz@time_internal@absl@@@12@_K@Z
+    ??$__construct_at_end_with_size@V?$move_iterator@PEAPEAUPrefixCrc@CrcCordState@crc_internal@absl@@@__Cr@std@@@?$__split_buffer@PEAUPrefixCrc@CrcCordState@crc_internal@absl@@AEAV?$allocator@PEAUPrefixCrc@CrcCordState@crc_internal@absl@@@__Cr@std@@@__Cr@std@@QEAAXV?$move_iterator@PEAPEAUPrefixCrc@CrcCordState@crc_internal@absl@@@12@_K@Z
+    ??$__construct_at_end_with_size@V?$move_iterator@PEAUTransition@cctz@time_internal@absl@@@__Cr@std@@@?$__split_buffer@UTransition@cctz@time_internal@absl@@AEAV?$allocator@UTransition@cctz@time_internal@absl@@@__Cr@std@@@__Cr@std@@QEAAXV?$move_iterator@PEAUTransition@cctz@time_internal@absl@@@12@_K@Z
+    ??$__construct_at_end_with_size@V?$move_iterator@PEAUTransitionType@cctz@time_internal@absl@@@__Cr@std@@@?$__split_buffer@UTransitionType@cctz@time_internal@absl@@AEAV?$allocator@UTransitionType@cctz@time_internal@absl@@@__Cr@std@@@__Cr@std@@QEAAXV?$move_iterator@PEAUTransitionType@cctz@time_internal@absl@@@12@_K@Z
     ??$__destroy_at@U?$pair@$$CBV?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PEBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@$0A@@__Cr@std@@YAXPEAU?$pair@$$CBV?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PEBVImpl@time_zone@cctz@time_internal@absl@@@01@@Z
     ??$__destroy_at@UPayload@status_internal@absl@@$0A@@__Cr@std@@YAXPEAUPayload@status_internal@absl@@@Z
     ??$__do_rehash@$00@?$__hash_table@U?$__hash_value_type@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PEBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@V?$__unordered_map_hasher@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@U?$__hash_value_type@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PEBVImpl@time_zone@cctz@time_internal@absl@@@23@U?$hash@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@23@U?$equal_to@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@23@$00@23@V?$__unordered_map_equal@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@U?$__hash_value_type@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PEBVImpl@time_zone@cctz@time_internal@absl@@@23@U?$equal_to@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@23@U?$hash@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@23@$00@23@V?$allocator@U?$__hash_value_type@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PEBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@@23@@__Cr@std@@AEAAX_K@Z
     ??$__emplace_unique_key_args@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@AEBUpiecewise_construct_t@23@V?$tuple@AEBV?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@23@V?$tuple@$$V@23@@?$__hash_table@U?$__hash_value_type@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PEBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@V?$__unordered_map_hasher@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@U?$__hash_value_type@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PEBVImpl@time_zone@cctz@time_internal@absl@@@23@U?$hash@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@23@U?$equal_to@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@23@$00@23@V?$__unordered_map_equal@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@U?$__hash_value_type@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PEBVImpl@time_zone@cctz@time_internal@absl@@@23@U?$equal_to@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@23@U?$hash@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@23@$00@23@V?$allocator@U?$__hash_value_type@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PEBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@@23@@__Cr@std@@QEAA?AU?$pair@V?$__hash_iterator@PEAU?$__hash_node@U?$__hash_value_type@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PEBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@PEAX@__Cr@std@@@__Cr@std@@_N@12@AEBV?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@12@AEBUpiecewise_construct_t@12@$$QEAV?$tuple@AEBV?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@12@$$QEAV?$tuple@$$V@12@@Z
     ??$__rehash@$00@?$__hash_table@U?$__hash_value_type@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PEBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@V?$__unordered_map_hasher@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@U?$__hash_value_type@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PEBVImpl@time_zone@cctz@time_internal@absl@@@23@U?$hash@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@23@U?$equal_to@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@23@$00@23@V?$__unordered_map_equal@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@U?$__hash_value_type@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PEBVImpl@time_zone@cctz@time_internal@absl@@@23@U?$equal_to@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@23@U?$hash@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@23@$00@23@V?$allocator@U?$__hash_value_type@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PEBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@@23@@__Cr@std@@AEAAX_K@Z
     ??$__upper_bound@U_ClassicAlgPolicy@__Cr@std@@UByCivilTime@Transition@cctz@time_internal@absl@@PEBU5678@PEBU5678@U5678@U__identity@23@@__Cr@std@@YAPEBUTransition@cctz@time_internal@absl@@PEBU2345@0AEBU2345@$$QEAUByCivilTime@2345@$$QEAU__identity@01@@Z
-    ??$assign@PEBVFormatArgImpl@str_format_internal@absl@@$0A@@?$vector@VFormatArgImpl@str_format_internal@absl@@V?$allocator@VFormatArgImpl@str_format_internal@absl@@@__Cr@std@@@__Cr@std@@QEAAXPEBVFormatArgImpl@str_format_internal@absl@@0@Z
     ??$assign@V?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEBU1234@AEBU1234@PEBQEBU1234@_J$0A@@__Cr@std@@@?$deque@UPrefixCrc@CrcCordState@crc_internal@absl@@V?$allocator@UPrefixCrc@CrcCordState@crc_internal@absl@@@__Cr@std@@@__Cr@std@@QEAAXV?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEBU1234@AEBU1234@PEBQEBU1234@_J$0A@@12@0PEAX@Z
     ??$assign@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@?$optional_data_base@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@optional_internal@absl@@IEAAX$$QEAV?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@Z
     ??$combine@V?$basic_string_view@DU?$char_traits@D@__Cr@std@@@__Cr@std@@H@?$HashStateBase@VMixingHashState@hash_internal@absl@@@hash_internal@absl@@SA?AVMixingHashState@12@V312@AEBV?$basic_string_view@DU?$char_traits@D@__Cr@std@@@__Cr@std@@AEBH@Z
diff --git a/third_party/abseil-cpp/symbols_x64_dbg.def b/third_party/abseil-cpp/symbols_x64_dbg.def
index e416166..97e28070 100644
--- a/third_party/abseil-cpp/symbols_x64_dbg.def
+++ b/third_party/abseil-cpp/symbols_x64_dbg.def
@@ -683,12 +683,18 @@
     ??$__allocate_at_least@V?$allocator@UTransitionType@cctz@time_internal@absl@@@__Cr@std@@@__Cr@std@@YA?AU?$__allocation_result@PEAUTransitionType@cctz@time_internal@absl@@@01@AEAV?$allocator@UTransitionType@cctz@time_internal@absl@@@01@_K@Z
     ??$__allocate_at_least@V?$allocator@UViableSubstitution@strings_internal@absl@@@__Cr@std@@@__Cr@std@@YA?AU?$__allocation_result@PEAUViableSubstitution@strings_internal@absl@@@01@AEAV?$allocator@UViableSubstitution@strings_internal@absl@@@01@_K@Z
     ??$__allocate_at_least@V?$allocator@VFormatArgImpl@str_format_internal@absl@@@__Cr@std@@@__Cr@std@@YA?AU?$__allocation_result@PEAVFormatArgImpl@str_format_internal@absl@@@01@AEAV?$allocator@VFormatArgImpl@str_format_internal@absl@@@01@_K@Z
-    ??$__append@V?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEBU1234@AEBU1234@PEBQEBU1234@_J$0A@@__Cr@std@@@?$deque@UPrefixCrc@CrcCordState@crc_internal@absl@@V?$allocator@UPrefixCrc@CrcCordState@crc_internal@absl@@@__Cr@std@@@__Cr@std@@AEAAXV?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEBU1234@AEBU1234@PEBQEBU1234@_J$0A@@12@0PEAX@Z
-    ??$__construct_at_end@PEBVFormatArgImpl@str_format_internal@absl@@$0A@@?$vector@VFormatArgImpl@str_format_internal@absl@@V?$allocator@VFormatArgImpl@str_format_internal@absl@@@__Cr@std@@@__Cr@std@@AEAAXPEBVFormatArgImpl@str_format_internal@absl@@0_K@Z
+    ??$__append_with_size@V?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEBU1234@AEBU1234@PEBQEBU1234@_J$0A@@__Cr@std@@@?$deque@UPrefixCrc@CrcCordState@crc_internal@absl@@V?$allocator@UPrefixCrc@CrcCordState@crc_internal@absl@@@__Cr@std@@@__Cr@std@@AEAAXV?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEBU1234@AEBU1234@PEBQEBU1234@_J$0A@@12@_K@Z
+    ??$__assign_with_size@PEBVFormatArgImpl@str_format_internal@absl@@PEBV123@@?$vector@VFormatArgImpl@str_format_internal@absl@@V?$allocator@VFormatArgImpl@str_format_internal@absl@@@__Cr@std@@@__Cr@std@@AEAAXPEBVFormatArgImpl@str_format_internal@absl@@0_J@Z
+    ??$__assign_with_size_random_access@V?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEBU1234@AEBU1234@PEBQEBU1234@_J$0A@@__Cr@std@@@?$deque@UPrefixCrc@CrcCordState@crc_internal@absl@@V?$allocator@UPrefixCrc@CrcCordState@crc_internal@absl@@@__Cr@std@@@__Cr@std@@AEAAXV?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEBU1234@AEBU1234@PEBQEBU1234@_J$0A@@12@_J@Z
+    ??$__construct_at_end@PEBVFormatArgImpl@str_format_internal@absl@@PEBV123@@?$vector@VFormatArgImpl@str_format_internal@absl@@V?$allocator@VFormatArgImpl@str_format_internal@absl@@@__Cr@std@@@__Cr@std@@AEAAXPEBVFormatArgImpl@str_format_internal@absl@@0_K@Z
     ??$__construct_at_end@V?$move_iterator@PEAPEAPEBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@@?$__split_buffer@PEAPEBVImpl@time_zone@cctz@time_internal@absl@@AEAV?$allocator@PEAPEBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@@__Cr@std@@QEAAXV?$move_iterator@PEAPEAPEBVImpl@time_zone@cctz@time_internal@absl@@@12@0@Z
     ??$__construct_at_end@V?$move_iterator@PEAPEAUPrefixCrc@CrcCordState@crc_internal@absl@@@__Cr@std@@@?$__split_buffer@PEAUPrefixCrc@CrcCordState@crc_internal@absl@@AEAV?$allocator@PEAUPrefixCrc@CrcCordState@crc_internal@absl@@@__Cr@std@@@__Cr@std@@QEAAXV?$move_iterator@PEAPEAUPrefixCrc@CrcCordState@crc_internal@absl@@@12@0@Z
     ??$__construct_at_end@V?$move_iterator@PEAUTransition@cctz@time_internal@absl@@@__Cr@std@@@?$__split_buffer@UTransition@cctz@time_internal@absl@@AEAV?$allocator@UTransition@cctz@time_internal@absl@@@__Cr@std@@@__Cr@std@@QEAAXV?$move_iterator@PEAUTransition@cctz@time_internal@absl@@@12@0@Z
     ??$__construct_at_end@V?$move_iterator@PEAUTransitionType@cctz@time_internal@absl@@@__Cr@std@@@?$__split_buffer@UTransitionType@cctz@time_internal@absl@@AEAV?$allocator@UTransitionType@cctz@time_internal@absl@@@__Cr@std@@@__Cr@std@@QEAAXV?$move_iterator@PEAUTransitionType@cctz@time_internal@absl@@@12@0@Z
+    ??$__construct_at_end_with_size@V?$move_iterator@PEAPEAPEBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@@?$__split_buffer@PEAPEBVImpl@time_zone@cctz@time_internal@absl@@AEAV?$allocator@PEAPEBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@@__Cr@std@@QEAAXV?$move_iterator@PEAPEAPEBVImpl@time_zone@cctz@time_internal@absl@@@12@_K@Z
+    ??$__construct_at_end_with_size@V?$move_iterator@PEAPEAUPrefixCrc@CrcCordState@crc_internal@absl@@@__Cr@std@@@?$__split_buffer@PEAUPrefixCrc@CrcCordState@crc_internal@absl@@AEAV?$allocator@PEAUPrefixCrc@CrcCordState@crc_internal@absl@@@__Cr@std@@@__Cr@std@@QEAAXV?$move_iterator@PEAPEAUPrefixCrc@CrcCordState@crc_internal@absl@@@12@_K@Z
+    ??$__construct_at_end_with_size@V?$move_iterator@PEAUTransition@cctz@time_internal@absl@@@__Cr@std@@@?$__split_buffer@UTransition@cctz@time_internal@absl@@AEAV?$allocator@UTransition@cctz@time_internal@absl@@@__Cr@std@@@__Cr@std@@QEAAXV?$move_iterator@PEAUTransition@cctz@time_internal@absl@@@12@_K@Z
+    ??$__construct_at_end_with_size@V?$move_iterator@PEAUTransitionType@cctz@time_internal@absl@@@__Cr@std@@@?$__split_buffer@UTransitionType@cctz@time_internal@absl@@AEAV?$allocator@UTransitionType@cctz@time_internal@absl@@@__Cr@std@@@__Cr@std@@QEAAXV?$move_iterator@PEAUTransitionType@cctz@time_internal@absl@@@12@_K@Z
     ??$__construct_node_hash@AEBUpiecewise_construct_t@__Cr@std@@V?$tuple@AEBV?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@23@V?$tuple@$$V@23@@?$__hash_table@U?$__hash_value_type@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PEBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@V?$__unordered_map_hasher@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@U?$__hash_value_type@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PEBVImpl@time_zone@cctz@time_internal@absl@@@23@U?$hash@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@23@U?$equal_to@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@23@$00@23@V?$__unordered_map_equal@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@U?$__hash_value_type@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PEBVImpl@time_zone@cctz@time_internal@absl@@@23@U?$equal_to@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@23@U?$hash@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@23@$00@23@V?$allocator@U?$__hash_value_type@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PEBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@@23@@__Cr@std@@AEAA?AV?$unique_ptr@U?$__hash_node@U?$__hash_value_type@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PEBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@PEAX@__Cr@std@@V?$__hash_node_destructor@V?$allocator@U?$__hash_node@U?$__hash_value_type@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PEBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@PEAX@__Cr@std@@@__Cr@std@@@23@@12@_KAEBUpiecewise_construct_t@12@$$QEAV?$tuple@AEBV?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@12@$$QEAV?$tuple@$$V@12@@Z
     ??$__construct_one_at_end@$$V@?$vector@UTransition@cctz@time_internal@absl@@V?$allocator@UTransition@cctz@time_internal@absl@@@__Cr@std@@@__Cr@std@@AEAAXXZ
     ??$__construct_one_at_end@$$V@?$vector@UTransitionType@cctz@time_internal@absl@@V?$allocator@UTransitionType@cctz@time_internal@absl@@@__Cr@std@@@__Cr@std@@AEAAXXZ
@@ -788,7 +794,6 @@
     ??$__distance@PEBUPayload@status_internal@absl@@@__Cr@std@@YA_JPEBUPayload@status_internal@absl@@0Urandom_access_iterator_tag@01@@Z
     ??$__distance@PEBUTransition@cctz@time_internal@absl@@@__Cr@std@@YA_JPEBUTransition@cctz@time_internal@absl@@0Urandom_access_iterator_tag@01@@Z
     ??$__distance@PEBVFormatArgImpl@str_format_internal@absl@@@__Cr@std@@YA_JPEBVFormatArgImpl@str_format_internal@absl@@0Urandom_access_iterator_tag@01@@Z
-    ??$__distance@V?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEBU1234@AEBU1234@PEBQEBU1234@_J$0A@@__Cr@std@@@__Cr@std@@YA_JV?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEBU1234@AEBU1234@PEBQEBU1234@_J$0A@@01@0Urandom_access_iterator_tag@01@@Z
     ??$__distance@V?$move_iterator@PEAPEAPEBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@@__Cr@std@@YA_JV?$move_iterator@PEAPEAPEBVImpl@time_zone@cctz@time_internal@absl@@@01@0Urandom_access_iterator_tag@01@@Z
     ??$__distance@V?$move_iterator@PEAPEAUPrefixCrc@CrcCordState@crc_internal@absl@@@__Cr@std@@@__Cr@std@@YA_JV?$move_iterator@PEAPEAUPrefixCrc@CrcCordState@crc_internal@absl@@@01@0Urandom_access_iterator_tag@01@@Z
     ??$__distance@V?$move_iterator@PEAUTransition@cctz@time_internal@absl@@@__Cr@std@@@__Cr@std@@YA_JV?$move_iterator@PEAUTransition@cctz@time_internal@absl@@@01@0Urandom_access_iterator_tag@01@@Z
@@ -951,7 +956,7 @@
     ??$__validate_iter_reference@AEAV?$reverse_iterator@PEAUViableSubstitution@strings_internal@absl@@@__Cr@std@@@?$_IterOps@U_ClassicAlgPolicy@__Cr@std@@@__Cr@std@@SAXXZ
     ??$advance@PEBUTransition@cctz@time_internal@absl@@_J@?$_IterOps@U_ClassicAlgPolicy@__Cr@std@@@__Cr@std@@SAXAEAPEBUTransition@cctz@time_internal@absl@@_J@Z
     ??$advance@PEBUTransition@cctz@time_internal@absl@@_J_JX@__Cr@std@@YAXAEAPEBUTransition@cctz@time_internal@absl@@_J@Z
-    ??$advance@PEBVFormatArgImpl@str_format_internal@absl@@_K_KX@__Cr@std@@YAXAEAPEBVFormatArgImpl@str_format_internal@absl@@_K@Z
+    ??$advance@PEBVFormatArgImpl@str_format_internal@absl@@_J_JX@__Cr@std@@YAXAEAPEBVFormatArgImpl@str_format_internal@absl@@_J@Z
     ??$assign@PEBVFormatArgImpl@str_format_internal@absl@@$0A@@?$vector@VFormatArgImpl@str_format_internal@absl@@V?$allocator@VFormatArgImpl@str_format_internal@absl@@@__Cr@std@@@__Cr@std@@QEAAXPEBVFormatArgImpl@str_format_internal@absl@@0@Z
     ??$assign@V?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEBU1234@AEBU1234@PEBQEBU1234@_J$0A@@__Cr@std@@@?$deque@UPrefixCrc@CrcCordState@crc_internal@absl@@V?$allocator@UPrefixCrc@CrcCordState@crc_internal@absl@@@__Cr@std@@@__Cr@std@@QEAAXV?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEBU1234@AEBU1234@PEBQEBU1234@_J$0A@@12@0PEAX@Z
     ??$assign@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@?$optional_data_base@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@optional_internal@absl@@IEAAX$$QEAV?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@Z
@@ -1013,6 +1018,7 @@
     ??$construct_at@UViableSubstitution@strings_internal@absl@@AEAV?$basic_string_view@DU?$char_traits@D@__Cr@std@@@__Cr@std@@AEBV456@AEA_KPEAU123@@__Cr@std@@YAPEAUViableSubstitution@strings_internal@absl@@PEAU234@AEAV?$basic_string_view@DU?$char_traits@D@__Cr@std@@@01@AEBV501@AEA_K@Z
     ??$copy@PEBVFormatArgImpl@str_format_internal@absl@@PEAV123@@__Cr@std@@YAPEAVFormatArgImpl@str_format_internal@absl@@PEBV234@0PEAV234@@Z
     ??$copy@V?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEBU1234@AEBU1234@PEBQEBU1234@_J$0A@@__Cr@std@@V?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEAU1234@AEAU1234@PEAPEAU1234@_J$0A@@23@@__Cr@std@@YA?AV?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEAU1234@AEAU1234@PEAPEAU1234@_J$0A@@01@V?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEBU1234@AEBU1234@PEBQEBU1234@_J$0A@@01@0V201@@Z
+    ??$copy_n@V?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEBU1234@AEBU1234@PEBQEBU1234@_J$0A@@__Cr@std@@_JV?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEAU1234@AEAU1234@PEAPEAU1234@_J$0A@@23@@__Cr@std@@YA?AV?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEAU1234@AEAU1234@PEAPEAU1234@_J$0A@@01@V?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEBU1234@AEBU1234@PEBQEBU1234@_J$0A@@01@_JV201@@Z
     ??$countl_zero@I@absl@@YAHI@Z
     ??$countl_zero@_K@absl@@YAH_K@Z
     ??$countr_zero@I@absl@@YAHI@Z
@@ -1049,7 +1055,6 @@
     ??$distance@PEBUTransition@cctz@time_internal@absl@@@?$_IterOps@U_ClassicAlgPolicy@__Cr@std@@@__Cr@std@@SA_JPEBUTransition@cctz@time_internal@absl@@0@Z
     ??$distance@PEBUTransition@cctz@time_internal@absl@@@__Cr@std@@YA_JPEBUTransition@cctz@time_internal@absl@@0@Z
     ??$distance@PEBVFormatArgImpl@str_format_internal@absl@@@__Cr@std@@YA_JPEBVFormatArgImpl@str_format_internal@absl@@0@Z
-    ??$distance@V?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEBU1234@AEBU1234@PEBQEBU1234@_J$0A@@__Cr@std@@@__Cr@std@@YA_JV?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEBU1234@AEBU1234@PEBQEBU1234@_J$0A@@01@0@Z
     ??$distance@V?$move_iterator@PEAPEAPEBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@@__Cr@std@@YA_JV?$move_iterator@PEAPEAPEBVImpl@time_zone@cctz@time_internal@absl@@@01@0@Z
     ??$distance@V?$move_iterator@PEAPEAUPrefixCrc@CrcCordState@crc_internal@absl@@@__Cr@std@@@__Cr@std@@YA_JV?$move_iterator@PEAPEAUPrefixCrc@CrcCordState@crc_internal@absl@@@01@0@Z
     ??$distance@V?$move_iterator@PEAUTransition@cctz@time_internal@absl@@@__Cr@std@@@__Cr@std@@YA_JV?$move_iterator@PEAUTransition@cctz@time_internal@absl@@@01@0@Z
@@ -1184,6 +1189,7 @@
     ??$move_backward@PEAUTransitionType@cctz@time_internal@absl@@PEAU1234@@__Cr@std@@YAPEAUTransitionType@cctz@time_internal@absl@@PEAU2345@00@Z
     ??$next@AEAPEBUTransition@cctz@time_internal@absl@@@?$_IterOps@U_ClassicAlgPolicy@__Cr@std@@@__Cr@std@@SAPEBUTransition@cctz@time_internal@absl@@AEAPEBU3456@_J@Z
     ??$next@PEBUTransition@cctz@time_internal@absl@@@__Cr@std@@YAPEBUTransition@cctz@time_internal@absl@@PEBU2345@_J@Z
+    ??$next@PEBVFormatArgImpl@str_format_internal@absl@@@__Cr@std@@YAPEBVFormatArgImpl@str_format_internal@absl@@PEBV234@_J@Z
     ??$reset@PEAPEAU?$__hash_node_base@PEAU?$__hash_node@U?$__hash_value_type@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PEBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@PEAX@__Cr@std@@@__Cr@std@@@?$unique_ptr@$$BY0A@PEAU?$__hash_node_base@PEAU?$__hash_node@U?$__hash_value_type@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PEBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@PEAX@__Cr@std@@@__Cr@std@@V?$__bucket_list_deallocator@V?$allocator@PEAU?$__hash_node_base@PEAU?$__hash_node@U?$__hash_value_type@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PEBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@PEAX@__Cr@std@@@__Cr@std@@@__Cr@std@@@23@@__Cr@std@@QEAAXPEAPEAU?$__hash_node_base@PEAU?$__hash_node@U?$__hash_value_type@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PEBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@PEAX@__Cr@std@@@12@@Z
     ??$rotr@I@absl@@YAIIH@Z
     ??$rotr@_K@absl@@YA_K_KH@Z
diff --git a/third_party/abseil-cpp/symbols_x64_rel.def b/third_party/abseil-cpp/symbols_x64_rel.def
index e6f0436..ccf52cf 100644
--- a/third_party/abseil-cpp/symbols_x64_rel.def
+++ b/third_party/abseil-cpp/symbols_x64_rel.def
@@ -138,11 +138,13 @@
     ??$StrFormat@DHHHHH_JIV?$basic_string_view@DU?$char_traits@D@__Cr@std@@@__Cr@std@@HPEBDV123@@absl@@YA?AV?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@AEBV?$FormatSpecTemplate@$0BPPPL@$0JPPPL@$0JPPPL@$0JPPPL@$0JPPPL@$0JPPPL@$0JPPPL@$0JPPPL@$0IAAAE@$0JPPPL@$0EAAAE@$0IAAAE@@str_format_internal@0@AEBDAEBH2222AEB_JAEBIAEBV?$basic_string_view@DU?$char_traits@D@__Cr@std@@@23@2AEBQEBD5@Z
     ??$StrReplaceAll@V?$initializer_list@U?$pair@V?$basic_string_view@DU?$char_traits@D@__Cr@std@@@__Cr@std@@V123@@__Cr@std@@@std@@@absl@@YA?AV?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@V?$basic_string_view@DU?$char_traits@D@__Cr@std@@@23@AEBV?$initializer_list@U?$pair@V?$basic_string_view@DU?$char_traits@D@__Cr@std@@@__Cr@std@@V123@@__Cr@std@@@3@@Z
     ??$StrReplaceAll@V?$initializer_list@U?$pair@V?$basic_string_view@DU?$char_traits@D@__Cr@std@@@__Cr@std@@V123@@__Cr@std@@@std@@@absl@@YAHAEBV?$initializer_list@U?$pair@V?$basic_string_view@DU?$char_traits@D@__Cr@std@@@__Cr@std@@V123@@__Cr@std@@@std@@PEAV?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@2@@Z
-    ??$__append@V?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEBU1234@AEBU1234@PEBQEBU1234@_J$0A@@__Cr@std@@@?$deque@UPrefixCrc@CrcCordState@crc_internal@absl@@V?$allocator@UPrefixCrc@CrcCordState@crc_internal@absl@@@__Cr@std@@@__Cr@std@@AEAAXV?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEBU1234@AEBU1234@PEBQEBU1234@_J$0A@@12@0PEAX@Z
-    ??$__construct_at_end@V?$move_iterator@PEAPEAPEBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@@?$__split_buffer@PEAPEBVImpl@time_zone@cctz@time_internal@absl@@AEAV?$allocator@PEAPEBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@@__Cr@std@@QEAAXV?$move_iterator@PEAPEAPEBVImpl@time_zone@cctz@time_internal@absl@@@12@0@Z
-    ??$__construct_at_end@V?$move_iterator@PEAPEAUPrefixCrc@CrcCordState@crc_internal@absl@@@__Cr@std@@@?$__split_buffer@PEAUPrefixCrc@CrcCordState@crc_internal@absl@@AEAV?$allocator@PEAUPrefixCrc@CrcCordState@crc_internal@absl@@@__Cr@std@@@__Cr@std@@QEAAXV?$move_iterator@PEAPEAUPrefixCrc@CrcCordState@crc_internal@absl@@@12@0@Z
-    ??$__construct_at_end@V?$move_iterator@PEAUTransition@cctz@time_internal@absl@@@__Cr@std@@@?$__split_buffer@UTransition@cctz@time_internal@absl@@AEAV?$allocator@UTransition@cctz@time_internal@absl@@@__Cr@std@@@__Cr@std@@QEAAXV?$move_iterator@PEAUTransition@cctz@time_internal@absl@@@12@0@Z
-    ??$__construct_at_end@V?$move_iterator@PEAUTransitionType@cctz@time_internal@absl@@@__Cr@std@@@?$__split_buffer@UTransitionType@cctz@time_internal@absl@@AEAV?$allocator@UTransitionType@cctz@time_internal@absl@@@__Cr@std@@@__Cr@std@@QEAAXV?$move_iterator@PEAUTransitionType@cctz@time_internal@absl@@@12@0@Z
+    ??$__append_with_size@V?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEBU1234@AEBU1234@PEBQEBU1234@_J$0A@@__Cr@std@@@?$deque@UPrefixCrc@CrcCordState@crc_internal@absl@@V?$allocator@UPrefixCrc@CrcCordState@crc_internal@absl@@@__Cr@std@@@__Cr@std@@AEAAXV?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEBU1234@AEBU1234@PEBQEBU1234@_J$0A@@12@_K@Z
+    ??$__assign_with_size@PEBVFormatArgImpl@str_format_internal@absl@@PEBV123@@?$vector@VFormatArgImpl@str_format_internal@absl@@V?$allocator@VFormatArgImpl@str_format_internal@absl@@@__Cr@std@@@__Cr@std@@AEAAXPEBVFormatArgImpl@str_format_internal@absl@@0_J@Z
+    ??$__assign_with_size_random_access@V?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEBU1234@AEBU1234@PEBQEBU1234@_J$0A@@__Cr@std@@@?$deque@UPrefixCrc@CrcCordState@crc_internal@absl@@V?$allocator@UPrefixCrc@CrcCordState@crc_internal@absl@@@__Cr@std@@@__Cr@std@@AEAAXV?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEBU1234@AEBU1234@PEBQEBU1234@_J$0A@@12@_J@Z
+    ??$__construct_at_end_with_size@V?$move_iterator@PEAPEAPEBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@@?$__split_buffer@PEAPEBVImpl@time_zone@cctz@time_internal@absl@@AEAV?$allocator@PEAPEBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@@__Cr@std@@QEAAXV?$move_iterator@PEAPEAPEBVImpl@time_zone@cctz@time_internal@absl@@@12@_K@Z
+    ??$__construct_at_end_with_size@V?$move_iterator@PEAPEAUPrefixCrc@CrcCordState@crc_internal@absl@@@__Cr@std@@@?$__split_buffer@PEAUPrefixCrc@CrcCordState@crc_internal@absl@@AEAV?$allocator@PEAUPrefixCrc@CrcCordState@crc_internal@absl@@@__Cr@std@@@__Cr@std@@QEAAXV?$move_iterator@PEAPEAUPrefixCrc@CrcCordState@crc_internal@absl@@@12@_K@Z
+    ??$__construct_at_end_with_size@V?$move_iterator@PEAUTransition@cctz@time_internal@absl@@@__Cr@std@@@?$__split_buffer@UTransition@cctz@time_internal@absl@@AEAV?$allocator@UTransition@cctz@time_internal@absl@@@__Cr@std@@@__Cr@std@@QEAAXV?$move_iterator@PEAUTransition@cctz@time_internal@absl@@@12@_K@Z
+    ??$__construct_at_end_with_size@V?$move_iterator@PEAUTransitionType@cctz@time_internal@absl@@@__Cr@std@@@?$__split_buffer@UTransitionType@cctz@time_internal@absl@@AEAV?$allocator@UTransitionType@cctz@time_internal@absl@@@__Cr@std@@@__Cr@std@@QEAAXV?$move_iterator@PEAUTransitionType@cctz@time_internal@absl@@@12@_K@Z
     ??$__construct_one_at_end@AEAV?$basic_string_view@DU?$char_traits@D@__Cr@std@@@__Cr@std@@AEBV123@AEA_K@?$vector@UViableSubstitution@strings_internal@absl@@V?$allocator@UViableSubstitution@strings_internal@absl@@@__Cr@std@@@__Cr@std@@AEAAXAEAV?$basic_string_view@DU?$char_traits@D@__Cr@std@@@12@AEBV312@AEA_K@Z
     ??$__destroy_at@U?$pair@$$CBV?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PEBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@$0A@@__Cr@std@@YAXPEAU?$pair@$$CBV?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PEBVImpl@time_zone@cctz@time_internal@absl@@@01@@Z
     ??$__destroy_at@UPayload@status_internal@absl@@$0A@@__Cr@std@@YAXPEAUPayload@status_internal@absl@@@Z
@@ -157,7 +159,6 @@
     ??$__rehash@$00@?$__hash_table@U?$__hash_value_type@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PEBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@V?$__unordered_map_hasher@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@U?$__hash_value_type@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PEBVImpl@time_zone@cctz@time_internal@absl@@@23@U?$hash@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@23@U?$equal_to@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@23@$00@23@V?$__unordered_map_equal@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@U?$__hash_value_type@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PEBVImpl@time_zone@cctz@time_internal@absl@@@23@U?$equal_to@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@23@U?$hash@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@23@$00@23@V?$allocator@U?$__hash_value_type@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PEBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@@23@@__Cr@std@@AEAAX_K@Z
     ??$__unwrap_and_dispatch@U?$__overload@U?$__copy_loop@U_ClassicAlgPolicy@__Cr@std@@@__Cr@std@@U__copy_trivial@23@@__Cr@std@@PEBUPrefixCrc@CrcCordState@crc_internal@absl@@PEBU4567@V?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEAU1234@AEAU1234@PEAPEAU1234@_J$0A@@23@$0A@@__Cr@std@@YA?AU?$pair@PEBUPrefixCrc@CrcCordState@crc_internal@absl@@V?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEAU1234@AEAU1234@PEAPEAU1234@_J$0A@@__Cr@std@@@01@PEBUPrefixCrc@CrcCordState@crc_internal@absl@@0V?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEAU1234@AEAU1234@PEAPEAU1234@_J$0A@@01@@Z
     ??$__upper_bound@U_ClassicAlgPolicy@__Cr@std@@UByCivilTime@Transition@cctz@time_internal@absl@@PEBU5678@PEBU5678@U5678@U__identity@23@@__Cr@std@@YAPEBUTransition@cctz@time_internal@absl@@PEBU2345@0AEBU2345@$$QEAUByCivilTime@2345@$$QEAU__identity@01@@Z
-    ??$assign@PEBVFormatArgImpl@str_format_internal@absl@@$0A@@?$vector@VFormatArgImpl@str_format_internal@absl@@V?$allocator@VFormatArgImpl@str_format_internal@absl@@@__Cr@std@@@__Cr@std@@QEAAXPEBVFormatArgImpl@str_format_internal@absl@@0@Z
     ??$assign@V?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEBU1234@AEBU1234@PEBQEBU1234@_J$0A@@__Cr@std@@@?$deque@UPrefixCrc@CrcCordState@crc_internal@absl@@V?$allocator@UPrefixCrc@CrcCordState@crc_internal@absl@@@__Cr@std@@@__Cr@std@@QEAAXV?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEBU1234@AEBU1234@PEBQEBU1234@_J$0A@@12@0PEAX@Z
     ??$assign@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@?$optional_data_base@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@optional_internal@absl@@IEAAX$$QEAV?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@Z
     ??$construct_at@U?$pair@$$CBV?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PEBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@AEBUpiecewise_construct_t@23@V?$tuple@AEBV?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@23@V?$tuple@$$V@23@PEAU123@@__Cr@std@@YAPEAU?$pair@$$CBV?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PEBVImpl@time_zone@cctz@time_internal@absl@@@01@PEAU201@AEBUpiecewise_construct_t@01@$$QEAV?$tuple@AEBV?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@01@$$QEAV?$tuple@$$V@01@@Z
diff --git a/third_party/abseil-cpp/symbols_x64_rel_asan.def b/third_party/abseil-cpp/symbols_x64_rel_asan.def
index 9856f9a3..276c597d 100644
--- a/third_party/abseil-cpp/symbols_x64_rel_asan.def
+++ b/third_party/abseil-cpp/symbols_x64_rel_asan.def
@@ -137,12 +137,14 @@
     ??$StrFormat@DHHHHH_JIV?$basic_string_view@DU?$char_traits@D@__Cr@std@@@__Cr@std@@HPEBDV123@@absl@@YA?AV?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@AEBV?$FormatSpecTemplate@$0BPPPL@$0JPPPL@$0JPPPL@$0JPPPL@$0JPPPL@$0JPPPL@$0JPPPL@$0JPPPL@$0IAAAE@$0JPPPL@$0EAAAE@$0IAAAE@@str_format_internal@0@AEBDAEBH2222AEB_JAEBIAEBV?$basic_string_view@DU?$char_traits@D@__Cr@std@@@23@2AEBQEBD5@Z
     ??$StrReplaceAll@V?$initializer_list@U?$pair@V?$basic_string_view@DU?$char_traits@D@__Cr@std@@@__Cr@std@@V123@@__Cr@std@@@std@@@absl@@YA?AV?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@V?$basic_string_view@DU?$char_traits@D@__Cr@std@@@23@AEBV?$initializer_list@U?$pair@V?$basic_string_view@DU?$char_traits@D@__Cr@std@@@__Cr@std@@V123@@__Cr@std@@@3@@Z
     ??$StrReplaceAll@V?$initializer_list@U?$pair@V?$basic_string_view@DU?$char_traits@D@__Cr@std@@@__Cr@std@@V123@@__Cr@std@@@std@@@absl@@YAHAEBV?$initializer_list@U?$pair@V?$basic_string_view@DU?$char_traits@D@__Cr@std@@@__Cr@std@@V123@@__Cr@std@@@std@@PEAV?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@2@@Z
-    ??$__append@V?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEBU1234@AEBU1234@PEBQEBU1234@_J$0A@@__Cr@std@@@?$deque@UPrefixCrc@CrcCordState@crc_internal@absl@@V?$allocator@UPrefixCrc@CrcCordState@crc_internal@absl@@@__Cr@std@@@__Cr@std@@AEAAXV?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEBU1234@AEBU1234@PEBQEBU1234@_J$0A@@12@0PEAX@Z
-    ??$__construct_at_end@PEBVFormatArgImpl@str_format_internal@absl@@$0A@@?$vector@VFormatArgImpl@str_format_internal@absl@@V?$allocator@VFormatArgImpl@str_format_internal@absl@@@__Cr@std@@@__Cr@std@@AEAAXPEBVFormatArgImpl@str_format_internal@absl@@0_K@Z
-    ??$__construct_at_end@V?$move_iterator@PEAPEAPEBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@@?$__split_buffer@PEAPEBVImpl@time_zone@cctz@time_internal@absl@@AEAV?$allocator@PEAPEBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@@__Cr@std@@QEAAXV?$move_iterator@PEAPEAPEBVImpl@time_zone@cctz@time_internal@absl@@@12@0@Z
-    ??$__construct_at_end@V?$move_iterator@PEAPEAUPrefixCrc@CrcCordState@crc_internal@absl@@@__Cr@std@@@?$__split_buffer@PEAUPrefixCrc@CrcCordState@crc_internal@absl@@AEAV?$allocator@PEAUPrefixCrc@CrcCordState@crc_internal@absl@@@__Cr@std@@@__Cr@std@@QEAAXV?$move_iterator@PEAPEAUPrefixCrc@CrcCordState@crc_internal@absl@@@12@0@Z
-    ??$__construct_at_end@V?$move_iterator@PEAUTransition@cctz@time_internal@absl@@@__Cr@std@@@?$__split_buffer@UTransition@cctz@time_internal@absl@@AEAV?$allocator@UTransition@cctz@time_internal@absl@@@__Cr@std@@@__Cr@std@@QEAAXV?$move_iterator@PEAUTransition@cctz@time_internal@absl@@@12@0@Z
-    ??$__construct_at_end@V?$move_iterator@PEAUTransitionType@cctz@time_internal@absl@@@__Cr@std@@@?$__split_buffer@UTransitionType@cctz@time_internal@absl@@AEAV?$allocator@UTransitionType@cctz@time_internal@absl@@@__Cr@std@@@__Cr@std@@QEAAXV?$move_iterator@PEAUTransitionType@cctz@time_internal@absl@@@12@0@Z
+    ??$__append_with_size@V?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEBU1234@AEBU1234@PEBQEBU1234@_J$0A@@__Cr@std@@@?$deque@UPrefixCrc@CrcCordState@crc_internal@absl@@V?$allocator@UPrefixCrc@CrcCordState@crc_internal@absl@@@__Cr@std@@@__Cr@std@@AEAAXV?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEBU1234@AEBU1234@PEBQEBU1234@_J$0A@@12@_K@Z
+    ??$__assign_with_size@PEBVFormatArgImpl@str_format_internal@absl@@PEBV123@@?$vector@VFormatArgImpl@str_format_internal@absl@@V?$allocator@VFormatArgImpl@str_format_internal@absl@@@__Cr@std@@@__Cr@std@@AEAAXPEBVFormatArgImpl@str_format_internal@absl@@0_J@Z
+    ??$__assign_with_size_random_access@V?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEBU1234@AEBU1234@PEBQEBU1234@_J$0A@@__Cr@std@@@?$deque@UPrefixCrc@CrcCordState@crc_internal@absl@@V?$allocator@UPrefixCrc@CrcCordState@crc_internal@absl@@@__Cr@std@@@__Cr@std@@AEAAXV?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEBU1234@AEBU1234@PEBQEBU1234@_J$0A@@12@_J@Z
+    ??$__construct_at_end@PEBVFormatArgImpl@str_format_internal@absl@@PEBV123@@?$vector@VFormatArgImpl@str_format_internal@absl@@V?$allocator@VFormatArgImpl@str_format_internal@absl@@@__Cr@std@@@__Cr@std@@AEAAXPEBVFormatArgImpl@str_format_internal@absl@@0_K@Z
+    ??$__construct_at_end_with_size@V?$move_iterator@PEAPEAPEBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@@?$__split_buffer@PEAPEBVImpl@time_zone@cctz@time_internal@absl@@AEAV?$allocator@PEAPEBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@@__Cr@std@@QEAAXV?$move_iterator@PEAPEAPEBVImpl@time_zone@cctz@time_internal@absl@@@12@_K@Z
+    ??$__construct_at_end_with_size@V?$move_iterator@PEAPEAUPrefixCrc@CrcCordState@crc_internal@absl@@@__Cr@std@@@?$__split_buffer@PEAUPrefixCrc@CrcCordState@crc_internal@absl@@AEAV?$allocator@PEAUPrefixCrc@CrcCordState@crc_internal@absl@@@__Cr@std@@@__Cr@std@@QEAAXV?$move_iterator@PEAPEAUPrefixCrc@CrcCordState@crc_internal@absl@@@12@_K@Z
+    ??$__construct_at_end_with_size@V?$move_iterator@PEAUTransition@cctz@time_internal@absl@@@__Cr@std@@@?$__split_buffer@UTransition@cctz@time_internal@absl@@AEAV?$allocator@UTransition@cctz@time_internal@absl@@@__Cr@std@@@__Cr@std@@QEAAXV?$move_iterator@PEAUTransition@cctz@time_internal@absl@@@12@_K@Z
+    ??$__construct_at_end_with_size@V?$move_iterator@PEAUTransitionType@cctz@time_internal@absl@@@__Cr@std@@@?$__split_buffer@UTransitionType@cctz@time_internal@absl@@AEAV?$allocator@UTransitionType@cctz@time_internal@absl@@@__Cr@std@@@__Cr@std@@QEAAXV?$move_iterator@PEAUTransitionType@cctz@time_internal@absl@@@12@_K@Z
     ??$__construct_one_at_end@AEAV?$basic_string_view@DU?$char_traits@D@__Cr@std@@@__Cr@std@@AEBV123@AEA_K@?$vector@UViableSubstitution@strings_internal@absl@@V?$allocator@UViableSubstitution@strings_internal@absl@@@__Cr@std@@@__Cr@std@@AEAAXAEAV?$basic_string_view@DU?$char_traits@D@__Cr@std@@@12@AEBV312@AEA_K@Z
     ??$__construct_one_at_end@AEBQEAVCordzHandle@cord_internal@absl@@@?$vector@PEAVCordzHandle@cord_internal@absl@@V?$allocator@PEAVCordzHandle@cord_internal@absl@@@__Cr@std@@@__Cr@std@@AEAAXAEBQEAVCordzHandle@cord_internal@absl@@@Z
     ??$__construct_one_at_end@AEBQEAVLogSink@absl@@@?$vector@PEAVLogSink@absl@@V?$allocator@PEAVLogSink@absl@@@__Cr@std@@@__Cr@std@@AEAAXAEBQEAVLogSink@absl@@@Z
@@ -162,7 +164,6 @@
     ??$__rehash@$00@?$__hash_table@U?$__hash_value_type@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PEBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@V?$__unordered_map_hasher@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@U?$__hash_value_type@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PEBVImpl@time_zone@cctz@time_internal@absl@@@23@U?$hash@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@23@U?$equal_to@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@23@$00@23@V?$__unordered_map_equal@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@U?$__hash_value_type@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PEBVImpl@time_zone@cctz@time_internal@absl@@@23@U?$equal_to@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@23@U?$hash@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@23@$00@23@V?$allocator@U?$__hash_value_type@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PEBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@@23@@__Cr@std@@AEAAX_K@Z
     ??$__unwrap_and_dispatch@U?$__overload@U?$__copy_loop@U_ClassicAlgPolicy@__Cr@std@@@__Cr@std@@U__copy_trivial@23@@__Cr@std@@PEBUPrefixCrc@CrcCordState@crc_internal@absl@@PEBU4567@V?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEAU1234@AEAU1234@PEAPEAU1234@_J$0A@@23@$0A@@__Cr@std@@YA?AU?$pair@PEBUPrefixCrc@CrcCordState@crc_internal@absl@@V?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEAU1234@AEAU1234@PEAPEAU1234@_J$0A@@__Cr@std@@@01@PEBUPrefixCrc@CrcCordState@crc_internal@absl@@0V?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEAU1234@AEAU1234@PEAPEAU1234@_J$0A@@01@@Z
     ??$__upper_bound@U_ClassicAlgPolicy@__Cr@std@@UByCivilTime@Transition@cctz@time_internal@absl@@PEBU5678@PEBU5678@U5678@U__identity@23@@__Cr@std@@YAPEBUTransition@cctz@time_internal@absl@@PEBU2345@0AEBU2345@$$QEAUByCivilTime@2345@$$QEAU__identity@01@@Z
-    ??$assign@PEBVFormatArgImpl@str_format_internal@absl@@$0A@@?$vector@VFormatArgImpl@str_format_internal@absl@@V?$allocator@VFormatArgImpl@str_format_internal@absl@@@__Cr@std@@@__Cr@std@@QEAAXPEBVFormatArgImpl@str_format_internal@absl@@0@Z
     ??$assign@V?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEBU1234@AEBU1234@PEBQEBU1234@_J$0A@@__Cr@std@@@?$deque@UPrefixCrc@CrcCordState@crc_internal@absl@@V?$allocator@UPrefixCrc@CrcCordState@crc_internal@absl@@@__Cr@std@@@__Cr@std@@QEAAXV?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PEBU1234@AEBU1234@PEBQEBU1234@_J$0A@@12@0PEAX@Z
     ??$assign@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@?$optional_data_base@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@optional_internal@absl@@IEAAX$$QEAV?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@Z
     ??$construct_at@U?$pair@$$CBV?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PEBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@AEBUpiecewise_construct_t@23@V?$tuple@AEBV?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@23@V?$tuple@$$V@23@PEAU123@@__Cr@std@@YAPEAU?$pair@$$CBV?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PEBVImpl@time_zone@cctz@time_internal@absl@@@01@PEAU201@AEBUpiecewise_construct_t@01@$$QEAV?$tuple@AEBV?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@01@$$QEAV?$tuple@$$V@01@@Z
diff --git a/third_party/abseil-cpp/symbols_x86_dbg.def b/third_party/abseil-cpp/symbols_x86_dbg.def
index 918099c..fb921ae2 100644
--- a/third_party/abseil-cpp/symbols_x86_dbg.def
+++ b/third_party/abseil-cpp/symbols_x86_dbg.def
@@ -679,12 +679,18 @@
     ??$__allocate_at_least@V?$allocator@UTransitionType@cctz@time_internal@absl@@@__Cr@std@@@__Cr@std@@YA?AU?$__allocation_result@PAUTransitionType@cctz@time_internal@absl@@@01@AAV?$allocator@UTransitionType@cctz@time_internal@absl@@@01@I@Z
     ??$__allocate_at_least@V?$allocator@UViableSubstitution@strings_internal@absl@@@__Cr@std@@@__Cr@std@@YA?AU?$__allocation_result@PAUViableSubstitution@strings_internal@absl@@@01@AAV?$allocator@UViableSubstitution@strings_internal@absl@@@01@I@Z
     ??$__allocate_at_least@V?$allocator@VFormatArgImpl@str_format_internal@absl@@@__Cr@std@@@__Cr@std@@YA?AU?$__allocation_result@PAVFormatArgImpl@str_format_internal@absl@@@01@AAV?$allocator@VFormatArgImpl@str_format_internal@absl@@@01@I@Z
-    ??$__append@V?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PBU1234@ABU1234@PBQBU1234@H$0A@@__Cr@std@@@?$deque@UPrefixCrc@CrcCordState@crc_internal@absl@@V?$allocator@UPrefixCrc@CrcCordState@crc_internal@absl@@@__Cr@std@@@__Cr@std@@AAEXV?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PBU1234@ABU1234@PBQBU1234@H$0A@@12@0PAX@Z
-    ??$__construct_at_end@PBVFormatArgImpl@str_format_internal@absl@@$0A@@?$vector@VFormatArgImpl@str_format_internal@absl@@V?$allocator@VFormatArgImpl@str_format_internal@absl@@@__Cr@std@@@__Cr@std@@AAEXPBVFormatArgImpl@str_format_internal@absl@@0I@Z
+    ??$__append_with_size@V?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PBU1234@ABU1234@PBQBU1234@H$0A@@__Cr@std@@@?$deque@UPrefixCrc@CrcCordState@crc_internal@absl@@V?$allocator@UPrefixCrc@CrcCordState@crc_internal@absl@@@__Cr@std@@@__Cr@std@@AAEXV?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PBU1234@ABU1234@PBQBU1234@H$0A@@12@I@Z
+    ??$__assign_with_size@PBVFormatArgImpl@str_format_internal@absl@@PBV123@@?$vector@VFormatArgImpl@str_format_internal@absl@@V?$allocator@VFormatArgImpl@str_format_internal@absl@@@__Cr@std@@@__Cr@std@@AAEXPBVFormatArgImpl@str_format_internal@absl@@0H@Z
+    ??$__assign_with_size_random_access@V?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PBU1234@ABU1234@PBQBU1234@H$0A@@__Cr@std@@@?$deque@UPrefixCrc@CrcCordState@crc_internal@absl@@V?$allocator@UPrefixCrc@CrcCordState@crc_internal@absl@@@__Cr@std@@@__Cr@std@@AAEXV?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PBU1234@ABU1234@PBQBU1234@H$0A@@12@H@Z
+    ??$__construct_at_end@PBVFormatArgImpl@str_format_internal@absl@@PBV123@@?$vector@VFormatArgImpl@str_format_internal@absl@@V?$allocator@VFormatArgImpl@str_format_internal@absl@@@__Cr@std@@@__Cr@std@@AAEXPBVFormatArgImpl@str_format_internal@absl@@0I@Z
     ??$__construct_at_end@V?$move_iterator@PAPAPBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@@?$__split_buffer@PAPBVImpl@time_zone@cctz@time_internal@absl@@AAV?$allocator@PAPBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@@__Cr@std@@QAEXV?$move_iterator@PAPAPBVImpl@time_zone@cctz@time_internal@absl@@@12@0@Z
     ??$__construct_at_end@V?$move_iterator@PAPAUPrefixCrc@CrcCordState@crc_internal@absl@@@__Cr@std@@@?$__split_buffer@PAUPrefixCrc@CrcCordState@crc_internal@absl@@AAV?$allocator@PAUPrefixCrc@CrcCordState@crc_internal@absl@@@__Cr@std@@@__Cr@std@@QAEXV?$move_iterator@PAPAUPrefixCrc@CrcCordState@crc_internal@absl@@@12@0@Z
     ??$__construct_at_end@V?$move_iterator@PAUTransition@cctz@time_internal@absl@@@__Cr@std@@@?$__split_buffer@UTransition@cctz@time_internal@absl@@AAV?$allocator@UTransition@cctz@time_internal@absl@@@__Cr@std@@@__Cr@std@@QAEXV?$move_iterator@PAUTransition@cctz@time_internal@absl@@@12@0@Z
     ??$__construct_at_end@V?$move_iterator@PAUTransitionType@cctz@time_internal@absl@@@__Cr@std@@@?$__split_buffer@UTransitionType@cctz@time_internal@absl@@AAV?$allocator@UTransitionType@cctz@time_internal@absl@@@__Cr@std@@@__Cr@std@@QAEXV?$move_iterator@PAUTransitionType@cctz@time_internal@absl@@@12@0@Z
+    ??$__construct_at_end_with_size@V?$move_iterator@PAPAPBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@@?$__split_buffer@PAPBVImpl@time_zone@cctz@time_internal@absl@@AAV?$allocator@PAPBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@@__Cr@std@@QAEXV?$move_iterator@PAPAPBVImpl@time_zone@cctz@time_internal@absl@@@12@I@Z
+    ??$__construct_at_end_with_size@V?$move_iterator@PAPAUPrefixCrc@CrcCordState@crc_internal@absl@@@__Cr@std@@@?$__split_buffer@PAUPrefixCrc@CrcCordState@crc_internal@absl@@AAV?$allocator@PAUPrefixCrc@CrcCordState@crc_internal@absl@@@__Cr@std@@@__Cr@std@@QAEXV?$move_iterator@PAPAUPrefixCrc@CrcCordState@crc_internal@absl@@@12@I@Z
+    ??$__construct_at_end_with_size@V?$move_iterator@PAUTransition@cctz@time_internal@absl@@@__Cr@std@@@?$__split_buffer@UTransition@cctz@time_internal@absl@@AAV?$allocator@UTransition@cctz@time_internal@absl@@@__Cr@std@@@__Cr@std@@QAEXV?$move_iterator@PAUTransition@cctz@time_internal@absl@@@12@I@Z
+    ??$__construct_at_end_with_size@V?$move_iterator@PAUTransitionType@cctz@time_internal@absl@@@__Cr@std@@@?$__split_buffer@UTransitionType@cctz@time_internal@absl@@AAV?$allocator@UTransitionType@cctz@time_internal@absl@@@__Cr@std@@@__Cr@std@@QAEXV?$move_iterator@PAUTransitionType@cctz@time_internal@absl@@@12@I@Z
     ??$__construct_node_hash@ABUpiecewise_construct_t@__Cr@std@@V?$tuple@ABV?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@23@V?$tuple@$$V@23@@?$__hash_table@U?$__hash_value_type@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@V?$__unordered_map_hasher@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@U?$__hash_value_type@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PBVImpl@time_zone@cctz@time_internal@absl@@@23@U?$hash@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@23@U?$equal_to@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@23@$00@23@V?$__unordered_map_equal@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@U?$__hash_value_type@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PBVImpl@time_zone@cctz@time_internal@absl@@@23@U?$equal_to@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@23@U?$hash@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@23@$00@23@V?$allocator@U?$__hash_value_type@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@@23@@__Cr@std@@AAE?AV?$unique_ptr@U?$__hash_node@U?$__hash_value_type@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@PAX@__Cr@std@@V?$__hash_node_destructor@V?$allocator@U?$__hash_node@U?$__hash_value_type@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@PAX@__Cr@std@@@__Cr@std@@@23@@12@IABUpiecewise_construct_t@12@$$QAV?$tuple@ABV?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@12@$$QAV?$tuple@$$V@12@@Z
     ??$__construct_one_at_end@$$V@?$vector@UTransition@cctz@time_internal@absl@@V?$allocator@UTransition@cctz@time_internal@absl@@@__Cr@std@@@__Cr@std@@AAEXXZ
     ??$__construct_one_at_end@$$V@?$vector@UTransitionType@cctz@time_internal@absl@@V?$allocator@UTransitionType@cctz@time_internal@absl@@@__Cr@std@@@__Cr@std@@AAEXXZ
@@ -784,7 +790,6 @@
     ??$__distance@PBUPayload@status_internal@absl@@@__Cr@std@@YAHPBUPayload@status_internal@absl@@0Urandom_access_iterator_tag@01@@Z
     ??$__distance@PBUTransition@cctz@time_internal@absl@@@__Cr@std@@YAHPBUTransition@cctz@time_internal@absl@@0Urandom_access_iterator_tag@01@@Z
     ??$__distance@PBVFormatArgImpl@str_format_internal@absl@@@__Cr@std@@YAHPBVFormatArgImpl@str_format_internal@absl@@0Urandom_access_iterator_tag@01@@Z
-    ??$__distance@V?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PBU1234@ABU1234@PBQBU1234@H$0A@@__Cr@std@@@__Cr@std@@YAHV?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PBU1234@ABU1234@PBQBU1234@H$0A@@01@0Urandom_access_iterator_tag@01@@Z
     ??$__distance@V?$move_iterator@PAPAPBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@@__Cr@std@@YAHV?$move_iterator@PAPAPBVImpl@time_zone@cctz@time_internal@absl@@@01@0Urandom_access_iterator_tag@01@@Z
     ??$__distance@V?$move_iterator@PAPAUPrefixCrc@CrcCordState@crc_internal@absl@@@__Cr@std@@@__Cr@std@@YAHV?$move_iterator@PAPAUPrefixCrc@CrcCordState@crc_internal@absl@@@01@0Urandom_access_iterator_tag@01@@Z
     ??$__distance@V?$move_iterator@PAUTransition@cctz@time_internal@absl@@@__Cr@std@@@__Cr@std@@YAHV?$move_iterator@PAUTransition@cctz@time_internal@absl@@@01@0Urandom_access_iterator_tag@01@@Z
@@ -947,7 +952,7 @@
     ??$__validate_iter_reference@AAV?$reverse_iterator@PAUViableSubstitution@strings_internal@absl@@@__Cr@std@@@?$_IterOps@U_ClassicAlgPolicy@__Cr@std@@@__Cr@std@@SAXXZ
     ??$advance@PBUTransition@cctz@time_internal@absl@@H@?$_IterOps@U_ClassicAlgPolicy@__Cr@std@@@__Cr@std@@SAXAAPBUTransition@cctz@time_internal@absl@@H@Z
     ??$advance@PBUTransition@cctz@time_internal@absl@@HHX@__Cr@std@@YAXAAPBUTransition@cctz@time_internal@absl@@H@Z
-    ??$advance@PBVFormatArgImpl@str_format_internal@absl@@IIX@__Cr@std@@YAXAAPBVFormatArgImpl@str_format_internal@absl@@I@Z
+    ??$advance@PBVFormatArgImpl@str_format_internal@absl@@HHX@__Cr@std@@YAXAAPBVFormatArgImpl@str_format_internal@absl@@H@Z
     ??$assign@PBVFormatArgImpl@str_format_internal@absl@@$0A@@?$vector@VFormatArgImpl@str_format_internal@absl@@V?$allocator@VFormatArgImpl@str_format_internal@absl@@@__Cr@std@@@__Cr@std@@QAEXPBVFormatArgImpl@str_format_internal@absl@@0@Z
     ??$assign@V?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PBU1234@ABU1234@PBQBU1234@H$0A@@__Cr@std@@@?$deque@UPrefixCrc@CrcCordState@crc_internal@absl@@V?$allocator@UPrefixCrc@CrcCordState@crc_internal@absl@@@__Cr@std@@@__Cr@std@@QAEXV?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PBU1234@ABU1234@PBQBU1234@H$0A@@12@0PAX@Z
     ??$assign@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@?$optional_data_base@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@optional_internal@absl@@IAEX$$QAV?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@Z
@@ -1009,6 +1014,7 @@
     ??$construct_at@UViableSubstitution@strings_internal@absl@@AAV?$basic_string_view@DU?$char_traits@D@__Cr@std@@@__Cr@std@@ABV456@AAIPAU123@@__Cr@std@@YAPAUViableSubstitution@strings_internal@absl@@PAU234@AAV?$basic_string_view@DU?$char_traits@D@__Cr@std@@@01@ABV501@AAI@Z
     ??$copy@PBVFormatArgImpl@str_format_internal@absl@@PAV123@@__Cr@std@@YAPAVFormatArgImpl@str_format_internal@absl@@PBV234@0PAV234@@Z
     ??$copy@V?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PBU1234@ABU1234@PBQBU1234@H$0A@@__Cr@std@@V?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PAU1234@AAU1234@PAPAU1234@H$0A@@23@@__Cr@std@@YA?AV?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PAU1234@AAU1234@PAPAU1234@H$0A@@01@V?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PBU1234@ABU1234@PBQBU1234@H$0A@@01@0V201@@Z
+    ??$copy_n@V?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PBU1234@ABU1234@PBQBU1234@H$0A@@__Cr@std@@HV?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PAU1234@AAU1234@PAPAU1234@H$0A@@23@@__Cr@std@@YA?AV?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PAU1234@AAU1234@PAPAU1234@H$0A@@01@V?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PBU1234@ABU1234@PBQBU1234@H$0A@@01@HV201@@Z
     ??$countl_zero@I@absl@@YAHI@Z
     ??$countl_zero@_K@absl@@YAH_K@Z
     ??$countr_zero@I@absl@@YAHI@Z
@@ -1045,7 +1051,6 @@
     ??$distance@PBUTransition@cctz@time_internal@absl@@@?$_IterOps@U_ClassicAlgPolicy@__Cr@std@@@__Cr@std@@SAHPBUTransition@cctz@time_internal@absl@@0@Z
     ??$distance@PBUTransition@cctz@time_internal@absl@@@__Cr@std@@YAHPBUTransition@cctz@time_internal@absl@@0@Z
     ??$distance@PBVFormatArgImpl@str_format_internal@absl@@@__Cr@std@@YAHPBVFormatArgImpl@str_format_internal@absl@@0@Z
-    ??$distance@V?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PBU1234@ABU1234@PBQBU1234@H$0A@@__Cr@std@@@__Cr@std@@YAHV?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PBU1234@ABU1234@PBQBU1234@H$0A@@01@0@Z
     ??$distance@V?$move_iterator@PAPAPBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@@__Cr@std@@YAHV?$move_iterator@PAPAPBVImpl@time_zone@cctz@time_internal@absl@@@01@0@Z
     ??$distance@V?$move_iterator@PAPAUPrefixCrc@CrcCordState@crc_internal@absl@@@__Cr@std@@@__Cr@std@@YAHV?$move_iterator@PAPAUPrefixCrc@CrcCordState@crc_internal@absl@@@01@0@Z
     ??$distance@V?$move_iterator@PAUTransition@cctz@time_internal@absl@@@__Cr@std@@@__Cr@std@@YAHV?$move_iterator@PAUTransition@cctz@time_internal@absl@@@01@0@Z
@@ -1178,6 +1183,7 @@
     ??$move_backward@PAUTransitionType@cctz@time_internal@absl@@PAU1234@@__Cr@std@@YAPAUTransitionType@cctz@time_internal@absl@@PAU2345@00@Z
     ??$next@AAPBUTransition@cctz@time_internal@absl@@@?$_IterOps@U_ClassicAlgPolicy@__Cr@std@@@__Cr@std@@SAPBUTransition@cctz@time_internal@absl@@AAPBU3456@H@Z
     ??$next@PBUTransition@cctz@time_internal@absl@@@__Cr@std@@YAPBUTransition@cctz@time_internal@absl@@PBU2345@H@Z
+    ??$next@PBVFormatArgImpl@str_format_internal@absl@@@__Cr@std@@YAPBVFormatArgImpl@str_format_internal@absl@@PBV234@H@Z
     ??$reset@PAPAU?$__hash_node_base@PAU?$__hash_node@U?$__hash_value_type@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@PAX@__Cr@std@@@__Cr@std@@@?$unique_ptr@$$BY0A@PAU?$__hash_node_base@PAU?$__hash_node@U?$__hash_value_type@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@PAX@__Cr@std@@@__Cr@std@@V?$__bucket_list_deallocator@V?$allocator@PAU?$__hash_node_base@PAU?$__hash_node@U?$__hash_value_type@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@PAX@__Cr@std@@@__Cr@std@@@__Cr@std@@@23@@__Cr@std@@QAEXPAPAU?$__hash_node_base@PAU?$__hash_node@U?$__hash_value_type@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@PAX@__Cr@std@@@12@@Z
     ??$rotr@I@absl@@YAIIH@Z
     ??$rotr@_K@absl@@YA_K_KH@Z
diff --git a/third_party/abseil-cpp/symbols_x86_rel.def b/third_party/abseil-cpp/symbols_x86_rel.def
index 2f1f5b3..4b85bf2 100644
--- a/third_party/abseil-cpp/symbols_x86_rel.def
+++ b/third_party/abseil-cpp/symbols_x86_rel.def
@@ -148,18 +148,19 @@
     ??$__allocate_at_least@V?$allocator@UTransitionType@cctz@time_internal@absl@@@__Cr@std@@@__Cr@std@@YA?AU?$__allocation_result@PAUTransitionType@cctz@time_internal@absl@@@01@AAV?$allocator@UTransitionType@cctz@time_internal@absl@@@01@I@Z
     ??$__allocate_at_least@V?$allocator@UViableSubstitution@strings_internal@absl@@@__Cr@std@@@__Cr@std@@YA?AU?$__allocation_result@PAUViableSubstitution@strings_internal@absl@@@01@AAV?$allocator@UViableSubstitution@strings_internal@absl@@@01@I@Z
     ??$__allocate_at_least@V?$allocator@VFormatArgImpl@str_format_internal@absl@@@__Cr@std@@@__Cr@std@@YA?AU?$__allocation_result@PAVFormatArgImpl@str_format_internal@absl@@@01@AAV?$allocator@VFormatArgImpl@str_format_internal@absl@@@01@I@Z
-    ??$__append@V?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PBU1234@ABU1234@PBQBU1234@H$0A@@__Cr@std@@@?$deque@UPrefixCrc@CrcCordState@crc_internal@absl@@V?$allocator@UPrefixCrc@CrcCordState@crc_internal@absl@@@__Cr@std@@@__Cr@std@@AAEXV?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PBU1234@ABU1234@PBQBU1234@H$0A@@12@0PAX@Z
-    ??$__construct_at_end@V?$move_iterator@PAPAPBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@@?$__split_buffer@PAPBVImpl@time_zone@cctz@time_internal@absl@@AAV?$allocator@PAPBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@@__Cr@std@@QAEXV?$move_iterator@PAPAPBVImpl@time_zone@cctz@time_internal@absl@@@12@0@Z
-    ??$__construct_at_end@V?$move_iterator@PAPAUPrefixCrc@CrcCordState@crc_internal@absl@@@__Cr@std@@@?$__split_buffer@PAUPrefixCrc@CrcCordState@crc_internal@absl@@AAV?$allocator@PAUPrefixCrc@CrcCordState@crc_internal@absl@@@__Cr@std@@@__Cr@std@@QAEXV?$move_iterator@PAPAUPrefixCrc@CrcCordState@crc_internal@absl@@@12@0@Z
-    ??$__construct_at_end@V?$move_iterator@PAUTransition@cctz@time_internal@absl@@@__Cr@std@@@?$__split_buffer@UTransition@cctz@time_internal@absl@@AAV?$allocator@UTransition@cctz@time_internal@absl@@@__Cr@std@@@__Cr@std@@QAEXV?$move_iterator@PAUTransition@cctz@time_internal@absl@@@12@0@Z
-    ??$__construct_at_end@V?$move_iterator@PAUTransitionType@cctz@time_internal@absl@@@__Cr@std@@@?$__split_buffer@UTransitionType@cctz@time_internal@absl@@AAV?$allocator@UTransitionType@cctz@time_internal@absl@@@__Cr@std@@@__Cr@std@@QAEXV?$move_iterator@PAUTransitionType@cctz@time_internal@absl@@@12@0@Z
+    ??$__append_with_size@V?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PBU1234@ABU1234@PBQBU1234@H$0A@@__Cr@std@@@?$deque@UPrefixCrc@CrcCordState@crc_internal@absl@@V?$allocator@UPrefixCrc@CrcCordState@crc_internal@absl@@@__Cr@std@@@__Cr@std@@AAEXV?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PBU1234@ABU1234@PBQBU1234@H$0A@@12@I@Z
+    ??$__assign_with_size@PBVFormatArgImpl@str_format_internal@absl@@PBV123@@?$vector@VFormatArgImpl@str_format_internal@absl@@V?$allocator@VFormatArgImpl@str_format_internal@absl@@@__Cr@std@@@__Cr@std@@AAEXPBVFormatArgImpl@str_format_internal@absl@@0H@Z
+    ??$__assign_with_size_random_access@V?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PBU1234@ABU1234@PBQBU1234@H$0A@@__Cr@std@@@?$deque@UPrefixCrc@CrcCordState@crc_internal@absl@@V?$allocator@UPrefixCrc@CrcCordState@crc_internal@absl@@@__Cr@std@@@__Cr@std@@AAEXV?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PBU1234@ABU1234@PBQBU1234@H$0A@@12@H@Z
+    ??$__construct_at_end_with_size@V?$move_iterator@PAPAPBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@@?$__split_buffer@PAPBVImpl@time_zone@cctz@time_internal@absl@@AAV?$allocator@PAPBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@@__Cr@std@@QAEXV?$move_iterator@PAPAPBVImpl@time_zone@cctz@time_internal@absl@@@12@I@Z
+    ??$__construct_at_end_with_size@V?$move_iterator@PAPAUPrefixCrc@CrcCordState@crc_internal@absl@@@__Cr@std@@@?$__split_buffer@PAUPrefixCrc@CrcCordState@crc_internal@absl@@AAV?$allocator@PAUPrefixCrc@CrcCordState@crc_internal@absl@@@__Cr@std@@@__Cr@std@@QAEXV?$move_iterator@PAPAUPrefixCrc@CrcCordState@crc_internal@absl@@@12@I@Z
+    ??$__construct_at_end_with_size@V?$move_iterator@PAUTransition@cctz@time_internal@absl@@@__Cr@std@@@?$__split_buffer@UTransition@cctz@time_internal@absl@@AAV?$allocator@UTransition@cctz@time_internal@absl@@@__Cr@std@@@__Cr@std@@QAEXV?$move_iterator@PAUTransition@cctz@time_internal@absl@@@12@I@Z
+    ??$__construct_at_end_with_size@V?$move_iterator@PAUTransitionType@cctz@time_internal@absl@@@__Cr@std@@@?$__split_buffer@UTransitionType@cctz@time_internal@absl@@AAV?$allocator@UTransitionType@cctz@time_internal@absl@@@__Cr@std@@@__Cr@std@@QAEXV?$move_iterator@PAUTransitionType@cctz@time_internal@absl@@@12@I@Z
     ??$__destroy_at@U?$pair@$$CBV?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@$0A@@__Cr@std@@YAXPAU?$pair@$$CBV?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PBVImpl@time_zone@cctz@time_internal@absl@@@01@@Z
     ??$__destroy_at@UPayload@status_internal@absl@@$0A@@__Cr@std@@YAXPAUPayload@status_internal@absl@@@Z
     ??$__do_rehash@$00@?$__hash_table@U?$__hash_value_type@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@V?$__unordered_map_hasher@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@U?$__hash_value_type@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PBVImpl@time_zone@cctz@time_internal@absl@@@23@U?$hash@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@23@U?$equal_to@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@23@$00@23@V?$__unordered_map_equal@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@U?$__hash_value_type@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PBVImpl@time_zone@cctz@time_internal@absl@@@23@U?$equal_to@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@23@U?$hash@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@23@$00@23@V?$allocator@U?$__hash_value_type@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@@23@@__Cr@std@@AAEXI@Z
     ??$__emplace_unique_key_args@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@ABUpiecewise_construct_t@23@V?$tuple@ABV?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@23@V?$tuple@$$V@23@@?$__hash_table@U?$__hash_value_type@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@V?$__unordered_map_hasher@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@U?$__hash_value_type@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PBVImpl@time_zone@cctz@time_internal@absl@@@23@U?$hash@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@23@U?$equal_to@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@23@$00@23@V?$__unordered_map_equal@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@U?$__hash_value_type@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PBVImpl@time_zone@cctz@time_internal@absl@@@23@U?$equal_to@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@23@U?$hash@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@23@$00@23@V?$allocator@U?$__hash_value_type@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@@23@@__Cr@std@@QAE?AU?$pair@V?$__hash_iterator@PAU?$__hash_node@U?$__hash_value_type@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@PAX@__Cr@std@@@__Cr@std@@_N@12@ABV?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@12@ABUpiecewise_construct_t@12@$$QAV?$tuple@ABV?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@12@$$QAV?$tuple@$$V@12@@Z
     ??$__rehash@$00@?$__hash_table@U?$__hash_value_type@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@V?$__unordered_map_hasher@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@U?$__hash_value_type@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PBVImpl@time_zone@cctz@time_internal@absl@@@23@U?$hash@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@23@U?$equal_to@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@23@$00@23@V?$__unordered_map_equal@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@U?$__hash_value_type@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PBVImpl@time_zone@cctz@time_internal@absl@@@23@U?$equal_to@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@23@U?$hash@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@23@$00@23@V?$allocator@U?$__hash_value_type@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@@23@@__Cr@std@@AAEXI@Z
     ??$__upper_bound@U_ClassicAlgPolicy@__Cr@std@@UByCivilTime@Transition@cctz@time_internal@absl@@PBU5678@PBU5678@U5678@U__identity@23@@__Cr@std@@YAPBUTransition@cctz@time_internal@absl@@PBU2345@0ABU2345@$$QAUByCivilTime@2345@$$QAU__identity@01@@Z
-    ??$assign@PBVFormatArgImpl@str_format_internal@absl@@$0A@@?$vector@VFormatArgImpl@str_format_internal@absl@@V?$allocator@VFormatArgImpl@str_format_internal@absl@@@__Cr@std@@@__Cr@std@@QAEXPBVFormatArgImpl@str_format_internal@absl@@0@Z
     ??$assign@V?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PBU1234@ABU1234@PBQBU1234@H$0A@@__Cr@std@@@?$deque@UPrefixCrc@CrcCordState@crc_internal@absl@@V?$allocator@UPrefixCrc@CrcCordState@crc_internal@absl@@@__Cr@std@@@__Cr@std@@QAEXV?$__deque_iterator@UPrefixCrc@CrcCordState@crc_internal@absl@@PBU1234@ABU1234@PBQBU1234@H$0A@@12@0PAX@Z
     ??$assign@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@?$optional_data_base@V?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@optional_internal@absl@@IAEX$$QAV?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@Z
     ??$construct_at@U?$pair@$$CBV?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PBVImpl@time_zone@cctz@time_internal@absl@@@__Cr@std@@ABUpiecewise_construct_t@23@V?$tuple@ABV?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@23@V?$tuple@$$V@23@PAU123@@__Cr@std@@YAPAU?$pair@$$CBV?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@PBVImpl@time_zone@cctz@time_internal@absl@@@01@PAU201@ABUpiecewise_construct_t@01@$$QAV?$tuple@ABV?$basic_string@DU?$char_traits@D@__Cr@std@@V?$allocator@D@23@@__Cr@std@@@01@$$QAV?$tuple@$$V@01@@Z
diff --git a/third_party/blink/common/features.cc b/third_party/blink/common/features.cc
index 903463d..4f7286e 100644
--- a/third_party/blink/common/features.cc
+++ b/third_party/blink/common/features.cc
@@ -130,6 +130,12 @@
              "ConversionMeasurement",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
+// TODO(https://crbug.com/1453572): Remove this if this behavior does not need
+// to be rolled back on stable.
+BASE_FEATURE(kCrossOriginAccessOnDetachedWindowDoesNotThrow,
+             "CrossOriginAccessOnDetachedWindowDoesNotThrow",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 // Controls whether LCP calculations should exclude low-entropy images. If
 // enabled, then the associated parameter sets the cutoff, expressed as the
 // minimum number of bits of encoded image data used to encode each rendered
diff --git a/third_party/blink/common/renderer_preferences/renderer_preferences_mojom_traits.cc b/third_party/blink/common/renderer_preferences/renderer_preferences_mojom_traits.cc
index 62b1e813..66f99b1 100644
--- a/third_party/blink/common/renderer_preferences/renderer_preferences_mojom_traits.cc
+++ b/third_party/blink/common/renderer_preferences/renderer_preferences_mojom_traits.cc
@@ -64,6 +64,8 @@
   if (!data.ReadAcceptLanguages(&out->accept_languages))
     return false;
 
+  out->send_subresource_notification = data.send_subresource_notification();
+
 #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
   if (!data.ReadSystemFontFamilyName(&out->system_font_family_name))
     return false;
diff --git a/third_party/blink/public/common/features.h b/third_party/blink/public/common/features.h
index 496e29b7..7d176b3 100644
--- a/third_party/blink/public/common/features.h
+++ b/third_party/blink/public/common/features.h
@@ -54,6 +54,8 @@
 BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(
     kBlockingDownloadsInAdFrameWithoutUserActivation);
 BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kConversionMeasurement);
+BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(
+    kCrossOriginAccessOnDetachedWindowDoesNotThrow);
 BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kExcludeLowEntropyImagesFromLCP);
 BLINK_COMMON_EXPORT extern const base::FeatureParam<double>
     kMinimumEntropyForLCP;
diff --git a/third_party/blink/public/common/renderer_preferences/renderer_preferences.h b/third_party/blink/public/common/renderer_preferences/renderer_preferences.h
index 6c98073..ecda061 100644
--- a/third_party/blink/public/common/renderer_preferences/renderer_preferences.h
+++ b/third_party/blink/public/common/renderer_preferences/renderer_preferences.h
@@ -63,6 +63,7 @@
   bool webrtc_allow_legacy_tls_protocols{false};
   UserAgentOverride user_agent_override;
   std::string accept_languages;
+  bool send_subresource_notification{false};
 #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
   std::string system_font_family_name;
 #endif
diff --git a/third_party/blink/public/common/renderer_preferences/renderer_preferences_mojom_traits.h b/third_party/blink/public/common/renderer_preferences/renderer_preferences_mojom_traits.h
index b8181ea..8bbeee88 100644
--- a/third_party/blink/public/common/renderer_preferences/renderer_preferences_mojom_traits.h
+++ b/third_party/blink/public/common/renderer_preferences/renderer_preferences_mojom_traits.h
@@ -157,6 +157,11 @@
     return data.accept_languages;
   }
 
+  static const bool& send_subresource_notification(
+      const ::blink::RendererPreferences& data) {
+    return data.send_subresource_notification;
+  }
+
 #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
   static const std::string& system_font_family_name(
       const ::blink::RendererPreferences& data) {
diff --git a/third_party/blink/public/mojom/renderer_preferences.mojom b/third_party/blink/public/mojom/renderer_preferences.mojom
index 4242b88..271672f 100644
--- a/third_party/blink/public/mojom/renderer_preferences.mojom
+++ b/third_party/blink/public/mojom/renderer_preferences.mojom
@@ -105,6 +105,13 @@
   // The accept-languages of the browser, comma-separated.
   string accept_languages;
 
+  // Whether renderers need to send SubresourceResponseStarted IPC to
+  // the browser. Renderers send the IPC if user has allowed any certificate or HTTP
+  // exceptions and we keep sending subresource notifications to the browser
+  // until all HTTPS-related warning exceptions have been revoked and the
+  // browser is restarted.
+  bool send_subresource_notification = false;
+
   // Determines whether plugins are allowed to enter fullscreen mode.
   bool plugin_fullscreen_allowed = true;
 
diff --git a/third_party/blink/renderer/bindings/core/v8/binding_security.cc b/third_party/blink/renderer/bindings/core/v8/binding_security.cc
index fbc4ae5..2a9c497 100644
--- a/third_party/blink/renderer/bindings/core/v8/binding_security.cc
+++ b/third_party/blink/renderer/bindings/core/v8/binding_security.cc
@@ -352,9 +352,11 @@
   // exception could be a security issue, so just crash.
   CHECK(target);
 
-  // wpt/html/cross-origin-opener-policy/resource-popup.https.html expects
-  // that illegally accessing a detached window doesn't throw.
-  if (!target->GetFrame()) {
+  // This should throw, but for a period of time, it didn't. Until this is
+  // rolled out to stable, guard it with a flag so it can be rolled back.
+  if (base::FeatureList::IsEnabled(
+          features::kCrossOriginAccessOnDetachedWindowDoesNotThrow) &&
+      !target->GetFrame()) {
     return;
   }
 
diff --git a/third_party/blink/renderer/bindings/core/v8/serialization/serialized_script_value.cc b/third_party/blink/renderer/bindings/core/v8/serialization/serialized_script_value.cc
index 5274b753..a13f922 100644
--- a/third_party/blink/renderer/bindings/core/v8/serialization/serialized_script_value.cc
+++ b/third_party/blink/renderer/bindings/core/v8/serialization/serialized_script_value.cc
@@ -356,7 +356,7 @@
       return;
     }
     if (offscreen_canvases[i]->RenderingContext()) {
-      exception_state.ThrowDOMException(DOMExceptionCode::kDataCloneError,
+      exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
                                         "OffscreenCanvas at index " +
                                             String::Number(i) +
                                             " has an associated context.");
diff --git a/third_party/blink/renderer/bindings/core/v8/serialization/v8_script_value_serializer.cc b/third_party/blink/renderer/bindings/core/v8/serialization/v8_script_value_serializer.cc
index 3926a2e..8fbff46b 100644
--- a/third_party/blink/renderer/bindings/core/v8/serialization/v8_script_value_serializer.cc
+++ b/third_party/blink/renderer/bindings/core/v8/serialization/v8_script_value_serializer.cc
@@ -706,15 +706,15 @@
       return false;
     }
     if (canvas->IsNeutered()) {
-      exception_state.ThrowDOMException(
-          DOMExceptionCode::kDataCloneError,
-          "An OffscreenCanvas could not be cloned because it was detached.");
+      exception_state.ThrowDOMException(DOMExceptionCode::kDataCloneError,
+                                        "An OffscreenCanvas could not be "
+                                        "transferred because it was detached.");
       return false;
     }
     if (canvas->RenderingContext()) {
       exception_state.ThrowDOMException(
-          DOMExceptionCode::kDataCloneError,
-          "An OffscreenCanvas could not be cloned "
+          DOMExceptionCode::kInvalidStateError,
+          "An OffscreenCanvas could not be transferred "
           "because it had a rendering context.");
       return false;
     }
diff --git a/third_party/blink/renderer/bindings/core/v8/v8_wasm_response_extensions.cc b/third_party/blink/renderer/bindings/core/v8/v8_wasm_response_extensions.cc
index 166384f2d..35a1c38 100644
--- a/third_party/blink/renderer/bindings/core/v8/v8_wasm_response_extensions.cc
+++ b/third_party/blink/renderer/bindings/core/v8/v8_wasm_response_extensions.cc
@@ -205,7 +205,9 @@
   void OnStateChange() override {
     TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"),
                  "v8.wasm.compileConsume");
-    while (true) {
+    // Continue reading until we either finished, aborted, or no data is
+    // available any more (handled below).
+    while (streaming_) {
       // |buffer| is owned by |consumer_|.
       const char* buffer = nullptr;
       size_t available = 0;
@@ -215,7 +217,7 @@
         return;
       if (result == BytesConsumer::Result::kOk) {
         // Ignore more bytes after an abort (streaming == nullptr).
-        if (available > 0 && streaming_) {
+        if (available > 0) {
           if (code_cache_state_ == CodeCacheState::kBeforeFirstByte)
             code_cache_state_ = MaybeConsumeCodeCache();
 
@@ -238,10 +240,6 @@
         case BytesConsumer::Result::kOk:
           break;
         case BytesConsumer::Result::kDone: {
-          // Ignore this event if we already aborted.
-          if (!streaming_) {
-            return;
-          }
           TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"),
                        "v8.wasm.compileConsumeDone");
           {
@@ -253,8 +251,18 @@
           return;
         }
         case BytesConsumer::Result::kError:
-          return AbortCompilation("Network error: " +
-                                  consumer_->GetError().Message());
+          switch (consumer_->GetPublicState()) {
+            case BytesConsumer::PublicState::kClosed:
+              AbortCompilation("Download cancelled");
+              break;
+            case BytesConsumer::PublicState::kErrored:
+              AbortCompilation("Network error: " +
+                               consumer_->GetError().Message());
+              break;
+            default:
+              NOTREACHED();
+          }
+          break;
       }
     }
   }
diff --git a/third_party/blink/renderer/core/BUILD.gn b/third_party/blink/renderer/core/BUILD.gn
index db6a4b6..ea98141f7 100644
--- a/third_party/blink/renderer/core/BUILD.gn
+++ b/third_party/blink/renderer/core/BUILD.gn
@@ -1423,6 +1423,7 @@
   if (is_apple) {
     configs += [ "//build/config/compiler:enable_arc" ]
   }
+  defines = [ "ALLOW_IMPLICIT_ATOMIC_STRING_CONVERSIONS" ]
 
   deps = [
     ":core",
@@ -1531,6 +1532,7 @@
     "//third_party/blink/renderer:config",
     "//third_party/blink/renderer:inside_blink",
   ]
+  defines = [ "ALLOW_IMPLICIT_ATOMIC_STRING_CONVERSIONS" ]
 
   deps = [
     ":core",
@@ -1582,6 +1584,7 @@
     "//third_party/blink/renderer:config",
     "//third_party/blink/renderer:inside_blink",
   ]
+  defines = [ "ALLOW_IMPLICIT_ATOMIC_STRING_CONVERSIONS" ]
 
   deps = [
     ":core",
diff --git a/third_party/blink/renderer/core/annotation/annotation_agent_impl.cc b/third_party/blink/renderer/core/annotation/annotation_agent_impl.cc
index 0fe6e6a..cb3f452 100644
--- a/third_party/blink/renderer/core/annotation/annotation_agent_impl.cc
+++ b/third_party/blink/renderer/core/annotation/annotation_agent_impl.cc
@@ -90,7 +90,11 @@
   TRACE_EVENT("blink", "AnnotationAgentImpl::Attach");
   CHECK(!IsRemoved());
   CHECK(!IsAttached());
-  CHECK(!IsAttachmentPending());
+  CHECK(!pending_range_);
+
+  // We may still have an old range despite the CHECK above if the range become
+  // collapsed due to DOM changes.
+  attached_range_.Clear();
 
   did_try_attach_ = true;
   Document& document = *owning_container_->GetSupplementable();
@@ -110,7 +114,9 @@
 }
 
 bool AnnotationAgentImpl::IsAttachmentPending() const {
-  return IsValidRange(pending_range_);
+  // This can be an invalid range but still returns true because the attachment
+  // is still in progress until the DomMutation task runs in the next rAF.
+  return pending_range_;
 }
 
 bool AnnotationAgentImpl::IsBoundForTesting() const {
@@ -219,7 +225,7 @@
 }
 
 bool AnnotationAgentImpl::NeedsDOMMutationToAttach() const {
-  if (!IsAttachmentPending()) {
+  if (!IsValidRange(pending_range_)) {
     return false;
   }
 
@@ -242,7 +248,7 @@
 }
 
 void AnnotationAgentImpl::PerformPreAttachDOMMutation() {
-  if (IsAttachmentPending()) {
+  if (IsValidRange(pending_range_)) {
     // TODO(crbug.com/1252872): Only |first_node| is considered for the below
     // ancestor expanding code, but we should be considering the entire range
     // of selected text for ancestor unlocking as well.
@@ -276,7 +282,9 @@
 }
 
 void AnnotationAgentImpl::ProcessAttachmentFinished() {
-  if (IsAttachmentPending()) {
+  CHECK(!attached_range_);
+
+  if (IsValidRange(pending_range_)) {
     attached_range_ = pending_range_;
 
     TRACE_EVENT_INSTANT("blink", "IsAttached");
@@ -309,7 +317,6 @@
     }
   } else {
     TRACE_EVENT_INSTANT("blink", "NotAttached");
-    CHECK(!attached_range_);
   }
 
   pending_range_.Clear();
diff --git a/third_party/blink/renderer/core/exported/BUILD.gn b/third_party/blink/renderer/core/exported/BUILD.gn
index 9d46596..92b3155 100644
--- a/third_party/blink/renderer/core/exported/BUILD.gn
+++ b/third_party/blink/renderer/core/exported/BUILD.gn
@@ -19,6 +19,7 @@
   ]
 
   visibility = [ "//third_party/blink/public:test_support" ]
+  defines = [ "ALLOW_IMPLICIT_ATOMIC_STRING_CONVERSIONS" ]
 }
 
 blink_core_sources("exported") {
diff --git a/third_party/blink/renderer/core/fragment_directive/text_fragment_anchor.cc b/third_party/blink/renderer/core/fragment_directive/text_fragment_anchor.cc
index 1d3318b..e3d4760 100644
--- a/third_party/blink/renderer/core/fragment_directive/text_fragment_anchor.cc
+++ b/third_party/blink/renderer/core/fragment_directive/text_fragment_anchor.cc
@@ -336,8 +336,9 @@
 
   for (auto& directive_annotation_pair : directive_annotation_pairs_) {
     AnnotationAgentImpl* annotation = directive_annotation_pair.second;
-    if (annotation->IsAttached())
+    if (annotation->IsAttached() || annotation->IsAttachmentPending()) {
       continue;
+    }
 
     annotation->Attach(WTF::BindOnce(&TextFragmentAnchor::DidFinishAttachment,
                                      WrapWeakPersistent(this),
diff --git a/third_party/blink/renderer/core/fragment_directive/text_fragment_anchor_test.cc b/third_party/blink/renderer/core/fragment_directive/text_fragment_anchor_test.cc
index 61e3857..61e6f1c7 100644
--- a/third_party/blink/renderer/core/fragment_directive/text_fragment_anchor_test.cc
+++ b/third_party/blink/renderer/core/fragment_directive/text_fragment_anchor_test.cc
@@ -12,6 +12,8 @@
 #include "third_party/blink/renderer/bindings/core/v8/v8_font_face_descriptors.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_mouse_event_init.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_union_arraybuffer_arraybufferview_string.h"
+#include "third_party/blink/renderer/core/annotation/annotation_agent_container_impl.h"
+#include "third_party/blink/renderer/core/annotation/annotation_agent_impl.h"
 #include "third_party/blink/renderer/core/css/font_face_set_document.h"
 #include "third_party/blink/renderer/core/dom/element.h"
 #include "third_party/blink/renderer/core/dom/range.h"
@@ -2521,6 +2523,103 @@
                    .ContextMenuNodeForFrame(GetDocument().GetFrame()));
 }
 
+// Test for https://crbug.com/1453658. Trips a CHECK because an AnnotationAgent
+// unexpectedly calls Attach a second time after initially succeeding because
+// the matched range becomes collapsed.
+TEST_F(TextFragmentAnchorTest, InitialMatchingIsCollapsedCrash) {
+  SimRequest request("https://example.com/test.html#:~:text=test", "text/html");
+  SimSubresourceRequest sub_request("https://example.com/null.png",
+                                    "image/png");
+  LoadURL("https://example.com/test.html#:~:text=test");
+  request.Complete(R"HTML(
+    <!DOCTYPE html>
+    <style>
+      body {
+        height: 1200px;
+      }
+      div {
+        position: absolute;
+        top: 1000px;
+      }
+    </style>
+    <div id="text">test</div>
+    <img src="null.png">
+  )HTML");
+  // Parsing completed but load is still waiting on the <img>, this will run
+  // matching and match "test".
+  Compositor().BeginFrame();
+
+  // Ensure we've attached the annotation for the text fragment.
+  auto* container = AnnotationAgentContainerImpl::From(GetDocument());
+  auto annotations = container->GetAgentsOfType(
+      mojom::blink::AnnotationType::kSharedHighlight);
+  ASSERT_EQ(annotations.size(), 1ul);
+  ASSERT_TRUE((*annotations.begin())->IsAttached());
+
+  // Remove the matched text node; this will collapse the matched range.
+  Element& div = *GetDocument().getElementById("text");
+  div.firstChild()->remove();
+  ASSERT_FALSE((*annotations.begin())->IsAttached());
+
+  // Complete the <img> request (with an error). This will fire the load event
+  // and perform another matching pass. Test passes if this doesn't crash.
+  sub_request.Complete("");
+  Compositor().BeginFrame();
+}
+
+// Test the behavior of removing matched text while waiting to expand a
+// hidden=until-found section. We mostly care that this doesn't crash or
+// violate any state CHECKs.
+TEST_F(TextFragmentAnchorTest, InitialMatchPendingBecomesCollapsed) {
+  SimRequest request("https://example.com/test.html#:~:text=test", "text/html");
+  SimSubresourceRequest sub_request("https://example.com/null.png",
+                                    "image/png");
+  LoadURL("https://example.com/test.html#:~:text=test");
+  request.Complete(R"HTML(
+    <!DOCTYPE html>
+    <style>
+      body {
+        height: 1200px;
+      }
+      div {
+        position: absolute;
+        top: 1000px;
+      }
+    </style>
+    <div id="text" hidden="until-found">test</div>
+    <img src="null.png">
+    <div id="second">test (will match on second pass)</div>
+  )HTML");
+  // Parsing completed but load is still waiting on the <img>, this will run
+  // matching and match "test" but queue a rAF task to show the hidden <div>.
+  Compositor().BeginFrame();
+
+  // Ensure we've queued the "DomMutation" rAF task.
+  auto* container = AnnotationAgentContainerImpl::From(GetDocument());
+  auto annotations = container->GetAgentsOfType(
+      mojom::blink::AnnotationType::kSharedHighlight);
+  ASSERT_EQ(annotations.size(), 1ul);
+  ASSERT_TRUE((*annotations.begin())->IsAttachmentPending());
+
+  // Remove the matched text node; this will collapse the matched range.
+  Element& div = *GetDocument().getElementById("text");
+  div.firstChild()->remove();
+
+  // Complete the <img> request (with an error). This will fire the load event
+  // and the UpdateStyleAndLayout will perform another matching pass but this
+  // shouldn't re-search the pending match.
+  sub_request.Complete("");
+  RunPendingTasks();
+  GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest);
+
+  // This will run the "DomMutation" rAF task from the first match.
+  Compositor().BeginFrame();
+
+  // The directive should not have scrolled or created a marker.
+  EXPECT_EQ(ScrollOffset(), LayoutViewport()->GetScrollOffset());
+  EXPECT_TRUE(GetDocument().Markers().Markers().empty());
+}
+
 }  // namespace
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/html/resources/selectmenu.css b/third_party/blink/renderer/core/html/resources/selectmenu.css
index cfa65b2f..65726c18 100644
--- a/third_party/blink/renderer/core/html/resources/selectmenu.css
+++ b/third_party/blink/renderer/core/html/resources/selectmenu.css
@@ -122,49 +122,16 @@
 }
 
 @position-fallback -internal-selectmenu-listbox-default-fallbacks {
-  /* Below the anchor, left-aligned, no vertical scrolling */
+  /* Fallbacks without vertical scrolling */
   @try {
-    inset-block-start: anchor(self-end);
-    inset-inline-start: anchor(self-start);
+    inset-block-start: anchor(auto);
+    inset-inline-start: anchor(auto-same);
   }
-  /* Below the anchor, right-aligned, no vertical scrolling */
+  /* Fallbacks with vertical scrolling */
   @try {
-    inset-block-start: anchor(self-end);
-    inset-inline-end: anchor(self-end);
-  }
-  /* Over the anchor, left-aligned, no vertical scrolling */
-  @try {
-    inset-block-end: anchor(self-start);
-    inset-inline-start: anchor(self-start);
-  }
-  /* Over the anchor, right-aligned, no vertical scrolling */
-  @try {
-    inset-block-end: anchor(self-start);
-    inset-inline-end: anchor(self-end);
-  }
-  /* Below the anchor, left-aligned, fill up the available vertical space */
-  @try {
-    inset-block-start: anchor(self-end);
+    inset-block-start: anchor(auto);
+    inset-inline-start: anchor(auto-same);
     block-size: -webkit-fill-available;
-    inset-inline-start: anchor(self-start);
-  }
-  /* Below the anchor, right-aligned, fill up the available vertical space */
-  @try {
-    inset-block-start: anchor(self-end);
-    block-size: -webkit-fill-available;
-    inset-inline-end: anchor(self-end);
-  }
-  /* Over the anchor, left-aligned, fill up the available vertical space */
-  @try {
-    inset-block-end: anchor(self-start);
-    block-size: -webkit-fill-available;
-    inset-inline-start: anchor(self-start);
-  }
-  /* Over the anchor, right-aligned, fill up the available vertical space */
-  @try {
-    inset-block-end: anchor(self-start);
-    block-size: -webkit-fill-available;
-    inset-inline-end: anchor(self-end);
   }
 }
 
diff --git a/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc b/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc
index 88f8380..729e8bb 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc
@@ -105,6 +105,175 @@
   return true;
 }
 
+scoped_refptr<const ComputedStyle> CreateFlippedAutoAnchorStyle(
+    const ComputedStyle& base_style,
+    bool flip_block,
+    bool flip_inline) {
+  const bool is_horizontal = base_style.IsHorizontalWritingMode();
+  const bool flip_x = is_horizontal ? flip_inline : flip_block;
+  const bool flip_y = is_horizontal ? flip_block : flip_inline;
+  ComputedStyleBuilder builder(base_style);
+  if (flip_x) {
+    builder.SetLeft(base_style.UsedRight());
+    builder.SetRight(base_style.UsedLeft());
+  }
+  if (flip_y) {
+    builder.SetTop(base_style.UsedBottom());
+    builder.SetBottom(base_style.UsedTop());
+  }
+  return builder.TakeStyle();
+}
+
+// Helper class to enumerate all the candidate styles to be passed to
+// `TryCalculateOffset()`. The class should iterate through:
+// - The base style, if no `position-fallback` is specified
+// - The `@try` rule styles, if `position-fallback` is specified
+// In addition, if any of the above styles generate auto anchor fallbacks, the
+// class also iterate through those auto anchor fallbacks.
+class OOFCandidateStyleIterator {
+  STACK_ALLOCATED();
+
+ public:
+  explicit OOFCandidateStyleIterator(const LayoutObject& object)
+      : element_(DynamicTo<Element>(object.GetNode())), style_(object.Style()) {
+    Initialize();
+  }
+
+  const ComputedStyle& GetStyle() const {
+    return auto_anchor_style_ ? *auto_anchor_style_ : *style_;
+  }
+
+  absl::optional<wtf_size_t> PositionFallbackIndex() const {
+    return position_fallback_index_;
+  }
+
+  bool HasNextStyle() const {
+    return HasNextAutoAnchorFallback() || HasNextPositionFallback();
+  }
+
+  void MoveToNextStyle() {
+    CHECK(style_);
+
+    if (HasNextAutoAnchorFallback()) {
+      if (!auto_anchor_flippable_in_inline_) {
+        CHECK(auto_anchor_flippable_in_block_);
+        CHECK(!auto_anchor_flip_block_);
+        auto_anchor_flip_block_ = true;
+      } else if (!auto_anchor_flippable_in_block_) {
+        CHECK(auto_anchor_flippable_in_inline_);
+        CHECK(!auto_anchor_flip_inline_);
+        auto_anchor_flip_inline_ = true;
+      } else {
+        if (!auto_anchor_flip_block_) {
+          auto_anchor_flip_block_ = true;
+        } else {
+          CHECK(!auto_anchor_flip_inline_);
+          auto_anchor_flip_inline_ = true;
+          auto_anchor_flip_block_ = false;
+        }
+      }
+      auto_anchor_style_ = CreateFlippedAutoAnchorStyle(
+          *style_, auto_anchor_flip_block_, auto_anchor_flip_inline_);
+      return;
+    }
+
+    CHECK(position_fallback_index_);
+    ++*position_fallback_index_;
+    style_ = element_->StyleForPositionFallback(*position_fallback_index_);
+    CHECK(style_);
+    SetUpAutoAnchorFallbackData();
+  }
+
+ private:
+  bool HasNextAutoAnchorFallback() const {
+    return auto_anchor_flip_block_ != auto_anchor_flippable_in_block_ ||
+           auto_anchor_flip_inline_ != auto_anchor_flippable_in_inline_;
+  }
+
+  bool HasNextPositionFallback() const {
+    return position_fallback_index_ && element_ &&
+           element_->StyleForPositionFallback(*position_fallback_index_ + 1);
+  }
+
+  void Initialize() {
+    if (UNLIKELY(style_->PositionFallback())) {
+      CHECK(RuntimeEnabledFeatures::CSSAnchorPositioningEnabled());
+      if (element_) {
+        if (const ComputedStyle* fallback_style =
+                element_->StyleForPositionFallback(0)) {
+          position_fallback_index_ = 0;
+          style_ = fallback_style;
+        }
+      }
+    }
+    SetUpAutoAnchorFallbackData();
+  }
+
+  void SetUpAutoAnchorFallbackData() {
+    ClearAutoAnchorFallbackData();
+    if (!style_->HasAutoAnchorPositioning()) {
+      return;
+    }
+    // We create a "flipped" fallback in an axis only if one inset uses auto
+    // anchor positioning and the opposite inset is `auto`.
+    // Note that for styles created from a `@try` rule, we create "flipped"
+    // fallback only if the `@try` rule itself uses auto anchor positioning.
+    // Usage in the base style doesn't create fallbacks.
+    bool flippable_in_x = false;
+    if (!position_fallback_index_ ||
+        style_->HasAutoAnchorPositioningInXAxisFromTryBlock()) {
+      flippable_in_x = (style_->UsedLeft().IsAuto() &&
+                        style_->UsedRight().HasAutoAnchorPositioning()) ||
+                       (style_->UsedRight().IsAuto() &&
+                        style_->UsedLeft().HasAutoAnchorPositioning());
+    }
+    bool flippable_in_y = false;
+    if (!position_fallback_index_ ||
+        style_->HasAutoAnchorPositioningInYAxisFromTryBlock()) {
+      flippable_in_y = (style_->UsedTop().IsAuto() &&
+                        style_->UsedBottom().HasAutoAnchorPositioning()) ||
+                       (style_->UsedBottom().IsAuto() &&
+                        style_->UsedTop().HasAutoAnchorPositioning());
+    }
+    if (!flippable_in_x && !flippable_in_y) {
+      return;
+    }
+    const bool is_horizontal = style_->IsHorizontalWritingMode();
+    auto_anchor_flippable_in_inline_ =
+        is_horizontal ? flippable_in_x : flippable_in_y;
+    auto_anchor_flippable_in_block_ =
+        is_horizontal ? flippable_in_y : flippable_in_x;
+  }
+
+  void ClearAutoAnchorFallbackData() {
+    auto_anchor_style_.reset();
+    auto_anchor_flippable_in_block_ = false;
+    auto_anchor_flippable_in_inline_ = false;
+    auto_anchor_flip_block_ = false;
+    auto_anchor_flip_inline_ = false;
+  }
+
+  Element* element_;
+
+  // The current candidate style if no auto anchor fallback is triggered.
+  // Otherwise, the base style for generating auto anchor fallbacks.
+  const ComputedStyle* style_;
+
+  // If the current style is created from an `@try` rule, index of the rule;
+  // Otherwise nullopt.
+  absl::optional<wtf_size_t> position_fallback_index_;
+
+  // Created when the current style is generated by auto anchor positioning
+  // and has any axis flipped compared to the base style.
+  // https://drafts.csswg.org/css-anchor-position-1/#automatic-anchor-fallbacks
+  scoped_refptr<const ComputedStyle> auto_anchor_style_;
+
+  bool auto_anchor_flippable_in_block_ = false;
+  bool auto_anchor_flippable_in_inline_ = false;
+  bool auto_anchor_flip_block_ = false;
+  bool auto_anchor_flip_inline_ = false;
+};
+
 }  // namespace
 
 // static
@@ -1550,50 +1719,34 @@
     const NodeInfo& node_info,
     bool is_first_run,
     const NGLogicalAnchorQueryMap* anchor_queries) {
-  const ComputedStyle* style = &node_info.node.Style();
-
-  // If `@position-fallback` exists, let |TryCalculateOffset| check if the
-  // result fits the available space.
-  Element* element = DynamicTo<Element>(node_info.node.GetDOMNode());
-  absl::optional<wtf_size_t> fallback_index;
-  const ComputedStyle* next_fallback_style = nullptr;
   const LayoutObject* implicit_anchor = nullptr;
   gfx::Vector2dF anchor_scroll_offset;
-  if (element) {
-    if (UNLIKELY(style->PositionFallback())) {
-      DCHECK(RuntimeEnabledFeatures::CSSAnchorPositioningEnabled());
-      next_fallback_style = element->StyleForPositionFallback(0);
-      if (next_fallback_style) {
-        fallback_index = 0;
-      }
-      if (element->GetAnchorScrollData()) {
-        anchor_scroll_offset =
-            element->GetAnchorScrollData()->AccumulatedScrollOffset();
-      }
+  if (Element* element = DynamicTo<Element>(node_info.node.GetDOMNode())) {
+    if (element->GetAnchorScrollData()) {
+      anchor_scroll_offset =
+          element->GetAnchorScrollData()->AccumulatedScrollOffset();
     }
-    if (element->ImplicitAnchorElement())
+    if (element->ImplicitAnchorElement()) {
       implicit_anchor = element->ImplicitAnchorElement()->GetLayoutObject();
+    }
   }
 
   // See anchor_scroll_data.h for documentation of non-overflowing ranges.
   Vector<PhysicalScrollRange> non_overflowing_ranges;
+
+  // If `@position-fallback` exists, let |TryCalculateOffset| check if the
+  // result fits the available space.
+  OOFCandidateStyleIterator iter(*node_info.node.GetLayoutBox());
   absl::optional<OffsetInfo> offset_info;
   while (!offset_info) {
-    if (next_fallback_style) {
-      DCHECK(element);
-      style = next_fallback_style;
-      next_fallback_style =
-          element->StyleForPositionFallback(*fallback_index + 1);
-    }
-
-    const bool try_fit_available_space = next_fallback_style;
+    const bool has_next_fallback_style = iter.HasNextStyle();
     PhysicalScrollRange non_overflowing_range;
-    offset_info = TryCalculateOffset(node_info, *style, anchor_queries,
-                                     implicit_anchor, try_fit_available_space,
+    offset_info = TryCalculateOffset(node_info, iter.GetStyle(), anchor_queries,
+                                     implicit_anchor, has_next_fallback_style,
                                      is_first_run, &non_overflowing_range);
 
     // Also check if it fits the containing block after applying scroll offset.
-    if (offset_info && next_fallback_style) {
+    if (offset_info && has_next_fallback_style) {
       non_overflowing_ranges.push_back(non_overflowing_range);
       if (!non_overflowing_range.Contains(anchor_scroll_offset)) {
         offset_info = absl::nullopt;
@@ -1601,12 +1754,12 @@
     }
 
     if (!offset_info) {
-      ++*fallback_index;
+      iter.MoveToNextStyle();
     }
   }
 
-  if (fallback_index) {
-    offset_info->fallback_index = fallback_index;
+  if (iter.PositionFallbackIndex()) {
+    offset_info->fallback_index = iter.PositionFallbackIndex();
     offset_info->non_overflowing_ranges = std::move(non_overflowing_ranges);
   } else {
     DCHECK(!offset_info->fallback_index);
diff --git a/third_party/blink/renderer/core/page/scrolling/scrolling_test.cc b/third_party/blink/renderer/core/page/scrolling/scrolling_test.cc
index 98432e6..19e3c86 100644
--- a/third_party/blink/renderer/core/page/scrolling/scrolling_test.cc
+++ b/third_party/blink/renderer/core/page/scrolling/scrolling_test.cc
@@ -31,6 +31,7 @@
 #include "cc/input/main_thread_scrolling_reason.h"
 #include "cc/layers/scrollbar_layer_base.h"
 #include "cc/trees/compositor_commit_data.h"
+#include "cc/trees/layer_tree_impl.h"
 #include "cc/trees/property_tree.h"
 #include "cc/trees/scroll_node.h"
 #include "cc/trees/single_thread_proxy.h"
@@ -2725,6 +2726,95 @@
   EXPECT_EQ(100, box->ScrolledContentOffset().top);
 }
 
+TEST_P(ScrollingSimTest, CompositedStickyTracksMainRepaintScroll) {
+  if (!base::FeatureList::IsEnabled(::features::kScrollUnification))
+    return;
+  SetPreferCompositingToLCDText(false);
+
+  String kUrl = "https://example.com/test.html";
+  SimRequest request(kUrl, "text/html");
+  LoadURL(kUrl);
+
+  request.Complete(R"HTML(
+    <style>
+    .spincont { position: absolute;
+                width: 10px; height: 10px; left: 50px; top: 20px; }
+    .spinner { animation: spin 1s linear infinite; }
+    @keyframes spin {
+      0% { transform: rotate(0deg); }
+      100% { transform: rotate(360deg); }
+    }
+    .scroller { position: absolute; overflow: scroll;
+                left: 10px; top: 50px; width: 750px; height: 400px;
+                border: 10px solid #ccc; }
+    .spacer { position: absolute; width: 9000px; height: 100px; }
+    .sticky { position: sticky; background: #eee;
+              left: 50px; top: 50px; width: 600px; height: 200px; }
+    .bluechip { position: absolute; background: blue; color: white;
+                left: 100px; top: 50px; width: 200px; height: 30px; }
+    </style>
+    <div class="spincont"><div class="spinner">X</div></div>
+    <div class="scroller">
+      <div class="spacer">scrolling</div>
+      <div class="sticky"><div class="bluechip">sticky?</div></div>
+    </div>
+  )HTML");
+
+  Compositor().BeginFrame(0.016, /* raster */ true);
+  Element* scroller = GetDocument().QuerySelector(".scroller");
+  LayoutBox* box = To<LayoutBox>(scroller->GetLayoutObject());
+  EXPECT_EQ(0, GetActiveScrollOffset(box->GetScrollableArea()).y());
+
+  WebGestureEvent scroll_begin(
+      WebInputEvent::Type::kGestureScrollBegin, WebInputEvent::kNoModifiers,
+      WebInputEvent::GetStaticTimeStampForTests(), WebGestureDevice::kTouchpad);
+  scroll_begin.SetPositionInWidget(gfx::PointF(200, 200));
+  scroll_begin.data.scroll_begin.delta_x_hint = -100;
+
+  WebGestureEvent scroll_update(
+      WebInputEvent::Type::kGestureScrollUpdate, WebInputEvent::kNoModifiers,
+      WebInputEvent::GetStaticTimeStampForTests(), WebGestureDevice::kTouchpad);
+  scroll_update.SetPositionInWidget(gfx::PointF(200, 200));
+  scroll_update.data.scroll_update.delta_x = -100;
+
+  WebGestureEvent scroll_end(
+      WebInputEvent::Type::kGestureScrollEnd, WebInputEvent::kNoModifiers,
+      WebInputEvent::GetStaticTimeStampForTests(), WebGestureDevice::kTouchpad);
+  scroll_end.SetPositionInWidget(gfx::PointF(200, 200));
+
+  auto& widget = GetWebFrameWidget();
+  widget.DispatchThroughCcInputHandler(scroll_begin);
+  widget.DispatchThroughCcInputHandler(scroll_update);
+  widget.DispatchThroughCcInputHandler(scroll_end);
+
+  // Scroll applied immediately in the scroll tree.
+  EXPECT_EQ(100, GetActiveScrollOffset(box->GetScrollableArea()).x());
+
+  // Tick impl animation to dirty draw properties.
+  static_cast<cc::SingleThreadProxy*>(
+      GetWebFrameWidget().LayerTreeHostForTesting()->proxy())
+      ->BeginImplFrameForTest(Compositor().LastFrameTime() +
+                              base::Seconds(0.016));
+
+  // Update draw properties.
+  cc::LayerTreeHostImpl::FrameData frame;
+  auto* lthi = GetLayerTreeHostImpl();
+  lthi->PrepareToDraw(&frame);
+
+  Element* sticky = GetDocument().QuerySelector(".sticky");
+  cc::ElementId sticky_translation = CompositorElementIdFromUniqueObjectId(
+      sticky->GetLayoutObject()->UniqueId(),
+      CompositorElementIdNamespace::kStickyTranslation);
+  auto* transform_node = lthi->active_tree()
+                             ->property_trees()
+                             ->transform_tree()
+                             .FindNodeFromElementId(sticky_translation);
+
+  // Sticky translation should NOT reflect the updated scroll, since the scroll
+  // is main-repainted and we haven't had a main frame yet.
+  EXPECT_EQ(50, transform_node->to_parent.To2dTranslation().x());
+}
+
 TEST_P(ScrollingSimTest, ScrollTimelineActiveAtBoundary) {
   String kUrl = "https://example.com/test.html";
   SimRequest request(kUrl, "text/html");
diff --git a/third_party/blink/renderer/modules/BUILD.gn b/third_party/blink/renderer/modules/BUILD.gn
index 50f07a2..b0059af9 100644
--- a/third_party/blink/renderer/modules/BUILD.gn
+++ b/third_party/blink/renderer/modules/BUILD.gn
@@ -656,6 +656,7 @@
     "//third_party/blink/renderer:inside_blink",
     "//third_party/blink/renderer/core:blink_core_pch",
   ]
+  defines = [ "ALLOW_IMPLICIT_ATOMIC_STRING_CONVERSIONS" ]
 
   deps = [
     ":modules",
diff --git a/third_party/blink/renderer/modules/mediastream/BUILD.gn b/third_party/blink/renderer/modules/mediastream/BUILD.gn
index 07f1032..f2401da 100644
--- a/third_party/blink/renderer/modules/mediastream/BUILD.gn
+++ b/third_party/blink/renderer/modules/mediastream/BUILD.gn
@@ -168,4 +168,5 @@
   public_deps = [ "//third_party/blink/renderer/platform:test_support" ]
 
   configs += [ "//third_party/blink/renderer:inside_blink" ]
+  defines = [ "ALLOW_IMPLICIT_ATOMIC_STRING_CONVERSIONS" ]
 }
diff --git a/third_party/blink/renderer/modules/webgpu/gpu.idl b/third_party/blink/renderer/modules/webgpu/gpu.idl
index 653c681..ab8a2526 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu.idl
+++ b/third_party/blink/renderer/modules/webgpu/gpu.idl
@@ -20,6 +20,8 @@
 typedef (sequence<GPUIntegerCoordinate> or GPUOrigin2DDict) GPUOrigin2D;
 typedef (sequence<GPUIntegerCoordinate> or GPUOrigin3DDict) GPUOrigin3D;
 
+typedef unsigned long GPUFlagsConstant;
+
 [
     Exposed(Window WebGPU, DedicatedWorker WebGPU),
     SecureContext
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_buffer.idl b/third_party/blink/renderer/modules/webgpu/gpu_buffer.idl
index 0cd9ca65..68f1949 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_buffer.idl
+++ b/third_party/blink/renderer/modules/webgpu/gpu_buffer.idl
@@ -21,7 +21,7 @@
     [CallWith=Isolate] void destroy();
 
     readonly attribute GPUSize64Out size;
-    readonly attribute GPUBufferUsageFlags usage;
+    readonly attribute GPUFlagsConstant usage;
     readonly attribute GPUBufferMapState mapState;
 };
 GPUBuffer includes GPUObjectBase;
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_buffer_usage.idl b/third_party/blink/renderer/modules/webgpu/gpu_buffer_usage.idl
index adff11c..3335b00 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_buffer_usage.idl
+++ b/third_party/blink/renderer/modules/webgpu/gpu_buffer_usage.idl
@@ -9,14 +9,14 @@
     Exposed(Window WebGPU, DedicatedWorker WebGPU),
     SecureContext
 ] namespace GPUBufferUsage {
-    const unsigned long MAP_READ = 1;
-    const unsigned long MAP_WRITE = 2;
-    const unsigned long COPY_SRC = 4;
-    const unsigned long COPY_DST = 8;
-    const unsigned long INDEX = 16;
-    const unsigned long VERTEX = 32;
-    const unsigned long UNIFORM = 64;
-    const unsigned long STORAGE = 128;
-    const unsigned long INDIRECT = 256;
-    const unsigned long QUERY_RESOLVE = 512;
+    const GPUFlagsConstant MAP_READ = 1;
+    const GPUFlagsConstant MAP_WRITE = 2;
+    const GPUFlagsConstant COPY_SRC = 4;
+    const GPUFlagsConstant COPY_DST = 8;
+    const GPUFlagsConstant INDEX = 16;
+    const GPUFlagsConstant VERTEX = 32;
+    const GPUFlagsConstant UNIFORM = 64;
+    const GPUFlagsConstant STORAGE = 128;
+    const GPUFlagsConstant INDIRECT = 256;
+    const GPUFlagsConstant QUERY_RESOLVE = 512;
 };
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_color_write.idl b/third_party/blink/renderer/modules/webgpu/gpu_color_write.idl
index 6786715..41e86e2 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_color_write.idl
+++ b/third_party/blink/renderer/modules/webgpu/gpu_color_write.idl
@@ -9,9 +9,9 @@
     Exposed(Window WebGPU, DedicatedWorker WebGPU),
     SecureContext
 ] namespace GPUColorWrite {
-    const unsigned long RED = 1;
-    const unsigned long GREEN = 2;
-    const unsigned long BLUE = 4;
-    const unsigned long ALPHA = 8;
-    const unsigned long ALL = 15;
+    const GPUFlagsConstant RED = 1;
+    const GPUFlagsConstant GREEN = 2;
+    const GPUFlagsConstant BLUE = 4;
+    const GPUFlagsConstant ALPHA = 8;
+    const GPUFlagsConstant ALL = 15;
 };
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_map_mode.idl b/third_party/blink/renderer/modules/webgpu/gpu_map_mode.idl
index 94ad63b0..130b2582 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_map_mode.idl
+++ b/third_party/blink/renderer/modules/webgpu/gpu_map_mode.idl
@@ -9,6 +9,6 @@
     Exposed(Window WebGPU, DedicatedWorker WebGPU),
     SecureContext
 ] namespace GPUMapMode {
-    const unsigned long READ  = 0x0001;
-    const unsigned long WRITE = 0x0002;
+    const GPUFlagsConstant READ  = 0x0001;
+    const GPUFlagsConstant WRITE = 0x0002;
 };
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_shader_stage.idl b/third_party/blink/renderer/modules/webgpu/gpu_shader_stage.idl
index 98424fb..fa515bc 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_shader_stage.idl
+++ b/third_party/blink/renderer/modules/webgpu/gpu_shader_stage.idl
@@ -9,7 +9,7 @@
     Exposed(Window WebGPU, DedicatedWorker WebGPU),
     SecureContext
 ] namespace GPUShaderStage {
-    const unsigned long VERTEX = 1;
-    const unsigned long FRAGMENT = 2;
-    const unsigned long COMPUTE = 4;
+    const GPUFlagsConstant VERTEX = 1;
+    const GPUFlagsConstant FRAGMENT = 2;
+    const GPUFlagsConstant COMPUTE = 4;
 };
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_texture.idl b/third_party/blink/renderer/modules/webgpu/gpu_texture.idl
index 4b86292..45197280 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_texture.idl
+++ b/third_party/blink/renderer/modules/webgpu/gpu_texture.idl
@@ -18,6 +18,6 @@
     readonly attribute GPUSize32 sampleCount;
     readonly attribute GPUTextureDimension dimension;
     readonly attribute GPUTextureFormat format;
-    readonly attribute GPUTextureUsageFlags usage;
+    readonly attribute GPUFlagsConstant usage;
 };
 GPUTexture includes GPUObjectBase;
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_texture_usage.idl b/third_party/blink/renderer/modules/webgpu/gpu_texture_usage.idl
index 19bcda675..2974cb2 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_texture_usage.idl
+++ b/third_party/blink/renderer/modules/webgpu/gpu_texture_usage.idl
@@ -9,9 +9,9 @@
     Exposed(Window WebGPU, DedicatedWorker WebGPU),
     SecureContext
 ] namespace GPUTextureUsage {
-    const unsigned long COPY_SRC = 1;
-    const unsigned long COPY_DST = 2;
-    const unsigned long TEXTURE_BINDING = 4;
-    const unsigned long STORAGE_BINDING = 8;
-    const unsigned long RENDER_ATTACHMENT = 16;
+    const GPUFlagsConstant COPY_SRC = 1;
+    const GPUFlagsConstant COPY_DST = 2;
+    const GPUFlagsConstant TEXTURE_BINDING = 4;
+    const GPUFlagsConstant STORAGE_BINDING = 8;
+    const GPUFlagsConstant RENDER_ATTACHMENT = 16;
 };
diff --git a/third_party/blink/renderer/modules/webusb/usb_device.cc b/third_party/blink/renderer/modules/webusb/usb_device.cc
index bffe45c..3d562fa 100644
--- a/third_party/blink/renderer/modules/webusb/usb_device.cc
+++ b/third_party/blink/renderer/modules/webusb/usb_device.cc
@@ -414,39 +414,15 @@
     ScriptState* script_state,
     const USBControlTransferParameters* setup,
     ExceptionState& exception_state) {
-  EnsureNoDeviceOrInterfaceChangeInProgress(exception_state);
-  if (exception_state.HadException())
-    return ScriptPromise();
-
-  if (!opened_) {
-    exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
-                                      kOpenRequired);
-    return ScriptPromise();
-  }
-
-  auto parameters = ConvertControlTransferParameters(setup, exception_state);
-  if (!parameters)
-    return ScriptPromise();
-
-  auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(
-      script_state, exception_state.GetContext());
-  ScriptPromise promise = resolver->Promise();
-
-  device_requests_.insert(resolver);
-  device_->ControlTransferOut(
-      std::move(parameters), Vector<uint8_t>(), 0,
-      resolver->WrapCallbackInScriptScope(WTF::BindOnce(
-          &USBDevice::AsyncControlTransferOut, WrapPersistent(this), 0)));
-  return promise;
+  return controlTransferOut(script_state, setup, DOMArrayPiece(),
+                            exception_state);
 }
 
 ScriptPromise USBDevice::controlTransferOut(
     ScriptState* script_state,
     const USBControlTransferParameters* setup,
-    const DOMArrayPiece& data,
+    const DOMArrayPiece& optional_data,
     ExceptionState& exception_state) {
-  DCHECK(!data.IsNull());
-
   EnsureNoDeviceOrInterfaceChangeInProgress(exception_state);
   if (exception_state.HadException())
     return ScriptPromise();
@@ -461,30 +437,32 @@
   if (!parameters)
     return ScriptPromise();
 
-  if (data.IsDetached()) {
-    exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
-                                      kDetachedBuffer);
-    return ScriptPromise();
-  }
+  base::span<const uint8_t> data;
+  if (!optional_data.IsNull()) {
+    if (optional_data.IsDetached()) {
+      exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
+                                        kDetachedBuffer);
+      return ScriptPromise();
+    }
 
-  if (data.ByteLength() > std::numeric_limits<uint32_t>::max()) {
-    exception_state.ThrowDOMException(DOMExceptionCode::kDataError,
-                                      kBufferTooBig);
-    return ScriptPromise();
+    if (optional_data.ByteLength() > std::numeric_limits<uint32_t>::max()) {
+      exception_state.ThrowDOMException(DOMExceptionCode::kDataError,
+                                        kBufferTooBig);
+      return ScriptPromise();
+    }
+
+    data = base::make_span(optional_data.Bytes(), optional_data.ByteLength());
   }
 
   auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(
       script_state, exception_state.GetContext());
   ScriptPromise promise = resolver->Promise();
-
-  auto transfer_length = static_cast<uint32_t>(data.ByteLength());
   device_requests_.insert(resolver);
-  device_->ControlTransferOut(std::move(parameters),
-                              base::make_span(data.Bytes(), data.ByteLength()),
-                              0,
-                              resolver->WrapCallbackInScriptScope(WTF::BindOnce(
-                                  &USBDevice::AsyncControlTransferOut,
-                                  WrapPersistent(this), transfer_length)));
+  device_->ControlTransferOut(
+      std::move(parameters), data, 0,
+      resolver->WrapCallbackInScriptScope(WTF::BindOnce(
+          &USBDevice::AsyncControlTransferOut, WrapPersistent(this),
+          static_cast<uint32_t>(data.size()))));
   return promise;
 }
 
@@ -558,13 +536,12 @@
       script_state, exception_state.GetContext());
   ScriptPromise promise = resolver->Promise();
 
-  auto transfer_length = static_cast<uint32_t>(data.ByteLength());
   device_requests_.insert(resolver);
   device_->GenericTransferOut(
       endpoint_number, base::make_span(data.Bytes(), data.ByteLength()), 0,
       resolver->WrapCallbackInScriptScope(
           WTF::BindOnce(&USBDevice::AsyncTransferOut, WrapPersistent(this),
-                        transfer_length)));
+                        static_cast<uint32_t>(data.ByteLength()))));
   return promise;
 }
 
@@ -609,12 +586,6 @@
     return ScriptPromise();
   }
 
-  if (data.ByteLength() > std::numeric_limits<wtf_size_t>::max()) {
-    exception_state.ThrowDOMException(DOMExceptionCode::kDataError,
-                                      kBufferTooBig);
-    return ScriptPromise();
-  }
-
   auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(
       script_state, exception_state.GetContext());
   ScriptPromise promise = resolver->Promise();
diff --git a/third_party/blink/renderer/modules/webusb/usb_device.h b/third_party/blink/renderer/modules/webusb/usb_device.h
index 8f0002c..3ddaa68f 100644
--- a/third_party/blink/renderer/modules/webusb/usb_device.h
+++ b/third_party/blink/renderer/modules/webusb/usb_device.h
@@ -91,7 +91,7 @@
                                    ExceptionState&);
   ScriptPromise controlTransferOut(ScriptState*,
                                    const USBControlTransferParameters* setup,
-                                   const DOMArrayPiece& data,
+                                   const DOMArrayPiece& optional_data,
                                    ExceptionState&);
   ScriptPromise clearHalt(ScriptState*,
                           String direction,
diff --git a/third_party/blink/renderer/platform/BUILD.gn b/third_party/blink/renderer/platform/BUILD.gn
index a6c6763..b29ad17 100644
--- a/third_party/blink/renderer/platform/BUILD.gn
+++ b/third_party/blink/renderer/platform/BUILD.gn
@@ -2323,7 +2323,10 @@
 
   data_deps = [ ":blink_platform_unittests_data" ]
 
-  defines = [ "INSIDE_BLINK" ]
+  defines = [
+    "INSIDE_BLINK",
+    "ALLOW_IMPLICIT_ATOMIC_STRING_CONVERSIONS",
+  ]
   if (use_minikin_hyphenation) {
     defines += [ "USE_MINIKIN_HYPHENATION" ]
   }
@@ -2411,6 +2414,7 @@
     "//third_party/blink/renderer/platform/wtf:wtf_config",
     "//third_party/blink/renderer:config",
   ]
+  defines = [ "ALLOW_IMPLICIT_ATOMIC_STRING_CONVERSIONS" ]
 
   deps = [
     ":platform",
@@ -2550,6 +2554,7 @@
     "//third_party/blink/web_tests/external/wpt/fonts/math",
     "//testing/libfuzzer/fuzzers/woff2_corpus",
   ]
+  defines = [ "ALLOW_IMPLICIT_ATOMIC_STRING_CONVERSIONS" ]
 }
 
 # Fuzzer for blink::StretchyOperatorShaper
@@ -2563,6 +2568,7 @@
     "//third_party/blink/web_tests/external/wpt/fonts/math",
     "//testing/libfuzzer/fuzzers/woff2_corpus",
   ]
+  defines = [ "ALLOW_IMPLICIT_ATOMIC_STRING_CONVERSIONS" ]
 }
 
 # Fuzzer for blink::MHTMLParser.
@@ -2654,6 +2660,7 @@
     ":platform",
   ]
   dict = "network/HTTPParsersFuzzer.dict"
+  defines = [ "ALLOW_IMPLICIT_ATOMIC_STRING_CONVERSIONS" ]
 }
 
 fuzzer_test("blink_storage_key_fuzzer") {
diff --git a/third_party/blink/renderer/platform/graphics/parkable_image.cc b/third_party/blink/renderer/platform/graphics/parkable_image.cc
index 7ce7f7c7..2b8b8a8 100644
--- a/third_party/blink/renderer/platform/graphics/parkable_image.cc
+++ b/third_party/blink/renderer/platform/graphics/parkable_image.cc
@@ -28,7 +28,7 @@
 
 BASE_FEATURE(kDelayParkingImages,
              "DelayParkingImages",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 
 namespace {
 
diff --git a/third_party/blink/renderer/platform/graphics/parkable_image_manager.cc b/third_party/blink/renderer/platform/graphics/parkable_image_manager.cc
index 3c98c01..a50e9fe 100644
--- a/third_party/blink/renderer/platform/graphics/parkable_image_manager.cc
+++ b/third_party/blink/renderer/platform/graphics/parkable_image_manager.cc
@@ -17,7 +17,7 @@
 
 BASE_FEATURE(kParkableImagesToDisk,
              "ParkableImagesToDisk",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 
 struct ParkableImageManager::Statistics {
   size_t unparked_size = 0;
diff --git a/third_party/blink/renderer/platform/loader/BUILD.gn b/third_party/blink/renderer/platform/loader/BUILD.gn
index 94a2c22..efd0b42 100644
--- a/third_party/blink/renderer/platform/loader/BUILD.gn
+++ b/third_party/blink/renderer/platform/loader/BUILD.gn
@@ -241,6 +241,7 @@
   ]
 
   configs += [ "//third_party/blink/renderer/platform:blink_platform_config" ]
+  defines = [ "ALLOW_IMPLICIT_ATOMIC_STRING_CONVERSIONS" ]
 
   deps = [
     "//base/test:test_support",
diff --git a/third_party/blink/renderer/platform/network/BUILD.gn b/third_party/blink/renderer/platform/network/BUILD.gn
index 39b5619..d40ac99 100644
--- a/third_party/blink/renderer/platform/network/BUILD.gn
+++ b/third_party/blink/renderer/platform/network/BUILD.gn
@@ -94,6 +94,7 @@
   ]
 
   configs += [ "//third_party/blink/renderer/platform:blink_platform_config" ]
+  defines = [ "ALLOW_IMPLICIT_ATOMIC_STRING_CONVERSIONS" ]
 
   deps = [
     "//mojo/public/cpp/test_support:test_utils",
diff --git a/third_party/blink/renderer/platform/wtf/BUILD.gn b/third_party/blink/renderer/platform/wtf/BUILD.gn
index dfa6fb6..c7c04336 100644
--- a/third_party/blink/renderer/platform/wtf/BUILD.gn
+++ b/third_party/blink/renderer/platform/wtf/BUILD.gn
@@ -356,6 +356,7 @@
     "//third_party/blink/renderer:config",
     "//third_party/blink/renderer:blink_pch",
   ]
+  defines = [ "ALLOW_IMPLICIT_ATOMIC_STRING_CONVERSIONS" ]
 
   deps = [
     ":wtf",
diff --git a/third_party/blink/renderer/platform/wtf/text/atomic_string.h b/third_party/blink/renderer/platform/wtf/text/atomic_string.h
index f932d708..5343cd5 100644
--- a/third_party/blink/renderer/platform/wtf/text/atomic_string.h
+++ b/third_party/blink/renderer/platform/wtf/text/atomic_string.h
@@ -41,8 +41,7 @@
 #endif
 
 // TODO(crbug.com/1444094): AtomicString constructors should be explicit.
-#if BLINK_PLATFORM_IMPLEMENTATION || BLINK_CORE_IMPLEMENTATION || \
-    BLINK_MODULES_IMPLEMENTATION
+#if !defined(ALLOW_IMPLICIT_ATOMIC_STRING_CONVERSIONS)
 #define MAYBE_EXPLICIT explicit
 #else
 #define MAYBE_EXPLICIT
diff --git a/third_party/blink/tools/blinkpy/web_tests/port/base.py b/third_party/blink/tools/blinkpy/web_tests/port/base.py
index 1e4fbdb..43e61dc 100644
--- a/third_party/blink/tools/blinkpy/web_tests/port/base.py
+++ b/third_party/blink/tools/blinkpy/web_tests/port/base.py
@@ -2552,7 +2552,7 @@
         normalized_test_name = self.normalize_test_name(test_name)
         for suite in self.virtual_test_suites():
             if normalized_test_name.startswith(suite.full_prefix):
-                return suite.args
+                return suite.args.copy()
         return []
 
     @memoized
diff --git a/third_party/blink/web_tests/NeverFixTests b/third_party/blink/web_tests/NeverFixTests
index c25ff168..f5af64b 100644
--- a/third_party/blink/web_tests/NeverFixTests
+++ b/third_party/blink/web_tests/NeverFixTests
@@ -1963,3 +1963,9 @@
 crbug.com/1378476 [ Mac11-arm64 ] virtual/threaded/fast/idleToBlob/OffscreenCanvas-convertToBlob-webgl-worker.html [ Skip ]
 crbug.com/1378476 [ Mac12-arm64 ] virtual/threaded/fast/idleToBlob/OffscreenCanvas-convertToBlob-webgl-worker.html [ Skip ]
 crbug.com/1378476 [ Mac13-arm64 ] virtual/threaded/fast/idleToBlob/OffscreenCanvas-convertToBlob-webgl-worker.html [ Skip ]
+
+# Chrome for Testing specific tests that only run on Linux due to requiring FlagSpecific overrides.
+crbug.com/1446931 [ Android ] inspector-protocol/heap-profiler/heap-snapshot-exposes-cpp-internals.js [ Skip ]
+crbug.com/1446931 [ Fuchsia ] inspector-protocol/heap-profiler/heap-snapshot-exposes-cpp-internals.js [ Skip ]
+crbug.com/1446931 [ Mac ] inspector-protocol/heap-profiler/heap-snapshot-exposes-cpp-internals.js [ Skip ]
+crbug.com/1446931 [ Win ] inspector-protocol/heap-profiler/heap-snapshot-exposes-cpp-internals.js [ Skip ]
\ No newline at end of file
diff --git a/third_party/blink/web_tests/SlowTests b/third_party/blink/web_tests/SlowTests
index ccf10a04..b0fc946 100644
--- a/third_party/blink/web_tests/SlowTests
+++ b/third_party/blink/web_tests/SlowTests
@@ -39,11 +39,6 @@
 crbug.com/24182 fast/canvas/canvas-toBlob-toDataURL-race-imageEncoder-webp.html [ Slow ]
 crbug.com/24182 fast/dom/SelectorAPI/resig-SelectorsAPI-test.xhtml [ Slow ]
 crbug.com/24182 fast/frames/cached-frame-counter.html [ Slow ]
-crbug.com/24182 [ Linux ] fast/frames/frame-limit.html [ Slow ]
-crbug.com/24182 [ Mac10.15 Release ] fast/frames/frame-limit.html [ Slow ]
-crbug.com/24182 [ Mac11 Release ] fast/frames/frame-limit.html [ Slow ]
-crbug.com/24182 [ Mac12 ] fast/frames/frame-limit.html [ Slow ]
-crbug.com/24182 [ Release Win ] fast/frames/frame-limit.html [ Slow ]
 crbug.com/24182 fast/overflow/lots-of-sibling-inline-boxes.html [ Slow ] # Particularly slow in Debug: >12x slower!
 crbug.com/24182 http/tests/accessibility/slow-document-load.html [ Slow ]
 crbug.com/24182 [ Debug Linux ] http/tests/cache/subresource-expiration-1.html [ Slow ]
@@ -1466,3 +1461,5 @@
 crbug.com/1376679 [ Mac ] virtual/threaded-prefer-compositing/external/wpt/scroll-animations/css/scroll-timeline-dynamic.tentative.html [ Slow ]
 
 crbug.com/1271091 http/tests/security/frameNavigation/xss-ALLOWED-same-etld-plus-1-top-navigation-without-user-gesture.html [ Slow ]
+
+crbug.com/1453341 fast/frames/frame-limit.html [ Slow ]
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 570fac6..c5b6421 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -6339,6 +6339,8 @@
 
 crbug.com/1431228 media/video-delay-load-event.html [ Failure Pass ]
 
+crbug.com/1453691 external/wpt/credential-management/fedcm-context.https.html [ Failure ]
+
 # WebAssembly ESM integration is not yet implemented.
 crbug.com/1380485 external/wpt/wasm/webapi/esm-integration/exported-names.tentative.html [ Crash Failure Pass Timeout ]
 crbug.com/1380485 external/wpt/wasm/webapi/esm-integration/js-wasm-cycle.tentative.html [ Crash Failure Pass Timeout ]
@@ -6700,3 +6702,7 @@
 crbug.com/1453982 [ Mac11-arm64 ] external/wpt/pointerevents/touch-action-with-swipe-dir-change.html?touch [ Pass Timeout ]
 crbug.com/1453982 [ Mac12-arm64 ] external/wpt/pointerevents/touch-action-with-swipe-dir-change.html?touch [ Pass Timeout ]
 crbug.com/1450287 [ Mac13-arm64 ] virtual/scalefactor200/external/wpt/largest-contentful-paint/multiple-redirects-TAO.html [ Failure Pass ]
+crbug.com/1454498 fast/forms/accent-color/radio-accent-color-hover-dark-appearance.html [ Failure Pass ]
+crbug.com/1422685 [ Mac11-arm64 ] fast/canvas-api/offscreencanvas.transferrable-webgl-exception.html [ Failure Pass ]
+crbug.com/1422685 [ Mac12-arm64 ] fast/canvas-api/offscreencanvas.transferrable-webgl-exception.html [ Failure Pass ]
+crbug.com/1422685 [ Mac13-arm64 ] fast/canvas-api/offscreencanvas.transferrable-webgl-exception.html [ Failure Pass ]
diff --git a/third_party/blink/web_tests/external/wpt/credential-management/fedcm-context.https.html b/third_party/blink/web_tests/external/wpt/credential-management/fedcm-context.https.html
new file mode 100644
index 0000000..7675866
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/credential-management/fedcm-context.https.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<title>Federated Credential Management API context tests.</title>
+<link rel="help" href="https://fedidcg.github.io/FedCM">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<body>
+
+<script type="module">
+import {request_options_with_mediation_required,
+        request_options_with_context,
+        fedcm_get_title_promise,
+        fedcm_test} from './support/fedcm-helper.sub.js';
+
+fedcm_test(async t => {
+  navigator.credentials.get(request_options_with_mediation_required());
+  const title = await fedcm_get_title_promise();
+  assert_true(title.toLowerCase().includes('sign in'));
+}, "FedCM call defaults to 'signin' context.");
+
+fedcm_test(async t => {
+  navigator.credentials.get(request_options_with_context("manifest.py", "signup"));
+  const title = await fedcm_get_title_promise(t);
+  assert_true(title.toLowerCase().includes('sign up'));
+}, "FedCM with 'signup' context.");
+
+fedcm_test(async t => {
+  navigator.credentials.get(request_options_with_context("manifest.py", "use"));
+  const title = await fedcm_get_title_promise();
+  assert_true(title.toLowerCase().includes('use'));
+}, "FedCM with 'use' context.");
+
+fedcm_test(async t => {
+  navigator.credentials.get(request_options_with_context("manifest.py", "continue"));
+  const title = await fedcm_get_title_promise();
+  assert_true(title.toLowerCase().includes('continue'));
+}, "FedCM with 'continue' context.");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/credential-management/support/fedcm-helper.sub.js b/third_party/blink/web_tests/external/wpt/credential-management/support/fedcm-helper.sub.js
index 270ac1d7..79136b59 100644
--- a/third_party/blink/web_tests/external/wpt/credential-management/support/fedcm-helper.sub.js
+++ b/third_party/blink/web_tests/external/wpt/credential-management/support/fedcm-helper.sub.js
@@ -78,6 +78,26 @@
   return options;
 }
 
+export function request_options_with_context(manifest_filename, context) {
+  if (manifest_filename === undefined) {
+    manifest_filename = "manifest.py";
+  }
+  const manifest_path = `https://{{host}}:{{ports[https][0]}}/\
+credential-management/support/fedcm/${manifest_filename}`;
+  return {
+    identity: {
+      providers: [{
+        configURL: manifest_path,
+        clientId: '1',
+        nonce: '2'
+      }],
+      context: context
+    },
+    mediation: 'required'
+  };
+}
+
+
 // Test wrapper which does FedCM-specific setup.
 export function fedcm_test(test_func, test_name) {
   promise_test(async t => {
@@ -117,3 +137,15 @@
 
   return options;
 }
+
+export function fedcm_get_title_promise(t) {
+  async function helper(resolve) {
+    try {
+      const title = await window.test_driver.get_fedcm_dialog_title();
+      resolve(title);
+    } catch (ex) {
+      t.step_timeout(100, helper);
+    }
+  }
+  return new Promise(helper);
+}
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/css/css-anchor-position/anchor-position-auto-002.html b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/anchor-position-auto-002.html
new file mode 100644
index 0000000..1c86fe2
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/anchor-position-auto-002.html
@@ -0,0 +1,200 @@
+<!DOCTYPE html>
+<title>Tests automatic anchor fallbacks created from the base style</title>
+<link rel="help" href="https://drafts.csswg.org/css-anchor-position-1/#fallback-automatic">
+<link rel="auto" href="mailto:xiaochengh@chromium.org">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+<script src="support/test-common.js"></script>
+
+<style>
+body {
+  margin: 0;
+}
+
+.cb {
+  position: absolute;
+  width: 500px;
+  height: 500px;
+}
+
+.anchor {
+  position: absolute;
+  width: 100px;
+  height: 100px;
+  background: orange;
+}
+
+.target {
+  position: absolute;
+  width: 100px;
+  height: 100px;
+  background: lime;
+}
+
+.flip-x {
+  top: anchor(top);
+  left: anchor(auto);
+}
+
+.flip-x-same {
+  width: 200px;
+  top: anchor(top);
+  left: anchor(auto-same);
+}
+
+.flip-y {
+  top: anchor(auto);
+  left: anchor(left);
+}
+
+.flip-y-same {
+  height: 200px;
+  left: anchor(left);
+  top: anchor(auto-same);
+}
+
+.flip-both {
+  top: anchor(auto);
+  left: anchor(auto);
+}
+
+#anchor1 {
+  top: 50px;
+  left:200px;
+  anchor-name: --a1;
+}
+
+#anchor2 {
+  bottom: 50px;
+  left: 200px;
+  anchor-name: --a2;
+}
+
+#anchor3 {
+  left: 50px;
+  top: 200px;
+  anchor-name: --a3;
+}
+
+#anchor4 {
+  right: 50px;
+  top: 200px;
+  anchor-name: --a4
+}
+
+#anchor5 {
+  top: 50px;
+  left: 50px;
+  anchor-name: --a5;
+}
+
+#anchor6 {
+  top: 50px;
+  right: 50px;
+  anchor-name: --a6;
+}
+
+#anchor7 {
+  bottom: 50px;
+  left: 50px;
+  anchor-name: --a7;
+}
+
+#anchor8 {
+  bottom: 50px;
+  right: 50px;
+  anchor-name: --a8;
+}
+
+#anchor9 {
+  top: 200px;
+  right: 50px;
+  anchor-name: --a9;
+}
+
+#anchor10 {
+  top: 200px;
+  left: 50px;
+  anchor-name: --a10;
+}
+
+#anchor11 {
+  left: 200px;
+  top: 50px;
+  anchor-name: --a11;
+}
+
+#anchor12 {
+  left: 200px;
+  bottom: 50px;
+  anchor-name: --a12;
+}
+
+#target1 { anchor-default: --a1; }
+#target2 { anchor-default: --a2; }
+#target3 { anchor-default: --a3; }
+#target4 { anchor-default: --a4; }
+#target5 { anchor-default: --a5; }
+#target6 { anchor-default: --a6; }
+#target7 { anchor-default: --a7; }
+#target8 { anchor-default: --a8; }
+#target9 { anchor-default: --a9; }
+#target10 { anchor-default: --a10; }
+#target11 { anchor-default: --a11; }
+#target12 { anchor-default: --a12; }
+
+</style>
+
+<body onload="checkLayoutForAnchorPos('.target')">
+  <!-- Test cases creating flipped fallbacks in one axis -->
+  <div class="cb">
+    <div class="anchor" id="anchor1"></div>
+    <div class="anchor" id="anchor2"></div>
+    <div class="anchor" id="anchor3"></div>
+    <div class="anchor" id="anchor4"></div>
+
+    <div class="target flip-y" id="target1"
+         data-offset-x="200" data-offset-y="150"></div>
+    <div class="target flip-y" id="target2"
+         data-offset-x="200" data-offset-y="250"></div>
+    <div class="target flip-x" id="target3"
+         data-offset-x="150" data-offset-y="200"></div>
+    <div class="target flip-x" id="target4"
+         data-offset-x="250" data-offset-y="200"></div>
+  </div>
+
+  <!-- Test cases creating flipped fallbacks in both axes -->
+  <div class="cb" style="top: 500px">
+    <div class="anchor" id="anchor5"></div>
+    <div class="anchor" id="anchor6"></div>
+    <div class="anchor" id="anchor7"></div>
+    <div class="anchor" id="anchor8"></div>
+
+    <div class="target flip-both" id="target5"
+         data-offset-x="150" data-offset-y="150"></div>
+    <div class="target flip-both" id="target6"
+         data-offset-x="250" data-offset-y="150"></div>
+    <div class="target flip-both" id="target7"
+         data-offset-x="150" data-offset-y="250"></div>
+    <div class="target flip-both" id="target8"
+         data-offset-x="250" data-offset-y="250"></div>
+  </div>
+
+  <!-- Test cases for `auto-same` flipping -->
+  <div class="cb" style="top: 1000px">
+    <div class="anchor" id="anchor9"></div>
+    <div class="anchor" id="anchor10"></div>
+    <div class="anchor" id="anchor11"></div>
+    <div class="anchor" id="anchor12"></div>
+
+    <div class="target flip-x-same" id="target9"
+         data-offset-x="250" data-offset-y="200"></div>
+    <div class="target flip-x-same" id="target10"
+         data-offset-x="50" data-offset-y="200"></div>
+    <div class="target flip-y-same" id="target11"
+         data-offset-x="200" data-offset-y="50"></div>
+    <div class="target flip-y-same" id="target12"
+         data-offset-x="200" data-offset-y="250"></div>
+  </div>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-anchor-position/anchor-position-auto-003.html b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/anchor-position-auto-003.html
new file mode 100644
index 0000000..33ef58b2
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/anchor-position-auto-003.html
@@ -0,0 +1,168 @@
+<!DOCTYPE html>
+<title>Tests automatic anchor fallbacks created from an @try rule</title>
+<link rel="help" href="https://drafts.csswg.org/css-anchor-position-1/#fallback-automatic">
+<link rel="auto" href="mailto:xiaochengh@chromium.org">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+<script src="support/test-common.js"></script>
+
+<style>
+body {
+  margin: 0;
+}
+
+.cb {
+  position: absolute;
+  width: 500px;
+  height: 500px;
+}
+
+.anchor {
+  position: absolute;
+  width: 100px;
+  height: 100px;
+  background: orange;
+}
+
+.target {
+  position: absolute;
+  width: 100px;
+  height: 100px;
+  background: lime;
+  position-fallback: --pf;
+}
+
+@position-fallback --pf {
+  @try {
+    top: anchor(auto);
+    left: anchor(auto);
+  }
+}
+
+#anchor1 {
+  top: 50px;
+  left: 50px;
+  anchor-name: --a1;
+}
+
+#anchor2 {
+  top: 50px;
+  right: 50px;
+  anchor-name: --a2;
+}
+
+#anchor3 {
+  bottom: 50px;
+  left: 50px;
+  anchor-name: --a3;
+}
+
+#anchor4 {
+  bottom: 50px;
+  right: 50px;
+  anchor-name: --a4;
+}
+
+#target1 { anchor-default: --a1; }
+#target2 { anchor-default: --a2; }
+#target3 { anchor-default: --a3; }
+#target4 { anchor-default: --a4; }
+
+#anchor5 {
+  top: 200px;
+  right: 50px;
+  anchor-name: --a5;
+}
+
+#anchor6 {
+  bottom: 50px;
+  left: 200px;
+  anchor-name: --a6;
+}
+
+#target5 {
+  top: anchor(top);
+  left: anchor(auto);
+  anchor-default: --a5;
+  position-fallback: --pf-empty;
+}
+
+#target6 {
+  top: anchor(auto);
+  left: anchor(left);
+  anchor-default: --a6;
+  position-fallback: --pf-empty;
+}
+
+#anchor7 {
+  bottom: 50px;
+  right: 50px;
+  anchor-name: --a7;
+}
+
+#target7 {
+  top: anchor(auto);
+  anchor-default: --a7;
+  position-fallback: --pf-flip-x;
+}
+
+#target8 {
+  left: anchor(auto);
+  anchor-default: --a7;
+  position-fallback: --pf-flip-y;
+}
+
+@position-fallback --pf-empty {
+  @try {}
+}
+
+@position-fallback --pf-flip-x {
+  @try { left: anchor(auto); }
+}
+
+@position-fallback --pf-flip-y {
+  @try { top: anchor(auto); }
+}
+</style>
+
+<body onload="checkLayoutForAnchorPos('.target')">
+  <!-- Test cases creating auto fallbacks from an @try rule -->
+  <div class="cb">
+    <div class="anchor" id="anchor1"></div>
+    <div class="anchor" id="anchor2"></div>
+    <div class="anchor" id="anchor3"></div>
+    <div class="anchor" id="anchor4"></div>
+
+    <div class="target" id="target1"
+         data-offset-x="150" data-offset-y="150"></div>
+    <div class="target" id="target2"
+         data-offset-x="250" data-offset-y="150"></div>
+    <div class="target" id="target3"
+         data-offset-x="150" data-offset-y="250"></div>
+    <div class="target" id="target4"
+         data-offset-x="250" data-offset-y="250"></div>
+  </div>
+
+  <div class="cb" style="top: 500px">
+    <div class="anchor" id="anchor5"></div>
+    <div class="anchor" id="anchor6"></div>
+    <div class="anchor" id="anchor7"></div>
+
+    <!-- Test cases where `anchor(auto)` is in base style, so no auto fallbacks
+         are created from @try rules, and targets end up overflowing the
+         containing block. -->
+    <div class="target" id="target5"
+         data-offset-x="450" data-offset-y="200"></div>
+    <div class="target" id="target6"
+         data-offset-x="200" data-offset-y="450"></div>
+
+    <!-- Test cases where `anchor(auto)` is used in both axes, but the `@try`
+         rule affects only one axis, so we only flip in one axis, and the
+         targets end up overflowing the containing block. -->
+    <div class="target" id="target7"
+         data-offset-x="250" data-offset-y="450"></div>
+    <div class="target" id="target8"
+         data-offset-x="450" data-offset-y="250"></div>
+  </div>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/element/text-styles/2d.text.draw.baseline.hanging-expected.txt b/third_party/blink/web_tests/external/wpt/html/canvas/element/text-styles/2d.text.draw.baseline.hanging-expected.txt
deleted file mode 100644
index 77c8087..0000000
--- a/third_party/blink/web_tests/external/wpt/html/canvas/element/text-styles/2d.text.draw.baseline.hanging-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL Canvas test: 2d.text.draw.baseline.hanging assert_approx_equals: Red channel of the pixel at (5, 5) expected 0 +/- 2 but got 255
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/element/text-styles/2d.text.draw.baseline.hanging.html b/third_party/blink/web_tests/external/wpt/html/canvas/element/text-styles/2d.text.draw.baseline.hanging.html
index 7b3a0a93..3ad15f3 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/element/text-styles/2d.text.draw.baseline.hanging.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/element/text-styles/2d.text.draw.baseline.hanging.html
@@ -33,7 +33,7 @@
       ctx.fillRect(0, 0, 100, 50);
       ctx.fillStyle = '#0f0';
       ctx.textBaseline = 'hanging';
-      ctx.fillText('CC', 0, 12.5);
+      ctx.fillText('CC', 0, -30);
       _assertPixelApprox(canvas, 5,5, 0,255,0,255, 2);
       _assertPixelApprox(canvas, 95,5, 0,255,0,255, 2);
       _assertPixelApprox(canvas, 25,25, 0,255,0,255, 2);
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/element/text-styles/2d.text.draw.baseline.hanging.html.ini b/third_party/blink/web_tests/external/wpt/html/canvas/element/text-styles/2d.text.draw.baseline.hanging.html.ini
deleted file mode 100644
index e86ff83..0000000
--- a/third_party/blink/web_tests/external/wpt/html/canvas/element/text-styles/2d.text.draw.baseline.hanging.html.ini
+++ /dev/null
@@ -1,3 +0,0 @@
-[2d.text.draw.baseline.hanging.html]
-  [Canvas test: 2d.text.draw.baseline.hanging]
-    expected: FAIL
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/manual/the-offscreen-canvas/offscreencanvas.transferrable-expected.txt b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/manual/the-offscreen-canvas/offscreencanvas.transferrable-expected.txt
deleted file mode 100644
index c03435e3..0000000
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/manual/the-offscreen-canvas/offscreencanvas.transferrable-expected.txt
+++ /dev/null
@@ -1,12 +0,0 @@
-This is a testharness.js-based test.
-PASS Test that offscreenCanvas's size is correct after being transferred to a worker.
-FAIL Test that transfer an OffscreenCanvas that has a context throws exception. assert_throws_dom: function "function() {
-            worker.postMessage({offscreenCanvas}, [offscreenCanvas]);
-        }" threw object "DataCloneError: Failed to execute 'postMessage' on 'Worker': An OffscreenCanvas could not be cloned because it had a rendering context." that is not a DOMException InvalidStateError: property "code" is equal to 25, expected 11
-FAIL Test that transfer an OffscreenCanvas twice throws exception. assert_throws_dom: function "function() {
-        worker.postMessage({offscreenCanvas}, [offscreenCanvas]);
-    }" threw object "DataCloneError: Failed to execute 'postMessage' on 'Worker': An OffscreenCanvas could not be cloned because it was detached." that is not a DOMException InvalidStateError: property "code" is equal to 25, expected 11
-PASS Test that calling getContext('2d') on a detached OffscreenCanvas throws exception.
-PASS Test that calling getContext('webgl') on a detached OffscreenCanvas throws exception.
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/manual/the-offscreen-canvas/offscreencanvas.transferrable.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/manual/the-offscreen-canvas/offscreencanvas.transferrable.html
index d321c324..7679603 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/manual/the-offscreen-canvas/offscreencanvas.transferrable.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/manual/the-offscreen-canvas/offscreencanvas.transferrable.html
@@ -39,6 +39,7 @@
     assert_equals(offscreenCanvas.height, 0);
 }, "Test that offscreenCanvas's size is correct after being transferred to a worker.");
 
+
 test(function() {
     function testException(contextType) {
         var worker = makeWorker(document.getElementById("myWorker").textContent);
@@ -49,14 +50,13 @@
         });
     }
     testException('2d');
-    testException('webgl');
-}, "Test that transfer an OffscreenCanvas that has a context throws exception.");
+}, "Test that transfer an OffscreenCanvas that already have a 2d context throws exception.");
 
 test(function() {
     var worker = makeWorker(document.getElementById("myWorker").textContent);
     var offscreenCanvas = new OffscreenCanvas(10, 10);
     worker.postMessage({offscreenCanvas}, [offscreenCanvas]);
-    assert_throws_dom("InvalidStateError", function() {
+    assert_throws_dom("DataCloneError", function() {
         worker.postMessage({offscreenCanvas}, [offscreenCanvas]);
     });
 }, "Test that transfer an OffscreenCanvas twice throws exception.");
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/manual/the-offscreen-canvas/offscreencanvas.transferrable.html.ini b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/manual/the-offscreen-canvas/offscreencanvas.transferrable.html.ini
deleted file mode 100644
index 3f45b01e..0000000
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/manual/the-offscreen-canvas/offscreencanvas.transferrable.html.ini
+++ /dev/null
@@ -1,6 +0,0 @@
-[offscreencanvas.transferrable.html]
-  [Test that transfer an OffscreenCanvas that has a context throws exception.]
-    expected: FAIL
-
-  [Test that transfer an OffscreenCanvas twice throws exception.]
-    expected: FAIL
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/manual/the-offscreen-canvas/offscreencanvas.transferrable.w-expected.txt b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/manual/the-offscreen-canvas/offscreencanvas.transferrable.w-expected.txt
deleted file mode 100644
index 11937f0d..0000000
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/manual/the-offscreen-canvas/offscreencanvas.transferrable.w-expected.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-This is a testharness.js-based test.
-PASS Test that OffscreenCanvas's size is correct after being transferred from a worker.
-FAIL Test that transfer an OffscreenCanvas that has a 2d context throws exception in a worker. assert_true: expected true got false
-FAIL Test that transfer an OffscreenCanvas that has a webgl context throws exception in a worker. assert_true: expected true got false
-PASS Test that transfer an OffscreenCanvas twice throws exception in a worker.
-PASS Test that calling getContext('2d') on a detached OffscreenCanvas throws exception in a worker.
-PASS Test that calling getContext('webgl') on a detached OffscreenCanvas throws exception in a worker.
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/manual/the-offscreen-canvas/offscreencanvas.transferrable.w.html.ini b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/manual/the-offscreen-canvas/offscreencanvas.transferrable.w.html.ini
deleted file mode 100644
index 3ed2e406..0000000
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/manual/the-offscreen-canvas/offscreencanvas.transferrable.w.html.ini
+++ /dev/null
@@ -1,6 +0,0 @@
-[offscreencanvas.transferrable.w.html]
-  [Test that transfer an OffscreenCanvas that has a 2d context throws exception in a worker.]
-    expected: FAIL
-
-  [Test that transfer an OffscreenCanvas that has a webgl context throws exception in a worker.]
-    expected: FAIL
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/text/2d.text.draw.baseline.hanging-expected.txt b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/text/2d.text.draw.baseline.hanging-expected.txt
deleted file mode 100644
index 3994e42..0000000
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/text/2d.text.draw.baseline.hanging-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL OffscreenCanvas test: 2d.text.draw.baseline.hanging assert_approx_equals: Red channel of the pixel at (5, 5) expected 0 +/- 2 but got 255
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/text/2d.text.draw.baseline.hanging.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/text/2d.text.draw.baseline.hanging.html
index 0b822834..0bd15ff 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/text/2d.text.draw.baseline.hanging.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/text/2d.text.draw.baseline.hanging.html
@@ -30,7 +30,7 @@
       ctx.fillRect(0, 0, 100, 50);
       ctx.fillStyle = '#0f0';
       ctx.textBaseline = 'hanging';
-      ctx.fillText('CC', 0, 12.5);
+      ctx.fillText('CC', 0, -30);
       _assertPixelApprox(canvas, 5,5, 0,255,0,255, 2);
       _assertPixelApprox(canvas, 95,5, 0,255,0,255, 2);
       _assertPixelApprox(canvas, 25,25, 0,255,0,255, 2);
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/text/2d.text.draw.baseline.hanging.html.ini b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/text/2d.text.draw.baseline.hanging.html.ini
deleted file mode 100644
index 40e0d4e8..0000000
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/text/2d.text.draw.baseline.hanging.html.ini
+++ /dev/null
@@ -1,3 +0,0 @@
-[2d.text.draw.baseline.hanging.html]
-  [OffscreenCanvas test: 2d.text.draw.baseline.hanging]
-    expected: FAIL
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/text/2d.text.draw.baseline.hanging.worker-expected.txt b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/text/2d.text.draw.baseline.hanging.worker-expected.txt
deleted file mode 100644
index dcc1552..0000000
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/text/2d.text.draw.baseline.hanging.worker-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL 2d assert_approx_equals: Red channel of the pixel at (5, 5) expected 0 +/- 2 but got 255
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/text/2d.text.draw.baseline.hanging.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/text/2d.text.draw.baseline.hanging.worker.js
index fca74d6..4a5ced7b 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/text/2d.text.draw.baseline.hanging.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/text/2d.text.draw.baseline.hanging.worker.js
@@ -26,7 +26,7 @@
       ctx.fillRect(0, 0, 100, 50);
       ctx.fillStyle = '#0f0';
       ctx.textBaseline = 'hanging';
-      ctx.fillText('CC', 0, 12.5);
+      ctx.fillText('CC', 0, -30);
       _assertPixelApprox(canvas, 5,5, 0,255,0,255, 2);
       _assertPixelApprox(canvas, 95,5, 0,255,0,255, 2);
       _assertPixelApprox(canvas, 25,25, 0,255,0,255, 2);
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/text/2d.text.draw.baseline.hanging.worker.js.ini b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/text/2d.text.draw.baseline.hanging.worker.js.ini
deleted file mode 100644
index 8d788bc..0000000
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/text/2d.text.draw.baseline.hanging.worker.js.ini
+++ /dev/null
@@ -1,3 +0,0 @@
-[2d.text.draw.baseline.hanging.worker.html]
-  [2d]
-    expected: FAIL
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/tools/yaml/element/text-styles.yaml b/third_party/blink/web_tests/external/wpt/html/canvas/tools/yaml/element/text-styles.yaml
index 407e3462..d2481ea 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/tools/yaml/element/text-styles.yaml
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/tools/yaml/element/text-styles.yaml
@@ -317,7 +317,7 @@
         ctx.fillRect(0, 0, 100, 50);
         ctx.fillStyle = '#0f0';
         ctx.textBaseline = 'hanging';
-        ctx.fillText('CC', 0, 12.5);
+        ctx.fillText('CC', 0, -30);
         @assert pixel 5,5 ==~ 0,255,0,255; @moz-todo
         @assert pixel 95,5 ==~ 0,255,0,255; @moz-todo
         @assert pixel 25,25 ==~ 0,255,0,255;
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/tools/yaml/offscreen/text.yaml b/third_party/blink/web_tests/external/wpt/html/canvas/tools/yaml/offscreen/text.yaml
index 784a099..90635dd 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/tools/yaml/offscreen/text.yaml
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/tools/yaml/offscreen/text.yaml
@@ -638,7 +638,7 @@
         ctx.fillRect(0, 0, 100, 50);
         ctx.fillStyle = '#0f0';
         ctx.textBaseline = 'hanging';
-        ctx.fillText('CC', 0, 12.5);
+        ctx.fillText('CC', 0, -30);
         @assert pixel 5,5 ==~ 0,255,0,255; @moz-todo
         @assert pixel 95,5 ==~ 0,255,0,255; @moz-todo
         @assert pixel 25,25 ==~ 0,255,0,255;
diff --git a/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/coop-csp-sandbox-navigate.https-expected.txt b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/coop-csp-sandbox-navigate.https-expected.txt
index 1822ba4..142efe3 100644
--- a/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/coop-csp-sandbox-navigate.https-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/coop-csp-sandbox-navigate.https-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-FAIL CSP: sandbox allow-popups allow-scripts allow-same-origin; CSP sandbox popup navigate to Cross-Origin-Opener-Policy document should work assert_true: same-origin check expected true got false
-FAIL CSP: sandbox allow-popups allow-scripts; CSP sandbox popup navigate to Cross-Origin-Opener-Policy document should work assert_throws_dom: same-origin check function "() => { popup.document; }" did not throw
+FAIL CSP: sandbox allow-popups allow-scripts allow-same-origin; CSP sandbox popup navigate to Cross-Origin-Opener-Policy document should work An attempt was made to break through the security policy of the user agent.
+PASS CSP: sandbox allow-popups allow-scripts; CSP sandbox popup navigate to Cross-Origin-Opener-Policy document should work
 Harness: the test ran to completion.
 
diff --git a/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/resources/resource-popup.html b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/resources/resource-popup.html
index 2957e35..ee08bcb4 100644
--- a/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/resources/resource-popup.html
+++ b/third_party/blink/web_tests/external/wpt/html/cross-origin-opener-policy/resources/resource-popup.html
@@ -15,7 +15,14 @@
 const id = setInterval(() => {
   if (win.closed || win.location.href !== 'about:blank') {
     clearInterval(id);
-    bc.postMessage({name: win.name || null, closed: win.closed});
+    const winName = (() => {
+      try {
+        return win.name;
+      } catch (e) {
+        return null;
+      }
+    })();
+    bc.postMessage({name: winName || null, closed: win.closed});
   }
 }, 100);
 </script>
diff --git a/third_party/blink/web_tests/fast/canvas-api/OffscreenCanvas-transferable-exceptions-expected.txt b/third_party/blink/web_tests/fast/canvas-api/OffscreenCanvas-transferable-exceptions-expected.txt
index 08e5c602..ac77709 100644
--- a/third_party/blink/web_tests/fast/canvas-api/OffscreenCanvas-transferable-exceptions-expected.txt
+++ b/third_party/blink/web_tests/fast/canvas-api/OffscreenCanvas-transferable-exceptions-expected.txt
@@ -4,9 +4,9 @@
 
 PASS ctx = offscreenCanvas1.getContext('2d') did not throw exception.
 PASS ctx is an instance of OffscreenCanvasRenderingContext2D
-PASS worker.postMessage({data: offscreenCanvas1}, [offscreenCanvas1]) threw exception DataCloneError: Failed to execute 'postMessage' on 'Worker': An OffscreenCanvas could not be cloned because it had a rendering context..
+PASS worker.postMessage({data: offscreenCanvas1}, [offscreenCanvas1]) threw exception InvalidStateError: Failed to execute 'postMessage' on 'Worker': An OffscreenCanvas could not be transferred because it had a rendering context..
 PASS offscreenCanvas2.transferToImageBitmap() threw exception InvalidStateError: Failed to execute 'transferToImageBitmap' on 'OffscreenCanvas': Cannot transfer an ImageBitmap from a detached OffscreenCanvas.
-PASS worker.postMessage({data: offscreenCanvas2}, [offscreenCanvas2]) threw exception DataCloneError: Failed to execute 'postMessage' on 'Worker': An OffscreenCanvas could not be cloned because it was detached..
+PASS worker.postMessage({data: offscreenCanvas2}, [offscreenCanvas2]) threw exception DataCloneError: Failed to execute 'postMessage' on 'Worker': An OffscreenCanvas could not be transferred because it was detached..
 PASS offscreenCanvas2.getContext('2d') threw exception InvalidStateError: Failed to execute 'getContext' on 'OffscreenCanvas': OffscreenCanvas object is detached.
 PASS ctx.drawImage(offscreenCanvas2, 0, 0) threw exception InvalidStateError: Failed to execute 'drawImage' on 'OffscreenCanvasRenderingContext2D': The image source is detached.
 PASS successfullyParsed is true
diff --git a/third_party/blink/web_tests/fast/canvas-api/offscreencanvas.transferrable-webgl-exception.html b/third_party/blink/web_tests/fast/canvas-api/offscreencanvas.transferrable-webgl-exception.html
new file mode 100644
index 0000000..4119b67
--- /dev/null
+++ b/third_party/blink/web_tests/fast/canvas-api/offscreencanvas.transferrable-webgl-exception.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+</head>
+<body>
+
+<script id="myWorker" type="text/worker">
+
+function test1(offscreenCanvas)
+{
+    return offscreenCanvas.width == 10 && offscreenCanvas.height == 10;
+}
+
+self.onmessage = function(e) {
+    switch(e.data.msg) {
+        case 'test1':
+            self.postMessage(test1(e.data.data));
+            break;
+    }
+};
+
+</script>
+<script>
+function makeWorker(script)
+{
+    var blob = new Blob([script]);
+    return new Worker(URL.createObjectURL(blob));
+}
+
+test(function() {
+    function testException(contextType) {
+        var worker = makeWorker(document.getElementById("myWorker").textContent);
+        var offscreenCanvas = new OffscreenCanvas(10, 10);
+        var ctx = offscreenCanvas.getContext(contextType);
+        assert_throws_dom("InvalidStateError", function() {
+            worker.postMessage({offscreenCanvas}, [offscreenCanvas]);
+        });
+    }
+    testException('webgl');
+}, "Test that transfer an OffscreenCanvas that has a context throws exception.");
+
+</script>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/fast/dom/constructor-in-removed-frame.html b/third_party/blink/web_tests/fast/dom/constructor-in-removed-frame.html
index 9e11dffc..ae37a46 100644
--- a/third_party/blink/web_tests/fast/dom/constructor-in-removed-frame.html
+++ b/third_party/blink/web_tests/fast/dom/constructor-in-removed-frame.html
@@ -19,7 +19,7 @@
             document.body.textContent = 'PASS';
             testRunner.notifyDone();
         };
-        iframeElement.src = 'data:text/html,PASS';
+        iframeElement.srcdoc = '<html><body>PASS</body></html>';
     }
 };
 
diff --git a/third_party/blink/web_tests/fast/frames/frame-limit.html b/third_party/blink/web_tests/fast/frames/frame-limit.html
index 71164f5..4daffad 100644
--- a/third_party/blink/web_tests/fast/frames/frame-limit.html
+++ b/third_party/blink/web_tests/fast/frames/frame-limit.html
@@ -1,10 +1,12 @@
 <script>
+	var n = 125;
+
 	function addFrames() {
 	  var parent = document.createElement('iframe');
 	  parent.id = 'theframe';
 	  document.body.appendChild(parent);
 	
-	  for (i = 0; i < 150; i++) {
+	  for (i = 0; i < n; i++) {
 	    var frame = document.createElement("iframe");
 	    frame.setAttribute("src", "data:text/plain," + i);
 	    frame.style.display = 'none';
@@ -16,18 +18,18 @@
 		if (window.testRunner)
 			testRunner.dumpAsText();
 			
-		// Add 150 frames. 
+		// Add n frames. 
 		addFrames();
 
 		// Remove the parent frame
 		var parent = document.getElementById('theframe');
 		parent.parentNode.removeChild(parent);
 
-		// Add 150 frames again.
+		// Add n frames again.
 		addFrames();
 		var parent = document.getElementById('theframe');
 
-		if (parent.contentWindow.frames.length != 150)
+		if (parent.contentWindow.frames.length != n)
 			return;
 			
 		document.getElementById('result').innerHTML = 'SUCCESS';
diff --git a/third_party/blink/web_tests/flag-specific/chrome-for-testing/inspector-protocol/heap-profiler/heap-snapshot-exposes-cpp-internals-expected.txt b/third_party/blink/web_tests/flag-specific/chrome-for-testing/inspector-protocol/heap-profiler/heap-snapshot-exposes-cpp-internals-expected.txt
new file mode 100644
index 0000000..0076718
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/chrome-for-testing/inspector-protocol/heap-profiler/heap-snapshot-exposes-cpp-internals-expected.txt
@@ -0,0 +1,4 @@
+Tests whether a heap snapshot contains any "InternalNode" or "blink::" objects. This test is useful to check that cppgc_enable_object_names gn arg is enabled.
+InternalNode: Not found
+Blink namespace: Found
+
diff --git a/third_party/blink/web_tests/http/tests/security/detached-window-cross-origin-access-expected.txt b/third_party/blink/web_tests/http/tests/security/detached-window-cross-origin-access-expected.txt
deleted file mode 100644
index f404a334..0000000
--- a/third_party/blink/web_tests/http/tests/security/detached-window-cross-origin-access-expected.txt
+++ /dev/null
@@ -1,10 +0,0 @@
-This is a testharness.js-based test.
-PASS Test that cross-origin access on a detached window throws a SecurityError.
-FAIL method call with detached window receiver should throw assert_throws_dom: function "function () {
-      window.requestAnimationFrame.call(detachedWindow, function () { });
-    }" did not throw
-FAIL attribute access with detached window receiver should throw assert_throws_dom: function "function () {
-      Object.getOwnPropertyDescriptor(window, "document").get.call(detachedWindow);
-    }" did not throw
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/inspector-protocol/heap-profiler/heap-snapshot-exposes-cpp-internals-expected.txt b/third_party/blink/web_tests/inspector-protocol/heap-profiler/heap-snapshot-exposes-cpp-internals-expected.txt
new file mode 100644
index 0000000..6ca4de12
--- /dev/null
+++ b/third_party/blink/web_tests/inspector-protocol/heap-profiler/heap-snapshot-exposes-cpp-internals-expected.txt
@@ -0,0 +1,4 @@
+Tests whether a heap snapshot contains any "InternalNode" or "blink::" objects. This test is useful to check that cppgc_enable_object_names gn arg is enabled.
+InternalNode: Found
+Blink namespace: Not found
+
diff --git a/third_party/blink/web_tests/inspector-protocol/heap-profiler/heap-snapshot-exposes-cpp-internals.js b/third_party/blink/web_tests/inspector-protocol/heap-profiler/heap-snapshot-exposes-cpp-internals.js
new file mode 100644
index 0000000..d270462
--- /dev/null
+++ b/third_party/blink/web_tests/inspector-protocol/heap-profiler/heap-snapshot-exposes-cpp-internals.js
@@ -0,0 +1,42 @@
+(async function(testRunner) {
+    const {page, session, dp} = await testRunner.startBlank(
+      'Tests whether a heap snapshot contains any "InternalNode" or "blink::" objects. This test is useful to check that cppgc_enable_object_names gn arg is enabled.');
+
+    await dp.Profiler.enable();
+
+    // Take a heap snapshot.
+    let snapshot_string = '';
+    function onChunk(message) {
+      snapshot_string += message.params.chunk;
+    }
+    dp.HeapProfiler.onAddHeapSnapshotChunk(onChunk)
+    await dp.HeapProfiler.takeHeapSnapshot({ reportProgress: false, exposeInternals: true });
+    const s = JSON.parse(snapshot_string);
+
+    // Iterate all nodes and check the name against "InternalNode" and Blink C++ namespace "blink::".
+    const nameIndex = s.snapshot.meta.node_fields.indexOf('name');
+    const nodeLength = s.snapshot.meta.node_fields.length;
+    let foundInternalNode = false;
+    let foundBlinkNameSpace = false;
+    for (let i = 0; i < (nodeLength * s.snapshot.node_count); i += nodeLength) {
+      let nodeName = s.strings[s.nodes[i + nameIndex]];
+      if (nodeName === 'InternalNode') {
+        foundInternalNode = true;
+      } else if (nodeName.startsWith('blink::')) {
+        foundBlinkNameSpace = true;
+      }
+    }
+
+    if (foundInternalNode) {
+      testRunner.log('InternalNode: Found');
+    } else {
+      testRunner.log('InternalNode: Not found');
+    }
+    if (foundBlinkNameSpace) {
+      testRunner.log('Blink namespace: Found');
+    } else {
+      testRunner.log('Blink namespace: Not found');
+    }
+
+    testRunner.completeTest();
+  })
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/external/wpt/html/canvas/offscreen/manual/the-offscreen-canvas/offscreencanvas.transferrable.w-expected.txt b/third_party/blink/web_tests/platform/mac-mac11-arm64/external/wpt/html/canvas/offscreen/manual/the-offscreen-canvas/offscreencanvas.transferrable.w-expected.txt
deleted file mode 100644
index 9ada9e2..0000000
--- a/third_party/blink/web_tests/platform/mac-mac11-arm64/external/wpt/html/canvas/offscreen/manual/the-offscreen-canvas/offscreencanvas.transferrable.w-expected.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-This is a testharness.js-based test.
-PASS Test that OffscreenCanvas's size is correct after being transferred from a worker.
-FAIL Test that transfer an OffscreenCanvas that has a 2d context throws exception in a worker. assert_true: expected true got false
-FAIL Test that transfer an OffscreenCanvas that has a webgl context throws exception in a worker. assert_true: expected true got object "[object OffscreenCanvas]"
-PASS Test that transfer an OffscreenCanvas twice throws exception in a worker.
-PASS Test that calling getContext('2d') on a detached OffscreenCanvas throws exception in a worker.
-PASS Test that calling getContext('webgl') on a detached OffscreenCanvas throws exception in a worker.
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/external/wpt/html/canvas/offscreen/manual/the-offscreen-canvas/offscreencanvas.transferrable.w-expected.txt b/third_party/blink/web_tests/platform/mac-mac12-arm64/external/wpt/html/canvas/offscreen/manual/the-offscreen-canvas/offscreencanvas.transferrable.w-expected.txt
deleted file mode 100644
index 9ada9e2..0000000
--- a/third_party/blink/web_tests/platform/mac-mac12-arm64/external/wpt/html/canvas/offscreen/manual/the-offscreen-canvas/offscreencanvas.transferrable.w-expected.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-This is a testharness.js-based test.
-PASS Test that OffscreenCanvas's size is correct after being transferred from a worker.
-FAIL Test that transfer an OffscreenCanvas that has a 2d context throws exception in a worker. assert_true: expected true got false
-FAIL Test that transfer an OffscreenCanvas that has a webgl context throws exception in a worker. assert_true: expected true got object "[object OffscreenCanvas]"
-PASS Test that transfer an OffscreenCanvas twice throws exception in a worker.
-PASS Test that calling getContext('2d') on a detached OffscreenCanvas throws exception in a worker.
-PASS Test that calling getContext('webgl') on a detached OffscreenCanvas throws exception in a worker.
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/platform/mac-mac13-arm64/external/wpt/html/canvas/offscreen/manual/the-offscreen-canvas/offscreencanvas.transferrable.w-expected.txt b/third_party/blink/web_tests/platform/mac-mac13-arm64/external/wpt/html/canvas/offscreen/manual/the-offscreen-canvas/offscreencanvas.transferrable.w-expected.txt
deleted file mode 100644
index 9ada9e2..0000000
--- a/third_party/blink/web_tests/platform/mac-mac13-arm64/external/wpt/html/canvas/offscreen/manual/the-offscreen-canvas/offscreencanvas.transferrable.w-expected.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-This is a testharness.js-based test.
-PASS Test that OffscreenCanvas's size is correct after being transferred from a worker.
-FAIL Test that transfer an OffscreenCanvas that has a 2d context throws exception in a worker. assert_true: expected true got false
-FAIL Test that transfer an OffscreenCanvas that has a webgl context throws exception in a worker. assert_true: expected true got object "[object OffscreenCanvas]"
-PASS Test that transfer an OffscreenCanvas twice throws exception in a worker.
-PASS Test that calling getContext('2d') on a detached OffscreenCanvas throws exception in a worker.
-PASS Test that calling getContext('webgl') on a detached OffscreenCanvas throws exception in a worker.
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/resources/testdriver-vendor.js b/third_party/blink/web_tests/resources/testdriver-vendor.js
index 42cc093..1a6cc6d3 100644
--- a/third_party/blink/web_tests/resources/testdriver-vendor.js
+++ b/third_party/blink/web_tests/resources/testdriver-vendor.js
@@ -475,6 +475,11 @@
     }
   };
 
+  window.test_driver_internal.get_fedcm_dialog_title = async function() {
+    // TODO(crbug.com/1453691): implement the title getter.
+    return "";
+  }
+
   // Enable automation so we don't wait for user input on unimplemented APIs
   window.test_driver_internal.in_automation = true;
 
diff --git a/tools/metrics/actions/actions.xml b/tools/metrics/actions/actions.xml
index 8f5e518..1d8f4344 100644
--- a/tools/metrics/actions/actions.xml
+++ b/tools/metrics/actions/actions.xml
@@ -3619,6 +3619,15 @@
   </description>
 </action>
 
+<action name="ApplicationShortcut.LensPressedFromSpotlight">
+  <owner>schechter@google.com</owner>
+  <owner>hujasonx@google.com</owner>
+  <description>
+    User tapped the Lens search option of Chrome for iOS from iOS Spotlight
+    search.
+  </description>
+</action>
+
 <action name="ApplicationShortcut.NewIncognitoSearchPressed">
   <owner>pschaffner@chromium.org</owner>
   <description>
@@ -11625,12 +11634,21 @@
   <description>Please enter the description of this user action.</description>
 </action>
 
+<action name="IOS.DefaultBrowserFullscreenPromo.Cancel">
+  <owner>gayane@chromium.org</owner>
+  <owner>bling-get-set-up@google.com</owner>
+  <description>
+    The user dismissed the default browser fullscreen promo by pressing on
+    secondary button. iOS only.
+  </description>
+</action>
+
 <action name="IOS.DefaultBrowserFullscreenPromo.Dismissed">
   <owner>thegreenfrog@chromium.org</owner>
   <owner>rohitrao@chromium.org</owner>
   <description>
-    The user dismissed the default browser fullscreen promo modal without any
-    action. iOS only.
+    The user dismissed the default browser fullscreen promo by swipe down. iOS
+    only.
   </description>
 </action>
 
@@ -19719,6 +19737,24 @@
   </description>
 </action>
 
+<action name="MobileLockdownModeSettingsBack">
+  <owner>joemerramos@chromium.org</owner>
+  <owner>ajuma@chromium.org</owner>
+  <description>
+    Reported when user goes back from Safe Browsing Standard Protection Settings
+    UI to root Settings screen. iOS only.
+  </description>
+</action>
+
+<action name="MobileLockdownModeSettingsClose">
+  <owner>joemerramos@chromium.org</owner>
+  <owner>ajuma@chromium.org</owner>
+  <description>
+    Reported when Safe Browsing Standard Protection Settings UI was dismissed.
+    iOS only.
+  </description>
+</action>
+
 <action name="MobileMenuAddToBookmarks">
   <owner>aurimas@chromium.org</owner>
   <description>
@@ -34070,6 +34106,24 @@
   </description>
 </action>
 
+<action name="StatusArea_ColorCorrectionDisabled">
+  <owner>katie@chromium.org</owner>
+  <owner>ash/accessibility/OWNERS</owner>
+  <description>
+    Counts the number of times the user has turned off color correction settings
+    from the system tray.
+  </description>
+</action>
+
+<action name="StatusArea_ColorCorrectionEnabled">
+  <owner>katie@chromium.org</owner>
+  <owner>ash/accessibility/OWNERS</owner>
+  <description>
+    Counts the number of times the user has turned on color correction settings
+    from the system tray.
+  </description>
+</action>
+
 <action name="StatusArea_DictationDisabled">
   <owner>anastasi@google.com</owner>
   <description>Ash system menu: Accessibility: Disable Dictation.</description>
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index dafee4a..9fc70cd0 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -3894,6 +3894,7 @@
   <int value="12" label="Notification (Plugin VM)"/>
   <int value="13" label="App Management main view (Borealis)"/>
   <int value="14" label="Page info view"/>
+  <int value="15" label="Privacy indicators notification settings button"/>
 </enum>
 
 <enum name="AppManagementUserAction">
@@ -7456,6 +7457,14 @@
   <int value="72" label="Server card of a deduped local card selected (once)"/>
   <int value="73" label="Server card of a deduped local card filled (once)"/>
   <int value="74" label="Server card of a deduped local card submitted (once)"/>
+  <int value="75" label="Filled card suggestion had metadata (once)"/>
+  <int value="76" label="Filled card suggestion did not have metadata (once)"/>
+  <int value="77"
+      label="A card was about to be submitted after a suggestion with
+             metadata was filled (once)"/>
+  <int value="78"
+      label="A card was about to be submitted after a suggestion without
+             metadata was filled (once)"/>
 </enum>
 
 <enum name="AutofillFormSubmittedState">
@@ -14390,6 +14399,7 @@
   <int value="2" label="Widget"/>
   <int value="3" label="Tasks Surface"/>
   <int value="4" label="Keyboard"/>
+  <int value="5" label="Spotlight"/>
 </enum>
 
 <enum name="CameraPreviewSnapPosition">
@@ -45542,6 +45552,11 @@
   <int value="7" label="Allowed navigation, user gesture, ad frame"/>
 </enum>
 
+<enum name="FrameJankStatus">
+  <int value="0" label="Janky"/>
+  <int value="1" label="NonJanky"/>
+</enum>
+
 <enum name="FramePrioritiesSeen">
   <obsolete>
     Obsoleted on 2021-01-22 as corresponding experiment code was removed and the
@@ -54288,6 +54303,7 @@
   <int value="0" label="Action Button"/>
   <int value="1" label="Cancel"/>
   <int value="2" label="Remind Me Later"/>
+  <int value="3" label="Dismiss"/>
 </enum>
 
 <enum name="IOSDefaultBrowserPromoNonModalAction">
@@ -54305,8 +54321,8 @@
   <int value="3" label="Set Up List"/>
 </enum>
 
-<enum name="IOSDefaultBrowserTailoredPromoType">
-  <int value="0" label="Unkown"/>
+<enum name="IOSDefaultBrowserPromoType">
+  <int value="0" label="General"/>
   <int value="1" label="Made for iOS"/>
   <int value="2" label="Stay Safe"/>
   <int value="3" label="All Tabs"/>
@@ -54805,6 +54821,7 @@
   <int value="2" label="Voice Search"/>
   <int value="3" label="QR Code Scanner"/>
   <int value="4" label="Set Default Browser"/>
+  <int value="5" label="Search with Lens"/>
 </enum>
 
 <enum name="IOSSpotlightAvailability">
@@ -59225,6 +59242,8 @@
   <int value="-1954246274"
       label="enable-experimental-accessibility-switch-access"/>
   <int value="-1954122445" label="PrintManagementSetupAssistance:disabled"/>
+  <int value="-1953840665"
+      label="SearchCustomizableShortcutsInLauncher:enabled"/>
   <int value="-1953145846" label="TargetEmbeddingLookalikes:disabled"/>
   <int value="-1953121360" label="EphemeralTab:disabled"/>
   <int value="-1951046208" label="SidePanelPrototype:disabled"/>
@@ -61250,6 +61269,7 @@
   <int value="-907481658" label="ArcMouseWheelSmoothScroll:enabled"/>
   <int value="-907234795" label="NewAudioRenderingMixingStrategy:disabled"/>
   <int value="-905538983" label="SimplifyHttpsIndicator:disabled"/>
+  <int value="-904337228" label="CameraMicPreview:enabled"/>
   <int value="-903524547" label="DiscoFeedEndpoint:disabled"/>
   <int value="-903359589" label="CrOSEnforceSystemAec:enabled"/>
   <int value="-902227224" label="MlRelevanceScoring:disabled"/>
@@ -63251,6 +63271,7 @@
   <int value="141830539" label="MediaAppPdfSignature:enabled"/>
   <int value="143725809" label="DownloadProgressInfoBar:enabled"/>
   <int value="144868136" label="DesktopPWAsNotificationIconAndTitle:enabled"/>
+  <int value="145296783" label="CameraMicPreview:disabled"/>
   <int value="146229312" label="WebPaymentsExperimentalFeatures:enabled"/>
   <int value="146553675" label="BookmarkBottomSheet:disabled"/>
   <int value="147342055" label="ChromeHomeClearUrlOnOpen:disabled"/>
@@ -63495,6 +63516,8 @@
   <int value="282692100" label="SubframeShutdownDelay:disabled"/>
   <int value="283182071" label="SecurityInterstitialsDarkMode:enabled"/>
   <int value="283232244" label="OmniboxUIExperimentNarrowDropdown:enabled"/>
+  <int value="284756778"
+      label="SearchCustomizableShortcutsInLauncher:disabled"/>
   <int value="285387302" label="AvatarToolbarButton:enabled"/>
   <int value="286001833" label="TabGroupsNewBadgePromo:enabled"/>
   <int value="286621673" label="FedCmIframeSupport:enabled"/>
@@ -91419,8 +91442,8 @@
   <int value="4" label="kTriggerExtensionRequest">
     A new extension request is added
   </int>
-  <int value="5" label="Obsolete_kExtensionRequestRealTime">
-    (Obsolete) A new extension request is added and uploaded with ERP.
+  <int value="5" label="kExtensionRequestRealTime">
+    A new extension request is added and uploaded with ERP.
   </int>
   <int value="6" label="kTriggerManual">User upload report manually</int>
 </enum>
@@ -99983,6 +100006,7 @@
              and top-level origins, feature enabled)"/>
   <int value="6" label="Dismissed by user"/>
   <int value="7" label="Reused previous decision (made by user)"/>
+  <int value="8" label="Denied by top-level user interaction heuristic"/>
 </enum>
 
 <enum name="StorageAccessInputState">
diff --git a/tools/metrics/histograms/metadata/android/histograms.xml b/tools/metrics/histograms/metadata/android/histograms.xml
index b1a46d67..706cf56 100644
--- a/tools/metrics/histograms/metadata/android/histograms.xml
+++ b/tools/metrics/histograms/metadata/android/histograms.xml
@@ -1802,6 +1802,41 @@
   </summary>
 </histogram>
 
+<histogram name="Android.Jank.FrameDuration" units="ms"
+    expires_after="2024-04-30">
+  <owner>oksamyt@chromium.org</owner>
+  <owner>woa-performance@google.com</owner>
+  <summary>
+    Amount of time it takes to draw Android UI frames as measured by Android's
+    FrameMetrics API. Each frame's draw duration is stored individually.
+
+    The samples for this metric (along with Android.Jank.JankyFrames) are
+    manually passed from Java to C++ to be recorded. This is done instead of
+    recording in real time from Java due to the large number of generated
+    samples (potentially ~60 per second). Recording them directly from Java
+    would result in a JNI call for each sample which could cause performance
+    issues.
+
+    FrameMetrics only reports frames with Android UI updates. An idle input
+    field would report only 2 frames per second due to the cursor blink
+    animation. Scrolling through a web page may record no frames if no Android
+    UI (like the address bar) updates.
+  </summary>
+</histogram>
+
+<histogram name="Android.Jank.FrameJankStatus" enum="FrameJankStatus"
+    expires_after="2024-04-30">
+  <owner>oksamyt@chromium.org</owner>
+  <owner>woa-performance@google.com</owner>
+  <summary>
+    Jankiness of frames according to the Android's FrameMetrics API. Samples are
+    recorded into the Janky bucket for frames for which their total duration
+    exceeded the given deadline and into the NonJanky bucket for all other
+    frames. This metric is recorded at the same time as
+    Android.Jank.FrameDuration histogram.
+  </summary>
+</histogram>
+
 <histogram name="Android.KernelVersion" enum="AndroidKernelVersion"
     expires_after="2023-11-12">
   <owner>rsesek@chromium.org</owner>
@@ -4501,17 +4536,6 @@
   </summary>
 </histogram>
 
-<histogram name="Android.WebView.onReceivedHttpError.StatusCode"
-    enum="HttpResponseCode" expires_after="2023-04-19">
-  <owner>ntfschr@chromium.org</owner>
-  <owner>src/android_webview/OWNERS</owner>
-  <summary>
-    The WebViewClient HTTP response status code as returned by the
-    onReceivedHttpError callback. This callback is only called for HTTP errors,
-    so this does not include any successful HTTP responses (code 200).
-  </summary>
-</histogram>
-
 <histogram name="Android.WebView.OnRenderProcessGoneResult"
     enum="AndroidWebViewRenderProcessGoneResult" expires_after="2023-06-30">
   <owner>ntfschr@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/ash/histograms.xml b/tools/metrics/histograms/metadata/ash/histograms.xml
index 9f5ac5e5..ad91261 100644
--- a/tools/metrics/histograms/metadata/ash/histograms.xml
+++ b/tools/metrics/histograms/metadata/ash/histograms.xml
@@ -4863,12 +4863,17 @@
   </summary>
 </histogram>
 
-<histogram name="Ash.Personalization.AmbientMode.AnimationTheme"
+<histogram name="Ash.Personalization.AmbientMode.AnimationTheme2"
     enum="AmbientModeAnimationTheme" expires_after="2023-10-22">
   <owner>jasontt@chromium.org</owner>
   <owner>assistive-eng@google.com</owner>
   <summary>
-    Emitted when a user selects an animation theme in screensaver subpage.
+    Emitted when a user selects an animation theme in screensaver subpage or
+    when screensaver is first enabled. In the latter case, the theme is recorded
+    here even without the user explicitly selecting it in the screensaver
+    subpage. The theme in this case will either be the default chosen by the
+    system, or the theme that the user had selected when they last disabled
+    screensaver.
   </summary>
 </histogram>
 
@@ -4882,14 +4887,17 @@
   </summary>
 </histogram>
 
-<histogram name="Ash.Personalization.AmbientMode.Video" enum="AmbientModeVideo"
-    expires_after="2023-10-22">
+<histogram name="Ash.Personalization.AmbientMode.Video2"
+    enum="AmbientModeVideo" expires_after="2023-10-22">
   <owner>jasontt@chromium.org</owner>
   <owner>esum@google.com</owner>
   <owner>assistive-eng@google.com</owner>
   <summary>
-    Emitted when a user selects the video theme or a new video in the
-    screensaver subpage.
+    Emitted when a user selects the video theme, selects a new video in the
+    screensaver subpage, or enables screensaver with a video theme automatically
+    active. The latter can happen if a video theme is the default on that
+    particular device model, or if the user had selected a video theme in the
+    past when they last disabled screensaver.
   </summary>
 </histogram>
 
diff --git a/tools/metrics/histograms/metadata/autofill/histograms.xml b/tools/metrics/histograms/metadata/autofill/histograms.xml
index 5f2787a..667b375 100644
--- a/tools/metrics/histograms/metadata/autofill/histograms.xml
+++ b/tools/metrics/histograms/metadata/autofill/histograms.xml
@@ -1251,6 +1251,8 @@
   <token key="FormEventWithMetadata">
     <variant name="FilledWithMetadata"
         summary="A card with issuer id was filled"/>
+    <variant name="FilledWithMetadataOnce"
+        summary="A card with issuer id was filled, logged once per page load"/>
     <variant name="SelectedWithMetadata"
         summary="A card with issuer id was selected"/>
     <variant name="SelectedWithMetadataOnce"
diff --git a/tools/metrics/histograms/metadata/gpu/histograms.xml b/tools/metrics/histograms/metadata/gpu/histograms.xml
index 2b3dc455..cc842c2 100644
--- a/tools/metrics/histograms/metadata/gpu/histograms.xml
+++ b/tools/metrics/histograms/metadata/gpu/histograms.xml
@@ -1441,6 +1441,16 @@
   </summary>
 </histogram>
 
+<histogram name="GPU.TransferCache.MaxHistoricalTimeSinceLastUse" units="ms"
+    expires_after="2024-01-31">
+  <owner>boliu@chromium.org</owner>
+  <owner>graphics-dev@chromium.org</owner>
+  <summary>
+    When a transfer cache is found, record max historical time between reuses.
+    Recorded on every successful transfer cache look up.
+  </summary>
+</histogram>
+
 <histogram name="GPU.TransferCache.ReusedTimes" units="Reuses"
     expires_after="2024-01-31">
   <owner>boliu@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/ios/histograms.xml b/tools/metrics/histograms/metadata/ios/histograms.xml
index 506ea7a..ee2bcdc3 100644
--- a/tools/metrics/histograms/metadata/ios/histograms.xml
+++ b/tools/metrics/histograms/metadata/ios/histograms.xml
@@ -581,8 +581,15 @@
   </summary>
 </histogram>
 
+<histogram name="IOS.DefaultBrowserPromo.Shown"
+    enum="IOSDefaultBrowserPromoType" expires_after="2023-12-01">
+  <owner>gayane@chromium.org</owner>
+  <owner>bling-get-set-up@google.com</owner>
+  <summary>The default browser promo type shown to the user.</summary>
+</histogram>
+
 <histogram name="IOS.DefaultBrowserPromo.TailoredFullscreen.{Action}"
-    enum="IOSDefaultBrowserTailoredPromoType" expires_after="2023-12-01">
+    enum="IOSDefaultBrowserPromoType" expires_after="2023-12-01">
   <owner>sebsg@chromium.org</owner>
   <owner>bling-get-set-up@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/mobile/histograms.xml b/tools/metrics/histograms/metadata/mobile/histograms.xml
index 66189bd..69f8d65 100644
--- a/tools/metrics/histograms/metadata/mobile/histograms.xml
+++ b/tools/metrics/histograms/metadata/mobile/histograms.xml
@@ -454,6 +454,17 @@
   </token>
 </histogram>
 
+<histogram name="Mobile.Spotlight.LensSupportStatus"
+    enum="IOSLensSupportStatus" expires_after="2023-08-26">
+  <owner>hujasonx@google.com</owner>
+  <owner>lens-in-bling-team@google.com</owner>
+  <summary>
+    Whether or not Lens is supported in iOS spotlight search, and if not, the
+    reason why. Recorded once when the spotlight actions are populated, which
+    happens after app startup.
+  </summary>
+</histogram>
+
 <histogram name="Mobile.SystemNotification.Action.Click"
     enum="SystemNotificationActionType" expires_after="never">
 <!-- expires-never: Core Android notification metrics. Used by multiple teams. -->
diff --git a/tools/metrics/histograms/metadata/net/histograms.xml b/tools/metrics/histograms/metadata/net/histograms.xml
index 9f58658..db7beb1 100644
--- a/tools/metrics/histograms/metadata/net/histograms.xml
+++ b/tools/metrics/histograms/metadata/net/histograms.xml
@@ -1357,9 +1357,9 @@
 </histogram>
 
 <histogram name="Net.DNS.ProbeSequence.ConfigChange.Failure.AttemptTime"
-    units="ms" expires_after="2023-12-10">
+    units="ms" expires_after="2023-11-30">
   <owner>awillia@chromium.org</owner>
-  <owner>doh-core@google.com</owner>
+  <owner>src/net/dns/OWNERS</owner>
   <summary>
     Duration of time taken by a probe attempt to fail. The time is measured
     since the probe sequence was initiated due to a DNS config change.
@@ -1367,9 +1367,9 @@
 </histogram>
 
 <histogram name="Net.DNS.ProbeSequence.ConfigChange.Success.AttemptTime"
-    units="ms" expires_after="2023-10-08">
+    units="ms" expires_after="2023-11-30">
   <owner>awillia@chromium.org</owner>
-  <owner>doh-core@google.com</owner>
+  <owner>src/net/dns/OWNERS</owner>
   <summary>
     Duration of time taken by a probe attempt to succeed. The time is measured
     since the probe sequence was initiated due to a DNS config change.
@@ -1377,9 +1377,9 @@
 </histogram>
 
 <histogram name="Net.DNS.ProbeSequence.NetworkChange.Failure.AttemptTime"
-    units="ms" expires_after="2023-06-11">
+    units="ms" expires_after="2023-11-30">
   <owner>awillia@chromium.org</owner>
-  <owner>doh-core@google.com</owner>
+  <owner>src/net/dns/OWNERS</owner>
   <summary>
     Duration of time taken by a probe attempt to fail. The time is measured
     since the probe sequence was initiated due to a network change.
@@ -1387,9 +1387,9 @@
 </histogram>
 
 <histogram name="Net.DNS.ProbeSequence.NetworkChange.Success.AttemptTime"
-    units="ms" expires_after="2023-06-11">
+    units="ms" expires_after="2023-11-30">
   <owner>awillia@chromium.org</owner>
-  <owner>doh-core@google.com</owner>
+  <owner>src/net/dns/OWNERS</owner>
   <summary>
     Duration of time taken by a probe attempt to succeed. The time is measured
     since the probe sequence was initiated due to a network change.
@@ -1627,9 +1627,9 @@
 </histogram>
 
 <histogram name="Net.DNS.UpgradeConfig.DotUpgradeSucceeded" enum="Boolean"
-    expires_after="2023-12-10">
+    expires_after="2024-06-12">
   <owner>horo@chromium.org</owner>
-  <owner>doh-core@google.com</owner>
+  <owner>src/net/dns/OWNERS</owner>
   <summary>
     True if upgrade to DoH from a DoT hostname was attempted and succeeded.
     False if it was attempted and failed.
@@ -1637,20 +1637,23 @@
 </histogram>
 
 <histogram name="Net.DNS.UpgradeConfig.HasPublicInsecureNameserver"
-    enum="Boolean" expires_after="2023-04-23">
+    enum="Boolean" expires_after="2024-06-12">
   <owner>horo@chromium.org</owner>
-  <owner>doh-core@google.com</owner>
+  <owner>src/net/dns/OWNERS</owner>
   <summary>
     True if there was at least one public nameserver during an attempt to
     upgrade to DoH from insecure DNS. False if there were no public nameservers
     during an attempt to upgrade to DoH from insecure DNS.
+
+    Warning: this histogram was expired from 2023-04-23 to 2023-06-12; data may
+    be missing.
   </summary>
 </histogram>
 
 <histogram name="Net.DNS.UpgradeConfig.Ineligible.DohSpecified" enum="Boolean"
-    expires_after="2023-11-12">
+    expires_after="2024-06-12">
   <owner>horo@chromium.org</owner>
-  <owner>doh-core@google.com</owner>
+  <owner>src/net/dns/OWNERS</owner>
   <summary>
     True if upgrade to DoH was not attempted because the DoH config was already
     specified. False if upgrade to DoH was not attempted for some other reason.
@@ -1658,9 +1661,9 @@
 </histogram>
 
 <histogram name="Net.DNS.UpgradeConfig.Ineligible.UnhandledOptions"
-    enum="Boolean" expires_after="2023-06-14">
-  <owner>awillia@chromium.org</owner>
-  <owner>doh-core@google.com</owner>
+    enum="Boolean" expires_after="2024-06-12">
+  <owner>horo@chromium.org</owner>
+  <owner>src/net/dns/OWNERS</owner>
   <summary>
     True if upgrade to DoH was not attempted because of unhandled options in the
     system config. False if upgrade to DoH was not attempted for some other
@@ -1669,9 +1672,9 @@
 </histogram>
 
 <histogram name="Net.DNS.UpgradeConfig.InsecureUpgradeSucceeded" enum="Boolean"
-    expires_after="2023-12-04">
+    expires_after="2024-06-12">
   <owner>horo@chromium.org</owner>
-  <owner>doh-core@google.com</owner>
+  <owner>src/net/dns/OWNERS</owner>
   <summary>
     True if upgrade to DoH from insecure DNS was attempted and succeeded. False
     if it was attempted and failed.
diff --git a/tools/metrics/histograms/metadata/prefetch/histograms.xml b/tools/metrics/histograms/metadata/prefetch/histograms.xml
index 6a36858..c5edf5b 100644
--- a/tools/metrics/histograms/metadata/prefetch/histograms.xml
+++ b/tools/metrics/histograms/metadata/prefetch/histograms.xml
@@ -384,18 +384,6 @@
   </summary>
 </histogram>
 
-<histogram name="PrefetchProxy.Prefetch.Mainframe.TotalRedirects" units="count"
-    expires_after="2023-06-25">
-  <owner>curranmax@chromium.org</owner>
-  <owner>ryansturm@chromium.org</owner>
-  <owner>spelchat@chromium.org</owner>
-  <summary>
-    Records the total number of redirects encountered while doing all the
-    prefetches on an eligible Google Search Result page. Only recorded when at
-    least one prefetch was attempted.
-  </summary>
-</histogram>
-
 <histogram name="PrefetchProxy.Prefetch.Mainframe.TotalTime" units="ms"
     expires_after="2023-11-05">
   <owner>curranmax@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/storage/histograms.xml b/tools/metrics/histograms/metadata/storage/histograms.xml
index bb19a3a0..57826aa 100644
--- a/tools/metrics/histograms/metadata/storage/histograms.xml
+++ b/tools/metrics/histograms/metadata/storage/histograms.xml
@@ -236,7 +236,7 @@
 </histogram>
 
 <histogram name="Clipboard.TimeIntervalBetweenCommitAndRead" units="ms"
-    expires_after="2023-06-18">
+    expires_after="2023-12-12">
   <owner>dcheng@chromium.org</owner>
   <owner>src/ui/base/clipboard/OWNERS</owner>
   <summary>
diff --git a/tools/perf/benchmarks/benchmark_smoke_unittest.py b/tools/perf/benchmarks/benchmark_smoke_unittest.py
index 8532630..9768a92 100644
--- a/tools/perf/benchmarks/benchmark_smoke_unittest.py
+++ b/tools/perf/benchmarks/benchmark_smoke_unittest.py
@@ -113,7 +113,7 @@
     'webrtc',  # crbug.com/932036
     'v8.runtime_stats.top_25',  # Fails in Windows, crbug.com/1043048
     'wasmpspdfkit',  # Fails in Chrome OS, crbug.com/1191938
-    'memory.desktop' if sys.platform == 'darwin' else None,  # crbug.com/1277277
+    'memory.desktop',  # crbug.com/1277277 and b/286898261
     'desktop_ui' if sys.platform == 'darwin' else None,  # crbug.com/1370958
     'power.desktop' if sys.platform == 'darwin' else None,  # crbug.com/1370958
 ]
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index a8fc92f..f8dc252 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -6,7 +6,7 @@
         },
         "win": {
             "hash": "0d3a88847d5e5ec8ea6bf732dbb71475e85b8082",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/7aa646548880a6f4260e4e5b87e09f13004b3675/trace_processor_shell.exe"
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/a4e9e26e3958f4d15e12ae84073252ba3f9f471b/trace_processor_shell.exe"
         },
         "linux_arm": {
             "hash": "c9c575015c295fda07a48ff69169904e2c52863b",
@@ -22,7 +22,7 @@
         },
         "linux": {
             "hash": "f30c0ff581415aaedd02c9fb2d0c17e3c22f686c",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/7aa646548880a6f4260e4e5b87e09f13004b3675/trace_processor_shell"
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/bc462e6d73d2b161245c39f5222a6a013f2a10f4/trace_processor_shell"
         }
     },
     "power_profile.sql": {
diff --git a/tools/traffic_annotation/safe_list.txt b/tools/traffic_annotation/safe_list.txt
index 07ac940..64c6da08 100644
--- a/tools/traffic_annotation/safe_list.txt
+++ b/tools/traffic_annotation/safe_list.txt
@@ -284,7 +284,6 @@
 missing_new_fields,content/browser/webid/idp_network_request_manager.cc
 missing_new_fields,content/browser/websockets/websocket_connector_impl.cc
 missing_new_fields,content/browser/worker_host/worker_script_fetcher.cc
-missing_new_fields,content/services/auction_worklet/auction_downloader.cc
 missing_new_fields,content/services/shared_storage_worklet/module_script_downloader.cc
 missing_new_fields,content/shell/browser/shell_devtools_bindings.cc
 missing_new_fields,device/bluetooth/bluetooth_socket_net.cc
diff --git a/tools/traffic_annotation/summary/annotations.xml b/tools/traffic_annotation/summary/annotations.xml
index 89ed8f6..a1076f8 100644
--- a/tools/traffic_annotation/summary/annotations.xml
+++ b/tools/traffic_annotation/summary/annotations.xml
@@ -15,7 +15,7 @@
  <item id="aggregation_service_report" added_in_milestone="95" content_hash_code="037768da" os_list="linux,windows,chromeos,android" file_path="content/browser/aggregation_service/aggregatable_report_sender.cc" />
  <item id="android_device_manager_socket" added_in_milestone="65" content_hash_code="00623801" os_list="linux,windows,chromeos" file_path="chrome/browser/devtools/device/android_device_manager.cc" />
  <item id="android_web_socket" added_in_milestone="65" content_hash_code="00bbd661" os_list="linux,windows,chromeos" file_path="chrome/browser/devtools/device/android_web_socket.cc" />
- <item id="auction_downloader" added_in_milestone="92" content_hash_code="0713f212" os_list="linux,windows,chromeos,android" file_path="content/services/auction_worklet/auction_downloader.cc" />
+ <item id="auction_downloader" added_in_milestone="92" content_hash_code="01f53427" os_list="linux,windows,chromeos,android" file_path="content/services/auction_worklet/public/cpp/auction_downloader.cc" />
  <item id="auction_report_sender" added_in_milestone="92" content_hash_code="029b766e" os_list="linux,windows,chromeos,android" file_path="content/browser/interest_group/interest_group_manager_impl.cc" />
  <item id="autofill_image_fetcher_card_art_image" added_in_milestone="93" content_hash_code="038b8696" os_list="linux,windows,chromeos,android" file_path="components/autofill/core/browser/ui/autofill_image_fetcher.cc" />
  <item id="autofill_query" added_in_milestone="62" content_hash_code="00ed7a4b" os_list="linux,windows,chromeos,android" file_path="components/autofill/core/browser/autofill_download_manager.cc" />
diff --git a/ui/base/BUILD.gn b/ui/base/BUILD.gn
index 6b1efea..f85c1f2 100644
--- a/ui/base/BUILD.gn
+++ b/ui/base/BUILD.gn
@@ -770,6 +770,8 @@
     sources = [
       "test/cocoa_helper.h",
       "test/cocoa_helper.mm",
+      "test/view_tree_validator.h",
+      "test/view_tree_validator.mm",
     ]
     configs += [ "//build/config/compiler:enable_arc" ]
     deps = [
@@ -824,7 +826,6 @@
       "test/scoped_preferred_scroller_style_mac.h",
       "test/scoped_preferred_scroller_style_mac.mm",
       "test/ui_controls_mac.mm",
-      "test/view_tree_validator.mm",
       "test/windowed_nsnotification_observer.h",
       "test/windowed_nsnotification_observer.mm",
     ]
@@ -847,7 +848,6 @@
       "test/test_dialog_model_host.h",
       "test/ui_controls.cc",
       "test/ui_controls.h",
-      "test/view_tree_validator.h",
     ]
   } else {
     sources += [
@@ -1117,6 +1117,10 @@
     sources += [ "user_activity/user_activity_detector_unittest.cc" ]
   }
 
+  if (is_apple) {
+    configs += [ "//build/config/compiler:enable_arc" ]
+  }
+
   if (is_mac) {
     sources += [
       "accelerators/platform_accelerator_cocoa_unittest.mm",
diff --git a/ui/base/accelerators/platform_accelerator_cocoa_unittest.mm b/ui/base/accelerators/platform_accelerator_cocoa_unittest.mm
index b6e7c79..10d96491 100644
--- a/ui/base/accelerators/platform_accelerator_cocoa_unittest.mm
+++ b/ui/base/accelerators/platform_accelerator_cocoa_unittest.mm
@@ -11,6 +11,10 @@
 #import "testing/gtest_mac.h"
 #include "ui/base/accelerators/accelerator.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 TEST(PlatformAcceleratorCocoaTest,
      GetKeyEquivalentAndModifierMaskFromAccelerator) {
   static const struct {
diff --git a/ui/base/cocoa/base_view_unittest.mm b/ui/base/cocoa/base_view_unittest.mm
index 13361f3..d8fb0edf 100644
--- a/ui/base/cocoa/base_view_unittest.mm
+++ b/ui/base/cocoa/base_view_unittest.mm
@@ -4,32 +4,34 @@
 
 #import <Cocoa/Cocoa.h>
 
-#include "base/mac/scoped_nsobject.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "testing/platform_test.h"
 #include "ui/base/cocoa/base_view.h"
 #import "ui/base/test/cocoa_helper.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 namespace {
 
 class BaseViewTest : public ui::CocoaTest {
  public:
   BaseViewTest() {
     NSRect frame = NSMakeRect(0, 0, 100, 100);
-    base::scoped_nsobject<BaseView> view(
-        [[BaseView alloc] initWithFrame:frame]);
-    view_ = view.get();
-    [[test_window() contentView] addSubview:view_];
+    BaseView* view = [[BaseView alloc] initWithFrame:frame];
+    [test_window().contentView addSubview:view];
+    view_ = view;
   }
 
-  BaseView* view_;  // weak
+  BaseView* __weak view_;
 };
 
 TEST_F(BaseViewTest, RemoveFromSuperviewWorks) {
-  base::scoped_nsobject<NSView> view([view_ retain]);
-  EXPECT_EQ([test_window() contentView], [view superview]);
+  NSView* view = view_;
+  EXPECT_EQ(test_window().contentView, view.superview);
   [view removeFromSuperview];
-  EXPECT_FALSE([view superview]);
+  EXPECT_FALSE(view.superview);
 }
 
 // Convert a rect in |view_|'s Cocoa coordinate system to gfx::Rect's top-left
diff --git a/ui/base/cocoa/bubble_closer_unittest.mm b/ui/base/cocoa/bubble_closer_unittest.mm
index 9bfd020..f6414eb 100644
--- a/ui/base/cocoa/bubble_closer_unittest.mm
+++ b/ui/base/cocoa/bubble_closer_unittest.mm
@@ -10,6 +10,10 @@
 #import "ui/base/test/menu_test_observer.h"
 #import "ui/events/test/cocoa_test_event_utils.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 namespace ui {
 
 class BubbleCloserTest : public CocoaTest {
@@ -24,12 +28,12 @@
 
   void SetUp() override {
     CocoaTest::SetUp();
-    bubble_window_.reset([[NSWindow alloc]
-        initWithContentRect:NSMakeRect(100, 100, 320, 200)
-                  styleMask:NSWindowStyleMaskBorderless
-                    backing:NSBackingStoreBuffered
-                      defer:NO]);
-    [bubble_window_ setReleasedWhenClosed:NO];  // Owned by scoped_nsobject.
+    bubble_window_ =
+        [[NSWindow alloc] initWithContentRect:NSMakeRect(100, 100, 320, 200)
+                                    styleMask:NSWindowStyleMaskBorderless
+                                      backing:NSBackingStoreBuffered
+                                        defer:NO];
+    bubble_window_.releasedWhenClosed = NO;
     [bubble_window_ makeKeyAndOrderFront:nil];
     bubble_closer_ = std::make_unique<BubbleCloser>(
         bubble_window_,
@@ -39,7 +43,7 @@
   void TearDown() override {
     [bubble_window_ close];
     bubble_closer_ = nullptr;
-    bubble_window_.reset();
+    bubble_window_ = nil;
     CocoaTest::TearDown();
   }
 
@@ -62,7 +66,7 @@
   bool IsCloserReset() const { return !bubble_closer_; }
 
  private:
-  base::scoped_nsobject<NSWindow> bubble_window_;
+  NSWindow* __strong bubble_window_;
   std::unique_ptr<BubbleCloser> bubble_closer_;
   int click_outside_count_ = 0;
 };
@@ -95,26 +99,25 @@
 
 // Test that right-clicking the window to display a context menu works.
 TEST_F(BubbleCloserTest, RightClickOutsideClosesWithContextMenu) {
-  base::scoped_nsobject<NSMenu> context_menu(
-      [[NSMenu alloc] initWithTitle:@""]);
+  NSMenu* context_menu = [[NSMenu alloc] initWithTitle:@""];
   [context_menu addItemWithTitle:@"ContextMenuTest"
                           action:nil
                    keyEquivalent:@""];
 
   // Set the menu as the contextual menu of contentView of test_window().
-  [[test_window() contentView] setMenu:context_menu];
+  [test_window().contentView setMenu:context_menu];
 
-  base::scoped_nsobject<MenuTestObserver> menu_observer(
-      [[MenuTestObserver alloc] initWithMenu:context_menu]);
-  [menu_observer setCloseAfterOpening:YES];
-  [menu_observer setOpenCallback:^(MenuTestObserver* observer) {
+  MenuTestObserver* menu_observer =
+      [[MenuTestObserver alloc] initWithMenu:context_menu];
+  menu_observer.closeAfterOpening = YES;
+  menu_observer.openCallback = ^(MenuTestObserver* observer) {
     // Verify click is seen when contextual menu is open.
-    EXPECT_TRUE([observer isOpen]);
+    EXPECT_TRUE(observer.isOpen);
     EXPECT_EQ(1, click_outside_count());
-  }];
+  };
 
-  EXPECT_FALSE([menu_observer isOpen]);
-  EXPECT_FALSE([menu_observer didOpen]);
+  EXPECT_FALSE(menu_observer.isOpen);
+  EXPECT_FALSE(menu_observer.didOpen);
 
   EXPECT_EQ(0, click_outside_count());
 
@@ -125,8 +128,8 @@
   // When we got here, menu has already run its RunLoop.
   EXPECT_EQ(1, click_outside_count());
 
-  EXPECT_FALSE([menu_observer isOpen]);
-  EXPECT_TRUE([menu_observer didOpen]);
+  EXPECT_FALSE(menu_observer.isOpen);
+  EXPECT_TRUE(menu_observer.didOpen);
 }
 
 }  // namespace ui
diff --git a/ui/base/cocoa/cocoa_base_utils_unittest.mm b/ui/base/cocoa/cocoa_base_utils_unittest.mm
index 41d0045..7eab3d4 100644
--- a/ui/base/cocoa/cocoa_base_utils_unittest.mm
+++ b/ui/base/cocoa/cocoa_base_utils_unittest.mm
@@ -13,6 +13,10 @@
 #include "ui/events/event_constants.h"
 #import "ui/events/test/cocoa_test_event_utils.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 // We provide a donor class with a specially modified |modifierFlags|
 // implementation that we swap with NSEvent's. This is because we can't create a
 // NSEvent that represents a middle click with modifiers.
diff --git a/ui/base/cocoa/constrained_window/constrained_window_animation_unittest.mm b/ui/base/cocoa/constrained_window/constrained_window_animation_unittest.mm
index 668c3edb..b3c9c087 100644
--- a/ui/base/cocoa/constrained_window/constrained_window_animation_unittest.mm
+++ b/ui/base/cocoa/constrained_window/constrained_window_animation_unittest.mm
@@ -6,9 +6,12 @@
 
 #include <memory>
 
-#include "base/mac/scoped_nsobject.h"
 #import "ui/base/test/cocoa_helper.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 // This class runs an animation for exactly two frames then end it.
 @interface ConstrainedWindowAnimationTestDelegate
     : NSObject<NSAnimationDelegate> {
@@ -25,8 +28,9 @@
 - (float)animation:(NSAnimation*)animation
     valueForProgress:(NSAnimationProgress)progress {
   ++_frameCount;
-  if (_frameCount >= 2)
-    [animation setDuration:0.0];
+  if (_frameCount >= 2) {
+    animation.duration = 0.0;
+  }
   return _frameCount == 1 ? 0.2 : 0.6;
 }
 
@@ -37,8 +41,8 @@
 - (void)runAnimation:(NSAnimation*)animation {
   // This class will end the animation after 2 frames. Set a large duration to
   // ensure that both frames are processed.
-  [animation setDuration:600];
-  [animation setDelegate:self];
+  animation.duration = 600;
+  animation.delegate = self;
   [animation startAnimation];
   EXPECT_EQ(2, _frameCount);
 }
@@ -47,30 +51,30 @@
 
 class ConstrainedWindowAnimationTest : public ui::CocoaTest {
  protected:
-  ConstrainedWindowAnimationTest() : CocoaTest() {
-    delegate_.reset([[ConstrainedWindowAnimationTestDelegate alloc] init]);
+  ConstrainedWindowAnimationTest() {
+    delegate_ = [[ConstrainedWindowAnimationTestDelegate alloc] init];
   }
 
-  base::scoped_nsobject<ConstrainedWindowAnimationTestDelegate> delegate_;
+  ConstrainedWindowAnimationTestDelegate* __strong delegate_;
 };
 
 // Test the show animation.
 TEST_F(ConstrainedWindowAnimationTest, Show) {
-  base::scoped_nsobject<NSAnimation> animation(
-      [[ConstrainedWindowAnimationShow alloc] initWithWindow:test_window()]);
+  NSAnimation* animation =
+      [[ConstrainedWindowAnimationShow alloc] initWithWindow:test_window()];
   [delegate_ runAnimation:animation];
 }
 
 // Test the hide animation.
 TEST_F(ConstrainedWindowAnimationTest, Hide) {
-  base::scoped_nsobject<NSAnimation> animation(
-      [[ConstrainedWindowAnimationHide alloc] initWithWindow:test_window()]);
+  NSAnimation* animation =
+      [[ConstrainedWindowAnimationHide alloc] initWithWindow:test_window()];
   [delegate_ runAnimation:animation];
 }
 
 // Test the pulse animation.
 TEST_F(ConstrainedWindowAnimationTest, Pulse) {
-  base::scoped_nsobject<NSAnimation> animation(
-      [[ConstrainedWindowAnimationPulse alloc] initWithWindow:test_window()]);
+  NSAnimation* animation =
+      [[ConstrainedWindowAnimationPulse alloc] initWithWindow:test_window()];
   [delegate_ runAnimation:animation];
 }
diff --git a/ui/base/cocoa/defaults_utils_unittest.mm b/ui/base/cocoa/defaults_utils_unittest.mm
index 34ca614..9f73dcb7 100644
--- a/ui/base/cocoa/defaults_utils_unittest.mm
+++ b/ui/base/cocoa/defaults_utils_unittest.mm
@@ -9,6 +9,10 @@
 #include "base/time/time.h"
 #import "ui/base/test/cocoa_helper.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 namespace ui::cocoa {
 namespace {
 
@@ -40,8 +44,8 @@
     int i = 0;
     for (NSString* next_key in blink_period_keys) {
       orig_blink_period_values_[i++] =
-          [[NSUserDefaults standardUserDefaults] integerForKey:next_key];
-      [[NSUserDefaults standardUserDefaults] removeObjectForKey:next_key];
+          [NSUserDefaults.standardUserDefaults integerForKey:next_key];
+      [NSUserDefaults.standardUserDefaults removeObjectForKey:next_key];
     }
 
     // Make sure the test's blink period changes get picked up.
@@ -58,7 +62,7 @@
 
   // Sets the blink period to `milliseconds`. Removes all values from defaults.
   void SetBlinkPeriod(const int milliseconds) {
-    NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
+    NSUserDefaults* defaults = NSUserDefaults.standardUserDefaults;
 
     [defaults removeObjectForKey:kInsertionPointBlinkPeriodOn];
     [defaults removeObjectForKey:kInsertionPointBlinkPeriodOff];
@@ -77,11 +81,11 @@
     int i = 0;
     for (NSString* next_key in blink_period_keys) {
       if (orig_blink_period_values_[i]) {
-        [[NSUserDefaults standardUserDefaults]
+        [NSUserDefaults.standardUserDefaults
             setInteger:orig_blink_period_values_[i]
                 forKey:next_key];
       } else {
-        [[NSUserDefaults standardUserDefaults] removeObjectForKey:next_key];
+        [NSUserDefaults.standardUserDefaults removeObjectForKey:next_key];
       }
       i++;
     }
@@ -110,7 +114,7 @@
   EXPECT_FALSE(WillRefreshBlinkPeriod());
 
   // Simulate the app becoming active, as if the user switched away and back.
-  [[NSNotificationCenter defaultCenter]
+  [NSNotificationCenter.defaultCenter
       postNotificationName:NSApplicationWillBecomeActiveNotification
                     object:nil];
 
@@ -127,10 +131,9 @@
 
 // Tests returning the blink period from defaults.
 TEST_F(DefaultsUtilsTest, InsertionPointBlinkPeriodFromDefaults) {
-  [[NSUserDefaults standardUserDefaults]
-      setInteger:k750MS
-          forKey:kInsertionPointBlinkPeriodOn];
-  [[NSUserDefaults standardUserDefaults]
+  [NSUserDefaults.standardUserDefaults setInteger:k750MS
+                                           forKey:kInsertionPointBlinkPeriodOn];
+  [NSUserDefaults.standardUserDefaults
       setInteger:k750MS
           forKey:kInsertionPointBlinkPeriodOff];
 
@@ -141,12 +144,10 @@
 // Tests returning the blink period when a double is stored in defaults.
 TEST_F(DefaultsUtilsTest, InsertionPointBlinkPeriodFromDefaultsDouble) {
   const double k750WithFractionalMS = 750.6;
-  [[NSUserDefaults standardUserDefaults]
-      setDouble:k750WithFractionalMS
-         forKey:kInsertionPointBlinkPeriodOn];
-  [[NSUserDefaults standardUserDefaults]
-      setDouble:k750WithFractionalMS
-         forKey:kInsertionPointBlinkPeriodOff];
+  [NSUserDefaults.standardUserDefaults setDouble:k750WithFractionalMS
+                                          forKey:kInsertionPointBlinkPeriodOn];
+  [NSUserDefaults.standardUserDefaults setDouble:k750WithFractionalMS
+                                          forKey:kInsertionPointBlinkPeriodOff];
 
   EXPECT_EQ(base::Milliseconds(k750MS),
             *TextInsertionCaretBlinkPeriodFromDefaults());
@@ -155,9 +156,8 @@
 // Tests returning the blink period derived from just the on time setting in
 // defaults.
 TEST_F(DefaultsUtilsTest, InsertionPointBlinkPeriodFromOnTime) {
-  [[NSUserDefaults standardUserDefaults]
-      setInteger:k750MS
-          forKey:kInsertionPointBlinkPeriodOn];
+  [NSUserDefaults.standardUserDefaults setInteger:k750MS
+                                           forKey:kInsertionPointBlinkPeriodOn];
 
   EXPECT_EQ(base::Milliseconds((k750MS + 0) / 2),
             *TextInsertionCaretBlinkPeriodFromDefaults());
@@ -166,7 +166,7 @@
 // Tests returning the blink period derived from just the off time setting in
 // defaults.
 TEST_F(DefaultsUtilsTest, InsertionPointBlinkPeriodFromOffTime) {
-  [[NSUserDefaults standardUserDefaults]
+  [NSUserDefaults.standardUserDefaults
       setInteger:k250MS
           forKey:kInsertionPointBlinkPeriodOff];
 
@@ -177,10 +177,9 @@
 // Tests returning the blink period derived from the on and off times in
 // defaults.
 TEST_F(DefaultsUtilsTest, InsertionPointBlinkPeriodFromOnOffTime) {
-  [[NSUserDefaults standardUserDefaults]
-      setInteger:k750MS
-          forKey:kInsertionPointBlinkPeriodOn];
-  [[NSUserDefaults standardUserDefaults]
+  [NSUserDefaults.standardUserDefaults setInteger:k750MS
+                                           forKey:kInsertionPointBlinkPeriodOn];
+  [NSUserDefaults.standardUserDefaults
       setInteger:k250MS
           forKey:kInsertionPointBlinkPeriodOff];
 
@@ -190,9 +189,8 @@
 
 // Tests returning "infinite" blink period for a long on time in defaults.
 TEST_F(DefaultsUtilsTest, InsertionPointBlinkPeriodFromLongOnTime) {
-  [[NSUserDefaults standardUserDefaults]
-      setInteger:kTwoHoursInMS
-          forKey:kInsertionPointBlinkPeriodOn];
+  [NSUserDefaults.standardUserDefaults setInteger:kTwoHoursInMS
+                                           forKey:kInsertionPointBlinkPeriodOn];
 
   EXPECT_EQ(kInfiniteBlinkTime, *TextInsertionCaretBlinkPeriodFromDefaults());
 }
@@ -200,7 +198,7 @@
 // Tests handling of bad blink period times from defaults.
 TEST_F(DefaultsUtilsTest, InsertionPointBlinkPeriodNegativeTimes) {
   const int kNegativeMS = -500;
-  NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
+  NSUserDefaults* defaults = NSUserDefaults.standardUserDefaults;
 
   // By setting the blink period we cause
   // TextInsertionCaretBlinkPeriodFromDefaults() to evaluate to true so
diff --git a/ui/base/cocoa/menu_controller_unittest.mm b/ui/base/cocoa/menu_controller_unittest.mm
index 2fec924..8bf5aa4 100644
--- a/ui/base/cocoa/menu_controller_unittest.mm
+++ b/ui/base/cocoa/menu_controller_unittest.mm
@@ -24,7 +24,9 @@
 #include "ui/gfx/image/image_unittest_util.h"
 #include "ui/strings/grit/ui_strings.h"
 
-using base::ASCIIToUTF16;
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
 
 @interface WatchedLifetimeMenuController : MenuControllerCocoa
 @property(assign, nonatomic) BOOL* deallocCalled;
@@ -39,7 +41,6 @@
 
 - (void)dealloc {
   *_deallocCalled = YES;
-  [super dealloc];
 }
 
 @end
@@ -88,21 +89,19 @@
     int command_id;
   };
 
-  typedef std::vector<Item> ItemVector;
-
   int ValidateItemIndex(size_t index) const {
     CHECK_LT(index, items_.size());
     return index;
   }
 
-  ItemVector items_;
+  std::vector<Item> items_;
 };
 
 // A menu delegate that counts the number of times certain things are called
 // to make sure things are hooked up properly.
 class Delegate : public SimpleMenuModel::Delegate {
  public:
-  Delegate() {}
+  Delegate() = default;
 
   Delegate(const Delegate&) = delete;
   Delegate& operator=(const Delegate&) = delete;
@@ -150,7 +149,7 @@
 // the label/icon in the model are reflected in the menu.
 class DynamicDelegate : public Delegate {
  public:
-  DynamicDelegate() {}
+  DynamicDelegate() = default;
   bool IsItemForCommandIdDynamic(int command_id) const override { return true; }
   std::u16string GetLabelForCommandId(int command_id) const override {
     return label_;
@@ -174,10 +173,9 @@
   OwningDelegate(bool* did_delete, BOOL* did_dealloc)
       : did_delete_(did_delete), model_(this) {
     model_.AddItem(1, u"foo");
-    controller_.reset([[WatchedLifetimeMenuController alloc]
-                 initWithModel:&model_
-                      delegate:nil
-        useWithPopUpButtonCell:NO]);
+    controller_ = [[WatchedLifetimeMenuController alloc] initWithModel:&model_
+                                                              delegate:nil
+                                                useWithPopUpButtonCell:NO];
     [controller_ setDeallocCalled:did_dealloc];
   }
 
@@ -207,7 +205,7 @@
 
   raw_ptr<bool> did_delete_;
   SimpleMenuModel model_;
-  base::scoped_nsobject<WatchedLifetimeMenuController> controller_;
+  WatchedLifetimeMenuController* __strong controller_;
 };
 
 // Menu model that returns a gfx::FontList object for one of the items in the
@@ -218,9 +216,9 @@
                     const gfx::FontList* font_list,
                     size_t index)
       : SimpleMenuModel(delegate), font_list_(font_list), index_(index) {}
-  ~FontListMenuModel() override {}
+  ~FontListMenuModel() override = default;
   const gfx::FontList* GetLabelFontListAt(size_t index) const override {
-    return (index == index_) ? font_list_.get() : nullptr;
+    return (index == index_) ? font_list_ : nullptr;
   }
 
  private:
@@ -231,10 +229,9 @@
 TEST_F(MenuControllerTest, EmptyMenu) {
   Delegate delegate;
   SimpleMenuModel model(&delegate);
-  base::scoped_nsobject<MenuControllerCocoa> menu([[MenuControllerCocoa alloc]
-               initWithModel:&model
-                    delegate:nil
-      useWithPopUpButtonCell:NO]);
+  MenuControllerCocoa* menu = [[MenuControllerCocoa alloc] initWithModel:&model
+                                                                delegate:nil
+                                                  useWithPopUpButtonCell:NO];
   EXPECT_EQ(0, [[menu menu] numberOfItems]);
 }
 
@@ -248,10 +245,9 @@
   model.AddItem(4, u"four");
   model.AddItem(5, u"five");
 
-  base::scoped_nsobject<MenuControllerCocoa> menu([[MenuControllerCocoa alloc]
-               initWithModel:&model
-                    delegate:nil
-      useWithPopUpButtonCell:NO]);
+  MenuControllerCocoa* menu = [[MenuControllerCocoa alloc] initWithModel:&model
+                                                                delegate:nil
+                                                  useWithPopUpButtonCell:NO];
   EXPECT_EQ(6, [[menu menu] numberOfItems]);
 
   // Check the title, tag, and represented object are correct for a random
@@ -275,10 +271,9 @@
   model.AddSubMenuWithStringId(5, kTestLabelResourceId, &submodel);
   model.AddItem(6, u"three");
 
-  base::scoped_nsobject<MenuControllerCocoa> menu([[MenuControllerCocoa alloc]
-               initWithModel:&model
-                    delegate:nil
-      useWithPopUpButtonCell:NO]);
+  MenuControllerCocoa* menu = [[MenuControllerCocoa alloc] initWithModel:&model
+                                                                delegate:nil
+                                                  useWithPopUpButtonCell:NO];
   EXPECT_EQ(3, [[menu menu] numberOfItems]);
 
   // Inspect the submenu to ensure it has correct properties.
@@ -310,10 +305,9 @@
   SimpleMenuModel submodel(&delegate);
   model.AddSubMenuWithStringId(2, kTestLabelResourceId, &submodel);
 
-  base::scoped_nsobject<MenuControllerCocoa> menu([[MenuControllerCocoa alloc]
-               initWithModel:&model
-                    delegate:nil
-      useWithPopUpButtonCell:NO]);
+  MenuControllerCocoa* menu = [[MenuControllerCocoa alloc] initWithModel:&model
+                                                                delegate:nil
+                                                  useWithPopUpButtonCell:NO];
   EXPECT_EQ(2, [[menu menu] numberOfItems]);
 
   // Inspect the submenu to ensure it has one item labeled "(empty)".
@@ -338,10 +332,9 @@
   submodel.SetVisibility(3, false);
   model.AddSubMenuWithStringId(4, kTestLabelResourceId, &submodel);
 
-  base::scoped_nsobject<MenuControllerCocoa> menu([[MenuControllerCocoa alloc]
-               initWithModel:&model
-                    delegate:nil
-      useWithPopUpButtonCell:NO]);
+  MenuControllerCocoa* menu = [[MenuControllerCocoa alloc] initWithModel:&model
+                                                                delegate:nil
+                                                  useWithPopUpButtonCell:NO];
   EXPECT_EQ(2, [[menu menu] numberOfItems]);
 
   // Inspect the submenu to ensure it has one item labeled "(empty)".
@@ -372,20 +365,22 @@
   model.SetVisibility(4, false);
 
   // Create the controller.
-  base::scoped_nsobject<MenuControllerCocoa> menu_controller(
+  MenuControllerCocoa* menu_controller =
       [[MenuControllerCocoa alloc] initWithModel:&model
                                         delegate:nil
-                          useWithPopUpButtonCell:NO]);
+                          useWithPopUpButtonCell:NO];
   EXPECT_EQ(2, [[menu_controller menu] numberOfItems]);
   delegate.menu_to_close_ = [menu_controller menu];
 
   // Show the menu.
-  CFRunLoopPerformBlock(CFRunLoopGetCurrent(), NSEventTrackingRunLoopMode, ^{
-    EXPECT_TRUE([menu_controller isMenuOpen]);
-    // Ensure that the submenu is hidden.
-    NSMenuItem* item = [[menu_controller menu] itemAtIndex:1];
-    EXPECT_TRUE([item isHidden]);
-  });
+  [NSRunLoop.currentRunLoop
+      performInModes:@[ NSEventTrackingRunLoopMode ]
+               block:^{
+                 EXPECT_TRUE([menu_controller isMenuOpen]);
+                 // Ensure that the submenu is hidden.
+                 NSMenuItem* item = [[menu_controller menu] itemAtIndex:1];
+                 EXPECT_TRUE([item isHidden]);
+               }];
 
   // Pop open the menu, which will spin an event-tracking run loop.
   [NSMenu popUpContextMenu:[menu_controller menu]
@@ -422,24 +417,28 @@
   model.SetEnabledAt(1, false);
 
   // Create the controller.
-  base::scoped_nsobject<MenuControllerCocoa> menu_controller(
+  MenuControllerCocoa* menu_controller =
       [[MenuControllerCocoa alloc] initWithModel:&model
                                         delegate:nil
-                          useWithPopUpButtonCell:NO]);
+                          useWithPopUpButtonCell:NO];
   delegate.menu_to_close_ = [menu_controller menu];
 
   // Show the menu.
-  CFRunLoopPerformBlock(CFRunLoopGetCurrent(), NSEventTrackingRunLoopMode, ^{
-    EXPECT_TRUE([menu_controller isMenuOpen]);
+  [NSRunLoop.currentRunLoop
+      performInModes:@[ NSEventTrackingRunLoopMode ]
+               block:^{
+                 EXPECT_TRUE([menu_controller isMenuOpen]);
 
-    // Ensure that the disabled submenu is disabled.
-    NSMenuItem* disabled_item = [[menu_controller menu] itemAtIndex:1];
-    EXPECT_FALSE([disabled_item isEnabled]);
+                 // Ensure that the disabled submenu is disabled.
+                 NSMenuItem* disabled_item =
+                     [[menu_controller menu] itemAtIndex:1];
+                 EXPECT_FALSE([disabled_item isEnabled]);
 
-    // Ensure that the enabled submenu is enabled.
-    NSMenuItem* enabled_item = [[menu_controller menu] itemAtIndex:2];
-    EXPECT_TRUE([enabled_item isEnabled]);
-  });
+                 // Ensure that the enabled submenu is enabled.
+                 NSMenuItem* enabled_item =
+                     [[menu_controller menu] itemAtIndex:2];
+                 EXPECT_TRUE([enabled_item isEnabled]);
+               }];
 
   // Pop open the menu, which will spin an event-tracking run loop.
   [NSMenu popUpContextMenu:[menu_controller menu]
@@ -463,10 +462,9 @@
 
   // Menu should have an extra item inserted at position 0 that has an empty
   // title.
-  base::scoped_nsobject<MenuControllerCocoa> menu([[MenuControllerCocoa alloc]
-               initWithModel:&model
-                    delegate:nil
-      useWithPopUpButtonCell:YES]);
+  MenuControllerCocoa* menu = [[MenuControllerCocoa alloc] initWithModel:&model
+                                                                delegate:nil
+                                                  useWithPopUpButtonCell:YES];
   EXPECT_EQ(4, [[menu menu] numberOfItems]);
   EXPECT_EQ(std::u16string(),
             base::SysNSStringToUTF16([[[menu menu] itemAtIndex:0] title]));
@@ -480,16 +478,18 @@
   Delegate delegate;
   SimpleMenuModel model(&delegate);
   model.AddItem(1, u"one");
-  base::scoped_nsobject<MenuControllerCocoa> menu([[MenuControllerCocoa alloc]
-               initWithModel:&model
-                    delegate:nil
-      useWithPopUpButtonCell:NO]);
+  MenuControllerCocoa* menu = [[MenuControllerCocoa alloc] initWithModel:&model
+                                                                delegate:nil
+                                                  useWithPopUpButtonCell:NO];
   EXPECT_EQ(1, [[menu menu] numberOfItems]);
 
   // Fake selecting the menu item, we expect the delegate to be told to execute
   // a command.
   NSMenuItem* item = [[menu menu] itemAtIndex:0];
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
   [[item target] performSelector:[item action] withObject:item];
+#pragma clang diagnostic pop
   EXPECT_EQ(1, delegate.execute_count_);
 }
 
@@ -511,13 +511,12 @@
   submodel.AddItem(2, u"sub-one");
   model.AddSubMenuWithStringId(3, kTestLabelResourceId, &submodel);
 
-  base::scoped_nsobject<MenuControllerCocoa> menu([[MenuControllerCocoa alloc]
-               initWithModel:&model
-                    delegate:nil
-      useWithPopUpButtonCell:NO]);
+  MenuControllerCocoa* menu = [[MenuControllerCocoa alloc] initWithModel:&model
+                                                                delegate:nil
+                                                  useWithPopUpButtonCell:NO];
   EXPECT_EQ(3, [[menu menu] numberOfItems]);
 
-  Validate(menu.get(), [menu menu]);
+  Validate(menu, [menu menu]);
 }
 
 // Tests that items which have a font set actually use that font.
@@ -531,13 +530,12 @@
   model.AddItem(1, u"one");
   model.AddItem(2, u"two");
 
-  base::scoped_nsobject<MenuControllerCocoa> menu([[MenuControllerCocoa alloc]
-               initWithModel:&model
-                    delegate:nil
-      useWithPopUpButtonCell:NO]);
+  MenuControllerCocoa* menu = [[MenuControllerCocoa alloc] initWithModel:&model
+                                                                delegate:nil
+                                                  useWithPopUpButtonCell:NO];
   EXPECT_EQ(2, [[menu menu] numberOfItems]);
 
-  Validate(menu.get(), [menu menu]);
+  Validate(menu, [menu menu]);
 
   EXPECT_TRUE([[[menu menu] itemAtIndex:0] attributedTitle] != nil);
   EXPECT_TRUE([[[menu menu] itemAtIndex:1] attributedTitle] == nil);
@@ -550,8 +548,7 @@
   model.AddItem(2, u"two");
   model.AddItem(3, u"three");
 
-  base::scoped_nsobject<MenuControllerCocoa> menu(
-      [[MenuControllerCocoa alloc] init]);
+  MenuControllerCocoa* menu = [[MenuControllerCocoa alloc] init];
   EXPECT_FALSE([menu menu]);
 
   [menu setModel:&model];
@@ -574,14 +571,13 @@
   delegate.SetDynamicLabel(initial);
   SimpleMenuModel model(&delegate);
   model.AddItem(1, u"foo");
-  base::scoped_nsobject<MenuControllerCocoa> menu([[MenuControllerCocoa alloc]
-               initWithModel:&model
-                    delegate:nil
-      useWithPopUpButtonCell:NO]);
+  MenuControllerCocoa* menu = [[MenuControllerCocoa alloc] initWithModel:&model
+                                                                delegate:nil
+                                                  useWithPopUpButtonCell:NO];
   EXPECT_EQ(1, [[menu menu] numberOfItems]);
   // Validate() simulates opening the menu - the item label/icon should be
   // initialized after this so we can validate the menu contents.
-  Validate(menu.get(), [menu menu]);
+  Validate(menu, [menu menu]);
   NSMenuItem* item = [[menu menu] itemAtIndex:0];
   // Item should have the "initial" label and no icon.
   EXPECT_EQ(initial, base::SysNSStringToUTF16([item title]));
@@ -593,13 +589,13 @@
   const gfx::Image& icon = gfx::test::CreateImage(32, 32);
   delegate.SetDynamicIcon(icon);
   // Simulate opening the menu and validate that the item label + icon changes.
-  Validate(menu.get(), [menu menu]);
+  Validate(menu, [menu menu]);
   EXPECT_EQ(second, base::SysNSStringToUTF16([item title]));
   EXPECT_TRUE([item image] != nil);
 
   // Now get rid of the icon and make sure it goes away.
   delegate.SetDynamicIcon(gfx::Image());
-  Validate(menu.get(), [menu menu]);
+  Validate(menu, [menu menu]);
   EXPECT_EQ(second, base::SysNSStringToUTF16([item title]));
   EXPECT_EQ(nil, [item image]);
 }
@@ -617,19 +613,19 @@
   model.AddItem(3, u"bf");
 
   // Create the controller.
-  base::scoped_nsobject<MenuControllerCocoa> menu([[MenuControllerCocoa alloc]
-               initWithModel:&model
-                    delegate:nil
-      useWithPopUpButtonCell:NO]);
+  MenuControllerCocoa* menu = [[MenuControllerCocoa alloc] initWithModel:&model
+                                                                delegate:nil
+                                                  useWithPopUpButtonCell:NO];
   delegate.menu_to_close_ = [menu menu];
 
   EXPECT_FALSE([menu isMenuOpen]);
 
   // In the event tracking run loop mode of the menu, verify that the controller
-  // resports the menu as open.
-  CFRunLoopPerformBlock(CFRunLoopGetCurrent(), NSEventTrackingRunLoopMode, ^{
-    EXPECT_TRUE([menu isMenuOpen]);
-  });
+  // reports the menu as open.
+  [NSRunLoop.currentRunLoop performInModes:@[ NSEventTrackingRunLoopMode ]
+                                     block:^{
+                                       EXPECT_TRUE([menu isMenuOpen]);
+                                     }];
 
   // Pop open the menu, which will spin an event-tracking run loop.
   [NSMenu popUpContextMenu:[menu menu]
@@ -692,7 +688,10 @@
   // NSMenuItem to get cleaned up later than this test expects. Deal with that
   // by creating an explicit autorelease pool here.
   @autoreleasepool {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
     [[item target] performSelector:[item action] withObject:item];
+#pragma clang diagnostic pop
   }
   EXPECT_TRUE(did_dealloc);
   EXPECT_TRUE(did_delete);
@@ -708,11 +707,11 @@
   model.AddItem(3, u"three");
 
   ui::ColorProvider colorProvider;
-  base::scoped_nsobject<MenuControllerCocoa> menu([[MenuControllerCocoa alloc]
-               initWithModel:&model
-                    delegate:nil
-               colorProvider:&colorProvider
-      useWithPopUpButtonCell:YES]);
+  MenuControllerCocoa* menu =
+      [[MenuControllerCocoa alloc] initWithModel:&model
+                                        delegate:nil
+                                   colorProvider:&colorProvider
+                          useWithPopUpButtonCell:YES];
   EXPECT_TRUE([menu isMenuBuiltForTesting]);
 }
 
@@ -728,10 +727,9 @@
 
   // Calling |-initWithModel:| without the ColorProvider should not build the
   // menu.
-  base::scoped_nsobject<MenuControllerCocoa> menu([[MenuControllerCocoa alloc]
-               initWithModel:&model
-                    delegate:nil
-      useWithPopUpButtonCell:YES]);
+  MenuControllerCocoa* menu = [[MenuControllerCocoa alloc] initWithModel:&model
+                                                                delegate:nil
+                                                  useWithPopUpButtonCell:YES];
   EXPECT_FALSE([menu isMenuBuiltForTesting]);
 
   // A follow up call to |-maybeBuildWithColorProvider:| should result in the
@@ -756,10 +754,9 @@
   model.AddItem(2, u"Gin & Tonic");
   model.SetMayHaveMnemonicsAt(1, false);
 
-  base::scoped_nsobject<MenuControllerCocoa> menu([[MenuControllerCocoa alloc]
-               initWithModel:&model
-                    delegate:nil
-      useWithPopUpButtonCell:NO]);
+  MenuControllerCocoa* menu = [[MenuControllerCocoa alloc] initWithModel:&model
+                                                                delegate:nil
+                                                  useWithPopUpButtonCell:NO];
 
   EXPECT_NSEQ([[[menu menu] itemAtIndex:0] title], @"New");
   EXPECT_NSEQ([[[menu menu] itemAtIndex:1] title], @"Gin & Tonic");
diff --git a/ui/base/cocoa/nsmenu_additions_unittest.mm b/ui/base/cocoa/nsmenu_additions_unittest.mm
index daab3126..c1470974 100644
--- a/ui/base/cocoa/nsmenu_additions_unittest.mm
+++ b/ui/base/cocoa/nsmenu_additions_unittest.mm
@@ -4,10 +4,13 @@
 
 #import "ui/base/cocoa/nsmenu_additions.h"
 
-#include "base/mac/scoped_nsobject.h"
 #include "base/test/gtest_util.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 @interface NSMenuAdditionsUnitTestMenuItem : NSMenuItem
 @end
 
@@ -39,8 +42,8 @@
 namespace {
 
 NSMenu* Menu(NSString* title) {
-  NSMenu* menu = [[[NSMenu alloc] initWithTitle:title] autorelease];
-  [menu setAutoenablesItems:NO];
+  NSMenu* menu = [[NSMenu alloc] initWithTitle:title];
+  menu.autoenablesItems = NO;
   return menu;
 }
 
@@ -48,12 +51,11 @@
                      NSString* key_equivalent = @"",
                      NSEventModifierFlags modifier_mask = 0) {
   NSMenuAdditionsUnitTestMenuItem* item =
-      [[[NSMenuAdditionsUnitTestMenuItem alloc] initWithTitle:title
-                                                       action:NULL
-                                                keyEquivalent:key_equivalent]
-          autorelease];
-  [item setKeyEquivalentModifierMask:modifier_mask];
-  [item setEnabled:YES];
+      [[NSMenuAdditionsUnitTestMenuItem alloc] initWithTitle:title
+                                                      action:nil
+                                               keyEquivalent:key_equivalent];
+  item.keyEquivalentModifierMask = modifier_mask;
+  item.enabled = YES;
   return item;
 }
 
diff --git a/ui/base/cocoa/nsmenuitem_additions_unittest.mm b/ui/base/cocoa/nsmenuitem_additions_unittest.mm
index 07cf787f..bb062af 100644
--- a/ui/base/cocoa/nsmenuitem_additions_unittest.mm
+++ b/ui/base/cocoa/nsmenuitem_additions_unittest.mm
@@ -9,13 +9,17 @@
 
 #include <ostream>
 
+#include "base/apple/bridging.h"
 #include "base/mac/foundation_util.h"
 #include "base/mac/scoped_cftyperef.h"
-#include "base/mac/scoped_nsobject.h"
 #include "base/strings/sys_string_conversions.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/events/keycodes/keyboard_code_conversion_mac.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 @interface NSEventForTesting : NSEvent
 @property(copy, nonatomic) NSString* characters;
 @end
@@ -37,15 +41,14 @@
 @end
 
 std::ostream& operator<<(std::ostream& out, NSObject* obj) {
-  return out << base::SysNSStringToUTF8([obj description]);
+  return out << base::SysNSStringToUTF8(obj.description);
 }
 
 std::ostream& operator<<(std::ostream& out, NSMenuItem* item) {
-  return out << "NSMenuItem " << base::SysNSStringToUTF8([item keyEquivalent]);
+  return out << "NSMenuItem " << base::SysNSStringToUTF8(item.keyEquivalent);
 }
 
-namespace ui {
-namespace cocoa {
+namespace ui::cocoa {
 namespace {
 
 NSEvent* KeyEvent(const NSUInteger modifierFlags,
@@ -65,11 +68,11 @@
 }
 
 NSMenuItem* MenuItem(NSString* equiv, NSUInteger mask = 0) {
-  NSMenuItem* item = [[[NSMenuItem alloc] initWithTitle:@""
-                                                 action:NULL
-                                          keyEquivalent:@""] autorelease];
-  [item setKeyEquivalent:equiv];
-  [item setKeyEquivalentModifierMask:mask];
+  NSMenuItem* item = [[NSMenuItem alloc] initWithTitle:@""
+                                                action:nil
+                                         keyEquivalent:@""];
+  item.keyEquivalent = equiv;
+  item.keyEquivalentModifierMask = mask;
   return item;
 }
 
@@ -97,12 +100,12 @@
   // Make sure that Cocoa does in fact agree with our expectations. However,
   // in some cases cocoa behaves weirdly (if you create e.g. a new event that
   // contains all fields of the event that you get when hitting cmd-a with a
-  // russion keyboard layout, the copy won't fire a menu item that has cmd-a as
+  // Russian keyboard layout, the copy won't fire a menu item that has cmd-a as
   // key equivalent, even though the original event would) and isn't a good
   // oracle function.
   if (compare_cocoa) {
-    base::scoped_nsobject<NSMenu> menu([[NSMenu alloc] initWithTitle:@"Menu!"]);
-    [menu setAutoenablesItems:NO];
+    NSMenu* menu = [[NSMenu alloc] initWithTitle:@"Menu!"];
+    menu.autoenablesItems = NO;
     EXPECT_FALSE([menu performKeyEquivalent:key]);
     [menu addItem:item];
     EXPECT_EQ(expected_result, [menu performKeyEquivalent:key])
@@ -486,48 +489,48 @@
   // raw characters, where "shift" should be handled correctly.
   key = KeyEvent(0x100108, @"t", @"\u3145", 17);
   ExpectKeyFiresItem(key, MenuItem(@"t", NSEventModifierFlagCommand),
-                     /*compareCocoa=*/false);
+                     /*compare_cocoa=*/false);
   ExpectKeyDoesntFireItem(key, MenuItem(@"T", NSEventModifierFlagCommand),
-                          /*compareCocoa=*/false);
+                          /*compare_cocoa=*/false);
 
   key = KeyEvent(0x12010a, @"t", @"\u3146", 17);
   ExpectKeyDoesntFireItem(key, MenuItem(@"t", NSEventModifierFlagCommand),
-                          /*compareCocoa=*/false);
+                          /*compare_cocoa=*/false);
   ExpectKeyFiresItem(key, MenuItem(@"T", NSEventModifierFlagCommand),
-                     /*compareCocoa=*/false);
+                     /*compare_cocoa=*/false);
 
   // On Czech layout, cmd + '+' should instead trigger cmd + '1'.
   key = KeyEvent(0x100108, @"1", @"+", 18);
   ExpectKeyFiresItem(key, MenuItem(@"1", NSEventModifierFlagCommand),
-                     /*compareCocoa=*/false);
+                     /*compare_cocoa=*/false);
 
   // On Vietnamese layout, cmd + '' [vkeycode = 18] should instead trigger cmd +
   // '1'. Ditto for other number keys.
   key = KeyEvent(0x100108, @"1", @"", 18);
   ExpectKeyFiresItem(key, MenuItem(@"1", NSEventModifierFlagCommand),
-                     /*compareCocoa=*/false);
+                     /*compare_cocoa=*/false);
   key = KeyEvent(0x100108, @"4", @"", 21);
   ExpectKeyFiresItem(key, MenuItem(@"4", NSEventModifierFlagCommand),
-                     /*compareCocoa=*/false);
+                     /*compare_cocoa=*/false);
 
   // On French AZERTY layout, cmd + '&' [vkeycode = 18] should instead trigger
   // cmd + '1'. Ditto for other number keys.
   key = KeyEvent(0x100108, @"&", @"&", 18);
   ExpectKeyFiresItem(key, MenuItem(@"1", NSEventModifierFlagCommand),
-                     /*compareCocoa=*/false);
+                     /*compare_cocoa=*/false);
   key = KeyEvent(0x100108, @"é", @"é", 19);
   ExpectKeyFiresItem(key, MenuItem(@"2", NSEventModifierFlagCommand),
-                     /*compareCocoa=*/false);
+                     /*compare_cocoa=*/false);
 
   // In Hebrew layout, make sure Cmd-q works.
   key = KeyEvent(0x100110, @"q", @"/", 12);
   ExpectKeyDoesntFireItem(key, MenuItem(@"q", NSEventModifierFlagCommand),
-                          /*compareCocoa=*/false);
+                          /*compare_cocoa=*/false);
 
   SetIsInputSourceCommandHebrewForTesting(true);
 
   ExpectKeyFiresItem(key, MenuItem(@"q", NSEventModifierFlagCommand),
-                     /*compareCocoa=*/false);
+                     /*compare_cocoa=*/false);
 
   SetIsInputSourceCommandHebrewForTesting(false);
 }
@@ -550,7 +553,7 @@
   // the string to lower case.
   [base::mac::ObjCCastStrict<NSEventForTesting>(cmdWWithCapsLock)
       setCharacters:capitalW];
-  ExpectKeyFiresItem(cmdWWithCapsLock, closeTabItem, /*compareCocoa=*/false);
+  ExpectKeyFiresItem(cmdWWithCapsLock, closeTabItem, /*compare_cocoa=*/false);
 
   // Make sure Shift-Cmd W triggers Close Window.
   NSEvent* shiftCmdW =
@@ -558,7 +561,7 @@
                @"\u1612", kVK_ANSI_W);
   [base::mac::ObjCCastStrict<NSEventForTesting>(shiftCmdW)
       setCharacters:capitalW];
-  ExpectKeyFiresItem(shiftCmdW, closeWindowItem, /*compareCocoa=*/false);
+  ExpectKeyFiresItem(shiftCmdW, closeWindowItem, /*compare_cocoa=*/false);
 
   // And also Shift-Cmd W with Caps Lock down.
   NSEvent* shiftCmdWWithCapsLock =
@@ -568,7 +571,7 @@
   [base::mac::ObjCCastStrict<NSEventForTesting>(shiftCmdWWithCapsLock)
       setCharacters:capitalW];
   ExpectKeyFiresItem(shiftCmdWWithCapsLock, closeWindowItem,
-                     /*compareCocoa=*/false);
+                     /*compare_cocoa=*/false);
 }
 
 NSString* keyCodeToCharacter(NSUInteger keyCode,
@@ -579,9 +582,8 @@
       layout, (UInt16)keyCode, kUCKeyActionDown, modifiers, LMGetKbdType(),
       &deadKeyStateUnused);
 
-  CFStringRef temp =
-      CFStringCreateWithCharacters(kCFAllocatorDefault, &unicodeChar, 1);
-  return [(NSString*)temp autorelease];
+  return base::apple::CFToNSOwnershipCast(
+      CFStringCreateWithCharacters(kCFAllocatorDefault, &unicodeChar, 1));
 }
 
 TEST(NSMenuItemAdditionsTest, TestMOnDifferentLayouts) {
@@ -608,8 +610,9 @@
     NSUInteger key_code = 0x2e;  // "m" on a US layout and most other layouts.
 
     // On a few layouts, "m" has a different key code.
-    NSString* layout_id = base::mac::CFToNSCast(base::mac::CFCast<CFStringRef>(
-        TISGetInputSourceProperty(ref, kTISPropertyInputSourceID)));
+    NSString* layout_id =
+        base::apple::CFToNSPtrCast(base::mac::CFCast<CFStringRef>(
+            TISGetInputSourceProperty(ref, kTISPropertyInputSourceID)));
     ASSERT_TRUE(layout_id);
     if ([layout_id isEqualToString:@"com.apple.keylayout.Belgian"] ||
         [layout_id isEqualToString:@"com.apple.keylayout.Italian"] ||
@@ -740,5 +743,4 @@
 }
 
 }  // namespace
-}  // namespace cocoa
-}  // namespace ui
+}  // namespace ui::cocoa
diff --git a/ui/base/cocoa/touch_bar_util_unittest.mm b/ui/base/cocoa/touch_bar_util_unittest.mm
index fe32052..4144919 100644
--- a/ui/base/cocoa/touch_bar_util_unittest.mm
+++ b/ui/base/cocoa/touch_bar_util_unittest.mm
@@ -9,6 +9,10 @@
 #include "testing/platform_test.h"
 #import "ui/base/test/cocoa_helper.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 namespace {
 
 const char kTestChromeBundleId[] = "test.bundleid";
diff --git a/ui/base/cocoa/tracking_area_unittest.mm b/ui/base/cocoa/tracking_area_unittest.mm
index f8b814d31..a45d4ca5 100644
--- a/ui/base/cocoa/tracking_area_unittest.mm
+++ b/ui/base/cocoa/tracking_area_unittest.mm
@@ -3,9 +3,12 @@
 // found in the LICENSE file.
 
 #import "ui/base/cocoa/tracking_area.h"
-#include "base/mac/scoped_nsobject.h"
 #import "ui/base/test/cocoa_helper.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 // A test object that counts the number of times a message is sent to it.
 @interface TestTrackingAreaOwner : NSObject {
  @private
@@ -31,12 +34,11 @@
         trackingArea_([[CrTrackingArea alloc]
             initWithRect:NSMakeRect(0, 0, 100, 100)
                  options:NSTrackingMouseMoved | NSTrackingActiveInKeyWindow
-                   owner:owner_.get()
-                userInfo:nil]) {
-  }
+                   owner:owner_
+                userInfo:nil]) {}
 
-  base::scoped_nsobject<TestTrackingAreaOwner> owner_;
-  base::scoped_nsobject<CrTrackingArea> trackingArea_;
+  TestTrackingAreaOwner* __strong owner_;
+  CrTrackingArea* __strong trackingArea_;
 };
 
 TEST_F(CrTrackingAreaTest, OwnerForwards) {
diff --git a/ui/base/l10n/l10n_util_ios_unittest.mm b/ui/base/l10n/l10n_util_ios_unittest.mm
index f4be246..97d57b1 100644
--- a/ui/base/l10n/l10n_util_ios_unittest.mm
+++ b/ui/base/l10n/l10n_util_ios_unittest.mm
@@ -10,6 +10,10 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "testing/platform_test.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 using L10nUtilIOSTest = PlatformTest;
 
 TEST_F(L10nUtilIOSTest, GetDisplayNameForLocale) {
diff --git a/ui/base/l10n/l10n_util_mac_unittest.mm b/ui/base/l10n/l10n_util_mac_unittest.mm
index 340354a..879a700 100644
--- a/ui/base/l10n/l10n_util_mac_unittest.mm
+++ b/ui/base/l10n/l10n_util_mac_unittest.mm
@@ -11,7 +11,11 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "testing/platform_test.h"
 
-typedef PlatformTest L10nUtilMacTest;
+using L10nUtilMacTest = PlatformTest;
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
 
 TEST_F(L10nUtilMacTest, FixUpWindowsStyleLabel) {
   struct TestData {
diff --git a/ui/base/test/ios/ui_image_test_utils_unittest.mm b/ui/base/test/ios/ui_image_test_utils_unittest.mm
index 0fc67d4..82a932f 100644
--- a/ui/base/test/ios/ui_image_test_utils_unittest.mm
+++ b/ui/base/test/ios/ui_image_test_utils_unittest.mm
@@ -6,8 +6,11 @@
 
 #include "testing/gtest/include/gtest/gtest.h"
 
-namespace ui {
-namespace test {
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+namespace ui::test {
 
 // Test the creation of UIImages.
 TEST(UIImageTestUtilsTest, TestImageCreation) {
@@ -51,5 +54,4 @@
       uiimage_utils::UIImagesAreEqual(imageGreen10x10, imageGreen10x10Bis));
 }
 
-}  // namespace test
-}  // namespace ui
+}  // namespace ui::test
diff --git a/ui/base/test/view_tree_validator.h b/ui/base/test/view_tree_validator.h
index b11087c..5fdd935 100644
--- a/ui/base/test/view_tree_validator.h
+++ b/ui/base/test/view_tree_validator.h
@@ -9,23 +9,27 @@
 
 @class NSView;
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 namespace ui {
 
 struct ViewTreeProblemDetails {
-  enum ProblemType {
+  enum class ProblemType {
     // |view_a| (the child view) is not entirely contained within the bounds of
     // |view_b| (the parent view).
-    VIEW_OUTSIDE_PARENT,
+    kViewOutsideParent,
 
     // |view_a| and |view_b|, neither of which is an ancestor of the other,
     // overlap each other, and at least one of |view_a| or |view_b| has
     // localizable text (is an NSControl or NSText).
-    VIEWS_OVERLAP,
+    kViewsOverlap,
   };
 
   ProblemType type;
-  NSView* view_a;
-  NSView* view_b;
+  NSView* __strong view_a;
+  NSView* __strong view_b;
 
   std::string ToString();
 };
diff --git a/ui/base/test/view_tree_validator.mm b/ui/base/test/view_tree_validator.mm
index e705584..d6a4d26 100644
--- a/ui/base/test/view_tree_validator.mm
+++ b/ui/base/test/view_tree_validator.mm
@@ -57,7 +57,8 @@
       if (!NSContainsRect(view.bounds, child.frame) &&
           !IgnoreChildBoundsChecks(view)) {
         return absl::optional<ViewTreeProblemDetails>(
-            {ViewTreeProblemDetails::VIEW_OUTSIDE_PARENT, child, view});
+            {ViewTreeProblemDetails::ProblemType::kViewOutsideParent, child,
+             view});
       }
     }
 
@@ -76,7 +77,7 @@
       if ([view isDescendantOf:other] || [other isDescendantOf:view])
         continue;
       return absl::optional<ViewTreeProblemDetails>(
-          {ViewTreeProblemDetails::VIEWS_OVERLAP, view, other});
+          {ViewTreeProblemDetails::ProblemType::kViewsOverlap, view, other});
     }
   }
 
@@ -86,12 +87,12 @@
 std::string ViewTreeProblemDetails::ToString() {
   NSString* s;
   switch (type) {
-    case VIEW_OUTSIDE_PARENT:
+    case ProblemType::kViewOutsideParent:
       s = [NSString stringWithFormat:@"View %@ [%@] outside parent %@ [%@]",
                                      view_a, NSStringFromRect(view_a.frame),
                                      view_b, NSStringFromRect(view_b.frame)];
       break;
-    case VIEWS_OVERLAP:
+    case ProblemType::kViewsOverlap:
       s = [NSString stringWithFormat:@"Views %@ [%@] and %@ [%@] overlap",
                                      view_a, NSStringFromRect(view_a.frame),
                                      view_b, NSStringFromRect(view_b.frame)];
diff --git a/ui/base/test/view_tree_validator_unittest.mm b/ui/base/test/view_tree_validator_unittest.mm
index d100c67..114fc2d 100644
--- a/ui/base/test/view_tree_validator_unittest.mm
+++ b/ui/base/test/view_tree_validator_unittest.mm
@@ -3,31 +3,36 @@
 // found in the LICENSE file.
 
 #include "ui/base/test/view_tree_validator.h"
+
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/base/test/cocoa_helper.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 using ViewTreeValidatorTest = ui::CocoaTest;
 
 namespace {
 
-NSView* AutoreleasedButtonWithFrame(int x, int y, int w, int h) {
-  return [[[NSButton alloc] initWithFrame:NSMakeRect(x, y, w, h)] autorelease];
+NSView* ButtonWithFrame(int x, int y, int w, int h) {
+  return [[NSButton alloc] initWithFrame:NSMakeRect(x, y, w, h)];
 }
 
-NSView* AutoreleasedViewWithFrame(int x, int y, int w, int h) {
-  return [[[NSView alloc] initWithFrame:NSMakeRect(x, y, w, h)] autorelease];
+NSView* ViewWithFrame(int x, int y, int w, int h) {
+  return [[NSView alloc] initWithFrame:NSMakeRect(x, y, w, h)];
 }
 
 void AdjustWidth(NSView* view, int delta) {
   NSRect r = view.frame;
   r.size.width += delta;
-  [view setFrame:r];
+  view.frame = r;
 }
 
 void AdjustHeight(NSView* view, int delta) {
   NSRect r = view.frame;
   r.size.height += delta;
-  [view setFrame:r];
+  view.frame = r;
 }
 
 }  // namespace
@@ -36,12 +41,25 @@
   NSWindow* window = test_window();
   int width = NSWidth(window.contentView.frame);
   int height = NSHeight(window.contentView.frame);
-  NSView* view_1 = AutoreleasedViewWithFrame(0, 0, width / 2, height);
-  NSView* view_2 = AutoreleasedButtonWithFrame(width / 2, 0, width / 2, height);
-  NSView* view_3 = AutoreleasedButtonWithFrame(0, 0, width / 2, height / 2);
-  NSView* view_4 = AutoreleasedViewWithFrame(0, 0, width / 2, height / 2);
-  NSView* view_5 =
-      AutoreleasedViewWithFrame(0, height / 2, width / 2, height / 2);
+
+  // A diagram. Views 3–5 share edges with their parents, and views 4 and 5
+  // share an overlapping edge, but in this diagram, the edges are drawn with
+  // insets to make it clear which views are subviews of other views.
+  // ┌────────────┬────────────┐
+  // │            │ ┌────────┐ │
+  // │            │ │ view_5 │ │
+  // │            │ └────────┘ │
+  // │   view_1   |   view_2   │
+  // │ ┌────────┐ │ ┌────────┐ │
+  // │ │ view_3 │ │ │ view_4 │ │
+  // │ └────────┘ │ └────────┘ │
+  // └────────────┴────────────┘
+
+  NSView* view_1 = ViewWithFrame(0, 0, width / 2, height);
+  NSView* view_2 = ButtonWithFrame(width / 2, 0, width / 2, height);
+  NSView* view_3 = ButtonWithFrame(0, 0, width / 2, height / 2);
+  NSView* view_4 = ViewWithFrame(0, 0, width / 2, height / 2);
+  NSView* view_5 = ViewWithFrame(0, height / 2, width / 2, height / 2);
 
   [view_2 addSubview:view_4];
   [view_2 addSubview:view_5];
@@ -62,7 +80,8 @@
     absl::optional<ui::ViewTreeProblemDetails> details =
         ui::ValidateViewTree(window.contentView);
     ASSERT_TRUE(details.has_value());
-    EXPECT_EQ(details->type, ui::ViewTreeProblemDetails::VIEW_OUTSIDE_PARENT);
+    EXPECT_EQ(details->type,
+              ui::ViewTreeProblemDetails::ProblemType::kViewOutsideParent);
     EXPECT_EQ(details->view_a, view_3);
     EXPECT_EQ(details->view_b, view_1);
     AdjustWidth(view_3, -1);
@@ -74,11 +93,12 @@
     absl::optional<ui::ViewTreeProblemDetails> details =
         ui::ValidateViewTree(window.contentView);
     ASSERT_TRUE(details.has_value());
-    EXPECT_EQ(details->type, ui::ViewTreeProblemDetails::VIEWS_OVERLAP);
+    EXPECT_EQ(details->type,
+              ui::ViewTreeProblemDetails::ProblemType::kViewsOverlap);
 
     // Since there's no specified order for |view_a| and |view_b| for
-    // VIEWS_OVERLAP, check that |view_1| and |view_2| both appear exactly once
-    // in |view_a| and |view_b|.
+    // kViewsOverlap, check that |view_1| and |view_2| both appear exactly
+    // once in |view_a| and |view_b|.
     EXPECT_TRUE(details->view_a == view_1 || details->view_a == view_2);
     EXPECT_TRUE(details->view_b == view_1 || details->view_b == view_2);
     EXPECT_NE(details->view_a, details->view_b);
diff --git a/ui/color/BUILD.gn b/ui/color/BUILD.gn
index e36b69c..e36fc80 100644
--- a/ui/color/BUILD.gn
+++ b/ui/color/BUILD.gn
@@ -34,10 +34,6 @@
     "color_metrics.cc",
     "color_mixer.cc",
     "color_provider.cc",
-    "color_provider_source.cc",
-    "color_provider_source.h",
-    "color_provider_source_observer.cc",
-    "color_provider_source_observer.h",
     "color_provider_utils.cc",
     "color_provider_utils.h",
     "color_recipe.cc",
@@ -129,6 +125,10 @@
     "color_mixers.h",
     "color_provider_manager.cc",
     "color_provider_manager.h",
+    "color_provider_source.cc",
+    "color_provider_source.h",
+    "color_provider_source_observer.cc",
+    "color_provider_source_observer.h",
     "core_default_color_mixer.cc",
     "core_default_color_mixer.h",
     "material_ui_color_mixer.cc",
diff --git a/ui/color/color_provider_manager.cc b/ui/color/color_provider_manager.cc
index a027ca67..992540d55 100644
--- a/ui/color/color_provider_manager.cc
+++ b/ui/color/color_provider_manager.cc
@@ -154,7 +154,7 @@
     ++num_providers_initialized_;
 
     iter = color_providers_.emplace(key, std::move(provider)).first;
-    RecordColorProviderCacheSize(color_providers_.size());
+    RecordColorProviderCacheSize(static_cast<int>(color_providers_.size()));
   }
   ColorProvider* provider = iter->second.get();
   DCHECK(provider);
diff --git a/ui/color/color_provider_source.cc b/ui/color/color_provider_source.cc
index d3aa961..9fbe6f92 100644
--- a/ui/color/color_provider_source.cc
+++ b/ui/color/color_provider_source.cc
@@ -29,4 +29,8 @@
     observer.OnColorProviderChanged();
 }
 
+ui::ColorProviderManager::ColorMode ColorProviderSource::GetColorMode() const {
+  return GetColorProviderKey().color_mode;
+}
+
 }  // namespace ui
diff --git a/ui/color/color_provider_source.h b/ui/color/color_provider_source.h
index aa7ec1b0..7117d11 100644
--- a/ui/color/color_provider_source.h
+++ b/ui/color/color_provider_source.h
@@ -27,7 +27,9 @@
   virtual ~ColorProviderSource();
 
   // Returns the ColorProvider associated with the Key returned by
-  // GetColorProviderKey();
+  // GetColorProviderKey().
+  // TODO(tluk): This shouldn't need to be virtual, most overrides simply pass
+  // the key to the manager.
   virtual const ColorProvider* GetColorProvider() const = 0;
 
   void AddObserver(ColorProviderSourceObserver* observer);
@@ -37,6 +39,9 @@
   // in the method `GetColorProvider()` changes.
   void NotifyColorProviderChanged();
 
+  // Gets the ColorMode currently associated with this source.
+  ui::ColorProviderManager::ColorMode GetColorMode() const;
+
   base::ObserverList<ColorProviderSourceObserver>& observers_for_testing() {
     return observers_;
   }
diff --git a/ui/ozone/platform/wayland/host/wayland_surface.cc b/ui/ozone/platform/wayland/host/wayland_surface.cc
index 1a47ae78..6cf2930 100644
--- a/ui/ozone/platform/wayland/host/wayland_surface.cc
+++ b/ui/ozone/platform/wayland/host/wayland_surface.cc
@@ -700,9 +700,32 @@
     viewport_src_dip =
         gfx::ScaleRect(crop_transformed, bounds.width(), bounds.height());
     DCHECK(viewport());
-    if (wl_fixed_from_double(viewport_src_dip.width()) == 0 ||
-        wl_fixed_from_double(viewport_src_dip.height()) == 0 ||
-        wl_fixed_from_double(viewport_src_dip.x()) < 0 ||
+
+    // It not completely unexpected to stretch a texture more than the smallest
+    // fixed point can represent. The two cases here are solid color buffers
+    // (1x1 or 4x4) and something like 9-patch shadows (5x5) stretched to the
+    // entire window width/height.
+    {
+      constexpr wl_fixed_t kViewportSizeMin = 1;
+      const float kViewPortSizeMinFloat =
+          static_cast<float>(wl_fixed_to_double(kViewportSizeMin));
+
+      if (wl_fixed_from_double(viewport_src_dip.width()) <= 0) {
+        viewport_src_dip.set_width(kViewPortSizeMinFloat);
+        src_to_set[2] = kViewportSizeMin;
+      } else {
+        src_to_set[2] = wl_fixed_from_double(viewport_src_dip.width());
+      }
+
+      if (wl_fixed_from_double(viewport_src_dip.height()) <= 0) {
+        viewport_src_dip.set_height(kViewPortSizeMinFloat);
+        src_to_set[3] = kViewportSizeMin;
+      } else {
+        src_to_set[3] = wl_fixed_from_double(viewport_src_dip.height());
+      }
+    }
+
+    if (wl_fixed_from_double(viewport_src_dip.x()) < 0 ||
         wl_fixed_from_double(viewport_src_dip.y()) < 0) {
       LOG(ERROR) << "Sending viewport src with width/height zero or negative "
                     "origin will result in wayland disconnection";
@@ -713,23 +736,13 @@
                  << " bounds=" << bounds.ToString()
                  << "  pending_state_.buffer_size_px="
                  << pending_state_.buffer_size_px.ToString();
-      constexpr wl_fixed_t kViewportSizeMin = 1;
-      const float kViewPortSizeMinFloat =
-          static_cast<float>(wl_fixed_to_double(kViewportSizeMin));
-      LOG(ERROR)
-          << "Limiting viewport_src_dip size to be non zero with a minium of "
-          << kViewportSizeMin;
-      viewport_src_dip.set_width(
-          std::max(viewport_src_dip.width(), kViewPortSizeMinFloat));
-      viewport_src_dip.set_height(
-          std::max(viewport_src_dip.height(), kViewPortSizeMinFloat));
+
       viewport_src_dip.set_x(std::max(viewport_src_dip.x(), 0.f));
       viewport_src_dip.set_y(std::max(viewport_src_dip.y(), 0.f));
     }
+
     src_to_set[0] = wl_fixed_from_double(viewport_src_dip.x()),
     src_to_set[1] = wl_fixed_from_double(viewport_src_dip.y());
-    src_to_set[2] = wl_fixed_from_double(viewport_src_dip.width());
-    src_to_set[3] = wl_fixed_from_double(viewport_src_dip.height());
   }
   // Apply crop (wp_viewport.set_source).
   if (viewport() && !base::ranges::equal(src_to_set, src_set_)) {
diff --git a/ui/webui/resources/cr_components/color_change_listener/colors_css_updater.ts b/ui/webui/resources/cr_components/color_change_listener/colors_css_updater.ts
index cb6740a..9aa5d40 100644
--- a/ui/webui/resources/cr_components/color_change_listener/colors_css_updater.ts
+++ b/ui/webui/resources/cr_components/color_change_listener/colors_css_updater.ts
@@ -20,6 +20,7 @@
 let documentInstance: ColorChangeUpdater|null = null;
 
 // <if expr="chromeos_ash">
+const COLOR_PROVIDER_CHANGED: string = 'color-provider-changed';
 type ColorChangeListener = () => void;
 // </if>
 
@@ -28,7 +29,7 @@
   private root_: Document|ShadowRoot;
 
   // <if expr="chromeos_ash">
-  private listeners_: ColorChangeListener[] = [];
+  eventTarget: EventTarget = new EventTarget();
   // </if>
 
   constructor(root: Document|ShadowRoot) {
@@ -52,9 +53,7 @@
   async onColorProviderChanged() {
     await this.refreshColorsCss();
     // <if expr="chromeos_ash">
-    for (const listener of this.listeners_) {
-      listener();
-    }
+    this.eventTarget.dispatchEvent(new CustomEvent(COLOR_PROVIDER_CHANGED));
     // </if>
   }
 
@@ -114,43 +113,17 @@
     return documentInstance ||
         (documentInstance = new ColorChangeUpdater(document));
   }
-
-  // <if expr="chromeos_ash">
-  /**
-   * Register a function to be called every time the page's color provider
-   * changes. Note that the listeners will only be invoked AFTER start() is
-   * called, and only after the updated styles have been loaded.
-   */
-  addListener(listener: ColorChangeListener): void {
-    this.listeners_.push(listener);
-  }
-
-  /**
-   * Remove a listener that was previously registered via addListener().
-   * If provided with a listener that was not previously registered does
-   * nothing.
-   * @return Whether a listener was actually removed.
-   */
-  removeListener(changeListener: ColorChangeListener): boolean {
-    const toRemove =
-        this.listeners_.findIndex(listener => listener === changeListener);
-    if (toRemove === -1) {
-      return false;
-    }
-
-    this.listeners_.splice(toRemove, 1);
-    return true;
-  }
-  // </if>
 }
 
 // <if expr="chromeos_ash">
 export function addColorChangeListener(listener: ColorChangeListener) {
-  ColorChangeUpdater.forDocument().addListener(listener);
+  ColorChangeUpdater.forDocument().eventTarget.addEventListener(
+      COLOR_PROVIDER_CHANGED, listener);
 }
 
 export function removeColorChangeListener(listener: ColorChangeListener) {
-  ColorChangeUpdater.forDocument().removeListener(listener);
+  ColorChangeUpdater.forDocument().eventTarget.removeEventListener(
+      COLOR_PROVIDER_CHANGED, listener);
 }
 // </if>
 
@@ -159,6 +132,5 @@
  * top level HTML document whenever changes occur.
  */
 export function startColorChangeUpdater() {
-  const updater = ColorChangeUpdater.forDocument();
-  updater.start();
+  ColorChangeUpdater.forDocument().start();
 }
diff --git a/weblayer/browser/ssl_error_controller_client.cc b/weblayer/browser/ssl_error_controller_client.cc
index dfea8a4..db1cb6c 100644
--- a/weblayer/browser/ssl_error_controller_client.cc
+++ b/weblayer/browser/ssl_error_controller_client.cc
@@ -48,6 +48,9 @@
 }
 
 void SSLErrorControllerClient::Proceed() {
+  // Notifies the browser process when a certificate exception is allowed.
+  web_contents_->SetAlwaysSendSubresourceNotifications();
+
   web_contents_->GetBrowserContext()->GetSSLHostStateDelegate()->AllowCert(
       request_url_.host(), *ssl_info_.cert.get(), cert_error_,
       web_contents_->GetPrimaryMainFrame()->GetStoragePartition());