diff --git a/.clang-format b/.clang-format
index 4d839d95..5a40766 100644
--- a/.clang-format
+++ b/.clang-format
@@ -10,6 +10,7 @@
 # TODO(crbug.com/1392808): Remove when InsertBraces has been upstreamed into
 # the Chromium style (is implied by BasedOnStyle: Chromium).
 InsertBraces: true
+InsertNewlineAtEOF: true
 
 # Make sure code like:
 # IPC_BEGIN_MESSAGE_MAP()
diff --git a/DEPS b/DEPS
index cb84694..617c4de 100644
--- a/DEPS
+++ b/DEPS
@@ -304,15 +304,15 @@
   # 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': '90f653a7233ead5d7b648b1eb360b725987a85a8',
+  'skia_revision': '4f1cae66791c60dc6584f6cecbd58c533965a7ac',
   # 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': '379e492a67ce6ce01f75fb8475f0715577cde249',
+  'v8_revision': 'a34243892380ef2bba92a69af3bf7cb422a3755d',
   # 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': 'ad52f12e5943aff75499ab979bd5322c850114e0',
+  'angle_revision': '888ca8d9e307db9936747ca5411a168b5b7669dd',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -375,7 +375,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling catapult
   # and whatever else without interference from each other.
-  'catapult_revision': 'c82a855930faff1001ebe2298e75ca5a91f29391',
+  'catapult_revision': 'f6395b16b47ca583919ef1061a65981d3e799c32',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling CrossBench
   # and whatever else without interference from each other.
@@ -431,7 +431,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'dawn_revision': 'e8e3f9ada51ddfd611f56fbe6a835b9fb341e331',
+  'dawn_revision': '7605d59518e9db1871b0fd13796ca5d323406f94',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -495,7 +495,7 @@
 
   # If you change this, also update the libc++ revision in
   # //buildtools/deps_revisions.gni.
-  'libcxx_revision':       'af83f5d2fada265d8b5adc0a23a29e060907b3e7',
+  'libcxx_revision':       'e44019bfac2b2d3ebe1618628884f85c8600e322',
 
   # GN CIPD package version.
   'gn_version': 'git_revision:41fef642de70ecdcaaa26be96d56a0398f95abd4',
@@ -790,7 +790,7 @@
 
   'src/clank': {
     'url': Var('chrome_git') + '/clank/internal/apps.git' + '@' +
-    'bac2761c87c46aa392b7c8d18be2c288d3ea943b',
+    '25a0e75cf4d417f53269c6c6c7aaee7206c6785e',
     'condition': 'checkout_android and checkout_src_internal',
   },
 
@@ -987,7 +987,7 @@
   },
 
   'src/third_party/androidx_javascriptengine/src': {
-      'url': Var('chromium_git') + '/aosp/platform/frameworks/support/javascriptengine/javascriptengine/src.git' + '@' + '82ad02be1a83694592b1c32a974d44cd1da2238b',
+      'url': Var('chromium_git') + '/aosp/platform/frameworks/support/javascriptengine/javascriptengine/src.git' + '@' + '5fa7f8147a178691325db20142005742070335dc',
       'condition': 'checkout_android',
   },
 
@@ -1198,7 +1198,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' + '@' + '529b06282255290815da45b70a43b91eec1fdbaf',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + 'cfda6f1ab8b692fad1e5f0a9fc2cee273f19afbb',
       'condition': 'checkout_chromeos',
   },
 
@@ -1702,7 +1702,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + 'a0ad54d66f52deece55b502bcef0e7451e92cb7c',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + 'b4b873ba9cfeec866c5baf2097167fe6afdf71cb',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1847,7 +1847,7 @@
       'dep_type': 'cipd',
   },
 
-  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@379ee8599f0f7da141df2bce100d8ac921403a38',
+  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@623f01c24c99282845b5984880739d76fee13aa9',
 
   'src/third_party/vulkan_memory_allocator':
     Var('chromium_git') + '/external/github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git' + '@' + 'ebe84bec02c041d28f902da0214bf442743fc907',
@@ -1884,10 +1884,10 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + 'd1b65aa5a88f6efd900604dfcda840154e9f16e2',
 
   'src/third_party/webgpu-cts/src':
-    Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '147f559d72a3c9642d192466dcaaf245cb720649',
+    Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + 'dd474b33f85d63f950fedef2ba889d914c58652d',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '452d94047b5cdce41fdbdad2bec3f24f603dfcf4',
+    Var('webrtc_git') + '/src.git' + '@' + '6bdb285e2163e70b8f7d4e698b26dcea357e6616',
 
   # 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.
@@ -1964,7 +1964,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': Var('chrome_git') + '/chrome/src-internal.git@189bc195d4504e8c6cd28310a6b9f0489d454b1b',
+    'url': Var('chrome_git') + '/chrome/src-internal.git@ca3fc12c31cde3d7b3672864a5fa77bea98f6874',
     'condition': 'checkout_src_internal',
   },
 
@@ -2005,7 +2005,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/media_app/app',
-        'version': 'F64OcssxqyabjGLRpcDbljcEv_sa8tvyZ3NgfJve04EC',
+        'version': 'ACYRoNlTD56wvqxxbQBDo3y6aEWjdqhEP9mRWNFcoCYC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index 3684c4bb..295cbb6f 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -1170,7 +1170,7 @@
       [_THIRD_PARTY_EXCEPT_BLINK],  # Don't warn in third_party folders.
     ),
     BanRule(
-      r'/^\s*(import|export|module)\b',
+      r'/^\s*(export\s|import\s+["<:\w]|module(;|\s+[:\w]))',
       (
         'Modules are disallowed for now due to lack of toolchain support.',
       ),
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 b36f9ab7..6559049e 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
@@ -288,9 +288,6 @@
                     "Controls whether wake ups are possible for canceled tasks."),
             Flag.baseFeature(BaseFeatures.REMOVE_CANCELED_TASKS_IN_TASK_QUEUE,
                     "Controls whether or not canceled delayed tasks are removed from task queues."),
-            Flag.baseFeature(BaseFeatures.ALWAYS_ABANDON_SCHEDULED_TASK,
-                    "Controls whether or not the scheduled task is always abandoned when a timer "
-                            + "is stopped or resets."),
             Flag.baseFeature(BlinkFeatures.VIEW_TRANSITION,
                     "Enables the experimental View Transitions API."
                             + " See https://github.com/WICG/view-transitions/blob/main/explainer.md."),
diff --git a/android_webview/js_sandbox/java/src/org/chromium/android_webview/js_sandbox/service/JsSandboxIsolate.java b/android_webview/js_sandbox/java/src/org/chromium/android_webview/js_sandbox/service/JsSandboxIsolate.java
index 041e77c6..be838b69 100644
--- a/android_webview/js_sandbox/java/src/org/chromium/android_webview/js_sandbox/service/JsSandboxIsolate.java
+++ b/android_webview/js_sandbox/java/src/org/chromium/android_webview/js_sandbox/service/JsSandboxIsolate.java
@@ -5,15 +5,21 @@
 package org.chromium.android_webview.js_sandbox.service;
 
 import android.content.res.AssetFileDescriptor;
+import android.os.RemoteException;
 
 import androidx.javascriptengine.common.Utils;
 
+import org.chromium.android_webview.js_sandbox.common.IJsSandboxConsoleCallback;
 import org.chromium.android_webview.js_sandbox.common.IJsSandboxIsolate;
 import org.chromium.android_webview.js_sandbox.common.IJsSandboxIsolateCallback;
 import org.chromium.android_webview.js_sandbox.common.IJsSandboxIsolateSyncCallback;
+import org.chromium.base.Log;
+import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.annotations.JNINamespace;
 import org.chromium.base.annotations.NativeMethods;
 
+import java.util.concurrent.atomic.AtomicReference;
+
 import javax.annotation.concurrent.GuardedBy;
 
 /**
@@ -22,17 +28,24 @@
 @JNINamespace("android_webview")
 public class JsSandboxIsolate extends IJsSandboxIsolate.Stub {
     private static final String TAG = "JsSandboxIsolate";
+    // mLock must never be held whilst (synchronously) calling back into the client/embedding
+    // application, otherwise it's entirely possible for the embedder to then call back into service
+    // code (on another thread) and then try to take mLock again and therefore deadlock.
     private final Object mLock = new Object();
+    private final JsSandboxService mService;
+    private final AtomicReference<IJsSandboxConsoleCallback> mConsoleCallback =
+            new AtomicReference<IJsSandboxConsoleCallback>();
     @GuardedBy("mLock")
     private long mJsSandboxIsolate;
 
-    JsSandboxIsolate() {
-        mJsSandboxIsolate = JsSandboxIsolateJni.get().createNativeJsSandboxIsolateWrapper(0);
+    JsSandboxIsolate(JsSandboxService service) {
+        this(service, 0);
     }
 
-    JsSandboxIsolate(long maxHeapSizeBytes) {
-        mJsSandboxIsolate =
-                JsSandboxIsolateJni.get().createNativeJsSandboxIsolateWrapper(maxHeapSizeBytes);
+    JsSandboxIsolate(JsSandboxService service, long maxHeapSizeBytes) {
+        mService = service;
+        mJsSandboxIsolate = JsSandboxIsolateJni.get().createNativeJsSandboxIsolateWrapper(
+                this, maxHeapSizeBytes);
     }
 
     @Override
@@ -86,13 +99,66 @@
         }
     }
 
+    // Called by isolate thread
+    @CalledByNative
+    public void consoleMessage(int contextGroupId, int level, String message, String source,
+            int line, int column, String trace) {
+        final IJsSandboxConsoleCallback callback = mConsoleCallback.get();
+        if (callback == null) {
+            return;
+        }
+        final int messageLimit = 32768;
+        final int sourceLimit = 4096;
+        final int traceLimit = 16384;
+        if (message != null && message.length() > messageLimit) {
+            message = message.substring(0, messageLimit);
+        }
+        if (source != null && source.length() > sourceLimit) {
+            source = source.substring(0, sourceLimit);
+        }
+        if (trace != null && trace.length() > traceLimit) {
+            trace = trace.substring(0, traceLimit);
+        }
+        try {
+            callback.consoleMessage(contextGroupId, level, message, source, line, column, trace);
+        } catch (RemoteException e) {
+            Log.e(TAG, "consoleMessage notification failed", e);
+        }
+    }
+
+    // Called by isolate thread
+    @CalledByNative
+    public void consoleClear(int contextGroupId) {
+        final IJsSandboxConsoleCallback callback = mConsoleCallback.get();
+        if (callback == null) {
+            return;
+        }
+        try {
+            callback.consoleClear(contextGroupId);
+        } catch (RemoteException e) {
+            Log.e(TAG, "consoleClear notification failed", e);
+        }
+    }
+
+    @Override
+    public void setConsoleCallback(IJsSandboxConsoleCallback callback) {
+        synchronized (mLock) {
+            if (mJsSandboxIsolate == 0) {
+                throw new IllegalStateException("setConsoleCallback() called after close()");
+            }
+            mConsoleCallback.set(callback);
+            JsSandboxIsolateJni.get().setConsoleEnabled(mJsSandboxIsolate, this, callback != null);
+        }
+    }
+
     public static void initializeEnvironment() {
         JsSandboxIsolateJni.get().initializeEnvironment();
     }
 
     @NativeMethods
     public interface Natives {
-        long createNativeJsSandboxIsolateWrapper(long maxHeapSizeBytes);
+        long createNativeJsSandboxIsolateWrapper(
+                JsSandboxIsolate jsSandboxIsolate, long maxHeapSizeBytes);
 
         void initializeEnvironment();
 
@@ -107,5 +173,8 @@
 
         boolean provideNamedData(long nativeJsSandboxIsolate, JsSandboxIsolate caller, String name,
                 int fd, int length);
+
+        void setConsoleEnabled(
+                long nativeJsSandboxIsolate, JsSandboxIsolate caller, boolean enable);
     }
 }
diff --git a/android_webview/js_sandbox/java/src/org/chromium/android_webview/js_sandbox/service/JsSandboxService.java b/android_webview/js_sandbox/java/src/org/chromium/android_webview/js_sandbox/service/JsSandboxService.java
index ceace0a..dbee55e 100644
--- a/android_webview/js_sandbox/java/src/org/chromium/android_webview/js_sandbox/service/JsSandboxService.java
+++ b/android_webview/js_sandbox/java/src/org/chromium/android_webview/js_sandbox/service/JsSandboxService.java
@@ -25,17 +25,27 @@
     static final List<String> SUPPORTED_FEATURES = Arrays.asList(
             IJsSandboxService.ISOLATE_TERMINATION, IJsSandboxService.WASM_FROM_ARRAY_BUFFER,
             IJsSandboxService.ISOLATE_MAX_HEAP_SIZE_LIMIT,
-            IJsSandboxService.EVALUATE_WITHOUT_TRANSACTION_LIMIT);
+            IJsSandboxService.EVALUATE_WITHOUT_TRANSACTION_LIMIT,
+            IJsSandboxService.CONSOLE_MESSAGING);
+
+    /**
+     * Feature for {@link #isClientSideFeatureSupported(String)}.
+     * <p>
+     * When this feature is present, consoleMessage and consoleClear notifications are supported by
+     * the client.
+     * @hide
+     */
+    public static final String JS_FEATURE_CONSOLE_MESSAGING = "JS_FEATURE_CONSOLE_MESSAGING";
 
     private final IJsSandboxService.Stub mBinder = new IJsSandboxService.Stub() {
         @Override
         public IJsSandboxIsolate createIsolate() {
-            return new JsSandboxIsolate();
+            return new JsSandboxIsolate(JsSandboxService.this);
         }
 
         @Override
         public IJsSandboxIsolate createIsolateWithMaxHeapSizeBytes(long maxHeapSizeBytes) {
-            return new JsSandboxIsolate(maxHeapSizeBytes);
+            return new JsSandboxIsolate(JsSandboxService.this, maxHeapSizeBytes);
         }
 
         @Override
diff --git a/android_webview/js_sandbox/service/js_sandbox_isolate.cc b/android_webview/js_sandbox/service/js_sandbox_isolate.cc
index fff98664..61c02b0e 100644
--- a/android_webview/js_sandbox/service/js_sandbox_isolate.cc
+++ b/android_webview/js_sandbox/service/js_sandbox_isolate.cc
@@ -24,6 +24,7 @@
 #include "base/logging.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/scoped_refptr.h"
+#include "base/notreached.h"
 #include "base/numerics/safe_math.h"
 #include "base/strings/string_piece.h"
 #include "base/synchronization/waitable_event.h"
@@ -33,6 +34,7 @@
 #include "base/task/thread_pool.h"
 #include "base/task/thread_pool/thread_pool_instance.h"
 #include "base/threading/thread_restrictions.h"
+#include "base/time/time.h"
 #include "gin/arguments.h"
 #include "gin/array_buffer.h"
 #include "gin/function_template.h"
@@ -43,6 +45,8 @@
 #include "js_sandbox_isolate.h"
 #include "v8/include/v8-array-buffer.h"
 #include "v8/include/v8-function.h"
+#include "v8/include/v8-inspector.h"
+#include "v8/include/v8-isolate.h"
 #include "v8/include/v8-microtask-queue.h"
 #include "v8/include/v8-statistics.h"
 #include "v8/include/v8-template.h"
@@ -113,6 +117,47 @@
   return GetStackTrace(message, isolate);
 }
 
+jint remapConsoleMessageErrorLevel(const v8::Isolate::MessageErrorLevel level) {
+  // Converted level should match the values specified in the
+  // org.chromium.android_webview.js_sandbox.common.IJsSandboxIsolateClient AIDL
+  // file (in AndroidX).
+  //
+  // These will probably remain identical to the underlying v8 enums/constants,
+  // but are mapped explicitly here to ensure we can maintain compatibility even
+  // if there are changes or additions.
+  switch (level) {
+    case v8::Isolate::MessageErrorLevel::kMessageLog:
+      return 1 << 0;
+    case v8::Isolate::MessageErrorLevel::kMessageDebug:
+      return 1 << 1;
+    case v8::Isolate::MessageErrorLevel::kMessageInfo:
+      return 1 << 2;
+    case v8::Isolate::MessageErrorLevel::kMessageError:
+      return 1 << 3;
+    case v8::Isolate::MessageErrorLevel::kMessageWarning:
+      return 1 << 4;
+    case v8::Isolate::MessageErrorLevel::kMessageAll:
+      NOTREACHED_NORETURN();
+  }
+}
+
+// Converts a V8 inspector (UTF-8 or UTF-16) StringView to a jstring.
+base::android::ScopedJavaLocalRef<jstring> StringViewToJavaString(
+    JNIEnv* const env,
+    const v8_inspector::StringView& string_view) {
+  if (string_view.is8Bit()) {
+    return base::android::ConvertUTF8ToJavaString(
+        env, base::StringPiece(
+                 reinterpret_cast<const char*>(string_view.characters8()),
+                 string_view.length()));
+  } else {
+    return base::android::ConvertUTF16ToJavaString(
+        env, base::StringPiece16(
+                 reinterpret_cast<const char16_t*>(string_view.characters16()),
+                 string_view.length()));
+  }
+}
+
 void WasmAsyncResolvePromiseCallback(v8::Isolate* isolate,
                                      v8::Local<v8::Context> context,
                                      v8::Local<v8::Promise::Resolver> resolver,
@@ -127,6 +172,17 @@
   }
 }
 
+class NoopInspectorChannel final : public v8_inspector::V8Inspector::Channel {
+ public:
+  ~NoopInspectorChannel() override = default;
+  void sendResponse(
+      int callId,
+      std::unique_ptr<v8_inspector::StringBuffer> message) override {}
+  void sendNotification(
+      std::unique_ptr<v8_inspector::StringBuffer> message) override {}
+  void flushProtocolNotifications() override {}
+};
+
 }  // namespace
 
 namespace android_webview {
@@ -147,8 +203,69 @@
   length = len;
 }
 
-JsSandboxIsolate::JsSandboxIsolate(const size_t max_heap_size_bytes)
-    : isolate_max_heap_size_bytes_(max_heap_size_bytes),
+// This class must only be constructed, destructed, and used from the isolate
+// thread.
+class JsSandboxIsolate::InspectorClient final
+    : public v8_inspector::V8InspectorClient {
+ public:
+  explicit InspectorClient(JsSandboxIsolate& isolate) : isolate_(isolate) {}
+
+  ~InspectorClient() override = default;
+
+  void consoleAPIMessage(const int context_group_id,
+                         const v8::Isolate::MessageErrorLevel level,
+                         const v8_inspector::StringView& message,
+                         const v8_inspector::StringView& url,
+                         const unsigned int line_number,
+                         const unsigned int column_number,
+                         v8_inspector::V8StackTrace* const trace) override {
+    if (!isolate_->console_enabled_) {
+      return;
+    }
+
+    JNIEnv* env = base::android::AttachCurrentThread();
+    const jint converted_level = remapConsoleMessageErrorLevel(level);
+    base::android::ScopedJavaLocalRef<jstring> java_string_message =
+        StringViewToJavaString(env, message);
+    // url is actually just the source (file/expression) identifier.
+    base::android::ScopedJavaLocalRef<jstring> java_string_source =
+        StringViewToJavaString(env, url);
+    base::android::ScopedJavaLocalRef<jstring> java_string_trace;
+    if (trace && !trace->isEmpty()) {
+      StringViewToJavaString(env, trace->toString()->string());
+    }
+
+    android_webview::Java_JsSandboxIsolate_consoleMessage(
+        env, isolate_->j_isolate_, static_cast<jint>(context_group_id),
+        converted_level, java_string_message, java_string_source,
+        base::saturated_cast<jint>(line_number),
+        base::saturated_cast<jint>(column_number), java_string_trace);
+  }
+
+  void consoleClear(const int context_group_id) override {
+    if (!isolate_->console_enabled_) {
+      return;
+    }
+    JNIEnv* env = base::android::AttachCurrentThread();
+    android_webview::Java_JsSandboxIsolate_consoleClear(
+        env, isolate_->j_isolate_, static_cast<jint>(context_group_id));
+  }
+
+  double currentTimeMS() override {
+    // Note: although this is not monotonically increasing time, this reflects
+    // the behaviour of Blink code.
+    return base::Time::Now().ToDoubleT() * 1000.0;
+  }
+
+ private:
+  const base::raw_ref<JsSandboxIsolate> isolate_;
+};
+
+JsSandboxIsolate::JsSandboxIsolate(
+    const base::android::JavaParamRef<jobject>& j_isolate,
+    const size_t max_heap_size_bytes)
+    : j_isolate_(j_isolate),
+      isolate_max_heap_size_bytes_(max_heap_size_bytes),
       array_buffer_allocator_(std::make_unique<JsSandboxArrayBufferAllocator>(
           *gin::ArrayBufferAllocator::SharedInstance(),
           max_heap_size_bytes > 0
@@ -244,6 +361,17 @@
       .second;
 }
 
+// Called from Binder thread.
+void JsSandboxIsolate::SetConsoleEnabled(
+    JNIEnv* env,
+    const base::android::JavaParamRef<jobject>& obj,
+    const jboolean enable) {
+  control_task_runner_->PostTask(
+      FROM_HERE,
+      base::BindOnce(&JsSandboxIsolate::SetConsoleEnabledOnControlThread,
+                     base::Unretained(this), enable));
+}
+
 // Called from control sequence.
 void JsSandboxIsolate::PostEvaluationToIsolateThread(
     const std::string code,
@@ -722,6 +850,53 @@
       isolate_holder_->isolate(), std::move(backing_store)));
 }
 
+// Called from isolate thread.
+void JsSandboxIsolate::EnableOrDisableInspectorAsNeeded() {
+  const bool needed = console_enabled_;
+  const bool already_enabled = bool{inspector_client_};
+
+  if (already_enabled && !needed) {
+    inspector_session_.reset();
+    inspector_channel_.reset();
+    inspector_.reset();
+    inspector_client_.reset();
+  } else if (!already_enabled && needed) {
+    v8::Isolate::Scope isolate_scope(isolate_holder_->isolate());
+    v8::HandleScope handle_scope(isolate_holder_->isolate());
+    v8::Context::Scope scope(context_holder_->context());
+
+    constexpr int context_group_id = 1;
+    inspector_client_ = std::make_unique<InspectorClient>(*this);
+    inspector_ = v8_inspector::V8Inspector::create(isolate_holder_->isolate(),
+                                                   inspector_client_.get());
+    inspector_channel_ =
+        static_cast<std::unique_ptr<v8_inspector::V8Inspector::Channel>>(
+            std::make_unique<NoopInspectorChannel>());
+    inspector_session_ =
+        inspector_->connect(context_group_id, inspector_channel_.get(),
+                            /*state=*/v8_inspector::StringView(),
+                            v8_inspector::V8Inspector::kFullyTrusted,
+                            v8_inspector::V8Inspector::kNotWaitingForDebugger);
+    inspector_->contextCreated(v8_inspector::V8ContextInfo(
+        context_holder_->context(), context_group_id,
+        /*humanReadableName=*/v8_inspector::StringView()));
+  }
+}
+
+// Called from control sequence.
+void JsSandboxIsolate::SetConsoleEnabledOnControlThread(const bool enable) {
+  cancelable_task_tracker_->PostTask(
+      isolate_task_runner_.get(), FROM_HERE,
+      base::BindOnce(&JsSandboxIsolate::SetConsoleEnabledOnIsolateThread,
+                     base::Unretained(this), enable));
+}
+
+// Called from isolate thread.
+void JsSandboxIsolate::SetConsoleEnabledOnIsolateThread(const bool enable) {
+  console_enabled_ = enable;
+  EnableOrDisableInspectorAsNeeded();
+}
+
 static void JNI_JsSandboxIsolate_InitializeEnvironment(JNIEnv* env) {
   base::ThreadPoolInstance::CreateAndStartWithDefaultParams("JsSandboxIsolate");
 #ifdef V8_USE_EXTERNAL_STARTUP_DATA
@@ -733,10 +908,11 @@
 
 static jlong JNI_JsSandboxIsolate_CreateNativeJsSandboxIsolateWrapper(
     JNIEnv* env,
+    const base::android::JavaParamRef<jobject>& j_sandbox_isolate,
     jlong max_heap_size_bytes) {
   CHECK_GE(max_heap_size_bytes, 0);
-  JsSandboxIsolate* processor =
-      new JsSandboxIsolate(base::saturated_cast<size_t>(max_heap_size_bytes));
+  JsSandboxIsolate* processor = new JsSandboxIsolate(
+      j_sandbox_isolate, base::saturated_cast<size_t>(max_heap_size_bytes));
   return reinterpret_cast<intptr_t>(processor);
 }
 
diff --git a/android_webview/js_sandbox/service/js_sandbox_isolate.h b/android_webview/js_sandbox/service/js_sandbox_isolate.h
index f443e91a..c90ad64 100644
--- a/android_webview/js_sandbox/service/js_sandbox_isolate.h
+++ b/android_webview/js_sandbox/service/js_sandbox_isolate.h
@@ -17,6 +17,7 @@
 #include "base/synchronization/lock.h"
 #include "base/thread_annotations.h"
 #include "v8/include/v8-array-buffer.h"
+#include "v8/include/v8-inspector.h"
 #include "v8/include/v8-promise.h"
 
 namespace base {
@@ -43,7 +44,9 @@
 
 class JsSandboxIsolate {
  public:
-  explicit JsSandboxIsolate(size_t max_heap_size_bytes = 0);
+  explicit JsSandboxIsolate(
+      const base::android::JavaParamRef<jobject>& j_isolate_,
+      size_t max_heap_size_bytes);
   ~JsSandboxIsolate();
 
   jboolean EvaluateJavascript(
@@ -64,10 +67,18 @@
                             const base::android::JavaParamRef<jstring>& jname,
                             const jint fd,
                             const jint length);
+  // May enable or disable inspection, as needed.
+  void SetConsoleEnabled(JNIEnv* env,
+                         const base::android::JavaParamRef<jobject>& obj,
+                         jboolean enable);
 
  private:
+  class InspectorClient;
+
   void DeleteSelf();
   void InitializeIsolateOnThread();
+  // Will enabled or disable inspection depending on whether any dynamic
+  // features require it (for example, console logging).
   void EvaluateJavascriptOnThread(
       const std::string code,
       scoped_refptr<JsSandboxIsolateCallback> callback);
@@ -121,6 +132,13 @@
   [[noreturn]] void MemoryLimitExceeded();
   [[noreturn]] void FreezeThread();
 
+  void EnableOrDisableInspectorAsNeeded();
+  void SetConsoleEnabledOnControlThread(bool enable);
+  void SetConsoleEnabledOnIsolateThread(bool enable);
+
+  // Java-side JsSandboxIsolate object corresponding to this isolate.
+  const base::android::ScopedJavaGlobalRef<jobject> j_isolate_;
+
   // V8 heap size limit. Must be non-negative.
   //
   // 0 indicates no explicit limit (but use the default V8 limits).
@@ -157,6 +175,15 @@
   //
   // This pointer must only be accessed from the isolate thread.
   JsSandboxIsolateCallback* current_callback_;
+
+  bool console_enabled_;
+
+  // Inspector objects should be destructed before anything they're inspecting,
+  // so they are later in the field list.
+  std::unique_ptr<v8_inspector::V8InspectorClient> inspector_client_;
+  std::unique_ptr<v8_inspector::V8Inspector> inspector_;
+  std::unique_ptr<v8_inspector::V8Inspector::Channel> inspector_channel_;
+  std::unique_ptr<v8_inspector::V8InspectorSession> inspector_session_;
 };
 }  // namespace android_webview
 
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index cb93e552..d018d467 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -607,6 +607,8 @@
     "glanceables/glanceables_welcome_label.cc",
     "glanceables/glanceables_welcome_label.h",
     "glanceables/tasks/glanceables_tasks_client.h",
+    "glanceables/tasks/glanceables_tasks_types.cc",
+    "glanceables/tasks/glanceables_tasks_types.h",
     "host/ash_window_tree_host.cc",
     "host/ash_window_tree_host.h",
     "host/ash_window_tree_host_delegate.h",
diff --git a/ash/constants/ash_features.cc b/ash/constants/ash_features.cc
index bee22e07..cf5badb 100644
--- a/ash/constants/ash_features.cc
+++ b/ash/constants/ash_features.cc
@@ -317,6 +317,11 @@
              "CaptureModeDemoTools",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
+// Enables the tour that walks new users through the Capture Mode feature.
+BASE_FEATURE(kCaptureModeTour,
+             "CaptureModeTour",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 // If enabled, allow eSIM installation bypass the non-cellular internet
 // connectivity check.
 BASE_FEATURE(kCellularBypassESimInstallationConnectivityCheck,
@@ -335,6 +340,13 @@
              "CheckPasswordsAgainstCryptohomeHelper",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
+// When enabled alongside the keyboard auto-repeat setting, holding down Ctrl+V
+// will cause the clipboard history menu to show. From there, the user can
+// select a clipboard history item to replace the initially pasted content.
+BASE_FEATURE(kClipboardHistoryLongpress,
+             "ClipboardHistoryLongpress",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 // If enabled, the clipboard nudge shown prefs will be reset at the start of
 // each new user session.
 BASE_FEATURE(kClipboardHistoryNudgeSessionReset,
@@ -353,13 +365,6 @@
              "ClipboardHistoryReorder",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
-// When enabled alongside the keyboard auto-repeat setting, holding down Ctrl+V
-// will cause the clipboard history menu to show. From there, the user can
-// select a clipboard history item to replace the initially pasted content.
-BASE_FEATURE(kClipboardHistorySelector,
-             "ClipboardHistorySelector",
-             base::FEATURE_DISABLED_BY_DEFAULT);
-
 // If enabled and account falls under the new deal, will be allowed to toggle
 // auto updates.
 BASE_FEATURE(kConsumerAutoUpdateToggleAllowed,
@@ -1148,6 +1153,11 @@
              "HoldingSpaceSuggestions",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
+// Enables the tour that walks new users through the Holding Space feature.
+BASE_FEATURE(kHoldingSpaceTour,
+             "HoldingSpaceTour",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 // Enables a call-to-action label beside the home button.
 BASE_FEATURE(kHomeButtonWithText,
              "HomeButtonWithText",
@@ -1629,7 +1639,7 @@
 // screen.
 BASE_FEATURE(kPolicyProvidedTrustAnchorsAllowedAtLockScreen,
              "PolicyProvidedTrustAnchorsAllowedAtLockScreen",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 
 // Enables or disables the preference of using constant frame rate for camera
 // when streaming.
@@ -1889,6 +1899,11 @@
              "ShimlessRMADisableDarkMode",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
+// Enables or disables the diagnostic page in the Shimless RMA flow.
+BASE_FEATURE(kShimlessRMADiagnosticPage,
+             "ShimlessRMADiagnosticPage",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 // Enables or disables a toggle to enable Bluetooth debug logs.
 BASE_FEATURE(kShowBluetoothDebugLogToggle,
              "ShowBluetoothDebugLogToggle",
@@ -2432,6 +2447,10 @@
   return base::FeatureList::IsEnabled(kCaptivePortalErrorPage);
 }
 
+bool IsCaptureModeTourEnabled() {
+  return base::FeatureList::IsEnabled(kCaptureModeTour);
+}
+
 bool IsCheckPasswordsAgainstCryptohomeHelperEnabled() {
   return base::FeatureList::IsEnabled(kCheckPasswordsAgainstCryptohomeHelper);
 }
@@ -2440,6 +2459,10 @@
   return base::FeatureList::IsEnabled(kChromadAvailable);
 }
 
+bool IsClipboardHistoryLongpressEnabled() {
+  return base::FeatureList::IsEnabled(kClipboardHistoryLongpress);
+}
+
 bool IsClipboardHistoryNudgeSessionResetEnabled() {
   return base::FeatureList::IsEnabled(kClipboardHistoryNudgeSessionReset);
 }
@@ -2452,10 +2475,6 @@
   return base::FeatureList::IsEnabled(kClipboardHistoryReorder);
 }
 
-bool IsClipboardHistorySelectorEnabled() {
-  return base::FeatureList::IsEnabled(kClipboardHistorySelector);
-}
-
 bool IsCryptauthAttestationSyncingEnabled() {
   return base::FeatureList::IsEnabled(kCryptauthAttestationSyncing);
 }
@@ -2710,6 +2729,10 @@
   return base::FeatureList::IsEnabled(kHoldingSpaceSuggestions);
 }
 
+bool IsHoldingSpaceTourEnabled() {
+  return base::FeatureList::IsEnabled(kHoldingSpaceTour);
+}
+
 bool IsHomeButtonWithTextEnabled() {
   return base::FeatureList::IsEnabled(kHomeButtonWithText);
 }
@@ -3175,6 +3198,10 @@
   return base::FeatureList::IsEnabled(kShimlessRMADisableDarkMode);
 }
 
+bool IsShimlessRMADiagnosticPageEnabled() {
+  return base::FeatureList::IsEnabled(kShimlessRMADiagnosticPage);
+}
+
 bool IsSmdsDbusMigrationEnabled() {
   return base::FeatureList::IsEnabled(kSmdsDbusMigration);
 }
@@ -3246,6 +3273,11 @@
   return base::FeatureList::IsEnabled(kUseStorkSmdsServerAddress);
 }
 
+bool IsUserEducationEnabled() {
+  return IsCaptureModeTourEnabled() || IsHoldingSpaceTourEnabled() ||
+         IsWelcomeTourEnabled();
+}
+
 bool IsVideoConferenceEnabled() {
   return base::FeatureList::IsEnabled(kVideoConference) &&
          switches::IsCameraEffectsSupportedByHardware();
diff --git a/ash/constants/ash_features.h b/ash/constants/ash_features.h
index 0311d44..621bc56 100644
--- a/ash/constants/ash_features.h
+++ b/ash/constants/ash_features.h
@@ -93,6 +93,7 @@
 BASE_DECLARE_FEATURE(kCameraPrivacySwitchNotifications);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kCaptivePortalErrorPage);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kCaptureModeDemoTools);
+COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kCaptureModeTour);
 COMPONENT_EXPORT(ASH_CONSTANTS)
 BASE_DECLARE_FEATURE(kCellularBypassESimInstallationConnectivityCheck);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kCellularUseSecondEuicc);
@@ -100,10 +101,11 @@
 BASE_DECLARE_FEATURE(kCheckPasswordsAgainstCryptohomeHelper);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kChromadAvailable);
 COMPONENT_EXPORT(ASH_CONSTANTS)
+BASE_DECLARE_FEATURE(kClipboardHistoryLongpress);
+COMPONENT_EXPORT(ASH_CONSTANTS)
 BASE_DECLARE_FEATURE(kClipboardHistoryNudgeSessionReset);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kClipboardHistoryRefresh);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kClipboardHistoryReorder);
-COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kClipboardHistorySelector);
 COMPONENT_EXPORT(ASH_CONSTANTS)
 BASE_DECLARE_FEATURE(kConsumerAutoUpdateToggleAllowed);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kContextualNudges);
@@ -350,6 +352,7 @@
 BASE_DECLARE_FEATURE(kHoldingSpacePredictability);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kHoldingSpaceRefresh);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kHoldingSpaceSuggestions);
+COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kHoldingSpaceTour);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kHotspot);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kVirtualKeyboardNewHeader);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kImeDownloaderUpdate);
@@ -541,6 +544,8 @@
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kShimlessRMAFlow);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kShimlessRMAOsUpdate);
 COMPONENT_EXPORT(ASH_CONSTANTS)
+BASE_DECLARE_FEATURE(kShimlessRMADiagnosticPage);
+COMPONENT_EXPORT(ASH_CONSTANTS)
 BASE_DECLARE_FEATURE(kShimlessRMADisableDarkMode);
 COMPONENT_EXPORT(ASH_CONSTANTS)
 BASE_DECLARE_FEATURE(kShowBluetoothDebugLogToggle);
@@ -665,14 +670,15 @@
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsBluetoothQualityReportEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsCalendarJellyEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsCaptivePortalErrorPageEnabled();
+COMPONENT_EXPORT(ASH_CONSTANTS) bool IsCaptureModeTourEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS)
 bool IsCheckPasswordsAgainstCryptohomeHelperEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsChromadAvailableEnabled();
+COMPONENT_EXPORT(ASH_CONSTANTS) bool IsClipboardHistoryLongpressEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS)
 bool IsClipboardHistoryNudgeSessionResetEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsClipboardHistoryRefreshEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsClipboardHistoryReorderEnabled();
-COMPONENT_EXPORT(ASH_CONSTANTS) bool IsClipboardHistorySelectorEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsConsumerAutoUpdateToggleAllowed();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsCrosPrivacyHubEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsCrosPrivacyHubV0Enabled();
@@ -745,6 +751,7 @@
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsHoldingSpacePredictabilityEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsHoldingSpaceRefreshEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsHoldingSpaceSuggestionsEnabled();
+COMPONENT_EXPORT(ASH_CONSTANTS) bool IsHoldingSpaceTourEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsHomeButtonWithTextEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsHostnameSettingEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsHotspotEnabled();
@@ -868,6 +875,7 @@
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsShelfPalmRejectionSwipeOffsetEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsShelfStackedHotseatEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsSimLockPolicyEnabled();
+COMPONENT_EXPORT(ASH_CONSTANTS) bool IsShimlessRMADiagnosticPageEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsShimlessRMAFlowEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsShimlessRMAOsUpdateEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsShimlessRMADarkModeDisabled();
@@ -891,6 +899,7 @@
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsUploadOfficeToCloudEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsUseLoginShelfWidgetEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsUseStorkSmdsServerAddressEnabled();
+COMPONENT_EXPORT(ASH_CONSTANTS) bool IsUserEducationEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsVideoConferenceEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsVcBackgroundReplaceEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsVcControlsUiFakeEffectsEnabled();
diff --git a/ash/glanceables/tasks/glanceables_tasks_client.h b/ash/glanceables/tasks/glanceables_tasks_client.h
index 023d59d..7aad735 100644
--- a/ash/glanceables/tasks/glanceables_tasks_client.h
+++ b/ash/glanceables/tasks/glanceables_tasks_client.h
@@ -6,28 +6,34 @@
 #define ASH_GLANCEABLES_TASKS_GLANCEABLES_TASKS_CLIENT_H_
 
 #include <string>
+#include <vector>
 
 #include "ash/ash_export.h"
 #include "base/functional/callback_forward.h"
-#include "google_apis/tasks/tasks_api_requests.h"
 
 namespace ash {
 
+struct GlanceablesTask;
+struct GlanceablesTaskList;
+
 // Interface for the tasks browser client.
 class ASH_EXPORT GlanceablesTasksClient {
  public:
+  using GetTaskListsCallback = base::OnceCallback<void(
+      const std::vector<GlanceablesTaskList>& task_lists)>;
+  using GetTasksCallback =
+      base::OnceCallback<void(const std::vector<GlanceablesTask>& tasks)>;
+
   // Fetches all the authenticated user's task lists and invokes `callback` when
   // done.
   // Returned `base::OnceClosure` can cancel the api call.
-  virtual base::OnceClosure GetTaskLists(
-      google_apis::tasks::ListTaskListsRequest::Callback callback) = 0;
+  virtual base::OnceClosure GetTaskLists(GetTaskListsCallback callback) = 0;
 
   // Fetches all tasks in the specified task list (`task_list_id` must not be
   // empty) and invokes `callback` when done.
   // Returned `base::OnceClosure` can cancel the api call.
-  virtual base::OnceClosure GetTasks(
-      google_apis::tasks::ListTasksRequest::Callback callback,
-      const std::string& task_list_id) = 0;
+  virtual base::OnceClosure GetTasks(GetTasksCallback callback,
+                                     const std::string& task_list_id) = 0;
 
  protected:
   virtual ~GlanceablesTasksClient() = default;
diff --git a/ash/glanceables/tasks/glanceables_tasks_types.cc b/ash/glanceables/tasks/glanceables_tasks_types.cc
new file mode 100644
index 0000000..59b38a5
--- /dev/null
+++ b/ash/glanceables/tasks/glanceables_tasks_types.cc
@@ -0,0 +1,29 @@
+// 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 "ash/glanceables/tasks/glanceables_tasks_types.h"
+
+namespace ash {
+
+GlanceablesTaskList::GlanceablesTaskList(const std::string& id,
+                                         const std::string& title,
+                                         const base::Time& updated)
+    : id(id), title(title), updated(updated) {}
+
+GlanceablesTaskList::~GlanceablesTaskList() = default;
+
+// ----------------------------------------------------------------------------
+// GlanceablesTask:
+
+GlanceablesTask::GlanceablesTask(const std::string& id,
+                                 const std::string& title,
+                                 bool completed,
+                                 const std::vector<GlanceablesTask>& subtasks)
+    : id(id), title(title), completed(completed), subtasks(subtasks) {}
+
+GlanceablesTask::GlanceablesTask(const GlanceablesTask&) = default;
+
+GlanceablesTask::~GlanceablesTask() = default;
+
+}  // namespace ash
diff --git a/ash/glanceables/tasks/glanceables_tasks_types.h b/ash/glanceables/tasks/glanceables_tasks_types.h
new file mode 100644
index 0000000..3a2d1337
--- /dev/null
+++ b/ash/glanceables/tasks/glanceables_tasks_types.h
@@ -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.
+
+#ifndef ASH_GLANCEABLES_TASKS_GLANCEABLES_TASKS_TYPES_H_
+#define ASH_GLANCEABLES_TASKS_GLANCEABLES_TASKS_TYPES_H_
+
+#include <string>
+#include <vector>
+
+#include "ash/ash_export.h"
+#include "base/time/time.h"
+
+namespace ash {
+
+// Lightweight TaskList definition to separate API and ash/ui-friendly types.
+// Contains information that describes a single task list. All values are from
+// the API resource
+// https://developers.google.com/tasks/reference/rest/v1/tasklists.
+struct ASH_EXPORT GlanceablesTaskList {
+  GlanceablesTaskList() = delete;
+  GlanceablesTaskList(const std::string& id,
+                      const std::string& title,
+                      const base::Time& updated);
+  ~GlanceablesTaskList();
+
+  // Task list identifier.
+  const std::string id;
+
+  // Title of the task list.
+  const std::string title;
+
+  // Last modification time of the task list.
+  const base::Time updated;
+};
+
+// Lightweight Task definition to separate API and ash/ui-friendly types.
+// The most significant difference is that API tasks are flat (subtask-task
+// relationship is expressed by parent id property), but here they are
+// represented as a tree structure. All values are from the API resource
+// https://developers.google.com/tasks/reference/rest/v1/tasks.
+struct ASH_EXPORT GlanceablesTask {
+  GlanceablesTask() = delete;
+  GlanceablesTask(const std::string& id,
+                  const std::string& title,
+                  bool completed,
+                  const std::vector<GlanceablesTask>& subtasks);
+  GlanceablesTask(const GlanceablesTask&);
+  ~GlanceablesTask();
+
+  // Task identifier.
+  const std::string id;
+
+  // Title of the task.
+  const std::string title;
+
+  // Indicates whether the task is completed (has "status" field equals to
+  // "completed" on the API side).
+  bool completed;
+
+  // Subtasks of the task (pre-grouped tasks that have "parent" field equals to
+  // `id` on the API side).
+  const std::vector<GlanceablesTask> subtasks;
+};
+
+}  // namespace ash
+
+#endif  // ASH_GLANCEABLES_TASKS_GLANCEABLES_TASKS_TYPES_H_
diff --git a/ash/quick_pair/fast_pair_handshake/fast_pair_encryption_unittest.cc b/ash/quick_pair/fast_pair_handshake/fast_pair_encryption_unittest.cc
index 5981dd58..500feea5 100644
--- a/ash/quick_pair/fast_pair_handshake/fast_pair_encryption_unittest.cc
+++ b/ash/quick_pair/fast_pair_handshake/fast_pair_encryption_unittest.cc
@@ -122,21 +122,29 @@
   EXPECT_NE(GenerateHmacSha256(secret_key, nonce, input), expected);
 }
 
-TEST_F(FastPairEncryptionTest, GenerateHmacSha256_EmptyData) {
-  const std::vector<uint8_t> input = {};
+TEST_F(FastPairEncryptionTest, GenerateHmacSha256_EmptyParamCombos_NoCrash) {
+  const std::vector<uint8_t> input = {0xEE, 0x4A, 0x24, 0x83, 0x73, 0x80, 0x52,
+                                      0xE4, 0x4E, 0x9B, 0x2A, 0x14, 0x5E, 0x5D,
+                                      0xDF, 0xAA, 0x44, 0xB9, 0xE5, 0x53, 0x6A,
+                                      0xF4, 0x38, 0xE1, 0xE5, 0xC6};
 
-  const std::array<uint8_t, kNonceSizeBytes> nonce = {};
+  const std::array<uint8_t, kNonceSizeBytes> nonce = {0x00, 0x01, 0x02, 0x03,
+                                                      0x04, 0x05, 0x06, 0x07};
 
   const std::array<uint8_t, kSecretKeySizeBytes> secret_key = {
       0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF,
       0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF};
-
-  const std::array<uint8_t, kHmacSizeBytes> expected = {
-      0xBB, 0x07, 0xA3, 0xDE, 0x98, 0xC4, 0x97, 0x9C, 0x5F, 0x52, 0x19,
-      0x39, 0x9F, 0xCB, 0xDA, 0xEB, 0xA3, 0x7E, 0xD7, 0xFA, 0x84, 0xAA,
-      0x28, 0x2A, 0x76, 0xCF, 0xF3, 0xB6, 0x30, 0x36, 0x7E, 0x10};
-
-  EXPECT_EQ(GenerateHmacSha256(secret_key, nonce, input), expected);
+  std::vector<uint8_t> empty_input;
+  std::array<uint8_t, kNonceSizeBytes> empty_nonce;
+  std::array<uint8_t, kSecretKeySizeBytes> empty_secret_key;
+  for (size_t i = 0; i < 2; i++) {
+    for (size_t j = 0; j < 2; j++) {
+      for (size_t k = 0; k < 2; k++) {
+        GenerateHmacSha256(i ? secret_key : empty_secret_key,
+                           j ? nonce : empty_nonce, k ? input : empty_input);
+      }
+    }
+  }
 }
 
 TEST_F(FastPairEncryptionTest, GenerateHmacSha256_OneByteData) {
@@ -217,6 +225,29 @@
   EXPECT_EQ(EncryptAdditionalData(secret_key, nonce, input), expected);
 }
 
+TEST_F(FastPairEncryptionTest, EncryptAdditionalData_EmptyParamCombos_NoCrash) {
+  std::vector<uint8_t> input = {0x53, 0x6F, 0x6D, 0x65, 0x6F, 0x6E, 0x65,
+                                0x27, 0x73, 0x20, 0x47, 0x6F, 0x6F, 0x67,
+                                0x6C, 0x65, 0x20, 0x48, 0x65, 0x61, 0x64,
+                                0x70, 0x68, 0x6F, 0x6E, 0x65};
+  std::array<uint8_t, kSecretKeySizeBytes> secret_key = {
+      0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF,
+      0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF};
+  std::array<uint8_t, kNonceSizeBytes> nonce = {0x00, 0x01, 0x02, 0x03,
+                                                0x04, 0x05, 0x06, 0x07};
+  std::vector<uint8_t> empty_input;
+  std::array<uint8_t, kNonceSizeBytes> empty_nonce;
+  std::array<uint8_t, kSecretKeySizeBytes> empty_secret_key;
+  for (size_t i = 0; i < 2; i++) {
+    for (size_t j = 0; j < 2; j++) {
+      for (size_t k = 0; k < 2; k++) {
+        EncryptAdditionalData(i ? secret_key : empty_secret_key,
+                              j ? nonce : empty_nonce, k ? input : empty_input);
+      }
+    }
+  }
+}
+
 TEST_F(FastPairEncryptionTest, EncryptAdditionalData_OneByteData) {
   const std::vector<uint8_t> input = {0x00};
 
diff --git a/ash/shelf/drag_handle.cc b/ash/shelf/drag_handle.cc
index 46282a9..b8433fe 100644
--- a/ash/shelf/drag_handle.cc
+++ b/ash/shelf/drag_handle.cc
@@ -167,7 +167,8 @@
     hide_drag_handle_nudge_timer_.Start(
         FROM_HERE, nudge_duration,
         base::BindOnce(&DragHandle::HideDragHandleNudge, base::Unretained(this),
-                       contextual_tooltip::DismissNudgeReason::kTimeout));
+                       contextual_tooltip::DismissNudgeReason::kTimeout,
+                       /*animate=*/true));
   }
   contextual_tooltip::HandleNudgeShown(
       pref, contextual_tooltip::TooltipType::kInAppToHome);
@@ -192,7 +193,8 @@
 }
 
 void DragHandle::HideDragHandleNudge(
-    contextual_tooltip::DismissNudgeReason reason) {
+    contextual_tooltip::DismissNudgeReason reason,
+    bool animate) {
   StopDragHandleNudgeShowTimer();
   if (!gesture_nudge_target_visibility())
     return;
@@ -206,8 +208,9 @@
         contextual_tooltip::TooltipType::kInAppToHome);
   }
 
-  HideDragHandleNudgeHelper(/*hidden_by_tap=*/reason ==
-                            contextual_tooltip::DismissNudgeReason::kTap);
+  HideDragHandleNudgeHelper(
+      /*hidden_by_tap=*/reason == contextual_tooltip::DismissNudgeReason::kTap,
+      animate);
   gesture_nudge_target_visibility_ = false;
 }
 
@@ -230,7 +233,8 @@
     hide_drag_handle_nudge_timer_.Stop();
   } else {
     HideDragHandleNudge(
-        contextual_tooltip::DismissNudgeReason::kPerformedGesture);
+        contextual_tooltip::DismissNudgeReason::kPerformedGesture,
+        /*animate=*/true);
   }
 }
 
@@ -336,7 +340,8 @@
     SplitViewController::State state) {
   if (SplitViewController::Get(shelf_->shelf_widget()->GetNativeWindow())
           ->InSplitViewMode()) {
-    HideDragHandleNudge(contextual_tooltip::DismissNudgeReason::kOther);
+    HideDragHandleNudge(contextual_tooltip::DismissNudgeReason::kOther,
+                        /*animate=*/true);
   }
 }
 
@@ -431,7 +436,15 @@
   }
 }
 
-void DragHandle::HideDragHandleNudgeHelper(bool hidden_by_tap) {
+void DragHandle::HideDragHandleNudgeHelper(bool hidden_by_tap, bool animate) {
+  if (!animate) {
+    ScheduleDragHandleTranslationAnimation(
+        0, base::TimeDelta(), gfx::Tween::ZERO,
+        ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
+    drag_handle_nudge_->GetWidget()->CloseWithReason(
+        views::Widget::ClosedReason::kUnspecified);
+    return;
+  }
   ScheduleDragHandleTranslationAnimation(
       0,
       hidden_by_tap ? kInAppToHomeHideOnTapAnimationDuration
@@ -495,7 +508,8 @@
 void DragHandle::HandleTapOnNudge() {
   if (!drag_handle_nudge_)
     return;
-  HideDragHandleNudge(contextual_tooltip::DismissNudgeReason::kTap);
+  HideDragHandleNudge(contextual_tooltip::DismissNudgeReason::kTap,
+                      /*animate=*/true);
 }
 
 void DragHandle::StopDragHandleNudgeShowTimer() {
diff --git a/ash/shelf/drag_handle.h b/ash/shelf/drag_handle.h
index fd97c01..37c43bd 100644
--- a/ash/shelf/drag_handle.h
+++ b/ash/shelf/drag_handle.h
@@ -60,7 +60,8 @@
 
   // Immediately begins the animation to return the drag handle back to its
   // original position and hide the tooltip.
-  void HideDragHandleNudge(contextual_tooltip::DismissNudgeReason reason);
+  void HideDragHandleNudge(contextual_tooltip::DismissNudgeReason reason,
+                           bool animate);
 
   // Called when the window drag from shelf starts or ends. The drag handle
   // contextual nudge will remain visible while the gesture is in progress.
@@ -130,7 +131,7 @@
 
   // Helper function to hide the drag handle nudge. Called by
   // |hide_drag_handle_nudge_timer_|.
-  void HideDragHandleNudgeHelper(bool hidden_by_tap);
+  void HideDragHandleNudgeHelper(bool hidden_by_tap, bool animate);
 
   // Helper function to animate the drag handle for the drag handle gesture
   // contextual nudge.
diff --git a/ash/shelf/shelf_widget.cc b/ash/shelf/shelf_widget.cc
index faa1377..967a225 100644
--- a/ash/shelf/shelf_widget.cc
+++ b/ash/shelf/shelf_widget.cc
@@ -739,7 +739,7 @@
 
 void ShelfWidget::HideDragHandleNudge(
     contextual_tooltip::DismissNudgeReason context) {
-  delegate_view_->drag_handle()->HideDragHandleNudge(context);
+  delegate_view_->drag_handle()->HideDragHandleNudge(context, /*animate=*/true);
 }
 
 void ShelfWidget::SetLoginShelfButtonOpacity(float target_opacity) {
diff --git a/ash/shell.cc b/ash/shell.cc
index 5c95da6..cf3f858 100644
--- a/ash/shell.cc
+++ b/ash/shell.cc
@@ -1625,7 +1625,7 @@
         std::make_unique<federated::FederatedServiceControllerImpl>();
   }
 
-  if (features::IsWelcomeTourEnabled()) {
+  if (features::IsUserEducationEnabled()) {
     user_education_controller_ = std::make_unique<UserEducationController>(
         shell_delegate_->CreateUserEducationDelegate());
   }
diff --git a/ash/system/accessibility/accessibility_detailed_view.cc b/ash/system/accessibility/accessibility_detailed_view.cc
index a696ead..9702716 100644
--- a/ash/system/accessibility/accessibility_detailed_view.cc
+++ b/ash/system/accessibility/accessibility_detailed_view.cc
@@ -20,6 +20,7 @@
 #include "ash/strings/grit/ash_strings.h"
 #include "ash/style/ash_color_id.h"
 #include "ash/style/rounded_container.h"
+#include "ash/style/switch.h"
 #include "ash/system/machine_learning/user_settings_event_logger.h"
 #include "ash/system/model/system_tray_model.h"
 #include "ash/system/tray/hover_highlight_view.h"
@@ -104,9 +105,15 @@
   if (!item)
     return;
   views::View* right_view = item->right_view();
-  // The right view is either an enterprise icon or a tray toggle button.
-  if (views::IsViewClass<TrayToggleButton>(right_view)) {
-    TrayToggleButton* button = static_cast<TrayToggleButton*>(right_view);
+  // The right view is either an enterprise icon or a `TrayToggleButton`.
+  // For QsRevamp: the right view is either an enterprise icon or a `Switch`.
+  if (!features::IsQsRevampEnabled()) {
+    if (views::IsViewClass<TrayToggleButton>(right_view)) {
+      TrayToggleButton* button = static_cast<TrayToggleButton*>(right_view);
+      button->AnimateIsOn(toggled);
+    }
+  } else if (views::IsViewClass<Switch>(right_view)) {
+    Switch* button = static_cast<Switch*>(right_view);
     button->AnimateIsOn(toggled);
   }
   // The entire row is treated as one element for accessibility.
@@ -656,10 +663,15 @@
                        enterprise_managed_icon.Size().width());
   } else {
     // Create a non-clickable non-focusable toggle button on the right.
-    auto toggle = std::make_unique<TrayToggleButton>(
-        views::Button::PressedCallback(),
-        /*accessible_name_id=*/absl::nullopt,
-        /*use_empty_border=*/features::IsQsRevampEnabled());
+    std::unique_ptr<views::ToggleButton> toggle;
+    if (!features::IsQsRevampEnabled()) {
+      toggle = std::make_unique<TrayToggleButton>(
+          views::Button::PressedCallback(),
+          /*accessible_name_id=*/absl::nullopt,
+          /*use_empty_border=*/features::IsQsRevampEnabled());
+    } else {
+      toggle = std::make_unique<Switch>();
+    }
     toggle->SetIsOn(checked);
     toggle->SetCanProcessEventsWithinSubtree(false);
     toggle->SetFocusBehavior(views::View::FocusBehavior::NEVER);
diff --git a/ash/system/accessibility/accessibility_detailed_view_unittest.cc b/ash/system/accessibility/accessibility_detailed_view_unittest.cc
index 7f8d848..4a06a7b 100644
--- a/ash/system/accessibility/accessibility_detailed_view_unittest.cc
+++ b/ash/system/accessibility/accessibility_detailed_view_unittest.cc
@@ -14,9 +14,9 @@
 #include "ash/session/session_controller_impl.h"
 #include "ash/shell.h"
 #include "ash/style/rounded_container.h"
+#include "ash/style/switch.h"
 #include "ash/system/tray/fake_detailed_view_delegate.h"
 #include "ash/system/tray/hover_highlight_view.h"
-#include "ash/system/tray/tray_toggle_button.h"
 #include "ash/test/ash_test_base.h"
 #include "base/test/scoped_feature_list.h"
 #include "components/live_caption/pref_names.h"
@@ -126,14 +126,13 @@
   return node_data.GetCheckedState() == ax::mojom::CheckedState::kTrue;
 }
 
-// Returns true if `item` has a toggle button on the right and the button is
-// toggled.
-bool IsToggleButtonToggled(HoverHighlightView* item) {
+// Returns true if `item` has a `Switch` on the right and the button is toggled.
+bool IsSwitchToggled(HoverHighlightView* item) {
   views::View* right_view = item->right_view();
-  if (!views::IsViewClass<TrayToggleButton>(right_view)) {
+  if (!views::IsViewClass<Switch>(right_view)) {
     return false;
   }
-  return static_cast<TrayToggleButton*>(right_view)->GetIsOn();
+  return static_cast<Switch*>(right_view)->GetIsOn();
 }
 
 }  // namespace
@@ -671,11 +670,11 @@
   EnableSpokenFeedback(true);
   CreateDetailedMenu();
   ASSERT_TRUE(spoken_feedback_top_view());
-  EXPECT_TRUE(IsToggleButtonToggled(spoken_feedback_top_view()));
+  EXPECT_TRUE(IsSwitchToggled(spoken_feedback_top_view()));
   EXPECT_TRUE(IsCheckedForAccessibility(spoken_feedback_top_view()));
 
   ClickView(spoken_feedback_top_view());
-  EXPECT_FALSE(IsToggleButtonToggled(spoken_feedback_top_view()));
+  EXPECT_FALSE(IsSwitchToggled(spoken_feedback_top_view()));
   EXPECT_FALSE(IsCheckedForAccessibility(spoken_feedback_top_view()));
   EXPECT_FALSE(controller()->spoken_feedback().enabled());
 }
@@ -684,11 +683,11 @@
   EnableSelectToSpeak(true);
   CreateDetailedMenu();
   ASSERT_TRUE(select_to_speak_top_view());
-  EXPECT_TRUE(IsToggleButtonToggled(select_to_speak_top_view()));
+  EXPECT_TRUE(IsSwitchToggled(select_to_speak_top_view()));
   EXPECT_TRUE(IsCheckedForAccessibility(select_to_speak_top_view()));
 
   ClickView(select_to_speak_top_view());
-  EXPECT_FALSE(IsToggleButtonToggled(select_to_speak_top_view()));
+  EXPECT_FALSE(IsSwitchToggled(select_to_speak_top_view()));
   EXPECT_FALSE(IsCheckedForAccessibility(select_to_speak_top_view()));
   EXPECT_FALSE(controller()->select_to_speak().enabled());
 }
@@ -697,11 +696,11 @@
   EnableDictation(true);
   CreateDetailedMenu();
   ASSERT_TRUE(dictation_top_view());
-  EXPECT_TRUE(IsToggleButtonToggled(dictation_top_view()));
+  EXPECT_TRUE(IsSwitchToggled(dictation_top_view()));
   EXPECT_TRUE(IsCheckedForAccessibility(dictation_top_view()));
 
   ClickView(dictation_top_view());
-  EXPECT_FALSE(IsToggleButtonToggled(dictation_top_view()));
+  EXPECT_FALSE(IsSwitchToggled(dictation_top_view()));
   EXPECT_FALSE(IsCheckedForAccessibility(dictation_top_view()));
   EXPECT_FALSE(controller()->dictation().enabled());
 }
@@ -710,11 +709,11 @@
   EnableHighContrast(true);
   CreateDetailedMenu();
   ASSERT_TRUE(high_contrast_top_view());
-  EXPECT_TRUE(IsToggleButtonToggled(high_contrast_top_view()));
+  EXPECT_TRUE(IsSwitchToggled(high_contrast_top_view()));
   EXPECT_TRUE(IsCheckedForAccessibility(high_contrast_top_view()));
 
   ClickView(high_contrast_top_view());
-  EXPECT_FALSE(IsToggleButtonToggled(high_contrast_top_view()));
+  EXPECT_FALSE(IsSwitchToggled(high_contrast_top_view()));
   EXPECT_FALSE(IsCheckedForAccessibility(high_contrast_top_view()));
   EXPECT_FALSE(controller()->high_contrast().enabled());
 }
@@ -723,7 +722,7 @@
   Shell::Get()->accessibility_delegate()->SetMagnifierEnabled(true);
   CreateDetailedMenu();
   ASSERT_TRUE(screen_magnifier_top_view());
-  EXPECT_TRUE(IsToggleButtonToggled(screen_magnifier_top_view()));
+  EXPECT_TRUE(IsSwitchToggled(screen_magnifier_top_view()));
   EXPECT_TRUE(IsCheckedForAccessibility(screen_magnifier_top_view()));
 
   ClickView(screen_magnifier_top_view());
@@ -731,7 +730,7 @@
   // it manually.
   controller()->NotifyAccessibilityStatusChanged();
 
-  EXPECT_FALSE(IsToggleButtonToggled(screen_magnifier_top_view()));
+  EXPECT_FALSE(IsSwitchToggled(screen_magnifier_top_view()));
   EXPECT_FALSE(IsCheckedForAccessibility(screen_magnifier_top_view()));
   EXPECT_FALSE(Shell::Get()->accessibility_delegate()->IsMagnifierEnabled());
 }
@@ -740,11 +739,11 @@
   SetDockedMagnifierEnabled(true);
   CreateDetailedMenu();
   ASSERT_TRUE(docked_magnifier_top_view());
-  EXPECT_TRUE(IsToggleButtonToggled(docked_magnifier_top_view()));
+  EXPECT_TRUE(IsSwitchToggled(docked_magnifier_top_view()));
   EXPECT_TRUE(IsCheckedForAccessibility(docked_magnifier_top_view()));
 
   ClickView(docked_magnifier_top_view());
-  EXPECT_FALSE(IsToggleButtonToggled(docked_magnifier_top_view()));
+  EXPECT_FALSE(IsSwitchToggled(docked_magnifier_top_view()));
   EXPECT_FALSE(IsCheckedForAccessibility(docked_magnifier_top_view()));
   EXPECT_FALSE(Shell::Get()->docked_magnifier_controller()->GetEnabled());
 }
@@ -753,11 +752,11 @@
   EnableLargeCursor(true);
   CreateDetailedMenu();
   ASSERT_TRUE(large_cursor_top_view());
-  EXPECT_TRUE(IsToggleButtonToggled(large_cursor_top_view()));
+  EXPECT_TRUE(IsSwitchToggled(large_cursor_top_view()));
   EXPECT_TRUE(IsCheckedForAccessibility(large_cursor_top_view()));
 
   ClickView(large_cursor_top_view());
-  EXPECT_FALSE(IsToggleButtonToggled(large_cursor_top_view()));
+  EXPECT_FALSE(IsSwitchToggled(large_cursor_top_view()));
   EXPECT_FALSE(IsCheckedForAccessibility(large_cursor_top_view()));
   EXPECT_FALSE(controller()->large_cursor().enabled());
 }
@@ -766,11 +765,11 @@
   EnableLiveCaption(true);
   CreateDetailedMenu();
   ASSERT_TRUE(live_caption_top_view());
-  EXPECT_TRUE(IsToggleButtonToggled(live_caption_top_view()));
+  EXPECT_TRUE(IsSwitchToggled(live_caption_top_view()));
   EXPECT_TRUE(IsCheckedForAccessibility(live_caption_top_view()));
 
   ClickView(live_caption_top_view());
-  EXPECT_FALSE(IsToggleButtonToggled(live_caption_top_view()));
+  EXPECT_FALSE(IsSwitchToggled(live_caption_top_view()));
   EXPECT_FALSE(IsCheckedForAccessibility(live_caption_top_view()));
   EXPECT_FALSE(controller()->live_caption().enabled());
 }
@@ -779,11 +778,11 @@
   EnableAutoclick(true);
   CreateDetailedMenu();
   ASSERT_TRUE(autoclick_top_view());
-  EXPECT_TRUE(IsToggleButtonToggled(autoclick_top_view()));
+  EXPECT_TRUE(IsSwitchToggled(autoclick_top_view()));
   EXPECT_TRUE(IsCheckedForAccessibility(autoclick_top_view()));
 
   ClickView(autoclick_top_view());
-  EXPECT_FALSE(IsToggleButtonToggled(autoclick_top_view()));
+  EXPECT_FALSE(IsSwitchToggled(autoclick_top_view()));
   EXPECT_FALSE(IsCheckedForAccessibility(autoclick_top_view()));
   EXPECT_FALSE(controller()->autoclick().enabled());
 }
@@ -792,11 +791,11 @@
   EnableVirtualKeyboard(true);
   CreateDetailedMenu();
   ASSERT_TRUE(virtual_keyboard_top_view());
-  EXPECT_TRUE(IsToggleButtonToggled(virtual_keyboard_top_view()));
+  EXPECT_TRUE(IsSwitchToggled(virtual_keyboard_top_view()));
   EXPECT_TRUE(IsCheckedForAccessibility(virtual_keyboard_top_view()));
 
   ClickView(virtual_keyboard_top_view());
-  EXPECT_FALSE(IsToggleButtonToggled(virtual_keyboard_top_view()));
+  EXPECT_FALSE(IsSwitchToggled(virtual_keyboard_top_view()));
   EXPECT_FALSE(IsCheckedForAccessibility(virtual_keyboard_top_view()));
   EXPECT_FALSE(controller()->virtual_keyboard().enabled());
 }
@@ -805,11 +804,11 @@
   EnableMonoAudio(true);
   CreateDetailedMenu();
   ASSERT_TRUE(mono_audio_top_view());
-  EXPECT_TRUE(IsToggleButtonToggled(mono_audio_top_view()));
+  EXPECT_TRUE(IsSwitchToggled(mono_audio_top_view()));
   EXPECT_TRUE(IsCheckedForAccessibility(mono_audio_top_view()));
 
   ClickView(mono_audio_top_view());
-  EXPECT_FALSE(IsToggleButtonToggled(mono_audio_top_view()));
+  EXPECT_FALSE(IsSwitchToggled(mono_audio_top_view()));
   EXPECT_FALSE(IsCheckedForAccessibility(mono_audio_top_view()));
   EXPECT_FALSE(controller()->mono_audio().enabled());
 }
@@ -818,11 +817,11 @@
   SetCaretHighlightEnabled(true);
   CreateDetailedMenu();
   ASSERT_TRUE(caret_highlight_top_view());
-  EXPECT_TRUE(IsToggleButtonToggled(caret_highlight_top_view()));
+  EXPECT_TRUE(IsSwitchToggled(caret_highlight_top_view()));
   EXPECT_TRUE(IsCheckedForAccessibility(caret_highlight_top_view()));
 
   ClickView(caret_highlight_top_view());
-  EXPECT_FALSE(IsToggleButtonToggled(caret_highlight_top_view()));
+  EXPECT_FALSE(IsSwitchToggled(caret_highlight_top_view()));
   EXPECT_FALSE(IsCheckedForAccessibility(caret_highlight_top_view()));
   EXPECT_FALSE(controller()->caret_highlight().enabled());
 }
@@ -831,11 +830,11 @@
   SetCursorHighlightEnabled(true);
   CreateDetailedMenu();
   ASSERT_TRUE(highlight_mouse_cursor_top_view());
-  EXPECT_TRUE(IsToggleButtonToggled(highlight_mouse_cursor_top_view()));
+  EXPECT_TRUE(IsSwitchToggled(highlight_mouse_cursor_top_view()));
   EXPECT_TRUE(IsCheckedForAccessibility(highlight_mouse_cursor_top_view()));
 
   ClickView(highlight_mouse_cursor_top_view());
-  EXPECT_FALSE(IsToggleButtonToggled(highlight_mouse_cursor_top_view()));
+  EXPECT_FALSE(IsSwitchToggled(highlight_mouse_cursor_top_view()));
   EXPECT_FALSE(IsCheckedForAccessibility(highlight_mouse_cursor_top_view()));
   EXPECT_FALSE(controller()->cursor_highlight().enabled());
 }
@@ -844,11 +843,11 @@
   SetFocusHighlightEnabled(true);
   CreateDetailedMenu();
   ASSERT_TRUE(highlight_keyboard_focus_top_view());
-  EXPECT_TRUE(IsToggleButtonToggled(highlight_keyboard_focus_top_view()));
+  EXPECT_TRUE(IsSwitchToggled(highlight_keyboard_focus_top_view()));
   EXPECT_TRUE(IsCheckedForAccessibility(highlight_keyboard_focus_top_view()));
 
   ClickView(highlight_keyboard_focus_top_view());
-  EXPECT_FALSE(IsToggleButtonToggled(highlight_keyboard_focus_top_view()));
+  EXPECT_FALSE(IsSwitchToggled(highlight_keyboard_focus_top_view()));
   EXPECT_FALSE(IsCheckedForAccessibility(highlight_keyboard_focus_top_view()));
   EXPECT_FALSE(controller()->focus_highlight().enabled());
 }
@@ -857,11 +856,11 @@
   EnableStickyKeys(true);
   CreateDetailedMenu();
   ASSERT_TRUE(sticky_keys_top_view());
-  EXPECT_TRUE(IsToggleButtonToggled(sticky_keys_top_view()));
+  EXPECT_TRUE(IsSwitchToggled(sticky_keys_top_view()));
   EXPECT_TRUE(IsCheckedForAccessibility(sticky_keys_top_view()));
 
   ClickView(sticky_keys_top_view());
-  EXPECT_FALSE(IsToggleButtonToggled(sticky_keys_top_view()));
+  EXPECT_FALSE(IsSwitchToggled(sticky_keys_top_view()));
   EXPECT_FALSE(IsCheckedForAccessibility(sticky_keys_top_view()));
   EXPECT_FALSE(controller()->sticky_keys().enabled());
 }
@@ -874,11 +873,11 @@
   EnableSwitchAccess(true);
   CreateDetailedMenu();
   ASSERT_TRUE(switch_access_top_view());
-  EXPECT_TRUE(IsToggleButtonToggled(switch_access_top_view()));
+  EXPECT_TRUE(IsSwitchToggled(switch_access_top_view()));
   EXPECT_TRUE(IsCheckedForAccessibility(switch_access_top_view()));
 
   ClickView(switch_access_top_view());
-  EXPECT_FALSE(IsToggleButtonToggled(switch_access_top_view()));
+  EXPECT_FALSE(IsSwitchToggled(switch_access_top_view()));
   EXPECT_FALSE(IsCheckedForAccessibility(switch_access_top_view()));
   EXPECT_FALSE(controller()->switch_access().enabled());
 }
diff --git a/ash/system/audio/audio_detailed_view.cc b/ash/system/audio/audio_detailed_view.cc
index 157ff8c5..1c5ef1a 100644
--- a/ash/system/audio/audio_detailed_view.cc
+++ b/ash/system/audio/audio_detailed_view.cc
@@ -340,11 +340,10 @@
       l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_LIVE_CAPTION));
 
   // Creates a toggle button on the right.
-  auto toggle = std::make_unique<TrayToggleButton>(
-      base::BindRepeating(&AudioDetailedView::ToggleLiveCaptionState,
-                          weak_factory_.GetWeakPtr()),
-      IDS_ASH_STATUS_TRAY_LIVE_CAPTION,
-      /*use_empty_border=*/true);
+  auto toggle = std::make_unique<Switch>(base::BindRepeating(
+      &AudioDetailedView::ToggleLiveCaptionState, weak_factory_.GetWeakPtr()));
+  SetAccessibleName(
+      l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_LIVE_CAPTION));
   toggle->SetIsOn(live_caption_enabled);
   std::u16string toggle_tooltip =
       live_caption_enabled
@@ -435,10 +434,7 @@
 
   // Create a non-clickable non-focusable toggle button on the right. The events
   // and focus behavior should be handled by `noise_cancellation_view_` instead.
-  auto toggle =
-      std::make_unique<TrayToggleButton>(views::Button::PressedCallback(),
-                                         /*accessible_name_id=*/absl::nullopt,
-                                         /*use_empty_border=*/true);
+  auto toggle = std::make_unique<Switch>();
   toggle->SetIsOn(noise_cancellation_state);
   toggle->SetCanProcessEventsWithinSubtree(false);
   toggle->SetFocusBehavior(views::View::FocusBehavior::NEVER);
diff --git a/ash/system/audio/audio_detailed_view.h b/ash/system/audio/audio_detailed_view.h
index 49f2f5e..05ec61a 100644
--- a/ash/system/audio/audio_detailed_view.h
+++ b/ash/system/audio/audio_detailed_view.h
@@ -11,12 +11,12 @@
 
 #include "ash/accessibility/accessibility_observer.h"
 #include "ash/ash_export.h"
+#include "ash/style/switch.h"
 #include "ash/system/tray/hover_highlight_view.h"
 #include "ash/system/tray/tray_detailed_view.h"
 #include "chromeos/ash/components/audio/audio_device.h"
 #include "components/soda/soda_installer.h"
 #include "ui/views/controls/button/button.h"
-#include "ui/views/controls/button/toggle_button.h"
 #include "ui/views/view.h"
 
 namespace gfx {
@@ -134,10 +134,10 @@
   // Owned by the views hierarchy.
   HoverHighlightView* live_caption_view_ = nullptr;
   views::ImageView* live_caption_icon_ = nullptr;
-  views::ToggleButton* live_caption_button_ = nullptr;
+  Switch* live_caption_button_ = nullptr;
   HoverHighlightView* noise_cancellation_view_ = nullptr;
   views::ImageView* noise_cancellation_icon_ = nullptr;
-  views::ToggleButton* noise_cancellation_button_ = nullptr;
+  Switch* noise_cancellation_button_ = nullptr;
   views::Button* settings_button_ = nullptr;
 
   base::WeakPtrFactory<AudioDetailedView> weak_factory_{this};
diff --git a/ash/system/audio/unified_audio_detailed_view_controller_unittest.cc b/ash/system/audio/unified_audio_detailed_view_controller_unittest.cc
index 3022546..37a6e8d 100644
--- a/ash/system/audio/unified_audio_detailed_view_controller_unittest.cc
+++ b/ash/system/audio/unified_audio_detailed_view_controller_unittest.cc
@@ -8,6 +8,7 @@
 #include "ash/constants/ash_features.h"
 #include "ash/session/session_controller_impl.h"
 #include "ash/shell.h"
+#include "ash/style/switch.h"
 #include "ash/system/audio/audio_detailed_view.h"
 #include "ash/system/audio/mic_gain_slider_controller.h"
 #include "ash/system/audio/mic_gain_slider_view.h"
@@ -440,14 +441,7 @@
   // view first.
   GetAudioDetailedView();
   EXPECT_EQ(1u, toggles_map_.size());
-
-  if (!IsQsRevampEnabled()) {
-    views::ToggleButton* toggle =
-        (views::ToggleButton*)toggles_map_[internal_mic.id]->children()[1];
-    EXPECT_TRUE(toggle->GetIsOn());
-  } else {
-    EXPECT_TRUE(noise_cancellation_button());
-  }
+  noise_cancellation_button()->GetIsOn();
 }
 
 TEST_P(UnifiedAudioDetailedViewControllerTest,
@@ -468,15 +462,15 @@
   GetAudioDetailedView();
   EXPECT_EQ(1u, toggles_map_.size());
 
-  if (!IsQsRevampEnabled()) {
-    views::ToggleButton* toggle =
-        (views::ToggleButton*)toggles_map_[internal_mic.id]->children()[1];
-    auto widget = CreateFramelessTestWidget();
-    widget->SetContentsView(toggle);
+  views::ToggleButton* toggle = noise_cancellation_button();
+  auto widget = CreateFramelessTestWidget();
+  widget->SetContentsView(toggle);
 
-    // The toggle loaded the pref correctly.
-    EXPECT_FALSE(toggle->GetIsOn());
-    EXPECT_FALSE(audio_pref_handler_->GetNoiseCancellationState());
+  // The toggle loaded the pref correctly.
+  EXPECT_FALSE(toggle->GetIsOn());
+  EXPECT_FALSE(audio_pref_handler_->GetNoiseCancellationState());
+
+  if (!IsQsRevampEnabled()) {
     ui::MouseEvent press(ui::ET_MOUSE_PRESSED, gfx::PointF(), gfx::PointF(),
                          ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON,
                          ui::EF_NONE);
@@ -490,13 +484,6 @@
     views::test::ButtonTestApi(toggle).NotifyClick(press);
     EXPECT_FALSE(audio_pref_handler_->GetNoiseCancellationState());
   } else {
-    auto widget = CreateFramelessTestWidget();
-    widget->SetContentsView(noise_cancellation_button());
-
-    // The toggle loaded the pref correctly.
-    EXPECT_FALSE(noise_cancellation_button()->GetIsOn());
-    EXPECT_FALSE(audio_pref_handler_->GetNoiseCancellationState());
-
     // For QsRevamp, the entire row of `noise_cancellation_view_` is clickable.
     ToggleNoiseCancellation();
     EXPECT_TRUE(audio_pref_handler_->GetNoiseCancellationState());
diff --git a/ash/system/bluetooth/bluetooth_detailed_view_impl.cc b/ash/system/bluetooth/bluetooth_detailed_view_impl.cc
index 79c8c191..fe93164 100644
--- a/ash/system/bluetooth/bluetooth_detailed_view_impl.cc
+++ b/ash/system/bluetooth/bluetooth_detailed_view_impl.cc
@@ -96,8 +96,9 @@
       IDS_ASH_STATUS_TRAY_BLUETOOTH_TOGGLE_TOOLTIP, toggle_tooltip));
 
   // Ensure the toggle button is in sync with the current Bluetooth state.
-  if (toggle_button_->GetIsOn() != enabled)
+  if (toggle_button_->GetIsOn() != enabled) {
     toggle_button_->SetIsOn(enabled);
+  }
 
   InvalidateLayout();
 }
@@ -183,11 +184,9 @@
   toggle_row_->AddViewAndLabel(std::move(icon), u"");
   toggle_row_->text_label()->SetEnabledColorId(cros_tokens::kCrosSysOnSurface);
 
-  auto toggle = std::make_unique<TrayToggleButton>(
-      base::BindRepeating(&BluetoothDetailedViewImpl::OnToggleClicked,
-                          weak_factory_.GetWeakPtr()),
-      IDS_ASH_STATUS_TRAY_BLUETOOTH,
-      /*use_empty_border=*/true);
+  auto toggle = std::make_unique<Switch>(base::BindRepeating(
+      &BluetoothDetailedViewImpl::OnToggleClicked, weak_factory_.GetWeakPtr()));
+  SetAccessibleName(l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_BLUETOOTH));
   toggle_button_ = toggle.get();
   toggle_row_->AddRightView(toggle.release());
 
diff --git a/ash/system/bluetooth/bluetooth_detailed_view_impl.h b/ash/system/bluetooth/bluetooth_detailed_view_impl.h
index be087ea..4916b620 100644
--- a/ash/system/bluetooth/bluetooth_detailed_view_impl.h
+++ b/ash/system/bluetooth/bluetooth_detailed_view_impl.h
@@ -6,6 +6,7 @@
 #define ASH_SYSTEM_BLUETOOTH_BLUETOOTH_DETAILED_VIEW_IMPL_H_
 
 #include "ash/ash_export.h"
+#include "ash/style/switch.h"
 #include "ash/system/bluetooth/bluetooth_detailed_view.h"
 #include "ash/system/tray/tray_detailed_view.h"
 #include "base/memory/weak_ptr.h"
@@ -14,7 +15,6 @@
 namespace views {
 class Button;
 class ImageView;
-class ToggleButton;
 }  // namespace views
 
 namespace ash {
@@ -73,7 +73,7 @@
   RoundedContainer* top_container_ = nullptr;
   HoverHighlightView* toggle_row_ = nullptr;
   views::ImageView* toggle_icon_ = nullptr;
-  views::ToggleButton* toggle_button_ = nullptr;
+  Switch* toggle_button_ = nullptr;
   RoundedContainer* main_container_ = nullptr;
   HoverHighlightView* pair_new_device_view_ = nullptr;
   views::ImageView* pair_new_device_icon_ = nullptr;
diff --git a/ash/system/bluetooth/bluetooth_detailed_view_impl_unittest.cc b/ash/system/bluetooth/bluetooth_detailed_view_impl_unittest.cc
index c25fc11..f5ee01a 100644
--- a/ash/system/bluetooth/bluetooth_detailed_view_impl_unittest.cc
+++ b/ash/system/bluetooth/bluetooth_detailed_view_impl_unittest.cc
@@ -8,12 +8,12 @@
 
 #include "ash/public/cpp/test/test_system_tray_client.h"
 #include "ash/style/rounded_container.h"
+#include "ash/style/switch.h"
 #include "ash/system/bluetooth/bluetooth_device_list_item_view.h"
 #include "ash/system/tray/detailed_view_delegate.h"
 #include "ash/system/tray/hover_highlight_view.h"
 #include "ash/test/ash_test_base.h"
 #include "mojo/public/cpp/bindings/clone_traits.h"
-#include "ui/views/controls/button/toggle_button.h"
 #include "ui/views/controls/label.h"
 #include "ui/views/test/views_test_utils.h"
 #include "ui/views/widget/widget.h"
@@ -98,9 +98,7 @@
     return bluetooth_detailed_view_->toggle_row_;
   }
 
-  views::ToggleButton* GetToggleButton() {
-    return bluetooth_detailed_view_->toggle_button_;
-  }
+  Switch* GetToggleButton() { return bluetooth_detailed_view_->toggle_button_; }
 
   RoundedContainer* GetMainContainer() {
     return bluetooth_detailed_view_->main_container_;
@@ -137,7 +135,7 @@
 TEST_F(BluetoothDetailedViewImplTest,
        UpdateBluetoothEnabledStateChangesUIState) {
   HoverHighlightView* toggle_row = GetToggleRow();
-  views::ToggleButton* toggle_button = GetToggleButton();
+  Switch* toggle_button = GetToggleButton();
   RoundedContainer* main_container = GetMainContainer();
 
   bluetooth_detailed_view_->UpdateBluetoothEnabledState(true);
@@ -167,7 +165,7 @@
 }
 
 TEST_F(BluetoothDetailedViewImplTest, PressingToggleButtonNotifiesDelegate) {
-  views::ToggleButton* toggle_button = GetToggleButton();
+  Switch* toggle_button = GetToggleButton();
   EXPECT_FALSE(toggle_button->GetIsOn());
   EXPECT_FALSE(bluetooth_detailed_view_delegate_.last_toggle_state_);
 
diff --git a/ash/system/holding_space/holding_space_item_chip_view.cc b/ash/system/holding_space/holding_space_item_chip_view.cc
index 5b9d0f0..41c08b98 100644
--- a/ash/system/holding_space/holding_space_item_chip_view.cc
+++ b/ash/system/holding_space/holding_space_item_chip_view.cc
@@ -330,7 +330,18 @@
                               .SetID(kHoldingSpaceItemSecondaryChipLabelId)
                               .SetStyle(TypographyToken::kCrosLabel1)
                               .SetElideBehavior(gfx::FADE_TAIL)
-                              .SetCallback(paint_label_mask_callback)))
+                              .SetCallback(paint_label_mask_callback))
+                      .AfterBuild(base::BindOnce(
+                          [](HoldingSpaceItemChipView* self,
+                             views::BoxLayoutView* box_layout_view) {
+                            // Synchronize line heights between primary and
+                            // secondary labels so that text will be vertically
+                            // centered when both are shown despite differences
+                            // in font sizes.
+                            self->secondary_label_->SetLineHeight(
+                                self->primary_label_->GetLineHeight());
+                          },
+                          base::Unretained(this))))
               .AddChild(views::Builder<views::BoxLayoutView>()
                             .SetOrientation(Orientation::kHorizontal)
                             .SetMainAxisAlignment(MainAxisAlignment::kEnd)
diff --git a/ash/system/ime_menu/ime_list_view.cc b/ash/system/ime_menu/ime_list_view.cc
index b999639..e620ed7 100644
--- a/ash/system/ime_menu/ime_list_view.cc
+++ b/ash/system/ime_menu/ime_list_view.cc
@@ -19,6 +19,7 @@
 #include "ash/style/ash_color_id.h"
 #include "ash/style/ash_color_provider.h"
 #include "ash/style/rounded_container.h"
+#include "ash/style/switch.h"
 #include "ash/system/tray/actionable_view.h"
 #include "ash/system/tray/system_menu_button.h"
 #include "ash/system/tray/tray_detailed_view.h"
@@ -165,6 +166,7 @@
   ~KeyboardStatusRow() override = default;
 
   views::ToggleButton* toggle() const { return toggle_; }
+  Switch* qs_toggle() const { return qs_toggle_; }
 
   void Init(views::Button::PressedCallback callback) {
     const bool is_qs_revamp = features::IsQsRevampEnabled();
@@ -196,16 +198,29 @@
     tri_view->AddView(TriView::Container::CENTER, label);
 
     // The on-screen keyboard toggle button.
-    toggle_ = new TrayToggleButton(
-        std::move(callback), IDS_ASH_STATUS_TRAY_ACCESSIBILITY_VIRTUAL_KEYBOARD,
-        /*use_empty_border=*/is_qs_revamp);
-    toggle_->SetIsOn(keyboard::IsKeyboardEnabled());
-    tri_view->AddView(TriView::Container::END, toggle_);
+    if (!is_qs_revamp) {
+      toggle_ = new TrayToggleButton(
+          std::move(callback),
+          IDS_ASH_STATUS_TRAY_ACCESSIBILITY_VIRTUAL_KEYBOARD,
+          /*use_empty_border=*/is_qs_revamp);
+      toggle_->SetIsOn(keyboard::IsKeyboardEnabled());
+      tri_view->AddView(TriView::Container::END, toggle_);
+    } else {
+      auto qs_toggle = std::make_unique<Switch>(std::move(callback));
+      qs_toggle->SetAccessibleName(l10n_util::GetStringUTF16(
+          IDS_ASH_STATUS_TRAY_ACCESSIBILITY_VIRTUAL_KEYBOARD));
+      qs_toggle->SetIsOn(keyboard::IsKeyboardEnabled());
+      qs_toggle_ = qs_toggle.release();
+      tri_view->AddView(TriView::Container::END, qs_toggle_);
+    }
   }
 
  private:
-  // ToggleButton to toggle keyboard on or off.
+  // `ToggleButton` to toggle keyboard on or off.
   views::ToggleButton* toggle_ = nullptr;
+
+  // For QsRevamp: `KnobSwitch` to toggle keyboard on or off.
+  Switch* qs_toggle_ = nullptr;
 };
 
 BEGIN_METADATA(KeyboardStatusRow, views::View)
@@ -405,6 +420,9 @@
 views::View* ImeListViewTestApi::GetToggleView() const {
   if (!ime_list_view_->keyboard_status_row_)
     return nullptr;
+  if (features::IsQsRevampEnabled()) {
+    return ime_list_view_->keyboard_status_row_->qs_toggle();
+  }
   return ime_list_view_->keyboard_status_row_->toggle();
 }
 
diff --git a/ash/system/input_device_settings/pref_handlers/mouse_pref_handler_impl.cc b/ash/system/input_device_settings/pref_handlers/mouse_pref_handler_impl.cc
index 4510584..3c40d9ed 100644
--- a/ash/system/input_device_settings/pref_handlers/mouse_pref_handler_impl.cc
+++ b/ash/system/input_device_settings/pref_handlers/mouse_pref_handler_impl.cc
@@ -17,6 +17,18 @@
 namespace ash {
 namespace {
 
+// Whether or not settings taken during the transition period should be
+// persisted to the prefs. Values should only ever be true if the original
+// setting was a user-configured value.
+struct ForceMouseSettingPersistence {
+  bool swap_right = false;
+  bool sensitivity = false;
+  bool reverse_scrolling = false;
+  bool acceleration_enabled = false;
+  bool scroll_acceleration = false;
+  bool scroll_sensitivity = false;
+};
+
 mojom::MouseSettingsPtr GetDefaultMouseSettings() {
   mojom::MouseSettingsPtr settings = mojom::MouseSettings::New();
   settings->swap_right = kDefaultSwapRight;
@@ -30,94 +42,61 @@
 
 // GetMouseSettingsFromPrefs returns a mouse settings based on user prefs
 // to be used as settings for new mouses.
-mojom::MouseSettingsPtr GetMouseSettingsFromPrefs(PrefService* prefs) {
+mojom::MouseSettingsPtr GetMouseSettingsFromPrefs(
+    PrefService* prefs,
+    ForceMouseSettingPersistence& force_persistence) {
   mojom::MouseSettingsPtr settings = mojom::MouseSettings::New();
-  settings->swap_right = prefs->GetBoolean(prefs::kPrimaryMouseButtonRight);
-  settings->sensitivity = prefs->GetInteger(prefs::kMouseSensitivity);
-  settings->reverse_scrolling = prefs->GetBoolean(prefs::kMouseReverseScroll);
-  settings->acceleration_enabled = prefs->GetBoolean(prefs::kMouseAcceleration);
-  settings->scroll_sensitivity =
-      prefs->GetInteger(prefs::kMouseScrollSensitivity);
+
+  const auto* swap_right_preference =
+      prefs->GetUserPrefValue(prefs::kPrimaryMouseButtonRight);
+  settings->swap_right = swap_right_preference
+                             ? swap_right_preference->GetBool()
+                             : kDefaultSwapRight;
+  force_persistence.swap_right = swap_right_preference != nullptr;
+
+  const auto* sensitivity_preference =
+      prefs->GetUserPrefValue(prefs::kMouseSensitivity);
+  settings->sensitivity = sensitivity_preference
+                              ? sensitivity_preference->GetInt()
+                              : kDefaultSensitivity;
+  force_persistence.sensitivity = sensitivity_preference != nullptr;
+
+  const auto* reverse_scrolling_preference =
+      prefs->GetUserPrefValue(prefs::kMouseReverseScroll);
+  settings->reverse_scrolling = reverse_scrolling_preference
+                                    ? reverse_scrolling_preference->GetBool()
+                                    : kDefaultReverseScrolling;
+  force_persistence.reverse_scrolling = reverse_scrolling_preference != nullptr;
+
+  const auto* acceleration_enabled_preference =
+      prefs->GetUserPrefValue(prefs::kMouseAcceleration);
+  settings->acceleration_enabled =
+      acceleration_enabled_preference
+          ? acceleration_enabled_preference->GetBool()
+          : kDefaultAccelerationEnabled;
+  force_persistence.acceleration_enabled =
+      acceleration_enabled_preference != nullptr;
+
+  const auto* scroll_acceleration_preference =
+      prefs->GetUserPrefValue(prefs::kMouseScrollAcceleration);
   settings->scroll_acceleration =
-      prefs->GetBoolean(prefs::kMouseScrollAcceleration);
+      scroll_acceleration_preference ? scroll_acceleration_preference->GetBool()
+                                     : kDefaultSensitivity;
+  force_persistence.scroll_acceleration =
+      scroll_acceleration_preference != nullptr;
+
+  const auto* scroll_sensitivity_preference =
+      prefs->GetUserPrefValue(prefs::kMouseScrollSensitivity);
+  settings->scroll_sensitivity = scroll_sensitivity_preference
+                                     ? scroll_sensitivity_preference->GetInt()
+                                     : kDefaultScrollAcceleration;
+  force_persistence.scroll_sensitivity =
+      scroll_sensitivity_preference != nullptr;
+
   return settings;
 }
 
-}  // namespace
-
-MousePrefHandlerImpl::MousePrefHandlerImpl() = default;
-MousePrefHandlerImpl::~MousePrefHandlerImpl() = default;
-
-void MousePrefHandlerImpl::InitializeMouseSettings(PrefService* pref_service,
-                                                   mojom::Mouse* mouse) {
-  if (!pref_service) {
-    mouse->settings = GetDefaultMouseSettings();
-    return;
-  }
-
-  const auto& devices_dict =
-      pref_service->GetDict(prefs::kMouseDeviceSettingsDictPref);
-  const auto* settings_dict = devices_dict.FindDict(mouse->device_key);
-  if (!settings_dict) {
-    mouse->settings = GetNewMouseSettings(pref_service, *mouse);
-  } else {
-    mouse->settings =
-        RetrieveMouseSettings(pref_service, *mouse, *settings_dict);
-  }
-  DCHECK(mouse->settings);
-
-  UpdateMouseSettings(pref_service, *mouse);
-}
-
-void MousePrefHandlerImpl::UpdateMouseSettings(PrefService* pref_service,
-                                               const mojom::Mouse& mouse) {
-  DCHECK(mouse.settings);
-  const mojom::MouseSettings& settings = *mouse.settings;
-  // Populate `settings_dict` with all settings in `settings`.
-  base::Value::Dict settings_dict;
-  settings_dict.Set(prefs::kMouseSettingSwapRight, settings.swap_right);
-  settings_dict.Set(prefs::kMouseSettingSensitivity, settings.sensitivity);
-  settings_dict.Set(prefs::kMouseSettingReverseScrolling,
-                    settings.reverse_scrolling);
-  settings_dict.Set(prefs::kMouseSettingAccelerationEnabled,
-                    settings.acceleration_enabled);
-  settings_dict.Set(prefs::kMouseSettingScrollSensitivity,
-                    settings.scroll_sensitivity);
-  settings_dict.Set(prefs::kMouseSettingScrollAcceleration,
-                    settings.scroll_acceleration);
-
-  // Retrieve old settings and merge with the new ones.
-  base::Value::Dict devices_dict =
-      pref_service->GetDict(prefs::kMouseDeviceSettingsDictPref).Clone();
-
-  // If an old settings dict already exists for the device, merge the updated
-  // settings into the old settings. Otherwise, insert the dict at
-  // `mouse.device_key`.
-  base::Value::Dict* old_settings_dict =
-      devices_dict.FindDict(mouse.device_key);
-  if (old_settings_dict) {
-    old_settings_dict->Merge(std::move(settings_dict));
-  } else {
-    devices_dict.Set(mouse.device_key, std::move(settings_dict));
-  }
-
-  pref_service->SetDict(std::string(prefs::kMouseDeviceSettingsDictPref),
-                        std::move(devices_dict));
-}
-
-mojom::MouseSettingsPtr MousePrefHandlerImpl::GetNewMouseSettings(
-    PrefService* prefs,
-    const mojom::Mouse& mouse) {
-  // TODO(michaelcheco): Remove once transitioned to per-device settings.
-  if (Shell::Get()->input_device_tracker()->WasDevicePreviouslyConnected(
-          InputDeviceTracker::InputDeviceCategory::kMouse, mouse.device_key)) {
-    return GetMouseSettingsFromPrefs(prefs);
-  }
-
-  return GetDefaultMouseSettings();
-}
-
-mojom::MouseSettingsPtr MousePrefHandlerImpl::RetrieveMouseSettings(
+mojom::MouseSettingsPtr RetrieveMouseSettings(
     PrefService* pref_service,
     const mojom::Mouse& mouse,
     const base::Value::Dict& settings_dict) {
@@ -141,4 +120,132 @@
   return settings;
 }
 
+bool ExistingSettingsHasValue(base::StringPiece setting_key,
+                              const base::Value::Dict* existing_settings_dict) {
+  if (!existing_settings_dict) {
+    return false;
+  }
+
+  return existing_settings_dict->Find(setting_key) != nullptr;
+}
+
+void UpdateMouseSettingsImpl(
+    PrefService* pref_service,
+    const mojom::Mouse& mouse,
+    const ForceMouseSettingPersistence& force_persistence) {
+  DCHECK(mouse.settings);
+  base::Value::Dict devices_dict =
+      pref_service->GetDict(prefs::kMouseDeviceSettingsDictPref).Clone();
+  base::Value::Dict* existing_settings_dict =
+      devices_dict.FindDict(mouse.device_key);
+  const mojom::MouseSettings& settings = *mouse.settings;
+
+  // Populate `settings_dict` with all settings in `settings`.
+  base::Value::Dict settings_dict;
+
+  // Settings should only be persisted if one or more of the following is true:
+  // - Setting was previously persisted to storage
+  // - `force_persistence` requires the setting to be persisted, this means this
+  //   device is being transitioned from the old global settings to per-device
+  //   settings and the user specified the specific value for this setting.
+  // - Setting is different than the default, which means the user manually
+  //   changed the value.
+
+  if (ExistingSettingsHasValue(prefs::kMouseSettingSwapRight,
+                               existing_settings_dict) ||
+      force_persistence.swap_right ||
+      settings.swap_right != kDefaultSwapRight) {
+    settings_dict.Set(prefs::kMouseSettingSwapRight, settings.swap_right);
+  }
+
+  if (ExistingSettingsHasValue(prefs::kMouseSettingSensitivity,
+                               existing_settings_dict) ||
+      force_persistence.sensitivity ||
+      settings.sensitivity != kDefaultSensitivity) {
+    settings_dict.Set(prefs::kMouseSettingSensitivity, settings.sensitivity);
+  }
+
+  if (ExistingSettingsHasValue(prefs::kMouseSettingReverseScrolling,
+                               existing_settings_dict) ||
+      force_persistence.reverse_scrolling ||
+      settings.reverse_scrolling != kDefaultReverseScrolling) {
+    settings_dict.Set(prefs::kMouseSettingReverseScrolling,
+                      settings.reverse_scrolling);
+  }
+
+  if (ExistingSettingsHasValue(prefs::kMouseSettingAccelerationEnabled,
+                               existing_settings_dict) ||
+      force_persistence.acceleration_enabled ||
+      settings.acceleration_enabled != kDefaultAccelerationEnabled) {
+    settings_dict.Set(prefs::kMouseSettingAccelerationEnabled,
+                      settings.acceleration_enabled);
+  }
+
+  if (ExistingSettingsHasValue(prefs::kMouseSettingScrollSensitivity,
+                               existing_settings_dict) ||
+      force_persistence.scroll_sensitivity ||
+      settings.scroll_sensitivity != kDefaultSensitivity) {
+    settings_dict.Set(prefs::kMouseSettingScrollSensitivity,
+                      settings.scroll_sensitivity);
+  }
+
+  if (ExistingSettingsHasValue(prefs::kMouseSettingScrollAcceleration,
+                               existing_settings_dict) ||
+      force_persistence.scroll_acceleration ||
+      settings.scroll_acceleration != kDefaultScrollAcceleration) {
+    settings_dict.Set(prefs::kMouseSettingScrollAcceleration,
+                      settings.scroll_acceleration);
+  }
+
+  // If an old settings dict already exists for the device, merge the updated
+  // settings into the old settings. Otherwise, insert the dict at
+  // `mouse.device_key`.
+  if (existing_settings_dict) {
+    existing_settings_dict->Merge(std::move(settings_dict));
+  } else {
+    devices_dict.Set(mouse.device_key, std::move(settings_dict));
+  }
+
+  pref_service->SetDict(std::string(prefs::kMouseDeviceSettingsDictPref),
+                        std::move(devices_dict));
+}
+
+}  // namespace
+
+MousePrefHandlerImpl::MousePrefHandlerImpl() = default;
+MousePrefHandlerImpl::~MousePrefHandlerImpl() = default;
+
+void MousePrefHandlerImpl::InitializeMouseSettings(PrefService* pref_service,
+                                                   mojom::Mouse* mouse) {
+  if (!pref_service) {
+    mouse->settings = GetDefaultMouseSettings();
+    return;
+  }
+
+  const auto& devices_dict =
+      pref_service->GetDict(prefs::kMouseDeviceSettingsDictPref);
+  const auto* settings_dict = devices_dict.FindDict(mouse->device_key);
+  ForceMouseSettingPersistence force_persistence;
+
+  if (settings_dict) {
+    mouse->settings =
+        RetrieveMouseSettings(pref_service, *mouse, *settings_dict);
+  } else if (Shell::Get()->input_device_tracker()->WasDevicePreviouslyConnected(
+                 InputDeviceTracker::InputDeviceCategory::kMouse,
+                 mouse->device_key)) {
+    mouse->settings =
+        GetMouseSettingsFromPrefs(pref_service, force_persistence);
+  } else {
+    mouse->settings = GetDefaultMouseSettings();
+  }
+  DCHECK(mouse->settings);
+
+  UpdateMouseSettingsImpl(pref_service, *mouse, force_persistence);
+}
+
+void MousePrefHandlerImpl::UpdateMouseSettings(PrefService* pref_service,
+                                               const mojom::Mouse& mouse) {
+  UpdateMouseSettingsImpl(pref_service, mouse, /*force_persistence=*/{});
+}
+
 }  // namespace ash
diff --git a/ash/system/input_device_settings/pref_handlers/mouse_pref_handler_impl.h b/ash/system/input_device_settings/pref_handlers/mouse_pref_handler_impl.h
index 68d8a4a..bc6dca5 100644
--- a/ash/system/input_device_settings/pref_handlers/mouse_pref_handler_impl.h
+++ b/ash/system/input_device_settings/pref_handlers/mouse_pref_handler_impl.h
@@ -25,14 +25,6 @@
                                mojom::Mouse* mouse) override;
   void UpdateMouseSettings(PrefService* pref_service,
                            const mojom::Mouse& mouse) override;
-
- private:
-  mojom::MouseSettingsPtr GetNewMouseSettings(PrefService* prefs,
-                                              const mojom::Mouse& Mouse);
-  mojom::MouseSettingsPtr RetrieveMouseSettings(
-      PrefService* prefs,
-      const mojom::Mouse& mouse,
-      const base::Value::Dict& settings_dict);
 };
 
 }  // namespace ash
diff --git a/ash/system/input_device_settings/pref_handlers/mouse_pref_handler_unittest.cc b/ash/system/input_device_settings/pref_handlers/mouse_pref_handler_unittest.cc
index c3c6c16..30dff821 100644
--- a/ash/system/input_device_settings/pref_handlers/mouse_pref_handler_unittest.cc
+++ b/ash/system/input_device_settings/pref_handlers/mouse_pref_handler_unittest.cc
@@ -38,6 +38,14 @@
     /*scroll_sensitivity=*/kDefaultSensitivity,
     /*scroll_acceleration=*/kDefaultScrollAcceleration);
 
+const mojom::MouseSettings kMouseSettingsNotDefault(
+    /*swap_right=*/!kDefaultSwapRight,
+    /*sensitivity=*/1,
+    /*reverse_scrolling=*/!kDefaultReverseScrolling,
+    /*acceleration_enabled=*/!kDefaultAccelerationEnabled,
+    /*scroll_sensitivity=*/1,
+    /*scroll_acceleration=*/!kDefaultScrollAcceleration);
+
 const mojom::MouseSettings kMouseSettings1(
     /*swap_right=*/false,
     /*sensitivity=*/1,
@@ -82,17 +90,30 @@
     // We are using these test constants as a a way to differentiate values
     // retrieved from prefs or default mouse settings.
     pref_service_->registry()->RegisterBooleanPref(
-        prefs::kPrimaryMouseButtonRight, kTestSwapRight);
+        prefs::kPrimaryMouseButtonRight, kDefaultSwapRight);
     pref_service_->registry()->RegisterIntegerPref(prefs::kMouseSensitivity,
-                                                   kTestSensitivity);
+                                                   kDefaultSensitivity);
     pref_service_->registry()->RegisterBooleanPref(prefs::kMouseReverseScroll,
-                                                   kTestReverseScrolling);
+                                                   kDefaultReverseScrolling);
     pref_service_->registry()->RegisterBooleanPref(prefs::kMouseAcceleration,
-                                                   kTestAccelerationEnabled);
+                                                   kDefaultAccelerationEnabled);
     pref_service_->registry()->RegisterIntegerPref(
-        prefs::kMouseScrollSensitivity, kTestScrollSensitivity);
+        prefs::kMouseScrollSensitivity, kDefaultSensitivity);
     pref_service_->registry()->RegisterBooleanPref(
-        prefs::kMouseScrollAcceleration, kTestScrollAcceleration);
+        prefs::kMouseScrollAcceleration, kDefaultScrollAcceleration);
+
+    pref_service_->SetUserPref(prefs::kPrimaryMouseButtonRight,
+                               base::Value(kTestSwapRight));
+    pref_service_->SetUserPref(prefs::kMouseSensitivity,
+                               base::Value(kTestSensitivity));
+    pref_service_->SetUserPref(prefs::kMouseReverseScroll,
+                               base::Value(kTestReverseScrolling));
+    pref_service_->SetUserPref(prefs::kMouseAcceleration,
+                               base::Value(kTestAccelerationEnabled));
+    pref_service_->SetUserPref(prefs::kMouseScrollSensitivity,
+                               base::Value(kTestScrollSensitivity));
+    pref_service_->SetUserPref(prefs::kMouseScrollAcceleration,
+                               base::Value(kTestScrollAcceleration));
   }
 
   void CheckMouseSettingsAndDictAreEqual(
@@ -100,33 +121,51 @@
       const base::Value::Dict& settings_dict) {
     const auto swap_right =
         settings_dict.FindBool(prefs::kMouseSettingSwapRight);
-    ASSERT_TRUE(swap_right.has_value());
-    EXPECT_EQ(settings.swap_right, swap_right);
+    if (swap_right.has_value()) {
+      EXPECT_EQ(settings.swap_right, swap_right);
+    } else {
+      EXPECT_EQ(settings.swap_right, kDefaultSwapRight);
+    }
 
     const auto sensitivity =
         settings_dict.FindInt(prefs::kMouseSettingSensitivity);
-    ASSERT_TRUE(sensitivity.has_value());
-    EXPECT_EQ(settings.sensitivity, sensitivity);
+    if (sensitivity.has_value()) {
+      EXPECT_EQ(settings.sensitivity, sensitivity);
+    } else {
+      EXPECT_EQ(settings.sensitivity, kDefaultSensitivity);
+    }
 
     const auto reverse_scrolling =
         settings_dict.FindBool(prefs::kMouseSettingReverseScrolling);
-    ASSERT_TRUE(reverse_scrolling.has_value());
-    EXPECT_EQ(settings.reverse_scrolling, reverse_scrolling);
+    if (reverse_scrolling.has_value()) {
+      EXPECT_EQ(settings.reverse_scrolling, reverse_scrolling);
+    } else {
+      EXPECT_EQ(settings.reverse_scrolling, kDefaultReverseScrolling);
+    }
 
     const auto acceleration_enabled =
         settings_dict.FindBool(prefs::kMouseSettingAccelerationEnabled);
-    ASSERT_TRUE(acceleration_enabled.has_value());
-    EXPECT_EQ(settings.acceleration_enabled, acceleration_enabled);
+    if (acceleration_enabled.has_value()) {
+      EXPECT_EQ(settings.acceleration_enabled, acceleration_enabled);
+    } else {
+      EXPECT_EQ(settings.acceleration_enabled, kDefaultAccelerationEnabled);
+    }
 
     const auto scroll_sensitivity =
         settings_dict.FindInt(prefs::kMouseSettingScrollSensitivity);
-    ASSERT_TRUE(scroll_sensitivity.has_value());
-    EXPECT_EQ(settings.scroll_sensitivity, scroll_sensitivity);
+    if (scroll_sensitivity.has_value()) {
+      EXPECT_EQ(settings.scroll_sensitivity, scroll_sensitivity);
+    } else {
+      EXPECT_EQ(settings.scroll_sensitivity, kDefaultSensitivity);
+    }
 
     const auto scroll_acceleration =
         settings_dict.FindBool(prefs::kMouseSettingScrollAcceleration);
-    ASSERT_TRUE(scroll_acceleration.has_value());
-    EXPECT_EQ(settings.scroll_acceleration, scroll_acceleration);
+    if (scroll_acceleration.has_value()) {
+      EXPECT_EQ(settings.scroll_acceleration, scroll_acceleration);
+    } else {
+      EXPECT_EQ(settings.scroll_acceleration, kDefaultScrollAcceleration);
+    }
   }
 
   void CallUpdateMouseSettings(const std::string& device_key,
@@ -147,6 +186,16 @@
     return std::move(mouse->settings);
   }
 
+  const base::Value::Dict* GetSettingsDict(const std::string& device_key) {
+    const auto& devices_dict =
+        pref_service_->GetDict(prefs::kMouseDeviceSettingsDictPref);
+    EXPECT_EQ(1u, devices_dict.size());
+    const auto* settings_dict = devices_dict.FindDict(device_key);
+    EXPECT_NE(nullptr, settings_dict);
+
+    return settings_dict;
+  }
+
  protected:
   std::unique_ptr<MousePrefHandlerImpl> pref_handler_;
   std::unique_ptr<TestingPrefServiceSimple> pref_service_;
@@ -294,6 +343,71 @@
   ASSERT_EQ(settings->scroll_acceleration, kTestScrollAcceleration);
 }
 
+TEST_F(MousePrefHandlerTest, TransitionPeriodSettingsPersistedWhenUserChosen) {
+  mojom::Mouse mouse;
+  mouse.device_key = kMouseKey1;
+  Shell::Get()->input_device_tracker()->OnMouseConnected(mouse);
+
+  pref_service_->SetUserPref(prefs::kPrimaryMouseButtonRight,
+                             base::Value(kDefaultSwapRight));
+  pref_service_->SetUserPref(prefs::kMouseSensitivity,
+                             base::Value(kDefaultSensitivity));
+  pref_service_->SetUserPref(prefs::kMouseReverseScroll,
+                             base::Value(kDefaultReverseScrolling));
+  pref_service_->SetUserPref(prefs::kMouseAcceleration,
+                             base::Value(kDefaultAccelerationEnabled));
+  pref_service_->SetUserPref(prefs::kMouseScrollSensitivity,
+                             base::Value(kDefaultSensitivity));
+  pref_service_->SetUserPref(prefs::kMouseScrollAcceleration,
+                             base::Value(kDefaultScrollAcceleration));
+  mojom::MouseSettingsPtr settings =
+      CallInitializeMouseSettings(mouse.device_key);
+  EXPECT_EQ(kMouseSettingsDefault, *settings);
+
+  const auto* settings_dict = GetSettingsDict(kMouseKey1);
+  EXPECT_TRUE(settings_dict->contains(prefs::kMouseSettingSwapRight));
+  EXPECT_TRUE(settings_dict->contains(prefs::kMouseSettingSensitivity));
+  EXPECT_TRUE(settings_dict->contains(prefs::kMouseSettingReverseScrolling));
+  EXPECT_TRUE(settings_dict->contains(prefs::kMouseSettingAccelerationEnabled));
+  EXPECT_TRUE(settings_dict->contains(prefs::kMouseSettingScrollSensitivity));
+  EXPECT_TRUE(settings_dict->contains(prefs::kMouseSettingScrollAcceleration));
+  CheckMouseSettingsAndDictAreEqual(kMouseSettingsDefault, *settings_dict);
+}
+
+TEST_F(MousePrefHandlerTest, DefaultNotPersistedUntilUpdated) {
+  CallUpdateMouseSettings(kMouseKey1, kMouseSettingsDefault);
+
+  const auto* settings_dict = GetSettingsDict(kMouseKey1);
+  EXPECT_FALSE(settings_dict->contains(prefs::kMouseSettingSwapRight));
+  EXPECT_FALSE(settings_dict->contains(prefs::kMouseSettingSensitivity));
+  EXPECT_FALSE(settings_dict->contains(prefs::kMouseSettingReverseScrolling));
+  EXPECT_FALSE(
+      settings_dict->contains(prefs::kMouseSettingAccelerationEnabled));
+  EXPECT_FALSE(settings_dict->contains(prefs::kMouseSettingScrollSensitivity));
+  EXPECT_FALSE(settings_dict->contains(prefs::kMouseSettingScrollAcceleration));
+  CheckMouseSettingsAndDictAreEqual(kMouseSettingsDefault, *settings_dict);
+
+  CallUpdateMouseSettings(kMouseKey1, kMouseSettingsNotDefault);
+  settings_dict = GetSettingsDict(kMouseKey1);
+  EXPECT_TRUE(settings_dict->contains(prefs::kMouseSettingSwapRight));
+  EXPECT_TRUE(settings_dict->contains(prefs::kMouseSettingSensitivity));
+  EXPECT_TRUE(settings_dict->contains(prefs::kMouseSettingReverseScrolling));
+  EXPECT_TRUE(settings_dict->contains(prefs::kMouseSettingAccelerationEnabled));
+  EXPECT_TRUE(settings_dict->contains(prefs::kMouseSettingScrollSensitivity));
+  EXPECT_TRUE(settings_dict->contains(prefs::kMouseSettingScrollAcceleration));
+  CheckMouseSettingsAndDictAreEqual(kMouseSettingsNotDefault, *settings_dict);
+
+  CallUpdateMouseSettings(kMouseKey1, kMouseSettingsDefault);
+  settings_dict = GetSettingsDict(kMouseKey1);
+  EXPECT_TRUE(settings_dict->contains(prefs::kMouseSettingSwapRight));
+  EXPECT_TRUE(settings_dict->contains(prefs::kMouseSettingSensitivity));
+  EXPECT_TRUE(settings_dict->contains(prefs::kMouseSettingReverseScrolling));
+  EXPECT_TRUE(settings_dict->contains(prefs::kMouseSettingAccelerationEnabled));
+  EXPECT_TRUE(settings_dict->contains(prefs::kMouseSettingScrollSensitivity));
+  EXPECT_TRUE(settings_dict->contains(prefs::kMouseSettingScrollAcceleration));
+  CheckMouseSettingsAndDictAreEqual(kMouseSettingsDefault, *settings_dict);
+}
+
 class MouseSettingsPrefConversionTest
     : public MousePrefHandlerTest,
       public testing::WithParamInterface<
@@ -327,12 +441,7 @@
 TEST_P(MouseSettingsPrefConversionTest, CheckConversion) {
   CallUpdateMouseSettings(device_key_, settings_);
 
-  const auto& devices_dict =
-      pref_service_->GetDict(prefs::kMouseDeviceSettingsDictPref);
-  ASSERT_EQ(1u, devices_dict.size());
-  auto* settings_dict = devices_dict.FindDict(device_key_);
-  ASSERT_NE(nullptr, settings_dict);
-
+  const auto* settings_dict = GetSettingsDict(device_key_);
   CheckMouseSettingsAndDictAreEqual(settings_, *settings_dict);
 }
 
diff --git a/ash/system/input_device_settings/pref_handlers/touchpad_pref_handler_impl.cc b/ash/system/input_device_settings/pref_handlers/touchpad_pref_handler_impl.cc
index 9c0a015..d48e9e38 100644
--- a/ash/system/input_device_settings/pref_handlers/touchpad_pref_handler_impl.cc
+++ b/ash/system/input_device_settings/pref_handlers/touchpad_pref_handler_impl.cc
@@ -16,6 +16,22 @@
 
 namespace ash {
 namespace {
+
+// Whether or not settings taken during the transition period should be
+// persisted to the prefs. Values should only ever be true if the original
+// setting was a user-configured value.
+struct ForceTouchpadSettingPersistence {
+  bool sensitivity = false;
+  bool reverse_scrolling = false;
+  bool acceleration_enabled = false;
+  bool tap_to_click_enabled = false;
+  bool tap_dragging_enabled = false;
+  bool scroll_sensitivity = false;
+  bool scroll_acceleration = false;
+  bool haptic_sensitivity = false;
+  bool haptic_enabled = false;
+};
+
 mojom::TouchpadSettingsPtr GetDefaultTouchpadSettings() {
   mojom::TouchpadSettingsPtr settings = mojom::TouchpadSettings::New();
   settings->sensitivity = kDefaultSensitivity;
@@ -33,114 +49,96 @@
 
 // GetTouchpadSettingsFromPrefs returns a touchpad settings based on user prefs
 // to be used as settings for new touchpads.
-mojom::TouchpadSettingsPtr GetTouchpadSettingsFromPrefs(PrefService* prefs) {
+mojom::TouchpadSettingsPtr GetTouchpadSettingsFromPrefs(
+    PrefService* prefs,
+    ForceTouchpadSettingPersistence& force_persistence) {
   mojom::TouchpadSettingsPtr settings = mojom::TouchpadSettings::New();
-  settings->sensitivity = prefs->GetInteger(prefs::kTouchpadSensitivity);
-  settings->reverse_scrolling = prefs->GetBoolean(prefs::kNaturalScroll);
+
+  const auto* sensitivity_preference =
+      prefs->GetUserPrefValue(prefs::kTouchpadSensitivity);
+  settings->sensitivity = sensitivity_preference
+                              ? sensitivity_preference->GetInt()
+                              : kDefaultSensitivity;
+  force_persistence.sensitivity = sensitivity_preference != nullptr;
+
+  const auto* reverse_scrolling_preference =
+      prefs->GetUserPrefValue(prefs::kNaturalScroll);
+  settings->reverse_scrolling = reverse_scrolling_preference
+                                    ? reverse_scrolling_preference->GetBool()
+                                    : kDefaultReverseScrolling;
+  force_persistence.reverse_scrolling = reverse_scrolling_preference != nullptr;
+
+  const auto* acceleration_enabled_preference =
+      prefs->GetUserPrefValue(prefs::kTouchpadAcceleration);
   settings->acceleration_enabled =
-      prefs->GetBoolean(prefs::kTouchpadAcceleration);
-  settings->tap_to_click_enabled = prefs->GetBoolean(prefs::kTapToClickEnabled);
+      acceleration_enabled_preference
+          ? acceleration_enabled_preference->GetBool()
+          : kDefaultAccelerationEnabled;
+  force_persistence.acceleration_enabled =
+      acceleration_enabled_preference != nullptr;
+
+  const auto* tap_to_click_enabled_preference =
+      prefs->GetUserPrefValue(prefs::kTapToClickEnabled);
+  settings->tap_to_click_enabled =
+      tap_to_click_enabled_preference
+          ? tap_to_click_enabled_preference->GetBool()
+          : kDefaultTapToClickEnabled;
+  force_persistence.tap_to_click_enabled =
+      tap_to_click_enabled_preference != nullptr;
+
+  // Three finger click does not update `force_persistence` as it will soon be
+  // removed.
+  const auto* three_finger_click_enabled_preference =
+      prefs->GetUserPrefValue(prefs::kEnableTouchpadThreeFingerClick);
   settings->three_finger_click_enabled =
-      prefs->GetBoolean(prefs::kEnableTouchpadThreeFingerClick);
+      three_finger_click_enabled_preference
+          ? three_finger_click_enabled_preference->GetBool()
+          : kDefaultThreeFingerClickEnabled;
+
+  const auto* tap_dragging_enabled_preference =
+      prefs->GetUserPrefValue(prefs::kTapDraggingEnabled);
   settings->tap_dragging_enabled =
-      prefs->GetBoolean(prefs::kTapDraggingEnabled);
-  settings->scroll_sensitivity =
-      prefs->GetInteger(prefs::kTouchpadScrollSensitivity);
+      tap_dragging_enabled_preference
+          ? tap_dragging_enabled_preference->GetBool()
+          : kDefaultTapDraggingEnabled;
+  force_persistence.tap_dragging_enabled =
+      tap_dragging_enabled_preference != nullptr;
+
+  const auto* scroll_sensitivity_preference =
+      prefs->GetUserPrefValue(prefs::kTouchpadScrollSensitivity);
+  settings->scroll_sensitivity = scroll_sensitivity_preference
+                                     ? scroll_sensitivity_preference->GetInt()
+                                     : kDefaultSensitivity;
+  force_persistence.scroll_sensitivity =
+      scroll_sensitivity_preference != nullptr;
+
+  const auto* scroll_acceleration_preference =
+      prefs->GetUserPrefValue(prefs::kTouchpadScrollAcceleration);
   settings->scroll_acceleration =
-      prefs->GetBoolean(prefs::kTouchpadScrollAcceleration);
-  settings->haptic_sensitivity =
-      prefs->GetInteger(prefs::kTouchpadHapticClickSensitivity);
-  settings->haptic_enabled = prefs->GetBoolean(prefs::kTouchpadHapticFeedback);
+      scroll_acceleration_preference ? scroll_acceleration_preference->GetBool()
+                                     : kDefaultScrollAcceleration;
+  force_persistence.scroll_acceleration =
+      scroll_acceleration_preference != nullptr;
+
+  const auto* haptic_sensitivity_preference =
+      prefs->GetUserPrefValue(prefs::kTouchpadHapticClickSensitivity);
+  settings->haptic_sensitivity = haptic_sensitivity_preference
+                                     ? haptic_sensitivity_preference->GetInt()
+                                     : kDefaultHapticSensitivity;
+  force_persistence.haptic_sensitivity =
+      haptic_sensitivity_preference != nullptr;
+
+  const auto* haptic_enabled_preference =
+      prefs->GetUserPrefValue(prefs::kTouchpadHapticFeedback);
+  settings->haptic_enabled = haptic_enabled_preference
+                                 ? haptic_enabled_preference->GetBool()
+                                 : kDefaultHapticFeedbackEnabled;
+  force_persistence.haptic_enabled = haptic_enabled_preference != nullptr;
+
   return settings;
 }
 
-}  // namespace
-
-TouchpadPrefHandlerImpl::TouchpadPrefHandlerImpl() = default;
-TouchpadPrefHandlerImpl::~TouchpadPrefHandlerImpl() = default;
-
-void TouchpadPrefHandlerImpl::InitializeTouchpadSettings(
-    PrefService* pref_service,
-    mojom::Touchpad* touchpad) {
-  if (!pref_service) {
-    touchpad->settings = GetDefaultTouchpadSettings();
-    return;
-  }
-
-  const auto& devices_dict =
-      pref_service->GetDict(prefs::kTouchpadDeviceSettingsDictPref);
-  const auto* settings_dict = devices_dict.FindDict(touchpad->device_key);
-  if (!settings_dict) {
-    touchpad->settings = GetNewTouchpadSettings(pref_service, *touchpad);
-  } else {
-    touchpad->settings =
-        RetrieveTouchpadSettings(pref_service, *touchpad, *settings_dict);
-  }
-  DCHECK(touchpad->settings);
-
-  UpdateTouchpadSettings(pref_service, *touchpad);
-}
-
-void TouchpadPrefHandlerImpl::UpdateTouchpadSettings(
-    PrefService* pref_service,
-    const mojom::Touchpad& touchpad) {
-  DCHECK(touchpad.settings);
-  const mojom::TouchpadSettings& settings = *touchpad.settings;
-  // Populate `settings_dict` with all settings in `settings`.
-  base::Value::Dict settings_dict;
-  settings_dict.Set(prefs::kTouchpadSettingSensitivity, settings.sensitivity);
-  settings_dict.Set(prefs::kTouchpadSettingReverseScrolling,
-                    settings.reverse_scrolling);
-  settings_dict.Set(prefs::kTouchpadSettingAccelerationEnabled,
-                    settings.acceleration_enabled);
-  settings_dict.Set(prefs::kTouchpadSettingScrollSensitivity,
-                    settings.scroll_sensitivity);
-  settings_dict.Set(prefs::kTouchpadSettingScrollAcceleration,
-                    settings.scroll_acceleration);
-  settings_dict.Set(prefs::kTouchpadSettingTapToClickEnabled,
-                    settings.tap_to_click_enabled);
-  settings_dict.Set(prefs::kTouchpadSettingThreeFingerClickEnabled,
-                    settings.three_finger_click_enabled);
-  settings_dict.Set(prefs::kTouchpadSettingTapDraggingEnabled,
-                    settings.tap_dragging_enabled);
-  settings_dict.Set(prefs::kTouchpadSettingHapticSensitivity,
-                    settings.haptic_sensitivity);
-  settings_dict.Set(prefs::kTouchpadSettingHapticEnabled,
-                    settings.haptic_enabled);
-
-  // Retrieve old settings and merge with the new ones.
-  base::Value::Dict devices_dict =
-      pref_service->GetDict(prefs::kTouchpadDeviceSettingsDictPref).Clone();
-
-  // If an old settings dict already exists for the device, merge the updated
-  // settings into the old settings. Otherwise, insert the dict at
-  // `touchpad.device_key`.
-  base::Value::Dict* old_settings_dict =
-      devices_dict.FindDict(touchpad.device_key);
-  if (old_settings_dict) {
-    old_settings_dict->Merge(std::move(settings_dict));
-  } else {
-    devices_dict.Set(touchpad.device_key, std::move(settings_dict));
-  }
-
-  pref_service->SetDict(std::string(prefs::kTouchpadDeviceSettingsDictPref),
-                        std::move(devices_dict));
-}
-
-mojom::TouchpadSettingsPtr TouchpadPrefHandlerImpl::GetNewTouchpadSettings(
-    PrefService* prefs,
-    const mojom::Touchpad& touchpad) {
-  // TODO(michaelcheco): Remove once transitioned to per-device settings.
-  if (Shell::Get()->input_device_tracker()->WasDevicePreviouslyConnected(
-          InputDeviceTracker::InputDeviceCategory::kTouchpad,
-          touchpad.device_key)) {
-    return GetTouchpadSettingsFromPrefs(prefs);
-  }
-
-  return GetDefaultTouchpadSettings();
-}
-
-mojom::TouchpadSettingsPtr TouchpadPrefHandlerImpl::RetrieveTouchpadSettings(
+mojom::TouchpadSettingsPtr RetrieveTouchpadSettings(
     PrefService* pref_service,
     const mojom::Touchpad& touchpad,
     const base::Value::Dict& settings_dict) {
@@ -178,4 +176,166 @@
   return settings;
 }
 
+bool ExistingSettingsHasValue(base::StringPiece setting_key,
+                              const base::Value::Dict* existing_settings_dict) {
+  if (!existing_settings_dict) {
+    return false;
+  }
+
+  return existing_settings_dict->Find(setting_key) != nullptr;
+}
+
+void UpdateTouchpadSettingsImpl(
+    PrefService* pref_service,
+    const mojom::Touchpad& touchpad,
+    const ForceTouchpadSettingPersistence& force_persistence) {
+  DCHECK(touchpad.settings);
+  base::Value::Dict devices_dict =
+      pref_service->GetDict(prefs::kTouchpadDeviceSettingsDictPref).Clone();
+  base::Value::Dict* existing_settings_dict =
+      devices_dict.FindDict(touchpad.device_key);
+  const mojom::TouchpadSettings& settings = *touchpad.settings;
+
+  // Populate `settings_dict` with all settings in `settings`.
+  base::Value::Dict settings_dict;
+
+  // Settings should only be persisted if one or more of the following is true:
+  // - Setting was previously persisted to storage
+  // - `force_persistence` requires the setting to be persisted, this means this
+  //   device is being transitioned from the old global settings to per-device
+  //   settings and the user specified the specific value for this setting.
+  // - Setting is different than the default, which means the user manually
+  //   changed the value.
+
+  if (ExistingSettingsHasValue(prefs::kTouchpadSettingSensitivity,
+                               existing_settings_dict) ||
+      force_persistence.sensitivity ||
+      settings.sensitivity != kDefaultSensitivity) {
+    settings_dict.Set(prefs::kTouchpadSettingSensitivity, settings.sensitivity);
+  }
+
+  if (ExistingSettingsHasValue(prefs::kTouchpadSettingReverseScrolling,
+                               existing_settings_dict) ||
+      force_persistence.reverse_scrolling ||
+      settings.reverse_scrolling != kDefaultReverseScrolling) {
+    settings_dict.Set(prefs::kTouchpadSettingReverseScrolling,
+                      settings.reverse_scrolling);
+  }
+
+  if (ExistingSettingsHasValue(prefs::kTouchpadSettingAccelerationEnabled,
+                               existing_settings_dict) ||
+      force_persistence.acceleration_enabled ||
+      settings.acceleration_enabled != kDefaultAccelerationEnabled) {
+    settings_dict.Set(prefs::kTouchpadSettingAccelerationEnabled,
+                      settings.acceleration_enabled);
+  }
+
+  if (ExistingSettingsHasValue(prefs::kTouchpadSettingScrollSensitivity,
+                               existing_settings_dict) ||
+      force_persistence.scroll_sensitivity ||
+      settings.scroll_sensitivity != kDefaultSensitivity) {
+    settings_dict.Set(prefs::kTouchpadSettingScrollSensitivity,
+                      settings.scroll_sensitivity);
+  }
+
+  if (ExistingSettingsHasValue(prefs::kTouchpadSettingScrollAcceleration,
+                               existing_settings_dict) ||
+      force_persistence.scroll_acceleration ||
+      settings.scroll_acceleration != kDefaultScrollAcceleration) {
+    settings_dict.Set(prefs::kTouchpadSettingScrollAcceleration,
+                      settings.scroll_acceleration);
+  }
+
+  if (ExistingSettingsHasValue(prefs::kTouchpadSettingTapToClickEnabled,
+                               existing_settings_dict) ||
+      force_persistence.tap_to_click_enabled ||
+      settings.tap_to_click_enabled != kDefaultTapToClickEnabled) {
+    settings_dict.Set(prefs::kTouchpadSettingTapToClickEnabled,
+                      settings.tap_to_click_enabled);
+  }
+
+  if (ExistingSettingsHasValue(prefs::kTouchpadSettingThreeFingerClickEnabled,
+                               existing_settings_dict) ||
+      settings.three_finger_click_enabled != kDefaultThreeFingerClickEnabled) {
+    settings_dict.Set(prefs::kTouchpadSettingThreeFingerClickEnabled,
+                      settings.three_finger_click_enabled);
+  }
+
+  if (ExistingSettingsHasValue(prefs::kTouchpadSettingTapDraggingEnabled,
+                               existing_settings_dict) ||
+      force_persistence.tap_dragging_enabled ||
+      settings.tap_dragging_enabled != kDefaultTapDraggingEnabled) {
+    settings_dict.Set(prefs::kTouchpadSettingTapDraggingEnabled,
+                      settings.tap_dragging_enabled);
+  }
+
+  if (ExistingSettingsHasValue(prefs::kTouchpadSettingHapticSensitivity,
+                               existing_settings_dict) ||
+      force_persistence.haptic_sensitivity ||
+      settings.haptic_sensitivity != kDefaultSensitivity) {
+    settings_dict.Set(prefs::kTouchpadSettingHapticSensitivity,
+                      settings.haptic_sensitivity);
+  }
+
+  if (ExistingSettingsHasValue(prefs::kTouchpadSettingHapticEnabled,
+                               existing_settings_dict) ||
+      force_persistence.haptic_enabled ||
+      settings.haptic_enabled != kDefaultHapticFeedbackEnabled) {
+    settings_dict.Set(prefs::kTouchpadSettingHapticEnabled,
+                      settings.haptic_enabled);
+  }
+
+  // If an old settings dict already exists for the device, merge the updated
+  // settings into the old settings. Otherwise, insert the dict at
+  // `touchpad.device_key`.
+  if (existing_settings_dict) {
+    existing_settings_dict->Merge(std::move(settings_dict));
+  } else {
+    devices_dict.Set(touchpad.device_key, std::move(settings_dict));
+  }
+
+  pref_service->SetDict(std::string(prefs::kTouchpadDeviceSettingsDictPref),
+                        std::move(devices_dict));
+}
+
+}  // namespace
+
+TouchpadPrefHandlerImpl::TouchpadPrefHandlerImpl() = default;
+TouchpadPrefHandlerImpl::~TouchpadPrefHandlerImpl() = default;
+
+void TouchpadPrefHandlerImpl::InitializeTouchpadSettings(
+    PrefService* pref_service,
+    mojom::Touchpad* touchpad) {
+  if (!pref_service) {
+    touchpad->settings = GetDefaultTouchpadSettings();
+    return;
+  }
+
+  const auto& devices_dict =
+      pref_service->GetDict(prefs::kTouchpadDeviceSettingsDictPref);
+  const auto* settings_dict = devices_dict.FindDict(touchpad->device_key);
+  ForceTouchpadSettingPersistence force_persistence;
+  if (settings_dict) {
+    touchpad->settings =
+        RetrieveTouchpadSettings(pref_service, *touchpad, *settings_dict);
+  } else if (Shell::Get()->input_device_tracker()->WasDevicePreviouslyConnected(
+                 InputDeviceTracker::InputDeviceCategory::kTouchpad,
+                 touchpad->device_key)) {
+    touchpad->settings =
+        GetTouchpadSettingsFromPrefs(pref_service, force_persistence);
+  } else {
+    touchpad->settings = GetDefaultTouchpadSettings();
+  }
+  DCHECK(touchpad->settings);
+
+  UpdateTouchpadSettingsImpl(pref_service, *touchpad, force_persistence);
+}
+
+void TouchpadPrefHandlerImpl::UpdateTouchpadSettings(
+    PrefService* pref_service,
+    const mojom::Touchpad& touchpad) {
+  UpdateTouchpadSettingsImpl(pref_service, touchpad,
+                             /*force_persistence=*/{});
+}
+
 }  // namespace ash
diff --git a/ash/system/input_device_settings/pref_handlers/touchpad_pref_handler_impl.h b/ash/system/input_device_settings/pref_handlers/touchpad_pref_handler_impl.h
index 2bef2b7..c2f0c4c 100644
--- a/ash/system/input_device_settings/pref_handlers/touchpad_pref_handler_impl.h
+++ b/ash/system/input_device_settings/pref_handlers/touchpad_pref_handler_impl.h
@@ -25,15 +25,6 @@
                                   mojom::Touchpad* touchpad) override;
   void UpdateTouchpadSettings(PrefService* pref_service,
                               const mojom::Touchpad& touchpad) override;
-
- private:
-  mojom::TouchpadSettingsPtr GetNewTouchpadSettings(
-      PrefService* prefs,
-      const mojom::Touchpad& touchpad);
-  mojom::TouchpadSettingsPtr RetrieveTouchpadSettings(
-      PrefService* prefs,
-      const mojom::Touchpad& touchpad,
-      const base::Value::Dict& settings_dict);
 };
 
 }  // namespace ash
diff --git a/ash/system/input_device_settings/pref_handlers/touchpad_pref_handler_unittest.cc b/ash/system/input_device_settings/pref_handlers/touchpad_pref_handler_unittest.cc
index 3ec8bbc..1d6dcc62 100644
--- a/ash/system/input_device_settings/pref_handlers/touchpad_pref_handler_unittest.cc
+++ b/ash/system/input_device_settings/pref_handlers/touchpad_pref_handler_unittest.cc
@@ -45,6 +45,18 @@
     /*haptic_sensitivity=*/kDefaultHapticSensitivity,
     /*haptic_enabled=*/kDefaultHapticFeedbackEnabled);
 
+const mojom::TouchpadSettings kTouchpadSettingsNotDefault(
+    /*sensitivity=*/1,
+    /*reverse_scrolling=*/!kDefaultReverseScrolling,
+    /*acceleration_enabled=*/!kDefaultAccelerationEnabled,
+    /*tap_to_click_enabled=*/!kDefaultTapToClickEnabled,
+    /*three_finger_click_enabled=*/!kDefaultThreeFingerClickEnabled,
+    /*tap_dragging_enabled=*/!kDefaultTapDraggingEnabled,
+    /*scroll_sensitivity=*/1,
+    /*scroll_acceleration=*/!kDefaultScrollAcceleration,
+    /*haptic_sensitivity=*/1,
+    /*haptic_enabled=*/!kDefaultHapticFeedbackEnabled);
+
 const mojom::TouchpadSettings kTouchpadSettings1(
     /*sensitivity=*/1,
     /*reverse_scrolling=*/false,
@@ -97,25 +109,47 @@
     // We are using these test constants as a a way to differentiate values
     // retrieved from prefs or default touchpad settings.
     pref_service_->registry()->RegisterIntegerPref(prefs::kTouchpadSensitivity,
-                                                   kTestSensitivity);
+                                                   kDefaultSensitivity);
     pref_service_->registry()->RegisterBooleanPref(prefs::kNaturalScroll,
-                                                   kTestReverseScrolling);
+                                                   kDefaultReverseScrolling);
     pref_service_->registry()->RegisterBooleanPref(prefs::kTouchpadAcceleration,
-                                                   kTestAccelerationEnabled);
+                                                   kDefaultAccelerationEnabled);
     pref_service_->registry()->RegisterBooleanPref(prefs::kTapToClickEnabled,
-                                                   kTestTapToClickEnabled);
+                                                   kDefaultTapToClickEnabled);
     pref_service_->registry()->RegisterBooleanPref(
-        prefs::kEnableTouchpadThreeFingerClick, kTestThreeFingerClickEnabled);
+        prefs::kEnableTouchpadThreeFingerClick,
+        kDefaultThreeFingerClickEnabled);
     pref_service_->registry()->RegisterBooleanPref(prefs::kTapDraggingEnabled,
-                                                   kTestTapDraggingEnabled);
+                                                   kDefaultTapDraggingEnabled);
     pref_service_->registry()->RegisterIntegerPref(
-        prefs::kTouchpadScrollSensitivity, kTestSensitivity);
+        prefs::kTouchpadScrollSensitivity, kDefaultSensitivity);
     pref_service_->registry()->RegisterBooleanPref(
-        prefs::kTouchpadScrollAcceleration, kTestScrollAcceleration);
+        prefs::kTouchpadScrollAcceleration, kDefaultScrollAcceleration);
     pref_service_->registry()->RegisterIntegerPref(
-        prefs::kTouchpadHapticClickSensitivity, kTestHapticSensitivity);
+        prefs::kTouchpadHapticClickSensitivity, kDefaultHapticSensitivity);
     pref_service_->registry()->RegisterBooleanPref(
-        prefs::kTouchpadHapticFeedback, kTestHapticFeedbackEnabled);
+        prefs::kTouchpadHapticFeedback, kDefaultHapticFeedbackEnabled);
+
+    pref_service_->SetUserPref(prefs::kTouchpadSensitivity,
+                               base::Value(kTestSensitivity));
+    pref_service_->SetUserPref(prefs::kNaturalScroll,
+                               base::Value(kTestReverseScrolling));
+    pref_service_->SetUserPref(prefs::kTouchpadAcceleration,
+                               base::Value(kTestAccelerationEnabled));
+    pref_service_->SetUserPref(prefs::kTapToClickEnabled,
+                               base::Value(kTestTapToClickEnabled));
+    pref_service_->SetUserPref(prefs::kEnableTouchpadThreeFingerClick,
+                               base::Value(kTestThreeFingerClickEnabled));
+    pref_service_->SetUserPref(prefs::kTapDraggingEnabled,
+                               base::Value(kTestTapDraggingEnabled));
+    pref_service_->SetUserPref(prefs::kTouchpadScrollSensitivity,
+                               base::Value(kTestSensitivity));
+    pref_service_->SetUserPref(prefs::kTouchpadScrollAcceleration,
+                               base::Value(kTestScrollAcceleration));
+    pref_service_->SetUserPref(prefs::kTouchpadHapticClickSensitivity,
+                               base::Value(kTestHapticSensitivity));
+    pref_service_->SetUserPref(prefs::kTouchpadHapticFeedback,
+                               base::Value(kTestHapticFeedbackEnabled));
   }
 
   void CheckTouchpadSettingsAndDictAreEqual(
@@ -123,53 +157,85 @@
       const base::Value::Dict& settings_dict) {
     const auto sensitivity =
         settings_dict.FindInt(prefs::kTouchpadSettingSensitivity);
-    ASSERT_TRUE(sensitivity.has_value());
-    EXPECT_EQ(settings.sensitivity, sensitivity);
+    if (sensitivity.has_value()) {
+      EXPECT_EQ(settings.sensitivity, sensitivity);
+    } else {
+      EXPECT_EQ(settings.sensitivity, kDefaultSensitivity);
+    }
 
     const auto reverse_scrolling =
         settings_dict.FindBool(prefs::kTouchpadSettingReverseScrolling);
-    ASSERT_TRUE(reverse_scrolling.has_value());
-    EXPECT_EQ(settings.reverse_scrolling, reverse_scrolling);
+    if (reverse_scrolling.has_value()) {
+      EXPECT_EQ(settings.reverse_scrolling, reverse_scrolling);
+    } else {
+      EXPECT_EQ(settings.reverse_scrolling, kDefaultReverseScrolling);
+    }
 
     const auto acceleration_enabled =
         settings_dict.FindBool(prefs::kTouchpadSettingAccelerationEnabled);
-    ASSERT_TRUE(acceleration_enabled.has_value());
-    EXPECT_EQ(settings.acceleration_enabled, acceleration_enabled);
+    if (acceleration_enabled.has_value()) {
+      EXPECT_EQ(settings.acceleration_enabled, acceleration_enabled);
+    } else {
+      EXPECT_EQ(settings.acceleration_enabled, kDefaultAccelerationEnabled);
+    }
 
     const auto scroll_sensitivity =
         settings_dict.FindInt(prefs::kTouchpadSettingScrollSensitivity);
-    ASSERT_TRUE(scroll_sensitivity.has_value());
-    EXPECT_EQ(settings.scroll_sensitivity, scroll_sensitivity);
+    if (scroll_sensitivity.has_value()) {
+      EXPECT_EQ(settings.scroll_sensitivity, scroll_sensitivity);
+    } else {
+      EXPECT_EQ(settings.scroll_sensitivity, kDefaultSensitivity);
+    }
 
     const auto scroll_acceleration =
         settings_dict.FindBool(prefs::kTouchpadSettingScrollAcceleration);
-    ASSERT_TRUE(scroll_acceleration.has_value());
-    EXPECT_EQ(settings.scroll_acceleration, scroll_acceleration);
+    if (scroll_acceleration.has_value()) {
+      EXPECT_EQ(settings.scroll_acceleration, scroll_acceleration);
+    } else {
+      EXPECT_EQ(settings.scroll_acceleration, kDefaultScrollAcceleration);
+    }
 
     const auto tap_to_click_enabled =
         settings_dict.FindBool(prefs::kTouchpadSettingTapToClickEnabled);
-    ASSERT_TRUE(tap_to_click_enabled.has_value());
-    EXPECT_EQ(settings.tap_to_click_enabled, tap_to_click_enabled);
+    if (tap_to_click_enabled.has_value()) {
+      EXPECT_EQ(settings.tap_to_click_enabled, tap_to_click_enabled);
+    } else {
+      EXPECT_EQ(settings.tap_to_click_enabled, kDefaultTapToClickEnabled);
+    }
 
     const auto three_finger_click_enabled =
         settings_dict.FindBool(prefs::kTouchpadSettingThreeFingerClickEnabled);
-    ASSERT_TRUE(three_finger_click_enabled.has_value());
-    EXPECT_EQ(settings.three_finger_click_enabled, three_finger_click_enabled);
+    if (three_finger_click_enabled.has_value()) {
+      EXPECT_EQ(settings.three_finger_click_enabled,
+                three_finger_click_enabled);
+    } else {
+      EXPECT_EQ(settings.three_finger_click_enabled,
+                kDefaultThreeFingerClickEnabled);
+    }
 
     const auto tap_dragging_enabled =
         settings_dict.FindBool(prefs::kTouchpadSettingTapDraggingEnabled);
-    ASSERT_TRUE(tap_dragging_enabled.has_value());
-    EXPECT_EQ(settings.tap_dragging_enabled, tap_dragging_enabled);
+    if (tap_dragging_enabled.has_value()) {
+      EXPECT_EQ(settings.tap_dragging_enabled, tap_dragging_enabled);
+    } else {
+      EXPECT_EQ(settings.tap_dragging_enabled, kDefaultTapDraggingEnabled);
+    }
 
     const auto haptic_sensitivity =
         settings_dict.FindInt(prefs::kTouchpadSettingHapticSensitivity);
-    ASSERT_TRUE(haptic_sensitivity.has_value());
-    EXPECT_EQ(settings.haptic_sensitivity, haptic_sensitivity);
+    if (haptic_sensitivity.has_value()) {
+      EXPECT_EQ(settings.haptic_sensitivity, haptic_sensitivity);
+    } else {
+      EXPECT_EQ(settings.haptic_sensitivity, kDefaultHapticSensitivity);
+    }
 
     const auto haptic_enabled =
         settings_dict.FindBool(prefs::kTouchpadSettingHapticEnabled);
-    ASSERT_TRUE(haptic_enabled.has_value());
-    EXPECT_EQ(settings.haptic_enabled, haptic_enabled);
+    if (haptic_enabled.has_value()) {
+      EXPECT_EQ(settings.haptic_enabled, haptic_enabled);
+    } else {
+      EXPECT_EQ(settings.haptic_enabled, kDefaultHapticFeedbackEnabled);
+    }
   }
 
   void CallUpdateTouchpadSettings(const std::string& device_key,
@@ -191,6 +257,16 @@
     return std::move(touchpad->settings);
   }
 
+  const base::Value::Dict* GetSettingsDict(const std::string& device_key) {
+    const auto& devices_dict =
+        pref_service_->GetDict(prefs::kTouchpadDeviceSettingsDictPref);
+    EXPECT_EQ(1u, devices_dict.size());
+    const auto* settings_dict = devices_dict.FindDict(device_key);
+    EXPECT_NE(nullptr, settings_dict);
+
+    return settings_dict;
+  }
+
  protected:
   std::unique_ptr<TouchpadPrefHandlerImpl> pref_handler_;
   std::unique_ptr<TestingPrefServiceSimple> pref_service_;
@@ -330,6 +406,54 @@
                                        *settings_dict);
 }
 
+TEST_F(TouchpadPrefHandlerTest,
+       TransitionPeriodSettingsPersistedWhenUserChosen) {
+  mojom::Touchpad touchpad;
+  touchpad.device_key = kTouchpadKey1;
+  Shell::Get()->input_device_tracker()->OnTouchpadConnected(touchpad);
+
+  pref_service_->SetUserPref(prefs::kTouchpadSensitivity,
+                             base::Value(kDefaultSensitivity));
+  pref_service_->SetUserPref(prefs::kNaturalScroll,
+                             base::Value(kDefaultReverseScrolling));
+  pref_service_->SetUserPref(prefs::kTouchpadAcceleration,
+                             base::Value(kDefaultAccelerationEnabled));
+  pref_service_->SetUserPref(prefs::kTapToClickEnabled,
+                             base::Value(kDefaultTapToClickEnabled));
+  pref_service_->SetUserPref(prefs::kTapDraggingEnabled,
+                             base::Value(kDefaultTapDraggingEnabled));
+  pref_service_->SetUserPref(prefs::kTouchpadScrollSensitivity,
+                             base::Value(kDefaultSensitivity));
+  pref_service_->SetUserPref(prefs::kTouchpadScrollAcceleration,
+                             base::Value(kDefaultScrollAcceleration));
+  pref_service_->SetUserPref(prefs::kTouchpadHapticClickSensitivity,
+                             base::Value(kDefaultHapticSensitivity));
+  pref_service_->SetUserPref(prefs::kTouchpadHapticFeedback,
+                             base::Value(kDefaultHapticFeedbackEnabled));
+
+  auto settings = CallInitializeTouchpadSettings(kTouchpadKey1);
+  EXPECT_EQ(kTouchpadSettingsDefault, *settings);
+
+  const auto* settings_dict = GetSettingsDict(kTouchpadKey1);
+  EXPECT_TRUE(settings_dict->contains(prefs::kTouchpadSettingSensitivity));
+  EXPECT_TRUE(settings_dict->contains(prefs::kTouchpadSettingReverseScrolling));
+  EXPECT_TRUE(
+      settings_dict->contains(prefs::kTouchpadSettingAccelerationEnabled));
+  EXPECT_TRUE(
+      settings_dict->contains(prefs::kTouchpadSettingTapToClickEnabled));
+  EXPECT_TRUE(
+      settings_dict->contains(prefs::kTouchpadSettingTapDraggingEnabled));
+  EXPECT_TRUE(
+      settings_dict->contains(prefs::kTouchpadSettingScrollSensitivity));
+  EXPECT_TRUE(
+      settings_dict->contains(prefs::kTouchpadSettingScrollAcceleration));
+  EXPECT_TRUE(
+      settings_dict->contains(prefs::kTouchpadSettingHapticSensitivity));
+  EXPECT_TRUE(settings_dict->contains(prefs::kTouchpadSettingHapticEnabled));
+  CheckTouchpadSettingsAndDictAreEqual(kTouchpadSettingsDefault,
+                                       *settings_dict);
+}
+
 TEST_F(TouchpadPrefHandlerTest, TouchpadObserveredInTransitionPeriod) {
   mojom::Touchpad touchpad;
   touchpad.device_key = kTouchpadKey1;
@@ -350,6 +474,70 @@
   ASSERT_EQ(settings->haptic_enabled, kTestHapticFeedbackEnabled);
 }
 
+TEST_F(TouchpadPrefHandlerTest, DefaultNotPersistedUntilUpdated) {
+  CallUpdateTouchpadSettings(kTouchpadKey1, kTouchpadSettingsDefault);
+
+  const auto* settings_dict = GetSettingsDict(kTouchpadKey1);
+  EXPECT_FALSE(settings_dict->contains(prefs::kTouchpadSettingSensitivity));
+  EXPECT_FALSE(
+      settings_dict->contains(prefs::kTouchpadSettingReverseScrolling));
+  EXPECT_FALSE(
+      settings_dict->contains(prefs::kTouchpadSettingAccelerationEnabled));
+  EXPECT_FALSE(
+      settings_dict->contains(prefs::kTouchpadSettingTapToClickEnabled));
+  EXPECT_FALSE(
+      settings_dict->contains(prefs::kTouchpadSettingTapDraggingEnabled));
+  EXPECT_FALSE(
+      settings_dict->contains(prefs::kTouchpadSettingScrollSensitivity));
+  EXPECT_FALSE(
+      settings_dict->contains(prefs::kTouchpadSettingScrollAcceleration));
+  EXPECT_FALSE(
+      settings_dict->contains(prefs::kTouchpadSettingHapticSensitivity));
+  EXPECT_FALSE(settings_dict->contains(prefs::kTouchpadSettingHapticEnabled));
+  CheckTouchpadSettingsAndDictAreEqual(kTouchpadSettingsDefault,
+                                       *settings_dict);
+
+  CallUpdateTouchpadSettings(kTouchpadKey1, kTouchpadSettingsNotDefault);
+  settings_dict = GetSettingsDict(kTouchpadKey1);
+  EXPECT_TRUE(settings_dict->contains(prefs::kTouchpadSettingSensitivity));
+  EXPECT_TRUE(settings_dict->contains(prefs::kTouchpadSettingReverseScrolling));
+  EXPECT_TRUE(
+      settings_dict->contains(prefs::kTouchpadSettingAccelerationEnabled));
+  EXPECT_TRUE(
+      settings_dict->contains(prefs::kTouchpadSettingTapToClickEnabled));
+  EXPECT_TRUE(
+      settings_dict->contains(prefs::kTouchpadSettingTapDraggingEnabled));
+  EXPECT_TRUE(
+      settings_dict->contains(prefs::kTouchpadSettingScrollSensitivity));
+  EXPECT_TRUE(
+      settings_dict->contains(prefs::kTouchpadSettingScrollAcceleration));
+  EXPECT_TRUE(
+      settings_dict->contains(prefs::kTouchpadSettingHapticSensitivity));
+  EXPECT_TRUE(settings_dict->contains(prefs::kTouchpadSettingHapticEnabled));
+  CheckTouchpadSettingsAndDictAreEqual(kTouchpadSettingsNotDefault,
+                                       *settings_dict);
+
+  CallUpdateTouchpadSettings(kTouchpadKey1, kTouchpadSettingsDefault);
+  settings_dict = GetSettingsDict(kTouchpadKey1);
+  EXPECT_TRUE(settings_dict->contains(prefs::kTouchpadSettingSensitivity));
+  EXPECT_TRUE(settings_dict->contains(prefs::kTouchpadSettingReverseScrolling));
+  EXPECT_TRUE(
+      settings_dict->contains(prefs::kTouchpadSettingAccelerationEnabled));
+  EXPECT_TRUE(
+      settings_dict->contains(prefs::kTouchpadSettingTapToClickEnabled));
+  EXPECT_TRUE(
+      settings_dict->contains(prefs::kTouchpadSettingTapDraggingEnabled));
+  EXPECT_TRUE(
+      settings_dict->contains(prefs::kTouchpadSettingScrollSensitivity));
+  EXPECT_TRUE(
+      settings_dict->contains(prefs::kTouchpadSettingScrollAcceleration));
+  EXPECT_TRUE(
+      settings_dict->contains(prefs::kTouchpadSettingHapticSensitivity));
+  EXPECT_TRUE(settings_dict->contains(prefs::kTouchpadSettingHapticEnabled));
+  CheckTouchpadSettingsAndDictAreEqual(kTouchpadSettingsDefault,
+                                       *settings_dict);
+}
+
 class TouchpadSettingsPrefConversionTest
     : public TouchpadPrefHandlerTest,
       public testing::WithParamInterface<
@@ -383,12 +571,7 @@
 TEST_P(TouchpadSettingsPrefConversionTest, CheckConversion) {
   CallUpdateTouchpadSettings(device_key_, settings_);
 
-  const auto& devices_dict =
-      pref_service_->GetDict(prefs::kTouchpadDeviceSettingsDictPref);
-  ASSERT_EQ(1u, devices_dict.size());
-  auto* settings_dict = devices_dict.FindDict(device_key_);
-  ASSERT_NE(nullptr, settings_dict);
-
+  const auto* settings_dict = GetSettingsDict(device_key_);
   CheckTouchpadSettingsAndDictAreEqual(settings_, *settings_dict);
 }
 
diff --git a/ash/system/network/network_list_network_header_view.cc b/ash/system/network/network_list_network_header_view.cc
index 67ec65d..bf2ee98e 100644
--- a/ash/system/network/network_list_network_header_view.cc
+++ b/ash/system/network/network_list_network_header_view.cc
@@ -8,6 +8,7 @@
 #include "ash/constants/ash_features.h"
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
+#include "ash/style/switch.h"
 #include "ash/system/model/system_tray_model.h"
 #include "ash/system/network/network_list_header_view.h"
 #include "ash/system/network/tray_network_state_model.h"
@@ -41,13 +42,19 @@
   toggle_ = toggle.get();
 
   if (features::IsQsRevampEnabled()) {
+    auto qs_toggle = std::make_unique<Switch>(
+        base::BindRepeating(&NetworkListNetworkHeaderView::ToggleButtonPressed,
+                            weak_factory_.GetWeakPtr()));
+    qs_toggle->SetAccessibleName(l10n_util::GetStringUTF16(label_id));
+    qs_toggle->SetID(kQsToggleButtonId);
+    qs_toggle_ = qs_toggle.get();
     auto image_view = std::make_unique<views::ImageView>();
     image_view->SetImage(ui::ImageModel::FromVectorIcon(
         vector_icon, cros_tokens::kCrosSysOnSurface));
     entry_row()->AddViewAndLabel(std::move(image_view),
                                  l10n_util::GetStringUTF16(label_id));
     entry_row()->SetExpandable(true);
-    entry_row()->AddRightView(toggle.release());
+    entry_row()->AddRightView(qs_toggle.release());
   } else {
     container()->AddView(TriView::Container::END, toggle.release());
   }
@@ -58,16 +65,24 @@
 void NetworkListNetworkHeaderView::SetToggleState(bool enabled,
                                                   bool is_on,
                                                   bool animate_toggle) {
-  toggle_->SetEnabled(enabled);
-  toggle_->SetAcceptsEvents(enabled);
-
   if (features::IsQsRevampEnabled()) {
     entry_row()->SetEnabled(enabled);
-    // Update the  on/off label.
+    // Update the on/off label.
     entry_row()->text_label()->SetText(l10n_util::GetStringUTF16(
         is_on ? enabled_label_id_ : IDS_ASH_QUICK_SETTINGS_NETWORK_DISABLED));
+
+    qs_toggle_->SetEnabled(enabled);
+    qs_toggle_->SetCanProcessEventsWithinSubtree(enabled);
+    if (animate_toggle) {
+      qs_toggle_->AnimateIsOn(is_on);
+    } else {
+      qs_toggle_->SetIsOn(is_on);
+    }
+    return;
   }
 
+  toggle_->SetEnabled(enabled);
+  toggle_->SetAcceptsEvents(enabled);
   if (animate_toggle) {
     toggle_->AnimateIsOn(is_on);
     return;
@@ -81,7 +96,11 @@
 void NetworkListNetworkHeaderView::OnToggleToggled(bool is_on) {}
 
 void NetworkListNetworkHeaderView::SetToggleVisibility(bool visible) {
-  toggle_->SetVisible(visible);
+  if (features::IsQsRevampEnabled()) {
+    qs_toggle_->SetVisible(visible);
+  } else {
+    toggle_->SetVisible(visible);
+  }
 }
 
 void NetworkListNetworkHeaderView::ToggleButtonPressed() {
@@ -96,8 +115,14 @@
   // disabling of mobile radio. The toggle will get unlocked in the next
   // call to SetToggleState(). Note that we don't disable/enable
   // because that would clear focus.
-  toggle_->SetAcceptsEvents(false);
-  OnToggleToggled(has_new_state ? toggle_->GetIsOn() : !toggle_->GetIsOn());
+  if (features::IsQsRevampEnabled()) {
+    qs_toggle_->SetAcceptsEvents(false);
+    OnToggleToggled(has_new_state ? qs_toggle_->GetIsOn()
+                                  : !qs_toggle_->GetIsOn());
+  } else {
+    toggle_->SetAcceptsEvents(false);
+    OnToggleToggled(has_new_state ? toggle_->GetIsOn() : !toggle_->GetIsOn());
+  }
 }
 
 }  // namespace ash
diff --git a/ash/system/network/network_list_network_header_view.h b/ash/system/network/network_list_network_header_view.h
index dc4612de..1e68334 100644
--- a/ash/system/network/network_list_network_header_view.h
+++ b/ash/system/network/network_list_network_header_view.h
@@ -6,6 +6,7 @@
 #define ASH_SYSTEM_NETWORK_NETWORK_LIST_NETWORK_HEADER_VIEW_H_
 
 #include "ash/ash_export.h"
+#include "ash/style/switch.h"
 #include "ash/system/network/network_list_header_view.h"
 #include "ash/system/tray/tri_view.h"
 #include "base/memory/weak_ptr.h"
@@ -57,6 +58,7 @@
   // Used for testing.
   static constexpr int kToggleButtonId =
       NetworkListHeaderView::kTitleLabelViewId + 1;
+  static constexpr int kQsToggleButtonId = kToggleButtonId + 1;
 
  private:
   friend class NetworkListNetworkHeaderViewTest;
@@ -72,8 +74,10 @@
   TrayNetworkStateModel* model_;
   int const enabled_label_id_;
 
-  // ToggleButton to toggle section on or off.
+  // `ToggleButton` to toggle section on or off.
   views::ToggleButton* toggle_ = nullptr;
+  // `KnobSwitch` to toggle section on or off.
+  Switch* qs_toggle_ = nullptr;
 
   Delegate* delegate_ = nullptr;
 
diff --git a/ash/system/network/network_list_view_controller_unittest.cc b/ash/system/network/network_list_view_controller_unittest.cc
index bc86e46..f28334b 100644
--- a/ash/system/network/network_list_view_controller_unittest.cc
+++ b/ash/system/network/network_list_view_controller_unittest.cc
@@ -15,6 +15,7 @@
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
 #include "ash/style/ash_color_provider.h"
+#include "ash/style/switch.h"
 #include "ash/system/model/system_tray_model.h"
 #include "ash/system/network/network_detailed_network_view_impl.h"
 #include "ash/system/network/network_utils.h"
@@ -225,6 +226,36 @@
     return GetWifiSubHeader()->toggle_;
   }
 
+  Switch* GetQsMobileToggleButton() { return GetMobileSubHeader()->qs_toggle_; }
+
+  Switch* GetQsWifiToggleButton() { return GetWifiSubHeader()->qs_toggle_; }
+
+  void CheckWifiToggleButtonStatus(bool toggled_on) {
+    if (IsQsRevampEnabled()) {
+      EXPECT_TRUE(GetQsWifiToggleButton()->GetVisible());
+      EXPECT_TRUE(GetQsWifiToggleButton()->GetEnabled());
+      EXPECT_EQ(GetQsWifiToggleButton()->GetIsOn(), toggled_on);
+    } else {
+      EXPECT_TRUE(GetWifiToggleButton()->GetVisible());
+      EXPECT_TRUE(GetWifiToggleButton()->GetEnabled());
+      EXPECT_EQ(GetWifiToggleButton()->GetIsOn(), toggled_on);
+    }
+  }
+
+  void CheckMobileToggleButtonStatus(bool enabled,
+                                     bool toggled_on,
+                                     bool visible = true) {
+    if (IsQsRevampEnabled()) {
+      EXPECT_EQ(GetQsMobileToggleButton()->GetVisible(), visible);
+      EXPECT_EQ(GetQsMobileToggleButton()->GetEnabled(), enabled);
+      EXPECT_EQ(GetQsMobileToggleButton()->GetIsOn(), toggled_on);
+    } else {
+      EXPECT_EQ(GetMobileToggleButton()->GetVisible(), visible);
+      EXPECT_EQ(GetMobileToggleButton()->GetEnabled(), enabled);
+      EXPECT_EQ(GetMobileToggleButton()->GetIsOn(), toggled_on);
+    }
+  }
+
   IconButton* GetAddEsimButton() {
     return FindViewById<IconButton*>(
         NetworkListMobileHeaderViewImpl::kAddESimButtonId);
@@ -298,7 +329,7 @@
 
       // Expect that the view at `index` is a network item, and that it is an
       // wifi network.
-      if (!wifi_network_count && GetWifiToggleButton()->GetIsOn()) {
+      if (!wifi_network_count && GetQsWifiToggleButton()->GetIsOn()) {
         // When no WiFi networks are available, status message is shown.
         EXPECT_NE(nullptr, GetWifiStatusMessage());
       }
@@ -592,9 +623,7 @@
 
   ASSERT_THAT(GetWifiSubHeader(), NotNull());
   EXPECT_THAT(GetWifiSeparator(), IsNull());
-  EXPECT_TRUE(GetWifiToggleButton()->GetVisible());
-  EXPECT_TRUE(GetWifiToggleButton()->GetEnabled());
-  EXPECT_TRUE(GetWifiToggleButton()->GetIsOn());
+  CheckWifiToggleButtonStatus(/*toggled_on=*/true);
   histogram_tester.ExpectBucketCount("ChromeOS.SystemTray.Network.SectionShown",
                                      DetailedViewSection::kWifiSection, 1);
 
@@ -606,9 +635,7 @@
   cros_network()->SetDeviceProperties(properties.Clone());
 
   ASSERT_THAT(GetWifiSubHeader(), NotNull());
-  EXPECT_TRUE(GetWifiToggleButton()->GetVisible());
-  EXPECT_TRUE(GetWifiToggleButton()->GetEnabled());
-  EXPECT_FALSE(GetWifiToggleButton()->GetIsOn());
+  CheckWifiToggleButtonStatus(/*toggled_on=*/false);
   histogram_tester.ExpectBucketCount("ChromeOS.SystemTray.Network.SectionShown",
                                      DetailedViewSection::kWifiSection, 1);
 }
@@ -913,9 +940,7 @@
   cros_network()->SetDeviceProperties(properties.Clone());
 
   ASSERT_THAT(GetMobileStatusMessage(), NotNull());
-  EXPECT_TRUE(GetMobileToggleButton()->GetVisible());
-  EXPECT_FALSE(GetMobileToggleButton()->GetEnabled());
-  EXPECT_FALSE(GetMobileToggleButton()->GetIsOn());
+  CheckMobileToggleButtonStatus(/*enabled=*/false, /*toggled_on=*/false);
   EXPECT_EQ(
       l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_INITIALIZING_CELLULAR),
       GetMobileStatusMessage()->label()->GetText());
@@ -927,9 +952,7 @@
   ASSERT_THAT(GetMobileSubHeader(), NotNull());
   EXPECT_EQ(l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_NO_MOBILE_NETWORKS),
             GetMobileStatusMessage()->label()->GetText());
-  EXPECT_TRUE(GetMobileToggleButton()->GetEnabled());
-  EXPECT_TRUE(GetMobileToggleButton()->GetIsOn());
-  EXPECT_TRUE(GetMobileToggleButton()->GetVisible());
+  CheckMobileToggleButtonStatus(/*enabled=*/true, /*toggled_on=*/true);
 
   // No message is shown when there are available networks.
   cros_network()->AddNetworkAndDevice(
@@ -938,9 +961,7 @@
           ConnectionStateType::kConnected));
 
   EXPECT_THAT(GetMobileStatusMessage(), IsNull());
-  EXPECT_TRUE(GetMobileToggleButton()->GetEnabled());
-  EXPECT_TRUE(GetMobileToggleButton()->GetIsOn());
-  EXPECT_TRUE(GetMobileToggleButton()->GetVisible());
+  CheckMobileToggleButtonStatus(/*enabled=*/true, /*toggled_on=*/true);
 
   // Message shown again when list is empty.
   cros_network()->ClearNetworksAndDevices();
@@ -949,15 +970,17 @@
   ASSERT_THAT(GetMobileStatusMessage(), NotNull());
   EXPECT_EQ(l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_NO_MOBILE_NETWORKS),
             GetMobileStatusMessage()->label()->GetText());
-  EXPECT_TRUE(GetMobileToggleButton()->GetVisible());
+  if (IsQsRevampEnabled()) {
+    EXPECT_TRUE(GetQsMobileToggleButton()->GetVisible());
+  } else {
+    EXPECT_TRUE(GetMobileToggleButton()->GetVisible());
+  }
 
   // No message is shown when inhibited.
   properties->inhibit_reason = InhibitReason::kResettingEuiccMemory;
   cros_network()->SetDeviceProperties(properties.Clone());
   EXPECT_THAT(GetMobileStatusMessage(), IsNull());
-  EXPECT_FALSE(GetMobileToggleButton()->GetEnabled());
-  EXPECT_TRUE(GetMobileToggleButton()->GetIsOn());
-  EXPECT_TRUE(GetMobileToggleButton()->GetVisible());
+  CheckMobileToggleButtonStatus(/*enabled=*/false, /*toggled_on=*/true);
 
   // Uninhibit the device.
   properties->inhibit_reason = InhibitReason::kNotInhibited;
@@ -967,9 +990,7 @@
   ASSERT_THAT(GetMobileStatusMessage(), NotNull());
   EXPECT_EQ(l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_NO_MOBILE_NETWORKS),
             GetMobileStatusMessage()->label()->GetText());
-  EXPECT_TRUE(GetMobileToggleButton()->GetEnabled());
-  EXPECT_TRUE(GetMobileToggleButton()->GetIsOn());
-  EXPECT_TRUE(GetMobileToggleButton()->GetVisible());
+  CheckMobileToggleButtonStatus(/*enabled=*/true, /*toggled_on=*/true);
 
   // When device is in disabling message is shown.
   properties->device_state = DeviceStateType::kDisabling;
@@ -979,9 +1000,7 @@
   EXPECT_EQ(
       l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_NETWORK_MOBILE_DISABLING),
       GetMobileStatusMessage()->label()->GetText());
-  EXPECT_FALSE(GetMobileToggleButton()->GetEnabled());
-  EXPECT_FALSE(GetMobileToggleButton()->GetIsOn());
-  EXPECT_TRUE(GetMobileToggleButton()->GetVisible());
+  CheckMobileToggleButtonStatus(/*enabled=*/false, /*toggled_on=*/false);
 
   properties->device_state = DeviceStateType::kDisabled;
   cros_network()->SetDeviceProperties(properties.Clone());
@@ -998,9 +1017,7 @@
         GetMobileStatusMessage()->label()->GetText());
   }
 
-  EXPECT_TRUE(GetMobileToggleButton()->GetEnabled());
-  EXPECT_FALSE(GetMobileToggleButton()->GetIsOn());
-  EXPECT_TRUE(GetMobileToggleButton()->GetVisible());
+  CheckMobileToggleButtonStatus(/*enabled=*/true, /*toggled_on=*/false);
 
   // The toggle is not enabled, the cellular device SIM is locked, and user
   // cannot open the settings page.
@@ -1011,7 +1028,11 @@
   properties->sim_lock_status->lock_type = "lock";
   cros_network()->SetDeviceProperties(properties.Clone());
 
-  EXPECT_FALSE(GetMobileToggleButton()->GetEnabled());
+  if (IsQsRevampEnabled()) {
+    EXPECT_FALSE(GetQsMobileToggleButton()->GetEnabled());
+  } else {
+    EXPECT_FALSE(GetMobileToggleButton()->GetEnabled());
+  }
 }
 
 TEST_P(NetworkListViewControllerTest, HasCorrectTetherStatusMessage) {
@@ -1027,8 +1048,7 @@
 
   ASSERT_THAT(GetMobileStatusMessage(), NotNull());
   ASSERT_THAT(GetMobileSubHeader(), NotNull());
-  EXPECT_TRUE(GetMobileToggleButton()->GetEnabled());
-  EXPECT_TRUE(GetMobileToggleButton()->GetIsOn());
+  CheckMobileToggleButtonStatus(/*enabled=*/true, /*toggled_on=*/true);
   EXPECT_EQ(
       l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_NO_MOBILE_DEVICES_FOUND),
       GetMobileStatusMessage()->label()->GetText());
@@ -1037,8 +1057,7 @@
   properties->device_state = DeviceStateType::kUninitialized;
   cros_network()->SetDeviceProperties(properties.Clone());
   SetBluetoothAdapterState(BluetoothSystemState::kEnabling);
-  EXPECT_FALSE(GetMobileToggleButton()->GetEnabled());
-  EXPECT_TRUE(GetMobileToggleButton()->GetIsOn());
+  CheckMobileToggleButtonStatus(/*enabled=*/false, /*toggled_on=*/true);
   ASSERT_THAT(GetMobileStatusMessage(), NotNull());
   EXPECT_EQ(
       l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_INITIALIZING_CELLULAR),
@@ -1046,8 +1065,7 @@
 
   // Set Bluetooth device to disabling.
   SetBluetoothAdapterState(BluetoothSystemState::kDisabling);
-  EXPECT_TRUE(GetMobileToggleButton()->GetEnabled());
-  EXPECT_FALSE(GetMobileToggleButton()->GetIsOn());
+  CheckMobileToggleButtonStatus(/*enabled=*/true, /*toggled_on=*/false);
   ASSERT_THAT(GetMobileStatusMessage(), NotNull());
   EXPECT_EQ(l10n_util::GetStringUTF16(
                 IDS_ASH_STATUS_TRAY_ENABLING_MOBILE_ENABLES_BLUETOOTH),
@@ -1056,8 +1074,7 @@
   // Simulate login as secondary user and disable Bluetooth device.
   LoginAsSecondaryUser();
   SetBluetoothAdapterState(BluetoothSystemState::kDisabled);
-  EXPECT_FALSE(GetMobileToggleButton()->GetEnabled());
-  EXPECT_FALSE(GetMobileToggleButton()->GetIsOn());
+  CheckMobileToggleButtonStatus(/*enabled=*/false, /*toggled_on=*/false);
   ASSERT_THAT(GetMobileStatusMessage(), NotNull());
   EXPECT_EQ(l10n_util::GetStringUTF16(
                 IDS_ASH_STATUS_TRAY_ENABLING_MOBILE_ENABLES_BLUETOOTH),
diff --git a/ash/system/phonehub/phone_hub_ui_controller.cc b/ash/system/phonehub/phone_hub_ui_controller.cc
index f85dfc39..bf714768 100644
--- a/ash/system/phonehub/phone_hub_ui_controller.cc
+++ b/ash/system/phonehub/phone_hub_ui_controller.cc
@@ -219,6 +219,14 @@
   if (feature_status == FeatureStatus::kEnabledButDisconnected)
     phone_hub_manager_->GetConnectionScheduler()->ScheduleConnectionNow();
 
+  if (features::IsEcheNetworkConnectionStateEnabled() &&
+      feature_status == FeatureStatus::kEnabledAndConnected) {
+    if (phone_hub_manager_->GetEcheConnectionStatusHandler()) {
+      phone_hub_manager_->GetEcheConnectionStatusHandler()
+          ->CheckConnectionStatusForUi();
+    }
+  }
+
   phone_hub_manager_->GetBrowserTabsModelProvider()->TriggerRefresh();
   RecordStatusOnBubbleOpened();
 
diff --git a/ash/system/phonehub/phone_hub_ui_controller_unittest.cc b/ash/system/phonehub/phone_hub_ui_controller_unittest.cc
index b8eb5e5..a02926e 100644
--- a/ash/system/phonehub/phone_hub_ui_controller_unittest.cc
+++ b/ash/system/phonehub/phone_hub_ui_controller_unittest.cc
@@ -3,7 +3,9 @@
 // found in the LICENSE file.
 
 #include "ash/system/phonehub/phone_hub_ui_controller.h"
+#include <memory>
 
+#include "ash/constants/ash_features.h"
 #include "ash/shell.h"
 #include "ash/system/eche/eche_tray.h"
 #include "ash/system/phonehub/phone_hub_view_ids.h"
@@ -45,7 +47,8 @@
 
   // AshTestBase:
   void SetUp() override {
-    feature_list_.InitWithFeatures({features::kEcheSWA}, {});
+    feature_list_.InitWithFeatures(
+        {features::kEcheSWA, features::kEcheNetworkConnectionState}, {});
 
     AshTestBase::SetUp();
 
@@ -91,6 +94,11 @@
         phone_status_model);
   }
 
+  void SetEcheConnectionStatusHandler(
+      eche_app::EcheConnectionStatusHandler* handler) {
+    phone_hub_manager_.set_eche_connection_hander(handler);
+  }
+
   std::unique_ptr<PhoneHubContentView> OpenBubbleAndCreateView() {
     controller_->HandleBubbleOpened();
     return controller_->CreateContentView(/*delegate=*/nullptr);
@@ -281,6 +289,21 @@
                                phone_hub_metrics::Screen::kPhoneConnected, 1);
 }
 
+TEST_F(PhoneHubUiControllerTest, PhoneConnected_HasConnectionHandler) {
+  base::HistogramTester histograms;
+  SetPhoneStatusModel(phonehub::CreateFakePhoneStatusModel());
+  SetEcheConnectionStatusHandler(
+      std::make_unique<eche_app::EcheConnectionStatusHandler>().get());
+  GetFeatureStatusProvider()->SetStatus(FeatureStatus::kEnabledAndConnected);
+  EXPECT_EQ(PhoneHubUiController::UiState::kPhoneConnected,
+            controller_->ui_state());
+
+  auto content_view = OpenBubbleAndCreateView();
+  EXPECT_EQ(kPhoneConnectedView, content_view->GetID());
+  histograms.ExpectBucketCount(kScreenOnOpenedMetric,
+                               phone_hub_metrics::Screen::kPhoneConnected, 1);
+}
+
 TEST_F(PhoneHubUiControllerTest, UnavailableScreenLocked) {
   base::HistogramTester histograms;
   GetFeatureStatusProvider()->SetStatus(FeatureStatus::kLockOrSuspended);
diff --git a/ash/system/power/power_prefs.cc b/ash/system/power/power_prefs.cc
index 2b6d33e2..16e1a2a4 100644
--- a/ash/system/power/power_prefs.cc
+++ b/ash/system/power/power_prefs.cc
@@ -129,7 +129,7 @@
 double GetAdaptiveChargingMinProbability() {
   // An AdaptiveCharging decision is considered to be reliable if the inference
   // score is higher than this number.
-  constexpr double kDefaultAdaptiveChargingMinProbability = 0.2;
+  constexpr double kDefaultAdaptiveChargingMinProbability = 0.35;
 
   return base::GetFieldTrialParamByFeatureAsDouble(
       ash::features::kAdaptiveCharging, "adaptive_charging_min_probability",
diff --git a/ash/system/privacy_hub/microphone_privacy_switch_controller.cc b/ash/system/privacy_hub/microphone_privacy_switch_controller.cc
index be06b7a..117ebc7 100644
--- a/ash/system/privacy_hub/microphone_privacy_switch_controller.cc
+++ b/ash/system/privacy_hub/microphone_privacy_switch_controller.cc
@@ -191,10 +191,8 @@
 
   if (visible) {
     if (mic_muted_by_mute_switch_) {
-      if (!features::IsPrivacyIndicatorsEnabled()) {
-        privacy_hub_notification_controller->ShowHardwareSwitchNotification(
-            SensorDisabledNotificationDelegate::Sensor::kMicrophone);
-      }
+      privacy_hub_notification_controller->ShowHardwareSwitchNotification(
+          SensorDisabledNotificationDelegate::Sensor::kMicrophone);
     } else {
       privacy_hub_notification_controller->ShowSoftwareSwitchNotification(
           SensorDisabledNotificationDelegate::Sensor::kMicrophone);
@@ -218,10 +216,8 @@
       Shell::Get()->system_notification_controller()->privacy_hub();
 
   if (mic_muted_by_mute_switch_) {
-    if (!features::IsPrivacyIndicatorsEnabled()) {
-      privacy_hub_notification_controller->UpdateHardwareSwitchNotification(
-          SensorDisabledNotificationDelegate::Sensor::kMicrophone);
-    }
+    privacy_hub_notification_controller->UpdateHardwareSwitchNotification(
+        SensorDisabledNotificationDelegate::Sensor::kMicrophone);
   } else {
     privacy_hub_notification_controller->UpdateSoftwareSwitchNotification(
         SensorDisabledNotificationDelegate::Sensor::kMicrophone);
diff --git a/ash/system/privacy_hub/microphone_privacy_switch_controller_unittest.cc b/ash/system/privacy_hub/microphone_privacy_switch_controller_unittest.cc
index af5d061..244612d 100644
--- a/ash/system/privacy_hub/microphone_privacy_switch_controller_unittest.cc
+++ b/ash/system/privacy_hub/microphone_privacy_switch_controller_unittest.cc
@@ -490,7 +490,7 @@
   EXPECT_FALSE(GetSWSwitchNotification());
   message_center::Notification* notification = GetHWSwitchNotification();
 
-  if (IsVideoConferenceEnabled() || IsPrivacyIndicatorsEnabled()) {
+  if (IsVideoConferenceEnabled()) {
     EXPECT_FALSE(notification);
     return;
   }
@@ -518,7 +518,7 @@
 
   message_center::Notification* notification = GetHWSwitchNotification();
 
-  if (IsVideoConferenceEnabled() || IsPrivacyIndicatorsEnabled()) {
+  if (IsVideoConferenceEnabled()) {
     EXPECT_FALSE(notification);
     return;
   }
@@ -552,13 +552,6 @@
                   IDS_MICROPHONE_MUTED_NOTIFICATION_ACTION_BUTTON),
               notification->buttons()[0].title);
   }
-
-  if (IsVideoConferenceEnabled() || IsPrivacyIndicatorsEnabled()) {
-    // The rest of the test tests interaction between the hardware and software
-    // notifications, and the hardware notification is not shown when Video
-    // Conference or Privacy Indicator is enabled.
-    return;
-  }
   // Toggle microphone mute switch and verify that new notification appears
   // with a "Learn more" button.
   SetMicrophoneMuteSwitchState(/*muted=*/true);
@@ -566,7 +559,7 @@
   EXPECT_FALSE(GetSWSwitchNotification());
 
   notification = GetHWSwitchNotification();
-  if (IsVideoConferenceEnabled() || IsPrivacyIndicatorsEnabled()) {
+  if (IsVideoConferenceEnabled()) {
     EXPECT_FALSE(notification);
   } else {
     ASSERT_TRUE(notification);
@@ -596,23 +589,20 @@
     EXPECT_TRUE(GetSWSwitchPopupNotification());
   }
 
-  // Toggle microphone mute hardware switch and verify that toggling mute switch
-  // creates new hardware switch pop up notification and the software switch
+  // Toggle microphone mute switch and verify that toggling mute switch creates
+  // new hardware switch pop up notification and the software switch
   // notification is removed.
   SetMicrophoneMuteSwitchState(/*muted=*/true);
 
-  if (IsVideoConferenceEnabled() || IsPrivacyIndicatorsEnabled()) {
+  if (IsVideoConferenceEnabled()) {
     EXPECT_FALSE(GetHWSwitchNotification());
     EXPECT_FALSE(GetHWSwitchNotification());
-    // The rest of the test is not relevant as the hardware switch notification
-    // is not shown.
-    return;
+  } else {
+    // Verify the notification popup is shown.
+    EXPECT_TRUE(GetHWSwitchNotification());
+    EXPECT_TRUE(GetHWSwitchNotification());
   }
 
-  // Verify the notification popup is shown.
-  EXPECT_TRUE(GetHWSwitchNotification());
-  EXPECT_TRUE(GetHWSwitchNotification());
-
   // The software switch notification is instantly hidden.
   EXPECT_FALSE(GetSWSwitchNotification());
 
@@ -631,7 +621,7 @@
   SetMicrophoneMuteSwitchState(/*muted=*/true);
   LaunchApp(u"junior");
 
-  if (IsVideoConferenceEnabled() || IsPrivacyIndicatorsEnabled()) {
+  if (IsVideoConferenceEnabled()) {
     EXPECT_FALSE(GetHWSwitchNotification());
     EXPECT_FALSE(GetHWSwitchNotification());
   } else {
@@ -663,7 +653,7 @@
   // Add another audio input stream, and verify the notification popup shows.
   LaunchApp(u"junior1");
 
-  if (IsVideoConferenceEnabled() || IsPrivacyIndicatorsEnabled()) {
+  if (IsVideoConferenceEnabled()) {
     EXPECT_FALSE(GetHWSwitchNotification());
     EXPECT_FALSE(GetHWSwitchPopupNotification());
   } else {
@@ -680,7 +670,7 @@
   CloseApp(u"junior1");
 
   // Verify that notification popup is not reshown.
-  if (IsVideoConferenceEnabled() || IsPrivacyIndicatorsEnabled()) {
+  if (IsVideoConferenceEnabled()) {
     EXPECT_FALSE(GetHWSwitchNotification());
   } else {
     EXPECT_TRUE(GetHWSwitchNotification());
@@ -689,7 +679,7 @@
 
   // Adding another stream shows a popup again.
   LaunchApp(u"rose");
-  if (IsVideoConferenceEnabled() || IsPrivacyIndicatorsEnabled()) {
+  if (IsVideoConferenceEnabled()) {
     EXPECT_FALSE(GetHWSwitchNotification());
     EXPECT_FALSE(GetHWSwitchPopupNotification());
   } else {
@@ -783,7 +773,7 @@
 
   // Toggle the hw switch.
   SetMicrophoneMuteSwitchState(/*muted=*/true);
-  if (IsVideoConferenceEnabled() || IsPrivacyIndicatorsEnabled()) {
+  if (IsVideoConferenceEnabled()) {
     EXPECT_FALSE(GetHWSwitchNotification());
     EXPECT_FALSE(GetHWSwitchPopupNotification());
   } else {
@@ -862,7 +852,7 @@
   LaunchApp(app1);
 
   notification_ptr = GetHWSwitchNotification();
-  if (IsVideoConferenceEnabled() || IsPrivacyIndicatorsEnabled()) {
+  if (IsVideoConferenceEnabled()) {
     EXPECT_FALSE(notification_ptr);
   } else {
     ASSERT_TRUE(notification_ptr);
@@ -877,7 +867,7 @@
   CloseApp(app2);
 
   notification_ptr = GetHWSwitchNotification();
-  if (IsVideoConferenceEnabled() || IsPrivacyIndicatorsEnabled()) {
+  if (IsVideoConferenceEnabled()) {
     EXPECT_FALSE(notification_ptr);
   } else {
     ASSERT_TRUE(notification_ptr);
diff --git a/ash/system/privacy_hub/privacy_hub_notification_controller.cc b/ash/system/privacy_hub/privacy_hub_notification_controller.cc
index 9e79543..aef3655 100644
--- a/ash/system/privacy_hub/privacy_hub_notification_controller.cc
+++ b/ash/system/privacy_hub/privacy_hub_notification_controller.cc
@@ -172,11 +172,6 @@
 
 void PrivacyHubNotificationController::ShowHardwareSwitchNotification(
     const Sensor sensor) {
-  if (features::IsPrivacyIndicatorsEnabled() ||
-      features::IsVideoConferenceEnabled()) {
-    return;
-  }
-
   switch (sensor) {
     case Sensor::kMicrophone: {
       RemoveSensor(sensor);
@@ -213,10 +208,6 @@
 
 void PrivacyHubNotificationController::UpdateHardwareSwitchNotification(
     const Sensor sensor) {
-  if (features::IsPrivacyIndicatorsEnabled() ||
-      features::IsVideoConferenceEnabled()) {
-    return;
-  }
   switch (sensor) {
     case Sensor::kMicrophone: {
       microphone_hw_switch_notification_->Update();
diff --git a/ash/system/status_area_widget.cc b/ash/system/status_area_widget.cc
index b015ada..9caf0ce 100644
--- a/ash/system/status_area_widget.cc
+++ b/ash/system/status_area_widget.cc
@@ -15,6 +15,7 @@
 #include "ash/projector/projector_annotation_tray.h"
 #include "ash/public/cpp/shelf_config.h"
 #include "ash/session/session_controller_impl.h"
+#include "ash/shelf/drag_handle.h"
 #include "ash/shelf/shelf.h"
 #include "ash/shelf/shelf_layout_manager.h"
 #include "ash/shelf/shelf_widget.h"
@@ -276,6 +277,21 @@
 
   status_area_widget_delegate_->OnStatusAreaCollapseStateChanged(
       collapse_state_);
+
+  bool overlap =
+      shelf_->shelf_widget()->GetDragHandle()->GetBoundsInScreen().Intersects(
+          status_area_widget_delegate_->GetBoundsInScreen());
+
+  if (collapse_state_ == CollapseState::EXPANDED && overlap) {
+    // Hide the drag handle if the status_area_widget_delegate_ overlaps
+    // expected drag handle bounds. Otherwise show the drag handle.
+    shelf_->shelf_widget()->GetDragHandle()->HideDragHandleNudge(
+        contextual_tooltip::DismissNudgeReason::kOther,
+        /*animate*/ false);
+    shelf_->shelf_widget()->GetDragHandle()->SetVisible(false);
+  } else if (collapse_state_ == CollapseState::COLLAPSED) {
+    shelf_->shelf_widget()->GetDragHandle()->SetVisible(true);
+  }
 }
 
 void StatusAreaWidget::LogVisiblePodCountMetric() {
diff --git a/ash/user_education/user_education_controller_unittest.cc b/ash/user_education/user_education_controller_unittest.cc
index f27dffec..2d89d85c 100644
--- a/ash/user_education/user_education_controller_unittest.cc
+++ b/ash/user_education/user_education_controller_unittest.cc
@@ -16,35 +16,55 @@
 // UserEducationControllerTest -------------------------------------------------
 
 // Base class for tests of the `UserEducationController` parameterized by
-// whether the Welcome Tour feature is enabled.
+// whether user education features are enabled.
 class UserEducationControllerTest
     : public AshTestBase,
-      public testing::WithParamInterface</*welcome_tour_enabled=*/bool> {
+      public testing::WithParamInterface<
+          std::tuple</*capture_mode_tour_enabled=*/bool,
+                     /*holding_space_tour_enabled=*/bool,
+                     /*welcome_tour_enabled=*/bool>> {
  public:
   UserEducationControllerTest() {
     std::vector<base::test::FeatureRef> enabled_features;
     std::vector<base::test::FeatureRef> disabled_features;
+    (IsCaptureModeTourEnabled() ? enabled_features : disabled_features)
+        .emplace_back(features::kCaptureModeTour);
+    (IsHoldingSpaceTourEnabled() ? enabled_features : disabled_features)
+        .emplace_back(features::kHoldingSpaceTour);
     (IsWelcomeTourEnabled() ? enabled_features : disabled_features)
         .emplace_back(features::kWelcomeTour);
     scoped_feature_list_.InitWithFeatures(enabled_features, disabled_features);
   }
 
+  // Returns whether the Capture Mode Tour is enabled given test
+  // parameterization.
+  bool IsCaptureModeTourEnabled() const { return std::get<0>(GetParam()); }
+
+  // Returns whether the Holding Space Tour is enabled given test
+  // parameterization.
+  bool IsHoldingSpaceTourEnabled() const { return std::get<1>(GetParam()); }
+
   // Returns whether the Welcome Tour is enabled given test parameterization.
-  bool IsWelcomeTourEnabled() const { return GetParam(); }
+  bool IsWelcomeTourEnabled() const { return std::get<2>(GetParam()); }
 
  private:
   base::test::ScopedFeatureList scoped_feature_list_;
 };
 
-INSTANTIATE_TEST_SUITE_P(All,
-                         UserEducationControllerTest,
-                         /*welcome_tour_enabled=*/testing::Bool());
+INSTANTIATE_TEST_SUITE_P(
+    All,
+    UserEducationControllerTest,
+    testing::Combine(/*capture_mode_tour_enabled=*/testing::Bool(),
+                     /*holding_space_tour_enabled=*/testing::Bool(),
+                     /*welcome_tour_enabled=*/testing::Bool()));
 
 // Tests -----------------------------------------------------------------------
 
-// Verifies that the controller exists if and only if Welcome Tour is enabled.
+// Verifies that the controller exists iff user education features are enabled.
 TEST_P(UserEducationControllerTest, Exists) {
-  EXPECT_EQ(!!UserEducationController::Get(), IsWelcomeTourEnabled());
+  EXPECT_EQ(!!UserEducationController::Get(), IsCaptureModeTourEnabled() ||
+                                                  IsHoldingSpaceTourEnabled() ||
+                                                  IsWelcomeTourEnabled());
 }
 
 }  // namespace ash
diff --git a/base/BUILD.gn b/base/BUILD.gn
index e3518fb..b49d877 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -4552,7 +4552,6 @@
       "test/android/javatests/src/org/chromium/base/test/util/Manual.java",
       "test/android/javatests/src/org/chromium/base/test/util/Matchers.java",
       "test/android/javatests/src/org/chromium/base/test/util/MaxAndroidSdkLevel.java",
-      "test/android/javatests/src/org/chromium/base/test/util/MetricsUtils.java",
       "test/android/javatests/src/org/chromium/base/test/util/MinAndroidSdkLevel.java",
       "test/android/javatests/src/org/chromium/base/test/util/PackageManagerWrapper.java",
       "test/android/javatests/src/org/chromium/base/test/util/PayloadCallbackHelper.java",
diff --git a/base/android/javatests/src/org/chromium/base/metrics/RecordHistogramTest.java b/base/android/javatests/src/org/chromium/base/metrics/RecordHistogramTest.java
index 605046e..7c9071d 100644
--- a/base/android/javatests/src/org/chromium/base/metrics/RecordHistogramTest.java
+++ b/base/android/javatests/src/org/chromium/base/metrics/RecordHistogramTest.java
@@ -13,7 +13,6 @@
 
 import org.chromium.base.library_loader.LibraryLoader;
 import org.chromium.base.test.BaseJUnit4ClassRunner;
-import org.chromium.base.test.util.MetricsUtils.HistogramDelta;
 
 /**
  * Tests for the Java API for recording UMA histograms.
@@ -193,4 +192,35 @@
         Assert.assertEquals(0, oneCount.getDelta());
         Assert.assertEquals(1, twoCount.getDelta());
     }
+
+    /**
+     * Helper class that snapshots the given bucket of the given UMA histogram on its creation,
+     * allowing to inspect the number of samples recorded during its lifetime.
+     */
+    private static class HistogramDelta {
+        private final String mHistogram;
+        private final int mSampleValue;
+
+        private final int mInitialCount;
+
+        private int get() {
+            return RecordHistogram.getHistogramValueCountForTesting(mHistogram, mSampleValue);
+        }
+
+        /**
+         * Snapshots the given bucket of the given histogram.
+         * @param histogram name of the histogram to snapshot
+         * @param sampleValue the bucket that contains this value will be snapshot
+         */
+        public HistogramDelta(String histogram, int sampleValue) {
+            mHistogram = histogram;
+            mSampleValue = sampleValue;
+            mInitialCount = get();
+        }
+
+        /** Returns the number of samples of the snapshot bucket recorded since creation */
+        public int getDelta() {
+            return get() - mInitialCount;
+        }
+    }
 }
diff --git a/base/base_switches.cc b/base/base_switches.cc
index 88613473..8e0a485 100644
--- a/base/base_switches.cc
+++ b/base/base_switches.cc
@@ -132,10 +132,7 @@
 const char kDisableUsbKeyboardDetect[]      = "disable-usb-keyboard-detect";
 #endif
 
-// TODO(crbug.com/1052397): Revisit the macro expression once build flag switch
-// of lacros-chrome is complete.
-#if BUILDFLAG(IS_LINUX) && !BUILDFLAG(IS_CHROMEOS_ASH) && \
-    !BUILDFLAG(IS_CHROMEOS_LACROS)
+#if BUILDFLAG(IS_LINUX)
 // The /dev/shm partition is too small in certain VM environments, causing
 // Chrome to fail or crash (see http://crbug.com/715363). Use this flag to
 // work-around this issue (a temporary directory will always be used to create
diff --git a/base/base_switches.h b/base/base_switches.h
index 4f5dd6b..cead11f 100644
--- a/base/base_switches.h
+++ b/base/base_switches.h
@@ -41,10 +41,7 @@
 extern const char kDisableUsbKeyboardDetect[];
 #endif
 
-// TODO(crbug.com/1052397): Revisit the macro expression once build flag switch
-// of lacros-chrome is complete.
-#if BUILDFLAG(IS_LINUX) && !BUILDFLAG(IS_CHROMEOS_ASH) && \
-    !BUILDFLAG(IS_CHROMEOS_LACROS)
+#if BUILDFLAG(IS_LINUX)
 extern const char kDisableDevShmUsage[];
 #endif
 
diff --git a/base/task/task_features.cc b/base/task/task_features.cc
index dad6e0ae..b5861fe 100644
--- a/base/task/task_features.cc
+++ b/base/task/task_features.cc
@@ -39,10 +39,6 @@
              "RemoveCanceledTasksInTaskQueue2",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
-BASE_FEATURE(kAlwaysAbandonScheduledTask,
-             "AlwaysAbandonScheduledTask",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
 BASE_FEATURE(kDelayFirstWorkerWake,
              "DelayFirstWorkerWake",
              base::FEATURE_DISABLED_BY_DEFAULT);
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/MetricsUtils.java b/base/test/android/javatests/src/org/chromium/base/test/util/MetricsUtils.java
deleted file mode 100644
index 8283ccf7..0000000
--- a/base/test/android/javatests/src/org/chromium/base/test/util/MetricsUtils.java
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright 2014 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.base.test.util;
-
-import org.chromium.base.metrics.RecordHistogram;
-
-/**
- * Helpers for testing UMA metrics.
- */
-public class MetricsUtils {
-    /**
-     *
-     * Helper class that snapshots the given bucket of the given UMA histogram on its creation,
-     * allowing to inspect the number of samples recorded during its lifetime.
-     *
-     * @deprecated Use {@link HistogramWatcher} instead.
-     */
-    @Deprecated
-    public static class HistogramDelta {
-        private final String mHistogram;
-        private final int mSampleValue;
-
-        private final int mInitialCount;
-
-        private int get() {
-            return RecordHistogram.getHistogramValueCountForTesting(mHistogram, mSampleValue);
-        }
-
-        /**
-         * Snapshots the given bucket of the given histogram.
-         * @param histogram name of the histogram to snapshot
-         * @param sampleValue the bucket that contains this value will be snapshot
-         */
-        public HistogramDelta(String histogram, int sampleValue) {
-            mHistogram = histogram;
-            mSampleValue = sampleValue;
-            mInitialCount = get();
-        }
-
-        /** Returns the number of samples of the snapshot bucket recorded since creation */
-        public int getDelta() {
-            return get() - mInitialCount;
-        }
-    }
-}
diff --git a/base/timer/timer.cc b/base/timer/timer.cc
index 8f00af2..c7d1bf3 100644
--- a/base/timer/timer.cc
+++ b/base/timer/timer.cc
@@ -21,29 +21,6 @@
 namespace base {
 namespace internal {
 
-namespace {
-
-// Cache of the state of the kAlwaysAbandonScheduledTask feature. This avoids
-// the need to constantly query its enabled state through
-// FeatureList::IsEnabled().
-bool g_is_always_abandon_scheduled_task_enabled = true;
-
-}  // namespace
-
-// static
-void TimerBase::InitializeFeatures() {
-  // Since kAlwaysAbandonScheduledTask is not constexpr (forbidden for
-  // Features), it cannot be used to initialize
-  // |g_is_always_abandon_scheduled_task_enabled| at compile time. At least
-  // DCHECK that its initial value matches the default value of the feature
-  // here.
-  DCHECK_EQ(
-      g_is_always_abandon_scheduled_task_enabled,
-      kAlwaysAbandonScheduledTask.default_state == FEATURE_ENABLED_BY_DEFAULT);
-  g_is_always_abandon_scheduled_task_enabled =
-      FeatureList::IsEnabled(kAlwaysAbandonScheduledTask);
-}
-
 TimerBase::TimerBase(const Location& posted_from) : posted_from_(posted_from) {
   // It is safe for the timer to be created on a different thread/sequence than
   // the one from which the timer APIs are called. The first call to the
@@ -59,19 +36,6 @@
 
 bool TimerBase::IsRunning() const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-
-  // When the `kAlwaysAbandonScheduledTask` feature is enabled, checking
-  // `delayed_task_handle_.IsValid()` is sufficient to determine if the
-  // timer is running. When the feature is disabled, the delayed task
-  // is not abandoned when the timer is stopped and the handle remains
-  // valid, so it's necessary to also check `is_running_` (set to false
-  // from `Stop()`).
-  //
-  // TODO(crbug.com/1262205): Remove the `is_running_` check once the
-  // "AlwaysAbandonScheduledTask" feature is launched.
-  if (!is_running_)
-    return false;
-
   return delayed_task_handle_.IsValid();
 }
 
@@ -89,7 +53,6 @@
 void TimerBase::Stop() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
-  is_running_ = false;
   AbandonScheduledTask();
 
   OnStop();
@@ -133,17 +96,7 @@
 }
 
 void DelayTimerBase::AbandonAndStop() {
-  // Note: Stop() is more or less re-implemented here because it cannot be
-  // called without rebinding the |sequence_checker_| to the current sequence
-  // after the call to AbandonScheduledTask().
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-
-  is_running_ = false;
-
-  AbandonScheduledTask();
-
-  OnStop();
-  // No more member accesses here: |this| could be deleted at this point.
+  Stop();
 }
 
 void DelayTimerBase::Reset() {
@@ -151,49 +104,14 @@
 
   EnsureNonNullUserTask();
 
-  if (!g_is_always_abandon_scheduled_task_enabled) {
-    // If there's no pending task, start one up and return.
-    if (!delayed_task_handle_.IsValid()) {
-      ScheduleNewTask(delay_);
-      return;
-    }
-
-    // Set the new |desired_run_time_|.
-    if (delay_ > Microseconds(0))
-      desired_run_time_ = Now() + delay_;
-    else
-      desired_run_time_ = TimeTicks();
-
-    // We can use the existing scheduled task if it arrives before the new
-    // |desired_run_time_|.
-    if (desired_run_time_ >= scheduled_run_time_) {
-      is_running_ = true;
-      return;
-    }
-  }
-
   // We can't reuse the |scheduled_task_|, so abandon it and post a new one.
   AbandonScheduledTask();
   ScheduleNewTask(delay_);
 }
 
-// TODO(1262205): Merge with TimerBase::Stop() once the "always abandon
-// scheduled task" feature is launched.
-void DelayTimerBase::Stop() {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-
-  is_running_ = false;
-  if (g_is_always_abandon_scheduled_task_enabled)
-    AbandonScheduledTask();
-
-  OnStop();
-  // No more member accesses here: |this| could be deleted after Stop() call.
-}
-
 void DelayTimerBase::ScheduleNewTask(TimeDelta delay) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(!delayed_task_handle_.IsValid());
-  is_running_ = true;
 
   // Ignore negative deltas.
   // TODO(pmonette): Fix callers providing negative deltas and ban passing them.
@@ -207,7 +125,7 @@
   delayed_task_handle_ = GetTaskRunner()->PostCancelableDelayedTask(
       base::subtle::PostDelayedTaskPassKey(), posted_from_, timer_callback_,
       delay);
-  scheduled_run_time_ = desired_run_time_ = Now() + delay;
+  desired_run_time_ = Now() + delay;
 }
 
 TimeTicks DelayTimerBase::Now() const {
@@ -219,24 +137,6 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(!delayed_task_handle_.IsValid()) << posted_from_.ToString();
 
-  // The timer may have been stopped.
-  if (!is_running_)
-    return;
-
-  // First check if we need to delay the task because of a new target time.
-  if (desired_run_time_ > scheduled_run_time_) {
-    // Now() can be expensive, so only call it if we know the user has changed
-    // the |desired_run_time_|.
-    TimeTicks now = Now();
-    // Task runner may have called us late anyway, so only post a continuation
-    // task if the |desired_run_time_| is in the future.
-    if (desired_run_time_ > now) {
-      // Post a new task to span the remaining time.
-      ScheduleNewTask(desired_run_time_ - now);
-      return;
-    }
-  }
-
   RunUserTask();
   // No more member accesses here: |this| could be deleted at this point.
 }
@@ -308,6 +208,7 @@
 }
 
 void RepeatingTimer::OnStop() {}
+
 void RepeatingTimer::RunUserTask() {
   // Make a local copy of the task to run in case the task destroy the timer
   // instance.
@@ -346,6 +247,7 @@
 }
 
 void RetainingOneShotTimer::OnStop() {}
+
 void RetainingOneShotTimer::RunUserTask() {
   // Make a local copy of the task to run in case the task destroys the timer
   // instance.
@@ -382,7 +284,6 @@
 void DeadlineTimer::ScheduleNewTask(TimeTicks deadline,
                                     subtle::DelayPolicy delay_policy) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  is_running_ = true;
 
   if (!timer_callback_) {
     timer_callback_ =
@@ -446,7 +347,6 @@
 
 void MetronomeTimer::ScheduleNewTask() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  is_running_ = true;
 
   // The next wake up is scheduled at the next aligned time which is at least
   // `interval_ / 2` after now. `interval_ / 2` is added to avoid playing
diff --git a/base/timer/timer.h b/base/timer/timer.h
index 10fc7c9..afe8302a 100644
--- a/base/timer/timer.h
+++ b/base/timer/timer.h
@@ -88,10 +88,6 @@
 // This class wraps logic shared by all timers.
 class BASE_EXPORT TimerBase {
  public:
-  // Initializes the state of all the timer features. Must be invoked after
-  // FeatureList initialization and while Chrome is still single-threaded.
-  static void InitializeFeatures();
-
   TimerBase(const TimerBase&) = delete;
   TimerBase& operator=(const TimerBase&) = delete;
 
@@ -141,10 +137,6 @@
   // Location in user code.
   Location posted_from_ GUARDED_BY_CONTEXT(sequence_checker_);
 
-  // If true, |user_task_| is scheduled to run sometime in the future.
-  // TODO(1262205): Remove once kAlwaysAbandonScheduledTask is gone.
-  bool is_running_ GUARDED_BY_CONTEXT(sequence_checker_) = false;
-
   // The handle to the posted delayed task.
   DelayedTaskHandle delayed_task_handle_ GUARDED_BY_CONTEXT(sequence_checker_);
 
@@ -170,12 +162,8 @@
   // the timer is not running, this will start it by posting a task.
   virtual void Reset();
 
-  void Stop() override;
-
-  // Abandons the scheduled task (if any) and stops the timer (if running). Use
-  // this instead of Stop() only if the timer will need to be used or destroyed
-  // on another sequence.
-  // TODO(1262205): Remove once kAlwaysAbandonScheduledTask is gone.
+  // DEPRECATED. Call Stop() instead.
+  // TODO(1262205): Remove this method and all callers.
   void AbandonAndStop();
 
   TimeTicks desired_run_time() const {
@@ -199,8 +187,7 @@
   virtual void RunUserTask() = 0;
 
   // Schedules |OnScheduledTaskInvoked()| to run on the current sequence with
-  // the given |delay|. |scheduled_run_time_| and |desired_run_time_| are reset
-  // to Now() + delay.
+  // the given |delay|. |desired_run_time_| is reset to Now() + delay.
   void ScheduleNewTask(TimeDelta delay);
 
   void StartInternal(const Location& posted_from, TimeDelta delay);
@@ -220,17 +207,9 @@
   // Delay requested by user.
   TimeDelta delay_ GUARDED_BY_CONTEXT(sequence_checker_);
 
-  // The time at which the scheduled task is expected to fire. This time can be
-  // null if the task must be run immediately.
-  TimeTicks scheduled_run_time_ GUARDED_BY_CONTEXT(sequence_checker_);
-
   // The desired run time of |user_task_|. The user may update this at any time,
-  // even if their previous request has not run yet. If |desired_run_time_| is
-  // greater than |scheduled_run_time_|, a continuation task will be posted to
-  // wait for the remaining time. This allows us to reuse the pending task so as
-  // not to flood the delayed queues with orphaned tasks when the user code
-  // excessively Stops and Starts the timer. This time can be a "zero" TimeTicks
-  // if the task must be run immediately.
+  // even if their previous request has not run yet. This time can be a "zero"
+  // TimeTicks if the task must be run immediately.
   TimeTicks desired_run_time_ GUARDED_BY_CONTEXT(sequence_checker_);
 
   // The tick clock used to calculate the run time for scheduled tasks.
diff --git a/build/fuchsia/ffx_session.py b/build/fuchsia/ffx_session.py
deleted file mode 100755
index c002842..0000000
--- a/build/fuchsia/ffx_session.py
+++ /dev/null
@@ -1,619 +0,0 @@
-#!/usr/bin/env vpython3
-# Copyright 2022 The Chromium Authors
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-"""A helper tool for running Fuchsia's `ffx`.
-"""
-
-# Enable use of the print() built-in function.
-
-import argparse
-import contextlib
-import errno
-import json
-import logging
-import os
-import re
-import shutil
-import subprocess
-import sys
-import tempfile
-import time
-
-import log_manager
-
-sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__),
-                                             'test')))
-
-from common import SDK_ROOT
-from compatible_utils import get_host_arch, parse_host_port
-
-RUN_SUMMARY_SCHEMA = \
-  'https://fuchsia.dev/schema/ffx_test/run_summary-8d1dd964.json'
-
-
-def get_ffx_path():
-  """Returns the full path to `ffx`."""
-  return os.path.join(SDK_ROOT, 'tools', get_host_arch(), 'ffx')
-
-
-def format_host_port(host, port):
-  """Formats a host name or IP address and port number into a host:port string.
-  """
-  # Wrap `host` in brackets if it looks like an IPv6 address
-  return ('[%s]:%d' if ':' in host else '%s:%d') % (host, port)
-
-
-class FfxRunner():
-  """A helper to run `ffx` commands."""
-
-  def __init__(self, log_manager):
-    self._ffx = get_ffx_path()
-    self._log_manager = log_manager
-
-  def _get_daemon_status(self):
-    """Determines daemon status via `ffx daemon socket`.
-
-    Returns:
-      dict of status of the socket. Status will have a key Running or
-      NotRunning to indicate if the daemon is running.
-    """
-    status = json.loads(
-        self.run_ffx(['--machine', 'json', 'daemon', 'socket'],
-                     check=True,
-                     suppress_repair=True))
-    if status.get('pid') and status.get('pid', {}).get('status', {}):
-      return status['pid']['status']
-    return {'NotRunning': True}
-
-  def _is_daemon_running(self):
-    return 'Running' in self._get_daemon_status()
-
-  def _wait_for_daemon(self, start=True, timeout_seconds=100):
-    """Waits for daemon to reach desired state in a polling loop.
-
-    Sleeps for 5s between polls.
-
-    Args:
-      start: bool. Indicates to wait for daemon to start up. If False,
-        indicates waiting for daemon to die.
-      timeout_seconds: int. Number of seconds to wait for the daemon to reach
-        the desired status.
-    Raises:
-      TimeoutError: if the daemon does not reach the desired state in time.
-    """
-    wanted_status = 'start' if start else 'stop'
-    sleep_period_seconds = 5
-    attempts = int(timeout_seconds / sleep_period_seconds)
-    for i in range(attempts):
-      if self._is_daemon_running() == start:
-        return
-      if i != attempts:
-        logging.info('Waiting for daemon to %s...', wanted_status)
-        time.sleep(sleep_period_seconds)
-
-    raise TimeoutError(f'Daemon did not {wanted_status} in time.')
-
-  def _run_repair_command(self, output):
-    """Scans `output` for a self-repair command to run and, if found, runs it.
-
-    If logging is enabled, `ffx` is asked to emit its own logs to the log
-    directory.
-
-    Returns:
-      True if a repair command was found and ran successfully. False otherwise.
-    """
-    # Check for a string along the lines of:
-    # "Run `ffx doctor --restart-daemon` for further diagnostics."
-    match = re.search('`ffx ([^`]+)`', output)
-    if not match or len(match.groups()) != 1:
-      return False  # No repair command found.
-    args = match.groups()[0].split()
-    # Tell ffx to include the configuration file without prompting in case
-    # logging is enabled.
-    with self.scoped_config('doctor.record_config', 'true'):
-      # If the repair command is `ffx doctor` and logging is enabled, add the
-      # options to emit ffx logs to the logging directory.
-      if len(args) and args[0] == 'doctor' and \
-         self._log_manager.IsLoggingEnabled():
-        args.extend(
-            ('--record', '--output-dir', self._log_manager.GetLogDirectory()))
-      try:
-        self.run_ffx(args, suppress_repair=True)
-        self._wait_for_daemon(start=True)
-      except subprocess.CalledProcessError as cpe:
-        return False  # Repair failed.
-      return True  # Repair succeeded.
-
-  def run_ffx(self, args, check=True, suppress_repair=False):
-    """Runs `ffx` with the given arguments, waiting for it to exit.
-
-    If `ffx` exits with a non-zero exit code, the output is scanned for a
-    recommended repair command (e.g., "Run `ffx doctor --restart-daemon` for
-    further diagnostics."). If such a command is found, it is run and then the
-    original command is retried. This behavior can be suppressed via the
-    `suppress_repair` argument.
-
-    Args:
-      args: A sequence of arguments to ffx.
-      check: If True, CalledProcessError is raised if ffx returns a non-zero
-        exit code.
-      suppress_repair: If True, do not attempt to find and run a repair command.
-    Returns:
-      A string containing combined stdout and stderr.
-    Raises:
-      CalledProcessError if `check` is true.
-    """
-    log_file = self._log_manager.Open('ffx_log') \
-      if self._log_manager.IsLoggingEnabled() else None
-    command = [self._ffx]
-    command.extend(args)
-    logging.debug(command)
-    if log_file:
-      print(command, file=log_file)
-    repair_succeeded = False
-    try:
-      # TODO(grt): Switch to subprocess.run() with encoding='utf-8' when p3 is
-      # supported.
-      process = subprocess.Popen(command,
-                                 stdout=subprocess.PIPE,
-                                 stderr=subprocess.PIPE)
-      stdout_data, stderr_data = process.communicate()
-      stdout_data = stdout_data.decode('utf-8')
-      stderr_data = stderr_data.decode('utf-8')
-      if check and process.returncode != 0:
-        # TODO(grt): Pass stdout and stderr as two args when p2 support is no
-        # longer needed.
-        raise subprocess.CalledProcessError(
-            process.returncode, command, '\n'.join((stdout_data, stderr_data)))
-    except subprocess.CalledProcessError as cpe:
-      if log_file:
-        log_file.write('Process exited with code %d. Output: %s\n' %
-                       (cpe.returncode, cpe.output.strip()))
-      # Let the exception fly unless a repair command is found and succeeds.
-      if suppress_repair or not self._run_repair_command(cpe.output):
-        raise
-      repair_succeeded = True
-
-    # If the original command failed but a repair command was found and
-    # succeeded, try one more time with the original command.
-    if repair_succeeded:
-      return self.run_ffx(args, check, suppress_repair=True)
-
-    stripped_stdout = stdout_data.strip()
-    stripped_stderr = stderr_data.strip()
-    if log_file:
-      if process.returncode != 0 or stripped_stderr:
-        log_file.write('Process exited with code %d.' % process.returncode)
-        if stripped_stderr:
-          log_file.write(' Stderr:\n%s\n' % stripped_stderr)
-        if stripped_stdout:
-          log_file.write(' Stdout:\n%s\n' % stripped_stdout)
-        if not stripped_stderr and not stripped_stdout:
-          log_file.write('\n')
-      elif stripped_stdout:
-        log_file.write('%s\n' % stripped_stdout)
-    logging.debug(
-        'ffx command returned %d with %s%s', process.returncode,
-        ('output "%s"' % stripped_stdout if stripped_stdout else 'no output'),
-        (' and error "%s".' % stripped_stderr if stripped_stderr else '.'))
-    return stdout_data
-
-  def open_ffx(self, args):
-    """Runs `ffx` with some arguments.
-    Args:
-      args: A sequence of arguments to ffx.
-    Returns:
-      A subprocess.Popen object.
-    """
-    log_file = self._log_manager.Open('ffx_log') \
-      if self._log_manager.IsLoggingEnabled() else None
-    command = [self._ffx]
-    command.extend(args)
-    logging.debug(command)
-    if log_file:
-      print(command, file=log_file)
-    try:
-      # TODO(grt): Add encoding='utf-8' when p3 is supported.
-      return subprocess.Popen(command,
-                              stdin=open(os.devnull, 'r'),
-                              stdout=subprocess.PIPE,
-                              stderr=subprocess.STDOUT)
-    except:
-      logging.exception('Failed to open ffx')
-      if log_file:
-        print('Exception caught while opening ffx: %s' % str(sys.exc_info[1]))
-      raise
-
-  @contextlib.contextmanager
-  def scoped_config(self, name, value):
-    """Temporarily overrides `ffx` configuration.
-
-    Args:
-      name: The name of the property to set.
-      value: The value to associate with `name`.
-
-    Returns:
-      Yields nothing. Restores the previous value upon exit.
-    """
-    assert value is not None
-    # Cache the current value.
-    old_value = None
-    try:
-      old_value = self.run_ffx(['config', 'get', name]).strip()
-    except subprocess.CalledProcessError as cpe:
-      if cpe.returncode != 2:
-        raise  # The failure was for something other than value not found.
-    # Set the new value if it is different.
-    if value != old_value:
-      self.run_ffx(['config', 'set', name, value])
-    try:
-      yield None
-    finally:
-      if value == old_value:
-        return  # There is no need to restore an old value.
-      # Clear the new value.
-      self.run_ffx(['config', 'remove', name])
-      if old_value is None:
-        return
-      # Did removing the new value restore the original value on account of it
-      # either being the default or being set in a different scope?
-      if (self.run_ffx(['config', 'get', name],
-                       check=False).strip() == old_value):
-        return
-      # If not, explicitly set the original value.
-      self.run_ffx(['config', 'set', name, old_value])
-
-  def list_targets(self):
-    """Returns the (possibly empty) list of targets known to ffx.
-
-    Returns:
-      The list of targets parsed from the JSON output of `ffx target list`.
-    """
-    json_targets = self.run_ffx(['target', 'list', '-f', 'json'])
-    if not json_targets:
-      return []
-    try:
-      return json.loads(json_targets)
-    except ValueError:
-      # TODO(grt): Change to json.JSONDecodeError once p3 is supported.
-      return []
-
-  def list_active_targets(self):
-    """Gets the list of targets and filters down to the targets that are active.
-
-    Returns:
-      An iterator over active FfxTargets.
-    """
-    targets = [
-        FfxTarget.from_target_list_json(self, json_target)
-        for json_target in self.list_targets()
-    ]
-    return filter(lambda target: target.get_ssh_address(), targets)
-
-  def remove_stale_targets(self, address):
-    """Removes any targets from ffx that are listening at a given address.
-
-    Args:
-      address: A string representation of the target's ip address.
-    """
-    for target in self.list_targets():
-      if target['rcs_state'] == 'N' and address in target['addresses']:
-        self.run_ffx(['target', 'remove', address])
-
-  @contextlib.contextmanager
-  def scoped_target_context(self, address, port):
-    """Temporarily adds a new target.
-
-    Args:
-      address: The IP address at which the target is listening.
-      port: The port number on which the target is listening.
-
-    Yields:
-      An FfxTarget for interacting with the target.
-    """
-    target_id = format_host_port(address, port)
-    # -n allows `target add` to skip waiting for the device to come up,
-    # as this can take longer than the default wait period.
-    self.run_ffx(['target', 'add', '-n', target_id])
-    try:
-      yield FfxTarget.from_address(self, address, port)
-    finally:
-      self.run_ffx(['target', 'remove', target_id], check=False)
-
-  def get_node_name(self, address, port):
-    """Returns the node name for a target given its SSH address.
-
-    Args:
-      address: The address at which the target's SSH daemon is listening.
-      port: The port number on which the daemon is listening.
-
-    Returns:
-      The target's node name.
-
-    Raises:
-      Exception: If the target cannot be found.
-    """
-    for target in self.list_targets():
-      if target['nodename'] and address in target['addresses']:
-        ssh_address = FfxTarget.from_target_list_json(target).get_ssh_address()
-        if ssh_address and ssh_address[1] == port:
-          return target['nodename']
-    raise Exception('Failed to determine node name for target at %s' %
-                    format_host_port(address, port))
-
-  def daemon_stop(self):
-    """Stops the ffx daemon."""
-    self.run_ffx(['daemon', 'stop'], check=False, suppress_repair=True)
-    # Daemon should stop at this point.
-    self._wait_for_daemon(start=False)
-
-
-class FfxTarget():
-  """A helper to run `ffx` commands for a specific target."""
-
-  @classmethod
-  def from_address(cls, ffx_runner, address, port=None):
-    """Args:
-      ffx_runner: The runner to use to run ffx.
-      address: The target's address.
-      port: The target's port, defaults to None in which case it will target
-            the first device at the specified address
-    """
-    return cls(ffx_runner, format_host_port(address, port) if port else address)
-
-  @classmethod
-  def from_node_name(cls, ffx_runner, node_name):
-    """Args:
-      ffx_runner: The runner to use to run ffx.
-      node_name: The target's node name.
-    """
-    return cls(ffx_runner, node_name)
-
-  @classmethod
-  def from_target_list_json(cls, ffx_runner, json_target):
-    """Args:
-      ffx_runner: The runner to use to run ffx.
-      json_target: the json dict as returned from `ffx list targets`
-    """
-    # Targets seen via `fx serve-remote` frequently have no name, so fall back
-    # to using the first address.
-    if json_target['nodename'].startswith('<unknown'):
-      return cls.from_address(ffx_runner, json_target['addresses'][0])
-    return cls.from_node_name(ffx_runner, json_target['nodename'])
-
-  def __init__(self, ffx_runner, target_id):
-    """Args:
-      ffx_runner: The runner to use to run ffx.
-      target_id: The target's node name or addr:port string.
-    """
-    self._ffx_runner = ffx_runner
-    self._target_id = target_id
-    self._target_args = ('--target', target_id)
-
-  def format_runner_options(self):
-    """Returns a string holding options suitable for use with the runner scripts
-    to run tests on this target."""
-    try:
-      # First try extracting host:port from the target_id.
-      return '-d --host %s --port %d' % parse_host_port(self._target_args[1])
-    except ValueError:
-      # Must be a simple node name.
-      pass
-    return '-d --node-name %s' % self._target_args[1]
-
-  def wait(self, timeout=None):
-    """Waits for ffx to establish a connection with the target.
-
-    Args:
-      timeout: The number of seconds to wait (60 if not specified).
-    """
-    command = list(self._target_args)
-    command.extend(('target', 'wait'))
-    if timeout is not None:
-      command.extend(('-t', '%d' % int(timeout)))
-    self._ffx_runner.run_ffx(command)
-
-  def get_ssh_address(self):
-    """Returns the host and port of the target's SSH address
-
-    Returns:
-      A tuple of a host address string and a port number integer,
-        or None if there was an exception
-    """
-    command = list(self._target_args)
-    command.extend(('target', 'get-ssh-address'))
-    try:
-      return parse_host_port(self._ffx_runner.run_ffx(command))
-    except:
-      return None
-
-  def open_ffx(self, command):
-    """Runs `ffx` for the target with some arguments.
-    Args:
-      command: A command and its arguments to run on the target.
-    Returns:
-      A subprocess.Popen object.
-    """
-    args = list(self._target_args)
-    args.extend(command)
-    return self._ffx_runner.open_ffx(args)
-
-  def __str__(self):
-    return self._target_id
-
-  def __repr__(self):
-    return self._target_id
-
-
-# TODO(grt): Derive from contextlib.AbstractContextManager when p3 is supported.
-class FfxSession():
-  """A context manager that manages a session for running a test via `ffx`.
-
-  Upon entry, an instance of this class configures `ffx` to retrieve files
-  generated by a test and prepares a directory to hold these files either in a
-  LogManager's log directory or in tmp. On exit, any previous configuration of
-  `ffx` is restored and the temporary directory, if used, is deleted.
-
-  The prepared directory is used when invoking `ffx test run`.
-  """
-
-  def __init__(self, log_manager):
-    """Args:
-      log_manager: A Target's LogManager.
-    """
-    self._log_manager = log_manager
-    self._ffx = FfxRunner(log_manager)
-    self._own_output_dir = False
-    self._output_dir = None
-    self._run_summary = None
-    self._suite_summary = None
-    self._custom_artifact_directory = None
-    self._debug_data_directory = None
-
-  def __enter__(self):
-    if self._log_manager.IsLoggingEnabled():
-      # Use a subdir of the configured log directory to hold test outputs.
-      self._output_dir = os.path.join(self._log_manager.GetLogDirectory(),
-                                      'test_outputs')
-      # TODO(grt): Use exist_ok=True when p3 is supported.
-      try:
-        os.makedirs(self._output_dir)
-      except OSError as ose:
-        if ose.errno != errno.EEXIST:
-          raise
-    else:
-      # Create a temporary directory to hold test outputs.
-      # TODO(grt): Switch to tempfile.TemporaryDirectory when p3 is supported.
-      self._own_output_dir = True
-      self._output_dir = tempfile.mkdtemp(prefix='ffx_session_tmp_')
-    return self
-
-  def __exit__(self, exc_type, exc_val, exc_tb):
-    # Restore the previous test.output_path setting.
-    if self._own_output_dir:
-      # Clean up the temporary output directory.
-      shutil.rmtree(self._output_dir, ignore_errors=True)
-      self._own_output_dir = False
-    self._output_dir = None
-    return False
-
-  def get_output_dir(self):
-    """Returns the temporary output directory for the session."""
-    assert self._output_dir, 'FfxSession must be used in a context'
-    return self._output_dir
-
-  def test_run(self, ffx_target, component_uri, package_args):
-    """Runs a test on a target.
-    Args:
-      ffx_target: The target on which to run the test.
-      component_uri: The test component URI.
-      package_args: Arguments to the test package.
-    Returns:
-      A subprocess.Popen object.
-    """
-    command = [
-        '--config', 'test.experimental_structured_output=false', 'test', 'run',
-        '--output-directory', self._output_dir, component_uri, '--'
-    ]
-    command.extend(package_args)
-    return ffx_target.open_ffx(command)
-
-  def _parse_test_outputs(self):
-    """Parses the output files generated by the test runner.
-
-    The instance's `_custom_artifact_directory` member is set to the directory
-    holding output files emitted by the test.
-
-    This function is idempotent, and performs no work if it has already been
-    called.
-    """
-    if self._run_summary:
-      return  # The files have already been parsed.
-
-    # Parse the main run_summary.json file.
-    run_summary_path = os.path.join(self._output_dir, 'run_summary.json')
-    try:
-      with open(run_summary_path) as run_summary_file:
-        self._run_summary = json.load(run_summary_file)
-    except IOError as io_error:
-      logging.error('Error reading run summary file: %s', str(io_error))
-      return
-    except ValueError as value_error:
-      logging.error('Error parsing run summary file %s: %s', run_summary_path,
-                    str(value_error))
-      return
-
-    assert self._run_summary['schema_id'] == RUN_SUMMARY_SCHEMA, \
-      'Unsupported version found in %s' % run_summary_path
-
-    run_artifact_dir = self._run_summary.get('data', {})['artifact_dir']
-    for artifact_path, artifact in self._run_summary.get(
-        'data', {})['artifacts'].items():
-      if artifact['artifact_type'] == 'DEBUG':
-        self._debug_data_directory = os.path.join(self._output_dir,
-                                                  run_artifact_dir,
-                                                  artifact_path)
-        break
-
-    # There should be precisely one suite for the test that ran.
-    self._suite_summary = self._run_summary.get('data', {}).get('suites',
-                                                                [{}])[0]
-
-    # Get the top-level directory holding all artifacts for this suite.
-    artifact_dir = self._suite_summary.get('artifact_dir')
-    if not artifact_dir:
-      logging.error('Failed to find suite\'s artifact_dir in %s',
-                    run_summary_path)
-      return
-
-    # Get the path corresponding to artifacts
-    for artifact_path, artifact in self._suite_summary['artifacts'].items():
-      if artifact['artifact_type'] == 'CUSTOM':
-        self._custom_artifact_directory = os.path.join(self._output_dir,
-                                                       artifact_dir,
-                                                       artifact_path)
-        break
-
-  def get_custom_artifact_directory(self):
-    """Returns the full path to the directory holding custom artifacts emitted
-    by the test, or None if the path cannot be determined.
-    """
-    self._parse_test_outputs()
-    return self._custom_artifact_directory
-
-  def get_debug_data_directory(self):
-    """Returns the full path to the directory holding custom artifacts emitted
-    by the test, or None if the path cannot be determined.
-    """
-    self._parse_test_outputs()
-    return self._debug_data_directory
-
-
-def make_arg_parser():
-  parser = argparse.ArgumentParser(
-      description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
-  parser.add_argument('--logs-dir', help='Directory to write logs to.')
-  parser.add_argument('--verbose',
-                      '-v',
-                      action='store_true',
-                      default=False,
-                      help='Enable debug logging')
-  return parser
-
-
-def main(args):
-  args = make_arg_parser().parse_args(args)
-
-  logging.basicConfig(format='%(asctime)s:%(levelname)s:%(name)s:%(message)s',
-                      level=logging.DEBUG if args.verbose else logging.INFO)
-  log_mgr = log_manager.LogManager(args.logs_dir)
-
-  with FfxSession(log_mgr) as ffx_session:
-    logging.info(ffx_session.get_output_dir())
-
-  return 0
-
-
-if __name__ == '__main__':
-  sys.exit(main(sys.argv[1:]))
diff --git a/build/fuchsia/log_manager.py b/build/fuchsia/log_manager.py
deleted file mode 100644
index f2d142a..0000000
--- a/build/fuchsia/log_manager.py
+++ /dev/null
@@ -1,53 +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.
-"""Creates and manages log file objects.
-
-Provides an object that handles opening and closing file streams for
-logging purposes.
-"""
-
-import os
-
-
-class LogManager(object):
-  def __init__(self, logs_dir):
-
-    # A dictionary with the log file path as the key and a file stream as value.
-    self._logs = {}
-
-    self._logs_dir = logs_dir
-    if self._logs_dir:
-      if not os.path.isdir(self._logs_dir):
-        os.makedirs(self._logs_dir)
-
-  def IsLoggingEnabled(self):
-    return self._logs_dir is not None
-
-  def GetLogDirectory(self):
-    """Get the directory logs are placed into."""
-
-    return self._logs_dir
-
-  def Open(self, log_file_name):
-    """Open a file stream with log_file_name in the logs directory."""
-
-    parent_dir = self.GetLogDirectory()
-    if not parent_dir:
-      return open(os.devnull, 'w')
-    log_file_path = os.path.join(parent_dir, log_file_name)
-    if log_file_path in self._logs:
-      return self._logs[log_file_path]
-    log_file = open(log_file_path, 'w', buffering=1)
-    self._logs[log_file_path] = log_file
-    return log_file
-
-  def Stop(self):
-    for log in self._logs.values():
-      log.close()
-
-  def __enter__(self):
-    return self
-
-  def __exit__(self, exc_type, exc_value, traceback):
-    self.Stop()
diff --git a/build/fuchsia/test/coveragetest.py b/build/fuchsia/test/coveragetest.py
index 7dfd3d9..3a82e53c 100755
--- a/build/fuchsia/test/coveragetest.py
+++ b/build/fuchsia/test/coveragetest.py
@@ -19,7 +19,7 @@
 ]
 
 # The files will be tested without coverage requirements.
-TESTED_FILES = ['common.py']
+TESTED_FILES = ['common.py', 'ffx_emulator.py']
 
 
 def main():
diff --git a/build/fuchsia/test/ffx_emulator.py b/build/fuchsia/test/ffx_emulator.py
new file mode 100644
index 0000000..9e0a3318
--- /dev/null
+++ b/build/fuchsia/test/ffx_emulator.py
@@ -0,0 +1,144 @@
+# 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.
+"""Provide helpers for running Fuchsia's `ffx emu`."""
+
+import argparse
+import ast
+import logging
+import os
+import json
+import random
+import subprocess
+
+from contextlib import AbstractContextManager
+
+from common import check_ssh_config_file, run_ffx_command, \
+                   SDK_ROOT
+from compatible_utils import get_host_arch
+from ffx_integration import ScopedFfxConfig
+
+_EMU_COMMAND_RETRIES = 3
+
+
+class FfxEmulator(AbstractContextManager):
+    """A helper for managing emulators."""
+    def __init__(self, args: argparse.Namespace) -> None:
+        if args.product_bundle:
+            self._product_bundle = args.product_bundle
+        else:
+            target_cpu = get_host_arch()
+            self._product_bundle = f'terminal.qemu-{target_cpu}'
+
+        self._enable_graphics = args.enable_graphics
+        self._hardware_gpu = args.hardware_gpu
+        self._logs_dir = args.logs_dir
+        self._with_network = args.with_network
+        self._node_name = 'fuchsia-emulator-' + str(random.randint(1, 9999))
+
+        # Set the download path parallel to Fuchsia SDK directory
+        # permanently so that scripts can always find the product bundles.
+        run_ffx_command(('config', 'set', 'pbms.storage.path',
+                         os.path.join(SDK_ROOT, os.pardir, 'images')))
+
+        override_file = os.path.join(os.path.dirname(__file__), os.pardir,
+                                     'sdk_override.txt')
+        self._scoped_pb_metadata = None
+        if os.path.exists(override_file):
+            with open(override_file) as f:
+                pb_metadata = f.read().split('\n')
+                pb_metadata.append('{sdk.root}/*.json')
+                self._scoped_pb_metadata = ScopedFfxConfig(
+                    'pbms.metadata', json.dumps((pb_metadata)))
+
+    def __enter__(self) -> str:
+        """Start the emulator.
+
+        Returns:
+            The node name of the emulator.
+        """
+
+        logging.info('Starting emulator %s', self._node_name)
+        if self._scoped_pb_metadata:
+            self._scoped_pb_metadata.__enter__()
+        check_ssh_config_file()
+        emu_command = [
+            'emu', 'start', self._product_bundle, '--name', self._node_name
+        ]
+        if not self._enable_graphics:
+            emu_command.append('-H')
+        if self._hardware_gpu:
+            emu_command.append('--gpu')
+        if self._logs_dir:
+            emu_command.extend(
+                ('-l', os.path.join(self._logs_dir, 'emulator_log')))
+        if self._with_network:
+            emu_command.extend(('--net', 'tap'))
+
+        # TODO(https://crbug.com/1336776): remove when ffx has native support
+        # for starting emulator on arm64 host.
+        if get_host_arch() == 'arm64':
+
+            arm64_qemu_dir = os.path.join(SDK_ROOT, 'tools', 'arm64',
+                                          'qemu_internal')
+
+            # The arm64 emulator binaries are downloaded separately, so add
+            # a symlink to the expected location inside the SDK.
+            if not os.path.isdir(arm64_qemu_dir):
+                os.symlink(
+                    os.path.join(SDK_ROOT, '..', '..', 'qemu-linux-arm64'),
+                    arm64_qemu_dir)
+
+            # Add the arm64 emulator binaries to the SDK's manifest.json file.
+            sdk_manifest = os.path.join(SDK_ROOT, 'meta', 'manifest.json')
+            with open(sdk_manifest, 'r+') as f:
+                data = json.load(f)
+                for part in data['parts']:
+                    if part['meta'] == 'tools/x64/qemu_internal-meta.json':
+                        part['meta'] = 'tools/arm64/qemu_internal-meta.json'
+                        break
+                f.seek(0)
+                json.dump(data, f)
+                f.truncate()
+
+            # Generate a meta file for the arm64 emulator binaries using its
+            # x64 counterpart.
+            qemu_arm64_meta_file = os.path.join(SDK_ROOT, 'tools', 'arm64',
+                                                'qemu_internal-meta.json')
+            qemu_x64_meta_file = os.path.join(SDK_ROOT, 'tools', 'x64',
+                                              'qemu_internal-meta.json')
+            with open(qemu_x64_meta_file) as f:
+                data = str(json.load(f))
+            qemu_arm64_meta = data.replace(r'tools/x64', 'tools/arm64')
+            with open(qemu_arm64_meta_file, "w+") as f:
+                json.dump(ast.literal_eval(qemu_arm64_meta), f)
+            emu_command.extend(['--engine', 'qemu'])
+
+        with ScopedFfxConfig('emu.start.timeout', '90'):
+            for _ in range(_EMU_COMMAND_RETRIES):
+
+                # If the ffx daemon fails to establish a connection with
+                # the emulator after 85 seconds, that means the emulator
+                # failed to be brought up and a retry is needed.
+                # TODO(fxb/103540): Remove retry when start up issue is fixed.
+                try:
+                    run_ffx_command(emu_command, timeout=85)
+                    break
+                except (subprocess.TimeoutExpired,
+                        subprocess.CalledProcessError):
+                    run_ffx_command(('emu', 'stop'))
+        return self._node_name
+
+    def __exit__(self, exc_type, exc_value, traceback) -> bool:
+        """Shutdown the emulator."""
+
+        logging.info('Stopping the emulator %s', self._node_name)
+        # The emulator might have shut down unexpectedly, so this command
+        # might fail.
+        run_ffx_command(('emu', 'stop', self._node_name), check=False)
+
+        if self._scoped_pb_metadata:
+            self._scoped_pb_metadata.__exit__(exc_type, exc_value, traceback)
+
+        # Do not suppress exceptions.
+        return False
diff --git a/build/fuchsia/test/ffx_emulator_unittests.py b/build/fuchsia/test/ffx_emulator_unittests.py
new file mode 100755
index 0000000..81f1f61
--- /dev/null
+++ b/build/fuchsia/test/ffx_emulator_unittests.py
@@ -0,0 +1,32 @@
+#!/usr/bin/env vpython3
+# 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.
+"""File for testing ffx_emulator.py."""
+
+import argparse
+import unittest
+
+from ffx_emulator import FfxEmulator
+
+
+class FfxEmulatorTest(unittest.TestCase):
+    """Unittests for ffx_emulator.py"""
+    def test_use_random_node_name(self) -> None:
+        """FfxEmulator should not use a fixed node name."""
+        # Allowing the test case to access FfxEmulator._node_name directly.
+        # pylint: disable=protected-access
+        self.assertNotEqual(
+            FfxEmulator(
+                argparse.Namespace(
+                    **{
+                        'product_bundle': None,
+                        'enable_graphics': False,
+                        'hardware_gpu': False,
+                        'logs_dir': '.',
+                        'with_network': False
+                    }))._node_name, 'fuchsia-everlasting-emulator')
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/build/fuchsia/test/ffx_integration.py b/build/fuchsia/test/ffx_integration.py
index 2c0960e..507479b 100644
--- a/build/fuchsia/test/ffx_integration.py
+++ b/build/fuchsia/test/ffx_integration.py
@@ -3,11 +3,9 @@
 # found in the LICENSE file.
 """Provide helpers for running Fuchsia's `ffx`."""
 
-import ast
 import logging
 import os
 import json
-import random
 import subprocess
 import sys
 import tempfile
@@ -15,9 +13,7 @@
 from contextlib import AbstractContextManager
 from typing import IO, Iterable, List, Optional
 
-from common import check_ssh_config_file, run_ffx_command, \
-                   run_continuous_ffx_command, SDK_ROOT
-from compatible_utils import get_host_arch
+from common import run_continuous_ffx_command, run_ffx_command, SDK_ROOT
 
 _EMU_COMMAND_RETRIES = 3
 RUN_SUMMARY_SCHEMA = \
@@ -89,134 +85,6 @@
     run_ffx_command(('target', 'echo'), target_id)
 
 
-class FfxEmulator(AbstractContextManager):
-    """A helper for managing emulators."""
-
-    def __init__(self,
-                 enable_graphics: bool,
-                 hardware_gpu: bool,
-                 product_bundle: Optional[str],
-                 with_network: bool,
-                 logs_dir: Optional[str] = None) -> None:
-        if product_bundle:
-            self._product_bundle = product_bundle
-        else:
-            target_cpu = get_host_arch()
-            self._product_bundle = f'terminal.qemu-{target_cpu}'
-
-        self._enable_graphics = enable_graphics
-        self._hardware_gpu = hardware_gpu
-        self._logs_dir = logs_dir
-        self._with_network = with_network
-        node_name_suffix = random.randint(1, 9999)
-        self._node_name = f'fuchsia-emulator-{node_name_suffix}'
-
-        # Set the download path parallel to Fuchsia SDK directory
-        # permanently so that scripts can always find the product bundles.
-        run_ffx_command(('config', 'set', 'pbms.storage.path',
-                         os.path.join(SDK_ROOT, os.pardir, 'images')))
-
-        override_file = os.path.join(os.path.dirname(__file__), os.pardir,
-                                     'sdk_override.txt')
-        self._scoped_pb_metadata = None
-        if os.path.exists(override_file):
-            with open(override_file) as f:
-                pb_metadata = f.read().split('\n')
-                pb_metadata.append('{sdk.root}/*.json')
-                self._scoped_pb_metadata = ScopedFfxConfig(
-                    'pbms.metadata', json.dumps((pb_metadata)))
-
-    def __enter__(self) -> str:
-        """Start the emulator.
-
-        Returns:
-            The node name of the emulator.
-        """
-
-        if self._scoped_pb_metadata:
-            self._scoped_pb_metadata.__enter__()
-        check_ssh_config_file()
-        emu_command = [
-            'emu', 'start', self._product_bundle, '--name', self._node_name
-        ]
-        if not self._enable_graphics:
-            emu_command.append('-H')
-        if self._hardware_gpu:
-            emu_command.append('--gpu')
-        if self._logs_dir:
-            emu_command.extend(
-                ('-l', os.path.join(self._logs_dir, 'emulator_log')))
-        if self._with_network:
-            emu_command.extend(('--net', 'tap'))
-
-        # TODO(https://crbug.com/1336776): remove when ffx has native support
-        # for starting emulator on arm64 host.
-        if get_host_arch() == 'arm64':
-
-            arm64_qemu_dir = os.path.join(SDK_ROOT, 'tools', 'arm64',
-                                          'qemu_internal')
-
-            # The arm64 emulator binaries are downloaded separately, so add
-            # a symlink to the expected location inside the SDK.
-            if not os.path.isdir(arm64_qemu_dir):
-                os.symlink(
-                    os.path.join(SDK_ROOT, '..', '..', 'qemu-linux-arm64'),
-                    arm64_qemu_dir)
-
-            # Add the arm64 emulator binaries to the SDK's manifest.json file.
-            sdk_manifest = os.path.join(SDK_ROOT, 'meta', 'manifest.json')
-            with open(sdk_manifest, 'r+') as f:
-                data = json.load(f)
-                for part in data['parts']:
-                    if part['meta'] == 'tools/x64/qemu_internal-meta.json':
-                        part['meta'] = 'tools/arm64/qemu_internal-meta.json'
-                        break
-                f.seek(0)
-                json.dump(data, f)
-                f.truncate()
-
-            # Generate a meta file for the arm64 emulator binaries using its
-            # x64 counterpart.
-            qemu_arm64_meta_file = os.path.join(SDK_ROOT, 'tools', 'arm64',
-                                                'qemu_internal-meta.json')
-            qemu_x64_meta_file = os.path.join(SDK_ROOT, 'tools', 'x64',
-                                              'qemu_internal-meta.json')
-            with open(qemu_x64_meta_file) as f:
-                data = str(json.load(f))
-            qemu_arm64_meta = data.replace(r'tools/x64', 'tools/arm64')
-            with open(qemu_arm64_meta_file, "w+") as f:
-                json.dump(ast.literal_eval(qemu_arm64_meta), f)
-            emu_command.extend(['--engine', 'qemu'])
-
-        with ScopedFfxConfig('emu.start.timeout', '90'):
-            for _ in range(_EMU_COMMAND_RETRIES):
-
-                # If the ffx daemon fails to establish a connection with
-                # the emulator after 85 seconds, that means the emulator
-                # failed to be brought up and a retry is needed.
-                # TODO(fxb/103540): Remove retry when start up issue is fixed.
-                try:
-                    run_ffx_command(emu_command, timeout=85)
-                    break
-                except (subprocess.TimeoutExpired,
-                        subprocess.CalledProcessError):
-                    run_ffx_command(('emu', 'stop'))
-        return self._node_name
-
-    def __exit__(self, exc_type, exc_value, traceback) -> bool:
-        """Shutdown the emulator."""
-
-        # The emulator might have shut down unexpectedly, so this command
-        # might fail.
-        run_ffx_command(('emu', 'stop', self._node_name), check=False)
-
-        if self._scoped_pb_metadata:
-            self._scoped_pb_metadata.__exit__(exc_type, exc_value, traceback)
-
-        # Do not suppress exceptions.
-        return False
-
-
 class FfxTestRunner(AbstractContextManager):
     """A context manager that manages a session for running a test via `ffx`.
 
diff --git a/build/fuchsia/test/start_emulator.py b/build/fuchsia/test/start_emulator.py
index 2b3416ab..897ea79 100755
--- a/build/fuchsia/test/start_emulator.py
+++ b/build/fuchsia/test/start_emulator.py
@@ -10,7 +10,7 @@
 import time
 
 from common import catch_sigterm, register_log_args
-from ffx_integration import FfxEmulator
+from ffx_emulator import FfxEmulator
 
 
 def register_emulator_args(parser: argparse.ArgumentParser,
@@ -47,8 +47,7 @@
 def create_emulator_from_args(args: argparse.Namespace) -> FfxEmulator:
     """Helper method for initializing an FfxEmulator class with parsed
     arguments."""
-    return FfxEmulator(args.enable_graphics, args.hardware_gpu,
-                       args.product_bundle, args.with_network, args.logs_dir)
+    return FfxEmulator(args)
 
 
 def main():
diff --git a/build/fuchsia/update_product_bundles.py b/build/fuchsia/update_product_bundles.py
index 988d5974..a889d59 100755
--- a/build/fuchsia/update_product_bundles.py
+++ b/build/fuchsia/update_product_bundles.py
@@ -15,13 +15,12 @@
 import sys
 
 from contextlib import ExitStack
-import ffx_session
-import log_manager
 
 sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__),
                                              'test')))
 
 import common
+import ffx_integration
 
 _PRODUCT_BUNDLES = [
     'core.x64-dfv2',
@@ -77,27 +76,23 @@
     return json.load(f)['id']
 
 
-def remove_repositories(repo_names_to_remove, ffx_runner):
+def remove_repositories(repo_names_to_remove):
   """Removes given repos from repo list.
   Repo MUST be present in list to succeed.
 
   Args:
     repo_names_to_remove: List of repo names (as strings) to remove.
-    ffx_runner: ffx_session.FfxRunner instance to run the command.
   """
   for repo_name in repo_names_to_remove:
-    ffx_runner.run_ffx(('repository', 'remove', repo_name), check=True)
+    common.run_ffx_command(('repository', 'remove', repo_name), check=True)
 
 
-def get_repositories(ffx_runner):
+def get_repositories():
   """Lists repositories that are available on disk.
 
   Also prunes repositories that are listed, but do not have an actual packages
   directory.
 
-  Args:
-    ffx_runner: An FfxRunner instance.
-
   Returns:
     List of dictionaries containing info about the repositories. They have the
     following structure:
@@ -111,7 +106,9 @@
   """
 
   repos = json.loads(
-      ffx_runner.run_ffx(('--machine', 'json', 'repository', 'list')).strip())
+      common.run_ffx_command(('--machine', 'json', 'repository', 'list'),
+                             check=True,
+                             capture_output=True).stdout.strip())
   to_prune = set()
   sdk_root_abspath = os.path.abspath(os.path.dirname(common.SDK_ROOT))
   for repo in repos:
@@ -126,26 +123,23 @@
 
   repos = [repo for repo in repos if repo['name'] not in to_prune]
 
-  remove_repositories(to_prune, ffx_runner)
+  remove_repositories(to_prune)
   return repos
 
 
-def update_repositories_list(ffx_runner):
+def update_repositories_list():
   """Used to prune stale repositories."""
-  get_repositories(ffx_runner)
+  get_repositories()
 
 
-def remove_product_bundle(product_bundle, ffx_runner):
+def remove_product_bundle(product_bundle):
   """Removes product-bundle given."""
-  ffx_runner.run_ffx(('product-bundle', 'remove', '-f', product_bundle))
+  common.run_ffx_command(('product-bundle', 'remove', '-f', product_bundle))
 
 
-def get_product_bundle_urls(ffx_runner):
+def get_product_bundle_urls():
   """Retrieves URLs of available product-bundles.
 
-  Args:
-    ffx_runner: An FfxRunner instance.
-
   Returns:
     List of dictionaries of structure, indicating whether the product-bundle
     has been downloaded.
@@ -155,8 +149,8 @@
     }
   """
   # TODO(fxb/115328): Replaces with JSON API when available.
-  bundles = ffx_runner.run_ffx(('product-bundle', 'list'), check=True)
-
+  bundles = common.run_ffx_command(('product-bundle', 'list'),
+                                   capture_output=True).stdout.strip()
   urls = [
       line.strip() for line in bundles.splitlines() if 'gs://fuchsia' in line
   ]
@@ -170,40 +164,34 @@
   return structured_urls
 
 
-def keep_product_bundles_by_sdk_version(sdk_version, ffx_runner):
+def keep_product_bundles_by_sdk_version(sdk_version):
   """Prunes product bundles not containing the sdk_version given."""
-  urls = get_product_bundle_urls(ffx_runner)
+  urls = get_product_bundle_urls()
   for url in urls:
     if url['downloaded'] and sdk_version not in url['url']:
-      remove_product_bundle(url['url'], ffx_runner)
+      remove_product_bundle(url['url'])
 
 
-def get_product_bundles(ffx_runner):
+def get_product_bundles():
   """Lists all downloaded product-bundles for the given SDK.
 
   Cross-references the repositories with downloaded packages and the stated
   downloaded product-bundles to validate whether or not a product-bundle is
   present. Prunes invalid product-bundles with each call as well.
 
-  Args:
-    ffx_runner: An FfxRunner instance.
-
   Returns:
     List of strings of product-bundle names downloaded and that FFX is aware
     of.
   """
   downloaded_bundles = []
 
-  for url in get_product_bundle_urls(ffx_runner):
+  for url in get_product_bundle_urls():
     if url['downloaded']:
       # The product is separated by a #
       product = url['url'].split('#')
       downloaded_bundles.append(product[1])
 
-  # For each downloaded bundle, need to verify whether ffx repository believes
-  # it exists.
-  to_prune_bundles_index = []
-  repos = get_repositories(ffx_runner)
+  repos = get_repositories()
 
   # Some repo names do not match product-bundle names due to underscores.
   # Normalize them both.
@@ -216,19 +204,19 @@
     if name.replace('-', '_') in repo_names:
       return True
 
-    remove_product_bundle(name, ffx_runner)
+    remove_product_bundle(name)
     return False
 
   return list(filter(bundle_is_active, downloaded_bundles))
 
 
-def download_product_bundle(product_bundle, ffx_runner):
+def download_product_bundle(product_bundle):
   """Download product bundles using the SDK."""
   # This also updates the repository list, in case it is stale.
-  update_repositories_list(ffx_runner)
+  update_repositories_list()
 
   try:
-    ffx_runner.run_ffx(
+    common.run_ffx_command(
         ('product-bundle', 'get', product_bundle, '--force-repo'))
   except subprocess.CalledProcessError as cpe:
     logging.error('Product bundle download has failed. ' +
@@ -236,22 +224,19 @@
     raise
 
 
-def get_current_signature(ffx_runner):
+def get_current_signature():
   """Determines the SDK version of the product-bundles associated with the SDK.
 
   Parses this information from the URLs of the product-bundle.
 
-  Args:
-    ffx_runner: An FfxRunner instance.
-
   Returns:
     An SDK version string, or None if no product-bundle versions are downloaded.
   """
-  product_bundles = get_product_bundles(ffx_runner)
+  product_bundles = get_product_bundles()
   if not product_bundles:
     logging.info('No product bundles - signature will default to None')
     return None
-  product_bundle_urls = get_product_bundle_urls(ffx_runner)
+  product_bundle_urls = get_product_bundle_urls()
 
   # Get the numbers, hope they're the same.
   signatures = set()
@@ -299,11 +284,10 @@
                      'found in the DEPS file.')
 
   with ExitStack() as stack:
-    ffx_runner = ffx_session.FfxRunner(log_manager.LogManager(None))
 
     # Re-set the directory to which product bundles are downloaded so that
     # these bundles are located inside the Chromium codebase.
-    ffx_runner.run_ffx(
+    common.run_ffx_command(
         ('config', 'set', 'pbms.storage.path', common.IMAGES_ROOT))
 
     logging.debug('Checking for override file')
@@ -316,16 +300,17 @@
         pb_metadata = f.read().strip().split('\n')
         pb_metadata.append('{sdk.root}/*.json')
       stack.enter_context(
-          ffx_runner.scoped_config('pbms.metadata', json.dumps((pb_metadata))))
+          ffx_integration.ScopedFfxConfig('pbms.metadata',
+                                          json.dumps((pb_metadata))))
       logging.debug('Applied overrides')
 
     logging.debug('Getting new SDK hash')
     new_sdk_hash = get_hash_from_sdk()
-    keep_product_bundles_by_sdk_version(new_sdk_hash, ffx_runner)
+    keep_product_bundles_by_sdk_version(new_sdk_hash)
     logging.debug('Checking for current signature')
-    curr_signature = get_current_signature(ffx_runner)
+    curr_signature = get_current_signature()
 
-    current_images = get_product_bundles(ffx_runner)
+    current_images = get_product_bundles()
 
     # If SDK versions match, remove the product bundles that are no longer
     # needed and download missing ones.
@@ -335,34 +320,30 @@
       for image in current_images:
         if image not in new_product_bundles:
           logging.debug('Removing no longer needed Fuchsia image %s' % image)
-          remove_product_bundle(image, ffx_runner)
+          remove_product_bundle(image)
 
       bundles_to_download = set(new_product_bundles) - \
                             set(current_images)
       for bundle in bundles_to_download:
         logging.debug('Downloading image: %s', image)
-        download_product_bundle(bundle, ffx_runner)
+        download_product_bundle(bundle)
 
       return 0
 
     # If SDK versions do not match, remove all existing product bundles
     # and download the ones required.
     for pb in current_images:
-      remove_product_bundle(pb, ffx_runner)
+      remove_product_bundle(pb)
 
     logging.debug('Make clean images root')
-    curr_subdir = []
-    if os.path.exists(common.IMAGES_ROOT):
-      curr_subdir = os.listdir(common.IMAGES_ROOT)
     common.make_clean_directory(common.IMAGES_ROOT)
 
     for pb in new_product_bundles:
       logging.debug('Downloading bundle: %s', pb)
-      download_product_bundle(pb, ffx_runner)
+      download_product_bundle(pb)
 
-    current_pb = get_product_bundles(ffx_runner)
+    current_pb = get_product_bundles()
 
-    diff = set(current_pb) - set(new_product_bundles)
     assert set(current_pb) == set(new_product_bundles), (
         'Failed to download expected set of product-bundles. '
         f'Expected {new_product_bundles}, got {current_pb}')
diff --git a/build/fuchsia/update_product_bundles_test.py b/build/fuchsia/update_product_bundles_test.py
index 0532a12..bb85454 100755
--- a/build/fuchsia/update_product_bundles_test.py
+++ b/build/fuchsia/update_product_bundles_test.py
@@ -12,7 +12,6 @@
 
 from parameterized import parameterized
 
-import ffx_session
 import update_product_bundles
 
 sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__),
@@ -22,6 +21,14 @@
 
 
 class TestUpdateProductBundles(unittest.TestCase):
+  def setUp(self):
+    ffx_mock = mock.Mock()
+    ffx_mock.returncode = 0
+    self._ffx_patcher = mock.patch('common.run_ffx_command',
+                                   return_value=ffx_mock)
+    self._ffx_mock = self._ffx_patcher.start()
+    self.addCleanup(self._ffx_mock.stop)
+
   def testConvertToProductBundleDefaultsUnknownImage(self):
     self.assertEqual(
         update_product_bundles.convert_to_product_bundle(['unknown-image']),
@@ -62,13 +69,11 @@
 
     self.assertRaises(RuntimeError, update_product_bundles.get_hash_from_sdk)
 
-  def testRemoveRepositoriesRunsRemoveOnGivenRepos(self):
-    ffx_runner = mock.create_autospec(ffx_session.FfxRunner, instance=True)
+  @mock.patch('common.run_ffx_command')
+  def testRemoveRepositoriesRunsRemoveOnGivenRepos(self, ffx_mock):
+    update_product_bundles.remove_repositories(['foo', 'bar', 'fizz', 'buzz'])
 
-    update_product_bundles.remove_repositories(['foo', 'bar', 'fizz', 'buzz'],
-                                               ffx_runner)
-
-    ffx_runner.run_ffx.assert_has_calls([
+    ffx_mock.assert_has_calls([
         mock.call(('repository', 'remove', 'foo'), check=True),
         mock.call(('repository', 'remove', 'bar'), check=True),
         mock.call(('repository', 'remove', 'fizz'), check=True),
@@ -80,8 +85,7 @@
   def testGetRepositoriesPrunesReposThatDoNotExist(self, mock_abspath,
                                                    mock_exists):
     with mock.patch('common.SDK_ROOT', 'some/path'):
-      ffx_runner = mock.create_autospec(ffx_session.FfxRunner, instance=True)
-      ffx_runner.run_ffx.return_value = json.dumps([{
+      self._ffx_mock.return_value.stdout = json.dumps([{
           "name": "terminal.qemu-x64",
           "spec": {
               "type": "pm",
@@ -97,34 +101,30 @@
       mock_exists.side_effect = [True, False]
       mock_abspath.side_effect = lambda x: x
 
-      self.assertEqual(update_product_bundles.get_repositories(ffx_runner),
-                       [{
-                           "name": "terminal.qemu-x64",
-                           "spec": {
-                               "type": "pm",
-                               "path": "some/path/that/exists"
-                           }
-                       }])
+      self.assertEqual(update_product_bundles.get_repositories(), [{
+          "name": "terminal.qemu-x64",
+          "spec": {
+              "type": "pm",
+              "path": "some/path/that/exists"
+          }
+      }])
 
-      ffx_runner.run_ffx.assert_has_calls([
-          mock.call(('--machine', 'json', 'repository', 'list')),
+      self._ffx_mock.assert_has_calls([
+          mock.call(('--machine', 'json', 'repository', 'list'),
+                    capture_output=True,
+                    check=True),
           mock.call(('repository', 'remove', 'workstation-eng.chromebook-x64'),
                     check=True)
       ])
 
   def testRemoveProductBundle(self):
-    ffx_runner = mock.create_autospec(ffx_session.FfxRunner, instance=True)
+    update_product_bundles.remove_product_bundle('some-bundle-foo-bar')
 
-    update_product_bundles.remove_product_bundle('some-bundle-foo-bar',
-                                                 ffx_runner)
-
-    ffx_runner.run_ffx.assert_called_once_with(
+    self._ffx_mock.assert_called_once_with(
         ('product-bundle', 'remove', '-f', 'some-bundle-foo-bar'))
 
   def _InitFFXRunWithProductBundleList(self, sdk_version='10.20221114.2.1'):
-    ffx_runner = mock.create_autospec(ffx_session.FfxRunner, instance=True)
-
-    ffx_runner.run_ffx.return_value = f"""
+    self._ffx_mock.return_value.stdout = f"""
   gs://fuchsia/{sdk_version}/bundles.json#workstation_eng.qemu-x64
   gs://fuchsia/{sdk_version}/bundles.json#workstation_eng.chromebook-x64-dfv2
 * gs://fuchsia/{sdk_version}/bundles.json#workstation_eng.chromebook-x64
@@ -134,11 +134,10 @@
 
 *No need to fetch with `ffx product-bundle get ...`.
     """
-    return ffx_runner
 
   def testGetProductBundleUrlsMarksDesiredAsDownloaded(self):
-    urls = update_product_bundles.get_product_bundle_urls(
-        self._InitFFXRunWithProductBundleList())
+    self._InitFFXRunWithProductBundleList()
+    urls = update_product_bundles.get_product_bundle_urls()
     expected_urls = [{
         'url':
         'gs://fuchsia/10.20221114.2.1/bundles.json#workstation_eng.qemu-x64',
@@ -169,7 +168,7 @@
 
   @mock.patch('update_product_bundles.get_repositories')
   def testGetProductBundlesExtractsProductBundlesFromURLs(self, mock_get_repos):
-    ffx_runner = self._InitFFXRunWithProductBundleList()
+    self._InitFFXRunWithProductBundleList()
     mock_get_repos.return_value = [{
         'name': 'workstation-eng.chromebook-x64'
     }, {
@@ -179,7 +178,7 @@
     }]
 
     self.assertEqual(
-        set(update_product_bundles.get_product_bundles(ffx_runner)),
+        set(update_product_bundles.get_product_bundles()),
         set([
             'workstation_eng.chromebook-x64',
             'terminal.qemu-x64',
@@ -189,7 +188,7 @@
   @mock.patch('update_product_bundles.get_repositories')
   def testGetProductBundlesExtractsProductBundlesFromURLsFiltersMissingRepos(
       self, mock_get_repos):
-    ffx_runner = self._InitFFXRunWithProductBundleList()
+    self._InitFFXRunWithProductBundleList()
 
     # This will be missing two repos from the bundle list:
     # core and terminal.qemu-x64
@@ -201,34 +200,34 @@
         'name': 'terminal.qemu-arm64'
     }]
 
-    self.assertEqual(update_product_bundles.get_product_bundles(ffx_runner),
+    self.assertEqual(update_product_bundles.get_product_bundles(),
                      ['workstation_eng.chromebook-x64'])
-    ffx_runner.run_ffx.assert_has_calls([
+    self._ffx_mock.assert_has_calls([
         mock.call(('product-bundle', 'remove', '-f', 'terminal.qemu-x64')),
         mock.call(('product-bundle', 'remove', '-f', 'core.x64-dfv2')),
     ],
-                                        any_order=True)
+                                    any_order=True)
 
+  @mock.patch('common.run_ffx_command')
   @mock.patch('update_product_bundles.update_repositories_list')
   def testDownloadProductBundleUpdatesRepoListBeforeCall(
-      self, mock_update_repo):
-    ffx_runner = mock.create_autospec(ffx_session.FfxRunner, instance=True)
+      self, mock_update_repo, mock_ffx):
     mock_sequence = mock.Mock()
     mock_sequence.attach_mock(mock_update_repo, 'update_repo_list')
-    mock_sequence.attach_mock(ffx_runner.run_ffx, 'run_ffx')
+    mock_sequence.attach_mock(mock_ffx, 'run_ffx_command')
 
-    update_product_bundles.download_product_bundle('some-bundle', ffx_runner)
+    update_product_bundles.download_product_bundle('some-bundle')
 
     mock_sequence.assert_has_calls([
-        mock.call.update_repo_list(ffx_runner),
-        mock.call.run_ffx(
+        mock.call.update_repo_list(),
+        mock.call.run_ffx_command(
             ('product-bundle', 'get', 'some-bundle', '--force-repo'))
     ])
 
+  @mock.patch('common.run_ffx_command')
   @mock.patch('update_product_bundles.get_product_bundle_urls')
   def testFilterProductBundleURLsRemovesBundlesWithoutGivenString(
-      self, mock_get_urls):
-    ffx_runner = mock.create_autospec(ffx_session.FfxRunner, instance=True)
+      self, mock_get_urls, mock_ffx):
     mock_get_urls.return_value = [
         {
             'url': 'some-url-has-buzz',
@@ -243,27 +242,25 @@
             'downloaded': False,
         },
     ]
-    update_product_bundles.keep_product_bundles_by_sdk_version(
-        'buzz', ffx_runner)
-    ffx_runner.run_ffx.assert_called_once_with(
+    update_product_bundles.keep_product_bundles_by_sdk_version('buzz')
+    mock_ffx.assert_called_once_with(
         ('product-bundle', 'remove', '-f', 'some-url-to-remove-has-foo'))
 
   @mock.patch('update_product_bundles.get_repositories')
   def testGetCurrentSignatureReturnsNoneIfNoProductBundles(
       self, mock_get_repos):
-    ffx_runner = self._InitFFXRunWithProductBundleList()
+    self._InitFFXRunWithProductBundleList()
 
     # Forces no product-bundles
     mock_get_repos.return_value = []
 
     # Mutes logs
     with self.assertLogs():
-      self.assertIsNone(
-          update_product_bundles.get_current_signature(ffx_runner))
+      self.assertIsNone(update_product_bundles.get_current_signature())
 
   @mock.patch('update_product_bundles.get_repositories')
   def testGetCurrentSignatureParsesVersionCorrectly(self, mock_get_repos):
-    ffx_runner = self._InitFFXRunWithProductBundleList()
+    self._InitFFXRunWithProductBundleList()
     mock_get_repos.return_value = [{
         'name': 'workstation-eng.chromebook-x64'
     }, {
@@ -271,20 +268,19 @@
     }]
 
     self.assertEqual('10.20221114.2.1',
-                     update_product_bundles.get_current_signature(ffx_runner))
+                     update_product_bundles.get_current_signature())
 
   @mock.patch('update_product_bundles.get_repositories')
   def testGetCurrentSignatureParsesCustomArtifactsCorrectlys(
       self, mock_get_repos):
-    ffx_runner = self._InitFFXRunWithProductBundleList(sdk_version='51390009')
+    self._InitFFXRunWithProductBundleList(sdk_version='51390009')
     mock_get_repos.return_value = [{
         'name': 'workstation-eng.chromebook-x64'
     }, {
         'name': 'terminal.qemu-x64'
     }]
 
-    self.assertEqual('51390009',
-                     update_product_bundles.get_current_signature(ffx_runner))
+    self.assertEqual('51390009', update_product_bundles.get_current_signature())
 
 
 if __name__ == '__main__':
diff --git a/build/rust/tests/bindgen_test/src/lib.rs b/build/rust/tests/bindgen_test/src/lib.rs
index dfd231d..c8672e0 100644
--- a/build/rust/tests/bindgen_test/src/lib.rs
+++ b/build/rust/tests/bindgen_test/src/lib.rs
@@ -4,6 +4,7 @@
 
 mod c_ffi {
     #![allow(dead_code)]
+    #![allow(non_snake_case)]
     #![allow(non_camel_case_types)]
     #![allow(non_upper_case_globals)]
     include!(env!("BINDGEN_RS_FILE"));
diff --git a/buildtools/deps_revisions.gni b/buildtools/deps_revisions.gni
index 16d8d0c..9a37bd1 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 = "af83f5d2fada265d8b5adc0a23a29e060907b3e7"
+  libcxx_revision = "e44019bfac2b2d3ebe1618628884f85c8600e322"
 }
diff --git a/cc/base/features.cc b/cc/base/features.cc
index 583dff9..6c69172 100644
--- a/cc/base/features.cc
+++ b/cc/base/features.cc
@@ -122,4 +122,7 @@
              "MoreAggressiveSolidColorDetection",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
+BASE_FEATURE(kReducedFrameRateEstimation,
+             "kReducedFrameRateEstimation",
+             base::FEATURE_ENABLED_BY_DEFAULT);
 }  // namespace features
diff --git a/cc/base/features.h b/cc/base/features.h
index b853389..ed8f091 100644
--- a/cc/base/features.h
+++ b/cc/base/features.h
@@ -109,6 +109,10 @@
 // tiles.
 CC_BASE_EXPORT BASE_DECLARE_FEATURE(kMoreAggressiveSolidColorDetection);
 
+// Allow CC FrameRateEstimater to reduce the frame rate to half of the default
+// if the condition meets the requirement.
+CC_BASE_EXPORT BASE_DECLARE_FEATURE(kReducedFrameRateEstimation);
+
 }  // namespace features
 
 #endif  // CC_BASE_FEATURES_H_
diff --git a/cc/layers/layer_impl.cc b/cc/layers/layer_impl.cc
index 940ecc2..ab5e8fe 100644
--- a/cc/layers/layer_impl.cc
+++ b/cc/layers/layer_impl.cc
@@ -157,7 +157,7 @@
                 draw_properties_.opacity,
                 effect_node->HasRenderSurface() ? SkBlendMode::kSrcOver
                                                 : effect_node->blend_mode,
-                GetSortingContextId());
+                GetSortingContextId(), static_cast<uint32_t>(id()));
   state->is_fast_rounded_corner = draw_properties_.is_fast_rounded_corner;
 }
 
@@ -194,7 +194,7 @@
                 draw_properties().opacity,
                 effect_node->HasRenderSurface() ? SkBlendMode::kSrcOver
                                                 : effect_node->blend_mode,
-                GetSortingContextId());
+                GetSortingContextId(), static_cast<uint32_t>(id()));
   state->is_fast_rounded_corner = draw_properties().is_fast_rounded_corner;
 }
 
diff --git a/cc/raster/raster_source_unittest.cc b/cc/raster/raster_source_unittest.cc
index ff6a47a..a6470f1 100644
--- a/cc/raster/raster_source_unittest.cc
+++ b/cc/raster/raster_source_unittest.cc
@@ -17,9 +17,14 @@
 #include "cc/test/test_skcanvas.h"
 #include "cc/tiles/software_image_decode_cache.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "third_party/skia/include/core/SkCanvas.h"
+#include "third_party/skia/include/core/SkColor.h"
 #include "third_party/skia/include/core/SkPixelRef.h"
 #include "third_party/skia/include/core/SkRefCnt.h"
+#include "third_party/skia/include/core/SkScalar.h"
 #include "third_party/skia/include/core/SkShader.h"
+#include "third_party/skia/include/core/SkSurfaceProps.h"
 #include "ui/gfx/geometry/axis_transform2d.h"
 #include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/geometry/size_conversions.h"
diff --git a/cc/trees/frame_rate_estimator.cc b/cc/trees/frame_rate_estimator.cc
index 717fe9e..e47b7be6 100644
--- a/cc/trees/frame_rate_estimator.cc
+++ b/cc/trees/frame_rate_estimator.cc
@@ -5,6 +5,7 @@
 #include "cc/trees/frame_rate_estimator.h"
 
 #include "base/task/sequenced_task_runner.h"
+#include "cc/base/features.h"
 #include "components/viz/common/frame_sinks/begin_frame_args.h"
 
 namespace cc {
@@ -24,8 +25,11 @@
 FrameRateEstimator::~FrameRateEstimator() = default;
 
 void FrameRateEstimator::SetFrameEstimationEnabled(bool enabled) {
-  if (enabled == frame_rate_estimation_enabled_)
+  static const bool feature_allowed =
+      base::FeatureList::IsEnabled(features::kReducedFrameRateEstimation);
+  if (!feature_allowed || enabled == frame_rate_estimation_enabled_) {
     return;
+  }
 
   frame_rate_estimation_enabled_ = enabled;
   last_draw_time_ = base::TimeTicks();
diff --git a/cc/trees/layer_tree_host_impl.cc b/cc/trees/layer_tree_host_impl.cc
index a3bc012..89f3522 100644
--- a/cc/trees/layer_tree_host_impl.cc
+++ b/cc/trees/layer_tree_host_impl.cc
@@ -2683,11 +2683,13 @@
       frame->deadline_in_frames.value_or(0u), CurrentBeginFrameArgs().interval,
       frame->use_default_lower_bound_deadline);
 
+  static const bool feature_allowed =
+      base::FeatureList::IsEnabled(features::kReducedFrameRateEstimation);
   constexpr auto kFudgeDelta = base::Milliseconds(1);
   constexpr auto kTwiceOfDefaultInterval =
       viz::BeginFrameArgs::DefaultInterval() * 2;
   constexpr auto kMinDelta = kTwiceOfDefaultInterval - kFudgeDelta;
-  if (mutator_host_->MainThreadAnimationsCount() == 0 &&
+  if (feature_allowed && mutator_host_->MainThreadAnimationsCount() == 0 &&
       !mutator_host_->HasSmilAnimation() &&
       mutator_host_->NeedsTickAnimations() &&
       !frame_rate_estimator_.input_priority_mode() &&
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/AddToBookmarksToolbarButtonController.java b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/AddToBookmarksToolbarButtonController.java
index 36b1474..066dedb 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/AddToBookmarksToolbarButtonController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/AddToBookmarksToolbarButtonController.java
@@ -10,9 +10,13 @@
 
 import org.chromium.base.metrics.RecordUserAction;
 import org.chromium.base.supplier.Supplier;
+import org.chromium.chrome.R;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.toolbar.BaseButtonDataProvider;
 import org.chromium.chrome.browser.toolbar.adaptive.AdaptiveToolbarButtonVariant;
+import org.chromium.chrome.browser.user_education.IPHCommandBuilder;
+import org.chromium.components.feature_engagement.EventConstants;
+import org.chromium.components.feature_engagement.FeatureConstants;
 import org.chromium.components.feature_engagement.Tracker;
 
 /**
@@ -42,10 +46,23 @@
     }
 
     @Override
+    protected IPHCommandBuilder getIphCommandBuilder(Tab tab) {
+        return new IPHCommandBuilder(tab.getContext().getResources(),
+                FeatureConstants
+                        .ADAPTIVE_BUTTON_IN_TOP_TOOLBAR_CUSTOMIZATION_ADD_TO_BOOKMARKS_FEATURE,
+                /* stringId = */ R.string.adaptive_toolbar_button_add_to_bookmarks_iph,
+                /* accessibilityStringId = */
+                R.string.adaptive_toolbar_button_add_to_bookmarks_iph);
+    }
+
+    @Override
     public void onClick(View view) {
         if (!mTabBookmarkerSupplier.hasValue() || !mActiveTabSupplier.hasValue()) return;
 
-        // TODO: Record IPH event using mTrackerSupplier.
+        if (mTrackerSupplier.hasValue()) {
+            mTrackerSupplier.get().notifyEvent(
+                    EventConstants.ADAPTIVE_TOOLBAR_CUSTOMIZATION_ADD_TO_BOOKMARKS_OPENED);
+        }
 
         RecordUserAction.record("MobileTopToolbarAddToBookmarksButton");
         mTabBookmarkerSupplier.get().addOrEditBookmark(mActiveTabSupplier.get());
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/metrics/ActivityTabStartupMetricsTracker.java b/chrome/android/java/src/org/chromium/chrome/browser/metrics/ActivityTabStartupMetricsTracker.java
index 51db9bf2..ab12868 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/metrics/ActivityTabStartupMetricsTracker.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/metrics/ActivityTabStartupMetricsTracker.java
@@ -65,6 +65,7 @@
     private PageLoadMetricsObserverImpl mPageLoadMetricsObserver;
     private boolean mShouldTrackStartupMetrics;
     private boolean mFirstVisibleContentRecorded;
+    private boolean mFirstVisibleContent2Recorded;
     private boolean mVisibleContentRecorded;
 
     // Records whether the tracked first navigation commit was recorded pre-the app being in the
@@ -175,6 +176,7 @@
                 RecordHistogram.recordBooleanHistogram(
                         FIRST_PAINT_OCCURRED_PRE_FOREGROUND_HISTOGRAM, false);
                 recordFirstVisibleContent(durationMs);
+                recordFirstVisibleContent2(durationMs);
                 recordVisibleContent(durationMs);
             }
 
@@ -252,6 +254,7 @@
             mFirstCommitTimeMs = SystemClock.uptimeMillis() - mActivityStartTimeMs;
             RecordHistogram.recordMediumTimesHistogram(
                     "Startup.Android.Cold.TimeToFirstNavigationCommit2.Tabbed", mFirstCommitTimeMs);
+            recordFirstVisibleContent2(mFirstCommitTimeMs);
         }
 
         mShouldTrackStartupMetrics = false;
@@ -296,9 +299,11 @@
     }
 
     /**
-     * Record the time to first visible content. This metric acts as the Clank cold start guardian
-     * metric. Reports the minimum value of
-     * Startup.Android.Cold.TimeToFirstNavigationCommit.Tabbed and
+     * Records the legacy version of the time to first visible content.
+     *
+     * This metric acts as the Clank cold start guardian metric.
+     *
+     * Reports the minimum value of Startup.Android.Cold.TimeToFirstNavigationCommit.Tabbed and
      * Browser.PaintPreview.TabbedPlayer.TimeToFirstBitmap.
      *
      * @param durationMs duration in millis.
@@ -312,6 +317,24 @@
     }
 
     /**
+     * Records the time to first visible content.
+     *
+     * This metric aims to become the new the Clank cold start guardian metric.
+     *
+     * Reports the minimum value of Startup.Android.Cold.TimeToFirstNavigationCommit2.Tabbed and
+     * Browser.PaintPreview.TabbedPlayer.TimeToFirstBitmap.
+     *
+     * @param durationMs duration in millis.
+     */
+    private void recordFirstVisibleContent2(long durationMs) {
+        if (mFirstVisibleContent2Recorded) return;
+
+        mFirstVisibleContent2Recorded = true;
+        RecordHistogram.recordMediumTimesHistogram(
+                "Startup.Android.Cold.TimeToFirstVisibleContent2", durationMs);
+    }
+
+    /**
      * Record the first Visible Content time.
      * This metric reports the minimum value of
      * Startup.Android.Cold.TimeToFirstContentfulPaint.Tabbed and
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/RequestDesktopUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/RequestDesktopUtils.java
index acabeafa..44ec01a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/RequestDesktopUtils.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/RequestDesktopUtils.java
@@ -8,6 +8,7 @@
 import android.content.res.Resources;
 import android.os.Build;
 import android.text.TextUtils;
+import android.view.Display;
 
 import androidx.annotation.IntDef;
 import androidx.annotation.NonNull;
@@ -51,6 +52,7 @@
 import org.chromium.components.ukm.UkmRecorder;
 import org.chromium.content_public.browser.ContentFeatureList;
 import org.chromium.ui.display.DisplayAndroid;
+import org.chromium.ui.display.DisplayAndroidManager;
 import org.chromium.ui.modaldialog.DialogDismissalCause;
 import org.chromium.ui.modaldialog.ModalDialogManager;
 import org.chromium.ui.modaldialog.ModalDialogManager.ModalDialogType;
@@ -95,6 +97,8 @@
             "default_on_on_low_end_devices";
     static final String PARAM_GLOBAL_SETTING_DEFAULT_ON_ON_X86_DEVICES =
             "default_on_on_x86_devices";
+    static final String PARAM_GLOBAL_SETTING_DEFAULT_ON_ON_EXTERNAL_DISPLAY =
+            "default_on_on_external_display";
     static final String PARAM_GLOBAL_SETTING_DEFAULT_ON_SMALLEST_SCREEN_WIDTH =
             "default_on_smallest_screen_width";
     static final int DEFAULT_GLOBAL_SETTING_DEFAULT_ON_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP = 600;
@@ -391,13 +395,12 @@
             return false;
         }
 
-        // TODO(shuyng): Add downgrade path support for smallestScreenWidthDp or displaySizeInInches
-        //  change.
         // If the smallest screen size in dp is below threshold, avoid default-enabling the setting.
         if (context.getResources().getConfiguration().smallestScreenWidthDp
                 < ChromeFeatureList.getFieldTrialParamByFeatureAsInt(feature,
                         PARAM_GLOBAL_SETTING_DEFAULT_ON_SMALLEST_SCREEN_WIDTH,
                         DEFAULT_GLOBAL_SETTING_DEFAULT_ON_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP)) {
+            // TODO(shuyng): Add downgrade path support for smallestScreenWidthDp change.
             return false;
         }
 
@@ -416,12 +419,17 @@
         }
 
         SharedPreferencesManager sharedPreferencesManager = SharedPreferencesManager.getInstance();
+        boolean isOnExternalDisplay =
+                !ChromeFeatureList.getFieldTrialParamByFeatureAsBoolean(
+                        feature, PARAM_GLOBAL_SETTING_DEFAULT_ON_ON_EXTERNAL_DISPLAY, false)
+                && isOnExternalDisplay(context);
         double screenSizeThreshold = ChromeFeatureList.getFieldTrialParamByFeatureAsDouble(feature,
                 PARAM_GLOBAL_SETTING_DEFAULT_ON_DISPLAY_SIZE_THRESHOLD_INCHES,
                 DEFAULT_GLOBAL_SETTING_DEFAULT_ON_DISPLAY_SIZE_THRESHOLD_INCHES);
-        if (displaySizeInInches < screenSizeThreshold
+        if (displaySizeInInches < screenSizeThreshold && !isOnExternalDisplay
                 && sharedPreferencesManager.contains(
                         ChromePreferenceKeys.DEFAULT_ENABLE_DESKTOP_SITE_GLOBAL_SETTING_COHORT)) {
+            // TODO(shuyng): Add downgrade path support for displaySizeInInches change.
             silentlyReportingCrashes(
                     context, displaySizeInInches, "Display size falls below threshold");
         }
@@ -432,7 +440,8 @@
                 SingleCategorySettingsConstants
                         .USER_ENABLED_DESKTOP_SITE_GLOBAL_SETTING_PREFERENCE_KEY);
 
-        boolean inCohort = !previouslyUpdatedByUser && displaySizeInInches >= screenSizeThreshold;
+        boolean inCohort = !previouslyUpdatedByUser && displaySizeInInches >= screenSizeThreshold
+                && !isOnExternalDisplay;
         boolean wouldEnable = !previouslyDefaultEnabled && inCohort;
         if (wouldEnable) {
             // Store a SharedPreferences key to tag the device as qualified for the feature
@@ -469,11 +478,11 @@
         Configuration config = context.getResources().getConfiguration();
         String logMessage = String.format(Locale.US,
                 message + ", silently reporting crashes for debugging, displaySizeInInches: %.1f "
-                        + "displayWidth: %d displayHeight: %d xdpi: %.1f ydpi: %.1f "
-                        + "densityDpi: %d screenWidthDp: %d screenHeightDp: %d",
+                        + "displayWidth: %d displayHeight: %d xdpi: %.1f ydpi: %.1f densityDpi: %d "
+                        + "screenWidthDp: %d screenHeightDp: %d onExternalDisplay: %b",
                 displaySizeInInches, display.getDisplayWidth(), display.getDisplayHeight(),
                 display.getXdpi(), display.getYdpi(), config.densityDpi, config.screenWidthDp,
-                config.screenHeightDp);
+                config.screenHeightDp, isOnExternalDisplay(context));
         SharedPreferencesManager sharedPreferencesManager = SharedPreferencesManager.getInstance();
         String previousDisplaySpec = sharedPreferencesManager.readString(
                 ChromePreferenceKeys.DESKTOP_SITE_GLOBAL_SETTING_DEFAULT_ON_COHORT_DISPLAY_SPEC,
@@ -494,10 +503,10 @@
         String displaySpec = String.format(Locale.US,
                 "lastDisplaySizeInInches: %.1f lastDisplayWidth: %d lastDisplayHeight: %d "
                         + "lastXdpi: %.1f lastYdpi: %.1f lastDensityDpi: %d "
-                        + "lastScreenWidthDp: %d lastScreenHeightDp: %d",
+                        + "lastScreenWidthDp: %d lastScreenHeightDp: %d lastOnExternalDisplay: %b",
                 displaySizeInInches, display.getDisplayWidth(), display.getDisplayHeight(),
                 display.getXdpi(), display.getYdpi(), config.densityDpi, config.screenWidthDp,
-                config.screenHeightDp);
+                config.screenHeightDp, isOnExternalDisplay(context));
         sharedPreferencesManager.writeString(
                 ChromePreferenceKeys.DESKTOP_SITE_GLOBAL_SETTING_DEFAULT_ON_COHORT_DISPLAY_SPEC,
                 displaySpec);
@@ -976,4 +985,9 @@
         }
         return abiStrings[0].toLowerCase(Locale.ROOT).contains("arm");
     }
+
+    private static boolean isOnExternalDisplay(Context context) {
+        Display display = DisplayAndroidManager.getDefaultDisplayForContext(context);
+        return display.getDisplayId() != Display.DEFAULT_DISPLAY;
+    }
 }
\ No newline at end of file
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java
index 7283193..3501d5e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java
@@ -41,6 +41,7 @@
 import org.chromium.chrome.browser.app.omnibox.ActionChipsDelegateImpl;
 import org.chromium.chrome.browser.app.tab_activity_glue.TabReparentingController;
 import org.chromium.chrome.browser.back_press.BackPressManager;
+import org.chromium.chrome.browser.bookmarks.AddToBookmarksToolbarButtonController;
 import org.chromium.chrome.browser.bookmarks.BookmarkModel;
 import org.chromium.chrome.browser.bookmarks.TabBookmarker;
 import org.chromium.chrome.browser.browser_controls.BrowserControlsStateProvider;
@@ -1135,6 +1136,11 @@
                             AppCompatResources.getDrawable(mActivity, R.drawable.new_tab_icon),
                             mActivityLifecycleDispatcher, mTabCreatorManagerSupplier,
                             mActivityTabProvider, trackerSupplier);
+            AddToBookmarksToolbarButtonController addToBookmarksToolbarButtonController =
+                    new AddToBookmarksToolbarButtonController(mActivityTabProvider,
+                            AppCompatResources.getDrawable(mActivity, R.drawable.btn_star),
+                            mActivity.getString(R.string.menu_bookmark), mTabBookmarkerSupplier,
+                            trackerSupplier);
             AdaptiveToolbarButtonController adaptiveToolbarButtonController =
                     new AdaptiveToolbarButtonController(mActivity, new SettingsLauncherImpl(),
                             mActivityLifecycleDispatcher, new AdaptiveButtonActionMenuCoordinator(),
@@ -1146,6 +1152,9 @@
             adaptiveToolbarButtonController.addButtonVariant(
                     AdaptiveToolbarButtonVariant.VOICE, voiceToolbarButtonController);
             adaptiveToolbarButtonController.addButtonVariant(
+                    AdaptiveToolbarButtonVariant.ADD_TO_BOOKMARKS,
+                    addToBookmarksToolbarButtonController);
+            adaptiveToolbarButtonController.addButtonVariant(
                     AdaptiveToolbarButtonVariant.TRANSLATE, translateToolbarButtonController);
             adaptiveToolbarButtonController.addButtonVariant(
                     AdaptiveToolbarButtonVariant.PRICE_TRACKING, priceTrackingButtonController);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/metrics/StartupLoadingMetricsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/metrics/StartupLoadingMetricsTest.java
index b422e8f..8710b13 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/metrics/StartupLoadingMetricsTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/metrics/StartupLoadingMetricsTest.java
@@ -58,6 +58,8 @@
             "Startup.Android.Cold.TimeToFirstContentfulPaint";
     private static final String FIRST_VISIBLE_CONTENT_HISTOGRAM =
             "Startup.Android.Cold.TimeToFirstVisibleContent";
+    private static final String FIRST_VISIBLE_CONTENT_HISTOGRAM2 =
+            "Startup.Android.Cold.TimeToFirstVisibleContent2";
     private static final String VISIBLE_CONTENT_HISTOGRAM =
             "Startup.Android.Cold.TimeToVisibleContent";
     private static final String FIRST_COMMIT_OCCURRED_PRE_FOREGROUND_HISTOGRAM =
@@ -178,6 +180,9 @@
             Assert.assertEquals(firstCommitSamples,
                     RecordHistogram.getHistogramTotalCountForTesting(
                             FIRST_VISIBLE_CONTENT_HISTOGRAM));
+            Assert.assertEquals(expectedCount,
+                    RecordHistogram.getHistogramTotalCountForTesting(
+                            FIRST_VISIBLE_CONTENT_HISTOGRAM2));
         }
     }
 
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/bookmarks/AddToBookmarksToolbarButtonControllerUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/bookmarks/AddToBookmarksToolbarButtonControllerUnitTest.java
index bc32a1b..2b1192a 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/bookmarks/AddToBookmarksToolbarButtonControllerUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/bookmarks/AddToBookmarksToolbarButtonControllerUnitTest.java
@@ -29,6 +29,7 @@
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.toolbar.ButtonData;
+import org.chromium.components.feature_engagement.EventConstants;
 import org.chromium.components.feature_engagement.Tracker;
 import org.chromium.content_public.browser.WebContents;
 
@@ -97,6 +98,8 @@
 
         Assert.assertEquals(
                 1, mActionTester.getActionCount("MobileTopToolbarAddToBookmarksButton"));
+        verify(mTracker).notifyEvent(
+                EventConstants.ADAPTIVE_TOOLBAR_CUSTOMIZATION_ADD_TO_BOOKMARKS_OPENED);
         verify(mTabBookmarker).addOrEditBookmark(mTab);
     }
 }
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/share/ShareHelperUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/share/ShareHelperUnitTest.java
index c50970e..0dc419cbb 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/share/ShareHelperUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/share/ShareHelperUnitTest.java
@@ -273,6 +273,26 @@
         ShareHelper.shareWithSystemShareSheetUi(emptyShareParams(), null, false, provider);
     }
 
+    @Test
+    public void shareWithPreviewUri() {
+        ShareParams params = new ShareParams.Builder(mWindow, "title", JUnitTestGURLs.EXAMPLE_URL)
+                                     .setPreviewImageUri(mImageUri)
+                                     .setBypassFixingDomDistillerUrl(true)
+                                     .build();
+        ShareHelper.shareWithSystemShareSheetUi(params, null, true);
+
+        Intent nextIntent = Shadows.shadowOf(mActivity).peekNextStartedActivity();
+        assertNotNull("Shared intent is null.", nextIntent);
+        assertEquals(
+                "Intent is not a chooser intent.", Intent.ACTION_CHOOSER, nextIntent.getAction());
+
+        // Verify the intent has the right preview Uri.
+        Intent sharingIntent = nextIntent.getParcelableExtra(Intent.EXTRA_INTENT);
+        assertEquals("Intent is not a SEND intent.", Intent.ACTION_SEND, sharingIntent.getAction());
+        assertEquals("Preview image Uri not set correctly.", mImageUri,
+                sharingIntent.getClipData().getItemAt(0).getUri());
+    }
+
     private void selectComponentFromChooserIntent(Intent chooserIntent, ComponentName componentName)
             throws SendIntentException {
         Intent sendBackIntent = new Intent().putExtra(Intent.EXTRA_CHOSEN_COMPONENT, componentName);
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/tab/RequestDesktopUtilsUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/tab/RequestDesktopUtilsUnitTest.java
index 246efc7..e6dc132 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/tab/RequestDesktopUtilsUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/tab/RequestDesktopUtilsUnitTest.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.os.Build;
+import android.view.Display;
 
 import androidx.test.core.app.ApplicationProvider;
 
@@ -54,6 +55,7 @@
 import org.chromium.chrome.browser.preferences.SharedPreferencesManager;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.tab.RequestDesktopUtilsUnitTest.ShadowDisplayAndroid;
+import org.chromium.chrome.browser.tab.RequestDesktopUtilsUnitTest.ShadowDisplayAndroidManager;
 import org.chromium.chrome.browser.tab.RequestDesktopUtilsUnitTest.ShadowSysUtils;
 import org.chromium.chrome.browser.tab.RequestDesktopUtilsUnitTest.ShadowUmaSessionStats;
 import org.chromium.chrome.browser.tab.TabUtilsUnitTest.ShadowProfile;
@@ -77,6 +79,7 @@
 import org.chromium.content_public.browser.NavigationController;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.ui.display.DisplayAndroid;
+import org.chromium.ui.display.DisplayAndroidManager;
 import org.chromium.ui.modaldialog.DialogDismissalCause;
 import org.chromium.ui.modaldialog.ModalDialogManager;
 import org.chromium.ui.modaldialog.ModalDialogManager.ModalDialogType;
@@ -100,7 +103,8 @@
 @RunWith(BaseRobolectricTestRunner.class)
 @Config(manifest = Config.NONE,
         shadows = {ShadowGURL.class, ShadowSysUtils.class, ShadowProfile.class,
-                ShadowUmaSessionStats.class, ShadowDisplayAndroid.class})
+                ShadowUmaSessionStats.class, ShadowDisplayAndroid.class,
+                ShadowDisplayAndroidManager.class})
 public class RequestDesktopUtilsUnitTest {
     @Rule
     public JniMocker mJniMocker = new JniMocker();
@@ -172,6 +176,20 @@
         }
     }
 
+    @Implements(DisplayAndroidManager.class)
+    static class ShadowDisplayAndroidManager {
+        private static Display sDisplay;
+
+        public static void setDisplay(Display display) {
+            sDisplay = display;
+        }
+
+        @Implementation
+        public static Display getDefaultDisplayForContext(Context context) {
+            return sDisplay;
+        }
+    }
+
     @Mock
     private WebsitePreferenceBridge.Natives mWebsitePreferenceBridgeJniMock;
     @Mock
@@ -196,6 +214,8 @@
     private ObservableSupplier<Tab> mCurrentTabSupplier;
     @Mock
     private DisplayAndroid mDisplayAndroid;
+    @Mock
+    private Display mDisplay;
 
     private Tab mTab;
     private @ContentSettingValues int mRdsDefaultValue;
@@ -271,6 +291,8 @@
         when(mDisplayAndroid.getDisplayHeight()).thenReturn(2560);
         when(mDisplayAndroid.getXdpi()).thenReturn(275.5f);
         when(mDisplayAndroid.getYdpi()).thenReturn(276.5f);
+        ShadowDisplayAndroidManager.setDisplay(mDisplay);
+        when(mDisplay.getDisplayId()).thenReturn(Display.DEFAULT_DISPLAY);
         enableFeatureWithParams(
                 ChromeFeatureList.REQUEST_DESKTOP_SITE_DEFAULTS_LOGGING, null, false);
     }
@@ -456,6 +478,27 @@
     }
 
     @Test
+    public void testShouldDefaultEnableGlobalSetting_ExternalDisplay() {
+        Map<String, String> params = new HashMap<>();
+        enableFeatureWithParams(ChromeFeatureList.REQUEST_DESKTOP_SITE_DEFAULTS, params, true);
+        when(mDisplay.getDisplayId()).thenReturn(/*non built-in display*/ 2);
+        boolean shouldDefaultEnable = RequestDesktopUtils.shouldDefaultEnableGlobalSetting(
+                RequestDesktopUtils.DEFAULT_GLOBAL_SETTING_DEFAULT_ON_DISPLAY_SIZE_THRESHOLD_INCHES,
+                mActivity);
+        Assert.assertFalse(
+                "Desktop site global setting should not be default-enabled on external display",
+                shouldDefaultEnable);
+
+        when(mDisplay.getDisplayId()).thenReturn(Display.DEFAULT_DISPLAY);
+        shouldDefaultEnable = RequestDesktopUtils.shouldDefaultEnableGlobalSetting(
+                RequestDesktopUtils.DEFAULT_GLOBAL_SETTING_DEFAULT_ON_DISPLAY_SIZE_THRESHOLD_INCHES,
+                mActivity);
+        Assert.assertTrue(
+                "Desktop site global setting should be default-enabled on built-in display",
+                shouldDefaultEnable);
+    }
+
+    @Test
     public void testShouldDefaultEnableGlobalSetting_WithLogging() {
         Map<String, String> params = new HashMap<>();
         params.put(
diff --git a/chrome/app/chrome_main_delegate.cc b/chrome/app/chrome_main_delegate.cc
index 99c6ee4f..d4b31d1 100644
--- a/chrome/app/chrome_main_delegate.cc
+++ b/chrome/app/chrome_main_delegate.cc
@@ -939,7 +939,6 @@
   }
   base::HangWatcher::InitializeOnMainThread(hang_watcher_process_type);
 
-  base::internal::TimerBase::InitializeFeatures();
   base::InitializeCpuReductionExperiment();
   base::sequence_manager::internal::SequenceManagerImpl::InitializeFeatures();
 #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID)
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index eacc9c13..d17bb8e 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -5958,6 +5958,7 @@
       "//chrome/services/mac_notifications/public/mojom",
       "//components/crash/core/app",
       "//components/metal_util",
+      "//components/policy/core/common:common_constants",
       "//components/power_metrics",
       "//components/remote_cocoa/browser:browser",
       "//sandbox/mac:seatbelt",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 5559a2f..3cc7af8 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -4231,6 +4231,12 @@
      flag_descriptions::kAdaptiveButtonInTopToolbarTranslateDescription,
      kOsAndroid,
      FEATURE_VALUE_TYPE(chrome::android::kAdaptiveButtonInTopToolbarTranslate)},
+    {"adaptive-button-in-top-toolbar-add-to-bookmarks",
+     flag_descriptions::kAdaptiveButtonInTopToolbarAddToBookmarksName,
+     flag_descriptions::kAdaptiveButtonInTopToolbarAddToBookmarksDescription,
+     kOsAndroid,
+     FEATURE_VALUE_TYPE(
+         chrome::android::kAdaptiveButtonInTopToolbarAddToBookmarks)},
     {"adaptive-button-in-top-toolbar-customization",
      flag_descriptions::kAdaptiveButtonInTopToolbarCustomizationName,
      flag_descriptions::kAdaptiveButtonInTopToolbarCustomizationDescription,
@@ -5862,6 +5868,15 @@
          kPageContentAnnotationsVariations,
          "PageContentAnnotations")},
 
+    {"page-content-annotations-persist-salient-image-metadata",
+     flag_descriptions::kPageContentAnnotationsPersistSalientImageMetadataName,
+     flag_descriptions::
+         kPageContentAnnotationsPersistSalientImageMetadataDescription,
+     kOsDesktop,
+     FEATURE_VALUE_TYPE(
+         optimization_guide::features::
+             kPageContentAnnotationsPersistSalientImageMetadata)},
+
     {"page-entities-page-content-annotations",
      flag_descriptions::kPageEntitiesPageContentAnnotationsName,
      flag_descriptions::kPageEntitiesPageContentAnnotationsDescription,
@@ -6067,6 +6082,10 @@
      flag_descriptions::kNtpReducedLogoSpaceDescription, kOsDesktop,
      FEATURE_VALUE_TYPE(ntp_features::kNtpReducedLogoSpace)},
 
+    {"ntp-single-row-shortcuts", flag_descriptions::kNtpSingleRowShortcutsName,
+     flag_descriptions::kNtpSingleRowShortcutsDescription, kOsDesktop,
+     FEATURE_VALUE_TYPE(ntp_features::kNtpSingleRowShortcuts)},
+
     {"ntp-middle-slot-promo-dismissal",
      flag_descriptions::kNtpMiddleSlotPromoDismissalName,
      flag_descriptions::kNtpMiddleSlotPromoDismissalDescription, kOsDesktop,
@@ -7567,6 +7586,10 @@
      flag_descriptions::kShimlessRMADisableDarkModeName,
      flag_descriptions::kShimlessRMADisableDarkModeDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(ash::features::kShimlessRMADisableDarkMode)},
+    {"shimless-rma-diagnostic-page",
+     flag_descriptions::kShimlessRMADiagnosticPageName,
+     flag_descriptions::kShimlessRMADiagnosticPageDescription, kOsCrOS,
+     FEATURE_VALUE_TYPE(ash::features::kShimlessRMADisableDarkMode)},
     {"nearby-sharing-self-share-auto-accept",
      flag_descriptions::kNearbySharingSelfShareAutoAcceptName,
      flag_descriptions::kNearbySharingSelfShareAutoAcceptDescription, kOsCrOS,
@@ -9689,6 +9712,11 @@
      flag_descriptions::kPassthroughYuvRgbConversionDescription, kOsAll,
      FEATURE_VALUE_TYPE(features::kPassthroughYuvRgbConversion)},
 
+    {"use-multi-plane-format-for-hardware-video",
+     flag_descriptions::kUseMultiPlaneFormatForHardwareVideoName,
+     flag_descriptions::kUseMultiPlaneFormatForHardwareVideoDescription, kOsAll,
+     FEATURE_VALUE_TYPE(media::kUseMultiPlaneFormatForHardwareVideo)},
+
     {"policy-merge-multi-source",
      flag_descriptions::kPolicyMergeMultiSourceName,
      flag_descriptions::kPolicyMergeMultiSourceDescription, kOsAll,
diff --git a/chrome/browser/app_controller_mac.mm b/chrome/browser/app_controller_mac.mm
index 53752c95..9d3d078 100644
--- a/chrome/browser/app_controller_mac.mm
+++ b/chrome/browser/app_controller_mac.mm
@@ -50,6 +50,7 @@
 #include "chrome/browser/policy/chrome_browser_policy_connector.h"
 #include "chrome/browser/prefs/incognito_mode_prefs.h"
 #include "chrome/browser/profiles/keep_alive/profile_keep_alive_types.h"
+#include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_attributes_entry.h"
 #include "chrome/browser/profiles/profile_attributes_storage.h"
 #include "chrome/browser/profiles/profile_manager.h"
@@ -83,6 +84,8 @@
 #import "chrome/browser/ui/cocoa/tab_menu_bridge.h"
 #include "chrome/browser/ui/extensions/application_launch.h"
 #include "chrome/browser/ui/profile_picker.h"
+#include "chrome/browser/ui/startup/first_run_service.h"
+#include "chrome/browser/ui/startup/launch_mode_recorder.h"
 #include "chrome/browser/ui/startup/startup_browser_creator.h"
 #include "chrome/browser/ui/startup/startup_browser_creator_impl.h"
 #include "chrome/browser/ui/startup/startup_tab.h"
@@ -106,6 +109,7 @@
 #include "components/handoff/handoff_utility.h"
 #include "components/keep_alive_registry/keep_alive_types.h"
 #include "components/keep_alive_registry/scoped_keep_alive.h"
+#include "components/policy/core/common/policy_pref_names.h"
 #include "components/prefs/pref_service.h"
 #include "components/sessions/core/tab_restore_service.h"
 #include "content/public/browser/download_manager.h"
@@ -178,9 +182,36 @@
   return browser;
 }
 
+// Launches a browser window associated with |profile|. Checks if we are in the
+// first run of Chrome to decide if we need to launch a browser or not.
+// The profile can be `nullptr` and in that case the last-used profile will be
+// used.
+void LaunchBrowserStartup(Profile* profile) {
+  if (StartupProfileModeFromReason(ProfilePicker::GetStartupModeReason()) ==
+      StartupProfileMode::kProfilePicker) {
+    ProfilePicker::Show(ProfilePicker::Params::FromEntryPoint(
+        ProfilePicker::EntryPoint::kNewSessionOnExistingProcess));
+    return;
+  }
+  CHECK(profile);
+
+  base::AutoReset<bool> auto_reset_in_run(&g_is_opening_new_window, true);
+  StartupBrowserCreator browser_creator;
+  browser_creator.LaunchBrowser(*base::CommandLine::ForCurrentProcess(),
+                                profile, base::FilePath(),
+                                chrome::startup::IsProcessStartup::kNo,
+                                chrome::startup::IsFirstRun::kYes, nullptr);
+}
+
 // Creates an empty browser window with the given profile and returns a pointer
 // to the new |Browser|.
 Browser* CreateBrowser(Profile* profile) {
+  // Closes the first run if we open a new window.
+  if (auto* fre_service =
+          FirstRunServiceFactory::GetForBrowserContext(profile)) {
+    fre_service->FinishFirstRunWithoutResumeTask();
+  }
+
   {
     base::AutoReset<bool> auto_reset_in_run(&g_is_opening_new_window, true);
     chrome::NewEmptyWindow(profile);
@@ -213,15 +244,8 @@
     // Session was restored.
     return;
   }
-
   // No session to restore, proceed with normal startup.
-  if (StartupProfileModeFromReason(ProfilePicker::GetStartupModeReason()) ==
-      StartupProfileMode::kProfilePicker) {
-    ProfilePicker::Show(ProfilePicker::Params::FromEntryPoint(
-        ProfilePicker::EntryPoint::kNewSessionOnExistingProcess));
-  } else {
-    CreateBrowser(profile);
-  }
+  LaunchBrowserStartup(profile);
 }
 
 CFStringRef BaseBundleID_CFString() {
@@ -1412,6 +1436,7 @@
         ProfilePicker::EntryPoint::kNewSessionOnExistingProcess));
   } else {
     // Asynchronously load profile first if needed.
+    // TODO(crbug.com/1426857): Replace CreateBrowser by LaunchBrowserStartup
     app_controller_mac::RunInLastProfileSafely(
         base::BindOnce(base::IgnoreResult(&CreateBrowser)),
         app_controller_mac::kShowProfilePickerOnFailure);
@@ -1755,7 +1780,7 @@
   _profilePrefRegistrar = std::make_unique<PrefChangeRegistrar>();
   _profilePrefRegistrar->Init(_lastProfile->GetPrefs());
   _profilePrefRegistrar->Add(
-      prefs::kIncognitoModeAvailability,
+      policy::policy_prefs::kIncognitoModeAvailability,
       base::BindRepeating(&chrome::BrowserCommandController::
                               UpdateSharedCommandsForIncognitoAvailability,
                           _menuState.get(), _lastProfile));
diff --git a/chrome/browser/app_controller_mac_browsertest.mm b/chrome/browser/app_controller_mac_browsertest.mm
index f3fc82d..aa82566 100644
--- a/chrome/browser/app_controller_mac_browsertest.mm
+++ b/chrome/browser/app_controller_mac_browsertest.mm
@@ -32,6 +32,7 @@
 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
 #include "chrome/browser/browser_features.h"
 #include "chrome/browser/browser_process.h"
+#include "chrome/browser/first_run/first_run.h"
 #include "chrome/browser/history/history_service_factory.h"
 #include "chrome/browser/lifetime/application_lifetime.h"
 #include "chrome/browser/lifetime/application_lifetime_desktop.h"
@@ -57,7 +58,9 @@
 #include "chrome/browser/ui/cocoa/last_active_browser_cocoa.h"
 #include "chrome/browser/ui/cocoa/test/run_loop_testing.h"
 #include "chrome/browser/ui/profile_picker.h"
+#include "chrome/browser/ui/profile_ui_test_utils.h"
 #include "chrome/browser/ui/search/ntp_test_utils.h"
+#include "chrome/browser/ui/startup/first_run_service.h"
 #include "chrome/browser/ui/tabs/tab_enums.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/browser/ui/ui_features.h"
@@ -634,6 +637,49 @@
   EXPECT_TRUE(browser_added_observer.Wait());
 }
 
+class AppControllerFirstRunBrowserTest : public AppControllerBrowserTest {
+ public:
+  void SetUpDefaultCommandLine(base::CommandLine* command_line) override {
+    InProcessBrowserTest::SetUpDefaultCommandLine(command_line);
+    command_line->RemoveSwitch(switches::kNoFirstRun);
+  }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_{kForYouFre};
+};
+
+IN_PROC_BROWSER_TEST_F(AppControllerFirstRunBrowserTest,
+                       OpenNewWindowWhileFreIsRunning) {
+  EXPECT_TRUE(ProfilePicker::IsFirstRunOpen());
+  EXPECT_EQ(BrowserList::GetInstance()->size(), 0u);
+  AppController* ac = base::mac::ObjCCast<AppController>(
+      [[NSApplication sharedApplication] delegate]);
+  ASSERT_TRUE(ac);
+  NSMenu* menu = [ac applicationDockMenu:NSApp];
+  ASSERT_TRUE(menu);
+
+  NSMenuItem* item = [menu itemWithTag:IDC_NEW_WINDOW];
+  ASSERT_TRUE(item);
+  [ac commandDispatch:item];
+
+  profiles::testing::WaitForPickerClosed();
+  EXPECT_FALSE(ProfilePicker::IsFirstRunOpen());
+  EXPECT_EQ(BrowserList::GetInstance()->size(), 1u);
+}
+
+IN_PROC_BROWSER_TEST_F(AppControllerFirstRunBrowserTest,
+                       ClickingChromeDockIconDoesNotOpenBrowser) {
+  EXPECT_TRUE(ProfilePicker::IsFirstRunOpen());
+  EXPECT_EQ(BrowserList::GetInstance()->size(), 0u);
+  AppController* ac = base::mac::ObjCCast<AppController>(
+      [[NSApplication sharedApplication] delegate]);
+  ASSERT_TRUE(ac);
+  [ac applicationShouldHandleReopen:NSApp hasVisibleWindows:NO];
+
+  EXPECT_EQ(BrowserList::GetInstance()->size(), 0u);
+  ProfilePicker::Hide();
+}
+
 class AppControllerOpenShortcutBrowserTest : public InProcessBrowserTest {
  protected:
   AppControllerOpenShortcutBrowserTest()
diff --git a/chrome/browser/ash/app_list/search/app_discovery_metrics_manager.cc b/chrome/browser/ash/app_list/search/app_discovery_metrics_manager.cc
index fb6f415..541d7ed 100644
--- a/chrome/browser/ash/app_list/search/app_discovery_metrics_manager.cc
+++ b/chrome/browser/ash/app_list/search/app_discovery_metrics_manager.cc
@@ -55,6 +55,7 @@
       .SetAppId(app_id)
       .SetAppName(std::string(app_title.begin(), app_title.end()))
       .SetFuzzyStringMatch(string_match_score)
+      .SetResultCategory(result->metrics_type())
       .Record();
 }
 
diff --git a/chrome/browser/ash/app_list/search/app_discovery_metrics_manager_unittest.cc b/chrome/browser/ash/app_list/search/app_discovery_metrics_manager_unittest.cc
index 95012dfc..69446fd 100644
--- a/chrome/browser/ash/app_list/search/app_discovery_metrics_manager_unittest.cc
+++ b/chrome/browser/ash/app_list/search/app_discovery_metrics_manager_unittest.cc
@@ -99,7 +99,8 @@
   void ValidateAppLaunchEvent(const metrics::structured::Event& event,
                               const std::string& app_id,
                               const std::u16string& app_title,
-                              double match_score) {
+                              double match_score,
+                              const ash::SearchResultType search_result_type) {
     cros_events::AppDiscovery_AppLauncherResultOpened expected_event;
 
     EXPECT_EQ(expected_event.project_name(), event.project_name());
@@ -107,7 +108,8 @@
 
     expected_event.SetAppId(app_id)
         .SetAppName(std::string(app_title.begin(), app_title.end()))
-        .SetFuzzyStringMatch(match_score);
+        .SetFuzzyStringMatch(match_score)
+        .SetResultCategory(search_result_type);
 
     EXPECT_EQ(expected_event.metric_values(), event.metric_values());
   }
@@ -143,14 +145,16 @@
   const std::u16string app_name = u"app_name";
   const std::u16string query = u"app_name";
   const std::string app_id = "id";
+  const auto search_result_type =
+      ash::SearchResultType::PLAY_STORE_UNINSTALLED_APP;
 
-  TestSearchResult search_result(
-      app_id, app_name, ash::SearchResultType::PLAY_STORE_UNINSTALLED_APP);
+  TestSearchResult search_result(app_id, app_name, search_result_type);
 
   base::RunLoop run_loop;
   auto record_callback = base::BindLambdaForTesting(
       [&, this](const metrics::structured::Event& event) {
-        ValidateAppLaunchEvent(event, app_id, app_name, /*match_score=*/1.0);
+        ValidateAppLaunchEvent(event, app_id, app_name, /*match_score=*/1.0,
+                               search_result_type);
         run_loop.Quit();
       });
   test_structured_metrics_provider()->SetOnEventsRecordClosure(record_callback);
@@ -163,20 +167,21 @@
   const std::u16string app_name = u"app_name";
   const std::u16string query = u"app_name";
   const std::string app_id = "id";
+  const auto search_result_type =
+      ash::SearchResultType::PLAY_STORE_UNINSTALLED_APP;
 
   // Disable app-sync.
   sync_service()->SetDisableReasons(
       syncer::SyncService::DISABLE_REASON_ENTERPRISE_POLICY);
 
-  TestSearchResult search_result(
-      app_id, app_name, ash::SearchResultType::PLAY_STORE_UNINSTALLED_APP);
+  TestSearchResult search_result(app_id, app_name, search_result_type);
 
   base::RunLoop run_loop;
   auto record_callback = base::BindLambdaForTesting(
       [&, this](const metrics::structured::Event& event) {
         // App ID and app name will be stripped if app sync is disabled.
         ValidateAppLaunchEvent(event, /*app_id=*/"", /*app_title=*/u"",
-                               /*match_score=*/1.0);
+                               /*match_score=*/1.0, search_result_type);
         run_loop.Quit();
       });
   test_structured_metrics_provider()->SetOnEventsRecordClosure(record_callback);
diff --git a/chrome/browser/autofill/autofill_interactive_uitest.cc b/chrome/browser/autofill/autofill_interactive_uitest.cc
index 2dd7d20..49fc7c6 100644
--- a/chrome/browser/autofill/autofill_interactive_uitest.cc
+++ b/chrome/browser/autofill/autofill_interactive_uitest.cc
@@ -664,16 +664,30 @@
     {"address2", ""},  {"city", ""},     {"state", ""},
     {"zip", ""},       {"country", ""},  {"phone", ""}};
 
+const struct {
+  const char* first_name = "Milton";
+  const char* middle_name = "C.";
+  const char* last_name = "Waddams";
+  const char* address1 = "4120 Freidrich Lane";
+  const char* address2 = "Basement";
+  const char* city = "Austin";
+  const char* state_short = "TX";
+  const char* state = "Texas";
+  const char* zip = "78744";
+  const char* country = "US";
+  const char* phone = "15125551234";
+} kDefaultAddressValues;
+
 const std::vector<FieldValue> kDefaultAddress{
-    {"firstname", "Milton"},
-    {"lastname", "Waddams"},
-    {"address1", "4120 Freidrich Lane"},
-    {"address2", "Basement"},
-    {"city", "Austin"},
-    {"state", "TX"},
-    {"zip", "78744"},
-    {"country", "US"},
-    {"phone", "15125551234"}};
+    {"firstname", kDefaultAddressValues.first_name},
+    {"lastname", kDefaultAddressValues.last_name},
+    {"address1", kDefaultAddressValues.address1},
+    {"address2", kDefaultAddressValues.address2},
+    {"city", kDefaultAddressValues.city},
+    {"state", kDefaultAddressValues.state_short},
+    {"zip", kDefaultAddressValues.zip},
+    {"country", kDefaultAddressValues.country},
+    {"phone", kDefaultAddressValues.phone}};
 
 // Returns a copy of |fields| except that the value of `update.id` is set to
 // `update.value`.
@@ -1083,10 +1097,13 @@
 
   void CreateTestProfile() {
     AutofillProfile profile;
-    test::SetProfileInfo(&profile, "Milton", "C.", "Waddams",
-                         "red.swingline@initech.com", "Initech",
-                         "4120 Freidrich Lane", "Basement", "Austin", "Texas",
-                         "78744", "US", "15125551234");
+    test::SetProfileInfo(
+        &profile, kDefaultAddressValues.first_name,
+        kDefaultAddressValues.middle_name, kDefaultAddressValues.last_name,
+        "red.swingline@initech.com", "Initech", kDefaultAddressValues.address1,
+        kDefaultAddressValues.address2, kDefaultAddressValues.city,
+        kDefaultAddressValues.state, kDefaultAddressValues.zip,
+        kDefaultAddressValues.country, kDefaultAddressValues.phone);
     profile.set_use_count(9999999);  // We want this to be the first profile.
     AddTestProfile(browser()->profile(), profile);
   }
@@ -1428,6 +1445,7 @@
 
   // Modify a field.
   ASSERT_TRUE(FocusField(GetElementById("state"), GetWebContents()));
+  ASSERT_NE(strcmp(kDefaultAddressValues.state_short, "CA"), 0);
   FillElementWithValue("state", "CA");
 
   ASSERT_TRUE(AutofillFlow(GetElementById("firstname"), this));
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index 05f418f..44da0bc 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -3056,6 +3056,22 @@
       HostContentSettingsMapFactory::GetForProfile(profile));
 }
 
+bool ChromeContentBrowserClient::MayDeleteServiceWorkerRegistration(
+    const GURL& scope,
+    content::BrowserContext* browser_context) {
+  DCHECK(browser_context);
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+  if (!ChromeContentBrowserClientExtensionsPart::
+          MayDeleteServiceWorkerRegistration(scope, browser_context)) {
+    return false;
+  }
+#endif
+
+  return true;
+}
+
 void ChromeContentBrowserClient::
     UpdateEnabledBlinkRuntimeFeaturesInIsolatedWorker(
         content::BrowserContext* context,
diff --git a/chrome/browser/chrome_content_browser_client.h b/chrome/browser/chrome_content_browser_client.h
index 02dffaa..d2337ac 100644
--- a/chrome/browser/chrome_content_browser_client.h
+++ b/chrome/browser/chrome_content_browser_client.h
@@ -271,6 +271,9 @@
       const absl::optional<url::Origin>& top_frame_origin,
       const GURL& script_url,
       content::BrowserContext* context) override;
+  bool MayDeleteServiceWorkerRegistration(
+      const GURL& scope,
+      content::BrowserContext* browser_context) override;
   void UpdateEnabledBlinkRuntimeFeaturesInIsolatedWorker(
       content::BrowserContext* context,
       const GURL& script_url,
diff --git a/chrome/browser/engagement/site_engagement_service_unittest.cc b/chrome/browser/engagement/site_engagement_service_unittest.cc
index e1a7f22..62847fe 100644
--- a/chrome/browser/engagement/site_engagement_service_unittest.cc
+++ b/chrome/browser/engagement/site_engagement_service_unittest.cc
@@ -456,7 +456,8 @@
                                EngagementType::kNotificationInteraction, 4);
 }
 
-TEST_F(SiteEngagementServiceTest, RestrictedToHTTPAndHTTPS) {
+// TODO(https://crbug.com/1426914): Fix and enable this test.
+TEST_F(SiteEngagementServiceTest, DISABLED_RestrictedToHTTPAndHTTPS) {
   // The https and http versions of www.google.com should be separate.
   GURL url1("ftp://www.google.com/");
   GURL url2("file://blah");
diff --git a/chrome/browser/extensions/api/system_private/system_private_api.cc b/chrome/browser/extensions/api/system_private/system_private_api.cc
index d7fa9669..67ea8225 100644
--- a/chrome/browser/extensions/api/system_private/system_private_api.cc
+++ b/chrome/browser/extensions/api/system_private/system_private_api.cc
@@ -14,7 +14,7 @@
 #include "chrome/browser/extensions/event_router_forwarder.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/common/extensions/api/system_private.h"
-#include "chrome/common/pref_names.h"
+#include "components/policy/core/common/policy_pref_names.h"
 #include "components/prefs/pref_service.h"
 #include "google_apis/google_api_keys.h"
 
@@ -26,8 +26,8 @@
 
 namespace {
 
-// Maps prefs::kIncognitoModeAvailability values (0 = enabled, ...)
-// to strings exposed to extensions.
+// Maps policy::policy_prefs::kIncognitoModeAvailability values (0 = enabled,
+// ...) to strings exposed to extensions.
 const char* const kIncognitoModeAvailabilityStrings[] = {
   "enabled",
   "disabled",
@@ -56,7 +56,8 @@
 SystemPrivateGetIncognitoModeAvailabilityFunction::Run() {
   PrefService* prefs =
       Profile::FromBrowserContext(browser_context())->GetPrefs();
-  int value = prefs->GetInteger(prefs::kIncognitoModeAvailability);
+  int value =
+      prefs->GetInteger(policy::policy_prefs::kIncognitoModeAvailability);
   EXTENSION_FUNCTION_VALIDATE(
       value >= 0 &&
       value < static_cast<int>(std::size(kIncognitoModeAvailabilityStrings)));
diff --git a/chrome/browser/extensions/api/system_private/system_private_apitest.cc b/chrome/browser/extensions/api/system_private/system_private_apitest.cc
index 329cad2..2f970f7a 100644
--- a/chrome/browser/extensions/api/system_private/system_private_apitest.cc
+++ b/chrome/browser/extensions/api/system_private/system_private_apitest.cc
@@ -7,7 +7,7 @@
 #include "chrome/browser/extensions/extension_apitest.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
-#include "chrome/common/pref_names.h"
+#include "components/policy/core/common/policy_pref_names.h"
 #include "components/prefs/pref_service.h"
 #include "content/public/test/browser_test.h"
 
@@ -20,7 +20,7 @@
 
 IN_PROC_BROWSER_TEST_F(ExtensionApiTest, GetIncognitoModeAvailability) {
   PrefService* pref_service = browser()->profile()->GetPrefs();
-  pref_service->SetInteger(prefs::kIncognitoModeAvailability, 1);
+  pref_service->SetInteger(policy::policy_prefs::kIncognitoModeAvailability, 1);
 
   EXPECT_TRUE(RunExtensionTest("system/get_incognito_mode_availability", {},
                                {.load_as_component = true}))
diff --git a/chrome/browser/extensions/api/tab_capture/tab_capture_apitest.cc b/chrome/browser/extensions/api/tab_capture/tab_capture_apitest.cc
index 8a4bd53..4e315a4 100644
--- a/chrome/browser/extensions/api/tab_capture/tab_capture_apitest.cc
+++ b/chrome/browser/extensions/api/tab_capture/tab_capture_apitest.cc
@@ -36,6 +36,7 @@
 #include "extensions/test/extension_test_message_listener.h"
 #include "extensions/test/result_catcher.h"
 #include "testing/gmock/include/gmock/gmock.h"
+#include "ui/gl/gl_switches.h"
 #include "url/url_constants.h"
 
 namespace extensions {
@@ -52,6 +53,7 @@
     // Specify smallish window size to make testing of tab capture less CPU
     // intensive.
     command_line->AppendSwitchASCII(::switches::kWindowSize, "300,300");
+    command_line->AppendSwitch(::switches::kUseGpuInTests);
   }
 
   void AddExtensionToCommandLineAllowlist() {
diff --git a/chrome/browser/extensions/api/web_navigation/web_navigation_apitest.cc b/chrome/browser/extensions/api/web_navigation/web_navigation_apitest.cc
index 119a35b8..f1628be 100644
--- a/chrome/browser/extensions/api/web_navigation/web_navigation_apitest.cc
+++ b/chrome/browser/extensions/api/web_navigation/web_navigation_apitest.cc
@@ -749,7 +749,6 @@
 
   const char* kScript = R"(
     var ff = document.createElement('fencedframe');
-    ff.mode = 'opaque-ads';
     document.body.appendChild(ff);
   )";
   EXPECT_TRUE(content::ExecJs(rfh, kScript));
diff --git a/chrome/browser/extensions/chrome_content_browser_client_extensions_part.cc b/chrome/browser/extensions/chrome_content_browser_client_extensions_part.cc
index 14354d303..173f1b1 100644
--- a/chrome/browser/extensions/chrome_content_browser_client_extensions_part.cc
+++ b/chrome/browser/extensions/chrome_content_browser_client_extensions_part.cc
@@ -12,6 +12,7 @@
 #include <string>
 #include <vector>
 
+#include "base/auto_reset.h"
 #include "base/command_line.h"
 #include "base/functional/bind.h"
 #include "base/metrics/histogram_macros.h"
@@ -64,6 +65,7 @@
 #include "extensions/browser/process_map.h"
 #include "extensions/browser/renderer_startup_helper.h"
 #include "extensions/browser/service_worker/service_worker_host.h"
+#include "extensions/browser/service_worker_task_queue.h"
 #include "extensions/browser/url_loader_factory_manager.h"
 #include "extensions/browser/url_request_util.h"
 #include "extensions/browser/view_type_utils.h"
@@ -96,6 +98,9 @@
 
 namespace {
 
+// If non-null, a scope of a service worker to always allow to be unregistered.
+const GURL* g_allow_service_worker_unregistration_scope = nullptr;
+
 // Used by the GetPrivilegeRequiredByUrl() and GetProcessPrivilege() functions
 // below.  Extensions and hosted apps require different privileges to be
 // granted to their RenderProcessHosts.  This classification allows us to make
@@ -574,6 +579,53 @@
   return ::extensions::AllowServiceWorker(scope, script_url, extension);
 }
 
+bool ChromeContentBrowserClientExtensionsPart::
+    MayDeleteServiceWorkerRegistration(
+        const GURL& scope,
+        content::BrowserContext* browser_context) {
+  // We only care about extension urls.
+  if (!scope.SchemeIs(kExtensionScheme)) {
+    return true;
+  }
+
+  // Check if we're allowed to unregister this worker for testing purposes.
+  if (g_allow_service_worker_unregistration_scope &&
+      *g_allow_service_worker_unregistration_scope == scope) {
+    return true;
+  }
+
+  const Extension* extension = ExtensionRegistry::Get(browser_context)
+                                   ->enabled_extensions()
+                                   .GetExtensionOrAppByURL(scope);
+  if (!extension) {
+    return true;
+  }
+
+  // We only consider service workers that are root-scoped and for service
+  // worker-based extensions.
+  if (scope != extension->url() ||
+      !BackgroundInfo::IsServiceWorkerBased(extension)) {
+    return true;
+  }
+
+  base::Version registered_version =
+      ServiceWorkerTaskQueue::Get(browser_context)
+          ->RetrieveRegisteredServiceWorkerVersion(extension->id());
+  // The service worker was never fully registered; this can happen in the case
+  // of e.g. throwing errors in response to installation events (where the
+  // worker is registered, but then immediately unregistered).
+  if (!registered_version.IsValid()) {
+    return true;
+  }
+
+  // Don't allow the unregistration of a valid, enabled service worker-based
+  // extension's background service worker. Doing so would put the extension in
+  // a broken state. The service worker registration is instead tied to the
+  // extension's enablement; it is unregistered when the extension is disabled
+  // or uninstalled.
+  return registered_version != extension->version();
+}
+
 // static
 std::vector<url::Origin> ChromeContentBrowserClientExtensionsPart::
     GetOriginsRequiringDedicatedProcess() {
@@ -627,12 +679,20 @@
       ->Exists(extension_id);
 }
 
+// static
 bool ChromeContentBrowserClientExtensionsPart::AreExtensionsDisabledForProfile(
     content::BrowserContext* browser_context) {
   return AreKeyedServicesDisabledForProfileByDefault(
       Profile::FromBrowserContext(browser_context));
 }
 
+// static
+base::AutoReset<const GURL*> ChromeContentBrowserClientExtensionsPart::
+    AllowServiceWorkerUnregistrationForScopeForTesting(const GURL* scope) {
+  return base::AutoReset<const GURL*>(
+      &g_allow_service_worker_unregistration_scope, scope);
+}
+
 void ChromeContentBrowserClientExtensionsPart::RenderProcessWillLaunch(
     content::RenderProcessHost* host) {
   int id = host->GetID();
diff --git a/chrome/browser/extensions/chrome_content_browser_client_extensions_part.h b/chrome/browser/extensions/chrome_content_browser_client_extensions_part.h
index f4118bbc..83248ca 100644
--- a/chrome/browser/extensions/chrome_content_browser_client_extensions_part.h
+++ b/chrome/browser/extensions/chrome_content_browser_client_extensions_part.h
@@ -7,6 +7,7 @@
 
 #include <memory>
 
+#include "base/auto_reset.h"
 #include "base/gtest_prod_util.h"
 #include "chrome/browser/chrome_content_browser_client_parts.h"
 #include "components/download/public/common/quarantine_connection.h"
@@ -81,6 +82,9 @@
                                  const GURL& first_party_url,
                                  const GURL& script_url,
                                  content::BrowserContext* context);
+  static bool MayDeleteServiceWorkerRegistration(
+      const GURL& scope,
+      content::BrowserContext* browser_context);
   static std::vector<url::Origin> GetOriginsRequiringDedicatedProcess();
 
   // Helper function to call InfoMap::SetSigninProcess().
@@ -104,6 +108,12 @@
   static bool AreExtensionsDisabledForProfile(
       content::BrowserContext* browser_context);
 
+  // Temporarily allows unregistration of the service worker with the given
+  // `scope` for testing purposes; unregistration is allowed while the returned
+  // AutoReset remains in scope.
+  static base::AutoReset<const GURL*>
+  AllowServiceWorkerUnregistrationForScopeForTesting(const GURL* scope);
+
  private:
   FRIEND_TEST_ALL_PREFIXES(ChromeContentBrowserClientExtensionsPartTest,
                            IsolatedOriginsAndHostedAppWebExtents);
diff --git a/chrome/browser/extensions/service_worker_apitest.cc b/chrome/browser/extensions/service_worker_apitest.cc
index 7bcd17f7..927b930 100644
--- a/chrome/browser/extensions/service_worker_apitest.cc
+++ b/chrome/browser/extensions/service_worker_apitest.cc
@@ -6,6 +6,7 @@
 
 #include <utility>
 
+#include "base/auto_reset.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback_helpers.h"
 #include "base/json/json_reader.h"
@@ -21,6 +22,7 @@
 #include "build/build_config.h"
 #include "chrome/browser/extensions/api/permissions/permissions_api.h"
 #include "chrome/browser/extensions/browsertest_util.h"
+#include "chrome/browser/extensions/chrome_content_browser_client_extensions_part.h"
 #include "chrome/browser/extensions/chrome_test_extension_loader.h"
 #include "chrome/browser/extensions/crx_installer.h"
 #include "chrome/browser/extensions/error_console/error_console.h"
@@ -2389,7 +2391,7 @@
   // The registration should not have been stored, so we shouldn't cache the
   // extension version.
   base::Version stored_version =
-      service_worker_task_queue->RetrieveRegisteredServiceWorkerVersionForTest(
+      service_worker_task_queue->RetrieveRegisteredServiceWorkerVersion(
           extension->id());
   EXPECT_FALSE(stored_version.IsValid());
 }
@@ -2840,24 +2842,47 @@
 // Regression test for crbug.com/1271154.
 IN_PROC_BROWSER_TEST_F(ServiceWorkerTestWithEarlyReadyMesssage,
                        PRE_MissingRegistrationMitigated) {
-  const Extension* extension = LoadExtension(test_data_dir_.AppendASCII(
-      "service_worker/worker_based_background/activate_ensures_register"));
+  const Extension* extension = LoadExtension(
+      test_data_dir_.AppendASCII(
+          "service_worker/worker_based_background/activate_ensures_register"),
+      {.wait_for_registration_stored = true});
   ASSERT_TRUE(extension);
-  EXPECT_TRUE(WaitForMessage());
+  ASSERT_TRUE(WaitForMessage());
+  base::RunLoop().RunUntilIdle();
 
-  // Unregister the extension service worker.
+  // Since we wait for the registration to be stored (and run until idle,
+  // guaranteeing all observers see the result), we should now have a stored
+  // version for the service worker in the extensions system.
+  ServiceWorkerTaskQueue* service_worker_task_queue =
+      ServiceWorkerTaskQueue::Get(browser()->profile());
+  base::Version stored_version =
+      service_worker_task_queue->RetrieveRegisteredServiceWorkerVersion(
+          extension->id());
+
   {
+    // Bypass our unregistration protections to unregister the worker. Though
+    // we largely prevent this, it could still happen by means of e.g.
+    // disk or pref corruption.
     base::RunLoop run_loop;
     content::ServiceWorkerContext* context = GetServiceWorkerContext(profile());
-
     // The service worker is registered at the root scope.
+    const GURL& scope = extension->url();
+    base::AutoReset<const GURL*> allow_worker_unregistration =
+        ChromeContentBrowserClientExtensionsPart::
+            AllowServiceWorkerUnregistrationForScopeForTesting(&scope);
+
     context->UnregisterServiceWorker(
-        extension->url(),
-        blink::StorageKey::CreateFirstParty(extension->origin()),
+        scope, blink::StorageKey::CreateFirstParty(extension->origin()),
         base::BindLambdaForTesting(
-            [&run_loop](bool success) { run_loop.Quit(); }));
+            [&run_loop](bool success) { run_loop.QuitWhenIdle(); }));
     run_loop.Run();
   }
+
+  // The version should still be stored in the extension system.
+  stored_version =
+      service_worker_task_queue->RetrieveRegisteredServiceWorkerVersion(
+          extension->id());
+  EXPECT_TRUE(stored_version.IsValid());
 }
 
 IN_PROC_BROWSER_TEST_F(ServiceWorkerTestWithEarlyReadyMesssage,
diff --git a/chrome/browser/extensions/service_worker_registration_apitest.cc b/chrome/browser/extensions/service_worker_registration_apitest.cc
index 4b3d587..087a8e9e 100644
--- a/chrome/browser/extensions/service_worker_registration_apitest.cc
+++ b/chrome/browser/extensions/service_worker_registration_apitest.cc
@@ -4,6 +4,7 @@
 
 #include "base/test/test_future.h"
 #include "chrome/browser/extensions/extension_apitest.h"
+#include "chrome/test/base/ui_test_utils.h"
 #include "content/public/browser/service_worker_context.h"
 #include "content/public/browser/storage_partition.h"
 #include "content/public/test/browser_test.h"
@@ -14,6 +15,7 @@
 #include "extensions/common/extension.h"
 #include "extensions/common/mojom/manifest.mojom.h"
 #include "extensions/test/extension_test_message_listener.h"
+#include "extensions/test/result_catcher.h"
 #include "extensions/test/test_extension_dir.h"
 
 namespace extensions {
@@ -72,8 +74,7 @@
   ASSERT_TRUE(task_queue);
 
   base::Version stored_version =
-      task_queue->RetrieveRegisteredServiceWorkerVersionForTest(
-          extension->id());
+      task_queue->RetrieveRegisteredServiceWorkerVersion(extension->id());
   ASSERT_TRUE(stored_version.IsValid());
   EXPECT_EQ("0.1", stored_version.GetString());
   EXPECT_EQ(content::ServiceWorkerCapability::SERVICE_WORKER_NO_FETCH_HANDLER,
@@ -247,4 +248,107 @@
             GetServiceWorkerRegistrationState(*extension_ref));
 }
 
+// Verifies that a service worker registration associated with an extension's
+// manifest cannot be removed via the `chrome.browsingData` API.
+// Regression test for https://crbug.com/1392498.
+IN_PROC_BROWSER_TEST_F(ServiceWorkerRegistrationApiTest,
+                       RegistrationCannotBeRemovedByBrowsingDataAPI) {
+  // Load two extensions: one with a service worker-based background context and
+  // a second with access to the browsingData API.
+  static constexpr char kServiceWorkerManifest[] =
+      R"({
+           "name": "Service Worker Extension",
+           "manifest_version": 3,
+           "version": "0.1",
+           "background": {"service_worker": "background.js"}
+         })";
+  static constexpr char kServiceWorkerBackground[] =
+      R"(chrome.tabs.onCreated.addListener(tab => {
+           chrome.test.sendMessage('received event');
+         });)";
+
+  TestExtensionDir service_worker_extension_dir;
+  service_worker_extension_dir.WriteManifest(kServiceWorkerManifest);
+  service_worker_extension_dir.WriteFile(FILE_PATH_LITERAL("background.js"),
+                                         kServiceWorkerBackground);
+
+  static constexpr char kBrowsingDataManifest[] =
+      R"({
+           "name": "Browsing Data Remover",
+           "manifest_version": 3,
+           "version": "0.1",
+           "permissions": ["browsingData"]
+         })";
+  static constexpr char kClearDataJs[] =
+      R"(chrome.test.runTests([
+           async function clearServiceWorkers() {
+             // From the extension's perspective, this call should succeed (it
+             // will remove any service workers for extensions that aren't the
+             // root-scoped background service worker).
+             await chrome.browsingData.removeServiceWorkers(
+                 {originTypes: {extension: true}});
+             chrome.test.succeed();
+           },
+         ]);)";
+
+  TestExtensionDir browsing_data_extension_dir;
+  browsing_data_extension_dir.WriteManifest(kBrowsingDataManifest);
+  browsing_data_extension_dir.WriteFile(
+      FILE_PATH_LITERAL("clear_data.html"),
+      R"(<html><script src="clear_data.js"></script></html>)");
+  browsing_data_extension_dir.WriteFile(FILE_PATH_LITERAL("clear_data.js"),
+                                        kClearDataJs);
+
+  const Extension* service_worker_extension =
+      LoadExtension(service_worker_extension_dir.UnpackedPath(),
+                    {.wait_for_registration_stored = true});
+  ASSERT_TRUE(service_worker_extension);
+
+  const Extension* browsing_data_extension =
+      LoadExtension(browsing_data_extension_dir.UnpackedPath());
+  ASSERT_TRUE(browsing_data_extension);
+
+  auto open_new_tab = [this](const GURL& url) {
+    ASSERT_TRUE(ui_test_utils::NavigateToURLWithDisposition(
+        browser(), url, WindowOpenDisposition::NEW_FOREGROUND_TAB,
+        ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP));
+  };
+
+  // Verify the initial state. The service worker-based extension should have a
+  // worker registered...
+  EXPECT_EQ(content::ServiceWorkerCapability::SERVICE_WORKER_NO_FETCH_HANDLER,
+            GetServiceWorkerRegistrationState(*service_worker_extension));
+
+  const GURL about_blank("about:blank");
+
+  // ... And the worker should be able to receive incoming events.
+  {
+    ExtensionTestMessageListener listener("received event");
+    open_new_tab(about_blank);
+    ASSERT_TRUE(listener.WaitUntilSatisfied());
+  }
+
+  // Open a page to the browsing data extension, which will trigger a call to
+  // the browsingData API to remove registered service workers for extensions.
+  {
+    ResultCatcher result_catcher;
+    open_new_tab(browsing_data_extension->GetResourceURL("clear_data.html"));
+    EXPECT_TRUE(result_catcher.GetNextResult());
+  }
+
+  // The removal above should *not* have resulted in the background service
+  // worker for the extension being removed (which would put the extension into
+  // a broken state). The only way to remove a service worker from an extension
+  // manifest is to uninstall the extension.
+  // The worker should still be registered, and should still receive new events.
+  EXPECT_EQ(content::ServiceWorkerCapability::SERVICE_WORKER_NO_FETCH_HANDLER,
+            GetServiceWorkerRegistrationState(*service_worker_extension));
+
+  {
+    ExtensionTestMessageListener listener("received event");
+    open_new_tab(about_blank);
+    ASSERT_TRUE(listener.WaitUntilSatisfied());
+  }
+}
+
 }  // namespace extensions
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index cac09668..c471ebe 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -44,6 +44,11 @@
     "expiry_milestone": 99
   },
   {
+    "name": "adaptive-button-in-top-toolbar-add-to-bookmarks",
+    "owners": [ "salg@google.com", "chrome-segmentation-platform@google.com" ],
+    "expiry_milestone": 120
+  },
+  {
     "name": "adaptive-button-in-top-toolbar-customization",
     "owners": [ "shaktisahu", "chrome-segmentation-platform@google.com" ],
     "expiry_milestone": 120
@@ -1769,7 +1774,7 @@
   {
     "name": "enable-accessibility-service",
     "owners": [ "katie", "//ui/accessibility/OWNERS" ],
-    "expiry_milestone": 120
+    "expiry_milestone": 140
   },
   {
     "name": "enable-android-gamepad-vibration",
@@ -2265,11 +2270,6 @@
     "expiry_milestone": 111
   },
   {
-    "name": "enable-docked-magnifier-resizing",
-    "owners": [ "josiahk", "//ui/accessibility/OWNERS" ],
-    "expiry_milestone": 110
-  },
-  {
     "name": "enable-download-service-foreground-session",
     "owners": [ "rajendrant", "mcrouse", "chrome-intelligence-core@google.com" ],
     "expiry_milestone": 116
@@ -5179,6 +5179,11 @@
     "expiry_milestone": 105
   },
   {
+    "name": "ntp-single-row-shortcuts",
+    "owners": [ "//components/search/OWNERS" ],
+    "expiry_milestone": 115
+  },
+  {
     "name": "ntp-view-hierarchy-repair",
     "owners": [ "adamta", "sczs" ],
     "expiry_milestone": 115
@@ -5697,6 +5702,11 @@
     "expiry_milestone": 115
   },
   {
+    "name": "page-content-annotations-persist-salient-image-metadata",
+    "owners": [ "sophiechang", "chrome-intelligence-core@google.com"],
+    "expiry_milestone": 115
+  },
+  {
     "name": "page-entities-page-content-annotations",
     "owners": [ "sophiechang", "mcrouse", "chrome-intelligence-core@google.com"],
     "expiry_milestone": 115
@@ -6530,6 +6540,11 @@
     "expiry_milestone": 116
   },
   {
+    "name": "shimless-rma-diagnostic-page",
+    "owners": [ "zentaro", "gavinwill", "cros-peripherals@google.com" ],
+    "expiry_milestone": 120
+  },
+  {
     "name": "shimless-rma-disable-dark-mode",
     "owners": [ "zentaro", "gavinwill", "cros-peripherals@google.com" ],
     "expiry_milestone": 120
@@ -7203,6 +7218,11 @@
     "expiry_milestone": 128
   },
   {
+    "name": "use-multi-plane-format-for-hardware-video",
+    "owners": [ "hitawala", "vasilyt" ],
+    "expiry_milestone": 124
+  },
+  {
     "name": "use-nat64-for-ipv4-literal",
     "owners": [ "horo", "net-dev" ],
     "expiry_milestone": 120
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 5a5f51b9..c3c4298 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -2445,6 +2445,11 @@
 const char kPageContentAnnotationsDescription[] =
     "Enables page content to be annotated on-device.";
 
+const char kPageContentAnnotationsPersistSalientImageMetadataName[] =
+    "Page content annotations - Persist salient image metadata";
+const char kPageContentAnnotationsPersistSalientImageMetadataDescription[] =
+    "Enables salient image metadata per page load to be persisted on-device.";
+
 const char kPageEntitiesPageContentAnnotationsName[] =
     "Page entities content annotations";
 const char kPageEntitiesPageContentAnnotationsDescription[] =
@@ -3463,6 +3468,12 @@
     "Enable YUV to RGB and RGB to YUV conversion for media clients through "
     "passthrough command decoder";
 
+const char kUseMultiPlaneFormatForHardwareVideoName[] =
+    "Enable multi-plane formats for hardware video decoder";
+const char kUseMultiPlaneFormatForHardwareVideoDescription[] =
+    "Enable single shared image and mailbox for multi-plane formats for "
+    "hardware video decoder";
+
 const char kDurableClientHintsCacheName[] = "Persistent client hints";
 const char kDurableClientHintsCacheDescription[] =
     "Persist the client hints cache beyond browser restarts.";
@@ -4229,11 +4240,19 @@
 const char kAdaptiveButtonInTopToolbarName[] = "Adaptive button in top toolbar";
 const char kAdaptiveButtonInTopToolbarDescription[] =
     "Enables showing an adaptive action button in the top toolbar";
+
 const char kAdaptiveButtonInTopToolbarTranslateName[] =
     "Adaptive button in top toolbar - Translate button";
 const char kAdaptiveButtonInTopToolbarTranslateDescription[] =
     "Enables a translate button in the top toolbar. Must be selected in "
     "Settings > Toolbar Shortcut.";
+const char kAdaptiveButtonInTopToolbarAddToBookmarksName[] =
+    "Adaptive button in top toolbar - Add to bookmarks button";
+const char kAdaptiveButtonInTopToolbarAddToBookmarksDescription[] =
+    "Enables an add to bookmarks button in the top toolbar. Must be selected "
+    "in "
+    "Settings > Toolbar Shortcut.";
+
 const char kAdaptiveButtonInTopToolbarCustomizationName[] =
     "Adaptive button in top toolbar customization";
 const char kAdaptiveButtonInTopToolbarCustomizationDescription[] =
@@ -4482,6 +4501,10 @@
 const char kNtpSafeBrowsingModuleDescription[] =
     "Shows the safe browsing module on the New Tab Page.";
 
+const char kNtpSingleRowShortcutsName[] = "NTP Single Row Shortcuts";
+const char kNtpSingleRowShortcutsDescription[] =
+    "Shows shortcuts in a single wide row on the New Tab Page.";
+
 const char kEnableReaderModeName[] = "Enable Reader Mode";
 const char kEnableReaderModeDescription[] =
     "Allows viewing of simplified web pages by selecting 'Customize and "
@@ -6027,6 +6050,12 @@
 const char kShimlessRMADisableDarkModeDescription[] =
     "Disable dark mode and only allow light mode in Shimless RMA";
 
+const char kShimlessRMADiagnosticPageName[] =
+    "Enable diagnostic page in Shimless RMA";
+const char kShimlessRMADiagnosticPageDescription[] =
+    "Enable the diagnostic page in Shimless RMA for showing detailed device "
+    "information";
+
 const char kSchedulerConfigurationName[] = "Scheduler Configuration";
 const char kSchedulerConfigurationDescription[] =
     "Instructs the OS to use a specific scheduler configuration setting.";
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 95d739e..a3b9ffe 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -1367,6 +1367,10 @@
 extern const char kPageContentAnnotationsName[];
 extern const char kPageContentAnnotationsDescription[];
 
+extern const char kPageContentAnnotationsPersistSalientImageMetadataName[];
+extern const char
+    kPageContentAnnotationsPersistSalientImageMetadataDescription[];
+
 extern const char kPageEntitiesPageContentAnnotationsName[];
 extern const char kPageEntitiesPageContentAnnotationsDescription[];
 
@@ -1985,6 +1989,9 @@
 extern const char kPassthroughYuvRgbConversionName[];
 extern const char kPassthroughYuvRgbConversionDescription[];
 
+extern const char kUseMultiPlaneFormatForHardwareVideoName[];
+extern const char kUseMultiPlaneFormatForHardwareVideoDescription[];
+
 extern const char kDurableClientHintsCacheName[];
 extern const char kDurableClientHintsCacheDescription[];
 
@@ -2419,6 +2426,8 @@
 extern const char kAdaptiveButtonInTopToolbarDescription[];
 extern const char kAdaptiveButtonInTopToolbarTranslateName[];
 extern const char kAdaptiveButtonInTopToolbarTranslateDescription[];
+extern const char kAdaptiveButtonInTopToolbarAddToBookmarksName[];
+extern const char kAdaptiveButtonInTopToolbarAddToBookmarksDescription[];
 extern const char kAdaptiveButtonInTopToolbarCustomizationName[];
 extern const char kAdaptiveButtonInTopToolbarCustomizationDescription[];
 
@@ -2575,6 +2584,9 @@
 extern const char kNtpSafeBrowsingModuleName[];
 extern const char kNtpSafeBrowsingModuleDescription[];
 
+extern const char kNtpSingleRowShortcutsName[];
+extern const char kNtpSingleRowShortcutsDescription[];
+
 extern const char kEnableReaderModeName[];
 extern const char kEnableReaderModeDescription[];
 
@@ -3471,6 +3483,9 @@
 extern const char kShimlessRMADisableDarkModeName[];
 extern const char kShimlessRMADisableDarkModeDescription[];
 
+extern const char kShimlessRMADiagnosticPageName[];
+extern const char kShimlessRMADiagnosticPageDescription[];
+
 extern const char kSchedulerConfigurationName[];
 extern const char kSchedulerConfigurationDescription[];
 extern const char kSchedulerConfigurationConservative[];
diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc
index a7202bb..02af7d8 100644
--- a/chrome/browser/flags/android/chrome_feature_list.cc
+++ b/chrome/browser/flags/android/chrome_feature_list.cc
@@ -159,6 +159,7 @@
     &history_clusters::internal::kJourneys,
     &kAdaptiveButtonInTopToolbar,
     &kAdaptiveButtonInTopToolbarTranslate,
+    &kAdaptiveButtonInTopToolbarAddToBookmarks,
     &kAdaptiveButtonInTopToolbarCustomizationV2,
     &kAddEduAccountFromAccountSettingsForSupervisedUsers,
     &kAddToHomescreenIPH,
@@ -435,6 +436,10 @@
              "AdaptiveButtonInTopToolbarTranslate",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
+BASE_FEATURE(kAdaptiveButtonInTopToolbarAddToBookmarks,
+             "AdaptiveButtonInTopToolbarAddToBookmarks",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 BASE_FEATURE(kAdaptiveButtonInTopToolbarCustomizationV2,
              "AdaptiveButtonInTopToolbarCustomizationV2",
              base::FEATURE_ENABLED_BY_DEFAULT);
diff --git a/chrome/browser/flags/android/chrome_feature_list.h b/chrome/browser/flags/android/chrome_feature_list.h
index 0a4a29b..0215909 100644
--- a/chrome/browser/flags/android/chrome_feature_list.h
+++ b/chrome/browser/flags/android/chrome_feature_list.h
@@ -15,6 +15,7 @@
 // Alphabetical:
 BASE_DECLARE_FEATURE(kAdaptiveButtonInTopToolbar);
 BASE_DECLARE_FEATURE(kAdaptiveButtonInTopToolbarTranslate);
+BASE_DECLARE_FEATURE(kAdaptiveButtonInTopToolbarAddToBookmarks);
 BASE_DECLARE_FEATURE(kAdaptiveButtonInTopToolbarCustomizationV2);
 BASE_DECLARE_FEATURE(kAddEduAccountFromAccountSettingsForSupervisedUsers);
 BASE_DECLARE_FEATURE(kAddToHomescreenIPH);
diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
index 9a52713..e6593634 100644
--- a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
+++ b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
@@ -165,6 +165,8 @@
     public static final String ADAPTIVE_BUTTON_IN_TOP_TOOLBAR = "AdaptiveButtonInTopToolbar";
     public static final String ADAPTIVE_BUTTON_IN_TOP_TOOLBAR_TRANSLATE =
             "AdaptiveButtonInTopToolbarTranslate";
+    public static final String ADAPTIVE_BUTTON_IN_TOP_TOOLBAR_ADD_TO_BOOKMARKS =
+            "AdaptiveButtonInTopToolbarAddToBookmarks";
     public static final String ADAPTIVE_BUTTON_IN_TOP_TOOLBAR_CUSTOMIZATION_V2 =
             "AdaptiveButtonInTopToolbarCustomizationV2";
     public static final String ADD_EDU_ACCOUNT_FROM_ACCOUNT_SETTINGS_FOR_SUPERVISED_USERS =
diff --git a/chrome/browser/incognito/android/incognito_utils_android.cc b/chrome/browser/incognito/android/incognito_utils_android.cc
index 8cf37c25..117856e 100644
--- a/chrome/browser/incognito/android/incognito_utils_android.cc
+++ b/chrome/browser/incognito/android/incognito_utils_android.cc
@@ -7,7 +7,7 @@
 #include "chrome/browser/incognito/jni_headers/IncognitoUtils_jni.h"
 #include "chrome/browser/prefs/incognito_mode_prefs.h"
 #include "chrome/browser/profiles/profile_manager.h"
-#include "chrome/common/pref_names.h"
+#include "components/policy/core/common/policy_pref_names.h"
 #include "components/prefs/pref_service.h"
 
 static jboolean JNI_IncognitoUtils_GetIncognitoModeEnabled(JNIEnv* env) {
@@ -25,5 +25,6 @@
 static jboolean JNI_IncognitoUtils_GetIncognitoModeManaged(JNIEnv* env) {
   PrefService* prefs =
       ProfileManager::GetActiveUserProfile()->GetOriginalProfile()->GetPrefs();
-  return prefs->IsManagedPreference(prefs::kIncognitoModeAvailability);
+  return prefs->IsManagedPreference(
+      policy::policy_prefs::kIncognitoModeAvailability);
 }
diff --git a/chrome/browser/media/cdm_document_service_impl.cc b/chrome/browser/media/cdm_document_service_impl.cc
index 93086d3..9ee8c0177 100644
--- a/chrome/browser/media/cdm_document_service_impl.cc
+++ b/chrome/browser/media/cdm_document_service_impl.cc
@@ -45,6 +45,7 @@
 
 #include "base/files/file_enumerator.h"
 #include "base/files/file_util.h"
+#include "base/metrics/histogram_functions.h"
 #include "base/system/sys_info.h"
 #include "base/task/task_traits.h"
 #include "base/task/thread_pool.h"
@@ -368,11 +369,15 @@
   auto* monitor = MediaFoundationServiceMonitor::GetInstance();
 
   // Hardware context reset after power or display change is expected.
-  if (event == media::CdmEvent::kHardwareContextReset &&
-      monitor->HasRecentPowerOrDisplayChange()) {
-    DVLOG(2) << __func__
-             << ": HardwareContextReset ignored after power or display change";
-    return;
+  if (event == media::CdmEvent::kHardwareContextReset) {
+    bool has_change = monitor->HasRecentPowerOrDisplayChange();
+    base::UmaHistogramBoolean(
+        "Media.EME.MediaFoundationService.HardwareContextReset", has_change);
+    if (has_change) {
+      DVLOG(2) << __func__
+               << ": HardwareContextReset ignored after power/display change";
+      return;
+    }
   }
 
   // CdmDocumentServiceImpl is shared by all CDMs in the same RenderFrame.
diff --git a/chrome/browser/media/router/discovery/access_code/access_code_cast_sink_service.cc b/chrome/browser/media/router/discovery/access_code/access_code_cast_sink_service.cc
index 00153dc..9519e5a2 100644
--- a/chrome/browser/media/router/discovery/access_code/access_code_cast_sink_service.cc
+++ b/chrome/browser/media/router/discovery/access_code/access_code_cast_sink_service.cc
@@ -182,9 +182,11 @@
 
     // Only record the start time for local routes
     bool is_route_local = false;
+    MediaSource source = MediaSource::ForAnyTab();
     for (const auto& route : routes) {
       if (route.media_route_id() == new_route_id && route.is_local()) {
         is_route_local = true;
+        source = route.media_source();
       }
     }
 
@@ -199,7 +201,7 @@
                 MediaRoute::GetSinkIdFromMediaRouteId(new_route_id)),
             base::BindOnce(&AccessCodeCastSinkService::HandleMediaRouteAdded,
                            access_code_sink_service_->GetWeakPtr(),
-                           new_route_id, is_route_local));
+                           new_route_id, is_route_local, source));
   }
 
   // No routes were removed.
@@ -274,6 +276,7 @@
 void AccessCodeCastSinkService::HandleMediaRouteAdded(
     const MediaRoute::Id route_id,
     const bool is_route_local,
+    const MediaSource media_source,
     const MediaSinkInternal* sink) {
   if (!IsSinkValidAccessCodeSink(sink))
     return;
@@ -282,8 +285,19 @@
     current_route_start_times_[route_id] = base::Time::Now();
   }
 
+  bool is_saved = sink->cast_data().discovery_type ==
+                  CastDiscoveryType::kAccessCodeRememberedDevice;
+  AccessCodeCastCastMode source_type = AccessCodeCastCastMode::kPresentation;
+  if (media_source.IsTabMirroringSource()) {
+    source_type = AccessCodeCastCastMode::kTabMirror;
+  } else if (media_source.IsDesktopMirroringSource()) {
+    source_type = AccessCodeCastCastMode::kDesktopMirror;
+  } else if (media_source.IsRemotePlaybackSource()) {
+    source_type = AccessCodeCastCastMode::kRemotePlayback;
+  }
+
   AccessCodeCastMetrics::RecordAccessCodeRouteStarted(
-      GetAccessCodeDeviceDurationPref(profile_));
+      GetAccessCodeDeviceDurationPref(profile_), is_saved, source_type);
 }
 
 void AccessCodeCastSinkService::OnAccessCodeRouteRemoved(
diff --git a/chrome/browser/media/router/discovery/access_code/access_code_cast_sink_service.h b/chrome/browser/media/router/discovery/access_code/access_code_cast_sink_service.h
index 2cd901d..a8a28b1 100644
--- a/chrome/browser/media/router/discovery/access_code/access_code_cast_sink_service.h
+++ b/chrome/browser/media/router/discovery/access_code/access_code_cast_sink_service.h
@@ -237,6 +237,7 @@
   // Reports to metrics whenever the added route is to an access code sink.
   void HandleMediaRouteAdded(const MediaRoute::Id route_id,
                              const bool is_route_local,
+                             const MediaSource media_source,
                              const MediaSinkInternal* sink);
 
   void OnAccessCodeRouteRemoved(const MediaSinkInternal* sink);
diff --git a/chrome/browser/media/router/discovery/access_code/access_code_cast_sink_service_unittest.cc b/chrome/browser/media/router/discovery/access_code/access_code_cast_sink_service_unittest.cc
index 8a432aa..7d7d543 100644
--- a/chrome/browser/media/router/discovery/access_code/access_code_cast_sink_service_unittest.cc
+++ b/chrome/browser/media/router/discovery/access_code/access_code_cast_sink_service_unittest.cc
@@ -1464,22 +1464,22 @@
   histogram_tester.ExpectTotalCount(
       "AccessCodeCast.Discovery.DeviceDurationOnRoute", 0);
 
-  access_code_cast_sink_service_->HandleMediaRouteAdded(fake_route_1, true,
-                                                        &cast_sink1);
+  access_code_cast_sink_service_->HandleMediaRouteAdded(
+      fake_route_1, true, MediaSource::ForAnyTab(), &cast_sink1);
 
   // The histogram should not be logged to after a non access code route starts.
   histogram_tester.ExpectTotalCount(
       "AccessCodeCast.Discovery.DeviceDurationOnRoute", 0);
 
-  access_code_cast_sink_service_->HandleMediaRouteAdded(fake_route_2, true,
-                                                        &cast_sink2);
+  access_code_cast_sink_service_->HandleMediaRouteAdded(
+      fake_route_2, true, MediaSource::ForAnyTab(), &cast_sink2);
 
   // The histogram should log when a route starts to a new access code device.
   histogram_tester.ExpectBucketCount(
       "AccessCodeCast.Discovery.DeviceDurationOnRoute", 10, 1);
 
-  access_code_cast_sink_service_->HandleMediaRouteAdded(fake_route_3, true,
-                                                        &cast_sink3);
+  access_code_cast_sink_service_->HandleMediaRouteAdded(
+      fake_route_3, true, MediaSource::ForAnyTab(), &cast_sink3);
 
   // The histogram should log when a route starts to a saved access code device.
   histogram_tester.ExpectBucketCount(
@@ -1487,14 +1487,14 @@
 
   // Ensure various pref values are can be logged.
   SetDeviceDurationPrefForTest(base::Seconds(100));
-  access_code_cast_sink_service_->HandleMediaRouteAdded(fake_route_2, true,
-                                                        &cast_sink2);
+  access_code_cast_sink_service_->HandleMediaRouteAdded(
+      fake_route_2, true, MediaSource::ForAnyTab(), &cast_sink2);
   histogram_tester.ExpectBucketCount(
       "AccessCodeCast.Discovery.DeviceDurationOnRoute", 100, 1);
 
   SetDeviceDurationPrefForTest(base::Seconds(1000));
-  access_code_cast_sink_service_->HandleMediaRouteAdded(fake_route_2, true,
-                                                        &cast_sink2);
+  access_code_cast_sink_service_->HandleMediaRouteAdded(
+      fake_route_2, true, MediaSource::ForAnyTab(), &cast_sink2);
   histogram_tester.ExpectBucketCount(
       "AccessCodeCast.Discovery.DeviceDurationOnRoute", 1000, 1);
 
diff --git a/chrome/browser/media/webrtc/capture_handle_browsertest.cc b/chrome/browser/media/webrtc/capture_handle_browsertest.cc
index 267e09a..9b79746a5 100644
--- a/chrome/browser/media/webrtc/capture_handle_browsertest.cc
+++ b/chrome/browser/media/webrtc/capture_handle_browsertest.cc
@@ -30,6 +30,7 @@
 #include "content/public/test/browser_test_base.h"
 #include "content/public/test/browser_test_utils.h"
 #include "content/public/test/prerender_test_util.h"
+#include "ui/gl/gl_switches.h"
 
 using content::WebContents;
 
@@ -221,6 +222,7 @@
         switches::kEnableExperimentalWebPlatformFeatures);
     command_line->AppendSwitchASCII(
         switches::kAutoSelectTabCaptureSourceByTitle, kCapturedTabTitle);
+    command_line->AppendSwitch(switches::kUseGpuInTests);
   }
 
   void TearDownOnMainThread() override {
diff --git a/chrome/browser/media/webrtc/conditional_focus_browsertest.cc b/chrome/browser/media/webrtc/conditional_focus_browsertest.cc
index fb7f31bc..b9bcb49 100644
--- a/chrome/browser/media/webrtc/conditional_focus_browsertest.cc
+++ b/chrome/browser/media/webrtc/conditional_focus_browsertest.cc
@@ -19,6 +19,7 @@
 #include "content/public/common/content_switches.h"
 #include "content/public/test/browser_test.h"
 #include "third_party/blink/public/common/switches.h"
+#include "ui/gl/gl_switches.h"
 
 namespace {
 
@@ -73,6 +74,7 @@
         switches::kAutoSelectTabCaptureSourceByTitle, kCapturedPageTitle);
     command_line->AppendSwitchASCII(blink::switches::kConditionalFocusWindowMs,
                                     "5000");
+    command_line->AppendSwitch(switches::kUseGpuInTests);
   }
 
   WebContents* OpenTestPageInNewTab(const std::string& test_url) {
diff --git a/chrome/browser/media/webrtc/region_capture_browsertest.cc b/chrome/browser/media/webrtc/region_capture_browsertest.cc
index f28a3af..c699d74 100644
--- a/chrome/browser/media/webrtc/region_capture_browsertest.cc
+++ b/chrome/browser/media/webrtc/region_capture_browsertest.cc
@@ -33,6 +33,7 @@
 #include "content/public/test/prerender_test_util.h"
 #include "testing/gmock/include/gmock/gmock-matchers.h"
 #include "third_party/blink/public/common/features.h"
+#include "ui/gl/gl_switches.h"
 
 // TODO(crbug.com/1215089): Enable this test suite on Lacros.
 #if !BUILDFLAG(IS_CHROMEOS_LACROS)
@@ -293,6 +294,7 @@
   void SetUpCommandLine(base::CommandLine* command_line) override {
     command_line->AppendSwitch(
         switches::kEnableExperimentalWebPlatformFeatures);
+    command_line->AppendSwitch(switches::kUseGpuInTests);
     command_line_ = command_line;
   }
 
diff --git a/chrome/browser/media/webrtc/webrtc_desktop_capture_browsertest.cc b/chrome/browser/media/webrtc/webrtc_desktop_capture_browsertest.cc
index 78a9a6c6..d430468a 100644
--- a/chrome/browser/media/webrtc/webrtc_desktop_capture_browsertest.cc
+++ b/chrome/browser/media/webrtc/webrtc_desktop_capture_browsertest.cc
@@ -39,6 +39,7 @@
 #include "content/public/test/browser_test_utils.h"
 #include "media/base/media_switches.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
+#include "ui/gl/gl_switches.h"
 
 namespace {
 static const char kMainWebrtcTestHtmlPage[] = "/webrtc/webrtc_jsep01_test.html";
@@ -231,6 +232,7 @@
     command_line->AppendSwitchASCII(switches::kAutoSelectDesktopCaptureSource,
                                     "Entire screen");
     command_line->AppendSwitch(switches::kEnableUserMediaScreenCapturing);
+    command_line->AppendSwitch(switches::kUseGpuInTests);
   }
 
  protected:
diff --git a/chrome/browser/media/webrtc/webrtc_getdisplaymedia_browsertest.cc b/chrome/browser/media/webrtc/webrtc_getdisplaymedia_browsertest.cc
index f3e8c04..e520684 100644
--- a/chrome/browser/media/webrtc/webrtc_getdisplaymedia_browsertest.cc
+++ b/chrome/browser/media/webrtc/webrtc_getdisplaymedia_browsertest.cc
@@ -44,6 +44,7 @@
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/public/common/features.h"
 #include "ui/base/l10n/l10n_util.h"
+#include "ui/gl/gl_switches.h"
 
 #if BUILDFLAG(IS_MAC)
 #include "base/mac/mac_util.h"
@@ -414,6 +415,7 @@
         switches::kUseFakeDeviceForMediaStream,
         base::StringPrintf("display-media-type=%s",
                            test_config_.display_surface));
+    command_line->AppendSwitch(switches::kUseGpuInTests);
   }
 
   bool PreferCurrentTab() const override {
@@ -587,6 +589,7 @@
         switches::kEnableExperimentalWebPlatformFeatures);
     command_line->AppendSwitchASCII(
         switches::kAutoSelectTabCaptureSourceByTitle, kAppWindowTitle);
+    command_line->AppendSwitch(switches::kUseGpuInTests);
   }
 
   void SetUpOnMainThread() override {
@@ -652,6 +655,7 @@
         switches::kEnableExperimentalWebPlatformFeatures);
     command_line->AppendSwitchASCII(
         switches::kAutoSelectTabCaptureSourceByTitle, kSameOriginRenamedTitle);
+    command_line->AppendSwitch(switches::kUseGpuInTests);
   }
 
   void SetUpOnMainThread() override {
@@ -824,6 +828,7 @@
         switches::kUseFakeDeviceForMediaStream,
         base::StrCat({"display-media-type=",
                       DisplaySurfaceTypeAsString(display_surface_type_)}));
+    command_line->AppendSwitch(switches::kUseGpuInTests);
   }
 
   std::string GetVideoTrackType() {
@@ -1120,6 +1125,8 @@
         switches::kEnableExperimentalWebPlatformFeatures);
     command_line->AppendSwitchASCII(
         switches::kAutoSelectTabCaptureSourceByTitle, kCapturedTabTitle);
+    command_line->AppendSwitch(switches::kUseGpuInTests);
+
     if (!user_shared_audio_) {
       command_line->AppendSwitch(switches::kScreenCaptureAudioDefaultUnchecked);
     }
diff --git a/chrome/browser/page_load_metrics/observers/prefetch_page_load_metrics_observer_unittest.cc b/chrome/browser/page_load_metrics/observers/prefetch_page_load_metrics_observer_unittest.cc
index ca4d837..fff0b08 100644
--- a/chrome/browser/page_load_metrics/observers/prefetch_page_load_metrics_observer_unittest.cc
+++ b/chrome/browser/page_load_metrics/observers/prefetch_page_load_metrics_observer_unittest.cc
@@ -99,7 +99,8 @@
   bool in_main_frame_ = true;
 };
 
-TEST_F(PrefetchPageLoadMetricsObserverTest, DontRecordForNonHttp) {
+// TODO(https://crbug.com/1426914): Fix and enable this test.
+TEST_F(PrefetchPageLoadMetricsObserverTest, DISABLED_DontRecordForNonHttp) {
   set_navigation_url(GURL("chrome://version"));
 
   StartTest();
diff --git a/chrome/browser/password_manager/affiliation_service_factory.cc b/chrome/browser/password_manager/affiliation_service_factory.cc
index 1630ee5..91411f1 100644
--- a/chrome/browser/password_manager/affiliation_service_factory.cc
+++ b/chrome/browser/password_manager/affiliation_service_factory.cc
@@ -19,12 +19,7 @@
 AffiliationServiceFactory::AffiliationServiceFactory()
     : ProfileKeyedServiceFactory(
           "AffiliationService",
-          ProfileSelections::Builder()
-              .WithRegular(ProfileSelection::kOriginalOnly)
-              // TODO(crbug.com/1418376): Check if this service is needed in
-              // Guest mode.
-              .WithGuest(ProfileSelection::kOriginalOnly)
-              .Build()) {}
+          ProfileSelections::BuildRedirectedInIncognitoNonExperimental()) {}
 
 AffiliationServiceFactory::~AffiliationServiceFactory() = default;
 
diff --git a/chrome/browser/permissions/permission_context_base_permissions_policy_unittest.cc b/chrome/browser/permissions/permission_context_base_permissions_policy_unittest.cc
index f6139b4..2c5b7e0 100644
--- a/chrome/browser/permissions/permission_context_base_permissions_policy_unittest.cc
+++ b/chrome/browser/permissions/permission_context_base_permissions_policy_unittest.cc
@@ -66,7 +66,9 @@
                                 std::vector({blink::OriginWithPossibleWildcards(
                                     url::Origin::Create(GURL(origin)),
                                     /*has_subdomain_wildcard=*/false)}),
-                                false, false);
+                                /*self_if_matches=*/absl::nullopt,
+                                /*matches_all_origins=*/false,
+                                /*matches_opaque_src=*/false);
     }
     content::RenderFrameHost* result =
         content::RenderFrameHostTester::For(parent)->AppendChildWithPolicy(
@@ -92,7 +94,9 @@
                                   /*has_subdomain_wildcard=*/false);
     }
     navigation->SetPermissionsPolicyHeader(
-        {{feature, parsed_origins, false, false}});
+        {{feature, parsed_origins, /*self_if_matches=*/absl::nullopt,
+          /*matches_all_origins=*/false,
+          /*matches_opaque_src=*/false}});
     navigation->Commit();
     *rfh = navigation->GetFinalRenderFrameHost();
   }
diff --git a/chrome/browser/policy/test/policy_certs_browsertest.cc b/chrome/browser/policy/test/policy_certs_browsertest.cc
index e8a3451..317b6c81 100644
--- a/chrome/browser/policy/test/policy_certs_browsertest.cc
+++ b/chrome/browser/policy/test/policy_certs_browsertest.cc
@@ -563,48 +563,10 @@
       ash::NetworkCertLoader::Get()->authority_certs()));
 }
 
-// Test that the lock screen profile does not use the policy provided custom
-// trusted anchors of the primary profile by default (i.e. if the
-// PolicyProvidedTrustAnchorsAllowedAtLockScreen flag is disabled).
-IN_PROC_BROWSER_TEST_F(PolicyProvidedCertsRegularUserTest,
-                       LockScreenPrimaryProfileCertsFlagDisabled) {
-  ash::ScreenLockerTester locker;
-  locker.Lock();
-  // Showing the reauth dialog will create the lock screen profile.
-  ash::LockScreenReauthDialogTestHelper::ShowDialogAndWait();
-  ASSERT_THAT(ash::ProfileHelper::GetLockScreenProfile(), NotNull());
-
-  // Set policy provided trusted anchors on the primary profile.
-  user_policy_certs_helper_.SetRootCertONCUserPolicy(
-      browser()->profile(),
-      multi_profile_policy_helper_.policy_for_profile_1());
-
-  EXPECT_EQ(net::OK,
-            VerifyTestServerCert(browser()->profile(),
-                                 user_policy_certs_helper_.server_cert()));
-  // Verify that the lock screen can access the policy provided certs.
-  EXPECT_EQ(net::ERR_CERT_AUTHORITY_INVALID,
-            VerifyTestServerCert(ash::ProfileHelper::GetLockScreenProfile(),
-                                 user_policy_certs_helper_.server_cert()));
-}
-
-class PolicyProvidedCertsLockScreenFeatureTest
-    : public PolicyProvidedCertsRegularUserTest {
- protected:
-  PolicyProvidedCertsLockScreenFeatureTest() {
-    feature_list_.InitAndEnableFeature(
-        ash::features::kPolicyProvidedTrustAnchorsAllowedAtLockScreen);
-  }
-  ~PolicyProvidedCertsLockScreenFeatureTest() override = default;
-
- private:
-  base::test::ScopedFeatureList feature_list_;
-};
-
 // Test that the lock screen profile uses the policy provided custom trusted
 // anchors of the primary profile when the
 // `PolicyProvidedTrustAnchorsAllowedAtLockScreen` flag is enabled.
-IN_PROC_BROWSER_TEST_F(PolicyProvidedCertsLockScreenFeatureTest,
+IN_PROC_BROWSER_TEST_F(PolicyProvidedCertsRegularUserTest,
                        LockScreenPrimaryProfileCerts) {
   ash::ScreenLockerTester locker;
   locker.Lock();
@@ -628,7 +590,7 @@
 
 // Test that the lock screen profile doesn't use the policy provided custom
 // trusted anchors of a secondary profile.
-IN_PROC_BROWSER_TEST_F(PolicyProvidedCertsLockScreenFeatureTest,
+IN_PROC_BROWSER_TEST_F(PolicyProvidedCertsRegularUserTest,
                        LockScreenSecondaryProfileCerts) {
   ash::ScreenLockerTester locker;
   locker.Lock();
@@ -649,6 +611,44 @@
                                  user_policy_certs_helper_.server_cert()));
 }
 
+class PolicyProvidedCertsLockScreenFeatureTest
+    : public PolicyProvidedCertsRegularUserTest {
+ protected:
+  PolicyProvidedCertsLockScreenFeatureTest() {
+    feature_list_.InitAndDisableFeature(
+        ash::features::kPolicyProvidedTrustAnchorsAllowedAtLockScreen);
+  }
+  ~PolicyProvidedCertsLockScreenFeatureTest() override = default;
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+// Test that the lock screen profile does not use the policy provided custom
+// trusted anchors of the primary profile if the
+// PolicyProvidedTrustAnchorsAllowedAtLockScreen flag is disabled.
+IN_PROC_BROWSER_TEST_F(PolicyProvidedCertsLockScreenFeatureTest,
+                       LockScreenPrimaryProfileCertsFlagDisabled) {
+  ash::ScreenLockerTester locker;
+  locker.Lock();
+  // Showing the reauth dialog will create the lock screen profile.
+  ash::LockScreenReauthDialogTestHelper::ShowDialogAndWait();
+  ASSERT_THAT(ash::ProfileHelper::GetLockScreenProfile(), NotNull());
+
+  // Set policy provided trusted anchors on the primary profile.
+  user_policy_certs_helper_.SetRootCertONCUserPolicy(
+      browser()->profile(),
+      multi_profile_policy_helper_.policy_for_profile_1());
+
+  EXPECT_EQ(net::OK,
+            VerifyTestServerCert(browser()->profile(),
+                                 user_policy_certs_helper_.server_cert()));
+  // Verify that the lock screen can access the policy provided certs.
+  EXPECT_EQ(net::ERR_CERT_AUTHORITY_INVALID,
+            VerifyTestServerCert(ash::ProfileHelper::GetLockScreenProfile(),
+                                 user_policy_certs_helper_.server_cert()));
+}
+
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
 }  // namespace
diff --git a/chrome/browser/preferences/BUILD.gn b/chrome/browser/preferences/BUILD.gn
index b520755..d6ee9b2 100644
--- a/chrome/browser/preferences/BUILD.gn
+++ b/chrome/browser/preferences/BUILD.gn
@@ -42,6 +42,7 @@
     "//components/offline_pages/core/prefetch/prefetch_prefs.cc",
     "//components/password_manager/core/common/password_manager_pref_names.cc",
     "//components/payments/core/payment_prefs.cc",
+    "//components/policy/core/common/policy_pref_names.cc",
     "//components/privacy_sandbox/privacy_sandbox_prefs.cc",
     "//components/safe_browsing/core/common/safe_browsing_prefs.cc",
     "//components/signin/public/base/signin_pref_names.cc",
diff --git a/chrome/browser/prefs/incognito_mode_prefs.cc b/chrome/browser/prefs/incognito_mode_prefs.cc
index 0ee66fd5..dc0fc7f 100644
--- a/chrome/browser/prefs/incognito_mode_prefs.cc
+++ b/chrome/browser/prefs/incognito_mode_prefs.cc
@@ -14,6 +14,7 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/common/chrome_switches.h"
 #include "chrome/common/pref_names.h"
+#include "components/policy/core/common/policy_pref_names.h"
 #include "components/pref_registry/pref_registry_syncable.h"
 #include "components/prefs/pref_service.h"
 
@@ -54,15 +55,16 @@
 // static
 void IncognitoModePrefs::SetAvailability(PrefService* prefs,
                                          const Availability availability) {
-  prefs->SetInteger(prefs::kIncognitoModeAvailability,
+  prefs->SetInteger(policy::policy_prefs::kIncognitoModeAvailability,
                     static_cast<int>(availability));
 }
 
 // static
 void IncognitoModePrefs::RegisterProfilePrefs(
     user_prefs::PrefRegistrySyncable* registry) {
-  registry->RegisterIntegerPref(prefs::kIncognitoModeAvailability,
-                                static_cast<int>(kDefaultAvailability));
+  registry->RegisterIntegerPref(
+      policy::policy_prefs::kIncognitoModeAvailability,
+      static_cast<int>(kDefaultAvailability));
 #if BUILDFLAG(IS_ANDROID)
   registry->RegisterBooleanPref(prefs::kIncognitoReauthenticationForAndroid,
                                 false);
@@ -124,7 +126,8 @@
     const PrefService* pref_service,
     GetAvailabilityMode mode) {
   DCHECK(pref_service);
-  int pref_value = pref_service->GetInteger(prefs::kIncognitoModeAvailability);
+  int pref_value = pref_service->GetInteger(
+      policy::policy_prefs::kIncognitoModeAvailability);
   Availability result = kDefaultAvailability;
   bool valid = IntToAvailability(pref_value, &result);
   DCHECK(valid);
diff --git a/chrome/browser/prefs/incognito_mode_prefs_unittest.cc b/chrome/browser/prefs/incognito_mode_prefs_unittest.cc
index 5ab9251..de550b47 100644
--- a/chrome/browser/prefs/incognito_mode_prefs_unittest.cc
+++ b/chrome/browser/prefs/incognito_mode_prefs_unittest.cc
@@ -5,7 +5,7 @@
 #include "chrome/browser/prefs/incognito_mode_prefs.h"
 
 #include "base/test/gtest_util.h"
-#include "chrome/common/pref_names.h"
+#include "components/policy/core/common/policy_pref_names.h"
 #include "components/sync_preferences/testing_pref_service_syncable.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -38,19 +38,19 @@
 }
 
 TEST_F(IncognitoModePrefsTest, GetAvailability) {
-  prefs_.SetUserPref(prefs::kIncognitoModeAvailability,
+  prefs_.SetUserPref(policy::policy_prefs::kIncognitoModeAvailability,
                      std::make_unique<base::Value>(static_cast<int>(
                          IncognitoModePrefs::Availability::kEnabled)));
   EXPECT_EQ(IncognitoModePrefs::Availability::kEnabled,
             IncognitoModePrefs::GetAvailability(&prefs_));
 
-  prefs_.SetUserPref(prefs::kIncognitoModeAvailability,
+  prefs_.SetUserPref(policy::policy_prefs::kIncognitoModeAvailability,
                      std::make_unique<base::Value>(static_cast<int>(
                          IncognitoModePrefs::Availability::kDisabled)));
   EXPECT_EQ(IncognitoModePrefs::Availability::kDisabled,
             IncognitoModePrefs::GetAvailability(&prefs_));
 
-  prefs_.SetUserPref(prefs::kIncognitoModeAvailability,
+  prefs_.SetUserPref(policy::policy_prefs::kIncognitoModeAvailability,
                      std::make_unique<base::Value>(static_cast<int>(
                          IncognitoModePrefs::Availability::kForced)));
   EXPECT_EQ(IncognitoModePrefs::Availability::kForced,
@@ -60,7 +60,7 @@
 typedef IncognitoModePrefsTest IncognitoModePrefsDeathTest;
 
 TEST_F(IncognitoModePrefsDeathTest, GetAvailabilityBadValue) {
-  prefs_.SetUserPref(prefs::kIncognitoModeAvailability,
+  prefs_.SetUserPref(policy::policy_prefs::kIncognitoModeAvailability,
                      std::make_unique<base::Value>(-1));
   EXPECT_DCHECK_DEATH({
     IncognitoModePrefs::Availability availability =
diff --git a/chrome/browser/preloading/prefetch/search_prefetch/search_preload_unified_browsertest.cc b/chrome/browser/preloading/prefetch/search_prefetch/search_preload_unified_browsertest.cc
index 1848ade..4c0e2f5d 100644
--- a/chrome/browser/preloading/prefetch/search_prefetch/search_preload_unified_browsertest.cc
+++ b/chrome/browser/preloading/prefetch/search_prefetch/search_preload_unified_browsertest.cc
@@ -20,7 +20,6 @@
 #include "chrome/browser/preloading/prefetch/search_prefetch/search_prefetch_service.h"
 #include "chrome/browser/preloading/prefetch/search_prefetch/search_prefetch_service_factory.h"
 #include "chrome/browser/preloading/prefetch/search_prefetch/search_preload_test_response_utils.h"
-#include "chrome/browser/preloading/prefetch/search_prefetch/streaming_search_prefetch_url_loader.h"
 #include "chrome/browser/preloading/prerender/prerender_manager.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/search_engines/template_url_service_factory.h"
@@ -1644,9 +1643,10 @@
   base::test::ScopedFeatureList scoped_feature_list_;
 };
 
-// Tests cancelling prerenders should not delete the prefetched responses.
+// Tests prerender is cancelled after SearchPrefetchService cancels prefetch
+// requests.
 IN_PROC_BROWSER_TEST_F(SearchPreloadUnifiedFallbackBrowserTest,
-                       PrefetchSucceedAfterPrerenderFailed) {
+                       FetchPrerenderFetch) {
   base::HistogramTester histogram_tester;
   const GURL kInitialUrl = embedded_test_server()->GetURL("/empty.html");
   const GURL kNavigatedUrl = embedded_test_server()->GetURL("/title1.html");
@@ -1693,6 +1693,10 @@
           GetCanonicalSearchURL(expected_prerender_url));
   EXPECT_TRUE(prefetch_status.has_value());
 
+  // TODO: Use another metric to trigger whether prerender takes a prefetched
+  // response.
+
+  // Navigate away to flush the metrics.
   ASSERT_TRUE(
       content::NavigateToURL(GetActiveWebContents(), expected_prerender_url));
   histogram_tester.ExpectUniqueSample(
@@ -1700,8 +1704,8 @@
       SearchPrefetchStatus::kPrefetchServedForRealNavigation, 1);
 }
 
-// Tests that prefetched response can be served to prerender client
-// successfully.
+// Tests prerender is cancelled after SearchPrefetchService cancels prefetch
+// requests.
 IN_PROC_BROWSER_TEST_F(SearchPreloadUnifiedFallbackBrowserTest,
                        FetchPrerenderActivated) {
   base::HistogramTester histogram_tester;
@@ -1737,16 +1741,12 @@
   EXPECT_TRUE(prefetch_status.has_value());
   EXPECT_NE(prefetch_status.value(), SearchPrefetchStatus::kPrerendered);
 
+  // TODO: Use another metric to trigger whether prerender takes a prefetched
+  // response.
   content::test::PrerenderHostObserver prerender_observer(
       *GetActiveWebContents(), expected_prerender_url);
   NavigateToPrerenderedResult(expected_prerender_url);
   prerender_observer.WaitForActivation();
-  WaitForActivatedPageLoaded();
-  histogram_tester.ExpectBucketCount(
-      "Omnibox.SearchPreload.ResponseDataReaderFinalStatus.Prerender",
-      StreamingSearchPrefetchURLLoader::ResponseReader::
-          ResponseDataReaderStatus::kCompleted,
-      1);
 }
 
 // Tests that the SearchSuggestionService can trigger prerendering if it
@@ -1800,16 +1800,10 @@
                                                         expected_prerender_url);
   NavigateToPrerenderedResult(expected_prerender_url);
   prerender_observer.WaitForActivation();
-  WaitForActivatedPageLoaded();
 
   // No prerender requests went through network.
   EXPECT_EQ(1, prerender_helper().GetRequestCount(expected_prefetch_url));
   EXPECT_EQ(0, prerender_helper().GetRequestCount(expected_prerender_url));
-  histogram_tester.ExpectBucketCount(
-      "Omnibox.SearchPreload.ResponseDataReaderFinalStatus.Prerender",
-      StreamingSearchPrefetchURLLoader::ResponseReader::
-          ResponseDataReaderStatus::kCompleted,
-      1);
 }
 
 // Tests that once prefetch encountered error, prerender would be canceled as
@@ -1857,11 +1851,12 @@
   prerender_observer.WaitForDestroyed();
   WaitUntilStatusChangesTo(GetCanonicalSearchURL(expected_prefetch_url),
                            {SearchPrefetchStatus::kRequestFailed});
+  // TODO(crbug.com/1400881): We should have another metric to track the
+  // cancellation reason.
   histogram_tester.ExpectUniqueSample(
-      "Omnibox.SearchPreload.ResponseDataReaderFinalStatus.Prerender",
-      StreamingSearchPrefetchURLLoader::ResponseReader::
-          ResponseDataReaderStatus::kFailedWithErrorCode,
-      1);
+      "Prerender.Experimental.PrerenderHostFinalStatus.Embedder_"
+      "DefaultSearchEngine",
+      /*SearchPrefetchStatus::kPrefetchServedForRealNavigation*/ 16, 1);
 }
 
 // Edge case: when the prerendering navigation is still reading from the cache,
@@ -1931,11 +1926,6 @@
   // Prerender should not retry the request.
   EXPECT_EQ(0, prerender_helper().GetRequestCount(expected_prerender_url));
   EXPECT_EQ(1, prerender_helper().GetRequestCount(expected_prefetch_url));
-  histogram_tester.ExpectUniqueSample(
-      "Omnibox.SearchPreload.ResponseDataReaderFinalStatus.Prerender",
-      StreamingSearchPrefetchURLLoader::ResponseReader::
-          ResponseDataReaderStatus::kCompleted,
-      1);
 }
 
 class NoCancelSearchPreloadUnifiedFallbackBrowserTest
@@ -1965,7 +1955,7 @@
 // prefetched result in another tab and activate the prefetched response
 // successfully.
 IN_PROC_BROWSER_TEST_F(NoCancelSearchPreloadUnifiedFallbackBrowserTest,
-                       OpenPrefetchedResponseInBackgroundedTab) {
+                       ServingToPrerenderingUntilCompletion) {
   base::HistogramTester histogram_tester;
   set_service_deferral_type(
       SearchPreloadTestResponseDeferralType::kDeferChunkedResponseBody);
@@ -2017,7 +2007,6 @@
                                     ui::PAGE_TRANSITION_FROM_ADDRESS_BAR),
           /*is_renderer_initiated=*/false));
   WaitUntilStatusChangesTo(GetCanonicalSearchURL(expected_prerender_url), {});
-
   // TODO(crbug.com/1423259): Ideally we should open the tab with the
   // prerendered result.
   prerender_observer.WaitForDestroyed();
@@ -2055,17 +2044,6 @@
   EXPECT_TRUE(base::Contains(prefetch_inner_html, "PREFETCH"));
   EXPECT_EQ(0, prerender_helper().GetRequestCount(expected_prerender_url));
   EXPECT_EQ(2, prerender_helper().GetRequestCount(expected_prefetch_url));
-
-  histogram_tester.ExpectBucketCount(
-      "Omnibox.SearchPreload.ResponseDataReaderFinalStatus.Prerender",
-      StreamingSearchPrefetchURLLoader::ResponseReader::
-          ResponseDataReaderStatus::kDataWritingFailure,
-      1);
-  histogram_tester.ExpectBucketCount(
-      "Omnibox.SearchPreload.ResponseDataReaderFinalStatus.Prerender",
-      StreamingSearchPrefetchURLLoader::ResponseReader::
-          ResponseDataReaderStatus::kCompleted,
-      1);
 }
 
 #endif  // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_CHROMEOS_LACROS)
diff --git a/chrome/browser/preloading/prefetch/search_prefetch/streaming_search_prefetch_url_loader.cc b/chrome/browser/preloading/prefetch/search_prefetch/streaming_search_prefetch_url_loader.cc
index fa0d3aa..0a4aed6 100644
--- a/chrome/browser/preloading/prefetch/search_prefetch/streaming_search_prefetch_url_loader.cc
+++ b/chrome/browser/preloading/prefetch/search_prefetch/streaming_search_prefetch_url_loader.cc
@@ -21,7 +21,6 @@
 #include "chrome/browser/profiles/profile.h"
 #include "content/public/browser/storage_partition.h"
 #include "mojo/public/c/system/data_pipe.h"
-#include "net/base/net_errors.h"
 #include "net/http/http_response_headers.h"
 #include "net/http/http_status_code.h"
 #include "net/traffic_annotation/network_traffic_annotation.h"
@@ -85,11 +84,6 @@
     const network::URLLoaderCompletionStatus& status) {
   DCHECK(!url_loader_completion_status_);
   url_loader_completion_status_ = status;
-  if (url_loader_completion_status_->error_code != net::OK) {
-    // TODO(https://crbug.com/1400881): This status should only be kStarted
-    // after discarding the prerendered page upon pipeline creation failure.
-    status_ = ResponseDataReaderStatus::kFailedWithErrorCode;
-  }
   MaybeSendCompletionSignal();
 }
 
@@ -103,8 +97,6 @@
     // TODO(https://crbug.com/1400881): Discard prerender.
     return;
   }
-  CHECK_EQ(status_, ResponseDataReaderStatus::kCreated);
-  status_ = ResponseDataReaderStatus::kStarted;
   forwarding_client_->OnReceiveResponse(resource_response->Clone(),
                                         std::move(consumer_handle),
                                         /*cached_metadata=*/absl::nullopt);
@@ -128,9 +120,7 @@
     }
 
     if (result != MOJO_RESULT_OK) {
-      CHECK_EQ(status_, ResponseDataReaderStatus::kStarted);
-      // This case is usually caused by the client stopping loading.
-      status_ = ResponseDataReaderStatus::kDataWritingFailure;
+      // Failed it;
       return;
     }
 
@@ -158,49 +148,12 @@
     return;
   }
   if (producer_handle_) {
-    if (url_loader_completion_status_->error_code == net::OK) {
-      CHECK_EQ(status_, ResponseDataReaderStatus::kStarted);
-      status_ = ResponseDataReaderStatus::kCompleted;
-    }
     forwarding_client_->OnComplete(*url_loader_completion_status_);
   }
   producer_handle_.reset();
 }
 
-void StreamingSearchPrefetchURLLoader::ResponseReader::OnDestroyed(
-    bool canceled_by_client) {
-  switch (status_) {
-    // Used to track the data pipe construction error. Do not care about client.
-    case ResponseDataReaderStatus::kCreated:
-    // Has completed serving.
-    case ResponseDataReaderStatus::kCompleted:
-    // For tracking reader failures.
-    case ResponseDataReaderStatus::kFailedWithErrorCode:
-    case ResponseDataReaderStatus::kDataWritingFailure:
-    // Has recorded the reason.
-    case ResponseDataReaderStatus::kCanceledByClient:
-    case ResponseDataReaderStatus::kCanceledByLoader:
-      return;
-    // The `StreamingSearchPrefetchURLLoader` is destroyed, so this instance
-    // is destroyed.
-    case ResponseDataReaderStatus::kStarted:
-      status_ = canceled_by_client
-                    ? ResponseDataReaderStatus::kCanceledByClient
-                    : ResponseDataReaderStatus::kCanceledByLoader;
-      return;
-  }
-}
-
-StreamingSearchPrefetchURLLoader::ResponseReader::~ResponseReader() {
-  // Always ensure we recorded something on destruction.
-  OnDestroyed(/*canceled_by_client=*/false);
-
-  // TODO(crbug.com/1400881): For now prerender is the only use case. After
-  // refactoring it should specify the client type.
-  base::UmaHistogramEnumeration(
-      "Omnibox.SearchPreload.ResponseDataReaderFinalStatus.Prerender", status_);
-}
-
+StreamingSearchPrefetchURLLoader::ResponseReader::~ResponseReader() = default;
 void StreamingSearchPrefetchURLLoader::ResponseReader::FollowRedirect(
     const std::vector<std::string>& removed_headers,
     const net::HttpRequestHeaders& modified_headers,
@@ -381,9 +334,6 @@
     mojo::PendingRemote<network::mojom::URLLoaderClient> forwarding_client) {
   DCHECK(prerender_utils::SearchPreloadShareableCacheIsEnabled());
   DCHECK(streaming_prefetch_request_);
-  if (response_reader_for_prerender_) {
-    response_reader_for_prerender_->OnDestroyed(/* canceled_by_client=*/false);
-  }
   response_reader_for_prerender_ = std::make_unique<ResponseReader>(
       std::move(receiver), std::move(forwarding_client),
       base::BindOnce(
@@ -399,8 +349,6 @@
 
 void StreamingSearchPrefetchURLLoader::OnPrerenderForwardingDisconnect() {
   DCHECK(prerender_utils::SearchPreloadShareableCacheIsEnabled());
-  DCHECK(response_reader_for_prerender_);
-  response_reader_for_prerender_->OnDestroyed(/*canceled_by_client=*/true);
   response_reader_for_prerender_.reset();
   if (self_pointer_) {
     MaybeDeleteItself();
@@ -667,10 +615,6 @@
     OnForwardingComplete();
     return;
   }
-  status_ = status;
-  if (response_reader_for_prerender_) {
-    response_reader_for_prerender_->OnStatusCodeReady(status);
-  }
 
   if (streaming_prefetch_request_) {
     DCHECK(!forwarding_client_);
@@ -682,6 +626,11 @@
       return;
     }
   }
+
+  status_ = status;
+  if (response_reader_for_prerender_) {
+    response_reader_for_prerender_->OnStatusCodeReady(status);
+  }
 }
 
 void StreamingSearchPrefetchURLLoader::RunEventQueue() {
diff --git a/chrome/browser/preloading/prefetch/search_prefetch/streaming_search_prefetch_url_loader.h b/chrome/browser/preloading/prefetch/search_prefetch/streaming_search_prefetch_url_loader.h
index e756b74..2bbf3f54 100644
--- a/chrome/browser/preloading/prefetch/search_prefetch/streaming_search_prefetch_url_loader.h
+++ b/chrome/browser/preloading/prefetch/search_prefetch/streaming_search_prefetch_url_loader.h
@@ -39,29 +39,6 @@
   // `StreamingSearchPrefetchURLLoader`'s data cache.
   class ResponseReader : public network::mojom::URLLoader {
    public:
-    // These values are persisted to logs. Entries should not be renumbered and
-    // numeric values should never be reused. Recorded as
-    // SearchPrefetchResponseDataReaderStatus in logs.
-    enum class ResponseDataReaderStatus {
-      kCreated = 0,
-      // After this reader built a data pipe with the URLLoader that it is
-      // serving to successfully.
-      kStarted = 1,
-      // For a success serving case.
-      kCompleted = 2,
-      // Its `StreamingSearchPrefetchURLLoader` failed to read response from the
-      // internet successfully.
-      kFailedWithErrorCode = 3,
-      // It failed to push data to its client.
-      kDataWritingFailure = 4,
-      // The client is destroyed, so this instance is destroyed.
-      kCanceledByClient = 5,
-      // The `StreamingSearchPrefetchURLLoader` is destroyed, so this instance
-      // is destroyed.
-      kCanceledByLoader = 6,
-      kMaxValue = kCanceledByLoader,
-    };
-
     ResponseReader(
         mojo::PendingReceiver<network::mojom::URLLoader> forward_receiver,
         mojo::PendingRemote<network::mojom::URLLoaderClient> forwarding_client,
@@ -95,7 +72,6 @@
     void OnResponseDataComplete(int bytes_of_raw_data_to_transfer,
                                 const std::string& response_body);
     void OnStatusCodeReady(const network::URLLoaderCompletionStatus& status);
-    void OnDestroyed(bool canceled_by_client);
 
    private:
     // Checks if all data have be pushed to its consumer and the corresponding
@@ -113,9 +89,6 @@
     // Set to -1 when the URLLoader has not drained all data.
     int complete_size_bytes_to_transfer_ = -1;
 
-    // Tracking the current status.
-    ResponseDataReaderStatus status_ = ResponseDataReaderStatus::kCreated;
-
     // Data pipe for pushing the received response to the client.
     mojo::ScopedDataPipeProducerHandle producer_handle_;
     std::unique_ptr<mojo::SimpleWatcher> handle_watcher_;
@@ -284,7 +257,7 @@
   // the navigation stack.
   raw_ptr<SearchPrefetchRequest> streaming_prefetch_request_;
 
-  // Whether we are serving from |body_content_|.
+  // Whether we are serving from |bdoy_content_|.
   bool serving_from_data_ = false;
 
   // The status returned from |network_url_loader_|.
diff --git a/chrome/browser/printing/system_access_process_print_browsertest.cc b/chrome/browser/printing/system_access_process_print_browsertest.cc
index 810eb399..960fc92 100644
--- a/chrome/browser/printing/system_access_process_print_browsertest.cc
+++ b/chrome/browser/printing/system_access_process_print_browsertest.cc
@@ -812,7 +812,7 @@
 
 #if BUILDFLAG(ENABLE_OOP_PRINTING)
 
-IN_PROC_BROWSER_TEST_P(SystemAccessProcessServicePrintBrowserTest,
+IN_PROC_BROWSER_TEST_F(SystemAccessProcessSandboxedServicePrintBrowserTest,
                        StartPrinting) {
   AddPrinter("printer1");
   SetPrinterNameForSubsequentContexts("printer1");
@@ -867,7 +867,7 @@
 #endif  // BUILDFLAG(IS_LINUX) && BUILDFLAG(USE_CUPS)
 }
 
-IN_PROC_BROWSER_TEST_P(SystemAccessProcessServicePrintBrowserTest,
+IN_PROC_BROWSER_TEST_F(SystemAccessProcessSandboxedServicePrintBrowserTest,
                        StartPrintingMultipage) {
   AddPrinter("printer1");
   SetPrinterNameForSubsequentContexts("printer1");
@@ -1353,7 +1353,7 @@
   EXPECT_EQ(print_job_destruction_count(), 1);
 }
 
-IN_PROC_BROWSER_TEST_P(SystemAccessProcessServicePrintBrowserTest,
+IN_PROC_BROWSER_TEST_F(SystemAccessProcessSandboxedServicePrintBrowserTest,
                        StartBasicPrint) {
   AddPrinter("printer1");
   SetPrinterNameForSubsequentContexts("printer1");
@@ -1586,7 +1586,7 @@
   EXPECT_EQ(print_job_construction_count(), 0);
 }
 
-IN_PROC_BROWSER_TEST_P(SystemAccessProcessServicePrintBrowserTest,
+IN_PROC_BROWSER_TEST_F(SystemAccessProcessSandboxedServicePrintBrowserTest,
                        StartBasicPrintConcurrent) {
   ASSERT_TRUE(embedded_test_server()->Started());
   GURL url(embedded_test_server()->GetURL("/printing/test3.html"));
@@ -1622,7 +1622,7 @@
 }
 
 #if BUILDFLAG(ENABLE_PRINT_PREVIEW)
-IN_PROC_BROWSER_TEST_P(SystemAccessProcessServicePrintBrowserTest,
+IN_PROC_BROWSER_TEST_F(SystemAccessProcessSandboxedServicePrintBrowserTest,
                        SystemPrintFromPrintPreviewConcurrent) {
   AddPrinter("printer1");
   SetPrinterNameForSubsequentContexts("printer1");
diff --git a/chrome/browser/profiles/incognito_mode_policy_handler.cc b/chrome/browser/profiles/incognito_mode_policy_handler.cc
index f1d349f..844cdd3 100644
--- a/chrome/browser/profiles/incognito_mode_policy_handler.cc
+++ b/chrome/browser/profiles/incognito_mode_policy_handler.cc
@@ -10,10 +10,10 @@
 #include "base/values.h"
 #include "build/build_config.h"
 #include "chrome/browser/prefs/incognito_mode_prefs.h"
-#include "chrome/common/pref_names.h"
 #include "chrome/credential_provider/common/gcp_strings.h"
 #include "components/policy/core/browser/policy_error_map.h"
 #include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_pref_names.h"
 #include "components/policy/policy_constants.h"
 #include "components/prefs/pref_value_map.h"
 #include "components/strings/grit/components_strings.h"
@@ -80,14 +80,14 @@
     IncognitoModePrefs::Availability availability_enum_value;
     if (IncognitoModePrefs::IntToAvailability(availability->GetInt(),
                                               &availability_enum_value)) {
-      prefs->SetInteger(prefs::kIncognitoModeAvailability,
+      prefs->SetInteger(policy::policy_prefs::kIncognitoModeAvailability,
                         static_cast<int>(availability_enum_value));
     }
   } else if (deprecated_enabled) {
     // If kIncognitoModeAvailability is not specified, check the obsolete
     // kIncognitoEnabled.
     prefs->SetInteger(
-        prefs::kIncognitoModeAvailability,
+        policy::policy_prefs::kIncognitoModeAvailability,
         static_cast<int>(deprecated_enabled->GetBool()
                              ? IncognitoModePrefs::Availability::kEnabled
                              : IncognitoModePrefs::Availability::kDisabled));
diff --git a/chrome/browser/profiles/incognito_mode_policy_handler_unittest.cc b/chrome/browser/profiles/incognito_mode_policy_handler_unittest.cc
index 9817753..6ceeaf7 100644
--- a/chrome/browser/profiles/incognito_mode_policy_handler_unittest.cc
+++ b/chrome/browser/profiles/incognito_mode_policy_handler_unittest.cc
@@ -6,9 +6,9 @@
 
 #include "base/memory/ptr_util.h"
 #include "chrome/browser/prefs/incognito_mode_prefs.h"
-#include "chrome/common/pref_names.h"
 #include "components/policy/core/browser/configuration_policy_pref_store.h"
 #include "components/policy/core/browser/configuration_policy_pref_store_test.h"
+#include "components/policy/core/common/policy_pref_names.h"
 #include "components/policy/core/common/policy_types.h"
 #include "components/policy/policy_constants.h"
 
@@ -50,7 +50,8 @@
 
   void VerifyValues(IncognitoModePrefs::Availability availability) {
     const base::Value* value = nullptr;
-    EXPECT_TRUE(store_->GetValue(prefs::kIncognitoModeAvailability, &value));
+    EXPECT_TRUE(store_->GetValue(
+        policy::policy_prefs::kIncognitoModeAvailability, &value));
     EXPECT_EQ(base::Value(static_cast<int>(availability)), *value);
   }
 };
@@ -83,7 +84,8 @@
        NoObsoletePolicyAndNoIncognitoAvailability) {
   SetPolicies(INCOGNITO_ENABLED_UNKNOWN, kIncognitoModeAvailabilityNotSet);
   const base::Value* value = nullptr;
-  EXPECT_FALSE(store_->GetValue(prefs::kIncognitoModeAvailability, &value));
+  EXPECT_FALSE(store_->GetValue(
+      policy::policy_prefs::kIncognitoModeAvailability, &value));
 }
 
 // Checks that if the obsolete IncognitoEnabled policy is set, if sets
diff --git a/chrome/browser/resources/new_tab_page/app.html b/chrome/browser/resources/new_tab_page/app.html
index 60fbc56..6ebf410 100644
--- a/chrome/browser/resources/new_tab_page/app.html
+++ b/chrome/browser/resources/new_tab_page/app.html
@@ -372,7 +372,8 @@
   <dom-if if="[[lazyRender_]]" on-dom-change="onLazyRendered_">
     <template>
       <template is="dom-if" if="[[shortcutsEnabled_]]">
-        <cr-most-visited id="mostVisited" theme="[[theme_.mostVisited]]">
+        <cr-most-visited id="mostVisited" theme="[[theme_.mostVisited]]"
+            single-row="[[singleRowShortcutsEnabled_]]">
         </cr-most-visited>
       </template>
       <template is="dom-if" if="[[middleSlotPromoEnabled_]]">
diff --git a/chrome/browser/resources/new_tab_page/app.ts b/chrome/browser/resources/new_tab_page/app.ts
index 1a79b5ce..f2ed4922 100644
--- a/chrome/browser/resources/new_tab_page/app.ts
+++ b/chrome/browser/resources/new_tab_page/app.ts
@@ -229,6 +229,11 @@
         value: () => loadTimeData.getBoolean('shortcutsEnabled'),
       },
 
+      singleRowShortcutsEnabled_: {
+        type: Boolean,
+        value: () => loadTimeData.getBoolean('singleRowShortcutsEnabled'),
+      },
+
       modulesFreShown: {
         type: Boolean,
         reflectToAttribute: true,
diff --git a/chrome/browser/resources/new_tab_page/modules/history_clusters/suggest_tile.html b/chrome/browser/resources/new_tab_page/modules/history_clusters/suggest_tile.html
index 4ea0ec6f..64778966 100644
--- a/chrome/browser/resources/new_tab_page/modules/history_clusters/suggest_tile.html
+++ b/chrome/browser/resources/new_tab_page/modules/history_clusters/suggest_tile.html
@@ -52,7 +52,7 @@
 
   .title {
     color: var(--color-new-tab-page-primary-foreground);
-    font-size: 14px;
+    font-size: var(--ntp-module-text-size);
     margin: auto 0;
   }
 </style>
diff --git a/chrome/browser/resources/new_tab_page/modules/history_clusters/tile.html b/chrome/browser/resources/new_tab_page/modules/history_clusters/tile.html
index ef8b13a..f25890a0 100644
--- a/chrome/browser/resources/new_tab_page/modules/history_clusters/tile.html
+++ b/chrome/browser/resources/new_tab_page/modules/history_clusters/tile.html
@@ -75,27 +75,34 @@
   }
 
   #title {
+    -webkit-box-orient: vertical;
     color: var(--color-new-tab-page-primary-foreground);
-    font-size: 16px;
+    display: -webkit-box;
+    font-size: var(--ntp-module-text-size);
+    margin: 16px;
+    max-width: calc(100% - 32px);
     white-space: initial;
   }
 
   :host([large-format]) #title {
+    -webkit-line-clamp: 2;
     height: 48px;
-    max-width: 298px;
-    padding: 16px 16px 24px;
+    line-height: 24px;
+    margin-bottom: 28px;
   }
 
   :host([medium-format]) #title {
-    height: 60px;
+    -webkit-line-clamp: 4;
+    height: 80px;
+    line-height: 20px;
+    margin-inline-start: 8px;
     max-width: 184px;
-    padding: 18px 16px 32px 8px;
   }
 
   :host([small-format]) #title {
-    height: 60px;
-    max-width: 136px;
-    padding: 18px 16px 32px 16px;
+    -webkit-line-clamp: 4;
+    height: 80px;
+    line-height: 20px;
   }
 
   #info-container {
@@ -103,18 +110,17 @@
     display: flex;
     flex-direction: row;
     font-size: 11px;
-    margin-bottom: 24px;
+    height: 20px;
     margin-inline: 16px;
   }
 
-  :host([medium-format]) #info-container {
-    margin-bottom: 14px;
-    margin-inline-start: 8px;
+  :host([large-format]) #info-container {
+    margin-bottom: 24px;
   }
 
-  :host([small-format]) #info-container {
-    height: 20px;
-    margin-bottom: 14px;
+  :host([medium-format]) #info-container {
+    margin-bottom: 12px;
+    margin-inline-start: 8px;
   }
 
   #label,
diff --git a/chrome/browser/resources/password_manager/prefs/pref_toggle_button.html b/chrome/browser/resources/password_manager/prefs/pref_toggle_button.html
index b7ee3cb..2b4ef91 100644
--- a/chrome/browser/resources/password_manager/prefs/pref_toggle_button.html
+++ b/chrome/browser/resources/password_manager/prefs/pref_toggle_button.html
@@ -16,8 +16,8 @@
   }
 
   #labelWrapper {
-    padding: var(--cr-section-vertical-padding) 0;
     margin-inline-end: var(--control-label-spacing);
+    padding: var(--cr-section-vertical-padding) 0;
   }
 </style>
 <div id="outerRow" noSubLabel$="[[!subLabel]]">
diff --git a/chrome/browser/resources/settings/chromeos/OWNERS b/chrome/browser/resources/settings/chromeos/OWNERS
index f1a43124..08aadc4 100644
--- a/chrome/browser/resources/settings/chromeos/OWNERS
+++ b/chrome/browser/resources/settings/chromeos/OWNERS
@@ -13,15 +13,15 @@
 cowmoo@chromium.org
 
 # Subdir OWNERS can approve global changes related to their subdir.
-per-file BUILD.gn,lazy_load.ts,os_settings.gni,os_settings.ts=file://chrome/browser/resources/settings/chromeos/crostini_page/OWNERS
-per-file BUILD.gn,lazy_load.ts,os_settings.gni,os_settings.ts=file://chrome/browser/resources/settings/chromeos/device_page/OWNERS
-per-file BUILD.gn,lazy_load.ts,os_settings.gni,os_settings.ts=file://chrome/browser/resources/settings/chromeos/guest_os/OWNERS
-per-file BUILD.gn,lazy_load.ts,os_settings.gni,os_settings.ts=file://chrome/browser/resources/settings/chromeos/internet_page/OWNERS
-per-file BUILD.gn,lazy_load.ts,os_settings.gni,os_settings.ts=file://chrome/browser/resources/settings/chromeos/keyboard_shortcut_banner/OWNERS
-per-file BUILD.gn,lazy_load.ts,os_settings.gni,os_settings.ts=file://chrome/browser/resources/settings/chromeos/multidevice_page/OWNERS
-per-file BUILD.gn,lazy_load.ts,os_settings.gni,os_settings.ts=file://chrome/browser/resources/settings/chromeos/nearby_share_page/OWNERS
-per-file BUILD.gn,lazy_load.ts,os_settings.gni,os_settings.ts=file://chrome/browser/resources/settings/chromeos/os_apps_page/OWNERS
-per-file BUILD.gn,lazy_load.ts,os_settings.gni,os_settings.ts=file://chrome/browser/resources/settings/chromeos/os_apps_page/app_notifications_page/OWNERS
-per-file BUILD.gn,lazy_load.ts,os_settings.gni,os_settings.ts=file://chrome/browser/resources/settings/chromeos/os_bluetooth_page/OWNERS
-per-file BUILD.gn,lazy_load.ts,os_settings.gni,os_settings.ts=file://chrome/browser/resources/settings/chromeos/os_languages_page/OWNERS
-per-file BUILD.gn,lazy_load.ts,os_settings.gni,os_settings.ts=file://chrome/browser/resources/settings/chromeos/os_printing_page/OWNERS
+per-file lazy_load.ts,os_settings.gni,os_settings.ts=file://chrome/browser/resources/settings/chromeos/crostini_page/OWNERS
+per-file lazy_load.ts,os_settings.gni,os_settings.ts=file://chrome/browser/resources/settings/chromeos/device_page/OWNERS
+per-file lazy_load.ts,os_settings.gni,os_settings.ts=file://chrome/browser/resources/settings/chromeos/guest_os/OWNERS
+per-file lazy_load.ts,os_settings.gni,os_settings.ts=file://chrome/browser/resources/settings/chromeos/internet_page/OWNERS
+per-file lazy_load.ts,os_settings.gni,os_settings.ts=file://chrome/browser/resources/settings/chromeos/keyboard_shortcut_banner/OWNERS
+per-file lazy_load.ts,os_settings.gni,os_settings.ts=file://chrome/browser/resources/settings/chromeos/multidevice_page/OWNERS
+per-file lazy_load.ts,os_settings.gni,os_settings.ts=file://chrome/browser/resources/settings/chromeos/nearby_share_page/OWNERS
+per-file lazy_load.ts,os_settings.gni,os_settings.ts=file://chrome/browser/resources/settings/chromeos/os_apps_page/OWNERS
+per-file lazy_load.ts,os_settings.gni,os_settings.ts=file://chrome/browser/resources/settings/chromeos/os_apps_page/app_notifications_page/OWNERS
+per-file lazy_load.ts,os_settings.gni,os_settings.ts=file://chrome/browser/resources/settings/chromeos/os_bluetooth_page/OWNERS
+per-file lazy_load.ts,os_settings.gni,os_settings.ts=file://chrome/browser/resources/settings/chromeos/os_languages_page/OWNERS
+per-file lazy_load.ts,os_settings.gni,os_settings.ts=file://chrome/browser/resources/settings/chromeos/os_printing_page/OWNERS
diff --git a/chrome/browser/resources/settings/chromeos/device_page/device_page.html b/chrome/browser/resources/settings/chromeos/device_page/device_page.html
index 4ccf4bc..bfad43d 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/device_page.html
+++ b/chrome/browser/resources/settings/chromeos/device_page/device_page.html
@@ -110,7 +110,7 @@
     <os-settings-subpage page-title="$i18n{pointingStickTitle}">
       <settings-per-device-pointing-stick
           is-device-settings-split-enabled="[[isDeviceSettingsSplitEnabled_]]"
-          pointingSticks="[[pointingSticks]">
+          pointing-sticks="[[pointingSticks]]">
       </settings-per-device-pointing-stick>
     </os-settings-subpage>
   </template>
diff --git a/chrome/browser/resources/settings/chromeos/os_a11y_page/chromevox_subpage.html b/chrome/browser/resources/settings/chromeos/os_a11y_page/chromevox_subpage.html
index 01854b32..89edc658 100644
--- a/chrome/browser/resources/settings/chromeos/os_a11y_page/chromevox_subpage.html
+++ b/chrome/browser/resources/settings/chromeos/os_a11y_page/chromevox_subpage.html
@@ -17,6 +17,10 @@
     padding-inline-start: var(--cr-section-indent-width);
   }
 
+  .indented {
+    margin-inline-start: 32px;
+  }
+
   .settings-box {
     margin-inline-end: var(--cr-section-padding);
     padding-inline-end: 0;
@@ -190,5 +194,19 @@
         sub-label="$i18n{chromeVoxEventLogDescription}"
         on-click="onEventLogTap_" external embedded>
     </cr-link-row>
+    <template is="dom-if" if="[[prefs.settings.a11y.chromevox.enable_event_stream_logging.value]]">
+      <div class="indented">
+        <template is="dom-repeat" items="[[eventStreamFilters_]]">
+          <settings-toggle-button
+              id="[[item]]"
+              class="settings-box"
+              pref="[[getEventStreamFilterPref_(item, prefs.settings.a11y.chromevox.event_stream_filters)]]"
+              label="[[item]]"
+              on-settings-boolean-control-change="onEventStreamFilterPrefChanged_"
+              no-set-pref>
+          </settings-toggle-button>
+        </template>
+      </div>
+    </template>
   </div>
 </iron-collapse>
diff --git a/chrome/browser/resources/settings/chromeos/os_a11y_page/chromevox_subpage.ts b/chrome/browser/resources/settings/chromeos/os_a11y_page/chromevox_subpage.ts
index 852991b6..27d7782 100644
--- a/chrome/browser/resources/settings/chromeos/os_a11y_page/chromevox_subpage.ts
+++ b/chrome/browser/resources/settings/chromeos/os_a11y_page/chromevox_subpage.ts
@@ -28,10 +28,16 @@
 import {getTemplate} from './chromevox_subpage.html.js';
 import {ChromeVoxSubpageBrowserProxy, ChromeVoxSubpageBrowserProxyImpl} from './chromevox_subpage_browser_proxy.js';
 
+export {SettingsToggleButtonElement} from '../../controls/settings_toggle_button.js';
+
 const SYSTEM_VOICE = 'chromeos_system_voice';
 const CHROMEVOX_EXTENSION_ID = 'mndnfokpggljbaajbnioimlmbfngpief';
 const GOOGLE_TTS_EXTENSION_ID = 'gjjabgpgjpampikjhjpfhneeoapjbjaf';
 const ESPEAK_TTS_EXTENSION_ID = 'dakbfdmgjiabojdgbiljlhgjbokobjpg';
+const EVENT_STREAM_FILTERS_PREF_KEY =
+    'settings.a11y.chromevox.event_stream_filters';
+
+type EventStreamFiltersPrefValue = Record<string, boolean>;
 
 /**
  * Represents a voice as sent from the TTS Handler class.
@@ -169,6 +175,70 @@
         type: Boolean,
         value: false,
       },
+
+      /**
+       * Event stream filters list. Should match
+       * SettingsManager.EVENT_STREAM_FILTERS from settings_manager.js.
+       */
+      eventStreamFilters_: {
+        readOnly: true,
+        type: Array,
+        value() {
+          return [
+            'activedescendantchanged',
+            'alert',
+            'ariaAttributeChanged',
+            'autocorrectionOccured',
+            'blur',
+            'checkedStateChanged',
+            'childrenChanged',
+            'clicked',
+            'documentSelectionChanged',
+            'documentTitleChanged',
+            'expandedChanged',
+            'focus',
+            'focusContext',
+            'hide',
+            'hitTestResult',
+            'hover',
+            'imageFrameUpdated',
+            'invalidStatusChanged',
+            'layoutComplete',
+            'liveRegionChanged',
+            'liveRegionCreated',
+            'loadComplete',
+            'locationChanged',
+            'mediaStartedPlaying',
+            'mediaStoppedPlaying',
+            'menuEnd',
+            'menuItemSelected',
+            'menuListValueChanged',
+            'menuPopupEnd',
+            'menuPopupStart',
+            'menuStart',
+            'mouseCanceled',
+            'mouseDragged',
+            'mouseMoved',
+            'mousePressed',
+            'mouseReleased',
+            'rowCollapsed',
+            'rowCountChanged',
+            'rowExpanded',
+            'scrollPositionChanged',
+            'scrolledToAnchor',
+            'selectedChildrenChanged',
+            'selection',
+            'selectionAdd',
+            'selectionRemove',
+            'show',
+            'stateChanged',
+            'textChanged',
+            'textSelectionChanged',
+            'treeChanged',
+            'valueInTextFieldChanged',
+          ];
+        },
+      },
     };
   }
 
@@ -299,6 +369,34 @@
         'chrome-extension://' + CHROMEVOX_EXTENSION_ID +
         '/chromevox/log_page/log.html');
   }
+
+  private getEventStreamFilterPref_(eventStreamFilter: string):
+      chrome.settingsPrivate.PrefObject<boolean> {
+    return {
+      key: '',
+      type: chrome.settingsPrivate.PrefType.BOOLEAN,
+      value: Boolean(this.prefs) &&
+          this.getPref<EventStreamFiltersPrefValue>(
+                  EVENT_STREAM_FILTERS_PREF_KEY)
+              .value[eventStreamFilter],
+    };
+  }
+
+  /**
+   * When an event stream filter checkbox is checked, update the dictionary pref
+   * of event stream filter states.
+   */
+  private onEventStreamFilterPrefChanged_(e: Event): void {
+    // Get all eventStreamFilters, set new filter state.
+    const filter = e.target as SettingsToggleButtonElement;
+    const eventStreamFilters = {
+      ...this
+          .getPref<EventStreamFiltersPrefValue>(EVENT_STREAM_FILTERS_PREF_KEY)
+          .value,
+      [filter.id]: filter.checked,
+    };
+    this.setPrefValue(EVENT_STREAM_FILTERS_PREF_KEY, eventStreamFilters);
+  }
 }
 
 declare global {
diff --git a/chrome/browser/share/android/java/res/values/dimens.xml b/chrome/browser/share/android/java/res/values/dimens.xml
index eaa06cd4..658d69f70 100644
--- a/chrome/browser/share/android/java/res/values/dimens.xml
+++ b/chrome/browser/share/android/java/res/values/dimens.xml
@@ -23,4 +23,7 @@
 
     <!-- IPH for Link Toggle -->
     <dimen name="toggle_iph_y_inset">8dp</dimen>
+
+    <!-- Android share sheet -->
+    <dimen name="share_preview_favicon_size">32dp</dimen>
 </resources>
diff --git a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/android_share_sheet/AndroidShareSheetController.java b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/android_share_sheet/AndroidShareSheetController.java
index 6611306..800e7bd 100644
--- a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/android_share_sheet/AndroidShareSheetController.java
+++ b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/android_share_sheet/AndroidShareSheetController.java
@@ -5,6 +5,10 @@
 package org.chromium.chrome.browser.share.android_share_sheet;
 
 import android.app.Activity;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.SystemClock;
 import android.text.TextUtils;
 
 import androidx.annotation.VisibleForTesting;
@@ -12,17 +16,27 @@
 import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.base.Callback;
 import org.chromium.base.Log;
+import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.supplier.Supplier;
+import org.chromium.chrome.R;
 import org.chromium.chrome.browser.feature_engagement.TrackerFactory;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.share.ChromeCustomShareAction;
 import org.chromium.chrome.browser.share.ChromeShareExtras;
+import org.chromium.chrome.browser.share.ShareContentTypeHelper;
+import org.chromium.chrome.browser.share.ShareContentTypeHelper.ContentType;
 import org.chromium.chrome.browser.share.ShareHelper;
 import org.chromium.chrome.browser.share.share_sheet.ChromeOptionShareCallback;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
+import org.chromium.chrome.browser.ui.favicon.FaviconHelper;
+import org.chromium.chrome.browser.ui.favicon.FaviconUtils;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
+import org.chromium.components.browser_ui.share.ShareImageFileUtils;
 import org.chromium.components.browser_ui.share.ShareParams;
+import org.chromium.url.GURL;
+
+import java.util.Set;
 
 /**
  * Share sheet controller used to display Android share sheet.
@@ -95,10 +109,10 @@
         Profile profile = mProfileSupplier.get();
         boolean isIncognito = mTabModelSelectorSupplier.hasValue()
                 && mTabModelSelectorSupplier.get().isIncognitoSelected();
+        Activity activity = params.getWindow().getActivity().get();
         ChromeCustomShareAction.Provider provider = null;
 
         if (showCustomActions) {
-            Activity activity = params.getWindow().getActivity().get();
             boolean isInMultiWindow = ApiCompatibilityUtils.isInMultiWindowMode(activity);
             var actionProvider =
                     new AndroidCustomActionProvider(params.getWindow().getActivity().get(),
@@ -121,8 +135,49 @@
             String imageUrlToShare = getImageUrlToShare(params, chromeShareExtras);
             params.setUrl(imageUrlToShare);
         }
-        ShareHelper.shareWithSystemShareSheetUi(
-                params, profile, chromeShareExtras.saveLastUsed(), provider);
+        if (!isLinkSharing(params, chromeShareExtras)) {
+            ShareHelper.shareWithSystemShareSheetUi(
+                    params, profile, chromeShareExtras.saveLastUsed(), provider);
+            return;
+        }
+
+        long iconPrepStartTime = SystemClock.elapsedRealtime();
+        ChromeCustomShareAction.Provider finalProvider = provider;
+        preparePreviewFavicon(activity, profile, params.getUrl(), (uri) -> {
+            RecordHistogram.recordTimesHistogram("Sharing.PreparePreviewFaviconDuration",
+                    SystemClock.elapsedRealtime() - iconPrepStartTime);
+            params.setPreviewImageUri(uri);
+            ShareHelper.shareWithSystemShareSheetUi(
+                    params, profile, chromeShareExtras.saveLastUsed(), finalProvider);
+        });
+    }
+
+    private static void preparePreviewFavicon(
+            Context context, Profile profile, String pageUrl, Callback<Uri> onUriReady) {
+        int size = context.getResources().getDimensionPixelSize(R.dimen.share_preview_favicon_size);
+        FaviconHelper faviconHelper = new FaviconHelper();
+        faviconHelper.getLocalFaviconImageForURL(
+                profile, new GURL(pageUrl), size, (Bitmap icon, GURL iconUrl) -> {
+                    onFaviconRetrieved(context, icon, size, onUriReady);
+                });
+    }
+
+    private static void onFaviconRetrieved(
+            Context context, Bitmap bitmap, int size, Callback<Uri> onImageUriAvailable) {
+        // If bitmap is not provided, fallback to the globe placeholder icon.
+        if (bitmap == null) {
+            bitmap = FaviconUtils.createGenericFaviconBitmap(context, size);
+        }
+        String fileName = String.valueOf(System.currentTimeMillis());
+        ShareImageFileUtils.generateTemporaryUriFromBitmap(fileName, bitmap, onImageUriAvailable);
+    }
+
+    private static boolean isLinkSharing(ShareParams params, ChromeShareExtras chromeShareExtras) {
+        @ContentType
+        Set<Integer> contents = ShareContentTypeHelper.getContentTypes(params, chromeShareExtras);
+        return contents.contains(ContentType.LINK_PAGE_VISIBLE)
+                || contents.contains(ContentType.LINK_PAGE_NOT_VISIBLE)
+                || contents.contains(ContentType.LINK_AND_TEXT);
     }
 
     private static String getImageUrlToShare(
diff --git a/chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/android_share_sheet/AndroidShareSheetControllerUnitTest.java b/chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/android_share_sheet/AndroidShareSheetControllerUnitTest.java
index 1fe3bf6e..c46ac4c 100644
--- a/chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/android_share_sheet/AndroidShareSheetControllerUnitTest.java
+++ b/chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/android_share_sheet/AndroidShareSheetControllerUnitTest.java
@@ -5,21 +5,28 @@
 package org.chromium.chrome.browser.share.android_share_sheet;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verifyZeroInteractions;
 
 import android.app.Activity;
 import android.app.PendingIntent;
 import android.app.PendingIntent.CanceledException;
 import android.content.ComponentName;
 import android.content.Intent;
+import android.graphics.Bitmap;
 import android.graphics.drawable.Icon;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Parcelable;
 import android.text.TextUtils;
 
+import androidx.annotation.Nullable;
 import androidx.core.os.BuildCompat;
 import androidx.lifecycle.Lifecycle.State;
 import androidx.test.ext.junit.rules.ActivityScenarioRule;
@@ -40,9 +47,11 @@
 import org.robolectric.annotation.Implements;
 import org.robolectric.shadows.ShadowLooper;
 
+import org.chromium.base.Callback;
 import org.chromium.base.ContextUtils;
 import org.chromium.base.test.BaseRobolectricTestRunner;
 import org.chromium.base.test.util.CallbackHelper;
+import org.chromium.base.test.util.HistogramWatcher;
 import org.chromium.base.test.util.JniMocker;
 import org.chromium.base.test.util.PayloadCallbackHelper;
 import org.chromium.chrome.R;
@@ -55,11 +64,15 @@
 import org.chromium.chrome.browser.share.ShareHelper;
 import org.chromium.chrome.browser.share.android_share_sheet.AndroidShareSheetControllerUnitTest.ShadowBuildCompatForU;
 import org.chromium.chrome.browser.share.android_share_sheet.AndroidShareSheetControllerUnitTest.ShadowChooserActionHelper;
+import org.chromium.chrome.browser.share.android_share_sheet.AndroidShareSheetControllerUnitTest.ShadowShareImageFileUtils;
 import org.chromium.chrome.browser.share.send_tab_to_self.SendTabToSelfAndroidBridgeJni;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
+import org.chromium.chrome.browser.ui.favicon.FaviconHelper;
+import org.chromium.chrome.browser.ui.favicon.FaviconHelperJni;
 import org.chromium.chrome.test.util.browser.Features;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
+import org.chromium.components.browser_ui.share.ShareImageFileUtils;
 import org.chromium.components.browser_ui.share.ShareParams;
 import org.chromium.components.browser_ui.share.ShareParams.TargetChosenCallback;
 import org.chromium.components.feature_engagement.Tracker;
@@ -69,7 +82,9 @@
 import org.chromium.ui.base.IntentRequestTracker;
 import org.chromium.ui.base.TestActivity;
 import org.chromium.ui.base.WindowAndroid;
+import org.chromium.url.GURL;
 import org.chromium.url.JUnitTestGURLs;
+import org.chromium.url.ShadowGURL;
 
 /**
  * Test for {@link AndroidShareSheetController} and {@link AndroidCustomActionProvider}.
@@ -77,12 +92,17 @@
 @RunWith(BaseRobolectricTestRunner.class)
 @Features.DisableFeatures(
         {ChromeFeatureList.WEBNOTES_STYLIZE, ChromeFeatureList.SEND_TAB_TO_SELF_SIGNIN_PROMO})
+@Config(shadows = {ShadowShareImageFileUtils.class, ShadowGURL.class})
 public class AndroidShareSheetControllerUnitTest {
     private static final String INTENT_EXTRA_CHOOSER_CUSTOM_ACTIONS =
             "android.intent.extra.CHOOSER_CUSTOM_ACTIONS";
     private static final String KEY_CHOOSER_ACTION_ICON = "icon";
     private static final String KEY_CHOOSER_ACTION_NAME = "name";
     private static final String KEY_CHOOSER_ACTION_ACTION = "action";
+    private static final Uri TEST_WEB_FAVICON_PREVIEW_URI =
+            Uri.parse("content://test.web.favicon.preview");
+    private static final Uri TEST_FALLBACK_FAVICON_PREVIEW_URI =
+            Uri.parse("content://test.fallback.favicon.preview");
 
     @Rule
     public ActivityScenarioRule<TestActivity> mActivityScenario =
@@ -99,6 +119,8 @@
     @Mock
     UserPrefsJni mMockUserPrefsJni;
     @Mock
+    FaviconHelperJni mMockFaviconHelperJni;
+    @Mock
     BottomSheetController mBottomSheetController;
     @Mock
     TabModelSelector mTabModelSelector;
@@ -113,6 +135,7 @@
     private WindowAndroid mWindow;
     private PayloadCallbackHelper<Tab> mPrintCallback;
     private AndroidShareSheetController mController;
+    private Bitmap mTestWebFavicon;
 
     @Before
     public void setup() {
@@ -128,6 +151,12 @@
         PrefService service = mock(PrefService.class);
         doReturn(service).when(mMockUserPrefsJni).get(mProfile);
         doReturn(true).when(service).getBoolean(Pref.PRINTING_ENABLED);
+        // Set up favicon helper.
+        mJniMocker.mock(FaviconHelperJni.TEST_HOOKS, mMockFaviconHelperJni);
+        doReturn(1L).when(mMockFaviconHelperJni).init();
+        mTestWebFavicon = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
+        ShadowShareImageFileUtils.sExpectedWebBitmap = mTestWebFavicon;
+        setFaviconToFetchForTest(mTestWebFavicon);
 
         mActivityScenario.getScenario().onActivity((activity) -> mActivity = activity);
         mActivityScenario.getScenario().moveToState(State.RESUMED);
@@ -250,6 +279,68 @@
                 sharingIntent.getStringExtra(Intent.EXTRA_TEXT));
     }
 
+    @Test
+    public void shareTextWithPreviewFavicon() {
+        HistogramWatcher watcher =
+                HistogramWatcher.newSingleRecordWatcher("Sharing.PreparePreviewFaviconDuration");
+
+        ShareParams params = new ShareParams.Builder(mWindow, "title", JUnitTestGURLs.EXAMPLE_URL)
+                                     .setFileContentType("text/plain")
+                                     .setBypassFixingDomDistillerUrl(true)
+                                     .build();
+        ChromeShareExtras chromeShareExtras = new ChromeShareExtras.Builder().build();
+        mController.showShareSheet(params, chromeShareExtras, 1L);
+
+        Intent intent = Shadows.shadowOf((Activity) mActivity).peekNextStartedActivity();
+        Assert.assertNotNull("Preview clip data should not be null.", intent.getClipData());
+        Assert.assertEquals("Image preview Uri is null.", TEST_WEB_FAVICON_PREVIEW_URI,
+                intent.getClipData().getItemAt(0).getUri());
+        watcher.assertExpected();
+    }
+
+    @Test
+    public void shareTextWithPreviewWithFallbackFavicon() {
+        // Assume favicon is not available for this test.
+        setFaviconToFetchForTest(null);
+
+        ShareParams params = new ShareParams.Builder(mWindow, "title", JUnitTestGURLs.EXAMPLE_URL)
+                                     .setFileContentType("text/plain")
+                                     .setBypassFixingDomDistillerUrl(true)
+                                     .build();
+        ChromeShareExtras chromeShareExtras = new ChromeShareExtras.Builder().build();
+        mController.showShareSheet(params, chromeShareExtras, 1L);
+
+        Intent intent = Shadows.shadowOf((Activity) mActivity).peekNextStartedActivity();
+        Assert.assertNotNull("Preview clip data should not be null.", intent.getClipData());
+        Assert.assertEquals("Image preview Uri is not as expected.",
+                TEST_FALLBACK_FAVICON_PREVIEW_URI, intent.getClipData().getItemAt(0).getUri());
+    }
+
+    @Test
+    public void sharePlainTextDoesNotProvidePreview() {
+        ShareParams params = new ShareParams.Builder(mWindow, "", "")
+                                     .setFileContentType("text/plain")
+                                     .setText("text")
+                                     .setBypassFixingDomDistillerUrl(true)
+                                     .build();
+        ChromeShareExtras chromeShareExtras = new ChromeShareExtras.Builder().build();
+        mController.showShareSheet(params, chromeShareExtras, 1L);
+
+        Intent intent = Shadows.shadowOf((Activity) mActivity).peekNextStartedActivity();
+        Assert.assertNull("Preview clip data should be null.", intent.getClipData());
+        verifyZeroInteractions(mMockFaviconHelperJni);
+    }
+
+    private void setFaviconToFetchForTest(Bitmap favicon) {
+        doAnswer(invocation -> {
+            FaviconHelper.FaviconImageCallback callback = invocation.getArgument(4);
+            callback.onFaviconAvailable(favicon, GURL.emptyGURL());
+            return null;
+        })
+                .when(mMockFaviconHelperJni)
+                .getLocalFaviconImageForURL(anyLong(), eq(mProfile), any(), anyInt(), any());
+    }
+
     /**
      * Test implementation to build a ChooserAction.
      */
@@ -279,4 +370,20 @@
             return true;
         }
     }
+
+    // Shadow class to bypass actually saving the image as URL for this test.
+    @Implements(ShareImageFileUtils.class)
+    static class ShadowShareImageFileUtils {
+        static @Nullable Bitmap sExpectedWebBitmap;
+
+        @Implementation
+        public static void generateTemporaryUriFromBitmap(
+                String fileName, Bitmap bitmap, Callback<Uri> callback) {
+            if (bitmap != null && bitmap.equals(sExpectedWebBitmap)) {
+                callback.onResult(TEST_WEB_FAVICON_PREVIEW_URI);
+            } else {
+                callback.onResult(TEST_FALLBACK_FAVICON_PREVIEW_URI);
+            }
+        }
+    }
 }
diff --git a/chrome/browser/signin/chromeos_mirror_account_consistency_browsertest.cc b/chrome/browser/signin/chromeos_mirror_account_consistency_browsertest.cc
index b1637613..1431e09d 100644
--- a/chrome/browser/signin/chromeos_mirror_account_consistency_browsertest.cc
+++ b/chrome/browser/signin/chromeos_mirror_account_consistency_browsertest.cc
@@ -12,11 +12,11 @@
 #include "chrome/browser/signin/identity_manager_factory.h"
 #include "chrome/browser/supervised_user/supervised_user_settings_service_factory.h"
 #include "chrome/browser/ui/browser.h"
-#include "chrome/common/pref_names.h"
 #include "chrome/test/base/ui_test_utils.h"
 #include "components/account_id/account_id.h"
 #include "components/google/core/common/google_switches.h"
 #include "components/network_session_configurator/common/network_switches.h"
+#include "components/policy/core/common/policy_pref_names.h"
 #include "components/prefs/pref_service.h"
 #include "components/signin/core/browser/signin_header_helper.h"
 #include "components/signin/public/base/signin_pref_names.h"
@@ -139,7 +139,7 @@
   // Incognito is always disabled for child accounts.
   PrefService* prefs = profile->GetPrefs();
   prefs->SetInteger(
-      prefs::kIncognitoModeAvailability,
+      policy::policy_prefs::kIncognitoModeAvailability,
       static_cast<int>(IncognitoModePrefs::Availability::kDisabled));
   ASSERT_EQ(1, signin::PROFILE_MODE_INCOGNITO_DISABLED);
 
diff --git a/chrome/browser/signin/header_modification_delegate_impl.cc b/chrome/browser/signin/header_modification_delegate_impl.cc
index 19d59ea..e68d4a4e 100644
--- a/chrome/browser/signin/header_modification_delegate_impl.cc
+++ b/chrome/browser/signin/header_modification_delegate_impl.cc
@@ -13,7 +13,7 @@
 #include "chrome/browser/signin/chrome_signin_helper.h"
 #include "chrome/browser/signin/identity_manager_factory.h"
 #include "chrome/browser/sync/sync_service_factory.h"
-#include "chrome/common/pref_names.h"
+#include "components/policy/core/common/policy_pref_names.h"
 #include "components/prefs/pref_service.h"
 #include "components/signin/public/base/signin_pref_names.h"
 #include "components/signin/public/identity_manager/account_info.h"
@@ -95,7 +95,7 @@
       identity_manager->FindExtendedAccountInfo(account).is_child_account;
 
   int incognito_mode_availability =
-      prefs->GetInteger(prefs::kIncognitoModeAvailability);
+      prefs->GetInteger(policy::policy_prefs::kIncognitoModeAvailability);
 #if BUILDFLAG(IS_ANDROID)
   incognito_mode_availability =
       incognito_enabled_
diff --git a/chrome/browser/supervised_user/supervised_user_pref_store.cc b/chrome/browser/supervised_user/supervised_user_pref_store.cc
index 8e884073..36ba1db7 100644
--- a/chrome/browser/supervised_user/supervised_user_pref_store.cc
+++ b/chrome/browser/supervised_user/supervised_user_pref_store.cc
@@ -151,7 +151,7 @@
       // First-party sites use signed-in cookies to ensure that parental
       // restrictions are applied for Unicorn accounts.
       prefs_->SetInteger(
-          prefs::kIncognitoModeAvailability,
+          policy::policy_prefs::kIncognitoModeAvailability,
           static_cast<int>(IncognitoModePrefs::Availability::kDisabled));
     }
 
diff --git a/chrome/browser/supervised_user/supervised_user_pref_store_unittest.cc b/chrome/browser/supervised_user/supervised_user_pref_store_unittest.cc
index 71d46ca..352bce39 100644
--- a/chrome/browser/supervised_user/supervised_user_pref_store_unittest.cc
+++ b/chrome/browser/supervised_user/supervised_user_pref_store_unittest.cc
@@ -146,7 +146,7 @@
   // kIncognitoModeAvailability must be disabled for all supervised users.
   EXPECT_THAT(
       fixture.changed_prefs()->FindIntByDottedPath(
-          prefs::kIncognitoModeAvailability),
+          policy::policy_prefs::kIncognitoModeAvailability),
       Optional(static_cast<int>(IncognitoModePrefs::Availability::kDisabled)));
 
   // kSupervisedModeManualHosts does not have a hardcoded value.
diff --git a/chrome/browser/ui/android/favicon/BUILD.gn b/chrome/browser/ui/android/favicon/BUILD.gn
index 7fe0f9e..cee8503 100644
--- a/chrome/browser/ui/android/favicon/BUILD.gn
+++ b/chrome/browser/ui/android/favicon/BUILD.gn
@@ -23,6 +23,7 @@
     "//components/url_formatter/android:url_formatter_java",
     "//content/public/android:content_java",
     "//third_party/androidx:androidx_annotation_annotation_java",
+    "//third_party/androidx:androidx_appcompat_appcompat_resources_java",
     "//third_party/androidx:androidx_core_core_java",
     "//ui/android:ui_java",
     "//url:gurl_java",
diff --git a/chrome/browser/ui/android/favicon/java/src/org/chromium/chrome/browser/ui/favicon/FaviconUtils.java b/chrome/browser/ui/android/favicon/java/src/org/chromium/chrome/browser/ui/favicon/FaviconUtils.java
index db131d59..32c49443 100644
--- a/chrome/browser/ui/android/favicon/java/src/org/chromium/chrome/browser/ui/favicon/FaviconUtils.java
+++ b/chrome/browser/ui/android/favicon/java/src/org/chromium/chrome/browser/ui/favicon/FaviconUtils.java
@@ -7,11 +7,14 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.Canvas;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 
 import androidx.annotation.ColorInt;
 import androidx.annotation.Nullable;
+import androidx.appcompat.content.res.AppCompatResources;
 import androidx.core.graphics.drawable.RoundedBitmapDrawable;
 
 import org.chromium.components.browser_ui.widget.RoundedIconGenerator;
@@ -68,6 +71,21 @@
     }
 
     /**
+     * Create a bitmap with corresponding size of a generic favicon.
+     * @param context {@link Context} to read the generic favicon.
+     * @param size Desired size of the bitmap.
+     * @return A generic globe favicon.
+     */
+    public static Bitmap createGenericFaviconBitmap(Context context, int size) {
+        Bitmap bitmap = Bitmap.createBitmap(size, size, Config.ARGB_8888);
+        Drawable drawable = AppCompatResources.getDrawable(context, R.drawable.ic_globe_24dp);
+        drawable.setBounds(0, 0, size, size);
+        Canvas canvas = new Canvas(bitmap);
+        drawable.draw(canvas);
+        return bitmap;
+    }
+
+    /**
      * Creates a {@link Drawable} with the provided icon with
      * nearest-neighbor scaling through {@link Bitmap#createScaledBitmap(Bitmap, int, int,
      * boolean)}, or a fallback monogram.
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chrome/browser/ui/android/strings/android_chrome_strings.grd
index dde50890..fa55e75 100644
--- a/chrome/browser/ui/android/strings/android_chrome_strings.grd
+++ b/chrome/browser/ui/android/strings/android_chrome_strings.grd
@@ -3526,6 +3526,9 @@
       <message name="IDS_ADAPTIVE_TOOLBAR_BUTTON_PREFERENCE_VOICE_SEARCH" desc="Title name for the voice search option in the preference.">
         Voice search
       </message>
+      <message name="IDS_ADAPTIVE_TOOLBAR_BUTTON_PREFERENCE_ADD_TO_BOOKMARKS" desc="Title name for the add to bookmarks option in the preference.">
+        Add to bookmarks
+      </message>
       <message name="IDS_ADAPTIVE_TOOLBAR_BUTTON_PREFERENCE_TRANSLATE" desc="Title name for the translate option in the preference.">
         Translate
       </message>
@@ -3547,6 +3550,9 @@
       <message name="IDS_ADAPTIVE_TOOLBAR_BUTTON_VOICE_SEARCH_IPH" desc="An in-product-help message for the voice search button.">
         Quickly search with your voice. To edit this shortcut, touch and hold.
       </message>
+      <message name="IDS_ADAPTIVE_TOOLBAR_BUTTON_ADD_TO_BOOKMARKS_IPH" desc="An in-product-help message for the add to bookmarks button.">
+        Quickly bookmark this page. To edit this shortcut, touch and hold.
+      </message>
       <message name="IDS_ADAPTIVE_TOOLBAR_BUTTON_TRANSLATE_IPH" desc="An in-product-help message for the voice search button.">
         Quickly translate this page. To edit this shortcut, touch and hold.
       </message>
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_ADAPTIVE_TOOLBAR_BUTTON_ADD_TO_BOOKMARKS_IPH.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_ADAPTIVE_TOOLBAR_BUTTON_ADD_TO_BOOKMARKS_IPH.png.sha1
new file mode 100644
index 0000000..c251581
--- /dev/null
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_ADAPTIVE_TOOLBAR_BUTTON_ADD_TO_BOOKMARKS_IPH.png.sha1
@@ -0,0 +1 @@
+4b7f8076368ce26174085ba4e4e2df3ddddcd2e7
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_ADAPTIVE_TOOLBAR_BUTTON_PREFERENCE_ADD_TO_BOOKMARKS.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_ADAPTIVE_TOOLBAR_BUTTON_PREFERENCE_ADD_TO_BOOKMARKS.png.sha1
new file mode 100644
index 0000000..84f588b
--- /dev/null
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_ADAPTIVE_TOOLBAR_BUTTON_PREFERENCE_ADD_TO_BOOKMARKS.png.sha1
@@ -0,0 +1 @@
+ffc7b342d86702e60df30c4bf9f6d99004b7f1ba
\ No newline at end of file
diff --git a/chrome/browser/ui/android/toolbar/java/res/layout/radio_button_group_adaptive_toolbar_preference.xml b/chrome/browser/ui/android/toolbar/java/res/layout/radio_button_group_adaptive_toolbar_preference.xml
index 8814590..5da7a92 100644
--- a/chrome/browser/ui/android/toolbar/java/res/layout/radio_button_group_adaptive_toolbar_preference.xml
+++ b/chrome/browser/ui/android/toolbar/java/res/layout/radio_button_group_adaptive_toolbar_preference.xml
@@ -63,6 +63,15 @@
           app:iconSrc="@drawable/ic_translate"
           app:primaryText="@string/adaptive_toolbar_button_preference_translate" />
 
+      <org.chromium.components.browser_ui.widget.RadioButtonWithDescription
+          android:id="@+id/adaptive_option_add_to_bookmarks"
+          android:layout_width="match_parent"
+          android:layout_height="wrap_content"
+          android:minHeight="@dimen/min_touch_target_size"
+          android:paddingTop="10dp"
+          app:iconSrc="@drawable/btn_star"
+          app:primaryText="@string/adaptive_toolbar_button_preference_add_to_bookmarks" />
+
     </org.chromium.components.browser_ui.widget.RadioButtonWithDescriptionLayout>
 
 </LinearLayout>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/AdaptiveToolbarFeatures.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/AdaptiveToolbarFeatures.java
index dad1722..723509c0 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/AdaptiveToolbarFeatures.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/AdaptiveToolbarFeatures.java
@@ -149,6 +149,11 @@
                 ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR_TRANSLATE);
     }
 
+    public static boolean isAdaptiveToolbarAddToBookmarksEnabled() {
+        return ChromeFeatureList.isEnabled(
+                ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR_ADD_TO_BOOKMARKS);
+    }
+
     private static boolean isAnyContextualPageActionButtonEnabled() {
         return isPriceTrackingPageActionEnabled() || isReaderModePageActionEnabled();
     }
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/AdaptiveToolbarStatePredictor.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/AdaptiveToolbarStatePredictor.java
index b467ea1..ac2c08e 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/AdaptiveToolbarStatePredictor.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/AdaptiveToolbarStatePredictor.java
@@ -152,6 +152,7 @@
             case AdaptiveToolbarButtonVariant.SHARE:
             case AdaptiveToolbarButtonVariant.VOICE:
             case AdaptiveToolbarButtonVariant.TRANSLATE:
+            case AdaptiveToolbarButtonVariant.ADD_TO_BOOKMARKS:
                 return true;
             case AdaptiveToolbarButtonVariant.UNKNOWN:
             case AdaptiveToolbarButtonVariant.NONE:
@@ -221,6 +222,8 @@
                 return VoiceRecognitionUtil.isVoiceSearchEnabled(mAndroidPermissionDelegate);
             case AdaptiveToolbarButtonVariant.TRANSLATE:
                 return AdaptiveToolbarFeatures.isAdaptiveToolbarTranslateEnabled();
+            case AdaptiveToolbarButtonVariant.ADD_TO_BOOKMARKS:
+                return AdaptiveToolbarFeatures.isAdaptiveToolbarAddToBookmarksEnabled();
             default:
                 return true;
         }
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/AdaptiveToolbarStats.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/AdaptiveToolbarStats.java
index 8d09a9a..6cf3aa5 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/AdaptiveToolbarStats.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/AdaptiveToolbarStats.java
@@ -22,7 +22,9 @@
             AdaptiveToolbarRadioButtonState.AUTO_WITH_VOICE,
             AdaptiveToolbarRadioButtonState.NEW_TAB, AdaptiveToolbarRadioButtonState.SHARE,
             AdaptiveToolbarRadioButtonState.VOICE, AdaptiveToolbarRadioButtonState.TRANSLATE,
-            AdaptiveToolbarRadioButtonState.AUTO_WITH_TRANSLATE})
+            AdaptiveToolbarRadioButtonState.AUTO_WITH_TRANSLATE,
+            AdaptiveToolbarRadioButtonState.ADD_TO_BOOKMARKS,
+            AdaptiveToolbarRadioButtonState.AUTO_WITH_ADD_TO_BOOKMARKS})
     @Retention(RetentionPolicy.SOURCE)
     private @interface AdaptiveToolbarRadioButtonState {
         int UNKNOWN = 0;
@@ -34,7 +36,9 @@
         int VOICE = 6;
         int TRANSLATE = 7;
         int AUTO_WITH_TRANSLATE = 8;
-        int NUM_ENTRIES = 9;
+        int ADD_TO_BOOKMARKS = 9;
+        int AUTO_WITH_ADD_TO_BOOKMARKS = 10;
+        int NUM_ENTRIES = 11;
     }
 
     /**
@@ -85,6 +89,8 @@
                 return AdaptiveToolbarRadioButtonState.SHARE;
             case AdaptiveToolbarButtonVariant.VOICE:
                 return AdaptiveToolbarRadioButtonState.VOICE;
+            case AdaptiveToolbarButtonVariant.ADD_TO_BOOKMARKS:
+                return AdaptiveToolbarRadioButtonState.ADD_TO_BOOKMARKS;
             case AdaptiveToolbarButtonVariant.TRANSLATE:
                 return AdaptiveToolbarRadioButtonState.TRANSLATE;
             case AdaptiveToolbarButtonVariant.AUTO:
@@ -95,6 +101,8 @@
                         return AdaptiveToolbarRadioButtonState.AUTO_WITH_SHARE;
                     case AdaptiveToolbarButtonVariant.VOICE:
                         return AdaptiveToolbarRadioButtonState.AUTO_WITH_VOICE;
+                    case AdaptiveToolbarButtonVariant.ADD_TO_BOOKMARKS:
+                        return AdaptiveToolbarRadioButtonState.AUTO_WITH_ADD_TO_BOOKMARKS;
                     case AdaptiveToolbarButtonVariant.TRANSLATE:
                         return AdaptiveToolbarRadioButtonState.AUTO_WITH_TRANSLATE;
                 }
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/settings/AdaptiveToolbarPreferenceFragment.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/settings/AdaptiveToolbarPreferenceFragment.java
index f59d531a..6d500ce 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/settings/AdaptiveToolbarPreferenceFragment.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/settings/AdaptiveToolbarPreferenceFragment.java
@@ -57,6 +57,8 @@
         mRadioButtonGroup.setCanUseVoiceSearch(getCanUseVoiceSearch());
         mRadioButtonGroup.setCanUseTranslate(
                 AdaptiveToolbarFeatures.isAdaptiveToolbarTranslateEnabled());
+        mRadioButtonGroup.setCanUseAddToBookmarks(
+                AdaptiveToolbarFeatures.isAdaptiveToolbarAddToBookmarksEnabled());
         mRadioButtonGroup.setStatePredictor(new AdaptiveToolbarStatePredictor(
                 new ActivityAndroidPermissionDelegate(new WeakReference(getActivity()))));
         mRadioButtonGroup.setOnPreferenceChangeListener((preference, newValue) -> {
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/settings/AdaptiveToolbarPreferenceFragmentTest.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/settings/AdaptiveToolbarPreferenceFragmentTest.java
index 0540e96..886709b 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/settings/AdaptiveToolbarPreferenceFragmentTest.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/settings/AdaptiveToolbarPreferenceFragmentTest.java
@@ -46,7 +46,8 @@
 @EnableFeatures({ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR_CUSTOMIZATION_V2})
 @DisableFeatures({ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR,
         SettingsFeatureList.HIGHLIGHT_MANAGED_PREF_DISCLAIMER_ANDROID,
-        ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR_TRANSLATE})
+        ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR_TRANSLATE,
+        ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR_ADD_TO_BOOKMARKS})
 public class AdaptiveToolbarPreferenceFragmentTest {
     @Rule
     public TestRule mProcessor = new Features.JUnitProcessor();
@@ -201,6 +202,58 @@
         });
     }
 
+    @Test
+    @SmallTest
+    @EnableFeatures(ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR_ADD_TO_BOOKMARKS)
+    public void testAddToBookmarksOption_Enabled() {
+        FragmentScenario<AdaptiveToolbarPreferenceFragment> scenario =
+                FragmentScenario.launchInContainer(AdaptiveToolbarPreferenceFragment.class,
+                        Bundle.EMPTY, org.chromium.chrome.R.style.Theme_Chromium_Settings);
+        scenario.onFragment(fragment -> {
+            mRadioPreference = (RadioButtonGroupAdaptiveToolbarPreference) fragment.findPreference(
+                    AdaptiveToolbarPreferenceFragment.PREF_ADAPTIVE_RADIO_GROUP);
+
+            // Select Add to bookmarks.
+            Assert.assertEquals(R.id.adaptive_option_add_to_bookmarks,
+                    getButton(AdaptiveToolbarButtonVariant.ADD_TO_BOOKMARKS).getId());
+            selectButton(AdaptiveToolbarButtonVariant.ADD_TO_BOOKMARKS);
+            assertButtonCheckedCorrectly(
+                    "Add to bookmarks", AdaptiveToolbarButtonVariant.ADD_TO_BOOKMARKS);
+            Assert.assertEquals(
+                    AdaptiveToolbarButtonVariant.ADD_TO_BOOKMARKS, mRadioPreference.getSelection());
+            Assert.assertEquals(AdaptiveToolbarButtonVariant.ADD_TO_BOOKMARKS,
+                    SharedPreferencesManager.getInstance().readInt(
+                            ADAPTIVE_TOOLBAR_CUSTOMIZATION_SETTINGS));
+        });
+    }
+
+    @Test
+    @SmallTest
+    @DisableFeatures(ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR_ADD_TO_BOOKMARKS)
+    public void testAddToBookmarksOption_Disabled() {
+        // Set initial preference to add to bookmarks.
+        SharedPreferencesManager.getInstance().writeInt(ADAPTIVE_TOOLBAR_CUSTOMIZATION_SETTINGS,
+                AdaptiveToolbarButtonVariant.ADD_TO_BOOKMARKS);
+        FragmentScenario<AdaptiveToolbarPreferenceFragment> scenario =
+                FragmentScenario.launchInContainer(AdaptiveToolbarPreferenceFragment.class,
+                        Bundle.EMPTY, org.chromium.chrome.R.style.Theme_Chromium_Settings);
+        scenario.onFragment(fragment -> {
+            mRadioPreference = (RadioButtonGroupAdaptiveToolbarPreference) fragment.findPreference(
+                    AdaptiveToolbarPreferenceFragment.PREF_ADAPTIVE_RADIO_GROUP);
+
+            // Add to bookmarks option should be hidden, and we should have reverted back to "Auto".
+            Assert.assertEquals(R.id.adaptive_option_add_to_bookmarks,
+                    getButton(AdaptiveToolbarButtonVariant.ADD_TO_BOOKMARKS).getId());
+            Assert.assertEquals(View.GONE,
+                    getButton(AdaptiveToolbarButtonVariant.ADD_TO_BOOKMARKS).getVisibility());
+            assertButtonCheckedCorrectly("Based on your usage", AdaptiveToolbarButtonVariant.AUTO);
+            Assert.assertEquals(AdaptiveToolbarButtonVariant.AUTO, mRadioPreference.getSelection());
+            Assert.assertEquals(AdaptiveToolbarButtonVariant.AUTO,
+                    SharedPreferencesManager.getInstance().readInt(
+                            ADAPTIVE_TOOLBAR_CUSTOMIZATION_SETTINGS));
+        });
+    }
+
     private RadioButtonWithDescription getButton(@AdaptiveToolbarButtonVariant int type) {
         return (RadioButtonWithDescription) mRadioPreference.getButton(type);
     }
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/settings/RadioButtonGroupAdaptiveToolbarPreference.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/settings/RadioButtonGroupAdaptiveToolbarPreference.java
index 9737a083..0defaae 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/settings/RadioButtonGroupAdaptiveToolbarPreference.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/settings/RadioButtonGroupAdaptiveToolbarPreference.java
@@ -35,10 +35,12 @@
     private @NonNull RadioButtonWithDescription mShareButton;
     private @NonNull RadioButtonWithDescription mVoiceSearchButton;
     private @NonNull RadioButtonWithDescription mTranslateButton;
+    private @NonNull RadioButtonWithDescription mAddToBookmarksButton;
     private @AdaptiveToolbarButtonVariant int mSelected;
     private @Nullable AdaptiveToolbarStatePredictor mStatePredictor;
     private boolean mCanUseVoiceSearch = true;
     private boolean mCanUseTranslate;
+    private boolean mCanUseAddToBookmarks;
 
     public RadioButtonGroupAdaptiveToolbarPreference(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -61,6 +63,8 @@
                 (RadioButtonWithDescription) holder.findViewById(R.id.adaptive_option_voice_search);
         mTranslateButton =
                 (RadioButtonWithDescription) holder.findViewById(R.id.adaptive_option_translate);
+        mAddToBookmarksButton = (RadioButtonWithDescription) holder.findViewById(
+                R.id.adaptive_option_add_to_bookmarks);
 
         initializeRadioButtonSelection();
         RecordUserAction.record("Mobile.AdaptiveToolbarButton.SettingsPage.Opened");
@@ -90,6 +94,7 @@
                     getButtonString(uiState.autoButtonCaption)));
             updateVoiceButtonVisibility();
             updateTranslateButtonVisibility();
+            updateAddToBookmarksButtonVisibility();
         });
         AdaptiveToolbarStats.recordRadioButtonStateAsync(mStatePredictor, /*onStartup=*/true);
     }
@@ -108,6 +113,8 @@
             mSelected = AdaptiveToolbarButtonVariant.VOICE;
         } else if (mTranslateButton.isChecked()) {
             mSelected = AdaptiveToolbarButtonVariant.TRANSLATE;
+        } else if (mAddToBookmarksButton.isChecked()) {
+            mSelected = AdaptiveToolbarButtonVariant.ADD_TO_BOOKMARKS;
         } else {
             assert false : "No matching setting found.";
         }
@@ -141,6 +148,8 @@
                 return mVoiceSearchButton;
             case AdaptiveToolbarButtonVariant.TRANSLATE:
                 return mTranslateButton;
+            case AdaptiveToolbarButtonVariant.ADD_TO_BOOKMARKS:
+                return mAddToBookmarksButton;
         }
         return null;
     }
@@ -161,6 +170,9 @@
             case AdaptiveToolbarButtonVariant.TRANSLATE:
                 stringRes = R.string.adaptive_toolbar_button_preference_translate;
                 break;
+            case AdaptiveToolbarButtonVariant.ADD_TO_BOOKMARKS:
+                stringRes = R.string.adaptive_toolbar_button_preference_add_to_bookmarks;
+                break;
             default:
                 assert false : "Unknown variant " + variant;
         }
@@ -177,6 +189,11 @@
         updateTranslateButtonVisibility();
     }
 
+    void setCanUseAddToBookmarks(boolean canUseAddToBookmarks) {
+        mCanUseAddToBookmarks = canUseAddToBookmarks;
+        updateAddToBookmarksButtonVisibility();
+    }
+
     private void updateVoiceButtonVisibility() {
         updateButtonVisibility(mVoiceSearchButton, mCanUseVoiceSearch);
     }
@@ -185,6 +202,10 @@
         updateButtonVisibility(mTranslateButton, mCanUseTranslate);
     }
 
+    private void updateAddToBookmarksButtonVisibility() {
+        updateButtonVisibility(mAddToBookmarksButton, mCanUseAddToBookmarks);
+    }
+
     /**
      * Updates a button's visibility based on a boolean value. If the button is currently checked
      * and it needs to be hidden then we check the default "Auto" button.
diff --git a/chrome/browser/ui/ash/glanceables/glanceables_tasks_client_impl.cc b/chrome/browser/ui/ash/glanceables/glanceables_tasks_client_impl.cc
index 17199ee1..b983410 100644
--- a/chrome/browser/ui/ash/glanceables/glanceables_tasks_client_impl.cc
+++ b/chrome/browser/ui/ash/glanceables/glanceables_tasks_client_impl.cc
@@ -9,8 +9,14 @@
 #include <string>
 #include <vector>
 
+#include "ash/glanceables/tasks/glanceables_tasks_client.h"
+#include "ash/glanceables/tasks/glanceables_tasks_types.h"
 #include "base/check.h"
+#include "base/containers/flat_map.h"
+#include "base/functional/bind.h"
 #include "base/functional/callback_forward.h"
+#include "base/types/expected.h"
+#include "google_apis/common/api_error_codes.h"
 #include "google_apis/common/request_sender.h"
 #include "google_apis/gaia/gaia_constants.h"
 #include "google_apis/tasks/tasks_api_requests.h"
@@ -20,8 +26,11 @@
 namespace ash {
 namespace {
 
+using ::google_apis::ApiErrorCode;
 using ::google_apis::tasks::ListTaskListsRequest;
 using ::google_apis::tasks::ListTasksRequest;
+using ::google_apis::tasks::Task;
+using ::google_apis::tasks::TaskList;
 using ::google_apis::tasks::TaskLists;
 using ::google_apis::tasks::Tasks;
 
@@ -54,6 +63,64 @@
         }
     )");
 
+std::vector<GlanceablesTaskList> ConvertTaskLists(
+    const std::vector<std::unique_ptr<TaskList>>& raw_items) {
+  std::vector<GlanceablesTaskList> converted_task_lists;
+  converted_task_lists.reserve(raw_items.size());
+  for (const auto& item : raw_items) {
+    converted_task_lists.emplace_back(item->id(), item->title(),
+                                      item->updated());
+  }
+  return converted_task_lists;
+}
+
+GlanceablesTask ConvertTask(
+    const Task* const task,
+    base::flat_map<std::string, std::vector<const Task*>>& grouped_subtasks) {
+  const auto iter = grouped_subtasks.find(task->id());
+  std::vector<GlanceablesTask> converted_subtasks;
+  if (iter != grouped_subtasks.end()) {
+    converted_subtasks.reserve(iter->second.size());
+    for (const auto* const subtask : iter->second) {
+      converted_subtasks.push_back(ConvertTask(subtask, grouped_subtasks));
+    }
+    grouped_subtasks.erase(iter);
+  }
+
+  return GlanceablesTask(task->id(), task->title(),
+                         task->status() == Task::Status::kCompleted,
+                         converted_subtasks);
+}
+
+std::vector<GlanceablesTask> ConvertTasks(
+    const std::vector<std::unique_ptr<Task>>& raw_items) {
+  // Find root level tasks and group all other subtasks by their parent id.
+  std::vector<const Task*> root_tasks;
+  base::flat_map<std::string, std::vector<const Task*>> grouped_subtasks;
+  for (const auto& item : raw_items) {
+    if (item->parent_id().empty()) {
+      root_tasks.push_back(item.get());
+      continue;
+    }
+
+    grouped_subtasks[item->parent_id()].push_back(item.get());
+  }
+
+  std::vector<GlanceablesTask> converted_tasks;
+  converted_tasks.reserve(root_tasks.size());
+  for (const auto* const root_task : root_tasks) {
+    converted_tasks.push_back(ConvertTask(root_task, grouped_subtasks));
+  }
+
+  if (!grouped_subtasks.empty()) {
+    // At this moment `grouped_subtasks` should be empty. If not - something is
+    // wrong with the returned data (some tasks point to invalid `parent_id()`).
+    return std::vector<GlanceablesTask>();
+  }
+
+  return converted_tasks;
+}
+
 }  // namespace
 
 GlanceablesTasksClientImpl::GlanceablesTasksClientImpl(
@@ -64,21 +131,42 @@
 GlanceablesTasksClientImpl::~GlanceablesTasksClientImpl() = default;
 
 base::OnceClosure GlanceablesTasksClientImpl::GetTaskLists(
-    ListTaskListsRequest::Callback callback) {
+    GlanceablesTasksClient::GetTaskListsCallback callback) {
   EnsureRequestSenderExists();
   return request_sender_->StartRequestWithAuthRetry(
-      std::make_unique<ListTaskListsRequest>(request_sender_.get(),
-                                             std::move(callback)));
+      std::make_unique<ListTaskListsRequest>(
+          request_sender_.get(),
+          base::BindOnce(&GlanceablesTasksClientImpl::OnTaskListsFetched,
+                         weak_factory_.GetWeakPtr(), std::move(callback))));
 }
 
 base::OnceClosure GlanceablesTasksClientImpl::GetTasks(
-    ListTasksRequest::Callback callback,
+    GlanceablesTasksClient::GetTasksCallback callback,
     const std::string& task_list_id) {
   DCHECK(!task_list_id.empty());
   EnsureRequestSenderExists();
   return request_sender_->StartRequestWithAuthRetry(
-      std::make_unique<ListTasksRequest>(request_sender_.get(),
-                                         std::move(callback), task_list_id));
+      std::make_unique<ListTasksRequest>(
+          request_sender_.get(),
+          base::BindOnce(&GlanceablesTasksClientImpl::OnTasksFetched,
+                         weak_factory_.GetWeakPtr(), std::move(callback)),
+          task_list_id));
+}
+
+void GlanceablesTasksClientImpl::OnTaskListsFetched(
+    GlanceablesTasksClient::GetTaskListsCallback callback,
+    base::expected<std::unique_ptr<TaskLists>, ApiErrorCode> result) const {
+  std::move(callback).Run(result.has_value()
+                              ? ConvertTaskLists(result.value()->items())
+                              : std::vector<GlanceablesTaskList>());
+}
+
+void GlanceablesTasksClientImpl::OnTasksFetched(
+    GlanceablesTasksClient::GetTasksCallback callback,
+    base::expected<std::unique_ptr<Tasks>, ApiErrorCode> result) const {
+  std::move(callback).Run(result.has_value()
+                              ? ConvertTasks(result.value()->items())
+                              : std::vector<GlanceablesTask>());
 }
 
 void GlanceablesTasksClientImpl::EnsureRequestSenderExists() {
diff --git a/chrome/browser/ui/ash/glanceables/glanceables_tasks_client_impl.h b/chrome/browser/ui/ash/glanceables/glanceables_tasks_client_impl.h
index 04d6c3d1..192d014 100644
--- a/chrome/browser/ui/ash/glanceables/glanceables_tasks_client_impl.h
+++ b/chrome/browser/ui/ash/glanceables/glanceables_tasks_client_impl.h
@@ -11,10 +11,16 @@
 
 #include "ash/glanceables/tasks/glanceables_tasks_client.h"
 #include "base/functional/callback_forward.h"
+#include "base/memory/weak_ptr.h"
+#include "base/types/expected.h"
 #include "google_apis/tasks/tasks_api_requests.h"
 
 namespace google_apis {
 class RequestSender;
+namespace tasks {
+class TaskLists;
+class Tasks;
+}  // namespace tasks
 }  // namespace google_apis
 
 namespace net {
@@ -42,12 +48,23 @@
 
   // GlanceablesTasksClient:
   base::OnceClosure GetTaskLists(
-      google_apis::tasks::ListTaskListsRequest::Callback callback) override;
-  base::OnceClosure GetTasks(
-      google_apis::tasks::ListTasksRequest::Callback callback,
-      const std::string& task_list_id) override;
+      GlanceablesTasksClient::GetTaskListsCallback callback) override;
+  base::OnceClosure GetTasks(GlanceablesTasksClient::GetTasksCallback callback,
+                             const std::string& task_list_id) override;
 
  private:
+  // Callback for `GetTaskLists()`. Transforms fetched items to ash-friendly
+  // types.
+  void OnTaskListsFetched(
+      GlanceablesTasksClient::GetTaskListsCallback callback,
+      base::expected<std::unique_ptr<google_apis::tasks::TaskLists>,
+                     google_apis::ApiErrorCode> result) const;
+
+  // Callback for `GetTasks()`. Transforms fetched items to ash-friendly types.
+  void OnTasksFetched(GlanceablesTasksClient::GetTasksCallback callback,
+                      base::expected<std::unique_ptr<google_apis::tasks::Tasks>,
+                                     google_apis::ApiErrorCode> result) const;
+
   // Creates `request_sender_` by calling `create_request_sender_callback_` on
   // demand.
   void EnsureRequestSenderExists();
@@ -58,6 +75,8 @@
 
   // Helper class that sends requests, handles retries and authentication.
   std::unique_ptr<google_apis::RequestSender> request_sender_;
+
+  base::WeakPtrFactory<GlanceablesTasksClientImpl> weak_factory_{this};
 };
 
 }  // namespace ash
diff --git a/chrome/browser/ui/ash/glanceables/glanceables_tasks_client_impl_unittest.cc b/chrome/browser/ui/ash/glanceables/glanceables_tasks_client_impl_unittest.cc
index 2ae47a5..806cef6 100644
--- a/chrome/browser/ui/ash/glanceables/glanceables_tasks_client_impl_unittest.cc
+++ b/chrome/browser/ui/ash/glanceables/glanceables_tasks_client_impl_unittest.cc
@@ -7,25 +7,26 @@
 #include <algorithm>
 #include <memory>
 #include <string>
+#include <vector>
 
 #include "ash/constants/ash_features.h"
+#include "ash/glanceables/tasks/glanceables_tasks_types.h"
 #include "base/functional/bind.h"
+#include "base/functional/callback_forward.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/test/bind.h"
 #include "base/test/scoped_command_line.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/task_environment.h"
 #include "base/test/test_future.h"
-#include "base/types/expected.h"
 #include "content/public/test/browser_task_environment.h"
 #include "google_apis/common/api_error_codes.h"
 #include "google_apis/common/dummy_auth_service.h"
 #include "google_apis/common/request_sender.h"
-#include "google_apis/common/test_util.h"
+#include "google_apis/common/time_util.h"
 #include "google_apis/gaia/gaia_switches.h"
 #include "google_apis/gaia/gaia_urls.h"
 #include "google_apis/tasks/tasks_api_requests.h"
-#include "google_apis/tasks/tasks_api_response_types.h"
 #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"
@@ -38,8 +39,9 @@
 
 using ::base::test::TestFuture;
 using ::google_apis::ApiErrorCode;
-using ::google_apis::tasks::TaskLists;
-using ::google_apis::tasks::Tasks;
+using ::google_apis::util::FormatTimeAsString;
+using ::net::test_server::HttpRequest;
+using ::net::test_server::HttpResponse;
 
 // Helper class to temporary override `GaiaUrls` singleton.
 class GaiaUrlsOverrider {
@@ -51,10 +53,29 @@
   GaiaUrls test_gaia_urls_;
 };
 
+std::unique_ptr<net::test_server::HttpResponse> CreateSuccessfulResponse(
+    const std::string& content) {
+  auto response = std::make_unique<net::test_server::BasicHttpResponse>();
+  response->set_code(net::HTTP_OK);
+  response->set_content(content);
+  response->set_content_type("application/json");
+  return response;
+}
+
+std::unique_ptr<net::test_server::HttpResponse> CreateFailedResponse() {
+  auto response = std::make_unique<net::test_server::BasicHttpResponse>();
+  response->set_code(net::HTTP_INTERNAL_SERVER_ERROR);
+  return response;
+}
+
 }  // namespace
 
 class GlanceablesTasksClientImplTest : public testing::Test {
  public:
+  using GenerateResponseCallback =
+      base::RepeatingCallback<std::unique_ptr<HttpResponse>(
+          const HttpRequest& request)>;
+
   void SetUp() override {
     auto create_request_sender_callback = base::BindLambdaForTesting(
         [&](const std::vector<std::string>& scopes,
@@ -78,9 +99,8 @@
               test_server_.base_url().spec());
   }
 
-  // Relative to "google_apis/test/data/".
-  void set_file_path_for_response(const std::string& path) {
-    file_path_for_response_ = path;
+  void set_generate_response_callback(const GenerateResponseCallback& cb) {
+    generate_response_callback_ = cb;
   }
 
   GlanceablesTasksClientImpl* client() { return client_.get(); }
@@ -88,8 +108,7 @@
  private:
   std::unique_ptr<net::test_server::HttpResponse> HandleDataFileRequest(
       const net::test_server::HttpRequest& request) {
-    return google_apis::test_util::CreateHttpResponseFromFile(
-        google_apis::test_util::GetTestFilePath(file_path_for_response_));
+    return std::move(generate_response_callback_).Run(request);
   }
 
   content::BrowserTaskEnvironment task_environment_{
@@ -102,33 +121,170 @@
           /*network_service=*/nullptr,
           /*is_trusted=*/true);
   std::unique_ptr<GaiaUrlsOverrider> gaia_urls_overrider_;
-  std::string file_path_for_response_;
+  GenerateResponseCallback generate_response_callback_;
   std::unique_ptr<GlanceablesTasksClientImpl> client_;
 };
 
 TEST_F(GlanceablesTasksClientImplTest, GetTaskLists) {
-  set_file_path_for_response("tasks/task_lists.json");
+  set_generate_response_callback(
+      base::BindLambdaForTesting([](const HttpRequest& request) {
+        return CreateSuccessfulResponse(R"(
+          {
+            "kind": "tasks#taskLists",
+            "items": [
+              {
+                "id": "qwerty",
+                "title": "My Tasks 1",
+                "updated": "2023-01-30T22:19:22.812Z"
+              },
+              {
+                "id": "asdfgh",
+                "title": "My Tasks 2",
+                "updated": "2022-12-21T23:38:22.590Z"
+              }
+            ]
+          }
+        )");
+      }));
 
-  TestFuture<base::expected<std::unique_ptr<TaskLists>, ApiErrorCode>> future;
+  TestFuture<const std::vector<GlanceablesTaskList>&> future;
   auto cancel_closure = client()->GetTaskLists(future.GetCallback());
   ASSERT_TRUE(future.Wait());
 
   EXPECT_FALSE(cancel_closure.is_null());
-  EXPECT_TRUE(future.Get().has_value());
-  EXPECT_EQ(future.Get().value()->items().size(), 2u);
+
+  const auto& task_lists = future.Get();
+  EXPECT_EQ(task_lists.size(), 2u);
+
+  EXPECT_EQ(task_lists.at(0).id, "qwerty");
+  EXPECT_EQ(task_lists.at(0).title, "My Tasks 1");
+  EXPECT_EQ(FormatTimeAsString(task_lists.at(0).updated),
+            "2023-01-30T22:19:22.812Z");
+
+  EXPECT_EQ(task_lists.at(1).id, "asdfgh");
+  EXPECT_EQ(task_lists.at(1).title, "My Tasks 2");
+  EXPECT_EQ(FormatTimeAsString(task_lists.at(1).updated),
+            "2022-12-21T23:38:22.590Z");
+}
+
+TEST_F(GlanceablesTasksClientImplTest,
+       GetTaskListsReturnsEmptyVectorOnHttpError) {
+  set_generate_response_callback(base::BindLambdaForTesting(
+      [](const HttpRequest& request) { return CreateFailedResponse(); }));
+
+  TestFuture<const std::vector<GlanceablesTaskList>&> future;
+  auto cancel_closure = client()->GetTaskLists(future.GetCallback());
+  ASSERT_TRUE(future.Wait());
+
+  EXPECT_FALSE(cancel_closure.is_null());
+
+  const auto& task_lists = future.Get();
+  EXPECT_EQ(task_lists.size(), 0u);
 }
 
 TEST_F(GlanceablesTasksClientImplTest, GetTasks) {
-  set_file_path_for_response("tasks/tasks.json");
+  set_generate_response_callback(
+      base::BindLambdaForTesting([](const HttpRequest& request) {
+        return CreateSuccessfulResponse(R"(
+          {
+            "kind": "tasks#tasks",
+            "items": [
+              {
+                "id": "asd",
+                "title": "Parent task, level 1",
+                "status": "needsAction"
+              },
+              {
+                "id": "qwe",
+                "title": "Child task, level 2",
+                "parent": "asd",
+                "status": "needsAction"
+              },
+              {
+                "id": "zxc",
+                "title": "Child task, level 3",
+                "parent": "qwe",
+                "status": "completed"
+              }
+            ]
+          }
+        )");
+      }));
 
-  TestFuture<base::expected<std::unique_ptr<Tasks>, ApiErrorCode>> future;
+  TestFuture<const std::vector<GlanceablesTask>&> future;
   auto cancel_closure =
       client()->GetTasks(future.GetCallback(), "test-task-list-id");
   ASSERT_TRUE(future.Wait());
 
   EXPECT_FALSE(cancel_closure.is_null());
-  EXPECT_TRUE(future.Get().has_value());
-  EXPECT_EQ(future.Get().value()->items().size(), 2u);
+
+  const auto& root_tasks = future.Get();
+  EXPECT_EQ(root_tasks.size(), 1u);
+  EXPECT_EQ(root_tasks.at(0).id, "asd");
+  EXPECT_EQ(root_tasks.at(0).title, "Parent task, level 1");
+  EXPECT_EQ(root_tasks.at(0).completed, false);
+
+  const auto& subtasks_level_2 = root_tasks.at(0).subtasks;
+  EXPECT_EQ(subtasks_level_2.size(), 1u);
+  EXPECT_EQ(subtasks_level_2.at(0).id, "qwe");
+  EXPECT_EQ(subtasks_level_2.at(0).title, "Child task, level 2");
+  EXPECT_EQ(subtasks_level_2.at(0).completed, false);
+
+  const auto& subtasks_level_3 = subtasks_level_2.at(0).subtasks;
+  EXPECT_EQ(subtasks_level_3.size(), 1u);
+  EXPECT_EQ(subtasks_level_3.at(0).id, "zxc");
+  EXPECT_EQ(subtasks_level_3.at(0).title, "Child task, level 3");
+  EXPECT_EQ(subtasks_level_3.at(0).completed, true);
+}
+
+TEST_F(GlanceablesTasksClientImplTest, GetTasksReturnsEmptyVectorOnHttpError) {
+  set_generate_response_callback(base::BindLambdaForTesting(
+      [](const HttpRequest& request) { return CreateFailedResponse(); }));
+
+  TestFuture<const std::vector<GlanceablesTask>&> future;
+  auto cancel_closure =
+      client()->GetTasks(future.GetCallback(), "test-task-list-id");
+  ASSERT_TRUE(future.Wait());
+
+  EXPECT_FALSE(cancel_closure.is_null());
+
+  const auto& root_tasks = future.Get();
+  EXPECT_EQ(root_tasks.size(), 0u);
+}
+
+TEST_F(GlanceablesTasksClientImplTest,
+       GetTasksReturnsEmptyVectorOnConversionError) {
+  set_generate_response_callback(
+      base::BindLambdaForTesting([](const HttpRequest& request) {
+        return CreateSuccessfulResponse(R"(
+          {
+            "kind": "tasks#tasks",
+            "items": [
+              {
+                "id": "asd",
+                "title": "Parent task",
+                "status": "needsAction"
+              },
+              {
+                "id": "qwe",
+                "title": "Child task",
+                "parent": "asd1",
+                "status": "needsAction"
+              }
+            ]
+          }
+        )");
+      }));
+
+  TestFuture<const std::vector<GlanceablesTask>&> future;
+  auto cancel_closure =
+      client()->GetTasks(future.GetCallback(), "test-task-list-id");
+  ASSERT_TRUE(future.Wait());
+
+  EXPECT_FALSE(cancel_closure.is_null());
+
+  const auto& root_tasks = future.Get();
+  EXPECT_EQ(root_tasks.size(), 0u);
 }
 
 }  // namespace ash
diff --git a/chrome/browser/ui/ash/media_client_impl.cc b/chrome/browser/ui/ash/media_client_impl.cc
index 3be68da..6a97d5f 100644
--- a/chrome/browser/ui/ash/media_client_impl.cc
+++ b/chrome/browser/ui/ash/media_client_impl.cc
@@ -671,10 +671,6 @@
 void MediaClientImpl::ShowCameraOffNotification(const std::string& device_id,
                                                 const std::string& device_name,
                                                 const bool resurface) {
-  if (ash::features::IsPrivacyIndicatorsEnabled() ||
-      ash::features::IsVideoConferenceEnabled()) {
-    return;
-  }
   auto it = device_id_to_camera_privacy_switch_state_.find(device_id);
   if (it == device_id_to_camera_privacy_switch_state_.end() ||
       it->second != cros::mojom::CameraPrivacySwitchState::ON ||
diff --git a/chrome/browser/ui/ash/network/network_portal_signin_controller.cc b/chrome/browser/ui/ash/network/network_portal_signin_controller.cc
index 4a9e7b1..4040c47b 100644
--- a/chrome/browser/ui/ash/network/network_portal_signin_controller.cc
+++ b/chrome/browser/ui/ash/network/network_portal_signin_controller.cc
@@ -24,6 +24,7 @@
 #include "chromeos/ash/components/network/network_state_handler.h"
 #include "chromeos/ash/components/network/proxy/proxy_config_service_impl.h"
 #include "components/captive_portal/core/captive_portal_detector.h"
+#include "components/policy/core/common/policy_pref_names.h"
 #include "components/prefs/pref_service.h"
 #include "components/proxy_config/proxy_prefs.h"
 #include "components/user_manager/user_manager.h"
@@ -145,7 +146,8 @@
 
   IncognitoModePrefs::Availability availability;
   IncognitoModePrefs::IntToAvailability(
-      profile->GetPrefs()->GetInteger(prefs::kIncognitoModeAvailability),
+      profile->GetPrefs()->GetInteger(
+          policy::policy_prefs::kIncognitoModeAvailability),
       &availability);
   if (availability == IncognitoModePrefs::Availability::kDisabled) {
     // Use a dialog to prevent navigation and use an OTR profile due to
diff --git a/chrome/browser/ui/browser_command_controller.cc b/chrome/browser/ui/browser_command_controller.cc
index 645f50d3..dc085f1 100644
--- a/chrome/browser/ui/browser_command_controller.cc
+++ b/chrome/browser/ui/browser_command_controller.cc
@@ -58,6 +58,7 @@
 #include "components/dom_distiller/core/dom_distiller_features.h"
 #include "components/lens/buildflags.h"
 #include "components/lens/lens_features.h"
+#include "components/policy/core/common/policy_pref_names.h"
 #include "components/prefs/pref_service.h"
 #include "components/services/screen_ai/buildflags/buildflags.h"
 #include "components/sessions/content/session_tab_helper.h"
@@ -200,7 +201,7 @@
           &BrowserCommandController::UpdateCommandsForBookmarkBar,
           base::Unretained(this)));
   profile_pref_registrar_.Add(
-      prefs::kIncognitoModeAvailability,
+      policy::policy_prefs::kIncognitoModeAvailability,
       base::BindRepeating(
           &BrowserCommandController::UpdateCommandsForIncognitoAvailability,
           base::Unretained(this)));
diff --git a/chrome/browser/ui/browser_finder.cc b/chrome/browser/ui/browser_finder.cc
index 903ccdd7..32ea283d 100644
--- a/chrome/browser/ui/browser_finder.cc
+++ b/chrome/browser/ui/browser_finder.cc
@@ -214,6 +214,17 @@
   return browsers;
 }
 
+std::vector<Browser*> FindAllBrowsersWithProfile(Profile* profile) {
+  std::vector<Browser*> browsers;
+  for (auto* browser : *BrowserList::GetInstance()) {
+    if (BrowserMatches(browser, profile, Browser::FEATURE_NONE, kMatchAny,
+                       display::kInvalidDisplayId)) {
+      browsers.emplace_back(browser);
+    }
+  }
+  return browsers;
+}
+
 Browser* FindBrowserWithID(SessionID desired_id) {
   for (auto* browser : *BrowserList::GetInstance()) {
     if (browser->session_id() == desired_id)
diff --git a/chrome/browser/ui/browser_finder.h b/chrome/browser/ui/browser_finder.h
index 514f380..818d24f9 100644
--- a/chrome/browser/ui/browser_finder.h
+++ b/chrome/browser/ui/browser_finder.h
@@ -58,6 +58,10 @@
 // if no such browser currently exists.
 std::vector<Browser*> FindAllTabbedBrowsersWithProfile(Profile* profile);
 
+// Find all browsers of any type with the provided profile. Returns an empty
+// vector if no such browser currently exists.
+std::vector<Browser*> FindAllBrowsersWithProfile(Profile* profile);
+
 // Find an existing browser with the provided ID. Returns NULL if no such
 // browser currently exists.
 Browser* FindBrowserWithID(SessionID desired_id);
diff --git a/chrome/browser/ui/browser_navigator_browsertest.cc b/chrome/browser/ui/browser_navigator_browsertest.cc
index 7be0410e..f7754bf8 100644
--- a/chrome/browser/ui/browser_navigator_browsertest.cc
+++ b/chrome/browser/ui/browser_navigator_browsertest.cc
@@ -28,13 +28,13 @@
 #include "chrome/browser/ui/singleton_tabs.h"
 #include "chrome/browser/ui/tabs/tab_enums.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
-#include "chrome/common/pref_names.h"
 #include "chrome/common/url_constants.h"
 #include "chrome/test/base/ui_test_utils.h"
 #include "components/captive_portal/core/buildflags.h"
 #include "components/omnibox/browser/omnibox_edit_model.h"
 #include "components/omnibox/browser/omnibox_view.h"
 #include "components/omnibox/browser/tab_matcher.h"
+#include "components/policy/core/common/policy_pref_names.h"
 #include "components/prefs/pref_service.h"
 #include "content/public/browser/navigation_handle.h"
 #include "content/public/browser/notification_types.h"
@@ -205,11 +205,11 @@
   // Set kIncognitoModeAvailability to FORCED.
   PrefService* prefs1 = browser->profile()->GetPrefs();
   prefs1->SetInteger(
-      prefs::kIncognitoModeAvailability,
+      policy::policy_prefs::kIncognitoModeAvailability,
       static_cast<int>(IncognitoModePrefs::Availability::kForced));
   PrefService* prefs2 = browser->profile()->GetOriginalProfile()->GetPrefs();
   prefs2->SetInteger(
-      prefs::kIncognitoModeAvailability,
+      policy::policy_prefs::kIncognitoModeAvailability,
       static_cast<int>(IncognitoModePrefs::Availability::kForced));
 
   // Navigate to the page.
diff --git a/chrome/browser/ui/cocoa/applescript/browsercrapplication+applescript_browsertest.mm b/chrome/browser/ui/cocoa/applescript/browsercrapplication+applescript_browsertest.mm
index 3928eec..c43f089 100644
--- a/chrome/browser/ui/cocoa/applescript/browsercrapplication+applescript_browsertest.mm
+++ b/chrome/browser/ui/cocoa/applescript/browsercrapplication+applescript_browsertest.mm
@@ -54,14 +54,14 @@
   base::scoped_nsobject<WindowAppleScript> aWindow(
       [[WindowAppleScript alloc] init]);
   base::scoped_nsobject<NSString> var([[aWindow.get() uniqueID] copy]);
-  [aWindow.get() setValue:@YES forKey:@"isVisible"];
+  [aWindow.get() setValue:@YES forKey:@"visible"];
 
   [NSApp insertInAppleScriptWindows:aWindow.get()];
   chrome::testing::NSRunLoopRunAllPending();
 
   // Represents the window after it is added.
   WindowAppleScript* window = [NSApp appleScriptWindows][0];
-  EXPECT_NSEQ(@YES, [aWindow.get() valueForKey:@"isVisible"]);
+  EXPECT_NSEQ(@YES, [aWindow.get() valueForKey:@"visible"]);
   EXPECT_EQ([window container], NSApp);
   EXPECT_NSEQ(AppleScript::kWindowsProperty, [window containerProperty]);
   EXPECT_NSEQ(var, [window uniqueID]);
diff --git a/chrome/browser/ui/cocoa/applescript/window_applescript_browsertest.mm b/chrome/browser/ui/cocoa/applescript/window_applescript_browsertest.mm
index 92283dcd0..57f5042 100644
--- a/chrome/browser/ui/cocoa/applescript/window_applescript_browsertest.mm
+++ b/chrome/browser/ui/cocoa/applescript/window_applescript_browsertest.mm
@@ -150,10 +150,10 @@
 IN_PROC_BROWSER_TEST_F(WindowAppleScriptTest, NSWindowTest) {
   base::scoped_nsobject<WindowAppleScript> window(
       [[WindowAppleScript alloc] initWithBrowser:browser()]);
-  [window.get() setValue:@YES forKey:@"isMiniaturized"];
-  EXPECT_TRUE([[window.get() valueForKey:@"isMiniaturized"] boolValue]);
-  [window.get() setValue:@NO forKey:@"isMiniaturized"];
-  EXPECT_FALSE([[window.get() valueForKey:@"isMiniaturized"] boolValue]);
+  [window.get() setValue:@YES forKey:@"miniaturized"];
+  EXPECT_TRUE([[window.get() valueForKey:@"miniaturized"] boolValue]);
+  [window.get() setValue:@NO forKey:@"miniaturized"];
+  EXPECT_FALSE([[window.get() valueForKey:@"miniaturized"] boolValue]);
 }
 
 // Getting and setting the active tab.
diff --git a/chrome/browser/ui/color/new_tab_page_color_mixer.cc b/chrome/browser/ui/color/new_tab_page_color_mixer.cc
index e305202..757c4be 100644
--- a/chrome/browser/ui/color/new_tab_page_color_mixer.cc
+++ b/chrome/browser/ui/color/new_tab_page_color_mixer.cc
@@ -438,7 +438,7 @@
   mixer[kColorNewTabPageIconButtonBackgroundActive] = {
       dark_mode ? gfx::kGoogleGrey300 : gfx::kGoogleGrey700};
   mixer[kColorNewTabPageLink] = {dark_mode ? gfx::kGoogleBlue300
-                                           : SkColorSetRGB(0x06, 0x37, 0x74)};
+                                           : gfx::kGoogleBlue700};
   mixer[kColorNewTabPageMicBorderColor] = {dark_mode ? gfx::kGoogleGrey100
                                                      : gfx::kGoogleGrey300};
   mixer[kColorNewTabPageMicIconColor] = {dark_mode ? gfx::kGoogleGrey100
diff --git a/chrome/browser/ui/hats/hats_service_browsertest.cc b/chrome/browser/ui/hats/hats_service_browsertest.cc
index d22e155..d4407bb 100644
--- a/chrome/browser/ui/hats/hats_service_browsertest.cc
+++ b/chrome/browser/ui/hats/hats_service_browsertest.cc
@@ -20,11 +20,11 @@
 #include "chrome/browser/ui/hats/hats_service_factory.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/common/chrome_features.h"
-#include "chrome/common/pref_names.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/ui_test_utils.h"
 #include "components/content_settings/core/common/pref_names.h"
 #include "components/metrics_services_manager/metrics_services_manager.h"
+#include "components/policy/core/common/policy_pref_names.h"
 #include "components/version_info/version_info.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/test/browser_test.h"
@@ -260,7 +260,7 @@
   // Disable incognito mode for this profile.
   PrefService* pref_service = browser()->profile()->GetPrefs();
   pref_service->SetInteger(
-      prefs::kIncognitoModeAvailability,
+      policy::policy_prefs::kIncognitoModeAvailability,
       static_cast<int>(IncognitoModePrefs::Availability::kDisabled));
   EXPECT_EQ(IncognitoModePrefs::Availability::kDisabled,
             IncognitoModePrefs::GetAvailability(pref_service));
diff --git a/chrome/browser/ui/passwords/well_known_change_password_navigation_throttle.cc b/chrome/browser/ui/passwords/well_known_change_password_navigation_throttle.cc
index 6886d294..b97db8c 100644
--- a/chrome/browser/ui/passwords/well_known_change_password_navigation_throttle.cc
+++ b/chrome/browser/ui/passwords/well_known_change_password_navigation_throttle.cc
@@ -69,6 +69,14 @@
 std::unique_ptr<WellKnownChangePasswordNavigationThrottle>
 WellKnownChangePasswordNavigationThrottle::MaybeCreateThrottleFor(
     NavigationHandle* handle) {
+  auto* profile = Profile::FromBrowserContext(
+      handle->GetWebContents()->GetBrowserContext());
+  // Create WellKnownChangePasswordNavigationThrottle only for regular or
+  // incognito profiles.
+  if (!profile->IsRegularProfile() && !profile->IsIncognitoProfile()) {
+    return nullptr;
+  }
+
   // Don't handle navigations in subframes or main frames that are in a nested
   // frame tree (e.g. portals, fenced frames)
   if (handle->IsInOutermostMainFrame() &&
@@ -95,6 +103,7 @@
   affiliation_service_ =
       AffiliationServiceFactory::GetForProfile(Profile::FromBrowserContext(
           handle->GetWebContents()->GetBrowserContext()));
+  CHECK(affiliation_service_);
   if (affiliation_service_->GetChangePasswordURL(request_url_).is_empty()) {
     well_known_change_password_state_.PrefetchChangePasswordURLs(
         affiliation_service_, {request_url_});
diff --git a/chrome/browser/ui/profile_picker.h b/chrome/browser/ui/profile_picker.h
index 6bcefbf..181078b 100644
--- a/chrome/browser/ui/profile_picker.h
+++ b/chrome/browser/ui/profile_picker.h
@@ -43,9 +43,15 @@
     // (closed the browser app).
     kQuitAtEnd = 2,
 
+    // The user opens the first run again while it's still running.
+    kAbortTask = 3,
+
+    // The user does something that bypasses the FRE (opens new window etc..).
+    kAbandonedFlow = 4,
+
     // Add any new values above this one, and update kMaxValue to the highest
     // enumerator value.
-    kMaxValue = kQuitAtEnd
+    kMaxValue = kAbandonedFlow
   };
   using FirstRunExitedCallback =
       base::OnceCallback<void(FirstRunExitStatus status)>;
diff --git a/chrome/browser/ui/startup/first_run_service.cc b/chrome/browser/ui/startup/first_run_service.cc
index 3f8b5c0..c7a06442 100644
--- a/chrome/browser/ui/startup/first_run_service.cc
+++ b/chrome/browser/ui/startup/first_run_service.cc
@@ -193,37 +193,6 @@
   const PrefService* const local_state = g_browser_process->local_state();
   return local_state && local_state->GetBoolean(prefs::kFirstRunFinished);
 }
-
-// Processes the outcome from the FRE and resumes the user's interrupted task.
-// `original_intent_callback` should be run to allow the caller to resume what
-// they were trying to do before they stopped to show the FRE. If the FRE's
-// `status` is not `ProfilePicker::FirstRunExitStatus::kCompleted`, that
-// `original_intent_callback` will be called with `proceed` set to false,
-// otherwise it will be called with true.
-void OnFirstRunHasExited(ResumeTaskCallback original_intent_callback,
-                         ProfilePicker::FirstRunExitStatus status) {
-  bool should_mark_fre_finished =
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
-      status != ProfilePicker::FirstRunExitStatus::kQuitEarly;
-#else
-      true;
-#endif
-  if (should_mark_fre_finished) {
-    // The user got to the last step, we can mark the FRE as finished, whether
-    // we eventually proceed with the original intent or not.
-    SetFirstRunFinished(FinishedReason::kFinishedFlow);
-  }
-
-  base::UmaHistogramEnumeration("ProfilePicker.FirstRun.ExitStatus", status);
-
-  bool proceed = status == ProfilePicker::FirstRunExitStatus::kCompleted;
-#if BUILDFLAG(ENABLE_DICE_SUPPORT)
-  proceed |= kForYouFreCloseShouldProceed.Get();
-#endif
-
-  std::move(original_intent_callback).Run(proceed);
-}
-
 }  // namespace
 
 // FirstRunService -------------------------------------------------------------
@@ -322,22 +291,70 @@
 }
 #endif
 
-void FirstRunService::OpenFirstRunIfNeeded(EntryPoint entry_point,
-                                           ResumeTaskCallback callback) {
-  TryMarkFirstRunAlreadyFinished(base::BindOnce(
-      &FirstRunService::OpenFirstRunInternal, weak_ptr_factory_.GetWeakPtr(),
-      entry_point, std::move(callback)));
+// `resume_task_callback_` should be run to allow the caller to resume what
+// they were trying to do before they stopped to show the FRE.
+// If the FRE's `status` is not `ProfilePicker::FirstRunExitStatus::kCompleted`,
+// that `resume_task_callback_` will be called with `proceed` set to false,
+// otherwise it will be called with true.
+void FirstRunService::OnFirstRunHasExited(
+    ProfilePicker::FirstRunExitStatus status) {
+  if (!resume_task_callback_) {
+    return;
+  }
+
+  bool proceed = false;
+  bool should_mark_fre_finished = false;
+  switch (status) {
+    case ProfilePicker::FirstRunExitStatus::kCompleted:
+      proceed = true;
+      should_mark_fre_finished = true;
+      break;
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+    case ProfilePicker::FirstRunExitStatus::kQuitEarly:
+#endif
+    case ProfilePicker::FirstRunExitStatus::kAbortTask:
+      proceed = false;
+      should_mark_fre_finished = false;
+      break;
+    case ProfilePicker::FirstRunExitStatus::kQuitAtEnd:
+#if BUILDFLAG(ENABLE_DICE_SUPPORT)
+      proceed = kForYouFreCloseShouldProceed.Get();
+#endif
+      should_mark_fre_finished = true;
+      break;
+    case ProfilePicker::FirstRunExitStatus::kAbandonedFlow:
+      proceed = false;
+      should_mark_fre_finished = true;
+      break;
+  }
+
+  if (should_mark_fre_finished) {
+    // The user got to the last step, we can mark the FRE as finished, whether
+    // we eventually proceed with the original intent or not.
+    SetFirstRunFinished(FinishedReason::kFinishedFlow);
+  }
+
+  base::UmaHistogramEnumeration("ProfilePicker.FirstRun.ExitStatus", status);
+  std::move(resume_task_callback_).Run(proceed);
 }
 
-void FirstRunService::OpenFirstRunInternal(EntryPoint entry_point,
+void FirstRunService::OpenFirstRunIfNeeded(EntryPoint entry_point,
                                            ResumeTaskCallback callback) {
+  OnFirstRunHasExited(ProfilePicker::FirstRunExitStatus::kAbortTask);
+  resume_task_callback_ = std::move(callback);
+  TryMarkFirstRunAlreadyFinished(
+      base::BindOnce(&FirstRunService::OpenFirstRunInternal,
+                     weak_ptr_factory_.GetWeakPtr(), entry_point));
+}
+
+void FirstRunService::OpenFirstRunInternal(EntryPoint entry_point) {
   if (IsFirstRunMarkedFinishedInPrefs()) {
     // Opening the First Run is not needed. For example it might have been
     // marked finished silently, or is suppressed by policy.
     //
     // Note that this assumes that the prefs state is the the only part of
     // `ShouldOpenFirstRun()` that can change during the service's lifetime.
-    std::move(callback).Run(/*proceed=*/true);
+    std::move(resume_task_callback_).Run(/*proceed=*/true);
     return;
   }
 
@@ -349,8 +366,18 @@
   // Note: we call `Show()` even if the FRE might be already open and rely on
   // the ProfilePicker to decide what it wants to do with `callback`.
   ProfilePicker::Show(ProfilePicker::Params::ForFirstRun(
-      profile_->GetPath(),
-      base::BindOnce(&OnFirstRunHasExited, std::move(callback))));
+      profile_->GetPath(), base::BindOnce(&FirstRunService::OnFirstRunHasExited,
+                                          weak_ptr_factory_.GetWeakPtr())));
+}
+
+void FirstRunService::FinishFirstRunWithoutResumeTask() {
+  if (!resume_task_callback_) {
+    return;
+  }
+
+  DCHECK(ProfilePicker::IsFirstRunOpen());
+  OnFirstRunHasExited(ProfilePicker::FirstRunExitStatus::kAbandonedFlow);
+  ProfilePicker::Hide();
 }
 
 // FirstRunServiceFactory ------------------------------------------------------
diff --git a/chrome/browser/ui/startup/first_run_service.h b/chrome/browser/ui/startup/first_run_service.h
index c5cdb40c..81516c1 100644
--- a/chrome/browser/ui/startup/first_run_service.h
+++ b/chrome/browser/ui/startup/first_run_service.h
@@ -13,6 +13,7 @@
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
 #include "chrome/browser/profiles/profile_keyed_service_factory.h"
+#include "chrome/browser/ui/profile_picker.h"
 #include "components/keyed_service/core/keyed_service.h"
 #include "components/signin/public/base/signin_buildflags.h"
 
@@ -100,6 +101,9 @@
   void OpenFirstRunIfNeeded(EntryPoint entry_point,
                             ResumeTaskCallback callback);
 
+  // Terminates the first run without re-opening a browser window.
+  void FinishFirstRunWithoutResumeTask();
+
  private:
   friend class FirstRunServiceFactory;
   FRIEND_TEST_ALL_PREFIXES(FirstRunFieldTrialCreatorTest, SetUpFromClientSide);
@@ -138,8 +142,10 @@
   // The finished state can be checked by calling `ShouldOpenFirstRun()`.
   void TryMarkFirstRunAlreadyFinished(base::OnceClosure callback);
 
-  void OpenFirstRunInternal(EntryPoint entry_point,
-                            ResumeTaskCallback callback);
+  void OpenFirstRunInternal(EntryPoint entry_point);
+
+  // Processes the outcome from the FRE and resumes the user's interrupted task.
+  void OnFirstRunHasExited(ProfilePicker::FirstRunExitStatus status);
 
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
   void StartSilentSync(base::OnceClosure callback);
@@ -153,6 +159,7 @@
   std::unique_ptr<SilentSyncEnabler> silent_sync_enabler_;
 #endif
 
+  ResumeTaskCallback resume_task_callback_;
   base::WeakPtrFactory<FirstRunService> weak_ptr_factory_{this};
 };
 
diff --git a/chrome/browser/ui/startup/first_run_service_browsertest.cc b/chrome/browser/ui/startup/first_run_service_browsertest.cc
index f937c63..8341243 100644
--- a/chrome/browser/ui/startup/first_run_service_browsertest.cc
+++ b/chrome/browser/ui/startup/first_run_service_browsertest.cc
@@ -248,6 +248,40 @@
 #endif
 }
 
+IN_PROC_BROWSER_TEST_F(FirstRunServiceBrowserTest,
+                       OpenFirstRunIfNeededCalledTwice) {
+  // When `OpenFirstRunIfNeeded` is called twice, the callback passed to it the
+  // first time should be aborted (called with false) and replaced by the
+  // callback passed to it the second time, which will be later called with
+  // true on DICE and false on Lacros because it will quit early in the process.
+  base::RunLoop first_run_loop;
+  base::RunLoop second_run_loop;
+  base::HistogramTester histogram_tester;
+
+  ASSERT_TRUE(fre_service()->ShouldOpenFirstRun());
+  fre_service()->OpenFirstRunIfNeeded(
+      FirstRunService::EntryPoint::kOther,
+      ExpectProceed(false).Then(first_run_loop.QuitClosure()));
+  profiles::testing::WaitForPickerWidgetCreated();
+
+  bool second_proceed = true;
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+  second_proceed = false;
+#endif
+  fre_service()->OpenFirstRunIfNeeded(
+      FirstRunService::EntryPoint::kOther,
+      ExpectProceed(second_proceed).Then(second_run_loop.QuitClosure()));
+  first_run_loop.Run();
+
+  histogram_tester.ExpectBucketCount(
+      "ProfilePicker.FirstRun.ExitStatus",
+      ProfilePicker::FirstRunExitStatus::kAbortTask, 1);
+
+  ProfilePicker::Hide();
+  profiles::testing::WaitForPickerClosed();
+  second_run_loop.Run();
+}
+
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
 IN_PROC_BROWSER_TEST_F(FirstRunServiceBrowserTest,
                        FinishedSilentlyAlreadySyncing) {
diff --git a/chrome/browser/ui/views/autofill/payments/manage_saved_iban_bubble_view.cc b/chrome/browser/ui/views/autofill/payments/manage_saved_iban_bubble_view.cc
index 6d5e06bc..84e9f8d 100644
--- a/chrome/browser/ui/views/autofill/payments/manage_saved_iban_bubble_view.cc
+++ b/chrome/browser/ui/views/autofill/payments/manage_saved_iban_bubble_view.cc
@@ -111,7 +111,7 @@
           views::TableLayout::kFixedSize,
           provider->GetDistanceMetric(views::DISTANCE_RELATED_LABEL_HORIZONTAL))
       .AddColumn(views::LayoutAlignment::kStretch,
-                 views::LayoutAlignment::kCenter, 1.0,
+                 views::LayoutAlignment::kStretch, 1.0,
                  views::TableLayout::ColumnSize::kFixed, 0, 0)
       // Add a row for IBAN label and the value of IBAN. It might happen that
       // the revealed IBAN value is too long to fit in a single line while the
diff --git a/chrome/browser/ui/views/profiles/first_run_flow_sequence_diagram.png b/chrome/browser/ui/views/profiles/first_run_flow_sequence_diagram.png
index f52e8d0..9fdaf3d 100644
--- a/chrome/browser/ui/views/profiles/first_run_flow_sequence_diagram.png
+++ b/chrome/browser/ui/views/profiles/first_run_flow_sequence_diagram.png
Binary files differ
diff --git a/chrome/browser/ui/views/profiles/profile_management.md b/chrome/browser/ui/views/profiles/profile_management.md
index feb7a79..affdc7c 100644
--- a/chrome/browser/ui/views/profiles/profile_management.md
+++ b/chrome/browser/ui/views/profiles/profile_management.md
@@ -27,14 +27,15 @@
     activate FRS
     Caller->>FRS: OpenIfNeeded()<br/>with a ResumeTaskCallback<br/>aka void(bool success) callback
     deactivate Caller
-    note right of FRS: wraps ResumeTaskCallback as<br/>first_run_exited_callback having the<br/>signature void(FirstRunExitStatus)
+    note right of FRS: stores the ResumeTaskCallback and sends<br/> a first_run_exited_callback which is bound to <br/>OnFirstRunHasExited having the signature<br/>void(FirstRunExitStatus)
     FRS->>+PPV: ProfilePicker::Show()<br/>with first_run_exited_callback
     PPV->>+FC: Init()
     Note right of FC: FRE displayed,<br/>user advances through the flow.
 
     alt flow completed
       User->>FC: completes the flow
-      FC->>FRS: in PreFinishWithBrowser: run resume_task_callback with a<br/>success boolean based on the status
+      FC->>FRS: in PreFinishWithBrowser: run first_run_exited_callback with a<br/>success boolean based on the status
+      Note right of FRS: Handles the exit based on <br/>the status that is passed
       FRS->>+Caller: run ResumeTaskCallback<br/>with success=true
       Caller->>+Browser: launch browser
       deactivate Caller
@@ -52,6 +53,14 @@
       deactivate PPV
       FRS->>+Caller: run ResumeTaskCallback<br/>with success=false
       deactivate Caller
+    else chrome opened while first run is running
+      User->>+Caller: Open Chrome while the first run is still running
+      Caller->>FRS: OpenIfNeeded()<br/>with a ResumeTaskCallback<br/>aka void(bool success) callback
+      deactivate Caller
+      FRS->>+Caller: The first_run_exited_callback<br/>that was passed in the previous call to<br/>OpenIfNeeded() runs ResumeTaskCallback<br/> with success=false
+      deactivate Caller
+      FRS->>PPV: ProfilePicker::Show()<br/>with first_run_exited_callback
+      Note right of PPV: Opens the profile picker<br/> that has the first run already running.
     end
 
     deactivate FRS
diff --git a/chrome/browser/ui/views/side_panel/search_companion/search_companion_side_panel_coordinator.cc b/chrome/browser/ui/views/side_panel/search_companion/search_companion_side_panel_coordinator.cc
index 815af2a..2397446 100644
--- a/chrome/browser/ui/views/side_panel/search_companion/search_companion_side_panel_coordinator.cc
+++ b/chrome/browser/ui/views/side_panel/search_companion/search_companion_side_panel_coordinator.cc
@@ -29,6 +29,11 @@
 SearchCompanionSidePanelCoordinator::~SearchCompanionSidePanelCoordinator() =
     default;
 
+// static
+bool SearchCompanionSidePanelCoordinator::IsSupported(Profile* profile) {
+  return !profile->IsIncognitoProfile() && !profile->IsGuestSession();
+}
+
 void SearchCompanionSidePanelCoordinator::
     CreateAndRegisterEntriesForExistingWebContents(
         TabStripModel* tab_strip_model) {
diff --git a/chrome/browser/ui/views/side_panel/search_companion/search_companion_side_panel_coordinator.h b/chrome/browser/ui/views/side_panel/search_companion/search_companion_side_panel_coordinator.h
index cc988d3..9b8454df 100644
--- a/chrome/browser/ui/views/side_panel/search_companion/search_companion_side_panel_coordinator.h
+++ b/chrome/browser/ui/views/side_panel/search_companion/search_companion_side_panel_coordinator.h
@@ -14,6 +14,7 @@
 #include "chrome/browser/ui/views/side_panel/side_panel_entry.h"
 
 class Browser;
+class Profile;
 
 namespace views {
 class View;
@@ -32,6 +33,8 @@
       const SearchCompanionSidePanelCoordinator&) = delete;
   ~SearchCompanionSidePanelCoordinator() override;
 
+  static bool IsSupported(Profile* profile);
+
   void CreateAndRegisterEntriesForExistingWebContents(
       TabStripModel* tab_strip_model);
 
diff --git a/chrome/browser/ui/views/side_panel/side_panel_toolbar_container.cc b/chrome/browser/ui/views/side_panel/side_panel_toolbar_container.cc
index b428b87..de192c3 100644
--- a/chrome/browser/ui/views/side_panel/side_panel_toolbar_container.cc
+++ b/chrome/browser/ui/views/side_panel/side_panel_toolbar_container.cc
@@ -99,11 +99,16 @@
 void SidePanelToolbarContainer::CreatePinnedEntryButtons() {
   DCHECK(pinned_entry_buttons_.empty());
 
-  // The only pinned entry is the search companion. Add it here directly.
-  // If we support pinning side panel entries more broadly using this container
-  // then we can fetch the name and icon from the entry itself and update pinned
-  // entry toolbar buttons as the coordinator becomes aware of them. This sort
-  // of observation is unnecessary for now when there is only one pinned entry.
+  // The only pinned entry is the search companion. Add it here directly if
+  // supported. If we support pinning side panel entries more broadly using this
+  // container then we can fetch the name and icon from the entry itself and
+  // update pinned entry toolbar buttons as the coordinator becomes aware of
+  // them. This sort of observation is unnecessary for now when there is only
+  // one pinned entry.
+  if (!SearchCompanionSidePanelCoordinator::IsSupported(
+          browser_view_->GetProfile())) {
+    return;
+  }
   auto* search_companion_coordinator =
       SearchCompanionSidePanelCoordinator::GetOrCreateForBrowser(
           browser_view_->browser());
diff --git a/chrome/browser/ui/views/side_panel/side_panel_util.cc b/chrome/browser/ui/views/side_panel/side_panel_util.cc
index fdae3203..f0fc94be 100644
--- a/chrome/browser/ui/views/side_panel/side_panel_util.cc
+++ b/chrome/browser/ui/views/side_panel/side_panel_util.cc
@@ -92,7 +92,8 @@
   }
 
   // Create Search Companion coordinator.
-  if (base::FeatureList::IsEnabled(features::kSidePanelCompanion)) {
+  if (base::FeatureList::IsEnabled(features::kSidePanelCompanion) &&
+      SearchCompanionSidePanelCoordinator::IsSupported(browser->profile())) {
     SearchCompanionSidePanelCoordinator::GetOrCreateForBrowser(browser)
         ->CreateAndRegisterEntriesForExistingWebContents(
             browser->tab_strip_model());
diff --git a/chrome/browser/ui/webui/bookmarks/bookmarks_message_handler.cc b/chrome/browser/ui/webui/bookmarks/bookmarks_message_handler.cc
index 5e774f36..b4af685 100644
--- a/chrome/browser/ui/webui/bookmarks/bookmarks_message_handler.cc
+++ b/chrome/browser/ui/webui/bookmarks/bookmarks_message_handler.cc
@@ -7,8 +7,8 @@
 #include "base/functional/bind.h"
 #include "base/values.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/common/pref_names.h"
 #include "components/bookmarks/common/bookmark_pref_names.h"
+#include "components/policy/core/common/policy_pref_names.h"
 #include "components/prefs/pref_change_registrar.h"
 #include "components/prefs/pref_service.h"
 
@@ -32,7 +32,7 @@
   PrefService* prefs = Profile::FromWebUI(web_ui())->GetPrefs();
   pref_change_registrar_.Init(prefs);
   pref_change_registrar_.Add(
-      prefs::kIncognitoModeAvailability,
+      policy::policy_prefs::kIncognitoModeAvailability,
       base::BindRepeating(&BookmarksMessageHandler::UpdateIncognitoAvailability,
                           base::Unretained(this)));
   pref_change_registrar_.Add(
@@ -47,7 +47,7 @@
 
 int BookmarksMessageHandler::GetIncognitoAvailability() {
   PrefService* prefs = Profile::FromWebUI(web_ui())->GetPrefs();
-  return prefs->GetInteger(prefs::kIncognitoModeAvailability);
+  return prefs->GetInteger(policy::policy_prefs::kIncognitoModeAvailability);
 }
 
 void BookmarksMessageHandler::HandleGetIncognitoAvailability(
diff --git a/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.cc b/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.cc
index 93854b1..c932b5f 100644
--- a/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.cc
+++ b/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.cc
@@ -200,6 +200,9 @@
       base::FeatureList::IsEnabled(ntp_features::kNtpOneGoogleBar));
   source->AddBoolean("shortcutsEnabled",
                      base::FeatureList::IsEnabled(ntp_features::kNtpShortcuts));
+  source->AddBoolean(
+      "singleRowShortcutsEnabled",
+      base::FeatureList::IsEnabled(ntp_features::kNtpSingleRowShortcuts));
   source->AddBoolean("logoEnabled",
                      base::FeatureList::IsEnabled(ntp_features::kNtpLogo));
   source->AddBoolean(
diff --git a/chrome/browser/vr/chrome_xr_integration_client.cc b/chrome/browser/vr/chrome_xr_integration_client.cc
index 26b69d19..903cd1de 100644
--- a/chrome/browser/vr/chrome_xr_integration_client.cc
+++ b/chrome/browser/vr/chrome_xr_integration_client.cc
@@ -35,7 +35,7 @@
 #include "components/webxr/android/arcore_install_helper.h"
 #endif  // BUILDFLAG(ENABLE_ARCORE)
 #if BUILDFLAG(ENABLE_CARDBOARD)
-#include "device/vr/android/cardboard/cardboard_device_provider.h"
+#include "components/webxr/android/cardboard_device_provider.h"
 #endif
 #endif  // BUILDFLAG(IS_WIN)
 
@@ -120,7 +120,7 @@
   // If the cardboard runtime is enabled we want to use it rather than the GVR
   // runtime.
   if (base::FeatureList::IsEnabled(device::features::kEnableCardboard)) {
-    providers.emplace_back(std::make_unique<device::CardboardDeviceProvider>());
+    providers.emplace_back(std::make_unique<webxr::CardboardDeviceProvider>());
     add_gvr_device_provider = false;
   }
 #endif  // ENABLE_CARDBOARD
diff --git a/chrome/browser/web_applications/web_app.cc b/chrome/browser/web_applications/web_app.cc
index a3cdc83e..fb6fc8e 100644
--- a/chrome/browser/web_applications/web_app.cc
+++ b/chrome/browser/web_applications/web_app.cc
@@ -952,6 +952,10 @@
       }
       json_decl.Set("feature", feature_name->second);
       base::Value::List allowlist_json;
+      // TODO(crbug.com/1418009): Consolidate code and filter opaque origins.
+      if (decl.self_if_matches) {
+        allowlist_json.Append(decl.self_if_matches->Serialize());
+      }
       for (const auto& origin_with_possible_wildcards : decl.allowed_origins) {
         allowlist_json.Append(origin_with_possible_wildcards.Serialize());
       }
diff --git a/chrome/browser/web_applications/web_app_database.cc b/chrome/browser/web_applications/web_app_database.cc
index 96a01d6e..1953b679 100644
--- a/chrome/browser/web_applications/web_app_database.cc
+++ b/chrome/browser/web_applications/web_app_database.cc
@@ -719,6 +719,10 @@
         continue;
       const std::string feature_string(feature_name->second);
       proto_policy.set_feature(feature_string);
+      // TODO(crbug.com/1418009): Consolidate code and filter opaque origins.
+      if (decl.self_if_matches) {
+        proto_policy.add_allowed_origins(decl.self_if_matches->Serialize());
+      }
       for (const auto& origin_with_possible_wildcards : decl.allowed_origins) {
         proto_policy.add_allowed_origins(
             origin_with_possible_wildcards.Serialize());
@@ -1376,9 +1380,15 @@
       decl.feature = feature_enum->second;
 
       for (const std::string& origin : decl_proto.allowed_origins()) {
-        decl.allowed_origins.emplace_back(
-            blink::OriginWithPossibleWildcards::Parse(
-                origin, blink::OriginWithPossibleWildcards::NodeType::kHeader));
+        absl::optional<blink::OriginWithPossibleWildcards>
+            maybe_origin_with_possible_wildcards =
+                blink::OriginWithPossibleWildcards::Parse(
+                    origin,
+                    blink::OriginWithPossibleWildcards::NodeType::kHeader);
+        if (maybe_origin_with_possible_wildcards.has_value()) {
+          decl.allowed_origins.emplace_back(
+              *maybe_origin_with_possible_wildcards);
+        }
       }
       decl.matches_all_origins = decl_proto.matches_all_origins();
       decl.matches_opaque_src = decl_proto.matches_opaque_src();
diff --git a/chrome/browser/web_applications/web_app_database_unittest.cc b/chrome/browser/web_applications/web_app_database_unittest.cc
index 3023556da..83b670bc 100644
--- a/chrome/browser/web_applications/web_app_database_unittest.cc
+++ b/chrome/browser/web_applications/web_app_database_unittest.cc
@@ -706,11 +706,13 @@
 TEST_F(WebAppDatabaseProtoDataTest, PermissionsPolicyRoundTrip) {
   const blink::ParsedPermissionsPolicy policy = {
       {blink::mojom::PermissionsPolicyFeature::kGyroscope,
-       {},
+       /*allowed_origins=*/{},
+       /*self_if_matches=*/absl::nullopt,
        /*matches_all_origins=*/false,
        /*matches_opaque_src=*/true},
       {blink::mojom::PermissionsPolicyFeature::kGeolocation,
-       {},
+       /*allowed_origins=*/{},
+       /*self_if_matches=*/absl::nullopt,
        /*matches_all_origins=*/true,
        /*matches_opaque_src=*/false},
       {blink::mojom::PermissionsPolicyFeature::kGamepad,
@@ -720,6 +722,7 @@
          /*has_subdomain_wildcard=*/true},
         {url::Origin::Create(GURL("https://*.example.net")),
          /*has_subdomain_wildcard=*/false}},
+       /*self_if_matches=*/absl::nullopt,
        /*matches_all_origins=*/false,
        /*matches_opaque_src=*/false},
   };
@@ -733,11 +736,13 @@
 TEST_F(WebAppDatabaseProtoDataTest, PermissionsPolicyProto) {
   const blink::ParsedPermissionsPolicy policy = {
       {blink::mojom::PermissionsPolicyFeature::kGyroscope,
-       {},
+       /*allowed_origins=*/{},
+       /*self_if_matches=*/absl::nullopt,
        /*matches_all_origins=*/false,
        /*matches_opaque_src=*/true},
       {blink::mojom::PermissionsPolicyFeature::kGeolocation,
-       {},
+       /*allowed_origins=*/{},
+       /*self_if_matches=*/absl::nullopt,
        /*matches_all_origins=*/true,
        /*matches_opaque_src=*/false},
       {blink::mojom::PermissionsPolicyFeature::kGamepad,
@@ -747,6 +752,7 @@
          /*has_subdomain_wildcard=*/true},
         {url::Origin::Create(GURL("https://*.example.net")),
          /*has_subdomain_wildcard=*/false}},
+       /*self_if_matches=*/absl::nullopt,
        /*matches_all_origins=*/false,
        /*matches_opaque_src=*/false},
   };
diff --git a/chrome/browser/web_applications/web_app_install_utils.cc b/chrome/browser/web_applications/web_app_install_utils.cc
index 94426248..7c7a922 100644
--- a/chrome/browser/web_applications/web_app_install_utils.cc
+++ b/chrome/browser/web_applications/web_app_install_utils.cc
@@ -721,6 +721,7 @@
   for (const auto& decl : manifest.permissions_policy) {
     blink::ParsedPermissionsPolicyDeclaration copy;
     copy.feature = decl.feature;
+    copy.self_if_matches = decl.self_if_matches;
     for (const auto& origin : decl.allowed_origins)
       copy.allowed_origins.push_back(origin);
     copy.matches_all_origins = decl.matches_all_origins;
diff --git a/chrome/browser/web_applications/web_app_unittest.cc b/chrome/browser/web_applications/web_app_unittest.cc
index 7e315b632..044c4c1 100644
--- a/chrome/browser/web_applications/web_app_unittest.cc
+++ b/chrome/browser/web_applications/web_app_unittest.cc
@@ -358,11 +358,13 @@
                            GURL("https://example.com"))};
   app.SetPermissionsPolicy({
       {blink::mojom::PermissionsPolicyFeature::kGyroscope,
-       {},
+       /*allowed_origins=*/{},
+       /*self_if_matches=*/absl::nullopt,
        /*matches_all_origins=*/false,
        /*matches_opaque_src=*/true},
       {blink::mojom::PermissionsPolicyFeature::kGeolocation,
-       {},
+       /*allowed_origins=*/{},
+       /*self_if_matches=*/absl::nullopt,
        /*matches_all_origins=*/true,
        /*matches_opaque_src=*/false},
       {blink::mojom::PermissionsPolicyFeature::kGamepad,
@@ -372,6 +374,7 @@
          /*has_subdomain_wildcard=*/true},
         {url::Origin::Create(GURL("https://*.example.net")),
          /*has_subdomain_wildcard=*/false}},
+       /*self_if_matches=*/absl::nullopt,
        /*matches_all_origins=*/false,
        /*matches_opaque_src=*/false},
   });
diff --git a/chrome/browser/win/jumplist.cc b/chrome/browser/win/jumplist.cc
index 6bb953f..399f4c3 100644
--- a/chrome/browser/win/jumplist.cc
+++ b/chrome/browser/win/jumplist.cc
@@ -41,12 +41,12 @@
 #include "chrome/common/chrome_constants.h"
 #include "chrome/common/chrome_icon_resources_win.h"
 #include "chrome/common/chrome_switches.h"
-#include "chrome/common/pref_names.h"
 #include "chrome/grit/generated_resources.h"
 #include "chrome/install_static/install_util.h"
 #include "components/favicon/core/favicon_service.h"
 #include "components/history/core/browser/history_service.h"
 #include "components/history/core/browser/top_sites.h"
+#include "components/policy/core/common/policy_pref_names.h"
 #include "components/prefs/pref_change_registrar.h"
 #include "components/sessions/core/session_types.h"
 #include "components/strings/grit/components_strings.h"
@@ -269,7 +269,7 @@
   // base::Unretained is safe since |this| is guaranteed to outlive
   // pref_change_registrar_.
   pref_change_registrar_->Add(
-      prefs::kIncognitoModeAvailability,
+      policy::policy_prefs::kIncognitoModeAvailability,
       base::BindRepeating(&JumpList::OnIncognitoAvailabilityChanged,
                           base::Unretained(this)));
 }
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt
index 3c3f593..91bf43d 100644
--- a/chrome/build/linux.pgo.txt
+++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@
-chrome-linux-main-1679464574-855c77eb8be957c014ab9dd537a1dbe5271fb69e.profdata
+chrome-linux-main-1679507993-1765debab62d0f8031bc4ec09e550bffdefe51c8.profdata
diff --git a/chrome/build/mac-arm.pgo.txt b/chrome/build/mac-arm.pgo.txt
index 9e9e16d..4c1e6c4 100644
--- a/chrome/build/mac-arm.pgo.txt
+++ b/chrome/build/mac-arm.pgo.txt
@@ -1 +1 @@
-chrome-mac-arm-main-1679500768-c62459bacc77b58e7c44c1bae05ccd7e939b6f74.profdata
+chrome-mac-arm-main-1679507993-53772962ffb7f7587e0b7eae4acc0e20545d39a1.profdata
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt
index f2d64f5..ac08877 100644
--- a/chrome/build/mac.pgo.txt
+++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@
-chrome-mac-main-1679464574-4e3a85e8c457958c9d32e2e0fd5b2f0d93dd1e37.profdata
+chrome-mac-main-1679507993-5ce6247e25bb4d58baed3029cf49e476eb4c399b.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index f3dd548..f2d43e0 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1679475448-c4fd918cb966b8e797a63f1b533537af538f0796.profdata
+chrome-win32-main-1679497124-36b8ab8885de68b05f317bec92a6a9f9c4a007e8.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index 7dc6921f..b47e9a8 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1679497124-07b0619406614bb3158c4c387597025b38728e0f.profdata
+chrome-win64-main-1679507993-848d6670ef5c4e4105bd1796882f08193c258730.profdata
diff --git a/chrome/common/pref_names.cc b/chrome/common/pref_names.cc
index 3ff57c503..fe35de5 100644
--- a/chrome/common/pref_names.cc
+++ b/chrome/common/pref_names.cc
@@ -330,12 +330,6 @@
 const char kSSLErrorOverrideAllowedForOrigins[] =
     "ssl.error_override_allowed_for_origins";
 
-// Enum that specifies whether Incognito mode is:
-// 0 - Enabled. Default behaviour. Default mode is available on demand.
-// 1 - Disabled. User cannot browse pages in Incognito mode.
-// 2 - Forced. All pages/sessions are forced into Incognito.
-const char kIncognitoModeAvailability[] = "incognito.mode_availability";
-
 // Boolean that is true when Suggest support is enabled.
 const char kSearchSuggestEnabled[] = "search.suggest_enabled";
 
diff --git a/chrome/common/pref_names.h b/chrome/common/pref_names.h
index 6aa5392..26a664c 100644
--- a/chrome/common/pref_names.h
+++ b/chrome/common/pref_names.h
@@ -159,7 +159,6 @@
 #endif
 extern const char kSSLErrorOverrideAllowed[];
 extern const char kSSLErrorOverrideAllowedForOrigins[];
-extern const char kIncognitoModeAvailability[];
 extern const char kSearchSuggestEnabled[];
 #if BUILDFLAG(IS_ANDROID)
 extern const char kContextualSearchEnabled[];
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index dd1c470..2a859f1f 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -66,6 +66,7 @@
 # into the build.
 group("test") {
   testonly = true
+  deps = [ "fuzzing:test" ]
 }
 
 if (is_android) {
diff --git a/chrome/test/base/browser_tests_main_chromeos.cc b/chrome/test/base/browser_tests_main_chromeos.cc
index 3b0c6fb..ecc7734 100644
--- a/chrome/test/base/browser_tests_main_chromeos.cc
+++ b/chrome/test/base/browser_tests_main_chromeos.cc
@@ -7,6 +7,7 @@
 #include "base/test/launcher/test_launcher.h"
 #include "chrome/test/base/chrome_test_launcher.h"
 #include "chrome/test/base/chrome_test_suite.h"
+#include "content/public/common/content_switches.h"
 #include "services/tracing/public/cpp/perfetto/perfetto_traced_process.h"
 #include "ui/base/test/ui_controls.h"
 
@@ -52,5 +53,9 @@
   // custom system tracing service.
   tracing::PerfettoTracedProcess::SetSystemProducerEnabledForTesting(false);
 
+  // Temporarily force the CPU backend to use AAA. (https://crbug.com/1421297)
+  base::CommandLine::ForCurrentProcess()->AppendSwitch(
+      switches::kForceSkiaAnalyticAntialiasing);
+
   return LaunchChromeTests(parallel_jobs, &delegate, argc, argv);
 }
diff --git a/chrome/test/data/extensions/declarative_net_request/interest_group/fenced_frame.html b/chrome/test/data/extensions/declarative_net_request/interest_group/fenced_frame.html
index 1f365a7..959d2704 100644
--- a/chrome/test/data/extensions/declarative_net_request/interest_group/fenced_frame.html
+++ b/chrome/test/data/extensions/declarative_net_request/interest_group/fenced_frame.html
@@ -1,6 +1,6 @@
 <!DOCTYPE html>
 <html>
 <body>
-  <fencedframe mode=opaque-ads></fencedframe>
+  <fencedframe></fencedframe>
 </body>
 </html>
diff --git a/chrome/test/data/webui/BUILD.gn b/chrome/test/data/webui/BUILD.gn
index bde3d70..b79423f 100644
--- a/chrome/test/data/webui/BUILD.gn
+++ b/chrome/test/data/webui/BUILD.gn
@@ -90,10 +90,10 @@
       "async_gen.js",
       "bookmarks/bookmarks_browsertest.js",
       "chrome_timeticks_browsertest.js",
-      "color_provider_css_colors_browsertest.js",
       "commander/commander_browsertest.js",
       "cr_components/cr_components_browsertest.js",
       "cr_elements/cr_elements_browsertest.js",
+      "css/css_browsertest.js",
       "histograms/histograms_internals_ui_browsertest.js",
       "history/history_browsertest.js",
       "invalidations/about_invalidations_browsertest.js",
@@ -112,7 +112,6 @@
       "settings/cr_settings_browsertest.js",
       "settings/privacy_sandbox_browsertest.js",
       "settings/settings_idle_load_browsertest.js",
-      "text_defaults_browsertest.js",
       "whats_new/whats_new_browsertest.js",
     ]
 
@@ -381,8 +380,6 @@
   in_files = [
     "chai_assert.ts",
     "chrome_timeticks_test.ts",
-    "color_provider_css_colors_test_chromeos.ts",
-    "color_provider_css_colors_test.ts",
     "fake_chrome_event.ts",
     "invalidations/invalidations_test.ts",
     "metrics_test_support.ts",
@@ -397,7 +394,6 @@
     "test_plural_string_proxy.ts",
     "test_store_ts.ts",
     "test_util.ts",
-    "text_defaults_test.ts",
     "trusted_html.ts",
 
     # TODO(dpapad): Migrate the files below to TypeScript and remove allowJs
@@ -448,6 +444,7 @@
     "commander:build_grdp",
     "cr_components:build_grdp",
     "cr_elements:build_grdp",
+    "css:build_grdp",
     "discards:build_grdp",
     "downloads:build_grdp",
     "history:build_grdp",
@@ -475,6 +472,7 @@
     "$target_gen_dir/commander/resources.grdp",
     "$target_gen_dir/cr_components/resources.grdp",
     "$target_gen_dir/cr_elements/resources.grdp",
+    "$target_gen_dir/css/resources.grdp",
     "$target_gen_dir/discards/resources.grdp",
     "$target_gen_dir/downloads/resources.grdp",
     "$target_gen_dir/history/resources.grdp",
diff --git a/chrome/test/data/webui/build_webui_tests.gni b/chrome/test/data/webui/build_webui_tests.gni
index bf0fa8c4..dd00f0ac 100644
--- a/chrome/test/data/webui/build_webui_tests.gni
+++ b/chrome/test/data/webui/build_webui_tests.gni
@@ -10,11 +10,7 @@
 template("build_webui_tests") {
   not_needed([ "target_name" ])
 
-  forward_variables_from(invoker,
-                         [
-                           "files",
-                           "ts_deps",
-                         ])
+  forward_variables_from(invoker, [ "files" ])
 
   preprocess_dir = "${target_gen_dir}/preprocessed"
   tsc_dir = "${target_gen_dir}/tsc"
@@ -85,7 +81,10 @@
     if (defined(invoker.ts_definitions)) {
       definitions = invoker.ts_definitions
     }
-    deps = [ "//chrome/test/data/webui:build_ts" ] + ts_deps
+    deps = [ "//chrome/test/data/webui:build_ts" ]
+    if (defined(invoker.ts_deps)) {
+      deps += invoker.ts_deps
+    }
     extra_deps = [ ":preprocess" ]
 
     if (defined(invoker.mojo_files)) {
diff --git a/chrome/test/data/webui/color_provider_css_colors_browsertest.js b/chrome/test/data/webui/color_provider_css_colors_browsertest.js
deleted file mode 100644
index 291f5a8e..0000000
--- a/chrome/test/data/webui/color_provider_css_colors_browsertest.js
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright 2022 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-GEN('#include "build/chromeos_buildflags.h"');
-GEN('#include "content/public/test/browser_test.h"');
-
-var ColorProviderCSSColorsTest = class extends testing.Test {
-  /** @override */
-  get browsePreload() {
-    return 'chrome://webui-test/test_loader.html?module=color_provider_css_colors_test.js';
-  }
-
-  /** @override */
-  get isAsync() {
-    return true;
-  }
-
-  /** @override */
-  get extraLibraries() {
-    return [
-      '//third_party/mocha/mocha.js',
-      '//chrome/test/data/webui/mocha_adapter.js',
-    ];
-  }
-
-  /** @override */
-  get webuiHost() {
-    return 'dummyurl';
-  }
-};
-
-TEST_F('ColorProviderCSSColorsTest', 'All', function() {
-  mocha.run();
-});
-
-var ColorProviderCSSColorsTestChromeOS =
-    class extends ColorProviderCSSColorsTest {
-  /** @override */
-  get browsePreload() {
-    return 'chrome://webui-test/test_loader.html?module=color_provider_css_colors_test_chromeos.js';
-  }
-};
-
-GEN('#if BUILDFLAG(IS_CHROMEOS_ASH)');
-
-TEST_F('ColorProviderCSSColorsTestChromeOS', 'All', function() {
-  mocha.run();
-});
-
-GEN('#endif  // BUILDFLAG(IS_CHROMEOS_ASH)');
diff --git a/chrome/test/data/webui/cr_components/cr_components_mojo_browsertest.js b/chrome/test/data/webui/cr_components/cr_components_mojo_browsertest.js
index d8e02df..d92b665 100644
--- a/chrome/test/data/webui/cr_components/cr_components_mojo_browsertest.js
+++ b/chrome/test/data/webui/cr_components/cr_components_mojo_browsertest.js
@@ -86,6 +86,14 @@
   runMochaSuite('General');
 });
 
+TEST_F('CrComponentsMostVisitedTest', 'Layouts', function() {
+  runMochaSuite('Layouts');
+});
+
+TEST_F('CrComponentsMostVisitedTest', 'LoggingAndUpdates', function() {
+  runMochaSuite('LoggingAndUpdates');
+});
+
 // crbug.com/1226996
 GEN('#if BUILDFLAG(IS_LINUX) && !defined(NDEBUG)');
 GEN('#define MAYBE_Modification DISABLED_Modification');
@@ -96,6 +104,10 @@
   runMochaSuite('Modification');
 });
 
+TEST_F('CrComponentsMostVisitedTest', 'DragAndDrop', function() {
+  runMochaSuite('DragAndDrop');
+});
+
 TEST_F('CrComponentsMostVisitedTest', 'Theming', function() {
   runMochaSuite('Theming');
 });
diff --git a/chrome/test/data/webui/cr_components/most_visited_test.ts b/chrome/test/data/webui/cr_components/most_visited_test.ts
index 39eabed..e9910236 100644
--- a/chrome/test/data/webui/cr_components/most_visited_test.ts
+++ b/chrome/test/data/webui/cr_components/most_visited_test.ts
@@ -26,9 +26,7 @@
 let handler: TestMock<MostVisitedPageHandlerRemote>&
     MostVisitedPageHandlerRemote;
 let callbackRouterRemote: MostVisitedPageRemote;
-let mediaListenerWideWidth: FakeMediaQueryList;
-let mediaListenerMediumWidth: FakeMediaQueryList;
-let mediaListener: Function;
+const mediaListenerLists: Map<number, FakeMediaQueryList> = new Map();
 
 function queryAll<E extends Element = Element>(q: string): E[] {
   return Array.from(mostVisited.shadowRoot!.querySelectorAll<E>(q));
@@ -106,10 +104,7 @@
     this.media = query;
   }
 
-  addListener(listener: () => void) {
-    mediaListener = listener;
-  }
-
+  addListener() {}
   removeListener() {}
   onchange() {}
 }
@@ -117,25 +112,25 @@
 function createWindowProxy() {
   windowProxy = TestMock.fromClass(MostVisitedWindowProxy);
   windowProxy.setResultMapperFor('matchMedia', (query: string) => {
+    const result = query.match(/\(min-width: (\d+)px\)/);
+    assertTrue(!!result);
     const mediaListenerList = new FakeMediaQueryList(query);
-    if (query === '(min-width: 672px)') {
-      mediaListenerWideWidth = mediaListenerList;
-    } else if (query === '(min-width: 560px)') {
-      mediaListenerMediumWidth = mediaListenerList;
-    } else {
-      assertTrue(false);
-    }
+    mediaListenerLists.set(parseInt(result![1]!), mediaListenerList);
     return mediaListenerList;
   });
   MostVisitedWindowProxy.setInstance(windowProxy);
 }
 
 function updateScreenWidth(isWide: boolean, isMedium: boolean) {
+  mediaListenerLists.forEach(list => list.matches = false);
+  const mediaListenerWideWidth =
+      mediaListenerLists.get(Math.max(...mediaListenerLists.keys()));
+  const mediaListenerMediumWidth = mediaListenerLists.get(560);
   assertTrue(!!mediaListenerWideWidth);
   assertTrue(!!mediaListenerMediumWidth);
-  mediaListenerWideWidth.matches = isWide;
-  mediaListenerMediumWidth.matches = isMedium;
-  mediaListener();
+  mediaListenerWideWidth!.matches = isWide;
+  mediaListenerMediumWidth!.matches = isMedium;
+  mediaListenerMediumWidth!.dispatchEvent(new Event('change'));
 }
 
 function wide() {
@@ -146,18 +141,22 @@
   $$(mostVisited, '#dialogInputUrl').dispatchEvent(new Event('blur'));
 }
 
+function setUpTest(singleRow: boolean) {
+  document.body.innerHTML = window.trustedTypes!.emptyHTML;
+
+  createBrowserProxy();
+  createWindowProxy();
+
+  mostVisited = new MostVisitedElement();
+  mostVisited.singleRow = singleRow;
+  document.body.appendChild(mostVisited);
+  assertEquals(1, handler.getCallCount('updateMostVisitedInfo'));
+  wide();
+}
+
 suite('General', () => {
   setup(() => {
-    document.body.innerHTML = window.trustedTypes!.emptyHTML;
-
-    createBrowserProxy();
-    createWindowProxy();
-
-    mostVisited = new MostVisitedElement();
-    document.body.appendChild(mostVisited);
-    assertEquals(1, handler.getCallCount('updateMostVisitedInfo'));
-    assertEquals(2, windowProxy.getCallCount('matchMedia'));
-    wide();
+    setUpTest(/*singleRow=*/ false);
   });
 
   test('empty shows add shortcut only', async () => {
@@ -189,6 +188,12 @@
         new KeyboardEvent('keyup', {key: ' '}));
     assertTrue(mostVisited.$.dialog.open);
   });
+});
+
+function createLayoutsSuite(singleRow: boolean) {
+  setup(() => {
+    setUpTest(singleRow);
+  });
 
   test('four tiles fit on one line with addShortcut', async () => {
     await addTiles(4);
@@ -202,7 +207,7 @@
     });
   });
 
-  test('five tiles are displayed on two rows with addShortcut', async () => {
+  test('five tiles are displayed with addShortcut', async () => {
     await addTiles(5);
     assertEquals(5, queryTiles().length);
     assertAddShortcutShown();
@@ -211,7 +216,11 @@
     assertEquals(6, tops.length);
     const firstRowTop = tops[0];
     const secondRowTop = tops[3];
-    assertNotEquals(firstRowTop, secondRowTop);
+    if (singleRow) {
+      assertEquals(firstRowTop, secondRowTop);
+    } else {
+      assertNotEquals(firstRowTop, secondRowTop);
+    }
     tops.slice(0, 3).forEach(top => {
       assertEquals(firstRowTop, top);
     });
@@ -220,7 +229,7 @@
     });
   });
 
-  test('nine tiles are displayed on two rows with addShortcut', async () => {
+  test('nine tiles are displayed with addShortcut', async () => {
     await addTiles(9);
     assertEquals(9, queryTiles().length);
     assertAddShortcutShown();
@@ -229,7 +238,11 @@
     assertEquals(10, tops.length);
     const firstRowTop = tops[0];
     const secondRowTop = tops[5];
-    assertNotEquals(firstRowTop, secondRowTop);
+    if (singleRow) {
+      assertEquals(firstRowTop, secondRowTop);
+    } else {
+      assertNotEquals(firstRowTop, secondRowTop);
+    }
     tops.slice(0, 5).forEach(top => {
       assertEquals(firstRowTop, top);
     });
@@ -238,7 +251,7 @@
     });
   });
 
-  test('ten tiles are displayed on two rows without addShortcut', async () => {
+  test('ten tiles are displayed without addShortcut', async () => {
     await addTiles(10);
     assertEquals(10, queryTiles().length);
     assertAddShortcutHidden();
@@ -246,7 +259,11 @@
     assertEquals(10, tops.length);
     const firstRowTop = tops[0];
     const secondRowTop = tops[5];
-    assertNotEquals(firstRowTop, secondRowTop);
+    if (singleRow) {
+      assertEquals(firstRowTop, secondRowTop);
+    } else {
+      assertNotEquals(firstRowTop, secondRowTop);
+    }
     tops.slice(0, 5).forEach(top => {
       assertEquals(firstRowTop, top);
     });
@@ -311,56 +328,64 @@
       updateScreenWidth(false, false);
     }
 
-    test('six is max for narrow', async () => {
+    test('six / three is max for narrow', async () => {
       await addTiles(7);
       medium();
       assertTileLength(7);
-      assertHiddenTileLength(0);
+      assertHiddenTileLength(singleRow ? 3 : 0);
       narrow();
       assertTileLength(7);
-      assertHiddenTileLength(1);
+      assertHiddenTileLength(singleRow ? 4 : 1);
       medium();
       assertTileLength(7);
-      assertHiddenTileLength(0);
+      assertHiddenTileLength(singleRow ? 3 : 0);
     });
 
-    test('eight is max for medium', async () => {
+    test('eight / four is max for medium', async () => {
       await addTiles(8);
       narrow();
       assertTileLength(8);
-      assertHiddenTileLength(2);
+      assertHiddenTileLength(singleRow ? 5 : 2);
       medium();
       assertTileLength(8);
-      assertHiddenTileLength(0);
+      assertHiddenTileLength(singleRow ? 4 : 0);
       narrow();
       assertTileLength(8);
-      assertHiddenTileLength(2);
+      assertHiddenTileLength(singleRow ? 5 : 2);
     });
 
     test('eight is max for wide', async () => {
       await addTiles(8);
       narrow();
       assertTileLength(8);
-      assertHiddenTileLength(2);
+      assertHiddenTileLength(singleRow ? 5 : 2);
       wide();
       assertTileLength(8);
       assertHiddenTileLength(0);
       narrow();
       assertTileLength(8);
-      assertHiddenTileLength(2);
+      assertHiddenTileLength(singleRow ? 5 : 2);
     });
 
-    test('hide add shortcut if on third row (narrow)', async () => {
+    test('hide add shortcut (narrow)', async () => {
       await addTiles(6);
       medium();
-      assertAddShortcutShown();
+      if (singleRow) {
+        assertAddShortcutHidden();
+      } else {
+        assertAddShortcutShown();
+      }
       narrow();
       assertAddShortcutHidden();
       medium();
-      assertAddShortcutShown();
+      if (singleRow) {
+        assertAddShortcutHidden();
+      } else {
+        assertAddShortcutShown();
+      }
     });
 
-    test('hide add shortcut if on third row (medium)', async () => {
+    test('hide add shortcut with 8 tiles (medium)', async () => {
       await addTiles(8);
       wide();
       assertAddShortcutShown();
@@ -370,13 +395,43 @@
       assertAddShortcutShown();
     });
 
-    test('hide add shortcut if on third row (medium)', async () => {
+    test('hide add shortcut with 9 tiles (medium)', async () => {
       await addTiles(9);
       wide();
       assertAddShortcutShown();
       await addTiles(10);
       assertAddShortcutHidden();
     });
+
+    if (singleRow) {
+      test('shows correct number of tiles for all widths', async () => {
+        await addTiles(12);
+        mediaListenerLists.forEach(list => list.matches = false);
+        [...mediaListenerLists.keys()]
+            .sort((a, b) => a - b)
+            .forEach((width, i) => {
+              const list = mediaListenerLists.get(width)!;
+              list.matches = true;
+              list.dispatchEvent(new Event('change'));
+              assertHiddenTileLength(6 - i);
+            });
+      });
+    }
+  });
+}
+
+suite('Layouts', () => {
+  suite('double row', () => {
+    createLayoutsSuite(false);
+  });
+  suite('single row', () => {
+    createLayoutsSuite(true);
+  });
+});
+
+suite('LoggingAndUpdates', () => {
+  setup(() => {
+    setUpTest(/*singleRow=*/ false);
   });
 
   test('rendering tiles logs event', async () => {
@@ -458,16 +513,7 @@
   });
 
   setup(() => {
-    document.body.innerHTML = window.trustedTypes!.emptyHTML;
-
-    createBrowserProxy();
-    createWindowProxy();
-
-    mostVisited = new MostVisitedElement();
-    document.body.appendChild(mostVisited);
-    assertEquals(1, handler.getCallCount('updateMostVisitedInfo'));
-    assertEquals(2, windowProxy.getCallCount('matchMedia'));
-    wide();
+    setUpTest(/*singleRow=*/ false);
   });
 
   suite('add dialog', () => {
@@ -918,6 +964,13 @@
     await wait;
     assertFalse(toast.open);
   });
+});
+
+
+function createDragAndDropSuite(singleRow: boolean) {
+  setup(() => {
+    setUpTest(singleRow);
+  });
 
   test('drag first tile to second position', async () => {
     await addTiles(2);
@@ -1002,20 +1055,20 @@
     assertEquals('https://a/', newFirst!.href);
     assertEquals('https://b/', newSecond!.href);
   });
+}
+
+suite('DragAndDrop', () => {
+  suite('double row', () => {
+    createDragAndDropSuite(false);
+  });
+  suite('single row', () => {
+    createDragAndDropSuite(true);
+  });
 });
 
 suite('Theming', () => {
   setup(() => {
-    document.body.innerHTML = window.trustedTypes!.emptyHTML;
-
-    createBrowserProxy();
-    createWindowProxy();
-
-    mostVisited = new MostVisitedElement();
-    document.body.appendChild(mostVisited);
-    assertEquals(1, handler.getCallCount('updateMostVisitedInfo'));
-    assertEquals(2, windowProxy.getCallCount('matchMedia'));
-    wide();
+    setUpTest(/*singleRow=*/ false);
   });
 
   test('RIGHT_TO_LEFT tile title text direction', async () => {
diff --git a/chrome/test/data/webui/css/BUILD.gn b/chrome/test/data/webui/css/BUILD.gn
new file mode 100644
index 0000000..028513e0
--- /dev/null
+++ b/chrome/test/data/webui/css/BUILD.gn
@@ -0,0 +1,17 @@
+# 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("../build_webui_tests.gni")
+
+build_webui_tests("build") {
+  resource_path_prefix = "css"
+
+  files = [
+    "color_provider_css_colors_test.ts",
+    "text_defaults_test.ts",
+  ]
+  if (is_chromeos_ash) {
+    files += [ "color_provider_css_colors_test_chromeos.ts" ]
+  }
+}
diff --git a/chrome/test/data/webui/color_provider_css_colors_test.ts b/chrome/test/data/webui/css/color_provider_css_colors_test.ts
similarity index 95%
rename from chrome/test/data/webui/color_provider_css_colors_test.ts
rename to chrome/test/data/webui/css/color_provider_css_colors_test.ts
index 20a609e..4ac7d77 100644
--- a/chrome/test/data/webui/color_provider_css_colors_test.ts
+++ b/chrome/test/data/webui/css/color_provider_css_colors_test.ts
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import {assertEquals, assertNotEquals} from './chai_assert.js';
+import {assertEquals, assertNotEquals} from 'chrome://webui-test/chai_assert.js';
 
 suite('ColorProviderCSSColorsTest', function() {
   let link: HTMLLinkElement;
diff --git a/chrome/test/data/webui/color_provider_css_colors_test_chromeos.ts b/chrome/test/data/webui/css/color_provider_css_colors_test_chromeos.ts
similarity index 94%
rename from chrome/test/data/webui/color_provider_css_colors_test_chromeos.ts
rename to chrome/test/data/webui/css/color_provider_css_colors_test_chromeos.ts
index f8c5347..b65c45a 100644
--- a/chrome/test/data/webui/color_provider_css_colors_test_chromeos.ts
+++ b/chrome/test/data/webui/css/color_provider_css_colors_test_chromeos.ts
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import {assertNotEquals} from './chai_assert.js';
+import {assertNotEquals} from 'chrome://webui-test/chai_assert.js';
 
 suite('ColorProviderCSSColorsTest', function() {
   let link: HTMLLinkElement;
diff --git a/chrome/test/data/webui/css/css_browsertest.js b/chrome/test/data/webui/css/css_browsertest.js
new file mode 100644
index 0000000..ec5454f
--- /dev/null
+++ b/chrome/test/data/webui/css/css_browsertest.js
@@ -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.
+
+GEN('#include "build/chromeos_buildflags.h"');
+GEN('#include "content/public/test/browser_test.h"');
+
+var CssTest = class extends testing.Test {
+  /** @override */
+  get browsePreload() {
+    throw new Error('Should be overriden by subclasses');
+  }
+
+  /** @override */
+  get isAsync() {
+    return true;
+  }
+
+  /** @override */
+  get webuiHost() {
+    return 'dummyurl';
+  }
+};
+
+var TextDefaultsTest = class extends CssTest {
+  /** @override */
+  get browsePreload() {
+    return 'chrome://webui-test/test_loader.html?module=css/text_defaults_test.js';
+  }
+};
+
+TEST_F('TextDefaultsTest', 'All', function() {
+  mocha.run();
+});
+
+var ColorProviderCSSColorsTest = class extends CssTest {
+  /** @override */
+  get browsePreload() {
+    return 'chrome://webui-test/test_loader.html?module=css/color_provider_css_colors_test.js';
+  }
+};
+
+TEST_F('ColorProviderCSSColorsTest', 'All', function() {
+  mocha.run();
+});
+
+GEN('#if BUILDFLAG(IS_CHROMEOS_ASH)');
+
+var ColorProviderCSSColorsTestChromeOS =
+    class extends ColorProviderCSSColorsTest {
+  /** @override */
+  get browsePreload() {
+    return 'chrome://webui-test/test_loader.html?module=css/color_provider_css_colors_test_chromeos.js';
+  }
+};
+
+TEST_F('ColorProviderCSSColorsTestChromeOS', 'All', function() {
+  mocha.run();
+});
+
+GEN('#endif  // BUILDFLAG(IS_CHROMEOS_ASH)');
diff --git a/chrome/test/data/webui/text_defaults_test.ts b/chrome/test/data/webui/css/text_defaults_test.ts
similarity index 93%
rename from chrome/test/data/webui/text_defaults_test.ts
rename to chrome/test/data/webui/css/text_defaults_test.ts
index 2fdc524..a2cb0dc 100644
--- a/chrome/test/data/webui/text_defaults_test.ts
+++ b/chrome/test/data/webui/css/text_defaults_test.ts
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import {assertNotEquals, assertTrue} from './chai_assert.js';
+import {assertNotEquals, assertTrue} from 'chrome://webui-test/chai_assert.js';
 
 suite('TextDefaults', function() {
   setup(function() {
diff --git a/chrome/test/data/webui/settings/chromeos/chromevox_subpage_tests.js b/chrome/test/data/webui/settings/chromeos/chromevox_subpage_tests.js
index d0f804b..27aa027 100644
--- a/chrome/test/data/webui/settings/chromeos/chromevox_subpage_tests.js
+++ b/chrome/test/data/webui/settings/chromeos/chromevox_subpage_tests.js
@@ -127,6 +127,35 @@
     });
   });
 
+  test('event stream filter toggles sync to prefs', async () => {
+    // Enable event stream logging to allow enabling filter toggles.
+    const loggingToggle =
+        page.shadowRoot.querySelector('#enableEventStreamLoggingToggle');
+    loggingToggle.click();
+    await waitAfterNextRender(loggingToggle);
+
+    // Get all event stream filter prefs.
+    let pref = page.getPref('settings.a11y.chromevox.event_stream_filters');
+
+    // Toggle each filter, verify each pref is set.
+    page.eventStreamFilters_.forEach(filter => {
+      const toggle = page.shadowRoot.querySelector('#' + filter);
+
+      // Make sure toggle exists.
+      assertTrue(!!toggle);
+
+      // Make sure pref filter state is false or undefined (key is not present).
+      assertTrue([false, undefined].includes(pref.value[filter]));
+
+      // Enable event stream filter toggle.
+      toggle.click();
+
+      // Make sure event stream filter pref state is true.
+      pref = page.getPref('settings.a11y.chromevox.event_stream_filters');
+      assertTrue(pref.value[filter]);
+    });
+  });
+
   test('voices are ordered', async function() {
     // Make sure voices are ordered with the system default voice first, then
     // Google voices, then eSpeak, then local, then remote.
diff --git a/chrome/test/data/webui/text_defaults_browsertest.js b/chrome/test/data/webui/text_defaults_browsertest.js
deleted file mode 100644
index f27d4a56..0000000
--- a/chrome/test/data/webui/text_defaults_browsertest.js
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright 2016 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-GEN('#include "content/public/test/browser_test.h"');
-
-/**
- * Test fixture for testing async methods of cr.js.
- */
-var TextDefaultsTest = class extends testing.Test {
-  /**
-   * @override
-   */
-  get browsePreload() {
-    return 'chrome://webui-test/test_loader.html?module=text_defaults_test.js';
-  }
-
-  /** @override */
-  get isAsync() {
-    return true;
-  }
-
-  /** @override */
-  get extraLibraries() {
-    return [
-      '//third_party/mocha/mocha.js',
-      '//chrome/test/data/webui/mocha_adapter.js',
-    ];
-  }
-
-  /** @override */
-  get webuiHost() {
-    return 'dummyurl';
-  }
-};
-
-
-TEST_F('TextDefaultsTest', 'All', function() {
-  mocha.run();
-});
diff --git a/chrome/test/fuzzing/BUILD.gn b/chrome/test/fuzzing/BUILD.gn
new file mode 100644
index 0000000..a118478
--- /dev/null
+++ b/chrome/test/fuzzing/BUILD.gn
@@ -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.
+
+import("//build/buildflag_header.gni")
+import("//chrome/test/fuzzing/in_process_fuzz_test.gni")
+
+# This target exists to reference other test executables to bring these files
+# into the build.
+group("test") {
+  testonly = true
+}
+
+if (fuzzing_engine_supports_custom_main) {
+  static_library("in_process_fuzz_test_runner") {
+    testonly = true
+    sources = [
+      "in_process_fuzz_test.cc",
+      "in_process_fuzz_test.h",
+    ]
+    deps = [
+      ":in_process_fuzzing_buildflags",
+      "//testing/libfuzzer:fuzzing_engine_no_main",
+    ]
+    public_deps = [ "//chrome/test:test_support" ]
+  }
+
+  buildflag_header("in_process_fuzzing_buildflags") {
+    header = "in_process_fuzzing_buildflags.h"
+    flags = [ "AVOID_SINGLE_PROCESS_MODE=$use_libfuzzer" ]
+  }
+}
+
+if (!is_android) {
+  in_process_fuzz_test("html_in_process_fuzz_tests") {
+    sources = [ "html_in_process_fuzz_test.cc" ]
+  }
+}
diff --git a/chrome/test/fuzzing/OWNERS b/chrome/test/fuzzing/OWNERS
new file mode 100644
index 0000000..2f8c6f2
--- /dev/null
+++ b/chrome/test/fuzzing/OWNERS
@@ -0,0 +1 @@
+file://testing/libfuzzer/OWNERS
diff --git a/chrome/test/fuzzing/html_in_process_fuzz_test.cc b/chrome/test/fuzzing/html_in_process_fuzz_test.cc
new file mode 100644
index 0000000..17105aa
--- /dev/null
+++ b/chrome/test/fuzzing/html_in_process_fuzz_test.cc
@@ -0,0 +1,56 @@
+// 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/test/base/ui_test_utils.h"
+#include "chrome/test/fuzzing/in_process_fuzz_test.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+// This is an example use of the InProcessFuzzTest framework.
+// It fetches arbitrary HTML from an HTTPS server. It's not really
+// intended to be an effective fuzzer, but just to show an example
+// of how this framework can be used.
+
+class HtmlInProcessFuzzTest : virtual public InProcessFuzzTest {
+ public:
+  HtmlInProcessFuzzTest()
+      : https_test_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
+
+  void SetUpOnMainThread() override {
+    InProcessFuzzTest::SetUpOnMainThread();
+    host_resolver()->AddRule("*", "127.0.0.1");
+    https_test_server_.SetSSLConfig(net::EmbeddedTestServer::CERT_OK);
+    https_test_server_.RegisterRequestHandler(base::BindRepeating(
+        &HtmlInProcessFuzzTest::HandleHTTPRequest, base::Unretained(this)));
+    ASSERT_TRUE(https_test_server_.Start());
+  }
+  int Fuzz(const uint8_t* data, size_t size) override;
+  std::unique_ptr<net::test_server::HttpResponse> HandleHTTPRequest(
+      const net::test_server::HttpRequest& request) const;
+
+  net::EmbeddedTestServer https_test_server_;
+  std::string current_fuzz_case_;
+};
+
+REGISTER_IN_PROCESS_FUZZER(HtmlInProcessFuzzTest)
+
+std::unique_ptr<net::test_server::HttpResponse>
+HtmlInProcessFuzzTest::HandleHTTPRequest(
+    const net::test_server::HttpRequest& request) const {
+  std::unique_ptr<net::test_server::BasicHttpResponse> response;
+  response = std::make_unique<net::test_server::BasicHttpResponse>();
+  response->set_content_type("text/html");
+  response->set_content(current_fuzz_case_);
+  response->set_code(net::HTTP_OK);
+  return response;
+}
+
+int HtmlInProcessFuzzTest::Fuzz(const uint8_t* data, size_t size) {
+  std::string html_string(reinterpret_cast<const char*>(data), size);
+  current_fuzz_case_ = html_string;
+  GURL test_url = https_test_server_.GetURL("/test.html");
+  base::IgnoreResult(ui_test_utils::NavigateToURL(browser(), test_url));
+  return 0;
+}
diff --git a/chrome/test/fuzzing/in_process_fuzz_test.cc b/chrome/test/fuzzing/in_process_fuzz_test.cc
new file mode 100644
index 0000000..76abbab
--- /dev/null
+++ b/chrome/test/fuzzing/in_process_fuzz_test.cc
@@ -0,0 +1,181 @@
+// 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 <vector>
+
+#include "base/at_exit.h"
+#include "base/command_line.h"
+#include "base/functional/callback_forward.h"
+#include "base/test/bind.h"
+#include "chrome/test/base/chrome_test_launcher.h"
+#include "chrome/test/fuzzing/in_process_fuzz_test.h"
+#include "chrome/test/fuzzing/in_process_fuzzing_buildflags.h"
+#include "content/public/app/content_main.h"
+#include "content/public/test/test_launcher.h"
+
+// This is provided within libfuzzer, and documented, but is not its headers.
+extern "C" int LLVMFuzzerRunDriver(int* argc,
+                                   char*** argv,
+                                   int (*UserCb)(const uint8_t* Data,
+                                                 size_t Size));
+
+InProcessFuzzTestFactoryBase* g_in_process_fuzz_test_factory;
+
+InProcessFuzzTest::InProcessFuzzTest() = default;
+
+InProcessFuzzTest::~InProcessFuzzTest() = default;
+
+base::CommandLine::StringVector
+InProcessFuzzTest::GetChromiumCommandLineArguments() {
+  base::CommandLine::StringVector empty;
+  return empty;
+}
+
+void InProcessFuzzTest::Run(
+    const std::vector<std::string>& libfuzzer_command_line) {
+  libfuzzer_command_line_ = libfuzzer_command_line;
+  SetUp();
+  TearDown();
+}
+
+void InProcessFuzzTest::SetUpOnMainThread() {
+  InProcessBrowserTest::SetUpOnMainThread();
+}
+
+InProcessFuzzTest* g_test;
+
+class FuzzTestLauncherDelegate : public content::TestLauncherDelegate {
+ public:
+  FuzzTestLauncherDelegate(std::unique_ptr<InProcessFuzzTest>&& fuzz_test,
+                           std::vector<std::string>&& libfuzzer_arguments)
+      : fuzz_test_(std::move(fuzz_test)),
+        libfuzzer_arguments_(std::move(libfuzzer_arguments)) {
+    content_main_delegate_ =
+        std::make_unique<ChromeTestChromeMainDelegate>(base::TimeTicks::Now());
+  }
+
+  int RunTestSuite(int argc, char** argv) override {
+    fuzz_test_->Run(libfuzzer_arguments_);
+    return 0;
+  }
+#if !BUILDFLAG(IS_ANDROID)
+  // Android browser tests set the ContentMainDelegate itself for the test
+  // harness to use, and do not go through ContentMain() in TestLauncher.
+  content::ContentMainDelegate* CreateContentMainDelegate() override {
+    return &*content_main_delegate_;
+  }
+#endif
+
+ private:
+  std::unique_ptr<InProcessFuzzTest> fuzz_test_;
+  std::unique_ptr<content::ContentMainDelegate>
+      content_main_delegate_;  // TODO remove unique_ptr
+  std::vector<std::string> libfuzzer_arguments_;
+};
+
+int fuzz_callback(const uint8_t* data, size_t size) {
+  return g_test->FuzzCallback(data, size);
+}
+
+int InProcessFuzzTest::FuzzCallback(const uint8_t* data, size_t size) {
+  int result;
+  base::RunLoop run_loop;
+
+  base::RepeatingCallback<void()> run_fuzz_case_lambda =
+      base::BindLambdaForTesting([&]() {
+        result = Fuzz(data, size);
+        run_loop.QuitClosure().Run();
+      });
+  base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, run_fuzz_case_lambda);
+  run_loop.Run();
+  return result;
+}
+
+void InProcessFuzzTest::FuzzCaseFinished(
+    int* result_storage,
+    const base::RepeatingClosure& quit_closure,
+    int result) {
+  *result_storage = result;
+  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(FROM_HERE,
+                                                              quit_closure);
+}
+
+void InProcessFuzzTest::RunTestOnMainThread() {
+  std::vector<char*> argv;
+  for (const auto& arg : libfuzzer_command_line_) {
+    argv.push_back((char*)arg.data());
+  }
+  argv.push_back(nullptr);
+  int argc = argv.size() - 1;
+  char** argv2 = argv.data();
+  g_test = this;
+  base::IgnoreResult(LLVMFuzzerRunDriver(&argc, &argv2, fuzz_callback));
+  g_test = nullptr;
+}
+
+// Main function for running in process fuzz tests.
+// This aims to replicate //chrome browser tests as much as possible; we want
+// the whole browser environment to be available for this sort of test in as
+// realistic a fashion as possible.
+int main(int argc, char** argv) {
+  base::AtExitManager atexit_manager;
+  base::CommandLine::Init(argc, argv);
+
+  std::unique_ptr<InProcessFuzzTest> fuzzer =
+      g_in_process_fuzz_test_factory->CreateInProcessFuzzer();
+
+  // Oh dear, you've got to the part of the code relating to command lines.
+  // I'm sorry.
+  // Here are our constraints:
+  // * Both libfuzzer/centipede and Chromium expect a full command line
+  // * We set the format of neither command line
+  // * Chromium will launch other Chromium processes, giving them a command
+  // line.
+  // * The centipede runner will launch our fuzzer, giving it a command line.
+  // So, at this point, we have to figure out heuristics for what's up.
+  // Are we the original fuzzer process, in which case we pass the CLI to
+  // libfuzzer/centipede, and ask for a suitable Chromium command line from
+  // our fuzz test? Or, are we a child Chromium process which has been
+  // launched from a previous Chromium process? Well, dear reader, there are
+  // no telltail arguments guaranteed to be on either, so we're going to
+  // use a heuristic. If the first argument starts with --, we're assuming
+  // we're a Chromium child.
+
+  bool we_are_probably_a_chromium_child_process = false;
+  if (base::CommandLine::ForCurrentProcess()->argv().size() > 1) {
+    if (base::CommandLine::ForCurrentProcess()->argv()[1].starts_with("--")) {
+      we_are_probably_a_chromium_child_process = true;
+    }
+  }
+  std::vector<std::string> libfuzzer_arguments;
+  if (we_are_probably_a_chromium_child_process) {
+    // If we're a Chromium child, we don't alter the command-line,
+    // and in fact the libfuzzer code will never run, so we don't need to
+    // pass any arguments through to libfuzzer.
+  } else {
+    libfuzzer_arguments = base::CommandLine::ForCurrentProcess()->argv();
+    std::string executable_name = libfuzzer_arguments.at(0);
+    base::CommandLine::StringVector chromium_arguments =
+        fuzzer->GetChromiumCommandLineArguments();
+    chromium_arguments.insert(chromium_arguments.begin(), executable_name);
+    chromium_arguments.push_back("--single-process-tests");
+#if !BUILDFLAG(AVOID_SINGLE_PROCESS_MODE)
+    // TODO(1038952): make libfuzzer compatible with single-process mode.
+    // As it stands, single-process mode works with centipede (and is probably
+    // desirable both in terms of fuzzing speed and correctly gathering
+    // coverage information) but not yet with libfuzzer.
+    chromium_arguments.push_back("--single-process");
+#endif  // BUILDFLAG(AVOID_SINGLE_PROCESS_MODE)
+    chromium_arguments.push_back("--no-sandbox");
+    chromium_arguments.push_back("--no-zygote");
+    chromium_arguments.push_back("--disable-gpu");
+    base::CommandLine::ForCurrentProcess()->InitFromArgv(chromium_arguments);
+  }
+
+  FuzzTestLauncherDelegate* fuzz_test_launcher_delegate =
+      new FuzzTestLauncherDelegate(std::move(fuzzer),
+                                   std::move(libfuzzer_arguments));
+  return content::LaunchTests(fuzz_test_launcher_delegate, 1, argc, argv);
+}
diff --git a/chrome/test/fuzzing/in_process_fuzz_test.gni b/chrome/test/fuzzing/in_process_fuzz_test.gni
new file mode 100644
index 0000000..81b0de62
--- /dev/null
+++ b/chrome/test/fuzzing/in_process_fuzz_test.gni
@@ -0,0 +1,35 @@
+# 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("//testing/libfuzzer/fuzzer_test.gni")
+
+# This template allows creation of a fuzzer which has access to all the
+# functionality of a browser_test, including a full GUI instance of Chromium.
+# See in_process_fuzz_test.h.
+#
+# It accepts all the same arguments as //testing/libfuzzer/fuzzer_test.gni's
+# fuzzer_test template.
+template("in_process_fuzz_test") {
+  if (fuzzing_engine_supports_custom_main) {
+    fuzzer_test(target_name) {
+      deps = [ "//chrome/test/fuzzing:in_process_fuzz_test_runner" ]
+      if (defined(invoker.deps)) {
+        deps += invoker.deps
+      }
+      forward_variables_from(invoker,
+                             "*",
+                             [
+                               "deps",
+                               "exclude_main",
+                             ])
+      exclude_main = true
+    }
+  } else {
+    # noop if the fuzzer harness always provides its own main
+    not_needed(invoker, "*")
+
+    group(target_name) {
+    }
+  }
+}
diff --git a/chrome/test/fuzzing/in_process_fuzz_test.h b/chrome/test/fuzzing/in_process_fuzz_test.h
new file mode 100644
index 0000000..20adb85
--- /dev/null
+++ b/chrome/test/fuzzing/in_process_fuzz_test.h
@@ -0,0 +1,89 @@
+// 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_TEST_FUZZING_IN_PROCESS_FUZZ_TEST_H_
+#define CHROME_TEST_FUZZING_IN_PROCESS_FUZZ_TEST_H_
+
+#include <optional>
+
+#include "chrome/test/base/in_process_browser_test.h"
+
+// In-process fuzz test.
+//
+// This is equivalent to a browser test, in that the entire browser
+// environment is available for your use, and you can do rich things that
+// require the whole browser infrastructure.
+//
+// The 'Fuzz' method will be called repeatedly, and you just have to
+// implement something sensible there to explore parts of Chrome's
+// attack surface.
+//
+// Register your subclass with REGISTER_IN_PROCESS_FUZZER. There can only
+// be one per executable.
+class InProcessFuzzTest : virtual public InProcessBrowserTest {
+ public:
+  // Called by the main function to create this class.
+  // This is called prior to all the normal browser test setup,
+  // so don't do anything important in your constructor.
+  // Furthermore, this will be re-run even for child Chromium processes.
+  InProcessFuzzTest();
+  ~InProcessFuzzTest() override;
+
+  // Called by the main function to run this fuzzer, after the browser_test
+  // equivalent infrastructure has been set up.
+  void Run(const std::vector<std::string>& libfuzzer_command_line);
+
+  // If you override this, it's essential you call the superclass method.
+  void SetUpOnMainThread() override;
+  void RunTestOnMainThread() override;
+  void TestBody() override {}
+
+  friend int fuzz_callback(const uint8_t* data, size_t size);
+
+  // Override if you want to pass particular command line arguments to
+  // Chromium for its startup. This is called before any fuzz test case
+  // is actually run, so unfortunately you can't generate these through
+  // fuzzing. In addition, the browser test framework itself does all
+  // sorts of fiddling with the arguments (e.g. a user data dir).
+  // It's generally OK to leave this at the default unless you specifically
+  // need to enable a feature or similar.
+  // Do not include the executable name in your return value - that's
+  // prepended automatically.
+  virtual base::CommandLine::StringVector GetChromiumCommandLineArguments();
+
+ protected:
+  // Callback to actually do your fuzzing. This is called from the UI thread,
+  // so you should take care not to block the thread too long. If you need
+  // to run your fuzz case across multiple threads, consider a nested RunLoop.
+  virtual int Fuzz(const uint8_t* data, size_t size) = 0;
+
+ private:
+  int FuzzCallback(const uint8_t* data, size_t size);
+  void FuzzCaseFinished(int* result_storage,
+                        const base::RepeatingClosure& quit_closure,
+                        int result);
+  std::vector<std::string> libfuzzer_command_line_;
+};
+
+class InProcessFuzzTestFactoryBase {
+ public:
+  virtual std::unique_ptr<InProcessFuzzTest> CreateInProcessFuzzer() = 0;
+};
+
+extern InProcessFuzzTestFactoryBase* g_in_process_fuzz_test_factory;
+
+// Class used to register a single in-process fuzzer in each executable.
+template <typename T>
+class InProcessFuzzTestFactory : public InProcessFuzzTestFactoryBase {
+ public:
+  InProcessFuzzTestFactory() { g_in_process_fuzz_test_factory = this; }
+  std::unique_ptr<InProcessFuzzTest> CreateInProcessFuzzer() override {
+    return std::make_unique<T>();
+  }
+};
+
+#define REGISTER_IN_PROCESS_FUZZER(fuzzer_class) \
+  InProcessFuzzTestFactory<fuzzer_class> fuzzer_instance;
+
+#endif  // CHROME_TEST_FUZZING_IN_PROCESS_FUZZ_TEST_H_
diff --git a/chrome/updater/BUILD.gn b/chrome/updater/BUILD.gn
index 55e59cd..0d1ee727 100644
--- a/chrome/updater/BUILD.gn
+++ b/chrome/updater/BUILD.gn
@@ -62,6 +62,8 @@
       "app/app_server.h",
       "app/app_uninstall.cc",
       "app/app_uninstall.h",
+      "app/app_uninstall_self.cc",
+      "app/app_uninstall_self.h",
       "app/app_update.cc",
       "app/app_update.h",
       "app/app_utils.cc",
diff --git a/chrome/updater/app/app_uninstall.cc b/chrome/updater/app/app_uninstall.cc
index 1c2328b..2e85b41 100644
--- a/chrome/updater/app/app_uninstall.cc
+++ b/chrome/updater/app/app_uninstall.cc
@@ -92,9 +92,6 @@
   // Inter-process lock taken by AppInstall, AppUninstall, and AppUpdate.
   std::unique_ptr<ScopedLock> setup_lock_;
 
-  // Conditionally set, if prefs must be acquired for some uninstall scenarios.
-  // Creating the prefs instance may result in deadlocks. Therefore, the prefs
-  // lock can't be taken in all cases.
   scoped_refptr<GlobalPrefs> global_prefs_;
 };
 
@@ -102,10 +99,7 @@
   setup_lock_ =
       ScopedLock::Create(kSetupMutex, updater_scope(), kWaitForSetupLock);
 
-  const base::CommandLine* command_line =
-      base::CommandLine::ForCurrentProcess();
-  if (command_line->HasSwitch(kUninstallIfUnusedSwitch))
-    global_prefs_ = CreateGlobalPrefs(updater_scope());
+  global_prefs_ = CreateGlobalPrefs(updater_scope());
 }
 
 void AppUninstall::Uninitialize() {
@@ -137,30 +131,21 @@
     return;
   }
 
+  if (!global_prefs_) {
+    VLOG(0) << "Failed to acquire global prefs; shutting down.";
+    Shutdown(kErrorFailedToLockPrefsMutex);
+    return;
+  }
+
   const base::CommandLine* command_line =
       base::CommandLine::ForCurrentProcess();
 
   if (command_line->HasSwitch(kUninstallSwitch)) {
-    CHECK(!global_prefs_);
     UninstallAll();
     return;
   }
 
-  if (command_line->HasSwitch(kUninstallSelfSwitch)) {
-    CHECK(!global_prefs_);
-    base::ThreadPool::PostTaskAndReplyWithResult(
-        FROM_HERE, {base::MayBlock()},
-        base::BindOnce(&UninstallCandidate, updater_scope()),
-        base::BindOnce(&AppUninstall::Shutdown, this));
-    return;
-  }
-
   if (command_line->HasSwitch(kUninstallIfUnusedSwitch)) {
-    if (!global_prefs_) {
-      VLOG(0) << "Failed to acquire global prefs; shutting down.";
-      Shutdown(kErrorFailedToLockPrefsMutex);
-      return;
-    }
     auto persisted_data = base::MakeRefCounted<PersistedData>(
         updater_scope(), global_prefs_->GetPrefService());
     const bool should_uninstall = ShouldUninstall(
diff --git a/chrome/updater/app/app_uninstall_self.cc b/chrome/updater/app/app_uninstall_self.cc
new file mode 100644
index 0000000..ada9d558
--- /dev/null
+++ b/chrome/updater/app/app_uninstall_self.cc
@@ -0,0 +1,73 @@
+// 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/updater/app/app_uninstall_self.h"
+
+#include "base/functional/bind.h"
+#include "base/logging.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/task/task_traits.h"
+#include "base/task/thread_pool.h"
+#include "build/build_config.h"
+#include "chrome/updater/app/app.h"
+#include "chrome/updater/constants.h"
+#include "chrome/updater/lock.h"
+#include "chrome/updater/util/util.h"
+
+#if BUILDFLAG(IS_WIN)
+#include "chrome/updater/win/setup/uninstall.h"
+#elif BUILDFLAG(IS_POSIX)
+#include "chrome/updater/posix/setup.h"
+#endif
+
+namespace updater {
+
+// AppUninstallSelf uninstalls this instance of the updater.
+class AppUninstallSelf : public App {
+ public:
+  AppUninstallSelf() = default;
+
+ private:
+  ~AppUninstallSelf() override = default;
+  void Initialize() override;
+  void Uninitialize() override;
+  void FirstTaskRun() override;
+
+  void UninstallAll();
+
+  // Inter-process lock taken by AppInstall, AppUninstall, and AppUpdate.
+  std::unique_ptr<ScopedLock> setup_lock_;
+};
+
+void AppUninstallSelf::Initialize() {
+  setup_lock_ =
+      ScopedLock::Create(kSetupMutex, updater_scope(), kWaitForSetupLock);
+}
+
+void AppUninstallSelf::Uninitialize() {}
+
+void AppUninstallSelf::FirstTaskRun() {
+  if (WrongUser(updater_scope())) {
+    VLOG(0) << "The current user is not compatible with the current scope.";
+    Shutdown(kErrorWrongUser);
+    return;
+  }
+
+  if (!setup_lock_) {
+    VLOG(0) << "Failed to acquire setup mutex; shutting down.";
+    Shutdown(kErrorFailedToLockSetupMutex);
+    return;
+  }
+
+  base::ThreadPool::PostTaskAndReplyWithResult(
+      FROM_HERE, {base::MayBlock()},
+      base::BindOnce(&UninstallCandidate, updater_scope()),
+      base::BindOnce(&AppUninstallSelf::Shutdown, this));
+}
+
+scoped_refptr<App> MakeAppUninstallSelf() {
+  return base::MakeRefCounted<AppUninstallSelf>();
+}
+
+}  // namespace updater
diff --git a/chrome/updater/app/app_uninstall_self.h b/chrome/updater/app/app_uninstall_self.h
new file mode 100644
index 0000000..408eea3
--- /dev/null
+++ b/chrome/updater/app/app_uninstall_self.h
@@ -0,0 +1,18 @@
+// 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_UPDATER_APP_APP_UNINSTALL_SELF_H_
+#define CHROME_UPDATER_APP_APP_UNINSTALL_SELF_H_
+
+#include "base/memory/scoped_refptr.h"
+
+namespace updater {
+
+class App;
+
+scoped_refptr<App> MakeAppUninstallSelf();
+
+}  // namespace updater
+
+#endif  // CHROME_UPDATER_APP_APP_UNINSTALL_SELF_H_
diff --git a/chrome/updater/updater.cc b/chrome/updater/updater.cc
index 6079f561..b6538a50 100644
--- a/chrome/updater/updater.cc
+++ b/chrome/updater/updater.cc
@@ -24,6 +24,7 @@
 #include "chrome/updater/app/app_install.h"
 #include "chrome/updater/app/app_recover.h"
 #include "chrome/updater/app/app_uninstall.h"
+#include "chrome/updater/app/app_uninstall_self.h"
 #include "chrome/updater/app/app_update.h"
 #include "chrome/updater/app/app_wake.h"
 #include "chrome/updater/app/app_wakeall.h"
@@ -173,11 +174,14 @@
   }
 
   if (command_line->HasSwitch(kUninstallSwitch) ||
-      command_line->HasSwitch(kUninstallSelfSwitch) ||
       command_line->HasSwitch(kUninstallIfUnusedSwitch)) {
     return MakeAppUninstall()->Run();
   }
 
+  if (command_line->HasSwitch(kUninstallSelfSwitch)) {
+    return MakeAppUninstallSelf()->Run();
+  }
+
   if (command_line->HasSwitch(kRecoverSwitch) ||
       command_line->HasSwitch(kBrowserVersionSwitch)) {
     return MakeAppRecover()->Run();
diff --git a/chromeos/CHROMEOS_LKGM b/chromeos/CHROMEOS_LKGM
index 8c125ef0..a71c63f0 100644
--- a/chromeos/CHROMEOS_LKGM
+++ b/chromeos/CHROMEOS_LKGM
@@ -1 +1 @@
-15390.0.0
\ No newline at end of file
+15392.0.0
\ No newline at end of file
diff --git a/chromeos/ash/components/phonehub/fake_phone_hub_manager.cc b/chromeos/ash/components/phonehub/fake_phone_hub_manager.cc
index 9721199..2348770 100644
--- a/chromeos/ash/components/phonehub/fake_phone_hub_manager.cc
+++ b/chromeos/ash/components/phonehub/fake_phone_hub_manager.cc
@@ -103,5 +103,15 @@
   return &app_stream_manager_;
 }
 
+eche_app::EcheConnectionStatusHandler*
+FakePhoneHubManager::GetEcheConnectionStatusHandler() {
+  return eche_connection_status_handler_;
+}
+
+void FakePhoneHubManager::SetEcheConnectionStatusHandler(
+    eche_app::EcheConnectionStatusHandler* eche_connection_status_handler) {
+  eche_connection_status_handler_ = eche_connection_status_handler;
+}
+
 }  // namespace phonehub
 }  // namespace ash
diff --git a/chromeos/ash/components/phonehub/fake_phone_hub_manager.h b/chromeos/ash/components/phonehub/fake_phone_hub_manager.h
index 75e8517..82e2c82 100644
--- a/chromeos/ash/components/phonehub/fake_phone_hub_manager.h
+++ b/chromeos/ash/components/phonehub/fake_phone_hub_manager.h
@@ -105,6 +105,11 @@
     host_last_seen_timestamp_ = timestamp;
   }
 
+  void set_eche_connection_hander(
+      eche_app::EcheConnectionStatusHandler* handler) {
+    eche_connection_status_handler_ = handler;
+  }
+
  private:
   // PhoneHubManager:
   BrowserTabsModelProvider* GetBrowserTabsModelProvider() override;
@@ -129,6 +134,11 @@
       base::OnceCallback<void(absl::optional<base::Time>)> callback) override;
   IconDecoder* GetIconDecoder() override;
   AppStreamManager* GetAppStreamManager() override;
+  eche_app::EcheConnectionStatusHandler* GetEcheConnectionStatusHandler()
+      override;
+  void SetEcheConnectionStatusHandler(
+      eche_app::EcheConnectionStatusHandler* eche_connection_status_handler)
+      override;
 
   FakeDoNotDisturbController fake_do_not_disturb_controller_;
   FakeFeatureStatusProvider fake_feature_status_provider_;
@@ -149,6 +159,8 @@
   FakePingManager fake_ping_manager_;
   FakeIconDecoder fake_icon_decoder_;
   AppStreamManager app_stream_manager_;
+  eche_app::EcheConnectionStatusHandler* eche_connection_status_handler_ =
+      nullptr;
   absl::optional<base::Time> host_last_seen_timestamp_ = absl::nullopt;
 };
 
diff --git a/chromeos/ash/components/phonehub/phone_hub_manager.h b/chromeos/ash/components/phonehub/phone_hub_manager.h
index 1d41f23..a12487c 100644
--- a/chromeos/ash/components/phonehub/phone_hub_manager.h
+++ b/chromeos/ash/components/phonehub/phone_hub_manager.h
@@ -12,6 +12,11 @@
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace ash {
+
+namespace eche_app {
+class EcheConnectionStatusHandler;
+}
+
 namespace phonehub {
 
 class BrowserTabsModelProvider;
@@ -64,6 +69,11 @@
   virtual UserActionRecorder* GetUserActionRecorder() = 0;
   virtual IconDecoder* GetIconDecoder() = 0;
   virtual AppStreamManager* GetAppStreamManager() = 0;
+  virtual eche_app::EcheConnectionStatusHandler*
+  GetEcheConnectionStatusHandler() = 0;
+  virtual void SetEcheConnectionStatusHandler(
+      eche_app::EcheConnectionStatusHandler*
+          eche_connection_status_handler) = 0;
 
   // Retrieves the timestamp of the last successful discovery for active host,
   // or nullopt if it hasn't been seen in the current Chrome session.
diff --git a/chromeos/ash/components/phonehub/phone_hub_manager_impl.cc b/chromeos/ash/components/phonehub/phone_hub_manager_impl.cc
index 93b57452..f0ffc2d 100644
--- a/chromeos/ash/components/phonehub/phone_hub_manager_impl.cc
+++ b/chromeos/ash/components/phonehub/phone_hub_manager_impl.cc
@@ -279,6 +279,18 @@
   connection_manager_->GetHostLastSeenTimestamp(std::move(callback));
 }
 
+eche_app::EcheConnectionStatusHandler*
+PhoneHubManagerImpl::GetEcheConnectionStatusHandler() {
+  return eche_connection_status_handler_;
+}
+
+void PhoneHubManagerImpl::SetEcheConnectionStatusHandler(
+    eche_app::EcheConnectionStatusHandler* eche_connection_status_handler) {
+  eche_connection_status_handler_ = eche_connection_status_handler;
+  recent_apps_interaction_handler_->SetConnectionStatusHandler(
+      eche_connection_status_handler_);
+}
+
 // NOTE: These should be destroyed in the opposite order of how these objects
 // are initialized in the constructor.
 void PhoneHubManagerImpl::Shutdown() {
diff --git a/chromeos/ash/components/phonehub/phone_hub_manager_impl.h b/chromeos/ash/components/phonehub/phone_hub_manager_impl.h
index f2685486..01dbdac 100644
--- a/chromeos/ash/components/phonehub/phone_hub_manager_impl.h
+++ b/chromeos/ash/components/phonehub/phone_hub_manager_impl.h
@@ -91,6 +91,12 @@
   void GetHostLastSeenTimestamp(
       base::OnceCallback<void(absl::optional<base::Time>)> callback) override;
 
+  eche_app::EcheConnectionStatusHandler* GetEcheConnectionStatusHandler()
+      override;
+  void SetEcheConnectionStatusHandler(
+      eche_app::EcheConnectionStatusHandler* eche_connection_status_handler)
+      override;
+
  private:
   // KeyedService:
   void Shutdown() override;
@@ -130,6 +136,8 @@
   std::unique_ptr<FeatureSetupResponseProcessor>
       feature_setup_response_processor_;
   std::unique_ptr<PingManager> ping_manager_;
+  eche_app::EcheConnectionStatusHandler* eche_connection_status_handler_ =
+      nullptr;
 };
 
 }  // namespace phonehub
diff --git a/components/access_code_cast/common/access_code_cast_metrics.cc b/components/access_code_cast/common/access_code_cast_metrics.cc
index 4ac3ed6..3f57492 100644
--- a/components/access_code_cast/common/access_code_cast_metrics.cc
+++ b/components/access_code_cast/common/access_code_cast_metrics.cc
@@ -5,6 +5,7 @@
 #include "components/access_code_cast/common/access_code_cast_metrics.h"
 
 #include "base/metrics/histogram_functions.h"
+#include "base/notreached.h"
 
 AccessCodeCastMetrics::AccessCodeCastMetrics() = default;
 AccessCodeCastMetrics::~AccessCodeCastMetrics() = default;
@@ -28,6 +29,8 @@
     "AccessCodeCast.Ui.DialogOpenLocation";
 const char AccessCodeCastMetrics::kHistogramRememberedDevicesCount[] =
     "AccessCodeCast.Discovery.RememberedDevicesCount";
+const char AccessCodeCastMetrics::kHistogramRouteDiscoveryTypeAndSource[] =
+    "AccessCodeCast.Session.RouteDiscoveryTypeAndSource";
 const char AccessCodeCastMetrics::kHistogramRouteDuration[] =
     "AccessCodeCast.Session.RouteDuration";
 const char AccessCodeCastMetrics::kHistogramUiTabSwitcherUsageType[] =
@@ -54,11 +57,62 @@
 
 // static
 void AccessCodeCastMetrics::RecordAccessCodeRouteStarted(
-    base::TimeDelta duration) {
+    base::TimeDelta duration,
+    bool is_saved,
+    AccessCodeCastCastMode mode) {
   int64_t duration_seconds = duration.InSeconds();
   // Duration can take one of five values, ranging from zero (0 sec), up to
   // a year (31536000 sec). So, recording as a sparse histogram is best.
   base::UmaHistogramSparse(kHistogramDeviceDurationOnRoute, duration_seconds);
+
+  AccessCodeCastDiscoveryTypeAndSource discovery_type_and_source =
+      AccessCodeCastDiscoveryTypeAndSource::kUnknown;
+  if (is_saved) {
+    switch (mode) {
+      case AccessCodeCastCastMode::kPresentation:
+        discovery_type_and_source =
+            AccessCodeCastDiscoveryTypeAndSource::kSavedDevicePresentation;
+        break;
+      case AccessCodeCastCastMode::kTabMirror:
+        discovery_type_and_source =
+            AccessCodeCastDiscoveryTypeAndSource::kSavedDeviceTabMirror;
+        break;
+      case AccessCodeCastCastMode::kDesktopMirror:
+        discovery_type_and_source =
+            AccessCodeCastDiscoveryTypeAndSource::kSavedDeviceDesktopMirror;
+        break;
+      case AccessCodeCastCastMode::kRemotePlayback:
+        discovery_type_and_source =
+            AccessCodeCastDiscoveryTypeAndSource::kSavedDeviceRemotePlayback;
+        break;
+      default:
+        NOTREACHED_NORETURN();
+    }
+  } else { /* is_saved == false (A new device just added by access code) */
+    switch (mode) {
+      case AccessCodeCastCastMode::kPresentation:
+        discovery_type_and_source =
+            AccessCodeCastDiscoveryTypeAndSource::kNewDevicePresentation;
+        break;
+      case AccessCodeCastCastMode::kTabMirror:
+        discovery_type_and_source =
+            AccessCodeCastDiscoveryTypeAndSource::kNewDeviceTabMirror;
+        break;
+      case AccessCodeCastCastMode::kDesktopMirror:
+        discovery_type_and_source =
+            AccessCodeCastDiscoveryTypeAndSource::kNewDeviceDesktopMirror;
+        break;
+      case AccessCodeCastCastMode::kRemotePlayback:
+        discovery_type_and_source =
+            AccessCodeCastDiscoveryTypeAndSource::kNewDeviceRemotePlayback;
+        break;
+      default:
+        NOTREACHED_NORETURN();
+    }
+  }
+
+  base::UmaHistogramEnumeration(kHistogramRouteDiscoveryTypeAndSource,
+                                discovery_type_and_source);
 }
 
 // static
diff --git a/components/access_code_cast/common/access_code_cast_metrics.h b/components/access_code_cast/common/access_code_cast_metrics.h
index 2c67001..1a353e6 100644
--- a/components/access_code_cast/common/access_code_cast_metrics.h
+++ b/components/access_code_cast/common/access_code_cast_metrics.h
@@ -74,6 +74,24 @@
 
 // These values are persisted to logs. Entries should not be renumbered and
 // numeric values should never be reused.
+enum class AccessCodeCastDiscoveryTypeAndSource {
+  kUnknown = 0,
+  kSavedDevicePresentation = 1,
+  kSavedDeviceTabMirror = 2,
+  kSavedDeviceDesktopMirror = 3,
+  kSavedDeviceRemotePlayback = 4,
+  kNewDevicePresentation = 5,
+  kNewDeviceTabMirror = 6,
+  kNewDeviceDesktopMirror = 7,
+  kNewDeviceRemotePlayback = 8,
+
+  // NOTE: Do not reorder existing entries, and add entries only immediately
+  // above this line.
+  kMaxValue = kNewDeviceRemotePlayback
+};
+
+// These values are persisted to logs. Entries should not be renumbered and
+// numeric values should never be reused.
 enum class AccessCodeCastUiTabSwitcherUsage {
   kTabSwitcherUiShownAndNotUsed = 0,
   kTabSwitcherUiShownAndUsedToSwitchTabs = 1,
@@ -98,6 +116,7 @@
   static const char kHistogramDialogLoadTime[];
   static const char kHistogramDialogOpenLocation[];
   static const char kHistogramRememberedDevicesCount[];
+  static const char kHistogramRouteDiscoveryTypeAndSource[];
   static const char kHistogramRouteDuration[];
   static const char kHistogramUiTabSwitcherUsageType[];
   static const char kHistogramUiTabSwitchingCount[];
@@ -111,8 +130,10 @@
   static void RecordAccessCodeNotFoundCount(int count);
 
   // Records the value of the device duration pref on successful creation of
-  // an access code route.
-  static void RecordAccessCodeRouteStarted(base::TimeDelta duration);
+  // an access code route. Also records the discovery type and cast source.
+  static void RecordAccessCodeRouteStarted(base::TimeDelta duration,
+                                           bool is_saved,
+                                           AccessCodeCastCastMode mode);
 
   // Records the result of adding an access code sink.
   static void RecordAddSinkResult(bool is_remembered,
diff --git a/components/access_code_cast/common/access_code_cast_metrics_unittest.cc b/components/access_code_cast/common/access_code_cast_metrics_unittest.cc
index 54d54ba..b25347ab 100644
--- a/components/access_code_cast/common/access_code_cast_metrics_unittest.cc
+++ b/components/access_code_cast/common/access_code_cast_metrics_unittest.cc
@@ -122,17 +122,20 @@
 TEST(AccessCodeCastMetricsTest, RecordAccessCodeRouteStarted) {
   base::HistogramTester histogram_tester;
 
-  AccessCodeCastMetrics::RecordAccessCodeRouteStarted(base::Seconds(0));
+  AccessCodeCastCastMode cast_mode = AccessCodeCastCastMode::kPresentation;
+
+  AccessCodeCastMetrics::RecordAccessCodeRouteStarted(base::Seconds(0), false,
+                                                      cast_mode);
   histogram_tester.ExpectBucketCount(
       "AccessCodeCast.Discovery.DeviceDurationOnRoute", 0, 1);
 
   // Ensure the functions properly converts duration to seconds
-  AccessCodeCastMetrics::RecordAccessCodeRouteStarted(
-      base::Milliseconds(10000));
+  AccessCodeCastMetrics::RecordAccessCodeRouteStarted(base::Milliseconds(10000),
+                                                      false, cast_mode);
   histogram_tester.ExpectBucketCount(
       "AccessCodeCast.Discovery.DeviceDurationOnRoute", 10, 1);
-  AccessCodeCastMetrics::RecordAccessCodeRouteStarted(
-      base::Milliseconds(20000));
+  AccessCodeCastMetrics::RecordAccessCodeRouteStarted(base::Milliseconds(20000),
+                                                      false, cast_mode);
   histogram_tester.ExpectBucketCount(
       "AccessCodeCast.Discovery.DeviceDurationOnRoute", 20, 1);
 
@@ -140,6 +143,53 @@
       "AccessCodeCast.Discovery.DeviceDurationOnRoute", 3);
 }
 
+TEST(AccessCodeCastMetricsTest, RecordAccessCodeRouteStartedRouteInfo) {
+  base::HistogramTester histogram_tester;
+
+  AccessCodeCastMetrics::RecordAccessCodeRouteStarted(
+      base::Seconds(0), true, AccessCodeCastCastMode::kPresentation);
+  histogram_tester.ExpectBucketCount(
+      "AccessCodeCast.Session.RouteDiscoveryTypeAndSource", 1, 1);
+
+  AccessCodeCastMetrics::RecordAccessCodeRouteStarted(
+      base::Seconds(0), true, AccessCodeCastCastMode::kTabMirror);
+  histogram_tester.ExpectBucketCount(
+      "AccessCodeCast.Session.RouteDiscoveryTypeAndSource", 2, 1);
+
+  AccessCodeCastMetrics::RecordAccessCodeRouteStarted(
+      base::Seconds(0), true, AccessCodeCastCastMode::kDesktopMirror);
+  histogram_tester.ExpectBucketCount(
+      "AccessCodeCast.Session.RouteDiscoveryTypeAndSource", 3, 1);
+
+  AccessCodeCastMetrics::RecordAccessCodeRouteStarted(
+      base::Seconds(0), true, AccessCodeCastCastMode::kRemotePlayback);
+  histogram_tester.ExpectBucketCount(
+      "AccessCodeCast.Session.RouteDiscoveryTypeAndSource", 4, 1);
+
+  AccessCodeCastMetrics::RecordAccessCodeRouteStarted(
+      base::Seconds(0), false, AccessCodeCastCastMode::kPresentation);
+  histogram_tester.ExpectBucketCount(
+      "AccessCodeCast.Session.RouteDiscoveryTypeAndSource", 5, 1);
+
+  AccessCodeCastMetrics::RecordAccessCodeRouteStarted(
+      base::Seconds(0), false, AccessCodeCastCastMode::kTabMirror);
+  histogram_tester.ExpectBucketCount(
+      "AccessCodeCast.Session.RouteDiscoveryTypeAndSource", 6, 1);
+
+  AccessCodeCastMetrics::RecordAccessCodeRouteStarted(
+      base::Seconds(0), false, AccessCodeCastCastMode::kDesktopMirror);
+  histogram_tester.ExpectBucketCount(
+      "AccessCodeCast.Session.RouteDiscoveryTypeAndSource", 7, 1);
+
+  AccessCodeCastMetrics::RecordAccessCodeRouteStarted(
+      base::Seconds(0), false, AccessCodeCastCastMode::kRemotePlayback);
+  histogram_tester.ExpectBucketCount(
+      "AccessCodeCast.Session.RouteDiscoveryTypeAndSource", 8, 1);
+
+  histogram_tester.ExpectTotalCount(
+      "AccessCodeCast.Session.RouteDiscoveryTypeAndSource", 8);
+}
+
 TEST(AccessCodeCastMetricsTest, RecordDialogLoadTime) {
   base::HistogramTester histogram_tester;
 
@@ -255,4 +305,14 @@
       << "'AccessCodeCastDialogOpenLocation' enum was changed in "
          "access_code_cast_metrics.h. Please update the entry in "
          "enums.xml to match.";
+
+  // DiscoveryTypeAndSource
+  absl::optional<base::HistogramEnumEntryMap> discovery_types_and_sources =
+      base::ReadEnumFromEnumsXml("AccessCodeCastDiscoveryTypeAndSource");
+  EXPECT_TRUE(
+      discovery_types_and_sources->size() ==
+      static_cast<int>(AccessCodeCastDiscoveryTypeAndSource::kMaxValue) + 1)
+      << "'AccessCodeCastDicoveryTypeAndSource' enum was changed in "
+         "access_code_cast_metrics.h. Please update the entry in "
+         "enums.xml to match.";
 }
diff --git a/components/autofill/content/browser/form_forest_unittest.cc b/components/autofill/content/browser/form_forest_unittest.cc
index 62a20c0..c6d178e7 100644
--- a/components/autofill/content/browser/form_forest_unittest.cc
+++ b/components/autofill/content/browser/form_forest_unittest.cc
@@ -384,14 +384,18 @@
         blink::mojom::PermissionsPolicyFeature::kSharedAutofill,
         {blink::OriginWithPossibleWildcards(origin,
                                             /*has_subdomain_wildcard=*/false)},
-        false, false)};
+        /*self_if_matches=*/absl::nullopt,
+        /*matches_all_origins=*/false,
+        /*matches_opaque_src=*/false)};
   }
 
   // Explicitly disallows shared-autofill on all origins.
   static blink::ParsedPermissionsPolicy DisallowSharedAutofill() {
     return {blink::ParsedPermissionsPolicyDeclaration(
-        blink::mojom::PermissionsPolicyFeature::kSharedAutofill, {}, false,
-        false)};
+        blink::mojom::PermissionsPolicyFeature::kSharedAutofill,
+        /*allowed_origins=*/{}, /*self_if_matches=*/absl::nullopt,
+        /*matches_all_origins=*/false,
+        /*matches_opaque_src=*/false)};
   }
 
   MockContentAutofillDriver* NavigateFrame(content::RenderFrameHost* rfh,
diff --git a/components/autofill/core/browser/autofill_save_update_address_profile_delegate_ios.cc b/components/autofill/core/browser/autofill_save_update_address_profile_delegate_ios.cc
index 5efd6ef5b..5e2fa6d2 100644
--- a/components/autofill/core/browser/autofill_save_update_address_profile_delegate_ios.cc
+++ b/components/autofill/core/browser/autofill_save_update_address_profile_delegate_ios.cc
@@ -23,12 +23,16 @@
     AutofillSaveUpdateAddressProfileDelegateIOS(
         const AutofillProfile& profile,
         const AutofillProfile* original_profile,
+        absl::optional<std::u16string> syncing_user_email,
         const std::string& locale,
+        AutofillClient::SaveAddressProfilePromptOptions options,
         AutofillClient::AddressProfileSavePromptCallback callback)
     : locale_(locale),
       profile_(profile),
       original_profile_(base::OptionalFromPtr(original_profile)),
-      address_profile_save_prompt_callback_(std::move(callback)) {}
+      address_profile_save_prompt_callback_(std::move(callback)),
+      is_migration_to_account_(options.is_migration_to_account),
+      syncing_user_email_(syncing_user_email) {}
 
 AutofillSaveUpdateAddressProfileDelegateIOS::
     ~AutofillSaveUpdateAddressProfileDelegateIOS() {
@@ -74,6 +78,13 @@
 
 std::u16string AutofillSaveUpdateAddressProfileDelegateIOS::GetDescription()
     const {
+  if (is_migration_to_account_) {
+    DCHECK(profile_.source() != autofill::AutofillProfile::Source::kAccount);
+    DCHECK(syncing_user_email_);
+    return l10n_util::GetStringFUTF16(
+        IDS_IOS_AUTOFILL_SAVE_ADDRESS_IN_ACCOUNT_MESSAGE_SUBTITLE,
+        *syncing_user_email_);
+  }
   return GetProfileDescription(
       original_profile_ ? *original_profile_ : profile_, locale_,
       /*include_address_and_contacts=*/true);
@@ -184,6 +195,10 @@
 
 std::u16string AutofillSaveUpdateAddressProfileDelegateIOS::GetMessageText()
     const {
+  if (is_migration_to_account_) {
+    return l10n_util::GetStringUTF16(
+        IDS_IOS_AUTOFILL_SAVE_ADDRESS_IN_ACCOUNT_MESSAGE_TITLE);
+  }
   return l10n_util::GetStringUTF16(
       original_profile_ ? IDS_IOS_AUTOFILL_UPDATE_ADDRESS_MESSAGE_TITLE
                         : IDS_IOS_AUTOFILL_SAVE_ADDRESS_MESSAGE_TITLE);
diff --git a/components/autofill/core/browser/autofill_save_update_address_profile_delegate_ios.h b/components/autofill/core/browser/autofill_save_update_address_profile_delegate_ios.h
index 3539a821..4ab3476 100644
--- a/components/autofill/core/browser/autofill_save_update_address_profile_delegate_ios.h
+++ b/components/autofill/core/browser/autofill_save_update_address_profile_delegate_ios.h
@@ -23,7 +23,9 @@
   AutofillSaveUpdateAddressProfileDelegateIOS(
       const AutofillProfile& profile,
       const AutofillProfile* original_profile,
+      absl::optional<std::u16string> syncing_user_email,
       const std::string& locale,
+      AutofillClient::SaveAddressProfilePromptOptions options,
       AutofillClient::AddressProfileSavePromptCallback callback);
   AutofillSaveUpdateAddressProfileDelegateIOS(
       const AutofillSaveUpdateAddressProfileDelegateIOS&) = delete;
@@ -88,6 +90,8 @@
   bool Cancel() override;
   bool EqualsDelegate(infobars::InfoBarDelegate* delegate) const override;
 
+  bool IsMigrationToAccount() const { return is_migration_to_account_; }
+
 #if defined(UNIT_TEST)
   // Getter for |user_decision_|. Used for the testing purposes.
   AutofillClient::SaveAddressProfileOfferUserDecision user_decision() const {
@@ -121,6 +125,12 @@
   // True if the either of banner or modal is visible currently.
   bool is_infobar_visible_ = false;
 
+  // Denotes if an account migration prompt should be shown.
+  bool is_migration_to_account_;
+
+  // Denotes the email address of the syncing account.
+  absl::optional<std::u16string> syncing_user_email_;
+
   // Records the last user decision based on the interactions with the
   // banner/modal to be sent with |address_profile_save_prompt_callback_|.
   AutofillClient::SaveAddressProfileOfferUserDecision user_decision_ =
diff --git a/components/autofill/core/browser/autofill_save_update_address_profile_delegate_ios_unittest.cc b/components/autofill/core/browser/autofill_save_update_address_profile_delegate_ios_unittest.cc
index 2a422f7..126650b 100644
--- a/components/autofill/core/browser/autofill_save_update_address_profile_delegate_ios_unittest.cc
+++ b/components/autofill/core/browser/autofill_save_update_address_profile_delegate_ios_unittest.cc
@@ -15,30 +15,49 @@
 
 namespace autofill {
 
-TEST(AutofillSaveUpdateAddressProfileDelegateIOSTest,
-     HandleUserAction_Accepted) {
-  AutofillProfile profile = test::GetFullProfile();
-  base::MockCallback<AutofillClient::AddressProfileSavePromptCallback> callback;
-  auto delegate = std::make_unique<AutofillSaveUpdateAddressProfileDelegateIOS>(
-      profile, /*original_profile=*/nullptr, /*locale=*/"en-US",
-      callback.Get());
+class AutofillSaveUpdateAddressProfileDelegateIOSTest : public testing::Test {
+ protected:
+  AutofillSaveUpdateAddressProfileDelegateIOSTest() = default;
+  ~AutofillSaveUpdateAddressProfileDelegateIOSTest() override {}
 
+  void SetUp() override { profile_ = test::GetFullProfile(); }
+
+  std::unique_ptr<AutofillSaveUpdateAddressProfileDelegateIOS>
+  CreateAutofillSaveUpdateAddressProfileDelegate(
+      AutofillProfile* original_profile = nullptr,
+      absl::optional<std::u16string> email = absl::nullopt,
+      bool is_migration_to_account = false) {
+    return std::make_unique<AutofillSaveUpdateAddressProfileDelegateIOS>(
+        profile_, original_profile, email,
+        /*locale=*/"en-US",
+        AutofillClient::SaveAddressProfilePromptOptions{
+            .is_migration_to_account = is_migration_to_account},
+        callback_.Get());
+  }
+
+  AutofillProfile profile_;
+  base::MockCallback<AutofillClient::AddressProfileSavePromptCallback>
+      callback_;
+};
+
+// Tests that the callback is run with kAccepted on Accepted.
+TEST_F(AutofillSaveUpdateAddressProfileDelegateIOSTest,
+       HandleUserAction_Accepted) {
+  std::unique_ptr<AutofillSaveUpdateAddressProfileDelegateIOS> delegate =
+      CreateAutofillSaveUpdateAddressProfileDelegate();
   EXPECT_CALL(
-      callback,
+      callback_,
       Run(AutofillClient::SaveAddressProfileOfferUserDecision::kAccepted,
-          profile));
+          profile_));
   delegate->Accept();
 }
 
 // Tests that the delegate returns Save Address profile strings when the
 // original_profile is supplied as nullptr to the delegate.
-TEST(AutofillSaveUpdateAddressProfileDelegateIOSTest, TestSaveAddressStrings) {
-  AutofillProfile profile = test::GetFullProfile();
-  base::MockCallback<AutofillClient::AddressProfileSavePromptCallback> callback;
-  auto delegate = std::make_unique<AutofillSaveUpdateAddressProfileDelegateIOS>(
-      profile, /*original_profile=*/nullptr, /*locale=*/"en-US",
-      callback.Get());
-
+TEST_F(AutofillSaveUpdateAddressProfileDelegateIOSTest,
+       TestSaveAddressStrings) {
+  std::unique_ptr<AutofillSaveUpdateAddressProfileDelegateIOS> delegate =
+      CreateAutofillSaveUpdateAddressProfileDelegate();
   EXPECT_EQ(delegate->GetMessageActionText(),
             l10n_util::GetStringUTF16(
                 IDS_IOS_AUTOFILL_SAVE_ADDRESS_MESSAGE_PRIMARY_ACTION));
@@ -49,16 +68,29 @@
             std::u16string(u"John H. Doe, 666 Erebus St."));
 }
 
+// Tests the message UI strings when the profile is saved in the Google Account.
+TEST_F(AutofillSaveUpdateAddressProfileDelegateIOSTest,
+       TestSaveAddressInAccountStrings) {
+  std::unique_ptr<AutofillSaveUpdateAddressProfileDelegateIOS> delegate =
+      CreateAutofillSaveUpdateAddressProfileDelegate(nullptr, u"test@gmail.com",
+                                                     true);
+  EXPECT_EQ(delegate->GetDescription(),
+            l10n_util::GetStringFUTF16(
+                IDS_IOS_AUTOFILL_SAVE_ADDRESS_IN_ACCOUNT_MESSAGE_SUBTITLE,
+                u"test@gmail.com"));
+  EXPECT_EQ(delegate->GetMessageText(),
+            l10n_util::GetStringUTF16(
+                IDS_IOS_AUTOFILL_SAVE_ADDRESS_IN_ACCOUNT_MESSAGE_TITLE));
+}
+
 // Tests that the delegate returns Update Address profile strings when the
 // original_profile is supplied to the delegate.
-TEST(AutofillSaveUpdateAddressProfileDelegateIOSTest,
-     TestUpdateAddressStrings) {
-  AutofillProfile profile = test::GetFullProfile();
+TEST_F(AutofillSaveUpdateAddressProfileDelegateIOSTest,
+       TestUpdateAddressStrings) {
   AutofillProfile original_profile = test::GetFullProfile();
   original_profile.SetInfo(NAME_FULL, u"John Doe", "en-US");
-  base::MockCallback<AutofillClient::AddressProfileSavePromptCallback> callback;
-  auto delegate = std::make_unique<AutofillSaveUpdateAddressProfileDelegateIOS>(
-      profile, &original_profile, /*locale=*/"en-US", callback.Get());
+  std::unique_ptr<AutofillSaveUpdateAddressProfileDelegateIOS> delegate =
+      CreateAutofillSaveUpdateAddressProfileDelegate(&original_profile);
 
   EXPECT_EQ(delegate->GetMessageActionText(),
             l10n_util::GetStringUTF16(
@@ -71,17 +103,14 @@
 }
 
 // Tests that the callback is run with kDeclined on destruction.
-TEST(AutofillSaveUpdateAddressProfileDelegateIOSTest,
-     TestCallbackOnDestruction) {
-  AutofillProfile profile = test::GetFullProfile();
-  base::MockCallback<AutofillClient::AddressProfileSavePromptCallback> callback;
-  auto delegate = std::make_unique<AutofillSaveUpdateAddressProfileDelegateIOS>(
-      profile, /*original_profile=*/nullptr, /*locale=*/"en-US",
-      callback.Get());
+TEST_F(AutofillSaveUpdateAddressProfileDelegateIOSTest,
+       TestCallbackOnDestruction) {
+  std::unique_ptr<AutofillSaveUpdateAddressProfileDelegateIOS> delegate =
+      CreateAutofillSaveUpdateAddressProfileDelegate();
 
   delegate->Cancel();
   EXPECT_CALL(
-      callback,
+      callback_,
       Run(AutofillClient::SaveAddressProfileOfferUserDecision::kDeclined,
           testing::_));
   // The callback should run in the destructor.
@@ -89,30 +118,26 @@
 }
 
 // Tests that the callback is run with kAccepted on Accept.
-TEST(AutofillSaveUpdateAddressProfileDelegateIOSTest, TestCallbackOnSave) {
-  AutofillProfile profile = test::GetFullProfile();
-  base::MockCallback<AutofillClient::AddressProfileSavePromptCallback> callback;
+TEST_F(AutofillSaveUpdateAddressProfileDelegateIOSTest, TestCallbackOnSave) {
+  std::unique_ptr<AutofillSaveUpdateAddressProfileDelegateIOS> delegate =
+      CreateAutofillSaveUpdateAddressProfileDelegate();
   EXPECT_CALL(
-      callback,
+      callback_,
       Run(AutofillClient::SaveAddressProfileOfferUserDecision::kAccepted,
           testing::_));
-  AutofillSaveUpdateAddressProfileDelegateIOS(
-      profile, /*original_profile=*/nullptr, /*locale=*/"en-US", callback.Get())
-      .Accept();
+  delegate->Accept();
 }
 
 // Tests that the callback is run with kEditAccepted on EditAccepted.
-TEST(AutofillSaveUpdateAddressProfileDelegateIOSTest,
-     TestCallbackOnEditAccepted) {
-  AutofillProfile profile = test::GetFullProfile();
-  base::MockCallback<AutofillClient::AddressProfileSavePromptCallback> callback;
+TEST_F(AutofillSaveUpdateAddressProfileDelegateIOSTest,
+       TestCallbackOnEditAccepted) {
+  std::unique_ptr<AutofillSaveUpdateAddressProfileDelegateIOS> delegate =
+      CreateAutofillSaveUpdateAddressProfileDelegate();
   EXPECT_CALL(
-      callback,
+      callback_,
       Run(AutofillClient::SaveAddressProfileOfferUserDecision::kEditAccepted,
           testing::_));
-  AutofillSaveUpdateAddressProfileDelegateIOS(
-      profile, /*original_profile=*/nullptr, /*locale=*/"en-US", callback.Get())
-      .EditAccepted();
+  delegate->EditAccepted();
 }
 
 }  // namespace autofill
diff --git a/components/autofill_strings.grdp b/components/autofill_strings.grdp
index 8da5d1d..a84c0fb 100644
--- a/components/autofill_strings.grdp
+++ b/components/autofill_strings.grdp
@@ -384,6 +384,9 @@
     <message name="IDS_IOS_AUTOFILL_SAVE_ADDRESS_MESSAGE_TITLE" desc="In Title Case: Title shown in the message that offers the user to proceed with saving a new address." meaning="In Title Case for iOS">
       Save Address?
     </message>
+   <message name="IDS_IOS_AUTOFILL_SAVE_ADDRESS_IN_ACCOUNT_MESSAGE_TITLE" desc="In Title Case: Title shown in the message that offers the user to proceed with saving a new address in the google account" meaning="In Title Case for iOS">
+      Save in Account?
+    </message>
     <message name="IDS_IOS_AUTOFILL_SAVE_ADDRESS_MESSAGE_PRIMARY_ACTION" desc="Label of the primary action button in the message that offers the user to proceed with saving a new address.">
       Save…
     </message>
@@ -399,6 +402,9 @@
     <message name="IDS_IOS_AUTOFILL_UPDATE_ADDRESS_PROMPT_TITLE" desc="In Title Case: Title shown at the top of modal dialog that offers the user to update an existing address." meaning="In Title Case for iOS">
       Update Address
     </message>
+    <message name="IDS_IOS_AUTOFILL_SAVE_ADDRESS_IN_ACCOUNT_MESSAGE_SUBTITLE" desc="Subtitle shown in the messages UI view when a new profile is asked to be saved in the Google Account">
+       In your Google Account, <ph name="USER_EMAIL">$1<ex>janedoe@google.com</ex></ph>
+    </message>
   </if>
   <!-- Used on Desktop: -->
   <if expr="not is_android and not is_ios">
diff --git a/components/autofill_strings_grdp/IDS_IOS_AUTOFILL_SAVE_ADDRESS_IN_ACCOUNT_MESSAGE_SUBTITLE.png.sha1 b/components/autofill_strings_grdp/IDS_IOS_AUTOFILL_SAVE_ADDRESS_IN_ACCOUNT_MESSAGE_SUBTITLE.png.sha1
new file mode 100644
index 0000000..3dcd351
--- /dev/null
+++ b/components/autofill_strings_grdp/IDS_IOS_AUTOFILL_SAVE_ADDRESS_IN_ACCOUNT_MESSAGE_SUBTITLE.png.sha1
@@ -0,0 +1 @@
+b4214a2043e947761426d150be7989ddd7910283
\ No newline at end of file
diff --git a/components/autofill_strings_grdp/IDS_IOS_AUTOFILL_SAVE_ADDRESS_IN_ACCOUNT_MESSAGE_TITLE.png.sha1 b/components/autofill_strings_grdp/IDS_IOS_AUTOFILL_SAVE_ADDRESS_IN_ACCOUNT_MESSAGE_TITLE.png.sha1
new file mode 100644
index 0000000..e501015
--- /dev/null
+++ b/components/autofill_strings_grdp/IDS_IOS_AUTOFILL_SAVE_ADDRESS_IN_ACCOUNT_MESSAGE_TITLE.png.sha1
@@ -0,0 +1 @@
+0d6ac3728d07d0edd746e3ca4d2c12f55f7b9a38
\ No newline at end of file
diff --git a/components/browser_ui/share/android/java/src/org/chromium/components/browser_ui/share/ShareHelper.java b/components/browser_ui/share/android/java/src/org/chromium/components/browser_ui/share/ShareHelper.java
index c64aeb2..ca5c3bcd 100644
--- a/components/browser_ui/share/android/java/src/org/chromium/components/browser_ui/share/ShareHelper.java
+++ b/components/browser_ui/share/android/java/src/org/chromium/components/browser_ui/share/ShareHelper.java
@@ -338,6 +338,13 @@
                 }
             } else {
                 intent.setType("text/plain");
+                // For text sharing, only set the preview title when preview image is provided. This
+                // is meant to avoid confusion about the content being shared.
+                if (params.getPreviewImageUri() != null) {
+                    intent.putExtra(Intent.EXTRA_TITLE, params.getTitle());
+                    intent.setClipData(ClipData.newRawUri("", params.getPreviewImageUri()));
+                    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+                }
             }
         }
 
diff --git a/components/browser_ui/share/android/java/src/org/chromium/components/browser_ui/share/ShareParams.java b/components/browser_ui/share/android/java/src/org/chromium/components/browser_ui/share/ShareParams.java
index 709381d..5504fd7b 100644
--- a/components/browser_ui/share/android/java/src/org/chromium/components/browser_ui/share/ShareParams.java
+++ b/components/browser_ui/share/android/java/src/org/chromium/components/browser_ui/share/ShareParams.java
@@ -54,6 +54,9 @@
      */
     private final Uri mSingleImageUri;
 
+    /** The Uri of the preview image (e.g. a favicon) of the text being shared. */
+    private Uri mPreviewImageUri;
+
     /** The boolean result of link to text generation. */
     private final Boolean mLinkToTextSuccessful;
 
@@ -72,8 +75,9 @@
     private ShareParams(WindowAndroid window, String title, String text, String textFormat,
             String url, @Nullable String fileContentType, @Nullable ArrayList<Uri> fileUris,
             @Nullable String imageAltText, @Nullable Uri offlineUri, @Nullable Uri singleImageUri,
-            @Nullable TargetChosenCallback callback, @Nullable Boolean linkToTextSuccessful,
-            @Nullable String previewText, String previewTextFormat) {
+            @Nullable Uri previewImageUri, @Nullable TargetChosenCallback callback,
+            @Nullable Boolean linkToTextSuccessful, @Nullable String previewText,
+            String previewTextFormat) {
         mWindow = window;
         mTitle = title;
         mText = text;
@@ -84,6 +88,7 @@
         mImageAltText = imageAltText;
         mOfflineUri = offlineUri;
         mSingleImageUri = singleImageUri;
+        mPreviewImageUri = previewImageUri;
         mCallback = callback;
         mLinkToTextSuccessful = linkToTextSuccessful;
         mPreviewText = previewText;
@@ -189,6 +194,19 @@
         return mSingleImageUri;
     }
 
+    /** @return The Uri of the preview image (e.g. a favicon) of the text being shared. */
+    @Nullable
+    public Uri getPreviewImageUri() {
+        return mPreviewImageUri;
+    }
+
+    /**
+     * @param uri The Uri of the preview image (e.g. a favicon) of the text being shared.
+     */
+    public void setPreviewImageUri(Uri uri) {
+        mPreviewImageUri = uri;
+    }
+
     /**
      * @return The callback to be called when user makes a choice.
      */
@@ -249,6 +267,7 @@
         private String mImageAltText;
         private Uri mOfflineUri;
         private Uri mSingleImageUri;
+        private Uri mPreviewImageUri;
         private TargetChosenCallback mCallback;
         private Boolean mLinkToTextSuccessful;
         private String mPreviewText;
@@ -329,6 +348,14 @@
         }
 
         /**
+         * Sets the Uri of the preview image of the text being shared.
+         */
+        public Builder setPreviewImageUri(@Nullable Uri previewImageUri) {
+            mPreviewImageUri = previewImageUri;
+            return this;
+        }
+
+        /**
          * Sets the callback to be called when user makes a choice.
          */
         public Builder setCallback(@Nullable TargetChosenCallback callback) {
@@ -359,8 +386,8 @@
                 mUrl = DomDistillerUrlUtils.getOriginalUrlFromDistillerUrl(mUrl);
             }
             return new ShareParams(mWindow, mTitle, mText, mTextFormat, mUrl, mFileContentType,
-                    mFileUris, mImageAltText, mOfflineUri, mSingleImageUri, mCallback,
-                    mLinkToTextSuccessful, mPreviewText, mPreviewTextFormat);
+                    mFileUris, mImageAltText, mOfflineUri, mSingleImageUri, mPreviewImageUri,
+                    mCallback, mLinkToTextSuccessful, mPreviewText, mPreviewTextFormat);
         }
     }
 
diff --git a/components/browsing_topics/browsing_topics_page_load_data_tracker_unittest.cc b/components/browsing_topics/browsing_topics_page_load_data_tracker_unittest.cc
index 89b9943..fab6f74 100644
--- a/components/browsing_topics/browsing_topics_page_load_data_tracker_unittest.cc
+++ b/components/browsing_topics/browsing_topics_page_load_data_tracker_unittest.cc
@@ -95,6 +95,7 @@
       policy.emplace_back(
           blink::mojom::PermissionsPolicyFeature::kBrowsingTopics,
           /*allowed_origins=*/std::vector<blink::OriginWithPossibleWildcards>(),
+          /*self_if_matches=*/absl::nullopt,
           /*matches_all_origins=*/false,
           /*matches_opaque_src=*/false);
     }
@@ -104,6 +105,7 @@
           blink::mojom::PermissionsPolicyFeature::
               kBrowsingTopicsBackwardCompatible,
           /*allowed_origins=*/std::vector<blink::OriginWithPossibleWildcards>(),
+          /*self_if_matches=*/absl::nullopt,
           /*matches_all_origins=*/false,
           /*matches_opaque_src=*/false);
     }
diff --git a/components/exo/wayland/OWNERS b/components/exo/wayland/OWNERS
new file mode 100644
index 0000000..460d074f
--- /dev/null
+++ b/components/exo/wayland/OWNERS
@@ -0,0 +1 @@
+per-file zcr_ui_controls*=max@igalia.com
diff --git a/components/feature_engagement/public/android/java/src/org/chromium/components/feature_engagement/EventConstants.java b/components/feature_engagement/public/android/java/src/org/chromium/components/feature_engagement/EventConstants.java
index 5efa74a..1bee101 100644
--- a/components/feature_engagement/public/android/java/src/org/chromium/components/feature_engagement/EventConstants.java
+++ b/components/feature_engagement/public/android/java/src/org/chromium/components/feature_engagement/EventConstants.java
@@ -265,6 +265,10 @@
     public static final String ADAPTIVE_TOOLBAR_CUSTOMIZATION_TRANSLATE_OPENED =
             "adaptive_toolbar_customization_translate_opened";
 
+    /** AdaptiveButtonInTopToolbarCustomization add to bookmarks events. */
+    public static final String ADAPTIVE_TOOLBAR_CUSTOMIZATION_ADD_TO_BOOKMARKS_OPENED =
+            "adaptive_toolbar_customization_add_to_bookmarks_opened";
+
     /** Open new incognito tab from app menu. */
     public static final String APP_MENU_NEW_INCOGNITO_TAB_CLICKED =
             "app_menu_new_incognito_tab_clicked";
diff --git a/components/feature_engagement/public/android/java/src/org/chromium/components/feature_engagement/FeatureConstants.java b/components/feature_engagement/public/android/java/src/org/chromium/components/feature_engagement/FeatureConstants.java
index 623a67c..10a1317 100644
--- a/components/feature_engagement/public/android/java/src/org/chromium/components/feature_engagement/FeatureConstants.java
+++ b/components/feature_engagement/public/android/java/src/org/chromium/components/feature_engagement/FeatureConstants.java
@@ -17,6 +17,7 @@
         FeatureConstants.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR_CUSTOMIZATION_SHARE_FEATURE,
         FeatureConstants.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR_CUSTOMIZATION_VOICE_SEARCH_FEATURE,
         FeatureConstants.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR_CUSTOMIZATION_TRANSLATE_FEATURE,
+        FeatureConstants.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR_CUSTOMIZATION_ADD_TO_BOOKMARKS_FEATURE,
         FeatureConstants.ADD_TO_HOMESCREEN_MESSAGE_FEATURE,
         FeatureConstants.AUTO_DARK_OPT_OUT_FEATURE,
         FeatureConstants.AUTO_DARK_USER_EDUCATION_MESSAGE_FEATURE,
@@ -93,6 +94,8 @@
             "IPH_AdaptiveButtonInTopToolbarCustomization_VoiceSearch";
     String ADAPTIVE_BUTTON_IN_TOP_TOOLBAR_CUSTOMIZATION_TRANSLATE_FEATURE =
             "IPH_AdaptiveButtonInTopToolbarCustomization_Translate";
+    String ADAPTIVE_BUTTON_IN_TOP_TOOLBAR_CUSTOMIZATION_ADD_TO_BOOKMARKS_FEATURE =
+            "IPH_AdaptiveButtonInTopToolbarCustomization_AddToBookmarks";
     String ADD_TO_HOMESCREEN_MESSAGE_FEATURE = "IPH_AddToHomescreenMessage";
     String AUTO_DARK_OPT_OUT_FEATURE = "IPH_AutoDarkOptOut";
     String AUTO_DARK_USER_EDUCATION_MESSAGE_FEATURE = "IPH_AutoDarkUserEducationMessage";
diff --git a/components/feature_engagement/public/feature_constants.cc b/components/feature_engagement/public/feature_constants.cc
index 5f8b47f..b7b8227 100644
--- a/components/feature_engagement/public/feature_constants.cc
+++ b/components/feature_engagement/public/feature_constants.cc
@@ -138,6 +138,9 @@
 BASE_FEATURE(kIPHAdaptiveButtonInTopToolbarCustomizationTranslateFeature,
              "IPH_AdaptiveButtonInTopToolbarCustomization_Translate",
              base::FEATURE_DISABLED_BY_DEFAULT);
+BASE_FEATURE(kIPHAdaptiveButtonInTopToolbarCustomizationAddToBookmarksFeature,
+             "IPH_AdaptiveButtonInTopToolbarCustomization_AddToBookmarks",
+             base::FEATURE_DISABLED_BY_DEFAULT);
 BASE_FEATURE(kIPHAddToHomescreenMessageFeature,
              "IPH_AddToHomescreenMessage",
              base::FEATURE_ENABLED_BY_DEFAULT);
diff --git a/components/feature_engagement/public/feature_constants.h b/components/feature_engagement/public/feature_constants.h
index 67a3e46..66d82bb 100644
--- a/components/feature_engagement/public/feature_constants.h
+++ b/components/feature_engagement/public/feature_constants.h
@@ -70,6 +70,8 @@
     kIPHAdaptiveButtonInTopToolbarCustomizationVoiceSearchFeature);
 BASE_DECLARE_FEATURE(
     kIPHAdaptiveButtonInTopToolbarCustomizationTranslateFeature);
+BASE_DECLARE_FEATURE(
+    kIPHAdaptiveButtonInTopToolbarCustomizationAddToBookmarksFeature);
 BASE_DECLARE_FEATURE(kIPHAddToHomescreenMessageFeature);
 BASE_DECLARE_FEATURE(kIPHAutoDarkOptOutFeature);
 BASE_DECLARE_FEATURE(kIPHAutoDarkUserEducationMessageFeature);
diff --git a/components/feature_engagement/public/feature_list.cc b/components/feature_engagement/public/feature_list.cc
index 49ca66d5..35e8306 100644
--- a/components/feature_engagement/public/feature_list.cc
+++ b/components/feature_engagement/public/feature_list.cc
@@ -19,6 +19,7 @@
     &kIPHAdaptiveButtonInTopToolbarCustomizationNewTabFeature,
     &kIPHAdaptiveButtonInTopToolbarCustomizationShareFeature,
     &kIPHAdaptiveButtonInTopToolbarCustomizationVoiceSearchFeature,
+    &kIPHAdaptiveButtonInTopToolbarCustomizationAddToBookmarksFeature,
     &kIPHAdaptiveButtonInTopToolbarCustomizationTranslateFeature,
     &kIPHAddToHomescreenMessageFeature,
     &kIPHAutoDarkOptOutFeature,
diff --git a/components/feature_engagement/public/feature_list.h b/components/feature_engagement/public/feature_list.h
index 367e1a06..259379b 100644
--- a/components/feature_engagement/public/feature_list.h
+++ b/components/feature_engagement/public/feature_list.h
@@ -55,6 +55,9 @@
 DEFINE_VARIATION_PARAM(
     kIPHAdaptiveButtonInTopToolbarCustomizationTranslateFeature,
     "IPH_AdaptiveButtonInTopToolbarCustomization_Translate");
+DEFINE_VARIATION_PARAM(
+    kIPHAdaptiveButtonInTopToolbarCustomizationAddToBookmarksFeature,
+    "IPH_AdaptiveButtonInTopToolbarCustomization_AddToBookmarks");
 DEFINE_VARIATION_PARAM(kIPHAddToHomescreenMessageFeature,
                        "IPH_AddToHomescreenMessage");
 DEFINE_VARIATION_PARAM(kIPHAutoDarkOptOutFeature, "IPH_AutoDarkOptOut");
@@ -327,6 +330,8 @@
             kIPHAdaptiveButtonInTopToolbarCustomizationVoiceSearchFeature),
         VARIATION_ENTRY(
             kIPHAdaptiveButtonInTopToolbarCustomizationTranslateFeature),
+        VARIATION_ENTRY(
+            kIPHAdaptiveButtonInTopToolbarCustomizationAddToBookmarksFeature),
         VARIATION_ENTRY(kIPHAddToHomescreenMessageFeature),
         VARIATION_ENTRY(kIPHAutoDarkOptOutFeature),
         VARIATION_ENTRY(kIPHAutoDarkUserEducationMessageFeature),
diff --git a/components/image_service/image_service.cc b/components/image_service/image_service.cc
index 59cbdab..1d837c06 100644
--- a/components/image_service/image_service.cc
+++ b/components/image_service/image_service.cc
@@ -149,10 +149,10 @@
     optimization_guide::NewOptimizationGuideDecider* opt_guide,
     syncer::SyncService* sync_service)
     : autocomplete_provider_client_(std::move(autocomplete_provider_client)),
-      personalized_data_collection_consent_helper_(
+      history_consent_throttle_(
           unified_consent::UrlKeyedDataCollectionConsentHelper::
               NewPersonalizedDataCollectionConsentHelper(sync_service)),
-      bookmarks_data_collection_consent_helper_(
+      bookmarks_consent_throttle_(
           unified_consent::UrlKeyedDataCollectionConsentHelper::
               NewPersonalizedBookmarksDataCollectionConsentHelper(
                   sync_service)) {
@@ -171,23 +171,6 @@
   return weak_factory_.GetWeakPtr();
 }
 
-bool ImageService::HasPermissionToFetchImage(mojom::ClientId client_id) const {
-  switch (client_id) {
-    case mojom::ClientId::Journeys:
-    case mojom::ClientId::JourneysSidePanel:
-    case mojom::ClientId::NtpQuests: {
-      return personalized_data_collection_consent_helper_ &&
-             personalized_data_collection_consent_helper_->IsEnabled();
-    }
-    case mojom::ClientId::NtpRealbox:
-      // TODO(b/244507194): Figure out consent story for NTP realbox case.
-      return false;
-    case mojom::ClientId::Bookmarks:
-      return bookmarks_data_collection_consent_helper_ &&
-             bookmarks_data_collection_consent_helper_->IsEnabled();
-  }
-}
-
 void ImageService::FetchImageFor(mojom::ClientId client_id,
                                  const GURL& page_url,
                                  const mojom::Options& options,
@@ -198,7 +181,36 @@
     return std::move(callback).Run(GURL());
   }
 
-  if (!HasPermissionToFetchImage(client_id)) {
+  GetConsentToFetchImage(
+      client_id,
+      base::BindOnce(&ImageService::OnConsentResult, weak_factory_.GetWeakPtr(),
+                     client_id, page_url, options, std::move(callback)));
+}
+
+void ImageService::GetConsentToFetchImage(
+    mojom::ClientId client_id,
+    base::OnceCallback<void(bool)> callback) {
+  switch (client_id) {
+    case mojom::ClientId::Journeys:
+    case mojom::ClientId::JourneysSidePanel:
+    case mojom::ClientId::NtpQuests: {
+      return history_consent_throttle_.EnqueueRequest(std::move(callback));
+    }
+    case mojom::ClientId::NtpRealbox:
+      // TODO(b/244507194): Figure out consent story for NTP realbox case.
+      return std::move(callback).Run(false);
+    case mojom::ClientId::Bookmarks: {
+      return bookmarks_consent_throttle_.EnqueueRequest(std::move(callback));
+    }
+  }
+}
+
+void ImageService::OnConsentResult(mojom::ClientId client_id,
+                                   const GURL& page_url,
+                                   const mojom::Options& options,
+                                   ResultCallback callback,
+                                   bool consent_is_enabled) {
+  if (!consent_is_enabled) {
     return std::move(callback).Run(GURL());
   }
 
diff --git a/components/image_service/image_service.h b/components/image_service/image_service.h
index 588d4bb4..8b096cd 100644
--- a/components/image_service/image_service.h
+++ b/components/image_service/image_service.h
@@ -15,7 +15,7 @@
 #include "components/omnibox/browser/autocomplete_provider_client.h"
 #include "components/optimization_guide/core/optimization_guide_decision.h"
 #include "components/sync/driver/sync_service.h"
-#include "components/unified_consent/url_keyed_data_collection_consent_helper.h"
+#include "components/unified_consent/consent_throttle.h"
 
 namespace optimization_guide {
 class NewOptimizationGuideDecider;
@@ -42,9 +42,6 @@
   // object whose lifetime might exceed the service.
   base::WeakPtr<ImageService> GetWeakPtr();
 
-  // Returns true if `client_id` has permission to fetch images.
-  bool HasPermissionToFetchImage(mojom::ClientId client_id) const;
-
   // Fetches an image appropriate for `page_url`, returning the result
   // asynchronously to `callback`. The callback is always invoked. If there are
   // no images available, it is invoked with an empty GURL result.
@@ -53,9 +50,22 @@
                      const mojom::Options& options,
                      ResultCallback callback);
 
+  // Asynchronously returns whether `client_id` has consent to fetch an image.
+  // Public for testing purposes only.
+  void GetConsentToFetchImage(mojom::ClientId client_id,
+                              base::OnceCallback<void(bool)> callback);
+
  private:
   class SuggestEntityImageURLFetcher;
 
+  // Callback to `GetConsentToFetchImage`, proceeds to call the appropriate
+  // backend.
+  void OnConsentResult(mojom::ClientId client_id,
+                       const GURL& page_url,
+                       const mojom::Options& options,
+                       ResultCallback callback,
+                       bool consent_is_enabled);
+
   // Fetches an image from Suggest appropriate for `search_query` and
   // `entity_id`, returning the result asynchronously to `callback`.
   void FetchSuggestImage(const std::u16string& search_query,
@@ -90,13 +100,11 @@
   // Will be left as nullptr if the OptimizationGuide feature is disabled.
   raw_ptr<optimization_guide::NewOptimizationGuideDecider> opt_guide_ = nullptr;
 
-  // The History consent filter, used for most clients.
-  std::unique_ptr<unified_consent::UrlKeyedDataCollectionConsentHelper>
-      personalized_data_collection_consent_helper_;
+  // The History consent throttle, used for most clients.
+  unified_consent::ConsentThrottle history_consent_throttle_;
 
-  // The Bookmarks consent filter.
-  std::unique_ptr<unified_consent::UrlKeyedDataCollectionConsentHelper>
-      bookmarks_data_collection_consent_helper_;
+  // The Bookmarks consent throttle.
+  unified_consent::ConsentThrottle bookmarks_consent_throttle_;
 
   base::WeakPtrFactory<ImageService> weak_factory_{this};
 };
diff --git a/components/image_service/image_service_unittest.cc b/components/image_service/image_service_unittest.cc
index 2ed4abb..2afc22a 100644
--- a/components/image_service/image_service_unittest.cc
+++ b/components/image_service/image_service_unittest.cc
@@ -3,10 +3,14 @@
 // found in the LICENSE file.
 
 #include "components/image_service/image_service.h"
+
 #include <memory>
 
+#include "base/run_loop.h"
+#include "base/test/bind.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
+#include "base/test/task_environment.h"
 #include "build/build_config.h"
 #include "components/image_service/features.h"
 #include "components/image_service/metrics_util.h"
@@ -33,6 +37,8 @@
       const base::flat_set<proto::OptimizationType>& optimization_types,
       proto::RequestContext request_context,
       OnDemandOptimizationGuideDecisionRepeatingCallback callback) override {
+    requests_received_++;
+
     // For this test, we just want to store the parameters which were used in
     // the call, and the test will manually send a response to `callback`.
     on_demand_call_urls_ = urls;
@@ -41,6 +47,8 @@
     on_demand_call_callback_ = std::move(callback);
   }
 
+  size_t requests_received_ = 0;
+
   std::vector<GURL> on_demand_call_urls_;
   base::flat_set<proto::OptimizationType> on_demand_call_optimization_types_;
   proto::RequestContext on_demand_call_request_context_;
@@ -69,11 +77,25 @@
         nullptr, test_opt_guide_.get(), test_sync_service_.get());
   }
 
+  bool GetConsentToFetchImageAwaitResult(mojom::ClientId client_id) {
+    bool out_consent = false;
+    base::RunLoop loop;
+    image_service_->GetConsentToFetchImage(
+        client_id, base::BindLambdaForTesting([&](bool result) {
+          out_consent = result;
+          loop.Quit();
+        }));
+    loop.Run();
+    return out_consent;
+  }
+
   ImageServiceTest(const ImageServiceTest&) = delete;
   ImageServiceTest& operator=(const ImageServiceTest&) = delete;
 
  protected:
   base::test::ScopedFeatureList scoped_feature_list_;
+  base::test::SingleThreadTaskEnvironment task_environment{
+      base::test::SingleThreadTaskEnvironment::TimeSource::MOCK_TIME};
 
   std::unique_ptr<optimization_guide::ImageServiceTestOptGuide> test_opt_guide_;
   std::unique_ptr<syncer::TestSyncService> test_sync_service_;
@@ -88,28 +110,29 @@
   *out_image_url = image_url;
 }
 
+void AppendResponse(std::vector<GURL>* responses, const GURL& image_url) {
+  DCHECK(responses);
+  responses->push_back(image_url);
+}
+
 TEST_F(ImageServiceTest, RegisteredSalientImageType) {
   ASSERT_EQ(test_opt_guide_->registered_optimization_types().size(), 1U);
   EXPECT_EQ(test_opt_guide_->registered_optimization_types()[0],
             optimization_guide::proto::SALIENT_IMAGE);
 }
 
-TEST_F(ImageServiceTest, HasPermissionToFetchImage) {
+TEST_F(ImageServiceTest, GetConsentToFetchImage) {
   test_sync_service_->GetUserSettings()->SetSelectedTypes(
       /*sync_everything=*/false,
       /*types=*/syncer::UserSelectableTypeSet());
   test_sync_service_->FireStateChanged();
 
+  EXPECT_FALSE(GetConsentToFetchImageAwaitResult(mojom::ClientId::Journeys));
   EXPECT_FALSE(
-      image_service_->HasPermissionToFetchImage(mojom::ClientId::Journeys));
-  EXPECT_FALSE(image_service_->HasPermissionToFetchImage(
-      mojom::ClientId::JourneysSidePanel));
-  EXPECT_FALSE(
-      image_service_->HasPermissionToFetchImage(mojom::ClientId::NtpRealbox));
-  EXPECT_FALSE(
-      image_service_->HasPermissionToFetchImage(mojom::ClientId::NtpQuests));
-  EXPECT_FALSE(
-      image_service_->HasPermissionToFetchImage(mojom::ClientId::Bookmarks));
+      GetConsentToFetchImageAwaitResult(mojom::ClientId::JourneysSidePanel));
+  EXPECT_FALSE(GetConsentToFetchImageAwaitResult(mojom::ClientId::NtpRealbox));
+  EXPECT_FALSE(GetConsentToFetchImageAwaitResult(mojom::ClientId::NtpQuests));
+  EXPECT_FALSE(GetConsentToFetchImageAwaitResult(mojom::ClientId::Bookmarks));
 
   test_sync_service_->GetUserSettings()->SetSelectedTypes(
       /*sync_everything=*/false,
@@ -117,16 +140,62 @@
           syncer::UserSelectableType::kHistory));
   test_sync_service_->FireStateChanged();
 
+  EXPECT_TRUE(GetConsentToFetchImageAwaitResult(mojom::ClientId::Journeys));
   EXPECT_TRUE(
-      image_service_->HasPermissionToFetchImage(mojom::ClientId::Journeys));
-  EXPECT_TRUE(image_service_->HasPermissionToFetchImage(
-      mojom::ClientId::JourneysSidePanel));
-  EXPECT_FALSE(
-      image_service_->HasPermissionToFetchImage(mojom::ClientId::NtpRealbox));
-  EXPECT_TRUE(
-      image_service_->HasPermissionToFetchImage(mojom::ClientId::NtpQuests));
-  EXPECT_FALSE(
-      image_service_->HasPermissionToFetchImage(mojom::ClientId::Bookmarks));
+      GetConsentToFetchImageAwaitResult(mojom::ClientId::JourneysSidePanel));
+  EXPECT_FALSE(GetConsentToFetchImageAwaitResult(mojom::ClientId::NtpRealbox));
+  EXPECT_TRUE(GetConsentToFetchImageAwaitResult(mojom::ClientId::NtpQuests));
+  EXPECT_FALSE(GetConsentToFetchImageAwaitResult(mojom::ClientId::Bookmarks));
+}
+
+TEST_F(ImageServiceTest, SyncInitialization) {
+  // Put Sync into the initializing state.
+  test_sync_service_->SetTransportState(
+      syncer::SyncService::TransportState::INITIALIZING);
+  test_sync_service_->GetUserSettings()->SetSelectedTypes(
+      /*sync_everything=*/false,
+      /*types=*/syncer::UserSelectableTypeSet(
+          syncer::UserSelectableType::kHistory));
+  test_sync_service_->FireStateChanged();
+
+  mojom::Options options;
+  options.suggest_images = false;
+  options.optimization_guide_images = true;
+
+  std::vector<GURL> responses;
+  image_service_->FetchImageFor(mojom::ClientId::Journeys,
+                                GURL("https://page-url.com"), options,
+                                base::BindOnce(&AppendResponse, &responses));
+  EXPECT_EQ(test_opt_guide_->requests_received_, 0U)
+      << "Expect no immediate requests, because the consent should be "
+         "throttling it.";
+  EXPECT_TRUE(responses.empty());
+
+  task_environment.FastForwardBy(base::Seconds(10));
+  EXPECT_EQ(test_opt_guide_->requests_received_, 0U)
+      << "After 10 seconds, the throttle should have killed the request, never "
+         "passing it to the backend.";
+  ASSERT_EQ(responses.size(), 1U);
+  EXPECT_EQ(responses[0], GURL());
+
+  // Now send another request.
+  image_service_->FetchImageFor(mojom::ClientId::Journeys,
+                                GURL("https://page-url.com"), options,
+                                base::BindOnce(&AppendResponse, &responses));
+  task_environment.FastForwardBy(base::Seconds(3));
+  EXPECT_EQ(test_opt_guide_->requests_received_, 0U) << "Still throttled.";
+
+  // Now set the test sync service to active.
+  test_sync_service_->SetTransportState(
+      syncer::SyncService::TransportState::ACTIVE);
+  test_sync_service_->FireStateChanged();
+  EXPECT_EQ(test_opt_guide_->requests_received_, 1U)
+      << "The test backend should immediately get the request after Sync "
+         "activates, and the consent throttle unthrottles.";
+
+  // This test only covers sync unthrottling, so we don't care about fulfilling
+  // the actual request. That's covered by
+  // OptimizationGuideSalientImagesEndToEnd.
 }
 
 TEST_F(ImageServiceTest, OptimizationGuideSalientImagesEndToEnd) {
diff --git a/components/omnibox/browser/autocomplete_controller.cc b/components/omnibox/browser/autocomplete_controller.cc
index 5e6c6abc..c298c4310 100644
--- a/components/omnibox/browser/autocomplete_controller.cc
+++ b/components/omnibox/browser/autocomplete_controller.cc
@@ -474,6 +474,11 @@
   expire_timer_.Stop();
   stop_timer_.Stop();
 
+  // Cancel any pending requests to the scoring model and invalidate the WeakPtr
+  // to prevent its callbacks from being called.
+  scoring_model_task_tracker_.TryCancelAll();
+  scoring_model_weak_ptr_ = nullptr;
+
   // Start the new query.
   sync_pass_done_ = false;
   // Use `start_time` rather than `metrics.start_time_` for
@@ -1367,6 +1372,11 @@
     provider->Stop(clear_result, due_to_user_inactivity);
   }
 
+  // Cancel any pending requests to the scoring model and invalidate the WeakPtr
+  // to prevent its callbacks from being called.
+  scoring_model_task_tracker_.TryCancelAll();
+  scoring_model_weak_ptr_ = nullptr;
+
   expire_timer_.Stop();
   stop_timer_.Stop();
   done_ = true;
@@ -1522,11 +1532,14 @@
     return false;
   }
 
+  // Needed because the model is not owned and `this` may not longer be alive.
+  scoring_model_weak_ptr_ = weak_ptr_factory_.GetWeakPtr();
+
   auto barrier_callback = base::BarrierCallback<AutocompleteMatch>(
       result_.size(),
       base::BindOnce(
           &AutocompleteController::OnUrlScoringModelDoneForAllMatches,
-          weak_ptr_factory_.GetWeakPtr(), input_, last_default_match,
+          scoring_model_weak_ptr_, input_, last_default_match,
           last_default_associated_keyword, force_notify_default_match_changed));
 
   for (const auto& match : result_.matches_) {
@@ -1540,10 +1553,9 @@
     }
 
     scoring_model_service->ScoreAutocompleteUrlMatch(
-        match.scoring_signals,
+        &scoring_model_task_tracker_, match.scoring_signals,
         base::BindOnce(&AutocompleteController::OnUrlScoringModelDone,
-                       weak_ptr_factory_.GetWeakPtr(), barrier_callback,
-                       match));
+                       scoring_model_weak_ptr_, barrier_callback, match));
   }
 
   return true;
diff --git a/components/omnibox/browser/autocomplete_controller.h b/components/omnibox/browser/autocomplete_controller.h
index 4ec2c4a..9a80064 100644
--- a/components/omnibox/browser/autocomplete_controller.h
+++ b/components/omnibox/browser/autocomplete_controller.h
@@ -15,6 +15,7 @@
 #include "base/memory/ref_counted.h"
 #include "base/observer_list.h"
 #include "base/observer_list_types.h"
+#include "base/task/cancelable_task_tracker.h"
 #include "base/time/time.h"
 #include "base/timer/timer.h"
 #include "base/trace_event/memory_dump_provider.h"
@@ -506,6 +507,13 @@
 
   raw_ptr<TemplateURLService> template_url_service_;
 
+  // Combined, used to cancel model execution requests sent to
+  // `AutocompleteScoringModelService` and to prevent its callbacks from being
+  // called `base::CancelableTaskTracker` alone is insufficient because it
+  // cannot cancel tasks that have already started.
+  base::CancelableTaskTracker scoring_model_task_tracker_;
+  base::WeakPtr<AutocompleteController> scoring_model_weak_ptr_;
+
   base::WeakPtrFactory<AutocompleteController> weak_ptr_factory_{this};
 };
 
diff --git a/components/omnibox/browser/autocomplete_scoring_model_service.cc b/components/omnibox/browser/autocomplete_scoring_model_service.cc
index 61aa167..5451337 100644
--- a/components/omnibox/browser/autocomplete_scoring_model_service.cc
+++ b/components/omnibox/browser/autocomplete_scoring_model_service.cc
@@ -35,6 +35,7 @@
 AutocompleteScoringModelService::~AutocompleteScoringModelService() = default;
 
 void AutocompleteScoringModelService::ScoreAutocompleteUrlMatch(
+    base::CancelableTaskTracker* tracker,
     const metrics::OmniboxEventProto::Suggestion::ScoringSignals&
         scoring_signals,
     ResultCallback result_callback) {
@@ -51,6 +52,7 @@
   }
 
   url_scoring_model_handler_->ExecuteModelWithInput(
+      tracker,
       base::BindOnce(&AutocompleteScoringModelService::ProcessModelOutput,
                      base::Unretained(this), std::move(result_callback)),
       *input_signals);
diff --git a/components/omnibox/browser/autocomplete_scoring_model_service.h b/components/omnibox/browser/autocomplete_scoring_model_service.h
index c71dd1b..8ea722a 100644
--- a/components/omnibox/browser/autocomplete_scoring_model_service.h
+++ b/components/omnibox/browser/autocomplete_scoring_model_service.h
@@ -9,6 +9,7 @@
 
 #include "base/functional/callback.h"
 #include "base/memory/scoped_refptr.h"
+#include "base/task/cancelable_task_tracker.h"
 #include "base/task/sequenced_task_runner.h"
 #include "components/keyed_service/core/keyed_service.h"
 #include "components/omnibox/browser/autocomplete_scoring_model_executor.h"
@@ -36,6 +37,7 @@
   // Invokes the model to score the given `scoring_signals` and calls
   // `result_callback` with an optional relevance score generated by the model.
   void ScoreAutocompleteUrlMatch(
+      base::CancelableTaskTracker* tracker,
       const metrics::OmniboxEventProto::Suggestion::ScoringSignals&
           scoring_signals,
       ResultCallback result_callback);
diff --git a/components/password_manager/content/browser/password_change_success_tracker_factory.cc b/components/password_manager/content/browser/password_change_success_tracker_factory.cc
index e7f909b6..3960f15 100644
--- a/components/password_manager/content/browser/password_change_success_tracker_factory.cc
+++ b/components/password_manager/content/browser/password_change_success_tracker_factory.cc
@@ -46,4 +46,10 @@
   return tracker;
 }
 
+content::BrowserContext*
+PasswordChangeSuccessTrackerFactory::GetBrowserContextToUse(
+    content::BrowserContext* context) const {
+  return context;
+}
+
 }  // namespace password_manager
diff --git a/components/password_manager/content/browser/password_change_success_tracker_factory.h b/components/password_manager/content/browser/password_change_success_tracker_factory.h
index cb49ab3f..f2f35693 100644
--- a/components/password_manager/content/browser/password_change_success_tracker_factory.h
+++ b/components/password_manager/content/browser/password_change_success_tracker_factory.h
@@ -28,6 +28,8 @@
  private:
   KeyedService* BuildServiceInstanceFor(
       content::BrowserContext* browser_context) const override;
+  content::BrowserContext* GetBrowserContextToUse(
+      content::BrowserContext* context) const override;
 };
 }  // namespace password_manager
 
diff --git a/components/password_manager/core/browser/password_manager.cc b/components/password_manager/core/browser/password_manager.cc
index f8c711a1..e267cdd 100644
--- a/components/password_manager/core/browser/password_manager.cc
+++ b/components/password_manager/core/browser/password_manager.cc
@@ -343,6 +343,8 @@
   registry->RegisterBooleanPref(prefs::kPasswordsGroupingInfoRequested, false);
 #if BUILDFLAG(IS_IOS)
   registry->RegisterBooleanPref(prefs::kAccountStorageNoticeShown, false);
+  registry->RegisterIntegerPref(prefs::kAccountStorageNewFeatureIconImpressions,
+                                0);
 #endif
 #if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)  // Desktop
   registry->RegisterListPref(prefs::kPasswordManagerPromoCardsList);
diff --git a/components/password_manager/core/common/password_manager_features.h b/components/password_manager/core/common/password_manager_features.h
index 1582334..691db73 100644
--- a/components/password_manager/core/common/password_manager_features.h
+++ b/components/password_manager/core/common/password_manager_features.h
@@ -30,7 +30,13 @@
 #endif
 BASE_DECLARE_FEATURE(kBiometricTouchToFill);
 BASE_DECLARE_FEATURE(kEnableOverwritingPlaceholderUsernames);
+
 BASE_DECLARE_FEATURE(kEnablePasswordsAccountStorage);
+inline constexpr base::FeatureParam<int>
+    kMaxAccountStorageNewFeatureIconImpressions = {
+        &kEnablePasswordsAccountStorage,
+        "max_account_storage_new_feature_icon_impressions", 5};
+
 BASE_DECLARE_FEATURE(kEnablePasswordGenerationForClearTextFields);
 BASE_DECLARE_FEATURE(kEnablePasswordManagerWithinFencedFrame);
 BASE_DECLARE_FEATURE(kFillingAcrossAffiliatedWebsites);
diff --git a/components/password_manager/core/common/password_manager_pref_names.cc b/components/password_manager/core/common/password_manager_pref_names.cc
index b6bf7f42..c502f34a 100644
--- a/components/password_manager/core/common/password_manager_pref_names.cc
+++ b/components/password_manager/core/common/password_manager_pref_names.cc
@@ -127,6 +127,9 @@
 #if BUILDFLAG(IS_IOS)
 const char kAccountStorageNoticeShown[] =
     "password_manager.account_storage_notice_shown";
+
+const char kAccountStorageNewFeatureIconImpressions[] =
+    "password_manager.account_storage_new_feature_icon_impressions";
 #endif  // BUILDFLAG(IS_IOS)
 
 #if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)  // Desktop
diff --git a/components/password_manager/core/common/password_manager_pref_names.h b/components/password_manager/core/common/password_manager_pref_names.h
index 35b80f2c..3713e4f 100644
--- a/components/password_manager/core/common/password_manager_pref_names.h
+++ b/components/password_manager/core/common/password_manager_pref_names.h
@@ -232,6 +232,10 @@
 // Boolean pref indicating if the one-time notice for account storage was shown.
 // The notice informs passwords will start being saved to the signed-in account.
 extern const char kAccountStorageNoticeShown[];
+
+// Integer value indicating the number of times the "new feature icon" was
+// displayed with the account storage opt-out toggle.
+extern const char kAccountStorageNewFeatureIconImpressions[];
 #endif  // BUILDFLAG(IS_IOS)
 
 #if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)  // Desktop
diff --git a/components/permissions/permission_manager_unittest.cc b/components/permissions/permission_manager_unittest.cc
index e830876a..ca6cea1c 100644
--- a/components/permissions/permission_manager_unittest.cc
+++ b/components/permissions/permission_manager_unittest.cc
@@ -255,7 +255,9 @@
       parsed_origins.emplace_back(url::Origin::Create(GURL(origin)),
                                   /*has_subdomain_wildcard=*/false);
     navigation->SetPermissionsPolicyHeader(
-        {{feature, parsed_origins, false, false}});
+        {{feature, parsed_origins, /*self_if_matches=*/absl::nullopt,
+          /*matches_all_origins=*/false,
+          /*matches_opaque_src=*/false}});
     navigation->Commit();
     *rfh = navigation->GetFinalRenderFrameHost();
   }
@@ -271,7 +273,9 @@
                                   blink::OriginWithPossibleWildcards(
                                       url::Origin::Create(origin),
                                       /*has_subdomain_wildcard=*/false)},
-                              false, false});
+                              /*self_if_matches=*/absl::nullopt,
+                              /*matches_all_origins=*/false,
+                              /*matches_opaque_src=*/false});
     }
     content::RenderFrameHost* result =
         content::RenderFrameHostTester::For(parent)->AppendChildWithPolicy(
diff --git a/components/permissions/permission_uma_util_unittest.cc b/components/permissions/permission_uma_util_unittest.cc
index bfd4063..aaeb7cc7 100644
--- a/components/permissions/permission_uma_util_unittest.cc
+++ b/components/permissions/permission_uma_util_unittest.cc
@@ -50,7 +50,8 @@
     allow_origins.emplace_back(url::Origin::Create(GURL(origin)),
                                /*has_subdomain_wildcard=*/false);
   }
-  return {{feature, allow_origins, matches_all_origins,
+  return {{feature, allow_origins, /*self_if_matches=*/absl::nullopt,
+           matches_all_origins,
            /*matches_opaque_src*/ false}};
 }
 
diff --git a/components/policy/core/common/policy_pref_names.cc b/components/policy/core/common/policy_pref_names.cc
index c7c6f64..998e9237 100644
--- a/components/policy/core/common/policy_pref_names.cc
+++ b/components/policy/core/common/policy_pref_names.cc
@@ -148,5 +148,11 @@
 // from the New Tab Page and app launcher.
 const char kHideWebStoreIcon[] = "hide_web_store_icon";
 
+// Enum that specifies whether Incognito mode is:
+// 0 - Enabled. Default behaviour. Default mode is available on demand.
+// 1 - Disabled. User cannot browse pages in Incognito mode.
+// 2 - Forced. All pages/sessions are forced into Incognito.
+const char kIncognitoModeAvailability[] = "incognito.mode_availability";
+
 }  // namespace policy_prefs
 }  // namespace policy
diff --git a/components/policy/core/common/policy_pref_names.h b/components/policy/core/common/policy_pref_names.h
index af3c6339..47c3ed2 100644
--- a/components/policy/core/common/policy_pref_names.h
+++ b/components/policy/core/common/policy_pref_names.h
@@ -52,6 +52,7 @@
 extern const char kForceGoogleSafeSearch[];
 extern const char kForceYouTubeRestrict[];
 extern const char kHideWebStoreIcon[];
+extern const char kIncognitoModeAvailability[];
 
 }  // namespace policy_prefs
 }  // namespace policy
diff --git a/components/reading_list/core/dual_reading_list_model.cc b/components/reading_list/core/dual_reading_list_model.cc
index d055e57c..f46bbf60 100644
--- a/components/reading_list/core/dual_reading_list_model.cc
+++ b/components/reading_list/core/dual_reading_list_model.cc
@@ -126,8 +126,34 @@
 
 void DualReadingListModel::MarkAllSeen() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  // TODO(crbug.com/1402196): Implement.
-  NOTIMPLEMENTED();
+  DCHECK(loaded());
+
+  std::unique_ptr<DualReadingListModel::ScopedReadingListBatchUpdate>
+      scoped_model_batch_updates = BeginBatchUpdates();
+
+  for (const auto& url : GetKeys()) {
+    scoped_refptr<const ReadingListEntry> entry = GetEntryByURL(url);
+    const bool notify_observers = !entry->HasBeenSeen();
+    if (notify_observers) {
+      NotifyObserversWithWillUpdateEntry(url);
+      UpdateEntryStateCountersOnEntryRemoval(*entry);
+    }
+
+    {
+      base::AutoReset<bool> auto_reset_suppress_observer_notifications(
+          &suppress_observer_notifications_, true);
+      local_or_syncable_model_->MarkEntrySeenIfExists(url);
+      account_model_->MarkEntrySeenIfExists(url);
+    }
+
+    if (notify_observers) {
+      UpdateEntryStateCountersOnEntryInsertion(*GetEntryByURL(url));
+      NotifyObserversWithDidUpdateEntry(url);
+      NotifyObserversWithDidApplyChanges();
+    }
+  }
+
+  DCHECK_EQ(unseen_entry_count_, 0ul);
 }
 
 bool DualReadingListModel::DeleteAllEntries() {
diff --git a/components/reading_list/core/dual_reading_list_model_unittest.cc b/components/reading_list/core/dual_reading_list_model_unittest.cc
index 904b3b9..d8c3abd 100644
--- a/components/reading_list/core/dual_reading_list_model_unittest.cc
+++ b/components/reading_list/core/dual_reading_list_model_unittest.cc
@@ -307,6 +307,74 @@
   EXPECT_EQ(2ul, dual_model_->size());
 }
 
+TEST_F(DualReadingListModelTest, MarkAllSeen) {
+  const GURL kLocalUrl("http://local_url.com/");
+  const GURL kAccountUrl("http://account_url.com/");
+  const GURL kCommonUrl("http://common_url.com/");
+
+  ASSERT_TRUE(ResetStorageAndTriggerLoadCompletion(
+      /*initial_local_or_syncable_entries_builders=*/
+      {TestEntryBuilder(kLocalUrl, clock_.Now()),
+       TestEntryBuilder(kCommonUrl, clock_.Now())},
+      /*initial_account_entries_builders=*/{
+          TestEntryBuilder(kAccountUrl, clock_.Now()),
+          TestEntryBuilder(kCommonUrl, clock_.Now())}));
+  ASSERT_TRUE(dual_model_->loaded());
+
+  ASSERT_EQ(dual_model_->GetStorageStateForURLForTesting(kLocalUrl),
+            StorageStateForTesting::kExistsInLocalOrSyncableModelOnly);
+  ASSERT_EQ(dual_model_->GetStorageStateForURLForTesting(kAccountUrl),
+            StorageStateForTesting::kExistsInAccountModelOnly);
+  ASSERT_EQ(dual_model_->GetStorageStateForURLForTesting(kCommonUrl),
+            StorageStateForTesting::kExistsInBothModels);
+
+  ASSERT_FALSE(dual_model_->GetEntryByURL(kLocalUrl)->HasBeenSeen());
+  ASSERT_FALSE(dual_model_->GetEntryByURL(kAccountUrl)->HasBeenSeen());
+  ASSERT_FALSE(dual_model_->GetEntryByURL(kCommonUrl)->HasBeenSeen());
+  ASSERT_FALSE(dual_model_->GetEntryByURL(kLocalUrl)->IsRead());
+  ASSERT_FALSE(dual_model_->GetEntryByURL(kAccountUrl)->IsRead());
+  ASSERT_FALSE(dual_model_->GetEntryByURL(kCommonUrl)->IsRead());
+
+  {
+    testing::InSequence seq1;
+    EXPECT_CALL(observer_,
+                ReadingListWillUpdateEntry(dual_model_.get(), kLocalUrl));
+    EXPECT_CALL(observer_,
+                ReadingListDidUpdateEntry(dual_model_.get(), kLocalUrl));
+    EXPECT_CALL(observer_, ReadingListDidApplyChanges(dual_model_.get()))
+        .RetiresOnSaturation();
+  }
+
+  {
+    testing::InSequence seq2;
+    EXPECT_CALL(observer_,
+                ReadingListWillUpdateEntry(dual_model_.get(), kAccountUrl));
+    EXPECT_CALL(observer_,
+                ReadingListDidUpdateEntry(dual_model_.get(), kAccountUrl));
+    EXPECT_CALL(observer_, ReadingListDidApplyChanges(dual_model_.get()))
+        .RetiresOnSaturation();
+  }
+
+  {
+    testing::InSequence seq3;
+    EXPECT_CALL(observer_,
+                ReadingListWillUpdateEntry(dual_model_.get(), kCommonUrl));
+    EXPECT_CALL(observer_,
+                ReadingListDidUpdateEntry(dual_model_.get(), kCommonUrl));
+    EXPECT_CALL(observer_, ReadingListDidApplyChanges(dual_model_.get()))
+        .RetiresOnSaturation();
+  }
+
+  dual_model_->MarkAllSeen();
+
+  EXPECT_TRUE(dual_model_->GetEntryByURL(kLocalUrl)->HasBeenSeen());
+  EXPECT_TRUE(dual_model_->GetEntryByURL(kAccountUrl)->HasBeenSeen());
+  EXPECT_TRUE(dual_model_->GetEntryByURL(kCommonUrl)->HasBeenSeen());
+  EXPECT_FALSE(dual_model_->GetEntryByURL(kLocalUrl)->IsRead());
+  EXPECT_FALSE(dual_model_->GetEntryByURL(kAccountUrl)->IsRead());
+  EXPECT_FALSE(dual_model_->GetEntryByURL(kCommonUrl)->IsRead());
+}
+
 TEST_F(DualReadingListModelTest, BatchUpdates) {
   ASSERT_TRUE(ResetStorageAndTriggerLoadCompletion());
   EXPECT_CALL(observer_, ReadingListModelBeganBatchUpdates(dual_model_.get()));
@@ -1884,6 +1952,63 @@
   EXPECT_EQ(5ul, dual_model_->unread_size());
 }
 
+TEST_F(DualReadingListModelTest, ShouldMaintainCountsWhenMarkAllSeen) {
+  const GURL kUnseenLocalUrl("http://unseen_local_url.com/");
+  const GURL kUnreadLocalUrl("http://unread_local_url.com/");
+  const GURL kReadLocalUrl("http://read_local_url.com/");
+  const GURL kUnseenAccountUrl("http://unseen_account_url.com/");
+  const GURL kUnreadAccountUrl("http://unread_account_url.com/");
+  const GURL kReadAccountUrl("http://read_account_url.com/");
+  const GURL kUnseenCommonUrl("http://unseen_common_url.com/");
+  const GURL kUnreadCommonUrl("http://unread_common_url.com/");
+  const GURL kReadCommonUrl("http://read_common_url.com/");
+
+  ASSERT_TRUE(ResetStorageAndTriggerLoadCompletion(
+      /*initial_local_or_syncable_entries_builders=*/
+      {TestEntryBuilder(kUnseenLocalUrl, clock_.Now()),
+       TestEntryBuilder(kUnreadLocalUrl, clock_.Now()).SetRead(false),
+       TestEntryBuilder(kReadLocalUrl, clock_.Now()).SetRead(),
+       TestEntryBuilder(kUnseenCommonUrl, clock_.Now()),
+       TestEntryBuilder(kUnreadCommonUrl, clock_.Now()).SetRead(false),
+       TestEntryBuilder(kReadCommonUrl, clock_.Now())},
+      /*initial_account_entries_builders=*/{
+          TestEntryBuilder(kUnseenAccountUrl, clock_.Now()),
+          TestEntryBuilder(kUnreadAccountUrl, clock_.Now()).SetRead(false),
+          TestEntryBuilder(kReadAccountUrl, clock_.Now()).SetRead(),
+          TestEntryBuilder(kUnseenCommonUrl, clock_.Now()),
+          TestEntryBuilder(kUnreadCommonUrl, clock_.Now()).SetRead(false),
+          TestEntryBuilder(kReadCommonUrl, clock_.Now())
+              .SetRead(clock_.Now() + base::Seconds(1))}));
+  ASSERT_TRUE(dual_model_->loaded());
+
+  ASSERT_EQ(dual_model_->GetStorageStateForURLForTesting(kUnseenLocalUrl),
+            StorageStateForTesting::kExistsInLocalOrSyncableModelOnly);
+  ASSERT_EQ(dual_model_->GetStorageStateForURLForTesting(kUnreadLocalUrl),
+            StorageStateForTesting::kExistsInLocalOrSyncableModelOnly);
+  ASSERT_EQ(dual_model_->GetStorageStateForURLForTesting(kReadLocalUrl),
+            StorageStateForTesting::kExistsInLocalOrSyncableModelOnly);
+  ASSERT_EQ(dual_model_->GetStorageStateForURLForTesting(kUnseenAccountUrl),
+            StorageStateForTesting::kExistsInAccountModelOnly);
+  ASSERT_EQ(dual_model_->GetStorageStateForURLForTesting(kUnreadAccountUrl),
+            StorageStateForTesting::kExistsInAccountModelOnly);
+  ASSERT_EQ(dual_model_->GetStorageStateForURLForTesting(kReadAccountUrl),
+            StorageStateForTesting::kExistsInAccountModelOnly);
+  ASSERT_EQ(dual_model_->GetStorageStateForURLForTesting(kUnreadCommonUrl),
+            StorageStateForTesting::kExistsInBothModels);
+  ASSERT_EQ(dual_model_->GetStorageStateForURLForTesting(kReadCommonUrl),
+            StorageStateForTesting::kExistsInBothModels);
+
+  ASSERT_EQ(9ul, dual_model_->size());
+  ASSERT_EQ(3ul, dual_model_->unseen_size());
+  ASSERT_EQ(6ul, dual_model_->unread_size());
+
+  dual_model_->MarkAllSeen();
+
+  EXPECT_EQ(9ul, dual_model_->size());
+  EXPECT_EQ(0ul, dual_model_->unseen_size());
+  EXPECT_EQ(6ul, dual_model_->unread_size());
+}
+
 TEST_F(DualReadingListModelTest,
        ShouldMaintainCountsWhenRemoveLocalUnreadEntry) {
   ASSERT_TRUE(ResetStorageAndTriggerLoadCompletion(
diff --git a/components/reading_list/core/reading_list_model_impl.cc b/components/reading_list/core/reading_list_model_impl.cc
index f455fb7..f5d58eab 100644
--- a/components/reading_list/core/reading_list_model_impl.cc
+++ b/components/reading_list/core/reading_list_model_impl.cc
@@ -151,26 +151,7 @@
       BeginBatchUpdatesWithSyncMetadata();
 
   for (auto& iterator : entries_) {
-    ReadingListEntry& entry = *(iterator.second);
-    if (entry.HasBeenSeen()) {
-      continue;
-    }
-    for (auto& observer : observers_) {
-      observer.ReadingListWillUpdateEntry(this, iterator.first);
-    }
-    UpdateEntryStateCountersOnEntryRemoval(entry);
-    entry.SetRead(false, clock_->Now());
-    UpdateEntryStateCountersOnEntryInsertion(entry);
-
-    batch->GetStorageBatch()->SaveEntry(entry);
-    sync_bridge_.DidAddOrUpdateEntry(entry, batch->GetSyncMetadataChangeList());
-
-    for (ReadingListModelObserver& observer : observers_) {
-      observer.ReadingListDidUpdateEntry(this, iterator.first);
-    }
-    for (auto& observer : observers_) {
-      observer.ReadingListDidApplyChanges(this);
-    }
+    MarkEntrySeenImpl(iterator.second.get());
   }
   DCHECK(unseen_entry_count_ == 0);
 }
@@ -572,6 +553,17 @@
   return token;
 }
 
+void ReadingListModelImpl::MarkEntrySeenIfExists(const GURL& url) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(loaded());
+
+  auto iterator = entries_.find(url);
+  if (iterator == entries_.end()) {
+    return;
+  }
+  MarkEntrySeenImpl(iterator->second.get());
+}
+
 bool ReadingListModelImpl::IsTrackingSyncMetadata() const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   return sync_bridge_.change_processor()->IsTrackingMetadata();
@@ -659,6 +651,37 @@
   return storage_layer_.get();
 }
 
+void ReadingListModelImpl::MarkEntrySeenImpl(ReadingListEntry* entry) {
+  DCHECK(entry);
+
+  if (entry->HasBeenSeen()) {
+    return;
+  }
+
+  for (auto& observer : observers_) {
+    observer.ReadingListWillUpdateEntry(this, entry->URL());
+  }
+
+  UpdateEntryStateCountersOnEntryRemoval(*entry);
+  DCHECK(!entry->IsRead());
+  // SetRead() is used to transition the entry from the UNSEEN state to the
+  // UNREAD state.
+  entry->SetRead(false, clock_->Now());
+  UpdateEntryStateCountersOnEntryInsertion(*entry);
+
+  std::unique_ptr<ReadingListModelStorage::ScopedBatchUpdate> batch =
+      storage_layer_->EnsureBatchCreated();
+  batch->SaveEntry(*entry);
+  sync_bridge_.DidAddOrUpdateEntry(*entry, batch->GetSyncMetadataChangeList());
+
+  for (ReadingListModelObserver& observer : observers_) {
+    observer.ReadingListDidUpdateEntry(this, entry->URL());
+  }
+  for (ReadingListModelObserver& observer : observers_) {
+    observer.ReadingListDidApplyChanges(this);
+  }
+}
+
 void ReadingListModelImpl::AddEntryImpl(scoped_refptr<ReadingListEntry> entry,
                                         reading_list::EntrySource source) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
diff --git a/components/reading_list/core/reading_list_model_impl.h b/components/reading_list/core/reading_list_model_impl.h
index 6f6ec66..42972f6 100644
--- a/components/reading_list/core/reading_list_model_impl.h
+++ b/components/reading_list/core/reading_list_model_impl.h
@@ -110,6 +110,10 @@
     std::unique_ptr<ReadingListModelStorage::ScopedBatchUpdate> storage_token_;
   };
 
+  // If an entry exists with `url` and is unseen, it gets marked as seen (but
+  // unread).
+  void MarkEntrySeenIfExists(const GURL& url);
+
   // Same as BeginBatchUpdates(), but returns specifically
   // ReadingListModelImpl's ScopedReadingListBatchUpdateImpl.
   std::unique_ptr<ScopedReadingListBatchUpdateImpl>
@@ -151,6 +155,8 @@
   // Returns the |storage_layer_| of the model.
   ReadingListModelStorage* StorageLayer();
 
+  void MarkEntrySeenImpl(ReadingListEntry* entry);
+
   // Add |entry| to the model, which must not exist before, and notify the sync
   // bridge if |source| is not ADDED_VIA_SYNC.
   void AddEntryImpl(scoped_refptr<ReadingListEntry> entry,
diff --git a/components/reading_list/core/reading_list_model_unittest.cc b/components/reading_list/core/reading_list_model_unittest.cc
index 0313939..209d5f6 100644
--- a/components/reading_list/core/reading_list_model_unittest.cc
+++ b/components/reading_list/core/reading_list_model_unittest.cc
@@ -117,6 +117,18 @@
   void FakeStorageDidSaveEntry() override { storage_saved_ += 1; }
   void FakeStorageDidRemoveEntry() override { storage_removed_ += 1; }
 
+  size_t UnseenSize() {
+    size_t size = 0;
+    for (const auto& url : model_->GetKeys()) {
+      scoped_refptr<const ReadingListEntry> entry = model_->GetEntryByURL(url);
+      if (!entry->HasBeenSeen()) {
+        size++;
+      }
+    }
+    DCHECK_EQ(size, model_->unseen_size());
+    return size;
+  }
+
   size_t UnreadSize() {
     size_t size = 0;
     for (const auto& url : model_->GetKeys()) {
@@ -215,6 +227,74 @@
   model_.reset();
 }
 
+TEST_F(ReadingListModelTest, MarkEntrySeenIfExists) {
+  const GURL example1("http://example1.com/");
+  ASSERT_TRUE(ResetStorage()->TriggerLoadCompletion(
+      /*entries=*/{base::MakeRefCounted<ReadingListEntry>(
+          example1, "example1_title", clock_.Now())}));
+
+  ASSERT_TRUE(model_->loaded());
+  ASSERT_FALSE(model_->GetEntryByURL(example1)->HasBeenSeen());
+  ASSERT_FALSE(model_->GetEntryByURL(example1)->IsRead());
+  ASSERT_EQ(1ul, UnseenSize());
+  ASSERT_EQ(1ul, UnreadSize());
+
+  testing::InSequence seq;
+  EXPECT_CALL(observer_, ReadingListWillUpdateEntry(model_.get(), example1));
+  EXPECT_CALL(observer_, ReadingListDidUpdateEntry(model_.get(), example1));
+  EXPECT_CALL(observer_, ReadingListDidApplyChanges(model_.get()));
+
+  model_->MarkEntrySeenIfExists(example1);
+
+  EXPECT_TRUE(model_->GetEntryByURL(example1)->HasBeenSeen());
+  EXPECT_FALSE(model_->GetEntryByURL(example1)->IsRead());
+  EXPECT_EQ(0ul, UnseenSize());
+  EXPECT_EQ(1ul, UnreadSize());
+}
+
+TEST_F(ReadingListModelTest, MarkAllSeen) {
+  const GURL example1("http://example1.com/");
+  const GURL example2("http://example2.com/");
+  ASSERT_TRUE(ResetStorage()->TriggerLoadCompletion(
+      /*entries=*/{base::MakeRefCounted<ReadingListEntry>(
+                       example1, "example1_title", clock_.Now()),
+                   base::MakeRefCounted<ReadingListEntry>(
+                       example2, "example2_title", clock_.Now())}));
+
+  ASSERT_TRUE(model_->loaded());
+  ASSERT_FALSE(model_->GetEntryByURL(example1)->HasBeenSeen());
+  ASSERT_FALSE(model_->GetEntryByURL(example2)->HasBeenSeen());
+  ASSERT_FALSE(model_->GetEntryByURL(example1)->IsRead());
+  ASSERT_FALSE(model_->GetEntryByURL(example2)->IsRead());
+  ASSERT_EQ(2ul, UnseenSize());
+  ASSERT_EQ(2ul, UnreadSize());
+
+  {
+    testing::InSequence seq1;
+    EXPECT_CALL(observer_, ReadingListWillUpdateEntry(model_.get(), example1));
+    EXPECT_CALL(observer_, ReadingListDidUpdateEntry(model_.get(), example1));
+    EXPECT_CALL(observer_, ReadingListDidApplyChanges(model_.get()))
+        .RetiresOnSaturation();
+  }
+
+  {
+    testing::InSequence seq2;
+    EXPECT_CALL(observer_, ReadingListWillUpdateEntry(model_.get(), example2));
+    EXPECT_CALL(observer_, ReadingListDidUpdateEntry(model_.get(), example2));
+    EXPECT_CALL(observer_, ReadingListDidApplyChanges(model_.get()))
+        .RetiresOnSaturation();
+  }
+
+  model_->MarkAllSeen();
+
+  EXPECT_TRUE(model_->GetEntryByURL(example1)->HasBeenSeen());
+  EXPECT_TRUE(model_->GetEntryByURL(example2)->HasBeenSeen());
+  EXPECT_FALSE(model_->GetEntryByURL(example1)->IsRead());
+  EXPECT_FALSE(model_->GetEntryByURL(example2)->IsRead());
+  EXPECT_EQ(0ul, UnseenSize());
+  EXPECT_EQ(2ul, UnreadSize());
+}
+
 TEST_F(ReadingListModelTest, DeleteAllEntries) {
   const GURL example1("http://example1.com/");
   const GURL example2("http://example2.com/");
diff --git a/components/safe_browsing/content/browser/client_side_detection_host.cc b/components/safe_browsing/content/browser/client_side_detection_host.cc
index d314e48..4919b4ac2 100644
--- a/components/safe_browsing/content/browser/client_side_detection_host.cc
+++ b/components/safe_browsing/content/browser/client_side_detection_host.cc
@@ -581,6 +581,10 @@
             cached_csd_type ==
                 safe_browsing::ClientSideDetectionType::FORCE_REQUEST &&
             IsEnhancedProtectionEnabled(*delegate_->GetPrefs());
+        if (force_request_from_rt_url_lookup) {
+          verdict->set_client_side_detection_type(
+              safe_browsing::ClientSideDetectionType::FORCE_REQUEST);
+        }
       }
 
       base::UmaHistogramBoolean("SBClientPhishing.RTLookupForceRequest",
diff --git a/components/safe_browsing/content/browser/web_ui/safe_browsing_ui.cc b/components/safe_browsing/content/browser/web_ui/safe_browsing_ui.cc
index 91ff17e..38729c3 100644
--- a/components/safe_browsing/content/browser/web_ui/safe_browsing_ui.cc
+++ b/components/safe_browsing/content/browser/web_ui/safe_browsing_ui.cc
@@ -632,6 +632,16 @@
 
 #endif
 
+std::string SerializeClientSideDetectionType(ClientSideDetectionType csd_type) {
+  switch (csd_type) {
+    case ClientSideDetectionType::CLIENT_SIDE_DETECTION_TYPE_UNSPECIFIED:
+      return "CLIENT_SIDE_DETECTION_TYPE_UNSPECIFIED";
+    case ClientSideDetectionType::FORCE_REQUEST:
+      return "FORCE_REQUEST";
+  }
+  return "UNKNOWN_ENUM_SPECIFIED";
+}
+
 base::Value::Dict SerializeChromeUserPopulation(
     const ChromeUserPopulation& population) {
   base::Value::Dict population_dict;
@@ -1073,6 +1083,11 @@
     dict.Set("model_version", cpr.model_version());
   if (cpr.has_dom_model_version())
     dict.Set("dom_model_version", cpr.dom_model_version());
+  if (cpr.has_client_side_detection_type()) {
+    dict.Set(
+        "client_side_detection_type",
+        SerializeClientSideDetectionType(cpr.client_side_detection_type()));
+  }
 
   base::Value::List features;
   for (const auto& feature : cpr.feature_map()) {
diff --git a/components/search/ntp_features.cc b/components/search/ntp_features.cc
index 4002cb9..83d4e9647 100644
--- a/components/search/ntp_features.cc
+++ b/components/search/ntp_features.cc
@@ -233,6 +233,11 @@
 // If enabled, shortcuts will be shown.
 BASE_FEATURE(kNtpShortcuts, "NtpShortcuts", base::FEATURE_ENABLED_BY_DEFAULT);
 
+// If enabled, shortcuts will be shown in a wide single row.
+BASE_FEATURE(kNtpSingleRowShortcuts,
+             "NtpSingleRowShortcuts",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 // If enabled, the History clusters module will be shown.
 BASE_FEATURE(kNtpHistoryClustersModule,
              "NtpHistoryClustersModule",
diff --git a/components/search/ntp_features.h b/components/search/ntp_features.h
index 69dd969..1490c4d 100644
--- a/components/search/ntp_features.h
+++ b/components/search/ntp_features.h
@@ -63,6 +63,7 @@
 BASE_DECLARE_FEATURE(kNtpRemoveScrim);
 BASE_DECLARE_FEATURE(kNtpSafeBrowsingModule);
 BASE_DECLARE_FEATURE(kNtpShortcuts);
+BASE_DECLARE_FEATURE(kNtpSingleRowShortcuts);
 BASE_DECLARE_FEATURE(kNtpHandleMostVisitedNavigationExplicitly);
 BASE_DECLARE_FEATURE(kNtpHistoryClustersModule);
 BASE_DECLARE_FEATURE(kNtpHistoryClustersModuleBeginTimeDuration);
diff --git a/components/sync/driver/data_type_manager_impl.cc b/components/sync/driver/data_type_manager_impl.cc
index db259fea..a92f77fa 100644
--- a/components/sync/driver/data_type_manager_impl.cc
+++ b/components/sync/driver/data_type_manager_impl.cc
@@ -140,6 +140,13 @@
   // Add types with controllers.
   for (const auto& [type, controller] : *controllers_) {
     allowed_types.Put(type);
+
+    // Ensure that the initial precondition state is accurate, and clear
+    // existing metadata if necessary. Note that this happens for *all* data
+    // types, not just the preferred ones!
+    // TODO(crbug.com/897628): For non-preferred types, metadata should probably
+    // be cleared independent of the precondition state.
+    DataTypePreconditionChanged(type);
   }
 
   ConfigureImpl(Intersection(preferred_types, allowed_types), context);
@@ -357,7 +364,7 @@
     data_type_status_table_.ResetCryptoErrors();
   }
 
-  UpdatePreconditionErrors(preferred_types_);
+  UpdatePreconditionErrors();
 
   last_restart_time_ = base::Time::Now();
 
@@ -409,9 +416,8 @@
   StartNextConfiguration();
 }
 
-void DataTypeManagerImpl::UpdatePreconditionErrors(
-    const ModelTypeSet& desired_types) {
-  for (ModelType type : desired_types) {
+void DataTypeManagerImpl::UpdatePreconditionErrors() {
+  for (ModelType type : preferred_types_) {
     UpdatePreconditionError(type);
   }
 }
diff --git a/components/sync/driver/data_type_manager_impl.h b/components/sync/driver/data_type_manager_impl.h
index aa5eded..4799178c 100644
--- a/components/sync/driver/data_type_manager_impl.h
+++ b/components/sync/driver/data_type_manager_impl.h
@@ -88,9 +88,9 @@
   // Prepare the parameters for the configurer's configuration.
   ModelTypeConfigurer::ConfigureParams PrepareConfigureParams();
 
-  // Update precondition state of types in data_type_status_table_ to match
+  // Update precondition state of types in `data_type_status_table_` to match
   // value of DataTypeController::GetPreconditionState().
-  void UpdatePreconditionErrors(const ModelTypeSet& desired_types);
+  void UpdatePreconditionErrors();
 
   // Update precondition state for |type|, such that data_type_status_table_
   // matches DataTypeController::GetPreconditionState(). Returns true if there
diff --git a/components/sync/driver/data_type_manager_impl_unittest.cc b/components/sync/driver/data_type_manager_impl_unittest.cc
index 195990440..3400a82 100644
--- a/components/sync/driver/data_type_manager_impl_unittest.cc
+++ b/components/sync/driver/data_type_manager_impl_unittest.cc
@@ -1001,6 +1001,32 @@
   dtm_->Stop(ShutdownReason::STOP_SYNC_AND_KEEP_DATA);
   EXPECT_EQ(DataTypeManager::STOPPED, dtm_->state());
   EXPECT_TRUE(configurer_.connected_types().Empty());
+
+  EXPECT_EQ(0, GetController(BOOKMARKS)->model()->clear_metadata_call_count());
+}
+
+TEST_F(SyncDataTypeManagerImplTest, FailingPreconditionClearData) {
+  AddController(BOOKMARKS);
+  GetController(BOOKMARKS)->SetPreconditionState(
+      DataTypeController::PreconditionState::kMustStopAndClearData);
+
+  // Bookmarks is never started due to failing preconditions.
+  DataTypeStatusTable::TypeErrorMap error_map;
+  error_map[BOOKMARKS] =
+      SyncError(FROM_HERE, SyncError::DATATYPE_POLICY_ERROR, "", BOOKMARKS);
+  DataTypeStatusTable expected_status_table;
+  expected_status_table.UpdateFailedDataTypes(error_map);
+  SetConfigureStartExpectation();
+  SetConfigureDoneExpectation(DataTypeManager::OK, expected_status_table);
+
+  Configure(ModelTypeSet(BOOKMARKS));
+  FinishDownload(ModelTypeSet(), ModelTypeSet());  // control types
+
+  EXPECT_EQ(DataTypeController::NOT_RUNNING, GetController(BOOKMARKS)->state());
+  EXPECT_EQ(DataTypeManager::CONFIGURED, dtm_->state());
+  EXPECT_EQ(0U, configurer_.connected_types().Size());
+
+  EXPECT_EQ(1, GetController(BOOKMARKS)->model()->clear_metadata_call_count());
 }
 
 // Tests that unready types are not started after ResetDataTypeErrors and
diff --git a/components/sync/protocol/entity_specifics.proto b/components/sync/protocol/entity_specifics.proto
index 09713b5..97c0e080 100644
--- a/components/sync/protocol/entity_specifics.proto
+++ b/components/sync/protocol/entity_specifics.proto
@@ -57,6 +57,7 @@
 import "components/sync/protocol/typed_url_specifics.proto";
 import "components/sync/protocol/user_consent_specifics.proto";
 import "components/sync/protocol/user_event_specifics.proto";
+import "components/sync/protocol/web_apk_specifics.proto";
 import "components/sync/protocol/web_app_specifics.proto";
 import "components/sync/protocol/webauthn_credential_specifics.proto";
 import "components/sync/protocol/wifi_configuration_specifics.proto";
@@ -150,6 +151,7 @@
     UserConsentSpecifics user_consent = 556014;
     SendTabToSelfSpecifics send_tab_to_self = 601980;
     SecurityEventSpecifics security_event = 600372;
+    WebApkSpecifics web_apk = 1117170;
     WebAppSpecifics web_app = 673225;
     WifiConfigurationSpecifics wifi_configuration = 662827;
     OsPreferenceSpecifics os_preference = 702141;
diff --git a/components/sync/protocol/proto_enum_conversions.cc b/components/sync/protocol/proto_enum_conversions.cc
index 80a1cd6..106f628 100644
--- a/components/sync/protocol/proto_enum_conversions.cc
+++ b/components/sync/protocol/proto_enum_conversions.cc
@@ -676,6 +676,17 @@
   return "";
 }
 
+const char* ProtoEnumToString(sync_pb::WebApkIconInfo::Purpose purpose) {
+  ASSERT_ENUM_BOUNDS(sync_pb::WebApkIconInfo, Purpose, UNSPECIFIED, MONOCHROME);
+  switch (purpose) {
+    ENUM_CASE(sync_pb::WebApkIconInfo, UNSPECIFIED);
+    ENUM_CASE(sync_pb::WebApkIconInfo, ANY);
+    ENUM_CASE(sync_pb::WebApkIconInfo, MASKABLE);
+    ENUM_CASE(sync_pb::WebApkIconInfo, MONOCHROME);
+  }
+  NOTREACHED_NORETURN();
+}
+
 const char* ProtoEnumToString(sync_pb::WebAppIconInfo::Purpose purpose) {
   ASSERT_ENUM_BOUNDS(sync_pb::WebAppIconInfo, Purpose, UNSPECIFIED, MONOCHROME);
   switch (purpose) {
diff --git a/components/sync/protocol/proto_enum_conversions.h b/components/sync/protocol/proto_enum_conversions.h
index 9245045..c020c28 100644
--- a/components/sync/protocol/proto_enum_conversions.h
+++ b/components/sync/protocol/proto_enum_conversions.h
@@ -23,6 +23,7 @@
 #include "components/sync/protocol/sync_enums.pb.h"
 #include "components/sync/protocol/user_consent_types.pb.h"
 #include "components/sync/protocol/user_event_specifics.pb.h"
+#include "components/sync/protocol/web_apk_specifics.pb.h"
 #include "components/sync/protocol/web_app_specifics.pb.h"
 #include "components/sync/protocol/wifi_configuration_specifics.pb.h"
 #include "components/sync/protocol/workspace_desk_specifics.pb.h"
@@ -149,6 +150,8 @@
 const char* ProtoEnumToString(
     sync_pb::WalletMetadataSpecifics::Type wallet_metadata_type);
 
+const char* ProtoEnumToString(sync_pb::WebApkIconInfo::Purpose purpose);
+
 const char* ProtoEnumToString(sync_pb::WebAppIconInfo::Purpose purpose);
 
 const char* ProtoEnumToString(
diff --git a/components/sync/protocol/proto_visitors.h b/components/sync/protocol/proto_visitors.h
index ceea5e9..c294bc8 100644
--- a/components/sync/protocol/proto_visitors.h
+++ b/components/sync/protocol/proto_visitors.h
@@ -53,6 +53,7 @@
 #include "components/sync/protocol/unique_position.pb.h"
 #include "components/sync/protocol/user_consent_specifics.pb.h"
 #include "components/sync/protocol/user_event_specifics.pb.h"
+#include "components/sync/protocol/web_apk_specifics.pb.h"
 #include "components/sync/protocol/web_app_specifics.pb.h"
 #include "components/sync/protocol/webauthn_credential_specifics.pb.h"
 #include "components/sync/protocol/workspace_desk_specifics.pb.h"
@@ -1414,6 +1415,21 @@
   VISIT(instrument_token);
 }
 
+VISIT_PROTO_FIELDS(const sync_pb::WebApkIconInfo& proto) {
+  VISIT(size_in_px);
+  VISIT(url);
+  VISIT_ENUM(purpose);
+}
+
+VISIT_PROTO_FIELDS(const sync_pb::WebApkSpecifics& proto) {
+  VISIT(manifest_id);
+  VISIT(start_url);
+  VISIT(name);
+  VISIT(theme_color);
+  VISIT(scope);
+  VISIT_REP(icon_infos);
+}
+
 VISIT_PROTO_FIELDS(const sync_pb::WebAppIconInfo& proto) {
   VISIT(size_in_px);
   VISIT(url);
diff --git a/components/sync/protocol/protocol_sources.gni b/components/sync/protocol/protocol_sources.gni
index 83bd7faf..877ac18 100644
--- a/components/sync/protocol/protocol_sources.gni
+++ b/components/sync/protocol/protocol_sources.gni
@@ -73,6 +73,7 @@
   "user_consent_types.proto",
   "user_event_specifics.proto",
   "vault.proto",
+  "web_apk_specifics.proto",
   "web_app_specifics.proto",
   "webauthn_credential_specifics.proto",
   "wifi_configuration_specifics.proto",
diff --git a/components/sync/protocol/web_apk_specifics.proto b/components/sync/protocol/web_apk_specifics.proto
new file mode 100644
index 0000000..f727ce6
--- /dev/null
+++ b/components/sync/protocol/web_apk_specifics.proto
@@ -0,0 +1,46 @@
+// Copyright 2023 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// If you change or add any fields in this file, update
+// components/sync/protocol/proto_visitors.h and potentially
+// components/sync/protocol/proto_enum_conversions.{h, cc}.
+
+syntax = "proto2";
+
+option java_multiple_files = true;
+option java_package = "org.chromium.components.sync.protocol";
+
+option optimize_for = LITE_RUNTIME;
+
+package sync_pb;
+
+// Sync & local data: Information about web app icon.
+message WebApkIconInfo {
+  enum Purpose {
+    UNSPECIFIED = 0;
+    // Suitable for any purpose.
+    ANY = 1;
+    // Designed for masking.
+    MASKABLE = 2;
+    // Suitable for monochrome purposes.
+    MONOCHROME = 3;
+  }
+
+  // The size of the square app icon, in raw pixels.
+  optional int32 size_in_px = 1;
+  // The URL of the app icon.
+  optional string url = 2;
+  // The purpose or context in which the icon should be used.
+  optional Purpose purpose = 3;
+}
+
+// WebApk data.
+message WebApkSpecifics {
+  optional string manifest_id = 1;
+  optional string start_url = 2;
+  optional string name = 3;
+  optional uint32 theme_color = 4;
+  optional string scope = 5;
+  repeated WebApkIconInfo icon_infos = 6;
+}
diff --git a/components/unified_consent/BUILD.gn b/components/unified_consent/BUILD.gn
index 4c1a392..635cc8b9 100644
--- a/components/unified_consent/BUILD.gn
+++ b/components/unified_consent/BUILD.gn
@@ -4,6 +4,8 @@
 
 static_library("unified_consent") {
   sources = [
+    "consent_throttle.cc",
+    "consent_throttle.h",
     "pref_names.cc",
     "pref_names.h",
     "unified_consent_metrics.cc",
@@ -27,6 +29,7 @@
 source_set("unit_tests") {
   testonly = true
   sources = [
+    "consent_throttle_unittest.cc",
     "unified_consent_service_unittest.cc",
     "url_keyed_data_collection_consent_helper_unittest.cc",
   ]
diff --git a/components/unified_consent/consent_throttle.cc b/components/unified_consent/consent_throttle.cc
new file mode 100644
index 0000000..b0660e4
--- /dev/null
+++ b/components/unified_consent/consent_throttle.cc
@@ -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.
+
+#include "components/unified_consent/consent_throttle.h"
+#include "base/functional/bind.h"
+#include "base/time/time.h"
+#include "components/unified_consent/url_keyed_data_collection_consent_helper.h"
+
+namespace unified_consent {
+
+namespace {
+
+void FulfillRequestCallback(
+    UrlKeyedDataCollectionConsentHelper::State consent_state,
+    ConsentThrottle::RequestCallback callback) {
+  DCHECK_NE(consent_state,
+            UrlKeyedDataCollectionConsentHelper::State::kInitializing);
+  std::move(callback).Run(consent_state ==
+                          UrlKeyedDataCollectionConsentHelper::State::kEnabled);
+}
+
+}  // namespace
+
+ConsentThrottle::ConsentThrottle(
+    std::unique_ptr<unified_consent::UrlKeyedDataCollectionConsentHelper>
+        consent_helper,
+    base::TimeDelta timeout)
+    : consent_helper_(std::move(consent_helper)), timeout_(timeout) {
+  DCHECK(consent_helper_);
+  handle_observation_.Observe(consent_helper_.get());
+}
+
+ConsentThrottle::~ConsentThrottle() = default;
+
+void ConsentThrottle::OnUrlKeyedDataCollectionConsentStateChanged(
+    UrlKeyedDataCollectionConsentHelper* consent_helper) {
+  DCHECK_EQ(consent_helper, consent_helper_.get());
+
+  auto consent_state = consent_helper->GetConsentState();
+  if (consent_state ==
+      UrlKeyedDataCollectionConsentHelper::State::kInitializing) {
+    return;
+  }
+
+  for (auto& request_callback : enqueued_request_callbacks_) {
+    FulfillRequestCallback(consent_state, std::move(request_callback));
+  }
+
+  enqueued_request_callbacks_.clear();
+  request_processing_timer_.Stop();
+}
+
+void ConsentThrottle::EnqueueRequest(RequestCallback callback) {
+  auto consent_state = consent_helper_->GetConsentState();
+  if (consent_state !=
+      UrlKeyedDataCollectionConsentHelper::State::kInitializing) {
+    FulfillRequestCallback(consent_state, std::move(callback));
+    return;
+  }
+
+  enqueued_request_callbacks_.emplace_back(std::move(callback));
+  if (!request_processing_timer_.IsRunning()) {
+    request_processing_timer_.Start(
+        FROM_HERE, timeout_,
+        base::BindOnce(
+            &ConsentThrottle::OnTimeoutExpired,
+            // Unretained usage here okay, because this object owns the timer.
+            base::Unretained(this)));
+  }
+}
+
+void ConsentThrottle::OnTimeoutExpired() {
+  for (auto& request_callback : enqueued_request_callbacks_) {
+    CHECK_EQ(consent_helper_->GetConsentState(),
+             UrlKeyedDataCollectionConsentHelper::State::kInitializing);
+    std::move(request_callback).Run(false);
+  }
+  enqueued_request_callbacks_.clear();
+}
+
+}  // namespace unified_consent
diff --git a/components/unified_consent/consent_throttle.h b/components/unified_consent/consent_throttle.h
new file mode 100644
index 0000000..2ff7d670
--- /dev/null
+++ b/components/unified_consent/consent_throttle.h
@@ -0,0 +1,76 @@
+// 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_UNIFIED_CONSENT_CONSENT_THROTTLE_H_
+#define COMPONENTS_UNIFIED_CONSENT_CONSENT_THROTTLE_H_
+
+#include <vector>
+
+#include "base/functional/callback.h"
+#include "base/scoped_observation.h"
+#include "base/time/time.h"
+#include "base/timer/elapsed_timer.h"
+#include "base/timer/timer.h"
+#include "components/unified_consent/url_keyed_data_collection_consent_helper.h"
+
+namespace unified_consent {
+
+// This class throttles consent requests when the underlying consent helper is
+// still initializing. It operates in two modes:
+//
+//  1) If the underlying consent helper is still initializing:
+//      - It holds requests in a queue for up to as long as the timeout.
+//      - If the consent helper finishes initializing in that time, it will call
+//        the request callback with the definitive answer.
+//      - If the consent helper doesn't finish initializing in that time, it
+//        will call the callback with a False answer.
+//
+//  2) If the underlying consent helper is already initialized, this class is
+//     just a passthrough, and the underlying consent helper and request
+//     callback are both called synchronously.
+class ConsentThrottle : public UrlKeyedDataCollectionConsentHelper::Observer {
+ public:
+  using RequestCallback = base::OnceCallback<void(bool)>;
+
+  explicit ConsentThrottle(
+      std::unique_ptr<UrlKeyedDataCollectionConsentHelper> consent_helper,
+      base::TimeDelta timeout = base::Seconds(5));
+  ConsentThrottle(const ConsentThrottle&) = delete;
+  ConsentThrottle& operator=(const ConsentThrottle&) = delete;
+  ~ConsentThrottle();
+
+  // UrlKeyedDataCollectionConsentHelper::Observer:
+  void OnUrlKeyedDataCollectionConsentStateChanged(
+      UrlKeyedDataCollectionConsentHelper* consent_helper) override;
+
+  // If the underlying consent helper is initialized already, this method calls
+  // `callback` synchronously with the result. If not, it will hold the request
+  // up until the timeout for the consent helper to initialize.
+  void EnqueueRequest(RequestCallback callback);
+
+ private:
+  // This is run periodically to sweep away old queued requests.
+  void OnTimeoutExpired();
+
+  // The underlying consent helper.
+  std::unique_ptr<UrlKeyedDataCollectionConsentHelper> consent_helper_;
+  // The minimum timeout duration to hold the request for.
+  base::TimeDelta timeout_;
+
+  // Observe the consent helper to handle to state changes immediately.
+  base::ScopedObservation<UrlKeyedDataCollectionConsentHelper, ConsentThrottle>
+      handle_observation_{this};
+
+  // Requests waiting for the consent throttle to initialize. Requests are
+  // stored in the queue in order of their arrival.
+  std::vector<RequestCallback> enqueued_request_callbacks_;
+
+  // Timer used to periodically process unanswered enqueued requests, and
+  // respond to them in the negative.
+  base::OneShotTimer request_processing_timer_;
+};
+
+}  // namespace unified_consent
+
+#endif  // CHROME_BROWSER_COMPONENTS_UNIFIED_CONSENT_CONSENT_THROTTLE_H_
diff --git a/components/unified_consent/consent_throttle_unittest.cc b/components/unified_consent/consent_throttle_unittest.cc
new file mode 100644
index 0000000..0566d233
--- /dev/null
+++ b/components/unified_consent/consent_throttle_unittest.cc
@@ -0,0 +1,143 @@
+// 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/unified_consent/consent_throttle.h"
+#include <memory>
+
+#include "base/run_loop.h"
+#include "base/test/bind.h"
+#include "base/test/task_environment.h"
+#include "components/unified_consent/url_keyed_data_collection_consent_helper.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace unified_consent {
+namespace {
+
+class TestUrlKeyedDataCollectionConsentHelper
+    : public UrlKeyedDataCollectionConsentHelper {
+ public:
+  void SetConsentStateAndFireNotification(State state) {
+    consent_state_ = state;
+    FireOnStateChanged();
+  }
+
+  // UrlKeyedCollectionConsentHelper:
+  State GetConsentState() override { return consent_state_; }
+
+ private:
+  State consent_state_ = State::kInitializing;
+};
+
+class ConsentThrottleTest : public testing::Test {
+ protected:
+  bool GetResultSynchronously(ConsentThrottle* throttle) {
+    absl::optional<bool> out_result;
+    throttle->EnqueueRequest(
+        base::BindLambdaForTesting([&](bool result) { out_result = result; }));
+    EXPECT_TRUE(out_result.has_value())
+        << "The throttle must have run the callback.";
+    return *out_result;
+  }
+
+  base::test::TaskEnvironment task_environment_{
+      base::test::SingleThreadTaskEnvironment::TimeSource::MOCK_TIME};
+};
+
+TEST_F(ConsentThrottleTest, EnabledAndDisabledRunSynchronously) {
+  auto helper = std::make_unique<TestUrlKeyedDataCollectionConsentHelper>();
+  helper->SetConsentStateAndFireNotification(
+      UrlKeyedDataCollectionConsentHelper::State::kDisabled);
+
+  auto* helper_ptr = helper.get();
+
+  auto consent_throttle = ConsentThrottle(std::move(helper));
+  EXPECT_FALSE(GetResultSynchronously(&consent_throttle));
+
+  helper_ptr->SetConsentStateAndFireNotification(
+      UrlKeyedDataCollectionConsentHelper::State::kEnabled);
+  EXPECT_TRUE(GetResultSynchronously(&consent_throttle));
+}
+
+TEST_F(ConsentThrottleTest, ExpireOldRequests) {
+  auto helper = std::make_unique<TestUrlKeyedDataCollectionConsentHelper>();
+  ASSERT_EQ(helper->GetConsentState(),
+            UrlKeyedDataCollectionConsentHelper::State::kInitializing);
+
+  auto consent_throttle = ConsentThrottle(std::move(helper));
+  std::vector<bool> results;
+  consent_throttle.EnqueueRequest(base::BindLambdaForTesting(
+      [&](bool result) { results.push_back(result); }));
+
+  EXPECT_TRUE(results.empty()) << "Callback should not be run immediately.";
+  task_environment_.FastForwardBy(base::Seconds(3));
+  EXPECT_TRUE(results.empty()) << "Callback should not be run after 3 seconds.";
+
+  // Add another request while the first request is still pending.
+  consent_throttle.EnqueueRequest(base::BindLambdaForTesting(
+      [&](bool result) { results.push_back(result); }));
+
+  task_environment_.FastForwardBy(base::Seconds(5));
+  ASSERT_EQ(results.size(), 2U) << "Both callbacks should expire as false.";
+  EXPECT_FALSE(results[0]);
+  EXPECT_FALSE(results[1]);
+
+  // Enqueuing another one should restart the timer, which should expire after
+  // a second delay of 5 seconds.
+  consent_throttle.EnqueueRequest(base::BindLambdaForTesting(
+      [&](bool result) { results.push_back(result); }));
+  EXPECT_EQ(results.size(), 2U) << "Callback should not be run immediately.";
+  task_environment_.FastForwardBy(base::Seconds(3));
+  EXPECT_EQ(results.size(), 2U);
+  task_environment_.FastForwardBy(base::Seconds(3));
+  ASSERT_EQ(results.size(), 3U);
+  EXPECT_FALSE(results[2]);
+}
+
+TEST_F(ConsentThrottleTest, InitializationFulfillsAllQueuedRequests) {
+  auto helper = std::make_unique<TestUrlKeyedDataCollectionConsentHelper>();
+  ASSERT_EQ(helper->GetConsentState(),
+            UrlKeyedDataCollectionConsentHelper::State::kInitializing);
+
+  auto* helper_ptr = helper.get();
+  auto consent_throttle = ConsentThrottle(std::move(helper));
+
+  // Enqueue two requests, 2 seconds apart.
+  std::vector<bool> results;
+  consent_throttle.EnqueueRequest(base::BindLambdaForTesting(
+      [&](bool result) { results.push_back(result); }));
+  ASSERT_TRUE(results.empty());
+  task_environment_.FastForwardBy(base::Seconds(2));
+  consent_throttle.EnqueueRequest(base::BindLambdaForTesting(
+      [&](bool result) { results.push_back(result); }));
+  ASSERT_TRUE(results.empty()) << "Still nothing should be run yet.";
+
+  helper_ptr->SetConsentStateAndFireNotification(
+      UrlKeyedDataCollectionConsentHelper::State::kEnabled);
+  ASSERT_EQ(results.size(), 2U)
+      << "Requests should have been immediately fulfilled as true.";
+  EXPECT_TRUE(results[0]);
+  EXPECT_TRUE(results[1]);
+}
+
+TEST_F(ConsentThrottleTest, InitializationDisabledCase) {
+  auto helper = std::make_unique<TestUrlKeyedDataCollectionConsentHelper>();
+  ASSERT_EQ(helper->GetConsentState(),
+            UrlKeyedDataCollectionConsentHelper::State::kInitializing);
+
+  auto* helper_ptr = helper.get();
+  auto consent_throttle = ConsentThrottle(std::move(helper));
+
+  std::vector<bool> results;
+  consent_throttle.EnqueueRequest(base::BindLambdaForTesting(
+      [&](bool result) { results.push_back(result); }));
+  ASSERT_TRUE(results.empty());
+
+  helper_ptr->SetConsentStateAndFireNotification(
+      UrlKeyedDataCollectionConsentHelper::State::kDisabled);
+  ASSERT_EQ(results.size(), 1U);
+  EXPECT_FALSE(results[0]);
+}
+
+}  // namespace
+}  // namespace unified_consent
diff --git a/components/unified_consent/url_keyed_data_collection_consent_helper.cc b/components/unified_consent/url_keyed_data_collection_consent_helper.cc
index c7fa659..3624664 100644
--- a/components/unified_consent/url_keyed_data_collection_consent_helper.cc
+++ b/components/unified_consent/url_keyed_data_collection_consent_helper.cc
@@ -10,6 +10,7 @@
 #include "base/functional/bind.h"
 #include "base/memory/raw_ptr.h"
 #include "base/observer_list.h"
+#include "base/ranges/algorithm.h"
 #include "components/prefs/pref_change_registrar.h"
 #include "components/prefs/pref_service.h"
 #include "components/sync/base/model_type.h"
@@ -36,7 +37,7 @@
   ~PrefBasedUrlKeyedDataCollectionConsentHelper() override = default;
 
   // UrlKeyedDataCollectionConsentHelper:
-  bool IsEnabled() override;
+  State GetConsentState() override;
 
  private:
   void OnPrefChanged();
@@ -60,7 +61,7 @@
   ~SyncBasedUrlKeyedDataCollectionConsentHelper() override;
 
   // UrlKeyedDataCollectionConsentHelper:
-  bool IsEnabled() override;
+  State GetConsentState() override;
 
   // syncer::SyncServiceObserver:
   void OnStateChanged(syncer::SyncService* sync) override;
@@ -84,9 +85,13 @@
           base::Unretained(this)));
 }
 
-bool PrefBasedUrlKeyedDataCollectionConsentHelper::IsEnabled() {
+UrlKeyedDataCollectionConsentHelper::State
+PrefBasedUrlKeyedDataCollectionConsentHelper::GetConsentState() {
+  // There's no initializing state for pref-based helpers.
   return pref_service_->GetBoolean(
-      prefs::kUrlKeyedAnonymizedDataCollectionEnabled);
+             prefs::kUrlKeyedAnonymizedDataCollectionEnabled)
+             ? State::kEnabled
+             : State::kDisabled;
 }
 
 void PrefBasedUrlKeyedDataCollectionConsentHelper::OnPrefChanged() {
@@ -115,21 +120,37 @@
     sync_service_->RemoveObserver(this);
 }
 
-bool SyncBasedUrlKeyedDataCollectionConsentHelper::IsEnabled() {
+UrlKeyedDataCollectionConsentHelper::State
+SyncBasedUrlKeyedDataCollectionConsentHelper::GetConsentState() {
+  // Any sync type that's NOT_ACTIVE makes the whole consent kDisabled.
   for (const auto& sync_data_type_states : sync_data_type_states_) {
-    if (sync_data_type_states.second != syncer::UploadState::ACTIVE)
-      return false;
+    if (sync_data_type_states.second == syncer::UploadState::NOT_ACTIVE) {
+      return State::kDisabled;
+    }
   }
-  return true;
+
+  // If no sync type is NOT_ACTIVE, any sync type still INITIALIZING makes the
+  // whole consent kInitializing.
+  for (const auto& sync_data_type_states : sync_data_type_states_) {
+    if (sync_data_type_states.second == syncer::UploadState::INITIALIZING) {
+      return State::kInitializing;
+    }
+  }
+
+  DCHECK(base::ranges::all_of(sync_data_type_states_, [](auto& state) {
+    return state.second == syncer::UploadState::ACTIVE;
+  })) << "Nothing is NOT_ACTIVE or INITIALIZING, so all must be ACTIVE.";
+  return State::kEnabled;
 }
 
 void SyncBasedUrlKeyedDataCollectionConsentHelper::OnStateChanged(
     syncer::SyncService* sync_service) {
   DCHECK_EQ(sync_service_, sync_service);
-  bool enabled_before_state_updated = IsEnabled();
+  auto old_state = GetConsentState();
   UpdateSyncDataTypeStates();
-  if (enabled_before_state_updated != IsEnabled())
+  if (old_state != GetConsentState()) {
     FireOnStateChanged();
+  }
 }
 
 void SyncBasedUrlKeyedDataCollectionConsentHelper::OnSyncShutdown(
@@ -180,6 +201,10 @@
       std::set<syncer::ModelType>({syncer::ModelType::BOOKMARKS}));
 }
 
+bool UrlKeyedDataCollectionConsentHelper::IsEnabled() {
+  return GetConsentState() == State::kEnabled;
+}
+
 void UrlKeyedDataCollectionConsentHelper::AddObserver(Observer* observer) {
   observer_list_.AddObserver(observer);
 }
diff --git a/components/unified_consent/url_keyed_data_collection_consent_helper.h b/components/unified_consent/url_keyed_data_collection_consent_helper.h
index 1e648ab..6daa4ac 100644
--- a/components/unified_consent/url_keyed_data_collection_consent_helper.h
+++ b/components/unified_consent/url_keyed_data_collection_consent_helper.h
@@ -20,6 +20,12 @@
 // for URL-keyed data collection.
 class UrlKeyedDataCollectionConsentHelper {
  public:
+  enum class State {
+    kInitializing,
+    kDisabled,
+    kEnabled,
+  };
+
   class Observer {
    public:
     // Called when the state of the URL-keyed data collection changes.
@@ -63,9 +69,14 @@
 
   virtual ~UrlKeyedDataCollectionConsentHelper();
 
+  // Returns the state of the consent helper. To throttle requests until after
+  // initialization, use the `ConsentThrottle` class.
+  virtual State GetConsentState() = 0;
+
   // Returns true if the user has consented for URL keyed anonymized data
-  // collection.
-  virtual bool IsEnabled() = 0;
+  // collection. Note, this is a simplified form of `GetConsentState()` where
+  // kInitializing and kDisabled are both considered NOT enabled.
+  bool IsEnabled();
 
   // Methods to register or remove observers.
   void AddObserver(Observer* observer);
diff --git a/components/unified_consent/url_keyed_data_collection_consent_helper_unittest.cc b/components/unified_consent/url_keyed_data_collection_consent_helper_unittest.cc
index 9586f13..d8a4eed0 100644
--- a/components/unified_consent/url_keyed_data_collection_consent_helper_unittest.cc
+++ b/components/unified_consent/url_keyed_data_collection_consent_helper_unittest.cc
@@ -6,6 +6,7 @@
 
 #include <vector>
 
+#include "components/sync/driver/sync_service.h"
 #include "components/sync/engine/cycle/sync_cycle_snapshot.h"
 #include "components/sync/test/test_sync_service.h"
 #include "components/sync_preferences/testing_pref_service_syncable.h"
@@ -66,11 +67,15 @@
       UrlKeyedDataCollectionConsentHelper::
           NewAnonymizedDataCollectionConsentHelper(&pref_service_);
   helper->AddObserver(this);
+  EXPECT_EQ(helper->GetConsentState(),
+            UrlKeyedDataCollectionConsentHelper::State::kDisabled);
   EXPECT_FALSE(helper->IsEnabled());
   EXPECT_TRUE(state_changed_notifications_.empty());
 
   pref_service_.SetBoolean(prefs::kUrlKeyedAnonymizedDataCollectionEnabled,
                            true);
+  EXPECT_EQ(helper->GetConsentState(),
+            UrlKeyedDataCollectionConsentHelper::State::kEnabled);
   EXPECT_TRUE(helper->IsEnabled());
   ASSERT_EQ(1U, state_changed_notifications_.size());
   EXPECT_TRUE(state_changed_notifications_[0]);
@@ -78,6 +83,8 @@
   state_changed_notifications_.clear();
   pref_service_.SetBoolean(prefs::kUrlKeyedAnonymizedDataCollectionEnabled,
                            false);
+  EXPECT_EQ(helper->GetConsentState(),
+            UrlKeyedDataCollectionConsentHelper::State::kDisabled);
   EXPECT_FALSE(helper->IsEnabled());
   ASSERT_EQ(1U, state_changed_notifications_.size());
   EXPECT_FALSE(state_changed_notifications_[0]);
@@ -89,17 +96,32 @@
       UrlKeyedDataCollectionConsentHelper::
           NewPersonalizedDataCollectionConsentHelper(&sync_service_);
   helper->AddObserver(this);
+  EXPECT_EQ(helper->GetConsentState(),
+            UrlKeyedDataCollectionConsentHelper::State::kDisabled);
   EXPECT_FALSE(helper->IsEnabled());
   EXPECT_TRUE(state_changed_notifications_.empty());
 
+  sync_service_.SetTransportState(
+      syncer::SyncService::TransportState::INITIALIZING);
   sync_service_.GetUserSettings()->SetSelectedTypes(
       /*sync_everything=*/false,
       /*types=*/syncer::UserSelectableTypeSet(
           syncer::UserSelectableType::kHistory));
 
   sync_service_.FireOnStateChangeOnAllObservers();
+  EXPECT_EQ(helper->GetConsentState(),
+            UrlKeyedDataCollectionConsentHelper::State::kInitializing);
+  EXPECT_FALSE(helper->IsEnabled());
+  EXPECT_EQ(1U, state_changed_notifications_.size())
+      << "No state change notifications fired, because it's still not enabled, "
+         "it's just initializing.";
+
+  sync_service_.SetTransportState(syncer::SyncService::TransportState::ACTIVE);
+  sync_service_.FireOnStateChangeOnAllObservers();
+  EXPECT_EQ(helper->GetConsentState(),
+            UrlKeyedDataCollectionConsentHelper::State::kEnabled);
   EXPECT_TRUE(helper->IsEnabled());
-  EXPECT_EQ(1U, state_changed_notifications_.size());
+  EXPECT_EQ(2U, state_changed_notifications_.size());
   helper->RemoveObserver(this);
 }
 
diff --git a/components/viz/common/quads/shared_quad_state.cc b/components/viz/common/quads/shared_quad_state.cc
index 2191ce0b..24d71867 100644
--- a/components/viz/common/quads/shared_quad_state.cc
+++ b/components/viz/common/quads/shared_quad_state.cc
@@ -15,7 +15,11 @@
 namespace viz {
 
 SharedQuadState::SharedQuadState() = default;
+
 SharedQuadState::SharedQuadState(const SharedQuadState& other) = default;
+SharedQuadState& SharedQuadState::operator=(const SharedQuadState& other) =
+    default;
+
 SharedQuadState::~SharedQuadState() {
   TRACE_EVENT_OBJECT_DELETED_WITH_ID(TRACE_DISABLED_BY_DEFAULT("viz.quads"),
                                      "viz::SharedQuadState", this);
@@ -31,7 +35,9 @@
          clip_rect == other.clip_rect &&
          are_contents_opaque == other.are_contents_opaque &&
          opacity == other.opacity && blend_mode == other.blend_mode &&
-         sorting_context_id == other.sorting_context_id;
+         sorting_context_id == other.sorting_context_id &&
+         layer_id == other.layer_id &&
+         layer_namespace_id == other.layer_namespace_id;
 }
 
 void SharedQuadState::SetAll(const SharedQuadState& other) {
@@ -44,6 +50,8 @@
   opacity = other.opacity;
   blend_mode = other.blend_mode;
   sorting_context_id = other.sorting_context_id;
+  layer_id = other.layer_id;
+  layer_namespace_id = other.layer_namespace_id;
 }
 
 void SharedQuadState::SetAll(const gfx::Transform& transform,
@@ -54,7 +62,8 @@
                              bool contents_opaque,
                              float opacity_f,
                              SkBlendMode blend,
-                             int sorting_context) {
+                             int sorting_context,
+                             uint32_t layer) {
   quad_to_target_transform = transform;
   quad_layer_rect = layer_rect;
   visible_quad_layer_rect = visible_layer_rect;
@@ -64,6 +73,7 @@
   opacity = opacity_f;
   blend_mode = blend;
   sorting_context_id = sorting_context;
+  layer_id = layer;
 }
 
 void SharedQuadState::AsValueInto(base::trace_event::TracedValue* value) const {
@@ -92,6 +102,8 @@
   value->SetDouble("opacity", opacity);
   value->SetString("blend_mode", SkBlendMode_Name(blend_mode));
   value->SetInteger("sorting_context_id", sorting_context_id);
+  value->SetInteger("layer_id", layer_id);
+  value->SetInteger("layer_namespace_id", layer_id);
   value->SetBoolean("is_fast_rounded_corner", is_fast_rounded_corner);
   TracedValue::MakeDictIntoImplicitSnapshotWithCategory(
       TRACE_DISABLED_BY_DEFAULT("viz.quads"), value, "viz::SharedQuadState",
diff --git a/components/viz/common/quads/shared_quad_state.h b/components/viz/common/quads/shared_quad_state.h
index 07fc4ad..9680bb8d 100644
--- a/components/viz/common/quads/shared_quad_state.h
+++ b/components/viz/common/quads/shared_quad_state.h
@@ -13,11 +13,9 @@
 #include "ui/gfx/geometry/rrect_f.h"
 #include "ui/gfx/geometry/transform.h"
 
-namespace base {
-namespace trace_event {
+namespace base::trace_event {
 class TracedValue;
-}
-}  // namespace base
+}  // namespace base::trace_event
 
 namespace viz {
 
@@ -31,6 +29,7 @@
  public:
   SharedQuadState();
   SharedQuadState(const SharedQuadState& other);
+  SharedQuadState& operator=(const SharedQuadState& other);
   ~SharedQuadState();
 
   // No comparison for |overlay_damage_index| and |is_fast_rounded_corner|.
@@ -38,6 +37,8 @@
 
   void SetAll(const SharedQuadState& other);
 
+  // TODO(kylechar): Remove default value for `layer_id` after updating all
+  // callers.
   void SetAll(const gfx::Transform& transform,
               const gfx::Rect& layer_rect,
               const gfx::Rect& visible_layer_rect,
@@ -46,7 +47,8 @@
               bool contents_opaque,
               float opacity_f,
               SkBlendMode blend,
-              int sorting_context);
+              int sorting_context,
+              uint32_t layer_id = 0);
   void AsValueInto(base::trace_event::TracedValue* dict) const;
 
   // Transforms quad rects into the target content space.
@@ -67,9 +69,16 @@
   absl::optional<gfx::Rect> clip_rect;
   // Indicates whether the content in |quad_layer_rect| are fully opaque.
   bool are_contents_opaque = true;
-  float opacity = 1.f;
+  float opacity = 1.0f;
   SkBlendMode blend_mode = SkBlendMode::kSrcOver;
   int sorting_context_id = 0;
+  // Optionally set by the client with a stable ID for the layer that produced
+  // the DrawQuad(s). This is used to help identify that DrawQuad(s) in one
+  // frame came from the same layer as DrawQuads() from a previous frame, even
+  // if they changed position or other attributes.
+  uint32_t layer_id = 0;
+  // Used by SurfaceAggregator to namespace layer_ids from different clients.
+  uint32_t layer_namespace_id = 0;
   // Used by SurfaceAggregator to decide whether to merge quads for a surface
   // into their target render pass. It is a performance optimization by avoiding
   // render passes as much as possible.
diff --git a/components/viz/service/display/display.cc b/components/viz/service/display/display.cc
index bc97fd6a..7f20730 100644
--- a/components/viz/service/display/display.cc
+++ b/components/viz/service/display/display.cc
@@ -9,6 +9,7 @@
 #include <algorithm>
 #include <limits>
 #include <memory>
+#include <string>
 #include <utility>
 
 #include "base/containers/contains.h"
@@ -612,11 +613,15 @@
                     root_render_pass.damage_rect);
 
   for (auto* quad : root_render_pass.quad_list) {
-    auto& transform = quad->shared_quad_state->quad_to_target_transform;
+    auto* sqs = quad->shared_quad_state;
+    auto& transform = sqs->quad_to_target_transform;
     auto display_rect = transform.MapRect(gfx::RectF(quad->rect));
     DBG_DRAW_TEXT_OPT("frame.root.material", DBG_OPT_GREEN,
                       display_rect.origin(),
                       base::NumberToString(static_cast<int>(quad->material)));
+    DBG_DRAW_TEXT_OPT(
+        "frame.root.layer_id", DBG_OPT_BLUE, display_rect.origin(),
+        base::StringPrintf("%u:%u", sqs->layer_namespace_id, sqs->layer_id));
     DBG_DRAW_TEXT_OPT("frame.root.display_rect", DBG_OPT_GREEN,
                       display_rect.origin(), display_rect.ToString());
     DBG_DRAW_TEXT_OPT(
diff --git a/components/viz/service/display/resolved_frame_data.cc b/components/viz/service/display/resolved_frame_data.cc
index 4ae7a29..641c102 100644
--- a/components/viz/service/display/resolved_frame_data.cc
+++ b/components/viz/service/display/resolved_frame_data.cc
@@ -75,6 +75,10 @@
   previous_frame_index_ = kInvalidFrameIndex;
 }
 
+uint32_t ResolvedFrameData::GetClientNamespaceId() const {
+  return static_cast<uint32_t>(child_resource_id_);
+}
+
 void ResolvedFrameData::ForceReleaseResource() {
   // Resources for future frames are stored under a new child id going forward.
   resource_provider_->DestroyChild(child_resource_id_);
diff --git a/components/viz/service/display/resolved_frame_data.h b/components/viz/service/display/resolved_frame_data.h
index eac90925..f4e111d2 100644
--- a/components/viz/service/display/resolved_frame_data.h
+++ b/components/viz/service/display/resolved_frame_data.h
@@ -144,6 +144,10 @@
   bool is_valid() const { return valid_; }
   uint64_t previous_frame_index() const { return previous_frame_index_; }
 
+  // Returns namespace ID for the client that submitted this frame. This is used
+  // to deduplicate layer IDs from different clients.
+  uint32_t GetClientNamespaceId() const;
+
   void SetFullDamageForNextAggregation();
 
   // Force release all resources registered with display resource provider. Note
diff --git a/components/viz/service/display/surface_aggregator.cc b/components/viz/service/display/surface_aggregator.cc
index 280b757..d521e118 100644
--- a/components/viz/service/display/surface_aggregator.cc
+++ b/components/viz/service/display/surface_aggregator.cc
@@ -161,6 +161,7 @@
 // - |dest_render_pass| is where the new SharedQuadState will be created.
 SharedQuadState* CopyAndScaleSharedQuadState(
     const SharedQuadState* source_sqs,
+    uint32_t client_namespace_id,
     const gfx::Transform& quad_to_target_transform,
     const gfx::Transform& target_transform,
     const gfx::Rect& quad_layer_rect,
@@ -185,7 +186,9 @@
       new_transform, quad_layer_rect, visible_quad_layer_rect,
       mask_filter_info_ext.mask_filter_info, new_clip_rect,
       source_sqs->are_contents_opaque, source_sqs->opacity,
-      source_sqs->blend_mode, source_sqs->sorting_context_id);
+      source_sqs->blend_mode, source_sqs->sorting_context_id,
+      source_sqs->layer_id);
+  shared_quad_state->layer_namespace_id = client_namespace_id;
   shared_quad_state->is_fast_rounded_corner =
       mask_filter_info_ext.is_fast_rounded_corner;
   return shared_quad_state;
@@ -195,14 +198,16 @@
 // into it. See CopyAndScaleSharedQuadState() for full documentation.
 SharedQuadState* CopySharedQuadState(
     const SharedQuadState* source_sqs,
+    uint32_t client_namespace_id,
     const gfx::Transform& target_transform,
     const absl::optional<gfx::Rect>& added_clip_rect,
     const MaskFilterInfoExt& mask_filter_info,
     AggregatedRenderPass* dest_render_pass) {
   return CopyAndScaleSharedQuadState(
-      source_sqs, source_sqs->quad_to_target_transform, target_transform,
-      source_sqs->quad_layer_rect, source_sqs->visible_quad_layer_rect,
-      added_clip_rect, mask_filter_info, dest_render_pass);
+      source_sqs, client_namespace_id, source_sqs->quad_to_target_transform,
+      target_transform, source_sqs->quad_layer_rect,
+      source_sqs->visible_quad_layer_rect, added_clip_rect, mask_filter_info,
+      dest_render_pass);
 }
 
 // Returns true if |resolved_pass| needs full damage. This is because:
@@ -561,6 +566,7 @@
 void SurfaceAggregator::HandleSurfaceQuad(
     const CompositorRenderPass& source_pass,
     const SurfaceDrawQuad* surface_quad,
+    uint32_t embedder_client_namespace_id,
     float parent_device_scale_factor,
     const gfx::Transform& target_transform,
     const absl::optional<gfx::Rect>& added_clip_rect,
@@ -615,9 +621,9 @@
   // SolidColorDrawQuad with the provided default background color. This
   // can happen after a Viz process crash.
   if (!resolved_frame) {
-    EmitDefaultBackgroundColorQuad(surface_quad, target_transform,
-                                   surface_clip_rect, dest_pass,
-                                   mask_filter_info);
+    EmitDefaultBackgroundColorQuad(surface_quad, embedder_client_namespace_id,
+                                   target_transform, surface_clip_rect,
+                                   dest_pass, mask_filter_info);
     return;
   }
 
@@ -638,14 +644,16 @@
     // TODO(crbug.com/1308932): CompositorFrameMetadata to SkColor4f
     EmitGutterQuadsIfNecessary(surface_quad->visible_rect, fallback_rect,
                                surface_quad->shared_quad_state,
-                               target_transform, surface_clip_rect,
+                               embedder_client_namespace_id, target_transform,
+                               surface_clip_rect,
                                fallback_frame.metadata.root_background_color,
                                dest_pass, mask_filter_info);
   }
 
   EmitSurfaceContent(*resolved_frame, parent_device_scale_factor, surface_quad,
-                     target_transform, surface_clip_rect, combined_clip_rect,
-                     dest_pass, ignore_undamaged, damage_rect_in_quad_space,
+                     embedder_client_namespace_id, target_transform,
+                     surface_clip_rect, combined_clip_rect, dest_pass,
+                     ignore_undamaged, damage_rect_in_quad_space,
                      damage_rect_in_quad_space_valid, mask_filter_info);
 }
 
@@ -653,6 +661,7 @@
     const ResolvedFrameData& resolved_frame,
     float parent_device_scale_factor,
     const SurfaceDrawQuad* surface_quad,
+    uint32_t embedder_client_namespace_id,
     const gfx::Transform& target_transform,
     const absl::optional<gfx::Rect>& added_clip_rect,
     const absl::optional<gfx::Rect>& dest_root_target_clip_rect,
@@ -845,7 +854,8 @@
                     mask_filter_info);
   } else {
     auto* shared_quad_state = CopyAndScaleSharedQuadState(
-        surface_quad_sqs, scaled_quad_to_target_transform, target_transform,
+        surface_quad_sqs, embedder_client_namespace_id,
+        scaled_quad_to_target_transform, target_transform,
         gfx::ScaleToEnclosingRect(surface_quad_sqs->quad_layer_rect,
                                   inverse_extra_content_scale_x,
                                   inverse_extra_content_scale_y),
@@ -907,6 +917,7 @@
 
 void SurfaceAggregator::EmitDefaultBackgroundColorQuad(
     const SurfaceDrawQuad* surface_quad,
+    uint32_t embedder_client_namespace_id,
     const gfx::Transform& target_transform,
     const absl::optional<gfx::Rect>& clip_rect,
     AggregatedRenderPass* dest_pass,
@@ -917,9 +928,9 @@
   // No matching surface was found so create a SolidColorDrawQuad with the
   // SurfaceDrawQuad default background color.
   SkColor4f background_color = surface_quad->default_background_color;
-  auto* shared_quad_state =
-      CopySharedQuadState(surface_quad->shared_quad_state, target_transform,
-                          clip_rect, mask_filter_info, dest_pass);
+  auto* shared_quad_state = CopySharedQuadState(
+      surface_quad->shared_quad_state, embedder_client_namespace_id,
+      target_transform, clip_rect, mask_filter_info, dest_pass);
 
   auto* solid_color_quad =
       dest_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
@@ -931,6 +942,7 @@
     const gfx::Rect& primary_rect,
     const gfx::Rect& fallback_rect,
     const SharedQuadState* primary_shared_quad_state,
+    uint32_t embedder_client_namespace_id,
     const gfx::Transform& target_transform,
     const absl::optional<gfx::Rect>& clip_rect,
     SkColor4f background_color,
@@ -950,7 +962,7 @@
                                 primary_rect.height());
 
     SharedQuadState* shared_quad_state = CopyAndScaleSharedQuadState(
-        primary_shared_quad_state,
+        primary_shared_quad_state, embedder_client_namespace_id,
         primary_shared_quad_state->quad_to_target_transform, target_transform,
         right_gutter_rect, right_gutter_rect, clip_rect, mask_filter_info,
         dest_pass);
@@ -967,7 +979,7 @@
         primary_rect.height() - fallback_rect.height());
 
     SharedQuadState* shared_quad_state = CopyAndScaleSharedQuadState(
-        primary_shared_quad_state,
+        primary_shared_quad_state, embedder_client_namespace_id,
         primary_shared_quad_state->quad_to_target_transform, target_transform,
         bottom_gutter_rect, bottom_gutter_rect, clip_rect, mask_filter_info,
         dest_pass);
@@ -1221,6 +1233,9 @@
 
   size_t quad_index = 0;
   auto& resolved_draw_quads = resolved_pass.draw_quads();
+
+  uint32_t client_namespace_id = resolved_frame.GetClientNamespaceId();
+
   for (auto* quad : source_quad_list) {
     const ResolvedQuadData& quad_data = resolved_draw_quads[quad_index++];
 
@@ -1247,9 +1262,10 @@
             quad->shared_quad_state->is_fast_rounded_corner, target_transform);
       }
 
-      HandleSurfaceQuad(source_pass, surface_quad, parent_device_scale_factor,
-                        target_transform, clip_rect, dest_root_target_clip_rect,
-                        dest_pass, ignore_undamaged, &damage_rect_in_quad_space,
+      HandleSurfaceQuad(source_pass, surface_quad, client_namespace_id,
+                        parent_device_scale_factor, target_transform, clip_rect,
+                        dest_root_target_clip_rect, dest_pass, ignore_undamaged,
+                        &damage_rect_in_quad_space,
                         &damage_rect_in_quad_space_valid,
                         new_mask_filter_info_ext);
     } else {
@@ -1260,9 +1276,9 @@
                                 quad->shared_quad_state->is_fast_rounded_corner,
                                 target_transform);
         }
-        SharedQuadState* dest_shared_quad_state =
-            CopySharedQuadState(quad->shared_quad_state, target_transform,
-                                clip_rect, new_mask_filter_info_ext, dest_pass);
+        SharedQuadState* dest_shared_quad_state = CopySharedQuadState(
+            quad->shared_quad_state, client_namespace_id, target_transform,
+            clip_rect, new_mask_filter_info_ext, dest_pass);
         // Here we output the optional quad's |per_quad_damage| to the
         // |surface_damage_rect_list_|. Any non per quad damage associated with
         // this |source_pass| will have been added to the
diff --git a/components/viz/service/display/surface_aggregator.h b/components/viz/service/display/surface_aggregator.h
index 6e01986c..51ae2e9 100644
--- a/components/viz/service/display/surface_aggregator.h
+++ b/components/viz/service/display/surface_aggregator.h
@@ -139,6 +139,8 @@
   ResolvedFrameData* GetResolvedFrame(const SurfaceId& surface_id);
 
   // - |source_pass| is the render pass that contains |surface_quad|.
+  // - |embedder_client_namespace_id| is portion of layer_id that uniquely
+  //   identifies the client which contains |surface_quad|.
   // - |target_transform| is the transform from the coordinate space of
   //   |source_pass| to |dest_pass|.
   // - |added_clip_rect| is an added clip rect in the |dest_pass| coordinate
@@ -150,6 +152,7 @@
   void HandleSurfaceQuad(
       const CompositorRenderPass& source_pass,
       const SurfaceDrawQuad* surface_quad,
+      uint32_t embedder_client_namespace_id,
       float parent_device_scale_factor,
       const gfx::Transform& target_transform,
       const absl::optional<gfx::Rect>& added_clip_rect,
@@ -164,6 +167,7 @@
       const ResolvedFrameData& resolved_frame,
       float parent_device_scale_factor,
       const SurfaceDrawQuad* surface_quad,
+      uint32_t embedder_client_namespace_id,
       const gfx::Transform& target_transform,
       const absl::optional<gfx::Rect>& added_clip_rect,
       const absl::optional<gfx::Rect>& dest_root_target_clip_rect,
@@ -175,6 +179,7 @@
 
   void EmitDefaultBackgroundColorQuad(
       const SurfaceDrawQuad* surface_quad,
+      uint32_t embedder_client_namespace_id,
       const gfx::Transform& target_transform,
       const absl::optional<gfx::Rect>& clip_rect,
       AggregatedRenderPass* dest_pass,
@@ -184,6 +189,7 @@
       const gfx::Rect& primary_rect,
       const gfx::Rect& fallback_rect,
       const SharedQuadState* primary_shared_quad_state,
+      uint32_t embedder_client_namespace_id,
       const gfx::Transform& target_transform,
       const absl::optional<gfx::Rect>& clip_rect,
       SkColor4f background_color,
diff --git a/components/viz/service/display/surface_aggregator_unittest.cc b/components/viz/service/display/surface_aggregator_unittest.cc
index 5538812..76b4ce6 100644
--- a/components/viz/service/display/surface_aggregator_unittest.cc
+++ b/components/viz/service/display/surface_aggregator_unittest.cc
@@ -1448,6 +1448,113 @@
   }
 }
 
+// Verify that layer_ids are deduplicated in the final AggregatedFrame
+// correctly.
+TEST_F(SurfaceAggregatorValidSurfaceTest, LayerIds) {
+  TestSurfaceIdAllocator child_surface_id(child_sink_->frame_sink_id());
+
+  gfx::Rect child_surface_rect(20, 20);
+  {
+    CompositorFrame frame =
+        CompositorFrameBuilder()
+            .AddRenderPass(
+                RenderPassBuilder(child_surface_rect)
+                    .AddSolidColorQuad(gfx::Rect(20, 20), SkColors::kRed)
+                    .SetQuadLayerId(1u))
+            .Build();
+
+    child_sink_->SubmitCompositorFrame(child_surface_id.local_surface_id(),
+                                       std::move(frame));
+  }
+
+  {
+    CompositorFrame frame =
+        CompositorFrameBuilder()
+            .AddRenderPass(RenderPassBuilder(kSurfaceSize)
+                               .AddSurfaceQuad(child_surface_rect,
+                                               SurfaceRange(child_surface_id))
+                               .SetQuadLayerId(2)
+                               .AddSolidColorQuad(gfx::Rect(kSurfaceSize),
+                                                  SkColors::kBlack)
+                               .SetQuadLayerId(3))
+            .Build();
+    root_sink_->SubmitCompositorFrame(root_surface_id_.local_surface_id(),
+                                      std::move(frame));
+  }
+
+  {
+    auto frame = AggregateFrame(root_surface_id_);
+    ASSERT_EQ(1u, frame.render_pass_list.size());
+    auto* render_pass = frame.render_pass_list.back().get();
+
+    uint32_t root_surface_namespace =
+        aggregator_.GetLatestFrameData(root_surface_id_)
+            ->GetClientNamespaceId();
+    uint32_t child_surface_namespace =
+        aggregator_.GetLatestFrameData(child_surface_id)
+            ->GetClientNamespaceId();
+
+    // The child surface is merged into the root surface so there is a single
+    // render pass with a solid color draw quad from both clients. Both will
+    // have client namespace ID + original layer ID as their final layer ID.
+    EXPECT_THAT(render_pass->quad_list,
+                ElementsAre(AllOf(IsSolidColorQuad(SkColors::kRed),
+                                  HasLayerNamespaceId(child_surface_namespace),
+                                  HasLayerId(1u)),
+                            AllOf(IsSolidColorQuad(SkColors::kBlack),
+                                  HasLayerNamespaceId(root_surface_namespace),
+                                  HasLayerId(3u))));
+  }
+
+  // Redo the aggregation but don't allow merging child surface into the root
+  // render pass.
+  {
+    CompositorFrame frame =
+        CompositorFrameBuilder()
+            .AddRenderPass(RenderPassBuilder(kSurfaceSize)
+                               .AddSurfaceQuad(child_surface_rect,
+                                               SurfaceRange(child_surface_id),
+                                               {.allow_merge = false})
+                               .SetQuadLayerId(2)
+                               .AddSolidColorQuad(gfx::Rect(kSurfaceSize),
+                                                  SkColors::kBlack)
+                               .SetQuadLayerId(3))
+            .Build();
+    root_sink_->SubmitCompositorFrame(root_surface_id_.local_surface_id(),
+                                      std::move(frame));
+  }
+
+  {
+    auto frame = AggregateFrame(root_surface_id_);
+    ASSERT_EQ(2u, frame.render_pass_list.size());
+    auto* child_pass = frame.render_pass_list.at(0).get();
+    auto* root_pass = frame.render_pass_list.at(1).get();
+
+    uint32_t root_surface_namespace =
+        aggregator_.GetLatestFrameData(root_surface_id_)
+            ->GetClientNamespaceId();
+    uint32_t child_surface_namespace =
+        aggregator_.GetLatestFrameData(child_surface_id)
+            ->GetClientNamespaceId();
+
+    EXPECT_THAT(child_pass->quad_list,
+                ElementsAre(AllOf(IsSolidColorQuad(SkColors::kRed),
+                                  HasLayerNamespaceId(child_surface_namespace),
+                                  HasLayerId(1u))));
+
+    // The AggregatedRenderPassDrawQuad is taking the place of the
+    // SurfaceDrawQuad so it should have the same client namespace as other
+    // quads from the root surface.
+    EXPECT_THAT(root_pass->quad_list,
+                ElementsAre(AllOf(IsAggregatedRenderPassQuad(),
+                                  HasLayerNamespaceId(root_surface_namespace),
+                                  HasLayerId(2u)),
+                            AllOf(IsSolidColorQuad(SkColors::kBlack),
+                                  HasLayerNamespaceId(root_surface_namespace),
+                                  HasLayerId(3u))));
+  }
+}
+
 // This test verifies that the appropriate transform will be applied to a
 // surface embedded by a parent SurfaceDrawQuad marked as
 // stretch_content_to_fill_bounds.
diff --git a/components/viz/test/compositor_frame_helpers.cc b/components/viz/test/compositor_frame_helpers.cc
index b6539e6..1d2e6b2 100644
--- a/components/viz/test/compositor_frame_helpers.cc
+++ b/components/viz/test/compositor_frame_helpers.cc
@@ -13,6 +13,7 @@
 #include "components/viz/common/quads/compositor_render_pass.h"
 #include "components/viz/common/quads/compositor_render_pass_draw_quad.h"
 #include "components/viz/common/quads/shared_element_draw_quad.h"
+#include "components/viz/common/quads/shared_quad_state.h"
 #include "components/viz/common/quads/solid_color_draw_quad.h"
 #include "components/viz/common/quads/surface_draw_quad.h"
 #include "components/viz/common/quads/texture_draw_quad.h"
@@ -275,6 +276,12 @@
   return *this;
 }
 
+RenderPassBuilder& RenderPassBuilder::SetQuadLayerId(uint32_t layer_id) {
+  auto* sqs = GetLastQuadSharedQuadState();
+  sqs->layer_id = layer_id;
+  return *this;
+}
+
 SharedQuadState* RenderPassBuilder::AppendDefaultSharedQuadState(
     const gfx::Rect rect,
     const gfx::Rect visible_rect) {
diff --git a/components/viz/test/compositor_frame_helpers.h b/components/viz/test/compositor_frame_helpers.h
index b7b80c4e..fea7f19 100644
--- a/components/viz/test/compositor_frame_helpers.h
+++ b/components/viz/test/compositor_frame_helpers.h
@@ -165,6 +165,9 @@
   RenderPassBuilder& SetMaskFilter(const gfx::MaskFilterInfo& mask_filter_info,
                                    bool is_fast_rounded_corner);
 
+  // Sets SharedQuadState::layer_id for the last quad.
+  RenderPassBuilder& SetQuadLayerId(uint32_t layer_id);
+
  private:
   // Appends and returns a new SharedQuadState for quad.
   SharedQuadState* AppendDefaultSharedQuadState(const gfx::Rect rect,
diff --git a/components/viz/test/draw_quad_matchers.cc b/components/viz/test/draw_quad_matchers.cc
index 1d4f25eb..f8a852b 100644
--- a/components/viz/test/draw_quad_matchers.cc
+++ b/components/viz/test/draw_quad_matchers.cc
@@ -125,4 +125,16 @@
       testing::Eq(opaque)));
 }
 
+testing::Matcher<const DrawQuad*> HasLayerId(uint32_t layer_id) {
+  return HasSharedQuadState(testing::Field(
+      "layer_id", &SharedQuadState::layer_id, testing::Eq(layer_id)));
+}
+
+testing::Matcher<const DrawQuad*> HasLayerNamespaceId(
+    uint32_t layer_namespace_id) {
+  return HasSharedQuadState(testing::Field("layer_namespace_id",
+                                           &SharedQuadState::layer_namespace_id,
+                                           testing::Eq(layer_namespace_id)));
+}
+
 }  // namespace viz
diff --git a/components/viz/test/draw_quad_matchers.h b/components/viz/test/draw_quad_matchers.h
index 6193afc..d50aedc2 100644
--- a/components/viz/test/draw_quad_matchers.h
+++ b/components/viz/test/draw_quad_matchers.h
@@ -68,6 +68,13 @@
 // Matches a DrawQuad with expected SharedQuadState::are_contents_opaque.
 testing::Matcher<const DrawQuad*> AreContentsOpaque(bool opaque);
 
+// Matches a DrawQuad with expected SharedQuadState::layer_id.
+testing::Matcher<const DrawQuad*> HasLayerId(uint32_t layer_id);
+
+// Matches a DrawQuad with expected SharedQuadState::layer_namespace_id.
+testing::Matcher<const DrawQuad*> HasLayerNamespaceId(
+    uint32_t layer_namespace_id);
+
 }  // namespace viz
 
 #endif  // COMPONENTS_VIZ_TEST_DRAW_QUAD_MATCHERS_H_
diff --git a/components/webxr/android/BUILD.gn b/components/webxr/android/BUILD.gn
index d1c0434..e97e7593 100644
--- a/components/webxr/android/BUILD.gn
+++ b/components/webxr/android/BUILD.gn
@@ -37,6 +37,13 @@
       ]
     }
 
+    if (enable_cardboard) {
+      sources += [
+        "cardboard_device_provider.cc",
+        "cardboard_device_provider.h",
+      ]
+    }
+
     deps = [
       ":android_utils",
       ":xr_jni_headers",
@@ -55,6 +62,10 @@
       deps += [ "//device/vr/android/arcore" ]
     }
 
+    if (enable_cardboard) {
+      deps += [ "//device/vr/android/cardboard:vr_cardboard" ]
+    }
+
     libs = [ "android" ]
   }
 }
diff --git a/device/vr/android/cardboard/cardboard_device_provider.cc b/components/webxr/android/cardboard_device_provider.cc
similarity index 69%
rename from device/vr/android/cardboard/cardboard_device_provider.cc
rename to components/webxr/android/cardboard_device_provider.cc
index 62037bb..c108665d 100644
--- a/device/vr/android/cardboard/cardboard_device_provider.cc
+++ b/components/webxr/android/cardboard_device_provider.cc
@@ -2,22 +2,23 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "device/vr/android/cardboard/cardboard_device_provider.h"
+#include "components/webxr/android/cardboard_device_provider.h"
 
 #include "device/vr/android/cardboard/cardboard_device.h"
 #include "device/vr/android/cardboard/cardboard_sdk_impl.h"
 
-namespace device {
+namespace webxr {
 
 CardboardDeviceProvider::CardboardDeviceProvider() = default;
-
 CardboardDeviceProvider::~CardboardDeviceProvider() = default;
 
-void CardboardDeviceProvider::Initialize(VRDeviceProviderClient* client) {
+void CardboardDeviceProvider::Initialize(
+    device::VRDeviceProviderClient* client) {
+  CHECK(!initialized_);
   DVLOG(2) << __func__ << ": Cardboard is supported, creating device";
 
-  cardboard_device_ =
-      std::make_unique<CardboardDevice>(std::make_unique<CardboardSdkImpl>());
+  cardboard_device_ = std::make_unique<device::CardboardDevice>(
+      std::make_unique<device::CardboardSdkImpl>());
 
   client->AddRuntime(cardboard_device_->GetId(),
                      cardboard_device_->GetDeviceData(),
@@ -30,4 +31,4 @@
   return initialized_;
 }
 
-}  // namespace device
+}  // namespace webxr
diff --git a/device/vr/android/cardboard/cardboard_device_provider.h b/components/webxr/android/cardboard_device_provider.h
similarity index 65%
rename from device/vr/android/cardboard/cardboard_device_provider.h
rename to components/webxr/android/cardboard_device_provider.h
index 88c2319e..7c8a9582 100644
--- a/device/vr/android/cardboard/cardboard_device_provider.h
+++ b/components/webxr/android/cardboard_device_provider.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 DEVICE_VR_ANDROID_CARDBOARD_CARDBOARD_DEVICE_PROVIDER_H_
-#define DEVICE_VR_ANDROID_CARDBOARD_CARDBOARD_DEVICE_PROVIDER_H_
+#ifndef COMPONENTS_WEBXR_ANDROID_CARDBOARD_DEVICE_PROVIDER_H_
+#define COMPONENTS_WEBXR_ANDROID_CARDBOARD_DEVICE_PROVIDER_H_
 
 #include <memory>
 
@@ -12,11 +12,12 @@
 
 namespace device {
 class CardboardDevice;
+}
+namespace webxr {
 
-class COMPONENT_EXPORT(VR_CARDBOARD) CardboardDeviceProvider
-    : public device::VRDeviceProvider {
+class CardboardDeviceProvider : public device::VRDeviceProvider {
  public:
-  explicit CardboardDeviceProvider();
+  CardboardDeviceProvider();
   ~CardboardDeviceProvider() override;
 
   CardboardDeviceProvider(const CardboardDeviceProvider&) = delete;
@@ -30,6 +31,6 @@
   bool initialized_ = false;
 };
 
-}  // namespace device
+}  // namespace webxr
 
-#endif  // DEVICE_VR_ANDROID_CARDBOARD_CARDBOARD_DEVICE_PROVIDER_H_
+#endif  // COMPONENTS_WEBXR_ANDROID_CARDBOARD_DEVICE_PROVIDER_H_
diff --git a/components/webxr/android/java/src/org/chromium/components/webxr/ArOverlayDelegate.java b/components/webxr/android/java/src/org/chromium/components/webxr/ArOverlayDelegate.java
index 6269134..fa9db20 100644
--- a/components/webxr/android/java/src/org/chromium/components/webxr/ArOverlayDelegate.java
+++ b/components/webxr/android/java/src/org/chromium/components/webxr/ArOverlayDelegate.java
@@ -4,6 +4,7 @@
 
 package org.chromium.components.webxr;
 
+import android.content.res.Configuration;
 import android.graphics.PixelFormat;
 import android.view.MotionEvent;
 import android.view.SurfaceView;
@@ -103,4 +104,9 @@
             mArCompositorDelegate.dispatchTouchEvent(ev);
         }
     }
+
+    @Override
+    public int getDesiredOrientation() {
+        return Configuration.ORIENTATION_UNDEFINED;
+    }
 }
diff --git a/components/webxr/android/java/src/org/chromium/components/webxr/XrImmersiveOverlay.java b/components/webxr/android/java/src/org/chromium/components/webxr/XrImmersiveOverlay.java
index 7adf5901..6ca0770d 100644
--- a/components/webxr/android/java/src/org/chromium/components/webxr/XrImmersiveOverlay.java
+++ b/components/webxr/android/java/src/org/chromium/components/webxr/XrImmersiveOverlay.java
@@ -7,6 +7,7 @@
 import android.annotation.SuppressLint;
 import android.app.Activity;
 import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
 import android.os.Build;
 import android.view.MotionEvent;
 import android.view.SurfaceHolder;
@@ -75,6 +76,15 @@
          * otherwise been consumed by the XrImmersiveOverlay; e.g. to the compositor.
          */
         void maybeForwardTouchEvent(MotionEvent ev);
+
+        /**
+         * Returns the desired @{link android.content.res.Configuration} int representing the
+         * orientation that the OverlayDelegate desires the device to be in. Should be one of:
+         * Configuration.ORIENTATION_LANDSCAPE
+         * Configuration.ORIENTATION_PORTRAIT
+         * Configuration.ORIENTATION_UNDEFINED
+         */
+        int getDesiredOrientation();
     }
 
     private static final String TAG = "XrImmersiveOverlay";
@@ -448,7 +458,18 @@
         if (mRestoreOrientation == null) {
             mRestoreOrientation = mActivity.getRequestedOrientation();
         }
-        mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
+
+        int desiredOrientation = mOverlayDelegate.getDesiredOrientation();
+        int currentOrientation = mActivity.getResources().getConfiguration().orientation;
+
+        int requestOrientation = configurationToActivityInfoOrientation(desiredOrientation);
+
+        // If we have a desired orientation and it does not equal the current orientation, then we
+        // will need to swap dimensions.
+        boolean swapScreenDimensions = desiredOrientation != Configuration.ORIENTATION_UNDEFINED
+                && desiredOrientation != currentOrientation;
+
+        mActivity.setRequestedOrientation(requestOrientation);
 
         // While it would be preferable to wait until the surface is at the desired fullscreen
         // resolution, i.e. via mActivity.getFullscreenManager().getPersistentFullscreenMode(), that
@@ -457,8 +478,10 @@
         // after the session starts, but the session doesn't start until we report the drawing
         // surface being ready (including a configured size), so we use the reported size of the
         // display assuming that's what the fullscreen mode will use.
-        int screenWidth = display.getDisplayWidth();
-        int screenHeight = display.getDisplayHeight();
+        int screenWidth =
+                !swapScreenDimensions ? display.getDisplayWidth() : display.getDisplayHeight();
+        int screenHeight =
+                !swapScreenDimensions ? display.getDisplayHeight() : display.getDisplayWidth();
 
         if (width < screenWidth || height < screenHeight) {
             if (DEBUG_LOGS) {
@@ -515,4 +538,29 @@
         if (mRestoreOrientation != null) mActivity.setRequestedOrientation(mRestoreOrientation);
         mRestoreOrientation = null;
     }
+
+    /**
+     * Translates the provided int, which is expected to be of the result of
+     * @{link Delegate.getDesiredOrientation} and thus one of:
+     * Configuration.ORIENTATION_UNDEFINED,
+     * Configuration.ORIENTATION_LANDSCAPE,
+     * Configuration.ORIENTATION_PORTRAIT
+     * and translates it to a corresponding "ActivityInfo" value that can then be passed on to
+     * setRequestedOrientation.
+     */
+    private int configurationToActivityInfoOrientation(int configurationOrientation) {
+        switch (configurationOrientation) {
+            case Configuration.ORIENTATION_UNDEFINED:
+                return ActivityInfo.SCREEN_ORIENTATION_LOCKED;
+            case Configuration.ORIENTATION_LANDSCAPE:
+                return ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+            case Configuration.ORIENTATION_PORTRAIT:
+                return ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+            default:
+                Log.e(TAG,
+                        "Unexpected configurationOrientation: " + configurationOrientation
+                                + " using default of 'Locked'.");
+                return ActivityInfo.SCREEN_ORIENTATION_LOCKED;
+        }
+    }
 }
diff --git a/content/browser/accessibility/browser_accessibility_android.cc b/content/browser/accessibility/browser_accessibility_android.cc
index 96b0ed7..89c726da 100644
--- a/content/browser/accessibility/browser_accessibility_android.cc
+++ b/content/browser/accessibility/browser_accessibility_android.cc
@@ -533,11 +533,8 @@
 }
 
 bool BrowserAccessibilityAndroid::IsLeaf() const {
-  if (base::FeatureList::IsEnabled(
-          features::kOptimizeAccessibilityUiThreadWork)) {
-    if (g_leaf_map.Get().find(this) != g_leaf_map.Get().end()) {
-      return g_leaf_map.Get()[this];
-    }
+  if (g_leaf_map.Get().find(this) != g_leaf_map.Get().end()) {
+    return g_leaf_map.Get()[this];
   }
 
   if (BrowserAccessibility::IsLeaf()) {
@@ -744,9 +741,7 @@
     for (auto it = InternalChildrenBegin(); it != InternalChildrenEnd(); ++it) {
       text += static_cast<BrowserAccessibilityAndroid*>(it.get())
                   ->GetSubstringTextContentUTF16(predicate);
-      if (base::FeatureList::IsEnabled(
-              features::kOptimizeAccessibilityUiThreadWork) &&
-          predicate && predicate.value().Run(text)) {
+      if (predicate && predicate.value().Run(text)) {
         break;
       }
     }
diff --git a/content/browser/attribution_reporting/attribution_host_unittest.cc b/content/browser/attribution_reporting/attribution_host_unittest.cc
index 67c189255..d17df8f 100644
--- a/content/browser/attribution_reporting/attribution_host_unittest.cc
+++ b/content/browser/attribution_reporting/attribution_host_unittest.cc
@@ -681,6 +681,7 @@
             {blink::OriginWithPossibleWildcards(
                 url::Origin::Create(GURL(kAllowedOriginUrl)),
                 /*has_subdomain_wildcard=*/false)},
+            /*self_if_matches=*/absl::nullopt,
             /*matches_all_origins=*/false, /*matches_opaque_src=*/false)});
     simulator->Commit();
     fenced_frame = simulator->GetFinalRenderFrameHost();
@@ -713,10 +714,8 @@
     simulator1->SetPermissionsPolicyHeader(
         {blink::ParsedPermissionsPolicyDeclaration(
             blink::mojom::PermissionsPolicyFeature::kAttributionReporting,
-            /*allowed_origins=*/
-            {blink::OriginWithPossibleWildcards(
-                url::Origin::Create(GURL(kAllowedOriginUrl)),
-                /*has_subdomain_wildcard=*/false)},
+            /*allowed_origins=*/{},
+            /*self_if_matches*/ url::Origin::Create(GURL(kAllowedOriginUrl)),
             /*matches_all_origins=*/false, /*matches_opaque_src=*/false)});
     simulator1->Commit();
 
diff --git a/content/browser/attribution_reporting/attributions_browsertest.cc b/content/browser/attribution_reporting/attributions_browsertest.cc
index 75c5d3f..39184606 100644
--- a/content/browser/attribution_reporting/attributions_browsertest.cc
+++ b/content/browser/attribution_reporting/attributions_browsertest.cc
@@ -1691,7 +1691,6 @@
       scoped_refptr<FencedFrameReporter> fenced_frame_reporter) {
     static constexpr char kAddFencedFrameScript[] = R"({
         var f = document.createElement('fencedframe');
-        f.mode = 'opaque-ads';
         document.body.appendChild(f);
     })";
     EXPECT_TRUE(ExecJs(root, kAddFencedFrameScript));
diff --git a/content/browser/browsing_topics/browsing_topics_url_loader_service_unittest.cc b/content/browser/browsing_topics/browsing_topics_url_loader_service_unittest.cc
index e8372bf3..4c5f668b 100644
--- a/content/browser/browsing_topics/browsing_topics_url_loader_service_unittest.cc
+++ b/content/browser/browsing_topics/browsing_topics_url_loader_service_unittest.cc
@@ -172,6 +172,7 @@
                             blink::OriginWithPossibleWildcards(
                                 url::Origin::Create(GURL("https://foo3.com")),
                                 /*has_subdomain_wildcard=*/false)},
+                        /*self_if_matches=*/absl::nullopt,
                         /*matches_all_origins=*/false,
                         /*matches_opaque_src=*/false);
 
diff --git a/content/browser/direct_sockets/direct_sockets_test_utils.cc b/content/browser/direct_sockets/direct_sockets_test_utils.cc
index 4835087..2764d99 100644
--- a/content/browser/direct_sockets/direct_sockets_test_utils.cc
+++ b/content/browser/direct_sockets/direct_sockets_test_utils.cc
@@ -178,6 +178,7 @@
       /*allowed_origins=*/
       {blink::OriginWithPossibleWildcards(app_origin,
                                           /*has_subdomain_wildcard=*/false)},
+      /*self_if_matches=*/absl::nullopt,
       /*matches_all_origins=*/false, /*matches_opaque_src=*/false);
   out.push_back(decl);
   return out;
diff --git a/content/browser/fenced_frame/fenced_frame_browsertest.cc b/content/browser/fenced_frame/fenced_frame_browsertest.cc
index 82d5620..ebbd36a 100644
--- a/content/browser/fenced_frame/fenced_frame_browsertest.cc
+++ b/content/browser/fenced_frame/fenced_frame_browsertest.cc
@@ -2195,7 +2195,6 @@
   {
     EXPECT_TRUE(ExecJs(root,
                        "var f = document.createElement('fencedframe');"
-                       "f.mode = 'opaque-ads';"
                        "document.body.appendChild(f);"));
   }
   EXPECT_EQ(1U, root->child_count());
@@ -2292,7 +2291,6 @@
 
   EXPECT_TRUE(ExecJs(root,
                      "var f1 = document.createElement('fencedframe');"
-                     "f1.mode = 'opaque-ads';"
                      "document.body.appendChild(f1);"));
 
   EXPECT_EQ(1U, root->child_count());
@@ -2319,7 +2317,6 @@
 
   EXPECT_TRUE(ExecJs(fenced_frame_root_node1,
                      "var f2 = document.createElement('fencedframe');"
-                     "f2.mode = 'opaque-ads';"
                      "document.body.appendChild(f2);"));
 
   EXPECT_EQ(1U, fenced_frame_root_node1->child_count());
@@ -2357,12 +2354,10 @@
   {
     EXPECT_TRUE(ExecJs(root,
                        "var f1 = document.createElement('fencedframe');"
-                       "f1.mode = 'opaque-ads';"
                        "document.body.appendChild(f1);"));
 
     EXPECT_TRUE(ExecJs(root,
                        "var f2 = document.createElement('fencedframe');"
-                       "f2.mode = 'opaque-ads';"
                        "document.body.appendChild(f2);"));
   }
 
@@ -2421,7 +2416,6 @@
   {
     EXPECT_TRUE(ExecJs(root,
                        "var f = document.createElement('fencedframe');"
-                       "f.mode = 'opaque-ads';"
                        "document.body.appendChild(f);"));
   }
   EXPECT_EQ(1U, root->child_count());
@@ -2500,7 +2494,6 @@
   {
     EXPECT_TRUE(ExecJs(root,
                        "var f = document.createElement('fencedframe');"
-                       "f.mode = 'opaque-ads';"
                        "document.body.appendChild(f);"));
   }
   EXPECT_EQ(1U, root->child_count());
@@ -2570,7 +2563,6 @@
   {
     EXPECT_TRUE(ExecJs(root,
                        "var f = document.createElement('fencedframe');"
-                       "f.mode = 'opaque-ads';"
                        "document.body.appendChild(f);"));
   }
   EXPECT_EQ(1U, root->child_count());
@@ -2648,7 +2640,6 @@
   // Test the fenced frame.
   EXPECT_TRUE(ExecJs(root_rfh,
                      "var f = document.createElement('fencedframe');"
-                     "f.mode = 'opaque-ads';"
                      "document.body.appendChild(f);"));
   EXPECT_EQ(1U, root_rfh->child_count());
 
@@ -2731,7 +2722,6 @@
   // Add and navigate a fenced frame.
   EXPECT_TRUE(ExecJs(root_rfh,
                      "var f = document.createElement('fencedframe');"
-                     "f.mode = 'opaque-ads';"
                      "document.body.appendChild(f);"));
   EXPECT_EQ(1U, root_rfh->child_count());
   FrameTreeNode* fenced_frame_root_node =
@@ -3074,7 +3064,6 @@
   {
     EXPECT_TRUE(ExecJs(root,
                        "var f = document.createElement('fencedframe');"
-                       "f.mode = 'opaque-ads';"
                        "document.body.appendChild(f);"));
   }
   EXPECT_EQ(1U, root->child_count());
@@ -3230,7 +3219,6 @@
   // Create a fenced frame.
   EXPECT_TRUE(ExecJs(root,
                      "var f = document.createElement('fencedframe');"
-                     "f.mode = 'opaque-ads';"
                      "document.body.appendChild(f);"));
 
   EXPECT_EQ(1U, root->child_count());
@@ -3284,7 +3272,6 @@
   // Create a fenced frame.
   EXPECT_TRUE(ExecJs(root,
                      "var f = document.createElement('fencedframe');"
-                     "f.mode = 'opaque-ads';"
                      "document.body.appendChild(f);"));
 
   EXPECT_EQ(1U, root->child_count());
@@ -4033,7 +4020,6 @@
   {
     EXPECT_TRUE(ExecJs(root,
                        "var f = document.createElement('fencedframe');"
-                       "f.mode = 'opaque-ads';"
                        "document.body.appendChild(f);"));
   }
   EXPECT_EQ(1U, root->child_count());
@@ -4090,7 +4076,6 @@
 
     EXPECT_TRUE(ExecJs(root,
                        "var f = document.createElement('fencedframe');"
-                       "f.mode = 'opaque-ads';"
                        "document.body.appendChild(f);"));
 
     EXPECT_EQ(1U, root->child_count());
@@ -4421,7 +4406,6 @@
         nodeA,
         JsReplace(
             "var nested_fenced_frame = document.createElement('fencedframe');"
-            "nested_fenced_frame.mode = 'opaque-ads';"
             "nested_fenced_frame.width = $1;"
             "nested_fenced_frame.height = $2;"
             "document.body.appendChild(nested_fenced_frame);",
@@ -4761,7 +4745,6 @@
     EXPECT_TRUE(
         ExecJs(root,
                "var fenced_frame = document.createElement('fencedframe');"
-               "fenced_frame.mode = 'opaque-ads';"
                "document.body.appendChild(fenced_frame);"));
     EXPECT_EQ(1U, root->child_count());
     FrameTreeNode* fenced_frame_root_node =
@@ -4965,7 +4948,6 @@
                             .root();
   EXPECT_TRUE(ExecJs(root,
                      "var f = document.createElement('fencedframe');"
-                     "f.mode = 'default';"
                      "document.body.appendChild(f);"));
 
   EXPECT_EQ(1U, root->child_count());
@@ -5406,7 +5388,6 @@
                             .root();
   EXPECT_TRUE(ExecJs(root,
                      "var f = document.createElement('fencedframe');"
-                     "f.mode = 'opaque-ads';"
                      "document.body.appendChild(f);"));
 
   EXPECT_EQ(1U, root->child_count());
@@ -5538,7 +5519,6 @@
                             .root();
   EXPECT_TRUE(ExecJs(root,
                      "var f = document.createElement('fencedframe');"
-                     "f.mode = 'opaque-ads';"
                      "document.body.appendChild(f);"));
 
   EXPECT_EQ(1U, root->child_count());
@@ -5713,7 +5693,6 @@
                             .root();
   EXPECT_TRUE(ExecJs(root,
                      "var f = document.createElement('fencedframe');"
-                     "f.mode = 'opaque-ads';"
                      "document.body.appendChild(f);"));
 
   EXPECT_EQ(1U, root->child_count());
@@ -5798,7 +5777,6 @@
                             .root();
   EXPECT_TRUE(ExecJs(root,
                      "var f = document.createElement('fencedframe');"
-                     "f.mode = 'opaque-ads';"
                      "document.body.appendChild(f);"));
 
   EXPECT_EQ(1U, root->child_count());
@@ -6220,7 +6198,6 @@
 
     EXPECT_TRUE(
         ExecJs(root, JsReplace("var ad_frame = document.createElement($1);"
-                               "ad_frame.mode = 'opaque-ads';"
                                "document.body.appendChild(ad_frame);",
                                GetParam())));
 
diff --git a/content/browser/geolocation/geolocation_service_impl_unittest.cc b/content/browser/geolocation/geolocation_service_impl_unittest.cc
index 731afae..2f23dd0 100644
--- a/content/browser/geolocation/geolocation_service_impl_unittest.cc
+++ b/content/browser/geolocation/geolocation_service_impl_unittest.cc
@@ -109,7 +109,9 @@
            std::vector{blink::OriginWithPossibleWildcards(
                url::Origin::Create(kEmbeddedUrl),
                /*has_subdomain_wildcard=*/false)},
-           false, false});
+           /*self_if_matches=*/absl::nullopt,
+           /*matches_all_origins=*/false,
+           /*matches_opaque_src=*/false});
     }
     RenderFrameHost* embedded_rfh =
         RenderFrameHostTester::For(main_rfh())
diff --git a/content/browser/interest_group/ad_auction_service_impl_unittest.cc b/content/browser/interest_group/ad_auction_service_impl_unittest.cc
index e422593..1cf29ef 100644
--- a/content/browser/interest_group/ad_auction_service_impl_unittest.cc
+++ b/content/browser/interest_group/ad_auction_service_impl_unittest.cc
@@ -6453,6 +6453,7 @@
       std::vector<blink::OriginWithPossibleWildcards>{
           blink::OriginWithPossibleWildcards(kOriginA,
                                              /*has_subdomain_wildcard=*/false)},
+      /*self_if_matches=*/absl::nullopt,
       /*matches_all_origins=*/false,
       /*matches_opaque_src=*/false);
   simulator->SetPermissionsPolicyHeader(std::move(policy));
@@ -6664,6 +6665,7 @@
             blink::OriginWithPossibleWildcards(
                 kOriginA,
                 /*has_subdomain_wildcard=*/false)},
+        /*self_if_matches=*/absl::nullopt,
         /*matches_all_origins=*/false,
         /*matches_opaque_src=*/false);
     simulator->SetPermissionsPolicyHeader(std::move(policy));
@@ -6694,6 +6696,7 @@
             blink::OriginWithPossibleWildcards(
                 kOriginC,
                 /*has_subdomain_wildcard=*/false)},
+        /*self_if_matches=*/absl::nullopt,
         /*matches_all_origins=*/false,
         /*matches_opaque_src=*/false);
     simulator->SetPermissionsPolicyHeader(std::move(policy));
@@ -6755,6 +6758,7 @@
             blink::OriginWithPossibleWildcards(
                 kOriginA,
                 /*has_subdomain_wildcard=*/false)},
+        /*self_if_matches=*/absl::nullopt,
         /*matches_all_origins=*/false,
         /*matches_opaque_src=*/false);
     simulator->SetPermissionsPolicyHeader(std::move(policy));
@@ -6785,6 +6789,7 @@
             blink::OriginWithPossibleWildcards(
                 kOriginC,
                 /*has_subdomain_wildcard=*/false)},
+        /*self_if_matches=*/absl::nullopt,
         /*matches_all_origins=*/false,
         /*matches_opaque_src=*/false);
     simulator->SetPermissionsPolicyHeader(std::move(policy));
diff --git a/content/browser/interest_group/interest_group_browsertest.cc b/content/browser/interest_group/interest_group_browsertest.cc
index f7ad5c01..cb6c452 100644
--- a/content/browser/interest_group/interest_group_browsertest.cc
+++ b/content/browser/interest_group/interest_group_browsertest.cc
@@ -8180,7 +8180,6 @@
       EXPECT_TRUE(ExecJs(shell(),
                          "document.querySelector('fencedframe').remove();"
                          "const ff = document.createElement('fencedframe');"
-                         "ff.mode = 'opaque-ads';"
                          "document.body.appendChild(ff);"));
     }
     ClearReceivedRequests();
diff --git a/content/browser/media/media_devices_permission_checker_unittest.cc b/content/browser/media/media_devices_permission_checker_unittest.cc
index 389ed7782..287615e 100644
--- a/content/browser/media/media_devices_permission_checker_unittest.cc
+++ b/content/browser/media/media_devices_permission_checker_unittest.cc
@@ -57,8 +57,10 @@
     std::vector<blink::OriginWithPossibleWildcards> allowlist;
     if (enabled)
       allowlist.emplace_back(origin_, /*has_subdomain_wildcard=*/false);
-    navigation->SetPermissionsPolicyHeader(
-        {{feature, allowlist, false, false}});
+    navigation->SetPermissionsPolicyHeader({{feature, allowlist,
+                                             /*self_if_matches=*/absl::nullopt,
+                                             /*matches_all_origins=*/false,
+                                             /*matches_opaque_src=*/false}});
     navigation->Commit();
   }
 
diff --git a/content/browser/preloading/prefetch/prefetch_streaming_url_loader.cc b/content/browser/preloading/prefetch/prefetch_streaming_url_loader.cc
index bbcc4d76..4613f37 100644
--- a/content/browser/preloading/prefetch/prefetch_streaming_url_loader.cc
+++ b/content/browser/preloading/prefetch/prefetch_streaming_url_loader.cc
@@ -340,6 +340,9 @@
   DCHECK(!serving_url_loader_receiver_.is_bound());
   DCHECK(!self || self.get() == this);
 
+  // Once the prefetch is served, stop the timeout timer.
+  timeout_timer_.AbandonAndStop();
+
   status_ =
       completion_status_.has_value()
           ? PrefetchStreamingURLLoaderStatus::kSuccessfulServedAfterCompletion
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 0f95427..e04e6fb 100644
--- a/content/browser/preloading/prefetch/prefetch_streaming_url_loader_unittest.cc
+++ b/content/browser/preloading/prefetch/prefetch_streaming_url_loader_unittest.cc
@@ -1268,6 +1268,109 @@
       PrefetchStreamingURLLoaderStatus::kFailedNetError, 1);
 }
 
+TEST_F(PrefetchStreamingURLLoaderTest, StopTimeoutTimerAfterBeingServed) {
+  base::HistogramTester histogram_tester;
+  const GURL kTestUrl = GURL("https://example.com");
+  const std::string kBodyContent = "example body";
+
+  std::unique_ptr<network::ResourceRequest> prefetch_request =
+      std::make_unique<network::ResourceRequest>();
+  prefetch_request->url = kTestUrl;
+  prefetch_request->method = "GET";
+
+  base::RunLoop on_response_received_loop;
+  base::RunLoop on_response_complete_loop;
+
+  // Create the |PrefetchStreamingURLLoader| that is being tested.
+  std::unique_ptr<PrefetchStreamingURLLoader> streaming_loader =
+      std::make_unique<PrefetchStreamingURLLoader>(
+          test_url_loader_factory(), std::move(prefetch_request),
+          TRAFFIC_ANNOTATION_FOR_TESTS, /*timeout_duration=*/base::Seconds(1),
+          base::BindOnce(
+              [](base::RunLoop* on_response_received_loop,
+                 network::mojom::URLResponseHead* head) {
+                on_response_received_loop->Quit();
+                return PrefetchStreamingURLLoaderStatus::
+                    kHeadReceivedWaitingOnBody;
+              },
+              &on_response_received_loop),
+          base::BindOnce(
+              [](base::RunLoop* on_response_complete_loop,
+                 const network::URLLoaderCompletionStatus& completion_status) {
+                EXPECT_EQ(completion_status.error_code, net::OK);
+                on_response_complete_loop->Quit();
+              },
+              &on_response_complete_loop),
+          base::BindRepeating(
+              [](const net::RedirectInfo& redirect_info,
+                 const network::mojom::URLResponseHead& response_head) {
+                NOTREACHED();
+                return PrefetchStreamingURLLoaderStatus::kFollowRedirect;
+              }));
+
+  // Simulates receiving the head of the prefetch response.
+  test_url_loader_factory()->SimulateReceiveHead(net::HTTP_OK,
+                                                 kBodyContent.size());
+  on_response_received_loop.Run();
+
+  EXPECT_TRUE(streaming_loader->Servable(base::TimeDelta::Max()));
+
+  // Simulate serving the prefetch. This should stop the timeout timer.
+  base::WeakPtr<PrefetchStreamingURLLoader> weak_streaming_loader =
+      streaming_loader->GetWeakPtr();
+  PrefetchStreamingURLLoader::RequestHandler request_handler =
+      weak_streaming_loader->ServingFinalResponseHandler(
+          std::move(streaming_loader));
+
+  std::unique_ptr<TestURLLoaderClient> serving_url_loader_client =
+      std::make_unique<TestURLLoaderClient>();
+
+  network::ResourceRequest serving_request;
+  serving_request.url = kTestUrl;
+  serving_request.method = "GET";
+
+  std::move(request_handler)
+      .Run(serving_request,
+           serving_url_loader_client->BindURLloaderAndGetReceiver(),
+           serving_url_loader_client->BindURLLoaderClientAndGetRemote());
+
+  // Since the prefetch has been served, the timeout trigger should not be
+  // triggered.
+  task_environment()->FastForwardBy(base::Seconds(10));
+
+  // Simulate receiving the body of the response.
+  test_url_loader_factory()->SimulateReceiveData(kBodyContent);
+  test_url_loader_factory()->SimulateResponseComplete(net::OK);
+  on_response_complete_loop.Run();
+
+  EXPECT_TRUE(weak_streaming_loader->Servable(base::TimeDelta::Max()));
+
+  test_url_loader_factory()->DisconnectMojoPipes();
+
+  // Wait for the data to be drained from the body pipe.
+  task_environment()->RunUntilIdle();
+
+  EXPECT_TRUE(serving_url_loader_client->body_finished());
+  EXPECT_EQ(serving_url_loader_client->body_content(), kBodyContent);
+  EXPECT_EQ(serving_url_loader_client->total_bytes_read(), kBodyContent.size());
+
+  EXPECT_TRUE(serving_url_loader_client->completion_status());
+  EXPECT_EQ(serving_url_loader_client->completion_status()->error_code,
+            net::OK);
+
+  serving_url_loader_client->DisconnectMojoPipes();
+  task_environment()->RunUntilIdle();
+
+  // Once the streaming URL loader serves is finished (all prefetched data
+  // received and served) and all mojo pipes are disconnected, it should delete
+  // itself.
+  EXPECT_FALSE(weak_streaming_loader);
+
+  histogram_tester.ExpectUniqueSample(
+      "PrefetchProxy.Prefetch.StreamingURLLoaderFinalStatus",
+      PrefetchStreamingURLLoaderStatus::kSuccessfulServedBeforeCompletion, 1);
+}
+
 TEST_F(PrefetchStreamingURLLoaderTest, StaleResponse) {
   base::HistogramTester histogram_tester;
   const GURL kTestUrl = GURL("https://example.com");
diff --git a/content/browser/renderer_host/media/media_stream_ui_proxy_unittest.cc b/content/browser/renderer_host/media/media_stream_ui_proxy_unittest.cc
index 8be1987..8d9f9f25 100644
--- a/content/browser/renderer_host/media/media_stream_ui_proxy_unittest.cc
+++ b/content/browser/renderer_host/media/media_stream_ui_proxy_unittest.cc
@@ -555,7 +555,10 @@
       blink::mojom::PermissionsPolicyFeature feature) {
     auto navigation = NavigationSimulator::CreateRendererInitiated(
         main_rfh()->GetLastCommittedURL(), main_rfh());
-    navigation->SetPermissionsPolicyHeader({{feature, {}, false, false}});
+    navigation->SetPermissionsPolicyHeader(
+        {{feature, /*allowed_origins=*/{}, /*self_if_matches=*/absl::nullopt,
+          /*matches_all_origins=*/false,
+          /*matches_opaque_src=*/false}});
     navigation->Commit();
   }
 
diff --git a/content/browser/serial/serial_unittest.cc b/content/browser/serial/serial_unittest.cc
index 7b1643d..3633599c 100644
--- a/content/browser/serial/serial_unittest.cc
+++ b/content/browser/serial/serial_unittest.cc
@@ -491,9 +491,8 @@
           ->AppendChildWithPolicy(
               "embedded_frame",
               {{blink::mojom::PermissionsPolicyFeature::kSerial,
-                std::vector{blink::OriginWithPossibleWildcards(
-                    url::Origin::Create(kEmbeddedUrl),
-                    /*has_subdomain_wildcard=*/false)},
+                /*allowed_origins=*/{},
+                /*self_if_matches=*/url::Origin::Create(kEmbeddedUrl),
                 /*matches_all_origins=*/false, /*matches_opaque_src=*/true}});
   embedded_rfh = NavigationSimulator::NavigateAndCommitFromDocument(
       kEmbeddedUrl, embedded_rfh);
diff --git a/content/browser/service_worker/service_worker_context_core.cc b/content/browser/service_worker/service_worker_context_core.cc
index e998d341..5cd0a30 100644
--- a/content/browser/service_worker/service_worker_context_core.cc
+++ b/content/browser/service_worker/service_worker_context_core.cc
@@ -565,6 +565,15 @@
     bool is_immediate,
     UnregistrationCallback callback) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+  BrowserContext* browser_context = wrapper_->browser_context();
+  DCHECK(browser_context);
+  if (!GetContentClient()->browser()->MayDeleteServiceWorkerRegistration(
+          scope, browser_context)) {
+    std::move(callback).Run(blink::ServiceWorkerStatusCode::kErrorDisallowed);
+    return;
+  }
+
   job_coordinator_->Unregister(
       scope, key, is_immediate,
       base::BindOnce(&ServiceWorkerContextCore::UnregistrationComplete,
@@ -613,7 +622,19 @@
     return;
   }
 
-  int* expected_calls = new int(2 * registrations.size());
+  // Ignore any registrations we are not permitted to delete.
+  std::vector<scoped_refptr<ServiceWorkerRegistration>> filtered_registrations;
+  ContentBrowserClient* browser_client = GetContentClient()->browser();
+  BrowserContext* browser_context = wrapper_->browser_context();
+  DCHECK(browser_context);
+  for (auto registration : registrations) {
+    if (browser_client->MayDeleteServiceWorkerRegistration(
+            registration->scope(), browser_context)) {
+      filtered_registrations.push_back(std::move(registration));
+    }
+  }
+
+  int* expected_calls = new int(2 * filtered_registrations.size());
   auto* listeners =
       new std::vector<std::unique_ptr<RegistrationDeletionListener>>();
 
@@ -624,7 +645,7 @@
       base::BindRepeating(SuccessReportingCallback, base::Owned(expected_calls),
                           base::Owned(listeners),
                           base::OwnedRef(std::move(callback)));
-  for (const auto& registration : registrations) {
+  for (const auto& registration : filtered_registrations) {
     DCHECK(registration);
     if (*expected_calls != -1) {
       if (!registration->is_deleted()) {
diff --git a/content/browser/shared_storage/shared_storage_browsertest.cc b/content/browser/shared_storage/shared_storage_browsertest.cc
index ab582d7..bc898ee3 100644
--- a/content/browser/shared_storage/shared_storage_browsertest.cc
+++ b/content/browser/shared_storage/shared_storage_browsertest.cc
@@ -4853,7 +4853,6 @@
 
   EXPECT_TRUE(ExecJs(root,
                      "var f = document.createElement('fencedframe');"
-                     "f.mode = 'opaque-ads';"
                      "document.body.appendChild(f);"));
 
   EXPECT_EQ(1U, root->child_count());
@@ -4956,7 +4955,6 @@
 
   EXPECT_TRUE(ExecJs(root,
                      "var f = document.createElement('fencedframe');"
-                     "f.mode = 'opaque-ads';"
                      "document.body.appendChild(f);"));
 
   EXPECT_EQ(1U, root->child_count());
@@ -5160,7 +5158,6 @@
 
   EXPECT_TRUE(ExecJs(root,
                      "var f = document.createElement('fencedframe');"
-                     "f.mode = 'opaque-ads';"
                      "document.body.appendChild(f);"));
 
   EXPECT_EQ(2U, root->child_count());
@@ -5328,7 +5325,6 @@
 
   EXPECT_TRUE(ExecJs(root,
                      "var f = document.createElement('fencedframe');"
-                     "f.mode = 'opaque-ads';"
                      "document.body.appendChild(f);"));
 
   EXPECT_EQ(1U, root->child_count());
@@ -5448,7 +5444,6 @@
 
   EXPECT_TRUE(ExecJs(root,
                      "var f = document.createElement('fencedframe');"
-                     "f.mode = 'opaque-ads';"
                      "document.body.appendChild(f);"));
 
   EXPECT_EQ(1U, root->child_count());
diff --git a/content/browser/site_per_process_browsertest.cc b/content/browser/site_per_process_browsertest.cc
index 19726384..0fa82f5e 100644
--- a/content/browser/site_per_process_browsertest.cc
+++ b/content/browser/site_per_process_browsertest.cc
@@ -371,10 +371,14 @@
 CreateParsedPermissionsPolicyDeclaration(
     blink::mojom::PermissionsPolicyFeature feature,
     const std::vector<GURL>& origins,
-    bool match_all_origins = false) {
+    bool match_all_origins = false,
+    const absl::optional<GURL> self_if_matches = absl::nullopt) {
   blink::ParsedPermissionsPolicyDeclaration declaration;
 
   declaration.feature = feature;
+  if (self_if_matches.has_value()) {
+    declaration.self_if_matches = url::Origin::Create(*self_if_matches);
+  }
   declaration.matches_all_origins = match_all_origins;
   declaration.matches_opaque_src = match_all_origins;
 
@@ -391,15 +395,22 @@
 blink::ParsedPermissionsPolicy CreateParsedPermissionsPolicy(
     const std::vector<blink::mojom::PermissionsPolicyFeature>& features,
     const std::vector<GURL>& origins,
-    bool match_all_origins = false) {
+    bool match_all_origins = false,
+    const absl::optional<GURL> self_if_matches = absl::nullopt) {
   blink::ParsedPermissionsPolicy result;
   result.reserve(features.size());
   for (const auto& feature : features)
     result.push_back(CreateParsedPermissionsPolicyDeclaration(
-        feature, origins, match_all_origins));
+        feature, origins, match_all_origins, self_if_matches));
   return result;
 }
 
+blink::ParsedPermissionsPolicy CreateParsedPermissionsPolicyMatchesSelf(
+    const std::vector<blink::mojom::PermissionsPolicyFeature>& features,
+    const GURL& self_if_matches) {
+  return CreateParsedPermissionsPolicy(features, {}, false, self_if_matches);
+}
+
 blink::ParsedPermissionsPolicy CreateParsedPermissionsPolicyMatchesAll(
     const std::vector<blink::mojom::PermissionsPolicyFeature>& features) {
   return CreateParsedPermissionsPolicy(features, {}, true);
@@ -6800,9 +6811,9 @@
   EXPECT_TRUE(NavigateToURL(shell(), url));
 
   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
-  EXPECT_EQ(CreateParsedPermissionsPolicy(
+  EXPECT_EQ(CreateParsedPermissionsPolicyMatchesSelf(
                 {blink::mojom::PermissionsPolicyFeature::kGeolocation},
-                {url.DeprecatedGetOriginAsURL()}),
+                url.DeprecatedGetOriginAsURL()),
             root->current_replication_state().permissions_policy_header);
 }
 
@@ -6817,10 +6828,10 @@
   EXPECT_TRUE(NavigateToURL(shell(), start_url));
 
   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
-  EXPECT_EQ(CreateParsedPermissionsPolicy(
+  EXPECT_EQ(CreateParsedPermissionsPolicyMatchesSelf(
                 {blink::mojom::PermissionsPolicyFeature::kGeolocation,
                  blink::mojom::PermissionsPolicyFeature::kPayment},
-                {start_url.DeprecatedGetOriginAsURL()}),
+                start_url.DeprecatedGetOriginAsURL()),
             root->current_replication_state().permissions_policy_header);
 
   // When the main frame navigates to a page with a new policy, it should
@@ -6849,10 +6860,10 @@
   EXPECT_TRUE(NavigateToURL(shell(), start_url));
 
   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
-  EXPECT_EQ(CreateParsedPermissionsPolicy(
+  EXPECT_EQ(CreateParsedPermissionsPolicyMatchesSelf(
                 {blink::mojom::PermissionsPolicyFeature::kGeolocation,
                  blink::mojom::PermissionsPolicyFeature::kPayment},
-                {start_url.DeprecatedGetOriginAsURL()}),
+                start_url.DeprecatedGetOriginAsURL()),
             root->current_replication_state().permissions_policy_header);
 
   // When the main frame navigates to a page with a new policy, it should
@@ -6883,18 +6894,18 @@
   EXPECT_TRUE(NavigateToURL(shell(), main_url));
 
   FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
-  EXPECT_EQ(
-      CreateParsedPermissionsPolicy(
-          {blink::mojom::PermissionsPolicyFeature::kGeolocation,
-           blink::mojom::PermissionsPolicyFeature::kPayment},
-          {main_url.DeprecatedGetOriginAsURL(), GURL("http://example.com/")}),
-      root->current_replication_state().permissions_policy_header);
+  EXPECT_EQ(CreateParsedPermissionsPolicy(
+                {blink::mojom::PermissionsPolicyFeature::kGeolocation,
+                 blink::mojom::PermissionsPolicyFeature::kPayment},
+                {GURL("http://example.com/")}, /*match_all_origins=*/false,
+                main_url.DeprecatedGetOriginAsURL()),
+            root->current_replication_state().permissions_policy_header);
   EXPECT_EQ(1UL, root->child_count());
   EXPECT_EQ(
-      CreateParsedPermissionsPolicy(
+      CreateParsedPermissionsPolicyMatchesSelf(
           {blink::mojom::PermissionsPolicyFeature::kGeolocation,
            blink::mojom::PermissionsPolicyFeature::kPayment},
-          {main_url.DeprecatedGetOriginAsURL()}),
+          main_url.DeprecatedGetOriginAsURL()),
       root->child_at(0)->current_replication_state().permissions_policy_header);
 
   // Navigate the iframe cross-site.
diff --git a/content/browser/smart_card/smart_card_browsertest.cc b/content/browser/smart_card/smart_card_browsertest.cc
index 31dc608..7f8f2b4 100644
--- a/content/browser/smart_card/smart_card_browsertest.cc
+++ b/content/browser/smart_card/smart_card_browsertest.cc
@@ -172,10 +172,9 @@
   blink::ParsedPermissionsPolicy out;
   blink::ParsedPermissionsPolicyDeclaration decl(
       blink::mojom::PermissionsPolicyFeature::kSmartCard,
-      /*allowed_origins=*/
-      {blink::OriginWithPossibleWildcards(app_origin,
-                                          /*has_subdomain_wildcard=*/false)},
-      /*matches_all_origins=*/false, /*matches_opaque_src=*/false);
+      /*allowed_origins=*/{},
+      /*self_if_matches=*/app_origin, /*matches_all_origins=*/false,
+      /*matches_opaque_src=*/false);
   out.push_back(decl);
   return out;
 }
diff --git a/content/browser/usb/web_usb_service_impl_unittest.cc b/content/browser/usb/web_usb_service_impl_unittest.cc
index 24c2e16..6ab4a98 100644
--- a/content/browser/usb/web_usb_service_impl_unittest.cc
+++ b/content/browser/usb/web_usb_service_impl_unittest.cc
@@ -436,9 +436,8 @@
           ->AppendChildWithPolicy(
               "embedded_frame",
               {{blink::mojom::PermissionsPolicyFeature::kUsb,
-                std::vector{blink::OriginWithPossibleWildcards(
-                    url::Origin::Create(kEmbeddedUrl),
-                    /*has_subdomain_wildcard=*/false)},
+                /*allowed_origins=*/{},
+                /*self_if_matches=*/url::Origin::Create(kEmbeddedUrl),
                 /*matches_all_origins=*/false, /*matches_opaque_src=*/true}});
   embedded_rfh = NavigationSimulator::NavigateAndCommitFromDocument(
       kEmbeddedUrl, embedded_rfh);
diff --git a/content/browser/webauth/webauth_request_security_checker_unittest.cc b/content/browser/webauth/webauth_request_security_checker_unittest.cc
index a8f6d1d..fb5588e 100644
--- a/content/browser/webauth/webauth_request_security_checker_unittest.cc
+++ b/content/browser/webauth/webauth_request_security_checker_unittest.cc
@@ -27,7 +27,8 @@
 blink::ParsedPermissionsPolicy CreatePolicyToAllowWebAuthn() {
   return {blink::ParsedPermissionsPolicyDeclaration(
       blink::mojom::PermissionsPolicyFeature::kPublicKeyCredentialsGet,
-      /*values=*/{}, /*matches_all_origins=*/true,
+      /*allowed_origins=*/{}, /*self_if_matches=*/absl::nullopt,
+      /*matches_all_origins=*/true,
       /*matches_opaque_src=*/false)};
 }
 
@@ -36,13 +37,15 @@
 blink::ParsedPermissionsPolicy CreatePolicyToDenyWebAuthn() {
   return {blink::ParsedPermissionsPolicyDeclaration(
       blink::mojom::PermissionsPolicyFeature::kPublicKeyCredentialsGet,
-      /*values=*/{}, /*matches_all_origins=*/false,
+      /*allowed_origins=*/{}, /*self_if_matches=*/absl::nullopt,
+      /*matches_all_origins=*/false,
       /*matches_opaque_src=*/false)};
 }
 
 blink::ParsedPermissionsPolicy CreatePolicyToAllowWebPayments() {
   return {blink::ParsedPermissionsPolicyDeclaration(
-      blink::mojom::PermissionsPolicyFeature::kPayment, /*values=*/{},
+      blink::mojom::PermissionsPolicyFeature::kPayment, /*allowed_origins=*/{},
+      /*self_if_matches=*/absl::nullopt,
       /*matches_all_origins=*/true, /*matches_opaque_src=*/false)};
 }
 
diff --git a/content/common/profiling_utils.cc b/content/common/profiling_utils.cc
index a1037663..279ab85 100644
--- a/content/common/profiling_utils.cc
+++ b/content/common/profiling_utils.cc
@@ -23,6 +23,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/threading/thread_restrictions.h"
 #include "build/build_config.h"
+#include "content/public/common/content_switches.h"
 
 namespace content {
 
@@ -40,7 +41,15 @@
 #if BUILDFLAG(IS_ANDROID)
   base::PathService::Get(base::DIR_TEMP, &path);
   path = path.Append("pgo_profiles/");
-#else  // !BUILDFLAG(IS_ANDROID)
+  // Lacros is similar to Android that it's running on a device that is not
+  // the host machine and environment variables aren't well supported.
+  // But Lacros also need to pass in the path so it is the same path as
+  // isolate test output folder on bots.
+#elif BUILDFLAG(IS_CHROMEOS_LACROS)
+  path = base::CommandLine::ForCurrentProcess()
+             ->GetSwitchValuePath(switches::kLLVMProfileFile)
+             .DirName();
+#else
   std::string prof_template;
   std::unique_ptr<base::Environment> env(base::Environment::Create());
   if (env->GetVar("LLVM_PROFILE_FILE", &prof_template)) {
diff --git a/content/public/browser/content_browser_client.cc b/content/public/browser/content_browser_client.cc
index 3fd3620..cd77eef 100644
--- a/content/public/browser/content_browser_client.cc
+++ b/content/public/browser/content_browser_client.cc
@@ -397,6 +397,12 @@
   return AllowServiceWorkerResult::Yes();
 }
 
+bool ContentBrowserClient::MayDeleteServiceWorkerRegistration(
+    const GURL& scope,
+    BrowserContext* browser_context) {
+  return true;
+}
+
 void ContentBrowserClient::UpdateEnabledBlinkRuntimeFeaturesInIsolatedWorker(
     BrowserContext* context,
     const GURL& script_url,
diff --git a/content/public/browser/content_browser_client.h b/content/public/browser/content_browser_client.h
index e7b210e8..5500a0c 100644
--- a/content/public/browser/content_browser_client.h
+++ b/content/public/browser/content_browser_client.h
@@ -725,6 +725,14 @@
       const GURL& script_url,
       BrowserContext* context);
 
+  // Returns true if the service worker associated with the given `scope` may be
+  // deleted. This can return false if the service worker is tied to another
+  // service that fundamentally should not be allowed to be removed (today, this
+  // is limited to extensions).
+  virtual bool MayDeleteServiceWorkerRegistration(
+      const GURL& scope,
+      BrowserContext* browser_context);
+
   // Allows the embedder to enable process-wide blink features before starting a
   // service worker. This is similar to
   // `blink.mojom.CommitNavigationParams.force_enabled_origin_trials` but for
diff --git a/content/public/browser/trust_token_access_details.cc b/content/public/browser/trust_token_access_details.cc
index e8995b7..8b975fc 100644
--- a/content/public/browser/trust_token_access_details.cc
+++ b/content/public/browser/trust_token_access_details.cc
@@ -4,6 +4,8 @@
 
 #include "content/public/browser/trust_token_access_details.h"
 
+#include "services/network/public/mojom/trust_tokens.mojom.h"
+
 namespace content {
 
 TrustTokenAccessDetails::TrustTokenAccessDetails() = default;
diff --git a/content/public/browser/trust_token_access_details.h b/content/public/browser/trust_token_access_details.h
index bc4a0062..1d0efb303 100644
--- a/content/public/browser/trust_token_access_details.h
+++ b/content/public/browser/trust_token_access_details.h
@@ -7,6 +7,8 @@
 
 #include "content/common/content_export.h"
 #include "services/network/public/mojom/trust_token_access_observer.mojom.h"
+#include "services/network/public/mojom/trust_tokens.mojom-forward.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 #include "url/origin.h"
 
 namespace content {
diff --git a/content/public/common/content_switches.cc b/content/public/common/content_switches.cc
index 9f352c2..522d148 100644
--- a/content/public/common/content_switches.cc
+++ b/content/public/common/content_switches.cc
@@ -1008,6 +1008,10 @@
 // Linux speech service. Because it's buggy, the user must explicitly
 // enable it so that visiting a random webpage can't cause instability.
 const char kEnableSpeechDispatcher[] = "enable-speech-dispatcher";
+
+// For lacros, we do not use environment variable to pass values. Instead we
+// use a command line flag to pass the path to the device.
+const char kLLVMProfileFile[] = "llvm-profile-file";
 #endif
 
 #if BUILDFLAG(IS_WIN)
diff --git a/content/public/common/content_switches.h b/content/public/common/content_switches.h
index a8e677a..ea42089 100644
--- a/content/public/common/content_switches.h
+++ b/content/public/common/content_switches.h
@@ -270,6 +270,7 @@
 // of lacros-chrome is complete.
 #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS)
 CONTENT_EXPORT extern const char kEnableSpeechDispatcher[];
+CONTENT_EXPORT extern const char kLLVMProfileFile[];
 #endif
 
 #if BUILDFLAG(IS_WIN)
diff --git a/content/public/test/shared_storage_test_utils.cc b/content/public/test/shared_storage_test_utils.cc
index 2d34c4ef..fb17719 100644
--- a/content/public/test/shared_storage_test_utils.cc
+++ b/content/public/test/shared_storage_test_utils.cc
@@ -70,7 +70,6 @@
 
   EXPECT_TRUE(ExecJs(root,
                      "var f = document.createElement('fencedframe');"
-                     "f.mode = 'opaque-ads';"
                      "document.body.appendChild(f);"));
 
   EXPECT_EQ(initial_child_count + 1, root_node->child_count());
diff --git a/content/shell/browser/shell_content_browser_client.cc b/content/shell/browser/shell_content_browser_client.cc
index f56c616..e3f3e84 100644
--- a/content/shell/browser/shell_content_browser_client.cc
+++ b/content/shell/browser/shell_content_browser_client.cc
@@ -831,6 +831,7 @@
       blink::mojom::PermissionsPolicyFeature::kDirectSockets,
       {blink::OriginWithPossibleWildcards(app_origin,
                                           /*has_subdomain_wildcard=*/false)},
+      /*self_if_matches=*/absl::nullopt,
       /*matches_all_origins=*/false, /*matches_opaque_src=*/false);
   return {{decl}};
 }
diff --git a/content/test/data/fenced_frames/basic_fenced_frame_src_navigate_on_click.html b/content/test/data/fenced_frames/basic_fenced_frame_src_navigate_on_click.html
index 3f67d67..49ebcbeb 100644
--- a/content/test/data/fenced_frames/basic_fenced_frame_src_navigate_on_click.html
+++ b/content/test/data/fenced_frames/basic_fenced_frame_src_navigate_on_click.html
@@ -4,7 +4,7 @@
   <div>
     <button onclick="navigateFrames();">Navigate</button>
   </div>
-  <fencedframe mode=opaque-ads id="frame1"></fencedframe>
+  <fencedframe id="frame1"></fencedframe>
 
   <script>
     function navigateFrames() {
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 6cb2d930..cbb0a63 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
@@ -322,6 +322,15 @@
 crbug.com/1417485 [ android android-nexus-5x passthrough ] conformance/glsl/samplers/glsl-function-texture2dprojlod.html [ Failure ]
 crbug.com/1417485 [ android android-nexus-5x passthrough ] conformance/reading/read-pixels-test.html [ Failure ]
 
+###############################
+# Permanent Slow Expectations #
+###############################
+# The "Slow" expectations in this section are expected to stick around, i.e.
+# they are not attributed to some known issue that needs resolving.
+
+# Seems to do enough JavaScript work that heartbeats are not reliably sent.
+crbug.com/1426593 conformance/uniforms/no-over-optimization-on-uniform-array-* [ Slow ]
+
 ###################
 # Failures/Flakes #
 ###################
@@ -428,7 +437,6 @@
 crbug.com/1291276 [ win nvidia angle-vulkan passthrough ] conformance/ogles/GL/mod/mod_001_to_008.html [ Failure ]
 
 crbug.com/1364333 [ win debug angle-vulkan passthrough ] conformance/glsl/bugs/temp-expressions-should-not-crash.html [ Slow ]
-crbug.com/1364333 [ win debug angle-vulkan passthrough ] conformance/uniforms/no-over-optimization-on-uniform-array-* [ Slow ]
 
 ####################
 # Mac failures     #
@@ -570,7 +578,6 @@
 crbug.com/1288590 [ android android-pixel-6 ] conformance/misc/shader-precision-format.html [ Failure ]
 crbug.com/1288603 [ android android-pixel-6 passthrough angle-opengles ] conformance/extensions/angle-instanced-arrays-out-of-bounds.html [ Failure ]
 crbug.com/1357064 [ android android-pixel-6 no-passthrough ] conformance/rendering/blending.html [ Failure ]
-crbug.com/1426593 [ android android-pixel-6 no-passthrough ] conformance/uniforms/no-over-optimization-on-uniform-array-* [ Slow ]
 crbug.com/1426535 [ android android-pixel-6 ] conformance/state/gl-enable-enum-test.html [ RetryOnFailure ]
 crbug.com/1426535 [ android android-pixel-6 ] conformance/state/gl-get-calls.html [ RetryOnFailure ]
 
@@ -602,6 +609,10 @@
 crbug.com/1372155 [ android android-sm-a135m ] conformance/uniforms/uniform-samplers-test.html [ Failure ]
 crbug.com/1372149 [ android android-sm-a235m no-passthrough ] conformance/rendering/blending.html [ Failure ]
 
+crbug.com/1426793 [ android android-sm-a135m ] conformance/extensions/webgl-compressed-texture-astc.html [ Slow ]
+crbug.com/1426793 [ android android-sm-a135m ] conformance/glsl/misc/shader-uniform-packing-restrictions.html [ Slow ]
+crbug.com/1426793 [ android android-sm-a135m ] conformance/textures/misc/tex-image-and-sub-image-2d-with-array-buffer-view.html [ Slow ]
+
 ############
 # ChromeOS #
 ############
diff --git a/content/test/gpu/gpu_tests/webgpu_cts_integration_test.py b/content/test/gpu/gpu_tests/webgpu_cts_integration_test.py
index dc71773..a2dbaa53 100644
--- a/content/test/gpu/gpu_tests/webgpu_cts_integration_test.py
+++ b/content/test/gpu/gpu_tests/webgpu_cts_integration_test.py
@@ -102,12 +102,72 @@
     return True
 
   def _GetSerialGlobs(self) -> Set[str]:
-    return {
+    globs = {
         # crbug.com/1406799. Large test.
         # Run serially to avoid impact on other tests.
         '*:api,operation,rendering,basic:large_draw:*',
     }
 
+    # crbug.com/dawn/1500. Flaky tests on Mac-Intel when using 16 byte formats
+    # in parallel.
+    FORMATS_WITH_16_BYTE_BLOCKS = [
+        # Basic color formats
+        'rgba32uint',
+        'rgba32sint',
+        'rgba32float',
+        # BC compression formats
+        'bc2-rgba-unorm',
+        'bc2-rgba-unorm-srgb',
+        'bc3-rgba-unorm',
+        'bc3-rgba-unorm-srgb',
+        'bc5-rg-unorm',
+        'bc5-rg-snorm',
+        'bc6h-rgb-ufloat',
+        'bc6h-rgb-float',
+        'bc7-rgba-unorm',
+        'bc7-rgba-unorm-srgb',
+        # ETC2 compression formats
+        'etc2-rgba8unorm',
+        'etc2-rgba8unorm-srgb',
+        'eac-rg11unorm',
+        'eac-rg11snorm',
+        # ASTC compression formats
+        'astc-4x4-unorm',
+        'astc-4x4-unorm-srgb',
+        'astc-5x4-unorm',
+        'astc-5x4-unorm-srgb',
+        'astc-5x5-unorm',
+        'astc-5x5-unorm-srgb',
+        'astc-6x5-unorm',
+        'astc-6x5-unorm-srgb',
+        'astc-6x6-unorm',
+        'astc-6x6-unorm-srgb',
+        'astc-8x5-unorm',
+        'astc-8x5-unorm-srgb',
+        'astc-8x6-unorm',
+        'astc-8x6-unorm-srgb',
+        'astc-8x8-unorm',
+        'astc-8x8-unorm-srgb',
+        'astc-10x5-unorm',
+        'astc-10x5-unorm-srgb',
+        'astc-10x6-unorm',
+        'astc-10x6-unorm-srgb',
+        'astc-10x8-unorm',
+        'astc-10x8-unorm-srgb',
+        'astc-10x10-unorm',
+        'astc-10x10-unorm-srgb',
+        'astc-12x10-unorm',
+        'astc-12x10-unorm-srgb',
+        'astc-12x12-unorm',
+        'astc-12x12-unorm-srgb'
+    ]
+    for f in FORMATS_WITH_16_BYTE_BLOCKS:
+      globs.add((
+          '*:api,operation,command_buffer,image_copy:origins_and_extents:'
+          'initMethod="WriteTexture";checkMethod="PartialCopyT2B";format="%s";*'
+      ) % f)
+    return globs
+
   def _GetSerialTests(self) -> Set[str]:
     return set()
 
diff --git a/device/vr/android/cardboard/BUILD.gn b/device/vr/android/cardboard/BUILD.gn
index a2d8afe..41b62f2 100644
--- a/device/vr/android/cardboard/BUILD.gn
+++ b/device/vr/android/cardboard/BUILD.gn
@@ -12,8 +12,6 @@
   sources = [
     "cardboard_device.cc",
     "cardboard_device.h",
-    "cardboard_device_provider.cc",
-    "cardboard_device_provider.h",
     "cardboard_sdk.h",
     "cardboard_sdk_impl.cc",
     "cardboard_sdk_impl.h",
diff --git a/device/vr/android/cardboard/cardboard_sdk_impl.h b/device/vr/android/cardboard/cardboard_sdk_impl.h
index 0770bd0..a9e56349 100644
--- a/device/vr/android/cardboard/cardboard_sdk_impl.h
+++ b/device/vr/android/cardboard/cardboard_sdk_impl.h
@@ -7,6 +7,8 @@
 
 #include "device/vr/android/cardboard/cardboard_sdk.h"
 
+#include "base/component_export.h"
+
 namespace device {
 
 class COMPONENT_EXPORT(VR_CARDBOARD) CardboardSdkImpl : public CardboardSdk {
diff --git a/docs/infra/new_builder.md b/docs/infra/new_builder.md
index 8f321c5e..5477a6a 100644
--- a/docs/infra/new_builder.md
+++ b/docs/infra/new_builder.md
@@ -101,7 +101,8 @@
 (e.g. mac hardware, attached mobile devices, a specific GPU, etc.). Note that
 even if there's hardware currently available for the new builder, a resource
 request will still be needed if the footprint of the new builder equates to
-at least 5 VMs or 50 CPU cores. See [go/estimating-bot-capacity](https://goto.google.com/estimating-bot-capacity)
+at least 5 VMs or 50 CPU cores per hour. See
+[go/estimating-bot-capacity](https://goto.google.com/estimating-bot-capacity)
 for guidance on how many hosts to request.
 
 See [infradata docs][4] (internal) for information on how to register
diff --git a/extensions/browser/extension_function_dispatcher.cc b/extensions/browser/extension_function_dispatcher.cc
index 549c2f3..0d82b987 100644
--- a/extensions/browser/extension_function_dispatcher.cc
+++ b/extensions/browser/extension_function_dispatcher.cc
@@ -491,16 +491,11 @@
 
   scoped_refptr<ExtensionFunction> function = CreateExtensionFunction(
       params, extension, render_process_id, is_worker_request, rfh_url,
-      *process_map, ExtensionAPI::GetSharedInstance(), std::move(callback));
+      *process_map, ExtensionAPI::GetSharedInstance(), std::move(callback),
+      render_frame_host);
   if (!function.get())
     return;
 
-  function->set_worker_thread_id(params.worker_thread_id);
-  if (is_worker_request) {
-    function->set_service_worker_version_id(params.service_worker_version_id);
-  } else {
-    function->SetRenderFrameHost(render_frame_host);
-  }
   function->SetDispatcher(weak_ptr_factory_.GetWeakPtr());
   if (extension &&
       ExtensionsBrowserClient::Get()->CanExtensionCrossIncognito(
@@ -665,7 +660,8 @@
     const GURL* rfh_url,
     const ProcessMap& process_map,
     ExtensionAPI* api,
-    ExtensionFunction::ResponseCallback callback) {
+    ExtensionFunction::ResponseCallback callback,
+    content::RenderFrameHost* render_frame_host) {
   constexpr char kCreationFailed[] = "Access to extension API denied.";
 
   scoped_refptr<ExtensionFunction> function =
@@ -703,6 +699,12 @@
   function->set_response_callback(std::move(callback));
   function->set_source_context_type(context_type);
   function->set_source_process_id(requesting_process_id);
+  function->set_worker_thread_id(params.worker_thread_id);
+  if (is_worker_request) {
+    function->set_service_worker_version_id(params.service_worker_version_id);
+  } else {
+    function->SetRenderFrameHost(render_frame_host);
+  }
 
   if (!function->HasPermission()) {
     LOG(ERROR) << "Permission denied for " << params.name;
diff --git a/extensions/browser/extension_function_dispatcher.h b/extensions/browser/extension_function_dispatcher.h
index e661317..da169d3 100644
--- a/extensions/browser/extension_function_dispatcher.h
+++ b/extensions/browser/extension_function_dispatcher.h
@@ -141,7 +141,8 @@
       const GURL* rfh_url,
       const ProcessMap& process_map,
       ExtensionAPI* api,
-      ExtensionFunction::ResponseCallback callback);
+      ExtensionFunction::ResponseCallback callback,
+      content::RenderFrameHost* render_frame_host);
 
   void DispatchWithCallbackInternal(
       const mojom::RequestParams& params,
diff --git a/extensions/browser/service_worker_task_queue.h b/extensions/browser/service_worker_task_queue.h
index 404497e..7687b464 100644
--- a/extensions/browser/service_worker_task_queue.h
+++ b/extensions/browser/service_worker_task_queue.h
@@ -138,6 +138,13 @@
   // task queue.
   void ActivateIncognitoSplitModeExtensions(ServiceWorkerTaskQueue* other);
 
+  // Retrieves the version of the extension that has currently registered
+  // and stored an extension service worker. If there is no such version (if
+  // there was never a service worker or if the worker was unregistered),
+  // returns an invalid version.
+  base::Version RetrieveRegisteredServiceWorkerVersion(
+      const ExtensionId& extension_id);
+
   // content::ServiceWorkerContextObserver:
   void OnRegistrationStored(int64_t registration_id,
                             const GURL& scope) override;
@@ -174,11 +181,6 @@
 
   size_t GetNumPendingTasksForTest(const LazyContextId& lazy_context_id);
 
-  base::Version RetrieveRegisteredServiceWorkerVersionForTest(
-      const ExtensionId& extension_id) {
-    return RetrieveRegisteredServiceWorkerVersion(extension_id);
-  }
-
  private:
   using SequencedContextId = std::pair<LazyContextId, base::UnguessableToken>;
 
@@ -212,17 +214,11 @@
   void DidStartWorkerFail(const SequencedContextId& context_id,
                           blink::ServiceWorkerStatusCode status_code);
 
-  // The following three methods retrieve, store, and remove information
-  // about Service Worker registration of SW based background pages:
-  //
-  // Retrieves the last version of the extension, returns invalid version if
-  // there isn't any such extension.
-  base::Version RetrieveRegisteredServiceWorkerVersion(
-      const ExtensionId& extension_id);
   // Records that the extension with |extension_id| and |version| successfully
   // registered a Service Worker.
   void SetRegisteredServiceWorkerInfo(const ExtensionId& extension_id,
                                       const base::Version& version);
+
   // Clears any record of registered Service Worker for the given extension with
   // |extension_id|.
   void RemoveRegisteredServiceWorkerInfo(const ExtensionId& extension_id);
diff --git a/extensions/renderer/BUILD.gn b/extensions/renderer/BUILD.gn
index ee198edd..f66e3b1 100644
--- a/extensions/renderer/BUILD.gn
+++ b/extensions/renderer/BUILD.gn
@@ -178,6 +178,8 @@
     "render_frame_observer_natives.h",
     "renderer_extension_registry.cc",
     "renderer_extension_registry.h",
+    "renderer_frame_context_data.cc",
+    "renderer_frame_context_data.h",
     "resource_bundle_source_map.cc",
     "resource_bundle_source_map.h",
     "runtime_custom_bindings.cc",
diff --git a/extensions/renderer/renderer_frame_context_data.cc b/extensions/renderer/renderer_frame_context_data.cc
new file mode 100644
index 0000000..abc0a17
--- /dev/null
+++ b/extensions/renderer/renderer_frame_context_data.cc
@@ -0,0 +1,88 @@
+// 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 "extensions/renderer/renderer_frame_context_data.h"
+
+#include "third_party/blink/public/platform/web_security_origin.h"
+#include "third_party/blink/public/platform/web_url.h"
+#include "third_party/blink/public/web/blink.h"
+#include "third_party/blink/public/web/web_document.h"
+#include "third_party/blink/public/web/web_frame.h"
+#include "third_party/blink/public/web/web_local_frame.h"
+#include "url/gurl.h"
+#include "url/origin.h"
+
+namespace extensions {
+
+std::unique_ptr<ContextData> RendererFrameContextData::Clone() const {
+  return CloneFrameContextData();
+}
+
+std::unique_ptr<FrameContextData>
+RendererFrameContextData::CloneFrameContextData() const {
+  return std::make_unique<RendererFrameContextData>(frame_);
+}
+
+bool RendererFrameContextData::IsIsolatedApplication() const {
+  return blink::IsIsolatedContext();
+}
+
+std::unique_ptr<FrameContextData>
+RendererFrameContextData::GetLocalParentOrOpener() const {
+  blink::WebFrame* parent_or_opener = nullptr;
+  if (frame_->Parent()) {
+    parent_or_opener = frame_->Parent();
+  } else {
+    parent_or_opener = frame_->Opener();
+  }
+  if (!parent_or_opener || !parent_or_opener->IsWebLocalFrame()) {
+    return nullptr;
+  }
+
+  blink::WebLocalFrame* local_parent_or_opener =
+      parent_or_opener->ToWebLocalFrame();
+  if (local_parent_or_opener->GetDocument().IsNull()) {
+    return nullptr;
+  }
+
+  return std::make_unique<RendererFrameContextData>(local_parent_or_opener);
+}
+
+GURL RendererFrameContextData::GetUrl() const {
+  if (frame_->GetDocument().Url().IsEmpty()) {
+    // It's possible for URL to be empty when `frame_` is on the initial empty
+    // document. TODO(https://crbug.com/1197308): Consider making  `frame_`'s
+    // document's URL about:blank instead of empty in that case.
+    return GURL(url::kAboutBlankURL);
+  }
+  return frame_->GetDocument().Url();
+}
+
+url::Origin RendererFrameContextData::GetOrigin() const {
+  return frame_->GetSecurityOrigin();
+}
+
+bool RendererFrameContextData::CanAccess(const url::Origin& target) const {
+  return frame_->GetSecurityOrigin().CanAccess(target);
+}
+
+bool RendererFrameContextData::CanAccess(const FrameContextData& target) const {
+  // It is important that below `web_security_origin` wraps the security
+  // origin of the `target_frame` (rather than a new origin created via
+  // url::Origin round-trip - such an origin wouldn't be 100% equivalent -
+  // e.g. `disallowdocumentaccess` information might be lost).  FWIW, this
+  // scenario is execised by ScriptContextTest.GetEffectiveDocumentURL.
+  const blink::WebLocalFrame* target_frame =
+      static_cast<const RendererFrameContextData&>(target).frame_;
+  blink::WebSecurityOrigin web_security_origin =
+      target_frame->GetDocument().GetSecurityOrigin();
+
+  return frame_->GetSecurityOrigin().CanAccess(web_security_origin);
+}
+
+uintptr_t RendererFrameContextData::GetId() const {
+  return reinterpret_cast<uintptr_t>(frame_);
+}
+
+}  // namespace extensions
diff --git a/extensions/renderer/renderer_frame_context_data.h b/extensions/renderer/renderer_frame_context_data.h
new file mode 100644
index 0000000..7cea9a8
--- /dev/null
+++ b/extensions/renderer/renderer_frame_context_data.h
@@ -0,0 +1,49 @@
+// 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 EXTENSIONS_RENDERER_RENDERER_FRAME_CONTEXT_DATA_H_
+#define EXTENSIONS_RENDERER_RENDERER_FRAME_CONTEXT_DATA_H_
+
+#include <stdint.h>
+#include <memory>
+
+#include "extensions/common/frame_context_data.h"
+#include "third_party/blink/public/web/web_local_frame.h"
+
+class GURL;
+
+namespace url {
+class Origin;
+}  // namespace url
+
+namespace extensions {
+
+class RendererFrameContextData : public FrameContextData {
+ public:
+  explicit RendererFrameContextData(const blink::WebLocalFrame* frame)
+      : frame_(frame) {}
+
+  ~RendererFrameContextData() override = default;
+
+  std::unique_ptr<ContextData> Clone() const override;
+  std::unique_ptr<FrameContextData> CloneFrameContextData() const override;
+  bool IsIsolatedApplication() const override;
+
+  std::unique_ptr<FrameContextData> GetLocalParentOrOpener() const override;
+
+  GURL GetUrl() const override;
+  url::Origin GetOrigin() const override;
+
+  bool CanAccess(const url::Origin& target) const override;
+  bool CanAccess(const FrameContextData& target) const override;
+
+  uintptr_t GetId() const override;
+
+ private:
+  const blink::WebLocalFrame* const frame_;
+};
+
+}  // namespace extensions
+
+#endif  // EXTENSIONS_RENDERER_RENDERER_FRAME_CONTEXT_DATA_H_
diff --git a/extensions/renderer/script_context.cc b/extensions/renderer/script_context.cc
index bf4084c..27223c7 100644
--- a/extensions/renderer/script_context.cc
+++ b/extensions/renderer/script_context.cc
@@ -20,16 +20,13 @@
 #include "extensions/common/extension.h"
 #include "extensions/common/extension_api.h"
 #include "extensions/common/extension_urls.h"
-#include "extensions/common/frame_context_data.h"
 #include "extensions/common/manifest_handlers/sandboxed_page_info.h"
 #include "extensions/common/permissions/permissions_data.h"
 #include "extensions/renderer/dispatcher.h"
 #include "extensions/renderer/renderer_extension_registry.h"
+#include "extensions/renderer/renderer_frame_context_data.h"
 #include "extensions/renderer/v8_helpers.h"
 #include "third_party/blink/public/mojom/service_worker/service_worker_registration.mojom.h"
-#include "third_party/blink/public/platform/web_security_origin.h"
-#include "third_party/blink/public/web/blink.h"
-#include "third_party/blink/public/web/web_document.h"
 #include "third_party/blink/public/web/web_document_loader.h"
 #include "third_party/blink/public/web/web_local_frame.h"
 #include "v8/include/v8-context.h"
@@ -43,80 +40,6 @@
 
 namespace {
 
-class RendererFrameContextData : public FrameContextData {
- public:
-  explicit RendererFrameContextData(const blink::WebLocalFrame* frame)
-      : frame_(frame) {}
-
-  ~RendererFrameContextData() override = default;
-
-  std::unique_ptr<ContextData> Clone() const override {
-    return CloneFrameContextData();
-  }
-
-  std::unique_ptr<FrameContextData> CloneFrameContextData() const override {
-    return std::make_unique<RendererFrameContextData>(frame_);
-  }
-
-  bool IsIsolatedApplication() const override {
-    return blink::IsIsolatedContext();
-  }
-
-  std::unique_ptr<FrameContextData> GetLocalParentOrOpener() const override {
-    blink::WebFrame* parent_or_opener = nullptr;
-    if (frame_->Parent())
-      parent_or_opener = frame_->Parent();
-    else
-      parent_or_opener = frame_->Opener();
-    if (!parent_or_opener || !parent_or_opener->IsWebLocalFrame())
-      return nullptr;
-
-    blink::WebLocalFrame* local_parent_or_opener =
-        parent_or_opener->ToWebLocalFrame();
-    if (local_parent_or_opener->GetDocument().IsNull())
-      return nullptr;
-
-    return std::make_unique<RendererFrameContextData>(local_parent_or_opener);
-  }
-
-  GURL GetUrl() const override {
-    if (frame_->GetDocument().Url().IsEmpty()) {
-      // It's possible for URL to be empty when `frame_` is on the initial empty
-      // document. TODO(https://crbug.com/1197308): Consider making  `frame_`'s
-      // document's URL about:blank instead of empty in that case.
-      return GURL(url::kAboutBlankURL);
-    }
-    return frame_->GetDocument().Url();
-  }
-
-  url::Origin GetOrigin() const override { return frame_->GetSecurityOrigin(); }
-
-  bool CanAccess(const url::Origin& target) const override {
-    return frame_->GetSecurityOrigin().CanAccess(target);
-  }
-
-  bool CanAccess(const FrameContextData& target) const override {
-    // It is important that below `web_security_origin` wraps the security
-    // origin of the `target_frame` (rather than a new origin created via
-    // url::Origin round-trip - such an origin wouldn't be 100% equivalent -
-    // e.g. `disallowdocumentaccess` information might be lost).  FWIW, this
-    // scenario is execised by ScriptContextTest.GetEffectiveDocumentURL.
-    const blink::WebLocalFrame* target_frame =
-        static_cast<const RendererFrameContextData&>(target).frame_;
-    blink::WebSecurityOrigin web_security_origin =
-        target_frame->GetDocument().GetSecurityOrigin();
-
-    return frame_->GetSecurityOrigin().CanAccess(web_security_origin);
-  }
-
-  uintptr_t GetId() const override {
-    return reinterpret_cast<uintptr_t>(frame_);
-  }
-
- private:
-  const blink::WebLocalFrame* const frame_;
-};
-
 GURL GetEffectiveDocumentURL(
     blink::WebLocalFrame* frame,
     const GURL& document_url,
diff --git a/gpu/config/software_rendering_list.json b/gpu/config/software_rendering_list.json
index e7d662d..ba7dfb0 100644
--- a/gpu/config/software_rendering_list.json
+++ b/gpu/config/software_rendering_list.json
@@ -1638,12 +1638,12 @@
     },
     {
       "id": 174,
-      "description": "Disable canvas acceleration on some Haswell GPUs on ChromeOS",
+      "description": "Disable canvas acceleration on some Haswell and Crystal Well GPUs on ChromeOS",
       "os": {
         "type": "chromeos"
       },
       "vendor_id": "0x8086",
-      "device_id": ["0x0a16", "0x0a1e"],
+      "device_id": ["0x0a16", "0x0a1e", "0x0d26"],
       "features": [
         "accelerated_2d_canvas"
       ]
diff --git a/infra/config/generated/luci/commit-queue.cfg b/infra/config/generated/luci/commit-queue.cfg
index fc92dbb..c37634c 100644
--- a/infra/config/generated/luci/commit-queue.cfg
+++ b/infra/config/generated/luci/commit-queue.cfg
@@ -327,6 +327,10 @@
         includable_only: true
       }
       builders {
+        name: "chromium/codesearch/gen-ios-try"
+        includable_only: true
+      }
+      builders {
         name: "chromium/codesearch/gen-lacros-try"
         includable_only: true
       }
diff --git a/infra/config/generated/luci/cr-buildbucket.cfg b/infra/config/generated/luci/cr-buildbucket.cfg
index 1884132..8e2e725d 100644
--- a/infra/config/generated/luci/cr-buildbucket.cfg
+++ b/infra/config/generated/luci/cr-buildbucket.cfg
@@ -49094,6 +49094,84 @@
       }
     }
     builders {
+      name: "gen-ios-try"
+      swarming_host: "chromium-swarm.appspot.com"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:arm64"
+      dimensions: "os:Mac-13"
+      dimensions: "pool:luci.chromium.try"
+      exe {
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/main"
+        cmd: "luciexe"
+      }
+      properties:
+        '{'
+        '  "$build/goma": {'
+        '    "rpc_extra_params": "?prod",'
+        '    "server_host": "goma.chromium.org"'
+        '  },'
+        '  "$recipe_engine/resultdb/test_presentation": {'
+        '    "column_keys": [],'
+        '    "grouping_keys": ['
+        '      "status",'
+        '      "v.test_suite"'
+        '    ]'
+        '  },'
+        '  "builder_group": "tryserver.chromium.codesearch",'
+        '  "recipe": "chromium_codesearch",'
+        '  "recipe_properties": {'
+        '    "build_config": "ios",'
+        '    "platform": "ios"'
+        '  }'
+        '}'
+      execution_timeout_secs: 32400
+      expiration_secs: 7200
+      build_numbers: YES
+      service_account: "chromium-try-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: "try_test_results"
+          test_results {}
+        }
+        bq_exports {
+          project: "chrome-luci-data"
+          dataset: "chromium"
+          table: "gpu_try_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_try_test_results"
+          test_results {
+            predicate {
+              test_id_regexp: "(ninja://[^/]*blink_web_tests/.+)|(ninja://[^/]*blink_wpt_tests/.+)"
+            }
+          }
+        }
+        history_options {
+          use_invocation_timestamp: true
+        }
+      }
+    }
+    builders {
       name: "gen-lacros-try"
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builderless:1"
diff --git a/infra/config/generated/luci/luci-milo.cfg b/infra/config/generated/luci/luci-milo.cfg
index 48b54a0..49ee1fe 100644
--- a/infra/config/generated/luci/luci-milo.cfg
+++ b/infra/config/generated/luci/luci-milo.cfg
@@ -18689,6 +18689,9 @@
     name: "buildbucket/luci.chromium.codesearch/gen-fuchsia-try"
   }
   builders {
+    name: "buildbucket/luci.chromium.codesearch/gen-ios-try"
+  }
+  builders {
     name: "buildbucket/luci.chromium.codesearch/gen-lacros-try"
   }
   builders {
diff --git a/infra/config/subprojects/codesearch/codesearch.star b/infra/config/subprojects/codesearch/codesearch.star
index 78a939c24..d61c60f8 100644
--- a/infra/config/subprojects/codesearch/codesearch.star
+++ b/infra/config/subprojects/codesearch/codesearch.star
@@ -74,6 +74,18 @@
 )
 
 try_.builder(
+    name = "gen-ios-try",
+    os = os.MAC_13,
+    cpu = cpu.ARM64,
+    properties = {
+        "recipe_properties": {
+            "build_config": "ios",
+            "platform": "ios",
+        },
+    },
+)
+
+try_.builder(
     name = "gen-lacros-try",
     properties = {
         "recipe_properties": {
diff --git a/ios/chrome/app/strings/ios_chromium_strings.grd b/ios/chrome/app/strings/ios_chromium_strings.grd
index 1a025fc0..10c1f06 100644
--- a/ios/chrome/app/strings/ios_chromium_strings.grd
+++ b/ios/chrome/app/strings/ios_chromium_strings.grd
@@ -620,6 +620,12 @@
       <message name="IDS_IOS_ACCOUNT_TABLE_ERROR_HAS_TRUSTED_VAULT_RECOVERABILITY_DEGRADED_FOR_EVERYTHING_MESSAGE" desc="The error message to show in the account table when there is an error related to trusted vault recoverability degraded for everything. [iOS only]">
         To use and save Chromium data in your Google Account, verify it's you.
       </message>
+      <message name="IDS_IOS_IDENTITY_ERROR_INFOBAR_KEEP_USING_YOUR_CHROME_DATA_MESSAGE" desc="The error message in the identity error info bar related to keep using your Chromium data in your Google account. [iOS only]">
+        Keep using the Chromium data in your Google Account
+      </message>
+      <message name="IDS_IOS_IDENTITY_ERROR_INFOBAR_MAKE_SURE_YOU_CAN_ALWAYS_USE_CHROME_DATA_MESSAGE" desc="The error message in the identity error info bar related to making sure your can always use Chromium data in your Google account. [iOS only]">
+        Make sure you can always use the Chromium data in your Google Account
+      </message>
     </messages>
   </release>
 </grit>
diff --git a/ios/chrome/app/strings/ios_chromium_strings_grd/IDS_IOS_IDENTITY_ERROR_INFOBAR_KEEP_USING_YOUR_CHROME_DATA_MESSAGE.png.sha1 b/ios/chrome/app/strings/ios_chromium_strings_grd/IDS_IOS_IDENTITY_ERROR_INFOBAR_KEEP_USING_YOUR_CHROME_DATA_MESSAGE.png.sha1
new file mode 100644
index 0000000..9d309922
--- /dev/null
+++ b/ios/chrome/app/strings/ios_chromium_strings_grd/IDS_IOS_IDENTITY_ERROR_INFOBAR_KEEP_USING_YOUR_CHROME_DATA_MESSAGE.png.sha1
@@ -0,0 +1 @@
+3ae3fe7f782ef6e2abde6c2d67e39b6f3a8d7b8a
\ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_chromium_strings_grd/IDS_IOS_IDENTITY_ERROR_INFOBAR_MAKE_SURE_YOU_CAN_ALWAYS_USE_CHROME_DATA_MESSAGE.png.sha1 b/ios/chrome/app/strings/ios_chromium_strings_grd/IDS_IOS_IDENTITY_ERROR_INFOBAR_MAKE_SURE_YOU_CAN_ALWAYS_USE_CHROME_DATA_MESSAGE.png.sha1
new file mode 100644
index 0000000..90a1380
--- /dev/null
+++ b/ios/chrome/app/strings/ios_chromium_strings_grd/IDS_IOS_IDENTITY_ERROR_INFOBAR_MAKE_SURE_YOU_CAN_ALWAYS_USE_CHROME_DATA_MESSAGE.png.sha1
@@ -0,0 +1 @@
+8d22d6cba893ed3026f5c0c528a4097c97a1bf03
\ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_google_chrome_strings.grd b/ios/chrome/app/strings/ios_google_chrome_strings.grd
index 2d0a2f0..64b488b8 100644
--- a/ios/chrome/app/strings/ios_google_chrome_strings.grd
+++ b/ios/chrome/app/strings/ios_google_chrome_strings.grd
@@ -620,6 +620,12 @@
       <message name="IDS_IOS_ACCOUNT_TABLE_ERROR_HAS_TRUSTED_VAULT_RECOVERABILITY_DEGRADED_FOR_EVERYTHING_MESSAGE" desc="The error message to show in the account table when there is an error related to trusted vault recoverability degraded for everything. [iOS only]">
         To use and save Chrome data in your Google Account, verify it's you.
       </message>
+      <message name="IDS_IOS_IDENTITY_ERROR_INFOBAR_KEEP_USING_YOUR_CHROME_DATA_MESSAGE" desc="The error message in the identity error info bar related to keep using your Chromium data in your Google account. [iOS only]">
+        Keep using the Chrome data in your Google Account
+      </message>
+      <message name="IDS_IOS_IDENTITY_ERROR_INFOBAR_MAKE_SURE_YOU_CAN_ALWAYS_USE_CHROME_DATA_MESSAGE" desc="The error message in the identity error info bar related to making sure your can always use Chromium data in your Google account. [iOS only]">
+        Make sure you can always use the Chrome data in your Google Account
+      </message>
     </messages>
   </release>
 </grit>
diff --git a/ios/chrome/app/strings/ios_google_chrome_strings_grd/IDS_IOS_IDENTITY_ERROR_INFOBAR_KEEP_USING_YOUR_CHROME_DATA_MESSAGE.png.sha1 b/ios/chrome/app/strings/ios_google_chrome_strings_grd/IDS_IOS_IDENTITY_ERROR_INFOBAR_KEEP_USING_YOUR_CHROME_DATA_MESSAGE.png.sha1
new file mode 100644
index 0000000..9d309922
--- /dev/null
+++ b/ios/chrome/app/strings/ios_google_chrome_strings_grd/IDS_IOS_IDENTITY_ERROR_INFOBAR_KEEP_USING_YOUR_CHROME_DATA_MESSAGE.png.sha1
@@ -0,0 +1 @@
+3ae3fe7f782ef6e2abde6c2d67e39b6f3a8d7b8a
\ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_google_chrome_strings_grd/IDS_IOS_IDENTITY_ERROR_INFOBAR_MAKE_SURE_YOU_CAN_ALWAYS_USE_CHROME_DATA_MESSAGE.png.sha1 b/ios/chrome/app/strings/ios_google_chrome_strings_grd/IDS_IOS_IDENTITY_ERROR_INFOBAR_MAKE_SURE_YOU_CAN_ALWAYS_USE_CHROME_DATA_MESSAGE.png.sha1
new file mode 100644
index 0000000..90a1380
--- /dev/null
+++ b/ios/chrome/app/strings/ios_google_chrome_strings_grd/IDS_IOS_IDENTITY_ERROR_INFOBAR_MAKE_SURE_YOU_CAN_ALWAYS_USE_CHROME_DATA_MESSAGE.png.sha1
@@ -0,0 +1 @@
+8d22d6cba893ed3026f5c0c528a4097c97a1bf03
\ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_strings.grd b/ios/chrome/app/strings/ios_strings.grd
index f3f1027..4cb6c98f 100644
--- a/ios/chrome/app/strings/ios_strings.grd
+++ b/ios/chrome/app/strings/ios_strings.grd
@@ -4301,6 +4301,24 @@
       <message name="IDS_IOS_ACCOUNT_TABLE_ERROR_HAS_TRUSTED_VAULT_RECOVERABILITY_DEGRADED_FOR_PASSWORDS_MESSAGE" desc="The error message to show in the account table when there is an error related to trusted vault recoverability degraded for passwords. [iOS only]">
         To make sure you can always use the passwords in your Google Account, verify it's you.
       </message>
+      <message name="IDS_IOS_IDENTITY_ERROR_INFOBAR_ENTER_PASSPHRASE_TITLE" desc="The title of the identity error info bar when the passphrase is required. [iOS only]">
+        Enter your passphrase
+      </message>
+      <message name="IDS_IOS_IDENTITY_ERROR_INFOBAR_VERIFY_ITS_YOU_TITLE" desc="The title of the identity error info bar when identity verification is needed. [iOS only]">
+        Verify it's you
+      </message>
+      <message name="IDS_IOS_IDENTITY_ERROR_INFOBAR_KEEP_USING_PASSWORDS_MESSAGE" desc="The error message in the identity error info bar related to keep using passwords. [iOS only]">
+        Keep using the passwords in your Google Account
+      </message>
+      <message name="IDS_IOS_IDENTITY_ERROR_INFOBAR_MAKE_SURE_YOU_CAN_ALWAYS_USE_PASSWORDS_MESSAGE" desc="The error message in the identity error info bar related to making sure you can always use your passwords. [iOS only]">
+        Make sure you can always use the passwords in your Google Account
+      </message>
+      <message name="IDS_IOS_IDENTITY_ERROR_INFOBAR_ENTER_BUTTON_LABEL" desc="The label of the enter passphrase action button in the identity error info bar. [iOS only]">
+        Enter
+      </message>
+      <message name="IDS_IOS_IDENTITY_ERROR_INFOBAR_VERIFY_BUTTON_LABEL" desc="The label of the verify your account action button in the identity error info bar. [iOS only]">
+        Verify
+      </message>
     </messages>
   </release>
 </grit>
diff --git a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_IDENTITY_ERROR_INFOBAR_ENTER_BUTTON_LABEL.png.sha1 b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_IDENTITY_ERROR_INFOBAR_ENTER_BUTTON_LABEL.png.sha1
new file mode 100644
index 0000000..9d309922
--- /dev/null
+++ b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_IDENTITY_ERROR_INFOBAR_ENTER_BUTTON_LABEL.png.sha1
@@ -0,0 +1 @@
+3ae3fe7f782ef6e2abde6c2d67e39b6f3a8d7b8a
\ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_IDENTITY_ERROR_INFOBAR_ENTER_PASSPHRASE_TITLE.png.sha1 b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_IDENTITY_ERROR_INFOBAR_ENTER_PASSPHRASE_TITLE.png.sha1
new file mode 100644
index 0000000..9d309922
--- /dev/null
+++ b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_IDENTITY_ERROR_INFOBAR_ENTER_PASSPHRASE_TITLE.png.sha1
@@ -0,0 +1 @@
+3ae3fe7f782ef6e2abde6c2d67e39b6f3a8d7b8a
\ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_IDENTITY_ERROR_INFOBAR_KEEP_USING_PASSWORDS_MESSAGE.png.sha1 b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_IDENTITY_ERROR_INFOBAR_KEEP_USING_PASSWORDS_MESSAGE.png.sha1
new file mode 100644
index 0000000..8c21dff1
--- /dev/null
+++ b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_IDENTITY_ERROR_INFOBAR_KEEP_USING_PASSWORDS_MESSAGE.png.sha1
@@ -0,0 +1 @@
+61e63f995123ab2a64fd513df7379841ca7edaec
\ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_IDENTITY_ERROR_INFOBAR_MAKE_SURE_YOU_CAN_ALWAYS_USE_PASSWORDS_MESSAGE.png.sha1 b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_IDENTITY_ERROR_INFOBAR_MAKE_SURE_YOU_CAN_ALWAYS_USE_PASSWORDS_MESSAGE.png.sha1
new file mode 100644
index 0000000..4e5f6b9
--- /dev/null
+++ b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_IDENTITY_ERROR_INFOBAR_MAKE_SURE_YOU_CAN_ALWAYS_USE_PASSWORDS_MESSAGE.png.sha1
@@ -0,0 +1 @@
+a5c1839962f76466715be2ac383cd46f44a98c05
\ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_IDENTITY_ERROR_INFOBAR_VERIFY_BUTTON_LABEL.png.sha1 b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_IDENTITY_ERROR_INFOBAR_VERIFY_BUTTON_LABEL.png.sha1
new file mode 100644
index 0000000..8c21dff1
--- /dev/null
+++ b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_IDENTITY_ERROR_INFOBAR_VERIFY_BUTTON_LABEL.png.sha1
@@ -0,0 +1 @@
+61e63f995123ab2a64fd513df7379841ca7edaec
\ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_IDENTITY_ERROR_INFOBAR_VERIFY_ITS_YOU_TITLE.png.sha1 b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_IDENTITY_ERROR_INFOBAR_VERIFY_ITS_YOU_TITLE.png.sha1
new file mode 100644
index 0000000..8c21dff1
--- /dev/null
+++ b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_IDENTITY_ERROR_INFOBAR_VERIFY_ITS_YOU_TITLE.png.sha1
@@ -0,0 +1 @@
+61e63f995123ab2a64fd513df7379841ca7edaec
\ No newline at end of file
diff --git a/ios/chrome/browser/autofill/bottom_sheet/OWNERS b/ios/chrome/browser/autofill/bottom_sheet/OWNERS
new file mode 100644
index 0000000..7b2f0d0
--- /dev/null
+++ b/ios/chrome/browser/autofill/bottom_sheet/OWNERS
@@ -0,0 +1,2 @@
+tmartino@chromium.org
+sugoi@chromium.org
diff --git a/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/test/mock_autofill_save_update_address_profile_delegate_ios.mm b/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/test/mock_autofill_save_update_address_profile_delegate_ios.mm
index 7224d8e..75aad831 100644
--- a/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/test/mock_autofill_save_update_address_profile_delegate_ios.mm
+++ b/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/test/mock_autofill_save_update_address_profile_delegate_ios.mm
@@ -18,10 +18,13 @@
         const autofill::AutofillProfile* original_profile,
         const std::string& locale,
         autofill::AutofillClient::AddressProfileSavePromptCallback callback)
-    : AutofillSaveUpdateAddressProfileDelegateIOS(profile,
-                                                  original_profile,
-                                                  locale,
-                                                  std::move(callback)) {}
+    : AutofillSaveUpdateAddressProfileDelegateIOS(
+          profile,
+          original_profile,
+          /*syncing_user_email=*/absl::nullopt,
+          locale,
+          autofill::AutofillClient::SaveAddressProfilePromptOptions{},
+          std::move(callback)) {}
 
 MockAutofillSaveUpdateAddressProfileDelegateIOS::
     ~MockAutofillSaveUpdateAddressProfileDelegateIOS() = default;
diff --git a/ios/chrome/browser/ntp/features.h b/ios/chrome/browser/ntp/features.h
index d187506..614bc42 100644
--- a/ios/chrome/browser/ntp/features.h
+++ b/ios/chrome/browser/ntp/features.h
@@ -81,8 +81,8 @@
 extern const char kAppCloseBackgroundRefreshIntervalInSeconds[];
 
 // Feature param under `kEnableFeedInvisibleForegroundRefresh` for the time
-// interval used to set the session end timer.
-extern const char kFeedSessionEndTimerTimeoutInSeconds[];
+// interval used to set the refresh timer.
+extern const char kFeedRefreshTimerTimeoutInSeconds[];
 
 // Feature param under `kEnableFeedInvisibleForegroundRefresh` for the refresh
 // threshold when the last refresh was seen.
@@ -167,7 +167,7 @@
 double GetAppCloseBackgroundRefreshIntervalInSeconds();
 
 // Returns the time interval used to set the session end timer.
-double GetFeedSessionEndTimerTimeoutInSeconds();
+double GetFeedRefreshTimerTimeoutInSeconds();
 
 // Returns the refresh threshold (aka feed expiration) for a feed that has been
 // seen.
diff --git a/ios/chrome/browser/ntp/features.mm b/ios/chrome/browser/ntp/features.mm
index dcd43e2..c6dc3e2d 100644
--- a/ios/chrome/browser/ntp/features.mm
+++ b/ios/chrome/browser/ntp/features.mm
@@ -73,7 +73,6 @@
     "BackgroundRefreshIntervalInSeconds";
 const char kBackgroundRefreshMaxAgeInSeconds[] =
     "BackgroundRefreshMaxAgeInSeconds";
-
 const char kEnableFeedSessionCloseForegroundRefresh[] =
     "EnableFeedSessionCloseForegroundRefresh";
 const char kEnableFeedAppCloseForegroundRefresh[] =
@@ -82,8 +81,8 @@
     "EnableFeedAppCloseBackgroundRefresh";
 const char kAppCloseBackgroundRefreshIntervalInSeconds[] =
     "AppCloseBackgroundRefreshIntervalInSeconds";
-const char kFeedSessionEndTimerTimeoutInSeconds[] =
-    "FeedSessionEndTimerTimeoutInSeconds";
+const char kFeedRefreshTimerTimeoutInSeconds[] =
+    "FeedRefreshTimerTimeoutInSeconds";
 const char kFeedSeenRefreshThresholdInSeconds[] =
     "FeedSeenRefreshThresholdInSeconds";
 const char kFeedUnseenRefreshThresholdInSeconds[] =
@@ -234,15 +233,14 @@
       /*default=*/base::Minutes(5).InSecondsF());
 }
 
-double GetFeedSessionEndTimerTimeoutInSeconds() {
+double GetFeedRefreshTimerTimeoutInSeconds() {
   double override_value = [[NSUserDefaults standardUserDefaults]
-      doubleForKey:@"FeedSessionEndTimerTimeoutInSeconds"];
+      doubleForKey:@"FeedRefreshTimerTimeoutInSeconds"];
   if (override_value > 0.0) {
     return override_value;
   }
   return base::GetFieldTrialParamByFeatureAsDouble(
-      kEnableFeedInvisibleForegroundRefresh,
-      kFeedSessionEndTimerTimeoutInSeconds,
+      kEnableFeedInvisibleForegroundRefresh, kFeedRefreshTimerTimeoutInSeconds,
       /*default=*/base::Minutes(5).InSecondsF());
 }
 
diff --git a/ios/chrome/browser/overlays/public/infobar_banner/save_address_profile_infobar_banner_overlay_request_config.h b/ios/chrome/browser/overlays/public/infobar_banner/save_address_profile_infobar_banner_overlay_request_config.h
index 15a302b..57dce5e3 100644
--- a/ios/chrome/browser/overlays/public/infobar_banner/save_address_profile_infobar_banner_overlay_request_config.h
+++ b/ios/chrome/browser/overlays/public/infobar_banner/save_address_profile_infobar_banner_overlay_request_config.h
@@ -37,6 +37,8 @@
   // The banner type.
   BOOL is_update_banner() const { return is_update_banner_; }
 
+  BOOL is_migration_to_account() const { return is_migration_to_account_; }
+
  private:
   OVERLAY_USER_DATA_SETUP(SaveAddressProfileBannerRequestConfig);
   explicit SaveAddressProfileBannerRequestConfig(infobars::InfoBar* infobar);
@@ -53,6 +55,9 @@
   std::u16string button_label_text_;
   NSString* icon_image_name_ = nil;
 
+  // Denotes that the profile will be saved to Google Account.
+  bool is_migration_to_account_ = false;
+
   // Determines the type of the banner, true for save and false for the update.
   bool is_update_banner_ = false;
 };
diff --git a/ios/chrome/browser/overlays/public/infobar_banner/save_address_profile_infobar_banner_overlay_request_config.mm b/ios/chrome/browser/overlays/public/infobar_banner/save_address_profile_infobar_banner_overlay_request_config.mm
index 36e85a9..9bdf1c8 100644
--- a/ios/chrome/browser/overlays/public/infobar_banner/save_address_profile_infobar_banner_overlay_request_config.mm
+++ b/ios/chrome/browser/overlays/public/infobar_banner/save_address_profile_infobar_banner_overlay_request_config.mm
@@ -37,6 +37,7 @@
   description_ = delegate->GetDescription();
   is_update_banner_ = delegate->GetOriginalProfile() ? true : false;
   icon_image_name_ = kIconImageName;
+  is_migration_to_account_ = delegate->IsMigrationToAccount();
 }
 
 SaveAddressProfileBannerRequestConfig::
diff --git a/ios/chrome/browser/policy/configuration_policy_handler_list_factory.mm b/ios/chrome/browser/policy/configuration_policy_handler_list_factory.mm
index 4ea75f0a..ee312e46 100644
--- a/ios/chrome/browser/policy/configuration_policy_handler_list_factory.mm
+++ b/ios/chrome/browser/policy/configuration_policy_handler_list_factory.mm
@@ -81,7 +81,7 @@
     prefs::kManagedDefaultPopupsSetting,
     base::Value::Type::INTEGER },
   { policy::key::kIncognitoModeAvailability,
-    prefs::kIncognitoModeAvailability,
+    policy::policy_prefs::kIncognitoModeAvailability,
     base::Value::Type::INTEGER },
   { policy::key::kNTPContentSuggestionsEnabled,
     prefs::kNTPContentSuggestionsEnabled,
diff --git a/ios/chrome/browser/policy/policy_util.mm b/ios/chrome/browser/policy/policy_util.mm
index c242c9a8..fb806ee 100644
--- a/ios/chrome/browser/policy/policy_util.mm
+++ b/ios/chrome/browser/policy/policy_util.mm
@@ -5,8 +5,8 @@
 #import "ios/chrome/browser/policy/policy_util.h"
 
 #import "components/policy/core/common/policy_loader_ios_constants.h"
+#import "components/policy/core/common/policy_pref_names.h"
 #import "components/prefs/pref_service.h"
-#import "ios/chrome/browser/prefs/pref_names.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
@@ -15,18 +15,21 @@
 bool IsIncognitoPolicyApplied(PrefService* pref_service) {
   if (!pref_service)
     return NO;
-  return pref_service->IsManagedPreference(prefs::kIncognitoModeAvailability);
+  return pref_service->IsManagedPreference(
+      policy::policy_prefs::kIncognitoModeAvailability);
 }
 
 bool IsIncognitoModeDisabled(PrefService* pref_service) {
   return IsIncognitoPolicyApplied(pref_service) &&
-         pref_service->GetInteger(prefs::kIncognitoModeAvailability) ==
+         pref_service->GetInteger(
+             policy::policy_prefs::kIncognitoModeAvailability) ==
              static_cast<int>(IncognitoModePrefs::kDisabled);
 }
 
 bool IsIncognitoModeForced(PrefService* pref_service) {
   return IsIncognitoPolicyApplied(pref_service) &&
-         pref_service->GetInteger(prefs::kIncognitoModeAvailability) ==
+         pref_service->GetInteger(
+             policy::policy_prefs::kIncognitoModeAvailability) ==
              static_cast<int>(IncognitoModePrefs::kForced);
 }
 
diff --git a/ios/chrome/browser/prefs/browser_prefs.mm b/ios/chrome/browser/prefs/browser_prefs.mm
index fd58e11..e9fc1df 100644
--- a/ios/chrome/browser/prefs/browser_prefs.mm
+++ b/ios/chrome/browser/prefs/browser_prefs.mm
@@ -372,8 +372,9 @@
   registry->RegisterStringPref(prefs::kNewTabPageLocationOverride,
                                std::string());
 
-  registry->RegisterIntegerPref(prefs::kIncognitoModeAvailability,
-                                static_cast<int>(IncognitoModePrefs::kEnabled));
+  registry->RegisterIntegerPref(
+      policy::policy_prefs::kIncognitoModeAvailability,
+      static_cast<int>(IncognitoModePrefs::kEnabled));
 
   registry->RegisterBooleanPref(prefs::kPrintingEnabled, true);
 
diff --git a/ios/chrome/browser/prefs/pref_names.cc b/ios/chrome/browser/prefs/pref_names.cc
index ff1ba55..b81462e 100644
--- a/ios/chrome/browser/prefs/pref_names.cc
+++ b/ios/chrome/browser/prefs/pref_names.cc
@@ -68,12 +68,6 @@
 // * Otherwise: Default value driven by Finch config.
 const char kInactiveTabsTimeThreshold[] = "ios.inactive_tabs.time_threshold";
 
-// Integer that specifies whether Incognito mode is:
-// 0 - Enabled. Default behaviour. Default mode is available on demand.
-// 1 - Disabled. User cannot browse pages in Incognito mode.
-// 2 - Forced. All pages/sessions are forced into Incognito.
-const char kIncognitoModeAvailability[] = "incognito.mode_availability";
-
 // Boolean that is true when the Incognito interstitial for third-party intents
 // is enabled.
 const char kIncognitoInterstitialEnabled[] =
diff --git a/ios/chrome/browser/prefs/pref_names.h b/ios/chrome/browser/prefs/pref_names.h
index 40bb93e0..0eb9ffe 100644
--- a/ios/chrome/browser/prefs/pref_names.h
+++ b/ios/chrome/browser/prefs/pref_names.h
@@ -23,7 +23,6 @@
 extern const char kFirstFollowUIShownCount[];
 extern const char kHttpServerProperties[];
 extern const char kInactiveTabsTimeThreshold[];
-extern const char kIncognitoModeAvailability[];
 extern const char kIncognitoInterstitialEnabled[];
 extern const char kIosCredentialProviderPromoPolicyEnabled[];
 extern const char kIosCredentialProviderPromoStopPromo[];
diff --git a/ios/chrome/browser/resources/Settings.bundle/ExperimentalFeedRefresh.plist b/ios/chrome/browser/resources/Settings.bundle/ExperimentalFeedRefresh.plist
index 2cf0493..65ffbf5 100644
--- a/ios/chrome/browser/resources/Settings.bundle/ExperimentalFeedRefresh.plist
+++ b/ios/chrome/browser/resources/Settings.bundle/ExperimentalFeedRefresh.plist
@@ -90,9 +90,9 @@
 			<key>Type</key>
 			<string>PSTextFieldSpecifier</string>
 			<key>Title</key>
-			<string>Session end timeout in seconds</string>
+			<string>Refresh timer timeout in seconds</string>
 			<key>Key</key>
-			<string>FeedSessionEndTimerTimeoutInSeconds</string>
+			<string>FeedRefreshTimerTimeoutInSeconds</string>
 			<key>DefaultValue</key>
 			<string></string>
 			<key>KeyboardType</key>
diff --git a/ios/chrome/browser/tabs/inactive_tabs/BUILD.gn b/ios/chrome/browser/tabs/inactive_tabs/BUILD.gn
index 1ed537aa..367bf27 100644
--- a/ios/chrome/browser/tabs/inactive_tabs/BUILD.gn
+++ b/ios/chrome/browser/tabs/inactive_tabs/BUILD.gn
@@ -44,12 +44,16 @@
     "//base/test:test_support",
     "//ios/chrome/browser/browser_state:test_support",
     "//ios/chrome/browser/main:test_support",
+    "//ios/chrome/browser/ntp",
     "//ios/chrome/browser/snapshots",
     "//ios/chrome/browser/tabs:features",
+    "//ios/chrome/browser/url:constants",
+    "//ios/chrome/browser/web",
     "//ios/chrome/browser/web_state_list",
     "//ios/chrome/browser/web_state_list:test_support",
     "//ios/chrome/test:test_support",
     "//ios/web/public/test",
     "//ios/web/public/test/fakes",
+    "//third_party/ocmock",
   ]
 }
diff --git a/ios/chrome/browser/tabs/inactive_tabs/utils_unittest.mm b/ios/chrome/browser/tabs/inactive_tabs/utils_unittest.mm
index f211a65..e5af7b6 100644
--- a/ios/chrome/browser/tabs/inactive_tabs/utils_unittest.mm
+++ b/ios/chrome/browser/tabs/inactive_tabs/utils_unittest.mm
@@ -8,18 +8,24 @@
 #import "base/test/task_environment.h"
 #import "ios/chrome/browser/browser_state/test_chrome_browser_state.h"
 #import "ios/chrome/browser/main/test_browser.h"
+#import "ios/chrome/browser/ntp/new_tab_page_tab_helper.h"
+#import "ios/chrome/browser/ntp/new_tab_page_tab_helper_delegate.h"
 #import "ios/chrome/browser/snapshots/snapshot_browser_agent.h"
 #import "ios/chrome/browser/snapshots/snapshot_tab_helper.h"
 #import "ios/chrome/browser/tabs/features.h"
 #import "ios/chrome/browser/tabs/inactive_tabs/features.h"
 #import "ios/chrome/browser/tabs/inactive_tabs/utils.h"
+#import "ios/chrome/browser/url/chrome_url_constants.h"
+#import "ios/chrome/browser/web/web_navigation_util.h"
 #import "ios/chrome/browser/web_state_list/fake_web_state_list_delegate.h"
 #import "ios/chrome/browser/web_state_list/web_state_list.h"
 #import "ios/chrome/browser/web_state_list/web_state_opener.h"
 #import "ios/chrome/test/ios_chrome_scoped_testing_local_state.h"
+#import "ios/web/public/test/fakes/fake_navigation_manager.h"
 #import "ios/web/public/test/fakes/fake_web_state.h"
 #import "ios/web/public/test/web_task_environment.h"
 #import "testing/platform_test.h"
+#import "third_party/ocmock/OCMock/OCMock.h"
 #import "ui/base/device_form_factor.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
@@ -391,3 +397,60 @@
   std::vector<int> expected_last_activity_order = {18, 0, 10, 30, 2, 16, 0, 0};
   CheckOrder(active_web_state_list, expected_last_activity_order);
 }
+
+TEST_F(InactiveTabsUtilsTest, DoNotMoveNTPInInactive) {
+  // No inactive tabs on iPad.
+  if (ui::GetDeviceFormFactor() == ui::DEVICE_FORM_FACTOR_TABLET) {
+    return;
+  }
+  base::test::ScopedFeatureList feature_list;
+  std::map<std::string, std::string> parameters;
+  parameters[kTabInactivityThresholdParameterName] =
+      kTabInactivityThresholdOneWeekParam;
+  feature_list.InitAndEnableFeatureWithParameters(kTabInactivityThreshold,
+                                                  parameters);
+
+  // Needed to use the NewTabPageTabHelper and ensure that the tab is an NTP.
+  std::unique_ptr<web::FakeNavigationManager> fake_navigation_manager =
+      std::make_unique<web::FakeNavigationManager>();
+
+  std::unique_ptr<web::NavigationItem> pending_item =
+      web::NavigationItem::Create();
+  pending_item->SetURL(GURL(kChromeUIAboutNewTabURL));
+  fake_navigation_manager->SetPendingItem(pending_item.get());
+
+  // Create a New Tab Page (NTP) tab with the last activity at 30 days ago.
+  std::unique_ptr<web::FakeWebState> fake_web_state =
+      std::make_unique<web::FakeWebState>();
+  GURL url(kChromeUINewTabURL);
+  fake_web_state->SetVisibleURL(url);
+  fake_web_state->SetNavigationManager(std::move(fake_navigation_manager));
+  fake_web_state->SetLastActiveTime(base::Time::Now() - base::Days(30));
+
+  // Ensure this is an ntp web state.
+  id delegate = OCMProtocolMock(@protocol(NewTabPageTabHelperDelegate));
+  NewTabPageTabHelper::CreateForWebState(fake_web_state.get());
+  NewTabPageTabHelper* ntp_helper =
+      NewTabPageTabHelper::FromWebState(fake_web_state.get());
+  ntp_helper->SetDelegate(delegate);
+  ASSERT_TRUE(ntp_helper->IsActive());
+
+  WebStateList* active_web_state_list = browser_active_->GetWebStateList();
+  WebStateList* inactive_web_state_list = browser_inactive_->GetWebStateList();
+
+  EXPECT_EQ(active_web_state_list->count(), 0);
+  EXPECT_EQ(inactive_web_state_list->count(), 0);
+
+  // Add the created ntp in the active browser.
+  active_web_state_list->InsertWebState(0, std::move(fake_web_state),
+                                        WebStateList::INSERT_ACTIVATE,
+                                        WebStateOpener());
+
+  EXPECT_EQ(active_web_state_list->count(), 1);
+  EXPECT_EQ(inactive_web_state_list->count(), 0);
+
+  MoveTabsFromActiveToInactive(browser_active_.get(), browser_inactive_.get());
+
+  EXPECT_EQ(active_web_state_list->count(), 1);
+  EXPECT_EQ(inactive_web_state_list->count(), 0);
+}
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 f3bee55e..4a2bd92 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
@@ -116,10 +116,11 @@
 
   NSString* skipButtonTitle;
   if (self.accessPoint ==
-          signin_metrics::AccessPoint::ACCESS_POINT_SEND_TAB_TO_SELF_PROMO ||
-      self.accessPoint ==
-          signin_metrics::AccessPoint::ACCESS_POINT_NTP_FEED_CARD_MENU_PROMO) {
+      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);
   }
diff --git a/ios/chrome/browser/ui/authentication/signin/consistency_promo_signin/consistency_promo_signin_coordinator.mm b/ios/chrome/browser/ui/authentication/signin/consistency_promo_signin/consistency_promo_signin_coordinator.mm
index bc2395cbb..d3105e5 100644
--- a/ios/chrome/browser/ui/authentication/signin/consistency_promo_signin/consistency_promo_signin_coordinator.mm
+++ b/ios/chrome/browser/ui/authentication/signin/consistency_promo_signin/consistency_promo_signin_coordinator.mm
@@ -201,19 +201,12 @@
   DCHECK(completionInfo);
   [self.consistencyPromoSigninMediator
       systemIdentityAdded:completionInfo.identity];
+  self.defaultAccountCoordinator.selectedIdentity = completionInfo.identity;
 
   if (!startSignin) {
     return;
   }
-  AuthenticationFlow* authenticationFlow =
-      [[AuthenticationFlow alloc] initWithBrowser:self.browser
-                                         identity:completionInfo.identity
-                                 postSignInAction:POST_SIGNIN_ACTION_NONE
-                         presentingViewController:self.navigationController];
-  authenticationFlow.dispatcher = HandlerForProtocol(
-      self.browser->GetCommandDispatcher(), BrowsingDataCommands);
-  [self.consistencyPromoSigninMediator
-      signinWithAuthenticationFlow:authenticationFlow];
+  [self startSignIn];
 }
 
 // Opens an AddAccountSigninCoordinator to add an account to the device. If
@@ -267,6 +260,19 @@
                                completionInfo:completionInfo];
 }
 
+// Starts the sign-in flow.
+- (void)startSignIn {
+  AuthenticationFlow* authenticationFlow =
+      [[AuthenticationFlow alloc] initWithBrowser:self.browser
+                                         identity:self.selectedIdentity
+                                 postSignInAction:POST_SIGNIN_ACTION_NONE
+                         presentingViewController:self.navigationController];
+  authenticationFlow.dispatcher = HandlerForProtocol(
+      self.browser->GetCommandDispatcher(), BrowsingDataCommands);
+  [self.consistencyPromoSigninMediator
+      signinWithAuthenticationFlow:authenticationFlow];
+}
+
 #pragma mark - ConsistencyAccountChooserCoordinatorDelegate
 
 - (void)consistencyAccountChooserCoordinatorIdentitySelected:
@@ -333,15 +339,7 @@
 - (void)consistencyDefaultAccountCoordinatorSignin:
     (ConsistencyDefaultAccountCoordinator*)coordinator {
   DCHECK_EQ(coordinator, self.defaultAccountCoordinator);
-  AuthenticationFlow* authenticationFlow =
-      [[AuthenticationFlow alloc] initWithBrowser:self.browser
-                                         identity:self.selectedIdentity
-                                 postSignInAction:POST_SIGNIN_ACTION_NONE
-                         presentingViewController:self.navigationController];
-  authenticationFlow.dispatcher = HandlerForProtocol(
-      self.browser->GetCommandDispatcher(), BrowsingDataCommands);
-  [self.consistencyPromoSigninMediator
-      signinWithAuthenticationFlow:authenticationFlow];
+  [self startSignIn];
 }
 
 - (void)consistencyDefaultAccountCoordinatorOpenAddAccount:
diff --git a/ios/chrome/browser/ui/autofill/chrome_autofill_client_ios.h b/ios/chrome/browser/ui/autofill/chrome_autofill_client_ios.h
index 66d06064..607d50ca 100644
--- a/ios/chrome/browser/ui/autofill/chrome_autofill_client_ios.h
+++ b/ios/chrome/browser/ui/autofill/chrome_autofill_client_ios.h
@@ -156,6 +156,9 @@
       base::OnceCallback<void(const std::string&)> callback) override;
 
  private:
+  // Returns the account email if the account is syncing.
+  absl::optional<std::u16string> SyncingUserEmail();
+
   PrefService* pref_service_;
   syncer::SyncService* sync_service_;
   std::unique_ptr<AutofillDownloadManager> download_manager_;
diff --git a/ios/chrome/browser/ui/autofill/chrome_autofill_client_ios.mm b/ios/chrome/browser/ui/autofill/chrome_autofill_client_ios.mm
index 9e30bff..981af8b 100644
--- a/ios/chrome/browser/ui/autofill/chrome_autofill_client_ios.mm
+++ b/ios/chrome/browser/ui/autofill/chrome_autofill_client_ios.mm
@@ -49,8 +49,12 @@
 #import "ios/chrome/browser/infobars/infobar_ios.h"
 #import "ios/chrome/browser/infobars/infobar_utils.h"
 #import "ios/chrome/browser/passwords/password_tab_helper.h"
+#import "ios/chrome/browser/signin/authentication_service.h"
+#import "ios/chrome/browser/signin/authentication_service_factory.h"
 #import "ios/chrome/browser/signin/identity_manager_factory.h"
 #import "ios/chrome/browser/sync/sync_service_factory.h"
+#import "ios/chrome/browser/sync/sync_setup_service.h"
+#import "ios/chrome/browser/sync/sync_setup_service_factory.h"
 #import "ios/chrome/browser/translate/chrome_ios_translate_client.h"
 #import "ios/chrome/browser/ui/autofill/card_expiration_date_fix_flow_view_bridge.h"
 #import "ios/chrome/browser/ui/autofill/card_name_fix_flow_view_bridge.h"
@@ -380,8 +384,9 @@
   }
 
   auto delegate = std::make_unique<AutofillSaveUpdateAddressProfileDelegateIOS>(
-      profile, original_profile,
-      GetApplicationContext()->GetApplicationLocale(), std::move(callback));
+      profile, original_profile, SyncingUserEmail(),
+      GetApplicationContext()->GetApplicationLocale(), options,
+      std::move(callback));
 
   infobar_manager_->AddInfoBar(std::make_unique<InfoBarIOS>(
       InfobarType::kInfobarTypeSaveAutofillAddressProfile,
@@ -532,4 +537,20 @@
       base::SysNSStringToUTF8(ios::provider::GetRiskData()));
 }
 
+absl::optional<std::u16string> ChromeAutofillClientIOS::SyncingUserEmail() {
+  AuthenticationService* authenticationService =
+      AuthenticationServiceFactory::GetForBrowserState(browser_state_);
+  DCHECK(authenticationService);
+  id<SystemIdentity> identity =
+      authenticationService->GetPrimaryIdentity(signin::ConsentLevel::kSync);
+  if (identity) {
+    SyncSetupService* syncSetupService =
+        SyncSetupServiceFactory::GetForBrowserState(browser_state_);
+    if (syncSetupService->IsDataTypeActive(syncer::AUTOFILL)) {
+      return base::SysNSStringToUTF16(identity.userEmail);
+    }
+  }
+  return absl::nullopt;
+}
+
 }  // namespace autofill
diff --git a/ios/chrome/browser/ui/browser_view/browser_coordinator.mm b/ios/chrome/browser/ui/browser_view/browser_coordinator.mm
index 83d50da7b..598dbc7 100644
--- a/ios/chrome/browser/ui/browser_view/browser_coordinator.mm
+++ b/ios/chrome/browser/ui/browser_view/browser_coordinator.mm
@@ -868,6 +868,10 @@
       IdentityManagerFactory::GetForBrowserState(
           self.browser->GetBrowserState());
   _viewControllerDependencies.voiceSearchController = _voiceSearchController;
+  _viewControllerDependencies.secondaryToolbarContainerCoordinator =
+      [[ToolbarContainerCoordinator alloc]
+          initWithBrowser:self.browser
+                     type:ToolbarContainerType::kSecondary];
 }
 
 - (void)updateViewControllerDependencies {
@@ -928,6 +932,7 @@
   _viewControllerDependencies.readingModel = nil;
   _viewControllerDependencies.identityManager = nil;
   _viewControllerDependencies.voiceSearchController = nil;
+  _viewControllerDependencies.secondaryToolbarContainerCoordinator = nil;
 
   [_bookmarksCoordinator shutdown];
   _bookmarksCoordinator = nil;
diff --git a/ios/chrome/browser/ui/browser_view/browser_view_controller.h b/ios/chrome/browser/ui/browser_view/browser_view_controller.h
index ef2f823..47f943c 100644
--- a/ios/chrome/browser/ui/browser_view/browser_view_controller.h
+++ b/ios/chrome/browser/ui/browser_view/browser_view_controller.h
@@ -26,6 +26,7 @@
 #import "ios/chrome/browser/ui/page_info/requirements/page_info_presentation.h"
 #import "ios/chrome/browser/ui/settings/sync/utils/sync_presenter.h"
 #import "ios/chrome/browser/ui/thumb_strip/thumb_strip_supporting.h"
+#import "ios/chrome/browser/ui/toolbar_container/toolbar_container_coordinator.h"
 #import "ios/chrome/browser/url_loading/url_loading_browser_agent.h"
 #import "ios/chrome/browser/url_loading/url_loading_notifier_browser_agent.h"
 #import "ios/chrome/browser/web/web_navigation_browser_agent.h"
@@ -65,6 +66,7 @@
 @protocol TextZoomCommands;
 @class ToolbarAccessoryPresenter;
 @protocol ToolbarCommands;
+@class ToolbarContainerCoordinator;
 @protocol IncognitoReauthCommands;
 @class LayoutGuideCenter;
 @protocol LoadQueryCommands;
@@ -113,6 +115,7 @@
   TabUsageRecorderBrowserAgent* tabUsageRecorderBrowserAgent;
   WebNavigationBrowserAgent* webNavigationBrowserAgent;
   base::WeakPtr<WebStateList> webStateList;
+  ToolbarContainerCoordinator* secondaryToolbarContainerCoordinator;
 } BrowserViewControllerDependencies;
 
 // The top-level view controller for the browser UI. Manages other controllers
diff --git a/ios/chrome/browser/ui/browser_view/browser_view_controller.mm b/ios/chrome/browser/ui/browser_view/browser_view_controller.mm
index 4960b96..c8f0bd2 100644
--- a/ios/chrome/browser/ui/browser_view/browser_view_controller.mm
+++ b/ios/chrome/browser/ui/browser_view/browser_view_controller.mm
@@ -504,6 +504,8 @@
     _readingModel = dependencies.readingModel;
     _identityManager = dependencies.identityManager;
     _voiceSearchController = dependencies.voiceSearchController;
+    self.secondaryToolbarContainerCoordinator =
+        dependencies.secondaryToolbarContainerCoordinator;
 
     dependencies.lensCoordinator.delegate = self;
 
@@ -1512,10 +1514,6 @@
   // TODO(crbug.com/880672): Finish ToolbarContainer work.
   if (base::FeatureList::IsEnabled(
           toolbar_container::kToolbarContainerEnabled)) {
-    self.secondaryToolbarContainerCoordinator =
-        [[ToolbarContainerCoordinator alloc]
-            initWithBrowser:self.browser
-                       type:ToolbarContainerType::kSecondary];
     self.secondaryToolbarContainerCoordinator.toolbarCoordinators =
         @[ self.secondaryToolbarCoordinator ];
     [self.secondaryToolbarContainerCoordinator start];
diff --git a/ios/chrome/browser/ui/browser_view/browser_view_controller_unittest.mm b/ios/chrome/browser/ui/browser_view/browser_view_controller_unittest.mm
index 6d344a1..22cc5c1 100644
--- a/ios/chrome/browser/ui/browser_view/browser_view_controller_unittest.mm
+++ b/ios/chrome/browser/ui/browser_view/browser_view_controller_unittest.mm
@@ -281,6 +281,10 @@
         browser_.get()->GetBrowserState());
     dependencies.identityManager = IdentityManagerFactory::GetForBrowserState(
         browser_.get()->GetBrowserState());
+    dependencies.secondaryToolbarContainerCoordinator =
+        [[ToolbarContainerCoordinator alloc]
+            initWithBrowser:browser_.get()
+                       type:ToolbarContainerType::kSecondary];
 
     bvc_ = [[BrowserViewController alloc] initWithBrowser:browser_.get()
                            browserContainerViewController:container_
diff --git a/ios/chrome/browser/ui/credential_provider_promo/BUILD.gn b/ios/chrome/browser/ui/credential_provider_promo/BUILD.gn
index b83961f..eaa8ddd5 100644
--- a/ios/chrome/browser/ui/credential_provider_promo/BUILD.gn
+++ b/ios/chrome/browser/ui/credential_provider_promo/BUILD.gn
@@ -70,7 +70,10 @@
 
 source_set("unit_tests") {
   testonly = true
-  sources = [ "credential_provider_promo_mediator_unittest.mm" ]
+  sources = [
+    "credential_provider_promo_coordinator_unittest.mm",
+    "credential_provider_promo_mediator_unittest.mm",
+  ]
   configs += [ "//build/config/compiler:enable_arc" ]
   deps = [
     ":credential_provider_promo",
@@ -79,13 +82,17 @@
     "//components/password_manager/core/common",
     "//components/prefs:test_support",
     "//ios/chrome/app/strings",
+    "//ios/chrome/browser/browser_state:test_support",
     "//ios/chrome/browser/credential_provider_promo:features",
+    "//ios/chrome/browser/main:test_support",
     "//ios/chrome/browser/prefs:pref_names",
     "//ios/chrome/browser/promos_manager",
     "//ios/chrome/browser/promos_manager:test_support",
     "//ios/chrome/browser/shared/public/commands",
+    "//ios/chrome/common/ui/confirmation_alert",
     "//ios/chrome/test:test_support",
     "//ios/public/provider/chrome/browser/branded_images:branded_images_api",
+    "//ios/web/public/test",
     "//testing/gtest",
     "//third_party/ocmock",
     "//ui/base",
diff --git a/ios/chrome/browser/ui/credential_provider_promo/credential_provider_promo_coordinator_unittest.mm b/ios/chrome/browser/ui/credential_provider_promo/credential_provider_promo_coordinator_unittest.mm
new file mode 100644
index 0000000..82bc1abe
--- /dev/null
+++ b/ios/chrome/browser/ui/credential_provider_promo/credential_provider_promo_coordinator_unittest.mm
@@ -0,0 +1,197 @@
+// 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/credential_provider_promo/credential_provider_promo_coordinator.h"
+
+#import "base/test/metrics/histogram_tester.h"
+#import "base/test/scoped_feature_list.h"
+#import "ios/chrome/browser/browser_state/test_chrome_browser_state.h"
+#import "ios/chrome/browser/credential_provider_promo/features.h"
+#import "ios/chrome/browser/main/test_browser.h"
+#import "ios/chrome/browser/prefs/pref_names.h"
+#import "ios/chrome/browser/shared/public/commands/command_dispatcher.h"
+#import "ios/chrome/browser/shared/public/commands/credential_provider_promo_commands.h"
+#import "ios/chrome/browser/ui/credential_provider_promo/credential_provider_promo_constants.h"
+#import "ios/chrome/browser/ui/credential_provider_promo/credential_provider_promo_coordinator.h"
+#import "ios/chrome/browser/ui/credential_provider_promo/credential_provider_promo_metrics.h"
+#import "ios/chrome/common/ui/confirmation_alert/confirmation_alert_action_handler.h"
+#import "ios/chrome/test/ios_chrome_scoped_testing_local_state.h"
+#import "ios/web/public/test/web_task_environment.h"
+#import "testing/platform_test.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+// Test fixture for testing the CredentialProviderPromoCoordinator class.
+class CredentialProviderPromoCoordinatorTest : public PlatformTest {
+ public:
+  CredentialProviderPromoCoordinatorTest()
+      : browser_state_(TestChromeBrowserState::Builder().Build()),
+        browser_(std::make_unique<TestBrowser>(browser_state_.get())),
+        scoped_feature_list_(std::make_unique<base::test::ScopedFeatureList>()),
+        histogram_tester_(std::make_unique<base::HistogramTester>()) {
+    scoped_feature_list_->InitAndEnableFeature(
+        kCredentialProviderExtensionPromo);
+
+    coordinator_ = [[CredentialProviderPromoCoordinator alloc]
+        initWithBaseViewController:nil
+                           browser:browser_.get()];
+    [coordinator_ start];
+
+    credential_provider_promo_command_handler_ = HandlerForProtocol(
+        browser_->GetCommandDispatcher(), CredentialProviderPromoCommands);
+  }
+  ~CredentialProviderPromoCoordinatorTest() override { [coordinator_ stop]; }
+
+ protected:
+  web::WebTaskEnvironment task_environment_;
+  IOSChromeScopedTestingLocalState local_state_;
+  std::unique_ptr<TestChromeBrowserState> browser_state_;
+  std::unique_ptr<TestBrowser> browser_;
+  std::unique_ptr<base::test::ScopedFeatureList> scoped_feature_list_;
+  std::unique_ptr<base::HistogramTester> histogram_tester_;
+
+  CredentialProviderPromoCoordinator* coordinator_;
+  id<CredentialProviderPromoCommands>
+      credential_provider_promo_command_handler_;
+};
+
+#pragma mark - Tests
+
+// Tests that impression is recorded with the correct source.
+TEST_F(CredentialProviderPromoCoordinatorTest,
+       CredentialProviderPromoImpressionRecorded) {
+  histogram_tester_->ExpectBucketCount(
+      kIOSCredentialProviderPromoImpressionHistogram,
+      IOSCredentialProviderPromoSource::kPasswordCopied, 0);
+
+  // Coordinator will show the promo with PasswordCopied as source.
+  [credential_provider_promo_command_handler_
+      showCredentialProviderPromoWithTrigger:CredentialProviderPromoTrigger::
+                                                 PasswordCopied];
+
+  histogram_tester_->ExpectBucketCount(
+      kIOSCredentialProviderPromoImpressionHistogram,
+      IOSCredentialProviderPromoSource::kPasswordCopied, 1);
+}
+
+// Tests that the remind-me-later promo's impression is recorded with the
+// correct original source.
+TEST_F(CredentialProviderPromoCoordinatorTest,
+       CredentialProviderPromoImpressionFromReminderRecorded) {
+  histogram_tester_->ExpectBucketCount(
+      kIOSCredentialProviderPromoImpressionIsReminderHistogram,
+      IOSCredentialProviderPromoSource::kPasswordCopied, 0);
+
+  // Coordinator will show the promo with PasswordCopied as source, and update
+  // `prefs::kIosCredentialProviderPromoSource`.
+  [credential_provider_promo_command_handler_
+      showCredentialProviderPromoWithTrigger:CredentialProviderPromoTrigger::
+                                                 PasswordCopied];
+
+  // Coordinator is called to again to show the promo as reminder.
+  // `kPasswordCopied` will be used as the original source when recording
+  // metric.
+  [credential_provider_promo_command_handler_
+      showCredentialProviderPromoWithTrigger:CredentialProviderPromoTrigger::
+                                                 RemindMeLater];
+
+  histogram_tester_->ExpectBucketCount(
+      kIOSCredentialProviderPromoImpressionIsReminderHistogram,
+      IOSCredentialProviderPromoSource::kPasswordCopied, 1);
+}
+
+// Tests that tapping the primary CTA in both the first and second step of the
+// promo will result in two primary actions being recorded correctly.
+TEST_F(CredentialProviderPromoCoordinatorTest,
+       CredentialProviderPromoTwoStepPrimaryActionRecorded) {
+  histogram_tester_->ExpectBucketCount(
+      kIOSCredentialProviderPromoOnPasswordSavedHistogram,
+      credential_provider_promo::IOSCredentialProviderPromoAction::kLearnMore,
+      0);
+  // Trigger the promo with PasswordSaved.
+  // The primary CTA on the first step of the promo is 'learn more'.
+  [credential_provider_promo_command_handler_
+      showCredentialProviderPromoWithTrigger:CredentialProviderPromoTrigger::
+                                                 PasswordSaved];
+
+  // Perform the action. Coordinator will record the action and set.
+  // `promoContext` to 'learn more'
+  ASSERT_TRUE([coordinator_
+      conformsToProtocol:@protocol(ConfirmationAlertActionHandler)]);
+  [(id<ConfirmationAlertActionHandler>)
+          coordinator_ confirmationAlertPrimaryAction];
+
+  histogram_tester_->ExpectBucketCount(
+      kIOSCredentialProviderPromoOnPasswordSavedHistogram,
+      credential_provider_promo::IOSCredentialProviderPromoAction::kLearnMore,
+      1);
+  histogram_tester_->ExpectBucketCount(
+      kIOSCredentialProviderPromoOnPasswordSavedHistogram,
+      credential_provider_promo::IOSCredentialProviderPromoAction::
+          kGoToSettings,
+      0);
+
+  // When the `promoContext` is 'learn more', the primary CTA is 'go to
+  // settings'.
+  [(id<ConfirmationAlertActionHandler>)
+          coordinator_ confirmationAlertPrimaryAction];
+  histogram_tester_->ExpectBucketCount(
+      kIOSCredentialProviderPromoOnPasswordSavedHistogram,
+      credential_provider_promo::IOSCredentialProviderPromoAction::
+          kGoToSettings,
+      1);
+}
+
+// Tests that tapping the secondary CTA is recorded correctly when the promo is
+// shown from autofill.
+TEST_F(CredentialProviderPromoCoordinatorTest,
+       CredentialProviderPromoSecondaryActionRecorded) {
+  histogram_tester_->ExpectBucketCount(
+      kIOSCredentialProviderPromoOnAutofillUsedHistogram,
+      credential_provider_promo::IOSCredentialProviderPromoAction::kNo, 0);
+
+  // Trigger the promo with SuccessfulLoginUsingExistingPassword.
+  [credential_provider_promo_command_handler_
+      showCredentialProviderPromoWithTrigger:
+          CredentialProviderPromoTrigger::SuccessfulLoginUsingExistingPassword];
+
+  EXPECT_TRUE([coordinator_
+      conformsToProtocol:@protocol(ConfirmationAlertActionHandler)]);
+  // Perform the action, Coordinator will record the action.
+  [(id<ConfirmationAlertActionHandler>)
+          coordinator_ confirmationAlertSecondaryAction];
+
+  histogram_tester_->ExpectBucketCount(
+      kIOSCredentialProviderPromoOnAutofillUsedHistogram,
+      credential_provider_promo::IOSCredentialProviderPromoAction::kNo, 1);
+}
+
+// Tests that tapping the tertiary CTA is recorded correctly when the promo is
+// shown from password copied.
+TEST_F(CredentialProviderPromoCoordinatorTest,
+       CredentialProviderPromoTertiaryActionRecorded) {
+  histogram_tester_->ExpectBucketCount(
+      kIOSCredentialProviderPromoOnPasswordCopiedHistogram,
+      credential_provider_promo::IOSCredentialProviderPromoAction::
+          kRemindMeLater,
+      0);
+
+  // Trigger the promo with PasswordCopied.
+  [credential_provider_promo_command_handler_
+      showCredentialProviderPromoWithTrigger:CredentialProviderPromoTrigger::
+                                                 PasswordCopied];
+  // Perform the action, Coordinator will record the action.
+  EXPECT_TRUE([coordinator_
+      conformsToProtocol:@protocol(ConfirmationAlertActionHandler)]);
+  [(id<ConfirmationAlertActionHandler>)
+          coordinator_ confirmationAlertTertiaryAction];
+
+  histogram_tester_->ExpectBucketCount(
+      kIOSCredentialProviderPromoOnPasswordCopiedHistogram,
+      credential_provider_promo::IOSCredentialProviderPromoAction::
+          kRemindMeLater,
+      1);
+}
diff --git a/ios/chrome/browser/ui/credential_provider_promo/credential_provider_promo_metrics.h b/ios/chrome/browser/ui/credential_provider_promo/credential_provider_promo_metrics.h
index c601c11..7f366d04 100644
--- a/ios/chrome/browser/ui/credential_provider_promo/credential_provider_promo_metrics.h
+++ b/ios/chrome/browser/ui/credential_provider_promo/credential_provider_promo_metrics.h
@@ -6,6 +6,18 @@
 #ifndef IOS_CHROME_BROWSER_UI_CREDENTIAL_PROVIDER_PROMO_CREDENTIAL_PROVIDER_PROMO_METRICS_H_
 #define IOS_CHROME_BROWSER_UI_CREDENTIAL_PROVIDER_PROMO_CREDENTIAL_PROVIDER_PROMO_METRICS_H_
 
+extern const char kIOSCredentialProviderPromoImpressionHistogram[];
+extern const char kIOSCredentialProviderPromoImpressionIsReminderHistogram[];
+extern const char kIOSCredentialProviderPromoOnPasswordSavedHistogram[];
+extern const char
+    kIOSCredentialProviderPromoOnPasswordSavedIsReminderHistogram[];
+extern const char kIOSCredentialProviderPromoOnPasswordCopiedHistogram[];
+extern const char
+    kIOSCredentialProviderPromoOnPasswordCopiedIsReminderHistogram[];
+extern const char kIOSCredentialProviderPromoOnAutofillUsedHistogram[];
+extern const char
+    kIOSCredentialProviderPromoOnAutofillUsedIsReminderHistogram[];
+
 namespace credential_provider_promo {
 
 // Enum for histograms like
diff --git a/ios/chrome/browser/ui/credential_provider_promo/credential_provider_promo_metrics.mm b/ios/chrome/browser/ui/credential_provider_promo/credential_provider_promo_metrics.mm
index 7692534..cb8cb84 100644
--- a/ios/chrome/browser/ui/credential_provider_promo/credential_provider_promo_metrics.mm
+++ b/ios/chrome/browser/ui/credential_provider_promo/credential_provider_promo_metrics.mm
@@ -11,16 +11,35 @@
 #error "This file requires ARC support."
 #endif
 
+const char kIOSCredentialProviderPromoImpressionHistogram[] =
+    "IOS.CredentialProviderExtension.Promo.Impression";
+const char kIOSCredentialProviderPromoImpressionIsReminderHistogram[] =
+    "IOS.CredentialProviderExtension.Promo.Impression.IsReminder";
+const char kIOSCredentialProviderPromoOnPasswordSavedHistogram[] =
+    "IOS.CredentialProviderExtension.Promo.OnPasswordSaved";
+const char kIOSCredentialProviderPromoOnPasswordSavedIsReminderHistogram[] =
+    "IOS.CredentialProviderExtension.Promo.OnPasswordSaved.IsReminder";
+const char kIOSCredentialProviderPromoOnPasswordCopiedHistogram[] =
+    "IOS.CredentialProviderExtension.Promo.OnPasswordCopied";
+const char kIOSCredentialProviderPromoOnPasswordCopiedIsReminderHistogram[] =
+    "IOS.CredentialProviderExtension.Promo.OnPasswordCopied.IsReminder";
+const char kIOSCredentialProviderPromoOnAutofillUsedHistogram[] =
+    "IOS.CredentialProviderExtension.Promo."
+    "OnSuccessfulLoginWithAutofilledPassword";
+const char kIOSCredentialProviderPromoOnAutofillUsedIsReminderHistogram[] =
+    "IOS.CredentialProviderExtension.Promo."
+    "OnSuccessfulLoginWithAutofilledPassword.IsReminder";
+
 namespace credential_provider_promo {
 
 void RecordImpression(IOSCredentialProviderPromoSource source,
                       bool is_reminder) {
   if (is_reminder) {
     base::UmaHistogramEnumeration(
-        "IOS.CredentialProviderExtension.Promo.Impression.IsReminder", source);
+        kIOSCredentialProviderPromoImpressionIsReminderHistogram, source);
   } else {
     base::UmaHistogramEnumeration(
-        "IOS.CredentialProviderExtension.Promo.Impression", source);
+        kIOSCredentialProviderPromoImpressionHistogram, source);
   }
 }
 
@@ -30,22 +49,20 @@
   std::string name;
   switch (source) {
     case IOSCredentialProviderPromoSource::kPasswordCopied:
-      name = is_reminder
-                 ? "IOS.CredentialProviderExtension.Promo.OnPasswordCopied."
-                   "IsReminder"
-                 : "IOS.CredentialProviderExtension.Promo.OnPasswordCopied";
+      name =
+          is_reminder
+              ? kIOSCredentialProviderPromoOnPasswordCopiedIsReminderHistogram
+              : kIOSCredentialProviderPromoOnPasswordCopiedHistogram;
       break;
     case IOSCredentialProviderPromoSource::kPasswordSaved:
       name = is_reminder
-                 ? "IOS.CredentialProviderExtension.Promo.OnPasswordSaved."
-                   "IsReminder"
-                 : "IOS.CredentialProviderExtension.Promo.OnPasswordSaved";
+                 ? kIOSCredentialProviderPromoOnPasswordSavedIsReminderHistogram
+                 : kIOSCredentialProviderPromoOnPasswordSavedHistogram;
       break;
     case IOSCredentialProviderPromoSource::kAutofillUsed:
-      name = is_reminder ? "IOS.CredentialProviderExtension.Promo."
-                           "OnSuccessfulLoginWithAutofilledPassword.IsReminder"
-                         : "IOS.CredentialProviderExtension.Promo."
-                           "OnSuccessfulLoginWithAutofilledPassword";
+      name = is_reminder
+                 ? kIOSCredentialProviderPromoOnAutofillUsedIsReminderHistogram
+                 : kIOSCredentialProviderPromoOnAutofillUsedHistogram;
       break;
     case IOSCredentialProviderPromoSource::kUnknown:
       NOTREACHED();
diff --git a/ios/chrome/browser/ui/menu/BUILD.gn b/ios/chrome/browser/ui/menu/BUILD.gn
index 0503e158..e483210 100644
--- a/ios/chrome/browser/ui/menu/BUILD.gn
+++ b/ios/chrome/browser/ui/menu/BUILD.gn
@@ -59,6 +59,7 @@
     "resources:move_folder",
     "//base",
     "//base/test:test_support",
+    "//components/policy/core/common:common_constants",
     "//components/sync_preferences:test_support",
     "//ios/chrome/app/strings",
     "//ios/chrome/browser/browser_state:test_support",
diff --git a/ios/chrome/browser/ui/menu/browser_action_factory_unittest.mm b/ios/chrome/browser/ui/menu/browser_action_factory_unittest.mm
index c7105852..d6cc0cc 100644
--- a/ios/chrome/browser/ui/menu/browser_action_factory_unittest.mm
+++ b/ios/chrome/browser/ui/menu/browser_action_factory_unittest.mm
@@ -7,6 +7,7 @@
 #import "base/test/metrics/histogram_tester.h"
 #import "base/test/scoped_feature_list.h"
 #import "base/test/task_environment.h"
+#import "components/policy/core/common/policy_pref_names.h"
 #import "components/sync_preferences/testing_pref_service_syncable.h"
 #import "ios/chrome/browser/browser_state/test_chrome_browser_state.h"
 #import "ios/chrome/browser/main/test_browser.h"
@@ -248,7 +249,7 @@
   EXPECT_EQ(0U, action.attributes);
 
   chrome_browser_state_->GetTestingPrefService()->SetManagedPref(
-      prefs::kIncognitoModeAvailability,
+      policy::policy_prefs::kIncognitoModeAvailability,
       std::make_unique<base::Value>(
           static_cast<int>(IncognitoModePrefs::kForced)));
 
@@ -274,7 +275,7 @@
   EXPECT_EQ(0U, action.attributes);
 
   chrome_browser_state_->GetTestingPrefService()->SetManagedPref(
-      prefs::kIncognitoModeAvailability,
+      policy::policy_prefs::kIncognitoModeAvailability,
       std::make_unique<base::Value>(
           static_cast<int>(IncognitoModePrefs::kDisabled)));
 
@@ -352,7 +353,7 @@
   EXPECT_EQ(0U, action.attributes);
 
   chrome_browser_state_->GetTestingPrefService()->SetManagedPref(
-      prefs::kIncognitoModeAvailability,
+      policy::policy_prefs::kIncognitoModeAvailability,
       std::make_unique<base::Value>(
           static_cast<int>(IncognitoModePrefs::kForced)));
 
@@ -378,7 +379,7 @@
   EXPECT_EQ(0U, action.attributes);
 
   chrome_browser_state_->GetTestingPrefService()->SetManagedPref(
-      prefs::kIncognitoModeAvailability,
+      policy::policy_prefs::kIncognitoModeAvailability,
       std::make_unique<base::Value>(
           static_cast<int>(IncognitoModePrefs::kDisabled)));
 
diff --git a/ios/chrome/browser/ui/ntp/metrics/feed_metrics_recorder.mm b/ios/chrome/browser/ui/ntp/metrics/feed_metrics_recorder.mm
index 6b66800..bfac4b4 100644
--- a/ios/chrome/browser/ui/ntp/metrics/feed_metrics_recorder.mm
+++ b/ios/chrome/browser/ui/ntp/metrics/feed_metrics_recorder.mm
@@ -81,8 +81,8 @@
 // `ContentSuggestions.Feed.TimeSpentInFeed`
 @property(nonatomic, assign) base::TimeDelta timeSpentInFeed;
 
-// Timer to signal end of session.
-@property(nonatomic, strong) NSTimer* sessionEndTimer;
+// Timer to refresh the feed.
+@property(nonatomic, strong) NSTimer* refreshTimer;
 
 // YES if the NTP is visible.
 @property(nonatomic, assign) BOOL isNTPVisible;
@@ -106,8 +106,8 @@
 #pragma mark - Public
 
 - (void)dealloc {
-  [self.sessionEndTimer invalidate];
-  self.sessionEndTimer = nil;
+  [self.refreshTimer invalidate];
+  self.refreshTimer = nil;
 }
 
 + (void)recordFeedRefreshTrigger:(FeedRefreshTrigger)trigger {
@@ -166,7 +166,7 @@
   // Invalidate the timer when the user returns to the feed since the feed
   // should not be refreshed when the user is viewing it.
   if (visible) {
-    [self.sessionEndTimer invalidate];
+    [self.refreshTimer invalidate];
     [self recordDiscoverFeedUserActionHistogram:FeedUserActionType::
                                                     kOpenedFeedSurface
                                   asInteraction:NO];
@@ -909,7 +909,7 @@
   // engagement criteria. For example, setting `engagedSimpleReportedDiscover`
   // must happen before this call.
   if (IsFeedSessionCloseForegroundRefreshEnabled()) {
-    [self setOrExtendSessionEndTimer];
+    [self setOrExtendRefreshTimer];
   }
 }
 
@@ -1278,22 +1278,22 @@
   }
 }
 
-// Sets or extends the session end timer.
-- (void)setOrExtendSessionEndTimer {
-  [self.sessionEndTimer invalidate];
+// Sets or extends the refresh timer.
+- (void)setOrExtendRefreshTimer {
+  [self.refreshTimer invalidate];
   __weak FeedMetricsRecorder* weakSelf = self;
-  self.sessionEndTimer = [NSTimer
-      scheduledTimerWithTimeInterval:GetFeedSessionEndTimerTimeoutInSeconds()
+  self.refreshTimer = [NSTimer
+      scheduledTimerWithTimeInterval:GetFeedRefreshTimerTimeoutInSeconds()
                               target:weakSelf
-                            selector:@selector(sessionEndTimerEnded)
+                            selector:@selector(refreshTimerEnded)
                             userInfo:nil
                              repeats:NO];
 }
 
-// Signals that the session end timer ended.
-- (void)sessionEndTimerEnded {
-  [self.sessionEndTimer invalidate];
-  self.sessionEndTimer = nil;
+// Signals that the refresh timer ended.
+- (void)refreshTimerEnded {
+  [self.refreshTimer invalidate];
+  self.refreshTimer = nil;
   if (![self isNTPAndFeedVisible]) {
     // The feed refresher checks feed engagement criteria.
     self.feedRefresher->RefreshFeed(
diff --git a/ios/chrome/browser/ui/overlays/infobar_banner/autofill_address_profile/save_address_profile_infobar_banner_overlay_mediator.mm b/ios/chrome/browser/ui/overlays/infobar_banner/autofill_address_profile/save_address_profile_infobar_banner_overlay_mediator.mm
index df44197..f0abffd3 100644
--- a/ios/chrome/browser/ui/overlays/infobar_banner/autofill_address_profile/save_address_profile_infobar_banner_overlay_mediator.mm
+++ b/ios/chrome/browser/ui/overlays/infobar_banner/autofill_address_profile/save_address_profile_infobar_banner_overlay_mediator.mm
@@ -65,7 +65,10 @@
       setTitleText:base::SysUTF16ToNSString(self.config->message_text())];
   [self.consumer
       setSubtitleText:base::SysUTF16ToNSString(self.config->description())];
-  [self.consumer setRestrictSubtitleTextToSingleLine:YES];
+
+  if (!self.config->is_migration_to_account()) {
+    [self.consumer setRestrictSubtitleTextToSingleLine:YES];
+  }
 
   [self.consumer
       setIconImage:CustomSymbolWithPointSize(kLocationFillSymbol,
diff --git a/ios/chrome/browser/ui/overlays/infobar_banner/autofill_address_profile/save_address_profile_infobar_banner_overlay_mediator_unittest.mm b/ios/chrome/browser/ui/overlays/infobar_banner/autofill_address_profile/save_address_profile_infobar_banner_overlay_mediator_unittest.mm
index c19a8cf..4e404d1 100644
--- a/ios/chrome/browser/ui/overlays/infobar_banner/autofill_address_profile/save_address_profile_infobar_banner_overlay_mediator_unittest.mm
+++ b/ios/chrome/browser/ui/overlays/infobar_banner/autofill_address_profile/save_address_profile_infobar_banner_overlay_mediator_unittest.mm
@@ -50,7 +50,10 @@
   std::unique_ptr<autofill::AutofillSaveUpdateAddressProfileDelegateIOS>
       passed_delegate = std::make_unique<
           autofill::AutofillSaveUpdateAddressProfileDelegateIOS>(
-          profile, /*original_profile=*/nullptr, /*locale=*/"en-US",
+          profile, /*original_profile=*/nullptr,
+          /*syncing_user_email=*/absl::nullopt,
+          /*locale=*/"en-US",
+          autofill::AutofillClient::SaveAddressProfilePromptOptions{},
           base::DoNothing());
   autofill::AutofillSaveUpdateAddressProfileDelegateIOS* delegate =
       passed_delegate.get();
@@ -88,7 +91,10 @@
   std::unique_ptr<autofill::AutofillSaveUpdateAddressProfileDelegateIOS>
       passed_delegate = std::make_unique<
           autofill::AutofillSaveUpdateAddressProfileDelegateIOS>(
-          profile, /*original_profile=*/nullptr, /*locale=*/"en-US",
+          profile, /*original_profile=*/nullptr,
+          /*syncing_user_email=*/absl::nullopt,
+          /*locale=*/"en-US",
+          autofill::AutofillClient::SaveAddressProfilePromptOptions{},
           base::DoNothing());
   InfoBarIOS infobar(InfobarType::kInfobarTypeSaveAutofillAddressProfile,
                      std::move(passed_delegate));
diff --git a/ios/chrome/browser/ui/overlays/infobar_modal/autofill_address_profile/save_address_profile_infobar_modal_overlay_mediator_unittest.mm b/ios/chrome/browser/ui/overlays/infobar_modal/autofill_address_profile/save_address_profile_infobar_modal_overlay_mediator_unittest.mm
index 142b0ad..a202681d 100644
--- a/ios/chrome/browser/ui/overlays/infobar_modal/autofill_address_profile/save_address_profile_infobar_modal_overlay_mediator_unittest.mm
+++ b/ios/chrome/browser/ui/overlays/infobar_modal/autofill_address_profile/save_address_profile_infobar_modal_overlay_mediator_unittest.mm
@@ -47,7 +47,10 @@
     std::unique_ptr<autofill::AutofillSaveUpdateAddressProfileDelegateIOS>
         delegate = std::make_unique<
             autofill::AutofillSaveUpdateAddressProfileDelegateIOS>(
-            profile, /*original_profile=*/nullptr, /*locale=*/"en-US",
+            profile, /*original_profile=*/nullptr,
+            /*syncing_user_email=*/absl::nullopt,
+            /*locale=*/"en-US",
+            autofill::AutofillClient::SaveAddressProfilePromptOptions{},
             base::DoNothing());
     delegate_ = delegate.get();
     infobar_ = std::make_unique<InfoBarIOS>(
diff --git a/ios/chrome/browser/ui/settings/password/password_settings/password_settings_consumer.h b/ios/chrome/browser/ui/settings/password/password_settings/password_settings_consumer.h
index a0610c1c..4e1c7b8 100644
--- a/ios/chrome/browser/ui/settings/password/password_settings/password_settings_consumer.h
+++ b/ios/chrome/browser/ui/settings/password/password_settings/password_settings_consumer.h
@@ -47,6 +47,10 @@
 // Indicates the state of the account storage switch.
 - (void)setAccountStorageState:(PasswordSettingsAccountStorageState)state;
 
+// Whether the account storage switch (if displayed) should show an icon that
+// highlights it as a new feature. This doesn't mean the switch itself is shown.
+- (void)setShowAccountStorageNewFeatureIcon:(BOOL)show;
+
 // Indicates the signed-in account.
 - (void)setSignedInAccount:(NSString*)account;
 
diff --git a/ios/chrome/browser/ui/settings/password/password_settings/password_settings_delegate.h b/ios/chrome/browser/ui/settings/password/password_settings/password_settings_delegate.h
index c1d82d8..3255a95a 100644
--- a/ios/chrome/browser/ui/settings/password/password_settings/password_settings_delegate.h
+++ b/ios/chrome/browser/ui/settings/password/password_settings/password_settings_delegate.h
@@ -15,6 +15,9 @@
 // Indicates whether or not the account storage switch is set to enabled.
 - (void)accountStorageSwitchDidChange:(BOOL)enabled;
 
+// Indicates the new feature icon was shown for the account storage switch.
+- (void)accountStorageNewFeatureIconDidShow;
+
 @end
 
 #endif  // IOS_CHROME_BROWSER_UI_SETTINGS_PASSWORD_PASSWORD_SETTINGS_PASSWORD_SETTINGS_DELEGATE_H_
diff --git a/ios/chrome/browser/ui/settings/password/password_settings/password_settings_mediator.mm b/ios/chrome/browser/ui/settings/password/password_settings/password_settings_mediator.mm
index 6a7426b..a126cce 100644
--- a/ios/chrome/browser/ui/settings/password/password_settings/password_settings_mediator.mm
+++ b/ios/chrome/browser/ui/settings/password/password_settings/password_settings_mediator.mm
@@ -65,6 +65,10 @@
 
   // Sync observer.
   std::unique_ptr<SyncObserverBridge> _syncObserver;
+
+  // Flag to avoid incrementing the number of impressions of the icon more than
+  // once through the lifetime of the UI.
+  BOOL _accountStorageNewFeatureIconImpressionsIncremented;
 }
 
 // Helper object which maintains state about the "Export Passwords..." flow, and
@@ -137,6 +141,15 @@
 
   [self.consumer setAccountStorageState:[self computeAccountStorageState]];
 
+  // < and not <= below, because the next impression must be counted.
+  const int impressionCount = _prefService->GetInteger(
+      password_manager::prefs::kAccountStorageNewFeatureIconImpressions);
+  const int maxImpressionCount =
+      password_manager::features::kMaxAccountStorageNewFeatureIconImpressions
+          .Get();
+  [self.consumer
+      setShowAccountStorageNewFeatureIcon:impressionCount < maxImpressionCount];
+
   // TODO(crbug.com/1082827): In addition to setting this value here, we should
   // observe for changes (i.e., if policy changes while the screen is open) and
   // push that to the consumer.
@@ -227,6 +240,17 @@
                                                     types);
 }
 
+- (void)accountStorageNewFeatureIconDidShow {
+  if (!_accountStorageNewFeatureIconImpressionsIncremented) {
+    _accountStorageNewFeatureIconImpressionsIncremented = YES;
+    _prefService->SetInteger(
+        password_manager::prefs::kAccountStorageNewFeatureIconImpressions,
+        1 + _prefService->GetInteger(
+                password_manager::prefs::
+                    kAccountStorageNewFeatureIconImpressions));
+  }
+}
+
 #pragma mark - SavedPasswordsPresenterObserver
 
 - (void)savedPasswordsDidChange {
diff --git a/ios/chrome/browser/ui/settings/password/password_settings/password_settings_view_controller.mm b/ios/chrome/browser/ui/settings/password/password_settings/password_settings_view_controller.mm
index d4c0dbb6..1c99d15 100644
--- a/ios/chrome/browser/ui/settings/password/password_settings/password_settings_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/password/password_settings/password_settings_view_controller.mm
@@ -98,6 +98,10 @@
 @property(nonatomic, assign)
     PasswordSettingsAccountStorageState accountStorageState;
 
+// Indicates whether the account storage switch should contain an icon
+// indicating a new feature. This doesn't mean the switch itself is shown.
+@property(nonatomic, assign) BOOL showAccountStorageNewFeatureIcon;
+
 // Indicates the signed in account.
 @property(nonatomic, copy) NSString* signedInAccount;
 
@@ -242,8 +246,12 @@
                                 action:@selector(accountStorageSwitchChanged:)
                       forControlEvents:UIControlEventValueChanged];
 
+      if (!_showAccountStorageNewFeatureIcon) {
+        break;
+      }
+
       // Add new feature icon, vertically centered with the text.
-      // TODO(crbug.com/1377384): Limit impressions of the icon.
+      [self.delegate accountStorageNewFeatureIconDidShow];
       NSTextAttachment* iconAttachment = [[NSTextAttachment alloc] init];
       iconAttachment.image = [PasswordSettingsViewController newFeatureIcon];
       CGSize iconSize = iconAttachment.image.size;
@@ -541,6 +549,10 @@
   }
 }
 
+- (void)setShowAccountStorageNewFeatureIcon:(BOOL)show {
+  _showAccountStorageNewFeatureIcon = show;
+}
+
 - (void)setPasswordsInOtherAppsEnabled:(BOOL)enabled {
   if (_passwordsInOtherAppsEnabled.has_value() &&
       _passwordsInOtherAppsEnabled.value() == enabled) {
diff --git a/ios/chrome/browser/ui/settings/privacy/BUILD.gn b/ios/chrome/browser/ui/settings/privacy/BUILD.gn
index cff714e3..51370a83 100644
--- a/ios/chrome/browser/ui/settings/privacy/BUILD.gn
+++ b/ios/chrome/browser/ui/settings/privacy/BUILD.gn
@@ -121,6 +121,7 @@
   deps = [
     "//base/test:test_support",
     "//components/handoff",
+    "//components/policy/core/common:common_constants",
     "//components/prefs",
     "//components/prefs:test_support",
     "//components/prefs/ios",
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 34a1b1c..062b731 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
@@ -13,6 +13,7 @@
 #import "base/test/scoped_feature_list.h"
 #import "components/content_settings/core/common/features.h"
 #import "components/handoff/pref_names_ios.h"
+#import "components/policy/core/common/policy_pref_names.h"
 #import "components/prefs/pref_service.h"
 #import "components/safe_browsing/core/common/features.h"
 #import "components/safe_browsing/core/common/safe_browsing_prefs.h"
@@ -86,7 +87,7 @@
 
     // Set Incognito Mode availability depending on test config.
     chrome_browser_state_->GetTestingPrefService()->SetManagedPref(
-        prefs::kIncognitoModeAvailability,
+        policy::policy_prefs::kIncognitoModeAvailability,
         std::make_unique<base::Value>(
             static_cast<int>(GetParam().incognitoModeAvailability)));
   }
diff --git a/ios/chrome/browser/ui/settings/sync/utils/sync_error_infobar_delegate.h b/ios/chrome/browser/ui/settings/sync/utils/sync_error_infobar_delegate.h
index 4598d3ab..d7baec28 100644
--- a/ios/chrome/browser/ui/settings/sync/utils/sync_error_infobar_delegate.h
+++ b/ios/chrome/browser/ui/settings/sync/utils/sync_error_infobar_delegate.h
@@ -48,6 +48,7 @@
 
   // ConfirmInfoBarDelegate implementation.
   std::u16string GetMessageText() const override;
+  std::u16string GetTitleText() const override;
   int GetButtons() const override;
   std::u16string GetButtonLabel(InfoBarButton button) const override;
   bool UseIconBackgroundTint() const override;
@@ -64,6 +65,7 @@
   gfx::Image icon_;
   ChromeBrowserState* browser_state_;
   syncer::SyncService::UserActionableError error_state_;
+  std::u16string title_;
   std::u16string message_;
   std::u16string button_text_;
   id<SyncPresenter> presenter_;
diff --git a/ios/chrome/browser/ui/settings/sync/utils/sync_error_infobar_delegate.mm b/ios/chrome/browser/ui/settings/sync/utils/sync_error_infobar_delegate.mm
index 4e5c732..0cfec128 100644
--- a/ios/chrome/browser/ui/settings/sync/utils/sync_error_infobar_delegate.mm
+++ b/ios/chrome/browser/ui/settings/sync/utils/sync_error_infobar_delegate.mm
@@ -79,6 +79,7 @@
   // Set all of the UI based on the sync state at the same time to ensure
   // they all correspond to the same sync error.
   error_state_ = sync_service->GetUserActionableError();
+  title_ = GetSyncErrorInfoBarTitleForBrowserState(browser_state_);
   message_ = base::SysNSStringToUTF16(
       GetSyncErrorMessageForBrowserState(browser_state_));
   button_text_ = base::SysNSStringToUTF16(
@@ -103,6 +104,10 @@
   return message_;
 }
 
+std::u16string SyncErrorInfoBarDelegate::GetTitleText() const {
+  return title_;
+}
+
 int SyncErrorInfoBarDelegate::GetButtons() const {
   return button_text_.empty() ? BUTTON_NONE : BUTTON_OK;
 }
diff --git a/ios/chrome/browser/ui/settings/sync/utils/sync_util.h b/ios/chrome/browser/ui/settings/sync/utils/sync_util.h
index 210b89a6..650a871 100644
--- a/ios/chrome/browser/ui/settings/sync/utils/sync_util.h
+++ b/ios/chrome/browser/ui/settings/sync/utils/sync_util.h
@@ -23,6 +23,10 @@
 NSString* GetSyncErrorDescriptionForSyncService(
     syncer::SyncService* syncService);
 
+// Gets the title of the Sync error info bar.
+std::u16string GetSyncErrorInfoBarTitleForBrowserState(
+    ChromeBrowserState* browserState);
+
 // Gets the string message associated with the sync error state of
 // `browserState`. The returned error message does not contain any links.
 // Returns nil if there is no sync error.
diff --git a/ios/chrome/browser/ui/settings/sync/utils/sync_util.mm b/ios/chrome/browser/ui/settings/sync/utils/sync_util.mm
index 8f3dfc7..2d3adac 100644
--- a/ios/chrome/browser/ui/settings/sync/utils/sync_util.mm
+++ b/ios/chrome/browser/ui/settings/sync/utils/sync_util.mm
@@ -6,6 +6,7 @@
 
 #import "base/feature_list.h"
 #import "base/metrics/histogram_macros.h"
+#import "base/notreached.h"
 #import "components/infobars/core/infobar_manager.h"
 #import "components/signin/public/identity_manager/identity_manager.h"
 #import "components/strings/grit/components_strings.h"
@@ -22,6 +23,7 @@
 #import "ios/chrome/browser/sync/sync_service_factory.h"
 #import "ios/chrome/browser/ui/settings/settings_root_view_controlling.h"
 #import "ios/chrome/browser/ui/settings/sync/utils/account_error_ui_info.h"
+#import "ios/chrome/browser/ui/settings/sync/utils/identity_error_util.h"
 #import "ios/chrome/browser/ui/settings/sync/utils/sync_error_infobar_delegate.h"
 #import "ios/chrome/grit/ios_chromium_strings.h"
 #import "ios/chrome/grit/ios_strings.h"
@@ -51,6 +53,95 @@
   kMaxValue = SYNC_TRUSTED_VAULT_RECOVERABILITY_DEGRADED,
 };
 
+// Returns true if the identity error info bar should be used instead of the
+// Sync error info bar. Returns false for the case where
+// SyncService::IsSyncFeatureEnabled() returns true, because
+// GetAccountErrorUIInfo() is guaranteed to return nil.
+bool UseIdentityErrorInfobar(syncer::SyncService* sync_service) {
+  DCHECK(sync_service);
+
+  // TODO(crbug.com/1426861): Consider changing the way we detect that the Sync
+  // feature is enabled in GetAccountErrorUIInfo().
+  return GetAccountErrorUIInfo(sync_service) != nil;
+}
+
+// Gets the the title of the identity error info bar for the given `error`.
+std::u16string GetIdentityErrorInfoBarTitle(
+    syncer::SyncService::UserActionableError error) {
+  switch (error) {
+    case syncer::SyncService::UserActionableError::kNeedsPassphrase:
+      return l10n_util::GetStringUTF16(
+          IDS_IOS_IDENTITY_ERROR_INFOBAR_ENTER_PASSPHRASE_TITLE);
+    case syncer::SyncService::UserActionableError::
+        kNeedsTrustedVaultKeyForPasswords:
+    case syncer::SyncService::UserActionableError::
+        kNeedsTrustedVaultKeyForEverything:
+    case syncer::SyncService::UserActionableError::
+        kTrustedVaultRecoverabilityDegradedForPasswords:
+    case syncer::SyncService::UserActionableError::
+        kTrustedVaultRecoverabilityDegradedForEverything:
+      return l10n_util::GetStringUTF16(
+          IDS_IOS_IDENTITY_ERROR_INFOBAR_VERIFY_ITS_YOU_TITLE);
+    case syncer::SyncService::UserActionableError::kNone:
+    case syncer::SyncService::UserActionableError::kSignInNeedsUpdate:
+    case syncer::SyncService::UserActionableError::kGenericUnrecoverableError:
+      NOTREACHED_NORETURN();
+  }
+}
+
+// Gets the message of the identity error info bar.
+NSString* GetIdentityErrorInfoBarMessage(
+    syncer::SyncService::UserActionableError error) {
+  switch (error) {
+    case syncer::SyncService::UserActionableError::kNeedsPassphrase:
+      return l10n_util::GetNSString(
+          IDS_IOS_IDENTITY_ERROR_INFOBAR_KEEP_USING_YOUR_CHROME_DATA_MESSAGE);
+    case syncer::SyncService::UserActionableError::
+        kNeedsTrustedVaultKeyForPasswords:
+      return l10n_util::GetNSString(
+          IDS_IOS_IDENTITY_ERROR_INFOBAR_KEEP_USING_PASSWORDS_MESSAGE);
+    case syncer::SyncService::UserActionableError::
+        kNeedsTrustedVaultKeyForEverything:
+      return l10n_util::GetNSString(
+          IDS_IOS_IDENTITY_ERROR_INFOBAR_KEEP_USING_YOUR_CHROME_DATA_MESSAGE);
+    case syncer::SyncService::UserActionableError::
+        kTrustedVaultRecoverabilityDegradedForPasswords:
+      return l10n_util::GetNSString(
+          IDS_IOS_IDENTITY_ERROR_INFOBAR_MAKE_SURE_YOU_CAN_ALWAYS_USE_PASSWORDS_MESSAGE);
+    case syncer::SyncService::UserActionableError::
+        kTrustedVaultRecoverabilityDegradedForEverything:
+      return l10n_util::GetNSString(
+          IDS_IOS_IDENTITY_ERROR_INFOBAR_MAKE_SURE_YOU_CAN_ALWAYS_USE_CHROME_DATA_MESSAGE);
+    case syncer::SyncService::UserActionableError::kNone:
+    case syncer::SyncService::UserActionableError::kSignInNeedsUpdate:
+    case syncer::SyncService::UserActionableError::kGenericUnrecoverableError:
+      NOTREACHED_NORETURN();
+  }
+}
+
+NSString* GetIdentityErrorInfoBarButtonLabel(
+    syncer::SyncService::UserActionableError error) {
+  switch (error) {
+    case syncer::SyncService::UserActionableError::kNeedsPassphrase:
+      return l10n_util::GetNSString(
+          IDS_IOS_IDENTITY_ERROR_INFOBAR_ENTER_BUTTON_LABEL);
+    case syncer::SyncService::UserActionableError::
+        kNeedsTrustedVaultKeyForPasswords:
+    case syncer::SyncService::UserActionableError::
+        kNeedsTrustedVaultKeyForEverything:
+    case syncer::SyncService::UserActionableError::
+        kTrustedVaultRecoverabilityDegradedForPasswords:
+    case syncer::SyncService::UserActionableError::
+        kTrustedVaultRecoverabilityDegradedForEverything:
+      return l10n_util::GetNSString(
+          IDS_IOS_IDENTITY_ERROR_INFOBAR_VERIFY_BUTTON_LABEL);
+    case syncer::SyncService::UserActionableError::kNone:
+    case syncer::SyncService::UserActionableError::kSignInNeedsUpdate:
+    case syncer::SyncService::UserActionableError::kGenericUnrecoverableError:
+      NOTREACHED_NORETURN();
+  }
+}
+
 }  // namespace
 
 NSString* GetSyncErrorDescriptionForSyncService(
@@ -86,11 +177,37 @@
   }
 }
 
+std::u16string GetSyncErrorInfoBarTitleForBrowserState(
+    ChromeBrowserState* browser_state) {
+  DCHECK(browser_state);
+
+  syncer::SyncService* sync_service =
+      SyncServiceFactory::GetForBrowserState(browser_state);
+  DCHECK(sync_service);
+
+  if (UseIdentityErrorInfobar(sync_service)) {
+    DCHECK(!sync_service->IsSyncFeatureEnabled());
+    return GetIdentityErrorInfoBarTitle(sync_service->GetUserActionableError());
+  } else {
+    // There is no title in Sync error info bar.
+    return std::u16string();
+  }
+}
+
 NSString* GetSyncErrorMessageForBrowserState(ChromeBrowserState* browserState) {
   syncer::SyncService* syncService =
       SyncServiceFactory::GetForBrowserState(browserState);
   DCHECK(syncService);
-  switch (syncService->GetUserActionableError()) {
+
+  const syncer::SyncService::UserActionableError error =
+      syncService->GetUserActionableError();
+
+  if (UseIdentityErrorInfobar(syncService)) {
+    DCHECK(!syncService->IsSyncFeatureEnabled());
+    return GetIdentityErrorInfoBarMessage(error);
+  }
+
+  switch (error) {
     case syncer::SyncService::UserActionableError::kNone:
       return nil;
     case syncer::SyncService::UserActionableError::kSignInNeedsUpdate:
@@ -113,10 +230,21 @@
 
 NSString* GetSyncErrorButtonTitleForBrowserState(
     ChromeBrowserState* browserState) {
+  DCHECK(browserState);
+
   syncer::SyncService* syncService =
       SyncServiceFactory::GetForBrowserState(browserState);
   DCHECK(syncService);
-  switch (syncService->GetUserActionableError()) {
+
+  const syncer::SyncService::UserActionableError error =
+      syncService->GetUserActionableError();
+
+  if (UseIdentityErrorInfobar(syncService)) {
+    DCHECK(!syncService->IsSyncFeatureEnabled());
+    return GetIdentityErrorInfoBarButtonLabel(error);
+  }
+
+  switch (error) {
     case syncer::SyncService::UserActionableError::kSignInNeedsUpdate:
       return l10n_util::GetNSString(IDS_IOS_SYNC_UPDATE_CREDENTIALS_BUTTON);
     case syncer::SyncService::UserActionableError::kNeedsPassphrase:
@@ -169,18 +297,24 @@
     return false;
   }
 
-  // Avoid showing the sync error inforbar when sync changes are still pending.
-  // This is particularely requires during first run when the advanced sign-in
-  // settings are being presented on the NTP before sync changes being
-  // committed.
-  if (syncService->IsSetupInProgress()) {
-    return false;
-  }
+  if (!UseIdentityErrorInfobar(syncService)) {
+    // If the identity error info bar isn't used, fallback to the Sync error
+    // info bar.
 
-  signin::IdentityManager* identityManager =
-      IdentityManagerFactory::GetForBrowserState(browser_state);
-  if (!identityManager->HasPrimaryAccount(signin::ConsentLevel::kSync))
-    return false;
+    // Avoid showing the sync error info bar when sync changes are still
+    // pending. This is particularely requires during first run when the
+    // advanced sign-in settings are being presented on the NTP before sync
+    // changes being committed.
+    if (syncService->IsSetupInProgress()) {
+      return false;
+    }
+
+    signin::IdentityManager* identityManager =
+        IdentityManagerFactory::GetForBrowserState(browser_state);
+    if (!identityManager->HasPrimaryAccount(signin::ConsentLevel::kSync)) {
+      return false;
+    }
+  }
 
   // Logs when an infobar is shown to user. See crbug/265352.
   InfobarSyncError loggedErrorState;
diff --git a/media/DEPS b/media/DEPS
index b4998ce..88da8ba 100644
--- a/media/DEPS
+++ b/media/DEPS
@@ -4,6 +4,7 @@
   "+cc/paint",
   "+components/crash/core/common/crash_key.h",
   "+components/system_media_controls/linux/buildflags",
+  "+components/viz/common/resources",
   "+crypto",
   "+device/udev_linux",
   "+gpu",
diff --git a/media/base/media_switches.cc b/media/base/media_switches.cc
index 2f0c13c2..6173b83 100644
--- a/media/base/media_switches.cc
+++ b/media/base/media_switches.cc
@@ -508,6 +508,12 @@
              "MemoryPressureBasedSourceBufferGC",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
+// Enables creating single shared image and mailbox for multi-planar formats for
+// hardware video decoders.
+BASE_FEATURE(kUseMultiPlaneFormatForHardwareVideo,
+             "UseMultiPlaneFormatForHardwareVideo",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 // Enables binding software video NV12/P010 GMBs as separate shared images.
 BASE_FEATURE(kMultiPlaneSoftwareVideoSharedImages,
              "MultiPlaneSoftwareVideoSharedImages",
diff --git a/media/base/media_switches.h b/media/base/media_switches.h
index 51a5916..b482800 100644
--- a/media/base/media_switches.h
+++ b/media/base/media_switches.h
@@ -243,6 +243,7 @@
 MEDIA_EXPORT BASE_DECLARE_FEATURE(kMediaOptimizer);
 MEDIA_EXPORT BASE_DECLARE_FEATURE(kMediaPowerExperiment);
 MEDIA_EXPORT BASE_DECLARE_FEATURE(kMemoryPressureBasedSourceBufferGC);
+MEDIA_EXPORT BASE_DECLARE_FEATURE(kUseMultiPlaneFormatForHardwareVideo);
 MEDIA_EXPORT BASE_DECLARE_FEATURE(kMultiPlaneSoftwareVideoSharedImages);
 MEDIA_EXPORT BASE_DECLARE_FEATURE(kMultiPlaneVideoCaptureSharedImages);
 MEDIA_EXPORT BASE_DECLARE_FEATURE(kOpenscreenCastStreamingSession);
diff --git a/media/gpu/ipc/service/picture_buffer_manager.cc b/media/gpu/ipc/service/picture_buffer_manager.cc
index 5e96015e..0d76f42 100644
--- a/media/gpu/ipc/service/picture_buffer_manager.cc
+++ b/media/gpu/ipc/service/picture_buffer_manager.cc
@@ -320,6 +320,8 @@
 
     frame->set_color_space(picture.color_space());
 
+    frame->set_shared_image_format_type(picture.shared_image_format_type());
+
     frame->metadata().allow_overlay = picture.allow_overlay();
     frame->metadata().read_lock_fences_enabled =
         picture.read_lock_fences_enabled();
diff --git a/media/gpu/mac/BUILD.gn b/media/gpu/mac/BUILD.gn
index 33b29e5..0b6717f0 100644
--- a/media/gpu/mac/BUILD.gn
+++ b/media/gpu/mac/BUILD.gn
@@ -40,6 +40,7 @@
   deps = [
     "//base",
     "//components/crash/core/common:crash_key",
+    "//components/viz/common:resource_format",
     "//gpu/command_buffer/service:gles2",
     "//gpu/ipc/service",
     "//media",
diff --git a/media/gpu/mac/vt_video_decode_accelerator_mac.cc b/media/gpu/mac/vt_video_decode_accelerator_mac.cc
index 715d5dc..16a8fce9 100644
--- a/media/gpu/mac/vt_video_decode_accelerator_mac.cc
+++ b/media/gpu/mac/vt_video_decode_accelerator_mac.cc
@@ -40,11 +40,11 @@
 #include "base/trace_event/process_memory_dump.h"
 #include "base/version.h"
 #include "components/crash/core/common/crash_key.h"
-#include "components/viz/common/resources/resource_format_utils.h"
 #include "gpu/command_buffer/common/gpu_memory_buffer_support.h"
 #include "gpu/command_buffer/common/mailbox.h"
 #include "gpu/command_buffer/common/shared_image_usage.h"
 #include "gpu/command_buffer/service/shared_image/shared_image_factory.h"
+#include "gpu/config/gpu_finch_features.h"
 #include "gpu/ipc/service/shared_image_stub.h"
 #include "media/base/limits.h"
 #include "media/base/mac/color_space_util_mac.h"
@@ -521,6 +521,29 @@
   vda->Output(source_frame_refcon, status, image_buffer);
 }
 
+gfx::BufferFormat ToBufferFormat(viz::SharedImageFormat format) {
+  DCHECK(format.is_multi_plane());
+  if (format == viz::MultiPlaneFormat::kYVU_420) {
+    return gfx::BufferFormat::YVU_420;
+  }
+  if (format == viz::MultiPlaneFormat::kYUV_420_BIPLANAR) {
+    return gfx::BufferFormat::YUV_420_BIPLANAR;
+  }
+  if (format == viz::MultiPlaneFormat::kYUVA_420_TRIPLANAR) {
+    return gfx::BufferFormat::YUVA_420_TRIPLANAR;
+  }
+  if (format == viz::MultiPlaneFormat::kP010) {
+    return gfx::BufferFormat::P010;
+  }
+  NOTREACHED();
+  return gfx::BufferFormat::RGBA_8888;
+}
+
+bool MultiPlaneFormatForHardwareVideoEnabled() {
+  return base::FeatureList::IsEnabled(features::kPassthroughYuvRgbConversion) &&
+         base::FeatureList::IsEnabled(kUseMultiPlaneFormatForHardwareVideo);
+}
+
 }  // namespace
 
 // Detects coded size and color space changes. Also indicates when a frame won't
@@ -2173,15 +2196,15 @@
     picture_size_ = frame.image_size;
 
     if (has_alpha_) {
-      buffer_format_ = gfx::BufferFormat::YUVA_420_TRIPLANAR;
+      si_format_ = viz::MultiPlaneFormat::kYUVA_420_TRIPLANAR;
       picture_format_ = PIXEL_FORMAT_NV12A;
     } else if (config_.profile == VP9PROFILE_PROFILE2 ||
                config_.profile == HEVCPROFILE_MAIN10 ||
                config_.profile == HEVCPROFILE_REXT) {
-      buffer_format_ = gfx::BufferFormat::P010;
+      si_format_ = viz::MultiPlaneFormat::kP010;
       picture_format_ = PIXEL_FORMAT_P016LE;
     } else {
-      buffer_format_ = gfx::BufferFormat::YUV_420_BIPLANAR;
+      si_format_ = viz::MultiPlaneFormat::kYUV_420_BIPLANAR;
       picture_format_ = PIXEL_FORMAT_NV12;
     }
 
@@ -2212,20 +2235,24 @@
 
   const gfx::ColorSpace color_space = GetImageBufferColorSpace(frame.image);
   std::vector<gfx::BufferPlane> planes;
-  switch (picture_format_) {
-    case PIXEL_FORMAT_NV12:
-    case PIXEL_FORMAT_P016LE:
-      planes.push_back(gfx::BufferPlane::Y);
-      planes.push_back(gfx::BufferPlane::UV);
-      break;
-    case PIXEL_FORMAT_NV12A:
-      planes.push_back(gfx::BufferPlane::Y);
-      planes.push_back(gfx::BufferPlane::UV);
-      planes.push_back(gfx::BufferPlane::A);
-      break;
-    default:
-      NOTREACHED();
-      break;
+  if (MultiPlaneFormatForHardwareVideoEnabled()) {
+    planes.push_back(gfx::BufferPlane::DEFAULT);
+  } else {
+    switch (picture_format_) {
+      case PIXEL_FORMAT_NV12:
+      case PIXEL_FORMAT_P016LE:
+        planes.push_back(gfx::BufferPlane::Y);
+        planes.push_back(gfx::BufferPlane::UV);
+        break;
+      case PIXEL_FORMAT_NV12A:
+        planes.push_back(gfx::BufferPlane::Y);
+        planes.push_back(gfx::BufferPlane::UV);
+        planes.push_back(gfx::BufferPlane::A);
+        break;
+      default:
+        NOTREACHED();
+        break;
+    }
   }
   for (size_t plane = 0; plane < planes.size(); ++plane) {
     if (picture_info->uses_shared_images) {
@@ -2254,10 +2281,17 @@
                               base::scoped_policy::RETAIN);
 
       gpu::Mailbox mailbox = gpu::Mailbox::GenerateForSharedImage();
-      bool success = shared_image_stub->CreateSharedImage(
-          mailbox, std::move(handle), buffer_format_, planes[plane], frame_size,
-          color_space, kTopLeft_GrSurfaceOrigin, kOpaque_SkAlphaType,
-          shared_image_usage);
+      bool success;
+      if (MultiPlaneFormatForHardwareVideoEnabled()) {
+        success = shared_image_stub->CreateSharedImage(
+            mailbox, std::move(handle), si_format_, frame_size, color_space,
+            kTopLeft_GrSurfaceOrigin, kOpaque_SkAlphaType, shared_image_usage);
+      } else {
+        success = shared_image_stub->CreateSharedImage(
+            mailbox, std::move(handle), ToBufferFormat(si_format_),
+            planes[plane], frame_size, color_space, kTopLeft_GrSurfaceOrigin,
+            kOpaque_SkAlphaType, shared_image_usage);
+      }
       if (!success) {
         DLOG(ERROR) << "Failed to create shared image";
         NotifyError(PLATFORM_FAILURE, SFT_PLATFORM_ERROR);
@@ -2287,7 +2321,7 @@
           CVPixelBufferGetWidthOfPlane(frame.image.get(), plane),
           CVPixelBufferGetHeightOfPlane(frame.image.get(), plane));
       gfx::BufferFormat plane_buffer_format =
-          gpu::GetPlaneBufferFormat(planes[plane], buffer_format_);
+          gpu::GetPlaneBufferFormat(planes[plane], ToBufferFormat(si_format_));
 
       scoped_refptr<GLImageIOSurface> gl_image(
           GLImageIOSurface::Create(plane_size));
@@ -2328,7 +2362,12 @@
   picture.set_read_lock_fences_enabled(true);
   if (frame.hdr_metadata)
     picture.set_hdr_metadata(frame.hdr_metadata);
+  if (MultiPlaneFormatForHardwareVideoEnabled()) {
+    picture.set_shared_image_format_type(
+        SharedImageFormatType::kSharedImageFormat);
+  }
   if (picture_info->uses_shared_images) {
+    // For multiplanar shared images, planes.size() is 1.
     for (size_t plane = 0; plane < planes.size(); ++plane) {
       picture.set_scoped_shared_image(picture_info->scoped_shared_images[plane],
                                       plane);
diff --git a/media/gpu/mac/vt_video_decode_accelerator_mac.h b/media/gpu/mac/vt_video_decode_accelerator_mac.h
index de5b59a8..0ebf7086 100644
--- a/media/gpu/mac/vt_video_decode_accelerator_mac.h
+++ b/media/gpu/mac/vt_video_decode_accelerator_mac.h
@@ -23,6 +23,7 @@
 #include "base/task/single_thread_task_runner.h"
 #include "base/threading/thread_checker.h"
 #include "base/trace_event/memory_dump_provider.h"
+#include "components/viz/common/resources/shared_image_format.h"
 #include "gpu/config/gpu_driver_bug_workarounds.h"
 #include "media/base/media_log.h"
 #include "media/gpu/gpu_video_decode_accelerator_helpers.h"
@@ -275,8 +276,8 @@
   // Format of the assigned picture buffers.
   VideoPixelFormat picture_format_ = PIXEL_FORMAT_UNKNOWN;
 
-  // Corresponding GpuMemoryBuffer format.
-  gfx::BufferFormat buffer_format_ = gfx::BufferFormat::YUV_420_BIPLANAR;
+  // Corresponding SharedImageFormat.
+  viz::SharedImageFormat si_format_ = viz::MultiPlaneFormat::kYUV_420_BIPLANAR;
 
   // Frames that have not yet been decoded, keyed by bitstream ID; maintains
   // ownership of Frame objects while they flow through VideoToolbox.
diff --git a/media/video/picture.h b/media/video/picture.h
index 4f3bf02..f97a0a1 100644
--- a/media/video/picture.h
+++ b/media/video/picture.h
@@ -186,6 +186,14 @@
 
   bool is_webgpu_compatible() { return is_webgpu_compatible_; }
 
+  SharedImageFormatType shared_image_format_type() const {
+    return shared_image_format_type_;
+  }
+
+  void set_shared_image_format_type(SharedImageFormatType type) {
+    shared_image_format_type_ = type;
+  }
+
  private:
   int32_t picture_buffer_id_;
   int32_t bitstream_buffer_id_;
@@ -198,6 +206,8 @@
   bool texture_owner_;
   bool wants_promotion_hint_;
   bool is_webgpu_compatible_;
+  SharedImageFormatType shared_image_format_type_ =
+      SharedImageFormatType::kLegacy;
   std::array<scoped_refptr<ScopedSharedImage>, VideoFrame::kMaxPlanes>
       scoped_shared_images_;
 };
diff --git a/net/http/transport_security_state_static.json b/net/http/transport_security_state_static.json
index 965f7df7..9b387c1 100644
--- a/net/http/transport_security_state_static.json
+++ b/net/http/transport_security_state_static.json
@@ -50745,7 +50745,6 @@
     { "name": "iwantexchange.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "jackassofalltrades.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "jamesclark.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "janelauhomes.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "jclynne.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "jeffrhinelander.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "jennierobinson.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
diff --git a/printing/backend/cups_printer.cc b/printing/backend/cups_printer.cc
index 1e0707c..dc2d4d2 100644
--- a/printing/backend/cups_printer.cc
+++ b/printing/backend/cups_printer.cc
@@ -30,10 +30,20 @@
     DCHECK(cups_http_);
     DCHECK(destination_);
 
-    printer_uri_ = cupsGetOption(kCUPSOptPrinterUriSupported,
-                                 destination_.get()->num_options,
-                                 destination_.get()->options);
-    resource_path_ = std::string(GURL(printer_uri_).path_piece());
+    const char* printer_uri = cupsGetOption(kCUPSOptPrinterUriSupported,
+                                            destination_.get()->num_options,
+                                            destination_.get()->options);
+
+    // crbug.com/1418564: Every printer *should* have a "printer-uri-supported"
+    // attribute, but make sure Chromium doesn't crash if one doesn't for
+    // whatever reason. The printer in question won't actually work, but
+    // that's a better outcome than crashing here.
+    // TODO(crbug.com/1418564): filter such printers out before reaching this
+    // point
+    if (printer_uri) {
+      printer_uri_ = printer_uri;
+      resource_path_ = std::string(GURL(printer_uri_).path_piece());
+    }
   }
 
   CupsPrinterImpl(const CupsPrinterImpl&) = delete;
diff --git a/services/network/public/mojom/trust_token_access_observer.mojom b/services/network/public/mojom/trust_token_access_observer.mojom
index 82e04af..b6b08a7 100644
--- a/services/network/public/mojom/trust_token_access_observer.mojom
+++ b/services/network/public/mojom/trust_token_access_observer.mojom
@@ -4,8 +4,6 @@
 
 module network.mojom;
 
-import "sandbox/policy/mojom/context.mojom";
-import "services/network/public/mojom/trust_tokens.mojom";
 import "url/mojom/origin.mojom";
 
 union TrustTokenAccessDetails {
diff --git a/services/viz/public/cpp/compositing/shared_quad_state_mojom_traits.h b/services/viz/public/cpp/compositing/shared_quad_state_mojom_traits.h
index 27a6912..435783c 100644
--- a/services/viz/public/cpp/compositing/shared_quad_state_mojom_traits.h
+++ b/services/viz/public/cpp/compositing/shared_quad_state_mojom_traits.h
@@ -5,6 +5,7 @@
 #ifndef SERVICES_VIZ_PUBLIC_CPP_COMPOSITING_SHARED_QUAD_STATE_MOJOM_TRAITS_H_
 #define SERVICES_VIZ_PUBLIC_CPP_COMPOSITING_SHARED_QUAD_STATE_MOJOM_TRAITS_H_
 
+#include "base/check_op.h"
 #include "base/memory/raw_ptr.h"
 #include "components/viz/common/quads/shared_quad_state.h"
 #include "services/viz/public/mojom/compositing/shared_quad_state.mojom-shared.h"
@@ -68,6 +69,10 @@
     return input.sqs->sorting_context_id;
   }
 
+  static uint32_t layer_id(const OptSharedQuadState& input) {
+    return input.sqs->layer_id;
+  }
+
   static bool is_fast_rounded_corner(const OptSharedQuadState& input) {
     return input.sqs->is_fast_rounded_corner;
   }
@@ -113,6 +118,10 @@
     return sqs.sorting_context_id;
   }
 
+  static uint32_t layer_id(const viz::SharedQuadState& sqs) {
+    return sqs.layer_id;
+  }
+
   static bool is_fast_rounded_corner(const viz::SharedQuadState& sqs) {
     return sqs.is_fast_rounded_corner;
   }
@@ -135,10 +144,12 @@
 
     out->are_contents_opaque = data.are_contents_opaque();
     out->opacity = data.opacity();
-    if (data.blend_mode() > static_cast<int>(SkBlendMode::kLastMode))
+    if (data.blend_mode() > static_cast<int>(SkBlendMode::kLastMode)) {
       return false;
+    }
     out->blend_mode = static_cast<SkBlendMode>(data.blend_mode());
     out->sorting_context_id = data.sorting_context_id();
+    out->layer_id = data.layer_id();
     out->is_fast_rounded_corner = data.is_fast_rounded_corner();
 
     return true;
diff --git a/services/viz/public/mojom/compositing/shared_quad_state.mojom b/services/viz/public/mojom/compositing/shared_quad_state.mojom
index b143c88..5d76b47 100644
--- a/services/viz/public/mojom/compositing/shared_quad_state.mojom
+++ b/services/viz/public/mojom/compositing/shared_quad_state.mojom
@@ -37,7 +37,7 @@
   // supported.
   uint32 blend_mode;
   int32 sorting_context_id;
+  uint32 layer_id;
 
   bool is_fast_rounded_corner;
-
 };
diff --git a/testing/buildbot/README.md b/testing/buildbot/README.md
index 28ce834b..7ff51e2 100644
--- a/testing/buildbot/README.md
+++ b/testing/buildbot/README.md
@@ -115,7 +115,7 @@
    resource request. Depending on resources, the resource owners may not approve
    of the request. In which case, see step #5.
    1. Calculate the amount of machine resources needed for the tests. Googlers
-      can use [this dashboard](http://shortn/_nyyTPgDJtF) to determine the
+      can use [this dashboard](http://shortn/_X75IFjffFk) to determine the
       amount of bots required by comparing it to a similar suite on the same
       builder. Do this for each CQ builder and each suite that's being added.
    2. File a [resource request](http://go/file-chrome-resource-bug) for the
@@ -130,8 +130,8 @@
    explaining why. This can be revisited if things change.
 
 If your change doesn't affect the CQ but is expected to increase utilization in
-the testing pools by any more than 5 VMs or 50 CPU cores, it will still need to
-be approved via a resource request. Consult the
+the testing pools by any more than 5 VMs or 50 CPU cores per hour, it will still
+need to be approved via a resource request. Consult the
 [dashboard](http://shortn/_nyyTPgDJtF) linked above to calculate the resource
 usage of a test change. See http://go/i-need-hw for the steps involved in
 getting the approval.
diff --git a/testing/buildbot/chromium.android.fyi.json b/testing/buildbot/chromium.android.fyi.json
index 043a544..48e0c87a 100644
--- a/testing/buildbot/chromium.android.fyi.json
+++ b/testing/buildbot/chromium.android.fyi.json
@@ -154,7 +154,6 @@
           "--avd-config=../../tools/android/avd/proto/generic_android28.textpb",
           "--use-upstream-wpt"
         ],
-        "experiment_percentage": 100,
         "isolate_name": "chrome_public_wpt",
         "merge": {
           "args": [
@@ -1313,7 +1312,6 @@
           "--log-wptreport",
           "--use-upstream-wpt"
         ],
-        "experiment_percentage": 100,
         "isolate_name": "system_webview_wpt",
         "merge": {
           "args": [
diff --git a/testing/buildbot/chromium.chromiumos.json b/testing/buildbot/chromium.chromiumos.json
index 782bf50..eead1de5 100644
--- a/testing/buildbot/chromium.chromiumos.json
+++ b/testing/buildbot/chromium.chromiumos.json
@@ -5886,9 +5886,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v113.0.5668.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v113.0.5669.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 113.0.5668.0",
+        "description": "Run with ash-chrome version 113.0.5669.0",
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -5900,8 +5900,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v113.0.5668.0",
-              "revision": "version:113.0.5668.0"
+              "location": "lacros_version_skew_tests_v113.0.5669.0",
+              "revision": "version:113.0.5669.0"
             }
           ],
           "dimension_sets": [
@@ -6057,9 +6057,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v113.0.5668.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v113.0.5669.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 113.0.5668.0",
+        "description": "Run with ash-chrome version 113.0.5669.0",
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -6071,8 +6071,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v113.0.5668.0",
-              "revision": "version:113.0.5668.0"
+              "location": "lacros_version_skew_tests_v113.0.5669.0",
+              "revision": "version:113.0.5669.0"
             }
           ],
           "dimension_sets": [
@@ -6209,9 +6209,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v113.0.5668.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v113.0.5669.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 113.0.5668.0",
+        "description": "Run with ash-chrome version 113.0.5669.0",
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -6223,8 +6223,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v113.0.5668.0",
-              "revision": "version:113.0.5668.0"
+              "location": "lacros_version_skew_tests_v113.0.5669.0",
+              "revision": "version:113.0.5669.0"
             }
           ],
           "dimension_sets": [
diff --git a/testing/buildbot/chromium.coverage.json b/testing/buildbot/chromium.coverage.json
index d7b40f5..0729984 100644
--- a/testing/buildbot/chromium.coverage.json
+++ b/testing/buildbot/chromium.coverage.json
@@ -25631,9 +25631,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v113.0.5668.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v113.0.5669.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 113.0.5668.0",
+        "description": "Run with ash-chrome version 113.0.5669.0",
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -25645,8 +25645,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v113.0.5668.0",
-              "revision": "version:113.0.5668.0"
+              "location": "lacros_version_skew_tests_v113.0.5669.0",
+              "revision": "version:113.0.5669.0"
             }
           ],
           "dimension_sets": [
@@ -25802,9 +25802,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v113.0.5668.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v113.0.5669.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 113.0.5668.0",
+        "description": "Run with ash-chrome version 113.0.5669.0",
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -25816,8 +25816,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v113.0.5668.0",
-              "revision": "version:113.0.5668.0"
+              "location": "lacros_version_skew_tests_v113.0.5669.0",
+              "revision": "version:113.0.5669.0"
             }
           ],
           "dimension_sets": [
@@ -25954,9 +25954,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v113.0.5668.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v113.0.5669.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 113.0.5668.0",
+        "description": "Run with ash-chrome version 113.0.5669.0",
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -25968,8 +25968,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v113.0.5668.0",
-              "revision": "version:113.0.5668.0"
+              "location": "lacros_version_skew_tests_v113.0.5669.0",
+              "revision": "version:113.0.5669.0"
             }
           ],
           "dimension_sets": [
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json
index 4d05af10..81aa77b9 100644
--- a/testing/buildbot/chromium.fyi.json
+++ b/testing/buildbot/chromium.fyi.json
@@ -50174,9 +50174,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v113.0.5668.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v113.0.5669.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 113.0.5668.0",
+        "description": "Run with ash-chrome version 113.0.5669.0",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -50187,8 +50187,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v113.0.5668.0",
-              "revision": "version:113.0.5668.0"
+              "location": "lacros_version_skew_tests_v113.0.5669.0",
+              "revision": "version:113.0.5669.0"
             }
           ],
           "dimension_sets": [
@@ -50345,9 +50345,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v113.0.5668.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v113.0.5669.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 113.0.5668.0",
+        "description": "Run with ash-chrome version 113.0.5669.0",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -50358,8 +50358,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v113.0.5668.0",
-              "revision": "version:113.0.5668.0"
+              "location": "lacros_version_skew_tests_v113.0.5669.0",
+              "revision": "version:113.0.5669.0"
             }
           ],
           "dimension_sets": [
@@ -50497,9 +50497,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v113.0.5668.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v113.0.5669.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 113.0.5668.0",
+        "description": "Run with ash-chrome version 113.0.5669.0",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -50510,8 +50510,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v113.0.5668.0",
-              "revision": "version:113.0.5668.0"
+              "location": "lacros_version_skew_tests_v113.0.5669.0",
+              "revision": "version:113.0.5669.0"
             }
           ],
           "dimension_sets": [
@@ -52017,9 +52017,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v113.0.5668.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v113.0.5669.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 113.0.5668.0",
+        "description": "Run with ash-chrome version 113.0.5669.0",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -52030,8 +52030,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v113.0.5668.0",
-              "revision": "version:113.0.5668.0"
+              "location": "lacros_version_skew_tests_v113.0.5669.0",
+              "revision": "version:113.0.5669.0"
             }
           ],
           "dimension_sets": [
@@ -52188,9 +52188,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v113.0.5668.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v113.0.5669.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 113.0.5668.0",
+        "description": "Run with ash-chrome version 113.0.5669.0",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -52201,8 +52201,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v113.0.5668.0",
-              "revision": "version:113.0.5668.0"
+              "location": "lacros_version_skew_tests_v113.0.5669.0",
+              "revision": "version:113.0.5669.0"
             }
           ],
           "dimension_sets": [
@@ -52340,9 +52340,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v113.0.5668.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v113.0.5669.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 113.0.5668.0",
+        "description": "Run with ash-chrome version 113.0.5669.0",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -52353,8 +52353,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v113.0.5668.0",
-              "revision": "version:113.0.5668.0"
+              "location": "lacros_version_skew_tests_v113.0.5669.0",
+              "revision": "version:113.0.5669.0"
             }
           ],
           "dimension_sets": [
@@ -53108,9 +53108,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v113.0.5668.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v113.0.5669.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 113.0.5668.0",
+        "description": "Run with ash-chrome version 113.0.5669.0",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -53121,8 +53121,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v113.0.5668.0",
-              "revision": "version:113.0.5668.0"
+              "location": "lacros_version_skew_tests_v113.0.5669.0",
+              "revision": "version:113.0.5669.0"
             }
           ],
           "dimension_sets": [
diff --git a/testing/buildbot/chromium.memory.json b/testing/buildbot/chromium.memory.json
index 97ddcb7..267ebea 100644
--- a/testing/buildbot/chromium.memory.json
+++ b/testing/buildbot/chromium.memory.json
@@ -18414,12 +18414,12 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v113.0.5668.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v113.0.5669.0/test_ash_chrome",
           "--test-launcher-print-test-stdio=always",
           "--combine-ash-logs-on-bots",
           "--asan-symbolize-output"
         ],
-        "description": "Run with ash-chrome version 113.0.5668.0",
+        "description": "Run with ash-chrome version 113.0.5669.0",
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -18431,8 +18431,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v113.0.5668.0",
-              "revision": "version:113.0.5668.0"
+              "location": "lacros_version_skew_tests_v113.0.5669.0",
+              "revision": "version:113.0.5669.0"
             }
           ],
           "dimension_sets": [
@@ -18605,12 +18605,12 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v113.0.5668.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v113.0.5669.0/test_ash_chrome",
           "--test-launcher-print-test-stdio=always",
           "--combine-ash-logs-on-bots",
           "--asan-symbolize-output"
         ],
-        "description": "Run with ash-chrome version 113.0.5668.0",
+        "description": "Run with ash-chrome version 113.0.5669.0",
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -18622,8 +18622,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v113.0.5668.0",
-              "revision": "version:113.0.5668.0"
+              "location": "lacros_version_skew_tests_v113.0.5669.0",
+              "revision": "version:113.0.5669.0"
             }
           ],
           "dimension_sets": [
@@ -18772,12 +18772,12 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v113.0.5668.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v113.0.5669.0/test_ash_chrome",
           "--test-launcher-print-test-stdio=always",
           "--combine-ash-logs-on-bots",
           "--asan-symbolize-output"
         ],
-        "description": "Run with ash-chrome version 113.0.5668.0",
+        "description": "Run with ash-chrome version 113.0.5669.0",
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -18789,8 +18789,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v113.0.5668.0",
-              "revision": "version:113.0.5668.0"
+              "location": "lacros_version_skew_tests_v113.0.5669.0",
+              "revision": "version:113.0.5669.0"
             }
           ],
           "dimension_sets": [
diff --git a/testing/buildbot/test_suite_exceptions.pyl b/testing/buildbot/test_suite_exceptions.pyl
index 47d1f16..530af15 100644
--- a/testing/buildbot/test_suite_exceptions.pyl
+++ b/testing/buildbot/test_suite_exceptions.pyl
@@ -3693,6 +3693,9 @@
   },
   'system_webview_wpt': {
     'modifications': {
+      'android-pie-arm64-wpt-rel-non-cq': {
+        'experiment_percentage': 100,
+      },
       'android-webview-pie-x86-wpt-fyi-rel': {
         'args': [
           '--log-wptreport',
diff --git a/testing/buildbot/test_suites.pyl b/testing/buildbot/test_suites.pyl
index 78f001da..c5e1bf6e 100644
--- a/testing/buildbot/test_suites.pyl
+++ b/testing/buildbot/test_suites.pyl
@@ -546,7 +546,6 @@
           'expiration': 18000,
           'hard_timeout': 14400,
         },
-        'experiment_percentage': 100,
         'merge': {
           'args': [
             '--verbose',
@@ -5105,7 +5104,6 @@
           'expiration': 18000,
           'hard_timeout': 14400,
         },
-        'experiment_percentage': 100,
         'merge': {
           'args': [
             '--verbose',
diff --git a/testing/buildbot/variants.pyl b/testing/buildbot/variants.pyl
index 4a55d72d..cbf2fba12 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_v113.0.5668.0/test_ash_chrome',
+      '--ash-chrome-path-override=../../lacros_version_skew_tests_v113.0.5669.0/test_ash_chrome',
     ],
-    'description': 'Run with ash-chrome version 113.0.5668.0',
+    'description': 'Run with ash-chrome version 113.0.5669.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_v113.0.5668.0',
-          'revision': 'version:113.0.5668.0',
+          'location': 'lacros_version_skew_tests_v113.0.5669.0',
+          'revision': 'version:113.0.5669.0',
         },
       ],
     },
diff --git a/testing/libfuzzer/OWNERS b/testing/libfuzzer/OWNERS
index e9c93c89..6d21130 100644
--- a/testing/libfuzzer/OWNERS
+++ b/testing/libfuzzer/OWNERS
@@ -1,3 +1,4 @@
-metzman@chromium.org
+adetaylor@chromium.org
 bookholt@chromium.org
+metzman@chromium.org
 tiszka@chromium.org
diff --git a/testing/libfuzzer/README.md b/testing/libfuzzer/README.md
index 1222e2dc..9b3b62b 100644
--- a/testing/libfuzzer/README.md
+++ b/testing/libfuzzer/README.md
@@ -44,6 +44,7 @@
 * [AFL integration] with Chromium and ClusterFuzz.
 * [Detailed references] for other integration parts.
 * Writing fuzzers for the [non-browser parts of Chrome OS].
+* [Fuzzing browsertests] if you need to fuzz multiple Chrome subsystems.
 
 ## Trophies
 * [Issues automatically filed] by ClusterFuzz.
@@ -66,6 +67,7 @@
 [Creating a fuzz target that expects a protobuf]: libprotobuf-mutator.md
 [Detailed references]: reference.md
 [Fuzzing]: https://en.wikipedia.org/wiki/Fuzzing
+[Fuzzing browsertests]: fuzzing_browsertests.md
 [Fuzzing mojo interfaces]: ../../mojo/docs/mojolpm.md
 [Getting Started Guide]: getting_started.md
 [Guided in-process fuzzing of Chrome components]: https://security.googleblog.com/2016/08/guided-in-process-fuzzing-of-chrome.html
diff --git a/testing/libfuzzer/fuzzer_test.gni b/testing/libfuzzer/fuzzer_test.gni
index 59737d24..f9ab5fe 100644
--- a/testing/libfuzzer/fuzzer_test.gni
+++ b/testing/libfuzzer/fuzzer_test.gni
@@ -26,6 +26,7 @@
 # - seed_corpus - a directory with seed corpus.
 # - seed_corpus_deps - dependencies for generating the seed corpus.
 # - grammar_options - defines a grammar used by a grammar based mutator.
+# - exclude_main - if you're going to provide your own 'main' function
 #
 # If use_libfuzzer gn flag is defined, then proper fuzzer would be build.
 # Without use_libfuzzer or use_afl a unit-test style binary would be built on
@@ -39,7 +40,12 @@
   if (!disable_libfuzzer && use_fuzzing_engine) {
     assert(defined(invoker.sources), "Need sources in $target_name.")
 
-    test_deps = [ "//testing/libfuzzer:fuzzing_engine_main" ]
+    test_deps = []
+    if (defined(invoker.exclude_main) && invoker.exclude_main) {
+      test_deps += [ "//testing/libfuzzer:fuzzing_engine_no_main" ]
+    } else {
+      test_deps += [ "//testing/libfuzzer:fuzzing_engine_main" ]
+    }
     test_data_deps = []
 
     if (defined(invoker.deps)) {
diff --git a/testing/libfuzzer/fuzzing_browsertests.md b/testing/libfuzzer/fuzzing_browsertests.md
new file mode 100644
index 0000000..8eca6706
--- /dev/null
+++ b/testing/libfuzzer/fuzzing_browsertests.md
@@ -0,0 +1,52 @@
+# Fuzzing browsertests
+
+Fuzzing is effective if either:
+
+* it's guided by code coverage, and can execute incredible numbers of test cases
+  per second to explore the codebase (thousands); or
+* it has a smart mutator of some kind (out of scope here).
+
+If you have an API to be fuzzed, make a simple libfuzzer fuzzer for just that
+API, to get the speed required to explore its attack surface. If however we want
+to fuzz a larger, more complex set of Chromium code, we usually need an entire
+browser process environment around us. The browser process takes seconds to
+start, preventing coverage guided fuzzing from being effective.
+
+We now have an experimental 'in process fuzz test' framework which attempts to:
+* Start the browser process _once_
+* Execute lots of fuzz cases in that pre-existing browser.
+This _may_ amortize the start up cost sufficiently to make such coverage-guided
+fuzzing plausible. We don't yet know. But this document shows how to use it,
+just in case.
+
+# Writing an in process fuzz case
+
+* Use the template `chrome/test/in_process_fuzz_test.gni`
+* Provide a source code file which inherits from `InProcessFuzzTest`. This
+  must override the `Fuzz` method. You'll find that your base class inherits
+  from the full browser test infrastructure, so you can do anything you'd
+  do in a normal Chrome browser test.
+* In your `cc` file, also use the macro `REGISTER_IN_PROCESS_FUZZER` to
+  declare that this is the one and only such fuzzer in your executable.
+
+# Running such an in process fuzz case
+
+These cases can be run either with libfuzzer or centipede.
+
+For libfuzzer, provide gn arguments `use_sanitizer_coverage = true`,
+`use_libfuzzer = true`, `is_component_build = false` and `is_asan = true`
+(other permutations may work).
+
+This will give you a single binary you can run like this:
+`my_fuzzer /tmp/corpus -rss_limit_mb=81920`
+
+However, you'll more likely want to use
+[centipede](https://github.com/google/centipede) which has an
+out-of-process co-ordinator.
+
+To use centipede, specify `use_centipede = true` instead of `use_libfuzzer =
+true`. You'll then want to run centipede using some command like:
+
+```
+export ASAN_OPTIONS=detect_odr_violations=0 $BIN_DIR/centipede --binary=~/chromium/src/out/ASAN/html_in_process_fuzz_tests --workdir=$WD --shmem_size_mb 4096 --rss_limit_mb 0  --batch_size 100 --log_features_shards 2 --exit_on_crash 1
+```
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index a3393c9..8cd1a7ae 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -14744,5 +14744,20 @@
                 }
             ]
         }
+    ],
+    "ZeroCopyTabCaptureStudyWin": [
+        {
+            "platforms": [
+                "windows"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled_20230222",
+                    "enable_features": [
+                        "ZeroCopyTabCapture"
+                    ]
+                }
+            ]
+        }
     ]
 }
diff --git a/third_party/androidx_javascriptengine/BUILD.gn b/third_party/androidx_javascriptengine/BUILD.gn
index 2977dc7..204eead7 100644
--- a/third_party/androidx_javascriptengine/BUILD.gn
+++ b/third_party/androidx_javascriptengine/BUILD.gn
@@ -13,6 +13,7 @@
 android_aidl("js_sandbox_aidl") {
   import_include = [ "src/main/aidl" ]
   sources = [
+    "src/main/aidl/org/chromium/android_webview/js_sandbox/common/IJsSandboxConsoleCallback.aidl",
     "src/main/aidl/org/chromium/android_webview/js_sandbox/common/IJsSandboxIsolate.aidl",
     "src/main/aidl/org/chromium/android_webview/js_sandbox/common/IJsSandboxIsolateCallback.aidl",
     "src/main/aidl/org/chromium/android_webview/js_sandbox/common/IJsSandboxIsolateSyncCallback.aidl",
@@ -38,6 +39,7 @@
     "src/main/java/androidx/javascriptengine/ExecutionErrorTypes.java",
     "src/main/java/androidx/javascriptengine/IsolateStartupParameters.java",
     "src/main/java/androidx/javascriptengine/IsolateTerminatedException.java",
+    "src/main/java/androidx/javascriptengine/JavaScriptConsoleCallback.java",
     "src/main/java/androidx/javascriptengine/JavaScriptException.java",
     "src/main/java/androidx/javascriptengine/JavaScriptIsolate.java",
     "src/main/java/androidx/javascriptengine/JavaScriptSandbox.java",
diff --git a/third_party/blink/common/DEPS b/third_party/blink/common/DEPS
index 40bf560cf..56a66d24 100644
--- a/third_party/blink/common/DEPS
+++ b/third_party/blink/common/DEPS
@@ -17,6 +17,7 @@
     "+services/metrics/public/mojom/ukm_interface.mojom.h",
     "+services/network/public/cpp",
     "+services/network/public/mojom/content_security_policy.mojom.h",
+    "+services/network/public/mojom/content_security_policy.mojom-forward.h",
     "+services/network/public/mojom/fetch_api.mojom-shared.h",
     "+services/network/public/mojom/parsed_headers.mojom.h",
     "+services/network/public/mojom/referrer_policy.mojom.h",
diff --git a/third_party/blink/common/permissions_policy/origin_with_possible_wildcards.cc b/third_party/blink/common/permissions_policy/origin_with_possible_wildcards.cc
index 7ad2f84e..feecdca75 100644
--- a/third_party/blink/common/permissions_policy/origin_with_possible_wildcards.cc
+++ b/third_party/blink/common/permissions_policy/origin_with_possible_wildcards.cc
@@ -17,8 +17,8 @@
     const url::Origin& origin,
     bool has_subdomain_wildcard)
     : origin(origin), has_subdomain_wildcard(has_subdomain_wildcard) {
-  // Origins that do have wildcards cannot be opaque.
-  DCHECK(!origin.opaque() || !has_subdomain_wildcard);
+  // Origins cannot be opaque.
+  DCHECK(!origin.opaque());
 }
 
 OriginWithPossibleWildcards::OriginWithPossibleWildcards(
@@ -30,7 +30,7 @@
 OriginWithPossibleWildcards::~OriginWithPossibleWildcards() = default;
 
 // static
-OriginWithPossibleWildcards OriginWithPossibleWildcards::Parse(
+absl::optional<OriginWithPossibleWildcards> OriginWithPossibleWildcards::Parse(
     const std::string& allowlist_entry,
     const NodeType type) {
   auto wildcard_pos = std::string::npos;
@@ -50,7 +50,7 @@
     if (parsed_origin.opaque()) {
       // We early return here assuming even with the `*.` the origin parses
       // opaque.
-      return OriginWithPossibleWildcards();
+      return absl::nullopt;
     } else if (
         net::registry_controlled_domains::HostHasRegistryControlledDomain(
             parsed_origin.host(),
@@ -64,7 +64,7 @@
   // valid. Invalid strings will produce an opaque origin.
   const auto parsed_origin = url::Origin::Create(GURL(allowlist_entry));
   if (parsed_origin.opaque()) {
-    return OriginWithPossibleWildcards();
+    return absl::nullopt;
   } else {
     return OriginWithPossibleWildcards(parsed_origin,
                                        /*has_subdomain_wildcard=*/false);
@@ -72,6 +72,7 @@
 }
 
 std::string OriginWithPossibleWildcards::Serialize() const {
+  DCHECK(!origin.opaque());
   auto wildcard_pos = std::string::npos;
   auto serialized_origin = origin.Serialize();
   if (has_subdomain_wildcard &&
@@ -86,6 +87,7 @@
 
 bool OriginWithPossibleWildcards::DoesMatchOrigin(
     const url::Origin& match_origin) const {
+  DCHECK(!origin.opaque());
   if (has_subdomain_wildcard) {
     // This function won't match https://*.foo.com with https://foo.com.
     if (origin == match_origin) {
diff --git a/third_party/blink/common/permissions_policy/origin_with_possible_wildcards_unittest.cc b/third_party/blink/common/permissions_policy/origin_with_possible_wildcards_unittest.cc
index 404f0b6..afdb4d29 100644
--- a/third_party/blink/common/permissions_policy/origin_with_possible_wildcards_unittest.cc
+++ b/third_party/blink/common/permissions_policy/origin_with_possible_wildcards_unittest.cc
@@ -6,9 +6,9 @@
 
 #include "base/test/gtest_util.h"
 #include "mojo/public/cpp/test_support/test_utils.h"
+#include "services/network/public/mojom/content_security_policy.mojom.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/common/permissions_policy/permissions_policy_mojom_traits.h"
-#include "third_party/blink/public/mojom/permissions_policy/permissions_policy.mojom.h"
 #include "url/gurl.h"
 #include "url/origin.h"
 
@@ -33,14 +33,9 @@
       std::make_tuple(url::Origin::Create(GURL("https://foo.com")),
                       url::Origin::Create(GURL("https://bar.foo.com")), false,
                       false, "Different subdomain, no wildcard"),
-      std::make_tuple(url::Origin::Create(GURL("https://foo.com")),
-                      url::Origin(), false, false,
-                      "Origin to opaque, no wildcard"),
       std::make_tuple(url::Origin(),
                       url::Origin::Create(GURL("https://foo.com")), false,
                       false, "Opaque to origin, no wildcard"),
-      std::make_tuple(url::Origin(), url::Origin(), false, false,
-                      "Opaque to opaque, no wildcard"),
       std::make_tuple(url::Origin::Create(GURL("file:///test")),
                       url::Origin::Create(GURL("file:///test")), false, true,
                       "File, no wildcard"),
@@ -119,10 +114,10 @@
                       "https://%2A.foo.com", false,
                       "Origin with subdomain wildcard in attribute"),
       std::make_tuple("*://foo.com",
-                      OriginWithPossibleWildcards::NodeType::kHeader, "null",
-                      false, "Origin with scheme wildcard in header"),
+                      OriginWithPossibleWildcards::NodeType::kHeader, "", false,
+                      "Origin with scheme wildcard in header"),
       std::make_tuple("*://foo.com",
-                      OriginWithPossibleWildcards::NodeType::kAttribute, "null",
+                      OriginWithPossibleWildcards::NodeType::kAttribute, "",
                       false, "Origin with scheme wildcard in attribute"),
       std::make_tuple(
           "https://*", OriginWithPossibleWildcards::NodeType::kHeader,
@@ -164,10 +159,10 @@
                       "https://%2A.example.test", false,
                       "Origin with unknown tld host wildcard in attribute"),
       std::make_tuple("https://foo.com:*",
-                      OriginWithPossibleWildcards::NodeType::kHeader, "null",
-                      false, "Origin with port wildcard in header"),
+                      OriginWithPossibleWildcards::NodeType::kHeader, "", false,
+                      "Origin with port wildcard in header"),
       std::make_tuple("https://foo.com:*",
-                      OriginWithPossibleWildcards::NodeType::kAttribute, "null",
+                      OriginWithPossibleWildcards::NodeType::kAttribute, "",
                       false, "Origin with port wildcard in attribute"),
       std::make_tuple("https://bar.*.foo.com",
                       OriginWithPossibleWildcards::NodeType::kHeader,
@@ -191,10 +186,14 @@
         OriginWithPossibleWildcards::Parse(std::get<0>(value),
                                            std::get<1>(value));
     SCOPED_TRACE(std::get<4>(value));
-    EXPECT_EQ(std::get<2>(value),
-              origin_with_possible_wildcards.origin.Serialize());
-    EXPECT_EQ(std::get<3>(value),
-              origin_with_possible_wildcards.has_subdomain_wildcard);
+    if (strlen(std::get<2>(value))) {
+      EXPECT_EQ(std::get<2>(value),
+                origin_with_possible_wildcards->origin.Serialize());
+      EXPECT_EQ(std::get<3>(value),
+                origin_with_possible_wildcards->has_subdomain_wildcard);
+    } else {
+      EXPECT_FALSE(origin_with_possible_wildcards);
+    }
   }
 }
 
@@ -209,7 +208,6 @@
                       "Origin with improper subdomain wildcard"),
       std::make_tuple("https://%2A.com", false, "https://%2A.com",
                       "Origin with non-registerable subdomain wildcard"),
-      std::make_tuple("null", false, "null", "Opaque origin"),
   };
   for (const auto& value : values) {
     const auto& origin_with_possible_wildcards = OriginWithPossibleWildcards(
@@ -220,25 +218,28 @@
 }
 
 TEST(OriginWithPossibleWildcardsTest, Constructors) {
-  OriginWithPossibleWildcards a;
-  OriginWithPossibleWildcards b(url::Origin(), false);
-  OriginWithPossibleWildcards c(b);
+  OriginWithPossibleWildcards a(url::Origin::Create(GURL("https://google.com")),
+                                false);
+  OriginWithPossibleWildcards b(
+      url::Origin::Create(GURL("https://google.com:443")), false);
+  OriginWithPossibleWildcards c(url::Origin::Create(GURL("https://google.com")),
+                                true);
   OriginWithPossibleWildcards d = c;
-  EXPECT_NE(a, b);
-  EXPECT_EQ(b, c);
+  EXPECT_EQ(a, b);
+  EXPECT_NE(b, c);
   EXPECT_EQ(c, d);
-  mojo::test::SerializeAndDeserialize<mojom::OriginWithPossibleWildcards>(a, b);
+  EXPECT_TRUE(
+      mojo::test::SerializeAndDeserialize<network::mojom::CSPSource>(a, b));
   EXPECT_EQ(a, b);
 }
 
 TEST(OriginWithPossibleWildcardsTest, Opaque) {
   EXPECT_DCHECK_DEATH(OriginWithPossibleWildcards(url::Origin(), true));
-  OriginWithPossibleWildcards original(url::Origin(), false);
-  original.has_subdomain_wildcard = true;
+  EXPECT_DCHECK_DEATH(OriginWithPossibleWildcards(url::Origin(), false));
+  OriginWithPossibleWildcards original;
   OriginWithPossibleWildcards copy;
-  EXPECT_FALSE(
-      mojo::test::SerializeAndDeserialize<mojom::OriginWithPossibleWildcards>(
-          original, copy));
+  EXPECT_FALSE(mojo::test::SerializeAndDeserialize<network::mojom::CSPSource>(
+      original, copy));
 }
 
 }  // namespace blink
diff --git a/third_party/blink/common/permissions_policy/permissions_policy.cc b/third_party/blink/common/permissions_policy/permissions_policy.cc
index 6a369c2..b07a2a6 100644
--- a/third_party/blink/common/permissions_policy/permissions_policy.cc
+++ b/third_party/blink/common/permissions_policy/permissions_policy.cc
@@ -20,9 +20,11 @@
 
 // Extracts an Allowlist from a ParsedPermissionsPolicyDeclaration.
 PermissionsPolicy::Allowlist AllowlistFromDeclaration(
-    const ParsedPermissionsPolicyDeclaration& parsed_declaration,
-    const PermissionsPolicyFeatureList& feature_list) {
+    const ParsedPermissionsPolicyDeclaration& parsed_declaration) {
   auto result = PermissionsPolicy::Allowlist();
+  if (parsed_declaration.self_if_matches) {
+    result.AddSelf(parsed_declaration.self_if_matches);
+  }
   if (parsed_declaration.matches_all_origins)
     result.AddAll();
   if (parsed_declaration.matches_opaque_src)
@@ -46,6 +48,10 @@
   allowed_origins_.push_back(origin);
 }
 
+void PermissionsPolicy::Allowlist::AddSelf(absl::optional<url::Origin> self) {
+  self_if_matches_ = std::move(self);
+}
+
 void PermissionsPolicy::Allowlist::AddAll() {
   matches_all_origins_ = true;
 }
@@ -55,6 +61,9 @@
 }
 
 bool PermissionsPolicy::Allowlist::Contains(const url::Origin& origin) const {
+  if (origin == self_if_matches_) {
+    return true;
+  }
   for (const auto& allowed_origin : allowed_origins_) {
     if (allowed_origin.DoesMatchOrigin(origin))
       return true;
@@ -64,6 +73,11 @@
   return matches_all_origins_;
 }
 
+const absl::optional<url::Origin>& PermissionsPolicy::Allowlist::SelfIfMatches()
+    const {
+  return self_if_matches_;
+}
+
 bool PermissionsPolicy::Allowlist::MatchesAll() const {
   return matches_all_origins_;
 }
@@ -254,8 +268,7 @@
        parsed_header) {
     mojom::PermissionsPolicyFeature feature = parsed_declaration.feature;
     DCHECK(feature != mojom::PermissionsPolicyFeature::kNotFound);
-    allowlists_.emplace(
-        feature, AllowlistFromDeclaration(parsed_declaration, *feature_list_));
+    allowlists_.emplace(feature, AllowlistFromDeclaration(parsed_declaration));
   }
 }
 
@@ -266,8 +279,7 @@
        parsed_header) {
     mojom::PermissionsPolicyFeature feature = parsed_declaration.feature;
     DCHECK(feature != mojom::PermissionsPolicyFeature::kNotFound);
-    const auto header_allowlist =
-        AllowlistFromDeclaration(parsed_declaration, *feature_list_);
+    const auto header_allowlist = AllowlistFromDeclaration(parsed_declaration);
     auto& isolated_app_allowlist = allowlists_.at(feature);
 
     // If the header does not specify further restrictions we do not need to
@@ -283,6 +295,7 @@
       // clone() is implemented.
       isolated_app_allowlist.SetAllowedOrigins(header_allowed_origins);
       isolated_app_allowlist.RemoveMatchesAll();
+      isolated_app_allowlist.AddSelf(header_allowlist.SelfIfMatches());
       continue;
     }
 
@@ -306,8 +319,7 @@
        parsed_header) {
     mojom::PermissionsPolicyFeature feature = parsed_declaration.feature;
     DCHECK(GetPolicyFeatureToClientHintMap().contains(feature));
-    allowlists_[feature] =
-        AllowlistFromDeclaration(parsed_declaration, *feature_list_);
+    allowlists_[feature] = AllowlistFromDeclaration(parsed_declaration);
   }
 }
 
@@ -450,7 +462,7 @@
       // 9.8 5.1: If the allowlist for feature in container policy matches
       // origin, return "Enabled".
       // 9.8 5.2: Otherwise return "Disabled".
-      return AllowlistFromDeclaration(decl, *feature_list_).Contains(origin_);
+      return AllowlistFromDeclaration(decl).Contains(origin_);
     }
   }
   // 9.8 6: If feature’s default allowlist is *, return "Enabled".
diff --git a/third_party/blink/common/permissions_policy/permissions_policy_declaration.cc b/third_party/blink/common/permissions_policy/permissions_policy_declaration.cc
index 6a2f2261..6dc6dba6 100644
--- a/third_party/blink/common/permissions_policy/permissions_policy_declaration.cc
+++ b/third_party/blink/common/permissions_policy/permissions_policy_declaration.cc
@@ -22,10 +22,12 @@
 ParsedPermissionsPolicyDeclaration::ParsedPermissionsPolicyDeclaration(
     mojom::PermissionsPolicyFeature feature,
     const std::vector<blink::OriginWithPossibleWildcards>& allowed_origins,
+    const absl::optional<url::Origin>& self_if_matches,
     bool matches_all_origins,
     bool matches_opaque_src)
     : feature(feature),
-      allowed_origins(allowed_origins),
+      allowed_origins(std::move(allowed_origins)),
+      self_if_matches(std::move(self_if_matches)),
       matches_all_origins(matches_all_origins),
       matches_opaque_src(matches_opaque_src) {}
 
@@ -41,6 +43,9 @@
   if (matches_all_origins || (matches_opaque_src && origin.opaque())) {
     return true;
   }
+  if (origin == self_if_matches) {
+    return true;
+  }
   for (const auto& origin_with_possible_wildcards : allowed_origins) {
     if (origin_with_possible_wildcards.DoesMatchOrigin(origin)) {
       return true;
diff --git a/third_party/blink/common/permissions_policy/permissions_policy_declaration_unittest.cc b/third_party/blink/common/permissions_policy/permissions_policy_declaration_unittest.cc
index 493fd64..c28394d 100644
--- a/third_party/blink/common/permissions_policy/permissions_policy_declaration_unittest.cc
+++ b/third_party/blink/common/permissions_policy/permissions_policy_declaration_unittest.cc
@@ -48,6 +48,19 @@
       /*has_subdomain_wildcard=*/false);
   EXPECT_TRUE(match_decl.Contains(kTestOrigin));
   EXPECT_FALSE(match_decl.Contains(kOpaqueOrigin));
+
+  // Self match.
+  ParsedPermissionsPolicyDeclaration self_decl;
+  self_decl.self_if_matches =
+      url::Origin::Create(GURL("https://example.test/"));
+  EXPECT_TRUE(self_decl.Contains(kTestOrigin));
+  EXPECT_FALSE(self_decl.Contains(kOpaqueOrigin));
+
+  // Opaque self match.
+  ParsedPermissionsPolicyDeclaration opaque_self_decl;
+  opaque_self_decl.self_if_matches = kOpaqueOrigin;
+  EXPECT_FALSE(opaque_self_decl.Contains(kTestOrigin));
+  EXPECT_TRUE(opaque_self_decl.Contains(kOpaqueOrigin));
 }
 
 }  // namespace blink
diff --git a/third_party/blink/common/permissions_policy/permissions_policy_mojom_traits.cc b/third_party/blink/common/permissions_policy/permissions_policy_mojom_traits.cc
index aa462f9..7482e891 100644
--- a/third_party/blink/common/permissions_policy/permissions_policy_mojom_traits.cc
+++ b/third_party/blink/common/permissions_policy/permissions_policy_mojom_traits.cc
@@ -4,20 +4,34 @@
 
 #include "third_party/blink/common/permissions_policy/permissions_policy_mojom_traits.h"
 
+#include "services/network/public/mojom/content_security_policy.mojom.h"
 #include "url/mojom/origin_mojom_traits.h"
+#include "url/origin.h"
 
 namespace mojo {
 
-bool StructTraits<blink::mojom::OriginWithPossibleWildcardsDataView,
+bool StructTraits<network::mojom::CSPSourceDataView,
                   blink::OriginWithPossibleWildcards>::
-    Read(blink::mojom::OriginWithPossibleWildcardsDataView in,
+    Read(network::mojom::CSPSourceDataView in,
          blink::OriginWithPossibleWildcards* out) {
-  out->has_subdomain_wildcard = in.has_subdomain_wildcard();
-  if (!in.ReadOrigin(&out->origin))
+  // We do not support any wildcard types besides host
+  // based ones for now.
+  out->has_subdomain_wildcard = in.is_host_wildcard();
+  std::string scheme;
+  std::string host;
+  if (!in.ReadScheme(&scheme) || !in.ReadHost(&host)) {
     return false;
+  }
+  absl::optional<url::Origin> maybe_origin =
+      url::Origin::UnsafelyCreateTupleOriginWithoutNormalization(scheme, host,
+                                                                 in.port());
+  if (!maybe_origin) {
+    return false;
+  }
+  out->origin = *maybe_origin;
 
-  // An opaque origin cannot have a wildcard.
-  return !out->origin.opaque() || !out->has_subdomain_wildcard;
+  // Origins cannot be opaque.
+  return !out->origin.opaque();
 }
 
 bool StructTraits<blink::mojom::ParsedPermissionsPolicyDeclarationDataView,
@@ -27,7 +41,8 @@
   out->matches_all_origins = in.matches_all_origins();
   out->matches_opaque_src = in.matches_opaque_src();
   return in.ReadFeature(&out->feature) &&
-         in.ReadAllowedOrigins(&out->allowed_origins);
+         in.ReadAllowedOrigins(&out->allowed_origins) &&
+         in.ReadSelfIfMatches(&out->self_if_matches);
 }
 
 }  // namespace mojo
diff --git a/third_party/blink/common/permissions_policy/permissions_policy_mojom_traits.h b/third_party/blink/common/permissions_policy/permissions_policy_mojom_traits.h
index 01cf2e4..a2188cb 100644
--- a/third_party/blink/common/permissions_policy/permissions_policy_mojom_traits.h
+++ b/third_party/blink/common/permissions_policy/permissions_policy_mojom_traits.h
@@ -8,6 +8,7 @@
 #include <map>
 
 #include "mojo/public/cpp/bindings/enum_traits.h"
+#include "services/network/public/mojom/content_security_policy.mojom-forward.h"
 #include "third_party/blink/common/permissions_policy/policy_value_mojom_traits.h"
 #include "third_party/blink/public/common/common_export.h"
 #include "third_party/blink/public/common/permissions_policy/origin_with_possible_wildcards.h"
@@ -18,20 +19,35 @@
 namespace mojo {
 
 template <>
-class BLINK_COMMON_EXPORT
-    StructTraits<blink::mojom::OriginWithPossibleWildcardsDataView,
-                 blink::OriginWithPossibleWildcards> {
+class BLINK_COMMON_EXPORT StructTraits<network::mojom::CSPSourceDataView,
+                                       blink::OriginWithPossibleWildcards> {
  public:
-  static const url::Origin& origin(const blink::OriginWithPossibleWildcards&
+  static const std::string& scheme(const blink::OriginWithPossibleWildcards&
                                        origin_with_possible_wildcards) {
-    return origin_with_possible_wildcards.origin;
+    return origin_with_possible_wildcards.origin.scheme();
   }
-  static bool has_subdomain_wildcard(const blink::OriginWithPossibleWildcards&
-                                         origin_with_possible_wildcards) {
+  static const std::string& host(const blink::OriginWithPossibleWildcards&
+                                     origin_with_possible_wildcards) {
+    return origin_with_possible_wildcards.origin.host();
+  }
+  static int port(const blink::OriginWithPossibleWildcards&
+                      origin_with_possible_wildcards) {
+    return origin_with_possible_wildcards.origin.port();
+  }
+  static const std::string path(const blink::OriginWithPossibleWildcards&
+                                    origin_with_possible_wildcards) {
+    return std::string();
+  }
+  static bool is_host_wildcard(const blink::OriginWithPossibleWildcards&
+                                   origin_with_possible_wildcards) {
     return origin_with_possible_wildcards.has_subdomain_wildcard;
   }
+  static bool is_port_wildcard(const blink::OriginWithPossibleWildcards&
+                                   origin_with_possible_wildcards) {
+    return false;
+  }
 
-  static bool Read(blink::mojom::OriginWithPossibleWildcardsDataView in,
+  static bool Read(network::mojom::CSPSourceDataView in,
                    blink::OriginWithPossibleWildcards* out);
 };
 
@@ -48,6 +64,10 @@
       const blink::ParsedPermissionsPolicyDeclaration& policy) {
     return policy.allowed_origins;
   }
+  static const absl::optional<url::Origin>& self_if_matches(
+      const blink::ParsedPermissionsPolicyDeclaration& policy) {
+    return policy.self_if_matches;
+  }
   static bool matches_all_origins(
       const blink::ParsedPermissionsPolicyDeclaration& policy) {
     return policy.matches_all_origins;
diff --git a/third_party/blink/common/permissions_policy/permissions_policy_unittest.cc b/third_party/blink/common/permissions_policy/permissions_policy_unittest.cc
index ec12c0e..b28a197b 100644
--- a/third_party/blink/common/permissions_policy/permissions_policy_unittest.cc
+++ b/third_party/blink/common/permissions_policy/permissions_policy_unittest.cc
@@ -163,12 +163,11 @@
       CreateFromParentPolicy(nullptr, origin_a_);
   std::unique_ptr<PermissionsPolicy> policy2 =
       CreateFromParentPolicy(policy1.get(), origin_b_);
-  policy2->SetHeaderPolicy(
-      {{{kDefaultSelfFeature, /*allowed_origins=*/
-         {blink::OriginWithPossibleWildcards(origin_b_,
-                                             /*has_subdomain_wildcard=*/false)},
-         false,
-         false}}});
+  policy2->SetHeaderPolicy({{{kDefaultSelfFeature, /*allowed_origins=*/
+                              /*allowed_origins=*/{},
+                              /*self_if_matches=*/origin_b_,
+                              /*matches_all_origins=*/false,
+                              /*matches_opaque_src=*/false}}});
   EXPECT_FALSE(policy2->IsFeatureEnabled(kDefaultSelfFeature));
 }
 
@@ -190,12 +189,11 @@
   // they are at a different origin.
   std::unique_ptr<PermissionsPolicy> policy1 =
       CreateFromParentPolicy(nullptr, origin_a_);
-  policy1->SetHeaderPolicy(
-      {{{kDefaultSelfFeature, /*allowed_origins=*/
-         {blink::OriginWithPossibleWildcards(origin_a_,
-                                             /*has_subdomain_wildcard=*/false)},
-         false,
-         false}}});
+  policy1->SetHeaderPolicy({{{kDefaultSelfFeature, /*allowed_origins=*/
+                              /*allowed_origins=*/{},
+                              /*self_if_matches=*/origin_a_,
+                              /*matches_all_origins=*/false,
+                              /*matches_opaque_src=*/false}}});
   std::unique_ptr<PermissionsPolicy> policy2 =
       CreateFromParentPolicy(policy1.get(), origin_a_);
   std::unique_ptr<PermissionsPolicy> policy3 =
@@ -227,12 +225,40 @@
   // it is embedded by frame 2, for which the feature is not enabled.
   std::unique_ptr<PermissionsPolicy> policy1 =
       CreateFromParentPolicy(nullptr, origin_a_);
-  policy1->SetHeaderPolicy(
-      {{{kDefaultSelfFeature, /*allowed_origins=*/
-         {blink::OriginWithPossibleWildcards(origin_a_,
-                                             /*has_subdomain_wildcard=*/false)},
-         false,
-         false}}});
+  policy1->SetHeaderPolicy({{{kDefaultSelfFeature, /*allowed_origins=*/
+                              /*allowed_origins=*/{},
+                              /*self_if_matches=*/origin_a_,
+                              /*matches_all_origins=*/false,
+                              /*matches_opaque_src=*/false}}});
+  std::unique_ptr<PermissionsPolicy> policy2 =
+      CreateFromParentPolicy(policy1.get(), origin_b_);
+  std::unique_ptr<PermissionsPolicy> policy3 =
+      CreateFromParentPolicy(policy2.get(), origin_a_);
+  EXPECT_FALSE(policy2->IsFeatureEnabled(kDefaultSelfFeature));
+  EXPECT_FALSE(policy3->IsFeatureEnabled(kDefaultSelfFeature));
+}
+
+TEST_F(PermissionsPolicyTest, TestReflexiveFrameOriginAInheritance) {
+  // +-------------------------------------------+
+  // |(1) Origin A                               |
+  // |Permissions-Policy: default-self="OriginA" |
+  // | +-----------------+                       |
+  // | |(2) Origin B     |                       |
+  // | |No Policy        |                       |
+  // | | +-------------+ |                       |
+  // | | |(3)Origin A  | |                       |
+  // | | |No Policy    | |                       |
+  // | | +-------------+ |                       |
+  // | +-----------------+                       |
+  // +-------------------------------------------+
+  // Feature which is enabled at top-level should be disabled in frame 3, as
+  // it is embedded by frame 2, for which the feature is not enabled.
+  std::unique_ptr<PermissionsPolicy> policy1 =
+      CreateFromParentPolicy(nullptr, origin_a_);
+  policy1->SetHeaderPolicy({{{kDefaultSelfFeature, /*allowed_origins=*/{},
+                              /*self_if_matches=*/absl::nullopt,
+                              /*matches_all_origins=*/false,
+                              /*matches_opaque_src=*/false}}});
   std::unique_ptr<PermissionsPolicy> policy2 =
       CreateFromParentPolicy(policy1.get(), origin_b_);
   std::unique_ptr<PermissionsPolicy> policy3 =
@@ -263,8 +289,9 @@
       {{{kDefaultSelfFeature, /*allowed_origins=*/
          {blink::OriginWithPossibleWildcards(origin_b_,
                                              /*has_subdomain_wildcard=*/false)},
-         false,
-         false}}});
+         /*self_if_matches=*/absl::nullopt,
+         /*matches_all_origins=*/false,
+         /*matches_opaque_src=*/false}}});
   std::unique_ptr<PermissionsPolicy> policy2 =
       CreateFromParentPolicy(policy1.get(), origin_b_);
   std::unique_ptr<PermissionsPolicy> policy3 =
@@ -300,14 +327,16 @@
       {{{kDefaultSelfFeature, /*allowed_origins=*/
          {blink::OriginWithPossibleWildcards(origin_b_,
                                              /*has_subdomain_wildcard=*/false)},
-         false,
-         false}}});
+         /*self_if_matches=*/absl::nullopt,
+         /*matches_all_origins=*/false,
+         /*matches_opaque_src=*/false}}});
   ParsedPermissionsPolicy frame_policy = {
       {{kDefaultSelfFeature, /*allowed_origins=*/
         {blink::OriginWithPossibleWildcards(origin_b_,
                                             /*has_subdomain_wildcard=*/false)},
-        false,
-        false}}};
+        /*self_if_matches=*/absl::nullopt,
+        /*matches_all_origins=*/false,
+        /*matches_opaque_src=*/false}}};
   std::unique_ptr<PermissionsPolicy> policy2 =
       CreateFromParentWithFramePolicy(policy1.get(), frame_policy, origin_b_);
   std::unique_ptr<PermissionsPolicy> policy3 =
@@ -327,8 +356,10 @@
   // Default-on feature should be disabled in top-level frame.
   std::unique_ptr<PermissionsPolicy> policy1 =
       CreateFromParentPolicy(nullptr, origin_a_);
-  policy1->SetHeaderPolicy(
-      {{{kDefaultOnFeature, /*allowed_origins=*/{}, false, false}}});
+  policy1->SetHeaderPolicy({{{kDefaultOnFeature, /*allowed_origins=*/{},
+                              /*self_if_matches=*/absl::nullopt,
+                              /*matches_all_origins=*/false,
+                              /*matches_opaque_src=*/false}}});
   EXPECT_FALSE(policy1->IsFeatureEnabled(kDefaultOnFeature));
 }
 
@@ -344,8 +375,10 @@
   // Feature should be disabled in child frame.
   std::unique_ptr<PermissionsPolicy> policy1 =
       CreateFromParentPolicy(nullptr, origin_a_);
-  policy1->SetHeaderPolicy(
-      {{{kDefaultOnFeature, /*allowed_origins=*/{}, false, false}}});
+  policy1->SetHeaderPolicy({{{kDefaultOnFeature, /*allowed_origins=*/{},
+                              /*self_if_matches=*/absl::nullopt,
+                              /*matches_all_origins=*/false,
+                              /*matches_opaque_src=*/false}}});
   std::unique_ptr<PermissionsPolicy> policy2 =
       CreateFromParentPolicy(policy1.get(), origin_a_);
   EXPECT_FALSE(policy2->IsFeatureEnabled(kDefaultOnFeature));
@@ -365,8 +398,10 @@
       CreateFromParentPolicy(nullptr, origin_a_);
   std::unique_ptr<PermissionsPolicy> policy2 =
       CreateFromParentPolicy(policy1.get(), origin_b_);
-  policy2->SetHeaderPolicy(
-      {{{kDefaultOnFeature, /*allowed_origins=*/{}, false, false}}});
+  policy2->SetHeaderPolicy({{{kDefaultOnFeature, /*allowed_origins=*/{},
+                              /*self_if_matches=*/absl::nullopt,
+                              /*matches_all_origins=*/false,
+                              /*matches_opaque_src=*/false}}});
   EXPECT_FALSE(policy2->IsFeatureEnabled(kDefaultOnFeature));
 }
 
@@ -389,12 +424,11 @@
       CreateFromParentPolicy(nullptr, origin_a_);
   std::unique_ptr<PermissionsPolicy> policy2 =
       CreateFromParentPolicy(policy1.get(), origin_b_);
-  policy2->SetHeaderPolicy(
-      {{{kDefaultOnFeature, /*allowed_origins=*/
-         {blink::OriginWithPossibleWildcards(origin_b_,
-                                             /*has_subdomain_wildcard=*/false)},
-         false,
-         false}}});
+  policy2->SetHeaderPolicy({{{kDefaultOnFeature, /*allowed_origins=*/
+                              /*allowed_origins=*/{},
+                              /*self_if_matches=*/origin_b_,
+                              /*matches_all_origins=*/false,
+                              /*matches_opaque_src=*/false}}});
   std::unique_ptr<PermissionsPolicy> policy3 =
       CreateFromParentPolicy(policy2.get(), origin_c_);
   EXPECT_TRUE(policy2->IsFeatureEnabled(kDefaultOnFeature));
@@ -413,8 +447,10 @@
   // Default-on feature should be disabled in cross-origin child frame.
   std::unique_ptr<PermissionsPolicy> policy1 =
       CreateFromParentPolicy(nullptr, origin_a_);
-  policy1->SetHeaderPolicy(
-      {{{kDefaultOnFeature, /*allowed_origins=*/{}, false, false}}});
+  policy1->SetHeaderPolicy({{{kDefaultOnFeature, /*allowed_origins=*/{},
+                              /*self_if_matches=*/absl::nullopt,
+                              /*matches_all_origins=*/false,
+                              /*matches_opaque_src=*/false}}});
   std::unique_ptr<PermissionsPolicy> policy2 =
       CreateFromParentPolicy(policy1.get(), origin_b_);
   EXPECT_FALSE(policy2->IsFeatureEnabled(kDefaultOnFeature));
@@ -436,8 +472,10 @@
   // Feature should be enabled in top level; disabled in frame 2 and 3.
   std::unique_ptr<PermissionsPolicy> policy1 =
       CreateFromParentPolicy(nullptr, origin_a_);
-  policy1->SetHeaderPolicy(
-      {{{kDefaultSelfFeature, /*allowed_origins=*/{}, true, false}}});
+  policy1->SetHeaderPolicy({{{kDefaultSelfFeature, /*allowed_origins=*/{},
+                              /*self_if_matches=*/absl::nullopt,
+                              /*matches_all_origins=*/true,
+                              /*matches_opaque_src=*/false}}});
   std::unique_ptr<PermissionsPolicy> policy2 =
       CreateFromParentPolicy(policy1.get(), origin_b_);
   std::unique_ptr<PermissionsPolicy> policy3 =
@@ -464,14 +502,17 @@
   // Feature should be enabled in top and second level; disabled in frame 3.
   std::unique_ptr<PermissionsPolicy> policy1 =
       CreateFromParentPolicy(nullptr, origin_a_);
-  policy1->SetHeaderPolicy(
-      {{{kDefaultSelfFeature, /*allowed_origins=*/{}, true, false}}});
+  policy1->SetHeaderPolicy({{{kDefaultSelfFeature, /*allowed_origins=*/{},
+                              /*self_if_matches=*/absl::nullopt,
+                              /*matches_all_origins=*/true,
+                              /*matches_opaque_src=*/false}}});
   ParsedPermissionsPolicy frame_policy = {
       {{kDefaultSelfFeature, /*allowed_origins=*/
         {blink::OriginWithPossibleWildcards(origin_b_,
                                             /*has_subdomain_wildcard=*/false)},
-        false,
-        false}}};
+        /*self_if_matches=*/absl::nullopt,
+        /*matches_all_origins=*/false,
+        /*matches_opaque_src=*/false}}};
   std::unique_ptr<PermissionsPolicy> policy2 =
       CreateFromParentWithFramePolicy(policy1.get(), frame_policy, origin_b_);
   std::unique_ptr<PermissionsPolicy> policy3 =
@@ -501,8 +542,9 @@
       {{{kDefaultOnFeature, /*allowed_origins=*/
          {blink::OriginWithPossibleWildcards(origin_b_,
                                              /*has_subdomain_wildcard=*/false)},
-         false,
-         false}}});
+         /*self_if_matches=*/absl::nullopt,
+         /*matches_all_origins=*/false,
+         /*matches_opaque_src=*/false}}});
   std::unique_ptr<PermissionsPolicy> policy2 =
       CreateFromParentPolicy(policy1.get(), origin_b_);
   std::unique_ptr<PermissionsPolicy> policy3 =
@@ -533,12 +575,11 @@
       CreateFromParentPolicy(nullptr, origin_a_);
   policy1->SetHeaderPolicy(
       {{{kDefaultOnFeature, /*allowed_origins=*/
-         {blink::OriginWithPossibleWildcards(origin_a_,
-                                             /*has_subdomain_wildcard=*/false),
-          blink::OriginWithPossibleWildcards(origin_b_,
+         {blink::OriginWithPossibleWildcards(origin_b_,
                                              /*has_subdomain_wildcard=*/false)},
-         false,
-         false}}});
+         /*self_if_matches=*/origin_a_,
+         /*matches_all_origins=*/false,
+         /*matches_opaque_src=*/false}}});
   std::unique_ptr<PermissionsPolicy> policy2 =
       CreateFromParentPolicy(policy1.get(), origin_b_);
   std::unique_ptr<PermissionsPolicy> policy3 =
@@ -571,8 +612,9 @@
       {{{kDefaultSelfFeature, /*allowed_origins=*/
          {blink::OriginWithPossibleWildcards(origin_b_,
                                              /*has_subdomain_wildcard=*/false)},
-         false,
-         false}}});
+         /*self_if_matches=*/absl::nullopt,
+         /*matches_all_origins=*/false,
+         /*matches_opaque_src=*/false}}});
   std::unique_ptr<PermissionsPolicy> policy2 =
       CreateFromParentPolicy(policy1.get(), origin_b_);
   std::unique_ptr<PermissionsPolicy> policy3 =
@@ -604,18 +646,16 @@
       CreateFromParentPolicy(nullptr, origin_a_);
   policy1->SetHeaderPolicy(
       {{{kDefaultSelfFeature, /*allowed_origins=*/
-         {blink::OriginWithPossibleWildcards(origin_a_,
-                                             /*has_subdomain_wildcard=*/false),
-          blink::OriginWithPossibleWildcards(origin_b_,
+         {blink::OriginWithPossibleWildcards(origin_b_,
                                              /*has_subdomain_wildcard=*/false)},
-         false,
-         false}}});
+         /*self_if_matches=*/origin_a_,
+         /*matches_all_origins=*/false,
+         /*matches_opaque_src=*/false}}});
   ParsedPermissionsPolicy frame_policy = {
-      {{kDefaultSelfFeature, /*allowed_origins=*/
-        {blink::OriginWithPossibleWildcards(origin_b_,
-                                            /*has_subdomain_wildcard=*/false)},
-        false,
-        false}}};
+      {{kDefaultSelfFeature, /*allowed_origins=*/{},
+        /*self_if_matches=*/origin_b_,
+        /*matches_all_origins=*/false,
+        /*matches_opaque_src=*/false}}};
   std::unique_ptr<PermissionsPolicy> policy2 =
       CreateFromParentWithFramePolicy(policy1.get(), frame_policy, origin_b_);
   std::unique_ptr<PermissionsPolicy> policy3 =
@@ -646,10 +686,15 @@
   // not explicitly delegated.
   std::unique_ptr<PermissionsPolicy> policy1 =
       CreateFromParentPolicy(nullptr, origin_a_);
-  policy1->SetHeaderPolicy(
-      {{{kDefaultSelfFeature, /*allowed_origins=*/{}, true, false}}});
+  policy1->SetHeaderPolicy({{{kDefaultSelfFeature, /*allowed_origins=*/{},
+                              /*self_if_matches=*/absl::nullopt,
+                              /*matches_all_origins=*/true,
+                              /*matches_opaque_src=*/false}}});
   ParsedPermissionsPolicy frame_policy = {
-      {{kDefaultSelfFeature, /*allowed_origins=*/{}, true, true}}};
+      {{kDefaultSelfFeature, /*allowed_origins=*/{},
+        /*self_if_matches=*/absl::nullopt,
+        /*matches_all_origins=*/true,
+        /*matches_opaque_src=*/false}}};
   std::unique_ptr<PermissionsPolicy> policy2 =
       CreateFromParentWithFramePolicy(policy1.get(), frame_policy, origin_b_);
   std::unique_ptr<PermissionsPolicy> policy3 =
@@ -675,16 +720,17 @@
   // Feature should be enabled at the top level; disabled in all other frames.
   std::unique_ptr<PermissionsPolicy> policy1 =
       CreateFromParentPolicy(nullptr, origin_a_);
-  policy1->SetHeaderPolicy(
-      {{{kDefaultSelfFeature, /*allowed_origins=*/
-         {blink::OriginWithPossibleWildcards(origin_a_,
-                                             /*has_subdomain_wildcard=*/false)},
-         false,
-         false}}});
+  policy1->SetHeaderPolicy({{{kDefaultSelfFeature, /*allowed_origins=*/
+                              /*allowed_origins=*/{},
+                              /*self_if_matches=*/origin_a_,
+                              /*matches_all_origins=*/false,
+                              /*matches_opaque_src=*/false}}});
   std::unique_ptr<PermissionsPolicy> policy2 =
       CreateFromParentPolicy(policy1.get(), origin_b_);
-  policy2->SetHeaderPolicy(
-      {{{kDefaultSelfFeature, /*allowed_origins=*/{}, true, false}}});
+  policy2->SetHeaderPolicy({{{kDefaultSelfFeature, /*allowed_origins=*/{},
+                              /*self_if_matches=*/absl::nullopt,
+                              /*matches_all_origins=*/true,
+                              /*matches_opaque_src=*/false}}});
   std::unique_ptr<PermissionsPolicy> policy3 =
       CreateFromParentPolicy(policy2.get(), origin_a_);
   std::unique_ptr<PermissionsPolicy> policy4 =
@@ -714,19 +760,17 @@
   std::unique_ptr<PermissionsPolicy> policy1 =
       CreateFromParentPolicy(nullptr, origin_a_);
   ParsedPermissionsPolicy frame_policy = {
-      {{kDefaultSelfFeature, /*allowed_origins=*/
-        {blink::OriginWithPossibleWildcards(origin_b_,
-                                            /*has_subdomain_wildcard=*/false)},
-        false,
-        false}}};
+      {{kDefaultSelfFeature, /*allowed_origins=*/{},
+        /*self_if_matches=*/origin_b_,
+        /*matches_all_origins=*/false,
+        /*matches_opaque_src=*/false}}};
   std::unique_ptr<PermissionsPolicy> policy2 =
       CreateFromParentWithFramePolicy(policy1.get(), frame_policy, origin_b_);
   ParsedPermissionsPolicy frame_policy2 = {
-      {{kDefaultSelfFeature, /*allowed_origins=*/
-        {blink::OriginWithPossibleWildcards(origin_c_,
-                                            /*has_subdomain_wildcard=*/false)},
-        false,
-        false}}};
+      {{kDefaultSelfFeature, /*allowed_origins=*/{},
+        /*self_if_matches=*/origin_c_,
+        /*matches_all_origins=*/false,
+        /*matches_opaque_src=*/false}}};
   std::unique_ptr<PermissionsPolicy> policy3 =
       CreateFromParentWithFramePolicy(policy2.get(), frame_policy2, origin_c_);
   EXPECT_TRUE(policy1->IsFeatureEnabled(kDefaultSelfFeature));
@@ -752,12 +796,11 @@
       CreateFromParentPolicy(nullptr, origin_a_);
   policy1->SetHeaderPolicy({{
       {kDefaultOnFeature, /*allowed_origins=*/
-       {blink::OriginWithPossibleWildcards(origin_a_,
-                                           /*has_subdomain_wildcard=*/false),
-        blink::OriginWithPossibleWildcards(origin_b_,
+       {blink::OriginWithPossibleWildcards(origin_b_,
                                            /*has_subdomain_wildcard=*/false)},
-       false,
-       false},
+       /*self_if_matches=*/origin_a_,
+       /*matches_all_origins=*/false,
+       /*matches_opaque_src=*/false},
   }});
   std::unique_ptr<PermissionsPolicy> policy2 =
       CreateFromParentPolicy(policy1.get(), origin_b_);
@@ -790,12 +833,11 @@
       CreateFromParentPolicy(nullptr, origin_a_);
   policy1->SetHeaderPolicy(
       {{{kDefaultSelfFeature, /*allowed_origins=*/
-         {blink::OriginWithPossibleWildcards(origin_a_,
-                                             /*has_subdomain_wildcard=*/false),
-          blink::OriginWithPossibleWildcards(origin_b_,
+         {blink::OriginWithPossibleWildcards(origin_b_,
                                              /*has_subdomain_wildcard=*/false)},
-         false,
-         false}}});
+         /*self_if_matches=*/origin_a_,
+         /*matches_all_origins=*/false,
+         /*matches_opaque_src=*/false}}});
   std::unique_ptr<PermissionsPolicy> policy2 =
       CreateFromParentPolicy(policy1.get(), origin_b_);
   std::unique_ptr<PermissionsPolicy> policy3 =
@@ -831,45 +873,40 @@
       CreateFromParentPolicy(nullptr, origin_a_);
   policy1->SetHeaderPolicy(
       {{{kDefaultSelfFeature, /*allowed_origins=*/
-         {blink::OriginWithPossibleWildcards(origin_a_,
-                                             /*has_subdomain_wildcard=*/false),
-          blink::OriginWithPossibleWildcards(origin_b_,
+         {blink::OriginWithPossibleWildcards(origin_b_,
                                              /*has_subdomain_wildcard=*/false)},
-         false,
-         false},
+         /*self_if_matches=*/origin_a_,
+         /*matches_all_origins=*/false,
+         /*matches_opaque_src=*/false},
         {kDefaultOnFeature, /*allowed_origins=*/
-         {blink::OriginWithPossibleWildcards(origin_a_,
-                                             /*has_subdomain_wildcard=*/false)},
-         false,
-         false}}});
+         /*allowed_origins=*/{},
+         /*self_if_matches=*/origin_a_,
+         /*matches_all_origins=*/false,
+         /*matches_opaque_src=*/false}}});
   ParsedPermissionsPolicy frame_policy = {
       {{kDefaultSelfFeature, /*allowed_origins=*/
-        {blink::OriginWithPossibleWildcards(origin_a_,
-                                            /*has_subdomain_wildcard=*/false),
-         blink::OriginWithPossibleWildcards(origin_b_,
+        {blink::OriginWithPossibleWildcards(origin_b_,
                                             /*has_subdomain_wildcard=*/false)},
-        false,
-        false},
-       {kDefaultOnFeature, /*allowed_origins=*/
-        {blink::OriginWithPossibleWildcards(origin_a_,
-                                            /*has_subdomain_wildcard=*/false)},
-        false,
-        false}}};
+        /*self_if_matches=*/origin_a_,
+        /*matches_all_origins=*/false,
+        /*matches_opaque_src=*/false},
+       {kDefaultOnFeature, /*allowed_origins=*/{},
+        /*self_if_matches=*/origin_a_,
+        /*matches_all_origins=*/false,
+        /*matches_opaque_src=*/false}}};
   std::unique_ptr<PermissionsPolicy> policy2 =
       CreateFromParentWithFramePolicy(policy1.get(), frame_policy, origin_b_);
   ParsedPermissionsPolicy frame_policy2 = {
       {{kDefaultSelfFeature, /*allowed_origins=*/
-        {blink::OriginWithPossibleWildcards(origin_a_,
-                                            /*has_subdomain_wildcard=*/false),
-         blink::OriginWithPossibleWildcards(origin_c_,
+        {blink::OriginWithPossibleWildcards(origin_c_,
                                             /*has_subdomain_wildcard=*/false)},
-        false,
-        false},
-       {kDefaultOnFeature, /*allowed_origins=*/
-        {blink::OriginWithPossibleWildcards(origin_b_,
-                                            /*has_subdomain_wildcard=*/false)},
-        false,
-        false}}};
+        /*self_if_matches=*/origin_a_,
+        /*matches_all_origins=*/false,
+        /*matches_opaque_src=*/false},
+       {kDefaultOnFeature, /*allowed_origins=*/{},
+        /*self_if_matches=*/origin_b_,
+        /*matches_all_origins=*/false,
+        /*matches_opaque_src=*/false}}};
   std::unique_ptr<PermissionsPolicy> policy3 =
       CreateFromParentWithFramePolicy(policy2.get(), frame_policy2, origin_c_);
   EXPECT_TRUE(policy1->IsFeatureEnabled(kDefaultSelfFeature));
@@ -903,8 +940,9 @@
       {{kDefaultSelfFeature, /*allowed_origins=*/
         {blink::OriginWithPossibleWildcards(origin_b_,
                                             /*has_subdomain_wildcard=*/false)},
-        false,
-        false}}};
+        /*self_if_matches=*/absl::nullopt,
+        /*matches_all_origins=*/false,
+        /*matches_opaque_src=*/false}}};
   std::unique_ptr<PermissionsPolicy> policy2 =
       CreateFromParentWithFramePolicy(policy1.get(), frame_policy, origin_b_);
   EXPECT_TRUE(
@@ -931,7 +969,10 @@
   std::unique_ptr<PermissionsPolicy> policy1 =
       CreateFromParentPolicy(nullptr, origin_a_);
   ParsedPermissionsPolicy frame_policy = {
-      {{kDefaultSelfFeature, /*allowed_origins=*/{}, true, false}}};
+      {{kDefaultSelfFeature, /*allowed_origins=*/{},
+        /*self_if_matches=*/absl::nullopt,
+        /*matches_all_origins=*/true,
+        /*matches_opaque_src=*/false}}};
   std::unique_ptr<PermissionsPolicy> policy2 =
       CreateFromParentWithFramePolicy(policy1.get(), frame_policy, origin_b_);
   EXPECT_TRUE(
@@ -972,8 +1013,9 @@
       {kDefaultSelfFeature, /*allowed_origins=*/
        {blink::OriginWithPossibleWildcards(origin_b_,
                                            /*has_subdomain_wildcard=*/false)},
-       false,
-       false},
+       /*self_if_matches=*/absl::nullopt,
+       /*matches_all_origins=*/false,
+       /*matches_opaque_src=*/false},
   }};
   std::unique_ptr<PermissionsPolicy> policy2 =
       CreateFromParentWithFramePolicy(policy1.get(), frame_policy1, origin_b_);
@@ -981,8 +1023,9 @@
       {kDefaultSelfFeature, /*allowed_origins=*/
        {blink::OriginWithPossibleWildcards(origin_c_,
                                            /*has_subdomain_wildcard=*/false)},
-       false,
-       false},
+       /*self_if_matches=*/absl::nullopt,
+       /*matches_all_origins=*/false,
+       /*matches_opaque_src=*/false},
   }};
   std::unique_ptr<PermissionsPolicy> policy3 =
       CreateFromParentWithFramePolicy(policy2.get(), frame_policy2, origin_c_);
@@ -1020,11 +1063,17 @@
   std::unique_ptr<PermissionsPolicy> policy1 =
       CreateFromParentPolicy(nullptr, origin_a_);
   ParsedPermissionsPolicy frame_policy1 = {
-      {{kDefaultOnFeature, /*allowed_origins=*/{}, false, false}}};
+      {{kDefaultOnFeature, /*allowed_origins=*/{},
+        /*self_if_matches=*/absl::nullopt,
+        /*matches_all_origins=*/false,
+        /*matches_opaque_src=*/false}}};
   std::unique_ptr<PermissionsPolicy> policy2 =
       CreateFromParentWithFramePolicy(policy1.get(), frame_policy1, origin_a_);
   ParsedPermissionsPolicy frame_policy2 = {
-      {{kDefaultOnFeature, /*allowed_origins=*/{}, false, false}}};
+      {{kDefaultOnFeature, /*allowed_origins=*/{},
+        /*self_if_matches=*/absl::nullopt,
+        /*matches_all_origins=*/false,
+        /*matches_opaque_src=*/false}}};
   std::unique_ptr<PermissionsPolicy> policy3 =
       CreateFromParentWithFramePolicy(policy1.get(), frame_policy2, origin_b_);
   EXPECT_TRUE(policy1->IsFeatureEnabledForOrigin(kDefaultOnFeature, origin_a_));
@@ -1069,28 +1118,32 @@
       CreateFromParentPolicy(nullptr, origin_a_);
   policy1->SetHeaderPolicy({{
       {kDefaultSelfFeature, /*allowed_origins=*/
-       {blink::OriginWithPossibleWildcards(origin_a_,
-                                           /*has_subdomain_wildcard=*/false),
-        blink::OriginWithPossibleWildcards(origin_b_,
+       {blink::OriginWithPossibleWildcards(origin_b_,
                                            /*has_subdomain_wildcard=*/false)},
-       false,
-       false},
+       /*self_if_matches=*/origin_a_,
+       /*matches_all_origins=*/false,
+       /*matches_opaque_src=*/false},
   }});
   ParsedPermissionsPolicy frame_policy1 = {
-      {{kDefaultSelfFeature, /*allowed_origins=*/{}, false, false}}};
+      {{kDefaultSelfFeature, /*allowed_origins=*/{},
+        /*self_if_matches=*/absl::nullopt,
+        /*matches_all_origins=*/false,
+        /*matches_opaque_src=*/false}}};
   std::unique_ptr<PermissionsPolicy> policy2 =
       CreateFromParentWithFramePolicy(policy1.get(), frame_policy1, origin_b_);
   ParsedPermissionsPolicy frame_policy2 = {{
-      {kDefaultSelfFeature, /*allowed_origins=*/{}, false, false},
+      {kDefaultSelfFeature, /*allowed_origins=*/{},
+       /*self_if_matches=*/absl::nullopt,
+       /*matches_all_origins=*/false,
+       /*matches_opaque_src=*/false},
   }};
   std::unique_ptr<PermissionsPolicy> policy3 =
       CreateFromParentWithFramePolicy(policy1.get(), frame_policy2, origin_b_);
   policy3->SetHeaderPolicy({{
-      {kDefaultSelfFeature, /*allowed_origins=*/
-       {blink::OriginWithPossibleWildcards(origin_b_,
-                                           /*has_subdomain_wildcard=*/false)},
-       false,
-       false},
+      {kDefaultSelfFeature, /*allowed_origins=*/{},
+       /*self_if_matches=*/origin_b_,
+       /*matches_all_origins=*/false,
+       /*matches_opaque_src=*/false},
   }});
   EXPECT_FALSE(
       policy2->IsFeatureEnabledForOrigin(kDefaultSelfFeature, origin_b_));
@@ -1129,14 +1182,20 @@
       {{kDefaultSelfFeature, /*allowed_origins=*/
         {blink::OriginWithPossibleWildcards(origin_b_,
                                             /*has_subdomain_wildcard=*/false)},
-        false,
-        false}}};
+        /*self_if_matches=*/absl::nullopt,
+        /*matches_all_origins=*/false,
+        /*matches_opaque_src=*/false}}};
   std::unique_ptr<PermissionsPolicy> policy2 =
       CreateFromParentWithFramePolicy(policy1.get(), frame_policy1, origin_b_);
-  policy2->SetHeaderPolicy(
-      {{{kDefaultSelfFeature, /*allowed_origins=*/{}, true, false}}});
+  policy2->SetHeaderPolicy({{{kDefaultSelfFeature, /*allowed_origins=*/{},
+                              /*self_if_matches=*/absl::nullopt,
+                              /*matches_all_origins=*/true,
+                              /*matches_opaque_src=*/false}}});
   ParsedPermissionsPolicy frame_policy2 = {
-      {{kDefaultSelfFeature, /*allowed_origins=*/{}, false, false}}};
+      {{kDefaultSelfFeature, /*allowed_origins=*/{},
+        /*self_if_matches=*/absl::nullopt,
+        /*matches_all_origins=*/false,
+        /*matches_opaque_src=*/false}}};
   std::unique_ptr<PermissionsPolicy> policy3 =
       CreateFromParentWithFramePolicy(policy2.get(), frame_policy2, origin_c_);
   std::unique_ptr<PermissionsPolicy> policy4 =
@@ -1170,19 +1229,26 @@
   std::unique_ptr<PermissionsPolicy> policy1 =
       CreateFromParentPolicy(nullptr, origin_a_);
   policy1->SetHeaderPolicy({{
-      {kDefaultSelfFeature, /*allowed_origins=*/{}, false, false},
+      {kDefaultSelfFeature, /*allowed_origins=*/{},
+       /*self_if_matches=*/absl::nullopt,
+       /*matches_all_origins=*/false,
+       /*matches_opaque_src=*/false},
   }});
   ParsedPermissionsPolicy frame_policy1 = {{
       {kDefaultSelfFeature, /*allowed_origins=*/
        {blink::OriginWithPossibleWildcards(origin_b_,
                                            /*has_subdomain_wildcard=*/false)},
-       false,
-       false},
+       /*self_if_matches=*/absl::nullopt,
+       /*matches_all_origins=*/false,
+       /*matches_opaque_src=*/false},
   }};
   std::unique_ptr<PermissionsPolicy> policy2 =
       CreateFromParentWithFramePolicy(policy1.get(), frame_policy1, origin_b_);
   ParsedPermissionsPolicy frame_policy2 = {
-      {{kDefaultSelfFeature, /*allowed_origins=*/{}, true, false}}};
+      {{kDefaultSelfFeature, /*allowed_origins=*/{},
+        /*self_if_matches=*/absl::nullopt,
+        /*matches_all_origins=*/true,
+        /*matches_opaque_src=*/false}}};
   std::unique_ptr<PermissionsPolicy> policy3 =
       CreateFromParentWithFramePolicy(policy1.get(), frame_policy2, origin_a_);
   EXPECT_FALSE(
@@ -1222,30 +1288,34 @@
       CreateFromParentPolicy(nullptr, origin_a_);
   policy1->SetHeaderPolicy(
       {{{kDefaultSelfFeature, /*allowed_origins=*/
-         {blink::OriginWithPossibleWildcards(origin_a_,
-                                             /*has_subdomain_wildcard=*/false),
-          blink::OriginWithPossibleWildcards(origin_b_,
+         {blink::OriginWithPossibleWildcards(origin_b_,
                                              /*has_subdomain_wildcard=*/false)},
-         false,
-         false}}});
+         /*self_if_matches=*/origin_a_,
+         /*matches_all_origins=*/false,
+         /*matches_opaque_src=*/false}}});
   ParsedPermissionsPolicy frame_policy1 = {
       {{kDefaultSelfFeature, /*allowed_origins=*/
         {blink::OriginWithPossibleWildcards(origin_a_,
                                             /*has_subdomain_wildcard=*/false)},
-        false,
-        false}}};
+        /*self_if_matches=*/absl::nullopt,
+        /*matches_all_origins=*/false,
+        /*matches_opaque_src=*/false}}};
   std::unique_ptr<PermissionsPolicy> policy2 =
       CreateFromParentWithFramePolicy(policy1.get(), frame_policy1, origin_b_);
   ParsedPermissionsPolicy frame_policy2 = {
       {{kDefaultSelfFeature, /*allowed_origins=*/
         {blink::OriginWithPossibleWildcards(origin_b_,
                                             /*has_subdomain_wildcard=*/false)},
-        false,
-        false}}};
+        /*self_if_matches=*/absl::nullopt,
+        /*matches_all_origins=*/false,
+        /*matches_opaque_src=*/false}}};
   std::unique_ptr<PermissionsPolicy> policy3 =
       CreateFromParentWithFramePolicy(policy1.get(), frame_policy2, origin_b_);
   ParsedPermissionsPolicy frame_policy3 = {
-      {{kDefaultSelfFeature, /*allowed_origins=*/{}, true, false}}};
+      {{kDefaultSelfFeature, /*allowed_origins=*/{},
+        /*self_if_matches=*/absl::nullopt,
+        /*matches_all_origins=*/true,
+        /*matches_opaque_src=*/false}}};
   std::unique_ptr<PermissionsPolicy> policy4 =
       CreateFromParentWithFramePolicy(policy1.get(), frame_policy3, origin_b_);
   EXPECT_TRUE(
@@ -1307,7 +1377,42 @@
       CreateFromParentPolicy(nullptr, origin_a_);
   url::Origin sandboxed_origin = url::Origin();
   ParsedPermissionsPolicy frame_policy = {
-      {{kDefaultSelfFeature, /*allowed_origins=*/{}, true, true}}};
+      {{kDefaultSelfFeature, /*allowed_origins=*/{},
+        /*self_if_matches=*/absl::nullopt,
+        /*matches_all_origins=*/true,
+        /*matches_opaque_src=*/true}}};
+  std::unique_ptr<PermissionsPolicy> policy2 = CreateFromParentWithFramePolicy(
+      policy1.get(), frame_policy, sandboxed_origin);
+  EXPECT_TRUE(policy2->IsFeatureEnabledForOrigin(kDefaultOnFeature, origin_a_));
+  EXPECT_TRUE(
+      policy2->IsFeatureEnabledForOrigin(kDefaultOnFeature, sandboxed_origin));
+  EXPECT_TRUE(policy2->IsFeatureEnabled(kDefaultSelfFeature));
+  EXPECT_TRUE(policy2->IsFeatureEnabledForOrigin(kDefaultSelfFeature,
+                                                 sandboxed_origin));
+}
+
+TEST_F(PermissionsPolicyTest, TestSandboxedFramePolicyForSelf) {
+  // +-------------------------------------------+
+  // |(1)Origin A                                |
+  // |No Policy                                  |
+  // |                                           |
+  // |<iframe sandbox allow="default-self self"> |
+  // | +-------------+                           |
+  // | |(2)Sandboxed |                           |
+  // | |No Policy    |                           |
+  // | +-------------+                           |
+  // +-------------------------------------------+
+  // Default-self feature should be enabled in child frame with opaque origin,
+  // only for that origin, because container policy matches all origins.
+  // However, it will not pass that on to any other origin
+  std::unique_ptr<PermissionsPolicy> policy1 =
+      CreateFromParentPolicy(nullptr, origin_a_);
+  url::Origin sandboxed_origin = url::Origin();
+  ParsedPermissionsPolicy frame_policy = {
+      {{kDefaultSelfFeature, /*allowed_origins=*/{},
+        /*self_if_matches=*/sandboxed_origin,
+        /*matches_all_origins=*/true,
+        /*matches_opaque_src=*/true}}};
   std::unique_ptr<PermissionsPolicy> policy2 = CreateFromParentWithFramePolicy(
       policy1.get(), frame_policy, sandboxed_origin);
   EXPECT_TRUE(policy2->IsFeatureEnabledForOrigin(kDefaultOnFeature, origin_a_));
@@ -1336,7 +1441,10 @@
       CreateFromParentPolicy(nullptr, origin_a_);
   url::Origin sandboxed_origin = url::Origin();
   ParsedPermissionsPolicy frame_policy = {
-      {{kDefaultSelfFeature, /*allowed_origins=*/{}, false, true}}};
+      {{kDefaultSelfFeature, /*allowed_origins=*/{},
+        /*self_if_matches=*/absl::nullopt,
+        /*matches_all_origins=*/false,
+        /*matches_opaque_src=*/true}}};
   std::unique_ptr<PermissionsPolicy> policy2 = CreateFromParentWithFramePolicy(
       policy1.get(), frame_policy, sandboxed_origin);
   EXPECT_TRUE(policy2->IsFeatureEnabledForOrigin(kDefaultOnFeature, origin_a_));
@@ -1362,11 +1470,16 @@
   // policy.
   std::unique_ptr<PermissionsPolicy> policy1 =
       CreateFromParentPolicy(nullptr, origin_a_);
-  policy1->SetHeaderPolicy(
-      {{{kDefaultSelfFeature, /*allowed_origins=*/{}, true, false}}});
+  policy1->SetHeaderPolicy({{{kDefaultSelfFeature, /*allowed_origins=*/{},
+                              /*self_if_matches=*/absl::nullopt,
+                              /*matches_all_origins=*/true,
+                              /*matches_opaque_src=*/false}}});
   url::Origin sandboxed_origin = url::Origin();
   ParsedPermissionsPolicy frame_policy = {
-      {{kDefaultSelfFeature, /*allowed_origins=*/{}, false, true}}};
+      {{kDefaultSelfFeature, /*allowed_origins=*/{},
+        /*self_if_matches=*/absl::nullopt,
+        /*matches_all_origins=*/false,
+        /*matches_opaque_src=*/true}}};
   std::unique_ptr<PermissionsPolicy> policy2 = CreateFromParentWithFramePolicy(
       policy1.get(), frame_policy, sandboxed_origin);
   EXPECT_FALSE(policy2->IsFeatureEnabled(kDefaultSelfFeature));
@@ -1397,7 +1510,10 @@
   url::Origin sandboxed_origin_1 = url::Origin();
   url::Origin sandboxed_origin_2 = url::Origin();
   ParsedPermissionsPolicy frame_policy = {
-      {{kDefaultSelfFeature, /*allowed_origins=*/{}, true, false}}};
+      {{kDefaultSelfFeature, /*allowed_origins=*/{},
+        /*self_if_matches=*/absl::nullopt,
+        /*matches_all_origins=*/true,
+        /*matches_opaque_src=*/false}}};
   std::unique_ptr<PermissionsPolicy> policy2 = CreateFromParentWithFramePolicy(
       policy1.get(), frame_policy, sandboxed_origin_1);
   std::unique_ptr<PermissionsPolicy> policy3 =
@@ -1441,11 +1557,17 @@
   url::Origin sandboxed_origin_1 = origin_a_.DeriveNewOpaqueOrigin();
   url::Origin sandboxed_origin_2 = sandboxed_origin_1.DeriveNewOpaqueOrigin();
   ParsedPermissionsPolicy frame_policy_1 = {
-      {{kDefaultSelfFeature, /*allowed_origins=*/{}, true, true}}};
+      {{kDefaultSelfFeature, /*allowed_origins=*/{},
+        /*self_if_matches=*/absl::nullopt,
+        /*matches_all_origins=*/true,
+        /*matches_opaque_src=*/true}}};
   std::unique_ptr<PermissionsPolicy> policy2 = CreateFromParentWithFramePolicy(
       policy1.get(), frame_policy_1, sandboxed_origin_1);
   ParsedPermissionsPolicy frame_policy_2 = {
-      {{kDefaultSelfFeature, /*allowed_origins=*/{}, true, true}}};
+      {{kDefaultSelfFeature, /*allowed_origins=*/{},
+        /*self_if_matches=*/absl::nullopt,
+        /*matches_all_origins=*/true,
+        /*matches_opaque_src=*/true}}};
   std::unique_ptr<PermissionsPolicy> policy3 = CreateFromParentWithFramePolicy(
       policy2.get(), frame_policy_2, sandboxed_origin_2);
   EXPECT_TRUE(policy3->IsFeatureEnabledForOrigin(kDefaultOnFeature, origin_a_));
@@ -1473,8 +1595,13 @@
       CreateFromParentPolicy(nullptr, origin_a_);
   ParsedPermissionsPolicy frame_policy = {
       {{mojom::PermissionsPolicyFeature::kNotFound, /*allowed_origins=*/{},
-        false, true},
-       {kUnavailableFeature, /*allowed_origins=*/{}, false, true}}};
+        /*self_if_matches=*/absl::nullopt,
+        /*matches_all_origins=*/false,
+        /*matches_opaque_src=*/true},
+       {kUnavailableFeature, /*allowed_origins=*/{},
+        /*self_if_matches=*/absl::nullopt,
+        /*matches_all_origins=*/false,
+        /*matches_opaque_src=*/true}}};
   std::unique_ptr<PermissionsPolicy> policy2 =
       CreateFromParentWithFramePolicy(policy1.get(), frame_policy, origin_b_);
   EXPECT_FALSE(PolicyContainsInheritedValue(
@@ -1540,11 +1667,9 @@
 
     std::unique_ptr<PermissionsPolicy> policy =
         CreateFromParentPolicy(nullptr, origin_a_);
-    policy->SetHeaderPolicy({{{mojom::PermissionsPolicyFeature::
-                                   kBrowsingTopics, /*allowed_origins=*/
-                               {blink::OriginWithPossibleWildcards(
-                                   origin_a_,
-                                   /*has_subdomain_wildcard=*/false)},
+    policy->SetHeaderPolicy({{{mojom::PermissionsPolicyFeature::kBrowsingTopics,
+                               /*allowed_origins=*/{},
+                               /*self_if_matches=*/origin_a_,
                                /*matches_all_origins=*/false,
                                /*matches_opaque_src=*/false}}});
 
@@ -1572,9 +1697,9 @@
 
     std::unique_ptr<PermissionsPolicy> policy =
         CreateFromParentPolicy(nullptr, origin_a_);
-    policy->SetHeaderPolicy({{{mojom::PermissionsPolicyFeature::
-                                   kBrowsingTopics, /*allowed_origins=*/
-                               {},
+    policy->SetHeaderPolicy({{{mojom::PermissionsPolicyFeature::kBrowsingTopics,
+                               /*allowed_origins=*/{},
+                               /*self_if_matches=*/absl::nullopt,
                                /*matches_all_origins=*/false,
                                /*matches_opaque_src=*/false}}});
 
@@ -1602,9 +1727,9 @@
 
     std::unique_ptr<PermissionsPolicy> policy =
         CreateFromParentPolicy(nullptr, origin_a_);
-    policy->SetHeaderPolicy({{{mojom::PermissionsPolicyFeature::
-                                   kBrowsingTopics, /*allowed_origins=*/
-                               {},
+    policy->SetHeaderPolicy({{{mojom::PermissionsPolicyFeature::kBrowsingTopics,
+                               /*allowed_origins=*/{},
+                               /*self_if_matches=*/absl::nullopt,
                                /*matches_all_origins=*/true,
                                /*matches_opaque_src=*/false}}});
 
@@ -1638,6 +1763,7 @@
                                {blink::OriginWithPossibleWildcards(
                                    origin_b_,
                                    /*has_subdomain_wildcard=*/false)},
+                               /*self_if_matches=*/absl::nullopt,
                                /*matches_all_origins=*/false,
                                /*matches_opaque_src=*/false}}});
 
@@ -1728,8 +1854,10 @@
   // all child frames, regardless of any declared frame policies.
   std::unique_ptr<PermissionsPolicy> policy1 =
       CreateFromParentPolicy(nullptr, origin_a_);
-  policy1->SetHeaderPolicy(
-      {{{kDefaultSelfFeature, /*allowed_origins=*/{}, false, false}}});
+  policy1->SetHeaderPolicy({{{kDefaultSelfFeature, /*allowed_origins=*/{},
+                              /*self_if_matches=*/absl::nullopt,
+                              /*matches_all_origins=*/false,
+                              /*matches_opaque_src=*/false}}});
   std::unique_ptr<PermissionsPolicy> policy2 =
       CreateFromParentPolicy(policy1.get(), origin_a_);
   EXPECT_FALSE(policy2->IsFeatureEnabled(kDefaultSelfFeature));
@@ -1739,7 +1867,10 @@
   EXPECT_FALSE(policy3->IsFeatureEnabled(kDefaultSelfFeature));
 
   ParsedPermissionsPolicy frame_policy4 = {
-      {{kDefaultSelfFeature, /*allowed_origins=*/{}, true, false}}};
+      {{kDefaultSelfFeature, /*allowed_origins=*/{},
+        /*self_if_matches=*/absl::nullopt,
+        /*matches_all_origins=*/true,
+        /*matches_opaque_src=*/false}}};
   std::unique_ptr<PermissionsPolicy> policy4 =
       CreateFromParentWithFramePolicy(policy1.get(), frame_policy4, origin_b_);
   EXPECT_FALSE(policy4->IsFeatureEnabled(kDefaultSelfFeature));
@@ -1748,8 +1879,9 @@
       {{kDefaultSelfFeature, /*allowed_origins=*/
         {blink::OriginWithPossibleWildcards(origin_b_,
                                             /*has_subdomain_wildcard=*/false)},
-        false,
-        false}}};
+        /*self_if_matches=*/absl::nullopt,
+        /*matches_all_origins=*/false,
+        /*matches_opaque_src=*/false}}};
   std::unique_ptr<PermissionsPolicy> policy5 =
       CreateFromParentWithFramePolicy(policy1.get(), frame_policy5, origin_b_);
   EXPECT_FALSE(policy5->IsFeatureEnabled(kDefaultSelfFeature));
@@ -1758,8 +1890,9 @@
       {{kDefaultSelfFeature, /*allowed_origins=*/
         {blink::OriginWithPossibleWildcards(origin_c_,
                                             /*has_subdomain_wildcard=*/false)},
-        false,
-        false}}};
+        /*self_if_matches=*/absl::nullopt,
+        /*matches_all_origins=*/false,
+        /*matches_opaque_src=*/false}}};
   std::unique_ptr<PermissionsPolicy> policy6 =
       CreateFromParentWithFramePolicy(policy1.get(), frame_policy6, origin_b_);
   EXPECT_FALSE(policy6->IsFeatureEnabled(kDefaultSelfFeature));
@@ -1795,12 +1928,10 @@
   // the frame policy declares that the feature should be allowed.)
   std::unique_ptr<PermissionsPolicy> policy1 =
       CreateFromParentPolicy(nullptr, origin_a_);
-  policy1->SetHeaderPolicy(
-      {{{kDefaultSelfFeature, /*allowed_origins=*/
-         {blink::OriginWithPossibleWildcards(origin_a_,
-                                             /*has_subdomain_wildcard=*/false)},
-         false,
-         false}}});
+  policy1->SetHeaderPolicy({{{kDefaultSelfFeature, /*allowed_origins=*/{},
+                              /*self_if_matches=*/origin_a_,
+                              /*matches_all_origins=*/false,
+                              /*matches_opaque_src=*/false}}});
 
   std::unique_ptr<PermissionsPolicy> policy2 =
       CreateFromParentPolicy(policy1.get(), origin_a_);
@@ -1812,7 +1943,10 @@
 
   // This is a critical change from the existing semantics.
   ParsedPermissionsPolicy frame_policy4 = {
-      {{kDefaultSelfFeature, /*allowed_origins=*/{}, true, false}}};
+      {{kDefaultSelfFeature, /*allowed_origins=*/{},
+        /*self_if_matches=*/absl::nullopt,
+        /*matches_all_origins=*/true,
+        /*matches_opaque_src=*/false}}};
   std::unique_ptr<PermissionsPolicy> policy4 =
       CreateFromParentWithFramePolicy(policy1.get(), frame_policy4, origin_b_);
   EXPECT_FALSE(policy4->IsFeatureEnabled(kDefaultSelfFeature));
@@ -1822,8 +1956,9 @@
       {{kDefaultSelfFeature, /*allowed_origins=*/
         {blink::OriginWithPossibleWildcards(origin_b_,
                                             /*has_subdomain_wildcard=*/false)},
-        false,
-        false}}};
+        /*self_if_matches=*/absl::nullopt,
+        /*matches_all_origins=*/true,
+        /*matches_opaque_src=*/false}}};
   std::unique_ptr<PermissionsPolicy> policy5 =
       CreateFromParentWithFramePolicy(policy1.get(), frame_policy5, origin_b_);
   EXPECT_FALSE(policy5->IsFeatureEnabled(kDefaultSelfFeature));
@@ -1832,8 +1967,9 @@
       {{kDefaultSelfFeature, /*allowed_origins=*/
         {blink::OriginWithPossibleWildcards(origin_c_,
                                             /*has_subdomain_wildcard=*/false)},
-        false,
-        false}}};
+        /*self_if_matches=*/absl::nullopt,
+        /*matches_all_origins=*/true,
+        /*matches_opaque_src=*/false}}};
   std::unique_ptr<PermissionsPolicy> policy6 =
       CreateFromParentWithFramePolicy(policy1.get(), frame_policy6, origin_b_);
   EXPECT_FALSE(policy6->IsFeatureEnabled(kDefaultSelfFeature));
@@ -1872,12 +2008,11 @@
       CreateFromParentPolicy(nullptr, origin_a_);
   policy1->SetHeaderPolicy(
       {{{kDefaultSelfFeature, /*allowed_origins=*/
-         {blink::OriginWithPossibleWildcards(origin_a_,
-                                             /*has_subdomain_wildcard=*/false),
-          blink::OriginWithPossibleWildcards(origin_b_,
+         {blink::OriginWithPossibleWildcards(origin_b_,
                                              /*has_subdomain_wildcard=*/false)},
-         false,
-         false}}});
+         /*self_if_matches=*/origin_a_,
+         /*matches_all_origins=*/true,
+         /*matches_opaque_src=*/false}}});
 
   std::unique_ptr<PermissionsPolicy> policy2 =
       CreateFromParentPolicy(policy1.get(), origin_a_);
@@ -1889,7 +2024,10 @@
   EXPECT_FALSE(policy3->IsFeatureEnabled(kDefaultSelfFeature));
 
   ParsedPermissionsPolicy frame_policy4 = {
-      {{kDefaultSelfFeature, /*allowed_origins=*/{}, true, false}}};
+      {{kDefaultSelfFeature, /*allowed_origins=*/{},
+        /*self_if_matches=*/absl::nullopt,
+        /*matches_all_origins=*/true,
+        /*matches_opaque_src=*/false}}};
   std::unique_ptr<PermissionsPolicy> policy4 =
       CreateFromParentWithFramePolicy(policy1.get(), frame_policy4, origin_b_);
   EXPECT_TRUE(policy4->IsFeatureEnabled(kDefaultSelfFeature));
@@ -1898,8 +2036,9 @@
       {{kDefaultSelfFeature, /*allowed_origins=*/
         {blink::OriginWithPossibleWildcards(origin_b_,
                                             /*has_subdomain_wildcard=*/false)},
-        false,
-        false}}};
+        /*self_if_matches=*/absl::nullopt,
+        /*matches_all_origins=*/false,
+        /*matches_opaque_src=*/false}}};
   std::unique_ptr<PermissionsPolicy> policy5 =
       CreateFromParentWithFramePolicy(policy1.get(), frame_policy5, origin_b_);
   EXPECT_TRUE(policy5->IsFeatureEnabled(kDefaultSelfFeature));
@@ -1908,8 +2047,9 @@
       {{kDefaultSelfFeature, /*allowed_origins=*/
         {blink::OriginWithPossibleWildcards(origin_c_,
                                             /*has_subdomain_wildcard=*/false)},
-        false,
-        false}}};
+        /*self_if_matches=*/absl::nullopt,
+        /*matches_all_origins=*/false,
+        /*matches_opaque_src=*/false}}};
   std::unique_ptr<PermissionsPolicy> policy6 =
       CreateFromParentWithFramePolicy(policy1.get(), frame_policy6, origin_b_);
   EXPECT_FALSE(policy6->IsFeatureEnabled(kDefaultSelfFeature));
@@ -1946,8 +2086,10 @@
   // feature explicitly.)
   std::unique_ptr<PermissionsPolicy> policy1 =
       CreateFromParentPolicy(nullptr, origin_a_);
-  policy1->SetHeaderPolicy(
-      {{{kDefaultSelfFeature, /*allowed_origins=*/{}, true, false}}});
+  policy1->SetHeaderPolicy({{{kDefaultSelfFeature, /*allowed_origins=*/{},
+                              /*self_if_matches=*/absl::nullopt,
+                              /*matches_all_origins=*/true,
+                              /*matches_opaque_src=*/false}}});
 
   std::unique_ptr<PermissionsPolicy> policy2 =
       CreateFromParentPolicy(policy1.get(), origin_a_);
@@ -1959,7 +2101,10 @@
   EXPECT_FALSE(policy3->IsFeatureEnabled(kDefaultSelfFeature));
 
   ParsedPermissionsPolicy frame_policy4 = {
-      {{kDefaultSelfFeature, /*allowed_origins=*/{}, true, false}}};
+      {{kDefaultSelfFeature, /*allowed_origins=*/{},
+        /*self_if_matches=*/absl::nullopt,
+        /*matches_all_origins=*/true,
+        /*matches_opaque_src=*/false}}};
   std::unique_ptr<PermissionsPolicy> policy4 =
       CreateFromParentWithFramePolicy(policy1.get(), frame_policy4, origin_b_);
   EXPECT_TRUE(policy4->IsFeatureEnabled(kDefaultSelfFeature));
@@ -1968,8 +2113,9 @@
       {{kDefaultSelfFeature, /*allowed_origins=*/
         {blink::OriginWithPossibleWildcards(origin_b_,
                                             /*has_subdomain_wildcard=*/false)},
-        false,
-        false}}};
+        /*self_if_matches=*/absl::nullopt,
+        /*matches_all_origins=*/false,
+        /*matches_opaque_src=*/false}}};
   std::unique_ptr<PermissionsPolicy> policy5 =
       CreateFromParentWithFramePolicy(policy1.get(), frame_policy5, origin_b_);
   EXPECT_TRUE(policy5->IsFeatureEnabled(kDefaultSelfFeature));
@@ -1978,8 +2124,9 @@
       {{kDefaultSelfFeature, /*allowed_origins=*/
         {blink::OriginWithPossibleWildcards(origin_c_,
                                             /*has_subdomain_wildcard=*/false)},
-        false,
-        false}}};
+        /*self_if_matches=*/absl::nullopt,
+        /*matches_all_origins=*/false,
+        /*matches_opaque_src=*/false}}};
   std::unique_ptr<PermissionsPolicy> policy6 =
       CreateFromParentWithFramePolicy(policy1.get(), frame_policy6, origin_b_);
   EXPECT_FALSE(policy6->IsFeatureEnabled(kDefaultSelfFeature));
@@ -2006,12 +2153,11 @@
       CreateFromParentPolicy(nullptr, origin_a_);
   policy1->SetHeaderPolicy(
       {{{kDefaultSelfFeature, /*allowed_origins=*/
-         {blink::OriginWithPossibleWildcards(origin_a_,
-                                             /*has_subdomain_wildcard=*/false),
-          blink::OriginWithPossibleWildcards(origin_b_,
+         {blink::OriginWithPossibleWildcards(origin_b_,
                                              /*has_subdomain_wildcard=*/false)},
-         false,
-         false}}});
+         /*self_if_matches=*/origin_a_,
+         /*matches_all_origins=*/false,
+         /*matches_opaque_src=*/false}}});
 
   // This is where the change first occurs.
   std::unique_ptr<PermissionsPolicy> policy2 =
@@ -2020,7 +2166,10 @@
 
   // The proposed value in frame 2 should affect the proposed value in frame 3.
   ParsedPermissionsPolicy frame_policy3 = {
-      {{kDefaultSelfFeature, /*allowed_origins=*/{}, true, false}}};
+      {{kDefaultSelfFeature, /*allowed_origins=*/{},
+        /*self_if_matches=*/absl::nullopt,
+        /*matches_all_origins=*/true,
+        /*matches_opaque_src=*/false}}};
   std::unique_ptr<PermissionsPolicy> policy3 =
       CreateFromParentWithFramePolicy(policy2.get(), frame_policy3, origin_b_);
   EXPECT_FALSE(policy3->IsFeatureEnabled(kDefaultSelfFeature));
@@ -2055,8 +2204,9 @@
                                             /*has_subdomain_wildcard=*/false),
          blink::OriginWithPossibleWildcards(origin_b_,
                                             /*has_subdomain_wildcard=*/false)},
-        false,
-        false}}};
+        /*self_if_matches=*/absl::nullopt,
+        /*matches_all_origins=*/false,
+        /*matches_opaque_src=*/false}}};
   auto policy = CreateFromParsedPolicy(parsed_policy, origin_a_);
   EXPECT_TRUE(
       policy->IsFeatureEnabledForOrigin(kDefaultSelfFeature, origin_a_));
@@ -2069,8 +2219,9 @@
       {{kDefaultSelfFeature, /*allowed_origins=*/
         {blink::OriginWithPossibleWildcards(origin_b_,
                                             /*has_subdomain_wildcard=*/false)},
-        false,
-        false}}};
+        /*self_if_matches=*/absl::nullopt,
+        /*matches_all_origins=*/false,
+        /*matches_opaque_src=*/false}}};
   auto policy = CreateFromParsedPolicy(parsed_policy, origin_a_);
   EXPECT_FALSE(
       policy->IsFeatureEnabledForOrigin(kDefaultSelfFeature, origin_a_));
@@ -2080,7 +2231,10 @@
 
 TEST_F(PermissionsPolicyTest, CreateFromParsedPolicyWithEmptyAllowlist) {
   ParsedPermissionsPolicy parsed_policy = {
-      {{kDefaultSelfFeature, /*allowed_origins=*/{}, false, false}}};
+      {{kDefaultSelfFeature, /*allowed_origins=*/{},
+        /*self_if_matches=*/absl::nullopt,
+        /*matches_all_origins=*/false,
+        /*matches_opaque_src=*/false}}};
   auto policy = CreateFromParsedPolicy(parsed_policy, origin_a_);
   EXPECT_FALSE(policy->IsFeatureEnabled(kDefaultSelfFeature));
 }
@@ -2092,8 +2246,9 @@
       {{{kDefaultSelfFeature, /*allowed_origins=*/
          {blink::OriginWithPossibleWildcards(origin_a_,
                                              /*has_subdomain_wildcard=*/false)},
-         false,
-         false}}});
+         /*self_if_matches=*/absl::nullopt,
+         /*matches_all_origins=*/false,
+         /*matches_opaque_src=*/false}}});
   EXPECT_TRUE(policy1->IsFeatureEnabled(kDefaultSelfFeature));
 
   // We can't construct a policy, check, then set headers.
@@ -2103,8 +2258,9 @@
       {{{kDefaultSelfFeature, /*allowed_origins=*/
          {blink::OriginWithPossibleWildcards(origin_a_,
                                              /*has_subdomain_wildcard=*/false)},
-         false,
-         false}}}));
+         /*self_if_matches=*/absl::nullopt,
+         /*matches_all_origins=*/false,
+         /*matches_opaque_src=*/false}}}));
 
   // We can't construct a policy, set headers, then set the header.
   auto policy3 = CreateFromParentPolicy(nullptr, origin_a_);
@@ -2112,19 +2268,25 @@
       {{{kDefaultSelfFeature, /*allowed_origins=*/
          {blink::OriginWithPossibleWildcards(origin_a_,
                                              /*has_subdomain_wildcard=*/false)},
-         false,
-         false}}});
+         /*self_if_matches=*/absl::nullopt,
+         /*matches_all_origins=*/false,
+         /*matches_opaque_src=*/false}}});
   EXPECT_DCHECK_DEATH(policy3->SetHeaderPolicy(
       {{{kDefaultSelfFeature, /*allowed_origins=*/
          {blink::OriginWithPossibleWildcards(origin_a_,
                                              /*has_subdomain_wildcard=*/false)},
-         false,
-         false}}}));
+         /*self_if_matches=*/absl::nullopt,
+         /*matches_all_origins=*/false,
+         /*matches_opaque_src=*/false}}}));
 
   // We can't construct a policy, then set headers for kNotFound.
   auto policy4 = CreateFromParentPolicy(nullptr, origin_a_);
-  EXPECT_DCHECK_DEATH(policy4->SetHeaderPolicy(
-      {{{mojom::PermissionsPolicyFeature::kNotFound, {}, false, false}}}));
+  EXPECT_DCHECK_DEATH(
+      policy4->SetHeaderPolicy({{{mojom::PermissionsPolicyFeature::kNotFound,
+                                  {},
+                                  /*self_if_matches=*/absl::nullopt,
+                                  /*matches_all_origins=*/false,
+                                  /*matches_opaque_src=*/false}}}));
 }
 
 TEST_F(PermissionsPolicyTest, OverwriteHeaderPolicyForClientHints) {
@@ -2135,15 +2297,17 @@
          /*allowed_origins=*/
          {blink::OriginWithPossibleWildcards(origin_b_,
                                              /*has_subdomain_wildcard=*/false)},
-         false,
-         false}}});
+         /*self_if_matches=*/absl::nullopt,
+         /*matches_all_origins=*/false,
+         /*matches_opaque_src=*/false}}});
   policy1->OverwriteHeaderPolicyForClientHints(
       {{{mojom::PermissionsPolicyFeature::kClientHintDPR,
          /*allowed_origins=*/
          {blink::OriginWithPossibleWildcards(origin_a_,
                                              /*has_subdomain_wildcard=*/false)},
-         false,
-         false}}});
+         /*self_if_matches=*/absl::nullopt,
+         /*matches_all_origins=*/false,
+         /*matches_opaque_src=*/false}}});
   EXPECT_TRUE(policy1->IsFeatureEnabled(
       mojom::PermissionsPolicyFeature::kClientHintDPR));
 
@@ -2154,15 +2318,17 @@
          /*allowed_origins=*/
          {blink::OriginWithPossibleWildcards(origin_a_,
                                              /*has_subdomain_wildcard=*/false)},
-         false,
-         false}}});
+         /*self_if_matches=*/absl::nullopt,
+         /*matches_all_origins=*/false,
+         /*matches_opaque_src=*/false}}});
   policy2->OverwriteHeaderPolicyForClientHints(
       {{{mojom::PermissionsPolicyFeature::kClientHintDPR,
          /*allowed_origins=*/
          {blink::OriginWithPossibleWildcards(origin_b_,
                                              /*has_subdomain_wildcard=*/false)},
-         false,
-         false}}});
+         /*self_if_matches=*/absl::nullopt,
+         /*matches_all_origins=*/false,
+         /*matches_opaque_src=*/false}}});
   EXPECT_FALSE(policy2->IsFeatureEnabled(
       mojom::PermissionsPolicyFeature::kClientHintDPR));
 
@@ -2172,15 +2338,17 @@
       {{{kDefaultSelfFeature, /*allowed_origins=*/
          {blink::OriginWithPossibleWildcards(origin_b_,
                                              /*has_subdomain_wildcard=*/false)},
-         false,
-         false}}});
+         /*self_if_matches=*/absl::nullopt,
+         /*matches_all_origins=*/false,
+         /*matches_opaque_src=*/false}}});
   policy3->OverwriteHeaderPolicyForClientHints(
       {{{mojom::PermissionsPolicyFeature::kClientHintDPR,
          /*allowed_origins=*/
          {blink::OriginWithPossibleWildcards(origin_a_,
                                              /*has_subdomain_wildcard=*/false)},
-         false,
-         false}}});
+         /*self_if_matches=*/absl::nullopt,
+         /*matches_all_origins=*/false,
+         /*matches_opaque_src=*/false}}});
   EXPECT_TRUE(policy3->IsFeatureEnabled(
       mojom::PermissionsPolicyFeature::kClientHintDPR));
 
@@ -2190,8 +2358,9 @@
       {{{kDefaultSelfFeature, /*allowed_origins=*/
          {blink::OriginWithPossibleWildcards(origin_a_,
                                              /*has_subdomain_wildcard=*/false)},
-         false,
-         false}}}));
+         /*self_if_matches=*/absl::nullopt,
+         /*matches_all_origins=*/false,
+         /*matches_opaque_src=*/false}}}));
 
   // We can't construct a policy, set headers, check, then overwrite the header.
   auto policy5 = CreateFromParentPolicy(nullptr, origin_a_);
@@ -2200,8 +2369,9 @@
          /*allowed_origins=*/
          {blink::OriginWithPossibleWildcards(origin_a_,
                                              /*has_subdomain_wildcard=*/false)},
-         false,
-         false}}});
+         /*self_if_matches=*/absl::nullopt,
+         /*matches_all_origins=*/false,
+         /*matches_opaque_src=*/false}}});
   EXPECT_TRUE(policy5->IsFeatureEnabled(
       mojom::PermissionsPolicyFeature::kClientHintDPR));
   EXPECT_DCHECK_DEATH(policy5->OverwriteHeaderPolicyForClientHints(
@@ -2209,8 +2379,9 @@
          /*allowed_origins=*/
          {blink::OriginWithPossibleWildcards(origin_a_,
                                              /*has_subdomain_wildcard=*/false)},
-         false,
-         false}}}));
+         /*self_if_matches=*/absl::nullopt,
+         /*matches_all_origins=*/false,
+         /*matches_opaque_src=*/false}}}));
 }
 
 TEST_F(PermissionsPolicyTest, GetAllowlistForFeatureIfExists) {
@@ -2220,7 +2391,9 @@
       {blink::OriginWithPossibleWildcards(origin_b_,
                                           /*has_subdomain_wildcard=*/false)});
   policy1->SetHeaderPolicy({{{mojom::PermissionsPolicyFeature::kClientHintDPR,
-                              origins1, false, false}}});
+                              origins1, /*self_if_matches=*/absl::nullopt,
+                              /*matches_all_origins=*/false,
+                              /*matches_opaque_src=*/false}}});
   const auto& maybe_allow_list1 = policy1->GetAllowlistForFeatureIfExists(
       mojom::PermissionsPolicyFeature::kClientHintDPR);
   EXPECT_TRUE(maybe_allow_list1.has_value());
@@ -2240,11 +2413,16 @@
   const std::vector<blink::OriginWithPossibleWildcards> origins3(
       {blink::OriginWithPossibleWildcards(origin_a_,
                                           /*has_subdomain_wildcard=*/false)});
-  policy3->SetHeaderPolicy(
-      {{{mojom::PermissionsPolicyFeature::kClientHintDPR, {}, false, false}}});
+  policy3->SetHeaderPolicy({{{mojom::PermissionsPolicyFeature::kClientHintDPR,
+                              {},
+                              /*self_if_matches=*/absl::nullopt,
+                              /*matches_all_origins=*/false,
+                              /*matches_opaque_src=*/false}}});
   policy3->OverwriteHeaderPolicyForClientHints(
-      {{{mojom::PermissionsPolicyFeature::kClientHintDPR, origins3, false,
-         false}}});
+      {{{mojom::PermissionsPolicyFeature::kClientHintDPR, origins3,
+         /*self_if_matches=*/absl::nullopt,
+         /*matches_all_origins=*/false,
+         /*matches_opaque_src=*/false}}});
   const auto& maybe_allow_list3 = policy3->GetAllowlistForFeatureIfExists(
       mojom::PermissionsPolicyFeature::kClientHintDPR);
   EXPECT_TRUE(maybe_allow_list3.has_value());
@@ -2261,8 +2439,10 @@
        blink::OriginWithPossibleWildcards(origin_b_,
                                           /*has_subdomain_wildcard=*/false)});
   policy4->OverwriteHeaderPolicyForClientHints(
-      {{{mojom::PermissionsPolicyFeature::kClientHintDPR, origins4, false,
-         false}}});
+      {{{mojom::PermissionsPolicyFeature::kClientHintDPR, origins4,
+         /*self_if_matches=*/absl::nullopt,
+         /*matches_all_origins=*/false,
+         /*matches_opaque_src=*/false}}});
   const auto& maybe_allow_list4 = policy4->GetAllowlistForFeatureIfExists(
       mojom::PermissionsPolicyFeature::kClientHintDPR);
   EXPECT_TRUE(maybe_allow_list4.has_value());
diff --git a/third_party/blink/public/common/permissions_policy/origin_with_possible_wildcards.h b/third_party/blink/public/common/permissions_policy/origin_with_possible_wildcards.h
index a1d1285..a3b2eb2 100644
--- a/third_party/blink/public/common/permissions_policy/origin_with_possible_wildcards.h
+++ b/third_party/blink/public/common/permissions_policy/origin_with_possible_wildcards.h
@@ -33,8 +33,10 @@
   // This constructs a OriginWithPossibleWildcards from an allowlist_entry which
   // might or might not have a subdomain wildcard (only if the type is kHeader).
   // This does not support special types like *, 'self', 'src', or 'none'.
-  static OriginWithPossibleWildcards Parse(const std::string& allowlist_entry,
-                                           const NodeType type);
+  // If the entry cannot be parsed then absl::nullopt is returned.
+  static absl::optional<OriginWithPossibleWildcards> Parse(
+      const std::string& allowlist_entry,
+      const NodeType type);
 
   // This should neatly undo the work of Parse, which is to say it
   // serializes the origin and inserts a *. back into the front of the host
diff --git a/third_party/blink/public/common/permissions_policy/permissions_policy.h b/third_party/blink/public/common/permissions_policy/permissions_policy.h
index 2871e741..5e817dc 100644
--- a/third_party/blink/public/common/permissions_policy/permissions_policy.h
+++ b/third_party/blink/public/common/permissions_policy/permissions_policy.h
@@ -112,6 +112,9 @@
     // Adds a single origin with possible wildcards to the allowlist.
     void Add(const blink::OriginWithPossibleWildcards& origin);
 
+    // Add an origin representing self to the allowlist.
+    void AddSelf(absl::optional<url::Origin> self);
+
     // Adds all origins to the allowlist.
     void AddAll();
 
@@ -122,6 +125,9 @@
     // Returns true if the given origin has been added to the allowlist.
     bool Contains(const url::Origin& origin) const;
 
+    // Returns the origin for self if included in the allowlist.
+    const absl::optional<url::Origin>& SelfIfMatches() const;
+
     // Returns true if the allowlist matches all origins.
     bool MatchesAll() const;
 
@@ -148,6 +154,7 @@
 
    private:
     std::vector<OriginWithPossibleWildcards> allowed_origins_;
+    absl::optional<url::Origin> self_if_matches_;
     bool matches_all_origins_{false};
     bool matches_opaque_src_{false};
   };
diff --git a/third_party/blink/public/common/permissions_policy/permissions_policy_declaration.h b/third_party/blink/public/common/permissions_policy/permissions_policy_declaration.h
index 1817082..13be54ca6 100644
--- a/third_party/blink/public/common/permissions_policy/permissions_policy_declaration.h
+++ b/third_party/blink/public/common/permissions_policy/permissions_policy_declaration.h
@@ -26,6 +26,7 @@
   ParsedPermissionsPolicyDeclaration(
       mojom::PermissionsPolicyFeature feature,
       const std::vector<OriginWithPossibleWildcards>& allowed_origins,
+      const absl::optional<url::Origin>& self_if_matches,
       bool matches_all_origins,
       bool matches_opaque_src);
   ParsedPermissionsPolicyDeclaration(
@@ -43,8 +44,10 @@
 
   mojom::PermissionsPolicyFeature feature;
 
-  // An list of all the origins/wildcards allowed.
+  // An list of all the origins/wildcards allowed (none can be opaque).
   std::vector<OriginWithPossibleWildcards> allowed_origins;
+  // An origin that matches self if 'self' is in the allowlist.
+  absl::optional<url::Origin> self_if_matches;
   // Fallback value is used when feature is enabled for all or disabled for all.
   bool matches_all_origins{false};
   // This flag is set true for a declared policy on an <iframe sandbox>
@@ -52,6 +55,7 @@
   // document. Usually, the 'src' keyword in a declaration will cause the origin
   // of the iframe to be present in |origins|, but for sandboxed iframes, this
   // flag is set instead.
+  // TODO(crbug.com/1418009): Consider merging into `self_if_matches`.
   bool matches_opaque_src{false};
 
   // Indicates that the parsed policy is deprecated.
diff --git a/third_party/blink/public/mojom/permissions_policy/permissions_policy.mojom b/third_party/blink/public/mojom/permissions_policy/permissions_policy.mojom
index d88101c..d3ddb6c 100644
--- a/third_party/blink/public/mojom/permissions_policy/permissions_policy.mojom
+++ b/third_party/blink/public/mojom/permissions_policy/permissions_policy.mojom
@@ -4,18 +4,9 @@
 
 module blink.mojom;
 
-import "url/mojom/origin.mojom";
+import "services/network/public/mojom/content_security_policy.mojom";
 import "third_party/blink/public/mojom/permissions_policy/permissions_policy_feature.mojom";
-
-// This struct can represent an origin like https://foo.com/ or like
-// https://*.foo.com/. The wildcard can only represent a subdomain.
-// Note that https://*.foo.com/ matches domains like https://example.foo.com/
-// or https://test.example.foo.com/ but does not match https://foo.com/.
-// Origins that do have wildcards cannot be opaque.
-struct OriginWithPossibleWildcards {
-  url.mojom.Origin origin;
-  bool has_subdomain_wildcard;
-};
+import "url/mojom/origin.mojom";
 
 // This struct holds permissions policy allowlist data that needs to be replicated
 // between a RenderFrame and any of its associated RenderFrameProxies. A list of
@@ -23,7 +14,8 @@
 // NOTE: These types are used for replication frame state between processes.
 struct ParsedPermissionsPolicyDeclaration {
   PermissionsPolicyFeature feature;
-  array<OriginWithPossibleWildcards> allowed_origins;
+  array<network.mojom.CSPSource> allowed_origins;
+  url.mojom.Origin? self_if_matches;
   bool matches_all_origins;
   bool matches_opaque_src;
 };
diff --git a/third_party/blink/renderer/core/css/properties/computed_style_utils.cc b/third_party/blink/renderer/core/css/properties/computed_style_utils.cc
index 7a135ff..c1a2b686 100644
--- a/third_party/blink/renderer/core/css/properties/computed_style_utils.cc
+++ b/third_party/blink/renderer/core/css/properties/computed_style_utils.cc
@@ -12,6 +12,7 @@
 #include "third_party/blink/renderer/core/css/css_border_image_slice_value.h"
 #include "third_party/blink/renderer/core/css/css_bracketed_value_list.h"
 #include "third_party/blink/renderer/core/css/css_color.h"
+#include "third_party/blink/renderer/core/css/css_content_distribution_value.h"
 #include "third_party/blink/renderer/core/css/css_counter_value.h"
 #include "third_party/blink/renderer/core/css/css_custom_ident_value.h"
 #include "third_party/blink/renderer/core/css/css_font_family_value.h"
@@ -701,44 +702,54 @@
   return ZoomAdjustedPixelValueForLength(offset, style);
 }
 
-CSSValueList* ComputedStyleUtils::ValueForItemPositionWithOverflowAlignment(
+CSSValue* ComputedStyleUtils::ValueForItemPositionWithOverflowAlignment(
     const StyleSelfAlignmentData& data) {
-  CSSValueList* result = CSSValueList::CreateSpaceSeparated();
   if (data.PositionType() == ItemPositionType::kLegacy) {
-    result->Append(*CSSIdentifierValue::Create(CSSValueID::kLegacy));
+    // Legacy is only for justify-items and may only be created with the
+    // positions "left", "right", or "center". See
+    // JustifyItems::ParseSingleValue.
+    DCHECK(data.GetPosition() == ItemPosition::kLeft ||
+           data.GetPosition() == ItemPosition::kRight ||
+           data.GetPosition() == ItemPosition::kCenter)
+        << "Unexpected position: " << (unsigned)data.GetPosition();
+    DCHECK_EQ(data.Overflow(), OverflowAlignment::kDefault);
+    return MakeGarbageCollected<CSSValuePair>(
+        CSSIdentifierValue::Create(CSSValueID::kLegacy),
+        CSSIdentifierValue::Create(data.GetPosition()),
+        CSSValuePair::kDropIdenticalValues);
   }
+
   if (data.GetPosition() == ItemPosition::kBaseline) {
-    result->Append(*MakeGarbageCollected<CSSValuePair>(
-        CSSIdentifierValue::Create(CSSValueID::kBaseline),
-        CSSIdentifierValue::Create(CSSValueID::kBaseline),
-        CSSValuePair::kDropIdenticalValues));
+    return CSSIdentifierValue::Create(CSSValueID::kBaseline);
   } else if (data.GetPosition() == ItemPosition::kLastBaseline) {
-    result->Append(*MakeGarbageCollected<CSSValuePair>(
+    return MakeGarbageCollected<CSSValuePair>(
         CSSIdentifierValue::Create(CSSValueID::kLast),
         CSSIdentifierValue::Create(CSSValueID::kBaseline),
-        CSSValuePair::kDropIdenticalValues));
+        CSSValuePair::kDropIdenticalValues);
   } else {
+    auto* position = data.GetPosition() == ItemPosition::kLegacy
+                         ? CSSIdentifierValue::Create(CSSValueID::kNormal)
+                         : CSSIdentifierValue::Create(data.GetPosition());
     if (data.GetPosition() >= ItemPosition::kCenter &&
         data.Overflow() != OverflowAlignment::kDefault) {
-      result->Append(*CSSIdentifierValue::Create(data.Overflow()));
+      return MakeGarbageCollected<CSSValuePair>(
+          CSSIdentifierValue::Create(data.Overflow()), position,
+          CSSValuePair::kDropIdenticalValues);
     }
-    if (data.GetPosition() == ItemPosition::kLegacy) {
-      result->Append(*CSSIdentifierValue::Create(CSSValueID::kNormal));
-    } else {
-      result->Append(*CSSIdentifierValue::Create(data.GetPosition()));
-    }
+    return position;
   }
-  DCHECK_LE(result->length(), 2u);
-  return result;
 }
 
-CSSValueList*
+cssvalue::CSSContentDistributionValue*
 ComputedStyleUtils::ValueForContentPositionAndDistributionWithOverflowAlignment(
     const StyleContentAlignmentData& data) {
-  CSSValueList* result = CSSValueList::CreateSpaceSeparated();
+  CSSValueID distribution = CSSValueID::kInvalid;
+  CSSValueID position = CSSValueID::kInvalid;
+  CSSValueID overflow = CSSValueID::kInvalid;
+
   // Handle content-distribution values
   if (data.Distribution() != ContentDistributionType::kDefault) {
-    result->Append(*CSSIdentifierValue::Create(data.Distribution()));
+    distribution = CSSIdentifierValue(data.Distribution()).GetValueID();
   }
 
   // Handle content-position values (either as fallback or actual value)
@@ -746,28 +757,24 @@
     case ContentPosition::kNormal:
       // Handle 'normal' value, not valid as content-distribution fallback.
       if (data.Distribution() == ContentDistributionType::kDefault) {
-        result->Append(*CSSIdentifierValue::Create(CSSValueID::kNormal));
+        position = CSSValueID::kNormal;
       }
       break;
     case ContentPosition::kLastBaseline:
-      result->Append(*MakeGarbageCollected<CSSValuePair>(
-          CSSIdentifierValue::Create(CSSValueID::kLast),
-          CSSIdentifierValue::Create(CSSValueID::kBaseline),
-          CSSValuePair::kDropIdenticalValues));
+      position = CSSValueID::kLastBaseline;
       break;
     default:
       // Handle overflow-alignment (only allowed for content-position values)
       if ((data.GetPosition() >= ContentPosition::kCenter ||
            data.Distribution() != ContentDistributionType::kDefault) &&
           data.Overflow() != OverflowAlignment::kDefault) {
-        result->Append(*CSSIdentifierValue::Create(data.Overflow()));
+        overflow = CSSIdentifierValue::Create(data.Overflow())->GetValueID();
       }
-      result->Append(*CSSIdentifierValue::Create(data.GetPosition()));
+      position = CSSIdentifierValue::Create(data.GetPosition())->GetValueID();
   }
 
-  DCHECK_GT(result->length(), 0u);
-  DCHECK_LE(result->length(), 3u);
-  return result;
+  return MakeGarbageCollected<cssvalue::CSSContentDistributionValue>(
+      distribution, position, overflow);
 }
 
 CSSValue* ComputedStyleUtils::ValueForLineHeight(const ComputedStyle& style) {
diff --git a/third_party/blink/renderer/core/css/properties/computed_style_utils.h b/third_party/blink/renderer/core/css/properties/computed_style_utils.h
index a754c08..a0510a2f 100644
--- a/third_party/blink/renderer/core/css/properties/computed_style_utils.h
+++ b/third_party/blink/renderer/core/css/properties/computed_style_utils.h
@@ -32,6 +32,10 @@
 class StylePropertyShorthand;
 class StyleTimeline;
 
+namespace cssvalue {
+class CSSContentDistributionValue;
+}
+
 enum class CSSValuePhase { kComputedValue, kUsedValue };
 
 class CORE_EXPORT ComputedStyleUtils {
@@ -106,9 +110,9 @@
   static CSSValue* ValueForPositionOffset(const ComputedStyle&,
                                           const CSSProperty&,
                                           const LayoutObject*);
-  static CSSValueList* ValueForItemPositionWithOverflowAlignment(
+  static CSSValue* ValueForItemPositionWithOverflowAlignment(
       const StyleSelfAlignmentData&);
-  static CSSValueList*
+  static cssvalue::CSSContentDistributionValue*
   ValueForContentPositionAndDistributionWithOverflowAlignment(
       const StyleContentAlignmentData&);
   static CSSValue* ValueForLineHeight(const ComputedStyle&);
diff --git a/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc b/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc
index c2803d8c..0dcc039 100644
--- a/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc
+++ b/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc
@@ -8,6 +8,7 @@
 #include "third_party/blink/renderer/core/css/css_axis_value.h"
 #include "third_party/blink/renderer/core/css/css_bracketed_value_list.h"
 #include "third_party/blink/renderer/core/css/css_color.h"
+#include "third_party/blink/renderer/core/css/css_content_distribution_value.h"
 #include "third_party/blink/renderer/core/css/css_counter_value.h"
 #include "third_party/blink/renderer/core/css/css_cursor_image_value.h"
 #include "third_party/blink/renderer/core/css/css_custom_ident_value.h"
diff --git a/third_party/blink/renderer/core/frame/local_dom_window.h b/third_party/blink/renderer/core/frame/local_dom_window.h
index 03356ba..931e213 100644
--- a/third_party/blink/renderer/core/frame/local_dom_window.h
+++ b/third_party/blink/renderer/core/frame/local_dom_window.h
@@ -36,6 +36,7 @@
 #include "third_party/blink/public/common/frame/delegated_capability_request_token.h"
 #include "third_party/blink/public/common/frame/history_user_activation_state.h"
 #include "third_party/blink/public/common/metrics/post_message_counter.h"
+#include "third_party/blink/public/common/performance/performance_timeline_constants.h"
 #include "third_party/blink/public/common/scheduler/task_attribution_id.h"
 #include "third_party/blink/public/common/tokens/tokens.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_value.h"
@@ -661,7 +662,7 @@
   // The navigation id of a document is to identify navigation of special types
   // like bfcache navigation or soft navigation. It increments when navigations
   // of these types occur.
-  uint32_t navigation_id_ = 1;
+  uint32_t navigation_id_ = kNavigationIdDefaultValue;
 
   // Records whether this window has obtained storage access. It cannot be
   // revoked once set to true.
diff --git a/third_party/blink/renderer/core/html/client_hints_util.cc b/third_party/blink/renderer/core/html/client_hints_util.cc
index 67d6227..d7a4987 100644
--- a/third_party/blink/renderer/core/html/client_hints_util.cc
+++ b/third_party/blink/renderer/core/html/client_hints_util.cc
@@ -78,7 +78,8 @@
         policy_name,
         std::vector<blink::OriginWithPossibleWildcards>(origin_set.begin(),
                                                         origin_set.end()),
-        allow_list.MatchesAll(), allow_list.MatchesOpaqueSrc());
+        allow_list.SelfIfMatches(), allow_list.MatchesAll(),
+        allow_list.MatchesOpaqueSrc());
     container_policy.push_back(declaration);
   }
   auto new_policy = PermissionsPolicy::CopyStateFrom(current_policy);
@@ -137,6 +138,8 @@
 
     // Now we apply the changes from the parent policy to ensure any changes
     // since it was set are respected;
+    merged_policy.self_if_matches =
+        maybe_window_allow_list.value().SelfIfMatches();
     merged_policy.matches_all_origins |=
         maybe_window_allow_list.value().MatchesAll();
     merged_policy.matches_opaque_src |=
diff --git a/third_party/blink/renderer/core/html/forms/input_type.cc b/third_party/blink/renderer/core/html/forms/input_type.cc
index 4fcbacd0..3c8125e 100644
--- a/third_party/blink/renderer/core/html/forms/input_type.cc
+++ b/third_party/blink/renderer/core/html/forms/input_type.cc
@@ -32,6 +32,7 @@
 #include <memory>
 #include <utility>
 
+#include "base/debug/crash_logging.h"
 #include "third_party/blink/public/strings/grit/blink_strings.h"
 #include "third_party/blink/renderer/core/accessibility/ax_object_cache.h"
 #include "third_party/blink/renderer/core/dom/events/scoped_event_queue.h"
@@ -498,12 +499,26 @@
 }
 
 String InputType::RangeOverflowText(const Decimal&) const {
-  NOTREACHED();
+  static auto* input_type = base::debug::AllocateCrashKeyString(
+      "input-type", base::debug::CrashKeySize::Size32);
+  base::debug::SetCrashKeyString(input_type,
+                                 FormControlType().GetString().Utf8().c_str());
+  NOTREACHED() << "This should not get called. Check if input type '"
+               << FormControlType()
+               << "' should have a RangeOverflowText implementation."
+               << "See crbug.com/1423280";
   return String();
 }
 
 String InputType::RangeUnderflowText(const Decimal&) const {
-  NOTREACHED();
+  static auto* input_type = base::debug::AllocateCrashKeyString(
+      "input-type", base::debug::CrashKeySize::Size32);
+  base::debug::SetCrashKeyString(input_type,
+                                 FormControlType().GetString().Utf8().c_str());
+  NOTREACHED() << "This should not get called. Check if input type '"
+               << FormControlType()
+               << "' should have a RangeUnderflowText implementation."
+               << "See crbug.com/1423280";
   return String();
 }
 
diff --git a/third_party/blink/renderer/core/html/forms/range_input_type.cc b/third_party/blink/renderer/core/html/forms/range_input_type.cc
index 2cd86c9..8527ad3 100644
--- a/third_party/blink/renderer/core/html/forms/range_input_type.cc
+++ b/third_party/blink/renderer/core/html/forms/range_input_type.cc
@@ -35,6 +35,7 @@
 #include <limits>
 
 #include "third_party/blink/public/common/input/web_pointer_properties.h"
+#include "third_party/blink/public/strings/grit/blink_strings.h"
 #include "third_party/blink/renderer/core/accessibility/ax_object_cache.h"
 #include "third_party/blink/renderer/core/dom/events/scoped_event_queue.h"
 #include "third_party/blink/renderer/core/dom/events/simulated_click_options.h"
@@ -59,6 +60,7 @@
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 #include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
+#include "third_party/blink/renderer/platform/text/platform_locale.h"
 #include "third_party/blink/renderer/platform/wtf/math_extras.h"
 
 namespace blink {
@@ -322,6 +324,16 @@
       "The specified value %s cannot be parsed, or is out of range.", value);
 }
 
+String RangeInputType::RangeOverflowText(const Decimal& maximum) const {
+  return GetLocale().QueryString(IDS_FORM_VALIDATION_RANGE_OVERFLOW,
+                                 LocalizeValue(Serialize(maximum)));
+}
+
+String RangeInputType::RangeUnderflowText(const Decimal& minimum) const {
+  return GetLocale().QueryString(IDS_FORM_VALIDATION_RANGE_UNDERFLOW,
+                                 LocalizeValue(Serialize(minimum)));
+}
+
 void RangeInputType::DisabledAttributeChanged() {
   if (!HasCreatedShadowSubtree()) {
     return;
diff --git a/third_party/blink/renderer/core/html/forms/range_input_type.h b/third_party/blink/renderer/core/html/forms/range_input_type.h
index 42793bc3..75428d5 100644
--- a/third_party/blink/renderer/core/html/forms/range_input_type.h
+++ b/third_party/blink/renderer/core/html/forms/range_input_type.h
@@ -70,6 +70,8 @@
   void SanitizeValueInResponseToMinOrMaxAttributeChange() override;
   void StepAttributeChanged() override;
   void WarnIfValueIsInvalid(const String&) const override;
+  String RangeOverflowText(const Decimal& maxmum) const override;
+  String RangeUnderflowText(const Decimal& minimum) const override;
   void DidSetValue(const String&, bool value_changed) override;
   String SanitizeValue(const String& proposed_value) const override;
   bool ShouldRespectListAttribute() override;
diff --git a/third_party/blink/renderer/core/layout/layout_object.h b/third_party/blink/renderer/core/layout/layout_object.h
index bef2cb8..9ac7552 100644
--- a/third_party/blink/renderer/core/layout/layout_object.h
+++ b/third_party/blink/renderer/core/layout/layout_object.h
@@ -3318,6 +3318,11 @@
     return *fragment_;
   }
 
+  bool IsFragmented() const {
+    NOT_DESTROYED();
+    return !!FirstFragment().NextFragment();
+  }
+
   enum OverflowRecalcType {
     kOnlyVisualOverflowRecalc,
     kLayoutAndVisualOverflowRecalc,
diff --git a/third_party/blink/renderer/core/mobile_metrics/mobile_friendliness_checker_test.cc b/third_party/blink/renderer/core/mobile_metrics/mobile_friendliness_checker_test.cc
index a13b97c..16d5b82f 100644
--- a/third_party/blink/renderer/core/mobile_metrics/mobile_friendliness_checker_test.cc
+++ b/third_party/blink/renderer/core/mobile_metrics/mobile_friendliness_checker_test.cc
@@ -782,13 +782,7 @@
               20);
 }
 
-#if BUILDFLAG(IS_IOS)
-// TODO(crbug.com/1141478)
-#define MAYBE_TextAbsolutePositioning DISABLED_TextAbsolutePositioning
-#else
-#define MAYBE_TextAbsolutePositioning TextAbsolutePositioning
-#endif  // BUILDFLAG(IS_IOS)
-TEST_F(MobileFriendlinessCheckerTest, MAYBE_TextAbsolutePositioning) {
+TEST_F(MobileFriendlinessCheckerTest, TextAbsolutePositioning) {
   ukm::mojom::UkmEntry ukm = CalculateMetricsForHTMLString(
       R"HTML(
 <html>
diff --git a/third_party/blink/renderer/core/permissions_policy/dom_feature_policy.cc b/third_party/blink/renderer/core/permissions_policy/dom_feature_policy.cc
index 54c8f517..a751a35 100644
--- a/third_party/blink/renderer/core/permissions_policy/dom_feature_policy.cc
+++ b/third_party/blink/renderer/core/permissions_policy/dom_feature_policy.cc
@@ -122,6 +122,13 @@
         return Vector<String>({"*"});
     }
     Vector<String> result;
+    result.reserve(
+        static_cast<wtf_size_t>(allowed_origins.size()) +
+        static_cast<wtf_size_t>(allowlist.SelfIfMatches().has_value()));
+    if (allowlist.SelfIfMatches()) {
+      result.push_back(
+          WTF::String::FromUTF8(allowlist.SelfIfMatches()->Serialize()));
+    }
     for (const auto& origin_with_possible_wildcards : allowed_origins) {
       result.push_back(
           WTF::String::FromUTF8(origin_with_possible_wildcards.Serialize()));
diff --git a/third_party/blink/renderer/core/permissions_policy/permissions_policy_parser.cc b/third_party/blink/renderer/core/permissions_policy/permissions_policy_parser.cc
index 8d74e000..2c2eb775 100644
--- a/third_party/blink/renderer/core/permissions_policy/permissions_policy_parser.cc
+++ b/third_party/blink/renderer/core/permissions_policy/permissions_policy_parser.cc
@@ -120,6 +120,7 @@
   struct ParsedAllowlist {
     std::vector<blink::OriginWithPossibleWildcards> allowed_origins
         ALLOW_DISCOURAGED_TYPE("Permission policy uses STL for code sharing");
+    absl::optional<url::Origin> self_if_matches;
     bool matches_all_origins{false};
     bool matches_opaque_src{false};
 
@@ -242,8 +243,7 @@
     //       |src_origin| is not null), |src_origin| is not opaque; or
     //     c. the opaque origin of the frame, if |src_origin| is opaque.
     if (!src_origin_) {
-      allowlist.allowed_origins.emplace_back(self_origin_->ToUrlOrigin(),
-                                             /*has_subdomain_wildcard=*/false);
+      allowlist.self_if_matches = self_origin_->ToUrlOrigin();
     } else if (!src_origin_->IsOpaque()) {
       allowlist.allowed_origins.emplace_back(src_origin_->ToUrlOrigin(),
                                              /*has_subdomain_wildcard=*/false);
@@ -273,12 +273,13 @@
       // adding an origin to the allowlist.
       bool target_is_opaque = false;
       bool target_is_all = false;
+      bool target_is_self = false;
+      url::Origin self;
 
       // 'self' origin is used if the origin is exactly 'self'.
       if (EqualIgnoringASCIICase(origin_string, "'self'")) {
-        origin_with_possible_wildcards =
-            OriginWithPossibleWildcards(self_origin_->ToUrlOrigin(),
-                                        /*has_subdomain_wildcard=*/false);
+        target_is_self = true;
+        self = self_origin_->ToUrlOrigin();
       }
       // 'src' origin is used if |src_origin| is available and the
       // origin is a match for 'src'. |src_origin| is only set
@@ -300,9 +301,13 @@
       // valid. Invalid strings will produce an opaque origin, which will
       // result in an error message.
       else {
-        origin_with_possible_wildcards =
-            OriginWithPossibleWildcards::Parse(origin_string.Utf8(), type);
-        if (origin_with_possible_wildcards.origin.opaque()) {
+        absl::optional<OriginWithPossibleWildcards>
+            maybe_origin_with_possible_wildcards =
+                OriginWithPossibleWildcards::Parse(origin_string.Utf8(), type);
+        if (maybe_origin_with_possible_wildcards) {
+          origin_with_possible_wildcards =
+              *maybe_origin_with_possible_wildcards;
+        } else {
           logger_.Warn("Unrecognized origin: '" + origin_string + "'.");
           continue;
         }
@@ -313,6 +318,8 @@
         allowlist.matches_opaque_src = true;
       } else if (target_is_opaque) {
         allowlist.matches_opaque_src = true;
+      } else if (target_is_self) {
+        allowlist.self_if_matches = self;
       } else {
         allowlist.allowed_origins.emplace_back(origin_with_possible_wildcards);
       }
@@ -349,6 +356,7 @@
 
   ParsedPermissionsPolicyDeclaration parsed_feature(*feature);
   parsed_feature.allowed_origins = std::move(parsed_allowlist.allowed_origins);
+  parsed_feature.self_if_matches = parsed_allowlist.self_if_matches;
   parsed_feature.matches_all_origins = parsed_allowlist.matches_all_origins;
   parsed_feature.matches_opaque_src = parsed_allowlist.matches_opaque_src;
 
diff --git a/third_party/blink/renderer/core/permissions_policy/permissions_policy_test.cc b/third_party/blink/renderer/core/permissions_policy/permissions_policy_test.cc
index 4222477..7c929b7 100644
--- a/third_party/blink/renderer/core/permissions_policy/permissions_policy_test.cc
+++ b/third_party/blink/renderer/core/permissions_policy/permissions_policy_test.cc
@@ -116,6 +116,7 @@
 
 struct ParsedPolicyDeclarationForTest {
   mojom::blink::PermissionsPolicyFeature feature;
+  absl::optional<const char*> self_if_matches;
   bool matches_all_origins;
   bool matches_opaque_src;
   std::vector<OriginWithPossibleWildcardsForTest> allowed_origins;
@@ -184,6 +185,12 @@
       const auto& expected_declaration = expected[i];
 
       EXPECT_EQ(actual_declaration.feature, expected_declaration.feature);
+      if (expected_declaration.self_if_matches) {
+        EXPECT_TRUE(actual_declaration.self_if_matches->IsSameOriginWith(
+            url::Origin::Create(GURL(*expected_declaration.self_if_matches))));
+      } else {
+        EXPECT_FALSE(actual_declaration.self_if_matches);
+      }
       EXPECT_EQ(actual_declaration.matches_all_origins,
                 expected_declaration.matches_all_origins);
       EXPECT_EQ(actual_declaration.matches_opaque_src,
@@ -236,9 +243,10 @@
             {
                 {
                     mojom::blink::PermissionsPolicyFeature::kGeolocation,
+                    /* self_if_matches */ ORIGIN_A,
                     /* matches_all_origins */ false,
                     /* matches_opaque_src */ false,
-                    {{ORIGIN_A, /*has_subdomain_wildcard=*/false}},
+                    {},
                 },
             },
         },
@@ -252,9 +260,10 @@
             {
                 {
                     mojom::blink::PermissionsPolicyFeature::kGeolocation,
+                    /* self_if_matches */ ORIGIN_A,
                     /* matches_all_origins */ false,
                     /* matches_opaque_src */ false,
-                    {{ORIGIN_A, /*has_subdomain_wildcard=*/false}},
+                    {},
                 },
             },
         },
@@ -268,6 +277,7 @@
             {
                 {
                     mojom::blink::PermissionsPolicyFeature::kGeolocation,
+                    /* self_if_matches */ absl::nullopt,
                     /* matches_all_origins */ true,
                     /* matches_opaque_src */ true,
                     {},
@@ -290,12 +300,14 @@
             {
                 {
                     mojom::blink::PermissionsPolicyFeature::kGeolocation,
+                    /* self_if_matches */ absl::nullopt,
                     /* matches_all_origins */ true,
                     /* matches_opaque_src */ true,
                     {},
                 },
                 {
                     mojom::blink::PermissionsPolicyFeature::kFullscreen,
+                    /* self_if_matches */ absl::nullopt,
                     /* matches_all_origins */ false,
                     /* matches_opaque_src */ false,
                     {{ORIGIN_B, /*has_subdomain_wildcard=*/false},
@@ -303,9 +315,10 @@
                 },
                 {
                     mojom::blink::PermissionsPolicyFeature::kPayment,
+                    /* self_if_matches */ ORIGIN_A,
                     /* matches_all_origins */ false,
                     /* matches_opaque_src */ false,
-                    {{ORIGIN_A, /*has_subdomain_wildcard=*/false}},
+                    {},
                 },
             },
         },
@@ -325,12 +338,14 @@
             {
                 {
                     mojom::blink::PermissionsPolicyFeature::kGeolocation,
+                    /* self_if_matches */ absl::nullopt,
                     /* matches_all_origins */ true,
                     /* matches_opaque_src */ true,
                     {},
                 },
                 {
                     mojom::blink::PermissionsPolicyFeature::kFullscreen,
+                    /* self_if_matches */ absl::nullopt,
                     /* matches_all_origins */ false,
                     /* matches_opaque_src */ false,
                     {{ORIGIN_B, /*has_subdomain_wildcard=*/false},
@@ -338,9 +353,10 @@
                 },
                 {
                     mojom::blink::PermissionsPolicyFeature::kPayment,
+                    /* self_if_matches */ ORIGIN_A,
                     /* matches_all_origins */ false,
                     /* matches_opaque_src */ false,
-                    {{ORIGIN_A, /*has_subdomain_wildcard=*/false}},
+                    {},
                 },
             },
         },
@@ -358,21 +374,24 @@
             {
                 {
                     mojom::blink::PermissionsPolicyFeature::kGeolocation,
+                    /* self_if_matches */ ORIGIN_A,
                     /* matches_all_origins */ false,
                     /* matches_opaque_src */ false,
-                    {{ORIGIN_A, /*has_subdomain_wildcard=*/false}},
+                    {},
                 },
                 {
                     mojom::blink::PermissionsPolicyFeature::kFullscreen,
+                    /* self_if_matches */ ORIGIN_A,
                     /* matches_all_origins */ false,
                     /* matches_opaque_src */ false,
-                    {{ORIGIN_A, /*has_subdomain_wildcard=*/false}},
+                    {},
                 },
                 {
                     mojom::blink::PermissionsPolicyFeature::kPayment,
+                    /* self_if_matches */ ORIGIN_A,
                     /* matches_all_origins */ false,
                     /* matches_opaque_src */ false,
-                    {{ORIGIN_A, /*has_subdomain_wildcard=*/false}},
+                    {},
                 },
             },
         },
@@ -394,6 +413,7 @@
             {
                 {
                     mojom::blink::PermissionsPolicyFeature::kGeolocation,
+                    /* self_if_matches */ absl::nullopt,
                     /* matches_all_origins */ false,
                     /* matches_opaque_src */ true,
                     {},
@@ -410,6 +430,7 @@
             {
                 {
                     mojom::blink::PermissionsPolicyFeature::kGeolocation,
+                    /* self_if_matches */ absl::nullopt,
                     /* matches_all_origins */ false,
                     /* matches_opaque_src */ true,
                     {},
@@ -426,6 +447,7 @@
             {
                 {
                     mojom::blink::PermissionsPolicyFeature::kGeolocation,
+                    /* self_if_matches */ absl::nullopt,
                     /* matches_all_origins */ true,
                     /* matches_opaque_src */ true,
                     {},
@@ -443,6 +465,7 @@
             {
                 {
                     mojom::blink::PermissionsPolicyFeature::kGeolocation,
+                    /* self_if_matches */ absl::nullopt,
                     /* matches_all_origins */ false,
                     /* matches_opaque_src */ false,
                     {{ORIGIN_B, /*has_subdomain_wildcard=*/false},
@@ -461,6 +484,7 @@
             {
                 {
                     mojom::blink::PermissionsPolicyFeature::kGeolocation,
+                    /* self_if_matches */ absl::nullopt,
                     /* matches_all_origins */ false,
                     /* matches_opaque_src */ true,
                     {{ORIGIN_B, /*has_subdomain_wildcard=*/false}},
@@ -478,6 +502,7 @@
             {
                 {
                     mojom::blink::PermissionsPolicyFeature::kGeolocation,
+                    /* self_if_matches */ absl::nullopt,
                     /* matches_all_origins */ false,
                     /* matches_opaque_src */ false,
                     {},
@@ -495,6 +520,7 @@
             {
                 {
                     mojom::blink::PermissionsPolicyFeature::kGeolocation,
+                    /* self_if_matches */ absl::nullopt,
                     /* matches_all_origins */ false,
                     /* matches_opaque_src */ false,
                     {},
@@ -512,6 +538,7 @@
             {
                 {
                     mojom::blink::PermissionsPolicyFeature::kGeolocation,
+                    /* self_if_matches */ absl::nullopt,
                     /* matches_all_origins */ false,
                     /* matches_opaque_src */ false,
                     {},
@@ -528,6 +555,7 @@
             {
                 {
                     mojom::blink::PermissionsPolicyFeature::kGeolocation,
+                    /* self_if_matches */ absl::nullopt,
                     /* matches_all_origins */ false,
                     /* matches_opaque_src */ false,
                     {},
@@ -545,6 +573,7 @@
             {
                 {
                     mojom::blink::PermissionsPolicyFeature::kFullscreen,
+                    /* self_if_matches */ absl::nullopt,
                     /* matches_all_origins */ false,
                     /* matches_opaque_src */ false,
                     {{ORIGIN_A_SUBDOMAIN_ESCAPED,
@@ -563,6 +592,7 @@
             {
                 {
                     mojom::blink::PermissionsPolicyFeature::kFullscreen,
+                    /* self_if_matches */ absl::nullopt,
                     /* matches_all_origins */ false,
                     /* matches_opaque_src */ false,
                     {{ORIGIN_A,
@@ -584,6 +614,7 @@
             {
                 {
                     mojom::blink::PermissionsPolicyFeature::kFullscreen,
+                    /* self_if_matches */ absl::nullopt,
                     /* matches_all_origins */ false,
                     /* matches_opaque_src */ false,
                     {{"https://%2A.%2A.example.com",
@@ -643,6 +674,7 @@
           {
               // allowlist value 'none' is expected.
               mojom::blink::PermissionsPolicyFeature::kGeolocation,
+              /* self_if_matches */ absl::nullopt,
               /* matches_all_origins */ false,
               /* matches_opaque_src */ false,
               {},
@@ -666,9 +698,10 @@
           {
               // allowlist value 'self' is expected.
               mojom::blink::PermissionsPolicyFeature::kGeolocation,
+              /* self_if_matches */ ORIGIN_A,
               /* matches_all_origins */ false,
               /* matches_opaque_src */ false,
-              {{ORIGIN_A, /*has_subdomain_wildcard=*/false}},
+              {},
           },
       });
 
@@ -692,21 +725,24 @@
               // the value should be taken from permissions policy
               // header, which is 'self' here.
               mojom::blink::PermissionsPolicyFeature::kGeolocation,
+              /* self_if_matches */ ORIGIN_A,
               /* matches_all_origins */ false,
               /* matches_opaque_src */ false,
-              {{ORIGIN_A, /*has_subdomain_wildcard=*/false}},
+              {},
           },
           {
               mojom::blink::PermissionsPolicyFeature::kPayment,
+              /* self_if_matches */ absl::nullopt,
               /* matches_all_origins */ true,
               /* matches_opaque_src */ true,
               {},
           },
           {
               mojom::blink::PermissionsPolicyFeature::kFullscreen,
+              /* self_if_matches */ ORIGIN_A,
               /* matches_all_origins */ false,
               /* matches_opaque_src */ false,
-              {{ORIGIN_A, /*has_subdomain_wildcard=*/false}},
+              {},
           },
       });
 }
@@ -728,12 +764,14 @@
       {
           {
               mojom::blink::PermissionsPolicyFeature::kGeolocation,
+              /* self_if_matches */ absl::nullopt,
               /* matches_all_origins */ true,
               /* matches_opaque_src */ true,
               {},
           },
           {
               mojom::blink::PermissionsPolicyFeature::kFullscreen,
+              /* self_if_matches */ absl::nullopt,
               /* matches_all_origins */ true,
               /* matches_opaque_src */ true,
               {},
@@ -771,6 +809,7 @@
       {
           {
               mojom::blink::PermissionsPolicyFeature::kGeolocation,
+              /* self_if_matches */ absl::nullopt,
               /* matches_all_origins */ false,
               /* matches_opaque_src */ false,
               {},
@@ -804,9 +843,10 @@
       {
           {
               mojom::blink::PermissionsPolicyFeature::kGeolocation,
+              /* self_if_matches */ ORIGIN_A,
               /* matches_all_origins */ false,
               /* matches_opaque_src */ false,
-              {{ORIGIN_A, /*has_subdomain_wildcard=*/false}},
+              {},
           },
       });
 
@@ -998,19 +1038,21 @@
 
   ParsedPermissionsPolicy test_policy = {
       {mojom::blink::PermissionsPolicyFeature::kFullscreen,
-       /* allowed_origins */
+       /*allowed_origins=*/
        {blink::OriginWithPossibleWildcards(url_origin_a_,
                                            /*has_subdomain_wildcard=*/false),
         blink::OriginWithPossibleWildcards(url_origin_b_,
                                            /*has_subdomain_wildcard=*/false)},
-       false,
-       false},
+       /*self_if_matches=*/absl::nullopt,
+       /*matches_all_origins=*/false,
+       /*matches_opaque_src=*/false},
       {mojom::blink::PermissionsPolicyFeature::kGeolocation,
-       /* allowed_origins */
+       /*=allowed_origins*/
        {blink::OriginWithPossibleWildcards(url_origin_a_,
                                            /*has_subdomain_wildcard=*/false)},
-       false,
-       false}};
+       /*self_if_matches=*/absl::nullopt,
+       /*matches_all_origins=*/false,
+       /*matches_opaque_src=*/false}};
 
   ParsedPermissionsPolicy empty_policy = {};
 };
diff --git a/third_party/blink/renderer/core/view_transition/view_transition_style_tracker.cc b/third_party/blink/renderer/core/view_transition/view_transition_style_tracker.cc
index fa3af85..1121040e 100644
--- a/third_party/blink/renderer/core/view_transition/view_transition_style_tracker.cc
+++ b/third_party/blink/renderer/core/view_transition/view_transition_style_tracker.cc
@@ -360,7 +360,7 @@
   // (unless changed by something like z-index on the pseudo-elements).
   auto& root_object = root->GetLayoutObject();
   auto& root_style = root_object.StyleRef();
-  if (root_style.ViewTransitionName()) {
+  if (root_style.ViewTransitionName() && !root_object.IsFragmented()) {
     DCHECK(root_object.GetNode());
     DCHECK(root_object.GetNode()->IsElementNode());
     AddTransitionElement(DynamicTo<Element>(root_object.GetNode()),
@@ -859,6 +859,13 @@
     return false;
   }
 
+  // Check if the root element participates in a transition and has been
+  // fragmented.
+  if (new_root_data_ &&
+      document_->documentElement()->GetLayoutObject()->IsFragmented()) {
+    return false;
+  }
+
   for (auto& entry : element_data_map_) {
     auto& element_data = entry.value;
     if (!element_data->target_element)
@@ -873,6 +880,11 @@
       return false;
     }
 
+    // End the transition if any of the objects have become fragmented.
+    if (layout_object->IsFragmented()) {
+      return false;
+    }
+
     // TODO(bokan): This doesn't account for the local offset of an inline
     // element within its container. The object-view-box inset will ensure the
     // snapshot is rendered in the correct place but the pseudo is positioned
diff --git a/third_party/blink/renderer/modules/webaudio/audio_worklet_handler.cc b/third_party/blink/renderer/modules/webaudio/audio_worklet_handler.cc
index b7e7359..f1c3e6b 100644
--- a/third_party/blink/renderer/modules/webaudio/audio_worklet_handler.cc
+++ b/third_party/blink/renderer/modules/webaudio/audio_worklet_handler.cc
@@ -110,45 +110,43 @@
   TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("webaudio.audionode"),
                "AudioWorkletHandler::Process");
 
-  // Render and update the node state when the processor is ready with no error.
-  // We also need to check if the global scope is valid before we request
-  // the rendering in the AudioWorkletGlobalScope.
-  if (processor_ && !processor_->hasErrorOccurred()) {
-    // If the input is not connected, inform the processor with nullptr.
-    for (unsigned i = 0; i < NumberOfInputs(); ++i) {
-      inputs_[i] = Input(i).IsConnected() ? Input(i).Bus() : nullptr;
-    }
-    for (unsigned i = 0; i < NumberOfOutputs(); ++i) {
-      outputs_[i] = WrapRefCounted(Output(i).Bus());
-    }
-
-    for (const auto& param_name : param_value_map_.Keys()) {
-      auto* const param_handler = param_handler_map_.at(param_name);
-      AudioFloatArray* param_values = param_value_map_.at(param_name);
-      if (param_handler->HasSampleAccurateValues() &&
-          param_handler->IsAudioRate()) {
-        param_handler->CalculateSampleAccurateValues(
-            param_values->Data(), static_cast<uint32_t>(frames_to_process));
-      } else {
-        std::fill(param_values->Data(),
-                  param_values->Data() + frames_to_process,
-                  param_handler->FinalValue());
-      }
-    }
-
-    // Run the render code and check the state of processor. Finish the
-    // processor if needed.
-    if (!processor_->Process(inputs_, outputs_, param_value_map_) ||
-        processor_->hasErrorOccurred()) {
-      FinishProcessorOnRenderThread();
-    }
-  } else {
-    // The initialization of handler or the associated processor might not be
-    // ready yet or it is in the error state. If so, zero out the connected
-    // output.
+  // The associated processor is not ready, finished, or might be in an error
+  // state. If so, silence the connected outputs and return.
+  if (!processor_ || processor_->hasErrorOccurred()) {
     for (unsigned i = 0; i < NumberOfOutputs(); ++i) {
       Output(i).Bus()->Zero();
     }
+    return;
+  }
+
+  // If the input is not connected, inform the processor with nullptr.
+  for (unsigned i = 0; i < NumberOfInputs(); ++i) {
+    inputs_[i] = Input(i).IsConnected() ? Input(i).Bus() : nullptr;
+  }
+  for (unsigned i = 0; i < NumberOfOutputs(); ++i) {
+    outputs_[i] = WrapRefCounted(Output(i).Bus());
+  }
+
+  for (const auto& param_name : param_value_map_.Keys()) {
+    auto* const param_handler = param_handler_map_.at(param_name);
+    AudioFloatArray* param_values = param_value_map_.at(param_name);
+    if (param_handler->HasSampleAccurateValues() &&
+        param_handler->IsAudioRate()) {
+      param_handler->CalculateSampleAccurateValues(
+          param_values->Data(), static_cast<uint32_t>(frames_to_process));
+    } else {
+      std::fill(param_values->Data(),
+                param_values->Data() + frames_to_process,
+                param_handler->FinalValue());
+    }
+  }
+
+  // Run the render code and check the return value or the state of processor.
+  // If the return value is falsy, the processor's `Process()` function
+  // won't be called again.
+  if (!processor_->Process(inputs_, outputs_, param_value_map_) ||
+      processor_->hasErrorOccurred()) {
+    FinishProcessorOnRenderThread();
   }
 }
 
@@ -238,6 +236,14 @@
   Context()->NotifySourceNodeFinishedProcessing(this);
   processor_.Clear();
   tail_time_ = 0;
+
+  // The processor is cleared, so queue a task to mark this handler (and its
+  // associated AudioWorkletNode) is ready for GC.
+  PostCrossThreadTask(
+      *main_thread_task_runner_, FROM_HERE,
+      CrossThreadBindOnce(
+          &AudioWorkletHandler::MarkProcessorInactiveOnMainThread,
+          AsWeakPtr()));
 }
 
 void AudioWorkletHandler::NotifyProcessorError(
@@ -250,4 +256,10 @@
   static_cast<AudioWorkletNode*>(GetNode())->FireProcessorError(error_state);
 }
 
+void AudioWorkletHandler::MarkProcessorInactiveOnMainThread() {
+  DCHECK(IsMainThread());
+
+  is_processor_active_ = false;
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/webaudio/audio_worklet_handler.h b/third_party/blink/renderer/modules/webaudio/audio_worklet_handler.h
index 844803c..b52f39f 100644
--- a/third_party/blink/renderer/modules/webaudio/audio_worklet_handler.h
+++ b/third_party/blink/renderer/modules/webaudio/audio_worklet_handler.h
@@ -59,6 +59,9 @@
 
   void NotifyProcessorError(AudioWorkletProcessorErrorState);
 
+  void MarkProcessorInactiveOnMainThread();
+  bool IsProcessorActive() { return is_processor_active_; }
+
  private:
   AudioWorkletHandler(
       AudioNode&,
@@ -90,6 +93,11 @@
 
   // Used only if number of inputs and outputs are 1.
   bool is_output_channel_count_given_ = false;
+
+  // The active flag of the AudioWorkletProcessor is used to decide the
+  // lifecycle of an AudioWorkletNode and its handler. This flag becomes false
+  // when a processor stops invoking the user-defined `process()` callback.
+  bool is_processor_active_ = true;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/webaudio/audio_worklet_node.cc b/third_party/blink/renderer/modules/webaudio/audio_worklet_node.cc
index 491ae5a5..d226460 100644
--- a/third_party/blink/renderer/modules/webaudio/audio_worklet_node.cc
+++ b/third_party/blink/renderer/modules/webaudio/audio_worklet_node.cc
@@ -201,7 +201,7 @@
 }
 
 bool AudioWorkletNode::HasPendingActivity() const {
-  return !context()->IsContextCleared();
+  return GetWorkletHandler()->IsProcessorActive();
 }
 
 AudioParamMap* AudioWorkletNode::parameters() const {
diff --git a/third_party/blink/renderer/platform/fonts/win/font_cache_skia_win.cc b/third_party/blink/renderer/platform/fonts/win/font_cache_skia_win.cc
index 7af0b9f..6dddd16 100644
--- a/third_party/blink/renderer/platform/fonts/win/font_cache_skia_win.cc
+++ b/third_party/blink/renderer/platform/fonts/win/font_cache_skia_win.cc
@@ -317,9 +317,9 @@
 
     Bcp47Vector locales;
     locales.push_back(fallback_locale->LocaleForSkFontMgr());
-    SkTypeface* typeface = font_manager_->matchFamilyStyleCharacter(
+    sk_sp<SkTypeface> typeface(font_manager_->matchFamilyStyleCharacter(
         family_name.c_str(), font_description.SkiaFontStyle(), locales.data(),
-        locales.size(), codepoint);
+        locales.size(), codepoint));
 
     if (!typeface)
       return nullptr;
diff --git a/third_party/blink/tools/blinkpy/w3c/wpt_uploader.py b/third_party/blink/tools/blinkpy/w3c/wpt_uploader.py
index 2d3398e..104b5d0 100644
--- a/third_party/blink/tools/blinkpy/w3c/wpt_uploader.py
+++ b/third_party/blink/tools/blinkpy/w3c/wpt_uploader.py
@@ -65,12 +65,14 @@
                     initial_report, _, _ = body.partition(b'\n')
                     reports.append(json.loads(initial_report))
             merged_report = self.merge_reports(reports)
-
-            with tempfile.TemporaryDirectory() as tmpdir:
-                path = os.path.join(tmpdir, "reports.json.gz")
-                with gzip.open(path, 'wt', encoding="utf-8") as zipfile:
-                    json.dump(merged_report, zipfile)
-                rv = rv | self.upload_report(path)
+            if merged_report is None:
+                _log.error("No result to upload, skip...")
+            else:
+                with tempfile.TemporaryDirectory() as tmpdir:
+                    path = os.path.join(tmpdir, "reports.json.gz")
+                    with gzip.open(path, 'wt', encoding="utf-8") as zipfile:
+                        json.dump(merged_report, zipfile)
+                    rv = rv | self.upload_report(path)
             _log.info(" ")
 
         return rv
@@ -164,7 +166,7 @@
 
     def merge_reports(self, reports):
         if not reports:
-            return {}
+            return None
 
         merged_report = {}
         merged_report['run_info'] = reports[0]['run_info']
@@ -177,6 +179,8 @@
             merged_report['results'].extend(report['results'])
             merged_report['time_end'] = max(merged_report['time_end'],
                                             report['time_end'])
+        if not merged_report['results']:
+            return None
         return merged_report
 
     def parse_args(self, argv):
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index b2585d48..029ec63 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -4011,7 +4011,7 @@
 # Flaky devtools test for recalculating styles.
 crbug.com/1018177 http/tests/devtools/tracing/timeline-style/timeline-recalculate-styles.js [ Failure Pass ]
 
-crbug.com/910979 http/tests/html/validation-bubble-oopif-clip.html [ Failure Pass ]
+crbug.com/910979 http/tests/html/validation-bubble-oopif-clip.html [ Crash Failure Pass Timeout ]
 
 # Sheriff 2019-02-01, 2019-02-19
 # These are crashy on Win10, and seem to leave processes lying around, causing the swarming
diff --git a/third_party/blink/web_tests/external/wpt/css/css-align/animation/align-no-interpolation.html b/third_party/blink/web_tests/external/wpt/css/css-align/animation/align-no-interpolation.html
new file mode 100644
index 0000000..037743bd
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-align/animation/align-no-interpolation.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://github.com/w3c/csswg-drafts/issues/4441">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/interpolation-testcommon.js"></script>
+
+<body>
+<script>
+test_no_interpolation({
+  property: 'align-content',
+  from: 'initial',
+  to: 'baseline'
+});
+
+test_no_interpolation({
+  property: 'align-items',
+  from: 'initial',
+  to: 'baseline'
+});
+
+test_no_interpolation({
+  property: 'align-self',
+  from: 'initial',
+  to: 'baseline'
+});
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-view-transitions/fragmented-at-start-ignored-ref.html b/third_party/blink/web_tests/external/wpt/css/css-view-transitions/fragmented-at-start-ignored-ref.html
new file mode 100644
index 0000000..626e03d0
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-view-transitions/fragmented-at-start-ignored-ref.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<title>View transitions: fragmented elements skipped (ref)</title>
+<link rel="help" href="https://www.w3.org/TR/css-view-transitions-1/">
+<link rel="author" href="mailto:vmpstr@chromium.org">
+
+<style>
+body { background: pink }
+#spacer {
+  width: 100px;
+  height: 950px;
+  background: lightgreen;
+}
+#container {
+  width: 500px;
+  columns: 2;
+  height: 500px;
+  visibility: hidden;
+}
+#target {
+  width: 200px;
+  height: 200px;
+  background: green;
+}
+#unrelated {
+  width: 100px;
+  height: 100px;
+  background: lightblue;
+}
+</style>
+<div id=container>
+  <div id=spacer></div>
+  <div id=target></div>
+</div>
+<div id=unrelated></div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-view-transitions/fragmented-at-start-ignored.html b/third_party/blink/web_tests/external/wpt/css/css-view-transitions/fragmented-at-start-ignored.html
new file mode 100644
index 0000000..3b8628c
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-view-transitions/fragmented-at-start-ignored.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<title>View transitions: fragmented elements skipped</title>
+<link rel="help" href="https://www.w3.org/TR/css-view-transitions-1/">
+<link rel="author" href="mailto:vmpstr@chromium.org">
+<link rel="match" href="fragmented-at-start-ignored-ref.html">
+
+<script src="/common/reftest-wait.js"></script>
+<style>
+#spacer {
+  width: 100px;
+  height: 950px;
+  background: lightgreen;
+}
+#container {
+  width: 500px;
+  columns: 2;
+  height: 500px;
+}
+#target {
+  width: 200px;
+  height: 200px;
+  background: green;
+  view-transition-name: target;
+}
+#unrelated {
+  width: 100px;
+  height: 100px;
+  background: lightblue;
+  view-transition-name: unrelated;
+}
+
+::view-transition {
+  background: pink;
+}
+::view-transition-group(root) {
+  animation-duration: 500s;
+  visibility: hidden;
+}
+::view-transition-group(target) {
+  border: 1px solid black;
+}
+</style>
+<div id=container>
+  <div id=spacer></div>
+  <div id=target></div>
+</div>
+<div id=unrelated></div>
+
+<script>
+function runTransition() {
+  document.startViewTransition().ready.then(takeScreenshot);
+}
+
+requestAnimationFrame(() => requestAnimationFrame(runTransition))
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-view-transitions/fragmented-during-transition-skips-ref.html b/third_party/blink/web_tests/external/wpt/css/css-view-transitions/fragmented-during-transition-skips-ref.html
new file mode 100644
index 0000000..269a6a2
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-view-transitions/fragmented-during-transition-skips-ref.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<title>View transitions: fragmented elements skipped</title>
+<link rel="help" href="https://www.w3.org/TR/css-view-transitions-1/">
+<link rel="author" href="mailto:vmpstr@chromium.org">
+
+<style>
+#spacer {
+  width: 100px;
+  height: 950px;
+  background: lightgreen;
+}
+#container {
+  width: 500px;
+  height: 500px;
+  columns: 2;
+}
+#target {
+  width: 200px;
+  height: 200px;
+  background: green;
+}
+#unrelated {
+  width: 100px;
+  height: 100px;
+  background: lightblue;
+}
+</style>
+<div id=container>
+  <div id=spacer></div>
+  <div id=target></div>
+</div>
+<div id=unrelated></div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-view-transitions/fragmented-during-transition-skips.html b/third_party/blink/web_tests/external/wpt/css/css-view-transitions/fragmented-during-transition-skips.html
new file mode 100644
index 0000000..e872aa6
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-view-transitions/fragmented-during-transition-skips.html
@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<title>View transitions: fragmented elements skipped</title>
+<link rel="help" href="https://www.w3.org/TR/css-view-transitions-1/">
+<link rel="author" href="mailto:vmpstr@chromium.org">
+<link rel="match" href="fragmented-during-transition-skips-ref.html">
+
+<script src="/common/reftest-wait.js"></script>
+<style>
+#spacer {
+  width: 100px;
+  height: 950px;
+  background: lightgreen;
+}
+#container {
+  width: 500px;
+  height: 500px;
+}
+.fragment {
+  columns: 2;
+}
+#target {
+  width: 200px;
+  height: 200px;
+  background: green;
+  view-transition-name: target;
+}
+#unrelated {
+  width: 100px;
+  height: 100px;
+  background: lightblue;
+  view-transition-name: unrelated;
+}
+
+::view-transition {
+  background: pink;
+}
+::view-transition-group(root) {
+  animation-duration: 500s;
+  visibility: hidden;
+}
+::view-transition-group(target) {
+  border: 1px solid black;
+}
+</style>
+<div id=container>
+  <div id=spacer></div>
+  <div id=target></div>
+</div>
+<div id=unrelated></div>
+
+<script>
+function runTransition() {
+  let t = document.startViewTransition();
+  t.ready.then(() => {
+    requestAnimationFrame(() => container.classList.add("fragment"))
+  });
+  t.finished.then(takeScreenshot);
+}
+
+requestAnimationFrame(() => requestAnimationFrame(runTransition))
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-view-transitions/named-element-with-fix-pos-child-ref.html b/third_party/blink/web_tests/external/wpt/css/css-view-transitions/named-element-with-fix-pos-child-ref.html
index c1159f8..a161cd8f 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-view-transitions/named-element-with-fix-pos-child-ref.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-view-transitions/named-element-with-fix-pos-child-ref.html
@@ -8,7 +8,6 @@
   width: 100px;
   height: 100px;
   background: blue;
-  view-transition-name: target;
 }
 .child {
   width: 100px;
diff --git a/third_party/blink/web_tests/external/wpt/speculation-rules/prefetch/document-rules.https.html b/third_party/blink/web_tests/external/wpt/speculation-rules/prefetch/document-rules.https.html
index a5030f69..b0b07a8 100644
--- a/third_party/blink/web_tests/external/wpt/speculation-rules/prefetch/document-rules.https.html
+++ b/third_party/blink/web_tests/external/wpt/speculation-rules/prefetch/document-rules.https.html
@@ -13,6 +13,8 @@
 <meta name="variant" content="?include=not">
 <meta name="variant" content="?include=invalidPredicate">
 <meta name="variant" content="?include=linkInShadowTree">
+<meta name="variant" content="?include=linkHrefChanged">
+<meta name="variant" content="?include=newRuleSetAdded">
 
 <body>
 <script>
@@ -132,5 +134,37 @@
     assert_equals(await isUrlPrefetched(url), 1);
   }, 'test that matching link in a shadow tree is prefetched');
 
+  subsetTestByKey('linkHrefChanged', promise_test, async t => {
+    assert_implements(HTMLScriptElement.supports('speculationrules'),
+      'Speculation Rules not supported');
+
+    insertDocumentRule({href_matches: "*\\?*foo=bar*"});
+
+    const url = getPrefetchUrl();
+    const link = addLink(url);
+    await new Promise(resolve => t.step_timeout(resolve, 2000));
+    assert_equals(await isUrlPrefetched(url), 0);
+
+    const matching_url = getPrefetchUrl({foo: 'bar'});
+    link.href = matching_url;
+    await new Promise(resolve => t.step_timeout(resolve, 2000));
+    assert_equals(await isUrlPrefetched(matching_url), 1);
+  }, 'test that changing the href of an invalid link to a matching value triggers a prefetch');
+
+  subsetTestByKey('newRuleSetAdded', promise_test, async t => {
+    assert_implements(HTMLScriptElement.supports('speculationrules'),
+      'Speculation Rules not supported');
+
+    insertDocumentRule({href_matches: "*\\?*foo=bar*"});
+    const url = getPrefetchUrl({fizz: "buzz"});
+    addLink(url);
+    await new Promise(resolve => t.step_timeout(resolve, 2000));
+    assert_equals(await isUrlPrefetched(url), 0);
+
+    insertDocumentRule({href_matches: "*\\?*fizz=buzz*"});
+    await new Promise(resolve => t.step_timeout(resolve, 2000));
+    assert_equals(await isUrlPrefetched(url), 1);
+  }, 'test that adding a second rule set triggers prefetch');
+
 </script>
 </body>
diff --git a/third_party/blink/web_tests/webaudio/internals/audioworkletnode-gc.https.html b/third_party/blink/web_tests/webaudio/internals/audioworkletnode-gc.https.html
new file mode 100644
index 0000000..0682250
--- /dev/null
+++ b/third_party/blink/web_tests/webaudio/internals/audioworkletnode-gc.https.html
@@ -0,0 +1,55 @@
+<!doctype html>
+<html>
+<head>
+  <title>Test GC of AudioWorkletNode</title>
+  <script src="../../resources/gc.js"></script>
+  <script src="../../resources/testharness.js"></script>
+  <script src="../../resources/testharnessreport.js"></script>
+</head>
+<body>
+  <script>
+    // Creates an AudioWorkletNode (and AudioWorkletProcessor) and destroy
+    // it immediately. Then manually trigger GC and check the number of
+    // living WebAudio objects. See: https://crbug.com/1298955
+    promise_test(async () => {
+      // Check the pre-condition first.
+      assert_equals(internals.audioHandlerCount(), 0);
+
+      const context = new AudioContext();
+      const filePath = 'worklet-processors/falsy-processor.js';
+      await context.audioWorklet.addModule(filePath);
+
+      // Create a graph and start processing.
+      let workletNode = new AudioWorkletNode(context, 'falsy-processor');
+
+      return new Promise(resolve => {
+        // Prepare steps when the processor returns false.
+        workletNode.port.onmessage = async () => {
+          workletNode.port.onmessage = null;
+          workletNode.port.close();
+          workletNode.disconnect();
+          workletNode = null;
+
+          // Assuming the "active flag" from the processor successfully marked
+          // the mathching AudioWorkletNode, this should collect the node.
+          await asyncGC();
+
+          // Force the WebAudio's deferred task handler to clean up
+          // deletable AudioHandlers. Without this, the deletable handlers
+          // will be deleted a bit later.
+          await context.close();
+
+          assert_equals(internals.audioHandlerCount(), 1);
+          resolve();
+        };
+
+        // Start rendering the audio graph.
+        workletNode.connect(context.destination);
+
+        // Two handlers: the destination node and a woklet node.
+        assert_equals(internals.audioHandlerCount(), 2);
+      });
+    }, 'Test GC of AudioWorkletNode');
+  </script>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/webaudio/internals/worklet-processors/falsy-processor.js b/third_party/blink/web_tests/webaudio/internals/worklet-processors/falsy-processor.js
new file mode 100644
index 0000000..691ae11
--- /dev/null
+++ b/third_party/blink/web_tests/webaudio/internals/worklet-processors/falsy-processor.js
@@ -0,0 +1,18 @@
+class FalsyProcessor extends AudioWorkletProcessor {
+  constructor() {
+    super();
+  }
+
+  // Returns false immediately, and in turn this processor is going to end
+  // shortly and be marked for GC. See more information on
+  // AudioWorkletProcessor's return value:
+  // https://www.w3.org/TR/webaudio/#callback-audioworketprocess-callback
+  // https://www.w3.org/TR/webaudio/#rendering-a-graph
+  process() {
+    this.port.postMessage({});
+    this.port.close();
+    return false;
+  }
+}
+
+registerProcessor('falsy-processor', FalsyProcessor);
\ No newline at end of file
diff --git a/third_party/wayland-protocols/unstable/ui-controls/OWNERS b/third_party/wayland-protocols/unstable/ui-controls/OWNERS
new file mode 100644
index 0000000..78058f5
--- /dev/null
+++ b/third_party/wayland-protocols/unstable/ui-controls/OWNERS
@@ -0,0 +1 @@
+max@igalia.com
diff --git a/tools/binary_size/trybot_commit_size_checker.py b/tools/binary_size/trybot_commit_size_checker.py
index aee2448..e01190b7 100755
--- a/tools/binary_size/trybot_commit_size_checker.py
+++ b/tools/binary_size/trybot_commit_size_checker.py
@@ -21,8 +21,7 @@
     --after-dir out/binary-size-results/$HASH2 \
     --results-path output.json \
     --staging-dir tmp \
-    --local-test \
-    -v
+    --local-test
 """
 
 import argparse
@@ -53,8 +52,6 @@
 _HTML_REPORT_URL = (
     'https://chrome-supersize.firebaseapp.com/viewer.html?load_url={{' +
     _SIZEDIFF_FILENAME + '}}')
-_MAX_DEX_METHOD_COUNT_INCREASE = 200
-_MAX_NORMALIZED_INCREASE = 16 * 1024
 _MAX_PAK_INCREASE = 1024
 _TRYBOT_MD_URL = ('https://chromium.googlesource.com/chromium/src/+/main/docs/'
                   'speed/binary_size/android_binary_size_trybot.md')
@@ -99,6 +96,17 @@
     return self.name < other.name
 
 
+# See https://crbug.com/1426694
+def _MaxSizeIncrease(author, subject):
+  if 'AFDO' in subject:
+    return 1024 * 1024
+  if 'Update V8' in subject:
+    return 100 * 1024
+  if 'autoroll' in author:
+    return 50 * 1024
+  return 16 * 1024
+
+
 def _SymbolDiffHelper(title_fragment, symbols):
   added = symbols.WhereDiffStatusIs(models.DIFF_STATUS_ADDED)
   removed = symbols.WhereDiffStatusIs(models.DIFF_STATUS_REMOVED)
@@ -129,7 +137,7 @@
   return lines, _SizeDelta('Mutable Constants', 'symbols', 0, net_added)
 
 
-def _CreateMethodCountDelta(symbols):
+def _CreateMethodCountDelta(symbols, max_increase):
   symbols = symbols.WhereIsOnDemand(False)
   method_symbols = symbols.WhereInSection(models.SECTION_DEX_METHOD)
   method_lines, net_method_added = _SymbolDiffHelper('Methods', method_symbols)
@@ -143,25 +151,25 @@
   if method_lines:
     lines.extend(method_lines)
 
-  return lines, _SizeDelta('Dex Methods Count', 'methods',
-                           _MAX_DEX_METHOD_COUNT_INCREASE, net_method_added)
+  return lines, _SizeDelta('Dex Methods Count', 'methods', max_increase,
+                           net_method_added)
 
 
-def _CreateResourceSizesDelta(before_dir, after_dir):
+def _CreateResourceSizesDelta(before_dir, after_dir, max_increase):
   sizes_diff = diagnose_bloat.ResourceSizesDiff()
   sizes_diff.ProduceDiff(before_dir, after_dir)
 
-  return sizes_diff.Summary(), _SizeDelta(
-      'Normalized APK Size', 'bytes', _MAX_NORMALIZED_INCREASE,
-      sizes_diff.summary_stat.value)
+  return sizes_diff.Summary(), _SizeDelta('Normalized APK Size', 'bytes',
+                                          max_increase,
+                                          sizes_diff.summary_stat.value)
 
 
-def _CreateBaseModuleResourceSizesDelta(before_dir, after_dir):
+def _CreateBaseModuleResourceSizesDelta(before_dir, after_dir, max_increase):
   sizes_diff = diagnose_bloat.ResourceSizesDiff(include_sections=['base'])
   sizes_diff.ProduceDiff(before_dir, after_dir)
 
   return sizes_diff.DetailedResults(), _SizeDelta(
-      'Base Module Size', 'bytes', _MAX_NORMALIZED_INCREASE,
+      'Base Module Size', 'bytes', max_increase,
       sizes_diff.CombinedSizeChangeForSection('base'))
 
 
@@ -318,11 +326,10 @@
       '--local-test',
       action='store_true',
       help='Allow input directories to be diagnose_bloat.py ones.')
-  parser.add_argument('-v', '--verbose', action='store_true')
   args = parser.parse_args()
 
-  if args.verbose:
-    logging.basicConfig(level=logging.INFO)
+  logging.basicConfig(level=logging.INFO,
+                      format='%(levelname).1s %(relativeCreated)6d %(message)s')
 
   before_path = pathlib.Path(args.before_dir)
   after_path = pathlib.Path(args.after_dir)
@@ -350,6 +357,11 @@
       after_path_resolver(f) for f in config['mapping_files']
   ]
 
+  max_size_increase = _MaxSizeIncrease(args.author, args.review_subject)
+  # We do not care as much about method count anymore, so this limit is set
+  # such that it is very unlikely to be hit.
+  max_methods_increase = 200 if '-autoroll' not in args.author else 800
+
   logging.info('Creating Supersize diff')
   supersize_diff_lines, delta_size_info = _CreateSupersizeDiff(
       before_path_resolver(size_filename), after_path_resolver(size_filename),
@@ -358,9 +370,9 @@
   changed_symbols = delta_size_info.raw_symbols.WhereDiffStatusIs(
       models.DIFF_STATUS_UNCHANGED).Inverted()
 
-  # Monitor dex method count since the "multidex limit" is a thing.
   logging.info('Checking dex symbols')
-  dex_delta_lines, dex_delta = _CreateMethodCountDelta(changed_symbols)
+  dex_delta_lines, dex_delta = _CreateMethodCountDelta(changed_symbols,
+                                                       max_methods_increase)
   size_deltas = {dex_delta}
   metrics = {(dex_delta, _DEX_SYMBOLS_LOG)}
 
@@ -388,13 +400,14 @@
   # Normalized APK Size is the main metric we use to monitor binary size.
   logging.info('Creating sizes diff')
   resource_sizes_lines, resource_sizes_delta = (_CreateResourceSizesDelta(
-      args.before_dir, args.after_dir))
+      args.before_dir, args.after_dir, max_size_increase))
   size_deltas.add(resource_sizes_delta)
   metrics.add((resource_sizes_delta, _RESOURCE_SIZES_LOG))
 
   logging.info('Creating base module sizes diff')
   base_resource_sizes_lines, base_resource_sizes_delta = (
-      _CreateBaseModuleResourceSizesDelta(args.before_dir, args.after_dir))
+      _CreateBaseModuleResourceSizesDelta(args.before_dir, args.after_dir,
+                                          max_size_increase))
   size_deltas.add(base_resource_sizes_delta)
   metrics.add((base_resource_sizes_delta, _BASE_RESOURCE_SIZES_LOG))
 
@@ -413,7 +426,6 @@
   passing_deltas = set(d for d in size_deltas if d.IsAllowable())
   failing_deltas = size_deltas - passing_deltas
 
-  is_roller = '-autoroll' in args.author
   failing_checks_text = '\n'.join(d.explanation for d in sorted(failing_deltas))
   passing_checks_text = '\n'.join(d.explanation for d in sorted(passing_deltas))
   checks_text = """\
@@ -429,13 +441,6 @@
 """.format(failing_checks_text, passing_checks_text, _TRYBOT_MD_URL)
 
   status_code = int(bool(failing_deltas))
-
-  # Give rollers a free pass, except for mutable constants.
-  # Mutable constants are rare, and other regressions are generally noticed in
-  # size graphs and can be investigated after-the-fact.
-  if is_roller and mutable_constants_delta not in failing_deltas:
-    status_code = 0
-
   see_docs_lines = ['\n', f'For more details: {_TRYBOT_MD_URL}\n']
 
   summary = '<br>' + checks_text.replace('\n', '<br>')
diff --git a/tools/mac/BUILD.gn b/tools/mac/BUILD.gn
index 5527752..097e2edf7 100644
--- a/tools/mac/BUILD.gn
+++ b/tools/mac/BUILD.gn
@@ -5,12 +5,12 @@
 group("mac") {
   testonly = true
   deps = [
-    ":dsc_extract",
+    ":dsc_extractor",
     "power:all",
   ]
 }
 
-executable("dsc_extract") {
+executable("dsc_extractor") {
   testonly = true
-  sources = [ "dsc_extract.cc" ]
+  sources = [ "dsc_extractor.cc" ]
 }
diff --git a/tools/mac/dsc_extract.cc b/tools/mac/dsc_extractor.cc
similarity index 100%
rename from tools/mac/dsc_extract.cc
rename to tools/mac/dsc_extractor.cc
diff --git a/tools/make_gtest_filter.py b/tools/make_gtest_filter.py
index 1305456..9e8bb4a 100755
--- a/tools/make_gtest_filter.py
+++ b/tools/make_gtest_filter.py
@@ -243,6 +243,7 @@
     # Note: Test names have the following structures:
     #  * FixtureName.TestName
     #  * InstantiationName/FixtureName.TestName/##
+    #  * FixtureName.TestName/##
     # Since this script doesn't parse instantiations, we generate filters to
     # match either regular tests or instantiated tests.
     if args.wildcard_compress:
@@ -251,10 +252,12 @@
     elif args.class_only:
       fixtures = set([t.split('.')[0] for t in tests])
       test_filters = [c + '.*' for c in fixtures] + \
-          ['*/' + c + '.*/*' for c in fixtures]
+          ['*/' + c + '.*/*' for c in fixtures] + \
+          [c + '.*/*' for c in fixtures]
     else:
       test_filters = [c for c in tests] + \
-          ['*/' + c + '/*' for c in tests]
+          ['*/' + c + '/*' for c in tests] + \
+          [c + '/*' for c in tests]
 
   if args.as_exclusions:
     test_filters = ['-' + x for x in test_filters]
diff --git a/tools/mb/mb_config.pyl b/tools/mb/mb_config.pyl
index 47080392..acd81dc 100644
--- a/tools/mb/mb_config.pyl
+++ b/tools/mb/mb_config.pyl
@@ -588,6 +588,7 @@
       'codesearch-gen-chromium-android': 'codesearch_gen_chromium_android_bot',
       'codesearch-gen-chromium-chromiumos': 'codesearch_gen_chromium_chromiumos_bot',
       'codesearch-gen-chromium-fuchsia': 'codesearch_gen_chromium_fuchsia_bot',
+      'codesearch-gen-chromium-ios': 'codesearch_gen_chromium_ios_bot',
       'codesearch-gen-chromium-lacros': 'codesearch_gen_chromium_lacros_bot',
       'codesearch-gen-chromium-linux': 'codesearch_gen_chromium_bot',
       'codesearch-gen-chromium-mac': 'codesearch_gen_chromium_mac_bot',
@@ -1145,6 +1146,7 @@
       'gen-android-try': 'codesearch_gen_chromium_android_bot',
       'gen-chromiumos-try': 'codesearch_gen_chromium_chromiumos_bot',
       'gen-fuchsia-try': 'codesearch_gen_chromium_fuchsia_bot',
+      'gen-ios-try': 'codesearch_gen_chromium_ios_bot',
       'gen-lacros-try': 'codesearch_gen_chromium_lacros_bot',
       'gen-linux-try': 'codesearch_gen_chromium_bot',
       'gen-mac-try': 'codesearch_gen_chromium_mac_bot',
@@ -2444,6 +2446,10 @@
       'codesearch_release', 'fuchsia',
     ],
 
+    'codesearch_gen_chromium_ios_bot': [
+      'codesearch', 'ios',
+    ],
+
     # Lacros uses different gn args to build for chromeOS device vs. Linux. For
     # simplicity, we only generate codesearch x-refs for lacros on Linux.
     'codesearch_gen_chromium_lacros_bot': [
diff --git a/tools/mb/mb_config_expectations/chromium.infra.codesearch.json b/tools/mb/mb_config_expectations/chromium.infra.codesearch.json
index 0132555..a058e83c 100644
--- a/tools/mb/mb_config_expectations/chromium.infra.codesearch.json
+++ b/tools/mb/mb_config_expectations/chromium.infra.codesearch.json
@@ -38,6 +38,19 @@
       "use_goma": true
     }
   },
+  "codesearch-gen-chromium-ios": {
+    "gn_args": {
+      "blink_enable_generated_code_formatting": true,
+      "clang_use_chrome_plugins": false,
+      "enable_kythe_annotations": true,
+      "is_clang": true,
+      "is_component_build": true,
+      "is_debug": true,
+      "symbol_level": 1,
+      "target_os": "ios",
+      "use_goma": true
+    }
+  },
   "codesearch-gen-chromium-lacros": {
     "gn_args": {
       "blink_enable_generated_code_formatting": true,
diff --git a/tools/mb/mb_config_expectations/tryserver.chromium.codesearch.json b/tools/mb/mb_config_expectations/tryserver.chromium.codesearch.json
index 3a3dda6..2b8c58a 100644
--- a/tools/mb/mb_config_expectations/tryserver.chromium.codesearch.json
+++ b/tools/mb/mb_config_expectations/tryserver.chromium.codesearch.json
@@ -38,6 +38,19 @@
       "use_goma": true
     }
   },
+  "gen-ios-try": {
+    "gn_args": {
+      "blink_enable_generated_code_formatting": true,
+      "clang_use_chrome_plugins": false,
+      "enable_kythe_annotations": true,
+      "is_clang": true,
+      "is_component_build": true,
+      "is_debug": true,
+      "symbol_level": 1,
+      "target_os": "ios",
+      "use_goma": true
+    }
+  },
   "gen-lacros-try": {
     "gn_args": {
       "blink_enable_generated_code_formatting": true,
diff --git a/tools/metrics/actions/actions.xml b/tools/metrics/actions/actions.xml
index 9b2fb30..c4cee98 100644
--- a/tools/metrics/actions/actions.xml
+++ b/tools/metrics/actions/actions.xml
@@ -36738,6 +36738,9 @@
 </action>
 
 <action-suffix separator="_" ordering="suffix">
+  <suffix name="AdaptiveButtonInTopToolbarCustomization_AddToBookmarks"
+      label="For AdaptiveButtonInTopToolbarCustomization add to bookmarks
+             feature."/>
   <suffix name="AdaptiveButtonInTopToolbarCustomization_NewTab"
       label="For AdaptiveButtonInTopToolbarCustomization new tab feature."/>
   <suffix name="AdaptiveButtonInTopToolbarCustomization_Share"
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 5e6fc16a..6d181c0 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -176,6 +176,18 @@
   <int value="2" label="ChromeOS quick settings cast menu"/>
 </enum>
 
+<enum name="AccessCodeCastDiscoveryTypeAndSource">
+  <int value="0" label="Unknown discovery type or source"/>
+  <int value="1" label="Saved device and Presentation source"/>
+  <int value="2" label="Saved device and Tab mirror source"/>
+  <int value="3" label="Saved device and Desktop mirror source"/>
+  <int value="4" label="Saved device and Remote playback source"/>
+  <int value="5" label="New device and Presentation source"/>
+  <int value="6" label="New device and Tab mirror source"/>
+  <int value="7" label="New device and Desktop mirror source"/>
+  <int value="8" label="New device and Remote playback source"/>
+</enum>
+
 <enum name="AccessCodeCastUiTabSwitcherUsage">
   <int value="0" label="Tab switcher UI shown and not used"/>
   <int value="1" label="Tab switcher UI shown and used to switch tabs"/>
@@ -1452,6 +1464,7 @@
   <int value="6" label="Price tracking"/>
   <int value="7" label="Reader mode"/>
   <int value="8" label="Translate"/>
+  <int value="9" label="Add to bookmarks"/>
 </enum>
 
 <enum name="AdaptiveToolbarRadioButtonState">
@@ -1464,6 +1477,8 @@
   <int value="6" label="Voice"/>
   <int value="7" label="Translate"/>
   <int value="8" label="Auto with translate"/>
+  <int value="9" label="Add to bookmarks"/>
+  <int value="10" label="Auto with add to bookmarks"/>
 </enum>
 
 <enum name="AdaptiveToolbarSegmentSwitch">
@@ -44879,6 +44894,12 @@
              (Lacros only)"/>
   <int value="2"
       label="Quit at end: window closed, experience considered 'finished'"/>
+  <int value="3"
+      label="Abort task: user runs experience while it's already running.
+             Experience not considered 'finished'"/>
+  <int value="4"
+      label="Abandon flow: user does something to abandon the FRE (open a new
+             window etc..), experience is considered 'finished'"/>
 </enum>
 
 <enum name="FirstRunLaunchSource">
@@ -57891,7 +57912,10 @@
   <int value="-1986814580" label="om"/>
   <int value="-1920649864" label="an"/>
   <int value="-1898669966" label="zh-CN"/>
-  <int value="-1895779836" label="empty string"/>
+  <int value="-1895779836" label="empty string">
+    Reported as page source language when language detection code was not run
+    (i.e. page closed or navigated away before running).
+  </int>
   <int value="-1872667487" label="rm"/>
   <int value="-1855113037" label="vi"/>
   <int value="-1835493637" label="ce"/>
@@ -60672,6 +60696,7 @@
   <int value="-891856063" label="MidiManagerAndroid:enabled"/>
   <int value="-890224928" label="DownloadBubbleV2:enabled"/>
   <int value="-889670978" label="AssistantRoutines:disabled"/>
+  <int value="-888336510" label="NtpSingleRowShortcuts:enabled"/>
   <int value="-887101831" label="StoreHoursAndroid:disabled"/>
   <int value="-887094098" label="ForcedColors:enabled"/>
   <int value="-886912558" label="ChromeHomePromo:enabled"/>
@@ -61146,6 +61171,8 @@
   <int value="-632030508" label="NativeWindowNavButtons:disabled"/>
   <int value="-631740127" label="inert-visual-viewport"/>
   <int value="-631614101" label="CameraSystemWebApp:enabled"/>
+  <int value="-631136107"
+      label="PageContentAnnotationsPersistSalientImageMetadata:disabled"/>
   <int value="-630256330" label="EnableDrDc:enabled"/>
   <int value="-629084845" label="UseToastManager:disabled"/>
   <int value="-629041881" label="MuteNotificationsDuringScreenShare:disabled"/>
@@ -61745,6 +61772,8 @@
   <int value="-290329565" label="CrosVmCupsProxy:disabled"/>
   <int value="-288316828" label="enable-delegated-renderer"/>
   <int value="-286603268" label="hide-android-files-in-files-app"/>
+  <int value="-285499775"
+      label="AdaptiveButtonInTopToolbarAddToBookmarks:enabled"/>
   <int value="-284694765" label="JourneysIncludeSyncedVisits:disabled"/>
   <int value="-284547865" label="UnifiedConsent:enabled"/>
   <int value="-284470280" label="ShelfPalmRejectionTouchArea:enabled"/>
@@ -61777,6 +61806,8 @@
       label="MaintainShelfStateWhenEnteringOverview:disabled"/>
   <int value="-273570157" label="EnableBloom:enabled"/>
   <int value="-273534119" label="LacrosAuraCapture:enabled"/>
+  <int value="-272668067"
+      label="PageContentAnnotationsPersistSalientImageMetadata:enabled"/>
   <int value="-272544597"
       label="AccessibilitySelectToSpeakContextMenuOption:enabled"/>
   <int value="-272266925" label="ForceStartupSigninPromo:enabled"/>
@@ -62146,6 +62177,7 @@
   <int value="-64839201" label="SyncUSSAutofillWalletData:disabled"/>
   <int value="-64824628" label="VizHitTestSurfaceLayer:disabled"/>
   <int value="-64747770" label="IncognitoDownloadsWarning:disabled"/>
+  <int value="-63207112" label="NtpSingleRowShortcuts:disabled"/>
   <int value="-59530055" label="ChromeVoxArcSupport:enabled"/>
   <int value="-59401847" label="ContentSuggestionsLargeThumbnail:disabled"/>
   <int value="-58338160" label="ChromeShareLongScreenshot:enabled"/>
@@ -62339,6 +62371,7 @@
   <int value="38904620" label="TabSwitcherLongpressMenu:enabled"/>
   <int value="39035791"
       label="AutofillTouchToFillForCreditCardsAndroid:enabled"/>
+  <int value="39822765" label="ShimlessRMADiagnosticPage:disabled"/>
   <int value="41881657" label="MessagesForAndroidAdsBlocked:enabled"/>
   <int value="41990393" label="ContextMenuGoogleLensChip:enabled"/>
   <int value="42098736" label="TranslateAndroidManualTrigger:disabled"/>
@@ -62984,6 +63017,7 @@
   <int value="392782890" label="SafeBrowsingTelemetryForApkDownloads:enabled"/>
   <int value="393569083" label="OmniboxMatchToolbarAndStatusBarColor:enabled"/>
   <int value="393704200" label="account-consistency"/>
+  <int value="393869264" label="ShimlessRMADiagnosticPage:enabled"/>
   <int value="394384982" label="GaiaIdCacheInAccountManagerFacade:enabled"/>
   <int value="395148768" label="AutofillFillIbanFields:disabled"/>
   <int value="397275669" label="QuickCommands:disabled"/>
@@ -63244,6 +63278,8 @@
   <int value="550387510" label="NTPAssetDownloadSuggestions:disabled"/>
   <int value="550411207" label="enable-webgpu-developer-features"/>
   <int value="550741462" label="WebAppBorderless:disabled"/>
+  <int value="552143503"
+      label="AdaptiveButtonInTopToolbarAddToBookmarks:disabled"/>
   <int value="552158955" label="NotificationPermissionBottomSheet:enabled"/>
   <int value="552317551" label="MediaAppHandlesPdf:disabled"/>
   <int value="552421509" label="AssistMultiWordExpanded:enabled"/>
@@ -63874,6 +63910,7 @@
   <int value="904159815" label="VideoPlayerAppHidden:disabled"/>
   <int value="904437796" label="OmniboxBlurWithEscape:disabled"/>
   <int value="904811479" label="query-tiles-enable-trending"/>
+  <int value="907051864" label="UseMultiPlaneFormatForHardwareVideo:disabled"/>
   <int value="907056713" label="DetectFormSubmissionOnFormClear:enabled"/>
   <int value="908302031"
       label="OmniboxUIExperimentElideSuggestionUrlAfterHost:enabled"/>
@@ -64223,6 +64260,7 @@
   <int value="1102035446"
       label="AutofillUpstreamAuthenticatePreflightCall:enabled"/>
   <int value="1102780374" label="ClickToCallDetectionV2:disabled"/>
+  <int value="1104206686" label="UseMultiPlaneFormatForHardwareVideo:enabled"/>
   <int value="1104948452" label="manual-enhanced-bookmarks-optout"/>
   <int value="1105182444"
       label="ChromeCleanupScanCompletedNotification:disabled"/>
@@ -92855,16 +92893,6 @@
   <int value="8" label="Throttle prevented the prefetch"/>
 </enum>
 
-<enum name="SearchPrefetchResponseDataReaderStatus">
-  <int value="0" label="kCreated"/>
-  <int value="1" label="kStarted"/>
-  <int value="2" label="kCompleted"/>
-  <int value="3" label="kFailedWithErrorCode"/>
-  <int value="4" label="kDataWritingFailure"/>
-  <int value="5" label="kCanceledByClient"/>
-  <int value="6" label="kCanceledByLoader"/>
-</enum>
-
 <enum name="SearchPrefetchServingReason">
   <int value="0" label="Served a prefetch response"/>
   <int value="1" label="Search engine not set"/>
diff --git a/tools/metrics/histograms/metadata/autofill/histograms.xml b/tools/metrics/histograms/metadata/autofill/histograms.xml
index e2c4d7e..55b89ad6 100644
--- a/tools/metrics/histograms/metadata/autofill/histograms.xml
+++ b/tools/metrics/histograms/metadata/autofill/histograms.xml
@@ -2980,7 +2980,7 @@
 
 <histogram
     name="Autofill.OfferNotificationBubblePromoCodeButtonClicked.{BubbleType}"
-    enum="BooleanClicked" expires_after="2023-05-01">
+    enum="BooleanClicked" expires_after="2024-04-01">
   <owner>jsaul@google.com</owner>
   <owner>siyua@chromium.org</owner>
   <owner>payments-autofill-team@google.com</owner>
@@ -3007,7 +3007,7 @@
 </histogram>
 
 <histogram name="Autofill.OfferNotificationBubbleSuppressed.{BubbleType}"
-    enum="BooleanSuppressed" expires_after="2023-05-01">
+    enum="BooleanSuppressed" expires_after="2024-04-01">
   <owner>jsaul@google.com</owner>
   <owner>yuezhanggg@chromium.org</owner>
   <owner>siyua@chromium.org</owner>
@@ -3570,7 +3570,7 @@
 </histogram>
 
 <histogram name="Autofill.SaveCardCardholderNamePrefilled" enum="Boolean"
-    expires_after="2023-05-01">
+    expires_after="2024-04-01">
   <owner>jsaul@google.com</owner>
   <owner>payments-autofill-team@google.com</owner>
   <summary>
@@ -3581,7 +3581,7 @@
 </histogram>
 
 <histogram name="Autofill.SaveCardCardholderNameWasEdited" enum="Boolean"
-    expires_after="2023-05-01">
+    expires_after="2024-04-01">
   <owner>jsaul@google.com</owner>
   <owner>payments-autofill-team@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/cryptohome/histograms.xml b/tools/metrics/histograms/metadata/cryptohome/histograms.xml
index a19ae9a..11540ac9 100644
--- a/tools/metrics/histograms/metadata/cryptohome/histograms.xml
+++ b/tools/metrics/histograms/metadata/cryptohome/histograms.xml
@@ -749,7 +749,7 @@
 </histogram>
 
 <histogram name="Cryptohome.TimeToAuthSessionRemoveAuthFactorUSS" units="ms"
-    expires_after="2023-05-01">
+    expires_after="2023-11-01">
   <owner>thomascedeno@google.com</owner>
   <owner>cros-hwsec+uma@chromium.org</owner>
   <summary>
@@ -761,7 +761,7 @@
 </histogram>
 
 <histogram name="Cryptohome.TimeToAuthSessionRemoveAuthFactorVK" units="ms"
-    expires_after="2023-05-01">
+    expires_after="2023-11-01">
   <owner>thomascedeno@google.com</owner>
   <owner>cros-hwsec+uma@chromium.org</owner>
   <summary>
@@ -795,7 +795,7 @@
 </histogram>
 
 <histogram name="Cryptohome.TimeToCreatePersistentUser" units="ms"
-    expires_after="2023-05-01">
+    expires_after="2023-11-01">
   <owner>thomascedeno@google.com</owner>
   <owner>cros-hwsec+uma@chromium.org</owner>
   <summary>
@@ -1068,7 +1068,7 @@
 </histogram>
 
 <histogram name="Cryptohome.{AuthSessionFunction}.{AuthBlockType}" units="ms"
-    expires_after="2023-05-01">
+    expires_after="2023-11-01">
   <owner>thomascedeno@google.com</owner>
   <owner>cros-hwsec+uma@chromium.org</owner>
   <summary>
@@ -1104,7 +1104,7 @@
 </histogram>
 
 <histogram name="Cryptohome.{AuthSessionLifetime}.{Type}" units="ms"
-    expires_after="2023-05-01">
+    expires_after="2023-11-01">
   <owner>thomascedeno@google.com</owner>
   <owner>cros-hwsec+uma@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/feature_engagement/histograms.xml b/tools/metrics/histograms/metadata/feature_engagement/histograms.xml
index c833452..6bd7fdd 100644
--- a/tools/metrics/histograms/metadata/feature_engagement/histograms.xml
+++ b/tools/metrics/histograms/metadata/feature_engagement/histograms.xml
@@ -28,6 +28,8 @@
 </variants>
 
 <variants name="IPHFeature">
+  <variant name="IPH_AdaptiveButtonInTopToolbarCustomization_AddToBookmarks"
+      summary="the add to bookmarks adaptive button in the top toolbar"/>
   <variant name="IPH_AdaptiveButtonInTopToolbarCustomization_NewTab"
       summary="the new tab adaptive button in the top toolbar"/>
   <variant name="IPH_AdaptiveButtonInTopToolbarCustomization_Share"
diff --git a/tools/metrics/histograms/metadata/media/histograms.xml b/tools/metrics/histograms/metadata/media/histograms.xml
index 49e5b4b..7e67422 100644
--- a/tools/metrics/histograms/metadata/media/histograms.xml
+++ b/tools/metrics/histograms/metadata/media/histograms.xml
@@ -2407,7 +2407,7 @@
   <owner>xhwang@chromium.org</owner>
   <owner>media-dev@chromium.org</owner>
   <summary>
-    Whether a MediaFoundationService process crash is within 2 seconds after
+    Whether a MediaFoundationService process crash is within 5 seconds after
     power or display change. Reported when the MediaFoundationService process
     crashes.
   </summary>
@@ -2420,7 +2420,7 @@
   <owner>media-dev@chromium.org</owner>
   <summary>
     HRESULTs associated with errors happened in the MediaFoundationService
-    process within 2 seconds after power or display change.
+    process within 5 seconds after power or display change.
   </summary>
 </histogram>
 
@@ -2435,6 +2435,17 @@
   </summary>
 </histogram>
 
+<histogram name="Media.EME.MediaFoundationService.HardwareContextReset"
+    enum="BooleanAfterPowerOrDisplayChange" expires_after="2023-05-07">
+  <owner>xhwang@chromium.org</owner>
+  <owner>media-dev@chromium.org</owner>
+  <summary>
+    Whether a HardwareContextReset event happened in the MediaFoundationService
+    process within 5 seconds after power or display change. Reported whenever
+    such an event happens.
+  </summary>
+</histogram>
+
 <histogram name="Media.EME.MediaFoundationService.IsKeySystemSupported"
     units="ms" expires_after="2023-08-08">
   <owner>xhwang@chromium.org</owner>
@@ -5798,7 +5809,7 @@
 </histogram>
 
 <histogram name="Media.WebMediaPlayerImpl.HLS.HasAccessControl" enum="Boolean"
-    expires_after="2023-04-01">
+    expires_after="2023-08-08">
   <owner>cassew@chromium.org</owner>
   <owner>tguilbert@chromium.org</owner>
   <owner>media-dev@chromium.org</owner>
@@ -5811,7 +5822,7 @@
 </histogram>
 
 <histogram name="Media.WebMediaPlayerImpl.HLS.IsCorsCrossOrigin" enum="Boolean"
-    expires_after="2023-04-01">
+    expires_after="2023-08-08">
   <owner>cassew@chromium.org</owner>
   <owner>tguilbert@chromium.org</owner>
   <owner>media-dev@chromium.org</owner>
@@ -5837,7 +5848,7 @@
 </histogram>
 
 <histogram name="Media.WebMediaPlayerImpl.HLS{Variant}.MimeType"
-    enum="MimeTypeOfHlsManifest" expires_after="2023-04-01">
+    enum="MimeTypeOfHlsManifest" expires_after="2023-08-08">
   <owner>cassew@chromium.org</owner>
   <owner>lukasza@chromium.org</owner>
   <owner>media-dev@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/omnibox/histograms.xml b/tools/metrics/histograms/metadata/omnibox/histograms.xml
index 2eb31a55..8d51d1b 100644
--- a/tools/metrics/histograms/metadata/omnibox/histograms.xml
+++ b/tools/metrics/histograms/metadata/omnibox/histograms.xml
@@ -1538,18 +1538,6 @@
   </token>
 </histogram>
 
-<histogram name="Omnibox.SearchPreload.ResponseDataReaderFinalStatus.Prerender"
-    enum="SearchPrefetchResponseDataReaderStatus" expires_after="2023-08-02">
-  <owner>lingqi@chromium.org</owner>
-  <owner>ryansturm@chromium.org</owner>
-  <owner>chrome-prerendering@google.com</owner>
-  <summary>
-    Tracks whether the StreamingSearchPrefetchURLLoader::ResponseReader can
-    successfully serving to prerender clients. Recorded when a
-    `StreamingSearchPrefetchURLLoader::ResponseReader` is destroyed.
-  </summary>
-</histogram>
-
 <histogram name="Omnibox.SearchProviderMatches" units="units"
     expires_after="2022-09-11">
   <obsolete>
diff --git a/tools/metrics/histograms/metadata/others/histograms.xml b/tools/metrics/histograms/metadata/others/histograms.xml
index 53e37c1f..bcd7505 100644
--- a/tools/metrics/histograms/metadata/others/histograms.xml
+++ b/tools/metrics/histograms/metadata/others/histograms.xml
@@ -254,6 +254,17 @@
   </summary>
 </histogram>
 
+<histogram name="AccessCodeCast.Session.RouteDiscoveryTypeAndSource"
+    enum="AccessCodeCastDiscoveryTypeAndSource" expires_after="2023-08-20">
+  <owner>bzielinski@google.com</owner>
+  <owner>cros-edu-eng@google.com</owner>
+  <summary>
+    The discovery type of the associated sink and the cast media source for an
+    access code route. Recorded when at the start of a local access code route.
+    Logged only on desktop platforms.
+  </summary>
+</histogram>
+
 <histogram name="AccessCodeCast.Session.RouteDuration" units="ms"
     expires_after="2023-08-13">
   <owner>bzielinski@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/sharing/histograms.xml b/tools/metrics/histograms/metadata/sharing/histograms.xml
index 9b71023f..b78a35a6 100644
--- a/tools/metrics/histograms/metadata/sharing/histograms.xml
+++ b/tools/metrics/histograms/metadata/sharing/histograms.xml
@@ -182,6 +182,17 @@
   </summary>
 </histogram>
 
+<histogram name="Sharing.PreparePreviewFaviconDuration" units="ms"
+    expires_after="2023-07-21">
+  <owner>wenyufu@chromium.org</owner>
+  <owner>src/chrome/browser/share/OWNERS</owner>
+  <summary>
+    The time to download the favicon as the image preview for sharing an link
+    using Android share sheet. Recorded when sharing a link with Android share
+    sheet only.
+  </summary>
+</histogram>
+
 <histogram name="Sharing.RemoteCopyDecodeImageTime" units="ms"
     expires_after="M97">
   <owner>mvanouwerkerk@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/startup/histograms.xml b/tools/metrics/histograms/metadata/startup/histograms.xml
index afaa578..e6991b8 100644
--- a/tools/metrics/histograms/metadata/startup/histograms.xml
+++ b/tools/metrics/histograms/metadata/startup/histograms.xml
@@ -186,11 +186,31 @@
 
     - For WebApps, PWAs or WebApks.
 
+    Bugs:
+
+    - This metric does not get recorded when the first navigation commit happens
+    before post-native initialization. This behaviour is not intentional, see
+    crbug.com/1273097. TimeToFirstVisibleContent2 addresses this issue.
+
     This histogram is of special interest to the chrome-analysis-team@. Do not
     change its semantics or retire it without talking to them first.
   </summary>
 </histogram>
 
+<histogram name="Startup.Android.Cold.TimeToFirstVisibleContent2" units="ms"
+    expires_after="2023-09-22">
+  <owner>yfriedman@chromium.org</owner>
+  <owner>pasko@chromium.org</owner>
+  <owner>fredmello@chromium.org</owner>
+  <summary>
+    The time from Chrome tabbed activity creation to the moment the Chrome first
+    appears ready.
+
+    Currently experimental. Has the same semantics as
+    Startup.Android.Cold.TimeToFirstVisibleContent, without the known bug.
+  </summary>
+</histogram>
+
 <histogram name="Startup.Android.Cold.TimeToVisibleContent" units="ms"
     expires_after="2023-08-20">
   <owner>ckitagawa@chromium.org</owner>
diff --git a/tools/metrics/structured/structured.xml b/tools/metrics/structured/structured.xml
index 32df371..52f8f56 100644
--- a/tools/metrics/structured/structured.xml
+++ b/tools/metrics/structured/structured.xml
@@ -671,6 +671,12 @@
         Name of the app that was launched. Names are UTF-16 string encoding.
       </summary>
     </metric>
+    <metric name="ResultCategory" type="int">
+      <summary>
+        Enum representing the category of app the result is. This enum should map to
+        ash::SearchResultType.
+      </summary>
+    </metric>
   </event>
 
   <event name="UserLogin">
diff --git a/ui/accessibility/accessibility_features.cc b/ui/accessibility/accessibility_features.cc
index 68f8ab57f..e8ef83d 100644
--- a/ui/accessibility/accessibility_features.cc
+++ b/ui/accessibility/accessibility_features.cc
@@ -263,15 +263,6 @@
   return base::FeatureList::IsEnabled(
       ::features::kAccessibilityFormControlsMode);
 }
-
-BASE_FEATURE(kOptimizeAccessibilityUiThreadWork,
-             "OptimizeAccessibilityUiThreadWork",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
-bool IsOptimizeAccessibilityUiThreadWorkEnabled() {
-  return base::FeatureList::IsEnabled(
-      ::features::kOptimizeAccessibilityUiThreadWork);
-}
 #endif  // BUILDFLAG(IS_ANDROID)
 
 #if !BUILDFLAG(IS_ANDROID)
diff --git a/ui/accessibility/accessibility_features.h b/ui/accessibility/accessibility_features.h
index 4e288c0b..65fddcdf 100644
--- a/ui/accessibility/accessibility_features.h
+++ b/ui/accessibility/accessibility_features.h
@@ -207,10 +207,6 @@
 // Returns true if the form controls AXMode is enabled.
 AX_BASE_EXPORT bool IsAccessibilityFormControlsAXModeEnabled();
 
-AX_BASE_EXPORT BASE_DECLARE_FEATURE(kOptimizeAccessibilityUiThreadWork);
-
-bool IsOptimizeAccessibilityUiThreadWorkEnabled();
-
 #endif  // BUILDFLAG(IS_ANDROID)
 
 #if !BUILDFLAG(IS_ANDROID)
diff --git a/ui/base/interaction/interactive_test.h b/ui/base/interaction/interactive_test.h
index b671c724..c6737fb 100644
--- a/ui/base/interaction/interactive_test.h
+++ b/ui/base/interaction/interactive_test.h
@@ -396,6 +396,11 @@
     return *private_test_impl_;
   }
 
+  // Adds a step or steps to the end of an existing MultiStep. Shorthand for
+  // making one or more calls to `std::vector::emplace_back`.
+  static void AddStep(MultiStep& dest, StepBuilder src);
+  static void AddStep(MultiStep& dest, MultiStep src);
+
  private:
   // Implementation for RunTestSequenceInContext().
   bool RunTestSequenceImpl(ElementContext context,
@@ -406,9 +411,6 @@
   template <typename T>
   static void AddStep(InteractionSequence::Builder& builder, T&& step);
 
-  static void AddStep(MultiStep& dest, StepBuilder src);
-  static void AddStep(MultiStep& dest, MultiStep src);
-
   std::unique_ptr<internal::InteractiveTestPrivate> private_test_impl_;
 };
 
diff --git a/ui/gl/init/gl_display_initializer.cc b/ui/gl/init/gl_display_initializer.cc
index 09ecfaa..fb368d05 100644
--- a/ui/gl/init/gl_display_initializer.cc
+++ b/ui/gl/init/gl_display_initializer.cc
@@ -56,16 +56,6 @@
       (!command_line->HasSwitch(switches::kUseANGLE) ||
        requested_renderer == kANGLEImplementationDefaultName);
 
-  // If we're already requesting an ANGLE implementation, use it instead of the
-  // default.
-  // if ((requested_renderer.empty() ||
-  //      requested_renderer == kANGLEImplementationDefaultName) &&
-  //     gl::GetGLImplementationParts().gl == gl::kGLImplementationEGLANGLE) {
-  //   use_angle_default = false;
-  //   requested_renderer =
-  //       GetGLImplementationANGLEName(gl::GetGLImplementationParts());
-  // }
-
   if (supports_angle_null &&
       (requested_renderer == kANGLEImplementationNullName ||
        gl::GetANGLEImplementation() == ANGLEImplementation::kNull)) {
diff --git a/ui/message_center/public/cpp/notification_types.h b/ui/message_center/public/cpp/notification_types.h
index d6da1a3c..ec113cb8 100644
--- a/ui/message_center/public/cpp/notification_types.h
+++ b/ui/message_center/public/cpp/notification_types.h
@@ -12,7 +12,7 @@
 enum NotificationType {
   NOTIFICATION_TYPE_SIMPLE = 0,
   DEPRECATED_NOTIFICATION_TYPE_BASE_FORMAT =
-      1,  // Use NOTIFICATION_TYPE_SIMPLE instead.
+      1,  // Use `NOTIFICATION_TYPE_SIMPLE` instead.
   NOTIFICATION_TYPE_IMAGE = 2,
   NOTIFICATION_TYPE_MULTIPLE = 3,
   NOTIFICATION_TYPE_PROGRESS = 4,  // Notification with progress bar.
@@ -25,14 +25,16 @@
 enum NotificationPriority {
   MIN_PRIORITY = -2,
   LOW_PRIORITY = -1,
+  // In ChromeOS, if priority < `DEFAULT_PRIORITY`, the notification will be
+  // silently added to the tray (no pop-up will be shown).
   DEFAULT_PRIORITY = 0,
-  // Priorities > |DEFAULT_PRIORITY| have the capability to wake the display up
+  // Priorities > `DEFAULT_PRIORITY` have the capability to wake the display up
   // if it was off.
   HIGH_PRIORITY = 1,
   MAX_PRIORITY = 2,
 
   // Top priority for system-level notifications.. This can't be set from
-  // kPriorityKey, instead you have to call SetSystemPriority() of
+  // `kPriorityKey`, instead you have to call `SetSystemPriority()` of
   // Notification object.
   SYSTEM_PRIORITY = 3,
 };
diff --git a/ui/ozone/platform/wayland/gpu/gbm_pixmap_wayland.cc b/ui/ozone/platform/wayland/gpu/gbm_pixmap_wayland.cc
index 1a5016b..fd8e632 100644
--- a/ui/ozone/platform/wayland/gpu/gbm_pixmap_wayland.cc
+++ b/ui/ozone/platform/wayland/gpu/gbm_pixmap_wayland.cc
@@ -189,11 +189,8 @@
 
 gfx::NativePixmapHandle GbmPixmapWayland::ExportHandle() {
   gfx::NativePixmapHandle handle;
-  gfx::BufferFormat format = GetBufferFormat();
 
-  // TODO(dcastagna): Use gbm_bo_get_plane_count once all the formats we use are
-  // supported by gbm.
-  const size_t num_planes = gfx::NumberOfPlanesForLinearBufferFormat(format);
+  const size_t num_planes = gbm_bo_->GetNumPlanes();
   std::vector<base::ScopedFD> scoped_fds(num_planes);
   for (size_t i = 0; i < num_planes; ++i) {
     scoped_fds[i] = base::ScopedFD(HANDLE_EINTR(dup(GetDmaBufFd(i))));
diff --git a/ui/views/metadata/view_factory.h b/ui/views/metadata/view_factory.h
index 466475d3..6c6db5b 100644
--- a/ui/views/metadata/view_factory.h
+++ b/ui/views/metadata/view_factory.h
@@ -25,6 +25,7 @@
 class BaseViewBuilderT : public internal::ViewBuilderCore {
  public:
   using ViewClass_ = typename internal::ViewClassTrait<Builder>::ViewClass_;
+  using AfterBuildCallback = base::OnceCallback<void(ViewClass_*)>;
   using ConfigureCallback = base::OnceCallback<void(ViewClass_*)>;
   BaseViewBuilderT() { view_ = std::make_unique<ViewClass_>(); }
   explicit BaseViewBuilderT(std::unique_ptr<ViewClass_> view) {
@@ -35,6 +36,26 @@
   BaseViewBuilderT& operator=(BaseViewBuilderT&&) = default;
   ~BaseViewBuilderT() override = default;
 
+  Builder& AfterBuild(AfterBuildCallback after_build_callback) & {
+    // Allow multiple after build callbacks by chaining them.
+    if (after_build_callback_) {
+      after_build_callback_ = base::BindOnce(
+          [](AfterBuildCallback previous_callback,
+             AfterBuildCallback current_callback, ViewClass_* root_view) {
+            std::move(previous_callback).Run(root_view);
+            std::move(current_callback).Run(root_view);
+          },
+          std::move(after_build_callback_), std::move(after_build_callback));
+    } else {
+      after_build_callback_ = std::move(after_build_callback);
+    }
+    return *static_cast<Builder*>(this);
+  }
+
+  Builder&& AfterBuild(AfterBuildCallback after_build_callback) && {
+    return std::move(this->AfterBuild(std::move(after_build_callback)));
+  }
+
   template <typename ViewPtr>
   Builder& CopyAddressTo(ViewPtr* view_address) & {
     *view_address = view_ ? view_.get() : root_view_.get();
@@ -104,6 +125,7 @@
     SetProperties(view_.get());
     DoCustomConfigure(view_.get());
     CreateChildren(view_.get());
+    DoAfterBuild(view_.get());
     return std::move(view_);
   }
 
@@ -113,6 +135,7 @@
     SetProperties(root_view_);
     DoCustomConfigure(root_view_);
     CreateChildren(root_view_);
+    DoAfterBuild(root_view_);
   }
 
   template <typename T>
@@ -188,6 +211,12 @@
     return *static_cast<Builder*>(this);
   }
 
+  void DoAfterBuild(ViewClass_* view) {
+    if (after_build_callback_) {
+      std::move(after_build_callback_).Run(view);
+    }
+  }
+
   void DoCustomConfigure(ViewClass_* view) {
     if (configure_callback_)
       std::move(configure_callback_).Run(view);
@@ -195,6 +224,12 @@
 
   std::unique_ptr<View> DoBuild() override { return std::move(*this).Build(); }
 
+  // Optional callback invoked right after calling `CreateChildren()`. This
+  // allows additional configuration of the view not easily covered by the
+  // builder after all addresses have been copied, properties have been set,
+  // and children have themselves been built and added.
+  AfterBuildCallback after_build_callback_;
+
   // Optional callback invoked right before calling CreateChildren. This allows
   // any additional configuration of the view not easily covered by the builder.
   ConfigureCallback configure_callback_;
diff --git a/ui/views/metadata/view_factory_unittest.cc b/ui/views/metadata/view_factory_unittest.cc
index 4ee8dd0..810fa931 100644
--- a/ui/views/metadata/view_factory_unittest.cc
+++ b/ui/views/metadata/view_factory_unittest.cc
@@ -8,6 +8,7 @@
 
 #include "base/functional/bind.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/test/mock_callback.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/skia/include/core/SkColor.h"
 #include "ui/accessibility/ax_enums.mojom.h"
@@ -238,19 +239,59 @@
   EXPECT_EQ(cancel_button, view->children()[1]);
 }
 
-TEST_F(ViewFactoryTest, TestCustomConfigureChaining) {
-  int callback_count = 0;
-  std::unique_ptr<views::View> view =
-      views::Builder<views::View>()
-          .CustomConfigure(
-              base::BindOnce([](int* callback_count,
-                                views::View* view) { ++(*callback_count); },
-                             &callback_count))
-          .CustomConfigure(
-              base::BindOnce([](int* callback_count,
-                                views::View* view) { ++(*callback_count); },
-                             &callback_count))
-          .Build();
-  // Make sure both callbacks have been called.
-  EXPECT_EQ(callback_count, 2);
+TEST_F(ViewFactoryTest, TestOrderOfOperations) {
+  using ViewCallback = base::OnceCallback<void(views::View*)>;
+
+  views::View* view = nullptr;
+  base::MockCallback<ViewCallback> custom_configure_callback;
+  base::MockCallback<ViewCallback> after_build_callback;
+
+  EXPECT_CALL(custom_configure_callback, Run).Times(0);
+  EXPECT_CALL(after_build_callback, Run).Times(0);
+
+  views::Builder<views::View> builder;
+  builder.CopyAddressTo(&view)
+      .SetID(1)
+      .AddChild(views::Builder<views::View>())
+      .CustomConfigure(custom_configure_callback.Get())
+      .CustomConfigure(custom_configure_callback.Get())
+      .AfterBuild(after_build_callback.Get())
+      .AfterBuild(after_build_callback.Get());
+
+  // Addresses should be copied *before* build but properties shouldn't be set,
+  // children shouldn't be added, and callbacks shouldn't be run until *after*.
+  ASSERT_NE(view, nullptr);
+  EXPECT_EQ(view->GetID(), 0);
+  EXPECT_EQ(view->children().size(), 0u);
+  testing::Mock::VerifyAndClearExpectations(&custom_configure_callback);
+  testing::Mock::VerifyAndClearExpectations(&after_build_callback);
+
+  {
+    testing::InSequence sequence;
+
+    // Expect that two custom configure callbacks will be run *before* any
+    // after build callbacks. The order of the custom configure callbacks is
+    // not guaranteed by the builder.
+    EXPECT_CALL(custom_configure_callback, Run(testing::Pointer(view)))
+        .Times(2)
+        .WillRepeatedly(testing::Invoke([](views::View* view) {
+          // Properties should be set *before* but children shouldn't be added
+          // until *after* custom callbacks are run.
+          EXPECT_EQ(view->GetID(), 1);
+          EXPECT_EQ(view->children().size(), 0u);
+        }));
+
+    // Expect that two after build callbacks will be run *after* any custom
+    // configure callbacks. The order of the after build callbacks is not
+    // guaranteed by the builder.
+    EXPECT_CALL(after_build_callback, Run(testing::Pointer(view)))
+        .Times(2)
+        .WillRepeatedly(testing::Invoke([](views::View* view) {
+          // Children should be added *before* after build callbacks are run.
+          EXPECT_EQ(view->children().size(), 1u);
+        }));
+  }
+
+  // Build the view and verify order of operations.
+  std::ignore = std::move(builder).Build();
 }
diff --git a/ui/webui/resources/cr_components/most_visited/most_visited.ts b/ui/webui/resources/cr_components/most_visited/most_visited.ts
index 85504d2..5f3bd0e 100644
--- a/ui/webui/resources/cr_components/most_visited/most_visited.ts
+++ b/ui/webui/resources/cr_components/most_visited/most_visited.ts
@@ -32,12 +32,6 @@
 import {MostVisitedInfo, MostVisitedPageCallbackRouter, MostVisitedPageHandlerRemote, MostVisitedTheme, MostVisitedTile} from './most_visited.mojom-webui.js';
 import {MostVisitedWindowProxy} from './window_proxy.js';
 
-enum ScreenWidth {
-  NARROW = 0,
-  MEDIUM = 1,
-  WIDE = 2,
-}
-
 function resetTilePosition(tile: HTMLElement) {
   tile.style.position = '';
   tile.style.left = '';
@@ -98,6 +92,16 @@
       theme: Object,
 
       /**
+       * If true, renders MV tiles in a single row up to 10 columns wide.
+       * If false, renders MV tiles in up to 2 rows up to 5 columns wide.
+       */
+      singleRow: {
+        type: Boolean,
+        value: false,
+        observer: 'onSingleRowChange_',
+      },
+
+      /**
        * When the tile icon background is dark, the icon color is white for
        * contrast. This can be used to determine the color of the tile hover as
        * well.
@@ -119,12 +123,13 @@
 
       columnCount_: {
         type: Number,
-        computed: `computeColumnCount_(tiles_, screenWidth_, maxTiles_)`,
+        computed:
+            `computeColumnCount_(singleRow, tiles_, maxVisibleColumnCount_, maxTiles_)`,
       },
 
       rowCount_: {
         type: Number,
-        computed: 'computeRowCount_(columnCount_, tiles_)',
+        computed: 'computeRowCount_(singleRow, columnCount_, tiles_)',
       },
 
       customLinksEnabled_: {
@@ -198,7 +203,7 @@
 
       showToastButtons_: Boolean,
 
-      screenWidth_: Number,
+      maxVisibleColumnCount_: Number,
 
       tiles_: Array,
 
@@ -211,7 +216,8 @@
     };
   }
 
-  private theme: MostVisitedTheme|null;
+  public theme: MostVisitedTheme|null;
+  public singleRow: boolean;
   private useWhiteTileIcon_: boolean;
   private useTitlePill_: boolean;
   private columnCount_: number;
@@ -230,7 +236,7 @@
   private maxVisibleTiles_: number;
   private showAdd_: boolean;
   private showToastButtons_: boolean;
-  private screenWidth_: ScreenWidth;
+  private maxVisibleColumnCount_: number;
   private tiles_: MostVisitedTile[];
   private toastContent_: string;
   private visible_: boolean;
@@ -244,10 +250,8 @@
   private dragOffset_: {x: number, y: number}|null;
   private tileRects_: DOMRect[] = [];
   private isRtl_: boolean;
+  private mediaEventTracker_: EventTracker;
   private eventTracker_: EventTracker;
-  private boundOnWidthChange_: () => void;
-  private mediaListenerWideWidth_: MediaQueryList;
-  private mediaListenerMediumWidth_: MediaQueryList;
   private boundOnDocumentKeyDown_: (e: KeyboardEvent) => void;
 
   private get tileElements_() {
@@ -273,13 +277,17 @@
      * of the tile being dragged.
      */
     this.dragOffset_ = null;
+
+    this.mediaEventTracker_ = new EventTracker();
+    this.eventTracker_ = new EventTracker();
   }
 
   override connectedCallback() {
     super.connectedCallback();
 
     this.isRtl_ = window.getComputedStyle(this)['direction'] === 'rtl';
-    this.eventTracker_ = new EventTracker();
+
+    this.onSingleRowChange_();
 
     this.setMostVisitedInfoListenerId_ =
         this.callbackRouter_.setMostVisitedInfo.addListener(
@@ -305,25 +313,15 @@
 
   override disconnectedCallback() {
     super.disconnectedCallback();
-    assert(this.boundOnWidthChange_);
-    this.mediaListenerWideWidth_.removeListener(this.boundOnWidthChange_);
-    this.mediaListenerMediumWidth_.removeListener(this.boundOnWidthChange_);
+    this.mediaEventTracker_.removeAll();
+    this.eventTracker_.removeAll();
     this.ownerDocument.removeEventListener(
         'keydown', this.boundOnDocumentKeyDown_);
-    this.eventTracker_.removeAll();
   }
 
   override ready() {
     super.ready();
 
-    this.boundOnWidthChange_ = this.updateScreenWidth_.bind(this);
-    this.mediaListenerWideWidth_ =
-        this.windowProxy_.matchMedia('(min-width: 672px)');
-    this.mediaListenerWideWidth_.addListener(this.boundOnWidthChange_);
-    this.mediaListenerMediumWidth_ =
-        this.windowProxy_.matchMedia('(min-width: 560px)');
-    this.mediaListenerMediumWidth_.addListener(this.boundOnWidthChange_);
-    this.updateScreenWidth_();
     this.boundOnDocumentKeyDown_ = e => this.onDocumentKeyDown_(e);
     this.ownerDocument.addEventListener(
         'keydown', this.boundOnDocumentKeyDown_);
@@ -343,20 +341,15 @@
   }
 
   private computeColumnCount_(): number {
-    let maxColumns = 3;
-    if (this.screenWidth_ === ScreenWidth.WIDE) {
-      maxColumns = 5;
-    } else if (this.screenWidth_ === ScreenWidth.MEDIUM) {
-      maxColumns = 4;
-    }
-
     const shortcutCount = this.tiles_ ? this.tiles_.length : 0;
     const canShowAdd = this.maxTiles_ > shortcutCount;
     const tileCount =
         Math.min(this.maxTiles_, shortcutCount + (canShowAdd ? 1 : 0));
-    const columnCount = tileCount <= maxColumns ?
+    const columnCount = tileCount <= this.maxVisibleColumnCount_ ?
         tileCount :
-        Math.min(maxColumns, Math.ceil(tileCount / 2));
+        Math.min(
+            this.maxVisibleColumnCount_,
+            Math.ceil(tileCount / (this.singleRow ? 1 : 2)));
     return columnCount || 3;
   }
 
@@ -365,6 +358,10 @@
       return 0;
     }
 
+    if (this.singleRow) {
+      return 1;
+    }
+
     const shortcutCount = this.tiles_ ? this.tiles_.length : 0;
     return this.columnCount_ <= shortcutCount ? 2 : 1;
   }
@@ -569,6 +566,27 @@
     return index >= this.maxVisibleTiles_;
   }
 
+  private onSingleRowChange_() {
+    if (!this.isConnected) {
+      return;
+    }
+    this.mediaEventTracker_.removeAll();
+    const queryLists: MediaQueryList[] = [];
+    const updateCount = () => {
+      const index = queryLists.findIndex(listener => listener.matches);
+      this.maxVisibleColumnCount_ =
+          3 + (index > -1 ? queryLists.length - index : 0);
+    };
+    const maxColumnCount = this.singleRow ? 10 : 5;
+    for (let i = maxColumnCount; i >= 4; i--) {
+      const query = `(min-width: ${112 * (i + 1)}px)`;
+      const queryList = this.windowProxy_.matchMedia(query);
+      this.mediaEventTracker_.add(queryList, 'change', updateCount);
+      queryLists.push(queryList);
+    }
+    updateCount();
+  }
+
   private onAdd_() {
     this.dialogTitle_ = loadTimeData.getString('addLinkTitle');
     this.dialogTileTitle_ = '';
@@ -822,16 +840,6 @@
     this.tileFocus_(index);
   }
 
-  private updateScreenWidth_() {
-    if (this.mediaListenerWideWidth_.matches) {
-      this.screenWidth_ = ScreenWidth.WIDE;
-    } else if (this.mediaListenerMediumWidth_.matches) {
-      this.screenWidth_ = ScreenWidth.MEDIUM;
-    } else {
-      this.screenWidth_ = ScreenWidth.NARROW;
-    }
-  }
-
   private onTilesRendered_() {
     performance.measure('most-visited-rendered');
     assert(this.maxVisibleTiles_);
diff --git a/weblayer/browser/ad_tagging_browsertest.cc b/weblayer/browser/ad_tagging_browsertest.cc
index ed989ca..8fd3325 100644
--- a/weblayer/browser/ad_tagging_browsertest.cc
+++ b/weblayer/browser/ad_tagging_browsertest.cc
@@ -40,7 +40,7 @@
 };
 
 IN_PROC_BROWSER_TEST_F(AdTaggingBrowserTest,
-                       DISABLED_AdContentSettingAllowed_AdTaggingDisabled) {
+                       AdContentSettingAllowed_AdTaggingDisabled) {
   HostContentSettingsMapFactory::GetForBrowserContext(
       web_contents()->GetBrowserContext())
       ->SetDefaultContentSetting(ContentSettingsType::ADS,
diff --git a/weblayer/browser/background_sync/background_sync_browsertest.cc b/weblayer/browser/background_sync/background_sync_browsertest.cc
index 3ca48fd..64196e43 100644
--- a/weblayer/browser/background_sync/background_sync_browsertest.cc
+++ b/weblayer/browser/background_sync/background_sync_browsertest.cc
@@ -188,7 +188,7 @@
   EXPECT_FALSE(controller->IsOriginTracked(origin));
 }
 
-IN_PROC_BROWSER_TEST_F(BackgroundSyncBrowserTest, DISABLED_NormalProfile) {
+IN_PROC_BROWSER_TEST_F(BackgroundSyncBrowserTest, NormalProfile) {
   // TODO(crbug.com/1087486, 1091211): Make this use
   // BackgroundSyncController::ScheduleBrowserWakeup() once we support waking
   // the browser up.
@@ -212,7 +212,7 @@
 };
 
 IN_PROC_BROWSER_TEST_F(IncognitoBackgroundSyncBrowserTest,
-                       OffTheRecordProfile) {
+                       DISABLED_OffTheRecordProfile) {
   base::ScopedAllowBlockingForTesting allow_blocking;
 
   // TODO(crbug.com/1087486, 1091211): Make this use
diff --git a/weblayer/browser/clipboard_browsertest.cc b/weblayer/browser/clipboard_browsertest.cc
index fd24326..c55450e 100644
--- a/weblayer/browser/clipboard_browsertest.cc
+++ b/weblayer/browser/clipboard_browsertest.cc
@@ -71,7 +71,7 @@
   std::unique_ptr<content::ScopedPageFocusOverride> scoped_focus_;
 };
 
-IN_PROC_BROWSER_TEST_F(ClipboardBrowserTest, DISABLED_ReadTextSuccess) {
+IN_PROC_BROWSER_TEST_F(ClipboardBrowserTest, ReadTextSuccess) {
   prompt_factory()->set_response_type(
       permissions::PermissionRequestManager::ACCEPT_ALL);
   ExecuteScriptWithUserGesture(shell()->tab(), "tryClipboardReadText()");
@@ -91,8 +91,7 @@
   EXPECT_EQ(0, prompt_factory()->TotalRequestCount());
 }
 
-IN_PROC_BROWSER_TEST_F(ClipboardBrowserTest,
-                       DISABLED_ReadTextWithoutPermission) {
+IN_PROC_BROWSER_TEST_F(ClipboardBrowserTest, ReadTextWithoutPermission) {
   prompt_factory()->set_response_type(
       permissions::PermissionRequestManager::DENY_ALL);
   ExecuteScriptWithUserGesture(shell()->tab(), "tryClipboardReadText()");
diff --git a/weblayer/browser/download_browsertest.cc b/weblayer/browser/download_browsertest.cc
index e708236..669eaf26 100644
--- a/weblayer/browser/download_browsertest.cc
+++ b/weblayer/browser/download_browsertest.cc
@@ -197,7 +197,7 @@
   EXPECT_EQ(download_dropped_count(), 1);
 }
 
-IN_PROC_BROWSER_TEST_F(DownloadBrowserTest, DISABLED_Basic) {
+IN_PROC_BROWSER_TEST_F(DownloadBrowserTest, Basic) {
   GURL url(embedded_test_server()->GetURL("/content-disposition.html"));
 
   shell()->tab()->GetNavigationController()->Navigate(url);
@@ -231,13 +231,7 @@
             download_location().DirName());
 }
 
-// Test consistently failing on android: crbug.com/1273105
-#if BUILDFLAG(IS_ANDROID)
-#define MAYBE_OverrideDownloadDirectory DISABLED_OverrideDownloadDirectory
-#else
-#define MAYBE_OverrideDownloadDirectory OverrideDownloadDirectory
-#endif
-IN_PROC_BROWSER_TEST_F(DownloadBrowserTest, MAYBE_OverrideDownloadDirectory) {
+IN_PROC_BROWSER_TEST_F(DownloadBrowserTest, OverrideDownloadDirectory) {
   base::ScopedAllowBlockingForTesting allow_blocking;
   base::ScopedTempDir download_dir;
   ASSERT_TRUE(download_dir.CreateUniqueTempDir());
@@ -287,7 +281,7 @@
   EXPECT_EQ(download_state(), DownloadError::kCancelled);
 }
 
-IN_PROC_BROWSER_TEST_F(DownloadBrowserTest, DISABLED_PauseResume) {
+IN_PROC_BROWSER_TEST_F(DownloadBrowserTest, PauseResume) {
   // Add an initial navigation to avoid the tab being deleted if the first
   // navigation is a download, since we use the tab for convenience in the
   // lambda.
@@ -322,7 +316,7 @@
   EXPECT_EQ(download_dropped_count(), 0);
 }
 
-IN_PROC_BROWSER_TEST_F(DownloadBrowserTest, DISABLED_NetworkError) {
+IN_PROC_BROWSER_TEST_F(DownloadBrowserTest, NetworkError) {
   set_failed_callback(base::BindLambdaForTesting([](Download* download) {
     CHECK_EQ(download->GetState(), DownloadState::kFailed);
   }));
diff --git a/weblayer/browser/favicon/favicon_fetcher_browsertest.cc b/weblayer/browser/favicon/favicon_fetcher_browsertest.cc
index ce031c0..3e60674 100644
--- a/weblayer/browser/favicon/favicon_fetcher_browsertest.cc
+++ b/weblayer/browser/favicon/favicon_fetcher_browsertest.cc
@@ -84,8 +84,7 @@
   EXPECT_EQ(2, fetcher_delegate.on_favicon_changed_call_count());
 }
 
-IN_PROC_BROWSER_TEST_F(FaviconFetcherBrowserTest,
-                       DISABLED_NavigateToPageWithNoFavicon) {
+IN_PROC_BROWSER_TEST_F(FaviconFetcherBrowserTest, NavigateToPageWithNoFavicon) {
   ASSERT_TRUE(embedded_test_server()->Start());
   TestFaviconFetcherDelegate fetcher_delegate;
   auto fetcher = shell()->tab()->CreateFaviconFetcher(&fetcher_delegate);
diff --git a/weblayer/browser/navigation_browsertest.cc b/weblayer/browser/navigation_browsertest.cc
index 2f32bdb..e3d50d1 100644
--- a/weblayer/browser/navigation_browsertest.cc
+++ b/weblayer/browser/navigation_browsertest.cc
@@ -944,8 +944,7 @@
 
 // This test verifies the embedder can replace the X-Client-Data header that
 // is also set by //components/variations.
-IN_PROC_BROWSER_TEST_F(NavigationBrowserTest2,
-                       DISABLED_ReplaceXClientDataHeader) {
+IN_PROC_BROWSER_TEST_F(NavigationBrowserTest2, ReplaceXClientDataHeader) {
   std::unique_ptr<base::RunLoop> run_loop = std::make_unique<base::RunLoop>();
   std::string last_header_value;
   auto main_task_runner = base::SequencedTaskRunner::GetCurrentDefault();
diff --git a/weblayer/browser/no_state_prefetch/no_state_prefetch_browsertest.cc b/weblayer/browser/no_state_prefetch/no_state_prefetch_browsertest.cc
index cc9cdbb..81f91b7 100644
--- a/weblayer/browser/no_state_prefetch/no_state_prefetch_browsertest.cc
+++ b/weblayer/browser/no_state_prefetch/no_state_prefetch_browsertest.cc
@@ -131,14 +131,22 @@
 
 // Test that adding a link-rel prerender tag causes a fetch.
 IN_PROC_BROWSER_TEST_F(NoStatePrefetchBrowserTest,
-                       DISABLED_LinkRelPrerenderPageFetched) {
+                       LinkRelPrerenderPageFetched) {
   NavigateAndWaitForCompletion(GURL(https_server_->GetURL("/parent_page.html")),
                                shell());
   prerendered_page_fetched_->Run();
 }
 
+// Test that only render blocking resources are loaded during NoStatePrefetch.
+// TODO(https://crbug.com/1144282): Fix failures on Asan.
+#if defined(ADDRESS_SANITIZER)
+#define MAYBE_NSPLoadsRenderBlockingResource \
+  DISABLED_NSPLoadsRenderBlockingResource
+#else
+#define MAYBE_NSPLoadsRenderBlockingResource NSPLoadsRenderBlockingResource
+#endif
 IN_PROC_BROWSER_TEST_F(NoStatePrefetchBrowserTest,
-                       DISABLED_NSPLoadsRenderBlockingResource) {
+                       MAYBE_NSPLoadsRenderBlockingResource) {
   NavigateAndWaitForCompletion(GURL(https_server_->GetURL("/parent_page.html")),
                                shell());
   script_resource_fetched_->Run();
@@ -146,8 +154,16 @@
   EXPECT_FALSE(script_executed_);
 }
 
+// Test that navigating to a no-state-prefetched page executes JS and reuses
+// prerendered resources.
+// TODO(https://crbug.com/1144282): Fix failures on Asan.
+#if defined(ADDRESS_SANITIZER)
+#define MAYBE_NavigateToPrerenderedPage DISABLED_NavigateToPrerenderedPage
+#else
+#define MAYBE_NavigateToPrerenderedPage NavigateToPrerenderedPage
+#endif
 IN_PROC_BROWSER_TEST_F(NoStatePrefetchBrowserTest,
-                       DISABLED_NavigateToPrerenderedPage) {
+                       MAYBE_NavigateToPrerenderedPage) {
   NavigateAndWaitForCompletion(GURL(https_server_->GetURL("/parent_page.html")),
                                shell());
   script_resource_fetched_->Run();
@@ -189,7 +205,7 @@
 
 // link-rel="prerender" happens even when NoStatePrefetch has been disabled.
 IN_PROC_BROWSER_TEST_F(NoStatePrefetchBrowserTest,
-                       DISABLED_LinkRelPrerenderWithNSPDisabled) {
+                       LinkRelPrerenderWithNSPDisabled) {
   GetProfile()->SetBooleanSetting(SettingType::NETWORK_PREDICTION_ENABLED,
                                   false);
   NavigateAndWaitForCompletion(GURL(https_server_->GetURL("/parent_page.html")),
@@ -211,7 +227,7 @@
 #if defined(ADDRESS_SANITIZER)
 #define MAYBE_ExternalPrerender DISABLED_ExternalPrerender
 #else
-#define MAYBE_ExternalPrerender DISABLED_ExternalPrerender
+#define MAYBE_ExternalPrerender ExternalPrerender
 #endif
 IN_PROC_BROWSER_TEST_F(NoStatePrefetchBrowserTest, MAYBE_ExternalPrerender) {
   GetProfile()->GetPrerenderController()->Prerender(
diff --git a/weblayer/browser/popup_blocker_browsertest.cc b/weblayer/browser/popup_blocker_browsertest.cc
index 62efe451..56db082 100644
--- a/weblayer/browser/popup_blocker_browsertest.cc
+++ b/weblayer/browser/popup_blocker_browsertest.cc
@@ -32,10 +32,7 @@
   void SetUpOnMainThread() override {
     ASSERT_TRUE(embedded_test_server()->Start());
     original_tab_ = shell()->tab();
-#if !BUILDFLAG(IS_ANDROID)
-    // Android does this in Java.
     original_tab_->SetNewTabDelegate(this);
-#endif
     shell()->browser()->AddObserver(this);
 
     NavigateAndWaitForCompletion(embedded_test_server()->GetURL("/echo"),
@@ -130,19 +127,18 @@
   raw_ptr<Tab> new_tab_ = nullptr;
 };
 
-IN_PROC_BROWSER_TEST_F(PopupBlockerBrowserTest, DISABLED_BlocksPopup) {
+IN_PROC_BROWSER_TEST_F(PopupBlockerBrowserTest, BlocksPopup) {
   ExecuteScript(original_tab(), "window.open('https://google.com')", true);
   EXPECT_EQ(GetBlockedPopupCount(), 1u);
 }
 
-IN_PROC_BROWSER_TEST_F(PopupBlockerBrowserTest, DISABLED_BlocksMultiplePopups) {
+IN_PROC_BROWSER_TEST_F(PopupBlockerBrowserTest, BlocksMultiplePopups) {
   ExecuteScript(original_tab(), "window.open('https://google.com')", true);
   ExecuteScript(original_tab(), "window.open('https://google.com')", true);
   EXPECT_EQ(GetBlockedPopupCount(), 2u);
 }
 
-IN_PROC_BROWSER_TEST_F(PopupBlockerBrowserTest,
-                       DISABLED_DoesNotBlockUserGesture) {
+IN_PROC_BROWSER_TEST_F(PopupBlockerBrowserTest, DoesNotBlockUserGesture) {
   GURL popup_url = embedded_test_server()->GetURL("/echo?popup");
   ExecuteScriptWithUserGesture(
       original_tab(),
@@ -153,7 +149,7 @@
   EXPECT_EQ(GetBlockedPopupCount(), 0u);
 }
 
-IN_PROC_BROWSER_TEST_F(PopupBlockerBrowserTest, DISABLED_OpensBlockedPopup) {
+IN_PROC_BROWSER_TEST_F(PopupBlockerBrowserTest, OpensBlockedPopup) {
   GURL popup_url = embedded_test_server()->GetURL("/echo?popup");
   ExecuteScript(
       original_tab(),
@@ -171,7 +167,7 @@
 }
 
 IN_PROC_BROWSER_TEST_F(PopupBlockerBrowserTest,
-                       DISABLED_AllowsPopupThroughContentSettingException) {
+                       AllowsPopupThroughContentSettingException) {
   GURL popup_url = embedded_test_server()->GetURL("/echo?popup");
   HostContentSettingsMapFactory::GetForBrowserContext(
       GetWebContents(original_tab())->GetBrowserContext())
@@ -187,7 +183,7 @@
 }
 
 IN_PROC_BROWSER_TEST_F(PopupBlockerBrowserTest,
-                       DISABLED_AllowsPopupThroughContentSettingDefaultValue) {
+                       AllowsPopupThroughContentSettingDefaultValue) {
   GURL popup_url = embedded_test_server()->GetURL("/echo?popup");
   HostContentSettingsMapFactory::GetForBrowserContext(
       GetWebContents(original_tab())->GetBrowserContext())
@@ -202,7 +198,7 @@
 }
 
 IN_PROC_BROWSER_TEST_F(PopupBlockerBrowserTest,
-                       DISABLED_BlockPopupThroughContentSettingException) {
+                       BlockPopupThroughContentSettingException) {
   GURL popup_url = embedded_test_server()->GetURL("/echo?popup");
   HostContentSettingsMapFactory::GetForBrowserContext(
       GetWebContents(original_tab())->GetBrowserContext())
@@ -220,7 +216,7 @@
 }
 
 IN_PROC_BROWSER_TEST_F(PopupBlockerBrowserTest,
-                       DISABLED_BlocksAndOpensPopupFromOpenURL) {
+                       BlocksAndOpensPopupFromOpenURL) {
   GURL popup_url = embedded_test_server()->GetURL("/echo?popup");
   content::OpenURLParams params(popup_url, content::Referrer(),
                                 WindowOpenDisposition::NEW_FOREGROUND_TAB,
diff --git a/weblayer/browser/profile_browsertest.cc b/weblayer/browser/profile_browsertest.cc
index 08e6dfc1..091f53f 100644
--- a/weblayer/browser/profile_browsertest.cc
+++ b/weblayer/browser/profile_browsertest.cc
@@ -47,8 +47,7 @@
 
 #endif  // !BUILDFLAG(IS_ANDROID)
 
-IN_PROC_BROWSER_TEST_F(ProfileBrowserTest,
-                       DISABLED_GetCachedFaviconForPageUrl) {
+IN_PROC_BROWSER_TEST_F(ProfileBrowserTest, GetCachedFaviconForPageUrl) {
   // Navigation to a page with a favicon.
   ASSERT_TRUE(embedded_test_server()->Start());
   TestFaviconFetcherDelegate fetcher_delegate;
@@ -76,8 +75,7 @@
   run_loop.Run();
 }
 
-IN_PROC_BROWSER_TEST_F(ProfileBrowserTest,
-                       DISABLED_ClearBrowsingDataDeletesFavicons) {
+IN_PROC_BROWSER_TEST_F(ProfileBrowserTest, ClearBrowsingDataDeletesFavicons) {
   // Navigate to a page with a favicon.
   ASSERT_TRUE(embedded_test_server()->Start());
   TestFaviconFetcherDelegate fetcher_delegate;
diff --git a/weblayer/browser/reduce_accept_language_service_browsertest.cc b/weblayer/browser/reduce_accept_language_service_browsertest.cc
index 0891cc5..83fe91b 100644
--- a/weblayer/browser/reduce_accept_language_service_browsertest.cc
+++ b/weblayer/browser/reduce_accept_language_service_browsertest.cc
@@ -57,8 +57,7 @@
   std::unique_ptr<ReduceAcceptLanguageServiceTester> service_tester_;
 };
 
-IN_PROC_BROWSER_TEST_F(ReduceAcceptLanguageServiceTest,
-                       DISABLED_GetAcceptLanguageList) {
+IN_PROC_BROWSER_TEST_F(ReduceAcceptLanguageServiceTest, GetAcceptLanguageList) {
   tester()->VerifyFetchAcceptLanguageList({"en", "ja", "it"});
   reduce_accept_language::ReduceAcceptLanguageService incognito_service(
       settings_map(), prefs(), true);
diff --git a/weblayer/browser/safe_browsing/safe_browsing_browsertest.cc b/weblayer/browser/safe_browsing/safe_browsing_browsertest.cc
index 3abd90e4..f92d624d 100644
--- a/weblayer/browser/safe_browsing/safe_browsing_browsertest.cc
+++ b/weblayer/browser/safe_browsing/safe_browsing_browsertest.cc
@@ -320,18 +320,15 @@
   NavigateWithThreatType(safe_browsing::SB_THREAT_TYPE_SAFE, false);
 }
 
-IN_PROC_BROWSER_TEST_F(SafeBrowsingBrowserTest,
-                       DISABLED_ShowsInterstitial_Malware) {
+IN_PROC_BROWSER_TEST_F(SafeBrowsingBrowserTest, ShowsInterstitial_Malware) {
   NavigateWithThreatType(safe_browsing::SB_THREAT_TYPE_URL_MALWARE, true);
 }
 
-IN_PROC_BROWSER_TEST_F(SafeBrowsingBrowserTest,
-                       DISABLED_ShowsInterstitial_Phishing) {
+IN_PROC_BROWSER_TEST_F(SafeBrowsingBrowserTest, ShowsInterstitial_Phishing) {
   NavigateWithThreatType(safe_browsing::SB_THREAT_TYPE_URL_PHISHING, true);
 }
 
-IN_PROC_BROWSER_TEST_F(SafeBrowsingBrowserTest,
-                       DISABLED_CheckNavigationErrorType) {
+IN_PROC_BROWSER_TEST_F(SafeBrowsingBrowserTest, CheckNavigationErrorType) {
   auto threat_types = {
       safe_browsing::SB_THREAT_TYPE_URL_PHISHING,
       safe_browsing::SB_THREAT_TYPE_URL_MALWARE,
@@ -352,18 +349,16 @@
 
 // Tests below are disabled due to failures on Android.
 // See crbug.com/1340200.
-IN_PROC_BROWSER_TEST_F(SafeBrowsingBrowserTest,
-                       DISABLED_ShowsInterstitial_Unwanted) {
+IN_PROC_BROWSER_TEST_F(SafeBrowsingBrowserTest, ShowsInterstitial_Unwanted) {
   NavigateWithThreatType(safe_browsing::SB_THREAT_TYPE_URL_UNWANTED, true);
 }
 
-IN_PROC_BROWSER_TEST_F(SafeBrowsingBrowserTest,
-                       DISABLED_ShowsInterstitial_Billing) {
+IN_PROC_BROWSER_TEST_F(SafeBrowsingBrowserTest, ShowsInterstitial_Billing) {
   NavigateWithThreatType(safe_browsing::SB_THREAT_TYPE_BILLING, true);
 }
 
 IN_PROC_BROWSER_TEST_F(SafeBrowsingBrowserTest,
-                       DISABLED_ShowsInterstitial_Malware_Subresource) {
+                       ShowsInterstitial_Malware_Subresource) {
   NavigateWithSubResourceAndThreatType(
       safe_browsing::SB_THREAT_TYPE_URL_MALWARE, true);
 }
@@ -439,7 +434,7 @@
 }
 
 IN_PROC_BROWSER_TEST_F(SafeBrowsingBrowserTest,
-                       DISABLED_NoAccessTokenFetchInBasicSafeBrowsing) {
+                       NoAccessTokenFetchInBasicSafeBrowsing) {
   SetSafeBrowsingEnabled(true);
 
   GURL a_url(embedded_test_server()->GetURL("a.com", "/simple_page.html"));
@@ -448,9 +443,8 @@
   EXPECT_FALSE(access_token_fetch_delegate()->has_received_request());
 }
 
-IN_PROC_BROWSER_TEST_F(
-    SafeBrowsingBrowserTest,
-    DISABLED_NoAccessTokenFetchInRealTimeUrlLookupsUnlessEnabled) {
+IN_PROC_BROWSER_TEST_F(SafeBrowsingBrowserTest,
+                       NoAccessTokenFetchInRealTimeUrlLookupsUnlessEnabled) {
   SetRealTimeURLLookupsEnabled(true);
 
   GURL a_url(embedded_test_server()->GetURL("a.com", "/simple_page.html"));
@@ -476,7 +470,7 @@
 // completes due to Safe Browsing's timing out the access token fetch.
 IN_PROC_BROWSER_TEST_F(
     SafeBrowsingBrowserTest,
-    DISABLED_UnfulfilledAccessTokenFetchTimesOutAndNavigationCompletes) {
+    UnfulfilledAccessTokenFetchTimesOutAndNavigationCompletes) {
   SetRealTimeURLLookupsEnabled(true);
   EnableSafeBrowsingAccessTokenFetches();
   access_token_fetch_delegate()->set_should_respond_to_request(false);
diff --git a/weblayer/browser/safe_browsing/weblayer_ping_manager_factory_browsertest.cc b/weblayer/browser/safe_browsing/weblayer_ping_manager_factory_browsertest.cc
index 2ac6dd1b..b3f3b58 100644
--- a/weblayer/browser/safe_browsing/weblayer_ping_manager_factory_browsertest.cc
+++ b/weblayer/browser/safe_browsing/weblayer_ping_manager_factory_browsertest.cc
@@ -56,8 +56,7 @@
       expect_should_fetch);
 }
 
-IN_PROC_BROWSER_TEST_F(WeblayerPingManagerFactoryTest,
-                       DISABLED_ReportThreatDetails) {
+IN_PROC_BROWSER_TEST_F(WeblayerPingManagerFactoryTest, ReportThreatDetails) {
   auto* ping_manager = WebLayerPingManagerFactory::GetForBrowserContext(
       GetProfile()->GetBrowserContext());
 
@@ -87,26 +86,25 @@
             ReportThreatDetailsResult::SUCCESS);
 }
 IN_PROC_BROWSER_TEST_F(IncognitoModeWeblayerPingManagerFactoryTest,
-                       NoPingManagerForIncognito) {
+                       DISABLED_NoPingManagerForIncognito) {
   EXPECT_EQ(WebLayerPingManagerFactory::GetForBrowserContext(
                 GetProfile()->GetBrowserContext()),
             nullptr);
 }
 IN_PROC_BROWSER_TEST_F(WeblayerPingManagerFactoryTest,
-                       DISABLED_ShouldFetchAccessTokenForReport_Yes) {
+                       ShouldFetchAccessTokenForReport_Yes) {
   RunShouldFetchAccessTokenForReportTest(/*is_enhanced_protection=*/true,
                                          /*is_signed_in=*/true,
                                          /*expect_should_fetch=*/true);
 }
-IN_PROC_BROWSER_TEST_F(
-    WeblayerPingManagerFactoryTest,
-    DISABLED_ShouldFetchAccessTokenForReport_NotEnhancedProtection) {
+IN_PROC_BROWSER_TEST_F(WeblayerPingManagerFactoryTest,
+                       ShouldFetchAccessTokenForReport_NotEnhancedProtection) {
   RunShouldFetchAccessTokenForReportTest(/*is_enhanced_protection=*/false,
                                          /*is_signed_in=*/true,
                                          /*expect_should_fetch=*/false);
 }
 IN_PROC_BROWSER_TEST_F(WeblayerPingManagerFactoryTest,
-                       DISABLED_ShouldFetchAccessTokenForReport_NotSignedIn) {
+                       ShouldFetchAccessTokenForReport_NotSignedIn) {
   RunShouldFetchAccessTokenForReportTest(/*is_enhanced_protection=*/true,
                                          /*is_signed_in=*/false,
                                          /*expect_should_fetch=*/false);
diff --git a/weblayer/browser/site_isolation_browsertest.cc b/weblayer/browser/site_isolation_browsertest.cc
index d444a6b..bf4334c 100644
--- a/weblayer/browser/site_isolation_browsertest.cc
+++ b/weblayer/browser/site_isolation_browsertest.cc
@@ -109,16 +109,8 @@
   base::test::ScopedFeatureList feature_list_;
 };
 
-// Failing on Android, see https://crbug.com/1254509.
-#if defined(ANDROID)
-#define MAYBE_SiteIsIsolatedAfterEnteringPassword \
-  DISABLED_SiteIsIsolatedAfterEnteringPassword
-#else
-#define MAYBE_SiteIsIsolatedAfterEnteringPassword \
-  SiteIsIsolatedAfterEnteringPassword
-#endif
 IN_PROC_BROWSER_TEST_F(SiteIsolationBrowserTest,
-                       MAYBE_SiteIsIsolatedAfterEnteringPassword) {
+                       SiteIsIsolatedAfterEnteringPassword) {
   GURL url = embedded_test_server()->GetURL("sub.foo.com",
                                             "/simple_password_form.html");
   NavigateAndWaitForCompletion(url, shell());
@@ -218,8 +210,7 @@
 }
 #endif
 
-IN_PROC_BROWSER_TEST_F(SiteIsolationBrowserTest,
-                       DISABLED_IsolatedSiteIsSavedOnlyOnce) {
+IN_PROC_BROWSER_TEST_F(SiteIsolationBrowserTest, IsolatedSiteIsSavedOnlyOnce) {
   GURL saved_url =
       embedded_test_server()->GetURL("saved.com", "/simple_page.html");
   StartIsolatingSite(saved_url);
@@ -232,7 +223,7 @@
 // Failing on Android, see https://crbug.com/1254509.
 #if defined(ANDROID)
 #define MAYBE_ClearSiteDataHeaderDoesNotClearSavedIsolatedSites \
-  DISABLED_ClearSiteDataHeaderDoesNotClearSavedIsolatedSites
+  ClearSiteDataHeaderDoesNotClearSavedIsolatedSites
 #else
 #define MAYBE_ClearSiteDataHeaderDoesNotClearSavedIsolatedSites \
   ClearSiteDataHeaderDoesNotClearSavedIsolatedSites
@@ -262,9 +253,8 @@
               UnorderedElementsAre("https://saved.com"));
 }
 
-IN_PROC_BROWSER_TEST_F(
-    SiteIsolationBrowserTest,
-    DISABLED_ExplicitClearBrowsingDataClearsSavedIsolatedSites) {
+IN_PROC_BROWSER_TEST_F(SiteIsolationBrowserTest,
+                       ExplicitClearBrowsingDataClearsSavedIsolatedSites) {
   GURL saved_url =
       embedded_test_server()->GetURL("saved.com", "/simple_page.html");
   StartIsolatingSite(saved_url);
diff --git a/weblayer/browser/ssl_browsertest.cc b/weblayer/browser/ssl_browsertest.cc
index d88ef447..31caa04 100644
--- a/weblayer/browser/ssl_browsertest.cc
+++ b/weblayer/browser/ssl_browsertest.cc
@@ -21,6 +21,7 @@
 #include "weblayer/public/browser_observer.h"
 #include "weblayer/public/error_page.h"
 #include "weblayer/public/error_page_delegate.h"
+#include "weblayer/public/new_tab_delegate.h"
 #include "weblayer/public/tab.h"
 #include "weblayer/shell/browser/shell.h"
 #include "weblayer/test/interstitial_utils.h"
@@ -33,15 +34,13 @@
 
 #if BUILDFLAG(IS_ANDROID)
 // Waits for a new tab to be created, and then load |url|.
-class NewTabWaiter : public BrowserObserver {
+class NewTabWaiter : public NewTabDelegate {
  public:
-  NewTabWaiter(Browser* browser, const GURL& url) : url_(url) {
-    observation_.Observe(browser);
-  }
+  explicit NewTabWaiter(const GURL& url) : url_(url) {}
 
-  void OnTabAdded(Tab* tab) override {
+  void OnNewTab(Tab* new_tab, NewTabType type) override {
     navigation_observer_ = std::make_unique<TestNavigationObserver>(
-        url_, TestNavigationObserver::NavigationEvent::kStart, tab);
+        url_, TestNavigationObserver::NavigationEvent::kStart, new_tab);
     run_loop_.Quit();
   }
 
@@ -55,7 +54,6 @@
   GURL url_;
   std::unique_ptr<TestNavigationObserver> navigation_observer_;
   base::RunLoop run_loop_;
-  base::ScopedObservation<Browser, BrowserObserver> observation_{this};
 };
 #endif
 
@@ -228,9 +226,9 @@
 
     // Note: The embedded test server cannot actually load the captive portal
     // login URL, so simply detect the start of the navigation to the page.
-    NewTabWaiter waiter(shell()->browser(),
-                        WebLayerSecurityBlockingPageFactory::
+    NewTabWaiter waiter(WebLayerSecurityBlockingPageFactory::
                             GetCaptivePortalLoginPageUrlForTesting());
+    shell()->tab()->SetNewTabDelegate(&waiter);
     ExecuteScript(shell(), "window.certificateErrorPageController.openLogin();",
                   false /*use_separate_isolate*/);
     waiter.Wait();
@@ -353,8 +351,7 @@
 #if BUILDFLAG(IS_ANDROID)
 // Tests that after reaching a captive portal interstitial, clicking on the
 // connect link will cause a navigation to the login page.
-IN_PROC_BROWSER_TEST_F(SSLBrowserTest,
-                       DISABLED_CaptivePortalConnectToLoginPage) {
+IN_PROC_BROWSER_TEST_F(SSLBrowserTest, CaptivePortalConnectToLoginPage) {
   SSLErrorHandler::SetOSReportsCaptivePortalForTesting(true);
 
   NavigateToPageWithMismatchedCertExpectCaptivePortalInterstitial();
diff --git a/weblayer/browser/subresource_filter_browsertest.cc b/weblayer/browser/subresource_filter_browsertest.cc
index d2cf240f..b8dce79 100644
--- a/weblayer/browser/subresource_filter_browsertest.cc
+++ b/weblayer/browser/subresource_filter_browsertest.cc
@@ -279,7 +279,7 @@
 }
 
 IN_PROC_BROWSER_TEST_F(SubresourceFilterBrowserTest,
-                       DISABLED_ContentSettingsAllowlistGlobal_DoNotActivate) {
+                       ContentSettingsAllowlistGlobal_DoNotActivate) {
   auto* web_contents = static_cast<TabImpl*>(shell()->tab())->web_contents();
 
   GURL test_url(embedded_test_server()->GetURL(
diff --git a/weblayer/browser/translate_browsertest.cc b/weblayer/browser/translate_browsertest.cc
index 51f1875..f140445 100644
--- a/weblayer/browser/translate_browsertest.cc
+++ b/weblayer/browser/translate_browsertest.cc
@@ -3,8 +3,12 @@
 // found in the LICENSE file.
 
 #include "build/build_config.h"
+#include "components/infobars/android/infobar_android.h"  // nogncheck
+#include "components/infobars/content/content_infobar_manager.h"
+#include "components/infobars/core/infobar_manager.h"  // nogncheck
 #include "components/translate/content/browser/translate_waiter.h"
 #include "components/translate/core/browser/language_state.h"
+#include "components/translate/core/browser/translate_download_manager.h"
 #include "components/translate/core/browser/translate_error_details.h"
 #include "components/translate/core/browser/translate_manager.h"
 #include "components/translate/core/common/language_detection_details.h"
@@ -17,22 +21,15 @@
 #include "weblayer/browser/profile_impl.h"
 #include "weblayer/browser/tab_impl.h"
 #include "weblayer/browser/translate_client_impl.h"
+#include "weblayer/browser/translate_compact_infobar.h"
 #include "weblayer/public/navigation_controller.h"
 #include "weblayer/public/tab.h"
+#include "weblayer/shell/android/browsertests_apk/translate_test_bridge.h"
 #include "weblayer/shell/browser/shell.h"
 #include "weblayer/test/test_navigation_observer.h"
 #include "weblayer/test/weblayer_browser_test.h"
 #include "weblayer/test/weblayer_browser_test_utils.h"
 
-#if BUILDFLAG(IS_ANDROID)
-#include "components/infobars/android/infobar_android.h"  // nogncheck
-#include "components/infobars/content/content_infobar_manager.h"
-#include "components/infobars/core/infobar_manager.h"  // nogncheck
-#include "components/translate/core/browser/translate_download_manager.h"
-#include "weblayer/browser/translate_compact_infobar.h"
-#include "weblayer/shell/android/browsertests_apk/translate_test_bridge.h"
-#endif
-
 namespace weblayer {
 
 namespace {
@@ -103,7 +100,6 @@
 
 }  // namespace
 
-#if BUILDFLAG(IS_ANDROID)
 class TestInfoBarManagerObserver : public infobars::InfoBarManager::Observer {
  public:
   TestInfoBarManagerObserver() = default;
@@ -130,7 +126,6 @@
   base::OnceClosure on_infobar_added_callback_;
   base::OnceClosure on_infobar_removed_callback_;
 };
-#endif  // if BUILDFLAG(IS_ANDROID)
 
 class TranslateBrowserTest : public WebLayerBrowserTest {
  public:
@@ -298,7 +293,7 @@
 // Test that the translation infrastructure is set up properly when the user is
 // in incognito mode.
 IN_PROC_BROWSER_TEST_F(IncognitoTranslateBrowserTest,
-                       PageTranslationSuccess_IncognitoMode) {
+                       DISABLED_PageTranslationSuccess_IncognitoMode) {
   ASSERT_TRUE(GetProfile()->GetBrowserContext()->IsOffTheRecord());
 
   SetTranslateScript(kTestValidScript);
@@ -326,7 +321,7 @@
 }
 
 // Test if there was an error during translation.
-IN_PROC_BROWSER_TEST_F(TranslateBrowserTest, DISABLED_PageTranslationError) {
+IN_PROC_BROWSER_TEST_F(TranslateBrowserTest, PageTranslationError) {
   SetTranslateScript(kTestValidScript);
 
   TranslateClientImpl* translate_client = GetTranslateClient(shell());
@@ -356,7 +351,7 @@
 
 // Test if there was an error during translate library initialization.
 IN_PROC_BROWSER_TEST_F(TranslateBrowserTest,
-                       DISABLED_PageTranslationInitializationError) {
+                       PageTranslationInitializationError) {
   SetTranslateScript(kTestScriptInitializationError);
 
   TranslateClientImpl* translate_client = GetTranslateClient(shell());
@@ -383,8 +378,7 @@
 }
 
 // Test the checks translate lib never gets ready and throws timeout.
-IN_PROC_BROWSER_TEST_F(TranslateBrowserTest,
-                       DISABLED_PageTranslationTimeoutError) {
+IN_PROC_BROWSER_TEST_F(TranslateBrowserTest, PageTranslationTimeoutError) {
   SetTranslateScript(kTestScriptTimeout);
 
   TranslateClientImpl* translate_client = GetTranslateClient(shell());
@@ -411,7 +405,7 @@
 }
 
 // Test that autotranslation kicks in if configured via prefs.
-IN_PROC_BROWSER_TEST_F(TranslateBrowserTest, DISABLED_Autotranslation) {
+IN_PROC_BROWSER_TEST_F(TranslateBrowserTest, Autotranslation) {
   SetTranslateScript(kTestValidScript);
 
   TranslateClientImpl* translate_client = GetTranslateClient(shell());
@@ -436,7 +430,6 @@
   EXPECT_EQ("zh-CN", translate_client->GetLanguageState().current_language());
 }
 
-#if BUILDFLAG(IS_ANDROID)
 // Test that the translation infobar is presented when visiting a page with a
 // translation opportunity and removed when navigating away.
 IN_PROC_BROWSER_TEST_F(TranslateBrowserTest, TranslateInfoBarPresentation) {
@@ -481,9 +474,7 @@
   EXPECT_EQ(0u, infobar_manager->infobar_count());
   infobar_manager->RemoveObserver(&infobar_observer);
 }
-#endif
 
-#if BUILDFLAG(IS_ANDROID)
 // Test that the translation infobar is not presented when visiting a page with
 // a translation opportunity but where the page has specified that it should not
 // be translated.
@@ -511,9 +502,7 @@
   // were to be shown, this check would fail.
   EXPECT_EQ(0u, infobar_manager->infobar_count());
 }
-#endif
 
-#if BUILDFLAG(IS_ANDROID)
 // Test that the translation can be successfully initiated via infobar.
 IN_PROC_BROWSER_TEST_F(TranslateBrowserTest, TranslationViaInfoBar) {
   auto* web_contents = static_cast<TabImpl*>(shell()->tab())->web_contents();
@@ -575,9 +564,7 @@
 
   infobar_manager->RemoveObserver(&infobar_observer);
 }
-#endif
 
-#if BUILDFLAG(IS_ANDROID)
 // Test that translation occurs when a target language is set.
 IN_PROC_BROWSER_TEST_F(TranslateBrowserTest,
                        TranslationViaPredefinedTargetLanguage) {
@@ -618,9 +605,7 @@
 
   infobar_manager->RemoveObserver(&infobar_observer);
 }
-#endif
 
-#if BUILDFLAG(IS_ANDROID)
 // Test that the infobar appears on pages in the user's locale iff a target
 // language is set.
 IN_PROC_BROWSER_TEST_F(
@@ -662,9 +647,7 @@
 
   infobar_manager->RemoveObserver(&infobar_observer);
 }
-#endif
 
-#if BUILDFLAG(IS_ANDROID)
 // Test that when a predefined target language is set, the infobar does not
 // appear on pages in that language.
 IN_PROC_BROWSER_TEST_F(TranslateBrowserTest,
@@ -705,9 +688,7 @@
 
   infobar_manager->RemoveObserver(&infobar_observer);
 }
-#endif
 
-#if BUILDFLAG(IS_ANDROID)
 // Test that the translation infobar stays present when the "never translate
 // language" item is clicked. Note that this behavior is intentionally different
 // from that of Chrome, where the infobar is removed in this case and a snackbar
@@ -779,7 +760,6 @@
   infobar_manager->RemoveObserver(&infobar_observer);
 }
 
-#if BUILDFLAG(IS_ANDROID)
 // Test that the infobar shows when a predefined target language is set even if
 // the source language is in the "never translate" set.
 IN_PROC_BROWSER_TEST_F(TranslateBrowserTest,
@@ -832,7 +812,6 @@
 
   infobar_manager->RemoveObserver(&infobar_observer);
 }
-#endif
 
 // Test that the translation infobar stays present when the "never translate
 // site" item is clicked. Note that this behavior is intentionally different
@@ -896,12 +875,10 @@
   infobar_manager->RemoveObserver(&infobar_observer);
 }
 
-#if BUILDFLAG(IS_ANDROID)
-// Test that the infobar shows when a predefined target language is set even if
-// the site is in the "never translate" set.
-IN_PROC_BROWSER_TEST_F(
-    TranslateBrowserTest,
-    DISABLED_PredefinedTargetLanguageOverridesSiteBlocklist) {
+// Test that the infobar does not show when a predefined target language is set
+// and the user selects to never translate the site.
+IN_PROC_BROWSER_TEST_F(TranslateBrowserTest,
+                       PredefinedTargetLanguageDoesNotOverrideSiteBlocklist) {
   auto* tab = static_cast<TabImpl*>(shell()->tab());
   auto* web_contents = tab->web_contents();
   auto* infobar_manager =
@@ -933,37 +910,19 @@
   TranslateTestBridge::ClickOverflowMenuItem(
       infobar, TranslateTestBridge::OverflowMenuItemId::NEVER_TRANSLATE_SITE);
 
-  // Since a predefined target language is set, the infobar should still be
-  // shown on new navigations to this site even though the site is blocklisted.
   ResetLanguageDeterminationWaiter();
-  base::RunLoop run_loop2;
-  infobar_observer.set_on_infobar_added_callback(run_loop2.QuitClosure());
-
   NavigateAndWaitForCompletion(
       GURL(embedded_test_server()->GetURL("/french_page2.html")), shell());
   language_determination_waiter_->Wait();
   EXPECT_EQ("fr", translate_client->GetLanguageState().source_language());
 
-  run_loop2.Run();
-
-  EXPECT_EQ(1u, infobar_manager->infobar_count());
-
-  ResetLanguageDeterminationWaiter();
-  base::RunLoop run_loop3;
-  infobar_observer.set_on_infobar_added_callback(run_loop3.QuitClosure());
-
-  NavigateAndWaitForCompletion(
-      GURL(embedded_test_server()->GetURL("/german_page.html")), shell());
-  language_determination_waiter_->Wait();
-  EXPECT_EQ("de", translate_client->GetLanguageState().source_language());
-
-  run_loop3.Run();
-
-  EXPECT_EQ(1u, infobar_manager->infobar_count());
+  // NOTE: There is no notification to wait for for the event of the infobar not
+  // showing. However, in practice the infobar is added synchronously, so if it
+  // were to be shown, this check would fail.
+  EXPECT_EQ(0u, infobar_manager->infobar_count());
 
   infobar_manager->RemoveObserver(&infobar_observer);
 }
-#endif
 
 // Parameterized to run tests on the "never translate language" and "never
 // translate site" menu items.
@@ -1052,6 +1011,4 @@
         TranslateTestBridge::OverflowMenuItemId::NEVER_TRANSLATE_LANGUAGE,
         TranslateTestBridge::OverflowMenuItemId::NEVER_TRANSLATE_SITE));
 
-#endif  // BUILDFLAG(IS_ANDROID)
-
 }  // namespace weblayer
diff --git a/weblayer/browser/weblayer_variations_http_browsertest.cc b/weblayer/browser/weblayer_variations_http_browsertest.cc
index 825de23..0940f6c 100644
--- a/weblayer/browser/weblayer_variations_http_browsertest.cc
+++ b/weblayer/browser/weblayer_variations_http_browsertest.cc
@@ -145,7 +145,7 @@
 // Verify in an integration test that the variations header (X-Client-Data) is
 // attached to network requests to Google but stripped on redirects.
 IN_PROC_BROWSER_TEST_F(WebLayerVariationsHttpBrowserTest,
-                       DISABLED_TestStrippingHeadersFromResourceRequest) {
+                       TestStrippingHeadersFromResourceRequest) {
   OneShotNavigationObserver observer(shell());
   shell()->tab()->GetNavigationController()->Navigate(GetGoogleRedirectUrl1());
   observer.WaitForNavigation();
diff --git a/weblayer/public/javatestutil/org/chromium/weblayer/TestWebLayer.java b/weblayer/public/javatestutil/org/chromium/weblayer/TestWebLayer.java
index 31b6f7b..ece082db 100644
--- a/weblayer/public/javatestutil/org/chromium/weblayer/TestWebLayer.java
+++ b/weblayer/public/javatestutil/org/chromium/weblayer/TestWebLayer.java
@@ -141,13 +141,13 @@
         WebLayer.loadAsync(application, webLayer -> {
             Bundle args = new Bundle();
             args.putString(BrowserFragmentArgs.PROFILE_NAME, "browsertest");
-            args.putBoolean(BrowserFragmentArgs.IS_INCOGNITO, true);
+            args.putBoolean(BrowserFragmentArgs.IS_INCOGNITO, false);
 
             Browser browser = new Browser(webLayer.createBrowser(application, args));
-            browser.initializeState();
 
             WebFragmentEventHandler eventHandler =
                     new WebFragmentEventHandler(browser.connectFragment());
+            browser.initializeState();
             eventHandler.onAttach(application, null);
             eventHandler.onCreate();
             eventHandler.onStart();
diff --git a/weblayer/shell/android/browsertests_apk/AndroidManifest.xml b/weblayer/shell/android/browsertests_apk/AndroidManifest.xml
index 30f0b23..6ca4a95 100644
--- a/weblayer/shell/android/browsertests_apk/AndroidManifest.xml
+++ b/weblayer/shell/android/browsertests_apk/AndroidManifest.xml
@@ -12,6 +12,7 @@
 
     <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
     <uses-permission android:name="android.permission.CAMERA"/>
     <uses-permission android:name="android.permission.INTERNET"/>
     <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
diff --git a/weblayer/shell/android/support_apk/AndroidManifest.xml b/weblayer/shell/android/support_apk/AndroidManifest.xml
index bebb20c..bd4d5ad3 100644
--- a/weblayer/shell/android/support_apk/AndroidManifest.xml
+++ b/weblayer/shell/android/support_apk/AndroidManifest.xml
@@ -21,6 +21,7 @@
     <uses-permission android:name="android.permission.VIBRATE"/>
     <uses-permission android:name="android.permission.WAKE_LOCK"/>
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
 
     <application android:label="WebLayer support">
         <!-- AR (Augmented Reality) support. -->